@iowarp/clio-coder 0.3.3 → 0.3.6

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 (370) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/CONTRIBUTING.md +7 -7
  3. package/README.md +3 -3
  4. package/dist/{acp-P2AQILE2.js → acp-2BEHC4DL.js} +9 -8
  5. package/dist/{agents-72W3BI7I.js → agents-LNNFTM53.js} +29 -24
  6. package/dist/assets/codewiki.json +1 -1
  7. package/dist/{auth-5TWEIYDN.js → auth-KXXFI2VS.js} +14 -10
  8. package/dist/{chunk-GGXXDWE4.js → chunk-22NAGB7X.js} +2 -2
  9. package/dist/{chunk-YCWGATWI.js → chunk-24I7BN55.js} +2 -2
  10. package/dist/{chunk-EKMEHE4H.js → chunk-33YXPOE3.js} +2 -3
  11. package/dist/chunk-3BPUFZDL.js +37 -0
  12. package/dist/chunk-43AOLP7E.js +375 -0
  13. package/dist/{chunk-ZDOOVTXZ.js → chunk-4OC57DA6.js} +27 -4
  14. package/dist/{chunk-6SGHMWE3.js → chunk-5JGRAMKL.js} +5 -5
  15. package/dist/{chunk-V6RTAOC2.js → chunk-6US73PDB.js} +572 -51
  16. package/dist/{chunk-5UFT4SUX.js → chunk-6XXKFVSN.js} +3 -3
  17. package/dist/{chunk-A3CYT5EX.js → chunk-AD2SYQYC.js} +55 -2
  18. package/dist/chunk-AOCYTWAV.js +449 -0
  19. package/dist/chunk-CFGTUFWB.js +67 -0
  20. package/dist/chunk-CJUB2JJ2.js +1478 -0
  21. package/dist/{chunk-FNTMWMX5.js → chunk-CKXWIANG.js} +14 -12
  22. package/dist/{chunk-PIWWS5BL.js → chunk-CYQKWTG3.js} +63 -78
  23. package/dist/{chunk-LZSJBIVT.js → chunk-DJVECN66.js} +271 -762
  24. package/dist/{chunk-CBCAPZAA.js → chunk-E25LMLRW.js} +2 -2
  25. package/dist/{chunk-ZWMF7253.js → chunk-E2ER4LJF.js} +304 -9
  26. package/dist/{chunk-STBPMHSX.js → chunk-EKY57CSP.js} +51 -84
  27. package/dist/{chunk-DUYJ5IO6.js → chunk-EYPA3EGJ.js} +12 -4
  28. package/dist/{chunk-M6SHUN7Q.js → chunk-FO5ZOVUY.js} +2 -2
  29. package/dist/chunk-FYYLNIL5.js +313 -0
  30. package/dist/{chunk-OQ33BKR3.js → chunk-G7MUEIGA.js} +3 -60
  31. package/dist/chunk-GEYXPTRF.js +613 -0
  32. package/dist/chunk-GOXNB3AO.js +261 -0
  33. package/dist/{chunk-G4BMMOKF.js → chunk-HVDIIIQW.js} +2 -2
  34. package/dist/chunk-HWUFFB6L.js +83 -0
  35. package/dist/{chunk-4XUGQOHA.js → chunk-K7T3E2SR.js} +15 -8
  36. package/dist/chunk-K7VKOLQQ.js +15 -0
  37. package/dist/{chunk-UFIIWP2H.js → chunk-KHSFENX2.js} +8 -8
  38. package/dist/{chunk-BMEMKKIT.js → chunk-KOHPCX4K.js} +2 -2
  39. package/dist/chunk-LCGCVYZ4.js +57 -0
  40. package/dist/chunk-LL4KHSZI.js +22 -0
  41. package/dist/{chunk-PAJK6MAQ.js → chunk-LYF7OHWH.js} +42 -15
  42. package/dist/{chunk-POHLU5DW.js → chunk-M6L6IDJG.js} +3 -3
  43. package/dist/{chunk-5UUP6MWO.js → chunk-MV3K5QF2.js} +5 -436
  44. package/dist/{chunk-X4RCMKVQ.js → chunk-NDINPTJ4.js} +2 -2
  45. package/dist/{chunk-TZK7PACC.js → chunk-NILBFAPG.js} +14 -8
  46. package/dist/chunk-ODFEOB4F.js +1082 -0
  47. package/dist/{chunk-AGYYIBLL.js → chunk-OH3TOQTB.js} +6 -2
  48. package/dist/chunk-OZNBF4L3.js +23 -0
  49. package/dist/{chunk-DSELYM6W.js → chunk-PBTHKCPN.js} +30 -10
  50. package/dist/{verify-375KUB3Y.js → chunk-PCZJO5TI.js} +127 -42
  51. package/dist/{chunk-ED4KHGC3.js → chunk-PPAMZ32Z.js} +9 -2
  52. package/dist/{chunk-SRDMMSEP.js → chunk-QM3F2GKX.js} +1063 -1645
  53. package/dist/{chunk-X6IAEBZR.js → chunk-QNQHSOLF.js} +7 -7
  54. package/dist/{chunk-OC7FIQPC.js → chunk-R46L2BIR.js} +10 -7
  55. package/dist/{chunk-2TLUCQVG.js → chunk-RD5U66HV.js} +3 -3
  56. package/dist/{chunk-6N5PTWMY.js → chunk-RY3LY4J5.js} +50 -13
  57. package/dist/{chunk-OKGUZO2U.js → chunk-SPULKLCF.js} +4 -3
  58. package/dist/{chunk-OOJYHWRB.js → chunk-TSHXZTOQ.js} +6 -5
  59. package/dist/chunk-TZSKNMZG.js +434 -0
  60. package/dist/{chunk-7MNJORFF.js → chunk-UL3WSD3F.js} +6 -1
  61. package/dist/{chunk-VJWL6YS5.js → chunk-UUVG37B4.js} +2 -2
  62. package/dist/{chunk-COU2UHX6.js → chunk-VEZEGCGW.js} +170 -2
  63. package/dist/chunk-W6GROXXM.js +69 -0
  64. package/dist/{chunk-OAO4GE4M.js → chunk-WHGPSPT5.js} +2 -2
  65. package/dist/chunk-WHJYKASB.js +677 -0
  66. package/dist/{chunk-ORBHGJC5.js → chunk-WR67VIZY.js} +3 -3
  67. package/dist/{chunk-YHZX5GEU.js → chunk-XAKHZX5N.js} +2 -2
  68. package/dist/{chunk-TZTZS7QK.js → chunk-XE2VEJHX.js} +5 -3
  69. package/dist/{chunk-LM5TQCJZ.js → chunk-XF5N4U5A.js} +8 -7
  70. package/dist/{chunk-LWLEKMDQ.js → chunk-XXQNGV4M.js} +1073 -552
  71. package/dist/{chunk-KZWTDYJF.js → chunk-XYDYPRZI.js} +7 -7
  72. package/dist/chunk-ZGVHUX3M.js +66 -0
  73. package/dist/{chunk-LW6DSM3M.js → chunk-ZRGEBJ4T.js} +1192 -1119
  74. package/dist/{chunk-2DJ2KNFG.js → chunk-ZXF4XRKW.js} +202 -40
  75. package/dist/chunk-ZZMN5OM4.js +122 -0
  76. package/dist/cli/index.js +34 -30
  77. package/dist/{clio-JOU4FXVA.js → clio-M2KGYUFZ.js} +7 -6
  78. package/dist/{code-nav-7AX6FYE6.js → code-nav-GQNL7XA6.js} +8 -6
  79. package/dist/codewiki/build-worker.js +4 -4
  80. package/dist/{components-KELWS457.js → components-5TTYYX6G.js} +3 -3
  81. package/dist/{config-XCDVKR23.js → config-XUUYQIWO.js} +47 -35
  82. package/dist/{configure-4GAP54ZW.js → configure-IHJ7YOMV.js} +18 -15
  83. package/dist/{context-77FM5DV5.js → context-74JLXAWD.js} +18 -10
  84. package/dist/{context-4UOGGLQ5.js → context-75MIWW3U.js} +41 -29
  85. package/dist/{context-5VKGUVJJ.js → context-ZQ7SIFJV.js} +85 -9
  86. package/dist/{context-clear-XXJRLCJJ.js → context-clear-GYKWNUML.js} +41 -29
  87. package/dist/{context-index-BZ4UYMTC.js → context-index-SSR5ECNE.js} +3 -3
  88. package/dist/context-working-set-UX5KEP4J.js +1553 -0
  89. package/dist/{dispatch-runner-QPRDDBDX.js → dispatch-runner-GIJBHNFL.js} +47 -32
  90. package/dist/{docs-2C2LTVT2.js → docs-6FZSCG5B.js} +3 -3
  91. package/dist/{doctor-HR46URBJ.js → doctor-SVJ5BZCW.js} +12 -12
  92. package/dist/{eval-XSSNATB4.js → eval-CG6LLBLD.js} +54 -238
  93. package/dist/{evidence-6HG2PY2B.js → evidence-ZYFIEN42.js} +57 -28
  94. package/dist/{evolve-K7YU3NCY.js → evolve-QGEXEMDW.js} +36 -25
  95. package/dist/{extensions-QVDOHDGJ.js → extensions-ADGNCJJD.js} +3 -3
  96. package/dist/{fleet-VY3HHKN6.js → fleet-S5R4ZOQY.js} +73 -44
  97. package/dist/{fleet-preflight-DDN536IT.js → fleet-preflight-BHSNPBMH.js} +3 -3
  98. package/dist/{init-JYGXI3FK.js → init-5DRU55YR.js} +49 -37
  99. package/dist/memory-7YKKR6UC.js +467 -0
  100. package/dist/{models-I5QWSEOM.js → models-ZPOLRU2C.js} +24 -21
  101. package/dist/{monitor-GE4ID3IA.js → monitor-US5F5YGZ.js} +73 -46
  102. package/dist/{orchestrator-EM5MC3HM.js → orchestrator-E2AL4T5N.js} +1624 -1007
  103. package/dist/{paths-UXLN5YYZ.js → paths-E7KYAQWE.js} +3 -3
  104. package/dist/{reset-L2FQEE3E.js → reset-KZ652EK6.js} +6 -5
  105. package/dist/{run-ZU3QMZPZ.js → run-SRNBKDWD.js} +76 -54
  106. package/dist/{share-S5BZQC5I.js → share-CGZE33UP.js} +7 -6
  107. package/dist/{skills-X5VXCRNQ.js → skills-S2X4DLY5.js} +4 -4
  108. package/dist/{skills-eval-WKIHWTHR.js → skills-eval-W2GGIC4R.js} +40 -29
  109. package/dist/{targets-SNCPI2NR.js → targets-54SWINWB.js} +28 -23
  110. package/dist/{terminal-lease-BNAHVHBS.js → terminal-lease-SAIF2OGY.js} +6 -4
  111. package/dist/{uninstall-FZCQCDKC.js → uninstall-BVLWXKBT.js} +3 -3
  112. package/dist/{upgrade-JQHHPQ4K.js → upgrade-JKAR27XC.js} +20 -19
  113. package/dist/{usage-OR4O5SMZ.js → usage-MSAWCLX4.js} +79 -36
  114. package/dist/verifiers-NCBTHHN2.js +1220 -0
  115. package/dist/verify-X5HDROLA.js +25 -0
  116. package/dist/{wiki-generate-UEXP2ARI.js → wiki-generate-GUSOQ6ZP.js} +50 -37
  117. package/dist/worker/entry.js +90 -70
  118. package/dist/{workspace-G4ZWUIPR.js → workspace-ZJ6BFM3Q.js} +4 -4
  119. package/docs/README.md +8 -7
  120. package/docs/acp.md +1 -1
  121. package/docs/alcf-provider.md +1 -1
  122. package/docs/architecture.md +2 -2
  123. package/docs/artifact-placement.md +1 -2
  124. package/docs/artifact-versions.md +1 -1
  125. package/docs/built-in-agents.md +1 -1
  126. package/docs/capacity-and-scheduling.md +1 -1
  127. package/docs/commands-and-modes.md +60 -26
  128. package/docs/config-knobs-audit.md +1 -2
  129. package/docs/configuration-and-targets.md +26 -1
  130. package/docs/context-engine.md +67 -13
  131. package/docs/context-working-set.md +194 -0
  132. package/docs/development-pipeline.md +1 -1
  133. package/docs/documentation-coverage.md +6 -6
  134. package/docs/documentation-guide.md +7 -6
  135. package/docs/environment-variables.md +2 -1
  136. package/docs/eval-runner.md +1 -1
  137. package/docs/evals-internal.md +4 -32
  138. package/docs/evidence-and-memory.md +139 -7
  139. package/docs/evolution.md +1 -1
  140. package/docs/exit-codes-and-output.md +1 -1
  141. package/docs/extensions-and-sharing.md +2 -2
  142. package/docs/fleet-dispatch.md +49 -8
  143. package/docs/glossary.md +21 -1
  144. package/docs/installation-and-lifecycle.md +2 -2
  145. package/docs/middleware-and-components.md +19 -2
  146. package/docs/model-catalog.md +7 -9
  147. package/docs/observability.md +4 -4
  148. package/docs/proactive-memory.md +26 -16
  149. package/docs/prompt-envelope-and-tools.md +7 -5
  150. package/docs/provider-adapter-cookbook.md +1 -1
  151. package/docs/release-cut-checklist.md +43 -40
  152. package/docs/safety-model.md +49 -8
  153. package/docs/scientific-validation.md +21 -3
  154. package/docs/session-lifecycle.md +3 -3
  155. package/docs/skills-marketplace.md +1 -1
  156. package/docs/tool-usage.md +79 -12
  157. package/docs/trace-store.md +1 -1
  158. package/docs/troubleshooting.md +1 -1
  159. package/docs/tui-design.md +38 -4
  160. package/docs/worker-dispatch-mechanics.md +11 -1
  161. package/package.json +13 -13
  162. package/skills/meta/clio-test/SKILL.md +20 -17
  163. package/skills/meta/clio-test/evals.md +3 -3
  164. package/skills/meta/clio-test/references/harness.md +35 -6
  165. package/skills/meta/clio-test/references/test-map.md +20 -10
  166. package/skills/registry.yaml +2 -2
  167. package/skills/skill-marketplace.json +1 -1
  168. package/src/cli/agents.ts +2 -3
  169. package/src/cli/argv.ts +14 -1
  170. package/src/cli/context-working-set.ts +513 -0
  171. package/src/cli/context.ts +8 -0
  172. package/src/cli/evidence.ts +20 -2
  173. package/src/cli/fleet.ts +15 -0
  174. package/src/cli/index.ts +5 -1
  175. package/src/cli/memory.ts +272 -10
  176. package/src/cli/modes/json-stream.ts +2 -2
  177. package/src/cli/modes/print.ts +12 -1
  178. package/src/cli/run.ts +22 -2
  179. package/src/cli/targets.ts +12 -3
  180. package/src/cli/usage.ts +55 -7
  181. package/src/cli/verifiers.ts +325 -0
  182. package/src/core/bash-exec.ts +39 -14
  183. package/src/core/bus-events.ts +22 -4
  184. package/src/core/config.ts +54 -0
  185. package/src/core/defaults.ts +50 -3
  186. package/src/core/response-model-id.ts +134 -0
  187. package/src/core/toml.ts +62 -0
  188. package/src/core/verification-scripts.ts +6 -0
  189. package/src/core/workspace-files.ts +0 -1
  190. package/src/domains/agents/builtins/architect.md +1 -1
  191. package/src/domains/agents/builtins/verifier.md +3 -0
  192. package/src/domains/agents/catalog.ts +5 -4
  193. package/src/domains/agents/recipe.ts +54 -14
  194. package/src/domains/agents/result-contract.ts +7 -4
  195. package/src/domains/config/classify.ts +1 -0
  196. package/src/domains/context/bootstrap.ts +36 -27
  197. package/src/domains/context/project-metadata.ts +19 -63
  198. package/src/domains/context/prompt-context.ts +8 -0
  199. package/src/domains/context/working-set/contract.ts +161 -0
  200. package/src/domains/context/working-set/defaults.ts +28 -0
  201. package/src/domains/context/working-set/engine.ts +203 -0
  202. package/src/domains/context/working-set/fold.ts +62 -0
  203. package/src/domains/context/working-set/horizon.ts +38 -0
  204. package/src/domains/context/working-set/marker.ts +103 -0
  205. package/src/domains/context/working-set/path-index.ts +436 -0
  206. package/src/domains/context/working-set/payload.ts +152 -0
  207. package/src/domains/context/working-set/policies/age-horizon.ts +55 -0
  208. package/src/domains/context/working-set/policies/index.ts +20 -0
  209. package/src/domains/context/working-set/policies/structural.ts +160 -0
  210. package/src/domains/context/working-set/project.ts +132 -0
  211. package/src/domains/context/working-set/protect.ts +109 -0
  212. package/src/domains/context/working-set/recall.ts +177 -0
  213. package/src/domains/context/working-set/replay/controls.ts +112 -0
  214. package/src/domains/context/working-set/replay/load-clio.ts +199 -0
  215. package/src/domains/context/working-set/replay/metrics.ts +185 -0
  216. package/src/domains/context/working-set/replay/reference-graph.ts +79 -0
  217. package/src/domains/context/working-set/replay/report.ts +139 -0
  218. package/src/domains/context/working-set/replay/runner.ts +325 -0
  219. package/src/domains/context/working-set/replay/synthetic.ts +422 -0
  220. package/src/domains/context/working-set/replay/trace.ts +21 -0
  221. package/src/domains/context/working-set/visible.ts +54 -0
  222. package/src/domains/dispatch/budget-envelope.ts +396 -0
  223. package/src/domains/dispatch/contract.ts +2 -0
  224. package/src/domains/dispatch/extension.ts +81 -27
  225. package/src/domains/dispatch/orphan-recovery.ts +1 -0
  226. package/src/domains/dispatch/receipt-integrity.ts +4 -0
  227. package/src/domains/dispatch/state.ts +1 -0
  228. package/src/domains/dispatch/types.ts +10 -3
  229. package/src/domains/dispatch/validation.ts +14 -0
  230. package/src/domains/dispatch/worker-spawn.ts +14 -3
  231. package/src/domains/eval/metrics/evidence.ts +0 -116
  232. package/src/domains/eval/metrics/invariants.ts +1 -1
  233. package/src/domains/eval/runners/clio-run.ts +1 -10
  234. package/src/domains/eval/runners/external-command.ts +2 -29
  235. package/src/domains/eval/schema/suite.ts +0 -7
  236. package/src/domains/eval/suites/run.ts +1 -7
  237. package/src/domains/evidence/build.ts +112 -45
  238. package/src/domains/evidence/eval.ts +24 -7
  239. package/src/domains/evidence/index.ts +53 -0
  240. package/src/domains/evidence/ordering.ts +12 -0
  241. package/src/domains/evidence/run-trust.ts +221 -0
  242. package/src/domains/evidence/store.ts +46 -6
  243. package/src/domains/evidence/trust-status.ts +854 -0
  244. package/src/domains/evidence/types.ts +26 -0
  245. package/src/domains/memory/index.ts +22 -0
  246. package/src/domains/memory/operations.ts +58 -1
  247. package/src/domains/memory/promotion.ts +281 -0
  248. package/src/domains/memory/prompt-section.ts +25 -5
  249. package/src/domains/memory/proposal.ts +51 -7
  250. package/src/domains/memory/task-bank.ts +3 -2
  251. package/src/domains/memory/task-memory-handoff.ts +181 -24
  252. package/src/domains/memory/task-memory-policy.ts +3 -1
  253. package/src/domains/memory/types.ts +37 -0
  254. package/src/domains/memory/validate.ts +178 -0
  255. package/src/domains/middleware/memory-intervention.ts +38 -25
  256. package/src/domains/middleware/runtime.ts +6 -0
  257. package/src/domains/middleware/skills-reminder.ts +19 -4
  258. package/src/domains/middleware/stalled-turn.ts +208 -5
  259. package/src/domains/middleware/types.ts +10 -0
  260. package/src/domains/observability/contract.ts +6 -1
  261. package/src/domains/observability/cost.ts +20 -4
  262. package/src/domains/observability/extension.ts +2 -2
  263. package/src/domains/providers/index.ts +3 -0
  264. package/src/domains/providers/model-discovery.ts +9 -0
  265. package/src/domains/providers/runtime-resolution.ts +38 -1
  266. package/src/domains/providers/runtimes/common/probe-helpers.ts +97 -16
  267. package/src/domains/providers/types/context-window-slots.ts +18 -0
  268. package/src/domains/providers/types/runtime-descriptor.ts +3 -1
  269. package/src/domains/safety/autonomy.ts +1 -1
  270. package/src/domains/safety/call-target.ts +211 -14
  271. package/src/domains/safety/decision-presentation.ts +268 -0
  272. package/src/domains/safety/default-path-policy.ts +8 -0
  273. package/src/domains/safety/finish-contract.ts +4 -3
  274. package/src/domains/safety/policy-engine.ts +48 -6
  275. package/src/domains/safety/redaction.ts +73 -0
  276. package/src/domains/session/compaction/compact.ts +23 -1
  277. package/src/domains/session/compaction/cut-point.ts +2 -0
  278. package/src/domains/session/compaction/tokens.ts +16 -1
  279. package/src/domains/session/context-ledger.ts +12 -1
  280. package/src/domains/session/decision-board.ts +4 -0
  281. package/src/domains/session/entries.ts +110 -1
  282. package/src/domains/session/history.ts +68 -19
  283. package/src/domains/session/manager.ts +9 -2
  284. package/src/domains/session/migrations/index.ts +22 -3
  285. package/src/domains/session/usage.ts +24 -7
  286. package/src/engine/acp/event-mapper.ts +7 -0
  287. package/src/engine/acp/server.ts +32 -2
  288. package/src/engine/agent.ts +18 -1
  289. package/src/engine/apis/lmstudio.ts +25 -4
  290. package/src/engine/apis/openai-completions.ts +147 -22
  291. package/src/engine/claude/sdk-runtime.ts +8 -2
  292. package/src/engine/claude/tool-safety.ts +13 -0
  293. package/src/engine/loop-guard.ts +27 -3
  294. package/src/engine/session.ts +9 -3
  295. package/src/engine/worker-events.ts +4 -3
  296. package/src/engine/worker-runtime.ts +59 -54
  297. package/src/entry/orchestrator.ts +34 -5
  298. package/src/interactive/chat-loop-messages.ts +40 -6
  299. package/src/interactive/chat-loop.ts +13 -0
  300. package/src/interactive/chat-panel.ts +17 -1
  301. package/src/interactive/chat-renderer.ts +49 -24
  302. package/src/interactive/clio-editor.ts +44 -7
  303. package/src/interactive/context-meter.ts +10 -0
  304. package/src/interactive/context-overlay.ts +120 -7
  305. package/src/interactive/context-recall-command.ts +110 -0
  306. package/src/interactive/cost-overlay.ts +39 -8
  307. package/src/interactive/dispatch-board.ts +212 -35
  308. package/src/interactive/footer/widgets.ts +13 -0
  309. package/src/interactive/interactive-application.ts +6 -1
  310. package/src/interactive/interactive-input-runtime.ts +11 -1
  311. package/src/interactive/interactive-presentation.ts +11 -1
  312. package/src/interactive/interactive-slash-runtime.ts +37 -1
  313. package/src/interactive/memory-overlay.ts +89 -4
  314. package/src/interactive/model-session-replay.ts +21 -0
  315. package/src/interactive/overlay-ask-user-lifecycle.ts +1 -1
  316. package/src/interactive/overlay-frame.ts +5 -2
  317. package/src/interactive/overlay-general-openers.ts +46 -1
  318. package/src/interactive/overlay-key-routing.ts +41 -1
  319. package/src/interactive/overlay-lifecycle.ts +11 -4
  320. package/src/interactive/overlay-permission-lifecycle.ts +23 -8
  321. package/src/interactive/overlay-session-lifecycle.ts +8 -4
  322. package/src/interactive/overlay-transitions.ts +11 -0
  323. package/src/interactive/overlays/ask-user.ts +74 -30
  324. package/src/interactive/overlays/decisions.ts +3 -1
  325. package/src/interactive/permission-hint.ts +35 -0
  326. package/src/interactive/permission-overlay.ts +95 -45
  327. package/src/interactive/renderers/tool-execution.ts +37 -51
  328. package/src/interactive/session-last-turn.ts +8 -1
  329. package/src/interactive/session-transcript.ts +2 -2
  330. package/src/interactive/session-usage-reseed.ts +36 -10
  331. package/src/interactive/slash-commands.ts +31 -4
  332. package/src/interactive/status/summary.ts +5 -0
  333. package/src/interactive/status/types.ts +5 -0
  334. package/src/interactive/terminal-lease.ts +1 -0
  335. package/src/interactive/turn-context.ts +333 -110
  336. package/src/interactive/turn-middleware.ts +7 -6
  337. package/src/interactive/turn-runtime.ts +37 -8
  338. package/src/interactive/turn-state.ts +3 -0
  339. package/src/interactive/worker-progress.ts +440 -0
  340. package/src/interactive/worker-stream.ts +51 -110
  341. package/src/tools/agent-tools.ts +39 -7
  342. package/src/tools/ask-user.ts +21 -1
  343. package/src/tools/bash.ts +144 -82
  344. package/src/tools/builtin-tool-catalog.ts +11 -5
  345. package/src/tools/context/index.ts +107 -5
  346. package/src/tools/context/surface.ts +3 -2
  347. package/src/tools/core-bootstrap.ts +21 -0
  348. package/src/tools/dispatch-arguments.ts +8 -0
  349. package/src/tools/dispatch-event-text.ts +19 -0
  350. package/src/tools/dispatch-runner.ts +9 -7
  351. package/src/tools/dispatch.ts +24 -1
  352. package/src/tools/monitor.ts +43 -20
  353. package/src/tools/registry.ts +72 -10
  354. package/src/tools/result-disposition.ts +706 -0
  355. package/src/tools/result-shaping.ts +321 -20
  356. package/src/tools/safe-exec.ts +2 -0
  357. package/src/tools/verify/authoring.ts +1120 -0
  358. package/src/tools/verify/catalog.ts +346 -0
  359. package/src/tools/verify/index.ts +13 -3
  360. package/src/tools/verify/scripts.ts +135 -37
  361. package/src/tools/verify/surface.ts +9 -5
  362. package/src/tools/worker-evidence.ts +54 -12
  363. package/src/worker/spec-contract.ts +43 -3
  364. package/dist/chunk-J7CWMCQD.js +0 -255
  365. package/dist/chunk-T6YILFSB.js +0 -80
  366. package/dist/chunk-VAKQQHWR.js +0 -434
  367. package/dist/chunk-VPAYEGVX.js +0 -184
  368. package/dist/chunk-XBXAASKX.js +0 -18
  369. package/dist/memory-WFZMGYHX.js +0 -236
  370. package/src/domains/eval/metrics/chaos-stream.ts +0 -93
