gsd-pi 2.80.0-dev.2848d9009 → 2.80.0-dev.3065f7bfa
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/loop.js +32 -1
- package/dist/resources/extensions/gsd/auto/phases.js +178 -108
- package/dist/resources/extensions/gsd/auto/resolve.js +17 -0
- package/dist/resources/extensions/gsd/auto/run-unit.js +20 -30
- package/dist/resources/extensions/gsd/auto/session.js +8 -0
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +5 -32
- package/dist/resources/extensions/gsd/auto-dispatch.js +16 -0
- package/dist/resources/extensions/gsd/auto-post-unit.js +17 -4
- package/dist/resources/extensions/gsd/auto-prompts.js +103 -16
- package/dist/resources/extensions/gsd/auto-recovery.js +43 -1
- package/dist/resources/extensions/gsd/auto-start.js +197 -6
- package/dist/resources/extensions/gsd/auto-supervisor.js +8 -1
- package/dist/resources/extensions/gsd/auto-timeout-recovery.js +2 -2
- package/dist/resources/extensions/gsd/auto-worktree.js +111 -1
- package/dist/resources/extensions/gsd/auto.js +86 -26
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +42 -2
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +49 -36
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +15 -5
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +33 -20
- package/dist/resources/extensions/gsd/bootstrap/journal-tools.js +7 -1
- package/dist/resources/extensions/gsd/bootstrap/memory-tools.js +9 -3
- package/dist/resources/extensions/gsd/bootstrap/query-tools.js +8 -2
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +259 -28
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +82 -23
- package/dist/resources/extensions/gsd/clean-root-preflight.js +24 -6
- package/dist/resources/extensions/gsd/commands-handlers.js +23 -9
- package/dist/resources/extensions/gsd/context-budget.js +37 -2
- package/dist/resources/extensions/gsd/crash-recovery.js +56 -10
- package/dist/resources/extensions/gsd/db/unit-dispatches.js +39 -0
- package/dist/resources/extensions/gsd/db-base-schema.js +4 -2
- package/dist/resources/extensions/gsd/db-migration-steps.js +6 -0
- package/dist/resources/extensions/gsd/ecosystem/gsd-extension-api.js +2 -0
- package/dist/resources/extensions/gsd/git-service.js +36 -4
- package/dist/resources/extensions/gsd/gsd-db.js +46 -13
- package/dist/resources/extensions/gsd/guided-flow.js +99 -38
- package/dist/resources/extensions/gsd/memory-store.js +69 -12
- package/dist/resources/extensions/gsd/migrate/command.js +40 -1
- package/dist/resources/extensions/gsd/migration-auto-check.js +87 -0
- package/dist/resources/extensions/gsd/native-git-bridge.js +32 -8
- package/dist/resources/extensions/gsd/orphan-stash-audit.js +101 -0
- package/dist/resources/extensions/gsd/parallel-orchestrator.js +13 -3
- package/dist/resources/extensions/gsd/planning-path-scope.js +26 -0
- package/dist/resources/extensions/gsd/pre-execution-checks.js +22 -0
- package/dist/resources/extensions/gsd/prompt-loader.js +28 -2
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +16 -13
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/execute-task.md +4 -2
- package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/quick-task.md +1 -5
- package/dist/resources/extensions/gsd/prompts/replan-slice.md +2 -2
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
- package/dist/resources/extensions/gsd/quick.js +34 -2
- package/dist/resources/extensions/gsd/tools/context-mode-tool-result.js +15 -0
- package/dist/resources/extensions/gsd/tools/exec-search-tool.js +5 -0
- package/dist/resources/extensions/gsd/tools/exec-tool.js +3 -15
- package/dist/resources/extensions/gsd/tools/memory-tools.js +1 -0
- package/dist/resources/extensions/gsd/tools/plan-slice.js +9 -0
- package/dist/resources/extensions/gsd/tools/plan-task.js +9 -0
- package/dist/resources/extensions/gsd/tools/resume-tool.js +5 -0
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +1 -1
- package/dist/resources/extensions/gsd/unit-context-composer.js +12 -3
- package/dist/resources/extensions/gsd/unit-runtime.js +22 -0
- package/dist/resources/extensions/gsd/workflow-protocol.js +131 -0
- package/dist/resources/extensions/gsd/worktree-resolver.js +68 -21
- 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 +15 -15
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- 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 +15 -15
- 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/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +3 -3
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +22 -17
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/workflow-tools.test.ts +75 -2
- package/packages/mcp-server/src/workflow-tools.ts +30 -16
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/packages/native/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +4 -1
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/dist/agent.d.ts +5 -0
- package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent.js +2 -0
- package/packages/pi-agent-core/dist/agent.js.map +1 -1
- package/packages/pi-agent-core/dist/index.d.ts +1 -0
- package/packages/pi-agent-core/dist/index.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/index.js +2 -0
- package/packages/pi-agent-core/dist/index.js.map +1 -1
- package/packages/pi-agent-core/dist/token-audit.d.ts +47 -0
- package/packages/pi-agent-core/dist/token-audit.d.ts.map +1 -0
- package/packages/pi-agent-core/dist/token-audit.js +221 -0
- package/packages/pi-agent-core/dist/token-audit.js.map +1 -0
- package/packages/pi-agent-core/dist/types.d.ts +9 -0
- package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/types.js.map +1 -1
- package/packages/pi-agent-core/src/agent-loop.test.ts +128 -0
- package/packages/pi-agent-core/src/agent-loop.ts +4 -1
- package/packages/pi-agent-core/src/agent.ts +8 -0
- package/packages/pi-agent-core/src/index.ts +2 -0
- package/packages/pi-agent-core/src/token-audit.test.ts +189 -0
- package/packages/pi-agent-core/src/token-audit.ts +287 -0
- package/packages/pi-agent-core/src/types.ts +14 -0
- package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +32 -0
- package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js +18 -0
- package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts +12 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +44 -7
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +3 -1
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts +11 -0
- package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.js +9 -0
- package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js +103 -0
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.d.ts +15 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.js +66 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.test.js +24 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +8 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +3 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.js +6 -6
- package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +5 -3
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +39 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/hooks-runner.test.js +2 -0
- package/packages/pi-coding-agent/dist/core/hooks-runner.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk-tool-filter.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/sdk-tool-filter.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/sdk-tool-filter.test.js +46 -0
- package/packages/pi-coding-agent/dist/core/sdk-tool-filter.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/sdk.d.ts +10 -2
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +74 -2
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +20 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +25 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/skill-tool.test.js +22 -0
- package/packages/pi-coding-agent/dist/core/skill-tool.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts +6 -7
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +2 -3
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
- 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 +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +13 -5
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js +53 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js +10 -0
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js +3 -2
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +36 -0
- package/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts +25 -0
- package/packages/pi-coding-agent/src/core/agent-session.ts +48 -7
- package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +3 -1
- package/packages/pi-coding-agent/src/core/compaction/compaction.ts +18 -0
- package/packages/pi-coding-agent/src/core/compaction-threshold.test.ts +121 -0
- package/packages/pi-coding-agent/src/core/db-snapshot.test.ts +32 -0
- package/packages/pi-coding-agent/src/core/db-snapshot.ts +66 -0
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +10 -0
- package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +5 -3
- package/packages/pi-coding-agent/src/core/extensions/runner.ts +8 -5
- package/packages/pi-coding-agent/src/core/extensions/types.ts +42 -1
- package/packages/pi-coding-agent/src/core/hooks-runner.test.ts +2 -0
- package/packages/pi-coding-agent/src/core/sdk-tool-filter.test.ts +60 -0
- package/packages/pi-coding-agent/src/core/sdk.ts +85 -3
- package/packages/pi-coding-agent/src/core/settings-manager.ts +39 -1
- package/packages/pi-coding-agent/src/core/skill-tool.test.ts +28 -0
- package/packages/pi-coding-agent/src/core/system-prompt.ts +8 -10
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +4 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.test.ts +56 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +22 -7
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +3 -0
- package/packages/pi-coding-agent/src/resources/extensions/memory/storage-safety-guard.test.ts +14 -0
- package/packages/pi-coding-agent/src/resources/extensions/memory/storage.ts +3 -2
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-tui/dist/tui.d.ts.map +1 -1
- package/packages/pi-tui/dist/tui.js +18 -8
- package/packages/pi-tui/dist/tui.js.map +1 -1
- package/packages/pi-tui/src/tui.ts +20 -8
- package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -2
- package/src/resources/extensions/gsd/auto/loop.ts +50 -8
- package/src/resources/extensions/gsd/auto/phases.ts +251 -172
- package/src/resources/extensions/gsd/auto/resolve.ts +23 -1
- package/src/resources/extensions/gsd/auto/run-unit.ts +25 -30
- package/src/resources/extensions/gsd/auto/session.ts +8 -0
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +8 -34
- package/src/resources/extensions/gsd/auto-dispatch.ts +16 -0
- package/src/resources/extensions/gsd/auto-post-unit.ts +18 -4
- package/src/resources/extensions/gsd/auto-prompts.ts +112 -15
- package/src/resources/extensions/gsd/auto-recovery.ts +54 -0
- package/src/resources/extensions/gsd/auto-start.ts +230 -9
- package/src/resources/extensions/gsd/auto-supervisor.ts +7 -0
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +2 -2
- package/src/resources/extensions/gsd/auto-worktree.ts +123 -0
- package/src/resources/extensions/gsd/auto.ts +98 -21
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +49 -1
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +50 -36
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +16 -5
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +34 -19
- package/src/resources/extensions/gsd/bootstrap/journal-tools.ts +8 -1
- package/src/resources/extensions/gsd/bootstrap/memory-tools.ts +10 -3
- package/src/resources/extensions/gsd/bootstrap/query-tools.ts +9 -2
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +305 -28
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +90 -22
- package/src/resources/extensions/gsd/clean-root-preflight.ts +32 -7
- package/src/resources/extensions/gsd/commands-handlers.ts +34 -15
- package/src/resources/extensions/gsd/context-budget.ts +44 -2
- package/src/resources/extensions/gsd/crash-recovery.ts +67 -10
- package/src/resources/extensions/gsd/db/unit-dispatches.ts +41 -0
- package/src/resources/extensions/gsd/db-base-schema.ts +4 -2
- package/src/resources/extensions/gsd/db-migration-steps.ts +8 -0
- package/src/resources/extensions/gsd/ecosystem/gsd-extension-api.ts +3 -0
- package/src/resources/extensions/gsd/git-service.ts +46 -8
- package/src/resources/extensions/gsd/gsd-db.ts +50 -13
- package/src/resources/extensions/gsd/guided-flow.ts +123 -45
- package/src/resources/extensions/gsd/memory-store.ts +77 -12
- package/src/resources/extensions/gsd/migrate/command.ts +47 -1
- package/src/resources/extensions/gsd/migration-auto-check.ts +129 -0
- package/src/resources/extensions/gsd/native-git-bridge.ts +39 -6
- package/src/resources/extensions/gsd/orphan-stash-audit.ts +117 -0
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +13 -3
- package/src/resources/extensions/gsd/planning-path-scope.ts +35 -0
- package/src/resources/extensions/gsd/pre-execution-checks.ts +23 -0
- package/src/resources/extensions/gsd/preferences-types.ts +1 -1
- package/src/resources/extensions/gsd/prompt-loader.ts +27 -2
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +16 -13
- package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/execute-task.md +4 -2
- package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/quick-task.md +1 -5
- package/src/resources/extensions/gsd/prompts/replan-slice.md +2 -2
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
- package/src/resources/extensions/gsd/quick.ts +37 -2
- package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +417 -10
- package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +56 -13
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +14 -1
- package/src/resources/extensions/gsd/tests/auto-wrapup-inflight-guard.test.ts +168 -6
- package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +15 -6
- package/src/resources/extensions/gsd/tests/compaction-snapshot.test.ts +14 -1
- package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +31 -0
- package/src/resources/extensions/gsd/tests/complete-slice-composer.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/context-budget.test.ts +10 -1
- package/src/resources/extensions/gsd/tests/context-store.test.ts +7 -1
- package/src/resources/extensions/gsd/tests/crash-handler-secondary.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +5 -1
- package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +313 -0
- package/src/resources/extensions/gsd/tests/exec-history.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +65 -0
- package/src/resources/extensions/gsd/tests/execute-task-rendering.test.ts +5 -2
- package/src/resources/extensions/gsd/tests/fast-forward-reused-milestone-branch.test.ts +219 -0
- package/src/resources/extensions/gsd/tests/finalize-survivor-branch.test.ts +132 -0
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/isolation-none-branch-guard.test.ts +6 -3
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +239 -1
- package/src/resources/extensions/gsd/tests/journal-query-tool.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/knowledge.test.ts +47 -0
- package/src/resources/extensions/gsd/tests/memory-decay-factor.test.ts +90 -0
- package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +127 -0
- package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +242 -0
- package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +34 -2
- package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/orphan-merge-bootstrap.test.ts +133 -0
- package/src/resources/extensions/gsd/tests/orphan-stash-audit.test.ts +201 -0
- package/src/resources/extensions/gsd/tests/parallel-orchestrator-fast-forward.test.ts +113 -0
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/plan-task.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +45 -5
- package/src/resources/extensions/gsd/tests/prompt-duplication-cuts.test.ts +230 -0
- package/src/resources/extensions/gsd/tests/prompt-path-audit.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/query-tools-db-open.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/quick-external-gsd.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +38 -17
- package/src/resources/extensions/gsd/tests/schema-v27-v28-sequence.test.ts +156 -0
- package/src/resources/extensions/gsd/tests/select-resumable-milestone.test.ts +96 -0
- package/src/resources/extensions/gsd/tests/session-switch-abort-misclassification.test.ts +106 -0
- package/src/resources/extensions/gsd/tests/signal-handlers.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +49 -1
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/status-db-open.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/system-context-memory.test.ts +112 -0
- package/src/resources/extensions/gsd/tests/system-context-message-routing.test.ts +7 -9
- package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +291 -0
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +136 -4
- package/src/resources/extensions/gsd/tests/unit-dispatches.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +37 -0
- package/src/resources/extensions/gsd/tests/unstructured-continue-context-injection.test.ts +5 -4
- package/src/resources/extensions/gsd/tests/workflow-protocol-excerpt.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/worktree-path-injection.test.ts +22 -19
- package/src/resources/extensions/gsd/tests/worktree-project-root-degrade.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +167 -4
- package/src/resources/extensions/gsd/tools/context-mode-tool-result.ts +25 -0
- package/src/resources/extensions/gsd/tools/exec-search-tool.ts +7 -7
- package/src/resources/extensions/gsd/tools/exec-tool.ts +4 -23
- package/src/resources/extensions/gsd/tools/memory-tools.ts +1 -0
- package/src/resources/extensions/gsd/tools/plan-slice.ts +13 -0
- package/src/resources/extensions/gsd/tools/plan-task.ts +10 -0
- package/src/resources/extensions/gsd/tools/resume-tool.ts +7 -7
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +1 -1
- package/src/resources/extensions/gsd/unit-context-composer.ts +19 -4
- package/src/resources/extensions/gsd/unit-runtime.ts +25 -0
- package/src/resources/extensions/gsd/workflow-protocol.ts +160 -0
- package/src/resources/extensions/gsd/worktree-resolver.ts +85 -19
- package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +0 -97
- /package/dist/web/standalone/.next/static/{vaQ47E8aNlbbNEDoeME6G → O2WvnXROue6l7nAp_4dhC}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{vaQ47E8aNlbbNEDoeME6G → O2WvnXROue6l7nAp_4dhC}/_ssgManifest.js +0 -0
package/README.md
CHANGED
|
@@ -259,6 +259,7 @@ Full documentation is in the [`docs/`](./docs/) directory:
|
|
|
259
259
|
|
|
260
260
|
- **[Architecture](./docs/dev/architecture.md)** — system design and dispatch pipeline
|
|
261
261
|
- **[CI/CD Pipeline](./docs/dev/ci-cd-pipeline.md)** — three-stage promotion pipeline (Dev → Test → Prod)
|
|
262
|
+
- **[E2E Testing](./tests/e2e/README.md)** — real-process CLI/runtime coverage and CI runner expectations
|
|
262
263
|
- **[Pipeline Simplification (ADR-003)](./docs/dev/ADR-003-pipeline-simplification.md)** — merged research into planning, mechanical completion
|
|
263
264
|
- **[VS Code Extension](./vscode-extension/README.md)** — chat participant, sidebar dashboard, RPC integration
|
|
264
265
|
|
|
@@ -361,7 +362,7 @@ The database is authoritative for milestones, slices, tasks, requirements, decis
|
|
|
361
362
|
|
|
362
363
|
2. **Context pre-loading** — The dispatch prompt includes inlined task plans, slice plans, prior task summaries, dependency summaries, roadmap excerpts, and decisions register. The LLM starts with everything it needs instead of spending tool calls reading files.
|
|
363
364
|
|
|
364
|
-
3. **Context Mode** — Context Mode is enabled by default and gives
|
|
365
|
+
3. **Context Mode** — Context Mode is enabled by default and gives eligible auto-mode units guidance for preserving context. Agents are steered toward `gsd_exec` for noisy scans, builds, tests, and diagnostics; capped stdout/stderr and metadata are saved under `.gsd/exec/` while only a short digest enters the conversation. `gsd_exec_search` lets agents reuse prior runs instead of repeating expensive checks, and `gsd_resume` reads a prior compaction snapshot from `.gsd/last-snapshot.md` when one exists. Opt out with `context_mode.enabled: false` to disable Context Mode guidance, snapshot injection, `gsd_exec`, `gsd_exec_search`, and `gsd_resume`; tune sandbox timeout/output caps and environment forwarding with `context_mode.exec_timeout_ms`, `context_mode.exec_stdout_cap_bytes`, `context_mode.exec_digest_chars`, and `context_mode.exec_env_allowlist`.
|
|
365
366
|
|
|
366
367
|
4. **Git isolation** — When `git.isolation` is set to `worktree` or `branch`, each milestone runs on its own `milestone/<MID>` branch (in a worktree or in-place). All slice work commits sequentially — no branch switching, no merge conflicts. When the milestone completes, it's squash-merged to main as one clean commit. The default is `none` (work on the current branch), configurable via preferences. If `worktree` is configured in a repo with no committed `HEAD`, GSD temporarily behaves as `none` until the first commit exists because git worktrees need a committed start point.
|
|
367
368
|
|
|
@@ -659,10 +660,11 @@ auto_report: true
|
|
|
659
660
|
| `unique_milestone_ids` | Uses unique milestone names to avoid clashes when working in teams of people |
|
|
660
661
|
| `git.isolation` | `none` (default), `worktree`, or `branch` — enable worktree or branch isolation for milestone work. `worktree` requires a committed `HEAD`; zero-commit repos temporarily run as `none` |
|
|
661
662
|
| `git.manage_gitignore` | Set `false` to prevent GSD from modifying `.gitignore` |
|
|
662
|
-
| `context_mode.enabled` | Context Mode is default-on; set `false` to disable `gsd_exec`,
|
|
663
|
+
| `context_mode.enabled` | Context Mode is default-on; set `false` to disable prompt guidance, snapshot injection, `gsd_exec`, `gsd_exec_search`, and `gsd_resume` |
|
|
663
664
|
| `context_mode.exec_timeout_ms` | Timeout for sandboxed `gsd_exec` runs (default: 30000) |
|
|
664
665
|
| `context_mode.exec_stdout_cap_bytes` | Persisted stdout cap for `gsd_exec` output (default: 1048576) |
|
|
665
666
|
| `context_mode.exec_digest_chars` | Trailing stdout characters returned to the agent context (default: 300) |
|
|
667
|
+
| `context_mode.exec_env_allowlist` | Environment variables forwarded to sandboxed `gsd_exec` runs in addition to `PATH` and `HOME` |
|
|
666
668
|
| `verification_commands` | Array of shell commands to run after task execution (e.g., `["npm run lint", "npm run test"]`) |
|
|
667
669
|
| `verification_auto_fix` | Auto-retry on verification failures (default: true) |
|
|
668
670
|
| `verification_max_retries` | Max retries for verification failures (default: 2) |
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
89938c18ffd18359
|
|
@@ -25,6 +25,8 @@ import { normalizeRealPath } from "../paths.js";
|
|
|
25
25
|
import { decideCooldownRecovery, decideDispatchClaim, decideEngineDispatch, decideFinalizeResult, decideInfrastructureError, decideIterationErrorRecovery, decideMemoryPressure, decideModelPolicyBlocked, decideMinRequestInterval, decideWorkflowLoop, formatDispatchExceptionSummary, formatUnhandledDispatchErrorSummary, resolveUnitRequestTimestamp, shouldUseCustomEnginePath, } from "./workflow-kernel.js";
|
|
26
26
|
import { hydrateCustomVerifyRetryCounts, saveCustomVerifyRetryCounts, } from "./custom-verify-retry-store.js";
|
|
27
27
|
import { settleDispatchCompleted, settleDispatchFailed, } from "./workflow-dispatch-ledger.js";
|
|
28
|
+
import { emitOpenUnitEndForUnit } from "../crash-recovery.js";
|
|
29
|
+
import { writeUnitRuntimeRecord } from "../unit-runtime.js";
|
|
28
30
|
import { openDispatchClaim } from "./workflow-dispatch-claim.js";
|
|
29
31
|
import { completeWorkflowIteration } from "./workflow-iteration-completion.js";
|
|
30
32
|
import { createWorkflowJournalReporter } from "./workflow-journal-reporter.js";
|
|
@@ -133,6 +135,24 @@ async function enforceMinRequestInterval(s, prefs) {
|
|
|
133
135
|
await new Promise(r => setTimeout(r, decision.waitMs));
|
|
134
136
|
}
|
|
135
137
|
}
|
|
138
|
+
function closeOutCrashedUnit(s, iterData, err) {
|
|
139
|
+
const summary = formatDispatchExceptionSummary({ error: err });
|
|
140
|
+
try {
|
|
141
|
+
emitOpenUnitEndForUnit(s.basePath, iterData.unitType, iterData.unitId, "cancelled", {
|
|
142
|
+
message: summary,
|
|
143
|
+
category: "unit-exception",
|
|
144
|
+
isTransient: false,
|
|
145
|
+
});
|
|
146
|
+
writeUnitRuntimeRecord(s.basePath, iterData.unitType, iterData.unitId, s.currentUnit?.startedAt ?? Date.now(), {
|
|
147
|
+
phase: "crashed",
|
|
148
|
+
lastProgressAt: Date.now(),
|
|
149
|
+
lastProgressKind: "unit-exception",
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
catch (closeoutErr) {
|
|
153
|
+
logWarning("dispatch", `unit crash closeout failed: ${closeoutErr instanceof Error ? closeoutErr.message : String(closeoutErr)}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
136
156
|
/**
|
|
137
157
|
* Main auto-mode execution loop. Iterates: derive → dispatch → guards →
|
|
138
158
|
* runUnit → finalize → repeat. Exits when s.active becomes false or a
|
|
@@ -392,7 +412,17 @@ export async function autoLoop(ctx, pi, s, deps, options) {
|
|
|
392
412
|
}
|
|
393
413
|
// ── Unit execution (shared with dev path) ──
|
|
394
414
|
await enforceMinRequestInterval(s, prefs);
|
|
395
|
-
|
|
415
|
+
let unitPhaseResult;
|
|
416
|
+
try {
|
|
417
|
+
unitPhaseResult = await runUnitPhaseViaContract(dispatchContract, ic, iterData, loopState, undefined, unitDispatchDeps);
|
|
418
|
+
}
|
|
419
|
+
catch (err) {
|
|
420
|
+
if (err instanceof ModelPolicyDispatchBlockedError) {
|
|
421
|
+
throw err;
|
|
422
|
+
}
|
|
423
|
+
closeOutCrashedUnit(s, iterData, err);
|
|
424
|
+
throw err;
|
|
425
|
+
}
|
|
396
426
|
if (unitPhaseResult.action === "next") {
|
|
397
427
|
const requestTimestamp = resolveUnitRequestTimestamp(unitPhaseResult.data);
|
|
398
428
|
if (requestTimestamp !== undefined)
|
|
@@ -580,6 +610,7 @@ export async function autoLoop(ctx, pi, s, deps, options) {
|
|
|
580
610
|
if (err instanceof ModelPolicyDispatchBlockedError) {
|
|
581
611
|
throw err;
|
|
582
612
|
}
|
|
613
|
+
closeOutCrashedUnit(s, iterData, err);
|
|
583
614
|
dispatchSettled = settleDispatchFailed(dispatchId, formatDispatchExceptionSummary({ error: err }), {
|
|
584
615
|
markFailed: markDispatchFailed,
|
|
585
616
|
logWriteFailure: logDispatchLedgerWriteFailure,
|
|
@@ -23,12 +23,12 @@ import { existsSync, cpSync } from "node:fs";
|
|
|
23
23
|
import { logWarning, logError, _resetLogs, drainLogs, drainAndSummarize, formatForNotification, hasAnyIssues, } from "../workflow-logger.js";
|
|
24
24
|
import { gsdRoot } from "../paths.js";
|
|
25
25
|
import { atomicWriteSync } from "../atomic-write.js";
|
|
26
|
-
import { verifyExpectedArtifact, diagnoseExpectedArtifact, buildLoopRemediationSteps } from "../auto-recovery.js";
|
|
26
|
+
import { verifyExpectedArtifact, diagnoseExpectedArtifact, buildLoopRemediationSteps, refreshRecoveryDbForArtifact } from "../auto-recovery.js";
|
|
27
27
|
import { writeUnitRuntimeRecord } from "../unit-runtime.js";
|
|
28
28
|
import { withTimeout, FINALIZE_PRE_TIMEOUT_MS, FINALIZE_POST_TIMEOUT_MS } from "./finalize-timeout.js";
|
|
29
29
|
import { getEligibleSlices } from "../slice-parallel-eligibility.js";
|
|
30
30
|
import { startSliceParallel } from "../slice-parallel-orchestrator.js";
|
|
31
|
-
import { isDbAvailable, getMilestoneSlices
|
|
31
|
+
import { isDbAvailable, getMilestoneSlices } from "../gsd-db.js";
|
|
32
32
|
import { ensurePlanV2Graph, isEmptyPlanV2GraphResult, isMissingFinalizedContextResult } from "../uok/plan-v2.js";
|
|
33
33
|
import { resolveUokFlags } from "../uok/flags.js";
|
|
34
34
|
import { UokGateRunner } from "../uok/gate-runner.js";
|
|
@@ -42,12 +42,10 @@ import { getWorkflowTransportSupportError, getRequiredWorkflowToolsForAutoUnit,
|
|
|
42
42
|
function isSamePathLocal(a, b) {
|
|
43
43
|
return normalizeWorktreePathForCompare(a) === normalizeWorktreePathForCompare(b);
|
|
44
44
|
}
|
|
45
|
-
function
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return true;
|
|
50
|
-
return refreshOpenDatabaseFromDisk();
|
|
45
|
+
export function shouldDegradeEmptyWorktreeToProjectRoot(worktreeClassification, projectRootClassification) {
|
|
46
|
+
return (worktreeClassification.kind === "greenfield" &&
|
|
47
|
+
projectRootClassification.kind !== "greenfield" &&
|
|
48
|
+
projectRootClassification.kind !== "invalid-repo");
|
|
51
49
|
}
|
|
52
50
|
// ─── Session timeout auto-resume state ────────────────────────────────────────
|
|
53
51
|
let consecutiveSessionTimeouts = 0;
|
|
@@ -136,6 +134,74 @@ async function closeoutAndStop(ctx, pi, s, deps, reason) {
|
|
|
136
134
|
}
|
|
137
135
|
await deps.stopAuto(ctx, pi, reason);
|
|
138
136
|
}
|
|
137
|
+
async function stopOnPostflightRecoveryNeeded(ic, result, milestoneId) {
|
|
138
|
+
if (!result.needsManualRecovery)
|
|
139
|
+
return null;
|
|
140
|
+
const { ctx, pi, deps } = ic;
|
|
141
|
+
const reason = `Post-merge stash restore failed for milestone ${milestoneId}`;
|
|
142
|
+
ctx.ui.notify(`${reason}. Resolve the working tree before resuming auto-mode. ${result.message}`, "error");
|
|
143
|
+
await deps.stopAuto(ctx, pi, reason);
|
|
144
|
+
return { action: "break", reason: "postflight-stash-restore-failed" };
|
|
145
|
+
}
|
|
146
|
+
async function restorePreflightStashOrStop(ic, preflight, milestoneId) {
|
|
147
|
+
if (!preflight.stashPushed)
|
|
148
|
+
return null;
|
|
149
|
+
const { ctx, s, deps } = ic;
|
|
150
|
+
const result = deps.postflightPopStash(s.originalBasePath || s.basePath, milestoneId, preflight.stashMarker, ctx.ui.notify.bind(ctx.ui));
|
|
151
|
+
return stopOnPostflightRecoveryNeeded(ic, result, milestoneId);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Run a milestone merge surrounded by preflight stash + always-on postflight
|
|
155
|
+
* pop. The previous code popped the stash only after a successful merge, which
|
|
156
|
+
* leaked `gsd-preflight-stash:M00x:*` entries whenever `mergeAndExit` threw —
|
|
157
|
+
* leaving the user's pre-merge working tree silently stashed away after a
|
|
158
|
+
* merge-conflict or other merge error. This helper restores the stash on
|
|
159
|
+
* every exit path, then surfaces the merge or stash failure (in priority
|
|
160
|
+
* order) as the loop's stop reason.
|
|
161
|
+
*
|
|
162
|
+
* Returns a `break` action when auto-mode must stop, or `null` when the merge
|
|
163
|
+
* succeeded and the stash (if any) was restored cleanly.
|
|
164
|
+
*/
|
|
165
|
+
export async function _runMilestoneMergeWithStashRestore(ic, milestoneId) {
|
|
166
|
+
const { ctx, pi, s, deps } = ic;
|
|
167
|
+
const preflight = deps.preflightCleanRoot(s.originalBasePath || s.basePath, milestoneId, ctx.ui.notify.bind(ctx.ui));
|
|
168
|
+
let mergeError = null;
|
|
169
|
+
try {
|
|
170
|
+
deps.resolver.mergeAndExit(milestoneId, ctx.ui);
|
|
171
|
+
s.milestoneMergedInPhases = true;
|
|
172
|
+
}
|
|
173
|
+
catch (mergeErr) {
|
|
174
|
+
mergeError = mergeErr;
|
|
175
|
+
}
|
|
176
|
+
// Always attempt to restore the stashed working tree, even on merge error.
|
|
177
|
+
// postflightPopStash itself does not throw; failures surface via the
|
|
178
|
+
// PostflightResult.needsManualRecovery flag.
|
|
179
|
+
let stashResult = null;
|
|
180
|
+
if (preflight.stashPushed) {
|
|
181
|
+
stashResult = deps.postflightPopStash(s.originalBasePath || s.basePath, milestoneId, preflight.stashMarker, ctx.ui.notify.bind(ctx.ui));
|
|
182
|
+
}
|
|
183
|
+
// Merge failure takes priority over stash recovery — the merge is the
|
|
184
|
+
// authoritative gate. If the stash also needed manual recovery, the user
|
|
185
|
+
// already saw the postflightPopStash notify above.
|
|
186
|
+
if (mergeError) {
|
|
187
|
+
if (mergeError instanceof MergeConflictError) {
|
|
188
|
+
ctx.ui.notify(`Merge conflict: ${mergeError.conflictedFiles.join(", ")}. Resolve conflicts manually and run /gsd auto to resume.`, "error");
|
|
189
|
+
await deps.stopAuto(ctx, pi, `Merge conflict on milestone ${milestoneId}`);
|
|
190
|
+
return { action: "break", reason: "merge-conflict" };
|
|
191
|
+
}
|
|
192
|
+
logError("engine", "Milestone merge failed with non-conflict error", {
|
|
193
|
+
milestone: milestoneId,
|
|
194
|
+
error: String(mergeError),
|
|
195
|
+
});
|
|
196
|
+
ctx.ui.notify(`Merge failed: ${mergeError instanceof Error ? mergeError.message : String(mergeError)}. Resolve and run /gsd auto to resume.`, "error");
|
|
197
|
+
await deps.stopAuto(ctx, pi, `Merge error on milestone ${milestoneId}: ${String(mergeError)}`);
|
|
198
|
+
return { action: "break", reason: "merge-failed" };
|
|
199
|
+
}
|
|
200
|
+
if (stashResult) {
|
|
201
|
+
return stopOnPostflightRecoveryNeeded(ic, stashResult, milestoneId);
|
|
202
|
+
}
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
139
205
|
async function emitCancelledUnitEnd(ic, unitType, unitId, unitStartSeq, errorContext) {
|
|
140
206
|
ic.deps.emitJournalEvent({
|
|
141
207
|
ts: new Date().toISOString(),
|
|
@@ -477,28 +543,12 @@ export async function runPreDispatch(ic, loopState) {
|
|
|
477
543
|
s.unitLifetimeDispatches.clear();
|
|
478
544
|
loopState.recentUnits.length = 0;
|
|
479
545
|
loopState.stuckRecoveryAttempts = 0;
|
|
480
|
-
// Worktree lifecycle on milestone transition — merge current, enter next
|
|
481
|
-
// #2909: preflight
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
catch (mergeErr) {
|
|
487
|
-
if (mergeErr instanceof MergeConflictError) {
|
|
488
|
-
// Real code conflicts — stop the loop instead of retrying forever (#2330)
|
|
489
|
-
ctx.ui.notify(`Merge conflict: ${mergeErr.conflictedFiles.join(", ")}. Resolve conflicts manually and run /gsd auto to resume.`, "error");
|
|
490
|
-
await deps.stopAuto(ctx, pi, `Merge conflict on milestone ${s.currentMilestoneId}`);
|
|
491
|
-
return { action: "break", reason: "merge-conflict" };
|
|
492
|
-
}
|
|
493
|
-
// Non-conflict merge errors — stop auto to avoid advancing with unmerged work
|
|
494
|
-
logError("engine", "Milestone merge failed with non-conflict error", { milestone: s.currentMilestoneId, error: String(mergeErr) });
|
|
495
|
-
ctx.ui.notify(`Merge failed: ${mergeErr instanceof Error ? mergeErr.message : String(mergeErr)}. Resolve and run /gsd auto to resume.`, "error");
|
|
496
|
-
await deps.stopAuto(ctx, pi, `Merge error on milestone ${s.currentMilestoneId}: ${String(mergeErr)}`);
|
|
497
|
-
return { action: "break", reason: "merge-failed" };
|
|
498
|
-
}
|
|
499
|
-
// #2909: postflight — restore stashed changes after successful merge
|
|
500
|
-
if (preflightTransition.stashPushed) {
|
|
501
|
-
deps.postflightPopStash(s.originalBasePath || s.basePath, s.currentMilestoneId, preflightTransition.stashMarker, ctx.ui.notify.bind(ctx.ui));
|
|
546
|
+
// Worktree lifecycle on milestone transition — merge current, enter next.
|
|
547
|
+
// #2909 / #5538-followup: preflight stash + always-on postflight pop.
|
|
548
|
+
{
|
|
549
|
+
const stop = await _runMilestoneMergeWithStashRestore(ic, s.currentMilestoneId);
|
|
550
|
+
if (stop)
|
|
551
|
+
return stop;
|
|
502
552
|
}
|
|
503
553
|
// PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
|
|
504
554
|
deps.invalidateAllCaches();
|
|
@@ -552,30 +602,12 @@ export async function runPreDispatch(ic, loopState) {
|
|
|
552
602
|
}
|
|
553
603
|
const incomplete = state.registry.filter((m) => m.status !== "complete" && m.status !== "parked");
|
|
554
604
|
if (incomplete.length === 0 && state.registry.length > 0) {
|
|
555
|
-
// All milestones complete — merge milestone branch before stopping
|
|
605
|
+
// All milestones complete — merge milestone branch before stopping.
|
|
556
606
|
if (s.currentMilestoneId) {
|
|
557
|
-
// #2909: preflight
|
|
558
|
-
const
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
// Prevent stopAuto from attempting the same merge (#2645)
|
|
562
|
-
s.milestoneMergedInPhases = true;
|
|
563
|
-
}
|
|
564
|
-
catch (mergeErr) {
|
|
565
|
-
if (mergeErr instanceof MergeConflictError) {
|
|
566
|
-
ctx.ui.notify(`Merge conflict: ${mergeErr.conflictedFiles.join(", ")}. Resolve conflicts manually and run /gsd auto to resume.`, "error");
|
|
567
|
-
await deps.stopAuto(ctx, pi, `Merge conflict on milestone ${s.currentMilestoneId}`);
|
|
568
|
-
return { action: "break", reason: "merge-conflict" };
|
|
569
|
-
}
|
|
570
|
-
logError("engine", "Milestone merge failed with non-conflict error", { milestone: s.currentMilestoneId, error: String(mergeErr) });
|
|
571
|
-
ctx.ui.notify(`Merge failed: ${mergeErr instanceof Error ? mergeErr.message : String(mergeErr)}. Resolve and run /gsd auto to resume.`, "error");
|
|
572
|
-
await deps.stopAuto(ctx, pi, `Merge error on milestone ${s.currentMilestoneId}: ${String(mergeErr)}`);
|
|
573
|
-
return { action: "break", reason: "merge-failed" };
|
|
574
|
-
}
|
|
575
|
-
// #2909: postflight — restore stashed changes after successful merge
|
|
576
|
-
if (preflightAllComplete.stashPushed) {
|
|
577
|
-
deps.postflightPopStash(s.originalBasePath || s.basePath, s.currentMilestoneId, preflightAllComplete.stashMarker, ctx.ui.notify.bind(ctx.ui));
|
|
578
|
-
}
|
|
607
|
+
// #2909 / #5538-followup: preflight stash + always-on postflight pop.
|
|
608
|
+
const stop = await _runMilestoneMergeWithStashRestore(ic, s.currentMilestoneId);
|
|
609
|
+
if (stop)
|
|
610
|
+
return stop;
|
|
579
611
|
// PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
|
|
580
612
|
}
|
|
581
613
|
deps.sendDesktopNotification("GSD", "All milestones complete!", "success", "milestone", basename(s.originalBasePath || s.basePath));
|
|
@@ -638,30 +670,12 @@ export async function runPreDispatch(ic, loopState) {
|
|
|
638
670
|
}
|
|
639
671
|
// Terminal: complete
|
|
640
672
|
if (state.phase === "complete") {
|
|
641
|
-
// Milestone merge on complete (before closeout so branch state is clean)
|
|
673
|
+
// Milestone merge on complete (before closeout so branch state is clean).
|
|
642
674
|
if (s.currentMilestoneId) {
|
|
643
|
-
// #2909: preflight
|
|
644
|
-
const
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
// Prevent stopAuto from attempting the same merge (#2645)
|
|
648
|
-
s.milestoneMergedInPhases = true;
|
|
649
|
-
}
|
|
650
|
-
catch (mergeErr) {
|
|
651
|
-
if (mergeErr instanceof MergeConflictError) {
|
|
652
|
-
ctx.ui.notify(`Merge conflict: ${mergeErr.conflictedFiles.join(", ")}. Resolve conflicts manually and run /gsd auto to resume.`, "error");
|
|
653
|
-
await deps.stopAuto(ctx, pi, `Merge conflict on milestone ${s.currentMilestoneId}`);
|
|
654
|
-
return { action: "break", reason: "merge-conflict" };
|
|
655
|
-
}
|
|
656
|
-
logError("engine", "Milestone merge failed with non-conflict error", { milestone: s.currentMilestoneId, error: String(mergeErr) });
|
|
657
|
-
ctx.ui.notify(`Merge failed: ${mergeErr instanceof Error ? mergeErr.message : String(mergeErr)}. Resolve and run /gsd auto to resume.`, "error");
|
|
658
|
-
await deps.stopAuto(ctx, pi, `Merge error on milestone ${s.currentMilestoneId}: ${String(mergeErr)}`);
|
|
659
|
-
return { action: "break", reason: "merge-failed" };
|
|
660
|
-
}
|
|
661
|
-
// #2909: postflight — restore stashed changes after successful merge
|
|
662
|
-
if (preflightComplete.stashPushed) {
|
|
663
|
-
deps.postflightPopStash(s.originalBasePath || s.basePath, s.currentMilestoneId, preflightComplete.stashMarker, ctx.ui.notify.bind(ctx.ui));
|
|
664
|
-
}
|
|
675
|
+
// #2909 / #5538-followup: preflight stash + always-on postflight pop.
|
|
676
|
+
const stop = await _runMilestoneMergeWithStashRestore(ic, s.currentMilestoneId);
|
|
677
|
+
if (stop)
|
|
678
|
+
return stop;
|
|
665
679
|
// PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
|
|
666
680
|
}
|
|
667
681
|
deps.sendDesktopNotification("GSD", `Milestone ${mid} complete!`, "success", "milestone", basename(s.originalBasePath || s.basePath));
|
|
@@ -754,6 +768,48 @@ export async function runDispatch(ic, preData, loopState) {
|
|
|
754
768
|
let unitId = dispatchResult.unitId;
|
|
755
769
|
let prompt = dispatchResult.prompt;
|
|
756
770
|
const pauseAfterUatDispatch = dispatchResult.pauseAfterDispatch ?? false;
|
|
771
|
+
// Resolve hooks and prior-slice gating before health/stuck accounting so
|
|
772
|
+
// those checks run against the final dispatch unit.
|
|
773
|
+
const preDispatchResult = deps.runPreDispatchHooks(unitType, unitId, prompt, s.basePath);
|
|
774
|
+
if (preDispatchResult.firedHooks.length > 0) {
|
|
775
|
+
ctx.ui.notify(`Pre-dispatch hook${preDispatchResult.firedHooks.length > 1 ? "s" : ""}: ${preDispatchResult.firedHooks.join(", ")}`, "info");
|
|
776
|
+
deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: ic.nextSeq(), eventType: "pre-dispatch-hook", data: { firedHooks: preDispatchResult.firedHooks, action: preDispatchResult.action } });
|
|
777
|
+
}
|
|
778
|
+
if (preDispatchResult.action === "skip") {
|
|
779
|
+
ctx.ui.notify(`Skipping ${unitType} ${unitId} (pre-dispatch hook).`, "info");
|
|
780
|
+
await new Promise((r) => setImmediate(r));
|
|
781
|
+
return { action: "continue" };
|
|
782
|
+
}
|
|
783
|
+
if (preDispatchResult.action === "replace") {
|
|
784
|
+
prompt = preDispatchResult.prompt ?? prompt;
|
|
785
|
+
if (preDispatchResult.unitType)
|
|
786
|
+
unitType = preDispatchResult.unitType;
|
|
787
|
+
}
|
|
788
|
+
else if (preDispatchResult.prompt) {
|
|
789
|
+
prompt = preDispatchResult.prompt;
|
|
790
|
+
}
|
|
791
|
+
const guardBasePath = _resolveDispatchGuardBasePath(s);
|
|
792
|
+
const priorSliceBlocker = deps.getPriorSliceCompletionBlocker(guardBasePath, deps.getMainBranch(guardBasePath), unitType, unitId);
|
|
793
|
+
if (priorSliceBlocker) {
|
|
794
|
+
await deps.stopAuto(ctx, pi, priorSliceBlocker);
|
|
795
|
+
debugLog("autoLoop", { phase: "exit", reason: "prior-slice-blocker" });
|
|
796
|
+
return { action: "break", reason: "prior-slice-blocker" };
|
|
797
|
+
}
|
|
798
|
+
// Execute-task needs a real writable checkout. The same health check also
|
|
799
|
+
// exists in runUnitPhase, but the stuck-window detector runs before that
|
|
800
|
+
// phase. Check here too so repeated derivations of a broken worktree stop
|
|
801
|
+
// with the actionable worktree error instead of the generic stuck-loop error.
|
|
802
|
+
if (s.basePath && unitType === "execute-task") {
|
|
803
|
+
const gitMarker = join(s.basePath, ".git");
|
|
804
|
+
const hasGit = deps.existsSync(gitMarker);
|
|
805
|
+
if (!hasGit) {
|
|
806
|
+
const msg = `Worktree health check failed: ${s.basePath} has no .git — refusing to dispatch ${unitType} ${unitId}`;
|
|
807
|
+
debugLog("autoLoop", { phase: "dispatch-worktree-health-fail", basePath: s.basePath, hasGit });
|
|
808
|
+
ctx.ui.notify(msg, "error");
|
|
809
|
+
await deps.stopAuto(ctx, pi, msg);
|
|
810
|
+
return { action: "break", reason: "worktree-invalid" };
|
|
811
|
+
}
|
|
812
|
+
}
|
|
757
813
|
// ── Sliding-window stuck detection with graduated recovery ──
|
|
758
814
|
const derivedKey = `${unitType}/${unitId}`;
|
|
759
815
|
// Always record this dispatch in the sliding window so detectStuck() has
|
|
@@ -795,11 +851,18 @@ export async function runDispatch(ic, preData, loopState) {
|
|
|
795
851
|
level: 1,
|
|
796
852
|
action: "artifact-found",
|
|
797
853
|
});
|
|
798
|
-
|
|
799
|
-
if (!
|
|
800
|
-
ctx.ui.notify(
|
|
854
|
+
const recoveryDb = refreshRecoveryDbForArtifact(unitType, unitId);
|
|
855
|
+
if (!recoveryDb.ok) {
|
|
856
|
+
ctx.ui.notify(recoveryDb.fatal
|
|
857
|
+
? `${recoveryDb.message} Pausing auto-mode for manual recovery.`
|
|
858
|
+
: `${recoveryDb.message} Keeping stuck state for retry.`, "warning");
|
|
859
|
+
if (recoveryDb.fatal) {
|
|
860
|
+
await deps.pauseAuto(ctx, pi);
|
|
861
|
+
return { action: "break", reason: recoveryDb.reason };
|
|
862
|
+
}
|
|
801
863
|
return { action: "continue" };
|
|
802
864
|
}
|
|
865
|
+
ctx.ui.notify(`Stuck recovery: artifact for ${unitType} ${unitId} found on disk. Invalidating caches.`, "info");
|
|
803
866
|
deps.invalidateAllCaches();
|
|
804
867
|
loopState.recentUnits.length = 0;
|
|
805
868
|
loopState.stuckRecoveryAttempts = 0;
|
|
@@ -818,13 +881,20 @@ export async function runDispatch(ic, preData, loopState) {
|
|
|
818
881
|
level: 2,
|
|
819
882
|
action: "artifact-found",
|
|
820
883
|
});
|
|
821
|
-
|
|
822
|
-
if (
|
|
884
|
+
const recoveryDb = refreshRecoveryDbForArtifact(unitType, unitId);
|
|
885
|
+
if (recoveryDb.ok) {
|
|
886
|
+
ctx.ui.notify(`Stuck recovery: artifact for ${unitType} ${unitId} found on disk after cache invalidation. Continuing.`, "info");
|
|
823
887
|
loopState.recentUnits.length = 0;
|
|
824
888
|
loopState.stuckRecoveryAttempts = 0;
|
|
825
889
|
return { action: "continue" };
|
|
826
890
|
}
|
|
827
|
-
ctx.ui.notify(
|
|
891
|
+
ctx.ui.notify(recoveryDb.fatal
|
|
892
|
+
? `${recoveryDb.message} Pausing auto-mode for manual recovery.`
|
|
893
|
+
: `${recoveryDb.message} Stopping for manual recovery.`, "warning");
|
|
894
|
+
if (recoveryDb.fatal) {
|
|
895
|
+
await deps.pauseAuto(ctx, pi);
|
|
896
|
+
return { action: "break", reason: recoveryDb.reason };
|
|
897
|
+
}
|
|
828
898
|
}
|
|
829
899
|
debugLog("autoLoop", {
|
|
830
900
|
phase: "stuck-detected",
|
|
@@ -856,32 +926,6 @@ export async function runDispatch(ic, preData, loopState) {
|
|
|
856
926
|
}
|
|
857
927
|
}
|
|
858
928
|
}
|
|
859
|
-
// Pre-dispatch hooks
|
|
860
|
-
const preDispatchResult = deps.runPreDispatchHooks(unitType, unitId, prompt, s.basePath);
|
|
861
|
-
if (preDispatchResult.firedHooks.length > 0) {
|
|
862
|
-
ctx.ui.notify(`Pre-dispatch hook${preDispatchResult.firedHooks.length > 1 ? "s" : ""}: ${preDispatchResult.firedHooks.join(", ")}`, "info");
|
|
863
|
-
deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: ic.nextSeq(), eventType: "pre-dispatch-hook", data: { firedHooks: preDispatchResult.firedHooks, action: preDispatchResult.action } });
|
|
864
|
-
}
|
|
865
|
-
if (preDispatchResult.action === "skip") {
|
|
866
|
-
ctx.ui.notify(`Skipping ${unitType} ${unitId} (pre-dispatch hook).`, "info");
|
|
867
|
-
await new Promise((r) => setImmediate(r));
|
|
868
|
-
return { action: "continue" };
|
|
869
|
-
}
|
|
870
|
-
if (preDispatchResult.action === "replace") {
|
|
871
|
-
prompt = preDispatchResult.prompt ?? prompt;
|
|
872
|
-
if (preDispatchResult.unitType)
|
|
873
|
-
unitType = preDispatchResult.unitType;
|
|
874
|
-
}
|
|
875
|
-
else if (preDispatchResult.prompt) {
|
|
876
|
-
prompt = preDispatchResult.prompt;
|
|
877
|
-
}
|
|
878
|
-
const guardBasePath = _resolveDispatchGuardBasePath(s);
|
|
879
|
-
const priorSliceBlocker = deps.getPriorSliceCompletionBlocker(guardBasePath, deps.getMainBranch(guardBasePath), unitType, unitId);
|
|
880
|
-
if (priorSliceBlocker) {
|
|
881
|
-
await deps.stopAuto(ctx, pi, priorSliceBlocker);
|
|
882
|
-
debugLog("autoLoop", { phase: "exit", reason: "prior-slice-blocker" });
|
|
883
|
-
return { action: "break", reason: "prior-slice-blocker" };
|
|
884
|
-
}
|
|
885
929
|
return {
|
|
886
930
|
action: "next",
|
|
887
931
|
data: {
|
|
@@ -1111,6 +1155,25 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
1111
1155
|
}
|
|
1112
1156
|
}
|
|
1113
1157
|
else if (projectClassification.kind === "greenfield") {
|
|
1158
|
+
const projectRoot = s.canonicalProjectRoot;
|
|
1159
|
+
if (!isSamePathLocal(s.basePath, projectRoot)) {
|
|
1160
|
+
const projectRootClassification = classifyProject(projectRoot);
|
|
1161
|
+
if (shouldDegradeEmptyWorktreeToProjectRoot(projectClassification, projectRootClassification)) {
|
|
1162
|
+
debugLog("runUnitPhase", {
|
|
1163
|
+
phase: "worktree-health-degrade-to-project-root",
|
|
1164
|
+
worktreePath: s.basePath,
|
|
1165
|
+
projectRoot,
|
|
1166
|
+
worktreeClassification: projectClassification,
|
|
1167
|
+
projectRootClassification,
|
|
1168
|
+
});
|
|
1169
|
+
ctx.ui.notify(`Warning: ${s.basePath} has no project content, but ${projectRoot} does. Continuing in project root because the milestone worktree cannot represent untracked project files.`, "warning");
|
|
1170
|
+
s.basePath = projectRoot;
|
|
1171
|
+
s.isolationDegraded = true;
|
|
1172
|
+
projectClassification = projectRootClassification;
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
if (projectClassification.kind === "greenfield") {
|
|
1114
1177
|
debugLog("runUnitPhase", { phase: "worktree-health-greenfield", basePath: s.basePath, classification: projectClassification });
|
|
1115
1178
|
ctx.ui.notify(`Warning: ${s.basePath} has no project content yet — proceeding as greenfield project`, "warning");
|
|
1116
1179
|
}
|
|
@@ -1672,6 +1735,13 @@ export async function runFinalize(ic, iterData, loopState, sidecarItem) {
|
|
|
1672
1735
|
}
|
|
1673
1736
|
// Both pre and post verification completed without timeout — reset counter
|
|
1674
1737
|
loopState.consecutiveFinalizeTimeouts = 0;
|
|
1738
|
+
if (preUnitSnapshot) {
|
|
1739
|
+
writeUnitRuntimeRecord(s.basePath, preUnitSnapshot.type, preUnitSnapshot.id, preUnitSnapshot.startedAt, {
|
|
1740
|
+
phase: "finalized",
|
|
1741
|
+
lastProgressAt: Date.now(),
|
|
1742
|
+
lastProgressKind: "finalize-success",
|
|
1743
|
+
});
|
|
1744
|
+
}
|
|
1675
1745
|
s.currentUnit = null;
|
|
1676
1746
|
clearCurrentPhase();
|
|
1677
1747
|
// Surface accumulated workflow-logger issues for this unit to the user.
|
|
@@ -17,6 +17,7 @@ import { bumpTurnGeneration } from "./turn-epoch.js";
|
|
|
17
17
|
// scoped pendingResolve + pendingAgentEndQueue pattern.
|
|
18
18
|
let _currentResolve = null;
|
|
19
19
|
let _sessionSwitchInFlight = false;
|
|
20
|
+
let _pendingSwitchCancellation = null;
|
|
20
21
|
// ─── Setters (needed for cross-module mutation) ─────────────────────────────
|
|
21
22
|
export function _setCurrentResolve(fn) {
|
|
22
23
|
_currentResolve = fn;
|
|
@@ -27,6 +28,11 @@ export function _setSessionSwitchInFlight(v) {
|
|
|
27
28
|
export function _clearCurrentResolve() {
|
|
28
29
|
_currentResolve = null;
|
|
29
30
|
}
|
|
31
|
+
export function _consumePendingSwitchCancellation() {
|
|
32
|
+
const pending = _pendingSwitchCancellation;
|
|
33
|
+
_pendingSwitchCancellation = null;
|
|
34
|
+
return pending;
|
|
35
|
+
}
|
|
30
36
|
// ─── resolveAgentEnd ─────────────────────────────────────────────────────────
|
|
31
37
|
/**
|
|
32
38
|
* Called from the agent_end event handler in index.ts to resolve the
|
|
@@ -90,8 +96,18 @@ export function resolveAgentEndCancelled(errorContext) {
|
|
|
90
96
|
debugLog("resolveAgentEndCancelled", { status: "resolving-cancelled" });
|
|
91
97
|
const r = _currentResolve;
|
|
92
98
|
_currentResolve = null;
|
|
99
|
+
_pendingSwitchCancellation = null;
|
|
93
100
|
r({ status: "cancelled", ...(errorContext ? { errorContext } : {}) });
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
if (_sessionSwitchInFlight) {
|
|
104
|
+
bumpTurnGeneration(`cancelled-during-switch:${errorContext?.category ?? "unknown"}`);
|
|
105
|
+
_pendingSwitchCancellation = errorContext ? { errorContext } : {};
|
|
106
|
+
debugLog("resolveAgentEndCancelled", { status: "queued-during-switch" });
|
|
107
|
+
return false;
|
|
94
108
|
}
|
|
109
|
+
debugLog("resolveAgentEndCancelled", { status: "no-pending-resolve" });
|
|
110
|
+
return false;
|
|
95
111
|
}
|
|
96
112
|
// ─── resetPendingResolve (test helper) ───────────────────────────────────────
|
|
97
113
|
/**
|
|
@@ -101,6 +117,7 @@ export function resolveAgentEndCancelled(errorContext) {
|
|
|
101
117
|
export function _resetPendingResolve() {
|
|
102
118
|
_currentResolve = null;
|
|
103
119
|
_sessionSwitchInFlight = false;
|
|
120
|
+
_pendingSwitchCancellation = null;
|
|
104
121
|
}
|
|
105
122
|
export function _hasPendingResolveForTest() {
|
|
106
123
|
return _currentResolve !== null;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// GSD-2 + src/resources/extensions/gsd/auto/run-unit.ts - Runs one GSD auto-mode unit from session creation through agent completion.
|
|
2
2
|
import { NEW_SESSION_TIMEOUT_MS } from "./session.js";
|
|
3
|
-
import { _clearCurrentResolve, _setCurrentResolve, _setSessionSwitchInFlight } from "./resolve.js";
|
|
3
|
+
import { _clearCurrentResolve, _consumePendingSwitchCancellation, _setCurrentResolve, _setSessionSwitchInFlight, } from "./resolve.js";
|
|
4
4
|
import { getCurrentTurnGeneration, runWithTurnGeneration, } from "./turn-epoch.js";
|
|
5
5
|
import { debugLog } from "../debug-logger.js";
|
|
6
6
|
import { logWarning } from "../workflow-logger.js";
|
|
@@ -19,43 +19,21 @@ let sessionSwitchGeneration = 0;
|
|
|
19
19
|
*/
|
|
20
20
|
export async function runUnit(ctx, pi, s, unitType, unitId, prompt) {
|
|
21
21
|
debugLog("runUnit", { phase: "start", unitType, unitId });
|
|
22
|
-
// Ensure cwd matches basePath BEFORE newSession() captures it. The new
|
|
23
|
-
// session reads process.cwd() during construction to anchor its tool
|
|
24
|
-
// runtime and system prompt; if cwd has drifted (async_bash, background
|
|
25
|
-
// jobs, prior unit cleanup), the session would otherwise be rooted to
|
|
26
|
-
// the wrong directory. Must be synchronous — no awaits between chdir
|
|
27
|
-
// and newSession (#1389, #4762 follow-up).
|
|
28
|
-
try {
|
|
29
|
-
if (process.cwd() !== s.basePath) {
|
|
30
|
-
process.chdir(s.basePath);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
catch (e) {
|
|
34
|
-
const msg = `Failed to chdir to basePath before newSession (basePath: ${s.basePath}): ${String(e)}`;
|
|
35
|
-
logWarning("engine", msg, { basePath: s.basePath, error: String(e) });
|
|
36
|
-
return {
|
|
37
|
-
status: "cancelled",
|
|
38
|
-
errorContext: {
|
|
39
|
-
message: msg,
|
|
40
|
-
category: "session-failed",
|
|
41
|
-
isTransient: true,
|
|
42
|
-
},
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
22
|
// ── Session creation with timeout ──
|
|
46
23
|
debugLog("runUnit", { phase: "session-create", unitType, unitId });
|
|
47
24
|
let sessionResult;
|
|
48
25
|
let sessionTimeoutHandle;
|
|
49
26
|
const mySessionSwitchGeneration = ++sessionSwitchGeneration;
|
|
50
|
-
// #3731: Cancellation controller for newSession(). When
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
// it from capturing the (now-root) process.cwd() and rebuilding the tool
|
|
54
|
-
// runtime with the wrong cwd.
|
|
27
|
+
// #3731: Cancellation controller for newSession(). When session creation
|
|
28
|
+
// times out, abort before a late session switch can rebuild the tool runtime
|
|
29
|
+
// against a stale workspace root.
|
|
55
30
|
const sessionAbortController = new AbortController();
|
|
56
31
|
_setSessionSwitchInFlight(true);
|
|
57
32
|
try {
|
|
58
|
-
const sessionPromise = s.cmdCtx.newSession({
|
|
33
|
+
const sessionPromise = s.cmdCtx.newSession({
|
|
34
|
+
abortSignal: sessionAbortController.signal,
|
|
35
|
+
workspaceRoot: s.basePath,
|
|
36
|
+
}).finally(() => {
|
|
59
37
|
if (sessionSwitchGeneration === mySessionSwitchGeneration) {
|
|
60
38
|
_setSessionSwitchInFlight(false);
|
|
61
39
|
}
|
|
@@ -71,6 +49,7 @@ export async function runUnit(ctx, pi, s, unitType, unitId, prompt) {
|
|
|
71
49
|
catch (sessionErr) {
|
|
72
50
|
if (sessionTimeoutHandle)
|
|
73
51
|
clearTimeout(sessionTimeoutHandle);
|
|
52
|
+
_consumePendingSwitchCancellation();
|
|
74
53
|
const msg = sessionErr instanceof Error ? sessionErr.message : String(sessionErr);
|
|
75
54
|
debugLog("runUnit", {
|
|
76
55
|
phase: "session-error",
|
|
@@ -83,15 +62,18 @@ export async function runUnit(ctx, pi, s, unitType, unitId, prompt) {
|
|
|
83
62
|
if (sessionTimeoutHandle)
|
|
84
63
|
clearTimeout(sessionTimeoutHandle);
|
|
85
64
|
if (sessionResult.cancelled) {
|
|
65
|
+
_consumePendingSwitchCancellation();
|
|
86
66
|
debugLog("runUnit-session-timeout", { unitType, unitId });
|
|
87
67
|
return { status: "cancelled", errorContext: { message: "Session creation timed out", category: "timeout", isTransient: true } };
|
|
88
68
|
}
|
|
89
69
|
if (!s.active) {
|
|
70
|
+
_consumePendingSwitchCancellation();
|
|
90
71
|
return { status: "cancelled" };
|
|
91
72
|
}
|
|
92
73
|
if (s.currentUnitModel && typeof pi.setModel === "function") {
|
|
93
74
|
const restored = await pi.setModel(s.currentUnitModel, { persist: false });
|
|
94
75
|
if (!restored) {
|
|
76
|
+
_consumePendingSwitchCancellation();
|
|
95
77
|
const message = `Failed to restore configured model ${s.currentUnitModel.provider}/${s.currentUnitModel.id} after session creation`;
|
|
96
78
|
ctx.ui.notify(`${message}. Cancelling unit before dispatch.`, "warning");
|
|
97
79
|
return {
|
|
@@ -111,6 +93,14 @@ export async function runUnit(ctx, pi, s, unitType, unitId, prompt) {
|
|
|
111
93
|
const unitPromise = new Promise((resolve) => {
|
|
112
94
|
_setCurrentResolve(resolve);
|
|
113
95
|
});
|
|
96
|
+
const pendingSwitchCancellation = _consumePendingSwitchCancellation();
|
|
97
|
+
if (pendingSwitchCancellation) {
|
|
98
|
+
_clearCurrentResolve();
|
|
99
|
+
return {
|
|
100
|
+
status: "cancelled",
|
|
101
|
+
...(pendingSwitchCancellation.errorContext ? { errorContext: pendingSwitchCancellation.errorContext } : {}),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
114
104
|
// ── Provider request-readiness pre-check (#4555) ──
|
|
115
105
|
// Verify the provider can accept requests before dispatching. If the token
|
|
116
106
|
// has expired since bootstrap, return cancelled immediately so the unit is
|