@iowarp/clio-coder 0.3.7 → 0.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (400) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/README.md +13 -4
  3. package/dist/{acp-SK4MD6MM.js → acp-7LOELQFP.js} +13 -13
  4. package/dist/{agents-2FN2K6ME.js → agents-FIBG2SHA.js} +41 -37
  5. package/dist/assets/codewiki.json +1 -1
  6. package/dist/{auth-QIYZWM5I.js → auth-OI4LIH2I.js} +31 -24
  7. package/dist/builtins-AD25UL3C.js +17 -0
  8. package/dist/{chunk-5FR74PWO.js → chunk-2JDWVJND.js} +2 -2
  9. package/dist/chunk-3DPEIQKN.js +113 -0
  10. package/dist/{chunk-EOOQZZDE.js → chunk-3DUR4WUA.js} +19 -19
  11. package/dist/{chunk-WHJYKASB.js → chunk-3MRC2YSQ.js} +2 -2
  12. package/dist/{chunk-EBEFWSGL.js → chunk-3UUY7R3Z.js} +14 -10
  13. package/dist/{chunk-LADCF22A.js → chunk-3V5AYSEQ.js} +113 -54
  14. package/dist/{chunk-BMWK7ZIZ.js → chunk-465CC7FK.js} +16 -13
  15. package/dist/{chunk-5WIGXA4T.js → chunk-47CMYGET.js} +111 -4
  16. package/dist/{chunk-CEYBNUGC.js → chunk-4H6ULJ3H.js} +378 -36
  17. package/dist/{chunk-YTYFXUI3.js → chunk-4LJX2PUC.js} +9 -9
  18. package/dist/{chunk-DOOEX22V.js → chunk-56KB5IJP.js} +5 -5
  19. package/dist/{chunk-SPULKLCF.js → chunk-5DQRIYDZ.js} +2 -2
  20. package/dist/{chunk-TSHXZTOQ.js → chunk-5HFBWUMU.js} +23 -11
  21. package/dist/{chunk-5UJ6ECTS.js → chunk-5PVQ4SRS.js} +80 -8
  22. package/dist/{chunk-ZWLZP4ZT.js → chunk-5QKCQQ3E.js} +359 -17
  23. package/dist/{chunk-6M7VS3J3.js → chunk-5T7RBWN2.js} +111 -5
  24. package/dist/chunk-774ILSRL.js +172 -0
  25. package/dist/chunk-7C6RYZGQ.js +391 -0
  26. package/dist/{chunk-GH5622CP.js → chunk-A2NJGIB3.js} +2 -2
  27. package/dist/{chunk-C4JBQ5SR.js → chunk-AD7Y7STJ.js} +6 -6
  28. package/dist/{chunk-GEYXPTRF.js → chunk-AEYBF3TB.js} +33 -12
  29. package/dist/{chunk-2SFS6XQE.js → chunk-AMKHQW3C.js} +3 -2
  30. package/dist/{chunk-D4MDIG46.js → chunk-B5CSFE7B.js} +7 -7
  31. package/dist/{chunk-MXI6J5JF.js → chunk-B5XRQOLB.js} +10 -10
  32. package/dist/{chunk-X2KV5FXT.js → chunk-BVDVID7E.js} +2 -2
  33. package/dist/{chunk-JNXPYBB4.js → chunk-CA42X6KT.js} +3 -3
  34. package/dist/{chunk-VREKEFLL.js → chunk-D73KXYPF.js} +3 -3
  35. package/dist/{chunk-JTSEDYVQ.js → chunk-DG4M6ZUE.js} +7 -7
  36. package/dist/{chunk-DQA7QLMD.js → chunk-EBOC7MT3.js} +10 -25
  37. package/dist/{chunk-KZ2H5X4G.js → chunk-ECUO3KDP.js} +129 -14
  38. package/dist/{chunk-JRIO5UD2.js → chunk-EQ63NRB7.js} +5 -5
  39. package/dist/{chunk-YD734TPH.js → chunk-FALJGAWU.js} +2 -2
  40. package/dist/{chunk-GWS3VEIW.js → chunk-FWDFM5ZU.js} +24 -3
  41. package/dist/{chunk-XEGB6BCN.js → chunk-GAYUJ7LE.js} +68 -14
  42. package/dist/{chunk-UND3GU2L.js → chunk-H7IXIC72.js} +2 -2
  43. package/dist/{chunk-IR4CFBFN.js → chunk-HAY4ZE2P.js} +12 -12
  44. package/dist/{chunk-UVDSQ6LW.js → chunk-HCBCAYZU.js} +74 -147
  45. package/dist/{chunk-4DWFMQDR.js → chunk-HJB5IUKP.js} +89 -145
  46. package/dist/{chunk-M4AKACEO.js → chunk-HKO36JWF.js} +33 -5
  47. package/dist/{chunk-KCMKRQX4.js → chunk-HPCTNZM2.js} +45 -82
  48. package/dist/{chunk-465YSENW.js → chunk-IFBNV6H6.js} +3 -3
  49. package/dist/{chunk-FJ3H4MN5.js → chunk-IHKBWSXF.js} +2 -2
  50. package/dist/chunk-JEQQR47K.js +3025 -0
  51. package/dist/{chunk-FO5ZOVUY.js → chunk-KV2AOLDF.js} +27 -7
  52. package/dist/chunk-LU7P4LHA.js +33 -0
  53. package/dist/{chunk-6TUKSZVF.js → chunk-LXPJXFM5.js} +11 -11
  54. package/dist/{chunk-VQNODYQ4.js → chunk-MIX5N5AC.js} +488 -3668
  55. package/dist/chunk-MLOK6ZOS.js +2888 -0
  56. package/dist/{chunk-ZZMN5OM4.js → chunk-MV2VUEJC.js} +2 -2
  57. package/dist/{chunk-MVVUPGPW.js → chunk-MXHC5QYU.js} +6 -6
  58. package/dist/{chunk-OBMAI2DP.js → chunk-N3PBVRTZ.js} +12 -388
  59. package/dist/{chunk-WJHBC77E.js → chunk-N5XKWMDW.js} +17 -7
  60. package/dist/{chunk-5C3AQNDW.js → chunk-NNNWO6F2.js} +124 -36
  61. package/dist/{chunk-UFQ3F4FW.js → chunk-NQ6UCCOD.js} +4 -4
  62. package/dist/chunk-NUGM5KR6.js +165 -0
  63. package/dist/{chunk-DMD2AGVS.js → chunk-NZU6YDNV.js} +20 -18
  64. package/dist/{chunk-WHGPSPT5.js → chunk-O6I4CIEU.js} +151 -13
  65. package/dist/{chunk-XN3L4EYL.js → chunk-OEDBCISO.js} +2 -2
  66. package/dist/{chunk-PD3MESLB.js → chunk-P3JGPQFL.js} +4 -4
  67. package/dist/{chunk-UHXRNZ2J.js → chunk-PNY46YEY.js} +23 -6
  68. package/dist/{chunk-THKY7CD7.js → chunk-PZ4I4JE2.js} +134 -29
  69. package/dist/{chunk-SROCI7ZU.js → chunk-QQ7EKM72.js} +5 -5
  70. package/dist/{chunk-QCTRSGHQ.js → chunk-R7LNVMCS.js} +91 -53
  71. package/dist/{chunk-GOXNB3AO.js → chunk-RAPCMZL4.js} +75 -4
  72. package/dist/chunk-RKKLTLYB.js +45 -0
  73. package/dist/{chunk-OB5HIGJY.js → chunk-RKRLDWD3.js} +4 -1
  74. package/dist/{chunk-DJNLUABN.js → chunk-S4COXYBG.js} +588 -32
  75. package/dist/{chunk-3HAPLH5M.js → chunk-T3Z6VAAF.js} +172 -11
  76. package/dist/{chunk-FOT2FX5J.js → chunk-TD7UE2L5.js} +12 -10
  77. package/dist/{chunk-UUANF5CR.js → chunk-TEO2TLVN.js} +856 -967
  78. package/dist/{chunk-FCSXB6T2.js → chunk-UOSL25KY.js} +14 -2
  79. package/dist/{chunk-EELBMBT6.js → chunk-VKBMFOYV.js} +74 -15
  80. package/dist/chunk-VO2LKSTM.js +165 -0
  81. package/dist/{chunk-5C77SEEY.js → chunk-VPTUJU4P.js} +3 -3
  82. package/dist/{chunk-WEH5XRJQ.js → chunk-WIE7ZOSW.js} +2 -2
  83. package/dist/{chunk-J7PIKKWC.js → chunk-WXCJ7VME.js} +8 -8
  84. package/dist/{chunk-4DGYLA73.js → chunk-XDOQXGFO.js} +22 -7
  85. package/dist/{chunk-PPAMZ32Z.js → chunk-XK56QHLX.js} +6 -1
  86. package/dist/{chunk-AB4XIIVB.js → chunk-YKOFT37S.js} +6 -6
  87. package/dist/chunk-YSEHGPCT.js +127 -0
  88. package/dist/{chunk-HFSBBKSQ.js → chunk-YW7UVM5V.js} +138 -3
  89. package/dist/cli/index.js +32 -32
  90. package/dist/{clio-WBVQEBKO.js → clio-LT5V7SSZ.js} +9 -9
  91. package/dist/{code-nav-FGGFIE7L.js → code-nav-LMW275PA.js} +5 -5
  92. package/dist/codewiki/build-worker.js +4 -4
  93. package/dist/{components-F7OEATSO.js → components-ZFA3SAER.js} +8 -8
  94. package/dist/{config-TRBL3RCF.js → config-RXS5T3JT.js} +98 -65
  95. package/dist/{configure-OLCVPHNM.js → configure-2WYWSCSD.js} +26 -22
  96. package/dist/{context-MJIJ6GOX.js → context-I3BTOTCS.js} +12 -12
  97. package/dist/{context-XEWE3MOJ.js → context-MVOORGMF.js} +54 -47
  98. package/dist/{context-WFPKQSM6.js → context-PALKKQYL.js} +28 -28
  99. package/dist/{context-clear-KNOS2JPB.js → context-clear-N2WOYZ2K.js} +53 -46
  100. package/dist/{context-index-SSR5ECNE.js → context-index-HNG3MOME.js} +6 -6
  101. package/dist/{context-working-set-EUXAZI6N.js → context-working-set-MIEVECVZ.js} +17 -18
  102. package/dist/{dispatch-runner-B7MTOVKL.js → dispatch-runner-VVA4SRRH.js} +90 -61
  103. package/dist/{docs-FLJTIDSE.js → docs-7LQ23DLM.js} +8 -8
  104. package/dist/doctor-TWBWFK5V.js +165 -0
  105. package/dist/eval-IJ5VEZDJ.js +4483 -0
  106. package/dist/{evidence-JZNBUOQZ.js → evidence-L5APPXNV.js} +68 -61
  107. package/dist/{evolve-FJVC4KKI.js → evolve-RGNKFJ52.js} +47 -40
  108. package/dist/{extensions-IQL36S7K.js → extensions-7WYWUX5A.js} +13 -7
  109. package/dist/{fleet-BDKYJFCP.js → fleet-6CNVBZZP.js} +113 -76
  110. package/dist/{fleet-commands-ZFIWZSB3.js → fleet-commands-L2SXSYEI.js} +10 -10
  111. package/dist/{fleet-graph-Y6HPXIVF.js → fleet-graph-2J3OOIPO.js} +17 -15
  112. package/dist/{fleet-preflight-BHSNPBMH.js → fleet-preflight-CZRJ4JP5.js} +5 -6
  113. package/dist/{fleet-validate-BIYREGIK.js → fleet-validate-C5RI6DP7.js} +20 -19
  114. package/dist/{init-LQUB5COQ.js → init-VBN2ACVA.js} +70 -63
  115. package/dist/{library-NJAHIGG4.js → library-JHGUMLY2.js} +22 -20
  116. package/dist/{memory-OG6HOYKM.js → memory-K4OQIYWG.js} +49 -42
  117. package/dist/{models-5ZG5XY7J.js → models-2NCZUWDD.js} +35 -29
  118. package/dist/{monitor-TJ7AMTGB.js → monitor-MMVTJABD.js} +64 -45
  119. package/dist/{orchestrator-WZYB54DM.js → orchestrator-ZKBPCHW6.js} +1971 -520
  120. package/dist/{paths-XUC7GS6E.js → paths-DBXMZMDU.js} +5 -5
  121. package/dist/registry-LG64LTF4.js +11 -0
  122. package/dist/{reset-PXQT45IY.js → reset-DD5JGOY3.js} +11 -11
  123. package/dist/{run-FQ74YF62.js → run-QEGNX7FL.js} +89 -83
  124. package/dist/{share-FW7SVCL3.js → share-JKD3BQMW.js} +20 -18
  125. package/dist/{skills-7E7IRB3R.js → skills-LMQIKDOZ.js} +23 -21
  126. package/dist/{skills-eval-LI75W6OK.js → skills-eval-I7X2774U.js} +59 -52
  127. package/dist/{steer-GGWFUJUD.js → steer-CF5TDANS.js} +3 -3
  128. package/dist/support-I7LOJLIF.js +38 -0
  129. package/dist/{targets-4CIFKCTW.js → targets-RUSR6B5Z.js} +77 -42
  130. package/dist/{terminal-lease-WUZY7ZV5.js → terminal-lease-QYVORFR4.js} +6 -4
  131. package/dist/{trace-PNCASAXC.js → trace-ODOQIVIW.js} +61 -6
  132. package/dist/{uninstall-7FV7IP4E.js → uninstall-ZJF5H5ZN.js} +8 -8
  133. package/dist/{upgrade-K2HVIVMQ.js → upgrade-XANW3FXB.js} +29 -26
  134. package/dist/{usage-GTZELZQX.js → usage-4H7ZRXQT.js} +110 -61
  135. package/dist/{verifiers-RLAHT27O.js → verifiers-UZXNBZEB.js} +13 -13
  136. package/dist/{verify-BX3BRKH5.js → verify-BVKWTNDL.js} +9 -9
  137. package/dist/{wiki-generate-ASIFASCN.js → wiki-generate-MY7WV2QI.js} +76 -69
  138. package/dist/worker/entry.js +69 -66
  139. package/docs/alcf-provider.md +1 -1
  140. package/docs/architecture.md +1 -1
  141. package/docs/artifact-versions.md +11 -5
  142. package/docs/built-in-agents.md +1 -1
  143. package/docs/capacity-and-scheduling.md +23 -2
  144. package/docs/commands-and-modes.md +2 -2
  145. package/docs/configuration-and-targets.md +37 -5
  146. package/docs/context-engine.md +63 -4
  147. package/docs/documentation-coverage.md +3 -3
  148. package/docs/documentation-guide.md +1 -1
  149. package/docs/environment-variables.md +2 -0
  150. package/docs/eval-runner.md +262 -11
  151. package/docs/evals-internal.md +72 -2
  152. package/docs/evidence-and-memory.md +77 -12
  153. package/docs/evolution.md +1 -1
  154. package/docs/extensions-and-sharing.md +3 -1
  155. package/docs/fleet-dispatch.md +34 -9
  156. package/docs/glossary.md +21 -1
  157. package/docs/installation-and-lifecycle.md +1 -1
  158. package/docs/middleware-and-components.md +1 -1
  159. package/docs/model-catalog.md +1 -1
  160. package/docs/observability.md +54 -3
  161. package/docs/proactive-memory.md +127 -14
  162. package/docs/prompt-envelope-and-tools.md +22 -2
  163. package/docs/provider-adapter-cookbook.md +1 -1
  164. package/docs/release-cut-checklist.md +60 -41
  165. package/docs/safety-model.md +1 -1
  166. package/docs/scientific-validation.md +1 -1
  167. package/docs/skills-marketplace.md +1 -1
  168. package/docs/tool-usage.md +1 -1
  169. package/docs/trace-store.md +1 -1
  170. package/docs/troubleshooting.md +87 -0
  171. package/docs/tui-design.md +1 -1
  172. package/docs/worker-dispatch-mechanics.md +1 -1
  173. package/package.json +2 -2
  174. package/src/cli/agents.ts +1 -1
  175. package/src/cli/argv.ts +5 -0
  176. package/src/cli/config-inspect.ts +33 -6
  177. package/src/cli/config.ts +1 -1
  178. package/src/cli/configure.ts +107 -23
  179. package/src/cli/doctor-state-size.ts +82 -0
  180. package/src/cli/doctor.ts +7 -1
  181. package/src/cli/eval.ts +80 -16
  182. package/src/cli/evidence.ts +30 -25
  183. package/src/cli/extensions.ts +5 -1
  184. package/src/cli/fleet-preflight.ts +2 -12
  185. package/src/cli/fleet.ts +32 -3
  186. package/src/cli/shared.ts +1 -0
  187. package/src/cli/targets.ts +45 -11
  188. package/src/cli/trace.ts +63 -4
  189. package/src/cli/usage.ts +63 -14
  190. package/src/cli/validate-model.ts +60 -5
  191. package/src/core/bus-events.ts +54 -1
  192. package/src/core/cache-telemetry.ts +42 -0
  193. package/src/core/commit-attribution.ts +4 -4
  194. package/src/core/config.ts +18 -0
  195. package/src/core/defaults.ts +36 -6
  196. package/src/core/endpoint-key.ts +27 -0
  197. package/src/core/path-boundary.ts +100 -0
  198. package/src/core/residency-target-key.ts +25 -0
  199. package/src/core/response-schema.ts +36 -2
  200. package/src/domains/agents/extension.ts +2 -11
  201. package/src/domains/agents/fleet-contract.ts +30 -12
  202. package/src/domains/agents/recipe.ts +7 -1
  203. package/src/domains/agents/registry.ts +73 -5
  204. package/src/domains/agents/result-contract.ts +128 -17
  205. package/src/domains/agents/write-boundary.ts +15 -50
  206. package/src/domains/config/classify.ts +3 -0
  207. package/src/domains/context/codewiki/coordinator.ts +12 -4
  208. package/src/domains/context/project-rules.ts +51 -1
  209. package/src/domains/dispatch/admission.ts +40 -3
  210. package/src/domains/dispatch/assignment-reconcile.ts +22 -5
  211. package/src/domains/dispatch/assignment-store.ts +151 -14
  212. package/src/domains/dispatch/capacity-lease.ts +98 -9
  213. package/src/domains/dispatch/contract.ts +26 -1
  214. package/src/domains/dispatch/delegation-plan.ts +2 -5
  215. package/src/domains/dispatch/execution-plan.ts +44 -4
  216. package/src/domains/dispatch/execution-role.ts +9 -1
  217. package/src/domains/dispatch/extension.ts +309 -96
  218. package/src/domains/dispatch/fleet-run.ts +78 -4
  219. package/src/domains/dispatch/gate-role-prompts.ts +38 -0
  220. package/src/domains/dispatch/heartbeat.ts +32 -8
  221. package/src/domains/dispatch/index.ts +6 -1
  222. package/src/domains/dispatch/intent-requirements.ts +40 -0
  223. package/src/domains/dispatch/intent.ts +84 -8
  224. package/src/domains/dispatch/orphan-recovery.ts +5 -0
  225. package/src/domains/dispatch/path-scope.ts +370 -0
  226. package/src/domains/dispatch/receipt-integrity.ts +2 -1
  227. package/src/domains/dispatch/reservation-store.ts +116 -8
  228. package/src/domains/dispatch/state.ts +4 -0
  229. package/src/domains/dispatch/types.ts +14 -7
  230. package/src/domains/dispatch/validation.ts +6 -3
  231. package/src/domains/dispatch/worker-spawn.ts +25 -11
  232. package/src/domains/dispatch/write-boundary-enforcer.ts +62 -0
  233. package/src/domains/dispatch/write-boundary.ts +262 -22
  234. package/src/domains/eval/artifacts/store.ts +62 -0
  235. package/src/domains/eval/compare/behavioral.ts +224 -0
  236. package/src/domains/eval/compare/compare.ts +355 -2
  237. package/src/domains/eval/compare/envelope.ts +128 -0
  238. package/src/domains/eval/compare/gates.ts +24 -6
  239. package/src/domains/eval/compare/thresholds.ts +30 -3
  240. package/src/domains/eval/execution-provenance.ts +240 -0
  241. package/src/domains/eval/metrics/aggregate.ts +136 -0
  242. package/src/domains/eval/metrics/call-ledger-stream.ts +112 -0
  243. package/src/domains/eval/metrics/evidence.ts +79 -2
  244. package/src/domains/eval/metrics/tracked.ts +413 -0
  245. package/src/domains/eval/provenance.ts +117 -0
  246. package/src/domains/eval/reports/comparison.ts +128 -0
  247. package/src/domains/eval/reports/junit.ts +17 -3
  248. package/src/domains/eval/reports/markdown.ts +3 -3
  249. package/src/domains/eval/reports/text.ts +14 -0
  250. package/src/domains/eval/run-compare.ts +20 -0
  251. package/src/domains/eval/runners/clio-run.ts +139 -2
  252. package/src/domains/eval/runners/external-command.ts +28 -3
  253. package/src/domains/eval/schema/adapter.ts +111 -0
  254. package/src/domains/eval/schema/artifact.ts +20 -0
  255. package/src/domains/eval/schema/behavioral-metrics.ts +204 -0
  256. package/src/domains/eval/schema/behavioral.ts +520 -0
  257. package/src/domains/eval/schema/execution-envelope.ts +194 -0
  258. package/src/domains/eval/schema/serving.ts +74 -0
  259. package/src/domains/eval/schema/suite.ts +38 -8
  260. package/src/domains/eval/schema/validate.ts +58 -3
  261. package/src/domains/eval/schema/verdict.ts +237 -0
  262. package/src/domains/eval/suites/resolve.ts +2 -0
  263. package/src/domains/eval/suites/run.ts +264 -33
  264. package/src/domains/eval/verifiers/command.ts +2 -1
  265. package/src/domains/eval/workspaces/temp-copy.ts +145 -13
  266. package/src/domains/evidence/build.ts +68 -21
  267. package/src/domains/evidence/eval.ts +2 -12
  268. package/src/domains/evidence/findings-markdown.ts +33 -0
  269. package/src/domains/evidence/index.ts +21 -0
  270. package/src/domains/evidence/provenance.ts +46 -11
  271. package/src/domains/evidence/run-trust.ts +7 -113
  272. package/src/domains/evidence/trust-projection.ts +274 -0
  273. package/src/domains/evidence/trust-status.ts +145 -17
  274. package/src/domains/evidence/types.ts +4 -0
  275. package/src/domains/extensions/compatibility.ts +285 -0
  276. package/src/domains/extensions/discovery.ts +126 -4
  277. package/src/domains/extensions/resources.ts +21 -9
  278. package/src/domains/extensions/state.ts +18 -5
  279. package/src/domains/extensions/types.ts +6 -1
  280. package/src/domains/lifecycle/doctor.ts +209 -2
  281. package/src/domains/memory/index.ts +14 -0
  282. package/src/domains/memory/task-bank-promotion.ts +64 -0
  283. package/src/domains/memory/task-memory-policy.ts +77 -8
  284. package/src/domains/memory/task-memory-spend.ts +131 -0
  285. package/src/domains/memory/task-memory-status.ts +7 -0
  286. package/src/domains/memory/task-memory-telemetry.ts +2 -0
  287. package/src/domains/middleware/index.ts +1 -0
  288. package/src/domains/middleware/memory-intervention.ts +69 -5
  289. package/src/domains/middleware/memory-step-endpoint.ts +71 -0
  290. package/src/domains/observability/background-memory-usage.ts +140 -0
  291. package/src/domains/observability/cost.ts +1 -1
  292. package/src/domains/observability/index.ts +7 -0
  293. package/src/domains/observability/out-of-turn-usage.ts +51 -2
  294. package/src/domains/observability/trace-store.ts +192 -2
  295. package/src/domains/prompts/compiler.ts +100 -13
  296. package/src/domains/prompts/contract.ts +3 -5
  297. package/src/domains/providers/endpoint-capacity.ts +96 -0
  298. package/src/domains/providers/extension.ts +30 -2
  299. package/src/domains/providers/index.ts +10 -0
  300. package/src/domains/providers/models/local-models/clio-local-coding-targets.yaml +243 -1
  301. package/src/domains/providers/runtime-resolution.ts +8 -1
  302. package/src/domains/providers/runtimes/common/probe-helpers.ts +31 -9
  303. package/src/domains/providers/runtimes/local-native/llamacpp-anthropic.ts +1 -1
  304. package/src/domains/providers/runtimes/local-native/llamacpp-completion.ts +1 -1
  305. package/src/domains/providers/runtimes/local-native/llamacpp-embed.ts +1 -1
  306. package/src/domains/providers/runtimes/local-native/llamacpp-rerank.ts +1 -1
  307. package/src/domains/providers/runtimes/local-native/llamacpp.ts +4 -1
  308. package/src/domains/providers/runtimes/local-native/lmstudio.ts +4 -1
  309. package/src/domains/providers/runtimes/local-native/ollama-native.ts +6 -1
  310. package/src/domains/providers/types/capability-flags.ts +2 -0
  311. package/src/domains/providers/types/target-descriptor.ts +2 -0
  312. package/src/domains/resources/common-loader.ts +3 -0
  313. package/src/domains/resources/prompts/loader.ts +184 -17
  314. package/src/domains/safety/call-target.ts +52 -0
  315. package/src/domains/safety/policy-engine.ts +5 -5
  316. package/src/domains/safety/run-effects.ts +96 -2
  317. package/src/domains/safety/scope.ts +7 -12
  318. package/src/domains/session/context-accounting.ts +52 -1
  319. package/src/domains/session/context-ledger.ts +37 -13
  320. package/src/domains/session/index.ts +6 -0
  321. package/src/domains/session/prompt-cache.ts +140 -0
  322. package/src/domains/session/prompt-manifest.ts +42 -0
  323. package/src/engine/acp/adapter.ts +18 -3
  324. package/src/engine/acp/server.ts +4 -1
  325. package/src/engine/ai.ts +35 -0
  326. package/src/engine/apis/llamacpp-residency.ts +55 -3
  327. package/src/engine/apis/lmstudio.ts +25 -5
  328. package/src/engine/apis/ollama-native.ts +2 -1
  329. package/src/engine/apis/openai-completions.ts +80 -17
  330. package/src/engine/apis/residency-lock.ts +3 -1
  331. package/src/engine/apis/residency.ts +34 -1
  332. package/src/engine/prompt-templates.ts +18 -1
  333. package/src/engine/provider-payload.ts +29 -1
  334. package/src/engine/worker-runtime.ts +6 -3
  335. package/src/entry/orchestrator.ts +176 -30
  336. package/src/interactive/chat-loop-messages.ts +26 -7
  337. package/src/interactive/chat-loop.ts +318 -41
  338. package/src/interactive/chat-panel.ts +62 -8
  339. package/src/interactive/clio-editor.ts +45 -8
  340. package/src/interactive/context-activity.ts +5 -1
  341. package/src/interactive/context-meter.ts +1 -1
  342. package/src/interactive/context-overlay.ts +40 -10
  343. package/src/interactive/cost-overlay.ts +64 -6
  344. package/src/interactive/dispatch-board.ts +127 -5
  345. package/src/interactive/fleet-run-preview.ts +41 -15
  346. package/src/interactive/handoff-round.ts +41 -2
  347. package/src/interactive/interactive-application.ts +24 -1
  348. package/src/interactive/interactive-event-projection.ts +14 -0
  349. package/src/interactive/interactive-input-runtime.ts +8 -0
  350. package/src/interactive/interactive-presentation.ts +4 -0
  351. package/src/interactive/interactive-shell.ts +20 -17
  352. package/src/interactive/interactive-slash-runtime.ts +27 -4
  353. package/src/interactive/memory-overlay.ts +8 -0
  354. package/src/interactive/mutation-preview.ts +295 -0
  355. package/src/interactive/overlay-general-openers.ts +16 -0
  356. package/src/interactive/overlay-key-routing.ts +38 -0
  357. package/src/interactive/overlay-lifecycle.ts +38 -5
  358. package/src/interactive/overlay-permission-lifecycle.ts +22 -2
  359. package/src/interactive/overlay-session-lifecycle.ts +73 -9
  360. package/src/interactive/overlays/ask-user.ts +91 -19
  361. package/src/interactive/overlays/help-reference.ts +4 -0
  362. package/src/interactive/overlays/prompts.ts +11 -1
  363. package/src/interactive/overlays/settings.ts +176 -48
  364. package/src/interactive/permission-hint.ts +34 -2
  365. package/src/interactive/permission-overlay.ts +159 -9
  366. package/src/interactive/prewarm.ts +197 -0
  367. package/src/interactive/render-trace.ts +162 -15
  368. package/src/interactive/renderers/tool-execution.ts +4 -0
  369. package/src/interactive/side-question.ts +58 -1
  370. package/src/interactive/slash-commands.ts +7 -2
  371. package/src/interactive/status/controller.ts +11 -0
  372. package/src/interactive/status/state-machine.ts +54 -2
  373. package/src/interactive/status/types.ts +7 -0
  374. package/src/interactive/terminal-lease.ts +2 -0
  375. package/src/interactive/turn-context.ts +299 -31
  376. package/src/interactive/turn-persistence.ts +14 -4
  377. package/src/interactive/turn-prewarm.ts +364 -0
  378. package/src/interactive/turn-queues.ts +7 -4
  379. package/src/interactive/turn-runtime.ts +8 -1
  380. package/src/interactive/turn-state.ts +23 -0
  381. package/src/interactive/view/artifacts.ts +42 -9
  382. package/src/interactive/view/view-overlay.ts +43 -6
  383. package/src/interactive/worker-receipts.ts +14 -2
  384. package/src/interactive/worker-stream.ts +8 -0
  385. package/src/tools/ask-user.ts +43 -2
  386. package/src/tools/dispatch-admission.ts +12 -13
  387. package/src/tools/dispatch-arguments.ts +27 -0
  388. package/src/tools/dispatch-plan.ts +46 -9
  389. package/src/tools/dispatch-runner.ts +48 -13
  390. package/src/tools/dispatch-scout.ts +1 -1
  391. package/src/tools/monitor.ts +13 -0
  392. package/src/tools/registry.ts +16 -0
  393. package/src/tools/worker-evidence.ts +19 -13
  394. package/src/worker/spec-contract.ts +2 -1
  395. package/dist/chunk-AOCYTWAV.js +0 -449
  396. package/dist/chunk-HWUFFB6L.js +0 -83
  397. package/dist/chunk-R346GLFC.js +0 -31
  398. package/dist/chunk-ZGH7FGS5.js +0 -1079
  399. package/dist/doctor-RN4YKO2X.js +0 -87
  400. package/dist/eval-RUBJVSNQ.js +0 -2557
