flavor-code 1.4.1-beta.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,8 +2068,11 @@ 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
  }
2000
- function outputToggleShortcut(platform = process.platform) {
2001
- return platform === "darwin" ? "Cmd+O" : "Ctrl+O";
2071
+ function isOutputToggleShortcut(character, key) {
2072
+ return key.ctrl && character.toLowerCase() === "o";
2073
+ }
2074
+ function outputToggleShortcut(_platform = process.platform) {
2075
+ return "Ctrl+O";
2002
2076
  }
2003
2077
  function removeLastCliImageOnBackspace(input, cursor, images) {
2004
2078
  if (input.length === 0 && cursor === 0 && images.length > 0) {
@@ -2006,10 +2080,12 @@ function removeLastCliImageOnBackspace(input, cursor, images) {
2006
2080
  }
2007
2081
  return { handled: false, images: [...images] };
2008
2082
  }
2009
- function prepareCliSubmission(input, images) {
2083
+ function prepareCliSubmission(input, images, slashCommands = MVP_COMMANDS) {
2010
2084
  const entered = input.trim();
2011
2085
  if (entered.length === 0 && images.length === 0) return { kind: "empty" };
2012
- 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) {
2013
2089
  return { kind: "error", message: "Image attachments cannot be used with slash commands." };
2014
2090
  }
2015
2091
  const text = entered || "Analyze the attached image(s).";
@@ -2205,6 +2281,8 @@ function App({ workspace, home, resumeSession, instanceId, palAlias }) {
2205
2281
  pendingPromptRef.current ??= new PendingPromptQueue();
2206
2282
  const closing = useRef4(false);
2207
2283
  const clipboardBusy = useRef4(false);
2284
+ const clipboardFallbackTimer = useRef4(null);
2285
+ const terminalTextPasteRevision = useRef4(0);
2208
2286
  const textBuf = useRef4({ pending: "", timer: null });
2209
2287
  const thinkingBuf = useRef4({ pending: "", timer: null });
2210
2288
  const promptDraftRef = useRef4({
@@ -2221,6 +2299,9 @@ function App({ workspace, home, resumeSession, instanceId, palAlias }) {
2221
2299
  const promptHistoryStoreRef = useRef4(void 0);
2222
2300
  promptHistoryStoreRef.current ??= new PromptHistoryStore({ home: home ?? homedir(), workspace, maxEntries: HISTORY_CAP });
2223
2301
  promptDraftRef.current = { text: input, cursor: promptCursor, pastedBlocks, imageAttachments };
2302
+ useEffect3(() => () => {
2303
+ if (clipboardFallbackTimer.current !== null) clearTimeout(clipboardFallbackTimer.current);
2304
+ }, []);
2224
2305
  const commitPromptDraft = (next, recordHistory = true) => {
2225
2306
  const current = promptDraftRef.current;
2226
2307
  const resolved = {
@@ -2464,7 +2545,10 @@ function App({ workspace, home, resumeSession, instanceId, palAlias }) {
2464
2545
  setCustomQuestionActive(false);
2465
2546
  }, [questions]);
2466
2547
  useEffect3(() => setApprovalExpanded(false), [approval?.id]);
2467
- const derivedSlashCompletion = deriveSlashCompletion(input, promptCursor, slashCandidates, slashSelection);
2548
+ const derivedSlashCompletion = useMemo3(
2549
+ () => deriveSlashCompletion(input, promptCursor, slashCandidates, slashSelection),
2550
+ [input, promptCursor, slashCandidates, slashSelection]
2551
+ );
2468
2552
  const slashCompletion = canShowSlashCompletion(
2469
2553
  transcript.active !== void 0,
2470
2554
  input,
@@ -2482,27 +2566,43 @@ function App({ workspace, home, resumeSession, instanceId, palAlias }) {
2482
2566
  setMentionSelection(0);
2483
2567
  setDismissedMentionInput(next.text);
2484
2568
  };
2485
- const pasteClipboardImage = async () => {
2486
- const active = runtimeRef.current;
2487
- if (active === void 0) {
2488
- setClipboardNotice("Flavor is still starting; try pasting the image again.");
2489
- return;
2490
- }
2491
- if (promptDraftRef.current.imageAttachments.length >= DEFAULT_MAX_IMAGES) {
2492
- setClipboardNotice(`A prompt can contain at most ${DEFAULT_MAX_IMAGES} images.`);
2493
- return;
2494
- }
2569
+ const pasteClipboardContent = async (imageOnly = false) => {
2495
2570
  if (clipboardBusy.current) return;
2496
2571
  clipboardBusy.current = true;
2497
- setClipboardNotice("Reading image from clipboard...");
2572
+ const pasteRevision = terminalTextPasteRevision.current;
2573
+ setClipboardNotice(imageOnly ? "Reading clipboard image..." : "Reading clipboard...");
2498
2574
  try {
2499
- const attachment = await readClipboardImage();
2500
- if (attachment === void 0) {
2501
- 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.");
2502
2597
  return;
2503
2598
  }
2504
- const incomingBytes = Buffer.byteLength(attachment.dataBase64, "base64");
2505
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");
2506
2606
  const currentBytes = currentDraft.imageAttachments.reduce((sum, image) => sum + image.bytes, 0);
2507
2607
  if (currentBytes + incomingBytes > DEFAULT_MAX_TOTAL_IMAGE_BYTES) {
2508
2608
  throw new Error(`Image attachments exceed the maximum total size of ${DEFAULT_MAX_TOTAL_IMAGE_BYTES} bytes`);
@@ -2542,7 +2642,7 @@ function App({ workspace, home, resumeSession, instanceId, palAlias }) {
2542
2642
  }
2543
2643
  const active = runtimeRef.current;
2544
2644
  if (isCopyShortcut(character, key)) {
2545
- if (selection.hasSelection()) selection.copySelection();
2645
+ if (selection.hasSelection()) selection.copySelectionNoClear();
2546
2646
  else if (process.platform !== "darwin") interrupt();
2547
2647
  return;
2548
2648
  }
@@ -2550,12 +2650,30 @@ function App({ workspace, home, resumeSession, instanceId, palAlias }) {
2550
2650
  interrupt();
2551
2651
  return;
2552
2652
  }
2553
- if (isPlatformShortcut(character, key, "o")) {
2653
+ if (isOutputToggleShortcut(character, key)) {
2554
2654
  setExpandedOutput((value) => !value);
2555
2655
  return;
2556
2656
  }
2557
- if (shouldReadClipboardImage(character, key, event.keypress.isPasted)) {
2558
- 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);
2559
2677
  return;
2560
2678
  }
2561
2679
  if (active?.approvals.pending !== void 0) {
@@ -2713,8 +2831,19 @@ function App({ workspace, home, resumeSession, instanceId, palAlias }) {
2713
2831
  return;
2714
2832
  }
2715
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
+ }
2716
2845
  const images = [...imageAttachments];
2717
- const prepared = prepareCliSubmission(input, images);
2846
+ const prepared = prepareCliSubmission(input, images, slashCandidates.map(({ name }) => name));
2718
2847
  if (prepared.kind === "empty" || active === void 0) return;
2719
2848
  if (prepared.kind === "error") {
2720
2849
  setClipboardNotice(prepared.message);
@@ -2921,6 +3050,25 @@ var CLI_VISIBLE_TURN_LIMIT = 40;
2921
3050
  var CLI_VISIBLE_BLOCK_LIMIT = 320;
2922
3051
  var CLI_VISIBLE_BLOCKS_PER_TURN = 80;
2923
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
+ }
2924
3072
  function approvalDetailLines(approval) {
2925
3073
  const lines = [];
2926
3074
  if (approval.agent !== "main") lines.push(`Agent: ${approval.agent}`);
@@ -2970,25 +3118,32 @@ function boundedCliDisplayText(text, limit = CLI_VISIBLE_TEXT_CHARS) {
2970
3118
 
2971
3119
  ${text.slice(-half)}`;
2972
3120
  }
2973
- function boundedCliPresentation(presentation) {
3121
+ function boundedCliPresentation(presentation, maxTextChars = CLI_VISIBLE_TEXT_CHARS) {
2974
3122
  if (presentation.kind === "terminal") return {
2975
3123
  ...presentation,
2976
- ...presentation.stdout === void 0 ? {} : { stdout: boundedCliDisplayText(presentation.stdout) },
2977
- ...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) }
2978
3126
  };
2979
3127
  if (presentation.kind === "job" && presentation.output !== void 0) return {
2980
3128
  ...presentation,
2981
- output: boundedCliDisplayText(presentation.output)
3129
+ output: boundedCliDisplayText(presentation.output, maxTextChars)
2982
3130
  };
2983
3131
  return presentation;
2984
3132
  }
2985
- function boundedCliTurn(turn, maxBlocks = CLI_VISIBLE_BLOCKS_PER_TURN) {
3133
+ function boundedCliTurn(turn, maxBlocks = CLI_VISIBLE_BLOCKS_PER_TURN, maxTextChars = CLI_VISIBLE_TEXT_CHARS) {
2986
3134
  const blockLimit = Math.max(1, Math.floor(maxBlocks));
2987
3135
  const hiddenBlocks = Math.max(0, turn.blocks.length - blockLimit);
2988
- 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) } : {
2989
3137
  ...block,
2990
- ...block.details === void 0 ? {} : { details: boundedCliDisplayText(block.details) },
2991
- ...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
+ }
2992
3147
  });
2993
3148
  const blocks = hiddenBlocks === 0 ? visible : [{
2994
3149
  kind: "status",
@@ -3006,7 +3161,7 @@ function boundedCliTurn(turn, maxBlocks = CLI_VISIBLE_BLOCKS_PER_TURN) {
3006
3161
  }
3007
3162
  };
3008
3163
  }
3009
- 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) {
3010
3165
  const turns = [];
3011
3166
  let remainingBlocks = Math.max(1, Math.floor(maxBlocks));
3012
3167
  let hiddenBlocks = 0;
@@ -3014,7 +3169,7 @@ function cliTranscriptWindow(completed, maxTurns = CLI_VISIBLE_TURN_LIMIT, maxBl
3014
3169
  const source = completed[index];
3015
3170
  if (remainingBlocks <= 0) break;
3016
3171
  const allowance = Math.min(CLI_VISIBLE_BLOCKS_PER_TURN, remainingBlocks);
3017
- const bounded = boundedCliTurn(source, allowance);
3172
+ const bounded = boundedCliTurn(source, allowance, maxTextChars);
3018
3173
  turns.push(bounded.turn);
3019
3174
  hiddenBlocks += bounded.hiddenBlocks;
3020
3175
  remainingBlocks -= Math.min(source.blocks.length, allowance);
@@ -3063,23 +3218,29 @@ function TerminalLayout({
3063
3218
  const dividerWidth = Math.max(1, columns - 1);
3064
3219
  const outputShortcut = outputToggleShortcut();
3065
3220
  const showWelcome = completed.length === 0 && active === void 0;
3066
- const activeCompletion = completion ?? mentionCompletion;
3067
- const menuRows = activeCompletion === void 0 ? 0 : Math.min(6, activeCompletion.items.length - activeCompletion.windowStart);
3068
- const activeTaskBlocks = active?.blocks.filter(
3221
+ const budget = useMemo3(
3222
+ () => cliRenderBudget(rows, columns),
3223
+ [rows, columns]
3224
+ );
3225
+ const activeTaskBlocks = useMemo3(() => active?.blocks.filter(
3069
3226
  (block) => block.kind === "status" && block.task !== void 0
3070
- ) ?? [];
3071
- const rawActiveWithoutTasks = active === void 0 ? void 0 : {
3072
- ...active,
3073
- blocks: active.blocks.filter(
3074
- (block) => !(block.kind === "status" && block.task !== void 0)
3075
- )
3076
- };
3077
- const activeWithoutTasks = rawActiveWithoutTasks === void 0 ? void 0 : expandedOutput ? rawActiveWithoutTasks : boundedCliTurn(rawActiveWithoutTasks).turn;
3078
- 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
+ );
3079
3240
  const questionRows = questions === void 0 ? 0 : 4 + (questions[questionIndex]?.options.length ?? 0) + questions.length * 2;
3080
3241
  const memoryReviewRows = memoryReviews.length === 0 ? 0 : 5;
3081
3242
  const approvalRows = approval === void 0 ? 0 : 3 + (approvalExpanded ? approvalDetailLines(approval).length : 0);
3082
- 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;
3083
3244
  const taskPanelRows = taskPanelViewportRows(rows, fixedBottomRows, activeTaskBlocks.length > 0);
3084
3245
  const availableBottomRows = Math.max(1, rows - taskPanelRows - 1);
3085
3246
  const bottomMaxRows = Math.min(availableBottomRows, Math.max(Math.floor(rows / 2), fixedBottomRows + 1));
@@ -3098,9 +3259,7 @@ function TerminalLayout({
3098
3259
  completedWindow.hiddenTurns,
3099
3260
  " earlier turns and ",
3100
3261
  completedWindow.hiddenBlocks,
3101
- " output items hidden \xB7 ",
3102
- outputShortcut,
3103
- " expand all"
3262
+ " output items hidden to keep the terminal responsive"
3104
3263
  ] }) : null,
3105
3264
  completedWindow.turns.map((turn, index) => /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "column", children: [
3106
3265
  index > 0 ? /* @__PURE__ */ jsx5(TurnSeparator, { width: columns }) : null,
@@ -3123,82 +3282,86 @@ function TerminalLayout({
3123
3282
  ...onTaskPanelHoverChange === void 0 ? {} : { onHoverChange: onTaskPanelHoverChange }
3124
3283
  }
3125
3284
  ),
3126
- /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "column", flexShrink: 0, maxHeight: bottomMaxRows, width: "100%", overflowY: "hidden", children: [
3127
- approval === void 0 ? null : /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "column", marginBottom: 1, children: [
3128
- /* @__PURE__ */ jsxs4(Text, { color: "magenta", children: [
3129
- "\u250C\u2500 approval \xB7 ",
3130
- 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
+ ] })
3131
3327
  ] }),
3132
- /* @__PURE__ */ jsxs4(Text, { wrap: "truncate-end", color: "magentaBright", children: [
3133
- "\u2502 ",
3134
- 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"
3135
3336
  ] }),
3136
- approvalExpanded ? approvalDetailLines(approval).map((line, index) => /* @__PURE__ */ jsxs4(Text, { color: "magentaBright", wrap: "truncate-end", children: [
3137
- "\u2502 ",
3138
- line
3139
- ] }, `${approval.id}:detail:${index}`)) : null,
3140
- isDestructiveTool(approval.tool) || approval.allowAlways === false ? /* @__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: "cyan", children: "e" }),
3147
- "=accept edits / ",
3148
- /* @__PURE__ */ jsx5(Text, { color: "red", children: "n" }),
3149
- "=deny"
3150
- ] }) : /* @__PURE__ */ jsxs4(Text, { bold: true, color: "magenta", children: [
3151
- "\u2514\u2500 ",
3152
- /* @__PURE__ */ jsx5(Text, { color: "cyan", children: "v" }),
3153
- "=details / ",
3154
- /* @__PURE__ */ jsx5(Text, { color: "green", children: "y" }),
3155
- "=once / ",
3156
- /* @__PURE__ */ jsx5(Text, { color: "yellow", children: "a" }),
3157
- "=same-type / ",
3158
- /* @__PURE__ */ jsx5(Text, { color: "cyan", children: "e" }),
3159
- "=accept edits / ",
3160
- /* @__PURE__ */ jsx5(Text, { color: "red", children: "n" }),
3161
- "=deny"
3162
- ] })
3163
- ] }),
3164
- !questions || questions.length === 0 ? null : /* @__PURE__ */ jsx5(QuestionCards, { questions, activeIndex: questionIndex, answers: questionAnswers, customActive: customQuestionActive }),
3165
- memoryReviews.length === 0 ? null : /* @__PURE__ */ jsx5(MemoryReviewCards, { reviews: memoryReviews, autoDismissSeconds: memoryAutoDismissSeconds }),
3166
- completion === void 0 ? null : /* @__PURE__ */ jsx5(SlashMenu, { completion }),
3167
- mentionCompletion === void 0 ? null : /* @__PURE__ */ jsx5(MentionMenu, { completion: mentionCompletion, ...onMentionSelect === void 0 ? {} : { onSelect: onMentionSelect } }),
3168
- pendingPrompts.length === 0 ? null : /* @__PURE__ */ jsxs4(Text, { color: "yellow", wrap: "truncate-end", children: [
3169
- "Pending (",
3170
- pendingPrompts.length,
3171
- ") \xB7 ",
3172
- queuedPromptLabel(pendingPrompts.at(-1)),
3173
- " \xB7 Esc edit latest"
3174
- ] }),
3175
- /* @__PURE__ */ jsx5(Text, { dimColor: true, children: "\u2500".repeat(dividerWidth) }),
3176
- imageAttachments.map((image, index) => /* @__PURE__ */ jsxs4(Text, { color: "cyan", wrap: "truncate-end", children: [
3177
- "[Image #",
3178
- index + 1,
3179
- "] ",
3180
- image.name ?? basename3(image.source.path)
3181
- ] }, `${image.sha256}:${index}`)),
3182
- clipboardNotice === void 0 ? null : /* @__PURE__ */ jsx5(Text, { color: "yellow", wrap: "truncate-end", children: clipboardNotice }),
3183
- /* @__PURE__ */ jsx5(
3184
- PromptLine,
3185
- {
3186
- input,
3187
- pastedBlocks,
3188
- cursor: promptCursor,
3189
- columns,
3190
- maxVisibleLines: promptMaxLines,
3191
- completedSlashTokenLength: tokenLength,
3192
- ...onPromptCursorChange === void 0 ? {} : { onCursorChange: onPromptCursorChange }
3193
- }
3194
- ),
3195
- /* @__PURE__ */ jsx5(
3196
- FooterStatus,
3197
- {
3198
- hint: 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"} 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 ${outputShortcut} ${expandedOutput ? "collapse" : "expand"} output \xB7 Ctrl+C exit`,
3199
- ...ideContext === void 0 ? {} : { ideContext }
3200
- }
3201
- )
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
+ ] })
3202
3365
  ] })
3203
3366
  ] });
3204
3367
  }
@@ -3300,7 +3463,7 @@ function SlashMenu({ completion }) {
3300
3463
  presentation.marker,
3301
3464
  /* @__PURE__ */ jsx5(HighlightedName, { name: candidate.name, query: completion.query, matchStyle: presentation.matchStyle }),
3302
3465
  candidate.kind === "skill" || candidate.kind === "plugin" ? /* @__PURE__ */ jsx5(Text, { color: candidate.kind === "skill" ? "magentaBright" : "cyanBright", children: ` ${candidate.kind}` }) : null,
3303
- 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)}` })
3304
3467
  ] }, `${candidate.kind}:${candidate.name}`);
3305
3468
  }) });
3306
3469
  }
@@ -3379,7 +3542,7 @@ function scrollUp(scroll, amount) {
3379
3542
  function TurnSeparator({ width }) {
3380
3543
  return /* @__PURE__ */ jsx5(Text, { dimColor: true, children: "\u2500".repeat(Math.max(1, width - 1)) });
3381
3544
  }
3382
- function TurnView({
3545
+ var TurnView = React4.memo(function TurnView2({
3383
3546
  turn,
3384
3547
  interactive,
3385
3548
  workspaceName,
@@ -3418,11 +3581,11 @@ function TurnView({
3418
3581
  ] }),
3419
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}`)) })
3420
3583
  ] });
