@iowarp/clio-coder 0.3.4 → 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 (376) hide show
  1. package/CHANGELOG.md +65 -2
  2. package/CONTRIBUTING.md +6 -6
  3. package/README.md +16 -5
  4. package/dist/{acp-S5R4RR5B.js → acp-SK4MD6MM.js} +11 -11
  5. package/dist/{agents-P6DMMVZY.js → agents-2FN2K6ME.js} +33 -26
  6. package/dist/assets/codewiki.json +1 -1
  7. package/dist/{auth-2XCZLPKS.js → auth-QIYZWM5I.js} +15 -15
  8. package/dist/{chunk-EKMEHE4H.js → chunk-33YXPOE3.js} +2 -3
  9. package/dist/{chunk-VAWWTKDP.js → chunk-3HAPLH5M.js} +11 -11
  10. package/dist/{chunk-YCWGATWI.js → chunk-465YSENW.js} +3 -3
  11. package/dist/{chunk-4OC57DA6.js → chunk-4DGYLA73.js} +53 -2
  12. package/dist/{chunk-UZHIZC5S.js → chunk-4DWFMQDR.js} +61 -76
  13. package/dist/{chunk-ZWMF7253.js → chunk-5C3AQNDW.js} +328 -9
  14. package/dist/{chunk-22NAGB7X.js → chunk-5C77SEEY.js} +5 -94
  15. package/dist/{chunk-WPQLXFOZ.js → chunk-5FR74PWO.js} +3 -2
  16. package/dist/{chunk-35MKKU5R.js → chunk-5UJ6ECTS.js} +18 -10
  17. package/dist/{chunk-BRXQQJFP.js → chunk-6M7VS3J3.js} +571 -50
  18. package/dist/{chunk-QQK64KLB.js → chunk-6TUKSZVF.js} +141 -23
  19. package/dist/{chunk-N4CZJQRK.js → chunk-AB4XIIVB.js} +8 -6
  20. package/dist/{chunk-KRPY7NTG.js → chunk-BMWK7ZIZ.js} +14 -20
  21. package/dist/{chunk-4BPJXDWC.js → chunk-C4JBQ5SR.js} +30 -14
  22. package/dist/{chunk-ZYKPLLNQ.js → chunk-CEYBNUGC.js} +821 -83
  23. package/dist/{chunk-VEZEGCGW.js → chunk-D4MDIG46.js} +20 -18
  24. package/dist/chunk-DJNLUABN.js +843 -0
  25. package/dist/{chunk-BP4OYD6A.js → chunk-DMD2AGVS.js} +21 -2
  26. package/dist/{chunk-KOHPCX4K.js → chunk-DOOEX22V.js} +2 -2
  27. package/dist/chunk-DQA7QLMD.js +123 -0
  28. package/dist/chunk-DR52UMZW.js +21 -0
  29. package/dist/{chunk-3HZ5RWN2.js → chunk-EBEFWSGL.js} +9 -7
  30. package/dist/{chunk-EDRHSCIE.js → chunk-EELBMBT6.js} +128 -13
  31. package/dist/{chunk-HV5X7OR2.js → chunk-EOOQZZDE.js} +16 -14
  32. package/dist/{chunk-WR67VIZY.js → chunk-FOT2FX5J.js} +63 -5
  33. package/dist/{chunk-BPGS2WCQ.js → chunk-GEYXPTRF.js} +2 -1
  34. package/dist/{chunk-FYYLNIL5.js → chunk-GH5622CP.js} +2 -2
  35. package/dist/{chunk-BEY543CS.js → chunk-GOXNB3AO.js} +5 -2
  36. package/dist/chunk-GWS3VEIW.js +195 -0
  37. package/dist/{chunk-G4BMMOKF.js → chunk-HVDIIIQW.js} +2 -2
  38. package/dist/chunk-HWUFFB6L.js +83 -0
  39. package/dist/{chunk-X6COSD2O.js → chunk-J7PIKKWC.js} +8 -436
  40. package/dist/{chunk-NILBFAPG.js → chunk-JNXPYBB4.js} +2 -2
  41. package/dist/{chunk-4VP4KH3K.js → chunk-JRIO5UD2.js} +4 -4
  42. package/dist/{chunk-K6WL7QZT.js → chunk-JTSEDYVQ.js} +7 -7
  43. package/dist/{chunk-QKMUKYO7.js → chunk-KCMKRQX4.js} +236 -84
  44. package/dist/chunk-KZ2H5X4G.js +1026 -0
  45. package/dist/{chunk-A2GZF7DC.js → chunk-LADCF22A.js} +13 -13
  46. package/dist/chunk-LCGCVYZ4.js +57 -0
  47. package/dist/chunk-M4AKACEO.js +382 -0
  48. package/dist/{chunk-POHLU5DW.js → chunk-M6L6IDJG.js} +3 -3
  49. package/dist/{chunk-4JUF2NNX.js → chunk-MXI6J5JF.js} +7 -7
  50. package/dist/{chunk-X4RCMKVQ.js → chunk-NDINPTJ4.js} +2 -2
  51. package/dist/{chunk-TTNYS3EA.js → chunk-OB5HIGJY.js} +1 -1
  52. package/dist/{chunk-7RXG6QRZ.js → chunk-OBMAI2DP.js} +61 -840
  53. package/dist/{chunk-5M54SPOL.js → chunk-ODFEOB4F.js} +161 -5
  54. package/dist/chunk-PD3MESLB.js +242 -0
  55. package/dist/{chunk-ED4KHGC3.js → chunk-PPAMZ32Z.js} +9 -2
  56. package/dist/{chunk-VMNQ6OZA.js → chunk-QCTRSGHQ.js} +963 -786
  57. package/dist/chunk-RVG5JXAL.js +41 -0
  58. package/dist/{chunk-RD5U66HV.js → chunk-SROCI7ZU.js} +7 -7
  59. package/dist/{chunk-MFFY33HR.js → chunk-THKY7CD7.js} +466 -205
  60. package/dist/{chunk-34475P3I.js → chunk-TSHXZTOQ.js} +5 -4
  61. package/dist/{chunk-PCZJO5TI.js → chunk-UFQ3F4FW.js} +13 -178
  62. package/dist/{chunk-AD2SYQYC.js → chunk-UHXRNZ2J.js} +121 -3
  63. package/dist/chunk-UND3GU2L.js +103 -0
  64. package/dist/{chunk-QQL5RT5M.js → chunk-UUANF5CR.js} +2323 -2114
  65. package/dist/{chunk-VJWL6YS5.js → chunk-UUVG37B4.js} +2 -2
  66. package/dist/chunk-UVDSQ6LW.js +472 -0
  67. package/dist/{chunk-QWU7ZBO7.js → chunk-VQNODYQ4.js} +215 -56
  68. package/dist/chunk-VREKEFLL.js +37 -0
  69. package/dist/{chunk-2TZWSW76.js → chunk-WHGPSPT5.js} +2 -2
  70. package/dist/{chunk-TW3WDMVS.js → chunk-WHJYKASB.js} +2 -2
  71. package/dist/{chunk-MEQ45TQ4.js → chunk-WJHBC77E.js} +21 -7
  72. package/dist/{chunk-HXG4IURW.js → chunk-X2KV5FXT.js} +2 -2
  73. package/dist/{chunk-YHZX5GEU.js → chunk-XAKHZX5N.js} +2 -2
  74. package/dist/{chunk-2LZI5CAG.js → chunk-XEGB6BCN.js} +228 -36
  75. package/dist/{chunk-E25LMLRW.js → chunk-YD734TPH.js} +2 -2
  76. package/dist/{verifiers-4UUM6TEE.js → chunk-YTYFXUI3.js} +121 -372
  77. package/dist/{chunk-3JLKSKD7.js → chunk-ZGH7FGS5.js} +17 -7
  78. package/dist/{chunk-VSNATDE6.js → chunk-ZZMN5OM4.js} +2 -2
  79. package/dist/cli/index.js +34 -32
  80. package/dist/{clio-J5JIOIDS.js → clio-WBVQEBKO.js} +7 -7
  81. package/dist/{code-nav-AXCXSBHX.js → code-nav-FGGFIE7L.js} +7 -7
  82. package/dist/codewiki/build-worker.js +4 -4
  83. package/dist/{components-KELWS457.js → components-F7OEATSO.js} +5 -5
  84. package/dist/{config-OEBMIN2U.js → config-TRBL3RCF.js} +48 -41
  85. package/dist/{configure-PUQOSIXQ.js → configure-OLCVPHNM.js} +17 -17
  86. package/dist/{context-URSXPBCK.js → context-MJIJ6GOX.js} +12 -12
  87. package/dist/{context-EKDCKUUZ.js → context-WFPKQSM6.js} +26 -9
  88. package/dist/{context-MGSE4Z2T.js → context-XEWE3MOJ.js} +44 -37
  89. package/dist/{context-clear-KDAJRNUK.js → context-clear-KNOS2JPB.js} +44 -37
  90. package/dist/{context-index-BZ4UYMTC.js → context-index-SSR5ECNE.js} +3 -3
  91. package/dist/{context-working-set-SBKMPPI2.js → context-working-set-EUXAZI6N.js} +14 -13
  92. package/dist/{dispatch-runner-MSWN72NK.js → dispatch-runner-B7MTOVKL.js} +321 -60
  93. package/dist/{docs-2C2LTVT2.js → docs-FLJTIDSE.js} +5 -5
  94. package/dist/{doctor-7BSE27PJ.js → doctor-RN4YKO2X.js} +15 -15
  95. package/dist/{eval-IZGDOO4H.js → eval-RUBJVSNQ.js} +52 -236
  96. package/dist/{evidence-SR7WXB5B.js → evidence-JZNBUOQZ.js} +39 -33
  97. package/dist/{evolve-K7VE2CBX.js → evolve-FJVC4KKI.js} +39 -33
  98. package/dist/{extensions-QVDOHDGJ.js → extensions-IQL36S7K.js} +5 -5
  99. package/dist/{fleet-7XMJNQNF.js → fleet-BDKYJFCP.js} +243 -370
  100. package/dist/fleet-commands-ZFIWZSB3.js +70 -0
  101. package/dist/fleet-graph-Y6HPXIVF.js +125 -0
  102. package/dist/fleet-new-RDVJLHHH.js +48 -0
  103. package/dist/{fleet-preflight-AQNAH644.js → fleet-preflight-BHSNPBMH.js} +2 -2
  104. package/dist/fleet-validate-BIYREGIK.js +79 -0
  105. package/dist/{init-JGNPAYXT.js → init-LQUB5COQ.js} +57 -48
  106. package/dist/library-NJAHIGG4.js +217 -0
  107. package/dist/memory-OG6HOYKM.js +472 -0
  108. package/dist/{models-ZMMLFJNN.js → models-5ZG5XY7J.js} +23 -22
  109. package/dist/{monitor-2F3T5KHP.js → monitor-TJ7AMTGB.js} +69 -35
  110. package/dist/{orchestrator-ORHT43JB.js → orchestrator-WZYB54DM.js} +4868 -1189
  111. package/dist/{paths-UXLN5YYZ.js → paths-XUC7GS6E.js} +5 -5
  112. package/dist/{reset-NXGTYNUO.js → reset-PXQT45IY.js} +8 -8
  113. package/dist/{run-RF4WJGMT.js → run-FQ74YF62.js} +82 -62
  114. package/dist/{share-UT3W6E4M.js → share-FW7SVCL3.js} +34 -10
  115. package/dist/{skills-PSACKC5Q.js → skills-7E7IRB3R.js} +25 -9
  116. package/dist/{skills-eval-WJSI55RZ.js → skills-eval-LI75W6OK.js} +43 -35
  117. package/dist/{targets-PIIRAOYS.js → targets-4CIFKCTW.js} +27 -24
  118. package/dist/{terminal-lease-ULWXWNVY.js → terminal-lease-WUZY7ZV5.js} +5 -4
  119. package/dist/{uninstall-FZCQCDKC.js → uninstall-7FV7IP4E.js} +5 -5
  120. package/dist/{upgrade-346TZ6AV.js → upgrade-K2HVIVMQ.js} +21 -20
  121. package/dist/{usage-6KKXR32N.js → usage-GTZELZQX.js} +159 -59
  122. package/dist/verifiers-RLAHT27O.js +336 -0
  123. package/dist/{verify-X5HDROLA.js → verify-BX3BRKH5.js} +7 -6
  124. package/dist/{wiki-generate-7STOCIFZ.js → wiki-generate-ASIFASCN.js} +58 -48
  125. package/dist/worker/entry.js +98 -84
  126. package/dist/{workspace-G4ZWUIPR.js → workspace-ZJ6BFM3Q.js} +4 -4
  127. package/docs/README.md +4 -3
  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-placement.md +1 -2
  132. package/docs/artifact-versions.md +10 -6
  133. package/docs/built-in-agents.md +26 -2
  134. package/docs/capacity-and-scheduling.md +1 -1
  135. package/docs/commands-and-modes.md +90 -8
  136. package/docs/configuration-and-targets.md +90 -2
  137. package/docs/context-engine.md +4 -2
  138. package/docs/context-working-set.md +4 -4
  139. package/docs/development-pipeline.md +1 -1
  140. package/docs/dispatch-architecture-rationale.md +1 -1
  141. package/docs/documentation-coverage.md +4 -4
  142. package/docs/documentation-guide.md +4 -4
  143. package/docs/eval-runner.md +1 -1
  144. package/docs/evals-internal.md +4 -45
  145. package/docs/evidence-and-memory.md +70 -10
  146. package/docs/evolution.md +1 -1
  147. package/docs/exit-codes-and-output.md +4 -1
  148. package/docs/extensions-and-sharing.md +6 -2
  149. package/docs/fleet-demo-runbook.md +2 -2
  150. package/docs/fleet-dispatch.md +224 -11
  151. package/docs/git-commit-provenance.md +2 -2
  152. package/docs/glossary.md +1 -1
  153. package/docs/installation-and-lifecycle.md +2 -2
  154. package/docs/middleware-and-components.md +20 -2
  155. package/docs/model-catalog.md +1 -1
  156. package/docs/observability.md +55 -8
  157. package/docs/proactive-memory.md +26 -16
  158. package/docs/prompt-envelope-and-tools.md +4 -2
  159. package/docs/provider-adapter-cookbook.md +1 -1
  160. package/docs/release-cut-checklist.md +83 -65
  161. package/docs/resource-library.md +59 -0
  162. package/docs/safety-model.md +29 -7
  163. package/docs/scientific-validation.md +3 -3
  164. package/docs/session-lifecycle.md +37 -1
  165. package/docs/skills-marketplace.md +16 -3
  166. package/docs/tool-usage.md +14 -7
  167. package/docs/trace-store.md +1 -1
  168. package/docs/troubleshooting.md +1 -1
  169. package/docs/tui-design.md +38 -4
  170. package/docs/worker-dispatch-mechanics.md +3 -3
  171. package/package.json +7 -4
  172. package/src/cli/agents.ts +2 -3
  173. package/src/cli/argv.ts +14 -1
  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 +121 -0
  178. package/src/cli/fleet-validate.ts +30 -0
  179. package/src/cli/fleet.ts +188 -335
  180. package/src/cli/index.ts +4 -2
  181. package/src/cli/library.ts +190 -0
  182. package/src/cli/memory.ts +272 -10
  183. package/src/cli/modes/json-stream.ts +2 -2
  184. package/src/cli/modes/print.ts +12 -1
  185. package/src/cli/run.ts +22 -2
  186. package/src/cli/share.ts +13 -1
  187. package/src/cli/targets.ts +12 -3
  188. package/src/cli/usage.ts +160 -20
  189. package/src/core/bus-events.ts +7 -0
  190. package/src/core/commit-attribution.ts +4 -4
  191. package/src/core/config.ts +130 -0
  192. package/src/core/defaults.ts +81 -0
  193. package/src/core/response-model-id.ts +134 -0
  194. package/src/core/toml.ts +62 -0
  195. package/src/core/workspace-files.ts +0 -1
  196. package/src/domains/agents/builtins/architect.md +2 -1
  197. package/src/domains/agents/builtins/oracle.md +33 -0
  198. package/src/domains/agents/catalog.ts +18 -5
  199. package/src/domains/agents/fleet-contract.ts +278 -16
  200. package/src/domains/agents/index.ts +14 -0
  201. package/src/domains/agents/recipe.ts +54 -14
  202. package/src/domains/agents/result-contract.ts +242 -5
  203. package/src/domains/config/classify.ts +4 -0
  204. package/src/domains/context/bootstrap.ts +36 -27
  205. package/src/domains/context/project-metadata.ts +19 -63
  206. package/src/domains/context/prompt-context.ts +8 -0
  207. package/src/domains/context/working-set/policies/index.ts +3 -4
  208. package/src/domains/dispatch/active-route-planner.ts +14 -0
  209. package/src/domains/dispatch/backoff.ts +2 -1
  210. package/src/domains/dispatch/budget-envelope.ts +396 -0
  211. package/src/domains/dispatch/capability-match.ts +1 -0
  212. package/src/domains/dispatch/checkout-writer-lease.ts +175 -0
  213. package/src/domains/dispatch/contract.ts +36 -0
  214. package/src/domains/dispatch/delegation-plan.ts +167 -0
  215. package/src/domains/dispatch/execution-plan.ts +76 -5
  216. package/src/domains/dispatch/execution-role.ts +3 -1
  217. package/src/domains/dispatch/execution-scheduler.ts +183 -67
  218. package/src/domains/dispatch/extension.ts +339 -36
  219. package/src/domains/dispatch/fleet-gate.ts +14 -0
  220. package/src/domains/dispatch/fleet-plan.ts +63 -3
  221. package/src/domains/dispatch/fleet-run.ts +737 -0
  222. package/src/domains/dispatch/gate-role-prompts.ts +9 -0
  223. package/src/domains/dispatch/host-verification.ts +178 -0
  224. package/src/domains/dispatch/index.ts +38 -0
  225. package/src/domains/dispatch/intent.ts +159 -0
  226. package/src/domains/dispatch/orphan-recovery.ts +1 -0
  227. package/src/domains/dispatch/receipt-integrity.ts +12 -4
  228. package/src/domains/dispatch/state.ts +37 -3
  229. package/src/domains/dispatch/types.ts +61 -9
  230. package/src/domains/dispatch/validation.ts +80 -6
  231. package/src/domains/dispatch/worker-spawn.ts +14 -3
  232. package/src/domains/eval/metrics/evidence.ts +0 -116
  233. package/src/domains/eval/metrics/invariants.ts +1 -1
  234. package/src/domains/eval/runners/clio-run.ts +1 -10
  235. package/src/domains/eval/runners/external-command.ts +2 -29
  236. package/src/domains/eval/schema/suite.ts +0 -7
  237. package/src/domains/eval/suites/run.ts +1 -7
  238. package/src/domains/evidence/trust-status.ts +10 -1
  239. package/src/domains/memory/index.ts +22 -0
  240. package/src/domains/memory/operations.ts +58 -1
  241. package/src/domains/memory/promotion.ts +281 -0
  242. package/src/domains/memory/prompt-section.ts +25 -5
  243. package/src/domains/memory/proposal.ts +51 -7
  244. package/src/domains/memory/task-bank.ts +3 -2
  245. package/src/domains/memory/task-memory-handoff.ts +181 -24
  246. package/src/domains/memory/task-memory-policy.ts +3 -1
  247. package/src/domains/memory/types.ts +37 -0
  248. package/src/domains/memory/validate.ts +178 -0
  249. package/src/domains/middleware/index.ts +15 -0
  250. package/src/domains/middleware/memory-intervention.ts +35 -25
  251. package/src/domains/middleware/runtime.ts +6 -0
  252. package/src/domains/middleware/skills-reminder.ts +19 -4
  253. package/src/domains/middleware/stalled-turn.ts +43 -1
  254. package/src/domains/middleware/types.ts +10 -0
  255. package/src/domains/middleware/watchdog.ts +281 -0
  256. package/src/domains/observability/contract.ts +9 -2
  257. package/src/domains/observability/cost.ts +31 -4
  258. package/src/domains/observability/extension.ts +2 -2
  259. package/src/domains/observability/index.ts +10 -0
  260. package/src/domains/observability/out-of-turn-usage.ts +223 -0
  261. package/src/domains/providers/index.ts +3 -0
  262. package/src/domains/providers/model-discovery.ts +9 -0
  263. package/src/domains/providers/runtime-resolution.ts +38 -1
  264. package/src/domains/providers/runtimes/common/probe-helpers.ts +97 -16
  265. package/src/domains/providers/types/context-window-slots.ts +18 -0
  266. package/src/domains/providers/types/runtime-descriptor.ts +3 -1
  267. package/src/domains/resources/index.ts +20 -0
  268. package/src/domains/resources/library.ts +326 -0
  269. package/src/domains/resources/skills/marketplace.ts +37 -12
  270. package/src/domains/safety/call-target.ts +211 -14
  271. package/src/domains/safety/decision-presentation.ts +268 -0
  272. package/src/domains/safety/redaction.ts +73 -0
  273. package/src/domains/session/context-ledger.ts +10 -1
  274. package/src/domains/session/decision-board.ts +4 -0
  275. package/src/domains/session/entries.ts +3 -0
  276. package/src/domains/session/handoff.ts +629 -0
  277. package/src/domains/session/history.ts +68 -19
  278. package/src/domains/session/usage.ts +24 -7
  279. package/src/domains/share/archive.ts +67 -2
  280. package/src/engine/acp/event-mapper.ts +7 -0
  281. package/src/engine/acp/server.ts +29 -2
  282. package/src/engine/apis/lmstudio.ts +25 -4
  283. package/src/engine/apis/openai-completions.ts +147 -22
  284. package/src/engine/claude/sdk-runtime.ts +8 -2
  285. package/src/engine/claude/tool-safety.ts +13 -0
  286. package/src/engine/loop-guard.ts +27 -3
  287. package/src/engine/worker-events.ts +4 -3
  288. package/src/engine/worker-runtime.ts +59 -54
  289. package/src/entry/orchestrator.ts +55 -1
  290. package/src/interactive/bus-notices.ts +26 -0
  291. package/src/interactive/chat-loop-messages.ts +22 -0
  292. package/src/interactive/chat-loop.ts +248 -1
  293. package/src/interactive/chat-renderer.ts +41 -3
  294. package/src/interactive/clio-editor.ts +44 -7
  295. package/src/interactive/context-overlay.ts +43 -5
  296. package/src/interactive/cost-overlay.ts +70 -11
  297. package/src/interactive/council-dispatch.ts +30 -0
  298. package/src/interactive/council-grid.ts +213 -0
  299. package/src/interactive/council.ts +99 -0
  300. package/src/interactive/dispatch-board.ts +471 -50
  301. package/src/interactive/fleet-run-preview.ts +307 -0
  302. package/src/interactive/footer/notifications.ts +219 -0
  303. package/src/interactive/footer/widgets.ts +13 -0
  304. package/src/interactive/handoff-round.ts +56 -0
  305. package/src/interactive/interactive-application.ts +49 -2
  306. package/src/interactive/interactive-event-projection.ts +9 -1
  307. package/src/interactive/interactive-input-runtime.ts +11 -1
  308. package/src/interactive/interactive-presentation.ts +11 -1
  309. package/src/interactive/interactive-slash-runtime.ts +52 -2
  310. package/src/interactive/interactive-subscriptions.ts +14 -2
  311. package/src/interactive/memory-overlay.ts +89 -4
  312. package/src/interactive/oracle.ts +179 -0
  313. package/src/interactive/overlay-ask-user-lifecycle.ts +7 -1
  314. package/src/interactive/overlay-frame.ts +5 -2
  315. package/src/interactive/overlay-general-openers.ts +230 -2
  316. package/src/interactive/overlay-key-routing.ts +58 -2
  317. package/src/interactive/overlay-lifecycle.ts +52 -5
  318. package/src/interactive/overlay-permission-lifecycle.ts +33 -8
  319. package/src/interactive/overlay-resource-openers.ts +11 -3
  320. package/src/interactive/overlay-session-lifecycle.ts +234 -2
  321. package/src/interactive/overlay-transitions.ts +11 -0
  322. package/src/interactive/overlays/ask-user.ts +74 -30
  323. package/src/interactive/overlays/decisions.ts +3 -1
  324. package/src/interactive/overlays/fleet-run-approval.ts +208 -0
  325. package/src/interactive/overlays/handoff-review.ts +185 -0
  326. package/src/interactive/overlays/library-install-confirm.ts +151 -0
  327. package/src/interactive/overlays/list-overlay.ts +168 -2
  328. package/src/interactive/overlays/settings.ts +101 -4
  329. package/src/interactive/overlays/side-question.ts +139 -0
  330. package/src/interactive/overlays/skills-hub.ts +401 -15
  331. package/src/interactive/permission-hint.ts +35 -0
  332. package/src/interactive/permission-overlay.ts +95 -45
  333. package/src/interactive/renderers/tool-execution.ts +19 -49
  334. package/src/interactive/session-last-turn.ts +8 -1
  335. package/src/interactive/session-usage-reseed.ts +36 -10
  336. package/src/interactive/side-question.ts +171 -0
  337. package/src/interactive/slash-commands.ts +434 -7
  338. package/src/interactive/slash-spec.ts +19 -6
  339. package/src/interactive/status/summary.ts +5 -0
  340. package/src/interactive/status/types.ts +5 -0
  341. package/src/interactive/terminal-lease.ts +1 -0
  342. package/src/interactive/theme/tokens.ts +30 -0
  343. package/src/interactive/turn-context.ts +96 -23
  344. package/src/interactive/turn-middleware.ts +16 -1
  345. package/src/interactive/turn-runtime.ts +37 -8
  346. package/src/interactive/turn-state.ts +3 -0
  347. package/src/interactive/watchdog-run.ts +75 -0
  348. package/src/interactive/worker-progress.ts +440 -0
  349. package/src/interactive/worker-share.ts +56 -1
  350. package/src/interactive/worker-stream.ts +58 -110
  351. package/src/tools/agent-tools.ts +28 -3
  352. package/src/tools/ask-user.ts +21 -1
  353. package/src/tools/bootstrap.ts +3 -0
  354. package/src/tools/compete-worktrees.ts +13 -79
  355. package/src/tools/context/index.ts +2 -2
  356. package/src/tools/dispatch-admission.ts +242 -8
  357. package/src/tools/dispatch-arguments.ts +65 -1
  358. package/src/tools/dispatch-event-text.ts +19 -0
  359. package/src/tools/dispatch-plan.ts +136 -6
  360. package/src/tools/dispatch-runner.ts +319 -13
  361. package/src/tools/dispatch-types.ts +20 -1
  362. package/src/tools/dispatch.ts +96 -3
  363. package/src/tools/monitor.ts +31 -0
  364. package/src/tools/profiles.ts +18 -4
  365. package/src/tools/registry.ts +15 -5
  366. package/src/tools/result-disposition.ts +156 -0
  367. package/src/tools/result-shaping.ts +59 -1
  368. package/src/tools/task-worktree.ts +238 -0
  369. package/src/tools/verify/authoring.ts +116 -55
  370. package/src/tools/verify/scripts.ts +62 -0
  371. package/src/tools/worker-evidence.ts +21 -1
  372. package/src/worker/spec-contract.ts +44 -3
  373. package/dist/chunk-EFADSJET.js +0 -18
  374. package/dist/chunk-HC4CLZ2Y.js +0 -68
  375. package/dist/memory-4ALKDJ4Q.js +0 -246
  376. package/src/domains/eval/metrics/chaos-stream.ts +0 -93