@@ -1,5 +1,19 @@
1
1
  import type { HintEntry } from "./overlay-frame.js";
2
2
 
3
+ /**
4
+ * The key that opens and closes local inspection of a parked mutation. It lives
5
+ * here rather than beside the preview builder so the composer rail can name it
6
+ * without pulling the diff machinery into the Stage 0 boot shell closure.
7
+ */
8
+ export const MUTATION_PREVIEW_KEY = "v";
9
+
10
+ /**
11
+ * Whether this card has a mutation to read, and whether it is open.
12
+ * `none` covers every ask with nothing local to inspect, including a worker
13
+ * escalation, whose arguments never leave the worker.
14
+ */
15
+ export type PermissionInspectionHint = "none" | "closed" | "open";
16
+
3
17
  /**
4
18
  * The keys that answer a parked call, as data, so the dialog footer and the
5
19
  * composer rail render the same policy at any width.
@@ -16,17 +30,35 @@ import type { HintEntry } from "./overlay-frame.js";
16
30
  *
17
31
  * Esc says `deny`, not `close`: closing the dialog denies the call, and on the
18
32
  * one surface where a misread is a wrong decision about a tool call the key
19
- * names its effect.
33
+ * names its effect. It keeps that meaning while the mutation is open, so the
34
+ * inspect key is what puts the mutation away again.
20
35
  *
21
36
  * With a draft in the composer, Enter is inert (issue #186): the habitual send
22
37
  * key must not resolve toward "allow" on a safety rail. The entry then says so
23
38
  * and names the key that clears the draft, which the router passes through.
39
+ *
40
+ * A parked `write` or `edit` adds the inspect key (issue #254). It is
41
+ * droppable, and deliberately so: at 40 columns the four entries do not fit,
42
+ * and the three keys that answer the call outrank the one that reads it. The
43
+ * key keeps working at every width; the `/help` Autonomy & safety net topic is
44
+ * where it is documented for the widths that cannot show it.
24
45
  */
