flavor-code 1.4.1 → 1.4.2

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.
@@ -19,7 +19,7 @@ import {
19
19
  thinkingWindow,
20
20
  transcriptReducer,
21
21
  withTimeout
22
- } from "./chunk-O2PC44GY.js";
22
+ } from "./chunk-QKND3FX7.js";
23
23
  import "./chunk-N5MXHJKD.js";
24
24
  import "./chunk-7YFHRORG.js";
25
25
  import {
@@ -39,17 +39,17 @@ import {
39
39
  useStdout,
40
40
  use_app_default,
41
41
  use_input_default
42
- } from "./chunk-VMQWXQEC.js";
42
+ } from "./chunk-W2TIY6JY.js";
43
43
  import {
44
44
  execFileNoThrow,
45
45
  getGraphemeSegmenter
46
- } from "./chunk-ZJCAQXA7.js";
46
+ } from "./chunk-B23V72YR.js";
47
47
  import "./chunk-S32JRDZK.js";
48
48
 
49
49
  // src/ui/app.tsx
50
50
  import { basename as basename3 } from "path";
51
51
  import { homedir } from "os";
52
- import { useEffect as useEffect3, useReducer, useRef as useRef4, useState as useState2 } from "react";
52
+ import React4, { useEffect as useEffect3, useMemo as useMemo3, useReducer, useRef as useRef4, useState as useState2 } from "react";
53
53
 
54
54
  // src/claude-ink/hooks/use-animation-frame.ts
55
55
  import { useContext as useContext2, useEffect, useState } from "react";
@@ -1364,6 +1364,18 @@ var CANDIDATE_KIND_PRIORITY = {
1364
1364
  command: 1,
1365
1365
  tool: 2
1366
1366
  };
