@xmoon76/dsh-pi-tui 0.4.0 → 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.
- package/README.en.md +107 -105
- package/README.md +95 -101
- package/dist/builtins.mjs +1 -1
- package/dist/extensions.mjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{src-zkySYmLZ.mjs → src-CeGK2mHo.mjs} +457 -160
- package/dist/startup.mjs +1 -1
- package/package.json +7 -4
|
@@ -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]
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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}.
|
|
17036
|
-
*
|
|
17037
|
-
*
|
|
17038
|
-
*
|
|
17039
|
-
*
|
|
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 (
|
|
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.
|
|
22466
|
-
id: "
|
|
22467
|
-
text: [{ text:
|
|
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.
|
|
28728
|
-
*
|
|
28729
|
-
*
|
|
28730
|
-
|
|
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 (
|
|
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
|
|
29881
|
-
*
|
|
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":
|
|
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
|
-
/**
|
|
34667
|
-
|
|
34668
|
-
/**
|
|
34669
|
-
|
|
34670
|
-
|
|
34671
|
-
*
|
|
34672
|
-
|
|
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
|
-
|
|
34675
|
-
/** Issue #8:
|
|
34676
|
-
*
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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")
|
|
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"))
|
|
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
|
|
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)
|
|
35427
|
-
|
|
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)
|
|
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")
|
|
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)
|
|
35506
|
-
|
|
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)
|
|
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.
|
|
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)
|
|
35615
|
-
*
|
|
35616
|
-
*
|
|
35617
|
-
*
|
|
35618
|
-
|
|
35619
|
-
if (
|
|
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.
|
|
35727
|
+
this.armExitConfirmation(trigger);
|
|
35625
35728
|
this.requestRender();
|
|
35626
|
-
return
|
|
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
|
-
|
|
35636
|
-
this.
|
|
35637
|
-
|
|
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,
|
|
36728
|
-
*
|
|
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 =
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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 =
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
|
37376
|
-
*
|
|
37377
|
-
*
|
|
37378
|
-
*
|
|
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
|
-
|
|
37382
|
-
this.
|
|
37383
|
-
|
|
37384
|
-
this.
|
|
37574
|
+
armExitConfirmation(trigger) {
|
|
37575
|
+
this.clearExitConfirmation();
|
|
37576
|
+
const armedAt = Date.now();
|
|
37577
|
+
this.exitConfirmTrigger = trigger;
|
|
37578
|
+
this.exitConfirmAt = armedAt;
|
|
37385
37579
|
this.renderFooter();
|
|
37386
|
-
|
|
37387
|
-
this.
|
|
37388
|
-
this.
|
|
37389
|
-
this.
|
|
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.
|
|
37393
|
-
this.
|
|
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
|
|
37397
|
-
* runner clears it on session switches (a stale armed window
|
|
37398
|
-
* exit the NEW session). Idempotent; safe after dispose
|
|
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
|
-
|
|
37402
|
-
if (this.
|
|
37403
|
-
clearTimeout(this.
|
|
37404
|
-
this.
|
|
37600
|
+
clearExitConfirmation() {
|
|
37601
|
+
if (this.exitConfirmTimer !== void 0) {
|
|
37602
|
+
clearTimeout(this.exitConfirmTimer);
|
|
37603
|
+
this.exitConfirmTimer = void 0;
|
|
37405
37604
|
}
|
|
37406
|
-
if (this.
|
|
37407
|
-
this.
|
|
37408
|
-
this.
|
|
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
|
|
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
|
-
|
|
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
|
-
* (
|
|
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,
|
|
57132
|
-
* repaint can never show a stale Thought header
|
|
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.
|
|
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") {
|