@iowarp/clio-coder 0.3.8 → 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 (333) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +7 -3
  3. package/dist/{acp-U67UHUK2.js → acp-7LOELQFP.js} +6 -6
  4. package/dist/{agents-YU6SGALZ.js → agents-FIBG2SHA.js} +27 -25
  5. package/dist/assets/codewiki.json +1 -1
  6. package/dist/{auth-5ZPJOIVG.js → auth-OI4LIH2I.js} +11 -12
  7. package/dist/{builtins-C6JMZVV6.js → builtins-AD25UL3C.js} +5 -5
  8. package/dist/{chunk-5FR74PWO.js → chunk-2JDWVJND.js} +2 -2
  9. package/dist/chunk-3DPEIQKN.js +113 -0
  10. package/dist/{chunk-4SPRNWDE.js → chunk-3DUR4WUA.js} +15 -15
  11. package/dist/{chunk-VHN4MY6O.js → chunk-3MRC2YSQ.js} +2 -2
  12. package/dist/{chunk-5DHKRSMQ.js → chunk-3UUY7R3Z.js} +11 -7
  13. package/dist/{chunk-IGWKHNIQ.js → chunk-3V5AYSEQ.js} +8 -8
  14. package/dist/{chunk-FHJEP5SW.js → chunk-465CC7FK.js} +8 -5
  15. package/dist/{chunk-5WIGXA4T.js → chunk-47CMYGET.js} +111 -4
  16. package/dist/{chunk-A3WNZD3P.js → chunk-4H6ULJ3H.js} +67 -21
  17. package/dist/{chunk-TB5666IT.js → chunk-4LJX2PUC.js} +3 -3
  18. package/dist/{chunk-XWSF374K.js → chunk-56KB5IJP.js} +2 -2
  19. package/dist/{chunk-SPULKLCF.js → chunk-5DQRIYDZ.js} +2 -2
  20. package/dist/{chunk-2HEJ2F35.js → chunk-5HFBWUMU.js} +20 -8
  21. package/dist/{chunk-WNIJTQQK.js → chunk-5PVQ4SRS.js} +78 -6
  22. package/dist/{chunk-DYIM5TJT.js → chunk-5QKCQQ3E.js} +262 -6
  23. package/dist/{chunk-TYPGUK6W.js → chunk-5T7RBWN2.js} +111 -5
  24. package/dist/{chunk-IIZWH4XA.js → chunk-774ILSRL.js} +2 -2
  25. package/dist/chunk-7C6RYZGQ.js +391 -0
  26. package/dist/{chunk-TANS5ZJS.js → chunk-AD7Y7STJ.js} +3 -3
  27. package/dist/{chunk-RWSI4YD7.js → chunk-AEYBF3TB.js} +33 -12
  28. package/dist/{chunk-DGSYXYMX.js → chunk-AMKHQW3C.js} +2 -2
  29. package/dist/{chunk-VWZOAB7K.js → chunk-B5XRQOLB.js} +7 -7
  30. package/dist/{chunk-WXY7KU3G.js → chunk-BVDVID7E.js} +2 -2
  31. package/dist/{chunk-PMDBGQSJ.js → chunk-CA42X6KT.js} +2 -2
  32. package/dist/{chunk-HLE42MG7.js → chunk-D73KXYPF.js} +3 -3
  33. package/dist/{chunk-5Q2VVUKB.js → chunk-DG4M6ZUE.js} +3 -3
  34. package/dist/{chunk-ME6CCNFO.js → chunk-EBOC7MT3.js} +6 -6
  35. package/dist/{chunk-MXKJU4JB.js → chunk-ECUO3KDP.js} +48 -7
  36. package/dist/{chunk-26LEYJZH.js → chunk-FALJGAWU.js} +2 -2
  37. package/dist/{chunk-GWS3VEIW.js → chunk-FWDFM5ZU.js} +24 -3
  38. package/dist/{chunk-7RGZWPB6.js → chunk-GAYUJ7LE.js} +67 -13
  39. package/dist/{chunk-VCBR6CU7.js → chunk-HAY4ZE2P.js} +2 -2
  40. package/dist/{chunk-FBVTI2TJ.js → chunk-HCBCAYZU.js} +11 -130
  41. package/dist/{chunk-WSB3FPX7.js → chunk-HJB5IUKP.js} +32 -136
  42. package/dist/{chunk-J3YUBZWY.js → chunk-HKO36JWF.js} +33 -5
  43. package/dist/{chunk-E77JEWSD.js → chunk-HPCTNZM2.js} +6 -36
  44. package/dist/{chunk-FJ3H4MN5.js → chunk-IHKBWSXF.js} +2 -2
  45. package/dist/{chunk-NMPKI6XL.js → chunk-JEQQR47K.js} +37 -18
  46. package/dist/{chunk-3BINW3FP.js → chunk-KV2AOLDF.js} +24 -4
  47. package/dist/{chunk-YS5VLNH5.js → chunk-LXPJXFM5.js} +7 -7
  48. package/dist/{chunk-7RFXX52T.js → chunk-MIX5N5AC.js} +271 -41
  49. package/dist/{chunk-K4XHGFR5.js → chunk-MLOK6ZOS.js} +1297 -218
  50. package/dist/{chunk-ZNLWCMVZ.js → chunk-MV2VUEJC.js} +2 -2
  51. package/dist/{chunk-MVVUPGPW.js → chunk-MXHC5QYU.js} +6 -6
  52. package/dist/{chunk-5H3GB5BO.js → chunk-N3PBVRTZ.js} +4 -382
  53. package/dist/{chunk-2HFZQUHL.js → chunk-N5XKWMDW.js} +17 -7
  54. package/dist/{chunk-5C3AQNDW.js → chunk-NNNWO6F2.js} +124 -36
  55. package/dist/{chunk-GU2UIAFZ.js → chunk-NQ6UCCOD.js} +3 -3
  56. package/dist/{chunk-N22QMJKY.js → chunk-NZU6YDNV.js} +4 -4
  57. package/dist/{chunk-ZVJ5BLO2.js → chunk-O6I4CIEU.js} +151 -13
  58. package/dist/{chunk-XN3L4EYL.js → chunk-OEDBCISO.js} +2 -2
  59. package/dist/{chunk-VAWNZU7Z.js → chunk-P3JGPQFL.js} +2 -2
  60. package/dist/{chunk-IJ7RPIYJ.js → chunk-PNY46YEY.js} +20 -3
  61. package/dist/{chunk-U6MBIEMB.js → chunk-PZ4I4JE2.js} +56 -35
  62. package/dist/{chunk-GPIEI3LY.js → chunk-QQ7EKM72.js} +2 -2
  63. package/dist/{chunk-WLFILSD5.js → chunk-R7LNVMCS.js} +66 -28
  64. package/dist/{chunk-GOXNB3AO.js → chunk-RAPCMZL4.js} +75 -4
  65. package/dist/chunk-RKKLTLYB.js +45 -0
  66. package/dist/{chunk-TT36MB5S.js → chunk-RKRLDWD3.js} +3 -1
  67. package/dist/{chunk-TTHACPOM.js → chunk-S4COXYBG.js} +456 -18
  68. package/dist/{chunk-WWCZ5F23.js → chunk-T3Z6VAAF.js} +69 -10
  69. package/dist/{chunk-GN57SG4G.js → chunk-TD7UE2L5.js} +9 -7
  70. package/dist/{chunk-TLQJPP24.js → chunk-TEO2TLVN.js} +523 -322
  71. package/dist/{chunk-FCSXB6T2.js → chunk-UOSL25KY.js} +14 -2
  72. package/dist/{chunk-KTYTFRMB.js → chunk-VKBMFOYV.js} +17 -15
  73. package/dist/{chunk-PT7HYKEM.js → chunk-VO2LKSTM.js} +2 -2
  74. package/dist/{chunk-P43ETTHK.js → chunk-VPTUJU4P.js} +2 -2
  75. package/dist/{chunk-WEH5XRJQ.js → chunk-WIE7ZOSW.js} +2 -2
  76. package/dist/{chunk-EMYUUSFG.js → chunk-WXCJ7VME.js} +5 -5
  77. package/dist/{chunk-4DGYLA73.js → chunk-XDOQXGFO.js} +22 -7
  78. package/dist/{chunk-JOZYP4GM.js → chunk-YKOFT37S.js} +5 -5
  79. package/dist/chunk-YSEHGPCT.js +127 -0
  80. package/dist/{chunk-HFSBBKSQ.js → chunk-YW7UVM5V.js} +138 -3
  81. package/dist/cli/index.js +27 -27
  82. package/dist/{clio-QVTYJ57A.js → clio-LT5V7SSZ.js} +6 -6
  83. package/dist/{code-nav-FGGFIE7L.js → code-nav-LMW275PA.js} +4 -4
  84. package/dist/{config-LW5IJFQN.js → config-RXS5T3JT.js} +71 -43
  85. package/dist/{configure-7XIZCOU4.js → configure-2WYWSCSD.js} +14 -15
  86. package/dist/{context-Y6Y7QPR6.js → context-I3BTOTCS.js} +12 -12
  87. package/dist/{context-L3WL3X7K.js → context-MVOORGMF.js} +34 -33
  88. package/dist/{context-N52ZA626.js → context-PALKKQYL.js} +20 -20
  89. package/dist/{context-clear-MBQRLSDQ.js → context-clear-N2WOYZ2K.js} +34 -33
  90. package/dist/{context-index-HVMFQHK3.js → context-index-HNG3MOME.js} +2 -2
  91. package/dist/{context-working-set-GS6DSO7F.js → context-working-set-MIEVECVZ.js} +10 -11
  92. package/dist/{dispatch-runner-22ZCNOM3.js → dispatch-runner-VVA4SRRH.js} +34 -33
  93. package/dist/doctor-TWBWFK5V.js +165 -0
  94. package/dist/{eval-BEC2WHDA.js → eval-IJ5VEZDJ.js} +2016 -142
  95. package/dist/{evidence-REJUMSKM.js → evidence-L5APPXNV.js} +29 -28
  96. package/dist/{evolve-PY5ZBA5K.js → evolve-RGNKFJ52.js} +29 -28
  97. package/dist/{extensions-HVKU65YU.js → extensions-7WYWUX5A.js} +9 -3
  98. package/dist/{fleet-7WZEWRFA.js → fleet-6CNVBZZP.js} +87 -55
  99. package/dist/{fleet-commands-UVHWM76J.js → fleet-commands-L2SXSYEI.js} +6 -6
  100. package/dist/{fleet-graph-6ULH7PES.js → fleet-graph-2J3OOIPO.js} +14 -12
  101. package/dist/{fleet-preflight-J53T6CCE.js → fleet-preflight-CZRJ4JP5.js} +3 -4
  102. package/dist/{fleet-validate-72PC4SLA.js → fleet-validate-C5RI6DP7.js} +16 -15
  103. package/dist/{init-OG3TPGQG.js → init-VBN2ACVA.js} +50 -48
  104. package/dist/{library-CNTMPLRF.js → library-JHGUMLY2.js} +13 -11
  105. package/dist/{memory-6IS7F275.js → memory-K4OQIYWG.js} +31 -30
  106. package/dist/{models-ENRJDA5W.js → models-2NCZUWDD.js} +23 -22
  107. package/dist/{monitor-XLDVO7TN.js → monitor-MMVTJABD.js} +35 -34
  108. package/dist/{orchestrator-6KSPYRHA.js → orchestrator-ZKBPCHW6.js} +1627 -294
  109. package/dist/{reset-RZ4ER727.js → reset-DD5JGOY3.js} +3 -3
  110. package/dist/{run-Y2CNK5RU.js → run-QEGNX7FL.js} +56 -55
  111. package/dist/{share-A55GYP6Z.js → share-JKD3BQMW.js} +13 -11
  112. package/dist/{skills-ALC5J6AT.js → skills-LMQIKDOZ.js} +14 -12
  113. package/dist/{skills-eval-JPBEBYQU.js → skills-eval-I7X2774U.js} +34 -32
  114. package/dist/{steer-GGWFUJUD.js → steer-CF5TDANS.js} +3 -3
  115. package/dist/{support-MIETYA5E.js → support-I7LOJLIF.js} +4 -4
  116. package/dist/{targets-VGNXIR3S.js → targets-RUSR6B5Z.js} +58 -32
  117. package/dist/{terminal-lease-WOBR64YA.js → terminal-lease-QYVORFR4.js} +6 -4
  118. package/dist/{trace-PNCASAXC.js → trace-ODOQIVIW.js} +61 -6
  119. package/dist/{upgrade-FUSUAGHR.js → upgrade-XANW3FXB.js} +17 -16
  120. package/dist/{usage-N4MKVHKD.js → usage-4H7ZRXQT.js} +88 -44
  121. package/dist/{verifiers-YAWOJ3H2.js → verifiers-UZXNBZEB.js} +6 -6
  122. package/dist/{verify-LTDHYBGY.js → verify-BVKWTNDL.js} +5 -5
  123. package/dist/{wiki-generate-6M7GHTBJ.js → wiki-generate-MY7WV2QI.js} +49 -47
  124. package/dist/worker/entry.js +29 -33
  125. package/docs/alcf-provider.md +1 -1
  126. package/docs/architecture.md +1 -1
  127. package/docs/artifact-versions.md +6 -1
  128. package/docs/built-in-agents.md +1 -1
  129. package/docs/capacity-and-scheduling.md +23 -2
  130. package/docs/commands-and-modes.md +1 -1
  131. package/docs/configuration-and-targets.md +30 -3
  132. package/docs/context-engine.md +63 -4
  133. package/docs/documentation-coverage.md +3 -3
  134. package/docs/documentation-guide.md +1 -1
  135. package/docs/environment-variables.md +2 -0
  136. package/docs/eval-runner.md +262 -11
  137. package/docs/evals-internal.md +72 -2
  138. package/docs/evidence-and-memory.md +11 -10
  139. package/docs/evolution.md +1 -1
  140. package/docs/extensions-and-sharing.md +3 -1
  141. package/docs/fleet-dispatch.md +4 -4
  142. package/docs/installation-and-lifecycle.md +1 -1
  143. package/docs/middleware-and-components.md +1 -1
  144. package/docs/model-catalog.md +1 -1
  145. package/docs/observability.md +53 -2
  146. package/docs/proactive-memory.md +127 -14
  147. package/docs/prompt-envelope-and-tools.md +19 -1
  148. package/docs/provider-adapter-cookbook.md +1 -1
  149. package/docs/release-cut-checklist.md +19 -3
  150. package/docs/safety-model.md +1 -1
  151. package/docs/scientific-validation.md +1 -1
  152. package/docs/skills-marketplace.md +1 -1
  153. package/docs/tool-usage.md +1 -1
  154. package/docs/trace-store.md +1 -1
  155. package/docs/troubleshooting.md +87 -0
  156. package/docs/tui-design.md +1 -1
  157. package/docs/worker-dispatch-mechanics.md +1 -1
  158. package/package.json +2 -1
  159. package/src/cli/agents.ts +1 -1
  160. package/src/cli/config-inspect.ts +33 -6
  161. package/src/cli/config.ts +1 -1
  162. package/src/cli/doctor-state-size.ts +82 -0
  163. package/src/cli/doctor.ts +3 -1
  164. package/src/cli/eval.ts +80 -16
  165. package/src/cli/extensions.ts +5 -1
  166. package/src/cli/fleet.ts +32 -3
  167. package/src/cli/targets.ts +44 -13
  168. package/src/cli/trace.ts +63 -4
  169. package/src/cli/usage.ts +63 -14
  170. package/src/core/bus-events.ts +29 -1
  171. package/src/core/cache-telemetry.ts +42 -0
  172. package/src/core/config.ts +18 -0
  173. package/src/core/defaults.ts +36 -6
  174. package/src/core/endpoint-key.ts +27 -0
  175. package/src/core/residency-target-key.ts +25 -0
  176. package/src/core/response-schema.ts +36 -2
  177. package/src/domains/config/classify.ts +3 -0
  178. package/src/domains/context/codewiki/coordinator.ts +12 -4
  179. package/src/domains/dispatch/admission.ts +40 -3
  180. package/src/domains/dispatch/capacity-lease.ts +98 -9
  181. package/src/domains/dispatch/contract.ts +11 -0
  182. package/src/domains/dispatch/execution-plan.ts +44 -4
  183. package/src/domains/dispatch/extension.ts +166 -42
  184. package/src/domains/dispatch/fleet-run.ts +23 -3
  185. package/src/domains/dispatch/heartbeat.ts +32 -8
  186. package/src/domains/dispatch/index.ts +3 -0
  187. package/src/domains/dispatch/orphan-recovery.ts +5 -0
  188. package/src/domains/dispatch/reservation-store.ts +116 -8
  189. package/src/domains/dispatch/state.ts +4 -0
  190. package/src/domains/dispatch/worker-spawn.ts +25 -11
  191. package/src/domains/dispatch/write-boundary-enforcer.ts +20 -3
  192. package/src/domains/dispatch/write-boundary.ts +62 -1
  193. package/src/domains/eval/artifacts/store.ts +62 -0
  194. package/src/domains/eval/compare/behavioral.ts +224 -0
  195. package/src/domains/eval/compare/compare.ts +355 -2
  196. package/src/domains/eval/compare/envelope.ts +128 -0
  197. package/src/domains/eval/compare/gates.ts +24 -6
  198. package/src/domains/eval/compare/thresholds.ts +30 -3
  199. package/src/domains/eval/execution-provenance.ts +240 -0
  200. package/src/domains/eval/metrics/aggregate.ts +136 -0
  201. package/src/domains/eval/metrics/call-ledger-stream.ts +112 -0
  202. package/src/domains/eval/metrics/tracked.ts +413 -0
  203. package/src/domains/eval/provenance.ts +117 -0
  204. package/src/domains/eval/reports/comparison.ts +128 -0
  205. package/src/domains/eval/reports/junit.ts +17 -3
  206. package/src/domains/eval/reports/markdown.ts +3 -3
  207. package/src/domains/eval/reports/text.ts +14 -0
  208. package/src/domains/eval/run-compare.ts +20 -0
  209. package/src/domains/eval/runners/clio-run.ts +127 -0
  210. package/src/domains/eval/runners/external-command.ts +28 -3
  211. package/src/domains/eval/schema/adapter.ts +111 -0
  212. package/src/domains/eval/schema/artifact.ts +20 -0
  213. package/src/domains/eval/schema/behavioral-metrics.ts +204 -0
  214. package/src/domains/eval/schema/behavioral.ts +520 -0
  215. package/src/domains/eval/schema/execution-envelope.ts +194 -0
  216. package/src/domains/eval/schema/serving.ts +74 -0
  217. package/src/domains/eval/schema/suite.ts +38 -8
  218. package/src/domains/eval/schema/validate.ts +58 -3
  219. package/src/domains/eval/schema/verdict.ts +237 -0
  220. package/src/domains/eval/suites/resolve.ts +2 -0
  221. package/src/domains/eval/suites/run.ts +264 -33
  222. package/src/domains/eval/verifiers/command.ts +2 -1
  223. package/src/domains/eval/workspaces/temp-copy.ts +145 -13
  224. package/src/domains/evidence/build.ts +2 -13
  225. package/src/domains/evidence/eval.ts +2 -12
  226. package/src/domains/evidence/findings-markdown.ts +33 -0
  227. package/src/domains/evidence/run-trust.ts +7 -113
  228. package/src/domains/evidence/trust-projection.ts +2 -2
  229. package/src/domains/extensions/compatibility.ts +285 -0
  230. package/src/domains/extensions/discovery.ts +38 -3
  231. package/src/domains/extensions/resources.ts +1 -1
  232. package/src/domains/extensions/state.ts +12 -3
  233. package/src/domains/extensions/types.ts +2 -0
  234. package/src/domains/lifecycle/doctor.ts +69 -1
  235. package/src/domains/memory/index.ts +14 -0
  236. package/src/domains/memory/task-bank-promotion.ts +64 -0
  237. package/src/domains/memory/task-memory-policy.ts +77 -8
  238. package/src/domains/memory/task-memory-spend.ts +131 -0
  239. package/src/domains/memory/task-memory-status.ts +7 -0
  240. package/src/domains/memory/task-memory-telemetry.ts +2 -0
  241. package/src/domains/middleware/index.ts +1 -0
  242. package/src/domains/middleware/memory-intervention.ts +69 -5
  243. package/src/domains/middleware/memory-step-endpoint.ts +71 -0
  244. package/src/domains/observability/background-memory-usage.ts +140 -0
  245. package/src/domains/observability/cost.ts +1 -1
  246. package/src/domains/observability/index.ts +7 -0
  247. package/src/domains/observability/out-of-turn-usage.ts +51 -2
  248. package/src/domains/observability/trace-store.ts +192 -2
  249. package/src/domains/prompts/compiler.ts +100 -13
  250. package/src/domains/providers/endpoint-capacity.ts +96 -0
  251. package/src/domains/providers/index.ts +10 -0
  252. package/src/domains/providers/models/local-models/clio-local-coding-targets.yaml +243 -1
  253. package/src/domains/providers/runtime-resolution.ts +8 -1
  254. package/src/domains/providers/runtimes/common/probe-helpers.ts +31 -9
  255. package/src/domains/providers/runtimes/local-native/llamacpp-anthropic.ts +1 -1
  256. package/src/domains/providers/runtimes/local-native/llamacpp-completion.ts +1 -1
  257. package/src/domains/providers/runtimes/local-native/llamacpp-embed.ts +1 -1
  258. package/src/domains/providers/runtimes/local-native/llamacpp-rerank.ts +1 -1
  259. package/src/domains/providers/runtimes/local-native/llamacpp.ts +4 -1
  260. package/src/domains/providers/runtimes/local-native/lmstudio.ts +4 -1
  261. package/src/domains/providers/runtimes/local-native/ollama-native.ts +6 -1
  262. package/src/domains/providers/types/capability-flags.ts +2 -0
  263. package/src/domains/providers/types/target-descriptor.ts +2 -0
  264. package/src/domains/resources/prompts/loader.ts +95 -33
  265. package/src/domains/safety/call-target.ts +52 -0
  266. package/src/domains/safety/run-effects.ts +35 -4
  267. package/src/domains/session/context-accounting.ts +52 -1
  268. package/src/domains/session/context-ledger.ts +37 -13
  269. package/src/domains/session/index.ts +6 -0
  270. package/src/domains/session/prompt-cache.ts +140 -0
  271. package/src/domains/session/prompt-manifest.ts +42 -0
  272. package/src/engine/acp/adapter.ts +18 -3
  273. package/src/engine/ai.ts +35 -0
  274. package/src/engine/apis/llamacpp-residency.ts +55 -3
  275. package/src/engine/apis/lmstudio.ts +25 -5
  276. package/src/engine/apis/ollama-native.ts +2 -1
  277. package/src/engine/apis/openai-completions.ts +80 -17
  278. package/src/engine/apis/residency-lock.ts +3 -1
  279. package/src/engine/apis/residency.ts +34 -1
  280. package/src/engine/provider-payload.ts +29 -1
  281. package/src/entry/orchestrator.ts +176 -30
  282. package/src/interactive/chat-loop-messages.ts +26 -7
  283. package/src/interactive/chat-loop.ts +318 -41
  284. package/src/interactive/chat-panel.ts +62 -8
  285. package/src/interactive/clio-editor.ts +45 -8
  286. package/src/interactive/context-activity.ts +5 -1
  287. package/src/interactive/context-meter.ts +1 -1
  288. package/src/interactive/context-overlay.ts +40 -10
  289. package/src/interactive/cost-overlay.ts +64 -6
  290. package/src/interactive/dispatch-board.ts +84 -12
  291. package/src/interactive/fleet-run-preview.ts +41 -15
  292. package/src/interactive/handoff-round.ts +41 -2
  293. package/src/interactive/interactive-application.ts +24 -1
  294. package/src/interactive/interactive-input-runtime.ts +8 -0
  295. package/src/interactive/interactive-presentation.ts +4 -0
  296. package/src/interactive/interactive-shell.ts +20 -17
  297. package/src/interactive/interactive-slash-runtime.ts +27 -4
  298. package/src/interactive/memory-overlay.ts +8 -0
  299. package/src/interactive/mutation-preview.ts +295 -0
  300. package/src/interactive/overlay-general-openers.ts +16 -0
  301. package/src/interactive/overlay-key-routing.ts +38 -0
  302. package/src/interactive/overlay-lifecycle.ts +38 -5
  303. package/src/interactive/overlay-permission-lifecycle.ts +22 -2
  304. package/src/interactive/overlay-session-lifecycle.ts +73 -9
  305. package/src/interactive/overlays/ask-user.ts +91 -19
  306. package/src/interactive/overlays/help-reference.ts +4 -0
  307. package/src/interactive/overlays/prompts.ts +11 -1
  308. package/src/interactive/overlays/settings.ts +35 -1
  309. package/src/interactive/permission-hint.ts +34 -2
  310. package/src/interactive/permission-overlay.ts +159 -9
  311. package/src/interactive/prewarm.ts +197 -0
  312. package/src/interactive/render-trace.ts +162 -15
  313. package/src/interactive/renderers/tool-execution.ts +4 -0
  314. package/src/interactive/side-question.ts +58 -1
  315. package/src/interactive/status/controller.ts +11 -0
  316. package/src/interactive/status/state-machine.ts +54 -2
  317. package/src/interactive/status/types.ts +7 -0
  318. package/src/interactive/terminal-lease.ts +2 -0
  319. package/src/interactive/turn-context.ts +299 -31
  320. package/src/interactive/turn-persistence.ts +14 -4
  321. package/src/interactive/turn-prewarm.ts +364 -0
  322. package/src/interactive/turn-queues.ts +7 -4
  323. package/src/interactive/turn-runtime.ts +8 -1
  324. package/src/interactive/turn-state.ts +23 -0
  325. package/src/interactive/view/view-overlay.ts +28 -3
  326. package/src/tools/ask-user.ts +43 -2
  327. package/src/tools/dispatch-plan.ts +17 -9
  328. package/src/tools/dispatch-scout.ts +1 -1
  329. package/src/tools/registry.ts +16 -0
  330. package/dist/chunk-AOCYTWAV.js +0 -449
  331. package/dist/chunk-HWUFFB6L.js +0 -83
  332. package/dist/chunk-R346GLFC.js +0 -31
  333. package/dist/doctor-M7YEDGAE.js +0 -91
