@henryqw/pi-subagent 2.3.3 → 2.3.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.
- package/CONTEXT.md +1 -0
- package/README.md +1 -1
- package/extensions/subagent.ts +33 -1
- package/package.json +2 -2
package/CONTEXT.md
CHANGED
|
@@ -18,6 +18,7 @@ Provide validated user Roles, shared task-model Pi launch policy, generic manage
|
|
|
18
18
|
## Invariants
|
|
19
19
|
|
|
20
20
|
- One Delegated Task creates one ephemeral child process and no saved session. Its soft deadline is 10 minutes; active model/tool execution or activity within the last minute grants one 5-minute grace period before a hard stop.
|
|
21
|
+
- Up to four active ephemeral `delegate_task` children run per Main; excess calls wait FIFO. Queued calls do not start a child or consume child timeout. Managed Herdr workers are unaffected.
|
|
21
22
|
- Ambient child extensions and Skills stay disabled; Role explicitly selects extension sources and named Skills. Pi loads Skills supplied by those extension packages or their resource discovery. Omitted Role tools use Pi's effective `defaultTools`; an explicit list sets base tools while loaded extension tools activate automatically.
|
|
22
23
|
- Role Skill names resolve through Main's effective Pi Skill registry; unavailable names warn and skip without blocking delegation.
|
|
23
24
|
- Main selects Role and may override Model Class per task; omitted class uses shared `pi-subagent/delegateTask` assignment, initially `balanced`. Library callers select Role plus their own shared task ID.
|
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ pi install npm:@henryqw/pi-subagent
|
|
|
25
25
|
|
|
26
26
|
`modelClass` is `fast`, `balanced`, or `frontier`. Omitted class uses the shared `pi-subagent/delegateTask` assignment, which defaults to `balanced`. Primary route is resolved against current scoped text models; fallback is tried only before launch. If no route is usable, delegation rejects with `Run /task-models`. A started child is never retried.
|
|
27
27
|
|
|
28
|
-
Main splits broad work into independent bounded tasks and keeps integration and cross-cutting decisions. Each `task` states its objective, exact scope and exclusions, relevant context and constraints, expected deliverable, and validation. Each call uses the least capable `modelClass` that can reliably complete its task. Independent sibling calls can run concurrently; concurrent edit tasks must own non-overlapping files.
|
|
28
|
+
Main splits broad work into independent bounded tasks and keeps integration and cross-cutting decisions. Each `task` states its objective, exact scope and exclusions, relevant context and constraints, expected deliverable, and validation. Each call uses the least capable `modelClass` that can reliably complete its task. Independent sibling calls can run concurrently; concurrent edit tasks must own non-overlapping files. Up to four active ephemeral `delegate_task` children run per Main; excess calls wait FIFO. Queued calls do not start a child or consume child timeout. Managed Herdr workers are unaffected.
|
|
29
29
|
|
|
30
30
|
Each call starts one isolated child (`pi --mode json -p --no-session`). Ambient extensions and Skills are off. Role/caller extensions load; those packages' tools and Skills auto-load, plus any extra `skills` names. Child uses the delegated working directory and Main's project approval. Abort kills the child process group. An inactive child times out after 10 minutes; current model/tool execution or activity in the last minute grants one 5-minute grace period, then the child stops. Streaming output is capped at 50 KiB. Unused JSON event types are discarded before payload buffering; consumed or unclassifiable events above 1 MiB fail delegation.
|
|
31
31
|
|
package/extensions/subagent.ts
CHANGED
|
@@ -12,6 +12,7 @@ const MODEL_CLASSES = PROFILE_NAMES;
|
|
|
12
12
|
const SUBAGENT_TASK = "pi-subagent/delegateTask";
|
|
13
13
|
const MAX_OUTPUT_BYTES = 50 * 1024;
|
|
14
14
|
const MAX_JSON_EVENT_BYTES = 1024 * 1024;
|
|
15
|
+
const MAX_ACTIVE_CHILDREN = 4;
|
|
15
16
|
const CONSUMED_JSON_EVENTS = new Set(["message_start", "message_update", "message_end"]);
|
|
16
17
|
const JSON_EVENT_TYPE = /^\s*\{\s*"type"\s*:\s*"([^"\\]+)"/;
|
|
17
18
|
const WIDGET_KEY = "subagent-status";
|
|
@@ -433,6 +434,35 @@ export default function subagentExtension(
|
|
|
433
434
|
timeoutPolicy: TimeoutPolicy = DEFAULT_TIMEOUT_POLICY,
|
|
434
435
|
): void {
|
|
435
436
|
const widgetItems = new Map<string, WidgetItem>();
|
|
437
|
+
let activeChildren = 0;
|
|
438
|
+
const queuedChildren: Array<() => void> = [];
|
|
439
|
+
const acquireChildPermit = (signal: AbortSignal | undefined): Promise<void> => {
|
|
440
|
+
if (signal?.aborted) return Promise.reject(new Error("Subagent was aborted."));
|
|
441
|
+
if (activeChildren < MAX_ACTIVE_CHILDREN) {
|
|
442
|
+
activeChildren++;
|
|
443
|
+
return Promise.resolve();
|
|
444
|
+
}
|
|
445
|
+
return new Promise<void>((resolve, reject) => {
|
|
446
|
+
function abort() {
|
|
447
|
+
const index = queuedChildren.indexOf(grant);
|
|
448
|
+
if (index < 0) return;
|
|
449
|
+
queuedChildren.splice(index, 1);
|
|
450
|
+
signal?.removeEventListener("abort", abort);
|
|
451
|
+
reject(new Error("Subagent was aborted."));
|
|
452
|
+
}
|
|
453
|
+
const grant = () => {
|
|
454
|
+
signal?.removeEventListener("abort", abort);
|
|
455
|
+
resolve();
|
|
456
|
+
};
|
|
457
|
+
queuedChildren.push(grant);
|
|
458
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
459
|
+
});
|
|
460
|
+
};
|
|
461
|
+
const releaseChildPermit = () => {
|
|
462
|
+
const grant = queuedChildren.shift();
|
|
463
|
+
if (grant) grant();
|
|
464
|
+
else activeChildren--;
|
|
465
|
+
};
|
|
436
466
|
let widgetInstalled = false;
|
|
437
467
|
let widgetTimer: ReturnType<typeof setInterval> | undefined;
|
|
438
468
|
let spinnerIndex = 0;
|
|
@@ -555,9 +585,10 @@ export default function subagentExtension(
|
|
|
555
585
|
);
|
|
556
586
|
}
|
|
557
587
|
|
|
588
|
+
const args = ["--mode", "json", "-p", ...launch.args, `Task: ${task}`];
|
|
589
|
+
await acquireChildPermit(signal);
|
|
558
590
|
let widgetStatus: Exclude<WidgetStatus, "working"> = "failure";
|
|
559
591
|
try {
|
|
560
|
-
const args = ["--mode", "json", "-p", ...launch.args, `Task: ${task}`];
|
|
561
592
|
startWidgetItem(toolCallId, role.name, launch.model.id, thinkingLevel, task, ctx);
|
|
562
593
|
const details = { role: role.name, model: modelReferenceValue, thinkingLevel };
|
|
563
594
|
const result = await runPi(
|
|
@@ -578,6 +609,7 @@ export default function subagentExtension(
|
|
|
578
609
|
if (signal?.aborted && !(error instanceof SubagentTimeoutError)) widgetStatus = "aborted";
|
|
579
610
|
throw error;
|
|
580
611
|
} finally {
|
|
612
|
+
releaseChildPermit();
|
|
581
613
|
finishWidgetItem(toolCallId, widgetStatus);
|
|
582
614
|
}
|
|
583
615
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@henryqw/pi-subagent",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.5",
|
|
4
4
|
"description": "Delegate one task to an isolated Pi role with explicit extensions and skills.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"@earendil-works/pi-ai": "^0.84.2",
|
|
39
39
|
"@earendil-works/pi-coding-agent": "^0.84.2",
|
|
40
40
|
"@earendil-works/pi-tui": "^0.84.2",
|
|
41
|
-
"typebox": "^1.3.
|
|
41
|
+
"typebox": "^1.3.15"
|
|
42
42
|
},
|
|
43
43
|
"repository": {
|
|
44
44
|
"type": "git",
|