@xmoon76/dsh-pi-tui 0.4.0-alpha.2 → 0.4.1

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.
@@ -1087,7 +1087,7 @@ function summarizeToolArgs(name, argsRaw) {
1087
1087
  }
1088
1088
  /** Classify a tool name into its row variant; unknown names are `others`. */
1089
1089
  function classifyTool(name) {
1090
- return TOOL_VARIANTS[name] ?? "others";
1090
+ return Object.prototype.hasOwnProperty.call(TOOL_VARIANTS, name) ? TOOL_VARIANTS[name] : "others";
1091
1091
  }
1092
1092
  /** The structural icon semantic per exact tool name, for synthetic cards
1093
1093
  * without a registry entry (the glyph itself resolves through
@@ -1120,11 +1120,23 @@ const VARIANT_SEMANTICS = {
1120
1120
  * @param name - the tool name.
1121
1121
  */
1122
1122
  function toolIconSemantic(name) {
1123
- const exact = TOOL_SEMANTICS[name];
1123
+ const exact = Object.prototype.hasOwnProperty.call(TOOL_SEMANTICS, name) ? TOOL_SEMANTICS[name] : void 0;
1124
1124
  if (exact !== void 0) return exact;
1125
1125
  if (name.startsWith("/")) return "slash-command";
1126
1126
  return VARIANT_SEMANTICS[classifyTool(name)];
1127
1127
  }
1128
+ /**
1129
+ * The short title used by live tool-call presentations. Known names reuse the
1130
+ * same exact-title and variant tables as formal tool cards; an unknown tool is
1131
+ * intentionally generic rather than exposing its raw model-facing name.
1132
+ */
1133
+ function toolTitle(name) {
1134
+ if (name === "") return "tool";
1135
+ const exact = Object.prototype.hasOwnProperty.call(TOOL_TITLES, name) ? TOOL_TITLES[name] : Object.prototype.hasOwnProperty.call(TUI_TOOL_TITLES, name) ? TUI_TOOL_TITLES[name] : void 0;
1136
+ if (exact !== void 0) return exact;
1137
+ const variant = classifyTool(name);
1138
+ return variant === "others" ? "Tool" : VARIANT_TITLES[variant];
1139
+ }
1128
1140
  /** Derive the folded preview from a tool name and its raw args JSON. */