package/src/cli/trace.ts CHANGED
@@ -4,12 +4,15 @@ import { resolve } from "node:path";
4
4
  import { fileURLToPath, pathToFileURL } from "node:url";
5
5
  import { clioStatePath } from "../core/xdg.js";
6
6
  import {
7
+ DEFAULT_TRACE_RETENTION_POLICY,
8
+ resolveTraceRetentionPolicy,
7
9
  TRACE_EVENT_POLL_LIMIT,
8
10
  type TraceEventRow,
9
11
  type TracePhaseRow,
10
12
  type TraceProcessRow,
11
13
  TraceReader,
12
14
  type TraceRunRow,
15
+ TraceStore,
13
16
  traceDatabasePath,
14
17
  } from "../domains/observability/trace-store.js";
15
18
 
@@ -18,11 +21,15 @@ const HELP = `Usage:
18
21
  clio-coder trace phases <runId> [--db PATH]
19
22
  clio-coder trace tail <runId> [--follow] [--db PATH]
20
23
  clio-coder trace procs <runId> [--db PATH]
24
+ clio-coder trace prune [--max-age-days N] [--max-bytes N] [--db PATH] [--json]
21
25
  clio-coder trace sql <SELECT query> [--db PATH]
22
26
  clio-coder trace ui [--db PATH] [--port N] source checkout only
23
27
 
24
28
  The viewer ships with the repository, not the npm package, so from an installed
25
29
  Clio the subcommands above are the way in. They read the same database.
30
+ Pruning keeps ${DEFAULT_TRACE_RETENTION_POLICY.maxAgeDays} days and at most ${DEFAULT_TRACE_RETENTION_POLICY.maxBytes} bytes by default. Set
31
+ CLIO_CODER_TRACE_RETENTION_DAYS or CLIO_CODER_TRACE_MAX_BYTES to change the automatic
32
+ policy; prune flags override those values for one command.
26
33
  `;
