pi-subagents 0.66.0 → 0.68.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 +138 -0
- package/README.md +5 -4
- package/agents/evidence-auditor.md +34 -0
- package/agents/reviewer.md +3 -2
- package/docs/agents.md +43 -15
- package/docs/configuration.md +67 -23
- package/docs/extension-api.md +38 -19
- package/docs/missions.md +10 -2
- package/docs/models.md +11 -79
- package/docs/observability.md +22 -12
- package/docs/standalone-background.md +59 -0
- package/docs/tool-reference.md +33 -20
- package/docs/watchdog.md +39 -10
- package/docs/workflows.md +37 -13
- package/index.ts +5 -2
- package/inspector-runner.mjs +2 -2
- package/package.json +4 -2
- package/prompts/parallel-review.md +1 -1
- package/runner-peer-loader.mjs +24 -0
- package/runner-peer-preload.mjs +32 -0
- package/skills/pi-subagents/SKILL.md +32 -21
- package/skills/pi-subagents/references/constraints-and-recipes.md +3 -2
- package/skills/pi-subagents/references/execution-controls.md +11 -9
- 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 +17 -13
- package/skills/pi-subagents/references/review-and-validation.md +3 -3
- package/src/agents/advertised-agent-prompt.ts +34 -3
- package/src/agents/agent-management.ts +57 -58
- package/src/agents/agent-serializer.ts +4 -3
- package/src/agents/agents.ts +190 -71
- package/src/agents/builtin-names.ts +1 -0
- package/src/agents/chain-serializer.ts +5 -0
- package/src/agents/runtime-agent-registry.ts +7 -6
- package/src/api/delegation.ts +4 -0
- package/src/api/preflight.ts +94 -59
- package/src/api/required-child-extensions.ts +6 -0
- package/src/api/shared-types.ts +2 -0
- package/src/extension/config.ts +10 -37
- package/src/extension/fanout-child.ts +66 -4
- package/src/extension/herdr-pi-bridge.ts +160 -0
- package/src/extension/index.ts +62 -39
- package/src/extension/public-execution.ts +7 -5
- package/src/extension/rpc.ts +4 -0
- package/src/extension/schemas.ts +81 -81
- package/src/extension/tool-description.ts +30 -82
- package/src/inspectors/actions.ts +148 -0
- package/src/inspectors/ghostty/actions.ts +74 -0
- package/src/inspectors/ghostty/plugin.ts +17 -0
- package/src/inspectors/herdr/actions.ts +99 -179
- package/src/inspectors/herdr/plugin.ts +20 -0
- package/src/inspectors/herdr/project-panes.ts +1 -1
- package/src/inspectors/{herdr/inspector-runner.ts → inspector-runner.ts} +12 -12
- package/src/inspectors/plugins.ts +8 -0
- package/src/inspectors/{herdr/session-roots-codec.ts → session-roots-codec.ts} +3 -14
- package/src/inspectors/types.ts +51 -0
- package/src/intercom/intercom-bridge.ts +50 -8
- package/src/intercom/native-supervisor-channel.ts +44 -31
- package/src/policy/authority.ts +4 -0
- package/src/profiles/profiles.ts +12 -6
- package/src/runs/background/active-async-capacity.ts +4 -0
- package/src/runs/background/active-run-index.ts +17 -1
- package/src/runs/background/async-execution.ts +348 -176
- package/src/runs/background/async-job-tracker.ts +8 -6
- package/src/runs/background/async-resume.ts +17 -12
- package/src/runs/background/async-status.ts +15 -4
- package/src/runs/background/auto-drain.ts +23 -10
- package/src/runs/background/binary-bootstrap.ts +38 -0
- package/src/runs/background/chain-append.ts +1 -1
- package/src/runs/background/chain-root-attachment.ts +14 -33
- package/src/runs/background/fleet-view.ts +30 -2
- package/src/runs/background/notify.ts +105 -7
- package/src/runs/background/owned-process-tree.ts +29 -2
- 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 +81 -33
- package/src/runs/background/run-status.ts +3 -0
- package/src/runs/background/runner-aliases.ts +12 -33
- package/src/runs/background/runner-child-launch.ts +6 -1
- package/src/runs/background/runner-child-sessions.ts +5 -4
- package/src/runs/background/runner-http-dispatcher.ts +119 -0
- package/src/runs/background/scheduled-runs.ts +51 -18
- package/src/runs/background/stale-run-reconciler.ts +35 -11
- package/src/runs/background/steering.ts +20 -2
- package/src/runs/background/subagent-runner.ts +441 -304
- package/src/runs/background/subagent-wait.ts +176 -28
- 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 +5 -3
- package/src/runs/foreground/async-steering-action.ts +18 -7
- package/src/runs/foreground/async-stop-action.ts +93 -3
- package/src/runs/foreground/execution.ts +134 -248
- package/src/runs/foreground/foreground-history.ts +2 -1
- package/src/runs/foreground/prompt-audit.ts +3 -1
- package/src/runs/foreground/subagent-executor.ts +374 -178
- package/src/runs/foreground/workflow-detach-reconcile.ts +2 -0
- package/src/runs/foreground/workflow-foreground-steering.ts +2 -1
- package/src/runs/shared/acceptance.ts +38 -11
- package/src/runs/shared/async-status-projection.ts +127 -33
- package/src/runs/shared/capability-ceiling.ts +2 -0
- package/src/runs/shared/child-hooks.ts +25 -10
- package/src/runs/shared/child-launch-plan.ts +15 -3
- package/src/runs/shared/child-launch.ts +28 -5
- package/src/runs/shared/child-lifecycle.ts +6 -3
- package/src/runs/shared/child-runtime-config.ts +8 -1
- package/src/runs/shared/child-session.ts +127 -52
- package/src/runs/shared/child-tool-plan.ts +142 -11
- package/src/runs/shared/completion-guard.ts +5 -3
- package/src/runs/shared/dynamic-fanout.ts +2 -2
- package/src/runs/shared/effective-system-prompt.ts +33 -0
- package/src/runs/shared/external-cli-contract.ts +11 -1
- package/src/runs/shared/external-cli-preflight.ts +6 -2
- package/src/runs/shared/external-cli-runner.ts +9 -7
- 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/llm-intent-arbiter.ts +12 -3
- package/src/runs/shared/model-resolution-diagnostic.ts +76 -0
- package/src/runs/shared/{model-fallback.ts → model-resolution.ts} +22 -235
- package/src/runs/shared/model-scope.ts +1 -1
- package/src/runs/shared/nested-events.ts +11 -2
- package/src/runs/shared/orca-progress-tabs.ts +1 -1
- package/src/runs/shared/parallel-utils.ts +7 -2
- package/src/runs/shared/pi-spawn.ts +10 -0
- package/src/runs/shared/subagent-prompt-runtime.ts +12 -4
- package/src/runs/shared/task-intent.ts +46 -13
- package/src/runs/shared/workflow-async-child-guidance.ts +18 -0
- package/src/runs/shared/worktree-setup-command.ts +27 -4
- package/src/runs/shared/worktree.ts +45 -15
- package/src/shared/child-cache-retention.ts +43 -0
- package/src/shared/fork-context.ts +15 -72
- package/src/shared/launch-contract.ts +68 -8
- package/src/shared/opencode-session-headers.ts +30 -0
- 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 +74 -30
- package/src/slash/delegation-adapters.ts +3 -1
- package/src/slash/delegation-request.ts +14 -0
- package/src/slash/slash-commands.ts +2 -7
- package/src/slash/subagents-admin.ts +24 -13
- package/src/tui/fleet-status.ts +164 -19
- package/src/tui/fleet.ts +16 -14
- package/src/tui/render.ts +168 -37
- package/src/watchdog/child-status.ts +28 -28
- package/src/watchdog/lsp-diagnostics.ts +1 -1
- package/src/watchdog/model-selection.ts +21 -1
- package/src/watchdog/permission-arbiter.ts +3 -1
- package/src/watchdog/register-child.ts +10 -2
- package/src/watchdog/register-main.ts +39 -35
- package/src/watchdog/render.ts +1 -1
- package/src/watchdog/review.ts +123 -74
- package/src/watchdog/rules.ts +1 -1
- package/src/watchdog/runtime.ts +100 -27
- package/src/watchdog/scope.ts +1 -1
- package/src/watchdog/settings.ts +3 -0
- package/src/watchdog/tool-actions.ts +13 -12
- package/src/watchdog/turn-delta.ts +23 -0
- package/src/watchdog/types.ts +5 -3
- package/src/watchdog/warning-format.ts +1 -1
- package/src/workflows/scripted-workflow.ts +279 -10
- package/src/workflows/workflow-checklist.ts +2 -2
- package/src/workflows/workflow-receipt.ts +21 -3
- package/src/workflows/workflow-resources.ts +13 -2
- package/runner-server-preload.mjs +0 -13
- 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/inspectors/{herdr/shell-command.ts → shell-command.ts} +0 -0
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,11 +177,18 @@ 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;
|
|
182
184
|
modelSource?: AgentModelSourceInfo;
|
|
183
185
|
maxThinking?: ThinkingLevel;
|
|
186
|
+
/**
|
|
187
|
+
* Digest of the parsed definition, set when a runtime overlay such as the
|
|
188
|
+
* Intercom bridge rewrites launch-affecting fields. Launch identity reads
|
|
189
|
+
* this instead of re-hashing the overlaid copy.
|
|
190
|
+
*/
|
|
191
|
+
definitionDigest?: string;
|
|
184
192
|
}
|
|
185
193
|
|
|
186
194
|
type ProjectRootResolution = "nearest" | "git-root";
|
|
@@ -189,6 +197,7 @@ interface SubagentSettings {
|
|
|
189
197
|
overrides: Record<string, BuiltinAgentOverrideConfig>;
|
|
190
198
|
providerOverrides: Record<string, Record<string, BuiltinAgentOverrideConfig>>;
|
|
191
199
|
agentScanDirs?: string[];
|
|
200
|
+
agentExcludeDirs?: string[];
|
|
192
201
|
defaultModel?: string;
|
|
193
202
|
defaultProvider?: string;
|
|
194
203
|
defaultThinking?: string;
|
|
@@ -209,6 +218,7 @@ export interface ChainStepConfig {
|
|
|
209
218
|
label?: string;
|
|
210
219
|
as?: string;
|
|
211
220
|
outputSchema?: string | Record<string, unknown>;
|
|
221
|
+
machine?: string;
|
|
212
222
|
output?: string | false;
|
|
213
223
|
outputMode?: OutputMode;
|
|
214
224
|
reads?: string[] | false;
|
|
@@ -585,8 +595,8 @@ function collectSettingsPackageRoots(settingsFile: string, baseDir: string): str
|
|
|
585
595
|
for (const entry of packages) {
|
|
586
596
|
const packageSource = typeof entry === "string"
|
|
587
597
|
? entry
|
|
588
|
-
: typeof entry === "object" && entry !== null &&
|
|
589
|
-
?
|
|
598
|
+
: typeof entry === "object" && entry !== null && "source" in entry && typeof entry.source === "string"
|
|
599
|
+
? entry.source
|
|
590
600
|
: undefined;
|
|
591
601
|
if (!packageSource) continue;
|
|
592
602
|
const packageRoot = resolveSettingsPackageRoot(packageSource, baseDir);
|
|
@@ -694,12 +704,20 @@ function effectiveAgentMatch(matches: AgentConfig[]): { agent?: AgentConfig; err
|
|
|
694
704
|
|
|
695
705
|
export function resolveAgentName(name: string, agents: AgentConfig[]): { agent?: AgentConfig; error?: string } {
|
|
696
706
|
const raw = name.trim();
|
|
697
|
-
const
|
|
698
|
-
if (
|
|
699
|
-
if (
|
|
700
|
-
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);
|
|
701
711
|
if (effective.agent) return effective;
|
|
702
|
-
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(", ")}` };
|
|
703
721
|
}
|
|
704
722
|
|
|
705
723
|
const aliases = agents.filter((agent) => agent.aliases?.includes(raw));
|
|
@@ -749,12 +767,12 @@ function arraysEqual(a: string[] | undefined, b: string[] | undefined): boolean
|
|
|
749
767
|
function cloneOverrideBase(agent: AgentConfig): BuiltinAgentOverrideBase {
|
|
750
768
|
return {
|
|
751
769
|
description: agent.description,
|
|
770
|
+
...(agent.machine !== undefined ? { machine: agent.machine } : {}),
|
|
752
771
|
...(agent.output !== undefined ? { output: agent.output } : {}),
|
|
753
772
|
...(agent.outputMode !== undefined ? { outputMode: agent.outputMode } : {}),
|
|
754
773
|
...(agent.defaultReads !== undefined ? { defaultReads: [...agent.defaultReads] } : {}),
|
|
755
774
|
...(agent.model !== undefined ? { model: agent.model } : {}),
|
|
756
775
|
...(agent.modelProvider !== undefined ? { modelProvider: agent.modelProvider } : {}),
|
|
757
|
-
...(agent.fallbackModels ? { fallbackModels: [...agent.fallbackModels] } : {}),
|
|
758
776
|
...(agent.fast !== undefined ? { fast: agent.fast } : {}),
|
|
759
777
|
...(agent.thinking !== undefined ? { thinking: agent.thinking } : {}),
|
|
760
778
|
systemPromptMode: agent.systemPromptMode,
|
|
@@ -782,14 +800,12 @@ function cloneOverrideBase(agent: AgentConfig): BuiltinAgentOverrideBase {
|
|
|
782
800
|
function cloneOverrideValue(override: BuiltinAgentOverrideConfig): BuiltinAgentOverrideConfig {
|
|
783
801
|
return {
|
|
784
802
|
...(override.description !== undefined ? { description: override.description } : {}),
|
|
803
|
+
...(override.machine !== undefined ? { machine: override.machine } : {}),
|
|
785
804
|
...(override.output !== undefined ? { output: override.output } : {}),
|
|
786
805
|
...(override.outputMode !== undefined ? { outputMode: override.outputMode } : {}),
|
|
787
806
|
...(override.defaultReads !== undefined ? { defaultReads: override.defaultReads === false ? false : [...override.defaultReads] } : {}),
|
|
788
807
|
...(override.model !== undefined ? { model: override.model } : {}),
|
|
789
808
|
...(override.defaultProvider !== undefined ? { defaultProvider: override.defaultProvider } : {}),
|
|
790
|
-
...(override.fallbackModels !== undefined
|
|
791
|
-
? { fallbackModels: override.fallbackModels === false ? false : [...override.fallbackModels] }
|
|
792
|
-
: {}),
|
|
793
809
|
...(override.fast !== undefined ? { fast: override.fast } : {}),
|
|
794
810
|
...(override.thinking !== undefined ? { thinking: override.thinking } : {}),
|
|
795
811
|
...(override.systemPromptMode !== undefined ? { systemPromptMode: override.systemPromptMode } : {}),
|
|
@@ -818,8 +834,17 @@ function isProjectRootCandidate(dir: string): boolean {
|
|
|
818
834
|
|
|
819
835
|
function findProjectRootCandidates(cwd: string): string[] {
|
|
820
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)));
|
|
821
844
|
let currentDir = cwd;
|
|
822
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;
|
|
823
848
|
if (isProjectRootCandidate(currentDir)) roots.push(currentDir);
|
|
824
849
|
|
|
825
850
|
const parentDir = path.dirname(currentDir);
|
|
@@ -951,6 +976,15 @@ function parseToolsOverride(
|
|
|
951
976
|
throw new Error(`Builtin override '${meta.name}' in '${meta.filePath}' has invalid 'tools'; expected an array of strings, "inherit", or false.`);
|
|
952
977
|
}
|
|
953
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
|
+
|
|
954
988
|
function parseBuiltinOverrideEntry(
|
|
955
989
|
name: string,
|
|
956
990
|
value: unknown,
|
|
@@ -961,6 +995,9 @@ function parseBuiltinOverrideEntry(
|
|
|
961
995
|
}
|
|
962
996
|
|
|
963
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
|
+
}
|
|
964
1001
|
const override: BuiltinAgentOverrideConfig = {};
|
|
965
1002
|
|
|
966
1003
|
if ("description" in input) {
|
|
@@ -1078,11 +1115,15 @@ function parseBuiltinOverrideEntry(
|
|
|
1078
1115
|
else throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'systemPrompt'; expected a string.`);
|
|
1079
1116
|
}
|
|
1080
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
|
+
|
|
1081
1124
|
const defaultReads = parseOverrideStringArrayOrFalse(input.defaultReads, { filePath, name, field: "defaultReads" });
|
|
1082
1125
|
if (defaultReads !== undefined) override.defaultReads = defaultReads;
|
|
1083
1126
|
|
|
1084
|
-
const fallbackModels = parseOverrideStringArrayOrFalse(input.fallbackModels, { filePath, name, field: "fallbackModels" });
|
|
1085
|
-
if (fallbackModels !== undefined) override.fallbackModels = fallbackModels;
|
|
1086
1127
|
|
|
1087
1128
|
if ("defaultProvider" in input) {
|
|
1088
1129
|
if (input.defaultProvider === false) override.defaultProvider = false;
|
|
@@ -1185,6 +1226,14 @@ function readSubagentSettings(filePath: string | null): SubagentSettings {
|
|
|
1185
1226
|
}
|
|
1186
1227
|
agentScanDirs = subagentsObject.agentScanDirs.map((item) => item.trim());
|
|
1187
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
|
+
}
|
|
1188
1237
|
const modelScope = parseModelScopeConfig(subagentsObject.modelScope, { filePath });
|
|
1189
1238
|
|
|
1190
1239
|
const parsed: Record<string, BuiltinAgentOverrideConfig> = {};
|
|
@@ -1200,6 +1249,7 @@ function readSubagentSettings(filePath: string | null): SubagentSettings {
|
|
|
1200
1249
|
...(maxThinking !== undefined ? { maxThinking } : {}),
|
|
1201
1250
|
...(defaultExtensions !== undefined ? { defaultExtensions } : {}),
|
|
1202
1251
|
...(agentScanDirs !== undefined ? { agentScanDirs } : {}),
|
|
1252
|
+
...(agentExcludeDirs !== undefined ? { agentExcludeDirs } : {}),
|
|
1203
1253
|
...(disableBuiltins !== undefined ? { disableBuiltins } : {}),
|
|
1204
1254
|
...(disableThinking !== undefined ? { disableThinking } : {}),
|
|
1205
1255
|
...(modelScope !== undefined ? { modelScope } : {}),
|
|
@@ -1378,6 +1428,7 @@ function applyBuiltinOverride(
|
|
|
1378
1428
|
};
|
|
1379
1429
|
|
|
1380
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; }
|
|
1381
1432
|
if (override.output !== undefined) { if (override.output === false) delete next.output; else next.output = override.output; }
|
|
1382
1433
|
if (override.outputMode !== undefined) next.outputMode = override.outputMode;
|
|
1383
1434
|
if (override.defaultReads !== undefined) { if (override.defaultReads === false) delete next.defaultReads; else next.defaultReads = [...override.defaultReads]; }
|
|
@@ -1389,7 +1440,6 @@ function applyBuiltinOverride(
|
|
|
1389
1440
|
if (override.defaultProvider === false) delete next.modelProvider;
|
|
1390
1441
|
else next.modelProvider = override.defaultProvider;
|
|
1391
1442
|
}
|
|
1392
|
-
if (override.fallbackModels !== undefined) { if (override.fallbackModels === false) delete next.fallbackModels; else next.fallbackModels = [...override.fallbackModels]; }
|
|
1393
1443
|
if (override.fast !== undefined) next.fast = override.fast;
|
|
1394
1444
|
if (override.thinking !== undefined) { if (override.thinking === false) delete next.thinking; else next.thinking = override.thinking; }
|
|
1395
1445
|
if (override.systemPromptMode !== undefined) next.systemPromptMode = override.systemPromptMode;
|
|
@@ -1510,9 +1560,10 @@ function applyCustomAgentOverrides(
|
|
|
1510
1560
|
|
|
1511
1561
|
export function buildBuiltinOverrideConfig(
|
|
1512
1562
|
base: BuiltinAgentOverrideBase,
|
|
1513
|
-
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">>,
|
|
1514
1564
|
): BuiltinAgentOverrideConfig | undefined {
|
|
1515
1565
|
const override: BuiltinAgentOverrideConfig = {};
|
|
1566
|
+
if (draft.machine !== base.machine) override.machine = draft.machine ?? false;
|
|
1516
1567
|
|
|
1517
1568
|
if (draft.description !== undefined) {
|
|
1518
1569
|
const description = draft.description.trim();
|
|
@@ -1523,7 +1574,6 @@ export function buildBuiltinOverrideConfig(
|
|
|
1523
1574
|
if (!arraysEqual(draft.defaultReads, base.defaultReads)) override.defaultReads = draft.defaultReads ? [...draft.defaultReads] : false;
|
|
1524
1575
|
if (draft.model !== base.model) override.model = draft.model ?? false;
|
|
1525
1576
|
if (draft.modelProvider !== base.modelProvider) override.defaultProvider = draft.modelProvider ?? false;
|
|
1526
|
-
if (!arraysEqual(draft.fallbackModels, base.fallbackModels)) override.fallbackModels = draft.fallbackModels ? [...draft.fallbackModels] : false;
|
|
1527
1577
|
if (draft.fast !== base.fast) override.fast = draft.fast === true;
|
|
1528
1578
|
if (draft.thinking !== base.thinking) override.thinking = draft.thinking ?? false;
|
|
1529
1579
|
if (draft.systemPromptMode !== base.systemPromptMode) override.systemPromptMode = draft.systemPromptMode;
|
|
@@ -1578,21 +1628,25 @@ export function saveBuiltinAgentOverride(
|
|
|
1578
1628
|
return filePath;
|
|
1579
1629
|
}
|
|
1580
1630
|
|
|
1581
|
-
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 } {
|
|
1582
1632
|
const filePath = scope === "project" ? getProjectAgentSettingsPath(cwd) : getUserAgentSettingsPath();
|
|
1583
1633
|
if (!filePath) throw new Error("Project override is not available here. No project config root was found.");
|
|
1584
|
-
if (!fs.existsSync(filePath)) return { path: filePath, removed: false };
|
|
1634
|
+
if (!fs.existsSync(filePath)) return { path: filePath, removed: false, machinePreserved: false };
|
|
1585
1635
|
|
|
1586
1636
|
const settings = readSettingsFileStrict(filePath);
|
|
1587
1637
|
const subagents = settings.subagents;
|
|
1588
|
-
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 };
|
|
1589
1639
|
const nextSubagents = { ...(subagents as Record<string, unknown>) };
|
|
1590
1640
|
const agentOverrides = nextSubagents.agentOverrides;
|
|
1591
|
-
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 };
|
|
1592
1642
|
|
|
1593
1643
|
const nextOverrides = { ...(agentOverrides as Record<string, unknown>) };
|
|
1594
|
-
|
|
1595
|
-
|
|
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];
|
|
1596
1650
|
if (Object.keys(nextOverrides).length > 0) nextSubagents.agentOverrides = nextOverrides;
|
|
1597
1651
|
else delete nextSubagents.agentOverrides;
|
|
1598
1652
|
|
|
@@ -1600,7 +1654,7 @@ export function removeBuiltinAgentOverride(cwd: string, name: string, scope: "us
|
|
|
1600
1654
|
else delete settings.subagents;
|
|
1601
1655
|
|
|
1602
1656
|
writeSettingsFile(filePath, settings);
|
|
1603
|
-
return { path: filePath, removed: true };
|
|
1657
|
+
return { path: filePath, removed: true, machinePreserved: options?.preserveMachine === true && machine !== undefined };
|
|
1604
1658
|
}
|
|
1605
1659
|
|
|
1606
1660
|
export function mergeBuiltinAgentOverride(
|
|
@@ -1772,8 +1826,9 @@ const DEFAULT_AGENT_DEFINITION_INSPECTION_FS: AgentDefinitionInspectionFs = {
|
|
|
1772
1826
|
* state. A nested unreadable directory makes the whole inspection unavailable,
|
|
1773
1827
|
* preventing a partial traversal from being reported as empty or complete.
|
|
1774
1828
|
*/
|
|
1775
|
-
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 {
|
|
1776
1830
|
const root = path.resolve(dir);
|
|
1831
|
+
if (isExcluded(root)) return rememberAgentDefinitionInspection({ files: [], state: "empty" }, []);
|
|
1777
1832
|
try {
|
|
1778
1833
|
if (!operations.existsSync(root)) return rememberAgentDefinitionInspection({ files: [], state: "absent" }, [root]);
|
|
1779
1834
|
if (!operations.statSync(root).isDirectory()) return rememberAgentDefinitionInspection({ files: [], state: "not-directory" }, [root]);
|
|
@@ -1805,6 +1860,7 @@ export function inspectAgentDefinitionDirectory(dir: string, operations: AgentDe
|
|
|
1805
1860
|
}
|
|
1806
1861
|
for (const entry of entries) {
|
|
1807
1862
|
const filePath = path.join(current, entry.name);
|
|
1863
|
+
if (isExcluded(filePath)) continue;
|
|
1808
1864
|
let isDirectory = entry.isDirectory();
|
|
1809
1865
|
if (entry.isSymbolicLink()) {
|
|
1810
1866
|
try {
|
|
@@ -1904,7 +1960,7 @@ function parseAgentRunnerFrontmatter(raw: string | undefined, agentName: string)
|
|
|
1904
1960
|
|
|
1905
1961
|
function validateExternalRunnerProfile(frontmatter: Record<string, string>, agentName: string, runner: AgentRunnerConfig | undefined): void {
|
|
1906
1962
|
if (runner?.type !== "external-cli" && runner?.type !== "external-job") return;
|
|
1907
|
-
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"]
|
|
1908
1964
|
.filter((field) => frontmatter[field] !== undefined);
|
|
1909
1965
|
if (unsupported.length > 0) {
|
|
1910
1966
|
throw new Error(`Agent '${agentName}' uses runner.type='${runner.type}' and declares unsupported Pi-only fields: ${unsupported.join(", ")}.`);
|
|
@@ -2000,7 +2056,7 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
|
|
|
2000
2056
|
const skillStr = frontmatter.skill || frontmatter.skills;
|
|
2001
2057
|
const skills = parseFrontmatterList(skillStr);
|
|
2002
2058
|
const skillPath = parseFrontmatterList(frontmatter.skillPath);
|
|
2003
|
-
|
|
2059
|
+
if (frontmatter.fallbackModels !== undefined) throw new Error(`Agent '${filePath}' uses removed frontmatter field 'fallbackModels'. Configure one model instead.`);
|
|
2004
2060
|
const systemPromptMode = frontmatter.systemPromptMode === "replace"
|
|
2005
2061
|
? "replace"
|
|
2006
2062
|
: frontmatter.systemPromptMode === "append"
|
|
@@ -2093,6 +2149,12 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
|
|
|
2093
2149
|
}
|
|
2094
2150
|
toolBudget = parsed as ToolBudgetConfig;
|
|
2095
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
|
+
}
|
|
2096
2158
|
const completionGuard = frontmatter.completionGuard === "false"
|
|
2097
2159
|
? false
|
|
2098
2160
|
: frontmatter.completionGuard === "true"
|
|
@@ -2103,6 +2165,7 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
|
|
|
2103
2165
|
? parsedMaxSubagentDepth
|
|
2104
2166
|
: undefined;
|
|
2105
2167
|
const memory = parseMemoryFrontmatter(frontmatter.memory);
|
|
2168
|
+
const machine = validateOptionalMachine(frontmatter.machine, `Agent '${runtimeName}' frontmatter 'machine'`);
|
|
2106
2169
|
const agent: AgentConfig = {
|
|
2107
2170
|
name: runtimeName,
|
|
2108
2171
|
...(runner !== undefined ? { runner } : {}),
|
|
@@ -2119,7 +2182,6 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
|
|
|
2119
2182
|
...(allowNestedSubagents !== undefined ? { allowNestedSubagents } : {}),
|
|
2120
2183
|
...(mcpDirectTools.length > 0 ? { mcpDirectTools } : {}),
|
|
2121
2184
|
...(frontmatter.model !== undefined ? { model: frontmatter.model } : {}),
|
|
2122
|
-
...(fallbackModels?.length ? { fallbackModels } : {}),
|
|
2123
2185
|
...(fast !== undefined ? { fast } : {}),
|
|
2124
2186
|
...(frontmatter.thinking !== undefined ? { thinking: frontmatter.thinking === "false" ? false : frontmatter.thinking } : {}),
|
|
2125
2187
|
systemPromptMode,
|
|
@@ -2141,8 +2203,10 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
|
|
|
2141
2203
|
...(extensions !== undefined ? { extensions } : {}),
|
|
2142
2204
|
...(subagentOnlyExtensions !== undefined ? { subagentOnlyExtensions } : {}),
|
|
2143
2205
|
...(mutationTools?.length ? { mutationTools } : {}),
|
|
2206
|
+
...(machine !== undefined ? { machine } : {}),
|
|
2144
2207
|
...(frontmatter.output !== undefined ? { output: frontmatter.output } : {}),
|
|
2145
2208
|
...(outputMode !== undefined ? { outputMode } : {}),
|
|
2209
|
+
...(outputSchema !== undefined ? { outputSchema } : {}),
|
|
2146
2210
|
...(defaultReads?.length ? { defaultReads } : {}),
|
|
2147
2211
|
defaultProgress: frontmatter.defaultProgress === "true",
|
|
2148
2212
|
interactive: frontmatter.interactive === "true",
|
|
@@ -2262,31 +2326,60 @@ interface AgentScanDirs {
|
|
|
2262
2326
|
watchPaths: string[];
|
|
2263
2327
|
}
|
|
2264
2328
|
|
|
2265
|
-
function readConfiguredAgentScanDirs(filePath: string | null): string[] {
|
|
2329
|
+
function readConfiguredAgentScanDirs(filePath: string | null, key: "agentScanDirs" | "agentExcludeDirs" = "agentScanDirs"): string[] {
|
|
2266
2330
|
if (!filePath) return [];
|
|
2267
2331
|
try {
|
|
2268
2332
|
const settings = readSettingsFileStrict(filePath);
|
|
2269
2333
|
const subagents = settings.subagents;
|
|
2270
2334
|
if (!subagents || typeof subagents !== "object" || Array.isArray(subagents)) return [];
|
|
2271
|
-
const dirs = (subagents as
|
|
2335
|
+
const dirs = (subagents as Record<string, unknown>)[key];
|
|
2272
2336
|
return Array.isArray(dirs) ? dirs.filter((dir): dir is string => typeof dir === "string" && dir.trim().length > 0) : [];
|
|
2273
2337
|
} catch {
|
|
2274
2338
|
return [];
|
|
2275
2339
|
}
|
|
2276
2340
|
}
|
|
2277
2341
|
|
|
2278
|
-
|
|
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 {
|
|
2279
2371
|
const expanded = expandHomePath(pattern.trim()).replace(/[\\/]+/g, path.sep);
|
|
2280
2372
|
if (!expanded) return { dirs: [], watchPaths: [] };
|
|
2281
2373
|
const wildcardMatches = [...expanded.matchAll(/\*/g)];
|
|
2282
2374
|
if (wildcardMatches.length === 0) {
|
|
2283
2375
|
const dir = path.resolve(expanded);
|
|
2284
|
-
return { dirs: fs.existsSync(dir) ? [dir] : [], watchPaths: [dir] };
|
|
2376
|
+
return { dirs: !isExcluded(dir) && fs.existsSync(dir) ? [dir] : [], watchPaths: [dir] };
|
|
2285
2377
|
}
|
|
2286
2378
|
const parts = expanded.split(path.sep);
|
|
2287
2379
|
const wildcardIndex = parts.findIndex((part) => part.includes("*"));
|
|
2288
2380
|
if (wildcardMatches.length !== 1 || wildcardIndex === -1 || parts[wildcardIndex] !== "*") return { dirs: [], watchPaths: [] };
|
|
2289
2381
|
const base = path.resolve(parts.slice(0, wildcardIndex).join(path.sep) || path.sep);
|
|
2382
|
+
if (isExcluded(base)) return { dirs: [], watchPaths: [base] };
|
|
2290
2383
|
const rest = parts.slice(wildcardIndex + 1);
|
|
2291
2384
|
let entries: fs.Dirent[];
|
|
2292
2385
|
try {
|
|
@@ -2296,16 +2389,16 @@ function expandAgentScanDirPattern(pattern: string): AgentScanDirs {
|
|
|
2296
2389
|
}
|
|
2297
2390
|
const candidateDirs = entries.filter((entry) => entry.isDirectory()).map((entry) => path.join(base, entry.name, ...rest));
|
|
2298
2391
|
return {
|
|
2299
|
-
dirs: candidateDirs.filter((dir) => fs.existsSync(dir)),
|
|
2392
|
+
dirs: candidateDirs.filter((dir) => !isExcluded(dir) && fs.existsSync(dir)),
|
|
2300
2393
|
watchPaths: [base, ...candidateDirs],
|
|
2301
2394
|
};
|
|
2302
2395
|
}
|
|
2303
2396
|
|
|
2304
|
-
function settingsAgentScanDirs(entries: string[]): AgentScanDirs {
|
|
2397
|
+
function settingsAgentScanDirs(entries: string[], isExcluded: (filePath: string) => boolean): AgentScanDirs {
|
|
2305
2398
|
const dirs = new Set<string>();
|
|
2306
2399
|
const watchPaths = new Set<string>();
|
|
2307
2400
|
for (const entry of entries) {
|
|
2308
|
-
const expanded = expandAgentScanDirPattern(entry);
|
|
2401
|
+
const expanded = expandAgentScanDirPattern(entry, isExcluded);
|
|
2309
2402
|
for (const dir of expanded.dirs) dirs.add(dir);
|
|
2310
2403
|
for (const watchPath of expanded.watchPaths) watchPaths.add(watchPath);
|
|
2311
2404
|
}
|
|
@@ -2372,6 +2465,10 @@ interface AgentDiscoverySources {
|
|
|
2372
2465
|
userChains?: ReturnType<typeof loadChainsFromDir>;
|
|
2373
2466
|
projectChainLoaded?: Array<{ dir: string; loaded: ReturnType<typeof loadChainsFromDir> }>;
|
|
2374
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>;
|
|
2375
2472
|
watchPaths: string[];
|
|
2376
2473
|
}
|
|
2377
2474
|
|
|
@@ -2384,7 +2481,7 @@ const agentDiscoveryCache = new Map<string, AgentDiscoveryCacheEntry>();
|
|
|
2384
2481
|
|
|
2385
2482
|
function isPathWithin(root: string, candidate: string): boolean {
|
|
2386
2483
|
const relative = path.relative(path.resolve(root), path.resolve(candidate));
|
|
2387
|
-
return relative === "" || (!relative.startsWith(
|
|
2484
|
+
return relative === "" || (relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative));
|
|
2388
2485
|
}
|
|
2389
2486
|
|
|
2390
2487
|
function addDirectoryWatchPaths(paths: Set<string>, root: string, files: readonly string[], directories: readonly string[] = []): void {
|
|
@@ -2407,13 +2504,14 @@ function addDirectoryWatchPaths(paths: Set<string>, root: string, files: readonl
|
|
|
2407
2504
|
}
|
|
2408
2505
|
}
|
|
2409
2506
|
|
|
2410
|
-
function watchPathSignature(filePath: string): string {
|
|
2507
|
+
function watchPathSignature(filePath: string, isExcluded?: (filePath: string) => boolean): string {
|
|
2411
2508
|
try {
|
|
2412
2509
|
const stat = fs.statSync(filePath);
|
|
2413
2510
|
if (stat.isDirectory()) {
|
|
2414
2511
|
const entries = fs.readdirSync(filePath, { withFileTypes: true })
|
|
2512
|
+
.filter((entry) => !isExcluded || !isExcluded(path.join(filePath, entry.name)))
|
|
2415
2513
|
.sort((left, right) => left.name.localeCompare(right.name))
|
|
2416
|
-
.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"}`)
|
|
2417
2515
|
.join("|");
|
|
2418
2516
|
return `directory:${entries}`;
|
|
2419
2517
|
}
|
|
@@ -2423,10 +2521,17 @@ function watchPathSignature(filePath: string): string {
|
|
|
2423
2521
|
}
|
|
2424
2522
|
}
|
|
2425
2523
|
|
|
2426
|
-
function discoveryFingerprint(
|
|
2427
|
-
|
|
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))
|
|
2428
2533
|
.sort((left, right) => left.localeCompare(right))
|
|
2429
|
-
.map((filePath) => `${filePath}:${watchPathSignature(filePath)}`)
|
|
2534
|
+
.map((filePath) => `${filePath}:${watchPathSignature(filePath, sources.exclusionRoots.length && !unfilteredPaths.has(filePath) ? sources.isExcluded : undefined)}`)
|
|
2430
2535
|
.join("\n");
|
|
2431
2536
|
}
|
|
2432
2537
|
|
|
@@ -2472,21 +2577,23 @@ function buildAgentDiscoverySources(cwd: string, preferredModelProvider?: string
|
|
|
2472
2577
|
const userSettingsPath = getUserAgentSettingsPath();
|
|
2473
2578
|
const projectSettingsPath = getProjectAgentSettingsPath(effectiveCwd);
|
|
2474
2579
|
const packageSubagentPaths = collectPackageSubagentPaths(effectiveCwd);
|
|
2475
|
-
const
|
|
2476
|
-
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);
|
|
2477
2584
|
|
|
2478
2585
|
const builtinLoaded = loadAgentsFromDefinitionFiles(BUILTIN_AGENT_DEFINITION_FILES, "builtin");
|
|
2479
|
-
const userLoaded = [...extraUserAgentDirs(), ...userScanDirs.dirs, userDirOld, userDirNew].map((dir, discoveryPriority): LoadedAgentDirectory => {
|
|
2480
|
-
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);
|
|
2481
2588
|
return { dir, inspection, loaded: loadAgentsFromDir(dir, "user", discoveryPriority, undefined, inspection) };
|
|
2482
2589
|
});
|
|
2483
|
-
const projectInspections = new Map(projectCandidateDirs.map((dir) => [dir, inspectAgentDefinitionDirectory(dir)]));
|
|
2484
|
-
const projectLoaded = [...projectScanDirs.dirs, ...projectAgentDirs].map((dir, discoveryPriority): LoadedAgentDirectory => {
|
|
2485
|
-
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);
|
|
2486
2593
|
return { dir, inspection, loaded: loadAgentsFromDir(dir, "project", dir === projectAgentsDir ? 1 : discoveryPriority, undefined, inspection) };
|
|
2487
2594
|
});
|
|
2488
|
-
const packageLoaded = packageSubagentPaths.agents.map((entry, index): LoadedAgentDirectory => {
|
|
2489
|
-
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);
|
|
2490
2597
|
return { dir: entry.dir, inspection, packageEntry: entry, loaded: loadAgentsFromDir(entry.dir, "package", packageSubagentPaths.agents.length - index, entry, inspection) };
|
|
2491
2598
|
});
|
|
2492
2599
|
|
|
@@ -2516,7 +2623,7 @@ function buildAgentDiscoverySources(cwd: string, preferredModelProvider?: string
|
|
|
2516
2623
|
userDirNew,
|
|
2517
2624
|
userChainDir,
|
|
2518
2625
|
projectAgentDirs,
|
|
2519
|
-
projectCandidateDirs,
|
|
2626
|
+
projectCandidateDirs: projectCandidateDirs.filter((dir) => !isExcluded(dir)),
|
|
2520
2627
|
projectAgentsDir,
|
|
2521
2628
|
projectChainDirs,
|
|
2522
2629
|
projectChainDir,
|
|
@@ -2528,7 +2635,17 @@ function buildAgentDiscoverySources(cwd: string, preferredModelProvider?: string
|
|
|
2528
2635
|
projectLoaded,
|
|
2529
2636
|
projectInspections,
|
|
2530
2637
|
packageLoaded,
|
|
2531
|
-
|
|
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),
|
|
2532
2649
|
};
|
|
2533
2650
|
}
|
|
2534
2651
|
|
|
@@ -2537,29 +2654,30 @@ function ensureDiscoveryChains(sources: AgentDiscoverySources): void {
|
|
|
2537
2654
|
sources.packageChainLoaded = sources.packageSubagentPaths.chains.map((entry) => ({ entry, loaded: loadChainsFromDir(entry.dir, "package") }));
|
|
2538
2655
|
sources.userChains = loadChainsFromDir(sources.userChainDir, "user");
|
|
2539
2656
|
sources.projectChainLoaded = sources.projectChainDirs.map((dir) => ({ dir, loaded: loadChainsFromDir(dir, "project") }));
|
|
2540
|
-
const watchPaths = new Set(sources.watchPaths);
|
|
2657
|
+
const watchPaths = new Set(sources.packageSubagentPaths.watchPaths);
|
|
2541
2658
|
addDirectoryWatchPaths(watchPaths, sources.userChainDir, sources.userChains.files, sources.userChains.directories);
|
|
2542
2659
|
for (const chain of sources.projectChainLoaded) addDirectoryWatchPaths(watchPaths, chain.dir, chain.loaded.files, chain.loaded.directories);
|
|
2543
2660
|
for (const chain of sources.packageChainLoaded) addDirectoryWatchPaths(watchPaths, chain.entry.dir, chain.loaded.files, chain.loaded.directories);
|
|
2544
|
-
sources.
|
|
2661
|
+
sources.chainWatchPaths = watchPaths;
|
|
2662
|
+
sources.watchPaths = [...new Set([...sources.watchPaths, ...watchPaths])];
|
|
2545
2663
|
}
|
|
2546
2664
|
|
|
2547
2665
|
function getAgentDiscoverySources(cwd: string, preferredModelProvider?: string, includeChains = false): AgentDiscoverySources {
|
|
2548
2666
|
const key = discoveryCacheKey(cwd, preferredModelProvider);
|
|
2549
2667
|
const cached = agentDiscoveryCache.get(key);
|
|
2550
|
-
if (cached && cached.fingerprint === discoveryFingerprint(cached.sources
|
|
2668
|
+
if (cached && cached.fingerprint === discoveryFingerprint(cached.sources)) {
|
|
2551
2669
|
if (includeChains) {
|
|
2552
2670
|
ensureDiscoveryChains(cached.sources);
|
|
2553
|
-
cached.fingerprint = discoveryFingerprint(cached.sources
|
|
2671
|
+
cached.fingerprint = discoveryFingerprint(cached.sources);
|
|
2554
2672
|
}
|
|
2555
2673
|
return cached.sources;
|
|
2556
2674
|
}
|
|
2557
2675
|
const sources = buildAgentDiscoverySources(cwd, preferredModelProvider);
|
|
2558
|
-
const entry = { sources, fingerprint: discoveryFingerprint(sources
|
|
2676
|
+
const entry = { sources, fingerprint: discoveryFingerprint(sources) };
|
|
2559
2677
|
agentDiscoveryCache.set(key, entry);
|
|
2560
2678
|
if (includeChains) {
|
|
2561
2679
|
ensureDiscoveryChains(sources);
|
|
2562
|
-
entry.fingerprint = discoveryFingerprint(sources
|
|
2680
|
+
entry.fingerprint = discoveryFingerprint(sources);
|
|
2563
2681
|
}
|
|
2564
2682
|
return sources;
|
|
2565
2683
|
}
|
|
@@ -2755,27 +2873,28 @@ function discoverAgentsUncached(cwd: string, scope: AgentScope, preferredModelPr
|
|
|
2755
2873
|
const defaultExtensions = resolveSubagentDefaultExtensions(userSettings, projectSettings, projectSettingsPath);
|
|
2756
2874
|
const modelScope = projectSettings.modelScope ?? userSettings.modelScope;
|
|
2757
2875
|
const packageSubagentPaths = collectPackageSubagentPaths(effectiveCwd, { includeUser: scope !== "project", includeProject: scope !== "user" });
|
|
2876
|
+
const isExcluded = agentExclusions(agentExclusionRoots(userSettingsPath, projectSettingsPath));
|
|
2758
2877
|
const directories: AgentDefinitionDirectoryReport[] = [reportAgentDefinitionDirectory("builtin", BUILTIN_AGENTS_DIR, BUILTIN_AGENT_DEFINITION_INSPECTION)];
|
|
2759
2878
|
const builtinLoaded = loadAgentsFromDefinitionFiles(BUILTIN_AGENT_DEFINITION_FILES, "builtin");
|
|
2760
2879
|
const builtinAgents = applyBuiltinOverrides(applySubagentDefaults(builtinLoaded.agents, defaultModel, defaultProvider, defaultThinking, defaultExtensions), userSettings, projectSettings, userSettingsPath, projectSettingsPath);
|
|
2761
|
-
const userScanDirs = settingsAgentScanDirs(userSettings.agentScanDirs ?? []);
|
|
2762
|
-
const projectScanDirs = settingsAgentScanDirs(projectSettings.agentScanDirs ?? []);
|
|
2763
|
-
const userLoaded = scope === "project" ? [] : [...extraUserAgentDirs(), ...userScanDirs.dirs, userDirOld, userDirNew].map((dir, discoveryPriority) => {
|
|
2764
|
-
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);
|
|
2765
2884
|
directories.push(reportAgentDefinitionDirectory("user", dir, inspection));
|
|
2766
2885
|
return loadAgentsFromDir(dir, "user", discoveryPriority, undefined, inspection);
|
|
2767
2886
|
});
|
|
2768
2887
|
const userAgents = applyCustomAgentOverrides(applySubagentDefaults(userLoaded.flatMap((loaded) => loaded.agents), defaultModel, defaultProvider, defaultThinking, defaultExtensions), userSettings, projectSettings, userSettingsPath, projectSettingsPath);
|
|
2769
|
-
const projectInspections = scope === "user" ? new Map<string, AgentDefinitionInspection>() : new Map(projectCandidateDirs.map((dir) => [dir, inspectAgentDefinitionDirectory(dir)]));
|
|
2770
|
-
if (scope !== "user") for (const dir of
|
|
2771
|
-
const projectLoaded = scope === "user" ? [] : [...projectScanDirs.dirs, ...projectAgentDirs].map((dir, discoveryPriority) => {
|
|
2772
|
-
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);
|
|
2773
2892
|
if (!projectInspections.has(dir)) directories.push(reportAgentDefinitionDirectory("project", dir, inspection));
|
|
2774
2893
|
return loadAgentsFromDir(dir, "project", dir === projectAgentsDir ? 1 : discoveryPriority, undefined, inspection);
|
|
2775
2894
|
});
|
|
2776
2895
|
const projectAgents = applyCustomAgentOverrides(applySubagentDefaults(projectLoaded.flatMap((loaded) => loaded.agents), defaultModel, defaultProvider, defaultThinking, defaultExtensions), userSettings, projectSettings, userSettingsPath, projectSettingsPath);
|
|
2777
|
-
const packageLoaded = packageSubagentPaths.agents.map((entry, index) => {
|
|
2778
|
-
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);
|
|
2779
2898
|
directories.push(reportAgentDefinitionDirectory("package", entry.dir, inspection));
|
|
2780
2899
|
return loadAgentsFromDir(entry.dir, "package", packageSubagentPaths.agents.length - index, entry, inspection);
|
|
2781
2900
|
});
|