@iowarp/clio-coder 0.3.6 → 0.3.7

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 (258) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +16 -5
  3. package/dist/{acp-2BEHC4DL.js → acp-SK4MD6MM.js} +10 -10
  4. package/dist/{agents-LNNFTM53.js → agents-2FN2K6ME.js} +30 -25
  5. package/dist/assets/codewiki.json +1 -1
  6. package/dist/{auth-KXXFI2VS.js → auth-QIYZWM5I.js} +13 -13
  7. package/dist/{chunk-KHSFENX2.js → chunk-3HAPLH5M.js} +10 -10
  8. package/dist/{chunk-24I7BN55.js → chunk-465YSENW.js} +2 -2
  9. package/dist/{chunk-4OC57DA6.js → chunk-4DGYLA73.js} +53 -2
  10. package/dist/{chunk-CYQKWTG3.js → chunk-4DWFMQDR.js} +4 -4
  11. package/dist/{chunk-E2ER4LJF.js → chunk-5C3AQNDW.js} +25 -1
  12. package/dist/{chunk-22NAGB7X.js → chunk-5C77SEEY.js} +5 -94
  13. package/dist/{chunk-43AOLP7E.js → chunk-5FR74PWO.js} +2 -1
  14. package/dist/{chunk-K7T3E2SR.js → chunk-5UJ6ECTS.js} +10 -9
  15. package/dist/{chunk-6US73PDB.js → chunk-6M7VS3J3.js} +5 -5
  16. package/dist/{chunk-CJUB2JJ2.js → chunk-6TUKSZVF.js} +5 -5
  17. package/dist/{chunk-5JGRAMKL.js → chunk-AB4XIIVB.js} +8 -6
  18. package/dist/{chunk-R46L2BIR.js → chunk-BMWK7ZIZ.js} +14 -20
  19. package/dist/{chunk-4BPJXDWC.js → chunk-C4JBQ5SR.js} +30 -14
  20. package/dist/{chunk-XXQNGV4M.js → chunk-CEYBNUGC.js} +243 -63
  21. package/dist/{chunk-VEZEGCGW.js → chunk-D4MDIG46.js} +20 -18
  22. package/dist/chunk-DJNLUABN.js +843 -0
  23. package/dist/{chunk-RY3LY4J5.js → chunk-DMD2AGVS.js} +5 -4
  24. package/dist/{chunk-KOHPCX4K.js → chunk-DOOEX22V.js} +2 -2
  25. package/dist/chunk-DQA7QLMD.js +123 -0
  26. package/dist/chunk-DR52UMZW.js +21 -0
  27. package/dist/{chunk-XF5N4U5A.js → chunk-EBEFWSGL.js} +6 -5
  28. package/dist/{chunk-EYPA3EGJ.js → chunk-EELBMBT6.js} +120 -13
  29. package/dist/{chunk-CKXWIANG.js → chunk-EOOQZZDE.js} +16 -14
  30. package/dist/{chunk-WR67VIZY.js → chunk-FOT2FX5J.js} +63 -5
  31. package/dist/{chunk-FYYLNIL5.js → chunk-GH5622CP.js} +2 -2
  32. package/dist/chunk-GWS3VEIW.js +195 -0
  33. package/dist/{chunk-LYF7OHWH.js → chunk-J7PIKKWC.js} +8 -463
  34. package/dist/{chunk-NILBFAPG.js → chunk-JNXPYBB4.js} +2 -2
  35. package/dist/{chunk-4VP4KH3K.js → chunk-JRIO5UD2.js} +4 -4
  36. package/dist/{chunk-6XXKFVSN.js → chunk-JTSEDYVQ.js} +7 -7
  37. package/dist/{chunk-QKMUKYO7.js → chunk-KCMKRQX4.js} +236 -84
  38. package/dist/chunk-KZ2H5X4G.js +1026 -0
  39. package/dist/{chunk-QNQHSOLF.js → chunk-LADCF22A.js} +12 -12
  40. package/dist/chunk-M4AKACEO.js +382 -0
  41. package/dist/{chunk-XYDYPRZI.js → chunk-MXI6J5JF.js} +7 -7
  42. package/dist/{chunk-G7MUEIGA.js → chunk-OB5HIGJY.js} +1 -1
  43. package/dist/{chunk-EKY57CSP.js → chunk-OBMAI2DP.js} +61 -767
  44. package/dist/chunk-PD3MESLB.js +242 -0
  45. package/dist/{chunk-ZRGEBJ4T.js → chunk-QCTRSGHQ.js} +21 -21
  46. package/dist/chunk-RVG5JXAL.js +41 -0
  47. package/dist/{chunk-RD5U66HV.js → chunk-SROCI7ZU.js} +7 -7
  48. package/dist/{chunk-MFFY33HR.js → chunk-THKY7CD7.js} +466 -205
  49. package/dist/{chunk-PCZJO5TI.js → chunk-UFQ3F4FW.js} +13 -178
  50. package/dist/{chunk-AD2SYQYC.js → chunk-UHXRNZ2J.js} +121 -3
  51. package/dist/chunk-UND3GU2L.js +103 -0
  52. package/dist/{chunk-QM3F2GKX.js → chunk-UUANF5CR.js} +2247 -2096
  53. package/dist/chunk-UVDSQ6LW.js +472 -0
  54. package/dist/{chunk-DJVECN66.js → chunk-VQNODYQ4.js} +14 -14
  55. package/dist/{chunk-3BPUFZDL.js → chunk-VREKEFLL.js} +3 -3
  56. package/dist/{chunk-PBTHKCPN.js → chunk-WJHBC77E.js} +6 -6
  57. package/dist/{chunk-XE2VEJHX.js → chunk-X2KV5FXT.js} +2 -2
  58. package/dist/{chunk-ZXF4XRKW.js → chunk-XEGB6BCN.js} +157 -7
  59. package/dist/{chunk-E25LMLRW.js → chunk-YD734TPH.js} +2 -2
  60. package/dist/{verifiers-NCBTHHN2.js → chunk-YTYFXUI3.js} +65 -322
  61. package/dist/{chunk-OH3TOQTB.js → chunk-ZGH7FGS5.js} +13 -7
  62. package/dist/cli/index.js +32 -30
  63. package/dist/{clio-M2KGYUFZ.js → clio-WBVQEBKO.js} +7 -7
  64. package/dist/{code-nav-GQNL7XA6.js → code-nav-FGGFIE7L.js} +3 -3
  65. package/dist/{components-5TTYYX6G.js → components-F7OEATSO.js} +4 -4
  66. package/dist/{config-XUUYQIWO.js → config-TRBL3RCF.js} +34 -29
  67. package/dist/{configure-IHJ7YOMV.js → configure-OLCVPHNM.js} +15 -15
  68. package/dist/{context-74JLXAWD.js → context-MJIJ6GOX.js} +11 -11
  69. package/dist/{context-ZQ7SIFJV.js → context-WFPKQSM6.js} +19 -3
  70. package/dist/{context-75MIWW3U.js → context-XEWE3MOJ.js} +31 -26
  71. package/dist/{context-clear-GYKWNUML.js → context-clear-KNOS2JPB.js} +31 -26
  72. package/dist/{context-working-set-UX5KEP4J.js → context-working-set-EUXAZI6N.js} +8 -8
  73. package/dist/{dispatch-runner-GIJBHNFL.js → dispatch-runner-B7MTOVKL.js} +313 -53
  74. package/dist/{docs-6FZSCG5B.js → docs-FLJTIDSE.js} +4 -4
  75. package/dist/{doctor-SVJ5BZCW.js → doctor-RN4YKO2X.js} +14 -14
  76. package/dist/{eval-CG6LLBLD.js → eval-RUBJVSNQ.js} +8 -7
  77. package/dist/{evidence-ZYFIEN42.js → evidence-JZNBUOQZ.js} +30 -25
  78. package/dist/{evolve-QGEXEMDW.js → evolve-FJVC4KKI.js} +30 -25
  79. package/dist/{extensions-ADGNCJJD.js → extensions-IQL36S7K.js} +4 -4
  80. package/dist/{fleet-S5R4ZOQY.js → fleet-BDKYJFCP.js} +214 -360
  81. package/dist/fleet-commands-ZFIWZSB3.js +70 -0
  82. package/dist/fleet-graph-Y6HPXIVF.js +125 -0
  83. package/dist/fleet-new-RDVJLHHH.js +48 -0
  84. package/dist/fleet-validate-BIYREGIK.js +79 -0
  85. package/dist/{init-5DRU55YR.js → init-LQUB5COQ.js} +44 -37
  86. package/dist/library-NJAHIGG4.js +217 -0
  87. package/dist/{memory-7YKKR6UC.js → memory-OG6HOYKM.js} +31 -26
  88. package/dist/{models-ZPOLRU2C.js → models-5ZG5XY7J.js} +21 -20
  89. package/dist/{monitor-US5F5YGZ.js → monitor-TJ7AMTGB.js} +49 -30
  90. package/dist/{orchestrator-E2AL4T5N.js → orchestrator-WZYB54DM.js} +3827 -581
  91. package/dist/{paths-E7KYAQWE.js → paths-XUC7GS6E.js} +4 -4
  92. package/dist/{reset-KZ652EK6.js → reset-PXQT45IY.js} +7 -7
  93. package/dist/{run-SRNBKDWD.js → run-FQ74YF62.js} +53 -45
  94. package/dist/{share-CGZE33UP.js → share-FW7SVCL3.js} +33 -9
  95. package/dist/{skills-S2X4DLY5.js → skills-7E7IRB3R.js} +24 -8
  96. package/dist/{skills-eval-W2GGIC4R.js → skills-eval-LI75W6OK.js} +34 -27
  97. package/dist/{targets-54SWINWB.js → targets-4CIFKCTW.js} +23 -22
  98. package/dist/{terminal-lease-SAIF2OGY.js → terminal-lease-WUZY7ZV5.js} +4 -4
  99. package/dist/{uninstall-BVLWXKBT.js → uninstall-7FV7IP4E.js} +4 -4
  100. package/dist/{upgrade-JKAR27XC.js → upgrade-K2HVIVMQ.js} +20 -19
  101. package/dist/{usage-MSAWCLX4.js → usage-GTZELZQX.js} +116 -49
  102. package/dist/verifiers-RLAHT27O.js +336 -0
  103. package/dist/{verify-X5HDROLA.js → verify-BX3BRKH5.js} +7 -6
  104. package/dist/{wiki-generate-GUSOQ6ZP.js → wiki-generate-ASIFASCN.js} +45 -37
  105. package/dist/worker/entry.js +38 -35
  106. package/docs/README.md +3 -2
  107. package/docs/acp.md +1 -1
  108. package/docs/alcf-provider.md +1 -1
  109. package/docs/architecture.md +2 -2
  110. package/docs/artifact-versions.md +10 -6
  111. package/docs/built-in-agents.md +26 -2
  112. package/docs/capacity-and-scheduling.md +1 -1
  113. package/docs/commands-and-modes.md +82 -2
  114. package/docs/configuration-and-targets.md +79 -2
  115. package/docs/context-engine.md +1 -1
  116. package/docs/development-pipeline.md +1 -1
  117. package/docs/dispatch-architecture-rationale.md +1 -1
  118. package/docs/documentation-coverage.md +3 -3
  119. package/docs/documentation-guide.md +3 -3
  120. package/docs/eval-runner.md +1 -1
  121. package/docs/evals-internal.md +1 -1
  122. package/docs/evidence-and-memory.md +5 -5
  123. package/docs/evolution.md +1 -1
  124. package/docs/exit-codes-and-output.md +4 -1
  125. package/docs/extensions-and-sharing.md +6 -2
  126. package/docs/fleet-demo-runbook.md +2 -2
  127. package/docs/fleet-dispatch.md +197 -10
  128. package/docs/git-commit-provenance.md +2 -2
  129. package/docs/glossary.md +1 -1
  130. package/docs/installation-and-lifecycle.md +2 -2
  131. package/docs/middleware-and-components.md +2 -1
  132. package/docs/model-catalog.md +1 -1
  133. package/docs/observability.md +55 -8
  134. package/docs/proactive-memory.md +1 -1
  135. package/docs/prompt-envelope-and-tools.md +1 -1
  136. package/docs/provider-adapter-cookbook.md +1 -1
  137. package/docs/release-cut-checklist.md +79 -64
  138. package/docs/resource-library.md +59 -0
  139. package/docs/safety-model.md +2 -2
  140. package/docs/scientific-validation.md +3 -3
  141. package/docs/session-lifecycle.md +37 -1
  142. package/docs/skills-marketplace.md +16 -3
  143. package/docs/tool-usage.md +14 -7
  144. package/docs/trace-store.md +1 -1
  145. package/docs/troubleshooting.md +1 -1
  146. package/docs/tui-design.md +1 -1
  147. package/docs/worker-dispatch-mechanics.md +3 -3
  148. package/package.json +1 -1
  149. package/src/cli/fleet-commands.ts +37 -0
  150. package/src/cli/fleet-graph.ts +102 -0
  151. package/src/cli/fleet-new.ts +36 -0
  152. package/src/cli/fleet-preflight.ts +121 -0
  153. package/src/cli/fleet-validate.ts +30 -0
  154. package/src/cli/fleet.ts +173 -335
  155. package/src/cli/index.ts +3 -1
  156. package/src/cli/library.ts +190 -0
  157. package/src/cli/share.ts +13 -1
  158. package/src/cli/usage.ts +111 -19
  159. package/src/core/bus-events.ts +4 -0
  160. package/src/core/commit-attribution.ts +4 -4
  161. package/src/core/config.ts +130 -0
  162. package/src/core/defaults.ts +81 -0
  163. package/src/domains/agents/builtins/architect.md +1 -0
  164. package/src/domains/agents/builtins/oracle.md +33 -0
  165. package/src/domains/agents/catalog.ts +13 -1
  166. package/src/domains/agents/fleet-contract.ts +278 -16
  167. package/src/domains/agents/index.ts +14 -0
  168. package/src/domains/agents/result-contract.ts +235 -1
  169. package/src/domains/config/classify.ts +4 -0
  170. package/src/domains/dispatch/active-route-planner.ts +14 -0
  171. package/src/domains/dispatch/backoff.ts +2 -1
  172. package/src/domains/dispatch/capability-match.ts +1 -0
  173. package/src/domains/dispatch/checkout-writer-lease.ts +175 -0
  174. package/src/domains/dispatch/contract.ts +34 -0
  175. package/src/domains/dispatch/delegation-plan.ts +167 -0
  176. package/src/domains/dispatch/execution-plan.ts +76 -5
  177. package/src/domains/dispatch/execution-role.ts +3 -1
  178. package/src/domains/dispatch/execution-scheduler.ts +183 -67
  179. package/src/domains/dispatch/extension.ts +258 -9
  180. package/src/domains/dispatch/fleet-gate.ts +14 -0
  181. package/src/domains/dispatch/fleet-plan.ts +63 -3
  182. package/src/domains/dispatch/fleet-run.ts +737 -0
  183. package/src/domains/dispatch/gate-role-prompts.ts +9 -0
  184. package/src/domains/dispatch/host-verification.ts +178 -0
  185. package/src/domains/dispatch/index.ts +38 -0
  186. package/src/domains/dispatch/intent.ts +159 -0
  187. package/src/domains/dispatch/receipt-integrity.ts +8 -4
  188. package/src/domains/dispatch/state.ts +36 -3
  189. package/src/domains/dispatch/types.ts +51 -6
  190. package/src/domains/dispatch/validation.ts +66 -6
  191. package/src/domains/evidence/trust-status.ts +10 -1
  192. package/src/domains/middleware/index.ts +15 -0
  193. package/src/domains/middleware/watchdog.ts +281 -0
  194. package/src/domains/observability/contract.ts +3 -1
  195. package/src/domains/observability/cost.ts +12 -1
  196. package/src/domains/observability/extension.ts +2 -2
  197. package/src/domains/observability/index.ts +10 -0
  198. package/src/domains/observability/out-of-turn-usage.ts +223 -0
  199. package/src/domains/resources/index.ts +20 -0
  200. package/src/domains/resources/library.ts +326 -0
  201. package/src/domains/resources/skills/marketplace.ts +37 -12
  202. package/src/domains/session/handoff.ts +629 -0
  203. package/src/domains/share/archive.ts +67 -2
  204. package/src/entry/orchestrator.ts +37 -0
  205. package/src/interactive/bus-notices.ts +26 -0
  206. package/src/interactive/chat-loop.ts +235 -1
  207. package/src/interactive/chat-renderer.ts +22 -0
  208. package/src/interactive/cost-overlay.ts +31 -3
  209. package/src/interactive/council-dispatch.ts +30 -0
  210. package/src/interactive/council-grid.ts +213 -0
  211. package/src/interactive/council.ts +99 -0
  212. package/src/interactive/dispatch-board.ts +260 -16
  213. package/src/interactive/fleet-run-preview.ts +307 -0
  214. package/src/interactive/footer/notifications.ts +219 -0
  215. package/src/interactive/handoff-round.ts +56 -0
  216. package/src/interactive/interactive-application.ts +43 -1
  217. package/src/interactive/interactive-event-projection.ts +9 -1
  218. package/src/interactive/interactive-slash-runtime.ts +52 -2
  219. package/src/interactive/interactive-subscriptions.ts +14 -2
  220. package/src/interactive/oracle.ts +179 -0
  221. package/src/interactive/overlay-ask-user-lifecycle.ts +6 -0
  222. package/src/interactive/overlay-general-openers.ts +190 -1
  223. package/src/interactive/overlay-key-routing.ts +17 -1
  224. package/src/interactive/overlay-lifecycle.ts +41 -1
  225. package/src/interactive/overlay-permission-lifecycle.ts +10 -0
  226. package/src/interactive/overlay-resource-openers.ts +11 -3
  227. package/src/interactive/overlay-session-lifecycle.ts +234 -2
  228. package/src/interactive/overlays/fleet-run-approval.ts +208 -0
  229. package/src/interactive/overlays/handoff-review.ts +185 -0
  230. package/src/interactive/overlays/library-install-confirm.ts +151 -0
  231. package/src/interactive/overlays/list-overlay.ts +168 -2
  232. package/src/interactive/overlays/settings.ts +101 -4
  233. package/src/interactive/overlays/side-question.ts +139 -0
  234. package/src/interactive/overlays/skills-hub.ts +401 -15
  235. package/src/interactive/side-question.ts +171 -0
  236. package/src/interactive/slash-commands.ts +432 -5
  237. package/src/interactive/slash-spec.ts +19 -6
  238. package/src/interactive/theme/tokens.ts +30 -0
  239. package/src/interactive/turn-middleware.ts +15 -1
  240. package/src/interactive/watchdog-run.ts +75 -0
  241. package/src/interactive/worker-share.ts +56 -1
  242. package/src/interactive/worker-stream.ts +7 -0
  243. package/src/tools/bootstrap.ts +3 -0
  244. package/src/tools/compete-worktrees.ts +13 -79
  245. package/src/tools/dispatch-admission.ts +242 -8
  246. package/src/tools/dispatch-arguments.ts +57 -1
  247. package/src/tools/dispatch-plan.ts +136 -6
  248. package/src/tools/dispatch-runner.ts +319 -13
  249. package/src/tools/dispatch-types.ts +20 -1
  250. package/src/tools/dispatch.ts +72 -2
  251. package/src/tools/monitor.ts +16 -0
  252. package/src/tools/profiles.ts +18 -4
  253. package/src/tools/task-worktree.ts +238 -0
  254. package/src/tools/verify/authoring.ts +61 -1
  255. package/src/tools/verify/scripts.ts +62 -0
  256. package/src/tools/worker-evidence.ts +2 -1
  257. package/src/worker/spec-contract.ts +1 -0
  258. package/dist/chunk-HC4CLZ2Y.js +0 -68
