gsd-pi 2.51.0-dev.ae8f7cb → 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 +16 -16
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- 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/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_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 +2 -2
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- 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 +2 -2
- 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_client-reference-manifest.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_client-reference-manifest.js +1 -1
- 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_client-reference-manifest.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_client-reference-manifest.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_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/experimental/route.js +1 -1
- 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_client-reference-manifest.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_client-reference-manifest.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_client-reference-manifest.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_client-reference-manifest.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_client-reference-manifest.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_client-reference-manifest.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_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +1 -1
- 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_client-reference-manifest.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_client-reference-manifest.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_client-reference-manifest.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_client-reference-manifest.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_client-reference-manifest.js +1 -1
- 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_client-reference-manifest.js +1 -1
- 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_client-reference-manifest.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_client-reference-manifest.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_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 +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +2 -2
- 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 +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +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 +16 -16
- package/dist/web/standalone/.next/server/chunks/2229.js +2 -2
- 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/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/{webpack-cfc9a116e6450a6b.js → webpack-024d82be84800e52.js} +1 -1
- package/dist/web/standalone/.next/static/css/a58ef8a151aa0493.css +1 -0
- 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/css/de141508b083f922.css +0 -1
- /package/dist/resources/extensions/gsd/templates/{preferences.md → PREFERENCES.md} +0 -0
- /package/dist/web/standalone/.next/static/{5_KeZz1X0tXJK-d_4OhjB → KTe1kB5nPLQFIIFz2OcmI}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{5_KeZz1X0tXJK-d_4OhjB → KTe1kB5nPLQFIIFz2OcmI}/_ssgManifest.js +0 -0
- /package/src/resources/extensions/gsd/templates/{preferences.md → PREFERENCES.md} +0 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Server — registers 6 GSD orchestration tools on McpServer.
|
|
3
|
+
*
|
|
4
|
+
* Uses dynamic imports for @modelcontextprotocol/sdk because TS Node16
|
|
5
|
+
* cannot resolve the SDK's subpath exports statically (same pattern as
|
|
6
|
+
* src/mcp-server.ts in the main package).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { readFile, readdir, stat } from 'node:fs/promises';
|
|
10
|
+
import { join, resolve } from 'node:path';
|
|
11
|
+
import { z } from 'zod';
|
|
12
|
+
import type { SessionManager } from './session-manager.js';
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Constants
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
const MCP_PKG = '@modelcontextprotocol/sdk';
|
|
19
|
+
const SERVER_NAME = 'gsd';
|
|
20
|
+
const SERVER_VERSION = '2.51.0';
|
|
21
|
+
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Tool result helpers
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
/** Wrap a JSON-serializable value as MCP tool content. */
|
|
27
|
+
function jsonContent(data: unknown): { content: Array<{ type: 'text'; text: string }> } {
|
|
28
|
+
return { content: [{ type: 'text' as const, text: JSON.stringify(data, null, 2) }] };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Return an MCP error response. */
|
|
32
|
+
function errorContent(message: string): { isError: true; content: Array<{ type: 'text'; text: string }> } {
|
|
33
|
+
return { isError: true, content: [{ type: 'text' as const, text: message }] };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// gsd_query filesystem reader
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
async function readProjectState(projectDir: string, _query: string): Promise<Record<string, unknown>> {
|
|
41
|
+
const gsdDir = join(resolve(projectDir), '.gsd');
|
|
42
|
+
const result: Record<string, unknown> = { projectDir: resolve(projectDir) };
|
|
43
|
+
|
|
44
|
+
// STATE.md — current execution state
|
|
45
|
+
try {
|
|
46
|
+
result.state = await readFile(join(gsdDir, 'STATE.md'), 'utf-8');
|
|
47
|
+
} catch {
|
|
48
|
+
result.state = null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// PROJECT.md — project description
|
|
52
|
+
try {
|
|
53
|
+
result.project = await readFile(join(gsdDir, 'PROJECT.md'), 'utf-8');
|
|
54
|
+
} catch {
|
|
55
|
+
result.project = null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// REQUIREMENTS.md — requirement contract
|
|
59
|
+
try {
|
|
60
|
+
result.requirements = await readFile(join(gsdDir, 'REQUIREMENTS.md'), 'utf-8');
|
|
61
|
+
} catch {
|
|
62
|
+
result.requirements = null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// List milestones with basic metadata
|
|
66
|
+
const milestonesDir = join(gsdDir, 'milestones');
|
|
67
|
+
try {
|
|
68
|
+
const entries = await readdir(milestonesDir, { withFileTypes: true });
|
|
69
|
+
const milestones: Array<{ id: string; hasRoadmap: boolean; hasSummary: boolean }> = [];
|
|
70
|
+
for (const entry of entries) {
|
|
71
|
+
if (!entry.isDirectory()) continue;
|
|
72
|
+
const mDir = join(milestonesDir, entry.name);
|
|
73
|
+
const hasRoadmap = await fileExists(join(mDir, `${entry.name}-ROADMAP.md`));
|
|
74
|
+
const hasSummary = await fileExists(join(mDir, `${entry.name}-SUMMARY.md`));
|
|
75
|
+
milestones.push({ id: entry.name, hasRoadmap, hasSummary });
|
|
76
|
+
}
|
|
77
|
+
result.milestones = milestones;
|
|
78
|
+
} catch {
|
|
79
|
+
result.milestones = [];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function fileExists(path: string): Promise<boolean> {
|
|
86
|
+
try {
|
|
87
|
+
await stat(path);
|
|
88
|
+
return true;
|
|
89
|
+
} catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// MCP Server type — minimal interface for the dynamically-imported McpServer
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
interface McpServerInstance {
|
|
99
|
+
tool(name: string, description: string, params: Record<string, unknown>, handler: (args: Record<string, unknown>) => Promise<unknown>): unknown;
|
|
100
|
+
connect(transport: unknown): Promise<void>;
|
|
101
|
+
close(): Promise<void>;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
// createMcpServer
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Create and configure an MCP server with 6 GSD orchestration tools.
|
|
110
|
+
*
|
|
111
|
+
* Returns the McpServer instance — call `connect(transport)` to start serving.
|
|
112
|
+
* Uses dynamic imports for the MCP SDK to avoid TS subpath resolution issues.
|
|
113
|
+
*/
|
|
114
|
+
export async function createMcpServer(sessionManager: SessionManager): Promise<{
|
|
115
|
+
server: McpServerInstance;
|
|
116
|
+
}> {
|
|
117
|
+
// Dynamic import — same workaround as src/mcp-server.ts
|
|
118
|
+
const mcpMod = await import(`${MCP_PKG}/server/mcp.js`);
|
|
119
|
+
const McpServer = mcpMod.McpServer;
|
|
120
|
+
|
|
121
|
+
const server: McpServerInstance = new McpServer(
|
|
122
|
+
{ name: SERVER_NAME, version: SERVER_VERSION },
|
|
123
|
+
{ capabilities: { tools: {} } },
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
// -----------------------------------------------------------------------
|
|
127
|
+
// gsd_execute — start a new GSD auto-mode session
|
|
128
|
+
// -----------------------------------------------------------------------
|
|
129
|
+
server.tool(
|
|
130
|
+
'gsd_execute',
|
|
131
|
+
'Start a GSD auto-mode session for a project directory. Returns a sessionId for tracking.',
|
|
132
|
+
{
|
|
133
|
+
projectDir: z.string().describe('Absolute path to the project directory'),
|
|
134
|
+
command: z.string().optional().describe('Command to send (default: "/gsd auto")'),
|
|
135
|
+
model: z.string().optional().describe('Model ID override'),
|
|
136
|
+
bare: z.boolean().optional().describe('Run in bare mode (skip user config)'),
|
|
137
|
+
},
|
|
138
|
+
async (args: Record<string, unknown>) => {
|
|
139
|
+
const { projectDir, command, model, bare } = args as {
|
|
140
|
+
projectDir: string; command?: string; model?: string; bare?: boolean;
|
|
141
|
+
};
|
|
142
|
+
try {
|
|
143
|
+
const sessionId = await sessionManager.startSession(projectDir, { command, model, bare });
|
|
144
|
+
return jsonContent({ sessionId, status: 'started' });
|
|
145
|
+
} catch (err) {
|
|
146
|
+
return errorContent(err instanceof Error ? err.message : String(err));
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
// -----------------------------------------------------------------------
|
|
152
|
+
// gsd_status — poll session status
|
|
153
|
+
// -----------------------------------------------------------------------
|
|
154
|
+
server.tool(
|
|
155
|
+
'gsd_status',
|
|
156
|
+
'Get the current status of a GSD session including progress, recent events, and pending blockers.',
|
|
157
|
+
{
|
|
158
|
+
sessionId: z.string().describe('Session ID returned from gsd_execute'),
|
|
159
|
+
},
|
|
160
|
+
async (args: Record<string, unknown>) => {
|
|
161
|
+
const { sessionId } = args as { sessionId: string };
|
|
162
|
+
try {
|
|
163
|
+
const session = sessionManager.getSession(sessionId);
|
|
164
|
+
if (!session) return errorContent(`Session not found: ${sessionId}`);
|
|
165
|
+
|
|
166
|
+
const durationMs = Date.now() - session.startTime;
|
|
167
|
+
const toolCallCount = session.events.filter(
|
|
168
|
+
(e) => (e as Record<string, unknown>).type === 'tool_use' ||
|
|
169
|
+
(e as Record<string, unknown>).type === 'tool_execution_start'
|
|
170
|
+
).length;
|
|
171
|
+
|
|
172
|
+
return jsonContent({
|
|
173
|
+
status: session.status,
|
|
174
|
+
progress: {
|
|
175
|
+
eventCount: session.events.length,
|
|
176
|
+
toolCalls: toolCallCount,
|
|
177
|
+
},
|
|
178
|
+
recentEvents: session.events.slice(-10),
|
|
179
|
+
pendingBlocker: session.pendingBlocker
|
|
180
|
+
? {
|
|
181
|
+
id: session.pendingBlocker.id,
|
|
182
|
+
method: session.pendingBlocker.method,
|
|
183
|
+
message: session.pendingBlocker.message,
|
|
184
|
+
}
|
|
185
|
+
: null,
|
|
186
|
+
cost: session.cost,
|
|
187
|
+
durationMs,
|
|
188
|
+
});
|
|
189
|
+
} catch (err) {
|
|
190
|
+
return errorContent(err instanceof Error ? err.message : String(err));
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
// -----------------------------------------------------------------------
|
|
196
|
+
// gsd_result — get accumulated session result
|
|
197
|
+
// -----------------------------------------------------------------------
|
|
198
|
+
server.tool(
|
|
199
|
+
'gsd_result',
|
|
200
|
+
'Get the result of a GSD session. Returns partial results if the session is still running.',
|
|
201
|
+
{
|
|
202
|
+
sessionId: z.string().describe('Session ID returned from gsd_execute'),
|
|
203
|
+
},
|
|
204
|
+
async (args: Record<string, unknown>) => {
|
|
205
|
+
const { sessionId } = args as { sessionId: string };
|
|
206
|
+
try {
|
|
207
|
+
const result = sessionManager.getResult(sessionId);
|
|
208
|
+
return jsonContent(result);
|
|
209
|
+
} catch (err) {
|
|
210
|
+
return errorContent(err instanceof Error ? err.message : String(err));
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
// -----------------------------------------------------------------------
|
|
216
|
+
// gsd_cancel — cancel a running session
|
|
217
|
+
// -----------------------------------------------------------------------
|
|
218
|
+
server.tool(
|
|
219
|
+
'gsd_cancel',
|
|
220
|
+
'Cancel a running GSD session. Aborts the current operation and stops the process.',
|
|
221
|
+
{
|
|
222
|
+
sessionId: z.string().describe('Session ID returned from gsd_execute'),
|
|
223
|
+
},
|
|
224
|
+
async (args: Record<string, unknown>) => {
|
|
225
|
+
const { sessionId } = args as { sessionId: string };
|
|
226
|
+
try {
|
|
227
|
+
await sessionManager.cancelSession(sessionId);
|
|
228
|
+
return jsonContent({ cancelled: true });
|
|
229
|
+
} catch (err) {
|
|
230
|
+
return errorContent(err instanceof Error ? err.message : String(err));
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
// -----------------------------------------------------------------------
|
|
236
|
+
// gsd_query — read project state from filesystem (no session needed)
|
|
237
|
+
// -----------------------------------------------------------------------
|
|
238
|
+
server.tool(
|
|
239
|
+
'gsd_query',
|
|
240
|
+
'Query GSD project state from the filesystem. Returns STATE.md, PROJECT.md, requirements, and milestone listing. Does not require an active session.',
|
|
241
|
+
{
|
|
242
|
+
projectDir: z.string().describe('Absolute path to the project directory'),
|
|
243
|
+
query: z.string().describe('What to query (e.g. "status", "milestones", "requirements")'),
|
|
244
|
+
},
|
|
245
|
+
async (args: Record<string, unknown>) => {
|
|
246
|
+
const { projectDir, query } = args as { projectDir: string; query: string };
|
|
247
|
+
try {
|
|
248
|
+
const state = await readProjectState(projectDir, query);
|
|
249
|
+
return jsonContent(state);
|
|
250
|
+
} catch (err) {
|
|
251
|
+
return errorContent(err instanceof Error ? err.message : String(err));
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
// -----------------------------------------------------------------------
|
|
257
|
+
// gsd_resolve_blocker — resolve a pending blocker
|
|
258
|
+
// -----------------------------------------------------------------------
|
|
259
|
+
server.tool(
|
|
260
|
+
'gsd_resolve_blocker',
|
|
261
|
+
'Resolve a pending blocker in a GSD session by sending a response to the UI request.',
|
|
262
|
+
{
|
|
263
|
+
sessionId: z.string().describe('Session ID returned from gsd_execute'),
|
|
264
|
+
response: z.string().describe('Response to send for the pending blocker'),
|
|
265
|
+
},
|
|
266
|
+
async (args: Record<string, unknown>) => {
|
|
267
|
+
const { sessionId, response } = args as { sessionId: string; response: string };
|
|
268
|
+
try {
|
|
269
|
+
await sessionManager.resolveBlocker(sessionId, response);
|
|
270
|
+
return jsonContent({ resolved: true });
|
|
271
|
+
} catch (err) {
|
|
272
|
+
return errorContent(err instanceof Error ? err.message : String(err));
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
return { server };
|
|
278
|
+
}
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SessionManager — manages RpcClient lifecycle for background GSD execution.
|
|
3
|
+
*
|
|
4
|
+
* One active session per projectDir. Tracks events in a ring buffer,
|
|
5
|
+
* detects blockers, tracks terminal state, and accumulates cost using
|
|
6
|
+
* the cumulative-max pattern (K004).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { execSync } from 'node:child_process';
|
|
10
|
+
import { resolve } from 'node:path';
|
|
11
|
+
import { RpcClient } from '@gsd/rpc-client';
|
|
12
|
+
import type { SdkAgentEvent, RpcInitResult, RpcCostUpdateEvent, RpcExtensionUIRequest } from '@gsd/rpc-client';
|
|
13
|
+
import type {
|
|
14
|
+
ManagedSession,
|
|
15
|
+
ExecuteOptions,
|
|
16
|
+
PendingBlocker,
|
|
17
|
+
CostAccumulator,
|
|
18
|
+
SessionStatus,
|
|
19
|
+
} from './types.js';
|
|
20
|
+
import { MAX_EVENTS, INIT_TIMEOUT_MS } from './types.js';
|
|
21
|
+
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Inlined detection logic (from headless-events.ts — no internal package imports)
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
const FIRE_AND_FORGET_METHODS = new Set([
|
|
27
|
+
'notify', 'setStatus', 'setWidget', 'setTitle', 'set_editor_text',
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
const TERMINAL_PREFIXES = ['auto-mode stopped', 'step-mode stopped'];
|
|
31
|
+
|
|
32
|
+
function isTerminalNotification(event: Record<string, unknown>): boolean {
|
|
33
|
+
if (event.type !== 'extension_ui_request' || event.method !== 'notify') return false;
|
|
34
|
+
const message = String(event.message ?? '').toLowerCase();
|
|
35
|
+
return TERMINAL_PREFIXES.some((prefix) => message.startsWith(prefix));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isBlockedNotification(event: Record<string, unknown>): boolean {
|
|
39
|
+
if (event.type !== 'extension_ui_request' || event.method !== 'notify') return false;
|
|
40
|
+
const message = String(event.message ?? '').toLowerCase();
|
|
41
|
+
return message.includes('blocked:');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function isBlockingUIRequest(event: Record<string, unknown>): boolean {
|
|
45
|
+
if (event.type !== 'extension_ui_request') return false;
|
|
46
|
+
const method = String(event.method ?? '');
|
|
47
|
+
return !FIRE_AND_FORGET_METHODS.has(method);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// SessionManager
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
|
|
54
|
+
export class SessionManager {
|
|
55
|
+
/** Sessions keyed by projectDir for duplicate-start prevention */
|
|
56
|
+
private sessions = new Map<string, ManagedSession>();
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Start a new GSD auto-mode session for the given project directory.
|
|
60
|
+
*
|
|
61
|
+
* Rejects if a session already exists for this projectDir.
|
|
62
|
+
* Creates an RpcClient, starts the process, performs the v2 init handshake,
|
|
63
|
+
* wires event tracking, and sends '/gsd auto' to begin execution.
|
|
64
|
+
*/
|
|
65
|
+
async startSession(projectDir: string, options: ExecuteOptions = {}): Promise<string> {
|
|
66
|
+
if (!projectDir || projectDir.trim() === '') {
|
|
67
|
+
throw new Error('projectDir is required and cannot be empty');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const resolvedDir = resolve(projectDir);
|
|
71
|
+
|
|
72
|
+
const existing = this.sessions.get(resolvedDir);
|
|
73
|
+
if (existing) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`Session already active for ${resolvedDir} (sessionId: ${existing.sessionId}, status: ${existing.status})`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const cliPath = options.cliPath ?? SessionManager.resolveCLIPath();
|
|
80
|
+
|
|
81
|
+
const args: string[] = ['--mode', 'rpc'];
|
|
82
|
+
if (options.model) args.push('--model', options.model);
|
|
83
|
+
if (options.bare) args.push('--bare');
|
|
84
|
+
|
|
85
|
+
const client = new RpcClient({
|
|
86
|
+
cliPath,
|
|
87
|
+
cwd: resolvedDir,
|
|
88
|
+
args,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Build the session shell before async operations so we can track state
|
|
92
|
+
const session: ManagedSession = {
|
|
93
|
+
sessionId: '', // filled after init
|
|
94
|
+
projectDir: resolvedDir,
|
|
95
|
+
status: 'starting',
|
|
96
|
+
client,
|
|
97
|
+
events: [],
|
|
98
|
+
pendingBlocker: null,
|
|
99
|
+
cost: { totalCost: 0, tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 } },
|
|
100
|
+
startTime: Date.now(),
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Insert into map early (keyed by dir) so concurrent starts are rejected
|
|
104
|
+
this.sessions.set(resolvedDir, session);
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
// Start the process with timeout
|
|
108
|
+
await Promise.race([
|
|
109
|
+
client.start(),
|
|
110
|
+
timeout(INIT_TIMEOUT_MS, `RpcClient.start() timed out after ${INIT_TIMEOUT_MS}ms`),
|
|
111
|
+
]);
|
|
112
|
+
|
|
113
|
+
// Perform v2 init handshake
|
|
114
|
+
const initResult: RpcInitResult = await Promise.race([
|
|
115
|
+
client.init(),
|
|
116
|
+
timeout(INIT_TIMEOUT_MS, `RpcClient.init() timed out after ${INIT_TIMEOUT_MS}ms`),
|
|
117
|
+
]) as RpcInitResult;
|
|
118
|
+
|
|
119
|
+
session.sessionId = initResult.sessionId;
|
|
120
|
+
session.status = 'running';
|
|
121
|
+
|
|
122
|
+
// Wire event tracking
|
|
123
|
+
session.unsubscribe = client.onEvent((event: SdkAgentEvent) => {
|
|
124
|
+
this.handleEvent(session, event);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Kick off auto-mode
|
|
128
|
+
const command = options.command ?? '/gsd auto';
|
|
129
|
+
await client.prompt(command);
|
|
130
|
+
|
|
131
|
+
return session.sessionId;
|
|
132
|
+
} catch (err) {
|
|
133
|
+
session.status = 'error';
|
|
134
|
+
session.error = err instanceof Error ? err.message : String(err);
|
|
135
|
+
|
|
136
|
+
// Attempt cleanup
|
|
137
|
+
try { await client.stop(); } catch { /* swallow cleanup errors */ }
|
|
138
|
+
|
|
139
|
+
// Keep session in map so callers can inspect the error
|
|
140
|
+
throw new Error(`Failed to start session for ${resolvedDir}: ${session.error}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Look up a session by sessionId.
|
|
146
|
+
* Linear scan is fine — we expect <10 concurrent sessions.
|
|
147
|
+
*/
|
|
148
|
+
getSession(sessionId: string): ManagedSession | undefined {
|
|
149
|
+
for (const session of this.sessions.values()) {
|
|
150
|
+
if (session.sessionId === sessionId) return session;
|
|
151
|
+
}
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Look up a session by project directory (direct map lookup).
|
|
157
|
+
*/
|
|
158
|
+
getSessionByDir(projectDir: string): ManagedSession | undefined {
|
|
159
|
+
return this.sessions.get(resolve(projectDir));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Resolve a pending blocker by sending a UI response.
|
|
164
|
+
*/
|
|
165
|
+
async resolveBlocker(sessionId: string, response: string): Promise<void> {
|
|
166
|
+
const session = this.getSession(sessionId);
|
|
167
|
+
if (!session) throw new Error(`Session not found: ${sessionId}`);
|
|
168
|
+
if (!session.pendingBlocker) throw new Error(`No pending blocker for session ${sessionId}`);
|
|
169
|
+
|
|
170
|
+
const blocker = session.pendingBlocker;
|
|
171
|
+
session.client.sendUIResponse(blocker.id, { value: response });
|
|
172
|
+
session.pendingBlocker = null;
|
|
173
|
+
if (session.status === 'blocked') {
|
|
174
|
+
session.status = 'running';
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Cancel a running session — abort current operation then stop the process.
|
|
180
|
+
*/
|
|
181
|
+
async cancelSession(sessionId: string): Promise<void> {
|
|
182
|
+
const session = this.getSession(sessionId);
|
|
183
|
+
if (!session) throw new Error(`Session not found: ${sessionId}`);
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
await session.client.abort();
|
|
187
|
+
} catch { /* may already be stopped */ }
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
await session.client.stop();
|
|
191
|
+
} catch { /* swallow */ }
|
|
192
|
+
|
|
193
|
+
session.status = 'cancelled';
|
|
194
|
+
session.unsubscribe?.();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Build a HeadlessJsonResult-shaped object from accumulated session state.
|
|
199
|
+
*/
|
|
200
|
+
getResult(sessionId: string): Record<string, unknown> {
|
|
201
|
+
const session = this.getSession(sessionId);
|
|
202
|
+
if (!session) throw new Error(`Session not found: ${sessionId}`);
|
|
203
|
+
|
|
204
|
+
const durationMs = Date.now() - session.startTime;
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
sessionId: session.sessionId,
|
|
208
|
+
projectDir: session.projectDir,
|
|
209
|
+
status: session.status,
|
|
210
|
+
durationMs,
|
|
211
|
+
cost: session.cost,
|
|
212
|
+
recentEvents: session.events.slice(-10),
|
|
213
|
+
pendingBlocker: session.pendingBlocker
|
|
214
|
+
? { id: session.pendingBlocker.id, method: session.pendingBlocker.method, message: session.pendingBlocker.message }
|
|
215
|
+
: null,
|
|
216
|
+
error: session.error ?? null,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Stop all active sessions and clean up resources.
|
|
222
|
+
*/
|
|
223
|
+
async cleanup(): Promise<void> {
|
|
224
|
+
const stopPromises: Promise<void>[] = [];
|
|
225
|
+
|
|
226
|
+
for (const session of this.sessions.values()) {
|
|
227
|
+
session.unsubscribe?.();
|
|
228
|
+
if (session.status === 'running' || session.status === 'starting' || session.status === 'blocked') {
|
|
229
|
+
stopPromises.push(
|
|
230
|
+
session.client.stop().catch(() => { /* swallow */ })
|
|
231
|
+
);
|
|
232
|
+
session.status = 'cancelled';
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
await Promise.allSettled(stopPromises);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Resolve the GSD CLI path.
|
|
241
|
+
*
|
|
242
|
+
* 1. GSD_CLI_PATH env var (highest priority)
|
|
243
|
+
* 2. `which gsd` → resolve to the actual dist/cli.js
|
|
244
|
+
*/
|
|
245
|
+
static resolveCLIPath(): string {
|
|
246
|
+
// Check env var first
|
|
247
|
+
const envPath = process.env['GSD_CLI_PATH'];
|
|
248
|
+
if (envPath) return resolve(envPath);
|
|
249
|
+
|
|
250
|
+
// Fallback: locate `gsd` via which
|
|
251
|
+
try {
|
|
252
|
+
const gsdBin = execSync('which gsd', { encoding: 'utf-8' }).trim();
|
|
253
|
+
if (gsdBin) {
|
|
254
|
+
// gsd bin is typically a symlink to dist/loader.js — return the resolved path
|
|
255
|
+
return resolve(gsdBin);
|
|
256
|
+
}
|
|
257
|
+
} catch {
|
|
258
|
+
// which failed
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
throw new Error(
|
|
262
|
+
'Cannot find GSD CLI. Set GSD_CLI_PATH environment variable or ensure `gsd` is in PATH.'
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// ---------------------------------------------------------------------------
|
|
267
|
+
// Private: Event Handling
|
|
268
|
+
// ---------------------------------------------------------------------------
|
|
269
|
+
|
|
270
|
+
private handleEvent(session: ManagedSession, event: SdkAgentEvent): void {
|
|
271
|
+
// Ring buffer: push and trim
|
|
272
|
+
session.events.push(event);
|
|
273
|
+
if (session.events.length > MAX_EVENTS) {
|
|
274
|
+
session.events.splice(0, session.events.length - MAX_EVENTS);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Cost tracking (K004 — cumulative-max)
|
|
278
|
+
if (event.type === 'cost_update') {
|
|
279
|
+
const costEvent = event as unknown as RpcCostUpdateEvent;
|
|
280
|
+
session.cost.totalCost = Math.max(session.cost.totalCost, costEvent.cumulativeCost ?? 0);
|
|
281
|
+
if (costEvent.tokens) {
|
|
282
|
+
session.cost.tokens.input = Math.max(session.cost.tokens.input, costEvent.tokens.input ?? 0);
|
|
283
|
+
session.cost.tokens.output = Math.max(session.cost.tokens.output, costEvent.tokens.output ?? 0);
|
|
284
|
+
session.cost.tokens.cacheRead = Math.max(session.cost.tokens.cacheRead, costEvent.tokens.cacheRead ?? 0);
|
|
285
|
+
session.cost.tokens.cacheWrite = Math.max(session.cost.tokens.cacheWrite, costEvent.tokens.cacheWrite ?? 0);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Terminal detection — auto-mode/step-mode stopped
|
|
290
|
+
if (isTerminalNotification(event as Record<string, unknown>)) {
|
|
291
|
+
// Check if it's a blocked stop (not truly terminal — it's a blocker)
|
|
292
|
+
if (isBlockedNotification(event as Record<string, unknown>)) {
|
|
293
|
+
session.status = 'blocked';
|
|
294
|
+
session.pendingBlocker = extractBlocker(event);
|
|
295
|
+
} else {
|
|
296
|
+
session.status = 'completed';
|
|
297
|
+
session.unsubscribe?.();
|
|
298
|
+
}
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Blocker detection — non-fire-and-forget extension_ui_request
|
|
303
|
+
if (isBlockingUIRequest(event as Record<string, unknown>)) {
|
|
304
|
+
session.status = 'blocked';
|
|
305
|
+
session.pendingBlocker = extractBlocker(event);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// ---------------------------------------------------------------------------
|
|
311
|
+
// Helpers
|
|
312
|
+
// ---------------------------------------------------------------------------
|
|
313
|
+
|
|
314
|
+
function timeout(ms: number, message: string): Promise<never> {
|
|
315
|
+
return new Promise((_, reject) => {
|
|
316
|
+
setTimeout(() => reject(new Error(message)), ms);
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function extractBlocker(event: SdkAgentEvent): PendingBlocker {
|
|
321
|
+
const uiEvent = event as unknown as RpcExtensionUIRequest;
|
|
322
|
+
return {
|
|
323
|
+
id: String(uiEvent.id ?? ''),
|
|
324
|
+
method: String(uiEvent.method ?? ''),
|
|
325
|
+
message: String((uiEvent as Record<string, unknown>).title ?? (uiEvent as Record<string, unknown>).message ?? ''),
|
|
326
|
+
event: uiEvent,
|
|
327
|
+
};
|
|
328
|
+
}
|