pi-subagents 0.60.0 → 0.62.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 (76) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/docs/agents.md +7 -3
  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 +13 -4
  8. package/docs/workflows.md +14 -13
  9. package/install.mjs +1 -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 +41 -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 +121 -63
  18. package/src/agents/agent-serializer.ts +3 -0
  19. package/src/agents/agents.ts +543 -223
  20. package/src/agents/runtime-agent-registry.ts +5 -1
  21. package/src/api/background-work.ts +7 -2
  22. package/src/api/external-runs.ts +67 -4
  23. package/src/api/preflight.ts +17 -8
  24. package/src/api/shared-types.ts +1 -0
  25. package/src/extension/index.ts +7 -4
  26. package/src/extension/public-execution.ts +48 -4
  27. package/src/extension/rpc.ts +62 -4
  28. package/src/extension/schemas.ts +17 -9
  29. package/src/extension/tool-description.ts +15 -19
  30. package/src/runs/background/active-async-capacity.ts +26 -7
  31. package/src/runs/background/async-execution.ts +98 -51
  32. package/src/runs/background/async-job-tracker.ts +62 -3
  33. package/src/runs/background/async-resume.ts +6 -3
  34. package/src/runs/background/async-status.ts +59 -9
  35. package/src/runs/background/auto-drain.ts +1 -1
  36. package/src/runs/background/fleet-view.ts +1 -1
  37. package/src/runs/background/process-terminal.ts +16 -0
  38. package/src/runs/background/result-watcher.ts +1 -1
  39. package/src/runs/background/resume-guidance.ts +1 -1
  40. package/src/runs/background/run-status.ts +2 -2
  41. package/src/runs/background/scheduled-runs.ts +63 -6
  42. package/src/runs/background/steering.ts +4 -1
  43. package/src/runs/background/subagent-runner.ts +15 -9
  44. package/src/runs/background/subagent-wait.ts +20 -21
  45. package/src/runs/background/wait-completions.ts +1 -1
  46. package/src/runs/background/wait-tool.ts +18 -18
  47. package/src/runs/foreground/execution.ts +75 -9
  48. package/src/runs/foreground/subagent-executor.ts +181 -60
  49. package/src/runs/shared/acceptance.ts +113 -27
  50. package/src/runs/shared/capability-ceiling.ts +1 -0
  51. package/src/runs/shared/dynamic-fanout.ts +1 -1
  52. package/src/runs/shared/host-step-status.ts +1 -0
  53. package/src/runs/shared/model-fallback.ts +61 -17
  54. package/src/runs/shared/parallel-utils.ts +2 -6
  55. package/src/runs/shared/permissions.ts +1 -1
  56. package/src/runs/shared/pi-args.ts +24 -12
  57. package/src/runs/shared/pi-spawn.ts +69 -35
  58. package/src/runs/shared/structured-output.ts +33 -6
  59. package/src/runs/shared/subagent-prompt-runtime.ts +20 -3
  60. package/src/runs/shared/task-intent.ts +17 -6
  61. package/src/runs/shared/tool-timeout.ts +1 -1
  62. package/src/runs/shared/workflow-graph.ts +3 -2
  63. package/src/shared/atomic-json.ts +3 -1
  64. package/src/shared/fork-context.ts +0 -12
  65. package/src/shared/fork-session-cwd.ts +27 -0
  66. package/src/shared/launch-contract.ts +3 -0
  67. package/src/shared/types.ts +43 -4
  68. package/src/shared/workflow-child-permit.ts +91 -0
  69. package/src/slash/prompt-template-bridge.ts +37 -1
  70. package/src/slash/slash-commands.ts +18 -26
  71. package/src/slash/subagents-admin.ts +2 -0
  72. package/src/tui/render.ts +95 -52
  73. package/src/workflows/scripted-workflow.ts +153 -4
  74. package/src/workflows/workflow-child-summary.ts +1 -1
  75. package/src/workflows/workflow-receipt.ts +41 -4
  76. package/src/workflows/workflow-resources.ts +150 -0
