@iowarp/clio-coder 0.3.1 → 0.3.3

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 (601) hide show
  1. package/CHANGELOG.md +233 -373
  2. package/CONTRIBUTING.md +23 -23
  3. package/README.md +284 -613
  4. package/dist/{acp-FPR54DGL.js → acp-P2AQILE2.js} +43 -53
  5. package/dist/{agents-OGPIHPJH.js → agents-72W3BI7I.js} +43 -26
  6. package/dist/assets/codewiki.json +1 -1
  7. package/dist/{auth-IC3K6NIZ.js → auth-5TWEIYDN.js} +20 -12
  8. package/dist/chunk-2DJ2KNFG.js +2095 -0
  9. package/dist/{chunk-IS3ONKU3.js → chunk-2IR2NMPA.js} +6 -4
  10. package/dist/chunk-2SFS6XQE.js +122 -0
  11. package/dist/{chunk-PV4JUBVJ.js → chunk-2TLUCQVG.js} +40 -21
  12. package/dist/chunk-2VTFPG5O.js +48 -0
  13. package/dist/chunk-4BJ5BYCE.js +61 -0
  14. package/dist/{chunk-474KN5II.js → chunk-4BPJXDWC.js} +111 -181
  15. package/dist/chunk-4VP4KH3K.js +962 -0
  16. package/dist/chunk-4XUGQOHA.js +797 -0
  17. package/dist/chunk-4ZG3XFUR.js +77 -0
  18. package/dist/chunk-5B2AEOW5.js +5407 -0
  19. package/dist/{chunk-K2ITRMHZ.js → chunk-5TSRNF4G.js} +6 -138
  20. package/dist/{chunk-4QKXUHSR.js → chunk-5UFT4SUX.js} +70 -20
  21. package/dist/{chunk-OLBBMFRD.js → chunk-5UUP6MWO.js} +24 -62
  22. package/dist/chunk-65DEGPJ6.js +52 -0
  23. package/dist/chunk-6EJMN2Y3.js +17 -0
  24. package/dist/chunk-6N5PTWMY.js +136 -0
  25. package/dist/chunk-6SGHMWE3.js +277 -0
  26. package/dist/chunk-6XLNIQDB.js +27 -0
  27. package/dist/chunk-7CR24IG7.js +242 -0
  28. package/dist/chunk-7MNJORFF.js +22 -0
  29. package/dist/{chunk-KY56HMHH.js → chunk-A3CYT5EX.js} +125 -31
  30. package/dist/chunk-AGYYIBLL.js +1069 -0
  31. package/dist/{chunk-GB6QRBXN.js → chunk-APJ265NV.js} +54 -1187
  32. package/dist/{chunk-673JJUWJ.js → chunk-BMEMKKIT.js} +2 -2
  33. package/dist/chunk-CBCAPZAA.js +229 -0
  34. package/dist/chunk-CMZWFGD2.js +352 -0
  35. package/dist/chunk-COU2UHX6.js +400 -0
  36. package/dist/chunk-DSELYM6W.js +1077 -0
  37. package/dist/chunk-DUYJ5IO6.js +644 -0
  38. package/dist/chunk-ECH6PKUQ.js +39 -0
  39. package/dist/chunk-ED4KHGC3.js +143 -0
  40. package/dist/chunk-EKMEHE4H.js +340 -0
  41. package/dist/chunk-FCSXB6T2.js +338 -0
  42. package/dist/chunk-FJ3H4MN5.js +48 -0
  43. package/dist/{chunk-RPTR2H26.js → chunk-FNTMWMX5.js} +21 -15
  44. package/dist/chunk-FQ4SKYE4.js +29 -0
  45. package/dist/chunk-G4BMMOKF.js +182 -0
  46. package/dist/{chunk-ZPY3JZ5E.js → chunk-GGXXDWE4.js} +183 -1233
  47. package/dist/chunk-HC4CLZ2Y.js +68 -0
  48. package/dist/{chunk-LU4TK2PR.js → chunk-HFSBBKSQ.js} +5 -56
  49. package/dist/{chunk-PIUMUEMV.js → chunk-HKIYEGME.js} +10 -6
  50. package/dist/chunk-I4HZDVNP.js +73 -0
  51. package/dist/chunk-IKCO5N3L.js +162 -0
  52. package/dist/chunk-IR4CFBFN.js +56 -0
  53. package/dist/{chunk-R5KLMSBV.js → chunk-J5Q24KAG.js} +2 -2
  54. package/dist/chunk-J7CWMCQD.js +255 -0
  55. package/dist/{chunk-K5XEMXTI.js → chunk-JVCV3ICN.js} +1 -1
  56. package/dist/chunk-KZWTDYJF.js +217 -0
  57. package/dist/chunk-LBMZMYH2.js +285 -0
  58. package/dist/{chunk-G34LV2PF.js → chunk-LM5TQCJZ.js} +84 -170
  59. package/dist/chunk-LW6DSM3M.js +5135 -0
  60. package/dist/chunk-LWLEKMDQ.js +3482 -0
  61. package/dist/{chunk-H6F6BYOH.js → chunk-LZSJBIVT.js} +7003 -7434
  62. package/dist/{chunk-HQQID6OA.js → chunk-M6SHUN7Q.js} +5 -5
  63. package/dist/{chunk-FST4FYJB.js → chunk-MFFY33HR.js} +99 -140
  64. package/dist/{chunk-BSU2YIWB.js → chunk-MVVUPGPW.js} +131 -136
  65. package/dist/chunk-OAO4GE4M.js +619 -0
  66. package/dist/{chunk-Q3RUPKEJ.js → chunk-OC7FIQPC.js} +58 -189
  67. package/dist/chunk-OKGUZO2U.js +34 -0
  68. package/dist/{chunk-GAEBEQVI.js → chunk-OOJYHWRB.js} +32 -346
  69. package/dist/{chunk-Q5WJOSJ7.js → chunk-OQ33BKR3.js} +2 -1
  70. package/dist/chunk-OQE5J4C6.js +73 -0
  71. package/dist/{chunk-KKNLWXI6.js → chunk-ORBHGJC5.js} +8 -8
  72. package/dist/{chunk-MAR7Y6HW.js → chunk-PAJK6MAQ.js} +23 -16
  73. package/dist/{chunk-M5T5VO65.js → chunk-PIWWS5BL.js} +837 -635
  74. package/dist/chunk-POHLU5DW.js +1186 -0
  75. package/dist/chunk-QKMUKYO7.js +4961 -0
  76. package/dist/chunk-SRDMMSEP.js +16405 -0
  77. package/dist/chunk-SST6Z5JA.js +80 -0
  78. package/dist/chunk-STBPMHSX.js +2456 -0
  79. package/dist/chunk-T6YILFSB.js +80 -0
  80. package/dist/chunk-TZK7PACC.js +174 -0
  81. package/dist/chunk-TZTZS7QK.js +227 -0
  82. package/dist/{chunk-ASND7OZK.js → chunk-UFIIWP2H.js} +13 -13
  83. package/dist/chunk-UOV2BYIW.js +107 -0
  84. package/dist/{chunk-PFEFKVGL.js → chunk-V6RTAOC2.js} +13 -11
  85. package/dist/chunk-VAKQQHWR.js +434 -0
  86. package/dist/chunk-VG7TBQIY.js +128 -0
  87. package/dist/chunk-VJWL6YS5.js +244 -0
  88. package/dist/{chunk-EYOKLTMF.js → chunk-VPAYEGVX.js} +17 -3
  89. package/dist/chunk-WEH5XRJQ.js +32 -0
  90. package/dist/chunk-X4RCMKVQ.js +641 -0
  91. package/dist/{chunk-TEKV33Q5.js → chunk-X6IAEBZR.js} +65 -33
  92. package/dist/chunk-XBXAASKX.js +18 -0
  93. package/dist/chunk-XN3L4EYL.js +46 -0
  94. package/dist/{chunk-RDLVBZEO.js → chunk-YCWGATWI.js} +6 -4
  95. package/dist/chunk-YHZX5GEU.js +193 -0
  96. package/dist/chunk-YXLYO42X.js +91 -0
  97. package/dist/{chunk-NMOX6HFD.js → chunk-ZDOOVTXZ.js} +29 -77
  98. package/dist/chunk-ZI647VB5.js +37 -0
  99. package/dist/{chunk-C4PTHK7P.js → chunk-ZWLZP4ZT.js} +5 -5
  100. package/dist/chunk-ZWMF7253.js +1882 -0
  101. package/dist/cli/index.js +62 -54
  102. package/dist/clio-JOU4FXVA.js +25 -0
  103. package/dist/code-nav-7AX6FYE6.js +600 -0
  104. package/dist/codewiki/build-worker.js +66 -0
  105. package/dist/compile-cache-CVJMMODC.js +18 -0
  106. package/dist/{components-DMAOEKFB.js → components-KELWS457.js} +11 -6
  107. package/dist/{config-IRUQ7SE4.js → config-XCDVKR23.js} +92 -55
  108. package/dist/configure-4GAP54ZW.js +42 -0
  109. package/dist/{context-5RADCKTR.js → context-4UOGGLQ5.js} +71 -35
  110. package/dist/context-5VKGUVJJ.js +866 -0
  111. package/dist/{context-3KWFLHJG.js → context-77FM5DV5.js} +15 -13
  112. package/dist/{context-clear-7TSNPAAI.js → context-clear-XXJRLCJJ.js} +54 -28
  113. package/dist/{context-index-W4RLWOQH.js → context-index-BZ4UYMTC.js} +30 -24
  114. package/dist/dispatch-runner-QPRDDBDX.js +1997 -0
  115. package/dist/{docs-5AWSPS37.js → docs-2C2LTVT2.js} +23 -10
  116. package/dist/{doctor-UC5NAJYQ.js → doctor-HR46URBJ.js} +27 -17
  117. package/dist/{eval-U6TJHRLX.js → eval-XSSNATB4.js} +29 -16
  118. package/dist/{evidence-YEGUW4L3.js → evidence-6HG2PY2B.js} +46 -26
  119. package/dist/{evolve-TXARCTPG.js → evolve-K7YU3NCY.js} +45 -25
  120. package/dist/{extensions-OZFJ3A3G.js → extensions-QVDOHDGJ.js} +16 -7
  121. package/dist/{fleet-6G3DHNYE.js → fleet-VY3HHKN6.js} +163 -54
  122. package/dist/{fleet-preflight-DSNT37JK.js → fleet-preflight-DDN536IT.js} +7 -4
  123. package/dist/{init-KZ5QTF6M.js → init-JYGXI3FK.js} +69 -32
  124. package/dist/{memory-73ESV5YC.js → memory-WFZMGYHX.js} +48 -27
  125. package/dist/{models-A4PVNWJK.js → models-I5QWSEOM.js} +39 -25
  126. package/dist/monitor-GE4ID3IA.js +661 -0
  127. package/dist/{chunk-FCIH3BIZ.js → orchestrator-EM5MC3HM.js} +15979 -12407
  128. package/dist/{paths-C4H6IV77.js → paths-UXLN5YYZ.js} +10 -5
  129. package/dist/{preload-6WVMHX3A.js → preload-P6DGH2PZ.js} +2 -2
  130. package/dist/{reset-BGW6OGMV.js → reset-L2FQEE3E.js} +16 -10
  131. package/dist/{run-YTPEYQOH.js → run-ZU3QMZPZ.js} +101 -61
  132. package/dist/{share-YIFFV4NQ.js → share-S5BZQC5I.js} +15 -8
  133. package/dist/{skills-2V6RA3OQ.js → skills-X5VXCRNQ.js} +34 -14
  134. package/dist/{skills-eval-S2TVJO4F.js → skills-eval-WKIHWTHR.js} +70 -34
  135. package/dist/steer-GGWFUJUD.js +77 -0
  136. package/dist/{targets-TYXLPB23.js → targets-SNCPI2NR.js} +43 -27
  137. package/dist/terminal-lease-BNAHVHBS.js +395 -0
  138. package/dist/{trace-GGOJ6Q6Z.js → trace-PNCASAXC.js} +41 -16
  139. package/dist/{chunk-N6F52NLF.js → tree-sitter-HGKH6LG4.js} +28 -2306
  140. package/dist/{uninstall-LLLT4F4W.js → uninstall-FZCQCDKC.js} +10 -5
  141. package/dist/{upgrade-33G2LMM5.js → upgrade-JQHHPQ4K.js} +45 -25
  142. package/dist/{usage-ZAFSXKKG.js → usage-OR4O5SMZ.js} +62 -31
  143. package/dist/verify-375KUB3Y.js +716 -0
  144. package/dist/web-fetch-2YHJ3KTG.js +638 -0
  145. package/dist/{wiki-generate-NUQCVOQ3.js → wiki-generate-UEXP2ARI.js} +74 -34
  146. package/dist/worker/entry.js +221 -36
  147. package/dist/workspace-G4ZWUIPR.js +22 -0
  148. package/docs/README.md +22 -17
  149. package/docs/acp.md +168 -16
  150. package/docs/alcf-provider.md +1 -1
  151. package/docs/architecture.md +136 -7
  152. package/docs/artifact-versions.md +1 -1
  153. package/docs/built-in-agents.md +1 -1
  154. package/docs/capacity-and-scheduling.md +1 -1
  155. package/docs/commands-and-modes.md +114 -71
  156. package/docs/config-knobs-audit.md +1 -3
  157. package/docs/configuration-and-targets.md +174 -46
  158. package/docs/context-engine.md +29 -6
  159. package/docs/development-pipeline.md +26 -1
  160. package/docs/dispatch-architecture-rationale.md +1 -1
  161. package/docs/documentation-coverage.md +2 -2
  162. package/docs/documentation-guide.md +1 -1
  163. package/docs/environment-variables.md +13 -5
  164. package/docs/eval-runner.md +1 -1
  165. package/docs/evals-internal.md +1 -1
  166. package/docs/evidence-and-memory.md +6 -2
  167. package/docs/evolution.md +2 -2
  168. package/docs/exit-codes-and-output.md +15 -9
  169. package/docs/extensions-and-sharing.md +9 -9
  170. package/docs/fleet-dispatch.md +7 -5
  171. package/docs/git-commit-provenance.md +120 -0
  172. package/docs/glossary.md +1 -1
  173. package/docs/installation-and-lifecycle.md +34 -27
  174. package/docs/middleware-and-components.md +1 -1
  175. package/docs/model-catalog.md +45 -14
  176. package/docs/observability.md +8 -5
  177. package/docs/performance-methodology.md +491 -0
  178. package/docs/pi-boundary.md +72 -0
  179. package/docs/proactive-memory.md +3 -3
  180. package/docs/prompt-envelope-and-tools.md +24 -3
  181. package/docs/provider-adapter-cookbook.md +57 -4
  182. package/docs/release-cut-checklist.md +129 -115
  183. package/docs/safety-model.md +9 -5
  184. package/docs/scientific-validation.md +3 -3
  185. package/docs/session-lifecycle.md +55 -12
  186. package/docs/skills-marketplace.md +12 -8
  187. package/docs/time-conventions.md +1 -1
  188. package/docs/tool-usage.md +3 -3
  189. package/docs/trace-store.md +1 -1
  190. package/docs/troubleshooting.md +10 -7
  191. package/docs/tui-design.md +47 -10
  192. package/docs/worker-dispatch-mechanics.md +1 -1
  193. package/package.json +19 -22
  194. package/skills/coding/ast-grep/SKILL.md +136 -0
  195. package/skills/coding/ast-grep/evals.md +56 -0
  196. package/skills/coding/ast-grep/references/rule_reference.md +297 -0
  197. package/skills/coding/coding-standards/SKILL.md +113 -0
  198. package/skills/coding/coding-standards/evals.md +34 -0
  199. package/skills/coding/prototype/SKILL.md +86 -0
  200. package/skills/coding/prototype/evals.md +42 -0
  201. package/skills/coding/prototype/references/LOGIC.md +67 -0
  202. package/skills/coding/prototype/references/UI.md +112 -0
  203. package/skills/coding/tdd/SKILL.md +101 -0
  204. package/skills/coding/tdd/evals.md +41 -0
  205. package/skills/coding/tdd/references/mocking.md +59 -0
  206. package/skills/coding/tdd/references/tests.md +77 -0
  207. package/skills/context/context-handoff/SKILL.md +126 -0
  208. package/skills/context/context-handoff/evals.md +57 -0
  209. package/skills/context/context-handoff/scripts/new-handoff.sh +26 -0
  210. package/skills/context/context-prime/SKILL.md +95 -0
  211. package/skills/context/context-prime/evals.md +54 -0
  212. package/skills/meta/clio-dev/SKILL.md +91 -0
  213. package/skills/meta/clio-dev/evals.md +45 -0
  214. package/skills/meta/clio-test/SKILL.md +130 -0
  215. package/skills/meta/clio-test/evals.md +43 -0
  216. package/skills/meta/clio-test/references/harness.md +97 -0
  217. package/skills/meta/clio-test/references/test-map.md +59 -0
  218. package/skills/meta/credentials/SKILL.md +125 -0
  219. package/skills/meta/credentials/evals.md +104 -0
  220. package/skills/meta/find-skills/SKILL.md +72 -0
  221. package/skills/meta/find-skills/evals.md +47 -0
  222. package/skills/meta/herdr/SKILL.md +127 -0
  223. package/skills/meta/herdr/evals.md +38 -0
  224. package/skills/meta/skill-craft/SKILL.md +102 -0
  225. package/skills/meta/skill-craft/evals.md +41 -0
  226. package/skills/planning/architecture/SKILL.md +129 -0
  227. package/skills/planning/architecture/evals.md +36 -0
  228. package/skills/planning/backlog/SKILL.md +90 -0
  229. package/skills/planning/backlog/evals.md +43 -0
  230. package/skills/planning/prd/SKILL.md +82 -0
  231. package/skills/planning/prd/evals.md +49 -0
  232. package/skills/planning/product-intent/SKILL.md +112 -0
  233. package/skills/planning/product-intent/evals.md +36 -0
  234. package/skills/planning/tech-spec/SKILL.md +115 -0
  235. package/skills/planning/tech-spec/evals.md +47 -0
  236. package/skills/registry.yaml +136 -0
  237. package/skills/research/arxiv-literature/SKILL.md +104 -0
  238. package/skills/research/arxiv-literature/evals.md +58 -0
  239. package/skills/research/experiment-protocol/SKILL.md +122 -0
  240. package/skills/research/experiment-protocol/evals.md +91 -0
  241. package/skills/research/scientific-debugging/SKILL.md +119 -0
  242. package/skills/research/scientific-debugging/evals.md +138 -0
  243. package/skills/research/scientific-modernization/SKILL.md +138 -0
  244. package/skills/research/scientific-modernization/evals.md +84 -0
  245. package/skills/workflow/design-council/SKILL.md +139 -0
  246. package/skills/workflow/design-council/evals.md +97 -0
  247. package/skills/workflow/grill-me/SKILL.md +186 -0
  248. package/skills/workflow/grill-me/evals.md +78 -0
  249. package/skills/workflow/workflow-distiller/SKILL.md +136 -0
  250. package/skills/workflow/workflow-distiller/evals.md +107 -0
  251. package/src/cli/acp.ts +31 -4
  252. package/src/cli/clio.ts +68 -6
  253. package/src/cli/config-inspect.ts +28 -22
  254. package/src/cli/configure.ts +47 -9
  255. package/src/cli/context-clear.ts +2 -2
  256. package/src/cli/context-index.ts +21 -23
  257. package/src/cli/context.ts +13 -8
  258. package/src/cli/default-target.ts +9 -17
  259. package/src/cli/docs.ts +11 -5
  260. package/src/cli/evidence.ts +4 -1
  261. package/src/cli/extensions.ts +10 -1
  262. package/src/cli/fleet.ts +47 -6
  263. package/src/cli/index.ts +55 -26
  264. package/src/cli/memory.ts +3 -1
  265. package/src/cli/models.ts +1 -1
  266. package/src/cli/modes/json-stream.ts +37 -1
  267. package/src/cli/modes/print.ts +24 -9
  268. package/src/cli/run.ts +2 -2
  269. package/src/cli/skills-eval.ts +23 -8
  270. package/src/cli/skills.ts +19 -4
  271. package/src/cli/targets.ts +4 -0
  272. package/src/cli/text-layout.ts +15 -5
  273. package/src/cli/trace.ts +62 -14
  274. package/src/cli/upgrade.ts +18 -2
  275. package/src/cli/usage.ts +10 -3
  276. package/src/cli/wiki-generate.ts +2 -1
  277. package/src/core/agent-environment.ts +7 -0
  278. package/src/core/bash-exec.ts +72 -1
  279. package/src/core/boot-trace.ts +9 -4
  280. package/src/core/bus-events.ts +20 -4
  281. package/src/core/commit-attribution.ts +157 -0
  282. package/src/core/compile-cache.ts +159 -0
  283. package/src/core/config.ts +131 -2
  284. package/src/core/defaults.ts +39 -5
  285. package/src/core/domain-loader.ts +12 -5
  286. package/src/core/git-commit-attribution.ts +387 -0
  287. package/src/core/incomplete-installation.ts +45 -0
  288. package/src/core/response-schema.ts +1 -1
  289. package/src/core/safe-exec.ts +13 -1
  290. package/src/core/settings-layers.ts +155 -21
  291. package/src/core/skill-activation.ts +1 -1
  292. package/src/core/startup-timer.ts +3 -3
  293. package/src/core/state-file-lock.ts +13 -1
  294. package/src/core/termination.ts +78 -5
  295. package/src/domains/config/classify.ts +15 -3
  296. package/src/domains/config/extension.ts +19 -13
  297. package/src/domains/config/index.ts +10 -0
  298. package/src/domains/config/keybindings.ts +45 -9
  299. package/src/domains/context/bootstrap-prompt.ts +1 -1
  300. package/src/domains/context/bootstrap.ts +111 -18
  301. package/src/domains/context/clear.ts +16 -11
  302. package/src/domains/context/clio-md.ts +111 -9
  303. package/src/domains/context/codewiki/artifact.ts +400 -0
  304. package/src/domains/context/codewiki/build-worker-protocol.ts +24 -0
  305. package/src/domains/context/codewiki/build-worker.ts +54 -0
  306. package/src/domains/context/codewiki/coordinator.ts +182 -0
  307. package/src/domains/context/codewiki/indexer.ts +59 -144
  308. package/src/domains/context/codewiki/paths.ts +67 -0
  309. package/src/domains/context/codewiki/schema.ts +80 -0
  310. package/src/domains/context/codewiki/tree-sitter.ts +1 -1
  311. package/src/domains/context/contract.ts +11 -5
  312. package/src/domains/context/extension.ts +94 -143
  313. package/src/domains/context/fingerprint.ts +3 -1
  314. package/src/domains/context/index.ts +12 -22
  315. package/src/domains/context/project-metadata.ts +19 -0
  316. package/src/domains/context/prompt-context.ts +9 -10
  317. package/src/domains/context/refresh.ts +29 -21
  318. package/src/domains/context/runtime.ts +17 -0
  319. package/src/domains/context/wiki/generate.ts +39 -34
  320. package/src/domains/context/wiki/plan.ts +1 -1
  321. package/src/domains/context/wiki/prompts.ts +21 -8
  322. package/src/domains/dispatch/code-step.ts +20 -1
  323. package/src/domains/dispatch/extension.ts +158 -21
  324. package/src/domains/dispatch/failure-classification.ts +6 -0
  325. package/src/domains/dispatch/fleet-commit-attribution.ts +56 -0
  326. package/src/domains/dispatch/orphan-recovery.ts +50 -8
  327. package/src/domains/dispatch/receipt-integrity.ts +5 -0
  328. package/src/domains/dispatch/state.ts +31 -6
  329. package/src/domains/dispatch/transport.ts +2 -1
  330. package/src/domains/dispatch/types.ts +14 -0
  331. package/src/domains/dispatch/worker-spawn.ts +21 -2
  332. package/src/domains/eval/metrics/context.ts +1 -1
  333. package/src/domains/eval/types.ts +0 -1
  334. package/src/domains/evidence/build.ts +41 -1
  335. package/src/domains/lifecycle/migrations/2026-08-18-lmstudio-runtime-id.ts +52 -0
  336. package/src/domains/lifecycle/migrations/index.ts +24 -4
  337. package/src/domains/middleware/hooks-io.ts +12 -0
  338. package/src/domains/middleware/skills-reminder.ts +30 -15
  339. package/src/domains/prompts/compiler.ts +142 -84
  340. package/src/domains/prompts/contract.ts +18 -2
  341. package/src/domains/prompts/extension.ts +39 -7
  342. package/src/domains/prompts/fragment-loader.ts +0 -1
  343. package/src/domains/prompts/fragments/identity/clio.md +2 -4
  344. package/src/domains/prompts/fragments/identity/docs-routing.md +10 -0
  345. package/src/domains/prompts/fragments/identity/self-awareness.md +1 -45
  346. package/src/domains/prompts/fragments/operating/contract.md +4 -50
  347. package/src/domains/prompts/fragments/operating/delegation.md +42 -0
  348. package/src/domains/prompts/fragments/operating/skills.md +26 -0
  349. package/src/domains/prompts/fragments/operating/worker.md +16 -0
  350. package/src/domains/prompts/fragments/safety/auto-edit.md +5 -5
  351. package/src/domains/prompts/fragments/safety/full-auto.md +3 -3
  352. package/src/domains/prompts/fragments/safety/read-only.md +4 -4
  353. package/src/domains/prompts/fragments/safety/suggest.md +2 -2
  354. package/src/domains/prompts/fragments/wiki/page.md +10 -0
  355. package/src/domains/prompts/fragments/wiki/plan.md +10 -0
  356. package/src/domains/prompts/preload.ts +3 -3
  357. package/src/domains/providers/auth/api-key.ts +1 -1
  358. package/src/domains/providers/auth/backend-file.ts +20 -10
  359. package/src/domains/providers/auth/backend-memory.ts +59 -4
  360. package/src/domains/providers/auth/boot-status.ts +65 -0
  361. package/src/domains/providers/auth/oauth.ts +2 -1
  362. package/src/domains/providers/auth/storage.ts +97 -38
  363. package/src/domains/providers/capabilities.ts +12 -4
  364. package/src/domains/providers/contract.ts +15 -4
  365. package/src/domains/providers/extension.ts +18 -6
  366. package/src/domains/providers/model-runtime-capabilities.ts +15 -4
  367. package/src/domains/providers/models/local-models/clio-local-coding-targets.yaml +118 -35
  368. package/src/domains/providers/plugins.ts +5 -3
  369. package/src/domains/providers/probe/fingerprint.ts +25 -5
  370. package/src/domains/providers/registry.ts +31 -10
  371. package/src/domains/providers/runtimes/boot-manifest.ts +55 -0
  372. package/src/domains/providers/runtimes/builtins.ts +2 -2
  373. package/src/domains/providers/runtimes/common/lmstudio-http.ts +423 -0
  374. package/src/domains/providers/runtimes/common/local-synth.ts +6 -7
  375. package/src/domains/providers/runtimes/local-native/lmstudio.ts +241 -0
  376. package/src/domains/providers/support.ts +6 -3
  377. package/src/domains/providers/types/local-model-quirks.ts +7 -9
  378. package/src/domains/providers/types/runtime-descriptor.ts +12 -1
  379. package/src/domains/providers/types/target-descriptor.ts +22 -0
  380. package/src/domains/resources/contract.ts +0 -1
  381. package/src/domains/resources/extension.ts +1 -3
  382. package/src/domains/resources/loader.ts +3 -4
  383. package/src/domains/resources/prompts/loader.ts +16 -2
  384. package/src/domains/resources/prompts/substitute.ts +1 -65
  385. package/src/domains/resources/skills/content-hash.ts +2 -0
  386. package/src/domains/resources/skills/install.ts +17 -0
  387. package/src/domains/resources/skills/loader.ts +17 -10
  388. package/src/domains/resources/skills/marketplace.ts +55 -9
  389. package/src/domains/safety/action-classifier.ts +4 -2
  390. package/src/domains/safety/audit.ts +8 -2
  391. package/src/domains/safety/extension.ts +1 -1
  392. package/src/domains/session/compaction/branch-summary.ts +3 -2
  393. package/src/domains/session/compaction/cut-point.ts +2 -1
  394. package/src/domains/session/compaction/tokens.ts +2 -1
  395. package/src/domains/session/context-ledger.ts +14 -0
  396. package/src/domains/session/contract.ts +15 -0
  397. package/src/domains/session/decision-board.ts +190 -0
  398. package/src/domains/session/entries.ts +66 -3
  399. package/src/domains/session/extension.ts +93 -12
  400. package/src/domains/session/retry.ts +10 -18
  401. package/src/domains/session/session-artifacts.ts +107 -0
  402. package/src/domains/session/task-board.ts +207 -13
  403. package/src/domains/session/tree/active-path.ts +44 -5
  404. package/src/domains/session/tree/fork.ts +26 -27
  405. package/src/domains/session/tree/preview.ts +2 -2
  406. package/src/domains/session/workspace/git-probe.ts +17 -11
  407. package/src/domains/user-tasks/store.ts +297 -0
  408. package/src/engine/acp/errors.ts +96 -0
  409. package/src/engine/acp/server.ts +1728 -146
  410. package/src/engine/acp/transport.ts +135 -14
  411. package/src/engine/acp/types.ts +26 -0
  412. package/src/engine/agent.ts +3 -3
  413. package/src/engine/ai.ts +32 -27
  414. package/src/engine/alcf-oauth.ts +26 -19
  415. package/src/engine/api-registry.ts +223 -0
  416. package/src/engine/apis/index.ts +3 -7
  417. package/src/engine/apis/llamacpp-residency.ts +49 -9
  418. package/src/engine/apis/lmstudio-residency.ts +5 -21
  419. package/src/engine/apis/lmstudio.ts +243 -0
  420. package/src/engine/apis/ollama-native.ts +24 -3
  421. package/src/engine/apis/openai-completions.ts +170 -91
  422. package/src/engine/apis/residency.ts +139 -3
  423. package/src/engine/apis/types.ts +16 -0
  424. package/src/engine/env-api-keys.ts +98 -0
  425. package/src/engine/gemma-channel-filter.ts +223 -0
  426. package/src/engine/instrumented-tui.ts +192 -0
  427. package/src/engine/messages.ts +14 -0
  428. package/src/engine/models.ts +42 -0
  429. package/src/engine/oauth.ts +16 -12
  430. package/src/engine/prompt-templates.ts +1 -0
  431. package/src/engine/provider-payload.ts +16 -59
  432. package/src/engine/strip-tokenizer-sentinels.ts +1 -1
  433. package/src/engine/truncate.ts +9 -0
  434. package/src/engine/tui.ts +17 -9
  435. package/src/engine/types.ts +3 -6
  436. package/src/engine/worker-runtime-capabilities.ts +5 -0
  437. package/src/engine/worker-runtime.ts +1 -1
  438. package/src/engine/worker-tools.ts +9 -4
  439. package/src/entry/boot-options.ts +50 -0
  440. package/src/entry/orchestrator.ts +288 -150
  441. package/src/interactive/application-controller.ts +89 -2
  442. package/src/interactive/chat-loop.ts +266 -41
  443. package/src/interactive/chat-panel.ts +715 -278
  444. package/src/interactive/chat-renderer.ts +306 -85
  445. package/src/interactive/clio-editor.ts +3 -8
  446. package/src/interactive/command-fallbacks.ts +2 -2
  447. package/src/interactive/context-overlay.ts +27 -1
  448. package/src/interactive/editor-submit.ts +253 -24
  449. package/src/interactive/export-html/ansi-to-html.ts +161 -0
  450. package/src/interactive/export-html/index.ts +51 -0
  451. package/src/interactive/export-html/template.ts +45 -0
  452. package/src/interactive/export-html/tool-renderer.ts +54 -0
  453. package/src/interactive/footer/dashboard.ts +4 -0
  454. package/src/interactive/footer/notifications.ts +1 -1
  455. package/src/interactive/footer/widgets.ts +42 -22
  456. package/src/interactive/footer-panel.ts +8 -3
  457. package/src/interactive/format-time.ts +14 -2
  458. package/src/interactive/interactive-application.ts +203 -17
  459. package/src/interactive/interactive-event-projection.ts +18 -1
  460. package/src/interactive/interactive-input-runtime.ts +50 -4
  461. package/src/interactive/interactive-presentation.ts +151 -19
  462. package/src/interactive/interactive-shell.ts +268 -14
  463. package/src/interactive/interactive-slash-runtime.ts +184 -117
  464. package/src/interactive/interactive-tickers.ts +38 -7
  465. package/src/interactive/keybinding-manager.ts +1 -1
  466. package/src/interactive/layout.ts +40 -3
  467. package/src/interactive/overlay-frame.ts +1 -1
  468. package/src/interactive/overlay-general-openers.ts +58 -1
  469. package/src/interactive/overlay-key-routing.ts +3 -0
  470. package/src/interactive/overlay-lifecycle.ts +13 -0
  471. package/src/interactive/overlay-permission-lifecycle.ts +2 -1
  472. package/src/interactive/overlay-session-lifecycle.ts +69 -12
  473. package/src/interactive/overlays/ask-user.ts +146 -24
  474. package/src/interactive/overlays/decisions.ts +300 -0
  475. package/src/interactive/overlays/help-reference.ts +15 -10
  476. package/src/interactive/overlays/model-selector.ts +34 -16
  477. package/src/interactive/overlays/session-selector.ts +18 -0
  478. package/src/interactive/overlays/settings.ts +105 -17
  479. package/src/interactive/overlays/skills-hub.ts +4 -4
  480. package/src/interactive/overlays/tree-selector.ts +41 -6
  481. package/src/interactive/render-trace.ts +499 -90
  482. package/src/interactive/renderers/compaction-summary.ts +2 -2
  483. package/src/interactive/renderers/diff.ts +115 -104
  484. package/src/interactive/renderers/mermaid.ts +53 -0
  485. package/src/interactive/renderers/tool-execution.ts +516 -168
  486. package/src/interactive/renderers/worker-entry.ts +20 -4
  487. package/src/interactive/session-switch-settlement.ts +10 -0
  488. package/src/interactive/slash-autocomplete.ts +6 -114
  489. package/src/interactive/slash-commands.ts +135 -47
  490. package/src/interactive/slash-spec.ts +9 -38
  491. package/src/interactive/status/controller.ts +5 -1
  492. package/src/interactive/status/index.ts +12 -1
  493. package/src/interactive/status/reasoning.ts +87 -0
  494. package/src/interactive/status/summary.ts +13 -2
  495. package/src/interactive/stdout-backpressure.ts +99 -0
  496. package/src/interactive/stream-pacer.ts +530 -0
  497. package/src/interactive/stream-pacing-policy.ts +66 -0
  498. package/src/interactive/tasks-overlay.ts +368 -14
  499. package/src/interactive/terminal-lease.ts +485 -0
  500. package/src/interactive/theme/tokens.ts +1 -1
  501. package/src/interactive/transcript-detail.ts +120 -0
  502. package/src/interactive/turn-context.ts +4 -3
  503. package/src/interactive/turn-persistence.ts +30 -13
  504. package/src/interactive/turn-queues.ts +12 -0
  505. package/src/interactive/turn-recovery.ts +25 -8
  506. package/src/interactive/turn-runtime.ts +79 -12
  507. package/src/interactive/turn-state.ts +10 -0
  508. package/src/interactive/view/artifacts.ts +114 -4
  509. package/src/interactive/view/view-overlay.ts +3 -0
  510. package/src/interactive/welcome-dashboard.ts +17 -16
  511. package/src/interactive/worker-receipts.ts +52 -3
  512. package/src/interactive/worker-stream.ts +5 -1
  513. package/src/tools/agent-tools.ts +23 -3
  514. package/src/tools/artifact.ts +2 -2
  515. package/src/tools/ask-user.ts +23 -13
  516. package/src/tools/bash.ts +30 -2
  517. package/src/tools/bootstrap.ts +34 -431
  518. package/src/tools/builtin-tool-catalog.ts +271 -0
  519. package/src/tools/codewiki/code-nav-surface.ts +29 -0
  520. package/src/tools/codewiki/code-nav.ts +8 -22
  521. package/src/tools/codewiki/shared.ts +41 -38
  522. package/src/tools/context/docs-engine.ts +14 -3
  523. package/src/tools/context/index.ts +107 -28
  524. package/src/tools/context/surface.ts +19 -0
  525. package/src/tools/core-bootstrap.ts +168 -0
  526. package/src/tools/credential-present.ts +5 -5
  527. package/src/tools/dispatch-admission.ts +533 -0
  528. package/src/tools/dispatch-background.ts +54 -0
  529. package/src/tools/dispatch-event-text.ts +6 -0
  530. package/src/tools/dispatch-plan.ts +9 -4
  531. package/src/tools/dispatch-run-events.ts +238 -0
  532. package/src/tools/dispatch-runner.ts +2370 -0
  533. package/src/tools/dispatch-scout-admission.ts +295 -0
  534. package/src/tools/dispatch-types.ts +77 -0
  535. package/src/tools/dispatch.ts +67 -3161
  536. package/src/tools/find.ts +4 -2
  537. package/src/tools/grep.ts +2 -2
  538. package/src/tools/lazy-tool.ts +60 -0
  539. package/src/tools/ledger.ts +3 -3
  540. package/src/tools/monitor-surface.ts +36 -0
  541. package/src/tools/monitor.ts +2 -32
  542. package/src/tools/observers.ts +2 -2
  543. package/src/tools/presentation.ts +107 -0
  544. package/src/tools/registry.ts +45 -27
  545. package/src/tools/safe-exec.ts +2 -2
  546. package/src/tools/steer-surface.ts +17 -0
  547. package/src/tools/steer.ts +2 -13
  548. package/src/tools/tasks.ts +108 -11
  549. package/src/tools/truncate.ts +25 -184
  550. package/src/tools/verify/frontend.ts +3 -1
  551. package/src/tools/verify/index.ts +3 -38
  552. package/src/tools/verify/surface.ts +46 -0
  553. package/src/tools/web-fetch-surface.ts +23 -0
  554. package/src/tools/web-fetch.ts +2 -20
  555. package/src/tools/write.ts +7 -2
  556. package/src/worker/entry.ts +39 -2
  557. package/src/worker/spec-contract.ts +26 -5
  558. package/dist/chunk-7SS2CTV2.js +0 -61361
  559. package/dist/chunk-DKGKUHFA.js +0 -924
  560. package/dist/chunk-GEP36Y4X.js +0 -12796
  561. package/dist/chunk-XYWBQRDM.js +0 -137
  562. package/dist/clio-BZVGEUFJ.js +0 -58
  563. package/dist/configure-S7S6F6CL.js +0 -32
  564. package/docs/html/agents_blueprint.html +0 -936
  565. package/docs/html/alcf_blueprint.html +0 -324
  566. package/docs/html/architecture_blueprint.html +0 -850
  567. package/docs/html/commands_blueprint.html +0 -939
  568. package/docs/html/config_knobs_audit_blueprint.html +0 -178
  569. package/docs/html/configuration_blueprint.html +0 -1080
  570. package/docs/html/context_blueprint.html +0 -603
  571. package/docs/html/documentation_blueprint.html +0 -832
  572. package/docs/html/environment_blueprint.html +0 -404
  573. package/docs/html/eval_blueprint.html +0 -743
  574. package/docs/html/evals_internal_blueprint.html +0 -190
  575. package/docs/html/evolution_blueprint.html +0 -674
  576. package/docs/html/extensions_blueprint.html +0 -2065
  577. package/docs/html/fleet_dispatch_blueprint.html +0 -286
  578. package/docs/html/index.html +0 -919
  579. package/docs/html/lifecycle_blueprint.html +0 -723
  580. package/docs/html/memory_blueprint.html +0 -699
  581. package/docs/html/middleware_blueprint.html +0 -664
  582. package/docs/html/models_blueprint.html +0 -2366
  583. package/docs/html/observability_blueprint.html +0 -683
  584. package/docs/html/provider_adapter_blueprint.html +0 -245
  585. package/docs/html/safety_blueprint.html +0 -1386
  586. package/docs/html/shared.css +0 -571
  587. package/docs/html/shared.js +0 -143
  588. package/docs/html/skills_blueprint.html +0 -671
  589. package/docs/html/soak_blueprint.html +0 -182
  590. package/docs/html/tool_usage_blueprint.html +0 -350
  591. package/docs/html/tools_blueprint.html +0 -2249
  592. package/docs/html/trace_blueprint.html +0 -235
  593. package/docs/html/tui_design_blueprint.html +0 -374
  594. package/docs/html/validation_blueprint.html +0 -961
  595. package/docs/html/worker_dispatch_blueprint.html +0 -231
  596. package/src/core/release.ts +0 -2
  597. package/src/domains/providers/runtimes/common/lmstudio-logger.ts +0 -32
  598. package/src/domains/providers/runtimes/local-native/lmstudio-native.ts +0 -491
  599. package/src/engine/apis/lmstudio-native.ts +0 -1438
  600. package/src/engine/apis/thinking-replay.ts +0 -11
  601. package/src/tools/string-enum.ts +0 -15
