@iowarp/clio-coder 0.3.8 → 0.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (333) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +7 -3
  3. package/dist/{acp-U67UHUK2.js → acp-7LOELQFP.js} +6 -6
  4. package/dist/{agents-YU6SGALZ.js → agents-FIBG2SHA.js} +27 -25
  5. package/dist/assets/codewiki.json +1 -1
  6. package/dist/{auth-5ZPJOIVG.js → auth-OI4LIH2I.js} +11 -12
  7. package/dist/{builtins-C6JMZVV6.js → builtins-AD25UL3C.js} +5 -5
  8. package/dist/{chunk-5FR74PWO.js → chunk-2JDWVJND.js} +2 -2
  9. package/dist/chunk-3DPEIQKN.js +113 -0
  10. package/dist/{chunk-4SPRNWDE.js → chunk-3DUR4WUA.js} +15 -15
  11. package/dist/{chunk-VHN4MY6O.js → chunk-3MRC2YSQ.js} +2 -2
  12. package/dist/{chunk-5DHKRSMQ.js → chunk-3UUY7R3Z.js} +11 -7
  13. package/dist/{chunk-IGWKHNIQ.js → chunk-3V5AYSEQ.js} +8 -8
  14. package/dist/{chunk-FHJEP5SW.js → chunk-465CC7FK.js} +8 -5
  15. package/dist/{chunk-5WIGXA4T.js → chunk-47CMYGET.js} +111 -4
  16. package/dist/{chunk-A3WNZD3P.js → chunk-4H6ULJ3H.js} +67 -21
  17. package/dist/{chunk-TB5666IT.js → chunk-4LJX2PUC.js} +3 -3
  18. package/dist/{chunk-XWSF374K.js → chunk-56KB5IJP.js} +2 -2
  19. package/dist/{chunk-SPULKLCF.js → chunk-5DQRIYDZ.js} +2 -2
  20. package/dist/{chunk-2HEJ2F35.js → chunk-5HFBWUMU.js} +20 -8
  21. package/dist/{chunk-WNIJTQQK.js → chunk-5PVQ4SRS.js} +78 -6
  22. package/dist/{chunk-DYIM5TJT.js → chunk-5QKCQQ3E.js} +262 -6
  23. package/dist/{chunk-TYPGUK6W.js → chunk-5T7RBWN2.js} +111 -5
  24. package/dist/{chunk-IIZWH4XA.js → chunk-774ILSRL.js} +2 -2
  25. package/dist/chunk-7C6RYZGQ.js +391 -0
  26. package/dist/{chunk-TANS5ZJS.js → chunk-AD7Y7STJ.js} +3 -3
  27. package/dist/{chunk-RWSI4YD7.js → chunk-AEYBF3TB.js} +33 -12
  28. package/dist/{chunk-DGSYXYMX.js → chunk-AMKHQW3C.js} +2 -2
  29. package/dist/{chunk-VWZOAB7K.js → chunk-B5XRQOLB.js} +7 -7
  30. package/dist/{chunk-WXY7KU3G.js → chunk-BVDVID7E.js} +2 -2
  31. package/dist/{chunk-PMDBGQSJ.js → chunk-CA42X6KT.js} +2 -2
  32. package/dist/{chunk-HLE42MG7.js → chunk-D73KXYPF.js} +3 -3
  33. package/dist/{chunk-5Q2VVUKB.js → chunk-DG4M6ZUE.js} +3 -3
  34. package/dist/{chunk-ME6CCNFO.js → chunk-EBOC7MT3.js} +6 -6
  35. package/dist/{chunk-MXKJU4JB.js → chunk-ECUO3KDP.js} +48 -7
  36. package/dist/{chunk-26LEYJZH.js → chunk-FALJGAWU.js} +2 -2
  37. package/dist/{chunk-GWS3VEIW.js → chunk-FWDFM5ZU.js} +24 -3
  38. package/dist/{chunk-7RGZWPB6.js → chunk-GAYUJ7LE.js} +67 -13
  39. package/dist/{chunk-VCBR6CU7.js → chunk-HAY4ZE2P.js} +2 -2
  40. package/dist/{chunk-FBVTI2TJ.js → chunk-HCBCAYZU.js} +11 -130
  41. package/dist/{chunk-WSB3FPX7.js → chunk-HJB5IUKP.js} +32 -136
  42. package/dist/{chunk-J3YUBZWY.js → chunk-HKO36JWF.js} +33 -5
  43. package/dist/{chunk-E77JEWSD.js → chunk-HPCTNZM2.js} +6 -36
  44. package/dist/{chunk-FJ3H4MN5.js → chunk-IHKBWSXF.js} +2 -2
  45. package/dist/{chunk-NMPKI6XL.js → chunk-JEQQR47K.js} +37 -18
  46. package/dist/{chunk-3BINW3FP.js → chunk-KV2AOLDF.js} +24 -4
  47. package/dist/{chunk-YS5VLNH5.js → chunk-LXPJXFM5.js} +7 -7
  48. package/dist/{chunk-7RFXX52T.js → chunk-MIX5N5AC.js} +271 -41
  49. package/dist/{chunk-K4XHGFR5.js → chunk-MLOK6ZOS.js} +1297 -218
  50. package/dist/{chunk-ZNLWCMVZ.js → chunk-MV2VUEJC.js} +2 -2
  51. package/dist/{chunk-MVVUPGPW.js → chunk-MXHC5QYU.js} +6 -6
  52. package/dist/{chunk-5H3GB5BO.js → chunk-N3PBVRTZ.js} +4 -382
  53. package/dist/{chunk-2HFZQUHL.js → chunk-N5XKWMDW.js} +17 -7
  54. package/dist/{chunk-5C3AQNDW.js → chunk-NNNWO6F2.js} +124 -36
  55. package/dist/{chunk-GU2UIAFZ.js → chunk-NQ6UCCOD.js} +3 -3
  56. package/dist/{chunk-N22QMJKY.js → chunk-NZU6YDNV.js} +4 -4
  57. package/dist/{chunk-ZVJ5BLO2.js → chunk-O6I4CIEU.js} +151 -13
  58. package/dist/{chunk-XN3L4EYL.js → chunk-OEDBCISO.js} +2 -2
  59. package/dist/{chunk-VAWNZU7Z.js → chunk-P3JGPQFL.js} +2 -2
  60. package/dist/{chunk-IJ7RPIYJ.js → chunk-PNY46YEY.js} +20 -3
  61. package/dist/{chunk-U6MBIEMB.js → chunk-PZ4I4JE2.js} +56 -35
  62. package/dist/{chunk-GPIEI3LY.js → chunk-QQ7EKM72.js} +2 -2
  63. package/dist/{chunk-WLFILSD5.js → chunk-R7LNVMCS.js} +66 -28
  64. package/dist/{chunk-GOXNB3AO.js → chunk-RAPCMZL4.js} +75 -4
  65. package/dist/chunk-RKKLTLYB.js +45 -0
  66. package/dist/{chunk-TT36MB5S.js → chunk-RKRLDWD3.js} +3 -1
  67. package/dist/{chunk-TTHACPOM.js → chunk-S4COXYBG.js} +456 -18
  68. package/dist/{chunk-WWCZ5F23.js → chunk-T3Z6VAAF.js} +69 -10
  69. package/dist/{chunk-GN57SG4G.js → chunk-TD7UE2L5.js} +9 -7
  70. package/dist/{chunk-TLQJPP24.js → chunk-TEO2TLVN.js} +523 -322
  71. package/dist/{chunk-FCSXB6T2.js → chunk-UOSL25KY.js} +14 -2
  72. package/dist/{chunk-KTYTFRMB.js → chunk-VKBMFOYV.js} +17 -15
  73. package/dist/{chunk-PT7HYKEM.js → chunk-VO2LKSTM.js} +2 -2
  74. package/dist/{chunk-P43ETTHK.js → chunk-VPTUJU4P.js} +2 -2
  75. package/dist/{chunk-WEH5XRJQ.js → chunk-WIE7ZOSW.js} +2 -2
  76. package/dist/{chunk-EMYUUSFG.js → chunk-WXCJ7VME.js} +5 -5
  77. package/dist/{chunk-4DGYLA73.js → chunk-XDOQXGFO.js} +22 -7
  78. package/dist/{chunk-JOZYP4GM.js → chunk-YKOFT37S.js} +5 -5
  79. package/dist/chunk-YSEHGPCT.js +127 -0
  80. package/dist/{chunk-HFSBBKSQ.js → chunk-YW7UVM5V.js} +138 -3
  81. package/dist/cli/index.js +27 -27
  82. package/dist/{clio-QVTYJ57A.js → clio-LT5V7SSZ.js} +6 -6
  83. package/dist/{code-nav-FGGFIE7L.js → code-nav-LMW275PA.js} +4 -4
  84. package/dist/{config-LW5IJFQN.js → config-RXS5T3JT.js} +71 -43
  85. package/dist/{configure-7XIZCOU4.js → configure-2WYWSCSD.js} +14 -15
  86. package/dist/{context-Y6Y7QPR6.js → context-I3BTOTCS.js} +12 -12
  87. package/dist/{context-L3WL3X7K.js → context-MVOORGMF.js} +34 -33
  88. package/dist/{context-N52ZA626.js → context-PALKKQYL.js} +20 -20
  89. package/dist/{context-clear-MBQRLSDQ.js → context-clear-N2WOYZ2K.js} +34 -33
  90. package/dist/{context-index-HVMFQHK3.js → context-index-HNG3MOME.js} +2 -2
  91. package/dist/{context-working-set-GS6DSO7F.js → context-working-set-MIEVECVZ.js} +10 -11
  92. package/dist/{dispatch-runner-22ZCNOM3.js → dispatch-runner-VVA4SRRH.js} +34 -33
  93. package/dist/doctor-TWBWFK5V.js +165 -0
  94. package/dist/{eval-BEC2WHDA.js → eval-IJ5VEZDJ.js} +2016 -142
  95. package/dist/{evidence-REJUMSKM.js → evidence-L5APPXNV.js} +29 -28
  96. package/dist/{evolve-PY5ZBA5K.js → evolve-RGNKFJ52.js} +29 -28
  97. package/dist/{extensions-HVKU65YU.js → extensions-7WYWUX5A.js} +9 -3
  98. package/dist/{fleet-7WZEWRFA.js → fleet-6CNVBZZP.js} +87 -55
  99. package/dist/{fleet-commands-UVHWM76J.js → fleet-commands-L2SXSYEI.js} +6 -6
  100. package/dist/{fleet-graph-6ULH7PES.js → fleet-graph-2J3OOIPO.js} +14 -12
  101. package/dist/{fleet-preflight-J53T6CCE.js → fleet-preflight-CZRJ4JP5.js} +3 -4
  102. package/dist/{fleet-validate-72PC4SLA.js → fleet-validate-C5RI6DP7.js} +16 -15
  103. package/dist/{init-OG3TPGQG.js → init-VBN2ACVA.js} +50 -48
  104. package/dist/{library-CNTMPLRF.js → library-JHGUMLY2.js} +13 -11
  105. package/dist/{memory-6IS7F275.js → memory-K4OQIYWG.js} +31 -30
  106. package/dist/{models-ENRJDA5W.js → models-2NCZUWDD.js} +23 -22
  107. package/dist/{monitor-XLDVO7TN.js → monitor-MMVTJABD.js} +35 -34
  108. package/dist/{orchestrator-6KSPYRHA.js → orchestrator-ZKBPCHW6.js} +1627 -294
  109. package/dist/{reset-RZ4ER727.js → reset-DD5JGOY3.js} +3 -3
  110. package/dist/{run-Y2CNK5RU.js → run-QEGNX7FL.js} +56 -55
  111. package/dist/{share-A55GYP6Z.js → share-JKD3BQMW.js} +13 -11
  112. package/dist/{skills-ALC5J6AT.js → skills-LMQIKDOZ.js} +14 -12
  113. package/dist/{skills-eval-JPBEBYQU.js → skills-eval-I7X2774U.js} +34 -32
  114. package/dist/{steer-GGWFUJUD.js → steer-CF5TDANS.js} +3 -3
  115. package/dist/{support-MIETYA5E.js → support-I7LOJLIF.js} +4 -4
  116. package/dist/{targets-VGNXIR3S.js → targets-RUSR6B5Z.js} +58 -32
  117. package/dist/{terminal-lease-WOBR64YA.js → terminal-lease-QYVORFR4.js} +6 -4
  118. package/dist/{trace-PNCASAXC.js → trace-ODOQIVIW.js} +61 -6
  119. package/dist/{upgrade-FUSUAGHR.js → upgrade-XANW3FXB.js} +17 -16
  120. package/dist/{usage-N4MKVHKD.js → usage-4H7ZRXQT.js} +88 -44
  121. package/dist/{verifiers-YAWOJ3H2.js → verifiers-UZXNBZEB.js} +6 -6
  122. package/dist/{verify-LTDHYBGY.js → verify-BVKWTNDL.js} +5 -5
  123. package/dist/{wiki-generate-6M7GHTBJ.js → wiki-generate-MY7WV2QI.js} +49 -47
  124. package/dist/worker/entry.js +29 -33
  125. package/docs/alcf-provider.md +1 -1
  126. package/docs/architecture.md +1 -1
  127. package/docs/artifact-versions.md +6 -1
  128. package/docs/built-in-agents.md +1 -1
  129. package/docs/capacity-and-scheduling.md +23 -2
  130. package/docs/commands-and-modes.md +1 -1
  131. package/docs/configuration-and-targets.md +30 -3
  132. package/docs/context-engine.md +63 -4
  133. package/docs/documentation-coverage.md +3 -3
  134. package/docs/documentation-guide.md +1 -1
  135. package/docs/environment-variables.md +2 -0
  136. package/docs/eval-runner.md +262 -11
  137. package/docs/evals-internal.md +72 -2
  138. package/docs/evidence-and-memory.md +11 -10
  139. package/docs/evolution.md +1 -1
  140. package/docs/extensions-and-sharing.md +3 -1
  141. package/docs/fleet-dispatch.md +4 -4
  142. package/docs/installation-and-lifecycle.md +1 -1
  143. package/docs/middleware-and-components.md +1 -1
  144. package/docs/model-catalog.md +1 -1
  145. package/docs/observability.md +53 -2
  146. package/docs/proactive-memory.md +127 -14
  147. package/docs/prompt-envelope-and-tools.md +19 -1
  148. package/docs/provider-adapter-cookbook.md +1 -1
  149. package/docs/release-cut-checklist.md +19 -3
  150. package/docs/safety-model.md +1 -1
  151. package/docs/scientific-validation.md +1 -1
  152. package/docs/skills-marketplace.md +1 -1
  153. package/docs/tool-usage.md +1 -1
  154. package/docs/trace-store.md +1 -1
  155. package/docs/troubleshooting.md +87 -0
  156. package/docs/tui-design.md +1 -1
  157. package/docs/worker-dispatch-mechanics.md +1 -1
  158. package/package.json +2 -1
  159. package/src/cli/agents.ts +1 -1
  160. package/src/cli/config-inspect.ts +33 -6
  161. package/src/cli/config.ts +1 -1
  162. package/src/cli/doctor-state-size.ts +82 -0
  163. package/src/cli/doctor.ts +3 -1
  164. package/src/cli/eval.ts +80 -16
  165. package/src/cli/extensions.ts +5 -1
  166. package/src/cli/fleet.ts +32 -3
  167. package/src/cli/targets.ts +44 -13
  168. package/src/cli/trace.ts +63 -4
  169. package/src/cli/usage.ts +63 -14
  170. package/src/core/bus-events.ts +29 -1
  171. package/src/core/cache-telemetry.ts +42 -0
  172. package/src/core/config.ts +18 -0
  173. package/src/core/defaults.ts +36 -6
  174. package/src/core/endpoint-key.ts +27 -0
  175. package/src/core/residency-target-key.ts +25 -0
  176. package/src/core/response-schema.ts +36 -2
  177. package/src/domains/config/classify.ts +3 -0
  178. package/src/domains/context/codewiki/coordinator.ts +12 -4
  179. package/src/domains/dispatch/admission.ts +40 -3
  180. package/src/domains/dispatch/capacity-lease.ts +98 -9
  181. package/src/domains/dispatch/contract.ts +11 -0
  182. package/src/domains/dispatch/execution-plan.ts +44 -4
  183. package/src/domains/dispatch/extension.ts +166 -42
  184. package/src/domains/dispatch/fleet-run.ts +23 -3
  185. package/src/domains/dispatch/heartbeat.ts +32 -8
  186. package/src/domains/dispatch/index.ts +3 -0
  187. package/src/domains/dispatch/orphan-recovery.ts +5 -0
  188. package/src/domains/dispatch/reservation-store.ts +116 -8
  189. package/src/domains/dispatch/state.ts +4 -0
  190. package/src/domains/dispatch/worker-spawn.ts +25 -11
  191. package/src/domains/dispatch/write-boundary-enforcer.ts +20 -3
  192. package/src/domains/dispatch/write-boundary.ts +62 -1
  193. package/src/domains/eval/artifacts/store.ts +62 -0
  194. package/src/domains/eval/compare/behavioral.ts +224 -0
  195. package/src/domains/eval/compare/compare.ts +355 -2
  196. package/src/domains/eval/compare/envelope.ts +128 -0
  197. package/src/domains/eval/compare/gates.ts +24 -6
  198. package/src/domains/eval/compare/thresholds.ts +30 -3
  199. package/src/domains/eval/execution-provenance.ts +240 -0
  200. package/src/domains/eval/metrics/aggregate.ts +136 -0
  201. package/src/domains/eval/metrics/call-ledger-stream.ts +112 -0
  202. package/src/domains/eval/metrics/tracked.ts +413 -0
  203. package/src/domains/eval/provenance.ts +117 -0
  204. package/src/domains/eval/reports/comparison.ts +128 -0
  205. package/src/domains/eval/reports/junit.ts +17 -3
  206. package/src/domains/eval/reports/markdown.ts +3 -3
  207. package/src/domains/eval/reports/text.ts +14 -0
  208. package/src/domains/eval/run-compare.ts +20 -0
  209. package/src/domains/eval/runners/clio-run.ts +127 -0
  210. package/src/domains/eval/runners/external-command.ts +28 -3
  211. package/src/domains/eval/schema/adapter.ts +111 -0
  212. package/src/domains/eval/schema/artifact.ts +20 -0
  213. package/src/domains/eval/schema/behavioral-metrics.ts +204 -0
  214. package/src/domains/eval/schema/behavioral.ts +520 -0
  215. package/src/domains/eval/schema/execution-envelope.ts +194 -0
  216. package/src/domains/eval/schema/serving.ts +74 -0
  217. package/src/domains/eval/schema/suite.ts +38 -8
  218. package/src/domains/eval/schema/validate.ts +58 -3
  219. package/src/domains/eval/schema/verdict.ts +237 -0
  220. package/src/domains/eval/suites/resolve.ts +2 -0
  221. package/src/domains/eval/suites/run.ts +264 -33
  222. package/src/domains/eval/verifiers/command.ts +2 -1
  223. package/src/domains/eval/workspaces/temp-copy.ts +145 -13
  224. package/src/domains/evidence/build.ts +2 -13
  225. package/src/domains/evidence/eval.ts +2 -12
  226. package/src/domains/evidence/findings-markdown.ts +33 -0
  227. package/src/domains/evidence/run-trust.ts +7 -113
  228. package/src/domains/evidence/trust-projection.ts +2 -2
  229. package/src/domains/extensions/compatibility.ts +285 -0
  230. package/src/domains/extensions/discovery.ts +38 -3
  231. package/src/domains/extensions/resources.ts +1 -1
  232. package/src/domains/extensions/state.ts +12 -3
  233. package/src/domains/extensions/types.ts +2 -0
  234. package/src/domains/lifecycle/doctor.ts +69 -1
  235. package/src/domains/memory/index.ts +14 -0
  236. package/src/domains/memory/task-bank-promotion.ts +64 -0
  237. package/src/domains/memory/task-memory-policy.ts +77 -8
  238. package/src/domains/memory/task-memory-spend.ts +131 -0
  239. package/src/domains/memory/task-memory-status.ts +7 -0
  240. package/src/domains/memory/task-memory-telemetry.ts +2 -0
  241. package/src/domains/middleware/index.ts +1 -0
  242. package/src/domains/middleware/memory-intervention.ts +69 -5
  243. package/src/domains/middleware/memory-step-endpoint.ts +71 -0
  244. package/src/domains/observability/background-memory-usage.ts +140 -0
  245. package/src/domains/observability/cost.ts +1 -1
  246. package/src/domains/observability/index.ts +7 -0
  247. package/src/domains/observability/out-of-turn-usage.ts +51 -2
  248. package/src/domains/observability/trace-store.ts +192 -2
  249. package/src/domains/prompts/compiler.ts +100 -13
  250. package/src/domains/providers/endpoint-capacity.ts +96 -0
  251. package/src/domains/providers/index.ts +10 -0
  252. package/src/domains/providers/models/local-models/clio-local-coding-targets.yaml +243 -1
  253. package/src/domains/providers/runtime-resolution.ts +8 -1
  254. package/src/domains/providers/runtimes/common/probe-helpers.ts +31 -9
  255. package/src/domains/providers/runtimes/local-native/llamacpp-anthropic.ts +1 -1
  256. package/src/domains/providers/runtimes/local-native/llamacpp-completion.ts +1 -1
  257. package/src/domains/providers/runtimes/local-native/llamacpp-embed.ts +1 -1
  258. package/src/domains/providers/runtimes/local-native/llamacpp-rerank.ts +1 -1
  259. package/src/domains/providers/runtimes/local-native/llamacpp.ts +4 -1
  260. package/src/domains/providers/runtimes/local-native/lmstudio.ts +4 -1
  261. package/src/domains/providers/runtimes/local-native/ollama-native.ts +6 -1
  262. package/src/domains/providers/types/capability-flags.ts +2 -0
  263. package/src/domains/providers/types/target-descriptor.ts +2 -0
  264. package/src/domains/resources/prompts/loader.ts +95 -33
  265. package/src/domains/safety/call-target.ts +52 -0
  266. package/src/domains/safety/run-effects.ts +35 -4
  267. package/src/domains/session/context-accounting.ts +52 -1
  268. package/src/domains/session/context-ledger.ts +37 -13
  269. package/src/domains/session/index.ts +6 -0
  270. package/src/domains/session/prompt-cache.ts +140 -0
  271. package/src/domains/session/prompt-manifest.ts +42 -0
  272. package/src/engine/acp/adapter.ts +18 -3
  273. package/src/engine/ai.ts +35 -0
  274. package/src/engine/apis/llamacpp-residency.ts +55 -3
  275. package/src/engine/apis/lmstudio.ts +25 -5
  276. package/src/engine/apis/ollama-native.ts +2 -1
  277. package/src/engine/apis/openai-completions.ts +80 -17
  278. package/src/engine/apis/residency-lock.ts +3 -1
  279. package/src/engine/apis/residency.ts +34 -1
  280. package/src/engine/provider-payload.ts +29 -1
  281. package/src/entry/orchestrator.ts +176 -30
  282. package/src/interactive/chat-loop-messages.ts +26 -7
  283. package/src/interactive/chat-loop.ts +318 -41
  284. package/src/interactive/chat-panel.ts +62 -8
  285. package/src/interactive/clio-editor.ts +45 -8
  286. package/src/interactive/context-activity.ts +5 -1
  287. package/src/interactive/context-meter.ts +1 -1
  288. package/src/interactive/context-overlay.ts +40 -10
  289. package/src/interactive/cost-overlay.ts +64 -6
  290. package/src/interactive/dispatch-board.ts +84 -12
  291. package/src/interactive/fleet-run-preview.ts +41 -15
  292. package/src/interactive/handoff-round.ts +41 -2
  293. package/src/interactive/interactive-application.ts +24 -1
  294. package/src/interactive/interactive-input-runtime.ts +8 -0
  295. package/src/interactive/interactive-presentation.ts +4 -0
  296. package/src/interactive/interactive-shell.ts +20 -17
  297. package/src/interactive/interactive-slash-runtime.ts +27 -4
  298. package/src/interactive/memory-overlay.ts +8 -0
  299. package/src/interactive/mutation-preview.ts +295 -0
  300. package/src/interactive/overlay-general-openers.ts +16 -0
  301. package/src/interactive/overlay-key-routing.ts +38 -0
  302. package/src/interactive/overlay-lifecycle.ts +38 -5
  303. package/src/interactive/overlay-permission-lifecycle.ts +22 -2
  304. package/src/interactive/overlay-session-lifecycle.ts +73 -9
  305. package/src/interactive/overlays/ask-user.ts +91 -19
  306. package/src/interactive/overlays/help-reference.ts +4 -0
  307. package/src/interactive/overlays/prompts.ts +11 -1
  308. package/src/interactive/overlays/settings.ts +35 -1
  309. package/src/interactive/permission-hint.ts +34 -2
  310. package/src/interactive/permission-overlay.ts +159 -9
  311. package/src/interactive/prewarm.ts +197 -0
  312. package/src/interactive/render-trace.ts +162 -15
  313. package/src/interactive/renderers/tool-execution.ts +4 -0
  314. package/src/interactive/side-question.ts +58 -1
  315. package/src/interactive/status/controller.ts +11 -0
  316. package/src/interactive/status/state-machine.ts +54 -2
  317. package/src/interactive/status/types.ts +7 -0
  318. package/src/interactive/terminal-lease.ts +2 -0
  319. package/src/interactive/turn-context.ts +299 -31
  320. package/src/interactive/turn-persistence.ts +14 -4
  321. package/src/interactive/turn-prewarm.ts +364 -0
  322. package/src/interactive/turn-queues.ts +7 -4
  323. package/src/interactive/turn-runtime.ts +8 -1
  324. package/src/interactive/turn-state.ts +23 -0
  325. package/src/interactive/view/view-overlay.ts +28 -3
  326. package/src/tools/ask-user.ts +43 -2
  327. package/src/tools/dispatch-plan.ts +17 -9
  328. package/src/tools/dispatch-scout.ts +1 -1
  329. package/src/tools/registry.ts +16 -0
  330. package/dist/chunk-AOCYTWAV.js +0 -449
  331. package/dist/chunk-HWUFFB6L.js +0 -83
  332. package/dist/chunk-R346GLFC.js +0 -31
  333. package/dist/doctor-M7YEDGAE.js +0 -91