@@ -32,6 +32,10 @@ export interface CostRow {
32
32
  cacheWrite: number;
33
33
  reasoningTokens: number;
34
34
  apiCalls: number;
35
+ /** Calls in this row that were `/btw` side questions rather than turns. */
36
+ sideQuestions: number;
37
+ /** Calls in this row that were `/handoff` extraction rounds rather than turns. */
38
+ handoffs: number;
35
39
  cost: CostAggregate;
36
40
  }
37
41
 
@@ -61,6 +65,8 @@ export function aggregateCostEntries(entries: ReadonlyArray<CostEntry>): CostRow
61
65
  existing.row.cacheWrite += entry.cacheWrite;
62
66
  existing.row.reasoningTokens += entry.reasoningTokens;
63
67
  existing.row.apiCalls += entry.apiCalls ?? 1;
68
+ if (entry.label === "side-question") existing.row.sideQuestions += 1;
69
+ if (entry.label === "handoff") existing.row.handoffs += 1;
64
70
  for (const requestedModelId of entry.requestedModelIds) existing.requestedModelIds.add(requestedModelId);
65
71
  addResponseModelIdObservationCounts(
66
72
  existing.responseModelIdObservationCounts,
@@ -81,6 +87,8 @@ export function aggregateCostEntries(entries: ReadonlyArray<CostEntry>): CostRow
81
87
  cacheWrite: entry.cacheWrite,
82
88
  reasoningTokens: entry.reasoningTokens,
83
89
  apiCalls: entry.apiCalls ?? 1,
90
+ sideQuestions: entry.label === "side-question" ? 1 : 0,
91
+ handoffs: entry.label === "handoff" ? 1 : 0,
84
92
  },
