pi-subagents 0.57.0 → 0.59.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/docs/agents.md +18 -9
  3. package/docs/configuration.md +4 -4
  4. package/docs/extension-api.md +40 -1
  5. package/docs/models.md +27 -4
  6. package/docs/observability.md +1 -1
  7. package/docs/tool-reference.md +92 -6
  8. package/docs/workflows.md +115 -0
  9. package/package.json +3 -1
  10. package/prompts/review-loop.md +2 -2
  11. package/skills/council-mode/SKILL.md +1 -1
  12. package/skills/pi-subagents/SKILL.md +3 -1
  13. package/skills/pi-subagents/references/execution-controls.md +6 -2
  14. package/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
  15. package/skills/pi-subagents/references/prompting-and-roles.md +26 -5
  16. package/src/agents/agent-management.ts +28 -17
  17. package/src/agents/agent-serializer.ts +9 -2
  18. package/src/agents/agents.ts +103 -27
  19. package/src/agents/runtime-agent-events.ts +70 -0
  20. package/src/agents/runtime-agent-registry.ts +19 -18
  21. package/src/api/agents.ts +10 -5
  22. package/src/api/background-work.ts +5 -1
  23. package/src/api/delegation.ts +0 -7
  24. package/src/api/preflight.ts +32 -10
  25. package/src/extension/fanout-child.ts +5 -3
  26. package/src/extension/index.ts +66 -27
  27. package/src/extension/public-execution.ts +15 -2
  28. package/src/extension/rpc.ts +35 -14
  29. package/src/extension/schemas.ts +37 -12
  30. package/src/extension/tool-description.ts +25 -6
  31. package/src/integrations/herdr-status.ts +5 -0
  32. package/src/intercom/result-intercom.ts +2 -0
  33. package/src/profiles/profiles.ts +5 -6
  34. package/src/runs/background/active-async-capacity.ts +2 -2
  35. package/src/runs/background/async-execution.ts +78 -46
  36. package/src/runs/background/async-job-tracker.ts +14 -12
  37. package/src/runs/background/async-resume.ts +29 -27
  38. package/src/runs/background/async-status-snapshot.ts +23 -261
  39. package/src/runs/background/async-status.ts +68 -7
  40. package/src/runs/background/chain-append.ts +8 -3
  41. package/src/runs/background/chain-root-attachment.ts +60 -8
  42. package/src/runs/background/fleet-view.ts +21 -11
  43. package/src/runs/background/notify.ts +184 -9
  44. package/src/runs/background/result-delivery-ownership.ts +45 -0
  45. package/src/runs/background/result-files.ts +2 -1
  46. package/src/runs/background/result-watcher.ts +29 -10
  47. package/src/runs/background/resume-guidance.ts +1 -1
  48. package/src/runs/background/retained-children.ts +1 -1
  49. package/src/runs/background/run-status.ts +18 -8
  50. package/src/runs/background/scheduled-runs.ts +86 -7
  51. package/src/runs/background/stale-run-reconciler.ts +10 -4
  52. package/src/runs/background/steering.ts +4 -14
  53. package/src/runs/background/subagent-runner.ts +389 -350
  54. package/src/runs/background/subagent-wait.ts +58 -10
  55. package/src/runs/background/terminal-run-index.ts +1 -1
  56. package/src/runs/background/wait-completions.ts +22 -1
  57. package/src/runs/background/wait-config.ts +23 -9
  58. package/src/runs/background/wait-tool.ts +9 -2
  59. package/src/runs/foreground/async-steering-action.ts +2 -2
  60. package/src/runs/foreground/execution.ts +134 -121
  61. package/src/runs/foreground/foreground-control.ts +3 -0
  62. package/src/runs/foreground/foreground-history.ts +1 -0
  63. package/src/runs/foreground/subagent-executor.ts +527 -257
  64. package/src/runs/foreground/workflow-detach-reconcile.ts +130 -120
  65. package/src/runs/shared/abort-recovery.ts +119 -0
  66. package/src/runs/shared/async-status-projection.ts +463 -0
  67. package/src/runs/shared/child-identity.ts +19 -4
  68. package/src/runs/shared/child-launch-plan.ts +151 -0
  69. package/src/runs/shared/completion-evidence.ts +89 -0
  70. package/src/runs/shared/completion-guard.ts +5 -4
  71. package/src/runs/shared/dynamic-fanout.ts +3 -3
  72. package/src/runs/shared/fast-mode-extension.ts +5 -5
  73. package/src/runs/shared/host-step-status.ts +230 -0
  74. package/src/runs/shared/lane-metadata.ts +105 -0
  75. package/src/runs/shared/launch-cwd.ts +16 -0
  76. package/src/runs/shared/long-running-guard.ts +2 -1
  77. package/src/runs/shared/mcp-config-sources.ts +422 -0
  78. package/src/runs/shared/mcp-direct-tool-allowlist.ts +221 -158
  79. package/src/runs/shared/mcp-direct-tool-grant.ts +197 -0
  80. package/src/runs/shared/model-exclusions.ts +12 -0
  81. package/src/runs/shared/model-fallback.ts +25 -6
  82. package/src/runs/shared/nested-events.ts +6 -2
  83. package/src/runs/shared/nested-render.ts +7 -3
  84. package/src/runs/shared/parallel-handoff.ts +419 -7
  85. package/src/runs/shared/parallel-utils.ts +15 -1
  86. package/src/runs/shared/pi-args.ts +81 -8
  87. package/src/runs/shared/single-output.ts +44 -4
  88. package/src/runs/shared/subagent-prompt-runtime.ts +98 -13
  89. package/src/runs/shared/worktree-cleanup-plan.ts +847 -0
  90. package/src/runs/shared/worktree.ts +18 -0
  91. package/src/shared/child-session-name.ts +46 -0
  92. package/src/shared/extension-context.ts +24 -0
  93. package/src/shared/formatters.ts +11 -2
  94. package/src/shared/launch-contract.ts +5 -1
  95. package/src/shared/settings.ts +9 -103
  96. package/src/shared/types.ts +240 -30
  97. package/src/shared/utils.ts +41 -84
  98. package/src/slash/delegation-adapters.ts +1 -8
  99. package/src/slash/delegation-request.ts +0 -4
  100. package/src/slash/slash-bridge.ts +1 -2
  101. package/src/slash/slash-commands.ts +369 -90
  102. package/src/slash/slash-live-state.ts +22 -11
  103. package/src/slash/subagents-admin.ts +3 -0
  104. package/src/tui/fleet-status.ts +125 -74
  105. package/src/tui/fleet.ts +11 -5
  106. package/src/tui/render.ts +330 -33
  107. package/src/watchdog/turn-delta.ts +1 -1
  108. package/src/workflows/chat-progress.ts +6 -3
  109. package/src/workflows/host-command.ts +230 -0
  110. package/src/workflows/scripted-workflow.ts +496 -35
  111. package/src/workflows/workflow-child-summary.ts +9 -5
  112. package/src/workflows/workflow-preflight.ts +270 -0
  113. package/src/workflows/workflow-receipt.ts +79 -5
  114. package/src/workflows/workflow-settlement.ts +246 -0
  115. package/src/runs/shared/turn-budget.ts +0 -98
