smol-symphony 0.2.0 → 0.3.0

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 (716) hide show
  1. package/AGENTS.md +41 -22
  2. package/DESIGN.md +494 -273
  3. package/README.md +109 -57
  4. package/SPEC.md +33 -24
  5. package/WORKFLOW.minimal.yaml +34 -0
  6. package/{WORKFLOW.template.md → WORKFLOW.template.yaml} +409 -256
  7. package/WORKFLOW.yaml +487 -0
  8. package/assets/skills/symphony-issues/SKILL.md +136 -0
  9. package/assets/symphony-mise.system.toml +68 -0
  10. package/dist/src/bin/symphony.js +30 -0
  11. package/dist/src/bin/symphony.js.map +1 -0
  12. package/dist/src/core/actions/context.js +109 -0
  13. package/dist/src/core/actions/context.js.map +1 -0
  14. package/dist/{actions/parsing.js → src/core/actions/parse.js} +33 -114
  15. package/dist/src/core/actions/parse.js.map +1 -0
  16. package/dist/src/core/actions/plan.js +197 -0
  17. package/dist/src/core/actions/plan.js.map +1 -0
  18. package/dist/src/core/actions/predicates.js +111 -0
  19. package/dist/src/core/actions/predicates.js.map +1 -0
  20. package/dist/src/core/actions/run-fold.js +248 -0
  21. package/dist/src/core/actions/run-fold.js.map +1 -0
  22. package/dist/src/core/actions/template.js +118 -0
  23. package/dist/src/core/actions/template.js.map +1 -0
  24. package/dist/src/core/cli/args.js +116 -0
  25. package/dist/src/core/cli/args.js.map +1 -0
  26. package/dist/src/core/coerce.js +75 -0
  27. package/dist/src/core/coerce.js.map +1 -0
  28. package/dist/src/core/credential/account-id.js +20 -0
  29. package/dist/src/core/credential/account-id.js.map +1 -0
  30. package/dist/src/core/credential/adapter-config.js +136 -0
  31. package/dist/src/core/credential/adapter-config.js.map +1 -0
  32. package/dist/src/core/credential/availability.js +98 -0
  33. package/dist/src/core/credential/availability.js.map +1 -0
  34. package/dist/src/core/credential/extract.js +228 -0
  35. package/dist/src/core/credential/extract.js.map +1 -0
  36. package/dist/src/core/credential/fake-creds.js +171 -0
  37. package/dist/src/core/credential/fake-creds.js.map +1 -0
  38. package/dist/src/core/credential/identity.js +125 -0
  39. package/dist/src/core/credential/identity.js.map +1 -0
  40. package/dist/src/core/credential/shape.js +230 -0
  41. package/dist/src/core/credential/shape.js.map +1 -0
  42. package/dist/src/core/credential/strings.js +15 -0
  43. package/dist/src/core/credential/strings.js.map +1 -0
  44. package/dist/src/core/doctor/checks.js +303 -0
  45. package/dist/src/core/doctor/checks.js.map +1 -0
  46. package/dist/src/core/git/result.js +107 -0
  47. package/dist/src/core/git/result.js.map +1 -0
  48. package/dist/src/core/http/decisions.js +225 -0
  49. package/dist/src/core/http/decisions.js.map +1 -0
  50. package/dist/{http.js → src/core/http/render.js} +472 -738
  51. package/dist/src/core/http/render.js.map +1 -0
  52. package/dist/{http-handlers.js → src/core/http/routes.js} +52 -87
  53. package/dist/src/core/http/routes.js.map +1 -0
  54. package/dist/src/core/http/views.js +181 -0
  55. package/dist/src/core/http/views.js.map +1 -0
  56. package/dist/src/core/image/managed-image.js +95 -0
  57. package/dist/src/core/image/managed-image.js.map +1 -0
  58. package/dist/src/core/issue/file.js +149 -0
  59. package/dist/src/core/issue/file.js.map +1 -0
  60. package/dist/src/core/issue/parse.js +210 -0
  61. package/dist/src/core/issue/parse.js.map +1 -0
  62. package/dist/src/core/mcp/dispatch.js +239 -0
  63. package/dist/src/core/mcp/dispatch.js.map +1 -0
  64. package/dist/src/core/mcp/post-move.js +92 -0
  65. package/dist/src/core/mcp/post-move.js.map +1 -0
  66. package/dist/src/core/mcp/protocol.js +293 -0
  67. package/dist/src/core/mcp/protocol.js.map +1 -0
  68. package/dist/src/core/mcp/url.js +162 -0
  69. package/dist/src/core/mcp/url.js.map +1 -0
  70. package/dist/src/core/path.js +63 -0
  71. package/dist/src/core/path.js.map +1 -0
  72. package/dist/src/core/reconcile/image-decide.js +48 -0
  73. package/dist/src/core/reconcile/image-decide.js.map +1 -0
  74. package/dist/src/core/reconcile/ledger.js +142 -0
  75. package/dist/src/core/reconcile/ledger.js.map +1 -0
  76. package/dist/src/core/reconcile/pr-classify.js +62 -0
  77. package/dist/src/core/reconcile/pr-classify.js.map +1 -0
  78. package/dist/{reconciler → src/core/reconcile}/pr-decide.js +25 -12
  79. package/dist/src/core/reconcile/pr-decide.js.map +1 -0
  80. package/dist/src/core/reconcile/pr-loop.js +161 -0
  81. package/dist/src/core/reconcile/pr-loop.js.map +1 -0
  82. package/dist/src/core/reconcile/pr-notes.js +35 -0
  83. package/dist/src/core/reconcile/pr-notes.js.map +1 -0
  84. package/dist/src/core/reconcile/vm-decide.js +70 -0
  85. package/dist/src/core/reconcile/vm-decide.js.map +1 -0
  86. package/dist/src/core/reconcile/vm-reap.js +207 -0
  87. package/dist/src/core/reconcile/vm-reap.js.map +1 -0
  88. package/dist/src/core/reconcile/workspace-decide.js +162 -0
  89. package/dist/src/core/reconcile/workspace-decide.js.map +1 -0
  90. package/dist/src/core/runlog/summary.js +231 -0
  91. package/dist/src/core/runlog/summary.js.map +1 -0
  92. package/dist/src/core/runner/dispatch-config.js +95 -0
  93. package/dist/src/core/runner/dispatch-config.js.map +1 -0
  94. package/dist/src/core/runner/injection.js +61 -0
  95. package/dist/src/core/runner/injection.js.map +1 -0
  96. package/dist/src/core/runner/mise.js +210 -0
  97. package/dist/src/core/runner/mise.js.map +1 -0
  98. package/dist/src/core/runner/prompt.js +720 -0
  99. package/dist/src/core/runner/prompt.js.map +1 -0
  100. package/dist/src/core/runner/turn.js +242 -0
  101. package/dist/src/core/runner/turn.js.map +1 -0
  102. package/dist/src/core/runner/vm-plan.js +390 -0
  103. package/dist/src/core/runner/vm-plan.js.map +1 -0
  104. package/dist/src/core/schedule/admission.js +123 -0
  105. package/dist/src/core/schedule/admission.js.map +1 -0
  106. package/dist/src/core/schedule/circuit-breaker.js +111 -0
  107. package/dist/src/core/schedule/circuit-breaker.js.map +1 -0
  108. package/dist/src/core/schedule/eligibility.js +83 -0
  109. package/dist/src/core/schedule/eligibility.js.map +1 -0
  110. package/dist/src/core/schedule/reconcile-issue.js +82 -0
  111. package/dist/src/core/schedule/reconcile-issue.js.map +1 -0
  112. package/dist/src/core/schedule/retry.js +96 -0
  113. package/dist/src/core/schedule/retry.js.map +1 -0
  114. package/dist/src/core/schedule/sleep-cycle.js +133 -0
  115. package/dist/src/core/schedule/sleep-cycle.js.map +1 -0
  116. package/dist/src/core/schedule/slots.js +124 -0
  117. package/dist/src/core/schedule/slots.js.map +1 -0
  118. package/dist/src/core/schedule/tick.js +553 -0
  119. package/dist/src/core/schedule/tick.js.map +1 -0
  120. package/dist/src/core/schedule/token-fold.js +181 -0
  121. package/dist/src/core/schedule/token-fold.js.map +1 -0
  122. package/dist/src/core/state-resolve.js +86 -0
  123. package/dist/src/core/state-resolve.js.map +1 -0
  124. package/dist/src/core/vm-guards.js +278 -0
  125. package/dist/src/core/vm-guards.js.map +1 -0
  126. package/dist/src/core/workflow/derive.js +107 -0
  127. package/dist/src/core/workflow/derive.js.map +1 -0
  128. package/dist/src/core/workflow/parse.js +687 -0
  129. package/dist/src/core/workflow/parse.js.map +1 -0
  130. package/dist/src/core/workflow/prompt-probe.js +78 -0
  131. package/dist/src/core/workflow/prompt-probe.js.map +1 -0
  132. package/dist/src/core/workflow/validate.js +189 -0
  133. package/dist/src/core/workflow/validate.js.map +1 -0
  134. package/dist/src/core/workspace-key.js +19 -0
  135. package/dist/src/core/workspace-key.js.map +1 -0
  136. package/dist/src/shell/actions-runner.js +356 -0
  137. package/dist/src/shell/actions-runner.js.map +1 -0
  138. package/dist/src/shell/adapter/adapter-registry.js +45 -0
  139. package/dist/src/shell/adapter/adapter-registry.js.map +1 -0
  140. package/dist/src/shell/adapter/clock-random.js +96 -0
  141. package/dist/src/shell/adapter/clock-random.js.map +1 -0
  142. package/dist/src/shell/adapter/gondolin-dispatch-helpers.js +158 -0
  143. package/dist/src/shell/adapter/gondolin-dispatch-helpers.js.map +1 -0
  144. package/dist/src/shell/adapter/gondolin-dispatch.js +385 -0
  145. package/dist/src/shell/adapter/gondolin-dispatch.js.map +1 -0
  146. package/dist/src/shell/adapter/gondolin-image-converter.js +233 -0
  147. package/dist/src/shell/adapter/gondolin-image-converter.js.map +1 -0
  148. package/dist/src/shell/adapter/gondolin-image-fetch.js +180 -0
  149. package/dist/src/shell/adapter/gondolin-image-fetch.js.map +1 -0
  150. package/dist/src/shell/adapter/launcher-asset.js +57 -0
  151. package/dist/src/shell/adapter/launcher-asset.js.map +1 -0
  152. package/dist/src/shell/adapter/mise-config-asset.js +65 -0
  153. package/dist/src/shell/adapter/mise-config-asset.js.map +1 -0
  154. package/dist/src/shell/adapter/workflow-loader.js +304 -0
  155. package/dist/src/shell/adapter/workflow-loader.js.map +1 -0
  156. package/dist/src/shell/cli/doctor.js +268 -0
  157. package/dist/src/shell/cli/doctor.js.map +1 -0
  158. package/dist/src/shell/effect-interpreter-families.js +314 -0
  159. package/dist/src/shell/effect-interpreter-families.js.map +1 -0
  160. package/dist/src/shell/effect-interpreter.js +29 -0
  161. package/dist/src/shell/effect-interpreter.js.map +1 -0
  162. package/dist/src/shell/interp/acp-frame.js +137 -0
  163. package/dist/src/shell/interp/acp-frame.js.map +1 -0
  164. package/dist/src/shell/interp/acp-ws-conn.js +320 -0
  165. package/dist/src/shell/interp/acp-ws-conn.js.map +1 -0
  166. package/dist/src/shell/interp/acp-ws-frames.js +159 -0
  167. package/dist/src/shell/interp/acp-ws-frames.js.map +1 -0
  168. package/dist/src/shell/interp/acp-ws.js +197 -0
  169. package/dist/src/shell/interp/acp-ws.js.map +1 -0
  170. package/dist/src/shell/interp/acp.js +319 -0
  171. package/dist/src/shell/interp/acp.js.map +1 -0
  172. package/dist/src/shell/interp/credential-defaults.js +128 -0
  173. package/dist/src/shell/interp/credential-defaults.js.map +1 -0
  174. package/dist/src/shell/interp/credential-hooks.js +149 -0
  175. package/dist/src/shell/interp/credential-hooks.js.map +1 -0
  176. package/dist/src/shell/interp/credential-registry.js +226 -0
  177. package/dist/src/shell/interp/credential-registry.js.map +1 -0
  178. package/dist/src/shell/interp/credential.js +103 -0
  179. package/dist/src/shell/interp/credential.js.map +1 -0
  180. package/dist/src/shell/interp/gh.js +163 -0
  181. package/dist/src/shell/interp/gh.js.map +1 -0
  182. package/dist/src/shell/interp/git.js +28 -0
  183. package/dist/src/shell/interp/git.js.map +1 -0
  184. package/dist/src/shell/interp/log.js +213 -0
  185. package/dist/src/shell/interp/log.js.map +1 -0
  186. package/dist/src/shell/interp/process.js +178 -0
  187. package/dist/src/shell/interp/process.js.map +1 -0
  188. package/dist/src/shell/interp/runlog.js +193 -0
  189. package/dist/src/shell/interp/runlog.js.map +1 -0
  190. package/dist/src/shell/interp/timer.js +64 -0
  191. package/dist/src/shell/interp/timer.js.map +1 -0
  192. package/dist/src/shell/interp/tracker-disk.js +99 -0
  193. package/dist/src/shell/interp/tracker-disk.js.map +1 -0
  194. package/dist/src/shell/interp/tracker-parse.js +71 -0
  195. package/dist/src/shell/interp/tracker-parse.js.map +1 -0
  196. package/dist/src/shell/interp/tracker-scan.js +238 -0
  197. package/dist/src/shell/interp/tracker-scan.js.map +1 -0
  198. package/dist/src/shell/interp/tracker-write.js +91 -0
  199. package/dist/src/shell/interp/tracker-write.js.map +1 -0
  200. package/dist/src/shell/interp/tracker.js +41 -0
  201. package/dist/src/shell/interp/tracker.js.map +1 -0
  202. package/dist/src/shell/interp/tty.js +48 -0
  203. package/dist/src/shell/interp/tty.js.map +1 -0
  204. package/dist/src/shell/interp/vm.js +199 -0
  205. package/dist/src/shell/interp/vm.js.map +1 -0
  206. package/dist/src/shell/interp/workspace.js +310 -0
  207. package/dist/src/shell/interp/workspace.js.map +1 -0
  208. package/dist/src/shell/main-acp.js +78 -0
  209. package/dist/src/shell/main-acp.js.map +1 -0
  210. package/dist/src/shell/main-adapters.js +222 -0
  211. package/dist/src/shell/main-adapters.js.map +1 -0
  212. package/dist/src/shell/main-credential.js +122 -0
  213. package/dist/src/shell/main-credential.js.map +1 -0
  214. package/dist/src/shell/main-doctor.js +22 -0
  215. package/dist/src/shell/main-doctor.js.map +1 -0
  216. package/dist/src/shell/main-entry.js +46 -0
  217. package/dist/src/shell/main-entry.js.map +1 -0
  218. package/dist/src/shell/main-http-csrf.js +45 -0
  219. package/dist/src/shell/main-http-csrf.js.map +1 -0
  220. package/dist/src/shell/main-http-handler.js +389 -0
  221. package/dist/src/shell/main-http-handler.js.map +1 -0
  222. package/dist/src/shell/main-http-mcp.js +122 -0
  223. package/dist/src/shell/main-http-mcp.js.map +1 -0
  224. package/dist/src/shell/main-http-views.js +253 -0
  225. package/dist/src/shell/main-http-views.js.map +1 -0
  226. package/dist/src/shell/main-http.js +76 -0
  227. package/dist/src/shell/main-http.js.map +1 -0
  228. package/dist/src/shell/main-loops.js +130 -0
  229. package/dist/src/shell/main-loops.js.map +1 -0
  230. package/dist/src/shell/main-mcp.js +129 -0
  231. package/dist/src/shell/main-mcp.js.map +1 -0
  232. package/dist/src/shell/main-orchestrator.js +120 -0
  233. package/dist/src/shell/main-orchestrator.js.map +1 -0
  234. package/dist/src/shell/main-preflight.js +43 -0
  235. package/dist/src/shell/main-preflight.js.map +1 -0
  236. package/dist/src/shell/main-reconcilers-helpers.js +244 -0
  237. package/dist/src/shell/main-reconcilers-helpers.js.map +1 -0
  238. package/dist/src/shell/main-reconcilers-pr.js +148 -0
  239. package/dist/src/shell/main-reconcilers-pr.js.map +1 -0
  240. package/dist/src/shell/main-reconcilers.js +225 -0
  241. package/dist/src/shell/main-reconcilers.js.map +1 -0
  242. package/dist/src/shell/main-runner.js +355 -0
  243. package/dist/src/shell/main-runner.js.map +1 -0
  244. package/dist/src/shell/main-scaffold.js +116 -0
  245. package/dist/src/shell/main-scaffold.js.map +1 -0
  246. package/dist/src/shell/main-shutdown.js +115 -0
  247. package/dist/src/shell/main-shutdown.js.map +1 -0
  248. package/dist/src/shell/main-startup.js +48 -0
  249. package/dist/src/shell/main-startup.js.map +1 -0
  250. package/dist/src/shell/main-substrates.js +43 -0
  251. package/dist/src/shell/main-substrates.js.map +1 -0
  252. package/dist/src/shell/main.js +385 -0
  253. package/dist/src/shell/main.js.map +1 -0
  254. package/dist/src/shell/orchestrator-feedback.js +69 -0
  255. package/dist/src/shell/orchestrator-feedback.js.map +1 -0
  256. package/dist/src/shell/orchestrator-image.js +167 -0
  257. package/dist/src/shell/orchestrator-image.js.map +1 -0
  258. package/dist/src/shell/orchestrator-loop.js +468 -0
  259. package/dist/src/shell/orchestrator-loop.js.map +1 -0
  260. package/dist/src/shell/orchestrator-reconcile.js +36 -0
  261. package/dist/src/shell/orchestrator-reconcile.js.map +1 -0
  262. package/dist/src/shell/reconciler-loop.js +228 -0
  263. package/dist/src/shell/reconciler-loop.js.map +1 -0
  264. package/dist/src/shell/runner-loop-turn.js +301 -0
  265. package/dist/src/shell/runner-loop-turn.js.map +1 -0
  266. package/dist/src/shell/runner-loop.js +338 -0
  267. package/dist/src/shell/runner-loop.js.map +1 -0
  268. package/dist/src/shell/server/http.js +208 -0
  269. package/dist/src/shell/server/http.js.map +1 -0
  270. package/dist/src/shell/server/mcp-runtime-effects.js +237 -0
  271. package/dist/src/shell/server/mcp-runtime-effects.js.map +1 -0
  272. package/dist/src/shell/server/mcp-runtime.js +99 -0
  273. package/dist/src/shell/server/mcp-runtime.js.map +1 -0
  274. package/dist/src/shell/workspace-key.js +14 -0
  275. package/dist/src/shell/workspace-key.js.map +1 -0
  276. package/dist/src/types/acp.js +8 -0
  277. package/dist/src/types/acp.js.map +1 -0
  278. package/dist/src/types/actions/plan.js +6 -0
  279. package/dist/src/types/actions/plan.js.map +1 -0
  280. package/dist/src/types/actions/predicates.js +6 -0
  281. package/dist/src/types/actions/predicates.js.map +1 -0
  282. package/dist/src/types/actions/run-fold.js +8 -0
  283. package/dist/src/types/actions/run-fold.js.map +1 -0
  284. package/dist/src/types/actions.js +7 -0
  285. package/dist/src/types/actions.js.map +1 -0
  286. package/dist/src/types/adapter/clock-random.js +4 -0
  287. package/dist/src/types/adapter/clock-random.js.map +1 -0
  288. package/dist/src/types/adapter/gondolin-image-converter.js +5 -0
  289. package/dist/src/types/adapter/gondolin-image-converter.js.map +1 -0
  290. package/dist/src/types/adapter/gondolin-image-fetch.js +5 -0
  291. package/dist/src/types/adapter/gondolin-image-fetch.js.map +1 -0
  292. package/dist/src/types/adapter/workflow-loader.js +4 -0
  293. package/dist/src/types/adapter/workflow-loader.js.map +1 -0
  294. package/dist/src/types/cli/args.js +8 -0
  295. package/dist/src/types/cli/args.js.map +1 -0
  296. package/dist/src/types/config.js +8 -0
  297. package/dist/src/types/config.js.map +1 -0
  298. package/dist/src/types/credential-interp.js +6 -0
  299. package/dist/src/types/credential-interp.js.map +1 -0
  300. package/dist/src/types/credentials.js +10 -0
  301. package/dist/src/types/credentials.js.map +1 -0
  302. package/dist/src/types/doctor.js +7 -0
  303. package/dist/src/types/doctor.js.map +1 -0
  304. package/dist/src/types/domain.js +7 -0
  305. package/dist/src/types/domain.js.map +1 -0
  306. package/dist/src/types/effect.js +15 -0
  307. package/dist/src/types/effect.js.map +1 -0
  308. package/dist/src/types/errors.js +39 -0
  309. package/dist/src/types/errors.js.map +1 -0
  310. package/dist/src/types/http/decisions.js +6 -0
  311. package/dist/src/types/http/decisions.js.map +1 -0
  312. package/dist/src/types/http/render.js +10 -0
  313. package/dist/src/types/http/render.js.map +1 -0
  314. package/dist/src/types/http/views.js +6 -0
  315. package/dist/src/types/http/views.js.map +1 -0
  316. package/dist/src/types/http.js +9 -0
  317. package/dist/src/types/http.js.map +1 -0
  318. package/dist/src/types/image/managed-image.js +7 -0
  319. package/dist/src/types/image/managed-image.js.map +1 -0
  320. package/dist/src/types/interp/effect-interpreter.js +8 -0
  321. package/dist/src/types/interp/effect-interpreter.js.map +1 -0
  322. package/dist/src/types/interp/tracker.js +7 -0
  323. package/dist/src/types/interp/tracker.js.map +1 -0
  324. package/dist/src/types/issue/file.js +6 -0
  325. package/dist/src/types/issue/file.js.map +1 -0
  326. package/dist/src/types/issue/parse.js +8 -0
  327. package/dist/src/types/issue/parse.js.map +1 -0
  328. package/dist/src/types/main-acp.js +13 -0
  329. package/dist/src/types/main-acp.js.map +1 -0
  330. package/dist/src/types/main-adapters.js +5 -0
  331. package/dist/src/types/main-adapters.js.map +1 -0
  332. package/dist/src/types/main-credential.js +21 -0
  333. package/dist/src/types/main-credential.js.map +1 -0
  334. package/dist/src/types/main-doctor.js +6 -0
  335. package/dist/src/types/main-doctor.js.map +1 -0
  336. package/dist/src/types/main-http-handler.js +12 -0
  337. package/dist/src/types/main-http-handler.js.map +1 -0
  338. package/dist/src/types/main-http.js +5 -0
  339. package/dist/src/types/main-http.js.map +1 -0
  340. package/dist/src/types/main-loops.js +5 -0
  341. package/dist/src/types/main-loops.js.map +1 -0
  342. package/dist/src/types/main-mcp.js +12 -0
  343. package/dist/src/types/main-mcp.js.map +1 -0
  344. package/dist/src/types/main-orchestrator.js +5 -0
  345. package/dist/src/types/main-orchestrator.js.map +1 -0
  346. package/dist/src/types/main-reconcilers.js +11 -0
  347. package/dist/src/types/main-reconcilers.js.map +1 -0
  348. package/dist/src/types/main-runner.js +13 -0
  349. package/dist/src/types/main-runner.js.map +1 -0
  350. package/dist/src/types/main-startup.js +5 -0
  351. package/dist/src/types/main-startup.js.map +1 -0
  352. package/dist/src/types/main-substrates.js +5 -0
  353. package/dist/src/types/main-substrates.js.map +1 -0
  354. package/dist/src/types/mcp/dispatch.js +4 -0
  355. package/dist/src/types/mcp/dispatch.js.map +1 -0
  356. package/dist/src/types/mcp/post-move.js +7 -0
  357. package/dist/src/types/mcp/post-move.js.map +1 -0
  358. package/dist/src/types/mcp.js +9 -0
  359. package/dist/src/types/mcp.js.map +1 -0
  360. package/dist/src/types/ports.js +12 -0
  361. package/dist/src/types/ports.js.map +1 -0
  362. package/dist/src/types/reconcile/image-decide.js +5 -0
  363. package/dist/src/types/reconcile/image-decide.js.map +1 -0
  364. package/dist/src/types/reconcile/ledger.js +7 -0
  365. package/dist/src/types/reconcile/ledger.js.map +1 -0
  366. package/dist/src/types/reconcile/pr-loop.js +8 -0
  367. package/dist/src/types/reconcile/pr-loop.js.map +1 -0
  368. package/dist/src/types/reconcile/vm-reap.js +8 -0
  369. package/dist/src/types/reconcile/vm-reap.js.map +1 -0
  370. package/dist/src/types/reconcile/workspace-decide.js +7 -0
  371. package/dist/src/types/reconcile/workspace-decide.js.map +1 -0
  372. package/dist/src/types/reconcile.js +9 -0
  373. package/dist/src/types/reconcile.js.map +1 -0
  374. package/dist/src/types/runlog.js +7 -0
  375. package/dist/src/types/runlog.js.map +1 -0
  376. package/dist/src/types/runner/actions-runner.js +12 -0
  377. package/dist/src/types/runner/actions-runner.js.map +1 -0
  378. package/dist/src/types/runner/gondolin-dispatch.js +5 -0
  379. package/dist/src/types/runner/gondolin-dispatch.js.map +1 -0
  380. package/dist/src/types/runner/injection.js +6 -0
  381. package/dist/src/types/runner/injection.js.map +1 -0
  382. package/dist/src/types/runner/runner-loop.js +5 -0
  383. package/dist/src/types/runner/runner-loop.js.map +1 -0
  384. package/dist/src/types/runner/turn.js +4 -0
  385. package/dist/src/types/runner/turn.js.map +1 -0
  386. package/dist/src/types/runner/vm-plan.js +4 -0
  387. package/dist/src/types/runner/vm-plan.js.map +1 -0
  388. package/dist/src/types/runtime.js +9 -0
  389. package/dist/src/types/runtime.js.map +1 -0
  390. package/dist/src/types/schedule/admission.js +7 -0
  391. package/dist/src/types/schedule/admission.js.map +1 -0
  392. package/dist/src/types/schedule/circuit-breaker.js +2 -0
  393. package/dist/src/types/schedule/circuit-breaker.js.map +1 -0
  394. package/dist/src/types/schedule/eligibility.js +9 -0
  395. package/dist/src/types/schedule/eligibility.js.map +1 -0
  396. package/dist/src/types/schedule/orchestrator-loop.js +10 -0
  397. package/dist/src/types/schedule/orchestrator-loop.js.map +1 -0
  398. package/dist/src/types/schedule/sleep-cycle.js +4 -0
  399. package/dist/src/types/schedule/sleep-cycle.js.map +1 -0
  400. package/dist/src/types/schedule/slots.js +8 -0
  401. package/dist/src/types/schedule/slots.js.map +1 -0
  402. package/dist/src/types/schedule/tick.js +9 -0
  403. package/dist/src/types/schedule/tick.js.map +1 -0
  404. package/dist/src/types/server/mcp-runtime.js +8 -0
  405. package/dist/src/types/server/mcp-runtime.js.map +1 -0
  406. package/dist/src/types/workflow/parse.js +4 -0
  407. package/dist/src/types/workflow/parse.js.map +1 -0
  408. package/dist/tests/core/account-id.test.js +35 -0
  409. package/dist/tests/core/account-id.test.js.map +1 -0
  410. package/dist/tests/core/actions-parse.test.js +176 -0
  411. package/dist/tests/core/actions-parse.test.js.map +1 -0
  412. package/dist/tests/core/adapter-config.test.js +133 -0
  413. package/dist/tests/core/adapter-config.test.js.map +1 -0
  414. package/dist/tests/core/admission.test.js +215 -0
  415. package/dist/tests/core/admission.test.js.map +1 -0
  416. package/dist/tests/core/args.test.js +132 -0
  417. package/dist/tests/core/args.test.js.map +1 -0
  418. package/dist/tests/core/availability.test.js +62 -0
  419. package/dist/tests/core/availability.test.js.map +1 -0
  420. package/dist/tests/core/checks.test.js +395 -0
  421. package/dist/tests/core/checks.test.js.map +1 -0
  422. package/dist/tests/core/circuit-breaker.test.js +172 -0
  423. package/dist/tests/core/circuit-breaker.test.js.map +1 -0
  424. package/dist/tests/core/coerce.test.js +87 -0
  425. package/dist/tests/core/coerce.test.js.map +1 -0
  426. package/dist/tests/core/context.test.js +228 -0
  427. package/dist/tests/core/context.test.js.map +1 -0
  428. package/dist/tests/core/decisions.test.js +310 -0
  429. package/dist/tests/core/decisions.test.js.map +1 -0
  430. package/dist/tests/core/derive.test.js +205 -0
  431. package/dist/tests/core/derive.test.js.map +1 -0
  432. package/dist/tests/core/dispatch-config.test.js +164 -0
  433. package/dist/tests/core/dispatch-config.test.js.map +1 -0
  434. package/dist/tests/core/dispatch.test.js +302 -0
  435. package/dist/tests/core/dispatch.test.js.map +1 -0
  436. package/dist/tests/core/eligibility.test.js +163 -0
  437. package/dist/tests/core/eligibility.test.js.map +1 -0
  438. package/dist/tests/core/extract.test.js +139 -0
  439. package/dist/tests/core/extract.test.js.map +1 -0
  440. package/dist/tests/core/fake-creds.test.js +134 -0
  441. package/dist/tests/core/fake-creds.test.js.map +1 -0
  442. package/dist/tests/core/file.test.js +197 -0
  443. package/dist/tests/core/file.test.js.map +1 -0
  444. package/dist/tests/core/git-result.test.js +113 -0
  445. package/dist/tests/core/git-result.test.js.map +1 -0
  446. package/dist/tests/core/identity.test.js +180 -0
  447. package/dist/tests/core/identity.test.js.map +1 -0
  448. package/dist/tests/core/image-decide.test.js +59 -0
  449. package/dist/tests/core/image-decide.test.js.map +1 -0
  450. package/dist/tests/core/injection.test.js +163 -0
  451. package/dist/tests/core/injection.test.js.map +1 -0
  452. package/dist/tests/core/ledger.test.js +218 -0
  453. package/dist/tests/core/ledger.test.js.map +1 -0
  454. package/dist/tests/core/managed-image.test.js +68 -0
  455. package/dist/tests/core/managed-image.test.js.map +1 -0
  456. package/dist/tests/core/mise.test.js +138 -0
  457. package/dist/tests/core/mise.test.js.map +1 -0
  458. package/dist/tests/core/parse.test.js +174 -0
  459. package/dist/tests/core/parse.test.js.map +1 -0
  460. package/dist/tests/core/path.test.js +50 -0
  461. package/dist/tests/core/path.test.js.map +1 -0
  462. package/dist/tests/core/plan.test.js +218 -0
  463. package/dist/tests/core/plan.test.js.map +1 -0
  464. package/dist/tests/core/post-move.test.js +162 -0
  465. package/dist/tests/core/post-move.test.js.map +1 -0
  466. package/dist/tests/core/pr-classify.test.js +117 -0
  467. package/dist/tests/core/pr-classify.test.js.map +1 -0
  468. package/dist/tests/core/pr-decide.test.js +298 -0
  469. package/dist/tests/core/pr-decide.test.js.map +1 -0
  470. package/dist/tests/core/pr-loop.test.js +301 -0
  471. package/dist/tests/core/pr-loop.test.js.map +1 -0
  472. package/dist/tests/core/pr-notes.test.js +165 -0
  473. package/dist/tests/core/pr-notes.test.js.map +1 -0
  474. package/dist/tests/core/predicates.test.js +154 -0
  475. package/dist/tests/core/predicates.test.js.map +1 -0
  476. package/dist/tests/core/prompt.test.js +189 -0
  477. package/dist/tests/core/prompt.test.js.map +1 -0
  478. package/dist/tests/core/protocol.test.js +195 -0
  479. package/dist/tests/core/protocol.test.js.map +1 -0
  480. package/dist/tests/core/reconcile-issue.test.js +116 -0
  481. package/dist/tests/core/reconcile-issue.test.js.map +1 -0
  482. package/dist/tests/core/render.test.js +549 -0
  483. package/dist/tests/core/render.test.js.map +1 -0
  484. package/dist/tests/core/retry.test.js +186 -0
  485. package/dist/tests/core/retry.test.js.map +1 -0
  486. package/dist/tests/core/routes.test.js +247 -0
  487. package/dist/tests/core/routes.test.js.map +1 -0
  488. package/dist/tests/core/run-fold.test.js +299 -0
  489. package/dist/tests/core/run-fold.test.js.map +1 -0
  490. package/dist/tests/core/shape.test.js +185 -0
  491. package/dist/tests/core/shape.test.js.map +1 -0
  492. package/dist/tests/core/sleep-cycle.test.js +150 -0
  493. package/dist/tests/core/sleep-cycle.test.js.map +1 -0
  494. package/dist/tests/core/slots.test.js +201 -0
  495. package/dist/tests/core/slots.test.js.map +1 -0
  496. package/dist/tests/core/state-resolve.test.js +80 -0
  497. package/dist/tests/core/state-resolve.test.js.map +1 -0
  498. package/dist/tests/core/summary.test.js +200 -0
  499. package/dist/tests/core/summary.test.js.map +1 -0
  500. package/dist/tests/core/template.test.js +116 -0
  501. package/dist/tests/core/template.test.js.map +1 -0
  502. package/dist/tests/core/tick.test.js +558 -0
  503. package/dist/tests/core/tick.test.js.map +1 -0
  504. package/dist/tests/core/token-fold.test.js +176 -0
  505. package/dist/tests/core/token-fold.test.js.map +1 -0
  506. package/dist/tests/core/turn.test.js +388 -0
  507. package/dist/tests/core/turn.test.js.map +1 -0
  508. package/dist/tests/core/url.test.js +118 -0
  509. package/dist/tests/core/url.test.js.map +1 -0
  510. package/dist/tests/core/validate.test.js +247 -0
  511. package/dist/tests/core/validate.test.js.map +1 -0
  512. package/dist/tests/core/views.test.js +252 -0
  513. package/dist/tests/core/views.test.js.map +1 -0
  514. package/dist/tests/core/vm-decide.test.js +110 -0
  515. package/dist/tests/core/vm-decide.test.js.map +1 -0
  516. package/dist/tests/core/vm-guards.test.js +153 -0
  517. package/dist/tests/core/vm-guards.test.js.map +1 -0
  518. package/dist/tests/core/vm-plan.test.js +332 -0
  519. package/dist/tests/core/vm-plan.test.js.map +1 -0
  520. package/dist/tests/core/vm-reap.test.js +196 -0
  521. package/dist/tests/core/vm-reap.test.js.map +1 -0
  522. package/dist/tests/core/workflow-parse.test.js +493 -0
  523. package/dist/tests/core/workflow-parse.test.js.map +1 -0
  524. package/dist/tests/core/workspace-decide.test.js +236 -0
  525. package/dist/tests/core/workspace-decide.test.js.map +1 -0
  526. package/dist/tests/helpers/fixtures.js +167 -0
  527. package/dist/tests/helpers/fixtures.js.map +1 -0
  528. package/dist/tests/shell/acp-substrate.test.js +101 -0
  529. package/dist/tests/shell/acp-substrate.test.js.map +1 -0
  530. package/dist/tests/shell/actions-runner-push.test.js +203 -0
  531. package/dist/tests/shell/actions-runner-push.test.js.map +1 -0
  532. package/dist/tests/shell/credential-hooks.test.js +36 -0
  533. package/dist/tests/shell/credential-hooks.test.js.map +1 -0
  534. package/dist/tests/shell/credential-registry.test.js +165 -0
  535. package/dist/tests/shell/credential-registry.test.js.map +1 -0
  536. package/dist/tests/shell/credential-substrate.test.js +179 -0
  537. package/dist/tests/shell/credential-substrate.test.js.map +1 -0
  538. package/dist/tests/shell/dockerfile-mise-pin.test.js +51 -0
  539. package/dist/tests/shell/dockerfile-mise-pin.test.js.map +1 -0
  540. package/dist/tests/shell/doctor.test.js +101 -0
  541. package/dist/tests/shell/doctor.test.js.map +1 -0
  542. package/dist/tests/shell/effect-vm-create.test.js +52 -0
  543. package/dist/tests/shell/effect-vm-create.test.js.map +1 -0
  544. package/dist/tests/shell/gh-port.test.js +63 -0
  545. package/dist/tests/shell/gh-port.test.js.map +1 -0
  546. package/dist/tests/shell/gondolin-dispatch-guard.test.js +144 -0
  547. package/dist/tests/shell/gondolin-dispatch-guard.test.js.map +1 -0
  548. package/dist/tests/shell/gondolin-dispatch-shquote.test.js +168 -0
  549. package/dist/tests/shell/gondolin-dispatch-shquote.test.js.map +1 -0
  550. package/dist/tests/shell/gondolin-image-converter.test.js +208 -0
  551. package/dist/tests/shell/gondolin-image-converter.test.js.map +1 -0
  552. package/dist/tests/shell/gondolin-image-fetch.test.js +93 -0
  553. package/dist/tests/shell/gondolin-image-fetch.test.js.map +1 -0
  554. package/dist/tests/shell/http-handler.test.js +608 -0
  555. package/dist/tests/shell/http-handler.test.js.map +1 -0
  556. package/dist/tests/shell/http-server.test.js +53 -0
  557. package/dist/tests/shell/http-server.test.js.map +1 -0
  558. package/dist/tests/shell/mcp-runtime.test.js +366 -0
  559. package/dist/tests/shell/mcp-runtime.test.js.map +1 -0
  560. package/dist/tests/shell/mise-config-asset.test.js +87 -0
  561. package/dist/tests/shell/mise-config-asset.test.js.map +1 -0
  562. package/dist/tests/shell/orchestrator-loop.test.js +583 -0
  563. package/dist/tests/shell/orchestrator-loop.test.js.map +1 -0
  564. package/dist/tests/shell/reconciler-passes.test.js +314 -0
  565. package/dist/tests/shell/reconciler-passes.test.js.map +1 -0
  566. package/dist/tests/shell/runner-loop-turn.test.js +97 -0
  567. package/dist/tests/shell/runner-loop-turn.test.js.map +1 -0
  568. package/dist/tests/shell/runner-slice.test.js +536 -0
  569. package/dist/tests/shell/runner-slice.test.js.map +1 -0
  570. package/dist/tests/shell/scaffold.test.js +65 -0
  571. package/dist/tests/shell/scaffold.test.js.map +1 -0
  572. package/dist/tests/shell/tick-config.test.js +83 -0
  573. package/dist/tests/shell/tick-config.test.js.map +1 -0
  574. package/dist/tests/shell/tracker-parse-dates.test.js +44 -0
  575. package/dist/tests/shell/tracker-parse-dates.test.js.map +1 -0
  576. package/dist/tests/shell/tracker-write-issue.test.js +154 -0
  577. package/dist/tests/shell/tracker-write-issue.test.js.map +1 -0
  578. package/dist/tests/shell/workflow-prompt-split.test.js +208 -0
  579. package/dist/tests/shell/workflow-prompt-split.test.js.map +1 -0
  580. package/dist/tests/shell/workspace-live-config.test.js +140 -0
  581. package/dist/tests/shell/workspace-live-config.test.js.map +1 -0
  582. package/package.json +21 -9
  583. package/patches/@earendil-works+gondolin+0.12.0.patch +173 -0
  584. package/prompts/Reflect.md +91 -0
  585. package/prompts/Review.md +97 -0
  586. package/prompts/Todo.md +96 -0
  587. package/prompts/_footer.md +41 -0
  588. package/prompts/_preamble.md +42 -0
  589. package/prompts-minimal/Todo.md +26 -0
  590. package/scripts/postinstall.mjs +63 -0
  591. package/scripts/vm-agent.mjs +312 -90
  592. package/WORKFLOW.md +0 -744
  593. package/dist/acp-bridge.js +0 -324
  594. package/dist/acp-bridge.js.map +0 -1
  595. package/dist/actions/cache.js +0 -191
  596. package/dist/actions/cache.js.map +0 -1
  597. package/dist/actions/effects.js +0 -41
  598. package/dist/actions/effects.js.map +0 -1
  599. package/dist/actions/executor.js +0 -570
  600. package/dist/actions/executor.js.map +0 -1
  601. package/dist/actions/index.js +0 -13
  602. package/dist/actions/index.js.map +0 -1
  603. package/dist/actions/parsing.js.map +0 -1
  604. package/dist/actions/predicate-env.js +0 -27
  605. package/dist/actions/predicate-env.js.map +0 -1
  606. package/dist/actions/predicates.js +0 -49
  607. package/dist/actions/predicates.js.map +0 -1
  608. package/dist/actions/templating.js +0 -66
  609. package/dist/actions/templating.js.map +0 -1
  610. package/dist/actions/types.js +0 -15
  611. package/dist/actions/types.js.map +0 -1
  612. package/dist/agent/acp.js +0 -473
  613. package/dist/agent/acp.js.map +0 -1
  614. package/dist/agent/adapter-names.js +0 -159
  615. package/dist/agent/adapter-names.js.map +0 -1
  616. package/dist/agent/adapters.js +0 -511
  617. package/dist/agent/adapters.js.map +0 -1
  618. package/dist/agent/credential-extractors.js +0 -342
  619. package/dist/agent/credential-extractors.js.map +0 -1
  620. package/dist/agent/credential-secrets.js +0 -628
  621. package/dist/agent/credential-secrets.js.map +0 -1
  622. package/dist/agent/credential-ticker.js +0 -57
  623. package/dist/agent/credential-ticker.js.map +0 -1
  624. package/dist/agent/gondolin-creds-staging.js +0 -356
  625. package/dist/agent/gondolin-creds-staging.js.map +0 -1
  626. package/dist/agent/gondolin-dispatch.js +0 -375
  627. package/dist/agent/gondolin-dispatch.js.map +0 -1
  628. package/dist/agent/gondolin.js +0 -124
  629. package/dist/agent/gondolin.js.map +0 -1
  630. package/dist/agent/runner-decisions.js +0 -134
  631. package/dist/agent/runner-decisions.js.map +0 -1
  632. package/dist/agent/runner.js +0 -1456
  633. package/dist/agent/runner.js.map +0 -1
  634. package/dist/agent/tool-call-summary.js +0 -102
  635. package/dist/agent/tool-call-summary.js.map +0 -1
  636. package/dist/agent/vm-acp-mapping.js +0 -73
  637. package/dist/agent/vm-acp-mapping.js.map +0 -1
  638. package/dist/agent/vm-guards.js +0 -262
  639. package/dist/agent/vm-guards.js.map +0 -1
  640. package/dist/agent/vm-port.js +0 -22
  641. package/dist/agent/vm-port.js.map +0 -1
  642. package/dist/agent/vm-process-registry.js +0 -79
  643. package/dist/agent/vm-process-registry.js.map +0 -1
  644. package/dist/bin/cli-args.js +0 -105
  645. package/dist/bin/cli-args.js.map +0 -1
  646. package/dist/bin/symphony.js +0 -794
  647. package/dist/bin/symphony.js.map +0 -1
  648. package/dist/errors.js +0 -15
  649. package/dist/errors.js.map +0 -1
  650. package/dist/http-disk.js +0 -135
  651. package/dist/http-disk.js.map +0 -1
  652. package/dist/http-handlers.js.map +0 -1
  653. package/dist/http.js.map +0 -1
  654. package/dist/issues.js +0 -178
  655. package/dist/issues.js.map +0 -1
  656. package/dist/logging.js +0 -203
  657. package/dist/logging.js.map +0 -1
  658. package/dist/mcp.js +0 -706
  659. package/dist/mcp.js.map +0 -1
  660. package/dist/memory.js +0 -85
  661. package/dist/memory.js.map +0 -1
  662. package/dist/orchestrator-decisions.js +0 -331
  663. package/dist/orchestrator-decisions.js.map +0 -1
  664. package/dist/orchestrator.js +0 -1569
  665. package/dist/orchestrator.js.map +0 -1
  666. package/dist/prompt.js +0 -65
  667. package/dist/prompt.js.map +0 -1
  668. package/dist/reconciler/cache.js +0 -65
  669. package/dist/reconciler/cache.js.map +0 -1
  670. package/dist/reconciler/index.js +0 -448
  671. package/dist/reconciler/index.js.map +0 -1
  672. package/dist/reconciler/ledger.js +0 -131
  673. package/dist/reconciler/ledger.js.map +0 -1
  674. package/dist/reconciler/pr-adapters.js +0 -174
  675. package/dist/reconciler/pr-adapters.js.map +0 -1
  676. package/dist/reconciler/pr-decide.js.map +0 -1
  677. package/dist/reconciler/pr.js +0 -422
  678. package/dist/reconciler/pr.js.map +0 -1
  679. package/dist/reconciler/types.js +0 -12
  680. package/dist/reconciler/types.js.map +0 -1
  681. package/dist/reconciler/vm.js +0 -243
  682. package/dist/reconciler/vm.js.map +0 -1
  683. package/dist/reconciler/workspace-defaults.js +0 -83
  684. package/dist/reconciler/workspace-defaults.js.map +0 -1
  685. package/dist/reconciler/workspace.js +0 -272
  686. package/dist/reconciler/workspace.js.map +0 -1
  687. package/dist/runlog.js +0 -403
  688. package/dist/runlog.js.map +0 -1
  689. package/dist/scaffold.js +0 -165
  690. package/dist/scaffold.js.map +0 -1
  691. package/dist/trackers/local.js +0 -445
  692. package/dist/trackers/local.js.map +0 -1
  693. package/dist/trackers/types.js +0 -10
  694. package/dist/trackers/types.js.map +0 -1
  695. package/dist/types.js +0 -3
  696. package/dist/types.js.map +0 -1
  697. package/dist/util/clock.js +0 -12
  698. package/dist/util/clock.js.map +0 -1
  699. package/dist/util/crypto.js +0 -25
  700. package/dist/util/crypto.js.map +0 -1
  701. package/dist/util/frontmatter.js +0 -70
  702. package/dist/util/frontmatter.js.map +0 -1
  703. package/dist/util/fs-issues.js +0 -22
  704. package/dist/util/fs-issues.js.map +0 -1
  705. package/dist/util/process.js +0 -152
  706. package/dist/util/process.js.map +0 -1
  707. package/dist/util/workspace-key.js +0 -10
  708. package/dist/util/workspace-key.js.map +0 -1
  709. package/dist/workflow-loader.js +0 -147
  710. package/dist/workflow-loader.js.map +0 -1
  711. package/dist/workflow.js +0 -822
  712. package/dist/workflow.js.map +0 -1
  713. package/dist/workspace-types.js +0 -8
  714. package/dist/workspace-types.js.map +0 -1
  715. package/dist/workspace.js +0 -443
  716. package/dist/workspace.js.map +0 -1
