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
|
@@ -399,6 +399,12 @@ export async function autoLoop(
|
|
|
399
399
|
|
|
400
400
|
let dispatchId: number | null = null;
|
|
401
401
|
let dispatchSettled = false;
|
|
402
|
+
let iterationEndEmitted = false;
|
|
403
|
+
const emitIterationEnd = (details: Record<string, unknown> = {}): void => {
|
|
404
|
+
if (iterationEndEmitted) return;
|
|
405
|
+
iterationEndEmitted = true;
|
|
406
|
+
journalReporter.emit("iteration-end", { iteration, ...details });
|
|
407
|
+
};
|
|
402
408
|
const completeIteration = (): void => {
|
|
403
409
|
completeWorkflowIteration({
|
|
404
410
|
get consecutiveErrors() { return consecutiveErrors; },
|
|
@@ -407,11 +413,15 @@ export async function autoLoop(
|
|
|
407
413
|
set consecutiveCooldowns(value) { consecutiveCooldowns = value; },
|
|
408
414
|
recentErrorMessages,
|
|
409
415
|
}, {
|
|
410
|
-
emitIterationEnd: () =>
|
|
416
|
+
emitIterationEnd: () => emitIterationEnd(),
|
|
411
417
|
saveStuckState: () => saveStuckState(s, loopState),
|
|
412
418
|
logIterationComplete: () => debugLog("autoLoop", { phase: "iteration-complete", iteration }),
|
|
413
419
|
});
|
|
414
420
|
};
|
|
421
|
+
const finishIncompleteIteration = (details: Record<string, unknown>): void => {
|
|
422
|
+
emitIterationEnd(details);
|
|
423
|
+
saveStuckState(s, loopState);
|
|
424
|
+
};
|
|
415
425
|
|
|
416
426
|
try {
|
|
417
427
|
// ── Blanket try/catch: one bad iteration must not kill the session
|
|
@@ -492,6 +502,7 @@ export async function autoLoop(
|
|
|
492
502
|
});
|
|
493
503
|
if (engineState.isComplete) {
|
|
494
504
|
finishTurn("completed");
|
|
505
|
+
emitIterationEnd({ status: "completed", reason: "custom-engine-complete" });
|
|
495
506
|
await deps.stopAuto(ctx, pi, "Workflow complete");
|
|
496
507
|
break;
|
|
497
508
|
}
|
|
@@ -509,16 +520,23 @@ export async function autoLoop(
|
|
|
509
520
|
});
|
|
510
521
|
if (dispatchFlow.action === "break") {
|
|
511
522
|
finishTurn("stopped", "manual-attention", "custom-engine-dispatch-stop");
|
|
523
|
+
finishIncompleteIteration({
|
|
524
|
+
status: "stopped",
|
|
525
|
+
reason: "custom-engine-dispatch-stop",
|
|
526
|
+
failureClass: "manual-attention",
|
|
527
|
+
});
|
|
512
528
|
break;
|
|
513
529
|
}
|
|
514
530
|
if (dispatchFlow.action === "continue") {
|
|
515
531
|
finishTurn("skipped");
|
|
532
|
+
emitIterationEnd({ status: "skipped", reason: "custom-engine-dispatch-skip" });
|
|
516
533
|
continue;
|
|
517
534
|
}
|
|
518
535
|
|
|
519
536
|
// dispatch.action === "dispatch"
|
|
520
537
|
if (dispatch.action !== "dispatch") {
|
|
521
538
|
finishTurn("skipped");
|
|
539
|
+
emitIterationEnd({ status: "skipped", reason: "custom-engine-dispatch-mismatch" });
|
|
522
540
|
continue;
|
|
523
541
|
}
|
|
524
542
|
const step = dispatch.step;
|
|
@@ -547,6 +565,13 @@ export async function autoLoop(
|
|
|
547
565
|
});
|
|
548
566
|
if (guardsResult.action === "break") {
|
|
549
567
|
finishTurn("stopped", "manual-attention", "guard-break");
|
|
568
|
+
finishIncompleteIteration({
|
|
569
|
+
status: "stopped",
|
|
570
|
+
reason: "guard-break",
|
|
571
|
+
unitType: iterData.unitType,
|
|
572
|
+
unitId: iterData.unitId,
|
|
573
|
+
failureClass: "manual-attention",
|
|
574
|
+
});
|
|
550
575
|
break;
|
|
551
576
|
}
|
|
552
577
|
|
|
@@ -578,6 +603,13 @@ export async function autoLoop(
|
|
|
578
603
|
unitId: iterData.unitId,
|
|
579
604
|
});
|
|
580
605
|
if (unitPhaseResult.action === "break") {
|
|
606
|
+
finishIncompleteIteration({
|
|
607
|
+
status: "stopped",
|
|
608
|
+
reason: unitPhaseResult.reason ?? "unit-break",
|
|
609
|
+
unitType: iterData.unitType,
|
|
610
|
+
unitId: iterData.unitId,
|
|
611
|
+
failureClass: "execution",
|
|
612
|
+
});
|
|
581
613
|
finishTurn("stopped", "execution", "unit-break");
|
|
582
614
|
break;
|
|
583
615
|
}
|
|
@@ -596,7 +628,16 @@ export async function autoLoop(
|
|
|
596
628
|
finishTurn,
|
|
597
629
|
},
|
|
598
630
|
});
|
|
599
|
-
if (verifyFlow.action === "break")
|
|
631
|
+
if (verifyFlow.action === "break") {
|
|
632
|
+
finishIncompleteIteration({
|
|
633
|
+
status: "paused",
|
|
634
|
+
reason: "custom-engine-verify-pause",
|
|
635
|
+
unitType: iterData.unitType,
|
|
636
|
+
unitId: iterData.unitId,
|
|
637
|
+
failureClass: "manual-attention",
|
|
638
|
+
});
|
|
639
|
+
break;
|
|
640
|
+
}
|
|
600
641
|
}
|
|
601
642
|
if (verifyResult === "retry") {
|
|
602
643
|
const retryOutcome = await handleCustomEngineVerifyRetry({
|
|
@@ -630,7 +671,22 @@ export async function autoLoop(
|
|
|
630
671
|
finishTurn,
|
|
631
672
|
},
|
|
632
673
|
});
|
|
633
|
-
if (retryFlow.action === "break")
|
|
674
|
+
if (retryFlow.action === "break") {
|
|
675
|
+
finishIncompleteIteration({
|
|
676
|
+
status: retryOutcome.action === "stop" ? "stopped" : "paused",
|
|
677
|
+
reason: retryOutcome.action === "retry" ? "custom-engine-verify-retry" : retryOutcome.turnError,
|
|
678
|
+
unitType: iterData.unitType,
|
|
679
|
+
unitId: iterData.unitId,
|
|
680
|
+
failureClass: "manual-attention",
|
|
681
|
+
});
|
|
682
|
+
break;
|
|
683
|
+
}
|
|
684
|
+
finishIncompleteIteration({
|
|
685
|
+
status: "retry",
|
|
686
|
+
reason: "custom-engine-verify-retry",
|
|
687
|
+
unitType: iterData.unitType,
|
|
688
|
+
unitId: iterData.unitId,
|
|
689
|
+
});
|
|
634
690
|
continue;
|
|
635
691
|
}
|
|
636
692
|
|
|
@@ -841,6 +897,13 @@ export async function autoLoop(
|
|
|
841
897
|
markFailed: markDispatchFailed,
|
|
842
898
|
logWriteFailure: logDispatchLedgerWriteFailure,
|
|
843
899
|
}) || dispatchSettled;
|
|
900
|
+
finishIncompleteIteration({
|
|
901
|
+
status: "stopped",
|
|
902
|
+
reason: unitPhaseResult.reason ?? "unit-break",
|
|
903
|
+
unitType: iterData.unitType,
|
|
904
|
+
unitId: iterData.unitId,
|
|
905
|
+
failureClass: "execution",
|
|
906
|
+
});
|
|
844
907
|
finishTurn("stopped", "execution", "unit-break");
|
|
845
908
|
break;
|
|
846
909
|
}
|
|
@@ -848,12 +911,25 @@ export async function autoLoop(
|
|
|
848
911
|
// ── Phase 5: Finalize ───────────────────────────────────────────────
|
|
849
912
|
|
|
850
913
|
let finalizeResult: Awaited<ReturnType<typeof runFinalize>>;
|
|
914
|
+
journalReporter.emit("post-unit-finalize-start", {
|
|
915
|
+
iteration,
|
|
916
|
+
unitType: iterData.unitType,
|
|
917
|
+
unitId: iterData.unitId,
|
|
918
|
+
});
|
|
851
919
|
try {
|
|
852
920
|
finalizeResult = await runFinalize(ic, iterData, loopState, sidecarItem);
|
|
853
921
|
} catch (err) {
|
|
922
|
+
const error = formatDispatchExceptionSummary({ error: err });
|
|
923
|
+
journalReporter.emit("post-unit-finalize-end", {
|
|
924
|
+
iteration,
|
|
925
|
+
unitType: iterData.unitType,
|
|
926
|
+
unitId: iterData.unitId,
|
|
927
|
+
status: "failed",
|
|
928
|
+
error,
|
|
929
|
+
});
|
|
854
930
|
dispatchSettled = settleDispatchFailed(
|
|
855
931
|
dispatchId,
|
|
856
|
-
|
|
932
|
+
error,
|
|
857
933
|
{
|
|
858
934
|
markFailed: markDispatchFailed,
|
|
859
935
|
logWriteFailure: logDispatchLedgerWriteFailure,
|
|
@@ -865,6 +941,15 @@ export async function autoLoop(
|
|
|
865
941
|
unitType: iterData.unitType,
|
|
866
942
|
unitId: iterData.unitId,
|
|
867
943
|
});
|
|
944
|
+
const finalizeReason = finalizeResult.action === "break" ? finalizeResult.reason : undefined;
|
|
945
|
+
journalReporter.emit("post-unit-finalize-end", {
|
|
946
|
+
iteration,
|
|
947
|
+
unitType: iterData.unitType,
|
|
948
|
+
unitId: iterData.unitId,
|
|
949
|
+
status: finalizeResult.action === "next" ? "completed" : finalizeResult.action === "continue" ? "retry" : "stopped",
|
|
950
|
+
action: finalizeResult.action,
|
|
951
|
+
...(finalizeReason ? { reason: finalizeReason } : {}),
|
|
952
|
+
});
|
|
868
953
|
const finalizeDecision = decideFinalizeResult(
|
|
869
954
|
finalizeResult.action === "break"
|
|
870
955
|
? { action: "break", reason: finalizeResult.reason }
|
|
@@ -877,6 +962,13 @@ export async function autoLoop(
|
|
|
877
962
|
markFailed: markDispatchFailed,
|
|
878
963
|
logWriteFailure: logDispatchLedgerWriteFailure,
|
|
879
964
|
}) || dispatchSettled;
|
|
965
|
+
finishIncompleteIteration({
|
|
966
|
+
status: "stopped",
|
|
967
|
+
reason: finalizeReason ?? "finalize-break",
|
|
968
|
+
unitType: iterData.unitType,
|
|
969
|
+
unitId: iterData.unitId,
|
|
970
|
+
failureClass: finalizeDecision.failureClass,
|
|
971
|
+
});
|
|
880
972
|
finishTurn("stopped", finalizeDecision.failureClass, finalizeDecision.turnError);
|
|
881
973
|
break;
|
|
882
974
|
}
|
|
@@ -885,6 +977,12 @@ export async function autoLoop(
|
|
|
885
977
|
markFailed: markDispatchFailed,
|
|
886
978
|
logWriteFailure: logDispatchLedgerWriteFailure,
|
|
887
979
|
}) || dispatchSettled;
|
|
980
|
+
finishIncompleteIteration({
|
|
981
|
+
status: "retry",
|
|
982
|
+
reason: "finalize-retry",
|
|
983
|
+
unitType: iterData.unitType,
|
|
984
|
+
unitId: iterData.unitId,
|
|
985
|
+
});
|
|
888
986
|
finishTurn("retry");
|
|
889
987
|
continue;
|
|
890
988
|
}
|
|
@@ -909,11 +1007,6 @@ export async function autoLoop(
|
|
|
909
1007
|
) || dispatchSettled;
|
|
910
1008
|
}
|
|
911
1009
|
|
|
912
|
-
// Always emit iteration-end on error so the journal records iteration
|
|
913
|
-
// completion even on failure (#2344). Without this, errors in
|
|
914
|
-
// runFinalize leave the journal incomplete, making diagnosis harder.
|
|
915
|
-
journalReporter.emit("iteration-end", { iteration, error: msg });
|
|
916
|
-
|
|
917
1010
|
// ── Pre-send model-policy block: not a retryable error (#4959 / #4850) ──
|
|
918
1011
|
// The model-policy gate runs before the prompt is sent. When every
|
|
919
1012
|
// candidate model is denied (cross-provider disabled + flat-rate
|
|
@@ -938,6 +1031,12 @@ export async function autoLoop(
|
|
|
938
1031
|
});
|
|
939
1032
|
ctx.ui.notify(policyDecision.notifyMessage, "error");
|
|
940
1033
|
journalReporter.emit("unit-end", policyDecision.journalData);
|
|
1034
|
+
finishIncompleteIteration({
|
|
1035
|
+
status: "blocked",
|
|
1036
|
+
reason: "model-policy-dispatch-blocked",
|
|
1037
|
+
unitType: loopErr.unitType,
|
|
1038
|
+
unitId: loopErr.unitId,
|
|
1039
|
+
});
|
|
941
1040
|
// Carry the blocked unit identity into the turn-result observer:
|
|
942
1041
|
// the throw originated inside dispatch, so observedUnitType/Id were
|
|
943
1042
|
// not assigned by the success path at lines 453/631/647 — but the
|
|
@@ -951,6 +1050,11 @@ export async function autoLoop(
|
|
|
951
1050
|
break;
|
|
952
1051
|
}
|
|
953
1052
|
|
|
1053
|
+
// Always emit iteration-end on error so the journal records iteration
|
|
1054
|
+
// completion even on failure (#2344). Without this, errors in
|
|
1055
|
+
// runFinalize leave the journal incomplete, making diagnosis harder.
|
|
1056
|
+
finishIncompleteIteration({ status: "failed", error: msg });
|
|
1057
|
+
|
|
954
1058
|
// ── Infrastructure errors: immediate stop, no retry ──
|
|
955
1059
|
// These are unrecoverable (disk full, OOM, etc.). Retrying just burns
|
|
956
1060
|
// LLM budget on guaranteed failures.
|
|
@@ -58,6 +58,7 @@ import { withTimeout, FINALIZE_PRE_TIMEOUT_MS, FINALIZE_POST_TIMEOUT_MS } from "
|
|
|
58
58
|
import { getEligibleSlices } from "../slice-parallel-eligibility.js";
|
|
59
59
|
import { startSliceParallel } from "../slice-parallel-orchestrator.js";
|
|
60
60
|
import { isDbAvailable, getMilestoneSlices } from "../gsd-db.js";
|
|
61
|
+
import { reconcileBeforeSpawn } from "../state-reconciliation.js";
|
|
61
62
|
import type { MinimalModelRegistry } from "../context-budget.js";
|
|
62
63
|
import type { PostflightResult, PreflightResult } from "../clean-root-preflight.js";
|
|
63
64
|
import { ensurePlanV2Graph, isEmptyPlanV2GraphResult, isMissingFinalizedContextResult } from "../uok/plan-v2.js";
|
|
@@ -74,6 +75,8 @@ import {
|
|
|
74
75
|
} from "../workflow-mcp.js";
|
|
75
76
|
import { resolveManifest } from "../unit-context-manifest.js";
|
|
76
77
|
import { createWorktreeSafetyModule, type WorktreeSafetyResult } from "../worktree-safety.js";
|
|
78
|
+
import { isSuspiciousGhostCompletion } from "../auto-unit-closeout.js";
|
|
79
|
+
import { decideVerificationRetry, verificationRetryKey } from "./verification-retry-policy.js";
|
|
77
80
|
|
|
78
81
|
// ─── Path Comparison Helper ───────────────────────────────────────────────
|
|
79
82
|
/** Compare two paths for physical identity, tolerating trailing slashes and symlinks. */
|
|
@@ -81,6 +84,56 @@ function isSamePathLocal(a: string, b: string): boolean {
|
|
|
81
84
|
return normalizeWorktreePathForCompare(a) === normalizeWorktreePathForCompare(b);
|
|
82
85
|
}
|
|
83
86
|
|
|
87
|
+
async function applyVerificationRetryPolicy(
|
|
88
|
+
ic: IterationContext,
|
|
89
|
+
unitType: string | undefined,
|
|
90
|
+
phase: "artifact-verification-retry" | "verification-retry",
|
|
91
|
+
): Promise<PhaseResult | null> {
|
|
92
|
+
const { ctx, pi, s, deps } = ic;
|
|
93
|
+
const retryInfo = s.pendingVerificationRetry;
|
|
94
|
+
const key = unitType && retryInfo
|
|
95
|
+
? verificationRetryKey(unitType, retryInfo.unitId)
|
|
96
|
+
: undefined;
|
|
97
|
+
const decision = decideVerificationRetry({
|
|
98
|
+
unitType,
|
|
99
|
+
retryInfo,
|
|
100
|
+
previousFailureHash: key ? s.verificationRetryFailureHashes.get(key) : undefined,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
if (decision.action === "pause") {
|
|
104
|
+
s.pendingVerificationRetry = null;
|
|
105
|
+
debugLog("autoLoop", {
|
|
106
|
+
phase: `${phase}-paused`,
|
|
107
|
+
reason: decision.reason,
|
|
108
|
+
unitType,
|
|
109
|
+
unitId: retryInfo?.unitId,
|
|
110
|
+
failureHash: decision.failureHash,
|
|
111
|
+
});
|
|
112
|
+
ctx.ui.notify(
|
|
113
|
+
decision.reason === "duplicate-failure-context"
|
|
114
|
+
? `Verification retry for ${unitType ?? "unit"} ${retryInfo?.unitId ?? "unknown"} produced the same failure context. Pausing auto-mode instead of re-dispatching.`
|
|
115
|
+
: "Verification retry requested without retry context. Pausing auto-mode instead of re-dispatching.",
|
|
116
|
+
"warning",
|
|
117
|
+
);
|
|
118
|
+
await deps.pauseAuto(ctx, pi);
|
|
119
|
+
return { action: "break", reason: decision.reason };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
s.verificationRetryFailureHashes.set(decision.key, decision.failureHash);
|
|
123
|
+
debugLog("autoLoop", {
|
|
124
|
+
phase: `${phase}-backoff`,
|
|
125
|
+
iteration: ic.iteration,
|
|
126
|
+
unitType,
|
|
127
|
+
unitId: retryInfo?.unitId,
|
|
128
|
+
attempt: retryInfo?.attempt,
|
|
129
|
+
delayMs: decision.delayMs,
|
|
130
|
+
baseDelayMs: decision.baseDelayMs,
|
|
131
|
+
failureHash: decision.failureHash,
|
|
132
|
+
});
|
|
133
|
+
await new Promise<void>((resolve) => setTimeout(resolve, decision.delayMs));
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
84
137
|
export function shouldDegradeEmptyWorktreeToProjectRoot(
|
|
85
138
|
worktreeClassification: ReturnType<typeof classifyProject>,
|
|
86
139
|
projectRootClassification: ReturnType<typeof classifyProject>,
|
|
@@ -826,6 +879,16 @@ export async function runPreDispatch(
|
|
|
826
879
|
`Slice-parallel: dispatching ${eligible.length} eligible slices for ${mid}.`,
|
|
827
880
|
"info",
|
|
828
881
|
);
|
|
882
|
+
// ADR-017 #5707: reconcile before spawning so each worker doesn't
|
|
883
|
+
// independently race on the same drift. Failure aborts the spawn.
|
|
884
|
+
const spawnGate = await reconcileBeforeSpawn(s.basePath);
|
|
885
|
+
if (!spawnGate.ok) {
|
|
886
|
+
ctx.ui.notify(
|
|
887
|
+
`Slice-parallel: aborting spawn — ${spawnGate.reason}`,
|
|
888
|
+
"error",
|
|
889
|
+
);
|
|
890
|
+
return { action: "break", reason: `slice-parallel-reconciliation-failed: ${spawnGate.reason}` };
|
|
891
|
+
}
|
|
829
892
|
const result = await startSliceParallel(
|
|
830
893
|
s.basePath,
|
|
831
894
|
mid,
|
|
@@ -1008,7 +1071,13 @@ export async function runPreDispatch(
|
|
|
1008
1071
|
"All milestones complete.",
|
|
1009
1072
|
"success",
|
|
1010
1073
|
);
|
|
1011
|
-
await deps.stopAuto(ctx, pi, "All milestones complete"
|
|
1074
|
+
await deps.stopAuto(ctx, pi, "All milestones complete", {
|
|
1075
|
+
completionWidget: {
|
|
1076
|
+
milestoneId: s.currentMilestoneId,
|
|
1077
|
+
milestoneTitle: midTitle,
|
|
1078
|
+
allMilestonesComplete: true,
|
|
1079
|
+
},
|
|
1080
|
+
});
|
|
1012
1081
|
} else if (incomplete.length === 0 && state.registry.length === 0) {
|
|
1013
1082
|
// Empty registry — no milestones visible, likely a path resolution bug
|
|
1014
1083
|
const diag = `basePath=${s.basePath}, phase=${state.phase}`;
|
|
@@ -1103,7 +1172,23 @@ export async function runPreDispatch(
|
|
|
1103
1172
|
`Milestone ${mid} complete.`,
|
|
1104
1173
|
"success",
|
|
1105
1174
|
);
|
|
1106
|
-
|
|
1175
|
+
if (s.currentUnit) {
|
|
1176
|
+
await deps.closeoutUnit(
|
|
1177
|
+
ctx,
|
|
1178
|
+
s.basePath,
|
|
1179
|
+
s.currentUnit.type,
|
|
1180
|
+
s.currentUnit.id,
|
|
1181
|
+
s.currentUnit.startedAt,
|
|
1182
|
+
deps.buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id),
|
|
1183
|
+
);
|
|
1184
|
+
s.currentUnit = null;
|
|
1185
|
+
}
|
|
1186
|
+
await deps.stopAuto(ctx, pi, `Milestone ${mid} complete`, {
|
|
1187
|
+
completionWidget: {
|
|
1188
|
+
milestoneId: mid,
|
|
1189
|
+
milestoneTitle: midTitle,
|
|
1190
|
+
},
|
|
1191
|
+
});
|
|
1107
1192
|
debugLog("autoLoop", { phase: "exit", reason: "milestone-complete" });
|
|
1108
1193
|
deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: ic.nextSeq(), eventType: "terminal", data: { reason: "milestone-complete", milestoneId: mid } });
|
|
1109
1194
|
return { action: "break", reason: "milestone-complete" };
|
|
@@ -1266,16 +1351,14 @@ export async function runDispatch(
|
|
|
1266
1351
|
// ── Sliding-window stuck detection with graduated recovery ──
|
|
1267
1352
|
const derivedKey = `${unitType}/${unitId}`;
|
|
1268
1353
|
|
|
1269
|
-
// Always record this dispatch in the sliding window
|
|
1270
|
-
//
|
|
1271
|
-
//
|
|
1272
|
-
// (#2007). Only the *response* to a stuck signal is suppressed during retries.
|
|
1354
|
+
// Always record this dispatch in the sliding window and run detection so
|
|
1355
|
+
// Rules 1/3/4 can catch retry loops with repeated failure content (#5719).
|
|
1356
|
+
// Rules 2/2b suppress legitimate retry backoff through the dispatch ledger.
|
|
1273
1357
|
loopState.recentUnits.push({ key: derivedKey });
|
|
1274
1358
|
if (loopState.recentUnits.length > STUCK_WINDOW_SIZE) loopState.recentUnits.shift();
|
|
1275
1359
|
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
if (stuckSignal) {
|
|
1360
|
+
const stuckSignal = detectStuck(loopState.recentUnits);
|
|
1361
|
+
if (stuckSignal) {
|
|
1279
1362
|
debugLog("autoLoop", {
|
|
1280
1363
|
phase: "stuck-check",
|
|
1281
1364
|
unitType,
|
|
@@ -1391,16 +1474,15 @@ export async function runDispatch(
|
|
|
1391
1474
|
);
|
|
1392
1475
|
return { action: "break", reason: "stuck-detected" };
|
|
1393
1476
|
}
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
}
|
|
1477
|
+
} else {
|
|
1478
|
+
// Progress detected — reset recovery counter
|
|
1479
|
+
if (loopState.stuckRecoveryAttempts > 0) {
|
|
1480
|
+
debugLog("autoLoop", {
|
|
1481
|
+
phase: "stuck-counter-reset",
|
|
1482
|
+
from: loopState.recentUnits[loopState.recentUnits.length - 2]?.key ?? "",
|
|
1483
|
+
to: derivedKey,
|
|
1484
|
+
});
|
|
1485
|
+
loopState.stuckRecoveryAttempts = 0;
|
|
1404
1486
|
}
|
|
1405
1487
|
}
|
|
1406
1488
|
|
|
@@ -1984,6 +2066,33 @@ export async function runUnitPhase(
|
|
|
1984
2066
|
status: unitResult.status,
|
|
1985
2067
|
});
|
|
1986
2068
|
|
|
2069
|
+
if (
|
|
2070
|
+
unitResult.status === "completed" &&
|
|
2071
|
+
s.currentUnit &&
|
|
2072
|
+
(unitResult.event?.messages?.length ?? 0) === 0 &&
|
|
2073
|
+
isSuspiciousGhostCompletion(ctx, unitResult.requestDispatchedAt ?? s.currentUnit.startedAt)
|
|
2074
|
+
) {
|
|
2075
|
+
const message =
|
|
2076
|
+
`${unitType} ${unitId} completed without assistant output or tool calls; treating as a stale ghost completion.`;
|
|
2077
|
+
debugLog("autoLoop", {
|
|
2078
|
+
phase: "ghost-completion",
|
|
2079
|
+
iteration: ic.iteration,
|
|
2080
|
+
unitType,
|
|
2081
|
+
unitId,
|
|
2082
|
+
elapsedMs: Date.now() - (unitResult.requestDispatchedAt ?? s.currentUnit.startedAt),
|
|
2083
|
+
});
|
|
2084
|
+
logWarning("engine", message);
|
|
2085
|
+
ctx.ui.notify(`${message} Pausing auto-mode before closeout side effects.`, "warning");
|
|
2086
|
+
await emitCancelledUnitEnd(ic, unitType, unitId, unitStartSeq, {
|
|
2087
|
+
message,
|
|
2088
|
+
category: "unknown",
|
|
2089
|
+
isTransient: true,
|
|
2090
|
+
});
|
|
2091
|
+
s.currentUnit = null;
|
|
2092
|
+
await deps.pauseAuto(ctx, pi);
|
|
2093
|
+
return { action: "break", reason: "ghost-completion" };
|
|
2094
|
+
}
|
|
2095
|
+
|
|
1987
2096
|
// Now that runUnit has called newSession(), the session file path is correct.
|
|
1988
2097
|
const sessionFile = deps.getSessionFile(ctx);
|
|
1989
2098
|
deps.updateSessionLock(
|
|
@@ -2364,6 +2473,14 @@ export async function runFinalize(
|
|
|
2364
2473
|
attempt: retryInfo?.attempt,
|
|
2365
2474
|
},
|
|
2366
2475
|
});
|
|
2476
|
+
const retryPolicyResult = await applyVerificationRetryPolicy(
|
|
2477
|
+
ic,
|
|
2478
|
+
preUnitSnapshot?.type,
|
|
2479
|
+
"artifact-verification-retry",
|
|
2480
|
+
);
|
|
2481
|
+
if (retryPolicyResult) {
|
|
2482
|
+
return retryPolicyResult;
|
|
2483
|
+
}
|
|
2367
2484
|
// Continue the loop — next iteration will inject the retry context into the prompt.
|
|
2368
2485
|
debugLog("autoLoop", { phase: "artifact-verification-retry", iteration: ic.iteration });
|
|
2369
2486
|
return { action: "continue" };
|
|
@@ -2401,6 +2518,14 @@ export async function runFinalize(
|
|
|
2401
2518
|
debugLog("autoLoop", { phase: "sidecar-verification-retry-skipped", iteration: ic.iteration });
|
|
2402
2519
|
} else {
|
|
2403
2520
|
// s.pendingVerificationRetry was set by runPostUnitVerification.
|
|
2521
|
+
const retryPolicyResult = await applyVerificationRetryPolicy(
|
|
2522
|
+
ic,
|
|
2523
|
+
iterData.unitType,
|
|
2524
|
+
"verification-retry",
|
|
2525
|
+
);
|
|
2526
|
+
if (retryPolicyResult) {
|
|
2527
|
+
return retryPolicyResult;
|
|
2528
|
+
}
|
|
2404
2529
|
// Continue the loop — next iteration will inject the retry context into the prompt.
|
|
2405
2530
|
debugLog("autoLoop", { phase: "verification-retry", iteration: ic.iteration });
|
|
2406
2531
|
return { action: "continue" };
|
|
@@ -26,6 +26,31 @@ import { debugLog } from "../debug-logger.js";
|
|
|
26
26
|
import { logWarning, logError } from "../workflow-logger.js";
|
|
27
27
|
import { resolveAutoSupervisorConfig } from "../preferences.js";
|
|
28
28
|
import { formatAutoUnitWorkingMessage } from "../working-output-messages.js";
|
|
29
|
+
import { readUnitRuntimeRecord, type AutoUnitRuntimeRecord } from "../unit-runtime.js";
|
|
30
|
+
|
|
31
|
+
const UNIT_FAILSAFE_BUFFER_MS = 30_000;
|
|
32
|
+
const UNIT_FAILSAFE_RECHECK_MS = 30_000;
|
|
33
|
+
|
|
34
|
+
export function shouldDeferUnitFailsafeTimeout(
|
|
35
|
+
runtime: AutoUnitRuntimeRecord | null,
|
|
36
|
+
opts: {
|
|
37
|
+
nowMs: number;
|
|
38
|
+
currentUnitStartedAt?: number;
|
|
39
|
+
freshProgressMs: number;
|
|
40
|
+
},
|
|
41
|
+
): boolean {
|
|
42
|
+
if (!runtime) return false;
|
|
43
|
+
if (opts.currentUnitStartedAt === undefined) return false;
|
|
44
|
+
if (runtime.startedAt !== opts.currentUnitStartedAt) return false;
|
|
45
|
+
if (runtime.lastProgressAt <= 0) return false;
|
|
46
|
+
const progressAgeMs = opts.nowMs - runtime.lastProgressAt;
|
|
47
|
+
if (progressAgeMs < 0) return false;
|
|
48
|
+
if (progressAgeMs > opts.freshProgressMs) return false;
|
|
49
|
+
if (runtime.phase === "recovered") return true;
|
|
50
|
+
if (runtime.lastProgressKind.includes("recovery")) return true;
|
|
51
|
+
if (runtime.recoveryAttempts && runtime.recoveryAttempts > 0) return true;
|
|
52
|
+
return progressAgeMs >= 0 && progressAgeMs <= opts.freshProgressMs;
|
|
53
|
+
}
|
|
29
54
|
|
|
30
55
|
// Tracks the latest session-switch attempt so a late timeout settlement from an
|
|
31
56
|
// older runUnit() call cannot clear the guard for a newer one.
|
|
@@ -192,8 +217,12 @@ export async function runUnit(
|
|
|
192
217
|
// Without this, a crashed agent that never emits agent_end hangs the loop (#3161).
|
|
193
218
|
const supervisor = resolveAutoSupervisorConfig();
|
|
194
219
|
const UNIT_HARD_TIMEOUT_MS = Math.max(
|
|
195
|
-
|
|
196
|
-
((supervisor.hard_timeout_minutes ?? 30) * 60 * 1000) +
|
|
220
|
+
UNIT_FAILSAFE_BUFFER_MS,
|
|
221
|
+
((supervisor.hard_timeout_minutes ?? 30) * 60 * 1000) + UNIT_FAILSAFE_BUFFER_MS,
|
|
222
|
+
);
|
|
223
|
+
const freshProgressMs = Math.max(
|
|
224
|
+
UNIT_FAILSAFE_BUFFER_MS,
|
|
225
|
+
((supervisor.idle_timeout_minutes ?? 10) * 60 * 1000) + UNIT_FAILSAFE_BUFFER_MS,
|
|
197
226
|
);
|
|
198
227
|
let unitTimeoutHandle: ReturnType<typeof setTimeout> | undefined;
|
|
199
228
|
let result: UnitResult;
|
|
@@ -205,9 +234,45 @@ export async function runUnit(
|
|
|
205
234
|
|
|
206
235
|
debugLog("runUnit", { phase: "awaiting-agent-end", unitType, unitId });
|
|
207
236
|
const timeoutResult = new Promise<UnitResult>((resolve) => {
|
|
208
|
-
|
|
237
|
+
const settleOrDefer = () => {
|
|
238
|
+
let runtime: AutoUnitRuntimeRecord | null;
|
|
239
|
+
try {
|
|
240
|
+
runtime = readUnitRuntimeRecord(s.basePath, unitType, unitId);
|
|
241
|
+
} catch (error) {
|
|
242
|
+
debugLog("runUnit", {
|
|
243
|
+
phase: "unit-failsafe-runtime-read-failed",
|
|
244
|
+
unitType,
|
|
245
|
+
unitId,
|
|
246
|
+
error: error instanceof Error ? error.message : String(error),
|
|
247
|
+
});
|
|
248
|
+
resolve({
|
|
249
|
+
status: "cancelled",
|
|
250
|
+
errorContext: {
|
|
251
|
+
message: "Unit hard timeout — supervision may have failed; runtime progress could not be read",
|
|
252
|
+
category: "timeout",
|
|
253
|
+
isTransient: true,
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (shouldDeferUnitFailsafeTimeout(runtime, {
|
|
259
|
+
nowMs: Date.now(),
|
|
260
|
+
currentUnitStartedAt: s.currentUnit?.startedAt,
|
|
261
|
+
freshProgressMs,
|
|
262
|
+
})) {
|
|
263
|
+
debugLog("runUnit", {
|
|
264
|
+
phase: "unit-failsafe-deferred",
|
|
265
|
+
unitType,
|
|
266
|
+
unitId,
|
|
267
|
+
runtimePhase: runtime?.phase,
|
|
268
|
+
lastProgressKind: runtime?.lastProgressKind,
|
|
269
|
+
});
|
|
270
|
+
unitTimeoutHandle = setTimeout(settleOrDefer, UNIT_FAILSAFE_RECHECK_MS);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
209
273
|
resolve({ status: "cancelled", errorContext: { message: "Unit hard timeout — supervision may have failed", category: "timeout", isTransient: true } });
|
|
210
|
-
}
|
|
274
|
+
};
|
|
275
|
+
unitTimeoutHandle = setTimeout(settleOrDefer, UNIT_HARD_TIMEOUT_MS);
|
|
211
276
|
});
|
|
212
277
|
result = await runWithTurnGeneration(capturedTurnGen, () =>
|
|
213
278
|
Promise.race([unitPromise, timeoutResult]),
|
|
@@ -88,6 +88,7 @@ export class AutoSession {
|
|
|
88
88
|
// ── Lifecycle ────────────────────────────────────────────────────────────
|
|
89
89
|
active = false;
|
|
90
90
|
paused = false;
|
|
91
|
+
completionStopInProgress = false;
|
|
91
92
|
stepMode = false;
|
|
92
93
|
verbose = false;
|
|
93
94
|
activeEngineId: string | null = null;
|
|
@@ -158,6 +159,7 @@ export class AutoSession {
|
|
|
158
159
|
pendingCrashRecovery: string | null = null;
|
|
159
160
|
pendingVerificationRetry: PendingVerificationRetry | null = null;
|
|
160
161
|
readonly verificationRetryCount = new Map<string, number>();
|
|
162
|
+
readonly verificationRetryFailureHashes = new Map<string, string>();
|
|
161
163
|
pausedSessionFile: string | null = null;
|
|
162
164
|
pausedUnitType: string | null = null;
|
|
163
165
|
pausedUnitId: string | null = null;
|
|
@@ -286,6 +288,7 @@ export class AutoSession {
|
|
|
286
288
|
// Lifecycle
|
|
287
289
|
this.active = false;
|
|
288
290
|
this.paused = false;
|
|
291
|
+
this.completionStopInProgress = false;
|
|
289
292
|
this.stepMode = false;
|
|
290
293
|
this.verbose = false;
|
|
291
294
|
this.activeEngineId = null;
|
|
@@ -334,6 +337,7 @@ export class AutoSession {
|
|
|
334
337
|
this.pendingCrashRecovery = null;
|
|
335
338
|
this.pendingVerificationRetry = null;
|
|
336
339
|
this.verificationRetryCount.clear();
|
|
340
|
+
this.verificationRetryFailureHashes.clear();
|
|
337
341
|
this.pausedSessionFile = null;
|
|
338
342
|
this.pausedUnitType = null;
|
|
339
343
|
this.pausedUnitId = null;
|
|
@@ -372,6 +376,12 @@ export class AutoSession {
|
|
|
372
376
|
// Loop promise state lives in auto-loop.ts module scope
|
|
373
377
|
}
|
|
374
378
|
|
|
379
|
+
resetAfterStop(options: { preserveCompletionSurface?: boolean } = {}): void {
|
|
380
|
+
const completionStopInProgress = options.preserveCompletionSurface ? this.completionStopInProgress : false;
|
|
381
|
+
this.reset();
|
|
382
|
+
this.completionStopInProgress = completionStopInProgress;
|
|
383
|
+
}
|
|
384
|
+
|
|
375
385
|
toJSON(): Record<string, unknown> {
|
|
376
386
|
const orchestrationStatus = this.orchestration?.getStatus();
|
|
377
387
|
return {
|