pi-subagents 0.65.0 → 0.66.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/CHANGELOG.md +87 -0
- package/README.md +1 -1
- package/agents/researcher.md +23 -13
- package/docs/agents.md +17 -3
- package/docs/configuration.md +34 -0
- package/docs/extension-api.md +94 -0
- package/docs/models.md +58 -1
- package/docs/observability.md +42 -2
- package/docs/tool-reference.md +11 -5
- package/docs/workflows.md +22 -7
- package/package.json +4 -1
- package/runner-server-preload.mjs +13 -0
- package/skills/pi-subagents/SKILL.md +2 -1
- package/skills/pi-subagents/references/execution-controls.md +19 -2
- package/skills/pi-subagents/references/management-authoring-rpc.md +2 -1
- package/skills/pi-subagents/references/multi-lane-orchestration.md +2 -0
- package/src/agents/advertised-agent-prompt.ts +63 -0
- package/src/agents/agent-management.ts +14 -1
- package/src/agents/agent-serializer.ts +2 -0
- package/src/agents/agents.ts +8 -0
- package/src/api/preflight.ts +5 -1
- package/src/api/shared-types.ts +1 -1
- package/src/api/workflow-resources.ts +6 -0
- package/src/extension/config.ts +4 -2
- package/src/extension/index.ts +71 -4
- package/src/extension/public-execution.ts +0 -1
- package/src/extension/rpc.ts +4 -21
- package/src/extension/schemas.ts +9 -7
- package/src/extension/tool-description.ts +10 -5
- package/src/integrations/pi-web-session-liveness.ts +73 -0
- package/src/intercom/native-supervisor-channel.ts +102 -88
- package/src/intercom/supervisor-ui.ts +3 -2
- package/src/missions/workflow-state.ts +37 -16
- package/src/runs/background/active-async-capacity.ts +18 -18
- package/src/runs/background/async-execution.ts +8 -1
- package/src/runs/background/async-job-tracker.ts +35 -3
- package/src/runs/background/async-resume.ts +3 -1
- package/src/runs/background/async-retention.ts +9 -0
- package/src/runs/background/async-status-snapshot.ts +10 -12
- package/src/runs/background/async-status.ts +17 -9
- package/src/runs/background/auto-drain.ts +40 -29
- package/src/runs/background/chain-root-attachment.ts +8 -0
- package/src/runs/background/control-channel.ts +78 -44
- package/src/runs/background/notify.ts +88 -12
- package/src/runs/background/owned-process-tree.ts +6 -6
- package/src/runs/background/process-terminal.ts +23 -23
- package/src/runs/background/retained-nested-route-tracker.ts +96 -0
- package/src/runs/background/run-child-session.ts +62 -33
- package/src/runs/background/run-status.ts +75 -5
- package/src/runs/background/runner-aliases.ts +46 -8
- package/src/runs/background/runner-child-launch.ts +86 -0
- package/src/runs/background/stale-run-reconciler.ts +3 -1
- package/src/runs/background/subagent-runner.ts +430 -208
- package/src/runs/background/subagent-wait.ts +3 -0
- package/src/runs/background/wait-completions.ts +4 -0
- package/src/runs/foreground/async-steering-action.ts +19 -0
- package/src/runs/foreground/execution.ts +116 -26
- package/src/runs/foreground/foreground-history.ts +3 -1
- package/src/runs/foreground/prompt-audit.ts +9 -5
- package/src/runs/foreground/subagent-executor.ts +531 -227
- package/src/runs/foreground/workflow-detach-reconcile.ts +8 -5
- package/src/runs/foreground/workflow-foreground-steering.ts +56 -2
- package/src/runs/shared/acceptance.ts +16 -3
- package/src/runs/shared/agent-contract.ts +1 -1
- package/src/runs/shared/async-status-projection.ts +47 -47
- package/src/runs/shared/child-hooks.ts +151 -2
- package/src/runs/shared/child-launch.ts +18 -13
- package/src/runs/shared/child-session.ts +55 -24
- package/src/runs/shared/child-tool-plan.ts +2 -2
- package/src/runs/shared/completion-evidence.ts +2 -2
- package/src/runs/shared/completion-guard.ts +1 -0
- package/src/runs/shared/host-step-status.ts +11 -11
- package/src/runs/shared/llm-intent-arbiter.ts +30 -20
- package/src/runs/shared/model-exclusions.ts +2 -1
- package/src/runs/shared/model-fallback.ts +41 -8
- package/src/runs/shared/nested-events.ts +8 -8
- package/src/runs/shared/orca-progress-tabs.ts +6 -0
- package/src/runs/shared/parallel-handoff.ts +57 -12
- package/src/runs/shared/parallel-utils.ts +3 -2
- package/src/runs/shared/readonly-drain-observation.ts +42 -0
- package/src/runs/shared/readonly-model-continuation.ts +69 -0
- package/src/runs/shared/readonly-session-evidence.ts +307 -0
- package/src/runs/shared/run-fanout-budget.ts +8 -8
- package/src/runs/shared/runtime-acknowledged-extensions.ts +3 -3
- package/src/runs/shared/subagent-prompt-runtime.ts +13 -3
- package/src/runs/shared/worktree-cleanup-plan.ts +6 -3
- package/src/runs/shared/worktree-setup-command.ts +190 -0
- package/src/runs/shared/worktree.ts +403 -210
- package/src/shared/model-response-aliases.ts +13 -0
- package/src/shared/types.ts +89 -60
- package/src/shared/utils.ts +10 -2
- package/src/shared/watch-strategy.ts +2 -0
- package/src/shared/workflow-child-permit.ts +18 -13
- package/src/tui/fleet-status.ts +1 -1
- package/src/tui/fleet.ts +11 -5
- package/src/tui/render.ts +44 -15
- package/src/workflows/chat-progress.ts +3 -3
- package/src/workflows/scripted-workflow.ts +70 -16
- package/src/workflows/workflow-checklist.ts +15 -18
- package/src/workflows/workflow-child-summary.ts +57 -8
- package/src/workflows/workflow-preflight.ts +19 -19
- package/src/workflows/workflow-receipt.ts +3 -3
- package/src/workflows/workflow-resources.ts +96 -21
- package/src/workflows/workflow-settlement.ts +3 -0
package/docs/tool-reference.md
CHANGED
|
@@ -6,7 +6,7 @@ Parameters and actions for the `subagent` tool. These are what the LLM passes wh
|
|
|
6
6
|
|
|
7
7
|
Chaining is code-driven through `workflowScript`. Use `await runs.run(...)` for sequential steps and `await runs.all([{ key, agent, task }, ...])` for ordinary parallel fanout. `runs.all` resolves to an ordered array, not a key map, so use indexes, destructuring, or `.map(...)`, not `results.<key>`. Do not read `.output` from an unawaited `runs.run` launch. Stored `runs.run` promises are only for the advanced rolling fanout pattern under [Workflow steering](#workflow-steering), where every promise is later observed with direct `await`, `Promise.race`, or `Promise.all`. Legacy top-level `chain`, `tasks`, and `parallel` inputs are not supported. Helper functions must be plain functions or explicit Promise chains. Nested `async function` helpers, async arrows, and async methods are rejected so child-launch tracking stays portable across Node and Bun. For permission-sensitive host calls, use an extension-owned named resource such as `{ workflow: "run-ci", args: { command: "npm test" } }`; raw public `workflowScript`/`workflowScriptPath` inputs have unknown resource provenance and cannot call `runs.host`. A resolved resource may internally use `runs.host(key, { kind: "command", command, timeoutMs, output?, role?, provider? })` within its authority ceiling; there is no per-step `cwd`, and commands and relative output paths use the workflow `cwd`. Set `cwd` on the outer `subagent({...})` request instead, or put a trusted directory change in the command (for example, `cd /path/to/worktree && npm test`).
|
|
8
8
|
|
|
9
|
-
Use `{ action: "validate", workflowScript }` to check statically decidable syntax and structure without launching children. It returns `{ ok, errors }` and fails the tool call when `ok` is false. Dynamic keys and values remain
|
|
9
|
+
Use `{ action: "validate", workflowScript }` to check statically decidable syntax and structure without launching children. It returns `{ ok, errors }` and fails the tool call when `ok` is false. Literal child `baseRef` values are checked against the runtime ref policy. Dynamic keys and values remain subject to runtime checks; static validation does not guess them.
|
|
10
10
|
|
|
11
11
|
Use `workflowScriptPath` instead of `workflowScript` to load the same JavaScript statement body from a file. The two fields are mutually exclusive. Relative paths resolve against the request `cwd`, and absolute paths pass through. The host reads the file before validation, scheduling, or sandbox execution. The workflow sandbox still has no filesystem access. Missing, unreadable, and empty files fail as file input errors.
|
|
12
12
|
|
|
@@ -108,7 +108,7 @@ The complete plain-JSON inventory is validated before the first launch (maximum
|
|
|
108
108
|
| `async` | boolean | default-on | Background execution. Workflows default to background. `async:false` blocks the parent until completion and runs the child as a session inside the parent Pi process; such foreground children never load the parent's ambient extensions, so agents that need MCP tools (`mcpDirectTools`, or MCP tools from an ambient adapter such as pi-mcp-adapter) or models from a provider extension must run as background children, which load them inside the detached runner process. |
|
|
109
109
|
| `chatProgress` | `auto \| off \| live-card` | `auto` | WorkflowScript chat projection. `auto` renders a live in-chat card only for watched foreground workflows in the same Git repository, including managed worktrees; it is off otherwise. Explicit `live-card` requires `async:false` and the same Git repository. Async workflows have no inline live card, so omit `chatProgress` or use `auto`/`off`; use `async:false` only when the parent must block. |
|
|
110
110
|
| `isolation` | `none \| worktree` | - | Workflow child isolation. `none` runs in the shared cwd and does not need Git. `worktree` requires a managed Git worktree. Do not combine it with a contradictory `worktree` value. |
|
|
111
|
-
| `baseRef` | string | `HEAD` |
|
|
111
|
+
| `baseRef` | string | `HEAD` | `HEAD` or a supported named ref such as `refs/heads/release`, `refs/tags/v1`, or `origin/main`. Full 40/64-character commit IDs and revision expressions such as `HEAD~1` are unsupported. The ref must resolve to a commit at worktree allocation; omitted values default to `HEAD` resolved at that time. Source-checkout cleanliness is still checked. For workflowScript, set it on the outer request as a default or on an individual `runs.run`/`runs.all` child to override it. |
|
|
112
112
|
| `timeoutMs` / `maxRuntimeMs` | number | config `timeoutMs`, else 30 min foreground / single-agent async | Optional run-level max runtime in milliseconds. When omitted, the global [`timeoutMs`](configuration.md#timeoutms) config provides the default; absent that, foreground and plain single-agent async runs fall back to 30 minutes, while composite async runs (chains, parallel tasks, workflows) stay unbounded at the top level. Expiration of this run-level deadline is terminal and does not trigger `fallbackModels`. |
|
|
113
113
|
| `toolTimeoutMs` | number | fast-tool default | Optional positive hard per-tool-call deadline in milliseconds. Precedence: call value → agent frontmatter → config → `PI_SUBAGENT_TOOL_TIMEOUT_MS`. The timer starts on `tool_execution_start`, clears on the matching `tool_execution_end`, and terminates the run with `timedOut: true` if the tool remains open. When omitted, known-fast built-in tools get a five-minute default; long-running tools get attention notices but no hard default. It never extends the run deadline; `contact_supervisor`, `intercom`, and `bg_wait` are exempt. |
|
|
114
114
|
| `toolBudget` | object | none | Optional child tool-call budget `{ soft?, hard, block? }`. At `soft` the child is nudged to finalize. After `hard`, configured tools are blocked; `block` defaults to `read`, `grep`, `find`, and `ls`, while `"*"` blocks every tool call. Final assistant text is never blocked. |
|
|
@@ -120,7 +120,7 @@ The complete plain-JSON inventory is validated before the first launch (maximum
|
|
|
120
120
|
| `share` | boolean | false | Upload session export to GitHub Gist. |
|
|
121
121
|
| `sessionDir` | string | derived | Override session log directory. |
|
|
122
122
|
| `acceptance` | string/object/false | inferred | Configure evidence gates. See [Acceptance gates](#acceptance-gates). |
|
|
123
|
-
| `gate` | string | - | One host-run verification command, shorthand for `acceptance: { level: "verified", verify: [{ id: "gate", command }] }`. Also valid on individual `runs.run`/`runs.all` items.
|
|
123
|
+
| `gate` | string | - | One host-run verification command, shorthand for `acceptance: { level: "verified", verify: [{ id: "gate", command }] }`. Also valid on individual `runs.run`/`runs.all` items. Rejects `acceptance` except `false` (treated as omitted), and rejects retained `resume`. |
|
|
124
124
|
|
|
125
125
|
### Budget guidance for writers
|
|
126
126
|
|
|
@@ -298,6 +298,12 @@ The manifest stores one of these fail-closed eligibility states: `active` (an ow
|
|
|
298
298
|
|
|
299
299
|
## Status and control actions
|
|
300
300
|
|
|
301
|
+
### Failed lane recovery and execution-mode boundaries
|
|
302
|
+
|
|
303
|
+
A failure in the subagent workflow, child launch, prompt runtime, extension loading, or child tooling setup is a lane infrastructure blocker, not permission to silently change execution mode. Stop and report the exact failure, run/status, and repo/cwd/worktree/branch/ref state. Before a same-protocol retry or asking the owner, verify the worktree is clean or capture the partial diff. Retry or fix the `subagent` path only through a clear same-protocol action.
|
|
304
|
+
|
|
305
|
+
For backlog lanes and other subagent-governed workflows, external/foreground/CLI fallback requires explicit owner approval. Do not silently switch to `interactive_shell`, `pi -ne`, Codex/Claude/Cursor CLI, a foreground agent, or another external mode. `interactive_shell` remains valid when the user explicitly requests visible foreground/CLI work or the task is outside the governed subagent protocol. Pi core may print a generic `pi -ne` extension-load hint; that out-of-repo hint is not protocol-approved fallback. Configured native model/provider fallback remains governed by its own contract.
|
|
306
|
+
|
|
301
307
|
```ts
|
|
302
308
|
subagent({ action: "status" })
|
|
303
309
|
subagent({ action: "status", view: "fleet" })
|
|
@@ -352,7 +358,7 @@ subagent({ action: "doctor" })
|
|
|
352
358
|
|
|
353
359
|
`steer` waits up to three seconds for a correlated child-Pi input acceptance and returns a request id with `delivered`, `scheduled`, `pending`, `partial`, `recovered`, or `failed` plus per-child states. The receipt also has `deliveryStatus: "delivered" | "queued"`. Delivery means Pi accepted the user message, not model compliance. A pending indexed child returns `scheduled`.
|
|
354
360
|
|
|
355
|
-
The optional `mode` is `steer` by default and keeps the current interrupt behavior. `follow_up` waits for the next turn boundary. `auto`
|
|
361
|
+
The optional `mode` is `steer` by default and keeps the current interrupt behavior. `follow_up` waits for the next turn boundary. `auto` uses the same native steer delivery path as `steer`, without automatic pause-and-revive recovery after a missed acknowledgment. The retained revival-brief queue holds 20 messages and returns a clear error when full; this is not a live follow-up queue bound. Terminal details report queued messages without recorded delivery. A live follow-up acknowledgment reports queue acceptance, not delivery, and has no later correlated queued-to-delivered receipt. A `follow_up` sent to a completed retained workflow child becomes the first brief for its next `resume`; it does not revive the child by itself.
|
|
356
362
|
|
|
357
363
|
Only a top-level single run may interrupt after the acknowledgment deadline and recover after a further 15-second pause/revival bound; durable multi-child and nested runs never auto-interrupt. Recovery launches a replacement only after the source is confirmed paused, a valid persisted session exists, and deadline, turn, and tool budgets remain. It preserves the original child contract and remaining limits; otherwise the source stays paused with an explicit failure. Late acceptance is recorded but cannot cancel committed recovery.
|
|
358
364
|
|
|
@@ -385,7 +391,7 @@ When one host-run command is the entire verification contract, use the `gate` sh
|
|
|
385
391
|
{ workflowScript: `return runs.run("impl", { agent: "worker", task: "Implement the fix", gate: "npm test" })` }
|
|
386
392
|
```
|
|
387
393
|
|
|
388
|
-
`gate` normalizes to verified acceptance with that single command, so the runtime executes it on the host and records the result as evidence. Verification results are memoized per tracked workspace state and effective environment, so an unchanged tree does not rerun the same command. Use explicit `acceptance.verify` when you need multiple commands, timeouts, or custom criteria. `gate`
|
|
394
|
+
`gate` normalizes to verified acceptance with that single command, so the runtime executes it on the host and records the result as evidence. Verification results are memoized per tracked workspace state and effective environment, so an unchanged tree does not rerun the same command. Use explicit `acceptance.verify` when you need multiple commands, timeouts, or custom criteria. `gate` rejects `acceptance` except `false` (treated as omitted), and rejects retained `resume` items. With `worktree: true`, the gate runs inside the child's managed worktree.
|
|
389
395
|
|
|
390
396
|
### Levels and inference
|
|
391
397
|
|
package/docs/workflows.md
CHANGED
|
@@ -19,6 +19,14 @@ Child-safety boundaries are enforced at runtime:
|
|
|
19
19
|
- By default, children do not register the `subagent` tool and receive boundary instructions that they are not the parent orchestrator and must not propose or run subagents.
|
|
20
20
|
- The explicit exception is an agent whose resolved builtin `tools` includes `subagent`; that child gets a child-safe `subagent` tool for the fanout work the parent assigned, still bounded by `maxSubagentDepth`.
|
|
21
21
|
|
|
22
|
+
### Failed lane recovery and execution-mode boundaries
|
|
23
|
+
|
|
24
|
+
A failure in the subagent workflow, child launch, prompt runtime, extension loading, or child tooling setup is a lane infrastructure blocker. It is not permission to silently retry through `interactive_shell`, `pi -ne`, Codex/Claude/Cursor CLI, a foreground agent, or another external execution mode.
|
|
25
|
+
|
|
26
|
+
Stop and report the exact failure, run/status, and repository/cwd/worktree/branch/ref state. Before a same-protocol retry or asking the owner, verify the worktree is clean or capture the partial diff. Retry or fix the `subagent` path only through a clear same-protocol action. For backlog lanes and other subagent-governed workflows, external/foreground/CLI fallback requires explicit owner approval. `interactive_shell` remains valid when the user explicitly requests visible foreground/CLI work or the task is outside the governed subagent protocol.
|
|
27
|
+
|
|
28
|
+
Pi core may print a generic `pi -ne` extension-load hint; that out-of-repo hint is not protocol-approved fallback. Configured native model/provider fallback remains governed by its own contract and does not authorize an execution-mode switch.
|
|
29
|
+
|
|
22
30
|
## Prompt shortcuts
|
|
23
31
|
|
|
24
32
|
The package includes reusable prompt templates for common workflows. You do not need them, but they are handy when you want the same shape every time:
|
|
@@ -88,10 +96,11 @@ subagent({
|
|
|
88
96
|
- `toolBudget` becomes the default for each child unless that child supplies a narrower value.
|
|
89
97
|
- `usageBudget` accounts for reported usage across completed workflow children. Once exhausted, it rejects later child launches but does not stop children that are already running.
|
|
90
98
|
- Budget and timeout stops return a structured `terminalOutcome` with `state: "partial"` and reason `budget_exhausted` or `timeout`. Workflow receipts keep settled child evidence for recovery.
|
|
99
|
+
- After an async workflow receipt is successfully published, `workflowReceiptPath` exposes its exact path in wait completion details, completion notifications, and exact status/debug details. Text responses also identify the receipt. Pending runs and failed receipt publications omit the reference; older status records are not backfilled. The reference records publication, not a guarantee against later retention cleanup. Raw result files retain `workflowReceipt: { path, receipt }`.
|
|
91
100
|
|
|
92
101
|
These controls are opt-in. Avoid tight hard budgets for mutation-capable workers unless the workflow has an explicit checkpoint and handoff path.
|
|
93
102
|
|
|
94
|
-
The result is `{ ok, errors }`. Invalid scripts return a tool error and include line and column data when available. Validation checks syntax, portable nested-async rules, literal `runs.run` and `runs.all` keys, duplicate literal keys in one `runs.all` group, direct keyed access to a known `runs.all` result, and statically clear non-JSON boundary values. Dynamic keys and other runtime-only values are accepted without a warning. Validation does not discover agents, launch children, or create run artifacts.
|
|
103
|
+
The result is `{ ok, errors }`. Invalid scripts return a tool error and include line and column data when available. Validation checks syntax, portable nested-async rules, literal `runs.run` and `runs.all` keys and child `baseRef` values, duplicate literal keys in one `runs.all` group, direct keyed access to a known `runs.all` result, and statically clear non-JSON boundary values. Dynamic keys and other runtime-only values are accepted without a warning. Validation does not discover agents, launch children, or create run artifacts.
|
|
95
104
|
|
|
96
105
|
```js
|
|
97
106
|
subagent({ workflowScript: `
|
|
@@ -347,6 +356,8 @@ known, or for explicit emergency hotfix lanes.
|
|
|
347
356
|
|
|
348
357
|
For watched same-repo workflows, pass `async:false` only when the parent must block until completion. That blocking mode also shows the live in-chat workflow card. `chatProgress` can force `off` or `live-card` when the automatic policy is not what you want. Blocking workflows default to a 30-minute timeout; async workflows have no default timeout. See the [tool reference](tool-reference.md) for the full parameter list.
|
|
349
358
|
|
|
359
|
+
Synchronous workflows publish trace and `emit(...)` updates through the tool update callback regardless of `chatProgress`, including RPC/headless and cross-repository runs. These updates include `details.workflow` and `details.workflowChildren`; `chatProgress: "off"` disables the live card, not transport progress. Running foreground child rows additionally expose bounded `activity` (current tool, timing, and counters), plus resolved model/thinking when available, keyed by `childId`. Activity-only updates coalesce over 100 ms; lifecycle updates remain immediate. Activity clears when children settle, and is not persisted for async workflows. Tool names are limited to 256 UTF-8 bytes and each activity object is below 2 KiB (including JSON escaping); arguments and transcripts are not forwarded.
|
|
360
|
+
|
|
350
361
|
The legacy `/chain`, `/parallel`, and `/run-chain` commands are not registered.
|
|
351
362
|
|
|
352
363
|
## Direct commands
|
|
@@ -369,10 +380,12 @@ Each child uses the existing worktree lifecycle: it branches from clean HEAD, jo
|
|
|
369
380
|
|
|
370
381
|
A top-level `{ workflowScript, worktree: true }` makes isolation the default for every workflow child. An individual child can override that default with `worktree: false`. Keep one writer when parallel writes are not intentionally isolated.
|
|
371
382
|
|
|
372
|
-
Use `baseRef` to branch managed worktrees from a named
|
|
383
|
+
Use `baseRef` to branch managed worktrees from `HEAD` or a supported named ref such as `refs/heads/release`, `refs/tags/v1`, or `origin/main`. Full 40/64-character commit IDs and revision expressions such as `HEAD~1` are unsupported. For example, `{ workflowScript, worktree: true, baseRef: "refs/heads/release" }` applies the release ref to children unless a child supplies its own `baseRef`. If omitted, the default `HEAD` is resolved at worktree allocation, not when the script is validated or a schedule is created. The source checkout must still be clean, and the ref must resolve to a commit before any worktree is allocated.
|
|
373
384
|
|
|
374
385
|
Configure the worktree provider, native path layout, base directory, and setup hook in [configuration.md](configuration.md).
|
|
375
386
|
|
|
387
|
+
Setup waits remain nonblocking and cancellable. Normal cleanup, including detached foreground finalization, waits for the same in-process setup turn rather than retaining worktrees merely because another setup is active. This is not a cross-process lock. Hooks must follow the [finite setup contract](configuration.md#worktreesetuphook).
|
|
388
|
+
|
|
376
389
|
### Lane metadata lifecycle
|
|
377
390
|
|
|
378
391
|
Workflow children may declare a bounded `lane` object (`version`, `key`, optional
|
|
@@ -394,11 +407,13 @@ Older runs without lane metadata remain readable and retain their existing
|
|
|
394
407
|
handoff/cleanup behavior. Missing lane, receipt, or handoff metadata is
|
|
395
408
|
unknown—not eligible for destructive cleanup.
|
|
396
409
|
|
|
397
|
-
|
|
398
|
-
display-only status
|
|
399
|
-
|
|
400
|
-
ownership
|
|
401
|
-
|
|
410
|
+
Managed setup records actual allocation attempts in the handoff; only validated
|
|
411
|
+
allocations become cleanup tasks and display-only status paths/branches. On
|
|
412
|
+
cancellation or failure with unknown settlement, it retains actual/attempted
|
|
413
|
+
ownership evidence and artifacts for manual reconciliation, blocking further
|
|
414
|
+
unsafe setup and cleanup in that process. An allocator interrupted before
|
|
415
|
+
reporting its path may leave branch-only diagnostics, never an invented path.
|
|
416
|
+
Inspect the handoff before reconciliation; cleanup still requires fresh checks.
|
|
402
417
|
|
|
403
418
|
## Supervisor coordination (child asks parent)
|
|
404
419
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-subagents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.66.0",
|
|
4
4
|
"description": "Pi extension for single-agent delegation and scripted multi-agent workflows",
|
|
5
5
|
"author": "Nico Bailon",
|
|
6
6
|
"license": "MIT",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"./agents": "./src/api/agents.ts",
|
|
14
14
|
"./delegation": "./src/api/delegation.ts",
|
|
15
15
|
"./capability-ceiling": "./src/api/capability-ceiling.ts",
|
|
16
|
+
"./workflow-resources": "./src/api/workflow-resources.ts",
|
|
16
17
|
"./preflight": "./src/api/preflight.ts",
|
|
17
18
|
"./control-channel": "./src/api/control-channel.ts",
|
|
18
19
|
"./intercom-bridge": "./src/api/intercom-bridge.ts",
|
|
@@ -90,9 +91,11 @@
|
|
|
90
91
|
}
|
|
91
92
|
},
|
|
92
93
|
"dependencies": {
|
|
94
|
+
"@earendil-works/pi-server": "0.85.0",
|
|
93
95
|
"acorn": "8.18.0",
|
|
94
96
|
"jiti": "2.7.0",
|
|
95
97
|
"typebox": "1.1.38",
|
|
98
|
+
"undici": "8.10.0",
|
|
96
99
|
"yaml": "2.8.3"
|
|
97
100
|
},
|
|
98
101
|
"devDependencies": {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Only loaded when the parent supplies Pi 0.85.0's missing server exports.
|
|
2
|
+
import { registerHooks } from "node:module";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
|
|
5
|
+
const aliases = JSON.parse(process.env.JITI_ALIAS);
|
|
6
|
+
registerHooks({
|
|
7
|
+
resolve(specifier, context, nextResolve) {
|
|
8
|
+
if (specifier === "@earendil-works/pi-server" || specifier === "@earendil-works/pi-server/unix") {
|
|
9
|
+
return nextResolve(pathToFileURL(aliases[specifier]).href, context);
|
|
10
|
+
}
|
|
11
|
+
return nextResolve(specifier, context);
|
|
12
|
+
},
|
|
13
|
+
});
|
|
@@ -91,7 +91,8 @@ review.
|
|
|
91
91
|
- Exact model names are deployment policy. Put them in user/project settings or profiles, not package guidance.
|
|
92
92
|
- Give every child a compact meta-prompt checklist: objective; repo/cwd/ref; authority/edit boundary; relevant files/contracts and constraints; success/acceptance criteria; validation; expected output/report; and stop/ask conditions. See `references/prompting-and-roles.md`.
|
|
93
93
|
- For mutation work, use an isolated lane/worktree when isolation, overlap, or concurrent juggling matters; keep one writer per cwd/worktree. See `references/multi-lane-orchestration.md` for lane mechanics.
|
|
94
|
-
- Keep long/high-output validation out of chat: prefer `interactive_shell` dispatch/background monitors, bounded logs, or subagent-owned reports; return a concise summary plus report path unless same-turn output is required.
|
|
94
|
+
- Keep long/high-output validation out of chat: prefer `interactive_shell` dispatch/background monitors, bounded logs, or subagent-owned reports; return a concise summary plus report path unless same-turn output is required. Do not use `interactive_shell` as an implicit fallback for a failed `subagent` lane; see `references/execution-controls.md`.
|
|
95
|
+
- Treat subagent workflow, child launch, prompt runtime, extension load, and child tooling setup failures as lane infrastructure blockers. Stop, report the exact failure and run/worktree state, verify a clean worktree or capture a partial diff, and use only a clear same-protocol retry or an owner-approved execution-mode fallback.
|
|
95
96
|
- For cross-codebase work, record the repo, explicit `cwd`, authority boundary, and expected output before launch.
|
|
96
97
|
- Make parallel prompts distinct by source seam, evidence, and decision. Do not clone prompts with only item numbers swapped.
|
|
97
98
|
- Prefer fresh-context review/validation fanout, then synthesize and apply fixes in the parent.
|
|
@@ -246,15 +246,19 @@ Use diagnostics when setup or child startup looks wrong:
|
|
|
246
246
|
subagent({ action: "doctor" })
|
|
247
247
|
```
|
|
248
248
|
|
|
249
|
+
### Failed lane recovery and execution-mode fallback
|
|
250
|
+
|
|
251
|
+
A failure in the subagent workflow, child launch, prompt runtime, extension loading, or child tooling setup is a lane infrastructure blocker, not permission to silently change execution mode. Stop and report the exact failure, run/status, and repo/cwd/worktree/branch/ref state. Retry or fix the `subagent` path only through a clear same-protocol retry; before retrying or asking the owner, verify the worktree is clean or capture the partial diff. For backlog lanes and other subagent-governed workflows, switching to `interactive_shell`, `pi -ne`, Codex/Claude/Cursor CLI, a foreground agent, or another external mode requires explicit owner approval. Pi core may print a generic `pi -ne` extension-load hint; that hint is outside this package and is not protocol-approved fallback. This execution-mode boundary does not prohibit configured native model/provider fallback.
|
|
252
|
+
|
|
249
253
|
### External terminal work
|
|
250
254
|
|
|
251
|
-
Use native `subagent` runs for unattended implementation, review, and gate work that needs managed isolation, durable artifacts, and process controls. Use `interactive_shell` for visible terminal work, alternate CLIs, trust prompts,
|
|
255
|
+
Use native `subagent` runs for unattended implementation, review, and gate work that needs managed isolation, durable artifacts, and process controls. Use `interactive_shell` for visible terminal work, alternate CLIs, trust prompts, or recovery only when the user explicitly requests that mode or the task is outside the governed subagent protocol; it is not an implicit replacement for a failed `subagent` lane.
|
|
252
256
|
|
|
253
257
|
A cooperating terminal runtime can register read-only external records through `pi-subagents/external-runs`. Records include the source, session, state, optional report path, and completion reason. They are observations only: pi-subagents does not start, stop, steer, or otherwise own the foreign process. Run unattended raw terminal agents in an explicit isolated cwd or worktree; do not use a live project checkout as disposable review space.
|
|
254
258
|
|
|
255
259
|
### Scheduled subagent runs
|
|
256
260
|
|
|
257
|
-
Schedules are durable project records under `.pi/subagents/schedules/`. They are enabled by default; set `{ "scheduledRuns": { "enabled": false } }` in `~/.pi/agent/extensions/subagent/config.json` to disable them. Only schedule explicit work the user asked for.
|
|
261
|
+
Schedules are durable project records under `.pi/subagents/schedules/`. They are enabled by default; set `{ "scheduledRuns": { "enabled": false } }` in `~/.pi/agent/extensions/subagent/config.json` to disable them. Only schedule explicit work the user asked for. To keep schedules outside the project repository, set `{ "scheduledRuns": { "storeRoot": "~/.pi/subagent-schedules" } }` in the same config: `storeRoot` accepts an absolute path or a `~/`-prefixed path, and records land under `<storeRoot>/<sha256(path.resolve(cwd)) first 20 hex>/<scheduleId>/`.
|
|
258
262
|
|
|
259
263
|
```typescript
|
|
260
264
|
// One-shot reviewer
|
|
@@ -323,6 +327,19 @@ subagent({ action: "steer", id: "abc123", message: "Focus on the failing test."
|
|
|
323
327
|
|
|
324
328
|
The action waits up to three seconds for the child Pi session to accept the correlated user input and returns a request id with `delivered`, `scheduled`, `pending`, `partial`, `recovered`, or `failed` plus per-child states. Indexed pending children return `scheduled` immediately. Only a top-level single-child run may automatically interrupt after a missed acknowledgment and recover after confirmed pause within a further 15 seconds. Recovery preserves the original child contract and only its remaining deadline, turn, and tool budgets. If the session is missing, a budget is exhausted, the pause cannot be confirmed, or replacement launch fails, the source remains paused when pausing succeeded and the action returns the exact failure. Chain, parallel, and nested runs never auto-interrupt; inspect their per-child outcomes and handle failures explicitly. A late acknowledgment is recorded and cannot cancel committed recovery.
|
|
325
329
|
|
|
330
|
+
Steering supports three delivery modes via the `mode` parameter (`steer` is the default):
|
|
331
|
+
|
|
332
|
+
- `mode: "steer"` — interrupt the child at the next safe point of its current turn and deliver the message.
|
|
333
|
+
- `mode: "follow_up"` — do not interrupt; queue input through Pi's native follow-up path for the next turn boundary. Eligible completed retained workflow children (single-step runs in state `complete` with a stored session file) receive the message as a revival brief (`queueRevivalBrief`) when they are revived; paused children reject follow-up steering outright. The 20-message queue limit applies to retained revival briefs, not live follow-up input.
|
|
334
|
+
- `mode: "auto"` — same next-safe-point delivery path as `steer`, but without the automatic pause-and-revive recovery after a missed acknowledgment.
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
subagent({ action: "steer", id: "abc123", mode: "follow_up", message: "After this step, also validate the config file." })
|
|
338
|
+
subagent({ action: "steer", id: "abc123", mode: "auto", message: "Switch to the failing test now." })
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
Direct input acceptance returns `delivered`, not proof of model compliance. A live follow-up acknowledgment reports `queued`, meaning Pi accepted it into its follow-up queue, not that it was delivered. The runtime does not provide a later correlated live queued-to-delivered receipt.
|
|
342
|
+
|
|
326
343
|
## Watchdog
|
|
327
344
|
|
|
328
345
|
The subagent watchdog is an **opt-in** adversarial change reviewer. It is not the
|
|
@@ -96,6 +96,7 @@ A minimal agent file looks like this:
|
|
|
96
96
|
name: my-agent
|
|
97
97
|
package: code-analysis
|
|
98
98
|
description: What this agent does
|
|
99
|
+
advertise: true
|
|
99
100
|
aliases: developer, coder
|
|
100
101
|
model: provider/model-id
|
|
101
102
|
thinking: high
|
|
@@ -111,7 +112,7 @@ skillPath: ./skills, ../shared-skills
|
|
|
111
112
|
Your system prompt here.
|
|
112
113
|
```
|
|
113
114
|
|
|
114
|
-
That is only a starting point. Omit `package` for the traditional unqualified runtime name. Common optional fields include:
|
|
115
|
+
That is only a starting point. Omit `package` for the traditional unqualified runtime name. Set `advertise: true` only when the parent should receive this agent's name and description before deciding whether to delegate; advertisement is off by default. Common optional fields include:
|
|
115
116
|
- `defaultProgress`
|
|
116
117
|
- `defaultReads`
|
|
117
118
|
- `output`
|
|
@@ -46,6 +46,8 @@ After a writer produces a candidate, run the required fresh-context, read-only r
|
|
|
46
46
|
|
|
47
47
|
Use stable lane-qualified artifact 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. Copy only the final evidence to memory, a mission record, or a PR/comment, then remove scratch files from the active worktree before closing the lane.
|
|
48
48
|
|
|
49
|
+
For backlog lanes and other subagent-governed workflows, a setup/runtime/tooling failure remains an infrastructure blocker. Preserve the exact failure, run/status, and repository/cwd/worktree/branch/ref state; verify a clean worktree or capture any partial diff before a same-protocol retry or asking the owner. Do not start another writer or switch to an external, foreground, or CLI execution mode without explicit owner approval.
|
|
50
|
+
|
|
49
51
|
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.
|
|
50
52
|
|
|
51
53
|
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.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import type { ResolvedSubagentCapabilityCeiling } from "../runs/shared/capability-ceiling.ts";
|
|
3
|
+
import { isAgentAllowedByCapabilityCeiling } from "../runs/shared/capability-ceiling.ts";
|
|
4
|
+
import type { AgentConfig } from "./agents.ts";
|
|
5
|
+
|
|
6
|
+
const MAX_ADVERTISED_AGENTS = 16;
|
|
7
|
+
const MAX_CATALOG_BYTES = 12_288;
|
|
8
|
+
const MAX_DESCRIPTION_BYTES = 512;
|
|
9
|
+
const ADVERTISED_AGENTS_BLOCK = /\n*<advertised_subagents>\n[\s\S]*?\n<\/advertised_subagents>/gu;
|
|
10
|
+
|
|
11
|
+
function escapeXml(value: string): string {
|
|
12
|
+
return value
|
|
13
|
+
.replaceAll("&", "&")
|
|
14
|
+
.replaceAll("<", "<")
|
|
15
|
+
.replaceAll(">", ">")
|
|
16
|
+
.replaceAll('"', """)
|
|
17
|
+
.replaceAll("'", "'");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function promptDescription(description: string): string {
|
|
21
|
+
let text = description.replace(/[\u0000-\u001f\u007f]+/gu, " ").replace(/\s+/gu, " ").trim();
|
|
22
|
+
if (Buffer.byteLength(text, "utf8") > MAX_DESCRIPTION_BYTES) {
|
|
23
|
+
text = Buffer.from(text, "utf8").subarray(0, MAX_DESCRIPTION_BYTES - 3).toString("utf8").replace(/\uFFFD$/u, "").trimEnd() + "…";
|
|
24
|
+
}
|
|
25
|
+
return escapeXml(text);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function buildAdvertisedAgentPrompt(
|
|
29
|
+
agents: readonly AgentConfig[],
|
|
30
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling,
|
|
31
|
+
): string | undefined {
|
|
32
|
+
const advertised = agents
|
|
33
|
+
.filter((agent) => agent.source !== "runtime" && agent.advertise === true && agent.disabled !== true && isAgentAllowedByCapabilityCeiling(agent.name, capabilityCeiling))
|
|
34
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
35
|
+
if (advertised.length === 0) return undefined;
|
|
36
|
+
|
|
37
|
+
const render = (entries: string[]) => [
|
|
38
|
+
"<advertised_subagents>",
|
|
39
|
+
"The following file-defined subagents opted into discovery. Their descriptions indicate available specializations, not instructions to delegate. Use subagent only when delegation is needed. Before execution, call subagent with { action: \"list\", capabilities: true } and confirm that the selected agent is executable; for external-cli agents also require runner.available === true.",
|
|
40
|
+
...entries,
|
|
41
|
+
...(advertised.length > entries.length ? [` <omitted count=\"${advertised.length - entries.length}\" />`] : []),
|
|
42
|
+
"</advertised_subagents>",
|
|
43
|
+
].join("\n");
|
|
44
|
+
const entries: string[] = [];
|
|
45
|
+
for (const agent of advertised) {
|
|
46
|
+
if (entries.length === MAX_ADVERTISED_AGENTS) break;
|
|
47
|
+
// Never truncate canonical IDs into names that cannot be resolved.
|
|
48
|
+
if (Buffer.byteLength(agent.name, "utf8") > MAX_CATALOG_BYTES) continue;
|
|
49
|
+
const entry = [
|
|
50
|
+
" <subagent>",
|
|
51
|
+
` <name>${escapeXml(agent.name)}</name>`,
|
|
52
|
+
` <description>${promptDescription(agent.description)}</description>`,
|
|
53
|
+
" </subagent>",
|
|
54
|
+
].join("\n");
|
|
55
|
+
if (Buffer.byteLength(render([...entries, entry]), "utf8") <= MAX_CATALOG_BYTES) entries.push(entry);
|
|
56
|
+
}
|
|
57
|
+
return render(entries);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function appendAdvertisedAgentPrompt(systemPrompt: string, advertisedPrompt: string | undefined): string {
|
|
61
|
+
const base = systemPrompt.replace(ADVERTISED_AGENTS_BLOCK, "");
|
|
62
|
+
return advertisedPrompt ? `${base.trimEnd()}\n\n${advertisedPrompt}` : base;
|
|
63
|
+
}
|
|
@@ -42,7 +42,7 @@ import { listExternalJobProviders } from "../api/external-job-provider.ts";
|
|
|
42
42
|
|
|
43
43
|
type ManagementAction = "list" | "get" | "models" | "create" | "update" | "delete" | "eject" | "disable" | "enable" | "reset";
|
|
44
44
|
type ManagementScope = "user" | "project";
|
|
45
|
-
type ManagementContext = Pick<ExtensionContext, "cwd" | "modelRegistry"> & { model?: ExtensionContext["model"]; config?: ExtensionConfig; currentSessionId?: string; runtimeAgentOwner?: RuntimeAgentOwner };
|
|
45
|
+
type ManagementContext = Pick<ExtensionContext, "cwd" | "modelRegistry"> & { model?: ExtensionContext["model"]; config?: ExtensionConfig; currentSessionId?: string; runtimeAgentOwner?: RuntimeAgentOwner; onAgentsChanged?: () => void };
|
|
46
46
|
|
|
47
47
|
interface ManagementParams {
|
|
48
48
|
action?: string;
|
|
@@ -348,6 +348,7 @@ export function preservedAgentFrontmatterFields(agent: AgentConfig, cfg: Record<
|
|
|
348
348
|
if (hasKey(cfg, "name")) changed("name");
|
|
349
349
|
if (hasKey(cfg, "package")) changed("package");
|
|
350
350
|
if (hasKey(cfg, "description")) changed("description");
|
|
351
|
+
if (hasKey(cfg, "advertise")) changed("advertise");
|
|
351
352
|
if (hasKey(cfg, "aliases")) changed("alias", "aliases");
|
|
352
353
|
if (hasKey(cfg, "systemPrompt")) changed("systemPrompt");
|
|
353
354
|
if (hasKey(cfg, "runner")) changed("runner");
|
|
@@ -415,6 +416,11 @@ function parseTools(raw: string): { tools?: string[]; mcpDirectTools?: string[]
|
|
|
415
416
|
}
|
|
416
417
|
|
|
417
418
|
function applyAgentConfig(target: AgentConfig, cfg: Record<string, unknown>): string | undefined {
|
|
419
|
+
if (hasKey(cfg, "advertise")) {
|
|
420
|
+
if (cfg.advertise === "") delete target.advertise;
|
|
421
|
+
else if (typeof cfg.advertise === "boolean") target.advertise = cfg.advertise;
|
|
422
|
+
else return "config.advertise must be a boolean or empty string when provided.";
|
|
423
|
+
}
|
|
418
424
|
if (hasKey(cfg, "aliases")) {
|
|
419
425
|
if (cfg.aliases === false || cfg.aliases === "") delete target.aliases;
|
|
420
426
|
else if (typeof cfg.aliases === "string") {
|
|
@@ -1176,6 +1182,7 @@ export function handleCreate(params: ManagementParams, ctx: ManagementContext):
|
|
|
1176
1182
|
const sw = skillsWarning(ctx.cwd, agent);
|
|
1177
1183
|
if (sw) warnings.push(sw);
|
|
1178
1184
|
fs.writeFileSync(targetPath, serializeAgent(agent), "utf-8");
|
|
1185
|
+
ctx.onAgentsChanged?.();
|
|
1179
1186
|
return result([`Created agent '${runtimeName}' at ${targetPath}.`, ...warnings].join("\n"));
|
|
1180
1187
|
}
|
|
1181
1188
|
|
|
@@ -1241,6 +1248,7 @@ export function handleUpdate(params: ManagementParams, ctx: ManagementContext):
|
|
|
1241
1248
|
updated.filePath = renamed.filePath!;
|
|
1242
1249
|
}
|
|
1243
1250
|
fs.writeFileSync(updated.filePath, serializeAgent(updated, { preserveFrontmatterFields }), "utf-8");
|
|
1251
|
+
ctx.onAgentsChanged?.();
|
|
1244
1252
|
const headline = updated.name === oldName
|
|
1245
1253
|
? `Updated agent '${updated.name}' at ${updated.filePath}.`
|
|
1246
1254
|
: `Updated agent '${oldName}' to '${updated.name}' at ${updated.filePath}.`;
|
|
@@ -1254,6 +1262,7 @@ function handleDelete(params: ManagementParams, ctx: ManagementContext): AgentTo
|
|
|
1254
1262
|
if ("content" in targetOrError) return targetOrError;
|
|
1255
1263
|
const target = targetOrError;
|
|
1256
1264
|
fs.unlinkSync(target.filePath);
|
|
1265
|
+
ctx.onAgentsChanged?.();
|
|
1257
1266
|
return result(`Deleted agent '${target.name}' at ${target.filePath}.`);
|
|
1258
1267
|
}
|
|
1259
1268
|
|
|
@@ -1292,6 +1301,7 @@ function handleEject(params: ManagementParams, ctx: ManagementContext): AgentToo
|
|
|
1292
1301
|
return result(`Failed to read source agent at ${source.filePath}: ${message}`, true);
|
|
1293
1302
|
}
|
|
1294
1303
|
fs.writeFileSync(targetPath, content, "utf-8");
|
|
1304
|
+
ctx.onAgentsChanged?.();
|
|
1295
1305
|
return result(`Ejected agent '${runtimeName}' from ${source.source} to ${scope} scope at ${targetPath}. Edit it there to customize; it shadows the bundled ${source.source} agent of the same name.`);
|
|
1296
1306
|
}
|
|
1297
1307
|
|
|
@@ -1314,6 +1324,7 @@ function handleDisable(params: ManagementParams, ctx: ManagementContext): AgentT
|
|
|
1314
1324
|
const settingsPath = mergeBuiltinAgentOverride(ctx.cwd, runtimeName, scope, { disabled: true });
|
|
1315
1325
|
const after = resolveEffectiveAgent(discoverAgentsAll(ctx.cwd), raw).agent;
|
|
1316
1326
|
if (after?.disabled === true) {
|
|
1327
|
+
ctx.onAgentsChanged?.();
|
|
1317
1328
|
return result(`Disabled agent '${runtimeName}' via ${scope} settings override at ${settingsPath}. It is now hidden from runtime discovery and { action: "list" }.`);
|
|
1318
1329
|
}
|
|
1319
1330
|
return result(`Wrote a disabled override for '${runtimeName}' at ${settingsPath}, but the agent is still enabled. A higher-precedence ${after?.override?.scope ?? "project"} override is likely winning. Try agentScope: '${after?.override?.scope ?? "project"}'.`, true);
|
|
@@ -1338,6 +1349,7 @@ function handleEnable(params: ManagementParams, ctx: ManagementContext): AgentTo
|
|
|
1338
1349
|
const { path: settingsPath, removed } = removeBuiltinAgentOverrideFields(ctx.cwd, runtimeName, scope, ["disabled"]);
|
|
1339
1350
|
const after = resolveEffectiveAgent(discoverAgentsAll(ctx.cwd), raw).agent;
|
|
1340
1351
|
if (after && after.disabled !== true) {
|
|
1352
|
+
if (removed) ctx.onAgentsChanged?.();
|
|
1341
1353
|
if (removed) return result(`Enabled agent '${runtimeName}' (removed disabled override at ${settingsPath}).`);
|
|
1342
1354
|
return result(`Agent '${runtimeName}' is already enabled.`);
|
|
1343
1355
|
}
|
|
@@ -1385,6 +1397,7 @@ function handleReset(params: ManagementParams, ctx: ManagementContext): AgentToo
|
|
|
1385
1397
|
return result(`Agent '${runtimeName}' has no ${scope} customization to reset.${note} It is at its bundled ${bundled.source} default.`);
|
|
1386
1398
|
}
|
|
1387
1399
|
lines.push(`Reset agent '${runtimeName}' to its bundled ${bundled.source} default.`);
|
|
1400
|
+
ctx.onAgentsChanged?.();
|
|
1388
1401
|
return result(lines.join("\n"));
|
|
1389
1402
|
}
|
|
1390
1403
|
|
|
@@ -6,6 +6,7 @@ export const KNOWN_FIELDS = new Set([
|
|
|
6
6
|
"name",
|
|
7
7
|
"package",
|
|
8
8
|
"description",
|
|
9
|
+
"advertise",
|
|
9
10
|
"alias",
|
|
10
11
|
"aliases",
|
|
11
12
|
"tools",
|
|
@@ -62,6 +63,7 @@ export function serializeAgent(config: AgentConfig, options: SerializeAgentOptio
|
|
|
62
63
|
lines.push(`name: ${frontmatterNameForConfig(config)}`);
|
|
63
64
|
if (config.packageName) lines.push(`package: ${config.packageName}`);
|
|
64
65
|
lines.push(`description: ${config.description}`);
|
|
66
|
+
if (config.advertise === true || preserve("advertise")) lines.push(`advertise: ${config.advertise === true ? "true" : "false"}`);
|
|
65
67
|
const aliasesValue = joinComma(config.aliases);
|
|
66
68
|
if (aliasesValue || preserve("alias", "aliases")) lines.push(`aliases: ${aliasesValue ?? ""}`);
|
|
67
69
|
|
package/src/agents/agents.ts
CHANGED
|
@@ -135,6 +135,7 @@ export interface AgentConfig {
|
|
|
135
135
|
packageSourceVersion?: string;
|
|
136
136
|
packageSourceRoot?: string;
|
|
137
137
|
description: string;
|
|
138
|
+
advertise?: boolean;
|
|
138
139
|
aliases?: string[];
|
|
139
140
|
tools?: string[];
|
|
140
141
|
excludeTools?: string[];
|
|
@@ -1981,6 +1982,12 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
|
|
|
1981
1982
|
|
|
1982
1983
|
const runner = parseAgentRunnerFrontmatter(frontmatter.runner, localName);
|
|
1983
1984
|
validateExternalRunnerProfile(frontmatter, localName, runner);
|
|
1985
|
+
let advertise: boolean | undefined;
|
|
1986
|
+
if (frontmatter.advertise !== undefined) {
|
|
1987
|
+
if (frontmatter.advertise === "true") advertise = true;
|
|
1988
|
+
else if (frontmatter.advertise === "false") advertise = false;
|
|
1989
|
+
else throw new Error(`Agent '${localName}' has invalid advertise frontmatter; expected true or false.`);
|
|
1990
|
+
}
|
|
1984
1991
|
const rawTools = parseFrontmatterList(frontmatter.tools);
|
|
1985
1992
|
const parsedTools = splitToolList(rawTools);
|
|
1986
1993
|
const tools = parsedTools.tools ?? [];
|
|
@@ -2105,6 +2112,7 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
|
|
|
2105
2112
|
...(packageSource?.packageVersion ? { packageSourceVersion: packageSource.packageVersion } : {}),
|
|
2106
2113
|
...(packageSource?.packageRoot ? { packageSourceRoot: packageSource.packageRoot } : {}),
|
|
2107
2114
|
description: frontmatter.description,
|
|
2115
|
+
...(advertise !== undefined ? { advertise } : {}),
|
|
2108
2116
|
...(aliases !== undefined ? { aliases } : {}),
|
|
2109
2117
|
...(rawTools !== undefined ? { tools } : {}),
|
|
2110
2118
|
...(excludeTools !== undefined ? { excludeTools } : {}),
|
package/src/api/preflight.ts
CHANGED
|
@@ -73,6 +73,7 @@ export interface SubagentLaunchContractInput {
|
|
|
73
73
|
/** Current parent leaf required before an implicit `defaultContext: fork` stays `fork`. */
|
|
74
74
|
parentLeafId?: string | null;
|
|
75
75
|
sessionRoot?: string;
|
|
76
|
+
/** Caller directory used as a root keyed by the child run id ("preflight" placeholder when runId is omitted). */
|
|
76
77
|
sessionDir?: string;
|
|
77
78
|
runId?: string;
|
|
78
79
|
/** Root run id supplied by a host when projecting nested async lifecycle paths. */
|
|
@@ -380,7 +381,10 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
|
|
|
380
381
|
const artifactsDir = artifactsEnabled ? getArtifactsDir(input.parentSessionFile ?? null, effectiveCwd, input.artifactDir) : undefined;
|
|
381
382
|
const artifactPaths = artifactsDir ? getArtifactPaths(artifactsDir, runId, agent.name, 0) : undefined;
|
|
382
383
|
const outputPath = resolveSingleOutputPath(behavior.output, effectiveCwd, effectiveCwd, artifactsDir ? path.join(artifactsDir, "outputs", runId) : undefined);
|
|
383
|
-
|
|
384
|
+
// An explicit sessionDir is a root keyed by the child run id, matching the
|
|
385
|
+
// sibling sessionRoot derivation; hosts omitting runId get the documented
|
|
386
|
+
// deterministic "preflight" placeholder.
|
|
387
|
+
const sessionRoot = input.sessionDir ? path.join(path.resolve(input.sessionDir), runId) : input.sessionRoot ? path.join(path.resolve(input.sessionRoot), runId) : undefined;
|
|
384
388
|
const sessionDir = sessionRoot ? path.join(sessionRoot, "run-0") : undefined;
|
|
385
389
|
const lifecycleAsyncDir = input.nestedRootRunId
|
|
386
390
|
? path.join(TEMP_ROOT_DIR, "nested-subagent-runs", input.nestedRootRunId, runId)
|
package/src/api/shared-types.ts
CHANGED
package/src/extension/config.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { DEFAULT_MODEL_EXCLUSION_TTL_MS, MAX_MODEL_EXCLUSION_TTL_MS, setDefaultT
|
|
|
10
10
|
import { validatePermissionConfig } from "../runs/shared/permissions.ts";
|
|
11
11
|
import { MAX_ABANDONED_SLOT_RELEASE_AFTER_MS, MIN_ABANDONED_SLOT_RELEASE_AFTER_MS } from "../runs/background/active-async-capacity.ts";
|
|
12
12
|
import { normalizeWorktreeBranchPrefix } from "../runs/shared/worktree.ts";
|
|
13
|
+
import { validateModelResponseAliases } from "../shared/model-response-aliases.ts";
|
|
13
14
|
|
|
14
15
|
const ARTIFACT_DIR_PREFERENCES = new Set<ArtifactDirPreference>(["project", "session", "temp"]);
|
|
15
16
|
const FLEET_KEYBINDING_ACTION_SET = new Set<string>(FLEET_KEYBINDING_ACTIONS);
|
|
@@ -177,6 +178,7 @@ function validateConfig(config: Record<string, unknown>): void {
|
|
|
177
178
|
validateArtifactConfig(config.artifactConfig);
|
|
178
179
|
validateCapacityConfig(config.capacity);
|
|
179
180
|
validateModelExclusionsConfig(config.modelExclusions);
|
|
181
|
+
validateModelResponseAliases(config.modelResponseAliases);
|
|
180
182
|
validateMainWindowRendererConfig(config.mainWindowRenderer);
|
|
181
183
|
validateOrcaProgressTabsConfig(config.orcaProgressTabs);
|
|
182
184
|
}
|
|
@@ -240,12 +242,12 @@ export function loadConfig(): ExtensionConfig {
|
|
|
240
242
|
return readConfigForUpdate(configPath);
|
|
241
243
|
} catch (error) {
|
|
242
244
|
if (error instanceof PrunedForkConfigError) throw error;
|
|
243
|
-
//
|
|
245
|
+
// Explicit route identity and worktree policies must not be silently
|
|
244
246
|
// discarded and replaced by the built-in defaults after validation fails.
|
|
245
247
|
try {
|
|
246
248
|
const raw = JSON.parse(fs.readFileSync(configPath, "utf-8")) as unknown;
|
|
247
249
|
if (raw && typeof raw === "object" && !Array.isArray(raw)
|
|
248
|
-
&& (Object.hasOwn(raw, "worktreeProvider") || Object.hasOwn(raw, "worktreeBranchPrefix"))) throw error;
|
|
250
|
+
&& (Object.hasOwn(raw, "worktreeProvider") || Object.hasOwn(raw, "worktreeBranchPrefix") || Object.hasOwn(raw, "modelResponseAliases"))) throw error;
|
|
249
251
|
} catch (readError) {
|
|
250
252
|
if (readError === error) throw error;
|
|
251
253
|
}
|