gsd-pi 2.79.0-dev.ece5fd8ba → 2.80.0-dev.c5f2443b3

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 (502) hide show
  1. package/dist/resources/.managed-resources-content-hash +1 -1
  2. package/dist/resources/extensions/github-sync/templates.js +55 -70
  3. package/dist/resources/extensions/gsd/auto/contracts.js +1 -0
  4. package/dist/resources/extensions/gsd/auto/custom-verify-retry-store.js +53 -0
  5. package/dist/resources/extensions/gsd/auto/loop.js +359 -523
  6. package/dist/resources/extensions/gsd/auto/orchestrator.js +146 -0
  7. package/dist/resources/extensions/gsd/auto/phases.js +55 -6
  8. package/dist/resources/extensions/gsd/auto/session.js +8 -0
  9. package/dist/resources/extensions/gsd/auto/workflow-custom-engine-dispatch-outcome.js +12 -0
  10. package/dist/resources/extensions/gsd/auto/workflow-custom-engine-iteration.js +24 -0
  11. package/dist/resources/extensions/gsd/auto/workflow-custom-engine-reconcile-outcome.js +33 -0
  12. package/dist/resources/extensions/gsd/auto/workflow-custom-engine-reconcile.js +26 -0
  13. package/dist/resources/extensions/gsd/auto/workflow-custom-engine-retry.js +49 -0
  14. package/dist/resources/extensions/gsd/auto/workflow-custom-engine-verify-outcome.js +25 -0
  15. package/dist/resources/extensions/gsd/auto/workflow-dispatch-claim.js +48 -0
  16. package/dist/resources/extensions/gsd/auto/workflow-dispatch-ledger.js +26 -0
  17. package/dist/resources/extensions/gsd/auto/workflow-iteration-completion.js +10 -0
  18. package/dist/resources/extensions/gsd/auto/workflow-journal-reporter.js +16 -0
  19. package/dist/resources/extensions/gsd/auto/workflow-kernel.js +263 -0
  20. package/dist/resources/extensions/gsd/auto/workflow-memory-pressure.js +36 -0
  21. package/dist/resources/extensions/gsd/auto/workflow-phase-reporter.js +9 -0
  22. package/dist/resources/extensions/gsd/auto/workflow-session-lock.js +35 -0
  23. package/dist/resources/extensions/gsd/auto/workflow-sidecar-iteration.js +24 -0
  24. package/dist/resources/extensions/gsd/auto/workflow-sidecar-queue.js +26 -0
  25. package/dist/resources/extensions/gsd/auto/workflow-turn-reporter.js +36 -0
  26. package/dist/resources/extensions/gsd/auto/workflow-unit-dispatch.js +44 -0
  27. package/dist/resources/extensions/gsd/auto/workflow-worker-heartbeat.js +15 -0
  28. package/dist/resources/extensions/gsd/auto-dashboard.js +3 -0
  29. package/dist/resources/extensions/gsd/auto-prompts.js +168 -3
  30. package/dist/resources/extensions/gsd/auto-recovery.js +45 -52
  31. package/dist/resources/extensions/gsd/auto-runtime-state.js +4 -0
  32. package/dist/resources/extensions/gsd/auto-verification.js +2 -11
  33. package/dist/resources/extensions/gsd/auto-worktree.js +87 -38
  34. package/dist/resources/extensions/gsd/auto.js +159 -2
  35. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +9 -1
  36. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +10 -0
  37. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +11 -5
  38. package/dist/resources/extensions/gsd/commands-ship.js +23 -46
  39. package/dist/resources/extensions/gsd/commands-workflow-templates.js +12 -7
  40. package/dist/resources/extensions/gsd/component-loader.js +5 -11
  41. package/dist/resources/extensions/gsd/custom-workflow-engine.js +4 -0
  42. package/dist/resources/extensions/gsd/db-adapter.js +47 -0
  43. package/dist/resources/extensions/gsd/db-base-schema.js +337 -0
  44. package/dist/resources/extensions/gsd/db-connection-cache.js +31 -0
  45. package/dist/resources/extensions/gsd/db-coordination-schema.js +104 -0
  46. package/dist/resources/extensions/gsd/db-decision-requirement-rows.js +71 -0
  47. package/dist/resources/extensions/gsd/db-gate-rows.js +16 -0
  48. package/dist/resources/extensions/gsd/db-lightweight-query-rows.js +29 -0
  49. package/dist/resources/extensions/gsd/db-memory-fts-schema.js +56 -0
  50. package/dist/resources/extensions/gsd/db-migration-backup.js +22 -0
  51. package/dist/resources/extensions/gsd/db-migration-steps.js +394 -0
  52. package/dist/resources/extensions/gsd/db-milestone-artifact-rows.js +35 -0
  53. package/dist/resources/extensions/gsd/db-open-state.js +32 -0
  54. package/dist/resources/extensions/gsd/db-provider.js +108 -0
  55. package/dist/resources/extensions/gsd/db-runtime-kv-schema.js +27 -0
  56. package/dist/resources/extensions/gsd/db-schema-metadata.js +23 -0
  57. package/dist/resources/extensions/gsd/db-task-slice-rows.js +86 -0
  58. package/dist/resources/extensions/gsd/db-transaction.js +63 -0
  59. package/dist/resources/extensions/gsd/db-verification-evidence-rows.js +3 -0
  60. package/dist/resources/extensions/gsd/db-verification-evidence-schema.js +19 -0
  61. package/dist/resources/extensions/gsd/escalation.js +2 -0
  62. package/dist/resources/extensions/gsd/gsd-db.js +215 -1519
  63. package/dist/resources/extensions/gsd/legacy-telemetry.js +70 -0
  64. package/dist/resources/extensions/gsd/markdown-renderer.js +2 -0
  65. package/dist/resources/extensions/gsd/model-router.js +9 -6
  66. package/dist/resources/extensions/gsd/notification-widget.js +21 -3
  67. package/dist/resources/extensions/gsd/post-execution-checks.js +27 -6
  68. package/dist/resources/extensions/gsd/pr-evidence.js +76 -0
  69. package/dist/resources/extensions/gsd/pre-execution-checks.js +2 -0
  70. package/dist/resources/extensions/gsd/process-task-path.js +61 -0
  71. package/dist/resources/extensions/gsd/prompt-loader.js +9 -5
  72. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +32 -30
  73. package/dist/resources/extensions/gsd/prompts/complete-slice.md +23 -34
  74. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +50 -96
  75. package/dist/resources/extensions/gsd/prompts/discuss.md +81 -181
  76. package/dist/resources/extensions/gsd/prompts/execute-task.md +40 -67
  77. package/dist/resources/extensions/gsd/prompts/forensics.md +41 -84
  78. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +29 -39
  79. package/dist/resources/extensions/gsd/prompts/guided-discuss-project.md +30 -65
  80. package/dist/resources/extensions/gsd/prompts/guided-discuss-requirements.md +25 -52
  81. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +36 -36
  82. package/dist/resources/extensions/gsd/prompts/guided-research-project.md +20 -38
  83. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +45 -59
  84. package/dist/resources/extensions/gsd/prompts/plan-slice.md +25 -87
  85. package/dist/resources/extensions/gsd/prompts/queue.md +46 -53
  86. package/dist/resources/extensions/gsd/prompts/refine-slice.md +23 -23
  87. package/dist/resources/extensions/gsd/prompts/research-slice.md +23 -23
  88. package/dist/resources/extensions/gsd/prompts/rethink.md +10 -10
  89. package/dist/resources/extensions/gsd/prompts/system.md +65 -107
  90. package/dist/resources/extensions/gsd/prompts/triage-captures.md +15 -15
  91. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +24 -24
  92. package/dist/resources/extensions/gsd/prompts/worktree-merge.md +35 -35
  93. package/dist/resources/extensions/gsd/state.js +4 -0
  94. package/dist/resources/extensions/gsd/tools/complete-milestone.js +14 -9
  95. package/dist/resources/extensions/gsd/tools/complete-task.js +2 -0
  96. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +6 -1
  97. package/dist/resources/extensions/gsd/unit-context-composer.js +1 -1
  98. package/dist/resources/extensions/gsd/uok/kernel.js +8 -3
  99. package/dist/resources/extensions/gsd/uok/plan-v2.js +2 -0
  100. package/dist/resources/extensions/gsd/workflow-logger.js +13 -13
  101. package/dist/resources/extensions/gsd/workflow-manifest.js +2 -0
  102. package/dist/resources/extensions/gsd/workflow-projections.js +2 -0
  103. package/dist/resources/extensions/gsd/workflow-templates.js +9 -0
  104. package/dist/resources/extensions/shared/interview-ui.js +15 -4
  105. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  106. package/dist/web/standalone/.next/BUILD_ID +1 -1
  107. package/dist/web/standalone/.next/app-path-routes-manifest.json +13 -13
  108. package/dist/web/standalone/.next/build-manifest.json +2 -2
  109. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  110. package/dist/web/standalone/.next/required-server-files.json +1 -1
  111. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  112. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  113. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  114. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  115. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  116. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  117. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  118. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  119. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  120. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  121. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  122. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  123. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  124. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  125. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  126. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  127. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
  129. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
  131. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
  133. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
  135. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
  137. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
  139. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
  141. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  142. package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
  143. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  144. package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  145. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  146. package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
  147. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  148. package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  149. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  150. package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
  151. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  152. package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
  153. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  154. package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
  155. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  156. package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
  157. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  158. package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
  159. package/dist/web/standalone/.next/server/app/api/notifications/route.js +1 -1
  160. package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  161. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  162. package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
  163. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  164. package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  165. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  166. package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
  167. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  168. package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
  169. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  170. package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
  171. package/dist/web/standalone/.next/server/app/api/session/events/route.js +1 -1
  172. package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
  173. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  174. package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
  175. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  176. package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
  177. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  178. package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
  179. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  180. package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
  181. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  182. package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
  183. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
  184. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
  185. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +1 -1
  186. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
  187. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  188. package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
  189. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  190. package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
  191. package/dist/web/standalone/.next/server/app/index.html +1 -1
  192. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  193. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  194. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  195. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  196. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  197. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  198. package/dist/web/standalone/.next/server/app-paths-manifest.json +13 -13
  199. package/dist/web/standalone/.next/server/chunks/167.js +2 -0
  200. package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
  201. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  202. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  203. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  204. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  205. package/dist/web/standalone/package.json +1 -0
  206. package/dist/web/standalone/server.js +1 -1
  207. package/package.json +9 -2
  208. package/packages/contracts/dist/index.d.ts +3 -0
  209. package/packages/contracts/dist/index.d.ts.map +1 -0
  210. package/packages/contracts/dist/index.js +5 -0
  211. package/packages/contracts/dist/index.js.map +1 -0
  212. package/packages/contracts/dist/rpc.d.ts +549 -0
  213. package/packages/contracts/dist/rpc.d.ts.map +1 -0
  214. package/packages/contracts/dist/rpc.js +53 -0
  215. package/packages/contracts/dist/rpc.js.map +1 -0
  216. package/packages/contracts/dist/rpc.test.d.ts +2 -0
  217. package/packages/contracts/dist/rpc.test.d.ts.map +1 -0
  218. package/packages/contracts/dist/rpc.test.js +47 -0
  219. package/packages/contracts/dist/rpc.test.js.map +1 -0
  220. package/packages/contracts/dist/workflow.d.ts +180 -0
  221. package/packages/contracts/dist/workflow.d.ts.map +1 -0
  222. package/packages/contracts/dist/workflow.js +201 -0
  223. package/packages/contracts/dist/workflow.js.map +1 -0
  224. package/packages/contracts/package.json +39 -0
  225. package/packages/contracts/src/index.ts +5 -0
  226. package/packages/contracts/src/rpc.test.ts +72 -0
  227. package/packages/contracts/src/rpc.ts +286 -0
  228. package/packages/contracts/src/workflow.ts +213 -0
  229. package/packages/contracts/tsconfig.json +25 -0
  230. package/packages/contracts/tsconfig.tsbuildinfo +1 -0
  231. package/packages/daemon/package.json +3 -2
  232. package/packages/daemon/src/event-bridge.test.ts +2 -1
  233. package/packages/daemon/src/event-bridge.ts +1 -1
  234. package/packages/daemon/src/event-formatter.test.ts +1 -2
  235. package/packages/daemon/src/event-formatter.ts +1 -2
  236. package/packages/daemon/src/session-manager.ts +2 -2
  237. package/packages/daemon/src/types.ts +3 -18
  238. package/packages/mcp-server/dist/server.d.ts +13 -0
  239. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  240. package/packages/mcp-server/dist/server.js +77 -0
  241. package/packages/mcp-server/dist/server.js.map +1 -1
  242. package/packages/mcp-server/dist/session-manager.js +1 -1
  243. package/packages/mcp-server/dist/session-manager.js.map +1 -1
  244. package/packages/mcp-server/dist/types.d.ts +3 -11
  245. package/packages/mcp-server/dist/types.d.ts.map +1 -1
  246. package/packages/mcp-server/dist/types.js.map +1 -1
  247. package/packages/mcp-server/dist/workflow-tools.d.ts +1 -1
  248. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  249. package/packages/mcp-server/dist/workflow-tools.js +2 -40
  250. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  251. package/packages/mcp-server/package.json +3 -2
  252. package/packages/mcp-server/src/mcp-server.test.ts +138 -0
  253. package/packages/mcp-server/src/server.ts +99 -1
  254. package/packages/mcp-server/src/session-manager.ts +2 -2
  255. package/packages/mcp-server/src/types.ts +7 -18
  256. package/packages/mcp-server/src/workflow-tools.ts +2 -40
  257. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  258. package/packages/native/package.json +1 -1
  259. package/packages/pi-agent-core/package.json +1 -1
  260. package/packages/pi-ai/package.json +1 -1
  261. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +14 -0
  262. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
  263. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js +20 -0
  264. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js.map +1 -1
  265. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  266. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +6 -1
  267. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  268. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +9 -1
  269. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  270. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +31 -10
  271. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  272. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts +1 -512
  273. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  274. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.js +3 -7
  275. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.js.map +1 -1
  276. package/packages/pi-coding-agent/package.json +2 -1
  277. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +23 -2
  278. package/packages/pi-coding-agent/src/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.ts +31 -0
  279. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +6 -1
  280. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +50 -9
  281. package/packages/pi-coding-agent/src/modes/rpc/rpc-types.ts +3 -336
  282. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  283. package/packages/pi-tui/dist/tui.d.ts +1 -0
  284. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  285. package/packages/pi-tui/dist/tui.js +8 -2
  286. package/packages/pi-tui/dist/tui.js.map +1 -1
  287. package/packages/pi-tui/package.json +1 -1
  288. package/packages/pi-tui/src/tui.ts +8 -2
  289. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  290. package/packages/rpc-client/README.md +3 -3
  291. package/packages/rpc-client/dist/index.d.ts +1 -1
  292. package/packages/rpc-client/dist/index.d.ts.map +1 -1
  293. package/packages/rpc-client/dist/index.js.map +1 -1
  294. package/packages/rpc-client/dist/rpc-client.d.ts +2 -6
  295. package/packages/rpc-client/dist/rpc-client.d.ts.map +1 -1
  296. package/packages/rpc-client/dist/rpc-client.js.map +1 -1
  297. package/packages/rpc-client/dist/rpc-types.d.ts +1 -565
  298. package/packages/rpc-client/dist/rpc-types.d.ts.map +1 -1
  299. package/packages/rpc-client/dist/rpc-types.js +3 -11
  300. package/packages/rpc-client/dist/rpc-types.js.map +1 -1
  301. package/packages/rpc-client/package.json +4 -1
  302. package/packages/rpc-client/src/index.ts +1 -1
  303. package/packages/rpc-client/src/rpc-client.ts +3 -6
  304. package/packages/rpc-client/src/rpc-types.ts +3 -398
  305. package/packages/rpc-client/tsconfig.tsbuildinfo +1 -1
  306. package/pkg/package.json +1 -1
  307. package/src/resources/extensions/github-sync/templates.ts +59 -84
  308. package/src/resources/extensions/github-sync/tests/templates.test.ts +10 -2
  309. package/src/resources/extensions/gsd/auto/contracts.ts +87 -0
  310. package/src/resources/extensions/gsd/auto/custom-verify-retry-store.ts +72 -0
  311. package/src/resources/extensions/gsd/auto/loop-deps.ts +10 -3
  312. package/src/resources/extensions/gsd/auto/loop.ts +411 -598
  313. package/src/resources/extensions/gsd/auto/orchestrator.ts +161 -0
  314. package/src/resources/extensions/gsd/auto/phases.ts +82 -8
  315. package/src/resources/extensions/gsd/auto/session.ts +11 -0
  316. package/src/resources/extensions/gsd/auto/workflow-custom-engine-dispatch-outcome.ts +28 -0
  317. package/src/resources/extensions/gsd/auto/workflow-custom-engine-iteration.ts +52 -0
  318. package/src/resources/extensions/gsd/auto/workflow-custom-engine-reconcile-outcome.ts +58 -0
  319. package/src/resources/extensions/gsd/auto/workflow-custom-engine-reconcile.ts +71 -0
  320. package/src/resources/extensions/gsd/auto/workflow-custom-engine-retry.ts +90 -0
  321. package/src/resources/extensions/gsd/auto/workflow-custom-engine-verify-outcome.ts +50 -0
  322. package/src/resources/extensions/gsd/auto/workflow-dispatch-claim.ts +97 -0
  323. package/src/resources/extensions/gsd/auto/workflow-dispatch-ledger.ts +45 -0
  324. package/src/resources/extensions/gsd/auto/workflow-iteration-completion.ts +26 -0
  325. package/src/resources/extensions/gsd/auto/workflow-journal-reporter.ts +33 -0
  326. package/src/resources/extensions/gsd/auto/workflow-kernel.ts +520 -0
  327. package/src/resources/extensions/gsd/auto/workflow-memory-pressure.ts +58 -0
  328. package/src/resources/extensions/gsd/auto/workflow-phase-reporter.ts +22 -0
  329. package/src/resources/extensions/gsd/auto/workflow-session-lock.ts +68 -0
  330. package/src/resources/extensions/gsd/auto/workflow-sidecar-iteration.ts +46 -0
  331. package/src/resources/extensions/gsd/auto/workflow-sidecar-queue.ts +46 -0
  332. package/src/resources/extensions/gsd/auto/workflow-turn-reporter.ts +68 -0
  333. package/src/resources/extensions/gsd/auto/workflow-unit-dispatch.ts +89 -0
  334. package/src/resources/extensions/gsd/auto/workflow-worker-heartbeat.ts +38 -0
  335. package/src/resources/extensions/gsd/auto-dashboard.ts +4 -0
  336. package/src/resources/extensions/gsd/auto-prompts.ts +170 -3
  337. package/src/resources/extensions/gsd/auto-recovery.ts +42 -50
  338. package/src/resources/extensions/gsd/auto-runtime-state.ts +7 -0
  339. package/src/resources/extensions/gsd/auto-verification.ts +5 -1
  340. package/src/resources/extensions/gsd/auto-worktree.ts +85 -36
  341. package/src/resources/extensions/gsd/auto.ts +167 -1
  342. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +14 -1
  343. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +11 -0
  344. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +13 -5
  345. package/src/resources/extensions/gsd/commands-ship.ts +24 -51
  346. package/src/resources/extensions/gsd/commands-workflow-templates.ts +13 -0
  347. package/src/resources/extensions/gsd/component-loader.ts +5 -11
  348. package/src/resources/extensions/gsd/custom-workflow-engine.ts +6 -0
  349. package/src/resources/extensions/gsd/db-adapter.ts +75 -0
  350. package/src/resources/extensions/gsd/db-base-schema.ts +368 -0
  351. package/src/resources/extensions/gsd/db-connection-cache.ts +45 -0
  352. package/src/resources/extensions/gsd/db-coordination-schema.ts +109 -0
  353. package/src/resources/extensions/gsd/db-decision-requirement-rows.ts +77 -0
  354. package/src/resources/extensions/gsd/db-gate-rows.ts +19 -0
  355. package/src/resources/extensions/gsd/db-lightweight-query-rows.ts +50 -0
  356. package/src/resources/extensions/gsd/db-memory-fts-schema.ts +66 -0
  357. package/src/resources/extensions/gsd/db-migration-backup.ts +34 -0
  358. package/src/resources/extensions/gsd/db-migration-steps.ts +434 -0
  359. package/src/resources/extensions/gsd/db-milestone-artifact-rows.ts +70 -0
  360. package/src/resources/extensions/gsd/db-open-state.ts +47 -0
  361. package/src/resources/extensions/gsd/db-provider.ts +148 -0
  362. package/src/resources/extensions/gsd/db-runtime-kv-schema.ts +30 -0
  363. package/src/resources/extensions/gsd/db-schema-metadata.ts +33 -0
  364. package/src/resources/extensions/gsd/db-task-slice-rows.ts +146 -0
  365. package/src/resources/extensions/gsd/db-transaction.ts +76 -0
  366. package/src/resources/extensions/gsd/db-verification-evidence-rows.ts +14 -0
  367. package/src/resources/extensions/gsd/db-verification-evidence-schema.ts +22 -0
  368. package/src/resources/extensions/gsd/escalation.ts +3 -1
  369. package/src/resources/extensions/gsd/gsd-db.ts +260 -1659
  370. package/src/resources/extensions/gsd/interrupted-session.ts +1 -0
  371. package/src/resources/extensions/gsd/legacy-telemetry.ts +99 -0
  372. package/src/resources/extensions/gsd/markdown-renderer.ts +4 -1
  373. package/src/resources/extensions/gsd/model-router.ts +10 -6
  374. package/src/resources/extensions/gsd/notification-widget.ts +25 -4
  375. package/src/resources/extensions/gsd/post-execution-checks.ts +35 -7
  376. package/src/resources/extensions/gsd/pr-evidence.ts +124 -0
  377. package/src/resources/extensions/gsd/pre-execution-checks.ts +4 -1
  378. package/src/resources/extensions/gsd/process-task-path.ts +81 -0
  379. package/src/resources/extensions/gsd/prompt-loader.ts +9 -5
  380. package/src/resources/extensions/gsd/prompts/complete-milestone.md +32 -30
  381. package/src/resources/extensions/gsd/prompts/complete-slice.md +23 -34
  382. package/src/resources/extensions/gsd/prompts/discuss-headless.md +50 -96
  383. package/src/resources/extensions/gsd/prompts/discuss.md +81 -181
  384. package/src/resources/extensions/gsd/prompts/execute-task.md +40 -67
  385. package/src/resources/extensions/gsd/prompts/forensics.md +41 -84
  386. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +29 -39
  387. package/src/resources/extensions/gsd/prompts/guided-discuss-project.md +30 -65
  388. package/src/resources/extensions/gsd/prompts/guided-discuss-requirements.md +25 -52
  389. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +36 -36
  390. package/src/resources/extensions/gsd/prompts/guided-research-project.md +20 -38
  391. package/src/resources/extensions/gsd/prompts/plan-milestone.md +45 -59
  392. package/src/resources/extensions/gsd/prompts/plan-slice.md +25 -87
  393. package/src/resources/extensions/gsd/prompts/queue.md +46 -53
  394. package/src/resources/extensions/gsd/prompts/refine-slice.md +23 -23
  395. package/src/resources/extensions/gsd/prompts/research-slice.md +23 -23
  396. package/src/resources/extensions/gsd/prompts/rethink.md +10 -10
  397. package/src/resources/extensions/gsd/prompts/system.md +65 -107
  398. package/src/resources/extensions/gsd/prompts/triage-captures.md +15 -15
  399. package/src/resources/extensions/gsd/prompts/validate-milestone.md +24 -24
  400. package/src/resources/extensions/gsd/prompts/worktree-merge.md +35 -35
  401. package/src/resources/extensions/gsd/state.ts +6 -3
  402. package/src/resources/extensions/gsd/tests/auto-abort-pause-regression.test.ts +32 -0
  403. package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +65 -0
  404. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +38 -0
  405. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +353 -0
  406. package/src/resources/extensions/gsd/tests/auto-pr-bugs.test.ts +19 -0
  407. package/src/resources/extensions/gsd/tests/auto-runtime-state.test.ts +39 -0
  408. package/src/resources/extensions/gsd/tests/auto-session-encapsulation.test.ts +3 -0
  409. package/src/resources/extensions/gsd/tests/commands-eval-review.test.ts +2 -2
  410. package/src/resources/extensions/gsd/tests/commands-ship-eval-warn.test.ts +3 -2
  411. package/src/resources/extensions/gsd/tests/complete-milestone-prompt-rendering.test.ts +47 -0
  412. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +19 -5
  413. package/src/resources/extensions/gsd/tests/component-loader.test.ts +2 -9
  414. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +38 -0
  415. package/src/resources/extensions/gsd/tests/custom-verify-retry-store.test.ts +139 -0
  416. package/src/resources/extensions/gsd/tests/custom-workflow-engine.test.ts +12 -0
  417. package/src/resources/extensions/gsd/tests/dashboard-custom-engine.test.ts +3 -3
  418. package/src/resources/extensions/gsd/tests/db-adapter.test.ts +82 -0
  419. package/src/resources/extensions/gsd/tests/db-base-schema.test.ts +62 -0
  420. package/src/resources/extensions/gsd/tests/db-connection-cache.test.ts +60 -0
  421. package/src/resources/extensions/gsd/tests/db-coordination-schema.test.ts +39 -0
  422. package/src/resources/extensions/gsd/tests/db-decision-requirement-rows.test.ts +135 -0
  423. package/src/resources/extensions/gsd/tests/db-gate-rows.test.ts +53 -0
  424. package/src/resources/extensions/gsd/tests/db-lightweight-query-rows.test.ts +45 -0
  425. package/src/resources/extensions/gsd/tests/db-memory-fts-schema.test.ts +86 -0
  426. package/src/resources/extensions/gsd/tests/db-migration-backup.test.ts +105 -0
  427. package/src/resources/extensions/gsd/tests/db-migration-steps.test.ts +159 -0
  428. package/src/resources/extensions/gsd/tests/db-milestone-artifact-rows.test.ts +53 -0
  429. package/src/resources/extensions/gsd/tests/db-open-state.test.ts +56 -0
  430. package/src/resources/extensions/gsd/tests/db-provider.test.ts +105 -0
  431. package/src/resources/extensions/gsd/tests/db-runtime-kv-schema.test.ts +37 -0
  432. package/src/resources/extensions/gsd/tests/db-schema-metadata.test.ts +115 -0
  433. package/src/resources/extensions/gsd/tests/db-task-slice-rows.test.ts +128 -0
  434. package/src/resources/extensions/gsd/tests/db-transaction.test.ts +110 -0
  435. package/src/resources/extensions/gsd/tests/db-verification-evidence-schema.test.ts +76 -0
  436. package/src/resources/extensions/gsd/tests/discuss-headless-rendering.test.ts +37 -0
  437. package/src/resources/extensions/gsd/tests/execute-task-rendering.test.ts +59 -0
  438. package/src/resources/extensions/gsd/tests/forensics-prompt-rendering.test.ts +36 -0
  439. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +95 -0
  440. package/src/resources/extensions/gsd/tests/guided-discuss-milestone-prompt-rendering.test.ts +43 -0
  441. package/src/resources/extensions/gsd/tests/guided-discuss-project-prompt-rendering.test.ts +41 -0
  442. package/src/resources/extensions/gsd/tests/guided-discuss-requirements-prompt-rendering.test.ts +45 -0
  443. package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +79 -0
  444. package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +37 -0
  445. package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts +5 -3
  446. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +134 -0
  447. package/src/resources/extensions/gsd/tests/legacy-component-format-telemetry.test.ts +62 -0
  448. package/src/resources/extensions/gsd/tests/legacy-telemetry.test.ts +144 -0
  449. package/src/resources/extensions/gsd/tests/memory-pressure-stuck-state.test.ts +40 -16
  450. package/src/resources/extensions/gsd/tests/model-router.test.ts +33 -12
  451. package/src/resources/extensions/gsd/tests/notification-store.test.ts +8 -0
  452. package/src/resources/extensions/gsd/tests/notification-widget.test.ts +40 -1
  453. package/src/resources/extensions/gsd/tests/paused-session-via-db.test.ts +2 -0
  454. package/src/resources/extensions/gsd/tests/plan-milestone-rendering.test.ts +45 -0
  455. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +65 -16
  456. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +27 -0
  457. package/src/resources/extensions/gsd/tests/post-execution-checks.test.ts +46 -0
  458. package/src/resources/extensions/gsd/tests/pr-evidence.test.ts +79 -0
  459. package/src/resources/extensions/gsd/tests/process-task-path.test.ts +51 -0
  460. package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +16 -1
  461. package/src/resources/extensions/gsd/tests/queue-prompt-rendering.test.ts +37 -0
  462. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +32 -9
  463. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +6 -6
  464. package/src/resources/extensions/gsd/tests/uok-kernel-path.test.ts +12 -0
  465. package/src/resources/extensions/gsd/tests/workflow-custom-engine-dispatch-outcome.test.ts +55 -0
  466. package/src/resources/extensions/gsd/tests/workflow-custom-engine-iteration.test.ts +93 -0
  467. package/src/resources/extensions/gsd/tests/workflow-custom-engine-reconcile-outcome.test.ts +108 -0
  468. package/src/resources/extensions/gsd/tests/workflow-custom-engine-reconcile.test.ts +146 -0
  469. package/src/resources/extensions/gsd/tests/workflow-custom-engine-retry.test.ts +136 -0
  470. package/src/resources/extensions/gsd/tests/workflow-custom-engine-verify-outcome.test.ts +95 -0
  471. package/src/resources/extensions/gsd/tests/workflow-dispatch-claim.test.ts +158 -0
  472. package/src/resources/extensions/gsd/tests/workflow-dispatch-ledger.test.ts +82 -0
  473. package/src/resources/extensions/gsd/tests/workflow-iteration-completion.test.ts +44 -0
  474. package/src/resources/extensions/gsd/tests/workflow-journal-reporter.test.ts +49 -0
  475. package/src/resources/extensions/gsd/tests/workflow-kernel.test.ts +607 -0
  476. package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +20 -4
  477. package/src/resources/extensions/gsd/tests/workflow-memory-pressure.test.ts +71 -0
  478. package/src/resources/extensions/gsd/tests/workflow-phase-reporter.test.ts +40 -0
  479. package/src/resources/extensions/gsd/tests/workflow-session-lock.test.ts +135 -0
  480. package/src/resources/extensions/gsd/tests/workflow-sidecar-iteration.test.ts +110 -0
  481. package/src/resources/extensions/gsd/tests/workflow-sidecar-queue.test.ts +116 -0
  482. package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +21 -0
  483. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +32 -0
  484. package/src/resources/extensions/gsd/tests/workflow-turn-reporter.test.ts +87 -0
  485. package/src/resources/extensions/gsd/tests/workflow-unit-dispatch.test.ts +160 -0
  486. package/src/resources/extensions/gsd/tests/workflow-worker-heartbeat.test.ts +123 -0
  487. package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +17 -33
  488. package/src/resources/extensions/gsd/tools/complete-milestone.ts +15 -9
  489. package/src/resources/extensions/gsd/tools/complete-task.ts +4 -1
  490. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +6 -1
  491. package/src/resources/extensions/gsd/unit-context-composer.ts +1 -1
  492. package/src/resources/extensions/gsd/uok/kernel.ts +10 -3
  493. package/src/resources/extensions/gsd/uok/plan-v2.ts +5 -1
  494. package/src/resources/extensions/gsd/workflow-logger.ts +13 -13
  495. package/src/resources/extensions/gsd/workflow-manifest.ts +6 -15
  496. package/src/resources/extensions/gsd/workflow-projections.ts +5 -1
  497. package/src/resources/extensions/gsd/workflow-templates.ts +11 -0
  498. package/src/resources/extensions/shared/interview-ui.ts +18 -5
  499. package/src/resources/extensions/shared/tests/interview-notes-loop.test.ts +41 -0
  500. package/dist/web/standalone/.next/server/chunks/6336.js +0 -1
  501. /package/dist/web/standalone/.next/static/{TzEVJ1Lh8vbez4n4Q9TqQ → bQDK5_LtkGVS64AirQgQG}/_buildManifest.js +0 -0
  502. /package/dist/web/standalone/.next/static/{TzEVJ1Lh8vbez4n4Q9TqQ → bQDK5_LtkGVS64AirQgQG}/_ssgManifest.js +0 -0
