pi-subagents 0.58.0 → 0.60.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 +102 -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 +93 -5
- package/docs/workflows.md +94 -3
- package/package.json +3 -1
- package/prompts/review-loop.md +2 -2
- package/skills/council-mode/SKILL.md +48 -243
- package/skills/council-mode/references/pass-contracts.md +150 -0
- package/skills/pi-subagents/SKILL.md +87 -37
- package/skills/pi-subagents/references/constraints-and-recipes.md +29 -233
- package/skills/pi-subagents/references/execution-controls.md +52 -7
- package/skills/pi-subagents/references/management-authoring-rpc.md +3 -4
- package/skills/pi-subagents/references/multi-lane-orchestration.md +13 -1
- package/skills/pi-subagents/references/prompting-and-roles.md +54 -26
- package/skills/pi-subagents/references/review-and-validation.md +73 -0
- package/src/agents/agent-management.ts +159 -42
- 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/api/shared-types.ts +2 -0
- package/src/extension/fanout-child.ts +5 -3
- package/src/extension/index.ts +51 -23
- package/src/extension/public-execution.ts +16 -2
- package/src/extension/schemas.ts +36 -10
- package/src/extension/tool-description.ts +24 -5
- 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 +52 -37
- package/src/runs/background/async-job-tracker.ts +17 -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 +110 -8
- package/src/runs/background/chain-append.ts +6 -3
- package/src/runs/background/chain-root-attachment.ts +60 -8
- package/src/runs/background/control-channel.ts +3 -2
- 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 +30 -9
- 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 +351 -358
- package/src/runs/background/subagent-wait.ts +68 -12
- package/src/runs/background/terminal-run-index.ts +1 -1
- package/src/runs/background/wait-completions.ts +25 -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 +125 -122
- 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 +576 -240
- 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 +597 -0
- package/src/runs/shared/background-process-options.ts +9 -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 +194 -0
- package/src/runs/shared/model-fallback.ts +9 -2
- package/src/runs/shared/mutation-evidence.ts +52 -3
- 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 +67 -3
- package/src/runs/shared/single-output.ts +72 -22
- package/src/runs/shared/subagent-prompt-runtime.ts +33 -6
- package/src/runs/shared/workflow-graph.ts +15 -0
- 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 +232 -30
- 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 +134 -75
- package/src/tui/fleet.ts +11 -5
- package/src/tui/render-helpers.ts +31 -0
- package/src/tui/render.ts +895 -121
- package/src/watchdog/change-signature.ts +40 -1
- package/src/watchdog/turn-delta.ts +1 -1
- package/src/workflows/chat-progress.ts +6 -3
- package/src/workflows/host-command.ts +235 -0
- package/src/workflows/scripted-workflow.ts +503 -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
|
@@ -31,11 +31,11 @@ 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
|
-
import { CODE_OWNED_EXTERNAL_CLI_ADAPTER_LABEL, isCodeOwnedExternalCliAdapterId, validateCodeOwnedProfileRunner } from "../runs/shared/external-cli-contract.ts";
|
|
37
|
-
import type { AcceptanceInput, Details, ExtensionConfig, ToolBudgetConfig } from "../shared/types.ts";
|
|
35
|
+
import { CODE_OWNED_EXTERNAL_CLI_ADAPTER_LABEL, isCodeOwnedExternalCliAdapterId, resolveExternalCliRunnerStatus, validateCodeOwnedProfileRunner } from "../runs/shared/external-cli-contract.ts";
|
|
36
|
+
import type { AcceptanceInput, AgentCapabilitiesSnapshot, AgentCapabilityRow, Details, ExtensionConfig, ToolBudgetConfig } from "../shared/types.ts";
|
|
38
37
|
import { getProjectConfigDir } from "../shared/utils.ts";
|
|
38
|
+
import { previewDisplayText } from "../shared/display-text.ts";
|
|
39
39
|
import { capabilityCeilingAgentRestrictionSources, isAgentAllowedByCapabilityCeiling, resolveCurrentSubagentCapabilityCeiling } from "../runs/shared/capability-ceiling.ts";
|
|
40
40
|
import { listRuntimeAgentConfigs, mergeRuntimeAgents, type RuntimeAgentOwner } from "./runtime-agent-registry.ts";
|
|
41
41
|
import { listExternalJobProviders } from "../api/external-job-provider.ts";
|
|
@@ -48,11 +48,21 @@ interface ManagementParams {
|
|
|
48
48
|
action?: string;
|
|
49
49
|
agent?: string;
|
|
50
50
|
agentScope?: unknown;
|
|
51
|
+
capabilities?: unknown;
|
|
51
52
|
config?: unknown;
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
function result(text: string, isError = false): AgentToolResult<Details> {
|
|
55
|
-
return { content: [{ type: "text", text }], isError, details: { mode: "management", results: [] } };
|
|
55
|
+
function result(text: string, isError = false, details?: Partial<Details>): AgentToolResult<Details> {
|
|
56
|
+
return { content: [{ type: "text", text }], isError, details: { mode: "management", results: [], ...details } };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function jsonDetails<T>(value: T): T {
|
|
60
|
+
return JSON.parse(JSON.stringify(value)) as T;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function presentDetails<T extends Record<string, unknown>>(value: T): T | undefined {
|
|
64
|
+
const cleaned = jsonDetails(value);
|
|
65
|
+
return Object.keys(cleaned).length > 0 ? cleaned : undefined;
|
|
56
66
|
}
|
|
57
67
|
|
|
58
68
|
function parseCsv(value: string): string[] {
|
|
@@ -331,7 +341,6 @@ export function preservedAgentFrontmatterFields(agent: AgentConfig, cfg: Record<
|
|
|
331
341
|
if (hasKey(cfg, "defaultContext")) changed("defaultContext");
|
|
332
342
|
if (hasKey(cfg, "async")) changed("async");
|
|
333
343
|
if (hasKey(cfg, "timeoutMs")) changed("timeoutMs");
|
|
334
|
-
if (hasKey(cfg, "turnBudget")) changed("turnBudget");
|
|
335
344
|
if (hasKey(cfg, "acceptance")) changed("acceptance");
|
|
336
345
|
if (hasKey(cfg, "acceptanceRole")) changed("acceptanceRole");
|
|
337
346
|
if (hasKey(cfg, "output")) changed("output");
|
|
@@ -511,15 +520,6 @@ function applyAgentConfig(target: AgentConfig, cfg: Record<string, unknown>): st
|
|
|
511
520
|
else if (typeof cfg.timeoutMs === "number" && Number.isInteger(cfg.timeoutMs) && cfg.timeoutMs > 0) target.defaultTimeoutMs = cfg.timeoutMs;
|
|
512
521
|
else return "config.timeoutMs must be a positive integer or false when provided.";
|
|
513
522
|
}
|
|
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
523
|
if (hasKey(cfg, "acceptance")) {
|
|
524
524
|
if (cfg.acceptance === "") delete target.defaultAcceptance;
|
|
525
525
|
else {
|
|
@@ -656,18 +656,135 @@ function runnerListBadge(agent: AgentConfig, providerNames: Set<string> | undefi
|
|
|
656
656
|
return undefined;
|
|
657
657
|
}
|
|
658
658
|
|
|
659
|
-
function
|
|
659
|
+
function agentListMetadata(agent: AgentConfig, providerNames: Set<string> | undefined): string {
|
|
660
660
|
const source = agent.source === "package" ? packageSourceLabel(agent) : agent.source;
|
|
661
|
-
|
|
661
|
+
return [
|
|
662
662
|
source,
|
|
663
663
|
runnerListBadge(agent, providerNames),
|
|
664
664
|
agent.defaultContext ? `context: ${agent.defaultContext}` : undefined,
|
|
665
665
|
agent.aliases?.length ? `aliases: ${agent.aliases.join(", ")}` : undefined,
|
|
666
|
-
].filter((part): part is string => Boolean(part));
|
|
667
|
-
|
|
666
|
+
].filter((part): part is string => Boolean(part)).join(", ");
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
function formatAgentListLine(agent: AgentConfig, providerNames: Set<string> | undefined): string {
|
|
670
|
+
return `- ${agent.name} (${agentListMetadata(agent, providerNames)}): ${agent.description}`;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
function formatAgentCapabilitiesLine(agent: AgentConfig, providerNames: Set<string> | undefined): string {
|
|
674
|
+
const declaredTools = [
|
|
675
|
+
...(agent.tools ?? []),
|
|
676
|
+
...(agent.mcpDirectTools ?? []).map((tool) => `mcp:${tool}`),
|
|
677
|
+
];
|
|
678
|
+
let tools = "none";
|
|
679
|
+
if (agent.tools === undefined && agent.mcpDirectTools === undefined) {
|
|
680
|
+
tools = "default/ambient";
|
|
681
|
+
} else if (declaredTools.length > 0) {
|
|
682
|
+
tools = declaredTools.join(", ");
|
|
683
|
+
}
|
|
684
|
+
let model = "inherits current session";
|
|
685
|
+
if (agent.model !== undefined) {
|
|
686
|
+
model = agent.model;
|
|
687
|
+
if (agent.modelProvider && !agent.model.includes("/")) model = `${agent.modelProvider}/${agent.model}`;
|
|
688
|
+
}
|
|
689
|
+
const thinking = agent.thinking === false ? "off" : agent.thinking ?? "default";
|
|
690
|
+
return `- ${agent.name} (${agentListMetadata(agent, providerNames)}): Description: ${previewDisplayText(agent.description, 240)}; Tools: ${tools}; Model: ${model}; Thinking: ${thinking}`;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
const EXTERNAL_JOB_CAPABILITIES = { stop: false, steer: false, resume: false, structuredOutput: false, toolEvents: false } as const;
|
|
694
|
+
const PI_AGENT_RUNNER = { type: "pi" } as const;
|
|
695
|
+
|
|
696
|
+
function listOrEmpty<T>(values: T[] | undefined): T[] {
|
|
697
|
+
return values ?? [];
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
function agentCapabilityRunner(agent: AgentConfig, providerNames: Set<string> | undefined): AgentCapabilityRow["runner"] {
|
|
701
|
+
const runner = agent.runner;
|
|
702
|
+
if (!runner || runner.type === "pi") return PI_AGENT_RUNNER;
|
|
703
|
+
if (runner.type === "external-cli") return { type: "external-cli", adapter: runner.adapter, capabilities: resolveExternalCliRunnerStatus(runner).capabilities };
|
|
704
|
+
return { type: "external-job", provider: runner.provider, available: providerNames?.has(runner.provider), capabilities: EXTERNAL_JOB_CAPABILITIES };
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
function agentCapabilityTools(agent: AgentConfig): AgentCapabilityRow["tools"] {
|
|
708
|
+
return {
|
|
709
|
+
ambient: agent.tools === undefined && agent.mcpDirectTools === undefined,
|
|
710
|
+
names: listOrEmpty(agent.tools),
|
|
711
|
+
mcpDirectTools: listOrEmpty(agent.mcpDirectTools),
|
|
712
|
+
mutationTools: agent.mutationTools,
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
function agentCapabilityRow(agent: AgentConfig, options: { executable: boolean; providerNames?: Set<string>; restrictionSources?: string[] }): AgentCapabilityRow {
|
|
717
|
+
return {
|
|
718
|
+
name: agent.name,
|
|
719
|
+
description: previewDisplayText(agent.description, 1000),
|
|
720
|
+
source: agent.source,
|
|
721
|
+
executable: options.executable,
|
|
722
|
+
restrictionSources: options.executable ? undefined : options.restrictionSources ?? [],
|
|
723
|
+
aliases: agent.aliases ? [...agent.aliases] : undefined,
|
|
724
|
+
runner: agentCapabilityRunner(agent, options.providerNames),
|
|
725
|
+
tools: agentCapabilityTools(agent),
|
|
726
|
+
model: presentDetails({ value: agent.model, fallbackModels: agent.fallbackModels, thinking: agent.thinking }),
|
|
727
|
+
execution: presentDetails({ defaultAsync: agent.defaultAsync, timeoutMs: agent.defaultTimeoutMs }),
|
|
728
|
+
output: presentDetails({ path: agent.output, mode: agent.outputMode }),
|
|
729
|
+
extensions: presentDetails({ names: agent.extensions, subagentOnly: agent.subagentOnlyExtensions, skills: agent.skills }),
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function agentCapabilitiesSnapshot(input: { agents: AgentConfig[]; restrictedAgents: AgentConfig[]; providerNames?: Set<string>; restrictedSources?: string[] }): AgentCapabilitiesSnapshot {
|
|
734
|
+
return {
|
|
735
|
+
agents: [
|
|
736
|
+
...input.agents.map((agent) => agentCapabilityRow(agent, { executable: true, providerNames: input.providerNames })),
|
|
737
|
+
...input.restrictedAgents.map((agent) => agentCapabilityRow(agent, { executable: false, providerNames: input.providerNames, restrictionSources: input.restrictedSources })),
|
|
738
|
+
],
|
|
739
|
+
restrictedCount: input.restrictedAgents.length,
|
|
740
|
+
...(input.restrictedSources?.length ? { capabilityCeilingSources: [...input.restrictedSources] } : {}),
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
function providerNames(status: ExternalJobProviderStatus): Set<string> | undefined {
|
|
745
|
+
return status.ok ? status.names : undefined;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
function appendRestrictedAgentLines(input: { lines: string[]; agents: AgentConfig[]; sources?: string[]; providerNames?: Set<string>; formatLine: (agent: AgentConfig, providerNames: Set<string> | undefined) => string }): void {
|
|
749
|
+
if (input.agents.length === 0) return;
|
|
750
|
+
input.lines.push(
|
|
751
|
+
"",
|
|
752
|
+
`Restricted agents (not executable in this session${input.sources?.length ? `; capability ceiling: ${input.sources.join(", ")}` : ""}):`,
|
|
753
|
+
...input.agents.map((agent) => input.formatLine(agent, input.providerNames)),
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
function appendExternalJobRegistryLine(lines: string[], agents: AgentConfig[], status: ExternalJobProviderStatus): void {
|
|
758
|
+
if (status.ok || !agents.some((agent) => agent.runner?.type === "external-job")) return;
|
|
759
|
+
lines.push("", `External-job provider registry unavailable: ${status.error}`);
|
|
668
760
|
}
|
|
669
761
|
|
|
670
|
-
function
|
|
762
|
+
function appendAgentDiagnosticLines(lines: string[], diagnostics: AgentDiscoveryDiagnostic[] | undefined): void {
|
|
763
|
+
if (!diagnostics?.length) return;
|
|
764
|
+
lines.push(
|
|
765
|
+
"",
|
|
766
|
+
"Invalid agent definitions:",
|
|
767
|
+
...diagnostics.map((diagnostic) => `- ${diagnostic.name ?? diagnostic.filePath} (${diagnostic.source}): ${diagnostic.error}`),
|
|
768
|
+
);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
function agentCapabilityDetails(input: { capabilityMode: boolean; agents: AgentConfig[]; restrictedAgents: AgentConfig[]; providerNames?: Set<string>; restrictedSources?: string[] }): Partial<Details> | undefined {
|
|
772
|
+
if (!input.capabilityMode) return undefined;
|
|
773
|
+
return {
|
|
774
|
+
agentCapabilities: jsonDetails(agentCapabilitiesSnapshot({
|
|
775
|
+
agents: input.agents,
|
|
776
|
+
restrictedAgents: input.restrictedAgents,
|
|
777
|
+
providerNames: input.providerNames,
|
|
778
|
+
restrictedSources: input.restrictedSources,
|
|
779
|
+
})),
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
function formatAgentListSections(
|
|
784
|
+
agents: AgentConfig[],
|
|
785
|
+
providerNames: Set<string> | undefined,
|
|
786
|
+
formatLine: (agent: AgentConfig, providerNames: Set<string> | undefined) => string = formatAgentListLine,
|
|
787
|
+
): string[] {
|
|
671
788
|
if (agents.length === 0) return ["- (none)"];
|
|
672
789
|
const sections: Array<[AgentSource, string]> = [
|
|
673
790
|
["package", "Package agents"],
|
|
@@ -681,7 +798,7 @@ function formatAgentListSections(agents: AgentConfig[], providerNames: Set<strin
|
|
|
681
798
|
const matches = agents.filter((agent) => agent.source === source);
|
|
682
799
|
if (matches.length === 0) continue;
|
|
683
800
|
if (lines.length > 0) lines.push("");
|
|
684
|
-
lines.push(label, ...matches.map((agent) =>
|
|
801
|
+
lines.push(label, ...matches.map((agent) => formatLine(agent, providerNames)));
|
|
685
802
|
}
|
|
686
803
|
return lines;
|
|
687
804
|
}
|
|
@@ -724,7 +841,6 @@ function formatAgentDetail(agent: AgentConfig): string {
|
|
|
724
841
|
if (agent.defaultContext) lines.push(`Default context: ${agent.defaultContext}`);
|
|
725
842
|
if (agent.defaultAsync !== undefined) lines.push(`Async: ${agent.defaultAsync ? "true" : "false"}`);
|
|
726
843
|
if (agent.defaultTimeoutMs !== undefined) lines.push(`Timeout: ${agent.defaultTimeoutMs}ms`);
|
|
727
|
-
if (agent.defaultTurnBudget) lines.push(`Turn budget: ${JSON.stringify(agent.defaultTurnBudget)}`);
|
|
728
844
|
if (agent.defaultAcceptance !== undefined) lines.push(`Acceptance: ${typeof agent.defaultAcceptance === "object" ? JSON.stringify(agent.defaultAcceptance) : String(agent.defaultAcceptance)}`);
|
|
729
845
|
if (agent.acceptanceRole) lines.push(`Acceptance role: ${agent.acceptanceRole}`);
|
|
730
846
|
if (agent.source === "builtin") lines.push(`Disabled: ${agent.disabled ? "true" : "false"}`);
|
|
@@ -746,7 +862,7 @@ function formatAgentDetail(agent: AgentConfig): string {
|
|
|
746
862
|
|
|
747
863
|
export function handleList(params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {
|
|
748
864
|
const scope = normalizeListScope(params.agentScope) ?? "both";
|
|
749
|
-
const d = discoverAgentsAll(ctx.cwd);
|
|
865
|
+
const d = discoverAgentsAll(ctx.cwd, ctx.model?.provider);
|
|
750
866
|
let scopedAgents = mergeAgentsForScope(scope, d.user, d.project, d.builtin, d.package);
|
|
751
867
|
if (ctx.runtimeAgentOwner && listRuntimeAgentConfigs(ctx.runtimeAgentOwner).length > 0) {
|
|
752
868
|
const configuredAgents: AgentConfig[] = [
|
|
@@ -770,23 +886,24 @@ export function handleList(params: ManagementParams, ctx: ManagementContext): Ag
|
|
|
770
886
|
discoverAvailableSkills: () => discoverAvailableSkills(ctx.cwd),
|
|
771
887
|
});
|
|
772
888
|
const providerStatus = registeredExternalJobProviderStatus();
|
|
889
|
+
const providerNameSet = providerNames(providerStatus);
|
|
890
|
+
const capabilityMode = params.capabilities === true;
|
|
891
|
+
const formatLine = capabilityMode ? formatAgentCapabilitiesLine : formatAgentListLine;
|
|
773
892
|
const lines = [
|
|
774
|
-
"Executable agents:",
|
|
775
|
-
...formatAgentListSections(agents,
|
|
776
|
-
...(restrictedAgents.length ? [
|
|
777
|
-
"",
|
|
778
|
-
`Restricted agents (not executable in this session${restrictedSources?.length ? `; capability ceiling: ${restrictedSources.join(", ")}` : ""}):`,
|
|
779
|
-
...restrictedAgents.map((a) => formatAgentListLine(a, providerStatus.ok ? providerStatus.names : undefined)),
|
|
780
|
-
] : []),
|
|
781
|
-
...(!providerStatus.ok && [...agents, ...restrictedAgents].some((agent) => agent.runner?.type === "external-job") ? ["", `External-job provider registry unavailable: ${providerStatus.error}`] : []),
|
|
782
|
-
...(d.agentDiagnostics?.length ? [
|
|
783
|
-
"",
|
|
784
|
-
"Invalid agent definitions:",
|
|
785
|
-
...d.agentDiagnostics.map((diagnostic) => `- ${diagnostic.name ?? diagnostic.filePath} (${diagnostic.source}): ${diagnostic.error}`),
|
|
786
|
-
] : []),
|
|
787
|
-
...(proactiveSuggestions.length ? ["", ...proactiveSuggestions] : []),
|
|
893
|
+
capabilityMode ? "Executable agents (capabilities):" : "Executable agents:",
|
|
894
|
+
...formatAgentListSections(agents, providerNameSet, formatLine),
|
|
788
895
|
];
|
|
789
|
-
|
|
896
|
+
appendRestrictedAgentLines({ lines, agents: restrictedAgents, sources: restrictedSources, providerNames: providerNameSet, formatLine });
|
|
897
|
+
appendExternalJobRegistryLine(lines, [...agents, ...restrictedAgents], providerStatus);
|
|
898
|
+
appendAgentDiagnosticLines(lines, d.agentDiagnostics);
|
|
899
|
+
if (proactiveSuggestions.length) lines.push("", ...proactiveSuggestions);
|
|
900
|
+
return result(lines.join("\n"), false, agentCapabilityDetails({
|
|
901
|
+
capabilityMode,
|
|
902
|
+
agents,
|
|
903
|
+
restrictedAgents,
|
|
904
|
+
providerNames: providerNameSet,
|
|
905
|
+
restrictedSources,
|
|
906
|
+
}));
|
|
790
907
|
}
|
|
791
908
|
|
|
792
909
|
function formatModelSource(agent: AgentConfig, currentModel: ParentModel | undefined): string {
|
|
@@ -807,7 +924,7 @@ function handleModels(params: ManagementParams, ctx: ManagementContext): AgentTo
|
|
|
807
924
|
return result(`Builtin agent '${requestedAgent}' not found. Available: ${BUILTIN_AGENT_NAMES.join(", ")}.`, true);
|
|
808
925
|
}
|
|
809
926
|
|
|
810
|
-
const discovered = discoverAgentsAll(ctx.cwd);
|
|
927
|
+
const discovered = discoverAgentsAll(ctx.cwd, ctx.model?.provider);
|
|
811
928
|
const builtinByName = new Map(discovered.builtin.map((agent) => [agent.name, agent]));
|
|
812
929
|
const resolveBuiltinModelAgent = (name: string): AgentConfig | undefined => builtinByName.get(name) ?? resolveAgentName(name, discovered.builtin).agent;
|
|
813
930
|
const availableModels = ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
@@ -886,7 +1003,7 @@ function handleGet(params: ManagementParams, ctx: ManagementContext): AgentToolR
|
|
|
886
1003
|
if (!params.agent) return result("Specify 'agent' for get.", true);
|
|
887
1004
|
const scope = normalizeListScope(params.agentScope);
|
|
888
1005
|
if (!scope) return result("agentScope must be 'user', 'project', or 'both' for get.", true);
|
|
889
|
-
const discovered = discoverAgentsAll(ctx.cwd);
|
|
1006
|
+
const discovered = discoverAgentsAll(ctx.cwd, ctx.model?.provider);
|
|
890
1007
|
const matches = findAgentsInDiscovery(params.agent, discovered, scope);
|
|
891
1008
|
const diagnostics = diagnosticsForScope(discovered.agentDiagnostics, scope);
|
|
892
1009
|
const rawName = params.agent.trim();
|
|
@@ -916,7 +1033,7 @@ export function handleCreate(params: ManagementParams, ctx: ManagementContext):
|
|
|
916
1033
|
const runtimeName = buildRuntimeName(name, parsedPackage.packageName);
|
|
917
1034
|
const scopeRaw = cfg.scope ?? "user";
|
|
918
1035
|
if (scopeRaw !== "user" && scopeRaw !== "project") return result("config.scope must be 'user' or 'project'.", true);
|
|
919
|
-
const scope = scopeRaw
|
|
1036
|
+
const scope = scopeRaw;
|
|
920
1037
|
if (hasKey(cfg, "steps")) return result("Durable chain definitions were removed; use workflowScript or /prompt-workflow for repeatable workflows.", true);
|
|
921
1038
|
const d = discoverAgentsAll(ctx.cwd);
|
|
922
1039
|
const projectConfigDir = getProjectConfigDir(ctx.cwd);
|
|
@@ -1164,7 +1281,7 @@ function handleReset(params: ManagementParams, ctx: ManagementContext): AgentToo
|
|
|
1164
1281
|
}
|
|
1165
1282
|
|
|
1166
1283
|
export function handleManagementAction(action: string, params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {
|
|
1167
|
-
switch (action
|
|
1284
|
+
switch (action) {
|
|
1168
1285
|
case "list": return handleList(params, ctx);
|
|
1169
1286
|
case "get": return handleGet(params, ctx);
|
|
1170
1287
|
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);
|