gsd-pi 2.79.0-dev.ece5fd8ba → 2.80.0-dev.4ea7d80e7

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 (681) hide show
  1. package/dist/resources/.managed-resources-content-hash +1 -1
  2. package/dist/resources/extensions/github-sync/templates.js +90 -74
  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 +365 -522
  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/run-unit.js +19 -15
  9. package/dist/resources/extensions/gsd/auto/session.js +8 -0
  10. package/dist/resources/extensions/gsd/auto/workflow-custom-engine-dispatch-outcome.js +12 -0
  11. package/dist/resources/extensions/gsd/auto/workflow-custom-engine-iteration.js +24 -0
  12. package/dist/resources/extensions/gsd/auto/workflow-custom-engine-reconcile-outcome.js +33 -0
  13. package/dist/resources/extensions/gsd/auto/workflow-custom-engine-reconcile.js +26 -0
  14. package/dist/resources/extensions/gsd/auto/workflow-custom-engine-retry.js +49 -0
  15. package/dist/resources/extensions/gsd/auto/workflow-custom-engine-verify-outcome.js +25 -0
  16. package/dist/resources/extensions/gsd/auto/workflow-dispatch-claim.js +48 -0
  17. package/dist/resources/extensions/gsd/auto/workflow-dispatch-ledger.js +26 -0
  18. package/dist/resources/extensions/gsd/auto/workflow-iteration-completion.js +10 -0
  19. package/dist/resources/extensions/gsd/auto/workflow-journal-reporter.js +16 -0
  20. package/dist/resources/extensions/gsd/auto/workflow-kernel.js +263 -0
  21. package/dist/resources/extensions/gsd/auto/workflow-memory-pressure.js +36 -0
  22. package/dist/resources/extensions/gsd/auto/workflow-phase-reporter.js +9 -0
  23. package/dist/resources/extensions/gsd/auto/workflow-session-lock.js +35 -0
  24. package/dist/resources/extensions/gsd/auto/workflow-sidecar-iteration.js +24 -0
  25. package/dist/resources/extensions/gsd/auto/workflow-sidecar-queue.js +26 -0
  26. package/dist/resources/extensions/gsd/auto/workflow-turn-reporter.js +36 -0
  27. package/dist/resources/extensions/gsd/auto/workflow-unit-dispatch.js +44 -0
  28. package/dist/resources/extensions/gsd/auto/workflow-worker-heartbeat.js +15 -0
  29. package/dist/resources/extensions/gsd/auto-dashboard.js +54 -15
  30. package/dist/resources/extensions/gsd/auto-dispatch.js +10 -0
  31. package/dist/resources/extensions/gsd/auto-prompts.js +168 -3
  32. package/dist/resources/extensions/gsd/auto-recovery.js +198 -59
  33. package/dist/resources/extensions/gsd/auto-runtime-state.js +4 -0
  34. package/dist/resources/extensions/gsd/auto-start.js +2 -3
  35. package/dist/resources/extensions/gsd/auto-verification.js +2 -11
  36. package/dist/resources/extensions/gsd/auto-worktree.js +87 -38
  37. package/dist/resources/extensions/gsd/auto.js +168 -3
  38. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +24 -2
  39. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +10 -0
  40. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +22 -6
  41. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +129 -1
  42. package/dist/resources/extensions/gsd/commands-extract-learnings.js +17 -12
  43. package/dist/resources/extensions/gsd/commands-ship.js +23 -46
  44. package/dist/resources/extensions/gsd/commands-workflow-templates.js +12 -7
  45. package/dist/resources/extensions/gsd/component-loader.js +5 -11
  46. package/dist/resources/extensions/gsd/custom-workflow-engine.js +25 -1
  47. package/dist/resources/extensions/gsd/db-adapter.js +47 -0
  48. package/dist/resources/extensions/gsd/db-base-schema.js +351 -0
  49. package/dist/resources/extensions/gsd/db-connection-cache.js +31 -0
  50. package/dist/resources/extensions/gsd/db-coordination-schema.js +104 -0
  51. package/dist/resources/extensions/gsd/db-decision-requirement-rows.js +71 -0
  52. package/dist/resources/extensions/gsd/db-gate-rows.js +16 -0
  53. package/dist/resources/extensions/gsd/db-lightweight-query-rows.js +29 -0
  54. package/dist/resources/extensions/gsd/db-memory-fts-schema.js +56 -0
  55. package/dist/resources/extensions/gsd/db-migration-backup.js +22 -0
  56. package/dist/resources/extensions/gsd/db-migration-steps.js +410 -0
  57. package/dist/resources/extensions/gsd/db-milestone-artifact-rows.js +35 -0
  58. package/dist/resources/extensions/gsd/db-open-state.js +32 -0
  59. package/dist/resources/extensions/gsd/db-provider.js +108 -0
  60. package/dist/resources/extensions/gsd/db-runtime-kv-schema.js +27 -0
  61. package/dist/resources/extensions/gsd/db-schema-metadata.js +23 -0
  62. package/dist/resources/extensions/gsd/db-task-slice-rows.js +86 -0
  63. package/dist/resources/extensions/gsd/db-transaction.js +63 -0
  64. package/dist/resources/extensions/gsd/db-verification-evidence-rows.js +3 -0
  65. package/dist/resources/extensions/gsd/db-verification-evidence-schema.js +19 -0
  66. package/dist/resources/extensions/gsd/escalation.js +2 -0
  67. package/dist/resources/extensions/gsd/graph.js +9 -3
  68. package/dist/resources/extensions/gsd/gsd-db.js +316 -1520
  69. package/dist/resources/extensions/gsd/guided-flow.js +2 -2
  70. package/dist/resources/extensions/gsd/legacy-telemetry.js +70 -0
  71. package/dist/resources/extensions/gsd/markdown-renderer.js +2 -0
  72. package/dist/resources/extensions/gsd/model-router.js +9 -6
  73. package/dist/resources/extensions/gsd/notification-widget.js +21 -3
  74. package/dist/resources/extensions/gsd/post-execution-checks.js +27 -6
  75. package/dist/resources/extensions/gsd/pr-evidence.js +117 -0
  76. package/dist/resources/extensions/gsd/pre-execution-checks.js +2 -0
  77. package/dist/resources/extensions/gsd/process-task-path.js +61 -0
  78. package/dist/resources/extensions/gsd/prompt-loader.js +9 -5
  79. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +32 -30
  80. package/dist/resources/extensions/gsd/prompts/complete-slice.md +23 -34
  81. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +50 -96
  82. package/dist/resources/extensions/gsd/prompts/discuss.md +81 -181
  83. package/dist/resources/extensions/gsd/prompts/execute-task.md +40 -67
  84. package/dist/resources/extensions/gsd/prompts/forensics.md +41 -84
  85. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +29 -39
  86. package/dist/resources/extensions/gsd/prompts/guided-discuss-project.md +30 -65
  87. package/dist/resources/extensions/gsd/prompts/guided-discuss-requirements.md +25 -52
  88. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +36 -36
  89. package/dist/resources/extensions/gsd/prompts/guided-research-project.md +20 -38
  90. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +45 -59
  91. package/dist/resources/extensions/gsd/prompts/plan-slice.md +25 -87
  92. package/dist/resources/extensions/gsd/prompts/queue.md +46 -53
  93. package/dist/resources/extensions/gsd/prompts/refine-slice.md +23 -23
  94. package/dist/resources/extensions/gsd/prompts/research-slice.md +23 -23
  95. package/dist/resources/extensions/gsd/prompts/rethink.md +10 -10
  96. package/dist/resources/extensions/gsd/prompts/system.md +65 -107
  97. package/dist/resources/extensions/gsd/prompts/triage-captures.md +15 -15
  98. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +24 -24
  99. package/dist/resources/extensions/gsd/prompts/worktree-merge.md +35 -35
  100. package/dist/resources/extensions/gsd/state.js +4 -0
  101. package/dist/resources/extensions/gsd/tools/complete-milestone.js +14 -9
  102. package/dist/resources/extensions/gsd/tools/complete-task.js +2 -0
  103. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +6 -1
  104. package/dist/resources/extensions/gsd/unit-context-composer.js +1 -1
  105. package/dist/resources/extensions/gsd/uok/kernel.js +8 -3
  106. package/dist/resources/extensions/gsd/uok/plan-v2.js +2 -0
  107. package/dist/resources/extensions/gsd/workflow-logger.js +13 -13
  108. package/dist/resources/extensions/gsd/workflow-manifest.js +2 -0
  109. package/dist/resources/extensions/gsd/workflow-projections.js +2 -0
  110. package/dist/resources/extensions/gsd/workflow-templates.js +9 -0
  111. package/dist/resources/extensions/gsd/working-output-messages.js +64 -0
  112. package/dist/resources/extensions/shared/interview-ui.js +15 -4
  113. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  114. package/dist/web/standalone/.next/BUILD_ID +1 -1
  115. package/dist/web/standalone/.next/app-path-routes-manifest.json +14 -14
  116. package/dist/web/standalone/.next/build-manifest.json +3 -3
  117. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  118. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  119. package/dist/web/standalone/.next/required-server-files.json +1 -1
  120. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  121. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  122. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  123. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  124. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  125. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  126. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  127. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  128. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  129. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  130. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  131. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  132. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  133. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  134. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  135. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  136. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  137. package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
  138. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  139. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
  140. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  141. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
  142. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +1 -1
  143. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
  144. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  145. package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
  146. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  147. package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
  148. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  149. package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
  150. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  151. package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
  152. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  153. package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  154. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  155. package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
  156. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  157. package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  158. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  159. package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
  160. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  161. package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
  162. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  163. package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
  164. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  165. package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
  166. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  167. package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
  168. package/dist/web/standalone/.next/server/app/api/notifications/route.js +1 -1
  169. package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  170. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  171. package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
  172. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  173. package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  174. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  175. package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
  176. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  177. package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
  178. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  179. package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
  180. package/dist/web/standalone/.next/server/app/api/session/events/route.js +1 -1
  181. package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
  182. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  183. package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
  184. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  185. package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
  186. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  187. package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
  188. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  189. package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
  190. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  191. package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
  192. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
  193. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
  194. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +1 -1
  195. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
  196. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  197. package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
  198. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  199. package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
  200. package/dist/web/standalone/.next/server/app/index.html +1 -1
  201. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  202. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  203. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  204. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  205. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  206. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  207. package/dist/web/standalone/.next/server/app-paths-manifest.json +14 -14
  208. package/dist/web/standalone/.next/server/chunks/167.js +2 -0
  209. package/dist/web/standalone/.next/server/chunks/6897.js +3 -3
  210. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  211. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  212. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  213. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  214. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  215. package/dist/web/standalone/.next/static/chunks/{8336.6f6f30e410419aff.js → 8336.631939fb583761fa.js} +1 -1
  216. package/dist/web/standalone/.next/static/chunks/{webpack-d82dbee6356c1733.js → webpack-0481f1221120a7c6.js} +1 -1
  217. package/dist/web/standalone/package.json +1 -0
  218. package/dist/web/standalone/server.js +1 -1
  219. package/package.json +18 -7
  220. package/packages/contracts/dist/index.d.ts +3 -0
  221. package/packages/contracts/dist/index.d.ts.map +1 -0
  222. package/packages/contracts/dist/index.js +5 -0
  223. package/packages/contracts/dist/index.js.map +1 -0
  224. package/packages/contracts/dist/rpc.d.ts +549 -0
  225. package/packages/contracts/dist/rpc.d.ts.map +1 -0
  226. package/packages/contracts/dist/rpc.js +53 -0
  227. package/packages/contracts/dist/rpc.js.map +1 -0
  228. package/packages/contracts/dist/rpc.test.d.ts +2 -0
  229. package/packages/contracts/dist/rpc.test.d.ts.map +1 -0
  230. package/packages/contracts/dist/rpc.test.js +47 -0
  231. package/packages/contracts/dist/rpc.test.js.map +1 -0
  232. package/packages/contracts/dist/workflow.d.ts +180 -0
  233. package/packages/contracts/dist/workflow.d.ts.map +1 -0
  234. package/packages/contracts/dist/workflow.js +201 -0
  235. package/packages/contracts/dist/workflow.js.map +1 -0
  236. package/packages/contracts/package.json +39 -0
  237. package/packages/contracts/src/index.ts +5 -0
  238. package/packages/contracts/src/rpc.test.ts +72 -0
  239. package/packages/contracts/src/rpc.ts +286 -0
  240. package/packages/contracts/src/workflow.ts +213 -0
  241. package/packages/contracts/tsconfig.json +25 -0
  242. package/packages/daemon/package.json +3 -2
  243. package/packages/daemon/src/event-bridge.test.ts +2 -1
  244. package/packages/daemon/src/event-bridge.ts +1 -1
  245. package/packages/daemon/src/event-formatter.test.ts +1 -2
  246. package/packages/daemon/src/event-formatter.ts +1 -2
  247. package/packages/daemon/src/session-manager.ts +2 -2
  248. package/packages/daemon/src/types.ts +3 -18
  249. package/packages/mcp-server/dist/server.d.ts +13 -0
  250. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  251. package/packages/mcp-server/dist/server.js +77 -0
  252. package/packages/mcp-server/dist/server.js.map +1 -1
  253. package/packages/mcp-server/dist/session-manager.js +1 -1
  254. package/packages/mcp-server/dist/session-manager.js.map +1 -1
  255. package/packages/mcp-server/dist/types.d.ts +3 -11
  256. package/packages/mcp-server/dist/types.d.ts.map +1 -1
  257. package/packages/mcp-server/dist/types.js.map +1 -1
  258. package/packages/mcp-server/dist/workflow-tools.d.ts +1 -1
  259. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  260. package/packages/mcp-server/dist/workflow-tools.js +2 -40
  261. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  262. package/packages/mcp-server/package.json +3 -2
  263. package/packages/mcp-server/src/mcp-server.test.ts +138 -0
  264. package/packages/mcp-server/src/server.ts +99 -1
  265. package/packages/mcp-server/src/session-manager.ts +2 -2
  266. package/packages/mcp-server/src/types.ts +7 -18
  267. package/packages/mcp-server/src/workflow-tools.ts +2 -40
  268. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  269. package/packages/native/package.json +1 -1
  270. package/packages/pi-agent-core/package.json +1 -1
  271. package/packages/pi-ai/dist/models/fake-model.d.ts +12 -0
  272. package/packages/pi-ai/dist/models/fake-model.d.ts.map +1 -0
  273. package/packages/pi-ai/dist/models/fake-model.js +27 -0
  274. package/packages/pi-ai/dist/models/fake-model.js.map +1 -0
  275. package/packages/pi-ai/dist/models/index.d.ts.map +1 -1
  276. package/packages/pi-ai/dist/models/index.js +8 -0
  277. package/packages/pi-ai/dist/models/index.js.map +1 -1
  278. package/packages/pi-ai/dist/providers/fake.d.ts +42 -0
  279. package/packages/pi-ai/dist/providers/fake.d.ts.map +1 -0
  280. package/packages/pi-ai/dist/providers/fake.js +319 -0
  281. package/packages/pi-ai/dist/providers/fake.js.map +1 -0
  282. package/packages/pi-ai/dist/providers/register-builtins.d.ts.map +1 -1
  283. package/packages/pi-ai/dist/providers/register-builtins.js +24 -0
  284. package/packages/pi-ai/dist/providers/register-builtins.js.map +1 -1
  285. package/packages/pi-ai/package.json +1 -1
  286. package/packages/pi-ai/src/models/fake-model.ts +30 -0
  287. package/packages/pi-ai/src/models/index.ts +9 -0
  288. package/packages/pi-ai/src/providers/fake.ts +376 -0
  289. package/packages/pi-ai/src/providers/register-builtins.ts +23 -0
  290. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  291. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +74 -0
  292. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
  293. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +2 -0
  294. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  295. package/packages/pi-coding-agent/dist/core/extensions/runner.js +14 -1
  296. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  297. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +97 -0
  298. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
  299. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  300. package/packages/pi-coding-agent/dist/core/model-registry.js +5 -0
  301. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  302. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +4 -0
  303. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  304. package/packages/pi-coding-agent/dist/core/settings-manager.js +8 -0
  305. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  306. package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
  307. package/packages/pi-coding-agent/dist/core/slash-commands.js +1 -0
  308. package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
  309. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.js +6 -4
  310. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.js.map +1 -1
  311. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +67 -14
  312. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
  313. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.d.ts +26 -0
  314. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.d.ts.map +1 -0
  315. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js +112 -0
  316. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js.map +1 -0
  317. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.test.d.ts +2 -0
  318. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.test.d.ts.map +1 -0
  319. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.test.js +51 -0
  320. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.test.js.map +1 -0
  321. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  322. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
  323. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts.map +1 -1
  324. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js +10 -9
  325. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js.map +1 -1
  326. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +3 -0
  327. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  328. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +11 -0
  329. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
  330. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js +27 -6
  331. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js.map +1 -1
  332. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +16 -0
  333. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  334. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +112 -18
  335. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  336. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  337. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +60 -1
  338. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  339. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js +40 -1
  340. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js.map +1 -1
  341. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +1 -0
  342. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -1
  343. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +1 -0
  344. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
  345. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
  346. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +12 -1
  347. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  348. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +54 -10
  349. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  350. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.d.ts.map +1 -1
  351. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +20 -0
  352. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
  353. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.test.d.ts +2 -0
  354. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.test.d.ts.map +1 -0
  355. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.test.js +79 -0
  356. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.test.js.map +1 -0
  357. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-schema.d.ts +12 -0
  358. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-schema.d.ts.map +1 -1
  359. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-schema.js +13 -0
  360. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-schema.js.map +1 -1
  361. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts +1 -1
  362. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  363. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js +18 -1
  364. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js.map +1 -1
  365. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.d.ts.map +1 -1
  366. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +36 -27
  367. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
  368. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.d.ts +11 -0
  369. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.d.ts.map +1 -0
  370. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.js +18 -0
  371. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.js.map +1 -0
  372. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.d.ts +2 -0
  373. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.d.ts.map +1 -0
  374. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js +48 -0
  375. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js.map +1 -0
  376. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts +1 -512
  377. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  378. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.js +3 -7
  379. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.js.map +1 -1
  380. package/packages/pi-coding-agent/package.json +2 -1
  381. package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +87 -0
  382. package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +108 -0
  383. package/packages/pi-coding-agent/src/core/extensions/runner.ts +16 -1
  384. package/packages/pi-coding-agent/src/core/model-registry.ts +4 -0
  385. package/packages/pi-coding-agent/src/core/settings-manager.ts +12 -0
  386. package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -0
  387. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.ts +7 -5
  388. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +100 -16
  389. package/packages/pi-coding-agent/src/modes/interactive/components/adaptive-layout.test.ts +59 -0
  390. package/packages/pi-coding-agent/src/modes/interactive/components/adaptive-layout.ts +160 -0
  391. package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +1 -0
  392. package/packages/pi-coding-agent/src/modes/interactive/components/chat-frame.ts +10 -9
  393. package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +15 -0
  394. package/packages/pi-coding-agent/src/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.ts +41 -9
  395. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +124 -18
  396. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.test.ts +43 -1
  397. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +75 -1
  398. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +1 -0
  399. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +1 -1
  400. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +75 -9
  401. package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.test.ts +95 -0
  402. package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +24 -1
  403. package/packages/pi-coding-agent/src/modes/interactive/theme/theme-schema.ts +13 -0
  404. package/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts +32 -2
  405. package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +36 -27
  406. package/packages/pi-coding-agent/src/modes/interactive/tui-mode.test.ts +65 -0
  407. package/packages/pi-coding-agent/src/modes/interactive/tui-mode.ts +29 -0
  408. package/packages/pi-coding-agent/src/modes/rpc/rpc-types.ts +3 -336
  409. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  410. package/packages/pi-tui/dist/__tests__/style.test.d.ts +2 -0
  411. package/packages/pi-tui/dist/__tests__/style.test.d.ts.map +1 -0
  412. package/packages/pi-tui/dist/__tests__/style.test.js +63 -0
  413. package/packages/pi-tui/dist/__tests__/style.test.js.map +1 -0
  414. package/packages/pi-tui/dist/__tests__/tui.test.js +24 -3
  415. package/packages/pi-tui/dist/__tests__/tui.test.js.map +1 -1
  416. package/packages/pi-tui/dist/index.d.ts +1 -0
  417. package/packages/pi-tui/dist/index.d.ts.map +1 -1
  418. package/packages/pi-tui/dist/index.js +2 -0
  419. package/packages/pi-tui/dist/index.js.map +1 -1
  420. package/packages/pi-tui/dist/style.d.ts +41 -0
  421. package/packages/pi-tui/dist/style.d.ts.map +1 -0
  422. package/packages/pi-tui/dist/style.js +158 -0
  423. package/packages/pi-tui/dist/style.js.map +1 -0
  424. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  425. package/packages/pi-tui/dist/tui.js +1 -0
  426. package/packages/pi-tui/dist/tui.js.map +1 -1
  427. package/packages/pi-tui/package.json +1 -1
  428. package/packages/pi-tui/src/__tests__/style.test.ts +76 -0
  429. package/packages/pi-tui/src/__tests__/tui.test.ts +29 -3
  430. package/packages/pi-tui/src/index.ts +9 -0
  431. package/packages/pi-tui/src/style.ts +225 -0
  432. package/packages/pi-tui/src/tui.ts +1 -0
  433. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  434. package/packages/rpc-client/README.md +3 -3
  435. package/packages/rpc-client/dist/index.d.ts +1 -1
  436. package/packages/rpc-client/dist/index.d.ts.map +1 -1
  437. package/packages/rpc-client/dist/index.js.map +1 -1
  438. package/packages/rpc-client/dist/rpc-client.d.ts +2 -6
  439. package/packages/rpc-client/dist/rpc-client.d.ts.map +1 -1
  440. package/packages/rpc-client/dist/rpc-client.js.map +1 -1
  441. package/packages/rpc-client/dist/rpc-types.d.ts +1 -565
  442. package/packages/rpc-client/dist/rpc-types.d.ts.map +1 -1
  443. package/packages/rpc-client/dist/rpc-types.js +3 -11
  444. package/packages/rpc-client/dist/rpc-types.js.map +1 -1
  445. package/packages/rpc-client/package.json +4 -1
  446. package/packages/rpc-client/src/index.ts +1 -1
  447. package/packages/rpc-client/src/rpc-client.ts +3 -6
  448. package/packages/rpc-client/src/rpc-types.ts +3 -398
  449. package/packages/rpc-client/tsconfig.tsbuildinfo +1 -1
  450. package/pkg/dist/modes/interactive/theme/theme-schema.d.ts +12 -0
  451. package/pkg/dist/modes/interactive/theme/theme-schema.d.ts.map +1 -1
  452. package/pkg/dist/modes/interactive/theme/theme-schema.js +13 -0
  453. package/pkg/dist/modes/interactive/theme/theme-schema.js.map +1 -1
  454. package/pkg/dist/modes/interactive/theme/theme.d.ts +1 -1
  455. package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  456. package/pkg/dist/modes/interactive/theme/theme.js +18 -1
  457. package/pkg/dist/modes/interactive/theme/theme.js.map +1 -1
  458. package/pkg/dist/modes/interactive/theme/themes.d.ts.map +1 -1
  459. package/pkg/dist/modes/interactive/theme/themes.js +36 -27
  460. package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
  461. package/pkg/package.json +1 -1
  462. package/src/resources/extensions/github-sync/templates.ts +93 -88
  463. package/src/resources/extensions/github-sync/tests/inline-code.test.ts +66 -0
  464. package/src/resources/extensions/github-sync/tests/templates.test.ts +10 -2
  465. package/src/resources/extensions/gsd/auto/contracts.ts +87 -0
  466. package/src/resources/extensions/gsd/auto/custom-verify-retry-store.ts +72 -0
  467. package/src/resources/extensions/gsd/auto/loop-deps.ts +10 -3
  468. package/src/resources/extensions/gsd/auto/loop.ts +416 -596
  469. package/src/resources/extensions/gsd/auto/orchestrator.ts +161 -0
  470. package/src/resources/extensions/gsd/auto/phases.ts +82 -8
  471. package/src/resources/extensions/gsd/auto/run-unit.ts +24 -14
  472. package/src/resources/extensions/gsd/auto/session.ts +11 -0
  473. package/src/resources/extensions/gsd/auto/workflow-custom-engine-dispatch-outcome.ts +28 -0
  474. package/src/resources/extensions/gsd/auto/workflow-custom-engine-iteration.ts +52 -0
  475. package/src/resources/extensions/gsd/auto/workflow-custom-engine-reconcile-outcome.ts +58 -0
  476. package/src/resources/extensions/gsd/auto/workflow-custom-engine-reconcile.ts +71 -0
  477. package/src/resources/extensions/gsd/auto/workflow-custom-engine-retry.ts +90 -0
  478. package/src/resources/extensions/gsd/auto/workflow-custom-engine-verify-outcome.ts +50 -0
  479. package/src/resources/extensions/gsd/auto/workflow-dispatch-claim.ts +97 -0
  480. package/src/resources/extensions/gsd/auto/workflow-dispatch-ledger.ts +45 -0
  481. package/src/resources/extensions/gsd/auto/workflow-iteration-completion.ts +26 -0
  482. package/src/resources/extensions/gsd/auto/workflow-journal-reporter.ts +33 -0
  483. package/src/resources/extensions/gsd/auto/workflow-kernel.ts +520 -0
  484. package/src/resources/extensions/gsd/auto/workflow-memory-pressure.ts +58 -0
  485. package/src/resources/extensions/gsd/auto/workflow-phase-reporter.ts +22 -0
  486. package/src/resources/extensions/gsd/auto/workflow-session-lock.ts +68 -0
  487. package/src/resources/extensions/gsd/auto/workflow-sidecar-iteration.ts +46 -0
  488. package/src/resources/extensions/gsd/auto/workflow-sidecar-queue.ts +46 -0
  489. package/src/resources/extensions/gsd/auto/workflow-turn-reporter.ts +68 -0
  490. package/src/resources/extensions/gsd/auto/workflow-unit-dispatch.ts +89 -0
  491. package/src/resources/extensions/gsd/auto/workflow-worker-heartbeat.ts +38 -0
  492. package/src/resources/extensions/gsd/auto-dashboard.ts +61 -8
  493. package/src/resources/extensions/gsd/auto-dispatch.ts +17 -0
  494. package/src/resources/extensions/gsd/auto-prompts.ts +170 -3
  495. package/src/resources/extensions/gsd/auto-recovery.ts +194 -56
  496. package/src/resources/extensions/gsd/auto-runtime-state.ts +7 -0
  497. package/src/resources/extensions/gsd/auto-start.ts +7 -6
  498. package/src/resources/extensions/gsd/auto-verification.ts +5 -1
  499. package/src/resources/extensions/gsd/auto-worktree.ts +85 -36
  500. package/src/resources/extensions/gsd/auto.ts +179 -2
  501. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +30 -2
  502. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +11 -0
  503. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +30 -6
  504. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +135 -1
  505. package/src/resources/extensions/gsd/commands-extract-learnings.ts +17 -12
  506. package/src/resources/extensions/gsd/commands-ship.ts +24 -51
  507. package/src/resources/extensions/gsd/commands-workflow-templates.ts +13 -0
  508. package/src/resources/extensions/gsd/component-loader.ts +5 -11
  509. package/src/resources/extensions/gsd/custom-workflow-engine.ts +29 -0
  510. package/src/resources/extensions/gsd/db-adapter.ts +75 -0
  511. package/src/resources/extensions/gsd/db-base-schema.ts +383 -0
  512. package/src/resources/extensions/gsd/db-connection-cache.ts +45 -0
  513. package/src/resources/extensions/gsd/db-coordination-schema.ts +109 -0
  514. package/src/resources/extensions/gsd/db-decision-requirement-rows.ts +77 -0
  515. package/src/resources/extensions/gsd/db-gate-rows.ts +19 -0
  516. package/src/resources/extensions/gsd/db-lightweight-query-rows.ts +50 -0
  517. package/src/resources/extensions/gsd/db-memory-fts-schema.ts +66 -0
  518. package/src/resources/extensions/gsd/db-migration-backup.ts +34 -0
  519. package/src/resources/extensions/gsd/db-migration-steps.ts +451 -0
  520. package/src/resources/extensions/gsd/db-milestone-artifact-rows.ts +70 -0
  521. package/src/resources/extensions/gsd/db-open-state.ts +47 -0
  522. package/src/resources/extensions/gsd/db-provider.ts +148 -0
  523. package/src/resources/extensions/gsd/db-runtime-kv-schema.ts +30 -0
  524. package/src/resources/extensions/gsd/db-schema-metadata.ts +33 -0
  525. package/src/resources/extensions/gsd/db-task-slice-rows.ts +146 -0
  526. package/src/resources/extensions/gsd/db-transaction.ts +76 -0
  527. package/src/resources/extensions/gsd/db-verification-evidence-rows.ts +14 -0
  528. package/src/resources/extensions/gsd/db-verification-evidence-schema.ts +22 -0
  529. package/src/resources/extensions/gsd/escalation.ts +3 -1
  530. package/src/resources/extensions/gsd/graph.ts +12 -5
  531. package/src/resources/extensions/gsd/gsd-db.ts +379 -1660
  532. package/src/resources/extensions/gsd/guided-flow.ts +2 -2
  533. package/src/resources/extensions/gsd/interrupted-session.ts +1 -0
  534. package/src/resources/extensions/gsd/legacy-telemetry.ts +99 -0
  535. package/src/resources/extensions/gsd/markdown-renderer.ts +4 -1
  536. package/src/resources/extensions/gsd/model-router.ts +10 -6
  537. package/src/resources/extensions/gsd/notification-widget.ts +25 -4
  538. package/src/resources/extensions/gsd/post-execution-checks.ts +35 -7
  539. package/src/resources/extensions/gsd/pr-evidence.ts +182 -0
  540. package/src/resources/extensions/gsd/pre-execution-checks.ts +4 -1
  541. package/src/resources/extensions/gsd/process-task-path.ts +81 -0
  542. package/src/resources/extensions/gsd/prompt-loader.ts +9 -5
  543. package/src/resources/extensions/gsd/prompts/complete-milestone.md +32 -30
  544. package/src/resources/extensions/gsd/prompts/complete-slice.md +23 -34
  545. package/src/resources/extensions/gsd/prompts/discuss-headless.md +50 -96
  546. package/src/resources/extensions/gsd/prompts/discuss.md +81 -181
  547. package/src/resources/extensions/gsd/prompts/execute-task.md +40 -67
  548. package/src/resources/extensions/gsd/prompts/forensics.md +41 -84
  549. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +29 -39
  550. package/src/resources/extensions/gsd/prompts/guided-discuss-project.md +30 -65
  551. package/src/resources/extensions/gsd/prompts/guided-discuss-requirements.md +25 -52
  552. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +36 -36
  553. package/src/resources/extensions/gsd/prompts/guided-research-project.md +20 -38
  554. package/src/resources/extensions/gsd/prompts/plan-milestone.md +45 -59
  555. package/src/resources/extensions/gsd/prompts/plan-slice.md +25 -87
  556. package/src/resources/extensions/gsd/prompts/queue.md +46 -53
  557. package/src/resources/extensions/gsd/prompts/refine-slice.md +23 -23
  558. package/src/resources/extensions/gsd/prompts/research-slice.md +23 -23
  559. package/src/resources/extensions/gsd/prompts/rethink.md +10 -10
  560. package/src/resources/extensions/gsd/prompts/system.md +65 -107
  561. package/src/resources/extensions/gsd/prompts/triage-captures.md +15 -15
  562. package/src/resources/extensions/gsd/prompts/validate-milestone.md +24 -24
  563. package/src/resources/extensions/gsd/prompts/worktree-merge.md +35 -35
  564. package/src/resources/extensions/gsd/state.ts +6 -3
  565. package/src/resources/extensions/gsd/tests/auto-abort-pause-regression.test.ts +38 -0
  566. package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +98 -0
  567. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +117 -0
  568. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +353 -0
  569. package/src/resources/extensions/gsd/tests/auto-pr-bugs.test.ts +19 -0
  570. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +170 -1
  571. package/src/resources/extensions/gsd/tests/auto-runtime-state.test.ts +39 -0
  572. package/src/resources/extensions/gsd/tests/auto-session-encapsulation.test.ts +3 -0
  573. package/src/resources/extensions/gsd/tests/commands-eval-review.test.ts +2 -2
  574. package/src/resources/extensions/gsd/tests/commands-extract-learnings.test.ts +9 -0
  575. package/src/resources/extensions/gsd/tests/commands-ship-eval-warn.test.ts +3 -2
  576. package/src/resources/extensions/gsd/tests/complete-milestone-prompt-rendering.test.ts +47 -0
  577. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +19 -5
  578. package/src/resources/extensions/gsd/tests/component-loader.test.ts +2 -9
  579. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +144 -0
  580. package/src/resources/extensions/gsd/tests/custom-verify-retry-store.test.ts +139 -0
  581. package/src/resources/extensions/gsd/tests/custom-workflow-engine.test.ts +50 -0
  582. package/src/resources/extensions/gsd/tests/dashboard-custom-engine.test.ts +3 -3
  583. package/src/resources/extensions/gsd/tests/db-adapter.test.ts +82 -0
  584. package/src/resources/extensions/gsd/tests/db-base-schema.test.ts +62 -0
  585. package/src/resources/extensions/gsd/tests/db-connection-cache.test.ts +60 -0
  586. package/src/resources/extensions/gsd/tests/db-coordination-schema.test.ts +39 -0
  587. package/src/resources/extensions/gsd/tests/db-decision-requirement-rows.test.ts +135 -0
  588. package/src/resources/extensions/gsd/tests/db-gate-rows.test.ts +53 -0
  589. package/src/resources/extensions/gsd/tests/db-lightweight-query-rows.test.ts +45 -0
  590. package/src/resources/extensions/gsd/tests/db-memory-fts-schema.test.ts +86 -0
  591. package/src/resources/extensions/gsd/tests/db-migration-backup.test.ts +105 -0
  592. package/src/resources/extensions/gsd/tests/db-migration-steps.integration.test.ts +428 -0
  593. package/src/resources/extensions/gsd/tests/db-migration-steps.test.ts +159 -0
  594. package/src/resources/extensions/gsd/tests/db-milestone-artifact-rows.test.ts +53 -0
  595. package/src/resources/extensions/gsd/tests/db-open-state.test.ts +56 -0
  596. package/src/resources/extensions/gsd/tests/db-provider.test.ts +105 -0
  597. package/src/resources/extensions/gsd/tests/db-runtime-kv-schema.test.ts +37 -0
  598. package/src/resources/extensions/gsd/tests/db-schema-metadata.test.ts +115 -0
  599. package/src/resources/extensions/gsd/tests/db-task-slice-rows.test.ts +128 -0
  600. package/src/resources/extensions/gsd/tests/db-transaction.test.ts +110 -0
  601. package/src/resources/extensions/gsd/tests/db-verification-evidence-schema.test.ts +76 -0
  602. package/src/resources/extensions/gsd/tests/discuss-headless-rendering.test.ts +37 -0
  603. package/src/resources/extensions/gsd/tests/execute-task-rendering.test.ts +59 -0
  604. package/src/resources/extensions/gsd/tests/fixtures/pr-body/commands-ship-basic.md +52 -0
  605. package/src/resources/extensions/gsd/tests/fixtures/pr-body/commands-ship-empty-optionals.md +42 -0
  606. package/src/resources/extensions/gsd/tests/fixtures/pr-body/swarm-lane-no-blockers.md +55 -0
  607. package/src/resources/extensions/gsd/tests/fixtures/pr-body/swarm-lane-with-blockers.md +60 -0
  608. package/src/resources/extensions/gsd/tests/forensics-prompt-rendering.test.ts +36 -0
  609. package/src/resources/extensions/gsd/tests/graph-operations.test.ts +10 -0
  610. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +139 -0
  611. package/src/resources/extensions/gsd/tests/guided-discuss-milestone-prompt-rendering.test.ts +43 -0
  612. package/src/resources/extensions/gsd/tests/guided-discuss-project-prompt-rendering.test.ts +41 -0
  613. package/src/resources/extensions/gsd/tests/guided-discuss-requirements-prompt-rendering.test.ts +45 -0
  614. package/src/resources/extensions/gsd/tests/has-pending-deep-stage.test.ts +33 -1
  615. package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +79 -0
  616. package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +37 -0
  617. package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts +5 -3
  618. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +134 -0
  619. package/src/resources/extensions/gsd/tests/legacy-component-format-telemetry.test.ts +62 -0
  620. package/src/resources/extensions/gsd/tests/legacy-telemetry.test.ts +144 -0
  621. package/src/resources/extensions/gsd/tests/memory-pressure-stuck-state.test.ts +40 -16
  622. package/src/resources/extensions/gsd/tests/model-router.test.ts +33 -12
  623. package/src/resources/extensions/gsd/tests/notification-store.test.ts +8 -0
  624. package/src/resources/extensions/gsd/tests/notification-widget.test.ts +40 -1
  625. package/src/resources/extensions/gsd/tests/paused-session-via-db.test.ts +2 -0
  626. package/src/resources/extensions/gsd/tests/plan-milestone-rendering.test.ts +45 -0
  627. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +65 -16
  628. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +27 -0
  629. package/src/resources/extensions/gsd/tests/post-execution-checks.test.ts +46 -0
  630. package/src/resources/extensions/gsd/tests/pr-evidence-equivalence.test.ts +102 -0
  631. package/src/resources/extensions/gsd/tests/pr-evidence-hardening.test.ts +165 -0
  632. package/src/resources/extensions/gsd/tests/pr-evidence.test.ts +79 -0
  633. package/src/resources/extensions/gsd/tests/process-task-path.test.ts +51 -0
  634. package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +16 -1
  635. package/src/resources/extensions/gsd/tests/queue-prompt-rendering.test.ts +37 -0
  636. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +46 -2
  637. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +32 -9
  638. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +6 -6
  639. package/src/resources/extensions/gsd/tests/uok-kernel-path.test.ts +12 -0
  640. package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +1 -1
  641. package/src/resources/extensions/gsd/tests/workflow-custom-engine-dispatch-outcome.test.ts +55 -0
  642. package/src/resources/extensions/gsd/tests/workflow-custom-engine-iteration.test.ts +93 -0
  643. package/src/resources/extensions/gsd/tests/workflow-custom-engine-reconcile-outcome.test.ts +108 -0
  644. package/src/resources/extensions/gsd/tests/workflow-custom-engine-reconcile.test.ts +146 -0
  645. package/src/resources/extensions/gsd/tests/workflow-custom-engine-retry.test.ts +136 -0
  646. package/src/resources/extensions/gsd/tests/workflow-custom-engine-verify-outcome.test.ts +95 -0
  647. package/src/resources/extensions/gsd/tests/workflow-dispatch-claim.test.ts +158 -0
  648. package/src/resources/extensions/gsd/tests/workflow-dispatch-ledger.test.ts +82 -0
  649. package/src/resources/extensions/gsd/tests/workflow-iteration-completion.test.ts +44 -0
  650. package/src/resources/extensions/gsd/tests/workflow-journal-reporter.test.ts +49 -0
  651. package/src/resources/extensions/gsd/tests/workflow-kernel.test.ts +607 -0
  652. package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +20 -4
  653. package/src/resources/extensions/gsd/tests/workflow-memory-pressure.test.ts +71 -0
  654. package/src/resources/extensions/gsd/tests/workflow-phase-reporter.test.ts +40 -0
  655. package/src/resources/extensions/gsd/tests/workflow-session-lock.test.ts +135 -0
  656. package/src/resources/extensions/gsd/tests/workflow-sidecar-iteration.test.ts +110 -0
  657. package/src/resources/extensions/gsd/tests/workflow-sidecar-queue.test.ts +116 -0
  658. package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +21 -0
  659. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +32 -0
  660. package/src/resources/extensions/gsd/tests/workflow-turn-reporter.test.ts +87 -0
  661. package/src/resources/extensions/gsd/tests/workflow-unit-dispatch.test.ts +160 -0
  662. package/src/resources/extensions/gsd/tests/workflow-worker-heartbeat.test.ts +123 -0
  663. package/src/resources/extensions/gsd/tests/working-output-messages.test.ts +93 -0
  664. package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +17 -33
  665. package/src/resources/extensions/gsd/tests/worktree-write-gate.test.ts +179 -0
  666. package/src/resources/extensions/gsd/tools/complete-milestone.ts +15 -9
  667. package/src/resources/extensions/gsd/tools/complete-task.ts +4 -1
  668. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +6 -1
  669. package/src/resources/extensions/gsd/unit-context-composer.ts +1 -1
  670. package/src/resources/extensions/gsd/uok/kernel.ts +10 -3
  671. package/src/resources/extensions/gsd/uok/plan-v2.ts +5 -1
  672. package/src/resources/extensions/gsd/workflow-logger.ts +13 -13
  673. package/src/resources/extensions/gsd/workflow-manifest.ts +6 -15
  674. package/src/resources/extensions/gsd/workflow-projections.ts +5 -1
  675. package/src/resources/extensions/gsd/workflow-templates.ts +11 -0
  676. package/src/resources/extensions/gsd/working-output-messages.ts +120 -0
  677. package/src/resources/extensions/shared/interview-ui.ts +18 -5
  678. package/src/resources/extensions/shared/tests/interview-notes-loop.test.ts +41 -0
  679. package/dist/web/standalone/.next/server/chunks/6336.js +0 -1
  680. /package/dist/web/standalone/.next/static/{TzEVJ1Lh8vbez4n4Q9TqQ → vIAZSyxIuvqNkCvXt9oqb}/_buildManifest.js +0 -0
  681. /package/dist/web/standalone/.next/static/{TzEVJ1Lh8vbez4n4Q9TqQ → vIAZSyxIuvqNkCvXt9oqb}/_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,30 +314,45 @@ 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
