pi-subagents 0.29.0 → 0.30.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 +17 -0
- package/README.md +9 -2
- package/package.json +1 -1
- package/skills/pi-subagents/SKILL.md +25 -0
- package/src/agents/agent-management.ts +19 -2
- package/src/agents/agent-serializer.ts +5 -0
- package/src/agents/agents.ts +36 -1
- package/src/agents/proactive-skills.ts +191 -0
- package/src/extension/fanout-child.ts +1 -1
- package/src/extension/index.ts +3 -1
- package/src/extension/schemas.ts +32 -5
- package/src/runs/background/async-execution.ts +166 -63
- package/src/runs/background/async-resume.ts +53 -5
- package/src/runs/background/async-status.ts +4 -1
- package/src/runs/background/chain-append.ts +282 -0
- package/src/runs/background/chain-root-attachment.ts +161 -0
- package/src/runs/background/run-status.ts +1 -0
- package/src/runs/background/subagent-runner.ts +79 -8
- package/src/runs/foreground/execution.ts +1 -0
- package/src/runs/foreground/subagent-executor.ts +288 -12
- package/src/runs/shared/chain-outputs.ts +23 -8
- package/src/runs/shared/dynamic-fanout.ts +1 -1
- package/src/runs/shared/parallel-utils.ts +7 -0
- package/src/runs/shared/pi-args.ts +3 -2
- package/src/runs/shared/subagent-control.ts +8 -11
- package/src/shared/types.ts +10 -1
- package/src/slash/slash-bridge.ts +3 -1
- package/src/slash/slash-commands.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.30.0] - 2026-06-20
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Allow active async chains to accept an `append-step` request that adds one new tail step while the chain is still running.
|
|
9
|
+
- Allow async subagent results to be attached as the root step of a new follow-up chain.
|
|
10
|
+
- Added `subagentOnlyExtensions` so agents can pass selected tool extensions only to spawned subagents without exposing them to the parent agent.
|
|
11
|
+
- Added proactive skill-subagent suggestions to `subagent({ action: "list" })` based on repeatedly configured skill use, while keeping the behavior advisory and opt-out friendly.
|
|
12
|
+
- Added regression coverage for long worker/reviewer chains and parallel -> funnel -> fanout chain flows across foreground and async execution.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- Interrupt live async children before delivering `resume` follow-up messages so intercom nudges reach workers that are stuck mid-turn more reliably.
|
|
16
|
+
- Reject appended chain steps with duplicate reserved output names or unknown named-output references before they are queued.
|
|
17
|
+
- Ignore legacy `.agents/skills` files during agent discovery so skill definitions are not registered as subagents. Thanks to chyax98 (@chyax98) for #257.
|
|
18
|
+
- Launch detached async runners through Node when Pi itself is not the Node executable. Thanks to Tetsuya.dev (@tetsuya-dev-jp) for #273.
|
|
19
|
+
- Preserve the slash command requester context when bridge requests launch subagents. Thanks to Victor Sumner (@vsumner) for #268.
|
|
20
|
+
- Trim repeated nested `subagent` tool schema descriptions so provider payloads stay compact while retaining top-level parameter guidance. Thanks to Thomas Mustier (@tmustier) for #250.
|
|
21
|
+
|
|
5
22
|
## [0.29.0] - 2026-06-19
|
|
6
23
|
|
|
7
24
|
### Added
|
package/README.md
CHANGED
|
@@ -424,6 +424,7 @@ package: code-analysis
|
|
|
424
424
|
description: Fast codebase recon
|
|
425
425
|
tools: read, grep, find, ls, bash, mcp:chrome-devtools
|
|
426
426
|
extensions:
|
|
427
|
+
subagentOnlyExtensions: ./tools/child-only-search.ts
|
|
427
428
|
model: claude-haiku-4-5
|
|
428
429
|
fallbackModels: openai/gpt-5-mini, anthropic/claude-sonnet-4
|
|
429
430
|
thinking: high
|
|
@@ -449,6 +450,7 @@ Important fields:
|
|
|
449
450
|
| `package` | Optional package identifier. A file with `name: scout` and `package: code-analysis` registers as `code-analysis.scout`; serialization keeps `name` and `package` separate. |
|
|
450
451
|
| `tools` | Builtin tool allowlist. `mcp:` entries select direct MCP tools when `pi-mcp-adapter` is installed. |
|
|
451
452
|
| `extensions` | Omitted means normal extensions; empty means no extensions; comma-separated values allowlist specific extensions. |
|
|
453
|
+
| `subagentOnlyExtensions` | Comma-separated extension paths loaded only in spawned child sessions for this agent. Tools registered there are unavailable to the main agent unless also installed through normal Pi extension configuration. |
|
|
452
454
|
| `model` | Default model. Bare ids prefer the current provider when possible, then unique registry matches. |
|
|
453
455
|
| `fallbackModels` | Ordered backup models for provider/model failures such as quota, auth, timeout, or unavailable model. Ordinary task failures do not trigger fallback. |
|
|
454
456
|
| `thinking` | Appended as a `:level` suffix at runtime unless a suffix is already present. |
|
|
@@ -491,6 +493,8 @@ extensions: /abs/path/to/ext-a.ts, /abs/path/to/ext-b.ts
|
|
|
491
493
|
|
|
492
494
|
When `extensions` is present, it takes precedence over extension paths implied by `tools` entries.
|
|
493
495
|
|
|
496
|
+
Use `subagentOnlyExtensions` when a custom extension tool should exist only inside child sessions. It is scoped by agent config: every run of that agent receives those extension paths, while other agents do not unless they declare the same field. The current model does not have a separate named-subagent audience inside one agent definition.
|
|
497
|
+
|
|
494
498
|
## Chain files
|
|
495
499
|
|
|
496
500
|
Chains are reusable workflows stored separately from agent files. Use `.chain.md` for simple sequential saved chains. Use `.chain.json` when a chain needs dynamic fanout.
|
|
@@ -786,7 +790,7 @@ Agent definitions are not loaded into context by default. Management actions let
|
|
|
786
790
|
|-------|------|---------|-------------|
|
|
787
791
|
| `agent` | string | - | Agent name for single mode, or target for management actions. |
|
|
788
792
|
| `task` | string | - | Task string for single mode. |
|
|
789
|
-
| `action` | string | - | `list`, `get`, `create`, `update`, `delete`, `status`, `interrupt`, `resume`, or `doctor`. |
|
|
793
|
+
| `action` | string | - | `list`, `get`, `create`, `update`, `delete`, `status`, `interrupt`, `resume`, `append-step`, or `doctor`. |
|
|
790
794
|
| `chainName` | string | - | Chain name for management actions. |
|
|
791
795
|
| `config` | object/string | - | Agent or chain config for create/update. |
|
|
792
796
|
| `output` | `string \| false` | agent default | Override single-agent output file. |
|
|
@@ -796,7 +800,7 @@ Agent definitions are not loaded into context by default. Management actions let
|
|
|
796
800
|
| `tasks` | array | - | Top-level parallel tasks. Supports `agent`, `task`, `cwd`, `count`, `output`, `outputMode`, `reads`, `progress`, `skill`, `model`, and `acceptance`. |
|
|
797
801
|
| `concurrency` | number | config or `4` | Top-level parallel concurrency. |
|
|
798
802
|
| `worktree` | boolean | false | Create isolated git worktrees for parallel tasks. |
|
|
799
|
-
| `chain` | array | - | Sequential, static parallel, and dynamic fanout chain steps. Steps and chain parallel tasks support `phase`, `label`, `as`, `outputSchema`, and `acceptance` in addition to the usual execution fields. Dynamic fanout uses `expand`, one child `parallel` template, and `collect`. |
|
|
803
|
+
| `chain` | array | - | Sequential, static parallel, and dynamic fanout chain steps. Steps and chain parallel tasks support `phase`, `label`, `as`, `outputSchema`, and `acceptance` in addition to the usual execution fields. Dynamic fanout uses `expand`, one child `parallel` template, and `collect`. With `action: "append-step"`, pass exactly one step to append to a running async chain. |
|
|
800
804
|
| `context` | `fresh \| fork` | agent default or `fresh` | `fork` creates real branched sessions from the parent leaf. Packaged `planner`, `worker`, and `oracle` default to `fork`. |
|
|
801
805
|
| `chainDir` | string | temp chain dir | Persistent directory for chain artifacts. |
|
|
802
806
|
| `clarify` | boolean | true for chains | Show TUI preview/edit flow. |
|
|
@@ -827,6 +831,7 @@ subagent({ action: "interrupt", id: "<nested-run-id>" })
|
|
|
827
831
|
subagent({ action: "resume", id: "<run-id>", message: "follow-up question" })
|
|
828
832
|
subagent({ action: "resume", id: "<run-id>", index: 1, message: "follow-up for child 2" })
|
|
829
833
|
subagent({ action: "resume", id: "<nested-run-id>", message: "follow-up for a nested child" })
|
|
834
|
+
subagent({ action: "append-step", id: "<run-id>", chain: [{ agent: "worker", task: "Continue from {previous}" }] })
|
|
830
835
|
subagent({ action: "doctor" })
|
|
831
836
|
```
|
|
832
837
|
|
|
@@ -834,6 +839,8 @@ subagent({ action: "doctor" })
|
|
|
834
839
|
|
|
835
840
|
`resume` sends the follow-up directly when an async child is still reachable over intercom. After completion, it revives the child by starting a new async child from the stored child session file. Multi-child async runs and remembered foreground single, parallel, or chain runs can be revived by passing `index` to choose the child. Nested runs can be resumed by nested id when their live route or persisted session metadata is available. Revive starts a new child process from the old session context; it does not restart the same OS process, and it requires the chosen child to have a persisted `.jsonl` session file.
|
|
836
841
|
|
|
842
|
+
`append-step` accepts exactly one sequential, static parallel, or dynamic fanout chain step for a top-level async chain whose status is still `running`. The step is persisted in the run directory and becomes eligible only after the chain's already-queued steps finish; completed, failed, paused, foreground, single, and top-level parallel runs reject appends.
|
|
843
|
+
|
|
837
844
|
## Worktree isolation
|
|
838
845
|
|
|
839
846
|
Parallel agents can clobber each other if they edit the same checkout. `worktree: true` gives each parallel child its own git worktree branched from `HEAD`.
|
package/package.json
CHANGED
|
@@ -20,6 +20,7 @@ Use this skill when the parent orchestrator needs to launch a specialized subage
|
|
|
20
20
|
- **Implementation handoff**: have `oracle` advise, then `worker` implement only after an approved direction
|
|
21
21
|
- **Recon and planning**: use `scout` or `context-builder`, then `planner`
|
|
22
22
|
- **Parallel exploration**: run multiple non-conflicting tasks concurrently
|
|
23
|
+
- **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
|
|
23
24
|
- **Long-running work**: launch async/background runs and inspect them later
|
|
24
25
|
- **Subagent control**: watch needs-attention signals and soft-interrupt only when a delegated run is genuinely blocked
|
|
25
26
|
- **Agent authoring**: create, update, or override agents and chains for a project
|
|
@@ -55,6 +56,30 @@ The prompt templates in `prompts/` encode workflows the parent agent can run on
|
|
|
55
56
|
|
|
56
57
|
Use this when the user wants adversarial review of a diff, plan, issue, file, or implemented work. Launch fresh-context `reviewer` agents with distinct angles generated from the actual target. Common angles are correctness/regressions, tests/validation, and simplicity/maintainability; adapt for TypeScript, UI, security, docs, or large structural changes. Reviewers should inspect files and diffs directly, return concise evidence-backed findings with file/line references, and avoid edits unless the user explicitly asks for a writer pass. The parent synthesizes fixes worth doing now, optional improvements, and feedback to ignore/defer before applying anything.
|
|
57
58
|
|
|
59
|
+
### Proactive skill-specialist technique
|
|
60
|
+
|
|
61
|
+
Use this when `{ action: "list" }` reports proactive skill subagent suggestions and the user's task would benefit from perspectives the parent regularly uses. These suggestions are conservative: a skill is recommended only when it is available and referenced repeatedly by configured agents or saved chains. Treat the list as an opt-in hint for the current task, not a command to always fan out.
|
|
62
|
+
|
|
63
|
+
Default guardrails:
|
|
64
|
+
- Keep the fanout small: usually one or two skill-specialist children, never more than the listed recommendations or configured cap.
|
|
65
|
+
- Prefer `context: "fresh"` and include only the files, diff, plan, URL, or request details each child needs. Use forked context only when private/session history is essential and appropriate to share.
|
|
66
|
+
- Use read-only agents for analysis/review unless implementation was explicitly requested; do not create several writers in the same worktree.
|
|
67
|
+
- Skip proactive skill subagents for tiny questions, direct commands, highly private requests, or when the user asks not to delegate.
|
|
68
|
+
- Make cost and concurrency visible by using an ordinary `subagent(...)` call rather than hidden/background automation.
|
|
69
|
+
|
|
70
|
+
Example shape:
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
subagent({
|
|
74
|
+
tasks: [
|
|
75
|
+
{ agent: "reviewer", task: "Apply the available 'deslop' skill to review the current diff for concrete cleanup findings only. Do not modify files.", skill: "deslop" },
|
|
76
|
+
{ agent: "reviewer", task: "Apply the available 'accessibility' skill to review the UI changes for concrete issues only. Do not modify files.", skill: "accessibility" }
|
|
77
|
+
],
|
|
78
|
+
context: "fresh",
|
|
79
|
+
concurrency: 2
|
|
80
|
+
})
|
|
81
|
+
```
|
|
82
|
+
|
|
58
83
|
### Review-loop technique
|
|
59
84
|
|
|
60
85
|
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 chain when the workflow is known, or continue with explicit follow-up subagent runs after each async completion. For an initial chain, pass `async: true` so the main chat is unblocked; do not set `clarify: true` unless the user explicitly wants the foreground clarify UI. 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 blockers or fixes worth doing now, remaining 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.
|
|
@@ -19,11 +19,14 @@ import {
|
|
|
19
19
|
import { serializeAgent } from "./agent-serializer.ts";
|
|
20
20
|
import { serializeChain, serializeJsonChain } from "./chain-serializer.ts";
|
|
21
21
|
import { discoverAvailableSkills } from "./skills.ts";
|
|
22
|
-
import
|
|
22
|
+
import {
|
|
23
|
+
buildProactiveSkillSubagentRecommendationLines,
|
|
24
|
+
} from "./proactive-skills.ts";
|
|
25
|
+
import type { Details, ExtensionConfig } from "../shared/types.ts";
|
|
23
26
|
|
|
24
27
|
type ManagementAction = "list" | "get" | "create" | "update" | "delete";
|
|
25
28
|
type ManagementScope = "user" | "project";
|
|
26
|
-
type ManagementContext = Pick<ExtensionContext, "cwd" | "modelRegistry"
|
|
29
|
+
type ManagementContext = Pick<ExtensionContext, "cwd" | "modelRegistry"> & { config?: ExtensionConfig };
|
|
27
30
|
|
|
28
31
|
interface ManagementParams {
|
|
29
32
|
action?: string;
|
|
@@ -273,6 +276,12 @@ function applyAgentConfig(target: AgentConfig, cfg: Record<string, unknown>): st
|
|
|
273
276
|
else if (typeof cfg.extensions === "string") target.extensions = parseCsv(cfg.extensions);
|
|
274
277
|
else return "config.extensions must be a comma-separated string, empty string, or false when provided.";
|
|
275
278
|
}
|
|
279
|
+
if (hasKey(cfg, "subagentOnlyExtensions")) {
|
|
280
|
+
if (cfg.subagentOnlyExtensions === false) target.subagentOnlyExtensions = undefined;
|
|
281
|
+
else if (cfg.subagentOnlyExtensions === "") target.subagentOnlyExtensions = [];
|
|
282
|
+
else if (typeof cfg.subagentOnlyExtensions === "string") target.subagentOnlyExtensions = parseCsv(cfg.subagentOnlyExtensions);
|
|
283
|
+
else return "config.subagentOnlyExtensions must be a comma-separated string, empty string, or false when provided.";
|
|
284
|
+
}
|
|
276
285
|
if (hasKey(cfg, "thinking")) {
|
|
277
286
|
if (cfg.thinking === false || cfg.thinking === "") target.thinking = undefined;
|
|
278
287
|
else if (typeof cfg.thinking === "string") target.thinking = cfg.thinking.trim() || undefined;
|
|
@@ -385,6 +394,7 @@ function formatAgentDetail(agent: AgentConfig): string {
|
|
|
385
394
|
if (agent.defaultContext) lines.push(`Default context: ${agent.defaultContext}`);
|
|
386
395
|
if (agent.source === "builtin") lines.push(`Disabled: ${agent.disabled ? "true" : "false"}`);
|
|
387
396
|
if (agent.extensions !== undefined) lines.push(`Extensions: ${agent.extensions.length ? agent.extensions.join(", ") : "(none)"}`);
|
|
397
|
+
if (agent.subagentOnlyExtensions !== undefined) lines.push(`Subagent-only extensions: ${agent.subagentOnlyExtensions.length ? agent.subagentOnlyExtensions.join(", ") : "(none)"}`);
|
|
388
398
|
if (agent.thinking) lines.push(`Thinking: ${agent.thinking}`);
|
|
389
399
|
if (agent.output) lines.push(`Output: ${agent.output}`);
|
|
390
400
|
if (agent.defaultReads?.length) lines.push(`Reads: ${agent.defaultReads.join(", ")}`);
|
|
@@ -450,6 +460,12 @@ export function handleList(params: ManagementParams, ctx: ManagementContext): Ag
|
|
|
450
460
|
const agents = scopedAgents.filter((a) => !a.disabled);
|
|
451
461
|
const chains = d.chains.filter((c) => scope === "both" || c.source === "package" || c.source === scope).sort((a, b) => a.name.localeCompare(b.name));
|
|
452
462
|
const diagnostics = d.chainDiagnostics.filter((entry) => scope === "both" || entry.source === scope);
|
|
463
|
+
const proactiveSuggestions = buildProactiveSkillSubagentRecommendationLines({
|
|
464
|
+
agents,
|
|
465
|
+
chains,
|
|
466
|
+
config: ctx.config?.proactiveSkillSubagents,
|
|
467
|
+
discoverAvailableSkills: () => discoverAvailableSkills(ctx.cwd),
|
|
468
|
+
});
|
|
453
469
|
const lines = [
|
|
454
470
|
"Executable agents:",
|
|
455
471
|
...(agents.length
|
|
@@ -458,6 +474,7 @@ export function handleList(params: ManagementParams, ctx: ManagementContext): Ag
|
|
|
458
474
|
"",
|
|
459
475
|
"Chains:",
|
|
460
476
|
...(chains.length ? chains.map((c) => `- ${c.name} (${c.source}): ${c.description}`) : ["- (none)"]),
|
|
477
|
+
...(proactiveSuggestions.length ? ["", ...proactiveSuggestions] : []),
|
|
461
478
|
...(diagnostics.length ? ["", "Chain diagnostics:", ...diagnostics.map((entry) => `- ${entry.filePath}: ${entry.error}`)] : []),
|
|
462
479
|
];
|
|
463
480
|
return result(lines.join("\n"));
|
|
@@ -16,6 +16,7 @@ export const KNOWN_FIELDS = new Set([
|
|
|
16
16
|
"skill",
|
|
17
17
|
"skills",
|
|
18
18
|
"extensions",
|
|
19
|
+
"subagentOnlyExtensions",
|
|
19
20
|
"output",
|
|
20
21
|
"defaultReads",
|
|
21
22
|
"defaultProgress",
|
|
@@ -59,6 +60,10 @@ export function serializeAgent(config: AgentConfig): string {
|
|
|
59
60
|
const extensionsValue = joinComma(config.extensions);
|
|
60
61
|
lines.push(`extensions: ${extensionsValue ?? ""}`);
|
|
61
62
|
}
|
|
63
|
+
if (config.subagentOnlyExtensions !== undefined) {
|
|
64
|
+
const subagentOnlyExtensionsValue = joinComma(config.subagentOnlyExtensions);
|
|
65
|
+
lines.push(`subagentOnlyExtensions: ${subagentOnlyExtensionsValue ?? ""}`);
|
|
66
|
+
}
|
|
62
67
|
|
|
63
68
|
if (config.output) lines.push(`output: ${config.output}`);
|
|
64
69
|
|
package/src/agents/agents.ts
CHANGED
|
@@ -47,6 +47,7 @@ export interface BuiltinAgentOverrideBase {
|
|
|
47
47
|
skills?: string[];
|
|
48
48
|
tools?: string[];
|
|
49
49
|
mcpDirectTools?: string[];
|
|
50
|
+
subagentOnlyExtensions?: string[];
|
|
50
51
|
completionGuard?: boolean;
|
|
51
52
|
}
|
|
52
53
|
|
|
@@ -62,6 +63,7 @@ interface BuiltinAgentOverrideConfig {
|
|
|
62
63
|
systemPrompt?: string;
|
|
63
64
|
skills?: string[] | false;
|
|
64
65
|
tools?: string[] | false;
|
|
66
|
+
subagentOnlyExtensions?: string[] | false;
|
|
65
67
|
completionGuard?: boolean;
|
|
66
68
|
}
|
|
67
69
|
|
|
@@ -90,6 +92,7 @@ export interface AgentConfig {
|
|
|
90
92
|
filePath: string;
|
|
91
93
|
skills?: string[];
|
|
92
94
|
extensions?: string[];
|
|
95
|
+
subagentOnlyExtensions?: string[];
|
|
93
96
|
output?: string;
|
|
94
97
|
defaultReads?: string[];
|
|
95
98
|
defaultProgress?: boolean;
|
|
@@ -457,6 +460,7 @@ function cloneOverrideBase(agent: AgentConfig): BuiltinAgentOverrideBase {
|
|
|
457
460
|
skills: agent.skills ? [...agent.skills] : undefined,
|
|
458
461
|
tools: agent.tools ? [...agent.tools] : undefined,
|
|
459
462
|
mcpDirectTools: agent.mcpDirectTools ? [...agent.mcpDirectTools] : undefined,
|
|
463
|
+
subagentOnlyExtensions: agent.subagentOnlyExtensions ? [...agent.subagentOnlyExtensions] : undefined,
|
|
460
464
|
completionGuard: agent.completionGuard,
|
|
461
465
|
};
|
|
462
466
|
}
|
|
@@ -476,6 +480,7 @@ function cloneOverrideValue(override: BuiltinAgentOverrideConfig): BuiltinAgentO
|
|
|
476
480
|
...(override.systemPrompt !== undefined ? { systemPrompt: override.systemPrompt } : {}),
|
|
477
481
|
...(override.skills !== undefined ? { skills: override.skills === false ? false : [...override.skills] } : {}),
|
|
478
482
|
...(override.tools !== undefined ? { tools: override.tools === false ? false : [...override.tools] } : {}),
|
|
483
|
+
...(override.subagentOnlyExtensions !== undefined ? { subagentOnlyExtensions: override.subagentOnlyExtensions === false ? false : [...override.subagentOnlyExtensions] } : {}),
|
|
479
484
|
...(override.completionGuard !== undefined ? { completionGuard: override.completionGuard } : {}),
|
|
480
485
|
};
|
|
481
486
|
}
|
|
@@ -635,6 +640,9 @@ function parseBuiltinOverrideEntry(
|
|
|
635
640
|
const tools = parseOverrideStringArrayOrFalse(input.tools, { filePath, name, field: "tools" });
|
|
636
641
|
if (tools !== undefined) override.tools = tools;
|
|
637
642
|
|
|
643
|
+
const subagentOnlyExtensions = parseOverrideStringArrayOrFalse(input.subagentOnlyExtensions, { filePath, name, field: "subagentOnlyExtensions" });
|
|
644
|
+
if (subagentOnlyExtensions !== undefined) override.subagentOnlyExtensions = subagentOnlyExtensions;
|
|
645
|
+
|
|
638
646
|
return Object.keys(override).length > 0 ? override : undefined;
|
|
639
647
|
}
|
|
640
648
|
|
|
@@ -693,6 +701,9 @@ function applyBuiltinOverride(
|
|
|
693
701
|
next.tools = tools;
|
|
694
702
|
next.mcpDirectTools = mcpDirectTools;
|
|
695
703
|
}
|
|
704
|
+
if (override.subagentOnlyExtensions !== undefined) {
|
|
705
|
+
next.subagentOnlyExtensions = override.subagentOnlyExtensions === false ? undefined : [...override.subagentOnlyExtensions];
|
|
706
|
+
}
|
|
696
707
|
if (override.completionGuard !== undefined) next.completionGuard = override.completionGuard;
|
|
697
708
|
|
|
698
709
|
return next;
|
|
@@ -733,7 +744,7 @@ function applyBuiltinOverrides(
|
|
|
733
744
|
|
|
734
745
|
export function buildBuiltinOverrideConfig(
|
|
735
746
|
base: BuiltinAgentOverrideBase,
|
|
736
|
-
draft: Pick<AgentConfig, "model" | "fallbackModels" | "thinking" | "systemPromptMode" | "inheritProjectContext" | "inheritSkills" | "defaultContext" | "disabled" | "systemPrompt" | "skills" | "tools" | "mcpDirectTools" | "completionGuard">,
|
|
747
|
+
draft: Pick<AgentConfig, "model" | "fallbackModels" | "thinking" | "systemPromptMode" | "inheritProjectContext" | "inheritSkills" | "defaultContext" | "disabled" | "systemPrompt" | "skills" | "tools" | "mcpDirectTools" | "subagentOnlyExtensions" | "completionGuard">,
|
|
737
748
|
): BuiltinAgentOverrideConfig | undefined {
|
|
738
749
|
const override: BuiltinAgentOverrideConfig = {};
|
|
739
750
|
|
|
@@ -751,6 +762,9 @@ export function buildBuiltinOverrideConfig(
|
|
|
751
762
|
const baseTools = joinToolList(base);
|
|
752
763
|
const draftTools = joinToolList(draft);
|
|
753
764
|
if (!arraysEqual(draftTools, baseTools)) override.tools = draftTools ? [...draftTools] : false;
|
|
765
|
+
if (!arraysEqual(draft.subagentOnlyExtensions, base.subagentOnlyExtensions)) {
|
|
766
|
+
override.subagentOnlyExtensions = draft.subagentOnlyExtensions ? [...draft.subagentOnlyExtensions] : false;
|
|
767
|
+
}
|
|
754
768
|
if ((draft.completionGuard !== false) !== (base.completionGuard !== false)) {
|
|
755
769
|
override.completionGuard = draft.completionGuard !== false;
|
|
756
770
|
}
|
|
@@ -830,10 +844,23 @@ function listFilesRecursive(dir: string, predicate: (fileName: string) => boolea
|
|
|
830
844
|
return files;
|
|
831
845
|
}
|
|
832
846
|
|
|
847
|
+
function isLegacyAgentSkillPath(rootDir: string, filePath: string): boolean {
|
|
848
|
+
const relative = path.relative(rootDir, filePath);
|
|
849
|
+
const parts = relative.split(path.sep).map((part) => part.toLowerCase());
|
|
850
|
+
if (path.basename(rootDir).toLowerCase() === ".agents") {
|
|
851
|
+
parts.unshift(".agents");
|
|
852
|
+
}
|
|
853
|
+
return parts.some((part, index) => part === ".agents" && parts[index + 1] === "skills");
|
|
854
|
+
}
|
|
855
|
+
|
|
833
856
|
function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
|
|
834
857
|
const agents: AgentConfig[] = [];
|
|
835
858
|
|
|
836
859
|
for (const filePath of listFilesRecursive(dir, (fileName) => fileName.endsWith(".md") && !fileName.endsWith(".chain.md"))) {
|
|
860
|
+
if (isLegacyAgentSkillPath(dir, filePath)) {
|
|
861
|
+
continue;
|
|
862
|
+
}
|
|
863
|
+
|
|
837
864
|
let content: string;
|
|
838
865
|
try {
|
|
839
866
|
content = fs.readFileSync(filePath, "utf-8");
|
|
@@ -912,6 +939,13 @@ function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
|
|
|
912
939
|
.map((e) => e.trim())
|
|
913
940
|
.filter(Boolean);
|
|
914
941
|
}
|
|
942
|
+
let subagentOnlyExtensions: string[] | undefined;
|
|
943
|
+
if (frontmatter.subagentOnlyExtensions !== undefined) {
|
|
944
|
+
subagentOnlyExtensions = frontmatter.subagentOnlyExtensions
|
|
945
|
+
.split(",")
|
|
946
|
+
.map((e) => e.trim())
|
|
947
|
+
.filter(Boolean);
|
|
948
|
+
}
|
|
915
949
|
|
|
916
950
|
const extraFields: Record<string, string> = {};
|
|
917
951
|
for (const [key, value] of Object.entries(frontmatter)) {
|
|
@@ -944,6 +978,7 @@ function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
|
|
|
944
978
|
filePath,
|
|
945
979
|
skills: skills && skills.length > 0 ? skills : undefined,
|
|
946
980
|
extensions,
|
|
981
|
+
subagentOnlyExtensions,
|
|
947
982
|
output: frontmatter.output,
|
|
948
983
|
defaultReads: defaultReads && defaultReads.length > 0 ? defaultReads : undefined,
|
|
949
984
|
defaultProgress: frontmatter.defaultProgress === "true",
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import type { AgentConfig, ChainConfig, ChainStepConfig } from "./agents.ts";
|
|
2
|
+
import type { ProactiveSkillSubagentsConfig } from "../shared/types.ts";
|
|
3
|
+
|
|
4
|
+
const SUBAGENT_ORCHESTRATION_SKILL = "pi-subagents";
|
|
5
|
+
const DEFAULT_MIN_REFERENCES = 2;
|
|
6
|
+
const DEFAULT_MAX_RECOMMENDATIONS = 3;
|
|
7
|
+
const DEFAULT_PREFERRED_AGENT = "reviewer";
|
|
8
|
+
const FALLBACK_AGENT_ORDER = ["reviewer", "context-builder", "delegate"];
|
|
9
|
+
const MAX_RECOMMENDATION_CAP = 5;
|
|
10
|
+
|
|
11
|
+
export interface ResolvedProactiveSkillSubagentsConfig {
|
|
12
|
+
enabled: boolean;
|
|
13
|
+
minReferences: number;
|
|
14
|
+
maxRecommendations: number;
|
|
15
|
+
preferredAgent: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ProactiveSkillSubagentRecommendation {
|
|
19
|
+
skill: string;
|
|
20
|
+
agent: string;
|
|
21
|
+
references: number;
|
|
22
|
+
sources: string[];
|
|
23
|
+
description?: string;
|
|
24
|
+
reason: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface AvailableSkill {
|
|
28
|
+
name: string;
|
|
29
|
+
description?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function positiveInteger(value: unknown): number | undefined {
|
|
33
|
+
if (typeof value !== "number") return undefined;
|
|
34
|
+
if (!Number.isInteger(value) || !Number.isFinite(value) || value < 1) return undefined;
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function resolveProactiveSkillSubagentsConfig(
|
|
39
|
+
config?: ProactiveSkillSubagentsConfig | false,
|
|
40
|
+
): ResolvedProactiveSkillSubagentsConfig {
|
|
41
|
+
if (config === false) {
|
|
42
|
+
return {
|
|
43
|
+
enabled: false,
|
|
44
|
+
minReferences: DEFAULT_MIN_REFERENCES,
|
|
45
|
+
maxRecommendations: DEFAULT_MAX_RECOMMENDATIONS,
|
|
46
|
+
preferredAgent: DEFAULT_PREFERRED_AGENT,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const maxRecommendations = positiveInteger(config?.maxRecommendations) ?? DEFAULT_MAX_RECOMMENDATIONS;
|
|
51
|
+
return {
|
|
52
|
+
enabled: config?.enabled ?? true,
|
|
53
|
+
minReferences: positiveInteger(config?.minReferences) ?? DEFAULT_MIN_REFERENCES,
|
|
54
|
+
maxRecommendations: Math.min(maxRecommendations, MAX_RECOMMENDATION_CAP),
|
|
55
|
+
preferredAgent: typeof config?.preferredAgent === "string" && config.preferredAgent.trim()
|
|
56
|
+
? config.preferredAgent.trim()
|
|
57
|
+
: DEFAULT_PREFERRED_AGENT,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function normalizeSkillNames(value: unknown): string[] {
|
|
62
|
+
if (value === false || value === true || value === undefined || value === null) return [];
|
|
63
|
+
if (Array.isArray(value)) {
|
|
64
|
+
return [...new Set(value.filter((entry): entry is string => typeof entry === "string").map((entry) => entry.trim()).filter(Boolean))];
|
|
65
|
+
}
|
|
66
|
+
if (typeof value === "string") {
|
|
67
|
+
return [...new Set(value.split(",").map((entry) => entry.trim()).filter(Boolean))];
|
|
68
|
+
}
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function collectStepSkills(step: ChainStepConfig, out: Set<string>): void {
|
|
73
|
+
for (const skill of normalizeSkillNames(step.skills ?? (step as { skill?: unknown }).skill)) {
|
|
74
|
+
out.add(skill);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const parallel = step.parallel;
|
|
78
|
+
if (!parallel) return;
|
|
79
|
+
if (Array.isArray(parallel)) {
|
|
80
|
+
for (const child of parallel) {
|
|
81
|
+
if (child && typeof child === "object" && !Array.isArray(child)) {
|
|
82
|
+
collectStepSkills(child as ChainStepConfig, out);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (typeof parallel === "object") {
|
|
88
|
+
collectStepSkills(parallel as ChainStepConfig, out);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function chooseRecommendationAgent(agents: AgentConfig[], preferredAgent: string): string | undefined {
|
|
93
|
+
const enabled = agents.filter((agent) => !agent.disabled);
|
|
94
|
+
if (enabled.some((agent) => agent.name === preferredAgent)) return preferredAgent;
|
|
95
|
+
for (const name of FALLBACK_AGENT_ORDER) {
|
|
96
|
+
if (enabled.some((agent) => agent.name === name)) return name;
|
|
97
|
+
}
|
|
98
|
+
return enabled[0]?.name;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function addSource(counts: Map<string, Set<string>>, skill: string, source: string): void {
|
|
102
|
+
if (skill === SUBAGENT_ORCHESTRATION_SKILL) return;
|
|
103
|
+
const sources = counts.get(skill) ?? new Set<string>();
|
|
104
|
+
sources.add(source);
|
|
105
|
+
counts.set(skill, sources);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function recommendProactiveSkillSubagents(input: {
|
|
109
|
+
agents: AgentConfig[];
|
|
110
|
+
chains?: ChainConfig[];
|
|
111
|
+
availableSkills?: AvailableSkill[];
|
|
112
|
+
config?: ProactiveSkillSubagentsConfig | false;
|
|
113
|
+
}): ProactiveSkillSubagentRecommendation[] {
|
|
114
|
+
const config = resolveProactiveSkillSubagentsConfig(input.config);
|
|
115
|
+
if (!config.enabled) return [];
|
|
116
|
+
|
|
117
|
+
const agent = chooseRecommendationAgent(input.agents, config.preferredAgent);
|
|
118
|
+
if (!agent) return [];
|
|
119
|
+
|
|
120
|
+
const availableByName = input.availableSkills
|
|
121
|
+
? new Map(input.availableSkills.map((skill) => [skill.name, skill]))
|
|
122
|
+
: undefined;
|
|
123
|
+
const counts = new Map<string, Set<string>>();
|
|
124
|
+
|
|
125
|
+
for (const candidate of input.agents) {
|
|
126
|
+
if (candidate.disabled) continue;
|
|
127
|
+
for (const skill of candidate.skills ?? []) {
|
|
128
|
+
addSource(counts, skill, `agent:${candidate.name}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
for (const chain of input.chains ?? []) {
|
|
133
|
+
const chainSkills = new Set<string>();
|
|
134
|
+
for (const step of chain.steps) {
|
|
135
|
+
collectStepSkills(step, chainSkills);
|
|
136
|
+
}
|
|
137
|
+
for (const skill of chainSkills) {
|
|
138
|
+
addSource(counts, skill, `chain:${chain.name}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return [...counts.entries()]
|
|
143
|
+
.filter(([skill, sources]) => sources.size >= config.minReferences && (!availableByName || availableByName.has(skill)))
|
|
144
|
+
.map(([skill, sources]) => ({
|
|
145
|
+
skill,
|
|
146
|
+
agent,
|
|
147
|
+
references: sources.size,
|
|
148
|
+
sources: [...sources].sort((a, b) => a.localeCompare(b)),
|
|
149
|
+
description: availableByName?.get(skill)?.description,
|
|
150
|
+
reason: `referenced by ${sources.size} configured agents/chains`,
|
|
151
|
+
}))
|
|
152
|
+
.sort((a, b) => b.references - a.references || a.skill.localeCompare(b.skill))
|
|
153
|
+
.slice(0, config.maxRecommendations);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function formatProactiveSkillSubagentRecommendations(
|
|
157
|
+
recommendations: ProactiveSkillSubagentRecommendation[],
|
|
158
|
+
): string[] {
|
|
159
|
+
if (recommendations.length === 0) return [];
|
|
160
|
+
return [
|
|
161
|
+
"Proactive skill subagent suggestions:",
|
|
162
|
+
...recommendations.map((recommendation) => {
|
|
163
|
+
const sampleSources = recommendation.sources.slice(0, 3).join(", ");
|
|
164
|
+
const extra = recommendation.sources.length > 3 ? `, +${recommendation.sources.length - 3} more` : "";
|
|
165
|
+
const description = recommendation.description ? ` - ${recommendation.description}` : "";
|
|
166
|
+
return `- ${recommendation.skill} via ${recommendation.agent} (${recommendation.reason}; ${sampleSources}${extra})${description}`;
|
|
167
|
+
}),
|
|
168
|
+
"Guardrails: use these for broad tasks where a skill-specialist pass is useful; keep fanout small, use fresh context unless private/session context is explicitly needed, and skip when the user asks for a direct answer.",
|
|
169
|
+
];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function buildProactiveSkillSubagentRecommendationLines(input: {
|
|
173
|
+
agents: AgentConfig[];
|
|
174
|
+
chains?: ChainConfig[];
|
|
175
|
+
config?: ProactiveSkillSubagentsConfig | false;
|
|
176
|
+
discoverAvailableSkills: () => AvailableSkill[];
|
|
177
|
+
}): string[] {
|
|
178
|
+
if (!resolveProactiveSkillSubagentsConfig(input.config).enabled) return [];
|
|
179
|
+
let availableSkills: AvailableSkill[];
|
|
180
|
+
try {
|
|
181
|
+
availableSkills = input.discoverAvailableSkills();
|
|
182
|
+
} catch {
|
|
183
|
+
availableSkills = [];
|
|
184
|
+
}
|
|
185
|
+
return formatProactiveSkillSubagentRecommendations(recommendProactiveSkillSubagents({
|
|
186
|
+
agents: input.agents,
|
|
187
|
+
chains: input.chains,
|
|
188
|
+
availableSkills,
|
|
189
|
+
config: input.config,
|
|
190
|
+
}));
|
|
191
|
+
}
|
|
@@ -156,7 +156,7 @@ export default function registerFanoutChildSubagentExtension(pi: ExtensionAPI):
|
|
|
156
156
|
label: "Subagent",
|
|
157
157
|
description: [
|
|
158
158
|
"Delegate to subagents from child-safe fanout mode.",
|
|
159
|
-
"Allowed management/control actions: list, get, status, interrupt, resume, doctor.",
|
|
159
|
+
"Allowed management/control actions: list, get, status, interrupt, resume, append-step, doctor.",
|
|
160
160
|
"Agent config mutation actions create, update, and delete are blocked in this mode.",
|
|
161
161
|
].join("\n"),
|
|
162
162
|
parameters: SubagentParams,
|
package/src/extension/index.ts
CHANGED
|
@@ -393,6 +393,7 @@ EXECUTION (use exactly ONE mode):
|
|
|
393
393
|
• CHAIN: { chain: [{agent:"agent-a"}, {parallel:[{agent:"agent-b",count:3}]}] } - sequential pipeline with optional parallel fan-out
|
|
394
394
|
• PARALLEL: { tasks: [{agent,task,count?,output?,reads?,progress?}, ...], concurrency?: number, worktree?: true } - concurrent execution (worktree: isolate each task in a git worktree)
|
|
395
395
|
• Optional context: { context: "fresh" | "fork" } (default: if any requested agent has defaultContext: "fork", the whole invocation uses fork; otherwise "fresh"; inspect agent defaults via { action: "list" })
|
|
396
|
+
• If { action: "list" } shows proactive skill subagent suggestions, consider a small fresh-context fanout for broad tasks where one of those skills would materially help
|
|
396
397
|
|
|
397
398
|
CHAIN TEMPLATE VARIABLES (use in task strings):
|
|
398
399
|
• {task} - The original task/request from the user
|
|
@@ -412,7 +413,8 @@ MANAGEMENT (use action field, omit agent/task/chain/tasks):
|
|
|
412
413
|
CONTROL:
|
|
413
414
|
• { action: "status", id: "..." } - inspect an async/background run by id or prefix
|
|
414
415
|
• { action: "interrupt", id?: "..." } - soft-interrupt the current child turn and leave the run paused
|
|
415
|
-
• { action: "resume", id: "...", message: "...", index?: 0 } - follow up with a live async child or revive a completed async/foreground child from its session
|
|
416
|
+
• { action: "resume", id: "...", message: "...", index?: 0 } - interrupt then follow up with a live async child, or revive a completed async/foreground child from its session
|
|
417
|
+
• { action: "append-step", id: "...", chain: [{agent:"agent-c", task:"Use {previous}"}] } - append one step to the tail of a running async chain
|
|
416
418
|
|
|
417
419
|
DIAGNOSTICS:
|
|
418
420
|
• { action: "doctor" } - read-only report for runtime paths, discovery, sessions, and intercom`,
|
package/src/extension/schemas.ts
CHANGED
|
@@ -5,6 +5,31 @@
|
|
|
5
5
|
import { Type } from "typebox";
|
|
6
6
|
import { SUBAGENT_ACTIONS } from "../shared/types.ts";
|
|
7
7
|
|
|
8
|
+
function keepTopLevelParameterDescriptions<T>(schema: T): T {
|
|
9
|
+
return pruneNestedDescriptions(schema, []) as T;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function pruneNestedDescriptions(value: unknown, path: string[]): unknown {
|
|
13
|
+
if (!value || typeof value !== "object") return value;
|
|
14
|
+
|
|
15
|
+
const result = Array.isArray(value) ? [] : Object.create(Object.getPrototypeOf(value));
|
|
16
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
17
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
18
|
+
if (!descriptor) continue;
|
|
19
|
+
if (key === "description" && !isTopLevelParameterDescription(path)) continue;
|
|
20
|
+
if ("value" in descriptor) {
|
|
21
|
+
const nextPath = typeof key === "string" ? [...path, key] : path;
|
|
22
|
+
descriptor.value = pruneNestedDescriptions(descriptor.value, nextPath);
|
|
23
|
+
}
|
|
24
|
+
Object.defineProperty(result, key, descriptor);
|
|
25
|
+
}
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function isTopLevelParameterDescription(path: string[]): boolean {
|
|
30
|
+
return path.length === 2 && path[0] === "properties";
|
|
31
|
+
}
|
|
32
|
+
|
|
8
33
|
const SkillOverride = Type.Unsafe({
|
|
9
34
|
anyOf: [
|
|
10
35
|
{ type: "array", items: { type: "string" } },
|
|
@@ -233,7 +258,7 @@ const ControlOverrides = Type.Object({
|
|
|
233
258
|
})),
|
|
234
259
|
});
|
|
235
260
|
|
|
236
|
-
|
|
261
|
+
const SubagentParamsSchema = Type.Object({
|
|
237
262
|
agent: Type.Optional(Type.String({ description: "Agent name (SINGLE mode) or target for management get/update/delete" })),
|
|
238
263
|
task: Type.Optional(Type.String({ description: "Task (SINGLE mode, optional for self-contained agents)" })),
|
|
239
264
|
// Management action (when present, tool operates in management mode)
|
|
@@ -242,10 +267,10 @@ export const SubagentParams = Type.Object({
|
|
|
242
267
|
description: "Management/control action. Omit for execution mode."
|
|
243
268
|
})),
|
|
244
269
|
id: Type.Optional(Type.String({
|
|
245
|
-
description: "Run id or prefix for action='status', action='interrupt', or action='
|
|
270
|
+
description: "Run id or prefix for action='status', action='interrupt', action='resume', or action='append-step'."
|
|
246
271
|
})),
|
|
247
272
|
runId: Type.Optional(Type.String({
|
|
248
|
-
description: "Target run ID for action='interrupt' or action='
|
|
273
|
+
description: "Target run ID for action='interrupt', action='resume', or action='append-step'. Defaults to the most recently active controllable run for interrupt. Prefer id for new calls."
|
|
249
274
|
})),
|
|
250
275
|
dir: Type.Optional(Type.String({
|
|
251
276
|
description: "Async run directory for action='status' or action='resume'."
|
|
@@ -262,7 +287,7 @@ export const SubagentParams = Type.Object({
|
|
|
262
287
|
{ type: "object", additionalProperties: true },
|
|
263
288
|
{ type: "string" },
|
|
264
289
|
],
|
|
265
|
-
description: "Agent or chain config for create/update. Agent: name, package (optional namespace; runtime name becomes package.name), description, scope ('user'|'project', default 'user'), systemPrompt, systemPromptMode, inheritProjectContext, inheritSkills, defaultContext ('fresh'|'fork'), model, tools (comma-separated), extensions (comma-separated), skills (comma-separated), thinking, output, reads, progress, maxSubagentDepth. Chain: name, package, description, scope, steps (array of {agent, task?, output?, outputMode?, reads?, model?, skill?, progress?}). Presence of 'steps' creates a chain instead of an agent. String values must be valid JSON."
|
|
290
|
+
description: "Agent or chain config for create/update. Agent: name, package (optional namespace; runtime name becomes package.name), description, scope ('user'|'project', default 'user'), systemPrompt, systemPromptMode, inheritProjectContext, inheritSkills, defaultContext ('fresh'|'fork'), model, tools (comma-separated), extensions (comma-separated), subagentOnlyExtensions (comma-separated child-only extension paths), skills (comma-separated), thinking, output, reads, progress, maxSubagentDepth. Chain: name, package, description, scope, steps (array of {agent, task?, output?, outputMode?, reads?, model?, skill?, progress?}). Presence of 'steps' creates a chain instead of an agent. String values must be valid JSON."
|
|
266
291
|
})),
|
|
267
292
|
tasks: Type.Optional(Type.Array(TaskItem, { description: "PARALLEL mode: [{agent, task, count?, output?, outputMode?, reads?, progress?}, ...]" })),
|
|
268
293
|
concurrency: Type.Optional(Type.Integer({ minimum: 1, description: "Top-level PARALLEL mode only: max concurrent tasks. Defaults to config.parallel.concurrency or 4." })),
|
|
@@ -271,7 +296,7 @@ export const SubagentParams = Type.Object({
|
|
|
271
296
|
"Prevents filesystem conflicts. Requires clean git state. " +
|
|
272
297
|
"Per-worktree diffs included in output."
|
|
273
298
|
})),
|
|
274
|
-
chain: Type.Optional(Type.Array(ChainItem, { description: "CHAIN mode: sequential pipeline where each step's response becomes {previous} for the next.
|
|
299
|
+
chain: Type.Optional(Type.Array(ChainItem, { description: "CHAIN mode: sequential pipeline where each step's response becomes {previous} for the next. With action='append-step', provide exactly one step to append to an active async chain; it can use {previous}, {chain_dir}, and existing {outputs.name} references." })),
|
|
275
300
|
context: Type.Optional(Type.String({
|
|
276
301
|
enum: ["fresh", "fork"],
|
|
277
302
|
description: "'fresh' or 'fork' to branch from parent session. If omitted, any requested agent with defaultContext: 'fork' makes the whole invocation forked; otherwise the default is 'fresh'.",
|
|
@@ -302,3 +327,5 @@ export const SubagentParams = Type.Object({
|
|
|
302
327
|
model: Type.Optional(Type.String({ description: "Override model for single agent (e.g. 'anthropic/claude-sonnet-4')" })),
|
|
303
328
|
acceptance: Type.Optional(AcceptanceOverride),
|
|
304
329
|
});
|
|
330
|
+
|
|
331
|
+
export const SubagentParams = keepTopLevelParameterDescriptions(SubagentParamsSchema);
|