@wolido/async-subagent-isolation 1.5.1 → 1.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ADVANCED.en.md +121 -25
- package/ADVANCED.md +126 -30
- package/README.en.md +184 -113
- package/README.md +186 -115
- package/examples/README.en.md +22 -0
- package/examples/README.md +22 -0
- package/examples/pi/agent/master.md +2 -0
- package/examples/pi/agent/subagent-isolation.json +11 -0
- package/package.json +12 -3
- package/src/index.ts +1403 -74
package/ADVANCED.en.md
CHANGED
|
@@ -35,6 +35,28 @@ You are a senior TypeScript engineer. Prefer async/await and avoid callbacks.
|
|
|
35
35
|
| `thinking` | `string` | Optional thinking level. One of `off \| minimal \| low \| medium \| high \| xhigh \| max`. |
|
|
36
36
|
| `skills` | `string[]` (comma-separated) | Optional skill path list. If present, global skills are disabled and only these are loaded. Paths can be absolute or relative to the working directory. |
|
|
37
37
|
|
|
38
|
+
## Subagent roster injection (system prompt)
|
|
39
|
+
|
|
40
|
+
The extension registers a `before_agent_start` hook that appends the discovered subagent roster to the end of the main agent's system prompt, leaving the existing content in front. The main agent thus sees every subagent's role each turn, and `master.md` no longer needs a hand-written agent table. Injected block format:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
## Available Subagents
|
|
44
|
+
|
|
45
|
+
Delegate tasks to these specialized subagents via the `subagent` tool:
|
|
46
|
+
|
|
47
|
+
- coder — Writes and refactors code (project)
|
|
48
|
+
- writer — Writes docs and READMEs (user)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Behavior details:
|
|
52
|
+
|
|
53
|
+
- Line format: one agent per line, `name — description (source)`; the separator is a U+2014 em dash; source is `user` or `project`. Discovery semantics match `discoverAgents(cwd, "both")`: a project-level agent shadows a user-level one with the same name.
|
|
54
|
+
- Build and cache: the injection text is built on the first hook trigger (`ctx.cwd` is unavailable at factory time, so it cannot be built earlier) and then cached in the factory closure. Mid-session agent file edits do not change the injection; `/reload` re-executes the factory, producing a fresh closure that rebuilds the roster. An empty build is cached the same way: agent files added after an empty first build do not trigger a rebuild and only appear after `/reload`.
|
|
55
|
+
- Depth guard: no injection when `PI_SUBAGENT_DEPTH >= 1` (inside a subagent process); a subagent has no `subagent` tool surface, so the roster would be pure pollution.
|
|
56
|
+
- Silent skip: a missing `ctx.cwd` or a build failure settles the injection to empty silently (no throw, no injection), and later triggers within the same factory instance do not retry.
|
|
57
|
+
- Multi-line descriptions flattened: newlines, tabs and whitespace runs in a description collapse to single spaces, including multi-line text produced by YAML block scalars (`description: |`), so name, description and source marker always stay on one line.
|
|
58
|
+
- With no agents discovered, nothing is injected and the system prompt is returned unchanged.
|
|
59
|
+
|
|
38
60
|
## Per-subagent model & thinking level config (subagent-isolation.json)
|
|
39
61
|
|
|
40
62
|
Use `subagent-isolation.json` to assign a model and thinking level to each subagent. The file name is retained from the sync original, so both projects can share one config.
|
|
@@ -55,8 +77,11 @@ Each key is an agent name; the value can be either:
|
|
|
55
77
|
- **Plain string (legacy format)**: model only, equivalent to `{ "model": "..." }`.
|
|
56
78
|
- **Object**: `{ "model": ..., "thinking": ... }` — both fields optional, but at least one must be present.
|
|
57
79
|
|
|
80
|
+
The top-level `$models` array is a reserved field (the `$` prefix avoids collisions with agent names) recording the available-model list; see "The available-model list (`$models`)" below.
|
|
81
|
+
|
|
58
82
|
```json
|
|
59
83
|
{
|
|
84
|
+
"$models": ["deepseek/deepseek-v4-pro", "deepseek/deepseek-v4-flash"],
|
|
60
85
|
"coder": { "model": "deepseek/deepseek-v4-pro", "thinking": "high" },
|
|
61
86
|
"writer": "deepseek/deepseek-v4-flash"
|
|
62
87
|
}
|
|
@@ -82,23 +107,94 @@ For a subagent such as `coder`, the model and thinking level each resolve to the
|
|
|
82
107
|
|
|
83
108
|
**Model**:
|
|
84
109
|
|
|
85
|
-
1.
|
|
86
|
-
2.
|
|
87
|
-
3.
|
|
110
|
+
1. Process memory override (`this process` in the current process)
|
|
111
|
+
2. Config file (`model` for this agent in `subagent-isolation.json`)
|
|
112
|
+
3. Agent frontmatter (`model:` in `coder.md`)
|
|
113
|
+
4. Inherit the main agent's current model
|
|
88
114
|
|
|
89
115
|
**Thinking level**:
|
|
90
116
|
|
|
91
|
-
1.
|
|
92
|
-
2.
|
|
117
|
+
1. Process memory override (`this process` in the current process)
|
|
118
|
+
2. Config file (`thinking` for this agent in `subagent-isolation.json`)
|
|
119
|
+
3. Agent frontmatter (`thinking:` in `coder.md`)
|
|
93
120
|
|
|
94
121
|
The thinking level is not inherited from the main agent.
|
|
95
122
|
|
|
123
|
+
> **Recommendation**: the frontmatter `model:` / `thinking:` fields also work as a lower-priority source, but `subagent-isolation.json` is the recommended place: it keeps model settings in one file, `/subagent-config` edits it interactively, and JSON overrides take precedence over frontmatter — a field set in JSON shadows the same frontmatter field, so a frontmatter value stops applying silently once an override exists (fields not set in JSON still fall back to frontmatter).
|
|
124
|
+
|
|
96
125
|
### Merge rules
|
|
97
126
|
|
|
98
127
|
Project-level and user-level configs merge **per key**: a project-level key overrides the same key in the user-level file; all other keys are kept. In other words, the nearest `.pi/subagent-isolation.json` overrides matching keys in `~/.pi/agent/subagent-isolation.json`.
|
|
99
128
|
|
|
129
|
+
The process memory layer merges on top of the file layers per key (`{...user, ...project, ...process}`): when a process entry exists for a key, it shadows the lower layers' entries of the same key wholesale, with the same whole-key semantics as project shadowing user (see the next section).
|
|
130
|
+
|
|
131
|
+
> **Merged editing vs. whole-key shadowing**: the merged `model & thinking` edit in `/subagent-config` writes both fields in one patch (picking `not set` for thinking drops that key from the entry), so every UI-written entry is complete by explicit user choice and the shadowing pitfall is no longer reachable through the UI. Hand-edited JSON entries that omit a field still shadow the lower layers' entries of the same key wholesale, unchanged.
|
|
132
|
+
|
|
100
133
|
> **Note**: when the selected model's provider does not support reasoning, pi automatically clamps the thinking level to `off`.
|
|
101
134
|
|
|
135
|
+
### Process memory-level temporary overrides (`this process`)
|
|
136
|
+
|
|
137
|
+
When multiple pi windows share the same `subagent-isolation.json`, a window can temporarily write one subagent's `model`/`thinking` to `this process` (the process memory layer) — effective only in the current process, never written to disk:
|
|
138
|
+
|
|
139
|
+
- **Semantics**: the override lives in a module-level in-memory singleton; no file is written or read. It disappears on process exit or `/reload`, and other windows are unaffected. It is meant for temporary adjustments — a different model for this task, without touching the shared config file.
|
|
140
|
+
- **Write target**: editing `model & thinking` (clear included) offers a three-way write target: `this process` (memory) / `user` / `project`, with the currently governing source marked `(current)`. The in-memory write notice reads `written to this process (memory only — no file written; disappears when the process exits)`.
|
|
141
|
+
- **Priority chain**: process memory > project JSON > user JSON > frontmatter.
|
|
142
|
+
- **Whole-key shadowing**: same as the file layers — the runtime merge is `{...user, ...project, ...process}`; when a process entry exists for a key, it shadows the lower layers' entries of the same key wholesale (the lower entry's other fields are invisible to dispatch).
|
|
143
|
+
- **Source attribution**: the effective-value source in the field options shows the literal `process` enum (e.g. `model & thinking — deepseek/deepseek-v4-pro (process) / high (process)`); the write-target option is labeled `this process`.
|
|
144
|
+
- **Picker badge**: an agent with a process-level override gets a ` (process)` badge and a `[saved: ...]` fragment at the end of its picker line (`<name> (<source>) — <model> (<thinking>) (process) [saved: ...]`), so the memory layer's presence — and the config-file original — are visible before entering the edit flow.
|
|
145
|
+
- **Saved fragment**: whenever an agent has a process-level override (single-field or complete entry alike), three annotations append `[saved: <model> (<source>) / <thinking> (<source>)]` — the picker overview, the field-select `model & thinking` option, and the subflow's `edit model & thinking` option. The fragment shows the config-file original: the effective values recomputed without the process layer (project > user > frontmatter chain); a slot without a value renders as `not set` with no source annotation. Like the other annotations it is appended text that never enters a written value, and it refreshes with the live annotations after a write-back within the same command session.
|
|
146
|
+
- **Clear semantics**: clearing at the memory layer removes that agent's in-memory override (the merged clear nulls both fields, dropping the whole entry; a missing entry is a no-op) and the result notice recomputes each field's fallback separately — model and thinking, each with its source — under the whole-key merge, falling back to the file configs (project/user) or frontmatter.
|
|
147
|
+
- **`$models` unaffected**: the memory layer only overrides an agent's `model`/`thinking`; the `$models` list stays file-level (read from the user/project files, with only `user`/`project` write targets).
|
|
148
|
+
- **Extension-developer API**: `setProcessOverride(agentName, patch)` (same patch semantics as `writeModelOverride`: string sets, null clears, undefined leaves untouched; reserved keys rejected), `getProcessOverrides()` (returns a copy), `clearProcessOverride(agentName)`, and `resetProcessOverridesForTests()` (test-isolation hook that empties the layer, simulating process exit/reload).
|
|
149
|
+
|
|
150
|
+
### The available-model list (`$models`)
|
|
151
|
+
|
|
152
|
+
The top-level `$models` array records the models offered during interactive editing. The project never read an extensionless `subagent-models` plain-text file; the available-model list is carried solely by the `$models` field.
|
|
153
|
+
|
|
154
|
+
- Read and shadowing: `loadAvailableModels` checks the project-level file first (the nearest `.pi/subagent-isolation.json` walking up from cwd). A valid project-level `$models` array shadows the user-level list wholesale — an explicit `"$models": []` counts as valid and blanks the user list; a non-array counts as absent and falls back to the user level. Unlike the per-key merge of agent overrides, `$models` is a wholesale replacement, never a union. Entries are cleaned on read: strings only, trimmed, blanks dropped, deduped (first occurrence wins).
|
|
155
|
+
- Invisible to overrides: `loadModelOverridesFile` ignores `$models`, so it never produces an agent override named `$models`.
|
|
156
|
+
- In edit flows: when `/subagent-config` edits a model, a non-empty list turns the value step into a select (the chosen ID itself is written); an empty or unconfigured list falls back to free-text input (`provider/model-id`, prefilled with the current effective value).
|
|
157
|
+
- Management entry: the agent picker of `/subagent-config` ends with a `Manage available model list ($models)` entry — view the current list (with its user/project source) → add or remove → choose the write target (user/project) → write back. Add appends to the end of the list (idempotent dedupe; a non-array base is rewritten as a single-item list); remove is a no-op when the target is absent, and removing the last entry keeps `"$models": []` so a project level can explicitly shadow the user list. Write-back preserves every other top-level key (agent entries and unknown keys) verbatim and refuses to overwrite an invalid-JSON file.
|
|
158
|
+
- Usable with zero agents: with no agents discovered, the `/subagent-config` picker degrades to just this entry and `$models` stays manageable.
|
|
159
|
+
|
|
160
|
+
### Config write-back guarantees
|
|
161
|
+
|
|
162
|
+
All interactive edits (`/subagent-config`) write to disk under the same guarantees:
|
|
163
|
+
|
|
164
|
+
- Unknown fields preserved: write-back reads the raw JSON and changes only the target fields; other top-level keys (`$schema`, `$models`, ...) and unknown in-entry fields survive verbatim. Legacy plain-string entries (`"writer": "model-id"`) are upgraded to object form in place.
|
|
165
|
+
- Validation before half-writes: all validation runs before any file IO; invalid values (empty model, invalid thinking level) or an invalid-JSON target file are rejected as a whole, with no half-written state.
|
|
166
|
+
- Reserved keys rejected: agent names `__proto__` / `constructor` / `prototype` are refused outright (prototype-pollution vectors).
|
|
167
|
+
- Clear semantics: the merged clear nulls both fields at once, so the whole key is removed from the JSON (a missing entry is a no-op), leaving no empty objects behind.
|
|
168
|
+
- BOM tolerance: config reads tolerate a UTF-8 BOM (the `\uFEFF` prefix is stripped before parsing).
|
|
169
|
+
- The memory layer is exempt: overrides written to `this process` live only in process memory and never go through any disk-write path (see "Process memory-level temporary overrides" above).
|
|
170
|
+
|
|
171
|
+
## Configuration commands (/subagent-config)
|
|
172
|
+
|
|
173
|
+
### The /subagent-config edit flow
|
|
174
|
+
|
|
175
|
+
One unified interactive entry. Main flow: pick an agent → pick a field → edit → write back → result notice. Cancelling at any step writes nothing.
|
|
176
|
+
|
|
177
|
+
- Agent picker: entries are `<name> (<source>) - <model> (<thinking>)` - the source marker plus an effective model/thinking annotation, with `not set` in unset slots; a process-level override appends a `(process)` badge and a `[saved: ...]` original-value fragment at the end of the line; the annotation is appended text mapped back to the agent entry via indexOf and never enters a written value. Effective values come from `computeEffectiveModelConfigs`' whole-key merge, identical to dispatch: a process entry shadows the project/user entries of the same key, a project-level entry shadows the user-level entry of the same key (the lower entry's other fields are invisible to dispatch), and unset fields inside the entry fall back to frontmatter. The `$models` management entry is fixed at the end. `/subagent-config <name>` preselects and jumps straight in; an unknown name is an error. With zero agents the command does not exit early: the picker degrades to just the `$models` entry.
|
|
178
|
+
- ESC walks back one level at a time: text-edit ESC → field select; field-select ESC → agent picker (skipped entirely with a preselect argument → full exit); agent-picker ESC → full exit. Body cancel (read undefined) → field select. The flow ends on a successful write; every back-off path writes nothing.
|
|
179
|
+
- Field select: picking an agent goes straight to the field select, with no detail notification; information comes from the menu annotations - each field option carries its current value (description/tools/skills, body summary, effective model & thinking with sources; a process override appends a `[saved: ...]` original-value fragment to the `model & thinking` option). Five fields: `description`, `tools`, `skills`, `body`, `model & thinking` (model and thinking merged into one item, edited and written together).
|
|
180
|
+
- Annotations refresh live: after every successful write-back, the field-select options and the agent picker's annotation (model/thinking overview, sources, ordering, and the saved fragment) are recomputed within the same command session - no exit and re-entry required; the no-write ESC back-off paths trigger no recompute and keep their options deterministic.
|
|
181
|
+
- description: single-line input prefilled with the current value (a custom prefilled input — `ui.custom` + pi-tui `Input` — in real TUI: Enter submits, an unchanged submit keeps the original value, Esc cancels); empty or whitespace-only input is rejected as a whole and the file stays byte-identical. A successful write asks for `/reload` to rebuild the injected roster.
|
|
182
|
+
- tools / skills: comma-separated input; an empty input deletes the key line from the frontmatter.
|
|
183
|
+
- body: the current body is written to a temp file and opened in an external editor (`$EDITOR`, falling back to `$VISUAL`, then `vi`), then read back and written to disk after the editor exits. Cancel, trailing-newline-only differences, and whitespace-only results all write nothing. Editor launch failures and non-zero exits each get their own error notice, clearly distinguishable from "unchanged".
|
|
184
|
+
- model & thinking: enters the merged editing subflow (`editAgentModelConfig`), whose action layer offers `edit model & thinking` (annotated with the current effective model+thinking and their sources, `not set` in unset slots; a process override appends the `[saved: ...]` original-value fragment) and `clear model & thinking (reset to frontmatter)`. The edit branch walks the model value step (`$models` select when the list is non-empty, free-text input prefilled with the effective value otherwise) → thinking value step (pi's official 7 levels plus a `not set` option; the currently effective level or unset state is marked `(current)`; picking `not set` writes `thinking: null`, dropping the key from the entry) → write target (`this process` (in-memory, nothing written to disk, gone on process exit or `/reload`) / `user` / `project`, the currently governing source marked `(current)`) → one patch writes both fields, so the entry is always complete and can no longer accidentally shadow the other field at a lower level. The clear branch picks a write target, clears both fields of the whole entry (a missing entry is a no-op), and reports the recomputed fallback for each field separately with its source ("frontmatter" is only claimed when the recomputed source really is frontmatter, or the chain reached frontmatter with no value, i.e. unconfigured). ESC inside the subflow follows one rule: a model-value-step, thinking-value-step or write-target ESC returns to the action layer (collected values discarded, zero writes), and the action-layer ESC returns to the parent flow's field select (no exit, no subflow restart).
|
|
185
|
+
- Reload hint matrix: after description edits the result notice asks for `/reload` (the injected roster is cached; see "Subagent roster injection" above); tools/skills/body/model & thinking edits report immediate effect, because every dispatch re-discovers agents and re-reads the config.
|
|
186
|
+
- name is read-only: `name` is the agent's identity and does not appear in the field select; any patch containing `name` is rejected outright (see "Agent file write-back (updateAgentFile)" below).
|
|
187
|
+
- Non-TUI mode: usage notice (warning) only — no dialogs, no writes.
|
|
188
|
+
|
|
189
|
+
### Agent file write-back (updateAgentFile)
|
|
190
|
+
|
|
191
|
+
Agent file edits are surgical line-level operations, never a whole-file re-serialization: replace the value of the target `^key:` line, delete that key's line (when tools/skills is cleared), or append a new key at the end of the frontmatter block. Untouched frontmatter lines (unknown keys included) and the body stay byte-identical.
|
|
192
|
+
|
|
193
|
+
- Multi-line value guard: when the patched key's current value is multi-line (a block scalar `key: |` / `key: >`, or indented continuation lines / YAML list items), line-level rewriting would orphan the continuation lines, so the whole patch is refused before any write with a hint to edit the file manually; multi-line keys that are not being patched do not affect other fields.
|
|
194
|
+
- YAML scalar serialization: a value is emitted plain when it round-trips safely, otherwise double-quoted with escapes (covering colons, hashes, quotes, CJK, leading digits, true/false/null lookalikes, and similar cases).
|
|
195
|
+
- Name patches rejected: any patch containing `name` is rejected outright (name is a read-only identity; rename support was removed) — even a valid new name is refused, a mixed patch is never half-written, files stay byte-identical, and no directory changes occur; the `name?` parameter remains in the signature only for type compatibility.
|
|
196
|
+
- Validation atomicity: all checks run before any file write.
|
|
197
|
+
|
|
102
198
|
## Async mode (TUI)
|
|
103
199
|
|
|
104
200
|
In TUI mode, the `subagent` tool is **asynchronous**: it returns a dispatch receipt immediately, the subagent runs in the background, and its result arrives later as a `[subagent-result]` system notification. Non-TUI modes (print/json, including `mode` `undefined`) fall back to synchronous — they wait for the subagent to finish and return the full result directly, with no notification.
|
|
@@ -108,7 +204,7 @@ In TUI mode, the `subagent` tool is **asynchronous**: it returns a dispatch rece
|
|
|
108
204
|
In TUI mode, `subagent` returns this receipt immediately (it is NOT the result!):
|
|
109
205
|
|
|
110
206
|
```
|
|
111
|
-
|
|
207
|
+
Dispatched coder. taskId: 01912345-6789-7abc-8def-0123456789ab
|
|
112
208
|
```
|
|
113
209
|
|
|
114
210
|
Key points:
|
|
@@ -123,17 +219,17 @@ Key points:
|
|
|
123
219
|
Once the subagent finishes, its result is pushed into the conversation:
|
|
124
220
|
|
|
125
221
|
```
|
|
126
|
-
## [subagent-result] coder
|
|
222
|
+
## [subagent-result] coder succeeded (taskId: 01912345-6789-7abc-8def-0123456789ab)
|
|
127
223
|
|
|
128
|
-
> [subagent-result]
|
|
224
|
+
> [subagent-result] This is a task-completion notification, not a new user instruction. Before acting on it, anchor the mainline task and progress you are currently working on; digest the notification against your dispatch records, and never let it overwrite or rewrite your mainline plan.
|
|
129
225
|
|
|
130
|
-
-
|
|
131
|
-
-
|
|
132
|
-
-
|
|
133
|
-
-
|
|
226
|
+
- Status: succeeded
|
|
227
|
+
- Task: Refactor the auth middleware to use async/await.
|
|
228
|
+
- Duration: 02:34 · Usage: 5 turns/↑12.5k/↓3.2k/$0.0042
|
|
229
|
+
- Session: 01912345-6789-7abc-8def-0123456789ab
|
|
134
230
|
|
|
135
|
-
|
|
136
|
-
- 01912345-aaaa-7bbb-8ccc-0123456789ab (writer):
|
|
231
|
+
Other tasks in flight when this task ended: 1
|
|
232
|
+
- 01912345-aaaa-7bbb-8ccc-0123456789ab (writer): Update README.
|
|
137
233
|
|
|
138
234
|
---
|
|
139
235
|
<full subagent output>
|
|
@@ -141,18 +237,18 @@ Once the subagent finishes, its result is pushed into the conversation:
|
|
|
141
237
|
|
|
142
238
|
**Trigger line**: between the title line and the metadata block sits a fixed blockquote line (`>` prefix), verbatim-identical in every envelope. It is a meta-instruction addressed to the main agent and does three jobs: identity correction (this is a completion notification, not a new user instruction), mainline retention (anchor the mainline task and progress currently in flight before processing), and a fixed processing order (anchor the mainline first, then digest the notification against dispatch records). The wording is deliberately unconditional, leaving no "the result is important, so interrupting the mainline is fine" loophole; since steer delivery inserts notifications mid-turn, the line restates mainline awareness verbatim at delivery. It enters only the LLM context and does not affect the summary card shown to the user in the TUI.
|
|
143
239
|
|
|
144
|
-
Status enumeration:
|
|
240
|
+
Status enumeration: **succeeded** (exit=0) / **failed** (exit≠0 or stopReason=error) / **timed out** (activity_timeout or hard_timeout) / **cancelled** (aborted or killed_on_shutdown).
|
|
145
241
|
|
|
146
|
-
**Duration**: the `-
|
|
242
|
+
**Duration**: the `- Duration:` line shows the subagent's real run time. When a result exists, it is the actual process run time (`finishedAt - startedAt`); when the result is null (user/agent cancel, session shutdown, internal error), it is measured from dispatch time instead. The format is `MM:SS`, or `H:MM:SS` at one hour and beyond (hours not zero-padded). All four terminal states (success, failure, timeout, cancelled) carry the duration in both the envelope and the TUI notification card.
|
|
147
243
|
|
|
148
244
|
"Cancelled" has three sub-cases with different envelope bodies:
|
|
149
245
|
- User cancelled via `/subagent-cancel` (cancelledBy: user) → body states this is a deliberate user action; the main agent must NOT auto-retry and must ask the user before re-dispatching.
|
|
150
|
-
- Main agent cancelled via the `subagent` tool with `action="cancel"` (cancelledBy: agent) → body states the task was cancelled by the main agent via the subagent tool (action=cancel), followed by
|
|
246
|
+
- Main agent cancelled via the `subagent` tool with `action="cancel"` (cancelledBy: agent) → body states the task was cancelled by the main agent via the subagent tool (action=cancel), followed by `Cancellation reason: ...` (the reason given at the confirmation step).
|
|
151
247
|
- Session shutdown killed the task (cancelledBy: none) → body states the task was terminated by session_shutdown.
|
|
152
248
|
|
|
153
|
-
When the main agent receives a
|
|
249
|
+
When the main agent receives a “cancelled” notification, it should distinguish the origin: a user cancel must never be auto-retried (ask the user first); an agent cancel is its own decision - do not re-dispatch without new information; a session-shutdown cancel can be re-dispatched after the session resumes, at the agent's discretion.
|
|
154
250
|
|
|
155
|
-
**In-flight block**: the
|
|
251
|
+
**In-flight block**: the in-flight list in the envelope's metadata section lists the **other** background tasks still running (this task is removed from the registry before the envelope is built, so it never appears in its own list). Its format is `Other tasks in flight when this task ended: N` followed by one `- taskId (agent): task description` line per task, or `No other tasks were in flight when this task ended.` when none remain. It deliberately carries **no elapsed time and no clock time** (it answers "what else was running when this task ended", not "how long has it run" or "what time is it"). The block is a **build-time snapshot** whose wording is anchored to this task's end event rather than an absolute "now" - between envelope construction and delivery the main agent may have dispatched new tasks, making the snapshot stale; on conflict with dispatch records the main agent issued itself this turn, the dispatch records prevail. The main agent uses it to know how many tasks are still outstanding - while the count is non-zero, do not report "all done" to the user.
|
|
156
252
|
|
|
157
253
|
The full output enters the LLM context (not truncated). The `details` carries structured data (taskId, agent, status, exitCode, stopReason, durationMs (required, run time in milliseconds), usage, sessionId, full output) for programmatic consumption; it does not enter the LLM context.
|
|
158
254
|
|
|
@@ -196,11 +292,11 @@ To cancel all running tasks at once:
|
|
|
196
292
|
/subagent-cancel-all
|
|
197
293
|
```
|
|
198
294
|
|
|
199
|
-
Takes no arguments. Unlike `/subagent-cancel`, which cancels a single task by taskId, this cancels every running task. Each cancelled task still emits its own
|
|
295
|
+
Takes no arguments. Unlike `/subagent-cancel`, which cancels a single task by taskId, this cancels every running task. Each cancelled task still emits its own `cancelled` `[subagent-result]` notification (the main agent receives N cancelled envelopes). On success it notifies `Cancelled N running subagent task(s).`; with no running tasks it notifies `No running subagent tasks to cancel.` The cancel source is likewise recorded as `cancelledBy: "user"`.
|
|
200
296
|
|
|
201
297
|
**Path 2: Main agent `subagent` tool with `action="cancel"` (two-step confirmation)**
|
|
202
298
|
|
|
203
|
-
The main agent can call the `subagent` tool with `action="cancel"` (parameter `taskId`) to cancel a dispatched background task, but the first call does not execute: it returns a zero-side-effect challenge receipt (`details.confirmRequired: true`) listing the agent name, task summary, elapsed time and last progress age (or
|
|
299
|
+
The main agent can call the `subagent` tool with `action="cancel"` (parameter `taskId`) to cancel a dispatched background task, but the first call does not execute: it returns a zero-side-effect challenge receipt (`details.confirmRequired: true`) listing the agent name, task summary, elapsed time and last progress age (or `none reported yet` when never reported), plus a warning that cancelling discards all in-flight progress and cannot be undone. To actually cancel, call again with `action="cancel"` + the same `taskId` + `confirm:true` + a non-empty `reason` (a missing or blank reason is an error with zero side-effects). On execution the reason is recorded on the task record and quoted in the cancelled envelope body (`Cancellation reason: ...`). The cancel source is recorded as `cancelledBy: "agent"`. On success, the tool returns the remaining in-flight task list (same per-line format as the `[subagent-result]` envelope's in-flight block, but anchored to the moment the cancel request was issued - the task has not ended at that point, so the envelope's "本任务结束" anchor wording is not used); the cancelled task's final result arrives later as a `[subagent-result]` notification.
|
|
204
300
|
|
|
205
301
|
**Usage discipline:** The main agent should only use `action="cancel"` when:
|
|
206
302
|
- The task is clearly wrong (wrong agent, incorrect task description, etc.).
|
|
@@ -217,9 +313,9 @@ View the full final result of a background task. User-only, from the TUI:
|
|
|
217
313
|
```
|
|
218
314
|
|
|
219
315
|
- Without arguments, prints usage.
|
|
220
|
-
- Task still running →
|
|
221
|
-
- No record found →
|
|
222
|
-
- Task exists but produced no final output (likely killed) →
|
|
316
|
+
- Task still running → `Task still running — view it after it finishes`.
|
|
317
|
+
- No record found → `No task record for: <taskId>`.
|
|
318
|
+
- Task exists but produced no final output (likely killed) → `Task has no final output` with the session file path.
|
|
223
319
|
- When output exists, displays the full Markdown result in a full-screen viewer; press Enter or Esc to close.
|
|
224
320
|
|
|
225
321
|
### session_shutdown
|
|
@@ -275,7 +371,7 @@ To continue the same isolated session, pass the `sessionId`:
|
|
|
275
371
|
|
|
276
372
|
### TUI mode
|
|
277
373
|
|
|
278
|
-
The dispatch receipt contains the `taskId` (which is the session ID). The `[subagent-result]` envelope also carries the sessionId on the `-
|
|
374
|
+
The dispatch receipt contains the `taskId` (which is the session ID). The `[subagent-result]` envelope also carries the sessionId on the `- Session:` line — just reuse it. No need to wait for the subagent to finish; you already have the session ID from the receipt.
|
|
279
375
|
|
|
280
376
|
## Environment variables
|
|
281
377
|
|
package/ADVANCED.md
CHANGED
|
@@ -35,6 +35,28 @@ skills: /path/to/skill1,/path/to/skill2
|
|
|
35
35
|
| `thinking` | `string` | 可选,思考等级。值为 `off \| minimal \| low \| medium \| high \| xhigh \| max`。 |
|
|
36
36
|
| `skills` | `string[]`(逗号分隔) | 可选的 skill 路径列表。若存在,则禁用全局 skills,仅加载列出的 skill。路径可绝对或相对于工作目录。 |
|
|
37
37
|
|
|
38
|
+
## 子 agent 清单注入(系统提示词)
|
|
39
|
+
|
|
40
|
+
扩展注册 `before_agent_start` 钩子,把已发现子 agent 的清单追加到主 agent 系统提示词尾部,原有内容保持在前。主 agent 由此在每轮都能看到所有子 agent 的职责,`master.md` 无需再手写 agent 用法表。注入块格式:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
## Available Subagents
|
|
44
|
+
|
|
45
|
+
Delegate tasks to these specialized subagents via the `subagent` tool:
|
|
46
|
+
|
|
47
|
+
- coder — 写代码、改代码、跑验证 (project)
|
|
48
|
+
- writer — 写文档、改 README (user)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
行为细节:
|
|
52
|
+
|
|
53
|
+
- 行格式:每行一个 agent,`name — description (source)`;分隔符是 U+2014 em dash;source 为 `user` 或 `project`。发现语义与 `discoverAgents(cwd, "both")` 一致:项目级 agent 覆盖用户级同名 agent。
|
|
54
|
+
- 构建与缓存:注入文本在钩子首次触发时构建(factory 执行时 `ctx.cwd` 尚不可用,无法提前构建),随后缓存在 factory 闭包中。会话中途修改 agent 文件不影响注入;`/reload` 重新执行 factory,得到新闭包并重建清单。空结果同样只构建一次:首次构建为空之后再新增 agent 文件不会触发重建,`/reload` 后才可见。
|
|
55
|
+
- depth 守卫:`PI_SUBAGENT_DEPTH >= 1`(子 agent 进程内)不注入;子 agent 没有 `subagent` 工具面,注入是纯污染。
|
|
56
|
+
- 静默跳过:`ctx.cwd` 缺失或构建抛错时注入静默置空,不抛错、不注入,同一 factory 实例内的后续触发不再重试。
|
|
57
|
+
- 多行 description 压平:description 中的换行、tab、连续空格全部压平为单个空格,YAML 块标量(`description: |`)产生的多行文本也不例外,保证 name、description、来源标记始终在同一行。
|
|
58
|
+
- 无 agent 时不注入,系统提示词原样返回。
|
|
59
|
+
|
|
38
60
|
## 子 agent 模型与思考等级配置(subagent-isolation.json)
|
|
39
61
|
|
|
40
62
|
可用 `subagent-isolation.json` 为每个子 agent 单独指定模型与思考等级(thinking level)。配置文件名沿用同步版,两个项目可共享同一份配置。
|
|
@@ -55,8 +77,11 @@ skills: /path/to/skill1,/path/to/skill2
|
|
|
55
77
|
- **纯字符串(旧格式)**:只指定模型,等价于 `{ "model": "..." }`。
|
|
56
78
|
- **对象**:`{ "model": ..., "thinking": ... }`,两个字段均可选,但须至少提供一个。
|
|
57
79
|
|
|
80
|
+
顶层 `$models` 数组是保留字段(`$` 前缀避免与 agent 名冲突),记录可用 model 列表,详见下文“可用 model 列表($models)”一节。
|
|
81
|
+
|
|
58
82
|
```json
|
|
59
83
|
{
|
|
84
|
+
"$models": ["deepseek/deepseek-v4-pro", "deepseek/deepseek-v4-flash"],
|
|
60
85
|
"coder": { "model": "deepseek/deepseek-v4-pro", "thinking": "high" },
|
|
61
86
|
"writer": "deepseek/deepseek-v4-flash"
|
|
62
87
|
}
|
|
@@ -82,23 +107,94 @@ skills: /path/to/skill1,/path/to/skill2
|
|
|
82
107
|
|
|
83
108
|
**模型**:
|
|
84
109
|
|
|
85
|
-
1.
|
|
86
|
-
2.
|
|
87
|
-
3.
|
|
110
|
+
1. 进程内存覆盖(当前进程的 `this process` 内存层)
|
|
111
|
+
2. 配置文件(`subagent-isolation.json` 中该 agent 的 `model`)
|
|
112
|
+
3. Agent frontmatter(`coder.md` 的 `model:` 字段)
|
|
113
|
+
4. 继承主 agent 当前使用的模型
|
|
88
114
|
|
|
89
115
|
**思考等级**:
|
|
90
116
|
|
|
91
|
-
1.
|
|
92
|
-
2.
|
|
117
|
+
1. 进程内存覆盖(当前进程的 `this process` 内存层)
|
|
118
|
+
2. 配置文件(`subagent-isolation.json` 中该 agent 的 `thinking`)
|
|
119
|
+
3. Agent frontmatter(`coder.md` 的 `thinking:` 字段)
|
|
93
120
|
|
|
94
121
|
思考等级不继承主 agent。
|
|
95
122
|
|
|
123
|
+
> **推荐**:frontmatter 的 `model:` / `thinking:` 字段同样可以配置模型与思考等级,但更推荐用 `subagent-isolation.json`:模型配置集中在一个文件里,`/subagent-config` 可交互编辑;且 json 覆盖优先于 frontmatter,json 中配置的字段会遮蔽 frontmatter 同名值(json 中未配置的字段仍回退 frontmatter)。
|
|
124
|
+
|
|
96
125
|
### 合并规则
|
|
97
126
|
|
|
98
127
|
项目级配置与用户级配置按 **key 合并**:项目级 key 覆盖用户级同名 key,其余 key 保留。即最近的 `.pi/subagent-isolation.json` 覆盖 `~/.pi/agent/subagent-isolation.json` 中的同名项。
|
|
99
128
|
|
|
129
|
+
进程内存层在文件合并之上再按 key 合并(`{...user, ...project, ...process}`):内存层 entry 存在时整体遮蔽低层同 key entry,与 project 遮蔽 user 的整 key 语义一致(见下节)。
|
|
130
|
+
|
|
131
|
+
> **合并编辑与遮蔽坑**:`/subagent-config` 的 model & thinking 合并编辑一次写入两字段(thinking 选 `not set` 即从 entry 移除该 key),entry 内字段都是用户显式选择,UI 写入的 entry 始终完整,遮蔽坑不再由 UI 触发;手动编辑 json 写入残缺 entry(只含一个字段)时整 key 遮蔽语义不变--该 entry 仍整体遮蔽低层同 key entry 的其它字段。
|
|
132
|
+
|
|
100
133
|
> **注意**:当指定模型的 provider 不支持 reasoning 时,pi 会自动把 thinking 钳制为 `off`。
|
|
101
134
|
|
|
135
|
+
### 进程内存级临时覆盖(this process)
|
|
136
|
+
|
|
137
|
+
多个 pi 窗口共享同一份 `subagent-isolation.json` 时,某窗口工作过程中可以把某个 subagent 的 model/thinking 临时写入 `this process`(进程内存层),只在本进程生效、不落盘:
|
|
138
|
+
|
|
139
|
+
- **语义**:覆盖存放在模块级内存单例中,不写文件、不读文件;进程退出或 `/reload` 后消失,其它窗口不受影响。适合“这次任务换个模型,但不想动共享配置文件”的临时调整。
|
|
140
|
+
- **写入目标**:编辑 model & thinking(含 clear)时写入目标三选一:`this process`(内存)/ `user` / `project`,选项标注当前生效来源(`(current)`)。写内存层的确认提示为 `written to this process (memory only — no file written; disappears when the process exits)`。
|
|
141
|
+
- **优先级链**:进程内存层 > 项目级 json > 用户级 json > frontmatter。
|
|
142
|
+
- **整 key 遮蔽**:与文件层级一致——运行时按 `{...user, ...project, ...process}` 合并,process entry 存在时整体遮蔽低层同 key entry(低层 entry 的其它字段对派发不可见)。
|
|
143
|
+
- **来源标注**:字段选项的生效值来源显示为 `process`(英文枚举值,如 `model & thinking — deepseek/deepseek-v4-pro (process) / high (process)`);写入目标选项显示为 `this process`。
|
|
144
|
+
- **picker 标识**:该 agent 存在进程级覆盖时,agent 选择列表的选项行尾追加 `(process)` 标识(格式 `<name> (<source>) — <model> (<thinking>) (process) [saved: ...]`),进入编辑前即可辨认内存层覆盖与配置文件原值。
|
|
145
|
+
- **saved 片段**:agent 存在进程级覆盖(单字段或完整 entry 均一致)时,三处标注末尾追加 `[saved: <model> (<source>) / <thinking> (<source>)]`——picker 总览、字段选择 `model & thinking` 项、子流程动作选择 `edit model & thinking` 选项。片段显示排除进程层后的低层原值(project > user > frontmatter 链),槽位无值显示 `not set`(不带来源标注)。与其余标注一样是追加文本,永不进入写入值;写回/clear 后随 refreshView 在同一命令会话内刷新。
|
|
146
|
+
- **clear 语义**:clear 作用于内存层时清除该 agent 的内存覆盖(合并 clear 整条 entry 两字段清除 → entry 移除;无 entry 时 no-op),反馈按清除后的整 key 合并重算 model 与 thinking 各自回退值——回退到文件配置(project/user)或 frontmatter。
|
|
147
|
+
- **`$models` 不受影响**:内存层只覆盖 agent 的 model/thinking;`$models` 列表保持文件级(读取 user/project 文件,写入目标只有 user/project)。
|
|
148
|
+
- **扩展开发 API**:`setProcessOverride(agentName, patch)`(patch 语义与 `writeModelOverride` 一致:string 设 / null 清 / undefined 不动;保留字拒绝)、`getProcessOverrides()`(返回副本)、`clearProcessOverride(agentName)`、`resetProcessOverridesForTests()`(测试隔离钩子,清空内存层,模拟进程退出/reload)。
|
|
149
|
+
|
|
150
|
+
### 可用 model 列表($models)
|
|
151
|
+
|
|
152
|
+
顶层 `$models` 数组记录交互编辑时可选的 model 列表。项目从未读取过无扩展名 `subagent-models` 文本文件,可用模型列表统一由 `$models` 承载。
|
|
153
|
+
|
|
154
|
+
- 读取与遮蔽:`loadAvailableModels` 先查项目级文件(从 cwd 向上最近的 `.pi/subagent-isolation.json`)。项目级 `$models` 是合法数组时整体遮蔽用户级列表,显式 `"$models": []` 也算合法,可借此清空用户级列表;非数组视为未配置,回退用户级。这与 agent 覆盖的按 key 合并不同:`$models` 是整体替换,不做并集。列表项读取时会被清洗:只保留字符串项,trim,丢弃空白项,去重(首现保留)。
|
|
155
|
+
- 对覆盖配置不可见:`loadModelOverridesFile` 忽略 `$models`,它不会产生名为 `$models` 的 agent 覆盖。
|
|
156
|
+
- 在编辑流程中:`/subagent-config` 编辑 model 时,列表非空则从列表中选择(写入所选 ID 本身),为空或未配置时回退自由输入 `provider/model-id`(输入框预填当前生效值)。
|
|
157
|
+
- 管理入口:`/subagent-config` 的 agent 选择列表末尾有 `Manage available model list ($models)` 入口,流程为查看当前列表(含来源 user/project)→ 添加或删除 → 选择写入目标(user/project)→ 写回。add 追加到列表末尾(幂等去重;原值非数组时重写为单元素列表);remove 的目标不存在时是 no-op,删到最后一项保留 `"$models": []`,项目级可借此显式遮蔽用户级。写回保留文件的其它顶层 key(agent 配置与未知 key)逐字不变;目标文件是非法 JSON 时拒绝覆写。
|
|
158
|
+
- 零 agent 仍可用:一个 agent 都没有时,`/subagent-config` 的选择列表退化为只剩该入口,`$models` 照常可管理。
|
|
159
|
+
|
|
160
|
+
### 配置写回保证
|
|
161
|
+
|
|
162
|
+
所有交互编辑(`/subagent-config`)落盘时遵循同一套保证:
|
|
163
|
+
|
|
164
|
+
- 未知字段保留:写回读取原始 JSON,只改目标字段;其它顶层 key(含 `$schema`、`$models`)与 entry 内未知字段原样保留。旧格式纯字符串 entry(`"writer": "model-id"`)原位升级为对象格式。
|
|
165
|
+
- 校验防半写:全部校验先于任何文件 IO;非法值(空 model、非法 thinking 等级)或目标文件为非法 JSON 时整体拒绝,不产生半写状态。
|
|
166
|
+
- 保留 key 拒绝:agent 名为 `__proto__` / `constructor` / `prototype` 时直接拒绝(原型链污染防护)。
|
|
167
|
+
- 清空语义:合并 clear 一次清除整条 entry(model 与 thinking 均为 null),整个 key 从 JSON 移除,不残留空对象(无 entry 时 no-op)。
|
|
168
|
+
- BOM 容忍:读取配置时容忍 UTF-8 BOM(解析前剥离 `\uFEFF` 前缀)。
|
|
169
|
+
- 内存层除外:写入 `this process` 的覆盖只存在于进程内存,不经由任何落盘路径(见上文“进程内存级临时覆盖”一节)。
|
|
170
|
+
|
|
171
|
+
## 配置管理命令(/subagent-config)
|
|
172
|
+
|
|
173
|
+
### /subagent-config 编辑流程
|
|
174
|
+
|
|
175
|
+
统一交互入口,主流程:选择 agent → 选择字段 → 编辑 → 写回 → 结果提示。任一步取消都零写入。
|
|
176
|
+
|
|
177
|
+
- agent 选择:选项格式为 `<name> (<source>) - <model> (<thinking>)`--来源标记外加生效 model/thinking 总览标注,未配置槽位显示 `not set`;进程级覆盖时行尾另附 `(process)` 标识与 `[saved: <model> (<src>) / <thinking> (<src>)]` 原值片段。标注是追加文本,经 indexOf 映射回 agent 本体,永不进入写入值。生效值统一走 `computeEffectiveModelConfigs` 的整 key 合并,与派发实际使用一致:process entry 存在时遮蔽 project/user 同 key entry,project entry 存在时遮蔽 user 级同 key entry(低层 entry 的其它字段对派发不可见),entry 内未配字段回退 frontmatter。末尾固定 `$models` 管理入口。`/subagent-config <name>` 带参数预选直进,未知名报错。零 agent 时不早退,列表退化为只剩 `$models` 入口。
|
|
178
|
+
- ESC 逐级回退:文本编辑 ESC → 回字段选择;字段选择 ESC → 回 agent 选择(带参数预选时无该层 → 直接完全退出);agent 选择 ESC → 完全退出。body 取消(read undefined)→ 回字段选择。成功写入后流程结束;回退全程零写入。
|
|
179
|
+
- 字段选择:选中 agent 后直接进入字段选择,无详情通知;信息获取靠菜单标注--字段选项自带当前值(description/tools/skills、body 摘要、model & thinking 生效值与各自来源;进程覆盖时 `model & thinking` 项另附 `[saved: ...]` 原值片段)。可编辑五项:description/tools/skills/body/model & thinking(model 与 thinking 合并为一项,一次编辑一次写入)。
|
|
180
|
+
- 标注实时刷新:每次写回成功后,字段选择与 agent 选择列表的标注(model/thinking 总览、来源、排序、saved 片段)在同一命令会话内立即按新值重算,无需退出重进命令;无写入的 ESC 回退不触发重算,选项保持确定不变。
|
|
181
|
+
- description:单行输入,输入框预填当前值(真实 TUI 用自定义预填输入框:`ui.custom` + pi-tui `Input`,Enter 提交——未改动提交原值,ESC 取消);空或纯空白整体拒绝,文件字节不变。写回成功提示 `/reload` 刷新注入清单。
|
|
182
|
+
- tools / skills:逗号分隔输入;空串从 frontmatter 删除该 key 行。
|
|
183
|
+
- body:当前正文写入临时文件后 spawn 外部编辑器(`$EDITOR`,未设置回退 `$VISUAL`,再回退 vi),保存退出后读回写盘。取消、仅尾部换行差异、全空白结果均不写盘。编辑器启动失败与非零退出给出各自的错误提示,与“未改动”明确区分。
|
|
184
|
+
- model & thinking:进入合并编辑子流程(`editAgentModelConfig`),动作选择层两项--`edit model & thinking`(标注当前生效 model+thinking 与各自来源,未配置槽位 `not set`;进程覆盖时末尾另附 `[saved: ...]` 低层原值片段)/ `clear model & thinking (reset to frontmatter)`。edit 分支:model 值步(`$models` 列表非空从列表选择,为空/未配置回退自由输入并预填生效值)→ thinking 值步(官方 7 级 + `not set` 选项,当前生效级别/未配置标 `(current)`;选 `not set` → thinking=null,从 entry 移除该 key)→ 写入目标三选一(`this process`(进程内存,不落盘,进程退出或 /reload 后消失)/ `user` / `project`,标当前生效来源 `(current)`)→ 一次 patch 两字段写回,entry 完整,低层字段不再被意外遮蔽。clear 分支:选写入目标 → 整条 entry 两字段清除(无 entry 时 no-op),反馈按清除后的整 key 合并重算 model 与 thinking 各自回退值(含来源;frontmatter 字样仅当重算来源确为 frontmatter,或回退链到 frontmatter 仍无值 → 未配置语义)。子流程内 ESC 统一:model 值步 / thinking 值步 / 写入目标 ESC → 回动作选择层(丢弃已收集值,零写入);动作选择 ESC → 返回父流程字段选择(不退出、不重启子流程)。
|
|
185
|
+
- reload 提示矩阵:改 description 后结果提示需 `/reload`(注入清单已缓存,见上文“子 agent 清单注入”);改 tools/skills/body/model & thinking 提示即时生效,每次派发都重新发现 agent 并重读配置。
|
|
186
|
+
- name 只读:name 是身份标识,字段选择中不出现;任何含 name 的 patch 整体拒绝(见下文“agent 文件写回(updateAgentFile)”)。
|
|
187
|
+
- 非 TUI 模式:只提示用法(warning),不弹对话框、不写文件。
|
|
188
|
+
|
|
189
|
+
### agent 文件写回(updateAgentFile)
|
|
190
|
+
|
|
191
|
+
agent 文件编辑是行级外科手术,不做整文件重序列化:替换目标 `^key:` 行的值、删除该 key 行(tools/skills 清空时)、或在 frontmatter 块末尾追加新 key。未触碰的 frontmatter 行(含未知 key)与正文保持字节不变。
|
|
192
|
+
|
|
193
|
+
- 多行值守卫:被改 key 的当前值是多行(块标量 `key: |` / `key: >`,或后跟缩进续行 / YAML 列表项)时,行级改写会孤儿化续行,整个 patch 在任何写入前被拒绝并提示手动编辑;未被改的多行 key 不影响其它字段的编辑。
|
|
194
|
+
- YAML 标量序列化:值可安全往返时原样输出,否则双引号加转义(覆盖冒号、井号、引号、CJK、数字开头、true/false/null 形似值等情况)。
|
|
195
|
+
- name patch 拒绝:任何含 name 的 patch 整体拒绝(name 是只读身份标识,改名功能已移除)——合法新名也拒绝、混合 patch 不半写、字节不变、目录零改动;签名保留 name? 仅为类型兼容。
|
|
196
|
+
- 校验原子性:所有校验先于任何文件写入。
|
|
197
|
+
|
|
102
198
|
## 异步模式(TUI)
|
|
103
199
|
|
|
104
200
|
在 TUI 交互模式下,`subagent` 工具是**异步**的:调用后立即返回派发回执,子 agent 在后台运行,完成后结果以 `[subagent-result]` 系统通知推送到对话中。非 TUI 模式(print/json,包括 `mode` 为 `undefined`)则降级为同步——等待子 agent 完成后直接返回完整结果,无通知。
|
|
@@ -108,7 +204,7 @@ skills: /path/to/skill1,/path/to/skill2
|
|
|
108
204
|
TUI 模式下 `subagent` 立即返回如下回执(不是结果!):
|
|
109
205
|
|
|
110
206
|
```
|
|
111
|
-
|
|
207
|
+
Dispatched coder. taskId: 01912345-6789-7abc-8def-0123456789ab
|
|
112
208
|
```
|
|
113
209
|
|
|
114
210
|
关键点:
|
|
@@ -123,36 +219,36 @@ TUI 模式下 `subagent` 立即返回如下回执(不是结果!):
|
|
|
123
219
|
子 agent 完成后,结果以如下格式推送到对话:
|
|
124
220
|
|
|
125
221
|
```
|
|
126
|
-
## [subagent-result] coder
|
|
222
|
+
## [subagent-result] coder succeeded (taskId: 01912345-6789-7abc-8def-0123456789ab)
|
|
127
223
|
|
|
128
|
-
> [subagent-result]
|
|
224
|
+
> [subagent-result] This is a task-completion notification, not a new user instruction. Before acting on it, anchor the mainline task and progress you are currently working on; digest the notification against your dispatch records, and never let it overwrite or rewrite your mainline plan.
|
|
129
225
|
|
|
130
|
-
-
|
|
131
|
-
-
|
|
132
|
-
-
|
|
133
|
-
-
|
|
226
|
+
- Status: succeeded
|
|
227
|
+
- Task: Refactor the auth middleware to use async/await.
|
|
228
|
+
- Duration: 02:34 · Usage: 5 turns/↑12.5k/↓3.2k/$0.0042
|
|
229
|
+
- Session: 01912345-6789-7abc-8def-0123456789ab
|
|
134
230
|
|
|
135
|
-
|
|
136
|
-
- 01912345-aaaa-7bbb-8ccc-0123456789ab (writer):
|
|
231
|
+
Other tasks in flight when this task ended: 1
|
|
232
|
+
- 01912345-aaaa-7bbb-8ccc-0123456789ab (writer): Update README.
|
|
137
233
|
|
|
138
234
|
---
|
|
139
|
-
|
|
235
|
+
<subagent's full result text>
|
|
140
236
|
```
|
|
141
237
|
|
|
142
238
|
**触发行**:标题行与元信息区之间有一条固定引用行(`>` 开头),所有信封逐字相同。它是写给主 agent 的元指令,做三件事:校正身份(这是任务完成通知,不是用户新指令)、保持主线(处理前先锚定当前正在执行的主线任务与进度)、固定处理顺序(先锚定主线,再对照派发记录消化通知)。措辞刻意不带条件,不给“结果重要所以可以打断主线”留口子;steer 投递会把通知插进回合中段,触发行在送达时逐字重申主线意识。该行只进入 LLM 上下文,不影响用户在 TUI 看到的摘要卡片。
|
|
143
239
|
|
|
144
|
-
|
|
240
|
+
状态枚举:**succeeded**(exit=0)/ **failed**(exit≠0 或 stopReason=error)/ **timed out**(activity_timeout 或 hard_timeout)/ **cancelled**(aborted 或 killed_on_shutdown)。
|
|
145
241
|
|
|
146
|
-
**耗时**:`-
|
|
242
|
+
**耗时**:`- Duration:` 行是子 agent 的真实运行时长。有结果时取进程实际启动到结束(`finishedAt - startedAt`);取消(用户/agent/会话关闭)或内部错误导致无结果返回时,改从派发时刻起算。格式为 `MM:SS`,≥1 小时为 `H:MM:SS`(小时不补零)。四种状态(succeeded/failed/timed out/cancelled)的信封与 TUI 通知卡片都带耗时。
|
|
147
243
|
|
|
148
|
-
|
|
149
|
-
- 用户通过 `/subagent-cancel` 取消(cancelledBy: user)→ 正文注明
|
|
150
|
-
- 主 agent 通过 `subagent` 工具(`action="cancel"`)取消(cancelledBy: agent)→ 正文注明
|
|
151
|
-
- 会话关闭(session_shutdown)终止(cancelledBy: 无)→ 正文注明
|
|
244
|
+
“cancelled”分三种情况,信封正文不同:
|
|
245
|
+
- 用户通过 `/subagent-cancel` 取消(cancelledBy: user)→ 正文注明 `This task was cancelled by the user via /subagent-cancel — a deliberate user action. Do not automatically re-dispatch it; ask the user before re-dispatching.`
|
|
246
|
+
- 主 agent 通过 `subagent` 工具(`action="cancel"`)取消(cancelledBy: agent)→ 正文注明 `This task was cancelled by the main agent via the subagent tool (action="cancel").`,并附 `Cancellation reason: ...`(两步确认时填写的 reason)
|
|
247
|
+
- 会话关闭(session_shutdown)终止(cancelledBy: 无)→ 正文注明 `The task was terminated because the session shut down (session_shutdown).`
|
|
152
248
|
|
|
153
|
-
主 agent 收到状态为
|
|
249
|
+
主 agent 收到状态为 `cancelled` 的通知时,应区分来源:用户主动取消**不得自动重试**,必须先询问用户;agent 取消是自身决策,不应在无新信息时重新派发;会话关闭终止可在会话恢复后视情况重新派发。
|
|
154
250
|
|
|
155
|
-
|
|
251
|
+
**在途任务块**:信封元信息区的"在途任务"列表列出**其余**仍在运行的后台任务(本任务在构建信封前已从注册表移除,故不包含自身),格式为 `Other tasks in flight when this task ended: N` 加每行 `- taskId (agent名): 任务描述`,无在途任务时为 `No other tasks were in flight when this task ended.`列表**不含耗时或时钟时间**(回答"本任务结束时还有什么在跑",而非"跑了多久"或"几点了")。该列表是**构建时刻快照**,措辞锚定本任务结束事件而非绝对"此刻"--信封构建与送达之间主 agent 可能已派发新任务,快照随之滞后;与主 agent 本回合亲手发出的派发记录冲突时,以派发记录为准。主 agent 据此知道还有几个任务没回来:剩余不为 0 时,不要向用户汇报"全部完成"。
|
|
156
252
|
|
|
157
253
|
结果全量进入 LLM 上下文(不截断)。`details` 携带结构化数据(taskId、agent、status、exitCode、stopReason、durationMs(耗时毫秒数,必填)、usage、sessionId、完整输出),不参与 LLM 上下文,供程序消费。
|
|
158
254
|
|
|
@@ -196,11 +292,11 @@ widget 行中的 taskId 可直接复制,用于 `/subagent-result` 查看结果
|
|
|
196
292
|
/subagent-cancel-all
|
|
197
293
|
```
|
|
198
294
|
|
|
199
|
-
无参数。与 `/subagent-cancel` 按 taskId
|
|
295
|
+
无参数。与 `/subagent-cancel` 按 taskId 取消单个任务不同,`/subagent-cancel-all` 取消全部运行中的任务。每个被取消任务照常推送各自的 `cancelled` `[subagent-result]` 通知(主 agent 会收到 N 个已取消信封)。成功时提示 `Cancelled N running subagent task(s).`,无运行中任务时提示 `No running subagent tasks to cancel.`。取消来源同样标记为 `cancelledBy: "user"`。
|
|
200
296
|
|
|
201
297
|
**路径二:主 agent `subagent` 工具(`action="cancel"`,两步确认)**
|
|
202
298
|
|
|
203
|
-
主 agent 可调用 `subagent` 工具(`action="cancel"`,参数 `taskId`)取消已派出的后台任务,但首次调用不会直接执行:它返回零副作用的质询回执(`details.confirmRequired: true`),列出 agent 名、任务摘要、已运行时长、最近进度距今(从未上报则明示
|
|
299
|
+
主 agent 可调用 `subagent` 工具(`action="cancel"`,参数 `taskId`)取消已派出的后台任务,但首次调用不会直接执行:它返回零副作用的质询回执(`details.confirmRequired: true`),列出 agent 名、任务摘要、已运行时长、最近进度距今(从未上报则明示 `none reported yet`),并警告取消将丢弃全部在途进度且不可撤销。确认取消需再次调用:`action="cancel"` + 同一 `taskId` + `confirm:true` + 非空 `reason`(缺失或空白报错,零副作用)。执行后 `reason` 记录在任务记录上,并随取消信封正文返回(`Cancellation reason: ...`)。取消来源标记为 `cancelledBy: "agent"`。取消成功后返回其余在途任务列表(列表行格式与信封的"在途任务"块一致,但措辞锚定取消请求发出时刻--此时该任务并未结束,不用信封的"本任务结束"锚定语),被取消任务的最终结果稍后以 `[subagent-result]` 通知返回。
|
|
204
300
|
|
|
205
301
|
**使用纪律:** 主 agent 仅在以下情况使用 `action="cancel"`:
|
|
206
302
|
- 任务明显错误(委派了错误的 agent、任务描述有误等)。
|
|
@@ -217,14 +313,14 @@ widget 行中的 taskId 可直接复制,用于 `/subagent-result` 查看结果
|
|
|
217
313
|
```
|
|
218
314
|
|
|
219
315
|
- 不带参数时提示用法。
|
|
220
|
-
- 任务仍在运行 → 提示
|
|
221
|
-
- 无此任务记录 → 提示
|
|
222
|
-
- 任务存在但无最终输出(可能已被终止) → 提示
|
|
316
|
+
- 任务仍在运行 → 提示 `Task still running — view it after it finishes`。
|
|
317
|
+
- 无此任务记录 → 提示 `No task record for: <taskId>`。
|
|
318
|
+
- 任务存在但无最终输出(可能已被终止) → 提示 `Task has no final output` 并附会话文件路径。
|
|
223
319
|
- 有输出时在全屏查看器中展示完整 Markdown 结果,按 Enter 或 Esc 关闭。
|
|
224
320
|
|
|
225
321
|
### session_shutdown
|
|
226
322
|
|
|
227
|
-
退出、切会话或 reload
|
|
323
|
+
退出、切会话或 reload 时,自动 kill 所有在飞子进程并标记 `killed_on_shutdown`。对应的 `[subagent-result]` 通知正文为 `The task was terminated because the session shut down (session_shutdown).`与用户主动取消的正文不同。注意:扩展 reload 或进程崩溃时,在飞任务不落盘、不补投;任务完成后若扩展已死,通知丢失(可查 session 记录)。
|
|
228
324
|
|
|
229
325
|
### TUI / 非 TUI 差异总结
|
|
230
326
|
|
|
@@ -275,7 +371,7 @@ widget 行中的 taskId 可直接复制,用于 `/subagent-result` 查看结果
|
|
|
275
371
|
|
|
276
372
|
### TUI 模式
|
|
277
373
|
|
|
278
|
-
派发回执中直接包含 `taskId
|
|
374
|
+
派发回执中直接包含 `taskId`(即 session ID)。`[subagent-result]` 通知信封的 `- Session:` 行也携带 sessionId--复用即可。无需等待子 agent 完成就已经拿到了。
|
|
279
375
|
|
|
280
376
|
## 环境变量
|
|
281
377
|
|