@iowarp/clio-coder 0.4.5 → 0.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/CONTRIBUTING.md +14 -9
  3. package/NOTICE +16 -0
  4. package/README.md +312 -685
  5. package/dist/{acp-H3CU2HBQ.js → acp-UVEVWSDL.js} +6 -6
  6. package/dist/{agents-IXQQZ7BD.js → agents-VWY37DTK.js} +31 -30
  7. package/dist/assets/codewiki.json +1 -1
  8. package/dist/{auth-R7KYF7N3.js → auth-PCV2XQDV.js} +6 -6
  9. package/dist/{builtins-CRFDQUVP.js → builtins-PD652MTK.js} +3 -3
  10. package/dist/{chunk-SDWNDICN.js → chunk-24T2YZ7A.js} +58 -56
  11. package/dist/{chunk-FSTIBAGM.js → chunk-2AKYFREM.js} +7 -7
  12. package/dist/{chunk-SE4ZKILO.js → chunk-3BJGMT2D.js} +30 -9
  13. package/dist/{chunk-GVYWZNRK.js → chunk-3HQVBIKD.js} +2 -2
  14. package/dist/{chunk-EWD5P3GK.js → chunk-3JLFK5MW.js} +4 -4
  15. package/dist/{chunk-O2RQ7LCK.js → chunk-3O2ZAUGQ.js} +2 -2
  16. package/dist/{chunk-GXCCHKN6.js → chunk-3QKXHCGS.js} +4 -4
  17. package/dist/{chunk-YEJIG7LN.js → chunk-3WBALRQH.js} +7 -7
  18. package/dist/{chunk-I7VLWRK2.js → chunk-4DU2TVI4.js} +5 -5
  19. package/dist/{chunk-TEG3RYVZ.js → chunk-4U4TKZFR.js} +3 -3
  20. package/dist/{chunk-EHCPSZJF.js → chunk-4VMV6OS6.js} +2 -2
  21. package/dist/{chunk-T7FDXNRU.js → chunk-536HXRFE.js} +4 -4
  22. package/dist/{chunk-ZMZQEBKI.js → chunk-5QRX7SLS.js} +8 -8
  23. package/dist/{chunk-RXIOCVBZ.js → chunk-6HAO6X5B.js} +4 -4
  24. package/dist/{chunk-HGSLRG33.js → chunk-6OII3LOK.js} +2 -2
  25. package/dist/{chunk-U2JOLB7O.js → chunk-6RYDAVQT.js} +32 -32
  26. package/dist/{chunk-T53XI6I7.js → chunk-6VRRJT7L.js} +2 -2
  27. package/dist/{chunk-RLTT5GVY.js → chunk-7JSSKXG2.js} +4 -4
  28. package/dist/{chunk-633EXGZA.js → chunk-ACCKVSHU.js} +3 -3
  29. package/dist/{chunk-6SYEGZI3.js → chunk-B3MOMLJ4.js} +2 -2
  30. package/dist/{chunk-UXAC2FX4.js → chunk-BLTEAUCB.js} +3 -3
  31. package/dist/{chunk-DRD2A54W.js → chunk-BT7HGDOX.js} +15 -15
  32. package/dist/{chunk-F34AR6O3.js → chunk-DXLUVUD4.js} +5 -5
  33. package/dist/{chunk-HR3HPQ76.js → chunk-E7RF4KBO.js} +2 -2
  34. package/dist/chunk-EM2WNQS2.js +93 -0
  35. package/dist/{chunk-KYKNBD4Y.js → chunk-F5Z7EWMN.js} +953 -233
  36. package/dist/{chunk-VLPTKCKT.js → chunk-GLYBJ4RQ.js} +3 -3
  37. package/dist/{chunk-KDWLXVMN.js → chunk-HPTJF6DG.js} +16 -39
  38. package/dist/{chunk-6CDNRHWH.js → chunk-HZ2T2YED.js} +2 -2
  39. package/dist/{chunk-UTUNT5QL.js → chunk-HZFBVU5X.js} +7 -7
  40. package/dist/{chunk-KF6GFW6O.js → chunk-I2K2NCZQ.js} +7 -7
  41. package/dist/{chunk-5DDOTTG4.js → chunk-IGLVVNEG.js} +2 -2
  42. package/dist/{chunk-4SVIVOMC.js → chunk-IKRWLEDA.js} +5 -5
  43. package/dist/{chunk-R6MAYTMS.js → chunk-JMPL7NUB.js} +3 -3
  44. package/dist/{chunk-5ZMXGW7P.js → chunk-KAFMW7WM.js} +4 -85
  45. package/dist/{chunk-R6MICQYU.js → chunk-LIN2K3EL.js} +4 -4
  46. package/dist/{chunk-QEQISF75.js → chunk-LK2FN5JY.js} +1 -1
  47. package/dist/{chunk-YYB3MC65.js → chunk-N2OKBREN.js} +2 -2
  48. package/dist/{chunk-XXBQKW32.js → chunk-NNWJYUNO.js} +4 -4
  49. package/dist/{chunk-TZ7MFRG4.js → chunk-P5JMBPDJ.js} +22 -20
  50. package/dist/{chunk-WFKDPU7U.js → chunk-P6M7RKMI.js} +2 -2
  51. package/dist/{chunk-MDU3C27C.js → chunk-PZN2LQY6.js} +5 -5
  52. package/dist/{chunk-S32UQ5GL.js → chunk-Q7NNUHQV.js} +15 -15
  53. package/dist/{chunk-U7VQ4NI4.js → chunk-QCOV5DFY.js} +5 -5
  54. package/dist/{chunk-MC3JSVR6.js → chunk-QJMRYTCX.js} +7 -7
  55. package/dist/{chunk-DIMRLNIC.js → chunk-QNWRHOMF.js} +3 -3
  56. package/dist/{chunk-ILJ7DWGE.js → chunk-QXFBABFJ.js} +4 -4
  57. package/dist/chunk-R2CT5YDO.js +66 -0
  58. package/dist/{chunk-E3BQTQUP.js → chunk-RGZNQ5CT.js} +3 -3
  59. package/dist/{chunk-SBZX6ARC.js → chunk-SXQKNKRA.js} +7 -7
  60. package/dist/{chunk-WLMSOT73.js → chunk-TJFDL26W.js} +3 -3
  61. package/dist/{chunk-EQEJJ53P.js → chunk-VAHLPXPY.js} +2 -2
  62. package/dist/{chunk-7TKWWPD7.js → chunk-VANN3GDP.js} +3 -23
  63. package/dist/{chunk-GNQRWTMY.js → chunk-VHMX7SXF.js} +2 -2
  64. package/dist/{chunk-UL36QJX6.js → chunk-WKUYQBCG.js} +6 -6
  65. package/dist/{chunk-FKIOX6DS.js → chunk-WNGF5AJX.js} +9 -4
  66. package/dist/{chunk-ICO4TQ2F.js → chunk-WV6TDAPN.js} +36 -20
  67. package/dist/{chunk-K6RDB7IA.js → chunk-X3BI7HTV.js} +9 -9
  68. package/dist/{chunk-NY6IWDVU.js → chunk-XWMSPJYN.js} +2 -2
  69. package/dist/{chunk-7HBHHVCK.js → chunk-YPRSNUTC.js} +2 -2
  70. package/dist/{chunk-ONRTXOZY.js → chunk-ZS43Y2FT.js} +2 -2
  71. package/dist/cli/index.js +27 -27
  72. package/dist/{clio-5I2IBFYL.js → clio-QQCMTUZE.js} +6 -6
  73. package/dist/{code-nav-YXIAEZVS.js → code-nav-GUPM6766.js} +8 -8
  74. package/dist/{config-Y2JDJ3JJ.js → config-IIM7YOD3.js} +42 -41
  75. package/dist/{configure-SML6UBZ5.js → configure-4ZM5HWJX.js} +28 -10
  76. package/dist/{context-S2EEC3EJ.js → context-44BZLJ3J.js} +12 -12
  77. package/dist/{context-P5YSEO55.js → context-AIDVQKMW.js} +34 -33
  78. package/dist/{context-QQ53HSGJ.js → context-OL5P3HEP.js} +25 -24
  79. package/dist/{context-clear-TMEQCOTH.js → context-clear-OQV76MYP.js} +34 -33
  80. package/dist/{context-working-set-CNLSQN5K.js → context-working-set-ZUW3EGLS.js} +10 -9
  81. package/dist/{detail-5572VLBO.js → detail-UXPWRANG.js} +35 -34
  82. package/dist/{dispatch-runner-FRHD6P2O.js → dispatch-runner-P3ZW6G77.js} +38 -37
  83. package/dist/{doctor-N3LCM2LG.js → doctor-JJVGPZWT.js} +18 -17
  84. package/dist/{eval-BUU3ZOUZ.js → eval-V2RTN3UH.js} +26 -25
  85. package/dist/{evidence-DTGEN456.js → evidence-35PYWGSO.js} +34 -33
  86. package/dist/{evidence-SQ5DNFEN.js → evidence-CIGGKI6J.js} +36 -35
  87. package/dist/{evolve-IJNUFZDS.js → evolve-33SM3NJP.js} +34 -33
  88. package/dist/{fleet-ZNNVSYFO.js → fleet-LFO7SIPC.js} +62 -61
  89. package/dist/{fleet-commands-5YE7NPJL.js → fleet-commands-RLK72GU3.js} +9 -9
  90. package/dist/fleet-decisions-3SBSCTCW.js +1 -1
  91. package/dist/{fleet-graph-6HHPWF5T.js → fleet-graph-JZIBCGMW.js} +11 -11
  92. package/dist/{fleet-inspect-JEYMGCGC.js → fleet-inspect-2L5YGGZJ.js} +35 -34
  93. package/dist/{fleet-validate-4PM4GDDL.js → fleet-validate-LIAVKDUU.js} +12 -12
  94. package/dist/{fleet-verify-NJDTHTO3.js → fleet-verify-JIK3OQZR.js} +34 -33
  95. package/dist/{fleet-view-UP2BWQQY.js → fleet-view-IWHYGQKK.js} +35 -34
  96. package/dist/{init-S3GZQ5ZF.js → init-NJEDOTQW.js} +46 -45
  97. package/dist/{interop-YOTGXFJM.js → interop-RVCAYECG.js} +4 -4
  98. package/dist/{inventory-K2OZ36YJ.js → inventory-74UL7UHU.js} +35 -34
  99. package/dist/{library-GJI6W6OK.js → library-CTKZA7K3.js} +10 -10
  100. package/dist/{memory-OQOXQGYD.js → memory-I76I5VUH.js} +34 -33
  101. package/dist/{models-3IK2Y5WE.js → models-D4YDIQZQ.js} +19 -18
  102. package/dist/{monitor-FTJLTKCQ.js → monitor-NKVD55HX.js} +40 -39
  103. package/dist/{orchestrator-N47O7SFD.js → orchestrator-I6OATVPK.js} +658 -1092
  104. package/dist/{preload-65U34NG6.js → preload-EJAWJEIO.js} +34 -33
  105. package/dist/{reset-MUVQDLNL.js → reset-4XMQKDNZ.js} +5 -3
  106. package/dist/{resources-LM76COAO.js → resources-TVYVXK3Q.js} +10 -10
  107. package/dist/{run-3VFZQ33N.js → run-OWQNNSJU.js} +61 -60
  108. package/dist/{share-4JUB4BL7.js → share-NPZZMUXG.js} +11 -11
  109. package/dist/{skills-S36AAJHU.js → skills-OXFEAWMT.js} +12 -12
  110. package/dist/{skills-eval-G6T57L7M.js → skills-eval-J5TCKSJA.js} +38 -37
  111. package/dist/{skills-inventory-WLM46PJH.js → skills-inventory-7II4DM57.js} +10 -10
  112. package/dist/{slash-commands-GBARG3Y7.js → slash-commands-MDSDSWYF.js} +26 -25
  113. package/dist/{targets-N5REUETU.js → targets-KILZEOCE.js} +36 -20
  114. package/dist/{tasks-6BVY5YZF.js → tasks-R6P6OV22.js} +7 -7
  115. package/dist/{terminal-lease-57UEOJKS.js → terminal-lease-LZCG2KN3.js} +2 -2
  116. package/dist/{upgrade-4HD5HAIV.js → upgrade-D2OZH6IQ.js} +9 -9
  117. package/dist/{usage-4GWM2WWP.js → usage-PIASNUS4.js} +40 -39
  118. package/dist/{verifiers-GSKWKWVG.js → verifiers-LTHUY2PX.js} +9 -9
  119. package/dist/{verify-3AS3ULCY.js → verify-HYF7KBVE.js} +7 -7
  120. package/dist/{wiki-generate-7BV7XWIY.js → wiki-generate-RK7JQ5NP.js} +51 -50
  121. package/dist/worker/entry.js +41 -39
  122. package/docs/architecture/context-engine.md +1 -1
  123. package/docs/architecture/tui-design.md +45 -30
  124. package/docs/guide/commands-and-modes.md +26 -25
  125. package/docs/guide/configuration-and-targets.md +122 -27
  126. package/docs/guide/configuration-reference.md +9 -4
  127. package/docs/guide/fleet-dispatch.md +1 -6
  128. package/docs/guide/glossary.md +1 -1
  129. package/docs/guide/installation-and-lifecycle.md +44 -11
  130. package/docs/process/development-pipeline.md +1 -1
  131. package/docs/process/documentation-coverage.md +1 -1
  132. package/docs/process/release-cut-checklist.md +18 -6
  133. package/package.json +1 -1
  134. package/src/cli/ask.ts +10 -10
  135. package/src/cli/configure-editor.ts +51 -0
  136. package/src/cli/configure-interop.ts +1 -1
  137. package/src/cli/configure-oauth.ts +1 -1
  138. package/src/cli/configure-onboarding.ts +232 -96
  139. package/src/cli/configure-prompts.ts +76 -0
  140. package/src/cli/configure-quick.ts +274 -0
  141. package/src/cli/configure-target.ts +14 -2
  142. package/src/cli/configure.ts +413 -174
  143. package/src/cli/oauth-manual-input.ts +1 -1
  144. package/src/cli/reset.ts +2 -0
  145. package/src/cli/select.ts +35 -4
  146. package/src/cli/upgrade.ts +2 -2
  147. package/src/core/config.ts +6 -1
  148. package/src/core/defaults.ts +33 -11
  149. package/src/{interactive → core}/external-editor.ts +4 -4
  150. package/src/domains/config/classify.ts +1 -1
  151. package/src/domains/config/keybindings.ts +3 -31
  152. package/src/domains/providers/probe/fingerprint.ts +13 -6
  153. package/src/domains/providers/probe/reasoning.ts +2 -0
  154. package/src/domains/providers/runtimes/common/probe-helpers.ts +23 -7
  155. package/src/domains/providers/runtimes/protocol/anthropic-compat.ts +7 -2
  156. package/src/domains/providers/runtimes/protocol/openai-compat.ts +10 -4
  157. package/src/interactive/application-controller.ts +1 -31
  158. package/src/interactive/chat-panel.ts +163 -637
  159. package/src/interactive/chat-renderer.ts +13 -30
  160. package/src/interactive/dispatch-board.ts +1 -3
  161. package/src/interactive/editor-submit.ts +8 -31
  162. package/src/interactive/footer/dashboard.ts +8 -3
  163. package/src/interactive/footer/widgets.ts +50 -91
  164. package/src/interactive/interactive-application.ts +16 -42
  165. package/src/interactive/interactive-event-projection.ts +2 -44
  166. package/src/interactive/interactive-input-runtime.ts +3 -28
  167. package/src/interactive/interactive-presentation.ts +30 -9
  168. package/src/interactive/interactive-slash-runtime.ts +3 -40
  169. package/src/interactive/layout.ts +29 -4
  170. package/src/interactive/overlay-general-openers.ts +2 -0
  171. package/src/interactive/overlay-lifecycle.ts +1 -0
  172. package/src/interactive/overlay-session-lifecycle.ts +1 -1
  173. package/src/interactive/overlays/settings.ts +9 -8
  174. package/src/interactive/renderers/preview.ts +16 -0
  175. package/src/interactive/renderers/tool-execution.ts +67 -160
  176. package/src/interactive/renderers/worker-entry.ts +57 -41
  177. package/src/interactive/slash-autocomplete.ts +1 -6
  178. package/src/interactive/slash-commands.ts +7 -39
  179. package/src/interactive/status/controller.ts +0 -5
  180. package/src/interactive/status/state-machine.ts +44 -26
  181. package/src/interactive/status/types.ts +3 -0
  182. package/src/interactive/status/verbs.ts +13 -12
  183. package/src/interactive/transcript-detail.ts +50 -112
  184. package/src/interactive/view/artifacts.ts +4 -0
  185. package/src/interactive/view/view-overlay.ts +44 -34
  186. package/src/tools/presentation.ts +1 -1
  187. package/src/tools/registry.ts +1 -1
  188. package/src/tools/verify/numeric.ts +4 -4
