pi-subagents 0.54.0 → 0.55.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 (61) hide show
  1. package/CHANGELOG.md +35 -1
  2. package/agents/reviewer.md +12 -2
  3. package/docs/agents.md +4 -4
  4. package/docs/configuration.md +4 -4
  5. package/docs/extension-api.md +14 -4
  6. package/docs/models.md +24 -3
  7. package/docs/observability.md +14 -3
  8. package/docs/tool-reference.md +21 -6
  9. package/docs/workflows.md +7 -1
  10. package/package.json +1 -1
  11. package/prompts/council.md +9 -0
  12. package/prompts/parallel-review.md +5 -1
  13. package/prompts/review-loop.md +9 -5
  14. package/skills/council-mode/SKILL.md +33 -10
  15. package/skills/pi-subagents/references/constraints-and-recipes.md +16 -1
  16. package/skills/pi-subagents/references/execution-controls.md +11 -4
  17. package/skills/pi-subagents/references/multi-lane-orchestration.md +3 -3
  18. package/skills/pi-subagents/references/prompting-and-roles.md +8 -8
  19. package/src/agents/agent-management.ts +30 -9
  20. package/src/agents/agent-serializer.ts +2 -2
  21. package/src/agents/agents.ts +146 -41
  22. package/src/api/external-job-provider.ts +10 -1
  23. package/src/api/preflight.ts +26 -17
  24. package/src/api/project-panes.ts +2 -0
  25. package/src/extension/index.ts +20 -1
  26. package/src/extension/public-execution.ts +12 -9
  27. package/src/extension/rpc.ts +77 -3
  28. package/src/extension/schemas.ts +2 -1
  29. package/src/extension/tool-description.ts +9 -6
  30. package/src/inspectors/herdr/focus.ts +55 -0
  31. package/src/inspectors/herdr/project-panes.ts +228 -44
  32. package/src/integrations/herdr-status.ts +26 -4
  33. package/src/runs/background/async-execution.ts +103 -9
  34. package/src/runs/background/async-job-tracker.ts +33 -14
  35. package/src/runs/background/async-resume.ts +20 -2
  36. package/src/runs/background/async-status.ts +10 -0
  37. package/src/runs/background/control-channel.ts +98 -10
  38. package/src/runs/background/notify.ts +62 -1
  39. package/src/runs/background/run-status.ts +15 -1
  40. package/src/runs/background/subagent-runner.ts +226 -37
  41. package/src/runs/foreground/async-stop-action.ts +23 -2
  42. package/src/runs/foreground/execution.ts +81 -7
  43. package/src/runs/foreground/subagent-executor.ts +348 -40
  44. package/src/runs/foreground/workflow-detach-reconcile.ts +3 -0
  45. package/src/runs/shared/acceptance.ts +1 -0
  46. package/src/runs/shared/child-identity.ts +36 -0
  47. package/src/runs/shared/completion-guard.ts +50 -1
  48. package/src/runs/shared/external-job-bridge.ts +53 -37
  49. package/src/runs/shared/external-job-runner.ts +126 -23
  50. package/src/runs/shared/model-fallback.ts +10 -0
  51. package/src/runs/shared/orca-progress-tabs.ts +71 -9
  52. package/src/runs/shared/parallel-utils.ts +12 -0
  53. package/src/runs/shared/pi-args.ts +9 -12
  54. package/src/runs/shared/subagent-prompt-runtime.ts +3 -1
  55. package/src/runs/shared/tool-availability.ts +1 -3
  56. package/src/shared/launch-contract.ts +6 -5
  57. package/src/shared/thinking-ceiling.ts +52 -0
  58. package/src/shared/types.ts +59 -2
  59. package/src/tui/fleet-status.ts +66 -13
  60. package/src/tui/render.ts +5 -4
  61. package/src/workflows/scripted-workflow.ts +107 -10
@@ -22,6 +22,7 @@ import { parseMemoryFrontmatter } from "./agent-memory.ts";
22
22
  import { resolveTurnBudgetConfig } from "../runs/shared/turn-budget.ts";
23
23
  import { validateAcceptanceInput } from "../runs/shared/acceptance.ts";
24
24
  import { validatePermissionRules, type PermissionRules } from "../runs/shared/permissions.ts";
25
+ import { parseThinkingLevel, type ThinkingLevel } from "../shared/thinking-ceiling.ts";
25
26
 
26
27
  export type AgentScope = "user" | "project" | "both";
27
28
 