@@ -13,11 +13,16 @@
13
13
  */
14
14
 
15
15
  import { sanitizeCallTargetText } from "../../domains/safety/call-target.js";
16
+ import { formatSize } from "../../engine/truncate.js";
16
17
  import { visibleWidth, wrapTextWithAnsi } from "../../engine/tui.js";
18
+ import { classifyResourceRead, toolPresentationPolicy } from "../../tools/presentation.js";
19
+ import type { ApprovalRequestView } from "../permission-overlay.js";
17
20
  import { clioTheme, formatCompactMs, GLYPH } from "../theme/index.js";
18
- import { type DiffRenderInput, renderUnifiedDiff } from "./diff.js";
21
+ import { renderDiffLines } from "./diff.js";
19
22
  import { tryRenderJson, tryRenderXml } from "./structured.js";
20
23
 
24
+ export { classifyResourceRead };
25
+
21
26
  const theme = clioTheme();
22
27
  const dim = (text: string): string => theme.fg("dim", text);
23
28
  const red = (text: string): string => theme.fg("error", text);
@@ -34,11 +39,12 @@ const BODY_INDENT_VISIBLE_WIDTH = 2;
34
39
  const HEADER_PREFIX_PLAIN = "▸ ";
35
40
  const ARG_PREVIEW_LIMIT = 60;
