@henryqw/pi-subagent 8.0.0 → 9.0.0
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 +2 -4
- package/README.md +136 -30
- package/dist/index.d.ts +0 -38
- package/dist/index.js +0 -230
- package/docs/orchestration.md +0 -2
- package/package.json +3 -4
package/CONTEXT.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
4
|
|
|
5
|
-
Provide validated built-in and user Roles, shared task-model Pi launch policy, generic
|
|
5
|
+
Provide validated built-in and user Roles, shared task-model Pi launch policy, generic `delegate_task` delegation, and package-owned `delegate_flow` Git orchestration. Main plans and orchestrates; `delegate_task` remains a flat bounded delegation tool, while Flow uses the effective Implementer and only invokes its effective Reviewer for explicit judgment criteria after authoritative validation. The bundled Main-side Skill adds no runtime behavior or changes generic fallback.
|
|
6
6
|
|
|
7
7
|
## Domain glossary
|
|
8
8
|
|
|
@@ -20,12 +20,11 @@ Provide validated built-in and user Roles, shared task-model Pi launch policy, g
|
|
|
20
20
|
- **Resource Policy**: Role ownership of base tools, extensions, and Skill names, plus explicit caller additions of tools, extensions, and environment through `createRoleLaunch`.
|
|
21
21
|
- **Pi Launch**: reusable `{env,args}` policy for one Role, resolved model route, explicit caller resources, and project trust.
|
|
22
22
|
- **Ephemeral Executor**: mechanism that receives a prepared Pi Launch, runs one bounded Delegated Task in one no-session child process, and returns its result without discovering resources or composing a Workflow.
|
|
23
|
-
- **Managed Subagent**: Pi agent hosted in a reconciled Herdr tab or pane; lifecycle orchestration remains with the caller.
|
|
24
23
|
|
|
25
24
|
## Invariants
|
|
26
25
|
|
|
27
26
|
- One Delegated Task creates one ephemeral child process and no saved session. Execution ends at the first hard budget: attempted turn 51 by default (`maxTurns`, positive safe integer) or `deadline = min(last recognized Pi JSON event + idle timeout, child start + maximum runtime)` (recognized Pi events renew; raw bytes do not; max always terminates). A terminal turn 50 succeeds; attempted continuation rejects with `turn_limit`, accumulated usage, and bounded output. On continuing `turn_end`, the existing child Role tool extension steers the fixed convergence warning once at 80% of completed turns and once at 80% of maximum runtime, combining thresholds first due together and never starting a warning timer or extra turn. After direct Pi exits, inherited stdout/stderr drain until EOF unless an escaped descendant holds them past short inactivity or a one-second hard deadline. Configurable in `~/.pi/agent/config/pi-subagent/pi-subagent.json` (`maxTurns` default 50; `timeout.idleMinutes`/`maxMinutes` defaults 10/30).
|
|
28
|
-
- 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.
|
|
27
|
+
- 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.
|
|
29
28
|
- Ambient child extensions and Skills stay disabled. Every Role requires `tools`, `extensions`, and `skills` YAML arrays, and every launch installs the Role tool policy. `tools: []` activates no base built-ins but does activate all tools from explicitly selected trusted extension bundles and explicit caller tool additions; `skills: []` selects no separately named Role Skills but trusted selected extension Skills still load; `extensions: []` selects no Role extension bundle. A Role/caller explicitly selected extension is a trusted atomic capability bundle: all tools it registers and all Skills supplied through its Pi package metadata or dynamic `resources_discover` load alongside separately named Role Skills. This intentionally includes the extension's executable lifecycle/prompt behavior; pi-subagent does not infer or externally narrow undocumented dependencies, and loading an extension is not sandboxing. Scope children by selecting fewer trusted extensions; finer granularity requires separate entry points/configuration or an upstream split. Explicit Role/caller tool names still verify against the final filtered registry, while parent-only recursive orchestration tools remain excluded.
|
|
30
29
|
- Role Skill names resolve through Main's effective Pi Skill registry; unavailable names warn and skip without blocking delegation. Explicit Role/caller tool names verify against the final filtered child registry after explicit provider `session_start` handlers, and unavailable names fail before the first turn.
|
|
31
30
|
- Main policy populates direct `model` and `thinking` only for explicit user overrides; otherwise it selects only `modelClass` (`fast` normally, `balanced` upfront for obvious complexity). This has no provenance tracking or runtime enforcement. An omitted class uses pi-subagent's local `pi-subagent/delegateTask` Model Task declaration (default `fast`); library callers select a Role plus their own Model Task declaration.
|
|
@@ -35,4 +34,3 @@ Provide validated built-in and user Roles, shared task-model Pi launch policy, g
|
|
|
35
34
|
- Flow is memory-only. Only a post-rebase commit drop produces a no-op (`base === tip`); it validates, skips Reviewer and merge, then cleans up ordinarily. Initial zero-commit implementations block. Implementer, validation, or reviewer blocks allow one `delegate_flow_continue({ guidance, modelClass? })` repair in the same worktree; omission retains the Unit's current class and presence replaces it for that repair. A second block is terminal. Rebase and evidence/Reviewer/infrastructure failures retain worktrees. A reported fast-forward failure retains its worktree unless Main is clean at the exact integrated tip, which completes with the merge diagnostic as a warning. Cleanup uses non-forced worktree removal and branch deletion; cleanup refusal is a completion warning.
|
|
36
35
|
- Flow has no graph, saved recovery, automatic retry, aggregate review, or post-merge validation. It never changes generic `delegate_task` Role resolution, isolation, non-Git fallback, or ordinary direct plan/file review.
|
|
37
36
|
- Numbered Codex routes prefer Main's active account slot and explicitly load the multi-Codex child extension.
|
|
38
|
-
- Generic Herdr host functions validate workspace ownership and provisioning identity while callers retain domain state, prompts, and lifecycle decisions.
|
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# `@henryqw/pi-subagent`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Main is the parent Pi session. It can delegate bounded single, parallel, and chained tasks, plus package-owned Git Flow work, to isolated Pi child roles.
|
|
4
4
|
|
|
5
5
|
## Why
|
|
6
6
|
|
|
7
|
-
- **Created for**:
|
|
8
|
-
- **Advantage**: Generic bounded delegation
|
|
7
|
+
- **Created for**: Delegate bounded work to isolated child Pi processes without losing Main's context.
|
|
8
|
+
- **Advantage**: Generic bounded delegation and a deterministic package-owned Git Flow use reusable Role launch policies for package authors.
|
|
9
9
|
|
|
10
10
|
## Install
|
|
11
11
|
|
|
@@ -19,8 +19,10 @@ pi install npm:@henryqw/pi-subagent
|
|
|
19
19
|
|
|
20
20
|
| Package | Why |
|
|
21
21
|
| --- | --- |
|
|
22
|
-
| `@henryqw/pi-
|
|
23
|
-
| `@henryqw/pi-
|
|
22
|
+
| `@henryqw/pi-multi-codex` | Required. Children can use Main's active Codex slot. |
|
|
23
|
+
| `@henryqw/pi-task-models` | Required. Shared `fast` / `balanced` / `frontier` / `fav` routes. |
|
|
24
|
+
|
|
25
|
+
Model routing is not configured here. Children resolve routes through shared `@henryqw/pi-task-models` config at `~/.pi/agent/config/pi-task-models.json`, which stores only explicit task overrides. The local `pi-subagent/delegateTask` declaration supplies the omitted-class default.
|
|
24
26
|
|
|
25
27
|
## Use
|
|
26
28
|
|
|
@@ -30,7 +32,7 @@ pi install npm:@henryqw/pi-subagent
|
|
|
30
32
|
| `delegate_flow` | tool | Package-owned parallel implementation and declared-order Git integration for 1–8 independent units. |
|
|
31
33
|
| `delegate_flow_continue` | tool | Repair the blocked Flow unit once in its existing worktree. |
|
|
32
34
|
|
|
33
|
-
All three delegation tool blocks use Pi's default boxed shell and background. Their compact custom content is an immutable call label, foreground aggregate partial-result status, and bounded final summaries
|
|
35
|
+
All three delegation tool blocks use Pi's default boxed shell and background. Their compact custom content is an immutable call label, foreground aggregate partial-result status, and bounded final summaries. There is no expanded view.
|
|
34
36
|
|
|
35
37
|
### `delegate_task`
|
|
36
38
|
|
|
@@ -47,37 +49,92 @@ Select exactly one shape:
|
|
|
47
49
|
{ chain: [{ role, task, model?, modelClass?, thinking? }], background? }
|
|
48
50
|
```
|
|
49
51
|
|
|
50
|
-
|
|
52
|
+
#### Model precedence
|
|
51
53
|
|
|
52
|
-
|
|
54
|
+
1. An explicit `model` is `provider/modelId` and overrides `modelClass`.
|
|
55
|
+
2. Main sets direct `model` and `thinking` only for an explicit user override.
|
|
56
|
+
3. Otherwise, Main selects `modelClass`: `fast` normally or `balanced` upfront for obviously complex work.
|
|
57
|
+
4. `modelClass` is `fast`, `balanced`, `frontier`, or `fav`.
|
|
58
|
+
When omitted, it uses pi-subagent's local `pi-subagent/delegateTask` Model Task declaration.
|
|
59
|
+
That declaration defaults to `fast` and shared config can explicitly override it.
|
|
53
60
|
|
|
54
|
-
|
|
61
|
+
This is Main policy only. The runtime records no provenance and does not enforce it. `background` applies to the whole selected mode and is never a per-delegation field.
|
|
55
62
|
|
|
56
|
-
|
|
63
|
+
#### Modes, limits, and isolation
|
|
57
64
|
|
|
58
|
-
|
|
65
|
+
- Parallel mode starts entries concurrently, waits for every entry, and reports them in input order.
|
|
66
|
+
- Chain mode is sequential and fail-fast. Each literal `{previous}` receives only the immediately preceding successful assistant output.
|
|
67
|
+
- Foreground failures throw after retaining bounded sibling and recovery evidence.
|
|
68
|
+
- One tool call has one aggregate 50 KiB Main-visible transport cap. It is not 50 KiB per child.
|
|
69
|
+
- Background workflows are session-scoped. Session shutdown or reload aborts them and may deliver only recoverable-work evidence or no follow-up message.
|
|
70
|
+
- Each delegation resolves its own Role, resources, route, and optional worktree request.
|
|
71
|
+
- When available, `isolation: worktree` gives each entry a deterministic separate worktree. Non-Git or unborn-`HEAD` contexts may use Main's cwd.
|
|
72
|
+
- Siblings and chain steps never implicitly share one created worktree.
|
|
59
73
|
|
|
60
74
|
See [Orchestration, isolation, and the public API](./docs/orchestration.md) for generic delegation, Flow behavior, and JavaScript composition examples.
|
|
61
75
|
|
|
62
76
|
### `delegate_flow`
|
|
63
77
|
|
|
64
|
-
Use Flow only for independent, commuting Git changes.
|
|
78
|
+
Use Flow only for independent, commuting Git changes. Commuting changes can integrate in any order.
|
|
79
|
+
|
|
80
|
+
Flow accepts 1–8 uniquely identified units. Each unit has a bounded task, optional `modelClass`, direct command/argument validation gate, and optional non-empty `review` judgment criterion:
|
|
65
81
|
|
|
66
82
|
```text
|
|
67
83
|
delegate_flow({ units: [{ id, task, modelClass?, validation: [{ command, args }], review? }] })
|
|
68
84
|
delegate_flow_continue({ guidance, modelClass? })
|
|
69
85
|
```
|
|
70
86
|
|
|
71
|
-
|
|
87
|
+
```mermaid
|
|
88
|
+
flowchart LR
|
|
89
|
+
setup["Setup: resolve roles and create Unit Worktrees"] --> implement["Implementers in parallel"]
|
|
90
|
+
implement -->|Implementer block| repair["One repair: delegate_flow_continue"]
|
|
91
|
+
implement --> rebase["Rebase, inspect committed state, and validate"]
|
|
92
|
+
rebase -->|Post-rebase no-op| cleanup["Cleanup"]
|
|
93
|
+
rebase --> review{"Review requested?"}
|
|
94
|
+
review -->|No| integrate["Integrate in declared order"]
|
|
95
|
+
review -->|Yes| packet["Exact {base, tip, patchPath}; require PASS"]
|
|
96
|
+
packet -->|PASS| integrate
|
|
97
|
+
rebase -->|Validation block| repair
|
|
98
|
+
packet -->|Review block| repair
|
|
99
|
+
repair --> revalidate["Revalidate repaired work"]
|
|
100
|
+
revalidate -->|No review requested| integrate
|
|
101
|
+
revalidate -->|Review required| rereview["Re-review exact {base, tip, patchPath}; require PASS"]
|
|
102
|
+
rereview -->|PASS| integrate
|
|
103
|
+
revalidate -->|Second block| terminal["Terminal failure: retain worktree"]
|
|
104
|
+
rereview -->|Second block| terminal
|
|
105
|
+
rebase -->|Rebase or infrastructure failure| terminal
|
|
106
|
+
repair -->|Infrastructure failure| terminal
|
|
107
|
+
integrate --> cleanup
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Objective verification is authoritative. Flow always inspects committed Git state and runs declared validation.
|
|
111
|
+
|
|
112
|
+
A unit without `review` skips review evidence and Reviewer launch. It fast-forwards its exact validated tip through the existing guarded `git merge --ff-only` path.
|
|
113
|
+
|
|
114
|
+
Add `review` only for judgment that automation cannot establish. That unit keeps the exact `{base, tip, patchPath}` protocol and requires exact `PASS` before the same integration path.
|
|
115
|
+
|
|
116
|
+
Only one memory-only Flow may be active. At start, it resolves and freezes the effective `implementer` Role, including a same-named user override. It resolves and freezes the effective `reviewer` only when at least one unit requests `review`.
|
|
117
|
+
|
|
118
|
+
An omitted `modelClass` uses the local `pi-subagent/delegateTask` declaration, which defaults to `fast`. A selected class resolves through its shared profile model-and-thinking route for the unit's Implementer and, when applicable, Reviewer.
|
|
72
119
|
|
|
73
|
-
|
|
120
|
+
Flow creates one Unit Worktree per unit before it launches Implementers. It runs Implementers in parallel, then processes settled results in declared order.
|
|
74
121
|
|
|
75
|
-
|
|
122
|
+
After integration, it removes the worktree and branch non-forcibly. A refusal is a completion warning with the retained worktree path and/or branch.
|
|
76
123
|
|
|
77
|
-
|
|
124
|
+
A rebase that drops all unit commits is a no-op. Flow validates it, skips Reviewer and merge, then cleans up ordinarily.
|
|
125
|
+
|
|
126
|
+
Implementer, validation, or review blocks can be repaired once through `delegate_flow_continue` in the same worktree. An omitted continuation `modelClass` retains the blocked unit's current class. A supplied class replaces it for that one repair.
|
|
127
|
+
|
|
128
|
+
Rebase and infrastructure failures are terminal. A reported fast-forward failure completes with its diagnostic as a warning only when Git left Main clean at the exact integrated tip.
|
|
129
|
+
|
|
130
|
+
Otherwise it is terminal and retains the affected worktree. Flow has no graph, saved recovery, automatic retry, aggregate review, or post-merge gate.
|
|
131
|
+
|
|
132
|
+
`delegate_task` keeps its generic isolation behavior. Flow uses the package-shipped Implementer by default and the package-shipped Reviewer only when a unit requests review. Same-named user Roles remain supported overrides.
|
|
78
133
|
|
|
79
134
|
### Delegate UI summary
|
|
80
135
|
|
|
136
|
+
The transient status widget shows status glyph, role, status label, task summary, activity, and metrics for each child. Activity is `thinking…` or the active tool with elapsed time and path basename. Metrics are completed turns, started tools, model, thinking level, tokens, and total duration.
|
|
137
|
+
|
|
81
138
|
| Aspect | Behavior |
|
|
82
139
|
| --- | --- |
|
|
83
140
|
| Call label | `delegate_task · single/parallel/chain · N task(s)`; `delegate_flow · parallel→serial · N unit(s)`; `delegate_flow_continue · repair continuation` |
|
|
@@ -88,11 +145,19 @@ A rebase that drops all unit commits is a no-op: Flow validates it, skips Review
|
|
|
88
145
|
| Terminal retention | Active rows persist; terminal rows clear on next user input |
|
|
89
146
|
| Final result | Bounded summaries with recovery paths; no expanded view |
|
|
90
147
|
|
|
148
|
+
- Rows are active-first: working items first, then stable insertion order.
|
|
149
|
+
- The hard six-physical-line maximum shows all child rows for six or fewer items.
|
|
150
|
+
- Above six, it shows five child rows and one status-aware overflow line: `… N more · X working · Y complete · Z failed · W stopped`.
|
|
151
|
+
- Terminal rows clear on the next real user input. Active rows persist until the child settles.
|
|
152
|
+
- The final `delegate_task` block is deliberately minimal. It has bounded final summaries and role attribution for parallel and chain work. It shows only retained-worktree recovery paths.
|
|
153
|
+
|
|
91
154
|
## Config
|
|
92
155
|
|
|
93
|
-
pi-subagent owns the extension-named config directory `~/.pi/agent/config/pi-subagent
|
|
156
|
+
pi-subagent owns the extension-named config directory `~/.pi/agent/config/pi-subagent/`. It holds two kinds of user-owned configuration.
|
|
157
|
+
|
|
158
|
+
One Markdown file belongs to each Role (see [Roles](#roles)). The other is its own optional JSON file below.
|
|
94
159
|
|
|
95
|
-
`~/.pi/agent/config/pi-subagent/pi-subagent.json` controls the
|
|
160
|
+
`~/.pi/agent/config/pi-subagent/pi-subagent.json` controls the child pool and execution limits. All fields are optional. A missing file uses defaults.
|
|
96
161
|
|
|
97
162
|
| Field | Required | Possible values | Default |
|
|
98
163
|
| --- | --- | --- | --- |
|
|
@@ -101,9 +166,17 @@ pi-subagent owns the extension-named config directory `~/.pi/agent/config/pi-sub
|
|
|
101
166
|
| `timeout.idleMinutes` | No | Positive number of minutes where minutes × 60 000 ms ≤ 2,147,483,647 | `10` |
|
|
102
167
|
| `timeout.maxMinutes` | No | Positive number within the same ms cap that must be greater than `timeout.idleMinutes`, otherwise the whole `timeout` object falls back to defaults | `30` |
|
|
103
168
|
|
|
104
|
-
Excess children wait FIFO without consuming child timeout.
|
|
169
|
+
- Excess children wait FIFO without consuming child timeout.
|
|
170
|
+
- A terminal response on turn 50 succeeds.
|
|
171
|
+
- An attempted continuation starts no model work and rejects with `turn_limit`.
|
|
172
|
+
- Before a continuing turn, the child gets one warning when completed turns reach 80%.
|
|
173
|
+
- It gets another warning when elapsed time reaches 80% of the maximum runtime.
|
|
174
|
+
- If both thresholds are first reached together, the child gets one combined warning.
|
|
175
|
+
- `PI_SUBAGENT_MAX_SUBAGENTS` overrides `maxSubagents` for the session. It must be a positive integer. An invalid value prevents the extension from loading and leaves `delegate_task` unavailable.
|
|
105
176
|
|
|
106
|
-
This JSON is read leniently
|
|
177
|
+
This JSON is read leniently. Malformed JSON, a non-object root, unknown keys, or invalid values are collected into one warning.
|
|
178
|
+
|
|
179
|
+
The affected settings fall back to defaults. The file is never rewritten.
|
|
107
180
|
|
|
108
181
|
### Role frontmatter
|
|
109
182
|
|
|
@@ -119,14 +192,14 @@ Each Role `.md` file in the same directory accepts these frontmatter fields:
|
|
|
119
192
|
| `skills` | Yes | YAML array of non-empty Skill names | `[]` selects no separately named Role Skills; trusted extension Skills still load |
|
|
120
193
|
| body | Yes | System-prompt Markdown after the frontmatter | — |
|
|
121
194
|
|
|
122
|
-
An unreadable or invalid Role file fails role loading fast
|
|
195
|
+
An unreadable or invalid Role file fails role loading fast. Duplicate role names are rejected.
|
|
123
196
|
|
|
124
197
|
## Roles
|
|
125
198
|
|
|
126
|
-
The package ships two working built-in Roles
|
|
199
|
+
The package ships two working built-in Roles. They are always available without configuration.
|
|
127
200
|
|
|
128
201
|
- `implementer`: focused edits requesting worktree isolation; commits completed scoped changes locally and never pushes or opens PRs without authorization
|
|
129
|
-
- `reviewer`: read-only correctness review of supplied plans
|
|
202
|
+
- `reviewer`: read-only correctness review of supplied plans or files, or—only when a Flow unit declares `review`—of Flow's exact `{base, tip, patchPath}` packet in its Unit Worktree; never edits or commits
|
|
130
203
|
|
|
131
204
|
A same-named Markdown file in `~/.pi/agent/config/pi-subagent/` explicitly overrides the built-in default.
|
|
132
205
|
|
|
@@ -135,25 +208,58 @@ The repository also includes optional inert samples:
|
|
|
135
208
|
- [`scout`](./examples/roles/scout.md): read-only discovery
|
|
136
209
|
- [`synthesizer`](./examples/roles/synthesizer.md): reconcile supplied reports
|
|
137
210
|
|
|
138
|
-
Copy them manually from your installed `@henryqw/pi-subagent` package
|
|
211
|
+
Copy them manually from your installed `@henryqw/pi-subagent` package if you want a starting point. npm installs ship the `examples/roles/` directory.
|
|
139
212
|
|
|
140
213
|
```bash
|
|
141
214
|
mkdir -p ~/.pi/agent/config/pi-subagent
|
|
142
215
|
cp <package-install-dir>/examples/roles/scout.md ~/.pi/agent/config/pi-subagent/
|
|
143
216
|
```
|
|
144
217
|
|
|
145
|
-
Locate the install directory with `npm root` inside your project, or
|
|
218
|
+
Locate the install directory with `npm root` inside your project, or through Pi's package installation path.
|
|
146
219
|
|
|
147
|
-
The package never installs or writes Role configuration. Sample names are not built-ins
|
|
220
|
+
The package never installs or writes Role configuration. Sample names are not built-ins. After copying, edit or replace them as your own Roles.
|
|
148
221
|
|
|
149
222
|
## Skill
|
|
150
223
|
|
|
151
|
-
The bundled [`pi-subagent-delegated-development`](./skills/pi-subagent-delegated-development/SKILL.md) Skill is Main-side planner
|
|
224
|
+
The bundled [`pi-subagent-delegated-development`](./skills/pi-subagent-delegated-development/SKILL.md) Skill is Main-side planner and orchestrator policy only. `delegate_flow` owns its fixed Git mechanics and validation authority.
|
|
225
|
+
|
|
226
|
+
The Skill adds no runtime code, configuration, or Role installation. Generic orchestration remains outside the executor under [ADR 001](./docs/adr/001-composable-ephemeral-execution.md).
|
|
227
|
+
|
|
228
|
+
## Role, Skill, and resource trust
|
|
229
|
+
|
|
230
|
+
### Role resources
|
|
231
|
+
|
|
232
|
+
A Role explicitly owns base tools, extensions, named Skills, instructions, and optional `isolation: worktree`. Every launch installs its Role tool policy.
|
|
233
|
+
|
|
234
|
+
Named Skills resolve through Main's effective Pi Skill registry. Ambient extension and Skill discovery is disabled in children.
|
|
235
|
+
|
|
236
|
+
### Trusted extensions are not sandboxing
|
|
237
|
+
|
|
238
|
+
Selecting an extension explicitly selects a trusted atomic capability bundle, not just a provider path. Every tool it registers and every Skill supplied through its Pi package metadata or dynamic `resources_discover` loads alongside separately named Role Skills.
|
|
152
239
|
|
|
153
|
-
|
|
240
|
+
This is intentional. An extension may depend on its tools, Skills, lifecycle, and prompt behavior. Loading it permits that executable behavior and is not sandboxing.
|
|
241
|
+
|
|
242
|
+
Scope a child by selecting fewer trusted extensions. Finer-grained selection requires separate extension entry points or configuration, or an upstream split. pi-subagent does not infer or externally narrow undocumented dependencies.
|
|
243
|
+
|
|
244
|
+
### Tool filtering
|
|
245
|
+
|
|
246
|
+
- `tools: []` activates no base built-ins. Trusted selected extension tools and explicit caller tool additions still activate.
|
|
247
|
+
- `skills: []` selects no separately named Role Skills. Trusted selected extension Skills still load.
|
|
248
|
+
- `extensions: []` selects no Role extension bundle.
|
|
249
|
+
- Parent-only recursive orchestration tools and interactive `ask_question` are excluded from children.
|
|
250
|
+
- Explicit Role or caller tool names are verified against the child’s final filtered active registry after provider extensions finish `session_start`.
|
|
251
|
+
- Unavailable tool names fail before the first model turn with provider-extension guidance. Unavailable named Skills warn and skip.
|
|
154
252
|
|
|
155
253
|
## Library API
|
|
156
254
|
|
|
157
|
-
The package root exports Role loading and launch resolution, `createEphemeralSubagentExecutor`,
|
|
255
|
+
The package root exports Role loading and launch resolution, `createEphemeralSubagentExecutor`, and worktree helpers.
|
|
256
|
+
|
|
257
|
+
The executor is for code already running inside active Pi. It does not provide standalone Node.js Pi discovery or launch support.
|
|
258
|
+
|
|
259
|
+
It defaults to a hard limit of 50 turns. An attempted continuation rejects with `turn_limit` while preserving accumulated usage and bounded output.
|
|
260
|
+
|
|
261
|
+
After Pi exits, the executor drains inherited stdout and stderr normally. It destroys streams held by escaped descendants after a short inactivity deadline or one-second hard deadline, so they cannot retain a pool permit.
|
|
262
|
+
|
|
263
|
+
Use [`docs/orchestration.md`](./docs/orchestration.md#public-role-and-executor-api) for exact API behavior.
|
|
158
264
|
|
|
159
|
-
|
|
265
|
+
It includes a post-permit `prepare` example. The example uses `resolveRoleLaunch` with a caller-owned Model Task declaration against the latest Pi context.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { type HerdrExecutor } from "@henryqw/pi-herdr";
|
|
3
2
|
import { type AvailableModel, type ModelTask, type ProfileName, type ResolvedTaskRoute, type ThinkingLevel } from "@henryqw/pi-task-models";
|
|
4
3
|
export { addUsage, capEphemeralSubagentOutput, createEphemeralSubagentExecutor, DEFAULT_MAX_TURNS, EphemeralSubagentError, EXECUTION_BUDGET_ENV, formatDuration, type EphemeralSubagentActivityEvent, type EphemeralSubagentErrorCode, type EphemeralSubagentExecutor, type EphemeralSubagentExecutorOptions, type EphemeralSubagentResult, type EphemeralSubagentRunInput, type EphemeralSubagentTimeout, } from "./ephemeral.ts";
|
|
5
4
|
export { createChildWorktree, finalizeChildWorktree, inspectIndexFlags, inspectWorktreeDirty, WorktreeSetupError, worktreeContextNote, type WorktreeDirtyInspection, type WorktreeInfo, type WorktreePayload, } from "./worktree.ts";
|
|
@@ -60,40 +59,3 @@ export declare function resolveTaskRoute(ctx: ExtensionContext, profileName: Pro
|
|
|
60
59
|
export declare function resolveRoleSkills(pi: Pick<ExtensionAPI, "getCommands">, role: Role): ResolvedRoleSkills;
|
|
61
60
|
export declare function createRoleLaunch(pi: Pick<ExtensionAPI, "getCommands">, ctx: Pick<ExtensionContext, "isProjectTrusted">, input: CreateRoleLaunchInput): ResolvedRoleLaunch;
|
|
62
61
|
export declare function resolveRoleLaunch(pi: Pick<ExtensionAPI, "getCommands">, ctx: ExtensionContext, input: ResolveRoleLaunchInput): ResolvedRoleLaunch;
|
|
63
|
-
export interface ManagedSubagentHost {
|
|
64
|
-
cwd: string;
|
|
65
|
-
workspaceId: string;
|
|
66
|
-
}
|
|
67
|
-
export interface ManagedSubagentCommandOptions {
|
|
68
|
-
cwd: string;
|
|
69
|
-
}
|
|
70
|
-
export type ManagedSubagentExecutor = HerdrExecutor<ManagedSubagentCommandOptions>;
|
|
71
|
-
export interface ManagedSubagentHostOptions {
|
|
72
|
-
execute: ManagedSubagentExecutor;
|
|
73
|
-
delay?: (milliseconds: number) => Promise<void>;
|
|
74
|
-
}
|
|
75
|
-
export interface ManagedSubagentTab {
|
|
76
|
-
tabId: string;
|
|
77
|
-
paneId: string;
|
|
78
|
-
}
|
|
79
|
-
export declare function managedSubagentName(workspaceId: string, ...identity: string[]): string;
|
|
80
|
-
export declare function managedSubagentWorkspaceId(cwd: string, mainPane: string, options: ManagedSubagentHostOptions): Promise<string>;
|
|
81
|
-
/** Returns pane ID to Herdr status for agents owned by this workspace. */
|
|
82
|
-
export declare function listManagedSubagents(host: ManagedSubagentHost, options: ManagedSubagentHostOptions): Promise<Map<string, string>>;
|
|
83
|
-
export declare function createManagedSubagentTab(host: ManagedSubagentHost, cwd: string, launch: PiLaunch, label: string, options: ManagedSubagentHostOptions): Promise<ManagedSubagentTab>;
|
|
84
|
-
export declare function reconcileManagedSubagentTab(host: ManagedSubagentHost, input: {
|
|
85
|
-
tabId?: string;
|
|
86
|
-
paneId?: string;
|
|
87
|
-
cwd: string;
|
|
88
|
-
launch: PiLaunch;
|
|
89
|
-
label: string;
|
|
90
|
-
}, options: ManagedSubagentHostOptions): Promise<ManagedSubagentTab>;
|
|
91
|
-
export declare function findManagedSubagentTab(host: ManagedSubagentHost, label: string, options: ManagedSubagentHostOptions): Promise<ManagedSubagentTab | undefined>;
|
|
92
|
-
export declare function managedSubagentTabExists(host: ManagedSubagentHost, tabId: string, options: ManagedSubagentHostOptions): Promise<boolean>;
|
|
93
|
-
export declare function reconcileManagedSubagentPane(host: ManagedSubagentHost, tabId: string, rootPaneId: string, cwd: string, launch: PiLaunch, label: string, options: ManagedSubagentHostOptions): Promise<string>;
|
|
94
|
-
export declare function startManagedSubagent(host: ManagedSubagentHost, agent: string, pane: string, launch: PiLaunch, options: ManagedSubagentHostOptions, hooks?: {
|
|
95
|
-
beforeStart?: () => Promise<void>;
|
|
96
|
-
onStarted?: () => Promise<void>;
|
|
97
|
-
}): Promise<"existing" | "started">;
|
|
98
|
-
export declare function promptManagedSubagent(host: ManagedSubagentHost, agent: string, prompt: string | Record<string, unknown>, options: ManagedSubagentHostOptions): Promise<void>;
|
|
99
|
-
export declare function retireManagedSubagentTab(host: ManagedSubagentHost, tabId: string, options: ManagedSubagentHostOptions): Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
1
|
import { readFileSync, readdirSync } from "node:fs";
|
|
3
2
|
import { isAbsolute, join } from "node:path";
|
|
4
3
|
import { fileURLToPath } from "node:url";
|
|
5
4
|
import { getAgentDir, parseFrontmatter } from "@earendil-works/pi-coding-agent";
|
|
6
|
-
import { createHerdrClient, herdrCommandFailure, hasHerdrErrorCode, startPiAgent } from "@henryqw/pi-herdr";
|
|
7
5
|
import { modelReference, orderedProfileRoutes, readTaskModelsConfig, resolveConfiguredTaskRoute, resolveTaskModelRoute, } from "@henryqw/pi-task-models";
|
|
8
6
|
export { addUsage, capEphemeralSubagentOutput, createEphemeralSubagentExecutor, DEFAULT_MAX_TURNS, EphemeralSubagentError, EXECUTION_BUDGET_ENV, formatDuration, } from "./ephemeral.js";
|
|
9
7
|
export { createChildWorktree, finalizeChildWorktree, inspectIndexFlags, inspectWorktreeDirty, WorktreeSetupError, worktreeContextNote, } from "./worktree.js";
|
|
@@ -197,231 +195,3 @@ export function resolveRoleLaunch(pi, ctx, input) {
|
|
|
197
195
|
route: resolveConfiguredTaskRoute(ctx, input.task, input.agentDir),
|
|
198
196
|
});
|
|
199
197
|
}
|
|
200
|
-
function launchEnvironmentArgs(launch) {
|
|
201
|
-
return Object.entries(launch.env).flatMap(([key, value]) => {
|
|
202
|
-
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key) || typeof value !== "string" || value.includes("\0")) {
|
|
203
|
-
throw new Error(`Invalid launch environment: ${key}`);
|
|
204
|
-
}
|
|
205
|
-
return ["--env", `${key}=${value}`];
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
export function managedSubagentName(workspaceId, ...identity) {
|
|
209
|
-
const parts = [nonEmptyString(workspaceId, "Herdr workspace id"), ...identity.map((part, index) => nonEmptyString(part, `Subagent identity ${index}`))];
|
|
210
|
-
if (!identity.length)
|
|
211
|
-
throw new Error("Managed Subagent identity is required");
|
|
212
|
-
return `subagent-${createHash("sha256").update(JSON.stringify(parts)).digest("hex").slice(0, 23)}`;
|
|
213
|
-
}
|
|
214
|
-
export async function managedSubagentWorkspaceId(cwd, mainPane, options) {
|
|
215
|
-
const paneId = nonEmptyString(mainPane, "recorded main Herdr pane");
|
|
216
|
-
const pane = (await listPanes(cwd, options))
|
|
217
|
-
.map((entry, index) => object(entry, `Herdr pane ${index}`))
|
|
218
|
-
.find((entry) => entry.pane_id === paneId);
|
|
219
|
-
if (!pane)
|
|
220
|
-
throw new Error(`Recorded main Herdr pane is missing: ${paneId}`);
|
|
221
|
-
return nonEmptyString(pane.workspace_id, "recorded main Herdr workspace");
|
|
222
|
-
}
|
|
223
|
-
/** Returns pane ID to Herdr status for agents owned by this workspace. */
|
|
224
|
-
export async function listManagedSubagents(host, options) {
|
|
225
|
-
const workspaceId = nonEmptyString(host.workspaceId, "recorded Herdr workspace");
|
|
226
|
-
const response = object(await createHerdrClient(options.execute).json(["agent", "list"], { cwd: host.cwd }), "Herdr agent list response");
|
|
227
|
-
const result = object(response.result, "Herdr agent list result");
|
|
228
|
-
return new Map(array(result.agents, "Herdr agents").flatMap((entry, index) => {
|
|
229
|
-
const agent = object(entry, `Herdr agent ${index}`);
|
|
230
|
-
if (nonEmptyString(agent.workspace_id, `Herdr agent ${index} workspace`) !== workspaceId)
|
|
231
|
-
return [];
|
|
232
|
-
return [[
|
|
233
|
-
nonEmptyString(agent.pane_id, `Herdr agent ${index} pane`),
|
|
234
|
-
nonEmptyString(agent.agent_status, `Herdr agent ${index} status`),
|
|
235
|
-
]];
|
|
236
|
-
}));
|
|
237
|
-
}
|
|
238
|
-
export async function createManagedSubagentTab(host, cwd, launch, label, options) {
|
|
239
|
-
const response = await createHerdrClient(options.execute).json([
|
|
240
|
-
"tab", "create", "--workspace", nonEmptyString(host.workspaceId, "recorded Herdr workspace"), "--cwd", cwd,
|
|
241
|
-
...launchEnvironmentArgs(launch), "--label", nonEmptyString(label, "Herdr tab label"), "--no-focus",
|
|
242
|
-
], { cwd: host.cwd });
|
|
243
|
-
const result = object(object(response, "Herdr tab response").result, "Herdr tab result");
|
|
244
|
-
return {
|
|
245
|
-
tabId: nonEmptyString(object(result.tab, "Herdr tab").tab_id, "Herdr tab id"),
|
|
246
|
-
paneId: nonEmptyString(object(result.root_pane, "Herdr root pane").pane_id, "Herdr root pane id"),
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
export async function reconcileManagedSubagentTab(host, input, options) {
|
|
250
|
-
if (input.tabId && input.paneId && await managedSubagentTabExists(host, input.tabId, options)) {
|
|
251
|
-
return { tabId: input.tabId, paneId: input.paneId };
|
|
252
|
-
}
|
|
253
|
-
return await findManagedSubagentTab(host, input.label, options)
|
|
254
|
-
?? await createManagedSubagentTab(host, input.cwd, input.launch, input.label, options);
|
|
255
|
-
}
|
|
256
|
-
export async function findManagedSubagentTab(host, label, options) {
|
|
257
|
-
const workspaceId = nonEmptyString(host.workspaceId, "recorded Herdr workspace");
|
|
258
|
-
const matches = (await listTabs(host.cwd, options))
|
|
259
|
-
.map((entry, index) => object(entry, `Herdr tab ${index}`))
|
|
260
|
-
.filter((tab, index) => nonEmptyString(tab.workspace_id, `Herdr tab ${index} workspace`) === workspaceId)
|
|
261
|
-
.filter((tab) => tab.label === label);
|
|
262
|
-
if (matches.length > 1)
|
|
263
|
-
throw new Error(`Multiple Herdr tabs match provisioning identity: ${label}`);
|
|
264
|
-
if (!matches.length)
|
|
265
|
-
return undefined;
|
|
266
|
-
const tabId = nonEmptyString(object(matches[0], "Herdr tab").tab_id, "Herdr tab id");
|
|
267
|
-
const panes = (await listPanes(host.cwd, options))
|
|
268
|
-
.filter((entry, index) => object(entry, `Herdr pane ${index}`).tab_id === tabId);
|
|
269
|
-
if (panes.length !== 1)
|
|
270
|
-
throw new Error(`Provisioned Herdr tab ${tabId} must contain exactly one root pane`);
|
|
271
|
-
return { tabId, paneId: nonEmptyString(object(panes[0], "Herdr pane").pane_id, "Herdr pane id") };
|
|
272
|
-
}
|
|
273
|
-
export async function managedSubagentTabExists(host, tabId, options) {
|
|
274
|
-
const expected = nonEmptyString(host.workspaceId, "recorded Herdr workspace");
|
|
275
|
-
const id = nonEmptyString(tabId, "Herdr tab id");
|
|
276
|
-
const tab = (await listTabs(host.cwd, options))
|
|
277
|
-
.map((entry, index) => object(entry, `Herdr tab ${index}`))
|
|
278
|
-
.find((entry) => entry.tab_id === id);
|
|
279
|
-
if (!tab)
|
|
280
|
-
return false;
|
|
281
|
-
const actual = nonEmptyString(tab.workspace_id, `Herdr tab ${id} workspace`);
|
|
282
|
-
if (actual !== expected)
|
|
283
|
-
throw new Error(`Herdr tab ${id} belongs to workspace ${actual}, expected initiating workspace ${expected}`);
|
|
284
|
-
return true;
|
|
285
|
-
}
|
|
286
|
-
export async function reconcileManagedSubagentPane(host, tabId, rootPaneId, cwd, launch, label, options) {
|
|
287
|
-
const tab = nonEmptyString(tabId, "Herdr tab id");
|
|
288
|
-
const workspace = nonEmptyString(host.workspaceId, "recorded Herdr workspace");
|
|
289
|
-
const ownerTab = (await listTabs(host.cwd, options))
|
|
290
|
-
.map((entry, index) => object(entry, `Herdr tab ${index}`))
|
|
291
|
-
.find((entry) => entry.tab_id === tab);
|
|
292
|
-
if (!ownerTab)
|
|
293
|
-
throw new Error(`Herdr tab is missing: ${tab}`);
|
|
294
|
-
const ownerWorkspace = nonEmptyString(ownerTab.workspace_id, `Herdr tab ${tab} workspace`);
|
|
295
|
-
if (ownerWorkspace !== workspace)
|
|
296
|
-
throw new Error(`Herdr tab ${tab} belongs to workspace ${ownerWorkspace}, expected initiating workspace ${workspace}`);
|
|
297
|
-
const root = nonEmptyString(rootPaneId, "Herdr root pane");
|
|
298
|
-
const panes = (await listPanes(host.cwd, options)).map((entry, index) => object(entry, `Herdr pane ${index}`));
|
|
299
|
-
const owner = panes.find((pane) => pane.pane_id === root);
|
|
300
|
-
if (!owner)
|
|
301
|
-
throw new Error(`Herdr root pane is missing: ${root}`);
|
|
302
|
-
if (owner.tab_id !== tab)
|
|
303
|
-
throw new Error(`Herdr root pane ${root} does not belong to tab ${tab}`);
|
|
304
|
-
const siblings = panes.filter((pane) => pane.tab_id === tab && pane.pane_id !== root);
|
|
305
|
-
const named = siblings.filter((pane) => pane.label === label);
|
|
306
|
-
if (named.length > 1)
|
|
307
|
-
throw new Error(`Multiple Herdr panes match provisioning identity: ${label}`);
|
|
308
|
-
if (named.length)
|
|
309
|
-
return nonEmptyString(named[0].pane_id, "Herdr Subagent pane id");
|
|
310
|
-
if (siblings.length > 1)
|
|
311
|
-
throw new Error(`Provisioned Herdr tab ${tab} has multiple Subagent panes`);
|
|
312
|
-
if (siblings.length)
|
|
313
|
-
return nonEmptyString(siblings[0].pane_id, "Herdr Subagent pane id");
|
|
314
|
-
const herdr = createHerdrClient(options.execute);
|
|
315
|
-
const response = await herdr.json([
|
|
316
|
-
"pane", "split", "--pane", root, "--direction", "right", "--cwd", cwd,
|
|
317
|
-
...launchEnvironmentArgs(launch), "--no-focus",
|
|
318
|
-
], { cwd: host.cwd });
|
|
319
|
-
const result = object(object(response, "Herdr pane response").result, "Herdr pane result");
|
|
320
|
-
const pane = nonEmptyString(object(result.pane, "Herdr Subagent pane").pane_id, "Herdr Subagent pane id");
|
|
321
|
-
await herdr.run(["pane", "rename", pane, nonEmptyString(label, "Herdr pane label")], { cwd: host.cwd });
|
|
322
|
-
return pane;
|
|
323
|
-
}
|
|
324
|
-
export async function startManagedSubagent(host, agent, pane, launch, options, hooks = {}) {
|
|
325
|
-
assertAgentName(agent);
|
|
326
|
-
const name = nonEmptyString(agent, "Herdr agent name");
|
|
327
|
-
const paneId = nonEmptyString(pane, "Herdr agent pane");
|
|
328
|
-
const existing = await getManagedSubagent(host, name, options);
|
|
329
|
-
if (existing) {
|
|
330
|
-
assertAgentPane(name, paneId, existing);
|
|
331
|
-
return "existing";
|
|
332
|
-
}
|
|
333
|
-
await hooks.beforeStart?.();
|
|
334
|
-
const herdr = createHerdrClient(options.execute);
|
|
335
|
-
const startArgs = ["agent", "start", name, "--kind", "pi", "--pane", paneId, "--", ...launch.args];
|
|
336
|
-
const result = await startPiAgent(herdr, {
|
|
337
|
-
name,
|
|
338
|
-
pane: paneId,
|
|
339
|
-
args: launch.args,
|
|
340
|
-
options: { cwd: host.cwd },
|
|
341
|
-
delay: options.delay,
|
|
342
|
-
});
|
|
343
|
-
if (result.code === 0 && !result.killed) {
|
|
344
|
-
await hooks.onStarted?.();
|
|
345
|
-
return "started";
|
|
346
|
-
}
|
|
347
|
-
if (hasHerdrErrorCode(result, "agent_name_taken")) {
|
|
348
|
-
const raced = await getManagedSubagent(host, name, options);
|
|
349
|
-
if (!raced)
|
|
350
|
-
throw new Error(`Herdr agent ${name} reported agent_name_taken but could not be found; refusing to start a duplicate`);
|
|
351
|
-
assertAgentPane(name, paneId, raced);
|
|
352
|
-
return "existing";
|
|
353
|
-
}
|
|
354
|
-
throw new Error(herdrCommandFailure(startArgs, result));
|
|
355
|
-
}
|
|
356
|
-
export async function promptManagedSubagent(host, agent, prompt, options) {
|
|
357
|
-
assertAgentName(agent);
|
|
358
|
-
const text = typeof prompt === "string" ? nonEmptyString(prompt, "Subagent prompt") : JSON.stringify(prompt);
|
|
359
|
-
await createHerdrClient(options.execute).run(["agent", "prompt", agent, text], { cwd: host.cwd });
|
|
360
|
-
}
|
|
361
|
-
export async function retireManagedSubagentTab(host, tabId, options) {
|
|
362
|
-
const id = nonEmptyString(tabId, "Herdr tab id");
|
|
363
|
-
try {
|
|
364
|
-
if (!(await managedSubagentTabExists(host, id, options)))
|
|
365
|
-
return;
|
|
366
|
-
await createHerdrClient(options.execute).run(["tab", "close", id], { cwd: host.cwd });
|
|
367
|
-
}
|
|
368
|
-
catch (error) {
|
|
369
|
-
if (!(await confirmsTabAbsent(host, id, options)))
|
|
370
|
-
throw error;
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
function assertAgentName(agent) {
|
|
374
|
-
if (!/^[a-z][a-z0-9_-]{0,31}$/.test(agent))
|
|
375
|
-
throw new Error(`Invalid Herdr agent name: ${agent}`);
|
|
376
|
-
}
|
|
377
|
-
function object(value, label) {
|
|
378
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
379
|
-
throw new Error(`${label} must be an object`);
|
|
380
|
-
return value;
|
|
381
|
-
}
|
|
382
|
-
function array(value, label) {
|
|
383
|
-
if (!Array.isArray(value))
|
|
384
|
-
throw new Error(`${label} must be an array`);
|
|
385
|
-
return value;
|
|
386
|
-
}
|
|
387
|
-
function nonEmptyString(value, label) {
|
|
388
|
-
if (typeof value !== "string")
|
|
389
|
-
throw new Error(`${label} must be a string`);
|
|
390
|
-
if (!value.trim() || value.includes("\0"))
|
|
391
|
-
throw new Error(`${label} must not be empty`);
|
|
392
|
-
return value;
|
|
393
|
-
}
|
|
394
|
-
async function listTabs(cwd, options) {
|
|
395
|
-
const response = await createHerdrClient(options.execute).json(["tab", "list"], { cwd });
|
|
396
|
-
return array(object(object(response, "Herdr tab list response").result, "Herdr tab list result").tabs, "Herdr tabs");
|
|
397
|
-
}
|
|
398
|
-
async function listPanes(cwd, options) {
|
|
399
|
-
const response = await createHerdrClient(options.execute).json(["pane", "list"], { cwd });
|
|
400
|
-
return array(object(object(response, "Herdr pane list response").result, "Herdr pane list result").panes, "Herdr panes");
|
|
401
|
-
}
|
|
402
|
-
async function getManagedSubagent(host, name, options) {
|
|
403
|
-
const arguments_ = ["agent", "get", name];
|
|
404
|
-
const result = await createHerdrClient(options.execute).exec(arguments_, { cwd: host.cwd });
|
|
405
|
-
if (result.code !== 0 || result.killed) {
|
|
406
|
-
if (hasHerdrErrorCode(result, "agent_not_found"))
|
|
407
|
-
return undefined;
|
|
408
|
-
throw new Error(herdrCommandFailure(arguments_, result));
|
|
409
|
-
}
|
|
410
|
-
const response = object(JSON.parse(result.stdout), "Herdr agent get response");
|
|
411
|
-
return object(object(response.result, "Herdr agent get result").agent, `Herdr agent ${name}`);
|
|
412
|
-
}
|
|
413
|
-
function assertAgentPane(name, expected, agent) {
|
|
414
|
-
const actual = typeof agent.pane_id === "string" ? agent.pane_id : "missing";
|
|
415
|
-
if (actual !== expected) {
|
|
416
|
-
throw new Error(`Herdr agent name collision for ${name}: expected pane ${expected}, found ${actual}; refusing to reuse or replace it`);
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
async function confirmsTabAbsent(host, tabId, options) {
|
|
420
|
-
try {
|
|
421
|
-
const result = await createHerdrClient(options.execute).exec(["tab", "get", tabId], { cwd: host.cwd });
|
|
422
|
-
return !result.killed && result.code !== 0 && hasHerdrErrorCode(result, "tab_not_found");
|
|
423
|
-
}
|
|
424
|
-
catch {
|
|
425
|
-
return false;
|
|
426
|
-
}
|
|
427
|
-
}
|
package/docs/orchestration.md
CHANGED
|
@@ -262,8 +262,6 @@ Activity text is limited to 4 KiB per field. An invalid `toolCallId` or `toolNam
|
|
|
262
262
|
|
|
263
263
|
The low-level executor does not interpret `Role.isolation`, discover resources, compose modes, create shared state, or promote child failure outcomes to tool errors. A direct caller that wants worktrees must call `createChildWorktree` after the permit, choose the returned `cwd`, call `finalizeChildWorktree` on every exit path, and preserve its recovery payload.
|
|
264
264
|
|
|
265
|
-
Generic managed Herdr exports (`managedSubagentWorkspaceId`, reconciliation helpers, `startManagedSubagent`, prompting/listing, and retirement) consume the same launch policy for durable workers. They intentionally contain no workflow prompts, semantic state, or retry policy.
|
|
266
|
-
|
|
267
265
|
## JavaScript composition
|
|
268
266
|
|
|
269
267
|
The examples below use caller-selected `Role` objects and the `runRole` function returned by the package's initializer. A consuming Pi extension calls the initializer once at startup:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@henryqw/pi-subagent",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.0.0",
|
|
4
4
|
"description": "Delegate bounded single, parallel, or chained tasks to isolated Pi roles.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -14,14 +14,14 @@
|
|
|
14
14
|
},
|
|
15
15
|
"license": "MIT",
|
|
16
16
|
"files": [
|
|
17
|
+
"LICENSE",
|
|
17
18
|
"dist",
|
|
18
19
|
"extensions",
|
|
19
20
|
"docs",
|
|
20
21
|
"examples",
|
|
21
22
|
"skills",
|
|
22
23
|
"README.md",
|
|
23
|
-
"CONTEXT.md"
|
|
24
|
-
"LICENSE"
|
|
24
|
+
"CONTEXT.md"
|
|
25
25
|
],
|
|
26
26
|
"types": "./dist/index.d.ts",
|
|
27
27
|
"exports": {
|
|
@@ -64,7 +64,6 @@
|
|
|
64
64
|
]
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"@henryqw/pi-herdr": "^0.4.0",
|
|
68
67
|
"@henryqw/pi-multi-codex": "^0.3.8",
|
|
69
68
|
"@henryqw/pi-task-models": "^3.0.0"
|
|
70
69
|
}
|