@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
@@ -29,7 +29,13 @@ import { appendOutOfTurnUsageRow, type OutOfTurnUsageRow } from "../domains/obse
29
29
  import type { PromptsContract } from "../domains/prompts/contract.js";
30
30
  import { toContextOverflowError } from "../domains/providers/errors.js";
31
31
  import type { ProvidersContract } from "../domains/providers/index.js";
32
- import { normalizeCostProvenance, runtimeTargetSnapshot, targetRequiresAuth } from "../domains/providers/index.js";
32
+ import {
33
+ canonicalEndpointKey,
34
+ normalizeCostProvenance,
35
+ registerForegroundStream,
36
+ runtimeTargetSnapshot,
37
+ targetRequiresAuth,
38
+ } from "../domains/providers/index.js";
33
39
  import type { ProtectedArtifactState } from "../domains/safety/protected-artifacts.js";
34
40
  import type { CompactResult } from "../domains/session/compaction/compact.js";
35
41
  import type { ContextSnapshot, ContextUsageSnapshot } from "../domains/session/context-accounting.js";
@@ -42,7 +48,7 @@ import { isRetryableErrorMessage, type RetrySettings } from "../domains/session/
42
48
  import { createEngineAgent } from "../engine/agent.js";
43
49
  import { resolveReservedOutputTokens } from "../engine/apis/output-budget.js";
44
50
  import { cwdHash } from "../engine/session.js";
45
- import type { AgentEvent, AgentMessage, ImageContent } from "../engine/types.js";
51
+ import type { AgentEvent, AgentMessage, ImageContent, Usage } from "../engine/types.js";
46
52
  import { resolveSessionTools } from "../tools/agent-tools.js";
47
53
  import { finalizeAskUserInterviewForHost } from "../tools/ask-user.js";
48
54
  import type { AskUserToolPolicy, ToolInvokeOptions, ToolRegistry } from "../tools/registry.js";
@@ -57,13 +63,15 @@ import {
57
63
  toolSignatureFromState,
58
64
  } from "./chat-loop-messages.js";
59
65
  import { normalizeRetrySettings } from "./chat-loop-policy.js";
60
- import { runHandoffRound } from "./handoff-round.js";
66
+ import { type HandoffRepairInput, runHandoffRound } from "./handoff-round.js";
61
67
  import type { ApprovalRequestView } from "./permission-overlay.js";
62
- import { runSideQuestion, type SideQuestionResult } from "./side-question.js";
68
+ import type { runPrewarmRound } from "./prewarm.js";
69
+ import { runSideQuestion, type SideQuestionResult, sideQuestionUsage } from "./side-question.js";
63
70
  import type { AgentStatusEvent } from "./status/types.js";
64
71
  import { createTurnContext } from "./turn-context.js";
65
72
  import { createTurnMiddleware } from "./turn-middleware.js";
66
73
  import { createTurnPersistence } from "./turn-persistence.js";
