diff --git a/web/lib/gsd-workspace-store.tsx b/web/lib/gsd-workspace-store.tsx index d22c16dc0..a34d91cf1 100644 --- a/web/lib/gsd-workspace-store.tsx +++ b/web/lib/gsd-workspace-store.tsx @@ -470,6 +470,10 @@ export type WorkspaceEvent = | TurnEndEvent | ({ type: Exclude; [key: string]: unknown } & Record) +export function isWorkspaceEvent(value: unknown): value is WorkspaceEvent { + return value !== null && typeof value === "object" && typeof (value as Record).type === "string" +} + export interface WorkspaceCommandResponse { type: "response" command: string @@ -4866,8 +4870,15 @@ export class GSDWorkspaceStore { stream.onmessage = (message) => { try { - const payload = JSON.parse(message.data) as WorkspaceEvent - this.handleEvent(payload) + const parsed: unknown = JSON.parse(message.data) + if (!isWorkspaceEvent(parsed)) { + this.patchState({ + lastClientError: "Malformed event received from stream", + terminalLines: withTerminalLine(this.state.terminalLines, createTerminalLine("error", "Malformed event received from stream")), + }) + return + } + this.handleEvent(parsed) } catch (error) { const text = normalizeClientError(error) this.patchState({ @@ -4945,6 +4956,15 @@ export class GSDWorkspaceStore { case "tool_execution_end": this.handleToolExecutionEnd(event as ToolExecutionEndEvent) break + case "bridge_status": + // Handled upstream in handleEvent with early return — never reaches here + break + case "live_state_invalidation": + // Handled upstream in handleEvent via handleLiveStateInvalidation — no live interaction state update needed + break + case "extension_error": + // Terminal line produced by summarizeEvent — no live interaction state update needed + break } }