@@ -476,47 +360,44 @@ export async function autoLoop(
476
360
  const uokFlags = resolveUokFlags(prefs);
477
361
 
478
362
  // ── 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
- }
363
+ // NOTE: Sidecar dequeue MUST run before validateWorkflowSessionLock so a
364
+ // queued item is popped (and the `sidecar-dequeue` journal event emitted)
365
+ // even when the session lock invalidates this iteration. Inverting this
366
+ // order silently drops queued items on lock-loss. Refs #5308.
367
+ const sidecarItem = await dequeueSidecarItem({
368
+ queue: s.sidecarQueue,
369
+ executionGraphEnabled: uokFlags.executionGraph,
370
+ scheduleQueue: scheduleSidecarQueue,
371
+ warnSchedulingFailure: message => logWarning("dispatch", `sidecar queue scheduling failed: ${message}`),
372
+ logDequeue: payload => debugLog("autoLoop", { phase: "sidecar-dequeue", ...payload }),
373
+ emitDequeue: payload => journalReporter.emit("sidecar-dequeue", payload),
374
+ });
497
375
 
498
- const sessionLockBase = deps.lockBase();
499
- if (sessionLockBase) {
500
- const lockStatus = deps.validateSessionLock(sessionLockBase);
501
- if (!lockStatus.valid) {
502
- debugLog("autoLoop", {
376
+ const sessionLockOutcome = validateWorkflowSessionLock({
377
+ active: s.active,
378
+ iteration,
379
+ maxIterations: MAX_LOOP_ITERATIONS,
380
+ deps: {
381
+ lockBase: deps.lockBase,
382
+ validateSessionLock: deps.validateSessionLock,
383
+ handleLostSessionLock: lockStatus => deps.handleLostSessionLock(ctx, lockStatus),
384
+ logInvalidSessionLock: details => debugLog("autoLoop", {
503
385
  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", {
386
+ ...details,
387
+ }),
388
+ logSessionLockExit: details => debugLog("autoLoop", {
510
389
  phase: "exit",
511
- reason: "session-lock-lost",
512
- detail: lockStatus.failureReason ?? "unknown",
513
- });
514
- break;
515
- }
390
+ ...details,
391
+ }),
392
+ },
393
+ });
394
+ if (sessionLockOutcome.action === "stop" && sessionLockOutcome.reason === "session-lock-lost") {
395
+ finishTurn("stopped", "manual-attention", sessionLockOutcome.reason);
396
+ break;
516
397
  }
517
398
 
518
399
  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 } });
