pi-subagents 0.58.0 → 0.59.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 +65 -0
- package/docs/agents.md +5 -3
- package/docs/configuration.md +4 -4
- package/docs/extension-api.md +1 -1
- package/docs/models.md +24 -1
- package/docs/observability.md +1 -1
- package/docs/tool-reference.md +89 -4
- package/docs/workflows.md +93 -2
- package/package.json +3 -1
- package/prompts/review-loop.md +2 -2
- package/skills/council-mode/SKILL.md +1 -1
- package/skills/pi-subagents/SKILL.md +1 -1
- package/skills/pi-subagents/references/execution-controls.md +5 -1
- package/skills/pi-subagents/references/management-authoring-rpc.md +1 -2
- package/skills/pi-subagents/references/prompting-and-roles.md +24 -3
- package/src/agents/agent-management.ts +5 -17
- package/src/agents/agent-serializer.ts +4 -2
- package/src/agents/agents.ts +67 -26
- package/src/agents/runtime-agent-registry.ts +9 -16
- package/src/api/background-work.ts +5 -1
- package/src/api/delegation.ts +0 -7
- package/src/api/preflight.ts +6 -9
- package/src/extension/fanout-child.ts +5 -3
- package/src/extension/index.ts +51 -23
- package/src/extension/public-execution.ts +15 -2
- package/src/extension/schemas.ts +35 -10
- package/src/extension/tool-description.ts +18 -3
- package/src/intercom/result-intercom.ts +2 -0
- package/src/profiles/profiles.ts +5 -6
- package/src/runs/background/active-async-capacity.ts +2 -2
- package/src/runs/background/async-execution.ts +50 -35
- package/src/runs/background/async-job-tracker.ts +14 -12
- package/src/runs/background/async-resume.ts +23 -25
- package/src/runs/background/async-status-snapshot.ts +23 -261
- package/src/runs/background/async-status.ts +66 -7
- package/src/runs/background/chain-append.ts +6 -3
- package/src/runs/background/chain-root-attachment.ts +60 -8
- package/src/runs/background/fleet-view.ts +21 -11
- package/src/runs/background/notify.ts +158 -6
- package/src/runs/background/result-files.ts +2 -1
- package/src/runs/background/result-watcher.ts +2 -0
- package/src/runs/background/resume-guidance.ts +1 -1
- package/src/runs/background/retained-children.ts +1 -1
- package/src/runs/background/run-status.ts +18 -8
- package/src/runs/background/scheduled-runs.ts +86 -7
- package/src/runs/background/stale-run-reconciler.ts +10 -4
- package/src/runs/background/steering.ts +4 -14
- package/src/runs/background/subagent-runner.ts +346 -357
- package/src/runs/background/subagent-wait.ts +58 -10
- package/src/runs/background/terminal-run-index.ts +1 -1
- package/src/runs/background/wait-completions.ts +22 -1
- package/src/runs/background/wait-config.ts +23 -9
- package/src/runs/background/wait-tool.ts +9 -2
- package/src/runs/foreground/async-steering-action.ts +2 -2
- package/src/runs/foreground/execution.ts +114 -120
- package/src/runs/foreground/foreground-control.ts +3 -0
- package/src/runs/foreground/foreground-history.ts +1 -0
- package/src/runs/foreground/subagent-executor.ts +478 -239
- package/src/runs/foreground/workflow-detach-reconcile.ts +99 -200
- package/src/runs/shared/abort-recovery.ts +119 -0
- package/src/runs/shared/async-status-projection.ts +463 -0
- package/src/runs/shared/child-identity.ts +19 -4
- package/src/runs/shared/child-launch-plan.ts +151 -0
- package/src/runs/shared/completion-evidence.ts +89 -0
- package/src/runs/shared/completion-guard.ts +1 -1
- package/src/runs/shared/dynamic-fanout.ts +2 -2
- package/src/runs/shared/host-step-status.ts +230 -0
- package/src/runs/shared/lane-metadata.ts +105 -0
- package/src/runs/shared/mcp-config-sources.ts +42 -6
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +93 -143
- package/src/runs/shared/mcp-direct-tool-grant.ts +197 -0
- package/src/runs/shared/model-fallback.ts +9 -2
- package/src/runs/shared/nested-events.ts +6 -2
- package/src/runs/shared/nested-render.ts +7 -3
- package/src/runs/shared/parallel-handoff.ts +419 -7
- package/src/runs/shared/parallel-utils.ts +7 -0
- package/src/runs/shared/pi-args.ts +20 -2
- package/src/runs/shared/single-output.ts +27 -4
- package/src/runs/shared/subagent-prompt-runtime.ts +13 -4
- package/src/runs/shared/worktree-cleanup-plan.ts +847 -0
- package/src/runs/shared/worktree.ts +18 -0
- package/src/shared/child-session-name.ts +46 -0
- package/src/shared/extension-context.ts +24 -0
- package/src/shared/formatters.ts +5 -2
- package/src/shared/launch-contract.ts +1 -1
- package/src/shared/settings.ts +9 -103
- package/src/shared/types.ts +198 -29
- package/src/shared/utils.ts +35 -55
- package/src/slash/delegation-adapters.ts +1 -8
- package/src/slash/delegation-request.ts +0 -4
- package/src/slash/slash-bridge.ts +1 -2
- package/src/slash/slash-commands.ts +369 -90
- package/src/slash/slash-live-state.ts +22 -11
- package/src/tui/fleet-status.ts +125 -74
- package/src/tui/fleet.ts +11 -5
- package/src/tui/render.ts +316 -27
- package/src/watchdog/turn-delta.ts +1 -1
- package/src/workflows/chat-progress.ts +6 -3
- package/src/workflows/host-command.ts +230 -0
- package/src/workflows/scripted-workflow.ts +452 -38
- package/src/workflows/workflow-child-summary.ts +9 -5
- package/src/workflows/workflow-preflight.ts +270 -0
- package/src/workflows/workflow-receipt.ts +43 -4
- package/src/workflows/workflow-settlement.ts +246 -0
- package/src/runs/shared/turn-budget.ts +0 -98
|
@@ -32,7 +32,7 @@ An agent may set `runner.type: external-job` with a non-empty `provider` and opt
|
|
|
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
|
|
|
35
|
-
External job profiles do not support foreground/clarify, steer/resume, Pi models/tools/extensions/skills, tool
|
|
35
|
+
External job profiles do not support foreground/clarify, steer/resume, Pi models/tools/extensions/skills, tool budgets, structured output, native child permissions, fallbacks, or Pi child sessions. Capacity conflicts fail closed and include the blocking provider job id when the provider supplies it.
|
|
36
36
|
|
|
37
37
|
### Single agent
|
|
38
38
|
|
|
@@ -84,6 +84,8 @@ For one host-run verification command, pass `gate: "npm test"` on a `runs.run`/`
|
|
|
84
84
|
|
|
85
85
|
Completed workflow children from this parent session stay addressable as retained children. `subagent({ action: "children.list" })` lists up to the last 10 with run ids and reports each row as `resumable` or `not resumable` with a reason. Resume only rows reported `resumable`. For a retained-child challenge, use `resume` instead of `steer` when the child is complete. If no retained child is resumable, launch a same-role fallback challenge and label it as fallback. A later workflow continues a resumable child with `runs.run(key, { resume: "<run-id>", task: "follow-up" })`. Inside `workflowScript`, awaiting that call waits for the revived child to finish and returns its completed output and new `runId`; top-level `{ action: "resume" }` remains detached. Pass explicit follow-up task text. Assign each returned child result back to the loop variable because every resume can return a new retained `runId`; always resume the latest returned id. `resume` and `agent` are mutually exclusive, the revived child keeps its stored agent/model/tool contract, and `gate` is rejected on retained resume items.
|
|
86
86
|
|
|
87
|
+
Each workflow key identifies one result lane: use a new stable workflow key for every distinct retained resume pass; same-key calls are reused only when launch parameters are identical, and incompatible parameters are rejected.
|
|
88
|
+
|
|
87
89
|
Terminal async workflows also persist `workflow-receipt.json` beside `status.json`. It maps each stable child key to its agent, requested and resolved context when known, latest run id, resumability, output reference, and continuation lineage. A later workflow can resume the latest retained child without copying its run id:
|
|
88
90
|
|
|
89
91
|
```js
|
|
@@ -118,6 +120,8 @@ subagent({
|
|
|
118
120
|
|
|
119
121
|
File-only output mode works for workflowScript child launches. Use relative child output paths for scratch reports so the runtime stores them under the run artifact directory and age-based cleanup can remove them. Use absolute paths only for user-approved durable destinations, such as session memory, a docs folder outside the repo, or a known handoff path. For cross-codebase waves, include the repo slug or lane key in each output path so reports from different repositories cannot collide.
|
|
120
122
|
|
|
123
|
+
The `output` field is the API binding; a filename mentioned in task text is only instruction and does not override runtime routing. When a later workflow step or parent needs a durable file, set `output` on `runs.run`/`runs.all` and return the child’s `outputReference`, `outputPathMapping`, or `artifactPaths`; arbitrary literal strings returned by workflow JavaScript are not rewritten. Omitted child output may use a managed aggregate-derived sibling path.
|
|
124
|
+
|
|
121
125
|
For review fanout where the parent continues a local audit:
|
|
122
126
|
|
|
123
127
|
```typescript
|
|
@@ -18,7 +18,7 @@ subagent({ action: "list" })
|
|
|
18
18
|
subagent({ action: "children.list" })
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
Lists up to the last 10 retained workflow children from this parent session with explicit `resumable` or `not resumable` rows. Resume only rows reported `resumable`. Send a simple follow-up or implementation challenge with `subagent({ action: "resume", id: "<run-id>", message: "..." })`. Continue one inside a workflow with `runs.run(key, { resume: "<run-id>", task: "follow-up" })`;
|
|
21
|
+
Lists up to the last 10 retained workflow children from this parent session with explicit `resumable` or `not resumable` rows. Resume only rows reported `resumable`. Send a simple follow-up or implementation challenge with `subagent({ action: "resume", id: "<run-id>", message: "..." })`. Continue one inside a workflow with `runs.run(key, { resume: "<run-id>", task: "follow-up" })`; each workflow key identifies one result lane, so use a new stable workflow key for every distinct retained resume pass. Same-key calls are reused only when launch parameters are identical, and incompatible parameters are rejected. The revived child keeps its stored agent, model, and tool contract. If no resumable child is listed, start a same-role fallback challenge and label it as fallback. `steer` with `mode: "follow_up"` only queues text for the next `resume` when the child has already completed.
|
|
22
22
|
|
|
23
23
|
### Refinement overlays
|
|
24
24
|
|
|
@@ -126,7 +126,6 @@ That is only a starting point. Omit `package` for the traditional unqualified ru
|
|
|
126
126
|
- `acceptanceRole`
|
|
127
127
|
- `async` — single-agent default for background launch (`true`/`false`); explicit tool-call `async` wins
|
|
128
128
|
- `timeoutMs` — single-agent default run-level max runtime in ms; foreground calls use a 30-minute package default only when neither the call nor agent provides one (tool alias `maxRuntimeMs` is also accepted)
|
|
129
|
-
- `turnBudget` — single-agent default `{ maxTurns, graceTurns? }` JSON object
|
|
130
129
|
|
|
131
130
|
`aliases` is an optional comma-separated or block-list set of alternate names for selecting an agent. Aliases resolve to the canonical `name` for execution, status, persistence, and config. Exact canonical names take precedence over aliases, and alias collisions between distinct canonical agents fail as ambiguous. Management create/update accepts a comma-separated string, string array, or `false`/empty string to clear aliases.
|
|
132
131
|
|
|
@@ -14,7 +14,7 @@ Parent extensions may register a session-scoped, out-of-band ceiling through `pi
|
|
|
14
14
|
- **Recon and planning**: use `scout`, then write a plan when needed
|
|
15
15
|
- **Parallel exploration**: run multiple non-conflicting tasks concurrently
|
|
16
16
|
- **Regular skill specialists**: when discovery shows proactive skill subagent suggestions and the current work is broad enough, launch a small fresh-context fanout that asks one subagent per relevant regularly used skill to apply that skill's perspective to the task
|
|
17
|
-
- **Long-running work**: launch async/background runs and inspect them later. For mutation-capable work, bound the delivery slice and elapsed runtime, then request checkpoints after active tool work returns. Reserve hard
|
|
17
|
+
- **Long-running work**: launch async/background runs and inspect them later. For mutation-capable work, bound the delivery slice and elapsed runtime, then request checkpoints after active tool work returns. Reserve hard tool-call caps for explicitly read-only children.
|
|
18
18
|
- **Subagent control**: watch needs-attention signals and soft-interrupt only when a delegated run is genuinely blocked
|
|
19
19
|
- **Agent authoring**: create, update, or override project agents. Treat saved chain records as legacy inspection or migration inputs, not as a current authoring target.
|
|
20
20
|
|
|
@@ -27,6 +27,7 @@ Agents use the `subagent(...)` tool with `workflowScript` for execution, and `ac
|
|
|
27
27
|
- `/subagents` — interactive admin for inspecting agents and editing model, thinking, or system prompt
|
|
28
28
|
- `/subagents-stop [run-id]` — stop a current-session top-level async run; opens a selector when no id is given
|
|
29
29
|
- `/subagents-detach [run-id]` — detach an active foreground single-subagent run without terminating its child
|
|
30
|
+
- `/subagents-steer <run-id> [--child <child-id>] <message>` — steer a live async run (or one child of it) from non-TUI sessions and RPC hosts
|
|
30
31
|
- `/subagent-cost` — show parent plus child token usage and cost for the session
|
|
31
32
|
- `/subagents-fleet` — open the live fleet inspector with per-child controls; `Ctrl+Alt+F` opens it during an active foreground turn, `↑↓`/`jk` selects children, `PgUp`/`PgDn` scrolls transcript detail, `s` steers the selected live async child, and `D` stops its top-level async run after confirmation
|
|
32
33
|
- `/subagents-watchdog` — inspect or configure the opt-in adversarial change watchdog (model, on/off, recommend-model, check)
|
|
@@ -90,7 +91,7 @@ subagent({
|
|
|
90
91
|
|
|
91
92
|
Use this when the user wants implementation or current diff review to continue until reviewers stop finding fixes worth doing now. Keep the loop in the parent session: one async `worker` implements or fixes, fresh-context `reviewer` agents inspect the actual repo and diff, the parent synthesizes accepted fixes, and one async forked `worker` applies them. The parent can express the sequence up front as an async/background `workflowScript` when the workflow is known, or continue with explicit follow-up workflowScript runs after each async completion. For an initial workflow, pass `async: true` so the main chat is unblocked. Treat an async implementation worker handoff as an intermediate state, not final completion, unless the user explicitly asked for worker-only work, review-only output, or to stop after implementation. Stop when reviewers find no P0 blockers or P1 fixes worth doing now, remaining P2 feedback is optional or deferred, an unapproved product/scope/architecture decision appears, or the max review-round cap is reached. Default to 3 review rounds unless the user sets a different cap. Do not loop for optional polish, and do not let children launch subagents or decide the loop outcome.
|
|
92
93
|
|
|
93
|
-
As a conservative orchestration policy, do not pass
|
|
94
|
+
As a conservative orchestration policy, do not pass a hard `toolBudget` to an implementation worker, fix worker, reviewer with edit authority, or other mutation-capable child. The default tool budget blocks read/search tools rather than mutation tools, but count limits still do not measure delivery safety. Use a narrow task plus an outer elapsed deadline with enough margin, then request a checkpoint after the current tool returns. The checkpoint should report changed files, build/test state, remaining work, and commit or PR state. An elapsed timeout is not a mutation-safe boundary and must not be used as the checkpoint trigger.
|
|
94
95
|
|
|
95
96
|
### Parallel research technique
|
|
96
97
|
|
|
@@ -192,6 +193,26 @@ For one run, use inline config:
|
|
|
192
193
|
|
|
193
194
|
For persistent tweaks, edit `subagents.agentOverrides` in user or project settings. User overrides apply everywhere. Project overrides apply only in that repo and win over user overrides. Use `/subagents-models` or `subagent({ action: "models" })` to inspect the live mapping after settings and overrides load.
|
|
194
195
|
|
|
196
|
+
Provider-scoped entries can layer on top of the default override for the active parent session provider. The provider is selected once from the parent model before child model fallback starts, so fallback attempts cannot switch configuration. Within each settings file, the provider entry wins per field; project settings still win over user settings.
|
|
197
|
+
|
|
198
|
+
```json
|
|
199
|
+
{
|
|
200
|
+
"subagents": {
|
|
201
|
+
"agentOverrides": {
|
|
202
|
+
"worker": { "thinking": "medium" }
|
|
203
|
+
},
|
|
204
|
+
"agentOverridesByProvider": {
|
|
205
|
+
"github-copilot": {
|
|
206
|
+
"worker": { "model": "github-copilot/gpt-5-mini" }
|
|
207
|
+
},
|
|
208
|
+
"openrouter": {
|
|
209
|
+
"worker": { "model": "openrouter/openai/gpt-5-mini" }
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
195
216
|
Model ids do not have to be exact. Separator variations (`claude-haiku-4.5` vs `claude-haiku-4-5`), case (`Claude-Sonnet-4`), and optional trailing date stamps (`claude-haiku-4-5-20251001`) all resolve to the same registry model. Exact `provider/id` wins; a qualified `provider/model` never switches providers. To constrain subagents to a budget or compliance profile, set `subagents.modelScope: { enforce: true, allow: ["anthropic/*", "openai/gpt-5-*"] }` in user or project settings. Out-of-scope models you pass explicitly error and abort; models inherited from frontmatter, `subagents.defaultModel`, agent frontmatter, or the parent session only warn.
|
|
196
217
|
|
|
197
218
|
For model fleets, use the profile commands instead of hand-editing repeated overrides: `/subagents-refresh-provider-models <provider>`, `/subagents-generate-profiles <provider>`, `/subagents-load-profile <name>`, and `/subagents-check-profile <name>`. Profiles live under `~/.pi/agent/profiles/pi-subagents/` and replace only `settings.subagents` when loaded.
|
|
@@ -206,7 +227,7 @@ A strong subagent prompt usually includes:
|
|
|
206
227
|
- **Authority boundary**: whether the child may read, edit, commit, push, comment, close, merge, publish, or release. Omit or forbid actions that are not approved.
|
|
207
228
|
- **Context/evidence**: relevant plan paths, files, diffs, decisions, or user constraints already approved.
|
|
208
229
|
- **Success criteria**: what must be true before the child can finish.
|
|
209
|
-
- **Hard constraints**: true invariants only, such as no edits for review-only tasks, one writer thread, child must not run subagents unless it is
|
|
230
|
+
- **Hard constraints**: true invariants only, such as no edits for review-only tasks, one writer thread, child must not run subagents unless it is explicitly authorized through `tools: subagent` or `allowNestedSubagents: true`, or escalation for unapproved decisions.
|
|
210
231
|
- **Validation**: targeted checks to run, or the next-best check when validation is impossible.
|
|
211
232
|
- **Output**: the expected summary shape, artifact path, or finding format. Use managed artifact paths for scratch reports; reserve repo-qualified absolute paths for durable handoffs that the user approved.
|
|
212
233
|
- **Stop rules**: when to ask via `intercom` or `contact_supervisor`, when to stop after enough evidence, and when not to keep searching.
|
|
@@ -31,7 +31,6 @@ 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";
|
|
34
|
-
import { resolveTurnBudgetConfig } from "../runs/shared/turn-budget.ts";
|
|
35
34
|
import { validateAcceptanceInput } from "../runs/shared/acceptance.ts";
|
|
36
35
|
import { CODE_OWNED_EXTERNAL_CLI_ADAPTER_LABEL, isCodeOwnedExternalCliAdapterId, validateCodeOwnedProfileRunner } from "../runs/shared/external-cli-contract.ts";
|
|
37
36
|
import type { AcceptanceInput, Details, ExtensionConfig, ToolBudgetConfig } from "../shared/types.ts";
|
|
@@ -331,7 +330,6 @@ export function preservedAgentFrontmatterFields(agent: AgentConfig, cfg: Record<
|
|
|
331
330
|
if (hasKey(cfg, "defaultContext")) changed("defaultContext");
|
|
332
331
|
if (hasKey(cfg, "async")) changed("async");
|
|
333
332
|
if (hasKey(cfg, "timeoutMs")) changed("timeoutMs");
|
|
334
|
-
if (hasKey(cfg, "turnBudget")) changed("turnBudget");
|
|
335
333
|
if (hasKey(cfg, "acceptance")) changed("acceptance");
|
|
336
334
|
if (hasKey(cfg, "acceptanceRole")) changed("acceptanceRole");
|
|
337
335
|
if (hasKey(cfg, "output")) changed("output");
|
|
@@ -511,15 +509,6 @@ function applyAgentConfig(target: AgentConfig, cfg: Record<string, unknown>): st
|
|
|
511
509
|
else if (typeof cfg.timeoutMs === "number" && Number.isInteger(cfg.timeoutMs) && cfg.timeoutMs > 0) target.defaultTimeoutMs = cfg.timeoutMs;
|
|
512
510
|
else return "config.timeoutMs must be a positive integer or false when provided.";
|
|
513
511
|
}
|
|
514
|
-
if (hasKey(cfg, "turnBudget")) {
|
|
515
|
-
if (cfg.turnBudget === false || cfg.turnBudget === "") delete target.defaultTurnBudget;
|
|
516
|
-
else {
|
|
517
|
-
const resolved = resolveTurnBudgetConfig(cfg.turnBudget, "config.turnBudget");
|
|
518
|
-
if (resolved.error) return resolved.error;
|
|
519
|
-
if (resolved.turnBudget !== undefined) target.defaultTurnBudget = resolved.turnBudget;
|
|
520
|
-
else delete target.defaultTurnBudget;
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
512
|
if (hasKey(cfg, "acceptance")) {
|
|
524
513
|
if (cfg.acceptance === "") delete target.defaultAcceptance;
|
|
525
514
|
else {
|
|
@@ -724,7 +713,6 @@ function formatAgentDetail(agent: AgentConfig): string {
|
|
|
724
713
|
if (agent.defaultContext) lines.push(`Default context: ${agent.defaultContext}`);
|
|
725
714
|
if (agent.defaultAsync !== undefined) lines.push(`Async: ${agent.defaultAsync ? "true" : "false"}`);
|
|
726
715
|
if (agent.defaultTimeoutMs !== undefined) lines.push(`Timeout: ${agent.defaultTimeoutMs}ms`);
|
|
727
|
-
if (agent.defaultTurnBudget) lines.push(`Turn budget: ${JSON.stringify(agent.defaultTurnBudget)}`);
|
|
728
716
|
if (agent.defaultAcceptance !== undefined) lines.push(`Acceptance: ${typeof agent.defaultAcceptance === "object" ? JSON.stringify(agent.defaultAcceptance) : String(agent.defaultAcceptance)}`);
|
|
729
717
|
if (agent.acceptanceRole) lines.push(`Acceptance role: ${agent.acceptanceRole}`);
|
|
730
718
|
if (agent.source === "builtin") lines.push(`Disabled: ${agent.disabled ? "true" : "false"}`);
|
|
@@ -746,7 +734,7 @@ function formatAgentDetail(agent: AgentConfig): string {
|
|
|
746
734
|
|
|
747
735
|
export function handleList(params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {
|
|
748
736
|
const scope = normalizeListScope(params.agentScope) ?? "both";
|
|
749
|
-
const d = discoverAgentsAll(ctx.cwd);
|
|
737
|
+
const d = discoverAgentsAll(ctx.cwd, ctx.model?.provider);
|
|
750
738
|
let scopedAgents = mergeAgentsForScope(scope, d.user, d.project, d.builtin, d.package);
|
|
751
739
|
if (ctx.runtimeAgentOwner && listRuntimeAgentConfigs(ctx.runtimeAgentOwner).length > 0) {
|
|
752
740
|
const configuredAgents: AgentConfig[] = [
|
|
@@ -807,7 +795,7 @@ function handleModels(params: ManagementParams, ctx: ManagementContext): AgentTo
|
|
|
807
795
|
return result(`Builtin agent '${requestedAgent}' not found. Available: ${BUILTIN_AGENT_NAMES.join(", ")}.`, true);
|
|
808
796
|
}
|
|
809
797
|
|
|
810
|
-
const discovered = discoverAgentsAll(ctx.cwd);
|
|
798
|
+
const discovered = discoverAgentsAll(ctx.cwd, ctx.model?.provider);
|
|
811
799
|
const builtinByName = new Map(discovered.builtin.map((agent) => [agent.name, agent]));
|
|
812
800
|
const resolveBuiltinModelAgent = (name: string): AgentConfig | undefined => builtinByName.get(name) ?? resolveAgentName(name, discovered.builtin).agent;
|
|
813
801
|
const availableModels = ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
@@ -886,7 +874,7 @@ function handleGet(params: ManagementParams, ctx: ManagementContext): AgentToolR
|
|
|
886
874
|
if (!params.agent) return result("Specify 'agent' for get.", true);
|
|
887
875
|
const scope = normalizeListScope(params.agentScope);
|
|
888
876
|
if (!scope) return result("agentScope must be 'user', 'project', or 'both' for get.", true);
|
|
889
|
-
const discovered = discoverAgentsAll(ctx.cwd);
|
|
877
|
+
const discovered = discoverAgentsAll(ctx.cwd, ctx.model?.provider);
|
|
890
878
|
const matches = findAgentsInDiscovery(params.agent, discovered, scope);
|
|
891
879
|
const diagnostics = diagnosticsForScope(discovered.agentDiagnostics, scope);
|
|
892
880
|
const rawName = params.agent.trim();
|
|
@@ -916,7 +904,7 @@ export function handleCreate(params: ManagementParams, ctx: ManagementContext):
|
|
|
916
904
|
const runtimeName = buildRuntimeName(name, parsedPackage.packageName);
|
|
917
905
|
const scopeRaw = cfg.scope ?? "user";
|
|
918
906
|
if (scopeRaw !== "user" && scopeRaw !== "project") return result("config.scope must be 'user' or 'project'.", true);
|
|
919
|
-
const scope = scopeRaw
|
|
907
|
+
const scope = scopeRaw;
|
|
920
908
|
if (hasKey(cfg, "steps")) return result("Durable chain definitions were removed; use workflowScript or /prompt-workflow for repeatable workflows.", true);
|
|
921
909
|
const d = discoverAgentsAll(ctx.cwd);
|
|
922
910
|
const projectConfigDir = getProjectConfigDir(ctx.cwd);
|
|
@@ -1164,7 +1152,7 @@ function handleReset(params: ManagementParams, ctx: ManagementContext): AgentToo
|
|
|
1164
1152
|
}
|
|
1165
1153
|
|
|
1166
1154
|
export function handleManagementAction(action: string, params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {
|
|
1167
|
-
switch (action
|
|
1155
|
+
switch (action) {
|
|
1168
1156
|
case "list": return handleList(params, ctx);
|
|
1169
1157
|
case "get": return handleGet(params, ctx);
|
|
1170
1158
|
case "models": return handleModels(params, ctx);
|
|
@@ -9,6 +9,7 @@ export const KNOWN_FIELDS = new Set([
|
|
|
9
9
|
"alias",
|
|
10
10
|
"aliases",
|
|
11
11
|
"tools",
|
|
12
|
+
"allowNestedSubagents",
|
|
12
13
|
"model",
|
|
13
14
|
"fallbackModels",
|
|
14
15
|
"fast",
|
|
@@ -21,7 +22,6 @@ export const KNOWN_FIELDS = new Set([
|
|
|
21
22
|
"async",
|
|
22
23
|
"timeoutMs",
|
|
23
24
|
"toolTimeoutMs",
|
|
24
|
-
"turnBudget",
|
|
25
25
|
"acceptance",
|
|
26
26
|
"acceptanceRole",
|
|
27
27
|
"skill",
|
|
@@ -70,6 +70,9 @@ export function serializeAgent(config: AgentConfig, options: SerializeAgentOptio
|
|
|
70
70
|
];
|
|
71
71
|
const toolsValue = joinComma(tools);
|
|
72
72
|
if (toolsValue || preserve("tools")) lines.push(`tools: ${toolsValue ?? ""}`);
|
|
73
|
+
if (config.allowNestedSubagents === true || preserve("allowNestedSubagents")) {
|
|
74
|
+
lines.push(`allowNestedSubagents: ${config.allowNestedSubagents === undefined ? "" : config.allowNestedSubagents ? "true" : "false"}`);
|
|
75
|
+
}
|
|
73
76
|
|
|
74
77
|
if (config.model || preserve("model")) lines.push(`model: ${config.model ?? ""}`);
|
|
75
78
|
const fallbackModelsValue = joinComma(config.fallbackModels);
|
|
@@ -94,7 +97,6 @@ export function serializeAgent(config: AgentConfig, options: SerializeAgentOptio
|
|
|
94
97
|
if (config.defaultAsync !== undefined || preserve("async")) lines.push(`async: ${config.defaultAsync === undefined ? "" : config.defaultAsync ? "true" : "false"}`);
|
|
95
98
|
if (config.defaultTimeoutMs !== undefined || preserve("timeoutMs")) lines.push(`timeoutMs: ${config.defaultTimeoutMs ?? ""}`);
|
|
96
99
|
if (config.defaultToolTimeoutMs !== undefined || preserve("toolTimeoutMs")) lines.push(`toolTimeoutMs: ${config.defaultToolTimeoutMs ?? ""}`);
|
|
97
|
-
if (config.defaultTurnBudget || preserve("turnBudget")) lines.push(`turnBudget: ${config.defaultTurnBudget ? JSON.stringify(config.defaultTurnBudget) : ""}`);
|
|
98
100
|
if (config.defaultAcceptance !== undefined || preserve("acceptance")) {
|
|
99
101
|
lines.push(`acceptance: ${config.defaultAcceptance === undefined
|
|
100
102
|
? ""
|
package/src/agents/agents.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { parse as parseYaml } from "yaml";
|
|
|
8
8
|
import * as os from "node:os";
|
|
9
9
|
import * as path from "node:path";
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
11
|
-
import type { AcceptanceInput, AcceptanceRole, AgentRunnerConfig, OutputMode, ToolBudgetConfig
|
|
11
|
+
import type { AcceptanceInput, AcceptanceRole, AgentRunnerConfig, OutputMode, ToolBudgetConfig } from "../shared/types.ts";
|
|
12
12
|
import { CODE_OWNED_EXTERNAL_CLI_ADAPTER_LABEL, isCodeOwnedExternalCliAdapterId, parseExternalCliCapabilityNarrowing, validateCodeOwnedProfileRunner } from "../runs/shared/external-cli-contract.ts";
|
|
13
13
|
import { getAgentDir, getProjectConfigDir } from "../shared/utils.ts";
|
|
14
14
|
import { KNOWN_FIELDS } from "./agent-serializer.ts";
|
|
@@ -20,7 +20,6 @@ import { parseModelScopeConfig, type ModelScopeConfig } from "../runs/shared/mod
|
|
|
20
20
|
export { BUILTIN_AGENT_NAMES } from "./builtin-names.ts";
|
|
21
21
|
export { buildRuntimeName, frontmatterNameForConfig, parsePackageName } from "./identity.ts";
|
|
22
22
|
import { parseMemoryFrontmatter } from "./agent-memory.ts";
|
|
23
|
-
import { resolveTurnBudgetConfig } from "../runs/shared/turn-budget.ts";
|
|
24
23
|
import { validateAcceptanceInput } from "../runs/shared/acceptance.ts";
|
|
25
24
|
import { validatePermissionRules, type PermissionRules } from "../runs/shared/permissions.ts";
|
|
26
25
|
import { parseThinkingLevel, type ThinkingLevel } from "../shared/thinking-ceiling.ts";
|
|
@@ -71,6 +70,7 @@ export interface BuiltinAgentOverrideBase {
|
|
|
71
70
|
skills?: string[];
|
|
72
71
|
skillPath?: string[];
|
|
73
72
|
tools?: string[];
|
|
73
|
+
allowNestedSubagents?: boolean;
|
|
74
74
|
mcpDirectTools?: string[];
|
|
75
75
|
extensions?: string[];
|
|
76
76
|
subagentOnlyExtensions?: string[];
|
|
@@ -99,6 +99,7 @@ interface BuiltinAgentOverrideConfig {
|
|
|
99
99
|
systemPrompt?: string;
|
|
100
100
|
skills?: string[] | false;
|
|
101
101
|
tools?: string[] | false | "inherit";
|
|
102
|
+
allowNestedSubagents?: boolean;
|
|
102
103
|
extensions?: string[] | false;
|
|
103
104
|
subagentOnlyExtensions?: string[] | false;
|
|
104
105
|
mutationTools?: string[] | false;
|
|
@@ -131,6 +132,7 @@ export interface AgentConfig {
|
|
|
131
132
|
description: string;
|
|
132
133
|
aliases?: string[];
|
|
133
134
|
tools?: string[];
|
|
135
|
+
allowNestedSubagents?: boolean;
|
|
134
136
|
mcpDirectTools?: string[];
|
|
135
137
|
model?: string;
|
|
136
138
|
modelProvider?: string;
|
|
@@ -145,7 +147,6 @@ export interface AgentConfig {
|
|
|
145
147
|
defaultAsync?: boolean;
|
|
146
148
|
defaultTimeoutMs?: number;
|
|
147
149
|
defaultToolTimeoutMs?: number;
|
|
148
|
-
defaultTurnBudget?: TurnBudgetConfig;
|
|
149
150
|
defaultAcceptance?: AcceptanceInput;
|
|
150
151
|
acceptanceRole?: AcceptanceRole;
|
|
151
152
|
systemPrompt: string;
|
|
@@ -179,6 +180,7 @@ type ProjectRootResolution = "nearest" | "git-root";
|
|
|
179
180
|
|
|
180
181
|
interface SubagentSettings {
|
|
181
182
|
overrides: Record<string, BuiltinAgentOverrideConfig>;
|
|
183
|
+
providerOverrides: Record<string, Record<string, BuiltinAgentOverrideConfig>>;
|
|
182
184
|
defaultModel?: string;
|
|
183
185
|
defaultProvider?: string;
|
|
184
186
|
defaultThinking?: string;
|
|
@@ -189,7 +191,7 @@ interface SubagentSettings {
|
|
|
189
191
|
modelScope?: ModelScopeConfig;
|
|
190
192
|
}
|
|
191
193
|
|
|
192
|
-
const EMPTY_SUBAGENT_SETTINGS: SubagentSettings = { overrides: {} };
|
|
194
|
+
const EMPTY_SUBAGENT_SETTINGS: SubagentSettings = { overrides: {}, providerOverrides: {} };
|
|
193
195
|
const agentFrontmatterFields = new WeakMap<AgentConfig, Set<string>>();
|
|
194
196
|
|
|
195
197
|
export interface ChainStepConfig {
|
|
@@ -725,6 +727,7 @@ function cloneOverrideBase(agent: AgentConfig): BuiltinAgentOverrideBase {
|
|
|
725
727
|
...(agent.skills ? { skills: [...agent.skills] } : {}),
|
|
726
728
|
...(agent.skillPath ? { skillPath: [...agent.skillPath] } : {}),
|
|
727
729
|
...(agent.tools ? { tools: [...agent.tools] } : {}),
|
|
730
|
+
...(agent.allowNestedSubagents !== undefined ? { allowNestedSubagents: agent.allowNestedSubagents } : {}),
|
|
728
731
|
...(agent.mcpDirectTools ? { mcpDirectTools: [...agent.mcpDirectTools] } : {}),
|
|
729
732
|
...(!agent.extensionsFromDefault && agent.extensions ? { extensions: [...agent.extensions] } : {}),
|
|
730
733
|
...(agent.subagentOnlyExtensions ? { subagentOnlyExtensions: [...agent.subagentOnlyExtensions] } : {}),
|
|
@@ -757,6 +760,7 @@ function cloneOverrideValue(override: BuiltinAgentOverrideConfig): BuiltinAgentO
|
|
|
757
760
|
...(override.systemPrompt !== undefined ? { systemPrompt: override.systemPrompt } : {}),
|
|
758
761
|
...(override.skills !== undefined ? { skills: override.skills === false ? false : [...override.skills] } : {}),
|
|
759
762
|
...(override.tools !== undefined ? { tools: Array.isArray(override.tools) ? [...override.tools] : override.tools } : {}),
|
|
763
|
+
...(override.allowNestedSubagents !== undefined ? { allowNestedSubagents: override.allowNestedSubagents } : {}),
|
|
760
764
|
...(override.extensions !== undefined ? { extensions: override.extensions === false ? false : [...override.extensions] } : {}),
|
|
761
765
|
...(override.subagentOnlyExtensions !== undefined ? { subagentOnlyExtensions: override.subagentOnlyExtensions === false ? false : [...override.subagentOnlyExtensions] } : {}),
|
|
762
766
|
...(override.mutationTools !== undefined ? { mutationTools: override.mutationTools === false ? false : [...override.mutationTools] } : {}),
|
|
@@ -1048,6 +1052,10 @@ function parseBuiltinOverrideEntry(
|
|
|
1048
1052
|
|
|
1049
1053
|
const tools = parseToolsOverride(input.tools, { filePath, name });
|
|
1050
1054
|
if (tools !== undefined) override.tools = tools;
|
|
1055
|
+
if ("allowNestedSubagents" in input) {
|
|
1056
|
+
if (typeof input.allowNestedSubagents === "boolean") override.allowNestedSubagents = input.allowNestedSubagents;
|
|
1057
|
+
else throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'allowNestedSubagents'; expected a boolean.`);
|
|
1058
|
+
}
|
|
1051
1059
|
|
|
1052
1060
|
const extensions = parseOverrideStringArrayOrFalse(input.extensions, { filePath, name, field: "extensions" });
|
|
1053
1061
|
if (extensions !== undefined) override.extensions = extensions;
|
|
@@ -1127,9 +1135,12 @@ function readSubagentSettings(filePath: string | null): SubagentSettings {
|
|
|
1127
1135
|
const modelScope = parseModelScopeConfig(subagentsObject.modelScope, { filePath });
|
|
1128
1136
|
|
|
1129
1137
|
const parsed: Record<string, BuiltinAgentOverrideConfig> = {};
|
|
1138
|
+
const providerOverrides: Record<string, Record<string, BuiltinAgentOverrideConfig>> = {};
|
|
1130
1139
|
const agentOverrides = subagentsObject.agentOverrides;
|
|
1140
|
+
const agentOverridesByProvider = subagentsObject.agentOverridesByProvider;
|
|
1131
1141
|
const parsedSettings: SubagentSettings = {
|
|
1132
1142
|
overrides: parsed,
|
|
1143
|
+
providerOverrides,
|
|
1133
1144
|
...(defaultModel !== undefined ? { defaultModel } : {}),
|
|
1134
1145
|
...(defaultProvider !== undefined ? { defaultProvider } : {}),
|
|
1135
1146
|
...(defaultThinking !== undefined ? { defaultThinking } : {}),
|
|
@@ -1139,16 +1150,42 @@ function readSubagentSettings(filePath: string | null): SubagentSettings {
|
|
|
1139
1150
|
...(disableThinking !== undefined ? { disableThinking } : {}),
|
|
1140
1151
|
...(modelScope !== undefined ? { modelScope } : {}),
|
|
1141
1152
|
};
|
|
1142
|
-
if (
|
|
1143
|
-
|
|
1153
|
+
if (agentOverrides && typeof agentOverrides === "object" && !Array.isArray(agentOverrides)) {
|
|
1154
|
+
for (const [name, value] of Object.entries(agentOverrides)) {
|
|
1155
|
+
const override = parseBuiltinOverrideEntry(name, value, filePath);
|
|
1156
|
+
if (override) parsed[name] = override;
|
|
1157
|
+
}
|
|
1144
1158
|
}
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1159
|
+
if (agentOverridesByProvider !== undefined) {
|
|
1160
|
+
if (!agentOverridesByProvider || typeof agentOverridesByProvider !== "object" || Array.isArray(agentOverridesByProvider)) {
|
|
1161
|
+
throw new Error(`Subagent settings in '${filePath}' have invalid 'agentOverridesByProvider'; expected an object keyed by provider.`);
|
|
1162
|
+
}
|
|
1163
|
+
for (const [provider, value] of Object.entries(agentOverridesByProvider)) {
|
|
1164
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1165
|
+
throw new Error(`Subagent settings in '${filePath}' have invalid 'agentOverridesByProvider.${provider}'; expected an object keyed by agent.`);
|
|
1166
|
+
}
|
|
1167
|
+
const entries: Record<string, BuiltinAgentOverrideConfig> = {};
|
|
1168
|
+
for (const [agentName, agentValue] of Object.entries(value)) {
|
|
1169
|
+
const override = parseBuiltinOverrideEntry(`agentOverridesByProvider.${provider}.${agentName}`, agentValue, filePath);
|
|
1170
|
+
if (override) entries[agentName] = override;
|
|
1171
|
+
}
|
|
1172
|
+
providerOverrides[provider] = entries;
|
|
1173
|
+
}
|
|
1148
1174
|
}
|
|
1149
1175
|
return parsedSettings;
|
|
1150
1176
|
}
|
|
1151
1177
|
|
|
1178
|
+
function selectProviderOverrides(settings: SubagentSettings, provider: string | undefined): SubagentSettings {
|
|
1179
|
+
if (!provider) return settings;
|
|
1180
|
+
const selected = settings.providerOverrides[provider];
|
|
1181
|
+
if (!selected) return settings;
|
|
1182
|
+
const overrides = { ...settings.overrides };
|
|
1183
|
+
for (const [name, override] of Object.entries(selected)) {
|
|
1184
|
+
overrides[name] = { ...overrides[name], ...override };
|
|
1185
|
+
}
|
|
1186
|
+
return { ...settings, overrides };
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1152
1189
|
function resolveSubagentDefaultProvider(
|
|
1153
1190
|
userSettings: SubagentSettings,
|
|
1154
1191
|
projectSettings: SubagentSettings,
|
|
@@ -1301,6 +1338,7 @@ function applyBuiltinOverride(
|
|
|
1301
1338
|
if (override.systemPrompt !== undefined) next.systemPrompt = override.systemPrompt;
|
|
1302
1339
|
if (override.skills !== undefined) { if (override.skills === false) delete next.skills; else next.skills = [...override.skills]; }
|
|
1303
1340
|
if (override.tools !== undefined) applyToolsOverride(next, override.tools);
|
|
1341
|
+
if (override.allowNestedSubagents !== undefined) next.allowNestedSubagents = override.allowNestedSubagents;
|
|
1304
1342
|
if (override.extensions !== undefined) { if (override.extensions === false) delete next.extensions; else next.extensions = [...override.extensions]; }
|
|
1305
1343
|
if (override.subagentOnlyExtensions !== undefined) { if (override.subagentOnlyExtensions === false) delete next.subagentOnlyExtensions; else next.subagentOnlyExtensions = [...override.subagentOnlyExtensions]; }
|
|
1306
1344
|
if (override.mutationTools !== undefined) { if (override.mutationTools === false) delete next.mutationTools; else next.mutationTools = [...override.mutationTools]; }
|
|
@@ -1465,6 +1503,9 @@ function applyCustomAgentOverride(
|
|
|
1465
1503
|
applyToolsOverride(mutable(), override.tools);
|
|
1466
1504
|
anyFilled = true;
|
|
1467
1505
|
}
|
|
1506
|
+
if (override.allowNestedSubagents !== undefined) {
|
|
1507
|
+
fill("allowNestedSubagents", ["allowNestedSubagents"], override.allowNestedSubagents);
|
|
1508
|
+
}
|
|
1468
1509
|
if (override.extensions !== undefined) {
|
|
1469
1510
|
fill("extensions", ["extensions"], override.extensions === false ? undefined : [...override.extensions]);
|
|
1470
1511
|
}
|
|
@@ -1517,7 +1558,7 @@ function applyCustomAgentOverrides(
|
|
|
1517
1558
|
|
|
1518
1559
|
export function buildBuiltinOverrideConfig(
|
|
1519
1560
|
base: BuiltinAgentOverrideBase,
|
|
1520
|
-
draft: Pick<AgentConfig, "model" | "modelProvider" | "fallbackModels" | "fast" | "thinking" | "systemPromptMode" | "inheritProjectContext" | "inheritGlobalContext" | "inheritSkills" | "defaultContext" | "acceptanceRole" | "disabled" | "systemPrompt" | "skills" | "tools" | "mcpDirectTools" | "extensions" | "subagentOnlyExtensions" | "mutationTools" | "completionGuard" | "toolBudget"> & Partial<Pick<AgentConfig, "description" | "output" | "outputMode" | "defaultReads">>,
|
|
1561
|
+
draft: Pick<AgentConfig, "model" | "modelProvider" | "fallbackModels" | "fast" | "thinking" | "systemPromptMode" | "inheritProjectContext" | "inheritGlobalContext" | "inheritSkills" | "defaultContext" | "acceptanceRole" | "disabled" | "systemPrompt" | "skills" | "tools" | "allowNestedSubagents" | "mcpDirectTools" | "extensions" | "subagentOnlyExtensions" | "mutationTools" | "completionGuard" | "toolBudget"> & Partial<Pick<AgentConfig, "description" | "output" | "outputMode" | "defaultReads">>,
|
|
1521
1562
|
): BuiltinAgentOverrideConfig | undefined {
|
|
1522
1563
|
const override: BuiltinAgentOverrideConfig = {};
|
|
1523
1564
|
|
|
@@ -1546,6 +1587,7 @@ export function buildBuiltinOverrideConfig(
|
|
|
1546
1587
|
const baseTools = joinToolList(base);
|
|
1547
1588
|
const draftTools = joinToolList(draft);
|
|
1548
1589
|
if (!arraysEqual(draftTools, baseTools)) override.tools = draftTools ? [...draftTools] : false;
|
|
1590
|
+
if (draft.allowNestedSubagents !== base.allowNestedSubagents) override.allowNestedSubagents = draft.allowNestedSubagents === true;
|
|
1549
1591
|
if (!arraysEqual(draft.extensions, base.extensions)) override.extensions = draft.extensions ? [...draft.extensions] : false;
|
|
1550
1592
|
if (!arraysEqual(draft.subagentOnlyExtensions, base.subagentOnlyExtensions)) {
|
|
1551
1593
|
override.subagentOnlyExtensions = draft.subagentOnlyExtensions ? [...draft.subagentOnlyExtensions] : false;
|
|
@@ -1680,7 +1722,7 @@ export function removeBuiltinAgentOverrideFields(
|
|
|
1680
1722
|
return { path: filePath, removed: true };
|
|
1681
1723
|
}
|
|
1682
1724
|
|
|
1683
|
-
const DISCOVERY_PRUNED_DIR_NAMES = new Set([".git", "node_modules"]);
|
|
1725
|
+
const DISCOVERY_PRUNED_DIR_NAMES = new Set([".git", "node_modules", ".pi", "sync-backups"]);
|
|
1684
1726
|
|
|
1685
1727
|
function isDiscoveryNestedProjectRoot(dir: string): boolean {
|
|
1686
1728
|
return isDirectory(getProjectConfigDir(dir)) || isDirectory(path.join(dir, ".agents"));
|
|
@@ -1894,7 +1936,7 @@ function parseAgentRunnerFrontmatter(raw: string | undefined, agentName: string)
|
|
|
1894
1936
|
|
|
1895
1937
|
function validateExternalRunnerProfile(frontmatter: Record<string, string>, agentName: string, runner: AgentRunnerConfig | undefined): void {
|
|
1896
1938
|
if (runner?.type !== "external-cli" && runner?.type !== "external-job") return;
|
|
1897
|
-
const unsupported = ["tools", "model", "fallbackModels", "thinking", "extensions", "subagentOnlyExtensions", "mutationTools", "maxSubagentDepth", "completionGuard", "skills", "skill", "skillPath", "toolBudget", "permission", "permissions"]
|
|
1939
|
+
const unsupported = ["tools", "allowNestedSubagents", "model", "fallbackModels", "thinking", "extensions", "subagentOnlyExtensions", "mutationTools", "maxSubagentDepth", "completionGuard", "skills", "skill", "skillPath", "toolBudget", "permission", "permissions"]
|
|
1898
1940
|
.filter((field) => frontmatter[field] !== undefined);
|
|
1899
1941
|
if (unsupported.length > 0) {
|
|
1900
1942
|
throw new Error(`Agent '${agentName}' uses runner.type='${runner.type}' and declares unsupported Pi-only fields: ${unsupported.join(", ")}.`);
|
|
@@ -2027,13 +2069,6 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
|
|
|
2027
2069
|
}
|
|
2028
2070
|
defaultToolTimeoutMs = parsed;
|
|
2029
2071
|
}
|
|
2030
|
-
let defaultTurnBudget: TurnBudgetConfig | undefined;
|
|
2031
|
-
if (frontmatter.turnBudget !== undefined && frontmatter.turnBudget.trim()) {
|
|
2032
|
-
const parsed = JSON.parse(frontmatter.turnBudget) as unknown;
|
|
2033
|
-
const resolved = resolveTurnBudgetConfig(parsed, `Agent '${localName}' turnBudget frontmatter`);
|
|
2034
|
-
if (resolved.error) throw new Error(resolved.error);
|
|
2035
|
-
defaultTurnBudget = resolved.turnBudget;
|
|
2036
|
-
}
|
|
2037
2072
|
const defaultAcceptance = parseAgentAcceptanceFrontmatter(frontmatter.acceptance, localName);
|
|
2038
2073
|
let outputMode: OutputMode | undefined;
|
|
2039
2074
|
if (frontmatter.outputMode !== undefined) {
|
|
@@ -2055,6 +2090,12 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
|
|
|
2055
2090
|
else if (frontmatter.fast === "false") fast = false;
|
|
2056
2091
|
else throw new Error(`Agent '${localName}' has invalid fast frontmatter; expected true or false.`);
|
|
2057
2092
|
}
|
|
2093
|
+
let allowNestedSubagents: boolean | undefined;
|
|
2094
|
+
if (frontmatter.allowNestedSubagents !== undefined) {
|
|
2095
|
+
if (frontmatter.allowNestedSubagents === "true") allowNestedSubagents = true;
|
|
2096
|
+
else if (frontmatter.allowNestedSubagents === "false") allowNestedSubagents = false;
|
|
2097
|
+
else throw new Error(`Agent '${localName}' has invalid allowNestedSubagents frontmatter; expected true or false.`);
|
|
2098
|
+
}
|
|
2058
2099
|
|
|
2059
2100
|
const extraFields: Record<string, string> = {};
|
|
2060
2101
|
for (const [key, value] of Object.entries(frontmatter)) {
|
|
@@ -2098,6 +2139,7 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
|
|
|
2098
2139
|
description: frontmatter.description,
|
|
2099
2140
|
...(aliases !== undefined ? { aliases } : {}),
|
|
2100
2141
|
...(rawTools !== undefined ? { tools } : {}),
|
|
2142
|
+
...(allowNestedSubagents !== undefined ? { allowNestedSubagents } : {}),
|
|
2101
2143
|
...(mcpDirectTools.length > 0 ? { mcpDirectTools } : {}),
|
|
2102
2144
|
...(frontmatter.model !== undefined ? { model: frontmatter.model } : {}),
|
|
2103
2145
|
...(fallbackModels?.length ? { fallbackModels } : {}),
|
|
@@ -2111,7 +2153,6 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
|
|
|
2111
2153
|
...(defaultAsync !== undefined ? { defaultAsync } : {}),
|
|
2112
2154
|
...(defaultTimeoutMs !== undefined ? { defaultTimeoutMs } : {}),
|
|
2113
2155
|
...(defaultToolTimeoutMs !== undefined ? { defaultToolTimeoutMs } : {}),
|
|
2114
|
-
...(defaultTurnBudget !== undefined ? { defaultTurnBudget } : {}),
|
|
2115
2156
|
...(defaultAcceptance !== undefined ? { defaultAcceptance } : {}),
|
|
2116
2157
|
...(acceptanceRole !== undefined ? { acceptanceRole } : {}),
|
|
2117
2158
|
systemPrompt: body,
|
|
@@ -2237,15 +2278,15 @@ function extraUserAgentDirs(): string[] {
|
|
|
2237
2278
|
.filter((dir) => dir.length > 0);
|
|
2238
2279
|
}
|
|
2239
2280
|
|
|
2240
|
-
export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryResult {
|
|
2281
|
+
export function discoverAgents(cwd: string, scope: AgentScope, preferredModelProvider?: string): AgentDiscoveryResult {
|
|
2241
2282
|
const effectiveCwd = path.resolve(cwd);
|
|
2242
2283
|
const userDirOld = path.join(getAgentDir(), "agents");
|
|
2243
2284
|
const userDirNew = path.join(os.homedir(), ".agents");
|
|
2244
2285
|
const { readDirs: projectAgentDirs, candidateDirs: projectCandidateDirs, preferredDir: projectAgentsDir } = resolveNearestProjectAgentDirs(effectiveCwd);
|
|
2245
2286
|
const userSettingsPath = getUserAgentSettingsPath();
|
|
2246
2287
|
const projectSettingsPath = getProjectAgentSettingsPath(effectiveCwd);
|
|
2247
|
-
const userSettings = scope === "project" ? EMPTY_SUBAGENT_SETTINGS : readSubagentSettings(userSettingsPath);
|
|
2248
|
-
const projectSettings = scope === "user" ? EMPTY_SUBAGENT_SETTINGS : readSubagentSettings(projectSettingsPath);
|
|
2288
|
+
const userSettings = selectProviderOverrides(scope === "project" ? EMPTY_SUBAGENT_SETTINGS : readSubagentSettings(userSettingsPath), preferredModelProvider);
|
|
2289
|
+
const projectSettings = selectProviderOverrides(scope === "user" ? EMPTY_SUBAGENT_SETTINGS : readSubagentSettings(projectSettingsPath), preferredModelProvider);
|
|
2249
2290
|
const defaultProvider = resolveSubagentDefaultProvider(userSettings, projectSettings, projectSettingsPath);
|
|
2250
2291
|
const defaultModel = resolveSubagentDefaultModel(userSettings, projectSettings, userSettingsPath, projectSettingsPath, defaultProvider);
|
|
2251
2292
|
const defaultThinking = resolveSubagentDefaultThinking(userSettings, projectSettings, projectSettingsPath);
|
|
@@ -2308,7 +2349,7 @@ export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryRe
|
|
|
2308
2349
|
return { agents, agentDiagnostics, projectAgentsDir, cwd: effectiveCwd, scope, directories, ...(modelScope !== undefined ? { modelScope } : {}), ...(maxThinking !== undefined ? { maxThinking } : {}) };
|
|
2309
2350
|
}
|
|
2310
2351
|
|
|
2311
|
-
export function discoverAgentsAll(cwd: string): {
|
|
2352
|
+
export function discoverAgentsAll(cwd: string, preferredModelProvider?: string): {
|
|
2312
2353
|
builtin: AgentConfig[];
|
|
2313
2354
|
package: AgentConfig[];
|
|
2314
2355
|
user: AgentConfig[];
|
|
@@ -2331,8 +2372,8 @@ export function discoverAgentsAll(cwd: string): {
|
|
|
2331
2372
|
const { readDirs: projectChainDirs, preferredDir: projectChainDir } = resolveNearestProjectChainDirs(cwd);
|
|
2332
2373
|
const userSettingsPath = getUserAgentSettingsPath();
|
|
2333
2374
|
const projectSettingsPath = getProjectAgentSettingsPath(cwd);
|
|
2334
|
-
const userSettings = readSubagentSettings(userSettingsPath);
|
|
2335
|
-
const projectSettings = readSubagentSettings(projectSettingsPath);
|
|
2375
|
+
const userSettings = selectProviderOverrides(readSubagentSettings(userSettingsPath), preferredModelProvider);
|
|
2376
|
+
const projectSettings = selectProviderOverrides(readSubagentSettings(projectSettingsPath), preferredModelProvider);
|
|
2336
2377
|
const defaultProvider = resolveSubagentDefaultProvider(userSettings, projectSettings, projectSettingsPath);
|
|
2337
2378
|
const defaultModel = resolveSubagentDefaultModel(userSettings, projectSettings, userSettingsPath, projectSettingsPath, defaultProvider);
|
|
2338
2379
|
const defaultThinking = resolveSubagentDefaultThinking(userSettings, projectSettings, projectSettingsPath);
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import type { AcceptanceInput, AcceptanceRole, AgentRunnerConfig, OutputMode, ToolBudgetConfig
|
|
2
|
+
import type { AcceptanceInput, AcceptanceRole, AgentRunnerConfig, OutputMode, ToolBudgetConfig } from "../shared/types.ts";
|
|
3
3
|
import { CODE_OWNED_EXTERNAL_CLI_ADAPTER_LABEL, isCodeOwnedExternalCliAdapterId, parseExternalCliCapabilityNarrowing, validateCodeOwnedProfileRunner } from "../runs/shared/external-cli-contract.ts";
|
|
4
4
|
import { validateAcceptanceInput } from "../runs/shared/acceptance.ts";
|
|
5
5
|
import { validatePermissionRules, type PermissionRules } from "../runs/shared/permissions.ts";
|
|
6
6
|
import { validateToolBudgetConfig } from "../runs/shared/tool-budget.ts";
|
|
7
|
-
import { resolveTurnBudgetConfig } from "../runs/shared/turn-budget.ts";
|
|
8
7
|
import { BUILTIN_AGENT_NAMES } from "./builtin-names.ts";
|
|
9
8
|
import type { AgentConfig, AgentDefaultContext, AgentDiscoveryDiagnostic } from "./agents.ts";
|
|
10
9
|
|
|
@@ -21,6 +20,7 @@ export interface RuntimeAgentDefinition {
|
|
|
21
20
|
systemPrompt: string;
|
|
22
21
|
aliases?: readonly string[];
|
|
23
22
|
tools?: readonly string[];
|
|
23
|
+
allowNestedSubagents?: boolean;
|
|
24
24
|
mcpDirectTools?: readonly string[];
|
|
25
25
|
model?: string;
|
|
26
26
|
fallbackModels?: readonly string[];
|
|
@@ -33,7 +33,6 @@ export interface RuntimeAgentDefinition {
|
|
|
33
33
|
defaultAsync?: boolean;
|
|
34
34
|
defaultTimeoutMs?: number;
|
|
35
35
|
defaultToolTimeoutMs?: number;
|
|
36
|
-
defaultTurnBudget?: TurnBudgetConfig;
|
|
37
36
|
defaultAcceptance?: AcceptanceInput;
|
|
38
37
|
acceptanceRole?: AcceptanceRole;
|
|
39
38
|
runner?: AgentRunnerConfig;
|
|
@@ -135,8 +134,8 @@ function validateStringList(value: unknown, field: string): string[] | undefined
|
|
|
135
134
|
|
|
136
135
|
function validatePositiveInteger(value: unknown, field: string): number | undefined {
|
|
137
136
|
if (value === undefined) return undefined;
|
|
138
|
-
if (!Number.isInteger(value) ||
|
|
139
|
-
return value
|
|
137
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) throw new Error(`${field} must be a positive integer when provided.`);
|
|
138
|
+
return value;
|
|
140
139
|
}
|
|
141
140
|
|
|
142
141
|
function validateBoolean(value: unknown, field: string): boolean | undefined {
|
|
@@ -183,12 +182,6 @@ function validateRunner(value: unknown): AgentRunnerConfig | undefined {
|
|
|
183
182
|
return { type: "external-cli", ...(isCodeOwnedExternalCliAdapterId(runner.adapter) ? { adapter: runner.adapter } : {}), command: runner.command.trim(), ...(args?.length ? { args } : {}), ...(runner.promptDelivery ? { promptDelivery: "stdin" as const } : {}), ...(capabilities ? { capabilities } : {}) };
|
|
184
183
|
}
|
|
185
184
|
|
|
186
|
-
function validateTurnBudget(value: unknown): TurnBudgetConfig | undefined {
|
|
187
|
-
const result = resolveTurnBudgetConfig(value, "Runtime agent definition defaultTurnBudget");
|
|
188
|
-
if (result.error) throw new Error(result.error);
|
|
189
|
-
return result.turnBudget;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
185
|
function validateAcceptance(value: unknown): AcceptanceInput | undefined {
|
|
193
186
|
const errors = validateAcceptanceInput(value, "Runtime agent definition defaultAcceptance");
|
|
194
187
|
if (errors.length > 0) throw new Error(errors.join(" "));
|
|
@@ -205,9 +198,9 @@ function validateDefinition(value: unknown): RuntimeAgentDefinition {
|
|
|
205
198
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Runtime agent definition must be an object.");
|
|
206
199
|
const definition = value as Record<string, unknown>;
|
|
207
200
|
const supported = new Set([
|
|
208
|
-
"description", "systemPrompt", "aliases", "tools", "mcpDirectTools", "model", "fallbackModels", "thinking",
|
|
201
|
+
"description", "systemPrompt", "aliases", "tools", "allowNestedSubagents", "mcpDirectTools", "model", "fallbackModels", "thinking",
|
|
209
202
|
"systemPromptMode", "inheritProjectContext", "inheritGlobalContext", "inheritSkills", "defaultContext", "defaultAsync", "defaultTimeoutMs",
|
|
210
|
-
"defaultToolTimeoutMs", "
|
|
203
|
+
"defaultToolTimeoutMs", "defaultAcceptance", "acceptanceRole", "runner", "skills", "skillPath",
|
|
211
204
|
"extensions", "subagentOnlyExtensions", "mutationTools", "output", "outputMode", "defaultReads", "defaultProgress", "interactive",
|
|
212
205
|
"maxSubagentDepth", "completionGuard", "toolBudget", "permissions",
|
|
213
206
|
]);
|
|
@@ -225,6 +218,7 @@ function validateDefinition(value: unknown): RuntimeAgentDefinition {
|
|
|
225
218
|
if (outputMode !== undefined && outputMode !== "inline" && outputMode !== "file-only") throw new Error("Runtime agent definition outputMode must be 'inline' or 'file-only'.");
|
|
226
219
|
const aliases = validateStringList(definition.aliases, "Runtime agent definition aliases");
|
|
227
220
|
const tools = validateStringList(definition.tools, "Runtime agent definition tools");
|
|
221
|
+
const allowNestedSubagents = validateBoolean(definition.allowNestedSubagents, "Runtime agent definition allowNestedSubagents");
|
|
228
222
|
const mcpDirectTools = validateStringList(definition.mcpDirectTools, "Runtime agent definition mcpDirectTools");
|
|
229
223
|
const model = validateOptionalString(definition.model, "Runtime agent definition model");
|
|
230
224
|
const fallbackModels = validateStringList(definition.fallbackModels, "Runtime agent definition fallbackModels");
|
|
@@ -234,7 +228,6 @@ function validateDefinition(value: unknown): RuntimeAgentDefinition {
|
|
|
234
228
|
const defaultAsync = validateBoolean(definition.defaultAsync, "Runtime agent definition defaultAsync");
|
|
235
229
|
const defaultTimeoutMs = validatePositiveInteger(definition.defaultTimeoutMs, "Runtime agent definition defaultTimeoutMs");
|
|
236
230
|
const defaultToolTimeoutMs = validatePositiveInteger(definition.defaultToolTimeoutMs, "Runtime agent definition defaultToolTimeoutMs");
|
|
237
|
-
const defaultTurnBudget = validateTurnBudget(definition.defaultTurnBudget);
|
|
238
231
|
const defaultAcceptance = validateAcceptance(definition.defaultAcceptance);
|
|
239
232
|
const runner = validateRunner(definition.runner);
|
|
240
233
|
const skills = validateStringList(definition.skills, "Runtime agent definition skills");
|
|
@@ -255,6 +248,7 @@ function validateDefinition(value: unknown): RuntimeAgentDefinition {
|
|
|
255
248
|
systemPrompt: validateString(definition.systemPrompt, "Runtime agent definition systemPrompt", MAX_SYSTEM_PROMPT_LENGTH),
|
|
256
249
|
...(aliases ? { aliases } : {}),
|
|
257
250
|
...(tools ? { tools } : {}),
|
|
251
|
+
...(allowNestedSubagents !== undefined ? { allowNestedSubagents } : {}),
|
|
258
252
|
...(mcpDirectTools ? { mcpDirectTools } : {}),
|
|
259
253
|
...(model ? { model } : {}),
|
|
260
254
|
...(fallbackModels ? { fallbackModels } : {}),
|
|
@@ -267,7 +261,6 @@ function validateDefinition(value: unknown): RuntimeAgentDefinition {
|
|
|
267
261
|
...(defaultAsync !== undefined ? { defaultAsync } : {}),
|
|
268
262
|
...(defaultTimeoutMs !== undefined ? { defaultTimeoutMs } : {}),
|
|
269
263
|
...(defaultToolTimeoutMs !== undefined ? { defaultToolTimeoutMs } : {}),
|
|
270
|
-
...(defaultTurnBudget !== undefined ? { defaultTurnBudget } : {}),
|
|
271
264
|
...(defaultAcceptance !== undefined ? { defaultAcceptance } : {}),
|
|
272
265
|
...(acceptanceRole !== undefined ? { acceptanceRole: acceptanceRole as AcceptanceRole } : {}),
|
|
273
266
|
...(runner !== undefined ? { runner } : {}),
|
|
@@ -334,6 +327,7 @@ function toAgentConfig(name: string, definition: RuntimeAgentDefinition): AgentC
|
|
|
334
327
|
...(aliases ? { aliases } : {}),
|
|
335
328
|
...(definition.runner !== undefined ? { runner: definition.runner } : {}),
|
|
336
329
|
...(definition.tools !== undefined ? { tools: [...definition.tools] } : {}),
|
|
330
|
+
...(definition.allowNestedSubagents !== undefined ? { allowNestedSubagents: definition.allowNestedSubagents } : {}),
|
|
337
331
|
...(definition.mcpDirectTools !== undefined ? { mcpDirectTools: [...definition.mcpDirectTools] } : {}),
|
|
338
332
|
...(definition.model !== undefined ? { model: definition.model } : {}),
|
|
339
333
|
...(definition.fallbackModels !== undefined ? { fallbackModels: [...definition.fallbackModels] } : {}),
|
|
@@ -346,7 +340,6 @@ function toAgentConfig(name: string, definition: RuntimeAgentDefinition): AgentC
|
|
|
346
340
|
...(definition.defaultAsync !== undefined ? { defaultAsync: definition.defaultAsync } : {}),
|
|
347
341
|
...(definition.defaultTimeoutMs !== undefined ? { defaultTimeoutMs: definition.defaultTimeoutMs } : {}),
|
|
348
342
|
...(definition.defaultToolTimeoutMs !== undefined ? { defaultToolTimeoutMs: definition.defaultToolTimeoutMs } : {}),
|
|
349
|
-
...(definition.defaultTurnBudget !== undefined ? { defaultTurnBudget: definition.defaultTurnBudget } : {}),
|
|
350
343
|
...(definition.defaultAcceptance !== undefined ? { defaultAcceptance: definition.defaultAcceptance } : {}),
|
|
351
344
|
...(definition.acceptanceRole !== undefined ? { acceptanceRole: definition.acceptanceRole } : {}),
|
|
352
345
|
systemPrompt: definition.systemPrompt,
|