74
+ import { createTurnPrewarm, type PrewarmOutcome, subscribePrewarmToCompaction } from "./turn-prewarm.js";
67
75
  import {
68
76
  createTurnQueues,
69
77
  DEFAULT_STEERING_MODE,
@@ -78,13 +86,18 @@ import {
78
86
  rewriteStallAbortMessage,
79
87
  } from "./turn-recovery.js";
80
88
  import { type AssistantDeltaEvent, createTurnRuntime, TurnAdmissionError } from "./turn-runtime.js";
81
- import { type AgentRuntime, type ChatLoopRunSnapshot, createTurnState } from "./turn-state.js";
89
+ import {
90
+ type AgentRuntime,
91
+ type ChatLoopRunSnapshot,
92
+ createTurnState,
93
+ type TurnPreparationPhase,
94
+ } from "./turn-state.js";
82
95
  import { isWorkerShareNote } from "./worker-share.js";
83
96
 
84
97
  export type { QueuedChatMessage, QueuedMessageKind, QueuedMessagesSnapshot, SteeringMode } from "./turn-queues.js";
85
98
  export type { RetryStatusEvent, RetryStatusPayload, RetryStatusPhase } from "./turn-recovery.js";
86
99
  export type { AssistantDeltaEvent } from "./turn-runtime.js";
87
- export type { ChatLoopRunSnapshot } from "./turn-state.js";
100
+ export type { ChatLoopRunSnapshot, TurnPreparationPhase } from "./turn-state.js";
88
101
 
89
102
  export interface QueueUpdateEvent {
90
103
  type: "queue_update";
@@ -182,6 +195,13 @@ export interface ChatSubmitOptions {
182
195
  * for the two states in which it degrades to `next-slot` instead.
183
196
  */
184
197
  steering?: SteeringMode;
198
+ /**
199
+ * Commits the presentation of a consumed prompt after preparation opens and
200
+ * before admission work can make the turn durable. Interactive uses this to
201
+ * guarantee that even a fast probe or prompt compile paints one pending
202
+ * frame instead of opening and closing entirely between renderer ticks.
203
+ */
204
+ onPreparationVisible?: () => Promise<void>;
185
205
  /**
186
206
  * Fires once this submit owns the live turn (`isStreaming()` is true) and
187
207
  * the next queued submit may use streaming queue routing. Stage 0 replay
@@ -232,6 +252,15 @@ export interface SideQuestionOptions {
232
252
  onDelta?: (partialText: string) => void;
233
253
  }
234
254
 
255
+ export interface HandoffRoundOptions extends SideQuestionOptions {
256
+ /**
257
+ * Run the second and last extraction round, quoting the parser's complaint
258
+ * and what the first round returned. Both rounds bill through the same
259
+ * out-of-turn usage store (issue #223).
260
+ */
261
+ repair?: HandoffRepairInput;
262
+ }
263
+
235
264
  /**
236
265
  * How a `/btw` round ended. `refused` is a round that never started (a turn was
237
266
  * in flight, or no orchestrator target is configured); `failed` is a round that
@@ -263,6 +292,15 @@ export interface ChatLoop {
263
292
  getSessionId(): string | null;
264
293
  lastRunSnapshot?(): ChatLoopRunSnapshot | null;
265
294
  isStreaming(): boolean;
295
+ /**
296
+ * Where a consumed prompt currently is between the editor and the stream.
297
+ * The composer and the footer read it so the window in which the prompt has
298
+ * been taken but the turn has not been admitted is never rendered as idle
299
+ * (issue #251).
300
+ */
301
+ turnPreparation(): { phase: TurnPreparationPhase; since: number };
302
+ /** Fires on every preparation transition, including back to `idle`. */
303
+ onTurnPreparation(handler: (phase: TurnPreparationPhase) => void): () => void;
266
304
  contextUsage(): ContextUsageSnapshot;
267
305
  /**
268
306
  * Categorized context-window ledger for the `/context` overlay: where every
@@ -302,7 +340,7 @@ export interface ChatLoop {
302
340
  * caller; this method only owns the provider call. Refused outright while a
303
341
  * turn is in flight rather than queued.
304
342
  */
305
- extractHandoff(goal: string, options?: SideQuestionOptions): Promise<SideQuestionOutcome>;
343
+ extractHandoff(goal: string, options?: HandoffRoundOptions): Promise<SideQuestionOutcome>;
306
344
  /**
307
345
  * Drop or replace the chat-loop's in-memory state after a session switch
308
346
  * (/resume, /fork, /new). `leafTurnId` is the id the next user turn
@@ -310,6 +348,12 @@ export interface ChatLoop {
310
348
  * from the selected session entries; omit it for a fresh session.
311
349
  */
312
350
  resetForSession(leafTurnId: string | null, replayMessages?: ReadonlyArray<AgentMessage>): void;
351
+ /**
352
+ * Resolves once the queued or in-flight session pre-warm has settled, with
353
+ * the outcome, or null when none ran. Diagnostics and contracts only: no
354
+ * turn path waits on a pre-warm.
355
+ */
356
+ whenPrewarmSettled(): Promise<PrewarmOutcome | null>;
313
357
  /** Abort the live agent and release pi-ai session-scoped resources before shutdown. */
314
358
  dispose(): void;
315
359
  /**
@@ -453,6 +497,34 @@ export interface CreateChatLoopDeps {
453
497
  * background registry, which holds exactly the attached calls.
454
498
  */
455
499
  hasAttachedDispatch?: () => boolean;
500
+ /**
501
+ * True while any dispatched worker run is outstanding, attached or detached.
502
+ * The session pre-warm stands down in that state so it never competes for the
503
+ * endpoint a worker is already using. Defaults to `hasAttachedDispatch`, which
504
+ * is the narrower fact a bare composition has.
505
+ */
506
+ hasActiveDispatch?: () => boolean;
507
+ /**
508
+ * True on a surface where a person is about to type the next turn. The
509
+ * orchestrator wires this false for headless `run`: the pre-warm buys latency
510
+ * that an unattended run never spends. Defaults to true.
511
+ */
512
+ isLatencySurface?: () => boolean;
513
+ /** The session pre-warm round. Injectable for the same reason `runSideQuestion` is. */
514
+ runPrewarm?: typeof runPrewarmRound;
515
+ /**
516
+ * Whether a submit aborts the in-flight pre-warm's request or only lets go of
517
+ * it. Defaults to what the measured local backend does with a cancelled
518
+ * request; see `ABORT_ROUND_ON_SUBMIT` in `turn-prewarm.ts`.
519
+ */
520
+ abortPrewarmOnSubmit?: boolean;
521
+ /**
522
+ * Claim one in-flight request on the pre-warm's endpoint for the duration of
523
+ * the round. Wired from the endpoint-capacity registry once #250 lands, so a
524
+ * pre-warm counts against the same per-endpoint bound the orchestrator's
525
+ * streaming turn does; see `registerEndpointSlot` in `turn-prewarm.ts`.
526
+ */
527
+ registerPrewarmEndpointSlot?: (runtime: AgentRuntime) => (() => void) | null;
456
528
  }
457
529
 
458
530
  export function reloadProtectedArtifactsForSession(
@@ -478,6 +550,56 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
478
550
  const state = createTurnState(deps.getSettings().orchestrator.thinkingLevel ?? "off");
479
551
  const toolStartTimes = new Map<string, number>();
480
552
 
553
+ const preparationListeners = new Set<(phase: TurnPreparationPhase) => void>();
554
+ /**
555
+ * Move the consumed prompt to a new preparation phase and tell everything
556
+ * that renders it. Idempotent, so the two compaction sites and the two
557
+ * clearing sites (admission and settlement, because a refusal never reaches
558
+ * admission) can all set the phase they mean without ordering rules.
559
+ */
560
+ const setTurnPreparation = (phase: TurnPreparationPhase): void => {
561
+ if (state.turnPreparation === phase) return;
562
+ // `since` is the age of the whole window, not of its current sub-state:
563
+ // what the operator is judging is how long ago they pressed Enter.
564
+ if (state.turnPreparation === "idle") state.turnPreparationSince = Date.now();
565
+ if (phase === "idle") state.turnPreparationSince = 0;
566
+ state.turnPreparation = phase;
567
+ for (const listener of preparationListeners) {
568
+ try {
569
+ listener(phase);
570
+ } catch {
571
+ // Preparation observers are presentation only and cannot fail a turn.
572
+ }
573
+ }
574
+ };
575
+
576
+ /**
577
+ * Submits currently holding a consumed prompt. The FIFO gate lets a second
578
+ * submit open its window while the first is still settling, so the phase is
579
+ * refcounted rather than owned: the last one out turns the light off.
580
+ */
581
+ let preparingSubmits = 0;
582
+ const enterPreparation = (): void => {
583
+ preparingSubmits += 1;
584
+ // Opening a window never narrows one that is already open: a second submit
585
+ // arriving while the first is compacting must not flip the composer from
586
+ // COMPACTING back to PREPARING while the compaction is still running.
587
+ if (state.turnPreparation === "idle") setTurnPreparation("preparing");
588
+ };
589
+ const leavePreparation = (): void => {
590
+ preparingSubmits = Math.max(0, preparingSubmits - 1);
591
+ if (preparingSubmits === 0) setTurnPreparation("idle");
592
+ };
593
+ /**
594
+ * Return to the plain preparing state after a compaction inside the window.
595
+ * Refcount-aware, because two submits share the window and the slower one's
596
+ * compaction can finish after the window has already closed; restoring
597
+ * `preparing` there would re-open it with nothing left to prepare.
598
+ */
599
+ const endPreparationCompaction = (): void => {
600
+ setTurnPreparation(preparingSubmits > 0 ? "preparing" : "idle");
601
+ };
602
+
481
603
  const emit = (event: ChatLoopEvent): void => {
482
604
  for (const listener of listeners) {
483
605
  listener(event);
@@ -589,7 +711,7 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
589
711
  middlewareToolChoice,
590
712
  consumePersistedEcho: (text) => queues.consumePersistedEcho(text),
591
713
  removeQueuedMirrorEntry: (text) => queues.removeQueuedMirrorEntry(text),
592
- promptCachePayloadForAssistant: (usage) => context.promptCachePayloadForAssistant(usage),
714
+ promptCachePayloadForAssistant: (usage, backend) => context.promptCachePayloadForAssistant(usage, backend),
593
715
  promptSideTokens: () => context.promptSideTokens(),
594
716
  observability: deps.observability,
595
717
  });
@@ -702,6 +824,23 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
702
824
  }
703
825
  };
