@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,1127 @@
1
+ /**
2
+ * Unified loop guard, packaged as a middleware hook registration.
3
+ *
4
+ * Both the orchestrator and worker registries register this single module on
5
+ * `before_tool`, so there is exactly one observation seam and no double-count
6
+ * hazard from separate interactive and worker guards. The registry feeds it
7
+ * every tool-call attempt, including safety-blocked ones, via
8
+ * `metadata.callFingerprint`.
9
+ *
10
+ * Parameterization covers both deployments: the orchestrator passes a bus
11
+ * (LoopBlocked visibility) and the per-turn block budget; workers pass the
12
+ * hard tool-call cap so a degenerate model cannot burn through a run by
13
+ * spamming distinct calls. Operator visibility flows over the event bus only;
14
+ * nothing here imports TUI code.
15
+ */
16
+
17
+ import {
18
+ BusChannels,
19
+ type LoopBlockedDisposition,
20
+ type LoopBlockedPayload,
21
+ type ToolBudgetExceededPayload,
22
+ } from "../core/bus-events.js";
23
+ import type { SafeEventBus } from "../core/event-bus.js";
24
+ import {
25
+ GUARDRAIL_DEFAULTS,
26
+ resolveGuardrail,
27
+ workerSynthesisReserveBlockReason,
28
+ workerSynthesisReserveDirective,
29
+ workerToolCallCapExceededReason,
30
+ workerToolCallCapSynthesisReason,
31
+ } from "../core/guardrails.js";
32
+ import { ToolNames } from "../core/tool-names.js";
33
+ import type { AgentProduct } from "../domains/agents/spec.js";
34
+ import type { MiddlewareHookRegistration } from "../domains/middleware/runtime.js";
35
+ import type { MiddlewareEffect, MiddlewareHookInput } from "../domains/middleware/types.js";
36
+ import type { SafetyContract } from "../domains/safety/contract.js";
37
+ import { hashToolCall } from "../domains/safety/loop-detector.js";
38
+ import type { AgentMessage } from "./types.js";
39
+
40
+ export const LOOP_GUARD_REGISTRATION_ID = "guard.loop";
41
+
42
+ /**
43
+ * Loop blocks tolerated per user turn before the turn is interrupted. Combined
44
+ * with the detector's identical-call threshold, a runaway turn is bounded to a
45
+ * few verbatim repeats (threshold-1 free calls plus this many blocked retries)
46
+ * rather than spinning for tens of seconds before the stop lands.
47
+ */
48
+ export const INTERACTIVE_LOOP_BLOCK_BUDGET = 2;
49
+
50
+ /** One loop block tolerated per this many admitted calls in a worker run. */
51
+ const WORKER_LOOP_BLOCK_CALLS_PER_BLOCK = 10;
52
+
53
+ /**
54
+ * Loop blocks a worker run tolerates before it is locked to synthesis.
55
+ *
56
+ * A worker has no turns: it never sets a turnId, so every block it collects
57
+ * lands in one bucket that lives for the whole run. Measuring that run-long
58
+ * count against the interactive per-turn budget made the second verbatim
59
+ * spiral anywhere in a ninety-call editing pass end the run, which is a
60
+ * verdict about run length rather than about degeneracy. The detector still
61
+ * blocks each spiral on the spot; this is the separate judgment that the run
62
+ * as a whole is not converging, so it scales with how long the run is allowed
63
+ * to be. The interactive budget stays the floor, so a short worker keeps
64
+ * exactly the bound it has today.
65
+ */
66
+ export function workerLoopBlockBudget(toolCalls: number): number {
67
+ if (!Number.isSafeInteger(toolCalls) || toolCalls <= 0) return INTERACTIVE_LOOP_BLOCK_BUDGET;
68
+ return Math.max(INTERACTIVE_LOOP_BLOCK_BUDGET, Math.ceil(toolCalls / WORKER_LOOP_BLOCK_CALLS_PER_BLOCK));
69
+ }
70
+
71
+ /**
72
+ * Post-lockout tool calls tolerated before the synthesis lockout falls back to
73
+ * a hard turn stop. Once a turn reaches its loop-block budget the guard locks
74
+ * tools for the rest of the turn and tells the model to answer from what it
75
+ * gathered; a model that keeps calling tools instead of answering is stopped
76
+ * after this many further denials so a degenerate model cannot spin forever.
77
+ */
78
+ export const LOOP_SYNTHESIS_BACKSTOP_DENIALS = 2;
79
+
80
+ /**
81
+ * Floor for the run-level denial budget, sized to cover one wide parallel
82
+ * batch. The budget is otherwise the run's own tool-call cap, and a run whose
83
+ * cap is tiny must still be able to steer a full batch of siblings rather than
84
+ * die on it: the per-round bounds exist precisely so a wide batch gets one
85
+ * synthesis round instead of a kill.
86
+ */
87
+ export const WIDE_BATCH_DENIAL_FLOOR = 32;
88
+
89
+ /**
90
+ * Default lifetime tool-call cap for a worker run. Value and env override live
91
+ * in core/guardrails.ts (`CLIO_CODER_WORKER_TOOL_CALL_CAP`); re-exported here for
92
+ * tests and callers that reason about the guard's tuning in one place.
93
+ */
94
+ export const DEFAULT_WORKER_TOOL_CALL_CAP = GUARDRAIL_DEFAULTS.workerToolCallCap;
95
+
96
+ /**
97
+ * Default soft per-turn tool-call budget for the interactive orchestrator.
98
+ * Crossing it blocks every further call this turn with a stop-and-summarize
99
+ * directive; the orchestrator is otherwise uncapped on the premise that an
100
+ * operator can intervene, which fails for weak local models that spray
101
+ * distinct commands the identical-call detector never sees.
102
+ *
103
+ * Sized as a backstop, not a routine ceiling: verbatim retry spirals are the
104
+ * identical-call detector's job, so this only has to catch a model spraying
105
+ * DISTINCT unproductive calls. Legitimate deep work (a repo-wide audit runs
106
+ * 25+ productive calls in one turn) must not be decapitated by it; mainstream
107
+ * harnesses run 100+ calls per turn with no ceiling at all. Value and env
108
+ * override (`CLIO_CODER_TURN_TOOL_CALL_BUDGET`) live in core/guardrails.ts.
109
+ */
110
+ export const DEFAULT_ORCH_TURN_TOOL_CALL_BUDGET = GUARDRAIL_DEFAULTS.turnToolCallBudget;
111
+ /**
112
+ * Hard ceiling sits this many calls above the soft budget. Reaching it
113
+ * interrupts the turn outright instead of merely nudging, bounding a model
114
+ * that ignores the directive to a small number of blocked retries.
115
+ */
116
+ export const ORCH_TURN_TOOL_CALL_HARD_MARGIN = 15;
117
+
118
+ /** Bounded turn-id memory, matching the registry's dispatch-guard policy. */
119
+ const LOOP_GUARD_TURN_LIMIT = 32;
120
+
121
+ /** Bounded per-fingerprint success memory for the block-reason evidence anchor. */
122
+ const SUCCEEDED_FINGERPRINT_LIMIT = 128;
123
+
124
+ /** Bucket for calls arriving without a turn id (e.g. pre-session probes). */
125
+ const NO_TURN_BUCKET = "no-turn";
126
+
127
+ /**
128
+ * Arguments that only change how much of the same answer comes back, never
129
+ * which answer it is. Stripped from the stagnation fingerprint so a model
130
+ * cycling limit/offset escalations on an otherwise identical call is caught,
131
+ * while genuinely different queries (new pattern, new path) reset the streak.
132
+ */
133
+ const SIZE_ONLY_ARG_KEYS = new Set(["limit", "offset", "max_bytes", "context"]);
134
+
135
+ /**
136
+ * Same-shape calls with byte-identical results tolerated before the next one
137
+ * is blocked. Two identical results already prove the size knobs are not
138
+ * adding information; the third attempt is never productive. Verbatim repeats
139
+ * trip the identical-call detector first; this catches the escalation cycle
140
+ * (limit: 10000 -> 20000 -> 50000 -> ...) that varies args enough to evade it.
141
+ */
142
+ export const RESULT_STAGNATION_THRESHOLD = 3;
143
+ const CROSS_ARGUMENT_RESULT_MIN_BYTES = 64;
144
+
145
+ function stagnationFingerprint(tool: string, args: Record<string, unknown> | undefined): string {
146
+ const reduced: Record<string, unknown> = {};
147
+ for (const [key, value] of Object.entries(args ?? {})) {
148
+ if (!SIZE_ONLY_ARG_KEYS.has(key)) reduced[key] = value;
149
+ }
150
+ return hashToolCall(tool, reduced);
151
+ }
152
+
153
+ function stagnationBlockReason(tool: string, identicalResults: number): string {
154
+ return (
155
+ `loop detected: the last ${identicalResults} ${tool} calls returned byte-identical results even though ` +
156
+ `size arguments (limit/offset) changed. Raising them is not producing new information. ` +
157
+ `Re-read the result above, change the query or tool, or answer from what you have.`
158
+ );
159
+ }
160
+
161
+ function crossArgumentResultMessage(tool: string, distinctArguments: number): string {
162
+ return (
163
+ `loop guard: ${distinctArguments} distinct ${tool} arguments returned the same substantial result this turn. ` +
164
+ "The tool may be ignoring an argument; re-read the result, change tools, or answer from what you have."
165
+ );
166
+ }
167
+
168
+ /**
169
+ * Base block reason: names the loop and asks for a strategy change (block #1).
170
+ * When the same call already returned a successful result earlier this run, the
171
+ * reason says so and points the model at that result — for a weak local model
172
+ * "you already have this answer" is the strongest available anchor, stronger
173
+ * than a generic "change strategy".
174
+ */
175
+ function loopBlockBaseReason(tool: string, repeatCount: number, priorSuccesses: number): string {
176
+ const evidence =
177
+ priorSuccesses > 0
178
+ ? `This exact call already succeeded ${priorSuccesses} ${priorSuccesses === 1 ? "time" : "times"} this run; ` +
179
+ `its result is already in the conversation above — re-read that result before calling tools again. `
180
+ : "";
181
+ return (
182
+ `loop detected: ${tool} was called ${repeatCount} times with identical arguments among this turn's recent ` +
183
+ `tool calls. Repeating the exact call is blocked. ${evidence}Change strategy: vary the arguments, use a ` +
184
+ `different tool, or explain what new information you expect before retrying.`
185
+ );
186
+ }
187
+
188
+ /**
189
+ * Directive returned when a turn is locked to synthesis: tool use is over, so
190
+ * the model must answer from what it already gathered. Fed back to the model as
191
+ * the blocked call's result; the agent loop ends naturally on the first round
192
+ * that emits text without a tool call.
193
+ */
194
+ function synthesisLockoutDirective(): string {
195
+ return (
196
+ "loop guard: this turn reached its tool-call limit after repeated identical calls, so tool calls are now " +
197
+ "disabled for the rest of this turn. Everything you retrieved is already in the conversation above. Answer " +
198
+ "the operator now, in plain prose, from what you have gathered. Do not write tool-call markup such as " +
199
+ "<tool_call> blocks; tool calls are disabled and will not run."
200
+ );
201
+ }
202
+
203
+ /** Blocked-call result text for the backstop stop (the operator sees loopBlockedStopReason). */
204
+ function synthesisBackstopReason(tool: string): string {
205
+ return (
206
+ `loop guard: tool calls stayed disabled and ${tool} was called again instead of answering, so the turn is ` +
207
+ "being stopped. Summarize what you found for the operator."
208
+ );
209
+ }
210
+
211
+ /** Scout/read-only exploration phase transition; deliberately not a hard-cap machine reason. */
212
+ function workerExplorationSynthesisDirective(limit: number): string {
213
+ return (
214
+ `worker exploration budget reached (${limit}); exploration tools are now disabled for this run. ` +
215
+ "Answer in plain prose from the evidence already gathered, with live path:line citations. Do not retry or " +
216
+ "substitute another tool call."
217
+ );
218
+ }
219
+
220
+ function disallowedToolsText(deliveryTools: ReadonlyArray<string>): string {
221
+ const candidates = ["code_nav", "ls", "grep", "find", "context", "git", "shell commands"];
222
+ const excluded = candidates.filter((tool) => !deliveryTools.includes(tool));
223
+ return excluded.join(", ");
224
+ }
225
+
226
+ function workerLiveReadReserveDirective(remaining: number, deliveryTools: ReadonlyArray<string>): string {
227
+ const admitted = deliveryTools.length > 0 ? `read and ${deliveryTools.join("/")}` : "the read tool";
228
+ const finish =
229
+ deliveryTools.length > 0
230
+ ? `Finish the work: re-read what you will cite and write it with ${deliveryTools.join(" or ")}.`
231
+ : "Read a specific live file needed for the handoff.";
232
+ return (
233
+ `worker live-read reserve: broad orientation is complete and ${remaining} ${remaining === 1 ? "call remains" : "calls remain"}. ` +
234
+ `Only ${admitted} ${deliveryTools.length > 0 ? "are" : "is"} admitted now. ${finish} Do not substitute ${disallowedToolsText(deliveryTools)}.`
235
+ );
236
+ }
237
+
238
+ /**
239
+ * Directive for a delivery-capable agent that has spent its declared soft
240
+ * budget. Discovery is over for the rest of the run; delivery continues under
241
+ * the lifetime cap, which is the bound that exists for it. Deliberately distinct
242
+ * from the reserve directive, which counts down remaining calls inside a window
243
+ * that has an end: past the soft limit there is no countdown to report, only the
244
+ * cap.
245
+ */
246
+ function workerDeliveryOnlyDirective(limit: number, deliveryTools: ReadonlyArray<string>): string {
247
+ return (
248
+ `worker discovery budget reached (${limit}); exploration tools are disabled for the rest of this run. ` +
249
+ `Only read and ${deliveryTools.join("/")} are admitted now. Write what you have with ` +
250
+ `${deliveryTools.join(" or ")} and finish. Do not substitute ${disallowedToolsText(deliveryTools)}.`
251
+ );
252
+ }
253
+
254
+ /**
255
+ * Dead tool-call markup a model can emit as plain text once tool_choice is
256
+ * forced to none: the chat template's call syntax arrives as prose because the
257
+ * runtime no longer parses it into a structured call. Measured on a local model
258
+ * target, roughly one locked turn in three answered the forced text-only round
259
+ * this way. Complete blocks are stripped wherever they appear; unterminated
260
+ * trailing blocks (a length stop mid-markup) are stripped only when followed
261
+ * by a call body (`<function=` or a JSON opener), so prose that merely quotes
262
+ * the "<tool_call>" phrase survives.
263
+ */
264
+ const DEAD_TOOL_CALL_MARKUP_PATTERNS: ReadonlyArray<RegExp> = [
265
+ /<tool_call>[\s\S]*?<\/tool_call>/gi,
266
+ /<function=[\w.-]+>[\s\S]*?<\/function>/gi,
267
+ /<tool_call>\s*(?:<function=|[{[])[\s\S]*$/i,
268
+ /<function=[\w.-]+>[\s\S]*$/i,
269
+ ];
270
+
271
+ /** Strip dead tool-call markup from locked-turn text; trims only when something was stripped. */
272
+ function stripDeadToolCallMarkup(text: string): string {
273
+ let out = text;
274
+ for (const pattern of DEAD_TOOL_CALL_MARKUP_PATTERNS) {
275
+ out = out.replace(pattern, "");
276
+ }
277
+ return out === text ? text : out.trim();
278
+ }
279
+
280
+ /**
281
+ * Final-answer replacement when a locked turn's reply was nothing but dead
282
+ * tool-call markup. Rendered as the assistant turn (TUI, headless result,
283
+ * session ledger), so it explains the stop the way the loop-guard closing
284
+ * messages do.
285
+ */
286
+ export function lockedSynthesisFallbackText(): string {
287
+ return (
288
+ "[Clio Coder] loop guard: tool calls were disabled for the rest of this turn, but the reply contained only " +
289
+ "tool-call markup, which cannot run and was removed. Ask me to continue with a different approach, or " +
290
+ "narrow the request."
291
+ );
292
+ }
293
+
294
+ /**
295
+ * Sanitize the assistant message a synthesis-locked turn produced: strip dead
296
+ * tool-call markup from its text blocks and fall back to
297
+ * {@link lockedSynthesisFallbackText} when nothing remains. Mutates the message
298
+ * in place; pi stores the same object in agent state before listeners run, so
299
+ * one mutation at the subscribe seam covers the emitted event, the session
300
+ * ledger, receipts, and the next round's provider payload without any surface
301
+ * re-implementing the rule. Returns whether the message changed.
302
+ *
303
+ * Deliberately narrow: only assistant messages that finished ("stop", or
304
+ * "length" for a mid-markup cutoff) with no structured tool call are touched.
305
+ * Aborted and error messages keep their text (their flows carry their own
306
+ * closing messages), and callers gate on the lockout being active so ordinary
307
+ * turns and user text are never sanitized.
308
+ */
309
+ export function sanitizeLockedSynthesisMessage(message: AgentMessage | undefined): boolean {
310
+ if (!message || typeof message !== "object" || (message as { role?: unknown }).role !== "assistant") return false;
311
+ const record = message as { content?: unknown; stopReason?: unknown };
312
+ const stopReason = record.stopReason;
313
+ if (stopReason !== undefined && stopReason !== "stop" && stopReason !== "length") return false;
314
+ if (!Array.isArray(record.content)) return false;
315
+ const blocks = record.content as Array<{ type?: unknown; text?: unknown }>;
316
+ if (blocks.some((block) => block?.type === "toolCall")) return false;
317
+ let changed = false;
318
+ for (const block of blocks) {
319
+ if (block?.type !== "text" || typeof block.text !== "string") continue;
320
+ const stripped = stripDeadToolCallMarkup(block.text);
321
+ if (stripped !== block.text) {
322
+ block.text = stripped;
323
+ changed = true;
324
+ }
325
+ }
326
+ if (!changed) return false;
327
+ const remaining = blocks
328
+ .filter((block): block is { type: "text"; text: string } => block?.type === "text" && typeof block.text === "string")
329
+ .map((block) => block.text)
330
+ .join("")
331
+ .trim();
332
+ if (remaining.length === 0) {
333
+ const firstText = blocks.find(
334
+ (block): block is { type: "text"; text: string } => block?.type === "text" && typeof block.text === "string",
335
+ );
336
+ if (firstText) firstText.text = lockedSynthesisFallbackText();
337
+ else blocks.push({ type: "text", text: lockedSynthesisFallbackText() });
338
+ }
339
+ return true;
340
+ }
341
+
342
+ /**
343
+ * Recognizes the synthesis-backstop stop reason on surfaces without a bus.
344
+ * Workers watch blocked tool-finish events for this reason (the same seam as
345
+ * the lifetime cap) and abort the run: without it, the "stop" disposition is
346
+ * bus-only and a worker that ignores the lockout would keep burning calls
347
+ * until the lifetime cap. Measured on a live 35B coder worker: one identical
348
+ * code_nav loop consumed the full 50-call cap (46 blocked calls, ~345k
349
+ * tokens) before this abort path existed.
350
+ */
351
+ export function isLoopGuardSynthesisBackstopReason(reason: string): boolean {
352
+ return /^loop guard: tool calls stayed disabled and .+ was called again instead of answering/.test(reason);
353
+ }
354
+
355
+ /** Worker lifetime tool-call cap: env > settings guardrails > default. */
356
+ export function readWorkerToolCallCap(env: NodeJS.ProcessEnv = process.env): number {
357
+ return resolveGuardrail("workerToolCallCap", env);
358
+ }
359
+
360
+ /** Soft/hard per-turn tool-call budget for the interactive orchestrator. */
361
+ export interface OrchTurnToolCallBudget {
362
+ /** Distinct calls in a turn that trigger the re-plan nudge. */
363
+ soft: number;
364
+ /** Distinct calls in a turn that interrupt the turn. */
365
+ hard: number;
366
+ }
367
+
368
+ export function readOrchTurnToolCallBudget(env: NodeJS.ProcessEnv = process.env): OrchTurnToolCallBudget {
369
+ const soft = resolveGuardrail("turnToolCallBudget", env);
370
+ return { soft, hard: soft + ORCH_TURN_TOOL_CALL_HARD_MARGIN };
371
+ }
372
+
373
+ export interface CreateLoopGuardRegistrationOptions {
374
+ safety: SafetyContract;
375
+ /** Orchestrator only: LoopBlocked events for the interactive layer. */
376
+ bus?: SafeEventBus;
377
+ /** Loop blocks tolerated per turn before the block reason announces a stop. */
378
+ turnBlockBudget?: number;
379
+ /**
380
+ * Worker only: hard cap on observed tool-call attempts for the lifetime of
381
+ * this registration. Absent means uncapped (the orchestrator has an
382
+ * operator who can intervene; workers do not).
383
+ */
384
+ toolCallCap?: number;
385
+ /**
386
+ * Worker only: successful exploration attempts allowed before a graceful,
387
+ * text-only synthesis phase. Unlike {@link toolCallCap}, crossing this soft
388
+ * boundary does not make an otherwise successful synthesized run fail.
389
+ */
390
+ toolCallSoftLimit?: number;
391
+ /**
392
+ * Worker only: tail of {@link toolCallSoftLimit} reserved for live source
393
+ * reads. Once the pre-reserve allowance is consumed, discovery attempts are
394
+ * blocked without spending the read allowance and the next provider round
395
+ * can be forced to `read` through {@link onSoftReadReserve}.
396
+ */
397
+ toolCallSoftReadReserve?: number;
398
+ /**
399
+ * Worker only: tools that carry this agent's deliverable rather than its
400
+ * exploration, admitted inside a reserve window alongside `read`. The
401
+ * reserve exists to end broad discovery, not to stop an agent from
402
+ * finishing: for an agent whose product is a report the tail is reads and
403
+ * prose, but for one whose product is files the tail is reads and writes,
404
+ * and blocking those made the last calls of every editing run bounce. Empty
405
+ * for read-only agents, which keeps the reserve read-only exactly as before.
406
+ */
407
+ deliveryTools?: ReadonlyArray<string>;
408
+ /**
409
+ * Worker only: calls held back from the tail of {@link toolCallCap} for
410
+ * verification reads and synthesis. Entering the window annotates one tool
411
+ * result with a one-shot reserve directive and blocks further non-read
412
+ * calls; reads still flow through the repetition and stagnation detectors.
413
+ * Inactive when absent, zero, or not smaller than the cap, so existing
414
+ * small-cap deployments and tests keep their exact behavior.
415
+ */
416
+ toolCallReserve?: number;
417
+ /**
418
+ * Orchestrator only: soft/hard per-turn tool-call budget. Crossing the soft
419
+ * budget blocks this and every further call in the turn with a
420
+ * stop-and-summarize directive; the hard ceiling additionally interrupts
421
+ * the turn over the bus. Absent for workers, which rely on the lifetime
422
+ * {@link toolCallCap} instead.
423
+ */
424
+ turnToolCallBudget?: OrchTurnToolCallBudget;
425
+ /**
426
+ * When the per-turn loop-block budget is exhausted, lock tool use for the
427
+ * rest of the turn instead of cancelling it outright. Every further call is
428
+ * denied with a synthesize-now directive so the model produces a final
429
+ * answer from what it already gathered; only a bounded backstop of extra
430
+ * denials ({@link LOOP_SYNTHESIS_BACKSTOP_DENIALS}) falls back to the hard
431
+ * stop. The orchestrator's stop rides the bus (LoopBlocked "stop" drives
432
+ * chat.cancel); workers have no bus and instead watch blocked tool-finish
433
+ * events for {@link isLoopGuardSynthesisBackstopReason} and abort the run,
434
+ * so a looping worker returns a synthesized report after a bounded number
435
+ * of denials rather than burning the lifetime {@link toolCallCap}.
436
+ */
437
+ turnSynthesisLockout?: boolean;
438
+ /**
439
+ * Invoked when a turn enters the synthesis lockout. Surfaces without a bus
440
+ * (workers) use it to flip their request-level tool-choice lock so the next
441
+ * model round is text-only; the orchestrator gets the same signal from the
442
+ * LoopBlocked "lockout" bus event instead.
443
+ */
444
+ onSynthesisLockout?: () => void;
445
+ /**
446
+ * Worker only: invoked when the final call allowed by the soft agent budget
447
+ * is admitted. The callback identifies the call whose completed tool-result
448
+ * message forms the post-execution boundary for a synthesis-disabled run.
449
+ * The guard locks later (including parallel sibling) calls before invoking
450
+ * this callback; callers must not stop the active call from this hook.
451
+ */
452
+ onSoftLimitFinalCallAdmitted?: (toolCallId: string | undefined) => void;
453
+ /** Invoked once when the soft-limit live-read reserve opens. */
454
+ onSoftReadReserve?: () => void;
455
+ now?: () => number;
456
+ }
457
+
458
+ export interface LoopGuardRegistration extends MiddlewareHookRegistration {
459
+ /** Read-only attempt counter for tests and telemetry. */
460
+ callCount(): number;
461
+ }
462
+
463
+ export function createLoopGuardRegistration(options: CreateLoopGuardRegistrationOptions): LoopGuardRegistration {
464
+ const budget = options.turnBlockBudget ?? INTERACTIVE_LOOP_BLOCK_BUDGET;
465
+ const cap = options.toolCallCap;
466
+ const softLimit =
467
+ options.toolCallSoftLimit !== undefined &&
468
+ Number.isSafeInteger(options.toolCallSoftLimit) &&
469
+ options.toolCallSoftLimit > 0 &&
470
+ (cap === undefined || options.toolCallSoftLimit <= cap)
471
+ ? options.toolCallSoftLimit
472
+ : undefined;
473
+ const turnBudget = options.turnToolCallBudget;
474
+ const synthesisLockout = options.turnSynthesisLockout === true;
475
+ const softReadReserve = options.toolCallSoftReadReserve ?? 0;
476
+ const softReadReserveThreshold =
477
+ softLimit !== undefined && softReadReserve > 0 && softLimit > softReadReserve ? softLimit - softReadReserve : null;
478
+ const deliveryTools = [...new Set(options.deliveryTools ?? [])];
479
+ const reserveAdmits = (tool: string | undefined): boolean => isReserveAdmittedTool(tool, deliveryTools);
480
+ let softAdmittedCount = 0;
481
+ let softReadReserveEntered = false;
482
+ /**
483
+ * Per-round non-compliance inside a reserve window. A reserve block is
484
+ * steering rather than a runaway signal, so it never spends the lifetime
485
+ * cap; this is what still bounds it. Reset by every admitted call, so the
486
+ * total is bounded by the reserve's own remaining calls.
487
+ */
488
+ let reserveDenials: { denials: number; correlationId?: string } = { denials: 0 };
489
+ /**
490
+ * Run-level bound on refused attempts, counted per call and blind to
491
+ * correlation. Every other bound here is per model round, which is correct
492
+ * for steering a model that keeps answering with tool calls but assumes a
493
+ * round is a handful of them. A degenerate local model emits one response
494
+ * whose parsed tool calls number in the hundreds, and every one of those
495
+ * siblings shares a correlation id, so the per-round bounds see a single
496
+ * denial while the batch drains. Charging refusals to the lifetime cap used
497
+ * to stop that by accident, at the cost of killing runs for obeying the
498
+ * guard. This is the same bound stated directly: a run may be refused as
499
+ * many calls as it was allowed to execute, and past that it is degenerate
500
+ * and ends on the backstop the worker already aborts on. The orchestrator
501
+ * has no cap and no bound here; it has an operator instead.
502
+ */
503
+ let deniedAttempts = 0;
504
+ const denialBudget = cap === undefined ? undefined : Math.max(cap, WIDE_BATCH_DENIAL_FLOOR);
505
+ const boundRunDenials = (
506
+ effects: ReadonlyArray<MiddlewareEffect>,
507
+ input: MiddlewareHookInput,
508
+ ): ReadonlyArray<MiddlewareEffect> => {
509
+ if (denialBudget === undefined || !effects.some((effect) => effect.kind === "block_tool")) return effects;
510
+ deniedAttempts += 1;
511
+ if (deniedAttempts <= denialBudget) return effects;
512
+ return [{ kind: "block_tool", reason: synthesisBackstopReason(input.toolName ?? "unknown"), severity: "hard-block" }];
513
+ };
514
+ // Reserve window bounds. Active only when a cap exists and is strictly
515
+ // larger than the reserve; a call is inside the window when its attempt
516
+ // ordinal exceeds this threshold but has not passed the cap itself.
517
+ const reserve = options.toolCallReserve ?? 0;
518
+ const reserveThreshold = cap !== undefined && reserve > 0 && cap > reserve ? cap - reserve : null;
519
+ let reserveDirectiveEmitted = false;
520
+ const blocksByTurn = new Map<string, number>();
521
+ const callsByTurn = new Map<string, number>();
522
+ /**
523
+ * Turns whose tool use is locked to synthesis after reaching the block
524
+ * budget. Key present means locked; the value carries the block that tripped
525
+ * the lockout (so the backstop's stop message names the looping tool) plus a
526
+ * running count of denials since the lockout for the backstop threshold.
527
+ */
528
+ const lockoutByTurn = new Map<
529
+ string,
530
+ { tool: string; repeatCount: number; blocksThisTurn: number; denials: number; correlationId?: string }
531
+ >();
532
+ /**
533
+ * Lifetime-cap synthesis lockout (workers). Entered exactly once when the
534
+ * observed attempt count crosses the cap while the synthesis lockout is
535
+ * wired, however many parallel calls cross it in the same batch; further
536
+ * attempts count denials toward the same bounded backstop the loop lockout
537
+ * uses. Null until the cap is crossed; without the lockout the cap keeps
538
+ * its original immediate-abort reason.
539
+ */
540
+ let capLockout: { denials: number; correlationId?: string } | null = null;
541
+ /** Graceful worker exploration-phase lockout, separate from hard-cap failure. */
542
+ let softLockout: { denials: number; correlationId?: string } | null = null;
543
+ /**
544
+ * Per-fingerprint count of successful executions this run, for the evidence
545
+ * anchor: when a looped call already returned a result, the block reason
546
+ * points the model at that result instead of only asking for a new strategy.
547
+ * Bounded LRU (larger than the turn maps because a turn can retrieve from
548
+ * many distinct calls before it starts repeating one).
549
+ */
550
+ const succeededFingerprints = new Map<string, number>();
551
+ /**
552
+ * Per-turn streak of consecutive same-shape calls (size args ignored) whose
553
+ * outputs hashed identically. Only the latest streak per turn is tracked:
554
+ * stagnation is a consecutive-call property, so any differently shaped or
555
+ * differently answered call resets it.
556
+ */
557
+ const stagnationByTurn = new Map<string, { reducedFingerprint: string; resultFingerprint: string; streak: number }>();
558
+ const crossArgumentResultsByTurn = new Map<
559
+ string,
560
+ { tool: string; resultFingerprint: string; argumentFingerprints: Set<string>; warned: boolean }
561
+ >();
562
+ let count = 0;
563
+
564
+ const enterSoftReadReserve = (): void => {
565
+ if (softReadReserveEntered) return;
566
+ softReadReserveEntered = true;
567
+ options.onSoftReadReserve?.();
568
+ };
569
+
570
+ const bumpBoundedCounter = (store: Map<string, number>, key: string, limit = LOOP_GUARD_TURN_LIMIT): number => {
571
+ if (!store.has(key)) {
572
+ while (store.size >= limit) {
573
+ const oldest = store.keys().next().value;
574
+ if (typeof oldest !== "string") break;
575
+ store.delete(oldest);
576
+ }
577
+ }
578
+ const next = (store.get(key) ?? 0) + 1;
579
+ store.set(key, next);
580
+ return next;
581
+ };
582
+
583
+ // A result whose observation envelope shows a truncated body with zero
584
+ // items is a cap stub or a budget-exhausted notice: the call technically
585
+ // returned "ok" but the model saw none of the payload. Counting it as a
586
+ // success would make the block reason claim "this exact call already
587
+ // succeeded; re-read that result" about a result that holds nothing.
588
+ const resultCarriesEvidence = (details: MiddlewareHookInput["toolResultDetails"]): boolean => {
589
+ const observation = details?.observation;
590
+ if (observation === null || typeof observation !== "object" || Array.isArray(observation)) return true;
591
+ const record = observation as { truncated?: unknown; shownCount?: unknown };
592
+ return !(record.truncated === true && record.shownCount === 0);
593
+ };
594
+
595
+ // after_tool touchpoint: a successful call (result kind "ok") records a
596
+ // success for its canonical fingerprint. Blocked calls never reach here
597
+ // (admission returns before execution), so only real results anchor.
598
+ const recordSuccessfulResult = (input: MiddlewareHookInput): void => {
599
+ if (input.metadata?.resultKind !== "ok") return;
600
+ if (!resultCarriesEvidence(input.toolResultDetails)) return;
601
+ const tool = input.toolName;
602
+ if (typeof tool !== "string" || tool.length === 0) return;
603
+ bumpBoundedCounter(succeededFingerprints, hashToolCall(tool, input.toolArgs ?? {}), SUCCEEDED_FINGERPRINT_LIMIT);
604
+ };
605
+
606
+ // after_tool touchpoint: extend or reset the per-turn stagnation streak. A
607
+ // result without a fingerprint (errors, non-string outputs) breaks the
608
+ // streak rather than extending it.
609
+ const recordResultForStagnation = (input: MiddlewareHookInput): void => {
610
+ const turnKey = input.turnId ?? NO_TURN_BUCKET;
611
+ const tool = input.toolName;
612
+ const resultFingerprint = input.metadata?.resultFingerprint;
613
+ if (
614
+ typeof tool !== "string" ||
615
+ tool.length === 0 ||
616
+ input.metadata?.resultKind !== "ok" ||
617
+ typeof resultFingerprint !== "string"
618
+ ) {
619
+ stagnationByTurn.delete(turnKey);
620
+ return;
621
+ }
622
+ const reducedFingerprint = stagnationFingerprint(tool, input.toolArgs);
623
+ const previous = stagnationByTurn.get(turnKey);
624
+ if (
625
+ previous !== undefined &&
626
+ previous.reducedFingerprint === reducedFingerprint &&
627
+ previous.resultFingerprint === resultFingerprint
628
+ ) {
629
+ previous.streak += 1;
630
+ return;
631
+ }
632
+ if (!stagnationByTurn.has(turnKey)) {
633
+ while (stagnationByTurn.size >= LOOP_GUARD_TURN_LIMIT) {
634
+ const oldest = stagnationByTurn.keys().next().value;
635
+ if (typeof oldest !== "string") break;
636
+ stagnationByTurn.delete(oldest);
637
+ }
638
+ }
639
+ stagnationByTurn.set(turnKey, { reducedFingerprint, resultFingerprint, streak: 1 });
640
+ };
641
+
642
+ // Diagnostic-only coverage for tools that accept a substantive argument but
643
+ // ignore it. Track one consecutive same-tool/result streak per turn and
644
+ // annotate the third distinct argument shape. Short generic outputs and
645
+ // zero-item cap stubs are deliberately excluded; this path never blocks.
646
+ const crossArgumentResultEffects = (input: MiddlewareHookInput): ReadonlyArray<MiddlewareEffect> => {
647
+ const turnKey = input.turnId ?? NO_TURN_BUCKET;
648
+ const tool = input.toolName;
649
+ const resultFingerprint = input.metadata?.resultFingerprint;
650
+ const resultBytes = input.metadata?.resultBytes;
651
+ if (
652
+ typeof tool !== "string" ||
653
+ tool.length === 0 ||
654
+ input.metadata?.actionClass !== "read" ||
655
+ input.metadata?.resultKind !== "ok" ||
656
+ typeof resultFingerprint !== "string" ||
657
+ typeof resultBytes !== "number" ||
658
+ resultBytes < CROSS_ARGUMENT_RESULT_MIN_BYTES ||
659
+ !resultCarriesEvidence(input.toolResultDetails)
660
+ ) {
661
+ crossArgumentResultsByTurn.delete(turnKey);
662
+ return [];
663
+ }
664
+ const argumentFingerprint = stagnationFingerprint(tool, input.toolArgs);
665
+ const previous = crossArgumentResultsByTurn.get(turnKey);
666
+ if (previous?.tool === tool && previous.resultFingerprint === resultFingerprint) {
667
+ if (previous.warned) return [];
668
+ previous.argumentFingerprints.add(argumentFingerprint);
669
+ if (previous.argumentFingerprints.size >= RESULT_STAGNATION_THRESHOLD) {
670
+ previous.warned = true;
671
+ return [
672
+ {
673
+ kind: "annotate_tool_result",
674
+ message: crossArgumentResultMessage(tool, previous.argumentFingerprints.size),
675
+ severity: "warn",
676
+ },
677
+ ];
678
+ }
679
+ return [];
680
+ }
681
+ if (!crossArgumentResultsByTurn.has(turnKey)) {
682
+ while (crossArgumentResultsByTurn.size >= LOOP_GUARD_TURN_LIMIT) {
683
+ const oldest = crossArgumentResultsByTurn.keys().next().value;
684
+ if (typeof oldest !== "string") break;
685
+ crossArgumentResultsByTurn.delete(oldest);
686
+ }
687
+ }
688
+ crossArgumentResultsByTurn.set(turnKey, {
689
+ tool,
690
+ resultFingerprint,
691
+ argumentFingerprints: new Set([argumentFingerprint]),
692
+ warned: false,
693
+ });
694
+ return [];
695
+ };
696
+
697
+ const bumpTurnBlocks = (turnId: string): number => bumpBoundedCounter(blocksByTurn, turnId);
698
+
699
+ const enterLockout = (
700
+ input: MiddlewareHookInput,
701
+ turnKey: string,
702
+ state: { tool: string; repeatCount: number; blocksThisTurn: number },
703
+ ): void => {
704
+ if (!lockoutByTurn.has(turnKey)) {
705
+ while (lockoutByTurn.size >= LOOP_GUARD_TURN_LIMIT) {
706
+ const oldest = lockoutByTurn.keys().next().value;
707
+ if (typeof oldest !== "string") break;
708
+ lockoutByTurn.delete(oldest);
709
+ }
710
+ }
711
+ lockoutByTurn.set(turnKey, {
712
+ ...state,
713
+ denials: 0,
714
+ ...(input.correlationId !== undefined ? { correlationId: input.correlationId } : {}),
715
+ });
716
+ };
717
+
718
+ /**
719
+ * Count non-compliance by provider/model round, not by sibling tool call.
720
+ * Parallel calls share correlationId; only the first call from a new round
721
+ * spends one denial. Calls without correlation retain legacy per-call bounds.
722
+ */
723
+ const reachesLockoutBackstop = (
724
+ state: { denials: number; correlationId?: string },
725
+ input: MiddlewareHookInput,
726
+ ): boolean => {
727
+ if (input.correlationId !== undefined && state.correlationId === input.correlationId) return false;
728
+ state.denials += 1;
729
+ if (input.correlationId !== undefined) state.correlationId = input.correlationId;
730
+ else delete state.correlationId;
731
+ return state.denials > LOOP_SYNTHESIS_BACKSTOP_DENIALS;
732
+ };
733
+
734
+ const emitLoopBlocked = (
735
+ input: MiddlewareHookInput,
736
+ info: { tool: string; repeatCount: number; blocksThisTurn: number; disposition: LoopBlockedDisposition },
737
+ at: number,
738
+ ): void => {
739
+ const payload: LoopBlockedPayload = {
740
+ tool: info.tool,
741
+ repeatCount: info.repeatCount,
742
+ blocksThisTurn: info.blocksThisTurn,
743
+ budget,
744
+ interrupted: info.disposition === "stop",
745
+ disposition: info.disposition,
746
+ at,
747
+ ...(input.turnId !== undefined ? { turnId: input.turnId } : {}),
748
+ };
749
+ options.bus?.emit(BusChannels.LoopBlocked, payload);
750
+ };
751
+
752
+ const emitBudgetEvent = (
753
+ input: MiddlewareHookInput,
754
+ callsThisTurn: number,
755
+ interrupted: boolean,
756
+ at: number,
757
+ ): void => {
758
+ if (turnBudget === undefined) return;
759
+ const payload: ToolBudgetExceededPayload = {
760
+ tool: input.toolName ?? "unknown",
761
+ callsThisTurn,
762
+ softBudget: turnBudget.soft,
763
+ hardCeiling: turnBudget.hard,
764
+ interrupted,
765
+ at,
766
+ ...(input.turnId !== undefined ? { turnId: input.turnId } : {}),
767
+ };
768
+ options.bus?.emit(BusChannels.ToolBudgetExceeded, payload);
769
+ };
770
+
771
+ const evaluateTurnBudget = (input: MiddlewareHookInput, now: number): ReadonlyArray<MiddlewareEffect> | null => {
772
+ if (turnBudget === undefined) return null;
773
+ const turnKey = input.turnId ?? NO_TURN_BUCKET;
774
+ const callsThisTurn = bumpBoundedCounter(callsByTurn, turnKey);
775
+ if (callsThisTurn >= turnBudget.hard) {
776
+ // Hard ceiling: interrupt the turn the same way the block budget does.
777
+ // The bus event drives chat.cancel() in the interactive layer; the
778
+ // block effect makes the in-flight attempt fail with the same reason.
779
+ if (callsThisTurn === turnBudget.hard) emitBudgetEvent(input, callsThisTurn, true, now);
780
+ return [
781
+ {
782
+ kind: "block_tool",
783
+ reason:
784
+ `tool-call budget exhausted: ${callsThisTurn} tool calls in this turn reached the hard ceiling ` +
785
+ `(${turnBudget.hard}); the turn is being stopped. Summarize what you found and wait for the operator.`,
786
+ severity: "hard-block",
787
+ },
788
+ ];
789
+ }
790
+ if (callsThisTurn >= turnBudget.soft) {
791
+ // Soft budget: block this attempt and every further one this turn.
792
+ // The directive must say so plainly — an earlier wording implied a
793
+ // narrower call could still succeed, which sent even compliant
794
+ // models into a retry spiral because no call after the crossing can
795
+ // ever run. The operator sees one warn notice per turn (the first
796
+ // crossing); the model keeps getting the directive on every further
797
+ // over-budget attempt so it cannot quietly resume spraying calls.
798
+ if (callsThisTurn === turnBudget.soft) emitBudgetEvent(input, callsThisTurn, false, now);
799
+ return [
800
+ {
801
+ kind: "block_tool",
802
+ reason:
803
+ `tool-call budget: you have made ${callsThisTurn} tool calls in this turn (soft budget ${turnBudget.soft}). ` +
804
+ `Every further tool call this turn will be blocked, so do not retry this call and do not substitute ` +
805
+ `another one. Summarize what you have found so far in plain text, state the single next step you ` +
806
+ `propose, and wait for the operator.`,
807
+ severity: "hard-block",
808
+ },
809
+ ];
810
+ }
811
+ return null;
812
+ };
813
+
814
+ // Shared loop-block machinery for both detectors: bump the turn's block
815
+ // count, enter the synthesis lockout at the budget, and emit visibility.
816
+ const blockAsLoop = (
817
+ input: MiddlewareHookInput,
818
+ turnKey: string,
819
+ tool: string,
820
+ repeatCount: number,
821
+ baseReason: string,
822
+ now: number,
823
+ ): ReadonlyArray<MiddlewareEffect> => {
824
+ const blocksThisTurn = bumpTurnBlocks(turnKey);
825
+ const reachedBudget = blocksThisTurn >= budget;
826
+ // Budget reached with the synthesis lockout wired: enter the lockout
827
+ // instead of stopping the turn. The block reason becomes the
828
+ // synthesize-now directive; the LoopBlocked event carries "lockout"
829
+ // (not "stop") so no surface cancels — the model gets its one bounded
830
+ // chance to answer from what it gathered.
831
+ if (synthesisLockout && reachedBudget) {
832
+ enterLockout(input, turnKey, { tool, repeatCount, blocksThisTurn });
833
+ options.onSynthesisLockout?.();
834
+ emitLoopBlocked(input, { tool, repeatCount, blocksThisTurn, disposition: "lockout" }, now);
835
+ return [{ kind: "block_tool", reason: synthesisLockoutDirective(), severity: "hard-block" }];
836
+ }
837
+ // Below budget, or a surface without the lockout (workers): the
838
+ // existing per-block behavior. Reaching the budget without a lockout
839
+ // still stops the turn with the "being stopped" reason.
840
+ emitLoopBlocked(input, { tool, repeatCount, blocksThisTurn, disposition: reachedBudget ? "stop" : "block" }, now);
841
+ const reason = reachedBudget
842
+ ? `${baseReason} Loop budget exhausted (${blocksThisTurn} blocks this turn); the agent is being stopped.`
843
+ : baseReason;
844
+ return [{ kind: "block_tool", reason, severity: "hard-block" }];
845
+ };
846
+
847
+ return {
848
+ id: LOOP_GUARD_REGISTRATION_ID,
849
+ description:
850
+ "blocks repeated tool calls, enforces tool-call caps, and annotates substantial identical read results across distinct arguments",
851
+ hooks: ["before_tool", "after_tool"],
852
+ callCount: () => count,
853
+ evaluate(input): ReadonlyArray<MiddlewareEffect> {
854
+ if (input.hook === "after_tool") {
855
+ recordSuccessfulResult(input);
856
+ recordResultForStagnation(input);
857
+ const effects = [...crossArgumentResultEffects(input)];
858
+ if (
859
+ softReadReserveEntered &&
860
+ softLockout === null &&
861
+ deliveryTools.length === 0 &&
862
+ input.toolName === ToolNames.Read
863
+ ) {
864
+ effects.push({ kind: "require_tool", toolName: ToolNames.Read });
865
+ }
866
+ // One-shot reserve directive: the first executed call inside the
867
+ // reserve window carries the steering annotation, so the model is
868
+ // told about the reserve without a blocked call spending budget.
869
+ if (
870
+ reserveThreshold !== null &&
871
+ cap !== undefined &&
872
+ !reserveDirectiveEmitted &&
873
+ count > reserveThreshold &&
874
+ count <= cap
875
+ ) {
876
+ reserveDirectiveEmitted = true;
877
+ effects.push({
878
+ kind: "annotate_tool_result",
879
+ message: workerSynthesisReserveDirective(Math.max(0, cap - count), cap),
880
+ severity: "warn",
881
+ });
882
+ }
883
+ return effects;
884
+ }
885
+ const now = options.now?.() ?? Date.now();
886
+ const turnKey = input.turnId ?? NO_TURN_BUCKET;
887
+ const decide = (): ReadonlyArray<MiddlewareEffect> => {
888
+ // Once a synthesis phase begins, sibling calls from the already-emitted
889
+ // parallel batch are denied without consuming the lifetime cap or the
890
+ // per-round non-compliance backstop.
891
+ if (capLockout !== null && cap !== undefined) {
892
+ if (reachesLockoutBackstop(capLockout, input)) {
893
+ return [
894
+ { kind: "block_tool", reason: synthesisBackstopReason(input.toolName ?? "unknown"), severity: "hard-block" },
895
+ ];
896
+ }
897
+ return [{ kind: "block_tool", reason: workerToolCallCapSynthesisReason(cap), severity: "hard-block" }];
898
+ }
899
+ if (softLockout !== null && softLimit !== undefined) {
900
+ if (reachesLockoutBackstop(softLockout, input)) {
901
+ return [
902
+ { kind: "block_tool", reason: synthesisBackstopReason(input.toolName ?? "unknown"), severity: "hard-block" },
903
+ ];
904
+ }
905
+ return [{ kind: "block_tool", reason: workerExplorationSynthesisDirective(softLimit), severity: "hard-block" }];
906
+ }
907
+
908
+ // Synthesis lockout after repeated calls. Check before counting the
909
+ // attempt so a wide parallel batch cannot turn a graceful lock into a
910
+ // lifetime-cap failure before the model gets its synthesis round.
911
+ const lockout = synthesisLockout ? lockoutByTurn.get(turnKey) : undefined;
912
+ if (lockout !== undefined) {
913
+ if (reachesLockoutBackstop(lockout, input)) {
914
+ emitLoopBlocked(
915
+ input,
916
+ {
917
+ tool: lockout.tool,
918
+ repeatCount: lockout.repeatCount,
919
+ blocksThisTurn: lockout.blocksThisTurn,
920
+ disposition: "stop",
921
+ },
922
+ now,
923
+ );
924
+ return [{ kind: "block_tool", reason: synthesisBackstopReason(lockout.tool), severity: "hard-block" }];
925
+ }
926
+ return [{ kind: "block_tool", reason: synthesisLockoutDirective(), severity: "hard-block" }];
927
+ }
928
+
929
+ // Soft agent budget and its reserve window, resolved before the call is
930
+ // counted: every refusal below is steering the harness authored, and a
931
+ // call the harness refused never ran. Counting those spent the lifetime
932
+ // cap on the guard's own denials, which is how a worker that obeyed the
933
+ // reserve by writing instead of grepping got killed by it. The bound on
934
+ // refusals is reserveDenials and the synthesis backstop, both per model
935
+ // round; the cap bounds executed work.
936
+ if (softLimit !== undefined) {
937
+ const spentSoftLimit = softAdmittedCount >= softLimit;
938
+ // The soft budget ends discovery, not the run's own product. An agent
939
+ // with no delivery tools has nothing left to do with a tool call, so
940
+ // the budget locks it to synthesis exactly as before. An agent that
941
+ // was granted mutation tools is still holding the files it was
942
+ // dispatched to write, and its bound is the lifetime cap, which sits
943
+ // above this limit for that reason. Refusing delivery here named the
944
+ // refusal after exploration and told a writer its writes were
945
+ // disabled: measured on the wiki documenter, ten of one pass's twelve
946
+ // write attempts were refused with "exploration tools are now
947
+ // disabled" and the pass landed no successful write at all.
948
+ if (spentSoftLimit && deliveryTools.length === 0) {
949
+ softLockout = {
950
+ denials: 0,
951
+ ...(input.correlationId !== undefined ? { correlationId: input.correlationId } : {}),
952
+ };
953
+ options.onSynthesisLockout?.();
954
+ return [{ kind: "block_tool", reason: workerExplorationSynthesisDirective(softLimit), severity: "hard-block" }];
955
+ }
956
+ const inReserve = softReadReserveThreshold !== null && softAdmittedCount >= softReadReserveThreshold;
957
+ if (spentSoftLimit || inReserve) {
958
+ enterSoftReadReserve();
959
+ if (!reserveAdmits(input.toolName)) {
960
+ // A model that keeps calling discovery tools inside the reserve
961
+ // is not going to finish; after a bounded number of model rounds
962
+ // it gets the same graceful synthesis lockout the soft budget
963
+ // ends in, rather than an unbounded bounce.
964
+ if (reachesLockoutBackstop(reserveDenials, input)) {
965
+ softLockout = {
966
+ denials: 0,
967
+ ...(input.correlationId !== undefined ? { correlationId: input.correlationId } : {}),
968
+ };
969
+ options.onSynthesisLockout?.();
970
+ return [{ kind: "block_tool", reason: workerExplorationSynthesisDirective(softLimit), severity: "hard-block" }];
971
+ }
972
+ return [
973
+ {
974
+ kind: "block_tool",
975
+ reason: spentSoftLimit
976
+ ? workerDeliveryOnlyDirective(softLimit, deliveryTools)
977
+ : workerLiveReadReserveDirective(softLimit - softAdmittedCount, deliveryTools),
978
+ severity: "hard-block",
979
+ },
980
+ ];
981
+ }
982
+ }
983
+ }
984
+
985
+ // Synthesis reserve (workers): the tail of the lifetime cap is held for
986
+ // verification reads and delivery. Discovery calls bounce with a
987
+ // steering reason that never carries the cap's machine prefix, so cap
988
+ // telemetry, worker aborts, and A2's cap-exhaustion notice stay
989
+ // untouched; reads and this agent's delivery tools keep flowing through
990
+ // the repetition and stagnation detectors below.
991
+ if (reserveThreshold !== null && cap !== undefined && count >= reserveThreshold && !reserveAdmits(input.toolName)) {
992
+ if (reachesLockoutBackstop(reserveDenials, input)) {
993
+ if (synthesisLockout) {
994
+ capLockout = {
995
+ denials: 0,
996
+ ...(input.correlationId !== undefined ? { correlationId: input.correlationId } : {}),
997
+ };
998
+ options.onSynthesisLockout?.();
999
+ }
1000
+ return [{ kind: "block_tool", reason: workerToolCallCapSynthesisReason(cap), severity: "hard-block" }];
1001
+ }
1002
+ return [
1003
+ {
1004
+ kind: "block_tool",
1005
+ reason: workerSynthesisReserveBlockReason(input.toolName ?? "unknown", Math.max(1, cap - count), cap),
1006
+ severity: "hard-block",
1007
+ },
1008
+ ];
1009
+ }
1010
+
1011
+ count += 1;
1012
+ reserveDenials = { denials: 0 };
1013
+ if (softLimit !== undefined) {
1014
+ softAdmittedCount += 1;
1015
+ if (softReadReserveThreshold !== null && softAdmittedCount >= softReadReserveThreshold) enterSoftReadReserve();
1016
+ if (softAdmittedCount >= softLimit) {
1017
+ // Arming the lockout here is what actually ends the work phase: the
1018
+ // softLockout branch above refuses every later call. A delivery-
1019
+ // capable agent must not be armed, or it would be locked out on the
1020
+ // call after the one that reached its budget and never deliver.
1021
+ // Its ending is the lifetime cap. `onSoftLimitFinalCallAdmitted` is
1022
+ // the separate synthesis:false runtime stop and still fires: an
1023
+ // agent that declared no synthesis phase ends where it said it did.
1024
+ if (deliveryTools.length === 0) {
1025
+ softLockout = {
1026
+ denials: 0,
1027
+ ...(input.correlationId !== undefined ? { correlationId: input.correlationId } : {}),
1028
+ };
1029
+ options.onSynthesisLockout?.();
1030
+ }
1031
+ options.onSoftLimitFinalCallAdmitted?.(input.toolCallId);
1032
+ }
1033
+ }
1034
+ if (cap !== undefined && count > cap) {
1035
+ // No tool body executes past the cap. With the synthesis lockout
1036
+ // wired the run is not aborted on the spot: the first crossing flips
1037
+ // the request-level tool lock (onSynthesisLockout) and directs the
1038
+ // model to answer from what it gathered; a model that keeps emitting
1039
+ // tool calls anyway reaches the bounded backstop, whose reason the
1040
+ // worker runtime recognizes and aborts on. Parallel calls crossing
1041
+ // the cap in one batch enter the lockout exactly once.
1042
+ if (!synthesisLockout) {
1043
+ return [{ kind: "block_tool", reason: workerToolCallCapExceededReason(cap), severity: "hard-block" }];
1044
+ }
1045
+ capLockout = {
1046
+ denials: 0,
1047
+ ...(input.correlationId !== undefined ? { correlationId: input.correlationId } : {}),
1048
+ };
1049
+ options.onSynthesisLockout?.();
1050
+ return [{ kind: "block_tool", reason: workerToolCallCapSynthesisReason(cap), severity: "hard-block" }];
1051
+ }
1052
+
1053
+ // Identical-repeat detection runs BEFORE the volume budget so verbatim
1054
+ // retries of budget-blocked calls still reach the detector. Its tighter
1055
+ // interrupt (a couple of blocks per turn) is what ends the retry spiral
1056
+ // a weak model falls into once the budget starts rejecting calls;
1057
+ // checking the budget first starved the detector and let that spiral
1058
+ // churn all the way to the hard ceiling.
1059
+ const fingerprint = input.metadata?.callFingerprint;
1060
+ if (typeof fingerprint !== "string" || fingerprint.length === 0) {
1061
+ return evaluateTurnBudget(input, now) ?? [];
1062
+ }
1063
+ // The detector key is turn-scoped: its state lives for the whole
1064
+ // session, and the detector now retains the most recent attempts
1065
+ // regardless of age, so an unscoped key would let one identical call
1066
+ // per user turn (rerunning a build across turns) accumulate into a
1067
+ // false loop.
1068
+ const verdict = options.safety.observeLoop(`${turnKey}|${fingerprint}`, now);
1069
+ if (verdict.looping) {
1070
+ const tool = input.toolName ?? "unknown";
1071
+ const priorSuccesses = succeededFingerprints.get(fingerprint) ?? 0;
1072
+ return blockAsLoop(
1073
+ input,
1074
+ turnKey,
1075
+ tool,
1076
+ verdict.count,
1077
+ loopBlockBaseReason(tool, verdict.count, priorSuccesses),
1078
+ now,
1079
+ );
1080
+ }
1081
+
1082
+ // Stagnation detection: the incoming call has the same shape (size
1083
+ // args ignored) as a streak of calls whose results hashed identical.
1084
+ // The escalation cycle (limit: 10k -> 20k -> 50k -> 10k ...) varies
1085
+ // arguments enough to evade the verbatim detector while never
1086
+ // producing new information; block the attempt the streak proves
1087
+ // futile.
1088
+ const toolName = input.toolName;
1089
+ if (typeof toolName === "string" && toolName.length > 0) {
1090
+ const entry = stagnationByTurn.get(turnKey);
1091
+ if (
1092
+ entry !== undefined &&
1093
+ entry.streak >= RESULT_STAGNATION_THRESHOLD - 1 &&
1094
+ entry.reducedFingerprint === stagnationFingerprint(toolName, input.toolArgs)
1095
+ ) {
1096
+ return blockAsLoop(
1097
+ input,
1098
+ turnKey,
1099
+ toolName,
1100
+ entry.streak + 1,
1101
+ stagnationBlockReason(toolName, entry.streak),
1102
+ now,
1103
+ );
1104
+ }
1105
+ }
1106
+ return evaluateTurnBudget(input, now) ?? [];
1107
+ };
1108
+ return boundRunDenials(decide(), input);
1109
+ },
1110
+ };
1111
+ }
1112
+
1113
+ export function isReserveAdmittedTool(tool: string | undefined, deliveryTools: ReadonlyArray<string>): boolean {
1114
+ return tool === ToolNames.Read || (tool !== undefined && deliveryTools.includes(tool));
1115
+ }
1116
+
1117
+ export function resolveDeliveryTools(
1118
+ allowedTools: ReadonlyArray<string> | ReadonlySet<string>,
1119
+ product?: AgentProduct,
1120
+ ): string[] {
1121
+ const candidates: string[] =
1122
+ product === "orientation" ? [ToolNames.Write, ToolNames.Edit, ToolNames.CodeNav] : [ToolNames.Write, ToolNames.Edit];
1123
+ if (allowedTools instanceof Set) {
1124
+ return candidates.filter((tool) => allowedTools.has(tool));
1125
+ }
1126
+ return candidates.filter((tool) => (allowedTools as ReadonlyArray<string>).includes(tool));
1127
+ }