@@ -50,8 +51,11 @@ export function defaultInheritSkills(): boolean {
50
51
 
51
52
  export interface BuiltinAgentOverrideBase {
52
53
  description?: string;
54
+ output?: string;
53
55
  outputMode?: OutputMode;
56
+ defaultReads?: string[];
54
57
  model?: string;
58
+ modelProvider?: string;
55
59
  fallbackModels?: string[];
56
60
  thinking?: string | false;
57
61
  systemPromptMode: SystemPromptMode;
@@ -73,8 +77,11 @@ export interface BuiltinAgentOverrideBase {
73
77
 
74
78
  interface BuiltinAgentOverrideConfig {
75
79
  description?: string;
80
+ output?: string | false;
76
81
  outputMode?: OutputMode;
82
+ defaultReads?: string[] | false;
77
83
  model?: string | false;
84
+ defaultProvider?: string | false;
78
85
  fallbackModels?: string[] | false;
79
86
  thinking?: string | false;
80
87
  systemPromptMode?: SystemPromptMode;
@@ -103,6 +110,7 @@ export interface AgentModelSourceInfo {
103
110
  scope: "user" | "project";
104
111
  path: string;
105
112
  model: string;
113
+ defaultProvider?: string;
106
114
  }
107
115
 
108
116
  export interface AgentConfig {
@@ -118,6 +126,7 @@ export interface AgentConfig {
118
126
  tools?: string[];
119
127
  mcpDirectTools?: string[];
120
128
  model?: string;
129
+ modelProvider?: string;
121
130
  fallbackModels?: string[];
122
131
  thinking?: string | false;
123
132
  systemPromptMode: SystemPromptMode;
@@ -153,6 +162,7 @@ export interface AgentConfig {
153
162
  extraFields?: Record<string, string>;
154
163
  override?: BuiltinAgentOverrideInfo;
155
164
  modelSource?: AgentModelSourceInfo;
165
+ maxThinking?: ThinkingLevel;
156
166
  }
157
167
 
158
168
  type ProjectRootResolution = "nearest" | "git-root";
@@ -160,7 +170,9 @@ type ProjectRootResolution = "nearest" | "git-root";
160
170
  interface SubagentSettings {
161
171
  overrides: Record<string, BuiltinAgentOverrideConfig>;
162
172
  defaultModel?: string;
173
+ defaultProvider?: string;
163
174
  defaultThinking?: string;
175
+ maxThinking?: ThinkingLevel;
164
176
  defaultExtensions?: string[];
165
177
  disableBuiltins?: boolean;
166
178
  disableThinking?: boolean;
@@ -252,6 +264,7 @@ interface AgentDiscoveryResult {
252
264
  agentDiagnostics?: AgentDiscoveryDiagnostic[];
253
265
  projectAgentsDir: string | null;
254
266
  modelScope?: ModelScopeConfig;
267
+ maxThinking?: ThinkingLevel;
255
268
  }
256
269
 
257
270
  function getUserChainDir(): string {
@@ -621,8 +634,11 @@ function arraysEqual(a: string[] | undefined, b: string[] | undefined): boolean
621
634
  function cloneOverrideBase(agent: AgentConfig): BuiltinAgentOverrideBase {
622
635
  return {
623
636
  description: agent.description,
637
+ ...(agent.output !== undefined ? { output: agent.output } : {}),
624
638
  ...(agent.outputMode !== undefined ? { outputMode: agent.outputMode } : {}),
639
+ ...(agent.defaultReads !== undefined ? { defaultReads: [...agent.defaultReads] } : {}),
625
640
  ...(agent.model !== undefined ? { model: agent.model } : {}),
641
+ ...(agent.modelProvider !== undefined ? { modelProvider: agent.modelProvider } : {}),
626
642
  ...(agent.fallbackModels ? { fallbackModels: [...agent.fallbackModels] } : {}),
627
643
  ...(agent.thinking !== undefined ? { thinking: agent.thinking } : {}),
628
644
  systemPromptMode: agent.systemPromptMode,
@@ -646,8 +662,11 @@ function cloneOverrideBase(agent: AgentConfig): BuiltinAgentOverrideBase {
646
662
  function cloneOverrideValue(override: BuiltinAgentOverrideConfig): BuiltinAgentOverrideConfig {
647
663
  return {
648
664
  ...(override.description !== undefined ? { description: override.description } : {}),
665
+ ...(override.output !== undefined ? { output: override.output } : {}),
649
666
  ...(override.outputMode !== undefined ? { outputMode: override.outputMode } : {}),
667
+ ...(override.defaultReads !== undefined ? { defaultReads: override.defaultReads === false ? false : [...override.defaultReads] } : {}),
650
668
  ...(override.model !== undefined ? { model: override.model } : {}),
669
+ ...(override.defaultProvider !== undefined ? { defaultProvider: override.defaultProvider } : {}),
651
670
  ...(override.fallbackModels !== undefined
652
671
  ? { fallbackModels: override.fallbackModels === false ? false : [...override.fallbackModels] }
653
672
  : {}),
@@ -827,6 +846,11 @@ function parseBuiltinOverrideEntry(
827
846
  }
828
847
  }
829
848
 
849
+ if ("output" in input) {
850
+ if ((typeof input.output === "string" && input.output.trim()) || input.output === false) override.output = input.output;
851
+ else throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'output'; expected a non-empty string or false.`);
852
+ }
853
+
830
854
  if ("outputMode" in input) {
831
855
  if (input.outputMode === "inline" || input.outputMode === "file-only") {
832
856
  override.outputMode = input.outputMode;
@@ -916,9 +940,18 @@ function parseBuiltinOverrideEntry(
916
940
  else throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'systemPrompt'; expected a string.`);
917
941
  }
918
942
 
943
+ const defaultReads = parseOverrideStringArrayOrFalse(input.defaultReads, { filePath, name, field: "defaultReads" });
944
+ if (defaultReads !== undefined) override.defaultReads = defaultReads;
945
+
919
946
  const fallbackModels = parseOverrideStringArrayOrFalse(input.fallbackModels, { filePath, name, field: "fallbackModels" });
920
947
  if (fallbackModels !== undefined) override.fallbackModels = fallbackModels;
921
948
 
949
+ if ("defaultProvider" in input) {
950
+ if (input.defaultProvider === false) override.defaultProvider = false;
951
+ else if (typeof input.defaultProvider === "string" && input.defaultProvider.trim()) override.defaultProvider = input.defaultProvider.trim();
952
+ else throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'defaultProvider'; expected a non-empty string or false.`);
953
+ }
954
+
922
955
  const skills = parseOverrideStringArrayOrFalse(input.skills, { filePath, name, field: "skills" });
923
956
  if (skills !== undefined) override.skills = skills;
924
957
 
@@ -965,6 +998,14 @@ function readSubagentSettings(filePath: string | null): SubagentSettings {
965
998
  throw new Error(`Subagent settings in '${filePath}' have invalid 'defaultModel'; expected a non-empty string.`);
966
999
  }
967
1000
  }
1001
+ let defaultProvider: string | undefined;
1002
+ if ("defaultProvider" in subagentsObject) {
1003
+ if (typeof subagentsObject.defaultProvider === "string" && subagentsObject.defaultProvider.trim()) {
1004
+ defaultProvider = subagentsObject.defaultProvider.trim();
1005
+ } else {
1006
+ throw new Error(`Subagent settings in '${filePath}' have invalid 'defaultProvider'; expected a non-empty string.`);
1007
+ }
1008
+ }
968
1009
  let defaultThinking: string | undefined;
969
1010
  if ("defaultThinking" in subagentsObject) {
970
1011
  if (typeof subagentsObject.defaultThinking === "string" && subagentsObject.defaultThinking.trim()) {
@@ -973,6 +1014,14 @@ function readSubagentSettings(filePath: string | null): SubagentSettings {
973
1014
  throw new Error(`Subagent settings in '${filePath}' have invalid 'defaultThinking'; expected a non-empty string.`);
974
1015
  }
975
1016
  }
1017
+ let maxThinking: ThinkingLevel | undefined;
1018
+ if ("maxThinking" in subagentsObject) {
1019
+ try {
1020
+ maxThinking = parseThinkingLevel(subagentsObject.maxThinking, `'${filePath}' subagents.maxThinking`);
1021
+ } catch (error) {
1022
+ throw new Error(`Subagent settings in '${filePath}' have invalid 'maxThinking'; expected one of off, minimal, low, medium, high, xhigh, or max.`, { cause: error instanceof Error ? error : undefined });
1023
+ }
1024
+ }
976
1025
  let defaultExtensions: string[] | undefined;
977
1026
  if ("defaultExtensions" in subagentsObject) {
978
1027
  if (!Array.isArray(subagentsObject.defaultExtensions)
@@ -988,7 +1037,9 @@ function readSubagentSettings(filePath: string | null): SubagentSettings {
988
1037
  const parsedSettings: SubagentSettings = {
989
1038
  overrides: parsed,
990
1039
  ...(defaultModel !== undefined ? { defaultModel } : {}),
1040
+ ...(defaultProvider !== undefined ? { defaultProvider } : {}),
991
1041
  ...(defaultThinking !== undefined ? { defaultThinking } : {}),
1042
+ ...(maxThinking !== undefined ? { maxThinking } : {}),
992
1043
  ...(defaultExtensions !== undefined ? { defaultExtensions } : {}),
993
1044
  ...(disableBuiltins !== undefined ? { disableBuiltins } : {}),
994
1045
  ...(disableThinking !== undefined ? { disableThinking } : {}),
@@ -1004,25 +1055,39 @@ function readSubagentSettings(filePath: string | null): SubagentSettings {
1004
1055
  return parsedSettings;
1005
1056
  }
1006
1057
 
1058
+ function resolveSubagentDefaultProvider(
1059
+ userSettings: SubagentSettings,
1060
+ projectSettings: SubagentSettings,
1061
+ projectSettingsPath: string | null,
1062
+ ): string | undefined {
1063
+ if (projectSettingsPath && projectSettings.defaultProvider !== undefined) return projectSettings.defaultProvider;
1064
+ return userSettings.defaultProvider;
1065
+ }
1066
+
1007
1067
  function resolveSubagentDefaultModel(
1008
1068
  userSettings: SubagentSettings,
1009
1069
  projectSettings: SubagentSettings,
1010
1070
  userSettingsPath: string,
1011
1071
  projectSettingsPath: string | null,
1072
+ defaultProvider: string | undefined,
1012
1073
  ): AgentModelSourceInfo | undefined {
1013
1074
  if (projectSettingsPath && projectSettings.defaultModel !== undefined) {
1014
- return { type: "subagents.defaultModel", scope: "project", path: projectSettingsPath, model: projectSettings.defaultModel };
1075
+ return { type: "subagents.defaultModel", scope: "project", path: projectSettingsPath, model: projectSettings.defaultModel, ...(defaultProvider ? { defaultProvider } : {}) };
1015
1076
  }
1016
1077
  return userSettings.defaultModel !== undefined
1017
- ? { type: "subagents.defaultModel", scope: "user", path: userSettingsPath, model: userSettings.defaultModel }
1078
+ ? { type: "subagents.defaultModel", scope: "user", path: userSettingsPath, model: userSettings.defaultModel, ...(defaultProvider ? { defaultProvider } : {}) }
1018
1079
  : undefined;
1019
1080
  }
1020
1081
 
1021
- function applySubagentDefaultModel(agents: AgentConfig[], defaultModel: AgentModelSourceInfo | undefined): AgentConfig[] {
1022
- if (!defaultModel) return agents;
1082
+ function applySubagentDefaultModel(agents: AgentConfig[], defaultModel: AgentModelSourceInfo | undefined, defaultProvider: string | undefined): AgentConfig[] {
1083
+ if (!defaultModel && !defaultProvider) return agents;
1023
1084
  return agents.map((agent) => {
1024
- if (agent.model !== undefined) return agent;
1025
- const next = { ...agent, model: defaultModel.model, modelSource: defaultModel };
1085
+ if (agent.model !== undefined && (agent.modelProvider !== undefined || !defaultProvider)) return agent;
1086
+ const next = {
1087
+ ...agent,
1088
+ ...(agent.model === undefined && defaultModel ? { model: defaultModel.model, modelSource: defaultModel } : {}),
1089
+ ...(defaultProvider ? { modelProvider: defaultProvider } : {}),
1090
+ };
1026
1091
  const frontmatterFields = agentFrontmatterFields.get(agent);
1027
1092
  if (frontmatterFields) agentFrontmatterFields.set(next, frontmatterFields);
1028
1093
  return next;
@@ -1049,6 +1114,20 @@ function applySubagentDefaultThinking(agents: AgentConfig[], defaultThinking: st
1049
1114
  });
1050
1115
  }
1051
1116
 
1117
+ function resolveSubagentMaxThinking(
1118
+ userSettings: SubagentSettings,
1119
+ projectSettings: SubagentSettings,
1120
+ projectSettingsPath: string | null,
1121
+ ): ThinkingLevel | undefined {
1122
+ if (projectSettingsPath && projectSettings.maxThinking !== undefined) return projectSettings.maxThinking;
1123
+ return userSettings.maxThinking;
1124
+ }
1125
+
1126
+ function applySubagentMaxThinking(agents: AgentConfig[], maxThinking: ThinkingLevel | undefined): AgentConfig[] {
1127
+ if (maxThinking === undefined) return agents;
1128
+ return agents.map((agent) => agent.maxThinking === maxThinking ? agent : { ...agent, maxThinking });
1129
+ }
1130
+
1052
1131
  function resolveSubagentDefaultExtensions(
1053
1132
  userSettings: SubagentSettings,
1054
1133
  projectSettings: SubagentSettings,
@@ -1072,11 +1151,12 @@ function applySubagentDefaultExtensions(agents: AgentConfig[], defaultExtensions
1072
1151
  function applySubagentDefaults(
1073
1152
  agents: AgentConfig[],
1074
1153
  defaultModel: AgentModelSourceInfo | undefined,
1154
+ defaultProvider: string | undefined,
1075
1155
  defaultThinking: string | undefined,
1076
1156
  defaultExtensions: string[] | undefined,
1077
1157
  ): AgentConfig[] {
1078
1158
  return applySubagentDefaultExtensions(
1079
- applySubagentDefaultThinking(applySubagentDefaultModel(agents, defaultModel), defaultThinking),
1159
+ applySubagentDefaultThinking(applySubagentDefaultModel(agents, defaultModel, defaultProvider), defaultThinking),
1080
1160
  defaultExtensions,
1081
1161
  );
1082
1162
  }
@@ -1103,11 +1183,17 @@ function applyBuiltinOverride(
1103
1183
  };
1104
1184
 
1105
1185
  if (override.description !== undefined) next.description = override.description;
1186
+ if (override.output !== undefined) { if (override.output === false) delete next.output; else next.output = override.output; }
1106
1187
  if (override.outputMode !== undefined) next.outputMode = override.outputMode;
1188
+ if (override.defaultReads !== undefined) { if (override.defaultReads === false) delete next.defaultReads; else next.defaultReads = [...override.defaultReads]; }
1107
1189
  if (override.model !== undefined) {
1108
1190
  if (override.model === false) delete next.model; else next.model = override.model;
1109
1191
  delete next.modelSource;
1110
1192
  }
1193
+ if (override.defaultProvider !== undefined) {
1194
+ if (override.defaultProvider === false) delete next.modelProvider;
1195
+ else next.modelProvider = override.defaultProvider;
1196
+ }
1111
1197
  if (override.fallbackModels !== undefined) { if (override.fallbackModels === false) delete next.fallbackModels; else next.fallbackModels = [...override.fallbackModels]; }
1112
1198
  if (override.thinking !== undefined) { if (override.thinking === false) delete next.thinking; else next.thinking = override.thinking; }
1113
1199
  if (override.systemPromptMode !== undefined) next.systemPromptMode = override.systemPromptMode;
@@ -1221,15 +1307,24 @@ function applyCustomAgentOverride(
1221
1307
  mutable().description = override.description;
1222
1308
  anyFilled = true;
1223
1309
  }
1310
+ if (override.output !== undefined) {
1311
+ fill("output", ["output"], override.output === false ? undefined : override.output);
1312
+ }
1224
1313
  if (override.outputMode !== undefined) {
1225
1314
  fill("outputMode", ["outputMode"], override.outputMode);
1226
1315
  }
1316
+ if (override.defaultReads !== undefined) {
1317
+ fill("defaultReads", ["defaultReads"], override.defaultReads === false ? undefined : [...override.defaultReads]);
1318
+ }
1227
1319
  if (override.model !== undefined && !agentHasFrontmatterField(agent, "model")) {
1228
1320
  const target = mutable();
1229
1321
  if (override.model === false) delete target.model; else target.model = override.model;
1230
1322
  delete target.modelSource;
1231
1323
  anyFilled = true;
1232
1324
  }
1325
+ if (override.defaultProvider !== undefined) {
1326
+ fill("modelProvider", ["modelProvider", "defaultProvider"], override.defaultProvider === false ? undefined : override.defaultProvider);
1327
+ }
1233
1328
  if (override.fallbackModels !== undefined) {
1234
1329
  fill(
1235
1330
  "fallbackModels",
@@ -1256,17 +1351,7 @@ function applyCustomAgentOverride(
1256
1351
  fill("acceptanceRole", ["acceptanceRole"], override.acceptanceRole === false ? undefined : override.acceptanceRole);
1257
1352
  }
1258
1353
  if (override.disabled !== undefined) {
1259
- // Unconditional, matching applyBuiltinOverride: custom agents have no
1260
- // frontmatter concept of `disabled` to protect (unlike model/thinking/
1261
- // etc., which use `fill()` + agentHasFrontmatterField to defer to the
1262
- // agent's own file). A guard here previously read `agent.disabled ===
1263
- // undefined`, which was a no-op before user+project layering existed
1264
- // (this function only ever ran once, against the pristine base agent,
1265
- // whose `.disabled` is always undefined for custom agents) but became a
1266
- // real bug once a user-scope override could run first: the guard then
1267
- // silently blocked a later project-scope override from ever changing
1268
- // `disabled`, breaking this PR's own "project wins" precedence for this
1269
- // one field.
1354
+ // Custom agent files cannot set `disabled`, so project overrides replace user overrides.
1270
1355
  mutable().disabled = override.disabled;
1271
1356
  anyFilled = true;
1272
1357
  }
@@ -1295,7 +1380,7 @@ function applyCustomAgentOverride(
1295
1380
  }
1296
1381
 
1297
1382
  if (!anyFilled || !next) return agent;
1298
- next.override = { ...meta, base: cloneOverrideBase(agent) };
1383
+ next.override = { ...meta, base: agent.override?.base ?? cloneOverrideBase(agent) };
1299
1384
  const frontmatterFields = agentFrontmatterFields.get(agent);
1300
1385
  if (frontmatterFields) agentFrontmatterFields.set(next, frontmatterFields);
1301
1386
  return next;
@@ -1308,13 +1393,7 @@ function applyCustomAgentOverrides(
1308
1393
  userSettingsPath: string,
1309
1394
  projectSettingsPath: string | null,
1310
1395
  ): AgentConfig[] {
1311
- // Both scopes are applied, user first then project, so a project override that
1312
- // only sets a subset of fields (e.g. just `extensions`) layers on top of the
1313
- // user's override instead of silently discarding it (#1341 / issue writeup:
1314
- // "agentOverrides project override drops user-only fields for custom agents").
1315
- // Per-field precedence still favors project over user: applyCustomAgentOverride
1316
- // only fills fields present in the override it's given, and project is applied
1317
- // last, so any field set at both scopes ends up with the project's value.
1396
+ // Apply user then project so project fields win without dropping user-only fields.
1318
1397
  return agents.map((agent) => {
1319
1398
  const userOverride = userSettings.overrides[agent.name];
1320
1399
  const withUserOverride = userOverride
@@ -1332,7 +1411,7 @@ function applyCustomAgentOverrides(
1332
1411
 
1333
1412
  export function buildBuiltinOverrideConfig(
1334
1413
  base: BuiltinAgentOverrideBase,
1335
- draft: Pick<AgentConfig, "model" | "fallbackModels" | "thinking" | "systemPromptMode" | "inheritProjectContext" | "inheritSkills" | "defaultContext" | "acceptanceRole" | "disabled" | "systemPrompt" | "skills" | "tools" | "mcpDirectTools" | "extensions" | "subagentOnlyExtensions" | "completionGuard" | "toolBudget"> & Partial<Pick<AgentConfig, "description" | "outputMode">>,
1414
+ draft: Pick<AgentConfig, "model" | "modelProvider" | "fallbackModels" | "thinking" | "systemPromptMode" | "inheritProjectContext" | "inheritSkills" | "defaultContext" | "acceptanceRole" | "disabled" | "systemPrompt" | "skills" | "tools" | "mcpDirectTools" | "extensions" | "subagentOnlyExtensions" | "completionGuard" | "toolBudget"> & Partial<Pick<AgentConfig, "description" | "output" | "outputMode" | "defaultReads">>,
1336
1415
  ): BuiltinAgentOverrideConfig | undefined {
1337
1416
  const override: BuiltinAgentOverrideConfig = {};
1338
1417
 
@@ -1340,8 +1419,11 @@ export function buildBuiltinOverrideConfig(
1340
1419
  const description = draft.description.trim();
1341
1420
  if (description && description !== base.description) override.description = description;
1342
1421
  }
1422
+ if (draft.output !== base.output) override.output = draft.output ?? false;
1343
1423
  if (draft.outputMode !== undefined && draft.outputMode !== base.outputMode) override.outputMode = draft.outputMode;
1424
+ if (!arraysEqual(draft.defaultReads, base.defaultReads)) override.defaultReads = draft.defaultReads ? [...draft.defaultReads] : false;
1344
1425
  if (draft.model !== base.model) override.model = draft.model ?? false;
1426
+ if (draft.modelProvider !== base.modelProvider) override.defaultProvider = draft.modelProvider ?? false;
1345
1427
  if (!arraysEqual(draft.fallbackModels, base.fallbackModels)) override.fallbackModels = draft.fallbackModels ? [...draft.fallbackModels] : false;
1346
1428
  if (draft.thinking !== base.thinking) override.thinking = draft.thinking ?? false;
1347
1429
  if (draft.systemPromptMode !== base.systemPromptMode) override.systemPromptMode = draft.systemPromptMode;
@@ -1931,8 +2013,10 @@ export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryRe
1931
2013
  const projectSettingsPath = getProjectAgentSettingsPath(cwd);
1932
2014
  const userSettings = scope === "project" ? EMPTY_SUBAGENT_SETTINGS : readSubagentSettings(userSettingsPath);
1933
2015
  const projectSettings = scope === "user" ? EMPTY_SUBAGENT_SETTINGS : readSubagentSettings(projectSettingsPath);
1934
- const defaultModel = resolveSubagentDefaultModel(userSettings, projectSettings, userSettingsPath, projectSettingsPath);
2016
+ const defaultProvider = resolveSubagentDefaultProvider(userSettings, projectSettings, projectSettingsPath);
2017
+ const defaultModel = resolveSubagentDefaultModel(userSettings, projectSettings, userSettingsPath, projectSettingsPath, defaultProvider);
1935
2018
  const defaultThinking = resolveSubagentDefaultThinking(userSettings, projectSettings, projectSettingsPath);
2019
+ const maxThinking = resolveSubagentMaxThinking(userSettings, projectSettings, projectSettingsPath);
1936
2020
  const defaultExtensions = resolveSubagentDefaultExtensions(userSettings, projectSettings, projectSettingsPath);
1937
2021
  const modelScope = projectSettings.modelScope ?? userSettings.modelScope;
1938
2022
  const packageSubagentPaths = collectPackageSubagentPaths(cwd, {
@@ -1942,7 +2026,7 @@ export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryRe
1942
2026
 
1943
2027
  const builtinLoaded = loadAgentsFromDefinitionFiles(BUILTIN_AGENT_DEFINITION_FILES, "builtin");
1944
2028
  const builtinAgents = applyBuiltinOverrides(
1945
- applySubagentDefaults(builtinLoaded.agents, defaultModel, defaultThinking, defaultExtensions),
2029
+ applySubagentDefaults(builtinLoaded.agents, defaultModel, defaultProvider, defaultThinking, defaultExtensions),
1946
2030
  userSettings,
1947
2031
  projectSettings,
1948
2032
  userSettingsPath,
@@ -1952,7 +2036,7 @@ export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryRe
1952
2036
  const userLoaded = scope === "project" ? [] : [...extraUserAgentDirs(), userDirOld, userDirNew]
1953
2037
  .map((dir, discoveryPriority) => loadAgentsFromDir(dir, "user", discoveryPriority));
1954
2038
  const userAgents = applyCustomAgentOverrides(
1955
- applySubagentDefaults(userLoaded.flatMap((loaded) => loaded.agents), defaultModel, defaultThinking, defaultExtensions),
2039
+ applySubagentDefaults(userLoaded.flatMap((loaded) => loaded.agents), defaultModel, defaultProvider, defaultThinking, defaultExtensions),
1956
2040
  userSettings,
1957
2041
  projectSettings,
1958
2042
  userSettingsPath,
@@ -1961,7 +2045,7 @@ export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryRe
1961
2045
 
1962
2046
  const projectLoaded = scope === "user" ? [] : projectAgentDirs.map((dir) => loadAgentsFromDir(dir, "project", dir === projectAgentsDir ? 1 : 0));
1963
2047
  const projectAgents = applyCustomAgentOverrides(
1964
- applySubagentDefaults(projectLoaded.flatMap((loaded) => loaded.agents), defaultModel, defaultThinking, defaultExtensions),
2048
+ applySubagentDefaults(projectLoaded.flatMap((loaded) => loaded.agents), defaultModel, defaultProvider, defaultThinking, defaultExtensions),
1965
2049
  userSettings,
1966
2050
  projectSettings,
1967
2051
  userSettingsPath,
@@ -1975,14 +2059,17 @@ export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryRe
1975
2059
  }
1976
2060
  }
1977
2061
  const packageAgents = applyCustomAgentOverrides(
1978
- applySubagentDefaults(Array.from(packageMap.values()), defaultModel, defaultThinking, defaultExtensions),
2062
+ applySubagentDefaults(Array.from(packageMap.values()), defaultModel, defaultProvider, defaultThinking, defaultExtensions),
1979
2063
  userSettings,
1980
2064
  projectSettings,
1981
2065
  userSettingsPath,
1982
2066
  projectSettingsPath,
1983
2067
  );
1984
- const agents = mergeAgentsForScope(scope, userAgents, projectAgents, builtinAgents, packageAgents)
1985
- .filter((agent) => agent.disabled !== true);
2068
+ const agents = applySubagentMaxThinking(
2069
+ mergeAgentsForScope(scope, userAgents, projectAgents, builtinAgents, packageAgents)
2070
+ .filter((agent) => agent.disabled !== true),
2071
+ maxThinking,
2072
+ );
1986
2073
 
1987
2074
  const agentDiagnostics = [
1988
2075
  ...builtinLoaded.diagnostics,
@@ -1990,7 +2077,7 @@ export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryRe
1990
2077
  ...projectLoaded.flatMap((loaded) => loaded.diagnostics),
1991
2078
  ...packageLoaded.flatMap((loaded) => loaded.diagnostics),
1992
2079
  ];
1993
- return { agents, agentDiagnostics, projectAgentsDir, ...(modelScope !== undefined ? { modelScope } : {}) };
2080
+ return { agents, agentDiagnostics, projectAgentsDir, ...(modelScope !== undefined ? { modelScope } : {}), ...(maxThinking !== undefined ? { maxThinking } : {}) };
1994
2081
  }
1995
2082
 
1996
2083
  export function discoverAgentsAll(cwd: string): {
@@ -2007,6 +2094,7 @@ export function discoverAgentsAll(cwd: string): {
2007
2094
  projectChainDir: string | null;
2008
2095
  userSettingsPath: string;
2009
2096
  projectSettingsPath: string | null;
2097
+ maxThinking?: ThinkingLevel;
2010
2098
  } {
2011
2099
  const userDirOld = path.join(getAgentDir(), "agents");
2012
2100
  const userDirNew = path.join(os.homedir(), ".agents");
@@ -2017,14 +2105,16 @@ export function discoverAgentsAll(cwd: string): {
2017
2105
  const projectSettingsPath = getProjectAgentSettingsPath(cwd);
2018
2106
  const userSettings = readSubagentSettings(userSettingsPath);
2019
2107
  const projectSettings = readSubagentSettings(projectSettingsPath);
2020
- const defaultModel = resolveSubagentDefaultModel(userSettings, projectSettings, userSettingsPath, projectSettingsPath);
2108
+ const defaultProvider = resolveSubagentDefaultProvider(userSettings, projectSettings, projectSettingsPath);
2109
+ const defaultModel = resolveSubagentDefaultModel(userSettings, projectSettings, userSettingsPath, projectSettingsPath, defaultProvider);
2021
2110
  const defaultThinking = resolveSubagentDefaultThinking(userSettings, projectSettings, projectSettingsPath);
2111
+ const maxThinking = resolveSubagentMaxThinking(userSettings, projectSettings, projectSettingsPath);
2022
2112
  const defaultExtensions = resolveSubagentDefaultExtensions(userSettings, projectSettings, projectSettingsPath);
2023
2113
  const packageSubagentPaths = collectPackageSubagentPaths(cwd);
2024
2114
 
2025
2115
  const builtinLoaded = loadAgentsFromDefinitionFiles(BUILTIN_AGENT_DEFINITION_FILES, "builtin");
2026
2116
  const builtin = applyBuiltinOverrides(
2027
- applySubagentDefaults(builtinLoaded.agents, defaultModel, defaultThinking, defaultExtensions),
2117
+ applySubagentDefaults(builtinLoaded.agents, defaultModel, defaultProvider, defaultThinking, defaultExtensions),
2028
2118
  userSettings,
2029
2119
  projectSettings,
2030
2120
  userSettingsPath,
@@ -2033,7 +2123,7 @@ export function discoverAgentsAll(cwd: string): {
2033
2123
  const userLoaded = [...extraUserAgentDirs(), userDirOld, userDirNew]
2034
2124
  .map((dir, discoveryPriority) => loadAgentsFromDir(dir, "user", discoveryPriority));
2035
2125
  const user = applyCustomAgentOverrides(
2036
- applySubagentDefaults(userLoaded.flatMap((loaded) => loaded.agents), defaultModel, defaultThinking, defaultExtensions),
2126
+ applySubagentDefaults(userLoaded.flatMap((loaded) => loaded.agents), defaultModel, defaultProvider, defaultThinking, defaultExtensions),
2037
2127
  userSettings,
2038
2128
  projectSettings,
2039
2129
  userSettingsPath,
@@ -2049,7 +2139,7 @@ export function discoverAgentsAll(cwd: string): {
2049
2139
  }
2050
2140
  }
2051
2141
  const packageAgents = applyCustomAgentOverrides(
2052
- applySubagentDefaults(Array.from(packageMap.values()), defaultModel, defaultThinking, defaultExtensions),
2142
+ applySubagentDefaults(Array.from(packageMap.values()), defaultModel, defaultProvider, defaultThinking, defaultExtensions),
2053
2143
  userSettings,
2054
2144
  projectSettings,
2055
2145
  userSettingsPath,
@@ -2065,7 +2155,7 @@ export function discoverAgentsAll(cwd: string): {
2065
2155
  }
2066
2156
  }
2067
2157
  const project = applyCustomAgentOverrides(
2068
- applySubagentDefaults(Array.from(projectMap.values()), defaultModel, defaultThinking, defaultExtensions),
2158
+ applySubagentDefaults(Array.from(projectMap.values()), defaultModel, defaultProvider, defaultThinking, defaultExtensions),
2069
2159
  userSettings,
2070
2160
  projectSettings,
2071
2161
  userSettingsPath,
@@ -2110,5 +2200,20 @@ export function discoverAgentsAll(cwd: string): {
2110
2200
 
2111
2201
  const userDir = process.env.PI_CODING_AGENT_DIR ? userDirOld : fs.existsSync(userDirNew) ? userDirNew : userDirOld;
2112
2202
 
2113
- return { builtin, package: packageAgents, user, project, agentDiagnostics, chains, chainDiagnostics, userDir, projectDir, userChainDir, projectChainDir, userSettingsPath, projectSettingsPath };
2203
+ return {
2204
+ builtin: applySubagentMaxThinking(builtin, maxThinking),
2205
+ package: applySubagentMaxThinking(packageAgents, maxThinking),
2206
+ user: applySubagentMaxThinking(user, maxThinking),
2207
+ project: applySubagentMaxThinking(project, maxThinking),
2208
+ agentDiagnostics,
2209
+ chains,
2210
+ chainDiagnostics,
2211
+ userDir,
2212
+ projectDir,
2213
+ userChainDir,
2214
+ projectChainDir,
2215
+ userSettingsPath,
2216
+ projectSettingsPath,
2217
+ ...(maxThinking !== undefined ? { maxThinking } : {}),
2218
+ };
2114
2219
  }
@@ -9,7 +9,7 @@ const MAX_FAILURE_MESSAGE_LENGTH = 4_096;
9
9
  const MAX_URL_LENGTH = 4_096;
10
10
 
11
11
  export type ExternalJobState = "queued" | "running" | "completed" | "failed" | "stopped" | "blocked";
12
- export type ExternalJobOperation = "start" | "status" | "result" | "reattach";
12
+ export type ExternalJobOperation = "start" | "follow-up" | "status" | "result" | "reattach";
13
13
 
14
14
  export type ExternalJobOptions = Record<string, unknown>;
15
15
 
@@ -24,6 +24,14 @@ export interface ExternalJobStartInput {
24
24
  sessionId?: string;
25
25
  }
26
26
 
27
+ export interface ExternalJobFollowUpInput extends ExternalJobStartInput {
28
+ sourceRunId: string;
29
+ sourceStepIndex: number;
30
+ parentProviderJobId: string;
31
+ requestId: string;
32
+ requestDigest: string;
33
+ }
34
+
27
35
  export interface ExternalJobHandle {
28
36
  providerJobId: string;
29
37
  state: ExternalJobState;
@@ -42,6 +50,7 @@ export interface ExternalJobResult extends ExternalJobHandle {
42
50
  export interface ExternalJobProvider {
43
51
  name: string;
44
52
  start(input: ExternalJobStartInput): Promise<ExternalJobHandle> | ExternalJobHandle;
53
+ followUp?(input: ExternalJobFollowUpInput): Promise<ExternalJobHandle> | ExternalJobHandle;
45
54
  status(providerJobId: string): Promise<ExternalJobHandle> | ExternalJobHandle;
46
55
  result(providerJobId: string): Promise<ExternalJobResult> | ExternalJobResult;
47
56
  reattach(providerJobId: string): Promise<ExternalJobHandle> | ExternalJobHandle;
@@ -1,4 +1,3 @@
1
- import { createHash } from "node:crypto";
2
1
  import * as fs from "node:fs";
3
2
  import * as path from "node:path";
4
3
  import { fileURLToPath } from "node:url";
@@ -12,6 +11,7 @@ import { applyThinkingSuffix, resolvePiLaunchToolPlan, type PiLaunchToolPlan } f
12
11
  import { injectOutputPathSystemPrompt, normalizeSingleOutputOverride, resolveSingleOutputPath } from "../runs/shared/single-output.ts";
13
12
  import { getArtifactPaths, getArtifactsDir } from "../shared/artifacts.ts";
14
13
  import { resolveEffectiveThinking } from "../shared/model-info.ts";
14
+ import { assertThinkingWithinCeiling, decodeThinkingCeiling, intersectThinkingCeilings, SUBAGENT_THINKING_CEILING_ENV, type ThinkingLevel } from "../shared/thinking-ceiling.ts";
15
15
  import { SUBAGENT_LIFECYCLE_ARTIFACT_VERSION, type ArtifactDirPreference, type ArtifactPaths, type JsonSchemaObject, type OutputMode } from "../shared/types.ts";
16
16
  import { capabilityCeilingAgentRestrictionMessage, intersectSubagentCapabilityCeilings, type ResolvedSubagentCapabilityCeiling, type SubagentCapabilityAudit } from "../runs/shared/capability-ceiling.ts";
17
17
  import { appendTurnBudgetSystemPrompt } from "../runs/shared/turn-budget.ts";
@@ -21,7 +21,7 @@ import type { ResolvedMcpDirectToolSelection } from "../runs/shared/mcp-direct-t
21
21
  import { resolveStepBehavior } from "../shared/settings.ts";
22
22
  import { canPreferForkFromSnapshot, resolveSubagentLaunchContext } from "../shared/fork-context.ts";
23
23
  import { loadConfig } from "../extension/config.ts";
24
- import { agentDefinitionDigest, AGENT_DEFINITION_PROJECTION_VERSION, launchBindingDigest } from "../shared/launch-contract.ts";
24
+ import { agentDefinitionDigest, AGENT_DEFINITION_PROJECTION_VERSION, launchBindingDigest, stableJsonDigest } from "../shared/launch-contract.ts";
25
25
  import { DIRS, TEMP_ROOT_DIR } from "../shared/types.ts";
26
26
  import { processTerminalCandidatePath, processTerminalPath } from "../runs/background/process-terminal.ts";
27
27
  import { resultFilePath } from "../runs/background/result-files.ts";
@@ -37,7 +37,8 @@ export type SubagentLaunchContractReasonCode =
37
37
  | "invalid_artifact_dir"
38
38
  | "invalid_cwd"
39
39
  | "unsupported_mode"
40
- | "restricted_agent";
40
+ | "restricted_agent"
41
+ | "thinking_ceiling";
41
42
 
42
43
  export interface SubagentLaunchContractDiagnostic {
43
44
  code: SubagentLaunchContractReasonCode | "host_required" | "snapshot_warning";
@@ -53,6 +54,8 @@ export interface SubagentLaunchContractInput {
53
54
  context?: "fresh" | "fork";
54
55
  model?: string;
55
56
  thinking?: string | false;
57
+ thinkingCeiling?: ThinkingLevel;
58
+ inheritedThinkingCeiling?: ThinkingLevel;
56
59
  parentModel?: ParentModel;
57
60
  availableModels?: ReadonlyArray<AvailableModelInfo | { provider: string; id: string; fullId?: string; reasoning?: boolean }>;
58
61
  preferredProvider?: string;
@@ -147,6 +150,7 @@ export interface SubagentLaunchContract {
147
150
  model?: string;
148
151
  modelCandidates: string[];
149
152
  thinking?: string;
153
+ thinkingCeiling?: ThinkingLevel;
150
154
  systemPromptMode: AgentConfig["systemPromptMode"];
151
155
  inheritProjectContext: boolean;
152
156
  inheritSkills: boolean;
@@ -176,20 +180,8 @@ function packageVersion(): string {
176
180
  return parsed.version;
177
181
  }
178
182
 
179
- function stableJson(value: unknown): string {
180
- if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
181
- if (value && typeof value === "object") {
182
- return `{${Object.entries(value as Record<string, unknown>)
183
- .filter(([, entry]) => entry !== undefined)
184
- .sort(([a], [b]) => a.localeCompare(b))
185
- .map(([key, entry]) => `${JSON.stringify(key)}:${stableJson(entry)}`)
186
- .join(",")}}`;
187
- }
188
- return JSON.stringify(value);
189
- }
190
-
191
183
  function digestContract(contract: Omit<SubagentLaunchContract, "digest">): string {
192
- return createHash("sha256").update(stableJson(contract)).digest("hex");
184
+ return stableJsonDigest(contract);
193
185
  }
194
186
 
195
187
  function normalizeAvailableModels(models: SubagentLaunchContractInput["availableModels"]): AvailableModelInfo[] {
@@ -289,12 +281,18 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
289
281
 
290
282
  const externalRunner = agent.runner?.type === "external-cli" || agent.runner?.type === "external-job";
291
283
  const availableModels = normalizeAvailableModels(input.availableModels);
292
- const preferredProvider = input.preferredProvider ?? input.parentModel?.provider;
284
+ const preferredProvider = agent.modelProvider ?? input.preferredProvider ?? input.parentModel?.provider;
293
285
  const modelScopes = resolveModelScopesForAgent(discovered.modelScope, agent.name, input.parentModel);
294
286
  const primaryModel = externalRunner
295
287
  ? undefined
296
288
  : resolveEffectiveSubagentModel(input.model, agent.model, input.parentModel, availableModels, preferredProvider, { scope: modelScopes });
297
289
  const effectiveThinkingConfig = input.thinking !== undefined ? input.thinking : agent.thinking;
290
+ const thinkingCeiling = externalRunner ? undefined : intersectThinkingCeilings(
291
+ discovered.maxThinking,
292
+ input.thinkingCeiling,
293
+ input.inheritedThinkingCeiling,
294
+ decodeThinkingCeiling(process.env[SUBAGENT_THINKING_CEILING_ENV]),
295
+ );
298
296
  const model = externalRunner ? undefined : applyThinkingSuffix(primaryModel, effectiveThinkingConfig, input.thinking !== undefined);
299
297
  const modelCandidates = externalRunner
300
298
  ? []
@@ -303,6 +301,16 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
303
301
  primaryModelFromParent: inheritsParentModel(input.model, agent.model, input.parentModel),
304
302
  })
305
303
  .map((candidate) => applyThinkingSuffix(candidate, effectiveThinkingConfig, input.thinking !== undefined) ?? candidate);
304
+ if (!externalRunner) {
305
+ try {
306
+ assertThinkingWithinCeiling({ model, configThinking: effectiveThinkingConfig, ceiling: thinkingCeiling, agent: agent.name, runId });
307
+ for (const candidate of modelCandidates) assertThinkingWithinCeiling({ model: candidate, configThinking: effectiveThinkingConfig, ceiling: thinkingCeiling, agent: agent.name, runId });
308
+ } catch (error) {
309
+ const message = error instanceof Error ? error.message : String(error);
310
+ diagnostics.push({ code: "thinking_ceiling", severity: "error", message });
311
+ return { ok: false, code: "thinking_ceiling", message, diagnostics };
312
+ }
313
+ }
306
314
  let toolPlan: PiLaunchToolPlan;
307
315
  const permissionRules = resolvePermissionRules(loadConfig().permissions, agent.permissions);
308
316
  try {
@@ -372,6 +380,7 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
372
380
  ...(model ? { model } : {}),
373
381
  modelCandidates,
374
382
  ...(resolveEffectiveThinking(model, effectiveThinkingConfig) ? { thinking: resolveEffectiveThinking(model, effectiveThinkingConfig) } : {}),
383
+ ...(thinkingCeiling ? { thinkingCeiling } : {}),
375
384
  systemPromptMode: agent.systemPromptMode,
376
385
  inheritProjectContext: agent.inheritProjectContext,
377
386
  inheritSkills: agent.inheritSkills,
@@ -10,6 +10,7 @@ export {
10
10
  createProjectPaneManager,
11
11
  openProjectPane,
12
12
  getProjectPaneStatus,
13
+ focusProjectPane,
13
14
  closeProjectPane,
14
15
  readProjectPaneBinding,
15
16
  projectPaneBindingPath,
@@ -21,6 +22,7 @@ export {
21
22
  type CloseProjectPaneOptions,
22
23
  type OpenProjectPaneData,
23
24
  type ProjectPaneStatusData,
25
+ type FocusProjectPaneData,
24
26
  type CloseProjectPaneData,
25
27
  type ProjectPaneRuntime,
26
28
  type ProjectPaneError,