@@ -14,7 +14,6 @@
14
14
  */
15
15
 
16
16
  import { existsSync } from "node:fs";
17
- import { ToolNames } from "../core/tool-names.js";
18
17
  import { foldWorkingSet } from "../domains/context/working-set/fold.js";
19
18
  import { compactionCut } from "../domains/context/working-set/visible.js";
20
19
  import { captureSkillContext } from "../domains/session/compaction/compact.js";
@@ -52,7 +51,6 @@ import {
52
51
  } from "../engine/messages.js";
53
52
  import { wrapTextWithAnsi } from "../engine/tui.js";
54
53
  import type { AgentMessage } from "../engine/types.js";
55
- import { toolPresentationPolicy } from "../tools/presentation.js";
56
54
  import { toolResultPresentationText } from "../tools/result-disposition.js";
57
55
  import type { ChatLoopEvent, RetryStatusPayload } from "./chat-loop.js";
58
56
  import { isSelfExplainingAbort, toolResultSummary } from "./chat-loop-messages.js";
@@ -69,7 +67,7 @@ import {
69
67
  type StreamPacer,
70
68
  type StreamPacerSlice,
71
69
  } from "./stream-pacer.js";
72
- import { type FoldOverride, policyToolFold, resolveFold, type TranscriptDetailPolicy } from "./transcript-detail.js";
70
+ import type { TranscriptDetailPolicy } from "./transcript-detail.js";
73
71
  import { readWorkerReceiptFactsForReplay } from "./worker-receipts.js";
