@iowarp/clio-coder 0.3.0 → 0.3.2

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 (1187) hide show
  1. package/CHANGELOG.md +123 -2
  2. package/CONTRIBUTING.md +23 -23
  3. package/README.md +295 -627
  4. package/dist/acp-BIYHVZIM.js +128 -0
  5. package/dist/{agents-Q4MYPMUW.js → agents-YT6SSRIT.js} +52 -27
  6. package/dist/assets/codewiki.json +1 -0
  7. package/dist/assets/grammars/notices/@vscode__tree-sitter-wasm/LICENSE +21 -0
  8. package/dist/assets/grammars/notices/@vscode__tree-sitter-wasm/cgmanifest.json +16 -0
  9. package/dist/assets/grammars/notices/tree-sitter-wasms/LICENSE +24 -0
  10. package/dist/assets/grammars/tree-sitter-c-sharp.wasm +0 -0
  11. package/dist/assets/grammars/tree-sitter-c.wasm +0 -0
  12. package/dist/assets/grammars/tree-sitter-cpp.wasm +0 -0
  13. package/dist/assets/grammars/tree-sitter-go.wasm +0 -0
  14. package/dist/assets/grammars/tree-sitter-java.wasm +0 -0
  15. package/dist/assets/grammars/tree-sitter-javascript.wasm +0 -0
  16. package/dist/assets/grammars/tree-sitter-python.wasm +0 -0
  17. package/dist/assets/grammars/tree-sitter-ruby.wasm +0 -0
  18. package/dist/assets/grammars/tree-sitter-rust.wasm +0 -0
  19. package/dist/assets/grammars/tree-sitter-tsx.wasm +0 -0
  20. package/dist/assets/grammars/tree-sitter-typescript.wasm +0 -0
  21. package/dist/assets/grammars/tree-sitter.wasm +0 -0
  22. package/dist/{auth-O6HYIJ6J.js → auth-5TWEIYDN.js} +30 -15
  23. package/dist/chunk-2EHAIA3X.js +481 -0
  24. package/dist/{chunk-ME6DNWIU.js → chunk-2IR2NMPA.js} +12 -4
  25. package/dist/chunk-2SFS6XQE.js +122 -0
  26. package/dist/{chunk-KKKPTZLM.js → chunk-2STGEDFV.js} +8 -1
  27. package/dist/chunk-2VTFPG5O.js +48 -0
  28. package/dist/chunk-3I7MS7N2.js +40 -0
  29. package/dist/chunk-3R73A4XB.js +69 -0
  30. package/dist/{chunk-JPOWPFCU.js → chunk-3ZXDFGR5.js} +52 -23
  31. package/dist/chunk-4BJ5BYCE.js +61 -0
  32. package/dist/{chunk-26BZQOAD.js → chunk-4BPJXDWC.js} +161 -260
  33. package/dist/chunk-4KLWL3UC.js +18 -0
  34. package/dist/{chunk-5LDRNKX2.js → chunk-4VP4KH3K.js} +161 -262
  35. package/dist/chunk-4ZG3XFUR.js +77 -0
  36. package/dist/chunk-5B2AEOW5.js +5407 -0
  37. package/dist/chunk-5TSRNF4G.js +510 -0
  38. package/dist/{chunk-WD6AJM35.js → chunk-5UUP6MWO.js} +48 -69
  39. package/dist/{chunk-X3BR7HWV.js → chunk-5WIGXA4T.js} +7 -1
  40. package/dist/chunk-65DEGPJ6.js +52 -0
  41. package/dist/chunk-6EJMN2Y3.js +17 -0
  42. package/dist/chunk-6EJV5X2W.js +16405 -0
  43. package/dist/chunk-6N5PTWMY.js +136 -0
  44. package/dist/chunk-6XLNIQDB.js +27 -0
  45. package/dist/{chunk-XNISANGE.js → chunk-77VKQEHF.js} +171 -74
  46. package/dist/chunk-7CR24IG7.js +242 -0
  47. package/dist/{chunk-UWIJNAOB.js → chunk-7EYHLWU7.js} +907 -638
  48. package/dist/chunk-7MNJORFF.js +22 -0
  49. package/dist/{chunk-JWHCJDCI.js → chunk-A3CYT5EX.js} +257 -79
  50. package/dist/chunk-AGYYIBLL.js +1069 -0
  51. package/dist/chunk-AO4RKG4M.js +277 -0
  52. package/dist/chunk-APJ265NV.js +7410 -0
  53. package/dist/chunk-ARBGF5F7.js +174 -0
  54. package/dist/{chunk-TFSSEXL6.js → chunk-BMEMKKIT.js} +14 -4
  55. package/dist/{chunk-X3NE4WVW.js → chunk-BNAZZHFG.js} +7 -1
  56. package/dist/chunk-CBCAPZAA.js +229 -0
  57. package/dist/{chunk-QV47YRF4.js → chunk-CGKSTWHD.js} +7 -1
  58. package/dist/chunk-CMZWFGD2.js +352 -0
  59. package/dist/chunk-ECH6PKUQ.js +39 -0
  60. package/dist/{chunk-67MTHP2E.js → chunk-ED4KHGC3.js} +13 -5
  61. package/dist/chunk-EKMEHE4H.js +340 -0
  62. package/dist/{chunk-O4EJEDHO.js → chunk-EPVUXGXG.js} +50 -35
  63. package/dist/chunk-FCSXB6T2.js +338 -0
  64. package/dist/chunk-FJ3H4MN5.js +48 -0
  65. package/dist/chunk-FQ4SKYE4.js +29 -0
  66. package/dist/chunk-G2DE3C7R.js +644 -0
  67. package/dist/chunk-G4BMMOKF.js +182 -0
  68. package/dist/{chunk-7BHLZB3A.js → chunk-GGXXDWE4.js} +190 -1218
  69. package/dist/chunk-HC4CLZ2Y.js +68 -0
  70. package/dist/{chunk-E3NYWENM.js → chunk-HFSBBKSQ.js} +55 -61
  71. package/dist/chunk-HKIYEGME.js +30 -0
  72. package/dist/chunk-I4HZDVNP.js +73 -0
  73. package/dist/chunk-IGLFWIYI.js +161 -0
  74. package/dist/chunk-IHXBNWMM.js +137 -0
  75. package/dist/chunk-IKCO5N3L.js +162 -0
  76. package/dist/chunk-IR4CFBFN.js +56 -0
  77. package/dist/{chunk-DXQNI4PC.js → chunk-IWHMRKLL.js} +8 -1
  78. package/dist/{chunk-VOO7NYPP.js → chunk-J5HN4RYU.js} +27 -9
  79. package/dist/{chunk-4UQA7NCT.js → chunk-J5Q24KAG.js} +7 -2
  80. package/dist/{chunk-KBR4MZZR.js → chunk-JVCV3ICN.js} +7 -1
  81. package/dist/chunk-KJ5LWLOE.js +1077 -0
  82. package/dist/chunk-LBMZMYH2.js +285 -0
  83. package/dist/{chunk-3DANZDGR.js → chunk-LBNRH5WM.js} +100 -161
  84. package/dist/{chunk-TKHQ4DGZ.js → chunk-LZSJBIVT.js} +7358 -7414
  85. package/dist/{chunk-UGYAX5YI.js → chunk-M6SHUN7Q.js} +13 -8
  86. package/dist/chunk-MAW544W2.js +1882 -0
  87. package/dist/chunk-MBS4V7ZP.js +217 -0
  88. package/dist/{chunk-2J63S4SF.js → chunk-MFFY33HR.js} +167 -140
  89. package/dist/chunk-MNA4JGU4.js +255 -0
  90. package/dist/chunk-MQSRRFWA.js +3428 -0
  91. package/dist/{chunk-RQDWMVRB.js → chunk-MVVUPGPW.js} +138 -137
  92. package/dist/chunk-OAO4GE4M.js +619 -0
  93. package/dist/chunk-OHHN2SO4.js +5135 -0
  94. package/dist/chunk-OKGUZO2U.js +34 -0
  95. package/dist/{chunk-BBUH4VAA.js → chunk-OOJYHWRB.js} +60 -361
  96. package/dist/{chunk-D5YD55AR.js → chunk-OQ33BKR3.js} +12 -1
  97. package/dist/chunk-OQE5J4C6.js +73 -0
  98. package/dist/{chunk-AMFR5YA3.js → chunk-ORBHGJC5.js} +26 -12
  99. package/dist/chunk-POHLU5DW.js +1186 -0
  100. package/dist/chunk-QKMUKYO7.js +4961 -0
  101. package/dist/{chunk-7RBKDI66.js → chunk-QTYWRVRA.js} +104 -12
  102. package/dist/{chunk-5N2FG33Q.js → chunk-R346GLFC.js} +7 -1
  103. package/dist/chunk-SRF2PJNW.js +184 -0
  104. package/dist/chunk-SST6Z5JA.js +80 -0
  105. package/dist/{chunk-XV4ZJ6ZM.js → chunk-STBPMHSX.js} +473 -1194
  106. package/dist/{chunk-HBU5MTAM.js → chunk-SVNXH4L2.js} +7 -1
  107. package/dist/chunk-T6YILFSB.js +80 -0
  108. package/dist/chunk-TXOTCRLG.js +15 -0
  109. package/dist/chunk-TZTZS7QK.js +227 -0
  110. package/dist/chunk-UOV2BYIW.js +107 -0
  111. package/dist/{chunk-527KG6XR.js → chunk-V4RXGQ5Q.js} +72 -185
  112. package/dist/chunk-VAKQQHWR.js +434 -0
  113. package/dist/chunk-VG7TBQIY.js +128 -0
  114. package/dist/chunk-VJWL6YS5.js +244 -0
  115. package/dist/chunk-WEH5XRJQ.js +32 -0
  116. package/dist/{chunk-VPAWTYLY.js → chunk-WEPFGWHJ.js} +7 -1
  117. package/dist/chunk-WMSVI4G2.js +2095 -0
  118. package/dist/chunk-WVO7V2QY.js +797 -0
  119. package/dist/chunk-X4RCMKVQ.js +641 -0
  120. package/dist/chunk-X75S7HFS.js +374 -0
  121. package/dist/chunk-XN3L4EYL.js +46 -0
  122. package/dist/{chunk-HMYNFFY4.js → chunk-XPLRXC72.js} +7 -2
  123. package/dist/{chunk-PIDUD6M2.js → chunk-YCWGATWI.js} +12 -5
  124. package/dist/chunk-YHZX5GEU.js +193 -0
  125. package/dist/chunk-YXLYO42X.js +91 -0
  126. package/dist/{chunk-UHTSULZS.js → chunk-ZDOOVTXZ.js} +38 -73
  127. package/dist/chunk-ZI647VB5.js +37 -0
  128. package/dist/{chunk-CLJ5HLUD.js → chunk-ZWLZP4ZT.js} +25 -6
  129. package/dist/cli/index.js +70 -56
  130. package/dist/clio-4LY5K2AC.js +25 -0
  131. package/dist/code-nav-7AX6FYE6.js +600 -0
  132. package/dist/codewiki/build-worker.js +66 -0
  133. package/dist/compile-cache-CVJMMODC.js +18 -0
  134. package/dist/{components-JVHMUBEB.js → components-KELWS457.js} +31 -9
  135. package/dist/{config-ZFCDBMDC.js → config-GTLUW2PR.js} +104 -56
  136. package/dist/configure-R6A64DHX.js +42 -0
  137. package/dist/context-5VKGUVJJ.js +866 -0
  138. package/dist/{context-CDXTP2MP.js → context-JFZEJ7W5.js} +21 -13
  139. package/dist/{context-E3KIFVXI.js → context-RW5HC47S.js} +77 -36
  140. package/dist/{context-clear-3F4PLXOS.js → context-clear-6ZHBAZZT.js} +64 -30
  141. package/dist/{context-index-Q7YSYTR3.js → context-index-BZ4UYMTC.js} +36 -24
  142. package/dist/dispatch-runner-VKBRCWQC.js +1997 -0
  143. package/dist/{docs-YIETIWZI.js → docs-2C2LTVT2.js} +32 -13
  144. package/dist/doctor-KI767GSN.js +87 -0
  145. package/dist/{eval-B3KZZESM.js → eval-XSSNATB4.js} +93 -27
  146. package/dist/{evidence-V67CHM35.js → evidence-UA6AWDQQ.js} +55 -27
  147. package/dist/{evolve-YDZSUQYA.js → evolve-QNTFGV6Z.js} +59 -26
  148. package/dist/{extensions-SRG7XCAH.js → extensions-QVDOHDGJ.js} +23 -8
  149. package/dist/{fleet-CA2CRTVG.js → fleet-Q7UOMUSG.js} +173 -56
  150. package/dist/fleet-preflight-DDN536IT.js +26 -0
  151. package/dist/{init-2OZDJE2D.js → init-WBB65ZHQ.js} +79 -34
  152. package/dist/{memory-3PIQQAKX.js → memory-MD3O64RI.js} +57 -28
  153. package/dist/{models-DY35XI7Y.js → models-BZU34YWD.js} +49 -24
  154. package/dist/monitor-MEQA5C3I.js +661 -0
  155. package/dist/{chunk-PS4PFJQP.js → orchestrator-CGFKEP27.js} +22837 -18564
  156. package/dist/{paths-5OMXW7Z4.js → paths-UXLN5YYZ.js} +17 -6
  157. package/dist/preload-P6DGH2PZ.js +13 -0
  158. package/dist/{reset-PIFYNOS3.js → reset-L2FQEE3E.js} +25 -13
  159. package/dist/{run-3VSPP24F.js → run-IV4Q6RLN.js} +115 -63
  160. package/dist/{share-D36RQCXM.js → share-S5BZQC5I.js} +23 -10
  161. package/dist/{skills-F2MRLELY.js → skills-LQEKRDTN.js} +42 -15
  162. package/dist/{skills-eval-E2ZTW4PL.js → skills-eval-3DC4HEWS.js} +90 -45
  163. package/dist/steer-GGWFUJUD.js +77 -0
  164. package/dist/{targets-DZMEZAH4.js → targets-C4SSGQOB.js} +78 -60
  165. package/dist/terminal-lease-IT5JW2NR.js +395 -0
  166. package/dist/{trace-7NYCUI2J.js → trace-PNCASAXC.js} +48 -17
  167. package/dist/tree-sitter-HGKH6LG4.js +4673 -0
  168. package/dist/{uninstall-AD3JWHBB.js → uninstall-FZCQCDKC.js} +19 -8
  169. package/dist/{upgrade-WYYBKGDY.js → upgrade-7TT7SQ3G.js} +66 -24
  170. package/dist/{usage-ULIDAGFF.js → usage-GV4PKT3M.js} +71 -32
  171. package/dist/verify-G6V4D2G7.js +716 -0
  172. package/dist/version-KGEG7EPO.js +21 -0
  173. package/dist/web-fetch-2YHJ3KTG.js +638 -0
  174. package/dist/{wiki-generate-PKFIX6OB.js → wiki-generate-DQF6Z66B.js} +98 -48
  175. package/dist/worker/entry.js +297 -54
  176. package/dist/workspace-G4ZWUIPR.js +22 -0
  177. package/docs/README.md +29 -21
  178. package/docs/acp.md +191 -16
  179. package/docs/alcf-provider.md +1 -1
  180. package/docs/architecture.md +157 -7
  181. package/docs/artifact-placement.md +101 -0
  182. package/docs/artifact-versions.md +1 -1
  183. package/docs/built-in-agents.md +4 -2
  184. package/docs/capacity-and-scheduling.md +1 -1
  185. package/docs/commands-and-modes.md +224 -97
  186. package/docs/config-knobs-audit.md +1 -3
  187. package/docs/configuration-and-targets.md +236 -38
  188. package/docs/context-engine.md +34 -7
  189. package/docs/development-pipeline.md +102 -0
  190. package/docs/dispatch-architecture-rationale.md +1 -1
  191. package/docs/documentation-coverage.md +4 -4
  192. package/docs/documentation-guide.md +3 -3
  193. package/docs/environment-variables.md +15 -6
  194. package/docs/eval-runner.md +3 -3
  195. package/docs/evals-internal.md +2 -2
  196. package/docs/evidence-and-memory.md +10 -2
  197. package/docs/evolution.md +2 -2
  198. package/docs/exit-codes-and-output.md +15 -9
  199. package/docs/extensions-and-sharing.md +25 -18
  200. package/docs/fleet-demo-runbook.md +22 -23
  201. package/docs/fleet-dispatch.md +117 -16
  202. package/docs/git-commit-provenance.md +120 -0
  203. package/docs/glossary.md +107 -15
  204. package/docs/installation-and-lifecycle.md +75 -17
  205. package/docs/middleware-and-components.md +1 -1
  206. package/docs/model-catalog.md +46 -15
  207. package/docs/observability.md +8 -5
  208. package/docs/performance-methodology.md +491 -0
  209. package/docs/pi-boundary.md +72 -0
  210. package/docs/proactive-memory.md +14 -14
  211. package/docs/prompt-envelope-and-tools.md +25 -4
  212. package/docs/provider-adapter-cookbook.md +57 -4
  213. package/docs/release-cut-checklist.md +129 -111
  214. package/docs/safety-model.md +28 -8
  215. package/docs/scientific-validation.md +3 -3
  216. package/docs/session-lifecycle.md +55 -12
  217. package/docs/skills-marketplace.md +12 -8
  218. package/docs/time-conventions.md +101 -0
  219. package/docs/tool-usage.md +6 -5
  220. package/docs/trace-store.md +1 -1
  221. package/docs/troubleshooting.md +10 -7
  222. package/docs/tui-design.md +121 -28
  223. package/docs/worker-dispatch-mechanics.md +3 -3
  224. package/package.json +33 -36
  225. package/skills/README.md +5 -6
  226. package/skills/coding/ast-grep/SKILL.md +136 -0
  227. package/skills/coding/ast-grep/evals.md +56 -0
  228. package/skills/coding/ast-grep/references/rule_reference.md +297 -0
  229. package/skills/coding/coding-standards/SKILL.md +113 -0
  230. package/skills/coding/coding-standards/evals.md +34 -0
  231. package/skills/coding/prototype/SKILL.md +86 -0
  232. package/skills/coding/prototype/evals.md +42 -0
  233. package/skills/coding/prototype/references/LOGIC.md +67 -0
  234. package/skills/coding/prototype/references/UI.md +112 -0
  235. package/skills/coding/tdd/SKILL.md +101 -0
  236. package/skills/coding/tdd/evals.md +41 -0
  237. package/skills/coding/tdd/references/mocking.md +59 -0
  238. package/skills/coding/tdd/references/tests.md +77 -0
  239. package/skills/context/context-handoff/SKILL.md +126 -0
  240. package/skills/context/context-handoff/evals.md +57 -0
  241. package/skills/context/context-handoff/scripts/new-handoff.sh +26 -0
  242. package/skills/context/context-prime/SKILL.md +95 -0
  243. package/skills/context/context-prime/evals.md +54 -0
  244. package/skills/git/file-ticket/SKILL.md +107 -0
  245. package/skills/git/file-ticket/evals.md +54 -0
  246. package/skills/git/fix-issue/SKILL.md +106 -0
  247. package/skills/git/fix-issue/evals.md +71 -0
  248. package/skills/git/ship/SKILL.md +92 -0
  249. package/skills/git/ship/evals.md +72 -0
  250. package/skills/meta/clio-dev/SKILL.md +91 -0
  251. package/skills/meta/clio-dev/evals.md +45 -0
  252. package/skills/meta/clio-test/SKILL.md +130 -0
  253. package/skills/meta/clio-test/evals.md +43 -0
  254. package/skills/meta/clio-test/references/harness.md +97 -0
  255. package/skills/meta/clio-test/references/test-map.md +59 -0
  256. package/skills/meta/credentials/SKILL.md +125 -0
  257. package/skills/meta/credentials/evals.md +104 -0
  258. package/skills/meta/find-skills/SKILL.md +72 -0
  259. package/skills/meta/find-skills/evals.md +47 -0
  260. package/skills/meta/herdr/SKILL.md +127 -0
  261. package/skills/meta/herdr/evals.md +38 -0
  262. package/skills/meta/skill-craft/SKILL.md +102 -0
  263. package/skills/meta/skill-craft/evals.md +41 -0
  264. package/skills/planning/architecture/SKILL.md +129 -0
  265. package/skills/planning/architecture/evals.md +36 -0
  266. package/skills/planning/backlog/SKILL.md +90 -0
  267. package/skills/planning/backlog/evals.md +43 -0
  268. package/skills/planning/prd/SKILL.md +82 -0
  269. package/skills/planning/prd/evals.md +49 -0
  270. package/skills/planning/product-intent/SKILL.md +112 -0
  271. package/skills/planning/product-intent/evals.md +36 -0
  272. package/skills/planning/tech-spec/SKILL.md +115 -0
  273. package/skills/planning/tech-spec/evals.md +47 -0
  274. package/skills/registry.yaml +136 -0
  275. package/skills/research/arxiv-literature/SKILL.md +104 -0
  276. package/skills/research/arxiv-literature/evals.md +58 -0
  277. package/skills/research/experiment-protocol/SKILL.md +122 -0
  278. package/skills/research/experiment-protocol/evals.md +91 -0
  279. package/skills/research/scientific-debugging/SKILL.md +119 -0
  280. package/skills/research/scientific-debugging/evals.md +138 -0
  281. package/skills/research/scientific-modernization/SKILL.md +138 -0
  282. package/skills/research/scientific-modernization/evals.md +84 -0
  283. package/skills/skill-marketplace.json +12 -20
  284. package/skills/workflow/design-council/SKILL.md +139 -0
  285. package/skills/workflow/design-council/evals.md +97 -0
  286. package/skills/workflow/grill-me/SKILL.md +186 -0
  287. package/skills/workflow/grill-me/evals.md +78 -0
  288. package/skills/workflow/workflow-distiller/SKILL.md +136 -0
  289. package/skills/workflow/workflow-distiller/evals.md +107 -0
  290. package/src/cli/acp.ts +115 -0
  291. package/src/cli/agents.ts +60 -0
  292. package/src/cli/args.ts +280 -0
  293. package/src/cli/argv.ts +170 -0
  294. package/src/cli/ask.ts +34 -0
  295. package/src/cli/auth.ts +468 -0
  296. package/src/cli/bootstrap-generate.ts +498 -0
  297. package/src/cli/clio.ts +96 -0
  298. package/src/cli/components.ts +195 -0
  299. package/src/cli/config-inspect.ts +336 -0
  300. package/src/cli/config.ts +72 -0
  301. package/src/cli/configure-interop.ts +67 -0
  302. package/src/cli/configure-layout.ts +172 -0
  303. package/src/cli/configure.ts +1525 -0
  304. package/src/cli/context-clear.ts +76 -0
  305. package/src/cli/context-index.ts +100 -0
  306. package/src/cli/context.ts +321 -0
  307. package/src/cli/default-target.ts +55 -0
  308. package/src/cli/docs.ts +346 -0
  309. package/src/cli/doctor.ts +54 -0
  310. package/src/cli/eval.ts +315 -0
  311. package/src/cli/evidence.ts +225 -0
  312. package/src/cli/evolve.ts +219 -0
  313. package/src/cli/extensions.ts +214 -0
  314. package/src/cli/fleet.ts +868 -0
  315. package/src/cli/index.ts +338 -0
  316. package/src/cli/init.ts +219 -0
  317. package/src/cli/initial-message.ts +47 -0
  318. package/src/cli/internal-dispatch.ts +55 -0
  319. package/src/cli/memory.ts +182 -0
  320. package/src/cli/models.ts +257 -0
  321. package/src/cli/modes/json-stream.ts +121 -0
  322. package/src/cli/modes/jsonl.ts +9 -0
  323. package/src/cli/modes/print.ts +604 -0
  324. package/src/cli/oauth-manual-input.ts +72 -0
  325. package/src/cli/oauth-select.ts +36 -0
  326. package/src/cli/output-guard.ts +48 -0
  327. package/src/cli/paths.ts +44 -0
  328. package/src/cli/provider-target.ts +113 -0
  329. package/src/cli/removal.ts +72 -0
  330. package/src/cli/reset.ts +253 -0
  331. package/src/cli/run.ts +497 -0
  332. package/src/cli/share.ts +243 -0
  333. package/src/cli/shared.ts +60 -0
  334. package/src/cli/skills-eval.ts +1240 -0
  335. package/src/cli/skills.ts +485 -0
  336. package/src/cli/steer-channel.ts +86 -0
  337. package/src/cli/targets.ts +1154 -0
  338. package/src/cli/text-layout.ts +112 -0
  339. package/src/cli/trace.ts +322 -0
  340. package/src/cli/uninstall.ts +405 -0
  341. package/src/cli/upgrade.ts +286 -0
  342. package/src/cli/usage.ts +876 -0
  343. package/src/cli/validate-model.ts +28 -0
  344. package/src/cli/version.ts +7 -0
  345. package/src/cli/wiki-generate.ts +510 -0
  346. package/src/core/agent-environment.ts +7 -0
  347. package/src/core/artifact-paths.ts +43 -0
  348. package/src/core/bash-exec.ts +313 -0
  349. package/src/core/boot-trace.ts +42 -0
  350. package/src/core/bus-events.ts +744 -0
  351. package/src/core/bus-trace.ts +53 -0
  352. package/src/core/c-header-language.ts +265 -0
  353. package/src/core/clio-repo.ts +78 -0
  354. package/src/core/commit-attribution.ts +157 -0
  355. package/src/core/compile-cache.ts +159 -0
  356. package/src/core/config.ts +1722 -0
  357. package/src/core/context-floor.ts +33 -0
  358. package/src/core/defaults.ts +659 -0
  359. package/src/core/domain-loader.ts +177 -0
  360. package/src/core/event-bus.ts +94 -0
  361. package/src/core/file-references.ts +282 -0
  362. package/src/core/git-commit-attribution.ts +362 -0
  363. package/src/core/guardrails.ts +185 -0
  364. package/src/core/headless-permission.ts +21 -0
  365. package/src/core/incomplete-installation.ts +45 -0
  366. package/src/core/init.ts +223 -0
  367. package/src/core/json-payload.ts +58 -0
  368. package/src/core/package-root.ts +54 -0
  369. package/src/core/path-canonical.ts +37 -0
  370. package/src/core/process-identity.ts +41 -0
  371. package/src/core/recent-models.ts +91 -0
  372. package/src/core/residency-protection.ts +56 -0
  373. package/src/core/resolve-config-value.ts +88 -0
  374. package/src/core/response-schema.ts +279 -0
  375. package/src/core/run-overrides.ts +77 -0
  376. package/src/core/safe-exec.ts +205 -0
  377. package/src/core/safe-id.ts +37 -0
  378. package/src/core/safe-resource-write.ts +162 -0
  379. package/src/core/session-routing.ts +339 -0
  380. package/src/core/settings-layers.ts +354 -0
  381. package/src/core/shared-bus.ts +8 -0
  382. package/src/core/shell-quote.ts +26 -0
  383. package/src/core/skill-activation.ts +190 -0
  384. package/src/core/startup-timer.ts +37 -0
  385. package/src/core/state-file-lock.ts +219 -0
  386. package/src/core/termination.ts +275 -0
  387. package/src/core/timers.ts +38 -0
  388. package/src/core/tool-names.ts +65 -0
  389. package/src/core/verification-scripts.ts +13 -0
  390. package/src/core/workspace-files.ts +492 -0
  391. package/src/core/xdg.ts +177 -0
  392. package/src/domains/agents/builtins/architect.md +4 -4
  393. package/src/domains/agents/builtins/coder.md +3 -2
  394. package/src/domains/agents/builtins/debugger.md +1 -1
  395. package/src/domains/agents/builtins/documenter.md +1 -1
  396. package/src/domains/agents/builtins/git-master.md +3 -3
  397. package/src/domains/agents/builtins/provenance.md +1 -1
  398. package/src/domains/agents/builtins/researcher.md +1 -1
  399. package/src/domains/agents/builtins/scout.md +1 -1
  400. package/src/domains/agents/builtins/tester.md +1 -1
  401. package/src/domains/agents/builtins/verifier.md +1 -1
  402. package/src/domains/agents/builtins/wiki-writer.md +1 -1
  403. package/src/domains/agents/catalog.ts +172 -0
  404. package/src/domains/agents/contract.ts +16 -0
  405. package/src/domains/agents/extension.ts +110 -0
  406. package/src/domains/agents/fleet-commands.ts +154 -0
  407. package/src/domains/agents/fleet-contract.ts +797 -0
  408. package/src/domains/agents/frontmatter.ts +40 -0
  409. package/src/domains/agents/index.ts +102 -0
  410. package/src/domains/agents/manifest.ts +6 -0
  411. package/src/domains/agents/recipe-schema.ts +202 -0
  412. package/src/domains/agents/recipe.ts +125 -0
  413. package/src/domains/agents/registry.ts +111 -0
  414. package/src/domains/agents/result-contract.ts +1265 -0
  415. package/src/domains/agents/spec.ts +371 -0
  416. package/src/domains/agents/write-boundary.ts +109 -0
  417. package/src/domains/components/diff.ts +99 -0
  418. package/src/domains/components/hash.ts +21 -0
  419. package/src/domains/components/index.ts +3 -0
  420. package/src/domains/components/scan.ts +304 -0
  421. package/src/domains/components/snapshot.ts +108 -0
  422. package/src/domains/components/types.ts +82 -0
  423. package/src/domains/config/agent-namespace.ts +13 -0
  424. package/src/domains/config/classify.ts +106 -0
  425. package/src/domains/config/contract.ts +22 -0
  426. package/src/domains/config/extension.ts +142 -0
  427. package/src/domains/config/index.ts +23 -0
  428. package/src/domains/config/keybindings.ts +200 -0
  429. package/src/domains/config/manifest.ts +6 -0
  430. package/src/domains/config/watcher.ts +41 -0
  431. package/src/domains/context/adoption.ts +880 -0
  432. package/src/domains/context/bootstrap-prompt.ts +273 -0
  433. package/src/domains/context/bootstrap.ts +1502 -0
  434. package/src/domains/context/clear.ts +91 -0
  435. package/src/domains/context/clio-md.ts +429 -0
  436. package/src/domains/context/codewiki/artifact.ts +400 -0
  437. package/src/domains/context/codewiki/build-worker-protocol.ts +24 -0
  438. package/src/domains/context/codewiki/build-worker.ts +54 -0
  439. package/src/domains/context/codewiki/cmake.ts +356 -0
  440. package/src/domains/context/codewiki/cooperative.ts +46 -0
  441. package/src/domains/context/codewiki/coordinator.ts +182 -0
  442. package/src/domains/context/codewiki/digest.ts +135 -0
  443. package/src/domains/context/codewiki/grammar-assets.ts +33 -0
  444. package/src/domains/context/codewiki/indexer.ts +1529 -0
  445. package/src/domains/context/codewiki/paths.ts +67 -0
  446. package/src/domains/context/codewiki/schema.ts +80 -0
  447. package/src/domains/context/codewiki/tree-sitter.ts +771 -0
  448. package/src/domains/context/contract.ts +68 -0
  449. package/src/domains/context/excluded-dirs.ts +1 -0
  450. package/src/domains/context/extension.ts +358 -0
  451. package/src/domains/context/fingerprint.ts +198 -0
  452. package/src/domains/context/index.ts +154 -0
  453. package/src/domains/context/init-options.ts +69 -0
  454. package/src/domains/context/manifest.ts +6 -0
  455. package/src/domains/context/operator-profile.ts +157 -0
  456. package/src/domains/context/project-metadata.ts +529 -0
  457. package/src/domains/context/project-rules.ts +227 -0
  458. package/src/domains/context/prompt-context.ts +61 -0
  459. package/src/domains/context/refresh.ts +178 -0
  460. package/src/domains/context/runtime.ts +17 -0
  461. package/src/domains/context/sibling-files.ts +10 -0
  462. package/src/domains/context/state.ts +237 -0
  463. package/src/domains/context/wiki/assemble.ts +303 -0
  464. package/src/domains/context/wiki/frontmatter.ts +210 -0
  465. package/src/domains/context/wiki/generate.ts +595 -0
  466. package/src/domains/context/wiki/layout.ts +111 -0
  467. package/src/domains/context/wiki/meta.ts +221 -0
  468. package/src/domains/context/wiki/plan-store.ts +217 -0
  469. package/src/domains/context/wiki/plan.ts +250 -0
  470. package/src/domains/context/wiki/prompts.ts +283 -0
  471. package/src/domains/context/wiki/staleness.ts +198 -0
  472. package/src/domains/dispatch/active-route-planner.ts +168 -0
  473. package/src/domains/dispatch/admission-queue.ts +150 -0
  474. package/src/domains/dispatch/admission.ts +334 -0
  475. package/src/domains/dispatch/agent-candidates.ts +307 -0
  476. package/src/domains/dispatch/agent-ledger-hub.ts +71 -0
  477. package/src/domains/dispatch/agent-ledger-store.ts +257 -0
  478. package/src/domains/dispatch/agent-ledger.ts +212 -0
  479. package/src/domains/dispatch/agent-plan-adapter.ts +85 -0
  480. package/src/domains/dispatch/assignment-events.ts +132 -0
  481. package/src/domains/dispatch/assignment-reconcile.ts +76 -0
  482. package/src/domains/dispatch/assignment-store.ts +162 -0
  483. package/src/domains/dispatch/assignment.ts +278 -0
  484. package/src/domains/dispatch/backoff.ts +59 -0
  485. package/src/domains/dispatch/batch-store.ts +134 -0
  486. package/src/domains/dispatch/batch-tracker.ts +66 -0
  487. package/src/domains/dispatch/capability-match.ts +155 -0
  488. package/src/domains/dispatch/capacity-lease.ts +413 -0
  489. package/src/domains/dispatch/code-step-store.ts +52 -0
  490. package/src/domains/dispatch/code-step.ts +387 -0
  491. package/src/domains/dispatch/contract.ts +278 -0
  492. package/src/domains/dispatch/dedup.ts +133 -0
  493. package/src/domains/dispatch/event-pump.ts +251 -0
  494. package/src/domains/dispatch/execution-handoff.ts +29 -0
  495. package/src/domains/dispatch/execution-plan.ts +328 -0
  496. package/src/domains/dispatch/execution-role.ts +277 -0
  497. package/src/domains/dispatch/execution-scheduler.ts +583 -0
  498. package/src/domains/dispatch/extension.ts +6006 -0
  499. package/src/domains/dispatch/failure-classification.ts +147 -0
  500. package/src/domains/dispatch/fleet-commit-attribution.ts +56 -0
  501. package/src/domains/dispatch/fleet-plan.ts +225 -0
  502. package/src/domains/dispatch/fleet-preflight.ts +394 -0
  503. package/src/domains/dispatch/gate-decisions.ts +1040 -0
  504. package/src/domains/dispatch/gate-role-prompts.ts +58 -0
  505. package/src/domains/dispatch/heartbeat.ts +27 -0
  506. package/src/domains/dispatch/index.ts +137 -0
  507. package/src/domains/dispatch/joint-route-adapter.ts +114 -0
  508. package/src/domains/dispatch/joint-route-resolver.ts +283 -0
  509. package/src/domains/dispatch/manifest.ts +6 -0
  510. package/src/domains/dispatch/orphan-recovery.ts +266 -0
  511. package/src/domains/dispatch/outcome.ts +109 -0
  512. package/src/domains/dispatch/phase-timing.ts +47 -0
  513. package/src/domains/dispatch/placement.ts +202 -0
  514. package/src/domains/dispatch/receipt-findings.ts +255 -0
  515. package/src/domains/dispatch/receipt-integrity.ts +383 -0
  516. package/src/domains/dispatch/recovery-candidates.ts +74 -0
  517. package/src/domains/dispatch/reproducibility.ts +48 -0
  518. package/src/domains/dispatch/reservation-store.ts +501 -0
  519. package/src/domains/dispatch/route-approval.ts +177 -0
  520. package/src/domains/dispatch/route-candidates.ts +57 -0
  521. package/src/domains/dispatch/route-decision.ts +1079 -0
  522. package/src/domains/dispatch/route-facts.ts +187 -0
  523. package/src/domains/dispatch/route-history.ts +238 -0
  524. package/src/domains/dispatch/route-observer.ts +488 -0
  525. package/src/domains/dispatch/route-policy.ts +327 -0
  526. package/src/domains/dispatch/route-quality.ts +256 -0
  527. package/src/domains/dispatch/route-readiness.ts +120 -0
  528. package/src/domains/dispatch/routing-intent.ts +287 -0
  529. package/src/domains/dispatch/run-identity.ts +56 -0
  530. package/src/domains/dispatch/scout-transition.ts +130 -0
  531. package/src/domains/dispatch/state.ts +302 -0
  532. package/src/domains/dispatch/tool-stats.ts +132 -0
  533. package/src/domains/dispatch/transport.ts +210 -0
  534. package/src/domains/dispatch/types.ts +799 -0
  535. package/src/domains/dispatch/validation-grounding.ts +154 -0
  536. package/src/domains/dispatch/validation.ts +721 -0
  537. package/src/domains/dispatch/worker-protocol.ts +275 -0
  538. package/src/domains/dispatch/worker-spawn.ts +585 -0
  539. package/src/domains/dispatch/write-boundary-enforcer.ts +97 -0
  540. package/src/domains/dispatch/write-boundary.ts +426 -0
  541. package/src/domains/eval/artifacts/redact.ts +42 -0
  542. package/src/domains/eval/artifacts/store.ts +173 -0
  543. package/src/domains/eval/compare/compare.ts +33 -0
  544. package/src/domains/eval/compare/gates.ts +66 -0
  545. package/src/domains/eval/compare/thresholds.ts +92 -0
  546. package/src/domains/eval/harness-metrics.ts +62 -0
  547. package/src/domains/eval/index.ts +66 -0
  548. package/src/domains/eval/metrics/chaos-stream.ts +93 -0
  549. package/src/domains/eval/metrics/context.ts +34 -0
  550. package/src/domains/eval/metrics/coverage.ts +21 -0
  551. package/src/domains/eval/metrics/evidence.ts +182 -0
  552. package/src/domains/eval/metrics/fleet-loop-stream.ts +196 -0
  553. package/src/domains/eval/metrics/invariants.ts +736 -0
  554. package/src/domains/eval/metrics/latency.ts +4 -0
  555. package/src/domains/eval/metrics/token-stream.ts +139 -0
  556. package/src/domains/eval/metrics/tokens.ts +51 -0
  557. package/src/domains/eval/metrics/tool-calls.ts +7 -0
  558. package/src/domains/eval/proactive-memory.ts +279 -0
  559. package/src/domains/eval/provenance.ts +35 -0
  560. package/src/domains/eval/report.ts +234 -0
  561. package/src/domains/eval/reports/json.ts +5 -0
  562. package/src/domains/eval/reports/junit.ts +23 -0
  563. package/src/domains/eval/reports/markdown.ts +20 -0
  564. package/src/domains/eval/reports/swe-jsonl.ts +16 -0
  565. package/src/domains/eval/reports/text.ts +25 -0
  566. package/src/domains/eval/run-compare.ts +274 -0
  567. package/src/domains/eval/runner.ts +244 -0
  568. package/src/domains/eval/runners/clio-run.ts +148 -0
  569. package/src/domains/eval/runners/context-index.ts +56 -0
  570. package/src/domains/eval/runners/context-init.ts +203 -0
  571. package/src/domains/eval/runners/external-command.ts +350 -0
  572. package/src/domains/eval/runtime-paths.ts +171 -0
  573. package/src/domains/eval/schema/artifact.ts +75 -0
  574. package/src/domains/eval/schema/suite.ts +235 -0
  575. package/src/domains/eval/schema/validate.ts +339 -0
  576. package/src/domains/eval/store.ts +392 -0
  577. package/src/domains/eval/suites/load.ts +74 -0
  578. package/src/domains/eval/suites/matrix.ts +19 -0
  579. package/src/domains/eval/suites/resolve.ts +44 -0
  580. package/src/domains/eval/suites/run.ts +325 -0
  581. package/src/domains/eval/task-file.ts +186 -0
  582. package/src/domains/eval/types.ts +140 -0
  583. package/src/domains/eval/verifiers/command.ts +27 -0
  584. package/src/domains/eval/verifiers/file-exists.ts +6 -0
  585. package/src/domains/eval/verifiers/patch.ts +22 -0
  586. package/src/domains/eval/workspaces/git.ts +41 -0
  587. package/src/domains/eval/workspaces/local.ts +17 -0
  588. package/src/domains/eval/workspaces/temp-copy.ts +29 -0
  589. package/src/domains/evidence/build.ts +1754 -0
  590. package/src/domains/evidence/eval.ts +605 -0
  591. package/src/domains/evidence/failure-attribution.ts +54 -0
  592. package/src/domains/evidence/finish-contract-map.ts +42 -0
  593. package/src/domains/evidence/index.ts +55 -0
  594. package/src/domains/evidence/provenance.ts +200 -0
  595. package/src/domains/evidence/redact.ts +81 -0
  596. package/src/domains/evidence/store.ts +291 -0
  597. package/src/domains/evidence/types.ts +325 -0
  598. package/src/domains/evolution/SELF_EDIT_GATE.md +91 -0
  599. package/src/domains/evolution/index.ts +26 -0
  600. package/src/domains/evolution/manifest.ts +154 -0
  601. package/src/domains/evolution/validate.ts +304 -0
  602. package/src/domains/extensions/contract.ts +22 -0
  603. package/src/domains/extensions/discovery.ts +193 -0
  604. package/src/domains/extensions/extension.ts +46 -0
  605. package/src/domains/extensions/index.ts +34 -0
  606. package/src/domains/extensions/manager.ts +30 -0
  607. package/src/domains/extensions/manifest.ts +6 -0
  608. package/src/domains/extensions/resources.ts +38 -0
  609. package/src/domains/extensions/state.ts +224 -0
  610. package/src/domains/extensions/types.ts +83 -0
  611. package/src/domains/interop/consent.ts +185 -0
  612. package/src/domains/interop/contract.ts +29 -0
  613. package/src/domains/interop/detect.ts +173 -0
  614. package/src/domains/interop/extension.ts +59 -0
  615. package/src/domains/interop/index.ts +34 -0
  616. package/src/domains/interop/manifest.ts +6 -0
  617. package/src/domains/interop/registry.ts +139 -0
  618. package/src/domains/interop/state.ts +92 -0
  619. package/src/domains/interop/types.ts +109 -0
  620. package/src/domains/lifecycle/contract.ts +11 -0
  621. package/src/domains/lifecycle/doctor.ts +536 -0
  622. package/src/domains/lifecycle/extension.ts +23 -0
  623. package/src/domains/lifecycle/index.ts +21 -0
  624. package/src/domains/lifecycle/install-method.ts +51 -0
  625. package/src/domains/lifecycle/manifest.ts +6 -0
  626. package/src/domains/lifecycle/migrations/2026-08-18-lmstudio-runtime-id.ts +52 -0
  627. package/src/domains/lifecycle/migrations/index.ts +114 -0
  628. package/src/domains/lifecycle/state.ts +93 -0
  629. package/src/domains/lifecycle/upgrade-notice.ts +35 -0
  630. package/src/domains/lifecycle/version.ts +35 -0
  631. package/src/domains/memory/index.ts +128 -0
  632. package/src/domains/memory/operations.ts +143 -0
  633. package/src/domains/memory/prompt-section.ts +86 -0
  634. package/src/domains/memory/proposal.ts +180 -0
  635. package/src/domains/memory/store.ts +163 -0
  636. package/src/domains/memory/task-bank.ts +229 -0
  637. package/src/domains/memory/task-memory-handoff.ts +222 -0
  638. package/src/domains/memory/task-memory-policy.ts +602 -0
  639. package/src/domains/memory/task-memory-status.ts +58 -0
  640. package/src/domains/memory/task-memory-telemetry.ts +346 -0
  641. package/src/domains/memory/task-memory-trace.ts +89 -0
  642. package/src/domains/memory/types.ts +74 -0
  643. package/src/domains/memory/validate.ts +297 -0
  644. package/src/domains/middleware/budget.ts +216 -0
  645. package/src/domains/middleware/contract.ts +35 -0
  646. package/src/domains/middleware/dispatch-nudge.ts +469 -0
  647. package/src/domains/middleware/extension.ts +129 -0
  648. package/src/domains/middleware/hook-receipts.ts +70 -0
  649. package/src/domains/middleware/hooks-io.ts +186 -0
  650. package/src/domains/middleware/hooks.ts +432 -0
  651. package/src/domains/middleware/index.ts +154 -0
  652. package/src/domains/middleware/manifest.ts +6 -0
  653. package/src/domains/middleware/memory-intervention.ts +701 -0
  654. package/src/domains/middleware/rules.ts +42 -0
  655. package/src/domains/middleware/runtime.ts +333 -0
  656. package/src/domains/middleware/skills-reminder.ts +202 -0
  657. package/src/domains/middleware/snapshot.ts +71 -0
  658. package/src/domains/middleware/stalled-turn.ts +71 -0
  659. package/src/domains/middleware/task-board-reminder.ts +87 -0
  660. package/src/domains/middleware/task-nudge.ts +77 -0
  661. package/src/domains/middleware/tool-choice-control.ts +42 -0
  662. package/src/domains/middleware/types.ts +131 -0
  663. package/src/domains/middleware/validate.ts +329 -0
  664. package/src/domains/observability/accountability.ts +67 -0
  665. package/src/domains/observability/contract.ts +168 -0
  666. package/src/domains/observability/cost.ts +206 -0
  667. package/src/domains/observability/evidence-index.ts +123 -0
  668. package/src/domains/observability/extension.ts +281 -0
  669. package/src/domains/observability/index.ts +78 -0
  670. package/src/domains/observability/manifest.ts +10 -0
  671. package/src/domains/observability/metrics.ts +37 -0
  672. package/src/domains/observability/projection.ts +477 -0
  673. package/src/domains/observability/telemetry.ts +89 -0
  674. package/src/domains/observability/trace-store.ts +1454 -0
  675. package/src/domains/prompts/compiler.ts +614 -0
  676. package/src/domains/prompts/contract.ts +46 -0
  677. package/src/domains/prompts/extension.ts +336 -0
  678. package/src/domains/prompts/fragment-loader.ts +138 -0
  679. package/src/domains/prompts/fragments/identity/clio.md +5 -6
  680. package/src/domains/prompts/fragments/identity/docs-routing.md +10 -0
  681. package/src/domains/prompts/fragments/identity/self-awareness.md +19 -0
  682. package/src/domains/prompts/fragments/operating/contract.md +4 -41
  683. package/src/domains/prompts/fragments/operating/delegation.md +42 -0
  684. package/src/domains/prompts/fragments/operating/skills.md +26 -0
  685. package/src/domains/prompts/fragments/operating/worker.md +16 -0
  686. package/src/domains/prompts/fragments/safety/auto-edit.md +5 -5
  687. package/src/domains/prompts/fragments/safety/full-auto.md +3 -3
  688. package/src/domains/prompts/fragments/safety/read-only.md +4 -4
  689. package/src/domains/prompts/fragments/safety/suggest.md +2 -2
  690. package/src/domains/prompts/fragments/wiki/page.md +10 -0
  691. package/src/domains/prompts/fragments/wiki/plan.md +10 -0
  692. package/src/domains/prompts/hash.ts +73 -0
  693. package/src/domains/prompts/index.ts +32 -0
  694. package/src/domains/prompts/manifest.ts +8 -0
  695. package/src/domains/prompts/memory-intervention.ts +57 -0
  696. package/src/domains/prompts/preload.ts +82 -0
  697. package/src/domains/providers/auth/api-key.ts +47 -0
  698. package/src/domains/providers/auth/backend-file.ts +79 -0
  699. package/src/domains/providers/auth/backend-memory.ts +72 -0
  700. package/src/domains/providers/auth/boot-status.ts +65 -0
  701. package/src/domains/providers/auth/index.ts +37 -0
  702. package/src/domains/providers/auth/oauth.ts +37 -0
  703. package/src/domains/providers/auth/storage.ts +726 -0
  704. package/src/domains/providers/capabilities.ts +58 -0
  705. package/src/domains/providers/catalog.ts +162 -0
  706. package/src/domains/providers/contract.ts +162 -0
  707. package/src/domains/providers/credentials.ts +20 -0
  708. package/src/domains/providers/eligibility.ts +24 -0
  709. package/src/domains/providers/errors.ts +64 -0
  710. package/src/domains/providers/extension.ts +548 -0
  711. package/src/domains/providers/index.ts +142 -0
  712. package/src/domains/providers/knowledge-base-path.ts +102 -0
  713. package/src/domains/providers/manifest.ts +6 -0
  714. package/src/domains/providers/model-capabilities.ts +136 -0
  715. package/src/domains/providers/model-discovery.ts +144 -0
  716. package/src/domains/providers/model-family.ts +32 -0
  717. package/src/domains/providers/model-runtime-capabilities.ts +723 -0
  718. package/src/domains/providers/models/local-models/clio-local-coding-targets.yaml +120 -37
  719. package/src/domains/providers/plugins.ts +61 -0
  720. package/src/domains/providers/probe/fingerprint.ts +64 -0
  721. package/src/domains/providers/probe/http.ts +114 -0
  722. package/src/domains/providers/probe/reasoning.ts +111 -0
  723. package/src/domains/providers/registry.ts +187 -0
  724. package/src/domains/providers/resolver.ts +156 -0
  725. package/src/domains/providers/runtime-resolution.ts +720 -0
  726. package/src/domains/providers/runtimes/antigravity/antigravity-code.ts +90 -0
  727. package/src/domains/providers/runtimes/boot-manifest.ts +55 -0
  728. package/src/domains/providers/runtimes/builtins.ts +78 -0
  729. package/src/domains/providers/runtimes/claude/claude-code.ts +38 -0
  730. package/src/domains/providers/runtimes/claude/claude-sdk.ts +38 -0
  731. package/src/domains/providers/runtimes/claude/common.ts +55 -0
  732. package/src/domains/providers/runtimes/cloud/alcf.ts +171 -0
  733. package/src/domains/providers/runtimes/cloud/anthropic-max.ts +71 -0
  734. package/src/domains/providers/runtimes/cloud/anthropic.ts +43 -0
  735. package/src/domains/providers/runtimes/cloud/bedrock.ts +35 -0
  736. package/src/domains/providers/runtimes/cloud/deepseek.ts +50 -0
  737. package/src/domains/providers/runtimes/cloud/google.ts +41 -0
  738. package/src/domains/providers/runtimes/cloud/groq.ts +46 -0
  739. package/src/domains/providers/runtimes/cloud/mistral.ts +46 -0
  740. package/src/domains/providers/runtimes/cloud/openai-codex.ts +49 -0
  741. package/src/domains/providers/runtimes/cloud/openai.ts +46 -0
  742. package/src/domains/providers/runtimes/cloud/openrouter.ts +111 -0
  743. package/src/domains/providers/runtimes/common/lmstudio-http.ts +423 -0
  744. package/src/domains/providers/runtimes/common/local-synth.ts +164 -0
  745. package/src/domains/providers/runtimes/common/probe-helpers.ts +474 -0
  746. package/src/domains/providers/runtimes/local-native/lemonade-anthropic.ts +27 -0
  747. package/src/domains/providers/runtimes/local-native/lemonade-openai.ts +26 -0
  748. package/src/domains/providers/runtimes/local-native/llamacpp-anthropic.ts +88 -0
  749. package/src/domains/providers/runtimes/local-native/llamacpp-completion.ts +207 -0
  750. package/src/domains/providers/runtimes/local-native/llamacpp-embed.ts +180 -0
  751. package/src/domains/providers/runtimes/local-native/llamacpp-rerank.ts +126 -0
  752. package/src/domains/providers/runtimes/local-native/llamacpp.ts +98 -0
  753. package/src/domains/providers/runtimes/local-native/lmstudio.ts +241 -0
  754. package/src/domains/providers/runtimes/local-native/ollama-native.ts +117 -0
  755. package/src/domains/providers/runtimes/local-native/sglang.ts +26 -0
  756. package/src/domains/providers/runtimes/local-native/vllm.ts +27 -0
  757. package/src/domains/providers/runtimes/protocol/anthropic-compat.ts +104 -0
  758. package/src/domains/providers/runtimes/protocol/anthropic-messages.ts +28 -0
  759. package/src/domains/providers/runtimes/protocol/bedrock.ts +26 -0
  760. package/src/domains/providers/runtimes/protocol/google.ts +27 -0
  761. package/src/domains/providers/runtimes/protocol/openai-compat.ts +145 -0
  762. package/src/domains/providers/support.ts +241 -0
  763. package/src/domains/providers/thinking-control-policy.ts +85 -0
  764. package/src/domains/providers/types/capability-flags.ts +114 -0
  765. package/src/domains/providers/types/cost-provenance.ts +7 -0
  766. package/src/domains/providers/types/inference.ts +41 -0
  767. package/src/domains/providers/types/knowledge-base.ts +156 -0
  768. package/src/domains/providers/types/local-model-quirks.ts +216 -0
  769. package/src/domains/providers/types/runtime-descriptor.ts +181 -0
  770. package/src/domains/providers/types/target-descriptor.ts +60 -0
  771. package/src/domains/resources/collision.ts +92 -0
  772. package/src/domains/resources/common-loader.ts +140 -0
  773. package/src/domains/resources/contract.ts +24 -0
  774. package/src/domains/resources/extension.ts +59 -0
  775. package/src/domains/resources/index.ts +74 -0
  776. package/src/domains/resources/loader.ts +85 -0
  777. package/src/domains/resources/manifest.ts +6 -0
  778. package/src/domains/resources/prompts/loader.ts +246 -0
  779. package/src/domains/resources/prompts/substitute.ts +1 -0
  780. package/src/domains/resources/skills/content-hash.ts +107 -0
  781. package/src/domains/resources/skills/evals.ts +175 -0
  782. package/src/domains/resources/skills/install.ts +427 -0
  783. package/src/domains/resources/skills/loader.ts +985 -0
  784. package/src/domains/resources/skills/marketplace.ts +301 -0
  785. package/src/domains/resources/skills/provenance-pin.ts +115 -0
  786. package/src/domains/safety/action-classifier.ts +270 -0
  787. package/src/domains/safety/approval-axis.ts +23 -0
  788. package/src/domains/safety/audit.ts +493 -0
  789. package/src/domains/safety/autonomy.ts +174 -0
  790. package/src/domains/safety/call-target.ts +59 -0
  791. package/src/domains/safety/contract.ts +65 -0
  792. package/src/domains/safety/damage-control.ts +44 -0
  793. package/src/domains/safety/default-path-policy.ts +117 -0
  794. package/src/domains/safety/extension.ts +383 -0
  795. package/src/domains/safety/finish-contract-registration.ts +128 -0
  796. package/src/domains/safety/finish-contract.ts +554 -0
  797. package/src/domains/safety/index.ts +44 -0
  798. package/src/domains/safety/loop-detector.ts +143 -0
  799. package/src/domains/safety/manifest.ts +6 -0
  800. package/src/domains/safety/path-policy.ts +164 -0
  801. package/src/domains/safety/policy-engine.ts +994 -0
  802. package/src/domains/safety/project-policy.ts +391 -0
  803. package/src/domains/safety/protected-artifacts-registration.ts +189 -0
  804. package/src/domains/safety/protected-artifacts.ts +993 -0
  805. package/src/domains/safety/rejection-feedback.ts +102 -0
  806. package/src/domains/safety/rigor.ts +72 -0
  807. package/src/domains/safety/rule-compiler.ts +48 -0
  808. package/src/domains/safety/rule-pack-loader.ts +92 -0
  809. package/src/domains/safety/run-effects.ts +197 -0
  810. package/src/domains/safety/scope.ts +61 -0
  811. package/src/domains/scheduling/budget.ts +35 -0
  812. package/src/domains/scheduling/cluster.ts +175 -0
  813. package/src/domains/scheduling/contract.ts +30 -0
  814. package/src/domains/scheduling/extension.ts +91 -0
  815. package/src/domains/scheduling/index.ts +12 -0
  816. package/src/domains/scheduling/manifest.ts +6 -0
  817. package/src/domains/session/archive-readers.ts +208 -0
  818. package/src/domains/session/checkpoint.ts +38 -0
  819. package/src/domains/session/compaction/auto.ts +89 -0
  820. package/src/domains/session/compaction/branch-summary.ts +187 -0
  821. package/src/domains/session/compaction/compact.ts +493 -0
  822. package/src/domains/session/compaction/cut-point.ts +172 -0
  823. package/src/domains/session/compaction/defaults.ts +34 -0
  824. package/src/domains/session/compaction/mask-observations.ts +265 -0
  825. package/src/domains/session/compaction/session-entries.ts +18 -0
  826. package/src/domains/session/compaction/tokens.ts +148 -0
  827. package/src/domains/session/context-accounting.ts +583 -0
  828. package/src/domains/session/context-ledger.ts +362 -0
  829. package/src/domains/session/contract.ts +159 -0
  830. package/src/domains/session/cwd-fallback.ts +65 -0
  831. package/src/domains/session/decision-board.ts +190 -0
  832. package/src/domains/session/entries.ts +572 -0
  833. package/src/domains/session/extension.ts +403 -0
  834. package/src/domains/session/history.ts +172 -0
  835. package/src/domains/session/index.ts +84 -0
  836. package/src/domains/session/manager.ts +128 -0
  837. package/src/domains/session/manifest.ts +6 -0
  838. package/src/domains/session/migrations/index.ts +27 -0
  839. package/src/domains/session/prompt-manifest.ts +173 -0
  840. package/src/domains/session/protected-artifact-journal.ts +241 -0
  841. package/src/domains/session/protected-artifacts.ts +90 -0
  842. package/src/domains/session/retry.ts +230 -0
  843. package/src/domains/session/session-artifacts.ts +107 -0
  844. package/src/domains/session/task-board.ts +569 -0
  845. package/src/domains/session/tree/active-path.ts +137 -0
  846. package/src/domains/session/tree/fork.ts +159 -0
  847. package/src/domains/session/tree/manager.ts +128 -0
  848. package/src/domains/session/tree/navigator.ts +177 -0
  849. package/src/domains/session/tree/preview.ts +259 -0
  850. package/src/domains/session/usage.ts +148 -0
  851. package/src/domains/session/workspace/git-probe.ts +167 -0
  852. package/src/domains/session/workspace/index.ts +4 -0
  853. package/src/domains/session/workspace/project-type.ts +217 -0
  854. package/src/domains/session/workspace/snapshot.ts +74 -0
  855. package/src/domains/share/archive.ts +684 -0
  856. package/src/domains/share/contract.ts +9 -0
  857. package/src/domains/share/extension.ts +26 -0
  858. package/src/domains/share/index.ts +28 -0
  859. package/src/domains/share/manifest.ts +6 -0
  860. package/src/domains/user-tasks/store.ts +297 -0
  861. package/src/engine/acp/adapter.ts +397 -0
  862. package/src/engine/acp/errors.ts +128 -0
  863. package/src/engine/acp/event-mapper.ts +154 -0
  864. package/src/engine/acp/server.ts +2238 -0
  865. package/src/engine/acp/tool-mediator.ts +686 -0
  866. package/src/engine/acp/transport.ts +775 -0
  867. package/src/engine/acp/types.ts +196 -0
  868. package/src/engine/agent.ts +29 -0
  869. package/src/engine/ai.ts +207 -0
  870. package/src/engine/alcf-oauth.ts +205 -0
  871. package/src/engine/antigravity/subprocess-runtime.ts +205 -0
  872. package/src/engine/api-registry.ts +223 -0
  873. package/src/engine/apis/degraded-inference.ts +95 -0
  874. package/src/engine/apis/index.ts +16 -0
  875. package/src/engine/apis/llamacpp-residency.ts +292 -0
  876. package/src/engine/apis/lmstudio-residency.ts +76 -0
  877. package/src/engine/apis/lmstudio.ts +243 -0
  878. package/src/engine/apis/ollama-native.ts +528 -0
  879. package/src/engine/apis/openai-completions.ts +795 -0
  880. package/src/engine/apis/output-budget.ts +99 -0
  881. package/src/engine/apis/residency-lock.ts +29 -0
  882. package/src/engine/apis/residency.ts +776 -0
  883. package/src/engine/apis/resident-models.ts +42 -0
  884. package/src/engine/apis/sampling-overrides.ts +38 -0
  885. package/src/engine/apis/types.ts +16 -0
  886. package/src/engine/claude/sdk-runtime.ts +685 -0
  887. package/src/engine/claude/session-id.ts +5 -0
  888. package/src/engine/claude/subprocess-runtime.ts +316 -0
  889. package/src/engine/claude/tool-safety.ts +400 -0
  890. package/src/engine/env-api-keys.ts +98 -0
  891. package/src/engine/external-subprocess.ts +23 -0
  892. package/src/engine/gemma-channel-filter.ts +223 -0
  893. package/src/engine/harmony-response.ts +165 -0
  894. package/src/engine/instrumented-tui.ts +192 -0
  895. package/src/engine/loop-guard.ts +1127 -0
  896. package/src/engine/messages.ts +14 -0
  897. package/src/engine/models.ts +42 -0
  898. package/src/engine/oauth.ts +196 -0
  899. package/src/engine/pi-mono-names.ts +13 -0
  900. package/src/engine/prompt-templates.ts +1 -0
  901. package/src/engine/provider-payload.ts +272 -0
  902. package/src/engine/select-list.ts +53 -0
  903. package/src/engine/session.ts +834 -0
  904. package/src/engine/strip-tokenizer-sentinels.ts +125 -0
  905. package/src/engine/truncate.ts +9 -0
  906. package/src/engine/tui.ts +111 -0
  907. package/src/engine/types.ts +63 -0
  908. package/src/engine/worker-events.ts +100 -0
  909. package/src/engine/worker-runtime-capabilities.ts +5 -0
  910. package/src/engine/worker-runtime.ts +1022 -0
  911. package/src/engine/worker-tools.ts +208 -0
  912. package/src/entry/boot-options.ts +50 -0
  913. package/src/entry/orchestrator.ts +2011 -0
  914. package/src/interactive/application-controller.ts +433 -0
  915. package/src/interactive/bus-notices.ts +265 -0
  916. package/src/interactive/chat-loop-messages.ts +626 -0
  917. package/src/interactive/chat-loop-policy.ts +18 -0
  918. package/src/interactive/chat-loop.ts +1090 -0
  919. package/src/interactive/chat-panel.ts +1820 -0
  920. package/src/interactive/chat-renderer.ts +1228 -0
  921. package/src/interactive/clio-editor.ts +161 -0
  922. package/src/interactive/command-fallbacks.ts +23 -0
  923. package/src/interactive/command-output.ts +85 -0
  924. package/src/interactive/context-activity.ts +203 -0
  925. package/src/interactive/context-meter.ts +223 -0
  926. package/src/interactive/context-overlay.ts +246 -0
  927. package/src/interactive/cost-overlay.ts +279 -0
  928. package/src/interactive/dispatch-board.ts +1319 -0
  929. package/src/interactive/dispatch-steering.ts +86 -0
  930. package/src/interactive/editor-bash.ts +46 -0
  931. package/src/interactive/editor-steer.ts +51 -0
  932. package/src/interactive/editor-submit.ts +507 -0
  933. package/src/interactive/export-html/ansi-to-html.ts +161 -0
  934. package/src/interactive/export-html/index.ts +51 -0
  935. package/src/interactive/export-html/template.ts +45 -0
  936. package/src/interactive/export-html/tool-renderer.ts +54 -0
  937. package/src/interactive/external-editor.ts +67 -0
  938. package/src/interactive/follow-up-queue-panel.ts +75 -0
  939. package/src/interactive/footer/dashboard.ts +538 -0
  940. package/src/interactive/footer/notifications.ts +275 -0
  941. package/src/interactive/footer/widgets.ts +1264 -0
  942. package/src/interactive/footer-panel.ts +187 -0
  943. package/src/interactive/format-time.ts +97 -0
  944. package/src/interactive/index.ts +11 -0
  945. package/src/interactive/interactive-application.ts +939 -0
  946. package/src/interactive/interactive-event-projection.ts +385 -0
  947. package/src/interactive/interactive-input-runtime.ts +307 -0
  948. package/src/interactive/interactive-presentation.ts +584 -0
  949. package/src/interactive/interactive-shell.ts +388 -0
  950. package/src/interactive/interactive-slash-runtime.ts +554 -0
  951. package/src/interactive/interactive-subscriptions.ts +111 -0
  952. package/src/interactive/interactive-tickers.ts +183 -0
  953. package/src/interactive/keybinding-manager.ts +496 -0
  954. package/src/interactive/layout.ts +57 -0
  955. package/src/interactive/leader-key.ts +143 -0
  956. package/src/interactive/loop-guard-interrupt.ts +101 -0
  957. package/src/interactive/memory-overlay.ts +280 -0
  958. package/src/interactive/overlay-ask-user-lifecycle.ts +110 -0
  959. package/src/interactive/overlay-auth-lifecycle.ts +308 -0
  960. package/src/interactive/overlay-frame.ts +502 -0
  961. package/src/interactive/overlay-general-openers.ts +274 -0
  962. package/src/interactive/overlay-key-routing.ts +216 -0
  963. package/src/interactive/overlay-lifecycle.ts +426 -0
  964. package/src/interactive/overlay-model-selectors.ts +127 -0
  965. package/src/interactive/overlay-permission-lifecycle.ts +345 -0
  966. package/src/interactive/overlay-resource-openers.ts +112 -0
  967. package/src/interactive/overlay-session-lifecycle.ts +327 -0
  968. package/src/interactive/overlay-transitions.ts +57 -0
  969. package/src/interactive/overlays/agents.ts +69 -0
  970. package/src/interactive/overlays/ask-user.ts +921 -0
  971. package/src/interactive/overlays/auth-dialog.ts +221 -0
  972. package/src/interactive/overlays/context-reset.ts +108 -0
  973. package/src/interactive/overlays/cwd-fallback.ts +141 -0
  974. package/src/interactive/overlays/decisions.ts +300 -0
  975. package/src/interactive/overlays/extensions.ts +54 -0
  976. package/src/interactive/overlays/help-reference.ts +145 -0
  977. package/src/interactive/overlays/interop.ts +139 -0
  978. package/src/interactive/overlays/keybinding-detail.ts +90 -0
  979. package/src/interactive/overlays/list-overlay.ts +606 -0
  980. package/src/interactive/overlays/message-picker.ts +155 -0
  981. package/src/interactive/overlays/model-selector.ts +1109 -0
  982. package/src/interactive/overlays/prompts.ts +72 -0
  983. package/src/interactive/overlays/session-selector-search.ts +31 -0
  984. package/src/interactive/overlays/session-selector.ts +284 -0
  985. package/src/interactive/overlays/settings.ts +3451 -0
  986. package/src/interactive/overlays/skills-hub.ts +237 -0
  987. package/src/interactive/overlays/tree-selector.ts +422 -0
  988. package/src/interactive/permission-overlay.ts +170 -0
  989. package/src/interactive/render-trace.ts +534 -0
  990. package/src/interactive/renderers/branch-summary.ts +86 -0
  991. package/src/interactive/renderers/code-ink.ts +420 -0
  992. package/src/interactive/renderers/compaction-summary.ts +112 -0
  993. package/src/interactive/renderers/diff.ts +137 -0
  994. package/src/interactive/renderers/mermaid.ts +53 -0
  995. package/src/interactive/renderers/notice.ts +28 -0
  996. package/src/interactive/renderers/retry-status.ts +31 -0
  997. package/src/interactive/renderers/structured.ts +89 -0
  998. package/src/interactive/renderers/tool-execution.ts +1357 -0
  999. package/src/interactive/renderers/worker-entry.ts +280 -0
  1000. package/src/interactive/session-last-turn.ts +186 -0
  1001. package/src/interactive/session-switch-settlement.ts +10 -0
  1002. package/src/interactive/session-transcript.ts +105 -0
  1003. package/src/interactive/session-usage-reseed.ts +74 -0
  1004. package/src/interactive/slash-autocomplete.ts +256 -0
  1005. package/src/interactive/slash-commands.ts +1407 -0
  1006. package/src/interactive/slash-spec.ts +424 -0
  1007. package/src/interactive/status/controller.ts +260 -0
  1008. package/src/interactive/status/index.ts +23 -0
  1009. package/src/interactive/status/state-machine.ts +530 -0
  1010. package/src/interactive/status/summary.ts +170 -0
  1011. package/src/interactive/status/types.ts +121 -0
  1012. package/src/interactive/status/verbs.ts +163 -0
  1013. package/src/interactive/status/watchdog.ts +24 -0
  1014. package/src/interactive/stdout-backpressure.ts +99 -0
  1015. package/src/interactive/stream-pacer.ts +530 -0
  1016. package/src/interactive/stream-pacing-policy.ts +66 -0
  1017. package/src/interactive/tasks-overlay.ts +540 -0
  1018. package/src/interactive/terminal-lease.ts +485 -0
  1019. package/src/interactive/theme/components.ts +55 -0
  1020. package/src/interactive/theme/glyphs.ts +70 -0
  1021. package/src/interactive/theme/index.ts +15 -0
  1022. package/src/interactive/theme/labels.ts +88 -0
  1023. package/src/interactive/theme/rules.ts +138 -0
  1024. package/src/interactive/theme/segments.ts +88 -0
  1025. package/src/interactive/theme/tokens.ts +121 -0
  1026. package/src/interactive/thinking-level-policy.ts +6 -0
  1027. package/src/interactive/tool-prose-loop.ts +122 -0
  1028. package/src/interactive/tool-prose-registration.ts +48 -0
  1029. package/src/interactive/turn-context.ts +855 -0
  1030. package/src/interactive/turn-middleware.ts +283 -0
  1031. package/src/interactive/turn-persistence.ts +458 -0
  1032. package/src/interactive/turn-queues.ts +181 -0
  1033. package/src/interactive/turn-recovery.ts +323 -0
  1034. package/src/interactive/turn-runtime.ts +869 -0
  1035. package/src/interactive/turn-state.ts +133 -0
  1036. package/src/interactive/view/artifacts.ts +1452 -0
  1037. package/src/interactive/view/view-overlay.ts +735 -0
  1038. package/src/interactive/welcome-dashboard.ts +537 -0
  1039. package/src/interactive/worker-receipts.ts +143 -0
  1040. package/src/interactive/worker-replay.ts +111 -0
  1041. package/src/interactive/worker-share.ts +100 -0
  1042. package/src/interactive/worker-stream.ts +446 -0
  1043. package/src/interactive/workspace-facts.ts +141 -0
  1044. package/src/tools/agent-tools.ts +402 -0
  1045. package/src/tools/artifact.ts +103 -0
  1046. package/src/tools/ask-user.ts +666 -0
  1047. package/src/tools/bash.ts +204 -0
  1048. package/src/tools/bootstrap.ts +77 -0
  1049. package/src/tools/builtin-tool-catalog.ts +265 -0
  1050. package/src/tools/codewiki/code-nav-surface.ts +29 -0
  1051. package/src/tools/codewiki/code-nav.ts +549 -0
  1052. package/src/tools/codewiki/shared.ts +118 -0
  1053. package/src/tools/compete-worktrees.ts +898 -0
  1054. package/src/tools/context/docs-engine.ts +630 -0
  1055. package/src/tools/context/index.ts +514 -0
  1056. package/src/tools/context/surface.ts +19 -0
  1057. package/src/tools/core-bootstrap.ts +168 -0
  1058. package/src/tools/credential-present.ts +151 -0
  1059. package/src/tools/dispatch-admission.ts +533 -0
  1060. package/src/tools/dispatch-arguments.ts +228 -0
  1061. package/src/tools/dispatch-background.ts +54 -0
  1062. package/src/tools/dispatch-event-text.ts +6 -0
  1063. package/src/tools/dispatch-plan.ts +746 -0
  1064. package/src/tools/dispatch-run-events.ts +238 -0
  1065. package/src/tools/dispatch-runner.ts +2370 -0
  1066. package/src/tools/dispatch-scout-admission.ts +295 -0
  1067. package/src/tools/dispatch-scout.ts +461 -0
  1068. package/src/tools/dispatch-types.ts +77 -0
  1069. package/src/tools/dispatch.ts +244 -0
  1070. package/src/tools/edit-diff.ts +534 -0
  1071. package/src/tools/edit.ts +128 -0
  1072. package/src/tools/executables.ts +22 -0
  1073. package/src/tools/file-mutation-queue.ts +35 -0
  1074. package/src/tools/find.ts +294 -0
  1075. package/src/tools/grep.ts +495 -0
  1076. package/src/tools/ignore-policy.ts +169 -0
  1077. package/src/tools/lazy-tool.ts +60 -0
  1078. package/src/tools/ledger.ts +206 -0
  1079. package/src/tools/ls.ts +110 -0
  1080. package/src/tools/monitor-surface.ts +36 -0
  1081. package/src/tools/monitor.ts +767 -0
  1082. package/src/tools/network-policy.ts +29 -0
  1083. package/src/tools/observation.ts +421 -0
  1084. package/src/tools/observers.ts +99 -0
  1085. package/src/tools/path-utils.ts +79 -0
  1086. package/src/tools/policy.ts +154 -0
  1087. package/src/tools/profiles.ts +108 -0
  1088. package/src/tools/read.ts +173 -0
  1089. package/src/tools/registry.ts +1169 -0
  1090. package/src/tools/result-shaping.ts +204 -0
  1091. package/src/tools/safe-exec.ts +128 -0
  1092. package/src/tools/spawn-hygiene.ts +182 -0
  1093. package/src/tools/steer-surface.ts +17 -0
  1094. package/src/tools/steer.ts +77 -0
  1095. package/src/tools/tasks.ts +265 -0
  1096. package/src/tools/truncate-utf8.ts +17 -0
  1097. package/src/tools/truncate.ts +43 -0
  1098. package/src/tools/verify/frontend.ts +687 -0
  1099. package/src/tools/verify/index.ts +31 -0
  1100. package/src/tools/verify/scripts.ts +108 -0
  1101. package/src/tools/verify/surface.ts +46 -0
  1102. package/src/tools/web-fetch-surface.ts +23 -0
  1103. package/src/tools/web-fetch.ts +759 -0
  1104. package/src/tools/worker-evidence.ts +215 -0
  1105. package/src/tools/write.ts +57 -0
  1106. package/src/utils/exif-orientation.ts +192 -0
  1107. package/src/utils/git.ts +24 -0
  1108. package/src/utils/image-resize.ts +148 -0
  1109. package/src/utils/photon.ts +103 -0
  1110. package/src/worker/control-lane.ts +22 -0
  1111. package/src/worker/entry.ts +294 -0
  1112. package/src/worker/event-projection.ts +56 -0
  1113. package/src/worker/heartbeat.ts +24 -0
  1114. package/src/worker/ledger-mirror.ts +95 -0
  1115. package/src/worker/ndjson.ts +40 -0
  1116. package/src/worker/protocol.ts +529 -0
  1117. package/src/worker/resource-facts.ts +79 -0
  1118. package/src/worker/runtime-registry.ts +12 -0
  1119. package/src/worker/spec-contract.ts +675 -0
  1120. package/src/worker/stdin-demux.ts +316 -0
  1121. package/dist/acp-UMLFVA3F.js +0 -92
  1122. package/dist/chunk-262G75JS.js +0 -35
  1123. package/dist/chunk-6CWDTGUC.js +0 -20
  1124. package/dist/chunk-BYEU76JP.js +0 -899
  1125. package/dist/chunk-GNGDQYDU.js +0 -34688
  1126. package/dist/chunk-GOTUR54M.js +0 -9
  1127. package/dist/chunk-NI4DEJMC.js +0 -88
  1128. package/dist/chunk-TPOCL34A.js +0 -2876
  1129. package/dist/chunk-UU3R62TT.js +0 -128
  1130. package/dist/clio-KIQ5SNDS.js +0 -53
  1131. package/dist/configure-G4E3A2PG.js +0 -27
  1132. package/dist/doctor-M5HJJZOL.js +0 -61
  1133. package/dist/domains/agents/builtins/architect.md +0 -33
  1134. package/dist/domains/agents/builtins/coder.md +0 -31
  1135. package/dist/domains/agents/builtins/context-bootstrap.md +0 -38
  1136. package/dist/domains/agents/builtins/debugger.md +0 -30
  1137. package/dist/domains/agents/builtins/documenter.md +0 -31
  1138. package/dist/domains/agents/builtins/git-master.md +0 -30
  1139. package/dist/domains/agents/builtins/provenance.md +0 -30
  1140. package/dist/domains/agents/builtins/researcher.md +0 -71
  1141. package/dist/domains/agents/builtins/scout.md +0 -42
  1142. package/dist/domains/agents/builtins/tester.md +0 -31
  1143. package/dist/domains/agents/builtins/verifier.md +0 -30
  1144. package/dist/domains/agents/builtins/wiki-writer.md +0 -41
  1145. package/dist/fleet-preflight-CLIAX7YR.js +0 -21
  1146. package/dist/preload-KZVHET2B.js +0 -11
  1147. package/dist/version-ROZ6CZKH.js +0 -16
  1148. package/docs/html/agents_blueprint.html +0 -936
  1149. package/docs/html/alcf_blueprint.html +0 -324
  1150. package/docs/html/architecture_blueprint.html +0 -850
  1151. package/docs/html/commands_blueprint.html +0 -794
  1152. package/docs/html/config_knobs_audit_blueprint.html +0 -178
  1153. package/docs/html/configuration_blueprint.html +0 -1080
  1154. package/docs/html/context_blueprint.html +0 -603
  1155. package/docs/html/documentation_blueprint.html +0 -832
  1156. package/docs/html/environment_blueprint.html +0 -404
  1157. package/docs/html/eval_blueprint.html +0 -743
  1158. package/docs/html/evals_internal_blueprint.html +0 -190
  1159. package/docs/html/evolution_blueprint.html +0 -674
  1160. package/docs/html/extensions_blueprint.html +0 -2065
  1161. package/docs/html/fleet_dispatch_blueprint.html +0 -286
  1162. package/docs/html/index.html +0 -919
  1163. package/docs/html/lifecycle_blueprint.html +0 -723
  1164. package/docs/html/memory_blueprint.html +0 -699
  1165. package/docs/html/middleware_blueprint.html +0 -664
  1166. package/docs/html/models_blueprint.html +0 -2366
  1167. package/docs/html/observability_blueprint.html +0 -683
  1168. package/docs/html/provider_adapter_blueprint.html +0 -245
  1169. package/docs/html/safety_blueprint.html +0 -1386
  1170. package/docs/html/shared.css +0 -571
  1171. package/docs/html/shared.js +0 -143
  1172. package/docs/html/skills_blueprint.html +0 -671
  1173. package/docs/html/soak_blueprint.html +0 -182
  1174. package/docs/html/tool_usage_blueprint.html +0 -350
  1175. package/docs/html/tools_blueprint.html +0 -2249
  1176. package/docs/html/trace_blueprint.html +0 -235
  1177. package/docs/html/tui_design_blueprint.html +0 -314
  1178. package/docs/html/validation_blueprint.html +0 -961
  1179. package/docs/html/worker_dispatch_blueprint.html +0 -231
  1180. package/skills/git/commit-crafting/SKILL.md +0 -79
  1181. package/skills/git/commit-crafting/evals.md +0 -92
  1182. package/skills/git/create-pr/SKILL.md +0 -116
  1183. package/skills/git/create-pr/evals.md +0 -114
  1184. package/skills/git/investigate-issue/SKILL.md +0 -139
  1185. package/skills/git/investigate-issue/evals.md +0 -94
  1186. package/skills/git/review-changes/SKILL.md +0 -103
  1187. package/skills/git/review-changes/evals.md +0 -85
