gsd-pi 2.80.0-dev.c5c38454b → 2.80.0-dev.cf9433f56

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 (266) hide show
  1. package/README.md +4 -2
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/GSD-WORKFLOW.md +2 -2
  4. package/dist/resources/extensions/gsd/auto/loop.js +32 -1
  5. package/dist/resources/extensions/gsd/auto/phases.js +66 -45
  6. package/dist/resources/extensions/gsd/auto/resolve.js +17 -0
  7. package/dist/resources/extensions/gsd/auto/run-unit.js +13 -1
  8. package/dist/resources/extensions/gsd/auto-post-unit.js +10 -10
  9. package/dist/resources/extensions/gsd/auto-prompts.js +124 -2
  10. package/dist/resources/extensions/gsd/auto-recovery.js +43 -1
  11. package/dist/resources/extensions/gsd/auto-supervisor.js +8 -1
  12. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +2 -2
  13. package/dist/resources/extensions/gsd/auto.js +77 -5
  14. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +21 -2
  15. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +27 -20
  16. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +21 -0
  17. package/dist/resources/extensions/gsd/clean-root-preflight.js +42 -4
  18. package/dist/resources/extensions/gsd/commands/dispatcher.js +5 -0
  19. package/dist/resources/extensions/gsd/context-budget.js +37 -2
  20. package/dist/resources/extensions/gsd/crash-recovery.js +56 -10
  21. package/dist/resources/extensions/gsd/db/unit-dispatches.js +39 -0
  22. package/dist/resources/extensions/gsd/db-base-schema.js +4 -2
  23. package/dist/resources/extensions/gsd/db-migration-steps.js +6 -0
  24. package/dist/resources/extensions/gsd/detection.js +106 -0
  25. package/dist/resources/extensions/gsd/git-service.js +36 -4
  26. package/dist/resources/extensions/gsd/gsd-db.js +46 -13
  27. package/dist/resources/extensions/gsd/guided-flow.js +80 -14
  28. package/dist/resources/extensions/gsd/memory-store.js +69 -12
  29. package/dist/resources/extensions/gsd/migrate/command.js +40 -1
  30. package/dist/resources/extensions/gsd/migration-auto-check.js +87 -0
  31. package/dist/resources/extensions/gsd/planning-path-scope.js +26 -0
  32. package/dist/resources/extensions/gsd/pre-execution-checks.js +7 -0
  33. package/dist/resources/extensions/gsd/prompt-loader.js +28 -2
  34. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +19 -19
  35. package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
  36. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +3 -1
  37. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  38. package/dist/resources/extensions/gsd/prompts/quick-task.md +1 -5
  39. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
  40. package/dist/resources/extensions/gsd/quick.js +34 -2
  41. package/dist/resources/extensions/gsd/safety/evidence-collector.js +10 -2
  42. package/dist/resources/extensions/gsd/tools/context-mode-tool-result.js +15 -0
  43. package/dist/resources/extensions/gsd/tools/exec-search-tool.js +5 -0
  44. package/dist/resources/extensions/gsd/tools/exec-tool.js +3 -15
  45. package/dist/resources/extensions/gsd/tools/memory-tools.js +1 -0
  46. package/dist/resources/extensions/gsd/tools/plan-slice.js +9 -0
  47. package/dist/resources/extensions/gsd/tools/plan-task.js +9 -0
  48. package/dist/resources/extensions/gsd/tools/resume-tool.js +5 -0
  49. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +1 -1
  50. package/dist/resources/extensions/gsd/unit-context-composer.js +12 -3
  51. package/dist/resources/extensions/gsd/unit-runtime.js +22 -0
  52. package/dist/resources/extensions/gsd/worktree-manager.js +16 -14
  53. package/dist/resources/extensions/gsd/worktree-resolver.js +33 -17
  54. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  55. package/dist/web/standalone/.next/BUILD_ID +1 -1
  56. package/dist/web/standalone/.next/app-path-routes-manifest.json +7 -7
  57. package/dist/web/standalone/.next/build-manifest.json +2 -2
  58. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  59. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  60. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  68. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/index.html +1 -1
  76. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app-paths-manifest.json +7 -7
  83. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  84. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  85. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  86. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  87. package/package.json +3 -3
  88. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  89. package/packages/mcp-server/dist/workflow-tools.js +22 -17
  90. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  91. package/packages/mcp-server/src/workflow-tools.test.ts +75 -2
  92. package/packages/mcp-server/src/workflow-tools.ts +30 -16
  93. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  94. package/packages/native/tsconfig.tsbuildinfo +1 -1
  95. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +32 -0
  96. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
  97. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  98. package/packages/pi-coding-agent/dist/core/agent-session.js +8 -0
  99. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  100. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +32 -0
  101. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
  102. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts +11 -0
  103. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
  104. package/packages/pi-coding-agent/dist/core/compaction/compaction.js +9 -0
  105. package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
  106. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts +2 -0
  107. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts.map +1 -0
  108. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js +103 -0
  109. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js.map +1 -0
  110. package/packages/pi-coding-agent/dist/core/db-snapshot.d.ts +15 -0
  111. package/packages/pi-coding-agent/dist/core/db-snapshot.d.ts.map +1 -0
  112. package/packages/pi-coding-agent/dist/core/db-snapshot.js +66 -0
  113. package/packages/pi-coding-agent/dist/core/db-snapshot.js.map +1 -0
  114. package/packages/pi-coding-agent/dist/core/db-snapshot.test.d.ts +2 -0
  115. package/packages/pi-coding-agent/dist/core/db-snapshot.test.d.ts.map +1 -0
  116. package/packages/pi-coding-agent/dist/core/db-snapshot.test.js +24 -0
  117. package/packages/pi-coding-agent/dist/core/db-snapshot.test.js.map +1 -0
  118. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +1 -0
  119. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  120. package/packages/pi-coding-agent/dist/core/extensions/runner.js +3 -0
  121. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  122. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +2 -0
  123. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
  124. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +7 -0
  125. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  126. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  127. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +20 -0
  128. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  129. package/packages/pi-coding-agent/dist/core/settings-manager.js +25 -0
  130. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  131. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
  132. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  133. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +3 -0
  134. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  135. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  136. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +15 -5
  137. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  138. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js +53 -0
  139. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js.map +1 -1
  140. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  141. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +3 -0
  142. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  143. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.d.ts +2 -0
  144. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.d.ts.map +1 -0
  145. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js +10 -0
  146. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js.map +1 -0
  147. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.d.ts.map +1 -1
  148. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js +3 -2
  149. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js.map +1 -1
  150. package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +36 -0
  151. package/packages/pi-coding-agent/src/core/agent-session.ts +8 -0
  152. package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +38 -0
  153. package/packages/pi-coding-agent/src/core/compaction/compaction.ts +18 -0
  154. package/packages/pi-coding-agent/src/core/compaction-threshold.test.ts +121 -0
  155. package/packages/pi-coding-agent/src/core/db-snapshot.test.ts +32 -0
  156. package/packages/pi-coding-agent/src/core/db-snapshot.ts +66 -0
  157. package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +2 -0
  158. package/packages/pi-coding-agent/src/core/extensions/runner.ts +3 -0
  159. package/packages/pi-coding-agent/src/core/extensions/types.ts +7 -0
  160. package/packages/pi-coding-agent/src/core/settings-manager.ts +39 -1
  161. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +4 -0
  162. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.test.ts +56 -0
  163. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +24 -7
  164. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +3 -0
  165. package/packages/pi-coding-agent/src/resources/extensions/memory/storage-safety-guard.test.ts +14 -0
  166. package/packages/pi-coding-agent/src/resources/extensions/memory/storage.ts +3 -2
  167. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  168. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  169. package/packages/pi-tui/dist/tui.js +18 -8
  170. package/packages/pi-tui/dist/tui.js.map +1 -1
  171. package/packages/pi-tui/src/tui.ts +20 -8
  172. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  173. package/src/resources/GSD-WORKFLOW.md +2 -2
  174. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
  175. package/src/resources/extensions/gsd/auto/loop.ts +50 -8
  176. package/src/resources/extensions/gsd/auto/phases.ts +77 -48
  177. package/src/resources/extensions/gsd/auto/resolve.ts +23 -1
  178. package/src/resources/extensions/gsd/auto/run-unit.ts +18 -1
  179. package/src/resources/extensions/gsd/auto-post-unit.ts +10 -10
  180. package/src/resources/extensions/gsd/auto-prompts.ts +133 -2
  181. package/src/resources/extensions/gsd/auto-recovery.ts +54 -0
  182. package/src/resources/extensions/gsd/auto-supervisor.ts +7 -0
  183. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +2 -2
  184. package/src/resources/extensions/gsd/auto.ts +92 -4
  185. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +21 -1
  186. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +27 -19
  187. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +22 -0
  188. package/src/resources/extensions/gsd/clean-root-preflight.ts +41 -3
  189. package/src/resources/extensions/gsd/commands/dispatcher.ts +6 -0
  190. package/src/resources/extensions/gsd/context-budget.ts +44 -2
  191. package/src/resources/extensions/gsd/crash-recovery.ts +67 -10
  192. package/src/resources/extensions/gsd/db/unit-dispatches.ts +41 -0
  193. package/src/resources/extensions/gsd/db-base-schema.ts +4 -2
  194. package/src/resources/extensions/gsd/db-migration-steps.ts +8 -0
  195. package/src/resources/extensions/gsd/detection.ts +128 -0
  196. package/src/resources/extensions/gsd/git-service.ts +46 -8
  197. package/src/resources/extensions/gsd/gsd-db.ts +50 -13
  198. package/src/resources/extensions/gsd/guided-flow.ts +96 -14
  199. package/src/resources/extensions/gsd/memory-store.ts +77 -12
  200. package/src/resources/extensions/gsd/migrate/command.ts +47 -1
  201. package/src/resources/extensions/gsd/migration-auto-check.ts +129 -0
  202. package/src/resources/extensions/gsd/planning-path-scope.ts +35 -0
  203. package/src/resources/extensions/gsd/pre-execution-checks.ts +7 -0
  204. package/src/resources/extensions/gsd/preferences-types.ts +1 -1
  205. package/src/resources/extensions/gsd/prompt-loader.ts +27 -2
  206. package/src/resources/extensions/gsd/prompts/complete-milestone.md +19 -19
  207. package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
  208. package/src/resources/extensions/gsd/prompts/plan-milestone.md +3 -1
  209. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  210. package/src/resources/extensions/gsd/prompts/quick-task.md +1 -5
  211. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
  212. package/src/resources/extensions/gsd/quick.ts +37 -2
  213. package/src/resources/extensions/gsd/safety/evidence-collector.ts +11 -2
  214. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +72 -1
  215. package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +56 -13
  216. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +14 -1
  217. package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +88 -2
  218. package/src/resources/extensions/gsd/tests/compaction-snapshot.test.ts +14 -1
  219. package/src/resources/extensions/gsd/tests/context-budget.test.ts +10 -1
  220. package/src/resources/extensions/gsd/tests/crash-handler-secondary.test.ts +55 -0
  221. package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +22 -0
  222. package/src/resources/extensions/gsd/tests/detection.test.ts +140 -0
  223. package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +313 -0
  224. package/src/resources/extensions/gsd/tests/exec-history.test.ts +15 -0
  225. package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +65 -0
  226. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +54 -0
  227. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +234 -0
  228. package/src/resources/extensions/gsd/tests/memory-decay-factor.test.ts +90 -0
  229. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +48 -0
  230. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +127 -0
  231. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +50 -0
  232. package/src/resources/extensions/gsd/tests/plan-task.test.ts +21 -0
  233. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +38 -0
  234. package/src/resources/extensions/gsd/tests/prompt-path-audit.test.ts +40 -0
  235. package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +19 -0
  236. package/src/resources/extensions/gsd/tests/quick-external-gsd.test.ts +40 -0
  237. package/src/resources/extensions/gsd/tests/right-sized-workflow-prompts.test.ts +192 -0
  238. package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +29 -0
  239. package/src/resources/extensions/gsd/tests/schema-v27-v28-sequence.test.ts +156 -0
  240. package/src/resources/extensions/gsd/tests/signal-handlers.test.ts +27 -0
  241. package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +38 -0
  242. package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +49 -1
  243. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +101 -2
  244. package/src/resources/extensions/gsd/tests/status-db-open.test.ts +9 -0
  245. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +136 -4
  246. package/src/resources/extensions/gsd/tests/unit-dispatches.test.ts +30 -0
  247. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +37 -0
  248. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +3 -0
  249. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +37 -6
  250. package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +7 -0
  251. package/src/resources/extensions/gsd/tests/worktree-nested-git-safety.test.ts +9 -2
  252. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +63 -1
  253. package/src/resources/extensions/gsd/tools/context-mode-tool-result.ts +25 -0
  254. package/src/resources/extensions/gsd/tools/exec-search-tool.ts +7 -7
  255. package/src/resources/extensions/gsd/tools/exec-tool.ts +4 -23
  256. package/src/resources/extensions/gsd/tools/memory-tools.ts +1 -0
  257. package/src/resources/extensions/gsd/tools/plan-slice.ts +13 -0
  258. package/src/resources/extensions/gsd/tools/plan-task.ts +10 -0
  259. package/src/resources/extensions/gsd/tools/resume-tool.ts +7 -7
  260. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +1 -1
  261. package/src/resources/extensions/gsd/unit-context-composer.ts +19 -4
  262. package/src/resources/extensions/gsd/unit-runtime.ts +25 -0
  263. package/src/resources/extensions/gsd/worktree-manager.ts +15 -4
  264. package/src/resources/extensions/gsd/worktree-resolver.ts +36 -15
  265. /package/dist/web/standalone/.next/static/{TCSim36ZpcPu2WgeoC45g → -5nHJWzSdG-WkPMul_khA}/_buildManifest.js +0 -0
  266. /package/dist/web/standalone/.next/static/{TCSim36ZpcPu2WgeoC45g → -5nHJWzSdG-WkPMul_khA}/_ssgManifest.js +0 -0