@@ -129,6 +129,14 @@ interface QuestionState {
129
129
  customAnswer: string;
130
130
  inputValue: string;
131
131
  answer: string;
132
+ /**
133
+ * The operator's typed text exactly as submitted, empty when they typed
134
+ * nothing. `answer` is the one-line rendering of the whole decision and gets
135
+ * trimmed and joined; this is the record of what they actually typed, and
136
+ * losing it is what made an interview re-ask for the same figures three times
137
+ * (issue #228).
138
+ */
139
+ rawValue: string;
132
140
  focusedValue?: string;
133
141
  }
134
142
 
@@ -154,6 +162,7 @@ function createQuestionState(question: AskUserQuestion): QuestionState {
154
162
  customAnswer: "",
155
163
  inputValue: "",
156
164
  answer: "",
165
+ rawValue: "",
157
166
  };
158
167
  }
159
168
 
@@ -205,12 +214,18 @@ function optionIndexFromValue(value: string): number | null {
205
214
  return Number.isInteger(index) && index >= 0 ? index : null;
206
215
  }
207
216
 
208
- function answerText(question: AskUserQuestion, selected: ReadonlySet<number>, customAnswer: string): string {
209
- const parts: string[] = [];
217
+ /** The labels the operator chose, in list order. */
218
+ function selectedOptionLabels(question: AskUserQuestion, selected: ReadonlySet<number>): string[] {
219
+ const labels: string[] = [];
210
220
  for (const index of [...selected].sort((a, b) => a - b)) {
211
221
  const label = question.options?.[index]?.label;
212
- if (label) parts.push(label);
222
+ if (label) labels.push(label);
213
223
  }
224
+ return labels;
225
+ }
226
+
227
+ function answerText(question: AskUserQuestion, selected: ReadonlySet<number>, customAnswer: string): string {
228
+ const parts = selectedOptionLabels(question, selected);
214
229
  const custom = customAnswer.trim();
215
230
  if (custom.length > 0) parts.push(custom);
216
231
  return parts.join("; ");
@@ -348,6 +363,10 @@ class AskUserOverlayView implements Component {
348
363
  this.commitMultiSelectOrOpenOther(state);
349
364
  return;
350
365
  }
366
+ if (this.isAddTextKey(data)) {
367
+ this.chooseFocusedAndOpenText(question, state);
368
+ return;
369
+ }
351
370
  this.list?.handleInput(data);
352
371
  }
353
372
 
@@ -502,24 +521,22 @@ class AskUserOverlayView implements Component {
502
521
  // The classified action label distinguishes recording an answer from
503
522
  // granting authority. Esc keeps the product's one word for the way out,
504
523
  // since it closes the prompt and the interview together.
524
+ // `t` is on every select footer because the operator cannot tell from a
525
+ // label whether the option needs a figure attached until they read it.
526
+ const addText: HintEntry = { key: "t", verb: "add text", short: "text" };
505
527
  if (question.multi_select === true) {
506
528
  return this.questions.length > 1
507
529
  ? this.withScrollHint([
508
530
  { key: "Left/Right", verb: "question" },
509
531
  { key: "Space", verb: "toggle" },
532
+ addText,
510
533
  { key: "Enter", verb: recordAnswer },
511
534
  ])
512
- : this.withScrollHint([
513
- { key: "Space", verb: "toggle" },
514
- { key: "Enter", verb: recordAnswer },
515
- ]);
535
+ : this.withScrollHint([{ key: "Space", verb: "toggle" }, addText, { key: "Enter", verb: recordAnswer }]);
516
536
  }
517
537
  return this.questions.length > 1
518
- ? this.withScrollHint([
519
- { key: "Left/Right", verb: "question" },
520
- { key: "Enter", verb: recordAnswer },
521
- ])
522
- : this.withScrollHint([{ key: "Enter", verb: recordAnswer }]);
538
+ ? this.withScrollHint([{ key: "Left/Right", verb: "question" }, addText, { key: "Enter", verb: recordAnswer }])
539
+ : this.withScrollHint([addText, { key: "Enter", verb: recordAnswer }]);
523
540
  }
524
541
 
525
542
  private renderDecisionContext(width: number): string[] {
@@ -644,6 +661,34 @@ class AskUserOverlayView implements Component {
644
661
  return this.questions.length > 1 && (matchesKey(data, "alt+right") || matchesKey(data, "ctrl+right"));
645
662
  }
646
663
 
664
+ /**
665
+ * The key that adds typed text to the option under the cursor.
666
+ *
667
+ * Choosing an option used to be the whole answer, so a model that offered
668
+ * "Exact number - I'll type it" gave the operator a label and nowhere to put
669
+ * the number; the interview then spent two of its four rounds asking for the
670
+ * figures again (issue #228). The list itself only reads the arrows, Enter,
671
+ * and Esc, so a letter is free here.
672
+ */
673
+ private isAddTextKey(data: string): boolean {
674
+ return matchesKey(data, "t");
675
+ }
676
+
677
+ /**
678
+ * Take the focused option and open the text field for it, without committing.
679
+ * Submitting the text records the label and the text together.
680
+ */
681
+ private chooseFocusedAndOpenText(question: AskUserQuestion, state: QuestionState): void {
682
+ const current = this.list?.getSelectedItem();
683
+ const optionIndex = current && current.value !== "other" ? optionIndexFromValue(current.value) : null;
684
+ if (optionIndex !== null) {
685
+ if (question.multi_select === true) state.selected.add(optionIndex);
686
+ else state.selected = new Set<number>([optionIndex]);
687
+ state.focusedValue = `option:${optionIndex}`;
688
+ }
689
+ this.openTextInput(state.selected.size > 0 ? "Add your answer to the chosen option" : "Other answer");
690
+ }
691
+
647
692
  /**
648
693
  * The row above the question, or nothing.
649
694
  *
@@ -829,14 +874,20 @@ class AskUserOverlayView implements Component {
829
874
  return;
830
875
  }
831
876
  state.inputValue = answer;
877
+ // Verbatim, before any joining or trimming the display line does. The
878
+ // typed text is the answer; the one-line `answer` is a rendering of it.
879
+ state.rawValue = value;
832
880
  if (questionHasOptions(question) && question.multi_select === true) {
833
881
  state.customAnswer = answer;
834
882
  this.commitCurrentAnswer();
835
- } else {
836
- state.customAnswer = questionHasOptions(question) ? answer : "";
837
- state.answer = answer;
838
- this.finishIfCompleteOrAdvance();
883
+ return;
839
884
  }
885
+ state.customAnswer = questionHasOptions(question) ? answer : "";
886
+ // The chosen labels and the typed text compose, so a question answered
887
+ // with an option that says "I will type it" keeps both. With nothing
888
+ // selected, which is the implicit Other path, this is the text alone.
889
+ state.answer = questionHasOptions(question) ? answerText(question, state.selected, state.customAnswer) : answer;
890
+ this.finishIfCompleteOrAdvance();
840
891
  };
841
892
  activeInput.onEscape = () => this.cancel();
842
893
  this.input = activeInput;
@@ -866,7 +917,14 @@ class AskUserOverlayView implements Component {
866
917
  return;
867
918
  }
868
919
  state.selected = new Set<number>([optionIndex]);
869
- state.answer = question.options?.[optionIndex]?.label ?? item.label;
920
+ // Choosing a plain option is a label-only answer, so the typed text
921
+ // clears with the choice: it was given for a label the operator has
922
+ // just moved off, and the composed answer runs through the same
923
+ // `answerText` every other path uses.
924
+ state.customAnswer = "";
925
+ state.rawValue = "";
926
+ state.inputValue = "";
927
+ state.answer = answerText(question, state.selected, state.customAnswer) || item.label;
870
928
  state.focusedValue = item.value;
871
929
  this.status = "";
872
930
  this.finishIfCompleteOrAdvance();
@@ -992,12 +1050,26 @@ class AskUserOverlayView implements Component {
992
1050
  return this.states.length > 0 && this.states.every((state) => state.answer.trim().length > 0);
993
1051
  }
994
1052
 
1053
+ /**
1054
+ * What the round captured, as three separable facts per question: the one-line
1055
+ * answer, the labels chosen, and the text typed. A reader tells a label-only
1056
+ * answer from a label-plus-value one by whether `value` is there, which the
1057
+ * joined string alone could never say.
1058
+ */
995
1059
  private answers(): AskUserResult["answers"] {
996
1060
  const answers: AskUserResult["answers"] = [];
997
1061
  for (let index = 0; index < this.questions.length; index += 1) {
998
1062
  const question = this.questions[index];
999
- const answer = this.states[index]?.answer.trim();
1000
- if (question && answer && answer.length > 0) answers.push({ question: question.question, answer });
1063
+ const state = this.states[index];
1064
+ const answer = state?.answer.trim();
1065
+ if (!question || !state || !answer || answer.length === 0) continue;
1066
+ const options = selectedOptionLabels(question, state.selected);
1067
+ answers.push({
1068
+ question: question.question,
1069
+ answer,
1070
+ ...(options.length > 0 ? { options } : {}),
1071
+ ...(state.rawValue.length > 0 ? { value: state.rawValue } : {}),
1072
+ });
1001
1073
  }
1002
1074
  return answers;
1003
1075
  }
@@ -129,6 +129,10 @@ export function openHelpOverlay(
129
129
  "auto-edit parks unrecognized commands, and full-auto runs them.",
130
130
  "**Approvals**: a parked call waits for a one-shot operator decision; approving resumes only that call. " +
131
131
  "Workers resolve asks per `workers.onPermission` (Approvals Routing); headless runs auto-deny.",
132
+ "**Inspecting a mutation**: a parked `write` or `edit` card carries the target, the byte count, and a `sha256` digest of the exact call arguments. " +
133
+ "Press `v` to read the complete proposed content, or the complete effective diff against the file on disk, and `v` again to put it away; ↑/↓ and PageUp/PageDown scroll it. " +
134
+ "Enter still allows, `s` still stops, and Esc still denies while it is open. The mutation text is shown locally and never enters the transcript, a notice, a desktop notification, or the render trace. " +
135
+ "A worker escalation has no preview because the call's arguments never leave the worker, and its card says so.",
132
136
  ],
133
137
  },
134
138
  ];
