@arhen/pi-core-subagent 1.1.5 → 1.1.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 (3) hide show
  1. package/README.md +3 -3
  2. package/package.json +2 -2
  3. package/src/index.ts +46 -15
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # @arhen/pi-subagent
1
+ # @arhen/pi-core-subagent
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/%40arhen%2Fpi-subagent?color=cb3837&logo=npm)](https://www.npmjs.com/package/@arhen/pi-subagent)
3
+ [![npm version](https://img.shields.io/npm/v/%40arhen%2Fpi-core-subagent?color=cb3837&logo=npm)](https://www.npmjs.com/package/@arhen/pi-core-subagent)
4
4
  [![license](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
5
5
  [![pi extension](https://img.shields.io/badge/pi-extension-7c3aed)](https://github.com/earendil-works/pi)
6
6
 
@@ -20,7 +20,7 @@ Built for one job: delegate work to isolated subagents **without bloating the pa
20
20
  ## Install
21
21
 
22
22
  ```sh
23
- pi install npm:@arhen/pi-subagent
23
+ pi install npm:@arhen/pi-core-subagent
24
24
  # or locally: pi install /path/to/pi-subagents
25
25
  ```
26
26
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arhen/pi-core-subagent",
3
- "version": "1.1.5",
3
+ "version": "1.1.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",
@@ -36,7 +36,7 @@
36
36
  },
37
37
  "repository": {
38
38
  "type": "git",
39
- "url": "git+https://github.com/arhen/pi-subagent.git"
39
+ "url": "git+https://github.com/arhen/pi-core-subagent.git"
40
40
  },
41
41
  "publishConfig": {
42
42
  "access": "public",
package/src/index.ts CHANGED
@@ -396,7 +396,7 @@ class SubagentManager {
396
396
  private liveChildren = new Map<string, { abort: () => void; dispose: () => void; touchWatchdog: () => void }>();
397
397
  private mailboxes: Mailbox = createMailbox();
398
398
  private runControllers = new Map<string, AbortController>();
399
- private widgetTimer: ReturnType<typeof setTimeout> | undefined;
399
+ private widgetTimers = new Map<string, ReturnType<typeof setTimeout>>(); // per-run stream throttle
400
400
  private widgetRuns: RunSnapshot[] = [];
401
401
 
402
402
  turnActivity = false;
@@ -431,14 +431,19 @@ class SubagentManager {
431
431
  return runId ? this.runs.get(runId) : undefined;
432
432
  }
433
433
  clearRuns(): void {
434
+ for (const child of this.liveChildren.values()) {
435
+ child.abort();
436
+ child.dispose();
437
+ }
438
+ this.liveChildren.clear();
434
439
  this.runs.clear();
435
440
  this.settlers.clear();
436
441
  this.pendingReplies.clear();
437
442
  this.runControllers.clear();
438
443
  this.mailboxes = createMailbox();
439
444
  this.widgetTui = null; // force re-registration on the next session
440
- if (this.widgetTimer) clearTimeout(this.widgetTimer);
441
- this.widgetTimer = undefined;
445
+ for (const t of this.widgetTimers.values()) clearTimeout(t);
446
+ this.widgetTimers.clear();
442
447
  this.widgetRuns = [];
443
448
  }
444
449
 
@@ -531,22 +536,23 @@ class SubagentManager {
531
536
  }
532
537
  private scheduleWidget(run: RunSnapshot | undefined, ctx?: ExtensionContext, onUpdate?: (partial: any) => void): void {
533
538
  this.upsertWidgetRun(run);
534
- if (this.widgetTimer || this.widgetRuns.length === 0) return;
535
- this.widgetTimer = setTimeout(() => {
536
- this.widgetTimer = undefined;
537
- if (this.widgetRuns.length === 0) return;
538
- const targets = [...this.widgetRuns]; // read at fire: never render stale runs
539
+ if (!run || this.widgetTimers.has(run.id)) return;
540
+ this.widgetTimers.set(run.id, setTimeout(() => {
541
+ this.widgetTimers.delete(run.id);
539
542
  if (ctx?.hasUI) {
540
543
  this.ensureWidget(ctx);
541
544
  this.widgetTui?.requestRender();
542
545
  }
543
- onUpdate?.({ content: [{ type: "text", text: compactLines(run ?? targets[0]!).join("\n") }] });
544
- }, WIDGET_THROTTLE_MS);
546
+ onUpdate?.({ content: [{ type: "text", text: compactLines(run).join("\n") }] });
547
+ }, WIDGET_THROTTLE_MS));
545
548
  }
546
549
  private flushWidget(run: RunSnapshot | undefined, ctx?: ExtensionContext, onUpdate?: (partial: any) => void): void {
547
- if (this.widgetTimer) {
548
- clearTimeout(this.widgetTimer);
549
- this.widgetTimer = undefined;
550
+ if (run) {
551
+ const t = this.widgetTimers.get(run.id);
552
+ if (t) {
553
+ clearTimeout(t);
554
+ this.widgetTimers.delete(run.id);
555
+ }
550
556
  }
551
557
  if (!run || this.widgetRuns.length === 0) return;
552
558
  if (ctx?.hasUI) {
@@ -586,6 +592,13 @@ class SubagentManager {
586
592
  this.updateTask(run, task, { status: "awaiting_parent" }, ctx);
587
593
  this.liveChildren.get(`${run.id}:${task.id}`)?.touchWatchdog();
588
594
  this.notifyParent(run, "asked", { taskId: task.id, question });
595
+ // A blocking run's parent can't reply mid-tool (followUp only fires after the
596
+ // tool returns) — only background runs can truly wait for the answer.
597
+ if (!run.background) {
598
+ this.updateTask(run, task, { status: "running" }, ctx);
599
+ this.liveChildren.get(`${run.id}:${task.id}`)?.touchWatchdog();
600
+ return "Parent cannot answer while this run is blocking. Continue autonomously with your best judgment.";
601
+ }
589
602
  const reply = await this.awaitParentReply(run.id, task.id);
590
603
  this.updateTask(run, task, { status: "running" }, ctx);
591
604
  this.liveChildren.get(`${run.id}:${task.id}`)?.touchWatchdog();
@@ -593,10 +606,24 @@ class SubagentManager {
593
606
  },
594
607
  onNotifyParent: (_taskId, message, level) => {
595
608
  this.emit("subagent:intercom", { runId: run.id, taskId: task.id, kind: "notify", level, message });
609
+ if (!run.awaited) {
610
+ try {
611
+ this.pi.sendUserMessage(`[Subagent ${task.agent}] ${message}`, { deliverAs: "followUp" });
612
+ } catch {
613
+ /* parent mid-stream */
614
+ }
615
+ }
596
616
  },
597
617
  onSendMessage: (_taskId, to, text) => {
598
618
  if (to === "leader") {
599
619
  this.emit("subagent:intercom", { runId: run.id, taskId: task.id, kind: "notify", level: "info", message: text });
620
+ if (!run.awaited) {
621
+ try {
622
+ this.pi.sendUserMessage(`[Subagent ${task.agent}] ${text}`, { deliverAs: "followUp" });
623
+ } catch {
624
+ /* parent mid-stream */
625
+ }
626
+ }
600
627
  return true;
601
628
  }
602
629
  // Run-scoped keys: sibling ids are run-local; cross-run task_1 can never collide.
@@ -706,7 +733,7 @@ class SubagentManager {
706
733
  });
707
734
 
708
735
  unsubscribe = child.subscribe((event: AgentSessionEvent) => {
709
- const active = event.type === "message_update" || event.type === "message_end" || event.type === "tool_execution_start" || event.type === "tool_execution_end" || event.type === "agent_settled";
736
+ const active = event.type === "message_update" || event.type === "message_end" || event.type === "tool_execution_start" || event.type === "tool_execution_update" || event.type === "tool_execution_end" || event.type === "bash_execution_update" || event.type === "agent_settled";
710
737
  if (active) {
711
738
  watchdog.touch();
712
739
  this.emit("subagent:session-event", { runId: run.id, taskId: task.id, seq: eventSeq++, event: { type: event.type } });
@@ -745,7 +772,10 @@ class SubagentManager {
745
772
  }
746
773
  });
747
774
 
748
- const abortChild = () => void child?.abort();
775
+ const abortChild = () => {
776
+ void child?.abort();
777
+ this.runControllers.get(run.id)?.abort(); // parent abort kills ALL siblings, not just this child
778
+ };
749
779
  const runController = this.runControllers.get(run.id);
750
780
  if (signal) signal.addEventListener("abort", abortChild, { once: true });
751
781
  if (runController) runController.signal.addEventListener("abort", abortChild, { once: true });
@@ -947,6 +977,7 @@ class SubagentManager {
947
977
  cancelRun(runId: string): { aborted: number } {
948
978
  const run = this.runs.get(runId);
949
979
  if (!run) return { aborted: 0 };
980
+ if (TERMINAL.includes(run.status)) return { aborted: 0 }; // never corrupt a finished run
950
981
  let aborted = 0;
951
982
  this.runControllers.get(runId)?.abort();
952
983
  for (const [key, child] of this.liveChildren) {