@@ -22,6 +22,7 @@ import { bumpTurnGeneration } from "./turn-epoch.js";
22
22
 
23
23
  let _currentResolve: ((result: UnitResult) => void) | null = null;
24
24
  let _sessionSwitchInFlight = false;
25
+ let _pendingSwitchCancellation: { errorContext?: ErrorContext } | null = null;
25
26
 
26
27
  // ─── Setters (needed for cross-module mutation) ─────────────────────────────
27
28
 
@@ -37,6 +38,12 @@ export function _clearCurrentResolve(): void {
37
38
  _currentResolve = null;
38
39
  }
39
40
 
41
+ export function _consumePendingSwitchCancellation(): { errorContext?: ErrorContext } | null {
42
+ const pending = _pendingSwitchCancellation;
43
+ _pendingSwitchCancellation = null;
44
+ return pending;
45
+ }
46
+
40
47
  // ─── resolveAgentEnd ─────────────────────────────────────────────────────────
41
48
 
42
49
  /**
@@ -96,7 +103,7 @@ export function bumpAndResolveSynthetic(reason: string): void {
96
103
  * blocks to ensure the autoLoop is never stuck awaiting a promise that
97
104
  * will never resolve. Safe to call when no resolver is pending (no-op).
98
105
  */
99
- export function resolveAgentEndCancelled(errorContext?: ErrorContext): void {
106
+ export function resolveAgentEndCancelled(errorContext?: ErrorContext): boolean {
100
107
  if (_currentResolve) {
101
108
  // Cancellation supersedes the in-flight turn the same way timeout
102
109
  // recovery does — bump the turn epoch so any lingering writes from the
@@ -107,8 +114,22 @@ export function resolveAgentEndCancelled(errorContext?: ErrorContext): void {
107
114
  debugLog("resolveAgentEndCancelled", { status: "resolving-cancelled" });
108
115
  const r = _currentResolve;
109
116
  _currentResolve = null;
117
+ _pendingSwitchCancellation = null;
110
118
  r({ status: "cancelled", ...(errorContext ? { errorContext } : {}) });
119
+ return true;
120
+ }
121
+
122
+ if (_sessionSwitchInFlight) {
123
+ bumpTurnGeneration(
124
+ `cancelled-during-switch:${errorContext?.category ?? "unknown"}`,
125
+ );
126
+ _pendingSwitchCancellation = errorContext ? { errorContext } : {};
127
+ debugLog("resolveAgentEndCancelled", { status: "queued-during-switch" });
128
+ return false;
111
129
  }
130
+
131
+ debugLog("resolveAgentEndCancelled", { status: "no-pending-resolve" });
132
+ return false;
112
133
  }
113
134
 
114
135
  // ─── resetPendingResolve (test helper) ───────────────────────────────────────
@@ -120,6 +141,7 @@ export function resolveAgentEndCancelled(errorContext?: ErrorContext): void {
120
141
  export function _resetPendingResolve(): void {
121
142
  _currentResolve = null;
122
143
  _sessionSwitchInFlight = false;
144
+ _pendingSwitchCancellation = null;
123
145
  }
124
146
 
125
147
  export function _hasPendingResolveForTest(): boolean {
@@ -11,7 +11,12 @@ import type { ExtensionAPI, ExtensionContext } from "@gsd/pi-coding-agent";
11
11
  import type { AutoSession } from "./session.js";
12
12
  import { NEW_SESSION_TIMEOUT_MS } from "./session.js";
13
13
  import type { UnitResult } from "./types.js";
14
- import { _clearCurrentResolve, _setCurrentResolve, _setSessionSwitchInFlight } from "./resolve.js";
14
+ import {
15
+ _clearCurrentResolve,
16
+ _consumePendingSwitchCancellation,
17
+ _setCurrentResolve,
18
+ _setSessionSwitchInFlight,
19
+ } from "./resolve.js";
15
20
  import {
16
21
  getCurrentTurnGeneration,
17
22
  runWithTurnGeneration,
@@ -97,6 +102,7 @@ export async function runUnit(
97
102
  sessionResult = await Promise.race([sessionPromise, timeoutPromise]);
98
103
  } catch (sessionErr) {
99
104
  if (sessionTimeoutHandle) clearTimeout(sessionTimeoutHandle);
105
+ _consumePendingSwitchCancellation();
100
106
  const msg =
101
107
  sessionErr instanceof Error ? sessionErr.message : String(sessionErr);
102
108
  debugLog("runUnit", {
@@ -110,17 +116,20 @@ export async function runUnit(
110
116
  if (sessionTimeoutHandle) clearTimeout(sessionTimeoutHandle);
111
117
 
112
118
  if (sessionResult.cancelled) {
119
+ _consumePendingSwitchCancellation();
113
120
  debugLog("runUnit-session-timeout", { unitType, unitId });
114
121
  return { status: "cancelled", errorContext: { message: "Session creation timed out", category: "timeout", isTransient: true } };
115
122
  }
116
123
 
117
124
  if (!s.active) {
125
+ _consumePendingSwitchCancellation();
118
126
  return { status: "cancelled" };
119
127
  }
120
128
 
121
129
  if (s.currentUnitModel && typeof pi.setModel === "function") {
122
130
  const restored = await pi.setModel(s.currentUnitModel, { persist: false });
123
131
  if (!restored) {
132
+ _consumePendingSwitchCancellation();
124
133
  const message =
125
134
  `Failed to restore configured model ${s.currentUnitModel.provider}/${s.currentUnitModel.id} after session creation`;
126
135
  ctx.ui.notify(
@@ -145,6 +154,14 @@ export async function runUnit(
145
154
  const unitPromise = new Promise<UnitResult>((resolve) => {
146
155
  _setCurrentResolve(resolve);
147
156
  });
157
+ const pendingSwitchCancellation = _consumePendingSwitchCancellation();
158
+ if (pendingSwitchCancellation) {
159
+ _clearCurrentResolve();
160
+ return {
161
+ status: "cancelled",
162
+ ...(pendingSwitchCancellation.errorContext ? { errorContext: pendingSwitchCancellation.errorContext } : {}),
163
+ };
164
+ }
148
165
 
149
166
  // ── Provider request-readiness pre-check (#4555) ──
150
167
  // Verify the provider can accept requests before dispatching. If the token
@@ -43,7 +43,7 @@ import {
43
43
  import { regenerateIfMissing } from "./workflow-projections.js";
44
44
  import { syncStateToProjectRoot } from "./auto-worktree.js";
45
45
  import { normalizeWorktreePathForCompare } from "./worktree-root.js";
46
- import { isDbAvailable, getTask, getSlice, getMilestone, updateTaskStatus, _getAdapter } from "./gsd-db.js";
46
+ import { isDbAvailable, getTask, getSlice, getMilestone, updateTaskStatus, _getAdapter, getVerificationEvidence } from "./gsd-db.js";
47
47
  import { renderPlanCheckboxes } from "./markdown-renderer.js";
48
48
  import { consumeSignal } from "./session-status-io.js";
49
49
  import {
@@ -852,22 +852,22 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV
852
852
  }
853
853
 
854
854
  // Evidence cross-reference (execute-task only)
855
- // Verification evidence is passed via the complete-task tool call and
856
- // stored in the SUMMARY.md on disk not available as structured data
857
- // in the DB. The evidence collector tracks actual bash tool calls, so
858
- // we can still detect units that claimed success but ran no commands.
855
+ // Only compare against concrete command evidence persisted by the task
856
+ // completion tool. A prose Verify field can be satisfied later by the
857
+ // host verification gate, so it is not enough to accuse the unit.
859
858
  if (safetyConfig.evidence_cross_reference && s.currentUnit.type === "execute-task") {
860
859
  try {
861
860
  const actual = getEvidence();
862
861
  const bashCalls = actual.filter(e => e.kind === "bash");
863
- // If the task is marked complete but zero bash commands were run,
864
- // it's suspicious — the LLM may have fabricated results.
865
862
  if (sMid && sSid && sTid && isDbAvailable()) {
866
863
  const taskRow = getTask(sMid, sSid, sTid);
867
- if (taskRow?.status === "complete" && taskRow.verify && bashCalls.length === 0) {
868
- logWarning("safety", "task marked complete with verification commands but no bash calls were executed");
864
+ const claimedCommands = getVerificationEvidence(sMid, sSid, sTid)
865
+ .map((row) => row.command)
866
+ .filter((command): command is string => typeof command === "string" && command.trim().length > 0);
867
+ if (taskRow?.status === "complete" && claimedCommands.length > 0 && bashCalls.length === 0) {
868
+ logWarning("safety", "task claimed verification command evidence but no execution tool calls were recorded");
869
869
  ctx.ui.notify(
870
- `Safety: task ${sTid} has verification commands but no bash calls were recorded`,
870
+ `Safety: task ${sTid} claimed command evidence but no execution tool calls were recorded`,
871
871
  "warning",
872
872
  );
873
873
  }
@@ -8,7 +8,7 @@
8
8
 
9
9
  import { loadFile, parseContinue, parseSummary, loadActiveOverrides, formatOverridesSection, parseTaskPlanFile } from "./files.js";
10
10
  import type { Override, UatType } from "./files.js";
11
- import { hasVerdict, getUatType } from "./verdict-parser.js";
11
+ import { hasVerdict, getUatType, extractVerdict } from "./verdict-parser.js";
12
12
  import { loadPrompt, inlineTemplate } from "./prompt-loader.js";
13
13
  import {
14
14
  resolveMilestoneFile, resolveSliceFile, resolveSlicePath,
@@ -35,10 +35,12 @@ import {
35
35
  import { formatDecisionsCompact, formatRequirementsCompact } from "./structured-data-formatter.js";
36
36
  import { readPhaseAnchor, formatAnchorForPrompt } from "./phase-anchor.js";
37
37
  import { composeContextModeInstructions, composeInlinedContext, type ArtifactResolver, type ContextModeRenderMode } from "./unit-context-composer.js";
38
+ import { readCompactionSnapshot } from "./compaction-snapshot.js";
38
39
  import { logWarning } from "./workflow-logger.js";
39
40
  import { inlineGraphSubgraph } from "./graph-context.js";
40
41
  import { buildExtractionStepsBlock } from "./commands-extract-learnings.js";
41
42
  import { resolveSkillManifest, warnIfManifestHasMissingSkills } from "./skill-manifest.js";
43
+ import { classifyProject, type ProjectClassification } from "./detection.js";
42
44
 
43
45
  // ─── Preamble Cap ─────────────────────────────────────────────────────────────
44
46
 
@@ -80,6 +82,108 @@ function resolveSummaryBudgetChars(): number {
80
82
  return resolvePromptBudgets().summaryBudgetChars;
81
83
  }
82
84
 
85
+ function formatProjectClassificationForPlanning(classification: ProjectClassification): string {
86
+ const sampleFiles = classification.contentFiles.slice(0, 8);
87
+ const sample = sampleFiles.length > 0 ? sampleFiles.map((file) => `\`${file}\``).join(", ") : "(none)";
88
+ const lines = [
89
+ "### Project Classification",
90
+ "",
91
+ `- **Kind:** ${classification.kind}`,
92
+ `- **Content files:** ${classification.contentFiles.length}`,
93
+ `- **Sample files:** ${sample}`,
94
+ `- **Reason:** ${classification.reason}`,
95
+ "",
96
+ ];
97
+
98
+ if (classification.kind === "untyped-existing") {
99
+ if (classification.contentFiles.length <= 2) {
100
+ lines.push(
101
+ "**Workflow sizing:** This is a tiny existing untyped project. Prefer exactly one slice unless the milestone request clearly spans multiple independent user-visible capabilities.",
102
+ );
103
+ } else if (classification.contentFiles.length <= 5) {
104
+ lines.push(
105
+ "**Workflow sizing:** This is a small existing untyped project. Prefer 1-2 slices unless the milestone request clearly spans multiple independent user-visible capabilities.",
106
+ );
107
+ } else {
108
+ lines.push(
109
+ "**Workflow sizing:** Existing untyped project. Use generic file-level workflow guidance and size slices by real capability boundaries, not by missing tooling markers.",
110
+ );
111
+ }
112
+ } else if (classification.kind === "greenfield") {
113
+ lines.push("**Workflow sizing:** No project content exists yet. Use normal greenfield sizing for the requested scope.");
114
+ } else if (classification.kind === "typed-existing") {
115
+ lines.push("**Workflow sizing:** Known project markers exist. Use normal ecosystem-aware planning guidance.");
116
+ } else {
117
+ lines.push("**Workflow sizing:** Invalid repository state. Planning should surface this as a blocker rather than inventing project structure.");
118
+ }
119
+
120
+ return lines.join("\n");
121
+ }
122
+
123
+ function normalizeArtifactRef(value: string): string {
124
+ return value.trim().replace(/^[-\s]+/, "").replace(/^["'`]+|["'`]+$/g, "").replaceAll("\\", "/").replace(/^\.\//, "");
125
+ }
126
+
127
+ function parseCoveredArtifacts(validationContent: string): Set<string> {
128
+ const covered = new Set<string>();
129
+ const lines = validationContent.split(/\r?\n/);
130
+ let inCoveredArtifacts = false;
131
+ for (const line of lines) {
132
+ if (/^\s*covered[-_]?artifacts\s*:/i.test(line)) {
133
+ inCoveredArtifacts = true;
134
+ const inline = line.split(/covered[-_]?artifacts\s*:/i)[1]?.trim();
135
+ if (inline && inline !== "[]") {
136
+ inline.replace(/^\[|\]$/g, "").split(",").map(normalizeArtifactRef).filter(Boolean).forEach((item) => covered.add(item));
137
+ }
138
+ continue;
139
+ }
140
+ if (!inCoveredArtifacts) continue;
141
+ if (/^\S/.test(line) && !/^\s*-/.test(line)) break;
142
+ const item = line.match(/^\s*-\s*(.+)$/)?.[1];
143
+ if (item) covered.add(normalizeArtifactRef(item));
144
+ }
145
+ return covered;
146
+ }
147
+
148
+ function isValidationFreshOrApplicable(validationContent: string | null, currentArtifacts: string[]): boolean {
149
+ if (!validationContent) return false;
150
+ if (!/validation_metadata:/i.test(validationContent)) return false;
151
+ const coveredArtifacts = parseCoveredArtifacts(validationContent);
152
+ if (coveredArtifacts.size === 0) return false;
153
+ return currentArtifacts
154
+ .map(normalizeArtifactRef)
155
+ .filter(Boolean)
156
+ .every((artifact) => coveredArtifacts.has(artifact));
157
+ }
158
+
159
+ function formatCloseoutReviewInstructions(validationContent: string | null, validationRel: string, currentArtifacts: string[]): string {
160
+ const verdict = validationContent ? extractVerdict(validationContent) : null;
161
+ const validationFresh = isValidationFreshOrApplicable(validationContent, currentArtifacts);
162
+ if (verdict === "pass" && validationFresh) {
163
+ return [
164
+ "### Passing Validation Artifact",
165
+ "",
166
+ `A passing validation artifact is present at \`${validationRel}\`. Treat it as authoritative for success criteria, requirement coverage, verification classes, and cross-slice integration.`,
167
+ "",
168
+ "Do not delegate fresh reviewer/security/tester audits and do not redo the validation evidence review unless the artifact is internally inconsistent with the inlined summaries. Focus this unit on final milestone narrative, learnings, PROJECT/requirements updates, and `gsd_complete_milestone`.",
169
+ ].join("\n");
170
+ }
171
+
172
+ if (verdict) {
173
+ return [
174
+ "### Validation Requires Attention",
175
+ "",
176
+ `A validation artifact is present at \`${validationRel}\` with verdict \`${verdict}\`, but it is missing freshness metadata or does not cover current milestone artifacts. Do not treat the milestone as complete unless the issues are resolved and evidence supports completion.`,
177
+ ].join("\n");
178
+ }
179
+
180
+ return [
181
+ "### No Passing Validation Artifact",
182
+ "",
183
+ `No passing validation artifact was found at \`${validationRel}\`. Use the full closeout review path before completion.`,
184
+ ].join("\n");
185
+ }
186
+
83
187
  function capPreamble(preamble: string): string {
84
188
  // Cap inlined context at min(historical 30K ceiling, scaled inline budget).
85
189
  // The ceiling preserves pre-fix behavior for large-window users; the scaled
@@ -102,13 +206,28 @@ function renderContextModeForPrompt(
102
206
  });
103
207
  }
104
208
 
209
+ function renderContextModeBlockForPrompt(
210
+ unitType: string,
211
+ base: string,
212
+ renderMode: ContextModeRenderMode = "standalone",
213
+ ): string {
214
+ const contextMode = renderContextModeForPrompt(unitType, base, renderMode);
215
+ if (!contextMode) return "";
216
+ if (renderMode === "nested") return contextMode;
217
+
218
+ const snapshot = readCompactionSnapshot(base);
219
+ if (!snapshot?.trim()) return contextMode;
220
+
221
+ return `${contextMode}\n\n## Context Snapshot\nSource: \`.gsd/last-snapshot.md\`\n\n${snapshot.trimEnd()}`;
222
+ }
223
+
105
224
  function prependContextModeToBlock(
106
225
  unitType: string,
107
226
  base: string,
108
227
  block: string,
109
228
  renderMode: ContextModeRenderMode = "standalone",
110
229
  ): string {
111
- const contextMode = renderContextModeForPrompt(unitType, base, renderMode);
230
+ const contextMode = renderContextModeBlockForPrompt(unitType, base, renderMode);
112
231
  if (!contextMode) return block;
113
232
  if (!block.trim()) return contextMode;
114
233
  return `${contextMode}\n\n${block}`;
@@ -1658,6 +1777,8 @@ export async function buildPlanMilestonePrompt(mid: string, midTitle: string, ba
1658
1777
  const researchAnchor = readPhaseAnchor(base, mid, "research-milestone");
1659
1778
  if (researchAnchor) inlined.push(formatAnchorForPrompt(researchAnchor));
1660
1779
 
1780
+ inlined.push(formatProjectClassificationForPlanning(classifyProject(base)));
1781
+
1661
1782
  inlined.push(await inlineFile(contextPath, contextRel, "Milestone Context"));
1662
1783
  const researchInline = await inlineFileOptional(researchPath, researchRel, "Milestone Research");
1663
1784
  if (researchInline) inlined.push(researchInline);
@@ -2348,6 +2469,9 @@ export async function buildCompleteMilestonePrompt(
2348
2469
  const inlineLevel = level ?? resolveInlineLevel();
2349
2470
  const roadmapPath = resolveMilestoneFile(base, mid, "ROADMAP");
2350
2471
  const roadmapRel = relMilestoneFile(base, mid, "ROADMAP");
2472
+ const validationPath = resolveMilestoneFile(base, mid, "VALIDATION");
2473
+ const validationRel = relMilestoneFile(base, mid, "VALIDATION");
2474
+ const validationContent = validationPath ? await loadFile(validationPath) : null;
2351
2475
 
2352
2476
  const inlined: string[] = [];
2353
2477
  inlined.push(await inlineFile(roadmapPath, roadmapRel, "Milestone Roadmap"));
@@ -2389,6 +2513,13 @@ export async function buildCompleteMilestonePrompt(
2389
2513
  `### On-demand Slice Summaries\n\nExcerpted above. Read the full file for any slice when the excerpt's section heads don't carry enough narrative for the milestone summary you're drafting:\n\n${pathList}`,
2390
2514
  );
2391
2515
  }
2516
+ const validationContext = [
2517
+ formatCloseoutReviewInstructions(validationContent, validationRel, [validationRel, roadmapRel, ...summaryRelPaths]),
2518
+ ];
2519
+ if (validationContent) {
2520
+ validationContext.push(`### Milestone Validation\nSource: \`${validationRel}\`\n\n${validationContent.trim()}`);
2521
+ }
2522
+ inlined.unshift(...validationContext);
2392
2523
 
2393
2524
  // Inline root GSD files (skip for minimal — completion can read these if needed)
2394
2525
  if (inlineLevel !== "minimal") {
@@ -67,6 +67,60 @@ export {
67
67
 
68
68
  // ─── Artifact Resolution & Verification ───────────────────────────────────────
69
69
 
70
+ export type ArtifactRecoveryDbRefreshResult =
71
+ | { ok: true }
72
+ | { ok: false; fatal: boolean; message: string; reason: string };
73
+
74
+ export function refreshRecoveryDbForArtifact(
75
+ unitType: string,
76
+ unitId: string,
77
+ ): ArtifactRecoveryDbRefreshResult {
78
+ if (unitType !== "plan-slice" && unitType !== "execute-task") return { ok: true };
79
+ if (!isDbAvailable()) return { ok: true };
80
+
81
+ if (!refreshOpenDatabaseFromDisk()) {
82
+ return {
83
+ ok: false,
84
+ fatal: unitType === "execute-task",
85
+ reason: `${unitType}-db-refresh-failed`,
86
+ message: `Stuck recovery found ${unitType} ${unitId} artifacts, but the DB refresh failed.`,
87
+ };
88
+ }
89
+
90
+ if (unitType !== "execute-task") return { ok: true };
91
+
92
+ const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
93
+ if (!mid || !sid || !tid) {
94
+ return {
95
+ ok: false,
96
+ fatal: true,
97
+ reason: "execute-task-invalid-unit-id",
98
+ message: `Stuck recovery found execute-task ${unitId} artifacts, but the unit id could not be parsed for DB verification.`,
99
+ };
100
+ }
101
+
102
+ const task = getTask(mid, sid, tid);
103
+ if (!task) {
104
+ return {
105
+ ok: false,
106
+ fatal: true,
107
+ reason: "execute-task-artifact-db-missing",
108
+ message: `Stuck recovery found execute-task ${unitId} artifacts, but no matching DB task row exists after refresh.`,
109
+ };
110
+ }
111
+
112
+ if (!isClosedStatus(task.status)) {
113
+ return {
114
+ ok: false,
115
+ fatal: true,
116
+ reason: "execute-task-artifact-db-mismatch",
117
+ message: `Stuck recovery found execute-task ${unitId} artifacts, but the DB task status is still '${task.status}' after refresh.`,
118
+ };
119
+ }
120
+
121
+ return { ok: true };
122
+ }
123
+
70
124
  function hasCapturedWorkflowPrefs(base: string): boolean {
71
125
  const prefsPath = resolveExpectedArtifactPath("workflow-preferences", "WORKFLOW-PREFS", base);
72
126
  if (!prefsPath || !existsSync(prefsPath)) return false;
@@ -32,6 +32,7 @@ let _currentSigtermHandler: (() => void) | null = null;
32
32
  export function registerSigtermHandler(
33
33
  currentBasePath: string,
34
34
  previousHandler: (() => void) | null,
35
+ onSignalCleanup?: () => void,
35
36
  ): () => void {
36
37
  // Remove the explicitly-passed previous handler
37
38
  if (previousHandler) {
@@ -43,6 +44,12 @@ export function registerSigtermHandler(
43
44
  for (const sig of CLEANUP_SIGNALS) process.off(sig, _currentSigtermHandler);
44
45
  }
45
46
  const handler = () => {
47
+ try {
48
+ onSignalCleanup?.();
49
+ } catch {
50
+ void 0;
51
+ // Signal cleanup is best-effort; lock cleanup and process exit still run.
52
+ }
46
53
  clearLock(currentBasePath);
47
54
  releaseSessionLock(currentBasePath);
48
55
  process.exit(0);
@@ -71,14 +71,14 @@ export async function recoverTimedOutUnit(
71
71
  recovery: status,
72
72
  });
73
73
 
74
- const durableComplete = status.summaryExists && status.taskChecked && status.nextActionAdvanced;
74
+ const durableComplete = status.dbComplete || (status.summaryExists && status.taskChecked && status.nextActionAdvanced);
75
75
  if (durableComplete) {
76
76
  writeUnitRuntimeRecord(basePath, unitType, unitId, currentUnitStartedAt, {
77
77
  phase: "finalized",
78
78
  recovery: status,
79
79
  });
80
80
  ctx.ui.notify(
81
- `${reason === "idle" ? "Idle" : "Timeout"} recovery: ${unitType} ${unitId} already completed on disk. Continuing auto-mode. (attempt ${attemptNumber})`,
81
+ `${reason === "idle" ? "Idle" : "Timeout"} recovery: ${unitType} ${unitId} already completed. Continuing auto-mode. (attempt ${attemptNumber})`,
82
82
  "info",
83
83
  );
84
84
  unitRecoveryCount.delete(recoveryKey);
@@ -59,6 +59,7 @@ import {
59
59
  isLockProcessAlive,
60
60
  formatCrashInfo,
61
61
  emitCrashRecoveredUnitEnd,
62
+ emitOpenUnitEndForUnit,
62
63
  } from "./crash-recovery.js";
63
64
  import {
64
65
  acquireSessionLock,
@@ -200,6 +201,8 @@ import {
200
201
  detectWorkingTreeActivity,
201
202
  } from "./auto-supervisor.js";
202
203
  import { isDbAvailable, getMilestone } from "./gsd-db.js";
204
+ import { markLatestActiveForWorkerCanceled } from "./db/unit-dispatches.js";
205
+ import { writeUnitRuntimeRecord } from "./unit-runtime.js";
203
206
  import { countPendingCaptures } from "./captures.js";
204
207
  import { CMUX_CHANNELS, type CmuxLogLevel } from "../shared/cmux-events.js";
205
208
  import { ensureDbOpen } from "./bootstrap/dynamic-tools.js";
@@ -242,6 +245,20 @@ import {
242
245
  type WorktreeResolverDeps,
243
246
  } from "./worktree-resolver.js";
244
247
  import { reorderForCaching } from "./prompt-ordering.js";
248
+ import { initTokenCounter } from "./token-counter.js";
249
+
250
+ // Warm the tiktoken encoder at extension startup so context-budget computations
251
+ // can use accurate token counts via countTokensSync without paying the load
252
+ // cost mid-prompt-build. Fire-and-forget — failure falls back to the
253
+ // provider-aware char-ratio estimator already used by getCharsPerToken().
254
+ // Catch rejections explicitly: an unhandled rejection at module-import time
255
+ // can destabilize startup before the engine logger is configured.
256
+ void initTokenCounter().catch((err) => {
257
+ logWarning(
258
+ "engine",
259
+ `token counter warm-up failed: ${err instanceof Error ? err.message : String(err)}`,
260
+ );
261
+ });
245
262
 
246
263
  // ─── Session State ─────────────────────────────────────────────────────────
247
264
 
@@ -293,11 +310,15 @@ const STATE_REBUILD_MIN_INTERVAL_MS = 30_000;
293
310
  * the DB is unavailable (e.g. fresh project before init) we skip registration
294
311
  * silently rather than blocking session start.
295
312
  */
296
- function registerAutoWorkerForSession(session: AutoSession): void {
313
+ function registerAutoWorkerForSession(
314
+ session: AutoSession,
315
+ projectRootOverride?: string,
316
+ ): void {
297
317
  if (session.workerId) return; // already registered (e.g. resume re-runs)
298
318
  try {
299
319
  const projectRootRealpath = normalizeRealPath(
300
- session.scope?.workspace.projectRoot
320
+ projectRootOverride
321
+ ?? session.scope?.workspace.projectRoot
301
322
  ?? (session.originalBasePath || session.basePath),
302
323
  );
303
324
  session.workerId = registerAutoWorker({ projectRootRealpath });
@@ -420,6 +441,17 @@ export function _synthesizePausedSessionRecoveryForTest(
420
441
  return synthesizePausedSessionRecovery(basePath, unitType, unitId, sessionFile);
421
442
  }
422
443
 
444
+ const DETACHED_AUTO_KEEPALIVE_INTERVAL_MS = 30_000;
445
+
446
+ function withDetachedAutoKeepalive<T>(run: Promise<T>): Promise<T> {
447
+ const keepAlive = setInterval(() => {}, DETACHED_AUTO_KEEPALIVE_INTERVAL_MS);
448
+ return run.finally(() => {
449
+ clearInterval(keepAlive);
450
+ });
451
+ }
452
+
453
+ export const _withDetachedAutoKeepaliveForTest = withDetachedAutoKeepalive;
454
+
423
455
  export function startAutoDetached(
424
456
  ctx: ExtensionCommandContext,
425
457
  pi: ExtensionAPI,
@@ -431,7 +463,7 @@ export function startAutoDetached(
431
463
  milestoneLock?: string | null;
432
464
  },
433
465
  ): void {
434
- void startAuto(ctx, pi, base, verboseMode, options).catch((err) => {
466
+ void withDetachedAutoKeepalive(startAuto(ctx, pi, base, verboseMode, options)).catch((err) => {
435
467
  const message = getErrorMessage(err);
436
468
  ctx.ui.notify(`Auto-start failed: ${message}`, "error");
437
469
  logWarning("engine", `auto start error: ${message}`, { file: "auto.ts" });
@@ -490,9 +522,54 @@ export {
490
522
  getBudgetEnforcementAction,
491
523
  } from "./auto-budget.js";
492
524
 
525
+ function closeOutSignalInterruptedUnit(currentBasePath: string): void {
526
+ const currentUnit = s.currentUnit;
527
+ if (!currentUnit) return;
528
+
529
+ const reason = "Auto-mode process received a termination signal";
530
+ const errorContext: ErrorContext = {
531
+ message: reason,
532
+ category: "aborted",
533
+ isTransient: false,
534
+ };
535
+ const basePath = s.basePath || currentBasePath;
536
+
537
+ try {
538
+ emitOpenUnitEndForUnit(basePath, currentUnit.type, currentUnit.id, "cancelled", errorContext);
539
+ } catch (err) {
540
+ logWarning("engine", `signal unit-end cleanup failed: ${getErrorMessage(err)}`, { file: "auto.ts" });
541
+ }
542
+
543
+ try {
544
+ writeUnitRuntimeRecord(basePath, currentUnit.type, currentUnit.id, currentUnit.startedAt, {
545
+ phase: "crashed",
546
+ lastProgressAt: Date.now(),
547
+ lastProgressKind: "signal",
548
+ });
549
+ } catch (err) {
550
+ logWarning("engine", `signal runtime cleanup failed: ${getErrorMessage(err)}`, { file: "auto.ts" });
551
+ }
552
+
553
+ try {
554
+ if (s.workerId) markLatestActiveForWorkerCanceled(s.workerId, "signal-exit");
555
+ } catch (err) {
556
+ logWarning("engine", `signal dispatch cleanup failed: ${getErrorMessage(err)}`, { file: "auto.ts" });
557
+ }
558
+
559
+ try {
560
+ resolveAgentEndCancelled(errorContext);
561
+ } catch (err) {
562
+ logWarning("engine", `signal resolve cleanup failed: ${getErrorMessage(err)}`, { file: "auto.ts" });
563
+ }
564
+ }
565
+
493
566
  /** Wrapper: register SIGTERM handler and store reference. */
494
567
  function registerSigtermHandler(currentBasePath: string): void {
495
- s.sigtermHandler = _registerSigtermHandler(currentBasePath, s.sigtermHandler);
568
+ s.sigtermHandler = _registerSigtermHandler(
569
+ currentBasePath,
570
+ s.sigtermHandler,
571
+ () => closeOutSignalInterruptedUnit(currentBasePath),
572
+ );
496
573
  }
497
574
 
498
575
  /** Wrapper: deregister SIGTERM handler and clear reference. */
@@ -957,6 +1034,8 @@ export async function stopAuto(
957
1034
  if (s.workerId) {
958
1035
  markWorkerStopping(s.workerId);
959
1036
  }
1037
+ s.workerId = null;
1038
+ s.milestoneLeaseToken = null;
960
1039
  } catch (e) {
961
1040
  debugLog("stop-cleanup-coordination", { error: e instanceof Error ? e.message : String(e) });
962
1041
  }
@@ -1960,6 +2039,10 @@ export async function startAuto(
1960
2039
  : new URL("../../../resource-loader.js", import.meta.url).href;
1961
2040
  const { initResources } = await import(resourceLoaderPath);
1962
2041
  initResources(agentDir);
2042
+ // initResources() uses synchronous fs APIs, so the prompt-template cache
2043
+ // can be primed immediately — no need for the legacy 1s setTimeout deferral.
2044
+ const { primeCache } = await import("./prompt-loader.js");
2045
+ primeCache();
1963
2046
  // Open the project DB before rebuild/derive so resume uses DB-backed
1964
2047
  // state instead of falling back to stale markdown parsing (#2940).
1965
2048
  await openProjectDbIfPresent(s.basePath);
@@ -2046,6 +2129,11 @@ export async function startAuto(
2046
2129
  buildResolver,
2047
2130
  };
2048
2131
 
2132
+ // Register the worker before bootstrap enters a milestone worktree.
2133
+ // This ensures enterMilestone can claim a lease and seed dispatch claims
2134
+ // for crash-recovery fidelity (#5405).
2135
+ registerAutoWorkerForSession(s, base);
2136
+
2049
2137
  const ready = await bootstrapAutoSession(
2050
2138
  s,
2051
2139
  ctx,
@@ -153,9 +153,29 @@ export async function handleAgentEnd(
153
153
  if (maybeHandleEmptyIntentTurn(event, isAutoActive())) return;
154
154
 
155
155
  if (!isAutoActive()) return;
156
- if (isSessionSwitchInFlight()) return;
157
156
 
158
157
  const lastMsg = event.messages[event.messages.length - 1];
158
+ if (isSessionSwitchInFlight()) {
159
+ if (lastMsg && "stopReason" in lastMsg && lastMsg.stopReason === "error") {
160
+ const rawErrorMsg = ("errorMessage" in lastMsg && lastMsg.errorMessage) ? String(lastMsg.errorMessage) : "";
161
+ if (isUserInitiatedAbortMessage(rawErrorMsg)) {
162
+ resolveAgentEndCancelled({
163
+ message: rawErrorMsg,
164
+ category: "aborted",
165
+ isTransient: false,
166
+ });
167
+ }
168
+ } else if (lastMsg && "stopReason" in lastMsg && lastMsg.stopReason === "aborted") {
169
+ const content = "content" in lastMsg ? lastMsg.content : undefined;
170
+ const hasEmptyContent = Array.isArray(content) && content.length === 0;
171
+ const hasErrorMessage = "errorMessage" in lastMsg && !!lastMsg.errorMessage;
172
+ if (!hasEmptyContent || hasErrorMessage) {
173
+ resolveAgentEndCancelled(_buildAbortedPauseContext(lastMsg as { errorMessage?: unknown }));
174
+ }
175
+ }
176
+ return;
177
+ }
178
+
159
179
  if (lastMsg && "stopReason" in lastMsg && lastMsg.stopReason === "aborted") {
160
180
  // Empty content with aborted stopReason is a non-fatal agent stop (the LLM
161
181
  // chose to end without producing output). Only pause on genuine fatal aborts