@marcoscale98/pi-extensible-workflows 5.14.1-fork.1 → 5.19.1-fork.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 +135 -0
- package/dist/src/agent-execution.js +114 -87
- package/dist/src/background-widget.d.ts +61 -0
- package/dist/src/background-widget.js +23 -16
- package/dist/src/decoders.js +28 -1
- package/dist/src/execution.d.ts +4 -2
- package/dist/src/execution.js +43 -2
- package/dist/src/host-delivery.js +1 -0
- package/dist/src/host-navigator.d.ts +18 -0
- package/dist/src/host-navigator.js +71 -13
- package/dist/src/host-recovery.d.ts +5 -2
- package/dist/src/host-recovery.js +3 -3
- package/dist/src/host-view.d.ts +21 -1
- package/dist/src/host-view.js +138 -47
- package/dist/src/host.d.ts +2 -2
- package/dist/src/host.js +246 -60
- package/dist/src/index.d.ts +3 -1
- package/dist/src/index.js +1346 -622
- package/dist/src/index.js.map +3 -3
- package/dist/src/paths.d.ts +2 -0
- package/dist/src/paths.js +2 -0
- package/dist/src/registry.js +3 -2
- package/dist/src/roles.d.ts +70 -0
- package/dist/src/roles.js +326 -0
- package/dist/src/script-tools.d.ts +27 -0
- package/dist/src/script-tools.js +87 -0
- package/dist/src/settings.d.ts +26 -0
- package/dist/src/settings.js +277 -0
- package/dist/src/trajectory.d.ts +39 -4
- package/dist/src/trajectory.js +82 -5
- package/dist/src/types.d.ts +30 -3
- package/dist/src/types.js +1 -1
- package/dist/src/utils.d.ts +21 -1
- package/dist/src/utils.js +40 -0
- package/dist/src/validation.d.ts +10 -17
- package/dist/src/validation.js +29 -458
- package/dist/starter/index.js +0 -2
- package/dist/starter/index.js.map +1 -1
- package/dist/starter/review-loop.d.ts +0 -13
- package/dist/starter/review-loop.js +0 -2
- package/dist/subagents/index.js +1344 -798
- package/dist/subagents/index.js.map +3 -3
- package/dist/subagents/src/contracts.d.ts +49 -29
- package/dist/subagents/src/contracts.js +18 -7
- package/dist/subagents/src/index.d.ts +1 -1
- package/dist/subagents/src/index.js +15 -7
- package/dist/subagents/src/manager.js +32 -13
- package/dist/subagents/src/navigator.js +396 -247
- package/dist/subagents/src/view.d.ts +24 -2
- package/dist/subagents/src/view.js +107 -80
- package/dist/trajectory/assets/index.html +370 -232
- package/dist/trajectory/assets/prism.min.js +13 -0
- package/dist/trajectory/index.js +445 -277
- package/dist/trajectory/index.js.map +3 -3
- package/dist/trajectory/src/assets/index.html +370 -232
- package/dist/trajectory/src/assets/prism.min.js +13 -0
- package/dist/trajectory/src/export.js +4 -2
- package/dist/trajectory/src/index.d.ts +2 -0
- package/dist/trajectory/src/index.js +151 -60
- package/dist/trajectory/src/server.js +138 -11
- package/dist/trajectory/src/server.js.map +2 -2
- package/package.json +7 -6
- package/skills/pi-extensible-workflows/SKILL.md +2 -0
- package/src/agent-execution.ts +110 -80
- package/src/background-widget.ts +26 -19
- package/src/decoders.ts +22 -1
- package/src/execution.ts +41 -4
- package/src/host-delivery.ts +1 -0
- package/src/host-navigator.ts +58 -14
- package/src/host-recovery.ts +8 -7
- package/src/host-view.ts +116 -42
- package/src/host.ts +223 -55
- package/src/index.ts +3 -1
- package/src/paths.ts +3 -0
- package/src/registry.ts +3 -2
- package/src/roles.ts +327 -0
- package/src/script-tools.ts +76 -0
- package/src/settings.ts +224 -0
- package/src/trajectory.ts +97 -9
- package/src/types.ts +12 -5
- package/src/utils.ts +36 -1
- package/src/validation.ts +30 -356
- package/starter/review-loop.ts +0 -2
- package/subagents/README.md +3 -3
- package/subagents/src/contracts.ts +22 -7
- package/subagents/src/index.ts +18 -7
- package/subagents/src/manager.ts +31 -13
- package/subagents/src/navigator.ts +336 -180
- package/subagents/src/view.ts +108 -76
- package/trajectory/src/assets/index.html +370 -232
- package/trajectory/src/assets/prism.min.js +13 -0
- package/trajectory/src/export.ts +4 -2
- package/trajectory/src/index.ts +124 -47
- package/trajectory/src/server.ts +114 -14
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentToolResult, ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
1
|
+
import type { AgentToolResult, ExtensionAPI, ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { SubagentRunRequest, SubagentStatus } from "./contracts.js";
|
|
3
3
|
type SubagentRenderArgs = Partial<SubagentRunRequest> & {
|
|
4
4
|
id?: string;
|
|
@@ -37,13 +37,35 @@ export declare function renderSubagentResult(result: AgentToolResult<unknown>, o
|
|
|
37
37
|
render(width: number): string[];
|
|
38
38
|
invalidate(): void;
|
|
39
39
|
};
|
|
40
|
-
|
|
40
|
+
type SubagentReceipt = {
|
|
41
|
+
readonly id: string;
|
|
42
|
+
readonly label: string;
|
|
43
|
+
readonly state: string;
|
|
44
|
+
readonly model?: string;
|
|
45
|
+
readonly role?: string;
|
|
46
|
+
readonly tools?: readonly string[];
|
|
47
|
+
readonly input: number;
|
|
48
|
+
readonly output: number;
|
|
49
|
+
readonly cacheRead: number;
|
|
50
|
+
readonly costUsd: number;
|
|
51
|
+
readonly durationMs: number;
|
|
52
|
+
readonly attempts: number;
|
|
53
|
+
readonly error?: string;
|
|
54
|
+
};
|
|
55
|
+
/** The transcript line a finished background run leaves behind, in the shape of the workflow receipt. */
|
|
56
|
+
export declare function renderSubagentReceipt(data: SubagentReceipt, expanded: boolean, theme: Theme): string[];
|
|
57
|
+
type ReceiptHost = {
|
|
58
|
+
appendEntry?: (customType: string, data: SubagentReceipt) => void;
|
|
59
|
+
registerEntryRenderer?: ExtensionAPI["registerEntryRenderer"];
|
|
60
|
+
};
|
|
61
|
+
export declare function createSubagentBackgroundWidget(host?: ReceiptHost): {
|
|
41
62
|
start(next: ExtensionContext): void;
|
|
42
63
|
update(status: Readonly<SubagentStatus>, request: Readonly<SubagentRunRequest>): void;
|
|
43
64
|
dispose(): void;
|
|
44
65
|
};
|
|
45
66
|
export declare function renderSubagentInspectCall(args: {
|
|
46
67
|
id?: string;
|
|
68
|
+
scope?: string;
|
|
47
69
|
}, theme: Theme): {
|
|
48
70
|
render(width: number): string[];
|
|
49
71
|
invalidate(): void;
|
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
2
|
-
import { formatCost, formatStalledDuration, sanitizeDisplayText, WORKFLOW_AGENT_STALL_THRESHOLD_MS } from "../../src/index.js";
|
|
2
|
+
import { formatCost, formatStalledDuration, formatWorkflowRuntime, sanitizeDisplayText, WORKFLOW_AGENT_STALL_THRESHOLD_MS } from "../../src/index.js";
|
|
3
|
+
import { agentActivityLabel, stalledDuration, textBlock } from "../../src/host-view.js";
|
|
4
|
+
import { drawFrame, formatElapsed, formatTokens, mark, QUIET_MS, REPAINT_MS } from "../../src/background-widget.js";
|
|
3
5
|
const SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
4
6
|
const TERMINAL_STATES = new Set(["completed", "failed", "stopped"]);
|
|
5
7
|
const MAX_INSPECT_VALUE_CHARS = 4000;
|
|
6
8
|
const MAX_INSPECT_TOOL_CALLS = 32;
|
|
7
9
|
const MAX_BACKGROUND_WIDGET_ROWS = 10;
|
|
8
|
-
function textBlock(text) {
|
|
9
|
-
return {
|
|
10
|
-
render(width) { return text.split("\n").map((line) => truncateToWidth(line, Math.max(1, width), "…")); },
|
|
11
|
-
invalidate() { },
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
10
|
function roleName(value) {
|
|
15
11
|
if (typeof value === "string")
|
|
16
12
|
return value.trim() || undefined;
|
|
@@ -59,20 +55,6 @@ function statusValue(value) {
|
|
|
59
55
|
return undefined;
|
|
60
56
|
return value;
|
|
61
57
|
}
|
|
62
|
-
function runtime(startedAt, finishedAt, now) {
|
|
63
|
-
if (startedAt === undefined)
|
|
64
|
-
return "";
|
|
65
|
-
const seconds = Math.max(0, Math.floor(((finishedAt ?? now) - startedAt) / 1000));
|
|
66
|
-
if (seconds < 60)
|
|
67
|
-
return `${String(seconds)}s`;
|
|
68
|
-
const minutes = Math.floor(seconds / 60);
|
|
69
|
-
const remainingSeconds = seconds % 60;
|
|
70
|
-
if (minutes < 60)
|
|
71
|
-
return `${String(minutes)}m${remainingSeconds ? ` ${String(remainingSeconds)}s` : ""}`;
|
|
72
|
-
const hours = Math.floor(minutes / 60);
|
|
73
|
-
const remainingMinutes = minutes % 60;
|
|
74
|
-
return `${String(hours)}h${remainingMinutes ? ` ${String(remainingMinutes)}m` : ""}`;
|
|
75
|
-
}
|
|
76
58
|
function stateGlyph(state, spinner) {
|
|
77
59
|
if (state === "running")
|
|
78
60
|
return spinner;
|
|
@@ -83,32 +65,14 @@ function stateColor(state) {
|
|
|
83
65
|
return "accent";
|
|
84
66
|
return state === "completed" ? "success" : "error";
|
|
85
67
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
return undefined;
|
|
89
|
-
const current = status.progress?.activity;
|
|
90
|
-
if (current?.kind === "reasoning" || current?.kind === "text") {
|
|
91
|
-
const name = current.kind === "reasoning" ? "reasoning" : "responding";
|
|
92
|
-
const text = sanitizeDisplayText(current.text);
|
|
93
|
-
return text && text !== name ? `${name} · ${text}` : name;
|
|
94
|
-
}
|
|
95
|
-
if (current?.kind === "tool")
|
|
96
|
-
return sanitizeDisplayText(current.text);
|
|
97
|
-
return sanitizeDisplayText([...(status.progress?.toolCalls ?? [])].reverse().find(({ state }) => state === "running")?.name ?? "");
|
|
98
|
-
}
|
|
99
|
-
function stalledDuration(status, now) {
|
|
100
|
-
const lastEventAt = status.progress?.lastEventAt;
|
|
101
|
-
if (status.state !== "running" || lastEventAt === undefined || !Number.isFinite(lastEventAt))
|
|
102
|
-
return undefined;
|
|
103
|
-
const duration = now - lastEventAt;
|
|
104
|
-
return duration >= WORKFLOW_AGENT_STALL_THRESHOLD_MS ? duration : undefined;
|
|
105
|
-
}
|
|
106
|
-
function accounting(status) {
|
|
68
|
+
/** Tokens and cost as the workflow header shows them. */
|
|
69
|
+
function usageStats(status) {
|
|
107
70
|
const value = status.progress?.accounting;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
71
|
+
return value ? [formatTokens(value.input + value.output), formatCost(value.cost)].filter(Boolean).join(" · ") : "";
|
|
72
|
+
}
|
|
73
|
+
function modelName(status) {
|
|
74
|
+
const model = status.progress?.state?.model ?? status.attemptDetails?.at(-1)?.setup.model;
|
|
75
|
+
return model ? `${model.model}${model.thinking ? `:${model.thinking}` : ""}` : "";
|
|
112
76
|
}
|
|
113
77
|
function timestamp(value) {
|
|
114
78
|
if (value === undefined || !Number.isFinite(value) || value < 0)
|
|
@@ -129,15 +93,16 @@ function boundedValue(value) {
|
|
|
129
93
|
}
|
|
130
94
|
function formatSubagentProgress(status, args, theme, spinner, now, expanded) {
|
|
131
95
|
const color = stateColor(status.state);
|
|
132
|
-
const elapsed =
|
|
96
|
+
const elapsed = status.startedAt === undefined ? "" : formatWorkflowRuntime((status.finishedAt ?? now) - status.startedAt);
|
|
133
97
|
const metadata = requestMetadata(args, args.id === undefined);
|
|
98
|
+
const stats = usageStats(status);
|
|
134
99
|
const lines = [
|
|
135
|
-
`${theme.fg(color, stateGlyph(status.state,
|
|
100
|
+
`${theme.fg(color, stateGlyph(status.state, "●"))} ${theme.bold(theme.fg("accent", `Subagent: ${label({ ...args, id: status.id })}`))} ${theme.fg(color, `[${status.state}]`)}${metadata ? ` ${theme.fg("dim", metadata)}` : ""}${stats ? ` ${stats}` : ""}${elapsed ? ` runtime=${elapsed}` : ""}`,
|
|
136
101
|
];
|
|
137
|
-
const current =
|
|
138
|
-
const stalled = stalledDuration(status, now);
|
|
139
|
-
if (current)
|
|
140
|
-
lines.push(` ${theme.fg("accent", spinner)} ${theme.fg("dim", current)}${stalled === undefined ? "" : ` ${theme.fg("warning", `- stalled? ${formatStalledDuration(stalled)}`)}`}`);
|
|
102
|
+
const current = status.state === "running" ? agentActivityLabel(status.progress ?? {}) : undefined;
|
|
103
|
+
const stalled = stalledDuration({ ...status.progress, state: status.state }, now);
|
|
104
|
+
if (current || status.state === "running")
|
|
105
|
+
lines.push(` ${theme.fg("accent", spinner)}${current ? ` ${theme.fg("dim", current)}` : ""}${stalled === undefined ? "" : ` ${theme.fg("warning", `- stalled? ${formatStalledDuration(stalled)}`)}`}`);
|
|
141
106
|
else if (stalled !== undefined)
|
|
142
107
|
lines.push(` ${theme.fg("warning", `stalled? ${formatStalledDuration(stalled)}`)}`);
|
|
143
108
|
if (status.error)
|
|
@@ -147,9 +112,6 @@ function formatSubagentProgress(status, args, theme, spinner, now, expanded) {
|
|
|
147
112
|
const model = status.progress?.state?.model;
|
|
148
113
|
if (model)
|
|
149
114
|
lines.push(` ${theme.fg("dim", `model=${model.provider}/${model.model}${model.thinking ? `:${model.thinking}` : ""}`)}`);
|
|
150
|
-
const usage = accounting(status);
|
|
151
|
-
if (usage)
|
|
152
|
-
lines.push(` ${theme.fg("dim", usage)}`);
|
|
153
115
|
if (status.worktree)
|
|
154
116
|
lines.push(` ${theme.fg("dim", `worktree=${status.worktree.path} branch=${status.worktree.branch}`)}`);
|
|
155
117
|
}
|
|
@@ -221,7 +183,7 @@ export function subagentProgressBlock(status, args, theme, freezeAt) {
|
|
|
221
183
|
const now = TERMINAL_STATES.has(current.state) ? frozenAt : Date.now();
|
|
222
184
|
if (!TERMINAL_STATES.has(current.state))
|
|
223
185
|
frozenAt = now;
|
|
224
|
-
const frame = SPINNER[Math.floor(now /
|
|
186
|
+
const frame = SPINNER[Math.floor(now / REPAINT_MS) % SPINNER.length] ?? "◇";
|
|
225
187
|
return formatSubagentProgress(current, args, currentTheme, frame, now, expanded).split("\n").map((line) => truncateToWidth(line, Math.max(1, width), "…"));
|
|
226
188
|
},
|
|
227
189
|
invalidate() { },
|
|
@@ -237,7 +199,7 @@ export function renderSubagentResult(result, options, theme, context) {
|
|
|
237
199
|
state.subagentStatus = state.subagentStatus?.id === incoming.id ? { ...state.subagentStatus, ...incoming } : incoming;
|
|
238
200
|
const status = state.subagentStatus;
|
|
239
201
|
if (status?.state === "running" && options.isPartial && !state.subagentSpinner) {
|
|
240
|
-
state.subagentSpinner = setInterval(() => { context.invalidate(); },
|
|
202
|
+
state.subagentSpinner = setInterval(() => { context.invalidate(); }, REPAINT_MS);
|
|
241
203
|
state.subagentSpinner.unref();
|
|
242
204
|
}
|
|
243
205
|
else if ((!options.isPartial || status?.state !== "running") && state.subagentSpinner) {
|
|
@@ -263,9 +225,75 @@ export function renderSubagentResult(result, options, theme, context) {
|
|
|
263
225
|
component.setExpanded(options.expanded);
|
|
264
226
|
return component;
|
|
265
227
|
}
|
|
266
|
-
|
|
228
|
+
/** One run as a workflow agent row: a quiet or stalled warning on the left, model and figures against the right rule. */
|
|
229
|
+
function widgetRow({ status, request }, now, paint) {
|
|
230
|
+
const lastEventAt = status.progress?.lastEventAt;
|
|
231
|
+
const silent = lastEventAt === undefined || !Number.isFinite(lastEventAt) || now - lastEventAt < QUIET_MS ? undefined : now - lastEventAt;
|
|
232
|
+
const role = silent !== undefined && silent >= WORKFLOW_AGENT_STALL_THRESHOLD_MS ? "error" : "warning";
|
|
233
|
+
const icon = silent === undefined ? mark(status.state, now, paint) : paint(role, "⚠");
|
|
234
|
+
const warning = silent === undefined ? "" : ` ${paint(role, `${role === "error" ? "stalled" : "quiet"} ${formatElapsed(silent)}`)}`;
|
|
235
|
+
const accounting = status.progress?.accounting;
|
|
236
|
+
const attempts = status.attempts ?? 1;
|
|
237
|
+
const right = [
|
|
238
|
+
modelName(status),
|
|
239
|
+
formatTokens(accounting === undefined ? undefined : accounting.input + accounting.output),
|
|
240
|
+
formatCost(accounting?.cost),
|
|
241
|
+
status.startedAt === undefined ? "" : formatElapsed(now - status.startedAt),
|
|
242
|
+
attempts > 1 ? `attempt ${String(attempts)}` : "",
|
|
243
|
+
].filter(Boolean).join(" · ");
|
|
244
|
+
return { text: `${icon} ${sanitizeDisplayText(label({ ...request, id: status.id }))}${warning}`, right };
|
|
245
|
+
}
|
|
246
|
+
const RECEIPT_ENTRY_TYPE = "piewf-subagent-receipt";
|
|
247
|
+
function receiptFor(status, request) {
|
|
248
|
+
const accounting = status.progress?.accounting;
|
|
249
|
+
const model = modelName(status);
|
|
250
|
+
const role = roleName(request.role);
|
|
251
|
+
const tools = status.progress?.state?.tools ?? status.attemptDetails?.at(-1)?.setup.tools;
|
|
252
|
+
return {
|
|
253
|
+
id: status.id,
|
|
254
|
+
label: label({ ...request, id: status.id }),
|
|
255
|
+
state: status.state,
|
|
256
|
+
...(model ? { model } : {}),
|
|
257
|
+
...(role === undefined ? {} : { role }),
|
|
258
|
+
...(tools === undefined ? {} : { tools: [...tools] }),
|
|
259
|
+
input: accounting?.input ?? 0,
|
|
260
|
+
output: accounting?.output ?? 0,
|
|
261
|
+
cacheRead: accounting?.cacheRead ?? 0,
|
|
262
|
+
costUsd: accounting?.cost ?? 0,
|
|
263
|
+
durationMs: status.startedAt === undefined ? 0 : Math.max(0, (status.finishedAt ?? Date.now()) - status.startedAt),
|
|
264
|
+
attempts: status.attempts ?? 1,
|
|
265
|
+
...(status.error === undefined ? {} : { error: `${status.error.code}: ${status.error.message}` }),
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
/** The transcript line a finished background run leaves behind, in the shape of the workflow receipt. */
|
|
269
|
+
export function renderSubagentReceipt(data, expanded, theme) {
|
|
270
|
+
const glyph = data.state === "completed" ? theme.fg("success", "✓") : data.state === "failed" ? theme.fg("error", "✗") : theme.fg("muted", "·");
|
|
271
|
+
const headline = [formatTokens(data.input + data.output), formatCost(data.costUsd) || "$0.00", formatElapsed(data.durationMs), data.state].filter(Boolean).join(" · ");
|
|
272
|
+
const lines = [`${glyph} ${theme.bold(sanitizeDisplayText(data.label))} ${theme.fg("muted", headline)}`];
|
|
273
|
+
if (!expanded)
|
|
274
|
+
return lines;
|
|
275
|
+
const meta = [data.model ?? "", data.role ? `role ${data.role}` : "", data.attempts > 1 ? `${String(data.attempts)} attempts` : "", data.tools?.join(" ") ?? ""].filter(Boolean);
|
|
276
|
+
if (meta.length > 0)
|
|
277
|
+
lines.push(theme.fg("muted", ` ${meta.join(" · ")}`));
|
|
278
|
+
const split = [data.input ? `in ${formatTokens(data.input)}` : "", data.output ? `out ${formatTokens(data.output)}` : "", data.cacheRead ? `cache ${formatTokens(data.cacheRead)}` : ""].filter(Boolean);
|
|
279
|
+
if (split.length > 0)
|
|
280
|
+
lines.push(theme.fg("muted", ` ${split.join(" · ")}`));
|
|
281
|
+
if (data.error !== undefined)
|
|
282
|
+
lines.push(theme.fg("error", ` ${sanitizeDisplayText(data.error)}`));
|
|
283
|
+
lines.push(theme.fg("muted", ` run ${data.id}`));
|
|
284
|
+
return lines;
|
|
285
|
+
}
|
|
286
|
+
export function createSubagentBackgroundWidget(host = {}) {
|
|
267
287
|
const key = "piewf-subagents-background";
|
|
268
288
|
const runs = new Map();
|
|
289
|
+
const receipted = new Set();
|
|
290
|
+
host.registerEntryRenderer?.(RECEIPT_ENTRY_TYPE, (entry, options, theme) => {
|
|
291
|
+
if (!entry.data)
|
|
292
|
+
return undefined;
|
|
293
|
+
const lines = renderSubagentReceipt(entry.data, options.expanded, theme);
|
|
294
|
+
// Pi treats an over-wide line as fatal, and an error message has no bound.
|
|
295
|
+
return { render: (width) => lines.map((line) => truncateToWidth(line, Math.max(1, width), "…")), invalidate() { } };
|
|
296
|
+
});
|
|
269
297
|
let context;
|
|
270
298
|
let requestRender;
|
|
271
299
|
let timer;
|
|
@@ -298,31 +326,20 @@ export function createSubagentBackgroundWidget() {
|
|
|
298
326
|
return {
|
|
299
327
|
render(width) {
|
|
300
328
|
const now = Date.now();
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
const
|
|
305
|
-
const
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
for (const block of blocks) {
|
|
310
|
-
if (runRows + block.length > runRowBudget)
|
|
311
|
-
break;
|
|
312
|
-
lines.push(...block);
|
|
313
|
-
runRows += block.length;
|
|
314
|
-
displayedRuns += 1;
|
|
315
|
-
}
|
|
316
|
-
const hiddenRuns = blocks.length - displayedRuns;
|
|
317
|
-
if (hiddenRuns > 0)
|
|
318
|
-
lines.push(`… ${String(hiddenRuns)} more`);
|
|
319
|
-
return lines.map((line) => truncateToWidth(line, Math.max(1, width), "…"));
|
|
329
|
+
const paintRole = (role, text) => theme.fg(role, text);
|
|
330
|
+
// One row per run, so the default concurrency of eight fills the frame exactly.
|
|
331
|
+
// ponytail: no Alt+O scrolling as in the workflow widget; add it if runs routinely exceed the frame.
|
|
332
|
+
const budget = MAX_BACKGROUND_WIDGET_ROWS - 2;
|
|
333
|
+
const rows = [...runs.values()].map((run) => widgetRow(run, now, paintRole));
|
|
334
|
+
const body = rows.length <= budget ? rows : [...rows.slice(0, budget - 1), { text: theme.fg("muted", `… ${String(rows.length - budget + 1)} more`) }];
|
|
335
|
+
const title = runs.size === 1 ? "Subagent" : `Subagents · ${String(runs.size)} runs`;
|
|
336
|
+
return drawFrame(title, "", body, width, paintRole, (text) => theme.fg("accent", text)).map((line) => truncateToWidth(line, Math.max(1, width), ""));
|
|
320
337
|
},
|
|
321
338
|
invalidate() { },
|
|
322
339
|
};
|
|
323
340
|
}, { placement: "belowEditor" });
|
|
324
341
|
showing = true;
|
|
325
|
-
timer = setInterval(() => { requestRender?.(); },
|
|
342
|
+
timer = setInterval(() => { requestRender?.(); }, REPAINT_MS);
|
|
326
343
|
timer.unref();
|
|
327
344
|
}
|
|
328
345
|
catch {
|
|
@@ -333,13 +350,23 @@ export function createSubagentBackgroundWidget() {
|
|
|
333
350
|
start(next) {
|
|
334
351
|
hide();
|
|
335
352
|
runs.clear();
|
|
353
|
+
receipted.clear();
|
|
336
354
|
context = next.mode === "tui" ? next : undefined;
|
|
337
355
|
},
|
|
338
356
|
update(status, request) {
|
|
339
357
|
if (request.mode !== "background" || context === undefined)
|
|
340
358
|
return;
|
|
341
|
-
if (TERMINAL_STATES.has(status.state))
|
|
359
|
+
if (TERMINAL_STATES.has(status.state)) {
|
|
342
360
|
runs.delete(status.id);
|
|
361
|
+
// A foreground run already has its tool block; a background one leaves a receipt, once, like a workflow run.
|
|
362
|
+
if (!receipted.has(status.id)) {
|
|
363
|
+
receipted.add(status.id);
|
|
364
|
+
try {
|
|
365
|
+
host.appendEntry?.(RECEIPT_ENTRY_TYPE, receiptFor(status, request));
|
|
366
|
+
}
|
|
367
|
+
catch { /* A receipt is display-only. */ }
|
|
368
|
+
}
|
|
369
|
+
}
|
|
343
370
|
else
|
|
344
371
|
runs.set(status.id, { status, request });
|
|
345
372
|
paint();
|
|
@@ -353,7 +380,7 @@ export function createSubagentBackgroundWidget() {
|
|
|
353
380
|
}
|
|
354
381
|
export function renderSubagentInspectCall(args, theme) {
|
|
355
382
|
const title = theme.fg("toolTitle", theme.bold("subagents_inspect"));
|
|
356
|
-
return textBlock(args.id ? `${title} ${theme.fg("accent", args.id)}` : `${title} ${theme.fg("muted", "all")}`);
|
|
383
|
+
return textBlock(args.id ? `${title} ${theme.fg("accent", args.id)}` : `${title} ${theme.fg("muted", args.scope === "all" ? "all" : "session")}`);
|
|
357
384
|
}
|
|
358
385
|
export function renderSubagentInspectResult(result, options, theme, args) {
|
|
359
386
|
const status = statusValue(result.details);
|