@linxiraos/pi-tui 1.4.0 → 1.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/CHANGELOG.md +178 -182
- package/dist/types/autocomplete.d.ts +6 -0
- package/dist/types/components/sparkline.d.ts +34 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/native/describe.d.ts +7 -1
- package/dist/types/native/overlay.d.ts +2 -0
- package/dist/types/native/tone.d.ts +2 -0
- package/dist/types/overlays/agent-hub.d.ts +3 -3
- package/dist/types/overlays/approval-dialog.d.ts +15 -1
- package/dist/types/overlays/jobs-panel.d.ts +3 -0
- package/dist/types/overlays/model-browser.d.ts +1 -1
- package/dist/types/render/output-block.d.ts +2 -0
- package/dist/types/render/tool-card.d.ts +2 -0
- package/dist/types/sixel.d.ts +28 -0
- package/dist/types/status-line/host.d.ts +3 -0
- package/dist/types/terminal-capabilities.d.ts +5 -0
- package/dist/types/tools/bash.d.ts +2 -0
- package/dist/types/tools/default-renderer.d.ts +3 -0
- package/dist/types/tools/eval.d.ts +9 -8
- package/dist/types/tools/irc.d.ts +6 -0
- package/dist/types/tools/job-widget.d.ts +226 -0
- package/dist/types/tools/json-tree.d.ts +2 -0
- package/dist/types/tools/line-ranges.d.ts +8 -1
- package/dist/types/tools/mcp.d.ts +23 -0
- package/dist/types/tools/output-meta.d.ts +1 -1
- package/dist/types/tools/read.d.ts +8 -2
- package/dist/types/tools/renderer.d.ts +8 -2
- package/dist/types/tools/result-card.d.ts +1 -1
- package/dist/types/tools/streaming-output.d.ts +1 -0
- package/dist/types/tools/task.d.ts +12 -0
- package/dist/types/tools/wait.d.ts +5 -0
- package/package.json +9 -9
- package/src/autocomplete.d.ts +6 -0
- package/src/autocomplete.ts +21 -6
- package/src/chat/skill-message.ts +16 -17
- package/src/chat/tool-execution.ts +35 -0
- package/src/chrome/transcript-container.ts +85 -13
- package/src/components/editor.ts +17 -7
- package/src/components/image.ts +5 -4
- package/src/components/markdown.ts +11 -3
- package/src/components/sparkline.d.ts +34 -0
- package/src/components/sparkline.ts +89 -0
- package/src/index.d.ts +1 -0
- package/src/index.ts +1 -0
- package/src/native/backend.ts +32 -6
- package/src/native/describe.d.ts +7 -1
- package/src/native/describe.ts +18 -1
- package/src/native/overlay.d.ts +2 -0
- package/src/native/overlay.ts +3 -0
- package/src/native/tone.d.ts +2 -0
- package/src/native/tone.ts +22 -0
- package/src/overlays/agent-hub.ts +14 -10
- package/src/overlays/agent-transcript-viewer.ts +2 -2
- package/src/overlays/agents-hub.ts +2 -1
- package/src/overlays/approval-dialog.ts +59 -13
- package/src/overlays/hub-frame.ts +2 -1
- package/src/overlays/jobs-panel.ts +24 -2
- package/src/overlays/model-browser.ts +3 -0
- package/src/overlays/model-hub.ts +2 -1
- package/src/prompt/attachment-chips.ts +1 -1
- package/src/render/output-block.d.ts +2 -0
- package/src/render/output-block.ts +25 -12
- package/src/render/tool-card.d.ts +2 -0
- package/src/render/tool-card.ts +6 -0
- package/src/sixel.d.ts +28 -0
- package/src/sixel.ts +185 -0
- package/src/status-line/component.ts +19 -2
- package/src/status-line/host.ts +3 -0
- package/src/terminal-capabilities.d.ts +5 -0
- package/src/terminal-capabilities.ts +43 -3
- package/src/theme/symbols.ts +5 -3
- package/src/tools/bash.d.ts +2 -0
- package/src/tools/bash.ts +25 -9
- package/src/tools/default-renderer.d.ts +3 -0
- package/src/tools/default-renderer.ts +54 -17
- package/src/tools/eval.d.ts +9 -8
- package/src/tools/eval.ts +32 -14
- package/src/tools/find.ts +4 -5
- package/src/tools/glob.ts +4 -6
- package/src/tools/grep.ts +2 -4
- package/src/tools/irc.d.ts +6 -0
- package/src/tools/irc.ts +6 -0
- package/src/tools/job-widget.d.ts +226 -0
- package/src/tools/job-widget.ts +1036 -0
- package/src/tools/json-tree.d.ts +2 -0
- package/src/tools/json-tree.ts +8 -0
- package/src/tools/line-ranges.d.ts +8 -1
- package/src/tools/line-ranges.ts +23 -13
- package/src/tools/mcp.d.ts +23 -0
- package/src/tools/mcp.ts +44 -1
- package/src/tools/output-meta.d.ts +1 -1
- package/src/tools/output-meta.ts +1 -1
- package/src/tools/read.d.ts +8 -2
- package/src/tools/read.ts +64 -21
- package/src/tools/renderer.d.ts +8 -2
- package/src/tools/renderer.ts +8 -2
- package/src/tools/result-card.d.ts +1 -1
- package/src/tools/result-card.ts +1 -2
- package/src/tools/streaming-output.d.ts +1 -0
- package/src/tools/streaming-output.ts +11 -9
- package/src/tools/task.d.ts +12 -0
- package/src/tools/task.ts +25 -1
- package/src/tools/wait.d.ts +5 -0
- package/src/tools/wait.ts +290 -147
- package/src/tui.ts +45 -5
package/src/tools/json-tree.d.ts
CHANGED
|
@@ -70,6 +70,8 @@ export interface JsonTreeDescribeOptions {
|
|
|
70
70
|
openDepth?: number;
|
|
71
71
|
/** Root object keys omitted. Defaults to internal tool argument metadata. */
|
|
72
72
|
hiddenRootKeys?: readonly string[];
|
|
73
|
+
/** Called after the tree is built if a depth, node or scalar cap left part of the value out. */
|
|
74
|
+
onTruncated?: () => void;
|
|
73
75
|
}
|
|
74
76
|
/**
|
|
75
77
|
* TSP `tree` of a JSON value: containers become expandable nodes labelled with
|
package/src/tools/json-tree.ts
CHANGED
|
@@ -402,6 +402,8 @@ export interface JsonTreeDescribeOptions {
|
|
|
402
402
|
openDepth?: number;
|
|
403
403
|
/** Root object keys omitted. Defaults to internal tool argument metadata. */
|
|
404
404
|
hiddenRootKeys?: readonly string[];
|
|
405
|
+
/** Called after the tree is built if a depth, node or scalar cap left part of the value out. */
|
|
406
|
+
onTruncated?: () => void;
|
|
405
407
|
}
|
|
406
408
|
|
|
407
409
|
type JsonEntry = readonly [key: string, value: unknown];
|
|
@@ -438,6 +440,7 @@ export function describeJsonTree(value: unknown, options: JsonTreeDescribeOption
|
|
|
438
440
|
const maxScalarLen = Math.max(1, options.maxScalarLen ?? JSON_TREE_SCALAR_LEN_EXPANDED);
|
|
439
441
|
const openDepth = options.openDepth ?? JSON_TREE_MAX_DEPTH_COLLAPSED;
|
|
440
442
|
let budget = Math.max(1, options.maxNodes ?? JSON_TREE_MAX_LINES_EXPANDED);
|
|
443
|
+
let truncated = false;
|
|
441
444
|
let nextId = 0;
|
|
442
445
|
const leaf = (label: readonly TspSpan[]): TspTreeNode => ({ id: `${nextId++}`, label });
|
|
443
446
|
|
|
@@ -445,6 +448,7 @@ export function describeJsonTree(value: unknown, options: JsonTreeDescribeOption
|
|
|
445
448
|
const out: TspTreeNode[] = [];
|
|
446
449
|
for (let index = 0; index < entries.length; index++) {
|
|
447
450
|
if (budget <= 0) {
|
|
451
|
+
truncated = true;
|
|
448
452
|
out.push(leaf([span(`… ${entries.length - index} more`, "dim")]));
|
|
449
453
|
break;
|
|
450
454
|
}
|
|
@@ -463,17 +467,20 @@ export function describeJsonTree(value: unknown, options: JsonTreeDescribeOption
|
|
|
463
467
|
const size = Array.isArray(entry) ? `[${entry.length}]` : `{${entries.length}}`;
|
|
464
468
|
const label = keySpans.length > 0 ? [...keySpans, span(` ${size}`, "dim")] : [span(size, "dim")];
|
|
465
469
|
if (entries.length === 0) return { id, label };
|
|
470
|
+
if (depth >= maxDepth) truncated = true;
|
|
466
471
|
const children = depth >= maxDepth ? [leaf([span("…", "dim")])] : describeEntries(entries, depth + 1);
|
|
467
472
|
return { id, label, open: depth < openDepth, children };
|
|
468
473
|
}
|
|
469
474
|
const prefix = keySpans.length > 0 ? [...keySpans, span(": ", "muted")] : [];
|
|
470
475
|
if (typeof entry === "string") {
|
|
471
476
|
const lines = plainText(entry).split("\n");
|
|
477
|
+
if (lines.some(line => line.length > maxScalarLen)) truncated = true;
|
|
472
478
|
const first = clipScalar(lines[0], maxScalarLen);
|
|
473
479
|
if (lines.length === 1) return { id, label: [...prefix, span(`"${first}"`, "syntaxString")] };
|
|
474
480
|
const children: TspTreeNode[] = [];
|
|
475
481
|
for (let index = 0; index < lines.length; index++) {
|
|
476
482
|
if (budget <= 0) {
|
|
483
|
+
truncated = true;
|
|
477
484
|
children.push(leaf([span(`… ${lines.length - index} more lines`, "dim")]));
|
|
478
485
|
break;
|
|
479
486
|
}
|
|
@@ -499,5 +506,6 @@ export function describeJsonTree(value: unknown, options: JsonTreeDescribeOption
|
|
|
499
506
|
} else {
|
|
500
507
|
nodes = [describeValue(undefined, value, 0)];
|
|
501
508
|
}
|
|
509
|
+
if (truncated) options.onTruncated?.();
|
|
502
510
|
return node("tree", { nodes });
|
|
503
511
|
}
|
|
@@ -10,12 +10,19 @@ export interface LineRange {
|
|
|
10
10
|
export declare const LINE_RANGE_CHUNK_SOURCE: string;
|
|
11
11
|
/** Parse a single `N`, `N-M`, `N-`, `N+K`, or `..`-aliased (`N..M`, `N..`) chunk. Throws via {@link ToolError} on invalid bounds. */
|
|
12
12
|
export declare function parseLineRangeChunk(sel: string): LineRange | null;
|
|
13
|
+
/** What a bare `N` selects in a one-chunk list: lines `N` onward (`:50`), or the single line `N`. */
|
|
14
|
+
export type BareLineMode = "onward" | "single";
|
|
13
15
|
/**
|
|
14
16
|
* Parse a comma-separated list of line ranges (e.g. `5-16,960-973` or `19,59`). Returns
|
|
15
17
|
* the ranges in ascending order with overlapping/adjacent ranges merged so
|
|
16
18
|
* downstream consumers can stream the file in a single forward pass per range.
|
|
17
19
|
*/
|
|
18
|
-
export declare function parseLineRanges(sel: string): [LineRange, ...LineRange[]] | null;
|
|
20
|
+
export declare function parseLineRanges(sel: string, bare?: BareLineMode): [LineRange, ...LineRange[]] | null;
|
|
21
|
+
/**
|
|
22
|
+
* Sort non-empty `ranges` by start line and merge the ones that overlap or touch;
|
|
23
|
+
* an open-ended range (`endLine` undefined) reaches EOF and absorbs every later one.
|
|
24
|
+
*/
|
|
25
|
+
export declare function mergeLineRanges(ranges: readonly LineRange[]): [LineRange, ...LineRange[]];
|
|
19
26
|
/**
|
|
20
27
|
* Extract the line-range component from a read-tool selector that may also
|
|
21
28
|
* carry a verbatim/index display mode (`raw`, `conflicts`) — alone or compounded
|
package/src/tools/line-ranges.ts
CHANGED
|
@@ -49,16 +49,19 @@ function parseChunk(sel: string, pinBare: boolean): LineRange | null {
|
|
|
49
49
|
return { startLine: rawStart, endLine: rawEnd };
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/** What a bare `N` selects in a one-chunk list: lines `N` onward (`:50`), or the single line `N`. */
|
|
53
|
+
export type BareLineMode = "onward" | "single";
|
|
54
|
+
|
|
52
55
|
/**
|
|
53
56
|
* Parse a comma-separated list of line ranges (e.g. `5-16,960-973` or `19,59`). Returns
|
|
54
57
|
* the ranges in ascending order with overlapping/adjacent ranges merged so
|
|
55
58
|
* downstream consumers can stream the file in a single forward pass per range.
|
|
56
59
|
*/
|
|
57
|
-
export function parseLineRanges(sel: string): [LineRange, ...LineRange[]] | null {
|
|
60
|
+
export function parseLineRanges(sel: string, bare: BareLineMode = "onward"): [LineRange, ...LineRange[]] | null {
|
|
58
61
|
const chunks = sel.split(",");
|
|
59
|
-
// A lone `:50` means "from line 50". Inside a comma list,
|
|
60
|
-
// that one line; otherwise `:19,59` collapses to "from 19 through EOF".
|
|
61
|
-
const pinBare = chunks.length > 1;
|
|
62
|
+
// A lone `:50` means "from line 50" unless `bare` pins it. Inside a comma list,
|
|
63
|
+
// a bare number is that one line; otherwise `:19,59` collapses to "from 19 through EOF".
|
|
64
|
+
const pinBare = bare === "single" || chunks.length > 1;
|
|
62
65
|
const parsed: LineRange[] = [];
|
|
63
66
|
for (const chunk of chunks) {
|
|
64
67
|
const range = parseChunk(chunk, pinBare);
|
|
@@ -66,14 +69,19 @@ export function parseLineRanges(sel: string): [LineRange, ...LineRange[]] | null
|
|
|
66
69
|
parsed.push(range);
|
|
67
70
|
}
|
|
68
71
|
if (parsed.length === 0) return null;
|
|
69
|
-
parsed
|
|
72
|
+
return mergeLineRanges(parsed);
|
|
73
|
+
}
|
|
70
74
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Sort non-empty `ranges` by start line and merge the ones that overlap or touch;
|
|
77
|
+
* an open-ended range (`endLine` undefined) reaches EOF and absorbs every later one.
|
|
78
|
+
*/
|
|
79
|
+
export function mergeLineRanges(ranges: readonly LineRange[]): [LineRange, ...LineRange[]] {
|
|
80
|
+
const [first, ...rest] = [...ranges].sort((a, b) => a.startLine - b.startLine);
|
|
81
|
+
const merged: [LineRange, ...LineRange[]] = [first];
|
|
82
|
+
for (const current of rest) {
|
|
74
83
|
const last = merged[merged.length - 1];
|
|
75
|
-
|
|
76
|
-
if (last.endLine === undefined) continue;
|
|
84
|
+
if (last.endLine === undefined) break;
|
|
77
85
|
// Merge when current starts within (or immediately after) the last range.
|
|
78
86
|
if (current.startLine <= last.endLine + 1) {
|
|
79
87
|
if (current.endLine === undefined || current.endLine > last.endLine) {
|
|
@@ -83,7 +91,7 @@ export function parseLineRanges(sel: string): [LineRange, ...LineRange[]] | null
|
|
|
83
91
|
}
|
|
84
92
|
merged.push(current);
|
|
85
93
|
}
|
|
86
|
-
return merged
|
|
94
|
+
return merged;
|
|
87
95
|
}
|
|
88
96
|
|
|
89
97
|
/**
|
|
@@ -98,10 +106,12 @@ export function parseLineRanges(sel: string): [LineRange, ...LineRange[]] | null
|
|
|
98
106
|
*/
|
|
99
107
|
export function selectorLineRanges(sel: string | undefined): [LineRange, ...LineRange[]] | undefined {
|
|
100
108
|
if (!sel) return undefined;
|
|
101
|
-
|
|
109
|
+
const chunks = sel.split(":");
|
|
110
|
+
const bare = chunks.some(chunk => chunk.toLowerCase() === "raw") ? "single" : "onward";
|
|
111
|
+
for (const chunk of chunks) {
|
|
102
112
|
const lower = chunk.toLowerCase();
|
|
103
113
|
if (lower === "raw" || lower === "conflicts") continue;
|
|
104
|
-
const ranges = parseLineRanges(chunk);
|
|
114
|
+
const ranges = parseLineRanges(chunk, bare);
|
|
105
115
|
if (ranges) return ranges;
|
|
106
116
|
}
|
|
107
117
|
return undefined;
|
package/src/tools/mcp.d.ts
CHANGED
|
@@ -63,6 +63,27 @@ export interface MCPResourceContent {
|
|
|
63
63
|
}
|
|
64
64
|
/** Supported MCP result content blocks retained in display metadata. */
|
|
65
65
|
export type MCPContent = MCPTextContent | MCPImageContent | MCPResourceContent;
|
|
66
|
+
/** Server-reported progress of an in-flight MCP request. */
|
|
67
|
+
export interface MCPProgress {
|
|
68
|
+
progress: number;
|
|
69
|
+
total?: number;
|
|
70
|
+
message?: string;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Read MCP progress fields from an untrusted record: `progress` must be a
|
|
74
|
+
* finite number; `total` is kept only when finite and `message` only when a
|
|
75
|
+
* string. Shared by the transports, the Eval bridge, and the status renderer.
|
|
76
|
+
*/
|
|
77
|
+
export declare function parseMCPProgress(value: unknown): MCPProgress | undefined;
|
|
78
|
+
/**
|
|
79
|
+
* One-line display text for MCP progress, at most `maxWidth` cells: the
|
|
80
|
+
* message, with `progress/total` when the server reports a total. The bare
|
|
81
|
+
* progress value appears only when there is no message: without a total it is
|
|
82
|
+
* an opaque counter, not a percentage. Home paths in the message show as `~`,
|
|
83
|
+
* and a long message is cut before the count, so the count stays visible.
|
|
84
|
+
* Every progress display (tool card and both Eval status renderers) uses it.
|
|
85
|
+
*/
|
|
86
|
+
export declare function formatMCPProgress(progress: MCPProgress, maxWidth?: number): string;
|
|
66
87
|
/** MCP result details shared by renderers and programmatic tool consumers. */
|
|
67
88
|
export interface MCPToolDetails {
|
|
68
89
|
/** Server name */
|
|
@@ -81,6 +102,8 @@ export interface MCPToolDetails {
|
|
|
81
102
|
provider?: string;
|
|
82
103
|
/** Provider display name (e.g., "Claude Code", "MCP Config") */
|
|
83
104
|
providerName?: string;
|
|
105
|
+
/** Latest progress of a running call; present only on partial updates. */
|
|
106
|
+
progress?: MCPProgress;
|
|
84
107
|
/** Structured output metadata (set by the spill wrapper when output is truncated to an artifact). */
|
|
85
108
|
meta?: OutputMeta;
|
|
86
109
|
}
|
package/src/tools/mcp.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { type Component, Markdown } from "../index";
|
|
8
8
|
|
|
9
9
|
import type { NativeToolHead, NativeToolView, RenderResultOptions } from "./renderer";
|
|
10
|
+
import { isRecord, sanitizeText } from "@linxiraos/pi-utils";
|
|
10
11
|
import { INTENT_FIELD } from "@linxiraos/pi-wire";
|
|
11
12
|
import { ansi, md } from "../native/describe";
|
|
12
13
|
import type { NativeChild } from "../native/node";
|
|
@@ -26,7 +27,8 @@ import {
|
|
|
26
27
|
renderJsonTreeLines,
|
|
27
28
|
} from "./json-tree";
|
|
28
29
|
import { formatStyledTruncationWarning, stripOutputNotice } from "./output-meta";
|
|
29
|
-
import { clipCollapsed, formatExpandHint } from "../render/render-utils";
|
|
30
|
+
import { clipCollapsed, flattenLine, formatExpandHint, shortenEmbeddedPaths } from "../render/render-utils";
|
|
31
|
+
import { truncateToWidth, visibleWidth } from "../utils";
|
|
30
32
|
import { formatOutputPaneLines, styleToolOutputLine } from "../render/output-pane";
|
|
31
33
|
import type { StatusLineOptions } from "../render/status-line";
|
|
32
34
|
import { plainToolCard, type ToolCardPhase } from "../render/tool-card";
|
|
@@ -388,6 +390,45 @@ export interface MCPResourceContent {
|
|
|
388
390
|
/** Supported MCP result content blocks retained in display metadata. */
|
|
389
391
|
export type MCPContent = MCPTextContent | MCPImageContent | MCPResourceContent;
|
|
390
392
|
|
|
393
|
+
/** Server-reported progress of an in-flight MCP request. */
|
|
394
|
+
export interface MCPProgress {
|
|
395
|
+
progress: number;
|
|
396
|
+
total?: number;
|
|
397
|
+
message?: string;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Read MCP progress fields from an untrusted record: `progress` must be a
|
|
402
|
+
* finite number; `total` is kept only when finite and `message` only when a
|
|
403
|
+
* string. Shared by the transports, the Eval bridge, and the status renderer.
|
|
404
|
+
*/
|
|
405
|
+
export function parseMCPProgress(value: unknown): MCPProgress | undefined {
|
|
406
|
+
if (!isRecord(value) || typeof value.progress !== "number" || !Number.isFinite(value.progress)) return undefined;
|
|
407
|
+
const progress: MCPProgress = { progress: value.progress };
|
|
408
|
+
if (typeof value.total === "number" && Number.isFinite(value.total)) progress.total = value.total;
|
|
409
|
+
if (typeof value.message === "string") progress.message = value.message;
|
|
410
|
+
return progress;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* One-line display text for MCP progress, at most `maxWidth` cells: the
|
|
415
|
+
* message, with `progress/total` when the server reports a total. The bare
|
|
416
|
+
* progress value appears only when there is no message: without a total it is
|
|
417
|
+
* an opaque counter, not a percentage. Home paths in the message show as `~`,
|
|
418
|
+
* and a long message is cut before the count, so the count stays visible.
|
|
419
|
+
* Every progress display (tool card and both Eval status renderers) uses it.
|
|
420
|
+
*/
|
|
421
|
+
export function formatMCPProgress(progress: MCPProgress, maxWidth = 200): string {
|
|
422
|
+
const message =
|
|
423
|
+
progress.message === undefined ? "" : shortenEmbeddedPaths(flattenLine(sanitizeText(progress.message)));
|
|
424
|
+
const amount = progress.total === undefined ? `${progress.progress}` : `${progress.progress}/${progress.total}`;
|
|
425
|
+
if (!message) return truncateToWidth(amount, maxWidth);
|
|
426
|
+
if (progress.total === undefined) return truncateToWidth(message, maxWidth);
|
|
427
|
+
const suffix = ` (${amount})`;
|
|
428
|
+
const messageWidth = maxWidth - visibleWidth(suffix);
|
|
429
|
+
return messageWidth > 0 ? `${truncateToWidth(message, messageWidth)}${suffix}` : truncateToWidth(amount, maxWidth);
|
|
430
|
+
}
|
|
431
|
+
|
|
391
432
|
/** MCP result details shared by renderers and programmatic tool consumers. */
|
|
392
433
|
export interface MCPToolDetails {
|
|
393
434
|
/** Server name */
|
|
@@ -406,6 +447,8 @@ export interface MCPToolDetails {
|
|
|
406
447
|
provider?: string;
|
|
407
448
|
/** Provider display name (e.g., "Claude Code", "MCP Config") */
|
|
408
449
|
providerName?: string;
|
|
450
|
+
/** Latest progress of a running call; present only on partial updates. */
|
|
451
|
+
progress?: MCPProgress;
|
|
409
452
|
/** Structured output metadata (set by the spill wrapper when output is truncated to an artifact). */
|
|
410
453
|
meta?: OutputMeta;
|
|
411
454
|
}
|
|
@@ -110,7 +110,7 @@ export interface OutputMeta {
|
|
|
110
110
|
/** Capture failure of this output itself; aggregate reports keep source failures on their entries. */
|
|
111
111
|
artifactError?: OutputArtifactError;
|
|
112
112
|
source?: SourceMeta;
|
|
113
|
-
/** The output is a bounded page of session artifact
|
|
113
|
+
/** The output is a bounded page of a file or session artifact its `source` re-reads with line selectors; spill-to-artifact skips it. */
|
|
114
114
|
pagedSource?: true;
|
|
115
115
|
diagnostics?: DiagnosticMeta;
|
|
116
116
|
limits?: LimitsMeta;
|
package/src/tools/output-meta.ts
CHANGED
|
@@ -87,7 +87,7 @@ export interface OutputMeta {
|
|
|
87
87
|
/** Capture failure of this output itself; aggregate reports keep source failures on their entries. */
|
|
88
88
|
artifactError?: OutputArtifactError;
|
|
89
89
|
source?: SourceMeta;
|
|
90
|
-
/** The output is a bounded page of session artifact
|
|
90
|
+
/** The output is a bounded page of a file or session artifact its `source` re-reads with line selectors; spill-to-artifact skips it. */
|
|
91
91
|
pagedSource?: true;
|
|
92
92
|
diagnostics?: DiagnosticMeta;
|
|
93
93
|
limits?: LimitsMeta;
|
package/src/tools/read.d.ts
CHANGED
|
@@ -71,8 +71,14 @@ export interface ReadToolDetails {
|
|
|
71
71
|
*/
|
|
72
72
|
displayReadTargetLinks?: Array<string | null>;
|
|
73
73
|
}
|
|
74
|
-
/**
|
|
75
|
-
|
|
74
|
+
/**
|
|
75
|
+
* Split a filesystem path from its trailing read selector. `foldListColons: false`
|
|
76
|
+
* keeps a colon repeated inside a list as part of the path (`a:1-30,:-30` peels
|
|
77
|
+
* only `:-30`), for callers checking whether that shorter split names a real file.
|
|
78
|
+
*/
|
|
79
|
+
export declare function splitPathAndSel(rawPath: string, options?: {
|
|
80
|
+
foldListColons?: boolean;
|
|
81
|
+
}): {
|
|
76
82
|
path: string;
|
|
77
83
|
sel?: string;
|
|
78
84
|
};
|
package/src/tools/read.ts
CHANGED
|
@@ -104,11 +104,13 @@ export interface ReadToolDetails {
|
|
|
104
104
|
// Parsing also recognizes incomplete counts to explain their errors; path splitting
|
|
105
105
|
// only peels complete selectors (not a trailing `+` or `L`).
|
|
106
106
|
const RANGE_SELECTOR_CHUNK = `${LINE_RANGE_CHUNK_SOURCE}(?<=[\\d.-])`;
|
|
107
|
-
const RANGE_LIST_SRC = `${RANGE_SELECTOR_CHUNK}(?:,${RANGE_SELECTOR_CHUNK})*`;
|
|
108
107
|
// A tail selector: `-N` reads the last N lines. Keep in sync with TAIL_SELECTOR_RE.
|
|
109
108
|
const TAIL_CHUNK_SRC = String.raw`-\d+`;
|
|
110
|
-
|
|
111
|
-
const
|
|
109
|
+
// A comma list may mix ranges and tails (`1-12,-15`); a lone tail is a one-chunk list.
|
|
110
|
+
const LIST_CHUNK_SRC = `(?:${RANGE_SELECTOR_CHUNK}|${TAIL_CHUNK_SRC})`;
|
|
111
|
+
const RANGE_LIST_SRC = `${LIST_CHUNK_SRC}(?:,${LIST_CHUNK_SRC})*`;
|
|
112
|
+
const FILE_LINE_RANGE_RE = new RegExp(`^(?:${RANGE_LIST_SRC}|raw|conflicts|img)$`, "i");
|
|
113
|
+
const FILE_LINE_RANGE_ONLY_RE = new RegExp(`^(?:${RANGE_LIST_SRC})$`, "i");
|
|
112
114
|
const FILE_RAW_ONLY_RE = /^raw$/i;
|
|
113
115
|
// Permissive selector chunk for internal URLs — accepts well-formed selectors
|
|
114
116
|
// plus common malformed shapes (e.g. `:-N-M`) so the read tool peels the entire
|
|
@@ -117,8 +119,33 @@ const INTERNAL_URL_SELECTOR_PART_RE = new RegExp(
|
|
|
117
119
|
String.raw`^(?:raw|conflicts|img|${RANGE_LIST_SRC}|-\d+(?:[-+]\d+)?)$`,
|
|
118
120
|
"i",
|
|
119
121
|
);
|
|
120
|
-
|
|
121
|
-
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Fold the list chunks that repeat the selector colon after a comma (`a:1-30,:-30`)
|
|
125
|
+
* into the `list` that follows them, so the pair reads as `a:1-30,-30`. Only
|
|
126
|
+
* colons at or after `minColon` count. Each chunk is tested once.
|
|
127
|
+
*/
|
|
128
|
+
function foldRepeatedListColons(basePath: string, list: string, minColon: number): { basePath: string; list: string } {
|
|
129
|
+
const chunks = [list];
|
|
130
|
+
while (basePath.endsWith(",")) {
|
|
131
|
+
const colon = basePath.lastIndexOf(":");
|
|
132
|
+
const chunk = basePath.slice(colon + 1, -1);
|
|
133
|
+
if (colon < minColon || !FILE_LINE_RANGE_ONLY_RE.test(chunk)) break;
|
|
134
|
+
chunks.push(chunk);
|
|
135
|
+
basePath = basePath.slice(0, colon);
|
|
136
|
+
}
|
|
137
|
+
return { basePath, list: chunks.reverse().join(",") };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Split a filesystem path from its trailing read selector. `foldListColons: false`
|
|
142
|
+
* keeps a colon repeated inside a list as part of the path (`a:1-30,:-30` peels
|
|
143
|
+
* only `:-30`), for callers checking whether that shorter split names a real file.
|
|
144
|
+
*/
|
|
145
|
+
export function splitPathAndSel(
|
|
146
|
+
rawPath: string,
|
|
147
|
+
options?: { foldListColons?: boolean },
|
|
148
|
+
): { path: string; sel?: string } {
|
|
122
149
|
const colon = rawPath.lastIndexOf(":");
|
|
123
150
|
if (colon <= 0) return { path: rawPath };
|
|
124
151
|
|
|
@@ -126,25 +153,33 @@ export function splitPathAndSel(rawPath: string): { path: string; sel?: string }
|
|
|
126
153
|
if (!FILE_LINE_RANGE_RE.test(candidate)) return { path: rawPath };
|
|
127
154
|
|
|
128
155
|
let basePath = rawPath.slice(0, colon);
|
|
129
|
-
let sel = candidate;
|
|
130
|
-
|
|
131
156
|
// Allow a compound trailing selector: `path:1-50:raw`, `path:raw:1-50`, or
|
|
132
|
-
// `path:raw:-60`. The two chunks must be one line
|
|
133
|
-
// `raw`, in either order.
|
|
134
|
-
|
|
135
|
-
|
|
157
|
+
// `path:raw:-60`. The two chunks must be one line list (ranges and tails)
|
|
158
|
+
// plus one `raw`, in either order.
|
|
159
|
+
let list: string | undefined;
|
|
160
|
+
let rawSuffix = "";
|
|
161
|
+
if (FILE_LINE_RANGE_ONLY_RE.test(candidate)) {
|
|
162
|
+
list = candidate;
|
|
163
|
+
} else if (FILE_RAW_ONLY_RE.test(candidate)) {
|
|
164
|
+
const innerColon = basePath.lastIndexOf(":");
|
|
136
165
|
const innerCandidate = basePath.slice(innerColon + 1);
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const outerIsRange = FILE_LINE_RANGE_ONLY_RE.test(candidate);
|
|
141
|
-
if ((innerIsRaw && outerIsRange) || (innerIsRange && outerIsRaw)) {
|
|
142
|
-
sel = `${innerCandidate}:${candidate}`;
|
|
166
|
+
if (innerColon > 0 && FILE_LINE_RANGE_ONLY_RE.test(innerCandidate)) {
|
|
167
|
+
list = innerCandidate;
|
|
168
|
+
rawSuffix = `:${candidate}`;
|
|
143
169
|
basePath = basePath.slice(0, innerColon);
|
|
144
170
|
}
|
|
145
171
|
}
|
|
172
|
+
if (list === undefined) return { path: basePath, sel: candidate };
|
|
146
173
|
|
|
147
|
-
|
|
174
|
+
if (options?.foldListColons !== false) ({ basePath, list } = foldRepeatedListColons(basePath, list, 1));
|
|
175
|
+
if (!rawSuffix) {
|
|
176
|
+
const innerColon = basePath.lastIndexOf(":");
|
|
177
|
+
const innerCandidate = basePath.slice(innerColon + 1);
|
|
178
|
+
if (innerColon > 0 && FILE_RAW_ONLY_RE.test(innerCandidate)) {
|
|
179
|
+
return { path: basePath.slice(0, innerColon), sel: `${innerCandidate}:${list}` };
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return { path: basePath, sel: `${list}${rawSuffix}` };
|
|
148
183
|
}
|
|
149
184
|
|
|
150
185
|
/**
|
|
@@ -189,9 +224,17 @@ export function splitInternalUrlSel(
|
|
|
189
224
|
// Stop before crossing into the scheme separator `://`.
|
|
190
225
|
if (colon < schemeEnd) break;
|
|
191
226
|
const tail = path.slice(colon + 1);
|
|
192
|
-
if (
|
|
193
|
-
|
|
194
|
-
|
|
227
|
+
if (INTERNAL_URL_SELECTOR_PART_RE.test(tail)) {
|
|
228
|
+
chunks.unshift(tail);
|
|
229
|
+
path = path.slice(0, colon);
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
// `artifact://7:1-30,:-30`: a list chunk ending in a comma joins the list peeled after it.
|
|
233
|
+
if (chunks.length === 0 || !FILE_LINE_RANGE_ONLY_RE.test(chunks[0])) break;
|
|
234
|
+
const folded = foldRepeatedListColons(path, chunks[0], schemeEnd);
|
|
235
|
+
if (folded.basePath === path) break;
|
|
236
|
+
chunks[0] = folded.list;
|
|
237
|
+
path = folded.basePath;
|
|
195
238
|
}
|
|
196
239
|
if (chunks.length === 0) return { path: rawPath };
|
|
197
240
|
return { path, sel: chunks.join(":") };
|
package/src/tools/renderer.d.ts
CHANGED
|
@@ -27,8 +27,9 @@ export interface RenderResultOptions {
|
|
|
27
27
|
executionStarted?: boolean;
|
|
28
28
|
/**
|
|
29
29
|
* Milliseconds since this call began executing (`tool_execution_start`),
|
|
30
|
-
* or its total once settled; undefined before it starts.
|
|
31
|
-
*
|
|
30
|
+
* or its total once settled; undefined before it starts. The terminal
|
|
31
|
+
* transcript's options object computes it on read, so a render-time read
|
|
32
|
+
* is live while the block's spinner ticks.
|
|
32
33
|
*/
|
|
33
34
|
elapsedMs?: number;
|
|
34
35
|
/** The call was abandoned without a final result (turn aborted, sealed). Native describe hooks only. */
|
|
@@ -177,6 +178,11 @@ export interface ToolRenderer<TArgs = unknown, TDetails = unknown> {
|
|
|
177
178
|
* `options.spinnerFrame`.
|
|
178
179
|
*/
|
|
179
180
|
animatedPartialResult?: boolean | ((args: TArgs) => boolean);
|
|
181
|
+
/**
|
|
182
|
+
* Repaint the block once a second while its call executes, for renderers
|
|
183
|
+
* that show live `options.elapsedMs` without consuming the spinner frame.
|
|
184
|
+
*/
|
|
185
|
+
tickElapsed?: boolean;
|
|
180
186
|
/**
|
|
181
187
|
* Whether replacing a pending call render with the first result requires a
|
|
182
188
|
* full viewport repaint. Use for merged renderers whose pending rows can be
|
package/src/tools/renderer.ts
CHANGED
|
@@ -28,8 +28,9 @@ export interface RenderResultOptions {
|
|
|
28
28
|
executionStarted?: boolean;
|
|
29
29
|
/**
|
|
30
30
|
* Milliseconds since this call began executing (`tool_execution_start`),
|
|
31
|
-
* or its total once settled; undefined before it starts.
|
|
32
|
-
*
|
|
31
|
+
* or its total once settled; undefined before it starts. The terminal
|
|
32
|
+
* transcript's options object computes it on read, so a render-time read
|
|
33
|
+
* is live while the block's spinner ticks.
|
|
33
34
|
*/
|
|
34
35
|
elapsedMs?: number;
|
|
35
36
|
/** The call was abandoned without a final result (turn aborted, sealed). Native describe hooks only. */
|
|
@@ -195,6 +196,11 @@ export interface ToolRenderer<TArgs = unknown, TDetails = unknown> {
|
|
|
195
196
|
* `options.spinnerFrame`.
|
|
196
197
|
*/
|
|
197
198
|
animatedPartialResult?: boolean | ((args: TArgs) => boolean);
|
|
199
|
+
/**
|
|
200
|
+
* Repaint the block once a second while its call executes, for renderers
|
|
201
|
+
* that show live `options.elapsedMs` without consuming the spinner frame.
|
|
202
|
+
*/
|
|
203
|
+
tickElapsed?: boolean;
|
|
198
204
|
/**
|
|
199
205
|
* Whether replacing a pending call render with the first result requires a
|
|
200
206
|
* full viewport repaint. Use for merged renderers whose pending rows can be
|
|
@@ -16,5 +16,5 @@ export interface CardToolResult {
|
|
|
16
16
|
export declare function firstText(result: CardToolResult): string;
|
|
17
17
|
/** Sanitize one display line: tabs become spaces, carriage returns are dropped. */
|
|
18
18
|
export declare function safe(value: string): string;
|
|
19
|
-
/**
|
|
19
|
+
/** Card whose lines are sanitized and truncated, re-rendered when the expanded state flips. */
|
|
20
20
|
export declare function card(lines: (width: number, expanded: boolean) => string[], options: RenderResultOptions): Component;
|
package/src/tools/result-card.ts
CHANGED
|
@@ -23,7 +23,7 @@ export function safe(value: string): string {
|
|
|
23
23
|
return replaceTabs(value).replace(/\r/g, "");
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
/**
|
|
26
|
+
/** Card whose lines are sanitized and truncated, re-rendered when the expanded state flips. */
|
|
27
27
|
export function card(lines: (width: number, expanded: boolean) => string[], options: RenderResultOptions): Component {
|
|
28
28
|
return createCachedComponent(
|
|
29
29
|
() => Boolean(options.expanded),
|
|
@@ -31,6 +31,5 @@ export function card(lines: (width: number, expanded: boolean) => string[], opti
|
|
|
31
31
|
lines(width, expanded)
|
|
32
32
|
.flatMap(line => line.split("\n"))
|
|
33
33
|
.map(line => truncateToWidth(safe(line), width, Ellipsis.Unicode)),
|
|
34
|
-
{ paddingX: 1 },
|
|
35
34
|
);
|
|
36
35
|
}
|
|
@@ -261,6 +261,7 @@ export declare class OutputSink {
|
|
|
261
261
|
* Replace the in-memory buffer with the given text. Used when an upstream
|
|
262
262
|
* minimizer rewrites the captured output after the raw bytes have already
|
|
263
263
|
* been streamed.
|
|
264
|
+
* The replacement is held to the per-line column cap like streamed output.
|
|
264
265
|
*
|
|
265
266
|
* After this call the buffer is authoritative: streaming counters realign
|
|
266
267
|
* to the replacement, the retained head window is cleared, and head
|
|
@@ -1432,6 +1432,7 @@ export class OutputSink {
|
|
|
1432
1432
|
* Replace the in-memory buffer with the given text. Used when an upstream
|
|
1433
1433
|
* minimizer rewrites the captured output after the raw bytes have already
|
|
1434
1434
|
* been streamed.
|
|
1435
|
+
* The replacement is held to the per-line column cap like streamed output.
|
|
1435
1436
|
*
|
|
1436
1437
|
* After this call the buffer is authoritative: streaming counters realign
|
|
1437
1438
|
* to the replacement, the retained head window is cleared, and head
|
|
@@ -1442,23 +1443,24 @@ export class OutputSink {
|
|
|
1442
1443
|
*/
|
|
1443
1444
|
replace(text: string): void {
|
|
1444
1445
|
this.#clearPendingChunkTimer();
|
|
1445
|
-
this.#
|
|
1446
|
-
this.#
|
|
1446
|
+
this.#currentLineBytes = 0;
|
|
1447
|
+
this.#columnEllipsisAdded = false;
|
|
1448
|
+
this.#columnDroppedBytes = 0;
|
|
1449
|
+
this.#columnTruncatedLines = 0;
|
|
1450
|
+
// The artifact keeps the replaced stream, so later line numbers no longer match it.
|
|
1451
|
+
this.#columnCutLinesUnmapped = true;
|
|
1452
|
+
const capped = this.#maxColumns > 0 ? this.#applyColumnCap(text, 0) : text;
|
|
1453
|
+
this.#buffer = capped;
|
|
1454
|
+
this.#bufferBytes = Buffer.byteLength(capped, "utf-8");
|
|
1447
1455
|
this.#tailUntrimmed = false;
|
|
1448
1456
|
this.#head = "";
|
|
1449
1457
|
this.#headBytes = 0;
|
|
1450
1458
|
this.#headLines = 0;
|
|
1451
1459
|
this.#headRetentionDisabled = true;
|
|
1452
|
-
this.#totalBytes =
|
|
1460
|
+
this.#totalBytes = Buffer.byteLength(text, "utf-8");
|
|
1453
1461
|
this.#totalLines = countNewlines(text);
|
|
1454
1462
|
this.#sawData = text.length > 0;
|
|
1455
1463
|
this.#truncated = false;
|
|
1456
|
-
this.#currentLineBytes = 0;
|
|
1457
|
-
this.#columnEllipsisAdded = false;
|
|
1458
|
-
this.#columnDroppedBytes = 0;
|
|
1459
|
-
this.#columnTruncatedLines = 0;
|
|
1460
|
-
// The artifact keeps the replaced stream, so later line numbers no longer match it.
|
|
1461
|
-
this.#columnCutLinesUnmapped = true;
|
|
1462
1464
|
this.#pendingChunk = "";
|
|
1463
1465
|
this.#pendingCarriageReturn = false;
|
|
1464
1466
|
}
|
package/src/tools/task.d.ts
CHANGED
|
@@ -264,6 +264,18 @@ export interface AgentProgress {
|
|
|
264
264
|
advisor?: boolean;
|
|
265
265
|
/** The agent's latest self-estimate of task completion (0–100), from the periodic `task.completionProbe` side request. */
|
|
266
266
|
completionPercent?: number;
|
|
267
|
+
/**
|
|
268
|
+
* The agent's current step in its own words (one line, ≤ 80 chars), from the
|
|
269
|
+
* same probe reply as {@link completionPercent}. Running rows prefer it over
|
|
270
|
+
* `lastIntent`; the job widget uses it as its stage line.
|
|
271
|
+
*/
|
|
272
|
+
stage?: string;
|
|
273
|
+
/**
|
|
274
|
+
* What the agent says shows its progress live (one line, ≤ 200 chars), e.g.
|
|
275
|
+
* "BENCH lines in bg_2 pipe:1, tok_s higher is better, target 70% of roofline";
|
|
276
|
+
* from the probe reply. Feeds the job widget author's brief; never rendered.
|
|
277
|
+
*/
|
|
278
|
+
watch?: string;
|
|
267
279
|
/** Data extracted by registered subprocess tool handlers (keyed by tool name) */
|
|
268
280
|
extractedToolData?: Record<string, unknown[]>;
|
|
269
281
|
/**
|
package/src/tools/task.ts
CHANGED
|
@@ -760,6 +760,15 @@ function renderAgentProgress(
|
|
|
760
760
|
|
|
761
761
|
// Current tool (if running) or most recent completed tool
|
|
762
762
|
if (progress.status === "running") {
|
|
763
|
+
if (progress.stage) {
|
|
764
|
+
lines.push(
|
|
765
|
+
truncateTaskRow(
|
|
766
|
+
`${continuePrefix}${theme.fg("muted", replaceTabs(sanitizeText(progress.stage)))}`,
|
|
767
|
+
maxWidth,
|
|
768
|
+
"",
|
|
769
|
+
),
|
|
770
|
+
);
|
|
771
|
+
}
|
|
763
772
|
if (progress.currentTool) {
|
|
764
773
|
let toolLine = `${continuePrefix}${theme.tree.hook} ${theme.fg("muted", sanitizeText(progress.currentTool))}`;
|
|
765
774
|
const toolDetail = toolCallDetail(
|
|
@@ -1869,6 +1878,9 @@ function describeProgressAgent(progress: AgentProgress, state: AgentDescribeStat
|
|
|
1869
1878
|
age: progress.currentToolStartMs ? Math.max(0, nowMs - progress.currentToolStartMs) : undefined,
|
|
1870
1879
|
}
|
|
1871
1880
|
: null;
|
|
1881
|
+
const summary = taskSummary(progress.description || progress.assignment || progress.task);
|
|
1882
|
+
// The agent's own stage line (from its completion probe) rides beside the task while it runs.
|
|
1883
|
+
const stage = running && progress.stage ? plainText(progress.stage) : "";
|
|
1872
1884
|
const retry =
|
|
1873
1885
|
running && progress.retryState
|
|
1874
1886
|
? {
|
|
@@ -1883,7 +1895,7 @@ function describeProgressAgent(progress: AgentProgress, state: AgentDescribeStat
|
|
|
1883
1895
|
{
|
|
1884
1896
|
name: formatTaskId(progress.id),
|
|
1885
1897
|
agent: progress.agent || undefined,
|
|
1886
|
-
task:
|
|
1898
|
+
task: stage ? (summary ? [span(summary), span(` · ${stage}`, "muted")] : stage) : summary || undefined,
|
|
1887
1899
|
status,
|
|
1888
1900
|
model: progress.resolvedModelIdentity ?? progress.resolvedModel,
|
|
1889
1901
|
thinking: thinkingToken(progress.resolvedThinkingLevel),
|
|
@@ -2379,6 +2391,18 @@ export interface AgentProgress {
|
|
|
2379
2391
|
advisor?: boolean;
|
|
2380
2392
|
/** The agent's latest self-estimate of task completion (0–100), from the periodic `task.completionProbe` side request. */
|
|
2381
2393
|
completionPercent?: number;
|
|
2394
|
+
/**
|
|
2395
|
+
* The agent's current step in its own words (one line, ≤ 80 chars), from the
|
|
2396
|
+
* same probe reply as {@link completionPercent}. Running rows prefer it over
|
|
2397
|
+
* `lastIntent`; the job widget uses it as its stage line.
|
|
2398
|
+
*/
|
|
2399
|
+
stage?: string;
|
|
2400
|
+
/**
|
|
2401
|
+
* What the agent says shows its progress live (one line, ≤ 200 chars), e.g.
|
|
2402
|
+
* "BENCH lines in bg_2 pipe:1, tok_s higher is better, target 70% of roofline";
|
|
2403
|
+
* from the probe reply. Feeds the job widget author's brief; never rendered.
|
|
2404
|
+
*/
|
|
2405
|
+
watch?: string;
|
|
2382
2406
|
/** Data extracted by registered subprocess tool handlers (keyed by tool name) */
|
|
2383
2407
|
extractedToolData?: Record<string, unknown[]>;
|
|
2384
2408
|
/**
|
package/src/tools/wait.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { Theme } from "../theme/theme";
|
|
|
3
3
|
import { type OutputMeta } from "./output-meta";
|
|
4
4
|
import { type ConfiguredThinkingLevel } from "../render/render-utils";
|
|
5
5
|
import type { StructuredSubagentOutput } from "./task";
|
|
6
|
+
import { type JobWidgetView } from "./job-widget";
|
|
6
7
|
import type { RenderResultOptions, ToolActivitySummary } from "./renderer";
|
|
7
8
|
import type { IrcDeliveryReceipt, IrcMessage } from "./irc";
|
|
8
9
|
import type { NativeToolView, ToolRenderResult } from "./renderer";
|
|
@@ -29,6 +30,10 @@ export interface JobSnapshot {
|
|
|
29
30
|
advisor?: boolean;
|
|
30
31
|
/** The task agent's latest self-estimated completion (0–100); see `AgentProgress.completionPercent`. */
|
|
31
32
|
completionPercent?: number;
|
|
33
|
+
/** The task agent's latest stage in its own words; see `AgentProgress.stage`. */
|
|
34
|
+
stage?: string;
|
|
35
|
+
/** The job's live widget, while one is being drawn; frozen at its last state once the job settles. */
|
|
36
|
+
widget?: JobWidgetView;
|
|
32
37
|
resultText?: string;
|
|
33
38
|
errorText?: string;
|
|
34
39
|
/** Source-output metadata retained for per-job warnings and persisted row rendering. */
|