@wolido/async-subagent-isolation 1.5.1 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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,89 @@ For a subagent such as `coder`, the model and thinking level each resolve to the
82
107
 
83
108
  **Model**:
84
109
 
85
- 1. Config file (`model` for this agent in `subagent-isolation.json`)
86
- 2. Agent frontmatter (`model:` in `coder.md`)
87
- 3. Inherit the main agent's current model
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. Config file (`thinking` for this agent in `subagent-isolation.json`)
92
- 2. Agent frontmatter (`thinking:` in `coder.md`)
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
+
100
131
  > **Note**: when the selected model's provider does not support reasoning, pi automatically clamps the thinking level to `off`.
101
132
 
133
+ ### Process memory-level temporary overrides (`this process`)
134
+
135
+ 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:
136
+
137
+ - **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.
138
+ - **Write target**: editing `model`/`thinking` (clear options 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)`.
139
+ - **Priority chain**: process memory > project JSON > user JSON > frontmatter.
140
+ - **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).
141
+ - **Source attribution**: the effective-value source in the field options shows the literal `process` enum (e.g. `model — deepseek/deepseek-v4-pro (process)`); the write-target option is labeled `this process`.
142
+ - **Clear semantics**: clearing at the memory layer removes that agent's in-memory override (a last-field clear drops the whole key; a missing entry is a no-op) and the result notice recomputes the effective value under the whole-key merge — falling back to the file configs (project/user) or frontmatter.
143
+ - **`$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).
144
+ - **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).
145
+
146
+ ### The available-model list (`$models`)
147
+
148
+ 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.
149
+
150
+ - 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).
151
+ - Invisible to overrides: `loadModelOverridesFile` ignores `$models`, so it never produces an agent override named `$models`.
152
+ - 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).
153
+ - 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.
154
+ - Usable with zero agents: with no agents discovered, the `/subagent-config` picker degrades to just this entry and `$models` stays manageable.
155
+
156
+ ### Config write-back guarantees
157
+
158
+ All interactive edits (`/subagent-config`) write to disk under the same guarantees:
159
+
160
+ - 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.
161
+ - 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.
162
+ - Reserved keys rejected: agent names `__proto__` / `constructor` / `prototype` are refused outright (prototype-pollution vectors).
163
+ - Clear semantics: after clearing a field via the clear option, if the agent has no other fields left, the whole key is removed from the JSON, leaving no empty objects behind.
164
+ - BOM tolerance: config reads tolerate a UTF-8 BOM (the `\uFEFF` prefix is stripped before parsing).
165
+ - 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).
166
+
167
+ ## Configuration commands (/subagent-config)
168
+
169
+ ### The /subagent-config edit flow
170
+
171
+ One unified interactive entry. Main flow: pick an agent → pick a field → edit → write back → result notice. Cancelling at any step writes nothing.
172
+
173
+ - Agent picker: entries are `<name> (<source>) — <model> (<thinking>)` — the source marker plus an effective model/thinking annotation, with `(未配置)` in unset slots; 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.
174
+ - 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.
175
+ - 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).
176
+ - 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.
177
+ - tools / skills: comma-separated input; an empty input deletes the key line from the frontmatter.
178
+ - 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".
179
+ - model / thinking: enters the model/thinking editing subflow (`editAgentModelConfig`); the field-select options carry the current effective value (`model — <value> (<source>)` style, source being process/project/user/frontmatter), and the clear options are `clear model (reset to frontmatter)` / `clear thinking (reset to frontmatter)`. The write target is a three-way choice: `this process` (in-memory, nothing written to disk, gone on process exit or `/reload`) / `user` / `project`, with the currently governing source marked `(current)`. A clear re-reads the user/project override files and the memory layer and recomputes the effective value under the whole-key merge for the result notice: a memory-layer clear falls back to the file configs, with dual-level config the value falls back to the other level's JSON or stays unchanged, and "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 walks back one level: value-step ESC → field select; write-target ESC → value step (the clear branches have no value step → straight back to field select); field-select ESC → back to the parent flow's field select (no exit, no subflow restart).
180
+ - 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.
181
+ - 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).
182
+ - Non-TUI mode: usage notice (warning) only — no dialogs, no writes.
183
+
184
+ ### Agent file write-back (updateAgentFile)
185
+
186
+ 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.
187
+
188
+ - 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.
189
+ - 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).
190
+ - 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.
191
+ - Validation atomicity: all checks run before any file write.
192
+
102
193
  ## Async mode (TUI)