704
826
 
827
+ /**
828
+ * Run one out-of-turn round while holding a slot on the endpoint it streams
829
+ * to, exactly as the turn and the pre-warm do. A `/btw` or `/handoff` round
830
+ * is a full request against the same inference scheduler, so endpoint
831
+ * capacity (#250) has to count it for as long as it is out, and the
832
+ * background-memory tier has to read that endpoint as busy (#229).
833
+ */
834
+ const withEndpointSlot = async <T>(runtime: AgentRuntime, round: () => Promise<T>): Promise<T> => {
835
+ const endpointKey = canonicalEndpointKey(runtime.runtimeResolution.target);
836
+ const release = endpointKey === null ? () => {} : registerForegroundStream(endpointKey);
837
+ try {
838
+ return await round();
839
+ } finally {
840
+ release();
841
+ }
842
+ };
843
+
705
844
  const writeOutOfTurnUsageRow =
706
845
  deps.recordOutOfTurnUsageRow ?? ((row: OutOfTurnUsageRow): void => appendOutOfTurnUsageRow(clioStateDir(), row));
707
846
 
@@ -764,6 +903,50 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
764
903
  });
765
904
  };
766
905
 
906
+ // A submit owns the turn state machine from the moment it starts running,
907
+ // which is well before `state.streaming` flips: the probe, auto-compaction,
908
+ // and the prompt compile all happen first. The pre-warm reads this flag, not
909
+ // `streaming`, so it can never be the request the operator's turn queues
910
+ // behind.
911
+ let turnActive = false;
912
+
913
+ const prewarm = createTurnPrewarm({
914
+ state,
915
+ getSettings: deps.getSettings,
916
+ providers: deps.providers,
917
+ context,
918
+ bus: deps.bus,
919
+ ...(deps.session ? { session: deps.session } : {}),
920
+ isLatencySurface: () => deps.isLatencySurface?.() !== false,
921
+ isTurnActive: () => turnActive,
922
+ hasActiveDispatch: () => (deps.hasActiveDispatch ?? deps.hasAttachedDispatch)?.() === true,
923
+ prepareRuntime: async (signal) => {
924
+ // The same probe a submit awaits, so the pre-warm resolves the model the
925
+ // next turn will resolve rather than a stale catalog entry.
926
+ await turnRuntime.ensureLiveCapabilitiesForSelectedModel().catch(() => {});
927
+ return prepareOutOfTurnRound("a turn is in flight", signal);
928
+ },
929
+ applySessionTools: (runtime) => {
930
+ runtime.agent.state.tools = resolveSessionTools(
931
+ runtime,
932
+ deps.toolRegistry,
933
+ currentToolInvokeOptions,
934
+ turnRuntime.toolTelemetry,
935
+ );
936
+ },
937
+ recordUsage: (runtime, usage: Usage | null) => {
938
+ recordOutOfTurnUsage(runtime, sideQuestionUsage(usage), "prewarm");
939
+ },
940
+ ...(deps.runPrewarm ? { runPrewarm: deps.runPrewarm } : {}),
941
+ ...(deps.abortPrewarmOnSubmit === undefined ? {} : { abortRoundOnSubmit: deps.abortPrewarmOnSubmit }),
942
+ ...(deps.registerPrewarmEndpointSlot ? { registerEndpointSlot: deps.registerPrewarmEndpointSlot } : {}),
943
+ });
944
+ const unsubscribePrewarmCompaction = subscribePrewarmToCompaction(deps.bus, prewarm);
945
+ // The prompt this process will send is known now: the session prompt compiles
946
+ // against the configured target, and a boot-time resume rebuilds the message
947
+ // array in the same tick, which the scheduler collapses onto one round.
948
+ prewarm.schedule("session-start");
949
+
767
950
  const api: ChatLoop = {
768
951
  steer: (text) => queues.steer(text),
769
952
  queueFollowUp: (text) => queues.queueFollowUp(text),
@@ -813,8 +996,11 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
813
996
  // lands as a user message before the next model turn.
814
997
  // alt+enter (queueFollowUp) keeps the after-this-run intent.
815
998
  if (isWorkerShareNote(trimmed)) state.turnSharedWorkerNote = true;
816
- if (mode === "end-of-turn") queues.queueFollowUp(trimmed);
817
- else queues.steer(trimmed);
999
+ // The queue carries the submitted bytes, not the trimmed copy the
1000
+ // guard above reads: a steer is a model-facing turn and the
1001
+ // payload contract applies to it too (issue #244).
1002
+ if (mode === "end-of-turn") queues.queueFollowUp(text);
1003
+ else queues.steer(text);
818
1004
  return;
819
1005
  }