@@ -0,0 +1,326 @@
1
+ import { createHash } from "node:crypto";
2
+ import { existsSync, readFileSync, statSync } from "node:fs";
3
+ import path from "node:path";
4
+ import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
5
+ import { readSettings, updateSettings } from "../../core/config.js";
6
+ import { runCommandVector, type SafeCommandResult } from "../../core/safe-exec.js";
7
+ import { safeResourceWrite } from "../../core/safe-resource-write.js";
8
+ import { clioConfigDir } from "../../core/xdg.js";
9
+ import { parseFrontmatter } from "../agents/frontmatter.js";
10
+ import {
11
+ assertAgentSpecPolicy,
12
+ normalizeAgentSpec,
13
+ parseAgentRecipeSchema,
14
+ parseFleetContract,
15
+ } from "../agents/index.js";
16
+ import { loadPromptTemplates } from "./prompts/loader.js";
17
+ import { normalizedSkillHash } from "./skills/install.js";
18
+ import {
19
+ type DiscoverMarketplaceOptions,
20
+ discoverMarketplaceSkills,
21
+ installSkill,
22
+ type LibraryEntryKind,
23
+ type LibraryRequirementRef,
24
+ type MarketplaceSkill,
25
+ } from "./skills/marketplace.js";
26
+
27
+ export type LibraryEntry = MarketplaceSkill;
28
+
29
+ export interface LibraryDiscoveryResult {
30
+ entries: LibraryEntry[];
31
+ diagnostics: string[];
32
+ refusals: Readonly<Record<string, string>>;
33
+ }
34
+
35
+ function isRecord(value: unknown): value is Record<string, unknown> {
36
+ return typeof value === "object" && value !== null && !Array.isArray(value);
37
+ }
38
+
39
+ function catalogPath(): string {
40
+ const configured = readSettings().library.catalog;
41
+ return path.resolve(configured ?? path.join(clioConfigDir(), "library.yaml"));
42
+ }
43
+
44
+ function parseCatalog(filePath: string, diagnostics: string[]): LibraryEntry[] {
45
+ if (!existsSync(filePath)) return [];
46
+ try {
47
+ const raw = readFileSync(filePath, "utf8");
48
+ const parsed = filePath.endsWith(".json") ? JSON.parse(raw) : parseYaml(raw);
49
+ const rows = Array.isArray(parsed) ? parsed : isRecord(parsed) && Array.isArray(parsed.skills) ? parsed.skills : [];
50
+ return rows.flatMap((value): LibraryEntry[] => {
51
+ if (
52
+ !isRecord(value) ||
53
+ typeof value.name !== "string" ||
54
+ typeof value.description !== "string" ||
55
+ typeof value.sourceUrl !== "string"
56
+ ) {
57
+ diagnostics.push(`library catalog entry malformed: ${filePath}`);
58
+ return [];
59
+ }
60
+ const kind: LibraryEntryKind =
61
+ value.kind === undefined || value.kind === "skill"
62
+ ? "skill"
63
+ : value.kind === "agent" || value.kind === "prompt" || value.kind === "fleet"
64
+ ? value.kind
65
+ : "skill";
66
+ if (value.kind !== undefined && !["skill", "agent", "prompt", "fleet"].includes(String(value.kind))) {
67
+ diagnostics.push(`library catalog entry has unsupported kind: ${value.name}`);
68
+ return [];
69
+ }
70
+ if (
71
+ value.requires !== undefined &&
72
+ (!Array.isArray(value.requires) || value.requires.some((item) => typeof item !== "string"))
73
+ ) {
74
+ diagnostics.push(`library_requirement_malformed: ${value.name}`);
75
+ return [];
76
+ }
77
+ const sourceUrl = /^(?:https?:\/\/|git@)/.test(value.sourceUrl)
78
+ ? value.sourceUrl
79
+ : path.resolve(path.dirname(filePath), value.sourceUrl);
80
+ const requires = Array.isArray(value.requires)
81
+ ? value.requires.filter((item): item is LibraryRequirementRef => typeof item === "string")
82
+ : undefined;
83
+ return [
84
+ {
85
+ kind,
86
+ name: value.name.trim(),
87
+ description: value.description.trim(),
88
+ sourceUrl,
89
+ origin: "index",
90
+ ...(typeof value.version === "string" ? { version: value.version } : {}),
91
+ ...(requires ? { requires } : {}),
92
+ },
93
+ ];
94
+ });
95
+ } catch (error) {
96
+ diagnostics.push(`library catalog unreadable: ${error instanceof Error ? error.message : String(error)}`);
97
+ return [];
98
+ }
99
+ }
100
+
101
+ export function libraryEntryRef(entry: Pick<LibraryEntry, "kind" | "name">): LibraryRequirementRef {
102
+ return `${entry.kind}:${entry.name}`;
103
+ }
104
+
105
+ export function discoverLibrary(
106
+ options: { catalog?: string; cwd?: string; marketplace?: DiscoverMarketplaceOptions } = {},
107
+ ): LibraryDiscoveryResult {
108
+ const marketplace = discoverMarketplaceSkills({
109
+ ...(options.marketplace ?? {}),
110
+ ...(options.cwd ? { cwd: options.cwd } : {}),
111
+ });
112
+ const diagnostics = [...marketplace.diagnostics.filter((item) => !item.includes("no local skill marketplace"))];
113
+ const privatePath = catalogPath();
114
+ const primary = options.catalog ? parseCatalog(path.resolve(options.catalog), diagnostics) : [];
115
+ const privateEntries = parseCatalog(privatePath, diagnostics);
116
+ const byRef = new Map<string, LibraryEntry>();
117
+ for (const entry of [...marketplace.skills, ...primary, ...privateEntries]) byRef.set(libraryEntryRef(entry), entry);
118
+ const refusals: Record<string, string> = {};
119
+ const entries = [...byRef.values()].filter((entry) => {
120
+ try {
121
+ resolveLibraryRequirements(entry, [...byRef.values()]);
122
+ return true;
123
+ } catch (error) {
124
+ const message = error instanceof Error ? error.message : String(error);
125
+ refusals[libraryEntryRef(entry)] = message;
126
+ if (!diagnostics.includes(message)) diagnostics.push(message);
127
+ return false;
128
+ }
129
+ });
130
+ return {
131
+ entries: entries.sort((a, b) => libraryEntryRef(a).localeCompare(libraryEntryRef(b))),
132
+ diagnostics,
133
+ refusals,
134
+ };
135
+ }
136
+
137
+ const REQUIREMENT_PATTERN = /^(skill|agent|prompt|fleet):([A-Za-z0-9][A-Za-z0-9._-]*)$/;
138
+
139
+ export function resolveLibraryRequirements(entry: LibraryEntry, catalog: ReadonlyArray<LibraryEntry>): LibraryEntry[] {
140
+ const byRef = new Map(catalog.map((item) => [libraryEntryRef(item), item]));
141
+ const ordered: LibraryEntry[] = [];
142
+ const visiting: string[] = [];
143
+ const visited = new Set<string>();
144
+ const visit = (current: LibraryEntry): void => {
145
+ const currentRef = libraryEntryRef(current);
146
+ if (visited.has(currentRef)) return;
147
+ const cycleAt = visiting.indexOf(currentRef);
148
+ if (cycleAt >= 0)
149
+ throw new Error(`library_requirement_cycle: ${[...visiting.slice(cycleAt), currentRef].join(" -> ")}`);
150
+ visiting.push(currentRef);
151
+ for (const requirement of current.requires ?? []) {
152
+ if (!REQUIREMENT_PATTERN.test(requirement)) throw new Error("library_requirement_malformed");
153
+ const dependency = byRef.get(requirement);
154
+ if (!dependency) throw new Error(`library_requirement_missing: ${requirement}`);
155
+ visit(dependency);
156
+ }
157
+ visiting.pop();
158
+ visited.add(currentRef);
159
+ ordered.push(current);
160
+ };
161
+ visit(entry);
162
+ return ordered;
163
+ }
164
+
165
+ function sourceFile(entry: LibraryEntry): string {
166
+ const source = path.resolve(entry.sourceUrl);
167
+ if (entry.kind === "skill") return source;
168
+ if (!existsSync(source)) throw new Error(`library source path does not exist: ${source}`);
169
+ return source;
170
+ }
171
+
172
+ function validateEntry(entry: LibraryEntry, raw: string, filePath: string): void {
173
+ if (entry.kind === "fleet") {
174
+ parseFleetContract(raw, filePath);
175
+ return;
176
+ }
177
+ if (entry.kind === "agent") {
178
+ const parsed = parseFrontmatter(raw, filePath);
179
+ const recipe = parseAgentRecipeSchema({ id: entry.name, source: "user", filepath: filePath, ...parsed });
180
+ assertAgentSpecPolicy(normalizeAgentSpec(recipe));
181
+ return;
182
+ }
183
+ if (entry.kind === "prompt") {
184
+ const loaded = loadPromptTemplates({ roots: [{ path: path.dirname(filePath), scope: "user", source: "library" }] });
185
+ if (!loaded.items.some((item) => item.filePath === filePath))
186
+ throw new Error(`prompt template is malformed: ${filePath}`);
187
+ }
188
+ }
189
+
190
+ export interface LibraryInstallPlan {
191
+ entry: LibraryEntry;
192
+ path: string;
193
+ sha256: string;
194
+ }
195
+
196
+ export function libraryInstallPath(entry: Pick<LibraryEntry, "kind" | "name">): string {
197
+ if (entry.kind === "skill") return path.join(clioConfigDir(), "skills", entry.name, "SKILL.md");
198
+ const root = entry.kind === "agent" ? "agents" : entry.kind === "fleet" ? "fleets" : "prompts";
199
+ return path.join(clioConfigDir(), root, `${entry.name}.md`);
200
+ }
201
+
202
+ function readLibraryPins(): Record<string, { sha256: string; sourceUrl: string }> {
203
+ const pinPath = path.join(clioConfigDir(), "library-pins.yaml");
204
+ if (!existsSync(pinPath)) return {};
205
+ const parsed = parseYaml(readFileSync(pinPath, "utf8"));
206
+ return isRecord(parsed) ? (parsed as Record<string, { sha256: string; sourceUrl: string }>) : {};
207
+ }
208
+
209
+ export interface LibraryRequirementStatus {
210
+ ordered: LibraryEntry[];
211
+ satisfied: LibraryEntry[];
212
+ unsatisfied: LibraryEntry[];
213
+ }
214
+
215
+ /**
216
+ * Whether this entry is already on disk for this operator. A kind-qualified pin
217
+ * and a kind-specific destination are each sufficient, which is the same rule
218
+ * the requirement classifier applies, so a surface that draws an installed or
219
+ * available column never disagrees with the gate that refuses an install.
220
+ */
221
+ export function libraryEntryInstalled(entry: Pick<LibraryEntry, "kind" | "name">): boolean {
222
+ return readLibraryPins()[libraryEntryRef(entry)] !== undefined || existsSync(libraryInstallPath(entry));
223
+ }
224
+
225
+ /**
226
+ * The pin recorded for this entry, or undefined when nothing pinned it. A
227
+ * destination can exist without a pin, which is why this is a separate question
228
+ * from `libraryEntryInstalled`.
229
+ */
230
+ export function libraryEntryPin(
231
+ entry: Pick<LibraryEntry, "kind" | "name">,
232
+ ): { sha256: string; sourceUrl: string } | undefined {
233
+ return readLibraryPins()[libraryEntryRef(entry)];
234
+ }
235
+
236
+ export function classifyLibraryRequirements(
237
+ entry: LibraryEntry,
238
+ catalog: ReadonlyArray<LibraryEntry>,
239
+ ): LibraryRequirementStatus {
240
+ const ordered = resolveLibraryRequirements(entry, catalog).slice(0, -1);
241
+ const satisfied = ordered.filter((requirement) => libraryEntryInstalled(requirement));
242
+ const satisfiedRefs = new Set(satisfied.map(libraryEntryRef));
243
+ return {
244
+ ordered,
245
+ satisfied,
246
+ unsatisfied: ordered.filter((requirement) => !satisfiedRefs.has(libraryEntryRef(requirement))),
247
+ };
248
+ }
249
+
250
+ export function planLibraryInstall(entry: LibraryEntry): LibraryInstallPlan {
251
+ if (entry.kind === "skill") {
252
+ const source = sourceFile(entry);
253
+ const file = statSync(source).isDirectory() ? path.join(source, "SKILL.md") : source;
254
+ const raw = readFileSync(file);
255
+ return {
256
+ entry,
257
+ path: libraryInstallPath(entry),
258
+ sha256: normalizedSkillHash(raw.toString("utf8")),
259
+ };
260
+ }
261
+ const source = sourceFile(entry);
262
+ const raw = readFileSync(source, "utf8");
263
+ validateEntry(entry, raw, source);
264
+ return {
265
+ entry,
266
+ path: libraryInstallPath(entry),
267
+ sha256: createHash("sha256").update(raw).digest("hex"),
268
+ };
269
+ }
270
+
271
+ function recordPin(plan: LibraryInstallPlan): void {
272
+ const pinPath = path.join(clioConfigDir(), "library-pins.yaml");
273
+ const pins = readLibraryPins();
274
+ pins[libraryEntryRef(plan.entry)] = { sha256: plan.sha256, sourceUrl: plan.entry.sourceUrl };
275
+ safeResourceWrite(pinPath, stringifyYaml(Object.fromEntries(Object.entries(pins).sort())), { encoding: "utf8" });
276
+ }
277
+
278
+ export function installLibraryPlan(plan: LibraryInstallPlan): void {
279
+ if (existsSync(plan.path)) throw new Error(`library destination already exists: ${plan.path}`);
280
+ if (plan.entry.kind === "skill") installSkill({ source: plan.entry.sourceUrl, scope: "user", name: plan.entry.name });
281
+ else safeResourceWrite(plan.path, readFileSync(sourceFile(plan.entry)));
282
+ recordPin(plan);
283
+ }
284
+
285
+ function confirmedRemote(): string {
286
+ const settings = readSettings().library;
287
+ if (!settings.remote || settings.remote !== settings.confirmedRemote) throw new Error("library_remote_unconfirmed");
288
+ return settings.remote;
289
+ }
290
+
291
+ export function confirmLibraryRemote(url: string): void {
292
+ const current = readSettings().library.remote;
293
+ if (current !== null && current !== url) throw new Error("library_remote_mismatch");
294
+ updateSettings((settings) => {
295
+ if (settings.library.remote === null) settings.library.remote = url;
296
+ settings.library.confirmedRemote = url;
297
+ });
298
+ }
299
+
300
+ export type LibraryCommandRunner = (
301
+ file: string,
302
+ args: ReadonlyArray<string>,
303
+ options: { cwd: string; workspaceRoot: string },
304
+ ) => Promise<SafeCommandResult>;
305
+
306
+ export async function syncLibrary(
307
+ direction: "sync" | "push",
308
+ runner: LibraryCommandRunner = runCommandVector,
309
+ ): Promise<void> {
310
+ const settings = readSettings().library;
311
+ if (!settings.sync) throw new Error("library_sync_disabled");
312
+ const remote = confirmedRemote();
313
+ const cwd = path.dirname(catalogPath());
314
+ const remoteResult = await runner("git", ["remote", "get-url", "library"], { cwd, workspaceRoot: cwd });
315
+ if (remoteResult.exitCode !== 0 || remoteResult.stdout.trim() !== remote)
316
+ throw new Error("library_remote_unconfirmed");
317
+ if (direction === "sync") {
318
+ const fetched = await runner("git", ["fetch", "library"], { cwd, workspaceRoot: cwd });
319
+ if (fetched.exitCode !== 0) throw new Error(fetched.stderr.trim() || "library sync failed");
320
+ const advanced = await runner("git", ["merge", "--ff-only", "FETCH_HEAD"], { cwd, workspaceRoot: cwd });
321
+ if (advanced.exitCode !== 0) throw new Error(advanced.stderr.trim() || "library sync failed");
322
+ return;
323
+ }
324
+ const result = await runner("git", ["push", "library"], { cwd, workspaceRoot: cwd });
325
+ if (result.exitCode !== 0) throw new Error(result.stderr.trim() || `library ${direction} failed`);
326
+ }
@@ -35,6 +35,8 @@ import { loadSkills, type Skill } from "./loader.js";
35
35
  */
