@iowarp/clio-coder 0.3.7 → 0.3.9

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 (400) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/README.md +13 -4
  3. package/dist/{acp-SK4MD6MM.js → acp-7LOELQFP.js} +13 -13
  4. package/dist/{agents-2FN2K6ME.js → agents-FIBG2SHA.js} +41 -37
  5. package/dist/assets/codewiki.json +1 -1
  6. package/dist/{auth-QIYZWM5I.js → auth-OI4LIH2I.js} +31 -24
  7. package/dist/builtins-AD25UL3C.js +17 -0
  8. package/dist/{chunk-5FR74PWO.js → chunk-2JDWVJND.js} +2 -2
  9. package/dist/chunk-3DPEIQKN.js +113 -0
  10. package/dist/{chunk-EOOQZZDE.js → chunk-3DUR4WUA.js} +19 -19
  11. package/dist/{chunk-WHJYKASB.js → chunk-3MRC2YSQ.js} +2 -2
  12. package/dist/{chunk-EBEFWSGL.js → chunk-3UUY7R3Z.js} +14 -10
  13. package/dist/{chunk-LADCF22A.js → chunk-3V5AYSEQ.js} +113 -54
  14. package/dist/{chunk-BMWK7ZIZ.js → chunk-465CC7FK.js} +16 -13
  15. package/dist/{chunk-5WIGXA4T.js → chunk-47CMYGET.js} +111 -4
  16. package/dist/{chunk-CEYBNUGC.js → chunk-4H6ULJ3H.js} +378 -36
  17. package/dist/{chunk-YTYFXUI3.js → chunk-4LJX2PUC.js} +9 -9
  18. package/dist/{chunk-DOOEX22V.js → chunk-56KB5IJP.js} +5 -5
  19. package/dist/{chunk-SPULKLCF.js → chunk-5DQRIYDZ.js} +2 -2
  20. package/dist/{chunk-TSHXZTOQ.js → chunk-5HFBWUMU.js} +23 -11
  21. package/dist/{chunk-5UJ6ECTS.js → chunk-5PVQ4SRS.js} +80 -8
  22. package/dist/{chunk-ZWLZP4ZT.js → chunk-5QKCQQ3E.js} +359 -17
  23. package/dist/{chunk-6M7VS3J3.js → chunk-5T7RBWN2.js} +111 -5
  24. package/dist/chunk-774ILSRL.js +172 -0
  25. package/dist/chunk-7C6RYZGQ.js +391 -0
  26. package/dist/{chunk-GH5622CP.js → chunk-A2NJGIB3.js} +2 -2
  27. package/dist/{chunk-C4JBQ5SR.js → chunk-AD7Y7STJ.js} +6 -6
  28. package/dist/{chunk-GEYXPTRF.js → chunk-AEYBF3TB.js} +33 -12
  29. package/dist/{chunk-2SFS6XQE.js → chunk-AMKHQW3C.js} +3 -2
  30. package/dist/{chunk-D4MDIG46.js → chunk-B5CSFE7B.js} +7 -7
  31. package/dist/{chunk-MXI6J5JF.js → chunk-B5XRQOLB.js} +10 -10
  32. package/dist/{chunk-X2KV5FXT.js → chunk-BVDVID7E.js} +2 -2
  33. package/dist/{chunk-JNXPYBB4.js → chunk-CA42X6KT.js} +3 -3
  34. package/dist/{chunk-VREKEFLL.js → chunk-D73KXYPF.js} +3 -3
  35. package/dist/{chunk-JTSEDYVQ.js → chunk-DG4M6ZUE.js} +7 -7
  36. package/dist/{chunk-DQA7QLMD.js → chunk-EBOC7MT3.js} +10 -25
  37. package/dist/{chunk-KZ2H5X4G.js → chunk-ECUO3KDP.js} +129 -14
  38. package/dist/{chunk-JRIO5UD2.js → chunk-EQ63NRB7.js} +5 -5
  39. package/dist/{chunk-YD734TPH.js → chunk-FALJGAWU.js} +2 -2
  40. package/dist/{chunk-GWS3VEIW.js → chunk-FWDFM5ZU.js} +24 -3
  41. package/dist/{chunk-XEGB6BCN.js → chunk-GAYUJ7LE.js} +68 -14
  42. package/dist/{chunk-UND3GU2L.js → chunk-H7IXIC72.js} +2 -2
  43. package/dist/{chunk-IR4CFBFN.js → chunk-HAY4ZE2P.js} +12 -12
  44. package/dist/{chunk-UVDSQ6LW.js → chunk-HCBCAYZU.js} +74 -147
  45. package/dist/{chunk-4DWFMQDR.js → chunk-HJB5IUKP.js} +89 -145
  46. package/dist/{chunk-M4AKACEO.js → chunk-HKO36JWF.js} +33 -5
  47. package/dist/{chunk-KCMKRQX4.js → chunk-HPCTNZM2.js} +45 -82
  48. package/dist/{chunk-465YSENW.js → chunk-IFBNV6H6.js} +3 -3
  49. package/dist/{chunk-FJ3H4MN5.js → chunk-IHKBWSXF.js} +2 -2
  50. package/dist/chunk-JEQQR47K.js +3025 -0
  51. package/dist/{chunk-FO5ZOVUY.js → chunk-KV2AOLDF.js} +27 -7
  52. package/dist/chunk-LU7P4LHA.js +33 -0
  53. package/dist/{chunk-6TUKSZVF.js → chunk-LXPJXFM5.js} +11 -11
  54. package/dist/{chunk-VQNODYQ4.js → chunk-MIX5N5AC.js} +488 -3668
  55. package/dist/chunk-MLOK6ZOS.js +2888 -0
  56. package/dist/{chunk-ZZMN5OM4.js → chunk-MV2VUEJC.js} +2 -2
  57. package/dist/{chunk-MVVUPGPW.js → chunk-MXHC5QYU.js} +6 -6
  58. package/dist/{chunk-OBMAI2DP.js → chunk-N3PBVRTZ.js} +12 -388
  59. package/dist/{chunk-WJHBC77E.js → chunk-N5XKWMDW.js} +17 -7
  60. package/dist/{chunk-5C3AQNDW.js → chunk-NNNWO6F2.js} +124 -36
  61. package/dist/{chunk-UFQ3F4FW.js → chunk-NQ6UCCOD.js} +4 -4
  62. package/dist/chunk-NUGM5KR6.js +165 -0
  63. package/dist/{chunk-DMD2AGVS.js → chunk-NZU6YDNV.js} +20 -18
  64. package/dist/{chunk-WHGPSPT5.js → chunk-O6I4CIEU.js} +151 -13
  65. package/dist/{chunk-XN3L4EYL.js → chunk-OEDBCISO.js} +2 -2
  66. package/dist/{chunk-PD3MESLB.js → chunk-P3JGPQFL.js} +4 -4
  67. package/dist/{chunk-UHXRNZ2J.js → chunk-PNY46YEY.js} +23 -6
  68. package/dist/{chunk-THKY7CD7.js → chunk-PZ4I4JE2.js} +134 -29
  69. package/dist/{chunk-SROCI7ZU.js → chunk-QQ7EKM72.js} +5 -5
  70. package/dist/{chunk-QCTRSGHQ.js → chunk-R7LNVMCS.js} +91 -53
  71. package/dist/{chunk-GOXNB3AO.js → chunk-RAPCMZL4.js} +75 -4
  72. package/dist/chunk-RKKLTLYB.js +45 -0
  73. package/dist/{chunk-OB5HIGJY.js → chunk-RKRLDWD3.js} +4 -1
  74. package/dist/{chunk-DJNLUABN.js → chunk-S4COXYBG.js} +588 -32
  75. package/dist/{chunk-3HAPLH5M.js → chunk-T3Z6VAAF.js} +172 -11
  76. package/dist/{chunk-FOT2FX5J.js → chunk-TD7UE2L5.js} +12 -10
  77. package/dist/{chunk-UUANF5CR.js → chunk-TEO2TLVN.js} +856 -967
  78. package/dist/{chunk-FCSXB6T2.js → chunk-UOSL25KY.js} +14 -2
  79. package/dist/{chunk-EELBMBT6.js → chunk-VKBMFOYV.js} +74 -15
  80. package/dist/chunk-VO2LKSTM.js +165 -0
  81. package/dist/{chunk-5C77SEEY.js → chunk-VPTUJU4P.js} +3 -3
  82. package/dist/{chunk-WEH5XRJQ.js → chunk-WIE7ZOSW.js} +2 -2
  83. package/dist/{chunk-J7PIKKWC.js → chunk-WXCJ7VME.js} +8 -8
  84. package/dist/{chunk-4DGYLA73.js → chunk-XDOQXGFO.js} +22 -7
  85. package/dist/{chunk-PPAMZ32Z.js → chunk-XK56QHLX.js} +6 -1
  86. package/dist/{chunk-AB4XIIVB.js → chunk-YKOFT37S.js} +6 -6
  87. package/dist/chunk-YSEHGPCT.js +127 -0
  88. package/dist/{chunk-HFSBBKSQ.js → chunk-YW7UVM5V.js} +138 -3
  89. package/dist/cli/index.js +32 -32
  90. package/dist/{clio-WBVQEBKO.js → clio-LT5V7SSZ.js} +9 -9
  91. package/dist/{code-nav-FGGFIE7L.js → code-nav-LMW275PA.js} +5 -5
  92. package/dist/codewiki/build-worker.js +4 -4
  93. package/dist/{components-F7OEATSO.js → components-ZFA3SAER.js} +8 -8
  94. package/dist/{config-TRBL3RCF.js → config-RXS5T3JT.js} +98 -65
  95. package/dist/{configure-OLCVPHNM.js → configure-2WYWSCSD.js} +26 -22
  96. package/dist/{context-MJIJ6GOX.js → context-I3BTOTCS.js} +12 -12
  97. package/dist/{context-XEWE3MOJ.js → context-MVOORGMF.js} +54 -47
  98. package/dist/{context-WFPKQSM6.js → context-PALKKQYL.js} +28 -28
  99. package/dist/{context-clear-KNOS2JPB.js → context-clear-N2WOYZ2K.js} +53 -46
  100. package/dist/{context-index-SSR5ECNE.js → context-index-HNG3MOME.js} +6 -6
  101. package/dist/{context-working-set-EUXAZI6N.js → context-working-set-MIEVECVZ.js} +17 -18
  102. package/dist/{dispatch-runner-B7MTOVKL.js → dispatch-runner-VVA4SRRH.js} +90 -61
  103. package/dist/{docs-FLJTIDSE.js → docs-7LQ23DLM.js} +8 -8
  104. package/dist/doctor-TWBWFK5V.js +165 -0
  105. package/dist/eval-IJ5VEZDJ.js +4483 -0
  106. package/dist/{evidence-JZNBUOQZ.js → evidence-L5APPXNV.js} +68 -61
  107. package/dist/{evolve-FJVC4KKI.js → evolve-RGNKFJ52.js} +47 -40
  108. package/dist/{extensions-IQL36S7K.js → extensions-7WYWUX5A.js} +13 -7
  109. package/dist/{fleet-BDKYJFCP.js → fleet-6CNVBZZP.js} +113 -76
  110. package/dist/{fleet-commands-ZFIWZSB3.js → fleet-commands-L2SXSYEI.js} +10 -10
  111. package/dist/{fleet-graph-Y6HPXIVF.js → fleet-graph-2J3OOIPO.js} +17 -15
  112. package/dist/{fleet-preflight-BHSNPBMH.js → fleet-preflight-CZRJ4JP5.js} +5 -6
  113. package/dist/{fleet-validate-BIYREGIK.js → fleet-validate-C5RI6DP7.js} +20 -19
  114. package/dist/{init-LQUB5COQ.js → init-VBN2ACVA.js} +70 -63
  115. package/dist/{library-NJAHIGG4.js → library-JHGUMLY2.js} +22 -20
  116. package/dist/{memory-OG6HOYKM.js → memory-K4OQIYWG.js} +49 -42
  117. package/dist/{models-5ZG5XY7J.js → models-2NCZUWDD.js} +35 -29
  118. package/dist/{monitor-TJ7AMTGB.js → monitor-MMVTJABD.js} +64 -45
  119. package/dist/{orchestrator-WZYB54DM.js → orchestrator-ZKBPCHW6.js} +1971 -520
  120. package/dist/{paths-XUC7GS6E.js → paths-DBXMZMDU.js} +5 -5
  121. package/dist/registry-LG64LTF4.js +11 -0
  122. package/dist/{reset-PXQT45IY.js → reset-DD5JGOY3.js} +11 -11
  123. package/dist/{run-FQ74YF62.js → run-QEGNX7FL.js} +89 -83
  124. package/dist/{share-FW7SVCL3.js → share-JKD3BQMW.js} +20 -18
  125. package/dist/{skills-7E7IRB3R.js → skills-LMQIKDOZ.js} +23 -21
  126. package/dist/{skills-eval-LI75W6OK.js → skills-eval-I7X2774U.js} +59 -52
  127. package/dist/{steer-GGWFUJUD.js → steer-CF5TDANS.js} +3 -3
  128. package/dist/support-I7LOJLIF.js +38 -0
  129. package/dist/{targets-4CIFKCTW.js → targets-RUSR6B5Z.js} +77 -42
  130. package/dist/{terminal-lease-WUZY7ZV5.js → terminal-lease-QYVORFR4.js} +6 -4
  131. package/dist/{trace-PNCASAXC.js → trace-ODOQIVIW.js} +61 -6
  132. package/dist/{uninstall-7FV7IP4E.js → uninstall-ZJF5H5ZN.js} +8 -8
  133. package/dist/{upgrade-K2HVIVMQ.js → upgrade-XANW3FXB.js} +29 -26
  134. package/dist/{usage-GTZELZQX.js → usage-4H7ZRXQT.js} +110 -61
  135. package/dist/{verifiers-RLAHT27O.js → verifiers-UZXNBZEB.js} +13 -13
  136. package/dist/{verify-BX3BRKH5.js → verify-BVKWTNDL.js} +9 -9
  137. package/dist/{wiki-generate-ASIFASCN.js → wiki-generate-MY7WV2QI.js} +76 -69
  138. package/dist/worker/entry.js +69 -66
  139. package/docs/alcf-provider.md +1 -1
  140. package/docs/architecture.md +1 -1
  141. package/docs/artifact-versions.md +11 -5
  142. package/docs/built-in-agents.md +1 -1
  143. package/docs/capacity-and-scheduling.md +23 -2
  144. package/docs/commands-and-modes.md +2 -2
  145. package/docs/configuration-and-targets.md +37 -5
  146. package/docs/context-engine.md +63 -4
  147. package/docs/documentation-coverage.md +3 -3
  148. package/docs/documentation-guide.md +1 -1
  149. package/docs/environment-variables.md +2 -0
  150. package/docs/eval-runner.md +262 -11
  151. package/docs/evals-internal.md +72 -2
  152. package/docs/evidence-and-memory.md +77 -12
  153. package/docs/evolution.md +1 -1
  154. package/docs/extensions-and-sharing.md +3 -1
  155. package/docs/fleet-dispatch.md +34 -9
  156. package/docs/glossary.md +21 -1
  157. package/docs/installation-and-lifecycle.md +1 -1
  158. package/docs/middleware-and-components.md +1 -1
  159. package/docs/model-catalog.md +1 -1
  160. package/docs/observability.md +54 -3
  161. package/docs/proactive-memory.md +127 -14
  162. package/docs/prompt-envelope-and-tools.md +22 -2
  163. package/docs/provider-adapter-cookbook.md +1 -1
  164. package/docs/release-cut-checklist.md +60 -41
  165. package/docs/safety-model.md +1 -1
  166. package/docs/scientific-validation.md +1 -1
  167. package/docs/skills-marketplace.md +1 -1
  168. package/docs/tool-usage.md +1 -1
  169. package/docs/trace-store.md +1 -1
  170. package/docs/troubleshooting.md +87 -0
  171. package/docs/tui-design.md +1 -1
  172. package/docs/worker-dispatch-mechanics.md +1 -1
  173. package/package.json +2 -2
  174. package/src/cli/agents.ts +1 -1
  175. package/src/cli/argv.ts +5 -0
  176. package/src/cli/config-inspect.ts +33 -6
  177. package/src/cli/config.ts +1 -1
  178. package/src/cli/configure.ts +107 -23
  179. package/src/cli/doctor-state-size.ts +82 -0
  180. package/src/cli/doctor.ts +7 -1
  181. package/src/cli/eval.ts +80 -16
  182. package/src/cli/evidence.ts +30 -25
  183. package/src/cli/extensions.ts +5 -1
  184. package/src/cli/fleet-preflight.ts +2 -12
  185. package/src/cli/fleet.ts +32 -3
  186. package/src/cli/shared.ts +1 -0
  187. package/src/cli/targets.ts +45 -11
  188. package/src/cli/trace.ts +63 -4
  189. package/src/cli/usage.ts +63 -14
  190. package/src/cli/validate-model.ts +60 -5
  191. package/src/core/bus-events.ts +54 -1
  192. package/src/core/cache-telemetry.ts +42 -0
  193. package/src/core/commit-attribution.ts +4 -4
  194. package/src/core/config.ts +18 -0
  195. package/src/core/defaults.ts +36 -6
  196. package/src/core/endpoint-key.ts +27 -0
  197. package/src/core/path-boundary.ts +100 -0
  198. package/src/core/residency-target-key.ts +25 -0
  199. package/src/core/response-schema.ts +36 -2
  200. package/src/domains/agents/extension.ts +2 -11
  201. package/src/domains/agents/fleet-contract.ts +30 -12
  202. package/src/domains/agents/recipe.ts +7 -1
  203. package/src/domains/agents/registry.ts +73 -5
  204. package/src/domains/agents/result-contract.ts +128 -17
  205. package/src/domains/agents/write-boundary.ts +15 -50
  206. package/src/domains/config/classify.ts +3 -0
  207. package/src/domains/context/codewiki/coordinator.ts +12 -4
  208. package/src/domains/context/project-rules.ts +51 -1
  209. package/src/domains/dispatch/admission.ts +40 -3
  210. package/src/domains/dispatch/assignment-reconcile.ts +22 -5
  211. package/src/domains/dispatch/assignment-store.ts +151 -14
  212. package/src/domains/dispatch/capacity-lease.ts +98 -9
  213. package/src/domains/dispatch/contract.ts +26 -1
  214. package/src/domains/dispatch/delegation-plan.ts +2 -5
  215. package/src/domains/dispatch/execution-plan.ts +44 -4
  216. package/src/domains/dispatch/execution-role.ts +9 -1
  217. package/src/domains/dispatch/extension.ts +309 -96
  218. package/src/domains/dispatch/fleet-run.ts +78 -4
  219. package/src/domains/dispatch/gate-role-prompts.ts +38 -0
  220. package/src/domains/dispatch/heartbeat.ts +32 -8
  221. package/src/domains/dispatch/index.ts +6 -1
  222. package/src/domains/dispatch/intent-requirements.ts +40 -0
  223. package/src/domains/dispatch/intent.ts +84 -8
  224. package/src/domains/dispatch/orphan-recovery.ts +5 -0
  225. package/src/domains/dispatch/path-scope.ts +370 -0
  226. package/src/domains/dispatch/receipt-integrity.ts +2 -1
  227. package/src/domains/dispatch/reservation-store.ts +116 -8
  228. package/src/domains/dispatch/state.ts +4 -0
  229. package/src/domains/dispatch/types.ts +14 -7
  230. package/src/domains/dispatch/validation.ts +6 -3
  231. package/src/domains/dispatch/worker-spawn.ts +25 -11
  232. package/src/domains/dispatch/write-boundary-enforcer.ts +62 -0
  233. package/src/domains/dispatch/write-boundary.ts +262 -22
  234. package/src/domains/eval/artifacts/store.ts +62 -0
  235. package/src/domains/eval/compare/behavioral.ts +224 -0
  236. package/src/domains/eval/compare/compare.ts +355 -2
  237. package/src/domains/eval/compare/envelope.ts +128 -0
  238. package/src/domains/eval/compare/gates.ts +24 -6
  239. package/src/domains/eval/compare/thresholds.ts +30 -3
  240. package/src/domains/eval/execution-provenance.ts +240 -0
  241. package/src/domains/eval/metrics/aggregate.ts +136 -0
  242. package/src/domains/eval/metrics/call-ledger-stream.ts +112 -0
  243. package/src/domains/eval/metrics/evidence.ts +79 -2
  244. package/src/domains/eval/metrics/tracked.ts +413 -0
  245. package/src/domains/eval/provenance.ts +117 -0
  246. package/src/domains/eval/reports/comparison.ts +128 -0
  247. package/src/domains/eval/reports/junit.ts +17 -3
  248. package/src/domains/eval/reports/markdown.ts +3 -3
  249. package/src/domains/eval/reports/text.ts +14 -0
  250. package/src/domains/eval/run-compare.ts +20 -0
  251. package/src/domains/eval/runners/clio-run.ts +139 -2
  252. package/src/domains/eval/runners/external-command.ts +28 -3
  253. package/src/domains/eval/schema/adapter.ts +111 -0
  254. package/src/domains/eval/schema/artifact.ts +20 -0
  255. package/src/domains/eval/schema/behavioral-metrics.ts +204 -0
  256. package/src/domains/eval/schema/behavioral.ts +520 -0
  257. package/src/domains/eval/schema/execution-envelope.ts +194 -0
  258. package/src/domains/eval/schema/serving.ts +74 -0
  259. package/src/domains/eval/schema/suite.ts +38 -8
  260. package/src/domains/eval/schema/validate.ts +58 -3
  261. package/src/domains/eval/schema/verdict.ts +237 -0
  262. package/src/domains/eval/suites/resolve.ts +2 -0
  263. package/src/domains/eval/suites/run.ts +264 -33
  264. package/src/domains/eval/verifiers/command.ts +2 -1
  265. package/src/domains/eval/workspaces/temp-copy.ts +145 -13
  266. package/src/domains/evidence/build.ts +68 -21
  267. package/src/domains/evidence/eval.ts +2 -12
  268. package/src/domains/evidence/findings-markdown.ts +33 -0
  269. package/src/domains/evidence/index.ts +21 -0
  270. package/src/domains/evidence/provenance.ts +46 -11
  271. package/src/domains/evidence/run-trust.ts +7 -113
  272. package/src/domains/evidence/trust-projection.ts +274 -0
  273. package/src/domains/evidence/trust-status.ts +145 -17
  274. package/src/domains/evidence/types.ts +4 -0
  275. package/src/domains/extensions/compatibility.ts +285 -0
  276. package/src/domains/extensions/discovery.ts +126 -4
  277. package/src/domains/extensions/resources.ts +21 -9
  278. package/src/domains/extensions/state.ts +18 -5
  279. package/src/domains/extensions/types.ts +6 -1
  280. package/src/domains/lifecycle/doctor.ts +209 -2
  281. package/src/domains/memory/index.ts +14 -0
  282. package/src/domains/memory/task-bank-promotion.ts +64 -0
  283. package/src/domains/memory/task-memory-policy.ts +77 -8
  284. package/src/domains/memory/task-memory-spend.ts +131 -0
  285. package/src/domains/memory/task-memory-status.ts +7 -0
  286. package/src/domains/memory/task-memory-telemetry.ts +2 -0
  287. package/src/domains/middleware/index.ts +1 -0
  288. package/src/domains/middleware/memory-intervention.ts +69 -5
  289. package/src/domains/middleware/memory-step-endpoint.ts +71 -0
  290. package/src/domains/observability/background-memory-usage.ts +140 -0
  291. package/src/domains/observability/cost.ts +1 -1
  292. package/src/domains/observability/index.ts +7 -0
  293. package/src/domains/observability/out-of-turn-usage.ts +51 -2
  294. package/src/domains/observability/trace-store.ts +192 -2
  295. package/src/domains/prompts/compiler.ts +100 -13
  296. package/src/domains/prompts/contract.ts +3 -5
  297. package/src/domains/providers/endpoint-capacity.ts +96 -0
  298. package/src/domains/providers/extension.ts +30 -2
  299. package/src/domains/providers/index.ts +10 -0
  300. package/src/domains/providers/models/local-models/clio-local-coding-targets.yaml +243 -1
  301. package/src/domains/providers/runtime-resolution.ts +8 -1
  302. package/src/domains/providers/runtimes/common/probe-helpers.ts +31 -9
  303. package/src/domains/providers/runtimes/local-native/llamacpp-anthropic.ts +1 -1
  304. package/src/domains/providers/runtimes/local-native/llamacpp-completion.ts +1 -1
  305. package/src/domains/providers/runtimes/local-native/llamacpp-embed.ts +1 -1
  306. package/src/domains/providers/runtimes/local-native/llamacpp-rerank.ts +1 -1
  307. package/src/domains/providers/runtimes/local-native/llamacpp.ts +4 -1
  308. package/src/domains/providers/runtimes/local-native/lmstudio.ts +4 -1
  309. package/src/domains/providers/runtimes/local-native/ollama-native.ts +6 -1
  310. package/src/domains/providers/types/capability-flags.ts +2 -0
  311. package/src/domains/providers/types/target-descriptor.ts +2 -0
  312. package/src/domains/resources/common-loader.ts +3 -0
  313. package/src/domains/resources/prompts/loader.ts +184 -17
  314. package/src/domains/safety/call-target.ts +52 -0
  315. package/src/domains/safety/policy-engine.ts +5 -5
  316. package/src/domains/safety/run-effects.ts +96 -2
  317. package/src/domains/safety/scope.ts +7 -12
  318. package/src/domains/session/context-accounting.ts +52 -1
  319. package/src/domains/session/context-ledger.ts +37 -13
  320. package/src/domains/session/index.ts +6 -0
  321. package/src/domains/session/prompt-cache.ts +140 -0
  322. package/src/domains/session/prompt-manifest.ts +42 -0
  323. package/src/engine/acp/adapter.ts +18 -3
  324. package/src/engine/acp/server.ts +4 -1
  325. package/src/engine/ai.ts +35 -0
  326. package/src/engine/apis/llamacpp-residency.ts +55 -3
  327. package/src/engine/apis/lmstudio.ts +25 -5
  328. package/src/engine/apis/ollama-native.ts +2 -1
  329. package/src/engine/apis/openai-completions.ts +80 -17
  330. package/src/engine/apis/residency-lock.ts +3 -1
  331. package/src/engine/apis/residency.ts +34 -1
  332. package/src/engine/prompt-templates.ts +18 -1
  333. package/src/engine/provider-payload.ts +29 -1
  334. package/src/engine/worker-runtime.ts +6 -3
  335. package/src/entry/orchestrator.ts +176 -30
  336. package/src/interactive/chat-loop-messages.ts +26 -7
  337. package/src/interactive/chat-loop.ts +318 -41
  338. package/src/interactive/chat-panel.ts +62 -8
  339. package/src/interactive/clio-editor.ts +45 -8
  340. package/src/interactive/context-activity.ts +5 -1
  341. package/src/interactive/context-meter.ts +1 -1
  342. package/src/interactive/context-overlay.ts +40 -10
  343. package/src/interactive/cost-overlay.ts +64 -6
  344. package/src/interactive/dispatch-board.ts +127 -5
  345. package/src/interactive/fleet-run-preview.ts +41 -15
  346. package/src/interactive/handoff-round.ts +41 -2
  347. package/src/interactive/interactive-application.ts +24 -1
  348. package/src/interactive/interactive-event-projection.ts +14 -0
  349. package/src/interactive/interactive-input-runtime.ts +8 -0
  350. package/src/interactive/interactive-presentation.ts +4 -0
  351. package/src/interactive/interactive-shell.ts +20 -17
  352. package/src/interactive/interactive-slash-runtime.ts +27 -4
  353. package/src/interactive/memory-overlay.ts +8 -0
  354. package/src/interactive/mutation-preview.ts +295 -0
  355. package/src/interactive/overlay-general-openers.ts +16 -0
  356. package/src/interactive/overlay-key-routing.ts +38 -0
  357. package/src/interactive/overlay-lifecycle.ts +38 -5
  358. package/src/interactive/overlay-permission-lifecycle.ts +22 -2
  359. package/src/interactive/overlay-session-lifecycle.ts +73 -9
  360. package/src/interactive/overlays/ask-user.ts +91 -19
  361. package/src/interactive/overlays/help-reference.ts +4 -0
  362. package/src/interactive/overlays/prompts.ts +11 -1
  363. package/src/interactive/overlays/settings.ts +176 -48
  364. package/src/interactive/permission-hint.ts +34 -2
  365. package/src/interactive/permission-overlay.ts +159 -9
  366. package/src/interactive/prewarm.ts +197 -0
  367. package/src/interactive/render-trace.ts +162 -15
  368. package/src/interactive/renderers/tool-execution.ts +4 -0
  369. package/src/interactive/side-question.ts +58 -1
  370. package/src/interactive/slash-commands.ts +7 -2
  371. package/src/interactive/status/controller.ts +11 -0
  372. package/src/interactive/status/state-machine.ts +54 -2
  373. package/src/interactive/status/types.ts +7 -0
  374. package/src/interactive/terminal-lease.ts +2 -0
  375. package/src/interactive/turn-context.ts +299 -31
  376. package/src/interactive/turn-persistence.ts +14 -4
  377. package/src/interactive/turn-prewarm.ts +364 -0
  378. package/src/interactive/turn-queues.ts +7 -4
  379. package/src/interactive/turn-runtime.ts +8 -1
  380. package/src/interactive/turn-state.ts +23 -0
  381. package/src/interactive/view/artifacts.ts +42 -9
  382. package/src/interactive/view/view-overlay.ts +43 -6
  383. package/src/interactive/worker-receipts.ts +14 -2
  384. package/src/interactive/worker-stream.ts +8 -0
  385. package/src/tools/ask-user.ts +43 -2
  386. package/src/tools/dispatch-admission.ts +12 -13
  387. package/src/tools/dispatch-arguments.ts +27 -0
  388. package/src/tools/dispatch-plan.ts +46 -9
  389. package/src/tools/dispatch-runner.ts +48 -13
  390. package/src/tools/dispatch-scout.ts +1 -1
  391. package/src/tools/monitor.ts +13 -0
  392. package/src/tools/registry.ts +16 -0
  393. package/src/tools/worker-evidence.ts +19 -13
  394. package/src/worker/spec-contract.ts +2 -1
  395. package/dist/chunk-AOCYTWAV.js +0 -449
  396. package/dist/chunk-HWUFFB6L.js +0 -83
  397. package/dist/chunk-R346GLFC.js +0 -31
  398. package/dist/chunk-ZGH7FGS5.js +0 -1079
  399. package/dist/doctor-RN4YKO2X.js +0 -87
  400. package/dist/eval-RUBJVSNQ.js +0 -2557
