@henryqw/pi-subagent 2.10.0 → 2.10.2
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 +3 -3
- package/README.md +2 -2
- package/extensions/config.ts +4 -4
- package/extensions/subagent.ts +5 -17
- package/package.json +1 -1
package/CONTEXT.md
CHANGED
|
@@ -17,12 +17,12 @@ Provide validated user Roles, shared task-model Pi launch policy, generic manage
|
|
|
17
17
|
|
|
18
18
|
## Invariants
|
|
19
19
|
|
|
20
|
-
- One Delegated Task creates one ephemeral child process and no saved session. Default timeouts: 10-minute soft deadline; active model/tool execution or activity within the last minute grants one 5-minute grace period before a hard stop. Every value is configurable via the `timeout` object in `~/.pi/agent/config/pi-subagent.json` (`softMinutes`, `graceMinutes`, `activeWindowSeconds`).
|
|
21
|
-
- Up to five active ephemeral `delegate_task` children run per Main by default, configurable via `maxSubagents` in `~/.pi/agent/config/pi-subagent.json` or the `PI_SUBAGENT_MAX_SUBAGENTS` environment variable; excess calls wait FIFO. Queued calls do not start a child or consume child timeout. Managed Herdr workers are unaffected.
|
|
20
|
+
- One Delegated Task creates one ephemeral child process and no saved session. Default timeouts: 10-minute soft deadline; active model/tool execution or activity within the last minute grants one 5-minute grace period before a hard stop. Every value is configurable via the `timeout` object in `~/.pi/agent/config/pi-subagent/pi-subagent.json` (`softMinutes`, `graceMinutes`, `activeWindowSeconds`).
|
|
21
|
+
- Up to five active ephemeral `delegate_task` children run per Main by default, configurable via `maxSubagents` in `~/.pi/agent/config/pi-subagent/pi-subagent.json` or the `PI_SUBAGENT_MAX_SUBAGENTS` environment variable; excess calls wait FIFO. Queued calls do not start a child or consume child timeout. Managed Herdr workers are unaffected.
|
|
22
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.
|
|
23
23
|
- Role Skill names resolve through Main's effective Pi Skill registry; unavailable names warn and skip without blocking delegation.
|
|
24
24
|
- Main selects Role and may override Model Class and thinking level per task; omitted class uses shared `pi-subagent/delegateTask` assignment, initially `balanced`. Library callers select Role plus their own shared task ID.
|
|
25
25
|
- The selected profile resolves primary then fallback only before launch when a route, model, or thinking level is unavailable. If neither route is usable, launch rejects with `Run /task-models`; a started child is never retried by this package.
|
|
26
|
-
- Role config
|
|
26
|
+
- Role and Subagent JSON config live only in the user `config/pi-subagent` directory; model routes live in shared `config/pi-task-models.json`; repository roles do not execute.
|
|
27
27
|
- Numbered Codex routes prefer Main's active account slot and explicitly load the multi-Codex child extension.
|
|
28
28
|
- Generic Herdr host functions validate workspace ownership and provisioning identity while callers retain domain state, prompts, and lifecycle decisions.
|
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ An explicit `model` (`provider/modelId`) overrides `modelClass` and resolves aga
|
|
|
27
27
|
|
|
28
28
|
`modelClass` is `fast`, `balanced`, `frontier`, or `fav`. 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.
|
|
29
29
|
|
|
30
|
-
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 five active ephemeral `delegate_task` subagents run per Main; excess calls wait FIFO. Configure the cap with `"maxSubagents"` (positive integer) in `~/.pi/agent/config/pi-subagent.json`, or override per session with the `PI_SUBAGENT_MAX_SUBAGENTS` environment variable. Child timeouts are configurable with a `"timeout"` object: `{ "softMinutes": 20, "graceMinutes": 10, "activeWindowSeconds": 90 }` (all keys optional; defaults 10/5/60). Invalid config values fall back to the default with a warning; an invalid environment variable fails fast. Queued calls do not start a child or consume child timeout. Managed Herdr workers are unaffected.
|
|
30
|
+
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 five active ephemeral `delegate_task` subagents run per Main; excess calls wait FIFO. Configure the cap with `"maxSubagents"` (positive integer) in `~/.pi/agent/config/pi-subagent/pi-subagent.json`, or override per session with the `PI_SUBAGENT_MAX_SUBAGENTS` environment variable. Child timeouts are configurable with a `"timeout"` object: `{ "softMinutes": 20, "graceMinutes": 10, "activeWindowSeconds": 90 }` (all keys optional; defaults 10/5/60). Invalid config values fall back to the default with a warning; an invalid environment variable fails fast. Queued calls do not start a child or consume child timeout. Managed Herdr workers are unaffected.
|
|
31
31
|
|
|
32
32
|
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.
|
|
33
33
|
|
|
@@ -35,7 +35,7 @@ TUI shows one row per Subagent with role, route, task, tokens, and elapsed time.
|
|
|
35
35
|
|
|
36
36
|
## Config
|
|
37
37
|
|
|
38
|
-
Role files live in `~/.pi/agent/config/pi-subagent
|
|
38
|
+
Role files and `pi-subagent.json` live in `~/.pi/agent/config/pi-subagent/`. Model routes live in `~/.pi/agent/config/pi-task-models.json`.
|
|
39
39
|
|
|
40
40
|
```markdown
|
|
41
41
|
---
|
package/extensions/config.ts
CHANGED
|
@@ -39,14 +39,14 @@ const TIMEOUT_FIELDS: Array<[keyof SubagentTimeoutConfig, number, string]> = [
|
|
|
39
39
|
];
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
|
-
*
|
|
43
|
-
* injectable so tests can point at a temp directory.
|
|
42
|
+
* All pi-subagent config lives in its existing extension-named directory;
|
|
43
|
+
* agentDir is injectable so tests can point at a temp directory.
|
|
44
44
|
*/
|
|
45
45
|
export const configPath = (agentDir = getAgentDir()): string =>
|
|
46
|
-
join(agentDir, "config", "pi-subagent.json");
|
|
46
|
+
join(agentDir, "config", "pi-subagent", "pi-subagent.json");
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
|
-
* Read the optional user config at `<agentDir>/config/pi-subagent.json`.
|
|
49
|
+
* Read the optional user config at `<agentDir>/config/pi-subagent/pi-subagent.json`.
|
|
50
50
|
* Treated as untrusted user data: malformed files are preserved untouched and
|
|
51
51
|
* reported instead of crashing the session; callers fall back to defaults.
|
|
52
52
|
*/
|
package/extensions/subagent.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { basename } from "node:path";
|
|
4
|
+
import { StringDecoder } from "node:string_decoder";
|
|
4
5
|
import { StringEnum } from "@earendil-works/pi-ai";
|
|
5
6
|
import { type ExtensionAPI, type ExtensionContext, type Theme } from "@earendil-works/pi-coding-agent";
|
|
6
7
|
import { type Component, truncateToWidth, type TUI, visibleWidth } from "@earendil-works/pi-tui";
|
|
@@ -11,7 +12,6 @@ import {
|
|
|
11
12
|
type ThinkingLevel,
|
|
12
13
|
modelReference,
|
|
13
14
|
PROFILE_NAMES,
|
|
14
|
-
type ProfileName,
|
|
15
15
|
resolveAvailableModel,
|
|
16
16
|
resolveConfiguredTaskRoute,
|
|
17
17
|
type ResolvedTaskRoute,
|
|
@@ -20,7 +20,6 @@ import {
|
|
|
20
20
|
import { Type } from "typebox";
|
|
21
21
|
import { createRoleLaunch, isProfileName, loadRoles, resolveTaskRoute } from "@henryqw/pi-subagent";
|
|
22
22
|
|
|
23
|
-
const MODEL_CLASSES = PROFILE_NAMES;
|
|
24
23
|
const SUBAGENT_TASK = "pi-subagent/delegateTask";
|
|
25
24
|
const MAX_OUTPUT_BYTES = 50 * 1024;
|
|
26
25
|
const MAX_JSON_EVENT_BYTES = 1024 * 1024;
|
|
@@ -48,7 +47,6 @@ export function resolveTimeoutPolicy(partial: SubagentTimeoutConfig | undefined)
|
|
|
48
47
|
if (partial.activeWindowSeconds !== undefined) policy.activeWindowMs = partial.activeWindowSeconds * 1_000;
|
|
49
48
|
return policy;
|
|
50
49
|
}
|
|
51
|
-
type ModelClass = ProfileName;
|
|
52
50
|
class SubagentTimeoutError extends Error {}
|
|
53
51
|
type ChildResult = {
|
|
54
52
|
exitCode: number;
|
|
@@ -68,8 +66,6 @@ type WidgetItem = {
|
|
|
68
66
|
removeAt?: number;
|
|
69
67
|
};
|
|
70
68
|
|
|
71
|
-
const isModelClass = isProfileName;
|
|
72
|
-
|
|
73
69
|
const cleanText = (value: unknown, field: string, file: string): string => {
|
|
74
70
|
if (typeof value !== "string" || !value.trim() || value.includes("\0")) {
|
|
75
71
|
throw new Error(`${file}: ${field} must be non-empty text.`);
|
|
@@ -103,15 +99,7 @@ function assistantText(message: unknown): string | undefined {
|
|
|
103
99
|
}
|
|
104
100
|
|
|
105
101
|
function utf8Prefix(text: string, maxBytes: number): string {
|
|
106
|
-
|
|
107
|
-
let high = Math.min(text.length, maxBytes);
|
|
108
|
-
while (low < high) {
|
|
109
|
-
const middle = Math.ceil((low + high) / 2);
|
|
110
|
-
if (Buffer.byteLength(text.slice(0, middle), "utf8") <= maxBytes) low = middle;
|
|
111
|
-
else high = middle - 1;
|
|
112
|
-
}
|
|
113
|
-
if (low > 0 && low < text.length && /[\uD800-\uDBFF]/.test(text[low - 1]) && /[\uDC00-\uDFFF]/.test(text[low])) low--;
|
|
114
|
-
return text.slice(0, low);
|
|
102
|
+
return new StringDecoder().write(Buffer.from(text).subarray(0, maxBytes));
|
|
115
103
|
}
|
|
116
104
|
|
|
117
105
|
function cappedPrefix(text: string, totalBytes: number): string {
|
|
@@ -439,7 +427,7 @@ const Parameters = Type.Object({
|
|
|
439
427
|
model: Type.Optional(Type.String({
|
|
440
428
|
description: "Designated model as provider/modelId; overrides modelClass. Unknown references reject with the list of available models.",
|
|
441
429
|
})),
|
|
442
|
-
modelClass: Type.Optional(StringEnum(
|
|
430
|
+
modelClass: Type.Optional(StringEnum(PROFILE_NAMES, {
|
|
443
431
|
description: "Classify task complexity: fast for narrow lookups or mechanical edits; balanced for normal bounded work; frontier for ambiguous, cross-cutting, or high-risk reasoning; fav for the user's favorite model when they ask for it. Defaults to the shared pi-subagent/delegateTask assignment.",
|
|
444
432
|
})),
|
|
445
433
|
background: Type.Optional(Type.Boolean({
|
|
@@ -486,7 +474,7 @@ export default function subagentExtension(
|
|
|
486
474
|
): void {
|
|
487
475
|
const widgetItems = new Map<string, WidgetItem>();
|
|
488
476
|
// Each child is a full Pi process issuing its own model calls; cap parallel
|
|
489
|
-
// spend. Precedence: PI_SUBAGENT_MAX_SUBAGENTS env > config/pi-subagent.json
|
|
477
|
+
// spend. Precedence: PI_SUBAGENT_MAX_SUBAGENTS env > config/pi-subagent/pi-subagent.json
|
|
490
478
|
// maxSubagents > default 5. Invalid config falls back to the default and is
|
|
491
479
|
// reported once the UI exists; an invalid env value fails fast.
|
|
492
480
|
const loadedConfig = readSubagentConfig();
|
|
@@ -693,7 +681,7 @@ export default function subagentExtension(
|
|
|
693
681
|
throw new Error(`Unknown Subagent role: ${params.role}. Available roles: ${roles.map(({ name }) => name).join(", ") || "none"}.`);
|
|
694
682
|
}
|
|
695
683
|
|
|
696
|
-
if (params.modelClass !== undefined && !
|
|
684
|
+
if (params.modelClass !== undefined && !isProfileName(params.modelClass)) {
|
|
697
685
|
throw new Error("delegate_task modelClass must be fast, balanced, frontier, or fav.");
|
|
698
686
|
}
|
|
699
687
|
// Resolve against the latest known session context: a task queued past
|