pi-subagents 0.62.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 +28 -0
- package/docs/agents.md +5 -4
- package/docs/configuration.md +28 -2
- package/docs/models.md +5 -5
- package/docs/observability.md +4 -1
- package/docs/tool-reference.md +2 -2
- package/package.json +1 -1
- 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 +22 -4
- package/src/agents/agents.ts +107 -125
- package/src/api/shared-types.ts +3 -0
- package/src/extension/config.ts +20 -0
- package/src/inspectors/herdr/inspector-runner.ts +19 -13
- package/src/runs/background/active-async-capacity.ts +0 -1
- package/src/runs/background/async-execution.ts +53 -7
- package/src/runs/background/async-resume.ts +3 -0
- package/src/runs/background/async-status.ts +18 -2
- package/src/runs/background/notify.ts +13 -1
- package/src/runs/background/run-status.ts +22 -2
- package/src/runs/background/subagent-runner.ts +30 -2
- package/src/runs/background/wait-completions.ts +13 -0
- package/src/runs/foreground/subagent-executor.ts +35 -3
- package/src/runs/shared/acceptance.ts +15 -9
- package/src/runs/shared/lane-metadata.ts +24 -3
- package/src/runs/shared/parallel-handoff.ts +4 -0
- package/src/runs/shared/pi-args.ts +8 -2
- package/src/runs/shared/task-intent.ts +5 -2
- package/src/runs/shared/worktree.ts +467 -63
- package/src/shared/types.ts +29 -0
- package/src/shared/utils.ts +18 -7
- package/src/slash/subagents-admin.ts +24 -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";
|
|
@@ -113,6 +114,8 @@ interface BuiltinAgentOverrideInfo {
|
|
|
113
114
|
scope: "user" | "project";
|
|
114
115
|
path: string;
|
|
115
116
|
base: BuiltinAgentOverrideBase;
|
|
117
|
+
fields?: string[];
|
|
118
|
+
fieldScopes?: Record<string, Array<"user" | "project">>;
|
|
116
119
|
}
|
|
117
120
|
|
|
118
121
|
export interface AgentModelSourceInfo {
|
|
@@ -184,6 +187,7 @@ type ProjectRootResolution = "nearest" | "git-root";
|
|
|
184
187
|
interface SubagentSettings {
|
|
185
188
|
overrides: Record<string, BuiltinAgentOverrideConfig>;
|
|
186
189
|
providerOverrides: Record<string, Record<string, BuiltinAgentOverrideConfig>>;
|
|
190
|
+
agentScanDirs?: string[];
|
|
187
191
|
defaultModel?: string;
|
|
188
192
|
defaultProvider?: string;
|
|
189
193
|
defaultThinking?: string;
|
|
@@ -1172,6 +1176,14 @@ function readSubagentSettings(filePath: string | null): SubagentSettings {
|
|
|
1172
1176
|
}
|
|
1173
1177
|
defaultExtensions = subagentsObject.defaultExtensions.map((item) => item.trim());
|
|
1174
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
|
+
}
|
|
1175
1187
|
const modelScope = parseModelScopeConfig(subagentsObject.modelScope, { filePath });
|
|
1176
1188
|
|
|
1177
1189
|
const parsed: Record<string, BuiltinAgentOverrideConfig> = {};
|
|
@@ -1186,6 +1198,7 @@ function readSubagentSettings(filePath: string | null): SubagentSettings {
|
|
|
1186
1198
|
...(defaultThinking !== undefined ? { defaultThinking } : {}),
|
|
1187
1199
|
...(maxThinking !== undefined ? { maxThinking } : {}),
|
|
1188
1200
|
...(defaultExtensions !== undefined ? { defaultExtensions } : {}),
|
|
1201
|
+
...(agentScanDirs !== undefined ? { agentScanDirs } : {}),
|
|
1189
1202
|
...(disableBuiltins !== undefined ? { disableBuiltins } : {}),
|
|
1190
1203
|
...(disableThinking !== undefined ? { disableThinking } : {}),
|
|
1191
1204
|
...(modelScope !== undefined ? { modelScope } : {}),
|
|
@@ -1348,9 +1361,19 @@ function applyBuiltinOverride(
|
|
|
1348
1361
|
override: BuiltinAgentOverrideConfig,
|
|
1349
1362
|
meta: { scope: "user" | "project"; path: string },
|
|
1350
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
|
+
}
|
|
1351
1374
|
const next: AgentConfig = {
|
|
1352
1375
|
...agent,
|
|
1353
|
-
override:
|
|
1376
|
+
override: overrideInfo,
|
|
1354
1377
|
};
|
|
1355
1378
|
|
|
1356
1379
|
if (override.description !== undefined) next.description = override.description;
|
|
@@ -1450,128 +1473,12 @@ function applyBuiltinOverrides(
|
|
|
1450
1473
|
});
|
|
1451
1474
|
}
|
|
1452
1475
|
|
|
1453
|
-
export function agentHasFrontmatterField(agent: AgentConfig, ...fields: string[]): boolean {
|
|
1454
|
-
const frontmatterFields = agentFrontmatterFields.get(agent);
|
|
1455
|
-
return frontmatterFields ? fields.some((field) => frontmatterFields.has(field)) : false;
|
|
1456
|
-
}
|
|
1457
|
-
|
|
1458
1476
|
function applyCustomAgentOverride(
|
|
1459
1477
|
agent: AgentConfig,
|
|
1460
1478
|
override: BuiltinAgentOverrideConfig,
|
|
1461
1479
|
meta: { scope: "user" | "project"; path: string },
|
|
1462
1480
|
): AgentConfig {
|
|
1463
|
-
|
|
1464
|
-
let anyFilled = false;
|
|
1465
|
-
|
|
1466
|
-
const mutable = (): AgentConfig => {
|
|
1467
|
-
next ??= { ...agent };
|
|
1468
|
-
return next;
|
|
1469
|
-
};
|
|
1470
|
-
|
|
1471
|
-
const fill = <K extends keyof AgentConfig>(
|
|
1472
|
-
field: K,
|
|
1473
|
-
frontmatterFields: string[],
|
|
1474
|
-
value: AgentConfig[K],
|
|
1475
|
-
): void => {
|
|
1476
|
-
if (agentHasFrontmatterField(agent, ...frontmatterFields)) return;
|
|
1477
|
-
const target = mutable();
|
|
1478
|
-
if (value === undefined) delete target[field]; else target[field] = value;
|
|
1479
|
-
anyFilled = true;
|
|
1480
|
-
};
|
|
1481
|
-
|
|
1482
|
-
if (override.description !== undefined) {
|
|
1483
|
-
mutable().description = override.description;
|
|
1484
|
-
anyFilled = true;
|
|
1485
|
-
}
|
|
1486
|
-
if (override.output !== undefined) {
|
|
1487
|
-
fill("output", ["output"], override.output === false ? undefined : override.output);
|
|
1488
|
-
}
|
|
1489
|
-
if (override.outputMode !== undefined) {
|
|
1490
|
-
fill("outputMode", ["outputMode"], override.outputMode);
|
|
1491
|
-
}
|
|
1492
|
-
if (override.defaultReads !== undefined) {
|
|
1493
|
-
fill("defaultReads", ["defaultReads"], override.defaultReads === false ? undefined : [...override.defaultReads]);
|
|
1494
|
-
}
|
|
1495
|
-
if (override.model !== undefined && !agentHasFrontmatterField(agent, "model")) {
|
|
1496
|
-
const target = mutable();
|
|
1497
|
-
if (override.model === false) delete target.model; else target.model = override.model;
|
|
1498
|
-
delete target.modelSource;
|
|
1499
|
-
anyFilled = true;
|
|
1500
|
-
}
|
|
1501
|
-
if (override.defaultProvider !== undefined) {
|
|
1502
|
-
fill("modelProvider", ["modelProvider", "defaultProvider"], override.defaultProvider === false ? undefined : override.defaultProvider);
|
|
1503
|
-
}
|
|
1504
|
-
if (override.fallbackModels !== undefined) {
|
|
1505
|
-
fill(
|
|
1506
|
-
"fallbackModels",
|
|
1507
|
-
["fallbackModels"],
|
|
1508
|
-
override.fallbackModels === false ? undefined : [...override.fallbackModels],
|
|
1509
|
-
);
|
|
1510
|
-
}
|
|
1511
|
-
if (override.fast !== undefined) {
|
|
1512
|
-
fill("fast", ["fast"], override.fast);
|
|
1513
|
-
}
|
|
1514
|
-
if (override.thinking !== undefined) {
|
|
1515
|
-
fill("thinking", ["thinking"], override.thinking === false ? undefined : override.thinking);
|
|
1516
|
-
}
|
|
1517
|
-
if (override.systemPromptMode !== undefined) {
|
|
1518
|
-
fill("systemPromptMode", ["systemPromptMode"], override.systemPromptMode);
|
|
1519
|
-
}
|
|
1520
|
-
if (override.inheritProjectContext !== undefined) {
|
|
1521
|
-
fill("inheritProjectContext", ["inheritProjectContext"], override.inheritProjectContext);
|
|
1522
|
-
}
|
|
1523
|
-
if (override.inheritGlobalContext !== undefined) {
|
|
1524
|
-
fill("inheritGlobalContext", ["inheritGlobalContext"], override.inheritGlobalContext);
|
|
1525
|
-
}
|
|
1526
|
-
if (override.inheritSkills !== undefined) {
|
|
1527
|
-
fill("inheritSkills", ["inheritSkills"], override.inheritSkills);
|
|
1528
|
-
}
|
|
1529
|
-
if (override.defaultContext !== undefined) {
|
|
1530
|
-
fill("defaultContext", ["defaultContext"], override.defaultContext === false ? undefined : override.defaultContext);
|
|
1531
|
-
}
|
|
1532
|
-
if (override.acceptanceRole !== undefined) {
|
|
1533
|
-
fill("acceptanceRole", ["acceptanceRole"], override.acceptanceRole === false ? undefined : override.acceptanceRole);
|
|
1534
|
-
}
|
|
1535
|
-
if (override.disabled !== undefined) {
|
|
1536
|
-
// Custom agent files cannot set `disabled`, so project overrides replace user overrides.
|
|
1537
|
-
mutable().disabled = override.disabled;
|
|
1538
|
-
anyFilled = true;
|
|
1539
|
-
}
|
|
1540
|
-
if (override.skills !== undefined) {
|
|
1541
|
-
fill("skills", ["skill", "skills"], override.skills === false ? undefined : [...override.skills]);
|
|
1542
|
-
}
|
|
1543
|
-
if (override.tools !== undefined && !agentHasFrontmatterField(agent, "tools")) {
|
|
1544
|
-
applyToolsOverride(mutable(), override.tools);
|
|
1545
|
-
anyFilled = true;
|
|
1546
|
-
}
|
|
1547
|
-
if (override.excludeTools !== undefined) {
|
|
1548
|
-
fill("excludeTools", ["excludeTools"], override.excludeTools === false ? undefined : [...override.excludeTools]);
|
|
1549
|
-
}
|
|
1550
|
-
if (override.allowNestedSubagents !== undefined) {
|
|
1551
|
-
fill("allowNestedSubagents", ["allowNestedSubagents"], override.allowNestedSubagents);
|
|
1552
|
-
}
|
|
1553
|
-
if (override.extensions !== undefined) {
|
|
1554
|
-
fill("extensions", ["extensions"], override.extensions === false ? undefined : [...override.extensions]);
|
|
1555
|
-
}
|
|
1556
|
-
if (override.subagentOnlyExtensions !== undefined) {
|
|
1557
|
-
fill(
|
|
1558
|
-
"subagentOnlyExtensions",
|
|
1559
|
-
["subagentOnlyExtensions"],
|
|
1560
|
-
override.subagentOnlyExtensions === false ? undefined : [...override.subagentOnlyExtensions],
|
|
1561
|
-
);
|
|
1562
|
-
}
|
|
1563
|
-
if (override.mutationTools !== undefined) {
|
|
1564
|
-
fill("mutationTools", ["mutationTools"], override.mutationTools === false ? undefined : [...override.mutationTools]);
|
|
1565
|
-
}
|
|
1566
|
-
if (override.completionGuard !== undefined) {
|
|
1567
|
-
fill("completionGuard", ["completionGuard"], override.completionGuard);
|
|
1568
|
-
}
|
|
1569
|
-
if (override.toolBudget !== undefined) {
|
|
1570
|
-
fill("toolBudget", ["toolBudget"], override.toolBudget === false ? undefined : override.toolBudget);
|
|
1571
|
-
}
|
|
1572
|
-
|
|
1573
|
-
if (!anyFilled || !next) return agent;
|
|
1574
|
-
next.override = { ...meta, base: agent.override?.base ?? cloneOverrideBase(agent) };
|
|
1481
|
+
const next = applyBuiltinOverride(agent, override, meta);
|
|
1575
1482
|
const frontmatterFields = agentFrontmatterFields.get(agent);
|
|
1576
1483
|
if (frontmatterFields) agentFrontmatterFields.set(next, frontmatterFields);
|
|
1577
1484
|
return next;
|
|
@@ -2342,6 +2249,61 @@ function extraUserAgentDirs(): string[] {
|
|
|
2342
2249
|
.filter((dir) => dir.length > 0);
|
|
2343
2250
|
}
|
|
2344
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
|
+
|
|
2345
2307
|
export interface AgentDiscoveryAllResult {
|
|
2346
2308
|
builtin: AgentConfig[];
|
|
2347
2309
|
package: AgentConfig[];
|
|
@@ -2502,16 +2464,18 @@ function buildAgentDiscoverySources(cwd: string, preferredModelProvider?: string
|
|
|
2502
2464
|
const userSettingsPath = getUserAgentSettingsPath();
|
|
2503
2465
|
const projectSettingsPath = getProjectAgentSettingsPath(effectiveCwd);
|
|
2504
2466
|
const packageSubagentPaths = collectPackageSubagentPaths(effectiveCwd);
|
|
2467
|
+
const userScanDirs = settingsAgentScanDirs(readConfiguredAgentScanDirs(userSettingsPath));
|
|
2468
|
+
const projectScanDirs = settingsAgentScanDirs(readConfiguredAgentScanDirs(projectSettingsPath));
|
|
2505
2469
|
|
|
2506
2470
|
const builtinLoaded = loadAgentsFromDefinitionFiles(BUILTIN_AGENT_DEFINITION_FILES, "builtin");
|
|
2507
|
-
const userLoaded = [...extraUserAgentDirs(), userDirOld, userDirNew].map((dir, discoveryPriority): LoadedAgentDirectory => {
|
|
2471
|
+
const userLoaded = [...extraUserAgentDirs(), ...userScanDirs.dirs, userDirOld, userDirNew].map((dir, discoveryPriority): LoadedAgentDirectory => {
|
|
2508
2472
|
const inspection = inspectAgentDefinitionDirectory(dir);
|
|
2509
2473
|
return { dir, inspection, loaded: loadAgentsFromDir(dir, "user", discoveryPriority, undefined, inspection) };
|
|
2510
2474
|
});
|
|
2511
2475
|
const projectInspections = new Map(projectCandidateDirs.map((dir) => [dir, inspectAgentDefinitionDirectory(dir)]));
|
|
2512
|
-
const projectLoaded = projectAgentDirs.map((dir): LoadedAgentDirectory => {
|
|
2513
|
-
const inspection = projectInspections.get(dir)
|
|
2514
|
-
return { dir, inspection, loaded: loadAgentsFromDir(dir, "project", dir === projectAgentsDir ? 1 :
|
|
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) };
|
|
2515
2479
|
});
|
|
2516
2480
|
const packageLoaded = packageSubagentPaths.agents.map((entry, index): LoadedAgentDirectory => {
|
|
2517
2481
|
const inspection = inspectAgentDefinitionDirectory(entry.dir);
|
|
@@ -2523,6 +2487,8 @@ function buildAgentDiscoverySources(cwd: string, preferredModelProvider?: string
|
|
|
2523
2487
|
...(projectSettingsPath ? [projectSettingsPath] : []),
|
|
2524
2488
|
...projectDiscoveryWatchPaths(effectiveCwd),
|
|
2525
2489
|
...findProjectRootCandidates(effectiveCwd).map((root) => path.join(getProjectConfigDir(root), "settings.json")),
|
|
2490
|
+
...userScanDirs.watchPaths,
|
|
2491
|
+
...projectScanDirs.watchPaths,
|
|
2526
2492
|
...packageSubagentPaths.watchPaths,
|
|
2527
2493
|
]);
|
|
2528
2494
|
for (const directory of userLoaded) addDirectoryWatchPaths(watchPaths, directory.dir, directory.inspection.files, inspectedAgentDefinitionDirectories(directory.inspection, directory.dir));
|
|
@@ -2530,6 +2496,7 @@ function buildAgentDiscoverySources(cwd: string, preferredModelProvider?: string
|
|
|
2530
2496
|
const inspection = projectInspections.get(dir);
|
|
2531
2497
|
addDirectoryWatchPaths(watchPaths, dir, inspection?.files ?? [], inspection ? inspectedAgentDefinitionDirectories(inspection, dir) : []);
|
|
2532
2498
|
}
|
|
2499
|
+
for (const directory of projectLoaded) addDirectoryWatchPaths(watchPaths, directory.dir, directory.inspection.files, inspectedAgentDefinitionDirectories(directory.inspection, directory.dir));
|
|
2533
2500
|
for (const directory of packageLoaded) addDirectoryWatchPaths(watchPaths, directory.dir, directory.inspection.files, inspectedAgentDefinitionDirectories(directory.inspection, directory.dir));
|
|
2534
2501
|
|
|
2535
2502
|
const userDir = process.env.PI_CODING_AGENT_DIR ? userDirOld : fs.existsSync(userDirNew) ? userDirNew : userDirOld;
|
|
@@ -2665,7 +2632,16 @@ function configuredAgentsForScope(sources: AgentDiscoverySources, scope: AgentSc
|
|
|
2665
2632
|
function discoveryDirectories(sources: AgentDiscoverySources, scope: AgentScope): AgentDefinitionDirectoryReport[] {
|
|
2666
2633
|
const directories: AgentDefinitionDirectoryReport[] = [reportAgentDefinitionDirectory("builtin", BUILTIN_AGENTS_DIR, BUILTIN_AGENT_DEFINITION_INSPECTION)];
|
|
2667
2634
|
if (scope !== "project") for (const directory of sources.userLoaded) directories.push(reportAgentDefinitionDirectory("user", directory.dir, directory.inspection));
|
|
2668
|
-
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
|
+
}
|
|
2669
2645
|
for (const directory of sources.packageLoaded) {
|
|
2670
2646
|
if (directory.packageEntry && packageEntryIncluded(scope, directory.packageEntry.scope)) directories.push(reportAgentDefinitionDirectory("package", directory.dir, directory.inspection));
|
|
2671
2647
|
}
|
|
@@ -2774,7 +2750,9 @@ function discoverAgentsUncached(cwd: string, scope: AgentScope, preferredModelPr
|
|
|
2774
2750
|
const directories: AgentDefinitionDirectoryReport[] = [reportAgentDefinitionDirectory("builtin", BUILTIN_AGENTS_DIR, BUILTIN_AGENT_DEFINITION_INSPECTION)];
|
|
2775
2751
|
const builtinLoaded = loadAgentsFromDefinitionFiles(BUILTIN_AGENT_DEFINITION_FILES, "builtin");
|
|
2776
2752
|
const builtinAgents = applyBuiltinOverrides(applySubagentDefaults(builtinLoaded.agents, defaultModel, defaultProvider, defaultThinking, defaultExtensions), userSettings, projectSettings, userSettingsPath, projectSettingsPath);
|
|
2777
|
-
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) => {
|
|
2778
2756
|
const inspection = inspectAgentDefinitionDirectory(dir);
|
|
2779
2757
|
directories.push(reportAgentDefinitionDirectory("user", dir, inspection));
|
|
2780
2758
|
return loadAgentsFromDir(dir, "user", discoveryPriority, undefined, inspection);
|
|
@@ -2782,7 +2760,11 @@ function discoverAgentsUncached(cwd: string, scope: AgentScope, preferredModelPr
|
|
|
2782
2760
|
const userAgents = applyCustomAgentOverrides(applySubagentDefaults(userLoaded.flatMap((loaded) => loaded.agents), defaultModel, defaultProvider, defaultThinking, defaultExtensions), userSettings, projectSettings, userSettingsPath, projectSettingsPath);
|
|
2783
2761
|
const projectInspections = scope === "user" ? new Map<string, AgentDefinitionInspection>() : new Map(projectCandidateDirs.map((dir) => [dir, inspectAgentDefinitionDirectory(dir)]));
|
|
2784
2762
|
if (scope !== "user") for (const dir of projectCandidateDirs) directories.push(reportAgentDefinitionDirectory("project", dir, projectInspections.get(dir)!));
|
|
2785
|
-
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
|
+
});
|
|
2786
2768
|
const projectAgents = applyCustomAgentOverrides(applySubagentDefaults(projectLoaded.flatMap((loaded) => loaded.agents), defaultModel, defaultProvider, defaultThinking, defaultExtensions), userSettings, projectSettings, userSettingsPath, projectSettingsPath);
|
|
2787
2769
|
const packageLoaded = packageSubagentPaths.agents.map((entry, index) => {
|
|
2788
2770
|
const inspection = inspectAgentDefinitionDirectory(entry.dir);
|
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 {};
|
|
@@ -41,7 +41,8 @@ export function formatInspectorDashboard(input: { status: AsyncStatus; asyncDir:
|
|
|
41
41
|
lines.push("");
|
|
42
42
|
}
|
|
43
43
|
lines.push(formatAsyncRunTranscript(status, asyncDir, { index: input.index, lines: 60, sessionRoots: input.sessionRoots }));
|
|
44
|
-
const
|
|
44
|
+
const acceptsPlainGuidance = input.index !== undefined || status.mode === "single";
|
|
45
|
+
const controls = [input.allowSteer === false || !acceptsPlainGuidance ? undefined : "type guidance", input.allowSteer === false ? undefined : "steer <message>", input.allowStop === false ? undefined : "stop", "status"].filter(Boolean);
|
|
45
46
|
lines.push("", `Controls: ${controls.join(" | ")}`, "Supervisor replies remain in the parent Pi session (subagent_supervisor/intercom).");
|
|
46
47
|
return lines.join("\n");
|
|
47
48
|
}
|
|
@@ -81,6 +82,20 @@ function isTerminal(status: AsyncStatus): boolean {
|
|
|
81
82
|
return status.state !== "queued" && status.state !== "running";
|
|
82
83
|
}
|
|
83
84
|
|
|
85
|
+
function queueInspectorSteer(options: RunnerOptions, status: AsyncStatus, message: string): string {
|
|
86
|
+
if (options.allowSteer === false) throw new Error("Authority policy does not allow steer from this inspector.");
|
|
87
|
+
if (isTerminal(status)) throw new Error(`Run '${options.runId}' is ${status.state} and cannot be steered.`);
|
|
88
|
+
const runningIndexes = (status.steps ?? []).map((step, index) => step.status === "running" ? index : undefined).filter((index): index is number => index !== undefined);
|
|
89
|
+
const targetIndex = options.index ?? (status.mode === "single" ? 0 : undefined);
|
|
90
|
+
if (targetIndex === undefined && runningIndexes.length === 0) throw new Error("No running child is available to steer. Open a child-specific inspector for a pending child.");
|
|
91
|
+
requestAsyncSteer(options.asyncDir, {
|
|
92
|
+
message,
|
|
93
|
+
...(targetIndex !== undefined ? { targetIndex } : { targetIndexes: runningIndexes }),
|
|
94
|
+
source: "herdr-inspector",
|
|
95
|
+
});
|
|
96
|
+
return steeringReceipt(message, `Steering queued for run ${options.runId}.`);
|
|
97
|
+
}
|
|
98
|
+
|
|
84
99
|
export function submitInspectorControl(options: RunnerOptions, line: string): string {
|
|
85
100
|
const command = line.trim();
|
|
86
101
|
if (!command || command === "status") return "Status refreshed.";
|
|
@@ -93,22 +108,13 @@ export function submitInspectorControl(options: RunnerOptions, line: string): st
|
|
|
93
108
|
return `Stop requested for run ${options.runId}.`;
|
|
94
109
|
}
|
|
95
110
|
if (command.startsWith("steer ")) {
|
|
96
|
-
if (options.allowSteer === false) throw new Error("Authority policy does not allow steer from this inspector.");
|
|
97
111
|
const message = command.slice("steer ".length).trim();
|
|
98
112
|
if (!message) throw new Error("steer requires a message.");
|
|
99
|
-
|
|
100
|
-
const runningIndexes = (status.steps ?? []).map((step, index) => step.status === "running" ? index : undefined).filter((index): index is number => index !== undefined);
|
|
101
|
-
const targetIndex = options.index ?? (status.mode === "single" ? 0 : undefined);
|
|
102
|
-
if (targetIndex === undefined && runningIndexes.length === 0) throw new Error("No running child is available to steer. Open a child-specific inspector for a pending child.");
|
|
103
|
-
requestAsyncSteer(options.asyncDir, {
|
|
104
|
-
message,
|
|
105
|
-
...(targetIndex !== undefined ? { targetIndex } : { targetIndexes: runningIndexes }),
|
|
106
|
-
source: "herdr-inspector",
|
|
107
|
-
});
|
|
108
|
-
return steeringReceipt(message, `Steering queued for run ${options.runId}.`);
|
|
113
|
+
return queueInspectorSteer(options, status, message);
|
|
109
114
|
}
|
|
110
115
|
if (command.startsWith("reply ")) throw new Error("Supervisor replies are owned by the parent Pi session; use subagent_supervisor/intercom there.");
|
|
111
|
-
throw new Error("
|
|
116
|
+
if (options.index === undefined && status.mode !== "single") throw new Error("Plain guidance requires a child-specific inspector. Use steer <message> to target all running children from the aggregate inspector.");
|
|
117
|
+
return queueInspectorSteer(options, status, command);
|
|
112
118
|
}
|
|
113
119
|
|
|
114
120
|
export function runInspector(argv = process.argv.slice(2)): void {
|
|
@@ -271,7 +271,6 @@ function workflowReleaseVerdict(owner: ActiveAsyncCapacityOwnerV1, status: Async
|
|
|
271
271
|
if (liveWorkflowRunIds.has(owner.runId)) return { state: "retained", reason: "workflow controller is still live" };
|
|
272
272
|
for (const step of status.steps ?? []) {
|
|
273
273
|
const label = step.workflowKey ?? step.agent;
|
|
274
|
-
if (step.status === "pending" || step.status === "running" || step.status === "paused") return { state: "retained", reason: `workflow child ${label} is still ${step.status}` };
|
|
275
274
|
if (typeof step.async !== "boolean") return { state: "retained", reason: `workflow child ${label} is missing async classification` };
|
|
276
275
|
if (!step.async) continue;
|
|
277
276
|
if (!step.runId) return { state: "retained", reason: `async workflow child ${label} is missing run id` };
|
|
@@ -16,7 +16,7 @@ import { currentCompletionOwnerId } from "../../shared/completion-owner.ts";
|
|
|
16
16
|
import { planChildLaunch, resolveStepBehavior, suppressProgressForReadOnlyTask, type ResolvedStepBehavior } from "../shared/child-launch-plan.ts";
|
|
17
17
|
import { applyThinkingSuffix, projectLaunchResolvedChildExtensions, resolvePiLaunchToolPlan } from "../shared/pi-args.ts";
|
|
18
18
|
import { injectOutputPathSystemPrompt, injectSingleOutputInstruction, normalizeSingleOutputOverride, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
|
|
19
|
-
import { buildChainInstructions, isDynamicParallelStep, isParallelStep, resolveExistingReadPaths, writeInitialProgressFile, type ChainStep, type SequentialStep, type StepOverrides } from "../../shared/settings.ts";
|
|
19
|
+
import { buildChainInstructions, isDynamicParallelStep, isParallelStep, resolveExistingReadInstructionPaths, resolveExistingReadPaths, writeInitialProgressFile, type ChainStep, type SequentialStep, type StepOverrides } from "../../shared/settings.ts";
|
|
20
20
|
import type { RunnerStep } from "../shared/parallel-utils.ts";
|
|
21
21
|
import type { ContextMode } from "../shared/context-mode.ts";
|
|
22
22
|
import { resolveInstalledPiPackageRoot, resolvePiPackageRoot } from "../shared/pi-spawn.ts";
|
|
@@ -31,7 +31,7 @@ import { resolveToolTimeoutMs, toolTimeoutFromEnv } from "../shared/tool-timeout
|
|
|
31
31
|
import { resolveModelScopesForAgent, type ModelScopeConfig } from "../shared/model-scope.ts";
|
|
32
32
|
import { findModelInfo, resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
33
33
|
import { assertThinkingWithinCeiling, decodeThinkingCeiling, intersectThinkingCeilings, SUBAGENT_THINKING_CEILING_ENV, type ThinkingLevel } from "../../shared/thinking-ceiling.ts";
|
|
34
|
-
import { resolveExpectedWorktreeAgentCwd } from "../shared/worktree.ts";
|
|
34
|
+
import { resolveExpectedWorktreeAgentCwd, resolveWorktreeProvider, shouldDeferWorktreeCwd, WORKTREE_AGENT_CWD_PLACEHOLDER } from "../shared/worktree.ts";
|
|
35
35
|
import { buildWorkflowGraphSnapshot } from "../shared/workflow-graph.ts";
|
|
36
36
|
import { ChainOutputValidationError, validateChainOutputBindings } from "../shared/chain-outputs.ts";
|
|
37
37
|
import { createStructuredOutputRuntime } from "../shared/structured-output.ts";
|
|
@@ -178,6 +178,8 @@ interface AsyncChainParams {
|
|
|
178
178
|
worktreeSetupHook?: string;
|
|
179
179
|
worktreeSetupHookTimeoutMs?: number;
|
|
180
180
|
worktreeBaseDir?: string;
|
|
181
|
+
worktreeProvider?: import("../../shared/types.ts").WorktreeProvider;
|
|
182
|
+
worktreeBranchPrefix?: string;
|
|
181
183
|
controlConfig?: ResolvedControlConfig;
|
|
182
184
|
controlIntercomTarget?: string;
|
|
183
185
|
childIntercomTarget?: (agent: string, index: number) => string | undefined;
|
|
@@ -245,6 +247,8 @@ interface AsyncSingleParams {
|
|
|
245
247
|
worktreeSetupHook?: string;
|
|
246
248
|
worktreeSetupHookTimeoutMs?: number;
|
|
247
249
|
worktreeBaseDir?: string;
|
|
250
|
+
worktreeProvider?: import("../../shared/types.ts").WorktreeProvider;
|
|
251
|
+
worktreeBranchPrefix?: string;
|
|
248
252
|
worktree?: boolean;
|
|
249
253
|
controlConfig?: ResolvedControlConfig;
|
|
250
254
|
intercomBridge?: IntercomBridgeConfig;
|
|
@@ -310,6 +314,8 @@ export interface AsyncRunnerStepBuildParams {
|
|
|
310
314
|
waitToolEnabled?: boolean;
|
|
311
315
|
waitToolDefaultTimeoutMs?: number;
|
|
312
316
|
worktreeBaseDir?: string;
|
|
317
|
+
worktreeProvider?: import("../../shared/types.ts").WorktreeProvider;
|
|
318
|
+
worktreeBranchPrefix?: string;
|
|
313
319
|
asyncDir: string;
|
|
314
320
|
outputBaseDir?: string;
|
|
315
321
|
validateOutputBindings?: boolean;
|
|
@@ -740,6 +746,8 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
740
746
|
thinkingOverridesByFlatIndex,
|
|
741
747
|
maxSubagentDepth,
|
|
742
748
|
worktreeBaseDir,
|
|
749
|
+
worktreeProvider,
|
|
750
|
+
worktreeBranchPrefix,
|
|
743
751
|
asyncDir,
|
|
744
752
|
} = params;
|
|
745
753
|
const outputBaseDir = params.outputBaseDir;
|
|
@@ -747,6 +755,15 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
747
755
|
const chainSkills = params.chainSkills ?? [];
|
|
748
756
|
const availableModels = params.availableModels;
|
|
749
757
|
const runnerCwd = resolveChildCwd(ctx.cwd, cwd);
|
|
758
|
+
let managedWorktreeProvider: "native" | "worktrunk" | undefined;
|
|
759
|
+
try {
|
|
760
|
+
if (chain.some((step) => "worktree" in step && step.worktree === true)) {
|
|
761
|
+
const resolved = resolveWorktreeProvider(worktreeProvider, worktreeBaseDir);
|
|
762
|
+
managedWorktreeProvider = shouldDeferWorktreeCwd(worktreeProvider, worktreeBaseDir) ? "worktrunk" : resolved;
|
|
763
|
+
}
|
|
764
|
+
} catch (error) {
|
|
765
|
+
return { error: error instanceof Error ? error.message : String(error) };
|
|
766
|
+
}
|
|
750
767
|
const progressDir = params.progressDir ?? runnerCwd;
|
|
751
768
|
const graphChain: ChainStep[] = params.attachRoot
|
|
752
769
|
? [{
|
|
@@ -873,7 +890,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
873
890
|
if (validationError) throw new AsyncStartValidationError(validationError);
|
|
874
891
|
let taskTemplate = s.task ?? "{previous}";
|
|
875
892
|
taskTemplate = taskTemplate.replace(/\{task\}/g, originalTask ?? "");
|
|
876
|
-
taskTemplate = taskTemplate.replace(/\{chain_dir\}/g, runnerCwd);
|
|
893
|
+
taskTemplate = taskTemplate.replace(/\{chain_dir\}/g, behaviorCwd ?? runnerCwd);
|
|
877
894
|
const taskText = `${readInstructions.prefix}${taskTemplate}${progressInstructions.suffix}`;
|
|
878
895
|
const task = namespaceOutputPath ? taskText : injectSingleOutputInstruction(taskText, outputPath, a);
|
|
879
896
|
|
|
@@ -1058,7 +1075,9 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
1058
1075
|
return {
|
|
1059
1076
|
parallel: s.parallel.map((t, taskIndex) => {
|
|
1060
1077
|
let behaviorCwd: string | undefined;
|
|
1061
|
-
if (s.worktree) {
|
|
1078
|
+
if (s.worktree && managedWorktreeProvider === "worktrunk") {
|
|
1079
|
+
behaviorCwd = WORKTREE_AGENT_CWD_PLACEHOLDER;
|
|
1080
|
+
} else if (s.worktree && managedWorktreeProvider === "native") {
|
|
1062
1081
|
try {
|
|
1063
1082
|
behaviorCwd = resolveExpectedWorktreeAgentCwd(runnerCwd, `${id}-s${stepIndex}`, taskIndex, worktreeBaseDir);
|
|
1064
1083
|
} catch {
|
|
@@ -1113,7 +1132,9 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
1113
1132
|
}
|
|
1114
1133
|
const sequential = s as SequentialStep;
|
|
1115
1134
|
let behaviorCwd: string | undefined;
|
|
1116
|
-
if (sequential.worktree) {
|
|
1135
|
+
if (sequential.worktree && managedWorktreeProvider === "worktrunk") {
|
|
1136
|
+
behaviorCwd = WORKTREE_AGENT_CWD_PLACEHOLDER;
|
|
1137
|
+
} else if (sequential.worktree && managedWorktreeProvider === "native") {
|
|
1117
1138
|
try {
|
|
1118
1139
|
behaviorCwd = resolveExpectedWorktreeAgentCwd(runnerCwd, `${id}-s${stepIndex}`, 0, worktreeBaseDir);
|
|
1119
1140
|
} catch {
|
|
@@ -1183,6 +1204,8 @@ export function executeAsyncChain(
|
|
|
1183
1204
|
worktreeSetupHook,
|
|
1184
1205
|
worktreeSetupHookTimeoutMs,
|
|
1185
1206
|
worktreeBaseDir,
|
|
1207
|
+
worktreeProvider,
|
|
1208
|
+
worktreeBranchPrefix,
|
|
1186
1209
|
controlConfig,
|
|
1187
1210
|
controlIntercomTarget,
|
|
1188
1211
|
childIntercomTarget,
|
|
@@ -1239,6 +1262,8 @@ export function executeAsyncChain(
|
|
|
1239
1262
|
waitToolEnabled: params.waitToolEnabled,
|
|
1240
1263
|
waitToolDefaultTimeoutMs: params.waitToolDefaultTimeoutMs,
|
|
1241
1264
|
worktreeBaseDir,
|
|
1265
|
+
worktreeProvider,
|
|
1266
|
+
worktreeBranchPrefix,
|
|
1242
1267
|
asyncDir,
|
|
1243
1268
|
fast: params.fast,
|
|
1244
1269
|
toolBudget: params.toolBudget,
|
|
@@ -1314,6 +1339,8 @@ export function executeAsyncChain(
|
|
|
1314
1339
|
worktreeSetupHook,
|
|
1315
1340
|
worktreeSetupHookTimeoutMs,
|
|
1316
1341
|
worktreeBaseDir,
|
|
1342
|
+
worktreeProvider,
|
|
1343
|
+
worktreeBranchPrefix,
|
|
1317
1344
|
controlConfig,
|
|
1318
1345
|
toolBudget: params.toolBudget,
|
|
1319
1346
|
usageBudget: params.usageBudget,
|
|
@@ -1511,6 +1538,8 @@ export function executeAsyncSingle(
|
|
|
1511
1538
|
worktreeSetupHook,
|
|
1512
1539
|
worktreeSetupHookTimeoutMs,
|
|
1513
1540
|
worktreeBaseDir,
|
|
1541
|
+
worktreeProvider,
|
|
1542
|
+
worktreeBranchPrefix,
|
|
1514
1543
|
controlConfig,
|
|
1515
1544
|
controlIntercomTarget,
|
|
1516
1545
|
childIntercomTarget,
|
|
@@ -1556,7 +1585,18 @@ export function executeAsyncSingle(
|
|
|
1556
1585
|
return formatAsyncStartError("single", error instanceof Error ? error.message : String(error));
|
|
1557
1586
|
}
|
|
1558
1587
|
const runnerCwd = resolveChildCwd(ctx.cwd, cwd);
|
|
1559
|
-
|
|
1588
|
+
let managedWorktreeProvider: "native" | "worktrunk" | undefined;
|
|
1589
|
+
if (params.worktree === true) {
|
|
1590
|
+
try {
|
|
1591
|
+
const resolved = resolveWorktreeProvider(params.worktreeProvider, worktreeBaseDir);
|
|
1592
|
+
managedWorktreeProvider = shouldDeferWorktreeCwd(params.worktreeProvider, worktreeBaseDir) ? "worktrunk" : resolved;
|
|
1593
|
+
} catch (error) {
|
|
1594
|
+
return formatAsyncStartError("single", error instanceof Error ? error.message : String(error));
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
const instructionCwd = params.worktree === true && managedWorktreeProvider === "worktrunk"
|
|
1598
|
+
? WORKTREE_AGENT_CWD_PLACEHOLDER
|
|
1599
|
+
: params.worktree === true && managedWorktreeProvider === "native"
|
|
1560
1600
|
? resolveExpectedWorktreeAgentCwd(runnerCwd, `${id}-s0`, 0, worktreeBaseDir)
|
|
1561
1601
|
: runnerCwd;
|
|
1562
1602
|
const readExistenceCwd = params.worktree === true ? runnerCwd : instructionCwd;
|
|
@@ -1610,7 +1650,11 @@ export function executeAsyncSingle(
|
|
|
1610
1650
|
// Reads: caller override > agent defaultReads > none. `~`/`~/` expand to home;
|
|
1611
1651
|
// absolute paths pass through; relative paths resolve against the child cwd.
|
|
1612
1652
|
const reads = params.reads !== undefined ? params.reads : agentConfig.defaultReads ?? false;
|
|
1613
|
-
const readPaths = Array.isArray(reads)
|
|
1653
|
+
const readPaths = Array.isArray(reads)
|
|
1654
|
+
? managedWorktreeProvider === "worktrunk"
|
|
1655
|
+
? resolveExistingReadInstructionPaths(reads, instructionCwd, readExistenceCwd)
|
|
1656
|
+
: resolveExistingReadPaths(reads, readExistenceCwd)
|
|
1657
|
+
: [];
|
|
1614
1658
|
const readsInstruction = readPaths.length > 0
|
|
1615
1659
|
? `[Read from: ${readPaths.join(", ")}]\n\n`
|
|
1616
1660
|
: "";
|
|
@@ -1901,6 +1945,8 @@ export function executeAsyncSingle(
|
|
|
1901
1945
|
worktreeSetupHook,
|
|
1902
1946
|
worktreeSetupHookTimeoutMs,
|
|
1903
1947
|
worktreeBaseDir,
|
|
1948
|
+
worktreeProvider,
|
|
1949
|
+
worktreeBranchPrefix,
|
|
1904
1950
|
controlConfig,
|
|
1905
1951
|
timeoutMs,
|
|
1906
1952
|
deadlineAt,
|
|
@@ -45,6 +45,8 @@ export type AsyncResumeTarget = {
|
|
|
45
45
|
sessionName?: string;
|
|
46
46
|
index: number;
|
|
47
47
|
cwd?: string;
|
|
48
|
+
/** True when cwd is the retained managed worktree recorded by the handoff. */
|
|
49
|
+
managedWorktree?: boolean;
|
|
48
50
|
sessionFile?: string;
|
|
49
51
|
model?: string;
|
|
50
52
|
thinking?: string;
|
|
@@ -569,6 +571,7 @@ export function resolveAsyncResumeTarget(params: AsyncResumeParams, deps: AsyncR
|
|
|
569
571
|
...(statusSteps[index]?.sessionName ?? resultSteps[index]?.sessionName ? { sessionName: statusSteps[index]?.sessionName ?? resultSteps[index]?.sessionName } : {}),
|
|
570
572
|
index,
|
|
571
573
|
...(resumeCwd ? { cwd: resumeCwd } : {}),
|
|
574
|
+
...(managedWorktreeCwd ? { managedWorktree: true } : {}),
|
|
572
575
|
...(resolvedSessionFile ? { sessionFile: resolvedSessionFile } : {}),
|
|
573
576
|
...(stepModel ? { model: stepModel } : {}),
|
|
574
577
|
...(stepThinking ? { thinking: stepThinking } : {}),
|