@@ -9,24 +9,39 @@
9
9
  import { randomUUID } from "node:crypto";
10
10
  import { MAX_LOOP_ITERATIONS, } from "./types.js";
11
11
  import { _clearCurrentResolve } from "./resolve.js";
12
- import { runPreDispatch, runDispatch, runGuards, runUnitPhase, runFinalize, } from "./phases.js";
12
+ import { runPreDispatch, runDispatch, runGuards, runFinalize, } from "./phases.js";
13
13
  import { debugLog } from "../debug-logger.js";
14
14
  import { isInfrastructureError, isTransientCooldownError, getCooldownRetryAfterMs, COOLDOWN_FALLBACK_WAIT_MS, MAX_COOLDOWN_RETRIES } from "./infra-errors.js";
15
15
  import { ModelPolicyDispatchBlockedError } from "../auto-model-selection.js";
16
16
  import { resolveEngine } from "../engine-resolver.js";
17
17
  import { logWarning } from "../workflow-logger.js";
18
- import { gsdRoot } from "../paths.js";
19
- import { heartbeatAutoWorker } from "../db/auto-workers.js";
20
18
  import { recordDispatchClaim, markRunning as markDispatchRunning, markCompleted as markDispatchCompleted, markFailed as markDispatchFailed, getRecentForUnit as getRecentDispatchesForUnit, getRecentUnitKeysForProjectRoot, } from "../db/unit-dispatches.js";
