@narumitw/pi-subagents 0.42.0 โ 0.43.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/README.md +96 -26
- package/package.json +1 -1
- package/src/agents.ts +33 -8
- package/src/config-ui.ts +85 -21
- package/src/consult-policy.ts +15 -0
- package/src/consult.ts +688 -0
- package/src/execution.ts +8 -2
- package/src/inspect.ts +405 -0
- package/src/limits.ts +1 -0
- package/src/params.ts +2 -0
- package/src/registry.ts +75 -0
- package/src/runner.ts +160 -22
- package/src/safe-text.ts +67 -0
- package/src/settings.ts +88 -1
- package/src/stateful.ts +24 -12
- package/src/subagents.ts +43 -4
package/README.md
CHANGED
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@narumitw/pi-subagents) [](https://pi.dev) [](./LICENSE)
|
|
4
4
|
|
|
5
|
-
`@narumitw/pi-subagents` is a native [Pi coding agent](https://pi.dev) extension for delegating work to specialized agents. By default,
|
|
5
|
+
`@narumitw/pi-subagents` is a native [Pi coding agent](https://pi.dev) extension for delegating work to specialized agents. By default, it exposes seven capability-specific tools: blocking batches, four detached lifecycle tools, side-effect-free inspection, and synchronous read-only consultation. Users can keep every delegation method, choose async-only delegation, retain only blocking delegation, or disable delegation while keeping inspection available.
|
|
6
6
|
|
|
7
7
|
Use it to split independent research, planning, implementation, and review work across focused workers. Under the default next-turn delivery policy, background delegation is for work the current response does not depend on. Opt-in auto-resume also supports final-answer-dependent background work by requesting a synthesis turn after completion.
|
|
8
8
|
|
|
9
9
|
## โจ Features
|
|
10
10
|
|
|
11
|
-
- Offers all delegation methods by default, with goal-oriented presets for async-only
|
|
11
|
+
- Offers all delegation methods by default, with goal-oriented presets for async-only, blocking-only, or disabled delegation.
|
|
12
|
+
- Adds `subagent_inspect` for bounded metadata without child launch, mailbox-content access, acknowledgement, or mutation.
|
|
13
|
+
- Adds `subagent_consult` for one synchronous ephemeral child constrained to built-in `read`, `grep`, `find`, and `ls` tools (or a narrower agent allow-list).
|
|
12
14
|
- Keeps batch workers isolated in `pi --mode json -p --no-session` subprocesses.
|
|
13
15
|
- Registers detached stateful lifecycle tools by default; completion can stay queued for the next turn or opt into an idle root synthesis turn.
|
|
14
16
|
- Supports an opt-in public-SDK `in-process` stateful transport with one reusable child `AgentSession` per `agentId`.
|
|
@@ -43,13 +45,14 @@ pi -e ./extensions/pi-subagents
|
|
|
43
45
|
|
|
44
46
|
## ๐ ๏ธ Pi tool
|
|
45
47
|
|
|
46
|
-
`pi-subagents` registers
|
|
48
|
+
`pi-subagents` registers seven tools by default. Run `/subagents`, choose **Change delegation**, review the concrete tool changes, then select **Save and reload** to apply one of these workflows:
|
|
47
49
|
|
|
48
50
|
| Workflow | Registered tools |
|
|
49
51
|
| --- | --- |
|
|
50
|
-
| **All delegation methods** (default) |
|
|
51
|
-
| **Async only** |
|
|
52
|
-
| **Blocking only** |
|
|
52
|
+
| **All delegation methods** (default) | Existing five delegation/lifecycle tools, `subagent_inspect`, and `subagent_consult` |
|
|
53
|
+
| **Async only** | Four detached lifecycle tools plus `subagent_inspect`; blocking `subagent` and `subagent_consult` are omitted |
|
|
54
|
+
| **Blocking only** | `subagent`, `subagent_consult`, and `subagent_inspect` |
|
|
55
|
+
| **Disabled** | `subagent_inspect` only; delegation is disabled |
|
|
53
56
|
|
|
54
57
|
The preview compares the selection with the tools registered in the current session, even when a manual settings edit is pending, and remains read-only until confirmation. Escape or **Cancel** leaves settings unchanged. Tool removal requires an extension reload because Pi does not expose extension tool unregistration. To avoid aborting work or removing isolated worktrees during `session_shutdown`, workflow changes are blocked while detached agents are retained; finish or clear them through **Current agents** first. Pi owns reload-error reporting and does not return a success result to extensions, so the save notification also tells users to run `/reload` if the tool surface does not refresh.
|
|
55
58
|
|
|
@@ -57,12 +60,16 @@ The available tools are:
|
|
|
57
60
|
|
|
58
61
|
- `subagent` โ delegate blocking single, parallel, fan-in, or chained batch work. The main agent cannot process queued steering until the call returns.
|
|
59
62
|
- `subagent_spawn` and related lifecycle tools โ when enabled, start reusable detached work, return immediately, and receive bounded completion messages automatically.
|
|
63
|
+
- `subagent_inspect` โ inspect agent/model/run/runtime metadata without launching work or changing state.
|
|
64
|
+
- `subagent_consult` โ run one ephemeral read-only consultation and wait for its answer.
|
|
60
65
|
|
|
61
|
-
After each session starts,
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
+
After each session starts, the descriptions of the registered `subagent`, `subagent_spawn`, and
|
|
67
|
+
`subagent_consult` tools include the same bounded parent-facing catalog of the agents available in
|
|
68
|
+
that session. Entries show the source (`built-in`, `user`, or `project`) and the `agentScope` needed to
|
|
69
|
+
invoke them; the `agent` parameters remain unconstrained strings for cwd and scope flexibility. The
|
|
70
|
+
catalog is rebuilt on
|
|
71
|
+
`/reload` or the next session start, and omitted entries are reported explicitly when the catalog
|
|
72
|
+
exceeds its metadata bounds.
|
|
66
73
|
|
|
67
74
|
Choose the API by lifecycle:
|
|
68
75
|
|
|
@@ -72,6 +79,8 @@ Choose the API by lifecycle:
|
|
|
72
79
|
| Broad research/review the current response does not depend on | Prefer one `subagent_spawn` covering related branches, when lifecycle tools are enabled |
|
|
73
80
|
| Final-answer-dependent broad work with `completionDelivery: "auto-resume"` | Prefer one `subagent_spawn`; completion requests a synthesis turn |
|
|
74
81
|
| Reusable history, follow-ups, or mailboxes | `subagent_spawn` and lifecycle tools, when enabled |
|
|
82
|
+
| Side-effect-free agent/model/run diagnostics | `subagent_inspect` |
|
|
83
|
+
| Synchronous reconnaissance, planning, or review that must not write | `subagent_consult`, when blocking delegation is enabled |
|
|
75
84
|
| One simple or critical-path action the root can perform directly | No subagent |
|
|
76
85
|
|
|
77
86
|
Execution modes:
|
|
@@ -95,10 +104,11 @@ When registered, the blocking `subagent` tool advertises only blocking guidance.
|
|
|
95
104
|
are registered, `subagent_spawn` adds detached guidance for the active completion-delivery policy.
|
|
96
105
|
Changing the policy through `/subagents settings` refreshes that guidance immediately.
|
|
97
106
|
|
|
98
|
-
The
|
|
99
|
-
needed. Built-ins and user agents appear under the
|
|
100
|
-
agents appear separately and explicitly require
|
|
101
|
-
names and descriptions are not read into
|
|
107
|
+
The `subagent`, `subagent_spawn`, and `subagent_consult` descriptions advertise the current agent
|
|
108
|
+
catalog automatically; no preliminary list call is needed. Built-ins and user agents appear under the
|
|
109
|
+
default `agentScope: "user"`. Trusted project agents appear separately and explicitly require
|
|
110
|
+
`agentScope: "project"` or `"both"`; project-authored names and descriptions are not read into
|
|
111
|
+
metadata for untrusted projects. If a project definition
|
|
102
112
|
shares a name with a user or built-in definition, the user version is the default and the project
|
|
103
113
|
version is used only for `"project"`/`"both"`. A user override of a built-in also shows the
|
|
104
114
|
built-in fallback available with `agentScope: "project"`; `"both"` keeps the user definition. The
|
|
@@ -169,6 +179,58 @@ A blocking fan-out is reserved for output that must be synthesized before the ro
|
|
|
169
179
|
}
|
|
170
180
|
```
|
|
171
181
|
|
|
182
|
+
## ๐ Read-only inspection
|
|
183
|
+
|
|
184
|
+
`subagent_inspect` is registered in every workflow, including disabled delegation. It never starts a child, sends or acknowledges mailbox messages, interrupts or closes a run, changes settings, refreshes providers, resolves credentials, or modifies files.
|
|
185
|
+
|
|
186
|
+
| Action | Parameters | Result |
|
|
187
|
+
| --- | --- | --- |
|
|
188
|
+
| `list_agents` | Optional `agentScope` (default `user`) and `limit` (default 32, maximum 100) | Bounded agent metadata and omission counts |
|
|
189
|
+
| `get_agent` | Required `agent`; optional `agentScope` | One resolved definition, safe source path, configured tools, and consultation-effective tools; never the system prompt |
|
|
190
|
+
| `list_runs` | Optional `includeClosed` and `limit` (default 50, maximum 100) | Metadata-only retained-run summaries and unread counts |
|
|
191
|
+
| `get_run` | Required `agentId` | Safe `cwd`, current-task/error summaries, thinking level, policy, history count, and unread count |
|
|
192
|
+
| `list_models` | Optional `limit` (default 50, maximum 100) | Session-scoped models, or the already-loaded available snapshot |
|
|
193
|
+
| `status` | No additional fields | Effective workflow, runtime counts/transport, completion delivery, and consultation-resource setting |
|
|
194
|
+
| `diagnose` | No additional fields | Structured `pass`, `warning`, and `fail` checks; failed checks are report data rather than a tool error |
|
|
195
|
+
|
|
196
|
+
The schema rejects fields that do not belong to the selected action. Explicit `project` or `both` scope fails before project-agent discovery unless Pi already trusts the project. Run inspection never returns history output, stored context, or mailbox content; unread counts come from a metadata-only snapshot and do not acknowledge messages. Paths beneath the Pi agent directory use `~`, project paths are workspace-relative, model objects are projected through an allow-list, and model-facing text is bounded to 50 KiB or 2,000 lines.
|
|
197
|
+
|
|
198
|
+
Compatibility: `subagent_manage({ "action": "list" })` remains supported with its existing behavior. Prefer `subagent_inspect` when a whole tool must be safe to activate on a read-only surface.
|
|
199
|
+
|
|
200
|
+
## ๐ Read-only consultation
|
|
201
|
+
|
|
202
|
+
`subagent_consult` is registered whenever blocking delegation is enabled. It runs exactly one synchronous, non-retained child with `--no-session`, `--no-extensions`, and only the effective intersection of the agent tools with `read`, `grep`, `find`, and `ls`. A missing tool list receives those four defaults; an explicit `tools: []` receives `--no-tools`; write, shell, lifecycle, custom, and extension tools cannot enter the child allow-list. The executor policy remains authoritative even when the task or agent prompt asks for implementation.
|
|
203
|
+
|
|
204
|
+
```json
|
|
205
|
+
{
|
|
206
|
+
"agent": "reviewer",
|
|
207
|
+
"task": "Inspect the authentication changes and report correctness and security findings with paths.",
|
|
208
|
+
"thinkingLevel": "high"
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
The actionless schema requires `agent` and `task` and accepts optional `agentScope`,
|
|
213
|
+
`confirmProjectAgents`, `cwd`, `timeoutMs`, and `thinkingLevel`. Any agent resolved from that scope may
|
|
214
|
+
be selected; consultation always intersects its configured tools with the enforced read-only
|
|
215
|
+
allow-list rather than defining a separate read-only agent category. An unknown name fails before
|
|
216
|
+
launch with a bounded name/source list for the requested scope. Project scope is rejected before
|
|
217
|
+
discovery when the project is untrusted. A trusted project agent still asks for confirmation by
|
|
218
|
+
default; non-interactive calls fail closed unless they explicitly send
|
|
219
|
+
`confirmProjectAgents: false`. Declining an interactive confirmation returns a normal cancelled result
|
|
220
|
+
without launching or charging a child.
|
|
221
|
+
|
|
222
|
+
`consult.resources` controls automatically inherited instruction resources:
|
|
223
|
+
|
|
224
|
+
| Value | Behavior |
|
|
225
|
+
| --- | --- |
|
|
226
|
+
| `"project-context"` (default) | Keep ordinary user context/system files and trusted project `AGENTS.md`, `CLAUDE.md`, and `SYSTEM.md`; disable skills and prompt templates |
|
|
227
|
+
| `"none"` | Use only the package consultation base, selected agent prompt, and enforced read-only instruction |
|
|
228
|
+
| `"all"` | Keep ordinarily discoverable trusted context/system/append-system files, skills, and prompt templates |
|
|
229
|
+
|
|
230
|
+
Extensions remain disabled for all three values. For an untrusted current project, project resources are omitted; because Pi's context-file switch cannot separate user and project `AGENTS.md` files, `project-context` and `all` fail closed by disabling context files while still permitting the user `SYSTEM.md` selected by the extension. The setting is user-owned in `~/.pi/agent/pi-subagents.json`; projects cannot override it. An external or symlink-escaped `cwd` is accepted only with `consult.resources: "none"`, but this is not a path sandbox: read-only tools can still read an explicitly requested accessible absolute path.
|
|
231
|
+
|
|
232
|
+
Result details report requested/effective tools and resources, agent/model/thinking/timeout metadata, and the facts that extensions, session persistence, and retained-agent state are disabled. Nested model usage is returned through Pi's usage field, so footer, `/session`, and RPC totals include consultation cost. Validation, trust, unsafe-cwd, and launch failures throw. Failures after model launch preserve bounded partial evidence and usage while the finalized Pi tool result is marked as an error. Abort, timeout, session replacement, and shutdown use the existing process-tree termination and temporary-file cleanup path.
|
|
233
|
+
|
|
172
234
|
## ๐ Blocking batch examples
|
|
173
235
|
|
|
174
236
|
Every example in this section calls `subagent` and keeps the main agent unavailable until the batch
|
|
@@ -258,14 +320,12 @@ Auto-resume is best-effort because Pi's custom-message API is fire-and-forget. S
|
|
|
258
320
|
The default `subprocess` transport preserves compatibility: each turn starts a fresh isolated `pi --mode json -p --no-session` child and receives sanitized, bounded history. Set `transport` to `in-process` to retain one public Pi SDK `AgentSession` per stateful `agentId`, avoiding repeated process startup while preserving native child history in memory.
|
|
259
321
|
|
|
260
322
|
Run `/subagents` in TUI mode to open the standard primary manager. It leads with the current
|
|
261
|
-
delegation workflow, human-readable async completion behavior, and active/retained counts. **Change
|
|
262
|
-
delegation**, **Current agents**, and **Completion behavior** cover the common workflows; agent
|
|
263
|
-
permissions, transport/runtime details, source, and settings path remain under **Advanced settings**.
|
|
323
|
+
delegation workflow, human-readable async completion behavior, consultation-resource policy, and active/retained counts. **Change delegation**, **Current agents**, and **Settings** cover the common workflows; agent permissions, transport/runtime details, source, and settings path remain under **Advanced settings**.
|
|
264
324
|
Escape returns from a nested screen to a newly refreshed manager; Ctrl+C closes the full flow.
|
|
265
325
|
Exact workflow/reload and project-agent safety confirmations remain extension-owned because they
|
|
266
326
|
guard live agent and trust-boundary policy rather than ordinary navigation.
|
|
267
327
|
|
|
268
|
-
The direct routes remain predictable: `/subagents settings` changes
|
|
328
|
+
The direct routes remain predictable: `/subagents settings` changes completion delivery and consultation resources and applies them immediately, including refreshing model-facing spawn guidance; `/subagents status` reports current-session runtime values separately from configured values, sources, and path; `/subagents help` summarizes the single-command interface. In RPC mode, bare `/subagents` emits the same bounded status through Pi's notification protocol instead of opening a custom TUI. JSON and print modes do not emit ad hoc command output. Manual edits use `~/.pi/agent/pi-subagents.json` and take effect after reloading Pi:
|
|
269
329
|
|
|
270
330
|
```json
|
|
271
331
|
{
|
|
@@ -285,11 +345,14 @@ The direct routes remain predictable: `/subagents settings` changes user complet
|
|
|
285
345
|
"idleTtlMs": 3600000,
|
|
286
346
|
"retentionDays": 30,
|
|
287
347
|
"maxStoredAgents": 50
|
|
348
|
+
},
|
|
349
|
+
"consult": {
|
|
350
|
+
"resources": "project-context"
|
|
288
351
|
}
|
|
289
352
|
}
|
|
290
353
|
```
|
|
291
354
|
|
|
292
|
-
The settings UI patches the raw JSON atomically and preserves unknown fields; it refuses to overwrite malformed or invalid settings. Supported Pi writers serialize the latest-document read and same-directory temporary-file rename through `pi-subagents.json.mutation-lock`. Editors and older extension versions do not participate in that lock, so avoid manual edits while a settings save is in progress. `blocking.enabled` defaults to `true`; set it to `false` for async-only delegation. `stateful.enabled` also defaults to `true`; its existing `false` value remains the blocking-only workflow. When stateful tools are enabled, their membership stays fixed across spawn, completion, interrupt, close, and mailbox transitions. This avoids lifecycle-driven tool-schema churn and preserves a stable provider prompt prefix for KV caching.
|
|
355
|
+
The settings UI patches the raw JSON atomically and preserves unknown fields; it refuses to overwrite malformed or invalid settings. Supported Pi writers serialize the latest-document read and same-directory temporary-file rename through `pi-subagents.json.mutation-lock`. Editors and older extension versions do not participate in that lock, so avoid manual edits while a settings save is in progress. `blocking.enabled` defaults to `true`; set it to `false` for async-only delegation. `stateful.enabled` also defaults to `true`; its existing `false` value remains the blocking-only workflow. `consult.resources` defaults to `"project-context"`; the Settings UI applies a saved change to subsequent consultations immediately, while manual edits take effect on session start or `/reload`. When stateful tools are enabled, their membership stays fixed across spawn, completion, interrupt, close, and mailbox transitions. This avoids lifecycle-driven tool-schema churn and preserves a stable provider prompt prefix for KV caching.
|
|
293
356
|
|
|
294
357
|
| Tool | Purpose |
|
|
295
358
|
| --- | --- |
|
|
@@ -318,7 +381,7 @@ The action schemas are flat for provider compatibility and reject parameters tha
|
|
|
318
381
|
|
|
319
382
|
Use the **Current agents** action in `/subagents` to inspect the indented agent tree, lifecycle state, unread count, and available actions, or to confirm clearing retained agents. Active turns are FIFO-limited by `maxActiveTurns`; excess retained work remains in `starting` state until a slot is available. `maxAgents` separately bounds running, queued, and idle records. `parentId` creates a bounded child relationship; subtree interrupt and close operate child-first.
|
|
320
383
|
|
|
321
|
-
### Migrating from the seven-tool lifecycle surface
|
|
384
|
+
### Migrating from the previous seven-tool lifecycle surface
|
|
322
385
|
|
|
323
386
|
The five replaced names are intentionally not registered as aliases. Update explicit prompts and integrations as follows:
|
|
324
387
|
|
|
@@ -418,7 +481,7 @@ Compatibility: a valid legacy `pi-subagents-config.json` remains readable with a
|
|
|
418
481
|
- Choose **Save changes** to write the draft, choose **Discard draft** to abandon it, or press Esc to
|
|
419
482
|
return to agent selection without writing.
|
|
420
483
|
- Save the default selection to remove a custom override and use the agent defaults again.
|
|
421
|
-
- Deselect every tool and save to run that agent with no tools.
|
|
484
|
+
- Deselect every tool and save to run that agent with no tools. An explicit empty list remains distinct from an absent list; blank `tools:` or `tools: []` in agent frontmatter also means no tools.
|
|
422
485
|
|
|
423
486
|
Configured tool names that are not currently registered are preserved, so settings for tools from
|
|
424
487
|
other extension sessions are not silently dropped.
|
|
@@ -445,6 +508,8 @@ You are an API review subagent. Do not edit files. Check compatibility,
|
|
|
445
508
|
test coverage, and migration risks. Report PASS/FAIL/PARTIAL with evidence.
|
|
446
509
|
```
|
|
447
510
|
|
|
511
|
+
`tools` accepts either the comma-separated form above or a YAML string array such as `tools: [read, grep]`. An omitted field keeps the agent's default tools; blank, `null`, or `[]` explicitly selects no tools.
|
|
512
|
+
|
|
448
513
|
`agentScope` is a top-level tool argument supplied per invocation. It is not a setting in
|
|
449
514
|
`~/.pi/agent/pi-subagents.json` and does not belong in agent frontmatter. The parent-facing tool
|
|
450
515
|
metadata discovers these definitions after session start and labels their source and required scope.
|
|
@@ -491,6 +556,7 @@ Each subprocess has a hard timeout to avoid runaway workers.
|
|
|
491
556
|
|
|
492
557
|
- Set `timeoutMs` on the top-level call to apply a default for all jobs.
|
|
493
558
|
- Set `timeoutMs` on a task, chain step, or aggregator to override it locally.
|
|
559
|
+
- Valid timeout values range from 1 to 2,147,483,647 milliseconds, matching the runtime timer limit.
|
|
494
560
|
- If omitted, the default is `PI_SUBAGENT_TIMEOUT_MS`, or `600000` milliseconds (10 minutes) when unset.
|
|
495
561
|
|
|
496
562
|
Set `thinkingLevel` to request one of Pi's supported levels: `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, or `max`. Blocking subprocess calls pass the resolved value through `--thinking <level>`.
|
|
@@ -520,7 +586,7 @@ The child event protocol limits each JSON line to 256 KiB. Captured output uses
|
|
|
520
586
|
- stderr: 16 KiB;
|
|
521
587
|
- captured messages: 200.
|
|
522
588
|
|
|
523
|
-
Truncated text includes a `truncated by pi-subagents` marker and details expose `truncated: true`. `PI_SUBAGENT_MAX_DEPTH` controls nested delegation depth and defaults to 1; child processes receive `PI_SUBAGENT_DEPTH` automatically.
|
|
589
|
+
Truncated text includes a `truncated by pi-subagents` marker and details expose `truncated: true`. Inspection and consultation model-facing content also stops at 2,000 lines, whichever limit is reached first. `PI_SUBAGENT_MAX_DEPTH` controls nested delegation depth and defaults to 1; child processes receive `PI_SUBAGENT_DEPTH` automatically.
|
|
524
590
|
|
|
525
591
|
## ๐ก Runtime status
|
|
526
592
|
|
|
@@ -528,7 +594,7 @@ While the `subagent` tool is running, `pi-subagents` publishes compact activity
|
|
|
528
594
|
|
|
529
595
|
## ๐ Safety notes
|
|
530
596
|
|
|
531
|
-
Subagents have separate processes and context windows, but they are **not security sandboxes**. They run as the same OS user, share the host filesystem and network access, and may conflict if they edit the same files. Tool allow-lists reduce available Pi tools but do not reduce operating-system permissions.
|
|
597
|
+
Subagents have separate processes and context windows, but they are **not security sandboxes**. They run as the same OS user, share the host filesystem and network access, and may conflict if they edit the same files. Tool allow-lists reduce available Pi tools but do not reduce operating-system permissions. `subagent_consult` prevents writes through its Pi tool surface and disables extensions, but it can read accessible paths, call the configured model over the network, and incur cost; its instruction-resource policy is not a filesystem or confidentiality boundary.
|
|
532
598
|
|
|
533
599
|
The runner explicitly reports policy continuity in result details:
|
|
534
600
|
|
|
@@ -547,16 +613,20 @@ extensions/pi-subagents/
|
|
|
547
613
|
โโโ src/
|
|
548
614
|
โ โโโ index.ts # Pi package entrypoint
|
|
549
615
|
โ โโโ subagents.ts # Extension registration and blocking tool schema
|
|
616
|
+
โ โโโ inspect.ts # Side-effect-free metadata inspection tool
|
|
617
|
+
โ โโโ consult.ts # Synchronous read-only consultation tool
|
|
618
|
+
โ โโโ consult-policy.ts # Enforced read-only tool intersection
|
|
619
|
+
โ โโโ safe-text.ts # Shared byte/line/path sanitization
|
|
550
620
|
โ โโโ stateful.ts # Detached lifecycle registration and dispatch
|
|
551
621
|
โ โโโ stateful-tool-params.ts # Consolidated action schemas and validation
|
|
552
|
-
โ โโโ *.ts # Package-local discovery, execution, rendering, and
|
|
622
|
+
โ โโโ *.ts # Package-local discovery, execution, rendering, and settings modules
|
|
553
623
|
โโโ README.md
|
|
554
624
|
โโโ LICENSE
|
|
555
625
|
โโโ tsconfig.json
|
|
556
626
|
โโโ package.json
|
|
557
627
|
```
|
|
558
628
|
|
|
559
|
-
`index.ts` is the Pi entrypoint and forwards to `subagents.ts`; the other source modules are internal. Workflow settings remain backward compatible: older files without `blocking.enabled`
|
|
629
|
+
`index.ts` is the Pi entrypoint and forwards to `subagents.ts`; the other source modules are internal. Workflow settings remain backward compatible: older files without `blocking.enabled` receive the seven-tool default, existing `stateful.enabled: false` files expose blocking delegation plus inspection/consultation, and older package releases ignore and preserve the optional `consult` object. The package exposes its Pi extension through `package.json`:
|
|
560
630
|
|
|
561
631
|
```json
|
|
562
632
|
{
|
package/package.json
CHANGED
package/src/agents.ts
CHANGED
|
@@ -41,6 +41,14 @@ export type SubagentTransportKind = "subprocess" | "in-process";
|
|
|
41
41
|
|
|
42
42
|
export type CompletionDelivery = "next-turn" | "auto-resume";
|
|
43
43
|
|
|
44
|
+
export const CONSULT_RESOURCE_POLICIES = ["project-context", "none", "all"] as const;
|
|
45
|
+
|
|
46
|
+
export type ConsultResourcePolicy = (typeof CONSULT_RESOURCE_POLICIES)[number];
|
|
47
|
+
|
|
48
|
+
export interface SubagentConsultSettings {
|
|
49
|
+
resources?: ConsultResourcePolicy;
|
|
50
|
+
}
|
|
51
|
+
|
|
44
52
|
export interface SubagentBlockingSettings {
|
|
45
53
|
enabled?: boolean;
|
|
46
54
|
}
|
|
@@ -64,6 +72,7 @@ export interface SubagentSettings {
|
|
|
64
72
|
agents?: Record<string, SubagentAgentConfig>;
|
|
65
73
|
blocking?: SubagentBlockingSettings;
|
|
66
74
|
stateful?: SubagentRuntimeSettings;
|
|
75
|
+
consult?: SubagentConsultSettings;
|
|
67
76
|
}
|
|
68
77
|
|
|
69
78
|
const BUILT_IN_AGENTS: AgentConfig[] = [
|
|
@@ -238,20 +247,36 @@ function loadAgentsFromDir(
|
|
|
238
247
|
continue;
|
|
239
248
|
}
|
|
240
249
|
|
|
241
|
-
const { frontmatter, body } = parseFrontmatter<Record<string,
|
|
250
|
+
const { frontmatter, body } = parseFrontmatter<Record<string, unknown>>(loaded.content);
|
|
242
251
|
|
|
243
|
-
if (
|
|
252
|
+
if (typeof frontmatter.name !== "string" || typeof frontmatter.description !== "string") {
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
244
255
|
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
256
|
+
const hasTools = hasOwn(frontmatter, "tools");
|
|
257
|
+
const rawTools = frontmatter.tools;
|
|
258
|
+
let tools: string[] | undefined;
|
|
259
|
+
if (hasTools) {
|
|
260
|
+
if (rawTools === null) {
|
|
261
|
+
tools = [];
|
|
262
|
+
} else if (Array.isArray(rawTools)) {
|
|
263
|
+
if (!rawTools.every((tool): tool is string => typeof tool === "string")) continue;
|
|
264
|
+
tools = rawTools.map((tool) => tool.trim()).filter(Boolean);
|
|
265
|
+
} else if (typeof rawTools === "string") {
|
|
266
|
+
tools = rawTools
|
|
267
|
+
.split(",")
|
|
268
|
+
.map((tool) => tool.trim())
|
|
269
|
+
.filter(Boolean);
|
|
270
|
+
} else {
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
249
274
|
|
|
250
275
|
agents.push({
|
|
251
276
|
name: frontmatter.name,
|
|
252
277
|
description: frontmatter.description,
|
|
253
|
-
|
|
254
|
-
model: frontmatter.model,
|
|
278
|
+
...(hasTools ? { tools: tools ?? [] } : {}),
|
|
279
|
+
model: typeof frontmatter.model === "string" ? frontmatter.model : undefined,
|
|
255
280
|
thinkingLevel: isThinkingLevel(frontmatter.thinkingLevel)
|
|
256
281
|
? frontmatter.thinkingLevel
|
|
257
282
|
: undefined,
|
package/src/config-ui.ts
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { defineMenu, runMenu } from "@narumitw/pi-tui-kit";
|
|
3
|
-
import { type CompletionDelivery, discoverAgents } from "./agents.js";
|
|
3
|
+
import { type CompletionDelivery, type ConsultResourcePolicy, discoverAgents } from "./agents.js";
|
|
4
4
|
import type { ManagedAgent } from "./registry.js";
|
|
5
5
|
import {
|
|
6
6
|
type DelegationWorkflow,
|
|
7
7
|
hasOwn,
|
|
8
8
|
inspectCompletionDeliverySettings,
|
|
9
|
+
inspectConsultResourceSettings,
|
|
9
10
|
inspectDelegationWorkflowSettings,
|
|
10
11
|
readSubagentSettings,
|
|
11
12
|
sameToolSet,
|
|
12
13
|
uniqueToolNames,
|
|
13
14
|
updateAgentToolsSetting,
|
|
14
15
|
updateCompletionDeliverySetting,
|
|
16
|
+
updateConsultResourceSetting,
|
|
15
17
|
updateDelegationWorkflowSetting,
|
|
16
18
|
} from "./settings.js";
|
|
17
19
|
import { formatStatefulAgentLine, type StatefulSubagentRuntimeStatus } from "./stateful.js";
|
|
18
20
|
|
|
19
21
|
const SUBCOMMANDS = [
|
|
20
|
-
{ value: "settings", label: "settings", description: "Configure
|
|
22
|
+
{ value: "settings", label: "settings", description: "Configure subagent user settings" },
|
|
21
23
|
{ value: "status", label: "status", description: "Show effective subagent settings" },
|
|
22
24
|
{ value: "help", label: "help", description: "Show subagent settings help" },
|
|
23
25
|
];
|
|
@@ -26,7 +28,9 @@ const TOOL_VIEWPORT_SIZE = 10;
|
|
|
26
28
|
export interface SubagentSettingsRuntime {
|
|
27
29
|
getBlockingEnabled(): boolean;
|
|
28
30
|
getCompletionDelivery(): CompletionDelivery;
|
|
31
|
+
getConsultResourcePolicy(): ConsultResourcePolicy;
|
|
29
32
|
setCompletionDelivery(value: CompletionDelivery): void;
|
|
33
|
+
setConsultResourcePolicy(value: ConsultResourcePolicy): void;
|
|
30
34
|
getRuntimeStatus(): StatefulSubagentRuntimeStatus;
|
|
31
35
|
listAgents(includeClosed?: boolean): ManagedAgent[];
|
|
32
36
|
clearAgents(): Promise<number>;
|
|
@@ -114,7 +118,7 @@ async function showSubagentManager(
|
|
|
114
118
|
| "main"
|
|
115
119
|
| "workflow"
|
|
116
120
|
| "agents"
|
|
117
|
-
| "
|
|
121
|
+
| "settings"
|
|
118
122
|
| "advanced"
|
|
119
123
|
| "status"
|
|
120
124
|
| "help"
|
|
@@ -124,6 +128,7 @@ async function showSubagentManager(
|
|
|
124
128
|
| "set-workflow"
|
|
125
129
|
| "clear-agents"
|
|
126
130
|
| "set-completion"
|
|
131
|
+
| "set-consult-resources"
|
|
127
132
|
| "load-agent-picker"
|
|
128
133
|
| "pick-agent"
|
|
129
134
|
| "toggle-tool"
|
|
@@ -154,10 +159,10 @@ async function showSubagentManager(
|
|
|
154
159
|
to: "agents",
|
|
155
160
|
},
|
|
156
161
|
{
|
|
157
|
-
id: "
|
|
158
|
-
label: "
|
|
159
|
-
description: "
|
|
160
|
-
to: "
|
|
162
|
+
id: "settings",
|
|
163
|
+
label: "Settings",
|
|
164
|
+
description: "Configure async completion and read-only consultation resources",
|
|
165
|
+
to: "settings",
|
|
161
166
|
},
|
|
162
167
|
{
|
|
163
168
|
id: "advanced",
|
|
@@ -236,7 +241,7 @@ async function showSubagentManager(
|
|
|
236
241
|
hint: "back",
|
|
237
242
|
};
|
|
238
243
|
},
|
|
239
|
-
|
|
244
|
+
settings: () => subagentSettingsScreen(runtime),
|
|
240
245
|
advanced: () => ({
|
|
241
246
|
kind: "actions",
|
|
242
247
|
title: "Advanced Subagent Settings",
|
|
@@ -388,6 +393,8 @@ async function showSubagentManager(
|
|
|
388
393
|
return { kind: "stay" };
|
|
389
394
|
},
|
|
390
395
|
"set-completion": async ({ value }) => applyCompletionSetting(value, ctx, runtime),
|
|
396
|
+
"set-consult-resources": async ({ value }) =>
|
|
397
|
+
applyConsultResourceSetting(value, ctx, runtime),
|
|
391
398
|
"load-agent-picker": async () => {
|
|
392
399
|
availableAgents = discoverAgents(ctx.cwd, "user", readSubagentSettings() ?? {}).agents;
|
|
393
400
|
if (availableAgents.length === 0) {
|
|
@@ -468,7 +475,7 @@ async function showSubagentSettings(
|
|
|
468
475
|
runtime: SubagentSettingsRuntime,
|
|
469
476
|
owner: MenuOwner,
|
|
470
477
|
) {
|
|
471
|
-
const snapshot =
|
|
478
|
+
const snapshot = inspectConsultResourceSettings();
|
|
472
479
|
if (ctx.mode !== "tui") {
|
|
473
480
|
if (ctx.hasUI) {
|
|
474
481
|
ctx.ui.notify(
|
|
@@ -479,11 +486,14 @@ async function showSubagentSettings(
|
|
|
479
486
|
return;
|
|
480
487
|
}
|
|
481
488
|
const generation = owner.generation;
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
489
|
+
type SettingsAction = "set-completion" | "set-consult-resources";
|
|
490
|
+
const menu = defineMenu<undefined, "settings", SettingsAction, ExtensionCommandContext>({
|
|
491
|
+
start: "settings",
|
|
492
|
+
screens: { settings: () => subagentSettingsScreen(runtime) },
|
|
485
493
|
actions: {
|
|
486
494
|
"set-completion": async ({ value }) => applyCompletionSetting(value, ctx, runtime),
|
|
495
|
+
"set-consult-resources": async ({ value }) =>
|
|
496
|
+
applyConsultResourceSetting(value, ctx, runtime),
|
|
487
497
|
},
|
|
488
498
|
});
|
|
489
499
|
await runMenu(ctx, menu, {
|
|
@@ -493,17 +503,19 @@ async function showSubagentSettings(
|
|
|
493
503
|
});
|
|
494
504
|
}
|
|
495
505
|
|
|
496
|
-
function
|
|
497
|
-
const
|
|
506
|
+
function subagentSettingsScreen(runtime: SubagentSettingsRuntime) {
|
|
507
|
+
const completion = inspectCompletionDeliverySettings();
|
|
508
|
+
const consult = inspectConsultResourceSettings();
|
|
509
|
+
const error = completion.error ?? consult.error;
|
|
498
510
|
return {
|
|
499
511
|
kind: "settings" as const,
|
|
500
|
-
title:
|
|
512
|
+
title: error ? "Subagent User Settings ยท Read only" : "Subagent User Settings",
|
|
501
513
|
lines: [
|
|
502
514
|
"Applies now and to future sessions",
|
|
503
|
-
safeTerminalText(
|
|
504
|
-
...(
|
|
515
|
+
safeTerminalText(consult.path),
|
|
516
|
+
...(error ? [`Settings cannot be edited: ${safeTerminalText(error)}`] : []),
|
|
505
517
|
],
|
|
506
|
-
items:
|
|
518
|
+
items: error
|
|
507
519
|
? []
|
|
508
520
|
: [
|
|
509
521
|
{
|
|
@@ -511,10 +523,19 @@ function completionSettingsScreen() {
|
|
|
511
523
|
label: "When async work finishes",
|
|
512
524
|
description:
|
|
513
525
|
"Wait for your next turn, or request one synthesis turn after the root settles.",
|
|
514
|
-
currentValue: completionLabel(
|
|
526
|
+
currentValue: completionLabel(runtime.getCompletionDelivery()),
|
|
515
527
|
values: ["Wait until my next turn", "Resume automatically when finished"],
|
|
516
528
|
action: "set-completion" as const,
|
|
517
529
|
},
|
|
530
|
+
{
|
|
531
|
+
id: "consultResources",
|
|
532
|
+
label: "Read-only consultation resources",
|
|
533
|
+
description:
|
|
534
|
+
"Choose which trusted context, system, skill, and prompt resources a consultation inherits.",
|
|
535
|
+
currentValue: consultResourceLabel(runtime.getConsultResourcePolicy()),
|
|
536
|
+
values: ["Project context only", "No inherited resources", "All trusted resources"],
|
|
537
|
+
action: "set-consult-resources" as const,
|
|
538
|
+
},
|
|
518
539
|
],
|
|
519
540
|
};
|
|
520
541
|
}
|
|
@@ -539,6 +560,30 @@ function applyCompletionSetting(
|
|
|
539
560
|
}
|
|
540
561
|
}
|
|
541
562
|
|
|
563
|
+
function applyConsultResourceSetting(
|
|
564
|
+
value: string | undefined,
|
|
565
|
+
ctx: ExtensionCommandContext,
|
|
566
|
+
runtime: SubagentSettingsRuntime,
|
|
567
|
+
) {
|
|
568
|
+
const previous = runtime.getConsultResourcePolicy();
|
|
569
|
+
const next: ConsultResourcePolicy =
|
|
570
|
+
value === "No inherited resources"
|
|
571
|
+
? "none"
|
|
572
|
+
: value === "All trusted resources"
|
|
573
|
+
? "all"
|
|
574
|
+
: "project-context";
|
|
575
|
+
if (next === previous) return { kind: "stay" as const };
|
|
576
|
+
try {
|
|
577
|
+
updateConsultResourceSetting(next);
|
|
578
|
+
runtime.setConsultResourcePolicy(next);
|
|
579
|
+
ctx.ui.notify(`Saved and applied: ${consultResourceLabel(next)}.`, "info");
|
|
580
|
+
return { kind: "stay" as const };
|
|
581
|
+
} catch (error) {
|
|
582
|
+
ctx.ui.notify(`Subagent settings were not saved: ${formatError(error)}`, "error");
|
|
583
|
+
return { kind: "rejected" as const };
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
542
587
|
function blockReloadWithRetainedAgents(
|
|
543
588
|
ctx: ExtensionCommandContext,
|
|
544
589
|
runtime: SubagentSettingsRuntime,
|
|
@@ -597,7 +642,7 @@ function helpLines(): string[] {
|
|
|
597
642
|
const snapshot = inspectCompletionDeliverySettings();
|
|
598
643
|
return [
|
|
599
644
|
"/subagents โ choose delegation workflow, manage current agents, and configure agent tools",
|
|
600
|
-
"/subagents settings โ configure async completion
|
|
645
|
+
"/subagents settings โ configure async completion and read-only consultation resources",
|
|
601
646
|
"/subagents status โ show current-session and user-setting values",
|
|
602
647
|
"/subagents help โ show this help",
|
|
603
648
|
`User settings: ${safeTerminalText(snapshot.path)}`,
|
|
@@ -613,6 +658,7 @@ function formatManagerSummary(
|
|
|
613
658
|
return [
|
|
614
659
|
`Delegation: ${workflowLabel(current)}`,
|
|
615
660
|
`Completion: ${completionLabel(status.completionDelivery)}`,
|
|
661
|
+
`Consult resources: ${consultResourceLabel(runtime.getConsultResourcePolicy())}`,
|
|
616
662
|
`Agents: ${status.activeAgents} active ยท ${status.retainedAgents} retained`,
|
|
617
663
|
...(configured.value !== current
|
|
618
664
|
? [`Configured after reload: ${workflowLabel(configured.value)}`]
|
|
@@ -627,6 +673,7 @@ function formatStatus(
|
|
|
627
673
|
runtime?: SubagentSettingsRuntime,
|
|
628
674
|
): string {
|
|
629
675
|
const configuredWorkflow = inspectDelegationWorkflowSettings();
|
|
676
|
+
const consult = inspectConsultResourceSettings();
|
|
630
677
|
const current = runtime ? currentWorkflow(runtime, status) : configuredWorkflow.value;
|
|
631
678
|
return [
|
|
632
679
|
"Current session",
|
|
@@ -640,6 +687,8 @@ function formatStatus(
|
|
|
640
687
|
` Configured delegation: ${workflowLabel(configuredWorkflow.value)}`,
|
|
641
688
|
` Completion source: ${snapshot.source}`,
|
|
642
689
|
` Configured completion: ${completionLabel(snapshot.value)}`,
|
|
690
|
+
` Consultation resources: ${consultResourceLabel(runtime?.getConsultResourcePolicy() ?? consult.value)}`,
|
|
691
|
+
` Consultation resource source: ${consult.source}`,
|
|
643
692
|
` Path: ${safeTerminalText(snapshot.path)}`,
|
|
644
693
|
configuredWorkflow.error || snapshot.error
|
|
645
694
|
? ` Warning: ${safeTerminalText(configuredWorkflow.error ?? snapshot.error ?? "invalid settings")}`
|
|
@@ -688,13 +737,28 @@ function completionLabel(value: CompletionDelivery): string {
|
|
|
688
737
|
return value === "auto-resume" ? "Resume automatically when finished" : "Wait until my next turn";
|
|
689
738
|
}
|
|
690
739
|
|
|
740
|
+
function consultResourceLabel(value: ConsultResourcePolicy): string {
|
|
741
|
+
switch (value) {
|
|
742
|
+
case "project-context":
|
|
743
|
+
return "Project context only";
|
|
744
|
+
case "none":
|
|
745
|
+
return "No inherited resources";
|
|
746
|
+
case "all":
|
|
747
|
+
return "All trusted resources";
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
691
751
|
function workflowEffects(current: DelegationWorkflow, next: DelegationWorkflow): string[] {
|
|
692
752
|
const blockingEnabled = (value: DelegationWorkflow) =>
|
|
693
753
|
value === "all" || value === "blocking-only";
|
|
694
754
|
const asyncEnabled = (value: DelegationWorkflow) => value === "all" || value === "async-only";
|
|
695
755
|
const effects: string[] = [];
|
|
696
756
|
if (blockingEnabled(current) !== blockingEnabled(next)) {
|
|
697
|
-
effects.push(
|
|
757
|
+
effects.push(
|
|
758
|
+
blockingEnabled(next)
|
|
759
|
+
? "Add blocking `subagent` and read-only `subagent_consult`"
|
|
760
|
+
: "Remove blocking `subagent` and read-only `subagent_consult`",
|
|
761
|
+
);
|
|
698
762
|
}
|
|
699
763
|
if (asyncEnabled(current) !== asyncEnabled(next)) {
|
|
700
764
|
effects.push(
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const READ_ONLY_CONSULT_TOOLS = ["read", "grep", "find", "ls"] as const;
|
|
2
|
+
|
|
3
|
+
const READ_ONLY_CONSULT_TOOL_SET = new Set<string>(READ_ONLY_CONSULT_TOOLS);
|
|
4
|
+
|
|
5
|
+
export function resolveConsultTools(tools: readonly string[] | undefined): string[] {
|
|
6
|
+
if (tools === undefined) return [...READ_ONLY_CONSULT_TOOLS];
|
|
7
|
+
const seen = new Set<string>();
|
|
8
|
+
const effective: string[] = [];
|
|
9
|
+
for (const tool of tools) {
|
|
10
|
+
if (!READ_ONLY_CONSULT_TOOL_SET.has(tool) || seen.has(tool)) continue;
|
|
11
|
+
seen.add(tool);
|
|
12
|
+
effective.push(tool);
|
|
13
|
+
}
|
|
14
|
+
return effective;
|
|
15
|
+
}
|