36
36
 
37
37
  export type MarketplaceSkillOrigin = "catalog" | "index";
38
+ export type LibraryEntryKind = "skill" | "agent" | "prompt" | "fleet";
39
+ export type LibraryRequirementRef = `${LibraryEntryKind}:${string}`;
38
40
 
39
41
  /**
40
42
  * The diagnostic that means "nothing is wrong, nothing is configured". Callers
@@ -44,6 +46,7 @@ export type MarketplaceSkillOrigin = "catalog" | "index";
44
46
  export const MARKETPLACE_UNCONFIGURED = "no local skill marketplace catalog or index configured";
45
47
 
46
48
  export interface MarketplaceSkill {
49
+ kind: LibraryEntryKind;
47
50
  name: string;
48
51
  description: string;
49
52
  /** Local path or URL accepted by `clio-coder skills install`. */
@@ -53,6 +56,7 @@ export interface MarketplaceSkill {
53
56
  /** Catalog grouping ("git", "research", ...); absent in a flat catalog. */
54
57
  category?: string;
55
58
  origin: MarketplaceSkillOrigin;
59
+ requires?: LibraryRequirementRef[];
56
60
  }
57
61
 
58
62
  export type MarketplaceStatus = "installed" | "installable" | "unavailable";
@@ -131,7 +135,7 @@ function indexAudit(value: unknown): MarketplaceSkill["audit"] {
131
135
  return value === "pass" || value === "warn" || value === "fail" || value === "unknown" ? value : undefined;
132
136
  }
