@arhen/pi-core-subagent 1.0.3 → 1.0.5

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 +36 -16
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arhen/pi-core-subagent",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
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
@@ -383,15 +383,23 @@ class SubagentManager {
383
383
  private mailboxes: Mailbox = createMailbox();
384
384
  private runControllers = new Map<string, AbortController>();
385
385
  private widgetTimer: ReturnType<typeof setTimeout> | undefined;
386
- private widgetRun: RunSnapshot | undefined;
386
+ private widgetRuns: RunSnapshot[] = [];
387
387
 
388
388
  turnActivity = false;
389
389
 
390
390
  constructor(private readonly pi: ExtensionAPI) {}
391
391
 
392
- /** Hide the widget. Call on agent_start when the turn made no subagent calls. */
392
+ /** Any run still has queued/running tasks? */
393
+ hasActiveRun(): boolean {
394
+ for (const run of this.runs.values()) {
395
+ if (run.tasks.some((t) => !TERMINAL.includes(t.status))) return true;
396
+ }
397
+ return false;
398
+ }
399
+
400
+ /** Hide the widget. */
393
401
  clearWidget(ctx: ExtensionContext): void {
394
- this.widgetRun = undefined;
402
+ this.widgetRuns = [];
395
403
  this.widgetTui = null;
396
404
  if (ctx.hasUI) {
397
405
  try {
@@ -417,7 +425,7 @@ class SubagentManager {
417
425
  this.widgetTui = null; // force re-registration on the next session
418
426
  if (this.widgetTimer) clearTimeout(this.widgetTimer);
419
427
  this.widgetTimer = undefined;
420
- this.widgetRun = undefined;
428
+ this.widgetRuns = [];
421
429
  }
422
430
 
423
431
  // ── persistence (sidecar per parent session) ────────────────────────
@@ -500,19 +508,27 @@ class SubagentManager {
500
508
  // The component self-animates the spinner via its own 100ms interval;
501
509
  // scheduleWidget just throttles status changes into requestRender calls.
502
510
  private widgetTui: TUI | null = null;
511
+ /** Upsert a run into the widget's visible set (all runs, not just the latest). */
512
+ private upsertWidgetRun(run: RunSnapshot | undefined): void {
513
+ if (!run) return;
514
+ const idx = this.widgetRuns.findIndex((r) => r.id === run.id);
515
+ if (idx >= 0) this.widgetRuns[idx] = run;
516
+ else this.widgetRuns.push(run);
517
+ }
503
518
  private scheduleWidget(run: RunSnapshot | undefined, ctx?: ExtensionContext, onUpdate?: (partial: any) => void): void {
504
- if (run) this.widgetRun = run;
505
- if (this.widgetTimer || !this.widgetRun) return;
519
+ this.upsertWidgetRun(run);
520
+ if (this.widgetTimer || this.widgetRuns.length === 0) return;
506
521
  this.widgetTimer = setTimeout(() => {
507
522
  this.widgetTimer = undefined;
508
- const target = this.widgetRun; // read at fire: never render a stale run
509
- if (!target) return;
523
+ if (this.widgetRuns.length === 0) return;
524
+ const targets = [...this.widgetRuns]; // read at fire: never render stale runs
510
525
  if (ctx?.hasUI) {
511
- ctx.ui.setStatus("subagents", `subagents: ${target.tasks.filter((t) => !TERMINAL.includes(t.status)).length} running`);
526
+ const live = targets.reduce((n, r) => n + r.tasks.filter((t) => !TERMINAL.includes(t.status)).length, 0);
527
+ ctx.ui.setStatus("subagents", `subagents: ${live} running`);
512
528
  this.ensureWidget(ctx);
513
529
  this.widgetTui?.requestRender();
514
530
  }
515
- onUpdate?.({ content: [{ type: "text", text: compactLines(target).join("\n") }] });
531
+ onUpdate?.({ content: [{ type: "text", text: targets.flatMap(compactLines).join("\n") }] });
516
532
  }, WIDGET_THROTTLE_MS);
517
533
  }
518
534
  private flushWidget(ctx?: ExtensionContext, onUpdate?: (partial: any) => void): void {
@@ -520,14 +536,12 @@ class SubagentManager {
520
536
  clearTimeout(this.widgetTimer);
521
537
  this.widgetTimer = undefined;
522
538
  }
523
- const run = this.widgetRun;
524
- if (!run) return;
539
+ if (this.widgetRuns.length === 0) return;
525
540
  if (ctx?.hasUI) {
526
- ctx.ui.setStatus("subagents", `subagents: ${run.status}`);
527
541
  this.ensureWidget(ctx);
528
542
  this.widgetTui?.requestRender();
529
543
  }
530
- onUpdate?.({ content: [{ type: "text", text: compactLines(run).join("\n") }] });
544
+ onUpdate?.({ content: [{ type: "text", text: this.widgetRuns.flatMap(compactLines).join("\n") }] });
531
545
  }
532
546
  private ensureWidget(ctx: ExtensionContext): void {
533
547
  if (this.widgetTui !== null || !ctx.hasUI) return;
@@ -535,7 +549,7 @@ class SubagentManager {
535
549
  "subagents",
536
550
  (tui, theme) => {
537
551
  this.widgetTui = tui;
538
- return new SubagentsWidget(() => this.widgetRun, theme);
552
+ return new SubagentsWidget(() => [...this.widgetRuns], theme);
539
553
  },
540
554
  { placement: "aboveEditor" },
541
555
  );
@@ -875,7 +889,13 @@ class SubagentManager {
875
889
  run.status = aborted ? "aborted" : failed ? "failed" : "completed";
876
890
  run.endedAt = Date.now();
877
891
  this.flushWidget(ctx, onUpdate);
878
- this.clearWidget(ctx); // run is done drop the widget immediately
892
+ // Run done: keep the widget only while another run is still live.
893
+ const live = this.listRuns().find((r) => !TERMINAL.includes(r.status));
894
+ if (live) {
895
+ this.scheduleWidget(live, ctx, onUpdate);
896
+ } else {
897
+ this.clearWidget(ctx);
898
+ }
879
899
  this.emit("subagent:run-completed", { runId: run.id, status: run.status, run: cloneRun(run), aggregateUsage: run.aggregateUsage });
880
900
  this.settleRun(run.id, run);
881
901
  this.runControllers.delete(run.id);