@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,3 +1,8 @@
1
+ import {
2
+ classifyDecisionPresentation,
3
+ type DecisionPresentation,
4
+ decisionFactsForAnswer,
5
+ } from "../../domains/safety/decision-presentation.js";
1
6
  import {
2
7
  type Component,
3
8
  Input,
@@ -18,16 +23,14 @@ import { type ClioToken, clioTheme, dotSep, GLYPH, screenTitle } from "../theme/
18
23
  import { nextContentScrollOffset, type ViewScrollAction } from "../view/view-overlay.js";
19
24
 
20
25
  /**
21
- * The border and title token while a decision is pending.
22
- *
23
- * Orange is the token that means Clio is acting, and a prompt holding the
24
- * keyboard is the moment the operator has to act with it. Everything
25
- * informational keeps the teal frame, so the one border that is not teal is the
26
- * one asking for an answer. All three surfaces carry it: what changes between
27
- * them is the size and the place, never the signal.
26
+ * The default title and border token for a local conversational answer.
27
+ * Outward and other consequence tiers replace both values with their typed
28
+ * presentation. All ask_user surfaces carry the same classified signal.
28
29
  */
29
- export const ASK_USER_DECISION_TONE: ClioToken = "action";
30
- export const ASK_USER_DECISION_TITLE = "Decision required";
30
+ const DEFAULT_ASK_USER_PRESENTATION = classifyDecisionPresentation(decisionFactsForAnswer("local"));
31
+
32
+ export const ASK_USER_DECISION_TONE: ClioToken = DEFAULT_ASK_USER_PRESENTATION.semanticToken;
33
+ export const ASK_USER_DECISION_TITLE = DEFAULT_ASK_USER_PRESENTATION.title;
31
34
  export const ASK_USER_WAITING_TITLE = "Ask User";
32
35
 
33
36
  /**
@@ -73,7 +76,7 @@ interface AskUserSurfaceGeometry {
73
76
  * `ASK_USER_FRAME_AND_MARGIN_ROWS` counts alongside the two border rows.
74
77
  */
75
78
  export const ASK_USER_SURFACE_GEOMETRY: Record<AskUserSurface, AskUserSurfaceGeometry> = {
76
- compact: { width: 68, maxInnerRows: 10, anchor: "bottom-center", margin: { top: 1, right: 2, bottom: 1, left: 2 } },
79
+ compact: { width: 68, maxInnerRows: 14, anchor: "bottom-center", margin: { top: 1, right: 2, bottom: 1, left: 2 } },
77
80
  panel: { width: 88, maxInnerRows: 18, anchor: "center", margin: 1 },
78
81
  // The interview is a workspace, not a modal. A cap of 42 turned a 66-row
79
82
  // terminal into a centered box with twenty blank rows around it, so the
@@ -111,7 +114,7 @@ export interface OpenAskUserOverlayDeps {
111
114
  }
112
115
 
113
116
  export interface AskUserOverlaySession extends OverlayHandle {
114
- ask(questions: ReadonlyArray<AskUserQuestion>): Promise<AskUserResult>;
117
+ ask(questions: ReadonlyArray<AskUserQuestion>, presentation?: DecisionPresentation): Promise<AskUserResult>;
115
118
  cancel(): void;
116
119
  close(): void;
117
120
  isWaiting(): boolean;
@@ -242,16 +245,21 @@ class AskUserOverlayView implements Component {
242
245
  private contentRegionHeight = 1;
243
246
  /** Rows the scroll region wanted on the last render. */
244
247
  private contentTotalRows = 0;
248
+ private presentation: DecisionPresentation = DEFAULT_ASK_USER_PRESENTATION;
245
249
 
246
250
  constructor(private readonly deps: AskUserOverlayViewDeps) {}
247
251
 
248
- begin(questions: ReadonlyArray<AskUserQuestion>): Promise<AskUserResult> {
252
+ begin(
253
+ questions: ReadonlyArray<AskUserQuestion>,
254
+ presentation: DecisionPresentation = DEFAULT_ASK_USER_PRESENTATION,
255
+ ): Promise<AskUserResult> {
249
256
  if (this.phase === "closed") return Promise.resolve(cancelledAskUserResult());
250
257
  if (this.resolveCurrent) return Promise.resolve(cancelledAskUserResult());
251
258
  this.phase = "asking";
252
259
  this.index = 0;
253
260
  this.status = "";
254
261
  this.questions = [...questions];
262
+ this.presentation = presentation;
255
263
  this.states = this.questions.map((question) => createQuestionState(question));
256
264
  this.list = null;
257
265
  this.input = null;
@@ -288,6 +296,14 @@ class AskUserOverlayView implements Component {
288
296
  return this.surface;
289
297
  }
290
298
 
299
+ decisionTitle(): string {
300
+ return this.presentation.title;
301
+ }
302
+
303
+ decisionTone(): ClioToken {
304
+ return this.presentation.semanticToken;
305
+ }
306
+
291
307
  invalidate(): void {
292
308
  this.list?.invalidate();
293
309
  this.input?.invalidate();
@@ -358,10 +374,17 @@ class AskUserOverlayView implements Component {
358
374
  const statusLines = this.status.length > 0 ? ["", fitLine(clioTheme().fg("dim", this.status), safeWidth)] : [];
359
375
  const headerLine = this.renderQuestionHeader(question, safeWidth);
360
376
  const headerLines = headerLine.length > 0 ? [headerLine] : [];
361
- const spent = headerLines.length + statusLines.length + 1 + controlLines.length;
362
- const questionLines = wrapTextWithAnsi(question.question, safeWidth).slice(0, Math.max(1, maxRows - spent));
363
-
364
- return [...headerLines, ...questionLines, ...statusLines, "", ...controlLines].slice(0, maxRows);
377
+ const bottom = [...statusLines, "", ...controlLines];
378
+ const topBudget = Math.max(1, maxRows - bottom.length);
379
+ const contextLines = this.renderDecisionContext(safeWidth);
380
+ const top = [
381
+ contextLines[0] ?? "",
382
+ ...headerLines,
383
+ ...wrapTextWithAnsi(question.question, safeWidth),
384
+ ...contextLines.slice(1),
385
+ ].slice(0, topBudget);
386
+
387
+ return [...top, ...bottom].slice(0, maxRows);
365
388
  }
366
389
 
367
390
  /**
@@ -380,7 +403,12 @@ class AskUserOverlayView implements Component {
380
403
  const headerLine = this.renderQuestionHeader(question, width);
381
404
  const headerLines = headerLine.length > 0 ? [headerLine] : [];
382
405
  const stripLines = [this.renderQuestionStrip(width), ""];
383
- const content = [...wrapTextWithAnsi(question.question, width), ...this.renderAnswerLedger(width)];
406
+ const content = [
407
+ ...wrapTextWithAnsi(question.question, width),
408
+ "",
409
+ ...this.renderDecisionContext(width),
410
+ ...this.renderAnswerLedger(width),
411
+ ];
384
412
 
385
413
  // Chrome sheds from the outside in when the terminal is too short to carry
386
414
  // all of it: the round strip first, then the header, then the status, and
@@ -460,36 +488,52 @@ class AskUserOverlayView implements Component {
460
488
  const question = this.currentQuestion();
461
489
  const state = this.currentState();
462
490
  if (!question || !state) return buildHint([]);
491
+ const recordAnswer =
492
+ this.presentation.requiredActions.find((action) => action.id === "record-answer")?.label.toLowerCase() ??
493
+ "record answer";
463
494
  if (state.mode === "text") {
464
495
  return this.questions.length > 1
465
496
  ? this.withScrollHint([
466
- { key: "Enter", verb: "submit" },
497
+ { key: "Enter", verb: recordAnswer },
467
498
  { key: "Alt+Left/Right", verb: "question" },
468
499
  ])
469
- : this.withScrollHint([{ key: "Enter", verb: "submit" }]);
500
+ : this.withScrollHint([{ key: "Enter", verb: recordAnswer }]);
470
501
  }
471
- // `accept` rather than `select` or `commit`: the footer of a decision prompt
472
- // has one job, which is to name the key that answers it. Esc keeps the
473
- // product's one word for the way out, since it closes the prompt and the
474
- // interview together.
502
+ // The classified action label distinguishes recording an answer from
503
+ // granting authority. Esc keeps the product's one word for the way out,
504
+ // since it closes the prompt and the interview together.
475
505
  if (question.multi_select === true) {
476
506
  return this.questions.length > 1
477
507
  ? this.withScrollHint([
478
508
  { key: "Left/Right", verb: "question" },
479
509
  { key: "Space", verb: "toggle" },
480
- { key: "Enter", verb: "accept" },
510
+ { key: "Enter", verb: recordAnswer },
481
511
  ])
482
512
  : this.withScrollHint([
483
513
  { key: "Space", verb: "toggle" },
484
- { key: "Enter", verb: "accept" },
514
+ { key: "Enter", verb: recordAnswer },
485
515
  ]);
486
516
  }
487
517
  return this.questions.length > 1
488
518
  ? this.withScrollHint([
489
519
  { key: "Left/Right", verb: "question" },
490
- { key: "Enter", verb: "accept" },
520
+ { key: "Enter", verb: recordAnswer },
491
521
  ])
492
- : this.withScrollHint([{ key: "Enter", verb: "accept" }]);
522
+ : this.withScrollHint([{ key: "Enter", verb: recordAnswer }]);
523
+ }
524
+
525
+ private renderDecisionContext(width: number): string[] {
526
+ const theme = clioTheme();
527
+ const tier = theme.style(this.presentation.semanticToken, this.presentation.tierLabel, { bold: true });
528
+ const requested = theme.fg("dim", `requested by ${this.presentation.requestedByCopy}`);
529
+ const effect = `${theme.fg("dim", "Effect:")} ${theme.fg("muted", this.presentation.authorizationCopy)}`;
530
+ return [
531
+ fitLine(`${tier}${dotSep(theme)}${requested}`, width),
532
+ ...wrapTextWithAnsi(effect, width).map((line) => fitLine(line, width)),
533
+ ...wrapTextWithAnsi(theme.fg("muted", this.presentation.reversibilityCopy), width).map((line) =>
534
+ fitLine(line, width),
535
+ ),
536
+ ];
493
537
  }
494
538
 
495
539
  /**
@@ -1012,8 +1056,8 @@ export function openAskUserOverlay(tui: TUI, deps: OpenAskUserOverlayDeps): AskU
1012
1056
  // the terminal has left after the margins.
1013
1057
  ...(geometry.maxInnerRows > 0 ? { maxHeight: geometry.maxInnerRows + ASK_USER_FRAME_ROWS } : {}),
1014
1058
  margin: geometry.margin,
1015
- title: () => (view.isDecisionPending() ? ASK_USER_DECISION_TITLE : ASK_USER_WAITING_TITLE),
1016
- tone: () => (view.isDecisionPending() ? ASK_USER_DECISION_TONE : undefined),
1059
+ title: () => (view.isDecisionPending() ? view.decisionTitle() : ASK_USER_WAITING_TITLE),
1060
+ tone: () => (view.isDecisionPending() ? view.decisionTone() : undefined),
1017
1061
  footerHint: () => view.footerHint(),
1018
1062
  });
1019
1063
  mounted = surface;
@@ -1034,7 +1078,7 @@ export function openAskUserOverlay(tui: TUI, deps: OpenAskUserOverlayDeps): AskU
1034
1078
  focus: () => handle?.focus(),
1035
1079
  unfocus: (options) => (options ? handle?.unfocus(options) : handle?.unfocus()),
1036
1080
  isFocused: () => handle?.isFocused() ?? false,
1037
- ask: (questions) => view.begin(questions),
1081
+ ask: (questions, presentation) => view.begin(questions, presentation),
1038
1082
  cancel: () => view.cancel(),
1039
1083
  close,
1040
1084
  hide: close,
@@ -1,3 +1,4 @@
1
+ import { classifyDecisionPresentation, decisionFactsForAnswer } from "../../domains/safety/decision-presentation.js";
1
2
  import type { DecisionLedgerEntry, DecisionRecord } from "../../domains/session/entries.js";
2
3
  import {
3
4
  type Component,
@@ -73,10 +74,11 @@ function selectableRows(interviews: ReadonlyArray<DecisionLedgerEntry>): Selecta
73
74
 
74
75
  function interviewHeader(interview: DecisionLedgerEntry, width: number, now: number): string[] {
75
76
  const theme = clioTheme();
77
+ const presentation = classifyDecisionPresentation(decisionFactsForAnswer(interview.exposure ?? "local"));
76
78
  const rounds = `${interview.roundCount} round${interview.roundCount === 1 ? "" : "s"}`;
77
79
  const status =
78
80
  interview.interviewStatus === "complete" ? theme.fg("success", "complete") : theme.fg("warning", "cancelled");
79
- const heading = `${theme.fg("accent", relativeTime(interview.endedAt, now))}${theme.fg("dim", " · ")}${theme.fg("muted", rounds)}${theme.fg("dim", " · ")}${status}`;
81
+ const heading = `${theme.style(presentation.semanticToken, presentation.tierLabel, { bold: true })}${theme.fg("dim", " · ")}${theme.fg("accent", relativeTime(interview.endedAt, now))}${theme.fg("dim", " · ")}${theme.fg("muted", rounds)}${theme.fg("dim", " · ")}${status}`;
80
82
  const lines = [fitLine(heading, width)];
81
83
  if (interview.summary) {
82
84
  lines.push(...wrapTextWithAnsi(theme.fg("dim", interview.summary), width).map((line) => fitLine(line, width)));
@@ -0,0 +1,35 @@
1
+ import type { HintEntry } from "./overlay-frame.js";
2
+
3
+ /**
4
+ * The keys that answer a parked call, as data, so the dialog footer and the
5
+ * composer rail render the same policy at any width.
6
+ *
7
+ * This is its own leaf because the composer is part of the Stage 0 boot shell
8
+ * and the permission overlay is not: importing the overlay from the editor
9
+ * pulled the safety domain's approval modules into the instant-shell closure.
10
+ *
11
+ * At 40 columns the old positional elider removed `[s] stop turn` and left
12
+ * "allow once" and an ambiguous "close" in front of an operator trying to
13
+ * refuse. The key kept working, so the layout was hiding a live safety action.
14
+ * Allow and stop are marked critical, Esc is marked droppable, and
15
+ * `fitHintEntries` shortens every label before it drops anything.
16
+ *
17
+ * Esc says `deny`, not `close`: closing the dialog denies the call, and on the
18
+ * one surface where a misread is a wrong decision about a tool call the key
19
+ * names its effect.
20
+ *
21
+ * With a draft in the composer, Enter is inert (issue #186): the habitual send
22
+ * key must not resolve toward "allow" on a safety rail. The entry then says so
23
+ * and names the key that clears the draft, which the router passes through.
24
+ */
25
+ export function permissionHintEntries(composerHasDraft = false): HintEntry[] {
26
+ return [
27
+ composerHasDraft
28
+ ? { key: "Backspace", verb: "clear the draft to allow", short: "clear draft", critical: true }
29
+ : { key: "Enter", verb: "allow once", short: "allow", critical: true },
30
+ { key: "s", verb: "stop turn", short: "stop", critical: true },
31
+ // With a draft, deny and stop are the only immediate answers, so Esc
32
+ // outranks the narrowing that would otherwise drop it first.
33
+ { key: "Esc", verb: "deny", critical: composerHasDraft },
34
+ ];
35
+ }
@@ -1,25 +1,37 @@
1
- import type { Component } from "../engine/tui.js";
2
- import { buildResponsiveHint } from "./overlay-frame.js";
1
+ import type { ActionClass } from "../domains/safety/action-classifier.js";
2
+ import type { AutonomyExposure } from "../domains/safety/autonomy.js";
3
+ import {
4
+ classifyDecisionPresentation,
5
+ type DecisionPresentation,
6
+ decisionFactsForPermission,
7
+ } from "../domains/safety/decision-presentation.js";
8
+ import type { Component, OverlayOptions, TUI } from "../engine/tui.js";
9
+ import { fitHintEntries } from "./overlay-frame.js";
10
+ import { permissionHintEntries } from "./permission-hint.js";
11
+ import type { ClioToken } from "./theme/index.js";
3
12
 
4
13
  export { type AskAxis, askAxis } from "../domains/safety/approval-axis.js";
5
14
  export { describeCallTarget, sanitizeCallTargetText } from "../domains/safety/call-target.js";
15
+ export { permissionHintEntries } from "./permission-hint.js";
6
16
 
7
17
  export interface ApprovalRequestView {
8
18
  requestId: string;
9
19
  tool: string;
10
- actionClass: string;
20
+ actionClass: ActionClass;
11
21
  axis: { kind: "net"; ruleId: string } | { kind: "autonomy"; level: string };
12
22
  origin: { kind: "main" } | { kind: "worker"; agentId: string; runId: string };
23
+ /** Admission-normalized exposure. Caller prose never supplies presentation fields. */
24
+ exposure?: AutonomyExposure;
13
25
  reason: string;
14
26
  /** Typed, sanitized multi-line artifact that this one approval authorizes. */
15
27
  artifact?: { kind: "dispatch-plan"; text: string };
16
28
  /**
17
- * One-line preview of the call's object: the command for bash, the path
18
- * for file tools, else a compact args preview. The operator is deciding
19
- * whether to allow this exact call, so the overlay must show what the
20
- * call will touch, not just the tool name. Main-agent asks derive it from
21
- * the parked call's args; worker escalations carry it in the escalation
22
- * payload. Absent only when nothing meaningful is derivable.
29
+ * One-line preview of the call's allowlisted object fields. The operator is
30
+ * deciding whether to allow this exact call, so the overlay must show what
31
+ * the call will touch, not just the tool name. Unlisted fields appear only as
32
+ * type-and-size summaries. Main-agent asks derive it from the parked call's
33
+ * args; worker escalations carry it in the escalation payload. Absent only
34
+ * when nothing meaningful is derivable.
23
35
  */
24
36
  target?: string;
25
37
  queueDepth?: number;
@@ -29,6 +41,44 @@ const PERMISSION_OVERLAY_CONTENT_WIDTH = 78;
29
41
 
30
42
  export const PERMISSION_OVERLAY_WIDTH = PERMISSION_OVERLAY_CONTENT_WIDTH + 4;
31
43
 
44
+ /**
45
+ * Place a permission frame immediately above the live composer in both TUI
46
+ * modes. The editor grows with wrapped and multiline drafts, while the footer
47
+ * can add notice rows, so their rendered heights are the bottom dock rather
48
+ * than a fixed clearance. Regular mode preserves terminal scrollback and may
49
+ * leave that dock above the viewport bottom when the transcript is short.
50
+ * Recompute its viewport row on every frame so draft changes and terminal
51
+ * resizes move the dialog with the composer.
52
+ */
53
+ export function permissionOverlayPlacement(
54
+ tui: Pick<TUI, "mode" | "render">,
55
+ editor: Pick<Component, "render">,
56
+ footer: Pick<Component, "render">,
57
+ ): OverlayOptions {
58
+ const margin = { bottom: 0 };
59
+ return {
60
+ anchor: "bottom-center",
61
+ margin,
62
+ visible: (termWidth, termHeight) => {
63
+ const dockHeight = editor.render(termWidth).length + footer.render(termWidth).length;
64
+ if (tui.mode === "fullscreen") {
65
+ margin.bottom = dockHeight;
66
+ return true;
67
+ }
68
+
69
+ const baseHeight = tui.render(termWidth).length;
70
+ const composerTop = Math.max(0, baseHeight - dockHeight);
71
+ const viewportStart = Math.max(0, baseHeight - termHeight);
72
+ const composerViewportRow = composerTop - viewportStart;
73
+ margin.bottom =
74
+ composerViewportRow >= 0 && composerViewportRow < termHeight
75
+ ? Math.max(0, termHeight - composerViewportRow)
76
+ : dockHeight;
77
+ return true;
78
+ },
79
+ };
80
+ }
81
+
32
82
  class PermissionOverlayBody implements Component {
33
83
  constructor(private readonly view: ApprovalRequestView) {}
34
84
 
@@ -89,46 +139,41 @@ function wrapArtifactLine(value: string, max: number): string[] {
89
139
  return lines;
90
140
  }
91
141
 
92
- function axisLabel(axis: ApprovalRequestView["axis"], style: ApprovalRequestView["origin"]["kind"]): string {
93
- if (axis.kind === "net") return `safety-net rail ${axis.ruleId}`;
94
- return style === "worker" ? `autonomy level ${axis.level}` : `autonomy level (${axis.level})`;
142
+ export function permissionDecisionPresentation(view: ApprovalRequestView): DecisionPresentation {
143
+ return classifyDecisionPresentation(
144
+ decisionFactsForPermission({
145
+ tool: view.tool,
146
+ actionClass: view.actionClass,
147
+ axis:
148
+ view.axis.kind === "net"
149
+ ? { kind: "safety-net", ruleId: view.axis.ruleId }
150
+ : { kind: "autonomy", level: view.axis.level },
151
+ origin: view.origin,
152
+ ...(view.exposure !== undefined ? { exposure: view.exposure } : {}),
153
+ }),
154
+ );
95
155
  }
96
156
 
97
- function askedBy(view: ApprovalRequestView): string {
98
- if (view.origin.kind === "worker") {
99
- return `worker ${view.origin.agentId} (run ${view.origin.runId}), ${axisLabel(view.axis, "worker")}`;
100
- }
101
- return axisLabel(view.axis, "main");
157
+ export function permissionOverlayTitle(view: ApprovalRequestView): string {
158
+ return permissionDecisionPresentation(view).title;
102
159
  }
103
160
 
104
- export function permissionOverlayTitle(): string {
105
- return "Allow this action once?";
161
+ export function permissionOverlayTone(view: ApprovalRequestView): ClioToken {
162
+ return permissionDecisionPresentation(view).semanticToken;
106
163
  }
107
164
 
108
165
  /**
109
- * The footer for a box `innerWidth` columns wide inside its borders.
110
- *
111
- * At 40 columns the old positional elider removed `[s] stop turn` and left
112
- * "allow once" and an ambiguous "close" in front of an operator trying to
113
- * refuse. The key kept working, so the layout was hiding a live safety action.
114
- * This surface used to answer that with three hand-written width tiers; it now
115
- * states the same policy as data. Allow and stop are marked critical, Esc is
116
- * marked droppable, and `fitHintEntries` shortens every label before it drops
117
- * anything, which reproduces the three tiers exactly.
166
+ * The footer for a box `innerWidth` columns wide inside its borders. The
167
+ * three columns the bottom border spends on `─ ` and the trailing space come
168
+ * off the top, which is what `buildResponsiveHint` does for every other frame.
118
169
  */
119
- export const permissionOverlayHint = buildResponsiveHint(
120
- [
121
- { key: "Enter", verb: "allow once", short: "allow", critical: true },
122
- { key: "s", verb: "stop turn", short: "stop", critical: true },
123
- ],
124
- { key: "Esc", verb: "close", critical: false },
125
- );
126
-
127
- const SAFETY_SENTENCES: ReadonlyArray<string> = [
128
- "Parked until you decide; allow or deny applies to this call only.",
129
- "Stopping the turn denies it and ends the run, so nothing asks again.",
130
- "Hard-blocked actions remain blocked.",
131
- ];
170
+ export function permissionOverlayHint(innerWidth: number, composerHasDraft = false): string {
171
+ return fitHintEntries(permissionHintEntries(composerHasDraft), innerWidth - 3);
172
+ }
173
+
174
+ function actionConsequence(presentation: DecisionPresentation, id: "deny" | "stop"): string {
175
+ return presentation.requiredActions.find((action) => action.id === id)?.consequence ?? "";
176
+ }
132
177
 
133
178
  /**
134
179
  * The overlay's body, laid out for the width the frame gives it.
@@ -140,15 +185,15 @@ const SAFETY_SENTENCES: ReadonlyArray<string> = [
140
185
  */
141
186
  export function permissionOverlayLines(view: ApprovalRequestView, width: number): string[] {
142
187
  const content = Math.max(8, Math.floor(width));
188
+ const presentation = permissionDecisionPresentation(view);
143
189
  // The parked call is awaiting a decision, not blocked: the raw rejection
144
190
  // short ("<tool> blocked: <class>") is never rendered here because its
145
191
  // wording contradicts the ask. Tool, Target, Action, and the asking axis
146
192
  // carry everything the operator needs to decide.
147
193
  const lines = [
148
- field("Tool: ", view.tool, content),
194
+ field("Tool: ", `${view.tool} · Action: ${view.actionClass}`, content),
149
195
  ...(view.target !== undefined && view.target.length > 0 ? [field("Target: ", view.target, content)] : []),
150
- field("Action: ", view.actionClass, content),
151
- field("Asked by: ", askedBy(view), content),
196
+ ...wrapSentence(`Requested by: ${presentation.requestedByCopy}`, content),
152
197
  ...(view.artifact !== undefined
153
198
  ? [
154
199
  "",
@@ -157,7 +202,12 @@ export function permissionOverlayLines(view: ApprovalRequestView, width: number)
157
202
  ]
158
203
  : []),
159
204
  "",
160
- ...SAFETY_SENTENCES.flatMap((sentence) => wrapSentence(sentence, content)),
205
+ ...wrapSentence(`Approval: ${presentation.authorizationCopy}`, content),
206
+ ...wrapSentence(`Consequence: ${presentation.consequenceCopy}`, content),
207
+ ...wrapSentence(presentation.reversibilityCopy, content),
208
+ ...wrapSentence(`Deny: ${actionConsequence(presentation, "deny")}`, content),
209
+ ...wrapSentence(`Stop: ${actionConsequence(presentation, "stop")}`, content),
210
+ ...wrapSentence("Hard-blocked actions remain blocked.", content),
161
211
  ];
162
212
  if (view.queueDepth !== undefined && view.queueDepth > 1) {
163
213
  lines.splice(lines.indexOf(""), 0, `1 of ${view.queueDepth} parked`);
@@ -13,15 +13,20 @@
13
13
  */
14
14
 
15
15
  import { sanitizeCallTargetText } from "../../domains/safety/call-target.js";
16
+ import { redactSecretString, redactToolArgs } from "../../domains/safety/redaction.js";
16
17
  import { formatSize } from "../../engine/truncate.js";
17
18
  import { visibleWidth, wrapTextWithAnsi } from "../../engine/tui.js";
18
19
  import { classifyResourceRead, toolPresentationPolicy } from "../../tools/presentation.js";
20
+ import { toolResultPresentationPolicy, toolResultPresentationText } from "../../tools/result-disposition.js";
19
21
  import type { ApprovalRequestView } from "../permission-overlay.js";
20
22
  import { clioTheme, formatCompactMs, GLYPH } from "../theme/index.js";
21
23
  import { renderDiffLines } from "./diff.js";
22
24
  import { tryRenderJson, tryRenderXml } from "./structured.js";
23
25
 
24
- export { classifyResourceRead };
26
+ // The argument projection lives in the safety domain so the worker tool seam
27
+ // scrubs by the same rules; re-exported here because this renderer is where
28
+ // callers and tests have always reached for it.
29
+ export { classifyResourceRead, redactToolArgs };
25
30
 
26
31
  const theme = clioTheme();
27
32
  const dim = (text: string): string => theme.fg("dim", text);
@@ -81,6 +86,13 @@ export interface ToolExecutionFinished {
81
86
  * model reading the same transcript, to guess at the rule.
82
87
  */
83
88
  blockReason?: string | undefined;
89
+ /**
90
+ * Working-set eviction reason, when the projection has replaced this
91
+ * result's body for the model. The transcript still renders the full body:
92
+ * the ledger is what the operator scrolls, the projection is only what the
93
+ * next request carries. The tag says the two now differ here.
94
+ */
95
+ evictedReason?: string | undefined;
84
96
  /** Structured exit status when the caller has one; text parsing is legacy fallback only. */
85
97
  exitCode?: number | string | null | undefined;
86
98
  /** Local `!!` bash output is visible to the operator but excluded from model context. */
@@ -119,52 +131,6 @@ function isPlainObject(value: unknown): value is Record<string, unknown> {
119
131
  return value !== null && typeof value === "object" && !Array.isArray(value);
120
132
  }
121
133
 
122
- const SECRET_KEY_RE =
123
- /(?:api[-_]?key|access[-_]?key|auth(?:orization)?|credential|password|private[-_]?key|secret|token)/iu;
124
- const ENV_KEY_RE = /^env(?:ironment)?$/iu;
125
- const SECRET_STRING_RE = /(https?:\/\/[^\s/@]+:)[^\s/@]+@/giu;
126
- const SECRET_URL_PARAM_RE =
127
- /([?&](?:api[-_]?key|access[-_]?token|auth(?:orization)?|credential|password|secret|token)[^=\s]*=)[^&#\s]+/giu;
128
- const SECRET_ASSIGNMENT_RE =
129
- /(?<![A-Z0-9_])((?:[A-Z0-9_]*(?:ACCESS|API|AUTH|CREDENTIAL|KEY|PASS(?:WORD)?|PRIVATE|SECRET|TOKEN)[A-Z0-9_]*)=)[^\s;&|]+/giu;
130
- const SECRET_FLAG_RE =
131
- /(--?(?:[a-z0-9-]*(?:api[-_]?key|access[-_]?token|auth(?:orization)?|credential|password|secret|token)[a-z0-9-]*))(=|\s+)([^\s;&|]+)/giu;
132
-
133
- function redactSecretString(value: string): string {
134
- return value
135
- .replace(SECRET_STRING_RE, "$1[redacted]@")
136
- .replace(SECRET_URL_PARAM_RE, "$1[redacted]")
137
- .replace(SECRET_ASSIGNMENT_RE, "$1[redacted]")
138
- .replace(SECRET_FLAG_RE, "$1$2[redacted]");
139
- }
140
-
141
- function redactEnvironmentValue(value: unknown, depth: number): unknown {
142
- if (depth > 8) return "[redacted nested values]";
143
- if (typeof value === "string") return "[redacted]";
144
- if (Array.isArray(value)) return value.map((item) => redactEnvironmentValue(item, depth + 1));
145
- if (!isPlainObject(value)) return value;
146
- const out: Record<string, unknown> = {};
147
- for (const [childKey, childValue] of Object.entries(value)) {
148
- out[childKey] = redactEnvironmentValue(childValue, depth + 1);
149
- }
150
- return out;
151
- }
152
-
153
- /** Project tool arguments for display only; execution and receipts retain the original values. */
154
- export function redactToolArgs(value: unknown, key = "", depth = 0): unknown {
155
- if (SECRET_KEY_RE.test(key)) return "[redacted]";
156
- if (ENV_KEY_RE.test(key)) return redactEnvironmentValue(value, depth);
157
- if (typeof value === "string") return redactSecretString(value);
158
- if (depth > 8) return "[redacted nested values]";
159
- if (Array.isArray(value)) return value.map((item) => redactToolArgs(item, key, depth + 1));
160
- if (!isPlainObject(value)) return value;
161
- const out: Record<string, unknown> = {};
162
- for (const [childKey, childValue] of Object.entries(value)) {
163
- out[childKey] = redactToolArgs(childValue, childKey, depth + 1);
164
- }
165
- return out;
166
- }
167
-
168
134
  function readStringField(args: unknown, key: string): string | null {
169
135
  if (!isPlainObject(args)) return null;
170
136
  const value = args[key];
@@ -381,6 +347,10 @@ function offloadPathOf(finished: ToolExecutionFinished): string | null {
381
347
  return isPlainObject(resultSize) ? stringField(resultSize, "offloadPath") : null;
382
348
  }
383
349
 
350
+ function offloadFileMissing(finished: ToolExecutionFinished): boolean {
351
+ return finished.resultSummary?.offloadFileMissing === true;
352
+ }
353
+
384
354
  function shownBytesOf(finished: ToolExecutionFinished): number | null {
385
355
  const observation = observationOf(finished);
386
356
  const fromObservation = numberField(observation, "shownBytes");
@@ -456,10 +426,16 @@ function ledgerTail(finished: ToolExecutionFinished): { facts: string; offload:
456
426
  if (details?.outputCapped === true) parts.push("output capped");
457
427
  }
458
428
  if (finished.excludeFromContext === true) parts.push("excluded from context");
429
+ if (finished.evictedReason !== undefined) parts.push("evicted", finished.evictedReason);
459
430
  const offloadPath = executed ? offloadPathOf(finished) : null;
460
431
  return {
461
432
  facts: parts.length > 0 ? dim(` · ${parts.join(" · ")}`) : "",
462
- offload: offloadPath !== null ? dim(` · full: ${offloadPath}`) : "",
433
+ offload:
434
+ offloadPath === null
435
+ ? ""
436
+ : offloadFileMissing(finished)
437
+ ? dim(" · full: gone after the 14-day retention sweep")
438
+ : dim(` · full: ${offloadPath}`),
463
439
  };
464
440
  }
465
441
 
@@ -473,7 +449,9 @@ const FAILURE_EXCERPT_LIMIT = 80;
473
449
  * its failures carry one; the renderer never names a tool here.
474
450
  */
475
451
  function failureExcerpt(finished: ToolExecutionFinished, width: number): string {
476
- if (!finished.isError || !toolPresentationPolicy(finished.toolName, finished.args).failureExcerpt) return "";
452
+ const presentation =
453
+ toolResultPresentationPolicy(finished.result) ?? toolPresentationPolicy(finished.toolName, finished.args);
454
+ if (!finished.isError || !presentation.failureExcerpt) return "";
477
455
  if (isNonExecutedOutcome(finished.outcome)) return "";
478
456
  const text = unwrapResultEnvelope(finished.result);
479
457
  if (typeof text !== "string") return "";
@@ -871,6 +849,8 @@ function renderArgsBody(toolName: string, args: unknown, width: number, isError:
871
849
  */
872
850
  function unwrapResultEnvelope(result: unknown): unknown {
873
851
  if (typeof result === "string" || result === null || result === undefined) return result;
852
+ const presentationText = toolResultPresentationText(result);
853
+ if (presentationText !== null) return presentationText;
874
854
  const blocks = Array.isArray(result)
875
855
  ? result
876
856
  : isPlainObject(result) && Array.isArray(result.content)
@@ -1041,6 +1021,7 @@ function outputFacts(finished: ToolExecutionFinished): string[] {
1041
1021
  }
1042
1022
  if (isPlainObject(finished.result) && finished.result.terminate === true) parts.push("terminal result");
1043
1023
  if (finished.excludeFromContext === true) parts.push("excluded from context");
1024
+ if (finished.evictedReason !== undefined) parts.push("evicted", finished.evictedReason);
1044
1025
  return parts;
1045
1026
  }
1046
1027
 
@@ -1058,7 +1039,10 @@ function renderOutputMeta(
1058
1039
  function renderOutputFooter(finished: ToolExecutionFinished, width: number, isError: boolean): string[] {
1059
1040
  const out: string[] = [];
1060
1041
  const offloadPath = isNonExecutedOutcome(finished.outcome) ? null : offloadPathOf(finished);
1061
- if (offloadPath !== null) out.push(...indentAndWrap(`${yellow("full output")} ${offloadPath}`, width, isError));
1042
+ if (offloadPath !== null) {
1043
+ const pointer = offloadFileMissing(finished) ? "gone after the 14-day retention sweep" : offloadPath;
1044
+ out.push(...indentAndWrap(`${yellow("full output")} ${pointer}`, width, isError));
1045
+ }
1062
1046
  const hint =
1063
1047
  stringField(resultSizeOf(finished), "followUpHint") ?? stringField(finished.resultSummary ?? null, "followUpHint");
1064
1048
  if (hint !== null) out.push(...indentAndWrap(`${dim("next")} ${hint}`, width, isError));
@@ -1174,7 +1158,9 @@ export interface ToolSublineRenderOptions {
1174
1158
  function foldedDiffRows(finished: ToolExecutionFinished, width: number, opts: ToolSublineRenderOptions): string[] {
1175
1159
  if (opts.foldedExtras === "none") return [];
1176
1160
  if (finished.isError || isNonExecutedOutcome(finished.outcome)) return [];
1177
- if (!toolPresentationPolicy(finished.toolName, finished.args).showDiffWhenFolded) return [];
1161
+ const presentation =
1162
+ toolResultPresentationPolicy(finished.result) ?? toolPresentationPolicy(finished.toolName, finished.args);
1163
+ if (!presentation.showDiffWhenFolded) return [];
1178
1164
  const diff = resultDiff(finished.result);
1179
1165
  if (diff === null) return [];
1180
1166
  return truncateRowsMiddle(
@@ -17,6 +17,7 @@
17
17
  * back off replayed thinking text.
18
18
  */
19
19
 
20
+ import { type ResponseModelIdObservation, responseModelIdObservationFromRecord } from "../core/response-model-id.js";
20
21
  import { rawDurationMs } from "../core/timers.js";
21
22
  import { extractReasoningTokens } from "../domains/session/context-accounting.js";
22
23
  import type { MessageEntry, SessionEntry, SessionUsageDefaults } from "../domains/session/index.js";
@@ -133,6 +134,10 @@ export function lastTurnSummaryFromLedger(
133
134
  let toolErrorCount = 0;
134
135
  let targetId = turn.targetId;
135
136
  let modelId = turn.modelId;
137
+ let responseModelIdObservation: ResponseModelIdObservation = {
138
+ state: "legacy-difference-only",
139
+ differingModelId: null,
140
+ };
136
141
 
137
142
  for (const row of turn.rows) {
138
143
  const record = payloadRecord(row.payload);
@@ -148,7 +153,8 @@ export function lastTurnSummaryFromLedger(
148
153
  const reason = stopReasonOf(record.stopReason);
149
154
  if (reason && reason !== "stop") stopReason = reason;
150
155
  targetId = targetId ?? nonEmptyString(record, "provider", "api");
151
- modelId = modelId ?? nonEmptyString(record, "responseModel", "model");
156
+ modelId = modelId ?? nonEmptyString(record, "model");
157
+ responseModelIdObservation = responseModelIdObservationFromRecord(record, "legacy-difference-only");
152
158
  const usage = payloadRecord(record.usage);
153
159
  if (!usage) continue;
154
160
  inputTokens += positiveNumber(usage.input);
@@ -177,6 +183,7 @@ export function lastTurnSummaryFromLedger(
177
183
  stopReason,
178
184
  watchdogPeak: 0,
179
185
  truncated: false,
186
+ responseModelIdObservation,
180
187
  };
181
188
  if (sawReasoning) {
182
189
  summary.reasoningTokens = reasoningTokens;