@iowarp/clio-coder 0.4.3 → 0.4.4

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 (340) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +1 -1
  3. package/dist/{acp-H2NGRPWO.js → acp-WNAYYF4F.js} +4 -5
  4. package/dist/{agents-TL5LLUQP.js → agents-3OKXHLOI.js} +33 -31
  5. package/dist/assets/codewiki.json +1 -1
  6. package/dist/{auth-E5SW4HMS.js → auth-VKNNMGPU.js} +10 -11
  7. package/dist/{builtins-IA7V7FUC.js → builtins-WGALA46I.js} +4 -4
  8. package/dist/{chunk-F5JHEYZM.js → chunk-23L32XTI.js} +10 -7
  9. package/dist/{chunk-DWUOQKRU.js → chunk-25QBEXRS.js} +2 -2
  10. package/dist/{chunk-LLDJM5XK.js → chunk-26QSH3EJ.js} +2 -2
  11. package/dist/{chunk-N2Z7HLVY.js → chunk-2ASED4PZ.js} +10 -10
  12. package/dist/{chunk-MCEPRMZW.js → chunk-2CU2H6KE.js} +2 -2
  13. package/dist/chunk-2DSOYNFC.js +108 -0
  14. package/dist/{chunk-LDJG7DW3.js → chunk-2ZSONWVL.js} +4 -4
  15. package/dist/{chunk-GI7YYQ3F.js → chunk-36CT5VVL.js} +324 -41
  16. package/dist/{chunk-4UVU7BJ5.js → chunk-3GY4F45V.js} +2 -2
  17. package/dist/{chunk-PUVDKJ2Y.js → chunk-3ODX73FK.js} +3 -5
  18. package/dist/{chunk-IUE3Y34X.js → chunk-3UNOLWNZ.js} +2 -2
  19. package/dist/{chunk-6PTFB5VS.js → chunk-3UUXNFEX.js} +8 -4
  20. package/dist/{chunk-2APPQIER.js → chunk-4M6Z5QVF.js} +4 -4
  21. package/dist/{chunk-K6BSR66V.js → chunk-4NSRCOYP.js} +4 -1
  22. package/dist/{chunk-JGRC33J2.js → chunk-54X7T7DK.js} +12 -3
  23. package/dist/{chunk-YQWYVTMC.js → chunk-5636DCO5.js} +4 -4
  24. package/dist/{chunk-7E7I3WLS.js → chunk-57XXR6DR.js} +23 -22
  25. package/dist/{chunk-7ZYNNDKC.js → chunk-5TUB6SLS.js} +2 -2
  26. package/dist/{chunk-VW6DOEDG.js → chunk-6OSVSQL5.js} +11 -2
  27. package/dist/{chunk-L47TF46W.js → chunk-6PAZTBPA.js} +14 -2
  28. package/dist/{chunk-XIVNBFZS.js → chunk-6Q3CYFD3.js} +34 -16
  29. package/dist/{chunk-RSJ25QSL.js → chunk-6QOTUPRG.js} +155 -36
  30. package/dist/{chunk-JKKCYP3C.js → chunk-6UINWWS6.js} +10 -4
  31. package/dist/{chunk-WBKFA554.js → chunk-72YIHOZQ.js} +2 -2
  32. package/dist/{chunk-PAJQJ7BS.js → chunk-75W7L2E2.js} +246 -697
  33. package/dist/{chunk-RRNP2ANY.js → chunk-7UGL4MB5.js} +4 -4
  34. package/dist/{chunk-EKCHAPYA.js → chunk-AUPNRN7C.js} +2 -2
  35. package/dist/{chunk-ZDN3Y73Y.js → chunk-BJVFZO5U.js} +6 -48
  36. package/dist/{chunk-IWT4SF4R.js → chunk-CUSRQKPU.js} +64 -2
  37. package/dist/{chunk-SKHCAU7K.js → chunk-DQOVN6KV.js} +3 -2
  38. package/dist/{chunk-ZWPRK62N.js → chunk-DT3LWJOB.js} +4 -4
  39. package/dist/chunk-DXKJURES.js +671 -0
  40. package/dist/{chunk-BEPZRGGU.js → chunk-EL24TAU4.js} +2 -2
  41. package/dist/{chunk-OML5D5V5.js → chunk-ELWDPP3Y.js} +5 -5
  42. package/dist/{chunk-NDINPTJ4.js → chunk-ELZVTCGV.js} +5 -4
  43. package/dist/chunk-EXLD33WO.js +381 -0
  44. package/dist/{chunk-IJNZMHLA.js → chunk-FEAXX7B6.js} +2 -2
  45. package/dist/{chunk-TM6LQDI3.js → chunk-FFUPXJC4.js} +74 -323
  46. package/dist/chunk-GX5WYQO4.js +59 -0
  47. package/dist/chunk-I2DWJ4GM.js +390 -0
  48. package/dist/{chunk-TXOTCRLG.js → chunk-I5FWO7L5.js} +5 -5
  49. package/dist/chunk-IRXAATOX.js +539 -0
  50. package/dist/chunk-IXIY2H4R.js +44 -0
  51. package/dist/{chunk-KK4JZPBQ.js → chunk-IZXGRF7P.js} +76 -10
  52. package/dist/{chunk-5TSRNF4G.js → chunk-JCI2ROMZ.js} +164 -6
  53. package/dist/{chunk-INY6HTFL.js → chunk-JEIYHLOR.js} +2 -2
  54. package/dist/{chunk-MUW2BDDH.js → chunk-JQLNNIKT.js} +2 -2
  55. package/dist/{chunk-XPWWI35G.js → chunk-JSD46VO2.js} +19 -8
  56. package/dist/{chunk-VA5FNYMT.js → chunk-JT2RFCC5.js} +39 -2
  57. package/dist/{chunk-V2ANDPVT.js → chunk-K6T2ZAMZ.js} +168 -6
  58. package/dist/{chunk-B4OAX3SI.js → chunk-KFV5L5SK.js} +13 -6
  59. package/dist/{chunk-JEQ3XTHC.js → chunk-LLXSDWXS.js} +2 -2
  60. package/dist/{chunk-E3TPLWFX.js → chunk-LTIKRKFL.js} +3 -3
  61. package/dist/{chunk-NIQJ66N4.js → chunk-N56KALIC.js} +17 -17
  62. package/dist/{chunk-JDAY6FIL.js → chunk-NAI6ZFCY.js} +13 -9
  63. package/dist/{chunk-AF4YM7Z4.js → chunk-NRO2BJRH.js} +2490 -2182
  64. package/dist/{chunk-WRBAGUNF.js → chunk-NXIMQY5W.js} +2 -2
  65. package/dist/{chunk-I7ZPNEJM.js → chunk-NXYCB2VD.js} +5 -5
  66. package/dist/chunk-ODGTEFFI.js +50 -0
  67. package/dist/{chunk-2VIKGWFZ.js → chunk-OEJSLEPW.js} +2 -2
  68. package/dist/{chunk-KKOJXO6R.js → chunk-OMQNJVKW.js} +4 -2
  69. package/dist/{chunk-54CBCGIR.js → chunk-Q4WO54TA.js} +132 -77
  70. package/dist/{chunk-B4VEBZKF.js → chunk-QUFRYSWI.js} +12 -6
  71. package/dist/{chunk-42FMPA75.js → chunk-QZWQA4DE.js} +2 -2
  72. package/dist/{chunk-HEQY7ZFI.js → chunk-RAY4OVGZ.js} +2 -2
  73. package/dist/{chunk-G76U63X4.js → chunk-RQCKCSRL.js} +7 -7
  74. package/dist/{chunk-64I3JVYM.js → chunk-RXTN6AKH.js} +2 -2
  75. package/dist/{chunk-UPZU6GE4.js → chunk-RZDWV63N.js} +3 -3
  76. package/dist/{chunk-2UH2KFUP.js → chunk-S6PYF2XF.js} +2 -2
  77. package/dist/{chunk-JSC3U7TI.js → chunk-TOIVGRUX.js} +2 -2
  78. package/dist/{chunk-7DRAWPTZ.js → chunk-TQAHXW6Y.js} +1 -1
  79. package/dist/{chunk-JIEGK6UF.js → chunk-U6TMQNSI.js} +48 -4
  80. package/dist/{chunk-AX2THNSA.js → chunk-UEPWCCTY.js} +4 -4
  81. package/dist/{chunk-QWGDJJYJ.js → chunk-USR47QNF.js} +4 -4
  82. package/dist/{chunk-Y3CBHOR6.js → chunk-V6HJFQZE.js} +2 -2
  83. package/dist/chunk-V76WTFTW.js +318 -0
  84. package/dist/{chunk-NMJXSHBJ.js → chunk-W54I7H25.js} +2 -2
  85. package/dist/{chunk-2UG5F4C5.js → chunk-XULDXHTN.js} +24 -12
  86. package/dist/chunk-XXYSBZIQ.js +283 -0
  87. package/dist/{chunk-MWUZBSAQ.js → chunk-Y55JBDO5.js} +331 -51
  88. package/dist/{chunk-W6RRQCPQ.js → chunk-YD5GIKET.js} +4 -4
  89. package/dist/{chunk-CE5AX47J.js → chunk-YECAMM3D.js} +2 -2
  90. package/dist/{chunk-WCXUNS7U.js → chunk-YNFKXPEC.js} +6 -6
  91. package/dist/cli/index.js +36 -35
  92. package/dist/{clio-CMMK4KRR.js → clio-QLICPCF5.js} +2 -2
  93. package/dist/{code-nav-MDZNQS33.js → code-nav-IJR2DBPR.js} +6 -6
  94. package/dist/{components-UCUQ4QXW.js → components-2TGAI2RC.js} +3 -4
  95. package/dist/{config-SVM5P5YI.js → config-IUA6OYNS.js} +52 -47
  96. package/dist/{configure-LE3IK2TJ.js → configure-VEPX4NMX.js} +14 -15
  97. package/dist/{context-VNCR7KAG.js → context-2DKHWH2T.js} +23 -23
  98. package/dist/{context-2OHRKS42.js → context-4MPR7WKB.js} +48 -42
  99. package/dist/{context-E3VC7RX5.js → context-BOYF5EJM.js} +11 -11
  100. package/dist/{context-clear-BW4O37TG.js → context-clear-S4ZJCQUX.js} +48 -42
  101. package/dist/{context-working-set-VDS25HXZ.js → context-working-set-3I3FYX6Y.js} +8 -8
  102. package/dist/detail-A7JAVSIG.js +98 -0
  103. package/dist/{dispatch-runner-5AHT53RF.js → dispatch-runner-RJ5I2F2O.js} +71 -58
  104. package/dist/{docs-PD3EXDKU.js → docs-SPOV3BAN.js} +3 -5
  105. package/dist/{doctor-WNNVO6FY.js → doctor-DKICC2SN.js} +54 -31
  106. package/dist/{eval-7G7SGAYO.js → eval-OQOQUDHK.js} +47 -56
  107. package/dist/{evidence-VD6736FQ.js → evidence-4DQ25GUQ.js} +53 -150
  108. package/dist/evidence-4F5USFKH.js +208 -0
  109. package/dist/{evolve-AL3NGVRL.js → evolve-GSS52E5J.js} +44 -41
  110. package/dist/{extensions-MOVJ32NM.js → extensions-G7MFLYHT.js} +4 -5
  111. package/dist/{fleet-QZHUMAGI.js → fleet-Q37YHHAQ.js} +74 -68
  112. package/dist/{fleet-commands-BAYT5FJZ.js → fleet-commands-G7E4N7SM.js} +14 -11
  113. package/dist/{fleet-decisions-IREVMRU4.js → fleet-decisions-O7M6QBA2.js} +6 -6
  114. package/dist/{fleet-graph-YCTT3HTI.js → fleet-graph-TOUBW6OW.js} +13 -13
  115. package/dist/{fleet-inspect-QVJTDAVB.js → fleet-inspect-SW33JJNI.js} +43 -39
  116. package/dist/{fleet-preflight-25QAFPK4.js → fleet-preflight-CV2655TW.js} +2 -3
  117. package/dist/{fleet-validate-5O57AAJ7.js → fleet-validate-KESZX2YH.js} +16 -16
  118. package/dist/{fleet-verify-CPH2W2T6.js → fleet-verify-UQPMTVE3.js} +42 -38
  119. package/dist/{fleet-view-SWBR3VGQ.js → fleet-view-MN2VG4MR.js} +43 -39
  120. package/dist/{init-J477LKZH.js → init-PXEXQSBF.js} +60 -54
  121. package/dist/{interop-3FCM6XLG.js → interop-ZG5T62U3.js} +6 -7
  122. package/dist/inventory-C26CFDRR.js +101 -0
  123. package/dist/{library-QUQEIUG6.js → library-B2W4N74O.js} +16 -17
  124. package/dist/{memory-SGGSEP65.js → memory-YCANYS5A.js} +45 -42
  125. package/dist/{models-HEKUAXXK.js → models-GERTU3YI.js} +20 -22
  126. package/dist/{monitor-HKU57TYQ.js → monitor-CPNIUULB.js} +50 -44
  127. package/dist/{orchestrator-VDFAEFAI.js → orchestrator-J4BSH4WQ.js} +491 -1182
  128. package/dist/{panes-DN2SSFOH.js → panes-BOHAEGYC.js} +3 -3
  129. package/dist/{panes-TALGNPZT.js → panes-NXSLDQZ2.js} +5 -6
  130. package/dist/{paths-NBMFAIEZ.js → paths-VSUWNC22.js} +3 -4
  131. package/dist/{reset-EAJFFJVB.js → reset-TNWTB5LU.js} +5 -6
  132. package/dist/{resources-OVKSEFVE.js → resources-4PXNMD5G.js} +14 -14
  133. package/dist/{run-7DP7ZF2J.js → run-D6XJ34CN.js} +79 -83
  134. package/dist/{share-WML67FT3.js → share-2NWMJJEE.js} +14 -15
  135. package/dist/{skills-SG662R2K.js → skills-KR7WON5G.js} +18 -19
  136. package/dist/{skills-eval-VVZEUU46.js → skills-eval-O2ZNOLDS.js} +49 -46
  137. package/dist/{skills-inventory-I2E23GET.js → skills-inventory-ZZOUBK7O.js} +14 -14
  138. package/dist/{slash-commands-S7MBJDQK.js → slash-commands-ZXPJD64J.js} +32 -23
  139. package/dist/{steer-2LQOMCPB.js → steer-XA25PSCS.js} +3 -3
  140. package/dist/{support-CC2UJBJ6.js → support-7EMVWYG2.js} +2 -2
  141. package/dist/{targets-4QC3HIEW.js → targets-OMH2XCSN.js} +25 -27
  142. package/dist/tasks-IPAGMEIX.js +36 -0
  143. package/dist/{terminal-lease-TUHIJ6Y2.js → terminal-lease-C2J3JYRE.js} +4 -4
  144. package/dist/{tools-TFGJICCU.js → tools-EFFEAIDP.js} +5 -6
  145. package/dist/{uninstall-5PEVOE5B.js → uninstall-HALS6BLF.js} +7 -8
  146. package/dist/{upgrade-M4WXY6KN.js → upgrade-MS72RJEP.js} +14 -11
  147. package/dist/{usage-N7ZNVLEM.js → usage-NHG6MCJM.js} +60 -51
  148. package/dist/{verifiers-DJTP4XX6.js → verifiers-7AUNVXDY.js} +150 -17
  149. package/dist/{verify-RWE4PPEK.js → verify-FWYGPKMR.js} +12 -10
  150. package/dist/{web-fetch-MPARV2K7.js → web-fetch-V4FKSDAV.js} +4 -4
  151. package/dist/{wiki-generate-C7IQOXSP.js → wiki-generate-743CIGJW.js} +63 -56
  152. package/dist/{with-panes-4GCGSL7J.js → with-panes-BDQEWBRT.js} +2 -2
  153. package/dist/worker/entry.js +36 -33
  154. package/docs/README.md +3 -2
  155. package/docs/architecture/acp.md +17 -0
  156. package/docs/architecture/artifact-versions.md +1 -1
  157. package/docs/architecture/dispatch-typed-intent.md +1 -1
  158. package/docs/architecture/evidence-and-memory.md +1 -1
  159. package/docs/architecture/observability.md +7 -1
  160. package/docs/architecture/prompt-envelope-and-tools.md +15 -5
  161. package/docs/architecture/safety-model.md +10 -17
  162. package/docs/architecture/tui-design.md +1 -1
  163. package/docs/guide/built-in-agents.md +8 -8
  164. package/docs/guide/commands-and-modes.md +17 -2
  165. package/docs/guide/configuration-and-targets.md +3 -1
  166. package/docs/guide/configuration-reference.md +10 -5
  167. package/docs/guide/environment-variables.md +2 -2
  168. package/docs/guide/tool-usage.md +78 -3
  169. package/docs/history/config-knobs-audit.md +2 -2
  170. package/docs/process/development-pipeline.md +6 -1
  171. package/docs/process/git-commit-provenance.md +15 -0
  172. package/docs/process/release-cut-checklist.md +207 -0
  173. package/docs/process/scientific-validation.md +18 -17
  174. package/evals/behavioral-machinery-support.ts +1 -0
  175. package/evals/behavioral-machinery.yaml +1 -1
  176. package/package.json +1 -1
  177. package/skills/coding/coding-standards/SKILL.md +14 -4
  178. package/skills/context/context-handoff/SKILL.md +3 -3
  179. package/skills/context/context-prime/SKILL.md +3 -3
  180. package/skills/planning/architecture/SKILL.md +5 -5
  181. package/skills/planning/backlog/SKILL.md +4 -4
  182. package/skills/planning/prd/SKILL.md +4 -3
  183. package/skills/planning/product-intent/SKILL.md +8 -8
  184. package/skills/planning/tech-spec/SKILL.md +4 -4
  185. package/skills/registry.yaml +32 -32
  186. package/skills/research/arxiv-literature/SKILL.md +3 -3
  187. package/skills/research/experiment-protocol/SKILL.md +3 -3
  188. package/skills/research/scientific-debugging/SKILL.md +3 -2
  189. package/skills/research/scientific-modernization/SKILL.md +3 -3
  190. package/skills/skill-marketplace.json +16 -16
  191. package/skills/workflow/cut-it/SKILL.md +3 -4
  192. package/skills/workflow/design-council/SKILL.md +5 -10
  193. package/skills/workflow/grill-me/SKILL.md +14 -15
  194. package/skills/workflow/workflow-distiller/SKILL.md +4 -4
  195. package/src/cli/args.ts +0 -8
  196. package/src/cli/configure.ts +2 -1
  197. package/src/cli/doctor-state-size.ts +1 -12
  198. package/src/cli/doctor-validation-contract.ts +28 -0
  199. package/src/cli/doctor.ts +5 -0
  200. package/src/cli/evidence-detail.ts +1 -75
  201. package/src/cli/evidence-inventory.ts +1 -167
  202. package/src/cli/index.ts +2 -0
  203. package/src/cli/run.ts +0 -2
  204. package/src/cli/targets.ts +1 -1
  205. package/src/cli/tasks.ts +84 -0
  206. package/src/cli/upgrade.ts +10 -5
  207. package/src/cli/usage.ts +6 -0
  208. package/src/cli/verifiers.ts +147 -1
  209. package/src/cli/wiki-generate.ts +1 -0
  210. package/src/core/commit-attribution.ts +41 -1
  211. package/src/core/git-commit-attribution.ts +46 -3
  212. package/src/core/run-overrides.ts +0 -5
  213. package/src/core/skill-activation.ts +3 -0
  214. package/src/core/tool-names.ts +5 -2
  215. package/src/domains/agents/builtins/architect.md +1 -1
  216. package/src/domains/agents/builtins/coder.md +1 -1
  217. package/src/domains/agents/builtins/documenter.md +1 -1
  218. package/src/domains/agents/builtins/git-master.md +1 -1
  219. package/src/domains/agents/builtins/provenance.md +7 -7
  220. package/src/domains/agents/builtins/tester.md +1 -1
  221. package/src/domains/agents/builtins/verifier.md +2 -2
  222. package/src/domains/agents/builtins/wiki-writer.md +4 -3
  223. package/src/domains/context/extension.ts +31 -7
  224. package/src/domains/context/refresh.ts +3 -0
  225. package/src/domains/context/wiki/frontmatter.ts +5 -2
  226. package/src/domains/context/wiki/generate.ts +6 -0
  227. package/src/domains/context/wiki/prompts.ts +43 -0
  228. package/src/domains/dispatch/active-route-planner.ts +4 -0
  229. package/src/domains/dispatch/code-step.ts +11 -4
  230. package/src/domains/dispatch/contract.ts +31 -7
  231. package/src/domains/dispatch/execution-scheduler.ts +2 -0
  232. package/src/domains/dispatch/extension.ts +50 -27
  233. package/src/domains/dispatch/fleet-commit-attribution.ts +5 -0
  234. package/src/domains/dispatch/fleet-run.ts +1 -0
  235. package/src/domains/dispatch/host-verification.ts +114 -13
  236. package/src/domains/dispatch/intent.ts +28 -18
  237. package/src/domains/dispatch/orphan-recovery.ts +2 -0
  238. package/src/domains/dispatch/receipt-integrity.ts +4 -0
  239. package/src/domains/dispatch/reservation-store.ts +5 -3
  240. package/src/domains/dispatch/state.ts +15 -2
  241. package/src/domains/dispatch/types.ts +17 -2
  242. package/src/domains/eval/runners/clio-run.ts +12 -9
  243. package/src/domains/eval/runners/context-index.ts +2 -7
  244. package/src/domains/eval/runners/context-init.ts +3 -6
  245. package/src/domains/eval/runners/external-command.ts +29 -11
  246. package/src/domains/evidence/build.ts +102 -15
  247. package/src/domains/evidence/detail.ts +69 -0
  248. package/src/domains/evidence/eval.ts +13 -1
  249. package/src/domains/evidence/finish-contract-map.ts +5 -1
  250. package/src/domains/evidence/inventory.ts +167 -0
  251. package/src/domains/evidence/store.ts +16 -0
  252. package/src/domains/evidence/types.ts +12 -0
  253. package/src/domains/extensions/resources.ts +7 -0
  254. package/src/domains/middleware/marketplace-offer.ts +20 -1
  255. package/src/domains/middleware/runtime.ts +7 -3
  256. package/src/domains/mux/detect.ts +3 -6
  257. package/src/domains/observability/accountability.ts +15 -1
  258. package/src/domains/observability/contract.ts +52 -7
  259. package/src/domains/observability/evidence-index.ts +10 -0
  260. package/src/domains/observability/extension.ts +9 -5
  261. package/src/domains/observability/projection.ts +394 -45
  262. package/src/{interactive → domains/observability}/worker-progress.ts +3 -3
  263. package/src/domains/prompts/fragments/operating/contract.md +2 -0
  264. package/src/domains/prompts/fragments/wiki/page.md +8 -0
  265. package/src/domains/providers/model-discovery.ts +1 -4
  266. package/src/domains/providers/models/local-models/clio-coder-local-coding-targets.yaml +16 -14
  267. package/src/domains/providers/runtimes/claude/claude-code.ts +9 -0
  268. package/src/domains/providers/support.ts +1 -1
  269. package/src/domains/providers/types/runtime-descriptor.ts +1 -1
  270. package/src/domains/safety/action-classifier.ts +7 -0
  271. package/src/domains/safety/default-path-policy.ts +2 -0
  272. package/src/domains/safety/finish-contract-registration.ts +29 -14
  273. package/src/domains/safety/finish-contract.ts +252 -40
  274. package/src/domains/safety/index.ts +20 -1
  275. package/src/domains/safety/policy-engine.ts +26 -6
  276. package/src/domains/safety/rigor.ts +53 -39
  277. package/src/domains/safety/validation-contract.ts +388 -0
  278. package/src/domains/session/archive-readers.ts +10 -1
  279. package/src/domains/session/decision-board.ts +101 -2
  280. package/src/domains/session/entries.ts +44 -7
  281. package/src/domains/session/extension.ts +4 -4
  282. package/src/domains/session/handoff.ts +2 -1
  283. package/src/domains/session/manager.ts +2 -3
  284. package/src/domains/session/task-board.ts +14 -1
  285. package/src/domains/session/tree/fork.ts +1 -2
  286. package/src/domains/session/tree/navigator.ts +1 -1
  287. package/src/domains/user-tasks/acceptance.ts +56 -0
  288. package/src/domains/user-tasks/active-acceptance.ts +40 -0
  289. package/src/domains/user-tasks/store.ts +34 -3
  290. package/src/engine/acp/adapter.ts +24 -6
  291. package/src/engine/acp/server.ts +21 -4
  292. package/src/engine/acp/transport.ts +53 -8
  293. package/src/engine/acp/types.ts +4 -0
  294. package/src/engine/apis/ollama-native.ts +15 -0
  295. package/src/engine/claude/subprocess-runtime.ts +107 -60
  296. package/src/engine/external-subprocess.ts +12 -4
  297. package/src/entry/orchestrator.ts +11 -2
  298. package/src/interactive/dispatch-board.ts +46 -717
  299. package/src/interactive/fleet-run-preview.ts +2 -1
  300. package/src/interactive/interactive-application.ts +3 -2
  301. package/src/interactive/interactive-presentation.ts +55 -12
  302. package/src/interactive/interactive-slash-runtime.ts +2 -1
  303. package/src/interactive/oracle.ts +5 -2
  304. package/src/interactive/overlays/fleet-run-approval.ts +3 -2
  305. package/src/interactive/overlays/message-picker.ts +2 -2
  306. package/src/interactive/overlays/settings.ts +2 -2
  307. package/src/interactive/overlays/tree-selector.ts +2 -2
  308. package/src/interactive/renderers/branch-summary.ts +1 -1
  309. package/src/interactive/slash-autocomplete.ts +4 -6
  310. package/src/interactive/slash-commands.ts +25 -39
  311. package/src/interactive/slash-spec.ts +28 -0
  312. package/src/interactive/view/artifacts.ts +2 -0
  313. package/src/interactive/worker-stream.ts +7 -3
  314. package/src/tools/bootstrap.ts +4 -0
  315. package/src/tools/builtin-tool-catalog.ts +31 -0
  316. package/src/tools/compete-worktrees.ts +7 -1
  317. package/src/tools/core-bootstrap.ts +16 -0
  318. package/src/tools/decide.ts +136 -0
  319. package/src/tools/dispatch-admission.ts +21 -0
  320. package/src/tools/dispatch-plan.ts +5 -2
  321. package/src/tools/dispatch-runner.ts +15 -1
  322. package/src/tools/dispatch-types.ts +6 -0
  323. package/src/tools/evidence.ts +96 -0
  324. package/src/tools/limitation.ts +76 -0
  325. package/src/tools/policy.ts +9 -0
  326. package/src/tools/presentation.ts +3 -0
  327. package/src/tools/registry.ts +1 -1
  328. package/src/tools/result-shaping.ts +17 -5
  329. package/src/tools/task-worktree.ts +13 -3
  330. package/src/tools/tasks.ts +10 -1
  331. package/src/tools/verify/authoring.ts +170 -83
  332. package/src/tools/verify/catalog.ts +122 -5
  333. package/src/tools/verify/index.ts +2 -1
  334. package/src/tools/verify/numeric.ts +298 -0
  335. package/src/tools/verify/perf.ts +143 -0
  336. package/src/tools/verify/scripts.ts +229 -2
  337. package/dist/chunk-7DICMOS6.js +0 -313
  338. package/dist/chunk-RVG5JXAL.js +0 -41
  339. package/dist/chunk-T56WDKA5.js +0 -183
  340. package/dist/chunk-VPKWYKEY.js +0 -169
