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