@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.
Files changed (71) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/dist/src/agent-execution.js +36 -51
  3. package/dist/src/background-widget.d.ts +56 -0
  4. package/dist/src/background-widget.js +22 -15
  5. package/dist/src/decoders.js +2 -1
  6. package/dist/src/host-navigator.d.ts +18 -0
  7. package/dist/src/host-navigator.js +71 -13
  8. package/dist/src/host-view.d.ts +16 -1
  9. package/dist/src/host-view.js +109 -42
  10. package/dist/src/host.js +57 -24
  11. package/dist/src/index.d.ts +3 -1
  12. package/dist/src/index.js +867 -545
  13. package/dist/src/index.js.map +3 -3
  14. package/dist/src/registry.js +2 -1
  15. package/dist/src/roles.d.ts +70 -0
  16. package/dist/src/roles.js +325 -0
  17. package/dist/src/settings.d.ts +13 -0
  18. package/dist/src/settings.js +253 -0
  19. package/dist/src/trajectory.d.ts +29 -4
  20. package/dist/src/trajectory.js +58 -3
  21. package/dist/src/types.d.ts +0 -1
  22. package/dist/src/utils.d.ts +2 -1
  23. package/dist/src/utils.js +10 -0
  24. package/dist/src/validation.d.ts +5 -17
  25. package/dist/src/validation.js +9 -458
  26. package/dist/subagents/index.js +1132 -694
  27. package/dist/subagents/index.js.map +3 -3
  28. package/dist/subagents/src/contracts.d.ts +4 -0
  29. package/dist/subagents/src/index.d.ts +1 -1
  30. package/dist/subagents/src/index.js +2 -1
  31. package/dist/subagents/src/manager.js +29 -10
  32. package/dist/subagents/src/navigator.js +396 -247
  33. package/dist/subagents/src/view.d.ts +23 -2
  34. package/dist/subagents/src/view.js +96 -30
  35. package/dist/trajectory/assets/index.html +347 -231
  36. package/dist/trajectory/assets/prism.min.js +13 -0
  37. package/dist/trajectory/index.js +377 -257
  38. package/dist/trajectory/index.js.map +3 -3
  39. package/dist/trajectory/src/assets/index.html +347 -231
  40. package/dist/trajectory/src/assets/prism.min.js +13 -0
  41. package/dist/trajectory/src/export.js +4 -2
  42. package/dist/trajectory/src/index.d.ts +2 -0
  43. package/dist/trajectory/src/index.js +143 -42
  44. package/dist/trajectory/src/server.js +134 -9
  45. package/dist/trajectory/src/server.js.map +1 -1
  46. package/package.json +2 -1
  47. package/src/agent-execution.ts +36 -42
  48. package/src/background-widget.ts +25 -18
  49. package/src/decoders.ts +2 -1
  50. package/src/host-navigator.ts +58 -14
  51. package/src/host-view.ts +93 -37
  52. package/src/host.ts +51 -21
  53. package/src/index.ts +3 -1
  54. package/src/registry.ts +2 -1
  55. package/src/roles.ts +326 -0
  56. package/src/settings.ts +206 -0
  57. package/src/trajectory.ts +76 -7
  58. package/src/types.ts +1 -1
  59. package/src/utils.ts +8 -1
  60. package/src/validation.ts +12 -356
  61. package/subagents/README.md +1 -1
  62. package/subagents/src/contracts.ts +4 -0
  63. package/subagents/src/index.ts +3 -2
  64. package/subagents/src/manager.ts +28 -10
  65. package/subagents/src/navigator.ts +336 -180
  66. package/subagents/src/view.ts +97 -28
  67. package/trajectory/src/assets/index.html +347 -231
  68. package/trajectory/src/assets/prism.min.js +13 -0
  69. package/trajectory/src/export.ts +4 -2
  70. package/trajectory/src/index.ts +118 -38
  71. 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
- export declare function createSubagentBackgroundWidget(): {
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
- function accounting(status) {
107
+ /** Tokens and cost as the workflow header shows them. */
108
+ function usageStats(status) {
107
109
  const value = status.progress?.accounting;
108
- if (!value)
109
- return undefined;
110
- const total = value.input + value.output + value.cacheRead + value.cacheWrite;
111
- return `tokens=${String(total)} cost=${formatCost(value.cost) || "$0.00"}`;
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
- export function createSubagentBackgroundWidget() {
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 frame = SPINNER[Math.floor(now / 80) % SPINNER.length] ?? "◇";
302
- const title = theme.bold(theme.fg("accent", `Subagents (${String(runs.size)} running)`));
303
- const blocks = [...runs.values()].map(({ status, request }) => formatSubagentProgress(status, request, theme, frame, now, false).split("\n"));
304
- const allRunsFit = blocks.reduce((rows, block) => rows + block.length, 1) <= MAX_BACKGROUND_WIDGET_ROWS;
305
- const runRowBudget = allRunsFit ? MAX_BACKGROUND_WIDGET_ROWS - 1 : MAX_BACKGROUND_WIDGET_ROWS - 2;
306
- const lines = [title];
307
- let runRows = 0;
308
- let displayedRuns = 0;
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();