820
1006
  emitNotice("[Clio Coder] response already in progress. Press Esc to cancel the active run.");
@@ -822,7 +1008,7 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
822
1008
  }
823
1009
  }
824
1010
 
825
- state.lastRunSnapshot = null;
1011
+ const previousRunSnapshot = state.lastRunSnapshot;
826
1012
  let agentRuntime: AgentRuntime | null;
827
1013
  try {
828
1014
  await turnRuntime.ensureLiveCapabilitiesForSelectedModel();
@@ -858,6 +1044,7 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
858
1044
  currentToolInvokeOptions,
859
1045
  turnRuntime.toolTelemetry,
860
1046
  );
1047
+ const toolSignature = toolSignatureFromState(agentRuntime.agent.state.tools);
861
1048
  const askUserPolicy = createAskUserToolPolicy(agentRuntime.agent.state.tools);
862
1049
  // turn_start: the prompt is accepted; registrations may inject
863
1050
  // context for this request. Accumulated reminders (turn_end
@@ -887,9 +1074,12 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
887
1074
  // 2. Pre-submit auto-compaction trigger
888
1075
  const forceNow = process.env.CLIO_CODER_FORCE_COMPACT === "1";
889
1076
  try {
1077
+ setTurnPreparation("compacting");
890
1078
  await context.runAutoCompact(agentRuntime, forceNow, undefined, undefined, submittedText);
891
1079
  } catch (err) {
892
1080
  emitNotice(`[Clio Coder] auto-compaction failed: ${err instanceof Error ? err.message : String(err)}`);
1081
+ } finally {
1082
+ endPreparationCompaction();
893
1083
  }
