pi-subagents 0.61.0 → 0.63.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 +52 -1
- package/docs/agents.md +11 -6
- package/docs/configuration.md +31 -5
- package/docs/extension-api.md +2 -2
- package/docs/models.md +5 -5
- package/docs/observability.md +5 -2
- package/docs/tool-reference.md +3 -3
- package/install.mjs +0 -1
- package/package.json +1 -1
- package/skills/pi-subagents/references/execution-controls.md +3 -4
- package/skills/pi-subagents/references/management-authoring-rpc.md +1 -1
- package/skills/pi-subagents/references/prompting-and-roles.md +3 -3
- package/src/agents/agent-management.ts +41 -4
- package/src/agents/agent-serializer.ts +3 -0
- package/src/agents/agents.ts +120 -124
- package/src/agents/runtime-agent-registry.ts +5 -1
- package/src/api/preflight.ts +4 -0
- package/src/api/shared-types.ts +3 -0
- package/src/extension/config.ts +20 -0
- package/src/extension/public-execution.ts +1 -0
- package/src/extension/schemas.ts +6 -2
- package/src/extension/tool-description.ts +1 -1
- package/src/inspectors/herdr/inspector-runner.ts +19 -13
- package/src/runs/background/active-async-capacity.ts +26 -8
- package/src/runs/background/async-execution.ts +100 -23
- package/src/runs/background/async-resume.ts +6 -2
- package/src/runs/background/async-status.ts +18 -2
- package/src/runs/background/notify.ts +13 -1
- package/src/runs/background/process-terminal.ts +16 -0
- package/src/runs/background/run-status.ts +22 -2
- package/src/runs/background/scheduled-runs.ts +63 -6
- package/src/runs/background/steering.ts +4 -1
- package/src/runs/background/subagent-runner.ts +44 -9
- package/src/runs/background/wait-completions.ts +13 -0
- package/src/runs/background/wait-tool.ts +1 -7
- package/src/runs/foreground/execution.ts +14 -7
- package/src/runs/foreground/subagent-executor.ts +38 -5
- package/src/runs/shared/acceptance.ts +85 -18
- package/src/runs/shared/capability-ceiling.ts +1 -0
- package/src/runs/shared/dynamic-fanout.ts +1 -1
- package/src/runs/shared/lane-metadata.ts +24 -3
- package/src/runs/shared/parallel-handoff.ts +4 -0
- package/src/runs/shared/parallel-utils.ts +2 -6
- package/src/runs/shared/permissions.ts +1 -1
- package/src/runs/shared/pi-args.ts +32 -14
- package/src/runs/shared/pi-spawn.ts +69 -35
- package/src/runs/shared/structured-output.ts +33 -6
- package/src/runs/shared/subagent-prompt-runtime.ts +20 -3
- package/src/runs/shared/task-intent.ts +21 -7
- package/src/runs/shared/tool-timeout.ts +1 -1
- package/src/runs/shared/worktree.ts +467 -63
- package/src/shared/atomic-json.ts +3 -1
- package/src/shared/fork-context.ts +0 -12
- package/src/shared/fork-session-cwd.ts +27 -0
- package/src/shared/launch-contract.ts +3 -0
- package/src/shared/types.ts +32 -1
- package/src/shared/utils.ts +18 -7
- package/src/slash/slash-commands.ts +1 -1
- package/src/slash/subagents-admin.ts +26 -12
- package/src/tui/fleet-status.ts +61 -2
- package/src/tui/fleet.ts +12 -7
- package/src/tui/render.ts +222 -14
- package/src/workflows/workflow-checklist.ts +441 -0
package/src/agents/agents.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { fileURLToPath } from "node:url";
|
|
|
11
11
|
import type { AcceptanceInput, AcceptanceRole, AgentRunnerConfig, 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
|
+
import { expandHomePath } from "../shared/settings.ts";
|
|
14
15
|
import { KNOWN_FIELDS } from "./agent-serializer.ts";
|
|
15
16
|
import { parseChain, parseJsonChain } from "./chain-serializer.ts";
|
|
16
17
|
import { mergeAgentsForScope } from "./agent-selection.ts";
|
|
@@ -70,6 +71,7 @@ export interface BuiltinAgentOverrideBase {
|
|
|
70
71
|
skills?: string[];
|
|
71
72
|
skillPath?: string[];
|
|
72
73
|
tools?: string[];
|
|
74
|
+
excludeTools?: string[];
|
|
73
75
|
allowNestedSubagents?: boolean;
|
|
74
76
|
mcpDirectTools?: string[];
|
|
75
77
|
extensions?: string[];
|
|
@@ -99,6 +101,7 @@ interface BuiltinAgentOverrideConfig {
|
|
|
99
101
|
systemPrompt?: string;
|
|
100
102
|
skills?: string[] | false;
|
|
101
103
|
tools?: string[] | false | "inherit";
|
|
104
|
+
excludeTools?: string[] | false;
|
|
102
105
|
allowNestedSubagents?: boolean;
|
|
103
106
|
extensions?: string[] | false;
|
|
104
107
|
subagentOnlyExtensions?: string[] | false;
|
|
@@ -111,6 +114,8 @@ interface BuiltinAgentOverrideInfo {
|
|
|
111
114
|
scope: "user" | "project";
|
|
112
115
|
path: string;
|
|
113
116
|
base: BuiltinAgentOverrideBase;
|
|
117
|
+
fields?: string[];
|
|
118
|
+
fieldScopes?: Record<string, Array<"user" | "project">>;
|
|
114
119
|
}
|
|
115
120
|
|
|
116
121
|
export interface AgentModelSourceInfo {
|
|
@@ -132,6 +137,7 @@ export interface AgentConfig {
|
|
|
132
137
|
description: string;
|
|
133
138
|
aliases?: string[];
|
|
134
139
|
tools?: string[];
|
|
140
|
+
excludeTools?: string[];
|
|
135
141
|
allowNestedSubagents?: boolean;
|
|
136
142
|
mcpDirectTools?: string[];
|
|
137
143
|
model?: string;
|
|
@@ -181,6 +187,7 @@ type ProjectRootResolution = "nearest" | "git-root";
|
|
|
181
187
|
interface SubagentSettings {
|
|
182
188
|
overrides: Record<string, BuiltinAgentOverrideConfig>;
|
|
183
189
|
providerOverrides: Record<string, Record<string, BuiltinAgentOverrideConfig>>;
|
|
190
|
+
agentScanDirs?: string[];
|
|
184
191
|
defaultModel?: string;
|
|
185
192
|
defaultProvider?: string;
|
|
186
193
|
defaultThinking?: string;
|
|
@@ -760,6 +767,7 @@ function cloneOverrideBase(agent: AgentConfig): BuiltinAgentOverrideBase {
|
|
|
760
767
|
...(agent.skills ? { skills: [...agent.skills] } : {}),
|
|
761
768
|
...(agent.skillPath ? { skillPath: [...agent.skillPath] } : {}),
|
|
762
769
|
...(agent.tools ? { tools: [...agent.tools] } : {}),
|
|
770
|
+
...(agent.excludeTools ? { excludeTools: [...agent.excludeTools] } : {}),
|
|
763
771
|
...(agent.allowNestedSubagents !== undefined ? { allowNestedSubagents: agent.allowNestedSubagents } : {}),
|
|
764
772
|
...(agent.mcpDirectTools ? { mcpDirectTools: [...agent.mcpDirectTools] } : {}),
|
|
765
773
|
...(!agent.extensionsFromDefault && agent.extensions ? { extensions: [...agent.extensions] } : {}),
|
|
@@ -793,6 +801,7 @@ function cloneOverrideValue(override: BuiltinAgentOverrideConfig): BuiltinAgentO
|
|
|
793
801
|
...(override.systemPrompt !== undefined ? { systemPrompt: override.systemPrompt } : {}),
|
|
794
802
|
...(override.skills !== undefined ? { skills: override.skills === false ? false : [...override.skills] } : {}),
|
|
795
803
|
...(override.tools !== undefined ? { tools: Array.isArray(override.tools) ? [...override.tools] : override.tools } : {}),
|
|
804
|
+
...(override.excludeTools !== undefined ? { excludeTools: override.excludeTools === false ? false : [...override.excludeTools] } : {}),
|
|
796
805
|
...(override.allowNestedSubagents !== undefined ? { allowNestedSubagents: override.allowNestedSubagents } : {}),
|
|
797
806
|
...(override.extensions !== undefined ? { extensions: override.extensions === false ? false : [...override.extensions] } : {}),
|
|
798
807
|
...(override.subagentOnlyExtensions !== undefined ? { subagentOnlyExtensions: override.subagentOnlyExtensions === false ? false : [...override.subagentOnlyExtensions] } : {}),
|
|
@@ -1085,6 +1094,8 @@ function parseBuiltinOverrideEntry(
|
|
|
1085
1094
|
|
|
1086
1095
|
const tools = parseToolsOverride(input.tools, { filePath, name });
|
|
1087
1096
|
if (tools !== undefined) override.tools = tools;
|
|
1097
|
+
const excludeTools = parseOverrideStringArrayOrFalse(input.excludeTools, { filePath, name, field: "excludeTools" });
|
|
1098
|
+
if (excludeTools !== undefined) override.excludeTools = excludeTools;
|
|
1088
1099
|
if ("allowNestedSubagents" in input) {
|
|
1089
1100
|
if (typeof input.allowNestedSubagents === "boolean") override.allowNestedSubagents = input.allowNestedSubagents;
|
|
1090
1101
|
else throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'allowNestedSubagents'; expected a boolean.`);
|
|
@@ -1165,6 +1176,14 @@ function readSubagentSettings(filePath: string | null): SubagentSettings {
|
|
|
1165
1176
|
}
|
|
1166
1177
|
defaultExtensions = subagentsObject.defaultExtensions.map((item) => item.trim());
|
|
1167
1178
|
}
|
|
1179
|
+
let agentScanDirs: string[] | undefined;
|
|
1180
|
+
if ("agentScanDirs" in subagentsObject) {
|
|
1181
|
+
if (!Array.isArray(subagentsObject.agentScanDirs)
|
|
1182
|
+
|| subagentsObject.agentScanDirs.some((item) => typeof item !== "string" || !item.trim())) {
|
|
1183
|
+
throw new Error(`Subagent settings in '${filePath}' have invalid 'agentScanDirs'; expected an array of non-empty strings.`);
|
|
1184
|
+
}
|
|
1185
|
+
agentScanDirs = subagentsObject.agentScanDirs.map((item) => item.trim());
|
|
1186
|
+
}
|
|
1168
1187
|
const modelScope = parseModelScopeConfig(subagentsObject.modelScope, { filePath });
|
|
1169
1188
|
|
|
1170
1189
|
const parsed: Record<string, BuiltinAgentOverrideConfig> = {};
|
|
@@ -1179,6 +1198,7 @@ function readSubagentSettings(filePath: string | null): SubagentSettings {
|
|
|
1179
1198
|
...(defaultThinking !== undefined ? { defaultThinking } : {}),
|
|
1180
1199
|
...(maxThinking !== undefined ? { maxThinking } : {}),
|
|
1181
1200
|
...(defaultExtensions !== undefined ? { defaultExtensions } : {}),
|
|
1201
|
+
...(agentScanDirs !== undefined ? { agentScanDirs } : {}),
|
|
1182
1202
|
...(disableBuiltins !== undefined ? { disableBuiltins } : {}),
|
|
1183
1203
|
...(disableThinking !== undefined ? { disableThinking } : {}),
|
|
1184
1204
|
...(modelScope !== undefined ? { modelScope } : {}),
|
|
@@ -1341,9 +1361,19 @@ function applyBuiltinOverride(
|
|
|
1341
1361
|
override: BuiltinAgentOverrideConfig,
|
|
1342
1362
|
meta: { scope: "user" | "project"; path: string },
|
|
1343
1363
|
): AgentConfig {
|
|
1364
|
+
const overrideInfo: BuiltinAgentOverrideInfo = {
|
|
1365
|
+
...meta,
|
|
1366
|
+
base: agent.override?.base ?? cloneOverrideBase(agent),
|
|
1367
|
+
fields: [...new Set([...(agent.override?.fields ?? []), ...Object.keys(override)])].sort(),
|
|
1368
|
+
fieldScopes: Object.fromEntries(Object.entries({ ...(agent.override?.fieldScopes ?? {}) }).map(([field, scopes]) => [field, [...scopes]])),
|
|
1369
|
+
};
|
|
1370
|
+
for (const field of Object.keys(override)) {
|
|
1371
|
+
const scopes = overrideInfo.fieldScopes![field] ?? [];
|
|
1372
|
+
overrideInfo.fieldScopes![field] = [...new Set([...scopes, meta.scope])].sort();
|
|
1373
|
+
}
|
|
1344
1374
|
const next: AgentConfig = {
|
|
1345
1375
|
...agent,
|
|
1346
|
-
override:
|
|
1376
|
+
override: overrideInfo,
|
|
1347
1377
|
};
|
|
1348
1378
|
|
|
1349
1379
|
if (override.description !== undefined) next.description = override.description;
|
|
@@ -1371,6 +1401,7 @@ function applyBuiltinOverride(
|
|
|
1371
1401
|
if (override.systemPrompt !== undefined) next.systemPrompt = override.systemPrompt;
|
|
1372
1402
|
if (override.skills !== undefined) { if (override.skills === false) delete next.skills; else next.skills = [...override.skills]; }
|
|
1373
1403
|
if (override.tools !== undefined) applyToolsOverride(next, override.tools);
|
|
1404
|
+
if (override.excludeTools !== undefined) { if (override.excludeTools === false) delete next.excludeTools; else next.excludeTools = [...override.excludeTools]; }
|
|
1374
1405
|
if (override.allowNestedSubagents !== undefined) next.allowNestedSubagents = override.allowNestedSubagents;
|
|
1375
1406
|
if (override.extensions !== undefined) { if (override.extensions === false) delete next.extensions; else next.extensions = [...override.extensions]; }
|
|
1376
1407
|
if (override.subagentOnlyExtensions !== undefined) { if (override.subagentOnlyExtensions === false) delete next.subagentOnlyExtensions; else next.subagentOnlyExtensions = [...override.subagentOnlyExtensions]; }
|
|
@@ -1442,125 +1473,12 @@ function applyBuiltinOverrides(
|
|
|
1442
1473
|
});
|
|
1443
1474
|
}
|
|
1444
1475
|
|
|
1445
|
-
export function agentHasFrontmatterField(agent: AgentConfig, ...fields: string[]): boolean {
|
|
1446
|
-
const frontmatterFields = agentFrontmatterFields.get(agent);
|
|
1447
|
-
return frontmatterFields ? fields.some((field) => frontmatterFields.has(field)) : false;
|
|
1448
|
-
}
|
|
1449
|
-
|
|
1450
1476
|
function applyCustomAgentOverride(
|
|
1451
1477
|
agent: AgentConfig,
|
|
1452
1478
|
override: BuiltinAgentOverrideConfig,
|
|
1453
1479
|
meta: { scope: "user" | "project"; path: string },
|
|
1454
1480
|
): AgentConfig {
|
|
1455
|
-
|
|
1456
|
-
let anyFilled = false;
|
|
1457
|
-
|
|
1458
|
-
const mutable = (): AgentConfig => {
|
|
1459
|
-
next ??= { ...agent };
|
|
1460
|
-
return next;
|
|
1461
|
-
};
|
|
1462
|
-
|
|
1463
|
-
const fill = <K extends keyof AgentConfig>(
|
|
1464
|
-
field: K,
|
|
1465
|
-
frontmatterFields: string[],
|
|
1466
|
-
value: AgentConfig[K],
|
|
1467
|
-
): void => {
|
|
1468
|
-
if (agentHasFrontmatterField(agent, ...frontmatterFields)) return;
|
|
1469
|
-
const target = mutable();
|
|
1470
|
-
if (value === undefined) delete target[field]; else target[field] = value;
|
|
1471
|
-
anyFilled = true;
|
|
1472
|
-
};
|
|
1473
|
-
|
|
1474
|
-
if (override.description !== undefined) {
|
|
1475
|
-
mutable().description = override.description;
|
|
1476
|
-
anyFilled = true;
|
|
1477
|
-
}
|
|
1478
|
-
if (override.output !== undefined) {
|
|
1479
|
-
fill("output", ["output"], override.output === false ? undefined : override.output);
|
|
1480
|
-
}
|
|
1481
|
-
if (override.outputMode !== undefined) {
|
|
1482
|
-
fill("outputMode", ["outputMode"], override.outputMode);
|
|
1483
|
-
}
|
|
1484
|
-
if (override.defaultReads !== undefined) {
|
|
1485
|
-
fill("defaultReads", ["defaultReads"], override.defaultReads === false ? undefined : [...override.defaultReads]);
|
|
1486
|
-
}
|
|
1487
|
-
if (override.model !== undefined && !agentHasFrontmatterField(agent, "model")) {
|
|
1488
|
-
const target = mutable();
|
|
1489
|
-
if (override.model === false) delete target.model; else target.model = override.model;
|
|
1490
|
-
delete target.modelSource;
|
|
1491
|
-
anyFilled = true;
|
|
1492
|
-
}
|
|
1493
|
-
if (override.defaultProvider !== undefined) {
|
|
1494
|
-
fill("modelProvider", ["modelProvider", "defaultProvider"], override.defaultProvider === false ? undefined : override.defaultProvider);
|
|
1495
|
-
}
|
|
1496
|
-
if (override.fallbackModels !== undefined) {
|
|
1497
|
-
fill(
|
|
1498
|
-
"fallbackModels",
|
|
1499
|
-
["fallbackModels"],
|
|
1500
|
-
override.fallbackModels === false ? undefined : [...override.fallbackModels],
|
|
1501
|
-
);
|
|
1502
|
-
}
|
|
1503
|
-
if (override.fast !== undefined) {
|
|
1504
|
-
fill("fast", ["fast"], override.fast);
|
|
1505
|
-
}
|
|
1506
|
-
if (override.thinking !== undefined) {
|
|
1507
|
-
fill("thinking", ["thinking"], override.thinking === false ? undefined : override.thinking);
|
|
1508
|
-
}
|
|
1509
|
-
if (override.systemPromptMode !== undefined) {
|
|
1510
|
-
fill("systemPromptMode", ["systemPromptMode"], override.systemPromptMode);
|
|
1511
|
-
}
|
|
1512
|
-
if (override.inheritProjectContext !== undefined) {
|
|
1513
|
-
fill("inheritProjectContext", ["inheritProjectContext"], override.inheritProjectContext);
|
|
1514
|
-
}
|
|
1515
|
-
if (override.inheritGlobalContext !== undefined) {
|
|
1516
|
-
fill("inheritGlobalContext", ["inheritGlobalContext"], override.inheritGlobalContext);
|
|
1517
|
-
}
|
|
1518
|
-
if (override.inheritSkills !== undefined) {
|
|
1519
|
-
fill("inheritSkills", ["inheritSkills"], override.inheritSkills);
|
|
1520
|
-
}
|
|
1521
|
-
if (override.defaultContext !== undefined) {
|
|
1522
|
-
fill("defaultContext", ["defaultContext"], override.defaultContext === false ? undefined : override.defaultContext);
|
|
1523
|
-
}
|
|
1524
|
-
if (override.acceptanceRole !== undefined) {
|
|
1525
|
-
fill("acceptanceRole", ["acceptanceRole"], override.acceptanceRole === false ? undefined : override.acceptanceRole);
|
|
1526
|
-
}
|
|
1527
|
-
if (override.disabled !== undefined) {
|
|
1528
|
-
// Custom agent files cannot set `disabled`, so project overrides replace user overrides.
|
|
1529
|
-
mutable().disabled = override.disabled;
|
|
1530
|
-
anyFilled = true;
|
|
1531
|
-
}
|
|
1532
|
-
if (override.skills !== undefined) {
|
|
1533
|
-
fill("skills", ["skill", "skills"], override.skills === false ? undefined : [...override.skills]);
|
|
1534
|
-
}
|
|
1535
|
-
if (override.tools !== undefined && !agentHasFrontmatterField(agent, "tools")) {
|
|
1536
|
-
applyToolsOverride(mutable(), override.tools);
|
|
1537
|
-
anyFilled = true;
|
|
1538
|
-
}
|
|
1539
|
-
if (override.allowNestedSubagents !== undefined) {
|
|
1540
|
-
fill("allowNestedSubagents", ["allowNestedSubagents"], override.allowNestedSubagents);
|
|
1541
|
-
}
|
|
1542
|
-
if (override.extensions !== undefined) {
|
|
1543
|
-
fill("extensions", ["extensions"], override.extensions === false ? undefined : [...override.extensions]);
|
|
1544
|
-
}
|
|
1545
|
-
if (override.subagentOnlyExtensions !== undefined) {
|
|
1546
|
-
fill(
|
|
1547
|
-
"subagentOnlyExtensions",
|
|
1548
|
-
["subagentOnlyExtensions"],
|
|
1549
|
-
override.subagentOnlyExtensions === false ? undefined : [...override.subagentOnlyExtensions],
|
|
1550
|
-
);
|
|
1551
|
-
}
|
|
1552
|
-
if (override.mutationTools !== undefined) {
|
|
1553
|
-
fill("mutationTools", ["mutationTools"], override.mutationTools === false ? undefined : [...override.mutationTools]);
|
|
1554
|
-
}
|
|
1555
|
-
if (override.completionGuard !== undefined) {
|
|
1556
|
-
fill("completionGuard", ["completionGuard"], override.completionGuard);
|
|
1557
|
-
}
|
|
1558
|
-
if (override.toolBudget !== undefined) {
|
|
1559
|
-
fill("toolBudget", ["toolBudget"], override.toolBudget === false ? undefined : override.toolBudget);
|
|
1560
|
-
}
|
|
1561
|
-
|
|
1562
|
-
if (!anyFilled || !next) return agent;
|
|
1563
|
-
next.override = { ...meta, base: agent.override?.base ?? cloneOverrideBase(agent) };
|
|
1481
|
+
const next = applyBuiltinOverride(agent, override, meta);
|
|
1564
1482
|
const frontmatterFields = agentFrontmatterFields.get(agent);
|
|
1565
1483
|
if (frontmatterFields) agentFrontmatterFields.set(next, frontmatterFields);
|
|
1566
1484
|
return next;
|
|
@@ -1591,7 +1509,7 @@ function applyCustomAgentOverrides(
|
|
|
1591
1509
|
|
|
1592
1510
|
export function buildBuiltinOverrideConfig(
|
|
1593
1511
|
base: BuiltinAgentOverrideBase,
|
|
1594
|
-
draft: Pick<AgentConfig, "model" | "modelProvider" | "fallbackModels" | "fast" | "thinking" | "systemPromptMode" | "inheritProjectContext" | "inheritGlobalContext" | "inheritSkills" | "defaultContext" | "acceptanceRole" | "disabled" | "systemPrompt" | "skills" | "tools" | "allowNestedSubagents" | "mcpDirectTools" | "extensions" | "subagentOnlyExtensions" | "mutationTools" | "completionGuard" | "toolBudget"> & Partial<Pick<AgentConfig, "description" | "output" | "outputMode" | "defaultReads">>,
|
|
1512
|
+
draft: Pick<AgentConfig, "model" | "modelProvider" | "fallbackModels" | "fast" | "thinking" | "systemPromptMode" | "inheritProjectContext" | "inheritGlobalContext" | "inheritSkills" | "defaultContext" | "acceptanceRole" | "disabled" | "systemPrompt" | "skills" | "tools" | "allowNestedSubagents" | "mcpDirectTools" | "extensions" | "subagentOnlyExtensions" | "mutationTools" | "completionGuard" | "toolBudget"> & Partial<Pick<AgentConfig, "description" | "output" | "outputMode" | "defaultReads" | "excludeTools">>,
|
|
1595
1513
|
): BuiltinAgentOverrideConfig | undefined {
|
|
1596
1514
|
const override: BuiltinAgentOverrideConfig = {};
|
|
1597
1515
|
|
|
@@ -1620,6 +1538,7 @@ export function buildBuiltinOverrideConfig(
|
|
|
1620
1538
|
const baseTools = joinToolList(base);
|
|
1621
1539
|
const draftTools = joinToolList(draft);
|
|
1622
1540
|
if (!arraysEqual(draftTools, baseTools)) override.tools = draftTools ? [...draftTools] : false;
|
|
1541
|
+
if (!arraysEqual(draft.excludeTools, base.excludeTools)) override.excludeTools = draft.excludeTools ? [...draft.excludeTools] : false;
|
|
1623
1542
|
if (draft.allowNestedSubagents !== base.allowNestedSubagents) override.allowNestedSubagents = draft.allowNestedSubagents === true;
|
|
1624
1543
|
if (!arraysEqual(draft.extensions, base.extensions)) override.extensions = draft.extensions ? [...draft.extensions] : false;
|
|
1625
1544
|
if (!arraysEqual(draft.subagentOnlyExtensions, base.subagentOnlyExtensions)) {
|
|
@@ -1984,7 +1903,7 @@ function parseAgentRunnerFrontmatter(raw: string | undefined, agentName: string)
|
|
|
1984
1903
|
|
|
1985
1904
|
function validateExternalRunnerProfile(frontmatter: Record<string, string>, agentName: string, runner: AgentRunnerConfig | undefined): void {
|
|
1986
1905
|
if (runner?.type !== "external-cli" && runner?.type !== "external-job") return;
|
|
1987
|
-
const unsupported = ["tools", "allowNestedSubagents", "model", "fallbackModels", "thinking", "extensions", "subagentOnlyExtensions", "mutationTools", "maxSubagentDepth", "completionGuard", "skills", "skill", "skillPath", "toolBudget", "permission", "permissions"]
|
|
1906
|
+
const unsupported = ["tools", "excludeTools", "allowNestedSubagents", "model", "fallbackModels", "thinking", "extensions", "subagentOnlyExtensions", "mutationTools", "maxSubagentDepth", "completionGuard", "skills", "skill", "skillPath", "toolBudget", "permission", "permissions"]
|
|
1988
1907
|
.filter((field) => frontmatter[field] !== undefined);
|
|
1989
1908
|
if (unsupported.length > 0) {
|
|
1990
1909
|
throw new Error(`Agent '${agentName}' uses runner.type='${runner.type}' and declares unsupported Pi-only fields: ${unsupported.join(", ")}.`);
|
|
@@ -2066,6 +1985,7 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
|
|
|
2066
1985
|
const parsedTools = splitToolList(rawTools);
|
|
2067
1986
|
const tools = parsedTools.tools ?? [];
|
|
2068
1987
|
const mcpDirectTools = parsedTools.mcpDirectTools ?? [];
|
|
1988
|
+
const excludeTools = parseFrontmatterList(frontmatter.excludeTools);
|
|
2069
1989
|
const defaultReads = parseFrontmatterList(frontmatter.defaultReads);
|
|
2070
1990
|
const aliases = normalizeAgentAliases(parseFrontmatterList(frontmatter.aliases ?? frontmatter.alias), runtimeName);
|
|
2071
1991
|
const profileError = validateCodeOwnedProfileRunner({ name: runtimeName, localName, aliases, runner });
|
|
@@ -2187,6 +2107,7 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
|
|
|
2187
2107
|
description: frontmatter.description,
|
|
2188
2108
|
...(aliases !== undefined ? { aliases } : {}),
|
|
2189
2109
|
...(rawTools !== undefined ? { tools } : {}),
|
|
2110
|
+
...(excludeTools !== undefined ? { excludeTools } : {}),
|
|
2190
2111
|
...(allowNestedSubagents !== undefined ? { allowNestedSubagents } : {}),
|
|
2191
2112
|
...(mcpDirectTools.length > 0 ? { mcpDirectTools } : {}),
|
|
2192
2113
|
...(frontmatter.model !== undefined ? { model: frontmatter.model } : {}),
|
|
@@ -2328,6 +2249,61 @@ function extraUserAgentDirs(): string[] {
|
|
|
2328
2249
|
.filter((dir) => dir.length > 0);
|
|
2329
2250
|
}
|
|
2330
2251
|
|
|
2252
|
+
interface AgentScanDirs {
|
|
2253
|
+
dirs: string[];
|
|
2254
|
+
watchPaths: string[];
|
|
2255
|
+
}
|
|
2256
|
+
|
|
2257
|
+
function readConfiguredAgentScanDirs(filePath: string | null): string[] {
|
|
2258
|
+
if (!filePath) return [];
|
|
2259
|
+
try {
|
|
2260
|
+
const settings = readSettingsFileStrict(filePath);
|
|
2261
|
+
const subagents = settings.subagents;
|
|
2262
|
+
if (!subagents || typeof subagents !== "object" || Array.isArray(subagents)) return [];
|
|
2263
|
+
const dirs = (subagents as { agentScanDirs?: unknown }).agentScanDirs;
|
|
2264
|
+
return Array.isArray(dirs) ? dirs.filter((dir): dir is string => typeof dir === "string" && dir.trim().length > 0) : [];
|
|
2265
|
+
} catch {
|
|
2266
|
+
return [];
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
function expandAgentScanDirPattern(pattern: string): AgentScanDirs {
|
|
2271
|
+
const expanded = expandHomePath(pattern.trim()).replace(/[\\/]+/g, path.sep);
|
|
2272
|
+
if (!expanded) return { dirs: [], watchPaths: [] };
|
|
2273
|
+
const wildcardMatches = [...expanded.matchAll(/\*/g)];
|
|
2274
|
+
if (wildcardMatches.length === 0) {
|
|
2275
|
+
const dir = path.resolve(expanded);
|
|
2276
|
+
return { dirs: fs.existsSync(dir) ? [dir] : [], watchPaths: [dir] };
|
|
2277
|
+
}
|
|
2278
|
+
const parts = expanded.split(path.sep);
|
|
2279
|
+
const wildcardIndex = parts.findIndex((part) => part.includes("*"));
|
|
2280
|
+
if (wildcardMatches.length !== 1 || wildcardIndex === -1 || parts[wildcardIndex] !== "*") return { dirs: [], watchPaths: [] };
|
|
2281
|
+
const base = path.resolve(parts.slice(0, wildcardIndex).join(path.sep) || path.sep);
|
|
2282
|
+
const rest = parts.slice(wildcardIndex + 1);
|
|
2283
|
+
let entries: fs.Dirent[];
|
|
2284
|
+
try {
|
|
2285
|
+
entries = fs.readdirSync(base, { withFileTypes: true });
|
|
2286
|
+
} catch {
|
|
2287
|
+
return { dirs: [], watchPaths: [base] };
|
|
2288
|
+
}
|
|
2289
|
+
const candidateDirs = entries.filter((entry) => entry.isDirectory()).map((entry) => path.join(base, entry.name, ...rest));
|
|
2290
|
+
return {
|
|
2291
|
+
dirs: candidateDirs.filter((dir) => fs.existsSync(dir)),
|
|
2292
|
+
watchPaths: [base, ...candidateDirs],
|
|
2293
|
+
};
|
|
2294
|
+
}
|
|
2295
|
+
|
|
2296
|
+
function settingsAgentScanDirs(entries: string[]): AgentScanDirs {
|
|
2297
|
+
const dirs = new Set<string>();
|
|
2298
|
+
const watchPaths = new Set<string>();
|
|
2299
|
+
for (const entry of entries) {
|
|
2300
|
+
const expanded = expandAgentScanDirPattern(entry);
|
|
2301
|
+
for (const dir of expanded.dirs) dirs.add(dir);
|
|
2302
|
+
for (const watchPath of expanded.watchPaths) watchPaths.add(watchPath);
|
|
2303
|
+
}
|
|
2304
|
+
return { dirs: [...dirs], watchPaths: [...watchPaths] };
|
|
2305
|
+
}
|
|
2306
|
+
|
|
2331
2307
|
export interface AgentDiscoveryAllResult {
|
|
2332
2308
|
builtin: AgentConfig[];
|
|
2333
2309
|
package: AgentConfig[];
|
|
@@ -2488,16 +2464,18 @@ function buildAgentDiscoverySources(cwd: string, preferredModelProvider?: string
|
|
|
2488
2464
|
const userSettingsPath = getUserAgentSettingsPath();
|
|
2489
2465
|
const projectSettingsPath = getProjectAgentSettingsPath(effectiveCwd);
|
|
2490
2466
|
const packageSubagentPaths = collectPackageSubagentPaths(effectiveCwd);
|
|
2467
|
+
const userScanDirs = settingsAgentScanDirs(readConfiguredAgentScanDirs(userSettingsPath));
|
|
2468
|
+
const projectScanDirs = settingsAgentScanDirs(readConfiguredAgentScanDirs(projectSettingsPath));
|
|
2491
2469
|
|
|
2492
2470
|
const builtinLoaded = loadAgentsFromDefinitionFiles(BUILTIN_AGENT_DEFINITION_FILES, "builtin");
|
|
2493
|
-
const userLoaded = [...extraUserAgentDirs(), userDirOld, userDirNew].map((dir, discoveryPriority): LoadedAgentDirectory => {
|
|
2471
|
+
const userLoaded = [...extraUserAgentDirs(), ...userScanDirs.dirs, userDirOld, userDirNew].map((dir, discoveryPriority): LoadedAgentDirectory => {
|
|
2494
2472
|
const inspection = inspectAgentDefinitionDirectory(dir);
|
|
2495
2473
|
return { dir, inspection, loaded: loadAgentsFromDir(dir, "user", discoveryPriority, undefined, inspection) };
|
|
2496
2474
|
});
|
|
2497
2475
|
const projectInspections = new Map(projectCandidateDirs.map((dir) => [dir, inspectAgentDefinitionDirectory(dir)]));
|
|
2498
|
-
const projectLoaded = projectAgentDirs.map((dir): LoadedAgentDirectory => {
|
|
2499
|
-
const inspection = projectInspections.get(dir)
|
|
2500
|
-
return { dir, inspection, loaded: loadAgentsFromDir(dir, "project", dir === projectAgentsDir ? 1 :
|
|
2476
|
+
const projectLoaded = [...projectScanDirs.dirs, ...projectAgentDirs].map((dir, discoveryPriority): LoadedAgentDirectory => {
|
|
2477
|
+
const inspection = projectInspections.get(dir) ?? inspectAgentDefinitionDirectory(dir);
|
|
2478
|
+
return { dir, inspection, loaded: loadAgentsFromDir(dir, "project", dir === projectAgentsDir ? 1 : discoveryPriority, undefined, inspection) };
|
|
2501
2479
|
});
|
|
2502
2480
|
const packageLoaded = packageSubagentPaths.agents.map((entry, index): LoadedAgentDirectory => {
|
|
2503
2481
|
const inspection = inspectAgentDefinitionDirectory(entry.dir);
|
|
@@ -2509,6 +2487,8 @@ function buildAgentDiscoverySources(cwd: string, preferredModelProvider?: string
|
|
|
2509
2487
|
...(projectSettingsPath ? [projectSettingsPath] : []),
|
|
2510
2488
|
...projectDiscoveryWatchPaths(effectiveCwd),
|
|
2511
2489
|
...findProjectRootCandidates(effectiveCwd).map((root) => path.join(getProjectConfigDir(root), "settings.json")),
|
|
2490
|
+
...userScanDirs.watchPaths,
|
|
2491
|
+
...projectScanDirs.watchPaths,
|
|
2512
2492
|
...packageSubagentPaths.watchPaths,
|
|
2513
2493
|
]);
|
|
2514
2494
|
for (const directory of userLoaded) addDirectoryWatchPaths(watchPaths, directory.dir, directory.inspection.files, inspectedAgentDefinitionDirectories(directory.inspection, directory.dir));
|
|
@@ -2516,6 +2496,7 @@ function buildAgentDiscoverySources(cwd: string, preferredModelProvider?: string
|
|
|
2516
2496
|
const inspection = projectInspections.get(dir);
|
|
2517
2497
|
addDirectoryWatchPaths(watchPaths, dir, inspection?.files ?? [], inspection ? inspectedAgentDefinitionDirectories(inspection, dir) : []);
|
|
2518
2498
|
}
|
|
2499
|
+
for (const directory of projectLoaded) addDirectoryWatchPaths(watchPaths, directory.dir, directory.inspection.files, inspectedAgentDefinitionDirectories(directory.inspection, directory.dir));
|
|
2519
2500
|
for (const directory of packageLoaded) addDirectoryWatchPaths(watchPaths, directory.dir, directory.inspection.files, inspectedAgentDefinitionDirectories(directory.inspection, directory.dir));
|
|
2520
2501
|
|
|
2521
2502
|
const userDir = process.env.PI_CODING_AGENT_DIR ? userDirOld : fs.existsSync(userDirNew) ? userDirNew : userDirOld;
|
|
@@ -2651,7 +2632,16 @@ function configuredAgentsForScope(sources: AgentDiscoverySources, scope: AgentSc
|
|
|
2651
2632
|
function discoveryDirectories(sources: AgentDiscoverySources, scope: AgentScope): AgentDefinitionDirectoryReport[] {
|
|
2652
2633
|
const directories: AgentDefinitionDirectoryReport[] = [reportAgentDefinitionDirectory("builtin", BUILTIN_AGENTS_DIR, BUILTIN_AGENT_DEFINITION_INSPECTION)];
|
|
2653
2634
|
if (scope !== "project") for (const directory of sources.userLoaded) directories.push(reportAgentDefinitionDirectory("user", directory.dir, directory.inspection));
|
|
2654
|
-
if (scope !== "user")
|
|
2635
|
+
if (scope !== "user") {
|
|
2636
|
+
const reported = new Set<string>();
|
|
2637
|
+
for (const dir of sources.projectCandidateDirs) {
|
|
2638
|
+
reported.add(path.resolve(dir));
|
|
2639
|
+
directories.push(reportAgentDefinitionDirectory("project", dir, sources.projectInspections.get(dir)!));
|
|
2640
|
+
}
|
|
2641
|
+
for (const directory of sources.projectLoaded) {
|
|
2642
|
+
if (!reported.has(path.resolve(directory.dir))) directories.push(reportAgentDefinitionDirectory("project", directory.dir, directory.inspection));
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2655
2645
|
for (const directory of sources.packageLoaded) {
|
|
2656
2646
|
if (directory.packageEntry && packageEntryIncluded(scope, directory.packageEntry.scope)) directories.push(reportAgentDefinitionDirectory("package", directory.dir, directory.inspection));
|
|
2657
2647
|
}
|
|
@@ -2760,7 +2750,9 @@ function discoverAgentsUncached(cwd: string, scope: AgentScope, preferredModelPr
|
|
|
2760
2750
|
const directories: AgentDefinitionDirectoryReport[] = [reportAgentDefinitionDirectory("builtin", BUILTIN_AGENTS_DIR, BUILTIN_AGENT_DEFINITION_INSPECTION)];
|
|
2761
2751
|
const builtinLoaded = loadAgentsFromDefinitionFiles(BUILTIN_AGENT_DEFINITION_FILES, "builtin");
|
|
2762
2752
|
const builtinAgents = applyBuiltinOverrides(applySubagentDefaults(builtinLoaded.agents, defaultModel, defaultProvider, defaultThinking, defaultExtensions), userSettings, projectSettings, userSettingsPath, projectSettingsPath);
|
|
2763
|
-
const
|
|
2753
|
+
const userScanDirs = settingsAgentScanDirs(userSettings.agentScanDirs ?? []);
|
|
2754
|
+
const projectScanDirs = settingsAgentScanDirs(projectSettings.agentScanDirs ?? []);
|
|
2755
|
+
const userLoaded = scope === "project" ? [] : [...extraUserAgentDirs(), ...userScanDirs.dirs, userDirOld, userDirNew].map((dir, discoveryPriority) => {
|
|
2764
2756
|
const inspection = inspectAgentDefinitionDirectory(dir);
|
|
2765
2757
|
directories.push(reportAgentDefinitionDirectory("user", dir, inspection));
|
|
2766
2758
|
return loadAgentsFromDir(dir, "user", discoveryPriority, undefined, inspection);
|
|
@@ -2768,7 +2760,11 @@ function discoverAgentsUncached(cwd: string, scope: AgentScope, preferredModelPr
|
|
|
2768
2760
|
const userAgents = applyCustomAgentOverrides(applySubagentDefaults(userLoaded.flatMap((loaded) => loaded.agents), defaultModel, defaultProvider, defaultThinking, defaultExtensions), userSettings, projectSettings, userSettingsPath, projectSettingsPath);
|
|
2769
2761
|
const projectInspections = scope === "user" ? new Map<string, AgentDefinitionInspection>() : new Map(projectCandidateDirs.map((dir) => [dir, inspectAgentDefinitionDirectory(dir)]));
|
|
2770
2762
|
if (scope !== "user") for (const dir of projectCandidateDirs) directories.push(reportAgentDefinitionDirectory("project", dir, projectInspections.get(dir)!));
|
|
2771
|
-
const projectLoaded = scope === "user" ? [] : projectAgentDirs.map((dir) =>
|
|
2763
|
+
const projectLoaded = scope === "user" ? [] : [...projectScanDirs.dirs, ...projectAgentDirs].map((dir, discoveryPriority) => {
|
|
2764
|
+
const inspection = projectInspections.get(dir) ?? inspectAgentDefinitionDirectory(dir);
|
|
2765
|
+
if (!projectInspections.has(dir)) directories.push(reportAgentDefinitionDirectory("project", dir, inspection));
|
|
2766
|
+
return loadAgentsFromDir(dir, "project", dir === projectAgentsDir ? 1 : discoveryPriority, undefined, inspection);
|
|
2767
|
+
});
|
|
2772
2768
|
const projectAgents = applyCustomAgentOverrides(applySubagentDefaults(projectLoaded.flatMap((loaded) => loaded.agents), defaultModel, defaultProvider, defaultThinking, defaultExtensions), userSettings, projectSettings, userSettingsPath, projectSettingsPath);
|
|
2773
2769
|
const packageLoaded = packageSubagentPaths.agents.map((entry, index) => {
|
|
2774
2770
|
const inspection = inspectAgentDefinitionDirectory(entry.dir);
|
|
@@ -20,6 +20,7 @@ export interface RuntimeAgentDefinition {
|
|
|
20
20
|
systemPrompt: string;
|
|
21
21
|
aliases?: readonly string[];
|
|
22
22
|
tools?: readonly string[];
|
|
23
|
+
excludeTools?: readonly string[];
|
|
23
24
|
allowNestedSubagents?: boolean;
|
|
24
25
|
mcpDirectTools?: readonly string[];
|
|
25
26
|
model?: string;
|
|
@@ -198,7 +199,7 @@ function validateDefinition(value: unknown): RuntimeAgentDefinition {
|
|
|
198
199
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Runtime agent definition must be an object.");
|
|
199
200
|
const definition = value as Record<string, unknown>;
|
|
200
201
|
const supported = new Set([
|
|
201
|
-
"description", "systemPrompt", "aliases", "tools", "allowNestedSubagents", "mcpDirectTools", "model", "fallbackModels", "thinking",
|
|
202
|
+
"description", "systemPrompt", "aliases", "tools", "excludeTools", "allowNestedSubagents", "mcpDirectTools", "model", "fallbackModels", "thinking",
|
|
202
203
|
"systemPromptMode", "inheritProjectContext", "inheritGlobalContext", "inheritSkills", "defaultContext", "defaultAsync", "defaultTimeoutMs",
|
|
203
204
|
"defaultToolTimeoutMs", "defaultAcceptance", "acceptanceRole", "runner", "skills", "skillPath",
|
|
204
205
|
"extensions", "subagentOnlyExtensions", "mutationTools", "output", "outputMode", "defaultReads", "defaultProgress", "interactive",
|
|
@@ -218,6 +219,7 @@ function validateDefinition(value: unknown): RuntimeAgentDefinition {
|
|
|
218
219
|
if (outputMode !== undefined && outputMode !== "inline" && outputMode !== "file-only") throw new Error("Runtime agent definition outputMode must be 'inline' or 'file-only'.");
|
|
219
220
|
const aliases = validateStringList(definition.aliases, "Runtime agent definition aliases");
|
|
220
221
|
const tools = validateStringList(definition.tools, "Runtime agent definition tools");
|
|
222
|
+
const excludeTools = validateStringList(definition.excludeTools, "Runtime agent definition excludeTools");
|
|
221
223
|
const allowNestedSubagents = validateBoolean(definition.allowNestedSubagents, "Runtime agent definition allowNestedSubagents");
|
|
222
224
|
const mcpDirectTools = validateStringList(definition.mcpDirectTools, "Runtime agent definition mcpDirectTools");
|
|
223
225
|
const model = validateOptionalString(definition.model, "Runtime agent definition model");
|
|
@@ -248,6 +250,7 @@ function validateDefinition(value: unknown): RuntimeAgentDefinition {
|
|
|
248
250
|
systemPrompt: validateString(definition.systemPrompt, "Runtime agent definition systemPrompt", MAX_SYSTEM_PROMPT_LENGTH),
|
|
249
251
|
...(aliases ? { aliases } : {}),
|
|
250
252
|
...(tools ? { tools } : {}),
|
|
253
|
+
...(excludeTools ? { excludeTools } : {}),
|
|
251
254
|
...(allowNestedSubagents !== undefined ? { allowNestedSubagents } : {}),
|
|
252
255
|
...(mcpDirectTools ? { mcpDirectTools } : {}),
|
|
253
256
|
...(model ? { model } : {}),
|
|
@@ -327,6 +330,7 @@ function toAgentConfig(name: string, definition: RuntimeAgentDefinition): AgentC
|
|
|
327
330
|
...(aliases ? { aliases } : {}),
|
|
328
331
|
...(definition.runner !== undefined ? { runner: definition.runner } : {}),
|
|
329
332
|
...(definition.tools !== undefined ? { tools: [...definition.tools] } : {}),
|
|
333
|
+
...(definition.excludeTools !== undefined ? { excludeTools: [...definition.excludeTools] } : {}),
|
|
330
334
|
...(definition.allowNestedSubagents !== undefined ? { allowNestedSubagents: definition.allowNestedSubagents } : {}),
|
|
331
335
|
...(definition.mcpDirectTools !== undefined ? { mcpDirectTools: [...definition.mcpDirectTools] } : {}),
|
|
332
336
|
...(definition.model !== undefined ? { model: definition.model } : {}),
|
package/src/api/preflight.ts
CHANGED
|
@@ -111,6 +111,7 @@ export interface SubagentLaunchContractSkills {
|
|
|
111
111
|
export interface SubagentLaunchContractTools {
|
|
112
112
|
requestedBuiltin: string[];
|
|
113
113
|
declaredBuiltin: string[];
|
|
114
|
+
excludeTools?: string[];
|
|
114
115
|
effectiveAllowlist: string[];
|
|
115
116
|
explicitAllowlist: boolean;
|
|
116
117
|
requiredChildTools: string[];
|
|
@@ -356,6 +357,7 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
|
|
|
356
357
|
try {
|
|
357
358
|
toolPlan = resolvePiLaunchToolPlan({
|
|
358
359
|
tools: agent.tools,
|
|
360
|
+
excludeTools: agent.excludeTools,
|
|
359
361
|
allowNestedSubagents: agent.allowNestedSubagents,
|
|
360
362
|
extensions: agent.extensions,
|
|
361
363
|
subagentOnlyExtensions: agent.subagentOnlyExtensions,
|
|
@@ -435,6 +437,7 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
|
|
|
435
437
|
tools: {
|
|
436
438
|
requestedBuiltin: toolPlan.requestedBuiltinTools,
|
|
437
439
|
declaredBuiltin: toolPlan.declaredBuiltinTools,
|
|
440
|
+
...(toolPlan.excludeTools.length > 0 ? { excludeTools: toolPlan.excludeTools } : {}),
|
|
438
441
|
effectiveAllowlist: toolPlan.effectiveToolAllowlist,
|
|
439
442
|
explicitAllowlist: toolPlan.explicitToolAllowlist,
|
|
440
443
|
requiredChildTools: toolPlan.requiredChildTools,
|
|
@@ -485,6 +488,7 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
|
|
|
485
488
|
inheritSkills: agent.inheritSkills,
|
|
486
489
|
skills: requestedSkills,
|
|
487
490
|
tools: toolPlan.effectiveToolAllowlist,
|
|
491
|
+
...(toolPlan.excludeTools.length > 0 ? { excludeTools: toolPlan.excludeTools } : {}),
|
|
488
492
|
extensions: toolPlan.extensionArgs,
|
|
489
493
|
mcpDirectTools: toolPlan.effectiveMcpTools,
|
|
490
494
|
...(outputPath ? { outputPath } : {}),
|
package/src/api/shared-types.ts
CHANGED
package/src/extension/config.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { getAgentDir } from "../shared/utils.ts";
|
|
|
9
9
|
import { DEFAULT_MODEL_EXCLUSION_TTL_MS, MAX_MODEL_EXCLUSION_TTL_MS, setDefaultTTL } from "../runs/shared/model-exclusions.ts";
|
|
10
10
|
import { validatePermissionConfig } from "../runs/shared/permissions.ts";
|
|
11
11
|
import { MAX_ABANDONED_SLOT_RELEASE_AFTER_MS, MIN_ABANDONED_SLOT_RELEASE_AFTER_MS } from "../runs/background/active-async-capacity.ts";
|
|
12
|
+
import { normalizeWorktreeBranchPrefix } from "../runs/shared/worktree.ts";
|
|
12
13
|
|
|
13
14
|
const ARTIFACT_DIR_PREFERENCES = new Set<ArtifactDirPreference>(["project", "session", "temp"]);
|
|
14
15
|
const FLEET_KEYBINDING_ACTION_SET = new Set<string>(FLEET_KEYBINDING_ACTIONS);
|
|
@@ -138,6 +139,16 @@ function validateMainWindowRendererConfig(value: unknown): void {
|
|
|
138
139
|
}
|
|
139
140
|
|
|
140
141
|
function validateConfig(config: Record<string, unknown>): void {
|
|
142
|
+
if (config.worktree !== undefined && typeof config.worktree !== "boolean") {
|
|
143
|
+
throw new Error("config.worktree must be a boolean");
|
|
144
|
+
}
|
|
145
|
+
if (config.worktreeProvider !== undefined && config.worktreeProvider !== "auto" && config.worktreeProvider !== "native" && config.worktreeProvider !== "worktrunk") {
|
|
146
|
+
throw new Error('config.worktreeProvider must be "auto", "native", or "worktrunk"');
|
|
147
|
+
}
|
|
148
|
+
if (config.worktreeBranchPrefix !== undefined) {
|
|
149
|
+
if (typeof config.worktreeBranchPrefix !== "string") throw new Error("config.worktreeBranchPrefix must be a string");
|
|
150
|
+
normalizeWorktreeBranchPrefix(config.worktreeBranchPrefix);
|
|
151
|
+
}
|
|
141
152
|
if (config.defaultSubagentContext !== undefined && config.defaultSubagentContext !== "fresh" && config.defaultSubagentContext !== "fork") {
|
|
142
153
|
throw new Error('config.defaultSubagentContext must be "fresh" or "fork"');
|
|
143
154
|
}
|
|
@@ -229,6 +240,15 @@ export function loadConfig(): ExtensionConfig {
|
|
|
229
240
|
return readConfigForUpdate(configPath);
|
|
230
241
|
} catch (error) {
|
|
231
242
|
if (error instanceof PrunedForkConfigError) throw error;
|
|
243
|
+
// An explicitly requested worktree provider/prefix must not be silently
|
|
244
|
+
// discarded and replaced by the built-in defaults after validation fails.
|
|
245
|
+
try {
|
|
246
|
+
const raw = JSON.parse(fs.readFileSync(configPath, "utf-8")) as unknown;
|
|
247
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)
|
|
248
|
+
&& (Object.hasOwn(raw, "worktreeProvider") || Object.hasOwn(raw, "worktreeBranchPrefix"))) throw error;
|
|
249
|
+
} catch (readError) {
|
|
250
|
+
if (readError === error) throw error;
|
|
251
|
+
}
|
|
232
252
|
console.error(`Failed to load subagent config from '${configPath}':`, error);
|
|
233
253
|
}
|
|
234
254
|
return {};
|
package/src/extension/schemas.ts
CHANGED
|
@@ -86,10 +86,13 @@ const AcceptanceOverride = Type.Unsafe({
|
|
|
86
86
|
deprecated: true,
|
|
87
87
|
description: "Invalid as an explicit policy. Recognized only so preflight can explain that reviewed is an achieved status.",
|
|
88
88
|
},
|
|
89
|
+
{
|
|
90
|
+
type: "string",
|
|
91
|
+
},
|
|
89
92
|
{ type: "boolean", enum: [false] },
|
|
90
93
|
{ type: "object", additionalProperties: true },
|
|
91
94
|
],
|
|
92
|
-
description: `Optional acceptance policy.
|
|
95
|
+
description: `Optional acceptance policy. Prefer an inline JSON object. JSON-encoded object strings are tolerated only during input normalization; invalid strings fail closed. Reviewer/read-only calls, omit acceptance. { level: "checked", evidence: ["commands-run", "changed-files"] }. Supported evidence kinds: ${AcceptanceEvidenceKinds.join(",")}. acceptance.review.required.`,
|
|
93
96
|
});
|
|
94
97
|
|
|
95
98
|
const AgentContractOverride = Type.Object({
|
|
@@ -317,8 +320,9 @@ const SubagentParamProperties = {
|
|
|
317
320
|
thinking: Type.Optional(Type.Unsafe({ anyOf: [{ type: "string" }, { type: "boolean", enum: [false] }], description: "Thinking level for action='watchdog.configure' only (off/minimal/low/medium/high/xhigh/max, inherit, or false for off). Ignored on dispatch; set per-run child thinking with a suffix on the model string, e.g. model: 'provider/id:high'." })),
|
|
318
321
|
at: Type.Optional(Type.String({ description: "One-shot trigger for action='schedule.create': a relative delay such as '+10m' or an ISO timestamp with timezone." })),
|
|
319
322
|
every: Type.Optional(Type.String({ description: "Fixed recurring interval for action='schedule.create', such as '30m', '6h', '2d', or '2w'." })),
|
|
323
|
+
sessionOnly: Type.Optional(Type.Boolean()),
|
|
320
324
|
on: Type.Optional(Type.Unsafe({ anyOf: [{ type: "string" }, { type: "integer" }], description: "Calendar selector reserved for a later schedule slice." })),
|
|
321
|
-
timezone: Type.Optional(Type.String(
|
|
325
|
+
timezone: Type.Optional(Type.String()),
|
|
322
326
|
overlap: Type.Optional(Type.String({ enum: ["skip"], description: "Overlap policy. This slice supports skip only." })),
|
|
323
327
|
catchUp: Type.Optional(Type.String({ enum: ["none", "latest"], description: "Missed occurrence policy for recurring schedules. Defaults to latest." })),
|
|
324
328
|
missionId: Type.Optional(Type.String({ description: "Mission id." })),
|
|
@@ -57,7 +57,7 @@ EXECUTION:
|
|
|
57
57
|
MANAGEMENT / CONTROL (use action; omit execution fields):
|
|
58
58
|
• validate checks workflowScript or workflowScriptPath syntax and statically decidable structure without launching children. list, get, models, guide, children.list, create, update, delete, eject, disable, enable, reset, status, debug.run, doctor, grant-spawn-budget, worktree.discard, worktree.cleanup (plan-only), lane.status, lane.recordMerge, lane.recordSupersession, refine/refine.show/refine.rollback, mission.create/list/show/update/resolve-decision/attach-run/close, inspector.open/status/close, project.open/status/close, and watchdog actions remain available. Use {action:"guide", topic:"overview"} for packaged current-version help; topics are overview, workflows, agents, missions, observability, tool-reference, configuration, models, watchdog, and extension-api.
|
|
59
59
|
• status, interrupt, stop, resume, and steer manage live or persisted runs. Use status view:"fleet" for an overview or view:"transcript" with id and optional index to tail output.
|
|
60
|
-
• Create durable project schedules with { action:"schedule.create", id?, name?, at:"+10m" | ISO, workflowScript:"return runs.run('main', {agent:'worker', task:'...'})" }, or use workflowScriptPath instead. Manage them with schedule.list/show/history/pause/resume/run/run-due/delete. This first slice supports fixed intervals; calendar schedules and schedule mission attachment are deferred.
|
|
60
|
+
• Create durable project schedules with { action:"schedule.create", id?, name?, sessionOnly?:true, at:"+10m" | ISO, workflowScript:"return runs.run('main', {agent:'worker', task:'...'})" }, or use workflowScriptPath instead. With sessionOnly:true, the schedule records the creating session file and only that session can restore or execute it; omitted/false preserves project-wide behavior. Manage them with schedule.list/show/history/pause/resume/run/run-due/delete. This first slice supports fixed intervals; calendar schedules and schedule mission attachment are deferred.
|
|
61
61
|
|
|
62
62
|
${SUBAGENT_SAFETY_GUIDANCE}`;
|
|
63
63
|
|