@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
@@ -1,13 +1,18 @@
1
1
  import { accessSync, chmodSync, constants, type Dirent, existsSync, readdirSync, type Stats, statSync } from "node:fs";
2
- import { join } from "node:path";
2
+ import { basename, dirname, join } from "node:path";
3
3
  import { formatSettingsIssues, readSettings, validateSettingsFile } from "../../core/config.js";
4
4
  import { initializeClioHome } from "../../core/init.js";
5
5
  import { resolveClioDirs } from "../../core/xdg.js";
6
6
  import { readSessionFileEntries, type SessionJsonlWarning } from "../../engine/session.js";
7
7
  import { detectInteropAgents, interopAgentKind, resolveOnPath } from "../interop/index.js";
8
- import { openAuthStorage } from "../providers/auth/index.js";
8
+ import { openAuthStorage, resolveAuthTarget, targetRequiresAuth } from "../providers/auth/index.js";
9
+ import { credentialsPresent } from "../providers/credentials.js";
9
10
  import { fingerprintNativeRuntime } from "../providers/probe/fingerprint.js";
11
+ import type { ProbeContext, ProbeResult, RuntimeDescriptor } from "../providers/types/runtime-descriptor.js";
12
+ import type { TargetDescriptor } from "../providers/types/target-descriptor.js";
10
13
  import { loadSkills, type SkillSource } from "../resources/skills/loader.js";
14
+ import { isSessionEntry, type SessionEntry } from "../session/entries.js";
15
+ import { foldPromptCacheTelemetry, hasPromptCacheTelemetry, topExpectedColdReason } from "../session/prompt-cache.js";
11
16
  import { readStateInfoResult } from "./state.js";
12
17
  import { getVersionInfo } from "./version.js";
13
18
 
@@ -205,6 +210,68 @@ function sessionStoreFinding(stateDir: string, stateMetadataPresent: boolean): D
205
210
  return { ok: false, name: "session store", detail: parts.join("; ") };
206
211
  }
207
212
 