27
34
 
28
35
  interface ParsedTraceArgs {
@@ -34,6 +41,8 @@ interface ParsedTraceArgs {
34
41
  limit: number;
35
42
  port: number;
36
43
  json: boolean;
44
+ maxAgeDays: number | undefined;
45
+ maxBytes: number | undefined;
37
46
  }
38
47
 
39
48
  function parseTraceArgs(args: string[]): ParsedTraceArgs {
@@ -44,11 +53,19 @@ function parseTraceArgs(args: string[]): ParsedTraceArgs {
44
53
  let limit = 50;
45
54
  let port = 0;
46
55
  let json = false;
56
+ let maxAgeDays: number | undefined;
57
+ let maxBytes: number | undefined;
47
58
  for (let index = 0; index < args.length; index += 1) {
48
59
  const arg = args[index];
49
60
  if (arg === "--follow") follow = true;
50
61
  else if (arg === "--json") json = true;
51
- else if (arg === "--db" || arg === "--limit" || arg === "--port") {
62
+ else if (
63
+ arg === "--db" ||
64
+ arg === "--limit" ||
65
+ arg === "--port" ||
66
+ arg === "--max-age-days" ||
67
+ arg === "--max-bytes"
68
+ ) {
52
69
  const value = args[index + 1];
53
70
  if (value === undefined) throw new Error(`${arg} requires a value`);
54
71
  index += 1;
@@ -56,20 +73,25 @@ function parseTraceArgs(args: string[]): ParsedTraceArgs {
56
73
  db = resolve(value);
57
74
  dbExplicit = true;
58
75
  } else if (arg === "--limit") limit = parseInteger(value, "--limit", 1, 500);
59
- else port = parseInteger(value, "--port", 0, 65_535);
76
+ else if (arg === "--port") port = parseInteger(value, "--port", 0, 65_535);
77
+ else if (arg === "--max-age-days") maxAgeDays = parseInteger(value, arg, 1, 36_500);
78
+ else maxBytes = parseInteger(value, arg, 1024 * 1024, Number.MAX_SAFE_INTEGER);
60
79
  } else if (arg?.startsWith("--db=")) {
61
80
  db = resolve(arg.slice(5));
62
81
  dbExplicit = true;
63
82
  } else if (arg?.startsWith("--limit=")) limit = parseInteger(arg.slice(8), "--limit", 1, 500);
64
83
  else if (arg?.startsWith("--port=")) port = parseInteger(arg.slice(7), "--port", 0, 65_535);
84
+ else if (arg?.startsWith("--max-age-days=")) maxAgeDays = parseInteger(arg.slice(15), "--max-age-days", 1, 36_500);
85
+ else if (arg?.startsWith("--max-bytes="))
86
+ maxBytes = parseInteger(arg.slice(12), "--max-bytes", 1024 * 1024, Number.MAX_SAFE_INTEGER);
65
87
  else if (arg?.startsWith("-")) throw new Error(`unknown trace flag: ${arg}`);
66
88
  else if (arg !== undefined) positional.push(arg);
67
89
  }
68
- return { positional, db, dbExplicit, follow, limit, port, json };
90
+ return { positional, db, dbExplicit, follow, limit, port, json, maxAgeDays, maxBytes };
69
91
  }
70
92
 
71
93
  /** Every subcommand `trace` answers to. Anything else is a usage error. */
72
- const TRACE_COMMANDS = new Set(["runs", "phases", "tail", "procs", "sql", "ui"]);
94
+ const TRACE_COMMANDS = new Set(["runs", "phases", "tail", "procs", "prune", "sql", "ui"]);
73
95
 
74
96
  /** The subcommands whose first positional is a run id. */
75
97
  const TRACE_COMMANDS_NEEDING_RUN_ID = new Set(["phases", "tail", "procs"]);
@@ -132,6 +154,7 @@ export async function runTraceCommand(args: string[]): Promise<number> {
132
154
  // the operator typed is a different claim, so a --db that is not there stays
133
155
  // an error and says which path it means.
134
156
  if (!existsSync(parsed.db)) return missingDatabase(parsed);
157
+ if (command === "prune") return runTracePrune(parsed);
135
158
 
136
159
  let reader: TraceReader;
137
160
  try {
@@ -189,6 +212,42 @@ export async function runTraceCommand(args: string[]): Promise<number> {
189
212
  }
190
213
  }
191
214
 
215
+ function runTracePrune(parsed: ParsedTraceArgs): number {
216
+ let policy: ReturnType<typeof resolveTraceRetentionPolicy>;
217
+ try {
218
+ policy = resolveTraceRetentionPolicy({
219
+ ...(parsed.maxAgeDays === undefined ? {} : { maxAgeDays: parsed.maxAgeDays }),
220
+ ...(parsed.maxBytes === undefined ? {} : { maxBytes: parsed.maxBytes }),
221
+ });
222
+ } catch (error) {
223
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
224
+ return 1;
225
+ }
226
+ let store: TraceStore;
227
+ try {
228
+ store = new TraceStore(parsed.db, { retention: policy });
229
+ } catch (error) {
230
+ process.stderr.write(`trace database: ${error instanceof Error ? error.message : String(error)}\n`);
231
+ return 1;
232
+ }
233
+ try {
234
+ const result = store.prune(policy);
235
+ if (parsed.json) {
236
+ process.stdout.write(`${JSON.stringify({ policy, ...result }, null, 2)}\n`);
237
+ } else {
238
+ process.stdout.write(
239
+ `trace prune: removed ${result.runsRemoved.toLocaleString("en-US")} runs and ${result.rowsRemoved.toLocaleString("en-US")} rows; reclaimed ${result.bytesRemoved.toLocaleString("en-US")} bytes; VACUUM ${result.vacuumed ? "ran" : "not needed"}; protected ${result.protectedRuns.toLocaleString("en-US")} live runs\n`,
240
+ );
241
+ }
242
+ return 0;
243
+ } catch (error) {
244
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
245
+ return 1;
246
+ } finally {
247
+ store.close();
248
+ }
249
+ }
250
+
192
251
  async function tail(reader: TraceReader, runId: string, follow: boolean): Promise<void> {
193
252
  let cursor = 0;
194
253
  let idleTerminalPolls = 0;
package/src/cli/usage.ts CHANGED
@@ -12,9 +12,12 @@ import { readEvidenceIndex } from "../domains/observability/evidence-index.js";
12
12
  import { type OutOfTurnUsageRow, readOutOfTurnUsageRows } from "../domains/observability/out-of-turn-usage.js";
13
13
  import { loadSkills } from "../domains/resources/index.js";
14
14
  import {
15
+ foldPromptCacheTelemetry,
16
+ hasPromptCacheTelemetry,
15
17
  type LedgerUsageCall,
16
18
  ledgerUsageCalls,
17
19
  listSessionLedgerRefs,
20
+ type PromptCacheTelemetry,
18
21
  parseSessionEntries,
19
22
  readAuditRows,
20
23
  } from "../domains/session/index.js";
@@ -69,6 +72,8 @@ interface SessionUsage {
69
72
  entriesInWindow: number;
70
73
  /** Completed API calls this session recorded in the window, one per call. */
71
74
  usageCalls: LedgerUsageCall[];
75
+ /** Backend prompt-cache facts persisted on assistant calls in the window. */
76
+ promptCache: PromptCacheTelemetry;
72
77
  }
73
78
 
74
79
  /** Per-call usage folded over one grouping (a model, or the whole window). */
@@ -102,32 +107,37 @@ function addResponseModelIdObservation(counts: ResponseModelIdObservationCounts,
102
107
 
103
108
  /**
104
109
  * How the folded calls split between the session's own turns and the rounds
105
- * that were billed beside it. A `/btw` side question and a `/handoff` round are
106
- * real spend and belong in the token and cost totals, but neither is a turn:
107
- * they never entered the session. `turns` therefore subtracts them from the
108
- * folded row count, which is exactly what the `/cost` overlay does.
110
+ * that were billed beside it. A `/btw` side question, a `/handoff` round, a
111
+ * session pre-warm, and a proactive-memory step are real spend and belong in
112
+ * the token and cost totals, but none of them is a turn: they never entered
113
+ * the session. `turns` therefore subtracts them from the folded row count,
114
+ * which is exactly what the `/cost` overlay does.
109
115
  */
110
116
  interface CallOrigins {
111
117
  rows: number;
112
118
  sideQuestions: number;
113
119
  handoffs: number;
120
+ prewarms: number;
121
+ backgroundMemory: number;
114
122
  }
115
123
 
116
124
  function emptyCallOrigins(): CallOrigins {
117
- return { rows: 0, sideQuestions: 0, handoffs: 0 };
125
+ return { rows: 0, sideQuestions: 0, handoffs: 0, prewarms: 0, backgroundMemory: 0 };
118
126
  }
119
127
 
120
128
  function turnsOf(origins: CallOrigins): number {
121
- return origins.rows - origins.sideQuestions - origins.handoffs;
129
+ return origins.rows - origins.sideQuestions - origins.handoffs - origins.prewarms - origins.backgroundMemory;
122
130
  }
123
131
 
124
132
  function hasLabelledCall(origins: CallOrigins): boolean {
125
- return origins.sideQuestions > 0 || origins.handoffs > 0;
133
+ return origins.sideQuestions > 0 || origins.handoffs > 0 || origins.prewarms > 0 || origins.backgroundMemory > 0;
126
134
  }
127
135
 
128
136
  function addOutOfTurnRow(origins: CallOrigins, row: OutOfTurnUsageRow): void {
129
137
  origins.rows += 1;
130
138
  if (row.label === "side-question") origins.sideQuestions += 1;
139
+ else if (row.label === "prewarm") origins.prewarms += 1;
140
+ else if (row.label === "background-memory") origins.backgroundMemory += 1;
131
141
  else origins.handoffs += 1;
132
142
  }
133
143
 
@@ -435,6 +445,9 @@ export async function runUsageCommand(argv: ReadonlyArray<string>): Promise<numb
435
445
  const usageRows = [...usageByModel.values()].sort(
436
446
  (a, b) => b.totals.totalTokens - a.totals.totalTokens || a.providerId.localeCompare(b.providerId),
437
447
  );
448
+ const cacheSessions = sessions
449
+ .filter((session) => hasPromptCacheTelemetry(session.promptCache))
450
+ .sort((a, b) => a.sessionId.localeCompare(b.sessionId));
438
451
 
439
452
  const approvedMemory = memoryRecords.filter((record) => record.approved && record.rejectedAt === undefined);
440
453
  const pendingMemory = memoryRecords.filter((record) => !record.approved && record.rejectedAt === undefined);
@@ -515,8 +528,8 @@ export async function runUsageCommand(argv: ReadonlyArray<string>): Promise<numb
515
528
  emit({ kind: "fact", fact: "audit-tool-calls", value: auditToolCalls.length, blocked: auditBlocked.length });
516
529
  if (usageMeasurable) {
517
530
  // The origin split is emitted only when something out of turn was
518
- // recorded, so a report over an archive with no `/btw` or `/handoff`
519
- // round in it stays byte-identical to what it printed before.
531
+ // recorded, so a report over an archive with no `/btw`, `/handoff`, or
532
+ // pre-warm round in it stays byte-identical to what it printed before.
520
533
  emit({
521
534
  kind: "fact",
522
535
  fact: "tokens",
@@ -526,6 +539,8 @@ export async function runUsageCommand(argv: ReadonlyArray<string>): Promise<numb
526
539
  turns: turnsOf(callOrigins),
527
540
  sideQuestions: callOrigins.sideQuestions,
528
541
  handoffs: callOrigins.handoffs,
542
+ prewarms: callOrigins.prewarms,
543
+ backgroundMemorySteps: callOrigins.backgroundMemory,
529
544
  }
530
545
  : {}),
531
546
  });
@@ -540,6 +555,15 @@ export async function runUsageCommand(argv: ReadonlyArray<string>): Promise<numb
540
555
  ...row.totals,
541
556
  });
542
557
  }
558
+ for (const session of cacheSessions) {
559
+ emit({
560
+ kind: "fact",
561
+ fact: "session-cache",
562
+ sessionId: session.sessionId,
563
+ uncachedPrefillTokens: session.promptCache.uncachedPrefillTokens,
564
+ verdictCounts: session.promptCache.verdictCounts,
565
+ });
566
+ }
543
567
  }
544
568
  for (const [tool, totals] of topTools) emit({ kind: "fact", fact: "top-tool", tool, ...totals });
545
569
  for (const [shape, count] of topShapes) {
@@ -600,6 +624,10 @@ export async function runUsageCommand(argv: ReadonlyArray<string>): Promise<numb
600
624
  out(` turns in window: ${turnsOf(callOrigins)}`);
601
625
  if (callOrigins.sideQuestions > 0) out(` side questions in window: ${callOrigins.sideQuestions}`);
602
626
  if (callOrigins.handoffs > 0) out(` handoffs in window: ${callOrigins.handoffs}`);
627
+ if (callOrigins.prewarms > 0) out(` pre-warms in window: ${callOrigins.prewarms}`);
628
+ if (callOrigins.backgroundMemory > 0) {
629
+ out(` background memory steps in window: ${callOrigins.backgroundMemory}`);
630
+ }
603
631
  }
604
632
  out(` provider-reported cost in window: $${usageTotals.costUsd.toFixed(4)}`);
605
633
  }