@@ -1,4 +1,9 @@
1
1
  import { createHash } from "node:crypto";
2
+ import {
3
+ legacyToolResultDigest,
4
+ sanitizeToolResultDigest,
5
+ type ToolResultDigest,
6
+ } from "../../tools/result-disposition.js";
2
7
  import { TASK_MEMORY_DEFAULT_PROCEDURAL_CAP, type TaskMemoryBank } from "../memory/task-bank.js";
3
8
  import {
4
9
  runTaskMemoryPolicy,
@@ -40,7 +45,6 @@ export interface MemoryInterventionSettings {
40
45
  timeoutMs: number;
41
46
  }
42
47
 
43
- const RESULT_DIGEST_MAX_CHARS = 240;
44
48
  const CALL_DESCRIPTION_MAX_CHARS = 180;
45
49
  const NO_EFFECTS: ReadonlyArray<MiddlewareEffect> = [];
46
50
 
@@ -48,7 +52,7 @@ type ToolOutcome = "ok" | "error";
48
52
 
49
53
  interface PendingToolStep {
50
54
  toolName: string;
51
- fingerprint: string;
55
+ operationFingerprint: string;
52
56
  callDescription: string;
53
57
  }
54
58
 
@@ -145,7 +149,7 @@ export function createMemoryInterventionRegistration(deps: MemoryInterventionDep
145
149
  let toolStep = 0;
146
150
  let lastTurnEndStep = 0;
147
151
  let reactivateAfterCompaction = false;
148
- let lastInjectedFingerprint: string | null = null;
152
+ let lastInjectedOperationFingerprint: string | null = null;
149
153
  let currentTask = "(current task unavailable)";
150
154
  let toolsSinceMemoryStep = 0;
151
155
  let consecutiveErrors = 0;
@@ -202,6 +206,9 @@ export function createMemoryInterventionRegistration(deps: MemoryInterventionDep
202
206
  return effects;
203
207
  }
204
208
  case "on_compaction":
209
+ // Recall grows the working set; it is an observability point, not
210
+ // context loss that should reactivate compacted task memory.
211
+ if (input.metadata?.stage === "working_set_recall") return NO_EFFECTS;
205
212
  reactivateAfterCompaction = true;
206
213
  return NO_EFFECTS;
207
214
  case "turn_start": {
@@ -464,13 +471,13 @@ export function createMemoryInterventionRegistration(deps: MemoryInterventionDep
464
471
  function observeBeforeTool(input: MiddlewareHookInput): void {
465
472
  const prepared = prepareToolStep(input);
466
473
  if (prepared === null) return;
467
- setBounded(pending, pendingKey(input, prepared.fingerprint), prepared, settings().windowSteps);
474
+ setBounded(pending, pendingKey(input, prepared.operationFingerprint), prepared, settings().windowSteps);
468
475
  }
469
476
 
470
477
  function observeAfterTool(input: MiddlewareHookInput): ReadonlyArray<MiddlewareEffect> {
471
478
  const fallback = prepareToolStep(input);
472
479
  if (fallback === null) return NO_EFFECTS;
473
- const key = pendingKey(input, fallback.fingerprint);
480
+ const key = pendingKey(input, fallback.operationFingerprint);
474
481
  const prepared = pending.get(key) ?? fallback;
475
482
  pending.delete(key);
476
483
  const outcome = input.metadata?.resultKind === "error" ? "error" : "ok";
@@ -484,11 +491,13 @@ export function createMemoryInterventionRegistration(deps: MemoryInterventionDep
484
491
  } else {
485
492
  consecutiveErrors = 0;
486
493
  }
494
+ const digest = resultDigest(input, outcome);
487
495
  const step: TrajectoryStep = {
488
496
  ...prepared,
489
497
  step: toolStep,
490
498
  outcome,
491
- resultDigest: resultDigest(input, outcome),
499
+ resultDigest: digest.text,
500
+ resultDigestProvenance: digest.provenance,
492
501
  };
493
502
  trajectory.push(step);
494
503
  if (trajectory.length > live.windowSteps) trajectory.splice(0, trajectory.length - live.windowSteps);
@@ -504,20 +513,20 @@ export function createMemoryInterventionRegistration(deps: MemoryInterventionDep
504
513
  * itself, which the model reads on its very next round.
505
514
  */
506
515
  function annotateRepeatedFailure(step: TrajectoryStep): ReadonlyArray<MiddlewareEffect> {
507
- if (annotatedThisTurn.has(step.fingerprint)) return NO_EFFECTS;
516
+ if (annotatedThisTurn.has(step.operationFingerprint)) return NO_EFFECTS;
508
517
  const occurrences = trajectory.filter(
509
- (candidate) => candidate.outcome === "error" && candidate.fingerprint === step.fingerprint,
518
+ (candidate) => candidate.outcome === "error" && candidate.operationFingerprint === step.operationFingerprint,
510
519
  ).length;
511
- const failure = failures.get(step.fingerprint);
520
+ const failure = failures.get(step.operationFingerprint);
512
521
  if (occurrences < 2 || failure === undefined) return NO_EFFECTS;
513
522
  const message = boundedReminder(
514
523
  `Memory: [${failure.entryId}] you already tried ${failure.callDescription} at step ${failure.firstStep} and it failed with ${failure.errorDigest}. Change the approach rather than repeating it.`,
515
524
  settings().maxTokens,
516
525
  );
517
526
  if (message.length === 0) return NO_EFFECTS;
518
- annotatedThisTurn.add(step.fingerprint);
527
+ annotatedThisTurn.add(step.operationFingerprint);
519
528
  // Claim the turn-end channel too, so one repeated failure is surfaced once.
520
- lastInjectedFingerprint = step.fingerprint;
529
+ lastInjectedOperationFingerprint = step.operationFingerprint;
521
530
  lastDecision = "injected";
522
531
  rulesInjectedSincePromptedStep = true;
523
532
  annotatedSinceTurnEnd = true;
@@ -527,7 +536,7 @@ export function createMemoryInterventionRegistration(deps: MemoryInterventionDep
527
536
  }
528
537
 
529
538
  function rememberFailure(step: TrajectoryStep): void {
530
- const previous = failures.get(step.fingerprint);
539
+ const previous = failures.get(step.operationFingerprint);
531
540
  const attempts = (previous?.attempts ?? 0) + 1;
532
541
  const firstStep = previous?.firstStep ?? step.step;
533
542
  const content = proceduralContent(step.callDescription, attempts, firstStep, step.resultDigest);
@@ -543,7 +552,7 @@ export function createMemoryInterventionRegistration(deps: MemoryInterventionDep
543
552
  }
544
553
  setBounded(
545
554
  failures,
546
- step.fingerprint,
555
+ step.operationFingerprint,
547
556
  {
548
557
  entryId,
549
558
  attempts,
@@ -563,21 +572,21 @@ export function createMemoryInterventionRegistration(deps: MemoryInterventionDep
563
572
  step === undefined ||
564
573
  step.outcome !== "error" ||
565
574
  step.step <= lastTurnEndStep ||
566
- step.fingerprint === lastInjectedFingerprint
575
+ step.operationFingerprint === lastInjectedOperationFingerprint
567
576
  ) {
568
577
  continue;
569
578
  }
570
579
  const occurrences = trajectory.filter(
571
- (candidate) => candidate.outcome === "error" && candidate.fingerprint === step.fingerprint,
580
+ (candidate) => candidate.outcome === "error" && candidate.operationFingerprint === step.operationFingerprint,
572
581
  ).length;
573
- const failure = failures.get(step.fingerprint);
582
+ const failure = failures.get(step.operationFingerprint);
574
583
  if (occurrences < 2 || failure === undefined) continue;
575
584
  const message = boundedReminder(
576
585
  `Memory: [${failure.entryId}] you already tried ${failure.callDescription} at step ${failure.firstStep} and it failed with ${failure.errorDigest}.`,
577
586
  settings().maxTokens,
578
587
  );
579
588
  if (message.length === 0) return NO_EFFECTS;
580
- lastInjectedFingerprint = step.fingerprint;
589
+ lastInjectedOperationFingerprint = step.operationFingerprint;
581
590
  lastInjectedMessage = message;
582
591
  lastDecision = "injected";
583
592
  rulesInjectedSincePromptedStep = true;
@@ -621,31 +630,35 @@ function prepareToolStep(input: MiddlewareHookInput): PendingToolStep | null {
621
630
  const canonical = hashToolCall(toolName, input.toolArgs ?? {});
622
631
  return {
623
632
  toolName,
624
- fingerprint: createHash("sha256").update(canonical).digest("hex").slice(0, 16),
633
+ operationFingerprint: createHash("sha256").update(canonical).digest("hex").slice(0, 16),
625
634
  callDescription: shortText(canonical, CALL_DESCRIPTION_MAX_CHARS),
626
635
  };
627
636
  }
628
637
 
629
- function pendingKey(input: MiddlewareHookInput, fingerprint: string): string {
630
- return input.toolCallId ?? `anonymous:${fingerprint}`;
638
+ function pendingKey(input: MiddlewareHookInput, operationFingerprint: string): string {
639
+ return input.toolCallId ?? `anonymous:${operationFingerprint}`;
631
640
  }
632
641
 
633
- function resultDigest(input: MiddlewareHookInput, outcome: ToolOutcome): string {
642
+ function resultDigest(input: MiddlewareHookInput, outcome: ToolOutcome): ToolResultDigest {
643
+ if (input.toolResultDigest !== undefined) return sanitizeToolResultDigest(input.toolResultDigest);
634
644
  if (outcome === "error") {
635
645
  const metadataMessage = input.metadata?.errorMessage;
636
646
  if (typeof metadataMessage === "string" && metadataMessage.trim().length > 0) {
637
- return shortText(diagnosticLine(metadataMessage), RESULT_DIGEST_MAX_CHARS);
647
+ return legacyToolResultDigest(diagnosticLine(metadataMessage), { outcome });
638
648
  }
639
649
  for (const key of ["error", "message"] as const) {
640
650
  const value = input.toolResultDetails?.[key];
641
651
  if (typeof value === "string" && value.trim().length > 0) {
642
- return shortText(diagnosticLine(value), RESULT_DIGEST_MAX_CHARS);
652
+ return legacyToolResultDigest(diagnosticLine(value), { outcome });
643
653
  }
644
654
  }
645
- return "an unknown tool error";
655
+ return legacyToolResultDigest("", { outcome });
646
656
  }
647
657
  const resultFingerprint = input.metadata?.resultFingerprint;
648
- return typeof resultFingerprint === "string" ? `ok result ${resultFingerprint.slice(0, 16)}` : "ok";
658
+ return legacyToolResultDigest(
659
+ typeof resultFingerprint === "string" ? `ok result ${resultFingerprint.slice(0, 16)}` : "ok",
660
+ { outcome },
661
+ );
649
662
  }
650
663
 
651
664
  const DIAGNOSTIC_HINT =
@@ -328,6 +328,12 @@ function cloneHookInput(input: MiddlewareHookInput): MiddlewareHookInput {
328
328
  if (input.metadata !== undefined) cloned.metadata = { ...input.metadata };
329
329
  if (input.toolArgs !== undefined) cloned.toolArgs = { ...input.toolArgs };
330
330
  if (input.toolResultDetails !== undefined) cloned.toolResultDetails = { ...input.toolResultDetails };
331
+ if (input.toolResultDigest !== undefined) {
332
+ cloned.toolResultDigest = {
333
+ text: input.toolResultDigest.text,
334
+ provenance: { ...input.toolResultDigest.provenance },
335
+ };
336
+ }
331
337
  if (input.text !== undefined) cloned.text = input.text.slice(0, MIDDLEWARE_HOOK_TEXT_MAX_CHARS);
332
338
  return cloned;
333
339
  }
@@ -1,5 +1,6 @@
1
1
  import { SKILL_SUGGESTION_ANCHOR } from "../../core/skill-activation.js";
2
2
  import type { MiddlewareHookRegistration } from "./runtime.js";
3
+ import { isSkillSuggestionWait, SKILL_SUGGESTION_WAIT_CONTINUATION_MESSAGE } from "./stalled-turn.js";
3
4
  import { type MiddlewareEffect, metadataNumber } from "./types.js";
4
5
 
5
6
  /**
@@ -32,12 +33,17 @@ export function skillsReminderMessage(installed: number, installable = 0): strin
32
33
  installable > 0
33
34
  ? `${installed} installed, ${installable} installable from the marketplace`
34
35
  : `${installed} installed`;
36
+ // Inline and non-blocking (issue #184): a 27B model read "and wait for the
37
+ // operator" as the whole job, spent a 40-second turn deliberating over the
38
+ // suggestion, and ended without one repository read. The suggestion is one
39
+ // line, the task continues in the same turn, and only the operator loads.
35
40
  return (
36
41
  `[Skills] ${counts}. Start this task by listing them with context(scope="skills") ` +
37
42
  "and checking for a match; if one matches, open your reply with the line " +
38
43
  `\`${SKILL_SUGGESTION_ANCHOR}\` (a comma-separated sequence, in order, when several compose) ` +
39
- "and wait for the operator; load only on operator request, and a marketplace skill is offered for " +
40
- "install when the operator runs it. If none match, do not mention skills and continue with the task."
44
+ "and then continue the task in the same turn without the skill. Only the operator loads a skill, and a " +
45
+ "marketplace skill is offered for install when the operator runs it. If none match, do not mention skills " +
46
+ "and continue with the task."
41
47
  );
42
48
  }
43
49
 
@@ -135,9 +141,18 @@ export function createSkillsReminderRegistration(deps: SkillsReminderDeps): Midd
135
141
 
136
142
  return {
137
143
  id: SKILLS_REMINDER_REGISTRATION_ID,
138
- description: "once per session, on the first substantive turn, teaches the skill-suggestion reply protocol",
139
- hooks: ["turn_start"],
144
+ description:
145
+ "once per session, on the first substantive turn, teaches the skill-suggestion reply protocol; keeps a turn going that stopped on the suggestion",
146
+ hooks: ["turn_start", "turn_end"],
140
147
  evaluate(input): ReadonlyArray<MiddlewareEffect> {
148
+ // The registration that teaches "suggest, then continue" owns the
149
+ // consequence when a model suggests and stops. The generic stalled-turn
150
+ // rule cannot see this case: the listing call counts as a tool call.
151
+ if (input.hook === "turn_end") {
152
+ return isSkillSuggestionWait(input)
153
+ ? [{ kind: "request_continuation", message: SKILL_SUGGESTION_WAIT_CONTINUATION_MESSAGE }]
154
+ : NO_EFFECTS;
155
+ }
141
156
  const sessionId = input.sessionId ?? null;
142
157
  if (lastSeenSessionId === undefined) {
143
158
  lastSeenSessionId = sessionId;
@@ -1,13 +1,127 @@
1
+ import { SKILL_SUGGESTION_PREFIX } from "../../core/skill-activation.js";
2
+ import { ToolNames } from "../../core/tool-names.js";
1
3
  import type { MiddlewareRuleDefinition } from "./runtime.js";
2
- import type { MiddlewareHookInput } from "./types.js";
4
+ import { type MiddlewareHookInput, metadataNumber, metadataString } from "./types.js";
3
5
 
4
6
  export const STALLED_TURN_REGISTRATION_ID = "nudge.stalled-turn";
5
7
 
6
8
  export const STALLED_TURN_REQUEST_CONTINUATION_MESSAGE =
7
9
  "You ended your turn after announcing an action without executing it: no tools were called. Continue now and perform the announced action, or state plainly that you are finished and waiting for the user.";
8
10
 
11
+ export const SKILL_SUGGESTION_WAIT_CONTINUATION_MESSAGE =
12
+ "You ended your turn on a skill suggestion without doing the task. The suggestion line has already made the offer and only the operator loads a skill, so nothing is needed from them before you proceed. Continue the task now without the skill.";
13
+
9
14
  const INTENT_PATTERN = /\b(let me|i['’]ll|i will|i am going to|i['’]m going to|now i|next i|let's|let us)\b/i;
15
+
16
+ // Bare stems only. Inflected forms are derived below so that "running",
17
+ // "checking", and "verifies" count as the same announced action as "run".
18
+ const CONCRETE_ACTION_STEMS = [
19
+ "add",
20
+ "analyse",
21
+ "analyze",
22
+ "apply",
23
+ "audit",
24
+ "build",
25
+ "change",
26
+ "check",
27
+ "compare",
28
+ "compile",
29
+ "create",
30
+ "debug",
31
+ "delete",
32
+ "edit",
33
+ "execute",
34
+ "explore",
35
+ "find",
36
+ "fix",
37
+ "format",
38
+ "grep",
39
+ "implement",
40
+ "inspect",
41
+ "install",
42
+ "investigate",
43
+ "list",
44
+ "load",
45
+ "look",
46
+ "modify",
47
+ "open",
48
+ "patch",
49
+ "query",
50
+ "read",
51
+ "refactor",
52
+ "remove",
53
+ "rename",
54
+ "replace",
55
+ "review",
56
+ "run",
57
+ "scan",
58
+ "search",
59
+ "test",
60
+ "trace",
61
+ "update",
62
+ "validate",
63
+ "verify",
64
+ "write",
65
+ ] as const;
66
+
67
+ // Stems whose final consonant doubles before a vowel suffix.
68
+ const DOUBLED_CONSONANT_STEMS = new Set(["debug", "format", "run", "scan"]);
69
+
70
+ function concreteActionForms(stem: string): string[] {
71
+ const forms = new Set([stem, `${stem}s`, `${stem}ed`, `${stem}ing`]);
72
+ if (/(?:ch|sh|s|x|z)$/.test(stem)) forms.add(`${stem}es`);
73
+ if (stem.endsWith("e")) {
74
+ forms.add(`${stem}d`);
75
+ forms.add(`${stem.slice(0, -1)}ing`);
76
+ }
77
+ if (stem.endsWith("y")) {
78
+ forms.add(`${stem.slice(0, -1)}ies`);
79
+ forms.add(`${stem.slice(0, -1)}ied`);
80
+ }
81
+ if (DOUBLED_CONSONANT_STEMS.has(stem)) {
82
+ const doubled = `${stem}${stem.slice(-1)}`;
83
+ forms.add(`${doubled}ed`);
84
+ forms.add(`${doubled}ing`);
85
+ }
86
+ return [...forms];
87
+ }
88
+
89
+ const CONCRETE_ACTION_PATTERN = new RegExp(
90
+ `\\b(?:${CONCRETE_ACTION_STEMS.flatMap(concreteActionForms).join("|")})\\b`,
91
+ "giu",
92
+ );
93
+
94
+ // Extensions a model actually names when announcing work on a file. Requiring
95
+ // one (or an explicit ./, ../, ~/ prefix) keeps "and/or" and "I/O" from
96
+ // reading as paths.
97
+ const NAMED_PATH_EXTENSIONS =
98
+ "c|cc|cfg|cjs|cmake|conf|cpp|cs|css|csv|env|go|h|hpp|html|ini|java|jl|js|json|jsx|lock|log|lua|md|mjs|nc|php|py|rb|rs|rst|sh|sql|toml|ts|tsx|txt|yaml|yml";
99
+ const NAMED_PATH_PATTERN = new RegExp(
100
+ `(?:^|[\\s\`"'(\\[])(?:[\\w@.\\-/]*[\\w@\\-]\\.(?:${NAMED_PATH_EXTENSIONS})\\b|(?:\\.{1,2}|~)\\/[\\w@.\\-/]+)`,
101
+ "iu",
102
+ );
103
+
104
+ // A named executable followed by at least one argument token. Words that are
105
+ // ordinary English on their own ("go", "make", "test") are deliberately absent.
106
+ const NAMED_COMMAND_PATTERN =
107
+ /\b(?:npm|npx|pnpm|yarn|bun|git|cargo|rustc|cmake|ctest|ninja|meson|bazel|gradle|mvn|pytest|pip|python3?|node|deno|tsc|biome|eslint|prettier|jest|vitest|ruff|mypy|docker|kubectl|mpirun|srun|sbatch|gcc|clang)\s+[-\w./]/iu;
108
+
109
+ const ACTION_OBJECT_CONNECTOR_PATTERN = /^(?:at|for|in|into|on|through|to)\s+/i;
110
+ const ACTION_OBJECT_DETERMINER_PATTERN = /^(?:a|an|my|our|that|the|their|these|this|those|your)\s+/i;
111
+ const ACTION_OBJECT_PATTERN = /^(?!(?:again|everyone|later|next|now|soon|then|you)\b)[`"'./~]*[\p{L}\p{N}_-]/iu;
112
+ const CONDITIONAL_LEAD_IN_PATTERN = /\b(?:if|once|unless|until|when|whenever)\b/i;
113
+ const CONDITIONAL_INVITATION_PATTERN = /\b(?:ask|give|point|say|send|share|tell)\b[\s\S]*\b(?:and|then)\s*$/i;
114
+ // "if needed", and any conditional whose condition is about the user: "if you
115
+ // want", "if that is what you need". Conditions about the work ("if the config
116
+ // is valid") stay announcements.
117
+ const CONDITIONAL_OFFER_SUFFIX_PATTERN =
118
+ /\b(?:if\s+(?:needed|necessary|requested)\b|if\b[^.!?]*\byou\b|when(?:ever)?\s+you\b)/i;
10
119
  const LET_ME_KNOW_PATTERN = /\blet me know\b/i;
120
+ // "I'll wait for your go-ahead before I touch src/cli/index.ts" names a path
121
+ // and promises nothing now. A clause that opens by deferring is a wait
122
+ // statement whatever it goes on to name, so it is read before any verb or
123
+ // path inside it can count as an announcement.
124
+ const DEFERRAL_LEAD_PATTERN = /^\s*(?:just\s+|simply\s+|now\s+)?(?:wait|hold\s+off|hold|stand\s+by|pause|defer)\b/i;
11
125
  const COMPLETION_PATTERN =
12
126
  /^\s*(?:all\s+done|done|complete|completed|all\s+set)\b|^\s*(?:here(?:'s| is)\s+)?(?:a\s+)?summary\b|^\s*summary\s*:?$/i;
13
127
 
@@ -24,6 +138,43 @@ export const STALLED_TURN_RULE_DEFINITION: MiddlewareRuleDefinition = {
24
138
  predicate: shouldRequestStalledTurnContinuation,
25
139
  };
26
140
 
141
+ // "run /skill X or say skip", "Suggested skill: /skill x", "want me to use
142
+ // the skill?": the sentence hands the operator a decision the protocol does
143
+ // not require of them.
144
+ const SKILL_WAIT_PATTERN = /\/skill\b|\bskills?\b|\bskip\b/i;
145
+
146
+ /**
147
+ * A turn that made the skill suggestion and then stopped, with only the
148
+ * listing call behind it (issue #184). The generic stalled-turn predicate
149
+ * cannot see this: `context(scope="skills")` is a tool call, so the turn
150
+ * looks busy, and "say skip and I'll explore the tree" reads as a
151
+ * conditional offer rather than an announcement. The suggestion is supposed
152
+ * to be one inline line before the work, so a reply that is the suggestion
153
+ * plus a wait has not done the task.
154
+ */
155
+ export function isSkillSuggestionWait(input: MiddlewareHookInput): boolean {
156
+ if (input.hook !== "turn_end") return false;
157
+ if (!isNormalStopReason(input.metadata?.stopReason)) return false;
158
+ const text = input.text ?? "";
159
+ if (!text.includes(SKILL_SUGGESTION_PREFIX)) return false;
160
+ const toolNames = metadataString(input, "turnToolNames");
161
+ if (toolNames === null) {
162
+ if ((metadataNumber(input, "turnToolCalls") ?? 0) !== 0) return false;
163
+ } else if (
164
+ toolNames
165
+ .split(",")
166
+ .filter((name) => name.length > 0)
167
+ .some((name) => name !== ToolNames.Context)
168
+ ) {
169
+ return false;
170
+ }
171
+ const lastLine = lastNonEmptyLine(text);
172
+ if (lastLine === null) return false;
173
+ const finalSentence = lastSentence(lastLine);
174
+ if (finalSentence.length === 0) return false;
175
+ return SKILL_WAIT_PATTERN.test(finalSentence) || isQuestion(finalSentence) || LET_ME_KNOW_PATTERN.test(finalSentence);
176
+ }
177
+
27
178
  export function shouldRequestStalledTurnContinuation(input: MiddlewareHookInput): boolean {
28
179
  if (input.hook !== "turn_end") return false;
29
180
  if (input.metadata?.turnToolCalls !== 0) return false;
@@ -38,7 +189,47 @@ export function shouldRequestStalledTurnContinuation(input: MiddlewareHookInput)
38
189
  if (LET_ME_KNOW_PATTERN.test(finalSentence)) return false;
39
190
  if (COMPLETION_PATTERN.test(lastLine) || COMPLETION_PATTERN.test(finalSentence)) return false;
40
191
 
41
- return finalSentence.endsWith(":") || INTENT_PATTERN.test(finalSentence);
192
+ return announcesConcreteAction(finalSentence);
193
+ }
194
+
195
+ function announcesConcreteAction(sentence: string): boolean {
196
+ const intent = INTENT_PATTERN.exec(sentence);
197
+ if (intent) {
198
+ const leadIn = sentence.slice(0, intent.index);
199
+ if (
200
+ CONDITIONAL_LEAD_IN_PATTERN.test(leadIn) ||
201
+ CONDITIONAL_INVITATION_PATTERN.test(leadIn) ||
202
+ CONDITIONAL_OFFER_SUFFIX_PATTERN.test(sentence)
203
+ ) {
204
+ return false;
205
+ }
206
+ const clause = sentence.slice(intent.index + intent[0].length);
207
+ if (DEFERRAL_LEAD_PATTERN.test(clause)) return false;
208
+ if (hasActionWithObject(clause)) return true;
209
+ // A path or command is evidence of an immediate action only when the
210
+ // announcement itself is unconditional; "once package.json is updated"
211
+ // names a file but promises nothing now.
212
+ if (CONDITIONAL_LEAD_IN_PATTERN.test(clause)) return false;
213
+ return namesToolTarget(clause);
214
+ }
215
+
216
+ // A trailing colon can still signal a cut-off action lead-in, but only when
217
+ // the lead-in names work and its target instead of relying on punctuation.
218
+ return sentence.endsWith(":") && hasActionWithObject(sentence);
219
+ }
220
+
221
+ function namesToolTarget(clause: string): boolean {
222
+ return NAMED_PATH_PATTERN.test(clause) || NAMED_COMMAND_PATTERN.test(clause);
223
+ }
224
+
225
+ function hasActionWithObject(clause: string): boolean {
226
+ for (const action of clause.matchAll(CONCRETE_ACTION_PATTERN)) {
227
+ const remainder = clause.slice((action.index ?? 0) + action[0].length).trimStart();
228
+ const withoutConnector = remainder.replace(ACTION_OBJECT_CONNECTOR_PATTERN, "");
229
+ const object = withoutConnector.replace(ACTION_OBJECT_DETERMINER_PATTERN, "");
230
+ if (ACTION_OBJECT_PATTERN.test(object)) return true;
231
+ }
232
+ return false;
42
233
  }
43
234
 
44
235
  function isNormalStopReason(value: unknown): boolean {
@@ -60,10 +251,22 @@ function lastNonEmptyLine(text: string): string | null {
60
251
  return null;
61
252
  }
62
253
 
254
+ // A period only terminates a sentence when it ends a token: "bash.ts" and
255
+ // "package.json" must survive extraction intact.
256
+ const SENTENCE_TERMINATOR_PATTERN = /[.!?]+["'’)\]]*(?=\s|$)/gu;
257
+
63
258
  function lastSentence(line: string): string {
64
- const matches = line.match(/[^.!?]+[.!?]+(?:["')\]]+)?|[^.!?]+$/g);
65
- const sentence = matches?.at(-1)?.trim() ?? line.trim();
66
- return sentence.replace(/^[\s"'([{-]+/, "").trim();
259
+ const trimmed = line.trimEnd();
260
+ let start = 0;
261
+ for (const terminator of trimmed.matchAll(SENTENCE_TERMINATOR_PATTERN)) {
262
+ const end = (terminator.index ?? 0) + terminator[0].length;
263
+ if (end >= trimmed.length) break;
264
+ start = end;
265
+ }
266
+ return trimmed
267
+ .slice(start)
268
+ .replace(/^[\s"'([{-]+/, "")
269
+ .trim();
67
270
  }
68
271
 
69
272
  function isQuestion(sentence: string): boolean {
@@ -1,3 +1,5 @@
1
+ import type { ToolResultDigest } from "../../tools/result-disposition.js";
2
+
1
3
  /**
2
4
  * The five lifecycle events of the unified middleware layer (w3 cutover).
3
5
  * Every event has a live producer: the registry fires before_tool/after_tool
@@ -86,6 +88,8 @@ export interface MiddlewareHookInput {
86
88
  toolArgs?: Readonly<Record<string, unknown>>;
87
89
  /** Structured result details, supplied by the registry on after_tool. Read-only by contract. */
88
90
  toolResultDetails?: Readonly<Record<string, unknown>>;
91
+ /** Bounded deterministic diagnostic supplied by the result-disposition layer on after_tool. */
92
+ toolResultDigest?: Readonly<ToolResultDigest>;
89
93
  /**
90
94
  * Free text attached to the hook occurrence; on turn_end this is the final
91
95
  * assistant message text. The one typed exception to the scalar `metadata`
@@ -114,6 +118,12 @@ export function metadataNumber(input: MiddlewareHookInput, key: string): number
114
118
  return typeof value === "number" && Number.isFinite(value) ? value : null;
115
119
  }
116
120
 
121
+ /** The string under `key`, or null when the caller did not report one. */
122
+ export function metadataString(input: MiddlewareHookInput, key: string): string | null {
123
+ const value = input.metadata?.[key];
124
+ return typeof value === "string" ? value : null;
125
+ }
126
+
117
127
  export function isMiddlewareHook(value: string): value is MiddlewareHook {
118
128
  return (MIDDLEWARE_HOOKS as ReadonlyArray<string>).includes(value);
119
129
  }
@@ -1,3 +1,4 @@
1
+ import type { ResponseModelIdObservationCounts } from "../../core/response-model-id.js";
1
2
  import type { TargetStatus } from "../providers/contract.js";
2
3
  import type { CostProvenance } from "../providers/index.js";
3
4
  import type { AccountabilitySummary } from "./accountability.js";
@@ -132,11 +133,15 @@ export interface ObservabilityContract {
132
133
  */
133
134
  recordTokens(
134
135
  providerId: string,
135
- modelId: string,
136
+ attributedModelId: string,
136
137
  tokens: number,
137
138
  costUsd?: number,
138
139
  breakdown?: Partial<UsageBreakdown>,
139
140
  costProvenance?: CostProvenance,
141
+ modelIdFacts?: {
142
+ requestedModelIds: ReadonlyArray<string>;
143
+ responseModelIdObservationCounts: Readonly<ResponseModelIdObservationCounts>;
144
+ },
140
145
  ): void;
141
146
  /** Record final output token throughput for one completed assistant stream. */
142
147
  recordTokenThroughput(snapshot: TokenThroughputSnapshot): void;
@@ -11,6 +11,7 @@
11
11
  * footer consumes the session sum through `ObservabilityContract.sessionTokens()`.
12
12
  */
13
13
 
14
+ import type { ResponseModelIdObservationCounts } from "../../core/response-model-id.js";
14
15
  import { type CostProvenance, normalizeCostProvenance } from "../providers/index.js";
15
16
 
16
17
  export interface CostAmount {
@@ -112,7 +113,9 @@ export interface UsageBreakdown {
112
113
 
113
114
  export interface CostEntry {
114
115
  providerId: string;
115
- modelId: string;
116
+ attributedModelId: string;
117
+ requestedModelIds: string[];
118
+ responseModelIdObservationCounts: ResponseModelIdObservationCounts;
116
119
  tokens: number;
117
120
  usd: number;
118
121
  provenance: CostProvenance;
@@ -127,11 +130,15 @@ export interface CostEntry {
127
130
  export interface CostTracker {
128
131
  accumulate(
129
132
  providerId: string,
130
- modelId: string,
133
+ attributedModelId: string,
131
134
  tokens: number,
132
135
  usd?: number,
133
136
  breakdown?: Partial<UsageBreakdown>,
134
137
  provenance?: CostProvenance,
138
+ modelIdFacts?: {
139
+ requestedModelIds: ReadonlyArray<string>;
140
+ responseModelIdObservationCounts: Readonly<ResponseModelIdObservationCounts>;
141
+ },
135
142
  ): number;
136
143
  sessionTotal(): number;
137
144
  sessionCost(): CostAggregate;
@@ -149,7 +156,7 @@ export function createCostTracker(): CostTracker {
149
156
  let total = 0;
150
157
  const totals = emptyBreakdown();
151
158
  return {
152
- accumulate(providerId, modelId, tokens, usd, breakdown, costProvenance) {
159
+ accumulate(providerId, attributedModelId, tokens, usd, breakdown, costProvenance, modelIdFacts) {
153
160
  const resolvedUsd = usd ?? 0;
154
161
  const provenance = normalizeCostProvenance(costProvenance);
155
162
  const input = breakdown?.input ?? 0;
@@ -160,7 +167,16 @@ export function createCostTracker(): CostTracker {
160
167
  const apiCalls = breakdown?.apiCalls;
161
168
  log.push({
162
169
  providerId,
163
- modelId,
170
+ attributedModelId,
171
+ requestedModelIds: [...(modelIdFacts?.requestedModelIds ?? [attributedModelId])],
172
+ responseModelIdObservationCounts: modelIdFacts
173
+ ? { ...modelIdFacts.responseModelIdObservationCounts }
174
+ : {
175
+ reportedCalls: 0,
176
+ notReportedCalls: 0,
177
+ notObservedCalls: apiCalls ?? 1,
178
+ legacyDifferenceOnlyCalls: 0,
179
+ },
164
180
  tokens,
165
181
  usd: resolvedUsd,
166
182
  provenance,
@@ -259,9 +259,9 @@ export function createObservabilityBundle(
259
259
  latestThroughput = null;
260
260
  projection.refresh();
261
261
  },
262
- recordTokens(providerId, modelId, tokens, costUsd, breakdown, costProvenance) {
262
+ recordTokens(providerId, attributedModelId, tokens, costUsd, breakdown, costProvenance, modelIdFacts) {
263
263
  telemetry.record("counter", "tokens.total", tokens);
264
- cost.accumulate(providerId, modelId, tokens, costUsd, breakdown, costProvenance);
264
+ cost.accumulate(providerId, attributedModelId, tokens, costUsd, breakdown, costProvenance, modelIdFacts);
265
265
  projection.refresh();
266
266
  },
267
267
  recordSessionTurn(sessionTurn) {
@@ -32,6 +32,7 @@ export type { ModelCapabilityPatchTarget } from "./model-capabilities.js";
32
32
  export { applyModelCapabilityPatch, resolveModelCapabilities } from "./model-capabilities.js";
33
33
  export {
34
34
  canonicalizeWireModelId,
35
+ contextSlotsForModel,
35
36
  hasLiveModelCatalog,
36
37
  loadedContextWindowForModel,
37
38
  type ModelResidency,
@@ -96,6 +97,7 @@ export {
96
97
  refineRuntimeTargetWithModelHints,
97
98
  resolveRuntimeTarget,
98
99
  runtimeResolutionWarnings,
100
+ runtimeResolutionWarningsBesideThinkingNotice,
99
101
  runtimeTargetSnapshot,
100
102
  } from "./runtime-resolution.js";
101
103
  export {
@@ -126,6 +128,7 @@ export {
126
128
  EMPTY_CAPABILITIES,
127
129
  VALID_THINKING_LEVELS,
128
130
  } from "./types/capability-flags.js";
131
+ export { type ContextWindowSlots, formatContextWindowSlots } from "./types/context-window-slots.js";
129
132
  export { type CostProvenance, normalizeCostProvenance } from "./types/cost-provenance.js";
130
133
  export type { KnowledgeBase, KnowledgeBaseEntry, KnowledgeBaseHit } from "./types/knowledge-base.js";
131
134
  export type {
@@ -1,5 +1,6 @@
1
1
  import type { TargetStatus } from "./contract.js";
2
2
  import { listKnownModelsForRuntime } from "./support.js";
3
+ import type { ContextWindowSlots } from "./types/context-window-slots.js";
3
4
 
4
5
  export type ProviderModelSource = "configured" | "live" | "catalog" | "default";
5
6
 
@@ -45,6 +46,14 @@ export function loadedContextWindowForModel(
45
46
  return typeof reported === "number" && Number.isFinite(reported) && reported > 0 ? reported : null;
46
47
  }
47
48
 
49
+ /** How the server splits its KV budget for this model, when the probe saw it split. */
50
+ export function contextSlotsForModel(
51
+ status: DiscoveryStatus | null | undefined,
52
+ modelId: string,
53
+ ): ContextWindowSlots | null {
54
+ return status?.discoveredModelStates?.[modelId]?.contextSlots ?? null;
55
+ }
56
+
48
57
  /**
49
58
  * Residency as one view, so the planner and the "not resident" notice cannot
50
59
  * disagree about the same model. A reported loaded window settles it whatever