894
1084
 
895
1085
  // 3. Ensure the session prompt (compiles only on explicit events)
@@ -907,20 +1097,33 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
907
1097
  pendingUserInput: submittedText,
908
1098
  images,
909
1099
  promptHash: compiledPrompt?.systemPromptHash,
910
- toolSignature: toolSignatureFromState(agentRuntime.agent.state.tools),
1100
+ toolSignature,
911
1101
  });
912
1102
 
913
1103
  const reservedOutput = resolveReservedOutputTokens(agentRuntime.runtimeResolution.capabilityDecisions.maxTokens);
914
1104
  const effectiveWindow = agentRuntime.runtimeResolution.contextWindowDetails.effectiveContextWindow;
915
1105
  const pendingInputTokens = ceilChars(submittedText.length);
916
1106
  let turnSnapshot = captureTurnSnapshot("pending");
917
- const totalEstimate = snapshotInputTokens(turnSnapshot) + pendingInputTokens + reservedOutput;
1107
+ // The snapshot prices the prompt at chars/4. When the provider has
1108
+ // already attested a larger prompt for this conversation, that figure
1109
+ // is what the request will actually cost, so the overflow guard reads
1110
+ // the higher of the two (issue #227). Both already carry the pending
1111
+ // user text and the tool schema estimate.
1112
+ const budgetedPromptTokens = (snapshot: ContextSnapshot): number =>
1113
+ Math.max(
1114
+ snapshotInputTokens(snapshot) + pendingInputTokens,
1115
+ context.liveContextEstimate(agentRuntime, submittedText).tokens,
1116
+ );
1117
+ const totalEstimate = budgetedPromptTokens(turnSnapshot) + reservedOutput;
918
1118
 
