gsd-pi 2.72.0 → 2.73.0-dev.27730dc
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 +12 -2
- package/dist/cli.js +59 -50
- package/dist/help-text.js +1 -1
- package/dist/onboarding.js +10 -0
- package/dist/resources/extensions/async-jobs/await-tool.js +7 -4
- package/dist/resources/extensions/async-jobs/job-manager.js +28 -3
- package/dist/resources/extensions/claude-code-cli/partial-builder.js +40 -12
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +48 -23
- package/dist/resources/extensions/gsd/auto/loop.js +84 -1
- package/dist/resources/extensions/gsd/auto-dispatch.js +5 -3
- package/dist/resources/extensions/gsd/auto-post-unit.js +6 -0
- package/dist/resources/extensions/gsd/auto-prompts.js +9 -6
- package/dist/resources/extensions/gsd/auto-recovery.js +11 -0
- package/dist/resources/extensions/gsd/auto.js +30 -20
- package/dist/resources/extensions/gsd/bootstrap/crash-log.js +31 -0
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +18 -7
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +9 -11
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +6 -1
- package/dist/resources/extensions/gsd/commands-handlers.js +4 -1
- package/dist/resources/extensions/gsd/context-injector.js +1 -1
- package/dist/resources/extensions/gsd/crash-recovery.js +51 -0
- package/dist/resources/extensions/gsd/custom-workflow-engine.js +3 -7
- package/dist/resources/extensions/gsd/definition-io.js +15 -0
- package/dist/resources/extensions/gsd/dispatch-guard.js +4 -0
- package/dist/resources/extensions/gsd/doctor-runtime-checks.js +6 -3
- package/dist/resources/extensions/gsd/git-service.js +11 -8
- package/dist/resources/extensions/gsd/gitignore.js +12 -6
- package/dist/resources/extensions/gsd/gsd-db.js +85 -8
- package/dist/resources/extensions/gsd/key-manager.js +2 -0
- package/dist/resources/extensions/gsd/milestone-actions.js +19 -1
- package/dist/resources/extensions/gsd/preferences-skills.js +2 -34
- package/dist/resources/extensions/gsd/preferences-types.js +15 -0
- package/dist/resources/extensions/gsd/preferences.js +16 -3
- package/dist/resources/extensions/gsd/prompt-loader.js +4 -1
- package/dist/resources/extensions/gsd/prompts/discuss.md +122 -13
- package/dist/resources/extensions/gsd/prompts/system.md +1 -1
- package/dist/resources/extensions/gsd/state.js +21 -1
- package/dist/resources/extensions/gsd/workflow-projections.js +7 -0
- package/dist/resources/extensions/gsd/worktree-manager.js +30 -3
- package/dist/resources/extensions/gsd/write-intercept.js +10 -1
- package/dist/resources/extensions/ollama/index.js +4 -5
- package/dist/resources/extensions/ollama/ollama-client.js +35 -6
- package/dist/resources/extensions/ollama/ollama-discovery.js +32 -6
- package/dist/startup-model-validation.js +8 -5
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +10 -10
- 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/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 +1 -1
- 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 +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- 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 +3 -3
- 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 +1 -1
- 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/notifications/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/notifications/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 +3 -3
- 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 +4 -4
- 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 +4 -4
- 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 +3 -3
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- 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 +10 -10
- package/dist/web/standalone/.next/server/chunks/2331.js +16 -16
- package/dist/web/standalone/.next/server/chunks/4741.js +12 -12
- package/dist/web/standalone/.next/server/chunks/5822.js +2 -2
- package/dist/web/standalone/.next/server/chunks/63.js +8 -8
- package/dist/web/standalone/.next/server/chunks/6897.js +3 -3
- package/dist/web/standalone/.next/server/edge-runtime-webpack.js +2 -0
- package/dist/web/standalone/.next/server/functions-config-manifest.json +0 -9
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-manifest.json +29 -2
- package/dist/web/standalone/.next/server/middleware.js +4 -12
- 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 +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/server/webpack-runtime.js +1 -1
- 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-f1e30ab6bb269149.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/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/package.json +1 -1
- package/packages/pi-ai/dist/env-api-keys.js +1 -0
- package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
- package/packages/pi-ai/dist/models.custom.d.ts +105 -0
- package/packages/pi-ai/dist/models.custom.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.custom.js +97 -0
- package/packages/pi-ai/dist/models.custom.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +648 -140
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +867 -370
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.test.d.ts +2 -0
- package/packages/pi-ai/dist/models.generated.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/models.generated.test.js +334 -0
- package/packages/pi-ai/dist/models.generated.test.js.map +1 -0
- package/packages/pi-ai/dist/models.test.js +105 -0
- package/packages/pi-ai/dist/models.test.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +1 -1
- 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/dist/utils/oauth/github-copilot.d.ts.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/github-copilot.js +5 -1
- package/packages/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/github-copilot.test.d.ts +2 -0
- package/packages/pi-ai/dist/utils/oauth/github-copilot.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/utils/oauth/github-copilot.test.js +57 -0
- package/packages/pi-ai/dist/utils/oauth/github-copilot.test.js.map +1 -0
- package/packages/pi-ai/src/env-api-keys.ts +1 -0
- package/packages/pi-ai/src/models.custom.ts +98 -0
- package/packages/pi-ai/src/models.generated.test.ts +373 -0
- package/packages/pi-ai/src/models.generated.ts +867 -370
- package/packages/pi-ai/src/models.test.ts +135 -0
- package/packages/pi-ai/src/types.ts +1 -0
- package/packages/pi-ai/src/utils/oauth/github-copilot.test.ts +71 -0
- package/packages/pi-ai/src/utils/oauth/github-copilot.ts +4 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js +27 -0
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +25 -67
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +9 -0
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +36 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +87 -12
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts +1 -0
- 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 +22 -9
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js +63 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js.map +1 -0
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/auth-storage.test.ts +38 -0
- package/packages/pi-coding-agent/src/core/auth-storage.ts +1 -1
- package/packages/pi-coding-agent/src/core/model-resolver.ts +26 -69
- package/packages/pi-coding-agent/src/core/sdk.ts +10 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +72 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +84 -12
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.test.ts +71 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +23 -9
- package/packages/pi-tui/dist/components/__tests__/editor.test.js +12 -0
- package/packages/pi-tui/dist/components/__tests__/editor.test.js.map +1 -1
- package/packages/pi-tui/dist/components/__tests__/input.test.js +12 -0
- package/packages/pi-tui/dist/components/__tests__/input.test.js.map +1 -1
- package/packages/pi-tui/dist/keys.d.ts.map +1 -1
- package/packages/pi-tui/dist/keys.js +27 -0
- package/packages/pi-tui/dist/keys.js.map +1 -1
- package/packages/pi-tui/src/components/__tests__/editor.test.ts +18 -0
- package/packages/pi-tui/src/components/__tests__/input.test.ts +18 -0
- package/packages/pi-tui/src/keys.ts +32 -0
- package/pkg/package.json +1 -1
- package/src/resources/extensions/async-jobs/await-tool.test.ts +40 -7
- package/src/resources/extensions/async-jobs/await-tool.ts +7 -4
- package/src/resources/extensions/async-jobs/job-manager.ts +33 -3
- package/src/resources/extensions/claude-code-cli/partial-builder.ts +45 -12
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +49 -24
- package/src/resources/extensions/claude-code-cli/tests/partial-builder.test.ts +91 -2
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +112 -0
- package/src/resources/extensions/gsd/auto/loop.ts +89 -1
- package/src/resources/extensions/gsd/auto-dispatch.ts +5 -0
- package/src/resources/extensions/gsd/auto-post-unit.ts +7 -0
- package/src/resources/extensions/gsd/auto-prompts.ts +9 -3
- package/src/resources/extensions/gsd/auto-recovery.ts +10 -0
- package/src/resources/extensions/gsd/auto.ts +30 -20
- package/src/resources/extensions/gsd/bootstrap/crash-log.ts +32 -0
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +19 -7
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +8 -10
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +8 -1
- package/src/resources/extensions/gsd/commands-handlers.ts +5 -1
- package/src/resources/extensions/gsd/context-injector.ts +1 -1
- package/src/resources/extensions/gsd/crash-recovery.ts +59 -0
- package/src/resources/extensions/gsd/custom-workflow-engine.ts +4 -8
- package/src/resources/extensions/gsd/definition-io.ts +18 -0
- package/src/resources/extensions/gsd/dispatch-guard.ts +5 -0
- package/src/resources/extensions/gsd/doctor-runtime-checks.ts +6 -3
- package/src/resources/extensions/gsd/git-service.ts +11 -8
- package/src/resources/extensions/gsd/gitignore.ts +12 -6
- package/src/resources/extensions/gsd/gsd-db.ts +106 -8
- package/src/resources/extensions/gsd/key-manager.ts +2 -0
- package/src/resources/extensions/gsd/milestone-actions.ts +19 -1
- package/src/resources/extensions/gsd/preferences-skills.ts +2 -36
- package/src/resources/extensions/gsd/preferences-types.ts +16 -0
- package/src/resources/extensions/gsd/preferences.ts +19 -6
- package/src/resources/extensions/gsd/prompt-loader.ts +6 -1
- package/src/resources/extensions/gsd/prompts/discuss.md +122 -13
- package/src/resources/extensions/gsd/prompts/system.md +1 -1
- package/src/resources/extensions/gsd/state.ts +20 -0
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/block-db-writes.test.ts +63 -0
- package/src/resources/extensions/gsd/tests/crash-handler-secondary.test.ts +235 -0
- package/src/resources/extensions/gsd/tests/definition-io.test.ts +57 -0
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +26 -0
- package/src/resources/extensions/gsd/tests/doctor-heal-fixable-warnings.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/false-degraded-mode-warning.test.ts +104 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +165 -5
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +8 -6
- package/src/resources/extensions/gsd/tests/key-manager.test.ts +63 -0
- package/src/resources/extensions/gsd/tests/memory-pressure-stuck-state.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/park-milestone.test.ts +64 -0
- package/src/resources/extensions/gsd/tests/plan-milestone-artifact-verification.test.ts +62 -0
- package/src/resources/extensions/gsd/tests/post-unit-state-rebuild.test.ts +34 -0
- package/src/resources/extensions/gsd/tests/preferences-formatting.test.ts +87 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +53 -0
- package/src/resources/extensions/gsd/tests/projection-regression.test.ts +96 -1
- package/src/resources/extensions/gsd/tests/prompt-loader-working-directory.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +97 -0
- package/src/resources/extensions/gsd/tests/stale-slice-rows.test.ts +41 -0
- package/src/resources/extensions/gsd/tests/subagent-model-dispatch.test.ts +267 -0
- package/src/resources/extensions/gsd/workflow-projections.ts +8 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +29 -3
- package/src/resources/extensions/gsd/write-intercept.ts +10 -1
- package/src/resources/extensions/ollama/index.ts +4 -5
- package/src/resources/extensions/ollama/ollama-client.ts +35 -6
- package/src/resources/extensions/ollama/ollama-discovery.ts +37 -6
- package/src/resources/extensions/ollama/tests/ollama-discovery.test.ts +54 -0
- package/dist/resources/extensions/gsd/auto-observability.js +0 -54
- package/dist/resources/extensions/gsd/file-watcher.js +0 -80
- package/dist/resources/extensions/gsd/rtk-status.js +0 -43
- package/dist/web/standalone/.next/static/chunks/app/page-7115e62689b5fd84.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/src/resources/extensions/gsd/auto-observability.ts +0 -72
- package/src/resources/extensions/gsd/file-watcher.ts +0 -100
- package/src/resources/extensions/gsd/rtk-status.ts +0 -53
- /package/dist/web/standalone/.next/static/{Y0I7CjXJl-tWoV__KidV4 → jNiH700EcljeLnbQ2_RCv}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{Y0I7CjXJl-tWoV__KidV4 → jNiH700EcljeLnbQ2_RCv}/_ssgManifest.js +0 -0
package/README.md
CHANGED
|
@@ -623,8 +623,10 @@ The best practice for working in teams is to ensure unique milestone names acros
|
|
|
623
623
|
# ── GSD: Runtime / Ephemeral (per-developer, per-session) ──────────────────
|
|
624
624
|
# Crash detection sentinel — PID lock, written per auto-mode session
|
|
625
625
|
.gsd/auto.lock
|
|
626
|
-
# Auto-mode dispatch tracker — prevents re-running completed units
|
|
627
|
-
.gsd/completed-units
|
|
626
|
+
# Auto-mode dispatch tracker — prevents re-running completed units (includes archived per-milestone files)
|
|
627
|
+
.gsd/completed-units*.json
|
|
628
|
+
# State manifest — workflow state for recovery
|
|
629
|
+
.gsd/state-manifest.json
|
|
628
630
|
# Derived state cache — regenerated from plan/roadmap files on disk
|
|
629
631
|
.gsd/STATE.md
|
|
630
632
|
# Per-developer token/cost accumulator
|
|
@@ -637,6 +639,14 @@ The best practice for working in teams is to ensure unique milestone names acros
|
|
|
637
639
|
.gsd/worktrees/
|
|
638
640
|
# Parallel orchestration IPC and worker status
|
|
639
641
|
.gsd/parallel/
|
|
642
|
+
# SQLite database and WAL sidecars — checkpoint state, forensics data
|
|
643
|
+
.gsd/gsd.db*
|
|
644
|
+
# Daily-rotated event journal — structured event log for forensics
|
|
645
|
+
.gsd/journal/
|
|
646
|
+
# Doctor run history — diagnostic check results
|
|
647
|
+
.gsd/doctor-history.jsonl
|
|
648
|
+
# Workflow event log — structured event stream
|
|
649
|
+
.gsd/event-log.jsonl
|
|
640
650
|
# Generated HTML reports (regenerable via /gsd export --html)
|
|
641
651
|
.gsd/reports/
|
|
642
652
|
# Session-specific interrupted-work markers
|
package/dist/cli.js
CHANGED
|
@@ -5,9 +5,7 @@ import { agentDir, sessionsDir, authFilePath } from './app-paths.js';
|
|
|
5
5
|
import { initResources, buildResourceLoader, getNewerManagedResourceVersion } from './resource-loader.js';
|
|
6
6
|
import { ensureManagedTools } from './tool-bootstrap.js';
|
|
7
7
|
import { loadStoredEnvKeys } from './wizard.js';
|
|
8
|
-
import { migratePiCredentials } from './pi-migration.js';
|
|
9
|
-
import { validateConfiguredModel } from './startup-model-validation.js';
|
|
10
|
-
import { shouldMigrateAnthropicToClaudeCode } from './provider-migrations.js';
|
|
8
|
+
import { migratePiCredentials, getPiDefaultModelAndProvider } from './pi-migration.js';
|
|
11
9
|
import { shouldRunOnboarding, runOnboarding } from './onboarding.js';
|
|
12
10
|
import chalk from 'chalk';
|
|
13
11
|
import { checkForUpdates } from './update-check.js';
|
|
@@ -102,6 +100,41 @@ function parseCliArgs(argv) {
|
|
|
102
100
|
}
|
|
103
101
|
return flags;
|
|
104
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Validate the configured default model against the registry and reset it if
|
|
105
|
+
* it no longer exists. Must run AFTER extensions have registered their
|
|
106
|
+
* providers so that extension models (e.g. pi-claude-cli) are visible.
|
|
107
|
+
*/
|
|
108
|
+
function validateConfiguredModel(modelRegistry, settingsManager) {
|
|
109
|
+
const configuredProvider = settingsManager.getDefaultProvider();
|
|
110
|
+
const configuredModel = settingsManager.getDefaultModel();
|
|
111
|
+
const allModels = modelRegistry.getAll();
|
|
112
|
+
const availableModels = modelRegistry.getAvailable();
|
|
113
|
+
const configuredExists = configuredProvider && configuredModel &&
|
|
114
|
+
allModels.some((m) => m.provider === configuredProvider && m.id === configuredModel);
|
|
115
|
+
const configuredAvailable = configuredProvider && configuredModel &&
|
|
116
|
+
availableModels.some((m) => m.provider === configuredProvider && m.id === configuredModel);
|
|
117
|
+
if (!configuredModel || !configuredExists) {
|
|
118
|
+
// Model not configured at all, or removed from registry — pick a fallback.
|
|
119
|
+
// Only fires when the model is genuinely unknown (not just temporarily unavailable).
|
|
120
|
+
const piDefault = getPiDefaultModelAndProvider();
|
|
121
|
+
const preferred = (piDefault
|
|
122
|
+
? availableModels.find((m) => m.provider === piDefault.provider && m.id === piDefault.model)
|
|
123
|
+
: undefined) ||
|
|
124
|
+
availableModels.find((m) => m.provider === 'openai' && m.id === 'gpt-5.4') ||
|
|
125
|
+
availableModels.find((m) => m.provider === 'openai') ||
|
|
126
|
+
availableModels.find((m) => m.provider === 'anthropic' && m.id === 'claude-opus-4-6') ||
|
|
127
|
+
availableModels.find((m) => m.provider === 'anthropic' && m.id.includes('opus')) ||
|
|
128
|
+
availableModels.find((m) => m.provider === 'anthropic') ||
|
|
129
|
+
availableModels[0];
|
|
130
|
+
if (preferred) {
|
|
131
|
+
settingsManager.setDefaultModelAndProvider(preferred.provider, preferred.id);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (settingsManager.getDefaultThinkingLevel() !== 'off' && !configuredExists) {
|
|
135
|
+
settingsManager.setDefaultThinkingLevel('off');
|
|
136
|
+
}
|
|
137
|
+
}
|
|
105
138
|
const cliFlags = parseCliArgs(process.argv);
|
|
106
139
|
const isPrintMode = cliFlags.print || cliFlags.mode !== undefined;
|
|
107
140
|
// Early resource-skew check — must run before TTY gate so version mismatch
|
|
@@ -320,8 +353,22 @@ if (!isPrintMode) {
|
|
|
320
353
|
if (!isPrintMode && process.stdout.columns && process.stdout.columns < 40) {
|
|
321
354
|
process.stderr.write(chalk.yellow(`[gsd] Terminal width is ${process.stdout.columns} columns (minimum recommended: 40). Output may be unreadable.\n`));
|
|
322
355
|
}
|
|
323
|
-
// --list-models:
|
|
356
|
+
// --list-models: load extensions so that extension-registered providers (e.g.
|
|
357
|
+
// pi-claude-cli) appear in the listing, then flush their pending registrations
|
|
358
|
+
// into the model registry before printing.
|
|
324
359
|
if (cliFlags.listModels !== undefined) {
|
|
360
|
+
exitIfManagedResourcesAreNewer(agentDir);
|
|
361
|
+
initResources(agentDir);
|
|
362
|
+
const listModelsLoader = new DefaultResourceLoader({
|
|
363
|
+
agentDir,
|
|
364
|
+
additionalExtensionPaths: cliFlags.extensions.length > 0 ? cliFlags.extensions : undefined,
|
|
365
|
+
});
|
|
366
|
+
await listModelsLoader.reload();
|
|
367
|
+
const listModelsExtensions = listModelsLoader.getExtensions();
|
|
368
|
+
for (const { name, config } of listModelsExtensions.runtime.pendingProviderRegistrations) {
|
|
369
|
+
modelRegistry.registerProvider(name, config);
|
|
370
|
+
}
|
|
371
|
+
listModelsExtensions.runtime.pendingProviderRegistrations = [];
|
|
325
372
|
const models = modelRegistry.getAvailable();
|
|
326
373
|
if (models.length === 0) {
|
|
327
374
|
console.log('No models available. Set API keys in environment variables.');
|
|
@@ -407,29 +454,6 @@ if (isPrintMode) {
|
|
|
407
454
|
isClaudeCodeReady: () => modelRegistry.isProviderRequestReady('claude-code'),
|
|
408
455
|
});
|
|
409
456
|
markStartup('createAgentSession');
|
|
410
|
-
// Migrate anthropic OAuth users to claude-code provider when CLI is available (#3772).
|
|
411
|
-
// Anthropic blocks third-party apps from using subscription quotas — routing through
|
|
412
|
-
// the local claude CLI binary is TOS-compliant.
|
|
413
|
-
if (shouldMigrateAnthropicToClaudeCode({
|
|
414
|
-
authStorage,
|
|
415
|
-
isClaudeCodeReady: modelRegistry.isProviderRequestReady('claude-code'),
|
|
416
|
-
defaultProvider: settingsManager.getDefaultProvider(),
|
|
417
|
-
})) {
|
|
418
|
-
const currentModelId = settingsManager.getDefaultModel();
|
|
419
|
-
if (currentModelId) {
|
|
420
|
-
const ccModel = modelRegistry.find('claude-code', currentModelId);
|
|
421
|
-
if (ccModel) {
|
|
422
|
-
try {
|
|
423
|
-
await session.setModel(ccModel);
|
|
424
|
-
// Only persist after successful session switch to avoid desync
|
|
425
|
-
settingsManager.setDefaultModelAndProvider('claude-code', currentModelId);
|
|
426
|
-
}
|
|
427
|
-
catch {
|
|
428
|
-
// claude-code provider not ready — leave both session and settings unchanged
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
457
|
// Validate configured model AFTER extensions have registered their models (#2626).
|
|
434
458
|
// Before this, extension-provided models (e.g. claude-code/*) were not yet in the
|
|
435
459
|
// registry, causing the user's valid choice to be silently overwritten.
|
|
@@ -461,6 +485,10 @@ if (isPrintMode) {
|
|
|
461
485
|
process.stderr.write(`[gsd] ${prefix}: ${err.error}\n`);
|
|
462
486
|
}
|
|
463
487
|
}
|
|
488
|
+
// Validate configured model now that extension providers are registered.
|
|
489
|
+
// Must run after createAgentSession() which flushes pendingProviderRegistrations
|
|
490
|
+
// so extension models (e.g. pi-claude-cli) are visible in the registry.
|
|
491
|
+
validateConfiguredModel(modelRegistry, settingsManager);
|
|
464
492
|
// Apply --model override if specified
|
|
465
493
|
if (cliFlags.model) {
|
|
466
494
|
const available = modelRegistry.getAvailable();
|
|
@@ -595,29 +623,6 @@ const { session, extensionsResult, modelFallbackMessage: interactiveFallbackMsg
|
|
|
595
623
|
isClaudeCodeReady: () => modelRegistry.isProviderRequestReady('claude-code'),
|
|
596
624
|
});
|
|
597
625
|
markStartup('createAgentSession');
|
|
598
|
-
// Migrate anthropic OAuth users to claude-code provider when CLI is available (#3772).
|
|
599
|
-
// Anthropic blocks third-party apps from using subscription quotas — routing through
|
|
600
|
-
// the local claude CLI binary is TOS-compliant.
|
|
601
|
-
if (shouldMigrateAnthropicToClaudeCode({
|
|
602
|
-
authStorage,
|
|
603
|
-
isClaudeCodeReady: modelRegistry.isProviderRequestReady('claude-code'),
|
|
604
|
-
defaultProvider: settingsManager.getDefaultProvider(),
|
|
605
|
-
})) {
|
|
606
|
-
const currentModelId = settingsManager.getDefaultModel();
|
|
607
|
-
if (currentModelId) {
|
|
608
|
-
const ccModel = modelRegistry.find('claude-code', currentModelId);
|
|
609
|
-
if (ccModel) {
|
|
610
|
-
try {
|
|
611
|
-
await session.setModel(ccModel);
|
|
612
|
-
// Only persist after successful session switch to avoid desync
|
|
613
|
-
settingsManager.setDefaultModelAndProvider('claude-code', currentModelId);
|
|
614
|
-
}
|
|
615
|
-
catch {
|
|
616
|
-
// claude-code provider not ready — leave both session and settings unchanged
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
626
|
// Validate configured model AFTER extensions have registered their models (#2626).
|
|
622
627
|
// Before this, extension-provided models (e.g. claude-code/*) were not yet in the
|
|
623
628
|
// registry, causing the user's valid choice to be silently overwritten.
|
|
@@ -648,6 +653,10 @@ if (extensionsResult.errors.length > 0) {
|
|
|
648
653
|
process.stderr.write(`[gsd] ${prefix}: ${err.error}\n`);
|
|
649
654
|
}
|
|
650
655
|
}
|
|
656
|
+
// Validate configured model now that extension providers are registered.
|
|
657
|
+
// Must run after createAgentSession() which flushes pendingProviderRegistrations
|
|
658
|
+
// so extension models (e.g. pi-claude-cli) are visible in the registry.
|
|
659
|
+
validateConfiguredModel(modelRegistry, settingsManager);
|
|
651
660
|
// Restore scoped models from settings on startup.
|
|
652
661
|
// The upstream InteractiveMode reads enabledModels from settings when /scoped-models is opened,
|
|
653
662
|
// but doesn't apply them to the session at startup — so Ctrl+P cycles all models instead of
|
package/dist/help-text.js
CHANGED
|
@@ -148,7 +148,7 @@ export function printHelp(version) {
|
|
|
148
148
|
process.stdout.write(' --print, -p Single-shot print mode\n');
|
|
149
149
|
process.stdout.write(' --continue, -c Resume the most recent session\n');
|
|
150
150
|
process.stdout.write(' --worktree, -w [name] Start in an isolated worktree (auto-named if omitted)\n');
|
|
151
|
-
process.stdout.write(' --model <id> Override model (e.g.
|
|
151
|
+
process.stdout.write(' --model <id> Override model (e.g. provider/model-id)\n');
|
|
152
152
|
process.stdout.write(' --no-session Disable session persistence\n');
|
|
153
153
|
process.stdout.write(' --extension <path> Load additional extension\n');
|
|
154
154
|
process.stdout.write(' --tools <a,b,c> Restrict available tools\n');
|
package/dist/onboarding.js
CHANGED
|
@@ -277,6 +277,16 @@ async function runLlmStep(p, pc, authStorage) {
|
|
|
277
277
|
p.log.info('Your Claude subscription will be used for inference. No API key needed.');
|
|
278
278
|
// Store sentinel so hasAuth('claude-code') returns true on future boots
|
|
279
279
|
authStorage.set('claude-code', { type: 'api_key', key: 'cli' });
|
|
280
|
+
// Persist claude-code as the default provider so the startup migration in
|
|
281
|
+
// cli.ts does not need to fire and the user is not left on "anthropic".
|
|
282
|
+
const settingsPath = join(agentDir, 'settings.json');
|
|
283
|
+
try {
|
|
284
|
+
const raw = existsSync(settingsPath) ? JSON.parse(readFileSync(settingsPath, 'utf-8')) : {};
|
|
285
|
+
raw.defaultProvider = 'claude-code';
|
|
286
|
+
mkdirSync(dirname(settingsPath), { recursive: true });
|
|
287
|
+
writeFileSync(settingsPath, JSON.stringify(raw, null, 2), 'utf-8');
|
|
288
|
+
}
|
|
289
|
+
catch { /* non-fatal — startup migration will catch it */ }
|
|
280
290
|
return true;
|
|
281
291
|
}
|
|
282
292
|
// ── Step 2: Which provider? ──────────────────────────────────────────────
|
|
@@ -54,11 +54,14 @@ export function createAwaitTool(getManager) {
|
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
//
|
|
57
|
+
// Suppress follow-up notifications for all watched jobs upfront.
|
|
58
|
+
// suppressFollowUp() cancels the pending delivery timer (if any), which
|
|
59
|
+
// handles both the within-turn case (job completes while we await) and
|
|
60
|
+
// the cross-turn case (job already completed before await_job was called).
|
|
61
|
+
// Previously this only set j.awaited = true, which missed the cross-turn
|
|
62
|
+
// case because the queueMicrotask had already fired (#3787).
|
|
60
63
|
for (const j of watched)
|
|
61
|
-
j.
|
|
64
|
+
manager.suppressFollowUp(j.id);
|
|
62
65
|
// If all watched jobs are already done, return immediately
|
|
63
66
|
const running = watched.filter((j) => j.status === "running");
|
|
64
67
|
if (running.length === 0) {
|
|
@@ -118,13 +118,38 @@ export class AsyncJobManager {
|
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
// ── Private ────────────────────────────────────────────────────────────
|
|
121
|
+
/**
|
|
122
|
+
* Suppress follow-up notification for a job — cancels any pending delivery
|
|
123
|
+
* timer and marks the job as awaited. Safe to call at any time, including
|
|
124
|
+
* before or after the job completes (#3787).
|
|
125
|
+
*/
|
|
126
|
+
suppressFollowUp(id) {
|
|
127
|
+
const job = this.jobs.get(id);
|
|
128
|
+
if (!job)
|
|
129
|
+
return;
|
|
130
|
+
job.awaited = true;
|
|
131
|
+
if (job.deliveryTimer !== undefined) {
|
|
132
|
+
clearTimeout(job.deliveryTimer);
|
|
133
|
+
job.deliveryTimer = undefined;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
121
136
|
deliverResult(job) {
|
|
122
137
|
if (!this.onJobComplete)
|
|
123
138
|
return;
|
|
124
|
-
//
|
|
125
|
-
//
|
|
139
|
+
// Use setTimeout(0) instead of queueMicrotask so the handle is cancellable.
|
|
140
|
+
// suppressFollowUp() can clear this timer even when await_job is called in
|
|
141
|
+
// a later LLM turn (after the job already completed). queueMicrotask ran
|
|
142
|
+
// immediately and could not be cancelled (#2762, #3787).
|
|
126
143
|
const cb = this.onJobComplete;
|
|
127
|
-
|
|
144
|
+
job.deliveryTimer = setTimeout(() => {
|
|
145
|
+
job.deliveryTimer = undefined;
|
|
146
|
+
if (!job.awaited)
|
|
147
|
+
cb(job);
|
|
148
|
+
}, 0);
|
|
149
|
+
// Allow process to exit even if timer is pending
|
|
150
|
+
if (typeof job.deliveryTimer === "object" && "unref" in job.deliveryTimer) {
|
|
151
|
+
job.deliveryTimer.unref();
|
|
152
|
+
}
|
|
128
153
|
}
|
|
129
154
|
scheduleEviction(id) {
|
|
130
155
|
const existing = this.evictionTimers.get(id);
|
|
@@ -6,6 +6,44 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { hasXmlParameterTags, repairToolJson } from "@gsd/pi-ai";
|
|
8
8
|
// ---------------------------------------------------------------------------
|
|
9
|
+
// MCP tool name parsing
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
/**
|
|
12
|
+
* Split a Claude Code MCP tool name (`mcp__<server>__<tool>`) into its parts.
|
|
13
|
+
* Returns null for non-prefixed names so callers can fall through unchanged.
|
|
14
|
+
*
|
|
15
|
+
* Server names may contain hyphens (`gsd-workflow`); the SDK uses the literal
|
|
16
|
+
* `__` delimiter between the server name and the tool name.
|
|
17
|
+
*/
|
|
18
|
+
export function parseMcpToolName(name) {
|
|
19
|
+
if (!name.startsWith("mcp__"))
|
|
20
|
+
return null;
|
|
21
|
+
const rest = name.slice("mcp__".length);
|
|
22
|
+
const delim = rest.indexOf("__");
|
|
23
|
+
if (delim <= 0 || delim === rest.length - 2)
|
|
24
|
+
return null;
|
|
25
|
+
return { server: rest.slice(0, delim), tool: rest.slice(delim + 2) };
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Build a GSD ToolCall block from a Claude Code SDK tool_use block, stripping
|
|
29
|
+
* the `mcp__<server>__` prefix from the name so registered extension renderers
|
|
30
|
+
* (which use the unprefixed canonical names) can match. The original server
|
|
31
|
+
* name is preserved on the block for diagnostics and rendering.
|
|
32
|
+
*/
|
|
33
|
+
function toolCallFromBlock(id, rawName, input) {
|
|
34
|
+
const parsed = parseMcpToolName(rawName);
|
|
35
|
+
const toolCall = {
|
|
36
|
+
type: "toolCall",
|
|
37
|
+
id,
|
|
38
|
+
name: parsed ? parsed.tool : rawName,
|
|
39
|
+
arguments: input,
|
|
40
|
+
};
|
|
41
|
+
if (parsed) {
|
|
42
|
+
toolCall.mcpServer = parsed.server;
|
|
43
|
+
}
|
|
44
|
+
return toolCall;
|
|
45
|
+
}
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
9
47
|
// Content-block mapping helpers
|
|
10
48
|
// ---------------------------------------------------------------------------
|
|
11
49
|
/**
|
|
@@ -22,12 +60,7 @@ export function mapContentBlock(block) {
|
|
|
22
60
|
...(block.signature ? { thinkingSignature: block.signature } : {}),
|
|
23
61
|
};
|
|
24
62
|
case "tool_use":
|
|
25
|
-
return
|
|
26
|
-
type: "toolCall",
|
|
27
|
-
id: block.id,
|
|
28
|
-
name: block.name,
|
|
29
|
-
arguments: block.input,
|
|
30
|
-
};
|
|
63
|
+
return toolCallFromBlock(block.id, block.name, block.input);
|
|
31
64
|
case "server_tool_use":
|
|
32
65
|
return {
|
|
33
66
|
type: "serverToolUse",
|
|
@@ -149,12 +182,7 @@ export class PartialMessageBuilder {
|
|
|
149
182
|
}
|
|
150
183
|
if (block.type === "tool_use") {
|
|
151
184
|
this.toolJsonAccum.set(streamIndex, "");
|
|
152
|
-
this.partial.content.push({
|
|
153
|
-
type: "toolCall",
|
|
154
|
-
id: block.id,
|
|
155
|
-
name: block.name,
|
|
156
|
-
arguments: {},
|
|
157
|
-
});
|
|
185
|
+
this.partial.content.push(toolCallFromBlock(block.id, block.name, {}));
|
|
158
186
|
return { type: "toolcall_start", contentIndex, partial: this.partial };
|
|
159
187
|
}
|
|
160
188
|
if (block.type === "server_tool_use") {
|
|
@@ -92,18 +92,34 @@ function extractMessageText(msg) {
|
|
|
92
92
|
* call effectively stateless. This version serialises the complete
|
|
93
93
|
* conversation history (system prompt + all user/assistant turns) so
|
|
94
94
|
* Claude Code has full context for multi-turn continuity.
|
|
95
|
+
*
|
|
96
|
+
* History is wrapped in XML-tag structure rather than `[User]`/`[Assistant]`
|
|
97
|
+
* bracket headers. Bracket headers read to the model as an in-context
|
|
98
|
+
* demonstration of how turns are delimited, causing it to fabricate fake
|
|
99
|
+
* user turns in its own output. XML tags read as document structure and
|
|
100
|
+
* don't get mirrored in free text.
|
|
95
101
|
*/
|
|
96
102
|
export function buildPromptFromContext(context) {
|
|
97
|
-
const
|
|
103
|
+
const hasContent = Boolean(context.systemPrompt) || context.messages.some((m) => extractMessageText(m));
|
|
104
|
+
if (!hasContent)
|
|
105
|
+
return "";
|
|
106
|
+
const parts = [
|
|
107
|
+
"Respond only to the final user message below. " +
|
|
108
|
+
"Do not emit <user_message>, <assistant_message>, or <prior_system_context> tags in your response.",
|
|
109
|
+
];
|
|
98
110
|
if (context.systemPrompt) {
|
|
99
|
-
parts.push(
|
|
111
|
+
parts.push(`<prior_system_context>\n${context.systemPrompt}\n</prior_system_context>`);
|
|
100
112
|
}
|
|
113
|
+
const turns = [];
|
|
101
114
|
for (const msg of context.messages) {
|
|
102
115
|
const text = extractMessageText(msg);
|
|
103
116
|
if (!text)
|
|
104
117
|
continue;
|
|
105
|
-
const
|
|
106
|
-
|
|
118
|
+
const tag = msg.role === "user" ? "user_message" : msg.role === "assistant" ? "assistant_message" : "system_message";
|
|
119
|
+
turns.push(`<${tag}>\n${text}\n</${tag}>`);
|
|
120
|
+
}
|
|
121
|
+
if (turns.length > 0) {
|
|
122
|
+
parts.push(`<conversation_history>\n${turns.join("\n")}\n</conversation_history>`);
|
|
107
123
|
}
|
|
108
124
|
return parts.join("\n\n");
|
|
109
125
|
}
|
|
@@ -389,32 +405,25 @@ export function makeAbortedMessage(model, lastTextContent) {
|
|
|
389
405
|
/**
|
|
390
406
|
* Resolve the Claude Code permission mode for the current run.
|
|
391
407
|
*
|
|
392
|
-
*
|
|
393
|
-
*
|
|
394
|
-
*
|
|
395
|
-
*
|
|
396
|
-
*
|
|
408
|
+
* GSD subagents run underneath a host Claude Code session the user has
|
|
409
|
+
* already consented to, and their work (edits, shell inspection, MCP calls)
|
|
410
|
+
* spans the full workflow toolset. Defaulting the inner SDK to
|
|
411
|
+
* `bypassPermissions` avoids per-tool approval prompts that offer no
|
|
412
|
+
* meaningful safety beyond what the host session and the subagent prompts
|
|
413
|
+
* already enforce. `GSD_CLAUDE_CODE_PERMISSION_MODE` lets security-conscious
|
|
414
|
+
* users opt into a stricter mode (`acceptEdits`, `default`, `plan`).
|
|
397
415
|
*
|
|
398
|
-
*
|
|
399
|
-
*
|
|
400
|
-
*
|
|
416
|
+
* Tradeoff: bypass means a prompt-injection payload read from an untrusted
|
|
417
|
+
* file could trigger tool calls without a second gate. Accepted for GSD
|
|
418
|
+
* because the workflow is explicit user intent and the alternative
|
|
419
|
+
* (#4099) is continuous approval fatigue that blocks real work.
|
|
401
420
|
*/
|
|
402
421
|
export async function resolveClaudePermissionMode(env = process.env) {
|
|
403
422
|
const override = env.GSD_CLAUDE_CODE_PERMISSION_MODE?.trim();
|
|
404
423
|
if (override === "bypassPermissions" || override === "acceptEdits" || override === "default" || override === "plan") {
|
|
405
424
|
return override;
|
|
406
425
|
}
|
|
407
|
-
|
|
408
|
-
const autoMod = (await import("../gsd/auto.js"));
|
|
409
|
-
if (typeof autoMod.isAutoActive === "function" && autoMod.isAutoActive()) {
|
|
410
|
-
return "bypassPermissions";
|
|
411
|
-
}
|
|
412
|
-
return "acceptEdits";
|
|
413
|
-
}
|
|
414
|
-
catch {
|
|
415
|
-
// auto.ts unavailable (tests, non-GSD contexts) — stay permissive.
|
|
416
|
-
return "bypassPermissions";
|
|
417
|
-
}
|
|
426
|
+
return "bypassPermissions";
|
|
418
427
|
}
|
|
419
428
|
/**
|
|
420
429
|
* Build the options object passed to the Claude Agent SDK's `query()` call.
|
|
@@ -431,6 +440,21 @@ export function buildSdkOptions(modelId, prompt, overrides, extraOptions = {}) {
|
|
|
431
440
|
const mcpServers = buildWorkflowMcpServers();
|
|
432
441
|
const permissionMode = overrides?.permissionMode ?? "bypassPermissions";
|
|
433
442
|
const disallowedTools = ["AskUserQuestion"];
|
|
443
|
+
// Pre-authorize the safe built-ins and every registered workflow MCP
|
|
444
|
+
// server's tools. `acceptEdits` mode (the interactive default) only
|
|
445
|
+
// auto-approves file edits — Read/Glob/Grep, basic shell inspection, and
|
|
446
|
+
// every `mcp__gsd-workflow__*` call still surface as "This command
|
|
447
|
+
// requires approval" and block GSD actions (#4099).
|
|
448
|
+
const allowedTools = [
|
|
449
|
+
"Read",
|
|
450
|
+
"Write",
|
|
451
|
+
"Edit",
|
|
452
|
+
"Glob",
|
|
453
|
+
"Grep",
|
|
454
|
+
"Bash(ls:*)",
|
|
455
|
+
"Bash(pwd)",
|
|
456
|
+
...(mcpServers ? Object.keys(mcpServers).map((serverName) => `mcp__${serverName}__*`) : []),
|
|
457
|
+
];
|
|
434
458
|
return {
|
|
435
459
|
pathToClaudeCodeExecutable: getClaudePath(),
|
|
436
460
|
model: modelId,
|
|
@@ -442,6 +466,7 @@ export function buildSdkOptions(modelId, prompt, overrides, extraOptions = {}) {
|
|
|
442
466
|
settingSources: ["project"],
|
|
443
467
|
systemPrompt: { type: "preset", preset: "claude_code" },
|
|
444
468
|
disallowedTools,
|
|
469
|
+
...(allowedTools.length > 0 ? { allowedTools } : {}),
|
|
445
470
|
...(mcpServers ? { mcpServers } : {}),
|
|
446
471
|
betas: modelId.includes("sonnet") ? ["context-1m-2025-08-07"] : [],
|
|
447
472
|
...extraOptions,
|
|
@@ -13,6 +13,69 @@ import { runPreDispatch, runDispatch, runGuards, runUnitPhase, runFinalize, } fr
|
|
|
13
13
|
import { debugLog } from "../debug-logger.js";
|
|
14
14
|
import { isInfrastructureError, isTransientCooldownError, getCooldownRetryAfterMs, COOLDOWN_FALLBACK_WAIT_MS, MAX_COOLDOWN_RETRIES } from "./infra-errors.js";
|
|
15
15
|
import { resolveEngine } from "../engine-resolver.js";
|
|
16
|
+
import { logWarning } from "../workflow-logger.js";
|
|
17
|
+
import { gsdRoot } from "../paths.js";
|
|
18
|
+
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
19
|
+
import { join } from "node:path";
|
|
20
|
+
// ── Stuck detection persistence (#3704) ──────────────────────────────────
|
|
21
|
+
// Persist stuck detection state to disk so it survives session restarts.
|
|
22
|
+
// Without this, restarting auto-mode resets all counters, allowing the
|
|
23
|
+
// same blocked unit to burn a full retry budget each session.
|
|
24
|
+
function stuckStatePath(basePath) {
|
|
25
|
+
return join(gsdRoot(basePath), "runtime", "stuck-state.json");
|
|
26
|
+
}
|
|
27
|
+
function loadStuckState(basePath) {
|
|
28
|
+
try {
|
|
29
|
+
const data = JSON.parse(readFileSync(stuckStatePath(basePath), "utf-8"));
|
|
30
|
+
return {
|
|
31
|
+
recentUnits: Array.isArray(data.recentUnits) ? data.recentUnits : [],
|
|
32
|
+
stuckRecoveryAttempts: typeof data.stuckRecoveryAttempts === "number" ? data.stuckRecoveryAttempts : 0,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
debugLog("autoLoop", { phase: "load-stuck-state-failed", error: err instanceof Error ? err.message : String(err) });
|
|
37
|
+
return { recentUnits: [], stuckRecoveryAttempts: 0 };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function saveStuckState(basePath, state) {
|
|
41
|
+
try {
|
|
42
|
+
const filePath = stuckStatePath(basePath);
|
|
43
|
+
mkdirSync(join(gsdRoot(basePath), "runtime"), { recursive: true });
|
|
44
|
+
writeFileSync(filePath, JSON.stringify({
|
|
45
|
+
recentUnits: state.recentUnits.slice(-20), // keep last 20 entries
|
|
46
|
+
stuckRecoveryAttempts: state.stuckRecoveryAttempts,
|
|
47
|
+
updatedAt: new Date().toISOString(),
|
|
48
|
+
}) + "\n");
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
debugLog("autoLoop", { phase: "save-stuck-state-failed", error: err instanceof Error ? err.message : String(err) });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// ── Memory pressure monitoring (#3331) ──────────────────────────────────
|
|
55
|
+
// Check heap usage every N iterations and trigger graceful shutdown before
|
|
56
|
+
// the OS OOM killer sends SIGKILL. The threshold is 90% of the V8 heap
|
|
57
|
+
// limit (--max-old-space-size or default ~1.5-4GB depending on platform).
|
|
58
|
+
const MEMORY_CHECK_INTERVAL = 5; // check every 5 iterations
|
|
59
|
+
const MEMORY_PRESSURE_THRESHOLD = 0.85; // 85% of heap limit
|
|
60
|
+
function checkMemoryPressure() {
|
|
61
|
+
const mem = process.memoryUsage();
|
|
62
|
+
// v8.getHeapStatistics() gives heap_size_limit but requires import
|
|
63
|
+
// Use a conservative estimate: RSS > 3GB is danger zone on most systems
|
|
64
|
+
const heapMB = Math.round(mem.heapUsed / 1024 / 1024);
|
|
65
|
+
const rssMB = Math.round(mem.rss / 1024 / 1024);
|
|
66
|
+
// Try to get the actual V8 heap limit
|
|
67
|
+
let limitMB = 4096; // conservative default
|
|
68
|
+
try {
|
|
69
|
+
const v8 = require("node:v8");
|
|
70
|
+
const stats = v8.getHeapStatistics();
|
|
71
|
+
limitMB = Math.round(stats.heap_size_limit / 1024 / 1024);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
limitMB = 4096; /* v8 stats unavailable — use conservative default */
|
|
75
|
+
}
|
|
76
|
+
const pct = heapMB / limitMB;
|
|
77
|
+
return { pressured: pct > MEMORY_PRESSURE_THRESHOLD, heapMB, limitMB, pct };
|
|
78
|
+
}
|
|
16
79
|
/**
|
|
17
80
|
* Main auto-mode execution loop. Iterates: derive → dispatch → guards →
|
|
18
81
|
* runUnit → finalize → repeat. Exits when s.active becomes false or a
|
|
@@ -24,7 +87,13 @@ import { resolveEngine } from "../engine-resolver.js";
|
|
|
24
87
|
export async function autoLoop(ctx, pi, s, deps) {
|
|
25
88
|
debugLog("autoLoop", { phase: "enter" });
|
|
26
89
|
let iteration = 0;
|
|
27
|
-
|
|
90
|
+
// Load persisted stuck state so counters survive session restarts (#3704)
|
|
91
|
+
const persisted = loadStuckState(s.basePath);
|
|
92
|
+
const loopState = {
|
|
93
|
+
recentUnits: persisted.recentUnits,
|
|
94
|
+
stuckRecoveryAttempts: persisted.stuckRecoveryAttempts,
|
|
95
|
+
consecutiveFinalizeTimeouts: 0,
|
|
96
|
+
};
|
|
28
97
|
let consecutiveErrors = 0;
|
|
29
98
|
let consecutiveCooldowns = 0;
|
|
30
99
|
const recentErrorMessages = [];
|
|
@@ -44,6 +113,19 @@ export async function autoLoop(ctx, pi, s, deps) {
|
|
|
44
113
|
await deps.stopAuto(ctx, pi, `Safety: loop exceeded ${MAX_LOOP_ITERATIONS} iterations — possible runaway`);
|
|
45
114
|
break;
|
|
46
115
|
}
|
|
116
|
+
// ── Memory pressure check (#3331) ──
|
|
117
|
+
// Graceful shutdown before OOM killer sends SIGKILL.
|
|
118
|
+
if (iteration % MEMORY_CHECK_INTERVAL === 0) {
|
|
119
|
+
const mem = checkMemoryPressure();
|
|
120
|
+
debugLog("autoLoop", { phase: "memory-check", ...mem });
|
|
121
|
+
if (mem.pressured) {
|
|
122
|
+
logWarning("dispatch", `Memory pressure: ${mem.heapMB}MB / ${mem.limitMB}MB (${Math.round(mem.pct * 100)}%) — stopping auto-mode to prevent OOM kill`);
|
|
123
|
+
await deps.stopAuto(ctx, pi, `Memory pressure: heap at ${mem.heapMB}MB / ${mem.limitMB}MB (${Math.round(mem.pct * 100)}%). ` +
|
|
124
|
+
`Stopping gracefully to prevent OOM kill after ${iteration} iterations. ` +
|
|
125
|
+
`Resume with /gsd auto to continue from where you left off.`);
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
47
129
|
if (!s.cmdCtx) {
|
|
48
130
|
debugLog("autoLoop", { phase: "exit", reason: "no-cmdCtx" });
|
|
49
131
|
break;
|
|
@@ -162,6 +244,7 @@ export async function autoLoop(ctx, pi, s, deps) {
|
|
|
162
244
|
consecutiveCooldowns = 0;
|
|
163
245
|
recentErrorMessages.length = 0;
|
|
164
246
|
deps.emitJournalEvent({ ts: new Date().toISOString(), flowId, seq: nextSeq(), eventType: "iteration-end", data: { iteration } });
|
|
247
|
+
saveStuckState(s.basePath, loopState); // persist across session restarts (#3704)
|
|
165
248
|
debugLog("autoLoop", { phase: "iteration-complete", iteration });
|
|
166
249
|
if (reconcileResult.outcome === "milestone-complete") {
|
|
167
250
|
await deps.stopAuto(ctx, pi, "Workflow complete");
|
|
@@ -18,6 +18,7 @@ import { logWarning, logError } from "./workflow-logger.js";
|
|
|
18
18
|
import { join } from "node:path";
|
|
19
19
|
import { hasImplementationArtifacts } from "./auto-recovery.js";
|
|
20
20
|
import { buildDiscussMilestonePrompt, buildResearchMilestonePrompt, buildPlanMilestonePrompt, buildResearchSlicePrompt, buildPlanSlicePrompt, buildExecuteTaskPrompt, buildCompleteSlicePrompt, buildCompleteMilestonePrompt, buildValidateMilestonePrompt, buildReplanSlicePrompt, buildRunUatPrompt, buildReassessRoadmapPrompt, buildRewriteDocsPrompt, buildReactiveExecutePrompt, buildGateEvaluatePrompt, buildParallelResearchSlicesPrompt, checkNeedsReassessment, checkNeedsRunUat, } from "./auto-prompts.js";
|
|
21
|
+
import { resolveModelWithFallbacksForUnit } from "./preferences-models.js";
|
|
21
22
|
function missingSliceStop(mid, phase) {
|
|
22
23
|
return {
|
|
23
24
|
action: "stop",
|
|
@@ -330,7 +331,7 @@ export const DISPATCH_RULES = [
|
|
|
330
331
|
action: "dispatch",
|
|
331
332
|
unitType: "research-slice",
|
|
332
333
|
unitId: `${mid}/parallel-research`,
|
|
333
|
-
prompt: await buildParallelResearchSlicesPrompt(mid, midTitle, researchReadySlices, basePath),
|
|
334
|
+
prompt: await buildParallelResearchSlicesPrompt(mid, midTitle, researchReadySlices, basePath, resolveModelWithFallbacksForUnit("subagent")?.primary),
|
|
334
335
|
};
|
|
335
336
|
},
|
|
336
337
|
},
|
|
@@ -401,7 +402,7 @@ export const DISPATCH_RULES = [
|
|
|
401
402
|
action: "dispatch",
|
|
402
403
|
unitType: "gate-evaluate",
|
|
403
404
|
unitId: `${mid}/${sid}/gates+${pending.map(g => g.gate_id).join(",")}`,
|
|
404
|
-
prompt: await buildGateEvaluatePrompt(mid, midTitle, sid, sTitle, basePath),
|
|
405
|
+
prompt: await buildGateEvaluatePrompt(mid, midTitle, sid, sTitle, basePath, resolveModelWithFallbacksForUnit("subagent")?.primary),
|
|
405
406
|
};
|
|
406
407
|
},
|
|
407
408
|
},
|
|
@@ -436,6 +437,7 @@ export const DISPATCH_RULES = [
|
|
|
436
437
|
const sid = state.activeSlice.id;
|
|
437
438
|
const sTitle = state.activeSlice.title;
|
|
438
439
|
const maxParallel = reactiveConfig.max_parallel ?? 2;
|
|
440
|
+
const subagentModel = reactiveConfig.subagent_model ?? resolveModelWithFallbacksForUnit("subagent")?.primary;
|
|
439
441
|
// Dry-run mode: max_parallel=1 means graph is derived and logged but
|
|
440
442
|
// execution remains sequential
|
|
441
443
|
if (maxParallel <= 1)
|
|
@@ -478,7 +480,7 @@ export const DISPATCH_RULES = [
|
|
|
478
480
|
action: "dispatch",
|
|
479
481
|
unitType: "reactive-execute",
|
|
480
482
|
unitId: `${mid}/${sid}/reactive+${batchSuffix}`,
|
|
481
|
-
prompt: await buildReactiveExecutePrompt(mid, midTitle, sid, sTitle, selected, basePath),
|
|
483
|
+
prompt: await buildReactiveExecutePrompt(mid, midTitle, sid, sTitle, selected, basePath, subagentModel),
|
|
482
484
|
};
|
|
483
485
|
}
|
|
484
486
|
catch (err) {
|
|
@@ -16,6 +16,7 @@ import { loadFile, parseSummary, resolveAllOverrides } from "./files.js";
|
|
|
16
16
|
import { loadPrompt } from "./prompt-loader.js";
|
|
17
17
|
import { resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveMilestoneFile, resolveTasksDir, buildTaskFileName, } from "./paths.js";
|
|
18
18
|
import { invalidateAllCaches } from "./cache.js";
|
|
19
|
+
import { rebuildState } from "./doctor.js";
|
|
19
20
|
import { parseUnitId } from "./unit-id.js";
|
|
20
21
|
import { closeoutUnit } from "./auto-unit-closeout.js";
|
|
21
22
|
import { autoCommitCurrentBranch, } from "./worktree.js";
|
|
@@ -288,6 +289,11 @@ export async function postUnitPreVerification(pctx, opts) {
|
|
|
288
289
|
debugLog("postUnit", { phase: "browser-teardown", status: "closed" });
|
|
289
290
|
}
|
|
290
291
|
});
|
|
292
|
+
// Keep the on-disk STATE.md aligned with the live derived state after
|
|
293
|
+
// ordinary unit completion, before any worktree state is synced back.
|
|
294
|
+
await runSafely("postUnit", "state-rebuild", async () => {
|
|
295
|
+
await rebuildState(s.basePath);
|
|
296
|
+
});
|
|
291
297
|
// Sync worktree state back to project root (skipped for lightweight sidecars)
|
|
292
298
|
if (!opts?.skipWorktreeSync && s.originalBasePath && s.originalBasePath !== s.basePath) {
|
|
293
299
|
await runSafely("postUnit", "worktree-sync", () => {
|