@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
package/subagents/src/view.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
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 { truncateToWidth } from "@earendil-works/pi-tui";
|
|
3
3
|
import { formatCost, formatStalledDuration, sanitizeDisplayText, WORKFLOW_AGENT_STALL_THRESHOLD_MS } from "../../src/index.js";
|
|
4
|
+
import { drawFrame, formatElapsed, formatTokens, mark, QUIET_MS, type Paint } from "../../src/background-widget.js";
|
|
4
5
|
import type { SubagentRunRequest, SubagentStatus } from "./contracts.js";
|
|
5
6
|
|
|
6
7
|
const SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] as const;
|
|
@@ -113,11 +114,14 @@ function stalledDuration(status: SubagentStatus, now: number): number | undefine
|
|
|
113
114
|
return duration >= WORKFLOW_AGENT_STALL_THRESHOLD_MS ? duration : undefined;
|
|
114
115
|
}
|
|
115
116
|
|
|
116
|
-
|
|
117
|
+
/** Tokens and cost as the workflow header shows them. */
|
|
118
|
+
function usageStats(status: SubagentStatus): string {
|
|
117
119
|
const value = status.progress?.accounting;
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
120
|
+
return value ? [formatTokens(value.input + value.output), formatCost(value.cost)].filter(Boolean).join(" · ") : "";
|
|
121
|
+
}
|
|
122
|
+
function modelName(status: SubagentStatus): string {
|
|
123
|
+
const model = status.progress?.state?.model ?? status.attemptDetails?.at(-1)?.setup.model;
|
|
124
|
+
return model ? `${model.model}${model.thinking ? `:${model.thinking}` : ""}` : "";
|
|
121
125
|
}
|
|
122
126
|
|
|
123
127
|
function timestamp(value: number | undefined): string | undefined {
|
|
@@ -141,8 +145,9 @@ function formatSubagentProgress(status: SubagentStatus, args: SubagentRenderArgs
|
|
|
141
145
|
const color = stateColor(status.state);
|
|
142
146
|
const elapsed = runtime(status.startedAt, status.finishedAt, now);
|
|
143
147
|
const metadata = requestMetadata(args, args.id === undefined);
|
|
148
|
+
const stats = usageStats(status);
|
|
144
149
|
const lines = [
|
|
145
|
-
`${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}` : ""}`,
|
|
150
|
+
`${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}` : ""}`,
|
|
146
151
|
];
|
|
147
152
|
const current = activity(status);
|
|
148
153
|
const stalled = stalledDuration(status, now);
|
|
@@ -153,8 +158,6 @@ function formatSubagentProgress(status: SubagentStatus, args: SubagentRenderArgs
|
|
|
153
158
|
lines.push(` ${theme.fg("dim", `id=${status.id}`)}`);
|
|
154
159
|
const model = status.progress?.state?.model;
|
|
155
160
|
if (model) lines.push(` ${theme.fg("dim", `model=${model.provider}/${model.model}${model.thinking ? `:${model.thinking}` : ""}`)}`);
|
|
156
|
-
const usage = accounting(status);
|
|
157
|
-
if (usage) lines.push(` ${theme.fg("dim", usage)}`);
|
|
158
161
|
if (status.worktree) lines.push(` ${theme.fg("dim", `worktree=${status.worktree.path} branch=${status.worktree.branch}`)}`);
|
|
159
162
|
}
|
|
160
163
|
return lines.join("\n");
|
|
@@ -257,9 +260,77 @@ export function renderSubagentResult(result: AgentToolResult<unknown>, options:
|
|
|
257
260
|
|
|
258
261
|
type WidgetRun = { status: Readonly<SubagentStatus>; request: Readonly<SubagentRunRequest> };
|
|
259
262
|
|
|
260
|
-
|
|
263
|
+
/** One run as a workflow agent row: a quiet or stalled warning on the left, model and figures against the right rule. */
|
|
264
|
+
function widgetRow({ status, request }: WidgetRun, now: number, paint: Paint): { text: string; right: string } {
|
|
265
|
+
const lastEventAt = status.progress?.lastEventAt;
|
|
266
|
+
const silent = lastEventAt === undefined || !Number.isFinite(lastEventAt) || now - lastEventAt < QUIET_MS ? undefined : now - lastEventAt;
|
|
267
|
+
const role = silent !== undefined && silent >= WORKFLOW_AGENT_STALL_THRESHOLD_MS ? "error" : "warning";
|
|
268
|
+
const icon = silent === undefined ? mark(status.state, now, paint) : paint(role, "⚠");
|
|
269
|
+
const warning = silent === undefined ? "" : ` ${paint(role, `${role === "error" ? "stalled" : "quiet"} ${formatElapsed(silent)}`)}`;
|
|
270
|
+
const accounting = status.progress?.accounting;
|
|
271
|
+
const attempts = status.attempts ?? 1;
|
|
272
|
+
const right = [
|
|
273
|
+
modelName(status),
|
|
274
|
+
formatTokens(accounting === undefined ? undefined : accounting.input + accounting.output),
|
|
275
|
+
formatCost(accounting?.cost),
|
|
276
|
+
status.startedAt === undefined ? "" : formatElapsed(now - status.startedAt),
|
|
277
|
+
attempts > 1 ? `attempt ${String(attempts)}` : "",
|
|
278
|
+
].filter(Boolean).join(" · ");
|
|
279
|
+
return { text: `${icon} ${sanitizeDisplayText(label({ ...request, id: status.id }))}${warning}`, right };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const RECEIPT_ENTRY_TYPE = "piewf-subagent-receipt";
|
|
283
|
+
type SubagentReceipt = { readonly id: string; readonly label: string; readonly state: string; readonly model?: string; readonly role?: string; readonly tools?: readonly string[]; readonly input: number; readonly output: number; readonly cacheRead: number; readonly costUsd: number; readonly durationMs: number; readonly attempts: number; readonly error?: string };
|
|
284
|
+
|
|
285
|
+
function receiptFor(status: Readonly<SubagentStatus>, request: Readonly<SubagentRunRequest>): SubagentReceipt {
|
|
286
|
+
const accounting = status.progress?.accounting;
|
|
287
|
+
const model = modelName(status);
|
|
288
|
+
const role = roleName(request.role);
|
|
289
|
+
const tools = status.progress?.state?.tools ?? status.attemptDetails?.at(-1)?.setup.tools;
|
|
290
|
+
return {
|
|
291
|
+
id: status.id,
|
|
292
|
+
label: label({ ...request, id: status.id }),
|
|
293
|
+
state: status.state,
|
|
294
|
+
...(model ? { model } : {}),
|
|
295
|
+
...(role === undefined ? {} : { role }),
|
|
296
|
+
...(tools === undefined ? {} : { tools: [...tools] }),
|
|
297
|
+
input: accounting?.input ?? 0,
|
|
298
|
+
output: accounting?.output ?? 0,
|
|
299
|
+
cacheRead: accounting?.cacheRead ?? 0,
|
|
300
|
+
costUsd: accounting?.cost ?? 0,
|
|
301
|
+
durationMs: status.startedAt === undefined ? 0 : Math.max(0, (status.finishedAt ?? Date.now()) - status.startedAt),
|
|
302
|
+
attempts: status.attempts ?? 1,
|
|
303
|
+
...(status.error === undefined ? {} : { error: `${status.error.code}: ${status.error.message}` }),
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** The transcript line a finished background run leaves behind, in the shape of the workflow receipt. */
|
|
308
|
+
export function renderSubagentReceipt(data: SubagentReceipt, expanded: boolean, theme: Theme): string[] {
|
|
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) return lines;
|
|
313
|
+
const meta = [data.model ?? "", data.role ? `role ${data.role}` : "", data.attempts > 1 ? `${String(data.attempts)} attempts` : "", data.tools?.join(" ") ?? ""].filter(Boolean);
|
|
314
|
+
if (meta.length > 0) lines.push(theme.fg("muted", ` ${meta.join(" · ")}`));
|
|
315
|
+
const split = [data.input ? `in ${formatTokens(data.input)}` : "", data.output ? `out ${formatTokens(data.output)}` : "", data.cacheRead ? `cache ${formatTokens(data.cacheRead)}` : ""].filter(Boolean);
|
|
316
|
+
if (split.length > 0) lines.push(theme.fg("muted", ` ${split.join(" · ")}`));
|
|
317
|
+
if (data.error !== undefined) lines.push(theme.fg("error", ` ${sanitizeDisplayText(data.error)}`));
|
|
318
|
+
lines.push(theme.fg("muted", ` run ${data.id}`));
|
|
319
|
+
return lines;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
type ReceiptHost = { appendEntry?: (customType: string, data: SubagentReceipt) => void; registerEntryRenderer?: ExtensionAPI["registerEntryRenderer"] };
|
|
323
|
+
|
|
324
|
+
export function createSubagentBackgroundWidget(host: ReceiptHost = {}) {
|
|
261
325
|
const key = "piewf-subagents-background";
|
|
262
326
|
const runs = new Map<string, WidgetRun>();
|
|
327
|
+
const receipted = new Set<string>();
|
|
328
|
+
host.registerEntryRenderer?.<SubagentReceipt>(RECEIPT_ENTRY_TYPE, (entry, options, theme) => {
|
|
329
|
+
if (!entry.data) return undefined;
|
|
330
|
+
const lines = renderSubagentReceipt(entry.data, options.expanded, theme);
|
|
331
|
+
// Pi treats an over-wide line as fatal, and an error message has no bound.
|
|
332
|
+
return { render: (width: number): string[] => lines.map((line) => truncateToWidth(line, Math.max(1, width), "…")), invalidate() {} };
|
|
333
|
+
});
|
|
263
334
|
let context: ExtensionContext | undefined;
|
|
264
335
|
let requestRender: (() => void) | undefined;
|
|
265
336
|
let timer: ReturnType<typeof setInterval> | undefined;
|
|
@@ -289,23 +360,14 @@ export function createSubagentBackgroundWidget() {
|
|
|
289
360
|
return {
|
|
290
361
|
render(width: number): string[] {
|
|
291
362
|
const now = Date.now();
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
const
|
|
296
|
-
const
|
|
297
|
-
const
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
for (const block of blocks) {
|
|
301
|
-
if (runRows + block.length > runRowBudget) break;
|
|
302
|
-
lines.push(...block);
|
|
303
|
-
runRows += block.length;
|
|
304
|
-
displayedRuns += 1;
|
|
305
|
-
}
|
|
306
|
-
const hiddenRuns = blocks.length - displayedRuns;
|
|
307
|
-
if (hiddenRuns > 0) lines.push(`… ${String(hiddenRuns)} more`);
|
|
308
|
-
return lines.map((line) => truncateToWidth(line, Math.max(1, width), "…"));
|
|
363
|
+
const paintRole: Paint = (role, text) => theme.fg(role, text);
|
|
364
|
+
// One row per run, so the default concurrency of eight fills the frame exactly.
|
|
365
|
+
// ponytail: no Alt+O scrolling as in the workflow widget; add it if runs routinely exceed the frame.
|
|
366
|
+
const budget = MAX_BACKGROUND_WIDGET_ROWS - 2;
|
|
367
|
+
const rows = [...runs.values()].map((run) => widgetRow(run, now, paintRole));
|
|
368
|
+
const body = rows.length <= budget ? rows : [...rows.slice(0, budget - 1), { text: theme.fg("muted", `… ${String(rows.length - budget + 1)} more`) }];
|
|
369
|
+
const title = runs.size === 1 ? "Subagent" : `Subagents · ${String(runs.size)} runs`;
|
|
370
|
+
return drawFrame(title, "", body, width, paintRole, (text) => theme.fg("accent", text)).map((line) => truncateToWidth(line, Math.max(1, width), ""));
|
|
309
371
|
},
|
|
310
372
|
invalidate() {},
|
|
311
373
|
};
|
|
@@ -322,12 +384,19 @@ export function createSubagentBackgroundWidget() {
|
|
|
322
384
|
start(next: ExtensionContext): void {
|
|
323
385
|
hide();
|
|
324
386
|
runs.clear();
|
|
387
|
+
receipted.clear();
|
|
325
388
|
context = next.mode === "tui" ? next : undefined;
|
|
326
389
|
},
|
|
327
390
|
update(status: Readonly<SubagentStatus>, request: Readonly<SubagentRunRequest>): void {
|
|
328
391
|
if (request.mode !== "background" || context === undefined) return;
|
|
329
|
-
if (TERMINAL_STATES.has(status.state))
|
|
330
|
-
|
|
392
|
+
if (TERMINAL_STATES.has(status.state)) {
|
|
393
|
+
runs.delete(status.id);
|
|
394
|
+
// A foreground run already has its tool block; a background one leaves a receipt, once, like a workflow run.
|
|
395
|
+
if (!receipted.has(status.id)) {
|
|
396
|
+
receipted.add(status.id);
|
|
397
|
+
try { host.appendEntry?.(RECEIPT_ENTRY_TYPE, receiptFor(status, request)); } catch { /* A receipt is display-only. */ }
|
|
398
|
+
}
|
|
399
|
+
} else runs.set(status.id, { status, request });
|
|
331
400
|
paint();
|
|
332
401
|
},
|
|
333
402
|
dispose(): void {
|