1129
1141
  function parseCallPreview(name, argsRaw) {
1130
1142
  const parsed = parseArgs(argsRaw);
@@ -7518,23 +7530,25 @@ const matchesLegacyModifierSequence = (data, key, modifier) => {
7518
7530
  if (modifier === MODIFIERS.ctrl) return matchesLegacySequence(data, LEGACY_CTRL_SEQUENCES[key]);
7519
7531
  return false;
7520
7532
  };
7533
+ const KITTY_EVENT_SEQUENCE = /^\x1b\[\d+(?::\d*)?(?::\d+)?;\d+:(?<event>[23])(?:u|~|[ABCDHF])$/u;
7534
+ function kittyEventType(data) {
7535
+ const match = KITTY_EVENT_SEQUENCE.exec(data);
7536
+ if (match === null) return void 0;
7537
+ return match.groups?.event === "2" ? "repeat" : "release";
7538
+ }
7521
7539
  /**
7522
- * Check if the last parsed key event was a key release.
7540
+ * Check if the input is a complete Kitty key release event.
7523
7541
  * Only meaningful when Kitty keyboard protocol with flag 2 is active.
7524
7542
  */
7525
7543
  function isKeyRelease(data) {
7526
- if (data.includes("\x1B[200~")) return false;
7527
- if (data.includes(":3u") || data.includes(":3~") || data.includes(":3A") || data.includes(":3B") || data.includes(":3C") || data.includes(":3D") || data.includes(":3H") || data.includes(":3F")) return true;
7528
- return false;
7544
+ return kittyEventType(data) === "release";
7529
7545
  }
7530
7546
  /**
7531
- * Check if the last parsed key event was a key repeat.
7547
+ * Check if the input is a complete Kitty key repeat event.
7532
7548
  * Only meaningful when Kitty keyboard protocol with flag 2 is active.
7533
7549
  */
7534
7550
  function isKeyRepeat(data) {
7535
- if (data.includes("\x1B[200~")) return false;
7536
- if (data.includes(":2u") || data.includes(":2~") || data.includes(":2A") || data.includes(":2B") || data.includes(":2C") || data.includes(":2D") || data.includes(":2H") || data.includes(":2F")) return true;
7537
- return false;
7551
+ return kittyEventType(data) === "repeat";
7538
7552
  }
7539
7553
  function parseEventType(eventTypeStr) {
7540
7554
  if (!eventTypeStr) return "press";
@@ -17032,12 +17046,11 @@ var TuiMainScreen = class extends TuiBase {
17032
17046
  previousLines = [];
17033
17047
  /**
17034
17048
  * Raw (pre-processing) lines of the previous frame, aligned with
17035
- * {@link previousLines}. Component render caches return identical string
17036
- * references for unchanged content (the host's BulletedComponent /
17037
- * ThinkingCompactComponent keep their output reference-stable for exactly
17038
- * this contract), which lets each frame reuse the processed output for
17039
- * every untouched line instead of re-normalizing, re-measuring, and
17040
- * re-scanning the whole transcript. (dsh-pi-tui divergence X035.)
17049
+ * {@link previousLines}. Equal primitive rendered-line values let each frame
17050
+ * reuse processed output for every untouched line, even when a component
17051
+ * returns a fresh array. Host render-array reference stability is an
17052
+ * incidental component-level optimization, not a prerequisite for this
17053
+ * cache. (dsh-pi-tui divergence X035.)
17041
17054
  */
17042
17055
  previousRawLines = [];
17043
17056
  /** Per-line kitty image ids of the previous frame, aligned with previousRawLines. */
@@ -19417,7 +19430,7 @@ const APP_KEYBINDINGS = {
19417
19430
  "app.exit.request": {
19418
19431
  id: "app.exit.request",
19419
19432
  defaultKeys: ["ctrl+c", "ctrl+d"],
19420
- description: "Quit the TUI (the default chord clears the draft first; a second press exits)",
19433
+ description: "Quit the TUI (keyboard exit requires a same-key second press; the default clear-draft and forward-delete editor behaviors are preserved)",
19421
19434
  category: "Agent",
19422
19435
  scope: "global",
19423
19436
  configurable: true
@@ -22462,9 +22475,9 @@ function createBuiltinFooterRegistry() {
22462
22475
  //#region src/footer/instruction.ts
22463
22476
  /** Resolve the active Host instruction, if any. */
22464
22477
  function resolveFooterInstruction(state) {
22465
- if (state.ctrlCExitArmed && !state.viewing) return {
22466
- id: "ctrl-c-exit",
22467
- text: [{ text: "Press Ctrl+C again to exit" }],
22478
+ if (state.exitConfirmKeyLabel !== void 0 && !state.viewing) return {
22479
+ id: "exit-confirm",
22480
+ text: [{ text: `Press ${state.exitConfirmKeyLabel} again to exit` }],
22468
22481
  priority: 100
22469
22482
  };
22470
22483
  if (state.leaderHint !== void 0 && !state.viewing) return {
@@ -28641,6 +28654,18 @@ function focusDurationText(activity, now) {
28641
28654
  const end = activity.completed ? activity.endedAt ?? now() : now();
28642
28655
  return formatFocusDuration(Math.max(0, end - activity.startedAt));
28643
28656
  }
28657
+ /** The compact Focus-collapsed Tool-slot text for live Preparing rows.
28658
+ * Names that do not map to a known tool title remain generic, so model-facing
28659
+ * names never make the compact Thought card noisy. The input is copied and
28660
+ * sorted so the summary is deterministic even when a caller supplies a fresh
28661
+ * order. */
28662
+ function focusPreparingSummary(previews) {
28663
+ if (previews.length === 0) return void 0;
28664
+ const ordered = [...previews].sort((left, right) => left.index - right.index);
28665
+ const known = ordered.map((preview) => preview.name === void 0 || preview.name === "" ? void 0 : toolTitle(preview.name)).find((title) => title !== void 0 && title !== "Tool" && title !== "tool");
28666
+ if (known === void 0) return ordered.length === 1 ? "Preparing tool…" : `Preparing ${ordered.length} tools…`;
28667
+ return ordered.length === 1 ? `Preparing ${known}…` : `Preparing ${known} +${ordered.length - 1}`;
28668
+ }
28644
28669
  /** Assemble the one-line header, dropping the stat tail progressively so
28645
28670
  * the header NEVER breaks the terminal (plan §14/§46): full tail → token +
28646
28671
  * tool count → token → bare label (then a hard truncate as the last
@@ -28724,13 +28749,15 @@ function previewTailLines(label, text, width, maxRows) {
28724
28749
  * Think, Tool, Message — then the error reason (plan §24). Think and
28725
28750
  * Tool are at most ONE visual row; Message is the third process slot and
28726
28751
  * shows the latest up to {@link FOCUS_MESSAGE_MAX_ROWS} visual rows of
28727
- * its bounded tail. Only existing slots render. The Tool line's status
28728
- * prefix follows plan §10: none while running, ✓ settled ok, ✗ settled
28729
- * error. */
28730
- function focusCollapsedBody(activity, width, toolDisplay) {
28752
+ * its bounded tail. Only existing slots render. A live Preparing display,
28753
+ * when supplied, temporarily owns the Tool slot over the formal Tool display.
28754
+ * The formal Tool line's status prefix follows plan §10: none while running,
28755
+ * ✓ settled ok, ✗ settled error. */
28756
+ function focusCollapsedBody(activity, width, toolDisplay, preparingDisplay) {
28731
28757
  const lines = [];
28732
28758
  if (activity.think !== void 0) lines.push(previewLine("Think:", activity.think.text, width));
28733
- if (activity.tool !== void 0 && toolDisplay !== void 0) {
28759
+ if (preparingDisplay !== void 0) lines.push(previewLine("Tool:", preparingDisplay, width));
28760
+ else if (activity.tool !== void 0 && toolDisplay !== void 0) {
28734
28761
  const prefix = activity.tool.status === "ok" ? "✓ " : activity.tool.status === "error" ? "✗ " : "";
28735
28762
  lines.push(previewLine("Tool:", `${prefix}${toolDisplay}`, width));
28736
28763
  }
@@ -28747,7 +28774,9 @@ function focusCollapsedBody(activity, width, toolDisplay) {
28747
28774
  * icon style) keeps that cheap. The component never mutates Focus state —
28748
28775
  * clicks route through the app's hit map to toggleFocusTurn (plan §17).
28749
28776
  * The Tool line's display text is PRECOMPUTED by the app (presenter-first,
28750
- * plan §38) — the component stays a pure renderer.
28777
+ * plan §38) — the component stays a pure renderer. A collapsed Preparing
28778
+ * summary is presentation input only; expanded rows are composed by TuiApp
28779
+ * after the projected process tail.
28751
28780
  */
28752
28781
  var FocusActivityComponent = class {
28753
28782
  activity;
@@ -28755,12 +28784,14 @@ var FocusActivityComponent = class {
28755
28784
  now;
28756
28785
  toolDisplay;
28757
28786
  iconStyle;
28787
+ preparingSummary;
28758
28788
  constructor(options) {
28759
28789
  this.activity = options.activity;
28760
28790
  this.expanded = options.expanded;
28761
28791
  this.now = options.now ?? (() => Date.now());
28762
28792
  this.toolDisplay = options.toolDisplay;
28763
28793
  this.iconStyle = options.iconStyle ?? "emoji";
28794
+ this.preparingSummary = options.preparingSummary;
28764
28795
  }
28765
28796
  /** The Component interface requires invalidate(); the component keeps no
28766
28797
  * render cache (duration is live per frame), so this is a no-op. */
@@ -28770,7 +28801,7 @@ var FocusActivityComponent = class {
28770
28801
  const indent = width >= 4 ? " " : "";
28771
28802
  const contentWidth = Math.max(1, width - visibleWidth(indent));
28772
28803
  lines.push(`${indent}${color.textDim(formatFocusHeaderLine(this.activity, this.expanded, this.now, contentWidth, this.iconStyle))}`);
28773
- if (!this.expanded) for (const line of focusCollapsedBody(this.activity, contentWidth, this.toolDisplay)) lines.push(`${indent}${color.textDim(line)}`);
28804
+ if (!this.expanded) for (const line of focusCollapsedBody(this.activity, contentWidth, this.toolDisplay, this.preparingSummary)) lines.push(`${indent}${color.textDim(line)}`);
28774
28805
  return lines;
28775
28806
  }
28776
28807
  };
@@ -29877,9 +29908,8 @@ var AppActionDispatcher = class {
29877
29908
  }
29878
29909
  /** Dispatch one resolved action. Returns whether the key was consumed.
29879
29910
  * @param action - the resolved semantic action.
29880
- * @param key - the physical key that resolved (the exit path needs it:
29881
- * Ctrl+C uses the clear-then-exit chord, every other key exits
29882
- * immediately). */
29911
+ * @param key - the effective key that resolved. Exit confirmation owns
29912
+ * same-key semantics at the TuiApp ingress; an absent key is invalid. */
29883
29913
  dispatch(action, key) {
29884
29914
  switch (action) {
29885
29915
  case "app.input.submit": return this.host.submitDraft(false);
@@ -29887,7 +29917,9 @@ var AppActionDispatcher = class {
29887
29917
  case "app.input.steer": return this.host.steerDraft();
29888
29918
  case "app.input.dequeue": return this.host.dequeueDraft();
29889
29919
  case "app.agent.interrupt": return this.host.interruptActivity();
29890
- case "app.exit.request": return this.host.requestExit(key ?? "enter");
29920
+ case "app.exit.request":
29921
+ if (key === void 0) return true;
29922
+ return this.host.requestExit(key);
29891
29923
  case "app.transcript.search": return this.host.openTranscriptSearch();
29892
29924
  case "app.transcript.jumpLatest": return this.host.jumpLatest();
29893
29925
  case "app.transcript.search.next": return this.host.searchNext();
@@ -30745,6 +30777,7 @@ var LeaderStateMachine = class {
30745
30777
  if (this.disposed) return { kind: "passed" };
30746
30778
  const leaderKey = this.config.key;
30747
30779
  if (leaderKey === void 0) return { kind: "passed" };
30780
+ if (isKeyRelease(data) || isKeyRepeat(data)) return { kind: "consumed" };
30748
30781
  if (this.state === "idle") {
30749
30782
  if (matchesKey(data, leaderKey)) {
30750
30783
  this.enterPending();
@@ -30752,7 +30785,6 @@ var LeaderStateMachine = class {
30752
30785
  }
30753
30786
  return { kind: "passed" };
30754
30787
  }
30755
- if (isKeyRelease(data) || isKeyRepeat(data)) return { kind: "consumed" };
30756
30788
  if (parseKey(data) === void 0) {
30757
30789
  this.cancel();
30758
30790
  return { kind: "cancelled-pass" };
@@ -30763,7 +30795,7 @@ var LeaderStateMachine = class {
30763
30795
  }
30764
30796
  for (const binding of this.bindings) if (matchesKey(data, binding.key)) {
30765
30797
  this.cancel();
30766
- const consumed = this.callbacks.onActivate(binding.action);
30798
+ const consumed = this.callbacks.onActivate(binding.action, binding.key);
30767
30799
  return {
30768
30800
  kind: "activated",
30769
30801
  action: binding.action,
@@ -30947,7 +30979,7 @@ var HostKeybindingManager = class HostKeybindingManager {
30947
30979
  timeoutMs: this.leaderTimeoutMs
30948
30980
  } : effectiveLeaderConfig;
30949
30981
  this.leader = new LeaderStateMachine(machineConfig, leaderBindings, {
30950
- onActivate: (action) => this.onLeaderActivate(action),
30982
+ onActivate: (action, key) => this.onLeaderActivate(action, key),
30951
30983
  onStateChange: () => this.onLeaderStateChange()
30952
30984
  });
30953
30985
  }
@@ -34416,6 +34448,10 @@ var TuiApp = class TuiApp {
34416
34448
  questionQueue = [];
34417
34449
  /** The folded transcript; re-rendered into the messages view on change. */
34418
34450
  messages = [];
34451
+ /** Live-only tool-call previews; never part of the folded transcript or
34452
+ * Focus activity state. The runner replaces this snapshot on each coalesced
34453
+ * transcript repaint. */
34454
+ streamingToolPreviews = [];
34419
34455
  /** Local (non-session) cards — e.g. `!` shell runs — rendered after the transcript. */
34420
34456
  localMessages = [];
34421
34457
  /** Submitted input history (newest first), mirrored for persistence. */
@@ -34663,21 +34699,22 @@ var TuiApp = class TuiApp {
34663
34699
  submitPendingDetail;
34664
34700
  /** Phase 4: the plugin working-message override (advanced host state). */
34665
34701
  workingMessageOverride;
34666
- /** Timestamp of the last Ctrl+C press, for the empty-editor exit chord. */
34667
- lastCtrlCAt;
34668
- /** The empty-editor double-Ctrl+C exit window in ms. A 500ms window
34669
- * silently misses a human-paced double press (0.6–1s apart), which
34670
- * read as "the chord doesn't work"; 1.5s covers a natural double
34671
- * press, and the armed state is announced by the hint. */
34672
- static CTRL_C_EXIT_WINDOW_MS = 1500;
34702
+ /** The effective key whose keyboard exit confirmation is armed. */
34703
+ exitConfirmTrigger;
34704
+ /** Timestamp at which the current keyboard exit confirmation was armed. */
34705
+ exitConfirmAt;
34706
+ /** The keyboard exit-confirmation window in ms. A too-short window silently
34707
+ * misses a human-paced double press (0.6–1s apart), which reads as "the
34708
+ * confirmation doesn't work"; 1.5s covers a natural double press, and the
34709
+ * armed state is announced by the footer. */
34710
+ static EXIT_CONFIRM_WINDOW_MS = 1500;
34673
34711
  /** Issue #8: the effective exit window (injectable for tests). */
34674
- ctrlCExitWindowMs;
34675
- /** Issue #8: whether the exit chord is armed — the footer's second line
34676
- * shows `Press Ctrl+C again to exit` while armed, and the hint's
34712
+ exitConfirmWindowMs;
34713
+ /** Issue #8: the footer instruction reflects the armed effective trigger
34714
+ * label while armed, and the instruction's
34677
34715
  * lifetime is EXACTLY the exit window (one shared timer, never a
34678
34716
  * lingering notify). */
34679
- ctrlCExitArmed = false;
34680
- ctrlCExitTimer;
34717
+ exitConfirmTimer;
34681
34718
  /** Session workspace root for path relativization (Web relativizeToCwd). */
34682
34719
  workspaceRoot;
34683
34720
  /** The tool presentation bridge, wired by the runner to the live registry. */
@@ -34880,7 +34917,7 @@ var TuiApp = class TuiApp {
34880
34917
  this.footerComposer = new FooterComposer(this.footerItemRegistry);
34881
34918
  this.extensionHost?.setChromeRefresher(() => this.refreshChrome());
34882
34919
  this.notifyDurationMs = options.notifyDurationMs ?? TuiApp.NOTIFY_DURATION_MS;
34883
- this.ctrlCExitWindowMs = options.ctrlCExitWindowMs ?? TuiApp.CTRL_C_EXIT_WINDOW_MS;
34920
+ this.exitConfirmWindowMs = options.exitConfirmWindowMs ?? TuiApp.EXIT_CONFIRM_WINDOW_MS;
34884
34921
  this.workspaceRoot = options.workspaceRoot;
34885
34922
  this.present = options.present;
34886
34923
  this.pluginActionFor = options.pluginActionFor;
@@ -34893,6 +34930,7 @@ var TuiApp = class TuiApp {
34893
34930
  this.inputRouter = new InputRouter();
34894
34931
  this.keybindings = new HostKeybindingManager({
34895
34932
  onInvalidate: () => {
34933
+ this.clearExitConfirmation();
34896
34934
  if (this.messagesView !== void 0) {
34897
34935
  this.refreshTranscriptWindowHint();
34898
34936
  this.rebuildMessages();
@@ -34900,11 +34938,19 @@ var TuiApp = class TuiApp {
34900
34938
  this.requestRender();
34901
34939
  },
34902
34940
  onLeaderStateChange: () => this.renderFooter(),
34903
- onLeaderActivate: (action) => {
34941
+ onLeaderActivate: (action, key) => {
34904
34942
  const target = this.viewerMode;
34905
34943
  if (target !== void 0 && isViewerAccessInteractive(resolveViewerAccess(target.mode, target.access)) && VIEWER_BLOCKED_PARENT_ACTIONS.has(action)) return true;
34906
34944
  if (!this.keybindings.canActivate(action, this.keybindingContext())) return false;
34907
- return this.dispatchResolvedAction(action, "");
34945
+ const leaderKey = this.keybindings.leaderMachine()?.leaderKey;
34946
+ const trigger = action === "app.exit.request" && leaderKey !== void 0 ? {
34947
+ identity: `leader:${leaderKey}:${key}`,
34948
+ key,
34949
+ label: formatLeaderSequence(key),
34950
+ leaderKey,
34951
+ clearsDraft: key === "ctrl+c"
34952
+ } : void 0;
34953
+ return this.dispatchResolvedAction(action, "", key, trigger);
34908
34954
  },
34909
34955
  onEditorSubmitSync: (keys) => {
34910
34956
  if (this.disposed) return;
@@ -34927,6 +34973,7 @@ var TuiApp = class TuiApp {
34927
34973
  this.editor = new TuiEditor(this.tui, editorTheme, { requestRender: (force) => this.requestRender(force) });
34928
34974
  this.editorBorder = this.editor.borderColor;
34929
34975
  this.editor.onSubmit = (text) => {
34976
+ this.clearExitConfirmation();
34930
34977
  const serialized = serializeEditorInput(this.editor.getInputMode(), text);
34931
34978
  if (!serializedDraftHasPayload(serialized) && this.events.isImageDraft?.() !== true) return;
34932
34979
  const target = this.viewerMode;
@@ -34942,11 +34989,11 @@ var TuiApp = class TuiApp {
34942
34989
  }
34943
34990
  this.rememberInput(serialized);
34944
34991
  this.clearNotify();
34945
- this.clearCtrlCExit();
34946
34992
  this.resetEditorMode();
34947
34993
  this.events.onSubmit(serialized);
34948
34994
  };
34949
34995
  this.editor.onChange = () => {
34996
+ this.clearExitConfirmation();
34950
34997
  if (this.tasksActive) this.renderFooter();
34951
34998
  const viewer = this.viewerMode;
34952
34999
  if (viewer !== void 0 && isViewerAccessInteractive(resolveViewerAccess(viewer.mode, viewer.access))) {
@@ -35151,7 +35198,7 @@ var TuiApp = class TuiApp {
35151
35198
  */
35152
35199
  suspendForExternalEditor() {
35153
35200
  this.keybindings.cancelLeader();
35154
- this.clearCtrlCExit();
35201
+ this.clearExitConfirmation();
35155
35202
  this.lastEscapeAt = void 0;
35156
35203
  if (this.fullscreen !== void 0) this.fullscreen.stop({ preserveScreen: true });
35157
35204
  else this.tui.stop();
@@ -35181,7 +35228,7 @@ var TuiApp = class TuiApp {
35181
35228
  * process-tui-slot.ts). */
35182
35229
  stop() {
35183
35230
  this.clearNotify();
35184
- this.clearCtrlCExit();
35231
+ this.clearExitConfirmation();
35185
35232
  this.keybindings.cancelLeader();
35186
35233
  this.lastEscapeAt = void 0;
35187
35234
  this.working.dispose();
@@ -35328,6 +35375,7 @@ var TuiApp = class TuiApp {
35328
35375
  if (replacement === void 0) return void 0;
35329
35376
  const event = this.editorInputEventOf(data);
35330
35377
  if (event === void 0) return void 0;
35378
+ this.clearExitConfirmation();
35331
35379
  if (replacement(event)) return { consume: true };
35332
35380
  if (forceEscapeRoute) return void 0;
35333
35381
  const retry = this.inputRouter.route(data, {
@@ -35390,7 +35438,8 @@ var TuiApp = class TuiApp {
35390
35438
  /** Shared key routing: questions, then approval, then folding/mode/cancel/exit. */
35391
35439
  handleInput(data) {
35392
35440
  if (isFocusReport(data)) return this.handleInputCore(data);
35393
- this.events.onUserInput?.();
35441
+ const protocolArtifact = isKeyRelease(data) || isKeyRepeat(data);
35442
+ if (!protocolArtifact) this.events.onUserInput?.();
35394
35443
  if (this.unstableInputRoute !== void 0) {
35395
35444
  if (this.unstableInputsLive?.() !== true) this.unstableEscPresses = [];
35396
35445
  else if (this.unstableFailSafe(data)) {
@@ -35398,12 +35447,19 @@ var TuiApp = class TuiApp {
35398
35447
  this.unstableFailSafeRelease?.();
35399
35448
  } catch {}
35400
35449
  this.notify("unstable captures released (emergency fail-safe)", "info");
35450
+ if (isKeyRelease(data) || isKeyRepeat(data)) return { consume: true };
35451
+ this.clearExitConfirmation();
35401
35452
  return { consume: true };
35402
35453
  }
35403
35454
  const outcome = this.unstableInputRoute(data, this.extensionHost?.surfaceId ?? "tui");
35404
- if (outcome.action === "consume") return { consume: true };
35455
+ if (outcome.action === "consume") {
35456
+ if (isKeyRelease(data) || isKeyRepeat(data)) return { consume: true };
35457
+ this.clearExitConfirmation();
35458
+ return { consume: true };
35459
+ }
35405
35460
  if (outcome.action === "rewrite") {
35406
35461
  if (isFocusReport(outcome.data)) return { consume: true };
35462
+ if (protocolArtifact && !isFocusReport(outcome.data) && !isKeyRelease(outcome.data) && !isKeyRepeat(outcome.data)) this.events.onUserInput?.();
35407
35463
  const result = this.handleInputCore(outcome.data);
35408
35464
  if (result === void 0) return { data: outcome.data };
35409
35465
  return result;
@@ -35414,18 +35470,28 @@ var TuiApp = class TuiApp {
35414
35470
  /** The host's own input precedence ladder (the raw stage has already
35415
35471
  * run — see {@link handleInput}). */
35416
35472
  handleInputCore(data) {
35417
- if (MOUSE_SEQUENCE.test(data) || data.length === 6 && data.startsWith("\x1B[M")) return;
35473
+ if (MOUSE_SEQUENCE.test(data) || data.length === 6 && data.startsWith("\x1B[M")) {
35474
+ this.clearExitConfirmation();
35475
+ return;
35476
+ }
35418
35477
  if (data === "\x1B[O" || data === "\x1B[I") {
35419
35478
  this.events.onTerminalFocus?.(data === "\x1B[I");
35420
35479
  if (this.fullscreen !== void 0) return void 0;
35421
35480
  return { consume: true };
35422
35481
  }
35423
- if (isKeyRelease(data) || isKeyRepeat(data)) return void 0;
35482
+ if (isKeyRelease(data) || isKeyRepeat(data)) return { consume: true };
35424
35483
  const physicalEscape = matchesKey(data, "escape");
35425
35484
  if (physicalEscape ? !this.keybindings.physicalEscapeEnabled() : !this.keybindings.matches(data, "app.agent.interrupt")) this.lastEscapeAt = void 0;
35426
- if (this.activeQuestions !== void 0) return this.handleQuestionKey(data);
35427
- if (this.activeApproval !== void 0) return this.handleApprovalKey(data);
35485
+ if (this.activeQuestions !== void 0) {
35486
+ this.clearExitConfirmation();
35487
+ return this.handleQuestionKey(data);
35488
+ }
35489
+ if (this.activeApproval !== void 0) {
35490
+ this.clearExitConfirmation();
35491
+ return this.handleApprovalKey(data);
35492
+ }
35428
35493
  if (this.viewerMode !== void 0 && !this.activeScreen.hasOverlayEntries) {
35494
+ this.clearExitConfirmation();
35429
35495
  const viewer = this.viewerMode;
35430
35496
  if (this.keybindings.matches(data, "app.transcript.jumpLatest")) {
35431
35497
  if (this.dispatchResolvedAction("app.transcript.jumpLatest", data)) return { consume: true };
@@ -35450,6 +35516,7 @@ var TuiApp = class TuiApp {
35450
35516
  }
35451
35517
  }
35452
35518
  if (this.searchOverlay !== void 0) {
35519
+ this.clearExitConfirmation();
35453
35520
  if (this.keybindings.matches(data, "app.transcript.jumpLatest")) {
35454
35521
  if (this.dispatchResolvedAction("app.transcript.jumpLatest", data)) return { consume: true };
35455
35522
  }
@@ -35472,28 +35539,38 @@ var TuiApp = class TuiApp {
35472
35539
  return;
35473
35540
  }
35474
35541
  if (matchesKey(data, "tab") && this.activeCategorizedPicker !== void 0) {
35542
+ this.clearExitConfirmation();
35475
35543
  this.activeCategorizedPicker.cycle();
35476
35544
  return { consume: true };
35477
35545
  }
35478
- if (this.activeScreen.hasOverlayEntries) return void 0;
35546
+ if (this.activeScreen.hasOverlayEntries) {
35547
+ this.clearExitConfirmation();
35548
+ return;
35549
+ }
35479
35550
  if (this.seatEditor().handleInput !== void 0 && !matchesKey(data, "escape") && this.isSubmitKey(data)) {
35551
+ this.clearExitConfirmation();
35480
35552
  this.editorSeatHolder.handleHostFallbackInput(data);
35481
35553
  return { consume: true };
35482
35554
  }
35483
35555
  const leader = this.keybindings.leaderMachine();
35484
35556
  if (leader !== void 0 && !(physicalEscape && !leader.pending)) {
35485
35557
  const outcome = leader.feed(data);
35486
- if (outcome.kind === "consumed" || outcome.kind === "cancelled-consume") return { consume: true };
35558
+ if (outcome.kind === "consumed" || outcome.kind === "cancelled-consume") {
35559
+ if (!(outcome.kind === "consumed" && leader.pending && this.exitConfirmTrigger?.leaderKey === leader.leaderKey)) this.clearExitConfirmation();
35560
+ return { consume: true };
35561
+ }
35487
35562
  if (outcome.kind === "activated") {
35488
35563
  if (outcome.consumed) return { consume: true };
35564
+ this.clearExitConfirmation();
35489
35565
  }
35566
+ if (outcome.kind === "cancelled-pass") this.clearExitConfirmation();
35490
35567
  }
35491
35568
  if (physicalEscape) return this.handleEscapeKey(data, this.keybindings.physicalEscapeEnabled());
35492
35569
  const resolution = this.keybindings.resolve(data, this.keybindingContext());
35493
35570
  let hostDeclined = false;
35494
35571
  if (resolution !== void 0) {
35495
35572
  if (resolution.owner === "host") {
35496
- if (this.dispatchResolvedAction(resolution.action, data, resolution.key)) return { consume: true };
35573
+ if (this.dispatchResolvedAction(resolution.action, data, resolution.key, void 0, resolution.source)) return { consume: true };
35497
35574
  hostDeclined = true;
35498
35575
  }
35499
35576
  }
@@ -35502,20 +35579,32 @@ var TuiApp = class TuiApp {
35502
35579
  hostDeclined
35503
35580
  };
35504
35581
  const replacement = this.seatEditor().handleInput;
35505
- if (context.hasOverlay) return void 0;
35506
- if (this.advancedInputRoute !== void 0 && this.advancedInputRoute(data) === "consumed") return { consume: true };
35582
+ if (context.hasOverlay) {
35583
+ this.clearExitConfirmation();
35584
+ return;
35585
+ }
35586
+ if (this.advancedInputRoute !== void 0 && this.advancedInputRoute(data) === "consumed") {
35587
+ this.clearExitConfirmation();
35588
+ return { consume: true };
35589
+ }
35507
35590
  const route = this.inputRouter.route(data, context, (key) => this.pluginActionForFor(key));
35508
35591
  if (route.kind === "protocol") return void 0;
35509
35592
  if (route.kind === "consumed") {
35510
- if (context.hasOverlay) return void 0;
35593
+ if (context.hasOverlay) {
35594
+ this.clearExitConfirmation();
35595
+ return;
35596
+ }
35597
+ this.clearExitConfirmation();
35511
35598
  return replacement === void 0 ? void 0 : { consume: true };
35512
35599
  }
35513
35600
  if (replacement !== void 0 && (route.kind === "editor" || route.kind === "viewer-editor")) return this.handleReplacementEditorInput(data, context, false);
35514
35601
  if ((route.kind === "editor" || route.kind === "viewer-editor") && replacement === void 0) {
35602
+ this.clearExitConfirmation();
35515
35603
  if (this.seatEditor().id !== "host") return { consume: true };
35516
35604
  return;
35517
35605
  }
35518
35606
  if (route.kind === "plugin-action") {
35607
+ this.clearExitConfirmation();
35519
35608
  try {
35520
35609
  this.events.onExtensionAction?.(route.action);
35521
35610
  this.recoverKeybinding(route.key);
@@ -35536,15 +35625,27 @@ var TuiApp = class TuiApp {
35536
35625
  * a user remap of app.agent.interrupt adds a semantic interrupt trigger
35537
35626
  * whose remapped key bypasses physical-editor Escape seams. Returns whether
35538
35627
  * the key was consumed. */
35539
- dispatchResolvedAction(action, data, key) {
35628
+ dispatchResolvedAction(action, data, key, trigger, source) {
35540
35629
  if (action === "app.agent.interrupt") {
35630
+ this.clearExitConfirmation();
35541
35631
  if (matchesKey(data, "escape")) return this.handleEscapeKey(data) !== void 0;
35542
35632
  return this.handleInterruptAction(data) !== void 0;
35543
35633
  }
35544
35634
  if (action === "app.exit.request") {
35545
- this.handleExitKey(data);
35635
+ if (key === "ctrl+d" && trigger === void 0 && source === "builtin" && this.seatEditor().getText() !== "") {
35636
+ this.clearExitConfirmation();
35637
+ return false;
35638
+ }
35639
+ if (key === void 0) return true;
35640
+ this.handleExitRequest(trigger ?? {
35641
+ identity: `direct:${key}`,
35642
+ key,
35643
+ label: formatKeyId(key),
35644
+ clearsDraft: key === "ctrl+c"
35645
+ });
35546
35646
  return true;
35547
35647
  }
35648
+ this.clearExitConfirmation();
35548
35649
  if (action === "app.input.submit" && data !== "") return false;
35549
35650
  return this.actionDispatcher.dispatch(action, key);
35550
35651
  }
@@ -35567,6 +35668,7 @@ var TuiApp = class TuiApp {
35567
35668
  return this.keybindings.editorSubmitKeysFor().some((key) => matchesKey(data, key));
35568
35669
  }
35569
35670
  handleInterruptAction(data) {
35671
+ this.clearExitConfirmation();
35570
35672
  if (this.events.onSingleEscape?.() === true) {
35571
35673
  this.lastEscapeAt = void 0;
35572
35674
  return { consume: true };
@@ -35592,6 +35694,7 @@ var TuiApp = class TuiApp {
35592
35694
  * preserving the remapped trigger's consecutive-press contract. Returns
35593
35695
  * undefined when the key must fall through. */
35594
35696
  handleEscapeKey(data, includeInterrupt = true) {
35697
+ this.clearExitConfirmation();
35595
35698
  if (includeInterrupt && this.busy) {
35596
35699
  this.lastEscapeAt = void 0;
35597
35700
  this.events.onCancel?.();
@@ -35611,30 +35714,28 @@ var TuiApp = class TuiApp {
35611
35714
  if (!includeInterrupt) return void 0;
35612
35715
  return this.handleInterruptAction(data);
35613
35716
  }
35614
- /** The exit path (app.exit.request): Ctrl+C keeps the clear-then-exit
35615
- * chord (a first press clears a non-empty draft and arms the window; a
35616
- * second press within it exits); every other key bound to the action
35617
- * (Ctrl+D or a user remap) exits immediately. */
35618
- handleExitKey(data) {
35619
- if (matchesKey(data, "ctrl+c")) {
35717
+ /** The keyboard exit path (app.exit.request). Every effective exit key
35718
+ * needs a second press of the SAME key within the confirmation window.
35719
+ * Ctrl+C additionally keeps its pi-parity clear-then-confirm behavior;
35720
+ * other exit keys preserve the draft on their first press. */
35721
+ handleExitRequest(trigger) {
35722
+ if (trigger.clearsDraft) {
35620
35723
  if (this.seatEditor().getText() !== "" || this.seatInputMode() !== "prompt") {
35621
35724
  this.seatEditor().setText("");
35622
35725
  this.resetEditorMode();
35623
35726
  this.editorSeatHolder.notifyChanged();
35624
- this.armCtrlCExit();
35727
+ this.armExitConfirmation(trigger);
35625
35728
  this.requestRender();
35626
- return { consume: true };
35729
+ return;
35627
35730
  }
35628
- const now = Date.now();
35629
- if (this.lastCtrlCAt !== void 0 && now - this.lastCtrlCAt < this.ctrlCExitWindowMs) {
35630
- this.clearCtrlCExit();
35631
- this.events.onExit();
35632
- } else this.armCtrlCExit();
35633
- return { consume: true };
35634
35731
  }
35635
- this.clearCtrlCExit();
35636
- this.events.onExit();
35637
- return { consume: true };
35732
+ const now = Date.now();
35733
+ if (this.exitConfirmationMatches(trigger, now)) {
35734
+ this.clearExitConfirmation();
35735
+ this.events.onExit();
35736
+ return;
35737
+ }
35738
+ this.armExitConfirmation(trigger);
35638
35739
  }
35639
35740
  /** The semantic action → host method surface (plan §9). The dispatcher
35640
35741
  * never re-implements business state — every method calls the existing
@@ -35848,6 +35949,7 @@ var TuiApp = class TuiApp {
35848
35949
  unfocus: () => {},
35849
35950
  isFocused: () => false
35850
35951
  };
35952
+ this.clearExitConfirmation();
35851
35953
  this.keybindings.cancelLeader();
35852
35954
  const merged = ownership.remountable === true ? {
35853
35955
  ...options,
@@ -36072,6 +36174,7 @@ var TuiApp = class TuiApp {
36072
36174
  */
36073
36175
  setFullscreen(enabled) {
36074
36176
  if (this.disposed) return;
36177
+ this.clearExitConfirmation();
36075
36178
  this.keybindings.cancelLeader();
36076
36179
  if (enabled === (this.fullscreen !== void 0)) return;
36077
36180
  const pending = this.activeApproval;
@@ -36310,10 +36413,13 @@ var TuiApp = class TuiApp {
36310
36413
  * @param activities - the same fold state's per-turn Focus activities
36311
36414
  * (plan §19: messages and activities must come from ONE fold snapshot,
36312
36415
  * so a repaint never shows a stale header against fresh rows).
36416
+ * @param streamingToolPreviews - live-only preparing rows for the current
36417
+ * presentation owner; omitted by legacy callers and never persisted.
36313
36418
  */
36314
- setTranscript(messages, activities, window) {
36419
+ setTranscript(messages, activities, window, streamingToolPreviews) {
36315
36420
  this.messages = messages;
36316
36421
  if (activities !== void 0) this.turnActivities = activities;
36422
+ this.streamingToolPreviews = [...streamingToolPreviews ?? []];
36317
36423
  this.transcriptWindow = window;
36318
36424
  this.refreshTranscriptWindowHint();
36319
36425
  this.rebuildMessages();
@@ -36709,6 +36815,90 @@ var TuiApp = class TuiApp {
36709
36815
  projectedBlocks(projectionExpanded) {
36710
36816
  return projectFocus(this.messages, this.turnActivities, projectionExpanded, this.focusModeEnabled);
36711
36817
  }
36818
+ /** The live Preparing snapshot for one Focus turn, kept in model order. */
36819
+ streamingToolPreviewsForTurn(turn) {
36820
+ return this.streamingToolPreviews.filter((preview) => preview.turn === turn).sort((left, right) => left.index - right.index || left.step - right.step || left.callId.localeCompare(right.callId));
36821
+ }
36822
+ /** Find the presentation insertion point for one expanded Thought. The
36823
+ * process rows carry the existing owner marker; the final assistant is held
36824
+ * back by projectFocus, so a preview block lands after process content and
36825
+ * before that final answer. A turn without a projected activity is ignored
36826
+ * rather than attaching a preview to an unrelated Thought. */
36827
+ focusPreparingInsertionIndex(blocks, turn) {
36828
+ let activityIndex = -1;
36829
+ let lastTurnIndex = -1;
36830
+ let lastProcessIndex = -1;
36831
+ let finalAssistantIndex = -1;
36832
+ for (let index = 0; index < blocks.length; index += 1) {
36833
+ const block = blocks[index];
36834
+ if (block.kind === "activity") {
36835
+ if (block.activity.turn === turn) {
36836
+ activityIndex = index;
36837
+ lastTurnIndex = index;
36838
+ }
36839
+ continue;
36840
+ }
36841
+ if (block.kind === "streaming-tool-previews") continue;
36842
+ if (!("turn" in block.message) || block.message.turn !== turn) continue;
36843
+ lastTurnIndex = index;
36844
+ if (block.collapseFocusOwnerOnClick === turn) lastProcessIndex = index;
36845
+ if (block.message.kind === "assistant" && block.collapseFocusOwnerOnClick === void 0) finalAssistantIndex = index;
36846
+ }
36847
+ if (activityIndex === -1) return void 0;
36848
+ if (lastProcessIndex !== -1) return lastProcessIndex + 1;
36849
+ if (finalAssistantIndex !== -1) return finalAssistantIndex;
36850
+ return lastTurnIndex + 1;
36851
+ }
36852
+ /** Build the rendered transcript blocks in visual order. Focus keeps the
36853
+ * ephemeral Preparing state outside the durable projection: collapsed rows
36854
+ * are summarized by their Thought header, while expanded rows are inserted
36855
+ * into the owning turn's process tail. Focus OFF retains the original
36856
+ * standalone block between the durable projection and local cards. */
36857
+ transcriptBlocks(projectionExpanded) {
36858
+ const blocks = [...this.projectedBlocks(projectionExpanded)];
36859
+ if (!this.focusModeEnabled) {
36860
+ if (this.streamingToolPreviews.length > 0) blocks.push({
36861
+ kind: "streaming-tool-previews",
36862
+ previews: this.streamingToolPreviews
36863
+ });
36864
+ } else if (this.streamingToolPreviews.length > 0) {
36865
+ const previewTurns = new Set(this.streamingToolPreviews.map((preview) => preview.turn));
36866
+ for (const turn of previewTurns) {
36867
+ if (!projectionExpanded.has(turn)) continue;
36868
+ const previews = this.streamingToolPreviewsForTurn(turn);
36869
+ if (previews.length === 0) continue;
36870
+ const insertion = this.focusPreparingInsertionIndex(blocks, turn);
36871
+ if (insertion === void 0) continue;
36872
+ blocks.splice(insertion, 0, {
36873
+ kind: "streaming-tool-previews",
36874
+ turn,
36875
+ previews,
36876
+ collapseFocusOwnerOnClick: turn
36877
+ });
36878
+ }
36879
+ }
36880
+ blocks.push(...this.localMessages.map((message) => ({
36881
+ kind: "message",
36882
+ message
36883
+ })));
36884
+ return blocks;
36885
+ }
36886
+ /** The expanded Focus owner used only for the spacer immediately before a
36887
+ * live Preparing block. The block itself still has no message/activity, so
36888
+ * its content remains inert in the fullscreen hit map. */
36889
+ focusOwnerForRenderBlock(block) {
36890
+ return block.kind === "message" || block.kind === "streaming-tool-previews" ? block.collapseFocusOwnerOnClick : void 0;
36891
+ }
36892
+ /** Render the live preparing rows with the current semantic tool icon and
36893
+ * title mappings. The arguments stream is intentionally not retained or
36894
+ * parsed; only the tool identity is shown. */
36895
+ streamingToolPreviewComponent(previews) {
36896
+ return new Text(previews.map((preview) => {
36897
+ const name = preview.name ?? "";
36898
+ const prefix = iconPrefix(toolIconSemantic(name), this.iconStyle);
36899
+ return color.textDim(`${prefix}Preparing ${toolTitle(name)}...`);
36900
+ }).join("\n"), 0, 0);
36901
+ }
36712
36902
  /** The precomputed Focus Tool-line display for one activity: presenter-
36713
36903
  * first, static fallback second (plan §38/§9.4). The FocusActivityComponent
36714
36904
  * stays a pure renderer — the presentation bridge lives here. */
@@ -36724,17 +36914,18 @@ var TuiApp = class TuiApp {
36724
36914
  * when the activity OBJECT changed (session/viewer switches mint fresh
36725
36915
  * folder objects, so the same turn number from another session can
36726
36916
  * never reuse this one's component), the activity revision moved, the
36727
- * disclosure flipped, the theme switched, or the precomputed Tool
36728
- * display changed (plan §39). render() re-reads Date.now() every frame,
36729
- * so the running duration is always live. */
36730
- focusActivityComponentFor(activity, expanded, toolDisplay) {
36917
+ * disclosure flipped, the theme switched, the precomputed Tool display
36918
+ * changed, or the live Preparing summary changed (plan §39). render()
36919
+ * re-reads Date.now() every frame, so the running duration is always live. */
36920
+ focusActivityComponentFor(activity, expanded, toolDisplay, preparingSummary) {
36731
36921
  const entry = this.focusActivityComponents.get(activity.turn);
36732
- if (entry !== void 0 && entry.activity === activity && entry.revision === activity.revision && entry.expanded === expanded && entry.themeRev === this.themeRevision && entry.iconStyle === this.iconStyle && entry.toolDisplay === toolDisplay) return entry.component;
36922
+ if (entry !== void 0 && entry.activity === activity && entry.revision === activity.revision && entry.expanded === expanded && entry.themeRev === this.themeRevision && entry.iconStyle === this.iconStyle && entry.toolDisplay === toolDisplay && entry.preparingSummary === preparingSummary) return entry.component;
36733
36923
  const component = new FocusActivityComponent({
36734
36924
  activity,
36735
36925
  expanded,
36736
36926
  toolDisplay,
36737
- iconStyle: this.iconStyle
36927
+ iconStyle: this.iconStyle,
36928
+ preparingSummary
36738
36929
  });
36739
36930
  this.focusActivityComponents.set(activity.turn, {
36740
36931
  activity,
@@ -36743,7 +36934,8 @@ var TuiApp = class TuiApp {
36743
36934
  expanded,
36744
36935
  themeRev: this.themeRevision,
36745
36936
  iconStyle: this.iconStyle,
36746
- toolDisplay
36937
+ toolDisplay,
36938
+ preparingSummary
36747
36939
  });
36748
36940
  return component;
36749
36941
  }
@@ -36763,17 +36955,18 @@ var TuiApp = class TuiApp {
36763
36955
  const boundary = this.expandBoundary();
36764
36956
  const width = this.transcriptRenderWidth();
36765
36957
  const rows = [];
36766
- const blocks = [...this.projectedBlocks(projectionExpanded), ...this.localMessages.map((message) => ({
36767
- kind: "message",
36768
- message
36769
- }))];
36958
+ const blocks = this.transcriptBlocks(projectionExpanded);
36770
36959
  blocks.forEach((block, index) => {
36771
36960
  let component;
36772
36961
  let rendered;
36773
36962
  let truncatedMarker = false;
36774
36963
  let attachments = [];
36964
+ const collapseFocusOwnerOnClick = this.focusOwnerForRenderBlock(block);
36775
36965
  if (block.kind === "activity") {
36776
- component = this.focusActivityComponentFor(block.activity, projectionExpanded.has(block.activity.turn), this.focusToolDisplayFor(block.activity));
36966
+ component = this.focusActivityComponentFor(block.activity, projectionExpanded.has(block.activity.turn), this.focusToolDisplayFor(block.activity), this.focusModeEnabled && !projectionExpanded.has(block.activity.turn) ? focusPreparingSummary(this.streamingToolPreviewsForTurn(block.activity.turn)) : void 0);
36967
+ rendered = component.render(width);
36968
+ } else if (block.kind === "streaming-tool-previews") {
36969
+ component = this.streamingToolPreviewComponent(block.previews);
36777
36970
  rendered = component.render(width);
36778
36971
  } else {
36779
36972
  component = this.componentForMessage(block.message, boundary, width);
@@ -36783,8 +36976,8 @@ var TuiApp = class TuiApp {
36783
36976
  }
36784
36977
  if (rendered.length === 0 && !truncatedMarker) {
36785
36978
  rows.push({
36786
- ...block.kind === "message" ? { message: block.message } : { activity: block.activity },
36787
- ...block.kind === "message" && block.collapseFocusOwnerOnClick !== void 0 ? { collapseFocusOwnerOnClick: block.collapseFocusOwnerOnClick } : {},
36979
+ ...block.kind === "message" ? { message: block.message } : block.kind === "activity" ? { activity: block.activity } : {},
36980
+ ...collapseFocusOwnerOnClick !== void 0 ? { collapseFocusOwnerOnClick } : {},
36788
36981
  height: 0,
36789
36982
  attachments: [],
36790
36983
  hasTrailingSpacer: false
@@ -36798,8 +36991,8 @@ var TuiApp = class TuiApp {
36798
36991
  }
36799
36992
  const height = rendered.length + (truncatedMarker ? 1 : 0) + (index < blocks.length - 1 ? 1 : 0);
36800
36993
  rows.push({
36801
- ...block.kind === "message" ? { message: block.message } : { activity: block.activity },
36802
- ...block.kind === "message" && block.collapseFocusOwnerOnClick !== void 0 ? { collapseFocusOwnerOnClick: block.collapseFocusOwnerOnClick } : {},
36994
+ ...block.kind === "message" ? { message: block.message } : block.kind === "activity" ? { activity: block.activity } : {},
36995
+ ...collapseFocusOwnerOnClick !== void 0 ? { collapseFocusOwnerOnClick } : {},
36803
36996
  height,
36804
36997
  attachments,
36805
36998
  hasTrailingSpacer: index < blocks.length - 1
@@ -36894,18 +37087,19 @@ var TuiApp = class TuiApp {
36894
37087
  const width = this.transcriptRenderWidth();
36895
37088
  const boundary = this.expandBoundary();
36896
37089
  const projectionExpanded = this.focusProjectionExpandedTurns();
36897
- const blocks = [...this.projectedBlocks(projectionExpanded), ...this.localMessages.map((message) => ({
36898
- kind: "message",
36899
- message
36900
- }))];
37090
+ const blocks = this.transcriptBlocks(projectionExpanded);
36901
37091
  const rows = [];
36902
37092
  blocks.forEach((block, index) => {
36903
37093
  let component;
36904
37094
  let rendered;
36905
37095
  let truncatedMarker = false;
36906
37096
  let attachments = [];
37097
+ const collapseFocusOwnerOnClick = this.focusOwnerForRenderBlock(block);
36907
37098
  if (block.kind === "activity") {
36908
- component = this.focusActivityComponentFor(block.activity, projectionExpanded.has(block.activity.turn), this.focusToolDisplayFor(block.activity));
37099
+ component = this.focusActivityComponentFor(block.activity, projectionExpanded.has(block.activity.turn), this.focusToolDisplayFor(block.activity), this.focusModeEnabled && !projectionExpanded.has(block.activity.turn) ? focusPreparingSummary(this.streamingToolPreviewsForTurn(block.activity.turn)) : void 0);
37100
+ rendered = component.render(width);
37101
+ } else if (block.kind === "streaming-tool-previews") {
37102
+ component = this.streamingToolPreviewComponent(block.previews);
36909
37103
  rendered = component.render(width);
36910
37104
  } else {
36911
37105
  component = this.componentForMessage(block.message, boundary, width);
@@ -36915,8 +37109,8 @@ var TuiApp = class TuiApp {
36915
37109
  }
36916
37110
  if (rendered.length === 0 && !truncatedMarker) {
36917
37111
  rows.push({
36918
- ...block.kind === "message" ? { message: block.message } : { activity: block.activity },
36919
- ...block.kind === "message" && block.collapseFocusOwnerOnClick !== void 0 ? { collapseFocusOwnerOnClick: block.collapseFocusOwnerOnClick } : {},
37112
+ ...block.kind === "message" ? { message: block.message } : block.kind === "activity" ? { activity: block.activity } : {},
37113
+ ...collapseFocusOwnerOnClick !== void 0 ? { collapseFocusOwnerOnClick } : {},
36920
37114
  height: 0,
36921
37115
  attachments: [],
36922
37116
  hasTrailingSpacer: false
@@ -36925,8 +37119,8 @@ var TuiApp = class TuiApp {
36925
37119
  }
36926
37120
  const height = rendered.length + (truncatedMarker ? 1 : 0) + (index < blocks.length - 1 ? 1 : 0);
36927
37121
  rows.push({
36928
- ...block.kind === "message" ? { message: block.message } : { activity: block.activity },
36929
- ...block.kind === "message" && block.collapseFocusOwnerOnClick !== void 0 ? { collapseFocusOwnerOnClick: block.collapseFocusOwnerOnClick } : {},
37122
+ ...block.kind === "message" ? { message: block.message } : block.kind === "activity" ? { activity: block.activity } : {},
37123
+ ...collapseFocusOwnerOnClick !== void 0 ? { collapseFocusOwnerOnClick } : {},
36930
37124
  height,
36931
37125
  attachments,
36932
37126
  hasTrailingSpacer: index < blocks.length - 1
@@ -37372,41 +37566,46 @@ var TuiApp = class TuiApp {
37372
37566
  this.rebuildMessages();
37373
37567
  }
37374
37568
  /**
37375
- * Issue #8: arm the empty-editor double-Ctrl+C exit window. The footer's
37376
- * second line switches to `Press Ctrl+C again to exit` for EXACTLY
37377
- * {@link ctrlCExitWindowMs} — the hint and the exit window share ONE
37378
- * timer, so a stale hint can never outlive a dead window (the old
37379
- * notify-based hint lingered ~8s while the window was already gone).
37569
+ * Issue #8: arm the keyboard exit-confirmation window for one effective
37570
+ * trigger. The footer hint and the exit window share ONE timer, so a stale hint
37571
+ * can never outlive a dead confirmation. A timer identity check also makes
37572
+ * an already-queued callback from an older arm harmless after replacement.
37380
37573
  */
37381
- armCtrlCExit() {
37382
- this.clearCtrlCExit();
37383
- this.lastCtrlCAt = Date.now();
37384
- this.ctrlCExitArmed = true;
37574
+ armExitConfirmation(trigger) {
37575
+ this.clearExitConfirmation();
37576
+ const armedAt = Date.now();
37577
+ this.exitConfirmTrigger = trigger;
37578
+ this.exitConfirmAt = armedAt;
37385
37579
  this.renderFooter();
37386
- this.ctrlCExitTimer = setTimeout(() => {
37387
- this.ctrlCExitTimer = void 0;
37388
- this.lastCtrlCAt = void 0;
37389
- this.ctrlCExitArmed = false;
37580
+ const timer = setTimeout(() => {
37581
+ if (this.exitConfirmTimer !== timer || this.exitConfirmAt !== armedAt) return;
37582
+ this.exitConfirmTimer = void 0;
37583
+ this.exitConfirmTrigger = void 0;
37584
+ this.exitConfirmAt = void 0;
37390
37585
  this.renderFooter();
37391
37586
  this.requestRender();
37392
- }, this.ctrlCExitWindowMs);
37393
- this.ctrlCExitTimer.unref?.();
37587
+ }, this.exitConfirmWindowMs);
37588
+ this.exitConfirmTimer = timer;
37589
+ this.exitConfirmTimer.unref?.();
37590
+ }
37591
+ /** Whether the current press is the armed trigger inside the live window. */
37592
+ exitConfirmationMatches(trigger, now) {
37593
+ return this.exitConfirmTrigger?.identity === trigger.identity && this.exitConfirmAt !== void 0 && now >= this.exitConfirmAt && now - this.exitConfirmAt < this.exitConfirmWindowMs;
37394
37594
  }
37395
37595
  /**
37396
- * Issue #8: disarm the exit chord and restore the footer. Public so the
37397
- * runner clears it on session switches (a stale armed window must not
37398
- * exit the NEW session). Idempotent; safe after dispose (renderFooter
37399
- * requests are benign no-ops then).
37596
+ * Issue #8: disarm the keyboard exit confirmation and restore the footer.
37597
+ * Public so the runner clears it on session switches (a stale armed window
37598
+ * must not exit the NEW session). Idempotent; safe after dispose.
37400
37599
  */
37401
- clearCtrlCExit() {
37402
- if (this.ctrlCExitTimer !== void 0) {
37403
- clearTimeout(this.ctrlCExitTimer);
37404
- this.ctrlCExitTimer = void 0;
37600
+ clearExitConfirmation() {
37601
+ if (this.exitConfirmTimer !== void 0) {
37602
+ clearTimeout(this.exitConfirmTimer);
37603
+ this.exitConfirmTimer = void 0;
37405
37604
  }
37406
- if (this.ctrlCExitArmed || this.lastCtrlCAt !== void 0) {
37407
- this.ctrlCExitArmed = false;
37408
- this.lastCtrlCAt = void 0;
37409
- this.renderFooter();
37605
+ if (this.exitConfirmTrigger !== void 0 || this.exitConfirmAt !== void 0) {
37606
+ this.exitConfirmTrigger = void 0;
37607
+ this.exitConfirmAt = void 0;
37608
+ if (this.footer !== void 0) this.renderFooter();
37410
37609
  }
37411
37610
  }
37412
37611
  /**
@@ -37423,6 +37622,7 @@ var TuiApp = class TuiApp {
37423
37622
  * exit).
37424
37623
  */
37425
37624
  setEditorText(text) {
37625
+ this.clearExitConfirmation();
37426
37626
  const target = this.viewerMode;
37427
37627
  if (target !== void 0 && isViewerAccessInteractive(resolveViewerAccess(target.mode, target.access))) {
37428
37628
  this.subagentDrafts.set(target.childSessionId, text);
@@ -37448,6 +37648,7 @@ var TuiApp = class TuiApp {
37448
37648
  * in the viewer is a later milestone — text-only follow-ups for now).
37449
37649
  */
37450
37650
  insertIntoEditor(text) {
37651
+ this.clearExitConfirmation();
37451
37652
  const target = this.viewerMode;
37452
37653
  if (target !== void 0 && isViewerAccessInteractive(resolveViewerAccess(target.mode, target.access))) {
37453
37654
  const editor = this.seatEditor();
@@ -37481,6 +37682,7 @@ var TuiApp = class TuiApp {
37481
37682
  * @param mode - the viewer target; `undefined` leaves the viewer.
37482
37683
  */
37483
37684
  setViewerMode(mode) {
37685
+ this.clearExitConfirmation();
37484
37686
  this.keybindings.cancelLeader();
37485
37687
  if (mode === void 0) {
37486
37688
  if (this.viewerMode === void 0) return;
@@ -37639,7 +37841,6 @@ var TuiApp = class TuiApp {
37639
37841
  const serialized = this.expandedSeatWireDraft();
37640
37842
  if (serialized.trim() === "") return;
37641
37843
  this.clearNotify();
37642
- this.clearCtrlCExit();
37643
37844
  this.subagentDrafts.set(target.childSessionId, "");
37644
37845
  this.seatEditor().setText("");
37645
37846
  this.editorSeatHolder.notifyChanged();
@@ -40011,6 +40212,7 @@ var TuiApp = class TuiApp {
40011
40212
  * exit).
40012
40213
  */
40013
40214
  setDraft(text) {
40215
+ this.clearExitConfirmation();
40014
40216
  const target = this.viewerMode;
40015
40217
  if (target !== void 0 && isViewerAccessInteractive(resolveViewerAccess(target.mode, target.access))) {
40016
40218
  this.subagentDrafts.set(target.childSessionId, text);
@@ -40057,12 +40259,12 @@ var TuiApp = class TuiApp {
40057
40259
  * the busyEnter preference.
40058
40260
  */
40059
40261
  submitDraft(forceQueue = false) {
40262
+ this.clearExitConfirmation();
40060
40263
  if (this.disposed) return;
40061
40264
  const target = this.viewerMode;
40062
40265
  const serialized = this.getDraft();
40063
40266
  if (serialized.trim() === "" && this.events.isImageDraft?.() !== true) return;
40064
40267
  this.clearNotify();
40065
- this.clearCtrlCExit();
40066
40268
  if (target !== void 0 && isViewerAccessInteractive(resolveViewerAccess(target.mode, target.access))) {
40067
40269
  this.subagentDrafts.set(target.childSessionId, "");
40068
40270
  this.seatEditor().setText("");
@@ -40251,7 +40453,7 @@ var TuiApp = class TuiApp {
40251
40453
  }
40252
40454
  /** Rebuild the footer from the unified status snapshot (M1): the
40253
40455
  * composer renders the active layout (default/compact) against the
40254
- * snapshot, and the Host instruction surface owns the Ctrl+C exit hint.
40456
+ * snapshot, and the Host instruction surface owns the keyboard exit hint.
40255
40457
  * The TuiApp no longer derives permission/plan/viewer/usage — the items
40256
40458
  * do, from the snapshot. An empty surface (every item unavailable, e.g.
40257
40459
  * an unloaded extension item) renders zero rows: the fork's Text emits
@@ -40262,7 +40464,7 @@ var TuiApp = class TuiApp {
40262
40464
  const width = Math.max(1, this.terminal.columns);
40263
40465
  const leader = this.keybindings.leaderMachine();
40264
40466
  const instruction = resolveFooterInstruction({
40265
- ctrlCExitArmed: this.ctrlCExitArmed,
40467
+ exitConfirmKeyLabel: this.exitConfirmTrigger?.label,
40266
40468
  viewing: this.viewerFooter !== void 0,
40267
40469
  leaderHint: leader !== void 0 && leader.pending ? this.leaderHint(leader) : void 0
40268
40470
  });
@@ -41337,6 +41539,45 @@ function startProcessTui(events, options = {}) {
41337
41539
  return app;
41338
41540
  }
41339
41541
  //#endregion
41542
+ //#region src/streaming-tool-preparing.ts
41543
+ function fallbackPreviewKey(turn, step, index) {
41544
+ return `\u0000streaming-tool-preview:${turn}:${step}:${index}`;
41545
+ }
41546
+ function previewAt(previews, turn, step, index) {
41547
+ for (const entry of previews) if (entry[1].turn === turn && entry[1].step === step && entry[1].index === index) return entry;
41548
+ }
41549
+ /** Upsert one local preview, preserving a delayed name and stable position. */
41550
+ function upsertStreamingToolPreview(previews, preview) {
41551
+ const previous = previewAt(previews, preview.turn, preview.step, preview.index);
41552
+ const key = preview.callId === "" ? previous?.[0] ?? fallbackPreviewKey(preview.turn, preview.step, preview.index) : preview.callId;
41553
+ if (previous !== void 0 && previous[0] !== key) previews.delete(previous[0]);
41554
+ const existing = previews.get(key);
41555
+ previews.set(key, {
41556
+ callId: preview.callId === "" ? existing?.callId ?? previous?.[1].callId ?? preview.callId : preview.callId,
41557
+ turn: preview.turn,
41558
+ step: preview.step,
41559
+ index: preview.index,
41560
+ name: preview.name ?? existing?.name ?? previous?.[1].name
41561
+ });
41562
+ }
41563
+ /** Remove the local preview represented by one formal tool call. */
41564
+ function removeStreamingToolPreview(previews, callId, turn, step) {
41565
+ previews.delete(callId);
41566
+ for (const [key, preview] of previews) if (preview.turn === turn && preview.step === step && preview.callId === callId) previews.delete(key);
41567
+ }
41568
+ /** Clear all orphan previews owned by one step. */
41569
+ function clearStreamingToolPreviewsForStep(previews, turn, step) {
41570
+ for (const [key, preview] of previews) if (preview.turn === turn && preview.step === step) previews.delete(key);
41571
+ }
41572
+ /** Clear all previews owned by one turn. */
41573
+ function clearStreamingToolPreviewsForTurn(previews, turn) {
41574
+ for (const [key, preview] of previews) if (preview.turn === turn) previews.delete(key);
41575
+ }
41576
+ /** Return a stable model-order snapshot for one presentation owner. */
41577
+ function streamingToolPreviewSnapshot(previews) {
41578
+ return [...previews.values()].sort((left, right) => left.index - right.index);
41579
+ }
41580
+ //#endregion
41340
41581
  //#region src/extension/internal/batcher.ts
41341
41582
  /** Coalescing invalidate dispatcher. `flush()` is idempotent per tick. */
41342
41583
  var InvalidateBatcher = class {
@@ -42925,7 +43166,8 @@ const TUI_ACTIONS = /* @__PURE__ */ new Set([
42925
43166
  * binds it), and the user-orchestrable keymap (src/keybindings/
42926
43167
  * definitions.ts) owns the effective keys. This list exists ONLY to
42927
43168
  * reject plugin registrations on the host's DEFAULT lifecycle keys
42928
- * (Ctrl+C/D exit, Ctrl+S steer-all, Ctrl+F search, Ctrl+O expand, Ctrl+T
43169
+ * (default keyboard exit confirmation, Ctrl+S steer-all, Ctrl+F search,
43170
+ * Ctrl+O expand, Ctrl+T
42929
43171
  * todo, Ctrl+G external editor, Ctrl+R history search, Ctrl+V clipboard,
42930
43172
  * Ctrl+Enter queue, Enter submit, Esc cancel, Shift+Tab permission,
42931
43173
  * Alt+Up dequeue, Alt+T thinking, Alt+K dismiss, Ctrl+End jump latest).
@@ -57126,12 +57368,52 @@ function bundleVersion() {
57126
57368
  return "0.0.0";
57127
57369
  }
57128
57370
  }
57371
+ /** Translate official DSH events into the local preview operations. */
57372
+ function applyStreamingToolPreviewEvent(previews, event) {
57373
+ if (event.type === "assistant/chunk" && event.data.chunk.type === "tool-call-delta") {
57374
+ const chunk = event.data.chunk;
57375
+ upsertStreamingToolPreview(previews, {
57376
+ callId: chunk.id,
57377
+ turn: event.data.turn,
57378
+ step: event.data.step,
57379
+ index: chunk.index,
57380
+ name: chunk.name
57381
+ });
57382
+ return;
57383
+ }
57384
+ if (event.type === "assistant/chunk" && event.data.chunk.type === "block-end" && event.data.chunk.block.type === "tool-call") {
57385
+ const chunk = event.data.chunk;
57386
+ const block = chunk.block;
57387
+ upsertStreamingToolPreview(previews, {
57388
+ callId: block.id,
57389
+ turn: event.data.turn,
57390
+ step: event.data.step,
57391
+ index: chunk.index,
57392
+ name: block.name
57393
+ });
57394
+ return;
57395
+ }
57396
+ if (event.type === "tool/call") {
57397
+ removeStreamingToolPreview(previews, event.data.callId, event.data.turn, event.data.step);
57398
+ return;
57399
+ }
57400
+ if (event.type === "llm/retry" || event.type === "llm/retry-started") {
57401
+ clearStreamingToolPreviewsForStep(previews, event.data.turn, event.data.step);
57402
+ return;
57403
+ }
57404
+ if (event.type === "step/end") {
57405
+ clearStreamingToolPreviewsForStep(previews, event.data.turn, event.data.step);
57406
+ return;
57407
+ }
57408
+ if (event.type === "turn/end") clearStreamingToolPreviewsForTurn(previews, event.data.turn);
57409
+ }
57129
57410
  /**
57130
57411
  * Repaint the transcript from the active folder's bounded window. Messages,
57131
- * navigation facts, and turn activities come from one fold snapshot so a
57132
- * repaint can never show a stale Thought header against fresh rows.
57412
+ * navigation facts, turn activities and live preparing rows come from one
57413
+ * presentation snapshot so a repaint can never show a stale Thought header
57414
+ * against fresh rows.
57133
57415
  */
57134
- function repaint(app, folder, windowController) {
57416
+ function repaint(app, folder, windowController, streamingToolPreviews) {
57135
57417
  windowController.setTurns(folder.groupedTurns());
57136
57418
  const endTurn = windowController.endTurn();
57137
57419
  const projection = folder.window({
@@ -57143,7 +57425,7 @@ function repaint(app, folder, windowController) {
57143
57425
  firstTurn: projection.firstTurn,
57144
57426
  lastTurn: projection.lastTurn,
57145
57427
  hasNewer: projection.hasNewer
57146
- });
57428
+ }, streamingToolPreviews);
57147
57429
  return projection;
57148
57430
  }
57149
57431
  /** Current git branch from the nearest .git/HEAD, or empty outside a checkout. */
@@ -58689,23 +58971,32 @@ function apply(ctx, config) {
58689
58971
  });
58690
58972
  };
58691
58973
  let repaintTimer;
58974
+ const mainStreamingToolPreviews = /* @__PURE__ */ new Map();
58692
58975
  let viewing;
58693
58976
  const pendingSubagentCalls = [];
58694
58977
  const viewCallToChild = /* @__PURE__ */ new Map();
58695
58978
  const activeFolder = () => viewing?.folder ?? folder;
58696
58979
  const activeWindow = () => viewing?.window ?? windowController;
58980
+ const activeStreamingToolPreviews = () => {
58981
+ if (!activeWindow().isLatest()) return [];
58982
+ return streamingToolPreviewSnapshot(viewing?.previews ?? mainStreamingToolPreviews);
58983
+ };
58984
+ const applyOwnerStreamingToolPreviewEvent = (previews, ownerFolder, event) => {
58985
+ if (event.type === "assistant/chunk" && ownerFolder.turnActivity(event.data.turn)?.completed === true) return;
58986
+ applyStreamingToolPreviewEvent(previews, event);
58987
+ };
58697
58988
  const paintNow = () => {
58698
58989
  if (repaintTimer !== void 0) {
58699
58990
  clearTimeout(repaintTimer);
58700
58991
  repaintTimer = void 0;
58701
58992
  }
58702
- repaint(app, activeFolder(), activeWindow());
58993
+ repaint(app, activeFolder(), activeWindow(), activeStreamingToolPreviews());
58703
58994
  };
58704
58995
  const schedulePaint = () => {
58705
58996
  if (repaintTimer !== void 0) return;
58706
58997
  repaintTimer = setTimeout(() => {
58707
58998
  repaintTimer = void 0;
58708
- repaint(app, activeFolder(), activeWindow());
58999
+ repaint(app, activeFolder(), activeWindow(), activeStreamingToolPreviews());
58709
59000
  }, REPAINT_FLUSH_MS);
58710
59001
  };
58711
59002
  const callArgs = /* @__PURE__ */ new Map();
@@ -58727,6 +59018,7 @@ function apply(ctx, config) {
58727
59018
  const bumpSessionGeneration = () => {
58728
59019
  sessionGeneration += 1;
58729
59020
  callArgs.clear();
59021
+ mainStreamingToolPreviews.clear();
58730
59022
  pendingSubagentCalls.length = 0;
58731
59023
  viewCallToChild.clear();
58732
59024
  resetSearchState();
@@ -58758,7 +59050,7 @@ function apply(ctx, config) {
58758
59050
  app.setViewerMode(void 0);
58759
59051
  app.setViewerFooter(void 0);
58760
59052
  app.discardFocusViewerScope();
58761
- repaint(app, folder, windowController);
59053
+ repaint(app, folder, windowController, activeStreamingToolPreviews());
58762
59054
  windowController.isLatest() ? app.scrollToBottom() : app.scrollToTop({ disableFollow: true });
58763
59055
  refreshStatusCheap();
58764
59056
  });
@@ -58787,7 +59079,7 @@ function apply(ctx, config) {
58787
59079
  const folder = activeFolder();
58788
59080
  const controller = activeWindow();
58789
59081
  controller.anchorAt(match.turn);
58790
- repaint(app, folder, controller);
59082
+ repaint(app, folder, controller, activeStreamingToolPreviews());
58791
59083
  app.scrollToBottom({ disableFollow: !controller.isLatest() });
58792
59084
  if (app.isFocusModeEnabled()) {
58793
59085
  const message = folder.resolveSearchMatch(match);
@@ -58871,10 +59163,11 @@ function apply(ctx, config) {
58871
59163
  mode,
58872
59164
  activity,
58873
59165
  access,
58874
- cwd: childCwd
59166
+ cwd: childCwd,
59167
+ previews: /* @__PURE__ */ new Map()
58875
59168
  };
58876
59169
  app.enterFocusViewerScope();
58877
- repaint(app, childFolder, childWindow);
59170
+ repaint(app, childFolder, childWindow, activeStreamingToolPreviews());
58878
59171
  app.setViewerMode({
58879
59172
  parentSessionId,
58880
59173
  childSessionId: childId,
@@ -58893,6 +59186,7 @@ function apply(ctx, config) {
58893
59186
  const exitView = () => {
58894
59187
  viewerOpen.invalidate();
58895
59188
  if (viewing === void 0) return false;
59189
+ viewing.previews.clear();
58896
59190
  viewing = void 0;
58897
59191
  viewerSessionAbort?.abort();
58898
59192
  viewerSessionAbort = void 0;
@@ -58901,7 +59195,7 @@ function apply(ctx, config) {
58901
59195
  app.setViewerMode(void 0);
58902
59196
  app.setViewerFooter(void 0);
58903
59197
  app.exitFocusViewerScope();
58904
- repaint(app, folder, windowController);
59198
+ repaint(app, folder, windowController, activeStreamingToolPreviews());
58905
59199
  windowController.isLatest() ? app.scrollToBottom() : app.scrollToTop({ disableFollow: true });
58906
59200
  refreshStatusCheap();
58907
59201
  return true;
@@ -59748,7 +60042,7 @@ function apply(ctx, config) {
59748
60042
  const anchor = app.captureTranscriptViewportAnchor();
59749
60043
  const controller = activeWindow();
59750
60044
  if (!controller.moveOlder()) return false;
59751
- repaint(app, activeFolder(), controller);
60045
+ repaint(app, activeFolder(), controller, activeStreamingToolPreviews());
59752
60046
  if (anchor === void 0) app.scrollToBottom({ disableFollow: true });
59753
60047
  else app.restoreTranscriptViewportAnchor(anchor, "top");
59754
60048
  return true;
@@ -59757,7 +60051,7 @@ function apply(ctx, config) {
59757
60051
  if (!app.isFullscreen()) return false;
59758
60052
  const controller = activeWindow();
59759
60053
  if (!controller.turnOlder()) return false;
59760
- repaint(app, activeFolder(), controller);
60054
+ repaint(app, activeFolder(), controller, activeStreamingToolPreviews());
59761
60055
  app.scrollToBottom({ disableFollow: true });
59762
60056
  return true;
59763
60057
  },
@@ -59765,7 +60059,7 @@ function apply(ctx, config) {
59765
60059
  if (!app.isFullscreen()) return false;
59766
60060
  const controller = activeWindow();
59767
60061
  if (!controller.turnNewer()) return false;
59768
- repaint(app, activeFolder(), controller);
60062
+ repaint(app, activeFolder(), controller, activeStreamingToolPreviews());
59769
60063
  app.scrollToBottom({ disableFollow: true });
59770
60064
  return true;
59771
60065
  },
@@ -59773,7 +60067,7 @@ function apply(ctx, config) {
59773
60067
  const anchor = app.captureTranscriptViewportAnchor();
59774
60068
  const controller = activeWindow();
59775
60069
  if (!controller.moveNewer()) return false;
59776
- repaint(app, activeFolder(), controller);
60070
+ repaint(app, activeFolder(), controller, activeStreamingToolPreviews());
59777
60071
  if (controller.isLatest()) app.scrollToBottom();
59778
60072
  else if (anchor === void 0) app.scrollToTop({ disableFollow: true });
59779
60073
  else app.restoreTranscriptViewportAnchor(anchor, "bottom");
@@ -59786,7 +60080,7 @@ function apply(ctx, config) {
59786
60080
  const closedSearch = app.closeTranscriptSearch();
59787
60081
  const controller = activeWindow();
59788
60082
  if (!controller.latest() && !closedSearch && !app.isFullscreen()) return false;
59789
- repaint(app, activeFolder(), controller);
60083
+ repaint(app, activeFolder(), controller, activeStreamingToolPreviews());
59790
60084
  app.scrollToBottom();
59791
60085
  app.setSearchResult(0, 0);
59792
60086
  return true;
@@ -59865,7 +60159,7 @@ function apply(ctx, config) {
59865
60159
  const controller = activeWindow();
59866
60160
  if (origin?.controller === controller && origin.state.mode === "history" && origin.state.endTurn !== void 0) controller.anchorAt(origin.state.endTurn);
59867
60161
  else controller.latest();
59868
- repaint(app, activeFolder(), controller);
60162
+ repaint(app, activeFolder(), controller, activeStreamingToolPreviews());
59869
60163
  if (controller.isLatest()) app.scrollToBottom();
59870
60164
  else app.scrollToTop({ disableFollow: true });
59871
60165
  },
@@ -60567,6 +60861,7 @@ function apply(ctx, config) {
60567
60861
  * plus every switch await the coordinator refresh themselves.
60568
60862
  */
60569
60863
  const initLiveSession = async (agent) => {
60864
+ app.clearExitConfirmation();
60570
60865
  modelSelections.installForAgent(agent);
60571
60866
  rememberHistoryCwd(agent.session.header.cwd ?? "");
60572
60867
  const events = agent.session.snapshotEvents();
@@ -60609,9 +60904,9 @@ function apply(ctx, config) {
60609
60904
  }
60610
60905
  app.clearLocalMessages();
60611
60906
  app.clearNotify();
60612
- app.clearCtrlCExit();
60907
+ app.clearExitConfirmation();
60613
60908
  notifiedSubagentNotices.clear();
60614
- repaint(app, folder, windowController);
60909
+ repaint(app, folder, windowController, activeStreamingToolPreviews());
60615
60910
  refreshStatusCheap();
60616
60911
  refreshQueue();
60617
60912
  scheduleInitialContextMeasure(agent);
@@ -61046,6 +61341,7 @@ function apply(ctx, config) {
61046
61341
  if (liveAgent === void 0) return;
61047
61342
  if (viewing !== void 0) {
61048
61343
  if (session.id === viewing.id) {
61344
+ applyOwnerStreamingToolPreviewEvent(viewing.previews, viewing.folder, event);
61049
61345
  viewing.folder.apply([event]);
61050
61346
  viewing.stats.apply([event]);
61051
61347
  if (event.type === "turn/start") viewing.activity = "running";
@@ -61057,6 +61353,7 @@ function apply(ctx, config) {
61057
61353
  }
61058
61354
  }
61059
61355
  if (session.id !== liveAgent.session.id) return;
61356
+ applyOwnerStreamingToolPreviewEvent(mainStreamingToolPreviews, folder, event);
61060
61357
  const selectionEvent = event;
61061
61358
  if (selectionEvent.type === "model/selection") modelSelections.observeSelectionEvent(liveAgent, selectionEvent);
61062
61359
  else if (event.type === "request/header") {