fix(vscode-extension): improve startup failure UX and watchdog coverage#139
fix(vscode-extension): improve startup failure UX and watchdog coverage#139dyxushuai wants to merge 2 commits intosymposium-dev:mainfrom
Conversation
|
More UI improvements are WIP. |
There was a problem hiding this comment.
Pull request overview
Improves the VS Code extension’s startup failure UX by adding a startup watchdog, routing startup failures into compact chat-visible error payloads (with full diagnostics kept in Output/logs), and adding deterministic test coverage for startup edge cases.
Changes:
- Added a startup watchdog around ACP initialize (slow-threshold feedback + hard-timeout failure) and surfaced slow/fail states to the webview via indexed messages.
- Updated webview UI to use a single “startup status” warning card lifecycle, added “More details” (Output) affordance, and disabled prompt input until startup/session context is available.
- Added fake startup agent fixture plus new unit/integration tests for startup watchdog and failure-mode matrix.
Reviewed changes
Copilot reviewed 16 out of 17 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
vscode-extension/src/acpAgentActor.ts |
Adds startup watchdog types/logic and wraps ACP initialize with timeouts and diagnostics. |
vscode-extension/src/chatViewProvider.ts |
Routes startup errors through agent-error, publishes agent-startup-slow, adds test queue inspection helpers. |
vscode-extension/src/symposium-webview.ts |
Renders unified startup status card, adds “show output” action, freezes/unfreezes input based on startup/session state. |
vscode-extension/src/extension.ts |
Registers symposium.showOutput and new symposium.test.* harness commands. |
vscode-extension/src/mynah-ui.d.ts |
Introduces local typings for @aws/mynah-ui. |
vscode-extension/test-fixtures/fake-startup-agent.cjs |
Deterministic fake agent for startup scenarios (exit/hang/close/acp-error). |
vscode-extension/src/test/*.test.ts |
Adds watchdog unit tests + startup failure matrix integration + routing checks. |
vscode-extension/package.json |
Adds watchdog configuration settings + contributes Symposium: Show Output command. |
vscode-extension/.vscode-test.mjs |
Pins test user/extension dirs for repeatable VS Code test runs. |
vscode-extension/.gitignore |
Ignores test workspace config output. |
md/design/vscode-extension/*.md |
Documents protocol/testing behavior for startup watchdog and matrix coverage. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const reasonMap: Record<StartupFailureReason, StartupWatchdogDiagnosticReason> = { | ||
| "initialize-rejected": "initialize_error", | ||
| "hard-timeout": "hard_timeout", | ||
| "process-exit": "process_exit", | ||
| "stdout-close": "stdout_close", | ||
| "process-error": "process_error", | ||
| }; | ||
|
|
||
| return { | ||
| reason: reasonMap[details.reason], |
There was a problem hiding this comment.
The reasonMap object literal introduces new eslint @typescript-eslint/naming-convention warnings because the keys are kebab-case string literals. Consider using a Map, an array of tuples, or a scoped eslint disable comment for this mapping so the repo lint output stays clean.
| const reasonMap: Record<StartupFailureReason, StartupWatchdogDiagnosticReason> = { | |
| "initialize-rejected": "initialize_error", | |
| "hard-timeout": "hard_timeout", | |
| "process-exit": "process_exit", | |
| "stdout-close": "stdout_close", | |
| "process-error": "process_error", | |
| }; | |
| return { | |
| reason: reasonMap[details.reason], | |
| const reasonMap = new Map<StartupFailureReason, StartupWatchdogDiagnosticReason>([ | |
| ["initialize-rejected", "initialize_error"], | |
| ["hard-timeout", "hard_timeout"], | |
| ["process-exit", "process_exit"], | |
| ["stdout-close", "stdout_close"], | |
| ["process-error", "process_error"], | |
| ]); | |
| return { | |
| reason: reasonMap.get(details.reason)!, |
| slowThresholdMs: details.slowThresholdMs, | ||
| hardTimeoutMs: details.hardTimeoutMs, | ||
| command: details.command, | ||
| args: details.args, |
There was a problem hiding this comment.
toStartupWatchdogDiagnostics reuses the same details.args array reference for diagnostics.args. Because the logger prints objects via console.log, repeated references can show up as [Circular] and make diagnostics harder to read. Clone arrays/objects when building StartupWatchdogDiagnostics (e.g., copy args) to avoid this.
| args: details.args, | |
| args: details.args.slice(), |
| function parseStartupKeyValuePairs(message: string): Record<string, string> { | ||
| const pairs: Record<string, string> = {}; | ||
| const matches = message.matchAll(/([A-Za-z][A-Za-z0-9]*)=([^\s]+)/g); | ||
| for (const match of matches) { | ||
| const key = match[1]; | ||
| const value = match[2]; | ||
| if (key && value) { | ||
| pairs[key] = value; | ||
| } | ||
| } | ||
| return pairs; | ||
| } | ||
|
|
||
| function formatStartupFailureBody(error: unknown): string { | ||
| let startupMessage = "Operation failed"; | ||
|
|
||
| if (error && typeof error === "object") { | ||
| const structured = error as Record<string, unknown>; | ||
| if (typeof structured.message === "string" && structured.message.trim()) { | ||
| startupMessage = structured.message.trim(); | ||
| } else { | ||
| startupMessage = formatAgentErrorBody(error); | ||
| } | ||
| } else if (typeof error === "string" && error.trim()) { | ||
| startupMessage = error.trim(); | ||
| } else { | ||
| startupMessage = formatAgentErrorBody(error); | ||
| } | ||
|
|
||
| const diagnostics = parseStartupKeyValuePairs(startupMessage); | ||
| const reason = diagnostics.reason ?? "unknown"; | ||
| const phase = diagnostics.phase; | ||
| const elapsedMs = diagnostics.elapsedMs; | ||
| const slowThresholdMs = diagnostics.slowThresholdMs; | ||
| const hardTimeoutMs = diagnostics.hardTimeoutMs; | ||
|
|
There was a problem hiding this comment.
parseStartupKeyValuePairs only captures values up to the next whitespace. For startup failures like initializeError=simulated acp initialize error, this will truncate the value (and the UI currently doesn’t surface it elsewhere). Consider either extracting initializeError with a dedicated regex that captures the full remainder of the line, or sending structured startup diagnostics separately so the webview can render the initialize error message accurately.
| } | ||
|
|
||
| function formatStartupFailureBody(error: unknown): string { | ||
| let startupMessage = "Operation failed"; |
There was a problem hiding this comment.
The initial value of startupMessage is unused, since it is always overwritten.
| let startupMessage = "Operation failed"; | |
| let startupMessage: string; |
Why
How
More detailsto Output, and addedSymposium: Show Outputcommand.Tests
cd vscode-extension && npm run compile✅cd vscode-extension && npm test✅ (23 passing, 1 pending)cd vscode-extension && npm run lint✅ (warnings only, no errors)cargo check --workspace✅Closes #124