@@ -0,0 +1,167 @@
1
+ // FCIS rewrite — the one-time OCI→gondolin image-build tracker (kind: shell helper, issue 206).
2
+ //
3
+ // Sliced out of the orchestrator loop (which sits at its max-lines budget) so the
4
+ // loop stays a thin rind. Owns the transient image-build state the poll gate + the
5
+ // dashboard banner read: the in-flight build, the last failed digest/ref (which
6
+ // suppresses an auto-retry storm), and a memoised read-only probe. The loop fires
7
+ // `ensure()` on the reducer's `ensure_image` effect and folds `readiness()` onto each
8
+ // snapshot; core decides whether to HOLD dispatch off that readiness.
9
+ //
10
+ // Readiness is DIGEST-based, not ref-string-based (issue 206 review rework): every
11
+ // `readiness()` re-runs the injected read-only `probeImage` to resolve the ref's
12
+ // CURRENT local digest + look up its cached selector. So a moving tag (`repo:latest`)
13
+ // whose underlying image changed reports not-ready until the new digest is
14
+ // reconverted — the tracker never reports "ready" off a ref string that succeeded
15
+ // once. The probe never pulls/builds (the build itself is the injected `ensureImage`).
16
+ //
17
+ // Shell rule: imports from src/types ONLY (no IO module — the conversion + probe are
18
+ // the injected `deps.ensureImage` / `deps.probeImage`, wired to the converter adapter).
19
+ export class ImageBuildTracker {
20
+ deps;
21
+ building = null;
22
+ // The last failed build, keyed by the resolved digest when known (else null = the
23
+ // pull/resolve itself failed, keyed by ref). Suppresses an auto-retry storm for THAT
24
+ // digest only — a moving tag that resolves to a NEW digest is retried (issue 206).
25
+ failure = null;
26
+ // The most recent read-only probe, refreshed by `readiness()`. `resolvedSelector`
27
+ // reads it so the selector dispatch boots agrees with the gate decision this tick.
28
+ lastProbe = null;
29
+ constructor(deps) {
30
+ this.deps = deps;
31
+ }
32
+ /**
33
+ * Project the live build state into the readiness the poll reducer gates on. Each
34
+ * call re-probes the ref's CURRENT digest + cached selector (cheap, read-only, no
35
+ * pull) so readiness tracks the live digest, not a ref string that converted once:
36
+ *
37
+ * - oci_image unset → auto-convert path inactive.
38
+ * - a build in flight → hold (don't probe mid-write); `building`.
39
+ * - a cached asset resolves for the current digest → `ready` (dispatch may boot it).
40
+ * - no cached asset → not ready; `failed` when THIS digest's last build failed
41
+ * (suppresses re-emit until the operator fixes the ref / the tag moves).
42
+ */
43
+ readiness(ref) {
44
+ const ociConfigured = ref !== null && ref.length > 0;
45
+ if (!ociConfigured) {
46
+ this.lastProbe = null;
47
+ // oci_image cleared/unset ⇒ the auto-convert path is off; drop any stale failure
48
+ // so the dashboard stops surfacing a banner for a ref no longer in play.
49
+ this.failure = null;
50
+ return { ociConfigured: false, ref: null, ready: false, building: this.building !== null, failed: false };
51
+ }
52
+ if (this.building !== null) {
53
+ // A build is in flight (the store is mid-write): hold without probing. The
54
+ // freshly-converted asset shows as ready on the NEXT tick once `ensure` clears.
55
+ return { ociConfigured: true, ref, ready: false, building: true, failed: false };
56
+ }
57
+ const probe = this.probe(ref);
58
+ this.lastProbe = probe;
59
+ const ready = probe.selector !== null;
60
+ const failed = !ready && this.matchesFailure(ref, probe.digest);
61
+ // A recorded failure that no longer gates THIS digest is superseded — the tag
62
+ // moved to a fresh digest (`failed` false, gate re-emits `ensure_image`) or the
63
+ // asset is now cached (`ready`). Drop it so the dashboard banner clears and the
64
+ // failure-suppression doesn't outlive the digest it was recorded for (issue 206).
65
+ if (this.failure !== null && !failed)
66
+ this.failure = null;
67
+ return { ociConfigured: true, ref, ready, building: false, failed };
68
+ }
69
+ /**
70
+ * The runtime image selector the converted asset boots with for the ref's CURRENT
71
+ * digest, or null when it isn't cached (not configured, not yet converted, or the
72
+ * tag moved to a digest that hasn't been reconverted). Dispatch threads this into
73
+ * `buildVmPlan` so a workflow that sets only `gondolin.oci_image` boots the cached
74
+ * asset instead of an empty `imagePath` (issue 206). Reads the readiness memo when
75
+ * it matches `ref` so it agrees with the same-tick gate decision; else re-probes.
76
+ */
77
+ resolvedSelector(ref) {
78
+ if (ref === null || ref.length === 0)
79
+ return null;
80
+ if (this.lastProbe !== null && this.lastProbe.ref === ref)
81
+ return this.lastProbe.selector;
82
+ return this.probe(ref).selector;
83
+ }
84
+ /** The in-flight build for the dashboard banner, or null. */
85
+ state() {
86
+ return this.building;
87
+ }
88
+ /**
89
+ * The last FAILED conversion for the dashboard banner, or null (issue 206 review).
90
+ * Surfaced ONLY while it still gates dispatch — the build threw, but the reconcile
91
+ * gate keeps holding VM dispatch and suppresses an auto-retry for this digest, so
92
+ * the operator needs a visible "why is nothing dispatching" answer. Returns null
93
+ * while a (re)build is in flight (the in-flight {@link state} banner wins) and once
94
+ * `readiness()` drops a superseded failure (tag moved / asset cached) or a
95
+ * successful `ensure()` clears it.
96
+ */
97
+ failureState() {
98
+ if (this.building !== null)
99
+ return null;
100
+ if (this.failure === null)
101
+ return null;
102
+ return { ref: this.failure.ref, since: this.failure.since, message: this.failure.message };
103
+ }
104
+ /** Drop the in-flight build state (shutdown). */
105
+ reset() {
106
+ this.building = null;
107
+ }
108
+ /**
109
+ * Run the one-time conversion off the tick. Idempotent against re-emits: a no-op
110
+ * while a build is already in flight (the readiness gate keeps a cached digest from
111
+ * re-emitting). On success clears any prior failure — the next tick's probe finds
112
+ * the freshly-tagged asset and reports ready; on failure records the error keyed by
113
+ * the resolved digest (so the gate stops re-emitting for THAT digest) and logs the
114
+ * remediation. The converter itself is idempotent (a cached digest is a cheap hit).
115
+ */
116
+ async ensure(ref) {
117
+ if (!this.deps.ensureImage)
118
+ return;
119
+ if (this.building !== null)
120
+ return;
121
+ this.building = { ref, since: this.deps.clock.iso() };
122
+ try {
123
+ await this.deps.ensureImage(ref);
124
+ this.failure = null;
125
+ }
126
+ catch (err) {
127
+ const message = err.message;
128
+ this.failure = { ref, digest: this.probe(ref).digest, message, since: this.deps.clock.iso() };
129
+ void this.deps.interpreter
130
+ .execute({
131
+ family: 'log',
132
+ kind: 'emit',
133
+ level: 'error',
134
+ message: 'one-time microVM image conversion failed; VM dispatch held',
135
+ fields: { oci_image: ref, error: message },
136
+ })
137
+ .catch(() => undefined);
138
+ }
139
+ finally {
140
+ this.building = null;
141
+ }
142
+ }
143
+ /** Read-only digest+cache probe via the injected adapter; null-safe (no probe wired,
144
+ * or the resolve threw ⇒ unresolvable, which the gate treats as not-ready). */
145
+ probe(ref) {
146
+ if (!this.deps.probeImage)
147
+ return { ref, digest: null, selector: null };
148
+ try {
149
+ const p = this.deps.probeImage(ref);
150
+ return { ref, digest: p.digest, selector: p.selector };
151
+ }
152
+ catch {
153
+ return { ref, digest: null, selector: null };
154
+ }
155
+ }
156
+ /** Whether the current (ref, digest) matches the recorded failure — suppressing an
157
+ * auto-retry storm for that exact digest (or the un-resolvable ref when pull failed). */
158
+ matchesFailure(ref, digest) {
159
+ const f = this.failure;
160
+ if (f === null)
161
+ return false;
162
+ if (f.digest !== null)
163
+ return digest === f.digest;
164
+ return f.ref === ref && digest === null;
165
+ }
166
+ }
167
+ //# sourceMappingURL=orchestrator-image.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orchestrator-image.js","sourceRoot":"","sources":["../../../src/shell/orchestrator-image.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,468 @@
1
+ // FCIS rewrite — the orchestrator poll-and-dispatch LOOP DRIVER (imperative shell).
2
+ //
3
+ // REF (read-only): src/orchestrator.ts — `tick()` / `dispatchSorted()` /
4
+ // `onWorkerExit()` / `onRetryTimer()` / `scheduleRetry()` / `maybeArmSleepCycle()`
5
+ // / `terminateRunning()` / `reportRuntimeEvent()` / `reportTurnStarted()`.
6
+ //
7
+ // This file owns the SHELL's mutable runtime state that core never touches:
8
+ // • `running` — live RunningEntry map (carries the `cancel` closure +
9
+ // the `active_tool_calls` Set — the two live handles core
10
+ // must not hold).
11
+ // • `claimed` — issue ids claimed for dispatch / pending retry.
12
+ // • `retryAttempts` — live retry-timer handles (TimerPort owns the actual fire).
13
+ // • circuit-breaker streaks + the sleep-cycle counter + the clamp flag.
14
+ //
15
+ // Each tick it MATERIALISES a plain-data snapshot from those live maps, calls the
16
+ // injected PURE reducer (`core/schedule/tick` — wired in by the composition root,
17
+ // since the shell may not import core), then APPLIES the returned next-state
18
+ // deltas to its live maps and DRAINS the returned Effect[] through the single
19
+ // EffectInterpreter. It also drives the imperative bits the reducer leaves to the
20
+ // shell: spawning the worker per DispatchPlan, and the success-gated sleep-cycle
21
+ // counter reset around the recurring-reflection mint.
22
+ //
23
+ // Async + IO live here. All scheduling DECISIONS live in core; this driver only
24
+ // moves data between the live maps, the reducer, and the interpreter. Imports
25
+ // from src/types ONLY (the reducer's shape is injected, kept structurally typed).
26
+ import { applyRuntimeEvent, applyTurnStarted, applyTokenUsage, detectStalledIssues } from './orchestrator-feedback.js';
27
+ import { reconcileRunningEntries } from './orchestrator-reconcile.js';
28
+ import { ImageBuildTracker } from './orchestrator-image.js';
29
+ // The stable poll-timer id + the sleep-cycle spawn actor are the CORE constants
30
+ // (`core/schedule/tick.ts:POLL_TIMER_ID` / `SPAWN_CYCLE_ACTOR`), injected on
31
+ // `deps` so the loop's `setTimer`/`effect.id ===`/`effect.actor ===` checks match
32
+ // the ids/actors core stamps on its emitted Effects — no hardcoded twin (§4b).
33
+ const RETRY_TIMER_PREFIX = 'retry:';
34
+ /**
35
+ * The imperative shell of the poll-and-dispatch tick. Holds the live maps + cancel
36
+ * closures + active_tool_calls Sets; folds runtime events through the injected pure
37
+ * reducer; drives the returned Effects through the interpreter.
38
+ */
39
+ export class OrchestratorLoop {
40
+ deps;
41
+ running = new Map();
42
+ claimed = new Set();
43
+ retryAttempts = new Map();
44
+ circuitBreakers = new Map();
45
+ doneSinceReflect = 0;
46
+ // Sleep-cycle mints in flight: a `write_issue` create the loop has STARTED but
47
+ // whose disk write hasn't settled yet. Surfaced on the snapshot so the reducer's
48
+ // `max_in_flight` guard counts an emitted-but-not-yet-landed mint (the on-disk
49
+ // candidate scan + running set can't see it) — without it a worker exit + poll
50
+ // racing between emit and landing could mint a SECOND reflection.
51
+ pendingSpawnMints = 0;
52
+ clampActive = false;
53
+ stopped = false;
54
+ lastPollAt = null;
55
+ // One-time OCI→gondolin image conversion state (issue 206) lives in a companion so
56
+ // the loop stays under its max-lines budget; the loop only fires + projects it.
57
+ imageTracker;
58
+ constructor(deps) {
59
+ this.deps = deps;
60
+ this.imageTracker = new ImageBuildTracker(deps);
61
+ }
62
+ /** Live read for the IntendedVmProvider / dashboard snapshot (shell-owned state). */
63
+ runningEntries() {
64
+ return [...this.running.values()];
65
+ }
66
+ retryEntries() {
67
+ return [...this.retryAttempts.values()];
68
+ }
69
+ claimedIds() {
70
+ return this.claimed;
71
+ }
72
+ lastPollAtIso() { return this.lastPollAt; }
73
+ /** Kick the first poll (REF: `start()` → `scheduleTick(0)`). */
74
+ start() {
75
+ this.deps.timers.setTimer(this.deps.pollTimerId, 0, () => void this.poll());
76
+ }
77
+ /** Tear down: cancel running, clear retry timers, drop claims (REF: `stop()`). */
78
+ stop() {
79
+ this.stopped = true;
80
+ this.deps.timers.clearTimer(this.deps.pollTimerId);
81
+ for (const e of this.retryAttempts.values()) {
82
+ this.deps.timers.clearTimer(RETRY_TIMER_PREFIX + e.issue_id);
83
+ }
84
+ this.retryAttempts.clear();
85
+ for (const e of this.running.values())
86
+ e.cancel();
87
+ this.running.clear();
88
+ this.claimed.clear();
89
+ this.circuitBreakers.clear();
90
+ this.imageTracker.reset();
91
+ }
92
+ /** The single operator-event write door (REF: the `triggerRefresh` side-door, now an event): build the snapshot, fold via the reducer, apply deltas + drain its Effect[] — same public pattern as {@link onWorkerExit}. An `operator_nudge` re-arms the poll at 0ms, coalescing an immediate poll exactly as `triggerRefresh` did. */
93
+ dispatch(event) {
94
+ if (this.stopped)
95
+ return;
96
+ this.applyResult(this.deps.tick(event, this.buildSnapshot()));
97
+ }
98
+ /** Build the plain-data snapshot the reducer folds over (no live handles leak). */
99
+ buildSnapshot() {
100
+ const cfg = this.deps.readConfig();
101
+ return {
102
+ cfg,
103
+ running: [...this.running.values()].map((e) => ({
104
+ issue_id: e.issue_id,
105
+ state: e.issue.state,
106
+ })),
107
+ claimed: this.claimed,
108
+ retries: [...this.retryAttempts.values()].map((r) => ({
109
+ issue_id: r.issue_id,
110
+ kind: r.kind,
111
+ target_state: r.target_state,
112
+ })),
113
+ circuitBreakers: this.circuitBreakers,
114
+ doneSinceReflect: this.doneSinceReflect,
115
+ pendingSpawnMints: this.pendingSpawnMints,
116
+ previousClampActive: this.clampActive,
117
+ memoryProbe: { memAvailableMib: this.deps.memProbe.read() },
118
+ now: this.deps.clock.now(),
119
+ nowIso: this.deps.clock.iso(),
120
+ // oci_image OR the managed prebuilt-image ref (issue 224) — same tracker path.
121
+ image: this.imageTracker.readiness(cfg.ociImage ?? cfg.managedImageRef ?? null),
122
+ };
123
+ }
124
+ /** Live reads for the dashboard's one-time-build banners (shell-owned state): the
125
+ * in-flight build, and the FAILED build that keeps dispatch held (issue 206). */
126
+ imageBuildState() { return this.imageTracker.state(); }
127
+ imageFailureState() { return this.imageTracker.failureState(); }
128
+ /**
129
+ * The runtime image selector the asset auto-converted from `gondolin.oci_image`
130
+ * boots with (issue 206), or null when inactive / not yet ready. The runner threads
131
+ * it into `buildVmPlan` so a workflow that sets only `oci_image` boots the cached
132
+ * asset; core lets the `gondolin.image` escape hatch win when it is also set.
133
+ */
134
+ // oci_image OR the managed prebuilt-image ref (issue 224); same resolved-selector path.
135
+ effectiveImageSelector() { const c = this.deps.readConfig(); return this.imageTracker.resolvedSelector(c.ociImage ?? c.managedImageRef ?? null); }
136
+ // ── poll tick (REF: tick()) ────────────────────────────────────────────────
137
+ async poll() {
138
+ if (this.stopped)
139
+ return;
140
+ // Stall reconcile runs every poll BEFORE dispatch (REF: reconcile()) so a
141
+ // wedged running entry frees its slot before fresh candidates are admitted.
142
+ this.reconcileStalls();
143
+ // Cancel-on-move safety net (issue 211): reconcile every LIVE running entry against
144
+ // its FRESH tracker state, so a disk `mv`/external move out of an active state
145
+ // cancels the in-flight attempt even when no dashboard move-event fired (the shared
146
+ // decider keeps it from drifting from the operator-move path). Runs before dispatch
147
+ // so a terminated entry frees its slot this same poll.
148
+ await reconcileRunningEntries(this.running, this.deps, (id, cleanup) => this.terminateRunning(id, cleanup));
149
+ const fetched = await this.deps.fetchCandidates();
150
+ if (!fetched) {
151
+ // Tracker miss — reschedule the backstop poll and bail (REF: fetch error path).
152
+ this.deps.timers.setTimer(this.deps.pollTimerId, this.deps.readConfig().pollIntervalMs, () => void this.poll());
153
+ return;
154
+ }
155
+ const result = this.deps.tick({ kind: 'poll', candidates: fetched.issues, trackerRoot: fetched.root }, this.buildSnapshot());
156
+ this.applyResult(result);
157
+ }
158
+ /**
159
+ * Worker exit (REF: onWorkerExit). Removing the exited entry from `running` is
160
+ * part of applying the exit: drop it FIRST (so the snapshot the reducer folds
161
+ * over — and thus its slot accounting — excludes it), then fold the exit through
162
+ * core for the breaker + retry/continuation decision. The caller hands us the
163
+ * exit event and nothing else; it no longer pre-pulls the entry via a separate
164
+ * `removeRunning` poke.
165
+ */
166
+ onWorkerExit(event) {
167
+ this.running.delete(event.issueId);
168
+ if (this.stopped) {
169
+ // stop() raced the unwind: drop the claim, do not arm a new retry (REF guard).
170
+ this.claimed.delete(event.issueId);
171
+ this.circuitBreakers.delete(event.issueId);
172
+ return;
173
+ }
174
+ this.applyResult(this.deps.tick(event, this.buildSnapshot()));
175
+ }
176
+ /** Retry timer firing (REF: onRetryTimer) — re-poll, re-check, redispatch/release. */
177
+ async onRetryTimer(issueId) {
178
+ if (this.stopped)
179
+ return;
180
+ const entry = this.retryAttempts.get(issueId);
181
+ if (!entry)
182
+ return;
183
+ this.retryAttempts.delete(issueId);
184
+ const fetched = await this.deps.fetchCandidates();
185
+ if (!fetched) {
186
+ // Re-poll failed — reschedule a failure backoff for THIS issue (REF: fetchRetryCandidates).
187
+ // The delay comes from the INJECTED core `backoffDelayMs` (pinned at the core
188
+ // FAILURE_BASE_MS) so this poll-failure backoff can't diverge from the normal-
189
+ // failure backoff core schedules for the same attempt (§4a). The attempt being
190
+ // scheduled is `entry.attempt + 1`.
191
+ const cfg = this.deps.readConfig();
192
+ const delayMs = this.deps.backoff(entry.attempt + 1, cfg.maxRetryBackoffMs);
193
+ this.scheduleRetry({
194
+ issueId,
195
+ identifier: entry.identifier,
196
+ plan: {
197
+ attempt: entry.attempt + 1,
198
+ delayMs,
199
+ error: 'retry poll failed',
200
+ kind: 'failure',
201
+ target_state: entry.target_state,
202
+ },
203
+ });
204
+ return;
205
+ }
206
+ this.applyResult(this.deps.tick({
207
+ kind: 'retry_timer',
208
+ issueId,
209
+ identifier: entry.identifier,
210
+ attempt: entry.attempt,
211
+ targetState: entry.target_state,
212
+ candidates: fetched.issues,
213
+ trackerRoot: fetched.root,
214
+ }, this.buildSnapshot()));
215
+ }
216
+ // ── apply the reducer's next-state deltas + drain its effects ───────────────
217
+ applyResult(result) {
218
+ // Snapshot the counter BEFORE the optimistic reset below, so the guarded mint
219
+ // can restore it if the create fails (the reset delta is applied at step 1).
220
+ const priorDoneSinceReflect = this.doneSinceReflect;
221
+ // 1. Persisted scalar deltas (REF: the orchestrator's instance-field writes).
222
+ if (result.lastPollAt !== undefined)
223
+ this.lastPollAt = result.lastPollAt;
224
+ if (result.clampActive !== undefined)
225
+ this.clampActive = result.clampActive;
226
+ if (result.doneSinceReflect !== undefined)
227
+ this.doneSinceReflect = result.doneSinceReflect;
228
+ if (result.breaker) {
229
+ if (result.breaker.op === 'set') {
230
+ this.circuitBreakers.set(result.breaker.issueId, result.breaker.state);
231
+ }
232
+ else {
233
+ this.circuitBreakers.delete(result.breaker.issueId);
234
+ }
235
+ }
236
+ // 2. Claims first so a dispatched issue is claimed before its worker spawns.
237
+ for (const id of result.claim)
238
+ this.claimed.add(id);
239
+ for (const id of result.release)
240
+ this.releaseClaim(id);
241
+ // 3. Spawn the workers for each dispatch plan (REF: dispatchIssue → runWorker).
242
+ for (const plan of result.dispatch)
243
+ this.dispatchPlan(plan);
244
+ // 4. Drain the side effects through the single interpreter seam.
245
+ for (const effect of result.effects)
246
+ this.executeEffect(effect, priorDoneSinceReflect);
247
+ }
248
+ /**
249
+ * Execute one effect. Timer/schedule_retry effects are wired to the live timer
250
+ * port HERE (they carry the loop's own callbacks); everything else goes straight
251
+ * to the central interpreter (REF: scheduleTick / scheduleRetry use the live
252
+ * setTimeout, all other IO routes through the adapters).
253
+ */
254
+ executeEffect(effect, priorDoneSinceReflect) {
255
+ // Timer/schedule_retry effects carry the loop's own callbacks — handled in a
256
+ // sibling method so this dispatcher stays under the shell complexity budget.
257
+ if (effect.family === 'timer') {
258
+ this.executeTimerEffect(effect);
259
+ return;
260
+ }
261
+ // worker.cancel is the ONE effect the loop interprets itself: the reducer
262
+ // decided to cancel a live attempt (operator move — issue 199) but core holds
263
+ // no live handles, so it emitted the decision and we perform it here via our
264
+ // OWN terminateRunning — the same path the stall reaper drives. cleanupWorkspace
265
+ // carries the reducer's terminal-vs-active cleanup decision.
266
+ if (effect.family === 'worker' && effect.kind === 'cancel') {
267
+ this.terminateRunning(effect.issueId, effect.cleanupWorkspace);
268
+ return;
269
+ }
270
+ // image.ensure is the other effect the loop interprets itself (issue 206): the
271
+ // reducer decided to convert + cache `gondolin.oci_image`, but the in-flight
272
+ // build state + the tracked async are shell-owned (core holds no live handles).
273
+ // The tracker runs the converter off the tick so the poll loop never blocks; the
274
+ // next poll's snapshot reads the updated readiness.
275
+ if (effect.family === 'image' && effect.kind === 'ensure')
276
+ return void this.imageTracker.ensure(effect.ref);
277
+ // tracker.write_issue from the sleep-cycle spawn needs the success-gated counter
278
+ // reset: core optimistically reset the counter to 0 this tick, so restore the
279
+ // prior value if the mint fails. Route it through the guarded mint path instead
280
+ // of firing it blind.
281
+ if (effect.family === 'tracker' &&
282
+ effect.kind === 'write_issue' &&
283
+ effect.actor === this.deps.spawnActor) {
284
+ void this.spawnReflection(effect, priorDoneSinceReflect);
285
+ return;
286
+ }
287
+ // Everything else: fire-and-forget through the central interpreter. Failures are
288
+ // surfaced via the interpreter's own EffectResult; the loop does not re-decide on
289
+ // log/runlog/move effects here (REF: those were best-effort side calls).
290
+ void this.deps.interpreter.execute(effect).catch(() => undefined);
291
+ }
292
+ /**
293
+ * Interpret a timer-family effect: the named timer's `fire` callback is the loop's
294
+ * OWN (the poll re-arm runs `poll()`, schedule_retry arms the retry timer), so the
295
+ * loop wires them here rather than routing through the central interpreter (REF:
296
+ * scheduleTick / scheduleRetry used the live setTimeout).
297
+ */
298
+ executeTimerEffect(effect) {
299
+ switch (effect.kind) {
300
+ case 'set_timer':
301
+ if (effect.id === this.deps.pollTimerId) {
302
+ this.deps.timers.setTimer(this.deps.pollTimerId, effect.ms, () => void this.poll());
303
+ }
304
+ else {
305
+ this.deps.timers.setTimer(effect.id, effect.ms, () => undefined);
306
+ }
307
+ return;
308
+ case 'clear_timer':
309
+ this.deps.timers.clearTimer(effect.id);
310
+ return;
311
+ case 'schedule_retry':
312
+ this.scheduleRetry({ issueId: effect.issueId, identifier: effect.identifier, plan: effect.plan });
313
+ return;
314
+ case 'sleep':
315
+ // No loop-driver sleep effects today; route to the interpreter for completeness.
316
+ return void this.deps.interpreter.execute(effect);
317
+ }
318
+ }
319
+ /**
320
+ * Mint the recurring reflection issue under the success-gated counter reset.
321
+ * Core already emitted the create effect + optimistically reset the counter to 0
322
+ * this tick (so a poll that fires the create can't re-fire it before the write
323
+ * lands). If the mint fails we RESTORE the counter to its PRE-reset value
324
+ * (`priorDoneSinceReflect`, captured before applyResult applied the reset) so a
325
+ * failed spawn doesn't silently discard the accumulated terminal count.
326
+ *
327
+ * The in-flight guard the immortal-ticket latch used to provide now lives in
328
+ * core's `max_in_flight` check — but that check counts issues VISIBLE in the
329
+ * on-disk candidate scan + the running set, and a write that's been emitted but
330
+ * not yet landed is in neither. So we bump `pendingSpawnMints` for the lifetime of
331
+ * the async write (it rides onto the next snapshot's in-flight count) and drop it
332
+ * once the write settles: a worker exit + poll racing between emit and landing then
333
+ * still sees the mint and can't slip a second one past `max_in_flight`.
334
+ */
335
+ async spawnReflection(createEffect, priorDoneSinceReflect) {
336
+ // Make the in-flight mint visible to the reducer's max_in_flight guard the moment
337
+ // the create is started (this runs synchronously within applyResult, before the
338
+ // next poll can build a snapshot), and drop it once the write settles.
339
+ this.pendingSpawnMints++;
340
+ try {
341
+ const r = await this.deps.interpreter.execute(createEffect);
342
+ if ('ok' in r && r.ok === false) {
343
+ // Mint declined — undo the optimistic reset core applied this tick.
344
+ this.doneSinceReflect = priorDoneSinceReflect;
345
+ }
346
+ }
347
+ catch {
348
+ this.doneSinceReflect = priorDoneSinceReflect;
349
+ }
350
+ finally {
351
+ this.pendingSpawnMints--;
352
+ }
353
+ }
354
+ // ── dispatch one plan: claim + build entry + spawn the worker ───────────────
355
+ dispatchPlan(plan) {
356
+ if (this.running.has(plan.issue.id))
357
+ return;
358
+ this.claimed.add(plan.issue.id);
359
+ // Replace any pending retry (its timer is cleared) — this dispatch supersedes it.
360
+ const existingRetry = this.retryAttempts.get(plan.issue.id);
361
+ if (existingRetry) {
362
+ this.deps.timers.clearTimer(RETRY_TIMER_PREFIX + plan.issue.id);
363
+ this.retryAttempts.delete(plan.issue.id);
364
+ }
365
+ const cancel = { cancelled: false };
366
+ const entry = this.deps.buildEntry(plan, () => {
367
+ cancel.cancelled = true;
368
+ });
369
+ this.running.set(plan.issue.id, entry);
370
+ void this.deps
371
+ .runWorker(plan, entry, cancel)
372
+ .catch(() => undefined)
373
+ .finally(() => {
374
+ // The worker fully unwound; the composition root is responsible for calling
375
+ // back into onWorkerExit with the attempt outcome. onWorkerExit removes the
376
+ // entry itself (so the reducer's slot accounting excludes it). Nothing to do
377
+ // in the happy path here — this .finally only backstops a thrown runWorker so
378
+ // a stuck claim can't leak if the caller forgot to report the exit.
379
+ if (this.running.get(plan.issue.id) === entry)
380
+ this.running.delete(plan.issue.id);
381
+ });
382
+ }
383
+ /**
384
+ * Arm a retry/continuation timer (REF: scheduleRetry). Clears any prior timer for
385
+ * the issue, records the live RetryEntry, claims the issue (so the eligibility gate
386
+ * still blocks a fresh dispatch during the backoff), and fires onRetryTimer.
387
+ */
388
+ scheduleRetry(input) {
389
+ if (this.stopped)
390
+ return;
391
+ const existing = this.retryAttempts.get(input.issueId);
392
+ if (existing)
393
+ this.deps.timers.clearTimer(RETRY_TIMER_PREFIX + input.issueId);
394
+ const dueAt = this.deps.clock.now() + input.plan.delayMs;
395
+ this.retryAttempts.set(input.issueId, {
396
+ issue_id: input.issueId,
397
+ identifier: input.identifier,
398
+ attempt: input.plan.attempt,
399
+ due_at_ms: dueAt,
400
+ timer_handle: null,
401
+ error: input.plan.error,
402
+ kind: input.plan.kind,
403
+ target_state: input.plan.target_state,
404
+ });
405
+ this.claimed.add(input.issueId);
406
+ this.deps.timers.setTimer(RETRY_TIMER_PREFIX + input.issueId, input.plan.delayMs, () => void this.onRetryTimer(input.issueId));
407
+ }
408
+ /** Release a claim + drop the breaker streak (REF: releaseRetryClaim / claimed.delete). */
409
+ releaseClaim(issueId) {
410
+ this.claimed.delete(issueId);
411
+ this.circuitBreakers.delete(issueId);
412
+ const retry = this.retryAttempts.get(issueId);
413
+ if (retry) {
414
+ this.deps.timers.clearTimer(RETRY_TIMER_PREFIX + issueId);
415
+ this.retryAttempts.delete(issueId);
416
+ }
417
+ }
418
+ // ── runner feedback the shell mutates directly on the live entry ─────────────
419
+ // (REF: reportRuntimeEvent / reportTurnStarted — the active_tool_calls Set is a
420
+ // live shell handle core never holds, so the loop applies the PURE core folds
421
+ // — core/schedule/token-fold, injected — and writes the result back here.)
422
+ /** REF: reportRuntimeEvent — track in-flight tool calls (stall-reaper suppression). */
423
+ reportRuntimeEvent(issueId, ev) {
424
+ const e = this.running.get(issueId);
425
+ if (e)
426
+ applyRuntimeEvent(e, ev, this.deps.tokenFold);
427
+ }
428
+ /** REF: reportTurnStarted — turn boundary clears the in-flight tool set. */
429
+ reportTurnStarted(issueId, turnNumber) {
430
+ const e = this.running.get(issueId);
431
+ if (e)
432
+ applyTurnStarted(e, turnNumber, this.deps.tokenFold);
433
+ }
434
+ /** REF: reportTokenUsage — fold absolute ACP token totals onto the live entry (§2c). */
435
+ reportTokenUsage(issueId, usage) {
436
+ const e = this.running.get(issueId);
437
+ if (e)
438
+ applyTokenUsage(e, usage, this.deps.tokenFold);
439
+ }
440
+ /** REF: detectStalls — terminate every running entry the core stall reaper flags (§2c). */
441
+ reconcileStalls() {
442
+ for (const issueId of detectStalledIssues(this.running, this.deps.stall, this.deps.clock.now())) {
443
+ this.terminateRunning(issueId, false);
444
+ }
445
+ }
446
+ /**
447
+ * Cancel a running attempt out of band (REF: terminateRunning — stall reaper /
448
+ * reconcile terminate; operator move via the reducer's `worker.cancel` effect).
449
+ * Sets the optional workspace-cleanup flag, fires the live cancel closure; the
450
+ * worker unwinds and reports its exit through onWorkerExit. Internal-only now —
451
+ * the move-time cancel is driven by the reducer through `executeEffect`, not an
452
+ * external poke (issue 199), so a pending retry's move-time release rides the
453
+ * reducer's `release` delta (→ releaseClaim) instead of a sibling mutator.
454
+ */
455
+ terminateRunning(issueId, cleanupWorkspace) {
456
+ const entry = this.running.get(issueId);
457
+ if (!entry)
458
+ return;
459
+ if (cleanupWorkspace)
460
+ entry.cleanup_workspace_on_exit = true;
461
+ entry.cancel();
462
+ }
463
+ /** Lookup a live running entry (dashboard detail / reconcile state refresh). */
464
+ getRunning(issueId) {
465
+ return this.running.get(issueId);
466
+ }
467
+ }
468
+ //# sourceMappingURL=orchestrator-loop.js.map