@@ -16,6 +16,7 @@
16
16
  * proportional reconciliation the footer bar already performs.
17
17
  */
18
18
 
19
+ import type { BackendCacheVerdict, BackendCompletionTimings } from "../../core/cache-telemetry.js";
19
20
  import type { ContextWindowSlots, ContextWindowSource } from "../providers/index.js";
20
21
  import { DEFAULT_COMPACTION_THRESHOLD } from "./compaction/auto.js";
21
22
 
@@ -100,6 +101,22 @@ export interface BuildContextLedgerInput {
100
101
  } | null;
101
102
  /** Prompt-cache honesty line; null until a turn has compiled and settled. */
102
103
  promptCache?: PromptCacheStats | null;
104
+ /** Last pre-warm since the previous settled run; null when none has run. */
105
+ prewarm?: PrewarmStats | null;
106
+ }
107
+
108
+ /**
109
+ * One pre-warm's result. It is deliberately not a token category: the pre-warm
110
+ * spends no window space and adds no message, it only says the backend has
111
+ * already prefilled the prefix the next turn will send.
112
+ */
113
+ export interface PrewarmStats {
114
+ /** Prompt tokens the backend processed; null when it reported no counts. */
115
+ tokens: number | null;
116
+ /** Wall clock the round took. */
117
+ ms: number;
118
+ /** True when the operator submitted before the round settled. */
119
+ aborted: boolean;
103
120
  }