133
137
 
134
- function indexSkills(indexPath: string, diagnostics: string[]): MarketplaceSkill[] {
138
+ function parseMarketplaceIndex(indexPath: string, diagnostics: string[] = []): MarketplaceSkill[] {
135
139
  try {
136
140
  const parsed = JSON.parse(readFileSync(indexPath, "utf8")) as unknown;
137
141
  const rawSkills = Array.isArray(parsed)
@@ -139,7 +143,7 @@ function indexSkills(indexPath: string, diagnostics: string[]): MarketplaceSkill
139
143
  : parsed && typeof parsed === "object" && Array.isArray((parsed as { skills?: unknown }).skills)
140
144
  ? (parsed as { skills: unknown[] }).skills
141
145
  : [];
142
- return rawSkills.filter(isIndexSkill).map((skill) => {
146
+ return rawSkills.filter(isIndexSkill).flatMap((skill): MarketplaceSkill[] => {
143
147
  const record = skill as unknown as Record<string, unknown>;
144
148
  // Version, audit and category are published by `npm run skills:pin`.
145
149
  // They are advisory display metadata; an index entry stays installable
@@ -147,15 +151,35 @@ function indexSkills(indexPath: string, diagnostics: string[]): MarketplaceSkill
147
151
  const version = optionalString(record.version);
148
152
  const audit = indexAudit(record.audit);
149
153
  const category = optionalString(record.category);
150
- return {
151
- name: skill.name.trim(),
152
- description: skill.description.trim(),
153
- sourceUrl: skill.sourceUrl.trim(),
154
- ...(version ? { version } : {}),
155
- ...(audit ? { audit } : {}),
156
- ...(category ? { category } : {}),
157
- origin: "index" as const,
158
- };
154
+ if (record.kind !== undefined && !["skill", "agent", "prompt", "fleet"].includes(String(record.kind))) {
155
+ diagnostics.push(`skill marketplace index entry has unsupported kind: ${skill.name}`);
156
+ return [];
157
+ }
158
+ if (
159
+ record.requires !== undefined &&
160
+ (!Array.isArray(record.requires) || record.requires.some((entry) => typeof entry !== "string"))
161
+ ) {
162
+ diagnostics.push(`library_requirement_malformed: ${skill.name}`);
163
+ return [];
164
+ }
165
+ const kind: LibraryEntryKind =
166
+ record.kind === "agent" || record.kind === "prompt" || record.kind === "fleet" ? record.kind : "skill";
167
+ const requires = Array.isArray(record.requires)
168
+ ? record.requires.filter((entry): entry is LibraryRequirementRef => typeof entry === "string")
169
+ : undefined;
170
+ return [
171
+ {
172
+ kind,
173
+ name: skill.name.trim(),
174
+ description: skill.description.trim(),
175
+ sourceUrl: skill.sourceUrl.trim(),
176
+ ...(version ? { version } : {}),
177
+ ...(audit ? { audit } : {}),
178
+ ...(category ? { category } : {}),
179
+ ...(requires ? { requires } : {}),
180
+ origin: "index" as const,
181
+ },
182
+ ];
159
183
  });
160
184
  } catch (err) {
161
185
  diagnostics.push(`skill marketplace index unreadable: ${err instanceof Error ? err.message : String(err)}`);
@@ -226,6 +250,7 @@ function catalogEntry(skill: Skill, catalogDir: string): MarketplaceSkill {
226
250
  ...(skill.provenance?.audit ? { audit: skill.provenance.audit } : {}),
227
251
  ...(category ? { category } : {}),
228
252
  origin: "catalog",
253
+ kind: "skill",
229
254
  };
230
255
  }
231
256
 
@@ -256,7 +281,7 @@ export function discoverMarketplaceSkills(options: DiscoverMarketplaceOptions =
256
281
  options.indexPath === null ? null : (options.indexPath ?? process.env.CLIO_CODER_SKILL_MARKETPLACE_INDEX ?? null);
257
282
  const resolvedIndexPath = indexPath ?? defaultIndexPath();
258
283
  if (options.indexPath !== null && resolvedIndexPath && existsSync(resolvedIndexPath)) {
259
- for (const skill of indexSkills(resolvedIndexPath, diagnostics)) {
284
+ for (const skill of parseMarketplaceIndex(resolvedIndexPath, diagnostics)) {
260
285
  if (seen.has(skill.name)) continue;
261
286
  seen.add(skill.name);
262
287
  skills.push(skill);
@@ -7,6 +7,8 @@
7
7
  * or spoof the UI that approves it.
8
8
  */
9
9
 
10
+ import { isSecretArgKey, redactSecretString } from "./redaction.js";
11
+
10
12
  const ESC_CHAR = String.fromCharCode(27);
11
13
  const BEL_CHAR = String.fromCharCode(7);
12
14
  // Built through the constructor so no control character appears in a regex
@@ -39,21 +41,216 @@ export function sanitizeCallTargetText(value: string): string {
39
41
  }
40
42
 
41
43
  /**
42
- * Derive the operator-facing object of a call for the approval overlay: the
43
- * command for bash, a path for file tools, else a compact args preview.
44
- * Returns an empty string when nothing meaningful is derivable, so callers
45
- * can omit the Target row instead of rendering a blank.
44
+ * What a tool call is doing, in the bounded form that may cross the worker
45
+ * stdout seam. The verb comes from a fixed vocabulary and the object from a
46
+ * fixed allowlist of argument fields, so an argument the table does not name
47
+ * cannot reach an operator surface however it is spelled.
48
+ */
49
+ export interface CallActionDescriptor {
50
+ /** One word from the vocabulary below naming what the call does. */
51
+ verb: string;
52
+ /** The redacted, bounded thing the call acts on. Absent when nothing safe is derivable. */
53
+ object?: string;
54
+ /** Whether the object was cut to {@link CALL_ACTION_OBJECT_MAX_CHARS}. */
55
+ truncated?: boolean;
56
+ }
57
+
58
+ /**
59
+ * Characters of object text a descriptor carries. Bounded here rather than at
60
+ * the renderer: this string crosses a process boundary, so a hostile argument
61
+ * must be small before it is transported, not after.
46
62
  */
47
- export function describeCallTarget(args: Record<string, unknown> | undefined): string {
63
+ export const CALL_ACTION_OBJECT_MAX_CHARS = 64;
64
+
65
+ /** Maximum characters carried by the approval overlay's one-line call target. */
66
+ export const CALL_TARGET_MAX_CHARS = 120;
67
+
68
+ /**
69
+ * The verb and object field for each tool Clio ships, plus the ACP tool kinds
70
+ * a delegated peer reports. A tool absent from this table gets the neutral
71
+ * `calling` verb, never a verb guessed from its name.
72
+ */
73
+ const CALL_ACTION_VOCABULARY: Readonly<Record<string, { verb: string; field: string }>> = {
74
+ read: { verb: "reading", field: "path" },
75
+ edit: { verb: "editing", field: "path" },
76
+ write: { verb: "writing", field: "path" },
77
+ ls: { verb: "listing", field: "path" },
78
+ bash: { verb: "running", field: "command" },
79
+ grep: { verb: "searching", field: "pattern" },
80
+ find: { verb: "finding", field: "pattern" },
81
+ web_fetch: { verb: "fetching", field: "url" },
82
+ git: { verb: "git", field: "op" },
83
+ verify: { verb: "verifying", field: "check" },
84
+ code_nav: { verb: "navigating", field: "query" },
85
+ context: { verb: "context", field: "scope" },
86
+ artifact: { verb: "writing", field: "kind" },
87
+ monitor: { verb: "monitoring", field: "run_id" },
88
+ steer: { verb: "steering", field: "run_id" },
89
+ tasks: { verb: "tasks", field: "action" },
90
+ dispatch: { verb: "dispatching", field: "agent" },
91
+ // ACP tool kinds. A peer names its own argument fields, so these rely on
92
+ // the shared allowlist below rather than on a field this side can predict.
93
+ execute: { verb: "running", field: "command" },
94
+ search: { verb: "searching", field: "query" },
95
+ fetch: { verb: "fetching", field: "url" },
96
+ delete: { verb: "deleting", field: "path" },
97
+ move: { verb: "moving", field: "path" },
98
+ think: { verb: "thinking", field: "" },
99
+ };
100
+
101
+ /**
102
+ * Argument fields any tool may surface as its object. A field outside this
103
+ * list is never read, so a tool that hides a credential in `body`, `headers`,
104
+ * or `env` cannot leak it through a descriptor.
105
+ */
106
+ const CALL_ACTION_OBJECT_FIELDS = ["path", "file_path", "command", "pattern", "query", "url"] as const;
107
+
108
+ /**
109
+ * Argument fields whose values may reach the approval overlay for each tool.
110
+ * Fields are ordered by decision value: the first present field is the plain
111
+ * target, and any later present fields are labeled facts. Everything outside
112
+ * the tool's list is described only by type and size.
113
+ */
114
+ const CALL_TARGET_FIELDS: Readonly<Record<string, ReadonlyArray<string>>> = {
115
+ read: ["path", "offset", "limit", "tail"],
116
+ grep: ["pattern", "path", "mode", "glob", "ignore_case", "literal", "context", "limit", "include_ignored"],
117
+ find: ["pattern", "path", "order", "limit", "include_ignored"],
118
+ ls: ["path", "limit"],
119
+ code_nav: ["mode", "query", "limit"],
120
+ context: ["scope", "query", "name", "limit", "ref", "include_tree"],
121
+ credential_present: ["name", "source", "file"],
122
+ write: ["path"],
123
+ edit: ["path"],
124
+ bash: ["command", "cwd", "timeout_ms", "output_policy"],
125
+ git: ["op", "path", "cached", "stat", "name_only", "limit", "cwd", "timeout_ms", "max_output_bytes"],
126
+ verify: ["check", "path", "browser", "cwd", "timeout_ms", "max_output_bytes"],
127
+ dispatch: [
128
+ "list",
129
+ "mode",
130
+ "agent",
131
+ "target",
132
+ "model",
133
+ "node",
134
+ "autonomy",
135
+ "tool_profile",
136
+ "thinking_level",
137
+ "detach",
138
+ "timeout_ms",
139
+ ],
140
+ monitor: ["run_id", "mode"],
141
+ steer: ["run_id", "action"],
142
+ tasks: ["action", "id"],
143
+ ledger: ["action", "kind", "path", "line", "target", "passed", "since"],
144
+ web_fetch: ["url", "method", "timeout_ms", "max_bytes", "format"],
145
+ ask_user: ["action", "mode", "max_rounds", "exposure"],
146
+ artifact: ["kind", "path", "title"],
147
+ // ACP tool kinds use only fields whose meaning the protocol defines.
148
+ execute: ["command", "cwd"],
149
+ search: ["query", "path"],
150
+ fetch: ["url", "method"],
151
+ delete: ["path"],
152
+ move: ["path", "target"],
153
+ };
154
+
155
+ /** Sanitize, scrub, and bound one candidate object string. Null when nothing is left. */
156
+ function boundActionObject(value: unknown): { object: string; truncated: boolean } | null {
157
+ if (typeof value !== "string") return null;
158
+ const clean = sanitizeCallTargetText(redactSecretString(value));
159
+ if (clean.length === 0) return null;
160
+ if (clean.length <= CALL_ACTION_OBJECT_MAX_CHARS) return { object: clean, truncated: false };
161
+ return { object: clean.slice(0, CALL_ACTION_OBJECT_MAX_CHARS), truncated: true };
162
+ }
163
+
164
+ /**
165
+ * Compose the redacted action descriptor for one tool call. Called at the
166
+ * trusted seams that hold the arguments (the tool registry's admission path,
167
+ * the Claude tool mapper, and the ACP update mapper) so the descriptor, and
168
+ * never the arguments, is what crosses into an operator surface.
169
+ *
170
+ * Returns null when the tool is unknown and no allowlisted field is present:
171
+ * a bare `calling` with nothing after it says less than the tool name already
172
+ * on the row.
173
+ */
174
+ export function describeCallAction(
175
+ tool: string,
176
+ args: Record<string, unknown> | undefined,
177
+ ): CallActionDescriptor | null {
178
+ const known = CALL_ACTION_VOCABULARY[tool];
179
+ const candidates = known?.field ? [known.field, ...CALL_ACTION_OBJECT_FIELDS] : [...CALL_ACTION_OBJECT_FIELDS];
180
+ let bounded: { object: string; truncated: boolean } | null = null;
181
+ if (args) {
182
+ for (const field of candidates) {
183
+ if (isSecretArgKey(field)) continue;
184
+ bounded = boundActionObject(args[field]);
185
+ if (bounded !== null) break;
186
+ }
187
+ }
188
+ if (known === undefined && bounded === null) return null;
189
+ const verb = known?.verb ?? "calling";
190
+ if (bounded === null) return { verb };
191
+ return { verb, object: bounded.object, ...(bounded.truncated ? { truncated: true } : {}) };
192
+ }
193
+
194
+ function renderAllowedTargetValue(value: unknown): string | null {
195
+ if (typeof value === "string") {
196
+ const rendered = sanitizeCallTargetText(redactSecretString(value));
197
+ return rendered.length > 0 ? rendered : null;
198
+ }
199
+ if (typeof value === "number" && Number.isFinite(value)) return String(value);
200
+ if (typeof value === "boolean") return String(value);
201
+ return null;
202
+ }
203
+
204
+ function countLabel(count: number, singular: string, plural: string): string {
205
+ return `${count} ${count === 1 ? singular : plural}`;
206
+ }
207
+
208
+ /** Describe an argument without copying any part of its value into display text. */
209
+ function summarizeUnlistedTargetValue(value: unknown): string {
210
+ if (typeof value === "string") {
211
+ return `<string ${countLabel(Buffer.byteLength(value, "utf8"), "byte", "bytes")}>`;
212
+ }
213
+ if (Array.isArray(value)) return `<array ${countLabel(value.length, "item", "items")}>`;
214
+ if (value !== null && typeof value === "object") {
215
+ return `<object ${countLabel(Object.keys(value).length, "field", "fields")}>`;
216
+ }
217
+ if (value === null) return "<null 0 values>";
218
+ if (typeof value === "undefined") return "<undefined 0 values>";
219
+ return `<${typeof value} 1 value>`;
220
+ }
221
+
222
+ function targetFieldName(value: string): string {
223
+ return sanitizeCallTargetText(value).slice(0, 32) || "field";
224
+ }
225
+
226
+ /**
227
+ * Derive the operator-facing object of a call for the approval overlay. Only
228
+ * values in the named tool's allowlist may render. Every other argument is
229
+ * summarized by its field name, type, and size, so an unexpected credential
230
+ * or pasted document still informs the decision without disclosing content.
231
+ * Returns an empty string when the call carries no arguments.
232
+ */
233
+ export function describeCallTarget(tool: string, args: Record<string, unknown> | undefined): string {
48
234
  if (!args) return "";
49
- const str = (value: unknown): string | null =>
50
- typeof value === "string" && value.trim().length > 0 ? oneLine(sanitizeForDisplay(value)).trim() || null : null;
51
- const candidate = str(args.command) ?? str(args.path) ?? str(args.file_path) ?? str(args.name) ?? str(args.pattern);
52
- if (candidate) return candidate;
53
- try {
54
- const json = JSON.stringify(args);
55
- return json === "{}" || json === undefined ? "" : oneLine(sanitizeForDisplay(json)).slice(0, 120);
56
- } catch {
57
- return "";
235
+ const allowedFields = CALL_TARGET_FIELDS[tool] ?? [];
236
+ const allowed = new Set(allowedFields);
237
+ const parts: string[] = [];
238
+ for (const field of allowedFields) {
239
+ if (!(field in args)) continue;
240
+ if (isSecretArgKey(field)) {
241
+ parts.push(`${targetFieldName(field)}=${summarizeUnlistedTargetValue(args[field])}`);
242
+ continue;
243
+ }
244
+ const rendered = renderAllowedTargetValue(args[field]);
245
+ if (rendered === null) {
246
+ parts.push(`${targetFieldName(field)}=${summarizeUnlistedTargetValue(args[field])}`);
247
+ continue;
248
+ }
249
+ parts.push(parts.length === 0 ? rendered : `${targetFieldName(field)}=${rendered}`);
250
+ }
251
+ for (const [field, value] of Object.entries(args)) {
252
+ if (allowed.has(field)) continue;
253
+ parts.push(`${targetFieldName(field)}=${summarizeUnlistedTargetValue(value)}`);
58
254
  }
255
+ return sanitizeCallTargetText(parts.join(" · ")).slice(0, CALL_TARGET_MAX_CHARS);
59
256
  }