@iowarp/clio-coder 0.3.6 → 0.3.8

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 (323) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +22 -6
  3. package/dist/{acp-2BEHC4DL.js → acp-U67UHUK2.js} +14 -14
  4. package/dist/{agents-LNNFTM53.js → agents-YU6SGALZ.js} +42 -35
  5. package/dist/assets/codewiki.json +1 -1
  6. package/dist/{auth-KXXFI2VS.js → auth-5ZPJOIVG.js} +32 -24
  7. package/dist/builtins-C6JMZVV6.js +17 -0
  8. package/dist/{chunk-E25LMLRW.js → chunk-26LEYJZH.js} +2 -2
  9. package/dist/{chunk-TSHXZTOQ.js → chunk-2HEJ2F35.js} +5 -5
  10. package/dist/{chunk-PBTHKCPN.js → chunk-2HFZQUHL.js} +7 -7
  11. package/dist/{chunk-FO5ZOVUY.js → chunk-3BINW3FP.js} +5 -5
  12. package/dist/{chunk-4OC57DA6.js → chunk-4DGYLA73.js} +53 -2
  13. package/dist/{chunk-CKXWIANG.js → chunk-4SPRNWDE.js} +18 -16
  14. package/dist/{chunk-E2ER4LJF.js → chunk-5C3AQNDW.js} +25 -1
  15. package/dist/{chunk-XF5N4U5A.js → chunk-5DHKRSMQ.js} +9 -8
  16. package/dist/{chunk-43AOLP7E.js → chunk-5FR74PWO.js} +2 -1
  17. package/dist/{chunk-EKY57CSP.js → chunk-5H3GB5BO.js} +68 -772
  18. package/dist/{chunk-6XXKFVSN.js → chunk-5Q2VVUKB.js} +4 -4
  19. package/dist/{chunk-DJVECN66.js → chunk-7RFXX52T.js} +295 -3705
  20. package/dist/{chunk-ZXF4XRKW.js → chunk-7RGZWPB6.js} +158 -8
  21. package/dist/{chunk-FYYLNIL5.js → chunk-A2NJGIB3.js} +2 -2
  22. package/dist/{chunk-XXQNGV4M.js → chunk-A3WNZD3P.js} +548 -72
  23. package/dist/{chunk-VEZEGCGW.js → chunk-B5CSFE7B.js} +23 -21
  24. package/dist/{chunk-2SFS6XQE.js → chunk-DGSYXYMX.js} +3 -2
  25. package/dist/chunk-DR52UMZW.js +21 -0
  26. package/dist/{chunk-ZWLZP4ZT.js → chunk-DYIM5TJT.js} +98 -12
  27. package/dist/{chunk-QKMUKYO7.js → chunk-E77JEWSD.js} +270 -125
  28. package/dist/{chunk-LYF7OHWH.js → chunk-EMYUUSFG.js} +12 -467
  29. package/dist/{chunk-4VP4KH3K.js → chunk-EQ63NRB7.js} +8 -8
  30. package/dist/chunk-FBVTI2TJ.js +518 -0
  31. package/dist/{chunk-R46L2BIR.js → chunk-FHJEP5SW.js} +19 -25
  32. package/dist/{chunk-WR67VIZY.js → chunk-GN57SG4G.js} +66 -8
  33. package/dist/{chunk-RD5U66HV.js → chunk-GPIEI3LY.js} +9 -9
  34. package/dist/{chunk-PCZJO5TI.js → chunk-GU2UIAFZ.js} +13 -178
  35. package/dist/chunk-GWS3VEIW.js +195 -0
  36. package/dist/chunk-H7IXIC72.js +103 -0
  37. package/dist/{chunk-3BPUFZDL.js → chunk-HLE42MG7.js} +3 -3
  38. package/dist/{chunk-24I7BN55.js → chunk-IFBNV6H6.js} +3 -3
  39. package/dist/{chunk-QNQHSOLF.js → chunk-IGWKHNIQ.js} +114 -55
  40. package/dist/chunk-IIZWH4XA.js +172 -0
  41. package/dist/{chunk-AD2SYQYC.js → chunk-IJ7RPIYJ.js} +124 -6
  42. package/dist/chunk-J3YUBZWY.js +382 -0
  43. package/dist/{chunk-5JGRAMKL.js → chunk-JOZYP4GM.js} +8 -6
  44. package/dist/{chunk-OH3TOQTB.js → chunk-K4XHGFR5.js} +751 -15
  45. package/dist/{chunk-EYPA3EGJ.js → chunk-KTYTFRMB.js} +178 -14
  46. package/dist/chunk-LU7P4LHA.js +33 -0
  47. package/dist/chunk-ME6CCNFO.js +108 -0
  48. package/dist/chunk-MXKJU4JB.js +1100 -0
  49. package/dist/{chunk-RY3LY4J5.js → chunk-N22QMJKY.js} +21 -18
  50. package/dist/chunk-NMPKI6XL.js +3006 -0
  51. package/dist/chunk-NUGM5KR6.js +165 -0
  52. package/dist/{chunk-22NAGB7X.js → chunk-P43ETTHK.js} +5 -94
  53. package/dist/{chunk-NILBFAPG.js → chunk-PMDBGQSJ.js} +2 -2
  54. package/dist/chunk-PT7HYKEM.js +165 -0
  55. package/dist/chunk-RVG5JXAL.js +41 -0
  56. package/dist/{chunk-GEYXPTRF.js → chunk-RWSI4YD7.js} +2 -2
  57. package/dist/{chunk-4BPJXDWC.js → chunk-TANS5ZJS.js} +35 -19
  58. package/dist/{verifiers-NCBTHHN2.js → chunk-TB5666IT.js} +67 -324
  59. package/dist/{chunk-QM3F2GKX.js → chunk-TLQJPP24.js} +7927 -8088
  60. package/dist/{chunk-G7MUEIGA.js → chunk-TT36MB5S.js} +2 -1
  61. package/dist/chunk-TTHACPOM.js +961 -0
  62. package/dist/{chunk-6US73PDB.js → chunk-TYPGUK6W.js} +7 -7
  63. package/dist/{chunk-MFFY33HR.js → chunk-U6MBIEMB.js} +554 -209
  64. package/dist/chunk-VAWNZU7Z.js +242 -0
  65. package/dist/{chunk-IR4CFBFN.js → chunk-VCBR6CU7.js} +12 -12
  66. package/dist/{chunk-WHJYKASB.js → chunk-VHN4MY6O.js} +2 -2
  67. package/dist/{chunk-XYDYPRZI.js → chunk-VWZOAB7K.js} +10 -10
  68. package/dist/{chunk-ZRGEBJ4T.js → chunk-WLFILSD5.js} +48 -48
  69. package/dist/{chunk-K7T3E2SR.js → chunk-WNIJTQQK.js} +12 -11
  70. package/dist/{chunk-CYQKWTG3.js → chunk-WSB3FPX7.js} +68 -20
  71. package/dist/{chunk-KHSFENX2.js → chunk-WWCZ5F23.js} +116 -14
  72. package/dist/{chunk-XE2VEJHX.js → chunk-WXY7KU3G.js} +2 -2
  73. package/dist/{chunk-PPAMZ32Z.js → chunk-XK56QHLX.js} +6 -1
  74. package/dist/{chunk-KOHPCX4K.js → chunk-XWSF374K.js} +5 -5
  75. package/dist/{chunk-CJUB2JJ2.js → chunk-YS5VLNH5.js} +10 -10
  76. package/dist/{chunk-ZZMN5OM4.js → chunk-ZNLWCMVZ.js} +2 -2
  77. package/dist/{chunk-WHGPSPT5.js → chunk-ZVJ5BLO2.js} +2 -2
  78. package/dist/cli/index.js +33 -31
  79. package/dist/{clio-M2KGYUFZ.js → clio-QVTYJ57A.js} +10 -10
  80. package/dist/{code-nav-GQNL7XA6.js → code-nav-FGGFIE7L.js} +5 -5
  81. package/dist/codewiki/build-worker.js +4 -4
  82. package/dist/{components-5TTYYX6G.js → components-ZFA3SAER.js} +9 -9
  83. package/dist/{config-XUUYQIWO.js → config-LW5IJFQN.js} +65 -55
  84. package/dist/{configure-IHJ7YOMV.js → configure-7XIZCOU4.js} +29 -24
  85. package/dist/{context-75MIWW3U.js → context-L3WL3X7K.js} +54 -43
  86. package/dist/{context-ZQ7SIFJV.js → context-N52ZA626.js} +30 -14
  87. package/dist/{context-74JLXAWD.js → context-Y6Y7QPR6.js} +12 -12
  88. package/dist/{context-clear-GYKWNUML.js → context-clear-MBQRLSDQ.js} +54 -43
  89. package/dist/{context-index-SSR5ECNE.js → context-index-HVMFQHK3.js} +5 -5
  90. package/dist/{context-working-set-UX5KEP4J.js → context-working-set-GS6DSO7F.js} +18 -18
  91. package/dist/{dispatch-runner-GIJBHNFL.js → dispatch-runner-22ZCNOM3.js} +367 -79
  92. package/dist/{docs-6FZSCG5B.js → docs-7LQ23DLM.js} +9 -9
  93. package/dist/{doctor-SVJ5BZCW.js → doctor-M7YEDGAE.js} +27 -23
  94. package/dist/{eval-CG6LLBLD.js → eval-BEC2WHDA.js} +73 -20
  95. package/dist/{evidence-ZYFIEN42.js → evidence-REJUMSKM.js} +70 -59
  96. package/dist/{evolve-QGEXEMDW.js → evolve-PY5ZBA5K.js} +50 -39
  97. package/dist/{extensions-ADGNCJJD.js → extensions-HVKU65YU.js} +7 -7
  98. package/dist/{fleet-S5R4ZOQY.js → fleet-7WZEWRFA.js} +236 -377
  99. package/dist/fleet-commands-UVHWM76J.js +70 -0
  100. package/dist/fleet-graph-6ULH7PES.js +125 -0
  101. package/dist/fleet-new-RDVJLHHH.js +48 -0
  102. package/dist/{fleet-preflight-BHSNPBMH.js → fleet-preflight-J53T6CCE.js} +5 -5
  103. package/dist/fleet-validate-72PC4SLA.js +79 -0
  104. package/dist/{init-5DRU55YR.js → init-OG3TPGQG.js} +69 -57
  105. package/dist/library-CNTMPLRF.js +217 -0
  106. package/dist/{memory-7YKKR6UC.js → memory-6IS7F275.js} +52 -41
  107. package/dist/{models-ZPOLRU2C.js → models-ENRJDA5W.js} +37 -31
  108. package/dist/{monitor-US5F5YGZ.js → monitor-XLDVO7TN.js} +79 -42
  109. package/dist/{orchestrator-E2AL4T5N.js → orchestrator-6KSPYRHA.js} +4126 -762
  110. package/dist/{paths-E7KYAQWE.js → paths-DBXMZMDU.js} +6 -6
  111. package/dist/registry-LG64LTF4.js +11 -0
  112. package/dist/{reset-KZ652EK6.js → reset-RZ4ER727.js} +12 -12
  113. package/dist/{run-SRNBKDWD.js → run-Y2CNK5RU.js} +89 -76
  114. package/dist/{share-CGZE33UP.js → share-A55GYP6Z.js} +37 -13
  115. package/dist/{skills-S2X4DLY5.js → skills-ALC5J6AT.js} +29 -13
  116. package/dist/{skills-eval-W2GGIC4R.js → skills-eval-JPBEBYQU.js} +60 -48
  117. package/dist/support-MIETYA5E.js +38 -0
  118. package/dist/{targets-54SWINWB.js → targets-VGNXIR3S.js} +46 -36
  119. package/dist/{terminal-lease-SAIF2OGY.js → terminal-lease-WOBR64YA.js} +4 -4
  120. package/dist/{uninstall-BVLWXKBT.js → uninstall-ZJF5H5ZN.js} +9 -9
  121. package/dist/{upgrade-JKAR27XC.js → upgrade-FUSUAGHR.js} +31 -28
  122. package/dist/{usage-MSAWCLX4.js → usage-N4MKVHKD.js} +140 -68
  123. package/dist/verifiers-YAWOJ3H2.js +336 -0
  124. package/dist/{verify-X5HDROLA.js → verify-LTDHYBGY.js} +10 -9
  125. package/dist/{wiki-generate-GUSOQ6ZP.js → wiki-generate-6M7GHTBJ.js} +75 -62
  126. package/dist/worker/entry.js +67 -57
  127. package/docs/README.md +3 -2
  128. package/docs/acp.md +1 -1
  129. package/docs/alcf-provider.md +1 -1
  130. package/docs/architecture.md +2 -2
  131. package/docs/artifact-versions.md +11 -6
  132. package/docs/built-in-agents.md +26 -2
  133. package/docs/capacity-and-scheduling.md +1 -1
  134. package/docs/commands-and-modes.md +83 -3
  135. package/docs/configuration-and-targets.md +86 -4
  136. package/docs/context-engine.md +1 -1
  137. package/docs/development-pipeline.md +1 -1
  138. package/docs/dispatch-architecture-rationale.md +1 -1
  139. package/docs/documentation-coverage.md +3 -3
  140. package/docs/documentation-guide.md +3 -3
  141. package/docs/eval-runner.md +1 -1
  142. package/docs/evals-internal.md +1 -1
  143. package/docs/evidence-and-memory.md +74 -10
  144. package/docs/evolution.md +1 -1
  145. package/docs/exit-codes-and-output.md +4 -1
  146. package/docs/extensions-and-sharing.md +6 -2
  147. package/docs/fleet-demo-runbook.md +2 -2
  148. package/docs/fleet-dispatch.md +228 -16
  149. package/docs/git-commit-provenance.md +2 -2
  150. package/docs/glossary.md +22 -2
  151. package/docs/installation-and-lifecycle.md +2 -2
  152. package/docs/middleware-and-components.md +2 -1
  153. package/docs/model-catalog.md +1 -1
  154. package/docs/observability.md +56 -9
  155. package/docs/proactive-memory.md +1 -1
  156. package/docs/prompt-envelope-and-tools.md +4 -2
  157. package/docs/provider-adapter-cookbook.md +1 -1
  158. package/docs/release-cut-checklist.md +83 -65
  159. package/docs/resource-library.md +59 -0
  160. package/docs/safety-model.md +2 -2
  161. package/docs/scientific-validation.md +3 -3
  162. package/docs/session-lifecycle.md +37 -1
  163. package/docs/skills-marketplace.md +16 -3
  164. package/docs/tool-usage.md +14 -7
  165. package/docs/trace-store.md +1 -1
  166. package/docs/troubleshooting.md +1 -1
  167. package/docs/tui-design.md +1 -1
  168. package/docs/worker-dispatch-mechanics.md +3 -3
  169. package/package.json +1 -2
  170. package/src/cli/argv.ts +5 -0
  171. package/src/cli/configure.ts +107 -23
  172. package/src/cli/doctor.ts +5 -1
  173. package/src/cli/evidence.ts +30 -25
  174. package/src/cli/fleet-commands.ts +37 -0
  175. package/src/cli/fleet-graph.ts +102 -0
  176. package/src/cli/fleet-new.ts +36 -0
  177. package/src/cli/fleet-preflight.ts +111 -0
  178. package/src/cli/fleet-validate.ts +30 -0
  179. package/src/cli/fleet.ts +173 -335
  180. package/src/cli/index.ts +3 -1
  181. package/src/cli/library.ts +190 -0
  182. package/src/cli/share.ts +13 -1
  183. package/src/cli/shared.ts +1 -0
  184. package/src/cli/targets.ts +4 -1
  185. package/src/cli/usage.ts +111 -19
  186. package/src/cli/validate-model.ts +60 -5
  187. package/src/core/bus-events.ts +29 -0
  188. package/src/core/commit-attribution.ts +4 -4
  189. package/src/core/config.ts +130 -0
  190. package/src/core/defaults.ts +81 -0
  191. package/src/core/path-boundary.ts +100 -0
  192. package/src/domains/agents/builtins/architect.md +1 -0
  193. package/src/domains/agents/builtins/oracle.md +33 -0
  194. package/src/domains/agents/catalog.ts +13 -1
  195. package/src/domains/agents/extension.ts +2 -11
  196. package/src/domains/agents/fleet-contract.ts +304 -24
  197. package/src/domains/agents/index.ts +14 -0
  198. package/src/domains/agents/recipe.ts +7 -1
  199. package/src/domains/agents/registry.ts +73 -5
  200. package/src/domains/agents/result-contract.ts +360 -15
  201. package/src/domains/agents/write-boundary.ts +15 -50
  202. package/src/domains/config/classify.ts +4 -0
  203. package/src/domains/context/project-rules.ts +51 -1
  204. package/src/domains/dispatch/active-route-planner.ts +14 -0
  205. package/src/domains/dispatch/assignment-reconcile.ts +22 -5
  206. package/src/domains/dispatch/assignment-store.ts +151 -14
  207. package/src/domains/dispatch/backoff.ts +2 -1
  208. package/src/domains/dispatch/capability-match.ts +1 -0
  209. package/src/domains/dispatch/checkout-writer-lease.ts +175 -0
  210. package/src/domains/dispatch/contract.ts +48 -0
  211. package/src/domains/dispatch/delegation-plan.ts +164 -0
  212. package/src/domains/dispatch/execution-plan.ts +76 -5
  213. package/src/domains/dispatch/execution-role.ts +12 -2
  214. package/src/domains/dispatch/execution-scheduler.ts +183 -67
  215. package/src/domains/dispatch/extension.ts +412 -74
  216. package/src/domains/dispatch/fleet-gate.ts +14 -0
  217. package/src/domains/dispatch/fleet-plan.ts +63 -3
  218. package/src/domains/dispatch/fleet-run.ts +791 -0
  219. package/src/domains/dispatch/gate-role-prompts.ts +47 -0
  220. package/src/domains/dispatch/host-verification.ts +178 -0
  221. package/src/domains/dispatch/index.ts +41 -1
  222. package/src/domains/dispatch/intent-requirements.ts +40 -0
  223. package/src/domains/dispatch/intent.ts +235 -0
  224. package/src/domains/dispatch/path-scope.ts +370 -0
  225. package/src/domains/dispatch/receipt-integrity.ts +9 -4
  226. package/src/domains/dispatch/state.ts +36 -3
  227. package/src/domains/dispatch/types.ts +64 -12
  228. package/src/domains/dispatch/validation.ts +69 -6
  229. package/src/domains/dispatch/write-boundary-enforcer.ts +45 -0
  230. package/src/domains/dispatch/write-boundary.ts +201 -22
  231. package/src/domains/eval/metrics/evidence.ts +79 -2
  232. package/src/domains/eval/runners/clio-run.ts +12 -2
  233. package/src/domains/evidence/build.ts +69 -11
  234. package/src/domains/evidence/index.ts +21 -0
  235. package/src/domains/evidence/provenance.ts +46 -11
  236. package/src/domains/evidence/trust-projection.ts +274 -0
  237. package/src/domains/evidence/trust-status.ts +155 -18
  238. package/src/domains/evidence/types.ts +4 -0
  239. package/src/domains/extensions/discovery.ts +88 -1
  240. package/src/domains/extensions/resources.ts +20 -8
  241. package/src/domains/extensions/state.ts +6 -2
  242. package/src/domains/extensions/types.ts +4 -1
  243. package/src/domains/lifecycle/doctor.ts +140 -1
  244. package/src/domains/middleware/index.ts +15 -0
  245. package/src/domains/middleware/watchdog.ts +281 -0
  246. package/src/domains/observability/contract.ts +3 -1
  247. package/src/domains/observability/cost.ts +12 -1
  248. package/src/domains/observability/extension.ts +2 -2
  249. package/src/domains/observability/index.ts +10 -0
  250. package/src/domains/observability/out-of-turn-usage.ts +223 -0
  251. package/src/domains/prompts/contract.ts +3 -5
  252. package/src/domains/providers/extension.ts +30 -2
  253. package/src/domains/resources/common-loader.ts +3 -0
  254. package/src/domains/resources/index.ts +20 -0
  255. package/src/domains/resources/library.ts +326 -0
  256. package/src/domains/resources/prompts/loader.ts +119 -14
  257. package/src/domains/resources/skills/marketplace.ts +37 -12
  258. package/src/domains/safety/policy-engine.ts +5 -5
  259. package/src/domains/safety/run-effects.ts +64 -1
  260. package/src/domains/safety/scope.ts +7 -12
  261. package/src/domains/session/handoff.ts +629 -0
  262. package/src/domains/share/archive.ts +67 -2
  263. package/src/engine/acp/server.ts +4 -1
  264. package/src/engine/prompt-templates.ts +18 -1
  265. package/src/engine/worker-runtime.ts +6 -3
  266. package/src/entry/orchestrator.ts +37 -0
  267. package/src/interactive/bus-notices.ts +26 -0
  268. package/src/interactive/chat-loop.ts +235 -1
  269. package/src/interactive/chat-renderer.ts +22 -0
  270. package/src/interactive/cost-overlay.ts +31 -3
  271. package/src/interactive/council-dispatch.ts +30 -0
  272. package/src/interactive/council-grid.ts +213 -0
  273. package/src/interactive/council.ts +99 -0
  274. package/src/interactive/dispatch-board.ts +311 -17
  275. package/src/interactive/fleet-run-preview.ts +307 -0
  276. package/src/interactive/footer/notifications.ts +219 -0
  277. package/src/interactive/handoff-round.ts +56 -0
  278. package/src/interactive/interactive-application.ts +43 -1
  279. package/src/interactive/interactive-event-projection.ts +23 -1
  280. package/src/interactive/interactive-slash-runtime.ts +52 -2
  281. package/src/interactive/interactive-subscriptions.ts +14 -2
  282. package/src/interactive/oracle.ts +179 -0
  283. package/src/interactive/overlay-ask-user-lifecycle.ts +6 -0
  284. package/src/interactive/overlay-general-openers.ts +190 -1
  285. package/src/interactive/overlay-key-routing.ts +17 -1
  286. package/src/interactive/overlay-lifecycle.ts +41 -1
  287. package/src/interactive/overlay-permission-lifecycle.ts +10 -0
  288. package/src/interactive/overlay-resource-openers.ts +11 -3
  289. package/src/interactive/overlay-session-lifecycle.ts +234 -2
  290. package/src/interactive/overlays/fleet-run-approval.ts +208 -0
  291. package/src/interactive/overlays/handoff-review.ts +185 -0
  292. package/src/interactive/overlays/library-install-confirm.ts +151 -0
  293. package/src/interactive/overlays/list-overlay.ts +168 -2
  294. package/src/interactive/overlays/settings.ts +221 -30
  295. package/src/interactive/overlays/side-question.ts +139 -0
  296. package/src/interactive/overlays/skills-hub.ts +401 -15
  297. package/src/interactive/side-question.ts +171 -0
  298. package/src/interactive/slash-commands.ts +439 -7
  299. package/src/interactive/slash-spec.ts +19 -6
  300. package/src/interactive/theme/tokens.ts +30 -0
  301. package/src/interactive/turn-middleware.ts +15 -1
  302. package/src/interactive/view/artifacts.ts +42 -9
  303. package/src/interactive/view/view-overlay.ts +15 -3
  304. package/src/interactive/watchdog-run.ts +75 -0
  305. package/src/interactive/worker-receipts.ts +14 -2
  306. package/src/interactive/worker-share.ts +56 -1
  307. package/src/interactive/worker-stream.ts +15 -0
  308. package/src/tools/bootstrap.ts +3 -0
  309. package/src/tools/compete-worktrees.ts +13 -79
  310. package/src/tools/dispatch-admission.ts +251 -18
  311. package/src/tools/dispatch-arguments.ts +84 -1
  312. package/src/tools/dispatch-plan.ts +165 -6
  313. package/src/tools/dispatch-runner.ts +364 -23
  314. package/src/tools/dispatch-types.ts +20 -1
  315. package/src/tools/dispatch.ts +72 -2
  316. package/src/tools/monitor.ts +29 -0
  317. package/src/tools/profiles.ts +18 -4
  318. package/src/tools/task-worktree.ts +238 -0
  319. package/src/tools/verify/authoring.ts +61 -1
  320. package/src/tools/verify/scripts.ts +62 -0
  321. package/src/tools/worker-evidence.ts +21 -14
  322. package/src/worker/spec-contract.ts +3 -1
  323. package/dist/chunk-HC4CLZ2Y.js +0 -68