104
121
 
105
122
  /**
@@ -117,13 +134,17 @@ export interface PromptCacheStats {
117
134
  cacheWriteTokens: number | null;
118
135
  /** Provider-reported uncached input tokens for the last run. */
119
136
  uncachedInputTokens: number | null;
137
+ /** Serving-backend timings for the run's last API call; null when none were captured. */
138
+ backend: BackendCompletionTimings | null;
139
+ /** Backend prompt tokens not served from cache, derived only when cache reads were reported. */
140
+ uncachedPrefillTokens: number | null;
120
141
  /**
121
- * Cache verdict of the run's first API call, classified from the
122
- * provider-reported usage by `backendCacheVerdict`. Null before any
123
- * settled run. "Shell reused but backend cold" is the dishonest
124
- * combination the overlay renders as a warning.
142
+ * Cache verdict of the run's first API call, classified by
143
+ * `backendCacheVerdict` from normalized provider usage and captured serving
144
+ * backend timings when available. Null before any settled run. "Shell reused
145
+ * but backend cold" is the dishonest combination the overlay warns about.
125
146
  */
126
- backendVerdict: "hot" | "partial" | "cold" | "small" | null;
147
+ backendVerdict: BackendCacheVerdict | null;
127
148
  /** Cache disturbances Clio expected before the last settled run. */
