smol-symphony 0.2.0 → 0.3.1

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 (540) 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/bin/symphony.js +22 -786
  11. package/dist/bin/symphony.js.map +1 -1
  12. package/dist/core/actions/context.js +109 -0
  13. package/dist/core/actions/context.js.map +1 -0
  14. package/dist/{actions/parsing.js → core/actions/parse.js} +33 -114
  15. package/dist/core/actions/parse.js.map +1 -0
  16. package/dist/core/actions/plan.js +197 -0
  17. package/dist/core/actions/plan.js.map +1 -0
  18. package/dist/core/actions/predicates.js +111 -0
  19. package/dist/core/actions/predicates.js.map +1 -0
  20. package/dist/core/actions/run-fold.js +248 -0
  21. package/dist/core/actions/run-fold.js.map +1 -0
  22. package/dist/core/actions/template.js +118 -0
  23. package/dist/core/actions/template.js.map +1 -0
  24. package/dist/core/cli/args.js +116 -0
  25. package/dist/core/cli/args.js.map +1 -0
  26. package/dist/core/coerce.js +75 -0
  27. package/dist/core/coerce.js.map +1 -0
  28. package/dist/core/credential/account-id.js +20 -0
  29. package/dist/core/credential/account-id.js.map +1 -0
  30. package/dist/core/credential/adapter-config.js +136 -0
  31. package/dist/core/credential/adapter-config.js.map +1 -0
  32. package/dist/core/credential/availability.js +98 -0
  33. package/dist/core/credential/availability.js.map +1 -0
  34. package/dist/core/credential/extract.js +228 -0
  35. package/dist/core/credential/extract.js.map +1 -0
  36. package/dist/core/credential/fake-creds.js +171 -0
  37. package/dist/core/credential/fake-creds.js.map +1 -0
  38. package/dist/core/credential/identity.js +125 -0
  39. package/dist/core/credential/identity.js.map +1 -0
  40. package/dist/core/credential/shape.js +230 -0
  41. package/dist/core/credential/shape.js.map +1 -0
  42. package/dist/core/credential/strings.js +15 -0
  43. package/dist/core/credential/strings.js.map +1 -0
  44. package/dist/core/doctor/checks.js +303 -0
  45. package/dist/core/doctor/checks.js.map +1 -0
  46. package/dist/core/git/result.js +107 -0
  47. package/dist/core/git/result.js.map +1 -0
  48. package/dist/core/http/decisions.js +225 -0
  49. package/dist/core/http/decisions.js.map +1 -0
  50. package/dist/{http.js → core/http/render.js} +472 -738
  51. package/dist/core/http/render.js.map +1 -0
  52. package/dist/{http-handlers.js → core/http/routes.js} +52 -87
  53. package/dist/core/http/routes.js.map +1 -0
  54. package/dist/core/http/views.js +181 -0
  55. package/dist/core/http/views.js.map +1 -0
  56. package/dist/core/image/managed-image.js +95 -0
  57. package/dist/core/image/managed-image.js.map +1 -0
  58. package/dist/core/issue/file.js +149 -0
  59. package/dist/core/issue/file.js.map +1 -0
  60. package/dist/core/issue/parse.js +210 -0
  61. package/dist/core/issue/parse.js.map +1 -0
  62. package/dist/core/mcp/dispatch.js +239 -0
  63. package/dist/core/mcp/dispatch.js.map +1 -0
  64. package/dist/core/mcp/post-move.js +92 -0
  65. package/dist/core/mcp/post-move.js.map +1 -0
  66. package/dist/core/mcp/protocol.js +293 -0
  67. package/dist/core/mcp/protocol.js.map +1 -0
  68. package/dist/core/mcp/url.js +162 -0
  69. package/dist/core/mcp/url.js.map +1 -0
  70. package/dist/core/path.js +63 -0
  71. package/dist/core/path.js.map +1 -0
  72. package/dist/core/reconcile/image-decide.js +48 -0
  73. package/dist/core/reconcile/image-decide.js.map +1 -0
  74. package/dist/core/reconcile/ledger.js +142 -0
  75. package/dist/core/reconcile/ledger.js.map +1 -0
  76. package/dist/core/reconcile/pr-classify.js +62 -0
  77. package/dist/core/reconcile/pr-classify.js.map +1 -0
  78. package/dist/{reconciler → core/reconcile}/pr-decide.js +25 -12
  79. package/dist/core/reconcile/pr-decide.js.map +1 -0
  80. package/dist/core/reconcile/pr-loop.js +161 -0
  81. package/dist/core/reconcile/pr-loop.js.map +1 -0
  82. package/dist/core/reconcile/pr-notes.js +35 -0
  83. package/dist/core/reconcile/pr-notes.js.map +1 -0
  84. package/dist/core/reconcile/vm-decide.js +70 -0
  85. package/dist/core/reconcile/vm-decide.js.map +1 -0
  86. package/dist/core/reconcile/vm-reap.js +207 -0
  87. package/dist/core/reconcile/vm-reap.js.map +1 -0
  88. package/dist/core/reconcile/workspace-decide.js +162 -0
  89. package/dist/core/reconcile/workspace-decide.js.map +1 -0
  90. package/dist/core/runlog/summary.js +231 -0
  91. package/dist/core/runlog/summary.js.map +1 -0
  92. package/dist/core/runner/dispatch-config.js +95 -0
  93. package/dist/core/runner/dispatch-config.js.map +1 -0
  94. package/dist/core/runner/injection.js +61 -0
  95. package/dist/core/runner/injection.js.map +1 -0
  96. package/dist/core/runner/mise.js +210 -0
  97. package/dist/core/runner/mise.js.map +1 -0
  98. package/dist/core/runner/prompt.js +720 -0
  99. package/dist/core/runner/prompt.js.map +1 -0
  100. package/dist/core/runner/turn.js +242 -0
  101. package/dist/core/runner/turn.js.map +1 -0
  102. package/dist/core/runner/vm-plan.js +390 -0
  103. package/dist/core/runner/vm-plan.js.map +1 -0
  104. package/dist/core/schedule/admission.js +123 -0
  105. package/dist/core/schedule/admission.js.map +1 -0
  106. package/dist/core/schedule/circuit-breaker.js +111 -0
  107. package/dist/core/schedule/circuit-breaker.js.map +1 -0
  108. package/dist/core/schedule/eligibility.js +83 -0
  109. package/dist/core/schedule/eligibility.js.map +1 -0
  110. package/dist/core/schedule/reconcile-issue.js +82 -0
  111. package/dist/core/schedule/reconcile-issue.js.map +1 -0
  112. package/dist/core/schedule/retry.js +96 -0
  113. package/dist/core/schedule/retry.js.map +1 -0
  114. package/dist/core/schedule/sleep-cycle.js +133 -0
  115. package/dist/core/schedule/sleep-cycle.js.map +1 -0
  116. package/dist/core/schedule/slots.js +124 -0
  117. package/dist/core/schedule/slots.js.map +1 -0
  118. package/dist/core/schedule/tick.js +553 -0
  119. package/dist/core/schedule/tick.js.map +1 -0
  120. package/dist/core/schedule/token-fold.js +181 -0
  121. package/dist/core/schedule/token-fold.js.map +1 -0
  122. package/dist/core/state-resolve.js +86 -0
  123. package/dist/core/state-resolve.js.map +1 -0
  124. package/dist/core/vm-guards.js +278 -0
  125. package/dist/core/vm-guards.js.map +1 -0
  126. package/dist/core/workflow/derive.js +107 -0
  127. package/dist/core/workflow/derive.js.map +1 -0
  128. package/dist/core/workflow/parse.js +687 -0
  129. package/dist/core/workflow/parse.js.map +1 -0
  130. package/dist/core/workflow/prompt-probe.js +78 -0
  131. package/dist/core/workflow/prompt-probe.js.map +1 -0
  132. package/dist/core/workflow/validate.js +189 -0
  133. package/dist/core/workflow/validate.js.map +1 -0
  134. package/dist/core/workspace-key.js +19 -0
  135. package/dist/core/workspace-key.js.map +1 -0
  136. package/dist/shell/actions-runner.js +356 -0
  137. package/dist/shell/actions-runner.js.map +1 -0
  138. package/dist/shell/adapter/adapter-registry.js +45 -0
  139. package/dist/shell/adapter/adapter-registry.js.map +1 -0
  140. package/dist/shell/adapter/clock-random.js +96 -0
  141. package/dist/shell/adapter/clock-random.js.map +1 -0
  142. package/dist/shell/adapter/gondolin-dispatch-helpers.js +158 -0
  143. package/dist/shell/adapter/gondolin-dispatch-helpers.js.map +1 -0
  144. package/dist/shell/adapter/gondolin-dispatch.js +385 -0
  145. package/dist/shell/adapter/gondolin-dispatch.js.map +1 -0
  146. package/dist/shell/adapter/gondolin-image-converter.js +233 -0
  147. package/dist/shell/adapter/gondolin-image-converter.js.map +1 -0
  148. package/dist/shell/adapter/gondolin-image-fetch.js +180 -0
  149. package/dist/shell/adapter/gondolin-image-fetch.js.map +1 -0
  150. package/dist/shell/adapter/launcher-asset.js +57 -0
  151. package/dist/shell/adapter/launcher-asset.js.map +1 -0
  152. package/dist/shell/adapter/mise-config-asset.js +65 -0
  153. package/dist/shell/adapter/mise-config-asset.js.map +1 -0
  154. package/dist/shell/adapter/workflow-loader.js +304 -0
  155. package/dist/shell/adapter/workflow-loader.js.map +1 -0
  156. package/dist/shell/cli/doctor.js +268 -0
  157. package/dist/shell/cli/doctor.js.map +1 -0
  158. package/dist/shell/effect-interpreter-families.js +314 -0
  159. package/dist/shell/effect-interpreter-families.js.map +1 -0
  160. package/dist/shell/effect-interpreter.js +29 -0
  161. package/dist/shell/effect-interpreter.js.map +1 -0
  162. package/dist/shell/interp/acp-frame.js +137 -0
  163. package/dist/shell/interp/acp-frame.js.map +1 -0
  164. package/dist/shell/interp/acp-ws-conn.js +320 -0
  165. package/dist/shell/interp/acp-ws-conn.js.map +1 -0
  166. package/dist/shell/interp/acp-ws-frames.js +159 -0
  167. package/dist/shell/interp/acp-ws-frames.js.map +1 -0
  168. package/dist/shell/interp/acp-ws.js +197 -0
  169. package/dist/shell/interp/acp-ws.js.map +1 -0
  170. package/dist/shell/interp/acp.js +319 -0
  171. package/dist/shell/interp/acp.js.map +1 -0
  172. package/dist/shell/interp/credential-defaults.js +128 -0
  173. package/dist/shell/interp/credential-defaults.js.map +1 -0
  174. package/dist/shell/interp/credential-hooks.js +149 -0
  175. package/dist/shell/interp/credential-hooks.js.map +1 -0
  176. package/dist/shell/interp/credential-registry.js +226 -0
  177. package/dist/shell/interp/credential-registry.js.map +1 -0
  178. package/dist/shell/interp/credential.js +103 -0
  179. package/dist/shell/interp/credential.js.map +1 -0
  180. package/dist/shell/interp/gh.js +163 -0
  181. package/dist/shell/interp/gh.js.map +1 -0
  182. package/dist/shell/interp/git.js +28 -0
  183. package/dist/shell/interp/git.js.map +1 -0
  184. package/dist/shell/interp/log.js +213 -0
  185. package/dist/shell/interp/log.js.map +1 -0
  186. package/dist/shell/interp/process.js +178 -0
  187. package/dist/shell/interp/process.js.map +1 -0
  188. package/dist/shell/interp/runlog.js +193 -0
  189. package/dist/shell/interp/runlog.js.map +1 -0
  190. package/dist/shell/interp/timer.js +64 -0
  191. package/dist/shell/interp/timer.js.map +1 -0
  192. package/dist/shell/interp/tracker-disk.js +99 -0
  193. package/dist/shell/interp/tracker-disk.js.map +1 -0
  194. package/dist/shell/interp/tracker-parse.js +71 -0
  195. package/dist/shell/interp/tracker-parse.js.map +1 -0
  196. package/dist/shell/interp/tracker-scan.js +238 -0
  197. package/dist/shell/interp/tracker-scan.js.map +1 -0
  198. package/dist/shell/interp/tracker-write.js +91 -0
  199. package/dist/shell/interp/tracker-write.js.map +1 -0
  200. package/dist/shell/interp/tracker.js +41 -0
  201. package/dist/shell/interp/tracker.js.map +1 -0
  202. package/dist/shell/interp/tty.js +48 -0
  203. package/dist/shell/interp/tty.js.map +1 -0
  204. package/dist/shell/interp/vm.js +199 -0
  205. package/dist/shell/interp/vm.js.map +1 -0
  206. package/dist/shell/interp/workspace.js +310 -0
  207. package/dist/shell/interp/workspace.js.map +1 -0
  208. package/dist/shell/main-acp.js +78 -0
  209. package/dist/shell/main-acp.js.map +1 -0
  210. package/dist/shell/main-adapters.js +222 -0
  211. package/dist/shell/main-adapters.js.map +1 -0
  212. package/dist/shell/main-credential.js +122 -0
  213. package/dist/shell/main-credential.js.map +1 -0
  214. package/dist/shell/main-doctor.js +22 -0
  215. package/dist/shell/main-doctor.js.map +1 -0
  216. package/dist/shell/main-entry.js +46 -0
  217. package/dist/shell/main-entry.js.map +1 -0
  218. package/dist/shell/main-http-csrf.js +45 -0
  219. package/dist/shell/main-http-csrf.js.map +1 -0
  220. package/dist/shell/main-http-handler.js +389 -0
  221. package/dist/shell/main-http-handler.js.map +1 -0
  222. package/dist/shell/main-http-mcp.js +122 -0
  223. package/dist/shell/main-http-mcp.js.map +1 -0
  224. package/dist/shell/main-http-views.js +253 -0
  225. package/dist/shell/main-http-views.js.map +1 -0
  226. package/dist/shell/main-http.js +76 -0
  227. package/dist/shell/main-http.js.map +1 -0
  228. package/dist/shell/main-loops.js +130 -0
  229. package/dist/shell/main-loops.js.map +1 -0
  230. package/dist/shell/main-mcp.js +129 -0
  231. package/dist/shell/main-mcp.js.map +1 -0
  232. package/dist/shell/main-orchestrator.js +120 -0
  233. package/dist/shell/main-orchestrator.js.map +1 -0
  234. package/dist/shell/main-preflight.js +43 -0
  235. package/dist/shell/main-preflight.js.map +1 -0
  236. package/dist/shell/main-reconcilers-helpers.js +244 -0
  237. package/dist/shell/main-reconcilers-helpers.js.map +1 -0
  238. package/dist/shell/main-reconcilers-pr.js +148 -0
  239. package/dist/shell/main-reconcilers-pr.js.map +1 -0
  240. package/dist/shell/main-reconcilers.js +225 -0
  241. package/dist/shell/main-reconcilers.js.map +1 -0
  242. package/dist/shell/main-runner.js +355 -0
  243. package/dist/shell/main-runner.js.map +1 -0
  244. package/dist/shell/main-scaffold.js +116 -0
  245. package/dist/shell/main-scaffold.js.map +1 -0
  246. package/dist/shell/main-shutdown.js +115 -0
  247. package/dist/shell/main-shutdown.js.map +1 -0
  248. package/dist/shell/main-startup.js +48 -0
  249. package/dist/shell/main-startup.js.map +1 -0
  250. package/dist/shell/main-substrates.js +43 -0
  251. package/dist/shell/main-substrates.js.map +1 -0
  252. package/dist/shell/main.js +385 -0
  253. package/dist/shell/main.js.map +1 -0
  254. package/dist/shell/orchestrator-feedback.js +69 -0
  255. package/dist/shell/orchestrator-feedback.js.map +1 -0
  256. package/dist/shell/orchestrator-image.js +167 -0
  257. package/dist/shell/orchestrator-image.js.map +1 -0
  258. package/dist/shell/orchestrator-loop.js +468 -0
  259. package/dist/shell/orchestrator-loop.js.map +1 -0
  260. package/dist/shell/orchestrator-reconcile.js +36 -0
  261. package/dist/shell/orchestrator-reconcile.js.map +1 -0
  262. package/dist/shell/reconciler-loop.js +228 -0
  263. package/dist/shell/reconciler-loop.js.map +1 -0
  264. package/dist/shell/runner-loop-turn.js +301 -0
  265. package/dist/shell/runner-loop-turn.js.map +1 -0
  266. package/dist/shell/runner-loop.js +338 -0
  267. package/dist/shell/runner-loop.js.map +1 -0
  268. package/dist/shell/server/http.js +208 -0
  269. package/dist/shell/server/http.js.map +1 -0
  270. package/dist/shell/server/mcp-runtime-effects.js +237 -0
  271. package/dist/shell/server/mcp-runtime-effects.js.map +1 -0
  272. package/dist/shell/server/mcp-runtime.js +99 -0
  273. package/dist/shell/server/mcp-runtime.js.map +1 -0
  274. package/dist/shell/workspace-key.js +14 -0
  275. package/dist/shell/workspace-key.js.map +1 -0
  276. package/dist/types/acp.js +8 -0
  277. package/dist/types/acp.js.map +1 -0
  278. package/dist/types/actions/plan.js +6 -0
  279. package/dist/types/actions/plan.js.map +1 -0
  280. package/dist/types/actions/predicates.js +6 -0
  281. package/dist/types/actions/predicates.js.map +1 -0
  282. package/dist/types/actions/run-fold.js +8 -0
  283. package/dist/types/actions/run-fold.js.map +1 -0
  284. package/dist/types/actions.js +7 -0
  285. package/dist/types/actions.js.map +1 -0
  286. package/dist/types/adapter/clock-random.js +4 -0
  287. package/dist/types/adapter/clock-random.js.map +1 -0
  288. package/dist/types/adapter/gondolin-image-converter.js +5 -0
  289. package/dist/types/adapter/gondolin-image-converter.js.map +1 -0
  290. package/dist/types/adapter/gondolin-image-fetch.js +5 -0
  291. package/dist/types/adapter/gondolin-image-fetch.js.map +1 -0
  292. package/dist/types/adapter/workflow-loader.js +4 -0
  293. package/dist/types/adapter/workflow-loader.js.map +1 -0
  294. package/dist/types/cli/args.js +8 -0
  295. package/dist/types/cli/args.js.map +1 -0
  296. package/dist/types/config.js +8 -0
  297. package/dist/types/config.js.map +1 -0
  298. package/dist/types/credential-interp.js +6 -0
  299. package/dist/types/credential-interp.js.map +1 -0
  300. package/dist/types/credentials.js +10 -0
  301. package/dist/types/credentials.js.map +1 -0
  302. package/dist/types/doctor.js +7 -0
  303. package/dist/types/doctor.js.map +1 -0
  304. package/dist/types/domain.js +7 -0
  305. package/dist/types/domain.js.map +1 -0
  306. package/dist/types/effect.js +15 -0
  307. package/dist/types/effect.js.map +1 -0
  308. package/dist/types/errors.js +39 -0
  309. package/dist/types/errors.js.map +1 -0
  310. package/dist/types/http/decisions.js +6 -0
  311. package/dist/types/http/decisions.js.map +1 -0
  312. package/dist/types/http/render.js +10 -0
  313. package/dist/types/http/render.js.map +1 -0
  314. package/dist/types/http/views.js +6 -0
  315. package/dist/types/http/views.js.map +1 -0
  316. package/dist/types/http.js +9 -0
  317. package/dist/types/http.js.map +1 -0
  318. package/dist/types/image/managed-image.js +7 -0
  319. package/dist/types/image/managed-image.js.map +1 -0
  320. package/dist/types/interp/effect-interpreter.js +8 -0
  321. package/dist/types/interp/effect-interpreter.js.map +1 -0
  322. package/dist/types/interp/tracker.js +7 -0
  323. package/dist/types/interp/tracker.js.map +1 -0
  324. package/dist/types/issue/file.js +6 -0
  325. package/dist/types/issue/file.js.map +1 -0
  326. package/dist/types/issue/parse.js +8 -0
  327. package/dist/types/issue/parse.js.map +1 -0
  328. package/dist/types/main-acp.js +13 -0
  329. package/dist/types/main-acp.js.map +1 -0
  330. package/dist/types/main-adapters.js +5 -0
  331. package/dist/types/main-adapters.js.map +1 -0
  332. package/dist/types/main-credential.js +21 -0
  333. package/dist/types/main-credential.js.map +1 -0
  334. package/dist/types/main-doctor.js +6 -0
  335. package/dist/types/main-doctor.js.map +1 -0
  336. package/dist/types/main-http-handler.js +12 -0
  337. package/dist/types/main-http-handler.js.map +1 -0
  338. package/dist/types/main-http.js +5 -0
  339. package/dist/types/main-http.js.map +1 -0
  340. package/dist/types/main-loops.js +5 -0
  341. package/dist/types/main-loops.js.map +1 -0
  342. package/dist/types/main-mcp.js +12 -0
  343. package/dist/types/main-mcp.js.map +1 -0
  344. package/dist/types/main-orchestrator.js +5 -0
  345. package/dist/types/main-orchestrator.js.map +1 -0
  346. package/dist/types/main-reconcilers.js +11 -0
  347. package/dist/types/main-reconcilers.js.map +1 -0
  348. package/dist/types/main-runner.js +13 -0
  349. package/dist/types/main-runner.js.map +1 -0
  350. package/dist/types/main-startup.js +5 -0
  351. package/dist/types/main-startup.js.map +1 -0
  352. package/dist/types/main-substrates.js +5 -0
  353. package/dist/types/main-substrates.js.map +1 -0
  354. package/dist/types/mcp/dispatch.js +4 -0
  355. package/dist/types/mcp/dispatch.js.map +1 -0
  356. package/dist/types/mcp/post-move.js +7 -0
  357. package/dist/types/mcp/post-move.js.map +1 -0
  358. package/dist/types/mcp.js +9 -0
  359. package/dist/types/mcp.js.map +1 -0
  360. package/dist/types/ports.js +12 -0
  361. package/dist/types/ports.js.map +1 -0
  362. package/dist/types/reconcile/image-decide.js +5 -0
  363. package/dist/types/reconcile/image-decide.js.map +1 -0
  364. package/dist/types/reconcile/ledger.js +7 -0
  365. package/dist/types/reconcile/ledger.js.map +1 -0
  366. package/dist/types/reconcile/pr-loop.js +8 -0
  367. package/dist/types/reconcile/pr-loop.js.map +1 -0
  368. package/dist/types/reconcile/vm-reap.js +8 -0
  369. package/dist/types/reconcile/vm-reap.js.map +1 -0
  370. package/dist/types/reconcile/workspace-decide.js +7 -0
  371. package/dist/types/reconcile/workspace-decide.js.map +1 -0
  372. package/dist/types/reconcile.js +9 -0
  373. package/dist/types/reconcile.js.map +1 -0
  374. package/dist/types/runlog.js +7 -0
  375. package/dist/types/runlog.js.map +1 -0
  376. package/dist/types/runner/actions-runner.js +12 -0
  377. package/dist/types/runner/actions-runner.js.map +1 -0
  378. package/dist/types/runner/gondolin-dispatch.js +5 -0
  379. package/dist/types/runner/gondolin-dispatch.js.map +1 -0
  380. package/dist/types/runner/injection.js +6 -0
  381. package/dist/types/runner/injection.js.map +1 -0
  382. package/dist/types/runner/runner-loop.js +5 -0
  383. package/dist/types/runner/runner-loop.js.map +1 -0
  384. package/dist/types/runner/turn.js +4 -0
  385. package/dist/types/runner/turn.js.map +1 -0
  386. package/dist/types/runner/vm-plan.js +4 -0
  387. package/dist/types/runner/vm-plan.js.map +1 -0
  388. package/dist/types/runtime.js +9 -0
  389. package/dist/types/runtime.js.map +1 -0
  390. package/dist/types/schedule/admission.js +7 -0
  391. package/dist/types/schedule/admission.js.map +1 -0
  392. package/dist/types/schedule/circuit-breaker.js +2 -0
  393. package/dist/types/schedule/circuit-breaker.js.map +1 -0
  394. package/dist/types/schedule/eligibility.js +9 -0
  395. package/dist/types/schedule/eligibility.js.map +1 -0
  396. package/dist/types/schedule/orchestrator-loop.js +10 -0
  397. package/dist/types/schedule/orchestrator-loop.js.map +1 -0
  398. package/dist/types/schedule/sleep-cycle.js +4 -0
  399. package/dist/types/schedule/sleep-cycle.js.map +1 -0
  400. package/dist/types/schedule/slots.js +8 -0
  401. package/dist/types/schedule/slots.js.map +1 -0
  402. package/dist/types/schedule/tick.js +9 -0
  403. package/dist/types/schedule/tick.js.map +1 -0
  404. package/dist/types/server/mcp-runtime.js +8 -0
  405. package/dist/types/server/mcp-runtime.js.map +1 -0
  406. package/dist/types/workflow/parse.js +4 -0
  407. package/dist/types/workflow/parse.js.map +1 -0
  408. package/package.json +22 -10
  409. package/patches/@earendil-works+gondolin+0.12.0.patch +173 -0
  410. package/prompts/Reflect.md +91 -0
  411. package/prompts/Review.md +97 -0
  412. package/prompts/Todo.md +96 -0
  413. package/prompts/_footer.md +41 -0
  414. package/prompts/_preamble.md +42 -0
  415. package/prompts-minimal/Todo.md +26 -0
  416. package/scripts/postinstall.mjs +63 -0
  417. package/scripts/vm-agent.mjs +312 -90
  418. package/WORKFLOW.md +0 -744
  419. package/dist/acp-bridge.js +0 -324
  420. package/dist/acp-bridge.js.map +0 -1
  421. package/dist/actions/cache.js +0 -191
  422. package/dist/actions/cache.js.map +0 -1
  423. package/dist/actions/effects.js +0 -41
  424. package/dist/actions/effects.js.map +0 -1
  425. package/dist/actions/executor.js +0 -570
  426. package/dist/actions/executor.js.map +0 -1
  427. package/dist/actions/index.js +0 -13
  428. package/dist/actions/index.js.map +0 -1
  429. package/dist/actions/parsing.js.map +0 -1
  430. package/dist/actions/predicate-env.js +0 -27
  431. package/dist/actions/predicate-env.js.map +0 -1
  432. package/dist/actions/predicates.js +0 -49
  433. package/dist/actions/predicates.js.map +0 -1
  434. package/dist/actions/templating.js +0 -66
  435. package/dist/actions/templating.js.map +0 -1
  436. package/dist/actions/types.js +0 -15
  437. package/dist/actions/types.js.map +0 -1
  438. package/dist/agent/acp.js +0 -473
  439. package/dist/agent/acp.js.map +0 -1
  440. package/dist/agent/adapter-names.js +0 -159
  441. package/dist/agent/adapter-names.js.map +0 -1
  442. package/dist/agent/adapters.js +0 -511
  443. package/dist/agent/adapters.js.map +0 -1
  444. package/dist/agent/credential-extractors.js +0 -342
  445. package/dist/agent/credential-extractors.js.map +0 -1
  446. package/dist/agent/credential-secrets.js +0 -628
  447. package/dist/agent/credential-secrets.js.map +0 -1
  448. package/dist/agent/credential-ticker.js +0 -57
  449. package/dist/agent/credential-ticker.js.map +0 -1
  450. package/dist/agent/gondolin-creds-staging.js +0 -356
  451. package/dist/agent/gondolin-creds-staging.js.map +0 -1
  452. package/dist/agent/gondolin-dispatch.js +0 -375
  453. package/dist/agent/gondolin-dispatch.js.map +0 -1
  454. package/dist/agent/gondolin.js +0 -124
  455. package/dist/agent/gondolin.js.map +0 -1
  456. package/dist/agent/runner-decisions.js +0 -134
  457. package/dist/agent/runner-decisions.js.map +0 -1
  458. package/dist/agent/runner.js +0 -1456
  459. package/dist/agent/runner.js.map +0 -1
  460. package/dist/agent/tool-call-summary.js +0 -102
  461. package/dist/agent/tool-call-summary.js.map +0 -1
  462. package/dist/agent/vm-acp-mapping.js +0 -73
  463. package/dist/agent/vm-acp-mapping.js.map +0 -1
  464. package/dist/agent/vm-guards.js +0 -262
  465. package/dist/agent/vm-guards.js.map +0 -1
  466. package/dist/agent/vm-port.js +0 -22
  467. package/dist/agent/vm-port.js.map +0 -1
  468. package/dist/agent/vm-process-registry.js +0 -79
  469. package/dist/agent/vm-process-registry.js.map +0 -1
  470. package/dist/bin/cli-args.js +0 -105
  471. package/dist/bin/cli-args.js.map +0 -1
  472. package/dist/errors.js +0 -15
  473. package/dist/errors.js.map +0 -1
  474. package/dist/http-disk.js +0 -135
  475. package/dist/http-disk.js.map +0 -1
  476. package/dist/http-handlers.js.map +0 -1
  477. package/dist/http.js.map +0 -1
  478. package/dist/issues.js +0 -178
  479. package/dist/issues.js.map +0 -1
  480. package/dist/logging.js +0 -203
  481. package/dist/logging.js.map +0 -1
  482. package/dist/mcp.js +0 -706
  483. package/dist/mcp.js.map +0 -1
  484. package/dist/memory.js +0 -85
  485. package/dist/memory.js.map +0 -1
  486. package/dist/orchestrator-decisions.js +0 -331
  487. package/dist/orchestrator-decisions.js.map +0 -1
  488. package/dist/orchestrator.js +0 -1569
  489. package/dist/orchestrator.js.map +0 -1
  490. package/dist/prompt.js +0 -65
  491. package/dist/prompt.js.map +0 -1
  492. package/dist/reconciler/cache.js +0 -65
  493. package/dist/reconciler/cache.js.map +0 -1
  494. package/dist/reconciler/index.js +0 -448
  495. package/dist/reconciler/index.js.map +0 -1
  496. package/dist/reconciler/ledger.js +0 -131
  497. package/dist/reconciler/ledger.js.map +0 -1
  498. package/dist/reconciler/pr-adapters.js +0 -174
  499. package/dist/reconciler/pr-adapters.js.map +0 -1
  500. package/dist/reconciler/pr-decide.js.map +0 -1
  501. package/dist/reconciler/pr.js +0 -422
  502. package/dist/reconciler/pr.js.map +0 -1
  503. package/dist/reconciler/types.js +0 -12
  504. package/dist/reconciler/types.js.map +0 -1
  505. package/dist/reconciler/vm.js +0 -243
  506. package/dist/reconciler/vm.js.map +0 -1
  507. package/dist/reconciler/workspace-defaults.js +0 -83
  508. package/dist/reconciler/workspace-defaults.js.map +0 -1
  509. package/dist/reconciler/workspace.js +0 -272
  510. package/dist/reconciler/workspace.js.map +0 -1
  511. package/dist/runlog.js +0 -403
  512. package/dist/runlog.js.map +0 -1
  513. package/dist/scaffold.js +0 -165
  514. package/dist/scaffold.js.map +0 -1
  515. package/dist/trackers/local.js +0 -445
  516. package/dist/trackers/local.js.map +0 -1
  517. package/dist/trackers/types.js +0 -10
  518. package/dist/trackers/types.js.map +0 -1
  519. package/dist/types.js +0 -3
  520. package/dist/types.js.map +0 -1
  521. package/dist/util/clock.js +0 -12
  522. package/dist/util/clock.js.map +0 -1
  523. package/dist/util/crypto.js +0 -25
  524. package/dist/util/crypto.js.map +0 -1
  525. package/dist/util/frontmatter.js +0 -70
  526. package/dist/util/frontmatter.js.map +0 -1
  527. package/dist/util/fs-issues.js +0 -22
  528. package/dist/util/fs-issues.js.map +0 -1
  529. package/dist/util/process.js +0 -152
  530. package/dist/util/process.js.map +0 -1
  531. package/dist/util/workspace-key.js +0 -10
  532. package/dist/util/workspace-key.js.map +0 -1
  533. package/dist/workflow-loader.js +0 -147
  534. package/dist/workflow-loader.js.map +0 -1
  535. package/dist/workflow.js +0 -822
  536. package/dist/workflow.js.map +0 -1
  537. package/dist/workspace-types.js +0 -8
  538. package/dist/workspace-types.js.map +0 -1
  539. package/dist/workspace.js +0 -443
  540. package/dist/workspace.js.map +0 -1
