@esso0428/pi-subagents 0.17.32 → 0.17.34

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/CHANGELOG.md CHANGED
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.17.34] - 2026-10-05
11
+
12
+ ### Added
13
+ - **Documented nested subagents**: README now carries the feature entry, the `allowed_subagents` frontmatter row, and a `### Nested subagents` section covering the allowlist as a privilege boundary, depth cap, ownership scoping, pool behaviour, transcript durability, and how this fork diverges from upstream by rendering children as a navigable subtree. Also records that the mechanism is hand-authored and deliberately absent from the tool description, so a model never grants itself delegation.
14
+
15
+ ## [0.17.33] - 2026-10-05
16
+
17
+ ### Fixed
18
+ - **Nested children now honour `subagents.agentOverrides`**: the overrides were applied only to the global registry at load time, so a nested dispatch resolved the stock definition for a type whose top-level counterpart is overridden — most visibly, `Explore` kept its `anthropic/claude-haiku-4-5` pin and failed with `model_not_supported` no matter what `settings.json` said. The same overrides are now re-applied to the config-derived registry nested resolves against, which also lets an override introduce a type that did not exist as an agent file.
19
+
10
20
  ## [0.17.32] - 2026-10-04
11
21
 
12
22
  ### Added
package/README.md CHANGED
@@ -17,6 +17,7 @@ https://github.com/user-attachments/assets/8685261b-9338-4fea-8dfe-1c590d5df543
17
17
  - **Agents panel UI** — persistent above-editor widget with animated spinners, live tool activity, token counts, colored status icons, and one focus-gated navigator. It shows every agent by default; press `↓` at an empty prompt to activate the panel, `↑`/`↓` to select, `Enter` to open a live or read-only history viewer, and `Esc` to return. Long rosters reserve a right-hand track/thumb scrollbar and show `↑ N more` or `↓ N more` at the clipped edge. Configure via `/agents → Settings → Widget`: `all`, `background`, or `off`
18
18
  - **Conversation viewer** — select any agent in `/agents` to open a live-scrolling overlay of its full conversation (auto-follows new content, scroll up to pause). The main viewer reserves a right-hand scrollbar rail for the transcript and exposes a `[preview]` header action for the focused tool. Steer a running agent inline by pressing `e` to open a Pi-native composer, typing, then `Enter` to send (`Esc` or an empty submit returns); the follow-up appears as a muted/gray USER message and redirects the agent after its current tool. `Alt+Up` (`a-up`/`alt+up`) recalls submitted follow-up drafts. Read-only tool previews replace the viewer in place, use the larger viewport with the same right-hand track/thumb scrollbar and top/bottom hidden-line affordances, and return with `Esc`, `q`, or the close control. Stop a still-running agent by pressing `x` (then `x` again to confirm) — both work for background agents too
19
19
  - **Custom agent types** — define agents in `.pi/agents/<name>.md` or `.agents/agents/<name>.md` (project) or globally, with YAML frontmatter: custom system prompts, model selection, thinking levels, tool restrictions
20
+ - **Nested subagents** — opt-in, default-off delegation: an agent whose frontmatter sets `allowed_subagents` gets its own ownership-scoped `Agent`, a blocking `wait_for_nested_agent`, and a scoped `steer_subagent`, depth-capped from the main session (default 2). Unlike upstream, nested children are **not** hidden — the agents roster renders them as an indented subtree you can navigate into. The allowlist is a privilege boundary, so it is set by hand in the agent file and never enabled automatically
20
21
  - **Mid-run steering** — inject messages into running agents to redirect their work without restarting
21
22
  - **Session resume** — pick up where an agent left off, preserving full conversation context
22
23
  - **Durable interruption recovery** — catchable shutdowns preserve running/queued metadata and partial transcripts so interrupted agents remain indexed after reload; abrupt `SIGKILL` termination cannot be checkpointed