@@ -1,8 +1,15 @@
1
- import { resolve } from "node:path";
1
+ import { isAbsolute, relative, resolve } from "node:path";
2
2
  import type { DispatchPlanTaskResolution, DispatchRequest } from "../domains/dispatch/contract.js";
3
3
  import type { ExecutionPlan } from "../domains/dispatch/execution-plan.js";
4
4
  import { gateDeciderAgentId } from "../domains/dispatch/execution-role.js";
5
- import { JUDGE_GATE_PROMPT, REVIEWER_GATE_PROMPT } from "../domains/dispatch/gate-role-prompts.js";
5
+ import {
6
+ COUNCIL_JUDGE_PROMPT,
7
+ JUDGE_GATE_PROMPT,
8
+ REVIEWER_GATE_PROMPT,
9
+ renderCouncilVoteMemberTask,
10
+ } from "../domains/dispatch/gate-role-prompts.js";
11
+ import { normalizeDispatchIntent } from "../domains/dispatch/intent.js";
12
+ import { renderDispatchReviewerTask } from "../domains/dispatch/intent-requirements.js";
6
13
  import { approvedRouteCandidates } from "../domains/dispatch/route-approval.js";
7
14
  import { defaultRoutingIntent } from "../domains/dispatch/routing-intent.js";
