pi-subagents 0.59.0 → 0.61.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.
Files changed (72) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/docs/agents.md +2 -2
  3. package/docs/configuration.md +9 -5
  4. package/docs/extension-api.md +14 -7
  5. package/docs/models.md +1 -1
  6. package/docs/observability.md +1 -1
  7. package/docs/tool-reference.md +15 -3
  8. package/docs/workflows.md +15 -14
  9. package/install.mjs +2 -1
  10. package/package.json +1 -1
  11. package/skills/council-mode/SKILL.md +48 -243
  12. package/skills/council-mode/references/pass-contracts.md +150 -0
  13. package/skills/pi-subagents/SKILL.md +89 -37
  14. package/skills/pi-subagents/references/constraints-and-recipes.md +30 -234
  15. package/skills/pi-subagents/references/execution-controls.md +79 -7
  16. package/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
  17. package/skills/pi-subagents/references/multi-lane-orchestration.md +13 -1
  18. package/skills/pi-subagents/references/prompting-and-roles.md +35 -28
  19. package/skills/pi-subagents/references/review-and-validation.md +73 -0
  20. package/src/agents/agent-management.ts +256 -88
  21. package/src/agents/agents.ts +527 -221
  22. package/src/api/background-work.ts +7 -2
  23. package/src/api/external-runs.ts +67 -4
  24. package/src/api/preflight.ts +13 -8
  25. package/src/api/shared-types.ts +3 -0
  26. package/src/extension/index.ts +7 -4
  27. package/src/extension/public-execution.ts +48 -4
  28. package/src/extension/rpc.ts +62 -4
  29. package/src/extension/schemas.ts +12 -7
  30. package/src/extension/tool-description.ts +16 -16
  31. package/src/runs/background/async-execution.ts +53 -37
  32. package/src/runs/background/async-job-tracker.ts +65 -3
  33. package/src/runs/background/async-resume.ts +3 -1
  34. package/src/runs/background/async-status.ts +104 -11
  35. package/src/runs/background/auto-drain.ts +1 -1
  36. package/src/runs/background/control-channel.ts +3 -2
  37. package/src/runs/background/fleet-view.ts +1 -1
  38. package/src/runs/background/result-watcher.ts +1 -1
  39. package/src/runs/background/resume-guidance.ts +1 -1
  40. package/src/runs/background/run-status.ts +15 -4
  41. package/src/runs/background/subagent-runner.ts +6 -3
  42. package/src/runs/background/subagent-wait.ts +30 -23
  43. package/src/runs/background/wait-completions.ts +4 -1
  44. package/src/runs/background/wait-tool.ts +24 -18
  45. package/src/runs/foreground/execution.ts +72 -4
  46. package/src/runs/foreground/subagent-executor.ts +276 -59
  47. package/src/runs/shared/acceptance.ts +43 -18
  48. package/src/runs/shared/async-status-projection.ts +138 -4
  49. package/src/runs/shared/background-process-options.ts +9 -0
  50. package/src/runs/shared/host-step-status.ts +1 -0
  51. package/src/runs/shared/mcp-direct-tool-grant.ts +2 -5
  52. package/src/runs/shared/model-fallback.ts +61 -17
  53. package/src/runs/shared/mutation-evidence.ts +52 -3
  54. package/src/runs/shared/permissions.ts +1 -1
  55. package/src/runs/shared/pi-args.ts +47 -1
  56. package/src/runs/shared/single-output.ts +45 -18
  57. package/src/runs/shared/subagent-prompt-runtime.ts +20 -2
  58. package/src/runs/shared/tool-timeout.ts +1 -1
  59. package/src/runs/shared/workflow-graph.ts +16 -0
  60. package/src/shared/types.ts +74 -4
  61. package/src/shared/workflow-child-permit.ts +91 -0
  62. package/src/slash/prompt-template-bridge.ts +37 -1
  63. package/src/slash/slash-commands.ts +18 -26
  64. package/src/tui/fleet-status.ts +11 -3
  65. package/src/tui/render-helpers.ts +31 -0
  66. package/src/tui/render.ts +673 -145
  67. package/src/watchdog/change-signature.ts +40 -1
  68. package/src/workflows/host-command.ts +6 -1
  69. package/src/workflows/scripted-workflow.ts +206 -6
  70. package/src/workflows/workflow-child-summary.ts +1 -1
  71. package/src/workflows/workflow-receipt.ts +41 -4
  72. package/src/workflows/workflow-resources.ts +150 -0