85
93
  requestedModelIds: new Set(entry.requestedModelIds),
86
94
  responseModelIdObservationCounts: { ...entry.responseModelIdObservationCounts },
@@ -117,8 +125,21 @@ function sumRows(
117
125
  cacheWrite: acc.cacheWrite + row.cacheWrite,
118
126
  reasoningTokens: acc.reasoningTokens + row.reasoningTokens,
119
127
  apiCalls: acc.apiCalls + row.apiCalls,
128
+ sideQuestions: acc.sideQuestions + row.sideQuestions,
129
+ handoffs: acc.handoffs + row.handoffs,
120
130
  }),
121
- { runs: 0, tokens: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, reasoningTokens: 0, apiCalls: 0 },
131
+ {
132
+ runs: 0,
133
+ tokens: 0,
134
+ input: 0,
135
+ output: 0,
136
+ cacheRead: 0,
137
+ cacheWrite: 0,
138
+ reasoningTokens: 0,
139
+ apiCalls: 0,
140
+ sideQuestions: 0,
141
+ handoffs: 0,
142
+ },
122
143
  );
123
144
  }
124
145
 
@@ -166,9 +187,14 @@ function summaryBlock(totalCost: CostAggregate, totalTokens: number, rows: Reado
166
187
  // unknown` it printed for a target that reports no pricing. The token rows
167
188
  // below are measured and stay either way. See formatCostAggregate.
168
189
  const cost = formatCostAggregate(totalCost);
190
+ // A side question is billed like anything else and is counted here, but it is
191
+ // deliberately not a turn: it never entered the session, so `turns` above
192
+ // excludes it and this row says how much of the spend sat beside the session.
169
193
  return kvBlock([
170
- ["turns", formatTokens(totals.runs)],
194
+ ["turns", formatTokens(totals.runs - totals.sideQuestions - totals.handoffs)],
171
195
  ["model calls", formatTokens(totals.apiCalls)],
196
+ ...(totals.sideQuestions > 0 ? [["side questions", formatTokens(totals.sideQuestions)] as const] : []),
197
+ ...(totals.handoffs > 0 ? [["handoffs", formatTokens(totals.handoffs)] as const] : []),
172
198
  ...(cost === null ? [] : [["cost", cost] as const]),
173
199
  ["input", formatTokens(totals.input)],
174
200
  ["output", formatTokens(totals.output)],
@@ -187,8 +213,10 @@ function modelBlock(row: CostRow): string[] {
187
213
  return kvBlock([
188
214
  ["requested model ids", row.requestedModelIds.join(", ")],
189
215
  ["response model id observation", responseModelIdObservationCountsLabel(row.responseModelIdObservationCounts)],
190
- ["turns", formatTokens(row.runs)],
216
+ ["turns", formatTokens(row.runs - row.sideQuestions - row.handoffs)],
191
217
  ["model calls", formatTokens(row.apiCalls)],
218
+ ...(row.sideQuestions > 0 ? [["side questions", formatTokens(row.sideQuestions)] as const] : []),
219
+ ...(row.handoffs > 0 ? [["handoffs", formatTokens(row.handoffs)] as const] : []),
192
220
  ...(cost === null ? [] : [["cost", cost] as const]),
193
221
  ["input", formatTokens(row.input)],
194
222
  ["output", formatTokens(row.output)],
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The one path `/council` takes into execution: the dispatch tool, admitted by
3
+ * the ordinary tool registry.
4
+ *
5
+ * The registry is where classification, safety, autonomy, middleware, and the
6
+ * operator approval park all live. A council started from the composer is a
7
+ * plan-scale dispatch exactly as a council the model asks for is, so it is
8
+ * admitted the same way and parks the same approval overlay. Nothing here
9
+ * builds a request, resolves a route, or touches the dispatch domain.
10
+ */
11
+
12
+ import { ToolNames } from "../core/tool-names.js";
13
+ import type { ToolRegistry } from "../tools/registry.js";
14
+ import type { CouncilDispatchOutcome } from "./slash-commands.js";
15
+
16
+ /**
17
+ * Invoke the dispatch tool with prepared council arguments and report the
18
+ * verdict in the command's own vocabulary. A tool that is not visible in this
19
+ * posture and a call the operator declined are both reported as what they are,
20
+ * because a council that never ran must never read as one that did.
21
+ */
22
+ export async function dispatchCouncilThroughRegistry(
23
+ registry: Pick<ToolRegistry, "invoke">,
24
+ args: Readonly<Record<string, unknown>>,
25
+ ): Promise<CouncilDispatchOutcome> {
26
+ const verdict = await registry.invoke({ tool: ToolNames.Dispatch, args: { ...args } });
27
+ if (verdict.kind === "blocked") return { status: "blocked", reason: verdict.reason };
28
+ if (verdict.kind === "not_visible") return { status: "error", message: verdict.reason };
29
+ return verdict.result.kind === "error" ? { status: "error", message: verdict.result.message } : { status: "ok" };
30
+ }
@@ -0,0 +1,213 @@
1
+ /**
2
+ * How a council reads on the Fleet Runs board.
3
+ *
4
+ * A council is one question asked of several members at once, so its runs are
5
+ * one thing on the screen rather than three to five unrelated cards. Members
6
+ * sit side by side while every column keeps enough width to read an answer in,
7
+ * and stack one under another when they do not. The synthesis run is the whole
8
+ * council's answer, so it takes the full width under the members rather than a
9
+ * column of its own.
10
+ *
11
+ * Pure presentation: the caller projects board rows into these views, and this
12
+ * module owns only layout, color, and text. It knows nothing about the board
13
+ * store, the bus, or run status semantics.
14
+ */
15
+
16
+ import { truncateToWidth, wrapTextWithAnsi } from "../engine/tui.js";
17
+ import {
18
+ type ClioTheme,
19
+ type ClioToken,
20
+ dotSep,
21
+ GLYPH,
22
+ innerDivider,
23
+ isClioToken,
24
+ padAnsi,
25
+ paintHex,
26
+ } from "./theme/index.js";
27
+
28
+ /**
29
+ * The narrowest column an answer is still worth reading in. Below it the grid
30
+ * becomes a set of one-word columns, so the group stacks instead: a member's
31
+ * answer is the reason the operator opened the board.
32
+ */
33
+ export const COUNCIL_COLUMN_MIN_WIDTH = 34;
34
+
35
+ /** Blank columns between two side-by-side member columns. */
36
+ export const COUNCIL_COLUMN_GUTTER = 2;
37
+
38
+ /** Rows of a member's bounded answer tail a column shows before it defers to `/view`. */
39
+ export const COUNCIL_ANSWER_ROWS = 4;
40
+
41
+ /** The label the coordinator gives the synthesis run of every council group. */
42
+ export const COUNCIL_SYNTHESIS_LABEL = "synthesis";
43
+
44
+ /** One council run as the board draws it: the roster facts plus the run's presentation. */
45
+ export interface CouncilMemberView {
46
+ runId: string;
47
+ label: string;
48
+ /** Roster color: a theme token name or a `#rrggbb` value. Absent members take the accent. */
49
+ color?: string;
50
+ round: number;
51
+ /** Route as the card names it, such as `local/example-model`. */
52
+ route: string;
53
+ status: { glyph: string; label: string; token: ClioToken };
54
+ /** Bounded answer tail the board already keeps for this run. */
55
+ tailText: string;
56
+ droppedLines: number;
57
+ }
58
+
59
+ export interface CouncilGroupView {
60
+ group: string;
61
+ /** Final-round member runs, in roster order. */
62
+ members: ReadonlyArray<CouncilMemberView>;
63
+ /** The council's own answer, or null while the members are still running. */
64
+ synthesis: CouncilMemberView | null;
65
+ /** Aggregate status of the group, used by the compact card. */
66
+ status: { glyph: string; label: string; token: ClioToken };
67
+ /** Highest round any member has reached. */
68
+ round: number;
69
+ /** Elapsed time of the longest-running member, formatted by the caller. */
70
+ elapsed: string;
71
+ /** Run id of the selected row when the selection sits inside this group. */
72
+ selectedRunId?: string;
73
+ }
74
+
75
+ export type CouncilLayout = { mode: "grid"; columnWidth: number } | { mode: "stack"; columnWidth: number };
76
+
77
+ /**
78
+ * Grid or stack for this many members at this content width. Columns divide the
79
+ * width evenly after the gutters are taken out, and the whole group stacks the
80
+ * moment one column would fall under {@link COUNCIL_COLUMN_MIN_WIDTH}: a grid
81
+ * where only some columns are readable is worse than no grid at all.
82
+ */
83
+ export function councilGridLayout(memberCount: number, contentWidth: number): CouncilLayout {
84
+ const count = Math.max(1, memberCount);
85
+ const usable = contentWidth - COUNCIL_COLUMN_GUTTER * (count - 1);
86
+ const columnWidth = Math.floor(usable / count);
87
+ if (count > 1 && columnWidth >= COUNCIL_COLUMN_MIN_WIDTH) return { mode: "grid", columnWidth };
88
+ return { mode: "stack", columnWidth: Math.max(1, contentWidth) };
89
+ }
90
+
91
+ /**
92
+ * A member's label in its roster color. A theme token name is painted as that
93
+ * token; a `#rrggbb` value is painted literally; anything else, including a
94
+ * member with no color at all, takes the accent, which is what the board paints
95
+ * the operator's own work in.
96
+ */
97
+ function councilLabelText(theme: ClioTheme, label: string, color?: string): string {
98
+ if (color !== undefined && isClioToken(color)) return theme.style(color, label, { bold: true });
99
+ if (color !== undefined) {
100
+ const painted = paintHex(label, color);
101
+ if (painted !== label) return painted;
102
+ }
103
+ return theme.style("accent", label, { bold: true });
104
+ }
105
+
106
+ /** The answer tail on a rail, wrapped to the column and bounded to {@link COUNCIL_ANSWER_ROWS} rows. */
107
+ function answerRows(theme: ClioTheme, member: CouncilMemberView, width: number, maxRows: number): string[] {
108
+ if (member.tailText.trim().length === 0) return [];
109
+ const railWidth = Math.max(1, width - 2);
110
+ const wrapped: string[] = [];
111
+ for (const line of member.tailText.split("\n")) {
112
+ for (const row of wrapTextWithAnsi(line, railWidth)) wrapped.push(row);
113
+ }
114
+ const shown = wrapped.slice(Math.max(0, wrapped.length - maxRows));
115
+ const hidden = wrapped.length - shown.length + member.droppedLines;
116
+ const rail = theme.fg("dim", `${GLYPH.rail} `);
117
+ const rows = shown.map((row) => `${rail}${theme.fg("muted", row)}`);
118
+ if (hidden > 0) {
119
+ rows.push(`${rail}${theme.fg("dim", truncateToWidth(`${hidden} more`, railWidth, "…", false))}`);
120
+ }
121
+ return rows;
122
+ }
123
+
124
+ /**
125
+ * One member column: the label, the route, the round and status, then the
126
+ * answer. Every line is clipped to the column so a long model id or a long
127
+ * answer line can never push a neighbouring column out of position.
128
+ */
129
+ export function councilMemberLines(
130
+ theme: ClioTheme,
131
+ member: CouncilMemberView,
132
+ width: number,
133
+ options: { selected?: boolean; maxAnswerRows?: number } = {},
134
+ ): string[] {
135
+ const dot = dotSep(theme);
136
+ const cursor = options.selected === true ? `${theme.fg("accent", GLYPH.cursor)} ` : "";
137
+ const status = theme.fg(member.status.token, `${member.status.glyph} ${member.status.label}`);
138
+ const head = `${cursor}${councilLabelText(theme, member.label, member.color)}`;
139
+ const lines = [
140
+ truncateToWidth(head, width, "…", false),
141
+ truncateToWidth(theme.fg("muted", member.route), width, "…", false),
142
+ truncateToWidth(`${status}${dot}${theme.fg("dim", `r${member.round}`)}`, width, "…", false),
143
+ ...answerRows(theme, member, width, options.maxAnswerRows ?? COUNCIL_ANSWER_ROWS),
144
+ ];
145
+ return lines.map((line) => padAnsi(line, width));
146
+ }
147
+
148
+ /** Zip member columns into rows, padding short columns so the grid keeps its shape. */
149
+ function zipColumns(columns: ReadonlyArray<ReadonlyArray<string>>, columnWidth: number): string[] {
150
+ const height = columns.reduce((tallest, column) => Math.max(tallest, column.length), 0);
151
+ const gutter = " ".repeat(COUNCIL_COLUMN_GUTTER);
152
+ const rows: string[] = [];
153
+ for (let index = 0; index < height; index += 1) {
154
+ rows.push(columns.map((column) => column[index] ?? " ".repeat(columnWidth)).join(gutter));
155
+ }
156
+ return rows;
157
+ }
158
+
159
+ /**
160
+ * The council group's body: the member grid or stack, then the synthesis run
161
+ * full width under it. The caller frames the result.
162
+ */
163
+ export function councilGroupBody(theme: ClioTheme, group: CouncilGroupView, contentWidth: number): string[] {
164
+ const layout = councilGridLayout(group.members.length, contentWidth);
165
+ const body: string[] = [];
166
+ if (layout.mode === "grid") {
167
+ const columns = group.members.map((member) =>
168
+ councilMemberLines(theme, member, layout.columnWidth, {
169
+ selected: member.runId === group.selectedRunId,
170
+ }),
171
+ );
172
+ body.push(...zipColumns(columns, layout.columnWidth));
173
+ } else {
174
+ for (const member of group.members) {
175
+ if (body.length > 0) body.push(innerDivider(theme, contentWidth));
176
+ body.push(
177
+ ...councilMemberLines(theme, member, layout.columnWidth, {
178
+ selected: member.runId === group.selectedRunId,
179
+ }),
180
+ );
181
+ }
182
+ }
183
+ if (group.synthesis !== null) {
184
+ body.push(innerDivider(theme, contentWidth));
185
+ body.push(
186
+ ...councilMemberLines(theme, group.synthesis, contentWidth, {
187
+ selected: group.synthesis.runId === group.selectedRunId,
188
+ maxAnswerRows: COUNCIL_ANSWER_ROWS + 2,
189
+ }),
190
+ );
191
+ }
192
+ return body;
193
+ }
194
+
195
+ /**
196
+ * The compact Fleet Runs island card for a council: one row for the whole
197
+ * group, naming how many members are seated and which round they are on. The
198
+ * grid belongs to the expanded board, where there is width to read it in.
199
+ */
200
+ export function councilIslandLines(theme: ClioTheme, group: CouncilGroupView, width: number): string[] {
201
+ const dot = dotSep(theme);
202
+ const glyph = theme.fg(group.status.token, group.status.glyph);
203
+ const status = theme.fg(group.status.token, group.status.label);
204
+ const title = theme.paint(`council ${group.group}`, { bold: true });
205
+ const head = `${glyph} ${title}${dot}${status}${dot}${theme.fg("muted", group.elapsed)}`;
206
+ const seats = `${group.members.length} member${group.members.length === 1 ? "" : "s"}`;
207
+ const facts = [
208
+ theme.fg("muted", seats),
209
+ theme.fg("dim", `r${group.round}`),
210
+ ...(group.synthesis !== null ? [theme.fg("info", COUNCIL_SYNTHESIS_LABEL)] : []),
211
+ ].join(dot);
212
+ return [padAnsi(truncateToWidth(head, width, "…", false), width), padAnsi(` ${facts}`, width)];
213
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * `/council [--roster <name>] [--rounds <n>] [--synthesis judge|vote|none] <task>`:
3
+ * the roster an operator-typed council resolves to and the dispatch-tool
4
+ * arguments it becomes.
5
+ *
6
+ * The command owns no execution path of its own. It shapes the same arguments
7
+ * a model would pass to the dispatch tool and hands them to the tool registry,
8
+ * so admission, the approval overlay under supervised autonomy, receipts, and
9
+ * the Fleet Runs board treat an operator council exactly as they treat one the
10
+ * model asked for.
11
+ *
12
+ * Pure: no I/O, no bus, no session, no dispatch. The caller owns the run.
13
+ */
14
+
15
+ /**
16
+ * The roster `--roster` falls back to. It is an ordinary roster name, so an
17
+ * operator declares it the same way they declare any other one, and a project
18
+ * with no roster called `default` gets the refusal rather than a guess.
19
+ */
20
+ export const COUNCIL_DEFAULT_ROSTER = "default";
21
+
22
+ /** Synthesis modes the dispatch tool declares, in the order the usage line lists them. */
23
+ export const COUNCIL_SYNTHESIS_MODES = ["judge", "vote", "none"] as const;
24
+ export type CouncilSynthesisMode = (typeof COUNCIL_SYNTHESIS_MODES)[number];
25
+
26
+ /** Rounds the dispatch tool admits. The command enforces the same bound where the operator typed it. */
27
+ export const COUNCIL_MAX_ROUNDS = 3;
28
+
29
+ export function isCouncilSynthesisMode(value: string): value is CouncilSynthesisMode {
30
+ return COUNCIL_SYNTHESIS_MODES.some((mode) => mode === value);
31
+ }
32
+
33
+ /** What the command needs to know about a configured roster: that it exists and how many members it seats. */
34
+ export interface CouncilRosterFacts {
35
+ members: ReadonlyArray<unknown>;
36
+ }
37
+
38
+ export type CouncilRosters = Readonly<Record<string, CouncilRosterFacts>>;
39
+
40
+ export interface CouncilCommandOptions {
41
+ roster?: string;
42
+ rounds?: number;
43
+ synthesis?: CouncilSynthesisMode;
44
+ }
45
+
46
+ export type CouncilRosterResolution = { ok: true; roster: string } | { ok: false; reason: string };
47
+
48
+ /**
49
+ * The notice a session with no roster and no `--roster` gets. It names the
50
+ * setting rather than the failure, because an operator who types `/council`
51
+ * without a roster has not made a mistake. They have not declared one yet.
52
+ */
53
+ export const COUNCIL_NO_ROSTER_NOTICE =
54
+ `/council needs a roster. Declare one under workers.rosters in settings.yaml, for example ` +
55
+ `workers.rosters.${COUNCIL_DEFAULT_ROSTER} with two to five members, each with a label and a target, ` +
56
+ `then run /council <task> or name another roster with --roster <name>.`;
57
+
58
+ function knownRosterNames(rosters: CouncilRosters): string[] {
59
+ return Object.keys(rosters).sort();
60
+ }
61
+
62
+ /**
63
+ * Which roster this council runs. A named roster must exist; an unnamed one
64
+ * resolves to `workers.rosters.default` when that roster exists. Neither is
65
+ * guessed from the roster list: seating a council from whichever roster happens
66
+ * to be first would run models the operator never chose.
67
+ */
68
+ export function resolveCouncilRoster(requested: string | undefined, rosters: CouncilRosters): CouncilRosterResolution {
69
+ const known = knownRosterNames(rosters);
70
+ const wanted = requested?.trim();
71
+ if (wanted !== undefined && wanted.length > 0) {
72
+ if (Object.hasOwn(rosters, wanted)) return { ok: true, roster: wanted };
73
+ const names = known.length === 0 ? "none are configured" : `configured rosters: ${known.join(", ")}`;
74
+ return { ok: false, reason: `no roster named "${wanted}" in workers.rosters (${names})` };
75
+ }
76
+ if (Object.hasOwn(rosters, COUNCIL_DEFAULT_ROSTER)) return { ok: true, roster: COUNCIL_DEFAULT_ROSTER };
77
+ return { ok: false, reason: COUNCIL_NO_ROSTER_NOTICE };
78
+ }
79
+
80
+ /**
81
+ * The dispatch-tool arguments this council becomes. Field names and value
82
+ * domains are the tool's own (`mode`, `roster`, `rounds`, `synthesis`), so the
83
+ * command adds no second grammar for the same contract. Defaults are left out
84
+ * rather than restated: the tool declares `rounds` 1 and `synthesis` none, and
85
+ * a caller that repeats them only creates a second place for them to drift.
86
+ */
87
+ export function buildCouncilDispatchArgs(
88
+ task: string,
89
+ roster: string,
90
+ options: CouncilCommandOptions = {},
91
+ ): Record<string, unknown> {
92
+ return {
93
+ mode: "council",
94
+ task,
95
+ roster,
96
+ ...(options.rounds !== undefined ? { rounds: options.rounds } : {}),
97
+ ...(options.synthesis !== undefined ? { synthesis: options.synthesis } : {}),
98
+ };
99
+ }