8
15
  import type { DispatchFailoverMode } from "../domains/dispatch/validation.js";
@@ -29,12 +36,15 @@ import {
29
36
  } from "./dispatch-scout-admission.js";
30
37
  import type {
31
38
  DispatchCompeteSettings,
39
+ DispatchCouncilSettings,
32
40
  DispatchExecutionSnapshot,
33
41
  DispatchMode,
34
42
  DispatchReviewSettings,
35
43
  DispatchToolDeps,
36
44
  } from "./dispatch-types.js";
37
45
  import type { ToolSpec } from "./registry.js";
46
+ import { gitCheckoutRoot } from "./task-worktree.js";
47
+ import { discoverDeclaredChecks } from "./verify/scripts.js";
38
48
 
39
49
  /**
40
50
  * The six identity-sensitive stores which bind synchronous admission to the
@@ -68,18 +78,67 @@ const COMPETE_SINGLE_TASK_MESSAGE = "dispatch: compete requires exactly one task
68
78
  const COMPETE_NO_REVIEW_MESSAGE = "dispatch: compete has its own judge and cannot combine with review";
69
79
  const COMPETE_MIN_CANDIDATES = 2;
70
80
  const COMPETE_MAX_CANDIDATES = 4;
81
+ const COUNCIL_LABEL = /^[a-z][a-z0-9_-]{0,31}$/u;
71
82
 
72
- function isRecord(value: unknown): value is Record<string, unknown> {
73
- return typeof value === "object" && value !== null && !Array.isArray(value);
83
+ function councilSettingsFromArgs(
84
+ args: Record<string, unknown>,
85
+ deps: DispatchToolDeps,
86
+ ): { ok: true; council: DispatchCouncilSettings } | { ok: false; message: string } {
87
+ const rosterName = stringArg(args, "roster");
88
+ const hasMembers = Object.hasOwn(args, "members");
89
+ if ((rosterName === undefined) === !hasMembers) {
90
+ return { ok: false, message: "dispatch: council requires exactly one of roster or members" };
91
+ }
92
+ let members: DispatchCouncilSettings["members"];
93
+ if (rosterName !== undefined) {
94
+ const roster = deps.getWorkerRosters?.()[rosterName];
95
+ if (roster === undefined) return { ok: false, message: `council_roster_unknown: ${rosterName}` };
96
+ members = roster.members.map((member) => ({ ...member }));
97
+ } else {
98
+ if (!Array.isArray(args.members)) return { ok: false, message: "council_members_out_of_range" };
99
+ members = [];
100
+ for (const raw of args.members) {
101
+ if (!isRecord(raw)) return { ok: false, message: "council_members_out_of_range" };
102
+ const label = stringArg(raw, "label");
103
+ const target = stringArg(raw, "target");
104
+ if (label === undefined || target === undefined || !COUNCIL_LABEL.test(label)) {
105
+ return { ok: false, message: "council_members_out_of_range" };
106
+ }
107
+ const model = stringArg(raw, "model");
108
+ const thinking = stringArg(raw, "thinking");
109
+ members.push({ label, target, ...(model ? { model } : {}), ...(thinking ? { thinking } : {}) });
110
+ }
111
+ }
112
+ if (members.length < 2 || members.length > 5) return { ok: false, message: "council_members_out_of_range" };
113
+ const labels = new Set<string>();
114
+ for (const member of members) {
115
+ if (labels.has(member.label)) return { ok: false, message: `council_member_label_duplicate: ${member.label}` };
116
+ labels.add(member.label);
117
+ }
118
+ const synthesis = args.synthesis === undefined ? "none" : args.synthesis;
119
+ if (synthesis !== "none" && synthesis !== "judge" && synthesis !== "vote") {
120
+ return { ok: false, message: "dispatch: synthesis must be none, judge, or vote" };
121
+ }
122
+ if (args.judge !== undefined && synthesis !== "judge") {
123
+ return { ok: false, message: "council_synthesis_requires_judge_settings" };
124
+ }
125
+ const rounds = args.rounds === undefined ? 1 : args.rounds;
126
+ if (!Number.isInteger(rounds) || Number(rounds) < 1 || Number(rounds) > 3) {
127
+ return { ok: false, message: "dispatch: rounds must be an integer 1..3" };
128
+ }
129
+ const judge = isRecord(args.judge)
130
+ ? Object.fromEntries(
131
+ ["agent", "model", "target", "node"].flatMap((key) => {
132
+ const value = stringArg(args.judge as Record<string, unknown>, key);
133
+ return value === undefined ? [] : [[key, value]];
134
+ }),
135
+ )
136
+ : undefined;
137
+ return { ok: true, council: { members, synthesis, rounds: Number(rounds), ...(judge ? { judge } : {}) } };
74
138
  }
75
139
 
76
- function reviewerTask(originalTask: string, builderRunId: string, cycle: number): string {
77
- return [
78
- `Review the work of builder run ${builderRunId} (review cycle ${cycle}).`,
79
- "The builder's final answer is provided as input data; verify it against the workspace, do not trust it blindly.",
80
- "Original task the builder was given:",
81
- originalTask,
82
- ].join("\n\n");
140
+ function isRecord(value: unknown): value is Record<string, unknown> {
141
+ return typeof value === "object" && value !== null && !Array.isArray(value);
83
142
  }
84
143
 
85
144
  function reviewSettingsFromArgs(
@@ -165,6 +224,28 @@ export function createDispatchAdmissionController(deps: DispatchToolDeps): Dispa
165
224
  approvedAuthorities: ["read-only", "verification", "artifact-write", "workspace-edit"],
166
225
  authorityBasis: deps.getAutonomy?.() === "full-auto" ? "full-auto-policy" : "operator-plan-approval",
167
226
  },
227
+ resolveIntent(rawIntent, cwd) {
228
+ const discovery = discoverDeclaredChecks(cwd);
229
+ if (!discovery.ok) return { ok: false, message: `verification_catalog_invalid: ${discovery.reason}` };
230
+ const declared = discovery.sources.flatMap((source) => source.checks);
231
+ const byId = new Map(declared.map((check) => [check.id, check]));
232
+ const normalized = normalizeDispatchIntent(rawIntent, byId);
233
+ if (!normalized.ok) return { ok: false, message: `${normalized.reason}: ${normalized.message}` };
234
+ return {
235
+ ok: true,
236
+ intent: normalized.intent,
237
+ resolvedVerification: normalized.intent.verification.map((entry) => {
238
+ const check = byId.get(entry.check);
239
+ if (check === undefined) throw new Error(`resolved verification check '${entry.check}' disappeared`);
240
+ return {
241
+ check: entry.check,
242
+ argv: [...check.command],
243
+ cwd: resolve(process.cwd(), check.cwd),
244
+ timeoutMs: entry.timeoutMs,
245
+ };
246
+ }),
247
+ };
248
+ },
168
249
  });
169
250
  const trustExecution = (args: Record<string, unknown>, snapshot: DispatchExecutionSnapshot) => {
170
251
  state.trustedExecutionSnapshots.set(args, deepFreeze(structuredClone(snapshot)));
@@ -172,11 +253,15 @@ export function createDispatchAdmissionController(deps: DispatchToolDeps): Dispa
172
253
  };
173
254
  const dispatchExecutionSnapshot = (
174
255
  args: Record<string, unknown>,
175
- fields: Pick<DispatchExecutionSnapshot & { kind: "dispatch" }, "requests" | "mode" | "review" | "compete">,
256
+ fields: Pick<
257
+ DispatchExecutionSnapshot & { kind: "dispatch" },
258
+ "requests" | "mode" | "review" | "compete" | "council"
259
+ >,
176
260
  ): DispatchExecutionSnapshot => ({
177
261
  kind: "dispatch",
178
262
  planView: describeDispatchPlan(args),
179
263
  ...fields,
264
+ writers: args.writers === 1 ? 1 : undefined,
180
265
  detach: args.detach === true,
181
266
  timeoutMs: timeoutMsArg(args),
182
267
  maxOutputBytes: maxOutputBytesArg(args),
@@ -217,6 +302,9 @@ export function createDispatchAdmissionController(deps: DispatchToolDeps): Dispa
217
302
  ): ResolvedPlanTask => {
218
303
  const resolution = deps.dispatch.preview?.(request);
219
304
  if (resolution === undefined) throw new Error("dispatch preview is unavailable");
305
+ if (request.resolvedVerification?.length && resolution.runtimeId === "claude-code") {
306
+ throw new Error("verification_unsupported_runtime: claude-code subprocess dispatch cannot run host verification");
307
+ }
220
308
  const pinned = request.node !== undefined || request.target !== undefined || request.model !== undefined;
221
309
  const approvedCandidates =
222
310
  pinned || request.failover !== "approved"
@@ -229,6 +317,15 @@ export function createDispatchAdmissionController(deps: DispatchToolDeps): Dispa
229
317
  agent: resolution.agentId,
230
318
  task: request.task,
231
319
  ...(request.briefing !== undefined ? { briefing: request.briefing } : {}),
320
+ ...(request.worktree === true
321
+ ? {
322
+ worktree: true as const,
323
+ apply: request.apply ?? "merge",
324
+ ...(gitCheckoutRoot(resolve(request.cwd ?? process.cwd())) === null
325
+ ? {}
326
+ : { worktreeDestination: gitCheckoutRoot(resolve(request.cwd ?? process.cwd())) as string }),
327
+ }
328
+ : {}),
232
329
  target: resolution.targetId,
233
330
  model: resolution.wireModelId,
234
331
  node: resolution.node.id,
@@ -248,6 +345,10 @@ export function createDispatchAdmissionController(deps: DispatchToolDeps): Dispa
248
345
  ...(failover === "approved" ? { allowedCandidates: approvedCandidates.map((candidate) => ({ ...candidate })) } : {}),
249
346
  role,
250
347
  position,
348
+ ...(request.intent !== undefined ? { intent: structuredClone(request.intent) } : {}),
349
+ ...(request.resolvedVerification !== undefined
350
+ ? { resolvedVerification: request.resolvedVerification.map((check) => ({ ...check, argv: [...check.argv] })) }
351
+ : {}),
251
352
  };
252
353
  state.taskResolutions.set(task, resolution);
253
354
  return task;
@@ -269,6 +370,8 @@ export function createDispatchAdmissionController(deps: DispatchToolDeps): Dispa
269
370
  if (topology === "fleet") return task.wave ?? index;
270
371
  if (topology === "parallel" || topology === "detached") return 0;
271
372
  if (topology === "compete") return task.role === "judge" ? 1 : 0;
373
+ if (topology === "council")
374
+ return task.role === "synthesis" ? (task.council?.rounds ?? 1) : (task.council?.round ?? 1) - 1;
272
375
  return index;
273
376
  };
274
377
  const markReservedPlan = (args: Record<string, unknown>, artifact: ResolvedDispatchPlanArtifact) => {
@@ -286,7 +389,7 @@ export function createDispatchAdmissionController(deps: DispatchToolDeps): Dispa
286
389
  };
287
390
  });
288
391
  const reservation = deps.dispatch.reservations.prepare({
289
- topology: artifact.topology === "fleet" ? "parallel" : artifact.topology,
392
+ topology: artifact.topology === "fleet" || artifact.topology === "council" ? "parallel" : artifact.topology,
290
393
  tasks,
291
394
  });
292
395
  try {
@@ -333,6 +436,7 @@ export function createDispatchAdmissionController(deps: DispatchToolDeps): Dispa
333
436
  mode: "parallel",
334
437
  review: undefined,
335
438
  compete: undefined,
439
+ council: undefined,
336
440
  }),
337
441
  );
338
442
  state.trustedExecutionPlans.set(marked, deepFreeze(structuredClone(prepared.executionPlan)));
@@ -375,10 +479,13 @@ export function createDispatchAdmissionController(deps: DispatchToolDeps): Dispa
375
479
  }
376
480
  const parsed = parseRequests(args);
377
481
  if (!parsed.ok) return shapeRejection(args, parsed.message);
378
- if (args.mode !== undefined && !["parallel", "sequential", "pipeline", "compete"].includes(String(args.mode))) {
482
+ if (
483
+ args.mode !== undefined &&
484
+ !["parallel", "sequential", "pipeline", "compete", "council"].includes(String(args.mode))
485
+ ) {
379
486
  return shapeRejection(
380
487
  args,
381
- `dispatch: mode must be parallel, sequential, pipeline, or compete; got '${String(args.mode)}'`,
488
+ `dispatch: mode must be parallel, sequential, pipeline, compete, or council; got '${String(args.mode)}'`,
382
489
  );
383
490
  }
384
491
  const mode: DispatchMode =
@@ -388,7 +495,30 @@ export function createDispatchAdmissionController(deps: DispatchToolDeps): Dispa
388
495
  ? "pipeline"
389
496
  : args.mode === "compete"
390
497
  ? "compete"
391
- : "parallel";
498
+ : args.mode === "council"
499
+ ? "council"
500
+ : "parallel";
501
+ if (args.writers !== undefined && args.writers !== 1) {
502
+ return shapeRejection(args, "dispatch: writers must be 1 when present");
503
+ }
504
+ if (args.writers === 1 && mode !== "parallel") {
505
+ return shapeRejection(args, "dispatch: writers is supported only for parallel dispatch");
506
+ }
507
+ const parentCheckout = gitCheckoutRoot(process.cwd());
508
+ for (const request of parsed.requests) {
509
+ if (request.worktree !== true) continue;
510
+ if (mode === "compete") return shapeRejection(args, "worktree_compete_incompatible");
511
+ if (parentCheckout === null) return shapeRejection(args, "worktree_non_git_checkout");
512
+ const spec = deps.getAgentSpecs().find((candidate) => candidate.id === request.agentId);
513
+ if (spec?.capabilityClass === "read-only") return shapeRejection(args, "worktree_read_only_agent");
514
+ if (request.cwd !== undefined) {
515
+ const requested = resolve(request.cwd);
516
+ const rel = relative(parentCheckout, requested);
517
+ if (rel === ".." || rel.startsWith("../") || isAbsolute(rel)) {
518
+ return shapeRejection(args, "worktree_cwd_outside_checkout");
519
+ }
520
+ }
521
+ }
392
522
  const reviewResult = reviewSettingsFromArgs(args);
393
523
  if (!reviewResult.ok) return shapeRejection(args, reviewResult.message);
394
524
  if (reviewResult.review !== undefined) {
@@ -401,13 +531,28 @@ export function createDispatchAdmissionController(deps: DispatchToolDeps): Dispa
401
531
  if (mode === "compete" && (parsed.requests.length !== 1 || parsed.requests[0] === undefined)) {
402
532
  return shapeRejection(args, COMPETE_SINGLE_TASK_MESSAGE);
403
533
  }
534
+ if (mode === "compete" && parsed.requests.some((request) => (request.resolvedVerification?.length ?? 0) > 0)) {
535
+ return shapeRejection(args, "verification_unsupported_for_mode: compete requests cannot run host verification");
536
+ }
404
537
  const competeResult = mode === "compete" ? competeSettingsFromArgs(args) : undefined;
405
538
  if (competeResult !== undefined && !competeResult.ok) return shapeRejection(args, competeResult.message);
539
+ const councilResult = mode === "council" ? councilSettingsFromArgs(args, deps) : undefined;
540
+ if (councilResult !== undefined && !councilResult.ok) return shapeRejection(args, councilResult.message);
541
+ if (mode === "council") {
542
+ if (parsed.requests.length !== 1 || parsed.requests[0] === undefined) {
543
+ return shapeRejection(args, "dispatch: council requires exactly one task");
544
+ }
545
+ if (reviewResult.review !== undefined) return shapeRejection(args, "dispatch: council cannot combine with review");
546
+ if ((parsed.requests[0].resolvedVerification?.length ?? 0) > 0) {
547
+ return shapeRejection(args, "council_verification_unsupported");
548
+ }
549
+ }
406
550
  const snapshotFields = {
407
551
  requests: parsed.requests,
408
552
  mode,
409
553
  review: reviewResult.review,
410
554
  compete: competeResult?.ok === true ? competeResult.compete : undefined,
555
+ council: councilResult?.ok === true ? councilResult.council : undefined,
411
556
  };
412
557
  if (deps.dispatch.preview === undefined) {
413
558
  const marked = markPrepared(args);
@@ -432,7 +577,7 @@ export function createDispatchAdmissionController(deps: DispatchToolDeps): Dispa
432
577
  {
433
578
  agentId: gateDeciderAgentId(reviewResult.review.reviewer),
434
579
  executionRole: "reviewer",
435
- task: reviewerTask(base.task, subject.runId, cycle),
580
+ task: renderDispatchReviewerTask(base.task, subject.runId, cycle, base.intent),
436
581
  systemPrompt: REVIEWER_GATE_PROMPT,
437
582
  autonomy: "read-only",
438
583
  gate: { role: "reviewer", group: "plan-preview", cycle, subjects: [subject] },
@@ -480,6 +625,81 @@ export function createDispatchAdmissionController(deps: DispatchToolDeps): Dispa
480
625
  1,
481
626
  ),
482
627
  );
628
+ } else if (mode === "council") {
629
+ const council = councilResult?.ok === true ? councilResult.council : undefined;
630
+ const base = parsed.requests[0];
631
+ if (council === undefined || base === undefined)
632
+ throw new Error("dispatch: council admission settings are unavailable");
633
+ const subjects: Array<{ runId: string; digest: null }> = [];
634
+ for (let round = 1; round <= council.rounds; round += 1) {
635
+ for (const [index, member] of council.members.entries()) {
636
+ const memberRequest: DispatchRequest = {
637
+ ...base,
638
+ executionRole: "researcher",
639
+ autonomy: "read-only",
640
+ toolProfile: "council-read-only",
641
+ // The runner reuses this composed task from the resolved plan, so
642
+ // the approval artifact the plan hash binds shows the operator the
643
+ // exact ballot the member is asked to cast.
644
+ ...(council.synthesis === "vote"
645
+ ? {
646
+ task: renderCouncilVoteMemberTask(base.task),
647
+ resultContractOverride: { kind: "council-ballot" as const },
648
+ }
649
+ : {}),
650
+ target: member.target,
651
+ ...(member.model ? { model: member.model } : {}),
652
+ ...(member.thinking ? { thinkingLevel: member.thinking as NonNullable<DispatchRequest["thinkingLevel"]> } : {}),
653
+ gate: { role: "member", group: "plan-preview", cycle: round },
654
+ };
655
+ let task: ResolvedPlanTask;
656
+ try {
657
+ task = resolveTask(memberRequest, "member", (round - 1) * council.members.length + index + 1);
658
+ } catch (error) {
659
+ return shapeRejection(
660
+ args,
661
+ `council_member_target_unknown: ${member.label}: ${error instanceof Error ? error.message : String(error)}`,
662
+ );
663
+ }
664
+ if (task.node !== "local" || task.nodeKind !== "local") {
665
+ return shapeRejection(args, `council_member_remote_node: ${member.label}`);
666
+ }
667
+ task.council = {
668
+ label: member.label,
669
+ ...(member.color ? { color: member.color } : {}),
670
+ round,
671
+ rounds: council.rounds,
672
+ synthesis: council.synthesis,
673
+ };
674
+ if (round === council.rounds) subjects.push({ runId: `plan-member-${member.label}`, digest: null });
675
+ tasks.push(task);
676
+ }
677
+ }
678
+ if (council.synthesis === "judge") {
679
+ const judgeRequest: DispatchRequest = {
680
+ agentId: council.judge?.agent ?? base.agentId,
681
+ executionRole: "judge",
682
+ task: "Plan-time capability check for council synthesis.",
683
+ systemPrompt: COUNCIL_JUDGE_PROMPT,
684
+ autonomy: "read-only",
685
+ toolProfile: "council-read-only",
686
+ gate: { role: "synthesis", group: "plan-preview", cycle: council.rounds, subjects },
687
+ ...(council.judge?.node ? { node: council.judge.node } : {}),
688
+ ...(council.judge?.model ? { model: council.judge.model } : {}),
689
+ ...(council.judge?.target ? { target: council.judge.target } : {}),
690
+ };
691
+ const judgeTask = resolveTask(judgeRequest, "synthesis", 1);
692
+ if (judgeTask.node !== "local" || judgeTask.nodeKind !== "local") {
693
+ return shapeRejection(args, "council_member_remote_node: judge");
694
+ }
695
+ judgeTask.council = {
696
+ label: "synthesis",
697
+ round: council.rounds,
698
+ rounds: council.rounds,
699
+ synthesis: council.synthesis,
700
+ };
701
+ tasks.push(judgeTask);
702
+ }
483
703
  } else {
484
704
  for (const [index, request] of parsed.requests.entries()) tasks.push(resolveTask(request, "task", index + 1));
485
705
  }
@@ -490,7 +710,20 @@ export function createDispatchAdmissionController(deps: DispatchToolDeps): Dispa
490
710
  topology === "compete" ||
491
711
  tasks.some((task) => task.node !== "local" || task.failover === "approved");
492
712
  if (!planScale) {
493
- const marked = markPrepared(args);
713
+ const marked = parsed.requests.some((request) => request.intent !== undefined)
714
+ ? markPrepared(
715
+ withResolvedDispatchPlan(args, {
716
+ version: 3,
717
+ topology,
718
+ source: null,
719
+ maxWorkers: null,
720
+ onFailure: null,
721
+ tasks,
722
+ costCeilingUsd: resolvedCostCeiling(),
723
+ deadlineMs: null,
724
+ }),
725
+ )
726
+ : markPrepared(args);
494
727
  return trustExecution(marked, dispatchExecutionSnapshot(marked, snapshotFields));
495
728
  }
496
729
  const marked = markReservedPlan(args, {
@@ -1,15 +1,24 @@
1
1
  /** Pure model-argument parsing; every returned DispatchRequest has a concrete agent id. */