128
149
  expectedColdReasons?: ReadonlyArray<string>;
129
150
  }
@@ -185,6 +206,8 @@ export interface ContextLedger {
185
206
  trigger: string;
186
207
  } | null;
187
208
  promptCache: PromptCacheStats | null;
209
+ /** Last pre-warm since the previous settled run; null when none has run. */
210
+ prewarm: PrewarmStats | null;
188
211
  }
189
212
 
190
213
  /** Maps a prompt segment id to the ledger bucket it belongs to. */
@@ -332,14 +355,6 @@ export function buildContextLedger(input: BuildContextLedgerInput): ContextLedge
332
355
  }
333
356
 
334
357
  const meter: ContextLedgerGroup[] = [...groups];
335
- if (reserveTokens > 0) {
336
- meter.push({
337
- category: "reserve",
338
- label: CONTEXT_CATEGORY_LABEL.reserve,
339
- tokens: reserveTokens,
340
- percent: percentOf(reserveTokens, window),
341
- });
342
- }
343
358
  if (window > 0) {
344
359
  meter.push({
345
360
  category: "free",
@@ -348,6 +363,14 @@ export function buildContextLedger(input: BuildContextLedgerInput): ContextLedge
348
363
  percent: percentOf(freeTokens, window),
349
364
  });
350
365
  }
