@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
@@ -6,7 +6,7 @@ export interface DelayedManualCodeInput {
6
6
  }
7
7
 
8
8
  export function createDelayedManualCodeInput(
9
- rl: ReadlineInterface,
9
+ rl: Pick<ReadlineInterface, "question">,
10
10
  prompt: string,
11
11
  options?: { delayMs?: number },
12
12
  ): DelayedManualCodeInput {
package/src/cli/reset.ts CHANGED
@@ -372,6 +372,8 @@ export async function runResetCommand(argv: ReadonlyArray<string>): Promise<numb
372
372
  presenter.finish();
373
373
  return 1;
374
374
  }
375
+ if (args.config || args.all) presenter.commandAdvice("Set up a model again:", "clio-coder configure");
376
+ else if (args.auth) presenter.commandAdvice("Reconnect your providers:", "clio-coder configure --section targets");
375
377
 
376
378
  presenter.done("Done");
377
379
  return 0;
package/src/cli/select.ts CHANGED
@@ -48,6 +48,8 @@ export interface SelectOptions<T> {
48
48
  backLabel?: string;
49
49
  /** Rows to show at once. Defaults to what the terminal has room for. */
50
50
  maxVisible?: number;
51
+ /** Filter a long list by typing; q/j/k are text in this mode. */
52
+ searchable?: boolean;
51
53
  /**
52
54
  * Erase the menu once it is answered, leaving the caller's transcript to say
53
55
  * what was chosen. A wizard that redraws its own answer rows needs this; a
@@ -243,7 +245,7 @@ function viewportFor(output: NodeJS.WriteStream, overhead: number, total: number
243
245
  export async function promptSelect<T>(options: SelectOptions<T>): Promise<SelectResult<T>> {
244
246
  const input = options.input ?? process.stdin;
245
247
  const output = options.output ?? process.stdout;
246
- const choices = options.choices;
248
+ let choices = options.choices;
247
249
  if (choices.length === 0) return { kind: "back" };
248
250
 
249
251
  const rail = options.railPrefix ?? "";
@@ -255,15 +257,25 @@ export async function promptSelect<T>(options: SelectOptions<T>): Promise<Select
255
257
 
256
258
  let index = Math.min(Math.max(options.initialIndex ?? 0, 0), choices.length - 1);
257
259
  let windowStart = 0;
260
+ let query = "";
261
+ const filter = () => {
262
+ choices = options.choices.filter((choice) => choice.label.toLowerCase().includes(query.toLowerCase()));
263
+ index = 0;
264
+ windowStart = 0;
265
+ render();
266
+ };
258
267
 
259
268
  const render = (): void => {
260
- const viewport = viewportFor(output, head.length + 5, choices.length, options.maxVisible);
269
+ const viewport = viewportFor(output, head.length + (options.searchable ? 6 : 5), choices.length, options.maxVisible);
261
270
  if (index < windowStart) windowStart = index;
262
271
  else if (index >= windowStart + viewport) windowStart = index - viewport + 1;
263
272
  windowStart = Math.max(0, Math.min(windowStart, choices.length - viewport));
264
273
  const lines: string[] = [];
265
274
  for (const line of head) lines.push(`${rail}${line}`.trimEnd());
266
275
  if (head.length > 0) lines.push(rail.trimEnd());
276
+ if (options.searchable)
277
+ lines.push(`${rail}${truncate(`Filter: ${query || "(type to search)"}`, terminalColumns(output) - railWidth)}`);
278
+ if (choices.length === 0) lines.push(`${rail}No matching models`);
267
279
  lines.push(
268
280
  ...choiceRows(
269
281
  {
@@ -284,7 +296,8 @@ export async function promptSelect<T>(options: SelectOptions<T>): Promise<Select
284
296
  // "quit" reads as two different exits.
285
297
  const legend = ["↑/↓ move", "enter select"];
286
298
  if (options.backLabel !== undefined && options.backLabel !== "quit") legend.push(`esc ${options.backLabel}`);
287
- legend.push(options.backLabel === "quit" ? "esc or q quit" : "q quit");
299
+ if (options.searchable) legend.push("type to filter", "ctrl-u clear", "ctrl-c quit");
300
+ else legend.push(options.backLabel === "quit" ? "esc or q quit" : "q quit");
288
301
  lines.push(`${rail}${chalk.dim(legend.join(" · "))}`);
289
302
  frame.draw(lines);
290
303
  };
@@ -294,11 +307,29 @@ export async function promptSelect<T>(options: SelectOptions<T>): Promise<Select
294
307
  output,
295
308
  true,
296
309
  render,
297
- (_str, key, finish) => {
310
+ (str, key, finish) => {
298
311
  if (key.ctrl && (key.name === "c" || key.name === "d")) {
299
312
  finish({ kind: "quit" });
300
313
  return;
301
314
  }
315
+ if (options.searchable) {
316
+ if (key.ctrl && key.name === "u") {
317
+ query = "";
318
+ filter();
319
+ return;
320
+ }
321
+ if (key.name === "backspace") {
322
+ query = query.slice(0, -1);
323
+ filter();
324
+ return;
325
+ }
326
+ if (!key.ctrl && !key.meta && str?.length === 1 && str >= " " && str !== "\x7f") {
327
+ query += str;
328
+ filter();
329
+ return;
330
+ }
331
+ if (choices.length === 0 && !["escape", "left"].includes(key.name ?? "")) return;
332
+ }
302
333
  switch (key.name) {
303
334
  case "up":
304
335
  case "k":
@@ -1,6 +1,6 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { initializeClioHome } from "../core/init.js";
3
- import { clioStateDir } from "../core/xdg.js";
3
+ import { resolveClioDirs } from "../core/xdg.js";
4
4
  import { detectInstallMethod } from "../domains/lifecycle/install-method.js";
5
5
  import { listMigrations, readMigrationManifest, runPending } from "../domains/lifecycle/migrations/index.js";
6
6
  import { readStateInfo } from "../domains/lifecycle/state.js";
@@ -185,7 +185,7 @@ export async function runUpgradeCommand(argv: ReadonlyArray<string>): Promise<nu
185
185
  presenter.header("Upgrade", "upgrade");
186
186
 
187
187
  const before = getVersionInfo().clio;
188
- const stateDir = clioStateDir();
188
+ const stateDir = resolveClioDirs().state;
189
189
  const method = detectInstallMethod();
190
190
  const methodLabel = method === "source" ? "source checkout" : opts.postInstall ? "package install" : "npm global";
191
191
  presenter.setMethod(methodLabel);
@@ -21,6 +21,7 @@ import {
21
21
  ACTIVE_ROUTING_ROLES,
22
22
  COUNCIL_MEMBER_LABEL_PATTERN,
23
23
  DEFAULT_SETTINGS,
24
+ normalizeOutputStyle,
24
25
  THEME_NAMED_COLORS,
25
26
  THINKING_LEVELS,
26
27
  type ThinkingLevel,
@@ -1745,8 +1746,12 @@ export function validateSettings(raw: unknown): SettingsValidationResult {
1745
1746
  const parsed = expectBoolean(issues, `interface.${key}`, ui[key]);
1746
1747
  if (parsed !== undefined) (settings.interface as unknown as Record<string, unknown>)[key] = parsed;
1747
1748
  }
1749
+ if ("outputDetail" in ui) {
1750
+ const style = normalizeOutputStyle(ui.outputDetail);
1751
+ if (style) settings.interface.outputDetail = style;
1752
+ else issues.add("interface.outputDetail", "expected compact, standard, or detailed");
1753
+ }
1748
1754
  for (const [key, allowed] of [
1749
- ["outputDetail", ["minimal", "default", "verbose"]],
1750
1755
  ["smoothStreaming", ["off", "auto", "on"]],
1751
1756
  ["mode", ["regular", "fullscreen"]],
1752
1757
  ["fullscreenScrollbar", ["hidden", "auto", "always"]],
@@ -219,15 +219,37 @@ export interface RetrySettings {
219
219
  streamStallMs: number;
220
220
  }
221
221
 
222
- export type OutputVerbosity = "minimal" | "default" | "verbose";
222
+ export const OUTPUT_STYLES = ["compact", "standard", "detailed"] as const;
223
+ export type OutputStyle = (typeof OUTPUT_STYLES)[number];
224
+
225
+ /** Read older preferences without rewriting a user's settings on startup. */
226
+ export function normalizeOutputStyle(value: unknown): OutputStyle | undefined {
227
+ switch (value) {
228
+ case "minimal":
229
+ case "compact":
230
+ return "compact";
231
+ case "default":
232
+ case "standard":
233
+ return "standard";
234
+ case "verbose":
235
+ case "detailed":
236
+ return "detailed";
237
+ default:
238
+ return undefined;
239
+ }
240
+ }
241
+
242
+ export function nextOutputStyle(style: OutputStyle): OutputStyle {
243
+ return OUTPUT_STYLES[(OUTPUT_STYLES.indexOf(style) + 1) % OUTPUT_STYLES.length] ?? "standard";
244
+ }
223
245
  export type TuiMode = "regular" | "fullscreen";
224
246
  export type FullscreenScrollbar = "hidden" | "auto" | "always";
225
247
  export type SmoothStreaming = "off" | "auto" | "on";
226
248
 
227
249
  export interface InterfaceSettings {
228
250
  terminalProgress: boolean;
229
- /** Transcript detail: collapsed, balanced, or fully transparent. */
230
- outputDetail: OutputVerbosity;
251
+ /** Output style: Compact, Standard, or Detailed bounded previews. */
252
+ outputDetail: OutputStyle;
231
253
  /** Regular scrollback-preserving renderer or alternate-screen sticky layout. */
232
254
  mode: TuiMode;
233
255
  /** Fullscreen transcript scrollbar visibility. */
@@ -456,7 +478,7 @@ export const DEFAULT_SETTINGS = {
456
478
  recentLimit: 12,
457
479
  } as ModelSelectorSettings,
458
480
  maxOutputTokens: 0,
459
- prewarm: true,
481
+ prewarm: false,
460
482
  retry: {
461
483
  enabled: true,
462
484
  maxRetries: 3,
@@ -484,7 +506,7 @@ export const DEFAULT_SETTINGS = {
484
506
  mode: "deny" as WorkerPermissionMode,
485
507
  escalation: { timeoutMs: 120000, fallback: "deny" } as WorkerEscalationSettings,
486
508
  },
487
- concurrency: "auto" as "auto" | number,
509
+ concurrency: 1 as "auto" | number,
488
510
  retry: { maxRetries: 2, routeCooldownMs: 15000 },
489
511
  limits: {
490
512
  toolCallsPerRun: GUARDRAIL_DEFAULTS.workerToolCallCap,
@@ -521,10 +543,10 @@ export const DEFAULT_SETTINGS = {
521
543
  } as SafetySettings,
522
544
  interface: {
523
545
  terminalProgress: false,
524
- outputDetail: "default",
546
+ outputDetail: "standard",
525
547
  mode: "regular",
526
548
  fullscreenScrollbar: "auto",
527
- smoothStreaming: "off",
549
+ smoothStreaming: "auto",
528
550
  desktopNotifications: false,
529
551
  panes: {
530
552
  enabled: "off",
@@ -591,7 +613,7 @@ chat:
591
613
  favorites: []
592
614
  recentLimit: 12
593
615
  maxOutputTokens: 0
594
- prewarm: true
616
+ prewarm: false
595
617
  retry:
596
618
  enabled: true
597
619
  maxRetries: 3
@@ -617,7 +639,7 @@ fleet:
617
639
  escalation:
618
640
  timeoutMs: 120000
619
641
  fallback: deny
620
- concurrency: auto
642
+ concurrency: 1
621
643
  retry:
622
644
  maxRetries: 2
623
645
  routeCooldownMs: 15000
@@ -659,8 +681,8 @@ safety:
659
681
  enabled: false
660
682
 
661
683
  interface:
662
- outputDetail: default
663
- smoothStreaming: off
684
+ outputDetail: standard
685
+ smoothStreaming: auto
664
686
  mode: regular
665
687
  fullscreenScrollbar: auto
666
688
  terminalProgress: false
@@ -3,7 +3,7 @@ import { randomUUID } from "node:crypto";
3
3
  import { readFileSync, unlinkSync, writeFileSync } from "node:fs";
4
4
  import { tmpdir } from "node:os";
5
5
  import { join } from "node:path";
6
- import { shellQuote } from "../core/shell-quote.js";
6
+ import { shellQuote } from "./shell-quote.js";
7
7
 
8
8
  export type ExternalEditorProbe = (candidates: ReadonlyArray<string>) => string | null;
9
9
 
@@ -48,11 +48,11 @@ export function resolveExternalEditor(
48
48
  return probe(["nano", "vi"]);
49
49
  }
50
50
 
51
- export function editTextExternally(initialText: string, command: string | null): ExternalEditResult {
51
+ export function editTextExternally(initialText: string, command: string | null, extension = ".md"): ExternalEditResult {
52
52
  if (!command) return { ok: false, error: "no external editor configured; set VISUAL or EDITOR" };
53
- const tmpFile = join(tmpdir(), `clio-coder-editor-${process.pid}-${randomUUID()}.md`);
53
+ const tmpFile = join(tmpdir(), `clio-coder-editor-${process.pid}-${randomUUID()}${extension}`);
54
54
  try {
55
- writeFileSync(tmpFile, initialText, "utf8");
55
+ writeFileSync(tmpFile, initialText, { encoding: "utf8", mode: 0o600, flag: "wx" });
56
56
  const result = runEditor(command, tmpFile);
57
57
  if (result.error) return { ok: false, error: result.error.message };
58
58
  if (result.status !== 0) return { ok: false, error: `external editor exited with code ${result.status ?? "?"}` };
@@ -22,6 +22,7 @@ const HOT_RELOAD_FIELDS = new Set<string>([
22
22
  "interface.keybindings",
23
23
  "safety.autonomy",
24
24
  "chat.modelPicker",
25
+ "interface.outputDetail",
25
26
  "interface.smoothStreaming",
26
27
  "interface.panes.notifications",
27
28
  "integrations.git.commitAttribution",
@@ -49,7 +50,6 @@ const NEXT_TURN_FIELDS = new Set<string>([
49
50
  "fleet.history",
50
51
  "context",
51
52
  "safety.limits",
52
- "interface.outputDetail",
53
53
  "interface.terminalProgress",
54
54
  "interface.desktopNotifications",
55
55
  "integrations.projectResources",
@@ -19,6 +19,7 @@ import { TUI_KEYBINDINGS } from "../../engine/tui.js";
19
19
  * live in `resolveCtrlCAction` and are not a simple keybinding.
20
20
  */
21
21
  export interface ClioAppKeybindings {
22
+ "clio-coder.output.cycle": true;
22
23
  "clio-coder.thinking.cycle": true;
23
24
  "clio-coder.exit": true;
24
25
  "clio-coder.status.toggle": true;
@@ -31,11 +32,6 @@ export interface ClioAppKeybindings {
31
32
  "clio-coder.model.select": true;
32
33
  "clio-coder.model.cycleForward": true;
33
34
  "clio-coder.model.cycleBackward": true;
34
- "clio-coder.tool.expand": true;
35
- "clio-coder.tool.expandAll": true;
36
- "clio-coder.tool.liveOutput": true;
37
- "clio-coder.thinking.expand": true;
38
- "clio-coder.thinking.expandAll": true;
39
35
  "clio-coder.editor.external": true;
40
36
  "clio-coder.message.followUp": true;
41
37
  "clio-coder.message.interrupt": true;
@@ -128,33 +124,9 @@ export const CLIO_APP_KEYBINDINGS = {
128
124
  defaultKeys: "alt+k",
129
125
  description: "Cycle to previous scoped model",
130
126
  },
131
- "clio-coder.tool.expand": {
127
+ "clio-coder.output.cycle": {
132
128
  defaultKeys: "alt+o",
133
- description:
134
- "Fold or unfold the newest tool call or worker block between its one-line summary and full details, overriding the /output level for that block",
135
- },
136
- "clio-coder.tool.expandAll": {
137
- // Alt+Shift+letter is commonly consumed by OS keyboard-layout switching.
138
- // Keep it discoverable, but pair it with the legacy-safe Ctrl+Alt form.
139
- defaultKeys: ["ctrl+alt+o", "alt+shift+o"],
140
- description:
141
- "Fold or unfold every tool call and worker block at once, overriding the /output level; changing /output clears the overrides",
142
- },
143
- "clio-coder.tool.liveOutput": {
144
- defaultKeys: "alt+p",
145
- description: "Toggle streaming partial tool output in expanded tool bodies",
146
- },
147
- "clio-coder.thinking.expand": {
148
- defaultKeys: "alt+r",
149
- description:
150
- "Toggle the latest thinking block between hidden marker and full body (no effect while /output verbose pins it open)",
151
- },
152
- "clio-coder.thinking.expandAll": {
153
- // Ctrl+Alt+R is the fallback for terminals/OSes that do not forward
154
- // Alt+Shift+R as a distinct key event.
155
- defaultKeys: ["ctrl+alt+r", "alt+shift+r"],
156
- description:
157
- "Toggle all thinking blocks between hidden markers and full bodies, overriding the /output level; changing /output clears the overrides",
129
+ description: "Cycle output style: Compact, Standard, Detailed",
158
130
  },
159
131
  "clio-coder.editor.external": {
160
132
  defaultKeys: "alt+g",
@@ -3,11 +3,15 @@ export interface NativeRuntimeFingerprint {
3
3
  displayName: string;
4
4
  }
5
5
 
6
- async function fetchWithTimeout(url: string, timeoutMs: number): Promise<Response | null> {
6
+ async function fetchWithTimeout(
7
+ url: string,
8
+ timeoutMs: number,
9
+ headers: Record<string, string>,
10
+ ): Promise<Response | null> {
7
11
  const controller = new AbortController();
8
12
  const timer = setTimeout(() => controller.abort(), timeoutMs);
9
13
  try {
10
- return await fetch(url, { signal: controller.signal });
14
+ return await fetch(url, { signal: controller.signal, headers });
11
15
  } catch {
12
16
  return null;
13
17
  } finally {
@@ -22,10 +26,13 @@ async function fetchWithTimeout(url: string, timeoutMs: number): Promise<Respons
22
26
  * by doctor + configure wizard to steer users onto native runtimes for
23
27
  * resident-model lifecycle.
24
28
  */
25
- export async function fingerprintNativeRuntime(baseUrl: string): Promise<NativeRuntimeFingerprint | null> {
29
+ export async function fingerprintNativeRuntime(
30
+ baseUrl: string,
31
+ headers: Record<string, string> = {},
32
+ ): Promise<NativeRuntimeFingerprint | null> {
26
33
  const normalized = baseUrl.replace(/^ws:/u, "http:").replace(/^wss:/u, "https:");
27
34
  const trimmed = normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
28
- const greeting = await fetchWithTimeout(`${trimmed}/lmstudio-greeting`, 750);
35
+ const greeting = await fetchWithTimeout(`${trimmed}/lmstudio-greeting`, 750, headers);
29
36
  if (greeting?.ok) {
30
37
  try {
31
38
  const data = (await greeting.json()) as { lmstudio?: unknown };
@@ -34,7 +41,7 @@ export async function fingerprintNativeRuntime(baseUrl: string): Promise<NativeR
34
41
  // The exact JSON body is the fingerprint.
35
42
  }
36
43
  }
37
- const lmStudioV0 = await fetchWithTimeout(`${trimmed}/api/v0/models`, 750);
44
+ const lmStudioV0 = await fetchWithTimeout(`${trimmed}/api/v0/models`, 750, headers);
38
45
  if (lmStudioV0?.ok) {
39
46
  try {
40
47
  const data = (await lmStudioV0.json()) as { data?: unknown };
@@ -48,7 +55,7 @@ export async function fingerprintNativeRuntime(baseUrl: string): Promise<NativeR
48
55
  // A malformed v0 body is not an LM Studio fingerprint.
49
56
  }
50
57
  }
51
- const ollama = await fetchWithTimeout(`${trimmed}/api/version`, 750);
58
+ const ollama = await fetchWithTimeout(`${trimmed}/api/version`, 750, headers);
52
59
  if (ollama?.ok) {
53
60
  try {
54
61
  const data = (await ollama.json()) as { version?: unknown };
@@ -19,6 +19,7 @@ export interface ProbeReasoningOptions {
19
19
  modelId: string;
20
20
  timeoutMs: number;
21
21
  apiKey?: string;
22
+ headers?: Record<string, string>;
22
23
  signal?: AbortSignal;
23
24
  }
24
25
 
@@ -72,6 +73,7 @@ export async function probeOpenAICompatReasoning(opts: ProbeReasoningOptions): P
72
73
 
73
74
  const headers: Record<string, string> = { "content-type": "application/json" };
74
75
  if (opts.apiKey && opts.apiKey.length > 0) headers.authorization = `Bearer ${opts.apiKey}`;
76
+ Object.assign(headers, opts.headers);
75
77
 
76
78
  const body = JSON.stringify({
77
79
  model: opts.modelId,
@@ -8,13 +8,27 @@ export interface OpenAIModelsResponse {
8
8
  data?: Array<Record<string, unknown> & { id?: unknown; status?: unknown }>;
9
9
  }
10
10
 
11
- export async function probeUrl(url: string, ctx: ProbeContext, method: "GET" | "HEAD" = "GET"): Promise<ProbeResult> {
12
- const base = { url, timeoutMs: ctx.httpTimeoutMs, method } as const;
11
+ function bearerHeaders(ctx: ProbeContext): Record<string, string> {
12
+ return ctx.authToken ? { Authorization: `Bearer ${ctx.authToken}` } : {};
13
+ }
14
+
15
+ export async function probeUrl(
16
+ url: string,
17
+ ctx: ProbeContext,
18
+ method: "GET" | "HEAD" = "GET",
19
+ headers: Record<string, string> = bearerHeaders(ctx),
20
+ ): Promise<ProbeResult> {
21
+ const base = { url, timeoutMs: ctx.httpTimeoutMs, method, headers } as const;
13
22
  return ctx.signal ? probeHttp({ ...base, signal: ctx.signal }) : probeHttp(base);
14
23
  }
15
24
 
16
- export async function probeOpenAIModels(base: string, ctx: ProbeContext, modelsPath = "/v1/models"): Promise<string[]> {
17
- return (await probeOpenAIModelCatalog(base, ctx, modelsPath)).models;
25
+ export async function probeOpenAIModels(
26
+ base: string,
27
+ ctx: ProbeContext,
28
+ modelsPath = "/v1/models",
29
+ headers: Record<string, string> = bearerHeaders(ctx),
30
+ ): Promise<string[]> {
31
+ return (await probeOpenAIModelCatalog(base, ctx, modelsPath, headers)).models;
18
32
  }
19
33
 
20
34
  export interface OpenAIModelCatalogProbe {
@@ -38,11 +52,12 @@ async function probeModelDetailRows(
38
52
  base: string,
39
53
  ctx: ProbeContext,
40
54
  modelsPath: string,
55
+ headers: Record<string, string>,
41
56
  ): Promise<Map<string, Record<string, unknown>>> {
42
57
  const rows = new Map<string, Record<string, unknown>>();
43
58
  for (const detailPath of OPENAI_COMPAT_DETAIL_PATHS) {
44
59
  if (detailPath === modelsPath) continue;
45
- const opts = { url: `${base}${detailPath}`, timeoutMs: ctx.httpTimeoutMs } as const;
60
+ const opts = { url: `${base}${detailPath}`, timeoutMs: ctx.httpTimeoutMs, headers } as const;
46
61
  const result = await (ctx.signal
47
62
  ? probeJson<OpenAIModelsResponse>({ ...opts, signal: ctx.signal })
48
63
  : probeJson<OpenAIModelsResponse>(opts));
@@ -60,13 +75,14 @@ export async function probeOpenAIModelCatalog(
60
75
  base: string,
61
76
  ctx: ProbeContext,
62
77
  modelsPath = "/v1/models",
78
+ headers: Record<string, string> = bearerHeaders(ctx),
63
79
  ): Promise<OpenAIModelCatalogProbe> {
64
- const opts = { url: `${base}${modelsPath}`, timeoutMs: ctx.httpTimeoutMs } as const;
80
+ const opts = { url: `${base}${modelsPath}`, timeoutMs: ctx.httpTimeoutMs, headers } as const;
65
81
  const result = await (ctx.signal
66
82
  ? probeJson<OpenAIModelsResponse>({ ...opts, signal: ctx.signal })
67
83
  : probeJson<OpenAIModelsResponse>(opts));
68
84
  if (!result.ok || !result.data?.data) return { models: [], modelCapabilities: {}, modelStates: {} };
69
- const detail = await probeModelDetailRows(base, ctx, modelsPath);
85
+ const detail = await probeModelDetailRows(base, ctx, modelsPath, headers);
70
86
  const models: string[] = [];
71
87
  const modelCapabilities: Record<string, Partial<CapabilityFlags>> = {};
72
88
  const modelStates: Record<string, ProbeModelStatus> = {};
@@ -49,6 +49,11 @@ function synthesizeAnthropicCompatModel(input: AnthropicCompatSynthesisInput): M
49
49
  export function makeAnthropicCompatRuntime(spec: AnthropicCompatSpec): RuntimeDescriptor {
50
50
  const messagesPath = spec.messagesPath ?? "/v1/messages";
51
51
  const modelsPath = spec.modelsPath ?? "/v1/models";
52
+ const headers = (target: TargetDescriptor, ctx: ProbeContext): Record<string, string> => ({
53
+ "anthropic-version": "2023-06-01",
54
+ ...(ctx.authToken ? { "x-api-key": ctx.authToken } : {}),
55
+ ...target.auth?.headers,
56
+ });
52
57
  return {
53
58
  id: spec.id,
54
59
  displayName: spec.displayName,
@@ -61,12 +66,12 @@ export function makeAnthropicCompatRuntime(spec: AnthropicCompatSpec): RuntimeDe
61
66
  async probe(target: TargetDescriptor, ctx: ProbeContext): Promise<ProbeResult> {
62
67
  const base = targetBaseUrl(target);
63
68
  if (!base) return { ok: false, error: "target has no url" };
64
- return probeUrl(`${base}${messagesPath}`, ctx, "HEAD");
69
+ return probeUrl(`${base}${messagesPath}`, ctx, "HEAD", headers(target, ctx));
65
70
  },
66
71
  async probeModels(target: TargetDescriptor, ctx: ProbeContext): Promise<string[]> {
67
72
  const base = targetBaseUrl(target);
68
73
  if (!base) return [];
69
- return probeOpenAIModels(base, ctx, modelsPath);
74
+ return probeOpenAIModels(base, ctx, modelsPath, headers(target, ctx));
70
75
  },
71
76
  synthesizeModel(target: TargetDescriptor, wireModelId: string, kb: KnowledgeBaseHit | null): Model<Api> {
72
77
  return synthesizeAnthropicCompatModel({
@@ -61,6 +61,10 @@ export function makeOpenAICompatRuntime(spec: OpenAICompatSpec): RuntimeDescript
61
61
  // root and a target naming the `/v1` mount point has to be reduced to it. An
62
62
  // `asIs` spec sends the URL verbatim, so it keeps whatever the user typed.
63
63
  const probeBase = asIs ? targetBaseUrl : targetRootUrl;
64
+ const headers = (target: TargetDescriptor, ctx: ProbeContext): Record<string, string> => ({
65
+ ...(ctx.authToken ? { Authorization: `Bearer ${ctx.authToken}` } : {}),
66
+ ...target.auth?.headers,
67
+ });
64
68
  return {
65
69
  id: spec.id,
66
70
  displayName: spec.displayName,
@@ -72,9 +76,9 @@ export function makeOpenAICompatRuntime(spec: OpenAICompatSpec): RuntimeDescript
72
76
  async probe(target: TargetDescriptor, ctx: ProbeContext): Promise<ProbeResult> {
73
77
  const base = probeBase(target);
74
78
  if (!base) return { ok: false, error: "target has no url" };
75
- const health = await probeUrl(`${base}${healthPath}`, ctx);
79
+ const health = await probeUrl(`${base}${healthPath}`, ctx, "GET", headers(target, ctx));
76
80
  if (!health.ok) return health;
77
- const catalog = await probeOpenAIModelCatalog(base, ctx, modelsPath);
81
+ const catalog = await probeOpenAIModelCatalog(base, ctx, modelsPath, headers(target, ctx));
78
82
  const result: ProbeResult = { ...health };
79
83
  if (catalog.models.length > 0) result.models = catalog.models;
80
84
  if (Object.keys(catalog.modelStates).length > 0) result.modelStates = catalog.modelStates;
@@ -92,19 +96,21 @@ export function makeOpenAICompatRuntime(spec: OpenAICompatSpec): RuntimeDescript
92
96
  async probeModels(target: TargetDescriptor, ctx: ProbeContext): Promise<string[]> {
93
97
  const base = probeBase(target);
94
98
  if (!base) return [];
95
- return probeOpenAIModels(base, ctx, modelsPath);
99
+ return probeOpenAIModels(base, ctx, modelsPath, headers(target, ctx));
96
100
  },
97
101
  async probeReasoning(target: TargetDescriptor, modelId: string, ctx: ProbeContext): Promise<ReasoningProbeResult> {
98
102
  const base = probeBase(target);
99
103
  if (!base) return { reasoning: false, latencyMs: 0, error: "target has no url" };
100
104
  const apiKeyEnv = target.auth?.apiKeyEnvVar;
101
- const apiKey = apiKeyEnv && ctx.credentialsPresent.has(apiKeyEnv) ? process.env[apiKeyEnv] : undefined;
105
+ const apiKey =
106
+ ctx.authToken ?? (apiKeyEnv && ctx.credentialsPresent.has(apiKeyEnv) ? process.env[apiKeyEnv] : undefined);
102
107
  const probeOpts: Parameters<typeof probeOpenAICompatReasoning>[0] = {
103
108
  baseUrl: base,
104
109
  modelId,
105
110
  timeoutMs: Math.max(ctx.httpTimeoutMs, 8000),
106
111
  };
107
112
  if (apiKey) probeOpts.apiKey = apiKey;
113
+ if (target.auth?.headers) probeOpts.headers = target.auth.headers;
108
114
  if (ctx.signal) probeOpts.signal = ctx.signal;
109
115
  return probeOpenAICompatReasoning(probeOpts);
110
116
  },
@@ -6,16 +6,12 @@ import { isEscapeKey, type OverlayState, overlayOwnsInput } from "./overlay-life
6
6
  export const APPLICATION_DOUBLE_TAP_MS = 500;
7
7
 
8
8
  const CLOSED_ACTION_ORDER = [
9
+ "clio-coder.output.cycle",
9
10
  "clio-coder.notifications.dismiss",
10
- "clio-coder.tool.expand",
11
- "clio-coder.tool.expandAll",
12
- "clio-coder.tool.liveOutput",
13
11
  "clio-coder.editor.external",
14
12
  "clio-coder.message.followUp",
15
13
  "clio-coder.message.interrupt",
16
14
  "clio-coder.message.dequeue",
17
- "clio-coder.thinking.expand",
18
- "clio-coder.thinking.expandAll",
19
15
  ] as const satisfies ReadonlyArray<ClioKeybinding>;
20
16
 
21
17
  export const GLOBAL_ACTION_ORDER = [
@@ -108,10 +104,6 @@ export interface ApplicationControllerDeps {
108
104
  listNotifications: () => ReadonlyArray<{ id: string }>;
109
105
  dismissNotification: (id: string) => void;
110
106
  dismissAllNotifications: () => void;
111
- toggleLastToolExpanded: () => boolean;
112
- toggleAllToolsExpanded: () => boolean;
113
- toggleLastThinking: () => boolean;
114
- toggleAllThinking: () => boolean;
115
107
  shutdownDisposers: ReadonlyArray<() => void>;
116
108
  /** Settle the last presentation mutation into an accepted/drained frame. */
117
109
  beforeStopUi?: () => Promise<void>;
@@ -145,8 +137,6 @@ export interface ApplicationController {
145
137
  handleInput(data: string): ApplicationInputResult;
146
138
  handleCtrlC(): void;
147
139
  dismissNotifications(): void;
148
- toggleToolExpansion(): void;
149
- toggleThinkingExpansion(): void;
150
140
  shutdown(): Promise<void>;
151
141
  }
152
142
 
@@ -163,8 +153,6 @@ export function createApplicationController(deps: ApplicationControllerDeps): Ap
163
153
  let shuttingDown = false;
164
154
  let lastCtrlCAt = 0;
165
155
  let lastNotificationDismissAt = 0;
166
- let lastToolExpandAt = 0;
167
- let lastThinkingExpandAt = 0;
168
156
 
169
157
  const isDoubleTap = (lastAt: number, now: number): boolean => lastAt > 0 && now - lastAt <= APPLICATION_DOUBLE_TAP_MS;
170
158
 
@@ -346,22 +334,6 @@ export function createApplicationController(deps: ApplicationControllerDeps): Ap
346
334
  if (first) deps.dismissNotification(first.id);
347
335
  };
348
336
 
349
- const toggleToolExpansion = (): void => {
350
- const now = deps.clock.now();
351
- const doubleTap = isDoubleTap(lastToolExpandAt, now);
352
- lastToolExpandAt = now;
353
- const changed = doubleTap ? deps.toggleAllToolsExpanded() : deps.toggleLastToolExpanded();
354
- if (changed) deps.requestRender();
355
- };
356
-
357
- const toggleThinkingExpansion = (): void => {
358
- const now = deps.clock.now();
359
- const doubleTap = isDoubleTap(lastThinkingExpandAt, now);
360
- lastThinkingExpandAt = now;
361
- const changed = doubleTap ? deps.toggleAllThinking() : deps.toggleLastThinking();
362
- if (changed) deps.requestRender();
363
- };
364
-
365
337
  const handleInput = (data: string): ApplicationInputResult => {
366
338
  const initialOverlayState = deps.getOverlayState();
367
339
  if (overlayOwnsInput(initialOverlayState) && deps.leaderKeys.isPending()) deps.leaderKeys.reset();
@@ -427,8 +399,6 @@ export function createApplicationController(deps: ApplicationControllerDeps): Ap
427
399
  handleInput,
428
400
  handleCtrlC,
429
401
  dismissNotifications,
430
- toggleToolExpansion,
431
- toggleThinkingExpansion,
432
402
  shutdown,
433
403
  };
434
404
  }