gsd-pi 2.81.0 → 2.82.0
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 +36 -24
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/loop.js +111 -8
- package/dist/resources/extensions/gsd/auto/phases.js +190 -97
- package/dist/resources/extensions/gsd/auto/run-unit.js +66 -3
- package/dist/resources/extensions/gsd/auto/session.js +9 -0
- package/dist/resources/extensions/gsd/auto/verification-retry-policy.js +43 -0
- package/dist/resources/extensions/gsd/auto-dashboard.js +182 -178
- package/dist/resources/extensions/gsd/auto-dispatch.js +14 -11
- package/dist/resources/extensions/gsd/auto-post-unit.js +7 -1
- package/dist/resources/extensions/gsd/auto-recovery.js +6 -181
- package/dist/resources/extensions/gsd/auto-runtime-state.js +5 -0
- package/dist/resources/extensions/gsd/auto-start.js +20 -23
- package/dist/resources/extensions/gsd/auto-unit-closeout.js +33 -5
- package/dist/resources/extensions/gsd/auto-verification.js +12 -6
- package/dist/resources/extensions/gsd/auto-worktree.js +8 -0
- package/dist/resources/extensions/gsd/auto.js +265 -76
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +13 -6
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +8 -2
- package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +4 -8
- package/dist/resources/extensions/gsd/commands/handlers/notifications-handler.js +4 -10
- package/dist/resources/extensions/gsd/commands/handlers/parallel.js +9 -0
- package/dist/resources/extensions/gsd/git-service.js +2 -1
- package/dist/resources/extensions/gsd/gsd-db.js +7 -23
- package/dist/resources/extensions/gsd/health-widget-core.js +1 -1
- package/dist/resources/extensions/gsd/health-widget.js +4 -10
- package/dist/resources/extensions/gsd/markdown-renderer.js +0 -95
- package/dist/resources/extensions/gsd/native-git-bridge.js +14 -14
- package/dist/resources/extensions/gsd/notification-overlay.js +35 -40
- package/dist/resources/extensions/gsd/parallel-merge.js +53 -30
- package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +25 -33
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +14 -12
- package/dist/resources/extensions/gsd/prompts/discuss-headless.md +20 -2
- package/dist/resources/extensions/gsd/prompts/discuss.md +20 -2
- package/dist/resources/extensions/gsd/recovery-classification.js +15 -1
- package/dist/resources/extensions/gsd/session-lock.js +40 -0
- package/dist/resources/extensions/gsd/state-reconciliation/drift/completion.js +131 -0
- package/dist/resources/extensions/gsd/state-reconciliation/drift/merge-state.js +247 -0
- package/dist/resources/extensions/gsd/state-reconciliation/drift/project-md.js +50 -0
- package/dist/resources/extensions/gsd/state-reconciliation/drift/roadmap.js +87 -0
- package/dist/resources/extensions/gsd/state-reconciliation/drift/sketch-flag.js +50 -0
- package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-render.js +124 -0
- package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-worker.js +32 -0
- package/dist/resources/extensions/gsd/state-reconciliation/errors.js +41 -0
- package/dist/resources/extensions/gsd/state-reconciliation/index.js +99 -0
- package/dist/resources/extensions/gsd/state-reconciliation/registry.js +24 -0
- package/dist/resources/extensions/gsd/state-reconciliation/spawn-gate.js +43 -0
- package/dist/resources/extensions/gsd/state-reconciliation/types.js +3 -0
- package/dist/resources/extensions/gsd/state-reconciliation.js +5 -26
- package/dist/resources/extensions/gsd/tui/render-kit.js +74 -0
- package/dist/resources/extensions/gsd/watch/header-renderer.js +92 -69
- package/dist/resources/extensions/gsd/watch/splash-palette.js +10 -0
- package/dist/resources/extensions/gsd/workflow-mcp.js +2 -2
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +722 -316
- package/dist/resources/extensions/gsd/worktree-telemetry.js +3 -1
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +9 -9
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- 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.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- 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 +1 -1
- 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/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- 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 +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +9 -9
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +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/welcome-screen.d.ts +0 -7
- package/dist/welcome-screen.js +60 -69
- package/package.json +1 -1
- package/packages/daemon/package.json +2 -2
- package/packages/mcp-server/package.json +2 -2
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/assistant-message-design.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/assistant-message-design.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/assistant-message-design.test.js +47 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/assistant-message-design.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +76 -9
- 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/__tests__/user-message-design.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/user-message-design.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/user-message-design.test.js +40 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/user-message-design.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.d.ts +0 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js +30 -29
- package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.test.js +10 -3
- package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.test.js.map +1 -1
- 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 +13 -13
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts +1 -3
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js +58 -3
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/diff.d.ts +2 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/diff.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/diff.js +12 -6
- package/packages/pi-coding-agent/dist/modes/interactive/components/diff.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +14 -41
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +0 -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 +86 -82
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/transcript-design.d.ts +35 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/transcript-design.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/transcript-design.js +152 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/transcript-design.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tui-style-kit.d.ts +16 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tui-style-kit.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tui-style-kit.js +73 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tui-style-kit.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js +12 -8
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-highlight.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-highlight.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-highlight.test.js +17 -0
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-highlight.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js +105 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +27 -26
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js +9 -6
- package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/assistant-message-design.test.ts +56 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +113 -9
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/user-message-design.test.ts +48 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/adaptive-layout.test.ts +10 -3
- package/packages/pi-coding-agent/src/modes/interactive/components/adaptive-layout.ts +43 -42
- package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +14 -14
- package/packages/pi-coding-agent/src/modes/interactive/components/bash-execution.ts +64 -3
- package/packages/pi-coding-agent/src/modes/interactive/components/diff.ts +13 -7
- package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +15 -42
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +84 -104
- package/packages/pi-coding-agent/src/modes/interactive/components/transcript-design.ts +196 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/tui-style-kit.ts +94 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/user-message.ts +14 -9
- package/packages/pi-coding-agent/src/modes/interactive/theme/theme-highlight.test.ts +23 -0
- package/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts +106 -1
- package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +27 -26
- package/packages/pi-coding-agent/src/modes/interactive/tui-mode.test.ts +9 -6
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-tui/dist/__tests__/overlay-layout.test.js +14 -1
- package/packages/pi-tui/dist/__tests__/overlay-layout.test.js.map +1 -1
- package/packages/pi-tui/dist/overlay-layout.d.ts.map +1 -1
- package/packages/pi-tui/dist/overlay-layout.js +9 -6
- package/packages/pi-tui/dist/overlay-layout.js.map +1 -1
- package/packages/pi-tui/package.json +1 -1
- package/packages/pi-tui/src/__tests__/overlay-layout.test.ts +20 -1
- package/packages/pi-tui/src/overlay-layout.ts +10 -7
- package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
- package/packages/rpc-client/package.json +1 -1
- package/pkg/dist/modes/interactive/theme/theme-highlight.test.d.ts +2 -0
- package/pkg/dist/modes/interactive/theme/theme-highlight.test.d.ts.map +1 -0
- package/pkg/dist/modes/interactive/theme/theme-highlight.test.js +17 -0
- package/pkg/dist/modes/interactive/theme/theme-highlight.test.js.map +1 -0
- package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/pkg/dist/modes/interactive/theme/theme.js +105 -1
- package/pkg/dist/modes/interactive/theme/theme.js.map +1 -1
- package/pkg/dist/modes/interactive/theme/themes.d.ts.map +1 -1
- package/pkg/dist/modes/interactive/theme/themes.js +27 -26
- package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto/loop-deps.ts +9 -5
- package/src/resources/extensions/gsd/auto/loop.ts +113 -9
- package/src/resources/extensions/gsd/auto/phases.ts +144 -19
- package/src/resources/extensions/gsd/auto/run-unit.ts +69 -4
- package/src/resources/extensions/gsd/auto/session.ts +10 -0
- package/src/resources/extensions/gsd/auto/verification-retry-policy.ts +82 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +230 -183
- package/src/resources/extensions/gsd/auto-dispatch.ts +15 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +7 -1
- package/src/resources/extensions/gsd/auto-recovery.ts +7 -209
- package/src/resources/extensions/gsd/auto-runtime-state.ts +5 -0
- package/src/resources/extensions/gsd/auto-start.ts +22 -22
- package/src/resources/extensions/gsd/auto-unit-closeout.ts +51 -0
- package/src/resources/extensions/gsd/auto-verification.ts +12 -6
- package/src/resources/extensions/gsd/auto-worktree.ts +8 -0
- package/src/resources/extensions/gsd/auto.ts +295 -75
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +21 -6
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +6 -2
- package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +5 -8
- package/src/resources/extensions/gsd/commands/handlers/notifications-handler.ts +4 -10
- package/src/resources/extensions/gsd/commands/handlers/parallel.ts +12 -0
- package/src/resources/extensions/gsd/git-service.ts +2 -0
- package/src/resources/extensions/gsd/gsd-db.ts +7 -23
- package/src/resources/extensions/gsd/health-widget-core.ts +1 -1
- package/src/resources/extensions/gsd/health-widget.ts +6 -10
- package/src/resources/extensions/gsd/journal.ts +2 -0
- package/src/resources/extensions/gsd/markdown-renderer.ts +4 -95
- package/src/resources/extensions/gsd/native-git-bridge.ts +14 -13
- package/src/resources/extensions/gsd/notification-overlay.ts +50 -46
- package/src/resources/extensions/gsd/parallel-merge.ts +61 -34
- package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +33 -35
- package/src/resources/extensions/gsd/prompts/complete-slice.md +14 -12
- package/src/resources/extensions/gsd/prompts/discuss-headless.md +20 -2
- package/src/resources/extensions/gsd/prompts/discuss.md +20 -2
- package/src/resources/extensions/gsd/recovery-classification.ts +18 -1
- package/src/resources/extensions/gsd/session-lock.ts +41 -0
- package/src/resources/extensions/gsd/state-reconciliation/drift/completion.ts +172 -0
- package/src/resources/extensions/gsd/state-reconciliation/drift/merge-state.ts +337 -0
- package/src/resources/extensions/gsd/state-reconciliation/drift/project-md.ts +69 -0
- package/src/resources/extensions/gsd/state-reconciliation/drift/roadmap.ts +109 -0
- package/src/resources/extensions/gsd/state-reconciliation/drift/sketch-flag.ts +68 -0
- package/src/resources/extensions/gsd/state-reconciliation/drift/stale-render.ts +185 -0
- package/src/resources/extensions/gsd/state-reconciliation/drift/stale-worker.ts +46 -0
- package/src/resources/extensions/gsd/state-reconciliation/errors.ts +67 -0
- package/src/resources/extensions/gsd/state-reconciliation/index.ts +142 -0
- package/src/resources/extensions/gsd/state-reconciliation/registry.ts +27 -0
- package/src/resources/extensions/gsd/state-reconciliation/spawn-gate.ts +60 -0
- package/src/resources/extensions/gsd/state-reconciliation/types.ts +83 -0
- package/src/resources/extensions/gsd/state-reconciliation.ts +21 -53
- package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +654 -176
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +291 -4
- package/src/resources/extensions/gsd/tests/auto-runtime-state.test.ts +16 -1
- package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +18 -0
- package/src/resources/extensions/gsd/tests/auto-unit-closeout.test.ts +68 -0
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +28 -1
- package/src/resources/extensions/gsd/tests/deep-project-auto-loop.test.ts +20 -2
- package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/header-renderer.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/headless-milestone-parity.test.ts +10 -0
- package/src/resources/extensions/gsd/tests/health-widget.test.ts +14 -4
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +26 -0
- package/src/resources/extensions/gsd/tests/integration/integration-proof.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/integration/parallel-merge.test.ts +116 -24
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +46 -11
- package/src/resources/extensions/gsd/tests/notification-overlay.test.ts +78 -41
- package/src/resources/extensions/gsd/tests/notifications-handler.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +12 -217
- package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +38 -6
- package/src/resources/extensions/gsd/tests/post-exec-retry-bypass.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/progressive-planning.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +24 -1
- package/src/resources/extensions/gsd/tests/resume-dispatch-worktree.test.ts +7 -3
- package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +6 -3
- package/src/resources/extensions/gsd/tests/session-switch-abort-misclassification.test.ts +24 -0
- package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +65 -58
- package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +952 -0
- package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/tui-header-lifecycle.test.ts +121 -1
- package/src/resources/extensions/gsd/tests/tui-render-kit.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/verification-retry-policy.test.ts +83 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +6 -0
- package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +158 -58
- package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +572 -118
- package/src/resources/extensions/gsd/tests/worktree-telemetry.test.ts +59 -2
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +18 -0
- package/src/resources/extensions/gsd/tui/render-kit.ts +109 -0
- package/src/resources/extensions/gsd/watch/header-renderer.ts +121 -79
- package/src/resources/extensions/gsd/watch/splash-palette.ts +11 -0
- package/src/resources/extensions/gsd/workflow-mcp.ts +2 -2
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +1151 -524
- package/src/resources/extensions/gsd/worktree-telemetry.ts +7 -2
- package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +0 -1544
- /package/dist/web/standalone/.next/static/{drLMkgfHQ8lzS229_HWYR → S44UQTFCUdA44dkjfYt6S}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{drLMkgfHQ8lzS229_HWYR → S44UQTFCUdA44dkjfYt6S}/_ssgManifest.js +0 -0
|
@@ -31,6 +31,7 @@ import { withTimeout, FINALIZE_PRE_TIMEOUT_MS, FINALIZE_POST_TIMEOUT_MS } from "
|
|
|
31
31
|
import { getEligibleSlices } from "../slice-parallel-eligibility.js";
|
|
32
32
|
import { startSliceParallel } from "../slice-parallel-orchestrator.js";
|
|
33
33
|
import { isDbAvailable, getMilestoneSlices } from "../gsd-db.js";
|
|
34
|
+
import { reconcileBeforeSpawn } from "../state-reconciliation.js";
|
|
34
35
|
import { ensurePlanV2Graph, isEmptyPlanV2GraphResult, isMissingFinalizedContextResult } from "../uok/plan-v2.js";
|
|
35
36
|
import { resolveUokFlags } from "../uok/flags.js";
|
|
36
37
|
import { UokGateRunner } from "../uok/gate-runner.js";
|
|
@@ -41,11 +42,53 @@ import { resolveSafetyHarnessConfig } from "../safety/safety-harness.js";
|
|
|
41
42
|
import { getWorkflowTransportSupportError, getRequiredWorkflowToolsForAutoUnit, supportsStructuredQuestions, } from "../workflow-mcp.js";
|
|
42
43
|
import { resolveManifest } from "../unit-context-manifest.js";
|
|
43
44
|
import { createWorktreeSafetyModule } from "../worktree-safety.js";
|
|
45
|
+
import { isSuspiciousGhostCompletion } from "../auto-unit-closeout.js";
|
|
46
|
+
import { decideVerificationRetry, verificationRetryKey } from "./verification-retry-policy.js";
|
|
44
47
|
// ─── Path Comparison Helper ───────────────────────────────────────────────
|
|
45
48
|
/** Compare two paths for physical identity, tolerating trailing slashes and symlinks. */
|
|
46
49
|
function isSamePathLocal(a, b) {
|
|
47
50
|
return normalizeWorktreePathForCompare(a) === normalizeWorktreePathForCompare(b);
|
|
48
51
|
}
|
|
52
|
+
async function applyVerificationRetryPolicy(ic, unitType, phase) {
|
|
53
|
+
const { ctx, pi, s, deps } = ic;
|
|
54
|
+
const retryInfo = s.pendingVerificationRetry;
|
|
55
|
+
const key = unitType && retryInfo
|
|
56
|
+
? verificationRetryKey(unitType, retryInfo.unitId)
|
|
57
|
+
: undefined;
|
|
58
|
+
const decision = decideVerificationRetry({
|
|
59
|
+
unitType,
|
|
60
|
+
retryInfo,
|
|
61
|
+
previousFailureHash: key ? s.verificationRetryFailureHashes.get(key) : undefined,
|
|
62
|
+
});
|
|
63
|
+
if (decision.action === "pause") {
|
|
64
|
+
s.pendingVerificationRetry = null;
|
|
65
|
+
debugLog("autoLoop", {
|
|
66
|
+
phase: `${phase}-paused`,
|
|
67
|
+
reason: decision.reason,
|
|
68
|
+
unitType,
|
|
69
|
+
unitId: retryInfo?.unitId,
|
|
70
|
+
failureHash: decision.failureHash,
|
|
71
|
+
});
|
|
72
|
+
ctx.ui.notify(decision.reason === "duplicate-failure-context"
|
|
73
|
+
? `Verification retry for ${unitType ?? "unit"} ${retryInfo?.unitId ?? "unknown"} produced the same failure context. Pausing auto-mode instead of re-dispatching.`
|
|
74
|
+
: "Verification retry requested without retry context. Pausing auto-mode instead of re-dispatching.", "warning");
|
|
75
|
+
await deps.pauseAuto(ctx, pi);
|
|
76
|
+
return { action: "break", reason: decision.reason };
|
|
77
|
+
}
|
|
78
|
+
s.verificationRetryFailureHashes.set(decision.key, decision.failureHash);
|
|
79
|
+
debugLog("autoLoop", {
|
|
80
|
+
phase: `${phase}-backoff`,
|
|
81
|
+
iteration: ic.iteration,
|
|
82
|
+
unitType,
|
|
83
|
+
unitId: retryInfo?.unitId,
|
|
84
|
+
attempt: retryInfo?.attempt,
|
|
85
|
+
delayMs: decision.delayMs,
|
|
86
|
+
baseDelayMs: decision.baseDelayMs,
|
|
87
|
+
failureHash: decision.failureHash,
|
|
88
|
+
});
|
|
89
|
+
await new Promise((resolve) => setTimeout(resolve, decision.delayMs));
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
49
92
|
export function shouldDegradeEmptyWorktreeToProjectRoot(worktreeClassification, projectRootClassification) {
|
|
50
93
|
return (worktreeClassification.kind === "greenfield" &&
|
|
51
94
|
projectRootClassification.kind !== "greenfield" &&
|
|
@@ -605,6 +648,13 @@ export async function runPreDispatch(ic, loopState) {
|
|
|
605
648
|
eligibleSlices: eligible.map(e => e.id),
|
|
606
649
|
});
|
|
607
650
|
ctx.ui.notify(`Slice-parallel: dispatching ${eligible.length} eligible slices for ${mid}.`, "info");
|
|
651
|
+
// ADR-017 #5707: reconcile before spawning so each worker doesn't
|
|
652
|
+
// independently race on the same drift. Failure aborts the spawn.
|
|
653
|
+
const spawnGate = await reconcileBeforeSpawn(s.basePath);
|
|
654
|
+
if (!spawnGate.ok) {
|
|
655
|
+
ctx.ui.notify(`Slice-parallel: aborting spawn — ${spawnGate.reason}`, "error");
|
|
656
|
+
return { action: "break", reason: `slice-parallel-reconciliation-failed: ${spawnGate.reason}` };
|
|
657
|
+
}
|
|
608
658
|
const result = await startSliceParallel(s.basePath, mid, eligible, {
|
|
609
659
|
maxWorkers: prefs.slice_parallel.max_workers ?? 2,
|
|
610
660
|
useExecutionGraph: uokFlags.executionGraph,
|
|
@@ -726,7 +776,13 @@ export async function runPreDispatch(ic, loopState) {
|
|
|
726
776
|
}
|
|
727
777
|
deps.sendDesktopNotification("GSD", "All milestones complete!", "success", "milestone", basename(s.originalBasePath || s.basePath));
|
|
728
778
|
deps.logCmuxEvent(prefs, "All milestones complete.", "success");
|
|
729
|
-
await deps.stopAuto(ctx, pi, "All milestones complete"
|
|
779
|
+
await deps.stopAuto(ctx, pi, "All milestones complete", {
|
|
780
|
+
completionWidget: {
|
|
781
|
+
milestoneId: s.currentMilestoneId,
|
|
782
|
+
milestoneTitle: midTitle,
|
|
783
|
+
allMilestonesComplete: true,
|
|
784
|
+
},
|
|
785
|
+
});
|
|
730
786
|
}
|
|
731
787
|
else if (incomplete.length === 0 && state.registry.length === 0) {
|
|
732
788
|
// Empty registry — no milestones visible, likely a path resolution bug
|
|
@@ -794,7 +850,16 @@ export async function runPreDispatch(ic, loopState) {
|
|
|
794
850
|
}
|
|
795
851
|
deps.sendDesktopNotification("GSD", `Milestone ${mid} complete!`, "success", "milestone", basename(s.originalBasePath || s.basePath));
|
|
796
852
|
deps.logCmuxEvent(prefs, `Milestone ${mid} complete.`, "success");
|
|
797
|
-
|
|
853
|
+
if (s.currentUnit) {
|
|
854
|
+
await deps.closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, deps.buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
|
|
855
|
+
s.currentUnit = null;
|
|
856
|
+
}
|
|
857
|
+
await deps.stopAuto(ctx, pi, `Milestone ${mid} complete`, {
|
|
858
|
+
completionWidget: {
|
|
859
|
+
milestoneId: mid,
|
|
860
|
+
milestoneTitle: midTitle,
|
|
861
|
+
},
|
|
862
|
+
});
|
|
798
863
|
debugLog("autoLoop", { phase: "exit", reason: "milestone-complete" });
|
|
799
864
|
deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: ic.nextSeq(), eventType: "terminal", data: { reason: "milestone-complete", milestoneId: mid } });
|
|
800
865
|
return { action: "break", reason: "milestone-complete" };
|
|
@@ -914,118 +979,115 @@ export async function runDispatch(ic, preData, loopState) {
|
|
|
914
979
|
return worktreeSafetyBlock;
|
|
915
980
|
// ── Sliding-window stuck detection with graduated recovery ──
|
|
916
981
|
const derivedKey = `${unitType}/${unitId}`;
|
|
917
|
-
// Always record this dispatch in the sliding window
|
|
918
|
-
//
|
|
919
|
-
//
|
|
920
|
-
// (#2007). Only the *response* to a stuck signal is suppressed during retries.
|
|
982
|
+
// Always record this dispatch in the sliding window and run detection so
|
|
983
|
+
// Rules 1/3/4 can catch retry loops with repeated failure content (#5719).
|
|
984
|
+
// Rules 2/2b suppress legitimate retry backoff through the dispatch ledger.
|
|
921
985
|
loopState.recentUnits.push({ key: derivedKey });
|
|
922
986
|
if (loopState.recentUnits.length > STUCK_WINDOW_SIZE)
|
|
923
987
|
loopState.recentUnits.shift();
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
if (
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
return { action: "break", reason: "complete-milestone-artifact-db-mismatch" };
|
|
950
|
-
}
|
|
951
|
-
debugLog("autoLoop", {
|
|
952
|
-
phase: "stuck-recovery",
|
|
953
|
-
level: 1,
|
|
954
|
-
action: "artifact-found",
|
|
955
|
-
});
|
|
956
|
-
const recoveryDb = refreshRecoveryDbForArtifact(unitType, unitId);
|
|
957
|
-
if (!recoveryDb.ok) {
|
|
958
|
-
ctx.ui.notify(recoveryDb.fatal
|
|
959
|
-
? `${recoveryDb.message} Pausing auto-mode for manual recovery.`
|
|
960
|
-
: `${recoveryDb.message} Keeping stuck state for retry.`, "warning");
|
|
961
|
-
if (recoveryDb.fatal) {
|
|
962
|
-
await deps.pauseAuto(ctx, pi);
|
|
963
|
-
return { action: "break", reason: recoveryDb.reason };
|
|
964
|
-
}
|
|
965
|
-
return { action: "continue" };
|
|
966
|
-
}
|
|
967
|
-
ctx.ui.notify(`Stuck recovery: artifact for ${unitType} ${unitId} found on disk. Invalidating caches.`, "info");
|
|
968
|
-
deps.invalidateAllCaches();
|
|
969
|
-
loopState.recentUnits.length = 0;
|
|
970
|
-
loopState.stuckRecoveryAttempts = 0;
|
|
971
|
-
return { action: "continue" };
|
|
988
|
+
const stuckSignal = detectStuck(loopState.recentUnits);
|
|
989
|
+
if (stuckSignal) {
|
|
990
|
+
debugLog("autoLoop", {
|
|
991
|
+
phase: "stuck-check",
|
|
992
|
+
unitType,
|
|
993
|
+
unitId,
|
|
994
|
+
reason: stuckSignal.reason,
|
|
995
|
+
recoveryAttempts: loopState.stuckRecoveryAttempts,
|
|
996
|
+
});
|
|
997
|
+
if (loopState.stuckRecoveryAttempts === 0) {
|
|
998
|
+
// Level 1: try verifying the artifact, then cache invalidation + retry
|
|
999
|
+
loopState.stuckRecoveryAttempts++;
|
|
1000
|
+
const artifactExists = verifyExpectedArtifact(unitType, unitId, s.basePath);
|
|
1001
|
+
if (artifactExists) {
|
|
1002
|
+
if (unitType === "complete-milestone") {
|
|
1003
|
+
const stuckDiag = diagnoseExpectedArtifact(unitType, unitId, s.basePath);
|
|
1004
|
+
const stuckParts = [
|
|
1005
|
+
`Detected ${unitType} ${unitId} output on disk, but the same unit is still being derived.`,
|
|
1006
|
+
"This usually means the milestone summary exists while the DB row still does not mark the milestone complete.",
|
|
1007
|
+
];
|
|
1008
|
+
if (stuckDiag)
|
|
1009
|
+
stuckParts.push(`Expected: ${stuckDiag}`);
|
|
1010
|
+
ctx.ui.notify(stuckParts.join(" "), "warning");
|
|
1011
|
+
await deps.pauseAuto(ctx, pi);
|
|
1012
|
+
return { action: "break", reason: "complete-milestone-artifact-db-mismatch" };
|
|
972
1013
|
}
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
if (artifactExists && unitType !== "complete-milestone") {
|
|
981
|
-
debugLog("autoLoop", {
|
|
982
|
-
phase: "stuck-recovery",
|
|
983
|
-
level: 2,
|
|
984
|
-
action: "artifact-found",
|
|
985
|
-
});
|
|
986
|
-
const recoveryDb = refreshRecoveryDbForArtifact(unitType, unitId);
|
|
987
|
-
if (recoveryDb.ok) {
|
|
988
|
-
ctx.ui.notify(`Stuck recovery: artifact for ${unitType} ${unitId} found on disk after cache invalidation. Continuing.`, "info");
|
|
989
|
-
loopState.recentUnits.length = 0;
|
|
990
|
-
loopState.stuckRecoveryAttempts = 0;
|
|
991
|
-
return { action: "continue" };
|
|
992
|
-
}
|
|
1014
|
+
debugLog("autoLoop", {
|
|
1015
|
+
phase: "stuck-recovery",
|
|
1016
|
+
level: 1,
|
|
1017
|
+
action: "artifact-found",
|
|
1018
|
+
});
|
|
1019
|
+
const recoveryDb = refreshRecoveryDbForArtifact(unitType, unitId);
|
|
1020
|
+
if (!recoveryDb.ok) {
|
|
993
1021
|
ctx.ui.notify(recoveryDb.fatal
|
|
994
1022
|
? `${recoveryDb.message} Pausing auto-mode for manual recovery.`
|
|
995
|
-
: `${recoveryDb.message}
|
|
1023
|
+
: `${recoveryDb.message} Keeping stuck state for retry.`, "warning");
|
|
996
1024
|
if (recoveryDb.fatal) {
|
|
997
1025
|
await deps.pauseAuto(ctx, pi);
|
|
998
1026
|
return { action: "break", reason: recoveryDb.reason };
|
|
999
1027
|
}
|
|
1028
|
+
return { action: "continue" };
|
|
1000
1029
|
}
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
});
|
|
1007
|
-
const stuckDiag = diagnoseExpectedArtifact(unitType, unitId, s.basePath);
|
|
1008
|
-
const stuckRemediation = buildLoopRemediationSteps(unitType, unitId, s.basePath);
|
|
1009
|
-
const stuckParts = [`Stuck on ${unitType} ${unitId} — ${stuckSignal.reason}.`];
|
|
1010
|
-
if (stuckDiag)
|
|
1011
|
-
stuckParts.push(`Expected: ${stuckDiag}`);
|
|
1012
|
-
if (stuckRemediation)
|
|
1013
|
-
stuckParts.push(`To recover:\n${stuckRemediation}`);
|
|
1014
|
-
ctx.ui.notify(stuckParts.join(" "), "error");
|
|
1015
|
-
await deps.stopAuto(ctx, pi, `Stuck: ${stuckSignal.reason}`);
|
|
1016
|
-
return { action: "break", reason: "stuck-detected" };
|
|
1030
|
+
ctx.ui.notify(`Stuck recovery: artifact for ${unitType} ${unitId} found on disk. Invalidating caches.`, "info");
|
|
1031
|
+
deps.invalidateAllCaches();
|
|
1032
|
+
loopState.recentUnits.length = 0;
|
|
1033
|
+
loopState.stuckRecoveryAttempts = 0;
|
|
1034
|
+
return { action: "continue" };
|
|
1017
1035
|
}
|
|
1036
|
+
ctx.ui.notify(`Stuck on ${unitType} ${unitId} (${stuckSignal.reason}). Invalidating caches and retrying.`, "warning");
|
|
1037
|
+
deps.invalidateAllCaches();
|
|
1018
1038
|
}
|
|
1019
1039
|
else {
|
|
1020
|
-
//
|
|
1021
|
-
|
|
1040
|
+
// Level 2: hard stop — genuinely stuck
|
|
1041
|
+
deps.invalidateAllCaches();
|
|
1042
|
+
const artifactExists = verifyExpectedArtifact(unitType, unitId, s.basePath);
|
|
1043
|
+
if (artifactExists && unitType !== "complete-milestone") {
|
|
1022
1044
|
debugLog("autoLoop", {
|
|
1023
|
-
phase: "stuck-
|
|
1024
|
-
|
|
1025
|
-
|
|
1045
|
+
phase: "stuck-recovery",
|
|
1046
|
+
level: 2,
|
|
1047
|
+
action: "artifact-found",
|
|
1026
1048
|
});
|
|
1027
|
-
|
|
1049
|
+
const recoveryDb = refreshRecoveryDbForArtifact(unitType, unitId);
|
|
1050
|
+
if (recoveryDb.ok) {
|
|
1051
|
+
ctx.ui.notify(`Stuck recovery: artifact for ${unitType} ${unitId} found on disk after cache invalidation. Continuing.`, "info");
|
|
1052
|
+
loopState.recentUnits.length = 0;
|
|
1053
|
+
loopState.stuckRecoveryAttempts = 0;
|
|
1054
|
+
return { action: "continue" };
|
|
1055
|
+
}
|
|
1056
|
+
ctx.ui.notify(recoveryDb.fatal
|
|
1057
|
+
? `${recoveryDb.message} Pausing auto-mode for manual recovery.`
|
|
1058
|
+
: `${recoveryDb.message} Stopping for manual recovery.`, "warning");
|
|
1059
|
+
if (recoveryDb.fatal) {
|
|
1060
|
+
await deps.pauseAuto(ctx, pi);
|
|
1061
|
+
return { action: "break", reason: recoveryDb.reason };
|
|
1062
|
+
}
|
|
1028
1063
|
}
|
|
1064
|
+
debugLog("autoLoop", {
|
|
1065
|
+
phase: "stuck-detected",
|
|
1066
|
+
unitType,
|
|
1067
|
+
unitId,
|
|
1068
|
+
reason: stuckSignal.reason,
|
|
1069
|
+
});
|
|
1070
|
+
const stuckDiag = diagnoseExpectedArtifact(unitType, unitId, s.basePath);
|
|
1071
|
+
const stuckRemediation = buildLoopRemediationSteps(unitType, unitId, s.basePath);
|
|
1072
|
+
const stuckParts = [`Stuck on ${unitType} ${unitId} — ${stuckSignal.reason}.`];
|
|
1073
|
+
if (stuckDiag)
|
|
1074
|
+
stuckParts.push(`Expected: ${stuckDiag}`);
|
|
1075
|
+
if (stuckRemediation)
|
|
1076
|
+
stuckParts.push(`To recover:\n${stuckRemediation}`);
|
|
1077
|
+
ctx.ui.notify(stuckParts.join(" "), "error");
|
|
1078
|
+
await deps.stopAuto(ctx, pi, `Stuck: ${stuckSignal.reason}`);
|
|
1079
|
+
return { action: "break", reason: "stuck-detected" };
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
else {
|
|
1083
|
+
// Progress detected — reset recovery counter
|
|
1084
|
+
if (loopState.stuckRecoveryAttempts > 0) {
|
|
1085
|
+
debugLog("autoLoop", {
|
|
1086
|
+
phase: "stuck-counter-reset",
|
|
1087
|
+
from: loopState.recentUnits[loopState.recentUnits.length - 2]?.key ?? "",
|
|
1088
|
+
to: derivedKey,
|
|
1089
|
+
});
|
|
1090
|
+
loopState.stuckRecoveryAttempts = 0;
|
|
1029
1091
|
}
|
|
1030
1092
|
}
|
|
1031
1093
|
return {
|
|
@@ -1472,6 +1534,29 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
1472
1534
|
unitId,
|
|
1473
1535
|
status: unitResult.status,
|
|
1474
1536
|
});
|
|
1537
|
+
if (unitResult.status === "completed" &&
|
|
1538
|
+
s.currentUnit &&
|
|
1539
|
+
(unitResult.event?.messages?.length ?? 0) === 0 &&
|
|
1540
|
+
isSuspiciousGhostCompletion(ctx, unitResult.requestDispatchedAt ?? s.currentUnit.startedAt)) {
|
|
1541
|
+
const message = `${unitType} ${unitId} completed without assistant output or tool calls; treating as a stale ghost completion.`;
|
|
1542
|
+
debugLog("autoLoop", {
|
|
1543
|
+
phase: "ghost-completion",
|
|
1544
|
+
iteration: ic.iteration,
|
|
1545
|
+
unitType,
|
|
1546
|
+
unitId,
|
|
1547
|
+
elapsedMs: Date.now() - (unitResult.requestDispatchedAt ?? s.currentUnit.startedAt),
|
|
1548
|
+
});
|
|
1549
|
+
logWarning("engine", message);
|
|
1550
|
+
ctx.ui.notify(`${message} Pausing auto-mode before closeout side effects.`, "warning");
|
|
1551
|
+
await emitCancelledUnitEnd(ic, unitType, unitId, unitStartSeq, {
|
|
1552
|
+
message,
|
|
1553
|
+
category: "unknown",
|
|
1554
|
+
isTransient: true,
|
|
1555
|
+
});
|
|
1556
|
+
s.currentUnit = null;
|
|
1557
|
+
await deps.pauseAuto(ctx, pi);
|
|
1558
|
+
return { action: "break", reason: "ghost-completion" };
|
|
1559
|
+
}
|
|
1475
1560
|
// Now that runUnit has called newSession(), the session file path is correct.
|
|
1476
1561
|
const sessionFile = deps.getSessionFile(ctx);
|
|
1477
1562
|
deps.updateSessionLock(deps.lockBase(), unitType, unitId, sessionFile);
|
|
@@ -1753,6 +1838,10 @@ export async function runFinalize(ic, iterData, loopState, sidecarItem) {
|
|
|
1753
1838
|
attempt: retryInfo?.attempt,
|
|
1754
1839
|
},
|
|
1755
1840
|
});
|
|
1841
|
+
const retryPolicyResult = await applyVerificationRetryPolicy(ic, preUnitSnapshot?.type, "artifact-verification-retry");
|
|
1842
|
+
if (retryPolicyResult) {
|
|
1843
|
+
return retryPolicyResult;
|
|
1844
|
+
}
|
|
1756
1845
|
// Continue the loop — next iteration will inject the retry context into the prompt.
|
|
1757
1846
|
debugLog("autoLoop", { phase: "artifact-verification-retry", iteration: ic.iteration });
|
|
1758
1847
|
return { action: "continue" };
|
|
@@ -1781,6 +1870,10 @@ export async function runFinalize(ic, iterData, loopState, sidecarItem) {
|
|
|
1781
1870
|
}
|
|
1782
1871
|
else {
|
|
1783
1872
|
// s.pendingVerificationRetry was set by runPostUnitVerification.
|
|
1873
|
+
const retryPolicyResult = await applyVerificationRetryPolicy(ic, iterData.unitType, "verification-retry");
|
|
1874
|
+
if (retryPolicyResult) {
|
|
1875
|
+
return retryPolicyResult;
|
|
1876
|
+
}
|
|
1784
1877
|
// Continue the loop — next iteration will inject the retry context into the prompt.
|
|
1785
1878
|
debugLog("autoLoop", { phase: "verification-retry", iteration: ic.iteration });
|
|
1786
1879
|
return { action: "continue" };
|
|
@@ -6,6 +6,31 @@ import { debugLog } from "../debug-logger.js";
|
|
|
6
6
|
import { logWarning } from "../workflow-logger.js";
|
|
7
7
|
import { resolveAutoSupervisorConfig } from "../preferences.js";
|
|
8
8
|
import { formatAutoUnitWorkingMessage } from "../working-output-messages.js";
|
|
9
|
+
import { readUnitRuntimeRecord } from "../unit-runtime.js";
|
|
10
|
+
const UNIT_FAILSAFE_BUFFER_MS = 30_000;
|
|
11
|
+
const UNIT_FAILSAFE_RECHECK_MS = 30_000;
|
|
12
|
+
export function shouldDeferUnitFailsafeTimeout(runtime, opts) {
|
|
13
|
+
if (!runtime)
|
|
14
|
+
return false;
|
|
15
|
+
if (opts.currentUnitStartedAt === undefined)
|
|
16
|
+
return false;
|
|
17
|
+
if (runtime.startedAt !== opts.currentUnitStartedAt)
|
|
18
|
+
return false;
|
|
19
|
+
if (runtime.lastProgressAt <= 0)
|
|
20
|
+
return false;
|
|
21
|
+
const progressAgeMs = opts.nowMs - runtime.lastProgressAt;
|
|
22
|
+
if (progressAgeMs < 0)
|
|
23
|
+
return false;
|
|
24
|
+
if (progressAgeMs > opts.freshProgressMs)
|
|
25
|
+
return false;
|
|
26
|
+
if (runtime.phase === "recovered")
|
|
27
|
+
return true;
|
|
28
|
+
if (runtime.lastProgressKind.includes("recovery"))
|
|
29
|
+
return true;
|
|
30
|
+
if (runtime.recoveryAttempts && runtime.recoveryAttempts > 0)
|
|
31
|
+
return true;
|
|
32
|
+
return progressAgeMs >= 0 && progressAgeMs <= opts.freshProgressMs;
|
|
33
|
+
}
|
|
9
34
|
// Tracks the latest session-switch attempt so a late timeout settlement from an
|
|
10
35
|
// older runUnit() call cannot clear the guard for a newer one.
|
|
11
36
|
let sessionSwitchGeneration = 0;
|
|
@@ -145,16 +170,54 @@ export async function runUnit(ctx, pi, s, unitType, unitId, prompt) {
|
|
|
145
170
|
// If supervision fails to resolve unitPromise within 30s, treat as cancelled.
|
|
146
171
|
// Without this, a crashed agent that never emits agent_end hangs the loop (#3161).
|
|
147
172
|
const supervisor = resolveAutoSupervisorConfig();
|
|
148
|
-
const UNIT_HARD_TIMEOUT_MS = Math.max(
|
|
173
|
+
const UNIT_HARD_TIMEOUT_MS = Math.max(UNIT_FAILSAFE_BUFFER_MS, ((supervisor.hard_timeout_minutes ?? 30) * 60 * 1000) + UNIT_FAILSAFE_BUFFER_MS);
|
|
174
|
+
const freshProgressMs = Math.max(UNIT_FAILSAFE_BUFFER_MS, ((supervisor.idle_timeout_minutes ?? 10) * 60 * 1000) + UNIT_FAILSAFE_BUFFER_MS);
|
|
149
175
|
let unitTimeoutHandle;
|
|
150
176
|
let result;
|
|
151
177
|
try {
|
|
152
178
|
pi.sendMessage({ customType: "gsd-auto", content: prompt, display: s.verbose }, { triggerTurn: true });
|
|
153
179
|
debugLog("runUnit", { phase: "awaiting-agent-end", unitType, unitId });
|
|
154
180
|
const timeoutResult = new Promise((resolve) => {
|
|
155
|
-
|
|
181
|
+
const settleOrDefer = () => {
|
|
182
|
+
let runtime;
|
|
183
|
+
try {
|
|
184
|
+
runtime = readUnitRuntimeRecord(s.basePath, unitType, unitId);
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
debugLog("runUnit", {
|
|
188
|
+
phase: "unit-failsafe-runtime-read-failed",
|
|
189
|
+
unitType,
|
|
190
|
+
unitId,
|
|
191
|
+
error: error instanceof Error ? error.message : String(error),
|
|
192
|
+
});
|
|
193
|
+
resolve({
|
|
194
|
+
status: "cancelled",
|
|
195
|
+
errorContext: {
|
|
196
|
+
message: "Unit hard timeout — supervision may have failed; runtime progress could not be read",
|
|
197
|
+
category: "timeout",
|
|
198
|
+
isTransient: true,
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (shouldDeferUnitFailsafeTimeout(runtime, {
|
|
204
|
+
nowMs: Date.now(),
|
|
205
|
+
currentUnitStartedAt: s.currentUnit?.startedAt,
|
|
206
|
+
freshProgressMs,
|
|
207
|
+
})) {
|
|
208
|
+
debugLog("runUnit", {
|
|
209
|
+
phase: "unit-failsafe-deferred",
|
|
210
|
+
unitType,
|
|
211
|
+
unitId,
|
|
212
|
+
runtimePhase: runtime?.phase,
|
|
213
|
+
lastProgressKind: runtime?.lastProgressKind,
|
|
214
|
+
});
|
|
215
|
+
unitTimeoutHandle = setTimeout(settleOrDefer, UNIT_FAILSAFE_RECHECK_MS);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
156
218
|
resolve({ status: "cancelled", errorContext: { message: "Unit hard timeout — supervision may have failed", category: "timeout", isTransient: true } });
|
|
157
|
-
}
|
|
219
|
+
};
|
|
220
|
+
unitTimeoutHandle = setTimeout(settleOrDefer, UNIT_HARD_TIMEOUT_MS);
|
|
158
221
|
});
|
|
159
222
|
result = await runWithTurnGeneration(capturedTurnGen, () => Promise.race([unitPromise, timeoutResult]));
|
|
160
223
|
}
|
|
@@ -25,6 +25,7 @@ export class AutoSession {
|
|
|
25
25
|
// ── Lifecycle ────────────────────────────────────────────────────────────
|
|
26
26
|
active = false;
|
|
27
27
|
paused = false;
|
|
28
|
+
completionStopInProgress = false;
|
|
28
29
|
stepMode = false;
|
|
29
30
|
verbose = false;
|
|
30
31
|
activeEngineId = null;
|
|
@@ -88,6 +89,7 @@ export class AutoSession {
|
|
|
88
89
|
pendingCrashRecovery = null;
|
|
89
90
|
pendingVerificationRetry = null;
|
|
90
91
|
verificationRetryCount = new Map();
|
|
92
|
+
verificationRetryFailureHashes = new Map();
|
|
91
93
|
pausedSessionFile = null;
|
|
92
94
|
pausedUnitType = null;
|
|
93
95
|
pausedUnitId = null;
|
|
@@ -207,6 +209,7 @@ export class AutoSession {
|
|
|
207
209
|
// Lifecycle
|
|
208
210
|
this.active = false;
|
|
209
211
|
this.paused = false;
|
|
212
|
+
this.completionStopInProgress = false;
|
|
210
213
|
this.stepMode = false;
|
|
211
214
|
this.verbose = false;
|
|
212
215
|
this.activeEngineId = null;
|
|
@@ -250,6 +253,7 @@ export class AutoSession {
|
|
|
250
253
|
this.pendingCrashRecovery = null;
|
|
251
254
|
this.pendingVerificationRetry = null;
|
|
252
255
|
this.verificationRetryCount.clear();
|
|
256
|
+
this.verificationRetryFailureHashes.clear();
|
|
253
257
|
this.pausedSessionFile = null;
|
|
254
258
|
this.pausedUnitType = null;
|
|
255
259
|
this.pausedUnitId = null;
|
|
@@ -282,6 +286,11 @@ export class AutoSession {
|
|
|
282
286
|
this.orchestration = null;
|
|
283
287
|
// Loop promise state lives in auto-loop.ts module scope
|
|
284
288
|
}
|
|
289
|
+
resetAfterStop(options = {}) {
|
|
290
|
+
const completionStopInProgress = options.preserveCompletionSurface ? this.completionStopInProgress : false;
|
|
291
|
+
this.reset();
|
|
292
|
+
this.completionStopInProgress = completionStopInProgress;
|
|
293
|
+
}
|
|
285
294
|
toJSON() {
|
|
286
295
|
const orchestrationStatus = this.orchestration?.getStatus();
|
|
287
296
|
return {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Project/App: GSD-2
|
|
2
|
+
// File Purpose: Central retry policy for auto-mode verification redispatches.
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
export const VERIFICATION_RETRY_BASE_DELAY_MS = 2_000;
|
|
5
|
+
export const VERIFICATION_RETRY_MAX_DELAY_MS = 30_000;
|
|
6
|
+
export const VERIFICATION_RETRY_JITTER_RATIO = 0.1;
|
|
7
|
+
export function verificationRetryKey(unitType, unitId) {
|
|
8
|
+
return `${unitType}:${unitId}`;
|
|
9
|
+
}
|
|
10
|
+
export function hashVerificationFailureContext(failureContext) {
|
|
11
|
+
const normalized = failureContext.replace(/\r\n/g, "\n").trim();
|
|
12
|
+
return createHash("sha256").update(normalized).digest("hex");
|
|
13
|
+
}
|
|
14
|
+
export function verificationRetryDelayMs(attempt, random = Math.random) {
|
|
15
|
+
const safeAttempt = Math.max(1, Math.floor(attempt));
|
|
16
|
+
const baseDelayMs = Math.min(VERIFICATION_RETRY_MAX_DELAY_MS, VERIFICATION_RETRY_BASE_DELAY_MS * 2 ** (safeAttempt - 1));
|
|
17
|
+
const jitterSpanMs = Math.round(baseDelayMs * VERIFICATION_RETRY_JITTER_RATIO);
|
|
18
|
+
const jitterMs = Math.round((random() - 0.5) * 2 * jitterSpanMs);
|
|
19
|
+
const delayMs = Math.min(VERIFICATION_RETRY_MAX_DELAY_MS, Math.max(0, baseDelayMs + jitterMs));
|
|
20
|
+
return { delayMs, baseDelayMs };
|
|
21
|
+
}
|
|
22
|
+
export function decideVerificationRetry(input) {
|
|
23
|
+
const { retryInfo, unitType } = input;
|
|
24
|
+
if (!retryInfo || !unitType) {
|
|
25
|
+
return { action: "pause", reason: "missing-retry-context" };
|
|
26
|
+
}
|
|
27
|
+
const key = verificationRetryKey(unitType, retryInfo.unitId);
|
|
28
|
+
const failureHash = hashVerificationFailureContext(retryInfo.failureContext);
|
|
29
|
+
if (input.previousFailureHash === failureHash) {
|
|
30
|
+
return {
|
|
31
|
+
action: "pause",
|
|
32
|
+
reason: "duplicate-failure-context",
|
|
33
|
+
key,
|
|
34
|
+
failureHash,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
action: "delay",
|
|
39
|
+
key,
|
|
40
|
+
failureHash,
|
|
41
|
+
...verificationRetryDelayMs(retryInfo.attempt, input.random),
|
|
42
|
+
};
|
|
43
|
+
}
|