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.
- package/CHANGELOG.md +55 -0
- package/docs/agents.md +7 -3
- package/docs/configuration.md +9 -5
- package/docs/extension-api.md +14 -7
- package/docs/models.md +1 -1
- package/docs/observability.md +1 -1
- package/docs/tool-reference.md +13 -4
- package/docs/workflows.md +14 -13
- package/install.mjs +1 -1
- package/package.json +1 -1
- package/skills/pi-subagents/SKILL.md +6 -4
- package/skills/pi-subagents/references/constraints-and-recipes.md +1 -1
- package/skills/pi-subagents/references/execution-controls.md +41 -11
- package/skills/pi-subagents/references/multi-lane-orchestration.md +1 -1
- package/skills/pi-subagents/references/prompting-and-roles.md +4 -4
- package/skills/pi-subagents/references/review-and-validation.md +1 -1
- package/src/agents/agent-management.ts +121 -63
- package/src/agents/agent-serializer.ts +3 -0
- package/src/agents/agents.ts +543 -223
- package/src/agents/runtime-agent-registry.ts +5 -1
- package/src/api/background-work.ts +7 -2
- package/src/api/external-runs.ts +67 -4
- package/src/api/preflight.ts +17 -8
- package/src/api/shared-types.ts +1 -0
- package/src/extension/index.ts +7 -4
- package/src/extension/public-execution.ts +48 -4
- package/src/extension/rpc.ts +62 -4
- package/src/extension/schemas.ts +17 -9
- package/src/extension/tool-description.ts +15 -19
- package/src/runs/background/active-async-capacity.ts +26 -7
- package/src/runs/background/async-execution.ts +98 -51
- package/src/runs/background/async-job-tracker.ts +62 -3
- package/src/runs/background/async-resume.ts +6 -3
- package/src/runs/background/async-status.ts +59 -9
- package/src/runs/background/auto-drain.ts +1 -1
- package/src/runs/background/fleet-view.ts +1 -1
- package/src/runs/background/process-terminal.ts +16 -0
- package/src/runs/background/result-watcher.ts +1 -1
- package/src/runs/background/resume-guidance.ts +1 -1
- package/src/runs/background/run-status.ts +2 -2
- package/src/runs/background/scheduled-runs.ts +63 -6
- package/src/runs/background/steering.ts +4 -1
- package/src/runs/background/subagent-runner.ts +15 -9
- package/src/runs/background/subagent-wait.ts +20 -21
- package/src/runs/background/wait-completions.ts +1 -1
- package/src/runs/background/wait-tool.ts +18 -18
- package/src/runs/foreground/execution.ts +75 -9
- package/src/runs/foreground/subagent-executor.ts +181 -60
- package/src/runs/shared/acceptance.ts +113 -27
- package/src/runs/shared/capability-ceiling.ts +1 -0
- package/src/runs/shared/dynamic-fanout.ts +1 -1
- package/src/runs/shared/host-step-status.ts +1 -0
- package/src/runs/shared/model-fallback.ts +61 -17
- package/src/runs/shared/parallel-utils.ts +2 -6
- package/src/runs/shared/permissions.ts +1 -1
- package/src/runs/shared/pi-args.ts +24 -12
- package/src/runs/shared/pi-spawn.ts +69 -35
- package/src/runs/shared/structured-output.ts +33 -6
- package/src/runs/shared/subagent-prompt-runtime.ts +20 -3
- package/src/runs/shared/task-intent.ts +17 -6
- package/src/runs/shared/tool-timeout.ts +1 -1
- package/src/runs/shared/workflow-graph.ts +3 -2
- package/src/shared/atomic-json.ts +3 -1
- package/src/shared/fork-context.ts +0 -12
- package/src/shared/fork-session-cwd.ts +27 -0
- package/src/shared/launch-contract.ts +3 -0
- package/src/shared/types.ts +43 -4
- package/src/shared/workflow-child-permit.ts +91 -0
- package/src/slash/prompt-template-bridge.ts +37 -1
- package/src/slash/slash-commands.ts +18 -26
- package/src/slash/subagents-admin.ts +2 -0
- package/src/tui/render.ts +95 -52
- package/src/workflows/scripted-workflow.ts +153 -4
- package/src/workflows/workflow-child-summary.ts +1 -1
- package/src/workflows/workflow-receipt.ts +41 -4
- package/src/workflows/workflow-resources.ts +150 -0
package/src/agents/agents.ts
CHANGED
|
@@ -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:
|
|
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:
|
|
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 =
|
|
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
|
-
|
|
597
|
-
|
|
598
|
-
|
|
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
|
-
|
|
604
|
-
|
|
605
|
-
|
|
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)
|
|
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<
|
|
615
|
-
const seenAgents = new
|
|
616
|
-
const seenChains = new
|
|
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:
|
|
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
|
-
|
|
622
|
-
|
|
623
|
-
|
|
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
|
-
|
|
626
|
-
seenAgents.
|
|
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
|
-
|
|
631
|
-
seenChains.
|
|
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
|
|
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
|
|
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:
|
|
2372
|
-
const { readDirs: projectChainDirs, preferredDir: projectChainDir } = resolveNearestProjectChainDirs(
|
|
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(
|
|
2375
|
-
const
|
|
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
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
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
|
-
|
|
2393
|
-
.
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
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
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
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
|
-
|
|
2411
|
-
|
|
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
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
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
|
|
2427
|
-
|
|
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
|
-
|
|
2431
|
-
|
|
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
|
-
|
|
2435
|
-
|
|
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
|
-
|
|
2438
|
-
|
|
2439
|
-
packageChainDiagnostics.push(...loaded.diagnostics);
|
|
2440
|
-
for (const
|
|
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
|
|
2446
|
-
|
|
2447
|
-
|
|
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
|
|
2456
|
-
...Array.from(
|
|
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(
|
|
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
|
+
}
|