pi-subagents 0.51.0 → 0.52.1
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 +57 -0
- package/agents/oracle.md +3 -1
- package/agents/reviewer.md +1 -0
- package/agents/scout.md +2 -2
- package/agents/worker.md +2 -1
- package/docs/agents.md +8 -3
- package/docs/extension-api.md +1 -1
- package/docs/models.md +2 -0
- package/docs/observability.md +37 -1
- package/docs/workflows.md +2 -0
- package/package.json +1 -1
- package/skills/pi-subagents/SKILL.md +3 -0
- package/skills/pi-subagents/references/constraints-and-recipes.md +6 -2
- package/skills/pi-subagents/references/execution-controls.md +12 -1
- package/skills/pi-subagents/references/multi-lane-orchestration.md +39 -0
- package/skills/pi-subagents/references/prompting-and-roles.md +2 -0
- package/src/agents/agent-management.ts +24 -6
- package/src/agents/agents.ts +37 -11
- package/src/agents/skills.ts +1 -1
- package/src/api/external-job-provider.ts +3 -2
- package/src/api/preflight.ts +5 -2
- package/src/extension/index.ts +1 -0
- package/src/extension/public-execution.ts +3 -1
- package/src/extension/schemas.ts +1 -1
- package/src/intercom/native-supervisor-channel.ts +2 -1
- package/src/missions/workflow-state.ts +2 -2
- package/src/runs/background/async-execution.ts +19 -9
- package/src/runs/background/async-resume.ts +19 -2
- package/src/runs/background/async-retention.ts +2 -4
- package/src/runs/background/completion-dedupe.ts +5 -1
- package/src/runs/background/completion-replay.ts +22 -12
- package/src/runs/background/fleet-view.ts +68 -19
- package/src/runs/background/inspect-rpc.ts +443 -0
- package/src/runs/background/notify.ts +28 -4
- package/src/runs/background/result-watcher.ts +44 -0
- package/src/runs/background/resume-guidance.ts +8 -5
- package/src/runs/background/scheduled-runs.ts +2 -0
- package/src/runs/background/subagent-runner.ts +3 -2
- package/src/runs/background/subagent-wait.ts +1 -1
- package/src/runs/background/wait-subscriptions.ts +1 -1
- package/src/runs/foreground/execution.ts +1 -1
- package/src/runs/foreground/subagent-executor.ts +239 -76
- package/src/runs/foreground/workflow-detach-reconcile.ts +194 -0
- package/src/runs/shared/acceptance.ts +4 -4
- package/src/runs/shared/external-job-bridge.ts +0 -6
- package/src/runs/shared/model-fallback.ts +96 -35
- package/src/runs/shared/session-lease.ts +0 -6
- package/src/runs/shared/worktree.ts +2 -1
- package/src/shared/types.ts +11 -0
- package/src/slash/slash-bridge.ts +2 -1
- package/src/slash/slash-commands.ts +18 -0
- package/src/watchdog/change-signature.ts +1 -1
- package/src/watchdog/lsp-diagnostics.ts +1 -0
- package/src/workflows/chat-progress.ts +1 -1
- package/src/workflows/scripted-workflow.ts +13 -2
- package/agents/gpt-pro.md +0 -17
package/CHANGELOG.md
CHANGED
|
@@ -2,8 +2,65 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.52.1] - 2026-08-20
|
|
6
|
+
|
|
7
|
+
### Highlights
|
|
8
|
+
- Model setup errors now point to the right alternate provider when there is one clear match.
|
|
9
|
+
- Surf's optional `gpt-pro` package agent has a smoother path to run ChatGPT Pro web jobs through the external-job bridge when the user is logged in.
|
|
10
|
+
- External-job providers can add metadata or extra operations without breaking provider discovery.
|
|
11
|
+
- The packaged skill now includes a concise guide for coordinating multiple tasks, worktrees, and repositories.
|
|
12
|
+
- Pi extension worktrees now have clearer guidance to avoid duplicate auto-loaded tools and shortcuts.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- Suggest the unique alternate provider model when an explicit qualified subagent model is unavailable, without resolving across providers. Thanks to [@lallenlowe](https://github.com/lallenlowe) for #1280.
|
|
16
|
+
- Accept extra fields on registered external-job providers, such as `kind`, `wakeChannels`, or additional operations. This keeps integrations such as Surf's `gpt-pro` package agent from breaking provider discovery as they add browser-backed job metadata, while job payload validation stays strict.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- Add a pi-subagents reference for coordinating multiple tasks, worktrees, and repositories, including guidance for keeping Pi extension worktrees outside auto-discovered extension directories.
|
|
20
|
+
|
|
21
|
+
## [0.52.0] - 2026-08-19
|
|
22
|
+
|
|
23
|
+
### Highlights
|
|
24
|
+
- Async workflows are much harder to break mid-flight: a transient status-file lock, a stalled child, or a paused supervisor hand-off no longer fails or loses an otherwise healthy run.
|
|
25
|
+
- Hosts can now inspect a running or completed async child on demand — task, recent transcript, and final output — without spending a model turn, and that output stays available after delivery.
|
|
26
|
+
- macOS and FreeBSD sandboxes stop warning about setuid `/bin/ps`, and Windows stops flashing console windows during busy runs.
|
|
27
|
+
- Gateway and proxy models work better: children can inherit the parent's session model, and Hugging Face-style `owner/name` model ids resolve correctly.
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
- Add `/subagents-inspect-rpc`, a host-facing bridge command that answers on-demand async child inspection requests with a correlated, bounded `PI_SUBAGENT_INSPECT_JSON:` widget payload (task, transcript window, final output), so RPC hosts can inspect children without a model turn while the live status feed stays small. Thanks to [@yanqianglu](https://github.com/yanqianglu) for #1254.
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
- Guide oracle plan and design advice through a short same-session consultation when a material tradeoff remains, while keeping the parent as final decision-maker (#1245).
|
|
34
|
+
- Improve bundled role and parent prompts for source-first discovery in noisy codebases (#1247).
|
|
35
|
+
- Make Surf's `gpt-pro` agent an optional package integration instead of a pi-subagents builtin. If you disabled the old builtin workaround, remove `agentOverrides.gpt-pro.disabled` before using Surf's package agent. Thanks to [@binhex](https://github.com/binhex) for #1256.
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
- Stop spawning setuid `/bin/ps` for process start identity on macOS and FreeBSD. Sandboxes such as nono no longer report `forbidden-exec-sugid` from session leases, retention locks, external-job claims, or mission state. Those platforms stay fail-closed without pid-reuse detection. Thanks to [@jdumas](https://github.com/jdumas) for #1273.
|
|
39
|
+
- Stop a transient lock on `status.json` (seen on Windows) from failing an already-completed workflow child and aborting its still-running siblings. Status updates after launch now degrade to a `subagent.workflow.status_write_failed` event instead of failing the run, and a throwing `onTrace` host callback can no longer reject a child promise. Follow-up to #1143. Thanks to [@MarcusNeufeldt](https://github.com/MarcusNeufeldt) for #1272.
|
|
40
|
+
- Keep a still-paused workflow result when reconcile republishes updated child output during paused delivery, so a same-state revision is not overwritten or deleted as the old payload.
|
|
41
|
+
- Persist async terminal `status.json` before publishing the result file, so observers cannot see a completed result while the run still looks `running`.
|
|
42
|
+
- Stop Windows opening a console window for each helper process (Git, `gh`, PowerShell, `npm root -g`) spawned during a run, which made a busy run disruptive to work alongside. Thanks to [@MarcusNeufeldt](https://github.com/MarcusNeufeldt) for #1274.
|
|
43
|
+
- Keep completed inspect RPC output available from the durable completion replay after result delivery consumes its one-shot payload, including per-child inline result tails (#1254).
|
|
44
|
+
- After a workflow child detaches for supervisor coordination, clear attention once the reply is delivered, keep `subagent_wait` blocked until the child exits, and reconcile the paused workflow when that child completes — even after the paused payload was already delivered. A timed-out workflow can also resume from its persisted child session when the workflow dir has no recovery descriptor. Thanks to [@skystar567](https://github.com/skystar567) for #1263.
|
|
45
|
+
- Wake the idle parent when an async workflow child needs attention, and persist that control event on the enclosing workflow. Status already showed the stall; the parent notice did not. Thanks to [@Yibo-Zhang](https://github.com/Yibo-Zhang) for #1266.
|
|
46
|
+
- Resolve Hugging Face-style `owner/name` model ids against the registry instead of treating every slash as `provider/id`. Fully qualified `huggingface/owner/name` still wins, and a first path segment that matches a registered provider still means `provider/id`. Thanks to [@mr-brobot](https://github.com/mr-brobot) for #1264.
|
|
47
|
+
- Keep public structured single-child calls synchronous when `asyncByDefault:false` and `async` is omitted. Thanks to [@Nofuture123](https://github.com/Nofuture123) for #1257.
|
|
48
|
+
- Trust the running parent session model when no model is configured, so gateway and proxy parent models can launch children outside the host registry. Thanks to [@Nofuture123](https://github.com/Nofuture123) for #1258.
|
|
49
|
+
- Isolate colliding inherited workflow child output defaults while preserving explicit output collision checks. Thanks to [@Reverier-Xu](https://github.com/Reverier-Xu) for #1253.
|
|
50
|
+
- Show a scheduled run's completion and name the schedule that produced it, so scheduled work no longer finishes silently in a session that cannot attribute it. Thanks to [@albertgwo](https://github.com/albertgwo) for #1246.
|
|
51
|
+
- Show resume-first guidance for failed async runs only when a matching recovery descriptor exists, so missing recovery data no longer points users to a resume command that cannot work. Thanks to [@graadient](https://github.com/graadient) for #1241.
|
|
52
|
+
- Keep bundled agent discovery stable across hot package updates, so long-running sessions do not parse newer bundled agent files with older loaded code. Thanks to [@graadient](https://github.com/graadient) for #1242.
|
|
53
|
+
- Resolve relative extension paths against the defining agent file, so portable agent definitions load child extensions from the declared location. Thanks to [@tayiorbeii](https://github.com/tayiorbeii) for #1249.
|
|
54
|
+
|
|
5
55
|
## [0.51.0] - 2026-08-18
|
|
6
56
|
|
|
57
|
+
### Highlights
|
|
58
|
+
- Workflow orchestration is easier to control with stable-key steering, clearer fanout guidance, and a supported external-job runner path.
|
|
59
|
+
- Async runs are harder to lose when storage is full, file access is temporarily denied, identifiers are too long, or multiple Pi windows share one session.
|
|
60
|
+
- macOS reloads and idle sessions do less fragile filesystem watching, which avoids reload hangs without adding always-on work.
|
|
61
|
+
- Herdr and Fleet are less disruptive: panes stay in the background by default, trusted transcripts open cleanly, and live workflow children steer through the right route.
|
|
62
|
+
- The workflow API is cleaner: scripted workflows are the supported path, and removed legacy chain surfaces now have direct migration guidance.
|
|
63
|
+
|
|
7
64
|
### Added
|
|
8
65
|
- Add stable-key `runs.steer` to `workflowScript`, with routing for foreground and async children, structured receipts, trace entries, and checks for unawaited calls (#1186).
|
|
9
66
|
- Add `runner.type: external-job`, the exported provider bridge, the Surf GPT Pro `gpt-pro` profile, and docs for external advisor data boundaries (#1189).
|
package/agents/oracle.md
CHANGED
|
@@ -16,7 +16,9 @@ Your primary job is to prevent the main agent from making hidden, conflicting, o
|
|
|
16
16
|
|
|
17
17
|
Before you do anything else, reconstruct the key inherited decisions, constraints, and open questions from the forked conversation, codebase state, and task. Those decisions form your baseline contract. Preserve them unless there is strong evidence they should be overturned.
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
Match search scope to the question. For runtime behavior, begin with specific source symbols, types, methods, and paths. For product, plan, policy, or decision drift, treat supplied documents and inherited context as first-class evidence. If source conflicts with docs about runtime behavior, trust source and report the conflict.
|
|
20
|
+
|
|
21
|
+
If the task asks about asking or consulting the oracle, or asks to ask, consult, discuss with, or come to agreement with the oracle about a plan, design, or architecture decision, treat it as a short live consultation unless the parent explicitly requests a one-shot report. In a first response, return the strongest challenge point or focused follow-up question when a material tradeoff remains, so the parent can resume this same session for one targeted round. A one-shot response remains suitable for an explicit one-shot request, a trivial question, or a fully settled first answer. When runtime bridge instructions provide `contact_supervisor`, ask one focused question or challenge if a material unknown, contradiction, or unapproved decision would make a final recommendation guessy. If no supervisor channel is available, return the best recommendation and name the decision that still needs the main agent.
|
|
20
22
|
|
|
21
23
|
If you need clarification from the main agent and bridge instructions provide `contact_supervisor`, use it with `reason: "need_decision"` and wait for the reply. Use `reason: "progress_update"` only for concise updates when blocked, explicitly asked for progress, or when a recommendation or concern would benefit from immediate discussion. Keep coordination traffic tight and purposeful. Do not narrate your whole review through `contact_supervisor`.
|
|
22
24
|
|
package/agents/reviewer.md
CHANGED
|
@@ -50,6 +50,7 @@ Review a PR or issue by understanding the context, then verifying:
|
|
|
50
50
|
- Tests and docs are updated as needed.
|
|
51
51
|
|
|
52
52
|
## Working rules
|
|
53
|
+
- Start from the exact diff and named source seam for code-behavior review. Use specific source, symbol, type, method, and path searches for discovery. Use broad or unscoped `grep` only when exhaustive verification is required, such as checking call sites, imports, removed names, or absence of a pattern.
|
|
53
54
|
- Read the relevant files first. Read plan and progress when the task supplies them.
|
|
54
55
|
- Repo-local `progress.md` files are allowed scratch/memory files. Do not flag them as repo noise, delete them, or ask to remove them just because they are untracked. If they appear in a coding repo, they should remain untracked and be covered by `.gitignore`.
|
|
55
56
|
- Do not use shell commands or write files. Report any test or Git command that a supervisor must run.
|
package/agents/scout.md
CHANGED
|
@@ -12,7 +12,7 @@ defaultProgress: true
|
|
|
12
12
|
|
|
13
13
|
You are a scouting subagent running inside pi.
|
|
14
14
|
|
|
15
|
-
Use the provided tools directly. Move fast, but do not guess. Prefer targeted search and selective reading over
|
|
15
|
+
Use the provided tools directly. Move fast, but do not guess. Start discovery with task-provided paths and specific symbols, types, methods, filenames, or likely source roots. Use `find` for path discovery. Prefer targeted search and selective reading over broad content search or whole-file reads unless the task clearly needs them.
|
|
16
16
|
|
|
17
17
|
Focus on the minimum context another agent needs in order to act:
|
|
18
18
|
- relevant entry points
|
|
@@ -22,7 +22,7 @@ Focus on the minimum context another agent needs in order to act:
|
|
|
22
22
|
- constraints, risks, and open questions
|
|
23
23
|
|
|
24
24
|
Working rules:
|
|
25
|
-
- Use `grep`, `find`, `ls`, and `read` to map the area before diving deeper.
|
|
25
|
+
- Use `grep`, `find`, `ls`, and `read` to map the area before diving deeper. Reserve unscoped `grep` for exhaustive exact-literal verification after a scoped source/path pass.
|
|
26
26
|
- Use `bash` only for non-interactive inspection commands.
|
|
27
27
|
- When you cite code, use exact file paths and line ranges.
|
|
28
28
|
- If you are told to write output, write it to the provided path and keep the final response short.
|
package/agents/worker.md
CHANGED
|
@@ -16,7 +16,7 @@ You are `worker`: the implementation subagent.
|
|
|
16
16
|
|
|
17
17
|
You are the single writer thread. Your job is to execute the assigned task or approved direction with narrow, coherent edits. The main agent and user remain the decision authority.
|
|
18
18
|
|
|
19
|
-
Use the provided tools directly. First
|
|
19
|
+
Use the provided tools directly. First read the inherited context, supplied files, plan, task paths, and named seams. Then implement carefully and minimally. Use broad search only to verify or expand from that starting point.
|
|
20
20
|
|
|
21
21
|
The builtin worker uses a strict tool allowlist. It does not inherit ambient extension tools from the parent session. To use an extension tool, configure a custom agent with the tool name explicitly listed in `tools` and load its provider through `extensions` or `subagentOnlyExtensions`.
|
|
22
22
|
|
|
@@ -34,6 +34,7 @@ Default responsibilities:
|
|
|
34
34
|
|
|
35
35
|
Working rules:
|
|
36
36
|
- Prefer narrow, correct changes over broad rewrites.
|
|
37
|
+
- Preserve source discoverability: use specific names, clear types, one spelling per concept, source-named tests, and definition comments only when they explain a needed constraint.
|
|
37
38
|
- Do not add speculative scaffolding or future-proofing unless explicitly required.
|
|
38
39
|
- Do not leave placeholder code, TODOs, or silent scope changes.
|
|
39
40
|
- Use `bash` for inspection, validation, and relevant tests.
|
package/docs/agents.md
CHANGED
|
@@ -41,14 +41,19 @@ Builtins load at the lowest priority, so a user or project agent with the same n
|
|
|
41
41
|
| `worker` | Implementation work, including approved oracle handoffs. It edits files, validates, and escalates unapproved decisions instead of guessing. |
|
|
42
42
|
| `reviewer` | Code review and small fixes. It checks the implementation against the task/plan, tests, edge cases, and simplicity. |
|
|
43
43
|
| `oracle` | A second opinion before acting. It challenges assumptions, catches drift, and recommends the safest next move without editing. |
|
|
44
|
-
| `gpt-pro` | Read-only Surf GPT Pro advice through the `surf-oracle` external-job provider bridge. |
|
|
45
44
|
| `delegate` | A lightweight general delegate when you want a child agent that behaves close to the parent session. |
|
|
46
45
|
|
|
47
46
|
Rule of thumb: `scout` before you understand the code, `researcher` before you trust external facts, `worker` to implement, `reviewer` to check, and `oracle` when the decision itself feels risky.
|
|
48
47
|
|
|
49
48
|
`oracle` is an advisory reviewer that critiques direction and proposes an execution prompt without editing files. `advisor` is the same bundled role under the Claude Code-compatible name.
|
|
50
49
|
|
|
51
|
-
|
|
50
|
+
### Optional Surf integration
|
|
51
|
+
|
|
52
|
+
When `surf-cli` is installed and loaded, Surf can expose a `gpt-pro` package agent through the `surf-oracle` external-job provider. It starts through the same `subagent({ agent: "gpt-pro" })` mental model as any other agent, but Surf owns the package agent and provider. Surf maps `model: pro` to ChatGPT GPT-5.6 Sol Pro web mode. pi-subagents does not own that model mapping.
|
|
53
|
+
|
|
54
|
+
If you disabled the old bundled `gpt-pro` workaround with `agentOverrides.gpt-pro.disabled`, remove that override before using Surf's package agent.
|
|
55
|
+
|
|
56
|
+
The Pi async run remains the source of truth for status, artifacts, wake/wait, mission attachment, retention, and diagnostics.
|
|
52
57
|
|
|
53
58
|
Claude Code can be configured as a read-only advisor with `runner.type: external-cli` when the Claude Code CLI is installed and you have verified the flags for your local version. pi-subagents does not ship or enforce Claude Code flags. Use a project or user agent like this only after checking your CLI help:
|
|
54
59
|
|
|
@@ -69,7 +74,7 @@ Review the task and return advice only. Do not edit files.
|
|
|
69
74
|
|
|
70
75
|
### Advisory runner data boundary
|
|
71
76
|
|
|
72
|
-
Native `oracle` runs inside Pi and can use its configured read tools. `claude-advisor` sends the assembled prompt to the configured local external CLI through stdin.
|
|
77
|
+
Native `oracle` runs inside Pi and can use its configured read tools. `claude-advisor` sends the assembled prompt to the configured local external CLI through stdin. An external-job agent sends the assembled prompt to its registered provider. Provider options and a prompt digest are persisted in Pi run state. The prompt text is delivered through the local host bridge to the provider and is not stored in the public result payload. Do not place secrets in advisory prompts unless the target provider is approved to receive them.
|
|
73
78
|
|
|
74
79
|
### External-job state table
|
|
75
80
|
|
package/docs/extension-api.md
CHANGED
|
@@ -357,7 +357,7 @@ This matters because "is the parent busy?" is the wrong idle signal. A parent th
|
|
|
357
357
|
If your host reclaims idle sessions, keep a session alive while it still has live detached work:
|
|
358
358
|
|
|
359
359
|
- Read run state from the status files under the async run directory rather than from event traffic. A long, quiet workflow sends almost nothing to the parent, so recent-activity heuristics conclude the wrong thing.
|
|
360
|
-
- Treat `queued` and `running` as live, matching `isActiveAsyncState`. `paused` is
|
|
360
|
+
- Treat `queued` and `running` as live, matching `isActiveAsyncState`. An interrupted run that is `paused` is finalized. A workflow that paused because a child used `contact_supervisor` still has a live child; keep that parent session until reconcile writes `complete` or `failed`.
|
|
361
361
|
- Do not treat `lastUpdate` as a heartbeat. The runner advances it in memory every second but only rewrites `status.json` when the activity classification changes, so a live run inside one long quiet tool call leaves a stale file behind. Judging liveness by file age will reap exactly the run you meant to protect.
|
|
362
362
|
- Prefer the recorded runner `pid`, which stays true through a silent tool call and goes false when the runner dies. Keep file age only as a fallback for runs that record no pid, and give it a wide window.
|
|
363
363
|
- Match `sessionId` in `status.json` against both forms. It is resolved as `getSessionFile() ?? getSessionId()`, so it is normally the parent's session *file path*, but a session that is not persisted records a bare session id instead.
|
package/docs/models.md
CHANGED
|
@@ -141,6 +141,8 @@ You do not have to spell a model exactly. Model ids are matched fuzzily against
|
|
|
141
141
|
|
|
142
142
|
Exact `provider/id` matches still win, and a qualified provider query never silently switches providers — it only matches within the named provider. Ambiguous bare ids that exist under multiple providers still require a provider prefix or the current session's provider to disambiguate.
|
|
143
143
|
|
|
144
|
+
Registry ids that themselves contain `/` (Hugging Face `owner/name`) resolve the same way as Pi's main agent: `thinkingmachines/Inkling` becomes `huggingface/thinkingmachines/Inkling` when that id is unique or offered by the current session provider. A first path segment that matches a registered provider still means `provider/id`.
|
|
145
|
+
|
|
144
146
|
## Model scope enforcement
|
|
145
147
|
|
|
146
148
|
To keep subagents inside a budget or compliance profile, enforce a model scope. Put `subagents.modelScope` in user or project settings (project overrides user):
|
package/docs/observability.md
CHANGED
|
@@ -99,6 +99,42 @@ Pi binds `Ctrl+B` to editor cursor-left by default. The extension shortcut takes
|
|
|
99
99
|
|
|
100
100
|
If something feels misconfigured, run `/subagents-doctor` or ask: "Check whether subagents and intercom are set up correctly."
|
|
101
101
|
|
|
102
|
+
## Host inspection protocol (RPC)
|
|
103
|
+
|
|
104
|
+
RPC hosts receive live async status through the bounded `subagent-async` widget
|
|
105
|
+
(`PI_SUBAGENT_ASYNC_JSON:` payload). For on-demand detail — a child's delegated
|
|
106
|
+
task, transcript window, or final output — hosts can invoke the extension
|
|
107
|
+
command:
|
|
108
|
+
|
|
109
|
+
```text
|
|
110
|
+
/subagents-inspect-rpc <requestId> <asyncId> [childId] [--lines N]
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Extension commands execute inline over Pi RPC without a model turn. The reply
|
|
114
|
+
arrives as a single emit-then-retract update on the dedicated `subagent-inspect`
|
|
115
|
+
widget key: the first (and only) line is `PI_SUBAGENT_INSPECT_JSON:<JSON>` with a
|
|
116
|
+
versioned `pi-subagents.inspect-reply` payload correlated by `requestId`. Hosts
|
|
117
|
+
must not render this widget; they should buffer the payload by `requestId` and
|
|
118
|
+
drop unmatched replies.
|
|
119
|
+
|
|
120
|
+
Inspection properties:
|
|
121
|
+
|
|
122
|
+
- Read-only and on demand: nothing is persisted, broadcast, or added to
|
|
123
|
+
notification details; every request re-reads canonical run artifacts after
|
|
124
|
+
the same reconciliation the status action performs.
|
|
125
|
+
- Session-scoped: runs owned by another session fail with `foreign_session`;
|
|
126
|
+
unknown ids fail with `not_found`; cleaned-up artifacts fail with `stale`.
|
|
127
|
+
- Bounded: per-field string caps, a message-count cap (`--lines`, max 200), and
|
|
128
|
+
a hard 64 KB serialized budget with explicit `truncated` markers.
|
|
129
|
+
- No filesystem paths appear in the reply.
|
|
130
|
+
- `task` is the child session's first user message and is only populated when
|
|
131
|
+
it is genuinely attributable (fresh-context child whose session file fits the
|
|
132
|
+
read window); forked children omit it.
|
|
133
|
+
- `childId` is exactly the node id the host received in the status snapshot
|
|
134
|
+
(step `workflowKey`/`runId`/`step:<index>`, or a nested run id).
|
|
135
|
+
|
|
136
|
+
In TUI mode the command only points at the interactive `/subagents` inspector.
|
|
137
|
+
|
|
102
138
|
## Async run artifacts
|
|
103
139
|
|
|
104
140
|
Async runs write machine-readable lifecycle artifacts for observability and workflow gates:
|
|
@@ -122,7 +158,7 @@ The result file is consumed and deleted once its completion notice is delivered.
|
|
|
122
158
|
|
|
123
159
|
`subagent_wait` surfaces a slim projection of each terminal payload it covered in its own tool-result `details.completions` — run identity, per-child agent/`runId`/success, artifact paths, and the bounded `archivePath`, without duplicating output text. It reads the replay when watcher delivery or a watcher restart has removed the one-shot result file and in-memory completion state is unavailable. Durable non-blocking wait subscriptions use the same replay in their delivered details. Workflow result files record each child's `runId` explicitly, since a workflow child's `artifactPaths` entry points at its saved output rather than the artifact files keyed by the id. Extensions observing `tool_result` events can read run and artifact identity from there instead of parsing the text summary.
|
|
124
160
|
|
|
125
|
-
Output archives reference an existing child output artifact or session file when one is available. For children without either file, the archive stores
|
|
161
|
+
Output archives reference an existing child output artifact or session file when one is available. For children without either file, the archive stores a per-child `result-tail` entry with `resultIndex`, bounded to 64 KiB per child, and records whether it was truncated. Replay and archive JSON use `version: 1`; consumers must ignore unknown fields.
|
|
126
162
|
|
|
127
163
|
Nested fanout status is stored as compact sidecar event/registry metadata and merged into parent status views and result/intercom payloads; full recursive status snapshots are not embedded in parent result files.
|
|
128
164
|
|
package/docs/workflows.md
CHANGED
|
@@ -280,6 +280,8 @@ The parent replies with `subagent_supervisor({ action: "reply", replyTo, message
|
|
|
280
280
|
|
|
281
281
|
Child-side routine completion handoffs are not expected. If a child appears stalled, needs-attention notices show up in the parent session with useful next actions, such as checking `subagent({ action: "status" })`, interrupting the run, or nudging the child.
|
|
282
282
|
|
|
283
|
+
If a `workflowScript` child detaches through `contact_supervisor`, the enclosing async workflow stays `paused` until that child exits. Then the extension reconciles it to `complete` or `failed`. Wait on the child until that happens.
|
|
284
|
+
|
|
283
285
|
If messages do not show up, run `/subagents-doctor`. Advanced users can tune the bridge with `intercomBridge` in [configuration.md](configuration.md).
|
|
284
286
|
|
|
285
287
|
## Recursion guard
|
package/package.json
CHANGED
|
@@ -22,6 +22,7 @@ Read the matching reference file before acting. Paths are relative to this `SKIL
|
|
|
22
22
|
| --- | --- |
|
|
23
23
|
| Decide whether to delegate, choose agents, compare tool versus slash commands, apply prompt techniques, or understand builtin roles | `references/prompting-and-roles.md` |
|
|
24
24
|
| Run one-child, scripted, async, scheduled, mission-backed, forked, watchdog, oracle, or intercom-coordinated workflows | `references/execution-controls.md` |
|
|
25
|
+
| Coordinate several independent tasks, worktrees, repositories, or writer lanes | `references/multi-lane-orchestration.md` |
|
|
25
26
|
| List/create/update/delete/eject/disable agents, inspect legacy chain records, edit agent files, use prompt-template integration, or expose extension RPC | `references/management-authoring-rpc.md` |
|
|
26
27
|
| Check safety constraints, best practices, standard workflows, or error handling | `references/constraints-and-recipes.md` |
|
|
27
28
|
|
|
@@ -30,6 +31,8 @@ For broad or uncertain requests, read more than one reference. For complex work,
|
|
|
30
31
|
## Always-on constraints
|
|
31
32
|
|
|
32
33
|
- Keep the parent as orchestrator and final decision-maker.
|
|
34
|
+
- Before multiple mutation-capable lanes, record a lane board and each lane's isolation path.
|
|
35
|
+
- For plan, design, or architecture advice that asks to consult, discuss with, or come to agreement with `oracle`, use a short same-session consultation loop: read the first result, resume once with a targeted challenge when material tradeoffs remain, then synthesize the parent decision. Keep explicit one-shot, trivial, and fully settled consultations one-shot.
|
|
33
36
|
- Use one writer per cwd/worktree unless isolated worktrees are intentional.
|
|
34
37
|
- For cross-codebase work, record the target repo, explicit `cwd`, authority boundary, and expected output before launch. Do not assume the parent session cwd is the child repo.
|
|
35
38
|
- For parallel fanout, compare child prompts before launch. Do not send clone prompts with only issue numbers, titles, or broad file globs swapped; each child needs a lane-specific task, source seam, prior evidence, and decision that remains distinct without the item number. Launch that fanout as one async `workflowScript` with stable keys and aggregate output unless there is truly only one child.
|
|
@@ -65,6 +65,10 @@ Give subagents specific tasks rather than vague mandates.
|
|
|
65
65
|
|
|
66
66
|
If a subagent encounters an unapproved product, architecture, scope, merge, release, credential, or authority choice, it should use `contact_supervisor` and wait for the reply instead of deciding alone. Generic `intercom` is external or provider-supplied only. Use it only when external bridge instructions provide an explicit safe target. External checks, receipts, and review bots provide evidence only; they do not grant authority.
|
|
67
67
|
|
|
68
|
+
### Use a short oracle consultation for material advice
|
|
69
|
+
|
|
70
|
+
When a user asks to ask, consult, discuss with, or come to agreement with `oracle` about a plan, design, or architecture decision, do not treat the first advisory report as final when it raises a material challenge or tradeoff. Read it, resume the same oracle session once with a targeted question, then make the parent decision. An explicit one-shot request, a trivial question, or a fully settled first answer does not need a follow-up.
|
|
71
|
+
|
|
68
72
|
### Intervene only on clear control signals
|
|
69
73
|
|
|
70
74
|
Use subagent control proactively when a delegated run emits `needs_attention`, or when a human asks you to regain control. Do not interrupt just because a child has briefly produced no output. Silence can be normal during long tool calls, test runs, or model reasoning.
|
|
@@ -79,8 +83,8 @@ Use `/name` so intercom targeting stays stable.
|
|
|
79
83
|
|
|
80
84
|
```js
|
|
81
85
|
subagent({ workflowScript: `
|
|
82
|
-
const context = await runs.run("recon", { agent: "scout", task: "
|
|
83
|
-
return (await runs.run("implement", { agent: "worker", task: "Implement from: " + context.output })).output;
|
|
86
|
+
const context = await runs.run("recon", { agent: "scout", task: "Start from the named source roots, paths, and symbols. Identify the implementation seam before broad search." });
|
|
87
|
+
return (await runs.run("implement", { agent: "worker", task: "Read the scout output, plan paths, and named files/seams first. Implement from: " + context.output })).output;
|
|
84
88
|
` })
|
|
85
89
|
```
|
|
86
90
|
|
|
@@ -28,7 +28,7 @@ External CLI profiles are async-only and one-shot. They support lifecycle artifa
|
|
|
28
28
|
|
|
29
29
|
### External job profiles
|
|
30
30
|
|
|
31
|
-
An agent may set `runner.type: external-job` with a non-empty `provider` and optional JSON `options`.
|
|
31
|
+
An agent may set `runner.type: external-job` with a non-empty `provider` and optional JSON `options`. When `surf-cli` is installed and loaded, Surf can optionally expose a `gpt-pro` package agent through provider `surf-oracle`. Surf maps `model: pro` to ChatGPT GPT-5.6 Sol Pro web mode. pi-subagents does not own that package agent or model mapping. Remove any old `agentOverrides.gpt-pro.disabled` workaround before using Surf's package agent. The provider must be registered in the host Pi process through `pi-subagents/external-job-provider`; the async runner talks to that parent-owned registry through a local operation bridge.
|
|
32
32
|
|
|
33
33
|
External job profiles are async-only. The provider owns the remote job and Pi owns the async run record. Status persists provider name, provider job id, prompt digest, provider options, handle/conversation URLs when supplied, result artifact path, last known state, and provider failure code/message. Recovery uses existing provider job metadata to call `reattach` and `result`; it refuses to redispatch a prompt when the persisted provider job does not match the prompt digest.
|
|
34
34
|
|
|
@@ -354,6 +354,17 @@ worktree, first confirm dependencies were linked, installed, or provisioned by
|
|
|
354
354
|
|
|
355
355
|
## The Oracle Workflow
|
|
356
356
|
|
|
357
|
+
### Oracle consultation loop
|
|
358
|
+
|
|
359
|
+
For plan, design, or architecture advice that asks to ask, consult, discuss with, or come to agreement with `oracle`, start with one forked oracle run. Read its result. If it challenges the direction or leaves a material tradeoff, resume that same completed child once with a focused follow-up, then synthesize the parent decision. `resume` returns a new run id, but continues the same oracle session and inherited context. Do not force a second round for an explicit one-shot request, a trivial question, or a fully settled first answer.
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
const first = await runs.run("oracle-consult", { agent: "oracle", task: "Review this plan and identify the strongest unresolved tradeoff." });
|
|
363
|
+
const final = await runs.run("oracle-consult-follow-up", { resume: first.runId, task: "Address this focused question, then state the best recommendation: ..." });
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
The parent remains the final decision-maker. Oracle advice does not approve a direction or start implementation.
|
|
367
|
+
|
|
357
368
|
The intended oracle loop is:
|
|
358
369
|
1. the main agent forks to `oracle`
|
|
359
370
|
2. `oracle` reviews direction, drift, assumptions, and risks
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Multi-Lane Orchestration
|
|
2
|
+
|
|
3
|
+
Use this reference when several independent tasks need coordinated workers, worktrees, or repositories. It defines lane ownership; use the other pi-subagents references for run controls, prompts, and mission details. The parent remains the final decision-maker.
|
|
4
|
+
|
|
5
|
+
## Lane board and authority
|
|
6
|
+
|
|
7
|
+
Before multiple mutation-capable lanes start, record this board in the parent context:
|
|
8
|
+
|
|
9
|
+
`Lane | repo/cwd | exact decision | claimed files or contract | isolation path | authority | next gate | handoff | why independent`
|
|
10
|
+
|
|
11
|
+
Record the isolation path before the first mutation. Do not split one source seam or decision into duplicate lanes. Make overlapping work one lane with one source of truth.
|
|
12
|
+
|
|
13
|
+
For every lane, record the delivery target, allowed actions, required validation, and review rigor. For cross-repository work, name the shared contract and which repository changes first. A blocked decision is a lane state: record the owner, options, recommended default, and evidence needed to continue.
|
|
14
|
+
|
|
15
|
+
## Partitioned runs
|
|
16
|
+
|
|
17
|
+
Use one writer per repo/cwd or worktree. Mutation lanes need distinct isolation paths and explicit `cwd` values. Set `worktree: true` when a run needs managed worktree isolation within one repository. Read-only runs can share a checkout only when they cannot change state or create generated files.
|
|
18
|
+
|
|
19
|
+
For Pi extension repositories, keep lane worktrees outside auto-discovered extension directories such as `~/.pi/agent/extensions`. A stale extension worktree there can auto-load duplicate tools and shortcuts. Remove or move it only after its handoff is durable, the worktree is clean, and no run owns it.
|
|
20
|
+
|
|
21
|
+
Partition fanout by repository, source seam, decision, or review angle. Each run needs a stable key, lane-specific task, and durable output path. Do not launch prompts that differ only by item name or broad file glob.
|
|
22
|
+
|
|
23
|
+
Use one async `workflowScript` for a coordinated wave. Use `runs.all` for independent lanes and `runs.run` for dependent lane stages. Give cross-repository runs explicit `cwd` values and lane-qualified outputs. Use `outputMode: "file-only"` when a report must survive the run or feed a later stage.
|
|
24
|
+
|
|
25
|
+
## Keep independent work moving
|
|
26
|
+
|
|
27
|
+
While one lane waits, run safe independent preparation, validation, or fresh read-only review lanes. Do not block the parent just because a run is active. If no safe lane remains, record the blocker and the event that will reopen work.
|
|
28
|
+
|
|
29
|
+
An ordinary coordinated workflow has one mission. Use its durable state, artifacts, run records, and receipts for recovery. Treat a receipt as evidence, not as authority or acceptance.
|
|
30
|
+
|
|
31
|
+
After a writer produces a candidate, run the required fresh-context, read-only reviewer. The reviewer inspects the exact worktree and returns evidence-backed findings. The parent decides which findings are in scope and whether the lane is ready. Send accepted fixes to that lane's sole writer, then rerun only the affected gate.
|
|
32
|
+
|
|
33
|
+
## Handoff, cleanup, and recovery
|
|
34
|
+
|
|
35
|
+
Use stable lane-qualified paths for reports and review output. A handoff states the lane status, repository and worktree, changed files, validation, open decisions, next action, and artifact or receipt paths.
|
|
36
|
+
|
|
37
|
+
Keep a worktree until its handoff is durable, no run owns it, and no later gate needs it. Clean up only inside the recorded authority boundary. If a run stops or needs attention, preserve its worktree and artifacts, record the last known state and recovery owner, then resume that run or create one replacement lane from the handoff. Do not start another writer while worktree ownership is uncertain.
|
|
38
|
+
|
|
39
|
+
Before completion, inspect the board. Every lane must be terminal or blocked with a named next action. Confirm one writer per repo/cwd or worktree, required validation, required fresh read-only review, and a durable handoff. The parent reports outcomes, evidence, residual risks, and the next decision.
|
|
@@ -204,6 +204,8 @@ A strong subagent prompt usually includes:
|
|
|
204
204
|
- **Output**: the expected summary shape, artifact path, or finding format. Use repo-qualified durable output paths for cross-codebase waves.
|
|
205
205
|
- **Stop rules**: when to ask via `intercom` or `contact_supervisor`, when to stop after enough evidence, and when not to keep searching.
|
|
206
206
|
|
|
207
|
+
Give each role useful discovery anchors. Name source roots, filenames, symbols, types, methods, and paths for scouts. Give workers context files, plans, task paths, and named source seams before asking them to search. Give reviewers changed files, contracts, and any exhaustive-verification target. Tell oracle whether current source behavior, product/policy documents, plans, or inherited decisions are the evidence that matters.
|
|
208
|
+
|
|
207
209
|
Avoid carrying over old prompt habits that over-specify every step. Use `must`, `always`, and `never` for real invariants; for judgment calls, give decision rules. For example, tell a reviewer to inspect the staged diff directly and report only evidence-backed findings, rather than prescribing every file or command. Tell a researcher the retrieval budget: start with broad targeted searches, fetch only the strongest sources, search again only when a required fact is missing, then stop.
|
|
208
210
|
|
|
209
211
|
For implementation handoffs, name the approved scope and success criteria more clearly than the process. Good prompts say what to change, what not to change, where the evidence lives, how to validate, and when to escalate. They should not ask the child to create another subagent plan or continue the parent conversation.
|
|
@@ -27,7 +27,7 @@ import { discoverAvailableSkills, resolveSkills } from "./skills.ts";
|
|
|
27
27
|
import {
|
|
28
28
|
buildProactiveSkillSubagentRecommendationLines,
|
|
29
29
|
} from "./proactive-skills.ts";
|
|
30
|
-
import { parseFrontmatter } from "./frontmatter.ts";
|
|
30
|
+
import { parseFrontmatter, parseFrontmatterList } from "./frontmatter.ts";
|
|
31
31
|
import { toModelInfo } from "../shared/model-info.ts";
|
|
32
32
|
import { resolveSubagentModelOverride, type ParentModel } from "../runs/shared/model-fallback.ts";
|
|
33
33
|
import { validateToolBudgetConfig } from "../runs/shared/tool-budget.ts";
|
|
@@ -196,6 +196,18 @@ function skillsWarning(cwd: string, agent: Pick<AgentConfig, "skills" | "skillPa
|
|
|
196
196
|
return missing.length ? `Warning: skills not found: ${missing.join(", ")}.` : undefined;
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
function withDeclaredExtensionPaths(config: AgentConfig, filePath: string): AgentConfig {
|
|
200
|
+
const { frontmatter } = parseFrontmatter(fs.readFileSync(filePath, "utf-8"));
|
|
201
|
+
const { extensions: _extensions, subagentOnlyExtensions: _subagentOnlyExtensions, ...withoutResolvedExtensions } = config;
|
|
202
|
+
return {
|
|
203
|
+
...withoutResolvedExtensions,
|
|
204
|
+
...(frontmatter.extensions !== undefined ? { extensions: parseFrontmatterList(frontmatter.extensions) } : {}),
|
|
205
|
+
...(frontmatter.subagentOnlyExtensions !== undefined
|
|
206
|
+
? { subagentOnlyExtensions: parseFrontmatterList(frontmatter.subagentOnlyExtensions) }
|
|
207
|
+
: {}),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
199
211
|
export function editableAgentConfig(agent: AgentConfig): AgentConfig {
|
|
200
212
|
const { extensions: _extensions, ...withoutExtensions } = agent;
|
|
201
213
|
const base = agent.override?.base;
|
|
@@ -220,13 +232,13 @@ export function editableAgentConfig(agent: AgentConfig): AgentConfig {
|
|
|
220
232
|
...editable
|
|
221
233
|
} = withoutExtensions;
|
|
222
234
|
if (!base) {
|
|
223
|
-
return {
|
|
235
|
+
return withDeclaredExtensionPaths({
|
|
224
236
|
...withoutExtensions,
|
|
225
237
|
...(agent.extensionsFromDefault ? {} : agent.extensions !== undefined ? { extensions: [...agent.extensions] } : {}),
|
|
226
|
-
};
|
|
238
|
+
}, agent.filePath);
|
|
227
239
|
}
|
|
228
240
|
|
|
229
|
-
return {
|
|
241
|
+
return withDeclaredExtensionPaths({
|
|
230
242
|
...editable,
|
|
231
243
|
...(base.model !== undefined ? { model: base.model } : {}),
|
|
232
244
|
...(base.fallbackModels !== undefined ? { fallbackModels: [...base.fallbackModels] } : {}),
|
|
@@ -245,7 +257,7 @@ export function editableAgentConfig(agent: AgentConfig): AgentConfig {
|
|
|
245
257
|
...(base.extensions !== undefined ? { extensions: [...base.extensions] } : {}),
|
|
246
258
|
...(base.subagentOnlyExtensions !== undefined ? { subagentOnlyExtensions: [...base.subagentOnlyExtensions] } : {}),
|
|
247
259
|
...(base.completionGuard !== undefined ? { completionGuard: base.completionGuard } : {}),
|
|
248
|
-
};
|
|
260
|
+
}, agent.filePath);
|
|
249
261
|
}
|
|
250
262
|
|
|
251
263
|
function readAgentFrontmatterFields(filePath: string): Set<string> {
|
|
@@ -806,7 +818,13 @@ export function handleUpdate(params: ManagementParams, ctx: ManagementContext):
|
|
|
806
818
|
if ("content" in targetOrError) return targetOrError;
|
|
807
819
|
const target = targetOrError;
|
|
808
820
|
if (target.source !== "user" && target.source !== "project") return result(`Cannot update ${target.source} agent '${target.name}'. Eject it to user or project scope first.`, true);
|
|
809
|
-
|
|
821
|
+
let updated: AgentConfig;
|
|
822
|
+
try {
|
|
823
|
+
updated = editableAgentConfig(target);
|
|
824
|
+
} catch (error) {
|
|
825
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
826
|
+
return result(`Could not reread agent definition ${target.filePath} before updating '${target.name}': ${message}`, true);
|
|
827
|
+
}
|
|
810
828
|
const oldName = target.name;
|
|
811
829
|
if (hasKey(cfg, "name") && (typeof cfg.name !== "string" || !cfg.name.trim())) return result("config.name must be a non-empty string when provided.", true);
|
|
812
830
|
if (hasKey(cfg, "description") && (typeof cfg.description !== "string" || !cfg.description.trim())) return result("config.description must be a non-empty string when provided.", true);
|
package/src/agents/agents.ts
CHANGED
|
@@ -38,7 +38,6 @@ export interface AgentMemoryConfig {
|
|
|
38
38
|
export const BUILTIN_AGENT_NAMES = [
|
|
39
39
|
"advisor",
|
|
40
40
|
"delegate",
|
|
41
|
-
"gpt-pro",
|
|
42
41
|
"oracle",
|
|
43
42
|
"researcher",
|
|
44
43
|
"reviewer",
|
|
@@ -384,7 +383,7 @@ function getGlobalNpmRoot(): string | null {
|
|
|
384
383
|
}
|
|
385
384
|
|
|
386
385
|
try {
|
|
387
|
-
cachedGlobalNpmRoot = fs.realpathSync(execSync("npm root -g", { encoding: "utf-8", timeout: 5000 }).trim());
|
|
386
|
+
cachedGlobalNpmRoot = fs.realpathSync(execSync("npm root -g", { encoding: "utf-8", timeout: 5000, windowsHide: true }).trim());
|
|
388
387
|
return cachedGlobalNpmRoot;
|
|
389
388
|
} catch {
|
|
390
389
|
cachedGlobalNpmRoot = "";
|
|
@@ -1573,22 +1572,44 @@ function parseAgentAcceptanceFrontmatter(raw: string | undefined, agentName: str
|
|
|
1573
1572
|
return parsed as AcceptanceInput;
|
|
1574
1573
|
}
|
|
1575
1574
|
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1575
|
+
interface AgentDefinitionFile {
|
|
1576
|
+
filePath: string;
|
|
1577
|
+
content: string;
|
|
1578
|
+
}
|
|
1579
1579
|
|
|
1580
|
+
function readAgentDefinitionFiles(dir: string): AgentDefinitionFile[] {
|
|
1581
|
+
const files: AgentDefinitionFile[] = [];
|
|
1580
1582
|
for (const filePath of listFilesRecursive(dir, (fileName) => fileName.endsWith(".md") && !fileName.endsWith(".chain.md"))) {
|
|
1581
1583
|
if (isLegacyAgentSkillPath(dir, filePath)) {
|
|
1582
1584
|
continue;
|
|
1583
1585
|
}
|
|
1584
1586
|
|
|
1585
|
-
let content: string;
|
|
1586
1587
|
try {
|
|
1587
|
-
content
|
|
1588
|
+
files.push({ filePath, content: fs.readFileSync(filePath, "utf-8") });
|
|
1588
1589
|
} catch {
|
|
1589
1590
|
continue;
|
|
1590
1591
|
}
|
|
1592
|
+
}
|
|
1593
|
+
return files;
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
function resolveAgentRelativeExtensionPaths(paths: string[] | undefined, agentFilePath: string): string[] | undefined {
|
|
1597
|
+
if (paths === undefined) return undefined;
|
|
1598
|
+
const baseDir = path.dirname(agentFilePath);
|
|
1599
|
+
return paths.map((entry) => {
|
|
1600
|
+
const trimmed = entry.trim();
|
|
1601
|
+
if (trimmed === "." || trimmed === ".." || trimmed.startsWith("./") || trimmed.startsWith("../")) {
|
|
1602
|
+
return path.resolve(baseDir, trimmed);
|
|
1603
|
+
}
|
|
1604
|
+
return entry;
|
|
1605
|
+
});
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: AgentSource, discoveryPriority?: number): { agents: AgentConfig[]; diagnostics: AgentDiscoveryDiagnostic[] } {
|
|
1609
|
+
const agents: AgentConfig[] = [];
|
|
1610
|
+
const diagnostics: AgentDiscoveryDiagnostic[] = [];
|
|
1591
1611
|
|
|
1612
|
+
for (const { filePath, content } of files) {
|
|
1592
1613
|
let name: string | undefined;
|
|
1593
1614
|
let runtimeName: string | undefined;
|
|
1594
1615
|
let packageSpecified = false;
|
|
@@ -1675,8 +1696,8 @@ function loadAgentsFromDir(dir: string, source: AgentSource, discoveryPriority?:
|
|
|
1675
1696
|
else throw new Error(`Agent '${localName}' has invalid acceptanceRole frontmatter; expected 'read-only' or 'writer'.`);
|
|
1676
1697
|
}
|
|
1677
1698
|
|
|
1678
|
-
const extensions = parseFrontmatterList(frontmatter.extensions);
|
|
1679
|
-
const subagentOnlyExtensions = parseFrontmatterList(frontmatter.subagentOnlyExtensions);
|
|
1699
|
+
const extensions = resolveAgentRelativeExtensionPaths(parseFrontmatterList(frontmatter.extensions), filePath);
|
|
1700
|
+
const subagentOnlyExtensions = resolveAgentRelativeExtensionPaths(parseFrontmatterList(frontmatter.subagentOnlyExtensions), filePath);
|
|
1680
1701
|
|
|
1681
1702
|
const extraFields: Record<string, string> = {};
|
|
1682
1703
|
for (const [key, value] of Object.entries(frontmatter)) {
|
|
@@ -1760,6 +1781,10 @@ function loadAgentsFromDir(dir: string, source: AgentSource, discoveryPriority?:
|
|
|
1760
1781
|
return { agents, diagnostics };
|
|
1761
1782
|
}
|
|
1762
1783
|
|
|
1784
|
+
function loadAgentsFromDir(dir: string, source: AgentSource, discoveryPriority?: number): { agents: AgentConfig[]; diagnostics: AgentDiscoveryDiagnostic[] } {
|
|
1785
|
+
return loadAgentsFromDefinitionFiles(readAgentDefinitionFiles(dir), source, discoveryPriority);
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1763
1788
|
function loadChainsFromDir(dir: string, source: AgentSource): { chains: ChainConfig[]; diagnostics: ChainDiscoveryDiagnostic[] } {
|
|
1764
1789
|
const chains = new Map<string, ChainConfig>();
|
|
1765
1790
|
const diagnostics: ChainDiscoveryDiagnostic[] = [];
|
|
@@ -1821,6 +1846,7 @@ function resolveNearestProjectChainDirs(cwd: string): { readDirs: string[]; pref
|
|
|
1821
1846
|
};
|
|
1822
1847
|
}
|
|
1823
1848
|
const BUILTIN_AGENTS_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "agents");
|
|
1849
|
+
const BUILTIN_AGENT_DEFINITION_FILES = readAgentDefinitionFiles(BUILTIN_AGENTS_DIR);
|
|
1824
1850
|
|
|
1825
1851
|
export const EXTRA_AGENT_DIRS_ENV = "PI_SUBAGENT_EXTRA_AGENT_DIRS";
|
|
1826
1852
|
|
|
@@ -1855,7 +1881,7 @@ export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryRe
|
|
|
1855
1881
|
includeProject: scope !== "user",
|
|
1856
1882
|
});
|
|
1857
1883
|
|
|
1858
|
-
const builtinLoaded =
|
|
1884
|
+
const builtinLoaded = loadAgentsFromDefinitionFiles(BUILTIN_AGENT_DEFINITION_FILES, "builtin");
|
|
1859
1885
|
const builtinAgents = applyBuiltinOverrides(
|
|
1860
1886
|
applySubagentDefaults(builtinLoaded.agents, defaultModel, defaultThinking, defaultExtensions),
|
|
1861
1887
|
userSettings,
|
|
@@ -1937,7 +1963,7 @@ export function discoverAgentsAll(cwd: string): {
|
|
|
1937
1963
|
const defaultExtensions = resolveSubagentDefaultExtensions(userSettings, projectSettings, projectSettingsPath);
|
|
1938
1964
|
const packageSubagentPaths = collectPackageSubagentPaths(cwd);
|
|
1939
1965
|
|
|
1940
|
-
const builtinLoaded =
|
|
1966
|
+
const builtinLoaded = loadAgentsFromDefinitionFiles(BUILTIN_AGENT_DEFINITION_FILES, "builtin");
|
|
1941
1967
|
const builtin = applyBuiltinOverrides(
|
|
1942
1968
|
applySubagentDefaults(builtinLoaded.agents, defaultModel, defaultThinking, defaultExtensions),
|
|
1943
1969
|
userSettings,
|
package/src/agents/skills.ts
CHANGED
|
@@ -142,7 +142,7 @@ function getGlobalNpmRoot(): string | null {
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
try {
|
|
145
|
-
cachedGlobalNpmRoot = fs.realpathSync(execSync("npm root -g", { encoding: "utf-8", timeout: 5000 }).trim());
|
|
145
|
+
cachedGlobalNpmRoot = fs.realpathSync(execSync("npm root -g", { encoding: "utf-8", timeout: 5000, windowsHide: true }).trim());
|
|
146
146
|
return cachedGlobalNpmRoot;
|
|
147
147
|
} catch {
|
|
148
148
|
// Global npm root is optional in constrained environments.
|
|
@@ -146,8 +146,9 @@ export function validateExternalJobResult(provider: string, value: unknown, fiel
|
|
|
146
146
|
function validateProvider(value: unknown): ExternalJobProvider {
|
|
147
147
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("External-job provider must be an object.");
|
|
148
148
|
const provider = value as Record<string, unknown>;
|
|
149
|
-
|
|
150
|
-
|
|
149
|
+
// Tolerate extra provider fields (for example kind, wakeChannels, or future
|
|
150
|
+
// operations) so one evolving provider cannot poison registry reads for all
|
|
151
|
+
// providers. Payload validation stays strict.
|
|
151
152
|
const name = validateString(provider.name, "External-job provider name", MAX_PROVIDER_NAME_LENGTH);
|
|
152
153
|
for (const op of ["start", "status", "result", "reattach"] as const) {
|
|
153
154
|
if (typeof provider[op] !== "function") throw new Error(`External-job provider '${name}' must expose ${op}().`);
|