@@ -70,6 +70,7 @@ export interface BuiltinAgentOverrideBase {
70
70
  skills?: string[];
71
71
  skillPath?: string[];
72
72
  tools?: string[];
73
+ excludeTools?: string[];
73
74
  allowNestedSubagents?: boolean;
74
75
  mcpDirectTools?: string[];
75
76
  extensions?: string[];
@@ -99,6 +100,7 @@ interface BuiltinAgentOverrideConfig {
99
100
  systemPrompt?: string;
100
101
  skills?: string[] | false;
101
102
  tools?: string[] | false | "inherit";
103
+ excludeTools?: string[] | false;
102
104
  allowNestedSubagents?: boolean;
103
105
  extensions?: string[] | false;
104
106
  subagentOnlyExtensions?: string[] | false;
@@ -132,6 +134,7 @@ export interface AgentConfig {
132
134
  description: string;
133
135
  aliases?: string[];
134
136
  tools?: string[];
137
+ excludeTools?: string[];
135
138
  allowNestedSubagents?: boolean;
136
139
  mcpDirectTools?: string[];
137
140
  model?: string;
@@ -341,16 +344,30 @@ function getUserChainDir(): string {
341
344
  return path.join(getAgentDir(), "chains");
342
345
  }
343
346
 
347
+ type PackageScope = "root" | "user" | "project";
348
+ type PackageSettingsScope = Exclude<PackageScope, "root">;
349
+
344
350
  interface PackageSubagentPath {
345
351
  dir: string;
352
+ // A package can be reachable from more than one settings scope. Keep the
353
+ // actual memberships instead of promoting a mixed-scope path to root.
354
+ scope: Set<PackageScope>;
346
355
  packageName?: string;
347
356
  packageVersion?: string;
348
357
  packageRoot: string;
349
358
  }
350
359
 
360
+ interface PackageChainPath {
361
+ dir: string;
362
+ scope: Set<PackageScope>;
363
+ packageRoot: string;
364
+ }
365
+
351
366
  interface PackageSubagentPaths {
352
367
  agents: PackageSubagentPath[];
353
- chains: string[];
368
+ chains: PackageChainPath[];
369
+ watchPaths: string[];
370
+ settingsErrors: Partial<Record<PackageSettingsScope, Error>>;
354
371
  }
355
372
 
356
373
  let cachedGlobalNpmRoot: string | null = null;
@@ -365,18 +382,6 @@ function readJsonFileBestEffort(filePath: string): unknown {
365
382
  }
366
383
  }
367
384
 
368
- function readOptionalJsonFile(filePath: string): unknown {
369
- try {
370
- return JSON.parse(fs.readFileSync(filePath, "utf-8"));
371
- } catch (error) {
372
- const code = typeof error === "object" && error !== null && "code" in error
373
- ? (error as { code?: unknown }).code
374
- : undefined;
375
- if (code === "ENOENT") return null;
376
- throw error;
377
- }
378
- }
379
-
380
385
  function isSafePackagePath(value: string): boolean {
381
386
  return value.length > 0
382
387
  && !path.isAbsolute(value)
@@ -488,7 +493,7 @@ function stringArray(value: unknown): string[] {
488
493
  return value.filter((entry): entry is string => typeof entry === "string" && entry.trim().length > 0);
489
494
  }
490
495
 
491
- function packageMetadata(pkg: Record<string, unknown>, packageRoot: string): Omit<PackageSubagentPath, "dir"> {
496
+ function packageMetadata(pkg: Record<string, unknown>, packageRoot: string): Omit<PackageSubagentPath, "dir" | "scope"> {
492
497
  const name = typeof pkg.name === "string" && pkg.name.trim() ? pkg.name.trim() : undefined;
493
498
  const version = typeof pkg.version === "string" && pkg.version.trim() ? pkg.version.trim() : undefined;
494
499
  return {
@@ -498,10 +503,10 @@ function packageMetadata(pkg: Record<string, unknown>, packageRoot: string): Omi
498
503
  };
499
504
  }
500
505
 
501
- function extractSubagentPathsFromPackageRoot(packageRoot: string): PackageSubagentPaths {
506
+ function extractSubagentPathsFromPackageRoot(packageRoot: string, scope: Set<PackageScope>): PackageSubagentPaths {
502
507
  const packageJsonPath = path.join(packageRoot, "package.json");
503
508
  const pkg = readJsonFileBestEffort(packageJsonPath);
504
- if (!pkg || typeof pkg !== "object" || Array.isArray(pkg)) return { agents: [], chains: [] };
509
+ if (!pkg || typeof pkg !== "object" || Array.isArray(pkg)) return { agents: [], chains: [], watchPaths: [packageJsonPath], settingsErrors: {} };
505
510
  const pkgRecord = pkg as Record<string, unknown>;
506
511
  const metadata = packageMetadata(pkgRecord, packageRoot);
507
512
 
@@ -520,16 +525,17 @@ function extractSubagentPathsFromPackageRoot(packageRoot: string): PackageSubage
520
525
  }
521
526
 
522
527
  const agents: PackageSubagentPath[] = [];
523
- const chains: string[] = [];
528
+ const chains: PackageChainPath[] = [];
524
529
  for (const root of roots) {
525
- for (const entry of stringArray(root.agents)) agents.push({ dir: path.resolve(packageRoot, entry), ...metadata });
526
- for (const entry of stringArray(root.chains)) chains.push(path.resolve(packageRoot, entry));
530
+ for (const entry of stringArray(root.agents)) agents.push({ dir: path.resolve(packageRoot, entry), scope, ...metadata });
531
+ for (const entry of stringArray(root.chains)) chains.push({ dir: path.resolve(packageRoot, entry), scope, packageRoot });
527
532
  }
528
- return { agents, chains };
533
+ return { agents, chains, watchPaths: [packageJsonPath], settingsErrors: {} };
529
534
  }
530
535
 
531
- function collectPackageRootsFromNodeModules(nodeModulesDir: string): string[] {
536
+ function collectPackageRootsFromNodeModules(nodeModulesDir: string, watchPaths?: string[]): string[] {
532
537
  const roots: string[] = [];
538
+ watchPaths?.push(nodeModulesDir);
533
539
  if (!fs.existsSync(nodeModulesDir)) return roots;
534
540
 
535
541
  let entries: fs.Dirent[];
@@ -545,6 +551,7 @@ function collectPackageRootsFromNodeModules(nodeModulesDir: string): string[] {
545
551
 
546
552
  if (entry.name.startsWith("@")) {
547
553
  const scopeDir = path.join(nodeModulesDir, entry.name);
554
+ watchPaths?.push(scopeDir);
548
555
  let scopeEntries: fs.Dirent[];
549
556
  try {
550
557
  scopeEntries = fs.readdirSync(scopeDir, { withFileTypes: true });
@@ -565,8 +572,7 @@ function collectPackageRootsFromNodeModules(nodeModulesDir: string): string[] {
565
572
  }
566
573
 
567
574
  function collectSettingsPackageRoots(settingsFile: string, baseDir: string): string[] {
568
- const settings = readOptionalJsonFile(settingsFile);
569
- if (!settings || typeof settings !== "object" || Array.isArray(settings)) return [];
575
+ const settings = readSettingsFileStrict(settingsFile);
570
576
  const packages = (settings as { packages?: unknown }).packages;
571
577
  if (!Array.isArray(packages)) return [];
572
578
 
@@ -587,52 +593,82 @@ function collectSettingsPackageRoots(settingsFile: string, baseDir: string): str
587
593
  function collectPackageSubagentPaths(cwd: string, options: { includeUser: boolean; includeProject: boolean } = { includeUser: true, includeProject: true }): PackageSubagentPaths {
588
594
  const agentDir = getAgentDir();
589
595
  const projectRoot = findConfiguredProjectRoot(cwd) ?? cwd;
590
- const packageRoots = [
591
- projectRoot,
596
+ const packageRoots: Array<{ root: string; scope: PackageScope }> = [
597
+ { root: projectRoot, scope: "root" },
592
598
  ];
593
-
599
+ const watchPaths: string[] = [path.join(projectRoot, "package.json")];
600
+ const settingsErrors: Partial<Record<PackageSettingsScope, Error>> = {};
601
+ const collectScopedSettingsRoots = (scope: PackageSettingsScope, settingsFile: string, baseDir: string): string[] => {
602
+ try {
603
+ return collectSettingsPackageRoots(settingsFile, baseDir);
604
+ } catch (error) {
605
+ // The raw source snapshot includes both settings scopes. Defer a
606
+ // strict failure until a projection actually selects this scope so an
607
+ // unrelated malformed settings file stays out of scoped discovery.
608
+ settingsErrors[scope] = error instanceof Error
609
+ ? error
610
+ : new Error(`Failed to read settings file '${settingsFile}': ${String(error)}`, { cause: error });
611
+ return [];
612
+ }
613
+ };
594
614
  if (options.includeProject) {
595
615
  const projectConfigDir = getProjectConfigDir(projectRoot);
596
- packageRoots.push(
597
- ...collectPackageRootsFromNodeModules(path.join(projectConfigDir, "npm", "node_modules")),
598
- ...collectSettingsPackageRoots(path.join(projectConfigDir, "settings.json"), projectConfigDir),
599
- );
616
+ const nodeModulesDir = path.join(projectConfigDir, "npm", "node_modules");
617
+ packageRoots.push(...collectPackageRootsFromNodeModules(nodeModulesDir, watchPaths).map((root) => ({ root, scope: "project" as const })));
618
+ packageRoots.push(...collectScopedSettingsRoots("project", path.join(projectConfigDir, "settings.json"), projectConfigDir).map((root) => ({ root, scope: "project" as const })));
600
619
  }
601
620
 
602
621
  if (options.includeUser) {
603
- packageRoots.push(
604
- ...collectPackageRootsFromNodeModules(path.join(agentDir, "npm", "node_modules")),
605
- ...collectSettingsPackageRoots(path.join(agentDir, "settings.json"), agentDir),
606
- );
622
+ const nodeModulesDir = path.join(agentDir, "npm", "node_modules");
623
+ packageRoots.push(...collectPackageRootsFromNodeModules(nodeModulesDir, watchPaths).map((root) => ({ root, scope: "user" as const })));
624
+ packageRoots.push(...collectScopedSettingsRoots("user", path.join(agentDir, "settings.json"), agentDir).map((root) => ({ root, scope: "user" as const })));
607
625
  }
608
626
 
609
627
  if (options.includeUser) {
610
628
  const globalRoot = getGlobalNpmRoot();
611
- if (globalRoot) packageRoots.push(...collectPackageRootsFromNodeModules(globalRoot));
629
+ if (globalRoot) {
630
+ packageRoots.push(...collectPackageRootsFromNodeModules(globalRoot, watchPaths).map((root) => ({ root, scope: "user" as const })));
631
+ }
612
632
  }
613
633
 
614
- const seenRoots = new Set<string>();
615
- const seenAgents = new Set<string>();
616
- const seenChains = new Set<string>();
634
+ const seenRoots = new Map<string, Set<PackageScope>>();
635
+ const seenAgents = new Map<string, PackageSubagentPath>();
636
+ const seenChains = new Map<string, PackageChainPath>();
617
637
  const agents: PackageSubagentPath[] = [];
618
- const chains: string[] = [];
619
- for (const packageRoot of packageRoots) {
638
+ const chains: PackageChainPath[] = [];
639
+ for (const { root: packageRoot, scope } of packageRoots) {
620
640
  const resolvedRoot = path.resolve(packageRoot);
621
- if (seenRoots.has(resolvedRoot)) continue;
622
- seenRoots.add(resolvedRoot);
623
- const paths = extractSubagentPathsFromPackageRoot(resolvedRoot);
641
+ const scopes = seenRoots.get(resolvedRoot);
642
+ if (scopes !== undefined) {
643
+ scopes.add(scope);
644
+ continue;
645
+ }
646
+ const packageScopes = new Set<PackageScope>([scope]);
647
+ seenRoots.set(resolvedRoot, packageScopes);
648
+ const paths = extractSubagentPathsFromPackageRoot(resolvedRoot, packageScopes);
649
+ watchPaths.push(...paths.watchPaths);
624
650
  for (const agentPath of paths.agents) {
625
- if (seenAgents.has(agentPath.dir)) continue;
626
- seenAgents.add(agentPath.dir);
651
+ const agentKey = `${agentPath.dir}\u0000${agentPath.packageRoot}`;
652
+ const existing = seenAgents.get(agentKey);
653
+ if (existing) {
654
+ for (const packageScope of agentPath.scope) existing.scope.add(packageScope);
655
+ continue;
656
+ }
657
+ seenAgents.set(agentKey, agentPath);
627
658
  agents.push(agentPath);
628
659
  }
629
660
  for (const chainDir of paths.chains) {
630
- if (seenChains.has(chainDir)) continue;
631
- seenChains.add(chainDir);
661
+ const chainKey = `${chainDir.dir}\u0000${chainDir.packageRoot}`;
662
+ const existing = seenChains.get(chainKey);
663
+ if (existing) {
664
+ for (const packageScope of chainDir.scope) existing.scope.add(packageScope);
665
+ continue;
666
+ }
667
+ seenChains.set(chainKey, chainDir);
632
668
  chains.push(chainDir);
633
669
  }
634
670
  }
635
- return { agents, chains };
671
+ return { agents, chains, watchPaths, settingsErrors };
636
672
  }
637
673
 
638
674
  function normalizeAgentAliases(rawAliases: string[] | undefined, agentName: string): string[] | undefined {
@@ -727,6 +763,7 @@ function cloneOverrideBase(agent: AgentConfig): BuiltinAgentOverrideBase {
727
763
  ...(agent.skills ? { skills: [...agent.skills] } : {}),
728
764
  ...(agent.skillPath ? { skillPath: [...agent.skillPath] } : {}),
729
765
  ...(agent.tools ? { tools: [...agent.tools] } : {}),
766
+ ...(agent.excludeTools ? { excludeTools: [...agent.excludeTools] } : {}),
730
767
  ...(agent.allowNestedSubagents !== undefined ? { allowNestedSubagents: agent.allowNestedSubagents } : {}),
731
768
  ...(agent.mcpDirectTools ? { mcpDirectTools: [...agent.mcpDirectTools] } : {}),
732
769
  ...(!agent.extensionsFromDefault && agent.extensions ? { extensions: [...agent.extensions] } : {}),
@@ -760,6 +797,7 @@ function cloneOverrideValue(override: BuiltinAgentOverrideConfig): BuiltinAgentO
760
797
  ...(override.systemPrompt !== undefined ? { systemPrompt: override.systemPrompt } : {}),
761
798
  ...(override.skills !== undefined ? { skills: override.skills === false ? false : [...override.skills] } : {}),
762
799
  ...(override.tools !== undefined ? { tools: Array.isArray(override.tools) ? [...override.tools] : override.tools } : {}),
800
+ ...(override.excludeTools !== undefined ? { excludeTools: override.excludeTools === false ? false : [...override.excludeTools] } : {}),
763
801
  ...(override.allowNestedSubagents !== undefined ? { allowNestedSubagents: override.allowNestedSubagents } : {}),
764
802
  ...(override.extensions !== undefined ? { extensions: override.extensions === false ? false : [...override.extensions] } : {}),
765
803
  ...(override.subagentOnlyExtensions !== undefined ? { subagentOnlyExtensions: override.subagentOnlyExtensions === false ? false : [...override.subagentOnlyExtensions] } : {}),
@@ -1052,6 +1090,8 @@ function parseBuiltinOverrideEntry(
1052
1090
 
1053
1091
  const tools = parseToolsOverride(input.tools, { filePath, name });
1054
1092
  if (tools !== undefined) override.tools = tools;
1093
+ const excludeTools = parseOverrideStringArrayOrFalse(input.excludeTools, { filePath, name, field: "excludeTools" });
1094
+ if (excludeTools !== undefined) override.excludeTools = excludeTools;
1055
1095
  if ("allowNestedSubagents" in input) {
1056
1096
  if (typeof input.allowNestedSubagents === "boolean") override.allowNestedSubagents = input.allowNestedSubagents;
1057
1097
  else throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'allowNestedSubagents'; expected a boolean.`);
@@ -1338,6 +1378,7 @@ function applyBuiltinOverride(
1338
1378
  if (override.systemPrompt !== undefined) next.systemPrompt = override.systemPrompt;
1339
1379
  if (override.skills !== undefined) { if (override.skills === false) delete next.skills; else next.skills = [...override.skills]; }
1340
1380
  if (override.tools !== undefined) applyToolsOverride(next, override.tools);
1381
+ if (override.excludeTools !== undefined) { if (override.excludeTools === false) delete next.excludeTools; else next.excludeTools = [...override.excludeTools]; }
1341
1382
  if (override.allowNestedSubagents !== undefined) next.allowNestedSubagents = override.allowNestedSubagents;
1342
1383
  if (override.extensions !== undefined) { if (override.extensions === false) delete next.extensions; else next.extensions = [...override.extensions]; }
1343
1384
  if (override.subagentOnlyExtensions !== undefined) { if (override.subagentOnlyExtensions === false) delete next.subagentOnlyExtensions; else next.subagentOnlyExtensions = [...override.subagentOnlyExtensions]; }
@@ -1503,6 +1544,9 @@ function applyCustomAgentOverride(
1503
1544
  applyToolsOverride(mutable(), override.tools);
1504
1545
  anyFilled = true;
1505
1546
  }
1547
+ if (override.excludeTools !== undefined) {
1548
+ fill("excludeTools", ["excludeTools"], override.excludeTools === false ? undefined : [...override.excludeTools]);
1549
+ }
1506
1550
  if (override.allowNestedSubagents !== undefined) {
1507
1551
  fill("allowNestedSubagents", ["allowNestedSubagents"], override.allowNestedSubagents);
1508
1552
  }
@@ -1558,7 +1602,7 @@ function applyCustomAgentOverrides(
1558
1602
 
1559
1603
  export function buildBuiltinOverrideConfig(
1560
1604
  base: BuiltinAgentOverrideBase,
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">>,
1605
+ 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" | "excludeTools">>,
1562
1606
  ): BuiltinAgentOverrideConfig | undefined {
1563
1607
  const override: BuiltinAgentOverrideConfig = {};
1564
1608
 
@@ -1587,6 +1631,7 @@ export function buildBuiltinOverrideConfig(
1587
1631
  const baseTools = joinToolList(base);
1588
1632
  const draftTools = joinToolList(draft);
1589
1633
  if (!arraysEqual(draftTools, baseTools)) override.tools = draftTools ? [...draftTools] : false;
1634
+ if (!arraysEqual(draft.excludeTools, base.excludeTools)) override.excludeTools = draft.excludeTools ? [...draft.excludeTools] : false;
1590
1635
  if (draft.allowNestedSubagents !== base.allowNestedSubagents) override.allowNestedSubagents = draft.allowNestedSubagents === true;
1591
1636
  if (!arraysEqual(draft.extensions, base.extensions)) override.extensions = draft.extensions ? [...draft.extensions] : false;
1592
1637
  if (!arraysEqual(draft.subagentOnlyExtensions, base.subagentOnlyExtensions)) {
@@ -1739,9 +1784,11 @@ function listFilesRecursive(
1739
1784
  predicate: (fileName: string) => boolean,
1740
1785
  rootDir = dir,
1741
1786
  visitedDirectories = new Set<string>(),
1787
+ inspectedDirectories = new Set<string>(),
1742
1788
  ): string[] {
1743
1789
  const files: string[] = [];
1744
1790
  if (!fs.existsSync(dir)) return files;
1791
+ inspectedDirectories.add(path.resolve(dir));
1745
1792
  let realDir: string;
1746
1793
  try {
1747
1794
  realDir = fs.realpathSync(dir);
@@ -1770,7 +1817,7 @@ function listFilesRecursive(
1770
1817
  }
1771
1818
  if (isDirectory) {
1772
1819
  if (!shouldPruneDiscoveryDir(rootDir, filePath, entry.name)) {
1773
- files.push(...listFilesRecursive(filePath, predicate, rootDir, visitedDirectories));
1820
+ files.push(...listFilesRecursive(filePath, predicate, rootDir, visitedDirectories, inspectedDirectories));
1774
1821
  }
1775
1822
  continue;
1776
1823
  }
@@ -1786,6 +1833,17 @@ export interface AgentDefinitionInspection {
1786
1833
  state: AgentDefinitionDirectoryState;
1787
1834
  }
1788
1835
 
1836
+ const agentDefinitionInspectionDirectories = new WeakMap<AgentDefinitionInspection, string[]>();
1837
+
1838
+ function rememberAgentDefinitionInspection(inspection: AgentDefinitionInspection, directories: Iterable<string>): AgentDefinitionInspection {
1839
+ agentDefinitionInspectionDirectories.set(inspection, [...new Set([...directories].map((directory) => path.resolve(directory)))]);
1840
+ return inspection;
1841
+ }
1842
+
1843
+ function inspectedAgentDefinitionDirectories(inspection: AgentDefinitionInspection, root: string): string[] {
1844
+ return agentDefinitionInspectionDirectories.get(inspection) ?? [path.resolve(root)];
1845
+ }
1846
+
1789
1847
  /** Narrow filesystem seam so unavailable paths can be tested without permissions. */
1790
1848
  export interface AgentDefinitionInspectionFs {
1791
1849
  existsSync(filePath: string): boolean;
@@ -1809,15 +1867,17 @@ const DEFAULT_AGENT_DEFINITION_INSPECTION_FS: AgentDefinitionInspectionFs = {
1809
1867
  export function inspectAgentDefinitionDirectory(dir: string, operations: AgentDefinitionInspectionFs = DEFAULT_AGENT_DEFINITION_INSPECTION_FS): AgentDefinitionInspection {
1810
1868
  const root = path.resolve(dir);
1811
1869
  try {
1812
- if (!operations.existsSync(root)) return { files: [], state: "absent" };
1813
- if (!operations.statSync(root).isDirectory()) return { files: [], state: "not-directory" };
1870
+ if (!operations.existsSync(root)) return rememberAgentDefinitionInspection({ files: [], state: "absent" }, [root]);
1871
+ if (!operations.statSync(root).isDirectory()) return rememberAgentDefinitionInspection({ files: [], state: "not-directory" }, [root]);
1814
1872
  } catch {
1815
- return { files: [], state: "unreadable" };
1873
+ return rememberAgentDefinitionInspection({ files: [], state: "unreadable" }, [root]);
1816
1874
  }
1817
1875
  const files: string[] = [];
1818
1876
  let unreadable = false;
1819
1877
  const visitedDirectories = new Set<string>();
1878
+ const inspectedDirectories = new Set<string>();
1820
1879
  const visit = (current: string): void => {
1880
+ inspectedDirectories.add(path.resolve(current));
1821
1881
  let realDir: string;
1822
1882
  try {
1823
1883
  realDir = operations.realpathSync?.(current) ?? path.resolve(current);
@@ -1853,7 +1913,7 @@ export function inspectAgentDefinitionDirectory(dir: string, operations: AgentDe
1853
1913
  }
1854
1914
  };
1855
1915
  visit(root);
1856
- return { files, state: unreadable ? "unreadable" : files.length ? "candidates" : "empty" };
1916
+ return rememberAgentDefinitionInspection({ files, state: unreadable ? "unreadable" : files.length ? "candidates" : "empty" }, inspectedDirectories);
1857
1917
  }
1858
1918
 
1859
1919
  function isLegacyAgentSkillPath(rootDir: string, filePath: string): boolean {
@@ -1936,7 +1996,7 @@ function parseAgentRunnerFrontmatter(raw: string | undefined, agentName: string)
1936
1996
 
1937
1997
  function validateExternalRunnerProfile(frontmatter: Record<string, string>, agentName: string, runner: AgentRunnerConfig | undefined): void {
1938
1998
  if (runner?.type !== "external-cli" && runner?.type !== "external-job") return;
1939
- const unsupported = ["tools", "allowNestedSubagents", "model", "fallbackModels", "thinking", "extensions", "subagentOnlyExtensions", "mutationTools", "maxSubagentDepth", "completionGuard", "skills", "skill", "skillPath", "toolBudget", "permission", "permissions"]
1999
+ const unsupported = ["tools", "excludeTools", "allowNestedSubagents", "model", "fallbackModels", "thinking", "extensions", "subagentOnlyExtensions", "mutationTools", "maxSubagentDepth", "completionGuard", "skills", "skill", "skillPath", "toolBudget", "permission", "permissions"]
1940
2000
  .filter((field) => frontmatter[field] !== undefined);
1941
2001
  if (unsupported.length > 0) {
1942
2002
  throw new Error(`Agent '${agentName}' uses runner.type='${runner.type}' and declares unsupported Pi-only fields: ${unsupported.join(", ")}.`);
@@ -1989,7 +2049,7 @@ function resolveAgentRelativeExtensionPaths(paths: string[] | undefined, agentFi
1989
2049
  });
1990
2050
  }
1991
2051
 
1992
- function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: AgentSource, discoveryPriority?: number, packageSource?: Omit<PackageSubagentPath, "dir">): { agents: AgentConfig[]; diagnostics: AgentDiscoveryDiagnostic[] } {
2052
+ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: AgentSource, discoveryPriority?: number, packageSource?: Omit<PackageSubagentPath, "dir" | "scope">): { agents: AgentConfig[]; diagnostics: AgentDiscoveryDiagnostic[] } {
1993
2053
  const agents: AgentConfig[] = [];
1994
2054
  const diagnostics: AgentDiscoveryDiagnostic[] = [];
1995
2055
 
@@ -2018,6 +2078,7 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
2018
2078
  const parsedTools = splitToolList(rawTools);
2019
2079
  const tools = parsedTools.tools ?? [];
2020
2080
  const mcpDirectTools = parsedTools.mcpDirectTools ?? [];
2081
+ const excludeTools = parseFrontmatterList(frontmatter.excludeTools);
2021
2082
  const defaultReads = parseFrontmatterList(frontmatter.defaultReads);
2022
2083
  const aliases = normalizeAgentAliases(parseFrontmatterList(frontmatter.aliases ?? frontmatter.alias), runtimeName);
2023
2084
  const profileError = validateCodeOwnedProfileRunner({ name: runtimeName, localName, aliases, runner });
@@ -2139,6 +2200,7 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
2139
2200
  description: frontmatter.description,
2140
2201
  ...(aliases !== undefined ? { aliases } : {}),
2141
2202
  ...(rawTools !== undefined ? { tools } : {}),
2203
+ ...(excludeTools !== undefined ? { excludeTools } : {}),
2142
2204
  ...(allowNestedSubagents !== undefined ? { allowNestedSubagents } : {}),
2143
2205
  ...(mcpDirectTools.length > 0 ? { mcpDirectTools } : {}),
2144
2206
  ...(frontmatter.model !== undefined ? { model: frontmatter.model } : {}),
@@ -2186,7 +2248,7 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
2186
2248
  return { agents, diagnostics };
2187
2249
  }
2188
2250
 
2189
- function loadAgentsFromDir(dir: string, source: AgentSource, discoveryPriority?: number, packageSource?: Omit<PackageSubagentPath, "dir">, inspection = inspectAgentDefinitionDirectory(dir)): { agents: AgentConfig[]; diagnostics: AgentDiscoveryDiagnostic[] } {
2251
+ function loadAgentsFromDir(dir: string, source: AgentSource, discoveryPriority?: number, packageSource?: Omit<PackageSubagentPath, "dir" | "scope">, inspection = inspectAgentDefinitionDirectory(dir)): { agents: AgentConfig[]; diagnostics: AgentDiscoveryDiagnostic[] } {
2190
2252
  return loadAgentsFromDefinitionFiles(readAgentDefinitionFiles(dir, inspection), source, discoveryPriority, packageSource);
2191
2253
  }
2192
2254
 
@@ -2199,11 +2261,13 @@ function reportAgentDefinitionDirectory(source: AgentSource, dir: string, inspec
2199
2261
  };
2200
2262
  }
2201
2263
 
2202
- function loadChainsFromDir(dir: string, source: AgentSource): { chains: ChainConfig[]; diagnostics: ChainDiscoveryDiagnostic[] } {
2264
+ function loadChainsFromDir(dir: string, source: AgentSource): { chains: ChainConfig[]; diagnostics: ChainDiscoveryDiagnostic[]; files: string[]; directories: string[] } {
2203
2265
  const chains = new Map<string, ChainConfig>();
2204
2266
  const diagnostics: ChainDiscoveryDiagnostic[] = [];
2267
+ const directories = new Set<string>();
2268
+ const files = listFilesRecursive(dir, (fileName) => fileName.endsWith(".chain.md") || fileName.endsWith(".chain.json"), dir, new Set<string>(), directories);
2205
2269
 
2206
- for (const filePath of listFilesRecursive(dir, (fileName) => fileName.endsWith(".chain.md") || fileName.endsWith(".chain.json"))) {
2270
+ for (const filePath of files) {
2207
2271
  let content: string;
2208
2272
  try {
2209
2273
  content = fs.readFileSync(filePath, "utf-8");
@@ -2222,7 +2286,7 @@ function loadChainsFromDir(dir: string, source: AgentSource): { chains: ChainCon
2222
2286
  }
2223
2287
  }
2224
2288
 
2225
- return { chains: Array.from(chains.values()), diagnostics };
2289
+ return { chains: Array.from(chains.values()), diagnostics, files, directories: [...directories] };
2226
2290
  }
2227
2291
 
2228
2292
  function isDirectory(p: string): boolean {
@@ -2278,78 +2342,7 @@ function extraUserAgentDirs(): string[] {
2278
2342
  .filter((dir) => dir.length > 0);
2279
2343
  }
2280
2344
 
2281
- export function discoverAgents(cwd: string, scope: AgentScope, preferredModelProvider?: string): AgentDiscoveryResult {
2282
- const effectiveCwd = path.resolve(cwd);
2283
- const userDirOld = path.join(getAgentDir(), "agents");
2284
- const userDirNew = path.join(os.homedir(), ".agents");
2285
- const { readDirs: projectAgentDirs, candidateDirs: projectCandidateDirs, preferredDir: projectAgentsDir } = resolveNearestProjectAgentDirs(effectiveCwd);
2286
- const userSettingsPath = getUserAgentSettingsPath();
2287
- const projectSettingsPath = getProjectAgentSettingsPath(effectiveCwd);
2288
- const userSettings = selectProviderOverrides(scope === "project" ? EMPTY_SUBAGENT_SETTINGS : readSubagentSettings(userSettingsPath), preferredModelProvider);
2289
- const projectSettings = selectProviderOverrides(scope === "user" ? EMPTY_SUBAGENT_SETTINGS : readSubagentSettings(projectSettingsPath), preferredModelProvider);
2290
- const defaultProvider = resolveSubagentDefaultProvider(userSettings, projectSettings, projectSettingsPath);
2291
- const defaultModel = resolveSubagentDefaultModel(userSettings, projectSettings, userSettingsPath, projectSettingsPath, defaultProvider);
2292
- const defaultThinking = resolveSubagentDefaultThinking(userSettings, projectSettings, projectSettingsPath);
2293
- const maxThinking = resolveSubagentMaxThinking(userSettings, projectSettings, projectSettingsPath);
2294
- const defaultExtensions = resolveSubagentDefaultExtensions(userSettings, projectSettings, projectSettingsPath);
2295
- const modelScope = projectSettings.modelScope ?? userSettings.modelScope;
2296
- const packageSubagentPaths = collectPackageSubagentPaths(effectiveCwd, {
2297
- includeUser: scope !== "project",
2298
- includeProject: scope !== "user",
2299
- });
2300
- const directories: AgentDefinitionDirectoryReport[] = [reportAgentDefinitionDirectory("builtin", BUILTIN_AGENTS_DIR, BUILTIN_AGENT_DEFINITION_INSPECTION)];
2301
-
2302
- const builtinLoaded = loadAgentsFromDefinitionFiles(BUILTIN_AGENT_DEFINITION_FILES, "builtin");
2303
- const builtinAgents = applyBuiltinOverrides(
2304
- applySubagentDefaults(builtinLoaded.agents, defaultModel, defaultProvider, defaultThinking, defaultExtensions),
2305
- userSettings, projectSettings, userSettingsPath, projectSettingsPath,
2306
- );
2307
-
2308
- const userLoaded = scope === "project" ? [] : [...extraUserAgentDirs(), userDirOld, userDirNew].map((dir, discoveryPriority) => {
2309
- const inspection = inspectAgentDefinitionDirectory(dir);
2310
- directories.push(reportAgentDefinitionDirectory("user", dir, inspection));
2311
- return loadAgentsFromDir(dir, "user", discoveryPriority, undefined, inspection);
2312
- });
2313
- const userAgents = applyCustomAgentOverrides(
2314
- applySubagentDefaults(userLoaded.flatMap((loaded) => loaded.agents), defaultModel, defaultProvider, defaultThinking, defaultExtensions),
2315
- userSettings, projectSettings, userSettingsPath, projectSettingsPath,
2316
- );
2317
-
2318
- // Report both legacy and preferred project definition paths only after the
2319
- // existing configured-root resolver found a root; never synthesize cwd paths.
2320
- const projectInspections = scope === "user" ? new Map<string, AgentDefinitionInspection>() : new Map(projectCandidateDirs.map((dir) => [dir, inspectAgentDefinitionDirectory(dir)]));
2321
- if (scope !== "user") for (const dir of projectCandidateDirs) directories.push(reportAgentDefinitionDirectory("project", dir, projectInspections.get(dir)!));
2322
- const projectLoaded = scope === "user" ? [] : projectAgentDirs.map((dir) => loadAgentsFromDir(dir, "project", dir === projectAgentsDir ? 1 : 0, undefined, projectInspections.get(dir)!));
2323
- const projectAgents = applyCustomAgentOverrides(
2324
- applySubagentDefaults(projectLoaded.flatMap((loaded) => loaded.agents), defaultModel, defaultProvider, defaultThinking, defaultExtensions),
2325
- userSettings, projectSettings, userSettingsPath, projectSettingsPath,
2326
- );
2327
-
2328
- const packageLoaded = packageSubagentPaths.agents.map((entry, index) => {
2329
- const inspection = inspectAgentDefinitionDirectory(entry.dir);
2330
- directories.push(reportAgentDefinitionDirectory("package", entry.dir, inspection));
2331
- return loadAgentsFromDir(entry.dir, "package", packageSubagentPaths.agents.length - index, entry, inspection);
2332
- });
2333
- const packageMap = new Map<string, AgentConfig>();
2334
- for (const loaded of packageLoaded) for (const agent of loaded.agents) if (!packageMap.has(agent.name)) packageMap.set(agent.name, agent);
2335
- const packageAgents = applyCustomAgentOverrides(
2336
- applySubagentDefaults(Array.from(packageMap.values()), defaultModel, defaultProvider, defaultThinking, defaultExtensions),
2337
- userSettings, projectSettings, userSettingsPath, projectSettingsPath,
2338
- );
2339
- const agents = applySubagentMaxThinking(
2340
- mergeAgentsForScope(scope, userAgents, projectAgents, builtinAgents, packageAgents).filter((agent) => agent.disabled !== true),
2341
- maxThinking,
2342
- );
2343
- const agentDiagnostics = [
2344
- ...builtinLoaded.diagnostics,
2345
- ...userLoaded.flatMap((loaded) => loaded.diagnostics),
2346
- ...projectLoaded.flatMap((loaded) => loaded.diagnostics),
2347
- ...packageLoaded.flatMap((loaded) => loaded.diagnostics),
2348
- ];
2349
- return { agents, agentDiagnostics, projectAgentsDir, cwd: effectiveCwd, scope, directories, ...(modelScope !== undefined ? { modelScope } : {}), ...(maxThinking !== undefined ? { maxThinking } : {}) };
2350
- }
2351
-
2352
- export function discoverAgentsAll(cwd: string, preferredModelProvider?: string): {
2345
+ export interface AgentDiscoveryAllResult {
2353
2346
  builtin: AgentConfig[];
2354
2347
  package: AgentConfig[];
2355
2348
  user: AgentConfig[];
@@ -2364,125 +2357,452 @@ export function discoverAgentsAll(cwd: string, preferredModelProvider?: string):
2364
2357
  userSettingsPath: string;
2365
2358
  projectSettingsPath: string | null;
2366
2359
  maxThinking?: ThinkingLevel;
2367
- } {
2360
+ }
2361
+
2362
+ /**
2363
+ * A single fresh source scan with both projections retained. The effective
2364
+ * projection intentionally remains separate from the all-source projection:
2365
+ * disabled and shadowed definitions are needed for diagnostics and runtime
2366
+ * collision checks even when they are not launchable.
2367
+ */
2368
+ export interface AgentDiscoverySnapshot {
2369
+ effective: AgentDiscoveryResult;
2370
+ all: AgentDiscoveryAllResult;
2371
+ }
2372
+
2373
+ interface LoadedAgentDirectory {
2374
+ dir: string;
2375
+ inspection: AgentDefinitionInspection;
2376
+ loaded: ReturnType<typeof loadAgentsFromDir>;
2377
+ packageEntry?: PackageSubagentPath;
2378
+ }
2379
+
2380
+ interface AgentDiscoverySources {
2381
+ cwd: string;
2382
+ preferredModelProvider?: string;
2383
+ userDir: string;
2384
+ userDirOld: string;
2385
+ userDirNew: string;
2386
+ userChainDir: string;
2387
+ projectAgentDirs: string[];
2388
+ projectCandidateDirs: string[];
2389
+ projectAgentsDir: string | null;
2390
+ projectChainDirs: string[];
2391
+ projectChainDir: string | null;
2392
+ userSettingsPath: string;
2393
+ projectSettingsPath: string | null;
2394
+ userSettings?: SubagentSettings;
2395
+ projectSettings?: SubagentSettings;
2396
+ packageSubagentPaths: PackageSubagentPaths;
2397
+ builtinLoaded: ReturnType<typeof loadAgentsFromDefinitionFiles>;
2398
+ userLoaded: LoadedAgentDirectory[];
2399
+ projectLoaded: LoadedAgentDirectory[];
2400
+ projectInspections: Map<string, AgentDefinitionInspection>;
2401
+ packageLoaded: LoadedAgentDirectory[];
2402
+ userChains?: ReturnType<typeof loadChainsFromDir>;
2403
+ projectChainLoaded?: Array<{ dir: string; loaded: ReturnType<typeof loadChainsFromDir> }>;
2404
+ packageChainLoaded?: Array<{ entry: PackageChainPath; loaded: ReturnType<typeof loadChainsFromDir> }>;
2405
+ watchPaths: string[];
2406
+ }
2407
+
2408
+ interface AgentDiscoveryCacheEntry {
2409
+ sources: AgentDiscoverySources;
2410
+ fingerprint: string;
2411
+ }
2412
+
2413
+ const agentDiscoveryCache = new Map<string, AgentDiscoveryCacheEntry>();
2414
+
2415
+ function isPathWithin(root: string, candidate: string): boolean {
2416
+ const relative = path.relative(path.resolve(root), path.resolve(candidate));
2417
+ return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
2418
+ }
2419
+
2420
+ function addDirectoryWatchPaths(paths: Set<string>, root: string, files: readonly string[], directories: readonly string[] = []): void {
2421
+ const resolvedRoot = path.resolve(root);
2422
+ paths.add(resolvedRoot);
2423
+ // The initial recursive inspection is already bounded to this discovery
2424
+ // root. Keep every visited directory in the fingerprint so a file added to
2425
+ // an existing empty nested directory invalidates the snapshot too.
2426
+ for (const directory of directories) {
2427
+ if (isPathWithin(resolvedRoot, directory)) paths.add(path.resolve(directory));
2428
+ }
2429
+ for (const file of files) {
2430
+ paths.add(path.resolve(file));
2431
+ let current = path.resolve(path.dirname(file));
2432
+ while (isPathWithin(resolvedRoot, current)) {
2433
+ paths.add(current);
2434
+ if (current === resolvedRoot) break;
2435
+ current = path.dirname(current);
2436
+ }
2437
+ }
2438
+ }
2439
+
2440
+ function watchPathSignature(filePath: string): string {
2441
+ try {
2442
+ const stat = fs.statSync(filePath);
2443
+ if (stat.isDirectory()) {
2444
+ const entries = fs.readdirSync(filePath, { withFileTypes: true })
2445
+ .sort((left, right) => left.name.localeCompare(right.name))
2446
+ .map((entry) => `${entry.name}:${entry.isDirectory() ? "d" : entry.isSymbolicLink() ? "l" : "f"}`)
2447
+ .join("|");
2448
+ return `directory:${entries}`;
2449
+ }
2450
+ return `file:${stat.size}:${stat.mtimeMs}`;
2451
+ } catch {
2452
+ return "missing";
2453
+ }
2454
+ }
2455
+
2456
+ function discoveryFingerprint(paths: readonly string[]): string {
2457
+ return [...new Set(paths)]
2458
+ .sort((left, right) => left.localeCompare(right))
2459
+ .map((filePath) => `${filePath}:${watchPathSignature(filePath)}`)
2460
+ .join("\n");
2461
+ }
2462
+
2463
+ function discoveryCacheKey(cwd: string, preferredModelProvider: string | undefined): string {
2464
+ return JSON.stringify([
2465
+ path.resolve(cwd),
2466
+ preferredModelProvider ?? null,
2467
+ getProjectConfigDir(path.resolve(cwd)),
2468
+ getAgentDir(),
2469
+ os.homedir(),
2470
+ process.env.HOME ?? "",
2471
+ process.env.USERPROFILE ?? "",
2472
+ process.env[EXTRA_AGENT_DIRS_ENV] ?? "",
2473
+ process.env.PI_OFFLINE ?? "",
2474
+ process.env.APPDATA ?? "",
2475
+ ]);
2476
+ }
2477
+
2478
+ function projectDiscoveryWatchPaths(cwd: string): string[] {
2479
+ const paths: string[] = [];
2480
+ let current = path.resolve(cwd);
2481
+ while (true) {
2482
+ paths.push(getProjectConfigDir(current), path.join(current, ".agents"), path.join(current, ".git"));
2483
+ const parent = path.dirname(current);
2484
+ if (parent === current) break;
2485
+ current = parent;
2486
+ }
2487
+ return paths;
2488
+ }
2489
+
2490
+ function packageEntryIncluded(scope: AgentScope, packageScopes: PackageSubagentPath["scope"]): boolean {
2491
+ if (scope === "both" || packageScopes.has("root")) return true;
2492
+ return packageScopes.has(scope);
2493
+ }
2494
+
2495
+ function buildAgentDiscoverySources(cwd: string, preferredModelProvider?: string): AgentDiscoverySources {
2496
+ const effectiveCwd = path.resolve(cwd);
2368
2497
  const userDirOld = path.join(getAgentDir(), "agents");
2369
2498
  const userDirNew = path.join(os.homedir(), ".agents");
2370
2499
  const userChainDir = getUserChainDir();
2371
- const { readDirs: projectDirs, preferredDir: projectDir } = resolveNearestProjectAgentDirs(cwd);
2372
- const { readDirs: projectChainDirs, preferredDir: projectChainDir } = resolveNearestProjectChainDirs(cwd);
2500
+ const { readDirs: projectAgentDirs, candidateDirs: projectCandidateDirs, preferredDir: projectAgentsDir } = resolveNearestProjectAgentDirs(effectiveCwd);
2501
+ const { readDirs: projectChainDirs, preferredDir: projectChainDir } = resolveNearestProjectChainDirs(effectiveCwd);
2373
2502
  const userSettingsPath = getUserAgentSettingsPath();
2374
- const projectSettingsPath = getProjectAgentSettingsPath(cwd);
2375
- const userSettings = selectProviderOverrides(readSubagentSettings(userSettingsPath), preferredModelProvider);
2376
- const projectSettings = selectProviderOverrides(readSubagentSettings(projectSettingsPath), preferredModelProvider);
2377
- const defaultProvider = resolveSubagentDefaultProvider(userSettings, projectSettings, projectSettingsPath);
2378
- const defaultModel = resolveSubagentDefaultModel(userSettings, projectSettings, userSettingsPath, projectSettingsPath, defaultProvider);
2379
- const defaultThinking = resolveSubagentDefaultThinking(userSettings, projectSettings, projectSettingsPath);
2380
- const maxThinking = resolveSubagentMaxThinking(userSettings, projectSettings, projectSettingsPath);
2381
- const defaultExtensions = resolveSubagentDefaultExtensions(userSettings, projectSettings, projectSettingsPath);
2382
- const packageSubagentPaths = collectPackageSubagentPaths(cwd);
2503
+ const projectSettingsPath = getProjectAgentSettingsPath(effectiveCwd);
2504
+ const packageSubagentPaths = collectPackageSubagentPaths(effectiveCwd);
2383
2505
 
2384
2506
  const builtinLoaded = loadAgentsFromDefinitionFiles(BUILTIN_AGENT_DEFINITION_FILES, "builtin");
2385
- const builtin = applyBuiltinOverrides(
2386
- applySubagentDefaults(builtinLoaded.agents, defaultModel, defaultProvider, defaultThinking, defaultExtensions),
2387
- userSettings,
2388
- projectSettings,
2507
+ const userLoaded = [...extraUserAgentDirs(), userDirOld, userDirNew].map((dir, discoveryPriority): LoadedAgentDirectory => {
2508
+ const inspection = inspectAgentDefinitionDirectory(dir);
2509
+ return { dir, inspection, loaded: loadAgentsFromDir(dir, "user", discoveryPriority, undefined, inspection) };
2510
+ });
2511
+ const projectInspections = new Map(projectCandidateDirs.map((dir) => [dir, inspectAgentDefinitionDirectory(dir)]));
2512
+ const projectLoaded = projectAgentDirs.map((dir): LoadedAgentDirectory => {
2513
+ const inspection = projectInspections.get(dir)!;
2514
+ return { dir, inspection, loaded: loadAgentsFromDir(dir, "project", dir === projectAgentsDir ? 1 : 0, undefined, inspection) };
2515
+ });
2516
+ const packageLoaded = packageSubagentPaths.agents.map((entry, index): LoadedAgentDirectory => {
2517
+ const inspection = inspectAgentDefinitionDirectory(entry.dir);
2518
+ return { dir: entry.dir, inspection, packageEntry: entry, loaded: loadAgentsFromDir(entry.dir, "package", packageSubagentPaths.agents.length - index, entry, inspection) };
2519
+ });
2520
+
2521
+ const watchPaths = new Set<string>([
2389
2522
  userSettingsPath,
2390
- projectSettingsPath,
2391
- );
2392
- const userLoaded = [...extraUserAgentDirs(), userDirOld, userDirNew]
2393
- .map((dir, discoveryPriority) => loadAgentsFromDir(dir, "user", discoveryPriority));
2394
- const user = applyCustomAgentOverrides(
2395
- applySubagentDefaults(userLoaded.flatMap((loaded) => loaded.agents), defaultModel, defaultProvider, defaultThinking, defaultExtensions),
2396
- userSettings,
2397
- projectSettings,
2523
+ ...(projectSettingsPath ? [projectSettingsPath] : []),
2524
+ ...projectDiscoveryWatchPaths(effectiveCwd),
2525
+ ...findProjectRootCandidates(effectiveCwd).map((root) => path.join(getProjectConfigDir(root), "settings.json")),
2526
+ ...packageSubagentPaths.watchPaths,
2527
+ ]);
2528
+ for (const directory of userLoaded) addDirectoryWatchPaths(watchPaths, directory.dir, directory.inspection.files, inspectedAgentDefinitionDirectories(directory.inspection, directory.dir));
2529
+ for (const dir of projectCandidateDirs) {
2530
+ const inspection = projectInspections.get(dir);
2531
+ addDirectoryWatchPaths(watchPaths, dir, inspection?.files ?? [], inspection ? inspectedAgentDefinitionDirectories(inspection, dir) : []);
2532
+ }
2533
+ for (const directory of packageLoaded) addDirectoryWatchPaths(watchPaths, directory.dir, directory.inspection.files, inspectedAgentDefinitionDirectories(directory.inspection, directory.dir));
2534
+
2535
+ const userDir = process.env.PI_CODING_AGENT_DIR ? userDirOld : fs.existsSync(userDirNew) ? userDirNew : userDirOld;
2536
+ return {
2537
+ cwd: effectiveCwd,
2538
+ ...(preferredModelProvider !== undefined ? { preferredModelProvider } : {}),
2539
+ userDir,
2540
+ userDirOld,
2541
+ userDirNew,
2542
+ userChainDir,
2543
+ projectAgentDirs,
2544
+ projectCandidateDirs,
2545
+ projectAgentsDir,
2546
+ projectChainDirs,
2547
+ projectChainDir,
2398
2548
  userSettingsPath,
2399
2549
  projectSettingsPath,
2400
- );
2401
- const packageMap = new Map<string, AgentConfig>();
2402
- const packageAgentDiagnostics: AgentDiscoveryDiagnostic[] = [];
2403
- for (const [index, entry] of packageSubagentPaths.agents.entries()) {
2404
- const loaded = loadAgentsFromDir(entry.dir, "package", packageSubagentPaths.agents.length - index, entry);
2405
- packageAgentDiagnostics.push(...loaded.diagnostics);
2406
- for (const agent of loaded.agents) {
2407
- if (!packageMap.has(agent.name)) packageMap.set(agent.name, agent);
2550
+ packageSubagentPaths,
2551
+ builtinLoaded,
2552
+ userLoaded,
2553
+ projectLoaded,
2554
+ projectInspections,
2555
+ packageLoaded,
2556
+ watchPaths: [...watchPaths],
2557
+ };
2558
+ }
2559
+
2560
+ function ensureDiscoveryChains(sources: AgentDiscoverySources): void {
2561
+ if (sources.userChains) return;
2562
+ sources.packageChainLoaded = sources.packageSubagentPaths.chains.map((entry) => ({ entry, loaded: loadChainsFromDir(entry.dir, "package") }));
2563
+ sources.userChains = loadChainsFromDir(sources.userChainDir, "user");
2564
+ sources.projectChainLoaded = sources.projectChainDirs.map((dir) => ({ dir, loaded: loadChainsFromDir(dir, "project") }));
2565
+ const watchPaths = new Set(sources.watchPaths);
2566
+ addDirectoryWatchPaths(watchPaths, sources.userChainDir, sources.userChains.files, sources.userChains.directories);
2567
+ for (const chain of sources.projectChainLoaded) addDirectoryWatchPaths(watchPaths, chain.dir, chain.loaded.files, chain.loaded.directories);
2568
+ for (const chain of sources.packageChainLoaded) addDirectoryWatchPaths(watchPaths, chain.entry.dir, chain.loaded.files, chain.loaded.directories);
2569
+ sources.watchPaths = [...watchPaths];
2570
+ }
2571
+
2572
+ function getAgentDiscoverySources(cwd: string, preferredModelProvider?: string, includeChains = false): AgentDiscoverySources {
2573
+ const key = discoveryCacheKey(cwd, preferredModelProvider);
2574
+ const cached = agentDiscoveryCache.get(key);
2575
+ if (cached && cached.fingerprint === discoveryFingerprint(cached.sources.watchPaths)) {
2576
+ if (includeChains) {
2577
+ ensureDiscoveryChains(cached.sources);
2578
+ cached.fingerprint = discoveryFingerprint(cached.sources.watchPaths);
2408
2579
  }
2580
+ return cached.sources;
2581
+ }
2582
+ const sources = buildAgentDiscoverySources(cwd, preferredModelProvider);
2583
+ const entry = { sources, fingerprint: discoveryFingerprint(sources.watchPaths) };
2584
+ agentDiscoveryCache.set(key, entry);
2585
+ if (includeChains) {
2586
+ ensureDiscoveryChains(sources);
2587
+ entry.fingerprint = discoveryFingerprint(sources.watchPaths);
2409
2588
  }
2410
- const packageAgents = applyCustomAgentOverrides(
2411
- applySubagentDefaults(Array.from(packageMap.values()), defaultModel, defaultProvider, defaultThinking, defaultExtensions),
2589
+ return sources;
2590
+ }
2591
+
2592
+ /** Clear process-local discovery snapshots, primarily for hosts that reload settings in place. */
2593
+ export function clearAgentDiscoveryCache(): void {
2594
+ agentDiscoveryCache.clear();
2595
+ }
2596
+
2597
+ function ensureSettingsForScope(sources: AgentDiscoverySources, scope: AgentScope, preferredModelProvider?: string): void {
2598
+ if (scope !== "project" && sources.userSettings === undefined) {
2599
+ if (sources.packageSubagentPaths.settingsErrors.user) throw sources.packageSubagentPaths.settingsErrors.user;
2600
+ sources.userSettings = selectProviderOverrides(readSubagentSettings(sources.userSettingsPath), preferredModelProvider);
2601
+ }
2602
+ if (scope !== "user" && sources.projectSettings === undefined) {
2603
+ if (sources.packageSubagentPaths.settingsErrors.project) throw sources.packageSubagentPaths.settingsErrors.project;
2604
+ sources.projectSettings = selectProviderOverrides(readSubagentSettings(sources.projectSettingsPath), preferredModelProvider);
2605
+ }
2606
+ }
2607
+
2608
+ function settingsForScope(sources: AgentDiscoverySources, scope: AgentScope): { user: SubagentSettings; project: SubagentSettings } {
2609
+ // Parse only settings that participate in this projection. The source cache
2610
+ // still retains both source trees, while malformed out-of-scope settings do
2611
+ // not make an otherwise valid scoped projection fail.
2612
+ ensureSettingsForScope(sources, scope, sources.preferredModelProvider);
2613
+ return {
2614
+ user: scope === "project" ? EMPTY_SUBAGENT_SETTINGS : sources.userSettings ?? EMPTY_SUBAGENT_SETTINGS,
2615
+ project: scope === "user" ? EMPTY_SUBAGENT_SETTINGS : sources.projectSettings ?? EMPTY_SUBAGENT_SETTINGS,
2616
+ };
2617
+ }
2618
+
2619
+ function configuredAgentsForScope(sources: AgentDiscoverySources, scope: AgentScope, settingsScope = scope): {
2620
+ builtin: AgentConfig[];
2621
+ package: AgentConfig[];
2622
+ user: AgentConfig[];
2623
+ project: AgentConfig[];
2624
+ userSettings: SubagentSettings;
2625
+ projectSettings: SubagentSettings;
2626
+ maxThinking?: ThinkingLevel;
2627
+ modelScope?: ModelScopeConfig;
2628
+ } {
2629
+ const { user: userSettings, project: projectSettings } = settingsForScope(sources, settingsScope);
2630
+ const defaultProvider = resolveSubagentDefaultProvider(userSettings, projectSettings, sources.projectSettingsPath);
2631
+ const defaultModel = resolveSubagentDefaultModel(userSettings, projectSettings, sources.userSettingsPath, sources.projectSettingsPath, defaultProvider);
2632
+ const defaultThinking = resolveSubagentDefaultThinking(userSettings, projectSettings, sources.projectSettingsPath);
2633
+ const maxThinking = resolveSubagentMaxThinking(userSettings, projectSettings, sources.projectSettingsPath);
2634
+ const defaultExtensions = resolveSubagentDefaultExtensions(userSettings, projectSettings, sources.projectSettingsPath);
2635
+ const applyDefaults = (agents: AgentConfig[]): AgentConfig[] => applySubagentDefaults(agents, defaultModel, defaultProvider, defaultThinking, defaultExtensions);
2636
+ const builtin = applyBuiltinOverrides(applyDefaults(sources.builtinLoaded.agents), userSettings, projectSettings, sources.userSettingsPath, sources.projectSettingsPath);
2637
+ const user = applyCustomAgentOverrides(
2638
+ applyDefaults(scope === "project" ? [] : sources.userLoaded.flatMap((loaded) => loaded.loaded.agents)),
2412
2639
  userSettings,
2413
2640
  projectSettings,
2414
- userSettingsPath,
2415
- projectSettingsPath,
2641
+ sources.userSettingsPath,
2642
+ sources.projectSettingsPath,
2416
2643
  );
2417
2644
  const projectMap = new Map<string, AgentConfig>();
2418
- const projectAgentDiagnostics: AgentDiscoveryDiagnostic[] = [];
2419
- for (const dir of projectDirs) {
2420
- const loaded = loadAgentsFromDir(dir, "project", dir === projectDir ? 1 : 0);
2421
- projectAgentDiagnostics.push(...loaded.diagnostics);
2422
- for (const agent of loaded.agents) {
2423
- projectMap.set(agent.name, agent);
2424
- }
2645
+ if (scope !== "user") for (const loaded of sources.projectLoaded) for (const agent of loaded.loaded.agents) projectMap.set(agent.name, agent);
2646
+ const project = applyCustomAgentOverrides(applyDefaults(Array.from(projectMap.values())), userSettings, projectSettings, sources.userSettingsPath, sources.projectSettingsPath);
2647
+ const packageMap = new Map<string, AgentConfig>();
2648
+ for (const loaded of sources.packageLoaded) {
2649
+ if (!loaded.packageEntry || !packageEntryIncluded(scope, loaded.packageEntry.scope)) continue;
2650
+ for (const agent of loaded.loaded.agents) if (!packageMap.has(agent.name)) packageMap.set(agent.name, agent);
2425
2651
  }
2426
- const project = applyCustomAgentOverrides(
2427
- applySubagentDefaults(Array.from(projectMap.values()), defaultModel, defaultProvider, defaultThinking, defaultExtensions),
2652
+ const packageAgents = applyCustomAgentOverrides(applyDefaults(Array.from(packageMap.values())), userSettings, projectSettings, sources.userSettingsPath, sources.projectSettingsPath);
2653
+ return {
2654
+ builtin,
2655
+ package: packageAgents,
2656
+ user,
2657
+ project,
2428
2658
  userSettings,
2429
2659
  projectSettings,
2430
- userSettingsPath,
2431
- projectSettingsPath,
2660
+ maxThinking,
2661
+ modelScope: projectSettings.modelScope ?? userSettings.modelScope,
2662
+ };
2663
+ }
2664
+
2665
+ function discoveryDirectories(sources: AgentDiscoverySources, scope: AgentScope): AgentDefinitionDirectoryReport[] {
2666
+ const directories: AgentDefinitionDirectoryReport[] = [reportAgentDefinitionDirectory("builtin", BUILTIN_AGENTS_DIR, BUILTIN_AGENT_DEFINITION_INSPECTION)];
2667
+ if (scope !== "project") for (const directory of sources.userLoaded) directories.push(reportAgentDefinitionDirectory("user", directory.dir, directory.inspection));
2668
+ if (scope !== "user") for (const dir of sources.projectCandidateDirs) directories.push(reportAgentDefinitionDirectory("project", dir, sources.projectInspections.get(dir)!));
2669
+ for (const directory of sources.packageLoaded) {
2670
+ if (directory.packageEntry && packageEntryIncluded(scope, directory.packageEntry.scope)) directories.push(reportAgentDefinitionDirectory("package", directory.dir, directory.inspection));
2671
+ }
2672
+ return directories;
2673
+ }
2674
+
2675
+ function discoveryDiagnostics(sources: AgentDiscoverySources, scope: AgentScope): AgentDiscoveryDiagnostic[] {
2676
+ return [
2677
+ ...sources.builtinLoaded.diagnostics,
2678
+ ...(scope === "project" ? [] : sources.userLoaded.flatMap((loaded) => loaded.loaded.diagnostics)),
2679
+ ...(scope === "user" ? [] : sources.projectLoaded.flatMap((loaded) => loaded.loaded.diagnostics)),
2680
+ ...sources.packageLoaded
2681
+ .filter((loaded) => loaded.packageEntry && packageEntryIncluded(scope, loaded.packageEntry.scope))
2682
+ .flatMap((loaded) => loaded.loaded.diagnostics),
2683
+ ];
2684
+ }
2685
+
2686
+ function buildEffectiveDiscovery(sources: AgentDiscoverySources, scope: AgentScope): AgentDiscoveryResult {
2687
+ const configured = configuredAgentsForScope(sources, scope);
2688
+ const agents = applySubagentMaxThinking(
2689
+ mergeAgentsForScope(scope, configured.user, configured.project, configured.builtin, configured.package).filter((agent) => agent.disabled !== true),
2690
+ configured.maxThinking,
2432
2691
  );
2692
+ return {
2693
+ agents,
2694
+ agentDiagnostics: discoveryDiagnostics(sources, scope),
2695
+ projectAgentsDir: sources.projectAgentsDir,
2696
+ cwd: sources.cwd,
2697
+ scope,
2698
+ directories: discoveryDirectories(sources, scope),
2699
+ ...(configured.modelScope !== undefined ? { modelScope: configured.modelScope } : {}),
2700
+ ...(configured.maxThinking !== undefined ? { maxThinking: configured.maxThinking } : {}),
2701
+ };
2702
+ }
2433
2703
 
2434
- const chainMap = new Map<string, ChainConfig>();
2435
- const packageChainDiagnostics: ChainDiscoveryDiagnostic[] = [];
2704
+ function buildAllDiscovery(sources: AgentDiscoverySources, includeChains: boolean, settingsScope: AgentScope): AgentDiscoveryAllResult {
2705
+ if (includeChains) ensureDiscoveryChains(sources);
2706
+ const configured = configuredAgentsForScope(sources, "both", settingsScope);
2436
2707
  const packageChainMap = new Map<string, ChainConfig>();
2437
- for (const dir of packageSubagentPaths.chains) {
2438
- const loaded = loadChainsFromDir(dir, "package");
2439
- packageChainDiagnostics.push(...loaded.diagnostics);
2440
- for (const chain of loaded.chains) {
2441
- if (!packageChainMap.has(chain.name)) packageChainMap.set(chain.name, chain);
2442
- }
2708
+ const packageChainDiagnostics: ChainDiscoveryDiagnostic[] = [];
2709
+ for (const chain of sources.packageChainLoaded ?? []) {
2710
+ packageChainDiagnostics.push(...chain.loaded.diagnostics);
2711
+ for (const definition of chain.loaded.chains) if (!packageChainMap.has(definition.name)) packageChainMap.set(definition.name, definition);
2443
2712
  }
2713
+ const projectChainMap = new Map<string, ChainConfig>();
2444
2714
  const projectChainDiagnostics: ChainDiscoveryDiagnostic[] = [];
2445
- for (const dir of projectChainDirs) {
2446
- const loaded = loadChainsFromDir(dir, "project");
2447
- projectChainDiagnostics.push(...loaded.diagnostics);
2448
- for (const chain of loaded.chains) {
2449
- chainMap.set(chain.name, chain);
2450
- }
2715
+ for (const chain of sources.projectChainLoaded ?? []) {
2716
+ projectChainDiagnostics.push(...chain.loaded.diagnostics);
2717
+ for (const definition of chain.loaded.chains) projectChainMap.set(definition.name, definition);
2451
2718
  }
2452
- const userChains = loadChainsFromDir(userChainDir, "user");
2453
2719
  const chains = [
2454
2720
  ...Array.from(packageChainMap.values()),
2455
- ...userChains.chains,
2456
- ...Array.from(chainMap.values()),
2457
- ];
2458
- const chainDiagnostics = [
2459
- ...packageChainDiagnostics,
2460
- ...userChains.diagnostics,
2461
- ...projectChainDiagnostics,
2721
+ ...(sources.userChains?.chains ?? []),
2722
+ ...Array.from(projectChainMap.values()),
2462
2723
  ];
2463
- const agentDiagnostics = [
2464
- ...builtinLoaded.diagnostics,
2465
- ...userLoaded.flatMap((loaded) => loaded.diagnostics),
2466
- ...packageAgentDiagnostics,
2467
- ...projectAgentDiagnostics,
2468
- ];
2469
-
2470
- const userDir = process.env.PI_CODING_AGENT_DIR ? userDirOld : fs.existsSync(userDirNew) ? userDirNew : userDirOld;
2471
-
2472
2724
  return {
2473
- builtin: applySubagentMaxThinking(builtin, maxThinking),
2474
- package: applySubagentMaxThinking(packageAgents, maxThinking),
2475
- user: applySubagentMaxThinking(user, maxThinking),
2476
- project: applySubagentMaxThinking(project, maxThinking),
2477
- agentDiagnostics,
2725
+ builtin: applySubagentMaxThinking(configured.builtin, configured.maxThinking),
2726
+ package: applySubagentMaxThinking(configured.package, configured.maxThinking),
2727
+ user: applySubagentMaxThinking(configured.user, configured.maxThinking),
2728
+ project: applySubagentMaxThinking(configured.project, configured.maxThinking),
2729
+ agentDiagnostics: [
2730
+ ...sources.builtinLoaded.diagnostics,
2731
+ ...sources.userLoaded.flatMap((loaded) => loaded.loaded.diagnostics),
2732
+ ...sources.packageLoaded.flatMap((loaded) => loaded.loaded.diagnostics),
2733
+ ...sources.projectLoaded.flatMap((loaded) => loaded.loaded.diagnostics),
2734
+ ],
2478
2735
  chains,
2479
- chainDiagnostics,
2480
- userDir,
2481
- projectDir,
2482
- userChainDir,
2483
- projectChainDir,
2484
- userSettingsPath,
2485
- projectSettingsPath,
2486
- ...(maxThinking !== undefined ? { maxThinking } : {}),
2736
+ chainDiagnostics: [...packageChainDiagnostics, ...(sources.userChains?.diagnostics ?? []), ...projectChainDiagnostics],
2737
+ userDir: sources.userDir,
2738
+ projectDir: sources.projectAgentsDir,
2739
+ userChainDir: sources.userChainDir,
2740
+ projectChainDir: sources.projectChainDir,
2741
+ userSettingsPath: sources.userSettingsPath,
2742
+ projectSettingsPath: sources.projectSettingsPath,
2743
+ ...(configured.maxThinking !== undefined ? { maxThinking: configured.maxThinking } : {}),
2487
2744
  };
2488
2745
  }
2746
+
2747
+ export function discoverAgentSnapshot(
2748
+ cwd: string,
2749
+ scope: AgentScope,
2750
+ preferredModelProvider?: string,
2751
+ options: { includeChains?: boolean } = {},
2752
+ ): AgentDiscoverySnapshot {
2753
+ const includeChains = options.includeChains !== false;
2754
+ const sources = getAgentDiscoverySources(cwd, preferredModelProvider, includeChains);
2755
+ return { effective: buildEffectiveDiscovery(sources, scope), all: buildAllDiscovery(sources, includeChains, scope) };
2756
+ }
2757
+
2758
+ function discoverAgentsUncached(cwd: string, scope: AgentScope, preferredModelProvider?: string): AgentDiscoveryResult {
2759
+ const effectiveCwd = path.resolve(cwd);
2760
+ const userDirOld = path.join(getAgentDir(), "agents");
2761
+ const userDirNew = path.join(os.homedir(), ".agents");
2762
+ const { readDirs: projectAgentDirs, candidateDirs: projectCandidateDirs, preferredDir: projectAgentsDir } = resolveNearestProjectAgentDirs(effectiveCwd);
2763
+ const userSettingsPath = getUserAgentSettingsPath();
2764
+ const projectSettingsPath = getProjectAgentSettingsPath(effectiveCwd);
2765
+ const userSettings = selectProviderOverrides(scope === "project" ? EMPTY_SUBAGENT_SETTINGS : readSubagentSettings(userSettingsPath), preferredModelProvider);
2766
+ const projectSettings = selectProviderOverrides(scope === "user" ? EMPTY_SUBAGENT_SETTINGS : readSubagentSettings(projectSettingsPath), preferredModelProvider);
2767
+ const defaultProvider = resolveSubagentDefaultProvider(userSettings, projectSettings, projectSettingsPath);
2768
+ const defaultModel = resolveSubagentDefaultModel(userSettings, projectSettings, userSettingsPath, projectSettingsPath, defaultProvider);
2769
+ const defaultThinking = resolveSubagentDefaultThinking(userSettings, projectSettings, projectSettingsPath);
2770
+ const maxThinking = resolveSubagentMaxThinking(userSettings, projectSettings, projectSettingsPath);
2771
+ const defaultExtensions = resolveSubagentDefaultExtensions(userSettings, projectSettings, projectSettingsPath);
2772
+ const modelScope = projectSettings.modelScope ?? userSettings.modelScope;
2773
+ const packageSubagentPaths = collectPackageSubagentPaths(effectiveCwd, { includeUser: scope !== "project", includeProject: scope !== "user" });
2774
+ const directories: AgentDefinitionDirectoryReport[] = [reportAgentDefinitionDirectory("builtin", BUILTIN_AGENTS_DIR, BUILTIN_AGENT_DEFINITION_INSPECTION)];
2775
+ const builtinLoaded = loadAgentsFromDefinitionFiles(BUILTIN_AGENT_DEFINITION_FILES, "builtin");
2776
+ const builtinAgents = applyBuiltinOverrides(applySubagentDefaults(builtinLoaded.agents, defaultModel, defaultProvider, defaultThinking, defaultExtensions), userSettings, projectSettings, userSettingsPath, projectSettingsPath);
2777
+ const userLoaded = scope === "project" ? [] : [...extraUserAgentDirs(), userDirOld, userDirNew].map((dir, discoveryPriority) => {
2778
+ const inspection = inspectAgentDefinitionDirectory(dir);
2779
+ directories.push(reportAgentDefinitionDirectory("user", dir, inspection));
2780
+ return loadAgentsFromDir(dir, "user", discoveryPriority, undefined, inspection);
2781
+ });
2782
+ const userAgents = applyCustomAgentOverrides(applySubagentDefaults(userLoaded.flatMap((loaded) => loaded.agents), defaultModel, defaultProvider, defaultThinking, defaultExtensions), userSettings, projectSettings, userSettingsPath, projectSettingsPath);
2783
+ const projectInspections = scope === "user" ? new Map<string, AgentDefinitionInspection>() : new Map(projectCandidateDirs.map((dir) => [dir, inspectAgentDefinitionDirectory(dir)]));
2784
+ if (scope !== "user") for (const dir of projectCandidateDirs) directories.push(reportAgentDefinitionDirectory("project", dir, projectInspections.get(dir)!));
2785
+ const projectLoaded = scope === "user" ? [] : projectAgentDirs.map((dir) => loadAgentsFromDir(dir, "project", dir === projectAgentsDir ? 1 : 0, undefined, projectInspections.get(dir)!));
2786
+ const projectAgents = applyCustomAgentOverrides(applySubagentDefaults(projectLoaded.flatMap((loaded) => loaded.agents), defaultModel, defaultProvider, defaultThinking, defaultExtensions), userSettings, projectSettings, userSettingsPath, projectSettingsPath);
2787
+ const packageLoaded = packageSubagentPaths.agents.map((entry, index) => {
2788
+ const inspection = inspectAgentDefinitionDirectory(entry.dir);
2789
+ directories.push(reportAgentDefinitionDirectory("package", entry.dir, inspection));
2790
+ return loadAgentsFromDir(entry.dir, "package", packageSubagentPaths.agents.length - index, entry, inspection);
2791
+ });
2792
+ const packageMap = new Map<string, AgentConfig>();
2793
+ for (const loaded of packageLoaded) for (const agent of loaded.agents) if (!packageMap.has(agent.name)) packageMap.set(agent.name, agent);
2794
+ const packageAgents = applyCustomAgentOverrides(applySubagentDefaults(Array.from(packageMap.values()), defaultModel, defaultProvider, defaultThinking, defaultExtensions), userSettings, projectSettings, userSettingsPath, projectSettingsPath);
2795
+ const agents = applySubagentMaxThinking(mergeAgentsForScope(scope, userAgents, projectAgents, builtinAgents, packageAgents).filter((agent) => agent.disabled !== true), maxThinking);
2796
+ const agentDiagnostics = [...builtinLoaded.diagnostics, ...userLoaded.flatMap((loaded) => loaded.diagnostics), ...projectLoaded.flatMap((loaded) => loaded.diagnostics), ...packageLoaded.flatMap((loaded) => loaded.diagnostics)];
2797
+ return { agents, agentDiagnostics, projectAgentsDir, cwd: effectiveCwd, scope, directories, ...(modelScope !== undefined ? { modelScope } : {}), ...(maxThinking !== undefined ? { maxThinking } : {}) };
2798
+ }
2799
+
2800
+ export function discoverAgents(cwd: string, scope: AgentScope, preferredModelProvider?: string): AgentDiscoveryResult {
2801
+ if (scope !== "both") return discoverAgentsUncached(cwd, scope, preferredModelProvider);
2802
+ const sources = getAgentDiscoverySources(cwd, preferredModelProvider);
2803
+ return buildEffectiveDiscovery(sources, scope);
2804
+ }
2805
+
2806
+ export function discoverAgentsAll(cwd: string, preferredModelProvider?: string): AgentDiscoveryAllResult {
2807
+ return discoverAgentSnapshot(cwd, "both", preferredModelProvider).all;
2808
+ }