@arhen/pi-core-subagent 1.2.0 → 1.2.2

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 (3) hide show
  1. package/README.md +10 -1
  2. package/package.json +1 -1
  3. package/src/index.ts +44 -1
package/README.md CHANGED
@@ -90,10 +90,19 @@ Chain — `{previous}` is replaced with the prior agent's output:
90
90
  }
91
91
  ```
92
92
 
93
+ The call line renders the graph in §2 notation as the model types it:
94
+
93
95
  ```
94
- wave 1: api ∥ db → gate → wave 2: doc
96
+ subagent graph 3
97
+ 6 at a time
98
+ wave1[api ∥ db] → gate → wave2[doc]
99
+ api api-mapper Map every route in src/api/
100
+ db db-mapper Map the schema in src/db/
101
+ doc writer ✎ ← api, db Write ARCHITECTURE.md from the maps above. Verify: test -s ARCHI…
95
102
  ```
96
103
 
104
+ `✎` marks a write-toolset task; `←` lists its edges. With no `needs` anywhere the wave line is omitted entirely.
105
+
97
106
  What the edge does:
98
107
 
99
108
  - **Gates** — `doc` starts only after both `api` and `db` finish.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arhen/pi-core-subagent",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "type": "module",
5
5
  "description": "pi extension: fast in-process subagents with a dependency-graph scheduler (needs edges gate tasks and carry upstream output into dependent prompts), plus background runs, intercom and agent-to-agent mailbox. Leader defines agents inline.",
6
6
  "license": "MIT",
package/src/index.ts CHANGED
@@ -472,6 +472,33 @@ export function resolveNeeds(inputs: { id?: string; needs?: string[] }[], mode:
472
472
  return edges;
473
473
  }
474
474
 
475
+ /**
476
+ * Graph Protocol §2 notation: `wave1[api ∥ db] → gate → wave2[doc]`.
477
+ *
478
+ * Tolerates half-streamed args: a need pointing at an id that has not arrived yet
479
+ * keeps its task out of the ready set, so the layout settles as the model types.
480
+ * Returns "" when there are no edges — flat fan-out gets no graph vocabulary.
481
+ */
482
+ export function waveNotation(tasks: { id?: string; needs?: string[] }[]): string {
483
+ if (!tasks.some((t) => t.needs?.length)) return "";
484
+ const ids = tasks.map((t, i) => t.id ?? `task_${i + 1}`);
485
+ const settled = new Set<string>();
486
+ let remaining = tasks.map((t, i) => ({ id: ids[i] as string, needs: t.needs ?? [] }));
487
+ const waves: string[][] = [];
488
+ while (remaining.length > 0) {
489
+ const ready = remaining.filter((t) => t.needs.every((n) => settled.has(n)));
490
+ if (ready.length === 0) break; // cycle, or an upstream id not typed yet
491
+ waves.push(ready.map((t) => t.id));
492
+ for (const t of ready) settled.add(t.id);
493
+ remaining = remaining.filter((t) => !settled.has(t.id));
494
+ }
495
+ if (remaining.length > 0) waves.push(remaining.map((t) => t.id)); // show them rather than drop them
496
+ if (waves.length < 2) return "";
497
+ const full = waves.map((w, i) => `wave${i + 1}[${w.join(" ∥ ")}]`).join(" → gate → ");
498
+ // Long graphs: keep the shape, drop the names.
499
+ return full.length <= 100 ? full : waves.map((w, i) => `wave${i + 1}[${w.length}]`).join(" → gate → ");
500
+ }
501
+
475
502
  /**
476
503
  * Graph Protocol §6: the edge carries the upstream output, not just ordering.
477
504
  * Upstream results are prepended verbatim; `{previous}` stays supported so old
@@ -1402,7 +1429,23 @@ export default function (pi: ExtensionAPI) {
1402
1429
  if (args.concurrency) parts.push(`${args.concurrency} at a time`);
1403
1430
  if (args.maxRuntimeMs) parts.push(`${Math.round(args.maxRuntimeMs / 60000)}m limit`);
1404
1431
  const params = parts.length > 0 ? `\n ${theme.fg("dim", parts.join(" · "))}` : "";
1405
- return new Text(`${theme.fg("toolTitle", theme.bold("subagent"))} ${theme.fg("accent", mode)}${flags ? ` ${theme.fg("muted", `[${flags}]`)}` : ""}${params}`, 0, 0);
1432
+ const notation = waveNotation(tasks);
1433
+ const graphLine = notation ? `\n ${theme.fg("muted", notation)}` : "";
1434
+ // The plan the model actually wrote: ids, edges, toolset. Streams in as args arrive,
1435
+ // so a graph is visible before the first child spawns.
1436
+ const plan = tasks
1437
+ .filter((t: any) => t.agent || t.id)
1438
+ .map((t: any, i: number) => {
1439
+ const id = t.id ?? `task_${i + 1}`;
1440
+ const edge = t.needs?.length ? theme.fg("muted", ` ← ${t.needs.join(", ")}`) : "";
1441
+ const mark = t.write ? theme.fg("warning", " ✎") : "";
1442
+ // Plain clip, not truncateText — that one appends a multi-line session-file notice.
1443
+ const flat = String(t.task ?? "").replace(/\s+/g, " ").trim();
1444
+ const what = flat ? theme.fg("dim", ` ${flat.length > 64 ? `${flat.slice(0, 64)}…` : flat}`) : "";
1445
+ return `\n ${theme.fg("muted", id)} ${theme.fg("accent", t.agent ?? "…")}${mark}${edge}${what}`;
1446
+ })
1447
+ .join("");
1448
+ return new Text(`${theme.fg("toolTitle", theme.bold("subagent"))} ${theme.fg("accent", mode)}${flags ? ` ${theme.fg("muted", `[${flags}]`)}` : ""}${params}${graphLine}${plan}`, 0, 0);
1406
1449
  },
1407
1450
  renderResult(result, { expanded }, theme) {
1408
1451
  const run = result.details?.run;