@@ -0,0 +1,2238 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { realpathSync } from "node:fs";
3
+ import { isAbsolute, resolve as resolvePath } from "node:path";
4
+ import { BusChannels, type LoopBlockedPayload } from "../../core/bus-events.js";
5
+ import { DEFAULT_DELEGATION_PERMISSION_TIMEOUT_MS } from "../../core/defaults.js";
6
+ import type { SafeEventBus } from "../../core/event-bus.js";
7
+ import { MAX_TIMER_DELAY_MS } from "../../core/timers.js";
8
+ import type { ProvidersContract } from "../../domains/providers/contract.js";
9
+ import { isOrchestratorEligibleRuntime } from "../../domains/providers/eligibility.js";
10
+ import { type AutonomyLevel, DEFAULT_AUTONOMY_LEVEL } from "../../domains/safety/autonomy.js";
11
+ import type { SessionContract, SessionMeta } from "../../domains/session/contract.js";
12
+ import type { MessageEntry, SessionEntry } from "../../domains/session/entries.js";
13
+ import { filterEntriesToActivePath } from "../../domains/session/tree/active-path.js";
14
+ import type { ToolRegistry } from "../../tools/registry.js";
15
+ import type { AgentMessage } from "../types.js";
16
+ import { ACP_TURN_FAILED_MESSAGE, AcpRequestError, AcpTimeoutError, acpErrorMessage } from "./errors.js";
17
+ import type { AcpJsonRpcPeerTransport } from "./transport.js";
18
+ import type {
19
+ AcpContentBlock,
20
+ AcpInitializeResponse,
21
+ AcpPromptResponse,
22
+ AcpRequestPermissionResponse,
23
+ AcpSessionUpdateParams,
24
+ AcpToolCallLocation,
25
+ AcpToolCallStatus,
26
+ AcpToolKind,
27
+ } from "./types.js";
28
+ import {
29
+ ACP_MAX_CHUNK_BYTES,
30
+ ACP_MAX_RAW_RECORD_BYTES,
31
+ ACP_MAX_STRING_BYTES,
32
+ ACP_MAX_TOOL_CALL_ID_BYTES,
33
+ ACP_SESSION_META_KEY,
34
+ ACP_USAGE_META_KEY,
35
+ } from "./types.js";
36
+
37
+ type AcpServerEvent = unknown;
38
+ type AcpEventRecord = Record<string, unknown> & { type?: unknown };
39
+
40
+ export interface AcpServerChat {
41
+ submit(text: string, options?: unknown): Promise<void>;
42
+ cancel(): void;
43
+ onEvent(handler: (event: AcpServerEvent) => void): () => void;
44
+ isStreaming(): boolean;
45
+ getSessionId(): string | null;
46
+ /** Replace provider context and the next persisted parent after session/load. */
47
+ resetForSession?(leafTurnId: string | null, replayMessages?: ReadonlyArray<AgentMessage>): void;
48
+ dispose?(): void;
49
+ }
50
+
51
+ export interface AcpRoutingSnapshot {
52
+ target: string | null;
53
+ model: string | null;
54
+ }
55
+
56
+ export type AcpThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
57
+
58
+ export interface AcpSafeSettingsSnapshot extends AcpRoutingSnapshot {
59
+ thinkingLevel: AcpThinkingLevel;
60
+ autonomy: AutonomyLevel;
61
+ }
62
+
63
+ export type AcpSafeSettingsPatch = Partial<{
64
+ "orchestrator.target": string | null;
65
+ "orchestrator.model": string | null;
66
+ "orchestrator.thinkingLevel": AcpThinkingLevel;
67
+ autonomy: AutonomyLevel;
68
+ }>;
69
+
70
+ export interface AcpSettingsControl {
71
+ read(): AcpSafeSettingsSnapshot;
72
+ commit(patch: AcpSafeSettingsPatch): AcpSafeSettingsSnapshot;
73
+ }
74
+
75
+ export interface ClioAcpServerOptions {
76
+ transport: AcpJsonRpcPeerTransport;
77
+ chat: AcpServerChat;
78
+ session?: SessionContract;
79
+ providers?: ProvidersContract;
80
+ settings?: AcpSettingsControl;
81
+ toolRegistry?: ToolRegistry;
82
+ bus?: SafeEventBus;
83
+ autonomy?: () => AutonomyLevel;
84
+ /** Initial effective next-turn route captured when a session is bound. */
85
+ routing?: () => AcpRoutingSnapshot;
86
+ /** Durable rich-entry reader used by standard session/load. */
87
+ readSessionEntries?: (sessionId: string) => ReadonlyArray<SessionEntry>;
88
+ /** Builds the provider context; this is deliberately separate from client replay. */
89
+ buildReplayMessages?: (entries: ReadonlyArray<SessionEntry>, leafTurnId: string | null) => ReadonlyArray<AgentMessage>;
90
+ onActiveSessionAutonomyChange?: (level: AutonomyLevel | null) => void;
91
+ cwd?: string;
92
+ version?: string;
93
+ permissionTimeoutMs?: number;
94
+ /**
95
+ * Where text this process did not author goes instead of the wire. Stdout is
96
+ * JSON-RPC only, so the operator-facing detail behind a failure lands on the
97
+ * unstructured stderr tail (CONTRACT C001 §6). Defaults to dropping it.
98
+ */
99
+ diagnostics?: (line: string) => void;
100
+ }
101
+
102
+ interface AcpServerSession {
103
+ id: string;
104
+ cwd: string;
105
+ autonomy: AutonomyLevel;
106
+ autonomySource: "settings" | "session";
107
+ target: string | null;
108
+ model: string | null;
109
+ createdAt: string;
110
+ activePrompt: ActivePrompt | null;
111
+ }
112
+
113
+ interface ActivePrompt {
114
+ cancelled: boolean;
115
+ /** The server approval ceiling won; distinct from operator cancellation/denial. */
116
+ permissionExpired: boolean;
117
+ /** The ACP presentation ceiling won before a 129th tool call reached the wire. */
118
+ toolCallLimitReached: boolean;
119
+ errored: boolean;
120
+ errorMessage?: string;
121
+ /** Machine-readable reason from the first admission notice of this turn. */
122
+ admissionReason?: string;
123
+ sentAssistantChars: number;
124
+ sentThinkingChars: number;
125
+ /** `session/update` notifications emitted for this turn. */
126
+ updatesSent: number;
127
+ /** True once the engine reported a turn end (`message_end` or `agent_end`). */
128
+ sawTurnEnd: boolean;
129
+ stopReason: string;
130
+ usage: AcpServerUsage;
131
+ usageMessages: WeakSet<object>;
132
+ /**
133
+ * Engine tool-call id -> every wire id it has been given this turn, oldest
134
+ * first. An engine that reuses one id for two calls gets a second wire id
135
+ * rather than a second call folded onto the first.
136
+ */
137
+ toolCallWireIds: Map<string, string[]>;
138
+ /** Every wire id this turn has handed out, literal engine ids and aliases alike. */
139
+ usedWireIds: Set<string>;
140
+ /** Wire ids that received `tool_call` and no terminal `tool_call_update`. */
141
+ openToolCalls: Set<string>;
142
+ /**
143
+ * Wire ids that have already received a terminal `tool_call_update`, the
144
+ * cancel/fail sweep included. A client renders the first terminal update it
145
+ * sees and a second one for the same id either resurrects a finished call or
146
+ * overwrites its result, so a terminal id is never updated again.
147
+ */
148
+ terminalToolCalls: Set<string>;
149
+ /**
150
+ * Wire id -> the exact frame material that id's `tool_call` carried. A
151
+ * permission request reuses this instead of deriving anything from the
152
+ * registry's copy of the call: a tool's `prepareAdmissionArguments` may
153
+ * normalize or wholly replace the arguments before the safety net sees them,
154
+ * so the two frames legitimately disagreed and a client diffing the call it
155
+ * rendered against the call it is asked to approve failed closed. One entry
156
+ * per emitted call, each bounded by the raw-record cap, so the map is bounded
157
+ * by the turn's tool calls and dies with the turn.
158
+ */
159
+ toolCallSnapshots: Map<string, AcpToolCallSnapshot>;
160
+ /** Most recent wire id a `tool_call` was actually emitted for, open or not. */
161
+ lastEmittedToolCallId: string | null;
162
+ toolCallSequence: number;
163
+ }
164
+
165
+ /** What one emitted `tool_call` put on the wire that its permission request must repeat. */
166
+ interface AcpToolCallSnapshot {
167
+ rawInput: Record<string, unknown>;
168
+ locations?: AcpToolCallLocation[];
169
+ }
170
+
171
+ interface AcpServerUsage {
172
+ input: number;
173
+ output: number;
174
+ cacheRead: number;
175
+ cacheWrite: number;
176
+ reasoning: number;
177
+ }
178
+
179
+ function isRecord(value: unknown): value is Record<string, unknown> {
180
+ return typeof value === "object" && value !== null && !Array.isArray(value);
181
+ }
182
+
183
+ function eventRecord(value: unknown): AcpEventRecord {
184
+ return isRecord(value) ? value : {};
185
+ }
186
+
187
+ function textContent(text: string): { type: "text"; text: string } {
188
+ return { type: "text", text };
189
+ }
190
+
191
+ /**
192
+ * Maps Clio canonical tool names onto the ACP v1 `ToolKind` closed enum. The
193
+ * kind is a UI hint; the human-readable tool name travels in `title`. Anything
194
+ * unrecognised (dynamic/MCP tools) falls back to `other` so the discriminated
195
+ * union always deserialises on strict clients.
196
+ */
197
+ const TOOL_KIND_BY_NAME: Record<string, AcpToolKind> = {
198
+ read: "read",
199
+ ls: "read",
200
+ context: "read",
201
+ write: "edit",
202
+ edit: "edit",
203
+ artifact: "edit",
204
+ grep: "search",
205
+ find: "search",
206
+ code_nav: "search",
207
+ bash: "execute",
208
+ verify: "execute",
209
+ web_fetch: "fetch",
210
+ git: "other",
211
+ dispatch: "other",
212
+ monitor: "read",
213
+ steer: "other",
214
+ };
215
+
216
+ /**
217
+ * The admission reasons this server puts on the wire (CONTRACT C001 §4). The
218
+ * engine's runtime-resolution diagnostics are a larger and faster-moving set
219
+ * (`runtime-target-unsupported`, `runtime-use-unsupported`,
220
+ * `required-capability-missing`, …), and a client cannot branch on codes the
221
+ * profile never promised, so anything outside this set is reported as the
222
+ * catch-all rather than leaking an engine-internal identifier.
223
+ */
224
+ const ACP_ADMISSION_REASONS = new Set([
225
+ "orchestrator-not-configured",
226
+ "target-unknown",
227
+ "target-not-configured",
228
+ "target-not-found",
229
+ "runtime-not-registered",
230
+ "model-not-configured",
231
+ "chat-unsupported",
232
+ "streaming-unsupported",
233
+ "admission-failed",
234
+ ]);
235
+
236
+ const ACP_ADMISSION_FALLBACK_REASON = "admission-failed";
237
+
238
+ function admissionReason(raw: string): string {
239
+ return ACP_ADMISSION_REASONS.has(raw) ? raw : ACP_ADMISSION_FALLBACK_REASON;
240
+ }
241
+
242
+ function toolKind(name: string | undefined): AcpToolKind {
243
+ if (!name) return "other";
244
+ return TOOL_KIND_BY_NAME[name] ?? "other";
245
+ }
246
+
247
+ /** ACP `ToolCallContent[]`. The `content` variant wraps a regular ContentBlock. */
248
+ function toolCallContent(text: string): Array<{ type: "content"; content: { type: "text"; text: string } }> {
249
+ return [{ type: "content", content: textContent(text) }];
250
+ }
251
+
252
+ function contentText(value: unknown): string {
253
+ if (typeof value === "string") return value;
254
+ if (Array.isArray(value)) return value.map(contentText).filter(Boolean).join("\n");
255
+ if (!isRecord(value)) return "";
256
+ if (value.type === "text" && typeof value.text === "string") return value.text;
257
+ if (Array.isArray(value.content)) return contentText(value.content);
258
+ if (typeof value.content === "string") return value.content;
259
+ return "";
260
+ }
261
+
262
+ /**
263
+ * The prompt text of one `session/prompt`. ACP v1 carries it as `params.prompt`,
264
+ * an array of content blocks, and only `text` blocks have a textual reading;
265
+ * image, audio, and resource_link blocks are ignored rather than coerced into
266
+ * prose the model would then answer. Nothing else is accepted: tolerating
267
+ * `params.content`, `params.message`, or a bare string meant this server
268
+ * answered request shapes no ACP client sends and no schema describes, so a
269
+ * client's own framing bug looked like a working prompt here and failed
270
+ * against every other agent.
271
+ */
272
+ function promptText(params: unknown): string {
273
+ if (!isRecord(params) || !Array.isArray(params.prompt)) return "";
274
+ const parts: string[] = [];
275
+ for (const block of params.prompt) {
276
+ if (!isRecord(block) || block.type !== "text" || typeof block.text !== "string") continue;
277
+ parts.push(block.text);
278
+ }
279
+ return parts.join("\n").trim();
280
+ }
281
+
282
+ /** Marker appended to any value this server shortened before sending it. */
283
+ const ACP_TRUNCATION_SUFFIX = "…[truncated]";
284
+
285
+ const ACP_TRUNCATION_SUFFIX_BYTES = Buffer.byteLength(ACP_TRUNCATION_SUFFIX, "utf8");
286
+
287
+ /** Workbench's frozen bounds for standard ACP tool-call presentation fields. */
288
+ const ACP_MAX_LOCATION_PATH_BYTES = 4 * 1024;
289
+ const ACP_MAX_TOOL_TITLE_BYTES = 512;
290
+
291
+ /** W001-A1 cardinality bounds for one live prompt and one load replay. */
292
+ const ACP_MAX_LIVE_TOOL_CALLS = 128;
293
+ const ACP_MAX_REPLAY_TOOL_CALLS = 8192;
294
+
295
+ /** Depth past which `boundRawRecord` stops walking and elides the subtree. */
296
+ const ACP_MAX_RAW_RECORD_DEPTH = 8;
297
+
298
+ /**
299
+ * The longest prefix of `value` that fits `maxBytes` UTF-8 bytes without
300
+ * splitting a code point. `Buffer.write` stops before a partial sequence, so a
301
+ * surrogate pair is either wholly inside the prefix or wholly outside it and no
302
+ * cut can produce a lone surrogate the peer decodes as a replacement character.
303
+ */
304
+ function sliceToBytes(value: string, maxBytes: number): string {
305
+ if (maxBytes <= 0) return "";
306
+ if (Buffer.byteLength(value, "utf8") <= maxBytes) return value;
307
+ const buffer = Buffer.allocUnsafe(maxBytes);
308
+ const written = buffer.write(value, 0, maxBytes, "utf8");
309
+ return buffer.toString("utf8", 0, written);
310
+ }
311
+
312
+ /** Bounds one string to `maxBytes` UTF-8 bytes, marker included in the budget. */
313
+ function boundString(value: string, maxBytes: number): string {
314
+ if (Buffer.byteLength(value, "utf8") <= maxBytes) return value;
315
+ // The marker is part of what goes on the wire, so it is reserved inside the
316
+ // cap rather than appended past it.
317
+ const budget = maxBytes - ACP_TRUNCATION_SUFFIX_BYTES;
318
+ if (budget <= 0) return sliceToBytes(value, maxBytes);
319
+ return `${sliceToBytes(value, budget)}${ACP_TRUNCATION_SUFFIX}`;
320
+ }
321
+
322
+ /**
323
+ * Splits one delta into wire-sized chunks. Nothing is dropped and no code point
324
+ * is split: the pieces concatenate back to the input, so a client that appends
325
+ * chunks in order reconstructs the model's text exactly.
326
+ */
327
+ function chunkText(text: string, maxBytes: number): string[] {
328
+ if (Buffer.byteLength(text, "utf8") <= maxBytes) return [text];
329
+ const chunks: string[] = [];
330
+ let rest = text;
331
+ while (rest.length > 0) {
332
+ const chunk = sliceToBytes(rest, maxBytes);
333
+ // One code point wider than the whole bound cannot be split further, so it
334
+ // travels oversized rather than being dropped or mangled.
335
+ if (chunk.length === 0) {
336
+ chunks.push(rest);
337
+ break;
338
+ }
339
+ chunks.push(chunk);
340
+ rest = rest.slice(chunk.length);
341
+ }
342
+ return chunks;
343
+ }
344
+
345
+ function boundRawValue(value: unknown, depth: number): unknown {
346
+ if (typeof value === "string") return boundString(value, ACP_MAX_STRING_BYTES);
347
+ if (Array.isArray(value)) {
348
+ if (depth >= ACP_MAX_RAW_RECORD_DEPTH) return "[depth]";
349
+ return value.map((entry) => boundRawValue(entry, depth + 1));
350
+ }
351
+ if (isRecord(value)) {
352
+ if (depth >= ACP_MAX_RAW_RECORD_DEPTH) return "[depth]";
353
+ const bounded: Record<string, unknown> = {};
354
+ for (const [key, entry] of Object.entries(value)) bounded[key] = boundRawValue(entry, depth + 1);
355
+ return bounded;
356
+ }
357
+ return value;
358
+ }
359
+
360
+ /**
361
+ * The record's serialized UTF-8 size before any bounding, or null when it does
362
+ * not serialize at all (a cycle, a BigInt, a throwing `toJSON`). This is the
363
+ * figure `{truncated:true,bytes}` reports: the size a client is told about is
364
+ * the payload the engine actually produced, not the size of the shortened copy
365
+ * this process built and then decided not to send.
366
+ */
367
+ function originalRawRecordBytes(value: unknown): number | null {
368
+ try {
369
+ const serialized = JSON.stringify(value);
370
+ return serialized === undefined ? null : Buffer.byteLength(serialized, "utf8");
371
+ } catch {
372
+ return null;
373
+ }
374
+ }
375
+
376
+ /**
377
+ * Bounds one `rawInput`/`rawOutput` record for the wire (CONTRACT C001 §3).
378
+ * Strings are capped first, then the whole record: a payload that is still over
379
+ * the record cap after per-string bounding is thousands of small fields, and
380
+ * the honest thing to send is the fact that it was elided plus its size.
381
+ */
382
+ function boundRawRecord(value: unknown): Record<string, unknown> {
383
+ const bounded = isRecord(value) ? (boundRawValue(value, 0) as Record<string, unknown>) : {};
384
+ let serialized: string | undefined;
385
+ try {
386
+ serialized = JSON.stringify(bounded);
387
+ } catch {
388
+ serialized = undefined;
389
+ }
390
+ // Nothing about this record can be measured, so the elision carries whatever
391
+ // size the original could still report and 0 when it could not report one.
392
+ if (serialized === undefined) return { truncated: true, bytes: originalRawRecordBytes(value) ?? 0 };
393
+ const boundedBytes = Buffer.byteLength(serialized, "utf8");
394
+ if (boundedBytes <= ACP_MAX_RAW_RECORD_BYTES) return bounded;
395
+ // The bounded size is the fallback: an unserializable original has no size of
396
+ // its own, and reporting the copy's is closer than reporting nothing.
397
+ return { truncated: true, bytes: originalRawRecordBytes(value) ?? boundedBytes };
398
+ }
399
+
400
+ /** Built-in tools whose first positional argument names a workspace path. */
401
+ const PATH_BEARING_TOOLS = new Set(["read", "write", "edit", "ls", "grep", "find"]);
402
+
403
+ /**
404
+ * The standard `locations` field for a path-bearing call. The path is resolved
405
+ * against the pinned workspace root but never realpath'ed: an `edit` or `write`
406
+ * target legitimately does not exist yet, and a client that canonicalizes on
407
+ * its own side treats an absent `locations` as "unavailable".
408
+ */
409
+ function toolLocations(toolName: string | undefined, args: unknown, cwd: string): AcpToolCallLocation[] | null {
410
+ if (toolName === undefined || !PATH_BEARING_TOOLS.has(toolName)) return null;
411
+ if (!isRecord(args)) return null;
412
+ const path = args.path;
413
+ if (typeof path !== "string" || path.length === 0) return null;
414
+ return [{ path: boundString(resolvePath(cwd, path), ACP_MAX_LOCATION_PATH_BYTES) }];
415
+ }
416
+
417
+ function utf8Bytes(value: string): number {
418
+ return Buffer.byteLength(value, "utf8");
419
+ }
420
+
421
+ function emptyUsage(): AcpServerUsage {
422
+ return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, reasoning: 0 };
423
+ }
424
+
425
+ function createActivePromptState(): ActivePrompt {
426
+ return {
427
+ cancelled: false,
428
+ permissionExpired: false,
429
+ toolCallLimitReached: false,
430
+ errored: false,
431
+ sentAssistantChars: 0,
432
+ sentThinkingChars: 0,
433
+ updatesSent: 0,
434
+ sawTurnEnd: false,
435
+ stopReason: "end_turn",
436
+ usage: emptyUsage(),
437
+ usageMessages: new WeakSet<object>(),
438
+ toolCallWireIds: new Map<string, string[]>(),
439
+ usedWireIds: new Set<string>(),
440
+ openToolCalls: new Set<string>(),
441
+ terminalToolCalls: new Set<string>(),
442
+ toolCallSnapshots: new Map<string, AcpToolCallSnapshot>(),
443
+ lastEmittedToolCallId: null,
444
+ toolCallSequence: 0,
445
+ };
446
+ }
447
+
448
+ function finite(value: unknown): number {
449
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
450
+ }
451
+
452
+ function mergeUsage(into: AcpServerUsage, usage: unknown): void {
453
+ if (!isRecord(usage)) return;
454
+ into.input +=
455
+ finite(usage.input) + finite(usage.inputTokens) + finite(usage.input_tokens) + finite(usage.prompt_tokens);
456
+ into.output +=
457
+ finite(usage.output) + finite(usage.outputTokens) + finite(usage.output_tokens) + finite(usage.completion_tokens);
458
+ into.cacheRead += finite(usage.cacheRead) + finite(usage.cacheReadTokens) + finite(usage.cache_read_tokens);
459
+ into.cacheWrite += finite(usage.cacheWrite) + finite(usage.cacheWriteTokens) + finite(usage.cache_write_tokens);
460
+ into.reasoning += finite(usage.reasoning) + finite(usage.reasoningTokens) + finite(usage.reasoning_tokens);
461
+ }
462
+
463
+ function mergeMessageUsage(into: AcpServerUsage, message: unknown, seen?: WeakSet<object>): void {
464
+ if (!isRecord(message)) return;
465
+ if (seen) {
466
+ if (seen.has(message)) return;
467
+ seen.add(message);
468
+ }
469
+ mergeUsage(into, message.usage);
470
+ }
471
+
472
+ function mergeMessagesUsage(into: AcpServerUsage, messages: unknown, seen?: WeakSet<object>): void {
473
+ if (!Array.isArray(messages)) return;
474
+ for (const message of messages) mergeMessageUsage(into, message, seen);
475
+ }
476
+
477
+ function assistantText(message: unknown): string {
478
+ if (!isRecord(message) || message.role !== "assistant") return "";
479
+ return contentText(message.content);
480
+ }
481
+
482
+ function assistantThinking(message: unknown): string {
483
+ if (!isRecord(message) || message.role !== "assistant" || !Array.isArray(message.content)) return "";
484
+ return message.content
485
+ .map((block) => {
486
+ if (!isRecord(block)) return "";
487
+ if (block.type === "thinking" && typeof block.thinking === "string") return block.thinking;
488
+ if (block.type === "thinking" && typeof block.text === "string") return block.text;
489
+ return "";
490
+ })
491
+ .filter(Boolean)
492
+ .join("");
493
+ }
494
+
495
+ /**
496
+ * Collapses pi-agent / Clio message stop reasons onto the ACP v1 StopReason
497
+ * closed enum (end_turn | max_tokens | max_turn_requests | refusal | cancelled).
498
+ * Tool-driven or unknown reasons ("stop", "toolUse", "length"…) map to
499
+ * "end_turn"; "error" is a sentinel that the prompt handler converts into a
500
+ * JSON-RPC error, since ACP has no error StopReason.
501
+ */
502
+ function mapAcpStopReason(raw: unknown): string {
503
+ switch (raw) {
504
+ case "aborted":
505
+ case "cancelled":
506
+ return "cancelled";
507
+ case "error":
508
+ return "error";
509
+ case "refusal":
510
+ return "refusal";
511
+ case "length":
512
+ case "max_tokens":
513
+ case "maxTokens":
514
+ return "max_tokens";
515
+ case "max_turn_requests":
516
+ case "maxTurnRequests":
517
+ return "max_turn_requests";
518
+ default:
519
+ return "end_turn";
520
+ }
521
+ }
522
+
523
+ /** Applies a message's stop reason to the active prompt, tracking the error sentinel. */
524
+ function applyStopReason(active: ActivePrompt, message: unknown): void {
525
+ const mapped = mapAcpStopReason(isRecord(message) ? message.stopReason : undefined);
526
+ if (mapped === "error") {
527
+ active.errored = true;
528
+ const explicit = isRecord(message) && typeof message.errorMessage === "string" ? message.errorMessage : "";
529
+ const text = explicit.length > 0 ? explicit : assistantText(message);
530
+ if (text.length > 0) active.errorMessage = text;
531
+ return;
532
+ }
533
+ active.errored = false;
534
+ active.stopReason = mapped;
535
+ }
536
+
537
+ function outputText(value: unknown): string {
538
+ if (typeof value === "string") return value;
539
+ if (!isRecord(value)) return "";
540
+ if (Array.isArray(value.content)) return contentText(value.content);
541
+ if (typeof value.output === "string") return value.output;
542
+ if (typeof value.text === "string") return value.text;
543
+ try {
544
+ return JSON.stringify(value);
545
+ } catch {
546
+ return "";
547
+ }
548
+ }
549
+
550
+ function toolStatus(event: AcpEventRecord): string {
551
+ return event.isError === true ? "failed" : "completed";
552
+ }
553
+
554
+ function eventString(event: AcpEventRecord, ...keys: string[]): string | undefined {
555
+ for (const key of keys) {
556
+ const value = event[key];
557
+ if (typeof value === "string" && value.length > 0) return value;
558
+ }
559
+ return undefined;
560
+ }
561
+
562
+ function eventTextDelta(event: AcpEventRecord): string {
563
+ const direct = eventString(event, "delta", "text");
564
+ if (direct !== undefined) return direct;
565
+ const assistantEvent = isRecord(event.assistantMessageEvent) ? event.assistantMessageEvent : null;
566
+ if (assistantEvent) {
567
+ const nested = assistantEvent.delta;
568
+ if (typeof nested === "string") return nested;
569
+ }
570
+ return "";
571
+ }
572
+
573
+ /**
574
+ * The next unused `clio-tool-<n>`. The counter alone is not enough: an engine
575
+ * that names its own calls `clio-tool-1` would otherwise collide with an alias
576
+ * this server minted, and two different calls sharing one wire id is the exact
577
+ * failure aliasing exists to prevent.
578
+ */
579
+ function nextAliasToolCallId(active: ActivePrompt): string {
580
+ for (;;) {
581
+ active.toolCallSequence += 1;
582
+ const alias = `clio-tool-${active.toolCallSequence}`;
583
+ if (active.usedWireIds.has(alias)) continue;
584
+ active.usedWireIds.add(alias);
585
+ return alias;
586
+ }
587
+ }
588
+
589
+ /** Records `wireId` as the newest wire id this engine id speaks under. */
590
+ function pushWireId(active: ActivePrompt, engineId: string, wireId: string): string {
591
+ const existing = active.toolCallWireIds.get(engineId);
592
+ if (existing === undefined) active.toolCallWireIds.set(engineId, [wireId]);
593
+ else existing.push(wireId);
594
+ return wireId;
595
+ }
596
+
597
+ /**
598
+ * The first wire id an engine id gets. The engine's own id is used when it fits
599
+ * the wire bound and no other call in this turn has claimed it; otherwise the
600
+ * call travels under a per-prompt alias.
601
+ */
602
+ function mintWireId(active: ActivePrompt, engineId: string): string {
603
+ const usable = utf8Bytes(engineId) <= ACP_MAX_TOOL_CALL_ID_BYTES && !active.usedWireIds.has(engineId);
604
+ if (usable) active.usedWireIds.add(engineId);
605
+ return pushWireId(active, engineId, usable ? engineId : nextAliasToolCallId(active));
606
+ }
607
+
608
+ /**
609
+ * The id a starting tool call travels under. An engine id this turn has already
610
+ * used gets a fresh alias rather than the wire id of the earlier call: engines
611
+ * that number their calls per request legitimately repeat an id, and reusing the
612
+ * wire id merged two distinct calls into one on the client, so the second call's
613
+ * arguments overwrote the first and one of the two ends was lost.
614
+ */
615
+ function startToolCallId(active: ActivePrompt, engineId: string | undefined): string {
616
+ if (engineId === undefined || engineId.length === 0) return nextAliasToolCallId(active);
617
+ if (active.toolCallWireIds.has(engineId)) return pushWireId(active, engineId, nextAliasToolCallId(active));
618
+ return mintWireId(active, engineId);
619
+ }
620
+
621
+ /**
622
+ * The most recently opened wire id for this engine id, or null when it names no
623
+ * call that is still open. "Open" is the client's own view: a `tool_call` was
624
+ * emitted and no terminal `tool_call_update` followed it.
625
+ */
626
+ function openWireIdFor(active: ActivePrompt, engineId: string): string | null {
627
+ const wireIds = active.toolCallWireIds.get(engineId);
628
+ if (wireIds === undefined) return null;
629
+ for (let index = wireIds.length - 1; index >= 0; index -= 1) {
630
+ const wireId = wireIds[index];
631
+ if (wireId !== undefined && active.openToolCalls.has(wireId)) return wireId;
632
+ }
633
+ return null;
634
+ }
635
+
636
+ /**
637
+ * The most recently opened wire id this turn still has running, or null when
638
+ * nothing is open. `openToolCalls` is insertion-ordered and only ever loses
639
+ * members, so the last one standing is the newest call the client has been shown
640
+ * and not yet seen finish.
641
+ */
642
+ function newestOpenToolCallId(active: ActivePrompt): string | null {
643
+ let newest: string | null = null;
644
+ for (const wireId of active.openToolCalls) newest = wireId;
645
+ return newest;
646
+ }
647
+
648
+ /**
649
+ * The wire id an ending tool call travels under, or null when this turn has no
650
+ * call it could belong to. Nothing here mints: an end is an update to a call the
651
+ * client already rendered, and a fresh id announced a `tool_call_update` for a
652
+ * `tool_call` that was never sent, which a client either drops or renders as a
653
+ * tool that finished without ever starting.
654
+ *
655
+ * An end that names an engine id is confined to that engine id's own calls. It
656
+ * closes them newest first, which is the order a nested or retried call finishes
657
+ * in, and when they are all finished it still resolves to that id's last wire id
658
+ * so the caller can drop the end as the duplicate it is. An engine id this turn
659
+ * emitted nothing for binds to nothing: borrowing another call's wire id
660
+ * reported one tool's result under another tool's identity and closed a call
661
+ * that was still running.
662
+ *
663
+ * An end with no engine id belongs to the newest call still running, which is
664
+ * what makes nested lifecycles work. With A and B open and B already ended, the
665
+ * next unidentified end is A's. With nothing open it falls back to the last call
666
+ * the client actually saw, so the caller drops it as a duplicate rather than
667
+ * inventing an identity for it.
668
+ */
669
+ function endToolCallId(active: ActivePrompt, engineId: string | undefined): string | null {
670
+ if (engineId === undefined || engineId.length === 0) {
671
+ return newestOpenToolCallId(active) ?? active.lastEmittedToolCallId;
672
+ }
673
+ const open = openWireIdFor(active, engineId);
674
+ if (open !== null) return open;
675
+ const wireIds = active.toolCallWireIds.get(engineId);
676
+ if (wireIds === undefined) return null;
677
+ return wireIds[wireIds.length - 1] ?? null;
678
+ }
679
+
680
+ function sendUpdate(
681
+ transport: AcpJsonRpcPeerTransport,
682
+ sessionId: string,
683
+ active: ActivePrompt,
684
+ update: Record<string, unknown>,
685
+ ): void {
686
+ const params: AcpSessionUpdateParams = { sessionId, update };
687
+ active.updatesSent += 1;
688
+ transport.notify("session/update", params);
689
+ }
690
+
691
+ function sendTextChunks(
692
+ transport: AcpJsonRpcPeerTransport,
693
+ sessionId: string,
694
+ active: ActivePrompt,
695
+ sessionUpdate: "agent_message_chunk" | "agent_thought_chunk",
696
+ text: string,
697
+ ): void {
698
+ for (const chunk of chunkText(text, ACP_MAX_CHUNK_BYTES)) {
699
+ sendUpdate(transport, sessionId, active, { sessionUpdate, content: textContent(chunk) });
700
+ }
701
+ }
702
+
703
+ /**
704
+ * Closes out every tool call that never received a terminal update. A client
705
+ * that renders a running spinner per `tool_call` otherwise keeps spinning after
706
+ * the turn settled, since ACP has no "the turn is over, drop what is open"
707
+ * signal beyond the prompt response itself.
708
+ */
709
+ function settleOpenToolCalls(
710
+ transport: AcpJsonRpcPeerTransport,
711
+ sessionId: string,
712
+ active: ActivePrompt,
713
+ text: string,
714
+ ): void {
715
+ for (const toolCallId of active.openToolCalls) {
716
+ sendUpdate(transport, sessionId, active, {
717
+ sessionUpdate: "tool_call_update",
718
+ toolCallId,
719
+ status: "failed" satisfies AcpToolCallStatus,
720
+ content: toolCallContent(text),
721
+ });
722
+ // The sweep is a terminal update like any other, so a late end for a swept
723
+ // id cannot reopen the call the client has already seen fail.
724
+ active.terminalToolCalls.add(toolCallId);
725
+ }
726
+ active.openToolCalls.clear();
727
+ }
728
+
729
+ function handleChatEvent(
730
+ rawEvent: AcpServerEvent,
731
+ transport: AcpJsonRpcPeerTransport,
732
+ sessionId: string,
733
+ active: ActivePrompt,
734
+ cwd: string,
735
+ diagnostics: ((line: string) => void) | undefined,
736
+ onToolCallLimitReached: () => void,
737
+ ): void {
738
+ const event = eventRecord(rawEvent);
739
+ // Once the approval ceiling wins, the prompt is terminal. Registry
740
+ // cancellation may still make engine promises unwind and emit ordinary tool
741
+ // or assistant events; none of those are part of a continuing model turn.
742
+ if (active.permissionExpired || active.toolCallLimitReached) return;
743
+ if (event.type === "text_delta") {
744
+ const text = eventTextDelta(event);
745
+ if (text.length === 0) return;
746
+ active.sentAssistantChars += text.length;
747
+ sendTextChunks(transport, sessionId, active, "agent_message_chunk", text);
748
+ return;
749
+ }
750
+ if (event.type === "thinking_delta") {
751
+ const text = eventTextDelta(event);
752
+ if (text.length === 0) return;
753
+ active.sentThinkingChars += text.length;
754
+ sendTextChunks(transport, sessionId, active, "agent_thought_chunk", text);
755
+ return;
756
+ }
757
+ if (event.type === "notice") {
758
+ // Advisory notices have no ACP v1 equivalent and stay dropped. The one
759
+ // exception is an admission notice: it is the only evidence that Clio
760
+ // refused to start the turn, and the prompt handler fails the request
761
+ // with its reason instead of returning an empty success.
762
+ const admission = isRecord(event.admission) ? event.admission : null;
763
+ const reason = admission !== null && typeof admission.reason === "string" ? admission.reason : "";
764
+ if (reason.length > 0 && active.admissionReason === undefined) active.admissionReason = admissionReason(reason);
765
+ return;
766
+ }
767
+ if (event.type === "tool_execution_start") {
768
+ // Tool starts are emitted before registry validation/admission, so Clio's
769
+ // configurable execution guard is not a wire-cardinality guarantee. Stop
770
+ // before minting or emitting the 129th id; the prompt handler resolves with
771
+ // ACP's standard max_turn_requests reason after the underlying run unwinds.
772
+ if (active.usedWireIds.size >= ACP_MAX_LIVE_TOOL_CALLS) {
773
+ active.toolCallLimitReached = true;
774
+ onToolCallLimitReached();
775
+ return;
776
+ }
777
+ const toolName = eventString(event, "toolName");
778
+ const toolCallId = startToolCallId(active, eventString(event, "toolCallId"));
779
+ active.openToolCalls.add(toolCallId);
780
+ active.lastEmittedToolCallId = toolCallId;
781
+ const locations = toolLocations(toolName, event.args, cwd);
782
+ // Built once and kept: the permission request for this call must send the
783
+ // same objects rather than recompute them from a copy of the arguments that
784
+ // may since have been normalized.
785
+ const snapshot: AcpToolCallSnapshot = {
786
+ rawInput: boundRawRecord(event.args),
787
+ ...(locations !== null ? { locations } : {}),
788
+ };
789
+ active.toolCallSnapshots.set(toolCallId, snapshot);
790
+ sendUpdate(transport, sessionId, active, {
791
+ sessionUpdate: "tool_call",
792
+ toolCallId,
793
+ title: boundString(toolName ?? "tool", ACP_MAX_TOOL_TITLE_BYTES),
794
+ kind: toolKind(toolName),
795
+ status: "in_progress" satisfies AcpToolCallStatus,
796
+ rawInput: snapshot.rawInput,
797
+ ...(snapshot.locations !== undefined ? { locations: snapshot.locations } : {}),
798
+ });
799
+ return;
800
+ }
801
+ if (event.type === "tool_execution_end") {
802
+ const toolName = eventString(event, "toolName");
803
+ const toolCallId = endToolCallId(active, eventString(event, "toolCallId"));
804
+ // This turn put no `tool_call` on the wire, so there is no call an update
805
+ // could name. The event is reported on the stderr tail instead.
806
+ if (toolCallId === null) {
807
+ diagnostics?.("dropped tool_execution_end with no tool call to update");
808
+ return;
809
+ }
810
+ // The client has already been told how this call ended. A second terminal
811
+ // update either resurrects a finished call or overwrites its result with a
812
+ // later one, so the duplicate goes to the stderr tail and not the wire.
813
+ if (active.terminalToolCalls.has(toolCallId)) {
814
+ diagnostics?.(`dropped duplicate terminal update for ${toolCallId}`);
815
+ return;
816
+ }
817
+ active.openToolCalls.delete(toolCallId);
818
+ active.terminalToolCalls.add(toolCallId);
819
+ const output = boundString(outputText(event.result), ACP_MAX_CHUNK_BYTES);
820
+ sendUpdate(transport, sessionId, active, {
821
+ sessionUpdate: "tool_call_update",
822
+ toolCallId,
823
+ title: boundString(toolName ?? "tool", ACP_MAX_TOOL_TITLE_BYTES),
824
+ kind: toolKind(toolName),
825
+ status: toolStatus(event),
826
+ ...(output.length > 0 ? { content: toolCallContent(output) } : {}),
827
+ rawOutput: boundRawRecord({ result: event.result, isError: event.isError === true }),
828
+ });
829
+ return;
830
+ }
831
+ if (event.type === "message_end") {
832
+ const message = event.message;
833
+ active.sawTurnEnd = true;
834
+ mergeMessageUsage(active.usage, message, active.usageMessages);
835
+ applyStopReason(active, message);
836
+ const thinking = assistantThinking(message);
837
+ if (thinking.length > active.sentThinkingChars) {
838
+ const tail = thinking.slice(active.sentThinkingChars);
839
+ active.sentThinkingChars = thinking.length;
840
+ sendTextChunks(transport, sessionId, active, "agent_thought_chunk", tail);
841
+ }
842
+ const text = assistantText(message);
843
+ if (text.length > active.sentAssistantChars) {
844
+ const tail = text.slice(active.sentAssistantChars);
845
+ active.sentAssistantChars = text.length;
846
+ sendTextChunks(transport, sessionId, active, "agent_message_chunk", tail);
847
+ }
848
+ return;
849
+ }
850
+ if (event.type === "agent_end") {
851
+ active.sawTurnEnd = true;
852
+ mergeMessagesUsage(active.usage, event.messages, active.usageMessages);
853
+ const messages = Array.isArray(event.messages) ? event.messages : [];
854
+ const last = [...messages].reverse().find((message) => isRecord(message) && message.role === "assistant");
855
+ if (last !== undefined) applyStopReason(active, last);
856
+ return;
857
+ }
858
+ // Clio lifecycle events (agent_start, retry_status, clio_plan_update) have
859
+ // no ACP v1 SessionUpdate equivalent. The prompt turn is bounded by the
860
+ // session/prompt response, so emitting non-spec `progress` updates would
861
+ // break strict clients. They are intentionally dropped. The same protocol
862
+ // gap applies to routing advisories (external settings divergence, active
863
+ // target removed): ACP v1 has no agent-initiated advisory channel, so the
864
+ // orchestrator records those as `custom` session-ledger entries
865
+ // (customType "clio.routing-notice") instead of inventing update kinds.
866
+ }
867
+
868
+ const ACP_MAX_SESSION_ID_BYTES = 128;
869
+ const ACP_MAX_TARGET_ID_BYTES = 128;
870
+ const ACP_MAX_MODEL_ID_BYTES = 256;
871
+ const ACP_MAX_LABEL_BYTES = 256;
872
+ const ACP_MAX_PREVIEW_BYTES = 512;
873
+ const ACP_MAX_REPLAY_TURNS = 64;
874
+ const ACP_MAX_REPLAY_BYTES = 4 * 1024 * 1024;
875
+ const ACP_REPLAY_META_KEY = "clio-coder/replay";
876
+
877
+ function hasControlCharacters(value: string): boolean {
878
+ for (const character of value) {
879
+ const codePoint = character.codePointAt(0);
880
+ if (codePoint !== undefined && (codePoint <= 0x1f || codePoint === 0x7f)) return true;
881
+ }
882
+ return false;
883
+ }
884
+
885
+ function requireBoundedClientString(
886
+ value: unknown,
887
+ name: string,
888
+ maxBytes: number,
889
+ options: { allowEmpty?: boolean } = {},
890
+ ): string {
891
+ if (typeof value !== "string" || (!options.allowEmpty && value.length === 0)) {
892
+ throw new AcpRequestError(-32602, `${name} is required`, { code: "invalid_params" });
893
+ }
894
+ if (utf8Bytes(value) > maxBytes || hasControlCharacters(value)) {
895
+ throw new AcpRequestError(-32602, `${name} is invalid`, { code: "invalid_params" });
896
+ }
897
+ return value;
898
+ }
899
+
900
+ function safeStoredString(value: unknown, maxBytes: number, fallback = ""): string {
901
+ if (typeof value !== "string") return fallback;
902
+ let safe = "";
903
+ for (const character of value) {
904
+ const codePoint = character.codePointAt(0);
905
+ safe += codePoint !== undefined && (codePoint <= 0x1f || codePoint === 0x7f) ? " " : character;
906
+ }
907
+ return boundString(safe, maxBytes);
908
+ }
909
+
910
+ function safeStoredIdentifier(value: unknown, maxBytes: number): string | null {
911
+ if (typeof value !== "string" || value.length === 0 || hasControlCharacters(value) || utf8Bytes(value) > maxBytes) {
912
+ return null;
913
+ }
914
+ return value;
915
+ }
916
+
917
+ /**
918
+ * A selected route cannot be represented as null merely to satisfy the wire
919
+ * bound: null means genuinely unselected in W001. Refuse the affected method
920
+ * instead, with the fixed internal-error envelope, until the operator repairs
921
+ * the local configuration.
922
+ */
923
+ function safeConfiguredIdentifier(value: unknown, maxBytes: number): string | null {
924
+ if (value === null || value === undefined) return null;
925
+ const safe = safeStoredIdentifier(value, maxBytes);
926
+ if (safe === null) {
927
+ throw new AcpRequestError(-32000, "configured route cannot be represented safely", { code: "internal_error" });
928
+ }
929
+ return safe;
930
+ }
931
+
932
+ function safeIso(value: unknown): string {
933
+ if (typeof value === "string" && !hasControlCharacters(value)) {
934
+ const timestamp = Date.parse(value);
935
+ if (Number.isFinite(timestamp)) return new Date(timestamp).toISOString();
936
+ }
937
+ return new Date(0).toISOString();
938
+ }
939
+
940
+ function payloadRecord(payload: unknown): Record<string, unknown> | null {
941
+ return isRecord(payload) ? payload : null;
942
+ }
943
+
944
+ function replayTextBlocks(entry: MessageEntry): { text: string[]; thinking: string[] } {
945
+ const text: string[] = [];
946
+ const thinking: string[] = [];
947
+ if (typeof entry.payload === "string") text.push(entry.payload);
948
+ const payload = payloadRecord(entry.payload);
949
+ if (payload === null) return { text, thinking };
950
+ if (typeof payload.text === "string") text.push(payload.text);
951
+ if (!Array.isArray(payload.content)) return { text, thinking };
952
+ // A rich payload's `text` is the flattened copy of `content`. Prefer the
953
+ // original blocks so a replay does not duplicate the same prose.
954
+ if (payload.content.length > 0) text.length = 0;
955
+ for (const block of payload.content) {
956
+ if (!isRecord(block)) continue;
957
+ if (block.type === "text" && typeof block.text === "string") text.push(block.text);
958
+ if (block.type === "thinking") {
959
+ if (typeof block.thinking === "string") thinking.push(block.thinking);
960
+ else if (typeof block.text === "string") thinking.push(block.text);
961
+ }
962
+ }
963
+ return { text, thinking };
964
+ }
965
+
966
+ interface AcpReplayFrame {
967
+ update: Record<string, unknown>;
968
+ }
969
+
970
+ interface AcpReplayTurn {
971
+ frames: AcpReplayFrame[];
972
+ toolCalls: number;
973
+ }
974
+
975
+ interface PreparedAcpReplay {
976
+ params: Array<AcpSessionUpdateParams & { _meta: Record<string, unknown> }>;
977
+ turns: number;
978
+ truncated: boolean;
979
+ }
980
+
981
+ function replayMessageChunks(
982
+ frames: AcpReplayFrame[],
983
+ sessionUpdate: "user_message_chunk" | "agent_message_chunk" | "agent_thought_chunk",
984
+ texts: ReadonlyArray<string>,
985
+ ): void {
986
+ for (const text of texts) {
987
+ for (const chunk of chunkText(text, ACP_MAX_CHUNK_BYTES)) {
988
+ if (chunk.length === 0) continue;
989
+ frames.push({ update: { sessionUpdate, content: textContent(chunk) } });
990
+ }
991
+ }
992
+ }
993
+
994
+ function replayToolCall(entry: MessageEntry): { engineId: string; name: string; args: unknown } {
995
+ const payload = payloadRecord(entry.payload);
996
+ return {
997
+ engineId:
998
+ typeof payload?.toolCallId === "string" && payload.toolCallId.length > 0
999
+ ? payload.toolCallId
1000
+ : `persisted-tool-${entry.turnId}`,
1001
+ name: safeStoredString(payload?.name ?? payload?.toolName, 64, "tool") || "tool",
1002
+ args: payload?.args,
1003
+ };
1004
+ }
1005
+
1006
+ function closeUnrecordedReplayCalls(active: ActivePrompt, frames: AcpReplayFrame[]): void {
1007
+ for (const toolCallId of active.openToolCalls) {
1008
+ frames.push({
1009
+ update: {
1010
+ sessionUpdate: "tool_call_update",
1011
+ toolCallId,
1012
+ status: "failed" satisfies AcpToolCallStatus,
1013
+ content: toolCallContent("unrecorded"),
1014
+ },
1015
+ });
1016
+ active.terminalToolCalls.add(toolCallId);
1017
+ }
1018
+ active.openToolCalls.clear();
1019
+ }
1020
+
1021
+ /**
1022
+ * Build client-visible replay from original active-branch transcript entries.
1023
+ * Provider-only synthetic context never enters this projection.
1024
+ */
1025
+ function prepareAcpReplay(
1026
+ entries: ReadonlyArray<SessionEntry>,
1027
+ leafTurnId: string | null,
1028
+ sessionId: string,
1029
+ ): PreparedAcpReplay {
1030
+ const branch = filterEntriesToActivePath(entries, leafTurnId ?? undefined);
1031
+ const turns: AcpReplayTurn[] = [];
1032
+ const ids = createActivePromptState();
1033
+ let current: AcpReplayTurn | null = null;
1034
+ for (const entry of branch) {
1035
+ if (entry.kind !== "message") continue;
1036
+ const payload = payloadRecord(entry.payload);
1037
+ if (entry.role === "user") {
1038
+ // Middleware continuations are provider context, not operator authorship.
1039
+ if (payload?.synthetic === true) continue;
1040
+ if (current !== null) closeUnrecordedReplayCalls(ids, current.frames);
1041
+ current = { frames: [], toolCalls: 0 };
1042
+ turns.push(current);
1043
+ const operatorText =
1044
+ typeof payload?.operatorText === "string" ? [payload.operatorText] : replayTextBlocks(entry).text;
1045
+ replayMessageChunks(current.frames, "user_message_chunk", operatorText);
1046
+ continue;
1047
+ }
1048
+ if (current === null) continue;
1049
+ if (entry.role === "assistant") {
1050
+ const blocks = replayTextBlocks(entry);
1051
+ replayMessageChunks(current.frames, "agent_thought_chunk", blocks.thinking);
1052
+ replayMessageChunks(current.frames, "agent_message_chunk", blocks.text);
1053
+ continue;
1054
+ }
1055
+ if (entry.role === "tool_call") {
1056
+ const call = replayToolCall(entry);
1057
+ const toolCallId = startToolCallId(ids, call.engineId);
1058
+ ids.openToolCalls.add(toolCallId);
1059
+ ids.lastEmittedToolCallId = toolCallId;
1060
+ current.toolCalls += 1;
1061
+ current.frames.push({
1062
+ update: {
1063
+ sessionUpdate: "tool_call",
1064
+ toolCallId,
1065
+ title: call.name,
1066
+ kind: toolKind(call.name),
1067
+ status: "in_progress" satisfies AcpToolCallStatus,
1068
+ rawInput: boundRawRecord(call.args),
1069
+ },
1070
+ });
1071
+ continue;
1072
+ }
1073
+ if (entry.role === "tool_result") {
1074
+ const engineId = typeof payload?.toolCallId === "string" ? payload.toolCallId : undefined;
1075
+ const toolCallId = endToolCallId(ids, engineId);
1076
+ if (toolCallId === null || ids.terminalToolCalls.has(toolCallId)) continue;
1077
+ ids.openToolCalls.delete(toolCallId);
1078
+ ids.terminalToolCalls.add(toolCallId);
1079
+ const failed = payload?.isError === true || payload?.outcome === "error" || payload?.outcome === "blocked";
1080
+ const output = boundString(outputText(payload?.result), ACP_MAX_CHUNK_BYTES);
1081
+ const name = safeStoredString(payload?.toolName, 64, "tool") || "tool";
1082
+ current.frames.push({
1083
+ update: {
1084
+ sessionUpdate: "tool_call_update",
1085
+ toolCallId,
1086
+ title: name,
1087
+ kind: toolKind(name),
1088
+ status: (failed ? "failed" : "completed") satisfies AcpToolCallStatus,
1089
+ ...(output.length > 0 ? { content: toolCallContent(output) } : {}),
1090
+ rawOutput: boundRawRecord({ result: payload?.result, isError: failed }),
1091
+ },
1092
+ });
1093
+ }
1094
+ }
1095
+ if (current !== null) closeUnrecordedReplayCalls(ids, current.frames);
1096
+
1097
+ let selected = turns.slice(-ACP_MAX_REPLAY_TURNS);
1098
+ let truncated = selected.length !== turns.length;
1099
+ let selectedToolCalls = selected.reduce((total, turn) => total + turn.toolCalls, 0);
1100
+ while (selectedToolCalls > ACP_MAX_REPLAY_TOOL_CALLS && selected.length > 0) {
1101
+ selectedToolCalls -= selected[0]?.toolCalls ?? 0;
1102
+ selected = selected.slice(1);
1103
+ truncated = true;
1104
+ }
1105
+ let decorated: Array<AcpSessionUpdateParams & { _meta: Record<string, unknown> }> = [];
1106
+ for (;;) {
1107
+ decorated = selected.flatMap((turn, index) =>
1108
+ turn.frames.map((frame) => ({
1109
+ sessionId,
1110
+ update: frame.update,
1111
+ _meta: { [ACP_REPLAY_META_KEY]: { turn: index + 1 } },
1112
+ })),
1113
+ );
1114
+ const bytes = decorated.reduce(
1115
+ (total, params) =>
1116
+ total + Buffer.byteLength(JSON.stringify({ jsonrpc: "2.0", method: "session/update", params }), "utf8") + 1,
1117
+ 0,
1118
+ );
1119
+ if (bytes <= ACP_MAX_REPLAY_BYTES || selected.length === 0) break;
1120
+ selected = selected.slice(1);
1121
+ truncated = true;
1122
+ }
1123
+ return { params: decorated, turns: selected.length, truncated };
1124
+ }
1125
+
1126
+ function sessionResultMeta(
1127
+ session: AcpServerSession,
1128
+ resumed: boolean,
1129
+ replayed?: { turns: number; truncated: boolean },
1130
+ ): Record<string, unknown> {
1131
+ return {
1132
+ sessionId: session.id,
1133
+ target: session.target,
1134
+ model: session.model,
1135
+ autonomy: session.autonomy,
1136
+ createdAt: safeIso(session.createdAt),
1137
+ resumed,
1138
+ ...(replayed !== undefined ? { replayed } : {}),
1139
+ };
1140
+ }
1141
+
1142
+ const ACP_SAFE_SETTINGS_KEYS = [
1143
+ "orchestrator.target",
1144
+ "orchestrator.model",
1145
+ "orchestrator.thinkingLevel",
1146
+ "autonomy",
1147
+ ] as const;
1148
+ const ACP_THINKING_LEVELS = new Set<AcpThinkingLevel>(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
1149
+ const ACP_AUTONOMY_LEVELS = new Set<AutonomyLevel>(["read-only", "suggest", "auto-edit", "full-auto"]);
1150
+ const ACP_MAX_TARGETS = 64;
1151
+ const ACP_MAX_TARGET_MODELS = 64;
1152
+ // Workbench's frozen reader ceiling is 256 KiB per JSON-RPC line. Reserve
1153
+ // 16 KiB for the response envelope/request id and keep this result a stable
1154
+ // prefix of whole targets/model ids.
1155
+ const ACP_MAX_TARGET_LIST_RESULT_BYTES = 240 * 1024;
1156
+ const ACP_EMPTY_TRUNCATED_TARGET_LIST_BYTES = utf8Bytes(
1157
+ JSON.stringify({ targets: [], _meta: { "clio-coder/truncated": true } }),
1158
+ );
1159
+ // Session summaries share the same Workbench JSON-RPC line ceiling. Keep a
1160
+ // stable newest-first prefix of whole rows and reserve 16 KiB for the envelope.
1161
+ const ACP_MAX_SESSION_LIST_RESULT_BYTES = 240 * 1024;
1162
+ const ACP_EMPTY_TRUNCATED_SESSION_LIST_BYTES = utf8Bytes(
1163
+ JSON.stringify({ sessions: [], truncated: true, _meta: { "clio-coder/truncated": true } }),
1164
+ );
1165
+
1166
+ function safeSettingsProjection(snapshot: AcpSafeSettingsSnapshot): Record<string, unknown> {
1167
+ const thinkingLevel = ACP_THINKING_LEVELS.has(snapshot.thinkingLevel) ? snapshot.thinkingLevel : "off";
1168
+ const autonomy = ACP_AUTONOMY_LEVELS.has(snapshot.autonomy) ? snapshot.autonomy : DEFAULT_AUTONOMY_LEVEL;
1169
+ return {
1170
+ settings: {
1171
+ orchestrator: {
1172
+ target: safeConfiguredIdentifier(snapshot.target, ACP_MAX_TARGET_ID_BYTES),
1173
+ model: safeConfiguredIdentifier(snapshot.model, ACP_MAX_MODEL_ID_BYTES),
1174
+ thinkingLevel,
1175
+ },
1176
+ autonomy,
1177
+ },
1178
+ editable: [...ACP_SAFE_SETTINGS_KEYS],
1179
+ };
1180
+ }
1181
+
1182
+ function safeTargetModels(status: ReturnType<ProvidersContract["list"]>[number]): string[] {
1183
+ const candidates = [status.target.defaultModel, ...(status.target.wireModels ?? []), ...status.discoveredModels];
1184
+ const seen = new Set<string>();
1185
+ const models: string[] = [];
1186
+ for (const candidate of candidates) {
1187
+ const id = safeStoredIdentifier(candidate, ACP_MAX_MODEL_ID_BYTES);
1188
+ if (id === null || seen.has(id)) continue;
1189
+ seen.add(id);
1190
+ models.push(id);
1191
+ if (models.length === ACP_MAX_TARGET_MODELS) break;
1192
+ }
1193
+ return models;
1194
+ }
1195
+
1196
+ interface AcpSafeTargetProjection {
1197
+ id: string;
1198
+ runtime: string;
1199
+ models: string[];
1200
+ isOrchestrator: boolean;
1201
+ }
1202
+
1203
+ function safeTargetProjection(status: ReturnType<ProvidersContract["list"]>[number]): AcpSafeTargetProjection | null {
1204
+ const id = safeStoredIdentifier(status.target.id, ACP_MAX_TARGET_ID_BYTES);
1205
+ const runtime = safeStoredIdentifier(status.target.runtime, 64);
1206
+ if (id === null || runtime === null) return null;
1207
+ return {
1208
+ id,
1209
+ runtime,
1210
+ models: safeTargetModels(status),
1211
+ isOrchestrator: status.runtime !== null && isOrchestratorEligibleRuntime(status.runtime),
1212
+ };
1213
+ }
1214
+
1215
+ function safeProbeReason(
1216
+ providers: ProvidersContract,
1217
+ status: ReturnType<ProvidersContract["list"]>[number],
1218
+ ): "not-configured" | "unreachable" | "unsupported" | null {
1219
+ if (status.runtime === null || typeof status.runtime.probe !== "function") return "unsupported";
1220
+ try {
1221
+ if (status.runtime.auth !== "none" && !providers.auth.statusForTarget(status.target, status.runtime).available) {
1222
+ return "not-configured";
1223
+ }
1224
+ } catch {
1225
+ return "not-configured";
1226
+ }
1227
+ return status.available && status.health.status === "healthy" ? null : "unreachable";
1228
+ }
1229
+
1230
+ interface AcpPermissionBridge {
1231
+ unregister(): void;
1232
+ /** Settles the outstanding permission request, if any, as cancellation. */
1233
+ cancelPending(reason: string): void;
1234
+ }
1235
+
1236
+ function installPermissionBridge(input: {
1237
+ transport: AcpJsonRpcPeerTransport;
1238
+ toolRegistry: ToolRegistry | undefined;
1239
+ bus?: SafeEventBus;
1240
+ activeSessionId: () => string | null;
1241
+ /**
1242
+ * The wire id of the engine id's most recently opened tool call, or null when
1243
+ * it names no call the client currently has open. Lookup only: this never
1244
+ * mints a wire id, so an engine id nothing was emitted for fails closed.
1245
+ */
1246
+ resolveToolCallId: (engineToolCallId: string) => string | null;
1247
+ /** The wire id of the turn's one open tool call, or null when it is not exactly one. */
1248
+ soleOpenToolCallId: () => string | null;
1249
+ /**
1250
+ * What this wire id's `tool_call` update put on the wire, or null when this
1251
+ * turn emitted no such call. The ask repeats it verbatim; nothing here is
1252
+ * rebuilt from the registry's arguments.
1253
+ */
1254
+ toolCallSnapshot: (wireId: string) => AcpToolCallSnapshot | null;
1255
+ permissionTimeoutMs: number;
1256
+ expireActivePrompt: () => void;
1257
+ }): AcpPermissionBridge {
1258
+ if (!input.toolRegistry) return { unregister: () => {}, cancelPending: () => {} };
1259
+ let pendingCancel: ((reason: string) => void) | null = null;
1260
+ let chain = Promise.resolve();
1261
+ const queuedRequestIds = new Set<string>();
1262
+ const queuedRequestDetails = new Map<string, { tool: string; actionClass: string }>();
1263
+ const unregister = input.toolRegistry.onPermissionRequired((call, decision, meta) => {
1264
+ if (queuedRequestIds.has(meta.requestId)) return;
1265
+ queuedRequestIds.add(meta.requestId);
1266
+ queuedRequestDetails.set(meta.requestId, {
1267
+ tool: call.tool,
1268
+ actionClass: decision.classification.actionClass,
1269
+ });
1270
+ const emitResolution = (payload: {
1271
+ status: "granted" | "denied" | "expired";
1272
+ decidedBy: string;
1273
+ reason?: string;
1274
+ }): void => {
1275
+ input.bus?.emit(BusChannels.PermissionResolved, {
1276
+ status: payload.status,
1277
+ requestId: meta.requestId,
1278
+ origin: "acp-server",
1279
+ decidedBy: payload.decidedBy,
1280
+ tool: call.tool,
1281
+ actionClass: decision.classification.actionClass,
1282
+ ...(payload.reason !== undefined ? { reason: payload.reason } : {}),
1283
+ });
1284
+ };
1285
+ const emitQueuedErrorResolutions = (currentRequestId: string, reason: string): void => {
1286
+ for (const requestId of queuedRequestIds) {
1287
+ if (requestId === currentRequestId) continue;
1288
+ const details = queuedRequestDetails.get(requestId);
1289
+ input.bus?.emit(BusChannels.PermissionResolved, {
1290
+ status: "denied",
1291
+ requestId,
1292
+ origin: "acp-server",
1293
+ decidedBy: "error",
1294
+ ...(details !== undefined ? { tool: details.tool, actionClass: details.actionClass } : {}),
1295
+ reason,
1296
+ });
1297
+ }
1298
+ };
1299
+ const emitQueuedExpiryResolutions = (currentRequestId: string): void => {
1300
+ for (const requestId of queuedRequestIds) {
1301
+ if (requestId === currentRequestId) continue;
1302
+ const details = queuedRequestDetails.get(requestId);
1303
+ input.bus?.emit(BusChannels.PermissionResolved, {
1304
+ status: "expired",
1305
+ requestId,
1306
+ origin: "acp-server",
1307
+ decidedBy: "timeout",
1308
+ ...(details !== undefined ? { tool: details.tool, actionClass: details.actionClass } : {}),
1309
+ reason: "permission approval expired",
1310
+ });
1311
+ }
1312
+ };
1313
+ const run = async (): Promise<void> => {
1314
+ if (!queuedRequestIds.has(meta.requestId)) return;
1315
+ const sessionId = input.activeSessionId();
1316
+ const noSessionReason = "ACP permission requested with no active session";
1317
+ if (!sessionId) {
1318
+ emitResolution({
1319
+ status: "denied",
1320
+ decidedBy: "error",
1321
+ reason: noSessionReason,
1322
+ });
1323
+ emitQueuedErrorResolutions(meta.requestId, noSessionReason);
1324
+ queuedRequestIds.clear();
1325
+ queuedRequestDetails.clear();
1326
+ input.toolRegistry?.cancelParkedCalls(noSessionReason);
1327
+ return;
1328
+ }
1329
+ // The client already rendered a tool_call under the engine's id; asking
1330
+ // about a different id would present the operator with a call they
1331
+ // cannot see. So the engine's id is resolved by lookup and nothing
1332
+ // else: an id no tool_call was emitted for, or one whose call the
1333
+ // client has already seen finish, binds to nothing. When the bridge
1334
+ // gets no engine id, the turn's one open call is the only binding that
1335
+ // can be right. Everything else fails closed: a bridge-local id nothing
1336
+ // on the client matches asked the operator to approve a call they had
1337
+ // no way to identify.
1338
+ const toolCallId =
1339
+ meta.toolCallId !== undefined && meta.toolCallId.length > 0
1340
+ ? input.resolveToolCallId(meta.toolCallId)
1341
+ : input.soleOpenToolCallId();
1342
+ // The operator is being shown a call the client already rendered, so the
1343
+ // arguments in the ask are that frame's own: a tool's admission normalizer
1344
+ // can rewrite a relative path to an absolute one or attach a prepared
1345
+ // artifact, and rebuilding the record from the registry's copy produced a
1346
+ // `rawInput` the client could not match to anything it had drawn. A bound
1347
+ // id always has a snapshot, since every open call stored one when it was
1348
+ // emitted; the missing case fails closed anyway.
1349
+ const snapshot = toolCallId === null ? null : input.toolCallSnapshot(toolCallId);
1350
+ if (toolCallId === null || snapshot === null) {
1351
+ const unbindableReason = "permission request has no bindable tool call";
1352
+ emitResolution({ status: "denied", decidedBy: "error", reason: unbindableReason });
1353
+ input.toolRegistry?.cancelParkedCall(meta.requestId, unbindableReason);
1354
+ queuedRequestIds.delete(meta.requestId);
1355
+ queuedRequestDetails.delete(meta.requestId);
1356
+ return;
1357
+ }
1358
+ // The transport has no per-request abort, so a cancelled prompt races
1359
+ // the outstanding request against a local deferral. The client's late
1360
+ // answer resolves a promise nothing is waiting on any more.
1361
+ const cancelled = new Promise<{ kind: "cancelled"; reason: string }>((resolveCancelled) => {
1362
+ pendingCancel = (reason: string) => resolveCancelled({ kind: "cancelled", reason });
1363
+ });
1364
+ try {
1365
+ const answered = Promise.resolve()
1366
+ .then(() =>
1367
+ input.transport.request<AcpRequestPermissionResponse>(
1368
+ "session/request_permission",
1369
+ {
1370
+ sessionId,
1371
+ toolCall: {
1372
+ sessionUpdate: "tool_call",
1373
+ toolCallId,
1374
+ title: boundString(call.tool, ACP_MAX_TOOL_TITLE_BYTES),
1375
+ kind: toolKind(call.tool),
1376
+ status: "pending",
1377
+ rawInput: snapshot.rawInput,
1378
+ ...(snapshot.locations !== undefined ? { locations: snapshot.locations } : {}),
1379
+ },
1380
+ options: [
1381
+ { optionId: "allow-once", name: "Allow once", kind: "allow_once" },
1382
+ { optionId: "reject-once", name: "Reject", kind: "reject_once" },
1383
+ ],
1384
+ },
1385
+ input.permissionTimeoutMs,
1386
+ ),
1387
+ )
1388
+ .then(
1389
+ (response) => ({ kind: "response" as const, response }),
1390
+ (err: unknown) => ({ kind: "failed" as const, err }),
1391
+ );
1392
+ const outcome = await Promise.race([answered, cancelled]);
1393
+ if (outcome.kind === "cancelled") {
1394
+ emitResolution({ status: "denied", decidedBy: "cancelled", reason: outcome.reason });
1395
+ input.toolRegistry?.cancelParkedCall(meta.requestId, outcome.reason);
1396
+ return;
1397
+ }
1398
+ if (outcome.kind === "response") {
1399
+ // Exactly one option grants. `startsWith("allow")` would let a
1400
+ // client mint `allow-always` and get a grant this server never
1401
+ // offered as an option.
1402
+ const answer = outcome.response.outcome;
1403
+ if (answer.outcome === "selected" && answer.optionId === "allow-once") {
1404
+ emitResolution({ status: "granted", decidedBy: "acp-client" });
1405
+ await input.toolRegistry?.resumeParkedCalls({
1406
+ actionClass: decision.classification.actionClass,
1407
+ requestId: meta.requestId,
1408
+ requestedBy: "acp-client",
1409
+ });
1410
+ return;
1411
+ }
1412
+ emitResolution({
1413
+ status: "denied",
1414
+ decidedBy: "acp-client",
1415
+ reason: "ACP client denied this tool call",
1416
+ });
1417
+ input.toolRegistry?.cancelParkedCall(meta.requestId, "ACP client denied this tool call");
1418
+ return;
1419
+ }
1420
+ const err = outcome.err;
1421
+ const message = `ACP permission request failed: ${err instanceof Error ? err.message : String(err)}`;
1422
+ if (err instanceof AcpTimeoutError) {
1423
+ emitResolution({
1424
+ status: "expired",
1425
+ decidedBy: "timeout",
1426
+ reason: "permission approval expired",
1427
+ });
1428
+ emitQueuedExpiryResolutions(meta.requestId);
1429
+ queuedRequestIds.clear();
1430
+ queuedRequestDetails.clear();
1431
+ // The registry has no non-denial settlement primitive. Cancel every
1432
+ // parked call only to let the aborted run unwind; the prompt handler
1433
+ // fails terminally before the model can observe/retry those verdicts.
1434
+ input.toolRegistry?.cancelParkedCalls("permission approval expired");
1435
+ input.expireActivePrompt();
1436
+ return;
1437
+ }
1438
+ emitResolution({
1439
+ status: "denied",
1440
+ decidedBy: "error",
1441
+ reason: message,
1442
+ });
1443
+ emitQueuedErrorResolutions(meta.requestId, message);
1444
+ queuedRequestIds.clear();
1445
+ queuedRequestDetails.clear();
1446
+ input.toolRegistry?.cancelParkedCalls(message);
1447
+ } finally {
1448
+ pendingCancel = null;
1449
+ queuedRequestIds.delete(meta.requestId);
1450
+ queuedRequestDetails.delete(meta.requestId);
1451
+ }
1452
+ };
1453
+ chain = chain.then(run, run);
1454
+ });
1455
+ return {
1456
+ unregister,
1457
+ cancelPending: (reason: string) => {
1458
+ pendingCancel?.(reason);
1459
+ },
1460
+ };
1461
+ }
1462
+
1463
+ /** How long transport close waits for an in-flight prompt handler to settle. */
1464
+ const ACP_PROMPT_SETTLE_BOUND_MS = 5000;
1465
+
1466
+ export async function serveClioAcpAgent(options: ClioAcpServerOptions): Promise<number> {
1467
+ const sessions = new Map<string, AcpServerSession>();
1468
+ const workspaceInstanceId = randomUUID();
1469
+ let initialized = false;
1470
+ let loopBlockedEventsEnabled = false;
1471
+ let eventSequence = 0;
1472
+ let activeSessionId: string | null = null;
1473
+ let activePromptState: ActivePrompt | null = null;
1474
+ let sessionCreated = false;
1475
+ let promptSettled: Promise<void> | null = null;
1476
+ const closedSessionIds = new Set<string>();
1477
+ // The launch cwd is the process's one workspace identity: settings, project
1478
+ // context, and the session ledger were all resolved against it during boot.
1479
+ // It is canonicalized once here so a client that passes a symlinked path, a
1480
+ // trailing slash, or a `/.` suffix is recognised rather than refused.
1481
+ const canonicalCwd = realpathSync(options.cwd ?? process.cwd());
1482
+ const permissionTimeoutMs = options.permissionTimeoutMs ?? DEFAULT_DELEGATION_PERMISSION_TIMEOUT_MS;
1483
+ if (
1484
+ !Number.isSafeInteger(permissionTimeoutMs) ||
1485
+ permissionTimeoutMs < 1 ||
1486
+ permissionTimeoutMs > MAX_TIMER_DELAY_MS
1487
+ ) {
1488
+ throw new Error(`ACP permission timeout must be between 1 and ${MAX_TIMER_DELAY_MS} milliseconds`);
1489
+ }
1490
+ const permission = installPermissionBridge({
1491
+ transport: options.transport,
1492
+ toolRegistry: options.toolRegistry,
1493
+ ...(options.bus ? { bus: options.bus } : {}),
1494
+ activeSessionId: () => activeSessionId,
1495
+ resolveToolCallId: (engineToolCallId) =>
1496
+ activePromptState === null ? null : openWireIdFor(activePromptState, engineToolCallId),
1497
+ soleOpenToolCallId: () => {
1498
+ if (activePromptState === null || activePromptState.openToolCalls.size !== 1) return null;
1499
+ const [only] = activePromptState.openToolCalls;
1500
+ return only ?? null;
1501
+ },
1502
+ toolCallSnapshot: (wireId) =>
1503
+ activePromptState === null ? null : (activePromptState.toolCallSnapshots.get(wireId) ?? null),
1504
+ permissionTimeoutMs,
1505
+ expireActivePrompt: () => {
1506
+ if (activePromptState === null) return;
1507
+ activePromptState.permissionExpired = true;
1508
+ options.chat.cancel();
1509
+ },
1510
+ });
1511
+ const unsubscribeLoopBlocked =
1512
+ options.bus?.on(BusChannels.LoopBlocked, (payload: LoopBlockedPayload) => {
1513
+ if (!initialized || !loopBlockedEventsEnabled || activeSessionId === null || activePromptState === null) return;
1514
+ if (
1515
+ !Number.isSafeInteger(payload.repeatCount) ||
1516
+ payload.repeatCount < 1 ||
1517
+ !Number.isSafeInteger(payload.blocksThisTurn) ||
1518
+ payload.blocksThisTurn < 1 ||
1519
+ !Number.isSafeInteger(payload.budget) ||
1520
+ payload.budget < 1 ||
1521
+ !(["block", "lockout", "stop"] as ReadonlyArray<string>).includes(payload.disposition) ||
1522
+ payload.interrupted !== (payload.disposition === "stop")
1523
+ ) {
1524
+ return;
1525
+ }
1526
+ const tool = safeStoredString(payload.tool, 64).trim();
1527
+ if (tool.length === 0) return;
1528
+ eventSequence += 1;
1529
+ try {
1530
+ options.transport.notify("clio-coder/event", {
1531
+ version: 1,
1532
+ workspaceInstanceId,
1533
+ sessionId: activeSessionId,
1534
+ turnId: safeStoredIdentifier(payload.turnId, ACP_MAX_SESSION_ID_BYTES),
1535
+ sequence: eventSequence,
1536
+ kind: "safety.loopBlocked",
1537
+ terminal: false,
1538
+ payload: {
1539
+ toolCallId: null,
1540
+ tool,
1541
+ repeatCount: payload.repeatCount,
1542
+ blocksThisTurn: payload.blocksThisTurn,
1543
+ budget: payload.budget,
1544
+ disposition: payload.disposition,
1545
+ interrupted: payload.interrupted,
1546
+ shape: null,
1547
+ },
1548
+ });
1549
+ } catch {
1550
+ options.diagnostics?.("failed to send an opted-in ACP event");
1551
+ }
1552
+ }) ?? (() => {});
1553
+
1554
+ const requireInitialized = (): void => {
1555
+ if (!initialized) throw new AcpRequestError(-32000, "initialize must be called first", { code: "not_initialized" });
1556
+ };
1557
+
1558
+ const sessionIdOf = (params: unknown): string => {
1559
+ return requireBoundedClientString(
1560
+ isRecord(params) ? params.sessionId : undefined,
1561
+ "sessionId",
1562
+ ACP_MAX_SESSION_ID_BYTES,
1563
+ );
1564
+ };
1565
+
1566
+ const assertParamKeys = (params: unknown, allowed: ReadonlySet<string>): Record<string, unknown> => {
1567
+ if (params === undefined) return {};
1568
+ if (!isRecord(params) || Object.keys(params).some((key) => !allowed.has(key))) {
1569
+ throw new AcpRequestError(-32602, "invalid method parameters", { code: "invalid_params" });
1570
+ }
1571
+ return params;
1572
+ };
1573
+
1574
+ const canonicalSessionCwd = (requested: unknown): string => {
1575
+ const mismatch = (): AcpRequestError =>
1576
+ new AcpRequestError(-32000, "session cwd does not match the server workspace", {
1577
+ code: "session_cwd_mismatch",
1578
+ });
1579
+ if (typeof requested !== "string" || requested.trim().length === 0 || !isAbsolute(requested)) throw mismatch();
1580
+ let sessionCwd: string;
1581
+ try {
1582
+ sessionCwd = realpathSync(resolvePath(requested));
1583
+ } catch {
1584
+ throw mismatch();
1585
+ }
1586
+ if (sessionCwd !== canonicalCwd) throw mismatch();
1587
+ return sessionCwd;
1588
+ };
1589
+
1590
+ const workspaceHistory = (): SessionMeta[] => {
1591
+ const history = options.session?.history() ?? [];
1592
+ return history.filter((meta) => {
1593
+ if (safeStoredIdentifier(meta.id, ACP_MAX_SESSION_ID_BYTES) === null) return false;
1594
+ try {
1595
+ return realpathSync(resolvePath(meta.cwd)) === canonicalCwd;
1596
+ } catch {
1597
+ return false;
1598
+ }
1599
+ });
1600
+ };
1601
+
1602
+ const workspaceMeta = (sessionId: string): SessionMeta => {
1603
+ const meta = workspaceHistory().find((candidate) => candidate.id === sessionId);
1604
+ if (!meta) throw new AcpRequestError(-32000, "unknown ACP session", { code: "session_unknown" });
1605
+ return meta;
1606
+ };
1607
+
1608
+ const routingSnapshot = (): AcpRoutingSnapshot => {
1609
+ const route = options.routing?.() ?? { target: null, model: null };
1610
+ return {
1611
+ target: safeConfiguredIdentifier(route.target, ACP_MAX_TARGET_ID_BYTES),
1612
+ model: safeConfiguredIdentifier(route.model, ACP_MAX_MODEL_ID_BYTES),
1613
+ };
1614
+ };
1615
+
1616
+ const getSession = (params: unknown): AcpServerSession => {
1617
+ const id = sessionIdOf(params);
1618
+ const session = sessions.get(id);
1619
+ if (!session) throw new AcpRequestError(-32000, "unknown ACP session", { code: "session_unknown" });
1620
+ return session;
1621
+ };
1622
+
1623
+ options.transport.onRequest("initialize", (params) => {
1624
+ if (initialized) throw new AcpRequestError(-32000, "already initialized", { code: "already_initialized" });
1625
+ // A client that asked for a version this server does not speak must be
1626
+ // told so. Accepting it silently would leave a future v2 client believing
1627
+ // negotiation succeeded while every frame it receives is v1.
1628
+ const version = isRecord(params) ? params.protocolVersion : undefined;
1629
+ if (version !== 1) {
1630
+ throw new AcpRequestError(-32602, "unsupported ACP protocol version", {
1631
+ code: "protocol_version_unsupported",
1632
+ supported: [1],
1633
+ });
1634
+ }
1635
+ const clientCapabilities = isRecord(params) && isRecord(params.clientCapabilities) ? params.clientCapabilities : null;
1636
+ const clientMeta =
1637
+ clientCapabilities !== null && isRecord(clientCapabilities._meta) ? clientCapabilities._meta : null;
1638
+ const eventRequest =
1639
+ clientMeta !== null && isRecord(clientMeta["clio-coder/events"]) ? clientMeta["clio-coder/events"] : null;
1640
+ const requestedEventKinds = eventRequest !== null ? eventRequest.kinds : null;
1641
+ loopBlockedEventsEnabled =
1642
+ eventRequest !== null &&
1643
+ eventRequest.version === 1 &&
1644
+ Array.isArray(requestedEventKinds) &&
1645
+ requestedEventKinds.length <= 16 &&
1646
+ requestedEventKinds.every(
1647
+ (kind) => typeof kind === "string" && utf8Bytes(kind) <= 64 && !hasControlCharacters(kind),
1648
+ ) &&
1649
+ requestedEventKinds.includes("safety.loopBlocked");
1650
+ const canLoadSession =
1651
+ options.session !== undefined &&
1652
+ options.readSessionEntries !== undefined &&
1653
+ options.buildReplayMessages !== undefined &&
1654
+ options.chat.resetForSession !== undefined;
1655
+ initialized = true;
1656
+ return {
1657
+ protocolVersion: 1,
1658
+ agentInfo: {
1659
+ name: "clio-coder",
1660
+ title: "Clio Coder",
1661
+ ...(options.version !== undefined ? { version: options.version } : {}),
1662
+ },
1663
+ agentCapabilities: {
1664
+ loadSession: canLoadSession,
1665
+ promptCapabilities: { audio: false, embeddedContext: false, image: false },
1666
+ mcpCapabilities: { http: false, sse: false },
1667
+ // Clio mediates every tool through its own safety policy and supports an
1668
+ // explicit session/close (a documented ACP RFD, not yet in the stable
1669
+ // schema). Both are advertised via the _meta extension slot so strict
1670
+ // clients never observe a non-spec capability field.
1671
+ _meta: {
1672
+ [ACP_SESSION_META_KEY]: {
1673
+ close: true,
1674
+ list: options.session !== undefined,
1675
+ label: options.session !== undefined,
1676
+ delete: options.session !== undefined,
1677
+ autonomy: true,
1678
+ },
1679
+ "clio-coder/settings": {
1680
+ get_safe: options.settings !== undefined,
1681
+ patch_safe: options.settings !== undefined,
1682
+ },
1683
+ "clio-coder/targets": {
1684
+ list: options.providers !== undefined,
1685
+ probe: options.providers !== undefined,
1686
+ },
1687
+ ...(options.bus
1688
+ ? {
1689
+ "clio-coder/events": {
1690
+ version: 1,
1691
+ notification: "clio-coder/event",
1692
+ kinds: ["safety.loopBlocked"],
1693
+ workspaceInstanceId,
1694
+ },
1695
+ }
1696
+ : {}),
1697
+ ...(options.toolRegistry !== undefined ? { "clio-coder/tools": "mediated" } : {}),
1698
+ },
1699
+ },
1700
+ authMethods: [],
1701
+ } satisfies AcpInitializeResponse;
1702
+ });
1703
+
1704
+ options.transport.onRequest("session/new", (params) => {
1705
+ requireInitialized();
1706
+ // One chat instance, one provider context, one ledger ancestry. A second
1707
+ // session id over the same process would share all three with the first,
1708
+ // so the honest answer is to refuse and let the client start a process.
1709
+ if (sessionCreated) {
1710
+ throw new AcpRequestError(-32000, "this server hosts one session per process", { code: "session_limit" });
1711
+ }
1712
+ const sessionCwd = canonicalSessionCwd(isRecord(params) ? params.cwd : undefined);
1713
+ const route = routingSnapshot();
1714
+ const createInput: { cwd: string; target?: string; model?: string } = { cwd: sessionCwd };
1715
+ if (route.target !== null) createInput.target = route.target;
1716
+ if (route.model !== null) createInput.model = route.model;
1717
+ const meta = options.session?.create(createInput);
1718
+ const id = meta?.id ?? randomUUID();
1719
+ if (safeStoredIdentifier(id, ACP_MAX_SESSION_ID_BYTES) === null) {
1720
+ throw new AcpRequestError(-32000, "session creation failed", { code: "internal_error" });
1721
+ }
1722
+ const autonomy = options.autonomy?.() ?? DEFAULT_AUTONOMY_LEVEL;
1723
+ const boundTarget = safeConfiguredIdentifier(meta?.target ?? route.target, ACP_MAX_TARGET_ID_BYTES);
1724
+ const boundModel = safeConfiguredIdentifier(meta?.model ?? route.model, ACP_MAX_MODEL_ID_BYTES);
1725
+ const session: AcpServerSession = {
1726
+ id,
1727
+ cwd: sessionCwd,
1728
+ autonomy,
1729
+ autonomySource: "settings",
1730
+ target: boundTarget,
1731
+ model: boundModel,
1732
+ createdAt: meta?.createdAt ?? new Date().toISOString(),
1733
+ activePrompt: null,
1734
+ };
1735
+ sessionCreated = true;
1736
+ sessions.set(id, session);
1737
+ // NewSessionResponse is { sessionId, modes?, models?, _meta? }; cwd is not a
1738
+ // schema field. Clio runs a single-session-per-process server pinned to the
1739
+ // launch cwd, so no extra fields are needed.
1740
+ return { sessionId: id, _meta: { [ACP_SESSION_META_KEY]: sessionResultMeta(session, false) } };
1741
+ });
1742
+
1743
+ options.transport.onRequest("session/load", async (params) => {
1744
+ requireInitialized();
1745
+ if (sessionCreated) {
1746
+ throw new AcpRequestError(-32000, "this server hosts one session per process", { code: "session_limit" });
1747
+ }
1748
+ const request = assertParamKeys(params, new Set(["sessionId", "cwd", "mcpServers"]));
1749
+ const id = sessionIdOf(request);
1750
+ canonicalSessionCwd(request.cwd);
1751
+ if (!Array.isArray(request.mcpServers) || request.mcpServers.length !== 0) {
1752
+ throw new AcpRequestError(-32602, "mcpServers must be an empty array", { code: "invalid_params" });
1753
+ }
1754
+ const stored = workspaceMeta(id);
1755
+ if (stored.endedAt === null) {
1756
+ throw new AcpRequestError(-32000, "session may already be open", { code: "session_open" });
1757
+ }
1758
+ const storedTarget = safeConfiguredIdentifier(stored.target, ACP_MAX_TARGET_ID_BYTES);
1759
+ const storedModel = safeConfiguredIdentifier(stored.model, ACP_MAX_MODEL_ID_BYTES);
1760
+ if (
1761
+ options.session === undefined ||
1762
+ options.readSessionEntries === undefined ||
1763
+ options.buildReplayMessages === undefined ||
1764
+ options.chat.resetForSession === undefined
1765
+ ) {
1766
+ throw new AcpRequestError(-32000, "session loading is unavailable", { code: "internal_error" });
1767
+ }
1768
+
1769
+ let leafTurnId: string | null;
1770
+ let replayMessages: ReadonlyArray<AgentMessage>;
1771
+ let replay: PreparedAcpReplay;
1772
+ try {
1773
+ leafTurnId = options.session.tree(id).leafId;
1774
+ const entries = options.readSessionEntries(id);
1775
+ replayMessages = options.buildReplayMessages(entries, leafTurnId);
1776
+ replay = prepareAcpReplay(entries, leafTurnId, id);
1777
+ } catch {
1778
+ throw new AcpRequestError(-32000, "session could not be loaded", { code: "internal_error" });
1779
+ }
1780
+
1781
+ let resumed: SessionMeta;
1782
+ try {
1783
+ resumed = options.session.resume(id);
1784
+ if (resumed.id !== id || options.session.current()?.id !== id) throw new Error("resume identity mismatch");
1785
+ options.chat.resetForSession(leafTurnId, replayMessages);
1786
+ } catch {
1787
+ if (options.session.current()?.id === id) {
1788
+ try {
1789
+ await options.session.close();
1790
+ } catch {
1791
+ // Best effort: no replay has been emitted and the slot remains unused.
1792
+ }
1793
+ }
1794
+ throw new AcpRequestError(-32000, "session could not be loaded", { code: "internal_error" });
1795
+ }
1796
+
1797
+ const autonomy = options.autonomy?.() ?? DEFAULT_AUTONOMY_LEVEL;
1798
+ const session: AcpServerSession = {
1799
+ id,
1800
+ cwd: canonicalCwd,
1801
+ autonomy,
1802
+ autonomySource: "settings",
1803
+ target: storedTarget,
1804
+ model: storedModel,
1805
+ createdAt: stored.createdAt,
1806
+ activePrompt: null,
1807
+ };
1808
+ sessionCreated = true;
1809
+ sessions.set(id, session);
1810
+ for (const replayParams of replay.params) options.transport.notify("session/update", replayParams);
1811
+ return {
1812
+ _meta: {
1813
+ [ACP_SESSION_META_KEY]: sessionResultMeta(session, true, {
1814
+ turns: replay.turns,
1815
+ truncated: replay.truncated,
1816
+ }),
1817
+ },
1818
+ };
1819
+ });
1820
+
1821
+ options.transport.onRequest("clio-coder/session/list", (params) => {
1822
+ requireInitialized();
1823
+ const request = assertParamKeys(params, new Set(["limit"]));
1824
+ const limit = request.limit === undefined ? 50 : request.limit;
1825
+ if (!Number.isSafeInteger(limit) || (limit as number) < 1 || (limit as number) > 200) {
1826
+ throw new AcpRequestError(-32602, "limit must be an integer from 1 to 200", { code: "invalid_params" });
1827
+ }
1828
+ const history = workspaceHistory();
1829
+ const projected: Array<Record<string, unknown>> = [];
1830
+ let budgetBytes = ACP_EMPTY_TRUNCATED_SESSION_LIST_BYTES;
1831
+ let budgetExhausted = false;
1832
+ for (const meta of history.slice(0, limit as number)) {
1833
+ const hosted = sessions.has(meta.id);
1834
+ const label = safeStoredString(meta.name, ACP_MAX_LABEL_BYTES).trim();
1835
+ const preview = safeStoredString(meta.firstMessagePreview, ACP_MAX_PREVIEW_BYTES).replace(/\s+/gu, " ").trim();
1836
+ const item = {
1837
+ sessionId: meta.id,
1838
+ label: label.length > 0 ? label : null,
1839
+ preview,
1840
+ createdAt: safeIso(meta.createdAt),
1841
+ updatedAt: safeIso(meta.lastActivityAt ?? meta.endedAt ?? meta.createdAt),
1842
+ turns: Number.isSafeInteger(meta.messageCount) && (meta.messageCount ?? 0) >= 0 ? meta.messageCount : 0,
1843
+ target: safeStoredIdentifier(meta.target, ACP_MAX_TARGET_ID_BYTES),
1844
+ model: safeStoredIdentifier(meta.model, ACP_MAX_MODEL_ID_BYTES),
1845
+ state: hosted ? "open" : meta.endedAt === null ? "unknown" : "closed",
1846
+ hosted,
1847
+ };
1848
+ const itemBytes = utf8Bytes(JSON.stringify(item));
1849
+ const separatorBytes = projected.length > 0 ? 1 : 0;
1850
+ if (budgetBytes + separatorBytes + itemBytes > ACP_MAX_SESSION_LIST_RESULT_BYTES) {
1851
+ budgetExhausted = true;
1852
+ break;
1853
+ }
1854
+ projected.push(item);
1855
+ budgetBytes += separatorBytes + itemBytes;
1856
+ }
1857
+ return {
1858
+ sessions: projected,
1859
+ truncated: history.length > projected.length,
1860
+ ...(budgetExhausted ? { _meta: { "clio-coder/truncated": true } } : {}),
1861
+ };
1862
+ });
1863
+
1864
+ options.transport.onRequest("clio-coder/session/label", (params) => {
1865
+ requireInitialized();
1866
+ const request = assertParamKeys(params, new Set(["sessionId", "label"]));
1867
+ const id = sessionIdOf(request);
1868
+ workspaceMeta(id);
1869
+ const label = requireBoundedClientString(request.label, "label", ACP_MAX_LABEL_BYTES, { allowEmpty: true });
1870
+ if (options.session === undefined) {
1871
+ throw new AcpRequestError(-32000, "session naming is unavailable", { code: "internal_error" });
1872
+ }
1873
+ try {
1874
+ options.session.setName(label, id);
1875
+ } catch {
1876
+ throw new AcpRequestError(-32000, "session label could not be saved", { code: "internal_error" });
1877
+ }
1878
+ return {};
1879
+ });
1880
+
1881
+ options.transport.onRequest("clio-coder/session/delete", (params) => {
1882
+ requireInitialized();
1883
+ const request = assertParamKeys(params, new Set(["sessionId"]));
1884
+ const id = sessionIdOf(request);
1885
+ const meta = workspaceMeta(id);
1886
+ if (sessions.has(id) || meta.endedAt === null) {
1887
+ throw new AcpRequestError(-32000, "session may already be open", { code: "session_open" });
1888
+ }
1889
+ if (options.session === undefined) {
1890
+ throw new AcpRequestError(-32000, "session deletion is unavailable", { code: "internal_error" });
1891
+ }
1892
+ try {
1893
+ options.session.deleteSession(id);
1894
+ } catch {
1895
+ throw new AcpRequestError(-32000, "session could not be deleted", { code: "internal_error" });
1896
+ }
1897
+ return {};
1898
+ });
1899
+
1900
+ options.transport.onRequest("clio-coder/session/autonomy", (params) => {
1901
+ requireInitialized();
1902
+ const request = assertParamKeys(params, new Set(["sessionId", "level"]));
1903
+ const session = getSession(request);
1904
+ if (request.level === undefined) return { level: session.autonomy, source: session.autonomySource };
1905
+ if (
1906
+ typeof request.level !== "string" ||
1907
+ !(["read-only", "suggest", "auto-edit", "full-auto"] as ReadonlyArray<string>).includes(request.level)
1908
+ ) {
1909
+ throw new AcpRequestError(-32602, "invalid autonomy level", { code: "invalid_params" });
1910
+ }
1911
+ if (session.activePrompt !== null) {
1912
+ throw new AcpRequestError(-32000, "cannot change autonomy during an active prompt", { code: "prompt_active" });
1913
+ }
1914
+ session.autonomy = request.level as AutonomyLevel;
1915
+ session.autonomySource = "session";
1916
+ return { level: session.autonomy, source: session.autonomySource };
1917
+ });
1918
+
1919
+ options.transport.onRequest("clio-coder/settings/get_safe", (params) => {
1920
+ requireInitialized();
1921
+ assertParamKeys(params, new Set());
1922
+ if (options.settings === undefined) {
1923
+ throw new AcpRequestError(-32000, "safe settings are unavailable", { code: "internal_error" });
1924
+ }
1925
+ return safeSettingsProjection(options.settings.read());
1926
+ });
1927
+
1928
+ options.transport.onRequest("clio-coder/settings/patch_safe", (params) => {
1929
+ requireInitialized();
1930
+ const request = assertParamKeys(params, new Set(["patch"]));
1931
+ if (!isRecord(request.patch)) {
1932
+ throw new AcpRequestError(-32602, "patch must be an object", { code: "invalid_params" });
1933
+ }
1934
+ if (activePromptState !== null) {
1935
+ throw new AcpRequestError(-32000, "cannot patch settings during an active prompt", { code: "prompt_active" });
1936
+ }
1937
+ if (options.settings === undefined) {
1938
+ throw new AcpRequestError(-32000, "safe settings are unavailable", { code: "internal_error" });
1939
+ }
1940
+ const patch: AcpSafeSettingsPatch = {};
1941
+ for (const [key, value] of Object.entries(request.patch)) {
1942
+ if (!(ACP_SAFE_SETTINGS_KEYS as ReadonlyArray<string>).includes(key)) {
1943
+ throw new AcpRequestError(-32602, "patch contains an unknown setting", { code: "invalid_params" });
1944
+ }
1945
+ switch (key) {
1946
+ case "orchestrator.target": {
1947
+ if (value === null) {
1948
+ patch[key] = null;
1949
+ break;
1950
+ }
1951
+ const target = requireBoundedClientString(value, key, ACP_MAX_TARGET_ID_BYTES);
1952
+ if (options.providers?.getTarget(target) === null || options.providers === undefined) {
1953
+ throw new AcpRequestError(-32602, "target is not configured", {
1954
+ code: "invalid_params",
1955
+ reason: "target-unknown",
1956
+ });
1957
+ }
1958
+ patch[key] = target;
1959
+ break;
1960
+ }
1961
+ case "orchestrator.model":
1962
+ patch[key] = value === null ? null : requireBoundedClientString(value, key, ACP_MAX_MODEL_ID_BYTES);
1963
+ break;
1964
+ case "orchestrator.thinkingLevel":
1965
+ if (typeof value !== "string" || !ACP_THINKING_LEVELS.has(value as AcpThinkingLevel)) {
1966
+ throw new AcpRequestError(-32602, "invalid thinking level", { code: "invalid_params" });
1967
+ }
1968
+ patch[key] = value as AcpThinkingLevel;
1969
+ break;
1970
+ case "autonomy":
1971
+ if (typeof value !== "string" || !ACP_AUTONOMY_LEVELS.has(value as AutonomyLevel)) {
1972
+ throw new AcpRequestError(-32602, "invalid autonomy level", { code: "invalid_params" });
1973
+ }
1974
+ patch[key] = value as AutonomyLevel;
1975
+ break;
1976
+ }
1977
+ }
1978
+ const current = options.settings.read();
1979
+ const nextTarget = patch["orchestrator.target"] === undefined ? current.target : patch["orchestrator.target"];
1980
+ const nextModel = patch["orchestrator.model"] === undefined ? current.model : patch["orchestrator.model"];
1981
+ if (nextTarget === null && nextModel !== null) {
1982
+ throw new AcpRequestError(-32602, "model requires a configured target", { code: "invalid_params" });
1983
+ }
1984
+ try {
1985
+ return safeSettingsProjection(options.settings.commit(patch));
1986
+ } catch {
1987
+ throw new AcpRequestError(-32000, "safe settings could not be updated", { code: "internal_error" });
1988
+ }
1989
+ });
1990
+
1991
+ options.transport.onRequest("clio-coder/targets/list", (params) => {
1992
+ requireInitialized();
1993
+ assertParamKeys(params, new Set());
1994
+ if (options.providers === undefined) {
1995
+ throw new AcpRequestError(-32000, "target discovery is unavailable", { code: "internal_error" });
1996
+ }
1997
+ const targets: AcpSafeTargetProjection[] = [];
1998
+ let budgetExhausted = false;
1999
+ // Budget incrementally from the exact JSON representation. Re-serializing
2000
+ // the growing full result for every one of up to 4,096 model ids turns a
2001
+ // bounded config list into avoidable quadratic work.
2002
+ let budgetBytes = ACP_EMPTY_TRUNCATED_TARGET_LIST_BYTES;
2003
+ for (const status of options.providers.list()) {
2004
+ const projected = safeTargetProjection(status);
2005
+ if (projected === null) continue;
2006
+ const bounded: AcpSafeTargetProjection = { ...projected, models: [] };
2007
+ let boundedBytes = utf8Bytes(JSON.stringify(bounded));
2008
+ const targetSeparatorBytes = targets.length > 0 ? 1 : 0;
2009
+ if (budgetBytes + targetSeparatorBytes + boundedBytes > ACP_MAX_TARGET_LIST_RESULT_BYTES) {
2010
+ budgetExhausted = true;
2011
+ break;
2012
+ }
2013
+ targets.push(bounded);
2014
+ budgetBytes += targetSeparatorBytes + boundedBytes;
2015
+ for (const model of projected.models) {
2016
+ const candidateModels = [...bounded.models, model];
2017
+ const candidate = { ...bounded, models: candidateModels };
2018
+ const candidateBytes = utf8Bytes(JSON.stringify(candidate));
2019
+ if (budgetBytes + candidateBytes - boundedBytes > ACP_MAX_TARGET_LIST_RESULT_BYTES) {
2020
+ budgetExhausted = true;
2021
+ break;
2022
+ }
2023
+ bounded.models.push(model);
2024
+ budgetBytes += candidateBytes - boundedBytes;
2025
+ boundedBytes = candidateBytes;
2026
+ }
2027
+ if (budgetExhausted) break;
2028
+ if (targets.length === ACP_MAX_TARGETS) break;
2029
+ }
2030
+ return {
2031
+ targets,
2032
+ ...(budgetExhausted ? { _meta: { "clio-coder/truncated": true } } : {}),
2033
+ };
2034
+ });
2035
+
2036
+ options.transport.onRequest("clio-coder/targets/probe", async (params) => {
2037
+ requireInitialized();
2038
+ const request = assertParamKeys(params, new Set(["targetId"]));
2039
+ const targetId = requireBoundedClientString(request.targetId, "targetId", ACP_MAX_TARGET_ID_BYTES);
2040
+ if (options.providers === undefined || options.providers.getTarget(targetId) === null) {
2041
+ throw new AcpRequestError(-32602, "target is not configured", {
2042
+ code: "invalid_params",
2043
+ reason: "target-unknown",
2044
+ });
2045
+ }
2046
+ try {
2047
+ const status = await options.providers.probeTarget(targetId, { reasoning: false });
2048
+ if (status === null) return { targetId, healthy: false, latencyMs: null, reason: "probe-failed" };
2049
+ const reason = safeProbeReason(options.providers, status);
2050
+ const latencyMs =
2051
+ typeof status.health.latencyMs === "number" &&
2052
+ Number.isFinite(status.health.latencyMs) &&
2053
+ status.health.latencyMs >= 0
2054
+ ? Math.round(status.health.latencyMs)
2055
+ : null;
2056
+ return { targetId, healthy: reason === null, latencyMs, reason };
2057
+ } catch {
2058
+ return { targetId, healthy: false, latencyMs: null, reason: "probe-failed" };
2059
+ }
2060
+ });
2061
+
2062
+ const cancelSession = (session: AcpServerSession, reason: string): void => {
2063
+ if (!session.activePrompt) return;
2064
+ session.activePrompt.cancelled = true;
2065
+ options.chat.cancel();
2066
+ // A prompt cancelled with a permission parked would otherwise sit on the
2067
+ // outstanding request until the client answers or the timeout fires, and
2068
+ // the turn cannot settle until the parked tool does.
2069
+ permission.cancelPending(reason);
2070
+ };
2071
+
2072
+ const cancel = (params: unknown): Record<string, never> => {
2073
+ requireInitialized();
2074
+ cancelSession(getSession(params), "prompt cancelled");
2075
+ return {};
2076
+ };
2077
+ options.transport.onRequest("session/cancel", cancel);
2078
+ options.transport.onNotification("session/cancel", (params) => {
2079
+ // A notification has no reply channel, so an ordering or identity error
2080
+ // has nowhere to go. Dropping it silently is the only conformant option.
2081
+ if (!initialized) return;
2082
+ const id = isRecord(params) && typeof params.sessionId === "string" ? params.sessionId : null;
2083
+ const session = id === null ? undefined : sessions.get(id);
2084
+ if (session) cancelSession(session, "prompt cancelled");
2085
+ });
2086
+
2087
+ options.transport.onRequest("session/close", async (params) => {
2088
+ requireInitialized();
2089
+ const id = sessionIdOf(params);
2090
+ // Idempotent: a client tearing down cannot always know whether the server
2091
+ // already dropped the session, and a second close is not an error.
2092
+ if (!sessions.has(id) && closedSessionIds.has(id)) return {};
2093
+ const session = getSession(params);
2094
+ // Closing under an active prompt is what raced the session writer against
2095
+ // the chat loop still persisting the aborted turn. The client cancels and
2096
+ // awaits the prompt's terminal response first.
2097
+ if (session.activePrompt) {
2098
+ throw new AcpRequestError(-32000, "cancel the active prompt before closing the session", {
2099
+ code: "prompt_active",
2100
+ });
2101
+ }
2102
+ if (options.session?.current()?.id === session.id) await options.session.close();
2103
+ sessions.delete(session.id);
2104
+ closedSessionIds.add(session.id);
2105
+ return {};
2106
+ });
2107
+
2108
+ options.transport.onRequest("session/prompt", async (params): Promise<AcpPromptResponse> => {
2109
+ requireInitialized();
2110
+ const session = getSession(params);
2111
+ if (session.activePrompt || options.chat.isStreaming()) {
2112
+ throw new AcpRequestError(-32000, "this session already has an active prompt", { code: "prompt_active" });
2113
+ }
2114
+ const text = promptText(params);
2115
+ if (text.length === 0) throw new AcpRequestError(-32602, "prompt text is required", { code: "invalid_params" });
2116
+ if (options.session?.current()?.id !== session.id && options.session) options.session.resume(session.id);
2117
+ const active = createActivePromptState();
2118
+ session.activePrompt = active;
2119
+ activePromptState = active;
2120
+ activeSessionId = session.id;
2121
+ // Transport close waits on this so the session store never stops while the
2122
+ // chat loop is still persisting an aborted turn.
2123
+ let settle: () => void = () => {};
2124
+ promptSettled = new Promise<void>((resolveSettled) => {
2125
+ settle = resolveSettled;
2126
+ });
2127
+ options.onActiveSessionAutonomyChange?.(session.autonomy);
2128
+ const unsubscribe = options.chat.onEvent((event) =>
2129
+ handleChatEvent(event, options.transport, session.id, active, canonicalCwd, options.diagnostics, () => {
2130
+ permission.cancelPending("tool call limit exceeded");
2131
+ options.toolRegistry?.cancelParkedCalls("tool call limit exceeded");
2132
+ options.chat.cancel();
2133
+ }),
2134
+ );
2135
+ try {
2136
+ await options.chat.submit(text);
2137
+ } catch (err) {
2138
+ active.errored = true;
2139
+ active.errorMessage = err instanceof Error ? err.message : String(err);
2140
+ } finally {
2141
+ unsubscribe();
2142
+ if (session.activePrompt === active) session.activePrompt = null;
2143
+ if (activePromptState === active) activePromptState = null;
2144
+ if (activeSessionId === session.id) {
2145
+ activeSessionId = null;
2146
+ options.onActiveSessionAutonomyChange?.(null);
2147
+ }
2148
+ promptSettled = null;
2149
+ settle();
2150
+ }
2151
+ // Expiry is not a delayed denial or an operator cancellation. It aborts
2152
+ // the run and fails the prompt so the model cannot react to/retry it.
2153
+ if (active.permissionExpired) {
2154
+ settleOpenToolCalls(options.transport, session.id, active, "permission approval expired");
2155
+ throw new AcpRequestError(-32000, "permission approval expired", { code: "permission_expired" });
2156
+ }
2157
+ // W001-A1: a peer that holds bounded per-turn state must never see a
2158
+ // 129th tool start. This is a normal ACP agent-side request ceiling, not a
2159
+ // provider failure or an operator cancellation.
2160
+ if (active.toolCallLimitReached) {
2161
+ settleOpenToolCalls(options.transport, session.id, active, "tool call limit exceeded");
2162
+ return promptResponse("max_turn_requests", active);
2163
+ }
2164
+ // Operator cancellation takes precedence over ordinary turn outcomes.
2165
+ if (active.cancelled) {
2166
+ settleOpenToolCalls(options.transport, session.id, active, "cancelled");
2167
+ return promptResponse("cancelled", active);
2168
+ }
2169
+ // The empty-success defect: Clio refused to start the turn, the notice
2170
+ // carrying the reason has no ACP equivalent, and nothing else in the turn
2171
+ // distinguishes the refusal from a model that chose to say nothing. The
2172
+ // reason travels as a code; the notice text carries the settings path and
2173
+ // stays off the wire.
2174
+ if (active.admissionReason !== undefined && !active.sawTurnEnd && active.updatesSent === 0) {
2175
+ throw new AcpRequestError(-32000, `Clio could not admit this prompt: ${active.admissionReason}`, {
2176
+ code: "prompt_not_admitted",
2177
+ reason: active.admissionReason,
2178
+ });
2179
+ }
2180
+ // ACP has no error StopReason: a failed turn is signalled by failing the
2181
+ // session/prompt request itself.
2182
+ if (active.errored) {
2183
+ settleOpenToolCalls(options.transport, session.id, active, "turn failed");
2184
+ // Provider prose never reaches the wire. Bounding it was not enough: a
2185
+ // provider error body legitimately quotes a request URL, a filesystem
2186
+ // path, or the credential it was rejected for, and a bounded copy of a
2187
+ // secret is still the secret. The client gets host-authored text plus
2188
+ // the `turn_failed` code; the original goes to the stderr tail.
2189
+ if (active.errorMessage !== undefined) {
2190
+ options.diagnostics?.(`turn failed: ${acpErrorMessage(active.errorMessage)}`);
2191
+ }
2192
+ throw new AcpRequestError(-32000, ACP_TURN_FAILED_MESSAGE, { code: "turn_failed" });
2193
+ }
2194
+ return promptResponse(active.stopReason, active);
2195
+ });
2196
+
2197
+ return await new Promise<number>((resolve) => {
2198
+ options.transport.onClose(() => {
2199
+ permission.unregister();
2200
+ unsubscribeLoopBlocked();
2201
+ permission.cancelPending("ACP transport closed");
2202
+ for (const session of sessions.values()) cancelSession(session, "ACP transport closed");
2203
+ const inFlight = promptSettled;
2204
+ if (inFlight === null) {
2205
+ resolve(0);
2206
+ return;
2207
+ }
2208
+ // The prompt handler owns the chat loop's settlement; resolving the
2209
+ // serve promise before it returns lets the orchestrator stop domains
2210
+ // under a live writer. The bound keeps a wedged turn from holding the
2211
+ // process open forever.
2212
+ const timer = setTimeout(() => resolve(0), ACP_PROMPT_SETTLE_BOUND_MS);
2213
+ timer.unref?.();
2214
+ void inFlight.then(() => {
2215
+ clearTimeout(timer);
2216
+ resolve(0);
2217
+ });
2218
+ });
2219
+ });
2220
+ }
2221
+
2222
+ /** PromptResponse is `{ stopReason, _meta? }`; usage is not an ACP v1 field. */
2223
+ function promptResponse(stopReason: string, active: ActivePrompt): AcpPromptResponse {
2224
+ return {
2225
+ stopReason,
2226
+ _meta: {
2227
+ [ACP_USAGE_META_KEY]: {
2228
+ input: active.usage.input,
2229
+ output: active.usage.output,
2230
+ cacheRead: active.usage.cacheRead,
2231
+ cacheWrite: active.usage.cacheWrite,
2232
+ reasoning: active.usage.reasoning,
2233
+ },
2234
+ },
2235
+ };
2236
+ }
2237
+
2238
+ export type AcpPromptContent = AcpContentBlock[];