@marcoscale98/pi-extensible-workflows 5.14.1-fork.1 → 5.17.0-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 +67 -0
- package/dist/src/agent-execution.js +36 -51
- package/dist/src/background-widget.d.ts +56 -0
- package/dist/src/background-widget.js +22 -15
- package/dist/src/decoders.js +2 -1
- package/dist/src/host-navigator.d.ts +18 -0
- package/dist/src/host-navigator.js +71 -13
- package/dist/src/host-view.d.ts +16 -1
- package/dist/src/host-view.js +109 -42
- package/dist/src/host.js +57 -24
- package/dist/src/index.d.ts +3 -1
- package/dist/src/index.js +867 -545
- package/dist/src/index.js.map +3 -3
- package/dist/src/registry.js +2 -1
- package/dist/src/roles.d.ts +70 -0
- package/dist/src/roles.js +325 -0
- package/dist/src/settings.d.ts +13 -0
- package/dist/src/settings.js +253 -0
- package/dist/src/trajectory.d.ts +29 -4
- package/dist/src/trajectory.js +58 -3
- package/dist/src/types.d.ts +0 -1
- package/dist/src/utils.d.ts +2 -1
- package/dist/src/utils.js +10 -0
- package/dist/src/validation.d.ts +5 -17
- package/dist/src/validation.js +9 -458
- package/dist/subagents/index.js +1132 -694
- package/dist/subagents/index.js.map +3 -3
- package/dist/subagents/src/contracts.d.ts +4 -0
- package/dist/subagents/src/index.d.ts +1 -1
- package/dist/subagents/src/index.js +2 -1
- package/dist/subagents/src/manager.js +29 -10
- package/dist/subagents/src/navigator.js +396 -247
- package/dist/subagents/src/view.d.ts +23 -2
- package/dist/subagents/src/view.js +96 -30
- package/dist/trajectory/assets/index.html +347 -231
- package/dist/trajectory/assets/prism.min.js +13 -0
- package/dist/trajectory/index.js +377 -257
- package/dist/trajectory/index.js.map +3 -3
- package/dist/trajectory/src/assets/index.html +347 -231
- 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 +143 -42
- package/dist/trajectory/src/server.js +134 -9
- package/dist/trajectory/src/server.js.map +1 -1
- package/package.json +2 -1
- package/src/agent-execution.ts +36 -42
- package/src/background-widget.ts +25 -18
- package/src/decoders.ts +2 -1
- package/src/host-navigator.ts +58 -14
- package/src/host-view.ts +93 -37
- package/src/host.ts +51 -21
- package/src/index.ts +3 -1
- package/src/registry.ts +2 -1
- package/src/roles.ts +326 -0
- package/src/settings.ts +206 -0
- package/src/trajectory.ts +76 -7
- package/src/types.ts +1 -1
- package/src/utils.ts +8 -1
- package/src/validation.ts +12 -356
- package/subagents/README.md +1 -1
- package/subagents/src/contracts.ts +4 -0
- package/subagents/src/index.ts +3 -2
- package/subagents/src/manager.ts +28 -10
- package/subagents/src/navigator.ts +336 -180
- package/subagents/src/view.ts +97 -28
- package/trajectory/src/assets/index.html +347 -231
- package/trajectory/src/assets/prism.min.js +13 -0
- package/trajectory/src/export.ts +4 -2
- package/trajectory/src/index.ts +118 -38
- package/trajectory/src/server.ts +112 -12
|
@@ -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,7 +37,28 @@ 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;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
2
2
|
import { formatCost, formatStalledDuration, sanitizeDisplayText, WORKFLOW_AGENT_STALL_THRESHOLD_MS } from "../../src/index.js";
|
|
3
|
+
import { drawFrame, formatElapsed, formatTokens, mark, QUIET_MS } from "../../src/background-widget.js";
|
|
3
4
|
const SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
4
5
|
const TERMINAL_STATES = new Set(["completed", "failed", "stopped"]);
|
|
5
6
|
const MAX_INSPECT_VALUE_CHARS = 4000;
|
|
@@ -103,12 +104,14 @@ function stalledDuration(status, now) {
|
|
|
103
104
|
const duration = now - lastEventAt;
|
|
104
105
|
return duration >= WORKFLOW_AGENT_STALL_THRESHOLD_MS ? duration : undefined;
|
|
105
106
|
}
|
|
106
|
-
|
|
107
|
+
/** Tokens and cost as the workflow header shows them. */
|
|
108
|
+
function usageStats(status) {
|
|
107
109
|
const value = status.progress?.accounting;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
return value ? [formatTokens(value.input + value.output), formatCost(value.cost)].filter(Boolean).join(" · ") : "";
|
|
111
|
+
}
|
|
112
|
+
function modelName(status) {
|
|
113
|
+
const model = status.progress?.state?.model ?? status.attemptDetails?.at(-1)?.setup.model;
|
|
114
|
+
return model ? `${model.model}${model.thinking ? `:${model.thinking}` : ""}` : "";
|
|
112
115
|
}
|
|
113
116
|
function timestamp(value) {
|
|
114
117
|
if (value === undefined || !Number.isFinite(value) || value < 0)
|
|
@@ -131,8 +134,9 @@ function formatSubagentProgress(status, args, theme, spinner, now, expanded) {
|
|
|
131
134
|
const color = stateColor(status.state);
|
|
132
135
|
const elapsed = runtime(status.startedAt, status.finishedAt, now);
|
|
133
136
|
const metadata = requestMetadata(args, args.id === undefined);
|
|
137
|
+
const stats = usageStats(status);
|
|
134
138
|
const lines = [
|
|
135
|
-
`${theme.fg(color, stateGlyph(status.state, spinner))} ${theme.bold(theme.fg("accent", `Subagent: ${label({ ...args, id: status.id })}`))} ${theme.fg(color, `[${status.state}]`)}${metadata ? ` ${theme.fg("dim", metadata)}` : ""}${elapsed ? ` runtime=${elapsed}` : ""}`,
|
|
139
|
+
`${theme.fg(color, stateGlyph(status.state, spinner))} ${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
140
|
];
|
|
137
141
|
const current = activity(status);
|
|
138
142
|
const stalled = stalledDuration(status, now);
|
|
@@ -147,9 +151,6 @@ function formatSubagentProgress(status, args, theme, spinner, now, expanded) {
|
|
|
147
151
|
const model = status.progress?.state?.model;
|
|
148
152
|
if (model)
|
|
149
153
|
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
154
|
if (status.worktree)
|
|
154
155
|
lines.push(` ${theme.fg("dim", `worktree=${status.worktree.path} branch=${status.worktree.branch}`)}`);
|
|
155
156
|
}
|
|
@@ -263,9 +264,75 @@ export function renderSubagentResult(result, options, theme, context) {
|
|
|
263
264
|
component.setExpanded(options.expanded);
|
|
264
265
|
return component;
|
|
265
266
|
}
|
|
266
|
-
|
|
267
|
+
/** One run as a workflow agent row: a quiet or stalled warning on the left, model and figures against the right rule. */
|
|
268
|
+
function widgetRow({ status, request }, now, paint) {
|
|
269
|
+
const lastEventAt = status.progress?.lastEventAt;
|
|
270
|
+
const silent = lastEventAt === undefined || !Number.isFinite(lastEventAt) || now - lastEventAt < QUIET_MS ? undefined : now - lastEventAt;
|
|
271
|
+
const role = silent !== undefined && silent >= WORKFLOW_AGENT_STALL_THRESHOLD_MS ? "error" : "warning";
|
|
272
|
+
const icon = silent === undefined ? mark(status.state, now, paint) : paint(role, "⚠");
|
|
273
|
+
const warning = silent === undefined ? "" : ` ${paint(role, `${role === "error" ? "stalled" : "quiet"} ${formatElapsed(silent)}`)}`;
|
|
274
|
+
const accounting = status.progress?.accounting;
|
|
275
|
+
const attempts = status.attempts ?? 1;
|
|
276
|
+
const right = [
|
|
277
|
+
modelName(status),
|
|
278
|
+
formatTokens(accounting === undefined ? undefined : accounting.input + accounting.output),
|
|
279
|
+
formatCost(accounting?.cost),
|
|
280
|
+
status.startedAt === undefined ? "" : formatElapsed(now - status.startedAt),
|
|
281
|
+
attempts > 1 ? `attempt ${String(attempts)}` : "",
|
|
282
|
+
].filter(Boolean).join(" · ");
|
|
283
|
+
return { text: `${icon} ${sanitizeDisplayText(label({ ...request, id: status.id }))}${warning}`, right };
|
|
284
|
+
}
|
|
285
|
+
const RECEIPT_ENTRY_TYPE = "piewf-subagent-receipt";
|
|
286
|
+
function receiptFor(status, request) {
|
|
287
|
+
const accounting = status.progress?.accounting;
|
|
288
|
+
const model = modelName(status);
|
|
289
|
+
const role = roleName(request.role);
|
|
290
|
+
const tools = status.progress?.state?.tools ?? status.attemptDetails?.at(-1)?.setup.tools;
|
|
291
|
+
return {
|
|
292
|
+
id: status.id,
|
|
293
|
+
label: label({ ...request, id: status.id }),
|
|
294
|
+
state: status.state,
|
|
295
|
+
...(model ? { model } : {}),
|
|
296
|
+
...(role === undefined ? {} : { role }),
|
|
297
|
+
...(tools === undefined ? {} : { tools: [...tools] }),
|
|
298
|
+
input: accounting?.input ?? 0,
|
|
299
|
+
output: accounting?.output ?? 0,
|
|
300
|
+
cacheRead: accounting?.cacheRead ?? 0,
|
|
301
|
+
costUsd: accounting?.cost ?? 0,
|
|
302
|
+
durationMs: status.startedAt === undefined ? 0 : Math.max(0, (status.finishedAt ?? Date.now()) - status.startedAt),
|
|
303
|
+
attempts: status.attempts ?? 1,
|
|
304
|
+
...(status.error === undefined ? {} : { error: `${status.error.code}: ${status.error.message}` }),
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
/** The transcript line a finished background run leaves behind, in the shape of the workflow receipt. */
|
|
308
|
+
export function renderSubagentReceipt(data, expanded, theme) {
|
|
309
|
+
const glyph = data.state === "completed" ? theme.fg("success", "✓") : data.state === "failed" ? theme.fg("error", "✗") : theme.fg("muted", "·");
|
|
310
|
+
const headline = [formatTokens(data.input + data.output), formatCost(data.costUsd) || "$0.00", formatElapsed(data.durationMs), data.state].filter(Boolean).join(" · ");
|
|
311
|
+
const lines = [`${glyph} ${theme.bold(sanitizeDisplayText(data.label))} ${theme.fg("muted", headline)}`];
|
|
312
|
+
if (!expanded)
|
|
313
|
+
return lines;
|
|
314
|
+
const meta = [data.model ?? "", data.role ? `role ${data.role}` : "", data.attempts > 1 ? `${String(data.attempts)} attempts` : "", data.tools?.join(" ") ?? ""].filter(Boolean);
|
|
315
|
+
if (meta.length > 0)
|
|
316
|
+
lines.push(theme.fg("muted", ` ${meta.join(" · ")}`));
|
|
317
|
+
const split = [data.input ? `in ${formatTokens(data.input)}` : "", data.output ? `out ${formatTokens(data.output)}` : "", data.cacheRead ? `cache ${formatTokens(data.cacheRead)}` : ""].filter(Boolean);
|
|
318
|
+
if (split.length > 0)
|
|
319
|
+
lines.push(theme.fg("muted", ` ${split.join(" · ")}`));
|
|
320
|
+
if (data.error !== undefined)
|
|
321
|
+
lines.push(theme.fg("error", ` ${sanitizeDisplayText(data.error)}`));
|
|
322
|
+
lines.push(theme.fg("muted", ` run ${data.id}`));
|
|
323
|
+
return lines;
|
|
324
|
+
}
|
|
325
|
+
export function createSubagentBackgroundWidget(host = {}) {
|
|
267
326
|
const key = "piewf-subagents-background";
|
|
268
327
|
const runs = new Map();
|
|
328
|
+
const receipted = new Set();
|
|
329
|
+
host.registerEntryRenderer?.(RECEIPT_ENTRY_TYPE, (entry, options, theme) => {
|
|
330
|
+
if (!entry.data)
|
|
331
|
+
return undefined;
|
|
332
|
+
const lines = renderSubagentReceipt(entry.data, options.expanded, theme);
|
|
333
|
+
// Pi treats an over-wide line as fatal, and an error message has no bound.
|
|
334
|
+
return { render: (width) => lines.map((line) => truncateToWidth(line, Math.max(1, width), "…")), invalidate() { } };
|
|
335
|
+
});
|
|
269
336
|
let context;
|
|
270
337
|
let requestRender;
|
|
271
338
|
let timer;
|
|
@@ -298,25 +365,14 @@ export function createSubagentBackgroundWidget() {
|
|
|
298
365
|
return {
|
|
299
366
|
render(width) {
|
|
300
367
|
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), "…"));
|
|
368
|
+
const paintRole = (role, text) => theme.fg(role, text);
|
|
369
|
+
// One row per run, so the default concurrency of eight fills the frame exactly.
|
|
370
|
+
// ponytail: no Alt+O scrolling as in the workflow widget; add it if runs routinely exceed the frame.
|
|
371
|
+
const budget = MAX_BACKGROUND_WIDGET_ROWS - 2;
|
|
372
|
+
const rows = [...runs.values()].map((run) => widgetRow(run, now, paintRole));
|
|
373
|
+
const body = rows.length <= budget ? rows : [...rows.slice(0, budget - 1), { text: theme.fg("muted", `… ${String(rows.length - budget + 1)} more`) }];
|
|
374
|
+
const title = runs.size === 1 ? "Subagent" : `Subagents · ${String(runs.size)} runs`;
|
|
375
|
+
return drawFrame(title, "", body, width, paintRole, (text) => theme.fg("accent", text)).map((line) => truncateToWidth(line, Math.max(1, width), ""));
|
|
320
376
|
},
|
|
321
377
|
invalidate() { },
|
|
322
378
|
};
|
|
@@ -333,13 +389,23 @@ export function createSubagentBackgroundWidget() {
|
|
|
333
389
|
start(next) {
|
|
334
390
|
hide();
|
|
335
391
|
runs.clear();
|
|
392
|
+
receipted.clear();
|
|
336
393
|
context = next.mode === "tui" ? next : undefined;
|
|
337
394
|
},
|
|
338
395
|
update(status, request) {
|
|
339
396
|
if (request.mode !== "background" || context === undefined)
|
|
340
397
|
return;
|
|
341
|
-
if (TERMINAL_STATES.has(status.state))
|
|
398
|
+
if (TERMINAL_STATES.has(status.state)) {
|
|
342
399
|
runs.delete(status.id);
|
|
400
|
+
// A foreground run already has its tool block; a background one leaves a receipt, once, like a workflow run.
|
|
401
|
+
if (!receipted.has(status.id)) {
|
|
402
|
+
receipted.add(status.id);
|
|
403
|
+
try {
|
|
404
|
+
host.appendEntry?.(RECEIPT_ENTRY_TYPE, receiptFor(status, request));
|
|
405
|
+
}
|
|
406
|
+
catch { /* A receipt is display-only. */ }
|
|
407
|
+
}
|
|
408
|
+
}
|
|
343
409
|
else
|
|
344
410
|
runs.set(status.id, { status, request });
|
|
345
411
|
paint();
|