@@ -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, TurnBudgetConfig } from "../shared/types.ts";
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";
@@ -62,6 +61,7 @@ export interface BuiltinAgentOverrideBase {
62
61
  thinking?: string | false;
63
62
  systemPromptMode: SystemPromptMode;
64
63
  inheritProjectContext: boolean;
64
+ inheritGlobalContext: boolean;
65
65
  inheritSkills: boolean;
66
66
  defaultContext?: AgentDefaultContext;
67
67
  acceptanceRole?: AcceptanceRole;
@@ -70,9 +70,11 @@ export interface BuiltinAgentOverrideBase {
70
70
  skills?: string[];
71
71
  skillPath?: string[];
72
72
  tools?: string[];
73
+ allowNestedSubagents?: boolean;
73
74
  mcpDirectTools?: string[];
74
75
  extensions?: string[];
75
76
  subagentOnlyExtensions?: string[];
77
+ mutationTools?: string[];
76
78
  completionGuard?: boolean;
77
79
  toolBudget?: ToolBudgetConfig;
78
80
  }
@@ -89,6 +91,7 @@ interface BuiltinAgentOverrideConfig {
89
91
  thinking?: string | false;
90
92
  systemPromptMode?: SystemPromptMode;
91
93
  inheritProjectContext?: boolean;
94
+ inheritGlobalContext?: boolean;
92
95
  inheritSkills?: boolean;
93
96
  defaultContext?: AgentDefaultContext | false;
94
97
  acceptanceRole?: AcceptanceRole | false;
@@ -96,8 +99,10 @@ interface BuiltinAgentOverrideConfig {
96
99
  systemPrompt?: string;
97
100
  skills?: string[] | false;
98
101
  tools?: string[] | false | "inherit";
102
+ allowNestedSubagents?: boolean;
99
103
  extensions?: string[] | false;
100
104
  subagentOnlyExtensions?: string[] | false;
105
+ mutationTools?: string[] | false;
101
106
  completionGuard?: boolean;
102
107
  toolBudget?: ToolBudgetConfig | false;
103
108
  }
@@ -127,6 +132,7 @@ export interface AgentConfig {
127
132
  description: string;
128
133
  aliases?: string[];
129
134
  tools?: string[];
135
+ allowNestedSubagents?: boolean;
130
136
  mcpDirectTools?: string[];
131
137
  model?: string;
132
138
  modelProvider?: string;
@@ -135,12 +141,12 @@ export interface AgentConfig {
135
141
  thinking?: string | false;
136
142
  systemPromptMode: SystemPromptMode;
137
143
  inheritProjectContext: boolean;
144
+ inheritGlobalContext: boolean;
138
145
  inheritSkills: boolean;
139
146
  defaultContext?: AgentDefaultContext;
140
147
  defaultAsync?: boolean;
141
148
  defaultTimeoutMs?: number;
142
149
  defaultToolTimeoutMs?: number;
143
- defaultTurnBudget?: TurnBudgetConfig;
144
150
  defaultAcceptance?: AcceptanceInput;
145
151
  acceptanceRole?: AcceptanceRole;
146
152
  systemPrompt: string;
@@ -152,6 +158,7 @@ export interface AgentConfig {
152
158
  extensions?: string[];
153
159
  extensionsFromDefault?: boolean;
154
160
  subagentOnlyExtensions?: string[];
161
+ mutationTools?: string[];
155
162
  output?: string;
156
163
  outputMode?: OutputMode;
157
164
  defaultReads?: string[];
@@ -173,6 +180,7 @@ type ProjectRootResolution = "nearest" | "git-root";
173
180
 
174
181
  interface SubagentSettings {
175
182
  overrides: Record<string, BuiltinAgentOverrideConfig>;
183
+ providerOverrides: Record<string, Record<string, BuiltinAgentOverrideConfig>>;
176
184
  defaultModel?: string;
177
185
  defaultProvider?: string;
178
186
  defaultThinking?: string;
@@ -183,7 +191,7 @@ interface SubagentSettings {
183
191
  modelScope?: ModelScopeConfig;
184
192
  }
185
193
 
186
- const EMPTY_SUBAGENT_SETTINGS: SubagentSettings = { overrides: {} };
194
+ const EMPTY_SUBAGENT_SETTINGS: SubagentSettings = { overrides: {}, providerOverrides: {} };
187
195
  const agentFrontmatterFields = new WeakMap<AgentConfig, Set<string>>();
188
196
 
189
197
  export interface ChainStepConfig {
@@ -710,6 +718,7 @@ function cloneOverrideBase(agent: AgentConfig): BuiltinAgentOverrideBase {
710
718
  ...(agent.thinking !== undefined ? { thinking: agent.thinking } : {}),
711
719
  systemPromptMode: agent.systemPromptMode,
712
720
  inheritProjectContext: agent.inheritProjectContext,
721
+ inheritGlobalContext: agent.inheritGlobalContext,
713
722
  inheritSkills: agent.inheritSkills,
714
723
  ...(agent.defaultContext !== undefined ? { defaultContext: agent.defaultContext } : {}),
715
724
  ...(agent.acceptanceRole !== undefined ? { acceptanceRole: agent.acceptanceRole } : {}),
@@ -718,9 +727,11 @@ function cloneOverrideBase(agent: AgentConfig): BuiltinAgentOverrideBase {
718
727
  ...(agent.skills ? { skills: [...agent.skills] } : {}),
719
728
  ...(agent.skillPath ? { skillPath: [...agent.skillPath] } : {}),
720
729
  ...(agent.tools ? { tools: [...agent.tools] } : {}),
730
+ ...(agent.allowNestedSubagents !== undefined ? { allowNestedSubagents: agent.allowNestedSubagents } : {}),
721
731
  ...(agent.mcpDirectTools ? { mcpDirectTools: [...agent.mcpDirectTools] } : {}),
722
732
  ...(!agent.extensionsFromDefault && agent.extensions ? { extensions: [...agent.extensions] } : {}),
723
733
  ...(agent.subagentOnlyExtensions ? { subagentOnlyExtensions: [...agent.subagentOnlyExtensions] } : {}),
734
+ ...(agent.mutationTools ? { mutationTools: [...agent.mutationTools] } : {}),
724
735
  ...(agent.completionGuard !== undefined ? { completionGuard: agent.completionGuard } : {}),
725
736
  ...(agent.toolBudget !== undefined ? { toolBudget: agent.toolBudget } : {}),
726
737
  };
@@ -741,6 +752,7 @@ function cloneOverrideValue(override: BuiltinAgentOverrideConfig): BuiltinAgentO
741
752
  ...(override.thinking !== undefined ? { thinking: override.thinking } : {}),
742
753
  ...(override.systemPromptMode !== undefined ? { systemPromptMode: override.systemPromptMode } : {}),
743
754
  ...(override.inheritProjectContext !== undefined ? { inheritProjectContext: override.inheritProjectContext } : {}),
755
+ ...(override.inheritGlobalContext !== undefined ? { inheritGlobalContext: override.inheritGlobalContext } : {}),
744
756
  ...(override.inheritSkills !== undefined ? { inheritSkills: override.inheritSkills } : {}),
745
757
  ...(override.defaultContext !== undefined ? { defaultContext: override.defaultContext } : {}),
746
758
  ...(override.acceptanceRole !== undefined ? { acceptanceRole: override.acceptanceRole } : {}),
@@ -748,8 +760,10 @@ function cloneOverrideValue(override: BuiltinAgentOverrideConfig): BuiltinAgentO
748
760
  ...(override.systemPrompt !== undefined ? { systemPrompt: override.systemPrompt } : {}),
749
761
  ...(override.skills !== undefined ? { skills: override.skills === false ? false : [...override.skills] } : {}),
750
762
  ...(override.tools !== undefined ? { tools: Array.isArray(override.tools) ? [...override.tools] : override.tools } : {}),
763
+ ...(override.allowNestedSubagents !== undefined ? { allowNestedSubagents: override.allowNestedSubagents } : {}),
751
764
  ...(override.extensions !== undefined ? { extensions: override.extensions === false ? false : [...override.extensions] } : {}),
752
765
  ...(override.subagentOnlyExtensions !== undefined ? { subagentOnlyExtensions: override.subagentOnlyExtensions === false ? false : [...override.subagentOnlyExtensions] } : {}),
766
+ ...(override.mutationTools !== undefined ? { mutationTools: override.mutationTools === false ? false : [...override.mutationTools] } : {}),
753
767
  ...(override.completionGuard !== undefined ? { completionGuard: override.completionGuard } : {}),
754
768
  ...(override.toolBudget !== undefined ? { toolBudget: override.toolBudget === false ? false : { ...override.toolBudget, ...(Array.isArray(override.toolBudget.block) ? { block: [...override.toolBudget.block] } : {}) } } : {}),
755
769
  };
@@ -799,7 +813,7 @@ export function findNearestProjectRoot(cwd: string): string | null {
799
813
  return findProjectRootCandidates(cwd)[0] ?? null;
800
814
  }
801
815
 
802
- function findConfiguredProjectRoot(cwd: string): string | null {
816
+ export function findConfiguredProjectRoot(cwd: string): string | null {
803
817
  const candidates = findProjectRootCandidates(cwd);
804
818
  const nearestRoot = candidates[0];
805
819
  if (!nearestRoot) return null;
@@ -958,6 +972,14 @@ function parseBuiltinOverrideEntry(
958
972
  }
959
973
  }
960
974
 
975
+ if ("inheritGlobalContext" in input) {
976
+ if (typeof input.inheritGlobalContext === "boolean") {
977
+ override.inheritGlobalContext = input.inheritGlobalContext;
978
+ } else {
979
+ throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'inheritGlobalContext'; expected a boolean.`);
980
+ }
981
+ }
982
+
961
983
  if ("inheritSkills" in input) {
962
984
  if (typeof input.inheritSkills === "boolean") {
963
985
  override.inheritSkills = input.inheritSkills;
@@ -1030,6 +1052,10 @@ function parseBuiltinOverrideEntry(
1030
1052
 
1031
1053
  const tools = parseToolsOverride(input.tools, { filePath, name });
1032
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
+ }
1033
1059
 
1034
1060
  const extensions = parseOverrideStringArrayOrFalse(input.extensions, { filePath, name, field: "extensions" });
1035
1061
  if (extensions !== undefined) override.extensions = extensions;
@@ -1037,6 +1063,9 @@ function parseBuiltinOverrideEntry(
1037
1063
  const subagentOnlyExtensions = parseOverrideStringArrayOrFalse(input.subagentOnlyExtensions, { filePath, name, field: "subagentOnlyExtensions" });
1038
1064
  if (subagentOnlyExtensions !== undefined) override.subagentOnlyExtensions = subagentOnlyExtensions;
1039
1065
 
1066
+ const mutationTools = parseOverrideStringArrayOrFalse(input.mutationTools, { filePath, name, field: "mutationTools" });
1067
+ if (mutationTools !== undefined) override.mutationTools = mutationTools;
1068
+
1040
1069
  return Object.keys(override).length > 0 ? override : undefined;
1041
1070
  }
1042
1071
 
@@ -1106,9 +1135,12 @@ function readSubagentSettings(filePath: string | null): SubagentSettings {
1106
1135
  const modelScope = parseModelScopeConfig(subagentsObject.modelScope, { filePath });
1107
1136
 
1108
1137
  const parsed: Record<string, BuiltinAgentOverrideConfig> = {};
1138
+ const providerOverrides: Record<string, Record<string, BuiltinAgentOverrideConfig>> = {};
1109
1139
  const agentOverrides = subagentsObject.agentOverrides;
1140
+ const agentOverridesByProvider = subagentsObject.agentOverridesByProvider;
1110
1141
  const parsedSettings: SubagentSettings = {
1111
1142
  overrides: parsed,
1143
+ providerOverrides,
1112
1144
  ...(defaultModel !== undefined ? { defaultModel } : {}),
1113
1145
  ...(defaultProvider !== undefined ? { defaultProvider } : {}),
1114
1146
  ...(defaultThinking !== undefined ? { defaultThinking } : {}),
@@ -1118,16 +1150,42 @@ function readSubagentSettings(filePath: string | null): SubagentSettings {
1118
1150
  ...(disableThinking !== undefined ? { disableThinking } : {}),
1119
1151
  ...(modelScope !== undefined ? { modelScope } : {}),
1120
1152
  };
1121
- if (!agentOverrides || typeof agentOverrides !== "object" || Array.isArray(agentOverrides)) {
1122
- return parsedSettings;
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
+ }
1123
1158
  }
1124
- for (const [name, value] of Object.entries(agentOverrides)) {
1125
- const override = parseBuiltinOverrideEntry(name, value, filePath);
1126
- if (override) parsed[name] = override;
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
+ }
1127
1174
  }
1128
1175
  return parsedSettings;
1129
1176
  }
1130
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
+
1131
1189
  function resolveSubagentDefaultProvider(
1132
1190
  userSettings: SubagentSettings,
1133
1191
  projectSettings: SubagentSettings,
@@ -1272,6 +1330,7 @@ function applyBuiltinOverride(
1272
1330
  if (override.thinking !== undefined) { if (override.thinking === false) delete next.thinking; else next.thinking = override.thinking; }
1273
1331
  if (override.systemPromptMode !== undefined) next.systemPromptMode = override.systemPromptMode;
1274
1332
  if (override.inheritProjectContext !== undefined) next.inheritProjectContext = override.inheritProjectContext;
1333
+ if (override.inheritGlobalContext !== undefined) next.inheritGlobalContext = override.inheritGlobalContext;
1275
1334
  if (override.inheritSkills !== undefined) next.inheritSkills = override.inheritSkills;
1276
1335
  if (override.defaultContext !== undefined) { if (override.defaultContext === false) delete next.defaultContext; else next.defaultContext = override.defaultContext; }
1277
1336
  if (override.acceptanceRole !== undefined) { if (override.acceptanceRole === false) delete next.acceptanceRole; else next.acceptanceRole = override.acceptanceRole; }
@@ -1279,8 +1338,10 @@ function applyBuiltinOverride(
1279
1338
  if (override.systemPrompt !== undefined) next.systemPrompt = override.systemPrompt;
1280
1339
  if (override.skills !== undefined) { if (override.skills === false) delete next.skills; else next.skills = [...override.skills]; }
1281
1340
  if (override.tools !== undefined) applyToolsOverride(next, override.tools);
1341
+ if (override.allowNestedSubagents !== undefined) next.allowNestedSubagents = override.allowNestedSubagents;
1282
1342
  if (override.extensions !== undefined) { if (override.extensions === false) delete next.extensions; else next.extensions = [...override.extensions]; }
1283
1343
  if (override.subagentOnlyExtensions !== undefined) { if (override.subagentOnlyExtensions === false) delete next.subagentOnlyExtensions; else next.subagentOnlyExtensions = [...override.subagentOnlyExtensions]; }
1344
+ if (override.mutationTools !== undefined) { if (override.mutationTools === false) delete next.mutationTools; else next.mutationTools = [...override.mutationTools]; }
1284
1345
  if (override.completionGuard !== undefined) next.completionGuard = override.completionGuard;
1285
1346
  if (override.toolBudget !== undefined) { if (override.toolBudget === false) delete next.toolBudget; else next.toolBudget = override.toolBudget; }
1286
1347
 
@@ -1418,6 +1479,9 @@ function applyCustomAgentOverride(
1418
1479
  if (override.inheritProjectContext !== undefined) {
1419
1480
  fill("inheritProjectContext", ["inheritProjectContext"], override.inheritProjectContext);
1420
1481
  }
1482
+ if (override.inheritGlobalContext !== undefined) {
1483
+ fill("inheritGlobalContext", ["inheritGlobalContext"], override.inheritGlobalContext);
1484
+ }
1421
1485
  if (override.inheritSkills !== undefined) {
1422
1486
  fill("inheritSkills", ["inheritSkills"], override.inheritSkills);
1423
1487
  }
@@ -1439,6 +1503,9 @@ function applyCustomAgentOverride(
1439
1503
  applyToolsOverride(mutable(), override.tools);
1440
1504
  anyFilled = true;
1441
1505
  }
1506
+ if (override.allowNestedSubagents !== undefined) {
1507
+ fill("allowNestedSubagents", ["allowNestedSubagents"], override.allowNestedSubagents);
1508
+ }
1442
1509
  if (override.extensions !== undefined) {
1443
1510
  fill("extensions", ["extensions"], override.extensions === false ? undefined : [...override.extensions]);
1444
1511
  }
@@ -1449,6 +1516,9 @@ function applyCustomAgentOverride(
1449
1516
  override.subagentOnlyExtensions === false ? undefined : [...override.subagentOnlyExtensions],
1450
1517
  );
1451
1518
  }
1519
+ if (override.mutationTools !== undefined) {
1520
+ fill("mutationTools", ["mutationTools"], override.mutationTools === false ? undefined : [...override.mutationTools]);
1521
+ }
1452
1522
  if (override.completionGuard !== undefined) {
1453
1523
  fill("completionGuard", ["completionGuard"], override.completionGuard);
1454
1524
  }
@@ -1488,7 +1558,7 @@ function applyCustomAgentOverrides(
1488
1558
 
1489
1559
  export function buildBuiltinOverrideConfig(
1490
1560
  base: BuiltinAgentOverrideBase,
1491
- draft: Pick<AgentConfig, "model" | "modelProvider" | "fallbackModels" | "fast" | "thinking" | "systemPromptMode" | "inheritProjectContext" | "inheritSkills" | "defaultContext" | "acceptanceRole" | "disabled" | "systemPrompt" | "skills" | "tools" | "mcpDirectTools" | "extensions" | "subagentOnlyExtensions" | "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">>,
1492
1562
  ): BuiltinAgentOverrideConfig | undefined {
1493
1563
  const override: BuiltinAgentOverrideConfig = {};
1494
1564
 
@@ -1506,6 +1576,7 @@ export function buildBuiltinOverrideConfig(
1506
1576
  if (draft.thinking !== base.thinking) override.thinking = draft.thinking ?? false;
1507
1577
  if (draft.systemPromptMode !== base.systemPromptMode) override.systemPromptMode = draft.systemPromptMode;
1508
1578
  if (draft.inheritProjectContext !== base.inheritProjectContext) override.inheritProjectContext = draft.inheritProjectContext;
1579
+ if (draft.inheritGlobalContext !== base.inheritGlobalContext) override.inheritGlobalContext = draft.inheritGlobalContext;
1509
1580
  if (draft.inheritSkills !== base.inheritSkills) override.inheritSkills = draft.inheritSkills;
1510
1581
  if (draft.defaultContext !== base.defaultContext) override.defaultContext = draft.defaultContext ?? false;
1511
1582
  if (draft.acceptanceRole !== base.acceptanceRole) override.acceptanceRole = draft.acceptanceRole ?? false;
@@ -1516,10 +1587,12 @@ export function buildBuiltinOverrideConfig(
1516
1587
  const baseTools = joinToolList(base);
1517
1588
  const draftTools = joinToolList(draft);
1518
1589
  if (!arraysEqual(draftTools, baseTools)) override.tools = draftTools ? [...draftTools] : false;
1590
+ if (draft.allowNestedSubagents !== base.allowNestedSubagents) override.allowNestedSubagents = draft.allowNestedSubagents === true;
1519
1591
  if (!arraysEqual(draft.extensions, base.extensions)) override.extensions = draft.extensions ? [...draft.extensions] : false;
1520
1592
  if (!arraysEqual(draft.subagentOnlyExtensions, base.subagentOnlyExtensions)) {
1521
1593
  override.subagentOnlyExtensions = draft.subagentOnlyExtensions ? [...draft.subagentOnlyExtensions] : false;
1522
1594
  }
1595
+ if (!arraysEqual(draft.mutationTools, base.mutationTools)) override.mutationTools = draft.mutationTools ? [...draft.mutationTools] : false;
1523
1596
  if ((draft.completionGuard !== false) !== (base.completionGuard !== false)) {
1524
1597
  override.completionGuard = draft.completionGuard !== false;
1525
1598
  }
@@ -1649,7 +1722,7 @@ export function removeBuiltinAgentOverrideFields(
1649
1722
  return { path: filePath, removed: true };
1650
1723
  }
1651
1724
 
1652
- const DISCOVERY_PRUNED_DIR_NAMES = new Set([".git", "node_modules"]);
1725
+ const DISCOVERY_PRUNED_DIR_NAMES = new Set([".git", "node_modules", ".pi", "sync-backups"]);
1653
1726
 
1654
1727
  function isDiscoveryNestedProjectRoot(dir: string): boolean {
1655
1728
  return isDirectory(getProjectConfigDir(dir)) || isDirectory(path.join(dir, ".agents"));
@@ -1863,7 +1936,7 @@ function parseAgentRunnerFrontmatter(raw: string | undefined, agentName: string)
1863
1936
 
1864
1937
  function validateExternalRunnerProfile(frontmatter: Record<string, string>, agentName: string, runner: AgentRunnerConfig | undefined): void {
1865
1938
  if (runner?.type !== "external-cli" && runner?.type !== "external-job") return;
1866
- const unsupported = ["tools", "model", "fallbackModels", "thinking", "extensions", "subagentOnlyExtensions", "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"]
1867
1940
  .filter((field) => frontmatter[field] !== undefined);
1868
1941
  if (unsupported.length > 0) {
1869
1942
  throw new Error(`Agent '${agentName}' uses runner.type='${runner.type}' and declares unsupported Pi-only fields: ${unsupported.join(", ")}.`);
@@ -1963,6 +2036,7 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
1963
2036
  : frontmatter.inheritProjectContext === "false"
1964
2037
  ? false
1965
2038
  : defaultInheritProjectContext(localName);
2039
+ const inheritGlobalContext = frontmatter.inheritGlobalContext === "true";
1966
2040
  const inheritSkills = frontmatter.inheritSkills === "true"
1967
2041
  ? true
1968
2042
  : frontmatter.inheritSkills === "false"
@@ -1995,13 +2069,6 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
1995
2069
  }
1996
2070
  defaultToolTimeoutMs = parsed;
1997
2071
  }
1998
- let defaultTurnBudget: TurnBudgetConfig | undefined;
1999
- if (frontmatter.turnBudget !== undefined && frontmatter.turnBudget.trim()) {
2000
- const parsed = JSON.parse(frontmatter.turnBudget) as unknown;
2001
- const resolved = resolveTurnBudgetConfig(parsed, `Agent '${localName}' turnBudget frontmatter`);
2002
- if (resolved.error) throw new Error(resolved.error);
2003
- defaultTurnBudget = resolved.turnBudget;
2004
- }
2005
2072
  const defaultAcceptance = parseAgentAcceptanceFrontmatter(frontmatter.acceptance, localName);
2006
2073
  let outputMode: OutputMode | undefined;
2007
2074
  if (frontmatter.outputMode !== undefined) {
@@ -2016,12 +2083,19 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
2016
2083
 
2017
2084
  const extensions = resolveAgentRelativeExtensionPaths(parseFrontmatterList(frontmatter.extensions), filePath);
2018
2085
  const subagentOnlyExtensions = resolveAgentRelativeExtensionPaths(parseFrontmatterList(frontmatter.subagentOnlyExtensions), filePath);
2086
+ const mutationTools = parseFrontmatterList(frontmatter.mutationTools);
2019
2087
  let fast: boolean | undefined;
2020
2088
  if (frontmatter.fast !== undefined) {
2021
2089
  if (frontmatter.fast === "true") fast = true;
2022
2090
  else if (frontmatter.fast === "false") fast = false;
2023
2091
  else throw new Error(`Agent '${localName}' has invalid fast frontmatter; expected true or false.`);
2024
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
+ }
2025
2099
 
2026
2100
  const extraFields: Record<string, string> = {};
2027
2101
  for (const [key, value] of Object.entries(frontmatter)) {
@@ -2065,6 +2139,7 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
2065
2139
  description: frontmatter.description,
2066
2140
  ...(aliases !== undefined ? { aliases } : {}),
2067
2141
  ...(rawTools !== undefined ? { tools } : {}),
2142
+ ...(allowNestedSubagents !== undefined ? { allowNestedSubagents } : {}),
2068
2143
  ...(mcpDirectTools.length > 0 ? { mcpDirectTools } : {}),
2069
2144
  ...(frontmatter.model !== undefined ? { model: frontmatter.model } : {}),
2070
2145
  ...(fallbackModels?.length ? { fallbackModels } : {}),
@@ -2072,12 +2147,12 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
2072
2147
  ...(frontmatter.thinking !== undefined ? { thinking: frontmatter.thinking === "false" ? false : frontmatter.thinking } : {}),
2073
2148
  systemPromptMode,
2074
2149
  inheritProjectContext,
2150
+ inheritGlobalContext,
2075
2151
  inheritSkills,
2076
2152
  ...(defaultContext !== undefined ? { defaultContext } : {}),
2077
2153
  ...(defaultAsync !== undefined ? { defaultAsync } : {}),
2078
2154
  ...(defaultTimeoutMs !== undefined ? { defaultTimeoutMs } : {}),
2079
2155
  ...(defaultToolTimeoutMs !== undefined ? { defaultToolTimeoutMs } : {}),
2080
- ...(defaultTurnBudget !== undefined ? { defaultTurnBudget } : {}),
2081
2156
  ...(defaultAcceptance !== undefined ? { defaultAcceptance } : {}),
2082
2157
  ...(acceptanceRole !== undefined ? { acceptanceRole } : {}),
2083
2158
  systemPrompt: body,
@@ -2088,6 +2163,7 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
2088
2163
  ...(skillPath?.length ? { skillPath } : {}),
2089
2164
  ...(extensions !== undefined ? { extensions } : {}),
2090
2165
  ...(subagentOnlyExtensions !== undefined ? { subagentOnlyExtensions } : {}),
2166
+ ...(mutationTools?.length ? { mutationTools } : {}),
2091
2167
  ...(frontmatter.output !== undefined ? { output: frontmatter.output } : {}),
2092
2168
  ...(outputMode !== undefined ? { outputMode } : {}),
2093
2169
  ...(defaultReads?.length ? { defaultReads } : {}),
@@ -2202,15 +2278,15 @@ function extraUserAgentDirs(): string[] {
2202
2278
  .filter((dir) => dir.length > 0);
2203
2279
  }
2204
2280
 
2205
- export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryResult {
2281
+ export function discoverAgents(cwd: string, scope: AgentScope, preferredModelProvider?: string): AgentDiscoveryResult {
2206
2282
  const effectiveCwd = path.resolve(cwd);
2207
2283
  const userDirOld = path.join(getAgentDir(), "agents");
2208
2284
  const userDirNew = path.join(os.homedir(), ".agents");
2209
2285
  const { readDirs: projectAgentDirs, candidateDirs: projectCandidateDirs, preferredDir: projectAgentsDir } = resolveNearestProjectAgentDirs(effectiveCwd);
2210
2286
  const userSettingsPath = getUserAgentSettingsPath();
2211
2287
  const projectSettingsPath = getProjectAgentSettingsPath(effectiveCwd);
2212
- const userSettings = scope === "project" ? EMPTY_SUBAGENT_SETTINGS : readSubagentSettings(userSettingsPath);
2213
- 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);
2214
2290
  const defaultProvider = resolveSubagentDefaultProvider(userSettings, projectSettings, projectSettingsPath);
2215
2291
  const defaultModel = resolveSubagentDefaultModel(userSettings, projectSettings, userSettingsPath, projectSettingsPath, defaultProvider);
2216
2292
  const defaultThinking = resolveSubagentDefaultThinking(userSettings, projectSettings, projectSettingsPath);
@@ -2273,7 +2349,7 @@ export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryRe
2273
2349
  return { agents, agentDiagnostics, projectAgentsDir, cwd: effectiveCwd, scope, directories, ...(modelScope !== undefined ? { modelScope } : {}), ...(maxThinking !== undefined ? { maxThinking } : {}) };
2274
2350
  }
2275
2351
 
2276
- export function discoverAgentsAll(cwd: string): {
2352
+ export function discoverAgentsAll(cwd: string, preferredModelProvider?: string): {
2277
2353
  builtin: AgentConfig[];
2278
2354
  package: AgentConfig[];
2279
2355
  user: AgentConfig[];
@@ -2296,8 +2372,8 @@ export function discoverAgentsAll(cwd: string): {
2296
2372
  const { readDirs: projectChainDirs, preferredDir: projectChainDir } = resolveNearestProjectChainDirs(cwd);
2297
2373
  const userSettingsPath = getUserAgentSettingsPath();
2298
2374
  const projectSettingsPath = getProjectAgentSettingsPath(cwd);
2299
- const userSettings = readSubagentSettings(userSettingsPath);
2300
- const projectSettings = readSubagentSettings(projectSettingsPath);
2375
+ const userSettings = selectProviderOverrides(readSubagentSettings(userSettingsPath), preferredModelProvider);
2376
+ const projectSettings = selectProviderOverrides(readSubagentSettings(projectSettingsPath), preferredModelProvider);
2301
2377
  const defaultProvider = resolveSubagentDefaultProvider(userSettings, projectSettings, projectSettingsPath);
2302
2378
  const defaultModel = resolveSubagentDefaultModel(userSettings, projectSettings, userSettingsPath, projectSettingsPath, defaultProvider);
2303
2379
  const defaultThinking = resolveSubagentDefaultThinking(userSettings, projectSettings, projectSettingsPath);
@@ -0,0 +1,70 @@
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ import { registerRuntimeAgent, type RuntimeAgentDefinition, type RuntimeAgentRegistration } from "./runtime-agent-registry.ts";
3
+
4
+ export const RUNTIME_AGENT_REGISTER_EVENT = "pi-subagents:runtime-agent-register:v1";
5
+ export const RUNTIME_AGENT_REGISTER_VERSION = 1;
6
+
7
+ export type RuntimeAgentRegistrationResult =
8
+ | { ok: true; registration: RuntimeAgentRegistration }
9
+ | { ok: false; error: Error };
10
+
11
+ export interface RuntimeAgentRegistrationRequest {
12
+ version: 1;
13
+ name: string;
14
+ definition: RuntimeAgentDefinition;
15
+ result?: RuntimeAgentRegistrationResult;
16
+ }
17
+
18
+ export interface RegisterRuntimeAgentViaEventsInput {
19
+ pi: Pick<ExtensionAPI, "events">;
20
+ name: string;
21
+ definition: RuntimeAgentDefinition;
22
+ }
23
+
24
+ function errorFrom(value: unknown): Error {
25
+ return value instanceof Error ? value : new Error(String(value));
26
+ }
27
+
28
+ /** Register through the installed pi-subagents owner in this Pi process. */
29
+ export function registerAgentViaEvents(input: RegisterRuntimeAgentViaEventsInput): RuntimeAgentRegistration {
30
+ const request: RuntimeAgentRegistrationRequest = {
31
+ version: RUNTIME_AGENT_REGISTER_VERSION,
32
+ name: input.name,
33
+ definition: input.definition,
34
+ };
35
+ input.pi.events.emit(RUNTIME_AGENT_REGISTER_EVENT, request);
36
+ const result = request.result as unknown;
37
+ if (result === undefined) {
38
+ throw new Error("pi-subagents is not installed, not ready, or does not support runtime agent event registration.");
39
+ }
40
+ if (result && typeof result === "object" && !Array.isArray(result)) {
41
+ const candidate = result as Record<string, unknown>;
42
+ if (candidate.ok === true && candidate.registration && typeof candidate.registration === "object" && typeof (candidate.registration as { dispose?: unknown }).dispose === "function") {
43
+ return candidate.registration as RuntimeAgentRegistration;
44
+ }
45
+ if (candidate.ok === false && candidate.error instanceof Error) throw candidate.error;
46
+ }
47
+ throw new Error("pi-subagents returned a malformed runtime agent registration result.");
48
+ }
49
+
50
+ /** Install the process-local registration listener for the owning pi-subagents runtime. */
51
+ export function registerRuntimeAgentEventListener(pi: ExtensionAPI): () => void {
52
+ return pi.events.on(RUNTIME_AGENT_REGISTER_EVENT, (rawRequest) => {
53
+ if (!rawRequest || typeof rawRequest !== "object" || Array.isArray(rawRequest)) return;
54
+ const request = rawRequest as Record<string, unknown>;
55
+ if (request.result !== undefined) return;
56
+ try {
57
+ if (request.version !== RUNTIME_AGENT_REGISTER_VERSION) {
58
+ throw new Error(`Unsupported runtime agent registration event version '${String(request.version)}'.`);
59
+ }
60
+ const registration = registerRuntimeAgent({
61
+ pi,
62
+ name: request.name as string,
63
+ definition: request.definition as RuntimeAgentDefinition,
64
+ });
65
+ request.result = { ok: true, registration } satisfies RuntimeAgentRegistrationResult;
66
+ } catch (error) {
67
+ request.result = { ok: false, error: errorFrom(error) } satisfies RuntimeAgentRegistrationResult;
68
+ }
69
+ });
70
+ }