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
package/dist/mcp.js DELETED
@@ -1,706 +0,0 @@
1
- // Per-issue MCP server. Each active dispatch registers an entry here; the agent inside
2
- // the Gondolin VM connects to /api/v1/issues/<id>/mcp with the per-dispatch bearer token and
3
- // sees these tools:
4
- //
5
- // transition(to_state, notes?) ─ atomic file move into another declared state,
6
- // optionally appending notes to the issue body
7
- // first. Sets the `transitioned` flag so the runner
8
- // exits cleanly on next post-turn check.
9
- // request_human_steering(question, context?)
10
- // ─ stashes the question on the RunningEntry, returns
11
- // an ack to the agent. The runner then pauses the
12
- // autonomous loop and awaits a human reply submitted
13
- // via POST /api/v1/issues/<id>/steering-reply.
14
- // propose_issue(title, ...) ─ drops a new issue file into the first declared
15
- // holding state directory for operator triage.
16
- //
17
- // The URL is the capability: the agent only ever knows its own /<id>/mcp endpoint. The
18
- // bearer token is belt-and-braces in case a non-agent caller can reach 8787.
19
- //
20
- // MCP wire format here is JSON-RPC 2.0 over HTTP (the "Streamable HTTP" transport's
21
- // non-SSE subset). We implement only the subset our tools need: initialize, tools/list,
22
- // tools/call, plus a polite notifications/initialized acknowledgement.
23
- import { log } from './logging.js';
24
- import { writeIssueFile, pickHoldingState, NoHoldingStateError } from './issues.js';
25
- import { realClock, isoFromClock } from './util/clock.js';
26
- import { realCrypto } from './util/crypto.js';
27
- const PROTOCOL_VERSION = '2025-06-18';
28
- const TOOL_LIST = [
29
- {
30
- name: 'request_human_steering',
31
- description: 'Pause work and ask the human operator a question. Your current turn will end immediately after this returns; the human response will arrive as the prompt for your next turn. Use only when you cannot proceed without a decision a human must make.',
32
- inputSchema: {
33
- type: 'object',
34
- properties: {
35
- question: {
36
- type: 'string',
37
- description: 'The question to ask the human. Be specific about what decision they need to make.',
38
- },
39
- context: {
40
- type: 'string',
41
- description: 'Optional: relevant context the human needs to answer (file paths, options considered, etc.).',
42
- },
43
- },
44
- required: ['question'],
45
- },
46
- },
47
- {
48
- name: 'transition',
49
- description: 'Move this issue into another declared state, optionally appending notes to its body for the next agent. Use this for handoffs — implementer → reviewer, reviewer → implementer (rework), or implementer → terminal Done — rather than only at the very end of the work. Notes are appended to the issue file BEFORE the state move, so the next dispatch sees them in `issue.description` along with everything the previous agents wrote. `to_state` must be one of the states declared in WORKFLOW.md; if the current state declares `allowed_transitions`, `to_state` must also be in that list. On a terminal target, the workspace is removed after your turn ends and no further turns will be dispatched. On a non-terminal target, the same workspace and `agent/<id>` git branch survive into the next state.',
50
- inputSchema: {
51
- type: 'object',
52
- properties: {
53
- to_state: {
54
- type: 'string',
55
- description: 'Declared state to transition into (case-insensitive match against `states:` in WORKFLOW.md). Examples: "Review", "Done", "Todo".',
56
- },
57
- notes: {
58
- type: 'string',
59
- description: 'Optional markdown notes to append to the issue body before the move. These become part of the issue description the next agent (in `to_state`) reads. Use this for review findings, rework instructions, or PR-body content on a terminal transition.',
60
- },
61
- },
62
- required: ['to_state'],
63
- },
64
- },
65
- {
66
- name: 'propose_issue',
67
- description: 'Propose a new issue for the human to triage. The orchestrator drops the proposal into a non-active "Triage" state directory and does NOT dispatch it — the operator approves (moves to the active queue) or discards it from the dashboard. Use this when you notice work that is out of scope for your current task: an unrelated bug, a follow-up the operator should size, a refactor a future agent could pick up. The parent issue you are working on is automatically recorded; do not paste it into the body.',
68
- inputSchema: {
69
- type: 'object',
70
- properties: {
71
- title: {
72
- type: 'string',
73
- description: 'Short single-line title in imperative voice (≤72 chars recommended). Example: "Fix race condition in workspace cleanup".',
74
- },
75
- description: {
76
- type: 'string',
77
- description: 'Optional multi-paragraph body for the issue. Explain what you observed, where (file paths), and why it is worth handling separately from your current task.',
78
- },
79
- labels: {
80
- type: 'array',
81
- items: { type: 'string' },
82
- description: 'Optional list of label strings.',
83
- },
84
- priority: {
85
- type: 'number',
86
- description: 'Optional integer priority hint (tracker-defined meaning).',
87
- },
88
- },
89
- required: ['title'],
90
- },
91
- },
92
- ];
93
- export class McpRegistry {
94
- tracker;
95
- byIdentifier = new Map();
96
- effectivePort = null;
97
- // Live state-config map, kept in sync with the orchestrator's view via
98
- // `updateStates`. Used by `transition` to validate `to_state` and to resolve
99
- // the role of the target (terminal => set cleanup_workspace_on_exit). An
100
- // empty map means every transition call lands in the "unknown_state" branch,
101
- // which is the correct behaviour: without a declared states map, the agent
102
- // has no valid target to name.
103
- states = {};
104
- // Issue 38 (generalised in issue 144): when the PR engine is enabled,
105
- // transitions into the merge state do NOT flip `cleanup_workspace_on_exit`
106
- // even though the target state's role is `terminal`. The pr resource owns
107
- // the workspace lifecycle for those issues — it rebases inside the workspace
108
- // and cleans it up after the PR merges/closes. Without this gate the
109
- // terminal-cleanup path would reap the workspace before the engine could
110
- // ever use it. The caller (bin/symphony.ts) derives `mergeState` from
111
- // `derivePrRouting(cfg.states).mergeState` and `enabled` from `cfg.pr.enabled`.
112
- prMerge = { enabled: false, mergeState: null };
113
- // Injected wall clock. Tests pin time; production wires `Date.now` (or
114
- // accepts the realClock default). Used to stamp `proposed_at` on
115
- // propose_issue submissions so the core is deterministic under test.
116
- now;
117
- // Injected crypto port. Production wires `realCrypto` (randomBytes +
118
- // timingSafeEqual); tests can pin deterministic tokens to assert wire
119
- // format without sampling randomness.
120
- crypto;
121
- constructor(tracker, opts = {}) {
122
- this.tracker = tracker;
123
- if (opts.states)
124
- this.states = opts.states;
125
- if (opts.prMerge)
126
- this.prMerge = opts.prMerge;
127
- this.now = opts.now ?? realClock;
128
- this.crypto = opts.crypto ?? realCrypto;
129
- }
130
- /**
131
- * Push the latest state-config map (and optional PR merge-cleanup gate) in
132
- * after a workflow reload. Tests that don't care about the PR engine can call
133
- * with a single argument; production wires both so reloads that flip
134
- * `pr.enabled` or move the merge state take effect on subsequent transitions.
135
- */
136
- updateStates(states, prMerge) {
137
- this.states = states;
138
- if (prMerge !== undefined) {
139
- this.prMerge = prMerge ?? { enabled: false, mergeState: null };
140
- }
141
- }
142
- /** Called once after the HTTP server binds, so URL construction uses the real port. */
143
- setEffectivePort(port) {
144
- this.effectivePort = port;
145
- }
146
- /**
147
- * The bound HTTP port, or null until the server binds. Exposed so the Gondolin
148
- * dispatch can wire the guest→host MCP `tcp.hosts` tunnel to the real
149
- * `mcp.host:effectivePort` while the guest dials a fixed synthetic host.
150
- */
151
- getEffectivePort() {
152
- return this.effectivePort;
153
- }
154
- /**
155
- * Build the URL the ACP agent will be told to POST to. Returns null when no HTTP server
156
- * is available and no explicit URL is configured; the runner uses that to skip MCP
157
- * injection (with a warning) instead of advertising an unreachable endpoint.
158
- *
159
- * `baseOverride` (e.g. the Gondolin guest synthetic base `http://symphony-mcp:7001`)
160
- * wins over both `explicit_host_url` and the real host:port: under Gondolin the
161
- * guest cannot reach the host loopback directly, so the runner passes the synthetic
162
- * base that a per-dispatch `tcp.hosts` entry tunnels to the real MCP server.
163
- */
164
- buildUrl(identifier, mcp, baseOverride) {
165
- const base = baseOverride
166
- ? baseOverride.replace(/\/+$/, '')
167
- : mcp.explicit_host_url
168
- ? mcp.explicit_host_url.replace(/\/+$/, '')
169
- : this.effectivePort === null
170
- ? null
171
- : `http://${mcp.host}:${this.effectivePort}`;
172
- if (!base)
173
- return null;
174
- return `${base}/api/v1/issues/${encodeURIComponent(identifier)}/mcp`;
175
- }
176
- /**
177
- * Register an active dispatch. Returns the bearer token the runner injects into the
178
- * agent's MCP server config via ACP's session/new mcpServers field.
179
- */
180
- activate(entry) {
181
- const token = this.crypto.newToken();
182
- entry.mcp_token = token;
183
- entry.transitioned = false;
184
- entry.steering_requested = false;
185
- entry.steering_question = null;
186
- entry.steering_context = null;
187
- // Carry the dispatch-time tracker-root snapshot through verbatim. Reading
188
- // this.tracker.currentRoot() here would be wrong: activate runs AFTER
189
- // workspace setup and Gondolin VM bring-up — a window during
190
- // which a WORKFLOW.md reload can mutate tracker.root. The dispatch-time
191
- // value is the only one that accurately reflects the world the run was
192
- // started in.
193
- const active = {
194
- issueId: entry.issue_id,
195
- identifier: entry.identifier,
196
- entry,
197
- token,
198
- pendingReply: null,
199
- trackerRootSnapshot: entry.tracker_root_at_dispatch,
200
- };
201
- this.byIdentifier.set(entry.identifier, active);
202
- log.debug('mcp activated', {
203
- issue_identifier: entry.identifier,
204
- tracker_root: active.trackerRootSnapshot,
205
- });
206
- return token;
207
- }
208
- deactivate(identifier) {
209
- const active = this.byIdentifier.get(identifier);
210
- if (!active)
211
- return;
212
- // Reject any waiter so the runner unblocks instead of hanging.
213
- if (active.pendingReply) {
214
- active.pendingReply.reject(new Error('mcp deactivated while awaiting human reply'));
215
- active.pendingReply = null;
216
- }
217
- this.byIdentifier.delete(identifier);
218
- log.debug('mcp deactivated', { issue_identifier: identifier });
219
- }
220
- /**
221
- * Resolve a pending human reply. Returns false if no waiter exists (the agent hasn't
222
- * requested steering, or the reply was already delivered).
223
- */
224
- submitSteeringReply(identifier, text) {
225
- const active = this.byIdentifier.get(identifier);
226
- if (!active || !active.pendingReply)
227
- return false;
228
- const { resolve } = active.pendingReply;
229
- active.pendingReply = null;
230
- resolve(text);
231
- return true;
232
- }
233
- /**
234
- * Block until a human reply arrives, the cancel signal trips, or the active entry is
235
- * deactivated. Returns the reply text or null if cancelled.
236
- */
237
- async awaitSteeringReply(identifier, cancelSignal) {
238
- const active = this.byIdentifier.get(identifier);
239
- if (!active)
240
- return null;
241
- if (active.pendingReply) {
242
- throw new Error('steering reply already being awaited for this issue');
243
- }
244
- let timer = null;
245
- try {
246
- return await new Promise((resolve, reject) => {
247
- active.pendingReply = {
248
- resolve: (text) => resolve(text),
249
- reject: (err) => reject(err),
250
- };
251
- // Poll cancellation every 250ms (same cadence as the runner's cancel-check timer).
252
- timer = setInterval(() => {
253
- if (cancelSignal.cancelled) {
254
- if (active.pendingReply) {
255
- active.pendingReply = null;
256
- resolve(null);
257
- }
258
- }
259
- }, 250);
260
- });
261
- }
262
- finally {
263
- if (timer)
264
- clearInterval(timer);
265
- }
266
- }
267
- /**
268
- * Handle a single JSON-RPC envelope. The HTTP layer takes care of routing (identifier
269
- * lookup, token check) and passes us the parsed body. Returns null for notifications
270
- * (no `id`); the HTTP layer responds with 204 in that case.
271
- */
272
- async handleJsonRpc(identifier, token, body) {
273
- const active = this.byIdentifier.get(identifier);
274
- if (!active) {
275
- return makeError(getId(body), -32001, 'issue not active');
276
- }
277
- if (!this.crypto.constantTimeEqual(active.token, token)) {
278
- return makeError(getId(body), -32002, 'invalid token');
279
- }
280
- if (!isRpcRequest(body)) {
281
- return makeError(getId(body), -32600, 'invalid JSON-RPC request');
282
- }
283
- const id = body.id ?? null;
284
- const isNotification = body.id === undefined;
285
- try {
286
- switch (body.method) {
287
- case 'initialize': {
288
- if (isNotification)
289
- return null;
290
- return {
291
- jsonrpc: '2.0',
292
- id,
293
- result: {
294
- protocolVersion: PROTOCOL_VERSION,
295
- capabilities: {
296
- tools: { listChanged: false },
297
- },
298
- serverInfo: {
299
- name: 'smol-symphony',
300
- version: '0.1.0',
301
- },
302
- },
303
- };
304
- }
305
- case 'notifications/initialized':
306
- case 'notifications/cancelled':
307
- case 'notifications/progress': {
308
- return null;
309
- }
310
- case 'tools/list': {
311
- if (isNotification)
312
- return null;
313
- return { jsonrpc: '2.0', id, result: { tools: TOOL_LIST } };
314
- }
315
- case 'tools/call': {
316
- if (isNotification)
317
- return null;
318
- const params = (body.params ?? {});
319
- const name = params.name;
320
- const args = params.arguments ?? {};
321
- if (name === 'transition') {
322
- return await this.callTransition(active, id, args);
323
- }
324
- if (name === 'request_human_steering') {
325
- return this.callRequestHumanSteering(active, id, args);
326
- }
327
- if (name === 'propose_issue') {
328
- return await this.callProposeIssue(active, id, args);
329
- }
330
- return makeError(id, -32601, `unknown tool: ${name}`);
331
- }
332
- case 'ping': {
333
- if (isNotification)
334
- return null;
335
- return { jsonrpc: '2.0', id, result: {} };
336
- }
337
- default:
338
- if (isNotification)
339
- return null;
340
- return makeError(id, -32601, `unknown method: ${body.method}`);
341
- }
342
- }
343
- catch (err) {
344
- log.warn('mcp handler error', {
345
- issue_identifier: identifier,
346
- method: body.method,
347
- error: err.message,
348
- });
349
- return makeError(id, -32603, err.message);
350
- }
351
- }
352
- /**
353
- * Shared file-move + flag-flip path used by `transition`. Returns the tracker's
354
- * resolved {from, to, newPath} on success, throwing the underlying TrackerError
355
- * on failure so the caller can wrap it in a tool-error response.
356
- *
357
- * Cleanup-on-exit is decided by the role of the canonical target state: terminal
358
- * targets clean the workspace, active/holding targets preserve it so the same
359
- * `agent/<id>` git branch survives across the handoff. Callers must have already
360
- * resolved `toState` to its canonical declared name via `canonicalStateName`, so
361
- * this routine assumes the lookup is in the states map.
362
- */
363
- async performTransition(active, toState, notes, actor) {
364
- if (!this.tracker.moveIssueToState) {
365
- throw new Error('this tracker does not support state transitions');
366
- }
367
- const fromRoot = active.trackerRootSnapshot ?? undefined;
368
- const fromState = active.entry.issue.state;
369
- const result = await this.tracker.moveIssueToState(active.issueId, toState, {
370
- fromRoot,
371
- fromState,
372
- notes: notes.length > 0 ? notes : undefined,
373
- actor,
374
- });
375
- active.entry.transitioned = true;
376
- // Look up the canonical declared name (preserving operator-supplied casing)
377
- // so the role lookup matches the workflow's `states:` map.
378
- const stateMap = this.states;
379
- const canonicalName = canonicalStateName(stateMap, result.toState);
380
- const targetIsTerminal = canonicalName !== null && stateMap[canonicalName].role === 'terminal';
381
- // Issue 38 / 144: suppress terminal cleanup when the PR engine owns this
382
- // state's workspace. The pr resource will reap the workspace once the
383
- // PR has merged (or been closed). Other terminal states (Cancelled
384
- // typically) still cleanup immediately — the close path only needs to talk
385
- // to GitHub, not the workspace.
386
- const suppressCleanupForMerge = targetIsTerminal &&
387
- canonicalName !== null &&
388
- this.prMerge.enabled &&
389
- this.prMerge.mergeState !== null &&
390
- canonicalName.toLowerCase() === this.prMerge.mergeState.toLowerCase();
391
- active.entry.cleanup_workspace_on_exit = targetIsTerminal && !suppressCleanupForMerge;
392
- // Mutate the entry's view of the issue's state so downstream code (runner's
393
- // cleanup, orchestrator's terminal-state workspace removal) resolves the right
394
- // state's `actions:`. Without this, the terminal-state cleanup actions would
395
- // resolve against the pre-transition state and a terminal-state handoff
396
- // (e.g. Done's push_branch + create_pr_if_missing) would never fire.
397
- active.entry.issue.state = canonicalName ?? result.toState;
398
- // Stash the transition so the orchestrator shell can fold it into the run
399
- // log as a `transition` lifecycle event after the attempt unwinds (issue
400
- // 123). Pure here — no IO; the run-log write happens host-side.
401
- active.entry.last_transition = {
402
- from_state: result.fromState,
403
- to_state: canonicalName ?? result.toState,
404
- notes,
405
- actor,
406
- terminal: targetIsTerminal,
407
- };
408
- return result;
409
- }
410
- /**
411
- * `symphony.transition({ to_state, notes? })`: validate the target against the
412
- * declared `states:` map + the current state's `allowed_transitions` list, then
413
- * delegate to `performTransition` which owns the actual file move and flag flip.
414
- *
415
- * Validation failures return MCP tool-result errors (`isError: true`) with a
416
- * human-readable text block AND a structured JSON block describing the error
417
- * shape. This is NOT a JSON-RPC `error` envelope — the SDK delivers it as a
418
- * normal tool result and the agent reads the structured payload to pick a valid
419
- * target on its next call. `transitioned` stays false; no file is touched.
420
- */
421
- async callTransition(active, id, args) {
422
- const toStateRaw = typeof args.to_state === 'string' ? args.to_state.trim() : '';
423
- const notes = typeof args.notes === 'string' ? args.notes : '';
424
- if (!toStateRaw) {
425
- return makeToolError(id, 'to_state is required and must be a non-empty string');
426
- }
427
- if (!this.tracker.moveIssueToState) {
428
- return makeToolError(id, 'this tracker does not support state transitions');
429
- }
430
- const stateMap = this.states;
431
- const declaredNames = Object.keys(stateMap);
432
- const canonicalTarget = canonicalStateName(stateMap, toStateRaw);
433
- if (canonicalTarget === null) {
434
- const text = `state "${toStateRaw}" is not declared. declared: ${declaredNames.length > 0 ? declaredNames.join(', ') : '<none>'}`;
435
- log.info('mcp transition rejected: unknown_state', {
436
- issue_identifier: active.identifier,
437
- requested_to_state: toStateRaw,
438
- declared_states: declaredNames,
439
- });
440
- return makeStructuredToolError(id, text, {
441
- error: 'unknown_state',
442
- declared_states: declaredNames,
443
- });
444
- }
445
- const fromStateRaw = active.entry.issue.state;
446
- const canonicalFrom = canonicalStateName(stateMap, fromStateRaw);
447
- if (canonicalFrom !== null) {
448
- // Per the brief: `allowed_transitions: null | undefined` => "any declared
449
- // state is reachable"; a present array => restrict to that list (empty
450
- // list => no transitions out, agent must wait for human cleanup).
451
- const allowed = stateMap[canonicalFrom].allowed_transitions;
452
- if (allowed) {
453
- const allowedLower = new Set(allowed.map((s) => s.toLowerCase()));
454
- if (!allowedLower.has(canonicalTarget.toLowerCase())) {
455
- const text = `transition to "${canonicalTarget}" is not allowed from "${canonicalFrom}". allowed: ${allowed.length > 0 ? allowed.join(', ') : '<none>'}`;
456
- log.info('mcp transition rejected: transition_not_allowed', {
457
- issue_identifier: active.identifier,
458
- from_state: canonicalFrom,
459
- requested_to_state: canonicalTarget,
460
- allowed_transitions: allowed,
461
- });
462
- return makeStructuredToolError(id, text, {
463
- error: 'transition_not_allowed',
464
- from_state: canonicalFrom,
465
- requested_to_state: canonicalTarget,
466
- allowed_transitions: allowed,
467
- });
468
- }
469
- }
470
- }
471
- const actor = active.entry.resolved_actor;
472
- try {
473
- const result = await this.performTransition(active, canonicalTarget, notes, actor);
474
- log.info('mcp transition', {
475
- issue_identifier: active.identifier,
476
- from: result.fromState,
477
- to: result.toState,
478
- notes_len: notes.length,
479
- actor,
480
- cleanup: active.entry.cleanup_workspace_on_exit,
481
- });
482
- return {
483
- jsonrpc: '2.0',
484
- id,
485
- result: {
486
- content: [
487
- {
488
- type: 'text',
489
- text: `Transitioned ${active.identifier} from ${result.fromState} to ${result.toState}${notes.length > 0 ? ` with notes (${notes.length} chars) appended` : ''}. End this turn now; the next dispatch will pick up under the new state.`,
490
- },
491
- ],
492
- isError: false,
493
- structuredContent: {
494
- from_state: result.fromState,
495
- to_state: result.toState,
496
- cleanup_workspace_on_exit: active.entry.cleanup_workspace_on_exit,
497
- notes_appended: notes.length > 0,
498
- },
499
- },
500
- };
501
- }
502
- catch (err) {
503
- return makeToolError(id, `failed to transition: ${err.message}`);
504
- }
505
- }
506
- callRequestHumanSteering(active, id, args) {
507
- const question = typeof args.question === 'string' ? args.question.trim() : '';
508
- const context = typeof args.context === 'string' ? args.context.trim() : '';
509
- if (!question) {
510
- return makeToolError(id, 'question is required and must be a non-empty string');
511
- }
512
- if (active.entry.steering_requested) {
513
- return makeToolError(id, 'a steering request is already pending for this issue; end the turn and wait for the response');
514
- }
515
- active.entry.steering_requested = true;
516
- active.entry.steering_question = question;
517
- active.entry.steering_context = context.length > 0 ? context : null;
518
- log.info('mcp request_human_steering', {
519
- issue_identifier: active.identifier,
520
- question_chars: question.length,
521
- has_context: context.length > 0,
522
- });
523
- return {
524
- jsonrpc: '2.0',
525
- id,
526
- result: {
527
- content: [
528
- {
529
- type: 'text',
530
- text: 'Question queued. End this turn now. The human response will arrive as the prompt for your next turn.',
531
- },
532
- ],
533
- isError: false,
534
- },
535
- };
536
- }
537
- /**
538
- * Drop a new issue file into the tracker's Triage/ directory. The orchestrator
539
- * never dispatches Triage entries because the state's role is `holding`, not
540
- * `active`; the operator approves or discards from the dashboard. Parent issue (the active
541
- * dispatch this MCP call came from) is stamped into the front-matter as
542
- * `proposed_by` so provenance is visible in the file and the UI.
543
- *
544
- * Uses the dispatch-time tracker root snapshot — same rationale as `transition`:
545
- * a WORKFLOW.md reload that mutates tracker.root mid-flight must not redirect
546
- * an in-flight propose call to a different filesystem location.
547
- */
548
- async callProposeIssue(active, id, args) {
549
- const titleRaw = typeof args.title === 'string' ? args.title.trim() : '';
550
- const description = typeof args.description === 'string' ? args.description : '';
551
- if (!titleRaw) {
552
- return makeToolError(id, 'title is required and must be a non-empty string');
553
- }
554
- if (titleRaw.includes('\n')) {
555
- return makeToolError(id, 'title must be a single line (no embedded newlines)');
556
- }
557
- const labels = Array.isArray(args.labels)
558
- ? args.labels.filter((x) => typeof x === 'string')
559
- : [];
560
- const priority = typeof args.priority === 'number' && Number.isFinite(args.priority) ? args.priority : null;
561
- // Resolve the tracker root: prefer the dispatch-time snapshot, fall back to the
562
- // tracker's live root (e.g. for tests / trackers without a snapshot). Without a
563
- // resolvable root we can't write the file, so surface a clean tool error.
564
- const root = active.trackerRootSnapshot ??
565
- (this.tracker.currentRoot ? this.tracker.currentRoot() : null);
566
- if (!root) {
567
- return makeToolError(id, 'tracker root is not available; cannot create issue files (is this a non-local tracker?)');
568
- }
569
- // Landing state: first declared `holding` state in declaration order. The
570
- // workflow parser refuses configs without one, but if validation was
571
- // bypassed we surface a structured `no_holding_state` error so the agent
572
- // doesn't keep retrying against a misconfigured workflow.
573
- let landingState;
574
- try {
575
- landingState = pickHoldingState(this.states);
576
- }
577
- catch (err) {
578
- if (err instanceof NoHoldingStateError) {
579
- return makeStructuredToolError(id, 'cannot propose issue: workflow has no holding-role state declared', {
580
- error: 'no_holding_state',
581
- declared_states: Object.keys(this.states),
582
- });
583
- }
584
- throw err;
585
- }
586
- try {
587
- const result = await writeIssueFile({
588
- trackerRoot: root,
589
- state: landingState,
590
- title: titleRaw,
591
- description,
592
- priority,
593
- labels,
594
- now: realClock,
595
- extra_front_matter: {
596
- proposed_by: active.identifier,
597
- proposed_at: isoFromClock(this.now),
598
- },
599
- });
600
- log.info('mcp propose_issue', {
601
- proposed_by: active.identifier,
602
- identifier: result.identifier,
603
- state: result.state,
604
- title: titleRaw,
605
- description_chars: description.length,
606
- });
607
- return {
608
- jsonrpc: '2.0',
609
- id,
610
- result: {
611
- content: [
612
- {
613
- type: 'text',
614
- text: `Proposed issue ${result.identifier} in ${result.state}/. The operator will approve or discard from the dashboard; do not wait for it. Continue your current task.`,
615
- },
616
- ],
617
- isError: false,
618
- // Structured data alongside the human-readable text — MCP clients that
619
- // surface this make the identifier programmatically available without
620
- // re-parsing the content string.
621
- structuredContent: {
622
- identifier: result.identifier,
623
- state: result.state,
624
- path: result.path,
625
- },
626
- },
627
- };
628
- }
629
- catch (err) {
630
- return makeToolError(id, `failed to propose issue: ${err.message}`);
631
- }
632
- }
633
- /** Snapshot of currently active issues, used by HTTP for routing lookups. */
634
- isActive(identifier, token) {
635
- const active = this.byIdentifier.get(identifier);
636
- return !!active && this.crypto.constantTimeEqual(active.token, token);
637
- }
638
- }
639
- function getId(body) {
640
- if (body && typeof body === 'object' && !Array.isArray(body)) {
641
- const rec = body;
642
- if (typeof rec.id === 'string' || typeof rec.id === 'number')
643
- return rec.id;
644
- }
645
- return null;
646
- }
647
- function isRpcRequest(body) {
648
- if (!body || typeof body !== 'object' || Array.isArray(body))
649
- return false;
650
- const rec = body;
651
- return rec.jsonrpc === '2.0' && typeof rec.method === 'string';
652
- }
653
- function makeError(id, code, message) {
654
- return { jsonrpc: '2.0', id, error: { code, message } };
655
- }
656
- function makeToolError(id, message) {
657
- // MCP convention: tool errors return result.isError=true rather than JSON-RPC error.
658
- return {
659
- jsonrpc: '2.0',
660
- id,
661
- result: {
662
- content: [{ type: 'text', text: message }],
663
- isError: true,
664
- },
665
- };
666
- }
667
- /**
668
- * MCP tool error with both a human-readable text block and a structured JSON
669
- * block. Used by `symphony.transition` so agents can read the rejection's
670
- * structured payload (`declared_states`, `allowed_transitions`, etc.) to pick a
671
- * valid target on their next call without re-parsing the prose. Pairs with
672
- * `makeToolError` (text-only) for everything else.
673
- */
674
- function makeStructuredToolError(id, text, json) {
675
- return {
676
- jsonrpc: '2.0',
677
- id,
678
- result: {
679
- // MCP 2025-06-18 `CallToolResult.content` is `ContentBlock[]` and does not
680
- // define a `json` block type; the canonical home for machine-readable
681
- // payloads is `structuredContent`. Keep only the text block in `content[]`
682
- // for human display and put the structured shape on the SDK-recognised slot.
683
- content: [{ type: 'text', text }],
684
- isError: true,
685
- structuredContent: json,
686
- },
687
- };
688
- }
689
- /**
690
- * Resolve a caller-supplied state name to its canonical declared form (the
691
- * casing the operator wrote in `states:`). Comparison is case-insensitive to
692
- * mirror the rest of symphony (eligibility, reconciliation, the local-tracker
693
- * directory scan all compare lowercase). Returns null when no declared name
694
- * matches.
695
- */
696
- function canonicalStateName(states, name) {
697
- if (Object.prototype.hasOwnProperty.call(states, name))
698
- return name;
699
- const lower = name.toLowerCase();
700
- for (const declared of Object.keys(states)) {
701
- if (declared.toLowerCase() === lower)
702
- return declared;
703
- }
704
- return null;
705
- }
706
- //# sourceMappingURL=mcp.js.map