400
+ journalReporter.emit("iteration-start", { iteration });
520
401
  let iterData: IterationData;
521
402
 
522
403
  // ── Custom engine path ──────────────────────────────────────────────
@@ -527,7 +408,11 @@ export async function autoLoop(
527
408
  //
528
409
  // GSD_ENGINE_BYPASS=1 skips the engine layer entirely — falls through
529
410
  // to the dev path below.
530
- if (s.activeEngineId != null && s.activeEngineId !== "dev" && !sidecarItem && process.env.GSD_ENGINE_BYPASS !== "1") {
411
+ if (shouldUseCustomEnginePath({
412
+ activeEngineId: s.activeEngineId,
413
+ hasSidecarItem: Boolean(sidecarItem),
414
+ engineBypass: process.env.GSD_ENGINE_BYPASS === "1",
415
+ })) {
531
416
  debugLog("autoLoop", { phase: "custom-engine-derive", iteration, engineId: s.activeEngineId });
532
417
 
533
418
  const { engine, policy } = resolveEngine({
@@ -547,45 +432,48 @@ export async function autoLoop(
547
432
  isComplete: engineState.isComplete,
548
433
  });
549
434
  if (engineState.isComplete) {
435
+ finishTurn("completed");
550
436
  await deps.stopAuto(ctx, pi, "Workflow complete");
551
437
  break;
552
438
  }
553
439
 
554
440
  debugLog("autoLoop", { phase: "custom-engine-dispatch", iteration });
555
441
  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");
442
+ const engineDispatchDecision = decideEngineDispatch(dispatch.action === "stop"
443
+ ? { action: "stop", reason: dispatch.reason }
444
+ : { action: dispatch.action });
445
+ const dispatchFlow = await handleCustomEngineDispatchOutcome({
446
+ decision: engineDispatchDecision,
447
+ deps: {
448
+ stopAuto: reason => deps.stopAuto(ctx, pi, reason),
449
+ },
450
+ });
451
+ if (dispatchFlow.action === "break") {
452
+ finishTurn("stopped", "manual-attention", "custom-engine-dispatch-stop");
559
453
  break;
560
454
  }
561
- if (dispatch.action === "skip") {
455
+ if (dispatchFlow.action === "continue") {
456
+ finishTurn("skipped");
562
457
  continue;
563
458
  }
564
459
 
565
460
  // 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",
461
+ if (dispatch.action !== "dispatch") {
462
+ finishTurn("skipped");
463
+ continue;
464
+ }
465
+ const step = dispatch.step;
466
+ iterData = await buildCustomEngineIterationData({
467
+ step,
571
468
  basePath: s.basePath,
572
469
  canonicalProjectRoot: s.canonicalProjectRoot,
573
- derivedPhase: gsdState.phase,
574
- activeUnit: gsdState.activeTask?.id ?? gsdState.activeSlice?.id ?? gsdState.activeMilestone?.id,
470
+ currentMilestoneId: s.currentMilestoneId,
471
+ deriveState: deps.deriveState,
472
+ logPostDerive: details => debugLog("autoLoop", {
473
+ phase: "post-derive",
474
+ ...details,
475
+ }),
575
476
  });
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
477
  observedUnitType = iterData.unitType;
590
478
  observedUnitId = iterData.unitId;
591
479
 
@@ -594,7 +482,7 @@ export async function autoLoop(
594
482
 
595
483
  // ── Guards (shared with dev path) ──
596
484
  const guardsResult = await runGuards(ic, s.currentMilestoneId ?? "workflow");
597
- deps.uokObserver?.onPhaseResult("guard", guardsResult.action, {
485
+ phaseReporter.report("guard", guardsResult.action, {
598
486
  unitType: iterData.unitType,
599
487
  unitId: iterData.unitId,
600
488
  });
@@ -610,12 +498,14 @@ export async function autoLoop(
610
498
  ic,
611
499
  iterData,
612
500
  loopState,
501
+ undefined,
502
+ unitDispatchDeps,
613
503
  );
614
504
  if (unitPhaseResult.action === "next") {
615
- const requestTimestamp = unitPhaseResult.data.requestDispatchedAt ?? unitPhaseResult.data.unitStartedAt;
616
- if (typeof requestTimestamp === "number") s.lastRequestTimestamp = requestTimestamp;
505
+ const requestTimestamp = resolveUnitRequestTimestamp(unitPhaseResult.data);
506
+ if (requestTimestamp !== undefined) s.lastRequestTimestamp = requestTimestamp;
617
507
  }
618
- deps.uokObserver?.onPhaseResult("unit", unitPhaseResult.action, {
508
+ phaseReporter.report("unit", unitPhaseResult.action, {
619
509
  unitType: iterData.unitType,
620
510
  unitId: iterData.unitId,
621
511
  });
@@ -628,116 +518,93 @@ export async function autoLoop(
628
518
  debugLog("autoLoop", { phase: "custom-engine-verify", iteration, unitId: iterData.unitId });
629
519
  const verifyResult = await policy.verify(iterData.unitType, iterData.unitId, { basePath: s.basePath });
630
520
  if (verifyResult === "pause") {
631
- await deps.pauseAuto(ctx, pi);
632
- deps.uokObserver?.onPhaseResult("custom-engine", "pause", {
521
+ const verifyFlow = await handleCustomEngineVerifyPause({
633
522
  unitType: iterData.unitType,
634
523
  unitId: iterData.unitId,
524
+ deps: {
525
+ pauseAuto: () => deps.pauseAuto(ctx, pi),
526
+ stopAuto: reason => deps.stopAuto(ctx, pi, reason),
527
+ reportPause: details => phaseReporter.report("custom-engine", "pause", details),
528
+ finishTurn,
529
+ },
635
530
  });
636
- finishTurn("paused", "manual-attention", "custom-engine-verify-pause");
637
- break;
531
+ if (verifyFlow.action === "break") break;
638
532
  }
639
533
  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", {
534
+ const retryOutcome = await handleCustomEngineVerifyRetry({
535
+ session: s,
647
536
  unitType: iterData.unitType,
648
537
  unitId: iterData.unitId,
649
- attempts,
538
+ basePath: s.basePath,
539
+ iteration,
540
+ maxRetries: MAX_CUSTOM_ENGINE_VERIFY_RETRIES,
541
+ deps: {
542
+ hydrateRetryCounts: () => hydrateCustomVerifyRetryCounts(s, {
543
+ logFailure: logCustomVerifyRetryLoadFailure,
544
+ }),
545
+ saveRetryCounts: () => saveCustomVerifyRetryCounts(s, {
546
+ logFailure: logCustomVerifyRetrySaveFailure,
547
+ }),
548
+ recover: (unitType, unitId, options) => policy.recover(unitType, unitId, options),
549
+ logRetry: details => debugLog("autoLoop", {
550
+ phase: "custom-engine-verify-retry",
551
+ ...details,
552
+ }),
553
+ reportRetry: details => phaseReporter.report("custom-engine", "retry", details),
554
+ },
555
+ });
556
+ const retryFlow = await handleCustomEngineVerifyRetryOutcome({
557
+ outcome: retryOutcome,
558
+ deps: {
559
+ pauseAuto: () => deps.pauseAuto(ctx, pi),
560
+ stopAuto: reason => deps.stopAuto(ctx, pi, reason),
561
+ reportPause: details => phaseReporter.report("custom-engine", "pause", details),
562
+ finishTurn,
563
+ },
650
564
  });
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");
565
+ if (retryFlow.action === "break") break;
679
566
  continue;
680
567
  }
681
568
 
682
569
  // 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(),
570
+ const reconcileOutcome = await handleCustomEngineReconcile({
571
+ session: s,
572
+ engineState,
573
+ iterData,
574
+ iteration,
575
+ deps: {
576
+ saveRetryCounts: () => saveCustomVerifyRetryCounts(s, {
577
+ logFailure: logCustomVerifyRetrySaveFailure,
578
+ }),
579
+ logReconcile: details => debugLog("autoLoop", {
580
+ phase: "custom-engine-reconcile",
581
+ ...details,
582
+ }),
583
+ reconcile: (state, completedStep) => engine.reconcile(state, completedStep),
584
+ now: () => Date.now(),
585
+ clearUnitTimeout: deps.clearUnitTimeout,
586
+ completeIteration,
587
+ },
691
588
  });
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", {
589
+ const reconcileFlow = await handleCustomEngineReconcileOutcome({
590
+ outcome: reconcileOutcome,
730
591
  unitType: iterData.unitType,
731
592
  unitId: iterData.unitId,
593
+ deps: {
594
+ stopAuto: reason => deps.stopAuto(ctx, pi, reason),
595
+ pauseAuto: () => deps.pauseAuto(ctx, pi),
596
+ report: (action, details) => phaseReporter.report("custom-engine", action, details),
597
+ finishTurn,
598
+ },
732
599
  });
733
- finishTurn("completed");
600
+ if (reconcileFlow.action === "break") break;
734
601
  continue;
735
602
  }
736
603
 
737
604
  if (!sidecarItem) {
738
605
  // ── Phase 1: Pre-dispatch ─────────────────────────────────────────
739
606
  const preDispatchResult = await runPreDispatch(ic, loopState);
740
- deps.uokObserver?.onPhaseResult("pre-dispatch", preDispatchResult.action);
607
+ phaseReporter.report("pre-dispatch", preDispatchResult.action);
741
608
  if (preDispatchResult.action === "break") {
742
609
  finishTurn("stopped", "manual-attention", "pre-dispatch-break");
743
610
  break;
@@ -751,7 +618,7 @@ export async function autoLoop(
751
618
 
752
619
  // ── Phase 2: Guards ───────────────────────────────────────────────
753
620
  const guardsResult = await runGuards(ic, preData.mid);
754
- deps.uokObserver?.onPhaseResult("guard", guardsResult.action);
621
+ phaseReporter.report("guard", guardsResult.action);
755
622
  if (guardsResult.action === "break") {
756
623
  finishTurn("stopped", "manual-attention", "guard-break");
757
624
  break;
@@ -759,7 +626,7 @@ export async function autoLoop(
759
626
 
760
627
  // ── Phase 3: Dispatch ─────────────────────────────────────────────
761
628
  const dispatchResult = await runDispatch(ic, preData, loopState);
762
- deps.uokObserver?.onPhaseResult("dispatch", dispatchResult.action);
629
+ phaseReporter.report("dispatch", dispatchResult.action);
763
630
  if (dispatchResult.action === "break") {
764
631
  finishTurn("stopped", "manual-attention", "dispatch-break");
765
632
  break;
@@ -772,30 +639,19 @@ export async function autoLoop(
772
639
  observedUnitType = iterData.unitType;
773
640
  observedUnitId = iterData.unitId;
774
641
  } 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",
642
+ iterData = await buildSidecarIterationData({
643
+ sidecarItem,
780
644
  basePath: s.basePath,
781
645
  canonicalProjectRoot: s.canonicalProjectRoot,
782
- derivedPhase: sidecarState.phase,
783
- activeUnit: sidecarState.activeTask?.id ?? sidecarState.activeSlice?.id ?? sidecarState.activeMilestone?.id,
646
+ deriveState: deps.deriveState,
647
+ logPostDerive: details => debugLog("autoLoop", {
648
+ phase: "post-derive",
649
+ ...details,
650
+ }),
784
651
  });
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
652
  observedUnitType = iterData.unitType;
797
653
  observedUnitId = iterData.unitId;
798
- deps.uokObserver?.onPhaseResult("dispatch", "sidecar", {
654
+ phaseReporter.report("dispatch", "sidecar", {
799
655
  unitType: iterData.unitType,
800
656
  unitId: iterData.unitId,
801
657
  sidecarKind: sidecarItem.kind,
@@ -810,12 +666,25 @@ export async function autoLoop(
810
666
  // null when DB unavailable, no worker registered, or no active lease
811
667
  // — those degraded paths fall through to the existing single-worker
812
668
  // 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);
669
+ const dispatchClaim = openDispatchClaim(s, flowId, turnId, iterData, {
670
+ getRecentDispatchesForUnit,
671
+ recordDispatchClaim,
672
+ markDispatchRunning,
673
+ logClaimRejected: logDispatchClaimRejected,
674
+ logClaimFailed: logDispatchClaimFailed,
675
+ });
676
+ const dispatchDecision = decideDispatchClaim(
677
+ dispatchClaim.kind === "opened"
678
+ ? { kind: "opened", dispatchId: dispatchClaim.dispatchId }
679
+ : dispatchClaim.kind === "skip"
680
+ ? { kind: "skip", reason: dispatchClaim.reason }
681
+ : { kind: "degraded" },
682
+ );
683
+ if (dispatchDecision.action === "skip") {
684
+ finishTurn("skipped", "execution", dispatchDecision.reason);
816
685
  continue;
817
686
  }
818
- dispatchId = dispatchClaim.kind === "opened" ? dispatchClaim.dispatchId : null;
687
+ dispatchId = dispatchDecision.dispatchId;
819
688
 
820
689
  let unitPhaseResult: Awaited<ReturnType<typeof runUnitPhaseViaContract>>;
821
690
  try {
@@ -825,40 +694,35 @@ export async function autoLoop(
825
694
  iterData,
826
695
  loopState,
827
696
  sidecarItem,
697
+ unitDispatchDeps,
828
698
  );
829
699
  } catch (err) {
830
700
  if (err instanceof ModelPolicyDispatchBlockedError) {
831
701
  throw err;
832
702
  }
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
- }
703
+ dispatchSettled = settleDispatchFailed(
704
+ dispatchId,
705
+ formatDispatchExceptionSummary({ error: err }),
706
+ {
707
+ markFailed: markDispatchFailed,
708
+ logWriteFailure: logDispatchLedgerWriteFailure,
709
+ },
710
+ ) || dispatchSettled;
843
711
  throw err;
844
712
  }
845
713
  if (unitPhaseResult.action === "next") {
846
- const requestTimestamp = unitPhaseResult.data.requestDispatchedAt ?? unitPhaseResult.data.unitStartedAt;
847
- if (typeof requestTimestamp === "number") s.lastRequestTimestamp = requestTimestamp;
714
+ const requestTimestamp = resolveUnitRequestTimestamp(unitPhaseResult.data);
715
+ if (requestTimestamp !== undefined) s.lastRequestTimestamp = requestTimestamp;
848
716
  }
849
- deps.uokObserver?.onPhaseResult("unit", unitPhaseResult.action, {
717
+ phaseReporter.report("unit", unitPhaseResult.action, {
850
718
  unitType: iterData.unitType,
851
719
  unitId: iterData.unitId,
852
720
  });
853
721
  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
- }
722
+ dispatchSettled = settleDispatchFailed(dispatchId, "unit-break", {
723
+ markFailed: markDispatchFailed,
724
+ logWriteFailure: logDispatchLedgerWriteFailure,
725
+ }) || dispatchSettled;
862
726
  finishTurn("stopped", "execution", "unit-break");
863
727
  break;
864
728
  }
@@ -869,84 +733,68 @@ export async function autoLoop(
869
733
  try {
870
734
  finalizeResult = await runFinalize(ic, iterData, loopState, sidecarItem);
871
735
  } 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
- }
736
+ dispatchSettled = settleDispatchFailed(
737
+ dispatchId,
738
+ formatDispatchExceptionSummary({ error: err }),
739
+ {
740
+ markFailed: markDispatchFailed,
741
+ logWriteFailure: logDispatchLedgerWriteFailure,
742
+ },
743
+ ) || dispatchSettled;
882
744
  throw err;
883
745
  }
884
- deps.uokObserver?.onPhaseResult("finalize", finalizeResult.action, {
746
+ phaseReporter.report("finalize", finalizeResult.action, {
885
747
  unitType: iterData.unitType,
886
748
  unitId: iterData.unitId,
887
749
  });
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");
750
+ const finalizeDecision = decideFinalizeResult(
751
+ finalizeResult.action === "break"
752
+ ? { action: "break", reason: finalizeResult.reason }
753
+ : finalizeResult.action === "continue"
754
+ ? { action: "continue" }
755
+ : { action: "next" },
756
+ );
757
+ if (finalizeDecision.action === "stop") {
758
+ dispatchSettled = settleDispatchFailed(dispatchId, finalizeDecision.ledgerErrorSummary, {
759
+ markFailed: markDispatchFailed,
760
+ logWriteFailure: logDispatchLedgerWriteFailure,
761
+ }) || dispatchSettled;
762
+ finishTurn("stopped", finalizeDecision.failureClass, finalizeDecision.turnError);
901
763
  break;
902
764
  }
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
- }
765
+ if (finalizeDecision.action === "retry") {
766
+ dispatchSettled = settleDispatchFailed(dispatchId, finalizeDecision.ledgerErrorSummary, {
767
+ markFailed: markDispatchFailed,
768
+ logWriteFailure: logDispatchLedgerWriteFailure,
769
+ }) || dispatchSettled;
912
770
  finishTurn("retry");
913
771
  continue;
914
772
  }
915
773
 
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 });
774
+ dispatchSettled = settleDispatchCompleted(dispatchId, {
775
+ markCompleted: markDispatchCompleted,
776
+ logWriteFailure: logDispatchLedgerWriteFailure,
777
+ }) || dispatchSettled;
778
+ completeIteration();
930
779
  finishTurn("completed");
931
780
  } catch (loopErr) {
932
781
  // ── Blanket catch: absorb unexpected exceptions, apply graduated recovery ──
933
782
  const msg = loopErr instanceof Error ? loopErr.message : String(loopErr);
934
783
  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
- }
784
+ dispatchSettled = settleDispatchFailed(
785
+ dispatchId,
786
+ formatUnhandledDispatchErrorSummary({ error: loopErr }),
787
+ {
788
+ markFailed: markDispatchFailed,
789
+ logWriteFailure: logDispatchLedgerWriteFailure,
790
+ },
791
+ ) || dispatchSettled;
944
792
  }
945
793
 
946
794
  // Always emit iteration-end on error so the journal records iteration
947
795
  // completion even on failure (#2344). Without this, errors in
948
796
  // 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 } });
797
+ journalReporter.emit("iteration-end", { iteration, error: msg });
950
798
 
951
799
  // ── Pre-send model-policy block: not a retryable error (#4959 / #4850) ──
952
800
  // The model-policy gate runs before the prompt is sent. When every
@@ -957,6 +805,12 @@ export async function autoLoop(
957
805
  // instead, with the per-model deny reasons surfaced from the typed
958
806
  // error.
959
807
  if (loopErr instanceof ModelPolicyDispatchBlockedError) {
808
+ const policyDecision = decideModelPolicyBlocked({
809
+ unitType: loopErr.unitType,
810
+ unitId: loopErr.unitId,
811
+ errorMessage: msg,
812
+ reasons: loopErr.reasons,
813
+ });
960
814
  debugLog("autoLoop", {
961
815
  phase: "model-policy-blocked",
962
816
  iteration,
@@ -964,23 +818,8 @@ export async function autoLoop(
964
818
  unitId: loopErr.unitId,
965
819
  reasons: loopErr.reasons,
966
820
  });
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
- });
821
+ ctx.ui.notify(policyDecision.notifyMessage, "error");
822
+ journalReporter.emit("unit-end", policyDecision.journalData);
984
823
  // Carry the blocked unit identity into the turn-result observer:
985
824
  // the throw originated inside dispatch, so observedUnitType/Id were
986
825
  // not assigned by the success path at lines 453/631/647 — but the
@@ -988,7 +827,7 @@ export async function autoLoop(
988
827
  observedUnitType = loopErr.unitType;
989
828
  observedUnitId = loopErr.unitId;
990
829
  await deps.pauseAuto(ctx, pi);
991
- finishTurn("paused", "manual-attention", msg);
830
+ finishTurn(policyDecision.turnStatus, policyDecision.failureClass, msg);
992
831
  // Do NOT increment consecutiveErrors — the failure is configuration,
993
832
  // not a transient runtime fault.
994
833
  break;
@@ -999,22 +838,19 @@ export async function autoLoop(
999
838
  // LLM budget on guaranteed failures.
1000
839
  const infraCode = isInfrastructureError(loopErr);
1001
840
  if (infraCode) {
841
+ const infraDecision = decideInfrastructureError({
842
+ code: infraCode,
843
+ errorMessage: msg,
844
+ });
1002
845
  debugLog("autoLoop", {
1003
846
  phase: "infrastructure-error",
1004
847
  iteration,
1005
848
  code: infraCode,
1006
849
  error: msg,
1007
850
  });
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);
851
+ ctx.ui.notify(infraDecision.notifyMessage, "error");
852
+ await deps.stopAuto(ctx, pi, infraDecision.stopMessage);
853
+ finishTurn(infraDecision.turnStatus, infraDecision.failureClass, msg);
1018
854
  break;
1019
855
  }
1020
856
 
@@ -1027,6 +863,12 @@ export async function autoLoop(
1027
863
  if (isTransientCooldownError(loopErr)) {
1028
864
  consecutiveCooldowns++;
1029
865
  const retryAfterMs = getCooldownRetryAfterMs(loopErr);
866
+ const cooldownDecision = decideCooldownRecovery({
867
+ consecutiveCooldowns,
868
+ maxCooldownRetries: MAX_COOLDOWN_RETRIES,
869
+ retryAfterMs,
870
+ fallbackWaitMs: COOLDOWN_FALLBACK_WAIT_MS,
871
+ });
1030
872
  debugLog("autoLoop", {
1031
873
  phase: "cooldown-wait",
1032
874
  iteration,
@@ -1035,27 +877,15 @@ export async function autoLoop(
1035
877
  error: msg,
1036
878
  });
1037
879
 
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
- );
880
+ if (cooldownDecision.action === "stop") {
881
+ ctx.ui.notify(cooldownDecision.notifyMessage, "error");
882
+ finishTurn("stopped", "timeout", msg);
883
+ await deps.stopAuto(ctx, pi, cooldownDecision.stopMessage);
1048
884
  break;
1049
885
  }
1050
886
 
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));
887
+ ctx.ui.notify(cooldownDecision.notifyMessage, "warning");
888
+ await new Promise(resolve => setTimeout(resolve, cooldownDecision.waitMs));
1059
889
  finishTurn("retry", "timeout", msg);
1060
890
  continue; // Retry iteration without incrementing consecutiveErrors
1061
891
  }
@@ -1069,34 +899,24 @@ export async function autoLoop(
1069
899
  error: msg,
1070
900
  });
1071
901
 
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);
902
+ const errorDecision = decideIterationErrorRecovery({
903
+ consecutiveErrors,
904
+ recentErrorMessages,
905
+ currentErrorMessage: msg,
906
+ });
907
+ if (errorDecision.action === "stop") {
908
+ ctx.ui.notify(errorDecision.notifyMessage, "error");
909
+ await deps.stopAuto(ctx, pi, errorDecision.stopMessage);
910
+ finishTurn(errorDecision.turnStatus, "execution", msg);
1087
911
  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
- );
912
+ }
913
+ if (errorDecision.action === "invalidate-and-retry") {
914
+ ctx.ui.notify(errorDecision.notifyMessage, "warning");
1094
915
  deps.invalidateAllCaches();
1095
916
  } else {
1096
- // 1st error: log and retry — transient failures happen
1097
- ctx.ui.notify(`Iteration error: ${msg}. Retrying.`, "warning");
917
+ ctx.ui.notify(errorDecision.notifyMessage, "warning");
1098
918
  }
1099
- finishTurn("retry", "execution", msg);
919
+ finishTurn(errorDecision.turnStatus, "execution", msg);
1100
920
  }
1101
921
  }
1102
922