@linxiraos/pi-tui 1.2.1 → 1.3.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/CHANGELOG.md +245 -224
- package/README.md +36 -0
- package/THIRD-PARTY-NOTICES.txt +55 -5
- package/dist/types/app-keybindings.d.ts +18 -8
- package/dist/types/apps/git/diff-pane.d.ts +5 -0
- package/dist/types/apps/git/git-tui.d.ts +16 -2
- package/dist/types/apps/git/state.d.ts +11 -0
- package/dist/types/autocomplete.d.ts +2 -0
- package/dist/types/charts/chart-svg.d.ts +7 -13
- package/dist/types/chat/advisor-message.d.ts +4 -1
- package/dist/types/chat/assistant-message.d.ts +9 -4
- package/dist/types/chat/figure-markdown.d.ts +7 -2
- package/dist/types/chat/messages.d.ts +3 -1
- package/dist/types/chat/read-tool-group.d.ts +2 -2
- package/dist/types/chat/table-chart.d.ts +11 -15
- package/dist/types/chat/tool-execution.d.ts +32 -20
- package/dist/types/chat/user-message.d.ts +3 -2
- package/dist/types/chrome/countdown-timer.d.ts +3 -2
- package/dist/types/chrome/diff.d.ts +2 -3
- package/dist/types/chrome/status-notice.d.ts +2 -0
- package/dist/types/chrome/transcript-container.d.ts +2 -0
- package/dist/types/components/image.d.ts +2 -0
- package/dist/types/components/markdown.d.ts +49 -3
- package/dist/types/components/text.d.ts +2 -0
- package/dist/types/debug-server.d.ts +2 -0
- package/dist/types/keybinding-matchers.d.ts +6 -0
- package/dist/types/multiplexers/tmux.d.ts +1 -0
- package/dist/types/multiplexers/types.d.ts +6 -0
- package/dist/types/native/backend.d.ts +5 -4
- package/dist/types/native/blobs.d.ts +24 -12
- package/dist/types/overlays/agent-hub-projection.d.ts +10 -5
- package/dist/types/overlays/annotation-overlay.d.ts +19 -0
- package/dist/types/overlays/approval-dialog.d.ts +79 -0
- package/dist/types/overlays/ask-dialog.d.ts +11 -6
- package/dist/types/overlays/btw-history-panel.d.ts +2 -6
- package/dist/types/overlays/hook-editor.d.ts +7 -2
- package/dist/types/overlays/hook-input.d.ts +12 -4
- package/dist/types/overlays/job-tree.d.ts +50 -0
- package/dist/types/overlays/jobs-panel.d.ts +25 -11
- package/dist/types/overlays/login-dialog.d.ts +8 -3
- package/dist/types/overlays/mcp-add-wizard.d.ts +9 -1
- package/dist/types/overlays/model-browser.d.ts +6 -1
- package/dist/types/overlays/model-picker.d.ts +2 -0
- package/dist/types/overlays/pause-screen.d.ts +5 -0
- package/dist/types/overlays/plan-review-overlay.d.ts +4 -0
- package/dist/types/overlays/report-panel.d.ts +5 -1
- package/dist/types/overlays/reset-usage-selector.d.ts +7 -5
- package/dist/types/overlays/session-account-selector.d.ts +4 -0
- package/dist/types/overlays/session-info-overlay.d.ts +7 -3
- package/dist/types/overlays/session-selector.d.ts +3 -1
- package/dist/types/overlays/usage-dashboard.d.ts +6 -6
- package/dist/types/prompt/composer.d.ts +23 -20
- package/dist/types/prompt/custom-editor.d.ts +20 -10
- package/dist/types/prompt/image-references.d.ts +22 -0
- package/dist/types/prompt/internal-url-autocomplete.d.ts +1 -0
- package/dist/types/prompt/macos-spelling.d.ts +2 -1
- package/dist/types/prompt/prose-gate.d.ts +3 -0
- package/dist/types/prompt/queued-messages.d.ts +1 -1
- package/dist/types/prompt/typing-word.d.ts +25 -0
- package/dist/types/prompt/welcome.d.ts +26 -6
- package/dist/types/render/hanging-wrap.d.ts +7 -0
- package/dist/types/render/hyperlink.d.ts +2 -0
- package/dist/types/render/render-utils.d.ts +9 -1
- package/dist/types/render/utils.d.ts +1 -1
- package/dist/types/setup/scenes/splash.d.ts +6 -5
- package/dist/types/status-line/context-usage.d.ts +4 -1
- package/dist/types/status-line/host.d.ts +6 -4
- package/dist/types/status-line/startup.d.ts +6 -6
- package/dist/types/status-line/types.d.ts +3 -0
- package/dist/types/stdin-buffer.d.ts +7 -0
- package/dist/types/terminal-capabilities.d.ts +16 -1
- package/dist/types/terminal-multiplexer.d.ts +7 -0
- package/dist/types/terminal.d.ts +8 -4
- package/dist/types/theme/symbols.d.ts +1 -1
- package/dist/types/theme/theme.d.ts +11 -0
- package/dist/types/thinking.d.ts +5 -0
- package/dist/types/tmux.d.ts +39 -4
- package/dist/types/tools/ast-edit.d.ts +2 -2
- package/dist/types/tools/ast-grep.d.ts +2 -2
- package/dist/types/tools/bash.d.ts +6 -1
- package/dist/types/tools/edit.d.ts +2 -0
- package/dist/types/tools/eval.d.ts +53 -4
- package/dist/types/tools/find.d.ts +14 -4
- package/dist/types/tools/glob.d.ts +3 -3
- package/dist/types/tools/grep.d.ts +2 -2
- package/dist/types/tools/grouped-file-output.d.ts +9 -4
- package/dist/types/tools/native-view.d.ts +1 -1
- package/dist/types/tools/read.d.ts +32 -10
- package/dist/types/tools/renderer.d.ts +2 -0
- package/dist/types/tools/task.d.ts +31 -0
- package/dist/types/tools/web-search-types.d.ts +1 -0
- package/dist/types/tools/write.d.ts +2 -0
- package/dist/types/tui.d.ts +20 -3
- package/package.json +10 -10
- package/src/app-keybindings.d.ts +18 -8
- package/src/app-keybindings.ts +20 -8
- package/src/apps/autoresearch-dashboard.ts +444 -27
- package/src/apps/debug/raw-sse.ts +2 -2
- package/src/apps/git/diff-pane.ts +39 -41
- package/src/apps/git/git-tui.ts +80 -14
- package/src/apps/git/sidebar.ts +10 -1
- package/src/apps/git/state.ts +12 -0
- package/src/autocomplete.d.ts +2 -0
- package/src/autocomplete.ts +21 -19
- package/src/bracketed-paste.ts +9 -2
- package/src/charts/chart-svg.d.ts +7 -13
- package/src/charts/chart-svg.ts +19 -22
- package/src/charts/table-data.ts +17 -6
- package/src/chat/advisor-message.d.ts +4 -1
- package/src/chat/advisor-message.ts +77 -24
- package/src/chat/assistant-message.d.ts +9 -4
- package/src/chat/assistant-message.ts +127 -31
- package/src/chat/bash-execution.ts +7 -2
- package/src/chat/figure-markdown.d.ts +7 -2
- package/src/chat/figure-markdown.ts +10 -3
- package/src/chat/image-loading.ts +9 -6
- package/src/chat/messages.d.ts +3 -1
- package/src/chat/messages.ts +4 -1
- package/src/chat/read-tool-group.d.ts +2 -2
- package/src/chat/read-tool-group.ts +97 -42
- package/src/chat/svg-figure.ts +3 -1
- package/src/chat/svg-source.ts +6 -3
- package/src/chat/table-chart.d.ts +11 -15
- package/src/chat/table-chart.ts +24 -57
- package/src/chat/tool-execution.d.ts +32 -20
- package/src/chat/tool-execution.ts +103 -36
- package/src/chat/user-message.d.ts +3 -2
- package/src/chat/user-message.ts +54 -34
- package/src/chrome/countdown-timer.ts +3 -3
- package/src/chrome/diff.d.ts +2 -3
- package/src/chrome/diff.ts +4 -9
- package/src/chrome/status-notice.ts +5 -0
- package/src/chrome/transcript-container.d.ts +2 -0
- package/src/chrome/transcript-container.ts +212 -28
- package/src/components/editor.ts +12 -12
- package/src/components/image.d.ts +2 -0
- package/src/components/image.ts +54 -20
- package/src/components/markdown.d.ts +49 -3
- package/src/components/markdown.ts +482 -12
- package/src/components/settings-list.ts +2 -4
- package/src/components/text.d.ts +2 -0
- package/src/components/text.ts +11 -1
- package/src/debug-server.d.ts +2 -0
- package/src/debug-server.ts +51 -69
- package/src/hotkeys-markdown.ts +2 -0
- package/src/keybinding-matchers.d.ts +6 -0
- package/src/keybinding-matchers.ts +19 -0
- package/src/multiplexers/tmux.d.ts +1 -0
- package/src/multiplexers/tmux.ts +3 -0
- package/src/multiplexers/types.d.ts +6 -0
- package/src/multiplexers/types.ts +6 -0
- package/src/native/backend.d.ts +5 -4
- package/src/native/backend.ts +13 -5
- package/src/native/blobs.d.ts +24 -12
- package/src/native/blobs.ts +56 -7
- package/src/native/reconcile.ts +43 -21
- package/src/overlays/advisor-config.ts +259 -8
- package/src/overlays/agent-hub-projection.d.ts +10 -5
- package/src/overlays/agent-hub-projection.ts +63 -12
- package/src/overlays/annotation-overlay.ts +422 -168
- package/src/overlays/approval-dialog.ts +389 -0
- package/src/overlays/ask-dialog.ts +244 -105
- package/src/overlays/btw-history-panel.ts +208 -147
- package/src/overlays/extensions/extension-list.ts +4 -6
- package/src/overlays/hook-editor.ts +90 -32
- package/src/overlays/hook-input.ts +105 -19
- package/src/overlays/job-tree.ts +177 -0
- package/src/overlays/jobs-panel.ts +222 -142
- package/src/overlays/login-dialog.ts +201 -91
- package/src/overlays/mcp-add-wizard.ts +562 -279
- package/src/overlays/model-browser.ts +32 -7
- package/src/overlays/model-hub.ts +4 -10
- package/src/overlays/model-picker.ts +58 -21
- package/src/overlays/pause-screen.ts +81 -23
- package/src/overlays/plan-review-overlay.ts +16 -0
- package/src/overlays/plugin-selector.ts +17 -1
- package/src/overlays/queue-mode-selector.ts +10 -0
- package/src/overlays/report-panel.ts +6 -2
- package/src/overlays/reset-usage-selector.ts +130 -21
- package/src/overlays/session-account-selector.ts +36 -0
- package/src/overlays/session-info-overlay.ts +199 -76
- package/src/overlays/session-selector.ts +15 -10
- package/src/overlays/settings-selector.ts +3 -0
- package/src/overlays/show-images-selector.ts +1 -0
- package/src/overlays/snapcompact-shape-preview.ts +13 -6
- package/src/overlays/theme-selector.ts +6 -1
- package/src/overlays/thinking-selector.ts +8 -4
- package/src/overlays/tree-selector.ts +117 -2
- package/src/overlays/usage-dashboard.ts +498 -144
- package/src/prompt/composer-cache.ts +4 -1
- package/src/prompt/composer.ts +200 -74
- package/src/prompt/custom-editor.ts +168 -65
- package/src/prompt/image-references.d.ts +22 -0
- package/src/prompt/image-references.ts +79 -2
- package/src/prompt/internal-url-autocomplete.ts +1 -0
- package/src/prompt/macos-spelling.ts +28 -5
- package/src/prompt/prose-gate.ts +3 -0
- package/src/prompt/queued-messages.ts +9 -2
- package/src/prompt/tips.txt +1 -2
- package/src/prompt/typing-word.ts +59 -0
- package/src/prompt/welcome.ts +97 -45
- package/src/render/code-cell.ts +18 -5
- package/src/render/hanging-wrap.d.ts +7 -0
- package/src/render/hanging-wrap.ts +58 -0
- package/src/render/hyperlink.d.ts +2 -0
- package/src/render/hyperlink.ts +5 -0
- package/src/render/render-utils.d.ts +9 -1
- package/src/render/render-utils.ts +16 -2
- package/src/render/utils.d.ts +1 -1
- package/src/render/utils.ts +1 -1
- package/src/render/width-aware-text.ts +6 -5
- package/src/setup/scenes/splash.ts +22 -8
- package/src/status-line/component.ts +79 -11
- package/src/status-line/context-usage.ts +176 -73
- package/src/status-line/footer.ts +18 -35
- package/src/status-line/host.ts +6 -1
- package/src/status-line/segments.ts +16 -5
- package/src/status-line/startup.ts +6 -6
- package/src/status-line/types.ts +1 -0
- package/src/stdin-buffer.d.ts +7 -0
- package/src/stdin-buffer.ts +300 -62
- package/src/terminal-capabilities.d.ts +16 -1
- package/src/terminal-capabilities.ts +66 -28
- package/src/terminal-multiplexer.d.ts +7 -0
- package/src/terminal-multiplexer.ts +14 -0
- package/src/terminal.d.ts +8 -4
- package/src/terminal.ts +190 -63
- package/src/theme/symbols.d.ts +1 -1
- package/src/theme/symbols.ts +1 -0
- package/src/theme/theme-class.ts +1 -0
- package/src/theme/theme.d.ts +11 -0
- package/src/theme/theme.ts +46 -14
- package/src/thinking.d.ts +5 -0
- package/src/thinking.ts +9 -1
- package/src/tmux.d.ts +39 -4
- package/src/tmux.ts +110 -16
- package/src/tools/ask.ts +20 -9
- package/src/tools/ast-edit.d.ts +2 -2
- package/src/tools/ast-edit.ts +30 -7
- package/src/tools/ast-grep.d.ts +2 -2
- package/src/tools/ast-grep.ts +39 -35
- package/src/tools/bash.d.ts +6 -1
- package/src/tools/bash.ts +15 -9
- package/src/tools/debug.ts +2 -1
- package/src/tools/default-renderer.ts +3 -2
- package/src/tools/edit.d.ts +2 -0
- package/src/tools/edit.ts +100 -21
- package/src/tools/eval.d.ts +53 -4
- package/src/tools/eval.ts +173 -137
- package/src/tools/find.d.ts +14 -4
- package/src/tools/find.ts +71 -18
- package/src/tools/glob.d.ts +3 -3
- package/src/tools/glob.ts +51 -17
- package/src/tools/grep.d.ts +2 -2
- package/src/tools/grep.ts +99 -22
- package/src/tools/grouped-file-output.d.ts +9 -4
- package/src/tools/grouped-file-output.ts +21 -6
- package/src/tools/lsp.ts +30 -18
- package/src/tools/mcp.ts +2 -2
- package/src/tools/native-view.d.ts +1 -1
- package/src/tools/native-view.ts +2 -2
- package/src/tools/read.d.ts +32 -10
- package/src/tools/read.ts +89 -43
- package/src/tools/renderer.d.ts +2 -0
- package/src/tools/renderer.ts +2 -0
- package/src/tools/task.d.ts +31 -0
- package/src/tools/task.ts +40 -7
- package/src/tools/todo.ts +3 -2
- package/src/tools/wait.ts +16 -30
- package/src/tools/web-search-types.d.ts +1 -0
- package/src/tools/web-search-types.ts +1 -0
- package/src/tools/web-search.ts +2 -1
- package/src/tools/write.d.ts +2 -0
- package/src/tools/write.ts +36 -12
- package/src/tools/yield.ts +2 -2
- package/src/tui.d.ts +20 -3
- package/src/tui.ts +160 -43
- package/src/utils.ts +5 -3
- package/dist/types/tools/eval-format/code-mode-declarations.d.ts +0 -5
- package/src/tools/eval-format/code-mode-declarations.ts +0 -61
|
@@ -8,7 +8,7 @@ import { Text } from "../components/text";
|
|
|
8
8
|
import { getImageDimensions, ImageProtocol, imageFallback, TERMINAL } from "../terminal-capabilities";
|
|
9
9
|
import { type Component, Container, type TUI } from "../tui";
|
|
10
10
|
import { truncateToWidth } from "../utils";
|
|
11
|
-
import { getProjectDir, isRecord, logger, sanitizeText } from "@linxiraos/pi-utils";
|
|
11
|
+
import { getProjectDir, isRecord, logger, sanitizeText, sanitizeTextDeep } from "@linxiraos/pi-utils";
|
|
12
12
|
import type { Theme } from "../theme/theme";
|
|
13
13
|
import { ensureThemeSync, getThemeEpoch, theme } from "../theme/theme";
|
|
14
14
|
import {
|
|
@@ -32,7 +32,8 @@ import {
|
|
|
32
32
|
import { plainText } from "../native/spans";
|
|
33
33
|
import { diagnosticsSection, displayPath, toolHead } from "../tools/native-view";
|
|
34
34
|
import type { FileDiagnosticsResult } from "../tools/lsp";
|
|
35
|
-
import { NativeImageCache } from "../native/blobs";
|
|
35
|
+
import { NativeImageCache, readMediaPath, shownMedia } from "../native/blobs";
|
|
36
|
+
import { imageLinksEnabled, linkedImageText } from "../prompt/image-references";
|
|
36
37
|
import { Memo } from "../native/memo";
|
|
37
38
|
import { type EditMode, type PerFileDiffPreview, renderStreamingFallback } from "../tools/edit";
|
|
38
39
|
import { taskCardAgentIds } from "../tools/task";
|
|
@@ -218,18 +219,17 @@ export interface ToolExecutionOptions {
|
|
|
218
219
|
useBuiltInRenderer?: boolean;
|
|
219
220
|
}
|
|
220
221
|
|
|
222
|
+
/** A tool result or progress frame as a card shows it. */
|
|
223
|
+
export interface ToolResultFrame {
|
|
224
|
+
content: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;
|
|
225
|
+
details?: unknown;
|
|
226
|
+
isError?: boolean;
|
|
227
|
+
}
|
|
228
|
+
|
|
221
229
|
export interface ToolExecutionHandle extends Component {
|
|
222
230
|
updateArgs(args: unknown, toolCallId?: string): void;
|
|
223
231
|
updateStreamPreview?(update: unknown): void;
|
|
224
|
-
updateResult(
|
|
225
|
-
result: {
|
|
226
|
-
content: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;
|
|
227
|
-
details?: unknown;
|
|
228
|
-
isError?: boolean;
|
|
229
|
-
},
|
|
230
|
-
isPartial?: boolean,
|
|
231
|
-
toolCallId?: string,
|
|
232
|
-
): void;
|
|
232
|
+
updateResult(result: ToolResultFrame, isPartial?: boolean, toolCallId?: string): void;
|
|
233
233
|
setArgsComplete(toolCallId?: string): void;
|
|
234
234
|
setExecutionStarted(toolCallId?: string): void;
|
|
235
235
|
setExpanded(expanded: boolean): void;
|
|
@@ -240,6 +240,50 @@ export interface ToolExecutionHandle extends Component {
|
|
|
240
240
|
seal(): void;
|
|
241
241
|
}
|
|
242
242
|
|
|
243
|
+
/** Tools whose returned call hands its card to the background job it started (see {@link parksAsBackground}). */
|
|
244
|
+
const JOB_CARD_TOOLS: Record<string, "always" | "native"> = { task: "always", bash: "native", eval: "native" };
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Whether a call that returned with `details.async.state === "running"` stays
|
|
248
|
+
* parked ({@link ToolExecutionHandle.parkAsBackground}) so its job's later
|
|
249
|
+
* frames keep filling the card until a `completed`/`failed` frame settles it.
|
|
250
|
+
* Detached `task` spawns always do; backgrounded `bash`/`eval` jobs only under
|
|
251
|
+
* native rendering, where the terminal transcript keeps them frozen instead.
|
|
252
|
+
* Shared by the live event controller and the transcript rebuild.
|
|
253
|
+
*/
|
|
254
|
+
export function parksAsBackground(toolName: string, details: unknown): boolean {
|
|
255
|
+
const mode = JOB_CARD_TOOLS[toolName];
|
|
256
|
+
if (mode === undefined || (mode === "native" && !isNativeRendering())) return false;
|
|
257
|
+
return asyncJobState(details) === "running";
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** The background job state a tool result reports (`details.async.state`: running, completed, failed, cancelled). */
|
|
261
|
+
export function asyncJobState(details: unknown): string | undefined {
|
|
262
|
+
if (!isRecord(details) || !isRecord(details.async)) return undefined;
|
|
263
|
+
return typeof details.async.state === "string" ? details.async.state : undefined;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Whether a frame is a background job's last one: it completed, failed or was cancelled. */
|
|
267
|
+
export function isSettledJobFrame(details: unknown): boolean {
|
|
268
|
+
const state = asyncJobState(details);
|
|
269
|
+
return state === "completed" || state === "failed" || state === "cancelled";
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Settle a parked card ({@link parksAsBackground}) with its job's last frame
|
|
274
|
+
* ({@link isSettledJobFrame}): a completed or failed job is the call's result;
|
|
275
|
+
* a cancelled one keeps the output it got to and seals the card as cancelled.
|
|
276
|
+
*/
|
|
277
|
+
export function settleWithJobFrame(component: ToolExecutionHandle, frame: ToolResultFrame, toolCallId: string): void {
|
|
278
|
+
const state = asyncJobState(frame.details);
|
|
279
|
+
if (state === "cancelled") {
|
|
280
|
+
component.updateResult(frame, true, toolCallId);
|
|
281
|
+
component.seal();
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
component.updateResult({ ...frame, isError: state === "failed" }, false, toolCallId);
|
|
285
|
+
}
|
|
286
|
+
|
|
243
287
|
/** Card tone per status when the renderer doesn't choose one; settled cards stay neutral. */
|
|
244
288
|
const NATIVE_STATUS_TONE: Record<TspCardStatus, TspTone | undefined> = {
|
|
245
289
|
pending: "pending",
|
|
@@ -376,6 +420,8 @@ export class ToolExecutionComponent extends Container {
|
|
|
376
420
|
isError?: boolean;
|
|
377
421
|
details?: unknown;
|
|
378
422
|
};
|
|
423
|
+
// Machine paths remain verbatim; only #result.details is handed to display renderers.
|
|
424
|
+
#rawDetails: unknown;
|
|
379
425
|
// Edit preview state
|
|
380
426
|
#editMode?: EditMode;
|
|
381
427
|
#editDiffPreview?: PerFileDiffPreview[];
|
|
@@ -581,22 +627,18 @@ export class ToolExecutionComponent extends Container {
|
|
|
581
627
|
return result !== undefined && !this.#isPartial && (result.isError === true || result.details != null);
|
|
582
628
|
}
|
|
583
629
|
|
|
584
|
-
updateResult(
|
|
585
|
-
result: {
|
|
586
|
-
content: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;
|
|
587
|
-
details?: unknown;
|
|
588
|
-
isError?: boolean;
|
|
589
|
-
},
|
|
590
|
-
isPartial = false,
|
|
591
|
-
_toolCallId?: string,
|
|
592
|
-
): void {
|
|
630
|
+
updateResult(result: ToolResultFrame, isPartial = false, _toolCallId?: string): void {
|
|
593
631
|
const hadNoResult = this.#result === undefined;
|
|
594
632
|
const wasPartialResult = this.#result !== undefined && this.#isPartial;
|
|
595
633
|
const firstResultRepaintShapePainted = this.#firstResultViewportRepaintShapePainted;
|
|
596
634
|
const partialResultPainted = this.#partialResultShapePainted;
|
|
597
635
|
this.#firstResultViewportRepaintShapePainted = false;
|
|
598
636
|
this.#partialResultShapePainted = false;
|
|
599
|
-
this.#
|
|
637
|
+
this.#rawDetails = result.details;
|
|
638
|
+
// Renderers receive sanitized details for display; raw paths are available
|
|
639
|
+
// separately for links and media through the built-in render context.
|
|
640
|
+
const details = sanitizeTextDeep(result.details);
|
|
641
|
+
this.#result = details === result.details ? result : { ...result, details };
|
|
600
642
|
this.#resultVersion++;
|
|
601
643
|
this.#blockVersion++;
|
|
602
644
|
this.#isPartial = isPartial;
|
|
@@ -941,6 +983,7 @@ export class ToolExecutionComponent extends Container {
|
|
|
941
983
|
override describe(cx?: DescribeContext): NativeNode {
|
|
942
984
|
if (this.#toolName === "wait" && this.#isBenignSkip()) return EMPTY_NODE;
|
|
943
985
|
const dataFirst = cx?.supports("tool") === true;
|
|
986
|
+
const media = cx?.supports("media") === true;
|
|
944
987
|
const key = [
|
|
945
988
|
dataFirst,
|
|
946
989
|
this.#resultVersion,
|
|
@@ -953,10 +996,11 @@ export class ToolExecutionComponent extends Container {
|
|
|
953
996
|
this.#showImages,
|
|
954
997
|
this.#displayInputVersion,
|
|
955
998
|
this.#toolActivityVisible,
|
|
999
|
+
media,
|
|
956
1000
|
getThemeEpoch(),
|
|
957
1001
|
];
|
|
958
1002
|
return this.#native.get(key, () =>
|
|
959
|
-
withHidden(dataFirst ? this.#describeTool() : this.#describeCard(), !this.#toolActivityVisible),
|
|
1003
|
+
withHidden(dataFirst ? this.#describeTool(media) : this.#describeCard(media), !this.#toolActivityVisible),
|
|
960
1004
|
);
|
|
961
1005
|
}
|
|
962
1006
|
|
|
@@ -1002,12 +1046,12 @@ export class ToolExecutionComponent extends Container {
|
|
|
1002
1046
|
* The data-first `tool` node (§7.2): the renderer's head data, status,
|
|
1003
1047
|
* timer, exit and intent as props; its body as borderless children.
|
|
1004
1048
|
*/
|
|
1005
|
-
#describeTool(): NativeNode {
|
|
1006
|
-
const view = this.#nativeView();
|
|
1049
|
+
#describeTool(media: boolean): NativeNode {
|
|
1050
|
+
const view = this.#nativeView(media);
|
|
1007
1051
|
const status = this.#nativeStatus();
|
|
1008
1052
|
const head = view.tool;
|
|
1009
1053
|
const late = this.#lateDiagnosticsParts();
|
|
1010
|
-
const body: NativeChild[] = [...(view.body ?? []), ...this.#nativeResultImages()];
|
|
1054
|
+
const body: NativeChild[] = [...(view.body ?? []), ...this.#nativeResultImages(view.body)];
|
|
1011
1055
|
if (late.section) body.push(late.section);
|
|
1012
1056
|
const inline = view.inline === true;
|
|
1013
1057
|
const hasBody = body.length > 0;
|
|
@@ -1072,8 +1116,8 @@ export class ToolExecutionComponent extends Container {
|
|
|
1072
1116
|
return spans;
|
|
1073
1117
|
}
|
|
1074
1118
|
|
|
1075
|
-
#describeCard(): NativeNode {
|
|
1076
|
-
const view = this.#nativeView();
|
|
1119
|
+
#describeCard(media: boolean): NativeNode {
|
|
1120
|
+
const view = this.#nativeView(media);
|
|
1077
1121
|
const status = this.#nativeStatus();
|
|
1078
1122
|
const late = this.#lateDiagnosticsParts();
|
|
1079
1123
|
const fallbackHead = this.#fallbackHead(view);
|
|
@@ -1095,7 +1139,7 @@ export class ToolExecutionComponent extends Container {
|
|
|
1095
1139
|
const children: NativeChild[] = [
|
|
1096
1140
|
node("row", { gap: "sm", align: "baseline" }, headChildren, "head"),
|
|
1097
1141
|
...(view.body ?? []),
|
|
1098
|
-
...this.#nativeResultImages(),
|
|
1142
|
+
...this.#nativeResultImages(view.body),
|
|
1099
1143
|
...(late.section ? [late.section] : []),
|
|
1100
1144
|
];
|
|
1101
1145
|
const role = `zeta.tool.${this.#toolName}`;
|
|
@@ -1127,9 +1171,10 @@ export class ToolExecutionComponent extends Container {
|
|
|
1127
1171
|
* the result view (merged renderers) or the call head over both bodies.
|
|
1128
1172
|
* A renderer that throws falls back to the generic card.
|
|
1129
1173
|
*/
|
|
1130
|
-
#nativeView(): NativeToolView {
|
|
1174
|
+
#nativeView(media: boolean): NativeToolView {
|
|
1131
1175
|
const status = this.#nativeStatus();
|
|
1132
1176
|
const options: RenderResultContextOptions = {
|
|
1177
|
+
media,
|
|
1133
1178
|
expanded: this.#expanded,
|
|
1134
1179
|
isPartial: this.#isPartial,
|
|
1135
1180
|
argsComplete: this.#argsComplete,
|
|
@@ -1208,12 +1253,17 @@ export class ToolExecutionComponent extends Container {
|
|
|
1208
1253
|
return indicators ? (output ? `${output}\n${indicators}` : indicators) : output;
|
|
1209
1254
|
}
|
|
1210
1255
|
|
|
1211
|
-
|
|
1256
|
+
/** Result images under the renderer's body, minus those the body already shows (as an `image` blob or a `media` file). */
|
|
1257
|
+
#nativeResultImages(body: readonly NativeChild[] | undefined): NativeNode[] {
|
|
1212
1258
|
if (!this.#result || !this.#showImages) return [];
|
|
1259
|
+
const shown = shownMedia(body);
|
|
1260
|
+
const mediaPath = readMediaPath(this.#rawDetails);
|
|
1261
|
+
if (mediaPath && shown.paths.has(mediaPath)) return [];
|
|
1213
1262
|
const images: NativeNode[] = [];
|
|
1214
1263
|
this.#getAllImageBlocks().forEach((image, index) => {
|
|
1215
|
-
if (image.data
|
|
1216
|
-
|
|
1264
|
+
if (!image.data || !image.mimeType) return;
|
|
1265
|
+
const described = this.#nativeImages.get(`img${index}`, image.data, image.mimeType, mediaPath);
|
|
1266
|
+
if (!shown.blobs.has((described.p as { blob?: string } | undefined)?.blob ?? "")) images.push(described);
|
|
1217
1267
|
});
|
|
1218
1268
|
return images;
|
|
1219
1269
|
}
|
|
@@ -1244,7 +1294,7 @@ export class ToolExecutionComponent extends Container {
|
|
|
1244
1294
|
// TUI startup, so a result rendered before it lands must re-shape once it
|
|
1245
1295
|
// does (it gates Image children vs text fallback in #rebuildDisplay); keyed
|
|
1246
1296
|
// here for the same reason markdown.ts keys its render cache on it.
|
|
1247
|
-
const key = `${this.#resultVersion}|${this.#expanded}|${this.#isPartial}|${this.#argsComplete ? "1" : "0"}|${this.#executionStarted ? "1" : "0"}|${this.#spinnerFrame ?? "-"}|${this.#showImages}|${getThemeEpoch()}|${this.#displayInputVersion}|${TERMINAL.imageProtocol ?? "-"}|${this.#imageSizeKey()}`;
|
|
1297
|
+
const key = `${this.#resultVersion}|${this.#expanded}|${this.#isPartial}|${this.#argsComplete ? "1" : "0"}|${this.#executionStarted ? "1" : "0"}|${this.#spinnerFrame ?? "-"}|${this.#showImages}|${getThemeEpoch()}|${this.#displayInputVersion}|${TERMINAL.imageProtocol ?? "-"}|${this.#imageSizeKey()}|${imageLinksEnabled()}`;
|
|
1248
1298
|
if (key === this.#lastDisplayKey && this.#displayBuilt) return;
|
|
1249
1299
|
this.#lastDisplayKey = key;
|
|
1250
1300
|
|
|
@@ -1514,6 +1564,10 @@ export class ToolExecutionComponent extends Container {
|
|
|
1514
1564
|
this.#contentBox.clear();
|
|
1515
1565
|
|
|
1516
1566
|
const renderContext = this.#buildRenderContext();
|
|
1567
|
+
const rawPerFileResults =
|
|
1568
|
+
isRecord(this.#rawDetails) && Array.isArray(this.#rawDetails.perFileResults)
|
|
1569
|
+
? this.#rawDetails.perFileResults
|
|
1570
|
+
: undefined;
|
|
1517
1571
|
this.#renderState.renderContext = renderContext;
|
|
1518
1572
|
|
|
1519
1573
|
for (let i = 0; i < perFileResults.length; i++) {
|
|
@@ -1525,9 +1579,13 @@ export class ToolExecutionComponent extends Container {
|
|
|
1525
1579
|
}
|
|
1526
1580
|
const fileBox = new Box(0, 0);
|
|
1527
1581
|
try {
|
|
1582
|
+
const fileOptions = {
|
|
1583
|
+
...this.#renderState,
|
|
1584
|
+
renderContext: { ...renderContext, rawDetails: rawPerFileResults?.[i] },
|
|
1585
|
+
};
|
|
1528
1586
|
const resultComponent = renderer.renderResult(
|
|
1529
1587
|
{ content: [], details: fileResult, isError: fileResult.isError },
|
|
1530
|
-
|
|
1588
|
+
fileOptions,
|
|
1531
1589
|
theme,
|
|
1532
1590
|
);
|
|
1533
1591
|
if (resultComponent) {
|
|
@@ -1651,6 +1709,7 @@ export class ToolExecutionComponent extends Container {
|
|
|
1651
1709
|
|
|
1652
1710
|
if (this.#result) {
|
|
1653
1711
|
const imageBlocks = this.#getAllImageBlocks();
|
|
1712
|
+
const sourcePath = readMediaPath(this.#rawDetails);
|
|
1654
1713
|
|
|
1655
1714
|
for (let i = 0; i < imageBlocks.length; i++) {
|
|
1656
1715
|
const img = imageBlocks[i];
|
|
@@ -1678,6 +1737,7 @@ export class ToolExecutionComponent extends Container {
|
|
|
1678
1737
|
{ fallbackColor: (s: string) => theme.fg("toolOutput", s) },
|
|
1679
1738
|
{
|
|
1680
1739
|
...resolveImageOptions(),
|
|
1740
|
+
sourcePath,
|
|
1681
1741
|
budget: this.#ui.imageBudget,
|
|
1682
1742
|
imageKey: `te${this.#instanceId}:${i}`,
|
|
1683
1743
|
requestRender: () => this.#ui.requestRender(),
|
|
@@ -1756,6 +1816,9 @@ export class ToolExecutionComponent extends Container {
|
|
|
1756
1816
|
return Math.max(1, Math.min(maxSeconds, value));
|
|
1757
1817
|
};
|
|
1758
1818
|
|
|
1819
|
+
if (this.#renderer && !this.#tool?.renderResult && this.#rawDetails !== undefined) {
|
|
1820
|
+
context.rawDetails = this.#rawDetails;
|
|
1821
|
+
}
|
|
1759
1822
|
if (this.#toolName === "bash") {
|
|
1760
1823
|
// Bash needs render context even before a result exists. The renderer uses the pending-call args
|
|
1761
1824
|
// plus this context to keep the inline command preview visible while tool-call JSON is still streaming.
|
|
@@ -1816,7 +1879,8 @@ export class ToolExecutionComponent extends Container {
|
|
|
1816
1879
|
|
|
1817
1880
|
#getTextOutput(): string {
|
|
1818
1881
|
if (!this.#result) return "";
|
|
1819
|
-
|
|
1882
|
+
// Image placeholders embed a link only under an enabled hyperlink policy.
|
|
1883
|
+
const key = `${this.#resultVersion}|${this.#showImages}|${TERMINAL.imageProtocol ?? "-"}|${imageLinksEnabled()}`;
|
|
1820
1884
|
if (key === this.#textOutputKey) return this.#textOutput;
|
|
1821
1885
|
|
|
1822
1886
|
const textBlocks = this.#result.content.filter(c => c.type === "text");
|
|
@@ -1833,7 +1897,10 @@ export class ToolExecutionComponent extends Container {
|
|
|
1833
1897
|
.map(img => {
|
|
1834
1898
|
const mimeType = String(img.mimeType);
|
|
1835
1899
|
const dims = img.data ? (getImageDimensions(img.data, mimeType) ?? undefined) : undefined;
|
|
1836
|
-
|
|
1900
|
+
const placeholder = imageFallback(mimeType, dims);
|
|
1901
|
+
return img.data
|
|
1902
|
+
? linkedImageText(placeholder, { data: img.data, mimeType }, readMediaPath(this.#rawDetails))
|
|
1903
|
+
: placeholder;
|
|
1837
1904
|
})
|
|
1838
1905
|
.join("\n");
|
|
1839
1906
|
output = output ? `${output}\n${imageIndicators}` : imageIndicators;
|
|
@@ -39,8 +39,9 @@ export declare class UserMessageComponent extends Container implements ReactionT
|
|
|
39
39
|
/**
|
|
40
40
|
* A head-less user-toned frame with the prompt as markdown under its
|
|
41
41
|
* attached images (titled `#N` like their chips, opening the materialized
|
|
42
|
-
* file on click): the fill says "you". A quiet
|
|
43
|
-
*
|
|
42
|
+
* file on click): the fill says "you". A quiet footer like a turn's usage line
|
|
43
|
+
* (`⧉ ⏪ · 09:45`: Copy, Rewind, the time sent) fades in at its bottom-left on hover when
|
|
44
|
+
* the host handles transcript actions. The live-steering marker and the
|
|
44
45
|
* agent's reaction are chips at the bottom-right, so a reaction landing
|
|
45
46
|
* later updates the frame in place, even deep in scrollback.
|
|
46
47
|
*/
|
package/src/chat/user-message.ts
CHANGED
|
@@ -18,12 +18,12 @@ import {
|
|
|
18
18
|
skillChipStyle,
|
|
19
19
|
} from "../prompt/composer-attachments";
|
|
20
20
|
import { MODEL_MENTION_TAG_RE } from "../prompt/model-mention-syntax";
|
|
21
|
-
import { expandKeyHint, fileHyperlink } from "../render";
|
|
22
|
-
import { imageReferenceHyperlink } from "../prompt/image-references";
|
|
21
|
+
import { expandKeyHint, fileHyperlink, fileLinkSpan } from "../render";
|
|
22
|
+
import { imageLinksEnabled, imageReferenceHyperlink } from "../prompt/image-references";
|
|
23
23
|
import { highlightMagicKeywords } from "../prompt/magic-keywords";
|
|
24
24
|
import type { ReactionTarget } from "./reaction";
|
|
25
25
|
import { card, md, node, row, span, text } from "../native/describe";
|
|
26
|
-
import { base64ImageNode } from "../native/blobs";
|
|
26
|
+
import { base64ImageNode, openableImageProps } from "../native/blobs";
|
|
27
27
|
import { hasTranscriptActions, runTranscriptAction } from "./transcript-actions";
|
|
28
28
|
import {
|
|
29
29
|
type DescribeContext,
|
|
@@ -145,8 +145,9 @@ export class UserMessageComponent extends Container implements ReactionTarget, T
|
|
|
145
145
|
readonly #tokens: RegExp;
|
|
146
146
|
#reaction: string | undefined;
|
|
147
147
|
#native: NativeNode | undefined;
|
|
148
|
-
/** The terminal's clock {@link #native} was described with. */
|
|
148
|
+
/** The terminal's clock and the image link policy {@link #native} was described with. */
|
|
149
149
|
#nativeHour12: boolean | undefined;
|
|
150
|
+
#nativeLinked = false;
|
|
150
151
|
|
|
151
152
|
constructor(text: string, options: UserBubbleOptions = {}) {
|
|
152
153
|
super();
|
|
@@ -195,44 +196,60 @@ export class UserMessageComponent extends Container implements ReactionTarget, T
|
|
|
195
196
|
/**
|
|
196
197
|
* A head-less user-toned frame with the prompt as markdown under its
|
|
197
198
|
* attached images (titled `#N` like their chips, opening the materialized
|
|
198
|
-
* file on click): the fill says "you". A quiet
|
|
199
|
-
*
|
|
199
|
+
* file on click): the fill says "you". A quiet footer like a turn's usage line
|
|
200
|
+
* (`⧉ ⏪ · 09:45`: Copy, Rewind, the time sent) fades in at its bottom-left on hover when
|
|
201
|
+
* the host handles transcript actions. The live-steering marker and the
|
|
200
202
|
* agent's reaction are chips at the bottom-right, so a reaction landing
|
|
201
203
|
* later updates the frame in place, even deep in scrollback.
|
|
202
204
|
*/
|
|
203
205
|
override describe(cx?: DescribeContext): NativeNode {
|
|
204
206
|
// The terminal's clock: Bun's own default locale ignores the user's.
|
|
205
207
|
const hour12 = cx?.hour12;
|
|
206
|
-
|
|
208
|
+
// Chip marks and thumbnails carry links only under an enabled policy.
|
|
209
|
+
const linked = imageLinksEnabled();
|
|
210
|
+
if (this.#native && this.#nativeHour12 === hour12 && this.#nativeLinked === linked) return this.#native;
|
|
207
211
|
this.#nativeHour12 = hour12;
|
|
212
|
+
this.#nativeLinked = linked;
|
|
208
213
|
const children: NativeChild[] = [];
|
|
209
214
|
if (!this.#synthetic && hasTranscriptActions()) {
|
|
210
|
-
const tools: NativeChild[] = [
|
|
215
|
+
const tools: NativeChild[] = [
|
|
216
|
+
// `copy-message`, not Tern's local `copy` (that would copy the icon's empty label).
|
|
217
|
+
node(
|
|
218
|
+
"icon",
|
|
219
|
+
{
|
|
220
|
+
name: "copy",
|
|
221
|
+
role: "zeta.user.tool",
|
|
222
|
+
title: "Copy message",
|
|
223
|
+
aria: "Copy message",
|
|
224
|
+
actions: { click: "copy-message" },
|
|
225
|
+
},
|
|
226
|
+
undefined,
|
|
227
|
+
"copy",
|
|
228
|
+
),
|
|
229
|
+
node(
|
|
230
|
+
"icon",
|
|
231
|
+
{
|
|
232
|
+
name: "rewind",
|
|
233
|
+
role: "zeta.user.tool",
|
|
234
|
+
title: "Rewind the conversation to an earlier message",
|
|
235
|
+
aria: "Rewind",
|
|
236
|
+
actions: { click: "rewind" },
|
|
237
|
+
},
|
|
238
|
+
undefined,
|
|
239
|
+
"rewind",
|
|
240
|
+
),
|
|
241
|
+
];
|
|
211
242
|
if (this.#timestamp !== undefined) {
|
|
212
243
|
const at = new Date(this.#timestamp);
|
|
213
244
|
tools.push(
|
|
245
|
+
node("text", { text: "·" }, undefined, "dot"),
|
|
214
246
|
text([span(at.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", hour12 }), "dim mono")], {
|
|
215
247
|
role: "zeta.user.time",
|
|
216
248
|
title: at.toLocaleString([], { hour12 }),
|
|
217
249
|
}),
|
|
218
250
|
);
|
|
219
251
|
}
|
|
220
|
-
|
|
221
|
-
// `copy-message`, not Tern's local `copy` (that would copy the label).
|
|
222
|
-
text("Copy", {
|
|
223
|
-
role: "zeta.user.tool",
|
|
224
|
-
actions: { click: "copy-message" },
|
|
225
|
-
title: "Copy message",
|
|
226
|
-
key: "copy",
|
|
227
|
-
}),
|
|
228
|
-
text("Rewind", {
|
|
229
|
-
role: "zeta.user.tool",
|
|
230
|
-
actions: { click: "rewind" },
|
|
231
|
-
title: "Rewind the conversation to an earlier message",
|
|
232
|
-
key: "rewind",
|
|
233
|
-
}),
|
|
234
|
-
);
|
|
235
|
-
children.push(node("row", { gap: "xs", role: "zeta.user.tools" }, tools, "tools"));
|
|
252
|
+
children.push(node("row", { gap: "xs", align: "center", role: "zeta.user.tools" }, tools, "tools"));
|
|
236
253
|
}
|
|
237
254
|
// Videos keep their chip only: the native image node decodes stills.
|
|
238
255
|
const thumbs: NativeNode[] = [];
|
|
@@ -240,16 +257,14 @@ export class UserMessageComponent extends Container implements ReactionTarget, T
|
|
|
240
257
|
this.#images.forEach((image, i) => {
|
|
241
258
|
if (!image.mimeType.startsWith("image/")) return;
|
|
242
259
|
const label = `#${i + 1}`;
|
|
243
|
-
const
|
|
244
|
-
// A click zooms the image in the terminal; the file opens from its context menu.
|
|
245
|
-
const open = link ? { href: link, actions: { menu: ["open"] } } : {};
|
|
260
|
+
const open = openableImageProps(linked ? this.#imageLinks?.[i] : undefined);
|
|
246
261
|
thumbs.push(base64ImageNode(image.data, image.mimeType, { alt: label, title: label, ...open }, label));
|
|
247
262
|
});
|
|
248
263
|
}
|
|
249
264
|
if (thumbs.length > 0) {
|
|
250
265
|
children.push(row(thumbs, { gap: "sm", wrap: true, align: "start", role: "zeta.user.images" }));
|
|
251
266
|
}
|
|
252
|
-
const marks = this.#synthetic ? [] : tokenMarks(this.#text, this.#tokens);
|
|
267
|
+
const marks = this.#synthetic ? [] : tokenMarks(this.#text, this.#tokens, this.#imageLinks);
|
|
253
268
|
children.push(md(this.#text, marks.length > 0 ? { marks } : undefined));
|
|
254
269
|
const badges: NativeChild[] = [];
|
|
255
270
|
if (this.#liveSteered) {
|
|
@@ -346,13 +361,13 @@ export class UserMessageComponent extends Container implements ReactionTarget, T
|
|
|
346
361
|
/**
|
|
347
362
|
* The composer tokens in `text` as `md` marks, styled as the composer
|
|
348
363
|
* decorates them (`CustomEditor.describeDecorations`), so a sent prompt keeps
|
|
349
|
-
* its chips highlighted.
|
|
364
|
+
* its chips highlighted. Image and video chips link to their file, as in text mode.
|
|
350
365
|
*/
|
|
351
|
-
function tokenMarks(text: string, tokens: RegExp): TspSpan[] {
|
|
366
|
+
function tokenMarks(text: string, tokens: RegExp, imageLinks: readonly (string | undefined)[] | undefined): TspSpan[] {
|
|
352
367
|
// One mark per distinct token: Tern styles every occurrence of its text.
|
|
353
|
-
const
|
|
368
|
+
const marks = new Map<string, TspSpan>();
|
|
354
369
|
const mark = (s: string) => (label: string) => {
|
|
355
|
-
|
|
370
|
+
marks.set(label, { t: label, s });
|
|
356
371
|
return "";
|
|
357
372
|
};
|
|
358
373
|
renderPlaceholders(
|
|
@@ -361,11 +376,16 @@ function tokenMarks(text: string, tokens: RegExp): TspSpan[] {
|
|
|
361
376
|
renderText: () => "",
|
|
362
377
|
renderSkill: mark("customMessageLabel strong"),
|
|
363
378
|
renderMention: mark("statusLineModel strong"),
|
|
364
|
-
renderReference:
|
|
379
|
+
renderReference: (label, kind, index) => {
|
|
380
|
+
const s = "accent strong";
|
|
381
|
+
const link = kind === "image" || kind === "video" ? imageLinks?.[index - 1] : undefined;
|
|
382
|
+
marks.set(label, link ? fileLinkSpan(link, label, s) : { t: label, s });
|
|
383
|
+
return "";
|
|
384
|
+
},
|
|
365
385
|
},
|
|
366
386
|
tokens,
|
|
367
387
|
);
|
|
368
|
-
return [...
|
|
388
|
+
return [...marks.values()];
|
|
369
389
|
}
|
|
370
390
|
|
|
371
391
|
/**
|
|
@@ -49,15 +49,15 @@ export class CountdownTimer {
|
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
51
|
* The remaining time as a terminal-clocked `elapsed` node: its age is the
|
|
52
|
-
* negative remaining time (the start lies in the future), so it counts
|
|
53
|
-
*
|
|
52
|
+
* negative remaining time (the start lies in the future), so it counts down
|
|
53
|
+
* to zero and holds there at expiry (no `stopped`: that would freeze it at
|
|
54
|
+
* zero from the start). The age is taken on the first
|
|
54
55
|
* call after a (re)start, when the node is first written, and the node is
|
|
55
56
|
* reused until {@link reset}.
|
|
56
57
|
*/
|
|
57
58
|
describe(): NativeNode {
|
|
58
59
|
this.#node ??= node("elapsed", {
|
|
59
60
|
age: Math.min(0, Date.now() - this.#deadlineMs),
|
|
60
|
-
stopped: 0,
|
|
61
61
|
format: "short",
|
|
62
62
|
});
|
|
63
63
|
return this.#node;
|
package/src/chrome/diff.d.ts
CHANGED
|
@@ -7,10 +7,9 @@ import type { NativeNode } from "../native/node";
|
|
|
7
7
|
* becomes one hunk; the terminal draws gutters, word emphasis and highlighting.
|
|
8
8
|
* Removed rows carry old line numbers, added rows new ones and context rows
|
|
9
9
|
* old ones, so the other side is derived from the running line delta.
|
|
10
|
+
* Tern picks the grammar from `path`.
|
|
10
11
|
*/
|
|
11
|
-
export declare function nativeDiff(diffText: string,
|
|
12
|
-
filePath?: string;
|
|
13
|
-
} & TspProps<"diff">): NativeNode;
|
|
12
|
+
export declare function nativeDiff(diffText: string, props?: TspProps<"diff">): NativeNode;
|
|
14
13
|
export interface RenderDiffOptions {
|
|
15
14
|
/** File path used to resolve indentation (.editorconfig + defaults) */
|
|
16
15
|
filePath?: string;
|
package/src/chrome/diff.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { diffWords } from "@linxiraos/pi-natives";
|
|
|
2
2
|
import { DEFAULT_TAB_WIDTH, sanitizeText } from "@linxiraos/pi-utils";
|
|
3
3
|
import type { TspDiffHunk, TspProps } from "@linxiraos/pi-wire";
|
|
4
4
|
import { theme as activeTheme, getLanguageFromPath, highlightCode, type Theme } from "../theme/index";
|
|
5
|
-
import { type CodeFrameMarker, formatCodeFrameLine, replaceTabs
|
|
5
|
+
import { type CodeFrameMarker, formatCodeFrameLine, replaceTabs } from "../render/render-utils";
|
|
6
6
|
import { node } from "../native/describe";
|
|
7
7
|
import type { NativeNode } from "../native/node";
|
|
8
8
|
/** SGR dim on / normal intensity — additive, preserves fg/bg colors. */
|
|
@@ -106,9 +106,9 @@ function renderIntraLineDiff(
|
|
|
106
106
|
* becomes one hunk; the terminal draws gutters, word emphasis and highlighting.
|
|
107
107
|
* Removed rows carry old line numbers, added rows new ones and context rows
|
|
108
108
|
* old ones, so the other side is derived from the running line delta.
|
|
109
|
+
* Tern picks the grammar from `path`.
|
|
109
110
|
*/
|
|
110
|
-
export function nativeDiff(diffText: string,
|
|
111
|
-
const { filePath, ...props } = options;
|
|
111
|
+
export function nativeDiff(diffText: string, props: TspProps<"diff"> = {}): NativeNode {
|
|
112
112
|
const hunks: TspDiffHunk[] = [];
|
|
113
113
|
let lines: string[] = [];
|
|
114
114
|
let oldStart = 0;
|
|
@@ -148,12 +148,7 @@ export function nativeDiff(diffText: string, options: { filePath?: string } & Ts
|
|
|
148
148
|
lines.push(`${parsed.prefix}${parsed.content}`);
|
|
149
149
|
}
|
|
150
150
|
close();
|
|
151
|
-
return node("diff", {
|
|
152
|
-
...props,
|
|
153
|
-
hunks,
|
|
154
|
-
path: props.path ?? (filePath ? shortenPath(filePath) : undefined),
|
|
155
|
-
lang: props.lang ?? (filePath ? getLanguageFromPath(filePath) : undefined),
|
|
156
|
-
});
|
|
151
|
+
return node("diff", { ...props, hunks });
|
|
157
152
|
}
|
|
158
153
|
|
|
159
154
|
export interface RenderDiffOptions {
|
|
@@ -42,6 +42,11 @@ export class StatusNotice extends Container {
|
|
|
42
42
|
this.addChild(this.#text);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
/** The text the notice currently shows. */
|
|
46
|
+
get message(): string {
|
|
47
|
+
return this.#message;
|
|
48
|
+
}
|
|
49
|
+
|
|
45
50
|
setMessage(message: string, options: StatusNoticeOptions = {}): void {
|
|
46
51
|
const toast = options.toast ?? true;
|
|
47
52
|
this.#text.setStyleFn(options.styleFn);
|
|
@@ -100,6 +100,8 @@ export declare class TranscriptContainer extends Container {
|
|
|
100
100
|
blockModes(): readonly TranscriptBlockMode[];
|
|
101
101
|
/** Emitted stable semantic-row counts in transcript order. */
|
|
102
102
|
emittedStableRows(): readonly number[];
|
|
103
|
+
/** Whether any block is still streaming or otherwise unfinalized. */
|
|
104
|
+
hasActiveBlock(): boolean;
|
|
103
105
|
/** Whether visible active capacity and live-block memory permit another admission. */
|
|
104
106
|
canAdmit(rows: number): boolean;
|
|
105
107
|
/** Prepares one atomic replay of the committed ledger and an emitted active-head prefix. */
|