gsd-pi 2.73.0 → 2.73.1-dev.06e4302
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/dist/cli-web-branch.d.ts +4 -3
- package/dist/cli-web-branch.js +10 -7
- package/dist/cli.js +99 -253
- package/dist/help-text.js +1 -1
- package/dist/logo.d.ts +1 -1
- package/dist/logo.js +1 -1
- package/dist/onboarding.js +59 -53
- package/dist/resource-loader.js +2 -2
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +68 -4
- package/dist/resources/extensions/gsd/auto/phases.js +15 -9
- package/dist/resources/extensions/gsd/auto-dispatch.js +16 -6
- package/dist/resources/extensions/gsd/auto-model-selection.js +54 -11
- package/dist/resources/extensions/gsd/auto-post-unit.js +41 -1
- package/dist/resources/extensions/gsd/auto-prompts.js +9 -6
- package/dist/resources/extensions/gsd/auto-start.js +23 -6
- package/dist/resources/extensions/gsd/auto-timeout-recovery.js +13 -0
- package/dist/resources/extensions/gsd/auto-verification.js +88 -3
- package/dist/resources/extensions/gsd/auto.js +34 -9
- 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/system-context.js +6 -1
- package/dist/resources/extensions/gsd/commands-handlers.js +8 -2
- package/dist/resources/extensions/gsd/crash-recovery.js +51 -0
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +1 -1
- package/dist/resources/extensions/gsd/gsd-db.js +36 -2
- package/dist/resources/extensions/gsd/milestone-actions.js +19 -1
- package/dist/resources/extensions/gsd/notification-widget.js +2 -2
- package/dist/resources/extensions/gsd/preferences-models.js +43 -0
- package/dist/resources/extensions/gsd/preferences-types.js +1 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +22 -0
- package/dist/resources/extensions/gsd/state.js +61 -14
- package/dist/startup-model-validation.js +8 -5
- package/dist/update-check.d.ts +1 -0
- package/dist/update-check.js +13 -5
- package/dist/update-cmd.js +4 -3
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
- 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 +4 -4
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +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 +12 -12
- package/dist/web/standalone/.next/server/chunks/63.js +3 -3
- package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
- package/dist/web/standalone/.next/server/middleware.js +2 -2
- package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
- package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +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 -2
- package/packages/pi-ai/dist/index.d.ts +1 -0
- package/packages/pi-ai/dist/index.d.ts.map +1 -1
- package/packages/pi-ai/dist/index.js +1 -0
- package/packages/pi-ai/dist/index.js.map +1 -1
- package/packages/pi-ai/dist/utils/overflow.d.ts.map +1 -1
- package/packages/pi-ai/dist/utils/overflow.js +12 -0
- package/packages/pi-ai/dist/utils/overflow.js.map +1 -1
- package/packages/pi-ai/dist/utils/tests/overflow.test.d.ts +2 -0
- package/packages/pi-ai/dist/utils/tests/overflow.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/utils/tests/overflow.test.js +50 -0
- package/packages/pi-ai/dist/utils/tests/overflow.test.js.map +1 -0
- package/packages/pi-ai/src/index.ts +4 -0
- package/packages/pi-ai/src/utils/overflow.ts +14 -1
- package/packages/pi-ai/src/utils/tests/overflow.test.ts +58 -0
- 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/chat-controller-ordering.test.js +313 -8
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/utils.js +5 -5
- package/packages/pi-coding-agent/dist/core/compaction/utils.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction-utils.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/compaction-utils.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/compaction-utils.test.js +45 -0
- package/packages/pi-coding-agent/dist/core/compaction-utils.test.js.map +1 -0
- 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 -68
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts +12 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +61 -28
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.d.ts +2 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.js +9 -3
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js +52 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js.map +1 -0
- 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 +116 -25
- 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/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +11 -3
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- 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/chat-controller-ordering.test.ts +355 -8
- package/packages/pi-coding-agent/src/core/compaction/utils.ts +5 -5
- package/packages/pi-coding-agent/src/core/compaction-utils.test.ts +50 -0
- package/packages/pi-coding-agent/src/core/model-resolver.ts +26 -70
- package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +74 -32
- package/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.test.ts +73 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.ts +9 -3
- 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 +136 -30
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +11 -3
- package/packages/pi-tui/dist/__tests__/tui.test.js +60 -1
- package/packages/pi-tui/dist/__tests__/tui.test.js.map +1 -1
- package/packages/pi-tui/dist/tui.d.ts +8 -0
- package/packages/pi-tui/dist/tui.d.ts.map +1 -1
- package/packages/pi-tui/dist/tui.js +32 -3
- package/packages/pi-tui/dist/tui.js.map +1 -1
- package/packages/pi-tui/src/__tests__/tui.test.ts +76 -1
- package/packages/pi-tui/src/tui.ts +31 -3
- package/pkg/package.json +1 -1
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +107 -5
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +111 -2
- package/src/resources/extensions/gsd/auto/phases.ts +22 -9
- package/src/resources/extensions/gsd/auto-dispatch.ts +15 -4
- package/src/resources/extensions/gsd/auto-model-selection.ts +85 -11
- package/src/resources/extensions/gsd/auto-post-unit.ts +47 -1
- package/src/resources/extensions/gsd/auto-prompts.ts +9 -3
- package/src/resources/extensions/gsd/auto-start.ts +30 -6
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +17 -0
- package/src/resources/extensions/gsd/auto-verification.ts +98 -3
- package/src/resources/extensions/gsd/auto.ts +36 -14
- 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/system-context.ts +8 -1
- package/src/resources/extensions/gsd/commands-handlers.ts +8 -2
- package/src/resources/extensions/gsd/crash-recovery.ts +59 -0
- package/src/resources/extensions/gsd/docs/preferences-reference.md +1 -1
- package/src/resources/extensions/gsd/gsd-db.ts +52 -2
- package/src/resources/extensions/gsd/milestone-actions.ts +19 -1
- package/src/resources/extensions/gsd/notification-widget.ts +2 -2
- package/src/resources/extensions/gsd/preferences-models.ts +41 -0
- package/src/resources/extensions/gsd/preferences-types.ts +12 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +23 -0
- package/src/resources/extensions/gsd/state.ts +71 -15
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/auto-post-unit-step-message.test.ts +53 -0
- package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +51 -2
- package/src/resources/extensions/gsd/tests/complete-milestone-false-merge.test.ts +142 -0
- package/src/resources/extensions/gsd/tests/completed-at-reconcile.test.ts +42 -0
- package/src/resources/extensions/gsd/tests/crash-handler-secondary.test.ts +235 -0
- package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +68 -8
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/flat-rate-routing-guard.test.ts +137 -1
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +59 -1
- package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +4 -2
- package/src/resources/extensions/gsd/tests/model-isolation.test.ts +91 -2
- package/src/resources/extensions/gsd/tests/park-milestone.test.ts +64 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +47 -0
- package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +5 -7
- package/src/resources/extensions/gsd/tests/subagent-model-dispatch.test.ts +267 -0
- package/src/resources/extensions/gsd/tests/token-profile.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/validate-milestone-stuck-guard.test.ts +179 -0
- 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/dist/web/standalone/.next/static/{KSZ2dcC3p4z6lOmUpPpzr → RXD20AQgB9BHSQJ07MDdd}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{KSZ2dcC3p4z6lOmUpPpzr → RXD20AQgB9BHSQJ07MDdd}/_ssgManifest.js +0 -0
|
@@ -12,10 +12,15 @@
|
|
|
12
12
|
|
|
13
13
|
import type { ExtensionContext, ExtensionAPI } from "@gsd/pi-coding-agent";
|
|
14
14
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
15
|
-
import { resolveSliceFile, resolveSlicePath } from "./paths.js";
|
|
15
|
+
import { resolveSliceFile, resolveSlicePath, resolveMilestoneFile } from "./paths.js";
|
|
16
16
|
import { parseUnitId } from "./unit-id.js";
|
|
17
|
-
import { isDbAvailable, getTask, getSliceTasks, type TaskRow } from "./gsd-db.js";
|
|
17
|
+
import { isDbAvailable, getTask, getSliceTasks, getMilestoneSlices, type TaskRow } from "./gsd-db.js";
|
|
18
18
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
19
|
+
import { extractVerdict } from "./verdict-parser.js";
|
|
20
|
+
import { isClosedStatus } from "./status-guards.js";
|
|
21
|
+
import { loadFile } from "./files.js";
|
|
22
|
+
import { parseRoadmap } from "./parsers-legacy.js";
|
|
23
|
+
import { isMilestoneComplete } from "./state.js";
|
|
19
24
|
import {
|
|
20
25
|
runVerificationGate,
|
|
21
26
|
formatFailureContext,
|
|
@@ -43,6 +48,88 @@ function isInfraVerificationFailure(stderr: string): boolean {
|
|
|
43
48
|
);
|
|
44
49
|
}
|
|
45
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Post-unit guard for `validate-milestone` units (#4094).
|
|
53
|
+
*
|
|
54
|
+
* When validate-milestone writes verdict=needs-remediation, the agent is
|
|
55
|
+
* expected to also call gsd_reassess_roadmap in the same turn to add
|
|
56
|
+
* remediation slices. If they don't, the state machine re-derives
|
|
57
|
+
* `phase: validating-milestone` indefinitely (all slices still complete +
|
|
58
|
+
* verdict still needs-remediation), wasting ~3 dispatches before the stuck
|
|
59
|
+
* detector fires.
|
|
60
|
+
*
|
|
61
|
+
* This guard fires immediately on the first occurrence: if VALIDATION.md
|
|
62
|
+
* verdict is needs-remediation and no incomplete slices exist for the
|
|
63
|
+
* milestone, pause the auto-loop with a clear blocker.
|
|
64
|
+
*/
|
|
65
|
+
async function runValidateMilestonePostCheck(
|
|
66
|
+
vctx: VerificationContext,
|
|
67
|
+
pauseAuto: (ctx?: ExtensionContext, pi?: ExtensionAPI) => Promise<void>,
|
|
68
|
+
): Promise<VerificationResult> {
|
|
69
|
+
const { s, ctx, pi } = vctx;
|
|
70
|
+
if (!s.currentUnit) return "continue";
|
|
71
|
+
|
|
72
|
+
const { milestone: mid } = parseUnitId(s.currentUnit.id);
|
|
73
|
+
if (!mid) return "continue";
|
|
74
|
+
|
|
75
|
+
const validationFile = resolveMilestoneFile(s.basePath, mid, "VALIDATION");
|
|
76
|
+
if (!validationFile) return "continue";
|
|
77
|
+
|
|
78
|
+
const validationContent = await loadFile(validationFile);
|
|
79
|
+
if (!validationContent) return "continue";
|
|
80
|
+
|
|
81
|
+
const verdict = extractVerdict(validationContent);
|
|
82
|
+
if (verdict !== "needs-remediation") return "continue";
|
|
83
|
+
|
|
84
|
+
const incompleteSliceCount = await countIncompleteSlices(s.basePath, mid);
|
|
85
|
+
|
|
86
|
+
// If any non-closed slices exist, the agent successfully queued remediation
|
|
87
|
+
// work — proceed normally. The state machine will execute those slices and
|
|
88
|
+
// re-validate per the #3596/#3670 fix.
|
|
89
|
+
if (incompleteSliceCount > 0) return "continue";
|
|
90
|
+
|
|
91
|
+
ctx.ui.notify(
|
|
92
|
+
`Milestone ${mid} validation returned verdict=needs-remediation but no remediation slices were added. Pausing for human review.`,
|
|
93
|
+
"error",
|
|
94
|
+
);
|
|
95
|
+
process.stderr.write(
|
|
96
|
+
`validate-milestone: pausing — verdict=needs-remediation with no incomplete slices for ${mid}. ` +
|
|
97
|
+
`The agent must call gsd_reassess_roadmap to add remediation slices before re-validation.\n`,
|
|
98
|
+
);
|
|
99
|
+
await pauseAuto(ctx, pi);
|
|
100
|
+
return "pause";
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Count slices for a milestone that are not in a closed status.
|
|
105
|
+
* DB-backed projects are authoritative (#4094 peer review); falls back to
|
|
106
|
+
* roadmap parsing only when the DB is unavailable.
|
|
107
|
+
*/
|
|
108
|
+
async function countIncompleteSlices(basePath: string, milestoneId: string): Promise<number> {
|
|
109
|
+
if (isDbAvailable()) {
|
|
110
|
+
const slices = getMilestoneSlices(milestoneId);
|
|
111
|
+
if (slices.length === 0) {
|
|
112
|
+
// No DB rows — treat as "unknown", do not pause.
|
|
113
|
+
return 1;
|
|
114
|
+
}
|
|
115
|
+
return slices.filter((slice) => !isClosedStatus(slice.status)).length;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Filesystem fallback: parse the roadmap markdown.
|
|
119
|
+
try {
|
|
120
|
+
const roadmapFile = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
|
|
121
|
+
if (!roadmapFile) return 1;
|
|
122
|
+
const roadmapContent = await loadFile(roadmapFile);
|
|
123
|
+
if (!roadmapContent) return 1;
|
|
124
|
+
const roadmap = parseRoadmap(roadmapContent);
|
|
125
|
+
if (roadmap.slices.length === 0) return 1;
|
|
126
|
+
return isMilestoneComplete(roadmap) ? 0 : 1;
|
|
127
|
+
} catch {
|
|
128
|
+
// Parsing failures should not cause false-positive pauses.
|
|
129
|
+
return 1;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
46
133
|
/**
|
|
47
134
|
* Run the verification gate for the current execute-task unit.
|
|
48
135
|
* Returns:
|
|
@@ -56,7 +143,15 @@ export async function runPostUnitVerification(
|
|
|
56
143
|
): Promise<VerificationResult> {
|
|
57
144
|
const { s, ctx, pi } = vctx;
|
|
58
145
|
|
|
59
|
-
if (!s.currentUnit
|
|
146
|
+
if (!s.currentUnit) {
|
|
147
|
+
return "continue";
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (s.currentUnit.type === "validate-milestone") {
|
|
151
|
+
return await runValidateMilestonePostCheck(vctx, pauseAuto);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (s.currentUnit.type !== "execute-task") {
|
|
60
155
|
return "continue";
|
|
61
156
|
}
|
|
62
157
|
|
|
@@ -52,6 +52,7 @@ import {
|
|
|
52
52
|
readCrashLock,
|
|
53
53
|
isLockProcessAlive,
|
|
54
54
|
formatCrashInfo,
|
|
55
|
+
emitCrashRecoveredUnitEnd,
|
|
55
56
|
} from "./crash-recovery.js";
|
|
56
57
|
import {
|
|
57
58
|
acquireSessionLock,
|
|
@@ -186,7 +187,7 @@ import {
|
|
|
186
187
|
deregisterSigtermHandler as _deregisterSigtermHandler,
|
|
187
188
|
detectWorkingTreeActivity,
|
|
188
189
|
} from "./auto-supervisor.js";
|
|
189
|
-
import { isDbAvailable } from "./gsd-db.js";
|
|
190
|
+
import { isDbAvailable, getMilestone } from "./gsd-db.js";
|
|
190
191
|
import { countPendingCaptures } from "./captures.js";
|
|
191
192
|
import { clearCmuxSidebar, logCmuxEvent, syncCmuxSidebar } from "../cmux/index.js";
|
|
192
193
|
|
|
@@ -198,6 +199,7 @@ import {
|
|
|
198
199
|
postUnitPostVerification,
|
|
199
200
|
} from "./auto-post-unit.js";
|
|
200
201
|
import { bootstrapAutoSession, openProjectDbIfPresent, type BootstrapDeps } from "./auto-start.js";
|
|
202
|
+
import { initHealthWidget } from "./health-widget.js";
|
|
201
203
|
import { autoLoop, resolveAgentEnd, resolveAgentEndCancelled, _resetPendingResolve, isSessionSwitchInFlight, type LoopDeps, type ErrorContext } from "./auto-loop.js";
|
|
202
204
|
// Slice-level parallelism (#2340)
|
|
203
205
|
import { getEligibleSlices } from "./slice-parallel-eligibility.js";
|
|
@@ -649,6 +651,7 @@ function handleLostSessionLock(
|
|
|
649
651
|
ctx?.ui.setStatus("gsd-auto", undefined);
|
|
650
652
|
ctx?.ui.setWidget("gsd-progress", undefined);
|
|
651
653
|
ctx?.ui.setFooter(undefined);
|
|
654
|
+
if (ctx) initHealthWidget(ctx);
|
|
652
655
|
}
|
|
653
656
|
|
|
654
657
|
/**
|
|
@@ -683,6 +686,7 @@ function cleanupAfterLoopExit(ctx: ExtensionContext): void {
|
|
|
683
686
|
ctx.ui.setStatus("gsd-auto", undefined);
|
|
684
687
|
ctx.ui.setWidget("gsd-progress", undefined);
|
|
685
688
|
ctx.ui.setFooter(undefined);
|
|
689
|
+
initHealthWidget(ctx);
|
|
686
690
|
}
|
|
687
691
|
|
|
688
692
|
// Restore CWD out of worktree back to original project root
|
|
@@ -757,24 +761,36 @@ export async function stopAuto(
|
|
|
757
761
|
: { notify: () => {} };
|
|
758
762
|
const resolver = buildResolver();
|
|
759
763
|
|
|
760
|
-
// Check if the milestone is complete
|
|
764
|
+
// Check if the milestone is complete. DB status is the authoritative
|
|
765
|
+
// signal — only a successful gsd_complete_milestone call flips it to
|
|
766
|
+
// "complete" (tools/complete-milestone.ts). SUMMARY file presence is
|
|
767
|
+
// NOT sufficient: a blocker placeholder stub or a partial write can
|
|
768
|
+
// leave a file behind without the milestone actually being done,
|
|
769
|
+
// which previously caused stopAuto to merge a failed milestone and
|
|
770
|
+
// emit a misleading metadata-only merge warning (#4175).
|
|
771
|
+
// DB-unavailable projects fall back to SUMMARY-file presence.
|
|
761
772
|
let milestoneComplete = false;
|
|
762
773
|
try {
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
// Also check in the worktree path (SUMMARY may not be synced yet)
|
|
770
|
-
const wtSummaryPath = resolveMilestoneFile(
|
|
771
|
-
s.basePath,
|
|
774
|
+
if (isDbAvailable()) {
|
|
775
|
+
const dbRow = getMilestone(s.currentMilestoneId);
|
|
776
|
+
milestoneComplete = dbRow?.status === "complete";
|
|
777
|
+
} else {
|
|
778
|
+
const summaryPath = resolveMilestoneFile(
|
|
779
|
+
s.originalBasePath || s.basePath,
|
|
772
780
|
s.currentMilestoneId,
|
|
773
781
|
"SUMMARY",
|
|
774
782
|
);
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
783
|
+
if (!summaryPath) {
|
|
784
|
+
// Also check in the worktree path (SUMMARY may not be synced yet)
|
|
785
|
+
const wtSummaryPath = resolveMilestoneFile(
|
|
786
|
+
s.basePath,
|
|
787
|
+
s.currentMilestoneId,
|
|
788
|
+
"SUMMARY",
|
|
789
|
+
);
|
|
790
|
+
milestoneComplete = wtSummaryPath !== null;
|
|
791
|
+
} else {
|
|
792
|
+
milestoneComplete = true;
|
|
793
|
+
}
|
|
778
794
|
}
|
|
779
795
|
} catch (err) {
|
|
780
796
|
// Non-fatal — fall through to preserveBranch path
|
|
@@ -942,6 +958,7 @@ export async function stopAuto(
|
|
|
942
958
|
ctx?.ui.setStatus("gsd-auto", undefined);
|
|
943
959
|
ctx?.ui.setWidget("gsd-progress", undefined);
|
|
944
960
|
ctx?.ui.setFooter(undefined);
|
|
961
|
+
if (ctx) initHealthWidget(ctx);
|
|
945
962
|
restoreProjectRootEnv();
|
|
946
963
|
restoreMilestoneLockEnv();
|
|
947
964
|
|
|
@@ -1043,6 +1060,7 @@ export async function pauseAuto(
|
|
|
1043
1060
|
ctx?.ui.setStatus("gsd-auto", "paused");
|
|
1044
1061
|
ctx?.ui.setWidget("gsd-progress", undefined);
|
|
1045
1062
|
ctx?.ui.setFooter(undefined);
|
|
1063
|
+
if (ctx) initHealthWidget(ctx);
|
|
1046
1064
|
const resumeCmd = s.stepMode ? "/gsd next" : "/gsd auto";
|
|
1047
1065
|
ctx?.ui.notify(
|
|
1048
1066
|
`${s.stepMode ? "Step" : "Auto"}-mode paused (Escape). Type to interact, or ${resumeCmd} to resume.`,
|
|
@@ -1332,6 +1350,10 @@ export async function startAuto(
|
|
|
1332
1350
|
}
|
|
1333
1351
|
|
|
1334
1352
|
if (freshStartAssessment.lock) {
|
|
1353
|
+
// Emit a synthetic unit-end for any unit-start that has no closing event.
|
|
1354
|
+
// This closes the journal gap reported in #3348 where the worker wrote side
|
|
1355
|
+
// effects (SUMMARY.md, DB updates) but died before emitting unit-end.
|
|
1356
|
+
emitCrashRecoveredUnitEnd(base, freshStartAssessment.lock);
|
|
1335
1357
|
clearLock(base);
|
|
1336
1358
|
}
|
|
1337
1359
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* crash-log.ts — Write crash diagnostics to ~/.gsd/crash/<timestamp>.log
|
|
3
|
+
*
|
|
4
|
+
* Zero cross-dependencies: only uses Node.js built-ins so it can be imported
|
|
5
|
+
* safely from uncaughtException / unhandledRejection handlers and from tests
|
|
6
|
+
* without pulling in the full extension dependency tree.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { appendFileSync, mkdirSync } from "node:fs";
|
|
10
|
+
import { homedir } from "node:os";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Write a crash log to ~/.gsd/crash/<timestamp>.log (or $GSD_HOME/crash/).
|
|
15
|
+
* Never throws — must be safe to call from any error handler.
|
|
16
|
+
*/
|
|
17
|
+
export function writeCrashLog(err: Error, source: string): void {
|
|
18
|
+
try {
|
|
19
|
+
const crashDir = join(process.env.GSD_HOME ?? join(homedir(), ".gsd"), "crash");
|
|
20
|
+
mkdirSync(crashDir, { recursive: true });
|
|
21
|
+
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
22
|
+
const logPath = join(crashDir, `${ts}.log`);
|
|
23
|
+
const lines = [
|
|
24
|
+
`[gsd] ${source}: ${err.message}`,
|
|
25
|
+
`timestamp: ${new Date().toISOString()}`,
|
|
26
|
+
`pid: ${process.pid}`,
|
|
27
|
+
err.stack ?? "(no stack trace available)",
|
|
28
|
+
"",
|
|
29
|
+
];
|
|
30
|
+
appendFileSync(logPath, lines.join("\n"));
|
|
31
|
+
} catch { /* never throw from crash handler */ }
|
|
32
|
+
}
|
|
@@ -11,6 +11,9 @@ import { registerJournalTools } from "./journal-tools.js";
|
|
|
11
11
|
import { registerQueryTools } from "./query-tools.js";
|
|
12
12
|
import { registerHooks } from "./register-hooks.js";
|
|
13
13
|
import { registerShortcuts } from "./register-shortcuts.js";
|
|
14
|
+
import { writeCrashLog } from "./crash-log.js";
|
|
15
|
+
|
|
16
|
+
export { writeCrashLog } from "./crash-log.js";
|
|
14
17
|
|
|
15
18
|
export function handleRecoverableExtensionProcessError(err: Error): boolean {
|
|
16
19
|
if ((err as NodeJS.ErrnoException).code === "EPIPE") {
|
|
@@ -33,16 +36,25 @@ export function handleRecoverableExtensionProcessError(err: Error): boolean {
|
|
|
33
36
|
function installEpipeGuard(): void {
|
|
34
37
|
if (!process.listeners("uncaughtException").some((listener) => listener.name === "_gsdEpipeGuard")) {
|
|
35
38
|
const _gsdEpipeGuard = (err: Error): void => {
|
|
36
|
-
if (handleRecoverableExtensionProcessError(err))
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
process.
|
|
42
|
-
if (err.stack) process.stderr.write(`${err.stack}\n`);
|
|
39
|
+
if (handleRecoverableExtensionProcessError(err)) return;
|
|
40
|
+
// Write crash log and exit cleanly for unrecoverable errors.
|
|
41
|
+
// Logging and continuing was the original double-fault fix (#3163), but
|
|
42
|
+
// continuing in an indeterminate state is worse than a clean exit (#3348).
|
|
43
|
+
writeCrashLog(err, "uncaughtException");
|
|
44
|
+
process.exit(1);
|
|
43
45
|
};
|
|
44
46
|
process.on("uncaughtException", _gsdEpipeGuard);
|
|
45
47
|
}
|
|
48
|
+
|
|
49
|
+
if (!process.listeners("unhandledRejection").some((listener) => listener.name === "_gsdRejectionGuard")) {
|
|
50
|
+
const _gsdRejectionGuard = (reason: unknown, _promise: Promise<unknown>): void => {
|
|
51
|
+
const err = reason instanceof Error ? reason : new Error(String(reason));
|
|
52
|
+
if (handleRecoverableExtensionProcessError(err)) return;
|
|
53
|
+
writeCrashLog(err, "unhandledRejection");
|
|
54
|
+
process.exit(1);
|
|
55
|
+
};
|
|
56
|
+
process.on("unhandledRejection", _gsdRejectionGuard);
|
|
57
|
+
}
|
|
46
58
|
}
|
|
47
59
|
|
|
48
60
|
export function registerGsdExtension(pi: ExtensionAPI): void {
|
|
@@ -9,6 +9,7 @@ import { debugTime } from "../debug-logger.js";
|
|
|
9
9
|
import { loadPrompt, getTemplatesDir } from "../prompt-loader.js";
|
|
10
10
|
import { readForensicsMarker } from "../forensics.js";
|
|
11
11
|
import { resolveAllSkillReferences, renderPreferencesForSystemPrompt, loadEffectiveGSDPreferences } from "../preferences.js";
|
|
12
|
+
import { resolveModelWithFallbacksForUnit } from "../preferences-models.js";
|
|
12
13
|
import { resolveSkillReference } from "../preferences-skills.js";
|
|
13
14
|
import { resolveGsdRootFile, resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTaskFiles, resolveTasksDir, relSliceFile, relSlicePath, relTaskFile } from "../paths.js";
|
|
14
15
|
import { ensureCodebaseMapFresh, readCodebaseMap } from "../codebase-generator.js";
|
|
@@ -175,7 +176,13 @@ export async function buildBeforeAgentStartResult(
|
|
|
175
176
|
const forensicsInjection = !injection ? buildForensicsContextInjection(process.cwd(), event.prompt) : null;
|
|
176
177
|
|
|
177
178
|
const worktreeBlock = buildWorktreeContextBlock();
|
|
178
|
-
|
|
179
|
+
|
|
180
|
+
const subagentModelConfig = resolveModelWithFallbacksForUnit("subagent");
|
|
181
|
+
const subagentModelBlock = subagentModelConfig
|
|
182
|
+
? `\n\n## Subagent Model\n\nWhen spawning subagents via the \`subagent\` tool, always pass \`model: "${subagentModelConfig.primary}"\` in the tool call parameters. Never omit this — always specify it explicitly.`
|
|
183
|
+
: "";
|
|
184
|
+
|
|
185
|
+
const fullSystem = `${event.systemPrompt}\n\n[SYSTEM CONTEXT — GSD]\n\n${systemContent}${preferenceBlock}${knowledgeBlock}${codebaseBlock}${memoryBlock}${newSkillsBlock}${worktreeBlock}${subagentModelBlock}`;
|
|
179
186
|
|
|
180
187
|
stopContextTimer({
|
|
181
188
|
systemPromptSize: fullSystem.length,
|
|
@@ -28,6 +28,11 @@ import { loadPrompt } from "./prompt-loader.js";
|
|
|
28
28
|
const UPDATE_REGISTRY_URL = "https://registry.npmjs.org/gsd-pi/latest";
|
|
29
29
|
const UPDATE_FETCH_TIMEOUT_MS = 5000;
|
|
30
30
|
|
|
31
|
+
function resolveInstallCommand(pkg: string): string {
|
|
32
|
+
if ('bun' in process.versions) return `bun add -g ${pkg}`;
|
|
33
|
+
return `npm install -g ${pkg}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
31
36
|
async function fetchLatestVersionForCommand(): Promise<string | null> {
|
|
32
37
|
const controller = new AbortController();
|
|
33
38
|
const timeout = setTimeout(() => controller.abort(), UPDATE_FETCH_TIMEOUT_MS);
|
|
@@ -431,8 +436,9 @@ export async function handleUpdate(ctx: ExtensionCommandContext): Promise<void>
|
|
|
431
436
|
|
|
432
437
|
ctx.ui.notify(`Updating: v${current} → v${latest}...`, "info");
|
|
433
438
|
|
|
439
|
+
const installCmd = resolveInstallCommand(`${NPM_PACKAGE}@latest`);
|
|
434
440
|
try {
|
|
435
|
-
execSync(
|
|
441
|
+
execSync(installCmd, {
|
|
436
442
|
stdio: ["ignore", "pipe", "ignore"],
|
|
437
443
|
});
|
|
438
444
|
ctx.ui.notify(
|
|
@@ -441,7 +447,7 @@ export async function handleUpdate(ctx: ExtensionCommandContext): Promise<void>
|
|
|
441
447
|
);
|
|
442
448
|
} catch {
|
|
443
449
|
ctx.ui.notify(
|
|
444
|
-
`Update failed. Try manually:
|
|
450
|
+
`Update failed. Try manually: ${installCmd}`,
|
|
445
451
|
"error",
|
|
446
452
|
);
|
|
447
453
|
}
|
|
@@ -15,6 +15,7 @@ import { join } from "node:path";
|
|
|
15
15
|
import { gsdRoot } from "./paths.js";
|
|
16
16
|
import { atomicWriteSync } from "./atomic-write.js";
|
|
17
17
|
import { effectiveLockFile } from "./session-lock.js";
|
|
18
|
+
import { emitJournalEvent, queryJournal } from "./journal.js";
|
|
18
19
|
|
|
19
20
|
export interface LockData {
|
|
20
21
|
pid: number;
|
|
@@ -118,3 +119,61 @@ export function formatCrashInfo(lock: LockData): string {
|
|
|
118
119
|
|
|
119
120
|
return lines.join("\n");
|
|
120
121
|
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Emit a synthetic unit-end event for a unit that crashed without emitting its own.
|
|
125
|
+
*
|
|
126
|
+
* Queries the journal to find the most recent unit-start for the crashed unit.
|
|
127
|
+
* If a matching unit-end already exists (e.g. the hard timeout fired), this is a
|
|
128
|
+
* no-op. Called during crash recovery, before clearing the stale lock.
|
|
129
|
+
*
|
|
130
|
+
* Addresses the gap reported in #3348 where `unit-start` was emitted but no
|
|
131
|
+
* `unit-end` followed — side effects landed but the worker died before closeout.
|
|
132
|
+
*/
|
|
133
|
+
export function emitCrashRecoveredUnitEnd(basePath: string, lock: LockData): void {
|
|
134
|
+
// Skip bootstrap / starting pseudo-units — they have no meaningful unit-start event.
|
|
135
|
+
if (!lock.unitType || !lock.unitId || lock.unitType === "starting") return;
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
const all = queryJournal(basePath);
|
|
139
|
+
|
|
140
|
+
// Find the most recent unit-start for this unitId
|
|
141
|
+
const starts = all.filter(
|
|
142
|
+
(e) => e.eventType === "unit-start" && e.data?.unitId === lock.unitId,
|
|
143
|
+
);
|
|
144
|
+
if (starts.length === 0) return;
|
|
145
|
+
|
|
146
|
+
const lastStart = starts[starts.length - 1];
|
|
147
|
+
|
|
148
|
+
// Check if a unit-end was already emitted (e.g. hard timeout fired after the crash)
|
|
149
|
+
const alreadyClosed = all.some(
|
|
150
|
+
(e) =>
|
|
151
|
+
e.eventType === "unit-end" &&
|
|
152
|
+
e.data?.unitId === lock.unitId &&
|
|
153
|
+
e.causedBy?.flowId === lastStart.flowId &&
|
|
154
|
+
e.causedBy?.seq === lastStart.seq,
|
|
155
|
+
);
|
|
156
|
+
if (alreadyClosed) return;
|
|
157
|
+
|
|
158
|
+
// Find the highest seq in this flow for monotonic ordering
|
|
159
|
+
const maxSeq = all
|
|
160
|
+
.filter((e) => e.flowId === lastStart.flowId)
|
|
161
|
+
.reduce((max, e) => Math.max(max, e.seq), lastStart.seq);
|
|
162
|
+
|
|
163
|
+
emitJournalEvent(basePath, {
|
|
164
|
+
ts: new Date().toISOString(),
|
|
165
|
+
flowId: lastStart.flowId,
|
|
166
|
+
seq: maxSeq + 1,
|
|
167
|
+
eventType: "unit-end",
|
|
168
|
+
data: {
|
|
169
|
+
unitType: lock.unitType,
|
|
170
|
+
unitId: lock.unitId,
|
|
171
|
+
status: "crash-recovered",
|
|
172
|
+
artifactVerified: false,
|
|
173
|
+
},
|
|
174
|
+
causedBy: { flowId: lastStart.flowId, seq: lastStart.seq },
|
|
175
|
+
});
|
|
176
|
+
} catch {
|
|
177
|
+
// Never throw from crash recovery path — journal failure must not block recovery
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -157,7 +157,7 @@ Setting `prefer_skills: []` does **not** disable skill discovery — it just mea
|
|
|
157
157
|
|
|
158
158
|
- `phases`: fine-grained control over which phases run. Usually set by `token_profile`, but can be overridden. Keys:
|
|
159
159
|
- `skip_research`: boolean — skip milestone-level research. Default: `false`.
|
|
160
|
-
- `reassess_after_slice`: boolean — run roadmap reassessment after each completed slice. Default: `
|
|
160
|
+
- `reassess_after_slice`: boolean — run roadmap reassessment after each completed slice. Default: `true`.
|
|
161
161
|
- `skip_reassess`: boolean — force-disable roadmap reassessment even if `reassess_after_slice` is enabled. Default: `false`.
|
|
162
162
|
- `skip_slice_research`: boolean — skip per-slice research. Default: `false`.
|
|
163
163
|
|
|
@@ -1564,6 +1564,23 @@ export interface TaskRow {
|
|
|
1564
1564
|
sequence: number;
|
|
1565
1565
|
}
|
|
1566
1566
|
|
|
1567
|
+
function parseTaskArrayColumn(raw: unknown): string[] {
|
|
1568
|
+
if (typeof raw !== "string" || raw.trim() === "") return [];
|
|
1569
|
+
|
|
1570
|
+
try {
|
|
1571
|
+
const parsed = JSON.parse(raw);
|
|
1572
|
+
if (Array.isArray(parsed)) return parsed.map((value) => String(value));
|
|
1573
|
+
if (parsed === null || parsed === undefined || parsed === "") return [];
|
|
1574
|
+
return [String(parsed)];
|
|
1575
|
+
} catch {
|
|
1576
|
+
// Older/corrupt rows may contain comma-separated strings instead of JSON.
|
|
1577
|
+
return raw
|
|
1578
|
+
.split(",")
|
|
1579
|
+
.map((value) => value.trim())
|
|
1580
|
+
.filter(Boolean);
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1567
1584
|
function rowToTask(row: Record<string, unknown>): TaskRow {
|
|
1568
1585
|
const parseTaskArray = (value: unknown): string[] => {
|
|
1569
1586
|
if (Array.isArray(value)) {
|
|
@@ -1603,8 +1620,8 @@ function rowToTask(row: Record<string, unknown>): TaskRow {
|
|
|
1603
1620
|
blocker_discovered: (row["blocker_discovered"] as number) === 1,
|
|
1604
1621
|
deviations: row["deviations"] as string,
|
|
1605
1622
|
known_issues: row["known_issues"] as string,
|
|
1606
|
-
key_files:
|
|
1607
|
-
key_decisions:
|
|
1623
|
+
key_files: parseTaskArrayColumn(row["key_files"]),
|
|
1624
|
+
key_decisions: parseTaskArrayColumn(row["key_decisions"]),
|
|
1608
1625
|
full_summary_md: row["full_summary_md"] as string,
|
|
1609
1626
|
description: (row["description"] as string) ?? "",
|
|
1610
1627
|
estimate: (row["estimate"] as string) ?? "",
|
|
@@ -2200,6 +2217,39 @@ export function deleteSlice(milestoneId: string, sliceId: string): void {
|
|
|
2200
2217
|
});
|
|
2201
2218
|
}
|
|
2202
2219
|
|
|
2220
|
+
export function deleteMilestone(milestoneId: string): void {
|
|
2221
|
+
if (!currentDb) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2222
|
+
transaction(() => {
|
|
2223
|
+
currentDb!.prepare(
|
|
2224
|
+
`DELETE FROM verification_evidence WHERE milestone_id = :mid`,
|
|
2225
|
+
).run({ ":mid": milestoneId });
|
|
2226
|
+
currentDb!.prepare(
|
|
2227
|
+
`DELETE FROM quality_gates WHERE milestone_id = :mid`,
|
|
2228
|
+
).run({ ":mid": milestoneId });
|
|
2229
|
+
currentDb!.prepare(
|
|
2230
|
+
`DELETE FROM tasks WHERE milestone_id = :mid`,
|
|
2231
|
+
).run({ ":mid": milestoneId });
|
|
2232
|
+
currentDb!.prepare(
|
|
2233
|
+
`DELETE FROM slice_dependencies WHERE milestone_id = :mid`,
|
|
2234
|
+
).run({ ":mid": milestoneId });
|
|
2235
|
+
currentDb!.prepare(
|
|
2236
|
+
`DELETE FROM slices WHERE milestone_id = :mid`,
|
|
2237
|
+
).run({ ":mid": milestoneId });
|
|
2238
|
+
currentDb!.prepare(
|
|
2239
|
+
`DELETE FROM replan_history WHERE milestone_id = :mid`,
|
|
2240
|
+
).run({ ":mid": milestoneId });
|
|
2241
|
+
currentDb!.prepare(
|
|
2242
|
+
`DELETE FROM assessments WHERE milestone_id = :mid`,
|
|
2243
|
+
).run({ ":mid": milestoneId });
|
|
2244
|
+
currentDb!.prepare(
|
|
2245
|
+
`DELETE FROM artifacts WHERE milestone_id = :mid`,
|
|
2246
|
+
).run({ ":mid": milestoneId });
|
|
2247
|
+
currentDb!.prepare(
|
|
2248
|
+
`DELETE FROM milestones WHERE id = :mid`,
|
|
2249
|
+
).run({ ":mid": milestoneId });
|
|
2250
|
+
});
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2203
2253
|
export function updateSliceFields(milestoneId: string, sliceId: string, fields: {
|
|
2204
2254
|
title?: string;
|
|
2205
2255
|
risk?: string;
|
|
@@ -20,7 +20,8 @@ import {
|
|
|
20
20
|
} from "./paths.js";
|
|
21
21
|
import { invalidateAllCaches } from "./cache.js";
|
|
22
22
|
import { loadQueueOrder, saveQueueOrder } from "./queue-order.js";
|
|
23
|
-
import { getMilestone, isDbAvailable, updateMilestoneStatus } from "./gsd-db.js";
|
|
23
|
+
import { deleteMilestone, getMilestone, isDbAvailable, updateMilestoneStatus } from "./gsd-db.js";
|
|
24
|
+
import { removeWorktree } from "./worktree-manager.js";
|
|
24
25
|
import { logWarning } from "./workflow-logger.js";
|
|
25
26
|
|
|
26
27
|
// ─── Park ──────────────────────────────────────────────────────────────────
|
|
@@ -110,6 +111,15 @@ export function discardMilestone(basePath: string, milestoneId: string): boolean
|
|
|
110
111
|
const mDir = resolveMilestonePath(basePath, milestoneId);
|
|
111
112
|
if (!mDir || !existsSync(mDir)) return false;
|
|
112
113
|
|
|
114
|
+
try {
|
|
115
|
+
removeWorktree(basePath, milestoneId, {
|
|
116
|
+
branch: `milestone/${milestoneId}`,
|
|
117
|
+
deleteBranch: true,
|
|
118
|
+
});
|
|
119
|
+
} catch (err) {
|
|
120
|
+
logWarning("engine", `discardMilestone worktree cleanup failed for ${milestoneId}: ${(err as Error).message}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
113
123
|
rmSync(mDir, { recursive: true, force: true });
|
|
114
124
|
|
|
115
125
|
// Prune from queue order if present
|
|
@@ -118,6 +128,14 @@ export function discardMilestone(basePath: string, milestoneId: string): boolean
|
|
|
118
128
|
saveQueueOrder(basePath, order.filter(id => id !== milestoneId));
|
|
119
129
|
}
|
|
120
130
|
|
|
131
|
+
if (isDbAvailable()) {
|
|
132
|
+
try {
|
|
133
|
+
deleteMilestone(milestoneId);
|
|
134
|
+
} catch (err) {
|
|
135
|
+
logWarning("engine", `discardMilestone DB cleanup failed for ${milestoneId}: ${(err as Error).message}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
121
139
|
invalidateAllCaches();
|
|
122
140
|
return true;
|
|
123
141
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// GSD Extension — Notification Widget
|
|
2
2
|
// Always-on ambient widget rendered belowEditor showing unread count and
|
|
3
|
-
// the most recent notification message. Refreshes every
|
|
3
|
+
// the most recent notification message. Refreshes every 30 seconds.
|
|
4
4
|
// Widget key: "gsd-notifications", placement: "belowEditor"
|
|
5
5
|
|
|
6
6
|
import type { ExtensionContext } from "@gsd/pi-coding-agent";
|
|
@@ -19,7 +19,7 @@ export function buildNotificationWidgetLines(): string[] {
|
|
|
19
19
|
|
|
20
20
|
// ─── Widget init ────────────────────────────────────────────────────────
|
|
21
21
|
|
|
22
|
-
const REFRESH_INTERVAL_MS =
|
|
22
|
+
const REFRESH_INTERVAL_MS = 30_000;
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Initialize the always-on notification widget (belowEditor).
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { homedir } from "node:os";
|
|
11
|
+
import { join } from "node:path";
|
|
10
12
|
import type { DynamicRoutingConfig } from "./model-router.js";
|
|
11
13
|
import { defaultRoutingConfig } from "./model-router.js";
|
|
12
14
|
import type { TokenProfile, InlineLevel } from "./types.js";
|
|
@@ -185,6 +187,45 @@ export function resolveDefaultSessionModel(
|
|
|
185
187
|
return undefined;
|
|
186
188
|
}
|
|
187
189
|
|
|
190
|
+
/**
|
|
191
|
+
* Returns true if `provider` is defined as a custom provider in the user's
|
|
192
|
+
* `~/.gsd/agent/models.json` (Ollama, vLLM, LM Studio, OpenAI-compatible
|
|
193
|
+
* proxies, etc.).
|
|
194
|
+
*
|
|
195
|
+
* Used by auto-mode bootstrap to decide whether the session model
|
|
196
|
+
* (set via `/gsd model`) should override `PREFERENCES.md`. Custom providers
|
|
197
|
+
* are never reachable from `PREFERENCES.md` (which only knows built-in
|
|
198
|
+
* providers), so when the user has explicitly selected one, it must take
|
|
199
|
+
* priority — otherwise auto-mode tries to start the built-in provider from
|
|
200
|
+
* PREFERENCES.md and fails with "Not logged in · Please run /login" (#4122).
|
|
201
|
+
*
|
|
202
|
+
* Reads models.json directly with a lightweight JSON parse to avoid
|
|
203
|
+
* pulling in the full model-registry at this call site. Falls back to
|
|
204
|
+
* `~/.pi/agent/models.json` for parity with `resolveModelsJsonPath()`.
|
|
205
|
+
* Any read or parse error yields `false` (treat as not-custom) so a
|
|
206
|
+
* malformed models.json never breaks the session bootstrap.
|
|
207
|
+
*/
|
|
208
|
+
export function isCustomProvider(provider: string | undefined): boolean {
|
|
209
|
+
if (!provider) return false;
|
|
210
|
+
const candidates = [
|
|
211
|
+
join(homedir(), ".gsd", "agent", "models.json"),
|
|
212
|
+
join(homedir(), ".pi", "agent", "models.json"),
|
|
213
|
+
];
|
|
214
|
+
for (const path of candidates) {
|
|
215
|
+
if (!existsSync(path)) continue;
|
|
216
|
+
try {
|
|
217
|
+
const raw = readFileSync(path, "utf-8");
|
|
218
|
+
const parsed = JSON.parse(raw) as { providers?: Record<string, unknown> };
|
|
219
|
+
if (parsed?.providers && Object.prototype.hasOwnProperty.call(parsed.providers, provider)) {
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
} catch {
|
|
223
|
+
// Ignore — malformed models.json must not break bootstrap.
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
|
|
188
229
|
/**
|
|
189
230
|
* Determines the next fallback model to try when the current model fails.
|
|
190
231
|
* If the current model is not in the configured list, returns the primary model.
|
|
@@ -113,6 +113,7 @@ export const KNOWN_PREFERENCE_KEYS = new Set<string>([
|
|
|
113
113
|
"discuss_preparation",
|
|
114
114
|
"discuss_web_research",
|
|
115
115
|
"discuss_depth",
|
|
116
|
+
"flat_rate_providers",
|
|
116
117
|
]);
|
|
117
118
|
|
|
118
119
|
/** Canonical list of all dispatch unit types. */
|
|
@@ -359,6 +360,17 @@ export interface GSDPreferences {
|
|
|
359
360
|
* Default: "standard".
|
|
360
361
|
*/
|
|
361
362
|
discuss_depth?: "quick" | "standard" | "thorough";
|
|
363
|
+
/**
|
|
364
|
+
* Extra provider IDs to treat as flat-rate (no cost benefit from dynamic
|
|
365
|
+
* routing). Dynamic routing is suppressed for any provider listed here,
|
|
366
|
+
* in addition to the built-in list (github-copilot, copilot, claude-code)
|
|
367
|
+
* and any provider auto-detected via `authMode: "externalCli"`.
|
|
368
|
+
*
|
|
369
|
+
* Intended for private subscription-backed proxies, enterprise-gated
|
|
370
|
+
* deployments, and custom CLI wrappers where every request costs the
|
|
371
|
+
* same regardless of model. Case-insensitive.
|
|
372
|
+
*/
|
|
373
|
+
flat_rate_providers?: string[];
|
|
362
374
|
}
|
|
363
375
|
|
|
364
376
|
export interface LoadedGSDPreferences {
|