@@ -7,7 +7,6 @@ import {
7
7
  type AgentDiscoveryDiagnostic,
8
8
  type AgentScope,
9
9
  type AgentSource,
10
- BUILTIN_AGENT_NAMES,
11
10
  defaultInheritProjectContext,
12
11
  defaultInheritSkills,
13
12
  defaultSystemPromptMode,
@@ -28,15 +27,16 @@ import {
28
27
  buildProactiveSkillSubagentRecommendationLines,
29
28
  } from "./proactive-skills.ts";
30
29
  import { parseFrontmatter, parseFrontmatterList } from "./frontmatter.ts";
31
- import { toModelInfo } from "../shared/model-info.ts";
30
+ import { resolveEffectiveThinking, toModelInfo } from "../shared/model-info.ts";
32
31
  import { resolveSubagentModelOverride, type ParentModel } from "../runs/shared/model-fallback.ts";
33
32
  import { validateToolBudgetConfig } from "../runs/shared/tool-budget.ts";
34
33
  import { validateAcceptanceInput } from "../runs/shared/acceptance.ts";
35
- import { CODE_OWNED_EXTERNAL_CLI_ADAPTER_LABEL, isCodeOwnedExternalCliAdapterId, validateCodeOwnedProfileRunner } from "../runs/shared/external-cli-contract.ts";
36
- import type { AcceptanceInput, Details, ExtensionConfig, ToolBudgetConfig } from "../shared/types.ts";
34
+ import { CODE_OWNED_EXTERNAL_CLI_ADAPTER_LABEL, isCodeOwnedExternalCliAdapterId, resolveExternalCliRunnerStatus, validateCodeOwnedProfileRunner } from "../runs/shared/external-cli-contract.ts";
35
+ import type { AcceptanceInput, AgentCapabilitiesSnapshot, AgentCapabilityRow, Details, ExtensionConfig, ToolBudgetConfig } from "../shared/types.ts";
37
36
  import { getProjectConfigDir } from "../shared/utils.ts";
37
+ import { previewDisplayText } from "../shared/display-text.ts";
38
38
  import { capabilityCeilingAgentRestrictionSources, isAgentAllowedByCapabilityCeiling, resolveCurrentSubagentCapabilityCeiling } from "../runs/shared/capability-ceiling.ts";
39
- import { listRuntimeAgentConfigs, mergeRuntimeAgents, type RuntimeAgentOwner } from "./runtime-agent-registry.ts";
39
+ import { mergeRuntimeAgents, type RuntimeAgentOwner } from "./runtime-agent-registry.ts";
40
40
  import { listExternalJobProviders } from "../api/external-job-provider.ts";
41
41
 
42
42
  type ManagementAction = "list" | "get" | "models" | "create" | "update" | "delete" | "eject" | "disable" | "enable" | "reset";
@@ -47,11 +47,21 @@ interface ManagementParams {
47
47
  action?: string;
48
48
  agent?: string;
49
49
  agentScope?: unknown;
50
+ capabilities?: unknown;
50
51
  config?: unknown;
51
52
  }
52
53
 
53
- function result(text: string, isError = false): AgentToolResult<Details> {
54
- return { content: [{ type: "text", text }], isError, details: { mode: "management", results: [] } };
54
+ function result(text: string, isError = false, details?: Partial<Details>): AgentToolResult<Details> {
55
+ return { content: [{ type: "text", text }], isError, details: { mode: "management", results: [], ...details } };
56
+ }
57
+
58
+ function jsonDetails<T>(value: T): T {
59
+ return JSON.parse(JSON.stringify(value)) as T;
60
+ }
61
+
62
+ function presentDetails<T extends Record<string, unknown>>(value: T): T | undefined {
63
+ const cleaned = jsonDetails(value);
64
+ return Object.keys(cleaned).length > 0 ? cleaned : undefined;
55
65
  }
56
66
 
57
67
  function parseCsv(value: string): string[] {
@@ -118,18 +128,39 @@ function allAgents(d: { builtin: AgentConfig[]; package: AgentConfig[]; user: Ag
118
128
  return [...d.builtin, ...d.package, ...d.user, ...d.project];
119
129
  }
120
130
 
121
- function availableAgentNamesFromDiscovery(d: { builtin: AgentConfig[]; package: AgentConfig[]; user: AgentConfig[]; project: AgentConfig[] }): string[] {
122
- return [...new Set(allAgents(d).map((agent) => agent.name))].sort((a, b) => a.localeCompare(b));
131
+ function effectiveAgentsForScope(
132
+ scope: AgentScope,
133
+ d: { builtin: AgentConfig[]; package: AgentConfig[]; user: AgentConfig[]; project: AgentConfig[] },
134
+ runtimeAgentOwner?: RuntimeAgentOwner,
135
+ ): AgentConfig[] {
136
+ let agents = mergeAgentsForScope(scope, d.user, d.project, d.builtin, d.package);
137
+ if (runtimeAgentOwner) {
138
+ agents = mergeRuntimeAgents(runtimeAgentOwner, { agents }, allAgents(d)).agents;
139
+ }
140
+ return agents;
141
+ }
142
+
143
+ function availableAgentNamesFromDiscovery(
144
+ d: { builtin: AgentConfig[]; package: AgentConfig[]; user: AgentConfig[]; project: AgentConfig[] },
145
+ runtimeAgentOwner?: RuntimeAgentOwner,
146
+ ): string[] {
147
+ const agents = runtimeAgentOwner ? effectiveAgentsForScope("both", d, runtimeAgentOwner) : allAgents(d);
148
+ return [...new Set(agents.map((agent) => agent.name))].sort((a, b) => a.localeCompare(b));
123
149
  }
124
150
 
125
151
  function availableAgentNames(cwd: string): string[] {
126
152
  return availableAgentNamesFromDiscovery(discoverAgentsAll(cwd));
127
153
  }
128
154
 
129
- function findAgentsInDiscovery(name: string, d: { builtin: AgentConfig[]; package: AgentConfig[]; user: AgentConfig[]; project: AgentConfig[] }, scope: AgentScope = "both"): AgentConfig[] {
155
+ function findAgentsInDiscovery(
156
+ name: string,
157
+ d: { builtin: AgentConfig[]; package: AgentConfig[]; user: AgentConfig[]; project: AgentConfig[] },
158
+ scope: AgentScope = "both",
159
+ runtimeAgentOwner?: RuntimeAgentOwner,
160
+ ): AgentConfig[] {
130
161
  const raw = name.trim();
131
162
  const sanitized = sanitizeName(raw);
132
- const scoped = mergeAgentsForScope(scope, d.user, d.project, d.builtin, d.package);
163
+ const scoped = effectiveAgentsForScope(scope, d, runtimeAgentOwner);
133
164
  let resolved = resolveAgentName(raw, scoped);
134
165
  if (!resolved.agent && !resolved.error && sanitized !== raw) resolved = resolveAgentName(sanitized, scoped);
135
166
  if (resolved.agent) return scoped.filter((agent) => agent.name === resolved.agent!.name).sort((a, b) => a.source.localeCompare(b.source));
@@ -645,18 +676,135 @@ function runnerListBadge(agent: AgentConfig, providerNames: Set<string> | undefi
645
676
  return undefined;
646
677
  }
647
678
 
648
- function formatAgentListLine(agent: AgentConfig, providerNames: Set<string> | undefined): string {
679
+ function agentListMetadata(agent: AgentConfig, providerNames: Set<string> | undefined): string {
649
680
  const source = agent.source === "package" ? packageSourceLabel(agent) : agent.source;
650
- const parts = [
681
+ return [
651
682
  source,
652
683
  runnerListBadge(agent, providerNames),
653
684
  agent.defaultContext ? `context: ${agent.defaultContext}` : undefined,
654
685
  agent.aliases?.length ? `aliases: ${agent.aliases.join(", ")}` : undefined,
655
- ].filter((part): part is string => Boolean(part));
656
- return `- ${agent.name} (${parts.join(", ")}): ${agent.description}`;
686
+ ].filter((part): part is string => Boolean(part)).join(", ");
687
+ }
688
+
689
+ function formatAgentListLine(agent: AgentConfig, providerNames: Set<string> | undefined): string {
690
+ return `- ${agent.name} (${agentListMetadata(agent, providerNames)}): ${agent.description}`;
691
+ }
692
+
693
+ function formatAgentCapabilitiesLine(agent: AgentConfig, providerNames: Set<string> | undefined): string {
694
+ const declaredTools = [
695
+ ...(agent.tools ?? []),
696
+ ...(agent.mcpDirectTools ?? []).map((tool) => `mcp:${tool}`),
697
+ ];
698
+ let tools = "none";
699
+ if (agent.tools === undefined && agent.mcpDirectTools === undefined) {
700
+ tools = "default/ambient";
701
+ } else if (declaredTools.length > 0) {
702
+ tools = declaredTools.join(", ");
703
+ }
704
+ let model = "inherits current session";
705
+ if (agent.model !== undefined) {
706
+ model = agent.model;
707
+ if (agent.modelProvider && !agent.model.includes("/")) model = `${agent.modelProvider}/${agent.model}`;
708
+ }
709
+ const thinking = agent.thinking === false ? "off" : agent.thinking ?? "default";
710
+ return `- ${agent.name} (${agentListMetadata(agent, providerNames)}): Description: ${previewDisplayText(agent.description, 240)}; Tools: ${tools}; Model: ${model}; Thinking: ${thinking}`;
711
+ }
712
+
713
+ const EXTERNAL_JOB_CAPABILITIES = { stop: false, steer: false, resume: false, structuredOutput: false, toolEvents: false } as const;
714
+ const PI_AGENT_RUNNER = { type: "pi" } as const;
715
+
716
+ function listOrEmpty<T>(values: T[] | undefined): T[] {
717
+ return values ?? [];
657
718
  }
658
719
 
659
- function formatAgentListSections(agents: AgentConfig[], providerNames: Set<string> | undefined): string[] {
720
+ function agentCapabilityRunner(agent: AgentConfig, providerNames: Set<string> | undefined): AgentCapabilityRow["runner"] {
721
+ const runner = agent.runner;
722
+ if (!runner || runner.type === "pi") return PI_AGENT_RUNNER;
723
+ if (runner.type === "external-cli") return { type: "external-cli", adapter: runner.adapter, capabilities: resolveExternalCliRunnerStatus(runner).capabilities };
724
+ return { type: "external-job", provider: runner.provider, available: providerNames?.has(runner.provider), capabilities: EXTERNAL_JOB_CAPABILITIES };
725
+ }
726
+
727
+ function agentCapabilityTools(agent: AgentConfig): AgentCapabilityRow["tools"] {
728
+ return {
729
+ ambient: agent.tools === undefined && agent.mcpDirectTools === undefined,
730
+ names: listOrEmpty(agent.tools),
731
+ mcpDirectTools: listOrEmpty(agent.mcpDirectTools),
732
+ mutationTools: agent.mutationTools,
733
+ };
734
+ }
735
+
736
+ function agentCapabilityRow(agent: AgentConfig, options: { executable: boolean; providerNames?: Set<string>; restrictionSources?: string[] }): AgentCapabilityRow {
737
+ return {
738
+ name: agent.name,
739
+ description: previewDisplayText(agent.description, 1000),
740
+ source: agent.source,
741
+ executable: options.executable,
742
+ restrictionSources: options.executable ? undefined : options.restrictionSources ?? [],
743
+ aliases: agent.aliases ? [...agent.aliases] : undefined,
744
+ runner: agentCapabilityRunner(agent, options.providerNames),
745
+ tools: agentCapabilityTools(agent),
746
+ model: presentDetails({ value: agent.model, fallbackModels: agent.fallbackModels, thinking: agent.thinking }),
747
+ execution: presentDetails({ defaultAsync: agent.defaultAsync, timeoutMs: agent.defaultTimeoutMs }),
748
+ output: presentDetails({ path: agent.output, mode: agent.outputMode }),
749
+ extensions: presentDetails({ names: agent.extensions, subagentOnly: agent.subagentOnlyExtensions, skills: agent.skills }),
750
+ };
751
+ }
752
+
753
+ function agentCapabilitiesSnapshot(input: { agents: AgentConfig[]; restrictedAgents: AgentConfig[]; providerNames?: Set<string>; restrictedSources?: string[] }): AgentCapabilitiesSnapshot {
754
+ return {
755
+ agents: [
756
+ ...input.agents.map((agent) => agentCapabilityRow(agent, { executable: true, providerNames: input.providerNames })),
757
+ ...input.restrictedAgents.map((agent) => agentCapabilityRow(agent, { executable: false, providerNames: input.providerNames, restrictionSources: input.restrictedSources })),
758
+ ],
759
+ restrictedCount: input.restrictedAgents.length,
760
+ ...(input.restrictedSources?.length ? { capabilityCeilingSources: [...input.restrictedSources] } : {}),
761
+ };
762
+ }
763
+
764
+ function providerNames(status: ExternalJobProviderStatus): Set<string> | undefined {
765
+ return status.ok ? status.names : undefined;
766
+ }
767
+
768
+ function appendRestrictedAgentLines(input: { lines: string[]; agents: AgentConfig[]; sources?: string[]; providerNames?: Set<string>; formatLine: (agent: AgentConfig, providerNames: Set<string> | undefined) => string }): void {
769
+ if (input.agents.length === 0) return;
770
+ input.lines.push(
771
+ "",
772
+ `Restricted agents (not executable in this session${input.sources?.length ? `; capability ceiling: ${input.sources.join(", ")}` : ""}):`,
773
+ ...input.agents.map((agent) => input.formatLine(agent, input.providerNames)),
774
+ );
775
+ }
776
+
777
+ function appendExternalJobRegistryLine(lines: string[], agents: AgentConfig[], status: ExternalJobProviderStatus): void {
778
+ if (status.ok || !agents.some((agent) => agent.runner?.type === "external-job")) return;
779
+ lines.push("", `External-job provider registry unavailable: ${status.error}`);
780
+ }
781
+
782
+ function appendAgentDiagnosticLines(lines: string[], diagnostics: AgentDiscoveryDiagnostic[] | undefined): void {
783
+ if (!diagnostics?.length) return;
784
+ lines.push(
785
+ "",
786
+ "Invalid agent definitions:",
787
+ ...diagnostics.map((diagnostic) => `- ${diagnostic.name ?? diagnostic.filePath} (${diagnostic.source}): ${diagnostic.error}`),
788
+ );
789
+ }
790
+
791
+ function agentCapabilityDetails(input: { capabilityMode: boolean; agents: AgentConfig[]; restrictedAgents: AgentConfig[]; providerNames?: Set<string>; restrictedSources?: string[] }): Partial<Details> | undefined {
792
+ if (!input.capabilityMode) return undefined;
793
+ return {
794
+ agentCapabilities: jsonDetails(agentCapabilitiesSnapshot({
795
+ agents: input.agents,
796
+ restrictedAgents: input.restrictedAgents,
797
+ providerNames: input.providerNames,
798
+ restrictedSources: input.restrictedSources,
799
+ })),
800
+ };
801
+ }
802
+
803
+ function formatAgentListSections(
804
+ agents: AgentConfig[],
805
+ providerNames: Set<string> | undefined,
806
+ formatLine: (agent: AgentConfig, providerNames: Set<string> | undefined) => string = formatAgentListLine,
807
+ ): string[] {
660
808
  if (agents.length === 0) return ["- (none)"];
661
809
  const sections: Array<[AgentSource, string]> = [
662
810
  ["package", "Package agents"],
@@ -670,7 +818,7 @@ function formatAgentListSections(agents: AgentConfig[], providerNames: Set<strin
670
818
  const matches = agents.filter((agent) => agent.source === source);
671
819
  if (matches.length === 0) continue;
672
820
  if (lines.length > 0) lines.push("");
673
- lines.push(label, ...matches.map((agent) => formatAgentListLine(agent, providerNames)));
821
+ lines.push(label, ...matches.map((agent) => formatLine(agent, providerNames)));
674
822
  }
675
823
  return lines;
676
824
  }
@@ -735,16 +883,7 @@ function formatAgentDetail(agent: AgentConfig): string {
735
883
  export function handleList(params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {
736
884
  const scope = normalizeListScope(params.agentScope) ?? "both";
737
885
  const d = discoverAgentsAll(ctx.cwd, ctx.model?.provider);
738
- let scopedAgents = mergeAgentsForScope(scope, d.user, d.project, d.builtin, d.package);
739
- if (ctx.runtimeAgentOwner && listRuntimeAgentConfigs(ctx.runtimeAgentOwner).length > 0) {
740
- const configuredAgents: AgentConfig[] = [
741
- ...d.builtin,
742
- ...d.package,
743
- ...d.user,
744
- ...d.project,
745
- ];
746
- scopedAgents = mergeRuntimeAgents(ctx.runtimeAgentOwner, { agents: scopedAgents }, configuredAgents).agents;
747
- }
886
+ let scopedAgents = effectiveAgentsForScope(scope, d, ctx.runtimeAgentOwner);
748
887
  scopedAgents = scopedAgents
749
888
  .sort((a, b) => a.name.localeCompare(b.name));
750
889
  const capabilityCeiling = resolveCurrentSubagentCapabilityCeiling(ctx.currentSessionId);
@@ -758,23 +897,24 @@ export function handleList(params: ManagementParams, ctx: ManagementContext): Ag
758
897
  discoverAvailableSkills: () => discoverAvailableSkills(ctx.cwd),
759
898
  });
760
899
  const providerStatus = registeredExternalJobProviderStatus();
900
+ const providerNameSet = providerNames(providerStatus);
901
+ const capabilityMode = params.capabilities === true;
902
+ const formatLine = capabilityMode ? formatAgentCapabilitiesLine : formatAgentListLine;
761
903
  const lines = [
762
- "Executable agents:",
763
- ...formatAgentListSections(agents, providerStatus.ok ? providerStatus.names : undefined),
764
- ...(restrictedAgents.length ? [
765
- "",
766
- `Restricted agents (not executable in this session${restrictedSources?.length ? `; capability ceiling: ${restrictedSources.join(", ")}` : ""}):`,
767
- ...restrictedAgents.map((a) => formatAgentListLine(a, providerStatus.ok ? providerStatus.names : undefined)),
768
- ] : []),
769
- ...(!providerStatus.ok && [...agents, ...restrictedAgents].some((agent) => agent.runner?.type === "external-job") ? ["", `External-job provider registry unavailable: ${providerStatus.error}`] : []),
770
- ...(d.agentDiagnostics?.length ? [
771
- "",
772
- "Invalid agent definitions:",
773
- ...d.agentDiagnostics.map((diagnostic) => `- ${diagnostic.name ?? diagnostic.filePath} (${diagnostic.source}): ${diagnostic.error}`),
774
- ] : []),
775
- ...(proactiveSuggestions.length ? ["", ...proactiveSuggestions] : []),
904
+ capabilityMode ? "Executable agents (capabilities):" : "Executable agents:",
905
+ ...formatAgentListSections(agents, providerNameSet, formatLine),
776
906
  ];
777
- return result(lines.join("\n"));
907
+ appendRestrictedAgentLines({ lines, agents: restrictedAgents, sources: restrictedSources, providerNames: providerNameSet, formatLine });
908
+ appendExternalJobRegistryLine(lines, [...agents, ...restrictedAgents], providerStatus);
909
+ appendAgentDiagnosticLines(lines, d.agentDiagnostics);
910
+ if (proactiveSuggestions.length) lines.push("", ...proactiveSuggestions);
911
+ return result(lines.join("\n"), false, agentCapabilityDetails({
912
+ capabilityMode,
913
+ agents,
914
+ restrictedAgents,
915
+ providerNames: providerNameSet,
916
+ restrictedSources,
917
+ }));
778
918
  }
779
919
 
780
920
  function formatModelSource(agent: AgentConfig, currentModel: ParentModel | undefined): string {
@@ -784,80 +924,108 @@ function formatModelSource(agent: AgentConfig, currentModel: ParentModel | undef
784
924
  if (agent.modelSource?.type === "subagents.defaultModel" && agent.model === agent.modelSource.model) {
785
925
  return `${agent.modelSource.scope} defaultModel`;
786
926
  }
787
- if (agent.model) return "builtin agent config";
927
+ if (agent.model) return `${agent.source} agent config`;
788
928
  if (currentModel) return "inherits current session model";
789
929
  return "inherit requested, but no current session model is available";
790
930
  }
791
931
 
792
932
  function handleModels(params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {
793
933
  const requestedAgent = params.agent?.trim();
794
- if (requestedAgent && !(BUILTIN_AGENT_NAMES as readonly string[]).includes(requestedAgent)) {
795
- return result(`Builtin agent '${requestedAgent}' not found. Available: ${BUILTIN_AGENT_NAMES.join(", ")}.`, true);
796
- }
934
+ const scope = normalizeListScope(params.agentScope);
935
+ if (!scope) return result("agentScope must be 'user', 'project', or 'both' for models.", true);
797
936
 
798
937
  const discovered = discoverAgentsAll(ctx.cwd, ctx.model?.provider);
799
- const builtinByName = new Map(discovered.builtin.map((agent) => [agent.name, agent]));
800
- const resolveBuiltinModelAgent = (name: string): AgentConfig | undefined => builtinByName.get(name) ?? resolveAgentName(name, discovered.builtin).agent;
938
+ const effectiveAgents = effectiveAgentsForScope(scope, discovered, ctx.runtimeAgentOwner)
939
+ .sort((a, b) => a.name.localeCompare(b.name));
801
940
  const availableModels = ctx.modelRegistry.getAvailable().map(toModelInfo);
802
941
  const currentModel = ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined;
803
942
  const preferredProvider = ctx.model?.provider;
804
- const names = requestedAgent ? [requestedAgent] : [...BUILTIN_AGENT_NAMES];
943
+ const capabilityCeiling = resolveCurrentSubagentCapabilityCeiling(ctx.currentSessionId);
805
944
 
945
+ let selectedAgents = effectiveAgents;
806
946
  if (requestedAgent) {
807
- const agent = resolveBuiltinModelAgent(requestedAgent);
808
- if (!agent) return result(`Builtin agent '${requestedAgent}' not found.`, true);
809
- const resolvedModel = resolveSubagentModelOverride(agent.model, currentModel, availableModels, preferredProvider);
810
- const lines = [
811
- "Builtin subagent model",
812
- "",
813
- `Agent: ${requestedAgent}`,
814
- "Effective model:",
815
- ` ${resolvedModel ?? "(unresolved)"}`,
816
- `Source: ${formatModelSource(agent, currentModel)}`,
817
- ];
818
- if (agent.override) {
819
- lines.push("Override file:");
820
- lines.push(` ${agent.override.path}`);
821
- }
822
- if (agent.model && resolvedModel && agent.model !== resolvedModel) {
823
- lines.push("Requested model setting:");
824
- lines.push(` ${agent.model}`);
947
+ const matches = findAgentsInDiscovery(requestedAgent, discovered, scope, ctx.runtimeAgentOwner);
948
+ const diagnostics = diagnosticsForScope(discovered.agentDiagnostics, scope);
949
+ const normalizedName = sanitizeName(requestedAgent);
950
+ const diagnostic = findBlockingAgentDiagnostic(requestedAgent, matches, diagnostics)
951
+ ?? (normalizedName !== requestedAgent ? findBlockingAgentDiagnostic(normalizedName, matches, diagnostics) : undefined);
952
+ if (diagnostic) return result(`Agent '${params.agent}' has invalid configuration: ${diagnostic.error}`, true);
953
+ const distinctNames = [...new Set(matches.map((agent) => agent.name))];
954
+ if (distinctNames.length > 1) return result(`Ambiguous agent alias or name '${params.agent}': ${distinctNames.sort((a, b) => a.localeCompare(b)).join(", ")}`, true);
955
+ if (!matches.length) {
956
+ return result(`Agent '${params.agent}' not found. Available: ${availableAgentNamesFromDiscovery(discovered, ctx.runtimeAgentOwner).join(", ") || "none"}.`, true);
825
957
  }
826
- if (agent.disabled) lines.push("Disabled: true");
827
- lines.push("Current session model:");
828
- lines.push(` ${currentModel ? `${currentModel.provider}/${currentModel.id}` : "(unavailable)"}`);
829
- return result(lines.join("\n"));
958
+ selectedAgents = [matches[0]!];
830
959
  }
831
960
 
832
961
  const lines = [
833
- "Builtin subagent models",
834
- "",
835
- "Current session model:",
836
- ` ${currentModel ? `${currentModel.provider}/${currentModel.id}` : "(unavailable)"}`,
962
+ requestedAgent ? "Subagent model" : "Subagent models",
837
963
  "",
964
+ ...(requestedAgent ? [] : [
965
+ "Current session model:",
966
+ ` ${currentModel ? `${currentModel.provider}/${currentModel.id}` : "(unavailable)"}`,
967
+ "",
968
+ ]),
838
969
  ];
839
970
 
840
- for (const name of names) {
841
- const agent = resolveBuiltinModelAgent(name);
842
- if (!agent) {
843
- lines.push(name);
844
- lines.push(" model:");
845
- lines.push(" (builtin definition not found)");
846
- lines.push(" source: missing");
847
- lines.push("");
848
- continue;
971
+ const modelEntries = selectedAgents.flatMap((agent) => requestedAgent
972
+ ? [{ agent, name: requestedAgent }]
973
+ : [{ agent, name: agent.name }, ...(agent.aliases ?? []).map((name) => ({ agent, name }))]);
974
+ for (const { agent, name } of modelEntries) {
975
+ const resolvedModel = resolveSubagentModelOverride(agent.model, currentModel, availableModels, agent.modelProvider ?? preferredProvider);
976
+ const effectiveThinking = resolveEffectiveThinking(resolvedModel, agent.thinking)
977
+ ?? (agent.thinking === false ? "off" : undefined);
978
+ const source = `${formatModelSource(agent, currentModel)}${agent.disabled ? "; disabled" : ""}${isAgentAllowedByCapabilityCeiling(agent.name, capabilityCeiling) ? "" : "; restricted"}`;
979
+ if (requestedAgent) {
980
+ lines.push(`Agent: ${requestedAgent}`);
981
+ lines.push("Effective model:");
982
+ lines.push(` ${resolvedModel ?? "(unresolved)"}`);
983
+ lines.push(`Source: ${source}`);
984
+ lines.push(`Thinking: ${effectiveThinking ?? "default"}`);
985
+ if (agent.fallbackModels?.length) {
986
+ lines.push("Fallback models:");
987
+ for (const fallback of agent.fallbackModels) {
988
+ lines.push(` ${resolveSubagentModelOverride(fallback, currentModel, availableModels, agent.modelProvider ?? preferredProvider) ?? fallback}`);
989
+ }
990
+ }
991
+ if (agent.override) {
992
+ lines.push("Override file:");
993
+ lines.push(` ${agent.override.path}`);
994
+ }
995
+ if (agent.model && resolvedModel && agent.model !== resolvedModel) {
996
+ lines.push("Requested model setting:");
997
+ lines.push(` ${agent.model}`);
998
+ }
999
+ if (agent.disabled) lines.push("Disabled: true");
1000
+ if (!isAgentAllowedByCapabilityCeiling(agent.name, capabilityCeiling)) lines.push("Restricted: true");
1001
+ lines.push("Current session model:");
1002
+ lines.push(` ${currentModel ? `${currentModel.provider}/${currentModel.id}` : "(unavailable)"}`);
1003
+ break;
849
1004
  }
850
- const resolvedModel = resolveSubagentModelOverride(agent.model, currentModel, availableModels, preferredProvider);
851
- const source = `${formatModelSource(agent, currentModel)}${agent.disabled ? "; disabled" : ""}`;
852
1005
  lines.push(name);
853
1006
  lines.push(" model:");
854
1007
  lines.push(` ${resolvedModel ?? "(unresolved)"}`);
855
1008
  lines.push(` source: ${source}`);
1009
+ lines.push(` thinking: ${effectiveThinking ?? "default"}`);
1010
+ if (agent.fallbackModels?.length) {
1011
+ lines.push(" fallback models:");
1012
+ for (const fallback of agent.fallbackModels) {
1013
+ lines.push(` ${resolveSubagentModelOverride(fallback, currentModel, availableModels, agent.modelProvider ?? preferredProvider) ?? fallback}`);
1014
+ }
1015
+ }
1016
+ if (agent.override) {
1017
+ lines.push(" override file:");
1018
+ lines.push(` ${agent.override.path}`);
1019
+ }
1020
+ if (agent.model && resolvedModel && agent.model !== resolvedModel) {
1021
+ lines.push(" requested model setting:");
1022
+ lines.push(` ${agent.model}`);
1023
+ }
856
1024
  lines.push("");
857
1025
  }
858
1026
 
859
1027
  const availableFullIds = availableModels.map((m) => m.fullId).sort();
860
- if (availableFullIds.length > 0) {
1028
+ if (!requestedAgent && availableFullIds.length > 0) {
861
1029
  lines.push("Available models in this session's registry (copy an exact provider/id when passing model):");
862
1030
  lines.push("");
863
1031
  const shown = availableFullIds.slice(0, 80);
@@ -866,7 +1034,7 @@ function handleModels(params: ManagementParams, ctx: ManagementContext): AgentTo
866
1034
  lines.push("");
867
1035
  lines.push("Use an exact provider/id from this list when you pass model; bare ids resolve only when unique in the registry.");
868
1036
  }
869
-
1037
+ if (!requestedAgent) appendAgentDiagnosticLines(lines, diagnosticsForScope(discovered.agentDiagnostics, scope));
870
1038
  return result(lines.join("\n"));
871
1039
  }
872
1040