36
41
  const WEB_FETCH_ARG_PREVIEW_LIMIT = 140;
37
- const RESULT_PREVIEW_LIMIT = 4000;
38
42
  const FULL_RESULT_PREVIEW_LIMIT = 60_000;
39
43
  const FULL_RESULT_ROW_LIMIT = 120;
40
44
  const STREAMING_RESULT_ROW_LIMIT = 20;
41
45
  const ARGS_BODY_LINE_LIMIT = 24;
46
+ /** Rows of mutation diff a folded edit/write row keeps visible before it defers to the body. */
47
+ const FOLDED_DIFF_ROW_LIMIT = 24;
42
48
  const STATUS_OK_GLYPH = GLYPH.ok;
43
49
  const STATUS_ERROR_GLYPH = GLYPH.error;
44
50
 
@@ -54,6 +60,8 @@ export interface ToolExecutionStart {
54
60
  args: unknown;
55
61
  /** Live elapsed time supplied by the panel for running segments. */
56
62
  elapsedMs?: number | undefined;
63
+ /** Pi may stream a tool call's arguments before execution starts. */
64
+ phase?: "forming" | "ready" | "running" | undefined;
57
65
  }
58
66
 
59
67
  export interface ToolExecutionFinished {
@@ -73,6 +81,10 @@ export interface ToolExecutionFinished {
73
81
  * model reading the same transcript, to guess at the rule.
74
82
  */
75
83
  blockReason?: string | undefined;
84
+ /** Structured exit status when the caller has one; text parsing is legacy fallback only. */
85
+ exitCode?: number | string | null | undefined;
86
+ /** Local `!!` bash output is visible to the operator but excluded from model context. */
87
+ excludeFromContext?: boolean | undefined;
76
88
  }
77
89
 
78
90
  export interface ToolBodyRenderOptions {
@@ -83,6 +95,8 @@ export interface ToolBodyRenderOptions {
83
95
  * the complete tool output the model actually received.
84
96
  */
85
97
  unbounded?: boolean;
98
+ /** Live rows color mutation diffs; replay and export deliberately use plain text. */
99
+ diffStyle?: "color" | "plain";
86
100
  }
87
101
 
88
102
  /** Row cap for a tool body: unbounded lifts both the row and char limits. */
@@ -218,10 +232,9 @@ function bashExitCodeFromResult(result: unknown): string | null {
218
232
 
219
233
  /**
220
234
  * Map of known tools to their canonical "primary" arg field. When the arg is
221
- * a string the header summarises it directly and the args body is suppressed
222
- * because echoing `{"path": "..."}` next to `tool: read(...)` is duplicate
223
- * noise. Tools not in this map (or with an unexpected arg shape) fall back
224
- * to a JSON-dump summary plus the full args body.
235
+ * a string the header summarises it directly and the expanded argument list
236
+ * omits only that repeated field. Tools not in this map (or with an unexpected
237
+ * arg shape) fall back to a JSON summary plus their complete redacted fields.
225
238
  */
226
239
  const PRIMARY_ARG_FIELD: Record<string, string> = {
227
240
  read: "path",
@@ -244,8 +257,8 @@ const PRIMARY_ARG_FIELD: Record<string, string> = {
244
257
 
245
258
  /**
246
259
  * Returns the captured primary-arg string when the header successfully used a
247
- * known tool's canonical arg, otherwise null. Drives the args-body suppression
248
- * so `tool: read(README.md)` does not get followed by `{"path": "README.md"}`.
260
+ * known tool's canonical arg, otherwise null. The argument renderer uses the
261
+ * same map to omit that one repeated field below `read(README.md)`.
249
262
  */
250
263
  function capturedPrimaryArg(toolName: string, args: unknown): string | null {
251
264
  const field = PRIMARY_ARG_FIELD[toolName];
@@ -278,24 +291,6 @@ function summarizeArgs(toolName: string, args: unknown): string {
278
291
  return truncate(jsonStringifySafe(safeArgs), ARG_PREVIEW_LIMIT);
279
292
  }
280
293
 
281
- /**
282
- * Compact resource-read classification. Reads of skill/handbook/agent
283
- * instruction files and docs pages collapse to one labeled line and never
284
- * auto-expand; their bodies are reference material, not task output.
285
- */
286
- export function classifyResourceRead(toolName: string, args: unknown): string | null {
287
- if (toolName !== "read") return null;
288
- const path = readStringField(args, "path");
289
- if (path === null) return null;
290
- const normalized = path.replace(/\\/g, "/");
291
- const base = normalized.split("/").pop() ?? "";
292
- if (base === "SKILL.md") return "skill";
293
- if (base === "CLIO-CODER.md") return "handbook";
294
- if (base === "AGENTS.md") return "agents";
295
- if (/(^|\/)docs\//.test(normalized)) return "docs";
296
- return null;
297
- }
298
-
299
294
  function detailsOf(result: unknown): Record<string, unknown> | null {
300
295
  if (!isPlainObject(result)) return null;
301
296
  return isPlainObject(result.details) ? result.details : null;
@@ -318,12 +313,6 @@ function stringField(record: Record<string, unknown> | null, key: string): strin
318
313
  return typeof value === "string" && value.length > 0 ? value : null;
319
314
  }
320
315
 
321
- function formatBytes(bytes: number): string {
322
- if (bytes < 1024) return `${bytes}B`;
323
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
324
- return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
325
- }
326
-
327
316
  function countSummary(observation: Record<string, unknown>): string | null {
328
317
  const unit = stringField(observation, "unit") ?? "results";
329
318
  const shown = numberField(observation, "shownCount");
@@ -396,9 +385,46 @@ function shownBytesOf(finished: ToolExecutionFinished): number | null {
396
385
  const observation = observationOf(finished);
397
386
  const fromObservation = numberField(observation, "shownBytes");
398
387
  if (fromObservation !== null) return fromObservation;
388
+ const resultSize = detailsOf(finished.result)?.resultSize;
389
+ const fromResultSize = isPlainObject(resultSize) ? numberField(resultSize, "shownBytes") : null;
390
+ if (fromResultSize !== null) return fromResultSize;
399
391
  return numberField(finished.resultSummary ?? null, "bytes");
400
392
  }
401
393
 
394
+ function totalBytesOf(finished: ToolExecutionFinished): number | null {
395
+ const observation = observationOf(finished);
396
+ const fromObservation = numberField(observation, "totalBytes");
397
+ if (fromObservation !== null) return fromObservation;
398
+ const resultSize = detailsOf(finished.result)?.resultSize;
399
+ if (isPlainObject(resultSize)) {
400
+ const total = numberField(resultSize, "bytes");
401
+ if (total !== null) return total;
402
+ }
403
+ return shownBytesOf(finished);
404
+ }
405
+
406
+ function resultSizeOf(finished: ToolExecutionFinished): Record<string, unknown> | null {
407
+ const value = detailsOf(finished.result)?.resultSize;
408
+ return isPlainObject(value) ? value : null;
409
+ }
410
+
411
+ function isTruncatedResult(finished: ToolExecutionFinished): boolean {
412
+ const observation = observationOf(finished);
413
+ if (observation?.truncated === true) return true;
414
+ if (resultSizeOf(finished)?.truncated === true) return true;
415
+ const truncation = detailsOf(finished.result)?.truncation;
416
+ if (isPlainObject(truncation) && truncation.truncated === true) return true;
417
+ return finished.resultSummary?.truncated === true;
418
+ }
419
+
420
+ function structuredExitCode(finished: ToolExecutionFinished): string | null {
421
+ if (isNonExecutedOutcome(finished.outcome)) return null;
422
+ if (finished.exitCode !== undefined && finished.exitCode !== null) return String(finished.exitCode);
423
+ const exitCode = detailsOf(finished.result)?.exitCode;
424
+ if (typeof exitCode === "number" || typeof exitCode === "string") return String(exitCode);
425
+ return finished.toolName === "bash" && finished.isError ? bashExitCodeFromResult(finished.result) : null;
426
+ }
427
+
402
428
  /**
403
429
  * The dim ledger tail appended to a finished collapsed subline: outcome facts,
404
430
  * then byte size (duration rides on the status glyph), then the offload path
@@ -409,15 +435,58 @@ function ledgerTail(finished: ToolExecutionFinished): { facts: string; offload:
409
435
  const parts: string[] = [];
410
436
  const outcome = outcomeSummary(finished);
411
437
  if (outcome !== null) parts.push(outcome);
412
- const bytes = isNonExecutedOutcome(finished.outcome) ? null : shownBytesOf(finished);
413
- if (bytes !== null && bytes > 0) parts.push(formatBytes(bytes));
414
- const offloadPath = offloadPathOf(finished);
438
+ const executed = !isNonExecutedOutcome(finished.outcome);
439
+ // A folded bash row is the only view of the call the operator gets by
440
+ // default, so its settlement facts have to be here rather than only in the
441
+ // expanded body. A failed row already carries `(exit N)` on the status glyph.
442
+ if (executed && finished.toolName === "bash" && !finished.isError) {
443
+ parts.push(`exit ${structuredExitCode(finished) ?? "0"}`);
444
+ }
445
+ const bytes = executed ? shownBytesOf(finished) : null;
446
+ if (bytes !== null && bytes > 0) {
447
+ const total = totalBytesOf(finished);
448
+ parts.push(
449
+ total !== null && total > bytes ? `${formatSize(bytes)} shown / ${formatSize(total)} total` : formatSize(bytes),
450
+ );
451
+ }
452
+ if (executed) {
453
+ if (isTruncatedResult(finished)) parts.push("truncated");
454
+ const details = detailsOf(finished.result);
455
+ if (details?.timedOut === true) parts.push("timed out");
456
+ if (details?.outputCapped === true) parts.push("output capped");
457
+ }
458
+ if (finished.excludeFromContext === true) parts.push("excluded from context");
459
+ const offloadPath = executed ? offloadPathOf(finished) : null;
415
460
  return {
416
461
  facts: parts.length > 0 ? dim(` · ${parts.join(" · ")}`) : "",
417
462
  offload: offloadPath !== null ? dim(` · full: ${offloadPath}`) : "",
418
463
  };
419
464
  }
420
465
 
466
+ /** Longest diagnostic excerpt a folded failure row may carry. */
467
+ const FAILURE_EXCERPT_LIMIT = 80;
468
+
469
+ /**
470
+ * Last non-empty output line of a failed call, bounded so the folded row stays
471
+ * diagnostically useful without opening the body. Sanitized to one line: the
472
+ * source is raw tool output. The tool's presentation policy decides whether
473
+ * its failures carry one; the renderer never names a tool here.
474
+ */
475
+ function failureExcerpt(finished: ToolExecutionFinished, width: number): string {
476
+ if (!finished.isError || !toolPresentationPolicy(finished.toolName, finished.args).failureExcerpt) return "";
477
+ if (isNonExecutedOutcome(finished.outcome)) return "";
478
+ const text = unwrapResultEnvelope(finished.result);
479
+ if (typeof text !== "string") return "";
480
+ let excerpt: string | undefined;
481
+ for (const raw of text.split("\n")) {
482
+ const line = sanitizeCallTargetText(raw).trim();
483
+ if (line.length > 0) excerpt = line;
484
+ }
485
+ if (excerpt === undefined) return "";
486
+ const limit = Math.min(FAILURE_EXCERPT_LIMIT, Math.max(20, width - 20));
487
+ return dim(` · ${truncate(excerpt, limit)}`);
488
+ }
489
+
421
490
  /**
422
491
  * Marker for a call parked at admission awaiting operator approval. Rendered
423
492
  * in place of the elapsed counter so a parked call never reads as executing
@@ -430,24 +499,40 @@ const AWAITING_APPROVAL_TAIL = ` ${yellow(GLYPH.phaseBlocked)}${dim(" awaiting a
430
499
  * No elapsed counter (nothing is running) and no status glyph (nothing has
431
500
  * finished): the awaiting-approval tail is the segment's whole state.
432
501
  */
433
- export function renderToolAwaitingApproval(call: ToolExecutionStart, width: number): string[] {
502
+ export function renderToolAwaitingApproval(
503
+ call: ToolExecutionStart,
504
+ width: number,
505
+ view?: ApprovalRequestView,
506
+ ): string[] {
434
507
  const parts = sublineParts({ toolCallId: call.toolCallId, toolName: call.toolName, args: call.args }, undefined, {});
435
- return wrapSublineWithTail(parts.lead, AWAITING_APPROVAL_TAIL, width);
508
+ const lines = wrapSublineWithTail(parts.lead, AWAITING_APPROVAL_TAIL, width);
509
+ if (view === undefined) return lines;
510
+ const axis = view.axis.kind === "net" ? `safety-net rail ${view.axis.ruleId}` : `autonomy level ${view.axis.level}`;
511
+ const facts = [
512
+ ["action", view.actionClass],
513
+ ["axis", axis],
514
+ ...(view.target !== undefined && view.target.length > 0 ? [["target", view.target]] : []),
515
+ ] as const;
516
+ for (const [label, value] of facts) {
517
+ lines.push(...indentAndWrap(`${dim(`${label} ·`)} ${value}`, width, false));
518
+ }
519
+ return lines;
436
520
  }
437
521
 
438
522
  /**
439
- * Header status: `undefined` when the call is still in flight (no glyph),
440
- * `"ok"` for success (green check), `"error"` for failure (red cross). The
523
+ * Header status follows Pi's call lifecycle before and during execution, then
524
+ * becomes `"ok"` or `"error"` at settlement. The
441
525
  * glyph hangs off the right of the header line so the tool name + args read
442
526
  * left-to-right without extra punctuation.
443
527
  */
444
- type HeaderStatus = "ok" | "error" | undefined;
528
+ type HeaderStatus = "forming" | "ready" | "running" | "ok" | "error" | undefined;
445
529
 
446
530
  /** Keeps a refusal reason to one scannable clause on the status tail. */
447
531
  const BLOCK_REASON_LIMIT = 72;
448
532
 
449
533
  interface StatusMeta {
450
534
  durationMs?: number | undefined;
535
+ elapsedMs?: number | undefined;
451
536
  exitCode?: string | null;
452
537
  outcome?: ToolExecutionFinished["outcome"];
453
538
  /** Refusal reason, rendered only alongside a non-executed outcome. */
@@ -456,6 +541,12 @@ interface StatusMeta {
456
541
 
457
542
  function statusGlyph(status: HeaderStatus, meta: StatusMeta = {}): string {
458
543
  if (status === undefined) return "";
544
+ if (status === "forming") return ` ${dim(GLYPH.queued)}${dim(" forming call")}`;
545
+ if (status === "ready") return ` ${dim(GLYPH.queued)}${dim(" ready")}`;
546
+ if (status === "running") {
547
+ const elapsed = optionalCompactMs(meta.elapsedMs);
548
+ return ` ${cyan(GLYPH.running)}${dim(elapsed === null ? " running" : ` running · ${elapsed}`)}`;
549
+ }
459
550
  const duration = optionalCompactMs(meta.durationMs);
460
551
  const durationSuffix = duration ? dim(` · ${duration}`) : "";
461
552
  if (status === "ok") return ` ${green(STATUS_OK_GLYPH)}${durationSuffix}`;
@@ -470,8 +561,8 @@ function statusGlyph(status: HeaderStatus, meta: StatusMeta = {}): string {
470
561
  }
471
562
 
472
563
  function headerLine(toolName: string, args: unknown, status: HeaderStatus, meta: StatusMeta = {}): string {
473
- const summary = summarizeArgs(toolName, args);
474
- const head = `${dim(HEADER_PREFIX_PLAIN)}${cyanBold(toolName)}${dim("(")}${cyan(summary)}${dim(")")}`;
564
+ const body = styleSublineBody(buildSublineBody(toolName, args, status, undefined, meta.outcome));
565
+ const head = `${dim(HEADER_PREFIX_PLAIN)}${body}`;
475
566
  return `${head}${statusGlyph(status, meta)}`;
476
567
  }
477
568
 
@@ -485,8 +576,9 @@ function styleSublineBody(body: string): string {
485
576
  return sublineLead(match.groups.lead, match.groups.rest);
486
577
  }
487
578
 
488
- function buildUnknownToolBody(toolName: string, args: unknown): string {
489
- return `${toolName}(${summarizeArgs(toolName, args)})`;
579
+ function buildGenericToolBody(toolName: string, args: unknown): string {
580
+ const summary = summarizeArgs(toolName, args);
581
+ return summary.length > 0 ? `tool action ${summary}` : "tool action";
490
582
  }
491
583
 
492
584
  function buildFieldSublineBody(
@@ -543,6 +635,7 @@ const SUBLINE_BODY_BUILDERS: Readonly<Record<string, (args: unknown) => string |
543
635
  bash: (args) => buildFieldSublineBody(args, "command", "running ", { wrapInBackticks: true }),
544
636
  grep: (args) => buildFieldSublineBody(args, "pattern", "searching for ", { wrapInBackticks: true }),
545
637
  find: (args) => buildFieldSublineBody(args, "pattern", "finding ", { wrapInBackticks: true }),
638
+ web_fetch: (args) => buildFieldSublineBody(args, "url", "fetching "),
546
639
  git: (args) => buildFieldSublineBody(args, "op", "git "),
547
640
  verify: (args) => buildFieldSublineBody(args, "check", "verifying "),
548
641
  code_nav: (args) => {
@@ -580,7 +673,7 @@ const SUBLINE_BODY_BUILDERS: Readonly<Record<string, (args: unknown) => string |
580
673
  /**
581
674
  * Per-tool subline templates. Maps a tool name to a function that builds the
582
675
  * verb-led subline body without the leading glyph and without the trailing
583
- * status glyph. Unknown tools fall back to the existing `<name>(<arg>)` form.
676
+ * status glyph. Unknown tools fall back to a tool-neutral action summary.
584
677
  */
585
678
  function webFetchMeta(result: unknown): string | null {
586
679
  if (!isPlainObject(result) || !isPlainObject(result.details)) return null;
@@ -597,11 +690,10 @@ function webFetchMeta(result: unknown): string | null {
597
690
 
598
691
  /**
599
692
  * A call the permission gate blocked never executed, so the collapsed row must
600
- * not claim it did. `ran \`id\` · 667B blocked` read as a command that ran and
601
- * produced 667 bytes, while the expanded header for the same node correctly said
602
- * `bash(id) blocked`. Blocked rows use that same call-signature form, and the
603
- * ledger byte count is suppressed because those bytes are the denial text, not
604
- * output.
693
+ * not claim it did. Blocked calls keep the ordinary operator-facing action
694
+ * description but use a neutral noun for commands; the status tail supplies the
695
+ * blocked outcome. The ledger byte count is suppressed elsewhere because those
696
+ * bytes are the denial text, not output.
605
697
  */
606
698
  function isNonExecutedOutcome(outcome: ToolExecutionFinished["outcome"]): boolean {
607
699
  return outcome === "blocked";
@@ -614,20 +706,29 @@ function buildSublineBody(
614
706
  result?: unknown,
615
707
  outcome?: ToolExecutionFinished["outcome"],
616
708
  ): string {
617
- if (isNonExecutedOutcome(outcome)) return buildUnknownToolBody(toolName, args);
709
+ if (isNonExecutedOutcome(outcome)) {
710
+ if (toolName === "bash") {
711
+ return (
712
+ buildFieldSublineBody(args, "command", "command ", { wrapInBackticks: true }) ??
713
+ buildGenericToolBody(toolName, args)
714
+ );
715
+ }
716
+ return SUBLINE_BODY_BUILDERS[toolName]?.(args) ?? buildGenericToolBody(toolName, args);
717
+ }
618
718
  if (toolName === "web_fetch") {
619
719
  const meta = status === undefined ? null : webFetchMeta(result);
620
- return `${buildUnknownToolBody(toolName, args)}${meta ? dim(` · ${meta}`) : ""}`;
720
+ const body = SUBLINE_BODY_BUILDERS.web_fetch?.(args) ?? buildGenericToolBody(toolName, args);
721
+ return `${body}${meta ? dim(` · ${meta}`) : ""}`;
621
722
  }
622
723
  if (toolName === "bash") {
623
- const lead = status === undefined ? "running " : "ran ";
724
+ const lead = status === "ok" || status === "error" ? "ran " : "running ";
624
725
  return (
625
- buildFieldSublineBody(args, "command", lead, { wrapInBackticks: true }) ?? buildUnknownToolBody(toolName, args)
726
+ buildFieldSublineBody(args, "command", lead, { wrapInBackticks: true }) ?? buildGenericToolBody(toolName, args)
626
727
  );
627
728
  }
628
729
  const body = SUBLINE_BODY_BUILDERS[toolName]?.(args);
629
730
  if (body !== null && body !== undefined) return body;
630
- return buildUnknownToolBody(toolName, args);
731
+ return buildGenericToolBody(toolName, args);
631
732
  }
632
733
 
633
734
  interface SublineParts {
@@ -660,9 +761,7 @@ function sublineParts(
660
761
  tail: `${statusGlyph(status, meta)}${ledger.offload}`,
661
762
  };
662
763
  }
663
- const elapsed = optionalCompactMs("elapsedMs" in call ? call.elapsedMs : undefined);
664
- const running = elapsed !== null ? dim(` · ${elapsed}`) : "";
665
- return { lead: `${dim(HEADER_PREFIX_PLAIN)}${body}${resource}${running}`, tail: "" };
764
+ return { lead: `${dim(HEADER_PREFIX_PLAIN)}${body}${resource}`, tail: statusGlyph(status, meta) };
666
765
  }
667
766
 
668
767
  /**
@@ -708,33 +807,69 @@ function indentAndWrap(line: string, width: number, isError: boolean): string[]
708
807
  return out;
709
808
  }
710
809
 
810
+ function scalarArgValue(value: unknown): string | null {
811
+ if (typeof value === "string") {
812
+ const lines = value.split("\n").length;
813
+ const bytes = Buffer.byteLength(value, "utf8");
814
+ if (lines > 1 || value.length > 160) return dim(`<${lines} lines · ${formatSize(bytes)} text>`);
815
+ return green(JSON.stringify(value));
816
+ }
817
+ if (typeof value === "number") return cyan(String(value));
818
+ if (typeof value === "boolean") return yellow(String(value));
819
+ if (value === null) return dim("null");
820
+ return null;
821
+ }
822
+
711
823
  /**
712
- * Render the pretty-printed JSON args body. Splits on `\n` first and then
713
- * applies a line cap so the cut never lands inside an open string or before
714
- * a closing brace; the previous codepoint-level `truncate` could leave a
715
- * malformed JSON snippet that broke copy-paste. Mirrors the line-cap strategy
716
- * `renderResultBlock` already uses for tool output.
824
+ * Render secondary arguments as a compact typed field list. The primary path,
825
+ * command, pattern, or query already lives in the call signature; keeping the
826
+ * rest means `cwd`, timeout, range, glob, and flags no longer disappear merely
827
+ * because one field was important enough for the header. Nested values retain
828
+ * the structured JSON renderer, while large strings become byte/line facts
829
+ * instead of flooding the transcript.
717
830
  */
718
- function renderArgsBody(args: unknown, width: number, isError: boolean): string[] {
831
+ function renderArgsBody(toolName: string, args: unknown, width: number, isError: boolean): string[] {
719
832
  if (isEmptyArgs(args)) return [];
833
+ const safeArgs = redactToolArgs(args);
834
+ if (!isPlainObject(safeArgs)) {
835
+ const bodyWidth = Math.max(1, width - BODY_INDENT_VISIBLE_WIDTH);
836
+ const lines = tryRenderJson(safeArgs, bodyWidth, { lineLimit: ARGS_BODY_LINE_LIMIT });
837
+ return lines?.flatMap((line) => indentAndWrap(line, width, isError)) ?? [];
838
+ }
839
+ const primary = PRIMARY_ARG_FIELD[toolName];
840
+ const entries = Object.entries(safeArgs).filter(([key]) => key !== primary);
841
+ if (entries.length === 0) return [];
720
842
  const out: string[] = [];
721
843
  const bodyWidth = Math.max(1, width - BODY_INDENT_VISIBLE_WIDTH);
722
- const lines = tryRenderJson(redactToolArgs(args), bodyWidth, { lineLimit: ARGS_BODY_LINE_LIMIT });
723
- if (!lines) return [];
724
- for (const raw of lines) {
725
- out.push(...indentAndWrap(raw, width, isError));
844
+ out.push(...indentAndWrap(`${cyanBold("args")}${dim(` · ${entries.length}`)}`, width, isError));
845
+ const keyWidth = Math.min(18, Math.max(...entries.map(([key]) => visibleWidth(key))));
846
+ for (const [key, value] of entries) {
847
+ const label = `${cyan(key.padEnd(keyWidth))} `;
848
+ const scalar = scalarArgValue(value);
849
+ if (scalar !== null) {
850
+ out.push(...indentAndWrap(`${label}${scalar}`, width, isError));
851
+ continue;
852
+ }
853
+ out.push(...indentAndWrap(cyan(key), width, isError));
854
+ const lines = tryRenderJson(value, Math.max(1, bodyWidth - 2), { lineLimit: ARGS_BODY_LINE_LIMIT });
855
+ for (const line of lines ?? [jsonStringifySafe(value)]) {
856
+ out.push(...indentAndWrap(` ${line}`, width, isError));
857
+ if (out.length >= ARGS_BODY_LINE_LIMIT) break;
858
+ }
859
+ if (out.length >= ARGS_BODY_LINE_LIMIT) break;
726
860
  }
861
+ if (out.length >= ARGS_BODY_LINE_LIMIT) out.push(...indentAndWrap(dim("… more arguments hidden"), width, isError));
727
862
  return out;
728
863
  }
729
864
 
730
865
  /**
731
866
  * pi-agent-core wraps tool results in `{ content: [{ type: "text", text }, ...] }`
732
- * envelopes. Rendering that JSON verbatim hides the actual tool output behind
733
- * a sea of escaped quotes. When the envelope shape matches, concatenate the
734
- * text segments and treat the join as the real result; otherwise return the
735
- * value untouched so callers stringify it normally.
867
+ * envelopes. Rendering that JSON verbatim hides the actual output and, for a
868
+ * mixed result, can dump a base64 image into the terminal. Text blocks are
869
+ * joined, image blocks become compact MIME/size placeholders, and unknown
870
+ * content blocks are named without serializing their payload.
736
871
  */
737
- export function unwrapResultEnvelope(result: unknown): unknown {
872
+ function unwrapResultEnvelope(result: unknown): unknown {
738
873
  if (typeof result === "string" || result === null || result === undefined) return result;
739
874
  const blocks = Array.isArray(result)
740
875
  ? result
@@ -745,16 +880,25 @@ export function unwrapResultEnvelope(result: unknown): unknown {
745
880
  const parts: string[] = [];
746
881
  for (const block of blocks) {
747
882
  if (!isPlainObject(block)) return result;
748
- if (block.type !== "text" || typeof block.text !== "string") return result;
749
- parts.push(block.text);
883
+ if (block.type === "text" && typeof block.text === "string") {
884
+ parts.push(block.text);
885
+ continue;
886
+ }
887
+ if (block.type === "image" && typeof block.data === "string") {
888
+ const mimeType = typeof block.mimeType === "string" ? block.mimeType : "image/unknown";
889
+ const padding = block.data.endsWith("==") ? 2 : block.data.endsWith("=") ? 1 : 0;
890
+ const bytes = Math.max(0, Math.floor((block.data.length * 3) / 4) - padding);
891
+ parts.push(`[image ${mimeType} · ${formatSize(bytes)}]`);
892
+ continue;
893
+ }
894
+ if (typeof block.type === "string") {
895
+ parts.push(`[${block.type} content]`);
896
+ continue;
897
+ }
898
+ return result;
750
899
  }
751
900
  if (parts.length === 0) return result;
752
- return parts.join("");
753
- }
754
-
755
- export function previewResult(result: unknown): string {
756
- if (typeof result === "string") return truncate(result, RESULT_PREVIEW_LIMIT);
757
- return truncate(jsonStringifySafe(result), RESULT_PREVIEW_LIMIT);
901
+ return parts.join("\n");
758
902
  }
759
903
 
760
904
  function isEmptyResult(result: unknown): boolean {
@@ -822,50 +966,14 @@ function highlightBashCommand(command: string): string {
822
966
  .join("");
823
967
  }
824
968
 
825
- interface EditDiffArgs {
826
- path?: string;
827
- old_string: string;
828
- new_string: string;
829
- }
830
-
831
- /**
832
- * Defensive shape check: edit-tool args must carry both `old_string` and
833
- * `new_string` strings before we can render a diff. `path` is optional; the
834
- * diff renderer falls back to `"file"` when absent. Anything else falls
835
- * through to the standard result block so the dispatch is opportunistic and
836
- * never throws.
837
- */
838
- function asEditDiffArgs(args: unknown): EditDiffArgs | null {
839
- if (!isPlainObject(args)) return null;
840
- const oldString = args.old_string;
841
- const newString = args.new_string;
842
- if (typeof oldString !== "string" || typeof newString !== "string") return null;
843
- const out: EditDiffArgs = { old_string: oldString, new_string: newString };
844
- if (typeof args.path === "string") out.path = args.path;
845
- return out;
846
- }
847
-
848
- // The `diff` library appends a `` sentinel row when a
849
- // side of the comparison lacks a trailing newline. It is transcript noise, so
850
- // the edit diff block drops it. The marker text carries no ANSI, so a substring
851
- // check against the styled row is reliable and keeps this module free of raw
852
- // escape literals.
853
- const NO_NEWLINE_MARKER = "\";
854
-
855
- function isNoNewlineMarkerRow(row: string): boolean {
856
- return row.includes(NO_NEWLINE_MARKER);
969
+ function resultDiff(result: unknown): string | null {
970
+ const value = detailsOf(result)?.diff;
971
+ return typeof value === "string" && value.length > 0 ? value : null;
857
972
  }
858
973
 
859
- function renderEditDiffBlock(args: EditDiffArgs, width: number): string[] {
974
+ function renderMutationDiffBlock(diff: string, width: number, color: boolean): string[] {
860
975
  const bodyWidth = Math.max(1, width - BODY_INDENT_VISIBLE_WIDTH);
861
- const input: DiffRenderInput = { oldText: args.old_string, newText: args.new_string };
862
- if (args.path !== undefined) input.filename = args.path;
863
- const out: string[] = [];
864
- for (const line of renderUnifiedDiff(input, bodyWidth)) {
865
- if (isNoNewlineMarkerRow(line)) continue;
866
- out.push(`${RAIL_DIM}${line}`);
867
- }
868
- return out;
976
+ return renderDiffLines(diff, bodyWidth, { color }).map((line) => `${RAIL_DIM}${line}`);
869
977
  }
870
978
 
871
979
  interface BashArgs {
@@ -884,6 +992,79 @@ function asBashArgs(args: unknown): BashArgs | null {
884
992
  return { command: redactSecretString(command) };
885
993
  }
886
994
 
995
+ function resultLineCount(result: unknown): number {
996
+ const unwrapped = unwrapResultEnvelope(result);
997
+ if (typeof unwrapped !== "string" || unwrapped.length === 0) return 0;
998
+ const normalized = unwrapped.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
999
+ const withoutTerminator = normalized.endsWith("\n") ? normalized.slice(0, -1) : normalized;
1000
+ return withoutTerminator.split("\n").length;
1001
+ }
1002
+
1003
+ function toolUsageFact(result: unknown): string | null {
1004
+ if (!isPlainObject(result) || !isPlainObject(result.usage)) return null;
1005
+ const total = numberField(result.usage, "totalTokens");
1006
+ if (total === null || total <= 0) return null;
1007
+ return `${total} tool token${total === 1 ? "" : "s"}`;
1008
+ }
1009
+
1010
+ function outputFacts(finished: ToolExecutionFinished): string[] {
1011
+ const parts: string[] = [];
1012
+ if (isNonExecutedOutcome(finished.outcome)) {
1013
+ if (finished.excludeFromContext === true) parts.push("excluded from context");
1014
+ return parts;
1015
+ }
1016
+ const exitCode = structuredExitCode(finished) ?? (finished.toolName === "bash" && !finished.isError ? "0" : null);
1017
+ if (exitCode !== null) parts.push(`exit ${exitCode}`);
1018
+ const observation = observationOf(finished);
1019
+ const count = observation === null ? null : countSummary(observation);
1020
+ if (count !== null) parts.push(count);
1021
+ const lines = resultLineCount(finished.result);
1022
+ if (count === null && lines > 0) parts.push(`${lines} line${lines === 1 ? "" : "s"}`);
1023
+ const shownBytes = shownBytesOf(finished);
1024
+ const totalBytes = totalBytesOf(finished);
1025
+ if (shownBytes !== null && shownBytes > 0) {
1026
+ parts.push(
1027
+ totalBytes !== null && totalBytes > shownBytes
1028
+ ? `${formatSize(shownBytes)} shown / ${formatSize(totalBytes)} total`
1029
+ : formatSize(shownBytes),
1030
+ );
1031
+ }
1032
+ if (isTruncatedResult(finished)) parts.push("truncated");
1033
+ const details = detailsOf(finished.result);
1034
+ if (details?.timedOut === true) parts.push("timed out");
1035
+ if (details?.outputCapped === true) parts.push("output capped");
1036
+ const usage = toolUsageFact(finished.result);
1037
+ if (usage !== null) parts.push(usage);
1038
+ if (isPlainObject(finished.result) && Array.isArray(finished.result.addedToolNames)) {
1039
+ const added = finished.result.addedToolNames.filter((name): name is string => typeof name === "string");
1040
+ if (added.length > 0) parts.push(`added ${added.join(", ")}`);
1041
+ }
1042
+ if (isPlainObject(finished.result) && finished.result.terminate === true) parts.push("terminal result");
1043
+ if (finished.excludeFromContext === true) parts.push("excluded from context");
1044
+ return parts;
1045
+ }
1046
+
1047
+ function renderOutputMeta(
1048
+ finished: ToolExecutionFinished,
1049
+ width: number,
1050
+ isError: boolean,
1051
+ label = "output",
1052
+ ): string[] {
1053
+ const facts = outputFacts(finished);
1054
+ const suffix = facts.length > 0 ? dim(` · ${facts.join(" · ")}`) : "";
1055
+ return indentAndWrap(`${cyanBold(label)}${suffix}`, width, isError);
1056
+ }
1057
+
1058
+ function renderOutputFooter(finished: ToolExecutionFinished, width: number, isError: boolean): string[] {
1059
+ const out: string[] = [];
1060
+ const offloadPath = isNonExecutedOutcome(finished.outcome) ? null : offloadPathOf(finished);
1061
+ if (offloadPath !== null) out.push(...indentAndWrap(`${yellow("full output")} ${offloadPath}`, width, isError));
1062
+ const hint =
1063
+ stringField(resultSizeOf(finished), "followUpHint") ?? stringField(finished.resultSummary ?? null, "followUpHint");
1064
+ if (hint !== null) out.push(...indentAndWrap(`${dim("next")} ${hint}`, width, isError));
1065
+ return out;
1066
+ }
1067
+
887
1068
  /**
888
1069
  * Bash subrenderer: emits `$ <cmd>` (full command) on its own line under the
889
1070
  * rail, then the unwrapped output via the same chain as `renderResultBlock`.
@@ -927,11 +1108,17 @@ function renderResultBlock(
927
1108
 
928
1109
  /**
929
1110
  * Header-only render for a tool call that has not yet finished. Used by the
930
- * live chat panel between `tool_execution_start` and `tool_execution_end`.
931
- * No status glyph: the absence of the glyph signals "still running".
1111
+ * live chat panel from streamed argument formation through execution. The
1112
+ * lifecycle tail distinguishes a forming call, a ready call, and a running
1113
+ * call without inventing execution time before Pi starts the tool.
932
1114
  */
933
1115
  export function renderToolCallHeader(call: ToolExecutionStart, width: number): string[] {
934
- return wrap(headerLine(call.toolName, call.args, undefined), width);
1116
+ return wrap(
1117
+ headerLine(call.toolName, call.args, call.phase ?? "running", {
1118
+ elapsedMs: call.elapsedMs,
1119
+ }),
1120
+ width,
1121
+ );
935
1122
  }
936
1123
 
937
1124
  /** Running-only footer used when an operator pauses live output; execution continues. */
@@ -939,11 +1126,7 @@ export function renderToolRunningStatus(call: ToolExecutionStart, width: number)
939
1126
  const elapsed = optionalCompactMs(call.elapsedMs);
940
1127
  return [
941
1128
  ...renderToolCallHeader(call, width),
942
- ...indentAndWrap(
943
- dim(elapsed ? `(running... ${elapsed}; live output hidden)` : "(running; live output hidden)"),
944
- width,
945
- false,
946
- ),
1129
+ ...indentAndWrap(dim(elapsed ? `live output paused · ${elapsed}` : "live output paused"), width, false),
947
1130
  ];
948
1131
  }
949
1132
 
@@ -952,7 +1135,7 @@ function sublineStatus(call: ToolExecutionStart | ToolExecutionFinished): Header
952
1135
  // carries a `result` field, so a `ToolExecutionStart` with a stray
953
1136
  // `isError: false` (e.g. from a future event-shape change) cannot trip the
954
1137
  // finished path. `result` is the type's load-bearing field.
955
- if (!("result" in call)) return undefined;
1138
+ if (!("result" in call)) return call.phase ?? "running";
956
1139
  return call.isError ? "error" : "ok";
957
1140
  }
958
1141
 
@@ -972,25 +1155,58 @@ function sublineStatus(call: ToolExecutionStart | ToolExecutionFinished): Header
972
1155
  * imports the keybindings manager directly; the caller resolves the key string
973
1156
  * and passes it in to keep this module pure.
974
1157
  */
1158
+ export interface ToolSublineRenderOptions {
1159
+ /** Live rows color the folded mutation diff; replay and export request plain rows. */
1160
+ diffStyle?: "color" | "plain";
1161
+ /**
1162
+ * Whether the folded row carries the extras its tool's presentation asks
1163
+ * for (today the bounded mutation diff). The bare subline level drops them;
1164
+ * the failure excerpt is not one of them and always rides the row.
1165
+ */
1166
+ foldedExtras?: "per-tool" | "none";
1167
+ }
1168
+
1169
+ /**
1170
+ * The bounded diff a folded mutation row keeps under itself, when the tool's
1171
+ * presentation asks for one and the result carries one. A folded `edit` that
1172
+ * hid its change told the operator nothing they could act on.
1173
+ */
1174
+ function foldedDiffRows(finished: ToolExecutionFinished, width: number, opts: ToolSublineRenderOptions): string[] {
1175
+ if (opts.foldedExtras === "none") return [];
1176
+ if (finished.isError || isNonExecutedOutcome(finished.outcome)) return [];
1177
+ if (!toolPresentationPolicy(finished.toolName, finished.args).showDiffWhenFolded) return [];
1178
+ const diff = resultDiff(finished.result);
1179
+ if (diff === null) return [];
1180
+ return truncateRowsMiddle(
1181
+ renderMutationDiffBlock(diff, width, opts.diffStyle !== "plain"),
1182
+ FOLDED_DIFF_ROW_LIMIT,
1183
+ false,
1184
+ );
1185
+ }
1186
+
975
1187
  export function renderToolSubline(
976
1188
  call: ToolExecutionStart | ToolExecutionFinished,
977
1189
  width: number,
978
1190
  expandKey?: string,
1191
+ opts: ToolSublineRenderOptions = {},
979
1192
  ): string[] {
980
1193
  const status = sublineStatus(call);
981
1194
  const meta: StatusMeta =
982
1195
  "result" in call
983
1196
  ? {
984
1197
  durationMs: call.durationMs,
985
- exitCode: call.toolName === "bash" && call.isError ? bashExitCodeFromResult(call.result) : null,
1198
+ exitCode: structuredExitCode(call),
986
1199
  outcome: call.outcome,
987
1200
  blockReason: call.blockReason,
988
1201
  }
989
- : {};
1202
+ : { elapsedMs: call.elapsedMs };
990
1203
  const parts = sublineParts(call, status, meta);
991
- const showHint = status !== undefined && expandKey !== undefined && expandKey.length > 0;
1204
+ const excerpt = "result" in call ? failureExcerpt(call, width) : "";
1205
+ const showHint = "result" in call && expandKey !== undefined && expandKey.length > 0;
992
1206
  const tail = showHint ? `${parts.tail}${dim(` (${expandKey})`)}` : parts.tail;
993
- return wrapSublineWithTail(parts.lead, tail, width);
1207
+ const lines = wrapSublineWithTail(`${parts.lead}${excerpt}`, tail, width);
1208
+ if ("result" in call) lines.push(...foldedDiffRows(call, width, opts));
1209
+ return lines;
994
1210
  }
995
1211
 
996
1212
  /**
@@ -1008,22 +1224,23 @@ export function renderToolExecution(
1008
1224
  const status: HeaderStatus = finished.isError ? "error" : "ok";
1009
1225
  const statusMeta: StatusMeta = {
1010
1226
  durationMs: finished.durationMs,
1011
- exitCode: finished.toolName === "bash" && finished.isError ? bashExitCodeFromResult(finished.result) : null,
1227
+ exitCode: structuredExitCode(finished),
1012
1228
  outcome: finished.outcome,
1013
1229
  blockReason: finished.blockReason,
1014
1230
  };
1015
1231
  const out: string[] = [];
1016
1232
  out.push(...wrap(headerLine(finished.toolName, finished.args, status, statusMeta), width));
1017
1233
 
1018
- // Edit-tool dispatch: when the tool succeeded and `args` carries the
1019
- // expected `{ old_string, new_string }` strings, swap the args body and
1020
- // result block for a unified diff. The header still renders so the user
1021
- // sees `▸ edit(<path>)`, and the args body is suppressed because echoing
1022
- // both strings would just duplicate what the diff already shows.
1023
- if (finished.toolName === "edit" && finished.isError === false) {
1024
- const editArgs = asEditDiffArgs(redactToolArgs(finished.args));
1025
- if (editArgs !== null) {
1026
- out.push(...renderEditDiffBlock(editArgs, width));
1234
+ // Edit and write tools produce one bounded numbered diff on result.details.
1235
+ // It is the authority because canonical edit args can contain multiple
1236
+ // replacements and fuzzy matching can change the actual base text. Live rows
1237
+ // receive Pi's word-level styling; replay and export request plain rows.
1238
+ if ((finished.toolName === "edit" || finished.toolName === "write") && finished.isError === false) {
1239
+ const diff = resultDiff(finished.result);
1240
+ if (diff !== null) {
1241
+ out.push(...renderOutputMeta(finished, width, false, "change"));
1242
+ out.push(...renderMutationDiffBlock(diff, width, opts.diffStyle !== "plain"));
1243
+ out.push(...renderOutputFooter(finished, width, false));
1027
1244
  return out;
1028
1245
  }
1029
1246
  }
@@ -1035,20 +1252,35 @@ export function renderToolExecution(
1035
1252
  if (finished.toolName === "bash") {
1036
1253
  const bashArgs = asBashArgs(redactToolArgs(finished.args));
1037
1254
  if (bashArgs !== null) {
1255
+ out.push(...renderArgsBody(finished.toolName, finished.args, width, finished.isError));
1256
+ out.push(
1257
+ ...renderOutputMeta(
1258
+ finished,
1259
+ width,
1260
+ finished.isError,
1261
+ isNonExecutedOutcome(finished.outcome) ? "decision" : "output",
1262
+ ),
1263
+ );
1038
1264
  out.push(...renderBashResultBlock(bashArgs, finished.result, width, finished.isError, opts));
1265
+ out.push(...renderOutputFooter(finished, width, finished.isError));
1039
1266
  return out;
1040
1267
  }
1041
1268
  }
1042
1269
 
1043
- // Suppress the args body when the header already encodes the salient arg
1044
- // (e.g. `▸ read(README.md)` already shows the path; rendering
1045
- // `{"path": "README.md"}` underneath is duplicate noise). For tools without
1046
- // a known primary arg the body still renders so users see what the model
1047
- // actually invoked.
1048
- if (capturedPrimaryArg(finished.toolName, finished.args) === null) {
1049
- out.push(...renderArgsBody(finished.args, width, finished.isError));
1050
- }
1270
+ // The primary argument already lives in the signature. Keep the secondary
1271
+ // fields below it so flags, ranges, working directories, and timeouts remain
1272
+ // inspectable without repeating the primary value.
1273
+ out.push(...renderArgsBody(finished.toolName, finished.args, width, finished.isError));
1274
+ out.push(
1275
+ ...renderOutputMeta(
1276
+ finished,
1277
+ width,
1278
+ finished.isError,
1279
+ isNonExecutedOutcome(finished.outcome) ? "decision" : "output",
1280
+ ),
1281
+ );
1051
1282
  out.push(...renderResultBlock(finished.result, finished.isError, width, opts));
1283
+ out.push(...renderOutputFooter(finished, width, finished.isError));
1052
1284
  return out;
1053
1285
  }
1054
1286
 
@@ -1065,13 +1297,15 @@ export function renderToolResultOnly(
1065
1297
  const status: HeaderStatus = finished.isError ? "error" : "ok";
1066
1298
  const statusMeta: StatusMeta = {
1067
1299
  durationMs: finished.durationMs,
1068
- exitCode: finished.toolName === "bash" && finished.isError ? bashExitCodeFromResult(finished.result) : null,
1300
+ exitCode: structuredExitCode(finished),
1069
1301
  outcome: finished.outcome,
1070
1302
  blockReason: finished.blockReason,
1071
1303
  };
1072
1304
  const out: string[] = [];
1073
1305
  out.push(...wrap(headerLine(finished.toolName, undefined, status, statusMeta), width));
1306
+ out.push(...renderOutputMeta(finished, width, finished.isError));
1074
1307
  out.push(...renderResultBlock(finished.result, finished.isError, width, opts));
1308
+ out.push(...renderOutputFooter(finished, width, finished.isError));
1075
1309
  return out;
1076
1310
  }
1077
1311
 
@@ -1079,26 +1313,140 @@ export function renderToolResultOnly(
1079
1313
  * Streaming render for an in-flight tool call whose expanded block should
1080
1314
  * surface the latest partial output. Used by the chat panel between
1081
1315
  * `tool_execution_start` and `tool_execution_end` when the user has expanded
1082
- * the tool segment. The header carries no status glyph (still running), the
1083
- * args body is suppressed when the header captures the primary arg, the
1084
- * partial output renders under the rail capped at `RESULT_LINE_LIMIT` lines,
1085
- * and a dim `(running...)` marker on the trailing line communicates that the
1086
- * block is still being written. An empty `partialOutput` renders
1316
+ * the tool segment. The header carries the running lifecycle tail, secondary
1317
+ * arguments remain visible, and the latest cumulative Pi result renders under
1318
+ * a labeled live-output rail capped at the streaming row limit. An empty result renders
1087
1319
  * `(no output yet)` so the user can distinguish a slow start from a stalled
1088
1320
  * call.
1089
1321
  */
1090
- export function renderToolStreamingExecution(call: ToolExecutionStart, width: number, partialOutput: string): string[] {
1322
+ export function renderToolStreamingExecution(
1323
+ call: ToolExecutionStart,
1324
+ width: number,
1325
+ partialResult: unknown,
1326
+ ): string[] {
1091
1327
  const out: string[] = [];
1092
- out.push(...wrap(headerLine(call.toolName, call.args, undefined), width));
1093
- if (capturedPrimaryArg(call.toolName, call.args) === null) {
1094
- out.push(...renderArgsBody(call.args, width, false));
1095
- }
1096
- if (partialOutput.length === 0) {
1328
+ out.push(...renderToolCallHeader({ ...call, phase: "running" }, width));
1329
+ out.push(...renderArgsBody(call.toolName, call.args, width, false));
1330
+ const partial: ToolExecutionFinished = {
1331
+ toolCallId: call.toolCallId,
1332
+ toolName: call.toolName,
1333
+ args: call.args,
1334
+ result: partialResult,
1335
+ isError: false,
1336
+ };
1337
+ out.push(...renderOutputMeta(partial, width, false, "live output"));
1338
+ const partialOutput = unwrapResultEnvelope(partialResult);
1339
+ if (isEmptyResult(partialOutput)) {
1097
1340
  out.push(...indentAndWrap(dim("(no output yet)"), width, false));
1098
1341
  } else {
1099
- out.push(...renderOutputRows(partialOutput, width, false, STREAMING_RESULT_ROW_LIMIT));
1342
+ if (call.toolName === "bash") {
1343
+ const bashArgs = asBashArgs(redactToolArgs(call.args));
1344
+ if (bashArgs !== null) {
1345
+ const commandLine = `${cyanBold("$")} ${highlightBashCommand(stripShellWrapperForDisplay(bashArgs.command))}`;
1346
+ out.push(...indentAndWrap(commandLine, width, false));
1347
+ }
1348
+ }
1349
+ out.push(
1350
+ ...renderOutputRows(resultText(partialOutput, FULL_RESULT_PREVIEW_LIMIT), width, false, STREAMING_RESULT_ROW_LIMIT),
1351
+ );
1100
1352
  }
1101
- const elapsed = optionalCompactMs(call.elapsedMs);
1102
- out.push(...indentAndWrap(dim(elapsed !== null ? `(running... ${elapsed})` : "(running...)"), width, false));
1103
1353
  return out;
1104
1354
  }
1355
+
1356
+ /** Display-only lifecycle for operator-issued `!` and `!!` bash commands. */
1357
+ export interface BashTranscriptExecution {
1358
+ command: string;
1359
+ output: string;
1360
+ running: boolean;
1361
+ elapsedMs?: number | undefined;
1362
+ totalBytes?: number | undefined;
1363
+ exitCode?: number | null | undefined;
1364
+ cancelled?: boolean | undefined;
1365
+ truncated?: boolean | undefined;
1366
+ fullOutputPath?: string | undefined;
1367
+ excludeFromContext?: boolean | undefined;
1368
+ error?: string | undefined;
1369
+ /**
1370
+ * Whether the block draws its one-line row instead of the full body. The
1371
+ * caller resolves this from the transcript detail policy and the operator's
1372
+ * override, the same way the panel resolves a model bash call. Omitted means
1373
+ * folded.
1374
+ */
1375
+ folded?: boolean | undefined;
1376
+ }
1377
+
1378
+ /**
1379
+ * Render local bash with the same call, argument, output, and settlement grammar
1380
+ * as model-initiated bash. This is a view projection only; callers keep Clio's
1381
+ * existing immutable `bashExecution` ledger entry.
1382
+ */
1383
+ export function renderBashTranscriptExecution(
1384
+ execution: BashTranscriptExecution,
1385
+ width: number,
1386
+ expandKey?: string,
1387
+ bodyOptions: ToolBodyRenderOptions = {},
1388
+ ): string[] {
1389
+ const shownBytes = Buffer.byteLength(execution.output, "utf8");
1390
+ const totalBytes = execution.totalBytes ?? shownBytes;
1391
+ const args: Record<string, unknown> = { command: execution.command };
1392
+ if (execution.excludeFromContext === true) args.context = "excluded from model context";
1393
+ const details = {
1394
+ resultSize: {
1395
+ bytes: totalBytes,
1396
+ shownBytes,
1397
+ truncated: execution.truncated === true,
1398
+ policy: "tail",
1399
+ ...(execution.fullOutputPath !== undefined ? { offloadPath: execution.fullOutputPath } : {}),
1400
+ },
1401
+ };
1402
+ const result = {
1403
+ content: [{ type: "text", text: execution.output }],
1404
+ details,
1405
+ };
1406
+ const folded = execution.folded !== false;
1407
+ if (execution.running) {
1408
+ if (folded) {
1409
+ return renderToolSubline(
1410
+ {
1411
+ toolCallId: "local-bash",
1412
+ toolName: "bash",
1413
+ args,
1414
+ elapsedMs: execution.elapsedMs,
1415
+ phase: "running",
1416
+ },
1417
+ width,
1418
+ );
1419
+ }
1420
+ return renderToolStreamingExecution(
1421
+ {
1422
+ toolCallId: "local-bash",
1423
+ toolName: "bash",
1424
+ args,
1425
+ elapsedMs: execution.elapsedMs,
1426
+ phase: "running",
1427
+ },
1428
+ width,
1429
+ result,
1430
+ );
1431
+ }
1432
+ const message = execution.error?.trim();
1433
+ const output = message
1434
+ ? `${execution.output}${execution.output.length > 0 ? "\n\n" : ""}${message}`
1435
+ : execution.output;
1436
+ const finished: ToolExecutionFinished = {
1437
+ toolCallId: "local-bash",
1438
+ toolName: "bash",
1439
+ args,
1440
+ result: { ...result, content: [{ type: "text", text: output }] },
1441
+ isError: execution.cancelled === true || execution.error !== undefined || (execution.exitCode ?? 0) !== 0,
1442
+ exitCode: execution.exitCode,
1443
+ ...(execution.cancelled === true ? { outcome: "aborted" as const } : {}),
1444
+ excludeFromContext: execution.excludeFromContext,
1445
+ resultSummary: {
1446
+ bytes: shownBytes,
1447
+ truncated: execution.truncated === true,
1448
+ ...(execution.fullOutputPath !== undefined ? { offloadPath: execution.fullOutputPath } : {}),
1449
+ },
1450
+ };
1451
+ return folded ? renderToolSubline(finished, width, expandKey) : renderToolExecution(finished, width, bodyOptions);
1452
+ }