@@ -639,6 +667,27 @@ export async function runUsageCommand(argv: ReadonlyArray<string>): Promise<numb
639
667
  ),
640
668
  );
641
669
  }
670
+ if (cacheSessions.length > 0) {
671
+ out("");
672
+ out(" prompt cache by session (from backend timings and persisted verdicts)");
673
+ process.stdout.write(
674
+ indent(
675
+ formatColumns([
676
+ ["session", "uncached prefill", "hot/partial/cold/small"],
677
+ ...cacheSessions.map((session) => [
678
+ session.sessionId,
679
+ session.promptCache.uncachedPrefillTokens === null ? "n/a" : String(session.promptCache.uncachedPrefillTokens),
680
+ [
681
+ session.promptCache.verdictCounts.hot,
682
+ session.promptCache.verdictCounts.partial,
683
+ session.promptCache.verdictCounts.cold,
684
+ session.promptCache.verdictCounts.small,
685
+ ].join("/"),
686
+ ]),
687
+ ]),
688
+ ),
689
+ );
690
+ }
642
691
  if (topTools.length > 0) {
643
692
  out("");
644
693
  out(" top tools (from receipt toolStats)");
@@ -865,6 +914,9 @@ async function readSessions(
865
914
  }
866
915
  const parsedEntries = parseSessionEntries(raw, ref.path);
867
916
  diagnostics.malformedSessionLines += parsedEntries.errors.length;
917
+ const accountingEntries = parsedEntries.entries.filter(
918
+ (entry) => entry.kind === "modelChange" || inWindow(entry.timestamp, windowStart, windowEnd),
919
+ );
868
920
  const usage: SessionUsage = {
869
921
  sessionId: ref.sessionId,
870
922
  cwdHash: ref.cwdHash,
@@ -874,11 +926,8 @@ async function readSessions(
874
926
  // modelChange rows are kept regardless of the window: they carry no
875
927
  // usage of their own, and dropping the ones that predate the window
876
928
  // would attribute in-window calls to the wrong target.
877
- usageCalls: ledgerUsageCalls(
878
- parsedEntries.entries.filter(
879
- (entry) => entry.kind === "modelChange" || inWindow(entry.timestamp, windowStart, windowEnd),
880
- ),
881
- ),
929
+ usageCalls: ledgerUsageCalls(accountingEntries),
930
+ promptCache: foldPromptCacheTelemetry(accountingEntries),
882
931
  };
883
932
  for (const entry of parsedEntries.entries) {
884
933
  if (!inWindow(entry.timestamp, windowStart, windowEnd)) continue;
@@ -51,6 +51,7 @@ export const BusChannels = {
51
51
  ToolBudgetExceeded: "safety.toolBudgetExceeded",
52
52
  ProviderHealth: "provider.health",
53
53
  RuntimeNotice: "runtime.notice",
54
+ ResidencyMutation: "residency.mutation",
54
55
  DispatchScopeNotice: "dispatch.scopeNotice",
55
56
  DispatchEnqueued: "dispatch.enqueued",
56
57
  DispatchStarted: "dispatch.started",
@@ -64,6 +65,7 @@ export const BusChannels = {
64
65
  ContextWarning: "context.warning",
65
66
  ContextPruned: "context.pruned",
66
67
  ContextRecalled: "context.recalled",
68
+ MemoryStepCompleted: "memory.stepCompleted",
67
69
  AgentStatusChanged: "agent.status.changed",
68
70
  RunAborted: "run.aborted",
69
71
  BudgetAlert: "budget.alert",
@@ -100,6 +102,28 @@ export interface RuntimeNoticePayload {
100
102
  detail?: Record<string, number | string | boolean>;
101
103
  }
102
104
 
105
+ /**
106
+ * One completed proactive-memory model step, published on
107
+ * {@link BusChannels.MemoryStepCompleted}. It carries the endpoint the step
108
+ * called and nothing about what the step decided: the only consumer is the
109
+ * turn context, which stamps an expected-cold reason when a step ran between
110
+ * turns on the endpoint the chat target streams against.
111
+ */
112
+ export interface MemoryStepCompletedPayload {
113
+ endpointKey: string;
114
+ targetId: string;
115
+ }
116
+
117
+ /** A successful model load or eviction on one residency-managed endpoint. */
118
+ export interface ResidencyMutationPayload {
119
+ targetKey: string;
120
+ targetId: string;
121
+ runtimeId: string;
122
+ model: string;
123
+ operation: "load" | "evict";
124
+ at: number;
125
+ }
126
+
103
127
  /**
104
128
  * Window-resolution warning published on {@link BusChannels.ContextWarning}.
105
129
  * Emitted on transitions only: `warning` carries the text when a warning
@@ -133,7 +157,7 @@ export type DispatchScopeNoticePayload =
133
157
  };
134
158
 
135
159
  export type ContextActivityKind = "context-init" | "context-clear" | "context-refresh" | "context-wiki" | "compaction";
136
- export type ContextActivityPhase = "scan" | "codewiki" | "generate" | "clio-md" | "state" | "done";
160
+ export type ContextActivityPhase = "scan" | "codewiki" | "generate" | "clio-md" | "state" | "compact" | "done";
137
161
  export type ContextActivityStatus = "started" | "running" | "completed" | "failed";
138
162
 
139
163
  /** Structured progress for context operations. Interactive renders this as a live context island. */
@@ -505,6 +529,8 @@ export interface DispatchRunIdentity {
505
529
  wireModelId: string;
506
530
  runtimeId: string;
507
531
  runtimeKind: RunKind;
532
+ /** Canonical inference scheduler and its resolved request-slot bound. */
533
+ endpoint?: { key: string; label: string; limit: number } | undefined;
508
534
  /** Admitted invocation budget provenance for native workers. */
509
535
  budget?: RunToolBudgetEnvelope | undefined;
510
536
  /** Fleet node id the run was placed on; absent renders as the local node. */
@@ -761,6 +787,7 @@ export type BusPayloadMap = {
761
787
  [BusChannels.ToolBudgetExceeded]: ToolBudgetExceededPayload;
762
788
  [BusChannels.ProviderHealth]: ProviderHealthPayload;
763
789
  [BusChannels.RuntimeNotice]: RuntimeNoticePayload;
790
+ [BusChannels.ResidencyMutation]: ResidencyMutationPayload;
764
791
  [BusChannels.DispatchScopeNotice]: DispatchScopeNoticePayload;
765
792
  [BusChannels.DispatchEnqueued]: DispatchEnqueuedPayload;
766
793
  [BusChannels.DispatchStarted]: DispatchStartedPayload;
@@ -774,6 +801,7 @@ export type BusPayloadMap = {
774
801
  [BusChannels.ContextWarning]: ContextWarningPayload;
775
802
  [BusChannels.ContextPruned]: ContextPrunedPayload;
776
803
  [BusChannels.ContextRecalled]: ContextRecalledPayload;
804
+ [BusChannels.MemoryStepCompleted]: MemoryStepCompletedPayload;
777
805
  [BusChannels.AgentStatusChanged]: AgentStatusChangedPayload;
778
806
  [BusChannels.RunAborted]: RunAbortedPayload;
779
807
  [BusChannels.BudgetAlert]: BudgetAlertPayload;
@@ -0,0 +1,42 @@
1
+ /** Cache classification for one backend completion call. */
2
+ export type BackendCacheVerdict = "hot" | "partial" | "cold" | "small";
3
+
4
+ /** Backend timing source that directly reports local prefill work. */
5
+ export type BackendTimingsSource = "llamacpp-timings" | "lmstudio-timings";
6
+
7
+ /** Normalized prefill and prediction timings reported by the serving backend. */
8
+ export interface BackendCompletionTimings {
9
+ promptTokens: number;
10
+ cachedTokens: number | null;
11
+ predictedTokens: number;
12
+ promptMs: number;
13
+ predictedMs: number;
14
+ source: BackendTimingsSource;
15
+ }
16
+
17
+ /** Per-session call counts grouped by backend cache verdict. */
18
+ export interface CacheVerdictCounts {
19
+ hot: number;
20
+ partial: number;
21
+ cold: number;
22
+ small: number;
23
+ }
24
+
25
+ export function emptyCacheVerdictCounts(): CacheVerdictCounts {
26
+ return { hot: 0, partial: 0, cold: 0, small: 0 };
27
+ }
28
+
29
+ /** Return newly evaluated prefill tokens when the backend reports cache reads. */
30
+ export function uncachedPrefillTokens(backend: BackendCompletionTimings | null | undefined): number | null {
31
+ if (backend === null || backend === undefined || backend.cachedTokens === null) return null;
32
+ if (
33
+ !Number.isFinite(backend.promptTokens) ||
34
+ backend.promptTokens < 0 ||
35
+ !Number.isFinite(backend.cachedTokens) ||
36
+ backend.cachedTokens < 0 ||
37
+ backend.cachedTokens > backend.promptTokens
38
+ ) {
39
+ return null;
40
+ }
41
+ return backend.promptTokens - backend.cachedTokens;
42
+ }
@@ -521,6 +521,7 @@ function validateTarget(issues: Issues, path: string, value: unknown): ClioSetti
521
521
  "gateway",
522
522
  "pricing",
523
523
  "lmstudio",
524
+ "maxConcurrentRequests",
524
525
  ]);
525
526
  const id = "id" in value ? expectString(issues, `${path}.id`, value.id) : undefined;
526
527
  const runtime = "runtime" in value ? expectString(issues, `${path}.runtime`, value.runtime) : undefined;
@@ -566,6 +567,10 @@ function validateTarget(issues: Issues, path: string, value: unknown): ClioSetti
566
567
  const v = validateLmStudioSettings(issues, `${path}.lmstudio`, value.lmstudio);
567
568
  if (v !== undefined) target.lmstudio = v;
568
569
  }
570
+ if ("maxConcurrentRequests" in value) {
571
+ const v = expectInteger(issues, `${path}.maxConcurrentRequests`, value.maxConcurrentRequests, { min: 1 });
572
+ if (v !== undefined) target.maxConcurrentRequests = v;
573
+ }
569
574
  return target;
570
575
  }
571
576
 
@@ -1004,6 +1009,7 @@ const TOP_LEVEL_KEYS = [
1004
1009
  "keybindings",
1005
1010
  "compaction",
1006
1011
  "context",
1012
+ "prewarm",
1007
1013
  "retry",
1008
1014
  "guardrails",
1009
1015
  ] as const;
@@ -1589,6 +1595,18 @@ export function validateSettings(raw: unknown): SettingsValidationResult {
1589
1595
  }
1590
1596
  }
1591
1597
 
1598
+ if ("prewarm" in raw) {
1599
+ if (!isPlainObject(raw.prewarm)) {
1600
+ issues.add("prewarm", `expected a map, got ${describe(raw.prewarm)}`);
1601
+ } else {
1602
+ issues.unknownKeys("prewarm", raw.prewarm, ["enabled"]);
1603
+ if ("enabled" in raw.prewarm) {
1604
+ const v = expectBoolean(issues, "prewarm.enabled", raw.prewarm.enabled);
1605
+ if (v !== undefined) settings.prewarm.enabled = v;
1606
+ }
1607
+ }
1608
+ }
1609
+
1592
1610
  if ("retry" in raw) {
1593
1611
  if (!isPlainObject(raw.retry)) {
1594
1612
  issues.add("retry", `expected a map, got ${describe(raw.retry)}`);
@@ -151,6 +151,21 @@ export interface WorkingSetSettings {
151
151
  minEvictableTokens: number;
152
152
  }
153
153
 
154
+ /**
155
+ * Prompt pre-warm settings (`prewarm`). On a local server prefill is the cost,
156
+ * and the prefix the next turn will send is fully known before the operator
157
+ * types anything: at session start, after a resume rebuilds the message array,
158
+ * and after a compaction settles. Clio sends that prefix to the backend right
159
+ * then so the slot's prefix cache already holds it when the real turn arrives.
160
+ *
161
+ * - enabled: master switch. The feature is off on every tier but
162
+ * `local-native` whatever this says, and off for workers and headless runs,
163
+ * because it buys latency for an operator watching a local server.
164
+ */
165
+ export interface PrewarmSettings {
166
+ enabled: boolean;
167
+ }
168
+
154
169
  /**
155
170
  * Transient provider retry controls for the interactive chat loop. These are
156
171
  * intentionally small and mirror the session retry helper defaults. Dispatched
@@ -354,11 +369,15 @@ export const DEFAULT_SETTINGS = {
354
369
  everyNTools: 10,
355
370
  windowSteps: 8,
356
371
  maxTokens: 400,
357
- // A memory step runs detached from the turn that triggered it, so a
358
- // generous deadline costs no interactive latency. Measured steps on a
359
- // small local route ranged past two minutes, and a deadline shorter than
360
- // the model discards finished work as a timeout.
361
- timeoutMs: 180_000,
372
+ // A memory step runs detached from the turn that triggered it, but it
373
+ // runs on a real inference endpoint that the operator's own turns and
374
+ // dispatched workers also queue against. The operator's ledger over 14
375
+ // days recorded 60 llm-tier steps spending 1,666 seconds of model time
376
+ // for 6 injections, with one step holding a local server for 102.5
377
+ // seconds to answer nothing. The deadline is what a turn boundary can
378
+ // actually wait for; a step that runs past it is recorded as timed out
379
+ // rather than spending the remaining minutes unobserved.
380
+ timeoutMs: 30_000,
362
381
  },
363
382
  },
364
383
  watchdog: { enabled: false } as WatchdogSettings,
@@ -444,6 +463,9 @@ export const DEFAULT_SETTINGS = {
444
463
  context: {
445
464
  workingSet: DEFAULT_WORKING_SET_SETTINGS,
446
465
  },
466
+ prewarm: {
467
+ enabled: true,
468
+ } as PrewarmSettings,
447
469
  retry: {
448
470
  enabled: true,
449
471
  maxRetries: 3,
@@ -539,7 +561,7 @@ memory:
539
561
  everyNTools: 10
540
562
  windowSteps: 8
541
563
  maxTokens: 400
542
- timeoutMs: 180000
564
+ timeoutMs: 30000
543
565
 
544
566
  # Opt-in turn-end watchdog. When enabled, a turn that changed the tree is
545
567
  # reviewed by one read-only verifier run briefed with the turn's coalesced diff
@@ -751,6 +773,14 @@ context:
751
773
  protectLastTurns: 6
752
774
  minEvictableTokens: 200
753
775
 
776
+ # Prompt pre-warm. At session start, after a resume rebuilds the message array,
777
+ # and after a compaction settles, Clio sends the prefix the next turn would send
778
+ # (minus your text) so a local server has already prefilled it when you press
779
+ # Enter. Local-native targets only, interactive sessions only, and never while a
780
+ # turn or a dispatch is running.
781
+ prewarm:
782
+ enabled: true
783
+
754
784
  # Transient provider/stream retry controls for interactive chat.
755
785
  # Retryable errors include overloads, rate limits, 5xx responses, network
756
786
  # resets, and timeouts. Context overflow uses compaction recovery instead.
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Canonical identity of an OpenAI-compatible inference endpoint: normalized
3
+ * HTTP scheme, lower-cased host, normalized port, and normalized base path with
4
+ * a conventional terminal `/v1` mount stripped. Credentials, query, and
5
+ * fragment do not participate. Host aliases are not resolved, so
6
+ * `localhost` and `127.0.0.1` stay distinct. Shared by dispatch capacity and
7
+ * residency locking so both speak the same key for one server.
8
+ */
9
+ export function canonicalEndpointUrl(raw: string | null | undefined): string | null {
10
+ if (!raw?.trim()) return null;
11
+ try {
12
+ const url = new URL(raw.trim().replace(/^ws:/u, "http:").replace(/^wss:/u, "https:"));
13
+ url.protocol = url.protocol.toLowerCase();
14
+ if (url.protocol !== "http:" && url.protocol !== "https:") return null;
15
+ url.hostname = url.hostname.toLowerCase();
16
+ url.username = "";
17
+ url.password = "";
18
+ url.hash = "";
19
+ url.search = "";
20
+ let path = url.pathname.replace(/\/{2,}/gu, "/").replace(/\/$/u, "");
21
+ if (path.endsWith("/v1")) path = path.slice(0, -3);
22
+ url.pathname = path || "/";
23
+ return url.toString().replace(/\/$/u, "");
24
+ } catch {
25
+ return null;
26
+ }
27
+ }
@@ -0,0 +1,25 @@
1
+ import { canonicalEndpointUrl } from "./endpoint-key.js";
2
+
3
+ /** Runtime families whose model residency Clio can actively mutate. */
4
+ export type ResidencyRuntimeId = "llamacpp" | "lmstudio" | "ollama-native";
5
+
6
+ const RESIDENCY_RUNTIME_IDS: ReadonlySet<string> = new Set<ResidencyRuntimeId>([
7
+ "llamacpp",
8
+ "lmstudio",
9
+ "ollama-native",
10
+ ]);
11
+
12
+ export function residencyTargetKey(runtimeId: ResidencyRuntimeId, baseUrl: string): string;
13
+ export function residencyTargetKey(runtimeId: string, baseUrl: string | null | undefined): string | null;
14
+ /** Stable key for one residency-managed server endpoint: its canonical URL. */
15
+ export function residencyTargetKey(runtimeId: string, baseUrl: string | null | undefined): string | null {
16
+ if (!RESIDENCY_RUNTIME_IDS.has(runtimeId) || typeof baseUrl !== "string") return null;
17
+ // The key is the canonical endpoint itself, without a runtime prefix, so the
18
+ // residency lock and dispatch capacity name one server the same way (#250).
19
+ // A base url the canonical form refuses, such as one written without a
20
+ // scheme, still gets a key: callers store this in `ResidencyAdapter.targetKey`,
21
+ // which is a `string`, and dispatch capacity has no key for that target
22
+ // either, so there is nothing to agree with and nothing to gain by dropping
23
+ // the lock the reconciler serializes on.
24
+ return canonicalEndpointUrl(baseUrl) ?? baseUrl;
25
+ }
@@ -11,11 +11,45 @@ export const RESPONSE_SCHEMA_MAX_SERIALIZED_BYTES = 64 * 1024;
11
11
  * `schema` key, and returns HTTP 200 with unconstrained JSON, so widening this
12
12
  * to the api family would convert a clean refusal into a silent
13
13
  * non-enforcement. lmstudio declares `structuredOutputs: "json-schema"`
14
- * truthfully and is still excluded here, because Clio has no dialect for its
15
- * transport. Widen this only together with a dialect for the runtime added.
14
+ * truthfully and is still excluded here, because the worker seam has no
15
+ * dialect for its transport. Widen this only together with a dialect for the
16
+ * runtime added; {@link responseSchemaDialectFor} is where the out-of-turn
17
+ * seam did exactly that (issue #223).
16
18
  */
17
19
  export const RESPONSE_SCHEMA_RUNTIME_ID = "llamacpp";
18
20
 
21
+ /** The runtime that takes the standard OpenAI `json_schema` response format. */
22
+ export const OPENAI_SCHEMA_RUNTIME_ID = "lmstudio";
23
+
24
+ /**
25
+ * How a runtime spells a JSON-schema response constraint on the wire.
26
+ *
27
+ * `llamacpp-json-object` is llama-server's own `response_format: { type:
28
+ * "json_object", schema }`, which it compiles into a sampler grammar.
29
+ * `openai-json-schema` is the standard `response_format: { type:
30
+ * "json_schema", json_schema: { name, strict, schema } }` that LM Studio
31
+ * implements.
32
+ */
33
+ export type ResponseSchemaDialect = "llamacpp-json-object" | "openai-json-schema";
34
+
35
+ /**
36
+ * The dialect this runtime takes, or null when Clio has none for it and the
37
+ * request has to go unconstrained.
38
+ *
39
+ * Deliberately a name check, for the reason {@link RESPONSE_SCHEMA_RUNTIME_ID}
40
+ * gives: a generic OpenAI-compatible gateway answers HTTP 200 to a spelling it
41
+ * does not implement and returns unconstrained JSON, so a capability flag would
42
+ * turn a known non-enforcement into a silent one. This is the out-of-turn
43
+ * seam's table; the worker seam keeps the narrower
44
+ * {@link runtimeSpeaksResponseSchemaDialect} check, because a worker that
45
+ * cannot enforce a contract must refuse admission rather than degrade.
46
+ */
47
+ export function responseSchemaDialectFor(runtimeId: string): ResponseSchemaDialect | null {
48
+ if (runtimeId === RESPONSE_SCHEMA_RUNTIME_ID) return "llamacpp-json-object";
49
+ if (runtimeId === OPENAI_SCHEMA_RUNTIME_ID) return "openai-json-schema";
50
+ return null;
51
+ }
52
+
19
53
  /** Whether this runtime speaks the dialect above. Transport shape, not capability. */
20
54
  export function runtimeSpeaksResponseSchemaDialect(runtime: { id: string; kind: string; apiFamily: string }): boolean {
21
55
  return (
@@ -61,6 +61,9 @@ const NEXT_TURN_FIELDS = new Set<string>([
61
61
  "delegation",
62
62
  "compaction",
63
63
  "context",
64
+ // The pre-warm reads its setting when a trigger fires, and every trigger sits
65
+ // on a turn boundary, so a change takes effect before the next turn runs.
66
+ "prewarm",
64
67
  "retry",
65
68
  ]);
66
69