@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
@@ -0,0 +1,364 @@
1
+ /**
2
+ * Pre-warm ownership: when the prefix is sent, what stops it, and what it
3
+ * records. The round itself lives in `prewarm.ts`.
4
+ *
5
+ * Three moments hand the backend a prefix the operator has not paid for yet:
6
+ * the session prompt compiling at session start, a resume rebuilding the
7
+ * message array, and a compaction settling. All three leave a known prefix and
8
+ * an operator who is reading rather than waiting on a turn. Everything else is
9
+ * a refusal: another tier, a turn in flight, a running dispatch, a surface with
10
+ * nobody watching.
11
+ */
12
+
13
+ import { BusChannels } from "../core/bus-events.js";
14
+ import type { ClioSettings } from "../core/config.js";
15
+ import type { SafeEventBus } from "../core/event-bus.js";
16
+ import type { ProvidersContract } from "../domains/providers/index.js";
17
+ import type { SessionContract } from "../domains/session/contract.js";
18
+ import type { Usage } from "../engine/types.js";
19
+ import { type PrewarmTrigger, prewarmPromptTokens, runPrewarmRound } from "./prewarm.js";
20
+ import type { TurnContext } from "./turn-context.js";
21
+ import type { AgentRuntime, ChatTurnState } from "./turn-state.js";
22
+
23
+ /** Why a pre-warm did not run. Returned for contracts; never shown to the operator. */
24
+ export type PrewarmSkipReason =
25
+ | "disabled"
26
+ | "surface"
27
+ | "turn-active"
28
+ | "dispatch-active"
29
+ | "tier"
30
+ | "unresolved"
31
+ | "superseded";
32
+
33
+ export type PrewarmOutcome = { ran: true; trigger: PrewarmTrigger } | { ran: false; reason: PrewarmSkipReason };
34
+
35
+ export interface TurnPrewarmDeps {
36
+ state: ChatTurnState;
37
+ getSettings: () => Readonly<ClioSettings>;
38
+ providers: ProvidersContract;
39
+ session?: SessionContract | undefined;
40
+ context: TurnContext;
41
+ bus?: SafeEventBus | undefined;
42
+ /**
43
+ * True on a surface where a person is about to type the next turn. False for
44
+ * headless `run`: the pre-warm buys latency an unattended run never spends.
45
+ * Workers never reach here at all; they do not build a chat loop.
46
+ */
47
+ isLatencySurface: () => boolean;
48
+ /** True while a submit owns, or is about to own, the turn state machine. */
49
+ isTurnActive: () => boolean;
50
+ /**
51
+ * True while an attached dispatch is running. Detached runs are counted from
52
+ * the bus instead, so a composition without this predicate still stands down
53
+ * for the worker traffic the bus reports.
54
+ */
55
+ hasActiveDispatch: () => boolean;
56
+ /** Resolve the runtime and credentials the next turn would use, or a refusal. */
57
+ prepareRuntime: (
58
+ signal: AbortSignal,
59
+ ) => Promise<{ ok: true; runtime: AgentRuntime; apiKey: string | undefined } | { ok: false; reason: string }>;
60
+ /** Freeze the session tool surface onto the agent, exactly as a submit does. */
61
+ applySessionTools: (runtime: AgentRuntime) => void;
62
+ /** Report the round's provider usage under its own `/cost` label. */
63
+ recordUsage: (runtime: AgentRuntime, usage: Usage | null) => void;
64
+ /**
65
+ * Claim one in-flight request on the runtime's endpoint for the duration of
66
+ * the round, returning the release handle. This is the seam brief 06 (#250)
67
+ * fills: on a merged tree the chat loop passes
68
+ *
69
+ * (runtime) => {
70
+ * const key = canonicalEndpointKey(runtime.runtimeResolution.target);
71
+ * return key === null ? null : registerForegroundStream(key);
72
+ * }
73
+ *
74
+ * from `src/domains/providers/index.ts`, so endpoint capacity counts the
75
+ * pre-warm the same way it counts the orchestrator's streaming turn. Until
76
+ * then it is absent and `dispatchActive()` stands in with a blanket skip.
77
+ */
78
+ registerEndpointSlot?: (runtime: AgentRuntime) => (() => void) | null;
79
+ /** Test seam. Production uses the real provider round. */
80
+ runPrewarm?: typeof runPrewarmRound;
81
+ /**
82
+ * Whether a submit aborts the round's request. Defaults to the measured
83
+ * backend behavior; see {@link ABORT_ROUND_ON_SUBMIT}.
84
+ */
85
+ abortRoundOnSubmit?: boolean;
86
+ }
87
+
88
+ export interface TurnPrewarm {
89
+ /**
90
+ * Queue a pre-warm for the next tick. Repeated calls before it fires collapse
91
+ * onto one round: boot and a boot-time resume both land in the same tick, and
92
+ * warming the pre-resume history would be work thrown away.
93
+ */
94
+ schedule(trigger: PrewarmTrigger): void;
95
+ /** Stop an in-flight or queued pre-warm. The operator's turn owns the slot now. */
96
+ cancel(): void;
97
+ /** Resolves once nothing is queued or in flight. For contracts and shutdown. */
98
+ settled(): Promise<PrewarmOutcome | null>;
99
+ dispose(): void;
100
+ }
101
+
102
+ /**
103
+ * Whether a submit aborts the in-flight pre-warm's HTTP request, or only stops
104
+ * treating it as the current one.
105
+ *
106
+ * Measured against the operator's llama.cpp router, build `b226-2115b73d8`,
107
+ * Qwen3.8-27B, `--parallel 1`: aborting the request 1.5 s into a 47,620-token
108
+ * prefill did not cancel the server's work. The server finished prefilling, so
109
+ * the prefix did survive the abort, and the very next request read 47,596 of
110
+ * 47,620 tokens from cache and spent 927 ms in prompt. That request also waited
111
+ * 89.5 s of wall clock for the abandoned one to finish, on a server that serves
112
+ * one slot. Letting the pre-warm complete instead cost 89.3 s plus a 1.3 s turn:
113
+ * the same wall clock, arrived at honestly.
114
+ *
115
+ * The abort therefore frees no slot and saves the operator no time on this
116
+ * backend. All it does is discard the usage and timings of prefill the server
117
+ * performed, which is spend the ledger should carry. So a submit detaches the
118
+ * round: Clio stops calling it the current pre-warm and never waits on it, and
119
+ * it still records what it cost. Set this true for a backend that honors
120
+ * cancellation, where dropping the request genuinely releases the slot.
121
+ */
122
+ const ABORT_ROUND_ON_SUBMIT = false;
123
+
124
+ export function createTurnPrewarm(deps: TurnPrewarmDeps): TurnPrewarm {
125
+ const round = deps.runPrewarm ?? runPrewarmRound;
126
+ const abortOnSubmit = deps.abortRoundOnSubmit ?? ABORT_ROUND_ON_SUBMIT;
127
+ let pendingTrigger: PrewarmTrigger | null = null;
128
+ let timer: ReturnType<typeof setTimeout> | null = null;
129
+ let active: AbortController | null = null;
130
+ // Rounds a submit or a session switch let go of. Their result is still
131
+ // recorded, but it no longer describes the prefix the next turn will send, so
132
+ // it never reaches the `/context` line.
133
+ const detached = new WeakSet<AbortController>();
134
+ let inFlight: Promise<PrewarmOutcome | null> = Promise.resolve(null);
135
+ // Dispatch traffic Clio can see without an endpoint registry: the run
136
+ // lifecycle the bus already publishes. `hasActiveDispatch` covers the attached
137
+ // calls the composition root holds; this covers detached ones. Both are the
138
+ // stand-in for #250's per-endpoint slot accounting.
139
+ let dispatchesInFlight = 0;
140
+ const unsubscribeDispatch = [
141
+ deps.bus?.on(BusChannels.DispatchStarted, () => {
142
+ dispatchesInFlight += 1;
143
+ }) ?? null,
144
+ ...[BusChannels.DispatchCompleted, BusChannels.DispatchFailed].map(
145
+ (channel) =>
146
+ deps.bus?.on(channel, () => {
147
+ dispatchesInFlight = Math.max(0, dispatchesInFlight - 1);
148
+ }) ?? null,
149
+ ),
150
+ ];
151
+ const dispatchActive = (): boolean => dispatchesInFlight > 0 || deps.hasActiveDispatch();
152
+
153
+ const admissionRefusal = (): PrewarmSkipReason | null => {
154
+ if (deps.getSettings().prewarm?.enabled === false) return "disabled";
155
+ if (!deps.isLatencySurface()) return "surface";
156
+ if (deps.state.streaming || deps.isTurnActive()) return "turn-active";
157
+ // Fleet interaction. Without per-endpoint slot accounting a pre-warm cannot
158
+ // know whether a worker is already occupying the server it would warm, so
159
+ // it stands down whenever any dispatch is outstanding. `registerEndpointSlot`
160
+ // is the other half: the round claims a slot on its endpoint while its
161
+ // request is out, so capacity sees it. Once that registry answers here too,
162
+ // this blanket skip narrows to "the endpoint is at its bound".
163
+ if (dispatchActive()) return "dispatch-active";
164
+ // The tier is settled from configuration before anything is resolved. The
165
+ // resolved runtime is checked again below, but asking here means a target
166
+ // that was never eligible costs no capability probe, no agent, and no
167
+ // notice from either.
168
+ const targetId = deps.getSettings().orchestrator?.target?.trim();
169
+ const runtimeId = targetId ? deps.providers.getTarget(targetId)?.runtime : undefined;
170
+ if (runtimeId === undefined || deps.providers.getRuntime(runtimeId)?.tier !== "local-native") return "tier";
171
+ return null;
172
+ };
173
+
174
+ const runOnce = async (trigger: PrewarmTrigger, controller: AbortController): Promise<PrewarmOutcome> => {
175
+ const refusal = admissionRefusal();
176
+ if (refusal) return { ran: false, reason: refusal };
177
+
178
+ const prepared = await deps.prepareRuntime(controller.signal);
179
+ if (!prepared.ok) return { ran: false, reason: "unresolved" };
180
+ const runtime = prepared.runtime;
181
+ if (deps.providers.getRuntime(runtime.runtimeId)?.tier !== "local-native") {
182
+ // Off on every other tier regardless of the setting. A cloud provider
183
+ // caches by exact prefix on its own schedule and bills the request; a
184
+ // pre-warm there is spend without a latency win.
185
+ return { ran: false, reason: "tier" };
186
+ }
187
+
188
+ deps.applySessionTools(runtime);
189
+ await deps.context.ensureSessionPrompt(runtime);
190
+
191
+ // Re-check after the awaits: a submit or a dispatch may have started while
192
+ // the prompt compiled, and the pre-warm must never be the request the
193
+ // operator's turn queues behind.
194
+ const lateRefusal = admissionRefusal();
195
+ if (lateRefusal) return { ran: false, reason: lateRefusal };
196
+ if (detached.has(controller) || controller.signal.aborted) return { ran: false, reason: "superseded" };
197
+
198
+ // The claim is taken before the request goes out and released in the
199
+ // finally, including on the detach path, because the request the server is
200
+ // still finishing is the one occupying the slot.
201
+ const releaseEndpointSlot = deps.registerEndpointSlot?.(runtime) ?? null;
202
+ let result: Awaited<ReturnType<typeof round>>;
203
+ try {
204
+ result = await round({
205
+ model: runtime.agent.state.model,
206
+ state: {
207
+ systemPrompt: runtime.agent.state.systemPrompt,
208
+ messages: runtime.agent.state.messages,
209
+ tools: runtime.agent.state.tools,
210
+ thinkingLevel: runtime.agent.state.thinkingLevel ?? "off",
211
+ },
212
+ ...(prepared.apiKey !== undefined ? { apiKey: prepared.apiKey } : {}),
213
+ signal: controller.signal,
214
+ });
215
+ } finally {
216
+ releaseEndpointSlot?.();
217
+ }
218
+
219
+ // The prefill the server did is spend whatever the operator did next, so a
220
+ // let-go round is recorded exactly like a completed one. Only the
221
+ // `/context` line is withheld: it answers "is the prefix the next turn
222
+ // wants already resident", and a detached round is no longer about that
223
+ // prefix.
224
+ const wasDetached = detached.has(controller) || controller.signal.aborted;
225
+ const promptTokens = prewarmPromptTokens(result);
226
+ if (!wasDetached) {
227
+ deps.context.notePrewarm({
228
+ tokens: promptTokens,
229
+ ms: result.timing.apiMs,
230
+ aborted: result.aborted,
231
+ });
232
+ }
233
+ deps.recordUsage(runtime, result.usage);
234
+
235
+ try {
236
+ deps.session?.appendEntry({
237
+ kind: "custom",
238
+ customType: "prewarm",
239
+ // Never a model message and never a rendered transcript block: a
240
+ // pre-warm is a fact about the backend, not something the session said.
241
+ display: false,
242
+ parentTurnId: deps.state.lastTurnId,
243
+ data: {
244
+ trigger,
245
+ target: runtime.targetId,
246
+ model: runtime.wireModelId,
247
+ promptTokens,
248
+ aborted: result.aborted,
249
+ detached: wasDetached,
250
+ timing: result.timing,
251
+ promptCache: {
252
+ input: result.usage?.input ?? 0,
253
+ cacheRead: result.usage?.cacheRead ?? 0,
254
+ cacheWrite: result.usage?.cacheWrite ?? 0,
255
+ ...(result.backend ? { backend: { ...result.backend } } : {}),
256
+ },
257
+ ...(result.errorMessage ? { error: result.errorMessage } : {}),
258
+ },
259
+ });
260
+ } catch {
261
+ // The ledger entry is diagnostics. A session that refuses it must not
262
+ // turn an optimization into a failure.
263
+ }
264
+ return { ran: true, trigger };
265
+ };
266
+
267
+ /**
268
+ * Let go of the round in flight. On a backend that honors cancellation this
269
+ * also aborts the request; see {@link ABORT_ROUND_ON_SUBMIT} for why the
270
+ * measured one does not.
271
+ */
272
+ const releaseActiveRound = (): void => {
273
+ const controller = active;
274
+ if (controller === null) return;
275
+ detached.add(controller);
276
+ if (abortOnSubmit) controller.abort();
277
+ active = null;
278
+ };
279
+
280
+ const fire = (): void => {
281
+ timer = null;
282
+ const trigger = pendingTrigger;
283
+ pendingTrigger = null;
284
+ if (trigger === null) return;
285
+ const controller = new AbortController();
286
+ active = controller;
287
+ const previous = inFlight;
288
+ inFlight = previous
289
+ .catch(() => null)
290
+ .then(() => runOnce(trigger, controller))
291
+ .catch(() => null)
292
+ .finally(() => {
293
+ if (active === controller) active = null;
294
+ });
295
+ };
296
+
297
+ return {
298
+ schedule(trigger: PrewarmTrigger): void {
299
+ // A newer trigger describes a newer prefix; the older round is no longer
300
+ // warming history this session has.
301
+ releaseActiveRound();
302
+ pendingTrigger = trigger;
303
+ if (timer !== null) return;
304
+ // Ref'd on purpose, same reasoning as `settled()` below: the timer is due
305
+ // in 0 ms, so it holds the event loop for one tick at most, and a Node 22
306
+ // loop that drains past a due unref'd timer would otherwise never start
307
+ // the round a test or caller is already awaiting.
308
+ timer = setTimeout(fire, 0);
309
+ },
310
+
311
+ cancel(): void {
312
+ pendingTrigger = null;
313
+ if (timer !== null) {
314
+ clearTimeout(timer);
315
+ timer = null;
316
+ }
317
+ releaseActiveRound();
318
+ },
319
+
320
+ settled(): Promise<PrewarmOutcome | null> {
321
+ // One hop past the scheduling tick so a caller that just scheduled a
322
+ // round waits for that round rather than for the previous one. The timer
323
+ // is deliberately ref'd, unlike the scheduling timer above: the caller is
324
+ // awaiting this promise, so the event loop must stay alive for the one
325
+ // tick that lets the scheduled round start. Node 22 drains the loop past
326
+ // a due unref'd timer when nothing else holds it, which left this promise
327
+ // pending forever and cancelled every later test in the lane; Node 24
328
+ // happens to fire the due timer first, which is why the hang never
329
+ // reproduced on a 24.x development machine.
330
+ return new Promise((resolve) => {
331
+ setTimeout(() => {
332
+ resolve(inFlight.catch(() => null));
333
+ }, 0);
334
+ });
335
+ },
336
+
337
+ dispose(): void {
338
+ for (const unsubscribe of unsubscribeDispatch) unsubscribe?.();
339
+ pendingTrigger = null;
340
+ if (timer !== null) {
341
+ clearTimeout(timer);
342
+ timer = null;
343
+ }
344
+ // Shutdown always aborts: the process is going away, and a socket held
345
+ // open for a round nobody will read is not a latency question.
346
+ active?.abort();
347
+ active = null;
348
+ },
349
+ };
350
+ }
351
+
352
+ /**
353
+ * Subscribe the pre-warm to compaction settling. A summary or an eviction moves
354
+ * the byte prefix, so the next turn is known to be cold, and the operator is
355
+ * usually reading the summary rather than typing. The idle guard inside
356
+ * `schedule` is what keeps this from firing for the auto-compaction a submit
357
+ * runs on its way to the provider.
358
+ */
359
+ export function subscribePrewarmToCompaction(bus: SafeEventBus | undefined, prewarm: TurnPrewarm): () => void {
360
+ const unsubscribe = bus?.on(BusChannels.CompactionEnd, () => {
361
+ prewarm.schedule("compaction");
362
+ });
363
+ return () => unsubscribe?.();
364
+ }
@@ -83,14 +83,17 @@ export function createTurnQueues(deps: TurnQueuesDeps): TurnQueues {
83
83
  // works. The former per-steer transcript notice duplicated the panel and
84
84
  // left a permanent line for a transient state.
85
85
  const enqueue = (text: string, kind: QueuedMessageKind): boolean => {
86
- const trimmed = text.trim();
87
- if (trimmed.length === 0 || !state.streaming || !state.runtime) return false;
86
+ // The payload crosses to the model exactly as it was submitted; only the
87
+ // emptiness test reads a trimmed copy. A queued turn that shortened its own
88
+ // text here would land in the ledger disagreeing with the expansion that
89
+ // produced it, which is the same defect the persisted echo had (issue #244).
90
+ if (text.trim().length === 0 || !state.streaming || !state.runtime) return false;
88
91
  const message = {
89
92
  role: "user",
90
- content: trimmed,
93
+ content: text,
91
94
  timestamp: Date.now(),
92
95
  } as AgentMessage;
93
- queuedMirror.push({ text: trimmed, kind });
96
+ queuedMirror.push({ text, kind });
94
97
  if (kind === "steer") {
95
98
  state.runtime.agent.steer(message);
96
99
  } else {
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  import { performance } from "node:perf_hooks";
9
+ import type { BackendCompletionTimings } from "../core/cache-telemetry.js";
9
10
  import type { ClioSettings } from "../core/config.js";
10
11
  import { attributedModelId } from "../core/response-model-id.js";
11
12
  import type { MiddlewareToolChoiceControl } from "../domains/middleware/index.js";
@@ -192,6 +193,10 @@ export function createTurnRuntime(deps: TurnRuntimeDeps): TurnRuntime {
192
193
  use: "orchestrator",
193
194
  requireTools: false,
194
195
  requireOutputBudget: true,
196
+ // A resumed session already knows what this backend had open; without
197
+ // it the first turn budgets against the probed server-wide figure and
198
+ // corrects only once discovery reports a loaded window (issue #227).
199
+ knownLoadedContextWindow: context.rememberedLoadedContextWindow(targetId, wireModelId),
195
200
  });
196
201
  if (!resolved.ok) {
197
202
  const failure = resolved.diagnostics.find((entry) => entry.severity === "error") ?? resolved.diagnostics[0];
@@ -315,6 +320,7 @@ export function createTurnRuntime(deps: TurnRuntimeDeps): TurnRuntime {
315
320
  use: "orchestrator",
316
321
  requireTools: false,
317
322
  requireOutputBudget: true,
323
+ knownLoadedContextWindow: context.rememberedLoadedContextWindow(target.target.id, target.wireModelId),
318
324
  });
319
325
  if (!refreshed.ok) return;
320
326
  const liveModel = state.runtime.agent.state.model;
@@ -850,7 +856,8 @@ export function createTurnRuntime(deps: TurnRuntimeDeps): TurnRuntime {
850
856
  if (isAssistant && usage && typeof usage === "object" && runFirstCallVerdict === null) {
851
857
  const input = typeof usage.input === "number" ? usage.input : 0;
852
858
  const cacheRead = typeof usage.cacheRead === "number" ? usage.cacheRead : 0;
853
- runFirstCallVerdict = backendCacheVerdict(input, cacheRead);
859
+ const backendTimings = (enrichedEvent.message as { backendTimings?: BackendCompletionTimings }).backendTimings;
860
+ runFirstCallVerdict = backendCacheVerdict(input, cacheRead, backendTimings);
854
861
  }
855
862
  if (usage) {
856
863
  context.reconcileUsage(usage);
@@ -53,6 +53,13 @@ export interface ChatLoopRunSnapshot {
53
53
  cwd: string;
54
54
  }
55
55
 
56
+ /**
57
+ * What a consumed prompt is doing before it owns the stream. `compacting` is
58
+ * the sub-state that made the defect visible, because it is the one that lasts
59
+ * long enough for an operator to conclude the keystroke was lost.
60
+ */
61
+ export type TurnPreparationPhase = "idle" | "preparing" | "compacting";
62
+
56
63
  export interface ChatTurnState {
57
64
  /** Live agent runtime; built or hot-swapped by turn-runtime. */
58
65
  runtime: AgentRuntime | null;
@@ -60,6 +67,20 @@ export interface ChatTurnState {
60
67
  lastTurnId: string | null;
61
68
  /** True while a submit is in flight. */
62
69
  streaming: boolean;
70
+ /**
71
+ * Where a consumed prompt is between the editor and the live stream.
72
+ *
73
+ * The editor is cleared and the prompt painted into the transcript before
74
+ * admission, and everything from the capability probe through pre-submit
75
+ * compaction to the prompt compile happens after that and before
76
+ * `streaming`. With nothing naming that window the composer went straight
77
+ * back to `MESSAGE` and the footer still read the previous turn as done, so
78
+ * a 77-second compaction was indistinguishable from a dropped Enter
79
+ * (issue #251).
80
+ */
81
+ turnPreparation: TurnPreparationPhase;
82
+ /** When the current preparation window opened, for its elapsed counter. */
83
+ turnPreparationSince: number;
63
84
  /** Thinking level the active request runs under; read by onPayload. */
64
85
  currentThinkingLevel: ThinkingLevel;
65
86
  /** Provider context rebuilt on session switch, consumed by the next runtime build. */
@@ -115,6 +136,8 @@ export function createTurnState(initialThinkingLevel: ThinkingLevel): ChatTurnSt
115
136
  runtime: null,
116
137
  lastTurnId: null,
117
138
  streaming: false,
139
+ turnPreparation: "idle",
140
+ turnPreparationSince: 0,
118
141
  currentThinkingLevel: initialThinkingLevel,
119
142
  replayedContextMessages: [],
120
143
  activeUserTurnId: null,
@@ -2,13 +2,16 @@ import { createReadStream, readFileSync, statSync } from "node:fs";
2
2
  import { readFile, realpath, stat } from "node:fs/promises";
3
3
  import { basename, isAbsolute, join, relative, resolve, sep } from "node:path";
4
4
  import type { DispatchContract } from "../../domains/dispatch/contract.js";
5
- import {
6
- isReceiptIntegrity,
7
- type ReceiptIntegrityResult,
8
- verifyReceiptIntegrity,
9
- } from "../../domains/dispatch/receipt-integrity.js";
5
+ import { isReceiptIntegrity, verifyReceiptIntegrity } from "../../domains/dispatch/receipt-integrity.js";
10
6
  import type { RunEnvelope, RunReceipt } from "../../domains/dispatch/types.js";
11
7
  import { evidenceDirectory, inspectEvidence, listEvidenceOverviews } from "../../domains/evidence/store.js";
8
+ import { formatTrustSummaryLine } from "../../domains/evidence/trust-projection.js";
9
+ import {
10
+ inspectRunReceiptTrustStatus,
11
+ type ReceiptIntegrityOutcome,
12
+ retiredReceiptIntegrity,
13
+ retiredReceiptIntegrityReason,
14
+ } from "../../domains/evidence/trust-status.js";
12
15
  import type { EvidenceFinding, EvidenceOverview, EvidenceSource } from "../../domains/evidence/types.js";
13
16
  import { type AccountabilitySummary, readAccountabilitySummary } from "../../domains/observability/index.js";
14
17
  import type {
@@ -72,7 +75,7 @@ export interface ViewArtifact {
72
75
  toolName?: string;
73
76
  searchText?: readonly string[];
74
77
  load(): Promise<ViewArtifactLoadResult>;
75
- verify?(): Promise<{ ok: boolean; detail: string }>;
78
+ verify?(): Promise<ViewArtifactVerification>;
76
79
  }
77
80
 
78
81
  export interface ArtifactProvider {
@@ -108,7 +111,14 @@ const ACCOUNTABILITY_TOP_CAUSES = 8;
108
111
  export const VIEW_ARTIFACT_LINE_CAP = 50_000;
109
112
  const JSON_PRETTY_MAX_BYTES = 10 * 1024 * 1024;
110
113
 
111
- export type ReceiptVerifyResult = ReceiptIntegrityResult;
114
+ export type ReceiptVerifyResult = ReceiptIntegrityOutcome;
115
+
116
+ /**
117
+ * What a verify action reports. `retired` marks a seal this build does not
118
+ * verify because its integrity version was retired: not a pass, and not the
119
+ * failure a tampered receipt reports.
120
+ */
121
+ export type ViewArtifactVerification = { ok: true; detail: string } | { ok: false; detail: string; retired?: true };
112
122
 
113
123
  const RECEIPT_REQUIRED_KEYS = [
114
124
  "runId",
@@ -286,6 +296,21 @@ function validateToolStats(value: unknown): ReceiptVerifyResult {
286
296
 
287
297
  type ReadLedgerResult = { ok: true; envelope: RunEnvelope } | { ok: false; reason: string };
288
298
 
299
+ /**
300
+ * The canonical trust line for a receipt the integrity verifier just accepted:
301
+ * the same file and ledger row, projected through the shared boundary so the
302
+ * receipt view spells its verdict exactly as the board and the CLI do.
303
+ */
304
+ export function receiptTrustDetail(stateDir: string, runId: string): string {
305
+ try {
306
+ const receipt = JSON.parse(readFileSync(receiptFilePath(stateDir, runId), "utf8")) as RunReceipt;
307
+ const ledger = readRunEnvelope(stateDir, runId);
308
+ return formatTrustSummaryLine(inspectRunReceiptTrustStatus(receipt, ledger.ok ? ledger.envelope : null).status);
309
+ } catch {
310
+ return "integrity verified; trust status unreadable";
311
+ }
312
+ }
313
+
289
314
  function readRunEnvelope(stateDir: string, runId: string): ReadLedgerResult {
290
315
  const runs = readRunLedger(stateDir);
291
316
  for (const entry of runs) {
@@ -398,6 +423,10 @@ export function verifyReceiptFile(stateDir: string, runId: string): ReceiptVerif
398
423
  if (!isNullableString(r.sessionId)) {
399
424
  return { ok: false, reason: `sessionId invalid: ${String(r.sessionId)}` };
400
425
  }
426
+ // A retired seal is diagnosed ahead of the shape check, which would
427
+ // otherwise report the older version as an invalid integrity block.
428
+ const retired = retiredReceiptIntegrity(r.integrity);
429
+ if (retired !== null) return { ok: false, reason: retiredReceiptIntegrityReason(retired), retired };
401
430
  if (!isReceiptIntegrity(r.integrity)) {
402
431
  return { ok: false, reason: "integrity invalid" };
403
432
  }
@@ -673,8 +702,12 @@ export class ReceiptArtifactProvider implements ArtifactProvider {
673
702
  load: () => loadJsonFileLines(path),
674
703
  verify: async () => {
675
704
  const result = verifyReceiptFile(this.deps.stateDir, env.id);
676
- if (!result.ok) return { ok: false, detail: result.reason };
677
- return { ok: true, detail: "integrity verified" };
705
+ if (!result.ok) {
706
+ return { ok: false, detail: result.reason, ...(result.retired !== undefined ? { retired: true } : {}) };
707
+ }
708
+ // "integrity verified" alone read as a verified result. The
709
+ // canonical line says what the seal proves and what it does not.
710
+ return { ok: true, detail: receiptTrustDetail(this.deps.stateDir, env.id) };
678
711
  },
679
712
  };
680
713
  });
@@ -7,6 +7,7 @@ import {
7
7
  type TUI,
8
8
  truncateToWidth,
9
9
  visibleWidth,
10
+ wrapTextWithAnsi,
10
11
  } from "../../engine/tui.js";
11
12
  import { clockLocal } from "../format-time.js";
12
13
  import { buildHint, showClioOverlayFrame } from "../overlay-frame.js";
@@ -51,7 +52,9 @@ export type ViewVerificationState =
51
52
  | { status: "idle" }
52
53
  | { status: "running" }
53
54
  | { status: "ok"; detail: string }
54
- | { status: "fail"; detail: string };
55
+ | { status: "fail"; detail: string }
56
+ /** A seal this build does not verify because its version was retired: neither a pass nor a tampered-receipt failure. */
57
+ | { status: "retired"; detail: string };
55
58
 
56
59
  export type ViewScrollAction =
57
60
  | "line-up"
@@ -327,6 +330,7 @@ function verificationText(state: ViewVerificationState | undefined): string {
327
330
  if (!state || state.status === "idle") return "";
328
331
  if (state.status === "running") return `${GLYPH.running} verify running`;
329
332
  if (state.status === "ok") return `${GLYPH.ok} verify ok ${state.detail}`;
333
+ if (state.status === "retired") return `${GLYPH.warn} verify retired ${state.detail}`;
330
334
  return `${GLYPH.error} verify fail ${state.detail}`;
331
335
  }
332
336
 
@@ -343,12 +347,43 @@ export function buildArtifactHeader(
343
347
  if (size.length > 0) parts.push(theme.fg("dim", size));
344
348
  const verify = verificationText(verification);
345
349
  if (verify.length > 0) {
346
- const token = verification?.status === "ok" ? "success" : verification?.status === "fail" ? "error" : "info";
350
+ const token =
351
+ verification?.status === "ok"
352
+ ? "success"
353
+ : verification?.status === "fail"
354
+ ? "error"
355
+ : verification?.status === "retired"
356
+ ? "warning"
357
+ : "info";
347
358
  parts.push(theme.fg(token, verify));
348
359
  }
349
360
  return padAnsi(parts.join(" "), width, ELLIPSIS);
350
361
  }
351
362
 
363
+ /** Put verification on wrapped rows so a canonical receipt verdict is never reduced to a prefix. */
364
+ export function buildArtifactHeaderLines(
365
+ artifact: ViewArtifact | undefined,
366
+ verification: ViewVerificationState | undefined,
367
+ width: number,
368
+ ): string[] {
369
+ const metadata = buildArtifactHeader(artifact, undefined, width);
370
+ const verify = verificationText(verification);
371
+ if (artifact === undefined || verify.length === 0) return [metadata];
372
+ const theme = clioTheme();
373
+ const token =
374
+ verification?.status === "ok"
375
+ ? "success"
376
+ : verification?.status === "fail"
377
+ ? "error"
378
+ : verification?.status === "retired"
379
+ ? "warning"
380
+ : "info";
381
+ return [
382
+ metadata,
383
+ ...wrapTextWithAnsi(theme.fg(token, verify), Math.max(1, width)).map((line) => padAnsi(line, width, ELLIPSIS)),
384
+ ];
385
+ }
386
+
352
387
  export function viewFooterHint(focus: ViewPaneFocus, canVerify: boolean, innerWidth?: number): string {
353
388
  // One pane at a time means Tab is how the other one is reached, so the
354
389
  // narrow footer states it. The generic elider drops middle entries, and Tab
@@ -559,8 +594,8 @@ export class ViewOverlayView implements Component {
559
594
  this.ensureContentLoaded(artifact);
560
595
  this.lastContentWidth = width;
561
596
  const verification = artifact ? this.verifications.get(artifactKey(artifact)) : undefined;
562
- const header = buildArtifactHeader(artifact, verification, width);
563
- const bodyHeight = Math.max(0, height - 1);
597
+ const header = buildArtifactHeaderLines(artifact, verification, width);
598
+ const bodyHeight = Math.max(0, height - header.length);
564
599
  this.lastContentBodyHeight = Math.max(1, bodyHeight);
565
600
  const body = this.renderedContentLines(width);
566
601
  const maxOffset = Math.max(0, body.length - Math.max(1, bodyHeight));
@@ -568,7 +603,7 @@ export class ViewOverlayView implements Component {
568
603
  const visible = body
569
604
  .slice(this.contentScrollOffset, this.contentScrollOffset + bodyHeight)
570
605
  .map((line) => padAnsi(line, width, ELLIPSIS));
571
- const lines = [header, ...visible];
606
+ const lines = [...header, ...visible];
572
607
  return this.fixedLines(lines, width, height);
573
608
  }
574
609
 
@@ -689,7 +724,9 @@ export class ViewOverlayView implements Component {
689
724
  .then((result) => {
690
725
  this.verifications.set(
691
726
  key,
692
- result.ok ? { status: "ok", detail: result.detail } : { status: "fail", detail: result.detail },
727
+ result.ok
728
+ ? { status: "ok", detail: result.detail }
729
+ : { status: result.retired === true ? "retired" : "fail", detail: result.detail },
693
730
  );
694
731
  })
695
732
  .catch((err) => {