gsd-pi 2.51.0 → 2.52.0-dev.585e355
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/headless-events.d.ts +18 -0
- package/dist/headless-events.js +36 -0
- package/dist/headless-types.d.ts +28 -0
- package/dist/headless-types.js +7 -0
- package/dist/headless.d.ts +8 -3
- package/dist/headless.js +47 -16
- package/dist/help-text.js +16 -5
- package/dist/onboarding.js +5 -4
- package/dist/remote-questions-config.js +1 -1
- package/dist/resources/extensions/async-jobs/async-bash-tool.js +29 -17
- package/dist/resources/extensions/async-jobs/job-manager.js +4 -1
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +18 -19
- package/dist/resources/extensions/gsd/auto/phases.js +6 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +18 -0
- package/dist/resources/extensions/gsd/auto-start.js +2 -0
- package/dist/resources/extensions/gsd/auto-timers.js +24 -2
- package/dist/resources/extensions/gsd/auto-tool-tracking.js +25 -7
- package/dist/resources/extensions/gsd/auto-worktree.js +21 -0
- package/dist/resources/extensions/gsd/auto.js +8 -4
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +105 -70
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +12 -2
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +1 -1
- package/dist/resources/extensions/gsd/claude-import.js +60 -9
- package/dist/resources/extensions/gsd/commands/handlers/auto.js +69 -6
- package/dist/resources/extensions/gsd/commands-config.js +10 -5
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +4 -4
- package/dist/resources/extensions/gsd/detection.js +6 -6
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +5 -5
- package/dist/resources/extensions/gsd/error-classifier.js +105 -0
- package/dist/resources/extensions/gsd/git-service.js +4 -3
- package/dist/resources/extensions/gsd/gitignore.js +7 -7
- package/dist/resources/extensions/gsd/gsd-db.js +298 -45
- package/dist/resources/extensions/gsd/init-wizard.js +2 -2
- package/dist/resources/extensions/gsd/key-manager.js +7 -16
- package/dist/resources/extensions/gsd/markdown-renderer.js +5 -4
- package/dist/resources/extensions/gsd/memory-store.js +28 -13
- package/dist/resources/extensions/gsd/milestone-actions.js +19 -0
- package/dist/resources/extensions/gsd/preferences-models.js +1 -13
- package/dist/resources/extensions/gsd/preferences-types.js +1 -1
- package/dist/resources/extensions/gsd/preferences.js +13 -13
- package/dist/resources/extensions/gsd/prompts/system.md +1 -1
- package/dist/resources/extensions/gsd/provider-error-pause.js +0 -44
- package/dist/resources/extensions/gsd/rule-registry.js +1 -1
- package/dist/resources/extensions/gsd/service-tier.js +13 -2
- package/dist/resources/extensions/gsd/state.js +33 -19
- package/dist/resources/extensions/gsd/status-guards.js +12 -0
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +7 -13
- package/dist/resources/extensions/gsd/tools/complete-slice.js +7 -20
- package/dist/resources/extensions/gsd/tools/complete-task.js +11 -21
- package/dist/resources/extensions/gsd/tools/plan-milestone.js +28 -29
- package/dist/resources/extensions/gsd/tools/plan-slice.js +27 -26
- package/dist/resources/extensions/gsd/tools/plan-task.js +23 -23
- package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +50 -41
- package/dist/resources/extensions/gsd/tools/reopen-slice.js +4 -3
- package/dist/resources/extensions/gsd/tools/reopen-task.js +5 -4
- package/dist/resources/extensions/gsd/tools/replan-slice.js +51 -41
- package/dist/resources/extensions/gsd/tools/validate-milestone.js +23 -16
- package/dist/resources/extensions/gsd/validation.js +21 -0
- package/dist/resources/extensions/gsd/workflow-logger.js +0 -1
- package/dist/resources/extensions/remote-questions/config.js +1 -1
- package/dist/resources/extensions/remote-questions/remote-command.js +1 -1
- package/dist/resources/extensions/search-the-web/native-search.js +1 -1
- package/dist/resources/extensions/search-the-web/provider.js +1 -1
- package/dist/resources/extensions/shared/rtk.js +5 -3
- package/dist/rtk.js +3 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
- package/dist/web/standalone/.next/build-manifest.json +4 -4
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
- package/dist/web/standalone/.next/required-server-files.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
- package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +4 -4
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +4 -4
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +4 -4
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +5 -5
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +5 -5
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/page.js +2 -2
- package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
- package/dist/web/standalone/.next/server/chunks/2229.js +3 -3
- package/dist/web/standalone/.next/server/chunks/7471.js +3 -3
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware.js +2 -2
- package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
- package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/4024.21054f459af5cc78.js +9 -0
- package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/page-b950e4e384cc62b3.js +1 -0
- package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
- package/dist/web/standalone/.next/static/chunks/{webpack-cfc9a116e6450a6b.js → webpack-024d82be84800e52.js} +1 -1
- package/dist/web/standalone/.next/static/css/a58ef8a151aa0493.css +1 -0
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
- package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
- package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
- package/dist/web/standalone/server.js +1 -1
- package/dist/wizard.js +4 -1
- package/package.json +2 -2
- package/packages/mcp-server/README.md +202 -0
- package/packages/mcp-server/package.json +36 -0
- package/packages/mcp-server/src/cli.ts +68 -0
- package/packages/mcp-server/src/index.ts +14 -0
- package/packages/mcp-server/src/mcp-server.test.ts +628 -0
- package/packages/mcp-server/src/server.ts +278 -0
- package/packages/mcp-server/src/session-manager.ts +328 -0
- package/packages/mcp-server/src/types.ts +107 -0
- package/packages/mcp-server/tsconfig.json +24 -0
- package/packages/pi-ai/dist/models.d.ts +14 -3
- package/packages/pi-ai/dist/models.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.js +53 -10
- package/packages/pi-ai/dist/models.js.map +1 -1
- package/packages/pi-ai/dist/models.test.js +102 -1
- package/packages/pi-ai/dist/models.test.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +30 -0
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/src/models.test.ts +114 -1
- package/packages/pi-ai/src/models.ts +70 -13
- package/packages/pi-ai/src/types.ts +31 -0
- package/packages/pi-coding-agent/dist/cli/args.d.ts +2 -0
- package/packages/pi-coding-agent/dist/cli/args.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/cli/args.js +3 -0
- package/packages/pi-coding-agent/dist/cli/args.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/bash-executor.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/bash-executor.js +5 -1
- package/packages/pi-coding-agent/dist/core/bash-executor.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +9 -4
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.d.ts +19 -0
- package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.js +83 -0
- package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash.js +5 -1
- package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/main.js +5 -3
- package/packages/pi-coding-agent/dist/main.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/index.d.ts +1 -1
- package/packages/pi-coding-agent/dist/modes/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +0 -2
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.d.ts +28 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.js +49 -0
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +114 -6
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.d.ts +9 -0
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.js +831 -0
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts +66 -0
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.js.map +1 -1
- package/packages/pi-coding-agent/dist/utils/shell.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/utils/shell.js +0 -1
- package/packages/pi-coding-agent/dist/utils/shell.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/cli/args.ts +4 -0
- package/packages/pi-coding-agent/src/core/bash-executor.ts +5 -1
- package/packages/pi-coding-agent/src/core/model-registry.ts +10 -3
- package/packages/pi-coding-agent/src/core/tools/bash-spawn-windows.test.ts +101 -0
- package/packages/pi-coding-agent/src/core/tools/bash.ts +5 -1
- package/packages/pi-coding-agent/src/index.ts +3 -0
- package/packages/pi-coding-agent/src/main.ts +5 -3
- package/packages/pi-coding-agent/src/modes/index.ts +8 -1
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +0 -2
- package/packages/pi-coding-agent/src/modes/rpc/rpc-client.ts +54 -1
- package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +124 -6
- package/packages/pi-coding-agent/src/modes/rpc/rpc-protocol-v2.test.ts +971 -0
- package/packages/pi-coding-agent/src/modes/rpc/rpc-types.ts +61 -4
- package/packages/pi-coding-agent/src/utils/shell.ts +0 -1
- package/packages/rpc-client/package.json +20 -0
- package/pkg/package.json +1 -1
- package/scripts/ensure-workspace-builds.cjs +36 -8
- package/src/resources/extensions/async-jobs/async-bash-tool.ts +22 -11
- package/src/resources/extensions/async-jobs/job-manager.ts +4 -1
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +19 -20
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +21 -0
- package/src/resources/extensions/gsd/auto/phases.ts +6 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +19 -0
- package/src/resources/extensions/gsd/auto-start.ts +2 -0
- package/src/resources/extensions/gsd/auto-timers.ts +25 -1
- package/src/resources/extensions/gsd/auto-tool-tracking.ts +30 -6
- package/src/resources/extensions/gsd/auto-worktree.ts +21 -0
- package/src/resources/extensions/gsd/auto.ts +10 -4
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +125 -73
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +11 -2
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +1 -1
- package/src/resources/extensions/gsd/claude-import.ts +58 -9
- package/src/resources/extensions/gsd/commands/handlers/auto.ts +73 -6
- package/src/resources/extensions/gsd/commands-config.ts +11 -5
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +4 -4
- package/src/resources/extensions/gsd/detection.ts +6 -6
- package/src/resources/extensions/gsd/docs/preferences-reference.md +5 -5
- package/src/resources/extensions/gsd/error-classifier.ts +139 -0
- package/src/resources/extensions/gsd/git-service.ts +4 -3
- package/src/resources/extensions/gsd/gitignore.ts +7 -7
- package/src/resources/extensions/gsd/gsd-db.ts +355 -63
- package/src/resources/extensions/gsd/init-wizard.ts +2 -2
- package/src/resources/extensions/gsd/key-manager.ts +7 -16
- package/src/resources/extensions/gsd/markdown-renderer.ts +5 -4
- package/src/resources/extensions/gsd/memory-store.ts +29 -18
- package/src/resources/extensions/gsd/milestone-actions.ts +17 -0
- package/src/resources/extensions/gsd/preferences-models.ts +1 -13
- package/src/resources/extensions/gsd/preferences-types.ts +1 -1
- package/src/resources/extensions/gsd/preferences.ts +12 -13
- package/src/resources/extensions/gsd/prompts/system.md +1 -1
- package/src/resources/extensions/gsd/provider-error-pause.ts +0 -57
- package/src/resources/extensions/gsd/rule-registry.ts +1 -1
- package/src/resources/extensions/gsd/service-tier.ts +14 -2
- package/src/resources/extensions/gsd/state.ts +34 -20
- package/src/resources/extensions/gsd/status-guards.ts +13 -0
- package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-milestone-target.test.ts +61 -0
- package/src/resources/extensions/gsd/tests/claude-import-marketplace-discovery.test.ts +191 -0
- package/src/resources/extensions/gsd/tests/claude-import-tui.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/commands-config.test.ts +24 -0
- package/src/resources/extensions/gsd/tests/complete-slice.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/complete-task-rollback-evidence.test.ts +106 -0
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +35 -7
- package/src/resources/extensions/gsd/tests/detection.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/doctor-git.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/empty-db-reconciliation.test.ts +79 -0
- package/src/resources/extensions/gsd/tests/git-service.test.ts +37 -4
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/idle-watchdog-stall-override.test.ts +125 -0
- package/src/resources/extensions/gsd/tests/init-wizard.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/interactive-tool-idle-exemption.test.ts +119 -0
- package/src/resources/extensions/gsd/tests/key-manager.test.ts +16 -1
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +7 -7
- package/src/resources/extensions/gsd/tests/park-db-sync.test.ts +85 -0
- package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +91 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +77 -70
- package/src/resources/extensions/gsd/tests/remediation-completion-guard.test.ts +110 -0
- package/src/resources/extensions/gsd/tests/remote-questions.test.ts +29 -0
- package/src/resources/extensions/gsd/tests/status-guards.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/terminated-transient.test.ts +42 -31
- package/src/resources/extensions/gsd/tests/token-cost-display.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/vacuous-truth-slices.test.ts +115 -0
- package/src/resources/extensions/gsd/tests/validate-milestone-write-order.test.ts +90 -0
- package/src/resources/extensions/gsd/tests/validation.test.ts +72 -0
- package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +81 -1
- package/src/resources/extensions/gsd/tests/worktree-preferences-sync.test.ts +130 -0
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +7 -17
- package/src/resources/extensions/gsd/tools/complete-slice.ts +7 -24
- package/src/resources/extensions/gsd/tools/complete-task.ts +13 -25
- package/src/resources/extensions/gsd/tools/plan-milestone.ts +30 -32
- package/src/resources/extensions/gsd/tools/plan-slice.ts +30 -30
- package/src/resources/extensions/gsd/tools/plan-task.ts +26 -26
- package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +57 -46
- package/src/resources/extensions/gsd/tools/reopen-slice.ts +4 -3
- package/src/resources/extensions/gsd/tools/reopen-task.ts +5 -4
- package/src/resources/extensions/gsd/tools/replan-slice.ts +55 -44
- package/src/resources/extensions/gsd/tools/validate-milestone.ts +26 -20
- package/src/resources/extensions/gsd/validation.ts +23 -0
- package/src/resources/extensions/gsd/workflow-logger.ts +0 -1
- package/src/resources/extensions/remote-questions/config.ts +1 -1
- package/src/resources/extensions/remote-questions/remote-command.ts +1 -1
- package/src/resources/extensions/search-the-web/native-search.ts +1 -1
- package/src/resources/extensions/search-the-web/provider.ts +1 -1
- package/src/resources/extensions/shared/rtk.ts +12 -3
- package/dist/web/standalone/.next/static/chunks/4024.9ad5def014d90ce4.js +0 -9
- package/dist/web/standalone/.next/static/chunks/app/page-fbecd1237e2d6d1f.js +0 -1
- package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
- package/dist/web/standalone/.next/static/css/de141508b083f922.css +0 -1
- /package/dist/resources/extensions/gsd/templates/{preferences.md → PREFERENCES.md} +0 -0
- /package/dist/web/standalone/.next/static/{vkr67v-utm1dgZnbrBWQh → KTe1kB5nPLQFIIFz2OcmI}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{vkr67v-utm1dgZnbrBWQh → KTe1kB5nPLQFIIFz2OcmI}/_ssgManifest.js +0 -0
- /package/src/resources/extensions/gsd/templates/{preferences.md → PREFERENCES.md} +0 -0
|
@@ -0,0 +1,831 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RPC Protocol v2 test suite.
|
|
3
|
+
*
|
|
4
|
+
* Tests v1 backward compatibility, v2 init handshake, protocol locking,
|
|
5
|
+
* v2 feature type shapes, and RpcClient command serialization against
|
|
6
|
+
* mock child processes using PassThrough streams.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, it, beforeEach, afterEach } from "node:test";
|
|
9
|
+
import assert from "node:assert/strict";
|
|
10
|
+
import { PassThrough } from "node:stream";
|
|
11
|
+
import { attachJsonlLineReader, serializeJsonLine } from "./jsonl.js";
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// Helpers
|
|
14
|
+
// ============================================================================
|
|
15
|
+
/** Collect JSONL output lines from a stream */
|
|
16
|
+
function collectLines(stream) {
|
|
17
|
+
const lines = [];
|
|
18
|
+
const detach = attachJsonlLineReader(stream, (line) => {
|
|
19
|
+
try {
|
|
20
|
+
lines.push(JSON.parse(line));
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// skip non-JSON lines
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
return { lines, detach };
|
|
27
|
+
}
|
|
28
|
+
/** Write a command as JSONL to a writable stream and wait for drain */
|
|
29
|
+
function writeLine(stream, obj) {
|
|
30
|
+
stream.write(serializeJsonLine(obj));
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Create a mock "child process" with piped stdin/stdout.
|
|
34
|
+
* clientStdin → data flows into the "server" (from the client's perspective, this is what the client writes to)
|
|
35
|
+
* clientStdout ← data flows out of the "server" (from the client's perspective, this is what the client reads from)
|
|
36
|
+
*
|
|
37
|
+
* The test acts as the "server": read from clientStdin, write to clientStdout.
|
|
38
|
+
*/
|
|
39
|
+
function createMockProcess() {
|
|
40
|
+
// Client writes to this → server reads from it
|
|
41
|
+
const clientStdin = new PassThrough();
|
|
42
|
+
// Server writes to this → client reads from it
|
|
43
|
+
const clientStdout = new PassThrough();
|
|
44
|
+
return { clientStdin, clientStdout };
|
|
45
|
+
}
|
|
46
|
+
/** Wait a tick for async handlers to process */
|
|
47
|
+
function tick(ms = 10) {
|
|
48
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
49
|
+
}
|
|
50
|
+
// ============================================================================
|
|
51
|
+
// JSONL utilities
|
|
52
|
+
// ============================================================================
|
|
53
|
+
describe("JSONL utilities", () => {
|
|
54
|
+
it("serializeJsonLine produces newline-terminated JSON", () => {
|
|
55
|
+
const result = serializeJsonLine({ type: "test", value: 42 });
|
|
56
|
+
assert.equal(result, '{"type":"test","value":42}\n');
|
|
57
|
+
});
|
|
58
|
+
it("serializeJsonLine handles nested objects", () => {
|
|
59
|
+
const result = serializeJsonLine({ a: { b: [1, 2, 3] } });
|
|
60
|
+
assert.ok(result.endsWith("\n"));
|
|
61
|
+
const parsed = JSON.parse(result.trim());
|
|
62
|
+
assert.deepEqual(parsed, { a: { b: [1, 2, 3] } });
|
|
63
|
+
});
|
|
64
|
+
it("attachJsonlLineReader splits on LF only", async () => {
|
|
65
|
+
const stream = new PassThrough();
|
|
66
|
+
const { lines, detach } = collectLines(stream);
|
|
67
|
+
stream.write('{"a":1}\n{"b":2}\n');
|
|
68
|
+
await tick();
|
|
69
|
+
assert.equal(lines.length, 2);
|
|
70
|
+
assert.deepEqual(lines[0], { a: 1 });
|
|
71
|
+
assert.deepEqual(lines[1], { b: 2 });
|
|
72
|
+
detach();
|
|
73
|
+
});
|
|
74
|
+
it("attachJsonlLineReader handles partial writes", async () => {
|
|
75
|
+
const stream = new PassThrough();
|
|
76
|
+
const { lines, detach } = collectLines(stream);
|
|
77
|
+
stream.write('{"partial":');
|
|
78
|
+
await tick();
|
|
79
|
+
assert.equal(lines.length, 0);
|
|
80
|
+
stream.write('"value"}\n');
|
|
81
|
+
await tick();
|
|
82
|
+
assert.equal(lines.length, 1);
|
|
83
|
+
assert.deepEqual(lines[0], { partial: "value" });
|
|
84
|
+
detach();
|
|
85
|
+
});
|
|
86
|
+
it("attachJsonlLineReader handles CR+LF", async () => {
|
|
87
|
+
const stream = new PassThrough();
|
|
88
|
+
const { lines, detach } = collectLines(stream);
|
|
89
|
+
stream.write('{"cr":"lf"}\r\n');
|
|
90
|
+
await tick();
|
|
91
|
+
assert.equal(lines.length, 1);
|
|
92
|
+
assert.deepEqual(lines[0], { cr: "lf" });
|
|
93
|
+
detach();
|
|
94
|
+
});
|
|
95
|
+
it("detach stops line delivery", async () => {
|
|
96
|
+
const stream = new PassThrough();
|
|
97
|
+
const { lines, detach } = collectLines(stream);
|
|
98
|
+
stream.write('{"before":1}\n');
|
|
99
|
+
await tick();
|
|
100
|
+
assert.equal(lines.length, 1);
|
|
101
|
+
detach();
|
|
102
|
+
stream.write('{"after":2}\n');
|
|
103
|
+
await tick();
|
|
104
|
+
// Should still be 1 since we detached
|
|
105
|
+
assert.equal(lines.length, 1);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
// ============================================================================
|
|
109
|
+
// v2 type shape assertions
|
|
110
|
+
// ============================================================================
|
|
111
|
+
describe("v2 type shapes", () => {
|
|
112
|
+
it("RpcInitResult has required fields", () => {
|
|
113
|
+
const initResult = {
|
|
114
|
+
protocolVersion: 2,
|
|
115
|
+
sessionId: "test-session-123",
|
|
116
|
+
capabilities: {
|
|
117
|
+
events: ["execution_complete", "cost_update"],
|
|
118
|
+
commands: ["init", "shutdown", "subscribe"],
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
assert.equal(initResult.protocolVersion, 2);
|
|
122
|
+
assert.ok(typeof initResult.sessionId === "string");
|
|
123
|
+
assert.ok(Array.isArray(initResult.capabilities.events));
|
|
124
|
+
assert.ok(Array.isArray(initResult.capabilities.commands));
|
|
125
|
+
assert.ok(initResult.capabilities.events.includes("execution_complete"));
|
|
126
|
+
assert.ok(initResult.capabilities.events.includes("cost_update"));
|
|
127
|
+
assert.ok(initResult.capabilities.commands.includes("init"));
|
|
128
|
+
assert.ok(initResult.capabilities.commands.includes("shutdown"));
|
|
129
|
+
assert.ok(initResult.capabilities.commands.includes("subscribe"));
|
|
130
|
+
});
|
|
131
|
+
it("RpcExecutionCompleteEvent matches expected shape", () => {
|
|
132
|
+
const event = {
|
|
133
|
+
type: "execution_complete",
|
|
134
|
+
runId: "run-abc-123",
|
|
135
|
+
status: "completed",
|
|
136
|
+
stats: {
|
|
137
|
+
cost: 0.05,
|
|
138
|
+
turns: 3,
|
|
139
|
+
duration: 12000,
|
|
140
|
+
tokens: { input: 1000, output: 500, cacheRead: 200, cacheWrite: 100 },
|
|
141
|
+
}, // SessionStats is complex, we just verify shape
|
|
142
|
+
};
|
|
143
|
+
assert.equal(event.type, "execution_complete");
|
|
144
|
+
assert.ok(typeof event.runId === "string");
|
|
145
|
+
assert.ok(["completed", "error", "cancelled"].includes(event.status));
|
|
146
|
+
assert.ok(event.stats !== undefined);
|
|
147
|
+
});
|
|
148
|
+
it("RpcExecutionCompleteEvent supports error status with reason", () => {
|
|
149
|
+
const event = {
|
|
150
|
+
type: "execution_complete",
|
|
151
|
+
runId: "run-err-456",
|
|
152
|
+
status: "error",
|
|
153
|
+
reason: "API rate limit exceeded",
|
|
154
|
+
stats: {},
|
|
155
|
+
};
|
|
156
|
+
assert.equal(event.status, "error");
|
|
157
|
+
assert.equal(event.reason, "API rate limit exceeded");
|
|
158
|
+
});
|
|
159
|
+
it("RpcCostUpdateEvent matches expected shape", () => {
|
|
160
|
+
const event = {
|
|
161
|
+
type: "cost_update",
|
|
162
|
+
runId: "run-cost-789",
|
|
163
|
+
turnCost: 0.01,
|
|
164
|
+
cumulativeCost: 0.05,
|
|
165
|
+
tokens: {
|
|
166
|
+
input: 500,
|
|
167
|
+
output: 200,
|
|
168
|
+
cacheRead: 100,
|
|
169
|
+
cacheWrite: 50,
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
assert.equal(event.type, "cost_update");
|
|
173
|
+
assert.ok(typeof event.runId === "string");
|
|
174
|
+
assert.ok(typeof event.turnCost === "number");
|
|
175
|
+
assert.ok(typeof event.cumulativeCost === "number");
|
|
176
|
+
assert.ok(typeof event.tokens.input === "number");
|
|
177
|
+
assert.ok(typeof event.tokens.output === "number");
|
|
178
|
+
assert.ok(typeof event.tokens.cacheRead === "number");
|
|
179
|
+
assert.ok(typeof event.tokens.cacheWrite === "number");
|
|
180
|
+
});
|
|
181
|
+
it("RpcV2Event discriminated union resolves by type field", () => {
|
|
182
|
+
const events = [
|
|
183
|
+
{
|
|
184
|
+
type: "execution_complete",
|
|
185
|
+
runId: "r1",
|
|
186
|
+
status: "completed",
|
|
187
|
+
stats: {},
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
type: "cost_update",
|
|
191
|
+
runId: "r2",
|
|
192
|
+
turnCost: 0.01,
|
|
193
|
+
cumulativeCost: 0.03,
|
|
194
|
+
tokens: { input: 100, output: 50, cacheRead: 10, cacheWrite: 5 },
|
|
195
|
+
},
|
|
196
|
+
];
|
|
197
|
+
for (const event of events) {
|
|
198
|
+
if (event.type === "execution_complete") {
|
|
199
|
+
// TypeScript narrows to RpcExecutionCompleteEvent
|
|
200
|
+
assert.ok("status" in event);
|
|
201
|
+
assert.ok("stats" in event);
|
|
202
|
+
}
|
|
203
|
+
else if (event.type === "cost_update") {
|
|
204
|
+
// TypeScript narrows to RpcCostUpdateEvent
|
|
205
|
+
assert.ok("turnCost" in event);
|
|
206
|
+
assert.ok("tokens" in event);
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
assert.fail(`Unexpected event type: ${event.type}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
it("RpcProtocolVersion is 1 or 2", () => {
|
|
214
|
+
const v1 = 1;
|
|
215
|
+
const v2 = 2;
|
|
216
|
+
assert.equal(v1, 1);
|
|
217
|
+
assert.equal(v2, 2);
|
|
218
|
+
});
|
|
219
|
+
it("v2 prompt response includes optional runId field", () => {
|
|
220
|
+
const v1Response = {
|
|
221
|
+
id: "1",
|
|
222
|
+
type: "response",
|
|
223
|
+
command: "prompt",
|
|
224
|
+
success: true,
|
|
225
|
+
};
|
|
226
|
+
assert.equal(v1Response.success, true);
|
|
227
|
+
assert.equal(v1Response.runId, undefined);
|
|
228
|
+
const v2Response = {
|
|
229
|
+
id: "2",
|
|
230
|
+
type: "response",
|
|
231
|
+
command: "prompt",
|
|
232
|
+
success: true,
|
|
233
|
+
runId: "run-123",
|
|
234
|
+
};
|
|
235
|
+
assert.equal(v2Response.success, true);
|
|
236
|
+
assert.equal(v2Response.runId, "run-123");
|
|
237
|
+
});
|
|
238
|
+
it("v2 command types are present in RpcCommand union", () => {
|
|
239
|
+
// These compile — that's the actual test. Runtime verification:
|
|
240
|
+
const initCmd = { type: "init", protocolVersion: 2 };
|
|
241
|
+
const shutdownCmd = { type: "shutdown" };
|
|
242
|
+
const subscribeCmd = { type: "subscribe", events: ["agent_end"] };
|
|
243
|
+
assert.equal(initCmd.type, "init");
|
|
244
|
+
assert.equal(shutdownCmd.type, "shutdown");
|
|
245
|
+
assert.equal(subscribeCmd.type, "subscribe");
|
|
246
|
+
});
|
|
247
|
+
it("init command supports optional clientId", () => {
|
|
248
|
+
const cmd = { type: "init", protocolVersion: 2, clientId: "my-client" };
|
|
249
|
+
assert.equal(cmd.type, "init");
|
|
250
|
+
if (cmd.type === "init") {
|
|
251
|
+
assert.equal(cmd.clientId, "my-client");
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
it("shutdown command supports optional graceful flag", () => {
|
|
255
|
+
const cmd = { type: "shutdown", graceful: true };
|
|
256
|
+
if (cmd.type === "shutdown") {
|
|
257
|
+
assert.equal(cmd.graceful, true);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
it("v2 response types include init, shutdown, subscribe", () => {
|
|
261
|
+
const initResp = {
|
|
262
|
+
type: "response",
|
|
263
|
+
command: "init",
|
|
264
|
+
success: true,
|
|
265
|
+
data: {
|
|
266
|
+
protocolVersion: 2,
|
|
267
|
+
sessionId: "s1",
|
|
268
|
+
capabilities: { events: [], commands: [] },
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
const shutdownResp = {
|
|
272
|
+
type: "response",
|
|
273
|
+
command: "shutdown",
|
|
274
|
+
success: true,
|
|
275
|
+
};
|
|
276
|
+
const subscribeResp = {
|
|
277
|
+
type: "response",
|
|
278
|
+
command: "subscribe",
|
|
279
|
+
success: true,
|
|
280
|
+
};
|
|
281
|
+
assert.equal(initResp.command, "init");
|
|
282
|
+
assert.equal(shutdownResp.command, "shutdown");
|
|
283
|
+
assert.equal(subscribeResp.command, "subscribe");
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
// ============================================================================
|
|
287
|
+
// v1 backward compatibility
|
|
288
|
+
// ============================================================================
|
|
289
|
+
describe("v1 backward compatibility — command shapes", () => {
|
|
290
|
+
it("v1 prompt command has no protocolVersion or runId", () => {
|
|
291
|
+
const cmd = { type: "prompt", message: "hello" };
|
|
292
|
+
assert.equal(cmd.type, "prompt");
|
|
293
|
+
assert.equal(cmd.protocolVersion, undefined);
|
|
294
|
+
assert.equal(cmd.runId, undefined);
|
|
295
|
+
});
|
|
296
|
+
it("v1 get_state response has no v2 fields", () => {
|
|
297
|
+
const state = {
|
|
298
|
+
thinkingLevel: "medium",
|
|
299
|
+
isStreaming: false,
|
|
300
|
+
isCompacting: false,
|
|
301
|
+
steeringMode: "all",
|
|
302
|
+
followUpMode: "all",
|
|
303
|
+
sessionId: "test-id",
|
|
304
|
+
autoCompactionEnabled: true,
|
|
305
|
+
autoRetryEnabled: false,
|
|
306
|
+
retryInProgress: false,
|
|
307
|
+
retryAttempt: 0,
|
|
308
|
+
messageCount: 0,
|
|
309
|
+
pendingMessageCount: 0,
|
|
310
|
+
extensionsReady: true,
|
|
311
|
+
};
|
|
312
|
+
// v1 state should not include any v2-specific fields
|
|
313
|
+
assert.equal(state.protocolVersion, undefined);
|
|
314
|
+
assert.equal(state.runId, undefined);
|
|
315
|
+
});
|
|
316
|
+
it("v1 prompt response has no runId", () => {
|
|
317
|
+
const resp = {
|
|
318
|
+
id: "1",
|
|
319
|
+
type: "response",
|
|
320
|
+
command: "prompt",
|
|
321
|
+
success: true,
|
|
322
|
+
};
|
|
323
|
+
assert.equal(resp.success, true);
|
|
324
|
+
// runId is optional; in v1 mode it won't be present
|
|
325
|
+
assert.equal(resp.runId, undefined);
|
|
326
|
+
});
|
|
327
|
+
it("error response shape is consistent across v1 and v2", () => {
|
|
328
|
+
const errResp = {
|
|
329
|
+
id: "err-1",
|
|
330
|
+
type: "response",
|
|
331
|
+
command: "init",
|
|
332
|
+
success: false,
|
|
333
|
+
error: "Protocol version already locked. init must be the first command.",
|
|
334
|
+
};
|
|
335
|
+
assert.equal(errResp.success, false);
|
|
336
|
+
if (!errResp.success) {
|
|
337
|
+
assert.ok(typeof errResp.error === "string");
|
|
338
|
+
assert.ok(errResp.error.length > 0);
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
// ============================================================================
|
|
343
|
+
// RpcClient command serialization tests (mock process)
|
|
344
|
+
// ============================================================================
|
|
345
|
+
describe("RpcClient command serialization", () => {
|
|
346
|
+
// We import the class dynamically to avoid the full module graph at test time.
|
|
347
|
+
// Instead we test the protocol framing directly — what gets written to stdin and
|
|
348
|
+
// what comes back from stdout — using PassThrough streams.
|
|
349
|
+
it("init command serializes correctly", () => {
|
|
350
|
+
const cmd = { id: "req_1", type: "init", protocolVersion: 2 };
|
|
351
|
+
const serialized = serializeJsonLine(cmd);
|
|
352
|
+
const parsed = JSON.parse(serialized);
|
|
353
|
+
assert.equal(parsed.type, "init");
|
|
354
|
+
assert.equal(parsed.protocolVersion, 2);
|
|
355
|
+
assert.equal(parsed.id, "req_1");
|
|
356
|
+
});
|
|
357
|
+
it("init command with clientId serializes correctly", () => {
|
|
358
|
+
const cmd = { id: "req_1", type: "init", protocolVersion: 2, clientId: "test-client" };
|
|
359
|
+
const serialized = serializeJsonLine(cmd);
|
|
360
|
+
const parsed = JSON.parse(serialized);
|
|
361
|
+
assert.equal(parsed.clientId, "test-client");
|
|
362
|
+
});
|
|
363
|
+
it("shutdown command serializes correctly", () => {
|
|
364
|
+
const cmd = { id: "req_2", type: "shutdown" };
|
|
365
|
+
const serialized = serializeJsonLine(cmd);
|
|
366
|
+
const parsed = JSON.parse(serialized);
|
|
367
|
+
assert.equal(parsed.type, "shutdown");
|
|
368
|
+
assert.equal(parsed.id, "req_2");
|
|
369
|
+
});
|
|
370
|
+
it("subscribe command serializes correctly with event list", () => {
|
|
371
|
+
const cmd = { id: "req_3", type: "subscribe", events: ["agent_end", "cost_update"] };
|
|
372
|
+
const serialized = serializeJsonLine(cmd);
|
|
373
|
+
const parsed = JSON.parse(serialized);
|
|
374
|
+
assert.equal(parsed.type, "subscribe");
|
|
375
|
+
assert.deepEqual(parsed.events, ["agent_end", "cost_update"]);
|
|
376
|
+
});
|
|
377
|
+
it("subscribe command with wildcard serializes correctly", () => {
|
|
378
|
+
const cmd = { id: "req_4", type: "subscribe", events: ["*"] };
|
|
379
|
+
const serialized = serializeJsonLine(cmd);
|
|
380
|
+
const parsed = JSON.parse(serialized);
|
|
381
|
+
assert.deepEqual(parsed.events, ["*"]);
|
|
382
|
+
});
|
|
383
|
+
it("subscribe command with empty array serializes correctly", () => {
|
|
384
|
+
const cmd = { id: "req_5", type: "subscribe", events: [] };
|
|
385
|
+
const serialized = serializeJsonLine(cmd);
|
|
386
|
+
const parsed = JSON.parse(serialized);
|
|
387
|
+
assert.deepEqual(parsed.events, []);
|
|
388
|
+
});
|
|
389
|
+
it("sendUIResponse serializes correct JSONL", () => {
|
|
390
|
+
const response = {
|
|
391
|
+
type: "extension_ui_response",
|
|
392
|
+
id: "ui-req-123",
|
|
393
|
+
value: "test-value",
|
|
394
|
+
};
|
|
395
|
+
const serialized = serializeJsonLine(response);
|
|
396
|
+
const parsed = JSON.parse(serialized);
|
|
397
|
+
assert.equal(parsed.type, "extension_ui_response");
|
|
398
|
+
assert.equal(parsed.id, "ui-req-123");
|
|
399
|
+
assert.equal(parsed.value, "test-value");
|
|
400
|
+
});
|
|
401
|
+
it("sendUIResponse with cancelled flag serializes correctly", () => {
|
|
402
|
+
const response = {
|
|
403
|
+
type: "extension_ui_response",
|
|
404
|
+
id: "ui-req-456",
|
|
405
|
+
cancelled: true,
|
|
406
|
+
};
|
|
407
|
+
const serialized = serializeJsonLine(response);
|
|
408
|
+
const parsed = JSON.parse(serialized);
|
|
409
|
+
assert.equal(parsed.type, "extension_ui_response");
|
|
410
|
+
assert.equal(parsed.cancelled, true);
|
|
411
|
+
});
|
|
412
|
+
it("sendUIResponse with confirmed flag serializes correctly", () => {
|
|
413
|
+
const response = {
|
|
414
|
+
type: "extension_ui_response",
|
|
415
|
+
id: "ui-req-789",
|
|
416
|
+
confirmed: true,
|
|
417
|
+
};
|
|
418
|
+
const serialized = serializeJsonLine(response);
|
|
419
|
+
const parsed = JSON.parse(serialized);
|
|
420
|
+
assert.equal(parsed.confirmed, true);
|
|
421
|
+
});
|
|
422
|
+
it("sendUIResponse with multiple values serializes correctly", () => {
|
|
423
|
+
const response = {
|
|
424
|
+
type: "extension_ui_response",
|
|
425
|
+
id: "ui-req-multi",
|
|
426
|
+
values: ["opt-a", "opt-b"],
|
|
427
|
+
};
|
|
428
|
+
const serialized = serializeJsonLine(response);
|
|
429
|
+
const parsed = JSON.parse(serialized);
|
|
430
|
+
assert.deepEqual(parsed.values, ["opt-a", "opt-b"]);
|
|
431
|
+
});
|
|
432
|
+
it("prompt command with runId in v2 response", () => {
|
|
433
|
+
const response = {
|
|
434
|
+
id: "req_10",
|
|
435
|
+
type: "response",
|
|
436
|
+
command: "prompt",
|
|
437
|
+
success: true,
|
|
438
|
+
runId: "run-uuid-abc",
|
|
439
|
+
};
|
|
440
|
+
const serialized = serializeJsonLine(response);
|
|
441
|
+
const parsed = JSON.parse(serialized);
|
|
442
|
+
assert.equal(parsed.runId, "run-uuid-abc");
|
|
443
|
+
assert.equal(parsed.command, "prompt");
|
|
444
|
+
assert.equal(parsed.success, true);
|
|
445
|
+
});
|
|
446
|
+
});
|
|
447
|
+
// ============================================================================
|
|
448
|
+
// Client ↔ Mock server integration (PassThrough streams)
|
|
449
|
+
// ============================================================================
|
|
450
|
+
describe("Client ↔ Mock server protocol exchange", () => {
|
|
451
|
+
let clientStdin;
|
|
452
|
+
let clientStdout;
|
|
453
|
+
beforeEach(() => {
|
|
454
|
+
const mockProc = createMockProcess();
|
|
455
|
+
clientStdin = mockProc.clientStdin;
|
|
456
|
+
clientStdout = mockProc.clientStdout;
|
|
457
|
+
});
|
|
458
|
+
afterEach(() => {
|
|
459
|
+
clientStdin.destroy();
|
|
460
|
+
clientStdout.destroy();
|
|
461
|
+
});
|
|
462
|
+
it("init handshake: client writes init, server responds with init_result", async () => {
|
|
463
|
+
// Collect what the client would write
|
|
464
|
+
const { lines: clientWrites, detach: detachStdin } = collectLines(clientStdin);
|
|
465
|
+
// Client sends init command
|
|
466
|
+
writeLine(clientStdin, { id: "req_1", type: "init", protocolVersion: 2 });
|
|
467
|
+
await tick();
|
|
468
|
+
assert.equal(clientWrites.length, 1);
|
|
469
|
+
const initCmd = clientWrites[0];
|
|
470
|
+
assert.equal(initCmd.type, "init");
|
|
471
|
+
assert.equal(initCmd.protocolVersion, 2);
|
|
472
|
+
// Server responds with init_result
|
|
473
|
+
const initResult = {
|
|
474
|
+
protocolVersion: 2,
|
|
475
|
+
sessionId: "sess-abc",
|
|
476
|
+
capabilities: {
|
|
477
|
+
events: ["execution_complete", "cost_update"],
|
|
478
|
+
commands: ["init", "shutdown", "subscribe"],
|
|
479
|
+
},
|
|
480
|
+
};
|
|
481
|
+
writeLine(clientStdout, {
|
|
482
|
+
id: "req_1",
|
|
483
|
+
type: "response",
|
|
484
|
+
command: "init",
|
|
485
|
+
success: true,
|
|
486
|
+
data: initResult,
|
|
487
|
+
});
|
|
488
|
+
// Collect server response
|
|
489
|
+
const { lines: serverResponses, detach: detachStdout } = collectLines(clientStdout);
|
|
490
|
+
// Already wrote above, but let's verify the shape by re-writing
|
|
491
|
+
writeLine(clientStdout, {
|
|
492
|
+
id: "req_verify",
|
|
493
|
+
type: "response",
|
|
494
|
+
command: "init",
|
|
495
|
+
success: true,
|
|
496
|
+
data: initResult,
|
|
497
|
+
});
|
|
498
|
+
await tick();
|
|
499
|
+
const resp = serverResponses[0];
|
|
500
|
+
assert.equal(resp.type, "response");
|
|
501
|
+
assert.equal(resp.command, "init");
|
|
502
|
+
assert.equal(resp.success, true);
|
|
503
|
+
assert.equal(resp.data.protocolVersion, 2);
|
|
504
|
+
assert.ok(typeof resp.data.sessionId === "string");
|
|
505
|
+
detachStdin();
|
|
506
|
+
detachStdout();
|
|
507
|
+
});
|
|
508
|
+
it("shutdown: client writes shutdown, server acknowledges", async () => {
|
|
509
|
+
const { lines: clientWrites, detach } = collectLines(clientStdin);
|
|
510
|
+
writeLine(clientStdin, { id: "req_2", type: "shutdown" });
|
|
511
|
+
await tick();
|
|
512
|
+
const cmd = clientWrites[0];
|
|
513
|
+
assert.equal(cmd.type, "shutdown");
|
|
514
|
+
detach();
|
|
515
|
+
});
|
|
516
|
+
it("subscribe: client writes subscribe with event list", async () => {
|
|
517
|
+
const { lines: clientWrites, detach } = collectLines(clientStdin);
|
|
518
|
+
writeLine(clientStdin, { id: "req_3", type: "subscribe", events: ["agent_end", "execution_complete"] });
|
|
519
|
+
await tick();
|
|
520
|
+
const cmd = clientWrites[0];
|
|
521
|
+
assert.equal(cmd.type, "subscribe");
|
|
522
|
+
assert.deepEqual(cmd.events, ["agent_end", "execution_complete"]);
|
|
523
|
+
detach();
|
|
524
|
+
});
|
|
525
|
+
it("sendUIResponse: client writes extension_ui_response", async () => {
|
|
526
|
+
const { lines: clientWrites, detach } = collectLines(clientStdin);
|
|
527
|
+
writeLine(clientStdin, {
|
|
528
|
+
type: "extension_ui_response",
|
|
529
|
+
id: "ui-123",
|
|
530
|
+
value: "selected-option",
|
|
531
|
+
});
|
|
532
|
+
await tick();
|
|
533
|
+
const msg = clientWrites[0];
|
|
534
|
+
assert.equal(msg.type, "extension_ui_response");
|
|
535
|
+
assert.equal(msg.id, "ui-123");
|
|
536
|
+
assert.equal(msg.value, "selected-option");
|
|
537
|
+
detach();
|
|
538
|
+
});
|
|
539
|
+
it("v2 event filtering: subscribe with empty array should filter all", async () => {
|
|
540
|
+
// An empty event filter means no events pass through (Set with 0 entries)
|
|
541
|
+
const subscribeCmd = { id: "req_4", type: "subscribe", events: [] };
|
|
542
|
+
const serialized = serializeJsonLine(subscribeCmd);
|
|
543
|
+
const parsed = JSON.parse(serialized);
|
|
544
|
+
assert.deepEqual(parsed.events, []);
|
|
545
|
+
// Server-side: `eventFilter = new Set([])` — Set.has(anything) returns false
|
|
546
|
+
const filter = new Set(parsed.events);
|
|
547
|
+
assert.equal(filter.has("agent_end"), false);
|
|
548
|
+
assert.equal(filter.has("execution_complete"), false);
|
|
549
|
+
assert.equal(filter.size, 0);
|
|
550
|
+
});
|
|
551
|
+
it("v2 event filtering: subscribe with wildcard resets filter", async () => {
|
|
552
|
+
// Server-side: `events.includes("*")` → `eventFilter = null`
|
|
553
|
+
const subscribeCmd = { type: "subscribe", events: ["*"] };
|
|
554
|
+
const parsed = JSON.parse(serializeJsonLine(subscribeCmd));
|
|
555
|
+
const hasWildcard = parsed.events.includes("*");
|
|
556
|
+
assert.equal(hasWildcard, true);
|
|
557
|
+
// When wildcard is detected, filter becomes null (all events pass)
|
|
558
|
+
});
|
|
559
|
+
it("multiple commands can be sent sequentially", async () => {
|
|
560
|
+
const { lines, detach } = collectLines(clientStdin);
|
|
561
|
+
writeLine(clientStdin, { id: "1", type: "init", protocolVersion: 2 });
|
|
562
|
+
writeLine(clientStdin, { id: "2", type: "subscribe", events: ["agent_end"] });
|
|
563
|
+
writeLine(clientStdin, { id: "3", type: "prompt", message: "hello" });
|
|
564
|
+
await tick();
|
|
565
|
+
assert.equal(lines.length, 3);
|
|
566
|
+
assert.equal(lines[0].type, "init");
|
|
567
|
+
assert.equal(lines[1].type, "subscribe");
|
|
568
|
+
assert.equal(lines[2].type, "prompt");
|
|
569
|
+
detach();
|
|
570
|
+
});
|
|
571
|
+
});
|
|
572
|
+
// ============================================================================
|
|
573
|
+
// Negative tests — malformed inputs, error paths, boundary conditions
|
|
574
|
+
// ============================================================================
|
|
575
|
+
describe("Negative tests — protocol error shapes", () => {
|
|
576
|
+
it("init with missing protocolVersion produces a type error at compile time", () => {
|
|
577
|
+
// Runtime check: a message missing protocolVersion is malformed
|
|
578
|
+
const malformed = { type: "init" };
|
|
579
|
+
assert.equal(malformed.protocolVersion, undefined);
|
|
580
|
+
// Server would treat this as v1 lock since it's not a valid init
|
|
581
|
+
});
|
|
582
|
+
it("subscribe with non-array events is a type violation", () => {
|
|
583
|
+
// Runtime: server expects events to be string[]
|
|
584
|
+
const malformed = { type: "subscribe", events: "agent_end" };
|
|
585
|
+
assert.equal(typeof malformed.events, "string"); // Not an array
|
|
586
|
+
assert.equal(Array.isArray(malformed.events), false);
|
|
587
|
+
});
|
|
588
|
+
it("double init error response shape", () => {
|
|
589
|
+
// When init is sent after protocol lock, server returns error
|
|
590
|
+
const errorResp = {
|
|
591
|
+
id: "req_dup",
|
|
592
|
+
type: "response",
|
|
593
|
+
command: "init",
|
|
594
|
+
success: false,
|
|
595
|
+
error: "Protocol version already locked. init must be the first command.",
|
|
596
|
+
};
|
|
597
|
+
assert.equal(errorResp.success, false);
|
|
598
|
+
if (!errorResp.success) {
|
|
599
|
+
assert.ok(errorResp.error.includes("already locked"));
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
it("init after v1 lock error response shape", () => {
|
|
603
|
+
// First command was get_state (v1 lock), then init arrives
|
|
604
|
+
const errorResp = {
|
|
605
|
+
id: "req_late_init",
|
|
606
|
+
type: "response",
|
|
607
|
+
command: "init",
|
|
608
|
+
success: false,
|
|
609
|
+
error: "Protocol version already locked. init must be the first command.",
|
|
610
|
+
};
|
|
611
|
+
assert.equal(errorResp.success, false);
|
|
612
|
+
if (!errorResp.success) {
|
|
613
|
+
assert.ok(errorResp.error.includes("init must be the first command"));
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
it("unknown command type produces error response", () => {
|
|
617
|
+
const errorResp = {
|
|
618
|
+
id: "req_unknown",
|
|
619
|
+
type: "response",
|
|
620
|
+
command: "nonexistent",
|
|
621
|
+
success: false,
|
|
622
|
+
error: "Unknown command: nonexistent",
|
|
623
|
+
};
|
|
624
|
+
assert.equal(errorResp.success, false);
|
|
625
|
+
if (!errorResp.success) {
|
|
626
|
+
assert.ok(errorResp.error.includes("Unknown command"));
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
it("malformed JSON parse error shape", () => {
|
|
630
|
+
const errorResp = {
|
|
631
|
+
type: "response",
|
|
632
|
+
command: "parse",
|
|
633
|
+
success: false,
|
|
634
|
+
error: "Failed to parse command: Unexpected token",
|
|
635
|
+
};
|
|
636
|
+
assert.equal(errorResp.command, "parse");
|
|
637
|
+
assert.equal(errorResp.success, false);
|
|
638
|
+
});
|
|
639
|
+
it("shutdown works in both v1 and v2 — no version gating", () => {
|
|
640
|
+
// shutdown returns success regardless of protocolVersion
|
|
641
|
+
const v1Shutdown = {
|
|
642
|
+
id: "s1",
|
|
643
|
+
type: "response",
|
|
644
|
+
command: "shutdown",
|
|
645
|
+
success: true,
|
|
646
|
+
};
|
|
647
|
+
const v2Shutdown = {
|
|
648
|
+
id: "s2",
|
|
649
|
+
type: "response",
|
|
650
|
+
command: "shutdown",
|
|
651
|
+
success: true,
|
|
652
|
+
};
|
|
653
|
+
assert.equal(v1Shutdown.success, true);
|
|
654
|
+
assert.equal(v2Shutdown.success, true);
|
|
655
|
+
});
|
|
656
|
+
});
|
|
657
|
+
// ============================================================================
|
|
658
|
+
// Protocol version detection logic (unit)
|
|
659
|
+
// ============================================================================
|
|
660
|
+
describe("Protocol version detection logic", () => {
|
|
661
|
+
it("simulates v1 lock when first command is non-init", () => {
|
|
662
|
+
let protocolVersion = 1;
|
|
663
|
+
let protocolLocked = false;
|
|
664
|
+
// Simulate first command being get_state
|
|
665
|
+
const command = { type: "get_state" };
|
|
666
|
+
if (!protocolLocked) {
|
|
667
|
+
protocolLocked = true;
|
|
668
|
+
if (command.type === "init") {
|
|
669
|
+
protocolVersion = 2;
|
|
670
|
+
}
|
|
671
|
+
else {
|
|
672
|
+
protocolVersion = 1;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
assert.equal(protocolVersion, 1);
|
|
676
|
+
assert.equal(protocolLocked, true);
|
|
677
|
+
});
|
|
678
|
+
it("simulates v2 lock when first command is init", () => {
|
|
679
|
+
let protocolVersion = 1;
|
|
680
|
+
let protocolLocked = false;
|
|
681
|
+
const command = { type: "init", protocolVersion: 2 };
|
|
682
|
+
if (!protocolLocked) {
|
|
683
|
+
protocolLocked = true;
|
|
684
|
+
if (command.type === "init") {
|
|
685
|
+
protocolVersion = 2;
|
|
686
|
+
}
|
|
687
|
+
else {
|
|
688
|
+
protocolVersion = 1;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
assert.equal(protocolVersion, 2);
|
|
692
|
+
assert.equal(protocolLocked, true);
|
|
693
|
+
});
|
|
694
|
+
it("rejects re-init after v2 lock", () => {
|
|
695
|
+
let protocolLocked = true; // already locked from first init
|
|
696
|
+
let errorMessage = null;
|
|
697
|
+
const command = { type: "init", protocolVersion: 2 };
|
|
698
|
+
if (protocolLocked && command.type === "init") {
|
|
699
|
+
errorMessage = "Protocol version already locked. init must be the first command.";
|
|
700
|
+
}
|
|
701
|
+
assert.ok(errorMessage !== null);
|
|
702
|
+
assert.ok(errorMessage.includes("already locked"));
|
|
703
|
+
});
|
|
704
|
+
it("rejects init after v1 lock", () => {
|
|
705
|
+
let protocolLocked = true; // already locked from first non-init command
|
|
706
|
+
let protocolVersion = 1;
|
|
707
|
+
let errorMessage = null;
|
|
708
|
+
const command = { type: "init", protocolVersion: 2 };
|
|
709
|
+
if (protocolLocked && command.type === "init") {
|
|
710
|
+
errorMessage = "Protocol version already locked. init must be the first command.";
|
|
711
|
+
}
|
|
712
|
+
assert.equal(protocolVersion, 1); // stays v1
|
|
713
|
+
assert.ok(errorMessage !== null);
|
|
714
|
+
});
|
|
715
|
+
it("extension_ui_response bypasses protocol detection", () => {
|
|
716
|
+
let protocolLocked = false;
|
|
717
|
+
let protocolDetectionTriggered = false;
|
|
718
|
+
// Simulate the handleInputLine logic
|
|
719
|
+
const parsed = { type: "extension_ui_response", id: "ui-1", value: "ok" };
|
|
720
|
+
if (parsed.type === "extension_ui_response") {
|
|
721
|
+
// Bypass — do not touch protocolLocked
|
|
722
|
+
}
|
|
723
|
+
else {
|
|
724
|
+
protocolDetectionTriggered = true;
|
|
725
|
+
if (!protocolLocked) {
|
|
726
|
+
protocolLocked = true;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
assert.equal(protocolLocked, false);
|
|
730
|
+
assert.equal(protocolDetectionTriggered, false);
|
|
731
|
+
});
|
|
732
|
+
});
|
|
733
|
+
// ============================================================================
|
|
734
|
+
// v2 event filter logic (unit)
|
|
735
|
+
// ============================================================================
|
|
736
|
+
describe("v2 event filter logic", () => {
|
|
737
|
+
/** Mimics the server-side event filter check: null means all events pass */
|
|
738
|
+
function shouldEmit(filter, eventType) {
|
|
739
|
+
return !filter || filter.has(eventType);
|
|
740
|
+
}
|
|
741
|
+
it("null filter passes all events", () => {
|
|
742
|
+
assert.equal(shouldEmit(null, "agent_end"), true);
|
|
743
|
+
assert.equal(shouldEmit(null, "cost_update"), true);
|
|
744
|
+
assert.equal(shouldEmit(null, "anything"), true);
|
|
745
|
+
});
|
|
746
|
+
it("filter with specific events passes matching events", () => {
|
|
747
|
+
const filter = new Set(["agent_end", "cost_update"]);
|
|
748
|
+
assert.equal(shouldEmit(filter, "agent_end"), true);
|
|
749
|
+
assert.equal(shouldEmit(filter, "cost_update"), true);
|
|
750
|
+
assert.equal(shouldEmit(filter, "execution_complete"), false);
|
|
751
|
+
assert.equal(shouldEmit(filter, "message_start"), false);
|
|
752
|
+
});
|
|
753
|
+
it("empty Set filter blocks all events", () => {
|
|
754
|
+
const filter = new Set();
|
|
755
|
+
assert.equal(shouldEmit(filter, "agent_end"), false);
|
|
756
|
+
assert.equal(shouldEmit(filter, "cost_update"), false);
|
|
757
|
+
assert.equal(shouldEmit(filter, "anything"), false);
|
|
758
|
+
assert.equal(filter.size, 0);
|
|
759
|
+
});
|
|
760
|
+
it("wildcard subscribe resets filter to null", () => {
|
|
761
|
+
let eventFilter = new Set(["agent_end"]);
|
|
762
|
+
// Simulate subscribe with wildcard
|
|
763
|
+
const events = ["*"];
|
|
764
|
+
if (events.includes("*")) {
|
|
765
|
+
eventFilter = null;
|
|
766
|
+
}
|
|
767
|
+
else {
|
|
768
|
+
eventFilter = new Set(events);
|
|
769
|
+
}
|
|
770
|
+
assert.equal(eventFilter, null);
|
|
771
|
+
});
|
|
772
|
+
it("subscribe replaces previous filter", () => {
|
|
773
|
+
let eventFilter = new Set(["agent_end"]);
|
|
774
|
+
// Subscribe with different events
|
|
775
|
+
const events = ["cost_update", "execution_complete"];
|
|
776
|
+
if (events.includes("*")) {
|
|
777
|
+
eventFilter = null;
|
|
778
|
+
}
|
|
779
|
+
else {
|
|
780
|
+
eventFilter = new Set(events);
|
|
781
|
+
}
|
|
782
|
+
assert.equal(eventFilter.has("agent_end"), false);
|
|
783
|
+
assert.equal(eventFilter.has("cost_update"), true);
|
|
784
|
+
assert.equal(eventFilter.has("execution_complete"), true);
|
|
785
|
+
});
|
|
786
|
+
it("filter applies to both regular and synthesized v2 events", () => {
|
|
787
|
+
const eventFilter = new Set(["execution_complete"]);
|
|
788
|
+
// Regular event
|
|
789
|
+
assert.equal(eventFilter.has("agent_end"), false); // filtered out
|
|
790
|
+
// Synthesized v2 event
|
|
791
|
+
assert.equal(eventFilter.has("execution_complete"), true); // passes
|
|
792
|
+
assert.equal(eventFilter.has("cost_update"), false); // filtered out
|
|
793
|
+
});
|
|
794
|
+
});
|
|
795
|
+
// ============================================================================
|
|
796
|
+
// v2 runId injection logic (unit)
|
|
797
|
+
// ============================================================================
|
|
798
|
+
describe("v2 runId injection", () => {
|
|
799
|
+
it("runId is present when protocolVersion is 2 and command is prompt/steer/follow_up", () => {
|
|
800
|
+
const protocolVersion = 2;
|
|
801
|
+
const commands = ["prompt", "steer", "follow_up"];
|
|
802
|
+
for (const cmdType of commands) {
|
|
803
|
+
const runId = protocolVersion === 2 ? `run-${cmdType}-uuid` : undefined;
|
|
804
|
+
assert.ok(runId !== undefined, `runId should be generated for ${cmdType} in v2`);
|
|
805
|
+
assert.ok(typeof runId === "string");
|
|
806
|
+
}
|
|
807
|
+
});
|
|
808
|
+
it("runId is undefined when protocolVersion is 1", () => {
|
|
809
|
+
// Test the v1 path: runId should not be generated
|
|
810
|
+
function generateRunId(version) {
|
|
811
|
+
return version === 2 ? "run-uuid" : undefined;
|
|
812
|
+
}
|
|
813
|
+
assert.equal(generateRunId(1), undefined);
|
|
814
|
+
assert.ok(typeof generateRunId(2) === "string");
|
|
815
|
+
});
|
|
816
|
+
it("runId is injected into event output via spread", () => {
|
|
817
|
+
const currentRunId = "run-abc-123";
|
|
818
|
+
const event = { type: "message_start", message: { role: "assistant" } };
|
|
819
|
+
// v2 injection logic from rpc-mode.ts
|
|
820
|
+
const outputEvent = currentRunId ? { ...event, runId: currentRunId } : event;
|
|
821
|
+
assert.equal(outputEvent.runId, "run-abc-123");
|
|
822
|
+
assert.equal(outputEvent.type, "message_start");
|
|
823
|
+
});
|
|
824
|
+
it("runId is not injected when null", () => {
|
|
825
|
+
const currentRunId = null;
|
|
826
|
+
const event = { type: "message_start", message: { role: "assistant" } };
|
|
827
|
+
const outputEvent = currentRunId ? { ...event, runId: currentRunId } : event;
|
|
828
|
+
assert.equal(outputEvent.runId, undefined);
|
|
829
|
+
});
|
|
830
|
+
});
|
|
831
|
+
//# sourceMappingURL=rpc-protocol-v2.test.js.map
|