3421
- }
3584
+ });
3422
3585
  var DIFF_CONTENT = fileDiffLineStyle("context").contentColor;
3423
3586
  var DIFF_REMOVED_MARKER = fileDiffLineStyle("removed").markerColor;
3424
3587
  var DIFF_ADDED_MARKER = fileDiffLineStyle("added").markerColor;
3425
- function FileDiffView({ presentation }) {
3588
+ function FileDiffView({ presentation, expanded }) {
3426
3589
  const operation = presentation.operation === "create" ? "Create" : presentation.operation === "delete" ? "Delete" : "Update";
3427
3590
  if (presentation.operation === "delete") {
3428
3591
  return /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "row", children: [
@@ -3454,7 +3617,7 @@ function FileDiffView({ presentation }) {
3454
3617
  ", removed ",
3455
3618
  lineCount(presentation.removed)
3456
3619
  ] }),
3457
- 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}`))
3458
3621
  ] });
3459
3622
  }
3460
3623
  function FileDiffRow({ line, lineWidth }) {
@@ -3600,6 +3763,7 @@ function StatusBlockView({
3600
3763
  }) {
3601
3764
  const visibleBlock = cliToolTitle(block);
3602
3765
  const outcome = cliToolOutcome(block);
3766
+ const toolOutput = block.tool !== void 0 || block.presentation !== void 0;
3603
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 });
3604
3768
  return /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "column", children: [
3605
3769
  primary,
@@ -3607,7 +3771,7 @@ function StatusBlockView({
3607
3771
  "\u2514 ",
3608
3772
  outcome
3609
3773
  ] }) }),
3610
- 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 }) })
3611
3775
  ] });
3612
3776
  }
3613
3777
  function ToolPresentationView({
@@ -3616,23 +3780,25 @@ function ToolPresentationView({
3616
3780
  expandedOutput
3617
3781
  }) {
3618
3782
  if (presentation.kind === "changeset") return /* @__PURE__ */ jsx5(ChangeSetPresentationView, { presentation, workspaceName, expanded: expandedOutput });
3619
- if (presentation.kind === "file-change") return /* @__PURE__ */ jsx5(FileDiffView, { presentation });
3783
+ if (presentation.kind === "file-change") return /* @__PURE__ */ jsx5(FileDiffView, { presentation, expanded: expandedOutput });
3620
3784
  if (presentation.kind === "terminal") return /* @__PURE__ */ jsx5(CommandPresentationView, { presentation, expanded: expandedOutput });
3621
3785
  if (presentation.kind === "web") return /* @__PURE__ */ jsx5(WebPresentationView, { presentation, expanded: expandedOutput });
3622
3786
  if (presentation.kind === "job") return /* @__PURE__ */ jsx5(JobPresentationView, { presentation, expanded: expandedOutput });
3623
- return /* @__PURE__ */ jsx5(Box_default, { paddingLeft: 2, children: /* @__PURE__ */ jsxs4(Text, { children: [
3624
- presentation.title,
3625
- presentation.summary ? ` \xB7 ${presentation.summary}` : ""
3626
- ] }) });
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
+ ] });
3627
3794
  }
3628
- var CLI_CHANGESET_FILE_LIMIT = 8;
3629
3795
  var CHANGESET_TONE = "#b99bf8";
3630
3796
  function ChangeSetPresentationView({
3631
3797
  presentation,
3632
3798
  workspaceName,
3633
3799
  expanded
3634
3800
  }) {
3635
- const visibleFiles = expanded ? presentation.files : presentation.files.slice(0, CLI_CHANGESET_FILE_LIMIT);
3801
+ const visibleFiles = expanded ? presentation.files : [];
3636
3802
  const added = presentation.files.reduce((total, file) => total + file.added, 0);
3637
3803
  const removed = presentation.files.reduce((total, file) => total + file.removed, 0);
3638
3804
  const fileLabel = presentation.files.length === 1 ? "FILE" : "FILES";
@@ -3699,7 +3865,6 @@ function workspaceRelativePath(value, workspaceName) {
3699
3865
  if (normalized.toLocaleLowerCase().startsWith(rootPrefix.toLocaleLowerCase())) return normalized.slice(rootPrefix.length);
3700
3866
  return normalized;
3701
3867
  }
3702
- var CLI_COMMAND_OUTPUT_LINE_LIMIT = 8;
3703
3868
  function CommandPresentationView({
3704
3869
  presentation,
3705
3870
  expanded
@@ -3707,14 +3872,9 @@ function CommandPresentationView({
3707
3872
  const state = presentation.state ?? commandState(presentation.exitCode);
3708
3873
  const tone = jobStateColor(state);
3709
3874
  const label = presentation.variant === "terminal" ? "TERMINAL" : "COMMAND";
3710
- const hasStdout = jobOutputLines(presentation.stdout ?? "").length > 0;
3711
- const hasStderr = jobOutputLines(presentation.stderr ?? "").length > 0;
3712
- const collapsedPerStreamLimit = hasStdout && hasStderr ? Math.floor(CLI_COMMAND_OUTPUT_LINE_LIMIT / 2) : CLI_COMMAND_OUTPUT_LINE_LIMIT;
3713
- const perStreamLimit = expanded ? Number.POSITIVE_INFINITY : collapsedPerStreamLimit;
3875
+ const perStreamLimit = expanded ? Number.POSITIVE_INFINITY : 0;
3714
3876
  const stdout = boundedCommandLines(presentation.stdout ?? "", perStreamLimit);
3715
3877
  const stderr = boundedCommandLines(presentation.stderr ?? "", perStreamLimit);
3716
- const collapsible = stdout.total > collapsedPerStreamLimit || stderr.total > collapsedPerStreamLimit;
3717
- const shortcut = outputToggleShortcut();
3718
3878
  return /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "column", paddingLeft: 2, marginBottom: 1, children: [
3719
3879
  /* @__PURE__ */ jsxs4(Box_default, { children: [
3720
3880
  /* @__PURE__ */ jsx5(Text, { color: tone, children: "\u250C\u2500 " }),
@@ -3730,21 +3890,13 @@ function CommandPresentationView({
3730
3890
  ] }),
3731
3891
  stdout.total === 0 ? null : /* @__PURE__ */ jsx5(CommandOutputSection, { label: "OUTPUT", value: stdout, tone, error: false }),
3732
3892
  stderr.total === 0 ? null : /* @__PURE__ */ jsx5(CommandOutputSection, { label: "ERROR", value: stderr, tone, error: true }),
3733
- presentation.diagnostic === void 0 ? null : /* @__PURE__ */ jsxs4(Box_default, { children: [
3893
+ !expanded || presentation.diagnostic === void 0 ? null : /* @__PURE__ */ jsxs4(Box_default, { children: [
3734
3894
  /* @__PURE__ */ jsx5(Text, { color: tone, children: "\u2502 " }),
3735
3895
  /* @__PURE__ */ jsx5(Text, { color: tone, children: presentation.diagnostic })
3736
3896
  ] }),
3737
3897
  /* @__PURE__ */ jsxs4(Box_default, { children: [
3738
3898
  /* @__PURE__ */ jsx5(Text, { color: tone, children: "\u2514\u2500 " }),
3739
- /* @__PURE__ */ jsx5(Text, { dimColor: true, children: commandFooter(presentation, state) }),
3740
- collapsible ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
3741
- /* @__PURE__ */ jsx5(Text, { dimColor: true, children: " \xB7 " }),
3742
- /* @__PURE__ */ jsx5(Text, { color: "ansi:cyanBright", children: shortcut }),
3743
- /* @__PURE__ */ jsxs4(Text, { dimColor: true, children: [
3744
- " ",
3745
- expanded ? "collapse" : "expand"
3746
- ] })
3747
- ] }) : null
3899
+ /* @__PURE__ */ jsx5(Text, { dimColor: true, children: commandFooter(presentation, state) })
3748
3900
  ] })
3749
3901
  ] });
3750
3902
  }
@@ -3783,6 +3935,7 @@ function CommandOutputLine({ line, tone, error }) {
3783
3935
  }
3784
3936
  function boundedCommandLines(output, limit) {
3785
3937
  const lines = jobOutputLines(output);
3938
+ if (limit <= 0) return { total: lines.length, head: [], tail: [], hidden: lines.length };
3786
3939
  if (lines.length <= limit) return { total: lines.length, head: lines, tail: [], hidden: 0 };
3787
3940
  const headCount = Math.ceil(limit * 0.625);
3788
3941
  const tailCount = Math.max(0, limit - headCount);
@@ -3802,7 +3955,6 @@ function commandFooter(presentation, state) {
3802
3955
  if (presentation.truncated === true) parts.push("output truncated");
3803
3956
  return parts.join(" \xB7 ");
3804
3957
  }
3805
- var CLI_WEB_RESULT_LIMIT = 5;
3806
3958
  function WebPresentationView({
3807
3959
  presentation,
3808
3960
  expanded
@@ -3823,7 +3975,7 @@ function WebPresentationView({
3823
3975
  /* @__PURE__ */ jsx5(Text, { dimColor: true, children: "Page content added to context" })
3824
3976
  ] })
3825
3977
  ] });
3826
- const visibleItems = expanded ? items : items.slice(0, CLI_WEB_RESULT_LIMIT);
3978
+ const visibleItems = expanded ? items : [];
3827
3979
  const query = presentation.title.replace(/^Search:\s*/iu, "").trim();
3828
3980
  return /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "column", paddingLeft: 2, marginBottom: 1, children: [
3829
3981
  /* @__PURE__ */ jsxs4(Box_default, { children: [
@@ -3866,8 +4018,6 @@ function compactWebSource(value) {
3866
4018
  return value;
3867
4019
  }
3868
4020
  }
3869
- var CLI_JOB_LOG_LINE_LIMIT = 12;
3870
- var CLI_JOB_LIST_LIMIT = 8;
3871
4021
  function JobPresentationView({
3872
4022
  presentation,
3873
4023
  expanded
@@ -3876,7 +4026,7 @@ function JobPresentationView({
3876
4026
  const state = presentation.state ?? "running";
3877
4027
  const tone = jobStateColor(state);
3878
4028
  const lines = jobOutputLines(presentation.output ?? "");
3879
- const visibleLines = expanded ? lines : lines.slice(-CLI_JOB_LOG_LINE_LIMIT);
4029
+ const visibleLines = expanded ? lines : [];
3880
4030
  const hiddenLines = lines.length - visibleLines.length;
3881
4031
  const showLog = presentation.action === "read" || presentation.action === "kill" || lines.length > 0;
3882
4032
  return /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "column", paddingLeft: 2, marginBottom: 1, children: [
@@ -3929,7 +4079,7 @@ function JobListPresentationView({
3929
4079
  expanded
3930
4080
  }) {
3931
4081
  const jobs = presentation.jobs ?? [];
3932
- const visibleJobs = expanded ? jobs : jobs.slice(0, CLI_JOB_LIST_LIMIT);
4082
+ const visibleJobs = expanded ? jobs : [];
3933
4083
  return /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "column", paddingLeft: 2, marginBottom: 1, children: [
3934
4084
  /* @__PURE__ */ jsxs4(Box_default, { children: [
3935
4085
  /* @__PURE__ */ jsx5(Text, { color: "#d7a657", children: "\u250C\u2500 " }),
@@ -4281,6 +4431,7 @@ export {
4281
4431
  boundedCliTurn,
4282
4432
  canShowSlashCompletion,
4283
4433
  classifyTerminalInput,
4434
+ cliRenderBudget,
4284
4435
  cliTranscriptWindow,
4285
4436
  closeAndDisposeRuntime,
4286
4437
  completionKeyAction,
@@ -4288,6 +4439,7 @@ export {
4288
4439
  editPromptWithPastedBlocks,
4289
4440
  ideFooterPresentation,
4290
4441
  isCopyShortcut,
4442
+ isOutputToggleShortcut,
4291
4443
  isPlatformShortcut,
4292
4444
  navigateHistory,
4293
4445
  navigatePromptHistory,