213
+ interface SessionCacheCandidate {
214
+ entries: SessionEntry[];
215
+ ledger: string;
216
+ sessionId: string;
217
+ updatedAt: number;
218
+ }
219
+
220
+ /**
221
+ * Report the latest transcript's durable cache evidence. Current ledgers are
222
+ * selected explicitly because the session directory also holds diagnostic
223
+ * JSONL files whose timestamps do not identify the latest conversation.
224
+ */
225
+ function latestSessionCacheFinding(stateDir: string): DoctorFinding | null {
226
+ const store = join(stateDir, "sessions");
227
+ const ledgers: string[] = [];
228
+ const unlistable: string[] = [];
229
+ collectSessionLedgers(store, ledgers, unlistable);
230
+ const candidates: SessionCacheCandidate[] = [];
231
+ for (const ledger of ledgers) {
232
+ if (basename(ledger) !== "current.jsonl") continue;
233
+ let parsed: unknown[];
234
+ let modifiedAt: number;
235
+ try {
236
+ parsed = readSessionFileEntries(ledger, { onWarning: () => {} });
237
+ modifiedAt = statSync(ledger).mtimeMs;
238
+ } catch {
239
+ continue;
240
+ }
241
+ const entries = parsed.filter(isSessionEntry);
242
+ const entryTimes = entries
243
+ .map((entry) => Date.parse(entry.timestamp))
244
+ .filter((timestamp) => Number.isFinite(timestamp));
245
+ candidates.push({
246
+ entries,
247
+ ledger,
248
+ sessionId: basename(dirname(ledger)),
249
+ updatedAt: entryTimes.length > 0 ? Math.max(...entryTimes) : modifiedAt,
250
+ });
251
+ }
252
+ const latest = candidates.sort(
253
+ (left, right) => right.updatedAt - left.updatedAt || left.ledger.localeCompare(right.ledger),
254
+ )[0];
255
+ if (latest === undefined) return null;
256
+
257
+ const telemetry = foldPromptCacheTelemetry(latest.entries);
258
+ if (!hasPromptCacheTelemetry(telemetry)) {
259
+ return {
260
+ ok: true,
261
+ level: "warn",
262
+ name: "cache telemetry",
263
+ detail: `last session ${latest.sessionId}: no prompt-cache telemetry recorded`,
264
+ };
265
+ }
266
+ const counts = telemetry.verdictCounts;
267
+ const topReason = topExpectedColdReason(telemetry);
268
+ return {
269
+ ok: true,
270
+ name: "cache telemetry",
271
+ detail: `last session ${latest.sessionId}: hot ${counts.hot} · partial ${counts.partial} · cold ${counts.cold} · small ${counts.small}; top expected reason ${topReason === null ? "none" : `${topReason.reason} (${topReason.count})`}`,
272
+ };
273
+ }
274
+
208
275
  /**
209
276
  * Why the credentials store did not fully parse, read through the same
210
277
  * `openAuthStorage` the auth commands use, or null when it is clean. The row
@@ -372,6 +439,10 @@ export function runDoctor(options: DoctorOptions = {}): DoctorFinding[] {
372
439
 
373
440
  const sessionStore = sessionStoreFinding(dirs.state, state !== null);
374
441
  if (sessionStore !== null) findings.push(sessionStore);
442
+ if (sessionStore?.ok) {
443
+ const cacheTelemetry = latestSessionCacheFinding(dirs.state);
444
+ if (cacheTelemetry !== null) findings.push(cacheTelemetry);
445
+ }
375
446
 
376
447
  return findings;
377
448
  }
@@ -534,3 +605,139 @@ export async function runDoctorRuntimeChecks(): Promise<DoctorFinding[]> {
534
605
  );
535
606
  return results.filter((finding): finding is DoctorFinding => finding !== null);
536
607
  }
608
+
609
+ /** One configured model pointer at a target, named the way settings.yaml spells it. */
610
+ interface ConfiguredModelRole {
611
+ role: string;
612
+ model: string;
613
+ }
614
+
615
+ function configuredModelRoles(
616
+ settings: ReturnType<typeof readSettings>,
617
+ target: TargetDescriptor,
618
+ ): ConfiguredModelRole[] {
619
+ const roles: ConfiguredModelRole[] = [];
620
+ if (target.defaultModel) roles.push({ role: "defaultModel", model: target.defaultModel });
621
+ if (settings.orchestrator.target === target.id && settings.orchestrator.model) {
622
+ roles.push({ role: "orchestrator.model", model: settings.orchestrator.model });
623
+ }
624
+ if (settings.background.target === target.id && settings.background.model) {
625
+ roles.push({ role: "background.model", model: settings.background.model });
626
+ }
627
+ if (settings.workers.default.target === target.id && settings.workers.default.model) {
628
+ roles.push({ role: "workers.default.model", model: settings.workers.default.model });
629
+ }
630
+ return roles;
631
+ }
632
+
633
+ /**
634
+ * Per-request budget for the model sweep. A target that does not answer in
635
+ * this time falls back to the list configure recorded, so a black-holed
636
+ * remote costs doctor a bounded wait rather than the probe's full timeout.
637
+ */
638
+ const DOCTOR_MODEL_PROBE_TIMEOUT_MS = 2_500;
639
+
640
+ async function doctorProbeContext(target: TargetDescriptor, runtime: RuntimeDescriptor): Promise<ProbeContext> {
641
+ const ctx: ProbeContext = { credentialsPresent: credentialsPresent(), httpTimeoutMs: DOCTOR_MODEL_PROBE_TIMEOUT_MS };
642
+ if (!targetRequiresAuth(target, runtime)) return ctx;
643
+ try {
644
+ const resolution = await openAuthStorage().resolveForTarget(resolveAuthTarget(target, runtime), {
645
+ includeFallback: false,
646
+ });
647
+ if (resolution.apiKey) ctx.authToken = resolution.apiKey;
648
+ } catch {
649
+ // The probe reports its own missing-auth failure; the wireModels fallback covers the check.
650
+ }
651
+ return ctx;
652
+ }
653
+
654
+ /** What the target advertises now, or null when it could not be asked. */
655
+ async function probeAdvertisedModels(
656
+ target: TargetDescriptor,
657
+ runtime: RuntimeDescriptor,
658
+ ): Promise<{ advertised: string[]; resident: string[] } | null> {
659
+ if (runtime.kind !== "http" || typeof runtime.probe !== "function" || !target.url) return null;
660
+ let probe: ProbeResult;
661
+ try {
662
+ probe = await runtime.probe(target, await doctorProbeContext(target, runtime));
663
+ } catch {
664
+ return null;
665
+ }
666
+ if (!probe.ok || !probe.models || probe.models.length === 0) return null;
667
+ const advertised = [...probe.models];
668
+ const resident: string[] = [];
669
+ for (const [id, status] of Object.entries(probe.modelStates ?? {})) {
670
+ if (!advertised.includes(id)) advertised.push(id);
671
+ if (status.state === "loaded" || status.state === "loading") resident.push(id);
672
+ }
673
+ return { advertised, resident };
674
+ }
675
+
676
+ /**
677
+ * Model sweep: every model pointer settings.yaml aims at a target with no
678
+ * static catalog is checked against what that target advertises. The live
679
+ * list wins when the target answers; the `wireModels` list configure recorded
680
+ * stands in when it does not, so an unreachable server still gets the
681
+ * placeholder id it was saved with called out. A target with neither list is
682
+ * a WARN, not a pass, because nothing was verified. Network-bound like the
683
+ * runtime sweep, so it is not part of the synchronous `runDoctor()` core.
684
+ */
685
+ export async function runDoctorModelChecks(): Promise<DoctorFinding[]> {
686
+ let settings: ReturnType<typeof readSettings>;
687
+ try {
688
+ settings = readSettings();
689
+ } catch {
690
+ return [];
691
+ }
692
+ if (settings.targets.length === 0) return [];
693
+ // Every runtime descriptor and the model catalog sit behind these two
694
+ // imports. They are loaded here, not at module load, so a doctor run on a
695
+ // home with no targets (the `--fix` seed every CLI test starts from) pays
696
+ // nothing for a sweep it has nothing to do.
697
+ const [{ getRuntimeRegistry }, { registerBuiltinRuntimes }, { listKnownModelsForRuntime }] = await Promise.all([
698
+ import("../providers/registry.js"),
699
+ import("../providers/runtimes/builtins.js"),
700
+ import("../providers/support.js"),
701
+ ]);
702
+ const registry = getRuntimeRegistry();
703
+ // doctor never loads the providers domain, so the registry is empty here
704
+ // unless another command in this process filled it.
705
+ if (registry.list().length === 0) registerBuiltinRuntimes(registry);
706
+ const results = await Promise.all(
707
+ settings.targets.map(async (target): Promise<DoctorFinding | null> => {
708
+ const runtime = registry.get(target.runtime);
709
+ if (!runtime) return null;
710
+ // Cloud runtimes are validated against their catalog at configure time.
711
+ if (listKnownModelsForRuntime(runtime.id).length > 0) return null;
712
+ const roles = configuredModelRoles(settings, target);
713
+ if (roles.length === 0) return null;
714
+ const live = await probeAdvertisedModels(target, runtime);
715
+ const recorded = target.wireModels ?? [];
716
+ if (live === null && recorded.length === 0) {
717
+ return {
718
+ ok: true,
719
+ level: "warn",
720
+ name: `model ${target.id}`,
721
+ detail: `${roles.map((entry) => `${entry.role} '${entry.model}'`).join(", ")} could not be verified: the target did not answer and configure recorded no model list; run \`clio-coder targets --probe\` once it is up`,
722
+ };
723
+ }
724
+ const advertised = live ? live.advertised : recorded;
725
+ const source = live ? `advertised by ${target.url ?? target.id} now` : "recorded by configure at last save";
726
+ const missing = roles.filter((entry) => !advertised.includes(entry.model));
727
+ if (missing.length === 0) {
728
+ return {
729
+ ok: true,
730
+ name: `model ${target.id}`,
731
+ detail: `${roles.map((entry) => `${entry.role} '${entry.model}'`).join(", ")} ${source}`,
732
+ };
733
+ }
734
+ const resident = live && live.resident.length > 0 ? live.resident.join(", ") : live ? "none" : "unknown";
735
+ return {
736
+ ok: false,
737
+ name: `model ${target.id}`,
738
+ detail: `${missing.map((entry) => `${entry.role} '${entry.model}'`).join(", ")} not ${source} (${advertised.length} ids). Resident instances: ${resident}. Re-run \`clio-coder configure --id ${target.id} --model <advertised id>\`; \`clio-coder targets --probe\` lists them`,
739
+ };
740
+ }),
741
+ );
742
+ return results.filter((finding): finding is DoctorFinding => finding !== null);
743
+ }
@@ -59,6 +59,11 @@ export {
59
59
  type TaskMemoryRenderableClass,
60
60
  type TaskMemorySnapshot,
61
61
  } from "./task-bank.js";