@@ -22,6 +22,7 @@
22
22
  */
23
23
 
24
24
  import { isAbsolute, normalize, relative, resolve } from "node:path";
25
+ import { decisionRationale } from "./decision-board.js";
25
26
  import type { DecisionLedgerEntry, SessionEntry } from "./entries.js";
26
27
  import { filterEntriesToActivePath } from "./tree/active-path.js";
27
28
 
@@ -480,7 +481,7 @@ export function mergeHandoffDecisions(
480
481
  if (decision.status !== "active") continue;
481
482
  const name = decision.label ?? decision.key;
482
483
  const summary = boundHandoffString(`${name}: ${decision.value}`).text;
483
- const rationale = decision.source_question ?? decision.correction;
484
+ const rationale = decisionRationale(decision);
484
485
  settled.push({
485
486
  summary,
486
487
  ...(rationale ? { rationale: boundHandoffString(rationale).text } : {}),
@@ -111,9 +111,8 @@ export function appendTurn(state: SessionManagerState, input: TurnInput): ClioTu
111
111
 
112
112
  /**
113
113
  * Append a rich SessionEntry via the engine writer. Non-message kinds are
114
- * written as JSON lines to current.jsonl; they do not project into tree.json
115
- * in slice 12a. Slice 12b extends the tree model so /fork can pick non-message
116
- * branch points too.
114
+ * written as JSON lines to current.jsonl and do not project into tree.json;
115
+ * the /fork picker offers assistant message turns only.
117
116
  */
118
117
  export function appendEntry(state: SessionManagerState, input: SessionEntryInput): SessionEntry {
119
118
  const turnId = input.turnId ?? newTurnId();
@@ -29,6 +29,7 @@ export interface TaskBoardTask {
29
29
  status: TaskLedgerStatus;
30
30
  origin?: "agent" | "user";
31
31
  userTaskId?: string;
32
+ requiredValidationEvidence?: TaskLedgerValidationEvidence[];
32
33
  /** Block or drop reason; empty for pending/active/completed tasks. */
33
34
  reason?: string;
34
35
  /** Evidence note recorded when the task was completed. */
@@ -64,7 +65,7 @@ export interface TaskBoardCounts {
64
65
  export type TaskBoardMutation =
65
66
  | { op: "plan"; title: string; tasks: ReadonlyArray<string> }
66
67
  | { op: "add"; tasks: ReadonlyArray<string> }
67
- | { op: "pick"; title: string; userTaskId: string }
68
+ | { op: "pick"; title: string; userTaskId: string; verification?: ReadonlyArray<{ check: string; timeoutMs: number }> }
68
69
  | { op: "start"; id: string }
69
70
  | { op: "done"; id: string; evidence: string }
70
71
  | { op: "block"; id: string; reason: string }
@@ -179,6 +180,7 @@ function boardStatus(tasks: ReadonlyArray<TaskBoardTask>): TaskLedgerStatus {
179
180
  export function toTaskLedgerEntryFields(board: TaskBoardSnapshot, now: Date): TaskLedgerEntryFields {
180
181
  const evidence: TaskLedgerValidationEvidence[] = [];
181
182
  const subgoals: TaskLedgerGoal[] = board.tasks.map((task) => {
183
+ evidence.push(...(task.requiredValidationEvidence ?? []).map((item) => ({ ...item })));
182
184
  if (task.status === "completed" && task.evidence) {
183
185
  evidence.push({
184
186
  id: `${task.id}.evidence`,
@@ -308,6 +310,8 @@ function toEntryView(entry: {
308
310
  origin: goal.origin ?? "agent",
309
311
  };
310
312
  if (goal.userTaskId) task.userTaskId = goal.userTaskId;
313
+ const required = entry.requiredValidationEvidence.filter((item) => item.id.startsWith(`${goal.id}.acceptance.`));
314
+ if (required.length > 0) task.requiredValidationEvidence = required.map((item) => ({ ...item }));
311
315
  if (goal.description) task.reason = goal.description;
312
316
  const evidence = evidenceByTask.get(goal.id);
313
317
  if (evidence) task.evidence = evidence;
@@ -390,6 +394,15 @@ function applyMutation(
390
394
  origin: "user",
391
395
  userTaskId: mutation.userTaskId,
392
396
  };
397
+ if (mutation.verification && mutation.verification.length > 0) {
398
+ picked.requiredValidationEvidence = mutation.verification.map((item, index) => ({
399
+ id: `${picked.id}.acceptance.${index}`,
400
+ description: `Operator acceptance: ${item.check}`,
401
+ command: item.check,
402
+ status: "pending",
403
+ notes: `timeoutMs=${item.timeoutMs}`,
404
+ }));
405
+ }
393
406
  return {
394
407
  board: {
395
408
  boardId: board?.boardId ?? "",
@@ -14,8 +14,7 @@ import { entryBelongsToPath } from "./active-path.js";
14
14
  * install it as current.
15
15
  *
16
16
  * Used by SessionContract.fork (current session) and the /fork message
17
- * picker path that lands in slice 12b-3. Kept out of extension.ts so the
18
- * bundle wiring stays thin.
17
+ * picker overlay. Kept out of extension.ts so the bundle wiring stays thin.
19
18
  */
20
19
  export interface ForkInput {
21
20
  /** The state we are forking from. Must carry a live writer. */
@@ -6,7 +6,7 @@ import type { ResolvedLabel } from "./manager.js";
6
6
  * Purely functional tree-walk helpers. Given the raw SessionTreeNode[] that
7
7
  * the engine persists plus a label map and a leaf pointer, produce a
8
8
  * render-friendly snapshot. No I/O. Used by SessionContract.tree() to
9
- * deliver a serializable structure to overlay renderers in slice 12b-2.
9
+ * deliver a serializable structure to the /tree overlay renderer.
10
10
  */
11
11
 
12
12
  /**
@@ -0,0 +1,56 @@
1
+ import {
2
+ DISPATCH_INTENT_TIMEOUT_MIN_MS,
3
+ type DispatchIntentCheckBound,
4
+ type DispatchIntentVerification,
5
+ normalizeIntentExpectedOutputs,
6
+ normalizeIntentVerification,
7
+ } from "../dispatch/intent.js";
8
+
9
+ export interface UserTaskAcceptance {
10
+ expectedOutputs: string[];
11
+ verification: DispatchIntentVerification[];
12
+ }
13
+
14
+ /** Validate stored acceptance without consulting a catalog that may have changed. */
15
+ export function normalizeUserTaskAcceptance(raw: unknown): UserTaskAcceptance {
16
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
17
+ throw new Error("acceptance must be an object");
18
+ }
19
+ const value = raw as Record<string, unknown>;
20
+ if (Object.keys(value).some((key) => key !== "expectedOutputs" && key !== "verification")) {
21
+ throw new Error("acceptance contains an unknown field");
22
+ }
23
+ if (!Array.isArray(value.expectedOutputs) || !Array.isArray(value.verification)) {
24
+ throw new Error("acceptance requires expectedOutputs and verification arrays");
25
+ }
26
+ const checks = new Map<string, DispatchIntentCheckBound>();
27
+ const rawVerification = value.verification.map((entry: unknown) => {
28
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
29
+ throw new Error("acceptance verification must contain check and timeoutMs");
30
+ }
31
+ const item = entry as Record<string, unknown>;
32
+ if (Object.keys(item).some((key) => key !== "check" && key !== "timeoutMs")) {
33
+ throw new Error("acceptance verification contains an unknown field");
34
+ }
35
+ if (
36
+ typeof item.check !== "string" ||
37
+ typeof item.timeoutMs !== "number" ||
38
+ !Number.isSafeInteger(item.timeoutMs) ||
39
+ item.timeoutMs <= 0
40
+ ) {
41
+ throw new Error("acceptance verification requires a check and a positive integer timeoutMs");
42
+ }
43
+ checks.set(item.check.trim(), {
44
+ id: item.check.trim(),
45
+ timeoutMs: Math.max(DISPATCH_INTENT_TIMEOUT_MIN_MS, item.timeoutMs, checks.get(item.check.trim())?.timeoutMs ?? 0),
46
+ });
47
+ return { check: item.check, timeout_ms: item.timeoutMs };
48
+ });
49
+ const expectedOutputs = normalizeIntentExpectedOutputs(value.expectedOutputs);
50
+ if (!Array.isArray(expectedOutputs))
51
+ throw new Error(expectedOutputs.ok ? "invalid acceptance outputs" : expectedOutputs.message);
52
+ const verification = normalizeIntentVerification(rawVerification, checks);
53
+ if (!Array.isArray(verification))
54
+ throw new Error(verification.ok ? "invalid acceptance verification" : verification.message);
55
+ return { expectedOutputs, verification };
56
+ }
@@ -0,0 +1,40 @@
1
+ import { isSessionEntry } from "../session/entries.js";
2
+ import type { TaskBoardSnapshot } from "../session/task-board.js";
3
+ import type { UserTaskAcceptance } from "./acceptance.js";
4
+ import type { UserTask } from "./store.js";
5
+
6
+ /** Keep a task's acceptance through the turn that finishes or blocks its board row. */
7
+ export function activeUserTaskAcceptance(
8
+ tasks: ReadonlyArray<UserTask>,
9
+ board: TaskBoardSnapshot | null,
10
+ sessionId: string | null,
11
+ window: ReadonlyArray<unknown>,
12
+ ): UserTaskAcceptance | undefined {
13
+ if (!board || !sessionId) return undefined;
14
+ const touched = new Set<string>();
15
+ for (const entry of window) {
16
+ if (!isSessionEntry(entry) || entry.kind !== "message" || entry.role !== "tool_call") continue;
17
+ if (!entry.payload || typeof entry.payload !== "object") continue;
18
+ const payload = entry.payload as { name?: string; toolName?: string; args?: { action?: string; id?: string } };
19
+ if (
20
+ (payload.name ?? payload.toolName) === "tasks" &&
21
+ payload.args?.id &&
22
+ ["done", "block", "drop"].includes(payload.args.action ?? "")
23
+ )
24
+ touched.add(payload.args.id);
25
+ }
26
+ const linked = new Set(
27
+ board.tasks
28
+ .filter((task) => task.status === "pending" || task.status === "active" || touched.has(task.id))
29
+ .map((task) => task.userTaskId),
30
+ );
31
+ const acceptance = tasks
32
+ .filter((task) => task.handedSessionId === sessionId && linked.has(task.id))
33
+ .flatMap((task) => (task.acceptance ? [task.acceptance] : []));
34
+ return acceptance.length > 0
35
+ ? {
36
+ expectedOutputs: [...new Set(acceptance.flatMap((item) => item.expectedOutputs))],
37
+ verification: acceptance.flatMap((item) => item.verification),
38
+ }
39
+ : undefined;
40
+ }
@@ -2,6 +2,7 @@ import { existsSync, readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { CLIO_PROJECT_DIR } from "../../core/artifact-paths.js";
4
4
  import { atomicWrite } from "../../engine/session.js";
5
+ import { normalizeUserTaskAcceptance, type UserTaskAcceptance } from "./acceptance.js";
5
6
 
6
7
  export const USER_TASKS_FILE_VERSION = 1 as const;
7
8
  export const USER_TASKS_RELATIVE_PATH = `${CLIO_PROJECT_DIR}/user-tasks.json`;
@@ -12,6 +13,7 @@ export interface UserTask {
12
13
  id: string;
13
14
  title: string;
14
15
  note?: string;
16
+ acceptance?: UserTaskAcceptance;
15
17
  status: UserTaskStatus;
16
18
  createdAt: string;
17
19
  updatedAt: string;
@@ -37,7 +39,8 @@ export interface UserTasksStore {
37
39
  readonly path: string;
38
40
  snapshot(): ReadonlyArray<UserTask>;
39
41
  get(id: string): UserTask | null;
40
- add(title: string, note?: string): UserTask;
42
+ add(title: string, note?: string, acceptance?: UserTaskAcceptance): UserTask;
43
+ setAcceptance(id: string, acceptance: UserTaskAcceptance): UserTask;
41
44
  hand(id: string, sessionId?: string): UserTask;
42
45
  done(id: string): UserTask;
43
46
  drop(id: string): UserTask;
@@ -65,6 +68,7 @@ const TASK_KEYS = new Set([
65
68
  "id",
66
69
  "title",
67
70
  "note",
71
+ "acceptance",
68
72
  "status",
69
73
  "createdAt",
70
74
  "updatedAt",
@@ -86,6 +90,13 @@ function isOptionalString(value: unknown): boolean {
86
90
 
87
91
  function isUserTask(value: unknown): value is UserTask {
88
92
  if (!isRecord(value) || !hasOnlyKeys(value, TASK_KEYS)) return false;
93
+ if (value.acceptance !== undefined) {
94
+ try {
95
+ normalizeUserTaskAcceptance(value.acceptance);
96
+ } catch {
97
+ return false;
98
+ }
99
+ }
89
100
  return (
90
101
  typeof value.id === "string" &&
91
102
  /^u[1-9]\d*$/.test(value.id) &&
@@ -138,7 +149,7 @@ function parseFile(body: string, path: string): UserTasksFile {
138
149
  }
139
150
 
140
151
  function copyTask(task: UserTask): UserTask {
141
- return { ...task };
152
+ return { ...task, ...(task.acceptance ? { acceptance: normalizeUserTaskAcceptance(task.acceptance) } : {}) };
142
153
  }
143
154
 
144
155
  function initialFile(): UserTasksFile {
@@ -219,7 +230,8 @@ export function createUserTasksStore(deps: UserTasksStoreDeps): UserTasksStore {
219
230
  const task = load().tasks.find((candidate) => candidate.id === id);
220
231
  return task ? copyTask(task) : null;
221
232
  },
222
- add(title: string, note?: string): UserTask {
233
+ add(title: string, note?: string, acceptance?: UserTaskAcceptance): UserTask {
234
+ const normalizedAcceptance = acceptance === undefined ? undefined : normalizeAcceptance(acceptance);
223
235
  const normalizedTitle = title.trim();
224
236
  if (normalizedTitle.length === 0) throw new UserTasksStoreError("operator task title cannot be empty");
225
237
  const normalizedNote = note?.trim();
@@ -229,6 +241,7 @@ export function createUserTasksStore(deps: UserTasksStoreDeps): UserTasksStore {
229
241
  id: `u${file.nextId}`,
230
242
  title: normalizedTitle,
231
243
  ...(normalizedNote ? { note: normalizedNote } : {}),
244
+ ...(normalizedAcceptance ? { acceptance: normalizedAcceptance } : {}),
232
245
  status: "open",
233
246
  createdAt: timestamp,
234
247
  updatedAt: timestamp,
@@ -238,6 +251,16 @@ export function createUserTasksStore(deps: UserTasksStoreDeps): UserTasksStore {
238
251
  save(file);
239
252
  return copyTask(task);
240
253
  },
254
+ setAcceptance(id: string, acceptance: UserTaskAcceptance): UserTask {
255
+ const normalized = normalizeAcceptance(acceptance);
256
+ const file = load();
257
+ const task = file.tasks.find((item) => item.id === id);
258
+ if (!task) throw new UserTasksStoreError(`operator task ${id} was not found`);
259
+ task.acceptance = normalized;
260
+ task.updatedAt = now();
261
+ save(file);
262
+ return copyTask(task);
263
+ },
241
264
  hand(id: string, sessionId?: string): UserTask {
242
265
  return mutate(id, ["open"], "handed", sessionId);
243
266
  },
@@ -295,3 +318,11 @@ export function createUserTasksStore(deps: UserTasksStoreDeps): UserTasksStore {
295
318
  },
296
319
  };
297
320
  }
321
+
322
+ function normalizeAcceptance(raw: unknown): UserTaskAcceptance {
323
+ try {
324
+ return normalizeUserTaskAcceptance(raw);
325
+ } catch (error) {
326
+ throw new UserTasksStoreError(error instanceof Error ? error.message : String(error), { cause: error });
327
+ }
328
+ }
@@ -134,13 +134,15 @@ function flattenPrompt(input: AcpDelegationRunInput): string {
134
134
  return parts.join("\n\n");
135
135
  }
136
136
 
137
- function emptyUsage(): AcpDelegationUsage {
137
+ export function emptyUsage(): AcpDelegationUsage {
138
138
  return {
139
139
  inputTokens: 0,
140
140
  outputTokens: 0,
141
141
  cacheReadTokens: 0,
142
142
  cacheWriteTokens: 0,
143
143
  reasoningTokens: 0,
144
+ totalTokens: 0,
145
+ costUsd: 0,
144
146
  };
145
147
  }
146
148
 
@@ -148,13 +150,29 @@ function finite(value: unknown): number {
148
150
  return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
149
151
  }
150
152
 
151
- function mergeUsage(into: AcpDelegationUsage, raw: unknown): void {
153
+ /** Same field the ACP server side and `sumRunUsage` read for a dollar figure; cost is never re-derived here. */
154
+ function costTotal(raw: Record<string, unknown>): number {
155
+ const cost = raw.cost;
156
+ return isRecord(cost) ? finite(cost.total) : 0;
157
+ }
158
+
159
+ export function mergeUsage(into: AcpDelegationUsage, raw: unknown): void {
152
160
  if (!isRecord(raw)) return;
153
- into.inputTokens += finite(raw.input) + finite(raw.inputTokens) + finite(raw.input_tokens);
154
- into.outputTokens += finite(raw.output) + finite(raw.outputTokens) + finite(raw.output_tokens);
155
- into.cacheReadTokens += finite(raw.cacheRead) + finite(raw.cacheReadTokens) + finite(raw.cache_read_tokens);
156
- into.cacheWriteTokens += finite(raw.cacheWrite) + finite(raw.cacheWriteTokens) + finite(raw.cache_write_tokens);
161
+ const input = finite(raw.input) + finite(raw.inputTokens) + finite(raw.input_tokens);
162
+ const output = finite(raw.output) + finite(raw.outputTokens) + finite(raw.output_tokens);
163
+ const cacheRead = finite(raw.cacheRead) + finite(raw.cacheReadTokens) + finite(raw.cache_read_tokens);
164
+ const cacheWrite = finite(raw.cacheWrite) + finite(raw.cacheWriteTokens) + finite(raw.cache_write_tokens);
165
+ into.inputTokens += input;
166
+ into.outputTokens += output;
167
+ into.cacheReadTokens += cacheRead;
168
+ into.cacheWriteTokens += cacheWrite;
157
169
  into.reasoningTokens += finite(raw.reasoning) + finite(raw.reasoningTokens) + finite(raw.reasoning_tokens);
170
+ // Same rule as the ACP server accumulator: prefer the peer's explicit total,
171
+ // otherwise sum the four billed categories (reasoning excluded, since a peer
172
+ // that reports it separately still counts it inside output).
173
+ const explicitTotal = finite(raw.totalTokens) + finite(raw.total_tokens);
174
+ into.totalTokens += explicitTotal > 0 ? explicitTotal : input + output + cacheRead + cacheWrite;
175
+ into.costUsd += costTotal(raw);
158
176
  }
159
177
 
160
178
  function errorMessage(value: unknown): string {
@@ -60,7 +60,7 @@ export interface AcpServerChat {
60
60
  onEvent(handler: (event: AcpServerEvent) => void): () => void;
61
61
  isStreaming(): boolean;
62
62
  getSessionId(): string | null;
63
- /** Replace provider context and the next persisted parent after session/load. */
63
+ /** Replace provider context and the next persisted parent for a new or loaded session. */
64
64
  resetForSession?(leafTurnId: string | null, replayMessages?: ReadonlyArray<AgentMessage>): void;
65
65
  dispose?(): void;
66
66
  }
@@ -1406,6 +1406,7 @@ function installPermissionBridge(input: {
1406
1406
  toolCallSnapshot: (wireId: string) => AcpToolCallSnapshot | null;
1407
1407
  permissionTimeoutMs: number;
1408
1408
  expireActivePrompt: () => void;
1409
+ cancelActivePrompt: (reason: string) => void;
1409
1410
  }): AcpPermissionBridge {
1410
1411
  if (!input.toolRegistry) return { unregister: () => {}, cancelPending: () => {} };
1411
1412
  let pendingCancel: ((reason: string) => void) | null = null;
@@ -1573,6 +1574,17 @@ function installPermissionBridge(input: {
1573
1574
  // client mint `allow-always` and get a grant this server never
1574
1575
  // offered as an option.
1575
1576
  const answer = outcome.response.outcome;
1577
+ if (answer.outcome === "cancelled") {
1578
+ const reason = "ACP client cancelled permission";
1579
+ // Abort before releasing the parked tools so their results cannot
1580
+ // start another model request while session/cancel is in flight.
1581
+ input.cancelActivePrompt(reason);
1582
+ emitResolution({ status: "denied", decidedBy: "cancelled", reason });
1583
+ queuedRequestIds.clear();
1584
+ queuedRequestDetails.clear();
1585
+ input.toolRegistry?.cancelParkedCalls(reason);
1586
+ return;
1587
+ }
1576
1588
  if (answer.outcome === "selected" && answer.optionId === "allow-once") {
1577
1589
  emitResolution({ status: "granted", decidedBy: "acp-client" });
1578
1590
  await input.toolRegistry?.resumeParkedCalls({
@@ -1695,6 +1707,10 @@ export async function serveClioAcpAgent(options: ClioAcpServerOptions): Promise<
1695
1707
  activePromptState.permissionExpired = true;
1696
1708
  options.chat.cancel();
1697
1709
  },
1710
+ cancelActivePrompt: (reason) => {
1711
+ const session = activeSessionId === null ? undefined : sessions.get(activeSessionId);
1712
+ if (session) cancelSession(session, reason);
1713
+ },
1698
1714
  });
1699
1715
  /**
1700
1716
  * Sends one frame of the opt-in stream. Every forwarded channel goes through
@@ -2128,9 +2144,8 @@ export async function serveClioAcpAgent(options: ClioAcpServerOptions): Promise<
2128
2144
 
2129
2145
  options.transport.onRequest("session/new", (params) => {
2130
2146
  requireInitialized();
2131
- // One chat instance, one provider context, one ledger ancestry. A second
2132
- // session id over the same process would share all three with the first,
2133
- // so the honest answer is to refuse and let the client start a process.
2147
+ // The chat instance can bind only one session at a time. Closing that
2148
+ // session releases the slot; the next creation resets its conversation.
2134
2149
  if (sessionCreated) {
2135
2150
  throw new AcpRequestError(-32000, "this server hosts one session per process", { code: "session_limit" });
2136
2151
  }
@@ -2144,6 +2159,7 @@ export async function serveClioAcpAgent(options: ClioAcpServerOptions): Promise<
2144
2159
  if (safeStoredIdentifier(id, ACP_MAX_SESSION_ID_BYTES) === null) {
2145
2160
  throw new AcpRequestError(-32000, "session creation failed", { code: "internal_error" });
2146
2161
  }
2162
+ options.chat.resetForSession?.(null);
2147
2163
  const autonomy = options.autonomy?.() ?? DEFAULT_AUTONOMY_LEVEL;
2148
2164
  const boundTarget = safeConfiguredIdentifier(meta?.target ?? route.target, ACP_MAX_TARGET_ID_BYTES);
2149
2165
  const boundModel = safeConfiguredIdentifier(meta?.model ?? route.model, ACP_MAX_MODEL_ID_BYTES);
@@ -2529,6 +2545,7 @@ export async function serveClioAcpAgent(options: ClioAcpServerOptions): Promise<
2529
2545
  if (options.session?.current()?.id === session.id) await options.session.close();
2530
2546
  sessions.delete(session.id);
2531
2547
  if (boundSessionId === session.id) boundSessionId = null;
2548
+ sessionCreated = false;
2532
2549
  closedSessionIds.add(session.id);
2533
2550
  return {};
2534
2551
  });
@@ -534,6 +534,10 @@ export function createStdioTransport(
534
534
  }
535
535
 
536
536
  class StreamJsonRpcPeerTransport implements AcpJsonRpcPeerTransport {
537
+ private outputPaused = false;
538
+ private queuedOutputBytes = 0;
539
+ private sinkOutputBytes = 0;
540
+ private readonly outputQueue: string[] = [];
537
541
  private nextId = 1;
538
542
  private buffer = "";
539
543
  /** True while the bytes of an oversized line are being dropped up to its terminating newline. */
@@ -626,18 +630,56 @@ class StreamJsonRpcPeerTransport implements AcpJsonRpcPeerTransport {
626
630
  }
627
631
 
628
632
  private write(message: AcpJsonRpcMessage): void {
633
+ if (this.closed) return;
629
634
  const line = `${JSON.stringify(message)}\n`;
630
- if (this.writeOverride) {
631
- this.writeOverride(line);
632
- return;
635
+ if (this.outputPaused) {
636
+ const bytes = Buffer.byteLength(line, "utf8");
637
+ // Include bytes already held by the sink in the existing frame budget.
638
+ if (this.queuedOutputBytes + this.sinkOutputBytes + bytes > ACP_MAX_INPUT_LINE_BYTES) {
639
+ this.markClosed(new AcpProcessError("ACP output buffer exceeded byte limit"));
640
+ return;
641
+ }
633
642
  }
634
- // A `false` return means the stream buffered rather than flushed. Honoring
635
- // it means pausing frame production, which changes turn timing, so the
636
- // decision to keep writing is deferred rather than forgotten (CONTRACT
637
- // C001 §6 leaves stdout backpressure out of this profile).
638
- this.output.write(line);
643
+ this.outputQueue.push(line);
644
+ this.queuedOutputBytes += Buffer.byteLength(line, "utf8");
645
+ if (!this.outputPaused) this.flushOutput();
639
646
  }
640
647
 
648
+ private readonly flushOutput = (): void => {
649
+ this.outputPaused = false;
650
+ while (!this.closed && !this.outputPaused && this.outputQueue.length > 0) {
651
+ const line = this.outputQueue.shift();
652
+ if (line === undefined) break;
653
+ this.queuedOutputBytes -= Buffer.byteLength(line, "utf8");
654
+ try {
655
+ // Stdout can count buffered strings in UTF-16 units rather than bytes.
656
+ // Retain their byte count until drain, bounding earlier writes by at
657
+ // most three UTF-8 bytes per unit still held by the sink.
658
+ this.sinkOutputBytes = Math.min(this.sinkOutputBytes, this.output.writableLength * 3);
659
+ let ready: boolean;
660
+ if (this.writeOverride) {
661
+ this.writeOverride(line);
662
+ // The stdout guard writes to this sink but does not return write's boolean.
663
+ ready = !this.output.writableNeedDrain;
664
+ } else {
665
+ ready = this.output.write(line);
666
+ }
667
+ this.sinkOutputBytes =
668
+ this.output.writableLength === 0 ? 0 : this.sinkOutputBytes + Buffer.byteLength(line, "utf8");
669
+ if (!ready && !this.closed) {
670
+ if (this.queuedOutputBytes + this.sinkOutputBytes > ACP_MAX_INPUT_LINE_BYTES) {
671
+ this.markClosed(new AcpProcessError("ACP output buffer exceeded byte limit"));
672
+ return;
673
+ }
674
+ this.outputPaused = true;
675
+ this.output.once("drain", this.flushOutput);
676
+ }
677
+ } catch (err) {
678
+ this.markClosed(new AcpProcessError(`ACP output write failed: ${errorMessage(err)}`));
679
+ }
680
+ }
681
+ };
682
+
641
683
  private consume(chunk: string): void {
642
684
  this.buffer += chunk;
643
685
  for (;;) {
@@ -757,6 +799,9 @@ class StreamJsonRpcPeerTransport implements AcpJsonRpcPeerTransport {
757
799
  private markClosed(reason: unknown): void {
758
800
  if (this.isClosed) return;
759
801
  this.isClosed = true;
802
+ this.output.removeListener("drain", this.flushOutput);
803
+ this.outputQueue.length = 0;
804
+ this.queuedOutputBytes = 0;
760
805
  this.failAll(reason);
761
806
  for (const handler of this.closeHandlers) handler();
762
807
  }
@@ -194,6 +194,10 @@ export interface AcpDelegationUsage {
194
194
  cacheReadTokens: number;
195
195
  cacheWriteTokens: number;
196
196
  reasoningTokens: number;
197
+ /** Peer-reported total when present, otherwise input+output+cacheRead+cacheWrite, matching the ACP server side. */
198
+ totalTokens: number;
199
+ /** Sum of the peer's `usage.cost.total`; stays 0 when the peer reports no cost. */
200
+ costUsd: number;
197
201
  }
198
202
 
199
203
  export interface AcpDelegationResult {
@@ -41,6 +41,7 @@ import { mergeSamplingOverride } from "./sampling-overrides.js";
41
41
  import type { EngineApiProvider } from "./types.js";
42
42
 
43
43
  const REASONING_CHARS_PER_TOKEN = 4;
44
+ const ownedModelsByTarget = new Map<string, Set<string>>();
44
45
 
45
46
  interface ClioRuntimeMetadata {
46
47
  clioCoder?: {
@@ -293,7 +294,10 @@ async function listResidentOllamaModels(client: OllamaEvictClient): Promise<Resi
293
294
  * weights release.
294
295
  */
295
296
  async function unloadOllamaModel(baseUrl: string, modelId: string, headers?: Record<string, string>): Promise<void> {
297
+ const owned = ownedModelsByTarget.get(residencyTargetKey("ollama-native", baseUrl));
298
+ if (!owned?.has(modelId)) return;
296
299
  await ollamaEvictClient(baseUrl, headers).generate({ model: modelId, prompt: "", keep_alive: 0, stream: false });
300
+ owned.delete(modelId);
297
301
  }
298
302
 
299
303
  function mapStopReason(reason: string | undefined, hadToolCall: boolean): AssistantMessage["stopReason"] {
@@ -466,8 +470,19 @@ function runStream(
466
470
  else emitText(segment.content);
467
471
  }
468
472
  };
473
+ let recordedOwnership = false;
469
474
  for await (const chunk of iterator) {
470
475
  const response = chunk as ChatResponse;
476
+ if (!recordedOwnership && model.baseUrl) {
477
+ const targetKey = residencyTargetKey("ollama-native", model.baseUrl);
478
+ let owned = ownedModelsByTarget.get(targetKey);
479
+ if (!owned) {
480
+ owned = new Set<string>();
481
+ ownedModelsByTarget.set(targetKey, owned);
482
+ }
483
+ owned.add(response.model || model.id);
484
+ recordedOwnership = true;
485
+ }
471
486
  const msg = response.message;
472
487
  if (msg?.thinking && msg.thinking.length > 0) {
473
488
  emitThinking(msg.thinking);