74
72
  import { workerEntriesFromRunEntries } from "./worker-replay.js";
75
73
  import type { WorkerReceiptReader } from "./worker-stream.js";
@@ -804,25 +802,24 @@ function appendReplayLine(chatPanel: ChatPanel, text: string): void {
804
802
  function renderBashExecutionEntry(
805
803
  entry: BashExecutionEntry,
806
804
  width: number,
807
- folded: boolean,
805
+ detail: TranscriptDetailPolicy,
808
806
  unbounded: boolean,
809
807
  ): string[] {
810
808
  const normalizedOutput = entry.output.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\s+$/g, "");
811
809
  return renderBashTranscriptExecution(
812
810
  {
813
811
  command: entry.command,
814
- output: unbounded ? normalizedOutput : truncateReplayText(normalizedOutput),
812
+ output: normalizedOutput,
815
813
  running: false,
816
814
  exitCode: entry.exitCode,
817
815
  cancelled: entry.cancelled,
818
816
  truncated: entry.truncated,
819
817
  fullOutputPath: entry.fullOutputPath,
820
818
  excludeFromContext: entry.excludeFromContext,
821
- folded,
822
819
  },
823
820
  width,
824
821
  undefined,
825
- { unbounded, diffStyle: "plain" },
822
+ { unbounded, diffStyle: "plain", detail },
826
823
  );
827
824
  }
828
825
 