103
194
 
104
195
  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.
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,89 @@ skills: /path/to/skill1,/path/to/skill2
82
107
 
83
108
  **模型**:
84
109
 
85
- 1. 配置文件(`subagent-isolation.json` 中该 agent 的 `model`)
86
- 2. Agent frontmatter(`coder.md` 的 `model:` 字段)
87
- 3. 继承主 agent 当前使用的模型
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. 配置文件(`subagent-isolation.json` 中该 agent 的 `thinking`)
92
- 2. Agent frontmatter(`coder.md` 的 `thinking:` 字段)
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
+
100
131
  > **注意**:当指定模型的 provider 不支持 reasoning 时,pi 会自动把 thinking 钳制为 `off`。
101
132
 
133
+ ### 进程内存级临时覆盖(this process)
134
+
135
+ 多个 pi 窗口共享同一份 `subagent-isolation.json` 时,某窗口工作过程中可以把某个 subagent 的 model/thinking 临时写入 `this process`(进程内存层),只在本进程生效、不落盘:
136
+
137
+ - **语义**:覆盖存放在模块级内存单例中,不写文件、不读文件;进程退出或 `/reload` 后消失,其它窗口不受影响。适合“这次任务换个模型,但不想动共享配置文件”的临时调整。
138
+ - **写入目标**:编辑 model/thinking(含 clear)时写入目标三选一:`this process`(内存)/ `user` / `project`,选项标注当前生效来源(`(current)`)。写内存层的确认提示为 `written to this process (memory only — no file written; disappears when the process exits)`。
139
+ - **优先级链**:进程内存层 > 项目级 json > 用户级 json > frontmatter。
140
+ - **整 key 遮蔽**:与文件层级一致——运行时按 `{...user, ...project, ...process}` 合并,process entry 存在时整体遮蔽低层同 key entry(低层 entry 的其它字段对派发不可见)。
141
+ - **来源标注**:字段选项的生效值来源显示为 `process`(英文枚举值,如 `model — deepseek/deepseek-v4-pro (process)`);写入目标选项显示为 `this process`。
142
+ - **clear 语义**:clear 作用于内存层时清除该 agent 的内存覆盖(末字段清空删整 key;无 entry 时 no-op),反馈按清除后的整 key 合并重算——回退到文件配置(project/user)或 frontmatter。
143
+ - **`$models` 不受影响**:内存层只覆盖 agent 的 model/thinking;`$models` 列表保持文件级(读取 user/project 文件,写入目标只有 user/project)。
144
+ - **扩展开发 API**:`setProcessOverride(agentName, patch)`(patch 语义与 `writeModelOverride` 一致:string 设 / null 清 / undefined 不动;保留字拒绝)、`getProcessOverrides()`(返回副本)、`clearProcessOverride(agentName)`、`resetProcessOverridesForTests()`(测试隔离钩子,清空内存层,模拟进程退出/reload)。
145
+
146
+ ### 可用 model 列表($models)
147
+
148
+ 顶层 `$models` 数组记录交互编辑时可选的 model 列表。项目从未读取过无扩展名 `subagent-models` 文本文件,可用模型列表统一由 `$models` 承载。
149
+
150
+ - 读取与遮蔽:`loadAvailableModels` 先查项目级文件(从 cwd 向上最近的 `.pi/subagent-isolation.json`)。项目级 `$models` 是合法数组时整体遮蔽用户级列表,显式 `"$models": []` 也算合法,可借此清空用户级列表;非数组视为未配置,回退用户级。这与 agent 覆盖的按 key 合并不同:`$models` 是整体替换,不做并集。列表项读取时会被清洗:只保留字符串项,trim,丢弃空白项,去重(首现保留)。
151
+ - 对覆盖配置不可见:`loadModelOverridesFile` 忽略 `$models`,它不会产生名为 `$models` 的 agent 覆盖。
152
+ - 在编辑流程中:`/subagent-config` 编辑 model 时,列表非空则从列表中选择(写入所选 ID 本身),为空或未配置时回退自由输入 `provider/model-id`(输入框预填当前生效值)。
153
+ - 管理入口:`/subagent-config` 的 agent 选择列表末尾有 `Manage available model list ($models)` 入口,流程为查看当前列表(含来源 user/project)→ 添加或删除 → 选择写入目标(user/project)→ 写回。add 追加到列表末尾(幂等去重;原值非数组时重写为单元素列表);remove 的目标不存在时是 no-op,删到最后一项保留 `"$models": []`,项目级可借此显式遮蔽用户级。写回保留文件的其它顶层 key(agent 配置与未知 key)逐字不变;目标文件是非法 JSON 时拒绝覆写。
154
+ - 零 agent 仍可用:一个 agent 都没有时,`/subagent-config` 的选择列表退化为只剩该入口,`$models` 照常可管理。
155
+
156
+ ### 配置写回保证
157
+
158
+ 所有交互编辑(`/subagent-config`)落盘时遵循同一套保证:
159
+
160
+ - 未知字段保留:写回读取原始 JSON,只改目标字段;其它顶层 key(含 `$schema`、`$models`)与 entry 内未知字段原样保留。旧格式纯字符串 entry(`"writer": "model-id"`)原位升级为对象格式。
161
+ - 校验防半写:全部校验先于任何文件 IO;非法值(空 model、非法 thinking 等级)或目标文件为非法 JSON 时整体拒绝,不产生半写状态。
162
+ - 保留 key 拒绝:agent 名为 `__proto__` / `constructor` / `prototype` 时直接拒绝(原型链污染防护)。
163
+ - 清空语义:用 clear 选项清除字段后,若该 agent 不再有其它字段,整个 key 从 JSON 移除,不残留空对象。
164
+ - BOM 容忍:读取配置时容忍 UTF-8 BOM(解析前剥离 `\uFEFF` 前缀)。
165
+ - 内存层除外:写入 `this process` 的覆盖只存在于进程内存,不经由任何落盘路径(见上文“进程内存级临时覆盖”一节)。
166
+
167
+ ## 配置管理命令(/subagent-config)
168
+
169
+ ### /subagent-config 编辑流程
170
+
171
+ 统一交互入口,主流程:选择 agent → 选择字段 → 编辑 → 写回 → 结果提示。任一步取消都零写入。
172
+
173
+ - agent 选择:选项格式为 `<name> (<source>) — <model> (<thinking>)`——来源标记外加生效 model/thinking 总览标注,未配置槽位显示 `(未配置)`;标注是追加文本,经 indexOf 映射回 agent 本体,永不进入写入值。生效值统一走 `computeEffectiveModelConfigs` 的整 key 合并,与派发实际使用一致:process entry 存在时遮蔽 project/user 同 key entry,project entry 存在时遮蔽 user 级同 key entry(低层 entry 的其它字段对派发不可见),entry 内未配字段回退 frontmatter。末尾固定 `$models` 管理入口。`/subagent-config <name>` 带参数预选直进,未知名报错。零 agent 时不早退,列表退化为只剩 `$models` 入口。
174
+ - ESC 逐级回退:文本编辑 ESC → 回字段选择;字段选择 ESC → 回 agent 选择(带参数预选时无该层 → 直接完全退出);agent 选择 ESC → 完全退出。body 取消(read undefined)→ 回字段选择。成功写入后流程结束;回退全程零写入。
175
+ - 字段选择:选中 agent 后直接进入字段选择,无详情通知;信息获取靠菜单标注——字段选项自带当前值(description/tools/skills、body 摘要、model/thinking 生效值与来源)。
176
+ - description:单行输入,输入框预填当前值(真实 TUI 用自定义预填输入框:`ui.custom` + pi-tui `Input`,Enter 提交——未改动提交原值,ESC 取消);空或纯空白整体拒绝,文件字节不变。写回成功提示 `/reload` 刷新注入清单。
177
+ - tools / skills:逗号分隔输入;空串从 frontmatter 删除该 key 行。
178
+ - body:当前正文写入临时文件后 spawn 外部编辑器(`$EDITOR`,未设置回退 `$VISUAL`,再回退 vi),保存退出后读回写盘。取消、仅尾部换行差异、全空白结果均不写盘。编辑器启动失败与非零退出给出各自的错误提示,与“未改动”明确区分。
179
+ - model / thinking:进入 model/thinking 编辑子流程(`editAgentModelConfig`),字段选择层选项带当前生效值标注(`model — <值> (<来源>)` 形式,来源为 process/project/user/frontmatter),clear 选项为 `clear model (reset to frontmatter)` / `clear thinking (reset to frontmatter)`。写入目标三选一:`this process`(进程内存,不落盘,进程退出或 /reload 后消失)/ `user` / `project`,选项标注当前生效来源(`(current)`)。clear 执行后重读 user/project 覆盖记录与内存层、按整 key 合并重算生效值作为反馈:内存层清除回退到文件配置,双层级配置下回退到另一级 json 或保持不变,frontmatter 字样仅当重算来源确为 frontmatter(或回退链到 frontmatter 仍无值 → 未配置语义)。子流程内 ESC 逐级回退:值步 ESC → 回字段选择;写入目标 ESC → 回值步(clear 分支无值步 → 直接回字段选择);字段选择 ESC → 返回父流程字段选择(不退出、不重启子流程)。
180
+ - reload 提示矩阵:改 description 后结果提示需 `/reload`(注入清单已缓存,见上文“子 agent 清单注入”);改 tools/skills/body/model/thinking 提示即时生效,每次派发都重新发现 agent 并重读配置。
181
+ - name 只读:name 是身份标识,字段选择中不出现;任何含 name 的 patch 整体拒绝(见下文“agent 文件写回(updateAgentFile)”)。
182
+ - 非 TUI 模式:只提示用法(warning),不弹对话框、不写文件。
183
+
184
+ ### agent 文件写回(updateAgentFile)
185
+
186
+ agent 文件编辑是行级外科手术,不做整文件重序列化:替换目标 `^key:` 行的值、删除该 key 行(tools/skills 清空时)、或在 frontmatter 块末尾追加新 key。未触碰的 frontmatter 行(含未知 key)与正文保持字节不变。
187
+
188
+ - 多行值守卫:被改 key 的当前值是多行(块标量 `key: |` / `key: >`,或后跟缩进续行 / YAML 列表项)时,行级改写会孤儿化续行,整个 patch 在任何写入前被拒绝并提示手动编辑;未被改的多行 key 不影响其它字段的编辑。
189
+ - YAML 标量序列化:值可安全往返时原样输出,否则双引号加转义(覆盖冒号、井号、引号、CJK、数字开头、true/false/null 形似值等情况)。
190
+ - name patch 拒绝:任何含 name 的 patch 整体拒绝(name 是只读身份标识,改名功能已移除)——合法新名也拒绝、混合 patch 不半写、字节不变、目录零改动;签名保留 name? 仅为类型兼容。
191
+ - 校验原子性:所有校验先于任何文件写入。
192
+
102
193
  ## 异步模式(TUI)
103
194
 
104
195
  在 TUI 交互模式下,`subagent` 工具是**异步**的:调用后立即返回派发回执,子 agent 在后台运行,完成后结果以 `[subagent-result]` 系统通知推送到对话中。非 TUI 模式(print/json,包括 `mode` 为 `undefined`)则降级为同步——等待子 agent 完成后直接返回完整结果,无通知。