@arhen/pi-core-subagent 1.1.4 → 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.
- package/README.md +3 -3
- package/package.json +2 -2
- package/src/index.ts +51 -20
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# @arhen/pi-subagent
|
|
1
|
+
# @arhen/pi-core-subagent
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/@arhen/pi-subagent)
|
|
3
|
+
[](https://www.npmjs.com/package/@arhen/pi-core-subagent)
|
|
4
4
|
[](./LICENSE)
|
|
5
5
|
[](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.
|
|
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
|
|
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
|
-
|
|
441
|
-
this.
|
|
445
|
+
for (const t of this.widgetTimers.values()) clearTimeout(t);
|
|
446
|
+
this.widgetTimers.clear();
|
|
442
447
|
this.widgetRuns = [];
|
|
443
448
|
}
|
|
444
449
|
|
|
@@ -531,29 +536,30 @@ 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 (
|
|
535
|
-
this.
|
|
536
|
-
this.
|
|
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:
|
|
544
|
-
}, WIDGET_THROTTLE_MS);
|
|
546
|
+
onUpdate?.({ content: [{ type: "text", text: compactLines(run).join("\n") }] });
|
|
547
|
+
}, WIDGET_THROTTLE_MS));
|
|
545
548
|
}
|
|
546
|
-
private flushWidget(ctx?: ExtensionContext, onUpdate?: (partial: any) => void): void {
|
|
547
|
-
if (
|
|
548
|
-
|
|
549
|
-
|
|
549
|
+
private flushWidget(run: RunSnapshot | undefined, ctx?: ExtensionContext, onUpdate?: (partial: any) => void): void {
|
|
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
|
-
if (this.widgetRuns.length === 0) return;
|
|
557
|
+
if (!run || this.widgetRuns.length === 0) return;
|
|
552
558
|
if (ctx?.hasUI) {
|
|
553
559
|
this.ensureWidget(ctx);
|
|
554
560
|
this.widgetTui?.requestRender();
|
|
555
561
|
}
|
|
556
|
-
onUpdate?.({ content: [{ type: "text", text:
|
|
562
|
+
onUpdate?.({ content: [{ type: "text", text: compactLines(run).join("\n") }] });
|
|
557
563
|
}
|
|
558
564
|
private ensureWidget(ctx: ExtensionContext): void {
|
|
559
565
|
if (this.widgetTui !== null || !ctx.hasUI) return;
|
|
@@ -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 = () =>
|
|
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 });
|
|
@@ -899,7 +929,7 @@ class SubagentManager {
|
|
|
899
929
|
const aborted = run.tasks.some((t) => t.status === "aborted") || Boolean(signal?.aborted);
|
|
900
930
|
run.status = aborted ? "aborted" : failed ? "failed" : "completed";
|
|
901
931
|
run.endedAt = Date.now();
|
|
902
|
-
this.flushWidget(ctx, onUpdate);
|
|
932
|
+
this.flushWidget(run, ctx, onUpdate);
|
|
903
933
|
// Run done: keep the widget only while another run is still live.
|
|
904
934
|
const live = this.listRuns().find((r) => !TERMINAL.includes(r.status));
|
|
905
935
|
if (live) {
|
|
@@ -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) {
|
|
@@ -1113,7 +1144,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1113
1144
|
promptSnippet: "Define and delegate work to specialized subagents.",
|
|
1114
1145
|
promptGuidelines: [
|
|
1115
1146
|
"Use subagent when independent review, testing, research, or parallel analysis improves quality.",
|
|
1116
|
-
"Decompose parallelizable work: if the request has 2+ independent sub-tasks (separate files, separate concerns, independent research/review), delegate each to its own subagent in
|
|
1147
|
+
"Decompose parallelizable work: if the request has 2+ independent sub-tasks (separate files, separate concerns, independent research/review), delegate each to its own subagent — in ONE call with tasks[] (single shot), not multiple parallel subagent calls.",
|
|
1117
1148
|
"If independent sub-tasks are sequential (each builds on the previous one's output), use chain mode with {previous}.",
|
|
1118
1149
|
"Define each subagent yourself: an invented name, a focused system prompt (prompt:), and a toolset — read-only (default) or write (write:true).",
|
|
1119
1150
|
"Prefer read-only subagents unless the task explicitly needs edits.",
|