@ferris1225/pi-subagents 0.22.0 → 0.24.0

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 +7 -15
  2. package/package.json +1 -1
  3. package/src/index.ts +68 -49
package/README.md CHANGED
@@ -459,25 +459,17 @@ The package has no runtime dependencies beyond pi peer dependencies.
459
459
  ## Acknowledgments
460
460
 
461
461
  - The official [pi subagent example](https://github.com/earendil-works/pi)
462
- (`examples/extensions/subagent`) — this extension's child-process dispatch and
463
- event-stream handling are adapted from it.
464
- - [nicobailon/pi-subagents](https://github.com/nicobailon/pi-subagents) — the most
465
- widely used pi sub-agent extension; its async delegation model and result
466
- truncation/artifact handling directly informed this project.
467
- - [tintinweb/pi-subagents](https://github.com/tintinweb/pi-subagents) — Claude Code-
468
- style sub-agents for pi with parallel execution and a live widget; this project's
469
- widget and parallel fan-out follow the same ideas.
470
- - [amosblomqvist/pi-subagents](https://github.com/amosblomqvist/pi-subagents) — a
471
- clean, minimal reference for markdown-defined agents in pi.
472
- - [edxeth/pi-subagents](https://github.com/edxeth/pi-subagents) — multi-agent
473
- coordination patterns (background agents, child-to-parent messaging) that are
474
- worth borrowing from.
462
+ (`examples/extensions/subagent`) — the child-process dispatch and
463
+ event-stream handling build on it.
464
+ - [tintinweb/pi-subagents](https://github.com/tintinweb/pi-subagents) — the
465
+ live widget (two lines per run: header + quiet gray activity row) and
466
+ parallel fan-out follow its design.
475
467
  - The sub-agent pattern itself, popularized by
476
468
  [Claude Code](https://github.com/anthropics/claude-code): role-specialized
477
469
  agents that receive self-contained briefs.
478
470
 
479
- The agent prompts and extension code are written independently for this project;
480
- the projects above served as design references.
471
+ The agent prompts and extension code are written independently for this
472
+ project; the projects above served as design references.
481
473
 
482
474
  ## License
483
475
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ferris1225/pi-subagents",
3
- "version": "0.22.0",
3
+ "version": "0.24.0",
4
4
  "description": "Focused sub-agent delegation for pi: explore / worker / reviewer agents in isolated context, with proactive dispatch injection and per-agent model selection.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/index.ts CHANGED
@@ -13,7 +13,7 @@
13
13
  */
14
14
 
15
15
  import { getAgentDir, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
16
- import { Text } from "@earendil-works/pi-tui";
16
+ import { Text, truncateToWidth } from "@earendil-works/pi-tui";
17
17
  import { Type } from "typebox";
18
18
  import { discoverAgents, type AgentConfig } from "./agents.ts";
19
19
  import { BackgroundTaskQueue } from "./background.ts";
@@ -52,7 +52,6 @@ import {
52
52
  formatToolActivity,
53
53
  formatUsageCompact,
54
54
  monitor,
55
- rightAlign,
56
55
  statusIcon,
57
56
  statusLabel,
58
57
  type RunChainMeta,
@@ -776,54 +775,74 @@ export default function (pi: ExtensionAPI): void {
776
775
  }
777
776
  }, 1000);
778
777
  return {
779
- render(width: number): string[] {
780
- const runs = monitor.getRuns();
781
- if (runs.length === 0) return [];
782
- const now = Date.now();
783
- const lines: string[] = [];
784
- for (const r of runs) {
785
- const icon = statusIcon(r.status, theme);
786
- // Chain-internal runs (auto-fix worker/reviewer) indent under their parent
787
- // reviewer. For those, the relationLabel ("fix round 1") is more
788
- // distinguishing than the repeated worker/reviewer name.
789
- const head = r.groupId ? theme.fg("dim", " ↳ ") : " ";
790
- const name = r.groupId ? (r.relationLabel ?? r.agent) : r.agent;
791
- // Header row stays short: icon, run id, agent name — the task summary
792
- // (keys-only fragments: paths, symbols, quoted phrases) gets its own
793
- // line below with a full width budget, so parallel runs of the SAME
794
- // agent are still distinguishable at a glance.
795
- const title = formatTaskSummary(r.task, Math.max(16, Math.floor(width * 0.65)), true);
796
- const left = `${head}${icon} ${theme.fg("dim", `#${r.id}`)} ${theme.bold(name)}`;
797
-
798
- // Right side: full model ref (provider/model), token usage (in/out +
799
- // cache read/write), tool count, elapsed, and the soft activity-state
800
- // annotation (idle / long-running). Trailing the header with a single
801
- // " · " chain keeps the row compact (no center gap); compactLine
802
- // clips on overflow, never the right side on its own.
803
- const model = r.model ?? "?";
804
- const usage = formatUsageCompact(r.usage);
805
- const tools = r.toolCount ? `${r.toolCount} tool${r.toolCount === 1 ? "" : "s"}` : "";
806
- const elapsed = formatElapsed(r, now);
807
- const metaParts = [model, usage, tools, elapsed].filter(Boolean);
808
- // Running is conveyed by the icon + elapsed; spell out the label only for
809
- // the other states (ready / done / stopped) so they are unambiguous.
810
- if (r.status !== "running") metaParts.push(statusLabel(r.status));
811
- const state = deriveActivityState(r, now);
812
- if (state) metaParts.push(activityStateLabel(state));
813
- if (r.annotation) metaParts.push(r.annotation);
814
- const right = metaParts.length ? theme.fg("dim", ` · ${metaParts.join(" · ")}`) : "";
815
- lines.push(compactLine(left, right, width));
816
-
817
- // Task summary sits one indent below the header, on its own line.
818
- lines.push(rightAlign(`${head} ${theme.fg("accent", title)}`, "", width));
819
-
820
- // Current activity sits one indent below, only while the run is active.
821
- if (r.activity && (r.status === "running" || r.status === "queued")) {
822
- lines.push(rightAlign(`${head} ${theme.fg("dim", r.activity)}`, "", width));
778
+ render(width: number): string[] {
779
+ const runs = monitor.getRuns();
780
+ if (runs.length === 0) return [];
781
+ const now = Date.now();
782
+ const lines: string[] = [];
783
+ // Tree layout: each top-level agent is a root whose title/activity hang
784
+ // off it as branches; auto-fix chain runs (groupId) become child nodes
785
+ // under their parent root, with a "│" continuation while more siblings
786
+ // follow. Blank lines separate agent blocks so parallel runs don't blur
787
+ // into one wall of text.
788
+ const dim = (t: string): string => theme.fg("dim", t);
789
+ for (let idx = 0; idx < runs.length; idx++) {
790
+ const r = runs[idx];
791
+ const isChain = Boolean(r.groupId);
792
+ const chainContinues = isChain && runs[idx + 1]?.groupId === r.groupId;
793
+ const activity =
794
+ r.activity && (r.status === "running" || r.status === "queued") ? r.activity : undefined;
795
+ const hasActivity = activity !== undefined;
796
+ const icon = statusIcon(r.status, theme);
797
+ // Chain-internal runs (auto-fix worker/reviewer) are child nodes under
798
+ // their parent reviewer. Their relationLabel ("fix round 1") is more
799
+ // distinguishing than the repeated worker/reviewer name.
800
+ const name = isChain ? (r.relationLabel ?? r.agent) : r.agent;
801
+ // Two lines per run: the header row (icon, run id, agent name) and the
802
+ // live activity branch below. The task summary is deliberately not
803
+ // shown the task lives in the tool result, and the agent name plus
804
+ // what it is doing right now is enough to tell runs apart. The header
805
+ // stays exactly as it was (accent name, dim stats), matching the
806
+ // referenced sub-agent widgets (tintinweb): the running indicator
807
+ // uses the accent color, everything else is quiet.
808
+ if (!isChain && lines.length > 0) lines.push("");
809
+ const nodeBranch = isChain ? (chainContinues ? "├─ " : "└─ ") : "";
810
+ const left = `${dim(nodeBranch)}${icon} ${dim(`#${r.id}`)} ${isChain ? name : theme.fg("accent", theme.bold(name))}`;
811
+
812
+ // Right side: full model ref (provider/model), token usage (in/out +
813
+ // cache read/write), tool count, elapsed, and the soft activity-state
814
+ // annotation (idle / long-running). Trailing the header with a single
815
+ // " · " chain keeps the row compact (no center gap); compactLine
816
+ // clips on overflow, never the right side on its own.
817
+ const model = r.model ?? "?";
818
+ const usage = formatUsageCompact(r.usage);
819
+ const tools = r.toolCount ? `${r.toolCount} tool${r.toolCount === 1 ? "" : "s"}` : "";
820
+ const elapsed = formatElapsed(r, now);
821
+ const metaParts = [model, usage, tools, elapsed].filter(Boolean);
822
+ // Running is conveyed by the icon + elapsed; spell out the label only for
823
+ // the other states (ready / done / stopped) so they are unambiguous.
824
+ if (r.status !== "running") metaParts.push(statusLabel(r.status));
825
+ const state = deriveActivityState(r, now);
826
+ if (state) metaParts.push(activityStateLabel(state));
827
+ if (r.annotation) metaParts.push(r.annotation);
828
+ // Metadata trails the header in dim — quiet, never competing with the
829
+ // accent agent name (the same restraint the referenced widgets use).
830
+ // Trailing with a single " · " chain keeps the row compact (no center
831
+ // gap); compactLine clips on overflow, never the right side on its own.
832
+ const right = metaParts.length ? dim(` · ${metaParts.join(" · ")}`) : "";
833
+ lines.push(compactLine(left, right, width));
834
+
835
+ // Current activity ("read src/index.ts", "bash npm test") is the only
836
+ // branch: gray, so it never competes with the agent name or pi's own
837
+ // UI. Chain nodes that still have siblings carry a "│" continuation
838
+ // down to the last one.
839
+ if (hasActivity) {
840
+ const continuation = isChain ? (chainContinues ? "│ " : " ") : "";
841
+ lines.push(truncateToWidth(`${continuation}${dim("└─ ")}${dim(activity)}`, width));
842
+ }
823
843
  }
824
- }
825
- return lines;
826
- },
844
+ return lines;
845
+ },
827
846
  invalidate() {},
828
847
  dispose() {
829
848
  unsub();