366
+ if (reserveTokens > 0) {
367
+ meter.push({
368
+ category: "reserve",
369
+ label: CONTEXT_CATEGORY_LABEL.reserve,
370
+ tokens: reserveTokens,
371
+ percent: percentOf(reserveTokens, window),
372
+ });
373
+ }
351
374
 
352
375
  return {
353
376
  provider: input.provider ?? null,
@@ -369,5 +392,6 @@ export function buildContextLedger(input: BuildContextLedgerInput): ContextLedge
369
392
  meter,
370
393
  lastCompaction: input.lastCompaction ?? null,
371
394
  promptCache: input.promptCache ?? null,
395
+ prewarm: input.prewarm ?? null,
372
396
  };
373
397
  }
@@ -61,6 +61,12 @@ export type {
61
61
  export { isSessionEntry, isSessionHeader, SESSION_ENTRY_KINDS } from "./entries.js";
62
62
  export { SessionManifest } from "./manifest.js";
63
63
  export { CURRENT_SESSION_FORMAT_VERSION, runMigrations } from "./migrations/index.js";
64
+ export type { ExpectedColdReasonCount, PromptCacheTelemetry } from "./prompt-cache.js";
65
+ export {
66
+ foldPromptCacheTelemetry,
67
+ hasPromptCacheTelemetry,
68
+ topExpectedColdReason,
69
+ } from "./prompt-cache.js";
64
70
  export {
65
71
  protectedArtifactEntryFromArtifact,
66
72
  protectedArtifactFromSessionEntry,
@@ -0,0 +1,140 @@
1
+ import {
2
+ type BackendCacheVerdict,
3
+ type BackendCompletionTimings,
4
+ type CacheVerdictCounts,
5
+ emptyCacheVerdictCounts,
6
+ uncachedPrefillTokens,
7
+ } from "../../core/cache-telemetry.js";
8
+ import type { SessionEntry } from "./entries.js";
9
+
10
+ /** Durable prompt-cache facts folded across assistant calls in one ledger slice. */
11
+ export interface PromptCacheTelemetry {
12
+ /** Sum of authoritative backend prefill work, or null when no call reported cache reads. */
13
+ uncachedPrefillTokens: number | null;
14
+ /** Calls contributing to `uncachedPrefillTokens`; distinguishes no evidence from a measured zero. */
15
+ uncachedPrefillCalls: number;
16
+ /** Counts of the verdict persisted on each assistant call. */
17
+ verdictCounts: CacheVerdictCounts;
18
+ /** Number of assistant calls carrying a recognized persisted verdict. */
19
+ verdictCalls: number;
20
+ /** How often each expected cache disturbance was stamped. */
21
+ expectedColdReasonCounts: Readonly<Record<string, number>>;
22
+ }
23
+
24
+ export interface ExpectedColdReasonCount {
25
+ reason: string;
26
+ count: number;
27
+ }
28
+
29
+ function isRecord(value: unknown): value is Record<string, unknown> {
30
+ return value !== null && typeof value === "object" && !Array.isArray(value);
31
+ }
32
+
33
+ function finiteNonNegative(value: unknown): value is number {
34
+ return typeof value === "number" && Number.isFinite(value) && value >= 0;
35
+ }
36
+
37
+ function backendTimings(value: unknown): BackendCompletionTimings | null {
38
+ if (!isRecord(value)) return null;
39
+ if (
40
+ !finiteNonNegative(value.promptTokens) ||
41
+ !finiteNonNegative(value.predictedTokens) ||
42
+ !finiteNonNegative(value.promptMs) ||
43
+ !finiteNonNegative(value.predictedMs) ||
44
+ (value.source !== "llamacpp-timings" && value.source !== "lmstudio-timings")
45
+ ) {
46
+ return null;
47
+ }
48
+ const cachedTokens = value.cachedTokens;
49
+ if (cachedTokens !== null && !finiteNonNegative(cachedTokens)) return null;
50
+ if (typeof cachedTokens === "number" && cachedTokens > value.promptTokens) return null;
51
+ return {
52
+ promptTokens: value.promptTokens,
53
+ cachedTokens,
54
+ predictedTokens: value.predictedTokens,
55
+ promptMs: value.promptMs,
56
+ predictedMs: value.predictedMs,
57
+ source: value.source,
58
+ };
59
+ }
60
+
61
+ function cacheVerdict(value: unknown): BackendCacheVerdict | null {
62
+ switch (value) {
63
+ case "hot":
64
+ case "partial":
65
+ case "cold":
66
+ case "small":
67
+ return value;
68
+ default:
69
+ return null;
70
+ }
71
+ }
72
+
73
+ /** True when the fold contains at least one durable cache fact. */
74
+ export function hasPromptCacheTelemetry(telemetry: PromptCacheTelemetry): boolean {
75
+ return (
76
+ telemetry.verdictCalls > 0 ||
77
+ telemetry.uncachedPrefillCalls > 0 ||
78
+ Object.keys(telemetry.expectedColdReasonCounts).length > 0
79
+ );
80
+ }
81
+
82
+ /** Most frequent expected disturbance, with deterministic lexical tie-breaking. */
83
+ export function topExpectedColdReason(telemetry: PromptCacheTelemetry): ExpectedColdReasonCount | null {
84
+ const ranked = Object.entries(telemetry.expectedColdReasonCounts).sort(
85
+ ([leftReason, leftCount], [rightReason, rightCount]) =>
86
+ rightCount - leftCount || leftReason.localeCompare(rightReason),
87
+ );
88
+ const first = ranked[0];
89
+ return first === undefined ? null : { reason: first[0], count: first[1] };
90
+ }
91
+
92
+ /**
93
+ * Fold persisted assistant `promptCache` payloads without reclassifying them.
94
+ * Callers provide the ledger slice they mean, such as one active branch or one
95
+ * report window, so every surface shares parsing while retaining its own scope.
96
+ */
97
+ export function foldPromptCacheTelemetry(entries: ReadonlyArray<SessionEntry>): PromptCacheTelemetry {
98
+ const verdictCounts = emptyCacheVerdictCounts();
99
+ const expectedColdReasonCounts: Record<string, number> = {};
100
+ let verdictCalls = 0;
101
+ let uncachedTotal = 0;
102
+ let uncachedPrefillCalls = 0;
103
+
104
+ for (const entry of entries) {
105
+ if (entry.kind !== "message" || entry.role !== "assistant" || !isRecord(entry.payload)) continue;
106
+ const promptCache = entry.payload.promptCache;
107
+ if (!isRecord(promptCache)) continue;
108
+
109
+ const verdict = cacheVerdict(promptCache.backendVerdict);
110
+ if (verdict !== null) {
111
+ verdictCounts[verdict] += 1;
112
+ verdictCalls += 1;
113
+ }
114
+
115
+ const backend = backendTimings(promptCache.backend);
116
+ const uncached = uncachedPrefillTokens(backend);
117
+ if (uncached !== null) {
118
+ uncachedTotal += uncached;
119
+ uncachedPrefillCalls += 1;
120
+ }
121
+
122
+ if (!Array.isArray(promptCache.expectedColdReasons)) continue;
123
+ const reasons = new Set(
124
+ promptCache.expectedColdReasons.filter(
125
+ (reason): reason is string => typeof reason === "string" && reason.trim().length > 0,
126
+ ),
127
+ );
128
+ for (const reason of reasons) {
129
+ expectedColdReasonCounts[reason] = (expectedColdReasonCounts[reason] ?? 0) + 1;
130
+ }
131
+ }
132
+
133
+ return {
134
+ uncachedPrefillTokens: uncachedPrefillCalls > 0 ? uncachedTotal : null,
135
+ uncachedPrefillCalls,
136
+ verdictCounts,
137
+ verdictCalls,
138
+ expectedColdReasonCounts,
139
+ };
140
+ }
@@ -14,6 +14,21 @@ import type { SessionMeta } from "./contract.js";
14
14
  * stay diffable without recompiling anything.
15
15
  */
16
16
 
17
+ /**
18
+ * Layout version of the compiled prompt these records describe.
19
+ *
20
+ * 1: the order shipped through 0.3.8.
21
+ * 2: stable-prefix-first ordering with one `# Memory` header, and
22
+ * `contextWindowSource` recorded alongside the window the prompt states
23
+ * (issue #249).
24
+ *
25
+ * A record without a `version` field predates the field and is read as 1, so a
26
+ * 0.3.8 manifest still parses. Bump this whenever the compiled text moves for a
27
+ * reason other than its inputs: a resumed session then has the version in hand
28
+ * to explain the one `promptRecompiled` entry its first compile writes.
29
+ */
30
+ export const PROMPT_MANIFEST_VERSION = 2;
31
+
17
32
  export interface PromptManifestSection {
18
33
  id: string;
19
34
  tokenEstimate: number;
@@ -27,6 +42,8 @@ export interface PromptManifestFragment {
27
42
  }
28
43
 
29
44
  export interface SessionPromptCompileRecord {
45
+ /** Prompt layout version; see `PROMPT_MANIFEST_VERSION`. Absent on pre-0.3.9 records. */
46
+ version?: number;
30
47
  /** ISO timestamp of the compile. */
31
48
  at: string;
32
49
  /** Hash of the previously served prompt, null on the first compile. */
@@ -35,6 +52,14 @@ export interface SessionPromptCompileRecord {
35
52
  tokenEstimate: number;
36
53
  /** Thinking dial applied to the live agent when this prompt was compiled. */
37
54
  thinkingLevel: string | null;
55
+ /**
56
+ * The window the prompt's `Context window: N` states, and the layer that
57
+ * answered it. `loaded` is what the backend has this model open at; anything
58
+ * else means the figure can still move once a loaded window is observed, and
59
+ * a later recompile is then explained rather than mysterious.
60
+ */
61
+ contextWindow?: number | null;
62
+ contextWindowSource?: string | null;
38
63
  projectPreload: ProjectPreloadClass | null;
39
64
  sections: PromptManifestSection[];
40
65
  fragments: PromptManifestFragment[];
@@ -102,9 +127,21 @@ function isProjectPreloadClass(value: unknown): value is ProjectPreloadClass {
102
127
  );
103
128
  }
104
129
 
130
+ /** Optional fields added after 0.3.8: absent is valid, present must be well-typed. */
131
+ function isOptional<T>(value: unknown, guard: (candidate: unknown) => candidate is T): boolean {
132
+ return value === undefined || guard(value);
133
+ }
134
+
105
135
  function isSessionPromptCompileRecord(value: unknown): value is SessionPromptCompileRecord {
106
136
  if (!isRecord(value)) return false;
107
137
  return (
138
+ isOptional(value.version, isNonNegativeInteger) &&
139
+ isOptional(value.contextWindow, (candidate): candidate is number | null =>
140
+ candidate === null ? true : isNonNegativeInteger(candidate),
141
+ ) &&
142
+ isOptional(value.contextWindowSource, (candidate): candidate is string | null =>
143
+ candidate === null ? true : typeof candidate === "string",
144
+ ) &&
108
145
  isCanonicalUtcTimestamp(value.at) &&
109
146
  (value.previousHash === null || isSha256(value.previousHash)) &&
110
147
  isSha256(value.systemPromptHash) &&
@@ -118,6 +155,11 @@ function isSessionPromptCompileRecord(value: unknown): value is SessionPromptCom
118
155
  );
119
156
  }
120
157
 
158
+ /** A record written before the field existed describes the 0.3.8 layout. */
159
+ export function promptManifestVersion(record: SessionPromptCompileRecord): number {
160
+ return record.version ?? 1;
161
+ }
162
+
121
163
  export function getPromptManifestFilePath(meta: SessionMeta, stateDir?: string): string {
122
164
  const safeMeta = {
123
165
  ...meta,
@@ -1,8 +1,10 @@
1
+ import { performance } from "node:perf_hooks";
1
2
  import {
2
3
  DEFAULT_DELEGATION_CONNECT_TIMEOUT_MS,
3
4
  DEFAULT_DELEGATION_TURN_TIMEOUT_MS,
4
5
  type DelegationAgentConfig,
5
6
  } from "../../core/defaults.js";
7
+ import type { HeartbeatStamp } from "../../domains/dispatch/heartbeat.js";
6
8
  import type { AutonomyLevel } from "../../domains/safety/autonomy.js";
7
9
  import type { SafetyContract } from "../../domains/safety/contract.js";
8
10
  import type { AgentEvent } from "../types.js";
@@ -40,6 +42,10 @@ export interface AcpDelegationRunInput {
40
42
  terminationWaitMs?: number;
41
43
  /** Grace for session/cancel before abort escalates into hard termination. */
42
44
  cancelGraceMs?: number;
45
+ /** Wall clock for the persisted heartbeat anchor. */
46
+ now?: () => number;
47
+ /** Monotonic clock for event-inactivity spans. */
48
+ monotonicNow?: () => number;
43
49
  }
44
50
 
45
51
  export interface AcpDelegationRunHandle {
@@ -54,7 +60,7 @@ export interface AcpDelegationRunHandle {
54
60
  * final observation bound expires.
55
61
  */
56
62
  kill(): void;
57
- heartbeatAt: { current: number };
63
+ heartbeatAt: HeartbeatStamp;
58
64
  toolCallLog(): ReturnType<AcpToolMediator["snapshot"]>["toolCallLog"];
59
65
  }
60
66
 
@@ -176,7 +182,14 @@ export function startAcpDelegationRun(input: AcpDelegationRunInput): AcpDelegati
176
182
  // peer into an unbounded request.
177
183
  const connectTimeoutMs = input.agent.connectTimeoutMs ?? DEFAULT_DELEGATION_CONNECT_TIMEOUT_MS;
178
184
  const turnTimeoutMs = input.agent.turnTimeoutMs ?? DEFAULT_DELEGATION_TURN_TIMEOUT_MS;
179
- const heartbeatAt = { current: Date.now() };
185
+ const now = input.now ?? Date.now;
186
+ const monotonicNow = input.monotonicNow ?? (() => performance.now());
187
+ const heartbeatWallAnchor = now();
188
+ const heartbeatMonotonicAnchor = monotonicNow();
189
+ const heartbeatAt: HeartbeatStamp = {
190
+ current: heartbeatWallAnchor,
191
+ monotonic: heartbeatMonotonicAnchor,
192
+ };
180
193
  const queue = new AsyncEventQueue<AcpRunEvent>();
181
194
  const usage = emptyUsage();
182
195
  const mapper = new AcpEventMapper();
@@ -202,7 +215,9 @@ export function startAcpDelegationRun(input: AcpDelegationRunInput): AcpDelegati
202
215
  cancelTimer = null;
203
216
  };
204
217
  const emit = (event: AcpRunEvent): void => {
205
- heartbeatAt.current = Date.now();
218
+ const stamp = monotonicNow();
219
+ heartbeatAt.monotonic = stamp;
220
+ heartbeatAt.current = heartbeatWallAnchor + (stamp - heartbeatMonotonicAnchor);
206
221
  queue.push(event);
207
222
  };
208
223
  const mediator = new AcpToolMediator({
@@ -1233,7 +1233,10 @@ function safeProbeReason(
1233
1233
  } catch {
1234
1234
  return "not-configured";
1235
1235
  }
1236
- return status.available && status.health.status === "healthy" ? null : "unreachable";
1236
+ // Degraded is reachable with a default the server does not serve; a client
1237
+ // that names its own model can still use the target.
1238
+ const reachable = status.health.status === "healthy" || status.health.status === "degraded";
1239
+ return status.available && reachable ? null : "unreachable";
1237
1240
  }
1238
1241
 
1239
1242
  interface AcpPermissionBridge {
package/src/engine/ai.ts CHANGED
@@ -27,9 +27,11 @@ import {
27
27
  type ToolCall,
28
28
  type Usage,
29
29
  } from "@earendil-works/pi-ai";
30
+ import type { BackendCompletionTimings } from "../core/cache-telemetry.js";
30
31
  import {
31
32
  completeEngineSimple,
32
33
  engineStream,
34
+ engineStreamSimple,
33
35
  registerEngineBuiltins,
34
36
  registerEngineFauxProvider,
35
37
  } from "./api-registry.js";
@@ -40,6 +42,14 @@ export { StringEnum };
40
42
 
41
43
  export const stream = engineStream;
42
44
 
45
+ /**
46
+ * The dispatcher a turn runs on. `createEngineAgent` hands this to pi-agent-core
47
+ * as its `streamFn`, so a caller that needs a request byte-identical to the one
48
+ * the next turn would send (the session pre-warm) sends it through here rather
49
+ * than through `stream`, whose bare-call path has to guess a thinking level.
50
+ */
51
+ export const streamSimple = engineStreamSimple;
52
+
43
53
  export interface EngineTextCompletionInput {
44
54
  model: EngineModel;
45
55
  systemPrompt: string;
@@ -55,6 +65,21 @@ export interface EngineTextCompletionResult {
55
65
  text: string;
56
66
  inputTokens: number;
57
67
  outputTokens: number;
68
+ /**
69
+ * The provider's full usage record for the call, so a caller that has to
70
+ * account for the spend does not have to re-derive it from two token counts.
71
+ */
72
+ usage: {
73
+ input: number;
74
+ output: number;
75
+ cacheRead: number;
76
+ cacheWrite: number;
77
+ reasoning: number;
78
+ totalTokens: number;
79
+ costUsd: number;
80
+ };
81
+ /** Backend prefill and prediction facts when the serving runtime reported them. */
82
+ backend: BackendCompletionTimings | null;
58
83
  }
59
84
 
60
85
  /** Run one tool-free completion while keeping pi message types at the engine boundary. */
@@ -83,6 +108,16 @@ export async function completeEngineText(input: EngineTextCompletionInput): Prom
83
108
  .join(""),
84
109
  inputTokens: response.usage.input,
85
110
  outputTokens: response.usage.output,
111
+ usage: {
112
+ input: response.usage.input,
113
+ output: response.usage.output,
114
+ cacheRead: response.usage.cacheRead,
115
+ cacheWrite: response.usage.cacheWrite,
116
+ reasoning: response.usage.reasoning ?? 0,
117
+ totalTokens: response.usage.totalTokens,
118
+ costUsd: response.usage.cost.total,
119
+ },
120
+ backend: response.backendTimings ?? null,
86
121
  };
87
122
  }
88
123
 
@@ -1,4 +1,5 @@
1
1
  import { performance } from "node:perf_hooks";
2
+ import { residencyTargetKey } from "../../core/residency-target-key.js";
2
3
  import { sleep } from "../../core/timers.js";
3
4
  import {
4
5
  type ResidencyAdapter,
@@ -169,6 +170,26 @@ async function fetchRouterModels(
169
170
  return parseRouterModels(await response.json());
170
171
  }
171
172
 
173
+ class RouterModelPostError extends Error {
174
+ constructor(
175
+ readonly url: string,
176
+ readonly status: number,
177
+ readonly responseBody: string,
178
+ ) {
179
+ super(`llama.cpp router rejected ${url}: HTTP ${status}; response: ${responseBody}`);
180
+ this.name = "RouterModelPostError";
181
+ }
182
+ }
183
+
184
+ async function routerErrorBody(response: Response): Promise<string> {
185
+ try {
186
+ const body = (await response.text()).trim();
187
+ return body.length > 0 ? body : "<empty body>";
188
+ } catch (error) {
189
+ return `<unreadable body: ${error instanceof Error ? error.message : String(error)}>`;
190
+ }
191
+ }
192
+
172
193
  async function postRouterModel(fetchImpl: typeof fetch, url: string, modelId: string): Promise<void> {
173
194
  const response = await fetchImpl(url, {
174
195
  method: "POST",
@@ -177,7 +198,38 @@ async function postRouterModel(fetchImpl: typeof fetch, url: string, modelId: st
177
198
  signal: AbortSignal.timeout(LOAD_TIMEOUT_MS),
178
199
  });
179
200
  if (response.ok) return;
180
- throw new Error(`llama.cpp router rejected ${url}: HTTP ${response.status}`);
201
+ throw new RouterModelPostError(url, response.status, await routerErrorBody(response));
202
+ }
203
+
204
+ function alreadyRunningResponse(body: string): boolean {
205
+ return body.toLowerCase().includes("already running");
206
+ }
207
+
208
+ /**
209
+ * A router wake and Clio's explicit load can cross between the listing and the
210
+ * POST. In that race the router rejects the duplicate request even though the
211
+ * requested model now owns a resident slot. Re-read the authoritative listing
212
+ * before treating a non-2xx as a load failure; only a definitively absent,
213
+ * unloaded, or failed model preserves the rejection.
214
+ */
215
+ async function postRouterLoad(input: LlamaCppResidencyInput, fetchImpl: typeof fetch, modelId: string): Promise<void> {
216
+ try {
217
+ await postRouterModel(fetchImpl, loadUrl(input.baseUrl), modelId);
218
+ } catch (error) {
219
+ if (!(error instanceof RouterModelPostError)) throw error;
220
+ let model: LlamaCppRouterModel | undefined;
221
+ try {
222
+ model = (await fetchRouterModels(input, fetchImpl)).find((entry) => entry.id === modelId);
223
+ } catch {
224
+ // The router's explicit "already running" answer is sufficient even if
225
+ // the confirming snapshot itself races or is temporarily unavailable.
226
+ if (alreadyRunningResponse(error.responseBody)) return;
227
+ throw error;
228
+ }
229
+ if (alreadyRunningResponse(error.responseBody)) return;
230
+ if (model !== undefined && model.state !== "unloaded" && model.state !== "failed") return;
231
+ throw error;
232
+ }
181
233
  }
182
234
 
183
235
  async function waitForLoaded(input: LlamaCppResidencyInput, fetchImpl: typeof fetch, modelId: string): Promise<void> {
@@ -206,7 +258,7 @@ async function ensureModelLoaded(
206
258
  // router answers `400 model is already running`, which fails the turn before
207
259
  // it reaches the model.
208
260
  if (knownState === "loaded" || knownState === "sleeping") return;
209
- if (knownState !== "loading") await postRouterModel(fetchImpl, loadUrl(input.baseUrl), modelId);
261
+ if (knownState !== "loading") await postRouterLoad(input, fetchImpl, modelId);
210
262
  await waitForLoaded(input, fetchImpl, modelId);
211
263
  }
212
264
 
@@ -246,7 +298,7 @@ export async function ensureLlamaCppResidency(input: LlamaCppResidencyInput): Pr
246
298
  const fetchImpl = input.fetchImpl ?? fetch;
247
299
  let snapshot: LlamaCppRouterModel[] = [];
248
300
  const adapter: ResidencyAdapter = {
249
- targetKey: `llamacpp|${input.baseUrl}`,
301
+ targetKey: residencyTargetKey("llamacpp", input.baseUrl),
250
302
  targetId: input.targetId,
251
303
  runtimeId: input.runtimeId,
252
304
  keepModelId: input.keepModelId,
@@ -1,5 +1,6 @@
1
1
  import type { Api, Model } from "@earendil-works/pi-ai";
2
2
 
3
+ import { residencyTargetKey } from "../../core/residency-target-key.js";
3
4
  import {
4
5
  type LmStudioCatalog,
5
6
  type LmStudioLoadedInstance,
@@ -11,7 +12,7 @@ import {
11
12
  } from "../../domains/providers/runtimes/common/lmstudio-http.js";
12
13
  import type { TargetDescriptor } from "../../domains/providers/types/target-descriptor.js";
13
14
  import { coResidentContextCeiling, fitLoadContextLength } from "./lmstudio-residency.js";
14
- import { emitResidencyNotice, reconcileResidency, residencyManagedFor } from "./residency.js";
15
+ import { emitResidencyMutation, emitResidencyNotice, reconcileResidency, residencyManagedFor } from "./residency.js";
15
16
  import { withResidencyLock } from "./residency-lock.js";
16
17
 
17
18
  interface LmStudioModelMetadata {
@@ -205,7 +206,7 @@ export async function ensureLmStudioResidency(
205
206
  entry.loadedInstances.map((instance) => ({ modelKey: entry.key, identifier: instance.id, instance })),
206
207
  );
207
208
  const managed = residencyManagedFor(info.lifecycle);
208
- const targetKey = `lmstudio|${lmStudioRootUrl(target.url ?? model.baseUrl)}`;
209
+ const targetKey = residencyTargetKey("lmstudio", target.url ?? model.baseUrl);
209
210
  const contextLength = target.lmstudio?.load?.contextLength;
210
211
  const plan = await reconcileResidency({
211
212
  targetKey,
@@ -248,17 +249,36 @@ export async function ensureLmStudioResidency(
248
249
  });
249
250
  }
250
251
  }
252
+ const loadAndReport = async (): Promise<string> => {
253
+ const instanceId = await loadOwnedInstance(target, targetKey, body, options.apiKey, options.signal);
254
+ emitResidencyMutation({
255
+ targetKey,
256
+ targetId: info.targetId,
257
+ runtimeId: "lmstudio",
258
+ model: modelKey,
259
+ operation: "load",
260
+ });
261
+ return instanceId ?? model.id;
262
+ };
251
263
  try {
252
- return (await loadOwnedInstance(target, targetKey, body, options.apiKey, options.signal)) ?? model.id;
264
+ return await loadAndReport();
253
265
  } catch (error) {
254
266
  if (plan.decision === "observe" || plan.fallbackEvict.length === 0) throw error;
255
267
  return withResidencyLock(targetKey, async () => {
256
268
  for (const candidate of plan.fallbackEvict) {
257
269
  for (const entry of instances.filter((resident) => resident.modelKey === candidate.modelId)) {
258
- await unloadOwnedInstance(target, targetKey, entry.identifier, options.apiKey, options.signal);
270
+ if (await unloadOwnedInstance(target, targetKey, entry.identifier, options.apiKey, options.signal)) {
271
+ emitResidencyMutation({
272
+ targetKey,
273
+ targetId: info.targetId,
274
+ runtimeId: "lmstudio",
275
+ model: candidate.modelId,
276
+ operation: "evict",
277
+ });
278
+ }
259
279
  }
260
280
  }
261
- return (await loadOwnedInstance(target, targetKey, body, options.apiKey, options.signal)) ?? model.id;
281
+ return loadAndReport();
262
282
  });
263
283
  }
264
284
  }
@@ -24,6 +24,7 @@ import {
24
24
  type Tool as OllamaTool,
25
25
  type ToolCall as OllamaToolCall,
26
26
  } from "ollama";
27
+ import { residencyTargetKey } from "../../core/residency-target-key.js";
27
28
  import {
28
29
  type AppliedThinking,
29
30
  resolveModelRuntimeCapabilitiesForModel,
@@ -75,7 +76,7 @@ async function reconcileOllamaResidency(model: Model<"ollama-native">, headers:
75
76
  if (!baseUrl) return;
76
77
  const metadata = (model as Model<"ollama-native"> & ClioRuntimeMetadata).clio;
77
78
  const adapter: ResidencyAdapter = {
78
- targetKey: `ollama-native|${baseUrl}`,
79
+ targetKey: residencyTargetKey("ollama-native", baseUrl),
79
80
  targetId: ollamaTargetId(model),
80
81
  runtimeId: "ollama-native",
81
82
  keepModelId: model.id,