@@ -1259,16 +1256,16 @@ export function rehydrateChatPanelFromTurns(
1259
1256
  case "message": {
1260
1257
  if (entry.role === "user") {
1261
1258
  runAssistantMessages = [];
1262
- const text = truncateReplayText(replayedUserText(entry));
1259
+ const text = replayedUserText(entry);
1263
1260
  if (text.length > 0) chatPanel.appendUser(text);
1264
1261
  break;
1265
1262
  }
1266
1263
  if (entry.role === "assistant") {
1267
1264
  const text = chatMessageText(entry);
1268
1265
  const failure = messageFailure(entry);
1269
- const richMessage = richMessageFromEntry(entry, MAX_REPLAY_TEXT_CHARS);
1266
+ const richMessage = richMessageFromEntry(entry, Number.POSITIVE_INFINITY);
1270
1267
  if (richMessage || text.length > 0 || failure) {
1271
- const message = richMessage ?? makeTextMessage("assistant", truncateReplayText(text), entry.timestamp);
1268
+ const message = richMessage ?? makeTextMessage("assistant", text, entry.timestamp);
1272
1269
  if (failure) {
1273
1270
  (message as { stopReason?: string; errorMessage?: string }).stopReason = failure.stopReason;
1274
1271
  (message as { stopReason?: string; errorMessage?: string }).errorMessage = failure.errorMessage;
@@ -1303,7 +1300,7 @@ export function rehydrateChatPanelFromTurns(
1303
1300
  type: "tool_execution_end",
1304
1301
  toolCallId: fallbackId,
1305
1302
  toolName: result.name,
1306
- result: displayReplayToolResult(result.result, options.unboundedToolBodies === true),
1303
+ result: displayReplayToolResult(result.result, true),
1307
1304
  isError: result.isError,
1308
1305
  ...(result.durationMs !== undefined ? { durationMs: result.durationMs } : {}),
1309
1306
  ...(resultSummary !== undefined ? { resultSummary } : {}),
@@ -1312,12 +1309,12 @@ export function rehydrateChatPanelFromTurns(
1312
1309
  ...(evictedReason !== undefined ? { evictedReason } : {}),
1313
1310
  } as ChatLoopEvent);
1314
1311
  } else {
1315
- chatPanel.appendReplayBlock((width) =>
1312
+ chatPanel.appendReplayBlock((width, detail, unbounded) =>
1316
1313
  renderToolResultOnly(
1317
1314
  {
1318
1315
  toolCallId: result.id ?? "",
1319
1316
  toolName: result.name,
1320
- result: displayReplayToolResult(result.result, options.unboundedToolBodies === true),
1317
+ result: displayReplayToolResult(result.result, true),
1321
1318
  isError: result.isError,
1322
1319
  ...(result.durationMs !== undefined ? { durationMs: result.durationMs } : {}),
1323
1320
  ...(resultSummary !== undefined ? { resultSummary } : {}),
@@ -1326,7 +1323,7 @@ export function rehydrateChatPanelFromTurns(
1326
1323
  ...(evictedReason !== undefined ? { evictedReason } : {}),
1327
1324
  },
1328
1325
  width,
1329
- { unbounded: options.unboundedToolBodies === true },
1326
+ { unbounded: unbounded || options.unboundedToolBodies === true, detail },
1330
1327
  ),
1331
1328
  );
1332
1329
  }
@@ -1345,21 +1342,8 @@ export function rehydrateChatPanelFromTurns(
1345
1342
  break;
1346
1343
  }
1347
1344
  case "bashExecution": {
1348
- let fold: FoldOverride;
1349
- const presentation = toolPresentationPolicy(ToolNames.Bash, undefined);
1350
- const policyFold = (detail: TranscriptDetailPolicy) => policyToolFold(detail, presentation);
1351
- const unbounded = options.unboundedToolBodies === true;
1352
- chatPanel.appendReplayBlock(
1353
- (width, detail) =>
1354
- renderBashExecutionEntry(entry, width, resolveFold(fold, policyFold(detail)) === "folded", unbounded),
1355
- undefined,
1356
- {
1357
- policyFold,
1358
- fold: () => fold,
1359
- setFold: (next: FoldOverride) => {
1360
- fold = next;
1361
- },
1362
- },
1345
+ chatPanel.appendReplayBlock((width, detail, unbounded) =>
1346
+ renderBashExecutionEntry(entry, width, detail, unbounded || options.unboundedToolBodies === true),
1363
1347
  );
1364
1348
  break;
1365
1349
  }
@@ -1427,5 +1411,4 @@ export function rehydrateChatPanelFromTurns(
1427
1411
  // whatever the operator had opened or folded before the switch belonged to
1428
1412
  // the transcript they left. /export reaches the same policy with a verbose
1429
1413
  // panel, so nothing here is terminal-only.
1430
- chatPanel.clearFoldOverrides();
1431
1414
  }
@@ -246,9 +246,7 @@ export interface DispatchOriginPresentation {
246
246
  * surface; a board row sits under the fleet summary, which already owns the
247
247
  * quadrant's single orange, so here the filled glyph carries origin by shape.
248
248
  */
249
- export function dispatchOriginPresentation(
250
- row: Pick<DispatchBoardRow, "requestOrigin">,
251
- ): DispatchOriginPresentation | null {
249
+ function dispatchOriginPresentation(row: Pick<DispatchBoardRow, "requestOrigin">): DispatchOriginPresentation | null {
252
250
  if (row.requestOrigin === "user") return { glyph: GLYPH.workerHuman, token: "accent" };
253
251
  if (row.requestOrigin === "agent") return { glyph: GLYPH.workerAgent, token: "muted" };
254
252
  if (row.requestOrigin === "internal") return { glyph: GLYPH.workerInternal, token: "dim" };
@@ -1,9 +1,8 @@
1
1
  import { combineBashOutput, runBashCommand } from "../core/bash-exec.js";
2
+ import { type ExternalEditResult, editTextExternally, resolveExternalEditor } from "../core/external-editor.js";
2
3
  import type { PendingSkillRequest } from "../core/skill-activation.js";
3
- import { ToolNames } from "../core/tool-names.js";
4
4
  import type { DispatchContract } from "../domains/dispatch/contract.js";
5
5
  import type { SessionContract, SessionEntry } from "../domains/session/index.js";
6
- import { toolPresentationPolicy } from "../tools/presentation.js";
7
6
  import type { ChatLoop } from "./chat-loop.js";
8
7
  import type { ChatPanel } from "./chat-panel.js";
9
8
  import { bashExecutionEntryInput, parseEditorBashCommand, unguardPastedEditorOperator } from "./editor-bash.js";
@@ -13,16 +12,8 @@ import {
13
12
  type RunningDispatchRef,
14
13
  resolveSteerTarget,
15
14
  } from "./editor-steer.js";
16
- import { type ExternalEditResult, editTextExternally, resolveExternalEditor } from "./external-editor.js";
17
15
  import { type BashTranscriptExecution, renderBashTranscriptExecution } from "./renderers/tool-execution.js";
18
16
  import { parseSlashCommand, type RunIo, type SlashCommand, type SlashCommandDispatchResult } from "./slash-commands.js";
19
- import {
20
- type FoldOverride,
21
- policyRunningToolFold,
22
- policyToolFold,
23
- resolveFold,
24
- type TranscriptDetailPolicy,
25
- } from "./transcript-detail.js";
26
17
 
27
18
  const EDITOR_BASH_TIMEOUT_MS = 300_000;
28
19
  /** Existing bash TERM-to-KILL grace (5s), plus settlement and session append. */
@@ -86,6 +77,7 @@ export interface EditorSubmitSessionTranscript {
86
77
  }
87
78
 
88
79
  export interface EditorSubmitDeps {
80
+ onLocalBashRunning?: (running: boolean) => void;
89
81
  editor: EditorSubmitEditor;
90
82
  ui: EditorSubmitUi;
91
83
  io: RunIo;
@@ -171,35 +163,19 @@ export function createEditorSubmitController(deps: EditorSubmitDeps): EditorSubm
171
163
  totalBytes: 0,
172
164
  excludeFromContext: parsed.excludeFromContext,
173
165
  };
174
- // The operator's bash row follows the same rule as a model bash call: the
175
- // transcript detail policy (through bash's presentation once settled)
176
- // unless the operator overrode this one block with the expand key.
177
- let fold: FoldOverride;
178
- const bashPresentation = toolPresentationPolicy(ToolNames.Bash, undefined);
179
- const policyFold = (detail: TranscriptDetailPolicy) =>
180
- execution.running ? policyRunningToolFold(detail) : policyToolFold(detail, bashPresentation);
181
166
  deps.chatPanel.appendReplayBlock(
182
- (width, detail) =>
167
+ (width, detail, unbounded) =>
183
168
  renderBashTranscriptExecution(
184
- {
185
- ...execution,
186
- folded: resolveFold(fold, policyFold(detail)) === "folded",
187
- ...(execution.running ? { elapsedMs: Math.max(0, performance.now() - startedAt) } : {}),
188
- },
169
+ { ...execution, ...(execution.running ? { elapsedMs: Math.max(0, performance.now() - startedAt) } : {}) },
189
170
  width,
171
+ undefined,
172
+ { detail, ...(unbounded ? { unbounded } : {}) },
190
173
  ),
191
174
  () => execution.running,
192
- {
193
- policyFold,
194
- fold: () => fold,
195
- setFold: (next: FoldOverride) => {
196
- fold = next;
197
- deps.ui.requestRender();
198
- },
199
- },
200
175
  );
201
176
  deps.ui.requestRender();
202
177
 
178
+ deps.onLocalBashRunning?.(true);
203
179
  let settlement!: Promise<void>;
204
180
  settlement = (async () => {
205
181
  try {
@@ -249,6 +225,7 @@ export function createEditorSubmitController(deps: EditorSubmitDeps): EditorSubm
249
225
  execution.error = msg;
250
226
  deps.io.stderr(`[bash] ${msg}\n`);
251
227
  } finally {
228
+ deps.onLocalBashRunning?.(false);
252
229
  if (activeEditorBash === abort) activeEditorBash = null;
253
230
  if (activeEditorBashSettlement === settlement) activeEditorBashSettlement = null;
254
231
  deps.ui.requestRender();
@@ -139,7 +139,12 @@ function statusText(status: AgentStatus | undefined, now: number, width: number,
139
139
  if (!status || status.phase === "idle") return null;
140
140
  const verb = resolveFooterVerb(status, now, width);
141
141
  if (!verb) return status.phase.replace(/_/g, " ");
142
- return status.phase === "ended" ? verb.text : `${spinnerFrame(frame)} ${verb.text}`;
142
+ return status.phase === "ended" ||
143
+ status.phase === "tool_blocked" ||
144
+ status.phase === "stuck" ||
145
+ status.tool?.toolName === "ask_user"
146
+ ? verb.text
147
+ : `${spinnerFrame(frame)} ${verb.text}`;
143
148
  }
144
149
 
145
150
  /** Null before anything has been priced, so the footer shows no cost field at all. */
@@ -172,7 +177,7 @@ function renderFooterCompactLines(state: FooterDashboardRenderState, width: numb
172
177
  state.throughput,
173
178
  state.sessionTokens,
174
179
  state.sessionCost,
175
- state.session.outputVerbosity,
180
+ state.session.outputStyle,
176
181
  state.session.leaderArmed ?? false,
177
182
  state.session.shutdownArmed ?? false,
178
183
  ),
@@ -434,7 +439,7 @@ export function buildFooterDashboard(deps: FooterDashboardDeps): FooterDashboard
434
439
  capabilities,
435
440
  safety,
436
441
  toolProfile,
437
- outputVerbosity: settings?.interface.outputDetail ?? "default",
442
+ outputStyle: settings?.interface.outputDetail ?? "standard",
438
443
  leaderArmed: deps.getLeaderArmed?.() ?? false,
439
444
  shutdownArmed: deps.getShutdownArmed?.() ?? false,
440
445
  memoryIntervention: taskMemory
@@ -1,4 +1,4 @@
1
- import type { OutputVerbosity } from "../../core/defaults.js";
1
+ import type { OutputStyle } from "../../core/defaults.js";
2
2
  import { ToolNames } from "../../core/tool-names.js";
3
3
  import {
4
4
  type CostAggregate,
@@ -14,7 +14,6 @@ import { CONTEXT_CATEGORY_TOKEN, contextCategorySwatch, renderContextMeterBar }
14
14
  import {
15
15
  agentDisplayLabel,
16
16
  type DispatchBoardRow,
17
- dispatchOriginPresentation,
18
17
  dispatchRowPrefix,
19
18
  dispatchStatusPresentation,
20
19
  } from "../dispatch-board.js";
@@ -70,7 +69,7 @@ export interface SessionFacts {
70
69
  safety: string | null;
71
70
  toolProfile: string | null;
72
71
  /** Active transcript detail mode, shown in the dashboard so visibility is never implicit. */
73
- outputVerbosity?: OutputVerbosity | null;
72
+ outputStyle?: OutputStyle | null;
74
73
  /**
75
74
  * Ctrl+G armed the portable leader and is waiting for the next key. Shown
76
75
  * because the frame between the two keystrokes was otherwise identical to
@@ -391,7 +390,7 @@ export function compactSecondaryLine(
391
390
  throughput: TokenThroughputSnapshot | null = null,
392
391
  sessionTokens: UsageBreakdown | null = null,
393
392
  sessionCost: CostAggregate | null = null,
394
- outputVerbosity?: OutputVerbosity | null,
393
+ outputStyle?: OutputStyle | null,
395
394
  leaderArmed = false,
396
395
  shutdownArmed = false,
397
396
  ): string {
@@ -427,15 +426,15 @@ export function compactSecondaryLine(
427
426
  context.used ?? undefined,
428
427
  maxRightWidth,
429
428
  compactMetricChipLimit(safeWidth),
430
- outputVerbosity,
429
+ outputStyle,
431
430
  leaderArmed,
432
431
  shutdownArmed,
433
432
  );
434
433
  // At the smallest widths the context meter consumes the entire secondary
435
434
  // row, so keep a compact mode marker on the left instead of silently hiding
436
435
  // the active transcript setting.
437
- if (outputVerbosity && outputVerbosity !== "default" && visibleWidth(right) === 0) {
438
- const marker = outputVerbosity === "minimal" ? "m" : outputVerbosity === "verbose" ? "v" : "d";
436
+ if (outputStyle && outputStyle !== "standard" && visibleWidth(right) === 0) {
437
+ const marker = outputStyle === "compact" ? "C" : outputStyle === "detailed" ? "D" : "S";
439
438
  left = `${left}${theme.fg("dim", ` out:${marker}`)}`;
440
439
  }
441
440
  return joinColumns(left, right, safeWidth);
@@ -541,8 +540,8 @@ export function sessionQuadrant(facts: SessionFacts, options: ExpandedQuadrantOp
541
540
  kv("profile", facts.toolProfile),
542
541
  kv(
543
542
  "output",
544
- facts.outputVerbosity && facts.outputVerbosity !== "default" ? facts.outputVerbosity : null,
545
- facts.outputVerbosity === "verbose" ? "accent" : "muted",
543
+ facts.outputStyle && facts.outputStyle !== "standard" ? facts.outputStyle : null,
544
+ facts.outputStyle === "detailed" ? "accent" : "muted",
546
545
  ),
547
546
  styledKv("memory", memoryValue),
548
547
  ]);
@@ -965,35 +964,41 @@ type HarnessPhasePresentation = {
965
964
 
966
965
  function shortToolLabel(status: AgentStatus, width: number): string {
967
966
  const name = status.tool?.toolName?.trim();
968
- if (name === ToolNames.AskUser) return width < 72 ? "ask" : "waiting for user";
969
- if (!name || width < 72) return "tool";
967
+ if (name === ToolNames.AskUser) return "Needs input";
968
+ if (name === ToolNames.Dispatch) return "Waiting for worker";
969
+ if (!name || width < 72) return "Running tool";
970
970
  const nameWidth = width >= 100 ? 18 : 12;
971
971
  // The pill label is never padded; truncate without pad so the tool name is
972
972
  // followed by a single space before the badge, not a column of blanks.
973
- return `tool ${truncateToWidth(name, nameWidth, "…", false)}`;
973
+ return `Running ${truncateToWidth(name, nameWidth, "…", false)}`;
974
974
  }
975
975
 
976
976
  function harnessPhasePresentation(status: AgentStatus, width: number, now: number): HarnessPhasePresentation {
977
977
  const ultraNarrow = width < 48;
978
978
  switch (status.phase) {
979
979
  case "idle":
980
- return { glyph: GLYPH.queued, label: "idle", token: "muted", live: false };
980
+ return { glyph: GLYPH.queued, label: "Ready", token: "muted", live: false };
981
981
  case "preparing":
982
- return { glyph: GLYPH.phaseWaiting, label: "prep", token: "info", live: true };
982
+ return { glyph: GLYPH.phaseWaiting, label: "Preparing", token: "info", live: true };
983
983
  case "waiting_model":
984
- return { glyph: GLYPH.phaseWaiting, label: "waiting", token: "info", live: true };
984
+ return { glyph: GLYPH.phaseWaiting, label: "Waiting for model", token: "info", live: true };
985
985
  case "thinking":
986
- return { glyph: GLYPH.phaseThinking, label: "thinking", token: "reason", live: true };
986
+ return { glyph: GLYPH.phaseThinking, label: "Thinking", token: "reason", live: true };
987
987
  case "writing":
988
- return { glyph: GLYPH.phaseWriting, label: "writing", token: "accent", live: true };
988
+ return { glyph: GLYPH.phaseWriting, label: "Writing", token: "accent", live: true };
989
989
  case "tool_running":
990
- return { glyph: GLYPH.phaseTool, label: shortToolLabel(status, width), token: "accent", live: true };
990
+ return {
991
+ glyph: GLYPH.phaseTool,
992
+ label: shortToolLabel(status, width),
993
+ token: "accent",
994
+ live: status.tool?.toolName !== ToolNames.AskUser,
995
+ };
991
996
  case "tool_blocked":
992
997
  // Attention states hold a static glyph rather than spinning: the work
993
998
  // has paused for a human, so the pill should not read as live progress.
994
999
  // The phase fires only on PermissionRequested, so the pill names the
995
1000
  // wait for confirmation; "blocked" would contradict the ask overlay.
996
- return { glyph: GLYPH.phaseBlocked, label: "confirm", token: "warning", live: false };
1001
+ return { glyph: GLYPH.phaseBlocked, label: "Needs approval", token: "warning", live: false };
997
1002
  case "retrying": {
998
1003
  const attempt = status.retry?.attempt ?? 0;
999
1004
  const maxAttempts = status.retry?.maxAttempts ?? 0;
@@ -1005,15 +1010,26 @@ function harnessPhasePresentation(status: AgentStatus, width: number, now: numbe
1005
1010
  };
1006
1011
  }
1007
1012
  case "compacting":
1008
- return { glyph: GLYPH.phaseCompact, label: "compacting", token: "reason", live: true };
1013
+ return { glyph: GLYPH.phaseCompact, label: "Compacting context", token: "reason", live: true };
1009
1014
  case "dispatching":
1010
- return { glyph: GLYPH.phaseDispatch, label: "dispatch", token: "action", live: true };
1015
+ return { glyph: GLYPH.phaseDispatch, label: "Waiting for worker", token: "action", live: true };
1011
1016
  case "stuck": {
1012
- const seconds = Math.max(0, Math.floor((now - status.since) / 1000));
1013
- return { glyph: GLYPH.warn, label: ultraNarrow ? "stuck" : `stuck ${seconds}s`, token: "error", live: false };
1017
+ const seconds = Math.max(0, Math.floor((now - status.lastMeaningfulAt) / 1000));
1018
+ return {
1019
+ glyph: GLYPH.warn,
1020
+ label: ultraNarrow ? "No output" : `No output · ${seconds}s`,
1021
+ token: "warning",
1022
+ live: false,
1023
+ };
1024
+ }
1025
+ case "ended": {
1026
+ const stop = status.summary?.stopReason;
1027
+ if (stop === "error") return { glyph: GLYPH.error, label: "Failed", token: "error", live: false };
1028
+ if (stop === "aborted" || stop === "cancelled")
1029
+ return { glyph: GLYPH.cancelled, label: "Cancelled", token: "muted", live: false };
1030
+ if (stop === "length") return { glyph: GLYPH.warn, label: "Output limit", token: "warning", live: false };
1031
+ return { glyph: GLYPH.ok, label: "Ready", token: "success", live: false };
1014
1032
  }
1015
- case "ended":
1016
- return { glyph: GLYPH.ok, label: "done", token: "success", live: false };
1017
1033
  }
1018
1034
  }
1019
1035
 
@@ -1021,31 +1037,10 @@ function activeWorkerRows(rows: ReadonlyArray<DispatchBoardRow>): ReadonlyArray<
1021
1037
  return rows.filter((row) => row.status === "running" || row.status === "stale" || row.status === "enqueued");
1022
1038
  }
1023
1039
 
1024
- /**
1025
- * The live worker count, split by who asked once more than one kind is running:
1026
- * `◇1 ◆3` says the operator has one run of their own alongside three the model
1027
- * started, which `fleet 4` cannot. A single kind keeps the plain count, and so
1028
- * does a set holding a run whose origin never reached the projection, because a
1029
- * split that cannot name every active run would report a total that is short.
1030
- *
1031
- * The chip keeps one color. Its token says the fleet is busy; origin is carried
1032
- * by glyph shape, which is the same pair the board rows and the transcript
1033
- * blocks use.
1034
- */
1040
+ /** Background work stays a count beside the main phase. */
1035
1041
  function activeWorkerChip(rows: ReadonlyArray<DispatchBoardRow>): string {
1036
- const active = activeWorkerRows(rows);
1037
- const counts = new Map<string, number>();
1038
- for (const row of active) {
1039
- const glyph = dispatchOriginPresentation(row)?.glyph;
1040
- if (glyph === undefined) return `fleet ${active.length}`;
1041
- counts.set(glyph, (counts.get(glyph) ?? 0) + 1);
1042
- }
1043
- if (counts.size < 2) return `fleet ${active.length}`;
1044
- const order = [GLYPH.workerHuman, GLYPH.workerAgent, GLYPH.workerInternal];
1045
- return order
1046
- .filter((glyph) => counts.has(glyph))
1047
- .map((glyph) => `${glyph}${counts.get(glyph)}`)
1048
- .join(" ");
1042
+ const count = activeWorkerRows(rows).length;
1043
+ return `${count} worker${count === 1 ? "" : "s"}`;
1049
1044
  }
1050
1045
 
1051
1046
  function harnessBadge(
@@ -1066,8 +1061,8 @@ function harnessBadge(
1066
1061
  return badgeText ? theme.fg("muted", badgeText) : "";
1067
1062
  }
1068
1063
 
1069
- function outputVerbosityLabel(verbosity: OutputVerbosity): string {
1070
- return verbosity === "minimal" ? "min" : verbosity === "verbose" ? "verbose" : "default";
1064
+ function outputStyleLabel(verbosity: OutputStyle): string {
1065
+ return verbosity.charAt(0).toUpperCase() + verbosity.slice(1);
1071
1066
  }
1072
1067
 
1073
1068
  function buildHarnessStatePill(
@@ -1112,7 +1107,6 @@ const CHIP_RANK_SHUTDOWN = -2;
1112
1107
  const CHIP_RANK_LEADER = -1;
1113
1108
  const CHIP_RANK_TOTALS = 0;
1114
1109
  const CHIP_RANK_DETAIL = 1;
1115
- const CHIP_RANK_DEFERRED = 2;
1116
1110
 
1117
1111
  interface RankedChip {
1118
1112
  text: string;
@@ -1156,20 +1150,17 @@ function buildMetricStrip(
1156
1150
  liveInputTokens: number | null | undefined,
1157
1151
  maxWidth: number,
1158
1152
  maxChipsCount = 6,
1159
- outputVerbosity?: OutputVerbosity | null,
1153
+ outputStyle?: OutputStyle | null,
1160
1154
  leaderArmed = false,
1161
1155
  shutdownArmed = false,
1162
1156
  ): string {
1163
1157
  const safeMaxWidth = Math.max(0, Math.floor(maxWidth));
1164
1158
  if (safeMaxWidth <= 0) return "";
1165
1159
  const isStreaming = status.phase !== "idle" && status.phase !== "ended";
1166
- const meaningfulVerbosity = outputVerbosity && outputVerbosity !== "default" ? outputVerbosity : null;
1167
- if (!isStreaming && !lastTurn && !meaningfulVerbosity && !leaderArmed && !shutdownArmed) return "";
1160
+ const style = outputStyle ?? "standard";
1168
1161
 
1169
1162
  const candidates: Array<string | null> = [];
1170
- /** Per-turn detail that ranks below the session totals when the strip is cut. */
1171
- const deferred: Array<string | null> = [];
1172
- if (isStreaming) {
1163
+ if (isStreaming && outputStyle === "detailed") {
1173
1164
  const tps = finiteNonNegative(throughput?.tokensPerSecond);
1174
1165
  const rounded = tps > 0 ? (tps >= 10 ? Math.round(tps) : Math.round(tps * 10) / 10) : null;
1175
1166
  candidates.push(rounded !== null ? theme.fg("success", `${GLYPH.speed}${rounded}/s`) : null);
@@ -1186,39 +1177,7 @@ function buildMetricStrip(
1186
1177
  finiteNonNegative(lastTurn?.inputTokens) ||
1187
1178
  finiteNonNegative(sessionTokens?.input);
1188
1179
  candidates.push(inputTokens > 0 ? theme.fg("muted", `${GLYPH.up}${formatFooterTokens(inputTokens)}`) : null);
1189
- } else if (lastTurn) {
1190
- const stop = stopReasonStyle(lastTurn.stopReason);
1191
- candidates.push(theme.fg(stop.token, `${stop.glyph} ${formatCompactMs(lastTurn.elapsedMs)}`));
1192
- candidates.push(
1193
- theme.fg(
1194
- "muted",
1195
- `${GLYPH.up}${formatFooterTokens(lastTurn.inputTokens)} ${GLYPH.down}${formatFooterTokens(lastTurn.outputTokens)}`,
1196
- ),
1197
- );
1198
- candidates.push(reasoningChip(theme, lastTurn));
1199
- // Held back so the session totals below outrank them. The chip list is cut
1200
- // to `maxChipsCount` before it is measured, and with cache and tools ahead
1201
- // of the totals an 80-column terminal spent the whole budget on per-turn
1202
- // detail: the cost field disappeared from the footer at the exact moment
1203
- // it acquired a value, while `/cost` on the same session read `cost
1204
- // unknown`. Two surfaces, two answers, because of a slice.
1205
- deferred.push(
1206
- lastTurn.cacheReadTokens > 0 || lastTurn.cacheWriteTokens > 0
1207
- ? theme.fg(
1208
- "dim",
1209
- `cache ${formatFooterTokens(lastTurn.cacheReadTokens)}/${formatFooterTokens(lastTurn.cacheWriteTokens)}`,
1210
- )
1211
- : null,
1212
- );
1213
- if (lastTurn.toolCount > 0) {
1214
- const label = `${lastTurn.toolCount} tool${lastTurn.toolCount === 1 ? "" : "s"}`;
1215
- const errors = lastTurn.toolErrorCount > 0 ? theme.fg("error", ` ${lastTurn.toolErrorCount}${GLYPH.error}`) : "";
1216
- deferred.push(`${theme.fg("muted", label)}${errors}`);
1217
- } else {
1218
- deferred.push(null);
1219
- }
1220
1180
  }
1221
- if (meaningfulVerbosity) deferred.push(theme.fg("muted", `out ${outputVerbosityLabel(meaningfulVerbosity)}`));
1222
1181
 
1223
1182
  const fallbackTotal = finiteNonNegative(sessionTokens?.input) + finiteNonNegative(sessionTokens?.output);
1224
1183
  const cumulativeTotal = finiteNonNegative(sessionTokens?.totalTokens) || fallbackTotal;
@@ -1232,6 +1191,7 @@ function buildMetricStrip(
1232
1191
 
1233
1192
  const chipLimit = Math.max(0, Math.floor(maxChipsCount));
1234
1193
  const chips: RankedChip[] = [];
1194
+ pushChip(chips, theme.fg("muted", outputStyleLabel(style)), CHIP_RANK_LEADER);
1235
1195
  // Ranked above every measurement: the strip is cut by dropping the
1236
1196
  // lowest-ranked chip, and these two are the answer to "did that key
1237
1197
  // register". The quit hint outranks the leader because its window is
@@ -1245,6 +1205,5 @@ function buildMetricStrip(
1245
1205
  for (const chip of candidates) pushChip(chips, chip, CHIP_RANK_DETAIL);
1246
1206
  pushChip(chips, totalChip, CHIP_RANK_TOTALS);
1247
1207
  pushChip(chips, costChip, CHIP_RANK_DETAIL);
1248
- for (const chip of deferred) pushChip(chips, chip, CHIP_RANK_DEFERRED);
1249
1208
  return joinChips(theme, selectChips(theme, chips, chipLimit, safeMaxWidth));
1250
1209
  }
@@ -1,5 +1,6 @@
1
1
  import type { PermissionRequestedPayload } from "../core/bus-events.js";
2
2
  import type { ClioSettings } from "../core/config.js";
3
+ import { nextOutputStyle } from "../core/defaults.js";
3
4
  import type { SafeEventBus } from "../core/event-bus.js";
4
5
  import { expandInlineFileReferencesAsync } from "../core/file-references.js";
5
6
  import type { PendingSkillRequest } from "../core/skill-activation.js";
@@ -326,11 +327,7 @@ export interface KeyBindingDeps {
326
327
  cycleScopedModelForward: () => void;
327
328
  cycleScopedModelBackward: () => void;
328
329
  dismissNotifications: () => void;
329
- toggleToolExpansion: () => void;
330
- toggleAllToolExpansion: () => void;
331
- toggleLiveToolOutput: () => void;
332
- toggleThinkingExpansion: () => void;
333
- toggleAllThinkingExpansion: () => void;
330
+ cycleOutputStyle: () => void;
334
331
  openExternalEditor: () => void;
335
332
  queueFollowUp: () => void;
336
333
  interruptWithMessage: () => void;
@@ -361,18 +358,12 @@ export function resolveCtrlCAction(deps: CtrlCActionDeps): CtrlCAction {
361
358
 
362
359
  function dispatchInteractiveAction(id: ClioKeybinding, deps: KeyBindingDeps): boolean {
363
360
  switch (id) {
361
+ case "clio-coder.output.cycle":
362
+ deps.cycleOutputStyle();
363
+ return true;
364
364
  case "clio-coder.notifications.dismiss":
365
365
  deps.dismissNotifications();
366
366
  return true;
367
- case "clio-coder.tool.expand":
368
- deps.toggleToolExpansion();
369
- return true;
370
- case "clio-coder.tool.expandAll":
371
- deps.toggleAllToolExpansion();
372
- return true;
373
- case "clio-coder.tool.liveOutput":
374
- deps.toggleLiveToolOutput();
375
- return true;
376
367
  case "clio-coder.editor.external":
377
368
  deps.openExternalEditor();
378
369
  return true;
@@ -385,12 +376,6 @@ function dispatchInteractiveAction(id: ClioKeybinding, deps: KeyBindingDeps): bo
385
376
  case "clio-coder.message.dequeue":
386
377
  deps.restoreQueuedFollowUps();
387
378
  return true;
388
- case "clio-coder.thinking.expand":
389
- deps.toggleThinkingExpansion();
390
- return true;
391
- case "clio-coder.thinking.expandAll":
392
- deps.toggleAllThinkingExpansion();
393
- return true;
394
379
  case "clio-coder.status.toggle":
395
380
  deps.toggleStatus();
396
381
  return true;
@@ -671,8 +656,6 @@ export async function createInteractiveApplication(deps: InteractiveDeps): Promi
671
656
  recordToolStart: (toolName, toolCallId) => presentation.recordToolStart(toolCallId, toolName),
672
657
  recordToolEnd: (_toolName, toolCallId, isError, truncated) =>
673
658
  presentation.recordToolEnd({ toolCallId, isError, truncated }),
674
- setStatusLine: (line) => chatPanel.setStatusLine(line),
675
- setLiveReasoning: (view) => chatPanel.setLiveReasoning(view),
676
659
  setLastTurnSummary: (summary) => presentation.setLastTurnSummary(summary),
677
660
  startTerminalProgress: () => agentProgress.start(),
678
661
  stopTerminalProgress: () => agentProgress.stop(),
@@ -710,7 +693,6 @@ export async function createInteractiveApplication(deps: InteractiveDeps): Promi
710
693
  chatPanel: {
711
694
  appendReplayBlock: (...args) => chatRenderer.mutate(() => chatPanel.appendReplayBlock(...args), "slash-output"),
712
695
  appendUser: (text, status) => chatRenderer.mutate(() => chatPanel.appendUser(text, status), "user-submit"),
713
- clearFoldOverrides: () => chatRenderer.mutate(() => chatPanel.clearFoldOverrides(), "output-detail"),
714
696
  },
715
697
  beforeSemanticSubmit: () => chatRenderer.flush(),
716
698
  settleVisibleFrame: (reason) => chatRenderer.flushAndCommit(reason),
@@ -788,6 +770,7 @@ export async function createInteractiveApplication(deps: InteractiveDeps): Promi
788
770
  });
789
771
 
790
772
  const editorSubmit = createEditorSubmitController({
773
+ onLocalBashRunning: presentation.setLocalBashRunning,
791
774
  editor,
792
775
  ui: tui,
793
776
  io,
@@ -1065,6 +1048,16 @@ export async function createInteractiveApplication(deps: InteractiveDeps): Promi
1065
1048
  dispatchAction: dispatchInteractiveAction,
1066
1049
  actions: {
1067
1050
  canExit: () => editor.getText().length === 0,
1051
+ cycleOutputStyle: () => {
1052
+ const settings = deps.getSettings?.();
1053
+ if (!settings || !deps.commitSetting) return;
1054
+ presentation.changeOutputStyle(() => {
1055
+ const next = structuredClone(settings) as ClioSettings;
1056
+ next.interface.outputDetail = nextOutputStyle(next.interface.outputDetail);
1057
+ deps.commitSetting?.("interface.outputDetail", next, "session");
1058
+ });
1059
+ footer.refresh();
1060
+ },
1068
1061
  availableThinkingLevels: () => availableInteractiveThinkingLevels(deps),
1069
1062
  onCycleThinking: () => deps.onCycleThinking?.(),
1070
1063
  cycleScopedModelForward: () => {
@@ -1123,25 +1116,6 @@ export async function createInteractiveApplication(deps: InteractiveDeps): Promi
1123
1116
  editorSubmit,
1124
1117
  requestRender: () => tui.requestRender(),
1125
1118
  notifications,
1126
- chatPanel: {
1127
- toggleLastToolExpanded: () => chatPanel.toggleLastToolExpanded(),
1128
- toggleAllToolsExpanded: () => chatPanel.toggleAllToolsExpanded(),
1129
- toggleLiveToolOutput: () => chatPanel.toggleLiveToolOutput(),
1130
- toggleLastThinking: () => {
1131
- let changed = false;
1132
- chatRenderer.mutate(() => {
1133
- changed = chatPanel.toggleLastThinking();
1134
- }, "thinking-visibility");
1135
- return changed;
1136
- },
1137
- toggleAllThinking: () => {
1138
- let changed = false;
1139
- chatRenderer.mutate(() => {
1140
- changed = chatPanel.toggleAllThinking();
1141
- }, "thinking-visibility");
1142
- return changed;
1143
- },
1144
- },
1145
1119
  shutdown: {
1146
1120
  stopTickers: presentation.stopTickers,
1147
1121
  disposeInteractiveTickers: interactiveTickers.dispose,