@@ -22,13 +22,23 @@ export function openPromptsOverlay(tui: TUI, ctx: SlashCommandContext, onClose:
22
22
  lines.push(`**Argument Hint:** \`${template.argumentHint}\``);
23
23
  }
24
24
  lines.push(`**Source:** ${template.sourceInfo.source ?? template.sourceInfo.scope}`);
25
+ if (template.unavailable !== undefined) {
26
+ lines.push(`**Unavailable:** ${template.unavailable}`);
27
+ }
25
28
  if (!template.trusted) {
26
29
  lines.push("**Untrusted:** set `skills.trustProjectCompatRoots` before this template will expand.");
27
30
  }
28
31
  return lines;
29
32
  },
30
33
  };
31
- const marker = template.trusted ? undefined : clioTheme().fg("warning", "untrusted");
34
+ // A template that loaded in a refusing state is listed, because the
35
+ // operator's command does exist; the marker is what says it will not run.
36
+ const marker =
37
+ template.unavailable !== undefined
38
+ ? clioTheme().fg("error", "unavailable")
39
+ : template.trusted
40
+ ? undefined
41
+ : clioTheme().fg("warning", "untrusted");
32
42
  if (marker) item.meta = template.argumentHint ? `${template.argumentHint} · ${marker}` : marker;