2
2
 
3
+ import { pathBoundaryCovers } from "../core/path-boundary.js";
3
4
  import type { AgentAutomationAuthority } from "../domains/agents/spec.js";
4
5
  import { type AgentTaskType, classifyAgentTask } from "../domains/dispatch/agent-candidates.js";
5
6
  import { cloneDispatchBudgetRequest } from "../domains/dispatch/budget-envelope.js";
6
7
  import type { DispatchRequest } from "../domains/dispatch/contract.js";
7
8
  import { type AgentRoleFactsResolver, requestExecutionRole } from "../domains/dispatch/execution-role.js";
9
+ import type { DispatchIntent } from "../domains/dispatch/intent.js";
8
10
  import { parseRoutingIntent } from "../domains/dispatch/routing-intent.js";
9
11
  import { DISPATCH_BRIEFING_MAX_BYTES, type JobThinkingLevel } from "../domains/dispatch/validation.js";
10
12
  import { isToolProfileName, TOOL_PROFILE_NAMES } from "./profiles.js";
11
13
 
12
14
  const DEFAULT_AGENT_ID = "coder";
15
+ /**
16
+ * The agent a council seats when the caller names none. Council admission pins
17
+ * every member to the `council-read-only` tool profile, and `coder` requires a
18
+ * write tool, so the ordinary default would fail admission for every member.
19
+ * `researcher` is the builtin read-only answerer.
20
+ */
21
+ const COUNCIL_DEFAULT_AGENT_ID = "researcher";
13
22
 