25
- export function permissionHintEntries(composerHasDraft = false): HintEntry[] {
46
+ export function permissionHintEntries(
47
+ composerHasDraft = false,
48
+ inspection: PermissionInspectionHint = "none",
49
+ ): HintEntry[] {
26
50
  return [
27
51
  composerHasDraft
28
52
  ? { key: "Backspace", verb: "clear the draft to allow", short: "clear draft", critical: true }
29
53
  : { key: "Enter", verb: "allow once", short: "allow", critical: true },
54
+ ...(inspection === "none"
55
+ ? []
56
+ : inspection === "open"
57
+ ? [
58
+ { key: "↑↓", verb: "scroll", critical: false },
59
+ { key: MUTATION_PREVIEW_KEY, verb: "hide mutation", short: "hide", critical: false },
60
+ ]
61
+ : [{ key: MUTATION_PREVIEW_KEY, verb: "inspect mutation", short: "inspect", critical: false }]),
30
62
  { key: "s", verb: "stop turn", short: "stop", critical: true },
31
63
  // With a draft, deny and stop are the only immediate answers, so Esc
32
64
  // outranks the narrowing that would otherwise drop it first.
@@ -6,13 +6,21 @@ import {
6
6
  decisionFactsForPermission,
7
7
  } from "../domains/safety/decision-presentation.js";
8
8
  import type { Component, OverlayOptions, TUI } from "../engine/tui.js";
9
+ import {
10
+ MUTATION_PREVIEW_VISIBLE_ROWS,
11
+ type MutationFacts,
12
+ type MutationInspector,
13
+ type MutationPreview,
14
+ mutationFactsLine,
15
+ mutationPreviewWindow,
16
+ } from "./mutation-preview.js";
9
17
  import { fitHintEntries } from "./overlay-frame.js";
10
- import { permissionHintEntries } from "./permission-hint.js";
18
+ import { MUTATION_PREVIEW_KEY, type PermissionInspectionHint, permissionHintEntries } from "./permission-hint.js";
11
19
  import type { ClioToken } from "./theme/index.js";
12
20
 
13
21
  export { type AskAxis, askAxis } from "../domains/safety/approval-axis.js";
14
22
  export { describeCallTarget, sanitizeCallTargetText } from "../domains/safety/call-target.js";
15
- export { permissionHintEntries } from "./permission-hint.js";
23
+ export { MUTATION_PREVIEW_KEY, type PermissionInspectionHint, permissionHintEntries } from "./permission-hint.js";
16
24
 
17
25
  export interface ApprovalRequestView {
18
26
  requestId: string;
@@ -34,6 +42,14 @@ export interface ApprovalRequestView {
34
42
  * when nothing meaningful is derivable.
35
43
  */
36
44
  target?: string;
45
+ /**
46
+ * Size and digest facts for a parked `write` or `edit`, and nothing else
47
+ * about it. The mutation text is deliberately absent: this view is what
48
+ * reaches the transcript row, the parked notice, and the approval-state
49
+ * event, so anything on it has already left the overlay. The text itself is
50
+ * held by the inspector the overlay opener is given (issue #254).
51
+ */
52
+ mutation?: MutationFacts;
37
53
  queueDepth?: number;
38
54
  }
39
55
 
@@ -79,11 +95,59 @@ export function permissionOverlayPlacement(
79
95
  };
80
96
  }
81
97
 
82
- class PermissionOverlayBody implements Component {
83
- constructor(private readonly view: ApprovalRequestView) {}
98
+ /**
99
+ * The overlay body, plus the inspection state the key router drives. The
100
+ * preview is built on the first toggle rather than per frame, so the file read
101
+ * and the diff happen once when the operator asks for them.
102
+ */
103
+ export interface PermissionOverlayBodyHandle extends Component {
104
+ /** Whether this card has a mutation the operator can read locally. */
105
+ canInspect(): boolean;
106
+ isInspecting(): boolean;
107
+ toggleInspect(): void;
108
+ scrollInspect(delta: number): void;
109
+ }
110
+
111
+ class PermissionOverlayBody implements PermissionOverlayBodyHandle {
112
+ private preview: MutationPreview | null = null;
113
+ private scroll = 0;
114
+ private lastLineCount = 0;
115
+
116
+ constructor(
117
+ private readonly view: ApprovalRequestView,
118
+ private readonly inspect?: MutationInspector,
119
+ ) {}
120
+
121
+ canInspect(): boolean {
122
+ return this.inspect !== undefined;
123
+ }
124
+
125
+ isInspecting(): boolean {
126
+ return this.preview !== null;
127
+ }
128
+
129
+ toggleInspect(): void {
130
+ if (this.inspect === undefined) return;
131
+ if (this.preview !== null) {
132
+ this.preview = null;
133
+ this.scroll = 0;
134
+ return;
135
+ }
136
+ this.preview = this.inspect();
137
+ this.scroll = 0;
138
+ }
139
+
140
+ scrollInspect(delta: number): void {
141
+ if (this.preview === null) return;
142
+ const maxScroll = Math.max(0, this.lastLineCount - MUTATION_PREVIEW_VISIBLE_ROWS);
143
+ this.scroll = Math.max(0, Math.min(this.scroll + delta, maxScroll));
144
+ }
84
145
 
85
146
  render(width: number): string[] {
86
- return permissionOverlayLines(this.view, width);
147
+ if (this.preview === null) return permissionOverlayLines(this.view, width);
148
+ const rendered = permissionInspectionLines(this.view, this.preview, width, this.scroll);
149
+ this.lastLineCount = rendered.wrappedLineCount;
150
+ return rendered.lines;
87
151
  }
88
152
 
89
153
  invalidate(): void {}
@@ -167,14 +231,74 @@ export function permissionOverlayTone(view: ApprovalRequestView): ClioToken {
167
231
  * three columns the bottom border spends on `─ ` and the trailing space come
168
232
  * off the top, which is what `buildResponsiveHint` does for every other frame.
169
233
  */
170
- export function permissionOverlayHint(innerWidth: number, composerHasDraft = false): string {
171
- return fitHintEntries(permissionHintEntries(composerHasDraft), innerWidth - 3);
234
+ export function permissionOverlayHint(
235
+ innerWidth: number,
236
+ composerHasDraft = false,
237
+ inspection: PermissionInspectionHint = "none",
238
+ ): string {
239
+ return fitHintEntries(permissionHintEntries(composerHasDraft, inspection), innerWidth - 3);
240
+ }
241
+
242
+ /** The tools whose parked call mutates a file, so a card without a preview owes the operator a reason. */
243
+ function isFileMutationTool(tool: string): boolean {
244
+ return tool === "write" || tool === "edit";
172
245
  }
173
246
 
174
247
  function actionConsequence(presentation: DecisionPresentation, id: "deny" | "stop"): string {
175
248
  return presentation.requiredActions.find((action) => action.id === id)?.consequence ?? "";
176
249
  }
177
250
 
251
+ /**
252
+ * Hard wrap for mutation text. Prose wraps on words; a proposed file line and a
253
+ * diff row do not, because the column a character sits in is part of what the
254
+ * operator is reading. The continuation marker says the row was folded rather
255
+ * than cut, which is the difference between a wrap and unmarked truncation.
256
+ */
257
+ function wrapMutationLine(value: string, max: number): string[] {
258
+ if (max <= 1) return [value];
259
+ if (value.length <= max) return [value];
260
+ const lines: string[] = [];
261
+ let remaining = value;
262
+ while (remaining.length > max) {
263
+ lines.push(`${remaining.slice(0, max - 1)}↩`);
264
+ remaining = remaining.slice(max - 1);
265
+ }
266
+ lines.push(remaining);
267
+ return lines;
268
+ }
269
+
270
+ /**
271
+ * The card with the mutation open. The tool, target, and digest facts stay at
272
+ * the top so the bytes on screen remain tied to the call they belong to, and
273
+ * the standing safety prose gives way to the mutation itself: the operator
274
+ * opened this to read what changes, and the keys that answer the ask are on the
275
+ * footer throughout.
276
+ */
277
+ function permissionInspectionLines(
278
+ view: ApprovalRequestView,
279
+ preview: MutationPreview,
280
+ width: number,
281
+ scroll: number,
282
+ ): { lines: string[]; wrappedLineCount: number } {
283
+ const content = Math.max(8, Math.floor(width));
284
+ const wrapped = preview.body.flatMap((line) => wrapMutationLine(line, content));
285
+ const pane = mutationPreviewWindow(wrapped, scroll);
286
+ const notes: string[] = [];
287
+ if (preview.neutralized) notes.push("control characters shown as ·");
288
+ if (preview.tabsExpanded) notes.push("tabs shown as spaces");
289
+ const lines = [
290
+ field("Tool: ", `${view.tool} · Action: ${view.actionClass}`, content),
291
+ ...(view.target !== undefined && view.target.length > 0 ? [field("Target: ", view.target, content)] : []),
292
+ ...wrapSentence(`Mutation: ${mutationFactsLine(preview.facts)}`, content),
293
+ "",
294
+ ...wrapSentence(preview.heading, content),
295
+ ...pane.window,
296
+ ...(pane.position === null ? [] : [pane.position]),
297
+ ...(notes.length > 0 ? [`Rendered safely: ${notes.join(", ")}.`] : []),
298
+ ];
299
+ return { lines, wrappedLineCount: wrapped.length };
300
+ }
301
+
178
302
  /**
179
303
  * The overlay's body, laid out for the width the frame gives it.
180
304
  *
@@ -193,6 +317,24 @@ export function permissionOverlayLines(view: ApprovalRequestView, width: number)
193
317
  const lines = [
194
318
  field("Tool: ", `${view.tool} · Action: ${view.actionClass}`, content),
195
319
  ...(view.target !== undefined && view.target.length > 0 ? [field("Target: ", view.target, content)] : []),
320
+ // Size and digest stay on the collapsed card whether or not the operator
321
+ // opens the mutation, so the decision always carries the identity of the
322
+ // bytes it applies to.
323
+ // Wrapped, never ellipsized: at 40 columns the digest is the tail of the
324
+ // line, and ellipsizing it would drop the one fact that ties the decision
325
+ // to the bytes it applies to.
326
+ ...(view.mutation !== undefined
327
+ ? [
328
+ ...wrapSentence(`Mutation: ${mutationFactsLine(view.mutation)}`, content),
329
+ ...wrapSentence(`Press ${MUTATION_PREVIEW_KEY} to read it before deciding.`, content),
330
+ ]
331
+ : []),
332
+ ...(view.mutation === undefined && view.origin.kind === "worker" && isFileMutationTool(view.tool)
333
+ ? wrapSentence(
334
+ "No local preview: this call's arguments stay inside the worker, so you are approving the target above without inspecting the mutation.",
335
+ content,
336
+ )
337
+ : []),
196
338
  ...wrapSentence(`Requested by: ${presentation.requestedByCopy}`, content),
197
339
  ...(view.artifact !== undefined
198
340
  ? [
@@ -215,6 +357,14 @@ export function permissionOverlayLines(view: ApprovalRequestView, width: number)
215
357
  return lines;
216
358
  }
217
359
 
218
- export function createPermissionOverlayBody(view: ApprovalRequestView): Component {
219
- return new PermissionOverlayBody(view);
360
+ /**
361
+ * The overlay body for one parked call. The inspector, when present, is the
362
+ * only path the mutation text takes: it is never placed on `view`, so nothing
363
+ * that renders or forwards the view can carry the bytes with it.
364
+ */
365
+ export function createPermissionOverlayBody(
366
+ view: ApprovalRequestView,
367
+ inspect?: MutationInspector,
368
+ ): PermissionOverlayBodyHandle {
369
+ return new PermissionOverlayBody(view, inspect);
220
370
  }
@@ -0,0 +1,197 @@
1
+ /**
2
+ * The session pre-warm round: the request the next turn would send, minus the
3
+ * operator's text, issued as soon as the prefix is known.
4
+ *
5
+ * On a local server prefill is the cost. A fresh session's first turn prefills
6
+ * the whole compiled prompt plus the tool schemas before the model emits a
7
+ * token, and a resumed session's first turn prefills the entire replayed
8
+ * history. Both are paid after the operator presses Enter, and both are fully
9
+ * determined before they type anything. llama.cpp picks the slot with the
10
+ * longest common prefix and re-evaluates only the suffix, so sending the prefix
11
+ * early leaves the processed KV in the slot for the real turn to land on.
12
+ *
13
+ * The payload is not hand-assembled. It goes through `streamSimple`, the same
14
+ * dispatcher `createEngineAgent` hands pi-agent-core as its `streamFn`, with the
15
+ * same system prompt, the same tool schemas, the same replayed messages, and the
16
+ * same thinking level, so `withSamplingOverrides`, the thinking composition, and
17
+ * `cache_prompt` resolve exactly as they do for a turn. Any byte that differs
18
+ * ahead of the user turn would defeat the purpose.
19
+ */
20
+
21
+ import type { BackendCompletionTimings } from "../core/cache-telemetry.js";
22
+ import { streamSimple } from "../engine/ai.js";
23
+ import type { AgentMessage, AgentTool, EngineModel, Usage } from "../engine/types.js";
24
+
25
+ /**
26
+ * The one user message appended so the chat template renders the prefix up to
27
+ * the user turn. One character: it has to exist for the template to close the
28
+ * prefix, and everything after the last shared byte is re-evaluated by the real
29
+ * turn anyway.
30
+ */
31
+ export const PREWARM_USER_TEXT = ".";
32
+
33
+ /** Output budget for the round. The tokens the model produces are not the point. */
34
+ export const PREWARM_MAX_TOKENS = 1;
35
+
36
+ /** Which trigger asked for the pre-warm. Recorded on the ledger entry. */
37
+ export type PrewarmTrigger = "session-start" | "resume" | "compaction";
38
+
39
+ export interface PrewarmContext {
40
+ systemPrompt: string;
41
+ messages: ReadonlyArray<AgentMessage>;
42
+ tools: ReadonlyArray<AgentTool>;
43
+ }
44
+
45
+ /**
46
+ * The roles pi-agent-core's default `convertToLlm` keeps when it turns the
47
+ * agent's message list into provider messages. The pre-warm applies the same
48
+ * filter so its message array is the one a turn would send, not the agent's
49
+ * internal list.
50
+ */
51
+ const LLM_MESSAGE_ROLES: ReadonlySet<string> = new Set(["user", "assistant", "toolResult"]);
52
+
53
+ /**
54
+ * The prefix of the next turn's request: system prompt, tool schemas, and the
55
+ * replayed conversation, with no user turn yet.
56
+ */
57
+ function prewarmPrefix(state: {
58
+ systemPrompt: string;
59
+ messages: ReadonlyArray<AgentMessage>;
60
+ tools: ReadonlyArray<AgentTool>;
61
+ }): PrewarmContext {
62
+ return {
63
+ systemPrompt: state.systemPrompt,
64
+ messages: state.messages.filter((message) => LLM_MESSAGE_ROLES.has((message as { role?: string }).role ?? "")),
65
+ tools: [...state.tools],
66
+ };
67
+ }
68
+
69
+ export interface PrewarmRoundInput {
70
+ model: EngineModel;
71
+ /** Live agent state the next turn would send. Read-only; the round copies it. */
72
+ state: {
73
+ systemPrompt: string;
74
+ messages: ReadonlyArray<AgentMessage>;
75
+ tools: ReadonlyArray<AgentTool>;
76
+ thinkingLevel: string;
77
+ };
78
+ apiKey?: string;
79
+ signal?: AbortSignal;
80
+ /** Test seam. Production uses the engine dispatcher a turn runs on. */
81
+ streamFn?: typeof streamSimple;
82
+ }
83
+
84
+ export interface PrewarmRoundResult {
85
+ /** True when the operator submitted (or the session moved) before the round settled. */
86
+ aborted: boolean;
87
+ /** Provider usage for the round; null when the backend reported none. */
88
+ usage: Usage | null;
89
+ /** Backend prefill and prediction timings when the server reported them. */
90
+ backend: BackendCompletionTimings | null;
91
+ /** Wall clock the round spent, in the shape a turn's assistant entry uses. */
92
+ timing: { ttftMs: number | null; apiMs: number };
93
+ /** Provider error text when the round failed outright. */
94
+ errorMessage: string | null;
95
+ }
96
+
97
+ function usageOf(message: unknown): Usage | null {
98
+ if (message === null || typeof message !== "object") return null;
99
+ const usage = (message as { usage?: unknown }).usage;
100
+ return usage !== null && typeof usage === "object" ? (usage as Usage) : null;
101
+ }
102
+
103
+ function backendOf(message: unknown): BackendCompletionTimings | null {
104
+ if (message === null || typeof message !== "object") return null;
105
+ const backend = (message as { backendTimings?: unknown }).backendTimings;
106
+ return backend !== null && typeof backend === "object" ? (backend as BackendCompletionTimings) : null;
107
+ }
108
+
109
+ /**
110
+ * Prompt tokens the backend actually processed for one pre-warm. The server's
111
+ * own prompt count is authoritative when it reported one; otherwise the
112
+ * provider usage prompt side, cached tokens folded in, is the honest figure.
113
+ * Null when neither exists, which is different from a measured zero.
114
+ */
115
+ export function prewarmPromptTokens(result: Pick<PrewarmRoundResult, "usage" | "backend">): number | null {
116
+ if (result.backend) return result.backend.promptTokens;
117
+ const usage = result.usage;
118
+ if (!usage) return null;
119
+ const total = (usage.input || 0) + (usage.cacheRead || 0) + (usage.cacheWrite || 0);
120
+ return total > 0 ? total : null;
121
+ }
122
+
123
+ /**
124
+ * Send the prefix and wait for the backend to acknowledge it. Resolves rather
125
+ * than rejects on a provider failure: a pre-warm is an optimization, and a
126
+ * backend that refuses it must not be able to break the session it was warming.
127
+ */
128
+ export async function runPrewarmRound(input: PrewarmRoundInput): Promise<PrewarmRoundResult> {
129
+ const prefix = prewarmPrefix(input.state);
130
+ const context = {
131
+ systemPrompt: prefix.systemPrompt,
132
+ messages: [
133
+ ...prefix.messages,
134
+ { role: "user", content: [{ type: "text", text: PREWARM_USER_TEXT }], timestamp: Date.now() },
135
+ ],
136
+ tools: prefix.tools,
137
+ };
138
+ // pi-agent-core maps `thinkingLevel: "off"` onto an absent `reasoning`; the
139
+ // pre-warm has to make the same mapping or the thinking composition resolves
140
+ // against a different level and the rendered template moves.
141
+ const options: Record<string, unknown> = { maxTokens: PREWARM_MAX_TOKENS };
142
+ if (input.state.thinkingLevel !== "off") options.reasoning = input.state.thinkingLevel;
143
+ if (input.apiKey !== undefined) options.apiKey = input.apiKey;
144
+ if (input.signal !== undefined) options.signal = input.signal;
145
+
146
+ const send = input.streamFn ?? streamSimple;
147
+ const startedAt = performance.now();
148
+ let firstDeltaAt: number | null = null;
149
+ const elapsed = (): number => Math.round(Math.max(0, performance.now() - startedAt));
150
+ const ttft = (): number | null => (firstDeltaAt === null ? null : Math.round(Math.max(0, firstDeltaAt - startedAt)));
151
+
152
+ try {
153
+ const events = send(
154
+ input.model,
155
+ context as unknown as Parameters<typeof streamSimple>[1],
156
+ options as unknown as Parameters<typeof streamSimple>[2],
157
+ );
158
+ for await (const event of events) {
159
+ if (firstDeltaAt === null && event.type !== "start") firstDeltaAt = performance.now();
160
+ if (event.type === "done") {
161
+ return {
162
+ aborted: false,
163
+ usage: usageOf(event.message),
164
+ backend: backendOf(event.message),
165
+ timing: { ttftMs: ttft(), apiMs: elapsed() },
166
+ errorMessage: null,
167
+ };
168
+ }
169
+ if (event.type === "error") {
170
+ const failed = event.error as { stopReason?: unknown; errorMessage?: unknown };
171
+ const aborted = event.reason === "aborted" || failed.stopReason === "aborted" || input.signal?.aborted === true;
172
+ return {
173
+ aborted,
174
+ usage: usageOf(event.error),
175
+ backend: backendOf(event.error),
176
+ timing: { ttftMs: ttft(), apiMs: elapsed() },
177
+ errorMessage: aborted ? null : typeof failed.errorMessage === "string" ? failed.errorMessage : "pre-warm failed",
178
+ };
179
+ }
180
+ }
181
+ } catch (error) {
182
+ return {
183
+ aborted: input.signal?.aborted === true,
184
+ usage: null,
185
+ backend: null,
186
+ timing: { ttftMs: ttft(), apiMs: elapsed() },
187
+ errorMessage: input.signal?.aborted === true ? null : error instanceof Error ? error.message : String(error),
188
+ };
189
+ }
190
+ return {
191
+ aborted: input.signal?.aborted === true,
192
+ usage: null,
193
+ backend: null,
194
+ timing: { ttftMs: ttft(), apiMs: elapsed() },
195
+ errorMessage: null,
196
+ };
197
+ }