21
19
  import { refreshMilestoneLease } from "../db/milestone-leases.js";
20
+ import { heartbeatAutoWorker } from "../db/auto-workers.js";
22
21
  import { getRuntimeKv, setRuntimeKv } from "../db/runtime-kv.js";
23
- import { atomicWriteSync } from "../atomic-write.js";
24
22
  import { resolveUokFlags } from "../uok/flags.js";
25
23
  import { scheduleSidecarQueue } from "../uok/execution-graph.js";
26
- import { ExecutionGraphScheduler } from "../uok/execution-graph.js";
27
- import { readFileSync, mkdirSync, unlinkSync } from "node:fs";
28
- import { join } from "node:path";
29
24
  import { normalizeRealPath } from "../paths.js";
25
+ import { decideCooldownRecovery, decideDispatchClaim, decideEngineDispatch, decideFinalizeResult, decideInfrastructureError, decideIterationErrorRecovery, decideMemoryPressure, decideModelPolicyBlocked, decideMinRequestInterval, decideWorkflowLoop, formatDispatchExceptionSummary, formatUnhandledDispatchErrorSummary, resolveUnitRequestTimestamp, shouldUseCustomEnginePath, } from "./workflow-kernel.js";
26
+ import { hydrateCustomVerifyRetryCounts, saveCustomVerifyRetryCounts, } from "./custom-verify-retry-store.js";
27
+ import { settleDispatchCompleted, settleDispatchFailed, } from "./workflow-dispatch-ledger.js";
28
+ import { openDispatchClaim } from "./workflow-dispatch-claim.js";
29
+ import { completeWorkflowIteration } from "./workflow-iteration-completion.js";
30
+ import { createWorkflowJournalReporter } from "./workflow-journal-reporter.js";
31
+ import { createWorkflowPhaseReporter } from "./workflow-phase-reporter.js";
32
+ import { createWorkflowTurnReporter } from "./workflow-turn-reporter.js";
33
+ import { validateWorkflowSessionLock } from "./workflow-session-lock.js";
34
+ import { dequeueSidecarItem } from "./workflow-sidecar-queue.js";
35
+ import { maintainWorkerHeartbeat } from "./workflow-worker-heartbeat.js";
36
+ import { measureMemoryPressure } from "./workflow-memory-pressure.js";
37
+ import { buildSidecarIterationData } from "./workflow-sidecar-iteration.js";
38
+ import { createExecutionGraphUnitDispatchDeps, runUnitPhaseViaContract, } from "./workflow-unit-dispatch.js";
39
+ import { handleCustomEngineDispatchOutcome } from "./workflow-custom-engine-dispatch-outcome.js";
40
+ import { buildCustomEngineIterationData } from "./workflow-custom-engine-iteration.js";
41
+ import { handleCustomEngineVerifyRetry } from "./workflow-custom-engine-retry.js";
42
+ import { handleCustomEngineVerifyPause, handleCustomEngineVerifyRetryOutcome, } from "./workflow-custom-engine-verify-outcome.js";
43
+ import { handleCustomEngineReconcile } from "./workflow-custom-engine-reconcile.js";
44
+ import { handleCustomEngineReconcileOutcome } from "./workflow-custom-engine-reconcile-outcome.js";
30
45
  // ── Stuck detection persistence (#3704) ──────────────────────────────────