919
1119
  if (effectiveWindow > 0 && totalEstimate > effectiveWindow) {
920
1120
  emitNotice(
921
- `[Clio Coder] Estimated request size ${totalEstimate} tokens (input ${snapshotInputTokens(turnSnapshot) + pendingInputTokens} + output budget ${reservedOutput}) exceeds the effective context window of ${effectiveWindow} tokens. Running compaction before sending...`,
1121
+ `[Clio Coder] Estimated request size ${totalEstimate} tokens (input ${totalEstimate - reservedOutput} + output budget ${reservedOutput}) exceeds the effective context window of ${effectiveWindow} tokens. Running compaction before sending...`,
922
1122
  );
923
- const compacted = await context.runAutoCompact(agentRuntime, true, undefined, undefined, submittedText);
1123
+ setTurnPreparation("compacting");
1124
+ const compacted = await context
1125
+ .runAutoCompact(agentRuntime, true, undefined, undefined, submittedText)
1126
+ .finally(endPreparationCompaction);
924
1127
  if (!compacted) {
925
1128
  emitNotice(
926
1129
  "[Clio Coder] Compaction could not reclaim enough space. Request blocked; trim the prompt, reduce active tools, or start a fresh session.",
@@ -929,7 +1132,7 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
929
1132
  }
930
1133
  context.refreshAgentMessagesFromSession(agentRuntime);
931
1134
  turnSnapshot = captureTurnSnapshot("pending");
932
- const postTotalEstimate = snapshotInputTokens(turnSnapshot) + pendingInputTokens + reservedOutput;
1135
+ const postTotalEstimate = budgetedPromptTokens(turnSnapshot) + reservedOutput;
933
1136
  if (postTotalEstimate > effectiveWindow) {
934
1137
  emitNotice(
935
1138
  `[Clio Coder] Request still exceeds the effective window after compaction (${postTotalEstimate} > ${effectiveWindow}). Request blocked.`,
@@ -956,6 +1159,16 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
956
1159
  // cancelled run's leftovers, which is the order the ledger has.
957
1160
  if (interrupted) emit({ type: "queued_user_turn", text, kind: "interrupt" });
958
1161
  context.logPromptCompileIfPending();
1162
+ const previousThinkingLevel = previousRunSnapshot?.runtimeResolution?.effectiveThinkingLevel;
1163
+ if (
1164
+ previousThinkingLevel !== undefined &&
1165
+ previousThinkingLevel !== agentRuntime.runtimeResolution.effectiveThinkingLevel
1166
+ ) {
1167
+ context.noteColdReason("thinking_change");
1168
+ }
1169
+ if (typeof previousRunSnapshot?.toolSignature === "string" && previousRunSnapshot.toolSignature !== toolSignature) {
1170
+ context.noteColdReason("tool_surface_change");
1171
+ }
959
1172
  turnSnapshot = { ...turnSnapshot, turnId: userTurnId ?? "unknown" };
960
1173
  context.setCurrentSnapshot(turnSnapshot);
961
1174
  context.persistContextSnapshot(turnSnapshot);
@@ -970,7 +1183,7 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
970
1183
  compiledPromptHash: promptHash,
971
1184
  staticCompositionHash: promptHash,
972
1185
  promptSignature: promptHash,
973
- toolSignature: toolSignatureFromState(agentRuntime.agent.state.tools),
1186
+ toolSignature,
974
1187
  runtimeResolution: runtimeTargetSnapshot(agentRuntime.runtimeResolution),
975
1188
  sessionId: deps.session?.current()?.id ?? null,
976
1189
  cwd: process.cwd(),
@@ -988,6 +1201,8 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
988
1201
 
989
1202
  // 7. Run the prompt, then route the settled state through recovery.
990
1203
  state.streaming = true;
1204
+ const endpointKey = canonicalEndpointKey(agentRuntime.runtimeResolution.target);
1205
+ const releaseForeground = endpointKey === null ? () => {} : registerForegroundStream(endpointKey);
991
1206
  try {
992
1207
  options.onAdmitted?.();
993
1208
  } catch {
@@ -1051,6 +1266,7 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
1051
1266
  }
1052
1267
  await recovery.runCompactAndRetry(agentRuntime, runtimePromptText, overflow, images);
1053
1268
  } finally {
1269
+ releaseForeground();
1054
1270
  if (askUserPolicy) {
1055
1271
  try {
1056
1272
  await finalizeAskUserInterviewForHost(
@@ -1151,9 +1367,21 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
1151
1367
  return state.streaming;
1152
1368
  },
1153
1369
 
1370
+ turnPreparation() {
1371
+ return { phase: state.turnPreparation, since: state.turnPreparationSince };
1372
+ },
1373
+
1374
+ onTurnPreparation(handler) {
1375
+ preparationListeners.add(handler);
1376
+ return () => {
1377
+ preparationListeners.delete(handler);
1378
+ };
1379
+ },
1380
+
1154
1381
  contextUsage: () => context.contextUsage(),
1155
1382
  contextLedger: () => context.contextLedger(),
1156
1383
  whenSettled: () => activeSubmit,
1384
+ whenPrewarmSettled: () => prewarm.settled(),
1157
1385
 
1158
1386
  resetForSession(leafTurnId: string | null, replayMessages?: ReadonlyArray<AgentMessage>): void {
1159
1387
  if (state.runtime) {
@@ -1166,6 +1394,7 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
1166
1394
  middleware.clearPendingReminders();
1167
1395
  middlewareToolChoice.reset();
1168
1396
  state.lastTurnId = leafTurnId;
1397
+ state.lastRunSnapshot = null;
1169
1398
  context.resetForSession();
1170
1399
  // The resumed ledger renders before the first new turn (issue #189),
1171
1400
  // and the window it renders against should be the probed one rather
@@ -1180,11 +1409,17 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
1180
1409
  if (deps.protectedArtifacts) {
1181
1410
  reloadProtectedArtifactsForSession(deps.protectedArtifacts, deps.readSessionEntries);
1182
1411
  }
1412
+ // A resume replays a history the backend has never seen; a fresh session
1413
+ // replays nothing but still compiles a prompt and a tool surface. Both
1414
+ // leave a prefix the next turn will pay for unless it is sent now.
1415
+ prewarm.schedule(replayMessages && replayMessages.length > 0 ? "resume" : "session-start");
1183
1416
  },
1184
1417
 
1185
1418
  dispose(): void {
1186
1419
  unsubscribeConfigReload?.();
1187
1420
  unsubscribeSynthesisLock?.();
1421
+ unsubscribePrewarmCompaction();
1422
+ prewarm.dispose();
1188
1423
  context.dispose();
1189
1424
  if (state.runtime) {
1190
1425
  state.runtime.agent.abort();
@@ -1211,16 +1446,18 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
1211
1446
  if (!prepared.ok) return { status: "refused", reason: prepared.reason };
1212
1447
  let result: SideQuestionResult;
1213
1448
  try {
1214
- result = await sideQuestionRound({
1215
- model: prepared.runtime.agent.state.model,
1216
- // Read-only: runSideQuestion copies before appending its own
1217
- // message, so the live agent's history is untouched.
1218
- messages: prepared.runtime.agent.state.messages,
1219
- question: text,
1220
- ...(prepared.apiKey !== undefined ? { apiKey: prepared.apiKey } : {}),
1221
- ...(options.signal ? { signal: options.signal } : {}),
1222
- ...(options.onDelta ? { onDelta: options.onDelta } : {}),
1223
- });
1449
+ result = await withEndpointSlot(prepared.runtime, () =>
1450
+ sideQuestionRound({
1451
+ model: prepared.runtime.agent.state.model,
1452
+ // Read-only: runSideQuestion copies before appending its own
1453
+ // message, so the live agent's history is untouched.
1454
+ messages: prepared.runtime.agent.state.messages,
1455
+ question: text,
1456
+ ...(prepared.apiKey !== undefined ? { apiKey: prepared.apiKey } : {}),
1457
+ ...(options.signal ? { signal: options.signal } : {}),
1458
+ ...(options.onDelta ? { onDelta: options.onDelta } : {}),
1459
+ }),
1460
+ );
1224
1461
  } catch (err) {
1225
1462
  return { status: "failed", reason: err instanceof Error ? err.message : String(err) };
1226
1463
  }
@@ -1228,7 +1465,7 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
1228
1465
  return result.aborted ? { status: "aborted", text: result.text } : { status: "answered", text: result.text };
1229
1466
  },
1230
1467
 
1231
- async extractHandoff(goal: string, options: SideQuestionOptions = {}): Promise<SideQuestionOutcome> {
1468
+ async extractHandoff(goal: string, options: HandoffRoundOptions = {}): Promise<SideQuestionOutcome> {
1232
1469
  const text = goal.trim();
1233
1470
  if (text.length === 0) {
1234
1471
  return { status: "refused", reason: "a handoff needs a goal" };
@@ -1243,14 +1480,20 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
1243
1480
  if (!prepared.ok) return { status: "refused", reason: prepared.reason };
1244
1481
  let result: SideQuestionResult;
1245
1482
  try {
1246
- result = await handoffRound({
1247
- model: prepared.runtime.agent.state.model,
1248
- // Read-only, exactly as the side-question round treats it.
1249
- messages: prepared.runtime.agent.state.messages,
1250
- goal: text,
1251
- ...(prepared.apiKey !== undefined ? { apiKey: prepared.apiKey } : {}),
1252
- ...(options.signal ? { signal: options.signal } : {}),
1253
- });
1483
+ result = await withEndpointSlot(prepared.runtime, () =>
1484
+ handoffRound({
1485
+ model: prepared.runtime.agent.state.model,
1486
+ // Read-only, exactly as the side-question round treats it.
1487
+ messages: prepared.runtime.agent.state.messages,
1488
+ goal: text,
1489
+ // The runtime id is what decides whether the schema can be bound
1490
+ // on the wire rather than only stated in the prompt.
1491
+ runtimeId: prepared.runtime.runtimeResolution.runtime.id,
1492
+ ...(prepared.apiKey !== undefined ? { apiKey: prepared.apiKey } : {}),
1493
+ ...(options.signal ? { signal: options.signal } : {}),
1494
+ ...(options.repair ? { repair: options.repair } : {}),
1495
+ }),
1496
+ );
1254
1497
  } catch (err) {
1255
1498
  return { status: "failed", reason: err instanceof Error ? err.message : String(err) };
1256
1499
  }
@@ -1295,7 +1538,13 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
1295
1538
  const submitInner = api.submit.bind(api);
1296
1539
  const submitTracked: ChatLoop["submit"] = (text, options) => {
1297
1540
  priorSubmit = activeSubmit;
1298
- const run = submitInner(text, options);
1541
+ // The flag brackets the whole submit, including the early returns an
1542
+ // admission failure takes, so a refused turn does not leave the pre-warm
1543
+ // believing a turn is still running.
1544
+ turnActive = true;
1545
+ const run = submitInner(text, options).finally(() => {
1546
+ turnActive = false;
1547
+ });
1299
1548
  activeSubmit = run.catch(() => {});
1300
1549
  return run;
1301
1550
  };
@@ -1312,6 +1561,11 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
1312
1561
  // released at admission, not settlement, so steering stays immediate.
1313
1562
  let admissionTail: Promise<void> | null = null;
1314
1563
  api.submit = (text, options = {}) => {
1564
+ // The operator owns the slot from the keystroke, not from admission. The
1565
+ // prefix the pre-warm already pushed through stays in it either way, so
1566
+ // aborting here costs nothing and stops the real turn from queueing behind
1567
+ // a request nobody is waiting on.
1568
+ prewarm.cancel();
1315
1569
  const previous = admissionTail;
1316
1570
  let release: () => void = () => {};
1317
1571
  const ticket = new Promise<void>((resolve) => {
@@ -1322,15 +1576,38 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
1322
1576
  release();
1323
1577
  if (admissionTail === ticket) admissionTail = null;
1324
1578
  };
1325
- const start = (): Promise<void> =>
1326
- submitTracked(text, {
1327
- ...options,
1579
+ // The window opens here, not at admission: the caller has already cleared
1580
+ // the editor and painted the prompt, and a submit that queues behind the
1581
+ // FIFO gate is still a prompt Clio is holding. It closes once, at
1582
+ // whichever of admission and settlement comes first, so a refused probe
1583
+ // and a blocked overflow preflight close it as reliably as a turn that
1584
+ // reaches the stream.
1585
+ enterPreparation();
1586
+ let leftPreparation = false;
1587
+ const leaveOnce = (): void => {
1588
+ if (leftPreparation) return;
1589
+ leftPreparation = true;
1590
+ leavePreparation();
1591
+ };
1592
+ const start = async (): Promise<void> => {
1593
+ const { onPreparationVisible, ...submitOptions } = options;
1594
+ try {
1595
+ await onPreparationVisible?.();
1596
+ } catch {
1597
+ // A presentation observer cannot refuse a turn after the editor has
1598
+ // consumed it. The ordinary render path can recover on its next tick.
1599
+ }
1600
+ return submitTracked(text, {
1601
+ ...submitOptions,
1328
1602
  onAdmitted: () => {
1329
1603
  releaseTicket();
1604
+ leaveOnce();
1330
1605
  options.onAdmitted?.();
1331
1606
  },
1332
1607
  }).finally(releaseTicket);
1333
- return previous ? previous.then(start) : start();
1608
+ };
1609
+ const run = previous ? previous.then(start) : start();
1610
+ return run.finally(leaveOnce);
1334
1611
  };
1335
1612
 
1336
1613
  return api;