62
+ export {
63
+ type ProposeInjectedTaskMemoryInput,
64
+ type ProposeInjectedTaskMemoryResult,
65
+ proposeInjectedTaskMemory,
66
+ } from "./task-bank-promotion.js";
62
67
  export {
63
68
  type LegacyTaskMemoryHandoffSnapshot,
64
69
  parseTaskMemoryHandoffSnapshot,
@@ -95,8 +100,17 @@ export {
95
100
  type TaskMemoryPolicyInput,
96
101
  type TaskMemoryPolicyReason,
97
102
  type TaskMemoryPolicyResult,
103
+ type TaskMemoryStepUsage,
98
104
  type TaskMemoryTrajectoryStep,
99
105
  } from "./task-memory-policy.js";
106
+ export {
107
+ emptyTaskMemorySpendSummary,
108
+ foldTaskMemorySpend,
109
+ formatTaskMemorySpend,
110
+ readTaskMemorySpendSummary,
111
+ type TaskMemorySpendSummary,
112
+ taskMemoryStepsPath,
113
+ } from "./task-memory-spend.js";
100
114
  export {
101
115
  describeTaskMemoryActivity,
102
116
  type TaskMemoryActivityEvent,
@@ -0,0 +1,64 @@
1
+ /**
2
+ * The bridge between the session task bank and the durable memory store.
3
+ *
4
+ * Through 0.3.9 these were two stores where only one was ever written. The
5
+ * background plane filled the bank on every step, and nothing but an explicit
6
+ * `clio-coder memory propose` created a record, so `<dataDir>/memory/records.json`
7
+ * on the operator's machine did not exist at all: the tier's own output was
8
+ * discarded with the session that produced it, and the next session's first
9
+ * step read an empty bank and recorded `bank_empty` (#229).
10
+ *
11
+ * A reminder that actually reached the operator is the strongest signal the
12
+ * tier produces, so its cited entries are proposed here for review. They are
13
+ * written unapproved, under the repository the session is working in, and
14
+ * approval stays a separate operator action, exactly as it is for a promotion
15
+ * made by hand from `/memory`.
16
+ */
17
+
18
+ import { canonicalMemoryRepositoryIdentity } from "./operations.js";
19
+ import { proposeMemoryPromotion } from "./promotion.js";
20
+ import type { TaskMemoryEntry } from "./task-bank.js";
21
+ import type { MemoryRecord } from "./types.js";
22
+
23
+ export interface ProposeInjectedTaskMemoryInput {
24
+ /** Session the reminder was produced in. Without one there is no provenance to file under. */
25
+ sessionId: string | null;
26
+ /** Working directory of that session, used for the repository scope. */
27
+ cwd: string;
28
+ entries: ReadonlyArray<TaskMemoryEntry>;
29
+ now?: Date;
30
+ }
31
+
32
+ export interface ProposeInjectedTaskMemoryResult {
33
+ records: MemoryRecord[];
34
+ /** One message per entry the store refused, for the caller to report. */
35
+ errors: string[];
36
+ }
37
+
38
+ export async function proposeInjectedTaskMemory(
39
+ dataDir: string,
40
+ input: ProposeInjectedTaskMemoryInput,
41
+ ): Promise<ProposeInjectedTaskMemoryResult> {
42
+ const result: ProposeInjectedTaskMemoryResult = { records: [], errors: [] };
43
+ const sessionId = input.sessionId;
44
+ const repository = canonicalMemoryRepositoryIdentity(input.cwd);
45
+ // No session means no provenance. No canonical repository leaves only global
46
+ // scope, which broadens applicability to every future session and is a claim
47
+ // a background step may never make on the operator's behalf.
48
+ if (sessionId === null || sessionId.length === 0 || repository === null) return result;
49
+ for (const entry of input.entries) {
50
+ if (entry.kind === "status") continue;
51
+ try {
52
+ const proposal = await proposeMemoryPromotion(
53
+ dataDir,
54
+ { kind: "task-bank-entry", sessionId, evidenceRefs: [`session:${sessionId}`], entry },
55
+ { scope: "repo", repository },
56
+ input.now ?? new Date(),
57
+ );
58
+ result.records.push(proposal.record);
59
+ } catch (error) {
60
+ result.errors.push(`${entry.id}: ${error instanceof Error ? error.message : String(error)}`);
61
+ }
62
+ }
63
+ return result;
64
+ }
@@ -1,8 +1,10 @@
1
+ import type { BackendCompletionTimings } from "../../core/cache-telemetry.js";
1
2
  import type { ToolResultDigestProvenance } from "../../tools/result-disposition.js";
2
3
  import {
3
4
  buildMemoryInterventionUserPrompt,
4
5
  MEMORY_INTERVENTION_SYSTEM_PROMPT,
5
6
  } from "../prompts/memory-intervention.js";
7
+ import type { CostProvenance } from "../providers/index.js";
6
8
  import { TASK_MEMORY_CONTENT_MAX_CHARS, type TaskMemoryBank, type TaskMemoryRenderableClass } from "./task-bank.js";
7
9
 
8
10
  export const TASK_MEMORY_POLICY_MAX_OPERATIONS = 8;
@@ -12,13 +14,10 @@ export const TASK_MEMORY_POLICY_MAX_OPERATIONS = 8;
12
14
  * directly.
13
15
  *
14
16
  * This must equal the settings default in `src/core/defaults.ts`; a contract
15
- * test pins the pair. It read 20000 while the shipped default was 180000, and
16
- * 20000 is the value measured to have produced 256 timeouts against a route
17
- * whose completions have a 15s median and an 81s tail. Two numbers for one
18
- * setting means any path that misses the settings object silently gets the one
19
- * known to be wrong.
17
+ * test pins the pair. Two numbers for one setting means any path that misses
18
+ * the settings object silently gets the one nobody chose.
20
19
  */
21
- export const TASK_MEMORY_POLICY_DEFAULT_TIMEOUT_MS = 180_000;
20
+ export const TASK_MEMORY_POLICY_DEFAULT_TIMEOUT_MS = 30_000;
22
21
  /**
23
22
  * The envelope itself needs a few hundred tokens. The rest is headroom for a
24
23
  * model that reasons despite thinking being requested off: measured preambles on
@@ -45,10 +44,39 @@ export interface TaskMemoryModelRequest {
45
44
  signal: AbortSignal;
46
45
  }
47
46
 
47
+ /**
48
+ * What one memory step cost, as the client that made the call observed it.
49
+ *
50
+ * The policy never prices anything itself; it carries this from the client to
51
+ * the composition root, which is the only layer that knows about the cost
52
+ * ledger. Without it a memory step was billed by a provider and recorded
53
+ * nowhere an operator could read: 137,205 tokens over 14 days appeared in no
54
+ * `/cost` row and in no usage report (#229).
55
+ */
56
+ export interface TaskMemoryStepUsage {
57
+ /** Target the step ran against, used as the cost row's provider id. */
58
+ targetId: string;
59
+ attributedModelId: string;
60
+ input: number;
61
+ output: number;
62
+ cacheRead: number;
63
+ cacheWrite: number;
64
+ reasoning: number;
65
+ totalTokens: number;
66
+ costUsd: number;
67
+ costProvenance: CostProvenance;
68
+ /** Wall time of the model call itself, measured by the client. */
69
+ durationMs: number;
70
+ /** Backend prefill facts when the serving runtime reported them (#247). */
71
+ backend: BackendCompletionTimings | null;
72
+ }
73
+
48
74
  export interface TaskMemoryModelResponse {
49
75
  text: string;
50
76
  inputTokens?: number;
51
77
  outputTokens?: number;
78
+ /** Provider-reported spend for this call. Absent on clients that report none. */
79
+ usage?: TaskMemoryStepUsage;
52
80
  }
53
81
 
54
82
  export interface TaskMemoryModelClient {
@@ -87,6 +115,21 @@ export type TaskMemoryPolicyReason =
87
115
  | "all_operations_invalid"
88
116
  /** The request did not answer inside the policy timeout. */
89
117
  | "deadline"
118
+ /**
119
+ * The transport hit its own deadline first and threw. Separated from
120
+ * `client_error` because a route that answers too slowly and a route that
121
+ * refuses the connection call for opposite responses, and separated from
122
+ * `deadline` because it says the abort came from the request rather than
123
+ * from the policy's race.
124
+ */
125
+ | "timed_out"
126
+ /**
127
+ * The step would have run on the endpoint the chat target is streaming
128
+ * against, so it never started. A single-slot local server queues the memory
129
+ * call behind the operator's own turn, or evicts the resident model to serve
130
+ * it; neither is a cost a background step may impose.
131
+ */
132
+ | "endpoint_busy"
90
133
  /** The model client threw: unreachable route, auth failure, malformed request. */
91
134
  | "client_error"
92
135
  /** No background role is configured, so the llm tier never ran. */
@@ -146,6 +189,8 @@ export interface TaskMemoryPolicyResult {
146
189
  reminder: string | null;
147
190
  inputTokens: number;
148
191
  outputTokens: number;
192
+ /** Provider-reported spend, when the client reported any. Null on every path that made no call. */
193
+ usage: TaskMemoryStepUsage | null;
149
194
  }
150
195
 
151
196
  type TaskMemoryOperation =
@@ -208,6 +253,7 @@ export async function runTaskMemoryPolicy(
208
253
  let userPrompt = "";
209
254
  let rawResponse = "";
210
255
  let clientError: string | null = null;
256
+ let stepUsage: TaskMemoryStepUsage | null = null;
211
257
  // Every exit reports through here so the trace sees the same envelope the
212
258
  // telemetry row summarizes, including the paths that used to throw away both.
213
259
  const settle = (
@@ -223,6 +269,7 @@ export async function runTaskMemoryPolicy(
223
269
  reminder: parts.reminder ?? null,
224
270
  inputTokens: parts.inputTokens ?? 0,
225
271
  outputTokens: parts.outputTokens ?? 0,
272
+ usage: stepUsage,
226
273
  };
227
274
  try {
228
275
  input.onEnvelope?.({
@@ -263,9 +310,10 @@ export async function runTaskMemoryPolicy(
263
310
  return settle("timeout", "deadline");
264
311
  }
265
312
  rawResponse = typeof response.text === "string" ? response.text : "";
313
+ stepUsage = response.usage ?? null;
266
314
  const usage = {
267
- inputTokens: nonNegativeInteger(response.inputTokens),
268
- outputTokens: nonNegativeInteger(response.outputTokens),
315
+ inputTokens: nonNegativeInteger(response.inputTokens ?? stepUsage?.input),
316
+ outputTokens: nonNegativeInteger(response.outputTokens ?? stepUsage?.output),
269
317
  };
270
318
  const read = readPolicyStep(rawResponse);
271
319
  if (!read.ok) return settle("malformed", read.reason, { ...usage, droppedOperations: read.dropped });
@@ -293,6 +341,11 @@ export async function runTaskMemoryPolicy(
293
341
  return settle("injected", "intervened", { ...counts, reminder });
294
342
  } catch (error) {
295
343
  clientError = errorMessage(error);
344
+ // A transport that aborted at its own deadline spent the whole budget and
345
+ // produced nothing. Reporting it as silence made a step that held a local
346
+ // server for its full timeout indistinguishable from a model that read the
347
+ // trajectory and decided there was nothing to say (#229).
348
+ if (isTimeoutError(error)) return settle("timeout", "timed_out");
296
349
  return settle("silent", "client_error");
297
350
  } finally {
298
351
  if (timer !== undefined) clearTimeout(timer);
@@ -598,6 +651,22 @@ function nonNegativeInteger(value: number | undefined): number {
598
651
  return value !== undefined && Number.isInteger(value) && value >= 0 ? value : 0;
599
652
  }
600
653
 
654
+ /**
655
+ * Whether a client failure was the deadline rather than the route.
656
+ *
657
+ * The engine surfaces an aborted or timed-out completion as a plain `Error`
658
+ * carrying the provider's message, so the name and the text are all there is to
659
+ * read. Both spellings appear: pi-ai raises `AbortError` when the policy's own
660
+ * signal fires, and the completion wrapper raises `model completion aborted`
661
+ * when its `timeoutMs` fires first.
662
+ */
663
+ function isTimeoutError(error: unknown): boolean {
664
+ const name = error instanceof Error ? error.name : "";
665
+ if (name === "AbortError" || name === "TimeoutError") return true;
666
+ const message = error instanceof Error ? error.message : typeof error === "string" ? error : "";
667
+ return /\b(abort(ed)?|timed out|timeout)\b/iu.test(message);
668
+ }
669
+
601
670
  function errorMessage(error: unknown): string {
602
671
  if (error instanceof Error) return error.message.length > 0 ? error.message : error.name;
603
672
  return typeof error === "string" && error.length > 0 ? error : "an unknown client failure";
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Lifetime spend and hit rate of the proactive-memory llm tier, folded from the
3
+ * telemetry ledger the sink already writes.
4
+ *
5
+ * The ledger recorded every step's tokens and latency from the day the tier
6
+ * shipped, and nothing ever read it back: the operator's own file held 60
7
+ * llm-tier steps, 137,205 tokens, and 1,666 seconds of model time for 6
8
+ * injections, and no surface said so (#229). `/memory` reads this, so the
9
+ * question "is the background plane earning its cost" is answered by the
10
+ * measurement rather than by an impression.
11
+ */
12
+
13
+ import { readFileSync, statSync } from "node:fs";
14
+ import { join } from "node:path";
15
+ import { clioStateDir } from "../../core/xdg.js";
16
+ import { parseTaskMemoryTelemetryRecord, TASK_MEMORY_TELEMETRY_FILE } from "./task-memory-telemetry.js";
17
+
18
+ export interface TaskMemorySpendSummary {
19
+ /** Steps that reached the background model. Rules-tier steps cost nothing and are excluded. */
20
+ llmSteps: number;
21
+ /** Llm-tier steps whose reminder reached the visible channel. */
22
+ injections: number;
23
+ /** Injections over llm steps, 0 when no llm step has run. */
24
+ hitRate: number;
25
+ inputTokens: number;
26
+ outputTokens: number;
27
+ totalTokens: number;
28
+ /** Cumulative llm-tier step latency. */
29
+ modelMs: number;
30
+ /** Slowest single llm-tier step, which is what a deadline has to answer for. */
31
+ slowestStepMs: number;
32
+ /** Llm-tier steps that spent their whole budget and answered nothing. */
33
+ timeouts: number;
34
+ /** Boundaries skipped because the chat endpoint was serving a turn. */
35
+ endpointBusySkips: number;
36
+ /** Oldest and newest row in the ledger, so a rate is read against a window. */
37
+ firstAt: string | null;
38
+ lastAt: string | null;
39
+ }
40
+
41
+ export function taskMemoryStepsPath(stateDir: string = clioStateDir()): string {
42
+ return join(stateDir, "memory", TASK_MEMORY_TELEMETRY_FILE);
43
+ }
44
+
45
+ export function emptyTaskMemorySpendSummary(): TaskMemorySpendSummary {
46
+ return {
47
+ llmSteps: 0,
48
+ injections: 0,
49
+ hitRate: 0,
50
+ inputTokens: 0,
51
+ outputTokens: 0,
52
+ totalTokens: 0,
53
+ modelMs: 0,
54
+ slowestStepMs: 0,
55
+ timeouts: 0,
56
+ endpointBusySkips: 0,
57
+ firstAt: null,
58
+ lastAt: null,
59
+ };
60
+ }
61
+
62
+ /** Fold parsed telemetry rows. Exported for callers that already hold the rows. */
63
+ export function foldTaskMemorySpend(lines: ReadonlyArray<string>): TaskMemorySpendSummary {
64
+ const summary = emptyTaskMemorySpendSummary();
65
+ for (const line of lines) {
66
+ if (line.trim().length === 0) continue;
67
+ let parsed: unknown;
68
+ try {
69
+ parsed = JSON.parse(line) as unknown;
70
+ } catch {
71
+ // A truncated tail line is the normal shape of a file being appended to.
72
+ continue;
73
+ }
74
+ const record = parseTaskMemoryTelemetryRecord(parsed);
75
+ if (record === null) continue;
76
+ if (summary.firstAt === null || record.at < summary.firstAt) summary.firstAt = record.at;
77
+ if (summary.lastAt === null || record.at > summary.lastAt) summary.lastAt = record.at;
78
+ if (record.reason === "endpoint_busy") summary.endpointBusySkips += 1;
79
+ if (record.tier !== "llm") continue;
80
+ summary.llmSteps += 1;
81
+ if (record.decision === "injected") summary.injections += 1;
82
+ if (record.decision === "timeout") summary.timeouts += 1;
83
+ summary.inputTokens += record.tokenCost.input;
84
+ summary.outputTokens += record.tokenCost.output;
85
+ summary.totalTokens += record.tokenCost.total;
86
+ summary.modelMs += record.latencyMs;
87
+ summary.slowestStepMs = Math.max(summary.slowestStepMs, record.latencyMs);
88
+ }
89
+ summary.hitRate = summary.llmSteps === 0 ? 0 : summary.injections / summary.llmSteps;
90
+ return summary;
91
+ }
92
+
93
+ interface SpendCacheEntry {
94
+ mtimeMs: number;
95
+ size: number;
96
+ summary: TaskMemorySpendSummary;
97
+ }
98
+
99
+ const spendCache = new Map<string, SpendCacheEntry>();
100
+
101
+ /**
102
+ * Read the ledger and fold it, reusing the previous fold while the file has not
103
+ * moved. `/memory` repaints once a second and the ledger is capped at a
104
+ * megabyte, so an idle second costs one `stat` rather than a megabyte of JSON.
105
+ * A missing file is an empty summary: a machine where the llm tier never ran
106
+ * has nothing to report, which is different from a ledger that failed to open.
107
+ */
108
+ export function readTaskMemorySpendSummary(stateDir: string = clioStateDir()): TaskMemorySpendSummary {
109
+ const path = taskMemoryStepsPath(stateDir);
110
+ try {
111
+ const info = statSync(path);
112
+ const cached = spendCache.get(path);
113
+ if (cached !== undefined && cached.mtimeMs === info.mtimeMs && cached.size === info.size) return cached.summary;
114
+ const summary = foldTaskMemorySpend(readFileSync(path, "utf8").split("\n"));
115
+ spendCache.set(path, { mtimeMs: info.mtimeMs, size: info.size, summary });
116
+ return summary;
117
+ } catch {
118
+ return emptyTaskMemorySpendSummary();
119
+ }
120
+ }
121
+
122
+ /** Compact operator wording. Empty when the llm tier has never run. */
123
+ export function formatTaskMemorySpend(summary: TaskMemorySpendSummary): string {
124
+ if (summary.llmSteps === 0) return "";
125
+ const tokens =
126
+ summary.totalTokens >= 1_000 ? `${(summary.totalTokens / 1_000).toFixed(1)}k tok` : `${summary.totalTokens} tok`;
127
+ const seconds = summary.modelMs / 1_000;
128
+ const time = seconds >= 60 ? `${(seconds / 60).toFixed(1)}m` : `${seconds.toFixed(1)}s`;
129
+ const rate = `${Math.round(summary.hitRate * 100)}%`;
130
+ return `spend ${summary.llmSteps} steps · ${tokens} · ${time} · hit ${summary.injections}/${summary.llmSteps} (${rate})`;
131
+ }
@@ -1,5 +1,6 @@
1
1
  import type { TaskMemorySnapshot } from "./task-bank.js";
2
2
  import type { TaskMemoryPolicyDecision, TaskMemoryPolicyReason } from "./task-memory-policy.js";
3
+ import type { TaskMemorySpendSummary } from "./task-memory-spend.js";
3
4
  import type {
4
5
  TaskMemoryTelemetryDecision,
5
6
  TaskMemoryTelemetryTier,
@@ -35,6 +36,12 @@ export interface TaskMemoryOperatorStatus {
35
36
  activity: ReadonlyArray<TaskMemoryActivityEvent>;
36
37
  /** True while a detached background memory step is still running. */
37
38
  stepInFlight: boolean;
39
+ /**
40
+ * Lifetime llm-tier spend and hit rate folded from the telemetry ledger. Null
41
+ * on a surface that does not read the ledger, which is every surface that
42
+ * only needs the live bank.
43
+ */
44
+ spend?: TaskMemorySpendSummary | null;
38
45
  }
39
46
 
40
47
  /**
@@ -220,6 +220,8 @@ const REASONS = new Set<TaskMemoryPolicyReason>([
220
220
  "unparseable",
221
221
  "all_operations_invalid",
222
222
  "deadline",
223
+ "timed_out",
224
+ "endpoint_busy",
223
225
  "client_error",
224
226
  "no_client",
225
227
  "no_consumer",
@@ -81,6 +81,7 @@ export {
81
81
  MEMORY_INTERVENTION_REGISTRATION_ID,
82
82
  type MemoryInterventionDeps,
83
83
  } from "./memory-intervention.js";
84
+ export { announceMemoryStepEndpoint, type MemoryStepEndpointAnnouncerDeps } from "./memory-step-endpoint.js";
84
85
  export { BUILTIN_MIDDLEWARE_RULE_IDS, listMiddlewareRuleDefinitions, listMiddlewareRules } from "./rules.js";
85
86
  export type {
86
87
  MiddlewareDiagnostic,