fix(vscode): prevent early SSE replay loss
This commit is contained in:
@@ -185,7 +185,7 @@ export class AgentManagerPanelProvider {
|
||||
private async _startSseProxy(message: BridgeRequest): Promise<BridgeResponse> {
|
||||
const { id, type, payload } = message;
|
||||
|
||||
const { path, headers } = (payload || {}) as { path?: string; headers?: Record<string, string> };
|
||||
const { path, headers, streamId: requestedStreamId } = (payload || {}) as { path?: string; headers?: Record<string, string>; streamId?: string };
|
||||
const normalizedPath = typeof path === 'string' && path.trim().length > 0 ? path.trim() : '/event';
|
||||
|
||||
if (!this._openCodeManager) {
|
||||
@@ -197,8 +197,11 @@ export class AgentManagerPanelProvider {
|
||||
};
|
||||
}
|
||||
|
||||
const streamId = `sse_${++this._sseCounter}_${Date.now()}`;
|
||||
const streamId = typeof requestedStreamId === 'string' && /^sse_webview_\d+_\d+$/.test(requestedStreamId)
|
||||
? requestedStreamId
|
||||
: `sse_${++this._sseCounter}_${Date.now()}`;
|
||||
const controller = new AbortController();
|
||||
this._sseStreams.set(streamId, controller);
|
||||
|
||||
try {
|
||||
const start = await openSseProxy({
|
||||
@@ -211,8 +214,6 @@ export class AgentManagerPanelProvider {
|
||||
},
|
||||
});
|
||||
|
||||
this._sseStreams.set(streamId, controller);
|
||||
|
||||
start.run
|
||||
.then(() => {
|
||||
this._panel?.webview.postMessage({ type: 'api:sse:end', streamId });
|
||||
@@ -238,6 +239,7 @@ export class AgentManagerPanelProvider {
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
this._sseStreams.delete(streamId);
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
return {
|
||||
id,
|
||||
|
||||
@@ -536,7 +536,7 @@ export class ChatViewProvider implements vscode.WebviewViewProvider {
|
||||
private async _startSseProxy(message: BridgeRequest): Promise<BridgeResponse> {
|
||||
const { id, type, payload } = message;
|
||||
|
||||
const { path, headers } = (payload || {}) as { path?: string; headers?: Record<string, string> };
|
||||
const { path, headers, streamId: requestedStreamId } = (payload || {}) as { path?: string; headers?: Record<string, string>; streamId?: string };
|
||||
const normalizedPath = typeof path === 'string' && path.trim().length > 0 ? path.trim() : '/event';
|
||||
|
||||
if (!this._openCodeManager) {
|
||||
@@ -548,8 +548,11 @@ export class ChatViewProvider implements vscode.WebviewViewProvider {
|
||||
};
|
||||
}
|
||||
|
||||
const streamId = `sse_${++this._sseCounter}_${Date.now()}`;
|
||||
const streamId = typeof requestedStreamId === 'string' && /^sse_webview_\d+_\d+$/.test(requestedStreamId)
|
||||
? requestedStreamId
|
||||
: `sse_${++this._sseCounter}_${Date.now()}`;
|
||||
const controller = new AbortController();
|
||||
this._sseStreams.set(streamId, { controller, view: this._view });
|
||||
|
||||
try {
|
||||
const start = await openSseProxy({
|
||||
@@ -562,8 +565,6 @@ export class ChatViewProvider implements vscode.WebviewViewProvider {
|
||||
},
|
||||
});
|
||||
|
||||
this._sseStreams.set(streamId, { controller, view: this._view });
|
||||
|
||||
start.run
|
||||
.then(() => {
|
||||
this._view?.webview.postMessage({ type: 'api:sse:end', streamId });
|
||||
@@ -589,6 +590,7 @@ export class ChatViewProvider implements vscode.WebviewViewProvider {
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
this._sseStreams.delete(streamId);
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
return {
|
||||
id,
|
||||
|
||||
@@ -47,6 +47,7 @@ The webview CSP permits `blob:` only for `worker-src` so shared UI parsers can r
|
||||
- `bridge-proxy-runtime.ts`
|
||||
- Proxy route handlers (`api:proxy`, `api:session:message`) with injected helper dependencies.
|
||||
- SSE routes are intentionally excluded from the generic proxy and use `sseProxy.ts`, whose upstream-only stall watchdog closes a quiet OpenCode stream so the webview can reconnect instead of trusting an open but silent response.
|
||||
- The webview allocates each SSE stream ID and installs its listener before requesting the upstream stream, so immediate OpenCode replay events cannot race the bridge start response.
|
||||
|
||||
- `bridge-config-runtime.ts`
|
||||
- Config and skills message handlers (`api:config/*`).
|
||||
|
||||
@@ -415,7 +415,7 @@ export class SessionEditorPanelProvider {
|
||||
private async _startSseProxy(message: BridgeRequest, entry: SessionPanelState): Promise<BridgeResponse> {
|
||||
const { id, type, payload } = message;
|
||||
|
||||
const { path, headers } = (payload || {}) as { path?: string; headers?: Record<string, string> };
|
||||
const { path, headers, streamId: requestedStreamId } = (payload || {}) as { path?: string; headers?: Record<string, string>; streamId?: string };
|
||||
const normalizedPath = typeof path === 'string' && path.trim().length > 0 ? path.trim() : '/event';
|
||||
|
||||
if (!this._openCodeManager) {
|
||||
@@ -427,8 +427,11 @@ export class SessionEditorPanelProvider {
|
||||
};
|
||||
}
|
||||
|
||||
const streamId = `sse_${++this._sseCounter}_${Date.now()}`;
|
||||
const streamId = typeof requestedStreamId === 'string' && /^sse_webview_\d+_\d+$/.test(requestedStreamId)
|
||||
? requestedStreamId
|
||||
: `sse_${++this._sseCounter}_${Date.now()}`;
|
||||
const controller = new AbortController();
|
||||
entry.sseStreams.set(streamId, controller);
|
||||
|
||||
try {
|
||||
const start = await openSseProxy({
|
||||
@@ -442,8 +445,6 @@ export class SessionEditorPanelProvider {
|
||||
},
|
||||
});
|
||||
|
||||
entry.sseStreams.set(streamId, controller);
|
||||
|
||||
start.run
|
||||
.then(() => {
|
||||
entry.panel?.webview?.postMessage({ type: 'api:sse:end', streamId });
|
||||
@@ -469,6 +470,7 @@ export class SessionEditorPanelProvider {
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
entry.sseStreams.delete(streamId);
|
||||
const messageText = error instanceof Error ? error.message : String(error);
|
||||
return {
|
||||
id,
|
||||
|
||||
Reference in New Issue
Block a user