@henryqw/pi-subagent 11.0.0 → 11.0.4
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 +67 -186
- package/dist/display-text.d.ts +5 -0
- package/dist/display-text.js +9 -0
- package/dist/ephemeral.js +2 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +10 -2
- package/docs/delegate-flow.html +202 -0
- package/docs/delegate-flow.svg +181 -0
- package/extensions/delegate-flow.ts +1 -2
- package/extensions/subagent.ts +1 -2
- package/extensions/task-name.ts +4 -6
- package/extensions/workflow.ts +3 -2
- package/package.json +1 -1
- package/extensions/delegation.ts +0 -9
package/README.md
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
# `@henryqw/pi-subagent`
|
|
2
2
|
|
|
3
|
-
Main
|
|
3
|
+
Main, the parent Pi session, can delegate bounded single, parallel, and chained tasks to isolated child Roles. It also provides a fixed Git Flow for independent implementation work.
|
|
4
4
|
|
|
5
5
|

|
|
6
6
|
|
|
7
7
|
## Why
|
|
8
8
|
|
|
9
9
|
- **Created for**: Delegate bounded work to isolated child Pi processes without losing Main's context.
|
|
10
|
-
- **Advantage**: Generic
|
|
10
|
+
- **Advantage**: Generic delegation and Git Flow use the same Role settings.
|
|
11
11
|
|
|
12
12
|
## Install
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
15
|
pi install npm:@henryqw/pi-task-models
|
|
16
|
-
pi install npm:@henryqw/pi-multi-codex
|
|
17
16
|
pi install npm:@henryqw/pi-subagent
|
|
18
17
|
```
|
|
19
18
|
|
|
@@ -21,249 +20,131 @@ pi install npm:@henryqw/pi-subagent
|
|
|
21
20
|
|
|
22
21
|
| Package | Why |
|
|
23
22
|
| --- | --- |
|
|
24
|
-
| `@henryqw/pi-
|
|
25
|
-
| `@henryqw/pi-task-models` | Required. Shared `fast` / `balanced` / `frontier` / `fav` routes. |
|
|
23
|
+
| `@henryqw/pi-task-models` | Required. Supplies `fast`, `balanced`, `frontier`, and `fav` model routes. |
|
|
26
24
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
The local config is optional and uses defaults quietly when missing. If shared task-model config is missing, pi-subagent warns once at session start because delegation needs a route.
|
|
25
|
+
Routes come from `~/.pi/agent/config/pi-task-models/config.json`. It stores explicit task overrides. An omitted class uses pi-subagent's `pi-subagent/delegateTask` default, `fast`. Missing shared model config warns once because delegation needs a route.
|
|
30
26
|
|
|
31
27
|
## Use
|
|
32
28
|
|
|
33
|
-
|
|
|
34
|
-
| --- | --- |
|
|
35
|
-
| `delegate_task` |
|
|
36
|
-
| `delegate_flow` |
|
|
37
|
-
| `delegate_flow_continue` |
|
|
38
|
-
|
|
39
|
-
All three delegation tools use Pi's built-in renderer. Its tool-execution block shows live updates and Pi's configured expansion hint; expanding shows the call and result content.
|
|
40
|
-
|
|
41
|
-
### `delegate_task`
|
|
29
|
+
| Tool | Purpose |
|
|
30
|
+
| --- | --- |
|
|
31
|
+
| `delegate_task` | Run one bounded task, independent tasks in parallel, or dependent tasks in a chain. |
|
|
32
|
+
| `delegate_flow` | Implement and integrate 1–8 independent Git units. |
|
|
33
|
+
| `delegate_flow_continue` | Repair the one blocked Flow unit once. |
|
|
42
34
|
|
|
43
|
-
Select exactly one shape:
|
|
35
|
+
Pi's built-in tool block shows each call and result. Select exactly one `delegate_task` shape:
|
|
44
36
|
|
|
45
37
|
```text
|
|
46
38
|
// Single
|
|
47
39
|
{ role, name, task, model?, modelClass?, background? }
|
|
48
40
|
|
|
49
|
-
// Parallel: 1–8 independent
|
|
41
|
+
// Parallel: 1–8 independent tasks
|
|
50
42
|
{ tasks: [{ role, name, task, model?, modelClass? }], background? }
|
|
51
43
|
|
|
52
|
-
// Chain: 1–8 dependent
|
|
44
|
+
// Chain: 1–8 dependent tasks
|
|
53
45
|
{ chain: [{ role, name, task, model?, modelClass? }], background? }
|
|
54
46
|
```
|
|
55
47
|
|
|
56
|
-
Main supplies
|
|
57
|
-
|
|
58
|
-
#### Model routing
|
|
48
|
+
Main supplies each `name`. It must be a short description, about five words and fewer than 30 characters, with no C0/C1 control characters such as newlines or terminal escapes. `role` and an explicit `model` also reject those controls.
|
|
59
49
|
|
|
60
|
-
|
|
61
|
-
2. An omitted class uses pi-subagent's `pi-subagent/delegateTask` declaration. Its default is `fast`.
|
|
62
|
-
3. The route supplies the model and exact thinking level.
|
|
63
|
-
4. An explicit `model` is `provider/modelId`. It replaces only the route model and must support its thinking level.
|
|
50
|
+
`modelClass` selects `fast`, `balanced`, `frontier`, or `fav`. The route sets the model and exact thinking level. An explicit `model` (`provider/modelId`) replaces only the route model and must support that thinking level. `background` applies to the whole selected mode, never one entry.
|
|
64
51
|
|
|
65
|
-
`
|
|
52
|
+
Parallel tasks start together, settle together, and report in input order. Chains are sequential and fail at the first failure. `{previous}` passes only the immediately preceding successful assistant output. Foreground failures throw after keeping bounded sibling and recovery evidence.
|
|
66
53
|
|
|
67
|
-
|
|
54
|
+
One call has one aggregate 50 KiB cap for Main-visible text. Background work belongs to its launching session; shutdown or reload aborts it and may leave only recoverable-work evidence or no follow-up message.
|
|
68
55
|
|
|
69
|
-
|
|
56
|
+
Each entry resolves its own Role, resources, route, and optional isolation. A Role with `isolation: worktree` gets a separate deterministic worktree when available. Non-Git and unborn-`HEAD` contexts can use Main's directory. Other setup failures, including unsafe submodule layouts, reject instead of falling back. Siblings and chain steps never share a created worktree.
|
|
70
57
|
|
|
71
|
-
|
|
72
|
-
- Chain mode is sequential and fail-fast. Each literal `{previous}` receives only the immediately preceding successful assistant output.
|
|
73
|
-
- Foreground failures throw after retaining bounded sibling and recovery evidence.
|
|
74
|
-
- One tool call has one aggregate 50 KiB Main-visible transport cap. It is not 50 KiB per child.
|
|
75
|
-
- Background workflows are session-scoped. Session shutdown or reload aborts them and may deliver only recoverable-work evidence or no follow-up message.
|
|
76
|
-
- Each delegation resolves its own Role, resources, route, and optional worktree request.
|
|
77
|
-
- When available, `isolation: worktree` gives each entry a deterministic separate worktree. Non-Git or unborn-`HEAD` contexts may use Main's cwd.
|
|
78
|
-
- Siblings and chain steps never implicitly share one created worktree.
|
|
58
|
+
See the [orchestration guide](./docs/orchestration.md) for full delegation, transport, isolation, and UI behavior.
|
|
79
59
|
|
|
80
|
-
|
|
60
|
+
### Flow
|
|
81
61
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
Use Flow only for independent, commuting Git changes. Commuting changes can integrate in any order.
|
|
85
|
-
|
|
86
|
-
Flow accepts 1–8 uniquely identified units. Each unit has a required Main-supplied short `name`, bounded task, optional `modelClass`, direct command/argument validation gate, and optional non-empty `review` judgment criterion. Names must not contain C0/C1 control characters, including newlines and terminal escape characters.
|
|
62
|
+
Flow requires a clean Main worktree on an attached branch with a committed `HEAD`. Use it only for independent Git changes that can merge in any order. Do not split units that overlap files, APIs, schemas, generated output, package metadata, lockfiles, or invariants.
|
|
87
63
|
|
|
88
64
|
```text
|
|
89
65
|
delegate_flow({ units: [{ id, name, task, modelClass?, validation: [{ command, args }], review? }] })
|
|
90
66
|
delegate_flow_continue({ guidance, modelClass? })
|
|
91
67
|
```
|
|
92
68
|
|
|
93
|
-
|
|
94
|
-
flowchart LR
|
|
95
|
-
setup["Setup: resolve roles and create Unit Worktrees"] --> implement["Implementers in parallel"]
|
|
96
|
-
implement -->|Implementer block| repair["One repair: delegate_flow_continue"]
|
|
97
|
-
implement --> rebase["Rebase, inspect committed state, and validate"]
|
|
98
|
-
rebase -->|Post-rebase no-op| cleanup["Cleanup"]
|
|
99
|
-
rebase --> review{"Review requested?"}
|
|
100
|
-
review -->|No| integrate["Integrate in declared order"]
|
|
101
|
-
review -->|Yes| packet["Exact {base, tip, patchPath}; require PASS"]
|
|
102
|
-
packet -->|PASS| integrate
|
|
103
|
-
rebase -->|Validation block| repair
|
|
104
|
-
packet -->|Review block| repair
|
|
105
|
-
repair --> revalidate["Revalidate repaired work"]
|
|
106
|
-
revalidate -->|No review requested| integrate
|
|
107
|
-
revalidate -->|Review required| rereview["Re-review exact {base, tip, patchPath}; require PASS"]
|
|
108
|
-
rereview -->|PASS| integrate
|
|
109
|
-
revalidate -->|Second block| terminal["Terminal failure: retain worktree"]
|
|
110
|
-
rereview -->|Second block| terminal
|
|
111
|
-
rebase -->|Rebase or infrastructure failure| terminal
|
|
112
|
-
repair -->|Infrastructure failure| terminal
|
|
113
|
-
integrate --> cleanup
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
Objective verification is authoritative. Flow always inspects committed Git state and runs declared validation.
|
|
117
|
-
|
|
118
|
-
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.
|
|
119
|
-
|
|
120
|
-
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.
|
|
69
|
+
A Flow has 1–8 units with unique non-empty IDs. It is memory-only and allows one active Flow. It freezes the effective Implementer at start and freezes the Reviewer only when a unit requests `review`.
|
|
121
70
|
|
|
122
|
-
|
|
71
|
+
- Each unit gets one worktree. Implementers run in parallel. Flow handles units in declared order.
|
|
72
|
+
- Flow runs each declared command with its arguments. That validation is authoritative for objective checks.
|
|
73
|
+
- Without `review`, Flow fast-forwards the exact validated tip.
|
|
74
|
+
- With `review`, the Reviewer receives the exact `{base, tip, patchPath}` packet and must return exactly `PASS` before the same integration path. Use `review` only for stated judgment that validation cannot decide.
|
|
123
75
|
|
|
124
|
-
|
|
76
|
+
One `delegate_flow_continue` can repair an Implementer, validation, or review block in the same worktree. Omitting its class keeps the unit's class; supplying one changes that repair only. A second block is terminal. Rebase and infrastructure failures are terminal. Flow has no dependency graph, saved recovery, automatic retry, aggregate review, or post-merge validation.
|
|
125
77
|
|
|
126
|
-
Flow
|
|
127
|
-
|
|
128
|
-
After integration, it removes the worktree and branch non-forcibly. A refusal is a completion warning with the retained worktree path and/or branch.
|
|
129
|
-
|
|
130
|
-
A rebase that drops all unit commits is a no-op. Flow validates it, skips Reviewer and merge, then cleans up ordinarily.
|
|
131
|
-
|
|
132
|
-
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.
|
|
133
|
-
|
|
134
|
-
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.
|
|
135
|
-
|
|
136
|
-
Otherwise it is terminal and retains the affected worktree. Flow has no graph, saved recovery, automatic retry, aggregate review, or post-merge gate.
|
|
137
|
-
|
|
138
|
-
`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; the built-in Scout is not part of Flow. Same-named user Roles remain supported overrides.
|
|
139
|
-
|
|
140
|
-
### Delegate UI summary
|
|
141
|
-
|
|
142
|
-
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.
|
|
143
|
-
|
|
144
|
-
| Aspect | Behavior |
|
|
145
|
-
| --- | --- |
|
|
146
|
-
| Tool-execution block | Pi's built-in renderer: live updates and expandable call/result content |
|
|
147
|
-
| Widget rows | One line per child: glyph, `[role initial]`, short name, activity, metrics |
|
|
148
|
-
| Ordering | Active-first stable (working first, then insertion order) |
|
|
149
|
-
| Line cap | 6 physical lines max (≤6 items: all child rows; >6 items: 5 rows + 1 status-aware overflow) |
|
|
150
|
-
| Terminal retention | Active rows persist; terminal rows clear on next user input |
|
|
151
|
-
|
|
152
|
-
- Rows are active-first: working items first, then stable insertion order.
|
|
153
|
-
- The hard six-physical-line maximum shows all child rows for six or fewer items.
|
|
154
|
-
- Above six, it shows five child rows and one status-aware overflow line: `… N more · X working · Y complete · Z failed · W stopped`.
|
|
155
|
-
- Terminal rows clear on the next real user input. Active rows persist until the child settles.
|
|
156
|
-
- 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.
|
|
78
|
+
Flow never force-deletes recoverable work. Failed or uncertain units, and cleanup refusals after integration, retain their worktree path or branch for recovery. See [Flow mechanics and recovery](./docs/orchestration.md#delegate_flow).
|
|
157
79
|
|
|
158
80
|
## Config
|
|
159
81
|
|
|
160
|
-
pi-subagent owns
|
|
161
|
-
|
|
162
|
-
One Markdown file belongs to each Role (see [Roles](#roles)). The other is its own optional JSON file below.
|
|
82
|
+
pi-subagent owns `~/.pi/agent/config/pi-subagent/config.json`. It is optional. A missing file uses these defaults without a warning.
|
|
163
83
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
|
167
|
-
|
|
|
168
|
-
| `
|
|
169
|
-
| `
|
|
170
|
-
| `timeout.idleMinutes` | No | Positive number of minutes where minutes × 60 000 ms ≤ 2,147,483,647 | `10` |
|
|
171
|
-
| `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` |
|
|
172
|
-
|
|
173
|
-
- Excess children wait FIFO without consuming child timeout.
|
|
174
|
-
- A terminal response on turn 50 succeeds.
|
|
175
|
-
- An attempted continuation starts no model work and rejects with `turn_limit`.
|
|
176
|
-
- Before a continuing turn, the child gets one warning when completed turns reach 80%.
|
|
177
|
-
- It gets another warning when elapsed time reaches 80% of the maximum runtime.
|
|
178
|
-
- If both thresholds are first reached together, the child gets one combined warning.
|
|
179
|
-
- `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.
|
|
180
|
-
|
|
181
|
-
This JSON is read leniently. Malformed JSON, a non-object root, unknown keys, or invalid values are collected into one warning.
|
|
182
|
-
|
|
183
|
-
The affected settings fall back to defaults. The file is never rewritten.
|
|
184
|
-
|
|
185
|
-
### Role frontmatter
|
|
84
|
+
| Field | Valid value | Default |
|
|
85
|
+
| --- | --- | --- |
|
|
86
|
+
| `maxSubagents` | Safe integer ≥ 1 | `5` |
|
|
87
|
+
| `maxTurns` | Safe integer ≥ 1 | `50` |
|
|
88
|
+
| `timeout.idleMinutes` | Positive minutes; minutes × 60,000 ≤ 2,147,483,647 ms | `10` |
|
|
89
|
+
| `timeout.maxMinutes` | Positive minutes greater than `idleMinutes`; minutes × 60,000 ≤ 2,147,483,647 ms | `30` |
|
|
186
90
|
|
|
187
|
-
|
|
91
|
+
Excess children wait FIFO without using a child timeout. A terminal response on turn 50 succeeds; an attempted continuation rejects with `turn_limit`.
|
|
188
92
|
|
|
189
|
-
|
|
190
|
-
| --- | --- | --- | --- |
|
|
191
|
-
| `name` | Yes | Non-empty text; unique across roles | — |
|
|
192
|
-
| `description` | Yes | Non-empty text | — |
|
|
193
|
-
| `tools` | Yes | YAML array of non-empty tool names | `[]` activates no base built-ins; trusted extension tools and caller additions still activate |
|
|
194
|
-
| `isolation` | No | `worktree` | None |
|
|
195
|
-
| `extensions` | Yes | YAML array of absolute paths, `~/…`, `file://`, or package sources (`npm:`, `git:`, `github:`, `https?:`, `ssh:`) | `[]` selects no Role extension bundle |
|
|
196
|
-
| `skills` | Yes | YAML array of non-empty Skill names | `[]` selects no separately named Role Skills; trusted extension Skills still load |
|
|
197
|
-
| body | Yes | System-prompt Markdown after the frontmatter | — |
|
|
93
|
+
Malformed or unreadable JSON, a non-object root, unknown keys, and invalid values produce one warning. Invalid settings use defaults while valid settings still apply. If the effective maximum is not greater than the idle timeout, both timeout settings use defaults. The file is never rewritten.
|
|
198
94
|
|
|
199
|
-
|
|
95
|
+
`PI_SUBAGENT_MAX_SUBAGENTS` overrides `maxSubagents` for the session. It must be a positive integer. An invalid value prevents the extension from loading, so `delegate_task` is unavailable.
|
|
200
96
|
|
|
201
97
|
## Roles
|
|
202
98
|
|
|
203
|
-
|
|
99
|
+
Role Markdown files live beside the config file. They require frontmatter and a Markdown system prompt body.
|
|
204
100
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
101
|
+
| Field | Requirement |
|
|
102
|
+
| --- | --- |
|
|
103
|
+
| `name` | Required unique, non-empty text without C0/C1 controls. |
|
|
104
|
+
| `description` | Required non-empty text without C0/C1 controls. |
|
|
105
|
+
| `tools` | Required YAML array of non-empty tool names. `[]` selects no base built-ins. |
|
|
106
|
+
| `isolation` | Optional; only `worktree`. |
|
|
107
|
+
| `extensions` | Required YAML array. Entries are absolute paths, `~/…`, `file://`, or `npm:`, `git:`, `github:`, `https?:`, or `ssh:` sources. |
|
|
108
|
+
| `skills` | Required YAML array of non-empty Skill names. |
|
|
109
|
+
| body | Required Markdown system prompt after the frontmatter. |
|
|
110
|
+
|
|
111
|
+
An unreadable or invalid Role fails loading fast. Duplicate Role names are rejected. A same-named user file overrides a built-in Role.
|
|
208
112
|
|
|
209
|
-
|
|
113
|
+
The package always provides these built-in Roles:
|
|
210
114
|
|
|
211
|
-
|
|
115
|
+
| Role | Purpose | Isolation/use |
|
|
116
|
+
| --- | --- | --- |
|
|
117
|
+
| `implementer` | Make and validate one focused change. | Requests a worktree; commits scoped work locally. Never pushes or opens a PR without permission. |
|
|
118
|
+
| `reviewer` | Review supplied plans or files for correctness. | Read-only. For Flow review, reads the exact packet in the Unit Worktree; never edits or commits. |
|
|
119
|
+
| `scout` | Map code and evidence for one bounded task. | Read-only and generic `delegate_task` only; never changes files. |
|
|
212
120
|
|
|
213
|
-
|
|
121
|
+
Flow uses the effective Implementer and, only when requested, Reviewer. The Scout is not part of Flow.
|
|
214
122
|
|
|
215
|
-
|
|
123
|
+
[`synthesizer`](./examples/roles/synthesizer.md) is an optional sample for reconciling supplied reports. It does nothing until you copy it:
|
|
216
124
|
|
|
217
125
|
```bash
|
|
218
126
|
mkdir -p ~/.pi/agent/config/pi-subagent
|
|
219
127
|
cp <package-install-dir>/examples/roles/synthesizer.md ~/.pi/agent/config/pi-subagent/
|
|
220
128
|
```
|
|
221
129
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
The package never installs or writes Role configuration. After copying, edit or replace `synthesizer.md` as your own Role.
|
|
225
|
-
|
|
226
|
-
## Skill
|
|
227
|
-
|
|
228
|
-
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.
|
|
229
|
-
|
|
230
|
-
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).
|
|
231
|
-
|
|
232
|
-
## Role, Skill, and resource trust
|
|
233
|
-
|
|
234
|
-
### Role resources
|
|
235
|
-
|
|
236
|
-
A Role explicitly owns base tools, extensions, named Skills, instructions, and optional `isolation: worktree`. Every launch installs its Role tool policy.
|
|
130
|
+
Use `npm root` in a project to find the package install directory. The package never installs or changes this file; after copying, it is yours.
|
|
237
131
|
|
|
238
|
-
|
|
132
|
+
## Role resources and trust
|
|
239
133
|
|
|
240
|
-
|
|
134
|
+
A Role selects base tools, extensions, named Skills, instructions, and optional worktree isolation. Named Skills resolve from Main's effective Pi registry; unavailable ones warn and skip.
|
|
241
135
|
|
|
242
|
-
|
|
136
|
+
Children disable ambient extension and Skill discovery. `tools: []` adds no base tools, but selected extension tools and caller tools still activate. `extensions: []` adds no Role extension bundle. `skills: []` adds no separately named Role Skills, but selected extension Skills still load.
|
|
243
137
|
|
|
244
|
-
|
|
138
|
+
An explicitly selected extension is trusted, not sandboxed. Its tools, Skills, and executable behavior load together. Select fewer trusted extensions to reduce scope. pi-subagent does not guess or remove undocumented dependencies.
|
|
245
139
|
|
|
246
|
-
|
|
140
|
+
Parent-only delegation tools and `ask_question` are always excluded. Requested Role or caller tool names are checked after provider loading; unavailable tools fail before the first model turn.
|
|
247
141
|
|
|
248
|
-
|
|
142
|
+
## Skill
|
|
249
143
|
|
|
250
|
-
|
|
251
|
-
- `skills: []` selects no separately named Role Skills. Trusted selected extension Skills still load.
|
|
252
|
-
- `extensions: []` selects no Role extension bundle.
|
|
253
|
-
- Parent-only recursive orchestration tools and interactive `ask_question` are excluded from children.
|
|
254
|
-
- Explicit Role or caller tool names are verified against the child’s final filtered active registry after provider extensions finish `session_start`.
|
|
255
|
-
- Unavailable tool names fail before the first model turn with provider-extension guidance. Unavailable named Skills warn and skip.
|
|
144
|
+
The bundled [`pi-subagent-delegated-development`](./skills/pi-subagent-delegated-development/SKILL.md) Skill guides Main's planning and orchestration. It adds no runtime code, config, or Role installation. `delegate_flow` owns its Git mechanics and validation authority.
|
|
256
145
|
|
|
257
146
|
## Library API
|
|
258
147
|
|
|
259
|
-
The package root exports
|
|
260
|
-
|
|
261
|
-
The executor is for code already running inside active Pi. It does not provide standalone Node.js Pi discovery or launch support.
|
|
262
|
-
|
|
263
|
-
It defaults to a hard limit of 50 turns. An attempted continuation rejects with `turn_limit` while preserving accumulated usage and bounded output.
|
|
264
|
-
|
|
265
|
-
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.
|
|
266
|
-
|
|
267
|
-
Use [`docs/orchestration.md`](./docs/orchestration.md#public-role-and-executor-api) for exact API behavior.
|
|
148
|
+
The package root exports `loadRoles`, `resolveRoleSkills`, `resolveRoleLaunch`, `createRoleLaunch`, `createEphemeralSubagentExecutor`, and worktree helpers. The executor works only inside the active Pi process; it does not discover or start a standalone Node.js Pi installation.
|
|
268
149
|
|
|
269
|
-
|
|
150
|
+
See the [public Role and executor API](./docs/orchestration.md#public-role-and-executor-api) for contracts and a `prepare` example.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const C0_C1_CONTROL_RANGES = "\\u0000-\\u001F\\u007F-\\u009F";
|
|
2
|
+
export const DISPLAY_TEXT_CONTRACT = {
|
|
3
|
+
controlRanges: C0_C1_CONTROL_RANGES,
|
|
4
|
+
pattern: `^(?![\\s\\S]*[${C0_C1_CONTROL_RANGES}])[\\s\\S]+$`,
|
|
5
|
+
};
|
|
6
|
+
const DISPLAY_TEXT_CONTROL_PATTERN = new RegExp(`[${DISPLAY_TEXT_CONTRACT.controlRanges}]`, "u");
|
|
7
|
+
export function hasDisplayControlCharacters(value) {
|
|
8
|
+
return DISPLAY_TEXT_CONTROL_PATTERN.test(value);
|
|
9
|
+
}
|
package/dist/ephemeral.js
CHANGED
|
@@ -2,6 +2,7 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { basename } from "node:path";
|
|
4
4
|
import { StringDecoder } from "node:string_decoder";
|
|
5
|
+
import { hasDisplayControlCharacters } from "./display-text.js";
|
|
5
6
|
const MAX_OUTPUT_BYTES = 50 * 1024;
|
|
6
7
|
const MAX_JSON_EVENT_BYTES = 1024 * 1024;
|
|
7
8
|
export const DEFAULT_MAX_TURNS = 50;
|
|
@@ -249,10 +250,7 @@ function activityTooLong(value) {
|
|
|
249
250
|
return typeof value === "string" && Buffer.byteLength(value, "utf8") > MAX_ACTIVITY_TEXT_BYTES;
|
|
250
251
|
}
|
|
251
252
|
function hasTerminalControlChars(text) {
|
|
252
|
-
return
|
|
253
|
-
const code = character.codePointAt(0);
|
|
254
|
-
return code <= 0x1f || code >= 0x7f && code <= 0x9f || code === 0x2028 || code === 0x2029;
|
|
255
|
-
});
|
|
253
|
+
return hasDisplayControlCharacters(text) || /[\u2028\u2029]/u.test(text);
|
|
256
254
|
}
|
|
257
255
|
function activityText(value) {
|
|
258
256
|
return typeof value === "string" && value.trim().length > 0 && !activityTooLong(value) && !hasTerminalControlChars(value);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { type AvailableModel, type ModelTask, type ProfileName, type ResolvedTaskRoute, type ThinkingLevel } from "@henryqw/pi-task-models";
|
|
3
|
+
export { DISPLAY_TEXT_CONTRACT, hasDisplayControlCharacters } from "./display-text.ts";
|
|
3
4
|
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";
|
|
4
5
|
export { createChildWorktree, finalizeChildWorktree, inspectIndexFlags, inspectWorktreeDirty, WorktreeSetupError, worktreeContextNote, type WorktreeDirtyInspection, type WorktreeInfo, type WorktreePayload, } from "./worktree.ts";
|
|
5
6
|
export { prepareExactReviewEvidence, REVIEW_MAX_PATCH_BYTES, REVIEW_MAX_PATHS, type PreparedReviewEvidence, type PrepareExactReviewEvidenceInput, } from "./review-evidence.ts";
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,9 @@ import { isAbsolute, join } from "node:path";
|
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { getAgentDir, parseFrontmatter } from "@earendil-works/pi-coding-agent";
|
|
5
5
|
import { extensionConfigDir } from "@henryqw/pi-config-store";
|
|
6
|
+
import { hasDisplayControlCharacters } from "./display-text.js";
|
|
6
7
|
import { loadTaskModelsConfig, modelReference, orderedProfileRoutes, resolveConfiguredTaskRoute, resolveTaskModelRoute, } from "@henryqw/pi-task-models";
|
|
8
|
+
export { DISPLAY_TEXT_CONTRACT, hasDisplayControlCharacters } from "./display-text.js";
|
|
7
9
|
export { addUsage, capEphemeralSubagentOutput, createEphemeralSubagentExecutor, DEFAULT_MAX_TURNS, EphemeralSubagentError, EXECUTION_BUDGET_ENV, formatDuration, } from "./ephemeral.js";
|
|
8
10
|
export { createChildWorktree, finalizeChildWorktree, inspectIndexFlags, inspectWorktreeDirty, WorktreeSetupError, worktreeContextNote, } from "./worktree.js";
|
|
9
11
|
export { prepareExactReviewEvidence, REVIEW_MAX_PATCH_BYTES, REVIEW_MAX_PATHS, } from "./review-evidence.js";
|
|
@@ -26,6 +28,12 @@ const cleanText = (value, field, source) => {
|
|
|
26
28
|
}
|
|
27
29
|
return value.trim();
|
|
28
30
|
};
|
|
31
|
+
const cleanDisplayText = (value, field, source) => {
|
|
32
|
+
if (typeof value === "string" && hasDisplayControlCharacters(value)) {
|
|
33
|
+
throw new Error(`${source}: ${field} must not contain C0/C1 control characters.`);
|
|
34
|
+
}
|
|
35
|
+
return cleanText(value, field, source);
|
|
36
|
+
};
|
|
29
37
|
const stringList = (value, field, source) => {
|
|
30
38
|
if (value === undefined)
|
|
31
39
|
throw new Error(`${source}: ${field} is required.`);
|
|
@@ -62,8 +70,8 @@ function parseRoleFile(file, raw) {
|
|
|
62
70
|
if (isolation !== undefined && isolation !== "worktree")
|
|
63
71
|
throw new Error(`${file}: isolation must be "worktree".`);
|
|
64
72
|
return {
|
|
65
|
-
name:
|
|
66
|
-
description:
|
|
73
|
+
name: cleanDisplayText(frontmatter.name, "name", file),
|
|
74
|
+
description: cleanDisplayText(frontmatter.description, "description", file),
|
|
67
75
|
tools: stringList(frontmatter.tools, "tools", file),
|
|
68
76
|
isolation,
|
|
69
77
|
extensions: extensionList(frontmatter.extensions, file),
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Delegate Flow lifecycle</title>
|
|
7
|
+
<link href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
|
8
|
+
<style>
|
|
9
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
10
|
+
body { min-height: 100vh; display: grid; place-items: center; padding: 32px; background: #f0eee9; color: #101828; }
|
|
11
|
+
.frame { width: min(1280px, 100%); }
|
|
12
|
+
.eyebrow { margin-bottom: 8px; color: #4c5665; font: 500 8px/1 'Geist Mono', monospace; letter-spacing: .18em; text-transform: uppercase; }
|
|
13
|
+
h1 { margin-bottom: 24px; font: 600 28px/1.15 'Geist', sans-serif; letter-spacing: -.03em; }
|
|
14
|
+
svg { display: block; width: 100%; min-width: 960px; }
|
|
15
|
+
</style>
|
|
16
|
+
</head>
|
|
17
|
+
<body>
|
|
18
|
+
<main class="frame">
|
|
19
|
+
<p class="eyebrow">Flowchart · @henryqw/pi-subagent</p>
|
|
20
|
+
<h1>Delegate Flow lifecycle</h1>
|
|
21
|
+
<svg viewBox="0 0 1280 720" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="delegate-flow-title delegate-flow-desc">
|
|
22
|
+
<title id="delegate-flow-title">Delegate Flow lifecycle</title>
|
|
23
|
+
<desc id="delegate-flow-desc">Flowchart showing setup, implementation, validation, optional exact review, one repair attempt, integration, cleanup, and retained-worktree failure outcomes.</desc>
|
|
24
|
+
<defs>
|
|
25
|
+
<style>
|
|
26
|
+
.zone { fill: #e6e4de; stroke: rgba(16,24,40,.141); stroke-width: 1; }
|
|
27
|
+
.zone-label { fill: #6a7282; font: 500 8px 'Geist Mono', monospace; letter-spacing: .18em; }
|
|
28
|
+
.node { fill: #fff; stroke: #101828; stroke-width: 1; }
|
|
29
|
+
.node-soft { fill: rgba(16,24,40,.03); stroke: rgba(16,24,40,.32); stroke-width: 1; }
|
|
30
|
+
.node-focal { fill: rgba(29,78,216,.08); stroke: #1d4ed8; stroke-width: 1.2; }
|
|
31
|
+
.node-failure { fill: rgba(16,24,40,.02); stroke: rgba(16,24,40,.42); stroke-width: 1; stroke-dasharray: 4 4; }
|
|
32
|
+
.name { fill: #101828; font: 600 12px 'Geist', sans-serif; text-anchor: middle; }
|
|
33
|
+
.sub { fill: #4c5665; font: 400 8px 'Geist Mono', monospace; text-anchor: middle; }
|
|
34
|
+
.edge { fill: none; stroke: #4c5665; stroke-width: 1.2; marker-end: url(#arrow); }
|
|
35
|
+
.edge-failure { fill: none; stroke: #6a7282; stroke-width: 1.2; stroke-dasharray: 5 4; marker-end: url(#arrow); }
|
|
36
|
+
.edge-success { fill: none; stroke: #1d4ed8; stroke-width: 1.4; marker-end: url(#arrow-accent); }
|
|
37
|
+
.label-mask { fill: #f0eee9; }
|
|
38
|
+
.label { fill: #4c5665; font: 500 8px 'Geist Mono', monospace; text-anchor: middle; letter-spacing: .04em; }
|
|
39
|
+
.label-success { fill: #1d4ed8; font: 500 8px 'Geist Mono', monospace; text-anchor: middle; letter-spacing: .04em; }
|
|
40
|
+
.legend { fill: #4c5665; font: 400 8px 'Geist', sans-serif; }
|
|
41
|
+
</style>
|
|
42
|
+
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="#4c5665"/></marker>
|
|
43
|
+
<marker id="arrow-accent" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="#1d4ed8"/></marker>
|
|
44
|
+
<marker id="arrow-link" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="#1d4ed8"/></marker>
|
|
45
|
+
</defs>
|
|
46
|
+
|
|
47
|
+
<rect width="1280" height="720" fill="#f0eee9"/>
|
|
48
|
+
|
|
49
|
+
<!-- Zones -->
|
|
50
|
+
<rect class="zone" x="40" y="40" width="760" height="280" rx="8"/>
|
|
51
|
+
<text class="zone-label" x="56" y="64">INITIAL RUN</text>
|
|
52
|
+
<rect class="zone" x="40" y="340" width="760" height="260" rx="8"/>
|
|
53
|
+
<text class="zone-label" x="56" y="364">ONE REPAIR MAXIMUM</text>
|
|
54
|
+
<rect class="zone" x="840" y="40" width="400" height="560" rx="8"/>
|
|
55
|
+
<text class="zone-label" x="856" y="64">OUTCOMES</text>
|
|
56
|
+
|
|
57
|
+
<!-- Initial-run edges -->
|
|
58
|
+
<path class="edge" d="M248 128 H288"/>
|
|
59
|
+
<path class="edge" d="M464 128 H504"/>
|
|
60
|
+
<path class="edge" d="M600 128 H624"/>
|
|
61
|
+
<path class="edge" d="M784 128 H872"/>
|
|
62
|
+
<path class="edge" d="M704 176 V216"/>
|
|
63
|
+
<path class="edge-success" d="M784 248 H812 Q820 248 820 240 V152 Q820 144 828 144 H856 Q864 144 864 136 V128 H872"/>
|
|
64
|
+
|
|
65
|
+
<!-- Blocked initial-run paths to repair -->
|
|
66
|
+
<path class="edge" d="M376 160 V304 Q376 312 368 312 H160 Q152 312 152 320 V412"/>
|
|
67
|
+
<path class="edge" d="M552 160 V288 Q552 296 544 296 H136 Q128 296 128 304 V412"/>
|
|
68
|
+
<path class="edge" d="M664 280 V320 Q664 328 656 328 H184 Q176 328 176 336 V412"/>
|
|
69
|
+
|
|
70
|
+
<!-- Repair path -->
|
|
71
|
+
<path class="edge" d="M272 444 H320"/>
|
|
72
|
+
<path class="edge" d="M520 444 H592"/>
|
|
73
|
+
<path class="edge-success" d="M384 412 V384 Q384 376 392 376 H808 Q816 376 816 368 V120 Q816 112 824 112 H856 Q864 112 864 120 V128 H872"/>
|
|
74
|
+
<path class="edge-success" d="M768 444 H824 Q832 444 832 436 V168 Q832 160 840 160 H856 Q864 160 864 152 V128 H872"/>
|
|
75
|
+
|
|
76
|
+
<!-- Success outcome -->
|
|
77
|
+
<path class="edge-success" d="M880 128 H912"/>
|
|
78
|
+
<path class="edge-success" d="M1012 160 V216"/>
|
|
79
|
+
<path class="edge" d="M552 96 V80 Q552 72 560 72 H1144 Q1152 72 1152 80 V240 Q1152 248 1144 248 H1112"/>
|
|
80
|
+
|
|
81
|
+
<!-- Failure paths, fanned into one merge -->
|
|
82
|
+
<path class="edge-failure" d="M584 160 V304 Q584 312 592 312 H808 Q816 312 816 320 V488 Q816 496 824 496 H872"/>
|
|
83
|
+
<path class="edge-failure" d="M232 476 V568 Q232 576 240 576 H856 Q864 576 864 568 V512 Q864 504 872 504"/>
|
|
84
|
+
<path class="edge-failure" d="M472 476 V536 Q472 544 480 544 H840 Q848 544 848 536 V520 Q848 512 856 512 H872"/>
|
|
85
|
+
<path class="edge-failure" d="M768 460 H808 Q816 460 816 468 V512 Q816 520 824 520 H856 Q864 520 864 512 V504 H872"/>
|
|
86
|
+
<path class="edge-failure" d="M880 504 H912"/>
|
|
87
|
+
|
|
88
|
+
<!-- Completion-warning and integration-failure outcomes -->
|
|
89
|
+
<path class="edge-failure" d="M1012 280 V336"/>
|
|
90
|
+
<path class="edge-failure" d="M1112 136 H1184 Q1192 136 1192 144 V356 Q1192 364 1184 364 H1152"/>
|
|
91
|
+
<path class="edge-failure" d="M1112 112 H1208 Q1216 112 1216 120 V496 Q1216 504 1208 504 H1152"/>
|
|
92
|
+
|
|
93
|
+
<!-- Edge labels -->
|
|
94
|
+
<rect class="label-mask" x="272" y="252" width="128" height="12" rx="2"/>
|
|
95
|
+
<text class="label" x="336" y="261">IMPLEMENTER BLOCK</text>
|
|
96
|
+
<rect class="label-mask" x="416" y="276" width="120" height="12" rx="2"/>
|
|
97
|
+
<text class="label" x="476" y="285">VALIDATION BLOCK</text>
|
|
98
|
+
<rect class="label-mask" x="504" y="316" width="96" height="12" rx="2"/>
|
|
99
|
+
<text class="label" x="552" y="325">REVIEW BLOCK</text>
|
|
100
|
+
<rect class="label-mask" x="800" y="100" width="32" height="12" rx="2"/>
|
|
101
|
+
<text class="label" x="816" y="109">NO</text>
|
|
102
|
+
<rect class="label-mask" x="688" y="188" width="32" height="12" rx="2"/>
|
|
103
|
+
<text class="label" x="704" y="197">YES</text>
|
|
104
|
+
<rect class="label-mask" x="828" y="224" width="40" height="12" rx="2"/>
|
|
105
|
+
<text class="label-success" x="848" y="233">PASS</text>
|
|
106
|
+
<rect class="label-mask" x="520" y="364" width="144" height="12" rx="2"/>
|
|
107
|
+
<text class="label-success" x="592" y="373">NO REVIEW REQUESTED</text>
|
|
108
|
+
<rect class="label-mask" x="528" y="424" width="56" height="12" rx="2"/>
|
|
109
|
+
<text class="label" x="556" y="433">REVIEW</text>
|
|
110
|
+
<rect class="label-mask" x="800" y="404" width="40" height="12" rx="2"/>
|
|
111
|
+
<text class="label-success" x="820" y="413">PASS</text>
|
|
112
|
+
<rect class="label-mask" x="816" y="292" width="160" height="12" rx="2"/>
|
|
113
|
+
<text class="label" x="896" y="301">REBASE / INFRA FAILURE</text>
|
|
114
|
+
<rect class="label-mask" x="336" y="556" width="136" height="12" rx="2"/>
|
|
115
|
+
<text class="label" x="404" y="565">INFRASTRUCTURE FAILURE</text>
|
|
116
|
+
<rect class="label-mask" x="560" y="524" width="96" height="12" rx="2"/>
|
|
117
|
+
<text class="label" x="608" y="533">SECOND BLOCK</text>
|
|
118
|
+
<rect class="label-mask" x="748" y="476" width="96" height="12" rx="2"/>
|
|
119
|
+
<text class="label" x="796" y="485">SECOND BLOCK</text>
|
|
120
|
+
<rect class="label-mask" x="920" y="60" width="128" height="12" rx="2"/>
|
|
121
|
+
<text class="label" x="984" y="69">POST-REBASE NO-OP</text>
|
|
122
|
+
<rect class="label-mask" x="1024" y="296" width="120" height="12" rx="2"/>
|
|
123
|
+
<text class="label" x="1084" y="305">CLEANUP REFUSAL</text>
|
|
124
|
+
<rect class="label-mask" x="1024" y="312" width="152" height="12" rx="2"/>
|
|
125
|
+
<text class="label" x="1100" y="321">FF DIAGNOSTIC / CLEAN TIP</text>
|
|
126
|
+
<rect class="label-mask" x="1064" y="432" width="136" height="12" rx="2"/>
|
|
127
|
+
<text class="label" x="1132" y="441">OTHER FF FAILURE</text>
|
|
128
|
+
|
|
129
|
+
<!-- Nodes -->
|
|
130
|
+
<rect class="node-soft" x="72" y="96" width="176" height="64" rx="32"/>
|
|
131
|
+
<text class="name" x="160" y="124">Resolve roles</text>
|
|
132
|
+
<text class="sub" x="160" y="140">CREATE UNIT WORKTREES</text>
|
|
133
|
+
|
|
134
|
+
<rect class="node" x="288" y="96" width="176" height="64" rx="6"/>
|
|
135
|
+
<text class="name" x="376" y="124">Implement in parallel</text>
|
|
136
|
+
<text class="sub" x="376" y="140">EACH UNIT ISOLATED</text>
|
|
137
|
+
|
|
138
|
+
<rect class="node" x="504" y="96" width="96" height="64" rx="6"/>
|
|
139
|
+
<text class="name" x="552" y="120">Rebase</text>
|
|
140
|
+
<text class="sub" x="552" y="136">INSPECT</text>
|
|
141
|
+
<text class="sub" x="552" y="148">VALIDATE</text>
|
|
142
|
+
|
|
143
|
+
<polygon class="node" points="704,80 784,128 704,176 624,128"/>
|
|
144
|
+
<text class="name" x="704" y="124">Review</text>
|
|
145
|
+
<text class="sub" x="704" y="140">REQUESTED?</text>
|
|
146
|
+
|
|
147
|
+
<rect class="node" x="624" y="216" width="160" height="64" rx="6"/>
|
|
148
|
+
<text class="name" x="704" y="240">Exact review packet</text>
|
|
149
|
+
<text class="sub" x="704" y="256">BASE · TIP · PATCH</text>
|
|
150
|
+
<text class="sub" x="704" y="268">REQUIRE PASS</text>
|
|
151
|
+
|
|
152
|
+
<rect class="node-focal" x="912" y="96" width="200" height="64" rx="6"/>
|
|
153
|
+
<text class="name" x="1012" y="124">Integrate in order</text>
|
|
154
|
+
<text class="sub" x="1012" y="140">FAST-FORWARD EXACT TIP</text>
|
|
155
|
+
|
|
156
|
+
<rect class="node-soft" x="912" y="216" width="200" height="64" rx="32"/>
|
|
157
|
+
<text class="name" x="1012" y="244">Cleanup</text>
|
|
158
|
+
<text class="sub" x="1012" y="260">REMOVE WORKTREE + BRANCH</text>
|
|
159
|
+
|
|
160
|
+
<rect class="node-soft" x="912" y="336" width="240" height="72" rx="32"/>
|
|
161
|
+
<text class="name" x="1032" y="364">Completion warning</text>
|
|
162
|
+
<text class="sub" x="1032" y="380">CLEAN TIP · OR CLEANUP REFUSAL</text>
|
|
163
|
+
<text class="sub" x="1032" y="392">RETAIN REPORTED RESOURCES</text>
|
|
164
|
+
|
|
165
|
+
<rect class="node" x="72" y="412" width="200" height="64" rx="6"/>
|
|
166
|
+
<text class="name" x="172" y="440">One repair</text>
|
|
167
|
+
<text class="sub" x="172" y="456">DELEGATE_FLOW_CONTINUE</text>
|
|
168
|
+
|
|
169
|
+
<rect class="node" x="320" y="412" width="200" height="64" rx="6"/>
|
|
170
|
+
<text class="name" x="420" y="440">Revalidate repair</text>
|
|
171
|
+
<text class="sub" x="420" y="456">SAME UNIT WORKTREE</text>
|
|
172
|
+
|
|
173
|
+
<rect class="node" x="592" y="412" width="176" height="64" rx="6"/>
|
|
174
|
+
<text class="name" x="680" y="440">Re-review packet</text>
|
|
175
|
+
<text class="sub" x="680" y="456">REQUIRE PASS AGAIN</text>
|
|
176
|
+
|
|
177
|
+
<rect class="node-failure" x="912" y="472" width="240" height="64" rx="32"/>
|
|
178
|
+
<text class="name" x="1032" y="500">Terminal failure</text>
|
|
179
|
+
<text class="sub" x="1032" y="516">RETAIN UNIT WORKTREE</text>
|
|
180
|
+
|
|
181
|
+
<!-- Merge points -->
|
|
182
|
+
<circle cx="876" cy="128" r="4" fill="#1d4ed8"/>
|
|
183
|
+
<circle cx="876" cy="504" r="4" fill="#6a7282"/>
|
|
184
|
+
|
|
185
|
+
<!-- Legend -->
|
|
186
|
+
<line x1="40" y1="636" x2="1240" y2="636" stroke="rgba(16,24,40,.141)" stroke-width="0.8"/>
|
|
187
|
+
<text class="zone-label" x="40" y="656">LEGEND</text>
|
|
188
|
+
<rect class="node" x="120" y="648" width="24" height="12" rx="2"/>
|
|
189
|
+
<text class="legend" x="152" y="658">Step</text>
|
|
190
|
+
<polygon class="node" points="236,654 248,648 260,654 248,660"/>
|
|
191
|
+
<text class="legend" x="272" y="658">Decision</text>
|
|
192
|
+
<circle cx="380" cy="654" r="4" fill="#4c5665"/>
|
|
193
|
+
<text class="legend" x="392" y="658">Branch merge</text>
|
|
194
|
+
<line x1="512" y1="654" x2="548" y2="654" stroke="#1d4ed8" stroke-width="1.4" marker-end="url(#arrow-accent)"/>
|
|
195
|
+
<text class="legend" x="560" y="658">Validated path</text>
|
|
196
|
+
<line x1="696" y1="654" x2="732" y2="654" stroke="#6a7282" stroke-width="1.2" stroke-dasharray="5 4" marker-end="url(#arrow)"/>
|
|
197
|
+
<text class="legend" x="744" y="658">Terminal path</text>
|
|
198
|
+
<text class="sub" x="1120" y="658">ONE REPAIR · NO AUTOMATIC RETRY</text>
|
|
199
|
+
</svg>
|
|
200
|
+
</main>
|
|
201
|
+
</body>
|
|
202
|
+
</html>
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg viewBox="0 0 1280 720" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="delegate-flow-title delegate-flow-desc">
|
|
3
|
+
<title id="delegate-flow-title">Delegate Flow lifecycle</title>
|
|
4
|
+
<desc id="delegate-flow-desc">Flowchart showing setup, implementation, validation, optional exact review, one repair attempt, integration, cleanup, and retained-worktree failure outcomes.</desc>
|
|
5
|
+
<defs>
|
|
6
|
+
<style>
|
|
7
|
+
@import url("https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&family=Geist+Mono:wght@400;500;600&display=swap");
|
|
8
|
+
.zone { fill: #e6e4de; stroke: rgba(16,24,40,.141); stroke-width: 1; }
|
|
9
|
+
.zone-label { fill: #6a7282; font: 500 8px 'Geist Mono', monospace; letter-spacing: .18em; }
|
|
10
|
+
.node { fill: #fff; stroke: #101828; stroke-width: 1; }
|
|
11
|
+
.node-soft { fill: rgba(16,24,40,.03); stroke: rgba(16,24,40,.32); stroke-width: 1; }
|
|
12
|
+
.node-focal { fill: rgba(29,78,216,.08); stroke: #1d4ed8; stroke-width: 1.2; }
|
|
13
|
+
.node-failure { fill: rgba(16,24,40,.02); stroke: rgba(16,24,40,.42); stroke-width: 1; stroke-dasharray: 4 4; }
|
|
14
|
+
.name { fill: #101828; font: 600 12px 'Geist', sans-serif; text-anchor: middle; }
|
|
15
|
+
.sub { fill: #4c5665; font: 400 8px 'Geist Mono', monospace; text-anchor: middle; }
|
|
16
|
+
.edge { fill: none; stroke: #4c5665; stroke-width: 1.2; marker-end: url(#arrow); }
|
|
17
|
+
.edge-failure { fill: none; stroke: #6a7282; stroke-width: 1.2; stroke-dasharray: 5 4; marker-end: url(#arrow); }
|
|
18
|
+
.edge-success { fill: none; stroke: #1d4ed8; stroke-width: 1.4; marker-end: url(#arrow-accent); }
|
|
19
|
+
.label-mask { fill: #f0eee9; }
|
|
20
|
+
.label { fill: #4c5665; font: 500 8px 'Geist Mono', monospace; text-anchor: middle; letter-spacing: .04em; }
|
|
21
|
+
.label-success { fill: #1d4ed8; font: 500 8px 'Geist Mono', monospace; text-anchor: middle; letter-spacing: .04em; }
|
|
22
|
+
.legend { fill: #4c5665; font: 400 8px 'Geist', sans-serif; }
|
|
23
|
+
</style>
|
|
24
|
+
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="#4c5665"/></marker>
|
|
25
|
+
<marker id="arrow-accent" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="#1d4ed8"/></marker>
|
|
26
|
+
<marker id="arrow-link" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="#1d4ed8"/></marker>
|
|
27
|
+
</defs>
|
|
28
|
+
|
|
29
|
+
<rect width="1280" height="720" fill="#f0eee9"/>
|
|
30
|
+
|
|
31
|
+
<!-- Zones -->
|
|
32
|
+
<rect class="zone" x="40" y="40" width="760" height="280" rx="8"/>
|
|
33
|
+
<text class="zone-label" x="56" y="64">INITIAL RUN</text>
|
|
34
|
+
<rect class="zone" x="40" y="340" width="760" height="260" rx="8"/>
|
|
35
|
+
<text class="zone-label" x="56" y="364">ONE REPAIR MAXIMUM</text>
|
|
36
|
+
<rect class="zone" x="840" y="40" width="400" height="560" rx="8"/>
|
|
37
|
+
<text class="zone-label" x="856" y="64">OUTCOMES</text>
|
|
38
|
+
|
|
39
|
+
<!-- Initial-run edges -->
|
|
40
|
+
<path class="edge" d="M248 128 H288"/>
|
|
41
|
+
<path class="edge" d="M464 128 H504"/>
|
|
42
|
+
<path class="edge" d="M600 128 H624"/>
|
|
43
|
+
<path class="edge" d="M784 128 H872"/>
|
|
44
|
+
<path class="edge" d="M704 176 V216"/>
|
|
45
|
+
<path class="edge-success" d="M784 248 H812 Q820 248 820 240 V152 Q820 144 828 144 H856 Q864 144 864 136 V128 H872"/>
|
|
46
|
+
|
|
47
|
+
<!-- Blocked initial-run paths to repair -->
|
|
48
|
+
<path class="edge" d="M376 160 V304 Q376 312 368 312 H160 Q152 312 152 320 V412"/>
|
|
49
|
+
<path class="edge" d="M552 160 V288 Q552 296 544 296 H136 Q128 296 128 304 V412"/>
|
|
50
|
+
<path class="edge" d="M664 280 V320 Q664 328 656 328 H184 Q176 328 176 336 V412"/>
|
|
51
|
+
|
|
52
|
+
<!-- Repair path -->
|
|
53
|
+
<path class="edge" d="M272 444 H320"/>
|
|
54
|
+
<path class="edge" d="M520 444 H592"/>
|
|
55
|
+
<path class="edge-success" d="M384 412 V384 Q384 376 392 376 H808 Q816 376 816 368 V120 Q816 112 824 112 H856 Q864 112 864 120 V128 H872"/>
|
|
56
|
+
<path class="edge-success" d="M768 444 H824 Q832 444 832 436 V168 Q832 160 840 160 H856 Q864 160 864 152 V128 H872"/>
|
|
57
|
+
|
|
58
|
+
<!-- Success outcome -->
|
|
59
|
+
<path class="edge-success" d="M880 128 H912"/>
|
|
60
|
+
<path class="edge-success" d="M1012 160 V216"/>
|
|
61
|
+
<path class="edge" d="M552 96 V80 Q552 72 560 72 H1144 Q1152 72 1152 80 V240 Q1152 248 1144 248 H1112"/>
|
|
62
|
+
|
|
63
|
+
<!-- Failure paths, fanned into one merge -->
|
|
64
|
+
<path class="edge-failure" d="M584 160 V304 Q584 312 592 312 H808 Q816 312 816 320 V488 Q816 496 824 496 H872"/>
|
|
65
|
+
<path class="edge-failure" d="M232 476 V568 Q232 576 240 576 H856 Q864 576 864 568 V512 Q864 504 872 504"/>
|
|
66
|
+
<path class="edge-failure" d="M472 476 V536 Q472 544 480 544 H840 Q848 544 848 536 V520 Q848 512 856 512 H872"/>
|
|
67
|
+
<path class="edge-failure" d="M768 460 H808 Q816 460 816 468 V512 Q816 520 824 520 H856 Q864 520 864 512 V504 H872"/>
|
|
68
|
+
<path class="edge-failure" d="M880 504 H912"/>
|
|
69
|
+
|
|
70
|
+
<!-- Completion-warning and integration-failure outcomes -->
|
|
71
|
+
<path class="edge-failure" d="M1012 280 V336"/>
|
|
72
|
+
<path class="edge-failure" d="M1112 136 H1184 Q1192 136 1192 144 V356 Q1192 364 1184 364 H1152"/>
|
|
73
|
+
<path class="edge-failure" d="M1112 112 H1208 Q1216 112 1216 120 V496 Q1216 504 1208 504 H1152"/>
|
|
74
|
+
|
|
75
|
+
<!-- Edge labels -->
|
|
76
|
+
<rect class="label-mask" x="272" y="252" width="128" height="12" rx="2"/>
|
|
77
|
+
<text class="label" x="336" y="261">IMPLEMENTER BLOCK</text>
|
|
78
|
+
<rect class="label-mask" x="416" y="276" width="120" height="12" rx="2"/>
|
|
79
|
+
<text class="label" x="476" y="285">VALIDATION BLOCK</text>
|
|
80
|
+
<rect class="label-mask" x="504" y="316" width="96" height="12" rx="2"/>
|
|
81
|
+
<text class="label" x="552" y="325">REVIEW BLOCK</text>
|
|
82
|
+
<rect class="label-mask" x="800" y="100" width="32" height="12" rx="2"/>
|
|
83
|
+
<text class="label" x="816" y="109">NO</text>
|
|
84
|
+
<rect class="label-mask" x="688" y="188" width="32" height="12" rx="2"/>
|
|
85
|
+
<text class="label" x="704" y="197">YES</text>
|
|
86
|
+
<rect class="label-mask" x="828" y="224" width="40" height="12" rx="2"/>
|
|
87
|
+
<text class="label-success" x="848" y="233">PASS</text>
|
|
88
|
+
<rect class="label-mask" x="520" y="364" width="144" height="12" rx="2"/>
|
|
89
|
+
<text class="label-success" x="592" y="373">NO REVIEW REQUESTED</text>
|
|
90
|
+
<rect class="label-mask" x="528" y="424" width="56" height="12" rx="2"/>
|
|
91
|
+
<text class="label" x="556" y="433">REVIEW</text>
|
|
92
|
+
<rect class="label-mask" x="800" y="404" width="40" height="12" rx="2"/>
|
|
93
|
+
<text class="label-success" x="820" y="413">PASS</text>
|
|
94
|
+
<rect class="label-mask" x="816" y="292" width="160" height="12" rx="2"/>
|
|
95
|
+
<text class="label" x="896" y="301">REBASE / INFRA FAILURE</text>
|
|
96
|
+
<rect class="label-mask" x="336" y="556" width="136" height="12" rx="2"/>
|
|
97
|
+
<text class="label" x="404" y="565">INFRASTRUCTURE FAILURE</text>
|
|
98
|
+
<rect class="label-mask" x="560" y="524" width="96" height="12" rx="2"/>
|
|
99
|
+
<text class="label" x="608" y="533">SECOND BLOCK</text>
|
|
100
|
+
<rect class="label-mask" x="748" y="476" width="96" height="12" rx="2"/>
|
|
101
|
+
<text class="label" x="796" y="485">SECOND BLOCK</text>
|
|
102
|
+
<rect class="label-mask" x="920" y="60" width="128" height="12" rx="2"/>
|
|
103
|
+
<text class="label" x="984" y="69">POST-REBASE NO-OP</text>
|
|
104
|
+
<rect class="label-mask" x="1024" y="296" width="120" height="12" rx="2"/>
|
|
105
|
+
<text class="label" x="1084" y="305">CLEANUP REFUSAL</text>
|
|
106
|
+
<rect class="label-mask" x="1024" y="312" width="152" height="12" rx="2"/>
|
|
107
|
+
<text class="label" x="1100" y="321">FF DIAGNOSTIC / CLEAN TIP</text>
|
|
108
|
+
<rect class="label-mask" x="1064" y="432" width="136" height="12" rx="2"/>
|
|
109
|
+
<text class="label" x="1132" y="441">OTHER FF FAILURE</text>
|
|
110
|
+
|
|
111
|
+
<!-- Nodes -->
|
|
112
|
+
<rect class="node-soft" x="72" y="96" width="176" height="64" rx="32"/>
|
|
113
|
+
<text class="name" x="160" y="124">Resolve roles</text>
|
|
114
|
+
<text class="sub" x="160" y="140">CREATE UNIT WORKTREES</text>
|
|
115
|
+
|
|
116
|
+
<rect class="node" x="288" y="96" width="176" height="64" rx="6"/>
|
|
117
|
+
<text class="name" x="376" y="124">Implement in parallel</text>
|
|
118
|
+
<text class="sub" x="376" y="140">EACH UNIT ISOLATED</text>
|
|
119
|
+
|
|
120
|
+
<rect class="node" x="504" y="96" width="96" height="64" rx="6"/>
|
|
121
|
+
<text class="name" x="552" y="120">Rebase</text>
|
|
122
|
+
<text class="sub" x="552" y="136">INSPECT</text>
|
|
123
|
+
<text class="sub" x="552" y="148">VALIDATE</text>
|
|
124
|
+
|
|
125
|
+
<polygon class="node" points="704,80 784,128 704,176 624,128"/>
|
|
126
|
+
<text class="name" x="704" y="124">Review</text>
|
|
127
|
+
<text class="sub" x="704" y="140">REQUESTED?</text>
|
|
128
|
+
|
|
129
|
+
<rect class="node" x="624" y="216" width="160" height="64" rx="6"/>
|
|
130
|
+
<text class="name" x="704" y="240">Exact review packet</text>
|
|
131
|
+
<text class="sub" x="704" y="256">BASE · TIP · PATCH</text>
|
|
132
|
+
<text class="sub" x="704" y="268">REQUIRE PASS</text>
|
|
133
|
+
|
|
134
|
+
<rect class="node-focal" x="912" y="96" width="200" height="64" rx="6"/>
|
|
135
|
+
<text class="name" x="1012" y="124">Integrate in order</text>
|
|
136
|
+
<text class="sub" x="1012" y="140">FAST-FORWARD EXACT TIP</text>
|
|
137
|
+
|
|
138
|
+
<rect class="node-soft" x="912" y="216" width="200" height="64" rx="32"/>
|
|
139
|
+
<text class="name" x="1012" y="244">Cleanup</text>
|
|
140
|
+
<text class="sub" x="1012" y="260">REMOVE WORKTREE + BRANCH</text>
|
|
141
|
+
|
|
142
|
+
<rect class="node-soft" x="912" y="336" width="240" height="72" rx="32"/>
|
|
143
|
+
<text class="name" x="1032" y="364">Completion warning</text>
|
|
144
|
+
<text class="sub" x="1032" y="380">CLEAN TIP · OR CLEANUP REFUSAL</text>
|
|
145
|
+
<text class="sub" x="1032" y="392">RETAIN REPORTED RESOURCES</text>
|
|
146
|
+
|
|
147
|
+
<rect class="node" x="72" y="412" width="200" height="64" rx="6"/>
|
|
148
|
+
<text class="name" x="172" y="440">One repair</text>
|
|
149
|
+
<text class="sub" x="172" y="456">DELEGATE_FLOW_CONTINUE</text>
|
|
150
|
+
|
|
151
|
+
<rect class="node" x="320" y="412" width="200" height="64" rx="6"/>
|
|
152
|
+
<text class="name" x="420" y="440">Revalidate repair</text>
|
|
153
|
+
<text class="sub" x="420" y="456">SAME UNIT WORKTREE</text>
|
|
154
|
+
|
|
155
|
+
<rect class="node" x="592" y="412" width="176" height="64" rx="6"/>
|
|
156
|
+
<text class="name" x="680" y="440">Re-review packet</text>
|
|
157
|
+
<text class="sub" x="680" y="456">REQUIRE PASS AGAIN</text>
|
|
158
|
+
|
|
159
|
+
<rect class="node-failure" x="912" y="472" width="240" height="64" rx="32"/>
|
|
160
|
+
<text class="name" x="1032" y="500">Terminal failure</text>
|
|
161
|
+
<text class="sub" x="1032" y="516">RETAIN UNIT WORKTREE</text>
|
|
162
|
+
|
|
163
|
+
<!-- Merge points -->
|
|
164
|
+
<circle cx="876" cy="128" r="4" fill="#1d4ed8"/>
|
|
165
|
+
<circle cx="876" cy="504" r="4" fill="#6a7282"/>
|
|
166
|
+
|
|
167
|
+
<!-- Legend -->
|
|
168
|
+
<line x1="40" y1="636" x2="1240" y2="636" stroke="rgba(16,24,40,.141)" stroke-width="0.8"/>
|
|
169
|
+
<text class="zone-label" x="40" y="656">LEGEND</text>
|
|
170
|
+
<rect class="node" x="120" y="648" width="24" height="12" rx="2"/>
|
|
171
|
+
<text class="legend" x="152" y="658">Step</text>
|
|
172
|
+
<polygon class="node" points="236,654 248,648 260,654 248,660"/>
|
|
173
|
+
<text class="legend" x="272" y="658">Decision</text>
|
|
174
|
+
<circle cx="380" cy="654" r="4" fill="#4c5665"/>
|
|
175
|
+
<text class="legend" x="392" y="658">Branch merge</text>
|
|
176
|
+
<line x1="512" y1="654" x2="548" y2="654" stroke="#1d4ed8" stroke-width="1.4" marker-end="url(#arrow-accent)"/>
|
|
177
|
+
<text class="legend" x="560" y="658">Validated path</text>
|
|
178
|
+
<line x1="696" y1="654" x2="732" y2="654" stroke="#6a7282" stroke-width="1.2" stroke-dasharray="5 4" marker-end="url(#arrow)"/>
|
|
179
|
+
<text class="legend" x="744" y="658">Terminal path</text>
|
|
180
|
+
<text class="sub" x="1120" y="658">ONE REPAIR · NO AUTOMATIC RETRY</text>
|
|
181
|
+
</svg>
|
|
@@ -19,7 +19,6 @@ import {
|
|
|
19
19
|
} from "@henryqw/pi-subagent";
|
|
20
20
|
import { Type, type Static } from "typebox";
|
|
21
21
|
import { Check } from "typebox/value";
|
|
22
|
-
import { runDelegation } from "./delegation.ts";
|
|
23
22
|
import { MODEL_CLASS_GUIDANCE } from "./model-class-policy.ts";
|
|
24
23
|
import { TASK_NAME_CONTRACT, TaskNameSchema, normalizeTaskName } from "./task-name.ts";
|
|
25
24
|
|
|
@@ -356,7 +355,7 @@ export function registerDelegateFlow(pi: ExtensionAPI, runtime: DelegateFlowRunt
|
|
|
356
355
|
let started = false;
|
|
357
356
|
try {
|
|
358
357
|
assertCurrent(flow);
|
|
359
|
-
const result = await
|
|
358
|
+
const result = await runtime.executor.run({
|
|
360
359
|
signal,
|
|
361
360
|
onTokens: (tokens) => runtime.updateWidgetTokens(widgetId, tokens),
|
|
362
361
|
onActivity: (event) => runtime.updateWidgetActivity(widgetId, event),
|
package/extensions/subagent.ts
CHANGED
|
@@ -34,7 +34,6 @@ import {
|
|
|
34
34
|
} from "@henryqw/pi-subagent";
|
|
35
35
|
import { DEFAULT_TIMEOUT_CONFIG, readSubagentConfig, type SubagentTimeoutConfig } from "./config.ts";
|
|
36
36
|
import { registerDelegateFlow } from "./delegate-flow.ts";
|
|
37
|
-
import { runDelegation } from "./delegation.ts";
|
|
38
37
|
import { MODEL_CLASS_GUIDANCE } from "./model-class-policy.ts";
|
|
39
38
|
import {
|
|
40
39
|
formatBackgroundWorkflowResult,
|
|
@@ -641,7 +640,7 @@ export default function subagentExtension(
|
|
|
641
640
|
: { ...base, status: nextStatus, assistantOutput: nextText });
|
|
642
641
|
};
|
|
643
642
|
try {
|
|
644
|
-
child = await
|
|
643
|
+
child = await executor.run({
|
|
645
644
|
signal: workflowSignal,
|
|
646
645
|
onUpdate: (output) => {
|
|
647
646
|
setState("running", output);
|
package/extensions/task-name.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { DISPLAY_TEXT_CONTRACT, hasDisplayControlCharacters } from "@henryqw/pi-subagent";
|
|
1
2
|
import { Type, type Static } from "typebox";
|
|
2
3
|
|
|
3
4
|
const TASK_NAME_MAX_LENGTH = 29 as const;
|
|
4
|
-
const TASK_NAME_CONTROL_RANGES = "\\u0000-\\u001F\\u007F-\\u009F" as const;
|
|
5
5
|
const TASK_NAME_LIMIT_WORDING = `about five words and fewer than ${TASK_NAME_MAX_LENGTH + 1} characters`;
|
|
6
6
|
const TASK_NAME_WORDING = `short descriptive name of ${TASK_NAME_LIMIT_WORDING}`;
|
|
7
7
|
|
|
@@ -10,8 +10,8 @@ export const TASK_NAME_CONTRACT = {
|
|
|
10
10
|
maxLength: TASK_NAME_MAX_LENGTH,
|
|
11
11
|
description: `Short descriptive task name, ${TASK_NAME_LIMIT_WORDING}; C0/C1 control characters are rejected.`,
|
|
12
12
|
promptGuidance: `Use a ${TASK_NAME_WORDING} without C0/C1 control characters.`,
|
|
13
|
-
controlRanges:
|
|
14
|
-
pattern:
|
|
13
|
+
controlRanges: DISPLAY_TEXT_CONTRACT.controlRanges,
|
|
14
|
+
pattern: DISPLAY_TEXT_CONTRACT.pattern,
|
|
15
15
|
} as const;
|
|
16
16
|
|
|
17
17
|
export const TaskNameSchema = Type.String({
|
|
@@ -23,10 +23,8 @@ export const TaskNameSchema = Type.String({
|
|
|
23
23
|
|
|
24
24
|
export type TaskName = Static<typeof TaskNameSchema>;
|
|
25
25
|
|
|
26
|
-
const TASK_NAME_CONTROL_PATTERN = new RegExp(`[${TASK_NAME_CONTRACT.controlRanges}]`, "u");
|
|
27
|
-
|
|
28
26
|
export function normalizeTaskName(value: TaskName, path: string): TaskName {
|
|
29
|
-
if (
|
|
27
|
+
if (hasDisplayControlCharacters(value)) throw new Error(`${path} must not contain C0/C1 control characters.`);
|
|
30
28
|
const normalized = value.trim();
|
|
31
29
|
if (!normalized) throw new Error(`${path} must be non-empty text.`);
|
|
32
30
|
return normalized;
|
package/extensions/workflow.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { StringEnum } from "@earendil-works/pi-ai";
|
|
2
|
+
import { DISPLAY_TEXT_CONTRACT } from "@henryqw/pi-subagent";
|
|
2
3
|
import { PROFILE_NAMES } from "@henryqw/pi-task-models";
|
|
3
4
|
import { Type, type Static } from "typebox";
|
|
4
5
|
import { Check } from "typebox/value";
|
|
@@ -6,9 +7,9 @@ import { TaskNameSchema, normalizeTaskName } from "./task-name.ts";
|
|
|
6
7
|
|
|
7
8
|
export const MAX_WORKFLOW_ENTRIES = 8;
|
|
8
9
|
|
|
9
|
-
const RoleSchema = Type.String({ minLength: 1, description: "Configured Subagent role name" });
|
|
10
|
+
const RoleSchema = Type.String({ minLength: 1, pattern: DISPLAY_TEXT_CONTRACT.pattern, description: "Configured Subagent role name" });
|
|
10
11
|
const TaskSchema = Type.String({ minLength: 1, description: "Bounded task packet" });
|
|
11
|
-
const ModelSchema = Type.String({ minLength: 1, description: "Designated model as provider/modelId; replaces the selected route model" });
|
|
12
|
+
const ModelSchema = Type.String({ minLength: 1, pattern: DISPLAY_TEXT_CONTRACT.pattern, description: "Designated model as provider/modelId; replaces the selected route model" });
|
|
12
13
|
const ModelClassSchema = StringEnum(PROFILE_NAMES, { description: "Task model profile" });
|
|
13
14
|
|
|
14
15
|
export const DelegationSchema = Type.Object({
|
package/package.json
CHANGED
package/extensions/delegation.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { EphemeralSubagentExecutor, EphemeralSubagentResult, EphemeralSubagentRunInput } from "@henryqw/pi-subagent";
|
|
2
|
-
|
|
3
|
-
/** Runs one caller-prepared child launch without making lifecycle decisions. */
|
|
4
|
-
export function runDelegation(
|
|
5
|
-
executor: EphemeralSubagentExecutor,
|
|
6
|
-
input: EphemeralSubagentRunInput,
|
|
7
|
-
): Promise<EphemeralSubagentResult> {
|
|
8
|
-
return executor.run(input);
|
|
9
|
-
}
|