gsd-pi 2.80.0-dev.e146beb20 → 2.80.0-dev.e6c48c3af

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.
Files changed (218) hide show
  1. package/README.md +4 -2
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/extensions/gsd/auto/phases.js +59 -21
  4. package/dist/resources/extensions/gsd/auto/resolve.js +17 -0
  5. package/dist/resources/extensions/gsd/auto/run-unit.js +17 -2
  6. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +1 -1
  7. package/dist/resources/extensions/gsd/auto-prompts.js +13 -1
  8. package/dist/resources/extensions/gsd/auto-recovery.js +43 -1
  9. package/dist/resources/extensions/gsd/auto-supervisor.js +8 -1
  10. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +2 -2
  11. package/dist/resources/extensions/gsd/auto.js +84 -5
  12. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +21 -2
  13. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +27 -20
  14. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +75 -4
  15. package/dist/resources/extensions/gsd/clean-root-preflight.js +24 -6
  16. package/dist/resources/extensions/gsd/context-budget.js +37 -2
  17. package/dist/resources/extensions/gsd/db/unit-dispatches.js +39 -0
  18. package/dist/resources/extensions/gsd/db-base-schema.js +4 -2
  19. package/dist/resources/extensions/gsd/db-migration-steps.js +6 -0
  20. package/dist/resources/extensions/gsd/git-service.js +36 -4
  21. package/dist/resources/extensions/gsd/gsd-db.js +46 -13
  22. package/dist/resources/extensions/gsd/guided-flow.js +33 -4
  23. package/dist/resources/extensions/gsd/memory-store.js +69 -12
  24. package/dist/resources/extensions/gsd/migrate/command.js +40 -1
  25. package/dist/resources/extensions/gsd/migration-auto-check.js +87 -0
  26. package/dist/resources/extensions/gsd/pre-execution-checks.js +7 -0
  27. package/dist/resources/extensions/gsd/prompt-loader.js +28 -2
  28. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +16 -13
  29. package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
  30. package/dist/resources/extensions/gsd/prompts/quick-task.md +1 -5
  31. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
  32. package/dist/resources/extensions/gsd/quick.js +34 -2
  33. package/dist/resources/extensions/gsd/tools/context-mode-tool-result.js +15 -0
  34. package/dist/resources/extensions/gsd/tools/exec-search-tool.js +5 -0
  35. package/dist/resources/extensions/gsd/tools/exec-tool.js +3 -15
  36. package/dist/resources/extensions/gsd/tools/memory-tools.js +1 -0
  37. package/dist/resources/extensions/gsd/tools/resume-tool.js +5 -0
  38. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +1 -1
  39. package/dist/resources/extensions/gsd/unit-context-composer.js +12 -3
  40. package/dist/resources/extensions/gsd/unit-runtime.js +11 -0
  41. package/dist/resources/extensions/gsd/worktree-resolver.js +33 -17
  42. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  43. package/dist/web/standalone/.next/BUILD_ID +1 -1
  44. package/dist/web/standalone/.next/app-path-routes-manifest.json +16 -16
  45. package/dist/web/standalone/.next/build-manifest.json +2 -2
  46. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  47. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  48. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  56. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/index.html +1 -1
  64. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app-paths-manifest.json +16 -16
  71. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  72. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  73. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  74. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  75. package/package.json +3 -3
  76. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  77. package/packages/mcp-server/dist/workflow-tools.js +22 -17
  78. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  79. package/packages/mcp-server/src/workflow-tools.test.ts +75 -2
  80. package/packages/mcp-server/src/workflow-tools.ts +30 -16
  81. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  82. package/packages/native/tsconfig.tsbuildinfo +1 -1
  83. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +32 -0
  84. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
  85. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js +15 -0
  86. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js.map +1 -1
  87. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +2 -0
  88. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  89. package/packages/pi-coding-agent/dist/core/agent-session.js +12 -3
  90. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  91. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +3 -1
  92. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
  93. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts +11 -0
  94. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
  95. package/packages/pi-coding-agent/dist/core/compaction/compaction.js +9 -0
  96. package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
  97. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts +2 -0
  98. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts.map +1 -0
  99. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js +103 -0
  100. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js.map +1 -0
  101. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +3 -0
  102. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  103. package/packages/pi-coding-agent/dist/core/extensions/runner.js +3 -0
  104. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  105. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +2 -0
  106. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
  107. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +12 -0
  108. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  109. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  110. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +20 -0
  111. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  112. package/packages/pi-coding-agent/dist/core/settings-manager.js +25 -0
  113. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  114. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
  115. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  116. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +3 -0
  117. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  118. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  119. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +13 -5
  120. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  121. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js +53 -0
  122. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js.map +1 -1
  123. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  124. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +3 -0
  125. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  126. package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +36 -0
  127. package/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts +18 -0
  128. package/packages/pi-coding-agent/src/core/agent-session.ts +14 -3
  129. package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +3 -1
  130. package/packages/pi-coding-agent/src/core/compaction/compaction.ts +18 -0
  131. package/packages/pi-coding-agent/src/core/compaction-threshold.test.ts +121 -0
  132. package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +2 -0
  133. package/packages/pi-coding-agent/src/core/extensions/runner.ts +5 -0
  134. package/packages/pi-coding-agent/src/core/extensions/types.ts +12 -0
  135. package/packages/pi-coding-agent/src/core/settings-manager.ts +39 -1
  136. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +4 -0
  137. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.test.ts +56 -0
  138. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +22 -7
  139. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +3 -0
  140. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  141. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  142. package/packages/pi-tui/dist/tui.js +18 -8
  143. package/packages/pi-tui/dist/tui.js.map +1 -1
  144. package/packages/pi-tui/src/tui.ts +20 -8
  145. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  146. package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -2
  147. package/src/resources/extensions/gsd/auto/phases.ts +85 -35
  148. package/src/resources/extensions/gsd/auto/resolve.ts +23 -1
  149. package/src/resources/extensions/gsd/auto/run-unit.ts +22 -2
  150. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +1 -1
  151. package/src/resources/extensions/gsd/auto-prompts.ts +17 -1
  152. package/src/resources/extensions/gsd/auto-recovery.ts +54 -0
  153. package/src/resources/extensions/gsd/auto-supervisor.ts +7 -0
  154. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +2 -2
  155. package/src/resources/extensions/gsd/auto.ts +96 -4
  156. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +21 -1
  157. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +27 -19
  158. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +88 -4
  159. package/src/resources/extensions/gsd/clean-root-preflight.ts +32 -7
  160. package/src/resources/extensions/gsd/context-budget.ts +44 -2
  161. package/src/resources/extensions/gsd/db/unit-dispatches.ts +41 -0
  162. package/src/resources/extensions/gsd/db-base-schema.ts +4 -2
  163. package/src/resources/extensions/gsd/db-migration-steps.ts +8 -0
  164. package/src/resources/extensions/gsd/git-service.ts +46 -8
  165. package/src/resources/extensions/gsd/gsd-db.ts +50 -13
  166. package/src/resources/extensions/gsd/guided-flow.ts +49 -4
  167. package/src/resources/extensions/gsd/memory-store.ts +77 -12
  168. package/src/resources/extensions/gsd/migrate/command.ts +47 -1
  169. package/src/resources/extensions/gsd/migration-auto-check.ts +129 -0
  170. package/src/resources/extensions/gsd/pre-execution-checks.ts +7 -0
  171. package/src/resources/extensions/gsd/preferences-types.ts +1 -1
  172. package/src/resources/extensions/gsd/prompt-loader.ts +27 -2
  173. package/src/resources/extensions/gsd/prompts/complete-milestone.md +16 -13
  174. package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
  175. package/src/resources/extensions/gsd/prompts/quick-task.md +1 -5
  176. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
  177. package/src/resources/extensions/gsd/quick.ts +37 -2
  178. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +215 -1
  179. package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +56 -13
  180. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +14 -1
  181. package/src/resources/extensions/gsd/tests/auto-wrapup-inflight-guard.test.ts +166 -4
  182. package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +15 -6
  183. package/src/resources/extensions/gsd/tests/compaction-snapshot.test.ts +14 -1
  184. package/src/resources/extensions/gsd/tests/context-budget.test.ts +10 -1
  185. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +5 -1
  186. package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +313 -0
  187. package/src/resources/extensions/gsd/tests/exec-history.test.ts +15 -0
  188. package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +65 -0
  189. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +54 -0
  190. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +239 -1
  191. package/src/resources/extensions/gsd/tests/memory-decay-factor.test.ts +90 -0
  192. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +48 -0
  193. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +127 -0
  194. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +38 -0
  195. package/src/resources/extensions/gsd/tests/prompt-path-audit.test.ts +40 -0
  196. package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +19 -0
  197. package/src/resources/extensions/gsd/tests/quick-external-gsd.test.ts +40 -0
  198. package/src/resources/extensions/gsd/tests/schema-v27-v28-sequence.test.ts +156 -0
  199. package/src/resources/extensions/gsd/tests/signal-handlers.test.ts +27 -0
  200. package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +49 -1
  201. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +55 -0
  202. package/src/resources/extensions/gsd/tests/status-db-open.test.ts +9 -0
  203. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +136 -4
  204. package/src/resources/extensions/gsd/tests/unit-dispatches.test.ts +30 -0
  205. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +30 -0
  206. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +3 -0
  207. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +63 -1
  208. package/src/resources/extensions/gsd/tools/context-mode-tool-result.ts +25 -0
  209. package/src/resources/extensions/gsd/tools/exec-search-tool.ts +7 -7
  210. package/src/resources/extensions/gsd/tools/exec-tool.ts +4 -23
  211. package/src/resources/extensions/gsd/tools/memory-tools.ts +1 -0
  212. package/src/resources/extensions/gsd/tools/resume-tool.ts +7 -7
  213. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +1 -1
  214. package/src/resources/extensions/gsd/unit-context-composer.ts +19 -4
  215. package/src/resources/extensions/gsd/unit-runtime.ts +11 -0
  216. package/src/resources/extensions/gsd/worktree-resolver.ts +36 -15
  217. /package/dist/web/standalone/.next/static/{y73quA-XdLo9n41nxphjW → 4dQ9NTZJ8pEvFwBgDUX93}/_buildManifest.js +0 -0
  218. /package/dist/web/standalone/.next/static/{y73quA-XdLo9n41nxphjW → 4dQ9NTZJ8pEvFwBgDUX93}/_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 every auto-mode unit guidance for preserving context. Agents are steered toward `gsd_exec` for noisy scans, builds, tests, and diagnostics; full stdout/stderr is 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 `.gsd/last-snapshot.md` after compaction or resume. Opt out with `context_mode.enabled: false`; tune sandbox timeout/output caps with `context_mode.exec_timeout_ms`, `context_mode.exec_stdout_cap_bytes`, and `context_mode.exec_digest_chars`.
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`, exec history guidance, and resume snapshots |
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
- b23ebc1d803b7582
1
+ 533c696bf1b57db9
@@ -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, refreshOpenDatabaseFromDisk } from "../gsd-db.js";
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,13 +42,6 @@ import { getWorkflowTransportSupportError, getRequiredWorkflowToolsForAutoUnit,
42
42
  function isSamePathLocal(a, b) {
43
43
  return normalizeWorktreePathForCompare(a) === normalizeWorktreePathForCompare(b);
44
44
  }
45
- function refreshPlanSliceRecoveryDbIfNeeded(unitType) {
46
- if (unitType !== "plan-slice")
47
- return true;
48
- if (!isDbAvailable())
49
- return true;
50
- return refreshOpenDatabaseFromDisk();
51
- }
52
45
  // ─── Session timeout auto-resume state ────────────────────────────────────────
53
46
  let consecutiveSessionTimeouts = 0;
54
47
  const MAX_SESSION_TIMEOUT_AUTO_RESUMES = 3;
@@ -136,6 +129,22 @@ async function closeoutAndStop(ctx, pi, s, deps, reason) {
136
129
  }
137
130
  await deps.stopAuto(ctx, pi, reason);
138
131
  }
132
+ async function stopOnPostflightRecoveryNeeded(ic, result, milestoneId) {
133
+ if (!result.needsManualRecovery)
134
+ return null;
135
+ const { ctx, pi, deps } = ic;
136
+ const reason = `Post-merge stash restore failed for milestone ${milestoneId}`;
137
+ ctx.ui.notify(`${reason}. Resolve the working tree before resuming auto-mode. ${result.message}`, "error");
138
+ await deps.stopAuto(ctx, pi, reason);
139
+ return { action: "break", reason: "postflight-stash-restore-failed" };
140
+ }
141
+ async function restorePreflightStashOrStop(ic, preflight, milestoneId) {
142
+ if (!preflight.stashPushed)
143
+ return null;
144
+ const { ctx, s, deps } = ic;
145
+ const result = deps.postflightPopStash(s.originalBasePath || s.basePath, milestoneId, preflight.stashMarker, ctx.ui.notify.bind(ctx.ui));
146
+ return stopOnPostflightRecoveryNeeded(ic, result, milestoneId);
147
+ }
139
148
  async function emitCancelledUnitEnd(ic, unitType, unitId, unitStartSeq, errorContext) {
140
149
  ic.deps.emitJournalEvent({
141
150
  ts: new Date().toISOString(),
@@ -482,6 +491,8 @@ export async function runPreDispatch(ic, loopState) {
482
491
  const preflightTransition = deps.preflightCleanRoot(s.originalBasePath || s.basePath, s.currentMilestoneId, ctx.ui.notify.bind(ctx.ui));
483
492
  try {
484
493
  deps.resolver.mergeAndExit(s.currentMilestoneId, ctx.ui);
494
+ // Prevent stopAuto() from attempting the same merge again if postflight recovery stops here.
495
+ s.milestoneMergedInPhases = true;
485
496
  }
486
497
  catch (mergeErr) {
487
498
  if (mergeErr instanceof MergeConflictError) {
@@ -497,8 +508,10 @@ export async function runPreDispatch(ic, loopState) {
497
508
  return { action: "break", reason: "merge-failed" };
498
509
  }
499
510
  // #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));
511
+ {
512
+ const postflightStop = await restorePreflightStashOrStop(ic, preflightTransition, s.currentMilestoneId);
513
+ if (postflightStop)
514
+ return postflightStop;
502
515
  }
503
516
  // PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
504
517
  deps.invalidateAllCaches();
@@ -573,8 +586,10 @@ export async function runPreDispatch(ic, loopState) {
573
586
  return { action: "break", reason: "merge-failed" };
574
587
  }
575
588
  // #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));
589
+ {
590
+ const postflightStop = await restorePreflightStashOrStop(ic, preflightAllComplete, s.currentMilestoneId);
591
+ if (postflightStop)
592
+ return postflightStop;
578
593
  }
579
594
  // PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
580
595
  }
@@ -659,8 +674,10 @@ export async function runPreDispatch(ic, loopState) {
659
674
  return { action: "break", reason: "merge-failed" };
660
675
  }
661
676
  // #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));
677
+ {
678
+ const postflightStop = await restorePreflightStashOrStop(ic, preflightComplete, s.currentMilestoneId);
679
+ if (postflightStop)
680
+ return postflightStop;
664
681
  }
665
682
  // PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
666
683
  }
@@ -795,11 +812,18 @@ export async function runDispatch(ic, preData, loopState) {
795
812
  level: 1,
796
813
  action: "artifact-found",
797
814
  });
798
- ctx.ui.notify(`Stuck recovery: artifact for ${unitType} ${unitId} found on disk. Invalidating caches.`, "info");
799
- if (!refreshPlanSliceRecoveryDbIfNeeded(unitType)) {
800
- ctx.ui.notify(`Stuck recovery found ${unitType} ${unitId} artifacts, but the DB refresh failed. Keeping stuck state for retry.`, "warning");
815
+ const recoveryDb = refreshRecoveryDbForArtifact(unitType, unitId);
816
+ if (!recoveryDb.ok) {
817
+ ctx.ui.notify(recoveryDb.fatal
818
+ ? `${recoveryDb.message} Pausing auto-mode for manual recovery.`
819
+ : `${recoveryDb.message} Keeping stuck state for retry.`, "warning");
820
+ if (recoveryDb.fatal) {
821
+ await deps.pauseAuto(ctx, pi);
822
+ return { action: "break", reason: recoveryDb.reason };
823
+ }
801
824
  return { action: "continue" };
802
825
  }
826
+ ctx.ui.notify(`Stuck recovery: artifact for ${unitType} ${unitId} found on disk. Invalidating caches.`, "info");
803
827
  deps.invalidateAllCaches();
804
828
  loopState.recentUnits.length = 0;
805
829
  loopState.stuckRecoveryAttempts = 0;
@@ -818,13 +842,20 @@ export async function runDispatch(ic, preData, loopState) {
818
842
  level: 2,
819
843
  action: "artifact-found",
820
844
  });
821
- ctx.ui.notify(`Stuck recovery: artifact for ${unitType} ${unitId} found on disk after cache invalidation. Continuing.`, "info");
822
- if (refreshPlanSliceRecoveryDbIfNeeded(unitType)) {
845
+ const recoveryDb = refreshRecoveryDbForArtifact(unitType, unitId);
846
+ if (recoveryDb.ok) {
847
+ ctx.ui.notify(`Stuck recovery: artifact for ${unitType} ${unitId} found on disk after cache invalidation. Continuing.`, "info");
823
848
  loopState.recentUnits.length = 0;
824
849
  loopState.stuckRecoveryAttempts = 0;
825
850
  return { action: "continue" };
826
851
  }
827
- ctx.ui.notify(`Stuck recovery found ${unitType} ${unitId} artifacts, but the DB refresh failed. Stopping for manual recovery.`, "warning");
852
+ ctx.ui.notify(recoveryDb.fatal
853
+ ? `${recoveryDb.message} Pausing auto-mode for manual recovery.`
854
+ : `${recoveryDb.message} Stopping for manual recovery.`, "warning");
855
+ if (recoveryDb.fatal) {
856
+ await deps.pauseAuto(ctx, pi);
857
+ return { action: "break", reason: recoveryDb.reason };
858
+ }
828
859
  }
829
860
  debugLog("autoLoop", {
830
861
  phase: "stuck-detected",
@@ -1672,6 +1703,13 @@ export async function runFinalize(ic, iterData, loopState, sidecarItem) {
1672
1703
  }
1673
1704
  // Both pre and post verification completed without timeout — reset counter
1674
1705
  loopState.consecutiveFinalizeTimeouts = 0;
1706
+ if (preUnitSnapshot) {
1707
+ writeUnitRuntimeRecord(s.basePath, preUnitSnapshot.type, preUnitSnapshot.id, preUnitSnapshot.startedAt, {
1708
+ phase: "finalized",
1709
+ lastProgressAt: Date.now(),
1710
+ lastProgressKind: "finalize-success",
1711
+ });
1712
+ }
1675
1713
  s.currentUnit = null;
1676
1714
  clearCurrentPhase();
1677
1715
  // 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";
@@ -55,7 +55,10 @@ export async function runUnit(ctx, pi, s, unitType, unitId, prompt) {
55
55
  const sessionAbortController = new AbortController();
56
56
  _setSessionSwitchInFlight(true);
57
57
  try {
58
- const sessionPromise = s.cmdCtx.newSession({ abortSignal: sessionAbortController.signal }).finally(() => {
58
+ const sessionPromise = s.cmdCtx.newSession({
59
+ abortSignal: sessionAbortController.signal,
60
+ cwd: s.basePath,
61
+ }).finally(() => {
59
62
  if (sessionSwitchGeneration === mySessionSwitchGeneration) {
60
63
  _setSessionSwitchInFlight(false);
61
64
  }
@@ -71,6 +74,7 @@ export async function runUnit(ctx, pi, s, unitType, unitId, prompt) {
71
74
  catch (sessionErr) {
72
75
  if (sessionTimeoutHandle)
73
76
  clearTimeout(sessionTimeoutHandle);
77
+ _consumePendingSwitchCancellation();
74
78
  const msg = sessionErr instanceof Error ? sessionErr.message : String(sessionErr);
75
79
  debugLog("runUnit", {
76
80
  phase: "session-error",
@@ -83,15 +87,18 @@ export async function runUnit(ctx, pi, s, unitType, unitId, prompt) {
83
87
  if (sessionTimeoutHandle)
84
88
  clearTimeout(sessionTimeoutHandle);
85
89
  if (sessionResult.cancelled) {
90
+ _consumePendingSwitchCancellation();
86
91
  debugLog("runUnit-session-timeout", { unitType, unitId });
87
92
  return { status: "cancelled", errorContext: { message: "Session creation timed out", category: "timeout", isTransient: true } };
88
93
  }
89
94
  if (!s.active) {
95
+ _consumePendingSwitchCancellation();
90
96
  return { status: "cancelled" };
91
97
  }
92
98
  if (s.currentUnitModel && typeof pi.setModel === "function") {
93
99
  const restored = await pi.setModel(s.currentUnitModel, { persist: false });
94
100
  if (!restored) {
101
+ _consumePendingSwitchCancellation();
95
102
  const message = `Failed to restore configured model ${s.currentUnitModel.provider}/${s.currentUnitModel.id} after session creation`;
96
103
  ctx.ui.notify(`${message}. Cancelling unit before dispatch.`, "warning");
97
104
  return {
@@ -111,6 +118,14 @@ export async function runUnit(ctx, pi, s, unitType, unitId, prompt) {
111
118
  const unitPromise = new Promise((resolve) => {
112
119
  _setCurrentResolve(resolve);
113
120
  });
121
+ const pendingSwitchCancellation = _consumePendingSwitchCancellation();
122
+ if (pendingSwitchCancellation) {
123
+ _clearCurrentResolve();
124
+ return {
125
+ status: "cancelled",
126
+ ...(pendingSwitchCancellation.errorContext ? { errorContext: pendingSwitchCancellation.errorContext } : {}),
127
+ };
128
+ }
114
129
  // ── Provider request-readiness pre-check (#4555) ──
115
130
  // Verify the provider can accept requests before dispatching. If the token
116
131
  // has expired since bootstrap, return cancelled immediately so the unit is
@@ -246,7 +246,7 @@ export async function dispatchDirectPhase(ctx, pi, phase, base) {
246
246
  ctx.ui.notify(`${msg}. Cancelling dispatch to avoid running in the wrong directory.`, "error");
247
247
  return;
248
248
  }
249
- const result = await ctx.newSession();
249
+ const result = await ctx.newSession({ cwd: dispatchBase });
250
250
  if (result.cancelled) {
251
251
  ctx.ui.notify("Session creation cancelled.", "warning");
252
252
  return;
@@ -21,6 +21,7 @@ import { assertGateCoverage, getGatesForTurn, } from "./gate-registry.js";
21
21
  import { formatDecisionsCompact, formatRequirementsCompact } from "./structured-data-formatter.js";
22
22
  import { readPhaseAnchor, formatAnchorForPrompt } from "./phase-anchor.js";
23
23
  import { composeContextModeInstructions, composeInlinedContext } from "./unit-context-composer.js";
24
+ import { readCompactionSnapshot } from "./compaction-snapshot.js";
24
25
  import { logWarning } from "./workflow-logger.js";
25
26
  import { inlineGraphSubgraph } from "./graph-context.js";
26
27
  import { buildExtractionStepsBlock } from "./commands-extract-learnings.js";
@@ -178,8 +179,19 @@ function renderContextModeForPrompt(unitType, base, renderMode = "standalone") {
178
179
  renderMode,
179
180
  });
180
181
  }
181
- function prependContextModeToBlock(unitType, base, block, renderMode = "standalone") {
182
+ function renderContextModeBlockForPrompt(unitType, base, renderMode = "standalone") {
182
183
  const contextMode = renderContextModeForPrompt(unitType, base, renderMode);
184
+ if (!contextMode)
185
+ return "";
186
+ if (renderMode === "nested")
187
+ return contextMode;
188
+ const snapshot = readCompactionSnapshot(base);
189
+ if (!snapshot?.trim())
190
+ return contextMode;
191
+ return `${contextMode}\n\n## Context Snapshot\nSource: \`.gsd/last-snapshot.md\`\n\n${snapshot.trimEnd()}`;
192
+ }
193
+ function prependContextModeToBlock(unitType, base, block, renderMode = "standalone") {
194
+ const contextMode = renderContextModeBlockForPrompt(unitType, base, renderMode);
183
195
  if (!contextMode)
184
196
  return block;
185
197
  if (!block.trim())
@@ -30,7 +30,49 @@ import { getProjectResearchStatus } from "./project-research-policy.js";
30
30
  // Re-export so existing consumers of auto-recovery.ts keep working.
31
31
  export { resolveExpectedArtifactPath, diagnoseExpectedArtifact };
32
32
  export { classifyMilestoneSummaryContent, } from "./milestone-summary-classifier.js";
33
- // ─── Artifact Resolution & Verification ───────────────────────────────────────
33
+ export function refreshRecoveryDbForArtifact(unitType, unitId) {
34
+ if (unitType !== "plan-slice" && unitType !== "execute-task")
35
+ return { ok: true };
36
+ if (!isDbAvailable())
37
+ return { ok: true };
38
+ if (!refreshOpenDatabaseFromDisk()) {
39
+ return {
40
+ ok: false,
41
+ fatal: unitType === "execute-task",
42
+ reason: `${unitType}-db-refresh-failed`,
43
+ message: `Stuck recovery found ${unitType} ${unitId} artifacts, but the DB refresh failed.`,
44
+ };
45
+ }
46
+ if (unitType !== "execute-task")
47
+ return { ok: true };
48
+ const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
49
+ if (!mid || !sid || !tid) {
50
+ return {
51
+ ok: false,
52
+ fatal: true,
53
+ reason: "execute-task-invalid-unit-id",
54
+ message: `Stuck recovery found execute-task ${unitId} artifacts, but the unit id could not be parsed for DB verification.`,
55
+ };
56
+ }
57
+ const task = getTask(mid, sid, tid);
58
+ if (!task) {
59
+ return {
60
+ ok: false,
61
+ fatal: true,
62
+ reason: "execute-task-artifact-db-missing",
63
+ message: `Stuck recovery found execute-task ${unitId} artifacts, but no matching DB task row exists after refresh.`,
64
+ };
65
+ }
66
+ if (!isClosedStatus(task.status)) {
67
+ return {
68
+ ok: false,
69
+ fatal: true,
70
+ reason: "execute-task-artifact-db-mismatch",
71
+ message: `Stuck recovery found execute-task ${unitId} artifacts, but the DB task status is still '${task.status}' after refresh.`,
72
+ };
73
+ }
74
+ return { ok: true };
75
+ }
34
76
  function hasCapturedWorkflowPrefs(base) {
35
77
  const prefsPath = resolveExpectedArtifactPath("workflow-preferences", "WORKFLOW-PREFS", base);
36
78
  if (!prefsPath || !existsSync(prefsPath))
@@ -24,7 +24,7 @@ let _currentSigtermHandler = null;
24
24
  *
25
25
  * Returns the new handler so the caller can store and deregister it later.
26
26
  */
27
- export function registerSigtermHandler(currentBasePath, previousHandler) {
27
+ export function registerSigtermHandler(currentBasePath, previousHandler, onSignalCleanup) {
28
28
  // Remove the explicitly-passed previous handler
29
29
  if (previousHandler) {
30
30
  for (const sig of CLEANUP_SIGNALS)
@@ -37,6 +37,13 @@ export function registerSigtermHandler(currentBasePath, previousHandler) {
37
37
  process.off(sig, _currentSigtermHandler);
38
38
  }
39
39
  const handler = () => {
40
+ try {
41
+ onSignalCleanup?.();
42
+ }
43
+ catch {
44
+ void 0;
45
+ // Signal cleanup is best-effort; lock cleanup and process exit still run.
46
+ }
40
47
  clearLock(currentBasePath);
41
48
  releaseSessionLock(currentBasePath);
42
49
  process.exit(0);
@@ -35,13 +35,13 @@ export async function recoverTimedOutUnit(ctx, pi, unitType, unitId, reason, rct
35
35
  writeUnitRuntimeRecord(basePath, unitType, unitId, currentUnitStartedAt, {
36
36
  recovery: status,
37
37
  });
38
- const durableComplete = status.summaryExists && status.taskChecked && status.nextActionAdvanced;
38
+ const durableComplete = status.dbComplete || (status.summaryExists && status.taskChecked && status.nextActionAdvanced);
39
39
  if (durableComplete) {
40
40
  writeUnitRuntimeRecord(basePath, unitType, unitId, currentUnitStartedAt, {
41
41
  phase: "finalized",
42
42
  recovery: status,
43
43
  });
44
- ctx.ui.notify(`${reason === "idle" ? "Idle" : "Timeout"} recovery: ${unitType} ${unitId} already completed on disk. Continuing auto-mode. (attempt ${attemptNumber})`, "info");
44
+ ctx.ui.notify(`${reason === "idle" ? "Idle" : "Timeout"} recovery: ${unitType} ${unitId} already completed. Continuing auto-mode. (attempt ${attemptNumber})`, "info");
45
45
  unitRecoveryCount.delete(recoveryKey);
46
46
  bumpAndResolveSynthetic(`timeout-recovery:${reason}:${unitType}/${unitId}`);
47
47
  return "recovered";
@@ -20,7 +20,7 @@ import { gsdRoot, resolveMilestoneFile, resolveMilestonePath, resolveDir, milest
20
20
  import { invalidateAllCaches } from "./cache.js";
21
21
  import { clearActivityLogState } from "./activity-log.js";
22
22
  import { synthesizeCrashRecovery, getDeepDiagnostic, readActiveMilestoneId, } from "./session-forensics.js";
23
- import { writeLock, clearLock, readCrashLock, isLockProcessAlive, formatCrashInfo, emitCrashRecoveredUnitEnd, } from "./crash-recovery.js";
23
+ import { writeLock, clearLock, readCrashLock, isLockProcessAlive, formatCrashInfo, emitCrashRecoveredUnitEnd, emitOpenUnitEndForUnit, } from "./crash-recovery.js";
24
24
  import { acquireSessionLock, getSessionLockStatus, releaseSessionLock, updateSessionLock, } from "./session-lock.js";
25
25
  import { resolveAutoSupervisorConfig, loadEffectiveGSDPreferences, getIsolationMode, } from "./preferences.js";
26
26
  import { sendDesktopNotification } from "./notifications.js";
@@ -61,6 +61,8 @@ import { isClosedStatus } from "./status-guards.js";
61
61
  import { updateProgressWidget as _updateProgressWidget, updateSliceProgressCache, clearSliceProgressCache, } from "./auto-dashboard.js";
62
62
  import { registerSigtermHandler as _registerSigtermHandler, deregisterSigtermHandler as _deregisterSigtermHandler, } from "./auto-supervisor.js";
63
63
  import { isDbAvailable, getMilestone } from "./gsd-db.js";
64
+ import { markLatestActiveForWorkerCanceled } from "./db/unit-dispatches.js";
65
+ import { writeUnitRuntimeRecord } from "./unit-runtime.js";
64
66
  import { countPendingCaptures } from "./captures.js";
65
67
  import { CMUX_CHANNELS } from "../shared/cmux-events.js";
66
68
  import { ensureDbOpen } from "./bootstrap/dynamic-tools.js";
@@ -85,6 +87,16 @@ import { validateDirectory } from "./validate-directory.js";
85
87
  import { createAutoOrchestrator } from "./auto/orchestrator.js";
86
88
  import { WorktreeResolver, } from "./worktree-resolver.js";
87
89
  import { reorderForCaching } from "./prompt-ordering.js";
90
+ import { initTokenCounter } from "./token-counter.js";
91
+ // Warm the tiktoken encoder at extension startup so context-budget computations
92
+ // can use accurate token counts via countTokensSync without paying the load
93
+ // cost mid-prompt-build. Fire-and-forget — failure falls back to the
94
+ // provider-aware char-ratio estimator already used by getCharsPerToken().
95
+ // Catch rejections explicitly: an unhandled rejection at module-import time
96
+ // can destabilize startup before the engine logger is configured.
97
+ void initTokenCounter().catch((err) => {
98
+ logWarning("engine", `token counter warm-up failed: ${err instanceof Error ? err.message : String(err)}`);
99
+ });
88
100
  export { STUB_RECOVERY_THRESHOLD, NEW_SESSION_TIMEOUT_MS, } from "./auto/session.js";
89
101
  import { autoSession as s } from "./auto-runtime-state.js";
90
102
  import { gsdHome } from "./gsd-home.js";
@@ -112,11 +124,12 @@ const STATE_REBUILD_MIN_INTERVAL_MS = 30_000;
112
124
  * the DB is unavailable (e.g. fresh project before init) we skip registration
113
125
  * silently rather than blocking session start.
114
126
  */
115
- function registerAutoWorkerForSession(session) {
127
+ function registerAutoWorkerForSession(session, projectRootOverride) {
116
128
  if (session.workerId)
117
129
  return; // already registered (e.g. resume re-runs)
118
130
  try {
119
- const projectRootRealpath = normalizeRealPath(session.scope?.workspace.projectRoot
131
+ const projectRootRealpath = normalizeRealPath(projectRootOverride
132
+ ?? session.scope?.workspace.projectRoot
120
133
  ?? (session.originalBasePath || session.basePath));
121
134
  session.workerId = registerAutoWorker({ projectRootRealpath });
122
135
  }
@@ -272,9 +285,50 @@ export function shouldUseWorktreeIsolation(basePath) {
272
285
  */
273
286
  // Re-export budget utilities for external consumers
274
287
  export { getBudgetAlertLevel, getNewBudgetAlertLevel, getBudgetEnforcementAction, } from "./auto-budget.js";
288
+ function closeOutSignalInterruptedUnit(currentBasePath) {
289
+ const currentUnit = s.currentUnit;
290
+ if (!currentUnit)
291
+ return;
292
+ const reason = "Auto-mode process received a termination signal";
293
+ const errorContext = {
294
+ message: reason,
295
+ category: "aborted",
296
+ isTransient: false,
297
+ };
298
+ const basePath = s.basePath || currentBasePath;
299
+ try {
300
+ emitOpenUnitEndForUnit(basePath, currentUnit.type, currentUnit.id, "cancelled", errorContext);
301
+ }
302
+ catch (err) {
303
+ logWarning("engine", `signal unit-end cleanup failed: ${getErrorMessage(err)}`, { file: "auto.ts" });
304
+ }
305
+ try {
306
+ writeUnitRuntimeRecord(basePath, currentUnit.type, currentUnit.id, currentUnit.startedAt, {
307
+ phase: "crashed",
308
+ lastProgressAt: Date.now(),
309
+ lastProgressKind: "signal",
310
+ });
311
+ }
312
+ catch (err) {
313
+ logWarning("engine", `signal runtime cleanup failed: ${getErrorMessage(err)}`, { file: "auto.ts" });
314
+ }
315
+ try {
316
+ if (s.workerId)
317
+ markLatestActiveForWorkerCanceled(s.workerId, "signal-exit");
318
+ }
319
+ catch (err) {
320
+ logWarning("engine", `signal dispatch cleanup failed: ${getErrorMessage(err)}`, { file: "auto.ts" });
321
+ }
322
+ try {
323
+ resolveAgentEndCancelled(errorContext);
324
+ }
325
+ catch (err) {
326
+ logWarning("engine", `signal resolve cleanup failed: ${getErrorMessage(err)}`, { file: "auto.ts" });
327
+ }
328
+ }
275
329
  /** Wrapper: register SIGTERM handler and store reference. */
276
330
  function registerSigtermHandler(currentBasePath) {
277
- s.sigtermHandler = _registerSigtermHandler(currentBasePath, s.sigtermHandler);
331
+ s.sigtermHandler = _registerSigtermHandler(currentBasePath, s.sigtermHandler, () => closeOutSignalInterruptedUnit(currentBasePath));
278
332
  }
279
333
  /** Wrapper: deregister SIGTERM handler and clear reference. */
280
334
  function deregisterSigtermHandler() {
@@ -666,6 +720,8 @@ export async function stopAuto(ctx, pi, reason) {
666
720
  if (s.workerId) {
667
721
  markWorkerStopping(s.workerId);
668
722
  }
723
+ s.workerId = null;
724
+ s.milestoneLeaseToken = null;
669
725
  }
670
726
  catch (e) {
671
727
  debugLog("stop-cleanup-coordination", { error: e instanceof Error ? e.message : String(e) });
@@ -798,6 +854,21 @@ export async function stopAuto(ctx, pi, reason) {
798
854
  catch (e) {
799
855
  debugLog("stop-cleanup-basepath", { error: e instanceof Error ? e.message : String(e) });
800
856
  }
857
+ // Re-root the active command session/tool runtime after worktree teardown.
858
+ // mergeAndExit restores process.cwd(), but AgentSession has already captured
859
+ // its own cwd for tools and system prompt; refresh it before returning to the
860
+ // user so follow-up commands do not target a removed milestone worktree.
861
+ if (s.originalBasePath && ctx && s.cmdCtx) {
862
+ try {
863
+ const result = await s.cmdCtx.newSession({ cwd: s.basePath });
864
+ if (result.cancelled) {
865
+ logWarning("engine", "post-stop session re-root was cancelled", { file: "auto.ts", basePath: s.basePath });
866
+ }
867
+ }
868
+ catch (err) {
869
+ logWarning("engine", `post-stop session re-root failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts", basePath: s.basePath });
870
+ }
871
+ }
801
872
  // ── Step 8: Ledger notification ──
802
873
  try {
803
874
  const ledger = getLedger();
@@ -1566,6 +1637,10 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
1566
1637
  : new URL("../../../resource-loader.js", import.meta.url).href;
1567
1638
  const { initResources } = await import(resourceLoaderPath);
1568
1639
  initResources(agentDir);
1640
+ // initResources() uses synchronous fs APIs, so the prompt-template cache
1641
+ // can be primed immediately — no need for the legacy 1s setTimeout deferral.
1642
+ const { primeCache } = await import("./prompt-loader.js");
1643
+ primeCache();
1569
1644
  // Open the project DB before rebuild/derive so resume uses DB-backed
1570
1645
  // state instead of falling back to stale markdown parsing (#2940).
1571
1646
  await openProjectDbIfPresent(s.basePath);
@@ -1631,6 +1706,10 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
1631
1706
  lockBase,
1632
1707
  buildResolver,
1633
1708
  };
1709
+ // Register the worker before bootstrap enters a milestone worktree.
1710
+ // This ensures enterMilestone can claim a lease and seed dispatch claims
1711
+ // for crash-recovery fidelity (#5405).
1712
+ registerAutoWorkerForSession(s, base);
1634
1713
  const ready = await bootstrapAutoSession(s, ctx, pi, base, verboseMode, requestedStepMode, bootstrapDeps, freshStartAssessment);
1635
1714
  if (!ready)
1636
1715
  return;
@@ -1769,7 +1848,7 @@ export async function dispatchHookUnit(ctx, pi, hookName, triggerUnitType, trigg
1769
1848
  }
1770
1849
  return false;
1771
1850
  }
1772
- const result = await s.cmdCtx.newSession();
1851
+ const result = await s.cmdCtx.newSession({ cwd: s.basePath });
1773
1852
  if (result.cancelled) {
1774
1853
  await stopAuto(ctx, pi);
1775
1854
  return false;
@@ -118,9 +118,28 @@ export async function handleAgentEnd(pi, event, ctx) {
118
118
  return;
119
119
  if (!isAutoActive())
120
120
  return;
121
- if (isSessionSwitchInFlight())
122
- return;
123
121
  const lastMsg = event.messages[event.messages.length - 1];
122
+ if (isSessionSwitchInFlight()) {
123
+ if (lastMsg && "stopReason" in lastMsg && lastMsg.stopReason === "error") {
124
+ const rawErrorMsg = ("errorMessage" in lastMsg && lastMsg.errorMessage) ? String(lastMsg.errorMessage) : "";
125
+ if (isUserInitiatedAbortMessage(rawErrorMsg)) {
126
+ resolveAgentEndCancelled({
127
+ message: rawErrorMsg,
128
+ category: "aborted",
129
+ isTransient: false,
130
+ });
131
+ }
132
+ }
133
+ else if (lastMsg && "stopReason" in lastMsg && lastMsg.stopReason === "aborted") {
134
+ const content = "content" in lastMsg ? lastMsg.content : undefined;
135
+ const hasEmptyContent = Array.isArray(content) && content.length === 0;
136
+ const hasErrorMessage = "errorMessage" in lastMsg && !!lastMsg.errorMessage;
137
+ if (!hasEmptyContent || hasErrorMessage) {
138
+ resolveAgentEndCancelled(_buildAbortedPauseContext(lastMsg));
139
+ }
140
+ }
141
+ return;
142
+ }
124
143
  if (lastMsg && "stopReason" in lastMsg && lastMsg.stopReason === "aborted") {
125
144
  // Empty content with aborted stopReason is a non-fatal agent stop (the LLM
126
145
  // chose to end without producing output). Only pause on genuine fatal aborts