14
23
  /**
15
24
  * Baseline recipe per task shape for `agent:"auto"`.
@@ -69,6 +78,12 @@ export interface DispatchArgumentParserOptions {
69
78
  approvedAuthorities: ReadonlyArray<AgentAutomationAuthority>;
70
79
  authorityBasis: "operator-plan-approval" | "full-auto-policy";
71
80
  };
81
+ resolveIntent?: (
82
+ rawIntent: unknown,
83
+ cwd: string | undefined,
84
+ ) =>
85
+ | { ok: true; intent: DispatchIntent; resolvedVerification: NonNullable<DispatchRequest["resolvedVerification"]> }
86
+ | { ok: false; message: string };
72
87
  }
73
88
 
74
89
  function isRecord(value: unknown): value is Record<string, unknown> {
@@ -100,7 +115,8 @@ function dispatchRequestFromArgs(
100
115
  const task = stringArg(args, "task");
101
116
  if (!task) return { ok: false, message: "missing task (pass list:true to see available agents)" };
102
117
  if (Object.hasOwn(args, "agent_id")) return { ok: false, message: "agent_id is unsupported; use agent" };
103
- const requestedAgent = stringArg(args, "agent") ?? DEFAULT_AGENT_ID;
118
+ const requestedAgent =
119
+ stringArg(args, "agent") ?? (args.mode === "council" ? COUNCIL_DEFAULT_AGENT_ID : DEFAULT_AGENT_ID);
104
120
  const auto = requestedAgent === "auto";
105
121
  const agentId = auto ? autoBaselineAgentId(task, options.hasAgent) : requestedAgent;
106
122
  const request: DispatchRequest = {
@@ -150,6 +166,47 @@ function dispatchRequestFromArgs(
150
166
  request.requiredCapabilities = routing.intent.requiredCapabilities;
151
167
  const cwd = stringArg(args, "cwd");
152
168
  if (cwd) request.cwd = cwd;
169
+ if (args.worktree !== undefined) {
170
+ if (args.worktree !== true) return { ok: false, message: "worktree must be true when present" };
171
+ request.worktree = true;
172
+ }
173
+ if (args.apply !== undefined) {
174
+ if (args.apply !== "merge" && args.apply !== "preserve")
175
+ return { ok: false, message: "apply must be merge or preserve" };
176
+ if (request.worktree !== true) return { ok: false, message: "apply requires worktree: true" };
177
+ request.apply = args.apply;
178
+ }
179
+ if (Object.hasOwn(args, "gate") && isRecord(args.intent) && Object.hasOwn(args.intent, "verification")) {
180
+ return { ok: false, message: "gate_and_intent_verification_conflict: gate cannot combine with intent.verification" };
181
+ }
182
+ let rawIntent = args.intent;
183
+ if (Object.hasOwn(args, "gate")) {
184
+ if (typeof args.gate !== "string" || args.gate.trim().length === 0) {
185
+ return { ok: false, message: "gate must be a non-empty declared check id" };
186
+ }
187
+ rawIntent = { ...(isRecord(rawIntent) ? rawIntent : {}), verification: [{ check: args.gate.trim() }] };
188
+ }
189
+ if (rawIntent !== undefined) {
190
+ if (options.resolveIntent === undefined) return { ok: false, message: "intent resolver is unavailable" };
191
+ const resolved = options.resolveIntent(rawIntent, cwd);
192
+ if (!resolved.ok) return resolved;
193
+ request.intent = resolved.intent;
194
+ if (resolved.resolvedVerification.length > 0) request.resolvedVerification = resolved.resolvedVerification;
195
+ if (resolved.intent.writeRoots.length > 0) {
196
+ const legacy = Array.isArray(args.writeRoots)
197
+ ? args.writeRoots.filter((entry): entry is string => typeof entry === "string")
198
+ : undefined;
199
+ if (legacy !== undefined) {
200
+ const left = [...new Set(legacy)].sort();
201
+ if (JSON.stringify(left) !== JSON.stringify(resolved.intent.writeRoots)) {
202
+ return { ok: false, message: "intent_write_roots_contradiction" };
203
+ }
204
+ request.writeRoots = legacy;
205
+ } else {
206
+ request.writeRoots = resolved.intent.writeRoots;
207
+ }
208
+ }
209
+ }
153
210
  if ("persona" in args && args.persona !== undefined) {
154
211
  if (typeof args.persona !== "string") return { ok: false, message: "persona must be a string" };
155
212
  const persona = args.persona.trim();
@@ -205,8 +262,34 @@ export function dispatchRequestsFromArgs(
205
262
  for (let index = 0; index < tasks.length; index += 1) {
206
263
  const item = tasks[index];
207
264
  const itemArgs: Record<string, unknown> = isRecord(item) ? { ...shared, ...item } : { ...shared, task: item };
265
+ const sharedIntent = isRecord(shared.intent) ? shared.intent : null;
266
+ const itemIntent = isRecord(item) && Object.hasOwn(item, "intent") && isRecord(item.intent) ? item.intent : null;
267
+ if (sharedIntent !== null && itemIntent !== null) itemArgs.intent = { ...sharedIntent, ...itemIntent };
208
268
  const parsed = dispatchRequestFromArgs(itemArgs, options);
209
269
  if (!parsed.ok) return { ok: false, message: `dispatch: task ${index + 1}: ${parsed.message}` };
270
+ if (sharedIntent !== null && itemIntent !== null) {
271
+ if (options.resolveIntent === undefined) {
272
+ return { ok: false, message: `dispatch: task ${index + 1}: intent resolver is unavailable` };
273
+ }
274
+ const ceiling = options.resolveIntent(sharedIntent, parsed.request.cwd);
275
+ if (!ceiling.ok) return { ok: false, message: `dispatch: task ${index + 1}: ${ceiling.message}` };
276
+ const pathCeilings = {
277
+ readRoots: [...ceiling.intent.readRoots, ...ceiling.intent.writeRoots],
278
+ writeRoots: ceiling.intent.writeRoots,
279
+ relevantPaths: [...ceiling.intent.readRoots, ...ceiling.intent.writeRoots, ...ceiling.intent.relevantPaths],
280
+ };
281
+ for (const field of ["readRoots", "writeRoots", "relevantPaths"] as const) {
282
+ const outside = parsed.request.intent?.[field].find(
283
+ (candidate) => !pathBoundaryCovers(pathCeilings[field], candidate),
284
+ );
285
+ if (outside !== undefined) {
286
+ return {
287
+ ok: false,
288
+ message: `dispatch: task ${index + 1}: intent_scope_widening: ${field} entry '${outside}' is outside the top-level intent ceiling`,
289
+ };
290
+ }
291
+ }
292
+ }
210
293
  requests.push(parsed.request);
211
294
  }
212
295
  return { ok: true, requests };