@@ -246,6 +247,7 @@ All fields are optional — sensible defaults for everything.
246
247
  | `skills` | `true` | Inherit skills from parent. Can be a comma-separated list of skill names to preload (see [Skill Preloading](#skill-preloading) for discovery locations) |
247
248
  | `memory` | — | Persistent agent memory scope: `project`, `local`, or `user`. Auto-detects read-only agents |
248
249
  | `disallowed_tools` | — | Comma-separated tools to deny even if extensions provide them |
250
+ | `allowed_subagents` | none | Opt in to scoped nested `Agent`, `wait_for_nested_agent`, and `steer_subagent` tools. Omitted / empty / `none` / `false` = no nesting; `all` (or `"*"` / `true`) = any enabled agent; comma-separated list = only those agent types |
249
251
  | `isolation` | — | Set to `worktree` to run in an isolated git worktree |
250
252
  | `model` | inherit parent | Model — `provider/modelId` or fuzzy name (`"haiku"`, `"sonnet"`). Resolved tolerantly (`.`/`-` and a trailing date stamp are interchangeable) and falls back to the same model under another provider if the named one doesn't have it |
251
253
  | `thinking` | inherit | off, minimal, low, medium, high, xhigh, max — actual availability depends on your pi version and model; pi clamps unsupported levels down |
@@ -300,6 +302,69 @@ A few rules the examples don't make obvious:
300
302
  - `exclude_extensions:` is **not a sandbox**: excluded extensions' factory code still executes once during loading. Exclusion suppresses their tools and their bound lifecycle hooks (`pi.on` handlers like `session_start` only fire for extensions bound to the session), but not other load-time side effects — a factory that subscribes directly to the shared `pi.events` bus stays live. Don't rely on it to contain an untrusted extension.
301
303
  - Array and string forms are equivalent: `[a, b]` == `"a, b"`.
302
304
 
305
+ ### Nested subagents
306
+
307
+ Nested delegation is default-off and hand-authored. An agent that owns a real
308
+ fan-out responsibility opts in by setting `allowed_subagents` in its own
309
+ frontmatter:
310
+
311
+ ```yaml
312
+ # .pi/agents/support-coordinator.md
313
+ ---
314
+ name: support-coordinator
315
+ description: Coordinates support-triage work across several areas
316
+ allowed_subagents: support-file-finder, support-callsite-tracer
317
+ tools: read, grep, find, ls
318
+ ---
319
+ ```
320
+
321
+ Omitted, empty, `none`, or `false` means no nested tools are injected at all.
322
+ `all` (or `"*"` / `true`) allows any enabled agent; a comma-separated list
323
+ restricts nesting to exactly those types. Unknown, disabled, and out-of-list
324
+ types are **rejected**, never fallen back to — a configured fallback agent
325
+ cannot hand a nested caller something outside its allowlist.
326
+
327
+ **The allowlist is a privilege boundary.** A nested child runs with its own tool
328
+ set, so choose it as carefully as you would `tools:`. It is set by hand in the
329
+ agent file and is never enabled automatically; no skill or tool description
330
+ teaches a model to grant itself delegation.
331
+
332
+ A nested child receives an ownership-scoped `Agent`, a `wait_for_nested_agent`
333
+ that always blocks until that child finishes, and a `steer_subagent` scoped to
334
+ its own children. Result, resume, and steer are ownership-checked, so a parent
335
+ cannot read, steer, or resume a foreign child. `maxSubagentDepth` caps how deep
336
+ nesting goes (default 2: main session 0, its subagents 1, nested children 2);
337
+ an agent already at the cap receives no nested tools at all — not even
338
+ `wait_for_nested_agent` — since it can never own a child. Change it project-wide
339
+ via `maxSubagentDepth` in `subagents.json` or `/agents` → Settings → Nested
340
+ depth. Nested children occupy no concurrency slot: their parent already holds
341
+ one, and queueing a child behind its own parent would deadlock.
342
+
343
+ Nested children consume **no** concurrency slot and are never reported to the
344
+ main session as top-level agents — their completion surfaces inside the parent,
345
+ and their token usage folds into every ancestor's total. Each still writes its
346
+ own durable transcript.
347
+
348
+ ### How nested children are shown
349
+
350
+ Upstream hides nested children from every surface. This fork keeps the same
351
+ reporting semantics but makes the hierarchy visible: the agents roster renders
352
+ children as an indented subtree, each with its own spinner and activity line,
353
+ token counts marked `(in parent)` so totals are not double-counted, and a
354
+ `nested blocked: …` note when the allowlist or depth cap refuses a dispatch.
355
+ `↑`/`↓` move across every visible row regardless of depth and `Enter` opens that
356
+ level's conversation viewer. Because the lookup is case-insensitive, an override
357
+ named `Explore` also changes what a lowercase `explore` request resolves to.
358
+
359
+ The `parentAgentId` and `depth` of a nested child are recorded in its recovery
360
+ checkpoint, so a reload rebuilds the same subtree rather than flattening it. If
361
+ a parent record is ever removed before its child, the child keeps its indent and
362
+ says its parent is gone.
363
+
364
+ `subagents.agentOverrides` applies to nested dispatch exactly as it does at
365
+ top level, including the project-over-global precedence described in
366
+ [Priority Chain](#priority-chain).
367
+
303
368
  ## `npm:pi-subagents`-Style JSON Agent Overrides
304
369
 
305
370
  This fork adds the ability to configure — and even create — agents entirely through JSON,
package/ROADMAP.md CHANGED
@@ -12,6 +12,18 @@
12
12
  - v0.17.6 的 ConversationViewer baseline(scrollbar rail、`[preview]`/`[Esc]`、`w` preview、in-place read-only Tool Output、鍵盤與 wheel scroll)不可被改寫
13
13
  - 資源安全政策不因功能恢復而撤回:Vitest / E2E / build 在此裝置需明確授權
14
14
 
15
+ ## 未來:讓模型知道 nested 怎麼用
16
+
17
+ `allowed_subagents` 目前只能由人手寫進 agent 檔案。上游刻意如此 — 它的 `Agent` 工具描述、`promptGuidelines`、`skills/`、範例 agent 檔全都沒有提 nested,所以模型不會自己開自己的權限。這是對的安全立場,不是缺陷。
18
+
19
+ 若要改善可用性,安全作法是**只教模型怎麼「建議」**,不給它自己開的權限:
20
+
21
+ - 在建立/編輯 agent 檔案的流程裡提示 `allowed_subagents` 的存在與語意(`all` / 逗號清單 / 省略 = 不開)
22
+ - 明確標示這是授權邊界,該由人確認,不接受模型自行套用
23
+ - 不在 `Agent` 工具描述或 `promptGuidelines` 中指示模型對既有 agent 開啟巢狀
24
+
25
+ 設計前提未定:這段提示該放哪(工具描述、skill、`/agents` 建立精靈的表單、還是 agent 檔的模板註解),以及如何避免模型把「建議」當成「已授權」。
26
+
15
27
  ## 交付 B:Workflow
16
28
 
17
29
  上游規模約 6,200 行:`src/workflow/**` 11 檔約 4,458 行(`runtime.ts` 1,219 + `worker-source.ts` 781 為骨幹)、`ui/workflow-*.ts` 3 檔約 1,778 行(`workflow-dialog.ts` 1,115 最大)。含 `node:vm` sandbox、worker thread、journal,是新的執行模型而非 UI,風險等級與 nested 不同,不與其他交付合併。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@esso0428/pi-subagents",
3
- "version": "0.17.32",
3
+ "version": "0.17.34",
4
4
  "description": "A pi extension that brings smart Claude Code-style autonomous sub-agents to pi, with npm:pi-subagents-style JSON agent overrides.",
5
5
  "author": "ESSO0428",
6
6
  "repository": {
@@ -22,6 +22,7 @@ import { loadCustomAgents } from "./custom-agents.js";
22
22
  import { resolveAgentInvocationConfig } from "./invocation-config.js";
23
23
  import { resolveModel } from "./model-resolver.js";
24
24
  import { checkModelScope } from "./model-scope.js";
25
+ import { applyNicoOverridesToMap, readNicoAgentOverrides } from "./nico-overrides.js";
25
26
  import { createOutputFilePath, streamToOutputFile, writeInitialEntry } from "./output-file.js";
26
27
  import { getStatusNote } from "./status-note.js";
27
28
  import type { AgentConfig, AgentInvocation, AgentRecord, IsolationMode, ThinkingLevel } from "./types.js";
@@ -119,6 +120,12 @@ export function createNestedSubagentTools(context: NestedToolContext): ToolDefin
119
120
  const config = getAgentConfig(name);
120
121
  if (config) registry.set(name, config);
121
122
  }
123
+ // `applyNicoOverrides()` runs against the global registry at load, so
124
+ // `subagents.agentOverrides` in settings.json is not visible here. Apply the
125
+ // same overrides to this map or a nested child would ignore configuration
126
+ // that governs the very same agent type one level up.
127
+ const { overrides, defaultModel } = readNicoAgentOverrides(context.configCwd);
128
+ applyNicoOverridesToMap(registry, overrides, defaultModel);
122
129
  return registry;
123
130
  };
124
131
  const allowedTypesIn = (registry: Map<string, AgentConfig>): Set<string> | undefined =>