@arhen/pi-core-subagent 1.0.4 → 1.0.6

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.ts +47 -36
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arhen/pi-core-subagent",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "type": "module",
5
5
  "description": "pi extension: fast in-process subagents with single/parallel/chain, background runs, intercom and agent-to-agent mailbox. Leader defines agents inline.",
6
6
  "license": "MIT",
package/src/index.ts CHANGED
@@ -253,7 +253,7 @@ function compactLines(run: RunSnapshot): string[] {
253
253
  */
254
254
  class SubagentsWidget implements Component {
255
255
  constructor(
256
- private readonly getRun: () => RunSnapshot | undefined,
256
+ private readonly getRuns: () => RunSnapshot[],
257
257
  private readonly theme: Theme,
258
258
  ) {}
259
259
 
@@ -262,28 +262,33 @@ class SubagentsWidget implements Component {
262
262
  }
263
263
 
264
264
  render(width: number): string[] {
265
- const run = this.getRun();
266
- if (!run || run.tasks.length === 0) return [];
267
- const done = run.tasks.filter((t) => TERMINAL.includes(t.status)).length;
268
- const active = !TERMINAL.includes(run.status);
269
- const head = active ? "accent" : "dim";
270
- const lines = [truncateToWidth(`${this.theme.fg(head, active ? "●" : "○")} ${this.theme.fg(head, `Subagents (${done}/${run.tasks.length})`)}`, width, "")];
271
- const allDone = TERMINAL.includes(run.status);
272
- const visible = run.tasks.slice(0, MAX_TASKS);
273
- visible.forEach((task, i) => {
274
- const last = i === visible.length - 1 && run.tasks.length <= MAX_TASKS;
275
- const conn = this.theme.fg("dim", last ? "└─" : "├─");
276
- const activity =
277
- !TERMINAL.includes(task.status) && task.lastActivity
278
- ? `${this.theme.fg("dim", `→ ${task.lastActivity}`)} · `
279
- : "";
280
- // Completed run: dim everything except the agent name.
281
- const line = allDone
282
- ? `${this.theme.fg("dim", `${statusIcon(task.status)} `)}${task.agent} ${this.theme.fg("dim", `· ${taskStatsWithUsage(task)} · ${taskTimer(task)}`)}`
283
- : `${statusIcon(task.status)} ${task.agent} · ${activity}${taskStatsWithUsage(task)} · ${taskTimer(task)}`;
284
- lines.push(truncateToWidth(`${conn} ${line}`, width, "…"));
265
+ const runs = this.getRuns();
266
+ if (runs.length === 0) return [];
267
+ const lines: string[] = [];
268
+ runs.forEach((run, ri) => {
269
+ if (run.tasks.length === 0) return;
270
+ if (ri > 0) lines.push("");
271
+ const done = run.tasks.filter((t) => TERMINAL.includes(t.status)).length;
272
+ const active = !TERMINAL.includes(run.status);
273
+ const head = active ? "accent" : "dim";
274
+ lines.push(truncateToWidth(`${this.theme.fg(head, active ? "●" : "○")} ${this.theme.fg(head, `Subagents (${done}/${run.tasks.length})`)}`, width, "…"));
275
+ const allDone = TERMINAL.includes(run.status);
276
+ const visible = run.tasks.slice(0, MAX_TASKS);
277
+ visible.forEach((task, i) => {
278
+ const last = i === visible.length - 1 && run.tasks.length <= MAX_TASKS;
279
+ const conn = this.theme.fg("dim", last ? "└─" : "├─");
280
+ const activity =
281
+ !TERMINAL.includes(task.status) && task.lastActivity
282
+ ? `${this.theme.fg("dim", `→ ${task.lastActivity}`)} · `
283
+ : "";
284
+ // Finished run: dim everything except the agent name.
285
+ const line = allDone
286
+ ? `${this.theme.fg("dim", `${statusIcon(task.status)} `)}${task.agent} ${this.theme.fg("dim", `· ${taskStatsWithUsage(task)} · ${taskTimer(task)}`)}`
287
+ : `${statusIcon(task.status)} ${task.agent} · ${activity}${taskStatsWithUsage(task)} · ${taskTimer(task)}`;
288
+ lines.push(truncateToWidth(`${conn} ${line}`, width, "…"));
289
+ });
290
+ if (run.tasks.length > MAX_TASKS) lines.push(`${this.theme.fg("dim", "└─")} ${this.theme.fg("dim", `+${run.tasks.length - MAX_TASKS} more`)}`);
285
291
  });
286
- if (run.tasks.length > MAX_TASKS) lines.push(`${this.theme.fg("dim", "└─")} ${this.theme.fg("dim", `+${run.tasks.length - MAX_TASKS} more`)}`);
287
292
  return lines;
288
293
  }
289
294
  }
@@ -383,7 +388,7 @@ class SubagentManager {
383
388
  private mailboxes: Mailbox = createMailbox();
384
389
  private runControllers = new Map<string, AbortController>();
385
390
  private widgetTimer: ReturnType<typeof setTimeout> | undefined;
386
- private widgetRun: RunSnapshot | undefined;
391
+ private widgetRuns: RunSnapshot[] = [];
387
392
 
388
393
  turnActivity = false;
389
394
 
@@ -399,7 +404,7 @@ class SubagentManager {
399
404
 
400
405
  /** Hide the widget. */
401
406
  clearWidget(ctx: ExtensionContext): void {
402
- this.widgetRun = undefined;
407
+ this.widgetRuns = [];
403
408
  this.widgetTui = null;
404
409
  if (ctx.hasUI) {
405
410
  try {
@@ -425,7 +430,7 @@ class SubagentManager {
425
430
  this.widgetTui = null; // force re-registration on the next session
426
431
  if (this.widgetTimer) clearTimeout(this.widgetTimer);
427
432
  this.widgetTimer = undefined;
428
- this.widgetRun = undefined;
433
+ this.widgetRuns = [];
429
434
  }
430
435
 
431
436
  // ── persistence (sidecar per parent session) ────────────────────────
@@ -508,19 +513,27 @@ class SubagentManager {
508
513
  // The component self-animates the spinner via its own 100ms interval;
509
514
  // scheduleWidget just throttles status changes into requestRender calls.
510
515
  private widgetTui: TUI | null = null;
516
+ /** Upsert a run into the widget's visible set (all runs, not just the latest). */
517
+ private upsertWidgetRun(run: RunSnapshot | undefined): void {
518
+ if (!run) return;
519
+ const idx = this.widgetRuns.findIndex((r) => r.id === run.id);
520
+ if (idx >= 0) this.widgetRuns[idx] = run;
521
+ else this.widgetRuns.push(run);
522
+ }
511
523
  private scheduleWidget(run: RunSnapshot | undefined, ctx?: ExtensionContext, onUpdate?: (partial: any) => void): void {
512
- if (run) this.widgetRun = run;
513
- if (this.widgetTimer || !this.widgetRun) return;
524
+ this.upsertWidgetRun(run);
525
+ if (this.widgetTimer || this.widgetRuns.length === 0) return;
514
526
  this.widgetTimer = setTimeout(() => {
515
527
  this.widgetTimer = undefined;
516
- const target = this.widgetRun; // read at fire: never render a stale run
517
- if (!target) return;
528
+ if (this.widgetRuns.length === 0) return;
529
+ const targets = [...this.widgetRuns]; // read at fire: never render stale runs
518
530
  if (ctx?.hasUI) {
519
- ctx.ui.setStatus("subagents", `subagents: ${target.tasks.filter((t) => !TERMINAL.includes(t.status)).length} running`);
531
+ const live = targets.reduce((n, r) => n + r.tasks.filter((t) => !TERMINAL.includes(t.status)).length, 0);
532
+ ctx.ui.setStatus("subagents", `subagents: ${live} running`);
520
533
  this.ensureWidget(ctx);
521
534
  this.widgetTui?.requestRender();
522
535
  }
523
- onUpdate?.({ content: [{ type: "text", text: compactLines(target).join("\n") }] });
536
+ onUpdate?.({ content: [{ type: "text", text: targets.flatMap(compactLines).join("\n") }] });
524
537
  }, WIDGET_THROTTLE_MS);
525
538
  }
526
539
  private flushWidget(ctx?: ExtensionContext, onUpdate?: (partial: any) => void): void {
@@ -528,14 +541,12 @@ class SubagentManager {
528
541
  clearTimeout(this.widgetTimer);
529
542
  this.widgetTimer = undefined;
530
543
  }
531
- const run = this.widgetRun;
532
- if (!run) return;
544
+ if (this.widgetRuns.length === 0) return;
533
545
  if (ctx?.hasUI) {
534
- ctx.ui.setStatus("subagents", `subagents: ${run.status}`);
535
546
  this.ensureWidget(ctx);
536
547
  this.widgetTui?.requestRender();
537
548
  }
538
- onUpdate?.({ content: [{ type: "text", text: compactLines(run).join("\n") }] });
549
+ onUpdate?.({ content: [{ type: "text", text: this.widgetRuns.flatMap(compactLines).join("\n") }] });
539
550
  }
540
551
  private ensureWidget(ctx: ExtensionContext): void {
541
552
  if (this.widgetTui !== null || !ctx.hasUI) return;
@@ -543,7 +554,7 @@ class SubagentManager {
543
554
  "subagents",
544
555
  (tui, theme) => {
545
556
  this.widgetTui = tui;
546
- return new SubagentsWidget(() => this.widgetRun, theme);
557
+ return new SubagentsWidget(() => [...this.widgetRuns], theme);
547
558
  },
548
559
  { placement: "aboveEditor" },
549
560
  );