1367
+ var SLASH_DESCRIPTION_CHAR_LIMIT = 240;
1368
+ function normalizeSlashDescription(description) {
1369
+ const singleLine = description.replace(/\s+/gu, " ").trim();
1370
+ const points = [...singleLine];
1371
+ if (points.length <= SLASH_DESCRIPTION_CHAR_LIMIT) return singleLine;
1372
+ return `${points.slice(0, SLASH_DESCRIPTION_CHAR_LIMIT - 1).join("")}\u2026`;
1373
+ }
1374
+ function optionalDescription(description) {
1375
+ if (description === void 0) return void 0;
1376
+ const normalized = normalizeSlashDescription(description);
1377
+ return normalized.length === 0 ? void 0 : normalized;
1378
+ }
1367
1379
  function slashCandidatePresentation(selected) {
1368
1380
  return {
1369
1381
  marker: selected ? "\u203A " : " ",
@@ -1374,16 +1386,22 @@ function slashCandidatePresentation(selected) {
1374
1386
  function buildSlashCandidates(commands, plugins, skills, tools = []) {
1375
1387
  const candidates = /* @__PURE__ */ new Map();
1376
1388
  for (const command of commands) {
1377
- candidates.set(command.name, { name: command.name, kind: "command", description: command.description });
1389
+ candidates.set(command.name, {
1390
+ name: command.name,
1391
+ kind: "command",
1392
+ description: normalizeSlashDescription(command.description)
1393
+ });
1378
1394
  }
1379
1395
  for (const plugin of plugins) {
1380
1396
  if (!candidates.has(plugin.name)) {
1381
- candidates.set(plugin.name, plugin.description === void 0 ? { name: plugin.name, kind: "plugin" } : { name: plugin.name, kind: "plugin", description: plugin.description });
1397
+ const description = optionalDescription(plugin.description);
1398
+ candidates.set(plugin.name, description === void 0 ? { name: plugin.name, kind: "plugin" } : { name: plugin.name, kind: "plugin", description });
1382
1399
  }
1383
1400
  }
1384
1401
  for (const tool of tools) {
1385
1402
  if (!candidates.has(tool.name)) {
1386
- candidates.set(tool.name, tool.description === void 0 ? { name: tool.name, kind: "tool" } : { name: tool.name, kind: "tool", description: tool.description });
1403
+ const description = optionalDescription(tool.description);
1404
+ candidates.set(tool.name, description === void 0 ? { name: tool.name, kind: "tool" } : { name: tool.name, kind: "tool", description });
1387
1405
  }
1388
1406
  }
1389
1407
  for (const skill of skills) {
@@ -1391,7 +1409,7 @@ function buildSlashCandidates(commands, plugins, skills, tools = []) {
1391
1409
  candidates.set(skill.name, {
1392
1410
  name: skill.name,
1393
1411
  kind: "skill",
1394
- description: skill.description,
1412
+ description: normalizeSlashDescription(skill.description),
1395
1413
  source: skill.source
1396
1414
  });
1397
1415
  }
@@ -1779,6 +1797,16 @@ function isCode(error, code) {
1779
1797
  // src/ui/clipboard-image.ts
1780
1798
  var CLIPBOARD_TIMEOUT_MS = 1e4;
1781
1799
  var NO_IMAGE_EXIT_CODE = 3;
1800
+ var NO_TEXT_EXIT_CODE = 3;
1801
+ var CLIPBOARD_IMAGE_MAX_BUFFER = Math.ceil(DEFAULT_MAX_IMAGE_BYTES / 3) * 4 + 64 * 1024;
1802
+ var READ_WINDOWS_CLIPBOARD_TEXT = String.raw`
1803
+ Add-Type -AssemblyName System.Windows.Forms
1804
+ if (-not [Windows.Forms.Clipboard]::ContainsText()) { exit 3 }
1805
+ $text = [Windows.Forms.Clipboard]::GetText()
1806
+ if ([string]::IsNullOrEmpty($text)) { exit 3 }
1807
+ $bytes = [System.Text.Encoding]::UTF8.GetBytes($text)
1808
+ [Console]::Out.Write([Convert]::ToBase64String($bytes))
1809
+ `.trim();
1782
1810
  var READ_WINDOWS_CLIPBOARD_IMAGE = String.raw`
1783
1811
  Add-Type -AssemblyName System.Windows.Forms
1784
1812
  Add-Type -AssemblyName System.Drawing
@@ -1843,8 +1871,47 @@ try {
1843
1871
  $.exit(3);
1844
1872
  }
1845
1873
  `.trim();
1846
- function shouldReadClipboardImage(input, key, isPasted) {
1847
- return (key.ctrl || key.meta === true) && input.toLowerCase() === "v" || isPasted && input.length === 0;
1874
+ function clipboardPasteIntent(input, key, isPasted, platform = process.platform) {
1875
+ if (isPasted) return input.length === 0 ? "immediate" : void 0;
1876
+ const pasteModifier = platform === "darwin" ? key.super === true : key.ctrl;
1877
+ return pasteModifier && input.toLowerCase() === "v" ? "deferred" : void 0;
1878
+ }
1879
+ async function readClipboardContent(options = {}) {
1880
+ const text = await readClipboardText(options);
1881
+ if (text !== void 0) return { kind: "text", text };
1882
+ const attachment = await readClipboardImage(options);
1883
+ return attachment === void 0 ? void 0 : { kind: "image", attachment };
1884
+ }
1885
+ async function readClipboardText(options = {}) {
1886
+ const platform = options.platform ?? process.platform;
1887
+ if (platform === "win32") return readWindowsClipboardText({ ...options, platform });
1888
+ if (platform === "darwin") return readMacClipboardText({ ...options, platform });
1889
+ throw new Error("Pasting native clipboard content is currently supported on Windows and macOS only");
1890
+ }
1891
+ async function readWindowsClipboardText(options = {}) {
1892
+ const platform = options.platform ?? process.platform;
1893
+ if (platform !== "win32") throw new Error("Reading clipboard text this way is supported on Windows only");
1894
+ const result = await (options.run ?? execFileNoThrow)(
1895
+ "powershell.exe",
1896
+ ["-NoProfile", "-NonInteractive", "-STA", "-Command", READ_WINDOWS_CLIPBOARD_TEXT],
1897
+ { timeout: CLIPBOARD_TIMEOUT_MS, useCwd: false }
1898
+ );
1899
+ if (result.code === NO_TEXT_EXIT_CODE) return void 0;
1900
+ if (result.code !== 0) throw clipboardReadError("text", result);
1901
+ const encoded = result.stdout.trim();
1902
+ if (encoded.length === 0) return void 0;
1903
+ return Buffer.from(encoded, "base64").toString("utf8") || void 0;
1904
+ }
1905
+ async function readMacClipboardText(options = {}) {
1906
+ const platform = options.platform ?? process.platform;
1907
+ if (platform !== "darwin") throw new Error("Reading clipboard text this way is supported on macOS only");
1908
+ const result = await (options.run ?? execFileNoThrow)(
1909
+ "pbpaste",
1910
+ ["-Prefer", "txt"],
1911
+ { timeout: CLIPBOARD_TIMEOUT_MS, useCwd: false }
1912
+ );
1913
+ if (result.code !== 0 || result.stdout.length === 0) return void 0;
1914
+ return result.stdout;
1848
1915
  }
1849
1916
  async function readClipboardImage(options = {}) {
1850
1917
  const platform = options.platform ?? process.platform;
@@ -1860,7 +1927,7 @@ async function readWindowsClipboardImage(options = {}) {
1860
1927
  const result = await (options.run ?? execFileNoThrow)(
1861
1928
  "powershell.exe",
1862
1929
  ["-NoProfile", "-NonInteractive", "-STA", "-Command", READ_WINDOWS_CLIPBOARD_IMAGE],
1863
- { timeout: CLIPBOARD_TIMEOUT_MS, useCwd: false }
1930
+ { timeout: CLIPBOARD_TIMEOUT_MS, useCwd: false, maxBuffer: CLIPBOARD_IMAGE_MAX_BUFFER }
1864
1931
  );
1865
1932
  return attachmentFromResult(result, options.now?.() ?? /* @__PURE__ */ new Date());
1866
1933
  }
@@ -1872,15 +1939,14 @@ async function readMacClipboardImage(options = {}) {
1872
1939
  const result = await (options.run ?? execFileNoThrow)(
1873
1940
  "osascript",
1874
1941
  ["-l", "JavaScript", "-e", READ_MAC_CLIPBOARD_IMAGE],
1875
- { timeout: CLIPBOARD_TIMEOUT_MS, useCwd: false }
1942
+ { timeout: CLIPBOARD_TIMEOUT_MS, useCwd: false, maxBuffer: CLIPBOARD_IMAGE_MAX_BUFFER }
1876
1943
  );
1877
1944
  return attachmentFromResult(result, options.now?.() ?? /* @__PURE__ */ new Date());
1878
1945
  }
1879
1946
  function attachmentFromResult(result, now) {
1880
1947
  if (result.code === NO_IMAGE_EXIT_CODE) return void 0;
1881
1948
  if (result.code !== 0) {
1882
- const detail = result.stderr.trim() || result.error?.trim();
1883
- throw new Error(detail ? `Could not read the clipboard image: ${detail}` : "Could not read the clipboard image");
1949
+ throw clipboardReadError("image", result);
1884
1950
  }
1885
1951
  const dataBase64 = result.stdout.trim();
1886
1952
  if (dataBase64.length === 0) return void 0;
@@ -1890,6 +1956,10 @@ function attachmentFromResult(result, now) {
1890
1956
  dataBase64
1891
1957
  };
1892
1958
  }
1959
+ function clipboardReadError(kind, result) {
1960
+ const detail = result.stderr.trim() || result.error?.trim();
1961
+ return new Error(detail ? `Could not read the clipboard ${kind}: ${detail}` : `Could not read the clipboard ${kind}`);
1962
+ }
1893
1963
  function timestamp(value) {
1894
1964
  const year = value.getUTCFullYear();
1895
1965
  const month = String(value.getUTCMonth() + 1).padStart(2, "0");
@@ -1946,6 +2016,7 @@ var PromptHistoryStore = class {
1946
2016
  // src/ui/app.tsx
1947
2017
  import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1948
2018
  var HISTORY_CAP = 200;
2019
+ var CLIPBOARD_SHORTCUT_FALLBACK_MS = 300;
1949
2020
  var BUILTIN_SLASH_CANDIDATES = MVP_COMMANDS.map((name) => ({ name, description: COMMAND_DESCRIPTIONS[name] }));
1950
2021
  var PROMPT_HORIZONTAL_PADDING = 1;
1951
2022
  function clonePromptDraft(state) {
@@ -1997,16 +2068,24 @@ function isPlatformShortcut(character, key, letter, platform = process.platform)
1997
2068
  if (character.toLowerCase() !== letter.toLowerCase()) return false;
1998
2069
  return platform === "darwin" ? key.super || key.ctrl : key.ctrl;
1999
2070
  }
2071
+ function isOutputToggleShortcut(character, key) {
2072
+ return key.ctrl && character.toLowerCase() === "o";
2073
+ }
2074
+ function outputToggleShortcut(_platform = process.platform) {
2075
+ return "Ctrl+O";
2076
+ }
2000
2077
  function removeLastCliImageOnBackspace(input, cursor, images) {
2001
2078
  if (input.length === 0 && cursor === 0 && images.length > 0) {
2002
2079
  return { handled: true, images: images.slice(0, -1) };
2003
2080
  }
2004
2081
  return { handled: false, images: [...images] };
2005
2082
  }
2006
- function prepareCliSubmission(input, images) {
2083
+ function prepareCliSubmission(input, images, slashCommands = MVP_COMMANDS) {
2007
2084
  const entered = input.trim();
2008
2085
  if (entered.length === 0 && images.length === 0) return { kind: "empty" };
2009
- if (images.length > 0 && entered.startsWith("/")) {
2086
+ const slashName = /^\/(\S+)/u.exec(entered)?.[1]?.toLowerCase();
2087
+ const isSlashCommand = slashName !== void 0 && slashCommands.some((command) => command.toLowerCase() === slashName);
2088
+ if (images.length > 0 && isSlashCommand) {
2010
2089
  return { kind: "error", message: "Image attachments cannot be used with slash commands." };
2011
2090
  }
2012
2091
  const text = entered || "Analyze the attached image(s).";
@@ -2123,11 +2202,13 @@ async function runTerminalSubmissionChain(options) {
2123
2202
  if (prompt !== void 0) options.onPendingConsumed(options.pending.values);
2124
2203
  }
2125
2204
  }
2126
- function classifyTerminalInput(key) {
2205
+ function classifyTerminalInput(key, activeSession = false) {
2127
2206
  if (key.wheelUp) return { type: "scroll", rows: -3 };
2128
2207
  if (key.wheelDown) return { type: "scroll", rows: 3 };
2129
2208
  if (key.pageUp) return { type: "page", fraction: -0.5 };
2130
2209
  if (key.pageDown) return { type: "page", fraction: 0.5 };
2210
+ if (activeSession && key.upArrow) return { type: "scroll", rows: -3 };
2211
+ if (activeSession && key.downArrow) return { type: "scroll", rows: 3 };
2131
2212
  if (key.upArrow) return { type: "history", direction: "up" };
2132
2213
  if (key.downArrow) return { type: "history", direction: "down" };
2133
2214
  return null;
@@ -2200,6 +2281,8 @@ function App({ workspace, home, resumeSession, instanceId, palAlias }) {
2200
2281
  pendingPromptRef.current ??= new PendingPromptQueue();
2201
2282
  const closing = useRef4(false);
2202
2283
  const clipboardBusy = useRef4(false);
2284
+ const clipboardFallbackTimer = useRef4(null);
2285
+ const terminalTextPasteRevision = useRef4(0);
2203
2286
  const textBuf = useRef4({ pending: "", timer: null });
2204
2287
  const thinkingBuf = useRef4({ pending: "", timer: null });
2205
2288
  const promptDraftRef = useRef4({
@@ -2216,6 +2299,9 @@ function App({ workspace, home, resumeSession, instanceId, palAlias }) {
2216
2299
  const promptHistoryStoreRef = useRef4(void 0);
2217
2300
  promptHistoryStoreRef.current ??= new PromptHistoryStore({ home: home ?? homedir(), workspace, maxEntries: HISTORY_CAP });
2218
2301
  promptDraftRef.current = { text: input, cursor: promptCursor, pastedBlocks, imageAttachments };
2302
+ useEffect3(() => () => {
2303
+ if (clipboardFallbackTimer.current !== null) clearTimeout(clipboardFallbackTimer.current);
2304
+ }, []);
2219
2305
  const commitPromptDraft = (next, recordHistory = true) => {
2220
2306
  const current = promptDraftRef.current;
2221
2307
  const resolved = {
@@ -2459,7 +2545,10 @@ function App({ workspace, home, resumeSession, instanceId, palAlias }) {
2459
2545
  setCustomQuestionActive(false);
2460
2546
  }, [questions]);
2461
2547
  useEffect3(() => setApprovalExpanded(false), [approval?.id]);
2462
- const derivedSlashCompletion = deriveSlashCompletion(input, promptCursor, slashCandidates, slashSelection);
2548
+ const derivedSlashCompletion = useMemo3(
2549
+ () => deriveSlashCompletion(input, promptCursor, slashCandidates, slashSelection),
2550
+ [input, promptCursor, slashCandidates, slashSelection]
2551
+ );
2463
2552
  const slashCompletion = canShowSlashCompletion(
2464
2553
  transcript.active !== void 0,
2465
2554
  input,
@@ -2477,27 +2566,43 @@ function App({ workspace, home, resumeSession, instanceId, palAlias }) {
2477
2566
  setMentionSelection(0);
2478
2567
  setDismissedMentionInput(next.text);
2479
2568
  };
2480
- const pasteClipboardImage = async () => {
2481
- const active = runtimeRef.current;
2482
- if (active === void 0) {
2483
- setClipboardNotice("Flavor is still starting; try pasting the image again.");
2484
- return;
2485
- }
2486
- if (promptDraftRef.current.imageAttachments.length >= DEFAULT_MAX_IMAGES) {
2487
- setClipboardNotice(`A prompt can contain at most ${DEFAULT_MAX_IMAGES} images.`);
2488
- return;
2489
- }
2569
+ const pasteClipboardContent = async (imageOnly = false) => {
2490
2570
  if (clipboardBusy.current) return;
2491
2571
  clipboardBusy.current = true;
2492
- setClipboardNotice("Reading image from clipboard...");
2572
+ const pasteRevision = terminalTextPasteRevision.current;
2573
+ setClipboardNotice(imageOnly ? "Reading clipboard image..." : "Reading clipboard...");
2493
2574
  try {
2494
- const attachment = await readClipboardImage();
2495
- if (attachment === void 0) {
2496
- setClipboardNotice("Clipboard does not contain an image.");
2575
+ const content = imageOnly ? await readClipboardImage().then((attachment2) => attachment2 === void 0 ? void 0 : { kind: "image", attachment: attachment2 }) : await readClipboardContent();
2576
+ if (pasteRevision !== terminalTextPasteRevision.current) {
2577
+ setClipboardNotice(void 0);
2578
+ return;
2579
+ }
2580
+ if (content === void 0) {
2581
+ setClipboardNotice(imageOnly ? "Clipboard does not contain an image." : "Clipboard does not contain pasteable text or an image.");
2582
+ return;
2583
+ }
2584
+ if (content.kind === "text") {
2585
+ const currentDraft2 = promptDraftRef.current;
2586
+ const nextPastedBlocks = /[\r\n]/u.test(content.text) ? [...currentDraft2.pastedBlocks, { id: currentDraft2.pastedBlocks.length + 1, text: content.text }] : currentDraft2.pastedBlocks;
2587
+ commitPromptDraft({
2588
+ ...editPrompt({ text: currentDraft2.text, cursor: currentDraft2.cursor }, { type: "insert", value: content.text }),
2589
+ pastedBlocks: nextPastedBlocks
2590
+ });
2591
+ setClipboardNotice(void 0);
2592
+ return;
2593
+ }
2594
+ const active = runtimeRef.current;
2595
+ if (active === void 0) {
2596
+ setClipboardNotice("Flavor is still starting; try pasting the image again.");
2497
2597
  return;
2498
2598
  }
2499
- const incomingBytes = Buffer.byteLength(attachment.dataBase64, "base64");
2500
2599
  const currentDraft = promptDraftRef.current;
2600
+ if (currentDraft.imageAttachments.length >= DEFAULT_MAX_IMAGES) {
2601
+ setClipboardNotice(`A prompt can contain at most ${DEFAULT_MAX_IMAGES} images.`);
2602
+ return;
2603
+ }
2604
+ const attachment = content.attachment;
2605
+ const incomingBytes = Buffer.byteLength(attachment.dataBase64, "base64");
2501
2606
  const currentBytes = currentDraft.imageAttachments.reduce((sum, image) => sum + image.bytes, 0);
2502
2607
  if (currentBytes + incomingBytes > DEFAULT_MAX_TOTAL_IMAGE_BYTES) {
2503
2608
  throw new Error(`Image attachments exceed the maximum total size of ${DEFAULT_MAX_TOTAL_IMAGE_BYTES} bytes`);
@@ -2513,7 +2618,10 @@ function App({ workspace, home, resumeSession, instanceId, palAlias }) {
2513
2618
  }
2514
2619
  };
2515
2620
  use_input_default((character, key, event) => {
2516
- const terminalAction = classifyTerminalInput(key);
2621
+ const terminalAction = classifyTerminalInput(
2622
+ key,
2623
+ transcript.active !== void 0 && slashCompletion === null && mentionCompletion === null
2624
+ );
2517
2625
  if (terminalAction?.type === "scroll") {
2518
2626
  const scroll = selectWheelScrollTarget(
2519
2627
  scrollRef.current,
@@ -2534,7 +2642,7 @@ function App({ workspace, home, resumeSession, instanceId, palAlias }) {
2534
2642
  }
2535
2643
  const active = runtimeRef.current;
2536
2644
  if (isCopyShortcut(character, key)) {
2537
- if (selection.hasSelection()) selection.copySelection();
2645
+ if (selection.hasSelection()) selection.copySelectionNoClear();
2538
2646
  else if (process.platform !== "darwin") interrupt();
2539
2647
  return;
2540
2648
  }
@@ -2542,13 +2650,30 @@ function App({ workspace, home, resumeSession, instanceId, palAlias }) {
2542
2650
  interrupt();
2543
2651
  return;
2544
2652
  }
2545
- if (isPlatformShortcut(character, key, "o")) {
2653
+ if (isOutputToggleShortcut(character, key)) {
2546
2654
  setExpandedOutput((value) => !value);
2547
- scrollRef.current?.scrollToBottom();
2548
2655
  return;
2549
2656
  }
2550
- if (shouldReadClipboardImage(character, key, event.keypress.isPasted)) {
2551
- void pasteClipboardImage();
2657
+ if (event.keypress.isPasted && character.length > 0) {
2658
+ terminalTextPasteRevision.current += 1;
2659
+ if (clipboardFallbackTimer.current !== null) {
2660
+ clearTimeout(clipboardFallbackTimer.current);
2661
+ clipboardFallbackTimer.current = null;
2662
+ }
2663
+ }
2664
+ const pasteIntent = clipboardPasteIntent(character, key, event.keypress.isPasted);
2665
+ if (pasteIntent === "immediate") {
2666
+ if (clipboardFallbackTimer.current !== null) clearTimeout(clipboardFallbackTimer.current);
2667
+ clipboardFallbackTimer.current = null;
2668
+ void pasteClipboardContent();
2669
+ return;
2670
+ }
2671
+ if (pasteIntent === "deferred") {
2672
+ if (clipboardFallbackTimer.current !== null) clearTimeout(clipboardFallbackTimer.current);
2673
+ clipboardFallbackTimer.current = setTimeout(() => {
2674
+ clipboardFallbackTimer.current = null;
2675
+ void pasteClipboardContent();
2676
+ }, CLIPBOARD_SHORTCUT_FALLBACK_MS);
2552
2677
  return;
2553
2678
  }
2554
2679
  if (active?.approvals.pending !== void 0) {
@@ -2706,8 +2831,19 @@ function App({ workspace, home, resumeSession, instanceId, palAlias }) {
2706
2831
  return;
2707
2832
  }
2708
2833
  if (key.return) {
2834
+ if (/^\/paste-image\s*$/iu.test(input)) {
2835
+ setInput("");
2836
+ setPromptCursor(0);
2837
+ setPastedBlocks([]);
2838
+ promptDraftRef.current = { text: "", cursor: 0, pastedBlocks: [], imageAttachments };
2839
+ promptEditHistory.current.reset();
2840
+ setSlashSelection(0);
2841
+ setDismissedSlashInput(void 0);
2842
+ void pasteClipboardContent(true);
2843
+ return;
2844
+ }
2709
2845
  const images = [...imageAttachments];
2710
- const prepared = prepareCliSubmission(input, images);
2846
+ const prepared = prepareCliSubmission(input, images, slashCandidates.map(({ name }) => name));
2711
2847
  if (prepared.kind === "empty" || active === void 0) return;
2712
2848
  if (prepared.kind === "error") {
2713
2849
  setClipboardNotice(prepared.message);
@@ -2914,6 +3050,25 @@ var CLI_VISIBLE_TURN_LIMIT = 40;
2914
3050
  var CLI_VISIBLE_BLOCK_LIMIT = 320;
2915
3051
  var CLI_VISIBLE_BLOCKS_PER_TURN = 80;
2916
3052
  var CLI_VISIBLE_TEXT_CHARS = 32e3;
3053
+ function cliRenderBudget(rows, columns) {
3054
+ const viewportRows = Math.max(8, Math.floor(rows));
3055
+ const viewportColumns = Math.max(20, Math.floor(columns));
3056
+ const screenMultiplier = 4;
3057
+ const blockMultiplier = 2;
3058
+ const blocks = Math.min(
3059
+ CLI_VISIBLE_BLOCK_LIMIT,
3060
+ Math.max(24, viewportRows * blockMultiplier)
3061
+ );
3062
+ return {
3063
+ turns: Math.min(16, Math.max(4, Math.ceil(viewportRows / 4))),
3064
+ blocks,
3065
+ blocksPerTurn: Math.min(CLI_VISIBLE_BLOCKS_PER_TURN, blocks),
3066
+ textChars: Math.min(
3067
+ 12e3,
3068
+ Math.max(4e3, viewportRows * viewportColumns * screenMultiplier)
3069
+ )
3070
+ };
3071
+ }
2917
3072
  function approvalDetailLines(approval) {
2918
3073
  const lines = [];
2919
3074
  if (approval.agent !== "main") lines.push(`Agent: ${approval.agent}`);
@@ -2963,25 +3118,32 @@ function boundedCliDisplayText(text, limit = CLI_VISIBLE_TEXT_CHARS) {
2963
3118
 
2964
3119
  ${text.slice(-half)}`;
2965
3120
  }
2966
- function boundedCliPresentation(presentation) {
3121
+ function boundedCliPresentation(presentation, maxTextChars = CLI_VISIBLE_TEXT_CHARS) {
2967
3122
  if (presentation.kind === "terminal") return {
2968
3123
  ...presentation,
2969
- ...presentation.stdout === void 0 ? {} : { stdout: boundedCliDisplayText(presentation.stdout) },
2970
- ...presentation.stderr === void 0 ? {} : { stderr: boundedCliDisplayText(presentation.stderr) }
3124
+ ...presentation.stdout === void 0 ? {} : { stdout: boundedCliDisplayText(presentation.stdout, maxTextChars) },
3125
+ ...presentation.stderr === void 0 ? {} : { stderr: boundedCliDisplayText(presentation.stderr, maxTextChars) }
2971
3126
  };
2972
3127
  if (presentation.kind === "job" && presentation.output !== void 0) return {
2973
3128
  ...presentation,
2974
- output: boundedCliDisplayText(presentation.output)
3129
+ output: boundedCliDisplayText(presentation.output, maxTextChars)
2975
3130
  };
2976
3131
  return presentation;
2977
3132
  }
2978
- function boundedCliTurn(turn, maxBlocks = CLI_VISIBLE_BLOCKS_PER_TURN) {
3133
+ function boundedCliTurn(turn, maxBlocks = CLI_VISIBLE_BLOCKS_PER_TURN, maxTextChars = CLI_VISIBLE_TEXT_CHARS) {
2979
3134
  const blockLimit = Math.max(1, Math.floor(maxBlocks));
2980
3135
  const hiddenBlocks = Math.max(0, turn.blocks.length - blockLimit);
2981
- const visible = turn.blocks.slice(-blockLimit).map((block) => block.kind === "text" ? { ...block, text: boundedCliDisplayText(block.text) } : {
3136
+ const visible = turn.blocks.slice(-blockLimit).map((block) => block.kind === "text" ? { ...block, text: boundedCliDisplayText(block.text, maxTextChars) } : {
2982
3137
  ...block,
2983
- ...block.details === void 0 ? {} : { details: boundedCliDisplayText(block.details) },
2984
- ...block.presentation === void 0 ? {} : { presentation: boundedCliPresentation(block.presentation) }
3138
+ ...block.details === void 0 ? {} : {
3139
+ details: boundedCliDisplayText(
3140
+ block.details,
3141
+ block.tool !== void 0 || block.presentation !== void 0 ? CLI_VISIBLE_TEXT_CHARS : maxTextChars
3142
+ )
3143
+ },
3144
+ ...block.presentation === void 0 ? {} : {
3145
+ presentation: boundedCliPresentation(block.presentation, CLI_VISIBLE_TEXT_CHARS)
3146
+ }
2985
3147
  });
2986
3148
  const blocks = hiddenBlocks === 0 ? visible : [{
2987
3149
  kind: "status",
@@ -2999,7 +3161,7 @@ function boundedCliTurn(turn, maxBlocks = CLI_VISIBLE_BLOCKS_PER_TURN) {
2999
3161
  }
3000
3162
  };
3001
3163
  }
3002
- function cliTranscriptWindow(completed, maxTurns = CLI_VISIBLE_TURN_LIMIT, maxBlocks = CLI_VISIBLE_BLOCK_LIMIT) {
3164
+ function cliTranscriptWindow(completed, maxTurns = CLI_VISIBLE_TURN_LIMIT, maxBlocks = CLI_VISIBLE_BLOCK_LIMIT, maxTextChars = CLI_VISIBLE_TEXT_CHARS) {
3003
3165
  const turns = [];
3004
3166
  let remainingBlocks = Math.max(1, Math.floor(maxBlocks));
3005
3167
  let hiddenBlocks = 0;
@@ -3007,7 +3169,7 @@ function cliTranscriptWindow(completed, maxTurns = CLI_VISIBLE_TURN_LIMIT, maxBl
3007
3169
  const source = completed[index];
3008
3170
  if (remainingBlocks <= 0) break;
3009
3171
  const allowance = Math.min(CLI_VISIBLE_BLOCKS_PER_TURN, remainingBlocks);
3010
- const bounded = boundedCliTurn(source, allowance);
3172
+ const bounded = boundedCliTurn(source, allowance, maxTextChars);
3011
3173
  turns.push(bounded.turn);
3012
3174
  hiddenBlocks += bounded.hiddenBlocks;
3013
3175
  remainingBlocks -= Math.min(source.blocks.length, allowance);
@@ -3054,24 +3216,31 @@ function TerminalLayout({
3054
3216
  ideContext
3055
3217
  }) {
3056
3218
  const dividerWidth = Math.max(1, columns - 1);
3219
+ const outputShortcut = outputToggleShortcut();
3057
3220
  const showWelcome = completed.length === 0 && active === void 0;
3058
- const activeCompletion = completion ?? mentionCompletion;
3059
- const menuRows = activeCompletion === void 0 ? 0 : Math.min(6, activeCompletion.items.length - activeCompletion.windowStart);
3060
- const activeTaskBlocks = active?.blocks.filter(
3221
+ const budget = useMemo3(
3222
+ () => cliRenderBudget(rows, columns),
3223
+ [rows, columns]
3224
+ );
3225
+ const activeTaskBlocks = useMemo3(() => active?.blocks.filter(
3061
3226
  (block) => block.kind === "status" && block.task !== void 0
3062
- ) ?? [];
3063
- const rawActiveWithoutTasks = active === void 0 ? void 0 : {
3064
- ...active,
3065
- blocks: active.blocks.filter(
3066
- (block) => !(block.kind === "status" && block.task !== void 0)
3067
- )
3068
- };
3069
- const activeWithoutTasks = rawActiveWithoutTasks === void 0 ? void 0 : expandedOutput ? rawActiveWithoutTasks : boundedCliTurn(rawActiveWithoutTasks).turn;
3070
- const completedWindow = expandedOutput ? { turns: completed, hiddenTurns: 0, hiddenBlocks: 0 } : cliTranscriptWindow(completed);
3227
+ ) ?? [], [active]);
3228
+ const activeWithoutTasks = useMemo3(() => {
3229
+ if (active === void 0) return void 0;
3230
+ const raw = {
3231
+ ...active,
3232
+ blocks: active.blocks.filter((block) => !(block.kind === "status" && block.task !== void 0))
3233
+ };
3234
+ return boundedCliTurn(raw, budget.blocksPerTurn, budget.textChars).turn;
3235
+ }, [active, budget]);
3236
+ const completedWindow = useMemo3(
3237
+ () => cliTranscriptWindow(completed, budget.turns, budget.blocks, budget.textChars),
3238
+ [completed, budget]
3239
+ );
3071
3240
  const questionRows = questions === void 0 ? 0 : 4 + (questions[questionIndex]?.options.length ?? 0) + questions.length * 2;
3072
3241
  const memoryReviewRows = memoryReviews.length === 0 ? 0 : 5;
3073
3242
  const approvalRows = approval === void 0 ? 0 : 3 + (approvalExpanded ? approvalDetailLines(approval).length : 0);
3074
- const fixedBottomRows = approvalRows + questionRows + memoryReviewRows + menuRows + (pendingPrompts.length === 0 ? 0 : 1) + imageAttachments.length + (clipboardNotice === void 0 ? 0 : 1) + 2;
3243
+ const fixedBottomRows = approvalRows + questionRows + memoryReviewRows + (pendingPrompts.length === 0 ? 0 : 1) + imageAttachments.length + (clipboardNotice === void 0 ? 0 : 1) + 2;
3075
3244
  const taskPanelRows = taskPanelViewportRows(rows, fixedBottomRows, activeTaskBlocks.length > 0);
3076
3245
  const availableBottomRows = Math.max(1, rows - taskPanelRows - 1);
3077
3246
  const bottomMaxRows = Math.min(availableBottomRows, Math.max(Math.floor(rows / 2), fixedBottomRows + 1));
@@ -3090,7 +3259,7 @@ function TerminalLayout({
3090
3259
  completedWindow.hiddenTurns,
3091
3260
  " earlier turns and ",
3092
3261
  completedWindow.hiddenBlocks,
3093
- " output items hidden \xB7 Ctrl/Cmd+O expand all"
3262
+ " output items hidden to keep the terminal responsive"
3094
3263
  ] }) : null,
3095
3264
  completedWindow.turns.map((turn, index) => /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "column", children: [
3096
3265
  index > 0 ? /* @__PURE__ */ jsx5(TurnSeparator, { width: columns }) : null,
@@ -3113,82 +3282,86 @@ function TerminalLayout({
3113
3282
  ...onTaskPanelHoverChange === void 0 ? {} : { onHoverChange: onTaskPanelHoverChange }
3114
3283
  }
3115
3284
  ),
3116
- /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "column", flexShrink: 0, maxHeight: bottomMaxRows, width: "100%", overflowY: "hidden", children: [
3117
- approval === void 0 ? null : /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "column", marginBottom: 1, children: [
3118
- /* @__PURE__ */ jsxs4(Text, { color: "magenta", children: [
3119
- "\u250C\u2500 approval \xB7 ",
3120
- approval.tool
3285
+ /* @__PURE__ */ jsxs4(Box_default, { position: "relative", flexShrink: 0, width: "100%", children: [
3286
+ completion === void 0 && mentionCompletion === void 0 ? null : /* @__PURE__ */ jsxs4(Box_default, { position: "absolute", bottom: "100%", left: 0, right: 0, flexDirection: "column", opaque: true, children: [
3287
+ completion === void 0 ? null : /* @__PURE__ */ jsx5(SlashMenu, { completion }),
3288
+ mentionCompletion === void 0 ? null : /* @__PURE__ */ jsx5(MentionMenu, { completion: mentionCompletion, ...onMentionSelect === void 0 ? {} : { onSelect: onMentionSelect } })
3289
+ ] }),
3290
+ /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "column", flexShrink: 0, maxHeight: bottomMaxRows, width: "100%", overflowY: "hidden", children: [
3291
+ approval === void 0 ? null : /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "column", marginBottom: 1, children: [
3292
+ /* @__PURE__ */ jsxs4(Text, { color: "magenta", children: [
3293
+ "\u250C\u2500 approval \xB7 ",
3294
+ approval.tool
3295
+ ] }),
3296
+ /* @__PURE__ */ jsxs4(Text, { wrap: "truncate-end", color: "magentaBright", children: [
3297
+ "\u2502 ",
3298
+ approval.reason ?? "This action needs permission."
3299
+ ] }),
3300
+ approvalExpanded ? approvalDetailLines(approval).map((line, index) => /* @__PURE__ */ jsxs4(Text, { color: "magentaBright", wrap: "truncate-end", children: [
3301
+ "\u2502 ",
3302
+ line
3303
+ ] }, `${approval.id}:detail:${index}`)) : null,
3304
+ isDestructiveTool(approval.tool) || approval.allowAlways === false ? /* @__PURE__ */ jsxs4(Text, { bold: true, color: "magenta", children: [
3305
+ "\u2514\u2500 ",
3306
+ /* @__PURE__ */ jsx5(Text, { color: "cyan", children: "v" }),
3307
+ "=details / ",
3308
+ /* @__PURE__ */ jsx5(Text, { color: "green", children: "y" }),
3309
+ "=once / ",
3310
+ /* @__PURE__ */ jsx5(Text, { color: "cyan", children: "e" }),
3311
+ "=accept edits / ",
3312
+ /* @__PURE__ */ jsx5(Text, { color: "red", children: "n" }),
3313
+ "=deny"
3314
+ ] }) : /* @__PURE__ */ jsxs4(Text, { bold: true, color: "magenta", children: [
3315
+ "\u2514\u2500 ",
3316
+ /* @__PURE__ */ jsx5(Text, { color: "cyan", children: "v" }),
3317
+ "=details / ",
3318
+ /* @__PURE__ */ jsx5(Text, { color: "green", children: "y" }),
3319
+ "=once / ",
3320
+ /* @__PURE__ */ jsx5(Text, { color: "yellow", children: "a" }),
3321
+ "=same-type / ",
3322
+ /* @__PURE__ */ jsx5(Text, { color: "cyan", children: "e" }),
3323
+ "=accept edits / ",
3324
+ /* @__PURE__ */ jsx5(Text, { color: "red", children: "n" }),
3325
+ "=deny"
3326
+ ] })
3121
3327
  ] }),
3122
- /* @__PURE__ */ jsxs4(Text, { wrap: "truncate-end", color: "magentaBright", children: [
3123
- "\u2502 ",
3124
- approval.reason ?? "This action needs permission."
3328
+ !questions || questions.length === 0 ? null : /* @__PURE__ */ jsx5(QuestionCards, { questions, activeIndex: questionIndex, answers: questionAnswers, customActive: customQuestionActive }),
3329
+ memoryReviews.length === 0 ? null : /* @__PURE__ */ jsx5(MemoryReviewCards, { reviews: memoryReviews, autoDismissSeconds: memoryAutoDismissSeconds }),
3330
+ pendingPrompts.length === 0 ? null : /* @__PURE__ */ jsxs4(Text, { color: "yellow", wrap: "truncate-end", children: [
3331
+ "Pending (",
3332
+ pendingPrompts.length,
3333
+ ") \xB7 ",
3334
+ queuedPromptLabel(pendingPrompts.at(-1)),
3335
+ " \xB7 Esc edit latest"
3125
3336
  ] }),
3126
- approvalExpanded ? approvalDetailLines(approval).map((line, index) => /* @__PURE__ */ jsxs4(Text, { color: "magentaBright", wrap: "truncate-end", children: [
3127
- "\u2502 ",
3128
- line
3129
- ] }, `${approval.id}:detail:${index}`)) : null,
3130
- isDestructiveTool(approval.tool) || approval.allowAlways === false ? /* @__PURE__ */ jsxs4(Text, { bold: true, color: "magenta", children: [
3131
- "\u2514\u2500 ",
3132
- /* @__PURE__ */ jsx5(Text, { color: "cyan", children: "v" }),
3133
- "=details / ",
3134
- /* @__PURE__ */ jsx5(Text, { color: "green", children: "y" }),
3135
- "=once / ",
3136
- /* @__PURE__ */ jsx5(Text, { color: "cyan", children: "e" }),
3137
- "=accept edits / ",
3138
- /* @__PURE__ */ jsx5(Text, { color: "red", children: "n" }),
3139
- "=deny"
3140
- ] }) : /* @__PURE__ */ jsxs4(Text, { bold: true, color: "magenta", children: [
3141
- "\u2514\u2500 ",
3142
- /* @__PURE__ */ jsx5(Text, { color: "cyan", children: "v" }),
3143
- "=details / ",
3144
- /* @__PURE__ */ jsx5(Text, { color: "green", children: "y" }),
3145
- "=once / ",
3146
- /* @__PURE__ */ jsx5(Text, { color: "yellow", children: "a" }),
3147
- "=same-type / ",
3148
- /* @__PURE__ */ jsx5(Text, { color: "cyan", children: "e" }),
3149
- "=accept edits / ",
3150
- /* @__PURE__ */ jsx5(Text, { color: "red", children: "n" }),
3151
- "=deny"
3152
- ] })
3153
- ] }),
3154
- !questions || questions.length === 0 ? null : /* @__PURE__ */ jsx5(QuestionCards, { questions, activeIndex: questionIndex, answers: questionAnswers, customActive: customQuestionActive }),
3155
- memoryReviews.length === 0 ? null : /* @__PURE__ */ jsx5(MemoryReviewCards, { reviews: memoryReviews, autoDismissSeconds: memoryAutoDismissSeconds }),
3156
- completion === void 0 ? null : /* @__PURE__ */ jsx5(SlashMenu, { completion }),
3157
- mentionCompletion === void 0 ? null : /* @__PURE__ */ jsx5(MentionMenu, { completion: mentionCompletion, ...onMentionSelect === void 0 ? {} : { onSelect: onMentionSelect } }),
3158
- pendingPrompts.length === 0 ? null : /* @__PURE__ */ jsxs4(Text, { color: "yellow", wrap: "truncate-end", children: [
3159
- "Pending (",
3160
- pendingPrompts.length,
3161
- ") \xB7 ",
3162
- queuedPromptLabel(pendingPrompts.at(-1)),
3163
- " \xB7 Esc edit latest"
3164
- ] }),
3165
- /* @__PURE__ */ jsx5(Text, { dimColor: true, children: "\u2500".repeat(dividerWidth) }),
3166
- imageAttachments.map((image, index) => /* @__PURE__ */ jsxs4(Text, { color: "cyan", wrap: "truncate-end", children: [
3167
- "[Image #",
3168
- index + 1,
3169
- "] ",
3170
- image.name ?? basename3(image.source.path)
3171
- ] }, `${image.sha256}:${index}`)),
3172
- clipboardNotice === void 0 ? null : /* @__PURE__ */ jsx5(Text, { color: "yellow", wrap: "truncate-end", children: clipboardNotice }),
3173
- /* @__PURE__ */ jsx5(
3174
- PromptLine,
3175
- {
3176
- input,
3177
- pastedBlocks,
3178
- cursor: promptCursor,
3179
- columns,
3180
- maxVisibleLines: promptMaxLines,
3181
- completedSlashTokenLength: tokenLength,
3182
- ...onPromptCursorChange === void 0 ? {} : { onCursorChange: onPromptCursorChange }
3183
- }
3184
- ),
3185
- /* @__PURE__ */ jsx5(
3186
- FooterStatus,
3187
- {
3188
- hint: activeSession ? `Esc ${pendingPrompts.length > 0 ? "edit latest" : "stop"} \xB7 Ctrl+C cancel \xB7 Enter queue \xB7 Ctrl/Cmd+R history \xB7 Ctrl/Cmd+O ${expandedOutput ? "collapse" : "expand"} output` : completion !== void 0 ? "\u2191/\u2193 select \xB7 Tab complete \xB7 Esc close" : mentionCompletion !== void 0 ? "\u2191/\u2193 select \xB7 Tab complete \xB7 click choose \xB7 Esc close" : `Enter send \xB7 \u2191\u2193/Ctrl/Cmd+R history \xB7 Ctrl/Cmd+O ${expandedOutput ? "collapse" : "expand"} output \xB7 Ctrl+C exit`,
3189
- ...ideContext === void 0 ? {} : { ideContext }
3190
- }
3191
- )
3337
+ /* @__PURE__ */ jsx5(Text, { dimColor: true, children: "\u2500".repeat(dividerWidth) }),
3338
+ imageAttachments.map((image, index) => /* @__PURE__ */ jsxs4(Text, { color: "cyan", wrap: "truncate-end", children: [
3339
+ "[Image #",
3340
+ index + 1,
3341
+ "] ",
3342
+ image.name ?? basename3(image.source.path)
3343
+ ] }, `${image.sha256}:${index}`)),
3344
+ clipboardNotice === void 0 ? null : /* @__PURE__ */ jsx5(Text, { color: "yellow", wrap: "truncate-end", children: clipboardNotice }),
3345
+ /* @__PURE__ */ jsx5(
3346
+ PromptLine,
3347
+ {
3348
+ input,
3349
+ pastedBlocks,
3350
+ cursor: promptCursor,
3351
+ columns,
3352
+ maxVisibleLines: promptMaxLines,
3353
+ completedSlashTokenLength: tokenLength,
3354
+ ...onPromptCursorChange === void 0 ? {} : { onCursorChange: onPromptCursorChange }
3355
+ }
3356
+ ),
3357
+ /* @__PURE__ */ jsx5(
3358
+ FooterStatus,
3359
+ {
3360
+ hint: completion !== void 0 ? "\u2191/\u2193 select \xB7 Tab complete \xB7 Esc close" : mentionCompletion !== void 0 ? "\u2191/\u2193 select \xB7 Tab complete \xB7 click choose \xB7 Esc close" : activeSession ? `Esc ${pendingPrompts.length > 0 ? "edit latest" : "stop"} \xB7 Ctrl+C cancel \xB7 Enter queue \xB7 Ctrl/Cmd+R history \xB7 ${outputShortcut} ${expandedOutput ? "collapse" : "expand"} tools` : `Enter send \xB7 \u2191\u2193/Ctrl/Cmd+R history \xB7 ${outputShortcut} ${expandedOutput ? "collapse" : "expand"} tools \xB7 Ctrl+C exit`,
3361
+ ...ideContext === void 0 ? {} : { ideContext }
3362
+ }
3363
+ )
3364
+ ] })
3192
3365
  ] })
3193
3366
  ] });
3194
3367
  }
@@ -3290,7 +3463,7 @@ function SlashMenu({ completion }) {
3290
3463
  presentation.marker,
3291
3464
  /* @__PURE__ */ jsx5(HighlightedName, { name: candidate.name, query: completion.query, matchStyle: presentation.matchStyle }),
3292
3465
  candidate.kind === "skill" || candidate.kind === "plugin" ? /* @__PURE__ */ jsx5(Text, { color: candidate.kind === "skill" ? "magentaBright" : "cyanBright", children: ` ${candidate.kind}` }) : null,
3293
- candidate.description === void 0 ? null : /* @__PURE__ */ jsx5(Text, { dimColor: true, children: ` ${candidate.description}` })
3466
+ candidate.description === void 0 ? null : /* @__PURE__ */ jsx5(Text, { dimColor: true, children: ` ${normalizeSlashDescription(candidate.description)}` })
3294
3467
  ] }, `${candidate.kind}:${candidate.name}`);
3295
3468
  }) });
3296
3469
  }
@@ -3369,7 +3542,7 @@ function scrollUp(scroll, amount) {
3369
3542
  function TurnSeparator({ width }) {
3370
3543
  return /* @__PURE__ */ jsx5(Text, { dimColor: true, children: "\u2500".repeat(Math.max(1, width - 1)) });
3371
3544
  }
3372
- function TurnView({
3545
+ var TurnView = React4.memo(function TurnView2({
3373
3546
  turn,
3374
3547
  interactive,
3375
3548
  workspaceName,
@@ -3408,11 +3581,11 @@ function TurnView({
3408
3581
  ] }),
3409
3582
  /* @__PURE__ */ jsx5(Box_default, { flexDirection: "column", paddingLeft: 2, marginTop: 1, children: turn.blocks.map((block, index) => block.kind === "status" ? /* @__PURE__ */ jsx5(StatusBlockView, { block, interactive, workspaceName, expandedOutput }, block.id) : /* @__PURE__ */ jsx5(Box_default, { marginBottom: 1, children: /* @__PURE__ */ jsx5(AssistantText, { text: block.text }) }, `${turn.id}-text-${index}`)) })
3410
3583
  ] });
3411
- }
3584
+ });
3412
3585
  var DIFF_CONTENT = fileDiffLineStyle("context").contentColor;
3413
3586
  var DIFF_REMOVED_MARKER = fileDiffLineStyle("removed").markerColor;
3414
3587
  var DIFF_ADDED_MARKER = fileDiffLineStyle("added").markerColor;
3415
- function FileDiffView({ presentation }) {
3588
+ function FileDiffView({ presentation, expanded }) {
3416
3589
  const operation = presentation.operation === "create" ? "Create" : presentation.operation === "delete" ? "Delete" : "Update";
3417
3590
  if (presentation.operation === "delete") {
3418
3591
  return /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "row", children: [
@@ -3444,7 +3617,7 @@ function FileDiffView({ presentation }) {
3444
3617
  ", removed ",
3445
3618
  lineCount(presentation.removed)
3446
3619
  ] }),
3447
- presentation.lines.map((line, index) => /* @__PURE__ */ jsx5(FileDiffRow, { line, lineWidth }, `${line.kind}:${line.oldLine ?? ""}:${line.newLine ?? ""}:${index}`))
3620
+ (expanded ? presentation.lines : []).map((line, index) => /* @__PURE__ */ jsx5(FileDiffRow, { line, lineWidth }, `${line.kind}:${line.oldLine ?? ""}:${line.newLine ?? ""}:${index}`))
3448
3621
  ] });
3449
3622
  }
3450
3623
  function FileDiffRow({ line, lineWidth }) {
@@ -3590,6 +3763,7 @@ function StatusBlockView({
3590
3763
  }) {
3591
3764
  const visibleBlock = cliToolTitle(block);
3592
3765
  const outcome = cliToolOutcome(block);
3766
+ const toolOutput = block.tool !== void 0 || block.presentation !== void 0;
3593
3767
  const primary = visibleBlock.activity === "model" || visibleBlock.task !== void 0 ? /* @__PURE__ */ jsx5(TaskStatusLine, { block: visibleBlock, interactive }) : visibleBlock.state === "completed" && visibleBlock.presentation !== void 0 ? /* @__PURE__ */ jsx5(ToolPresentationView, { presentation: visibleBlock.presentation, workspaceName, expandedOutput }) : /* @__PURE__ */ jsx5(StatusLine, { block: visibleBlock, interactive });
3594
3768
  return /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "column", children: [
3595
3769
  primary,
@@ -3597,7 +3771,7 @@ function StatusBlockView({
3597
3771
  "\u2514 ",
3598
3772
  outcome
3599
3773
  ] }) }),
3600
- block.details === void 0 || block.presentation?.kind === "changeset" ? null : /* @__PURE__ */ jsx5(Box_default, { paddingLeft: 2, children: /* @__PURE__ */ jsx5(AssistantText, { text: block.details }) })
3774
+ block.details === void 0 || block.presentation?.kind === "changeset" || toolOutput && !expandedOutput ? null : /* @__PURE__ */ jsx5(Box_default, { paddingLeft: 2, children: /* @__PURE__ */ jsx5(AssistantText, { text: block.details }) })
3601
3775
  ] });
3602
3776
  }
3603
3777
  function ToolPresentationView({
@@ -3606,23 +3780,25 @@ function ToolPresentationView({
3606
3780
  expandedOutput
3607
3781
  }) {
3608
3782
  if (presentation.kind === "changeset") return /* @__PURE__ */ jsx5(ChangeSetPresentationView, { presentation, workspaceName, expanded: expandedOutput });
3609
- if (presentation.kind === "file-change") return /* @__PURE__ */ jsx5(FileDiffView, { presentation });
3783
+ if (presentation.kind === "file-change") return /* @__PURE__ */ jsx5(FileDiffView, { presentation, expanded: expandedOutput });
3610
3784
  if (presentation.kind === "terminal") return /* @__PURE__ */ jsx5(CommandPresentationView, { presentation, expanded: expandedOutput });
3611
3785
  if (presentation.kind === "web") return /* @__PURE__ */ jsx5(WebPresentationView, { presentation, expanded: expandedOutput });
3612
3786
  if (presentation.kind === "job") return /* @__PURE__ */ jsx5(JobPresentationView, { presentation, expanded: expandedOutput });
3613
- return /* @__PURE__ */ jsx5(Box_default, { paddingLeft: 2, children: /* @__PURE__ */ jsxs4(Text, { children: [
3614
- presentation.title,
3615
- presentation.summary ? ` \xB7 ${presentation.summary}` : ""
3616
- ] }) });
3787
+ return /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "column", paddingLeft: 2, children: [
3788
+ /* @__PURE__ */ jsxs4(Text, { children: [
3789
+ presentation.title,
3790
+ presentation.summary ? ` \xB7 ${presentation.summary}` : ""
3791
+ ] }),
3792
+ !expandedOutput || presentation.details === void 0 ? null : /* @__PURE__ */ jsx5(AssistantText, { text: presentation.details })
3793
+ ] });
3617
3794
  }
3618
- var CLI_CHANGESET_FILE_LIMIT = 8;
3619
3795
  var CHANGESET_TONE = "#b99bf8";
3620
3796
  function ChangeSetPresentationView({
3621
3797
  presentation,
3622
3798
  workspaceName,
3623
3799
  expanded
3624
3800
  }) {
3625
- const visibleFiles = expanded ? presentation.files : presentation.files.slice(0, CLI_CHANGESET_FILE_LIMIT);
3801
+ const visibleFiles = expanded ? presentation.files : [];
3626
3802
  const added = presentation.files.reduce((total, file) => total + file.added, 0);
3627
3803
  const removed = presentation.files.reduce((total, file) => total + file.removed, 0);
3628
3804
  const fileLabel = presentation.files.length === 1 ? "FILE" : "FILES";
@@ -3689,7 +3865,6 @@ function workspaceRelativePath(value, workspaceName) {
3689
3865
  if (normalized.toLocaleLowerCase().startsWith(rootPrefix.toLocaleLowerCase())) return normalized.slice(rootPrefix.length);
3690
3866
  return normalized;
3691
3867
  }
3692
- var CLI_COMMAND_OUTPUT_LINE_LIMIT = 16;
3693
3868
  function CommandPresentationView({
3694
3869
  presentation,
3695
3870
  expanded
@@ -3697,9 +3872,7 @@ function CommandPresentationView({
3697
3872
  const state = presentation.state ?? commandState(presentation.exitCode);
3698
3873
  const tone = jobStateColor(state);
3699
3874
  const label = presentation.variant === "terminal" ? "TERMINAL" : "COMMAND";
3700
- const hasStdout = jobOutputLines(presentation.stdout ?? "").length > 0;
3701
- const hasStderr = jobOutputLines(presentation.stderr ?? "").length > 0;
3702
- const perStreamLimit = expanded ? Number.POSITIVE_INFINITY : hasStdout && hasStderr ? Math.floor(CLI_COMMAND_OUTPUT_LINE_LIMIT / 2) : CLI_COMMAND_OUTPUT_LINE_LIMIT;
3875
+ const perStreamLimit = expanded ? Number.POSITIVE_INFINITY : 0;
3703
3876
  const stdout = boundedCommandLines(presentation.stdout ?? "", perStreamLimit);
3704
3877
  const stderr = boundedCommandLines(presentation.stderr ?? "", perStreamLimit);
3705
3878
  return /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "column", paddingLeft: 2, marginBottom: 1, children: [
@@ -3717,7 +3890,7 @@ function CommandPresentationView({
3717
3890
  ] }),
3718
3891
  stdout.total === 0 ? null : /* @__PURE__ */ jsx5(CommandOutputSection, { label: "OUTPUT", value: stdout, tone, error: false }),
3719
3892
  stderr.total === 0 ? null : /* @__PURE__ */ jsx5(CommandOutputSection, { label: "ERROR", value: stderr, tone, error: true }),
3720
- presentation.diagnostic === void 0 ? null : /* @__PURE__ */ jsxs4(Box_default, { children: [
3893
+ !expanded || presentation.diagnostic === void 0 ? null : /* @__PURE__ */ jsxs4(Box_default, { children: [
3721
3894
  /* @__PURE__ */ jsx5(Text, { color: tone, children: "\u2502 " }),
3722
3895
  /* @__PURE__ */ jsx5(Text, { color: tone, children: presentation.diagnostic })
3723
3896
  ] }),
@@ -3737,7 +3910,8 @@ function CommandOutputSection({
3737
3910
  /* @__PURE__ */ jsxs4(Box_default, { children: [
3738
3911
  /* @__PURE__ */ jsx5(Text, { color: tone, children: "\u251C\u2500 " }),
3739
3912
  /* @__PURE__ */ jsx5(Text, { color: error ? "#e06c50" : tone, bold: true, children: label }),
3740
- /* @__PURE__ */ jsx5(Text, { dimColor: true, children: ` \xB7 ${value.total} ${value.total === 1 ? "LINE" : "LINES"}` })
3913
+ /* @__PURE__ */ jsx5(Text, { dimColor: true, children: ` \xB7 ${value.total} ${value.total === 1 ? "LINE" : "LINES"}` }),
3914
+ value.hidden === 0 ? null : /* @__PURE__ */ jsx5(Text, { dimColor: true, children: ` \xB7 ${value.head.length + value.tail.length} SHOWN` })
3741
3915
  ] }),
3742
3916
  value.head.map((line, index) => /* @__PURE__ */ jsx5(CommandOutputLine, { line, tone, error }, `head:${index}`)),
3743
3917
  value.hidden === 0 ? null : /* @__PURE__ */ jsxs4(Box_default, { children: [
@@ -3761,9 +3935,10 @@ function CommandOutputLine({ line, tone, error }) {
3761
3935
  }
3762
3936
  function boundedCommandLines(output, limit) {
3763
3937
  const lines = jobOutputLines(output);
3938
+ if (limit <= 0) return { total: lines.length, head: [], tail: [], hidden: lines.length };
3764
3939
  if (lines.length <= limit) return { total: lines.length, head: lines, tail: [], hidden: 0 };
3765
- const headCount = Math.ceil(limit / 2);
3766
- const tailCount = Math.floor(limit / 2);
3940
+ const headCount = Math.ceil(limit * 0.625);
3941
+ const tailCount = Math.max(0, limit - headCount);
3767
3942
  return {
3768
3943
  total: lines.length,
3769
3944
  head: lines.slice(0, headCount),
@@ -3780,7 +3955,6 @@ function commandFooter(presentation, state) {
3780
3955
  if (presentation.truncated === true) parts.push("output truncated");
3781
3956
  return parts.join(" \xB7 ");
3782
3957
  }
3783
- var CLI_WEB_RESULT_LIMIT = 5;
3784
3958
  function WebPresentationView({
3785
3959
  presentation,
3786
3960
  expanded
@@ -3801,7 +3975,7 @@ function WebPresentationView({
3801
3975
  /* @__PURE__ */ jsx5(Text, { dimColor: true, children: "Page content added to context" })
3802
3976
  ] })
3803
3977
  ] });
3804
- const visibleItems = expanded ? items : items.slice(0, CLI_WEB_RESULT_LIMIT);
3978
+ const visibleItems = expanded ? items : [];
3805
3979
  const query = presentation.title.replace(/^Search:\s*/iu, "").trim();
3806
3980
  return /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "column", paddingLeft: 2, marginBottom: 1, children: [
3807
3981
  /* @__PURE__ */ jsxs4(Box_default, { children: [
@@ -3844,8 +4018,6 @@ function compactWebSource(value) {
3844
4018
  return value;
3845
4019
  }
3846
4020
  }
3847
- var CLI_JOB_LOG_LINE_LIMIT = 12;
3848
- var CLI_JOB_LIST_LIMIT = 8;
3849
4021
  function JobPresentationView({
3850
4022
  presentation,
3851
4023
  expanded
@@ -3854,7 +4026,7 @@ function JobPresentationView({
3854
4026
  const state = presentation.state ?? "running";
3855
4027
  const tone = jobStateColor(state);
3856
4028
  const lines = jobOutputLines(presentation.output ?? "");
3857
- const visibleLines = expanded ? lines : lines.slice(-CLI_JOB_LOG_LINE_LIMIT);
4029
+ const visibleLines = expanded ? lines : [];
3858
4030
  const hiddenLines = lines.length - visibleLines.length;
3859
4031
  const showLog = presentation.action === "read" || presentation.action === "kill" || lines.length > 0;
3860
4032
  return /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "column", paddingLeft: 2, marginBottom: 1, children: [
@@ -3907,7 +4079,7 @@ function JobListPresentationView({
3907
4079
  expanded
3908
4080
  }) {
3909
4081
  const jobs = presentation.jobs ?? [];
3910
- const visibleJobs = expanded ? jobs : jobs.slice(0, CLI_JOB_LIST_LIMIT);
4082
+ const visibleJobs = expanded ? jobs : [];
3911
4083
  return /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "column", paddingLeft: 2, marginBottom: 1, children: [
3912
4084
  /* @__PURE__ */ jsxs4(Box_default, { children: [
3913
4085
  /* @__PURE__ */ jsx5(Text, { color: "#d7a657", children: "\u250C\u2500 " }),
@@ -4259,6 +4431,7 @@ export {
4259
4431
  boundedCliTurn,
4260
4432
  canShowSlashCompletion,
4261
4433
  classifyTerminalInput,
4434
+ cliRenderBudget,
4262
4435
  cliTranscriptWindow,
4263
4436
  closeAndDisposeRuntime,
4264
4437
  completionKeyAction,
@@ -4266,9 +4439,11 @@ export {
4266
4439
  editPromptWithPastedBlocks,
4267
4440
  ideFooterPresentation,
4268
4441
  isCopyShortcut,
4442
+ isOutputToggleShortcut,
4269
4443
  isPlatformShortcut,
4270
4444
  navigateHistory,
4271
4445
  navigatePromptHistory,
4446
+ outputToggleShortcut,
4272
4447
  pastedDraftPresentation,
4273
4448
  prepareCliSubmission,
4274
4449
  promptCursorFromClick,