pi-subagents 0.60.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 (57) hide show
  1. package/CHANGELOG.md +32 -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 +12 -3
  8. package/docs/workflows.md +14 -13
  9. package/install.mjs +2 -1
  10. package/package.json +1 -1
  11. package/skills/pi-subagents/SKILL.md +6 -4
  12. package/skills/pi-subagents/references/constraints-and-recipes.md +1 -1
  13. package/skills/pi-subagents/references/execution-controls.md +42 -11
  14. package/skills/pi-subagents/references/multi-lane-orchestration.md +1 -1
  15. package/skills/pi-subagents/references/prompting-and-roles.md +4 -4
  16. package/skills/pi-subagents/references/review-and-validation.md +1 -1
  17. package/src/agents/agent-management.ts +102 -63
  18. package/src/agents/agents.ts +527 -221
  19. package/src/api/background-work.ts +7 -2
  20. package/src/api/external-runs.ts +67 -4
  21. package/src/api/preflight.ts +13 -8
  22. package/src/api/shared-types.ts +1 -0
  23. package/src/extension/index.ts +7 -4
  24. package/src/extension/public-execution.ts +47 -4
  25. package/src/extension/rpc.ts +62 -4
  26. package/src/extension/schemas.ts +11 -7
  27. package/src/extension/tool-description.ts +14 -18
  28. package/src/runs/background/async-execution.ts +51 -35
  29. package/src/runs/background/async-job-tracker.ts +62 -3
  30. package/src/runs/background/async-resume.ts +3 -1
  31. package/src/runs/background/async-status.ts +59 -9
  32. package/src/runs/background/auto-drain.ts +1 -1
  33. package/src/runs/background/fleet-view.ts +1 -1
  34. package/src/runs/background/result-watcher.ts +1 -1
  35. package/src/runs/background/resume-guidance.ts +1 -1
  36. package/src/runs/background/run-status.ts +2 -2
  37. package/src/runs/background/subagent-runner.ts +1 -2
  38. package/src/runs/background/subagent-wait.ts +20 -21
  39. package/src/runs/background/wait-completions.ts +1 -1
  40. package/src/runs/background/wait-tool.ts +24 -18
  41. package/src/runs/foreground/execution.ts +61 -2
  42. package/src/runs/foreground/subagent-executor.ts +178 -58
  43. package/src/runs/shared/acceptance.ts +43 -18
  44. package/src/runs/shared/host-step-status.ts +1 -0
  45. package/src/runs/shared/model-fallback.ts +61 -17
  46. package/src/runs/shared/permissions.ts +1 -1
  47. package/src/runs/shared/tool-timeout.ts +1 -1
  48. package/src/runs/shared/workflow-graph.ts +3 -2
  49. package/src/shared/types.ts +40 -3
  50. package/src/shared/workflow-child-permit.ts +91 -0
  51. package/src/slash/prompt-template-bridge.ts +37 -1
  52. package/src/slash/slash-commands.ts +18 -26
  53. package/src/tui/render.ts +95 -52
  54. package/src/workflows/scripted-workflow.ts +153 -4
  55. package/src/workflows/workflow-child-summary.ts +1 -1
  56. package/src/workflows/workflow-receipt.ts +41 -4
  57. 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,7 +27,7 @@ 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";
@@ -37,7 +36,7 @@ import type { AcceptanceInput, AgentCapabilitiesSnapshot, AgentCapabilityRow, De
37
36
  import { getProjectConfigDir } from "../shared/utils.ts";
38
37
  import { previewDisplayText } from "../shared/display-text.ts";
39
38
  import { capabilityCeilingAgentRestrictionSources, isAgentAllowedByCapabilityCeiling, resolveCurrentSubagentCapabilityCeiling } from "../runs/shared/capability-ceiling.ts";
40
- import { listRuntimeAgentConfigs, mergeRuntimeAgents, type RuntimeAgentOwner } from "./runtime-agent-registry.ts";
39
+ import { mergeRuntimeAgents, type RuntimeAgentOwner } from "./runtime-agent-registry.ts";
41
40
  import { listExternalJobProviders } from "../api/external-job-provider.ts";
42
41
 
43
42
  type ManagementAction = "list" | "get" | "models" | "create" | "update" | "delete" | "eject" | "disable" | "enable" | "reset";
@@ -129,18 +128,39 @@ function allAgents(d: { builtin: AgentConfig[]; package: AgentConfig[]; user: Ag
129
128
  return [...d.builtin, ...d.package, ...d.user, ...d.project];
130
129
  }
131
130
 
132
- function availableAgentNamesFromDiscovery(d: { builtin: AgentConfig[]; package: AgentConfig[]; user: AgentConfig[]; project: AgentConfig[] }): string[] {
133
- 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));
134
149
  }
