pi-subagents 0.67.0 → 0.69.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 +90 -0
- package/README.md +1 -1
- package/docs/agents.md +41 -12
- package/docs/configuration.md +61 -19
- package/docs/extension-api.md +5 -1
- package/docs/missions.md +2 -2
- package/docs/models.md +11 -79
- package/docs/observability.md +18 -8
- package/docs/standalone-background.md +13 -3
- package/docs/tool-reference.md +38 -14
- package/docs/watchdog.md +10 -12
- package/docs/workflows.md +59 -1
- package/index.ts +5 -2
- package/package.json +4 -2
- package/runner-peer-loader.mjs +24 -0
- package/runner-peer-preload.mjs +25 -11
- package/skills/pi-subagents/SKILL.md +18 -21
- package/skills/pi-subagents/references/constraints-and-recipes.md +3 -2
- package/skills/pi-subagents/references/execution-controls.md +6 -4
- package/skills/pi-subagents/references/management-authoring-rpc.md +0 -1
- package/skills/pi-subagents/references/multi-lane-orchestration.md +1 -1
- package/skills/pi-subagents/references/prompting-and-roles.md +16 -12
- package/skills/pi-subagents/references/review-and-validation.md +3 -3
- package/src/agents/agent-management.ts +57 -58
- package/src/agents/agent-serializer.ts +4 -3
- package/src/agents/agents.ts +185 -72
- package/src/agents/chain-serializer.ts +5 -0
- package/src/agents/runtime-agent-registry.ts +7 -6
- package/src/agents/skills.ts +1 -1
- package/src/api/preflight.ts +20 -16
- package/src/api/required-child-extensions.ts +6 -0
- package/src/extension/config.ts +10 -37
- package/src/extension/fanout-child.ts +3 -0
- package/src/extension/herdr-pi-bridge.ts +160 -0
- package/src/extension/index.ts +42 -31
- package/src/extension/public-execution.ts +3 -3
- package/src/extension/schemas.ts +23 -6
- package/src/extension/tool-description.ts +8 -7
- package/src/inspectors/ghostty/plugin.ts +13 -1
- package/src/intercom/native-supervisor-channel.ts +22 -18
- package/src/policy/authority.ts +4 -0
- package/src/profiles/profiles.ts +12 -6
- package/src/runs/background/active-run-index.ts +17 -1
- package/src/runs/background/async-execution.ts +309 -126
- package/src/runs/background/async-job-tracker.ts +8 -6
- package/src/runs/background/async-resume.ts +13 -4
- package/src/runs/background/async-status.ts +15 -4
- package/src/runs/background/auto-drain.ts +20 -10
- package/src/runs/background/binary-bootstrap.ts +5 -0
- package/src/runs/background/chain-append.ts +1 -1
- package/src/runs/background/chain-root-attachment.ts +14 -33
- package/src/runs/background/notify.ts +74 -6
- package/src/runs/background/result-files.ts +8 -4
- package/src/runs/background/result-watcher.ts +19 -2
- package/src/runs/background/run-child-session.ts +20 -29
- package/src/runs/background/runner-aliases.ts +4 -33
- package/src/runs/background/runner-child-launch.ts +4 -1
- package/src/runs/background/runner-child-sessions.ts +2 -2
- package/src/runs/background/runner-http-dispatcher.ts +119 -0
- package/src/runs/background/scheduled-runs.ts +11 -5
- package/src/runs/background/stale-run-reconciler.ts +35 -11
- package/src/runs/background/subagent-runner.ts +413 -276
- package/src/runs/background/subagent-wait.ts +128 -23
- package/src/runs/background/wait-completions.ts +75 -27
- package/src/runs/background/wait-subscriptions.ts +9 -3
- package/src/runs/background/wait-tool.ts +4 -2
- package/src/runs/foreground/async-stop-action.ts +93 -3
- package/src/runs/foreground/execution.ts +115 -219
- package/src/runs/foreground/foreground-history.ts +2 -1
- package/src/runs/foreground/subagent-executor.ts +281 -80
- package/src/runs/shared/acceptance.ts +194 -37
- package/src/runs/shared/async-status-projection.ts +123 -33
- package/src/runs/shared/child-launch-plan.ts +15 -3
- package/src/runs/shared/child-launch.ts +19 -6
- package/src/runs/shared/child-runtime-config.ts +5 -0
- package/src/runs/shared/child-session.ts +94 -50
- package/src/runs/shared/child-tool-plan.ts +28 -16
- package/src/runs/shared/dynamic-fanout.ts +2 -2
- package/src/runs/shared/external-cli-contract.ts +11 -1
- package/src/runs/shared/external-cli-preflight.ts +6 -2
- package/src/runs/shared/herdr-connection.ts +134 -0
- package/src/runs/shared/herdr-external-adapters.ts +169 -0
- package/src/runs/shared/herdr-machine.ts +279 -0
- package/src/runs/shared/herdr-pi-protocol.ts +59 -0
- package/src/runs/shared/herdr-placed-run.ts +263 -0
- package/src/runs/shared/model-resolution-diagnostic.ts +76 -0
- package/src/runs/shared/{model-fallback.ts → model-resolution.ts} +22 -237
- package/src/runs/shared/model-scope.ts +1 -1
- package/src/runs/shared/nested-events.ts +11 -2
- package/src/runs/shared/parallel-utils.ts +7 -2
- package/src/runs/shared/pi-spawn.ts +1 -1
- package/src/runs/shared/subagent-prompt-runtime.ts +4 -2
- package/src/runs/shared/worktree-setup-command.ts +27 -4
- package/src/runs/shared/worktree.ts +30 -8
- package/src/shared/child-cache-retention.ts +43 -0
- package/src/shared/launch-contract.ts +6 -9
- package/src/shared/pruned-fork.ts +1 -1
- package/src/shared/required-child-extensions.ts +81 -0
- package/src/shared/settings.ts +5 -2
- package/src/shared/shortcuts.ts +0 -4
- package/src/shared/types.ts +81 -29
- package/src/slash/slash-commands.ts +0 -6
- package/src/slash/subagents-admin.ts +13 -9
- package/src/tui/render.ts +20 -10
- package/src/watchdog/child-status.ts +28 -36
- package/src/watchdog/lsp-diagnostics.ts +1 -1
- package/src/watchdog/model-selection.ts +1 -1
- package/src/watchdog/register-child.ts +10 -3
- package/src/watchdog/register-main.ts +20 -20
- package/src/watchdog/render.ts +1 -1
- package/src/watchdog/review.ts +14 -30
- package/src/watchdog/rules.ts +1 -1
- package/src/watchdog/runtime.ts +23 -12
- package/src/watchdog/settings.ts +3 -6
- package/src/watchdog/types.ts +3 -5
- package/src/watchdog/warning-format.ts +1 -1
- package/src/workflows/scripted-workflow.ts +68 -7
- package/src/workflows/workflow-receipt.ts +21 -3
- package/src/workflows/workflow-resources.ts +13 -2
- package/src/runs/shared/model-exclusions.ts +0 -374
- package/src/runs/shared/readonly-model-continuation.ts +0 -69
- package/src/runs/shared/readonly-session-evidence.ts +0 -307
package/src/agents/agents.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { parse as parseYaml } from "yaml";
|
|
|
8
8
|
import * as os from "node:os";
|
|
9
9
|
import * as path from "node:path";
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
11
|
-
import type { AcceptanceInput, AcceptanceRole, AgentRunnerConfig, OutputMode, ToolBudgetConfig } from "../shared/types.ts";
|
|
11
|
+
import type { AcceptanceInput, AcceptanceRole, AgentRunnerConfig, JsonSchemaObject, OutputMode, ToolBudgetConfig } from "../shared/types.ts";
|
|
12
12
|
import { CODE_OWNED_EXTERNAL_CLI_ADAPTER_LABEL, isCodeOwnedExternalCliAdapterId, parseExternalCliCapabilityNarrowing, validateCodeOwnedProfileRunner } from "../runs/shared/external-cli-contract.ts";
|
|
13
13
|
import { getAgentDir, getProjectConfigDir } from "../shared/utils.ts";
|
|
14
14
|
import { expandHomePath } from "../shared/settings.ts";
|
|
@@ -24,6 +24,7 @@ import { parseMemoryFrontmatter } from "./agent-memory.ts";
|
|
|
24
24
|
import { validateAcceptanceInput } from "../runs/shared/acceptance.ts";
|
|
25
25
|
import { validatePermissionRules, type PermissionRules } from "../runs/shared/permissions.ts";
|
|
26
26
|
import { parseThinkingLevel, type ThinkingLevel } from "../shared/thinking-ceiling.ts";
|
|
27
|
+
import { assertJsonSchemaObject } from "../runs/shared/structured-output.ts";
|
|
27
28
|
|
|
28
29
|
export type AgentScope = "user" | "project" | "both";
|
|
29
30
|
|
|
@@ -52,12 +53,12 @@ export function defaultInheritSkills(): boolean {
|
|
|
52
53
|
|
|
53
54
|
export interface BuiltinAgentOverrideBase {
|
|
54
55
|
description?: string;
|
|
56
|
+
machine?: string;
|
|
55
57
|
output?: string;
|
|
56
58
|
outputMode?: OutputMode;
|
|
57
59
|
defaultReads?: string[];
|
|
58
60
|
model?: string;
|
|
59
61
|
modelProvider?: string;
|
|
60
|
-
fallbackModels?: string[];
|
|
61
62
|
fast?: boolean;
|
|
62
63
|
thinking?: string | false;
|
|
63
64
|
systemPromptMode: SystemPromptMode;
|
|
@@ -83,12 +84,12 @@ export interface BuiltinAgentOverrideBase {
|
|
|
83
84
|
|
|
84
85
|
interface BuiltinAgentOverrideConfig {
|
|
85
86
|
description?: string;
|
|
87
|
+
machine?: string | false;
|
|
86
88
|
output?: string | false;
|
|
87
89
|
outputMode?: OutputMode;
|
|
88
90
|
defaultReads?: string[] | false;
|
|
89
91
|
model?: string | false;
|
|
90
92
|
defaultProvider?: string | false;
|
|
91
|
-
fallbackModels?: string[] | false;
|
|
92
93
|
fast?: boolean;
|
|
93
94
|
thinking?: string | false;
|
|
94
95
|
systemPromptMode?: SystemPromptMode;
|
|
@@ -143,7 +144,6 @@ export interface AgentConfig {
|
|
|
143
144
|
mcpDirectTools?: string[];
|
|
144
145
|
model?: string;
|
|
145
146
|
modelProvider?: string;
|
|
146
|
-
fallbackModels?: string[];
|
|
147
147
|
fast?: boolean;
|
|
148
148
|
thinking?: string | false;
|
|
149
149
|
systemPromptMode: SystemPromptMode;
|
|
@@ -168,6 +168,7 @@ export interface AgentConfig {
|
|
|
168
168
|
mutationTools?: string[];
|
|
169
169
|
output?: string;
|
|
170
170
|
outputMode?: OutputMode;
|
|
171
|
+
outputSchema?: JsonSchemaObject;
|
|
171
172
|
defaultReads?: string[];
|
|
172
173
|
defaultProgress?: boolean;
|
|
173
174
|
interactive?: boolean;
|
|
@@ -176,6 +177,7 @@ export interface AgentConfig {
|
|
|
176
177
|
toolBudget?: ToolBudgetConfig;
|
|
177
178
|
permissions?: PermissionRules;
|
|
178
179
|
memory?: AgentMemoryConfig;
|
|
180
|
+
machine?: string;
|
|
179
181
|
disabled?: boolean;
|
|
180
182
|
extraFields?: Record<string, string>;
|
|
181
183
|
override?: BuiltinAgentOverrideInfo;
|
|
@@ -195,6 +197,7 @@ interface SubagentSettings {
|
|
|
195
197
|
overrides: Record<string, BuiltinAgentOverrideConfig>;
|
|
196
198
|
providerOverrides: Record<string, Record<string, BuiltinAgentOverrideConfig>>;
|
|
197
199
|
agentScanDirs?: string[];
|
|
200
|
+
agentExcludeDirs?: string[];
|
|
198
201
|
defaultModel?: string;
|
|
199
202
|
defaultProvider?: string;
|
|
200
203
|
defaultThinking?: string;
|
|
@@ -215,6 +218,7 @@ export interface ChainStepConfig {
|
|
|
215
218
|
label?: string;
|
|
216
219
|
as?: string;
|
|
217
220
|
outputSchema?: string | Record<string, unknown>;
|
|
221
|
+
machine?: string;
|
|
218
222
|
output?: string | false;
|
|
219
223
|
outputMode?: OutputMode;
|
|
220
224
|
reads?: string[] | false;
|
|
@@ -491,7 +495,7 @@ function getGlobalNpmRoot(): string | null {
|
|
|
491
495
|
}
|
|
492
496
|
|
|
493
497
|
try {
|
|
494
|
-
cachedGlobalNpmRoot = fs.realpathSync(execSync("npm root -g", { encoding: "utf-8", timeout: 5000, windowsHide: true }).trim());
|
|
498
|
+
cachedGlobalNpmRoot = fs.realpathSync(execSync("npm root -g", { encoding: "utf-8", timeout: 5000, windowsHide: true, stdio: ["ignore", "pipe", "ignore"] }).trim());
|
|
495
499
|
return cachedGlobalNpmRoot;
|
|
496
500
|
} catch {
|
|
497
501
|
cachedGlobalNpmRoot = "";
|
|
@@ -591,8 +595,8 @@ function collectSettingsPackageRoots(settingsFile: string, baseDir: string): str
|
|
|
591
595
|
for (const entry of packages) {
|
|
592
596
|
const packageSource = typeof entry === "string"
|
|
593
597
|
? entry
|
|
594
|
-
: typeof entry === "object" && entry !== null &&
|
|
595
|
-
?
|
|
598
|
+
: typeof entry === "object" && entry !== null && "source" in entry && typeof entry.source === "string"
|
|
599
|
+
? entry.source
|
|
596
600
|
: undefined;
|
|
597
601
|
if (!packageSource) continue;
|
|
598
602
|
const packageRoot = resolveSettingsPackageRoot(packageSource, baseDir);
|
|
@@ -700,12 +704,20 @@ function effectiveAgentMatch(matches: AgentConfig[]): { agent?: AgentConfig; err
|
|
|
700
704
|
|
|
701
705
|
export function resolveAgentName(name: string, agents: AgentConfig[]): { agent?: AgentConfig; error?: string } {
|
|
702
706
|
const raw = name.trim();
|
|
703
|
-
const
|
|
704
|
-
if (
|
|
705
|
-
if (
|
|
706
|
-
const effective = effectiveAgentMatch(
|
|
707
|
+
const canonical = agents.filter((agent) => agent.name === raw);
|
|
708
|
+
if (canonical.length === 1) return canonical[0] ? { agent: canonical[0] } : {};
|
|
709
|
+
if (canonical.length > 1) {
|
|
710
|
+
const effective = effectiveAgentMatch(canonical);
|
|
707
711
|
if (effective.agent) return effective;
|
|
708
|
-
return { error: `Ambiguous agent name '${name}': ${
|
|
712
|
+
return { error: `Ambiguous agent name '${name}': ${canonical.map((agent) => agent.name).join(", ")}` };
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
const local = agents.filter((agent) => agent.localName === raw);
|
|
716
|
+
if (local.length === 1) return local[0] ? { agent: local[0] } : {};
|
|
717
|
+
if (local.length > 1) {
|
|
718
|
+
const effective = effectiveAgentMatch(local);
|
|
719
|
+
if (effective.agent) return effective;
|
|
720
|
+
return { error: `Ambiguous local agent name '${name}': ${local.map((agent) => agent.name).join(", ")}` };
|
|
709
721
|
}
|
|
710
722
|
|
|
711
723
|
const aliases = agents.filter((agent) => agent.aliases?.includes(raw));
|
|
@@ -755,12 +767,12 @@ function arraysEqual(a: string[] | undefined, b: string[] | undefined): boolean
|
|
|
755
767
|
function cloneOverrideBase(agent: AgentConfig): BuiltinAgentOverrideBase {
|
|
756
768
|
return {
|
|
757
769
|
description: agent.description,
|
|
770
|
+
...(agent.machine !== undefined ? { machine: agent.machine } : {}),
|
|
758
771
|
...(agent.output !== undefined ? { output: agent.output } : {}),
|
|
759
772
|
...(agent.outputMode !== undefined ? { outputMode: agent.outputMode } : {}),
|
|
760
773
|
...(agent.defaultReads !== undefined ? { defaultReads: [...agent.defaultReads] } : {}),
|
|
761
774
|
...(agent.model !== undefined ? { model: agent.model } : {}),
|
|
762
775
|
...(agent.modelProvider !== undefined ? { modelProvider: agent.modelProvider } : {}),
|
|
763
|
-
...(agent.fallbackModels ? { fallbackModels: [...agent.fallbackModels] } : {}),
|
|
764
776
|
...(agent.fast !== undefined ? { fast: agent.fast } : {}),
|
|
765
777
|
...(agent.thinking !== undefined ? { thinking: agent.thinking } : {}),
|
|
766
778
|
systemPromptMode: agent.systemPromptMode,
|
|
@@ -788,14 +800,12 @@ function cloneOverrideBase(agent: AgentConfig): BuiltinAgentOverrideBase {
|
|
|
788
800
|
function cloneOverrideValue(override: BuiltinAgentOverrideConfig): BuiltinAgentOverrideConfig {
|
|
789
801
|
return {
|
|
790
802
|
...(override.description !== undefined ? { description: override.description } : {}),
|
|
803
|
+
...(override.machine !== undefined ? { machine: override.machine } : {}),
|
|
791
804
|
...(override.output !== undefined ? { output: override.output } : {}),
|
|
792
805
|
...(override.outputMode !== undefined ? { outputMode: override.outputMode } : {}),
|
|
793
806
|
...(override.defaultReads !== undefined ? { defaultReads: override.defaultReads === false ? false : [...override.defaultReads] } : {}),
|
|
794
807
|
...(override.model !== undefined ? { model: override.model } : {}),
|
|
795
808
|
...(override.defaultProvider !== undefined ? { defaultProvider: override.defaultProvider } : {}),
|
|
796
|
-
...(override.fallbackModels !== undefined
|
|
797
|
-
? { fallbackModels: override.fallbackModels === false ? false : [...override.fallbackModels] }
|
|
798
|
-
: {}),
|
|
799
809
|
...(override.fast !== undefined ? { fast: override.fast } : {}),
|
|
800
810
|
...(override.thinking !== undefined ? { thinking: override.thinking } : {}),
|
|
801
811
|
...(override.systemPromptMode !== undefined ? { systemPromptMode: override.systemPromptMode } : {}),
|
|
@@ -824,8 +834,17 @@ function isProjectRootCandidate(dir: string): boolean {
|
|
|
824
834
|
|
|
825
835
|
function findProjectRootCandidates(cwd: string): string[] {
|
|
826
836
|
const roots: string[] = [];
|
|
837
|
+
const windowsProfile = process.env.HOMEDRIVE && process.env.HOMEPATH
|
|
838
|
+
? `${process.env.HOMEDRIVE}${process.env.HOMEPATH}`
|
|
839
|
+
: undefined;
|
|
840
|
+
const homeDirs = new Set([os.homedir(), process.env.HOME, process.env.USERPROFILE, windowsProfile]
|
|
841
|
+
.filter((value): value is string => Boolean(value?.trim()))
|
|
842
|
+
.filter(isDirectory)
|
|
843
|
+
.map((value) => fs.realpathSync.native(value)));
|
|
827
844
|
let currentDir = cwd;
|
|
828
845
|
while (true) {
|
|
846
|
+
// ~/.pi and ~/.agents are user configuration, never an implicit project.
|
|
847
|
+
if (isDirectory(currentDir) && homeDirs.has(fs.realpathSync.native(currentDir))) return roots;
|
|
829
848
|
if (isProjectRootCandidate(currentDir)) roots.push(currentDir);
|
|
830
849
|
|
|
831
850
|
const parentDir = path.dirname(currentDir);
|
|
@@ -957,6 +976,15 @@ function parseToolsOverride(
|
|
|
957
976
|
throw new Error(`Builtin override '${meta.name}' in '${meta.filePath}' has invalid 'tools'; expected an array of strings, "inherit", or false.`);
|
|
958
977
|
}
|
|
959
978
|
|
|
979
|
+
function validateOptionalMachine(value: unknown, label: string): string | undefined {
|
|
980
|
+
if (value === undefined || value === false) return undefined;
|
|
981
|
+
if (typeof value !== "string" || !value.trim()) throw new Error(label + " must be a non-empty string or false.");
|
|
982
|
+
const machine = value.trim();
|
|
983
|
+
if (machine.length > 128) throw new Error(label + " must be 128 characters or fewer.");
|
|
984
|
+
if (/[\u0000-\u001f\u007f]/u.test(machine)) throw new Error(label + " contains control characters.");
|
|
985
|
+
return machine;
|
|
986
|
+
}
|
|
987
|
+
|
|
960
988
|
function parseBuiltinOverrideEntry(
|
|
961
989
|
name: string,
|
|
962
990
|
value: unknown,
|
|
@@ -967,6 +995,9 @@ function parseBuiltinOverrideEntry(
|
|
|
967
995
|
}
|
|
968
996
|
|
|
969
997
|
const input = value as Record<string, unknown>;
|
|
998
|
+
if (Object.hasOwn(input, "fallbackModels")) {
|
|
999
|
+
throw new Error(`Builtin override '${name}' in '${filePath}' uses removed field 'fallbackModels'; configure one model instead.`);
|
|
1000
|
+
}
|
|
970
1001
|
const override: BuiltinAgentOverrideConfig = {};
|
|
971
1002
|
|
|
972
1003
|
if ("description" in input) {
|
|
@@ -1084,11 +1115,15 @@ function parseBuiltinOverrideEntry(
|
|
|
1084
1115
|
else throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'systemPrompt'; expected a string.`);
|
|
1085
1116
|
}
|
|
1086
1117
|
|
|
1118
|
+
if (input.machine === false) override.machine = false;
|
|
1119
|
+
else {
|
|
1120
|
+
const machine = validateOptionalMachine(input.machine, `Builtin override '${name}' in '${filePath}' field 'machine'`);
|
|
1121
|
+
if (machine !== undefined) override.machine = machine;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1087
1124
|
const defaultReads = parseOverrideStringArrayOrFalse(input.defaultReads, { filePath, name, field: "defaultReads" });
|
|
1088
1125
|
if (defaultReads !== undefined) override.defaultReads = defaultReads;
|
|
1089
1126
|
|
|
1090
|
-
const fallbackModels = parseOverrideStringArrayOrFalse(input.fallbackModels, { filePath, name, field: "fallbackModels" });
|
|
1091
|
-
if (fallbackModels !== undefined) override.fallbackModels = fallbackModels;
|
|
1092
1127
|
|
|
1093
1128
|
if ("defaultProvider" in input) {
|
|
1094
1129
|
if (input.defaultProvider === false) override.defaultProvider = false;
|
|
@@ -1191,6 +1226,14 @@ function readSubagentSettings(filePath: string | null): SubagentSettings {
|
|
|
1191
1226
|
}
|
|
1192
1227
|
agentScanDirs = subagentsObject.agentScanDirs.map((item) => item.trim());
|
|
1193
1228
|
}
|
|
1229
|
+
let agentExcludeDirs: string[] | undefined;
|
|
1230
|
+
if ("agentExcludeDirs" in subagentsObject) {
|
|
1231
|
+
if (!Array.isArray(subagentsObject.agentExcludeDirs)
|
|
1232
|
+
|| subagentsObject.agentExcludeDirs.some((item) => typeof item !== "string" || !item.trim())) {
|
|
1233
|
+
throw new Error(`Subagent settings in '${filePath}' have invalid 'agentExcludeDirs'; expected an array of non-empty strings.`);
|
|
1234
|
+
}
|
|
1235
|
+
agentExcludeDirs = subagentsObject.agentExcludeDirs.map((item) => item.trim());
|
|
1236
|
+
}
|
|
1194
1237
|
const modelScope = parseModelScopeConfig(subagentsObject.modelScope, { filePath });
|
|
1195
1238
|
|
|
1196
1239
|
const parsed: Record<string, BuiltinAgentOverrideConfig> = {};
|
|
@@ -1206,6 +1249,7 @@ function readSubagentSettings(filePath: string | null): SubagentSettings {
|
|
|
1206
1249
|
...(maxThinking !== undefined ? { maxThinking } : {}),
|
|
1207
1250
|
...(defaultExtensions !== undefined ? { defaultExtensions } : {}),
|
|
1208
1251
|
...(agentScanDirs !== undefined ? { agentScanDirs } : {}),
|
|
1252
|
+
...(agentExcludeDirs !== undefined ? { agentExcludeDirs } : {}),
|
|
1209
1253
|
...(disableBuiltins !== undefined ? { disableBuiltins } : {}),
|
|
1210
1254
|
...(disableThinking !== undefined ? { disableThinking } : {}),
|
|
1211
1255
|
...(modelScope !== undefined ? { modelScope } : {}),
|
|
@@ -1384,6 +1428,7 @@ function applyBuiltinOverride(
|
|
|
1384
1428
|
};
|
|
1385
1429
|
|
|
1386
1430
|
if (override.description !== undefined) next.description = override.description;
|
|
1431
|
+
if (override.machine !== undefined) { if (override.machine === false) delete next.machine; else next.machine = override.machine; }
|
|
1387
1432
|
if (override.output !== undefined) { if (override.output === false) delete next.output; else next.output = override.output; }
|
|
1388
1433
|
if (override.outputMode !== undefined) next.outputMode = override.outputMode;
|
|
1389
1434
|
if (override.defaultReads !== undefined) { if (override.defaultReads === false) delete next.defaultReads; else next.defaultReads = [...override.defaultReads]; }
|
|
@@ -1395,7 +1440,6 @@ function applyBuiltinOverride(
|
|
|
1395
1440
|
if (override.defaultProvider === false) delete next.modelProvider;
|
|
1396
1441
|
else next.modelProvider = override.defaultProvider;
|
|
1397
1442
|
}
|
|
1398
|
-
if (override.fallbackModels !== undefined) { if (override.fallbackModels === false) delete next.fallbackModels; else next.fallbackModels = [...override.fallbackModels]; }
|
|
1399
1443
|
if (override.fast !== undefined) next.fast = override.fast;
|
|
1400
1444
|
if (override.thinking !== undefined) { if (override.thinking === false) delete next.thinking; else next.thinking = override.thinking; }
|
|
1401
1445
|
if (override.systemPromptMode !== undefined) next.systemPromptMode = override.systemPromptMode;
|
|
@@ -1516,9 +1560,10 @@ function applyCustomAgentOverrides(
|
|
|
1516
1560
|
|
|
1517
1561
|
export function buildBuiltinOverrideConfig(
|
|
1518
1562
|
base: BuiltinAgentOverrideBase,
|
|
1519
|
-
draft: Pick<AgentConfig, "model" | "modelProvider" | "
|
|
1563
|
+
draft: Pick<AgentConfig, "model" | "modelProvider" | "fast" | "thinking" | "systemPromptMode" | "inheritProjectContext" | "inheritGlobalContext" | "inheritSkills" | "defaultContext" | "acceptanceRole" | "disabled" | "systemPrompt" | "skills" | "tools" | "allowNestedSubagents" | "mcpDirectTools" | "extensions" | "subagentOnlyExtensions" | "mutationTools" | "completionGuard" | "toolBudget"> & Partial<Pick<AgentConfig, "description" | "machine" | "output" | "outputMode" | "defaultReads" | "excludeTools">>,
|
|
1520
1564
|
): BuiltinAgentOverrideConfig | undefined {
|
|
1521
1565
|
const override: BuiltinAgentOverrideConfig = {};
|
|
1566
|
+
if (draft.machine !== base.machine) override.machine = draft.machine ?? false;
|
|
1522
1567
|
|
|
1523
1568
|
if (draft.description !== undefined) {
|
|
1524
1569
|
const description = draft.description.trim();
|
|
@@ -1529,7 +1574,6 @@ export function buildBuiltinOverrideConfig(
|
|
|
1529
1574
|
if (!arraysEqual(draft.defaultReads, base.defaultReads)) override.defaultReads = draft.defaultReads ? [...draft.defaultReads] : false;
|
|
1530
1575
|
if (draft.model !== base.model) override.model = draft.model ?? false;
|
|
1531
1576
|
if (draft.modelProvider !== base.modelProvider) override.defaultProvider = draft.modelProvider ?? false;
|
|
1532
|
-
if (!arraysEqual(draft.fallbackModels, base.fallbackModels)) override.fallbackModels = draft.fallbackModels ? [...draft.fallbackModels] : false;
|
|
1533
1577
|
if (draft.fast !== base.fast) override.fast = draft.fast === true;
|
|
1534
1578
|
if (draft.thinking !== base.thinking) override.thinking = draft.thinking ?? false;
|
|
1535
1579
|
if (draft.systemPromptMode !== base.systemPromptMode) override.systemPromptMode = draft.systemPromptMode;
|
|
@@ -1584,21 +1628,25 @@ export function saveBuiltinAgentOverride(
|
|
|
1584
1628
|
return filePath;
|
|
1585
1629
|
}
|
|
1586
1630
|
|
|
1587
|
-
export function removeBuiltinAgentOverride(cwd: string, name: string, scope: "user" | "project"): { path: string; removed: boolean } {
|
|
1631
|
+
export function removeBuiltinAgentOverride(cwd: string, name: string, scope: "user" | "project", options?: { preserveMachine?: boolean }): { path: string; removed: boolean; machinePreserved: boolean } {
|
|
1588
1632
|
const filePath = scope === "project" ? getProjectAgentSettingsPath(cwd) : getUserAgentSettingsPath();
|
|
1589
1633
|
if (!filePath) throw new Error("Project override is not available here. No project config root was found.");
|
|
1590
|
-
if (!fs.existsSync(filePath)) return { path: filePath, removed: false };
|
|
1634
|
+
if (!fs.existsSync(filePath)) return { path: filePath, removed: false, machinePreserved: false };
|
|
1591
1635
|
|
|
1592
1636
|
const settings = readSettingsFileStrict(filePath);
|
|
1593
1637
|
const subagents = settings.subagents;
|
|
1594
|
-
if (!subagents || typeof subagents !== "object" || Array.isArray(subagents)) return { path: filePath, removed: false };
|
|
1638
|
+
if (!subagents || typeof subagents !== "object" || Array.isArray(subagents)) return { path: filePath, removed: false, machinePreserved: false };
|
|
1595
1639
|
const nextSubagents = { ...(subagents as Record<string, unknown>) };
|
|
1596
1640
|
const agentOverrides = nextSubagents.agentOverrides;
|
|
1597
|
-
if (!agentOverrides || typeof agentOverrides !== "object" || Array.isArray(agentOverrides)) return { path: filePath, removed: false };
|
|
1641
|
+
if (!agentOverrides || typeof agentOverrides !== "object" || Array.isArray(agentOverrides)) return { path: filePath, removed: false, machinePreserved: false };
|
|
1598
1642
|
|
|
1599
1643
|
const nextOverrides = { ...(agentOverrides as Record<string, unknown>) };
|
|
1600
|
-
|
|
1601
|
-
|
|
1644
|
+
const current = nextOverrides[name];
|
|
1645
|
+
if (!Object.prototype.hasOwnProperty.call(nextOverrides, name)) return { path: filePath, removed: false, machinePreserved: false };
|
|
1646
|
+
const configuredMachine = current && typeof current === "object" && !Array.isArray(current) ? (current as Record<string, unknown>).machine : undefined;
|
|
1647
|
+
const machine = configuredMachine === false || (typeof configuredMachine === "string" && configuredMachine.trim()) ? configuredMachine : undefined;
|
|
1648
|
+
if (options?.preserveMachine && machine !== undefined) nextOverrides[name] = { machine };
|
|
1649
|
+
else delete nextOverrides[name];
|
|
1602
1650
|
if (Object.keys(nextOverrides).length > 0) nextSubagents.agentOverrides = nextOverrides;
|
|
1603
1651
|
else delete nextSubagents.agentOverrides;
|
|
1604
1652
|
|
|
@@ -1606,7 +1654,7 @@ export function removeBuiltinAgentOverride(cwd: string, name: string, scope: "us
|
|
|
1606
1654
|
else delete settings.subagents;
|
|
1607
1655
|
|
|
1608
1656
|
writeSettingsFile(filePath, settings);
|
|
1609
|
-
return { path: filePath, removed: true };
|
|
1657
|
+
return { path: filePath, removed: true, machinePreserved: options?.preserveMachine === true && machine !== undefined };
|
|
1610
1658
|
}
|
|
1611
1659
|
|
|
1612
1660
|
export function mergeBuiltinAgentOverride(
|
|
@@ -1778,8 +1826,9 @@ const DEFAULT_AGENT_DEFINITION_INSPECTION_FS: AgentDefinitionInspectionFs = {
|
|
|
1778
1826
|
* state. A nested unreadable directory makes the whole inspection unavailable,
|
|
1779
1827
|
* preventing a partial traversal from being reported as empty or complete.
|
|
1780
1828
|
*/
|
|
1781
|
-
export function inspectAgentDefinitionDirectory(dir: string, operations: AgentDefinitionInspectionFs = DEFAULT_AGENT_DEFINITION_INSPECTION_FS): AgentDefinitionInspection {
|
|
1829
|
+
export function inspectAgentDefinitionDirectory(dir: string, operations: AgentDefinitionInspectionFs = DEFAULT_AGENT_DEFINITION_INSPECTION_FS, isExcluded: (filePath: string) => boolean = () => false): AgentDefinitionInspection {
|
|
1782
1830
|
const root = path.resolve(dir);
|
|
1831
|
+
if (isExcluded(root)) return rememberAgentDefinitionInspection({ files: [], state: "empty" }, []);
|
|
1783
1832
|
try {
|
|
1784
1833
|
if (!operations.existsSync(root)) return rememberAgentDefinitionInspection({ files: [], state: "absent" }, [root]);
|
|
1785
1834
|
if (!operations.statSync(root).isDirectory()) return rememberAgentDefinitionInspection({ files: [], state: "not-directory" }, [root]);
|
|
@@ -1811,6 +1860,7 @@ export function inspectAgentDefinitionDirectory(dir: string, operations: AgentDe
|
|
|
1811
1860
|
}
|
|
1812
1861
|
for (const entry of entries) {
|
|
1813
1862
|
const filePath = path.join(current, entry.name);
|
|
1863
|
+
if (isExcluded(filePath)) continue;
|
|
1814
1864
|
let isDirectory = entry.isDirectory();
|
|
1815
1865
|
if (entry.isSymbolicLink()) {
|
|
1816
1866
|
try {
|
|
@@ -1910,7 +1960,7 @@ function parseAgentRunnerFrontmatter(raw: string | undefined, agentName: string)
|
|
|
1910
1960
|
|
|
1911
1961
|
function validateExternalRunnerProfile(frontmatter: Record<string, string>, agentName: string, runner: AgentRunnerConfig | undefined): void {
|
|
1912
1962
|
if (runner?.type !== "external-cli" && runner?.type !== "external-job") return;
|
|
1913
|
-
const unsupported = ["tools", "excludeTools", "allowNestedSubagents", "model", "
|
|
1963
|
+
const unsupported = ["tools", "excludeTools", "allowNestedSubagents", "model", "thinking", "extensions", "subagentOnlyExtensions", "mutationTools", "maxSubagentDepth", "completionGuard", "skills", "skill", "skillPath", "toolBudget", "permission", "permissions"]
|
|
1914
1964
|
.filter((field) => frontmatter[field] !== undefined);
|
|
1915
1965
|
if (unsupported.length > 0) {
|
|
1916
1966
|
throw new Error(`Agent '${agentName}' uses runner.type='${runner.type}' and declares unsupported Pi-only fields: ${unsupported.join(", ")}.`);
|
|
@@ -2006,7 +2056,7 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
|
|
|
2006
2056
|
const skillStr = frontmatter.skill || frontmatter.skills;
|
|
2007
2057
|
const skills = parseFrontmatterList(skillStr);
|
|
2008
2058
|
const skillPath = parseFrontmatterList(frontmatter.skillPath);
|
|
2009
|
-
|
|
2059
|
+
if (frontmatter.fallbackModels !== undefined) throw new Error(`Agent '${filePath}' uses removed frontmatter field 'fallbackModels'. Configure one model instead.`);
|
|
2010
2060
|
const systemPromptMode = frontmatter.systemPromptMode === "replace"
|
|
2011
2061
|
? "replace"
|
|
2012
2062
|
: frontmatter.systemPromptMode === "append"
|
|
@@ -2099,6 +2149,12 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
|
|
|
2099
2149
|
}
|
|
2100
2150
|
toolBudget = parsed as ToolBudgetConfig;
|
|
2101
2151
|
}
|
|
2152
|
+
let outputSchema: JsonSchemaObject | undefined;
|
|
2153
|
+
if (frontmatter.outputSchema !== undefined && frontmatter.outputSchema.trim()) {
|
|
2154
|
+
const parsed: unknown = JSON.parse(frontmatter.outputSchema);
|
|
2155
|
+
assertJsonSchemaObject(parsed, `Agent '${localName}' outputSchema`);
|
|
2156
|
+
outputSchema = parsed;
|
|
2157
|
+
}
|
|
2102
2158
|
const completionGuard = frontmatter.completionGuard === "false"
|
|
2103
2159
|
? false
|
|
2104
2160
|
: frontmatter.completionGuard === "true"
|
|
@@ -2109,6 +2165,7 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
|
|
|
2109
2165
|
? parsedMaxSubagentDepth
|
|
2110
2166
|
: undefined;
|
|
2111
2167
|
const memory = parseMemoryFrontmatter(frontmatter.memory);
|
|
2168
|
+
const machine = validateOptionalMachine(frontmatter.machine, `Agent '${runtimeName}' frontmatter 'machine'`);
|
|
2112
2169
|
const agent: AgentConfig = {
|
|
2113
2170
|
name: runtimeName,
|
|
2114
2171
|
...(runner !== undefined ? { runner } : {}),
|
|
@@ -2125,7 +2182,6 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
|
|
|
2125
2182
|
...(allowNestedSubagents !== undefined ? { allowNestedSubagents } : {}),
|
|
2126
2183
|
...(mcpDirectTools.length > 0 ? { mcpDirectTools } : {}),
|
|
2127
2184
|
...(frontmatter.model !== undefined ? { model: frontmatter.model } : {}),
|
|
2128
|
-
...(fallbackModels?.length ? { fallbackModels } : {}),
|
|
2129
2185
|
...(fast !== undefined ? { fast } : {}),
|
|
2130
2186
|
...(frontmatter.thinking !== undefined ? { thinking: frontmatter.thinking === "false" ? false : frontmatter.thinking } : {}),
|
|
2131
2187
|
systemPromptMode,
|
|
@@ -2147,8 +2203,10 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
|
|
|
2147
2203
|
...(extensions !== undefined ? { extensions } : {}),
|
|
2148
2204
|
...(subagentOnlyExtensions !== undefined ? { subagentOnlyExtensions } : {}),
|
|
2149
2205
|
...(mutationTools?.length ? { mutationTools } : {}),
|
|
2206
|
+
...(machine !== undefined ? { machine } : {}),
|
|
2150
2207
|
...(frontmatter.output !== undefined ? { output: frontmatter.output } : {}),
|
|
2151
2208
|
...(outputMode !== undefined ? { outputMode } : {}),
|
|
2209
|
+
...(outputSchema !== undefined ? { outputSchema } : {}),
|
|
2152
2210
|
...(defaultReads?.length ? { defaultReads } : {}),
|
|
2153
2211
|
defaultProgress: frontmatter.defaultProgress === "true",
|
|
2154
2212
|
interactive: frontmatter.interactive === "true",
|
|
@@ -2268,31 +2326,60 @@ interface AgentScanDirs {
|
|
|
2268
2326
|
watchPaths: string[];
|
|
2269
2327
|
}
|
|
2270
2328
|
|
|
2271
|
-
function readConfiguredAgentScanDirs(filePath: string | null): string[] {
|
|
2329
|
+
function readConfiguredAgentScanDirs(filePath: string | null, key: "agentScanDirs" | "agentExcludeDirs" = "agentScanDirs"): string[] {
|
|
2272
2330
|
if (!filePath) return [];
|
|
2273
2331
|
try {
|
|
2274
2332
|
const settings = readSettingsFileStrict(filePath);
|
|
2275
2333
|
const subagents = settings.subagents;
|
|
2276
2334
|
if (!subagents || typeof subagents !== "object" || Array.isArray(subagents)) return [];
|
|
2277
|
-
const dirs = (subagents as
|
|
2335
|
+
const dirs = (subagents as Record<string, unknown>)[key];
|
|
2278
2336
|
return Array.isArray(dirs) ? dirs.filter((dir): dir is string => typeof dir === "string" && dir.trim().length > 0) : [];
|
|
2279
2337
|
} catch {
|
|
2280
2338
|
return [];
|
|
2281
2339
|
}
|
|
2282
2340
|
}
|
|
2283
2341
|
|
|
2284
|
-
|
|
2342
|
+
// Resolve existing ancestors to canonicalize missing descendants of symlinks.
|
|
2343
|
+
function canonicalAgentPath(filePath: string): string {
|
|
2344
|
+
const resolved = path.resolve(filePath);
|
|
2345
|
+
try {
|
|
2346
|
+
return fs.realpathSync(resolved);
|
|
2347
|
+
} catch {
|
|
2348
|
+
const parent = path.dirname(resolved);
|
|
2349
|
+
return parent === resolved ? resolved : path.join(canonicalAgentPath(parent), path.basename(resolved));
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
|
|
2353
|
+
function agentExclusionRoots(userSettingsPath: string, projectSettingsPath: string | null): Array<{ resolved: string; real: string }> {
|
|
2354
|
+
return [userSettingsPath, projectSettingsPath].flatMap((settingsPath) => settingsPath
|
|
2355
|
+
? readConfiguredAgentScanDirs(settingsPath, "agentExcludeDirs").map((entry) => {
|
|
2356
|
+
const resolved = path.resolve(path.dirname(settingsPath), expandHomePath(entry.trim()).replace(/[\\/]+/g, path.sep));
|
|
2357
|
+
return { resolved, real: canonicalAgentPath(resolved) };
|
|
2358
|
+
}) : []);
|
|
2359
|
+
}
|
|
2360
|
+
|
|
2361
|
+
function agentExclusions(roots: Array<{ resolved: string; real: string }>): (filePath: string) => boolean {
|
|
2362
|
+
return (filePath) => {
|
|
2363
|
+
if (!roots.length) return false;
|
|
2364
|
+
if (roots.some((root) => isPathWithin(root.resolved, filePath))) return true;
|
|
2365
|
+
const real = canonicalAgentPath(filePath);
|
|
2366
|
+
return roots.some((root) => isPathWithin(root.real, real));
|
|
2367
|
+
};
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
function expandAgentScanDirPattern(pattern: string, isExcluded: (filePath: string) => boolean): AgentScanDirs {
|
|
2285
2371
|
const expanded = expandHomePath(pattern.trim()).replace(/[\\/]+/g, path.sep);
|
|
2286
2372
|
if (!expanded) return { dirs: [], watchPaths: [] };
|
|
2287
2373
|
const wildcardMatches = [...expanded.matchAll(/\*/g)];
|
|
2288
2374
|
if (wildcardMatches.length === 0) {
|
|
2289
2375
|
const dir = path.resolve(expanded);
|
|
2290
|
-
return { dirs: fs.existsSync(dir) ? [dir] : [], watchPaths: [dir] };
|
|
2376
|
+
return { dirs: !isExcluded(dir) && fs.existsSync(dir) ? [dir] : [], watchPaths: [dir] };
|
|
2291
2377
|
}
|
|
2292
2378
|
const parts = expanded.split(path.sep);
|
|
2293
2379
|
const wildcardIndex = parts.findIndex((part) => part.includes("*"));
|
|
2294
2380
|
if (wildcardMatches.length !== 1 || wildcardIndex === -1 || parts[wildcardIndex] !== "*") return { dirs: [], watchPaths: [] };
|
|
2295
2381
|
const base = path.resolve(parts.slice(0, wildcardIndex).join(path.sep) || path.sep);
|
|
2382
|
+
if (isExcluded(base)) return { dirs: [], watchPaths: [base] };
|
|
2296
2383
|
const rest = parts.slice(wildcardIndex + 1);
|
|
2297
2384
|
let entries: fs.Dirent[];
|
|
2298
2385
|
try {
|
|
@@ -2302,16 +2389,16 @@ function expandAgentScanDirPattern(pattern: string): AgentScanDirs {
|
|
|
2302
2389
|
}
|
|
2303
2390
|
const candidateDirs = entries.filter((entry) => entry.isDirectory()).map((entry) => path.join(base, entry.name, ...rest));
|
|
2304
2391
|
return {
|
|
2305
|
-
dirs: candidateDirs.filter((dir) => fs.existsSync(dir)),
|
|
2392
|
+
dirs: candidateDirs.filter((dir) => !isExcluded(dir) && fs.existsSync(dir)),
|
|
2306
2393
|
watchPaths: [base, ...candidateDirs],
|
|
2307
2394
|
};
|
|
2308
2395
|
}
|
|
2309
2396
|
|
|
2310
|
-
function settingsAgentScanDirs(entries: string[]): AgentScanDirs {
|
|
2397
|
+
function settingsAgentScanDirs(entries: string[], isExcluded: (filePath: string) => boolean): AgentScanDirs {
|
|
2311
2398
|
const dirs = new Set<string>();
|
|
2312
2399
|
const watchPaths = new Set<string>();
|
|
2313
2400
|
for (const entry of entries) {
|
|
2314
|
-
const expanded = expandAgentScanDirPattern(entry);
|
|
2401
|
+
const expanded = expandAgentScanDirPattern(entry, isExcluded);
|
|
2315
2402
|
for (const dir of expanded.dirs) dirs.add(dir);
|
|
2316
2403
|
for (const watchPath of expanded.watchPaths) watchPaths.add(watchPath);
|
|
2317
2404
|
}
|
|
@@ -2378,6 +2465,10 @@ interface AgentDiscoverySources {
|
|
|
2378
2465
|
userChains?: ReturnType<typeof loadChainsFromDir>;
|
|
2379
2466
|
projectChainLoaded?: Array<{ dir: string; loaded: ReturnType<typeof loadChainsFromDir> }>;
|
|
2380
2467
|
packageChainLoaded?: Array<{ entry: PackageChainPath; loaded: ReturnType<typeof loadChainsFromDir> }>;
|
|
2468
|
+
isExcluded: (filePath: string) => boolean;
|
|
2469
|
+
exclusionRoots: Array<{ resolved: string; real: string }>;
|
|
2470
|
+
identityWatchPaths: string[];
|
|
2471
|
+
chainWatchPaths?: Set<string>;
|
|
2381
2472
|
watchPaths: string[];
|
|
2382
2473
|
}
|
|
2383
2474
|
|
|
@@ -2390,7 +2481,7 @@ const agentDiscoveryCache = new Map<string, AgentDiscoveryCacheEntry>();
|
|
|
2390
2481
|
|
|
2391
2482
|
function isPathWithin(root: string, candidate: string): boolean {
|
|
2392
2483
|
const relative = path.relative(path.resolve(root), path.resolve(candidate));
|
|
2393
|
-
return relative === "" || (!relative.startsWith(
|
|
2484
|
+
return relative === "" || (relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative));
|
|
2394
2485
|
}
|
|
2395
2486
|
|
|
2396
2487
|
function addDirectoryWatchPaths(paths: Set<string>, root: string, files: readonly string[], directories: readonly string[] = []): void {
|
|
@@ -2413,13 +2504,14 @@ function addDirectoryWatchPaths(paths: Set<string>, root: string, files: readonl
|
|
|
2413
2504
|
}
|
|
2414
2505
|
}
|
|
2415
2506
|
|
|
2416
|
-
function watchPathSignature(filePath: string): string {
|
|
2507
|
+
function watchPathSignature(filePath: string, isExcluded?: (filePath: string) => boolean): string {
|
|
2417
2508
|
try {
|
|
2418
2509
|
const stat = fs.statSync(filePath);
|
|
2419
2510
|
if (stat.isDirectory()) {
|
|
2420
2511
|
const entries = fs.readdirSync(filePath, { withFileTypes: true })
|
|
2512
|
+
.filter((entry) => !isExcluded || !isExcluded(path.join(filePath, entry.name)))
|
|
2421
2513
|
.sort((left, right) => left.name.localeCompare(right.name))
|
|
2422
|
-
.map((entry) => `${entry.name}:${entry.isDirectory() ? "d" : entry.isSymbolicLink() ? "l" : "f"}`)
|
|
2514
|
+
.map((entry) => `${entry.name}:${entry.isDirectory() ? "d" : entry.isSymbolicLink() ? (isExcluded ? `l:${canonicalAgentPath(path.join(filePath, entry.name))}` : "l") : "f"}`)
|
|
2423
2515
|
.join("|");
|
|
2424
2516
|
return `directory:${entries}`;
|
|
2425
2517
|
}
|
|
@@ -2429,10 +2521,17 @@ function watchPathSignature(filePath: string): string {
|
|
|
2429
2521
|
}
|
|
2430
2522
|
}
|
|
2431
2523
|
|
|
2432
|
-
function discoveryFingerprint(
|
|
2433
|
-
|
|
2524
|
+
function discoveryFingerprint(sources: AgentDiscoverySources): string {
|
|
2525
|
+
const exclusionIdentity = JSON.stringify(sources.exclusionRoots.map((root) => ({ resolved: root.resolved, real: canonicalAgentPath(root.resolved) })));
|
|
2526
|
+
// A retargeted exclusion needs a fresh scan before fingerprinting old roots.
|
|
2527
|
+
if (exclusionIdentity !== JSON.stringify(sources.exclusionRoots)) return exclusionIdentity;
|
|
2528
|
+
const rootIdentities = JSON.stringify(sources.identityWatchPaths.map((root) => [root, canonicalAgentPath(root)]));
|
|
2529
|
+
// Package metadata can redirect agents outside an excluded tree without chains.
|
|
2530
|
+
const unfilteredPaths = new Set([...sources.packageSubagentPaths.watchPaths, ...(sources.chainWatchPaths ?? [])]);
|
|
2531
|
+
return exclusionIdentity + "\n" + rootIdentities + "\n" + [...new Set([...sources.watchPaths, ...unfilteredPaths])]
|
|
2532
|
+
.filter((filePath) => unfilteredPaths.has(filePath) || filePath === sources.userSettingsPath || filePath === sources.projectSettingsPath || !sources.isExcluded(filePath))
|
|
2434
2533
|
.sort((left, right) => left.localeCompare(right))
|
|
2435
|
-
.map((filePath) => `${filePath}:${watchPathSignature(filePath)}`)
|
|
2534
|
+
.map((filePath) => `${filePath}:${watchPathSignature(filePath, sources.exclusionRoots.length && !unfilteredPaths.has(filePath) ? sources.isExcluded : undefined)}`)
|
|
2436
2535
|
.join("\n");
|
|
2437
2536
|
}
|
|
2438
2537
|
|
|
@@ -2478,21 +2577,23 @@ function buildAgentDiscoverySources(cwd: string, preferredModelProvider?: string
|
|
|
2478
2577
|
const userSettingsPath = getUserAgentSettingsPath();
|
|
2479
2578
|
const projectSettingsPath = getProjectAgentSettingsPath(effectiveCwd);
|
|
2480
2579
|
const packageSubagentPaths = collectPackageSubagentPaths(effectiveCwd);
|
|
2481
|
-
const
|
|
2482
|
-
const
|
|
2580
|
+
const exclusionRoots = agentExclusionRoots(userSettingsPath, projectSettingsPath);
|
|
2581
|
+
const isExcluded = agentExclusions(exclusionRoots);
|
|
2582
|
+
const userScanDirs = settingsAgentScanDirs(readConfiguredAgentScanDirs(userSettingsPath), isExcluded);
|
|
2583
|
+
const projectScanDirs = settingsAgentScanDirs(readConfiguredAgentScanDirs(projectSettingsPath), isExcluded);
|
|
2483
2584
|
|
|
2484
2585
|
const builtinLoaded = loadAgentsFromDefinitionFiles(BUILTIN_AGENT_DEFINITION_FILES, "builtin");
|
|
2485
|
-
const userLoaded = [...extraUserAgentDirs(), ...userScanDirs.dirs, userDirOld, userDirNew].map((dir, discoveryPriority): LoadedAgentDirectory => {
|
|
2486
|
-
const inspection = inspectAgentDefinitionDirectory(dir);
|
|
2586
|
+
const userLoaded = [...extraUserAgentDirs(), ...userScanDirs.dirs, userDirOld, userDirNew].filter((dir) => !isExcluded(dir)).map((dir, discoveryPriority): LoadedAgentDirectory => {
|
|
2587
|
+
const inspection = inspectAgentDefinitionDirectory(dir, undefined, isExcluded);
|
|
2487
2588
|
return { dir, inspection, loaded: loadAgentsFromDir(dir, "user", discoveryPriority, undefined, inspection) };
|
|
2488
2589
|
});
|
|
2489
|
-
const projectInspections = new Map(projectCandidateDirs.map((dir) => [dir, inspectAgentDefinitionDirectory(dir)]));
|
|
2490
|
-
const projectLoaded = [...projectScanDirs.dirs, ...projectAgentDirs].map((dir, discoveryPriority): LoadedAgentDirectory => {
|
|
2491
|
-
const inspection = projectInspections.get(dir) ?? inspectAgentDefinitionDirectory(dir);
|
|
2590
|
+
const projectInspections = new Map(projectCandidateDirs.filter((dir) => !isExcluded(dir)).map((dir) => [dir, inspectAgentDefinitionDirectory(dir, undefined, isExcluded)]));
|
|
2591
|
+
const projectLoaded = [...projectScanDirs.dirs, ...projectAgentDirs].filter((dir) => !isExcluded(dir)).map((dir, discoveryPriority): LoadedAgentDirectory => {
|
|
2592
|
+
const inspection = projectInspections.get(dir) ?? inspectAgentDefinitionDirectory(dir, undefined, isExcluded);
|
|
2492
2593
|
return { dir, inspection, loaded: loadAgentsFromDir(dir, "project", dir === projectAgentsDir ? 1 : discoveryPriority, undefined, inspection) };
|
|
2493
2594
|
});
|
|
2494
|
-
const packageLoaded = packageSubagentPaths.agents.map((entry, index): LoadedAgentDirectory => {
|
|
2495
|
-
const inspection = inspectAgentDefinitionDirectory(entry.dir);
|
|
2595
|
+
const packageLoaded = packageSubagentPaths.agents.filter((entry) => !isExcluded(entry.dir)).map((entry, index): LoadedAgentDirectory => {
|
|
2596
|
+
const inspection = inspectAgentDefinitionDirectory(entry.dir, undefined, isExcluded);
|
|
2496
2597
|
return { dir: entry.dir, inspection, packageEntry: entry, loaded: loadAgentsFromDir(entry.dir, "package", packageSubagentPaths.agents.length - index, entry, inspection) };
|
|
2497
2598
|
});
|
|
2498
2599
|
|
|
@@ -2522,7 +2623,7 @@ function buildAgentDiscoverySources(cwd: string, preferredModelProvider?: string
|
|
|
2522
2623
|
userDirNew,
|
|
2523
2624
|
userChainDir,
|
|
2524
2625
|
projectAgentDirs,
|
|
2525
|
-
projectCandidateDirs,
|
|
2626
|
+
projectCandidateDirs: projectCandidateDirs.filter((dir) => !isExcluded(dir)),
|
|
2526
2627
|
projectAgentsDir,
|
|
2527
2628
|
projectChainDirs,
|
|
2528
2629
|
projectChainDir,
|
|
@@ -2534,7 +2635,17 @@ function buildAgentDiscoverySources(cwd: string, preferredModelProvider?: string
|
|
|
2534
2635
|
projectLoaded,
|
|
2535
2636
|
projectInspections,
|
|
2536
2637
|
packageLoaded,
|
|
2537
|
-
|
|
2638
|
+
isExcluded,
|
|
2639
|
+
exclusionRoots,
|
|
2640
|
+
// Aliases outside excluded lexical subtrees can be retargeted into allowed
|
|
2641
|
+
// storage. Watch only their identity, never the excluded target's contents.
|
|
2642
|
+
identityWatchPaths: [...new Set([
|
|
2643
|
+
...userScanDirs.watchPaths, ...projectScanDirs.watchPaths,
|
|
2644
|
+
...extraUserAgentDirs(), userDirOld, userDirNew, ...projectCandidateDirs,
|
|
2645
|
+
...packageSubagentPaths.agents.map((entry) => entry.dir),
|
|
2646
|
+
])]
|
|
2647
|
+
.filter((dir) => !exclusionRoots.some((root) => isPathWithin(root.resolved, dir)) && isExcluded(dir)),
|
|
2648
|
+
watchPaths: [...watchPaths].filter((filePath) => !isExcluded(filePath) || filePath === userSettingsPath || filePath === projectSettingsPath),
|
|
2538
2649
|
};
|
|
2539
2650
|
}
|
|
2540
2651
|
|
|
@@ -2543,29 +2654,30 @@ function ensureDiscoveryChains(sources: AgentDiscoverySources): void {
|
|
|
2543
2654
|
sources.packageChainLoaded = sources.packageSubagentPaths.chains.map((entry) => ({ entry, loaded: loadChainsFromDir(entry.dir, "package") }));
|
|
2544
2655
|
sources.userChains = loadChainsFromDir(sources.userChainDir, "user");
|
|
2545
2656
|
sources.projectChainLoaded = sources.projectChainDirs.map((dir) => ({ dir, loaded: loadChainsFromDir(dir, "project") }));
|
|
2546
|
-
const watchPaths = new Set(sources.watchPaths);
|
|
2657
|
+
const watchPaths = new Set(sources.packageSubagentPaths.watchPaths);
|
|
2547
2658
|
addDirectoryWatchPaths(watchPaths, sources.userChainDir, sources.userChains.files, sources.userChains.directories);
|
|
2548
2659
|
for (const chain of sources.projectChainLoaded) addDirectoryWatchPaths(watchPaths, chain.dir, chain.loaded.files, chain.loaded.directories);
|
|
2549
2660
|
for (const chain of sources.packageChainLoaded) addDirectoryWatchPaths(watchPaths, chain.entry.dir, chain.loaded.files, chain.loaded.directories);
|
|
2550
|
-
sources.
|
|
2661
|
+
sources.chainWatchPaths = watchPaths;
|
|
2662
|
+
sources.watchPaths = [...new Set([...sources.watchPaths, ...watchPaths])];
|
|
2551
2663
|
}
|
|
2552
2664
|
|
|
2553
2665
|
function getAgentDiscoverySources(cwd: string, preferredModelProvider?: string, includeChains = false): AgentDiscoverySources {
|
|
2554
2666
|
const key = discoveryCacheKey(cwd, preferredModelProvider);
|
|
2555
2667
|
const cached = agentDiscoveryCache.get(key);
|
|
2556
|
-
if (cached && cached.fingerprint === discoveryFingerprint(cached.sources
|
|
2668
|
+
if (cached && cached.fingerprint === discoveryFingerprint(cached.sources)) {
|
|
2557
2669
|
if (includeChains) {
|
|
2558
2670
|
ensureDiscoveryChains(cached.sources);
|
|
2559
|
-
cached.fingerprint = discoveryFingerprint(cached.sources
|
|
2671
|
+
cached.fingerprint = discoveryFingerprint(cached.sources);
|
|
2560
2672
|
}
|
|
2561
2673
|
return cached.sources;
|
|
2562
2674
|
}
|
|
2563
2675
|
const sources = buildAgentDiscoverySources(cwd, preferredModelProvider);
|
|
2564
|
-
const entry = { sources, fingerprint: discoveryFingerprint(sources
|
|
2676
|
+
const entry = { sources, fingerprint: discoveryFingerprint(sources) };
|
|
2565
2677
|
agentDiscoveryCache.set(key, entry);
|
|
2566
2678
|
if (includeChains) {
|
|
2567
2679
|
ensureDiscoveryChains(sources);
|
|
2568
|
-
entry.fingerprint = discoveryFingerprint(sources
|
|
2680
|
+
entry.fingerprint = discoveryFingerprint(sources);
|
|
2569
2681
|
}
|
|
2570
2682
|
return sources;
|
|
2571
2683
|
}
|
|
@@ -2761,27 +2873,28 @@ function discoverAgentsUncached(cwd: string, scope: AgentScope, preferredModelPr
|
|
|
2761
2873
|
const defaultExtensions = resolveSubagentDefaultExtensions(userSettings, projectSettings, projectSettingsPath);
|
|
2762
2874
|
const modelScope = projectSettings.modelScope ?? userSettings.modelScope;
|
|
2763
2875
|
const packageSubagentPaths = collectPackageSubagentPaths(effectiveCwd, { includeUser: scope !== "project", includeProject: scope !== "user" });
|
|
2876
|
+
const isExcluded = agentExclusions(agentExclusionRoots(userSettingsPath, projectSettingsPath));
|
|
2764
2877
|
const directories: AgentDefinitionDirectoryReport[] = [reportAgentDefinitionDirectory("builtin", BUILTIN_AGENTS_DIR, BUILTIN_AGENT_DEFINITION_INSPECTION)];
|
|
2765
2878
|
const builtinLoaded = loadAgentsFromDefinitionFiles(BUILTIN_AGENT_DEFINITION_FILES, "builtin");
|
|
2766
2879
|
const builtinAgents = applyBuiltinOverrides(applySubagentDefaults(builtinLoaded.agents, defaultModel, defaultProvider, defaultThinking, defaultExtensions), userSettings, projectSettings, userSettingsPath, projectSettingsPath);
|
|
2767
|
-
const userScanDirs = settingsAgentScanDirs(userSettings.agentScanDirs ?? []);
|
|
2768
|
-
const projectScanDirs = settingsAgentScanDirs(projectSettings.agentScanDirs ?? []);
|
|
2769
|
-
const userLoaded = scope === "project" ? [] : [...extraUserAgentDirs(), ...userScanDirs.dirs, userDirOld, userDirNew].map((dir, discoveryPriority) => {
|
|
2770
|
-
const inspection = inspectAgentDefinitionDirectory(dir);
|
|
2880
|
+
const userScanDirs = settingsAgentScanDirs(userSettings.agentScanDirs ?? [], isExcluded);
|
|
2881
|
+
const projectScanDirs = settingsAgentScanDirs(projectSettings.agentScanDirs ?? [], isExcluded);
|
|
2882
|
+
const userLoaded = scope === "project" ? [] : [...extraUserAgentDirs(), ...userScanDirs.dirs, userDirOld, userDirNew].filter((dir) => !isExcluded(dir)).map((dir, discoveryPriority) => {
|
|
2883
|
+
const inspection = inspectAgentDefinitionDirectory(dir, undefined, isExcluded);
|
|
2771
2884
|
directories.push(reportAgentDefinitionDirectory("user", dir, inspection));
|
|
2772
2885
|
return loadAgentsFromDir(dir, "user", discoveryPriority, undefined, inspection);
|
|
2773
2886
|
});
|
|
2774
2887
|
const userAgents = applyCustomAgentOverrides(applySubagentDefaults(userLoaded.flatMap((loaded) => loaded.agents), defaultModel, defaultProvider, defaultThinking, defaultExtensions), userSettings, projectSettings, userSettingsPath, projectSettingsPath);
|
|
2775
|
-
const projectInspections = scope === "user" ? new Map<string, AgentDefinitionInspection>() : new Map(projectCandidateDirs.map((dir) => [dir, inspectAgentDefinitionDirectory(dir)]));
|
|
2776
|
-
if (scope !== "user") for (const dir of
|
|
2777
|
-
const projectLoaded = scope === "user" ? [] : [...projectScanDirs.dirs, ...projectAgentDirs].map((dir, discoveryPriority) => {
|
|
2778
|
-
const inspection = projectInspections.get(dir) ?? inspectAgentDefinitionDirectory(dir);
|
|
2888
|
+
const projectInspections = scope === "user" ? new Map<string, AgentDefinitionInspection>() : new Map(projectCandidateDirs.filter((dir) => !isExcluded(dir)).map((dir) => [dir, inspectAgentDefinitionDirectory(dir, undefined, isExcluded)]));
|
|
2889
|
+
if (scope !== "user") for (const [dir, inspection] of projectInspections) directories.push(reportAgentDefinitionDirectory("project", dir, inspection));
|
|
2890
|
+
const projectLoaded = scope === "user" ? [] : [...projectScanDirs.dirs, ...projectAgentDirs].filter((dir) => !isExcluded(dir)).map((dir, discoveryPriority) => {
|
|
2891
|
+
const inspection = projectInspections.get(dir) ?? inspectAgentDefinitionDirectory(dir, undefined, isExcluded);
|
|
2779
2892
|
if (!projectInspections.has(dir)) directories.push(reportAgentDefinitionDirectory("project", dir, inspection));
|
|
2780
2893
|
return loadAgentsFromDir(dir, "project", dir === projectAgentsDir ? 1 : discoveryPriority, undefined, inspection);
|
|
2781
2894
|
});
|
|
2782
2895
|
const projectAgents = applyCustomAgentOverrides(applySubagentDefaults(projectLoaded.flatMap((loaded) => loaded.agents), defaultModel, defaultProvider, defaultThinking, defaultExtensions), userSettings, projectSettings, userSettingsPath, projectSettingsPath);
|
|
2783
|
-
const packageLoaded = packageSubagentPaths.agents.map((entry, index) => {
|
|
2784
|
-
const inspection = inspectAgentDefinitionDirectory(entry.dir);
|
|
2896
|
+
const packageLoaded = packageSubagentPaths.agents.filter((entry) => !isExcluded(entry.dir)).map((entry, index) => {
|
|
2897
|
+
const inspection = inspectAgentDefinitionDirectory(entry.dir, undefined, isExcluded);
|
|
2785
2898
|
directories.push(reportAgentDefinitionDirectory("package", entry.dir, inspection));
|
|
2786
2899
|
return loadAgentsFromDir(entry.dir, "package", packageSubagentPaths.agents.length - index, entry, inspection);
|
|
2787
2900
|
});
|