31
46
  // Phase C migration: stuck-state.json deleted in favor of DB-backed
32
47
  // equivalents. recentUnits is rebuilt from unit_dispatches (Phase B
@@ -70,200 +85,54 @@ function saveStuckState(s, state) {
70
85
  debugLog("autoLoop", { phase: "save-stuck-state-failed", error: err instanceof Error ? err.message : String(err) });
71
86
  }
72
87
  }
73
- // ── Custom workflow verification retry persistence ───────────────────────
74
- // Custom workflows can request verification retries after a step runs. The
75
- // retry budget must survive an auto-mode restart or a failing verifier can
76
- // consume a fresh retry budget every session.
77
- function customVerifyRetryStateDir(s) {
78
- return s.activeRunDir ? join(s.activeRunDir, "runtime") : join(gsdRoot(s.basePath), "runtime");
88
+ function logDispatchLedgerWriteFailure(err) {
89
+ debugLog("autoLoop", {
90
+ phase: "dispatch-ledger-write-failed",
91
+ error: err instanceof Error ? err.message : String(err),
92
+ });
79
93
  }
80
- function customVerifyRetryStatePath(s) {
81
- return join(customVerifyRetryStateDir(s), "custom-verify-retries.json");
94
+ function logDispatchClaimRejected(details) {
95
+ debugLog("autoLoop", {
96
+ phase: "dispatch-claim-rejected",
97
+ ...details,
98
+ });
82
99
  }
83
- function hydrateCustomVerifyRetryCounts(s) {
84
- if (s.verificationRetryCount.size > 0) {
85
- return s.verificationRetryCount;
86
- }
87
- try {
88
- const raw = JSON.parse(readFileSync(customVerifyRetryStatePath(s), "utf-8"));
89
- const counts = raw && typeof raw === "object" && raw.counts && typeof raw.counts === "object"
90
- ? raw.counts
91
- : {};
92
- for (const [key, value] of Object.entries(counts)) {
93
- if (typeof value === "number" && Number.isFinite(value) && value > 0) {
94
- s.verificationRetryCount.set(key, Math.floor(value));
95
- }
96
- }
97
- }
98
- catch (err) {
99
- debugLog("autoLoop", { phase: "load-custom-verify-retries-failed", error: err instanceof Error ? err.message : String(err) });
100
- }
101
- return s.verificationRetryCount;
100
+ function logDispatchClaimFailed(err) {
101
+ debugLog("autoLoop", {
102
+ phase: "dispatch-claim-failed",
103
+ error: err instanceof Error ? err.message : String(err),
104
+ });
102
105
  }
103
- function saveCustomVerifyRetryCounts(s) {
104
- const retryCounts = s.verificationRetryCount;
105
- const filePath = customVerifyRetryStatePath(s);
106
- try {
107
- if (!retryCounts || retryCounts.size === 0) {
108
- unlinkSync(filePath);
109
- return;
110
- }
111
- mkdirSync(customVerifyRetryStateDir(s), { recursive: true });
112
- atomicWriteSync(filePath, JSON.stringify({
113
- counts: Object.fromEntries(retryCounts),
114
- updatedAt: new Date().toISOString(),
115
- }) + "\n");
116
- }
117
- catch (err) {
118
- const code = err && typeof err === "object" && "code" in err ? err.code : undefined;
119
- if (code !== "ENOENT") {
120
- debugLog("autoLoop", { phase: "save-custom-verify-retries-failed", error: err instanceof Error ? err.message : String(err) });
121
- }
122
- }
106
+ function logCustomVerifyRetryLoadFailure(err) {
107
+ debugLog("autoLoop", {
108
+ phase: "load-custom-verify-retries-failed",
109
+ error: err instanceof Error ? err.message : String(err),
110
+ });
123
111
  }
124
- function openDispatchClaim(s, flowId, turnId, iterData) {
125
- if (!s.workerId || s.milestoneLeaseToken === null)
126
- return { kind: "degraded" };
127
- const mid = iterData.mid;
128
- if (!mid)
129
- return { kind: "degraded" };
130
- const recent = getRecentDispatchesForUnit(iterData.unitId, 1);
131
- const attemptN = (recent[0]?.attempt_n ?? 0) + 1;
132
- let claim;
133
- try {
134
- claim = recordDispatchClaim({
135
- traceId: flowId,
136
- turnId,
137
- workerId: s.workerId,
138
- milestoneLeaseToken: s.milestoneLeaseToken,
139
- milestoneId: mid,
140
- sliceId: iterData.state.activeSlice?.id ?? null,
141
- taskId: iterData.state.activeTask?.id ?? null,
142
- unitType: iterData.unitType,
143
- unitId: iterData.unitId,
144
- attemptN,
145
- });
146
- if (!claim.ok) {
147
- debugLog("autoLoop", {
148
- phase: "dispatch-claim-rejected",
149
- unitId: iterData.unitId,
150
- reason: claim.error,
151
- existingId: "existingId" in claim ? claim.existingId : undefined,
152
- existingWorker: "existingWorker" in claim ? claim.existingWorker : undefined,
153
- });
154
- if (claim.error === "already_active") {
155
- return {
156
- kind: "skip",
157
- reason: "already-active",
158
- existingId: claim.existingId,
159
- existingWorker: claim.existingWorker,
160
- };
161
- }
162
- return { kind: "skip", reason: "stale-lease" };
163
- }
164
- markDispatchRunning(claim.dispatchId);
165
- return { kind: "opened", dispatchId: claim.dispatchId };
166
- }
167
- catch (err) {
168
- debugLog("autoLoop", {
169
- phase: "dispatch-claim-failed",
170
- error: err instanceof Error ? err.message : String(err),
171
- });
172
- return { kind: "degraded" };
173
- }
112
+ function logCustomVerifyRetrySaveFailure(err) {
113
+ debugLog("autoLoop", {
114
+ phase: "save-custom-verify-retries-failed",
115
+ error: err instanceof Error ? err.message : String(err),
116
+ });
174
117
  }
175
118
  // ── Memory pressure monitoring (#3331) ──────────────────────────────────
176
119
  // Check heap usage every N iterations and trigger graceful shutdown before
177
120
  // the OS OOM killer sends SIGKILL. The threshold is 90% of the V8 heap
178
121
  // limit (--max-old-space-size or default ~1.5-4GB depending on platform).
179
122
  const MEMORY_CHECK_INTERVAL = 5; // check every 5 iterations
180
- const MEMORY_PRESSURE_THRESHOLD = 0.85; // 85% of heap limit
181
123
  const MAX_CUSTOM_ENGINE_VERIFY_RETRIES = 3;
182
- function checkMemoryPressure() {
183
- const mem = process.memoryUsage();
184
- // v8.getHeapStatistics() gives heap_size_limit but requires import
185
- // Use a conservative estimate: RSS > 3GB is danger zone on most systems
186
- const heapMB = Math.round(mem.heapUsed / 1024 / 1024);
187
- const rssMB = Math.round(mem.rss / 1024 / 1024);
188
- // Try to get the actual V8 heap limit
189
- let limitMB = 4096; // conservative default
190
- try {
191
- const v8 = require("node:v8");
192
- const stats = v8.getHeapStatistics();
193
- limitMB = Math.round(stats.heap_size_limit / 1024 / 1024);
194
- }
195
- catch {
196
- limitMB = 4096; /* v8 stats unavailable — use conservative default */
197
- }
198
- const pct = heapMB / limitMB;
199
- return { pressured: pct > MEMORY_PRESSURE_THRESHOLD, heapMB, limitMB, pct };
200
- }
201
- function resolveDispatchNodeKind(unitType, sidecarItem) {
202
- if (sidecarItem?.kind === "hook")
203
- return "hook";
204
- if (sidecarItem?.kind === "triage")
205
- return "verification";
206
- if (sidecarItem?.kind === "quick-task")
207
- return "team-worker";
208
- if (unitType.startsWith("hook/"))
209
- return "hook";
210
- if (unitType === "reactive-execute")
211
- return "subagent";
212
- if (unitType === "gate-evaluate"
213
- || unitType === "validate-milestone"
214
- || unitType === "run-uat"
215
- || unitType === "complete-slice") {
216
- return "verification";
217
- }
218
- if (unitType === "replan-slice" || unitType === "reassess-roadmap") {
219
- return "reprocess";
220
- }
221
- return "unit";
222
- }
223
124
  async function enforceMinRequestInterval(s, prefs) {
224
125
  const minInterval = prefs?.min_request_interval_ms ?? 0;
225
- if (minInterval > 0 && s.lastRequestTimestamp > 0) {
226
- const elapsed = Date.now() - s.lastRequestTimestamp;
227
- if (elapsed < minInterval) {
228
- const waitMs = minInterval - elapsed;
229
- debugLog("autoLoop", { phase: "rate-limit-wait", waitMs });
230
- await new Promise(r => setTimeout(r, waitMs));
231
- }
126
+ const decision = decideMinRequestInterval({
127
+ minIntervalMs: minInterval,
128
+ lastRequestTimestamp: s.lastRequestTimestamp,
129
+ nowMs: Date.now(),
130
+ });
131
+ if (decision.action === "wait") {
132
+ debugLog("autoLoop", { phase: "rate-limit-wait", waitMs: decision.waitMs });
133
+ await new Promise(r => setTimeout(r, decision.waitMs));
232
134
  }
233
135
  }
234
- async function runUnitPhaseViaContract(dispatchContract, ic, iterData, loopState, sidecarItem) {
235
- if (dispatchContract === "legacy-direct") {
236
- return runUnitPhase(ic, iterData, loopState, sidecarItem);
237
- }
238
- const scheduler = new ExecutionGraphScheduler();
239
- let outcome = null;
240
- const executeNode = async () => {
241
- outcome = await runUnitPhase(ic, iterData, loopState, sidecarItem);
242
- };
243
- const kinds = [
244
- "unit",
245
- "hook",
246
- "subagent",
247
- "team-worker",
248
- "verification",
249
- "reprocess",
250
- ];
251
- for (const kind of kinds)
252
- scheduler.registerHandler(kind, executeNode);
253
- const nodeId = `dispatch:${ic.iteration}:${iterData.unitType}:${iterData.unitId}`;
254
- await scheduler.run([
255
- {
256
- id: nodeId,
257
- kind: resolveDispatchNodeKind(iterData.unitType, sidecarItem),
258
- dependsOn: [],
259
- metadata: {
260
- unitType: iterData.unitType,
261
- unitId: iterData.unitId,
262
- },
263
- },
264
- ], { parallel: false, maxWorkers: 1 });
265
- return outcome ?? { action: "break", reason: "scheduler-dispatch-missing-result" };
266
- }
267
136
  /**
268
137
  * Main auto-mode execution loop. Iterates: derive → dispatch → guards →
269
138
  * runUnit → finalize → repeat. Exits when s.active becomes false or a
@@ -276,6 +145,7 @@ export async function autoLoop(ctx, pi, s, deps, options) {
276
145
  debugLog("autoLoop", { phase: "enter" });
277
146
  let iteration = 0;
278
147
  const dispatchContract = options?.dispatchContract ?? "legacy-direct";
148
+ const unitDispatchDeps = createExecutionGraphUnitDispatchDeps();
279
149
  // Load persisted stuck state so counters survive session restarts (#3704)
280
150
  const persisted = loadStuckState(s);
281
151
  const loopState = {
@@ -289,65 +159,65 @@ export async function autoLoop(ctx, pi, s, deps, options) {
289
159
  while (s.active) {
290
160
  iteration++;
291
161
  debugLog("autoLoop", { phase: "loop-top", iteration });
292
- // Phase B: heartbeat the worker registry + active milestone lease so
293
- // janitors and concurrent workers see a live process. Best-effort —
294
- // DB unavailability or stale state must not stop the loop.
295
- if (s.workerId) {
296
- try {
297
- heartbeatAutoWorker(s.workerId);
298
- if (s.currentMilestoneId && s.milestoneLeaseToken) {
299
- refreshMilestoneLease(s.workerId, s.currentMilestoneId, s.milestoneLeaseToken);
300
- }
301
- }
302
- catch (err) {
303
- debugLog("autoLoop", {
304
- phase: "heartbeat-failed",
305
- error: err instanceof Error ? err.message : String(err),
306
- });
307
- }
308
- }
162
+ maintainWorkerHeartbeat(s, {
163
+ heartbeatAutoWorker,
164
+ refreshMilestoneLease,
165
+ logHeartbeatFailure: err => debugLog("autoLoop", {
166
+ phase: "heartbeat-failed",
167
+ error: err instanceof Error ? err.message : String(err),
168
+ }),
169
+ });
309
170
  // ── Journal: per-iteration flow grouping ──
310
171
  const flowId = randomUUID();
311
172
  let seqCounter = 0;
312
173
  const nextSeq = () => ++seqCounter;
174
+ const journalReporter = createWorkflowJournalReporter({
175
+ emitJournalEvent: deps.emitJournalEvent,
176
+ flowId,
177
+ nextSeq,
178
+ });
313
179
  const turnId = randomUUID();
314
180
  s.currentTraceId = flowId;
315
181
  s.currentTurnId = turnId;
316
182
  const turnStartedAt = new Date().toISOString();
317
183
  let observedUnitType;
318
184
  let observedUnitId;
319
- let turnFinished = false;
185
+ const phaseReporter = createWorkflowPhaseReporter({
186
+ observer: deps.uokObserver,
187
+ });
188
+ const turnReporter = createWorkflowTurnReporter({
189
+ observer: deps.uokObserver,
190
+ traceId: flowId,
191
+ turnId,
192
+ iteration,
193
+ basePath: s.basePath,
194
+ startedAt: turnStartedAt,
195
+ clearCurrentTurn: () => {
196
+ s.currentTraceId = null;
197
+ s.currentTurnId = null;
198
+ },
199
+ });
320
200
  const finishTurn = (status, failureClass = "none", error) => {
321
- if (turnFinished)
322
- return;
323
- turnFinished = true;
324
- deps.uokObserver?.onTurnResult({
325
- traceId: flowId,
326
- turnId,
327
- iteration,
201
+ turnReporter.finish({
328
202
  unitType: observedUnitType,
329
203
  unitId: observedUnitId,
330
204
  status,
331
205
  failureClass,
332
- phaseResults: [],
333
206
  error,
334
- startedAt: turnStartedAt,
335
- finishedAt: new Date().toISOString(),
336
207
  });
337
- s.currentTraceId = null;
338
- s.currentTurnId = null;
339
208
  };
340
- deps.uokObserver?.onTurnStart({
341
- traceId: flowId,
342
- turnId,
209
+ turnReporter.start();
210
+ const iterationDecision = decideWorkflowLoop({
211
+ active: s.active,
343
212
  iteration,
344
- basePath: s.basePath,
345
- startedAt: turnStartedAt,
213
+ maxIterations: MAX_LOOP_ITERATIONS,
214
+ hasCommandContext: true,
215
+ sessionLockValid: true,
346
216
  });
347
- if (iteration > MAX_LOOP_ITERATIONS) {
217
+ if (iterationDecision.action === "stop" && iterationDecision.reason === "max-iterations") {
348
218
  debugLog("autoLoop", {
349
219
  phase: "exit",
350
- reason: "max-iterations",
220
+ reason: iterationDecision.reason,
351
221
  iteration,
352
222
  });
353
223
  await deps.stopAuto(ctx, pi, `Safety: loop exceeded ${MAX_LOOP_ITERATIONS} iterations — possible runaway`);
@@ -357,69 +227,79 @@ export async function autoLoop(ctx, pi, s, deps, options) {
357
227
  // ── Memory pressure check (#3331) ──
358
228
  // Graceful shutdown before OOM killer sends SIGKILL.
359
229
  if (iteration % MEMORY_CHECK_INTERVAL === 0) {
360
- const mem = checkMemoryPressure();
230
+ const mem = measureMemoryPressure();
361
231
  debugLog("autoLoop", { phase: "memory-check", ...mem });
362
- if (mem.pressured) {
363
- logWarning("dispatch", `Memory pressure: ${mem.heapMB}MB / ${mem.limitMB}MB (${Math.round(mem.pct * 100)}%) — stopping auto-mode to prevent OOM kill`);
364
- await deps.stopAuto(ctx, pi, `Memory pressure: heap at ${mem.heapMB}MB / ${mem.limitMB}MB (${Math.round(mem.pct * 100)}%). ` +
365
- `Stopping gracefully to prevent OOM kill after ${iteration} iterations. ` +
366
- `Resume with /gsd auto to continue from where you left off.`);
367
- finishTurn("stopped", "timeout", "memory-pressure");
232
+ const memoryDecision = decideMemoryPressure({ ...mem, iteration });
233
+ if (memoryDecision.action === "stop") {
234
+ logWarning("dispatch", memoryDecision.warningMessage);
235
+ await deps.stopAuto(ctx, pi, memoryDecision.stopMessage);
236
+ finishTurn("stopped", "timeout", memoryDecision.turnError);
368
237
  break;
369
238
  }
370
239
  }
371
- if (!s.cmdCtx) {
240
+ const commandContextDecision = decideWorkflowLoop({
241
+ active: s.active,
242
+ iteration,
243
+ maxIterations: MAX_LOOP_ITERATIONS,
244
+ hasCommandContext: Boolean(s.cmdCtx),
245
+ sessionLockValid: true,
246
+ });
247
+ if (commandContextDecision.action === "stop" && commandContextDecision.reason === "missing-command-context") {
372
248
  debugLog("autoLoop", { phase: "exit", reason: "no-cmdCtx" });
373
- finishTurn("stopped", "manual-attention", "missing-command-context");
249
+ finishTurn("stopped", "manual-attention", commandContextDecision.reason);
374
250
  break;
375
251
  }
376
252
  let dispatchId = null;
377
253
  let dispatchSettled = false;
254
+ const completeIteration = () => {
255
+ completeWorkflowIteration({
256
+ get consecutiveErrors() { return consecutiveErrors; },
257
+ set consecutiveErrors(value) { consecutiveErrors = value; },
258
+ get consecutiveCooldowns() { return consecutiveCooldowns; },
259
+ set consecutiveCooldowns(value) { consecutiveCooldowns = value; },
260
+ recentErrorMessages,
261
+ }, {
262
+ emitIterationEnd: () => journalReporter.emit("iteration-end", { iteration }),
263
+ saveStuckState: () => saveStuckState(s, loopState),
264
+ logIterationComplete: () => debugLog("autoLoop", { phase: "iteration-complete", iteration }),
265
+ });
266
+ };
378
267
  try {
379
268
  // ── Blanket try/catch: one bad iteration must not kill the session
380
269
  const prefs = deps.loadEffectiveGSDPreferences()?.preferences;
381
270
  const uokFlags = resolveUokFlags(prefs);
382
- // ── Check sidecar queue before deriveState ──
383
- let sidecarItem;
384
- if (s.sidecarQueue.length > 0) {
385
- if (uokFlags.executionGraph && s.sidecarQueue.length > 1) {
386
- try {
387
- s.sidecarQueue = await scheduleSidecarQueue(s.sidecarQueue);
388
- }
389
- catch (err) {
390
- logWarning("dispatch", `sidecar queue scheduling failed: ${err instanceof Error ? err.message : String(err)}`);
391
- }
392
- }
393
- sidecarItem = s.sidecarQueue.shift();
394
- debugLog("autoLoop", {
395
- phase: "sidecar-dequeue",
396
- kind: sidecarItem.kind,
397
- unitType: sidecarItem.unitType,
398
- unitId: sidecarItem.unitId,
399
- });
400
- deps.emitJournalEvent({ ts: new Date().toISOString(), flowId, seq: nextSeq(), eventType: "sidecar-dequeue", data: { kind: sidecarItem.kind, unitType: sidecarItem.unitType, unitId: sidecarItem.unitId } });
401
- }
402
- const sessionLockBase = deps.lockBase();
403
- if (sessionLockBase) {
404
- const lockStatus = deps.validateSessionLock(sessionLockBase);
405
- if (!lockStatus.valid) {
406
- debugLog("autoLoop", {
271
+ const sessionLockOutcome = validateWorkflowSessionLock({
272
+ active: s.active,
273
+ iteration,
274
+ maxIterations: MAX_LOOP_ITERATIONS,
275
+ deps: {
276
+ lockBase: deps.lockBase,
277
+ validateSessionLock: deps.validateSessionLock,
278
+ handleLostSessionLock: lockStatus => deps.handleLostSessionLock(ctx, lockStatus),
279
+ logInvalidSessionLock: details => debugLog("autoLoop", {
407
280
  phase: "session-lock-invalid",
408
- reason: lockStatus.failureReason ?? "unknown",
409
- existingPid: lockStatus.existingPid,
410
- expectedPid: lockStatus.expectedPid,
411
- });
412
- deps.handleLostSessionLock(ctx, lockStatus);
413
- debugLog("autoLoop", {
281
+ ...details,
282
+ }),
283
+ logSessionLockExit: details => debugLog("autoLoop", {
414
284
  phase: "exit",
415
- reason: "session-lock-lost",
416
- detail: lockStatus.failureReason ?? "unknown",
417
- });
418
- break;
419
- }
285
+ ...details,
286
+ }),
287
+ },
288
+ });
289
+ if (sessionLockOutcome.action === "stop" && sessionLockOutcome.reason === "session-lock-lost") {
290
+ break;
420
291
  }
292
+ // ── Check sidecar queue before deriveState ──
293
+ const sidecarItem = await dequeueSidecarItem({
294
+ queue: s.sidecarQueue,
295
+ executionGraphEnabled: uokFlags.executionGraph,
296
+ scheduleQueue: scheduleSidecarQueue,
297
+ warnSchedulingFailure: message => logWarning("dispatch", `sidecar queue scheduling failed: ${message}`),
298
+ logDequeue: payload => debugLog("autoLoop", { phase: "sidecar-dequeue", ...payload }),
299
+ emitDequeue: payload => journalReporter.emit("sidecar-dequeue", payload),
300
+ });
421
301
  const ic = { ctx, pi, s, deps, prefs, iteration, flowId, nextSeq };
422
- deps.emitJournalEvent({ ts: new Date().toISOString(), flowId, seq: nextSeq(), eventType: "iteration-start", data: { iteration } });
302
+ journalReporter.emit("iteration-start", { iteration });
423
303
  let iterData;
424
304
  // ── Custom engine path ──────────────────────────────────────────────
425
305
  // When activeEngineId is a non-dev value, bypass runPreDispatch and
@@ -429,7 +309,11 @@ export async function autoLoop(ctx, pi, s, deps, options) {
429
309
  //
430
310
  // GSD_ENGINE_BYPASS=1 skips the engine layer entirely — falls through
431
311
  // to the dev path below.
432
- if (s.activeEngineId != null && s.activeEngineId !== "dev" && !sidecarItem && process.env.GSD_ENGINE_BYPASS !== "1") {
312
+ if (shouldUseCustomEnginePath({
313
+ activeEngineId: s.activeEngineId,
314
+ hasSidecarItem: Boolean(sidecarItem),
315
+ engineBypass: process.env.GSD_ENGINE_BYPASS === "1",
316
+ })) {
433
317
  debugLog("autoLoop", { phase: "custom-engine-derive", iteration, engineId: s.activeEngineId });
434
318
  const { engine, policy } = resolveEngine({
435
319
  activeEngineId: s.activeEngineId,
@@ -452,43 +336,47 @@ export async function autoLoop(ctx, pi, s, deps, options) {
452
336
  }
453
337
  debugLog("autoLoop", { phase: "custom-engine-dispatch", iteration });
454
338
  const dispatch = await engine.resolveDispatch(engineState, { basePath: s.basePath });
455
- if (dispatch.action === "stop") {
456
- await deps.stopAuto(ctx, pi, dispatch.reason ?? "Engine stopped");
339
+ const engineDispatchDecision = decideEngineDispatch(dispatch.action === "stop"
340
+ ? { action: "stop", reason: dispatch.reason }
341
+ : { action: dispatch.action });
342
+ const dispatchFlow = await handleCustomEngineDispatchOutcome({
343
+ decision: engineDispatchDecision,
344
+ deps: {
345
+ stopAuto: reason => deps.stopAuto(ctx, pi, reason),
346
+ },
347
+ });
348
+ if (dispatchFlow.action === "break") {
349
+ finishTurn("stopped", "manual-attention", "custom-engine-dispatch-stop");
457
350
  break;
458
351
  }
459
- if (dispatch.action === "skip") {
352
+ if (dispatchFlow.action === "continue") {
353
+ finishTurn("skipped");
460
354
  continue;
461
355
  }
462
356
  // dispatch.action === "dispatch"
357
+ if (dispatch.action !== "dispatch") {
358
+ finishTurn("skipped");
359
+ continue;
360
+ }
463
361
  const step = dispatch.step;
464
- const gsdState = await deps.deriveState(s.canonicalProjectRoot);
465
- debugLog("autoLoop", {
466
- phase: "post-derive",
467
- site: "custom-engine-gsd-state",
362
+ iterData = await buildCustomEngineIterationData({
363
+ step,
468
364
  basePath: s.basePath,
469
365
  canonicalProjectRoot: s.canonicalProjectRoot,
470
- derivedPhase: gsdState.phase,
471
- activeUnit: gsdState.activeTask?.id ?? gsdState.activeSlice?.id ?? gsdState.activeMilestone?.id,
366
+ currentMilestoneId: s.currentMilestoneId,
367
+ deriveState: deps.deriveState,
368
+ logPostDerive: details => debugLog("autoLoop", {
369
+ phase: "post-derive",
370
+ ...details,
371
+ }),
472
372
  });
473
- iterData = {
474
- unitType: step.unitType,
475
- unitId: step.unitId,
476
- prompt: step.prompt,
477
- finalPrompt: step.prompt,
478
- pauseAfterUatDispatch: false,
479
- state: gsdState,
480
- mid: s.currentMilestoneId ?? "workflow",
481
- midTitle: "Workflow",
482
- isRetry: false,
483
- previousTier: undefined,
484
- };
485
373
  observedUnitType = iterData.unitType;
486
374
  observedUnitId = iterData.unitId;
487
375
  // ── Progress widget (mirrors dev path in runDispatch) ──
488
376
  deps.updateProgressWidget(ctx, iterData.unitType, iterData.unitId, iterData.state);
489
377
  // ── Guards (shared with dev path) ──
490
378
  const guardsResult = await runGuards(ic, s.currentMilestoneId ?? "workflow");
491
- deps.uokObserver?.onPhaseResult("guard", guardsResult.action, {
379
+ phaseReporter.report("guard", guardsResult.action, {
492
380
  unitType: iterData.unitType,
493
381
  unitId: iterData.unitId,
494
382
  });
@@ -498,13 +386,13 @@ export async function autoLoop(ctx, pi, s, deps, options) {
498
386
  }
499
387
  // ── Unit execution (shared with dev path) ──
500
388
  await enforceMinRequestInterval(s, prefs);
501
- const unitPhaseResult = await runUnitPhaseViaContract(dispatchContract, ic, iterData, loopState);
389
+ const unitPhaseResult = await runUnitPhaseViaContract(dispatchContract, ic, iterData, loopState, undefined, unitDispatchDeps);
502
390
  if (unitPhaseResult.action === "next") {
503
- const requestTimestamp = unitPhaseResult.data.requestDispatchedAt ?? unitPhaseResult.data.unitStartedAt;
504
- if (typeof requestTimestamp === "number")
391
+ const requestTimestamp = resolveUnitRequestTimestamp(unitPhaseResult.data);
392
+ if (requestTimestamp !== undefined)
505
393
  s.lastRequestTimestamp = requestTimestamp;
506
394
  }
507
- deps.uokObserver?.onPhaseResult("unit", unitPhaseResult.action, {
395
+ phaseReporter.report("unit", unitPhaseResult.action, {
508
396
  unitType: iterData.unitType,
509
397
  unitId: iterData.unitId,
510
398
  });
@@ -516,103 +404,94 @@ export async function autoLoop(ctx, pi, s, deps, options) {
516
404
  debugLog("autoLoop", { phase: "custom-engine-verify", iteration, unitId: iterData.unitId });
517
405
  const verifyResult = await policy.verify(iterData.unitType, iterData.unitId, { basePath: s.basePath });
518
406
  if (verifyResult === "pause") {
519
- await deps.pauseAuto(ctx, pi);
520
- deps.uokObserver?.onPhaseResult("custom-engine", "pause", {
407
+ const verifyFlow = await handleCustomEngineVerifyPause({
521
408
  unitType: iterData.unitType,
522
409
  unitId: iterData.unitId,
410
+ deps: {
411
+ pauseAuto: () => deps.pauseAuto(ctx, pi),
412
+ stopAuto: reason => deps.stopAuto(ctx, pi, reason),
413
+ reportPause: details => phaseReporter.report("custom-engine", "pause", details),
414
+ finishTurn,
415
+ },
523
416
  });
524
- finishTurn("paused", "manual-attention", "custom-engine-verify-pause");
525
- break;
417
+ if (verifyFlow.action === "break")
418
+ break;
526
419
  }
527
420
  if (verifyResult === "retry") {
528
- const recoveryKey = `${iterData.unitType}/${iterData.unitId}`;
529
- const retryCounts = hydrateCustomVerifyRetryCounts(s);
530
- const attempts = (retryCounts.get(recoveryKey) ?? 0) + 1;
531
- retryCounts.set(recoveryKey, attempts);
532
- saveCustomVerifyRetryCounts(s);
533
- debugLog("autoLoop", { phase: "custom-engine-verify-retry", iteration, unitId: iterData.unitId, attempts });
534
- deps.uokObserver?.onPhaseResult("custom-engine", "retry", {
421
+ const retryOutcome = await handleCustomEngineVerifyRetry({
422
+ session: s,
535
423
  unitType: iterData.unitType,
536
424
  unitId: iterData.unitId,
537
- attempts,
425
+ basePath: s.basePath,
426
+ iteration,
427
+ maxRetries: MAX_CUSTOM_ENGINE_VERIFY_RETRIES,
428
+ deps: {
429
+ hydrateRetryCounts: () => hydrateCustomVerifyRetryCounts(s, {
430
+ logFailure: logCustomVerifyRetryLoadFailure,
431
+ }),
432
+ saveRetryCounts: () => saveCustomVerifyRetryCounts(s, {
433
+ logFailure: logCustomVerifyRetrySaveFailure,
434
+ }),
435
+ recover: (unitType, unitId, options) => policy.recover(unitType, unitId, options),
436
+ logRetry: details => debugLog("autoLoop", {
437
+ phase: "custom-engine-verify-retry",
438
+ ...details,
439
+ }),
440
+ reportRetry: details => phaseReporter.report("custom-engine", "retry", details),
441
+ },
538
442
  });
539
- if (attempts > MAX_CUSTOM_ENGINE_VERIFY_RETRIES) {
540
- const recovery = await policy.recover(iterData.unitType, iterData.unitId, { basePath: s.basePath });
541
- if (recovery.outcome === "pause") {
542
- await deps.pauseAuto(ctx, pi);
543
- finishTurn("paused", "manual-attention", recovery.reason ?? "custom-engine-verify-retry-exhausted");
544
- break;
545
- }
546
- if (recovery.outcome === "skip") {
547
- await deps.stopAuto(ctx, pi, recovery.reason ??
548
- `Custom workflow verification for ${iterData.unitId} requested skip after retry exhaustion, but the custom engine cannot reconcile skipped steps.`);
549
- finishTurn("stopped", "manual-attention", "custom-engine-verify-retry-exhausted");
550
- break;
551
- }
552
- const exhaustedReason = `Custom workflow verification for ${iterData.unitId} requested retry ${attempts} times without passing.`;
553
- await deps.stopAuto(ctx, pi, recovery.outcome === "stop" && recovery.reason ? recovery.reason : exhaustedReason);
554
- finishTurn("stopped", "manual-attention", "custom-engine-verify-retry-exhausted");
443
+ const retryFlow = await handleCustomEngineVerifyRetryOutcome({
444
+ outcome: retryOutcome,
445
+ deps: {
446
+ pauseAuto: () => deps.pauseAuto(ctx, pi),
447
+ stopAuto: reason => deps.stopAuto(ctx, pi, reason),
448
+ reportPause: details => phaseReporter.report("custom-engine", "pause", details),
449
+ finishTurn,
450
+ },
451
+ });
452
+ if (retryFlow.action === "break")
555
453
  break;
556
- }
557
- finishTurn("retry");
558
454
  continue;
559
455
  }
560
456
  // Verification passed — mark step complete
561
- s.verificationRetryCount?.delete(`${iterData.unitType}/${iterData.unitId}`);
562
- saveCustomVerifyRetryCounts(s);
563
- debugLog("autoLoop", { phase: "custom-engine-reconcile", iteration, unitId: iterData.unitId });
564
- const reconcileResult = await engine.reconcile(engineState, {
565
- unitType: iterData.unitType,
566
- unitId: iterData.unitId,
567
- startedAt: s.currentUnit?.startedAt ?? Date.now(),
568
- finishedAt: Date.now(),
457
+ const reconcileOutcome = await handleCustomEngineReconcile({
458
+ session: s,
459
+ engineState,
460
+ iterData,
461
+ iteration,
462
+ deps: {
463
+ saveRetryCounts: () => saveCustomVerifyRetryCounts(s, {
464
+ logFailure: logCustomVerifyRetrySaveFailure,
465
+ }),
466
+ logReconcile: details => debugLog("autoLoop", {
467
+ phase: "custom-engine-reconcile",
468
+ ...details,
469
+ }),
470
+ reconcile: (state, completedStep) => engine.reconcile(state, completedStep),
471
+ now: () => Date.now(),
472
+ clearUnitTimeout: deps.clearUnitTimeout,
473
+ completeIteration,
474
+ },
569
475
  });
570
- deps.clearUnitTimeout();
571
- consecutiveErrors = 0;
572
- consecutiveCooldowns = 0;
573
- recentErrorMessages.length = 0;
574
- deps.emitJournalEvent({ ts: new Date().toISOString(), flowId, seq: nextSeq(), eventType: "iteration-end", data: { iteration } });
575
- saveStuckState(s, loopState); // persist across session restarts (#3704)
576
- debugLog("autoLoop", { phase: "iteration-complete", iteration });
577
- if (reconcileResult.outcome === "milestone-complete") {
578
- await deps.stopAuto(ctx, pi, "Workflow complete");
579
- deps.uokObserver?.onPhaseResult("custom-engine", "milestone-complete", {
580
- unitType: iterData.unitType,
581
- unitId: iterData.unitId,
582
- });
583
- finishTurn("completed");
584
- break;
585
- }
586
- if (reconcileResult.outcome === "pause") {
587
- await deps.pauseAuto(ctx, pi);
588
- deps.uokObserver?.onPhaseResult("custom-engine", "pause", {
589
- unitType: iterData.unitType,
590
- unitId: iterData.unitId,
591
- });
592
- finishTurn("paused", "manual-attention");
593
- break;
594
- }
595
- if (reconcileResult.outcome === "stop") {
596
- await deps.stopAuto(ctx, pi, reconcileResult.reason ?? "Engine stopped");
597
- deps.uokObserver?.onPhaseResult("custom-engine", "stop", {
598
- unitType: iterData.unitType,
599
- unitId: iterData.unitId,
600
- reason: reconcileResult.reason,
601
- });
602
- finishTurn("stopped", "manual-attention", reconcileResult.reason);
603
- break;
604
- }
605
- deps.uokObserver?.onPhaseResult("custom-engine", "continue", {
476
+ const reconcileFlow = await handleCustomEngineReconcileOutcome({
477
+ outcome: reconcileOutcome,
606
478
  unitType: iterData.unitType,
607
479
  unitId: iterData.unitId,
480
+ deps: {
481
+ stopAuto: reason => deps.stopAuto(ctx, pi, reason),
482
+ pauseAuto: () => deps.pauseAuto(ctx, pi),
483
+ report: (action, details) => phaseReporter.report("custom-engine", action, details),
484
+ finishTurn,
485
+ },
608
486
  });
609
- finishTurn("completed");
487
+ if (reconcileFlow.action === "break")
488
+ break;
610
489
  continue;
611
490
  }
612
491
  if (!sidecarItem) {
613
492
  // ── Phase 1: Pre-dispatch ─────────────────────────────────────────
614
493
  const preDispatchResult = await runPreDispatch(ic, loopState);
615
- deps.uokObserver?.onPhaseResult("pre-dispatch", preDispatchResult.action);
494
+ phaseReporter.report("pre-dispatch", preDispatchResult.action);
616
495
  if (preDispatchResult.action === "break") {
617
496
  finishTurn("stopped", "manual-attention", "pre-dispatch-break");
618
497
  break;
@@ -624,14 +503,14 @@ export async function autoLoop(ctx, pi, s, deps, options) {
624
503
  const preData = preDispatchResult.data;
625
504
  // ── Phase 2: Guards ───────────────────────────────────────────────
626
505
  const guardsResult = await runGuards(ic, preData.mid);
627
- deps.uokObserver?.onPhaseResult("guard", guardsResult.action);
506
+ phaseReporter.report("guard", guardsResult.action);
628
507
  if (guardsResult.action === "break") {
629
508
  finishTurn("stopped", "manual-attention", "guard-break");
630
509
  break;
631
510
  }
632
511
  // ── Phase 3: Dispatch ─────────────────────────────────────────────
633
512
  const dispatchResult = await runDispatch(ic, preData, loopState);
634
- deps.uokObserver?.onPhaseResult("dispatch", dispatchResult.action);
513
+ phaseReporter.report("dispatch", dispatchResult.action);
635
514
  if (dispatchResult.action === "break") {
636
515
  finishTurn("stopped", "manual-attention", "dispatch-break");
637
516
  break;
@@ -645,30 +524,19 @@ export async function autoLoop(ctx, pi, s, deps, options) {
645
524
  observedUnitId = iterData.unitId;
646
525
  }
647
526
  else {
648
- // ── Sidecar path: use values from the sidecar item directly ──
649
- const sidecarState = await deps.deriveState(s.canonicalProjectRoot);
650
- debugLog("autoLoop", {
651
- phase: "post-derive",
652
- site: "sidecar",
527
+ iterData = await buildSidecarIterationData({
528
+ sidecarItem,
653
529
  basePath: s.basePath,
654
530
  canonicalProjectRoot: s.canonicalProjectRoot,
655
- derivedPhase: sidecarState.phase,
656
- activeUnit: sidecarState.activeTask?.id ?? sidecarState.activeSlice?.id ?? sidecarState.activeMilestone?.id,
531
+ deriveState: deps.deriveState,
532
+ logPostDerive: details => debugLog("autoLoop", {
533
+ phase: "post-derive",
534
+ ...details,
535
+ }),
657
536
  });
658
- iterData = {
659
- unitType: sidecarItem.unitType,
660
- unitId: sidecarItem.unitId,
661
- prompt: sidecarItem.prompt,
662
- finalPrompt: sidecarItem.prompt,
663
- pauseAfterUatDispatch: false,
664
- state: sidecarState,
665
- mid: sidecarState.activeMilestone?.id,
666
- midTitle: sidecarState.activeMilestone?.title,
667
- isRetry: false, previousTier: undefined,
668
- };
669
537
  observedUnitType = iterData.unitType;
670
538
  observedUnitId = iterData.unitId;
671
- deps.uokObserver?.onPhaseResult("dispatch", "sidecar", {
539
+ phaseReporter.report("dispatch", "sidecar", {
672
540
  unitType: iterData.unitType,
673
541
  unitId: iterData.unitId,
674
542
  sidecarKind: sidecarItem.kind,
@@ -681,52 +549,51 @@ export async function autoLoop(ctx, pi, s, deps, options) {
681
549
  // null when DB unavailable, no worker registered, or no active lease
682
550
  // — those degraded paths fall through to the existing single-worker
683
551
  // semantics with no ledger entry, preserving back-compat.
684
- const dispatchClaim = openDispatchClaim(s, flowId, turnId, iterData);
685
- if (dispatchClaim.kind === "skip") {
686
- finishTurn("skipped", "execution", dispatchClaim.reason);
552
+ const dispatchClaim = openDispatchClaim(s, flowId, turnId, iterData, {
553
+ getRecentDispatchesForUnit,
554
+ recordDispatchClaim,
555
+ markDispatchRunning,
556
+ logClaimRejected: logDispatchClaimRejected,
557
+ logClaimFailed: logDispatchClaimFailed,
558
+ });
559
+ const dispatchDecision = decideDispatchClaim(dispatchClaim.kind === "opened"
560
+ ? { kind: "opened", dispatchId: dispatchClaim.dispatchId }
561
+ : dispatchClaim.kind === "skip"
562
+ ? { kind: "skip", reason: dispatchClaim.reason }
563
+ : { kind: "degraded" });
564
+ if (dispatchDecision.action === "skip") {
565
+ finishTurn("skipped", "execution", dispatchDecision.reason);
687
566
  continue;
688
567
  }
689
- dispatchId = dispatchClaim.kind === "opened" ? dispatchClaim.dispatchId : null;
568
+ dispatchId = dispatchDecision.dispatchId;
690
569
  let unitPhaseResult;
691
570
  try {
692
- unitPhaseResult = await runUnitPhaseViaContract(dispatchContract, ic, iterData, loopState, sidecarItem);
571
+ unitPhaseResult = await runUnitPhaseViaContract(dispatchContract, ic, iterData, loopState, sidecarItem, unitDispatchDeps);
693
572
  }
694
573
  catch (err) {
695
574
  if (err instanceof ModelPolicyDispatchBlockedError) {
696
575
  throw err;
697
576
  }
698
- if (dispatchId !== null) {
699
- try {
700
- markDispatchFailed(dispatchId, {
701
- errorSummary: `exception:${err instanceof Error ? err.message : String(err)}`,
702
- });
703
- dispatchSettled = true;
704
- }
705
- catch (ledgerErr) {
706
- debugLog("autoLoop", { phase: "dispatch-ledger-write-failed", error: ledgerErr instanceof Error ? ledgerErr.message : String(ledgerErr) });
707
- }
708
- }
577
+ dispatchSettled = settleDispatchFailed(dispatchId, formatDispatchExceptionSummary({ error: err }), {
578
+ markFailed: markDispatchFailed,
579
+ logWriteFailure: logDispatchLedgerWriteFailure,
580
+ }) || dispatchSettled;
709
581
  throw err;
710
582
  }
711
583
  if (unitPhaseResult.action === "next") {
712
- const requestTimestamp = unitPhaseResult.data.requestDispatchedAt ?? unitPhaseResult.data.unitStartedAt;
713
- if (typeof requestTimestamp === "number")
584
+ const requestTimestamp = resolveUnitRequestTimestamp(unitPhaseResult.data);
585
+ if (requestTimestamp !== undefined)
714
586
  s.lastRequestTimestamp = requestTimestamp;
715
587
  }
716
- deps.uokObserver?.onPhaseResult("unit", unitPhaseResult.action, {
588
+ phaseReporter.report("unit", unitPhaseResult.action, {
717
589
  unitType: iterData.unitType,
718
590
  unitId: iterData.unitId,
719
591
  });
720
592
  if (unitPhaseResult.action === "break") {
721
- if (dispatchId !== null) {
722
- try {
723
- markDispatchFailed(dispatchId, { errorSummary: "unit-break" });
724
- dispatchSettled = true;
725
- }
726
- catch (err) {
727
- debugLog("autoLoop", { phase: "dispatch-ledger-write-failed", error: err instanceof Error ? err.message : String(err) });
728
- }
729
- }
593
+ dispatchSettled = settleDispatchFailed(dispatchId, "unit-break", {
594
+ markFailed: markDispatchFailed,
595
+ logWriteFailure: logDispatchLedgerWriteFailure,
596
+ }) || dispatchSettled;
730
597
  finishTurn("stopped", "execution", "unit-break");
731
598
  break;
732
599
  }
@@ -736,88 +603,57 @@ export async function autoLoop(ctx, pi, s, deps, options) {
736
603
  finalizeResult = await runFinalize(ic, iterData, loopState, sidecarItem);
737
604
  }
738
605
  catch (err) {
739
- if (dispatchId !== null) {
740
- try {
741
- markDispatchFailed(dispatchId, {
742
- errorSummary: `exception:${err instanceof Error ? err.message : String(err)}`,
743
- });
744
- dispatchSettled = true;
745
- }
746
- catch (ledgerErr) {
747
- debugLog("autoLoop", { phase: "dispatch-ledger-write-failed", error: ledgerErr instanceof Error ? ledgerErr.message : String(ledgerErr) });
748
- }
749
- }
606
+ dispatchSettled = settleDispatchFailed(dispatchId, formatDispatchExceptionSummary({ error: err }), {
607
+ markFailed: markDispatchFailed,
608
+ logWriteFailure: logDispatchLedgerWriteFailure,
609
+ }) || dispatchSettled;
750
610
  throw err;
751
611
  }
752
- deps.uokObserver?.onPhaseResult("finalize", finalizeResult.action, {
612
+ phaseReporter.report("finalize", finalizeResult.action, {
753
613
  unitType: iterData.unitType,
754
614
  unitId: iterData.unitId,
755
615
  });
756
- if (finalizeResult.action === "break") {
757
- const finalizeFailureClass = finalizeResult.reason === "git-closeout-failure"
758
- ? "git"
759
- : "closeout";
760
- if (dispatchId !== null) {
761
- try {
762
- markDispatchFailed(dispatchId, { errorSummary: `finalize-break:${finalizeResult.reason ?? "unknown"}` });
763
- dispatchSettled = true;
764
- }
765
- catch (err) {
766
- debugLog("autoLoop", { phase: "dispatch-ledger-write-failed", error: err instanceof Error ? err.message : String(err) });
767
- }
768
- }
769
- finishTurn("stopped", finalizeFailureClass, "finalize-break");
616
+ const finalizeDecision = decideFinalizeResult(finalizeResult.action === "break"
617
+ ? { action: "break", reason: finalizeResult.reason }
618
+ : finalizeResult.action === "continue"
619
+ ? { action: "continue" }
620
+ : { action: "next" });
621
+ if (finalizeDecision.action === "stop") {
622
+ dispatchSettled = settleDispatchFailed(dispatchId, finalizeDecision.ledgerErrorSummary, {
623
+ markFailed: markDispatchFailed,
624
+ logWriteFailure: logDispatchLedgerWriteFailure,
625
+ }) || dispatchSettled;
626
+ finishTurn("stopped", finalizeDecision.failureClass, finalizeDecision.turnError);
770
627
  break;
771
628
  }
772
- if (finalizeResult.action === "continue") {
773
- if (dispatchId !== null) {
774
- try {
775
- markDispatchFailed(dispatchId, { errorSummary: "finalize-retry" });
776
- dispatchSettled = true;
777
- }
778
- catch (err) {
779
- debugLog("autoLoop", { phase: "dispatch-ledger-write-failed", error: err instanceof Error ? err.message : String(err) });
780
- }
781
- }
629
+ if (finalizeDecision.action === "retry") {
630
+ dispatchSettled = settleDispatchFailed(dispatchId, finalizeDecision.ledgerErrorSummary, {
631
+ markFailed: markDispatchFailed,
632
+ logWriteFailure: logDispatchLedgerWriteFailure,
633
+ }) || dispatchSettled;
782
634
  finishTurn("retry");
783
635
  continue;
784
636
  }
785
- if (dispatchId !== null) {
786
- try {
787
- markDispatchCompleted(dispatchId);
788
- dispatchSettled = true;
789
- }
790
- catch (err) {
791
- debugLog("autoLoop", { phase: "dispatch-ledger-write-failed", error: err instanceof Error ? err.message : String(err) });
792
- }
793
- }
794
- consecutiveErrors = 0; // Iteration completed successfully
795
- consecutiveCooldowns = 0;
796
- recentErrorMessages.length = 0;
797
- deps.emitJournalEvent({ ts: new Date().toISOString(), flowId, seq: nextSeq(), eventType: "iteration-end", data: { iteration } });
798
- saveStuckState(s, loopState); // persist across session restarts (#4382)
799
- debugLog("autoLoop", { phase: "iteration-complete", iteration });
637
+ dispatchSettled = settleDispatchCompleted(dispatchId, {
638
+ markCompleted: markDispatchCompleted,
639
+ logWriteFailure: logDispatchLedgerWriteFailure,
640
+ }) || dispatchSettled;
641
+ completeIteration();
800
642
  finishTurn("completed");
801
643
  }
802
644
  catch (loopErr) {
803
645
  // ── Blanket catch: absorb unexpected exceptions, apply graduated recovery ──
804
646
  const msg = loopErr instanceof Error ? loopErr.message : String(loopErr);
805
647
  if (dispatchId !== null && !dispatchSettled && !(loopErr instanceof ModelPolicyDispatchBlockedError)) {
806
- try {
807
- markDispatchFailed(dispatchId, { errorSummary: `unhandled-error:${msg.slice(0, 200)}` });
808
- dispatchSettled = true;
809
- }
810
- catch (err) {
811
- debugLog("autoLoop", {
812
- phase: "dispatch-ledger-write-failed",
813
- error: err instanceof Error ? err.message : String(err),
814
- });
815
- }
648
+ dispatchSettled = settleDispatchFailed(dispatchId, formatUnhandledDispatchErrorSummary({ error: loopErr }), {
649
+ markFailed: markDispatchFailed,
650
+ logWriteFailure: logDispatchLedgerWriteFailure,
651
+ }) || dispatchSettled;
816
652
  }
817
653
  // Always emit iteration-end on error so the journal records iteration
818
654
  // completion even on failure (#2344). Without this, errors in
819
655
  // runFinalize leave the journal incomplete, making diagnosis harder.
820
- deps.emitJournalEvent({ ts: new Date().toISOString(), flowId, seq: nextSeq(), eventType: "iteration-end", data: { iteration, error: msg } });
656
+ journalReporter.emit("iteration-end", { iteration, error: msg });
821
657
  // ── Pre-send model-policy block: not a retryable error (#4959 / #4850) ──
822
658
  // The model-policy gate runs before the prompt is sent. When every
823
659
  // candidate model is denied (cross-provider disabled + flat-rate
@@ -827,6 +663,12 @@ export async function autoLoop(ctx, pi, s, deps, options) {
827
663
  // instead, with the per-model deny reasons surfaced from the typed
828
664
  // error.
829
665
  if (loopErr instanceof ModelPolicyDispatchBlockedError) {
666
+ const policyDecision = decideModelPolicyBlocked({
667
+ unitType: loopErr.unitType,
668
+ unitId: loopErr.unitId,
669
+ errorMessage: msg,
670
+ reasons: loopErr.reasons,
671
+ });
830
672
  debugLog("autoLoop", {
831
673
  phase: "model-policy-blocked",
832
674
  iteration,
@@ -834,20 +676,8 @@ export async function autoLoop(ctx, pi, s, deps, options) {
834
676
  unitId: loopErr.unitId,
835
677
  reasons: loopErr.reasons,
836
678
  });
837
- ctx.ui.notify(`Auto-mode paused: model-policy denied dispatch for ${loopErr.unitType}/${loopErr.unitId}. ${msg}`, "error");
838
- deps.emitJournalEvent({
839
- ts: new Date().toISOString(),
840
- flowId,
841
- seq: nextSeq(),
842
- eventType: "unit-end",
843
- data: {
844
- unitType: loopErr.unitType,
845
- unitId: loopErr.unitId,
846
- status: "blocked",
847
- reason: "model-policy-dispatch-blocked",
848
- reasons: loopErr.reasons,
849
- },
850
- });
679
+ ctx.ui.notify(policyDecision.notifyMessage, "error");
680
+ journalReporter.emit("unit-end", policyDecision.journalData);
851
681
  // Carry the blocked unit identity into the turn-result observer:
852
682
  // the throw originated inside dispatch, so observedUnitType/Id were
853
683
  // not assigned by the success path at lines 453/631/647 — but the
@@ -855,7 +685,7 @@ export async function autoLoop(ctx, pi, s, deps, options) {
855
685
  observedUnitType = loopErr.unitType;
856
686
  observedUnitId = loopErr.unitId;
857
687
  await deps.pauseAuto(ctx, pi);
858
- finishTurn("paused", "manual-attention", msg);
688
+ finishTurn(policyDecision.turnStatus, policyDecision.failureClass, msg);
859
689
  // Do NOT increment consecutiveErrors — the failure is configuration,
860
690
  // not a transient runtime fault.
861
691
  break;
@@ -865,15 +695,19 @@ export async function autoLoop(ctx, pi, s, deps, options) {
865
695
  // LLM budget on guaranteed failures.
866
696
  const infraCode = isInfrastructureError(loopErr);
867
697
  if (infraCode) {
698
+ const infraDecision = decideInfrastructureError({
699
+ code: infraCode,
700
+ errorMessage: msg,
701
+ });
868
702
  debugLog("autoLoop", {
869
703
  phase: "infrastructure-error",
870
704
  iteration,
871
705
  code: infraCode,
872
706
  error: msg,
873
707
  });
874
- ctx.ui.notify(`Auto-mode stopped: infrastructure error ${infraCode} — ${msg}`, "error");
875
- await deps.stopAuto(ctx, pi, `Infrastructure error (${infraCode}): not recoverable by retry`);
876
- finishTurn("failed", "execution", msg);
708
+ ctx.ui.notify(infraDecision.notifyMessage, "error");
709
+ await deps.stopAuto(ctx, pi, infraDecision.stopMessage);
710
+ finishTurn(infraDecision.turnStatus, infraDecision.failureClass, msg);
877
711
  break;
878
712
  }
879
713
  // ── Credential cooldown: wait and retry with bounded budget ──
@@ -885,6 +719,12 @@ export async function autoLoop(ctx, pi, s, deps, options) {
885
719
  if (isTransientCooldownError(loopErr)) {
886
720
  consecutiveCooldowns++;
887
721
  const retryAfterMs = getCooldownRetryAfterMs(loopErr);
722
+ const cooldownDecision = decideCooldownRecovery({
723
+ consecutiveCooldowns,
724
+ maxCooldownRetries: MAX_COOLDOWN_RETRIES,
725
+ retryAfterMs,
726
+ fallbackWaitMs: COOLDOWN_FALLBACK_WAIT_MS,
727
+ });
888
728
  debugLog("autoLoop", {
889
729
  phase: "cooldown-wait",
890
730
  iteration,
@@ -892,16 +732,13 @@ export async function autoLoop(ctx, pi, s, deps, options) {
892
732
  retryAfterMs,
893
733
  error: msg,
894
734
  });
895
- if (consecutiveCooldowns > MAX_COOLDOWN_RETRIES) {
896
- ctx.ui.notify(`Auto-mode stopped: ${consecutiveCooldowns} consecutive credential cooldowns — rate limit or quota may be persistently exhausted.`, "error");
897
- await deps.stopAuto(ctx, pi, `${consecutiveCooldowns} consecutive credential cooldowns exceeded retry budget`);
735
+ if (cooldownDecision.action === "stop") {
736
+ ctx.ui.notify(cooldownDecision.notifyMessage, "error");
737
+ await deps.stopAuto(ctx, pi, cooldownDecision.stopMessage);
898
738
  break;
899
739
  }
900
- const waitMs = (retryAfterMs !== undefined && retryAfterMs > 0 && retryAfterMs <= 60_000)
901
- ? retryAfterMs + 500 // Use structured hint + small buffer
902
- : COOLDOWN_FALLBACK_WAIT_MS;
903
- ctx.ui.notify(`Credentials in cooldown (${consecutiveCooldowns}/${MAX_COOLDOWN_RETRIES}) — waiting ${Math.round(waitMs / 1000)}s before retrying.`, "warning");
904
- await new Promise(resolve => setTimeout(resolve, waitMs));
740
+ ctx.ui.notify(cooldownDecision.notifyMessage, "warning");
741
+ await new Promise(resolve => setTimeout(resolve, cooldownDecision.waitMs));
905
742
  finishTurn("retry", "timeout", msg);
906
743
  continue; // Retry iteration without incrementing consecutiveErrors
907
744
  }
@@ -913,26 +750,25 @@ export async function autoLoop(ctx, pi, s, deps, options) {
913
750
  consecutiveErrors,
914
751
  error: msg,
915
752
  });
916
- if (consecutiveErrors >= 3) {
917
- // 3+ consecutive: hard stop — something is fundamentally broken
918
- const errorHistory = recentErrorMessages
919
- .map((m, i) => ` ${i + 1}. ${m}`)
920
- .join("\n");
921
- ctx.ui.notify(`Auto-mode stopped: ${consecutiveErrors} consecutive iteration failures:\n${errorHistory}`, "error");
922
- await deps.stopAuto(ctx, pi, `${consecutiveErrors} consecutive iteration failures`);
923
- finishTurn("failed", "execution", msg);
753
+ const errorDecision = decideIterationErrorRecovery({
754
+ consecutiveErrors,
755
+ recentErrorMessages,
756
+ currentErrorMessage: msg,
757
+ });
758
+ if (errorDecision.action === "stop") {
759
+ ctx.ui.notify(errorDecision.notifyMessage, "error");
760
+ await deps.stopAuto(ctx, pi, errorDecision.stopMessage);
761
+ finishTurn(errorDecision.turnStatus, "execution", msg);
924
762
  break;
925
763
  }
926
- else if (consecutiveErrors === 2) {
927
- // 2nd consecutive: try invalidating caches + re-deriving state
928
- ctx.ui.notify(`Iteration error (attempt ${consecutiveErrors}): ${msg}. Invalidating caches and retrying.`, "warning");
764
+ if (errorDecision.action === "invalidate-and-retry") {
765
+ ctx.ui.notify(errorDecision.notifyMessage, "warning");
929
766
  deps.invalidateAllCaches();
930
767
  }
931
768
  else {
932
- // 1st error: log and retry — transient failures happen
933
- ctx.ui.notify(`Iteration error: ${msg}. Retrying.`, "warning");
769
+ ctx.ui.notify(errorDecision.notifyMessage, "warning");
934
770
  }
935
- finishTurn("retry", "execution", msg);
771
+ finishTurn(errorDecision.turnStatus, "execution", msg);
936
772
  }
937
773
  }
938
774
  _clearCurrentResolve();