33
43
  else if (template.argumentHint) item.meta = template.argumentHint;
34
44
  return item;
@@ -9,6 +9,7 @@ import {
9
9
  import { DEFAULT_SETTINGS, THINKING_LEVELS } from "../../core/defaults.js";
10
10
  import { getAtPath, isRoutingPath } from "../../core/session-routing.js";
11
11
  import { MAX_TIMER_DELAY_MS } from "../../core/timers.js";
12
+ import { capacityLeaseUsage } from "../../domains/dispatch/capacity-lease.js";
12
13
  import {
13
14
  delegationEntryForKind,
14
15
  type InteropAgentId,
@@ -17,6 +18,8 @@ import {
17
18
  } from "../../domains/interop/index.js";
18
19
  import {
19
20
  type CapabilityFlags,
21
+ endpointCapacitiesForStatuses,
22
+ endpointCapacityForStatus,
20
23
  isDispatchEligibleRuntime,
21
24
  isOrchestratorEligibleRuntime,
22
25
  type ProvidersContract,
@@ -225,7 +228,8 @@ type EntrySettingId =
225
228
  | `workers.profiles.${string}`
226
229
  | `workers.agentBindings.${string}`
227
230
  | `targets.${string}`
228
- | `fleet.nodes.${string}`;
231
+ | `fleet.nodes.${string}`
232
+ | `fleet.endpoints.${string}`;
229
233
  export type EditableSettingId = keyof typeof SETTINGS_LABELS_BY_ID | EntrySettingId;
230
234
  type FleetGroupHeaderId = `fleet.group.${"defaults" | "profiles" | "agent-routes" | "placement"}`;
231
235
  type TargetsCtaId = "targets.add-cta";
@@ -1482,6 +1486,7 @@ export function buildSettingItems(
1482
1486
  }),
1483
1487
  fleetGroupHeader("fleet.group.placement", "Placement"),
1484
1488
  ...fleetNodeRows(options?.getFleetNodes?.() ?? []),
1489
+ ...fleetEndpointRows(options?.providers),
1485
1490
  settingItem("targets", "", {
1486
1491
  description: "Live inference target inventory and routing roles.",
1487
1492
  affordance: "column heading",
@@ -1860,6 +1865,35 @@ function fleetNodeRows(nodes: ReadonlyArray<FleetNodeSnapshot>): SettingsCenterI
1860
1865
  });
1861
1866
  }
1862
1867
 
1868
+ /** Read-only endpoint rows show the independent inference scheduler dimension. */
1869
+ function fleetEndpointRows(providers: ProvidersContract | undefined): SettingsCenterItem[] {
1870
+ if (providers === undefined) return [];
1871
+ let usage: Readonly<Record<string, number>> = {};
1872
+ try {
1873
+ usage = capacityLeaseUsage().endpoints;
1874
+ } catch {
1875
+ // A damaged capacity file belongs to admission. Settings still shows the configured limits.
1876
+ }
1877
+ const capacities = endpointCapacitiesForStatuses(providers.list());
1878
+ const targetsByEndpoint = new Map<string, string[]>();
1879
+ for (const status of providers.list()) {
1880
+ const endpoint = endpointCapacityForStatus(status);
1881
+ if (endpoint === null) continue;
1882
+ targetsByEndpoint.set(endpoint.key, [...(targetsByEndpoint.get(endpoint.key) ?? []), status.target.id]);
1883
+ }
1884
+ return Object.entries(capacities).map(([key, endpoint]) => {
1885
+ const slots = `slots ${usage[key] ?? 0}/${endpoint.limit}`;
1886
+ return settingItem(`fleet.endpoints.${key}`, slots, {
1887
+ label: `endpoint ${endpoint.label}`,
1888
+ description: `Targets: ${(targetsByEndpoint.get(key) ?? []).join(", ")} · Limit source: ${endpoint.source}`,
1889
+ affordance: "read-only inference endpoint capacity",
1890
+ readOnly: true,
1891
+ presentationKind: "status",
1892
+ valueSegments: [{ text: slots, tone: "neutral" }],
1893
+ });
1894
+ });
1895
+ }
1896
+
1863
1897
  export function buildSettingsSections(items: readonly SettingsCenterItem[]): SettingsCenterSection[] {
1864
1898
  return SETTINGS_SECTIONS.map((section) => ({
1865
1899
  id: section.id,
@@ -1,5 +1,19 @@
1
1
  import type { HintEntry } from "./overlay-frame.js";
2
2
 
3
+ /**
4
+ * The key that opens and closes local inspection of a parked mutation. It lives
5
+ * here rather than beside the preview builder so the composer rail can name it
6
+ * without pulling the diff machinery into the Stage 0 boot shell closure.
7
+ */
8
+ export const MUTATION_PREVIEW_KEY = "v";
9
+
10
+ /**
11
+ * Whether this card has a mutation to read, and whether it is open.
12
+ * `none` covers every ask with nothing local to inspect, including a worker
13
+ * escalation, whose arguments never leave the worker.
14
+ */
15
+ export type PermissionInspectionHint = "none" | "closed" | "open";
16
+
3
17
  /**
4
18
  * The keys that answer a parked call, as data, so the dialog footer and the
5
19
  * composer rail render the same policy at any width.
@@ -16,17 +30,35 @@ import type { HintEntry } from "./overlay-frame.js";
16
30
  *
17
31
  * Esc says `deny`, not `close`: closing the dialog denies the call, and on the
18
32
  * one surface where a misread is a wrong decision about a tool call the key
19
- * names its effect.
33
+ * names its effect. It keeps that meaning while the mutation is open, so the
34
+ * inspect key is what puts the mutation away again.
20
35
  *
21
36
  * With a draft in the composer, Enter is inert (issue #186): the habitual send
22
37
  * key must not resolve toward "allow" on a safety rail. The entry then says so
23
38
  * and names the key that clears the draft, which the router passes through.
39
+ *
40
+ * A parked `write` or `edit` adds the inspect key (issue #254). It is
41
+ * droppable, and deliberately so: at 40 columns the four entries do not fit,
42
+ * and the three keys that answer the call outrank the one that reads it. The
43
+ * key keeps working at every width; the `/help` Autonomy & safety net topic is
44
+ * where it is documented for the widths that cannot show it.
24
45
  */
25
- export function permissionHintEntries(composerHasDraft = false): HintEntry[] {
46
+ export function permissionHintEntries(
47
+ composerHasDraft = false,
48
+ inspection: PermissionInspectionHint = "none",
49
+ ): HintEntry[] {
26
50
  return [
27
51
  composerHasDraft
28
52
  ? { key: "Backspace", verb: "clear the draft to allow", short: "clear draft", critical: true }
29
53
  : { key: "Enter", verb: "allow once", short: "allow", critical: true },
54
+ ...(inspection === "none"
55
+ ? []
56
+ : inspection === "open"
57
+ ? [
58
+ { key: "↑↓", verb: "scroll", critical: false },
59
+ { key: MUTATION_PREVIEW_KEY, verb: "hide mutation", short: "hide", critical: false },
60
+ ]
61
+ : [{ key: MUTATION_PREVIEW_KEY, verb: "inspect mutation", short: "inspect", critical: false }]),
30
62
  { key: "s", verb: "stop turn", short: "stop", critical: true },
31
63
  // With a draft, deny and stop are the only immediate answers, so Esc
32
64
  // outranks the narrowing that would otherwise drop it first.
@@ -6,13 +6,21 @@ import {
6
6
  decisionFactsForPermission,
7
7
  } from "../domains/safety/decision-presentation.js";
8
8
  import type { Component, OverlayOptions, TUI } from "../engine/tui.js";
9
+ import {
10
+ MUTATION_PREVIEW_VISIBLE_ROWS,
11
+ type MutationFacts,
12
+ type MutationInspector,
13
+ type MutationPreview,
14
+ mutationFactsLine,
15
+ mutationPreviewWindow,
16
+ } from "./mutation-preview.js";
9
17
  import { fitHintEntries } from "./overlay-frame.js";
10
- import { permissionHintEntries } from "./permission-hint.js";
18
+ import { MUTATION_PREVIEW_KEY, type PermissionInspectionHint, permissionHintEntries } from "./permission-hint.js";
11
19
  import type { ClioToken } from "./theme/index.js";
12
20
 
13
21
  export { type AskAxis, askAxis } from "../domains/safety/approval-axis.js";
14
22
  export { describeCallTarget, sanitizeCallTargetText } from "../domains/safety/call-target.js";
15
- export { permissionHintEntries } from "./permission-hint.js";
23
+ export { MUTATION_PREVIEW_KEY, type PermissionInspectionHint, permissionHintEntries } from "./permission-hint.js";
16
24
 
17
25
  export interface ApprovalRequestView {
18
26
  requestId: string;
@@ -34,6 +42,14 @@ export interface ApprovalRequestView {
34
42
  * when nothing meaningful is derivable.
35
43
  */
36
44
  target?: string;
45
+ /**
46
+ * Size and digest facts for a parked `write` or `edit`, and nothing else
47
+ * about it. The mutation text is deliberately absent: this view is what
48
+ * reaches the transcript row, the parked notice, and the approval-state
49
+ * event, so anything on it has already left the overlay. The text itself is
50
+ * held by the inspector the overlay opener is given (issue #254).
51
+ */
52
+ mutation?: MutationFacts;
37
53
  queueDepth?: number;
38
54
  }
39
55
 
@@ -79,11 +95,59 @@ export function permissionOverlayPlacement(
79
95
  };
80
96
  }
81
97
 
82
- class PermissionOverlayBody implements Component {
83
- constructor(private readonly view: ApprovalRequestView) {}
98
+ /**
99
+ * The overlay body, plus the inspection state the key router drives. The
100
+ * preview is built on the first toggle rather than per frame, so the file read
101
+ * and the diff happen once when the operator asks for them.
102
+ */
103
+ export interface PermissionOverlayBodyHandle extends Component {
104
+ /** Whether this card has a mutation the operator can read locally. */
105
+ canInspect(): boolean;
106
+ isInspecting(): boolean;
107
+ toggleInspect(): void;
108
+ scrollInspect(delta: number): void;
109
+ }
110
+
111
+ class PermissionOverlayBody implements PermissionOverlayBodyHandle {
112
+ private preview: MutationPreview | null = null;
113
+ private scroll = 0;
114
+ private lastLineCount = 0;
115
+
116
+ constructor(
117
+ private readonly view: ApprovalRequestView,
118
+ private readonly inspect?: MutationInspector,
119
+ ) {}
120
+
121
+ canInspect(): boolean {
122
+ return this.inspect !== undefined;
123
+ }
124
+
125
+ isInspecting(): boolean {
126
+ return this.preview !== null;
127
+ }
128
+
129
+ toggleInspect(): void {
130
+ if (this.inspect === undefined) return;
131
+ if (this.preview !== null) {
132
+ this.preview = null;
133
+ this.scroll = 0;
134
+ return;
135
+ }
136
+ this.preview = this.inspect();
137
+ this.scroll = 0;
138
+ }
139
+
140
+ scrollInspect(delta: number): void {
141
+ if (this.preview === null) return;
142
+ const maxScroll = Math.max(0, this.lastLineCount - MUTATION_PREVIEW_VISIBLE_ROWS);
143
+ this.scroll = Math.max(0, Math.min(this.scroll + delta, maxScroll));
144
+ }
84
145
 
85
146
  render(width: number): string[] {
86
- return permissionOverlayLines(this.view, width);
147
+ if (this.preview === null) return permissionOverlayLines(this.view, width);
148
+ const rendered = permissionInspectionLines(this.view, this.preview, width, this.scroll);
149
+ this.lastLineCount = rendered.wrappedLineCount;
150
+ return rendered.lines;
87
151
  }
88
152
 
89
153
  invalidate(): void {}
@@ -167,14 +231,74 @@ export function permissionOverlayTone(view: ApprovalRequestView): ClioToken {
167
231
  * three columns the bottom border spends on `─ ` and the trailing space come
168
232
  * off the top, which is what `buildResponsiveHint` does for every other frame.
169
233
  */
170
- export function permissionOverlayHint(innerWidth: number, composerHasDraft = false): string {
171
- return fitHintEntries(permissionHintEntries(composerHasDraft), innerWidth - 3);
234
+ export function permissionOverlayHint(
235
+ innerWidth: number,
236
+ composerHasDraft = false,
237
+ inspection: PermissionInspectionHint = "none",
238
+ ): string {
239
+ return fitHintEntries(permissionHintEntries(composerHasDraft, inspection), innerWidth - 3);
240
+ }
241
+
242
+ /** The tools whose parked call mutates a file, so a card without a preview owes the operator a reason. */
243
+ function isFileMutationTool(tool: string): boolean {
244
+ return tool === "write" || tool === "edit";
172
245
  }
173
246
 
174
247
  function actionConsequence(presentation: DecisionPresentation, id: "deny" | "stop"): string {
175
248
  return presentation.requiredActions.find((action) => action.id === id)?.consequence ?? "";
176
249
  }
177
250
 
251
+ /**
252
+ * Hard wrap for mutation text. Prose wraps on words; a proposed file line and a
253
+ * diff row do not, because the column a character sits in is part of what the
254
+ * operator is reading. The continuation marker says the row was folded rather
255
+ * than cut, which is the difference between a wrap and unmarked truncation.
256
+ */
257
+ function wrapMutationLine(value: string, max: number): string[] {
258
+ if (max <= 1) return [value];
259
+ if (value.length <= max) return [value];
260
+ const lines: string[] = [];
261
+ let remaining = value;
262
+ while (remaining.length > max) {
263
+ lines.push(`${remaining.slice(0, max - 1)}↩`);
264
+ remaining = remaining.slice(max - 1);
265
+ }
266
+ lines.push(remaining);
267
+ return lines;
268
+ }
269
+
270
+ /**
271
+ * The card with the mutation open. The tool, target, and digest facts stay at
272
+ * the top so the bytes on screen remain tied to the call they belong to, and
273
+ * the standing safety prose gives way to the mutation itself: the operator
274
+ * opened this to read what changes, and the keys that answer the ask are on the
275
+ * footer throughout.
276
+ */
277
+ function permissionInspectionLines(
278
+ view: ApprovalRequestView,
279
+ preview: MutationPreview,
280
+ width: number,
281
+ scroll: number,
282
+ ): { lines: string[]; wrappedLineCount: number } {
283
+ const content = Math.max(8, Math.floor(width));
284
+ const wrapped = preview.body.flatMap((line) => wrapMutationLine(line, content));
285
+ const pane = mutationPreviewWindow(wrapped, scroll);
286
+ const notes: string[] = [];
287
+ if (preview.neutralized) notes.push("control characters shown as ·");
288
+ if (preview.tabsExpanded) notes.push("tabs shown as spaces");
289
+ const lines = [
290
+ field("Tool: ", `${view.tool} · Action: ${view.actionClass}`, content),
291
+ ...(view.target !== undefined && view.target.length > 0 ? [field("Target: ", view.target, content)] : []),
292
+ ...wrapSentence(`Mutation: ${mutationFactsLine(preview.facts)}`, content),
293
+ "",
294
+ ...wrapSentence(preview.heading, content),
295
+ ...pane.window,
296
+ ...(pane.position === null ? [] : [pane.position]),
297
+ ...(notes.length > 0 ? [`Rendered safely: ${notes.join(", ")}.`] : []),
298
+ ];
299
+ return { lines, wrappedLineCount: wrapped.length };
300
+ }
301
+
178
302
  /**
179
303
  * The overlay's body, laid out for the width the frame gives it.
180
304
  *
@@ -193,6 +317,24 @@ export function permissionOverlayLines(view: ApprovalRequestView, width: number)
193
317
  const lines = [
194
318
  field("Tool: ", `${view.tool} · Action: ${view.actionClass}`, content),
195
319
  ...(view.target !== undefined && view.target.length > 0 ? [field("Target: ", view.target, content)] : []),
320
+ // Size and digest stay on the collapsed card whether or not the operator
321
+ // opens the mutation, so the decision always carries the identity of the
322
+ // bytes it applies to.
323
+ // Wrapped, never ellipsized: at 40 columns the digest is the tail of the
324
+ // line, and ellipsizing it would drop the one fact that ties the decision
325
+ // to the bytes it applies to.
326
+ ...(view.mutation !== undefined
327
+ ? [
328
+ ...wrapSentence(`Mutation: ${mutationFactsLine(view.mutation)}`, content),
329
+ ...wrapSentence(`Press ${MUTATION_PREVIEW_KEY} to read it before deciding.`, content),
330
+ ]
331
+ : []),
332
+ ...(view.mutation === undefined && view.origin.kind === "worker" && isFileMutationTool(view.tool)
333
+ ? wrapSentence(
334
+ "No local preview: this call's arguments stay inside the worker, so you are approving the target above without inspecting the mutation.",
335
+ content,
336
+ )
337
+ : []),
196
338
  ...wrapSentence(`Requested by: ${presentation.requestedByCopy}`, content),
197
339
  ...(view.artifact !== undefined
198
340
  ? [
@@ -215,6 +357,14 @@ export function permissionOverlayLines(view: ApprovalRequestView, width: number)
215
357
  return lines;
216
358
  }
217
359
 
218
- export function createPermissionOverlayBody(view: ApprovalRequestView): Component {
219
- return new PermissionOverlayBody(view);
360
+ /**
361
+ * The overlay body for one parked call. The inspector, when present, is the
362
+ * only path the mutation text takes: it is never placed on `view`, so nothing
363
+ * that renders or forwards the view can carry the bytes with it.
364
+ */
365
+ export function createPermissionOverlayBody(
366
+ view: ApprovalRequestView,
367
+ inspect?: MutationInspector,
368
+ ): PermissionOverlayBodyHandle {
369
+ return new PermissionOverlayBody(view, inspect);
220
370
  }