@@ -0,0 +1,238 @@
1
+ // Deps-bound tracker IO split out of tracker.ts (kind: adapter): scan/fetch/
2
+ // move/write + the per-effect router. Takes a small context (live config + log)
3
+ // so the factory in tracker.ts stays a thin wiring shell.
4
+ //
5
+ // Fidelity: src/trackers/local.ts (scan/normalize/move) + src/http-disk.ts.
6
+ import { mkdir, readdir, readFile, rename, stat } from 'node:fs/promises';
7
+ import path from 'node:path';
8
+ import { TrackerError } from '../../types/errors.js';
9
+ import { splitFrontMatter } from './tracker-parse.js';
10
+ import { listIssues, readIssue } from './tracker-disk.js';
11
+ import { assertTargetFree, buildNotesBody, durablyCreateExclusive, durablyReplaceInPlace } from './tracker-write.js';
12
+ const warn = (ctx, msg, fields) => ctx.log?.emit('warn', msg, fields);
13
+ function rootOf(ctx) {
14
+ const r = ctx.config().root;
15
+ if (!r)
16
+ throw new TrackerError('local_no_root', 'tracker.root is required');
17
+ return r;
18
+ }
19
+ // Parse every .md under one declared state dir. Per-file failures skip with a
20
+ // warning so one bad file doesn't poison the scan (faithful to local.ts).
21
+ async function scanStateDir(ctx, dirPath, stateDir, out) {
22
+ let files;
23
+ try {
24
+ files = await readdir(dirPath);
25
+ }
26
+ catch {
27
+ return;
28
+ }
29
+ for (const fileName of files) {
30
+ if (!fileName.endsWith('.md'))
31
+ continue;
32
+ const filePath = path.join(dirPath, fileName);
33
+ try {
34
+ if (!(await stat(filePath)).isFile())
35
+ continue;
36
+ }
37
+ catch {
38
+ continue;
39
+ }
40
+ let text;
41
+ try {
42
+ text = await readFile(filePath, 'utf8');
43
+ }
44
+ catch (err) {
45
+ warn(ctx, 'skipping unreadable issue file', { file: filePath, error: err.message });
46
+ continue;
47
+ }
48
+ try {
49
+ const { fields, body } = splitFrontMatter(text);
50
+ out.push({ filePath, identifier: fileName.slice(0, -3), state: stateDir, frontMatter: fields, description: body });
51
+ }
52
+ catch (err) {
53
+ warn(ctx, 'skipping malformed issue file', { file: filePath, error: err.message });
54
+ }
55
+ }
56
+ }
57
+ // Scan every declared state dir under `root`; undeclared dirs are warned + skipped.
58
+ async function scanAllAt(ctx, root) {
59
+ let entries;
60
+ try {
61
+ entries = await readdir(root);
62
+ }
63
+ catch (err) {
64
+ // A not-yet-created root is an EMPTY tracker, not an error: a fresh
65
+ // `scaffold → run` has no issues until the first one is filed (the write path
66
+ // mkdir's the state dir on create), so the first poll must NOT crash. Mirror
67
+ // scanStateDir's tolerance of a missing state dir. Any OTHER read failure
68
+ // (EACCES, ENOTDIR, …) is a real misconfiguration → surface it loudly.
69
+ if (err.code === 'ENOENT')
70
+ return [];
71
+ throw new TrackerError('local_root_read_error', `cannot read tracker.root ${root}: ${err.message}`);
72
+ }
73
+ const declared = new Set(Object.keys(ctx.config().states).map((n) => n.toLowerCase()));
74
+ const out = [];
75
+ for (const dirEntry of entries) {
76
+ const dirPath = path.join(root, dirEntry);
77
+ try {
78
+ if (!(await stat(dirPath)).isDirectory())
79
+ continue;
80
+ }
81
+ catch {
82
+ continue;
83
+ }
84
+ if (!declared.has(dirEntry.toLowerCase())) {
85
+ warn(ctx, 'skipping undeclared state directory', { dir: dirPath });
86
+ continue;
87
+ }
88
+ await scanStateDir(ctx, dirPath, dirEntry, out);
89
+ }
90
+ return out;
91
+ }
92
+ /**
93
+ * The ONE tracker read: scan every declared state dir into raw decoded
94
+ * front-matter (no filtering, no normalization). Selection/normalization is the
95
+ * pure core's job — the shell hands it this raw list and lets it filter down
96
+ * (core/issue/parse.ts:selectCandidateIssues/selectIssuesByIds/...). `root` is
97
+ * captured here so the orchestrator's atomic candidate-fetch can report it.
98
+ */
99
+ export async function loadAllIssues(ctx) {
100
+ const root = rootOf(ctx);
101
+ return { raw: await scanAllAt(ctx, root), root };
102
+ }
103
+ // Append-notes-then-atomic-rename: write notes into a same-dir tmp+fsync, rename
104
+ // in place, THEN cross-dir rename into the target state. A crash between the two
105
+ // renames leaves notes persisted in the source dir (faithful to local.ts).
106
+ export async function moveIssueToState(ctx, issueId, toState, opts) {
107
+ const root = opts?.fromRoot ?? rootOf(ctx);
108
+ // Match resolution (filter-by-id + duplicate disambiguation) is pure core logic
109
+ // injected through ScanCtx; the shell only does the readdir + the rename IO.
110
+ const match = ctx.resolveMatch(await scanAllAt(ctx, root), issueId, opts?.fromState);
111
+ if (match.state.toLowerCase() === toState.toLowerCase()) {
112
+ return { fromState: match.state, toState: match.state, newPath: match.filePath };
113
+ }
114
+ await appendNotes(match, toState, opts);
115
+ const newPath = path.join(root, toState, path.basename(match.filePath));
116
+ await mkdir(path.join(root, toState), { recursive: true });
117
+ await assertTargetFree(newPath);
118
+ try {
119
+ await rename(match.filePath, newPath);
120
+ }
121
+ catch (err) {
122
+ throw new TrackerError('local_issue_move_error', `failed to move ${match.filePath} -> ${newPath}: ${err.message}`);
123
+ }
124
+ ctx.log?.emit('info', 'issue transitioned', { issue_id: issueId, from_state: match.state, to_state: toState, path: newPath });
125
+ return { fromState: match.state, toState, newPath };
126
+ }
127
+ async function appendNotes(match, toState, opts) {
128
+ const notes = typeof opts?.notes === 'string' ? opts.notes : '';
129
+ if (notes.length === 0)
130
+ return;
131
+ let original;
132
+ try {
133
+ original = await readFile(match.filePath, 'utf8');
134
+ }
135
+ catch (err) {
136
+ throw new TrackerError('local_issue_notes_read_error', `failed to read ${match.filePath} for notes append: ${err.message}`);
137
+ }
138
+ await durablyReplaceInPlace(match.filePath, buildNotesBody(original, match.state, toState, notes, opts?.actor));
139
+ }
140
+ export async function start(ctx) {
141
+ const root = rootOf(ctx);
142
+ for (const name of Object.keys(ctx.config().states)) {
143
+ await mkdir(path.join(root, name), { recursive: true });
144
+ }
145
+ }
146
+ // Collect every existing identifier (filename stem) across ALL state dirs under
147
+ // `root` — the readdir half of the original `collectExistingIdentifiers`. Counts
148
+ // stems from EVERY state so a `Done/3.md` can't silently shadow a freshly-picked
149
+ // `Holding/3.md`. The pure next-free-numeric pick (core) consumes this set.
150
+ async function collectExistingIdentifiers(ctx, root) {
151
+ const existing = new Set();
152
+ for (const raw of await scanAllAt(ctx, root))
153
+ existing.add(raw.identifier);
154
+ return existing;
155
+ }
156
+ // write_issue: durable-write a NEW issue file. Every caller is a CREATE path
157
+ // (the HTTP create-issue handler, the MCP propose_issue write) — none is an
158
+ // in-place UPDATE — so an explicit identifier whose target file is already
159
+ // occupied is a collision, NOT a silent overwrite. The serialization
160
+ // (identifier sanitize, YAML front-matter render, body framing) is the PURE
161
+ // core `composeIssueFile`, injected through the ScanCtx.
162
+ //
163
+ // Identifier resolution (both branches scan the EXISTING ids across ALL state
164
+ // dirs so the create-409 contract holds either way):
165
+ // • effect.identifier PRESENT (HTTP create / triage path) → fed back in
166
+ // verbatim; composeIssueFile sanitizes it (idempotent) and rejects with
167
+ // `identifier_exists` when the sanitized id is already taken — that rejection
168
+ // surfaces here as a `local_issue_target_exists` TrackerError the HTTP layer
169
+ // maps to 409, instead of `durablyReplaceInPlace` overwriting the live file.
170
+ // • effect.identifier ABSENT (MCP propose_issue path) → the injected core
171
+ // `pickNextNumericIdentifier` chooses the next free numeric id over the SAME
172
+ // existing set. This replaces the old `?? '1'` default that overwrote
173
+ // `<holding>/1.md` on every proposal.
174
+ export async function writeIssue(ctx, effect) {
175
+ // Scan once across every state dir: the explicit-identifier branch needs the set
176
+ // to detect a collision (the create-409 contract); the no-identifier branch needs
177
+ // it to pick the next free numeric id. (Pre-fix the explicit branch passed an
178
+ // EMPTY set, so composeIssueFile's collision guard never fired and an occupied
179
+ // target was silently overwritten via durablyReplaceInPlace.)
180
+ const existingIdentifiers = await collectExistingIdentifiers(ctx, effect.trackerRoot);
181
+ const identifierIn = effect.identifier ?? ctx.pickNextNumericIdentifier(existingIdentifiers);
182
+ const composed = ctx.composeIssueFile({
183
+ trackerRoot: effect.trackerRoot,
184
+ state: effect.state,
185
+ title: effect.title,
186
+ identifier: identifierIn,
187
+ ...(effect.description != null ? { description: effect.description } : {}),
188
+ ...(effect.priority != null ? { priority: effect.priority } : {}),
189
+ ...(effect.labels !== undefined ? { labels: effect.labels } : {}),
190
+ ...(effect.blocked_by !== undefined ? { blocked_by: effect.blocked_by } : {}),
191
+ ...(effect.extraFrontMatter !== undefined ? { extra_front_matter: effect.extraFrontMatter } : {}),
192
+ existingIdentifiers,
193
+ nowIso: new Date().toISOString(),
194
+ });
195
+ if (!composed.ok) {
196
+ // An explicit identifier already occupied → honour the create-409 contract:
197
+ // surface a `local_issue_target_exists` (the same code assertTargetFree uses)
198
+ // so the HTTP create handler maps it to 409 rather than overwriting the file.
199
+ if (composed.error.code === 'identifier_exists') {
200
+ throw new TrackerError('local_issue_target_exists', composed.error.message);
201
+ }
202
+ throw new TrackerError('local_issue_write_error', `failed to compose issue file: ${composed.error.message}`);
203
+ }
204
+ const { path: filePath, identifier } = composed.result;
205
+ await mkdir(path.dirname(filePath), { recursive: true });
206
+ // CREATE via an EXCLUSIVE (O_EXCL) open, NOT an overwriting tmp+rename: the
207
+ // scan-based collision check above is a TOCTOU window, so two concurrent creates
208
+ // of the SAME identifier could both pass it. The exclusive create makes the kernel
209
+ // the arbiter — exactly one wins; the loser's EEXIST surfaces as
210
+ // `local_issue_target_exists` (→ HTTP 409), with no overwrite/corruption.
211
+ await durablyCreateExclusive(filePath, composed.result.content);
212
+ return { kind: 'write_issue', path: filePath, identifier, state: effect.state };
213
+ }
214
+ async function interpretMove(ctx, effect) {
215
+ const r = await moveIssueToState(ctx, effect.issueId, effect.toState, {
216
+ fromRoot: effect.fromRoot ?? undefined,
217
+ fromState: effect.fromState ?? undefined,
218
+ notes: effect.notes,
219
+ actor: effect.actor ?? undefined,
220
+ });
221
+ return { kind: 'move_state', fromState: r.fromState, toState: r.toState, newPath: r.newPath };
222
+ }
223
+ // Per-effect router (the central interpreter delegates the tracker family).
224
+ export async function interpret(ctx, effect) {
225
+ switch (effect.kind) {
226
+ case 'current_root':
227
+ return { kind: 'tracker_root', root: ctx.config().root };
228
+ case 'move_state':
229
+ return interpretMove(ctx, effect);
230
+ case 'write_issue':
231
+ return writeIssue(ctx, effect);
232
+ case 'list_issues_from_disk':
233
+ return { kind: 'disk_issues', issues: await listIssues(effect.root) };
234
+ case 'read_issue_from_disk':
235
+ return { kind: 'disk_issue_detail', detail: await readIssue(effect.root, effect.identifier) };
236
+ }
237
+ }
238
+ //# sourceMappingURL=tracker-scan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tracker-scan.js","sourceRoot":"","sources":["../../../src/shell/interp/tracker-scan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,91 @@
1
+ // Durable-write + move primitives split out of tracker.ts (kind: adapter).
2
+ // Stateless (no closure deps). Faithful to src/trackers/local.ts (atomic
3
+ // rename, fsync, notes append). Match resolution (filter-by-id + duplicate
4
+ // disambiguation) is the pure core's job (core/issue/parse.ts:matchingCandidates
5
+ // +resolveMatch), injected into the move path through ScanCtx.
6
+ import { open, rename, stat } from 'node:fs/promises';
7
+ import { TrackerError } from '../../types/errors.js';
8
+ export function buildNotesBody(original, fromState, toState, notes, actor) {
9
+ const who = actor && actor.length > 0 ? actor : 'unknown';
10
+ const header = `## ${who} — ${new Date().toISOString()} — ${fromState} → ${toState}`;
11
+ const sep = original.endsWith('\n\n') ? '' : original.endsWith('\n') ? '\n' : '\n\n';
12
+ return `${original}${sep}${header}\n\n${notes}\n`;
13
+ }
14
+ // Same-dir tmp+fsync then atomic rename in place. UPDATE semantics: the rename
15
+ // OVERWRITES the target, so this is for in-place edits of an EXISTING file (notes
16
+ // append, move-in-place). New-issue CREATE must use durablyCreateExclusive — its
17
+ // overwriting rename cannot enforce the create-409 contract under concurrency.
18
+ export async function durablyReplaceInPlace(targetPath, contents) {
19
+ const tmpPath = targetPath + '.tmp';
20
+ let fh;
21
+ try {
22
+ fh = await open(tmpPath, 'w', 0o644);
23
+ await fh.writeFile(contents, 'utf8');
24
+ await fh.sync();
25
+ }
26
+ catch (err) {
27
+ try {
28
+ if (fh)
29
+ await fh.close();
30
+ }
31
+ catch { /* best-effort */ }
32
+ throw new TrackerError('local_issue_notes_write_error', `failed to write notes tmp file ${tmpPath}: ${err.message}`);
33
+ }
34
+ try {
35
+ await fh.close();
36
+ }
37
+ catch { /* on disk + fsync'd; close is not load-bearing */ }
38
+ try {
39
+ await rename(tmpPath, targetPath);
40
+ }
41
+ catch (err) {
42
+ throw new TrackerError('local_issue_notes_rename_error', `failed to atomic-rename ${tmpPath} -> ${targetPath}: ${err.message}`);
43
+ }
44
+ }
45
+ // Durable CREATE of a NEW issue file. The scan-based 409 check in writeIssue is a
46
+ // TOCTOU window — two concurrent creates of the same identifier can both pass it,
47
+ // then a deterministic-tmp+overwriting-rename (durablyReplaceInPlace) would let
48
+ // the second silently clobber the first. Instead open the FINAL path with O_EXCL
49
+ // ('wx'): the kernel guarantees exactly one creator wins; the loser gets EEXIST,
50
+ // which we map to the same `local_issue_target_exists` TrackerError the create-409
51
+ // contract already uses (→ HTTP 409). fsync keeps durability. No tmp+rename: the
52
+ // excl open IS the atomic claim, so there is no shared temp path to race on.
53
+ export async function durablyCreateExclusive(targetPath, contents) {
54
+ let fh;
55
+ try {
56
+ fh = await open(targetPath, 'wx', 0o644);
57
+ }
58
+ catch (err) {
59
+ if (err.code === 'EEXIST') {
60
+ throw new TrackerError('local_issue_target_exists', `refusing to overwrite existing file at ${targetPath}`);
61
+ }
62
+ throw new TrackerError('local_issue_write_error', `failed to create issue file ${targetPath}: ${err.message}`);
63
+ }
64
+ try {
65
+ await fh.writeFile(contents, 'utf8');
66
+ await fh.sync();
67
+ }
68
+ catch (err) {
69
+ try {
70
+ await fh.close();
71
+ }
72
+ catch { /* best-effort */ }
73
+ throw new TrackerError('local_issue_write_error', `failed to write issue file ${targetPath}: ${err.message}`);
74
+ }
75
+ try {
76
+ await fh.close();
77
+ }
78
+ catch { /* on disk + fsync'd; close is not load-bearing */ }
79
+ }
80
+ export async function assertTargetFree(newPath) {
81
+ try {
82
+ await stat(newPath);
83
+ }
84
+ catch (err) {
85
+ if (err.code === 'ENOENT')
86
+ return;
87
+ throw new TrackerError('local_issue_move_error', `failed to stat target ${newPath}: ${err.message}`);
88
+ }
89
+ throw new TrackerError('local_issue_target_exists', `refusing to overwrite existing file at ${newPath}`);
90
+ }
91
+ //# sourceMappingURL=tracker-write.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tracker-write.js","sourceRoot":"","sources":["../../../src/shell/interp/tracker-write.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,yEAAyE;AACzE,2EAA2E;AAC3E,iFAAiF;AACjF,+DAA+D;AAE/D,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAErD,MAAM,UAAU,cAAc,CAAC,QAAgB,EAAE,SAAiB,EAAE,OAAe,EAAE,KAAa,EAAE,KAAyB;IAC3H,MAAM,GAAG,GAAG,KAAK,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IAC1D,MAAM,MAAM,GAAG,MAAM,GAAG,MAAM,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,MAAM,SAAS,MAAM,OAAO,EAAE,CAAC;IACrF,MAAM,GAAG,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;IACrF,OAAO,GAAG,QAAQ,GAAG,GAAG,GAAG,MAAM,OAAO,KAAK,IAAI,CAAC;AACpD,CAAC;AAED,+EAA+E;AAC/E,kFAAkF;AAClF,iFAAiF;AACjF,+EAA+E;AAC/E,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,UAAkB,EAAE,QAAgB;IAC9E,MAAM,OAAO,GAAG,UAAU,GAAG,MAAM,CAAC;IACpC,IAAI,EAAE,CAAC;IACP,IAAI,CAAC;QAAC,EAAE,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;QAAC,MAAM,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC;IAAC,CAAC;IACpG,OAAO,GAAG,EAAE,CAAC;QACX,IAAI,CAAC;YAAC,IAAI,EAAE;gBAAE,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC,CAAC,iBAAiB,CAAC,CAAC;QAC7D,MAAM,IAAI,YAAY,CAAC,+BAA+B,EAAE,kCAAkC,OAAO,KAAM,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;IAClI,CAAC;IACD,IAAI,CAAC;QAAC,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,kDAAkD,CAAC,CAAC;IACtF,IAAI,CAAC;QAAC,MAAM,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IAAC,CAAC;IAC1C,OAAO,GAAG,EAAE,CAAC;QAAC,MAAM,IAAI,YAAY,CAAC,gCAAgC,EAAE,2BAA2B,OAAO,OAAO,UAAU,KAAM,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;IAAC,CAAC;AAC7J,CAAC;AAED,kFAAkF;AAClF,kFAAkF;AAClF,gFAAgF;AAChF,iFAAiF;AACjF,iFAAiF;AACjF,mFAAmF;AACnF,iFAAiF;AACjF,6EAA6E;AAC7E,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,UAAkB,EAAE,QAAgB;IAC/E,IAAI,EAAE,CAAC;IACP,IAAI,CAAC;QACH,EAAE,GAAG,MAAM,IAAI,CAAC,UAAU,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;IAC3C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACrD,MAAM,IAAI,YAAY,CAAC,2BAA2B,EAAE,0CAA0C,UAAU,EAAE,CAAC,CAAC;QAC9G,CAAC;QACD,MAAM,IAAI,YAAY,CAAC,yBAAyB,EAAE,+BAA+B,UAAU,KAAM,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;IAC5H,CAAC;IACD,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QACrC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC;IAClB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC;YAAC,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC,CAAC,iBAAiB,CAAC,CAAC;QACrD,MAAM,IAAI,YAAY,CAAC,yBAAyB,EAAE,8BAA8B,UAAU,KAAM,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;IAC3H,CAAC;IACD,IAAI,CAAC;QAAC,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,kDAAkD,CAAC,CAAC;AACxF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,OAAe;IACpD,IAAI,CAAC;QAAC,MAAM,IAAI,CAAC,OAAO,CAAC,CAAC;IAAC,CAAC;IAC5B,OAAO,GAAG,EAAE,CAAC;QACX,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO;QAC7D,MAAM,IAAI,YAAY,CAAC,wBAAwB,EAAE,yBAAyB,OAAO,KAAM,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;IAClH,CAAC;IACD,MAAM,IAAI,YAAY,CAAC,2BAA2B,EAAE,0CAA0C,OAAO,EAAE,CAAC,CAAC;AAC3G,CAAC"}
@@ -0,0 +1,41 @@
1
+ // FCIS rewrite — tracker effect interpreter (kind: adapter).
2
+ //
3
+ // Interprets every `TrackerEffect` variant against the filesystem-backed
4
+ // LocalMarkdownTracker layout `<tracker.root>/<state>/<identifier>.md`:
5
+ // • current_root — snapshot the live tracker root
6
+ // • move_state — append-notes-then-atomic-rename across state dirs
7
+ // • write_issue — render + durable write a new issue file
8
+ // • list/read_issue_from_disk — the dashboard's direct-disk reads (critic gap)
9
+ // Selection reads (candidates / by-id) go through loadAllIssues + the pure core
10
+ // selectors at the composition root, not through an effect.
11
+ //
12
+ // Fidelity anchors (read-only reference, NOT modified):
13
+ // • src/trackers/local.ts (LocalMarkdownTracker: scan/normalize/move)
14
+ // • src/http-disk.ts (listIssuesFromDisk / readIssueFromDisk)
15
+ //
16
+ // This file is the thin wiring shell: it binds the live config + log into a
17
+ // ScanCtx and exposes the adapter surface. The stateless halves (front-matter
18
+ // decode, normalize/coerce, YAML render, durable-write/move primitives, disk
19
+ // dashboard reads) and the deps-bound scan/fetch/move/write IO live in siblings.
20
+ import * as scan from './tracker-scan.js';
21
+ import { listIssues, readIssue } from './tracker-disk.js';
22
+ export function createTrackerAdapter(deps) {
23
+ const ctx = {
24
+ config: deps.config,
25
+ log: deps.log,
26
+ resolveMatch: deps.resolveMatch,
27
+ composeIssueFile: deps.composeIssueFile,
28
+ pickNextNumericIdentifier: deps.pickNextNumericIdentifier,
29
+ };
30
+ return {
31
+ loadAllIssues: () => scan.loadAllIssues(ctx),
32
+ moveIssueToState: (id, to, opts) => scan.moveIssueToState(ctx, id, to, opts),
33
+ currentRoot: () => deps.config().root,
34
+ start: () => scan.start(ctx),
35
+ listIssues,
36
+ readIssue,
37
+ writeIssue: (effect) => scan.writeIssue(ctx, effect),
38
+ interpret: (effect) => scan.interpret(ctx, effect),
39
+ };
40
+ }
41
+ //# sourceMappingURL=tracker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tracker.js","sourceRoot":"","sources":["../../../src/shell/interp/tracker.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAC7D,EAAE;AACF,yEAAyE;AACzE,wEAAwE;AACxE,oDAAoD;AACpD,qEAAqE;AACrE,4DAA4D;AAC5D,iFAAiF;AACjF,gFAAgF;AAChF,4DAA4D;AAC5D,EAAE;AACF,wDAAwD;AACxD,wEAAwE;AACxE,gEAAgE;AAChE,EAAE;AACF,4EAA4E;AAC5E,8EAA8E;AAC9E,6EAA6E;AAC7E,iFAAiF;AAGjF,OAAO,KAAK,IAAI,MAAM,mBAAmB,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAI1D,MAAM,UAAU,oBAAoB,CAAC,IAAwB;IAC3D,MAAM,GAAG,GAAiB;QACxB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,gBAAgB,EAAE,IAAI,CAAC,gBAAgB;QACvC,yBAAyB,EAAE,IAAI,CAAC,yBAAyB;KAC1D,CAAC;IACF,OAAO;QACL,aAAa,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC;QAC5C,gBAAgB,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,gBAAgB,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC;QAC5E,WAAW,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI;QACrC,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC;QAC5B,UAAU;QACV,SAAS;QACT,UAAU,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC;QACpD,SAAS,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC;KACnD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,48 @@
1
+ // FCIS rewrite — shell adapter: interactive TTY (kind: adapter).
2
+ //
3
+ // Backs the scaffold-on-missing-workflow preflight: read one line of operator
4
+ // input, gate on an interactive terminal, and parse the yes/no accept decision.
5
+ //
6
+ // Fidelity anchors (REF main branch src/bin/symphony.ts, read-only):
7
+ // • promptLine — the readline interface, closed before resolving so the
8
+ // process can exit cleanly afterwards.
9
+ // • maybeScaffoldMissingWorkflow — the `!isTTY` gate and the
10
+ // `'' | 'y' | 'yes'` (trimmed, lower-cased) default-accept parse, kept inline
11
+ // in the shell exactly as the original did.
12
+ //
13
+ // Shell rule: imports from src/types/** ONLY (this adapter needs none).
14
+ import readline from 'node:readline';
15
+ import process from 'node:process';
16
+ /**
17
+ * Read a single line from stdin with the given prompt. The readline interface
18
+ * is closed before resolving so the process can exit cleanly afterwards.
19
+ * Ported byte-for-byte from REF promptLine.
20
+ */
21
+ export function promptLine(message) {
22
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
23
+ return new Promise((resolve) => {
24
+ rl.question(message, (answer) => {
25
+ rl.close();
26
+ resolve(answer);
27
+ });
28
+ });
29
+ }
30
+ /**
31
+ * True only when BOTH stdin and stdout are attached to an interactive terminal.
32
+ * The scaffold offer is suppressed otherwise (cron jobs, CI, container
33
+ * ENTRYPOINTs). Ported from the gate at the top of REF maybeScaffoldMissingWorkflow.
34
+ */
35
+ export function isInteractive() {
36
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY);
37
+ }
38
+ /**
39
+ * Prompt the operator with a yes/no question and resolve to the accept decision.
40
+ * Default-accept: bare enter, "y", or "yes" (case-insensitive, trimmed); anything
41
+ * else rejects. Ported from the parse in REF maybeScaffoldMissingWorkflow. Callers
42
+ * gate on {@link isInteractive} first, as the original did.
43
+ */
44
+ export async function promptYesNo(message) {
45
+ const normalized = (await promptLine(message)).trim().toLowerCase();
46
+ return normalized === '' || normalized === 'y' || normalized === 'yes';
47
+ }
48
+ //# sourceMappingURL=tty.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tty.js","sourceRoot":"","sources":["../../../src/shell/interp/tty.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,EAAE;AACF,8EAA8E;AAC9E,gFAAgF;AAChF,EAAE;AACF,qEAAqE;AACrE,0EAA0E;AAC1E,2CAA2C;AAC3C,+DAA+D;AAC/D,kFAAkF;AAClF,gDAAgD;AAChD,EAAE;AACF,wEAAwE;AAExE,OAAO,QAAQ,MAAM,eAAe,CAAC;AACrC,OAAO,OAAO,MAAM,cAAc,CAAC;AAEnC;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,OAAe;IACxC,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACtF,OAAO,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,EAAE;QACrC,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,EAAE;YAC9B,EAAE,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,CAAC,MAAM,CAAC,CAAC;QAClB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa;IAC3B,OAAO,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC9D,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAe;IAC/C,MAAM,UAAU,GAAG,CAAC,MAAM,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACpE,OAAO,UAAU,KAAK,EAAE,IAAI,UAAU,KAAK,GAAG,IAAI,UAAU,KAAK,KAAK,CAAC;AACzE,CAAC"}
@@ -0,0 +1,199 @@
1
+ // FCIS rewrite — VM-substrate adapter (kind: adapter) implementing the VmClient
2
+ // port over `@earendil-works/gondolin`.
3
+ //
4
+ // Thin by design: `createVm` → `VM.create` (host mounts become RealFSProvider /
5
+ // ReadonlyProvider VFS entries), the handle's `exec` → `vm.exec` (piped stdio + an
6
+ // AbortController standing in for the port's `kill()`), `close` → `vm.close`, and
7
+ // `listSessions` / `gc` → Gondolin's session registry. A tiny in-process
8
+ // VmProcessRegistry tracks each live VM's backing qemu host pid so a forced exit
9
+ // can SIGKILL them synchronously before `process.exit` (issue 152): VMs are
10
+ // created in-process so each qemu is a DIRECT CHILD of the orchestrator, and the
11
+ // session registry only records OUR pid — `getHostPid()` is the sole reliable
12
+ // handle on the backing process.
13
+ //
14
+ // Shell rule: imports from src/types/** ONLY (plus the real Gondolin SDK). ZERO
15
+ // decisions live here. NodeNext ESM with .js extensions on relative imports.
16
+ import { mkdir } from 'node:fs/promises';
17
+ import { VM, RealFSProvider, ReadonlyProvider, listSessions as gondolinListSessions, gcSessions, } from '@earendil-works/gondolin';
18
+ /**
19
+ * Pure: reduce candidate VM host pids (some unknown/null) to the distinct, safe
20
+ * set to SIGKILL — positive safe integers, never the caller's own pid (which
21
+ * `process.kill` would aim at the orchestrator) and never `<= 0` (which would
22
+ * signal a whole process group).
23
+ */
24
+ function safeKillTargets(hostPids, selfPid) {
25
+ const out = new Set();
26
+ for (const pid of hostPids) {
27
+ if (pid == null || !Number.isSafeInteger(pid) || pid <= 0 || pid === selfPid)
28
+ continue;
29
+ out.add(pid);
30
+ }
31
+ return [...out];
32
+ }
33
+ class VmProcessRegistry {
34
+ live = new Set();
35
+ kill;
36
+ selfPid;
37
+ constructor(opts = {}) {
38
+ this.kill = opts.kill ?? ((pid, signal) => process.kill(pid, signal));
39
+ this.selfPid = opts.selfPid ?? process.pid;
40
+ }
41
+ /**
42
+ * Register a live VM. Returns a deregister callback to invoke after a clean
43
+ * `close()` so the set stays bounded. Correctness does NOT depend on it: once a
44
+ * VM's runner exits, `getHostPid()` returns null and {@link killAll} skips it.
45
+ */
46
+ track(vm) {
47
+ this.live.add(vm);
48
+ return () => void this.live.delete(vm);
49
+ }
50
+ /**
51
+ * SIGKILL the backing host process of every tracked VM that still has a live
52
+ * runner pid. Synchronous and bounded (one signal per tracked VM, no IO), so
53
+ * the forced-shutdown path can call it and `process.exit` immediately. Best
54
+ * effort: ESRCH / EPERM / etc. are swallowed. Returns the count targeted.
55
+ */
56
+ killAll() {
57
+ const pids = safeKillTargets([...this.live].map((vm) => vm.getHostPid()), this.selfPid);
58
+ for (const pid of pids) {
59
+ try {
60
+ this.kill(pid, 'SIGKILL');
61
+ }
62
+ catch {
63
+ // ESRCH (already gone) / EPERM / etc. — best-effort on the way out.
64
+ }
65
+ }
66
+ return pids.length;
67
+ }
68
+ }
69
+ // ─── VM handle (one launched Gondolin VM) ──────────────────────────────────
70
+ class GondolinVmHandle {
71
+ vm;
72
+ onClose;
73
+ /**
74
+ * `onClose` deregisters this VM from the client's process registry after a
75
+ * clean `close()` (qemu already torn down). Defaults to a no-op so the handle
76
+ * can be constructed without a registry in tests.
77
+ */
78
+ constructor(vm, onClose = () => undefined) {
79
+ this.vm = vm;
80
+ this.onClose = onClose;
81
+ }
82
+ get id() {
83
+ return this.vm.id;
84
+ }
85
+ exec(opts) {
86
+ // Gondolin has no `proc.kill()`; aborting the signal passed to `exec` is the
87
+ // teardown path, so the handle owns the controller.
88
+ const ac = new AbortController();
89
+ const proc = this.vm.exec(opts.command, {
90
+ cwd: opts.workdir ?? undefined,
91
+ env: opts.env,
92
+ stdin: true,
93
+ stdout: 'pipe',
94
+ stderr: 'pipe',
95
+ signal: ac.signal,
96
+ });
97
+ const { stdout, stderr } = proc;
98
+ if (!stdout || !stderr) {
99
+ throw new Error('gondolin exec did not expose piped stdout/stderr');
100
+ }
101
+ let killed = false;
102
+ return {
103
+ stdin: { write: (chunk) => proc.write(chunk), end: () => proc.end() },
104
+ stdout,
105
+ stderr,
106
+ pid: this.vm.getHostPid() ?? undefined,
107
+ exit: proc.result.then((r) => ({ code: r.exitCode, signal: r.signal ?? null }), (err) => {
108
+ // An intentional kill() aborts the AbortController, which makes Gondolin reject
109
+ // the exec result with Error("exec aborted"). That abort IS the teardown signal,
110
+ // not a failure — resolve it to a killed sentinel. Otherwise a caller that does
111
+ // not observe `exit` (the launch agent's exec is a bare process tether: its
112
+ // lifecycle rides the /acp WebSocket, nothing awaits `exit`) would turn the
113
+ // teardown abort into an unhandled rejection that crashes the orchestrator.
114
+ // Genuine (non-kill) errors still reject so observing callers see them.
115
+ if (killed)
116
+ return { code: null, signal: null };
117
+ throw err;
118
+ }),
119
+ kill: () => {
120
+ if (killed)
121
+ return;
122
+ killed = true;
123
+ try {
124
+ ac.abort();
125
+ }
126
+ catch {
127
+ // idempotent — already aborted / process gone
128
+ }
129
+ },
130
+ };
131
+ }
132
+ async close() {
133
+ // Deregister only after a clean close. If `close()` throws, the VM stays
134
+ // tracked so the force-quit backstop (`killAllVms`) can still SIGKILL its
135
+ // backing qemu — a teardown that failed must not leave an orphan.
136
+ await this.vm.close();
137
+ this.onClose();
138
+ }
139
+ }
140
+ // ─── VM client (the VmClient port) ──────────────────────────────────────────
141
+ export class GondolinVmClient {
142
+ procRegistry;
143
+ constructor(opts = {}) {
144
+ this.procRegistry = new VmProcessRegistry(opts);
145
+ }
146
+ async createVm(opts) {
147
+ const mounts = {};
148
+ for (const m of opts.mounts) {
149
+ // Provision symphony-managed RW mount hosts before mounting (issue 209): the mise
150
+ // download-cache dir may not exist on a fresh host. Idempotent; only flagged
151
+ // mounts (never the workspace / user volumes) are created.
152
+ if (m.ensureHostDir)
153
+ await mkdir(m.host, { recursive: true });
154
+ const provider = new RealFSProvider(m.host);
155
+ mounts[m.guest] = m.readonly ? new ReadonlyProvider(provider) : provider;
156
+ }
157
+ // The port keeps httpHooks/tcp/dns opaque (`unknown`) to avoid leaking
158
+ // Gondolin infra types upward; the composition root produces the real shapes
159
+ // (createHttpHooks / tcp.hosts tunnel) and passes them straight through. The
160
+ // cast at this single-adapter seam is deliberate and cheap. Per-guest file
161
+ // staging (creds + launcher) is the dispatcher's job over `exec` AFTER create,
162
+ // not the createVm port surface — matching the reference gondolin.ts adapter.
163
+ const options = {
164
+ sandbox: { imagePath: opts.imagePath },
165
+ cpus: opts.cpus,
166
+ memory: `${opts.memMib}M`,
167
+ // Grow the guest rootfs so the ephemeral mise toolchain install fits (issue 222);
168
+ // qemu size syntax (e.g. "3G"). Omitted ⇒ the substrate's default rootfs size.
169
+ ...(opts.rootfsSize ? { rootfs: { size: opts.rootfsSize } } : {}),
170
+ env: opts.env,
171
+ vfs: { mounts },
172
+ httpHooks: opts.httpHooks,
173
+ tcp: opts.tcp,
174
+ dns: opts.dns,
175
+ allowWebSockets: opts.allowWebSockets,
176
+ sessionLabel: opts.sessionLabel,
177
+ };
178
+ const vm = await VM.create(options);
179
+ return new GondolinVmHandle(vm, this.procRegistry.track(vm));
180
+ }
181
+ /** @see VmClient.killAllVms — synchronous, best-effort qemu reap on forced exit. */
182
+ killAllVms() {
183
+ return this.procRegistry.killAll();
184
+ }
185
+ async listSessions() {
186
+ const sessions = await gondolinListSessions();
187
+ return sessions.map((s) => ({
188
+ id: s.id,
189
+ pid: s.pid,
190
+ label: s.label,
191
+ alive: s.alive,
192
+ createdAt: s.createdAt,
193
+ }));
194
+ }
195
+ async gc() {
196
+ return gcSessions();
197
+ }
198
+ }
199
+ //# sourceMappingURL=vm.js.map