pi-subagents 0.55.0 → 0.57.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 +81 -3
- package/agents/claude-code-writer.md +15 -0
- package/agents/claude-code.md +15 -0
- package/agents/codex-exec-writer.md +15 -0
- package/agents/codex-exec.md +15 -0
- package/agents/cursor-agent-writer.md +14 -0
- package/agents/cursor-agent.md +14 -0
- package/docs/agents.md +111 -15
- package/docs/configuration.md +37 -0
- package/docs/extension-api.md +2 -2
- package/docs/models.md +6 -0
- package/docs/observability.md +8 -7
- package/docs/tool-reference.md +11 -1
- package/docs/workflows.md +20 -0
- package/package.json +1 -1
- package/skills/pi-subagents/references/execution-controls.md +1 -1
- package/src/agents/agent-management.ts +13 -5
- package/src/agents/agent-refinements.ts +4 -4
- package/src/agents/agent-serializer.ts +2 -0
- package/src/agents/agents.ts +242 -49
- package/src/agents/builtin-names.ts +6 -0
- package/src/agents/runtime-agent-registry.ts +8 -2
- package/src/api/preflight.ts +22 -3
- package/src/extension/config.ts +70 -0
- package/src/extension/doctor.ts +3 -3
- package/src/extension/index.ts +18 -4
- package/src/extension/public-execution.ts +29 -13
- package/src/extension/rpc.ts +20 -5
- package/src/extension/schemas.ts +9 -3
- package/src/extension/tool-description.ts +11 -9
- package/src/inspectors/herdr/actions.ts +2 -1
- package/src/inspectors/herdr/inspector-runner.ts +2 -10
- package/src/inspectors/herdr/session-roots-codec.ts +42 -0
- package/src/integrations/herdr-status.ts +46 -3
- package/src/runs/background/active-async-capacity.ts +77 -10
- package/src/runs/background/async-execution.ts +148 -27
- package/src/runs/background/async-job-tracker.ts +5 -0
- package/src/runs/background/async-resume.ts +3 -0
- package/src/runs/background/async-retention.ts +20 -3
- package/src/runs/background/async-status.ts +5 -0
- package/src/runs/background/chain-root-attachment.ts +15 -1
- package/src/runs/background/fleet-view.ts +16 -10
- package/src/runs/background/inspect-rpc.ts +8 -8
- package/src/runs/background/result-files.ts +27 -14
- package/src/runs/background/result-watcher.ts +9 -5
- package/src/runs/background/run-status.ts +33 -6
- package/src/runs/background/scheduled-runs.ts +7 -1
- package/src/runs/background/subagent-runner.ts +254 -49
- package/src/runs/background/wait-completions.ts +4 -0
- package/src/runs/foreground/execution.ts +107 -16
- package/src/runs/foreground/foreground-control.ts +6 -0
- package/src/runs/foreground/foreground-history.ts +25 -3
- package/src/runs/foreground/subagent-executor.ts +324 -84
- package/src/runs/foreground/workflow-detach-reconcile.ts +31 -7
- package/src/runs/shared/acceptance.ts +10 -5
- package/src/runs/shared/agent-contract.ts +1 -1
- package/src/runs/shared/child-protocol.ts +21 -7
- package/src/runs/shared/claude-code-adapter.ts +129 -0
- package/src/runs/shared/codex-exec-adapter.ts +129 -0
- package/src/runs/shared/completion-guard.ts +3 -2
- package/src/runs/shared/cursor-agent-adapter.ts +114 -0
- package/src/runs/shared/dynamic-fanout.ts +1 -1
- package/src/runs/shared/extension-bindings.ts +78 -0
- package/src/runs/shared/external-cli-contract.ts +167 -0
- package/src/runs/shared/external-cli-preflight.ts +122 -0
- package/src/runs/shared/external-cli-runner.ts +349 -54
- package/src/runs/shared/fast-mode-extension.ts +10 -0
- package/src/runs/shared/model-exclusions.ts +59 -8
- package/src/runs/shared/model-fallback.ts +25 -8
- package/src/runs/shared/mutation-evidence.ts +150 -0
- package/src/runs/shared/nested-events.ts +3 -1
- package/src/runs/shared/nested-render.ts +2 -2
- package/src/runs/shared/parallel-utils.ts +3 -0
- package/src/runs/shared/pi-args.ts +47 -0
- package/src/runs/shared/process-signal.ts +13 -0
- package/src/runs/shared/run-history.ts +21 -1
- package/src/runs/shared/structured-output.ts +18 -4
- package/src/runs/shared/subagent-prompt-runtime.ts +9 -5
- package/src/shared/fork-context.ts +21 -0
- package/src/shared/formatters.ts +7 -1
- package/src/shared/launch-contract.ts +6 -0
- package/src/shared/pruned-fork.ts +450 -0
- package/src/shared/session-file-trust.ts +19 -0
- package/src/shared/session-tokens.ts +14 -3
- package/src/shared/settings.ts +16 -3
- package/src/shared/shortcuts.ts +17 -0
- package/src/shared/types.ts +166 -9
- package/src/shared/workflow-child-permit.ts +116 -0
- package/src/slash/delegation-adapters.ts +0 -1
- package/src/slash/slash-commands.ts +8 -6
- package/src/tui/fleet-status.ts +27 -10
- package/src/tui/fleet-transcript.ts +11 -5
- package/src/tui/fleet.ts +28 -13
- package/src/tui/render.ts +47 -21
- package/src/workflows/scripted-workflow.ts +298 -30
- package/src/workflows/workflow-child-summary.ts +117 -0
- package/src/workflows/workflow-receipt.ts +119 -4
|
@@ -33,6 +33,7 @@ import { resolveSubagentModelOverride, type ParentModel } from "../runs/shared/m
|
|
|
33
33
|
import { validateToolBudgetConfig } from "../runs/shared/tool-budget.ts";
|
|
34
34
|
import { resolveTurnBudgetConfig } from "../runs/shared/turn-budget.ts";
|
|
35
35
|
import { validateAcceptanceInput } from "../runs/shared/acceptance.ts";
|
|
36
|
+
import { CODE_OWNED_EXTERNAL_CLI_ADAPTER_LABEL, isCodeOwnedExternalCliAdapterId, validateCodeOwnedProfileRunner } from "../runs/shared/external-cli-contract.ts";
|
|
36
37
|
import type { AcceptanceInput, Details, ExtensionConfig, ToolBudgetConfig } from "../shared/types.ts";
|
|
37
38
|
import { getProjectConfigDir } from "../shared/utils.ts";
|
|
38
39
|
import { capabilityCeilingAgentRestrictionSources, isAgentAllowedByCapabilityCeiling, resolveCurrentSubagentCapabilityCeiling } from "../runs/shared/capability-ceiling.ts";
|
|
@@ -378,15 +379,17 @@ function applyAgentConfig(target: AgentConfig, cfg: Record<string, unknown>): st
|
|
|
378
379
|
if (runner.type === "pi" && Object.keys(runner).every((key) => key === "type")) target.runner = { type: "pi" };
|
|
379
380
|
else if (runner.type === "external-cli" && typeof runner.command === "string" && runner.command.trim()
|
|
380
381
|
&& (runner.args === undefined || (Array.isArray(runner.args) && runner.args.every((arg) => typeof arg === "string")))
|
|
382
|
+
&& (runner.adapter === undefined || isCodeOwnedExternalCliAdapterId(runner.adapter))
|
|
383
|
+
&& (runner.adapter === undefined || runner.args === undefined || runner.args.length === 0)
|
|
381
384
|
&& (runner.promptDelivery === undefined || runner.promptDelivery === "stdin")
|
|
382
|
-
&& Object.keys(runner).every((key) => ["type", "command", "args", "promptDelivery"].includes(key))) {
|
|
385
|
+
&& Object.keys(runner).every((key) => ["type", "adapter", "command", "args", "promptDelivery"].includes(key))) {
|
|
383
386
|
const runnerArgs = Array.isArray(runner.args) ? runner.args.filter((arg): arg is string => typeof arg === "string") : undefined;
|
|
384
|
-
target.runner = { type: "external-cli", command: runner.command.trim(), ...(runnerArgs?.length ? { args: runnerArgs } : {}), ...(runner.promptDelivery ? { promptDelivery: "stdin" } : {}) };
|
|
387
|
+
target.runner = { type: "external-cli", ...(isCodeOwnedExternalCliAdapterId(runner.adapter) ? { adapter: runner.adapter } : {}), command: runner.command.trim(), ...(runnerArgs?.length ? { args: runnerArgs } : {}), ...(runner.promptDelivery ? { promptDelivery: "stdin" } : {}) };
|
|
385
388
|
} else if (runner.type === "external-job" && typeof runner.provider === "string" && runner.provider.trim() === runner.provider && runner.provider
|
|
386
389
|
&& (runner.options === undefined || (runner.options && typeof runner.options === "object" && !Array.isArray(runner.options) && isJsonSerializable(runner.options)))
|
|
387
390
|
&& Object.keys(runner).every((key) => ["type", "provider", "options"].includes(key))) {
|
|
388
391
|
target.runner = { type: "external-job", provider: runner.provider, ...(runner.options ? { options: runner.options as Record<string, unknown> } : {}) };
|
|
389
|
-
} else return
|
|
392
|
+
} else return `config.runner must be { type: 'pi' }, { type: 'external-cli', adapter?: ${CODE_OWNED_EXTERNAL_CLI_ADAPTER_LABEL}, command: string, args?: string[], promptDelivery?: 'stdin' }, or { type: 'external-job', provider: string, options?: object }.`;
|
|
390
393
|
} else return "config.runner must be an object, false, or empty string when provided.";
|
|
391
394
|
}
|
|
392
395
|
if (hasKey(cfg, "model")) {
|
|
@@ -784,13 +787,14 @@ function handleModels(params: ManagementParams, ctx: ManagementContext): AgentTo
|
|
|
784
787
|
|
|
785
788
|
const discovered = discoverAgentsAll(ctx.cwd);
|
|
786
789
|
const builtinByName = new Map(discovered.builtin.map((agent) => [agent.name, agent]));
|
|
790
|
+
const resolveBuiltinModelAgent = (name: string): AgentConfig | undefined => builtinByName.get(name) ?? resolveAgentName(name, discovered.builtin).agent;
|
|
787
791
|
const availableModels = ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
788
792
|
const currentModel = ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined;
|
|
789
793
|
const preferredProvider = ctx.model?.provider;
|
|
790
794
|
const names = requestedAgent ? [requestedAgent] : [...BUILTIN_AGENT_NAMES];
|
|
791
795
|
|
|
792
796
|
if (requestedAgent) {
|
|
793
|
-
const agent =
|
|
797
|
+
const agent = resolveBuiltinModelAgent(requestedAgent);
|
|
794
798
|
if (!agent) return result(`Builtin agent '${requestedAgent}' not found.`, true);
|
|
795
799
|
const resolvedModel = resolveSubagentModelOverride(agent.model, currentModel, availableModels, preferredProvider);
|
|
796
800
|
const lines = [
|
|
@@ -824,7 +828,7 @@ function handleModels(params: ManagementParams, ctx: ManagementContext): AgentTo
|
|
|
824
828
|
];
|
|
825
829
|
|
|
826
830
|
for (const name of names) {
|
|
827
|
-
const agent =
|
|
831
|
+
const agent = resolveBuiltinModelAgent(name);
|
|
828
832
|
if (!agent) {
|
|
829
833
|
lines.push(name);
|
|
830
834
|
lines.push(" model:");
|
|
@@ -915,6 +919,8 @@ export function handleCreate(params: ManagementParams, ctx: ManagementContext):
|
|
|
915
919
|
};
|
|
916
920
|
const applyError = applyAgentConfig(agent, cfg);
|
|
917
921
|
if (applyError) return result(applyError, true);
|
|
922
|
+
const profileError = validateCodeOwnedProfileRunner(agent);
|
|
923
|
+
if (profileError) return result(profileError, true);
|
|
918
924
|
const mw = modelWarning(ctx, agent.model);
|
|
919
925
|
if (mw) warnings.push(mw);
|
|
920
926
|
const fmw = fallbackModelsWarning(ctx, agent.fallbackModels);
|
|
@@ -966,6 +972,8 @@ export function handleUpdate(params: ManagementParams, ctx: ManagementContext):
|
|
|
966
972
|
if (newPackageName !== undefined) updated.packageName = newPackageName;
|
|
967
973
|
else delete updated.packageName;
|
|
968
974
|
updated.name = buildRuntimeName(newLocalName, newPackageName);
|
|
975
|
+
const profileError = validateCodeOwnedProfileRunner(updated);
|
|
976
|
+
if (profileError) return result(profileError, true);
|
|
969
977
|
if (hasKey(cfg, "description")) updated.description = (cfg.description as string).trim();
|
|
970
978
|
if (hasKey(cfg, "model")) {
|
|
971
979
|
const mw = modelWarning(ctx, updated.model);
|
|
@@ -2,7 +2,7 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
5
|
-
import { discoverAgents, resolveAgentName, type AgentConfig } from "./agents.ts";
|
|
5
|
+
import { discoverAgents, formatUnknownAgentError, resolveAgentName, unknownAgentDiagnosticContext, type AgentConfig } from "./agents.ts";
|
|
6
6
|
import { getProjectSubagentsDir } from "../shared/artifacts.ts";
|
|
7
7
|
import type { Details, JsonSchemaObject, SingleResult, SubagentState } from "../shared/types.ts";
|
|
8
8
|
|
|
@@ -536,10 +536,10 @@ function guidanceFromProposal(proposal: RefinementProposal): string {
|
|
|
536
536
|
}
|
|
537
537
|
|
|
538
538
|
function resolveOneAgent(cwd: string, agentName: string): { ok: true; agent: AgentConfig } | { ok: false; error: string } {
|
|
539
|
-
const
|
|
540
|
-
const resolved = resolveAgentName(agentName, agents);
|
|
539
|
+
const discovered = discoverAgents(cwd, "both");
|
|
540
|
+
const resolved = resolveAgentName(agentName, discovered.agents);
|
|
541
541
|
if (resolved.error) return { ok: false, error: resolved.error };
|
|
542
|
-
if (!resolved.agent) return { ok: false, error:
|
|
542
|
+
if (!resolved.agent) return { ok: false, error: formatUnknownAgentError(agentName, unknownAgentDiagnosticContext(discovered)) };
|
|
543
543
|
return { ok: true, agent: resolved.agent };
|
|
544
544
|
}
|
|
545
545
|
|
|
@@ -11,6 +11,7 @@ export const KNOWN_FIELDS = new Set([
|
|
|
11
11
|
"tools",
|
|
12
12
|
"model",
|
|
13
13
|
"fallbackModels",
|
|
14
|
+
"fast",
|
|
14
15
|
"thinking",
|
|
15
16
|
"systemPromptMode",
|
|
16
17
|
"inheritProjectContext",
|
|
@@ -71,6 +72,7 @@ export function serializeAgent(config: AgentConfig, options: SerializeAgentOptio
|
|
|
71
72
|
if (config.model || preserve("model")) lines.push(`model: ${config.model ?? ""}`);
|
|
72
73
|
const fallbackModelsValue = joinComma(config.fallbackModels);
|
|
73
74
|
if (fallbackModelsValue || preserve("fallbackModels")) lines.push(`fallbackModels: ${fallbackModelsValue ?? ""}`);
|
|
75
|
+
if (config.fast === true || preserve("fast")) lines.push(`fast: ${config.fast === undefined ? "" : config.fast ? "true" : "false"}`);
|
|
74
76
|
if ((config.thinking && (config.thinking !== "off" || preserve("thinking"))) || (!config.thinking && preserve("thinking"))) {
|
|
75
77
|
lines.push(`thinking: ${config.thinking ?? ""}`);
|
|
76
78
|
}
|
package/src/agents/agents.ts
CHANGED
|
@@ -9,6 +9,7 @@ import * as os from "node:os";
|
|
|
9
9
|
import * as path from "node:path";
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
11
11
|
import type { AcceptanceInput, AcceptanceRole, AgentRunnerConfig, OutputMode, ToolBudgetConfig, TurnBudgetConfig } from "../shared/types.ts";
|
|
12
|
+
import { CODE_OWNED_EXTERNAL_CLI_ADAPTER_LABEL, isCodeOwnedExternalCliAdapterId, parseExternalCliCapabilityNarrowing, validateCodeOwnedProfileRunner } from "../runs/shared/external-cli-contract.ts";
|
|
12
13
|
import { getAgentDir, getProjectConfigDir } from "../shared/utils.ts";
|
|
13
14
|
import { KNOWN_FIELDS } from "./agent-serializer.ts";
|
|
14
15
|
import { parseChain, parseJsonChain } from "./chain-serializer.ts";
|
|
@@ -57,6 +58,7 @@ export interface BuiltinAgentOverrideBase {
|
|
|
57
58
|
model?: string;
|
|
58
59
|
modelProvider?: string;
|
|
59
60
|
fallbackModels?: string[];
|
|
61
|
+
fast?: boolean;
|
|
60
62
|
thinking?: string | false;
|
|
61
63
|
systemPromptMode: SystemPromptMode;
|
|
62
64
|
inheritProjectContext: boolean;
|
|
@@ -83,6 +85,7 @@ interface BuiltinAgentOverrideConfig {
|
|
|
83
85
|
model?: string | false;
|
|
84
86
|
defaultProvider?: string | false;
|
|
85
87
|
fallbackModels?: string[] | false;
|
|
88
|
+
fast?: boolean;
|
|
86
89
|
thinking?: string | false;
|
|
87
90
|
systemPromptMode?: SystemPromptMode;
|
|
88
91
|
inheritProjectContext?: boolean;
|
|
@@ -128,6 +131,7 @@ export interface AgentConfig {
|
|
|
128
131
|
model?: string;
|
|
129
132
|
modelProvider?: string;
|
|
130
133
|
fallbackModels?: string[];
|
|
134
|
+
fast?: boolean;
|
|
131
135
|
thinking?: string | false;
|
|
132
136
|
systemPromptMode: SystemPromptMode;
|
|
133
137
|
inheritProjectContext: boolean;
|
|
@@ -259,14 +263,72 @@ export function findBlockingAgentDiagnostic(name: string, agent: AgentConfig | r
|
|
|
259
263
|
return !agents.length || (match && agentDefinitionPriority(match) > highestPriority) ? match : undefined;
|
|
260
264
|
}
|
|
261
265
|
|
|
262
|
-
|
|
266
|
+
export type AgentDefinitionDirectoryState = "absent" | "empty" | "candidates" | "unreadable" | "not-directory";
|
|
267
|
+
|
|
268
|
+
/** A definition directory actually inspected during one agent-discovery operation. */
|
|
269
|
+
export interface AgentDefinitionDirectoryReport {
|
|
270
|
+
source: AgentSource;
|
|
271
|
+
path: string;
|
|
272
|
+
state: AgentDefinitionDirectoryState;
|
|
273
|
+
candidateCount?: number;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Filesystem provenance for a failed agent resolution. Context is created from
|
|
278
|
+
* the discovery result that supplied the effective agents; callers must not
|
|
279
|
+
* pair arbitrary agent arrays with these directory reports.
|
|
280
|
+
*/
|
|
281
|
+
export interface UnknownAgentDiagnosticContext {
|
|
282
|
+
cwd: string;
|
|
283
|
+
scope: AgentScope;
|
|
284
|
+
directories: readonly AgentDefinitionDirectoryReport[];
|
|
285
|
+
agents: readonly AgentConfig[];
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export interface AgentDiscoveryResult {
|
|
263
289
|
agents: AgentConfig[];
|
|
264
290
|
agentDiagnostics?: AgentDiscoveryDiagnostic[];
|
|
265
291
|
projectAgentsDir: string | null;
|
|
292
|
+
cwd: string;
|
|
293
|
+
scope: AgentScope;
|
|
294
|
+
directories: readonly AgentDefinitionDirectoryReport[];
|
|
266
295
|
modelScope?: ModelScopeConfig;
|
|
267
296
|
maxThinking?: ThinkingLevel;
|
|
268
297
|
}
|
|
269
298
|
|
|
299
|
+
/** Create formatter input from the exact discovery operation used for resolution. */
|
|
300
|
+
export function unknownAgentDiagnosticContext(discovered: Pick<AgentDiscoveryResult, "cwd" | "scope" | "directories" | "agents">): UnknownAgentDiagnosticContext {
|
|
301
|
+
return {
|
|
302
|
+
cwd: discovered.cwd,
|
|
303
|
+
scope: discovered.scope,
|
|
304
|
+
directories: discovered.directories,
|
|
305
|
+
agents: discovered.agents,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/** Render local discovery evidence without exposing filesystem error details. */
|
|
310
|
+
export function formatUnknownAgentError(name: string, context: UnknownAgentDiagnosticContext, prefix = "Unknown agent"): string {
|
|
311
|
+
const directories = context.directories.map((directory) => {
|
|
312
|
+
const state = directory.state === "candidates"
|
|
313
|
+
? `${directory.candidateCount ?? 0} candidate${directory.candidateCount === 1 ? "" : "s"}`
|
|
314
|
+
: directory.state === "empty" ? "present but empty"
|
|
315
|
+
: directory.state === "not-directory" ? "not a directory"
|
|
316
|
+
: directory.state;
|
|
317
|
+
return `- ${directory.source}: ${directory.path} (${state})`;
|
|
318
|
+
});
|
|
319
|
+
const agents = [...context.agents]
|
|
320
|
+
.sort((left, right) => left.name.localeCompare(right.name) || left.source.localeCompare(right.source))
|
|
321
|
+
.map((agent) => `- ${agent.name} (${agent.source})`);
|
|
322
|
+
return [
|
|
323
|
+
`${prefix}: ${name}`,
|
|
324
|
+
`Effective cwd: ${path.resolve(context.cwd)}`,
|
|
325
|
+
"Consulted agent-definition directories:",
|
|
326
|
+
...(directories.length ? directories : ["- (none)"]),
|
|
327
|
+
"Discovered agents:",
|
|
328
|
+
...(agents.length ? agents : ["- (none)"]),
|
|
329
|
+
].join("\n");
|
|
330
|
+
}
|
|
331
|
+
|
|
270
332
|
function getUserChainDir(): string {
|
|
271
333
|
return path.join(getAgentDir(), "chains");
|
|
272
334
|
}
|
|
@@ -378,6 +440,10 @@ function resolveSettingsPackageRoot(source: string, baseDir: string): string | u
|
|
|
378
440
|
if (normalized === "." || normalized === ".." || normalized.startsWith("./") || normalized.startsWith("../")) {
|
|
379
441
|
return path.resolve(baseDir, normalized);
|
|
380
442
|
}
|
|
443
|
+
if (/^https?:\/\//i.test(trimmed)) {
|
|
444
|
+
const parsed = parseGitPackagePath(`git:${trimmed}`);
|
|
445
|
+
return parsed ? path.join(baseDir, "git", parsed.host, parsed.repoPath) : undefined;
|
|
446
|
+
}
|
|
381
447
|
return undefined;
|
|
382
448
|
}
|
|
383
449
|
|
|
@@ -640,6 +706,7 @@ function cloneOverrideBase(agent: AgentConfig): BuiltinAgentOverrideBase {
|
|
|
640
706
|
...(agent.model !== undefined ? { model: agent.model } : {}),
|
|
641
707
|
...(agent.modelProvider !== undefined ? { modelProvider: agent.modelProvider } : {}),
|
|
642
708
|
...(agent.fallbackModels ? { fallbackModels: [...agent.fallbackModels] } : {}),
|
|
709
|
+
...(agent.fast !== undefined ? { fast: agent.fast } : {}),
|
|
643
710
|
...(agent.thinking !== undefined ? { thinking: agent.thinking } : {}),
|
|
644
711
|
systemPromptMode: agent.systemPromptMode,
|
|
645
712
|
inheritProjectContext: agent.inheritProjectContext,
|
|
@@ -670,6 +737,7 @@ function cloneOverrideValue(override: BuiltinAgentOverrideConfig): BuiltinAgentO
|
|
|
670
737
|
...(override.fallbackModels !== undefined
|
|
671
738
|
? { fallbackModels: override.fallbackModels === false ? false : [...override.fallbackModels] }
|
|
672
739
|
: {}),
|
|
740
|
+
...(override.fast !== undefined ? { fast: override.fast } : {}),
|
|
673
741
|
...(override.thinking !== undefined ? { thinking: override.thinking } : {}),
|
|
674
742
|
...(override.systemPromptMode !== undefined ? { systemPromptMode: override.systemPromptMode } : {}),
|
|
675
743
|
...(override.inheritProjectContext !== undefined ? { inheritProjectContext: override.inheritProjectContext } : {}),
|
|
@@ -864,6 +932,11 @@ function parseBuiltinOverrideEntry(
|
|
|
864
932
|
else throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'model'; expected a string or false.`);
|
|
865
933
|
}
|
|
866
934
|
|
|
935
|
+
if ("fast" in input) {
|
|
936
|
+
if (typeof input.fast === "boolean") override.fast = input.fast;
|
|
937
|
+
else throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'fast'; expected a boolean.`);
|
|
938
|
+
}
|
|
939
|
+
|
|
867
940
|
if ("thinking" in input) {
|
|
868
941
|
if (typeof input.thinking === "string" || input.thinking === false) override.thinking = input.thinking;
|
|
869
942
|
else throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'thinking'; expected a string or false.`);
|
|
@@ -1195,6 +1268,7 @@ function applyBuiltinOverride(
|
|
|
1195
1268
|
else next.modelProvider = override.defaultProvider;
|
|
1196
1269
|
}
|
|
1197
1270
|
if (override.fallbackModels !== undefined) { if (override.fallbackModels === false) delete next.fallbackModels; else next.fallbackModels = [...override.fallbackModels]; }
|
|
1271
|
+
if (override.fast !== undefined) next.fast = override.fast;
|
|
1198
1272
|
if (override.thinking !== undefined) { if (override.thinking === false) delete next.thinking; else next.thinking = override.thinking; }
|
|
1199
1273
|
if (override.systemPromptMode !== undefined) next.systemPromptMode = override.systemPromptMode;
|
|
1200
1274
|
if (override.inheritProjectContext !== undefined) next.inheritProjectContext = override.inheritProjectContext;
|
|
@@ -1332,6 +1406,9 @@ function applyCustomAgentOverride(
|
|
|
1332
1406
|
override.fallbackModels === false ? undefined : [...override.fallbackModels],
|
|
1333
1407
|
);
|
|
1334
1408
|
}
|
|
1409
|
+
if (override.fast !== undefined) {
|
|
1410
|
+
fill("fast", ["fast"], override.fast);
|
|
1411
|
+
}
|
|
1335
1412
|
if (override.thinking !== undefined) {
|
|
1336
1413
|
fill("thinking", ["thinking"], override.thinking === false ? undefined : override.thinking);
|
|
1337
1414
|
}
|
|
@@ -1411,7 +1488,7 @@ function applyCustomAgentOverrides(
|
|
|
1411
1488
|
|
|
1412
1489
|
export function buildBuiltinOverrideConfig(
|
|
1413
1490
|
base: BuiltinAgentOverrideBase,
|
|
1414
|
-
draft: Pick<AgentConfig, "model" | "modelProvider" | "fallbackModels" | "thinking" | "systemPromptMode" | "inheritProjectContext" | "inheritSkills" | "defaultContext" | "acceptanceRole" | "disabled" | "systemPrompt" | "skills" | "tools" | "mcpDirectTools" | "extensions" | "subagentOnlyExtensions" | "completionGuard" | "toolBudget"> & Partial<Pick<AgentConfig, "description" | "output" | "outputMode" | "defaultReads">>,
|
|
1491
|
+
draft: Pick<AgentConfig, "model" | "modelProvider" | "fallbackModels" | "fast" | "thinking" | "systemPromptMode" | "inheritProjectContext" | "inheritSkills" | "defaultContext" | "acceptanceRole" | "disabled" | "systemPrompt" | "skills" | "tools" | "mcpDirectTools" | "extensions" | "subagentOnlyExtensions" | "completionGuard" | "toolBudget"> & Partial<Pick<AgentConfig, "description" | "output" | "outputMode" | "defaultReads">>,
|
|
1415
1492
|
): BuiltinAgentOverrideConfig | undefined {
|
|
1416
1493
|
const override: BuiltinAgentOverrideConfig = {};
|
|
1417
1494
|
|
|
@@ -1425,6 +1502,7 @@ export function buildBuiltinOverrideConfig(
|
|
|
1425
1502
|
if (draft.model !== base.model) override.model = draft.model ?? false;
|
|
1426
1503
|
if (draft.modelProvider !== base.modelProvider) override.defaultProvider = draft.modelProvider ?? false;
|
|
1427
1504
|
if (!arraysEqual(draft.fallbackModels, base.fallbackModels)) override.fallbackModels = draft.fallbackModels ? [...draft.fallbackModels] : false;
|
|
1505
|
+
if (draft.fast !== base.fast) override.fast = draft.fast === true;
|
|
1428
1506
|
if (draft.thinking !== base.thinking) override.thinking = draft.thinking ?? false;
|
|
1429
1507
|
if (draft.systemPromptMode !== base.systemPromptMode) override.systemPromptMode = draft.systemPromptMode;
|
|
1430
1508
|
if (draft.inheritProjectContext !== base.inheritProjectContext) override.inheritProjectContext = draft.inheritProjectContext;
|
|
@@ -1583,9 +1661,22 @@ function shouldPruneDiscoveryDir(rootDir: string, dir: string, dirName: string):
|
|
|
1583
1661
|
return path.resolve(dir) !== path.resolve(rootDir) && isDiscoveryNestedProjectRoot(dir);
|
|
1584
1662
|
}
|
|
1585
1663
|
|
|
1586
|
-
function listFilesRecursive(
|
|
1664
|
+
function listFilesRecursive(
|
|
1665
|
+
dir: string,
|
|
1666
|
+
predicate: (fileName: string) => boolean,
|
|
1667
|
+
rootDir = dir,
|
|
1668
|
+
visitedDirectories = new Set<string>(),
|
|
1669
|
+
): string[] {
|
|
1587
1670
|
const files: string[] = [];
|
|
1588
1671
|
if (!fs.existsSync(dir)) return files;
|
|
1672
|
+
let realDir: string;
|
|
1673
|
+
try {
|
|
1674
|
+
realDir = fs.realpathSync(dir);
|
|
1675
|
+
} catch {
|
|
1676
|
+
return files;
|
|
1677
|
+
}
|
|
1678
|
+
if (visitedDirectories.has(realDir)) return files;
|
|
1679
|
+
visitedDirectories.add(realDir);
|
|
1589
1680
|
|
|
1590
1681
|
let entries: fs.Dirent[];
|
|
1591
1682
|
try {
|
|
@@ -1596,9 +1687,17 @@ function listFilesRecursive(dir: string, predicate: (fileName: string) => boolea
|
|
|
1596
1687
|
|
|
1597
1688
|
for (const entry of entries) {
|
|
1598
1689
|
const filePath = path.join(dir, entry.name);
|
|
1599
|
-
|
|
1690
|
+
let isDirectory = entry.isDirectory();
|
|
1691
|
+
if (entry.isSymbolicLink()) {
|
|
1692
|
+
try {
|
|
1693
|
+
isDirectory = fs.statSync(filePath).isDirectory();
|
|
1694
|
+
} catch {
|
|
1695
|
+
isDirectory = false;
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
if (isDirectory) {
|
|
1600
1699
|
if (!shouldPruneDiscoveryDir(rootDir, filePath, entry.name)) {
|
|
1601
|
-
files.push(...listFilesRecursive(filePath, predicate, rootDir));
|
|
1700
|
+
files.push(...listFilesRecursive(filePath, predicate, rootDir, visitedDirectories));
|
|
1602
1701
|
}
|
|
1603
1702
|
continue;
|
|
1604
1703
|
}
|
|
@@ -1609,6 +1708,81 @@ function listFilesRecursive(dir: string, predicate: (fileName: string) => boolea
|
|
|
1609
1708
|
return files;
|
|
1610
1709
|
}
|
|
1611
1710
|
|
|
1711
|
+
export interface AgentDefinitionInspection {
|
|
1712
|
+
files: string[];
|
|
1713
|
+
state: AgentDefinitionDirectoryState;
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
/** Narrow filesystem seam so unavailable paths can be tested without permissions. */
|
|
1717
|
+
export interface AgentDefinitionInspectionFs {
|
|
1718
|
+
existsSync(filePath: string): boolean;
|
|
1719
|
+
realpathSync?(filePath: string): string;
|
|
1720
|
+
statSync(filePath: string): { isDirectory(): boolean };
|
|
1721
|
+
readdirSync(dir: string): fs.Dirent[];
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
const DEFAULT_AGENT_DEFINITION_INSPECTION_FS: AgentDefinitionInspectionFs = {
|
|
1725
|
+
existsSync: fs.existsSync,
|
|
1726
|
+
realpathSync: fs.realpathSync,
|
|
1727
|
+
statSync: fs.statSync,
|
|
1728
|
+
readdirSync: (dir) => fs.readdirSync(dir, { withFileTypes: true }),
|
|
1729
|
+
};
|
|
1730
|
+
|
|
1731
|
+
/**
|
|
1732
|
+
* Inspect one agent-definition directory while retaining traversal failure
|
|
1733
|
+
* state. A nested unreadable directory makes the whole inspection unavailable,
|
|
1734
|
+
* preventing a partial traversal from being reported as empty or complete.
|
|
1735
|
+
*/
|
|
1736
|
+
export function inspectAgentDefinitionDirectory(dir: string, operations: AgentDefinitionInspectionFs = DEFAULT_AGENT_DEFINITION_INSPECTION_FS): AgentDefinitionInspection {
|
|
1737
|
+
const root = path.resolve(dir);
|
|
1738
|
+
try {
|
|
1739
|
+
if (!operations.existsSync(root)) return { files: [], state: "absent" };
|
|
1740
|
+
if (!operations.statSync(root).isDirectory()) return { files: [], state: "not-directory" };
|
|
1741
|
+
} catch {
|
|
1742
|
+
return { files: [], state: "unreadable" };
|
|
1743
|
+
}
|
|
1744
|
+
const files: string[] = [];
|
|
1745
|
+
let unreadable = false;
|
|
1746
|
+
const visitedDirectories = new Set<string>();
|
|
1747
|
+
const visit = (current: string): void => {
|
|
1748
|
+
let realDir: string;
|
|
1749
|
+
try {
|
|
1750
|
+
realDir = operations.realpathSync?.(current) ?? path.resolve(current);
|
|
1751
|
+
} catch {
|
|
1752
|
+
unreadable = true;
|
|
1753
|
+
return;
|
|
1754
|
+
}
|
|
1755
|
+
if (visitedDirectories.has(realDir)) return;
|
|
1756
|
+
visitedDirectories.add(realDir);
|
|
1757
|
+
|
|
1758
|
+
let entries: fs.Dirent[];
|
|
1759
|
+
try {
|
|
1760
|
+
entries = operations.readdirSync(current).sort((a, b) => a.name.localeCompare(b.name));
|
|
1761
|
+
} catch {
|
|
1762
|
+
unreadable = true;
|
|
1763
|
+
return;
|
|
1764
|
+
}
|
|
1765
|
+
for (const entry of entries) {
|
|
1766
|
+
const filePath = path.join(current, entry.name);
|
|
1767
|
+
let isDirectory = entry.isDirectory();
|
|
1768
|
+
if (entry.isSymbolicLink()) {
|
|
1769
|
+
try {
|
|
1770
|
+
isDirectory = operations.statSync(filePath).isDirectory();
|
|
1771
|
+
} catch {
|
|
1772
|
+
isDirectory = false;
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
if (isDirectory) {
|
|
1776
|
+
if (!shouldPruneDiscoveryDir(root, filePath, entry.name)) visit(filePath);
|
|
1777
|
+
continue;
|
|
1778
|
+
}
|
|
1779
|
+
if ((entry.isFile() || entry.isSymbolicLink()) && entry.name.endsWith(".md") && !entry.name.endsWith(".chain.md") && !isLegacyAgentSkillPath(root, filePath)) files.push(filePath);
|
|
1780
|
+
}
|
|
1781
|
+
};
|
|
1782
|
+
visit(root);
|
|
1783
|
+
return { files, state: unreadable ? "unreadable" : files.length ? "candidates" : "empty" };
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1612
1786
|
function isLegacyAgentSkillPath(rootDir: string, filePath: string): boolean {
|
|
1613
1787
|
const relative = path.relative(rootDir, filePath);
|
|
1614
1788
|
const parts = relative.split(path.sep).map((part) => part.toLowerCase());
|
|
@@ -1667,18 +1841,23 @@ function parseAgentRunnerFrontmatter(raw: string | undefined, agentName: string)
|
|
|
1667
1841
|
if (runner.args !== undefined && (!Array.isArray(runner.args) || runner.args.some((arg) => typeof arg !== "string"))) {
|
|
1668
1842
|
throw new Error(`Agent '${agentName}' external-cli runner args must be an array of strings.`);
|
|
1669
1843
|
}
|
|
1844
|
+
if (runner.adapter !== undefined && !isCodeOwnedExternalCliAdapterId(runner.adapter)) throw new Error(`Agent '${agentName}' external-cli runner adapter must be ${CODE_OWNED_EXTERNAL_CLI_ADAPTER_LABEL}.`);
|
|
1845
|
+
if (runner.adapter !== undefined && Array.isArray(runner.args) && runner.args.length > 0) throw new Error(`Agent '${agentName}' ${runner.adapter} adapter owns its argv; runner args are not supported.`);
|
|
1670
1846
|
if (runner.promptDelivery !== undefined && runner.promptDelivery !== "stdin") {
|
|
1671
1847
|
throw new Error(`Agent '${agentName}' external-cli runner promptDelivery must be 'stdin'.`);
|
|
1672
1848
|
}
|
|
1673
|
-
const
|
|
1849
|
+
const capabilities = parseExternalCliCapabilityNarrowing(runner.capabilities, `Agent '${agentName}' external-cli runner capabilities`);
|
|
1850
|
+
const supported = new Set(["type", "adapter", "command", "args", "promptDelivery", "capabilities"]);
|
|
1674
1851
|
const unknown = Object.keys(runner).filter((key) => !supported.has(key));
|
|
1675
1852
|
if (unknown.length > 0) throw new Error(`Agent '${agentName}' external-cli runner has unsupported fields: ${unknown.join(", ")}.`);
|
|
1676
1853
|
const runnerArgs = Array.isArray(runner.args) ? runner.args.filter((arg): arg is string => typeof arg === "string") : undefined;
|
|
1677
1854
|
return {
|
|
1678
1855
|
type: "external-cli",
|
|
1856
|
+
...(isCodeOwnedExternalCliAdapterId(runner.adapter) ? { adapter: runner.adapter } : {}),
|
|
1679
1857
|
command: runner.command.trim(),
|
|
1680
1858
|
...(runnerArgs?.length ? { args: runnerArgs } : {}),
|
|
1681
1859
|
...(runner.promptDelivery ? { promptDelivery: "stdin" as const } : {}),
|
|
1860
|
+
...(capabilities ? { capabilities } : {}),
|
|
1682
1861
|
};
|
|
1683
1862
|
}
|
|
1684
1863
|
|
|
@@ -1709,9 +1888,9 @@ interface AgentDefinitionFile {
|
|
|
1709
1888
|
content: string;
|
|
1710
1889
|
}
|
|
1711
1890
|
|
|
1712
|
-
function readAgentDefinitionFiles(dir: string): AgentDefinitionFile[] {
|
|
1891
|
+
function readAgentDefinitionFiles(dir: string, inspection = inspectAgentDefinitionDirectory(dir)): AgentDefinitionFile[] {
|
|
1713
1892
|
const files: AgentDefinitionFile[] = [];
|
|
1714
|
-
for (const filePath of
|
|
1893
|
+
for (const filePath of inspection.files) {
|
|
1715
1894
|
if (isLegacyAgentSkillPath(dir, filePath)) {
|
|
1716
1895
|
continue;
|
|
1717
1896
|
}
|
|
@@ -1768,6 +1947,8 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
|
|
|
1768
1947
|
const mcpDirectTools = parsedTools.mcpDirectTools ?? [];
|
|
1769
1948
|
const defaultReads = parseFrontmatterList(frontmatter.defaultReads);
|
|
1770
1949
|
const aliases = normalizeAgentAliases(parseFrontmatterList(frontmatter.aliases ?? frontmatter.alias), runtimeName);
|
|
1950
|
+
const profileError = validateCodeOwnedProfileRunner({ name: runtimeName, localName, aliases, runner });
|
|
1951
|
+
if (profileError) throw new Error(profileError);
|
|
1771
1952
|
const skillStr = frontmatter.skill || frontmatter.skills;
|
|
1772
1953
|
const skills = parseFrontmatterList(skillStr);
|
|
1773
1954
|
const skillPath = parseFrontmatterList(frontmatter.skillPath);
|
|
@@ -1835,6 +2016,12 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
|
|
|
1835
2016
|
|
|
1836
2017
|
const extensions = resolveAgentRelativeExtensionPaths(parseFrontmatterList(frontmatter.extensions), filePath);
|
|
1837
2018
|
const subagentOnlyExtensions = resolveAgentRelativeExtensionPaths(parseFrontmatterList(frontmatter.subagentOnlyExtensions), filePath);
|
|
2019
|
+
let fast: boolean | undefined;
|
|
2020
|
+
if (frontmatter.fast !== undefined) {
|
|
2021
|
+
if (frontmatter.fast === "true") fast = true;
|
|
2022
|
+
else if (frontmatter.fast === "false") fast = false;
|
|
2023
|
+
else throw new Error(`Agent '${localName}' has invalid fast frontmatter; expected true or false.`);
|
|
2024
|
+
}
|
|
1838
2025
|
|
|
1839
2026
|
const extraFields: Record<string, string> = {};
|
|
1840
2027
|
for (const [key, value] of Object.entries(frontmatter)) {
|
|
@@ -1881,6 +2068,7 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
|
|
|
1881
2068
|
...(mcpDirectTools.length > 0 ? { mcpDirectTools } : {}),
|
|
1882
2069
|
...(frontmatter.model !== undefined ? { model: frontmatter.model } : {}),
|
|
1883
2070
|
...(fallbackModels?.length ? { fallbackModels } : {}),
|
|
2071
|
+
...(fast !== undefined ? { fast } : {}),
|
|
1884
2072
|
...(frontmatter.thinking !== undefined ? { thinking: frontmatter.thinking === "false" ? false : frontmatter.thinking } : {}),
|
|
1885
2073
|
systemPromptMode,
|
|
1886
2074
|
inheritProjectContext,
|
|
@@ -1922,8 +2110,17 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
|
|
|
1922
2110
|
return { agents, diagnostics };
|
|
1923
2111
|
}
|
|
1924
2112
|
|
|
1925
|
-
function loadAgentsFromDir(dir: string, source: AgentSource, discoveryPriority?: number, packageSource?: Omit<PackageSubagentPath, "dir"
|
|
1926
|
-
return loadAgentsFromDefinitionFiles(readAgentDefinitionFiles(dir), source, discoveryPriority, packageSource);
|
|
2113
|
+
function loadAgentsFromDir(dir: string, source: AgentSource, discoveryPriority?: number, packageSource?: Omit<PackageSubagentPath, "dir">, inspection = inspectAgentDefinitionDirectory(dir)): { agents: AgentConfig[]; diagnostics: AgentDiscoveryDiagnostic[] } {
|
|
2114
|
+
return loadAgentsFromDefinitionFiles(readAgentDefinitionFiles(dir, inspection), source, discoveryPriority, packageSource);
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2117
|
+
function reportAgentDefinitionDirectory(source: AgentSource, dir: string, inspection: AgentDefinitionInspection): AgentDefinitionDirectoryReport {
|
|
2118
|
+
return {
|
|
2119
|
+
source,
|
|
2120
|
+
path: path.resolve(dir),
|
|
2121
|
+
state: inspection.state,
|
|
2122
|
+
...(inspection.state === "candidates" ? { candidateCount: inspection.files.length } : {}),
|
|
2123
|
+
};
|
|
1927
2124
|
}
|
|
1928
2125
|
|
|
1929
2126
|
function loadChainsFromDir(dir: string, source: AgentSource): { chains: ChainConfig[]; diagnostics: ChainDiscoveryDiagnostic[] } {
|
|
@@ -1960,20 +2157,18 @@ function isDirectory(p: string): boolean {
|
|
|
1960
2157
|
}
|
|
1961
2158
|
}
|
|
1962
2159
|
|
|
1963
|
-
function resolveNearestProjectAgentDirs(cwd: string): { readDirs: string[]; preferredDir: string | null } {
|
|
2160
|
+
function resolveNearestProjectAgentDirs(cwd: string): { readDirs: string[]; candidateDirs: string[]; preferredDir: string | null } {
|
|
1964
2161
|
const projectRoot = findConfiguredProjectRoot(cwd);
|
|
1965
|
-
if (!projectRoot) return { readDirs: [], preferredDir: null };
|
|
2162
|
+
if (!projectRoot) return { readDirs: [], candidateDirs: [], preferredDir: null };
|
|
1966
2163
|
|
|
1967
2164
|
const legacyDir = path.join(projectRoot, ".agents");
|
|
1968
2165
|
const preferredDir = path.join(getProjectConfigDir(projectRoot), "agents");
|
|
2166
|
+
const candidateDirs = [legacyDir, preferredDir];
|
|
1969
2167
|
const readDirs: string[] = [];
|
|
1970
2168
|
if (isDirectory(legacyDir)) readDirs.push(legacyDir);
|
|
1971
2169
|
if (isDirectory(preferredDir)) readDirs.push(preferredDir);
|
|
1972
2170
|
|
|
1973
|
-
return {
|
|
1974
|
-
readDirs,
|
|
1975
|
-
preferredDir,
|
|
1976
|
-
};
|
|
2171
|
+
return { readDirs, candidateDirs, preferredDir };
|
|
1977
2172
|
}
|
|
1978
2173
|
|
|
1979
2174
|
function resolveNearestProjectChainDirs(cwd: string): { readDirs: string[]; preferredDir: string | null } {
|
|
@@ -1987,7 +2182,9 @@ function resolveNearestProjectChainDirs(cwd: string): { readDirs: string[]; pref
|
|
|
1987
2182
|
};
|
|
1988
2183
|
}
|
|
1989
2184
|
const BUILTIN_AGENTS_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "agents");
|
|
1990
|
-
|
|
2185
|
+
// Candidate files and inspection state must describe the same cached builtin scan.
|
|
2186
|
+
const BUILTIN_AGENT_DEFINITION_INSPECTION = inspectAgentDefinitionDirectory(BUILTIN_AGENTS_DIR);
|
|
2187
|
+
const BUILTIN_AGENT_DEFINITION_FILES = readAgentDefinitionFiles(BUILTIN_AGENTS_DIR, BUILTIN_AGENT_DEFINITION_INSPECTION);
|
|
1991
2188
|
|
|
1992
2189
|
export const EXTRA_AGENT_DIRS_ENV = "PI_SUBAGENT_EXTRA_AGENT_DIRS";
|
|
1993
2190
|
|
|
@@ -2006,11 +2203,12 @@ function extraUserAgentDirs(): string[] {
|
|
|
2006
2203
|
}
|
|
2007
2204
|
|
|
2008
2205
|
export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryResult {
|
|
2206
|
+
const effectiveCwd = path.resolve(cwd);
|
|
2009
2207
|
const userDirOld = path.join(getAgentDir(), "agents");
|
|
2010
2208
|
const userDirNew = path.join(os.homedir(), ".agents");
|
|
2011
|
-
const { readDirs: projectAgentDirs, preferredDir: projectAgentsDir } = resolveNearestProjectAgentDirs(
|
|
2209
|
+
const { readDirs: projectAgentDirs, candidateDirs: projectCandidateDirs, preferredDir: projectAgentsDir } = resolveNearestProjectAgentDirs(effectiveCwd);
|
|
2012
2210
|
const userSettingsPath = getUserAgentSettingsPath();
|
|
2013
|
-
const projectSettingsPath = getProjectAgentSettingsPath(
|
|
2211
|
+
const projectSettingsPath = getProjectAgentSettingsPath(effectiveCwd);
|
|
2014
2212
|
const userSettings = scope === "project" ? EMPTY_SUBAGENT_SETTINGS : readSubagentSettings(userSettingsPath);
|
|
2015
2213
|
const projectSettings = scope === "user" ? EMPTY_SUBAGENT_SETTINGS : readSubagentSettings(projectSettingsPath);
|
|
2016
2214
|
const defaultProvider = resolveSubagentDefaultProvider(userSettings, projectSettings, projectSettingsPath);
|
|
@@ -2019,65 +2217,60 @@ export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryRe
|
|
|
2019
2217
|
const maxThinking = resolveSubagentMaxThinking(userSettings, projectSettings, projectSettingsPath);
|
|
2020
2218
|
const defaultExtensions = resolveSubagentDefaultExtensions(userSettings, projectSettings, projectSettingsPath);
|
|
2021
2219
|
const modelScope = projectSettings.modelScope ?? userSettings.modelScope;
|
|
2022
|
-
const packageSubagentPaths = collectPackageSubagentPaths(
|
|
2220
|
+
const packageSubagentPaths = collectPackageSubagentPaths(effectiveCwd, {
|
|
2023
2221
|
includeUser: scope !== "project",
|
|
2024
2222
|
includeProject: scope !== "user",
|
|
2025
2223
|
});
|
|
2224
|
+
const directories: AgentDefinitionDirectoryReport[] = [reportAgentDefinitionDirectory("builtin", BUILTIN_AGENTS_DIR, BUILTIN_AGENT_DEFINITION_INSPECTION)];
|
|
2026
2225
|
|
|
2027
2226
|
const builtinLoaded = loadAgentsFromDefinitionFiles(BUILTIN_AGENT_DEFINITION_FILES, "builtin");
|
|
2028
2227
|
const builtinAgents = applyBuiltinOverrides(
|
|
2029
2228
|
applySubagentDefaults(builtinLoaded.agents, defaultModel, defaultProvider, defaultThinking, defaultExtensions),
|
|
2030
|
-
userSettings,
|
|
2031
|
-
projectSettings,
|
|
2032
|
-
userSettingsPath,
|
|
2033
|
-
projectSettingsPath,
|
|
2229
|
+
userSettings, projectSettings, userSettingsPath, projectSettingsPath,
|
|
2034
2230
|
);
|
|
2035
2231
|
|
|
2036
|
-
const userLoaded = scope === "project" ? [] : [...extraUserAgentDirs(), userDirOld, userDirNew]
|
|
2037
|
-
|
|
2232
|
+
const userLoaded = scope === "project" ? [] : [...extraUserAgentDirs(), userDirOld, userDirNew].map((dir, discoveryPriority) => {
|
|
2233
|
+
const inspection = inspectAgentDefinitionDirectory(dir);
|
|
2234
|
+
directories.push(reportAgentDefinitionDirectory("user", dir, inspection));
|
|
2235
|
+
return loadAgentsFromDir(dir, "user", discoveryPriority, undefined, inspection);
|
|
2236
|
+
});
|
|
2038
2237
|
const userAgents = applyCustomAgentOverrides(
|
|
2039
2238
|
applySubagentDefaults(userLoaded.flatMap((loaded) => loaded.agents), defaultModel, defaultProvider, defaultThinking, defaultExtensions),
|
|
2040
|
-
userSettings,
|
|
2041
|
-
projectSettings,
|
|
2042
|
-
userSettingsPath,
|
|
2043
|
-
projectSettingsPath,
|
|
2239
|
+
userSettings, projectSettings, userSettingsPath, projectSettingsPath,
|
|
2044
2240
|
);
|
|
2045
2241
|
|
|
2046
|
-
|
|
2242
|
+
// Report both legacy and preferred project definition paths only after the
|
|
2243
|
+
// existing configured-root resolver found a root; never synthesize cwd paths.
|
|
2244
|
+
const projectInspections = scope === "user" ? new Map<string, AgentDefinitionInspection>() : new Map(projectCandidateDirs.map((dir) => [dir, inspectAgentDefinitionDirectory(dir)]));
|
|
2245
|
+
if (scope !== "user") for (const dir of projectCandidateDirs) directories.push(reportAgentDefinitionDirectory("project", dir, projectInspections.get(dir)!));
|
|
2246
|
+
const projectLoaded = scope === "user" ? [] : projectAgentDirs.map((dir) => loadAgentsFromDir(dir, "project", dir === projectAgentsDir ? 1 : 0, undefined, projectInspections.get(dir)!));
|
|
2047
2247
|
const projectAgents = applyCustomAgentOverrides(
|
|
2048
2248
|
applySubagentDefaults(projectLoaded.flatMap((loaded) => loaded.agents), defaultModel, defaultProvider, defaultThinking, defaultExtensions),
|
|
2049
|
-
userSettings,
|
|
2050
|
-
projectSettings,
|
|
2051
|
-
userSettingsPath,
|
|
2052
|
-
projectSettingsPath,
|
|
2249
|
+
userSettings, projectSettings, userSettingsPath, projectSettingsPath,
|
|
2053
2250
|
);
|
|
2054
|
-
|
|
2251
|
+
|
|
2252
|
+
const packageLoaded = packageSubagentPaths.agents.map((entry, index) => {
|
|
2253
|
+
const inspection = inspectAgentDefinitionDirectory(entry.dir);
|
|
2254
|
+
directories.push(reportAgentDefinitionDirectory("package", entry.dir, inspection));
|
|
2255
|
+
return loadAgentsFromDir(entry.dir, "package", packageSubagentPaths.agents.length - index, entry, inspection);
|
|
2256
|
+
});
|
|
2055
2257
|
const packageMap = new Map<string, AgentConfig>();
|
|
2056
|
-
for (const loaded of packageLoaded)
|
|
2057
|
-
for (const agent of loaded.agents) {
|
|
2058
|
-
if (!packageMap.has(agent.name)) packageMap.set(agent.name, agent);
|
|
2059
|
-
}
|
|
2060
|
-
}
|
|
2258
|
+
for (const loaded of packageLoaded) for (const agent of loaded.agents) if (!packageMap.has(agent.name)) packageMap.set(agent.name, agent);
|
|
2061
2259
|
const packageAgents = applyCustomAgentOverrides(
|
|
2062
2260
|
applySubagentDefaults(Array.from(packageMap.values()), defaultModel, defaultProvider, defaultThinking, defaultExtensions),
|
|
2063
|
-
userSettings,
|
|
2064
|
-
projectSettings,
|
|
2065
|
-
userSettingsPath,
|
|
2066
|
-
projectSettingsPath,
|
|
2261
|
+
userSettings, projectSettings, userSettingsPath, projectSettingsPath,
|
|
2067
2262
|
);
|
|
2068
2263
|
const agents = applySubagentMaxThinking(
|
|
2069
|
-
mergeAgentsForScope(scope, userAgents, projectAgents, builtinAgents, packageAgents)
|
|
2070
|
-
.filter((agent) => agent.disabled !== true),
|
|
2264
|
+
mergeAgentsForScope(scope, userAgents, projectAgents, builtinAgents, packageAgents).filter((agent) => agent.disabled !== true),
|
|
2071
2265
|
maxThinking,
|
|
2072
2266
|
);
|
|
2073
|
-
|
|
2074
2267
|
const agentDiagnostics = [
|
|
2075
2268
|
...builtinLoaded.diagnostics,
|
|
2076
2269
|
...userLoaded.flatMap((loaded) => loaded.diagnostics),
|
|
2077
2270
|
...projectLoaded.flatMap((loaded) => loaded.diagnostics),
|
|
2078
2271
|
...packageLoaded.flatMap((loaded) => loaded.diagnostics),
|
|
2079
2272
|
];
|
|
2080
|
-
return { agents, agentDiagnostics, projectAgentsDir, ...(modelScope !== undefined ? { modelScope } : {}), ...(maxThinking !== undefined ? { maxThinking } : {}) };
|
|
2273
|
+
return { agents, agentDiagnostics, projectAgentsDir, cwd: effectiveCwd, scope, directories, ...(modelScope !== undefined ? { modelScope } : {}), ...(maxThinking !== undefined ? { maxThinking } : {}) };
|
|
2081
2274
|
}
|
|
2082
2275
|
|
|
2083
2276
|
export function discoverAgentsAll(cwd: string): {
|