135
150
 
136
151
  function availableAgentNames(cwd: string): string[] {
137
152
  return availableAgentNamesFromDiscovery(discoverAgentsAll(cwd));
138
153
  }
139
154
 
140
- 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[] {
141
161
  const raw = name.trim();
142
162
  const sanitized = sanitizeName(raw);
143
- const scoped = mergeAgentsForScope(scope, d.user, d.project, d.builtin, d.package);
163
+ const scoped = effectiveAgentsForScope(scope, d, runtimeAgentOwner);
144
164
  let resolved = resolveAgentName(raw, scoped);
145
165
  if (!resolved.agent && !resolved.error && sanitized !== raw) resolved = resolveAgentName(sanitized, scoped);
146
166
  if (resolved.agent) return scoped.filter((agent) => agent.name === resolved.agent!.name).sort((a, b) => a.source.localeCompare(b.source));
@@ -863,16 +883,7 @@ function formatAgentDetail(agent: AgentConfig): string {
863
883
  export function handleList(params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {
864
884
  const scope = normalizeListScope(params.agentScope) ?? "both";
865
885
  const d = discoverAgentsAll(ctx.cwd, ctx.model?.provider);
866
- let scopedAgents = mergeAgentsForScope(scope, d.user, d.project, d.builtin, d.package);
867
- if (ctx.runtimeAgentOwner && listRuntimeAgentConfigs(ctx.runtimeAgentOwner).length > 0) {
868
- const configuredAgents: AgentConfig[] = [
869
- ...d.builtin,
870
- ...d.package,
871
- ...d.user,
872
- ...d.project,
873
- ];
874
- scopedAgents = mergeRuntimeAgents(ctx.runtimeAgentOwner, { agents: scopedAgents }, configuredAgents).agents;
875
- }
886
+ let scopedAgents = effectiveAgentsForScope(scope, d, ctx.runtimeAgentOwner);
876
887
  scopedAgents = scopedAgents
877
888
  .sort((a, b) => a.name.localeCompare(b.name));
878
889
  const capabilityCeiling = resolveCurrentSubagentCapabilityCeiling(ctx.currentSessionId);
@@ -913,80 +924,108 @@ function formatModelSource(agent: AgentConfig, currentModel: ParentModel | undef
913
924
  if (agent.modelSource?.type === "subagents.defaultModel" && agent.model === agent.modelSource.model) {
914
925
  return `${agent.modelSource.scope} defaultModel`;
915
926
  }
916
- if (agent.model) return "builtin agent config";
927
+ if (agent.model) return `${agent.source} agent config`;
917
928
  if (currentModel) return "inherits current session model";
918
929
  return "inherit requested, but no current session model is available";
919
930
  }
920
931
 
921
932
  function handleModels(params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {
922
933
  const requestedAgent = params.agent?.trim();
923
- if (requestedAgent && !(BUILTIN_AGENT_NAMES as readonly string[]).includes(requestedAgent)) {
924
- return result(`Builtin agent '${requestedAgent}' not found. Available: ${BUILTIN_AGENT_NAMES.join(", ")}.`, true);
925
- }
934
+ const scope = normalizeListScope(params.agentScope);
935
+ if (!scope) return result("agentScope must be 'user', 'project', or 'both' for models.", true);
926
936
 
927
937
  const discovered = discoverAgentsAll(ctx.cwd, ctx.model?.provider);
928
- const builtinByName = new Map(discovered.builtin.map((agent) => [agent.name, agent]));
929
- 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));
930
940
  const availableModels = ctx.modelRegistry.getAvailable().map(toModelInfo);
931
941
  const currentModel = ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined;
932
942
  const preferredProvider = ctx.model?.provider;
933
- const names = requestedAgent ? [requestedAgent] : [...BUILTIN_AGENT_NAMES];
943
+ const capabilityCeiling = resolveCurrentSubagentCapabilityCeiling(ctx.currentSessionId);
934
944
 
945
+ let selectedAgents = effectiveAgents;
935
946
  if (requestedAgent) {
936
- const agent = resolveBuiltinModelAgent(requestedAgent);
937
- if (!agent) return result(`Builtin agent '${requestedAgent}' not found.`, true);
938
- const resolvedModel = resolveSubagentModelOverride(agent.model, currentModel, availableModels, preferredProvider);
939
- const lines = [
940
- "Builtin subagent model",
941
- "",
942
- `Agent: ${requestedAgent}`,
943
- "Effective model:",
944
- ` ${resolvedModel ?? "(unresolved)"}`,
945
- `Source: ${formatModelSource(agent, currentModel)}`,
946
- ];
947
- if (agent.override) {
948
- lines.push("Override file:");
949
- lines.push(` ${agent.override.path}`);
950
- }
951
- if (agent.model && resolvedModel && agent.model !== resolvedModel) {
952
- lines.push("Requested model setting:");
953
- 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);
954
957
  }
955
- if (agent.disabled) lines.push("Disabled: true");
956
- lines.push("Current session model:");
957
- lines.push(` ${currentModel ? `${currentModel.provider}/${currentModel.id}` : "(unavailable)"}`);
958
- return result(lines.join("\n"));
958
+ selectedAgents = [matches[0]!];
959
959
  }
960
960
 
961
961
  const lines = [
962
- "Builtin subagent models",
963
- "",
964
- "Current session model:",
965
- ` ${currentModel ? `${currentModel.provider}/${currentModel.id}` : "(unavailable)"}`,
962
+ requestedAgent ? "Subagent model" : "Subagent models",
966
963
  "",
964
+ ...(requestedAgent ? [] : [
965
+ "Current session model:",
966
+ ` ${currentModel ? `${currentModel.provider}/${currentModel.id}` : "(unavailable)"}`,
967
+ "",
968
+ ]),
967
969
  ];
968
970
 
969
- for (const name of names) {
970
- const agent = resolveBuiltinModelAgent(name);
971
- if (!agent) {
972
- lines.push(name);
973
- lines.push(" model:");
974
- lines.push(" (builtin definition not found)");
975
- lines.push(" source: missing");
976
- lines.push("");
977
- 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;
978
1004
  }
979
- const resolvedModel = resolveSubagentModelOverride(agent.model, currentModel, availableModels, preferredProvider);
980
- const source = `${formatModelSource(agent, currentModel)}${agent.disabled ? "; disabled" : ""}`;
981
1005
  lines.push(name);
982
1006
  lines.push(" model:");
983
1007
  lines.push(` ${resolvedModel ?? "(unresolved)"}`);
984
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
+ }
985
1024
  lines.push("");
986
1025
  }
987
1026
 
988
1027
  const availableFullIds = availableModels.map((m) => m.fullId).sort();
989
- if (availableFullIds.length > 0) {
1028
+ if (!requestedAgent && availableFullIds.length > 0) {
990
1029
  lines.push("Available models in this session's registry (copy an exact provider/id when passing model):");
991
1030
  lines.push("");
992
1031
  const shown = availableFullIds.slice(0, 80);
@@ -995,7 +1034,7 @@ function handleModels(params: ManagementParams, ctx: ManagementContext): AgentTo
995
1034
  lines.push("");
996
1035
  lines.push("Use an exact provider/id from this list when you pass model; bare ids resolve only when unique in the registry.");
997
1036
  }
998
-
1037
+ if (!requestedAgent) appendAgentDiagnosticLines(lines, diagnosticsForScope(discovered.agentDiagnostics, scope));
999
1038
  return result(lines.join("\n"));
1000
1039
  }
1001
1040