pi-subagents 0.31.1 → 0.32.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 +40 -4
- package/README.md +97 -7
- package/package.json +1 -1
- package/skills/pi-subagents/SKILL.md +6 -1
- package/src/agents/agent-management.ts +6 -1
- package/src/agents/agents.ts +55 -11
- package/src/extension/companion-suggestions.ts +359 -0
- package/src/extension/config.ts +27 -4
- package/src/extension/doctor.ts +2 -0
- package/src/extension/fanout-child.ts +1 -0
- package/src/extension/index.ts +58 -4
- package/src/extension/schemas.ts +2 -2
- package/src/runs/background/async-execution.ts +138 -33
- package/src/runs/background/async-job-tracker.ts +77 -1
- package/src/runs/background/async-status.ts +41 -9
- package/src/runs/background/chain-root-attachment.ts +34 -4
- package/src/runs/background/control-channel.ts +50 -0
- package/src/runs/background/run-status.ts +1 -0
- package/src/runs/background/stale-run-reconciler.ts +28 -1
- package/src/runs/background/subagent-runner.ts +454 -115
- package/src/runs/foreground/chain-execution.ts +29 -7
- package/src/runs/foreground/execution.ts +24 -6
- package/src/runs/foreground/subagent-executor.ts +209 -35
- package/src/runs/shared/acceptance.ts +45 -22
- package/src/runs/shared/dynamic-fanout.ts +1 -1
- package/src/runs/shared/model-fallback.ts +4 -0
- package/src/runs/shared/nested-events.ts +58 -0
- package/src/runs/shared/parallel-utils.ts +49 -1
- package/src/runs/shared/pi-args.ts +5 -3
- package/src/runs/shared/pi-spawn.ts +52 -20
- package/src/runs/shared/single-output.ts +2 -0
- package/src/runs/shared/subagent-prompt-runtime.ts +2 -2
- package/src/runs/shared/worktree.ts +28 -5
- package/src/shared/artifacts.ts +15 -1
- package/src/shared/fork-context.ts +133 -22
- package/src/shared/types.ts +81 -3
- package/src/shared/utils.ts +99 -14
- package/src/slash/slash-commands.ts +117 -0
- package/src/tui/render.ts +16 -4
|
@@ -4,7 +4,7 @@ import * as path from "node:path";
|
|
|
4
4
|
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
5
5
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
6
6
|
import { type AgentConfig, type AgentScope } from "../../agents/agents.ts";
|
|
7
|
-
import { getArtifactsDir } from "../../shared/artifacts.ts";
|
|
7
|
+
import { getArtifactsDir, getProjectChainRunsDir } from "../../shared/artifacts.ts";
|
|
8
8
|
import { ChainClarifyComponent, type ChainClarifyResult } from "./chain-clarify.ts";
|
|
9
9
|
import { toModelInfo, type ModelInfo } from "../../shared/model-info.ts";
|
|
10
10
|
import { executeChain } from "./chain-execution.ts";
|
|
@@ -40,7 +40,8 @@ import { resolveCurrentSessionId } from "../../shared/session-identity.ts";
|
|
|
40
40
|
import { applyIntercomBridgeToAgent, INTERCOM_BRIDGE_MARKER, resolveIntercomBridge, resolveIntercomSessionTarget, resolveSubagentIntercomTarget, type IntercomBridgeState } from "../../intercom/intercom-bridge.ts";
|
|
41
41
|
import { formatControlIntercomMessage, formatControlNoticeMessage, resolveControlConfig, shouldNotifyControlEvent } from "../shared/subagent-control.ts";
|
|
42
42
|
import { finalizeSingleOutput, injectSingleOutputInstruction, normalizeSingleOutputOverride, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
|
|
43
|
-
import { compactForegroundDetails, getSingleResultOutput, mapConcurrent, readStatus, resolveChildCwd } from "../../shared/utils.ts";
|
|
43
|
+
import { compactForegroundDetails, getSingleResultOutput, mapConcurrent, readStatus, resolveChildCwd, sumResultsCost, sumResultsUsage } from "../../shared/utils.ts";
|
|
44
|
+
import { DEFAULT_GLOBAL_CONCURRENCY_LIMIT, Semaphore } from "../shared/parallel-utils.ts";
|
|
44
45
|
import {
|
|
45
46
|
attachNestedChildrenToResultChildren,
|
|
46
47
|
buildSubagentResultIntercomPayload,
|
|
@@ -54,7 +55,7 @@ import { buildRevivedAsyncTask, interruptLiveAsyncResumeTarget, resolveAsyncResu
|
|
|
54
55
|
import { deliverInterruptRequest } from "../background/control-channel.ts";
|
|
55
56
|
import { reconcileAsyncRun } from "../background/stale-run-reconciler.ts";
|
|
56
57
|
import { resolveAsyncRootResultPath } from "../background/chain-root-attachment.ts";
|
|
57
|
-
import { createNestedRoute, readNestedControlResults, resolveInheritedNestedRouteFromEnv, resolveNestedAsyncDir, resolveNestedParentAddressFromEnv, updateForegroundNestedProjection, writeNestedControlRequest, writeNestedEvent, type NestedRunResolutionScope } from "../shared/nested-events.ts";
|
|
58
|
+
import { attachRootChildrenToSteps, createNestedRoute, readNestedControlResults, resolveInheritedNestedRouteFromEnv, resolveNestedAsyncDir, resolveNestedParentAddressFromEnv, updateForegroundNestedProjection, writeNestedControlRequest, writeNestedEvent, type NestedRunResolutionScope } from "../shared/nested-events.ts";
|
|
58
59
|
import { resolveSubagentRunId, type ResolvedSubagentRunId } from "../background/run-id-resolver.ts";
|
|
59
60
|
import { formatNestedRunStatusLines } from "../shared/nested-render.ts";
|
|
60
61
|
import { inspectSubagentStatus } from "../background/run-status.ts";
|
|
@@ -92,6 +93,7 @@ import {
|
|
|
92
93
|
SUBAGENT_CONTROL_EVENT,
|
|
93
94
|
SUBAGENT_CONTROL_INTERCOM_EVENT,
|
|
94
95
|
checkSubagentDepth,
|
|
96
|
+
resolveMaxSubagentSpawnsPerSession,
|
|
95
97
|
resolveTopLevelParallelConcurrency,
|
|
96
98
|
resolveTopLevelParallelMaxTasks,
|
|
97
99
|
resolveChildMaxSubagentDepth,
|
|
@@ -154,6 +156,7 @@ interface ExecutorDeps {
|
|
|
154
156
|
state: SubagentState;
|
|
155
157
|
config: ExtensionConfig;
|
|
156
158
|
asyncByDefault: boolean;
|
|
159
|
+
companionSuggestionLines?: (input: { surface: "list" | "doctor"; cwd: string; context?: "fresh" | "fork"; orchestratorTarget?: string }) => string[];
|
|
157
160
|
tempArtifactsDir: string;
|
|
158
161
|
getSubagentSessionRoot: (parentSessionFile: string | null) => string;
|
|
159
162
|
expandTilde: (p: string) => string;
|
|
@@ -175,6 +178,7 @@ interface ExecutionContextData {
|
|
|
175
178
|
sessionDirForIndex: (idx?: number) => string;
|
|
176
179
|
sessionFileForIndex: (idx?: number) => string | undefined;
|
|
177
180
|
sessionFileForTask: (agentName: string, idx?: number) => string | undefined;
|
|
181
|
+
thinkingOverrideForTask: (agentName: string, idx?: number) => AgentConfig["thinking"] | undefined;
|
|
178
182
|
artifactConfig: ArtifactConfig;
|
|
179
183
|
artifactsDir: string;
|
|
180
184
|
backgroundRequestedWhileClarifying: boolean;
|
|
@@ -233,6 +237,35 @@ function nestedResolutionScopeForExecutor(deps: ExecutorDeps): NestedRunResoluti
|
|
|
233
237
|
};
|
|
234
238
|
}
|
|
235
239
|
|
|
240
|
+
function reserveSubagentSpawns(input: { state: SubagentState; config: ExtensionConfig; sessionId: string | null; requested: number; mode: "single" | "parallel" | "chain" }): AgentToolResult<Details> | undefined {
|
|
241
|
+
if (input.requested <= 0) return undefined;
|
|
242
|
+
if (input.state.subagentSpawns?.sessionId !== input.sessionId) {
|
|
243
|
+
input.state.subagentSpawns = { sessionId: input.sessionId, count: 0 };
|
|
244
|
+
}
|
|
245
|
+
const maxSpawns = resolveMaxSubagentSpawnsPerSession(input.config.maxSubagentSpawnsPerSession);
|
|
246
|
+
const used = input.state.subagentSpawns.count;
|
|
247
|
+
if (used + input.requested > maxSpawns) {
|
|
248
|
+
return {
|
|
249
|
+
content: [{ type: "text", text: `Subagent spawn limit reached for this session (${used}/${maxSpawns} used, ${input.requested} requested). Complete the work directly or start a new session.` }],
|
|
250
|
+
isError: true,
|
|
251
|
+
details: { mode: input.mode, results: [] },
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
input.state.subagentSpawns.count = used + input.requested;
|
|
255
|
+
return undefined;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function countRequestedSubagentSpawns(params: SubagentParamsLike, config: ExtensionConfig): number {
|
|
259
|
+
if (params.tasks) return params.tasks.length;
|
|
260
|
+
if (params.chain) {
|
|
261
|
+
return params.chain.reduce((total, step) => {
|
|
262
|
+
if (isDynamicParallelStep(step)) return total + (step.expand.maxItems ?? config.chain?.dynamicFanout?.maxItems ?? 0);
|
|
263
|
+
return total + getStepAgents(step).length;
|
|
264
|
+
}, 0);
|
|
265
|
+
}
|
|
266
|
+
return params.agent ? 1 : 0;
|
|
267
|
+
}
|
|
268
|
+
|
|
236
269
|
function foregroundStatusResult(control: SubagentState["foregroundControls"] extends Map<string, infer T> ? T : never): AgentToolResult<Details> {
|
|
237
270
|
let nestedWarning: string | undefined;
|
|
238
271
|
try {
|
|
@@ -921,7 +954,7 @@ async function resumeAsyncRun(input: {
|
|
|
921
954
|
availableModels,
|
|
922
955
|
cwd: effectiveCwd,
|
|
923
956
|
maxOutput: input.params.maxOutput,
|
|
924
|
-
artifactsDir:
|
|
957
|
+
artifactsDir: getArtifactsDir(parentSessionFile, effectiveCwd),
|
|
925
958
|
artifactConfig,
|
|
926
959
|
shareEnabled: input.params.share === true,
|
|
927
960
|
sessionRoot: input.deps.getSubagentSessionRoot(parentSessionFile),
|
|
@@ -930,9 +963,11 @@ async function resumeAsyncRun(input: {
|
|
|
930
963
|
maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
|
|
931
964
|
worktreeSetupHook: input.deps.config.worktreeSetupHook,
|
|
932
965
|
worktreeSetupHookTimeoutMs: input.deps.config.worktreeSetupHookTimeoutMs,
|
|
966
|
+
worktreeBaseDir: input.deps.config.worktreeBaseDir,
|
|
933
967
|
controlConfig: resolveControlConfig(input.deps.config.control, input.params.control),
|
|
934
968
|
controlIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
|
|
935
969
|
childIntercomTarget: intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
|
|
970
|
+
globalConcurrencyLimit: input.deps.config.globalConcurrencyLimit,
|
|
936
971
|
});
|
|
937
972
|
if (result.isError) return result;
|
|
938
973
|
const attachedId = result.details.asyncId ?? runId;
|
|
@@ -948,6 +983,7 @@ async function resumeAsyncRun(input: {
|
|
|
948
983
|
|
|
949
984
|
const runId = randomUUID().slice(0, 8);
|
|
950
985
|
const artifactConfig: ArtifactConfig = { ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false };
|
|
986
|
+
const artifactsDir = getArtifactsDir(parentSessionFile, effectiveCwd);
|
|
951
987
|
const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
952
988
|
const result = executeAsyncSingle(runId, {
|
|
953
989
|
agent: target.agent,
|
|
@@ -963,14 +999,16 @@ async function resumeAsyncRun(input: {
|
|
|
963
999
|
},
|
|
964
1000
|
cwd: effectiveCwd,
|
|
965
1001
|
maxOutput: input.params.maxOutput,
|
|
966
|
-
artifactsDir
|
|
1002
|
+
artifactsDir,
|
|
967
1003
|
artifactConfig,
|
|
968
1004
|
shareEnabled: input.params.share === true,
|
|
969
1005
|
sessionRoot: input.deps.getSubagentSessionRoot(parentSessionFile),
|
|
970
1006
|
sessionFile: target.sessionFile,
|
|
1007
|
+
outputBaseDir: resolveSingleRunOutputBaseDir(input.deps, artifactsDir, runId),
|
|
971
1008
|
maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
|
|
972
1009
|
worktreeSetupHook: input.deps.config.worktreeSetupHook,
|
|
973
1010
|
worktreeSetupHookTimeoutMs: input.deps.config.worktreeSetupHookTimeoutMs,
|
|
1011
|
+
worktreeBaseDir: input.deps.config.worktreeBaseDir,
|
|
974
1012
|
controlConfig: resolveControlConfig(input.deps.config.control, input.params.control),
|
|
975
1013
|
controlIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
|
|
976
1014
|
childIntercomTarget: intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
|
|
@@ -1391,6 +1429,7 @@ function toExecutionErrorResult(params: SubagentParamsLike, error: unknown): Age
|
|
|
1391
1429
|
function collectChainSessionFiles(
|
|
1392
1430
|
chain: ChainStep[],
|
|
1393
1431
|
sessionFileForTask: (agentName: string, idx?: number) => string | undefined,
|
|
1432
|
+
dynamicFanoutMaxItems?: number,
|
|
1394
1433
|
): (string | undefined)[] {
|
|
1395
1434
|
const sessionFiles: (string | undefined)[] = [];
|
|
1396
1435
|
let flatIndex = 0;
|
|
@@ -1403,7 +1442,11 @@ function collectChainSessionFiles(
|
|
|
1403
1442
|
continue;
|
|
1404
1443
|
}
|
|
1405
1444
|
if (isDynamicParallelStep(step)) {
|
|
1406
|
-
|
|
1445
|
+
const maxItems = step.expand.maxItems ?? dynamicFanoutMaxItems ?? 0;
|
|
1446
|
+
for (let itemIndex = 0; itemIndex < maxItems; itemIndex++) {
|
|
1447
|
+
sessionFiles.push(sessionFileForTask(step.parallel.agent, flatIndex));
|
|
1448
|
+
flatIndex++;
|
|
1449
|
+
}
|
|
1407
1450
|
continue;
|
|
1408
1451
|
}
|
|
1409
1452
|
sessionFiles.push(sessionFileForTask((step as SequentialStep).agent, flatIndex));
|
|
@@ -1412,6 +1455,35 @@ function collectChainSessionFiles(
|
|
|
1412
1455
|
return sessionFiles;
|
|
1413
1456
|
}
|
|
1414
1457
|
|
|
1458
|
+
function collectChainThinkingOverrides(
|
|
1459
|
+
chain: ChainStep[],
|
|
1460
|
+
thinkingOverrideForTask: (agentName: string, idx?: number) => AgentConfig["thinking"] | undefined,
|
|
1461
|
+
dynamicFanoutMaxItems?: number,
|
|
1462
|
+
): (AgentConfig["thinking"] | undefined)[] {
|
|
1463
|
+
const thinkingOverrides: (AgentConfig["thinking"] | undefined)[] = [];
|
|
1464
|
+
let flatIndex = 0;
|
|
1465
|
+
for (const step of chain) {
|
|
1466
|
+
if (isParallelStep(step)) {
|
|
1467
|
+
for (const task of step.parallel) {
|
|
1468
|
+
thinkingOverrides.push(thinkingOverrideForTask(task.agent, flatIndex));
|
|
1469
|
+
flatIndex++;
|
|
1470
|
+
}
|
|
1471
|
+
continue;
|
|
1472
|
+
}
|
|
1473
|
+
if (isDynamicParallelStep(step)) {
|
|
1474
|
+
const maxItems = step.expand.maxItems ?? dynamicFanoutMaxItems ?? 0;
|
|
1475
|
+
for (let itemIndex = 0; itemIndex < maxItems; itemIndex++) {
|
|
1476
|
+
thinkingOverrides.push(thinkingOverrideForTask(step.parallel.agent, flatIndex));
|
|
1477
|
+
flatIndex++;
|
|
1478
|
+
}
|
|
1479
|
+
continue;
|
|
1480
|
+
}
|
|
1481
|
+
thinkingOverrides.push(thinkingOverrideForTask((step as SequentialStep).agent, flatIndex));
|
|
1482
|
+
flatIndex++;
|
|
1483
|
+
}
|
|
1484
|
+
return thinkingOverrides;
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1415
1487
|
function wrapChainTasksForFork(chain: ChainStep[], contextPolicy: AgentDefaultContextPolicy): ChainStep[] {
|
|
1416
1488
|
return chain.map((step, stepIndex) => {
|
|
1417
1489
|
if (isParallelStep(step)) {
|
|
@@ -1450,6 +1522,7 @@ function preflightForkSessionsForStaticTasks(
|
|
|
1450
1522
|
params: SubagentParamsLike,
|
|
1451
1523
|
contextPolicy: AgentDefaultContextPolicy,
|
|
1452
1524
|
sessionFileForTask: (agentName: string, idx?: number) => string | undefined,
|
|
1525
|
+
dynamicFanoutMaxItems?: number,
|
|
1453
1526
|
): void {
|
|
1454
1527
|
if (!contextPolicy.usesFork) return;
|
|
1455
1528
|
if (params.agent) {
|
|
@@ -1473,8 +1546,11 @@ function preflightForkSessionsForStaticTasks(
|
|
|
1473
1546
|
continue;
|
|
1474
1547
|
}
|
|
1475
1548
|
if (isDynamicParallelStep(step)) {
|
|
1476
|
-
|
|
1477
|
-
|
|
1549
|
+
const maxItems = step.expand.maxItems ?? dynamicFanoutMaxItems ?? 0;
|
|
1550
|
+
if (shouldForkAgent(contextPolicy, step.parallel.agent)) {
|
|
1551
|
+
for (let itemIndex = 0; itemIndex < maxItems; itemIndex++) sessionFileForTask(step.parallel.agent, flatIndex + itemIndex);
|
|
1552
|
+
}
|
|
1553
|
+
flatIndex += maxItems;
|
|
1478
1554
|
continue;
|
|
1479
1555
|
}
|
|
1480
1556
|
const sequential = step as SequentialStep;
|
|
@@ -1493,6 +1569,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1493
1569
|
sessionRoot,
|
|
1494
1570
|
sessionFileForIndex,
|
|
1495
1571
|
sessionFileForTask,
|
|
1572
|
+
thinkingOverrideForTask,
|
|
1496
1573
|
artifactConfig,
|
|
1497
1574
|
artifactsDir,
|
|
1498
1575
|
effectiveAsync,
|
|
@@ -1586,13 +1663,17 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1586
1663
|
sessionRoot,
|
|
1587
1664
|
chainSkills: [],
|
|
1588
1665
|
sessionFilesByFlatIndex: params.tasks.map((task, index) => sessionFileForTask(task.agent, index)),
|
|
1666
|
+
thinkingOverridesByFlatIndex: params.tasks.map((task, index) => thinkingOverrideForTask(task.agent, index)),
|
|
1589
1667
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
1590
1668
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1591
1669
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
1670
|
+
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
1592
1671
|
controlConfig,
|
|
1593
1672
|
controlIntercomTarget,
|
|
1594
1673
|
childIntercomTarget,
|
|
1595
1674
|
nestedRoute,
|
|
1675
|
+
timeoutMs: data.timeoutMs,
|
|
1676
|
+
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
1596
1677
|
});
|
|
1597
1678
|
}
|
|
1598
1679
|
|
|
@@ -1613,15 +1694,19 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1613
1694
|
shareEnabled,
|
|
1614
1695
|
sessionRoot,
|
|
1615
1696
|
chainSkills,
|
|
1616
|
-
sessionFilesByFlatIndex: collectChainSessionFiles(chain, sessionFileForTask),
|
|
1697
|
+
sessionFilesByFlatIndex: collectChainSessionFiles(chain, sessionFileForTask, deps.config.chain?.dynamicFanout?.maxItems),
|
|
1698
|
+
thinkingOverridesByFlatIndex: collectChainThinkingOverrides(chain, thinkingOverrideForTask, deps.config.chain?.dynamicFanout?.maxItems),
|
|
1617
1699
|
dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
|
|
1618
1700
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
1619
1701
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1620
1702
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
1703
|
+
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
1621
1704
|
controlConfig,
|
|
1622
1705
|
controlIntercomTarget,
|
|
1623
1706
|
childIntercomTarget,
|
|
1624
1707
|
nestedRoute,
|
|
1708
|
+
timeoutMs: data.timeoutMs,
|
|
1709
|
+
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
1625
1710
|
});
|
|
1626
1711
|
}
|
|
1627
1712
|
|
|
@@ -1657,15 +1742,19 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1657
1742
|
skills,
|
|
1658
1743
|
output: effectiveOutput,
|
|
1659
1744
|
outputMode: effectiveOutputMode,
|
|
1745
|
+
outputBaseDir: resolveSingleRunOutputBaseDir(deps, artifactsDir, id),
|
|
1660
1746
|
modelOverride,
|
|
1747
|
+
thinkingOverride: thinkingOverrideForTask(params.agent!, 0),
|
|
1661
1748
|
maxSubagentDepth,
|
|
1662
1749
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1663
1750
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
1751
|
+
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
1664
1752
|
controlConfig,
|
|
1665
1753
|
controlIntercomTarget,
|
|
1666
1754
|
childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(agent, index) : undefined,
|
|
1667
1755
|
nestedRoute,
|
|
1668
1756
|
acceptance: params.acceptance,
|
|
1757
|
+
timeoutMs: data.timeoutMs,
|
|
1669
1758
|
});
|
|
1670
1759
|
}
|
|
1671
1760
|
|
|
@@ -1684,6 +1773,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
1684
1773
|
sessionDirForIndex,
|
|
1685
1774
|
sessionFileForIndex,
|
|
1686
1775
|
sessionFileForTask,
|
|
1776
|
+
thinkingOverrideForTask,
|
|
1687
1777
|
artifactsDir,
|
|
1688
1778
|
artifactConfig,
|
|
1689
1779
|
onUpdate,
|
|
@@ -1711,6 +1801,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
1711
1801
|
sessionDirForIndex,
|
|
1712
1802
|
sessionFileForIndex,
|
|
1713
1803
|
sessionFileForTask,
|
|
1804
|
+
thinkingOverrideForTask,
|
|
1714
1805
|
artifactsDir,
|
|
1715
1806
|
artifactConfig,
|
|
1716
1807
|
includeProgress: params.includeProgress,
|
|
@@ -1723,19 +1814,18 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
1723
1814
|
foregroundControl,
|
|
1724
1815
|
nestedRoute: foregroundControl?.nestedRoute,
|
|
1725
1816
|
chainSkills,
|
|
1726
|
-
chainDir: params.chainDir,
|
|
1817
|
+
chainDir: params.chainDir ?? getProjectChainRunsDir(effectiveCwd),
|
|
1727
1818
|
dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
|
|
1728
1819
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
1729
1820
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1730
1821
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
1822
|
+
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
1731
1823
|
timeoutMs: data.timeoutMs,
|
|
1732
1824
|
deadlineAt: data.deadlineAt,
|
|
1825
|
+
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
1733
1826
|
});
|
|
1734
1827
|
|
|
1735
1828
|
if (chainResult.requestedAsync) {
|
|
1736
|
-
if (data.timeoutMs !== undefined) {
|
|
1737
|
-
return buildRequestedModeError(params, "timeoutMs/maxRuntimeMs are only supported for foreground runs; background launch from clarify cannot preserve the timeout.");
|
|
1738
|
-
}
|
|
1739
1829
|
if (!isAsyncAvailable()) {
|
|
1740
1830
|
return {
|
|
1741
1831
|
content: [{ type: "text", text: "Background mode requires upstream jiti for TypeScript execution but it could not be found. Ensure the pi-subagents package dependencies are installed." }],
|
|
@@ -1766,20 +1856,29 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
1766
1856
|
shareEnabled,
|
|
1767
1857
|
sessionRoot,
|
|
1768
1858
|
chainSkills: chainResult.requestedAsync.chainSkills,
|
|
1769
|
-
sessionFilesByFlatIndex: collectChainSessionFiles(asyncChain, sessionFileForTask),
|
|
1859
|
+
sessionFilesByFlatIndex: collectChainSessionFiles(asyncChain, sessionFileForTask, deps.config.chain?.dynamicFanout?.maxItems),
|
|
1860
|
+
thinkingOverridesByFlatIndex: collectChainThinkingOverrides(asyncChain, thinkingOverrideForTask, deps.config.chain?.dynamicFanout?.maxItems),
|
|
1770
1861
|
dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
|
|
1771
1862
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
1772
1863
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1773
1864
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
1865
|
+
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
1774
1866
|
controlConfig,
|
|
1775
1867
|
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
1776
1868
|
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
|
|
1777
1869
|
nestedRoute: data.nestedRoute,
|
|
1870
|
+
timeoutMs: data.timeoutMs,
|
|
1871
|
+
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
1778
1872
|
});
|
|
1779
1873
|
}
|
|
1780
1874
|
|
|
1781
|
-
const
|
|
1782
|
-
if (foregroundControl)
|
|
1875
|
+
const rawChainDetails = chainResult.details ? { ...chainResult.details, runId } : undefined;
|
|
1876
|
+
if (foregroundControl && rawChainDetails) {
|
|
1877
|
+
updateForegroundNestedProjection(foregroundControl);
|
|
1878
|
+
attachRootChildrenToSteps(runId, rawChainDetails.results, foregroundControl.nestedChildren);
|
|
1879
|
+
rawChainDetails.totalCost = sumResultsCost(rawChainDetails.results);
|
|
1880
|
+
}
|
|
1881
|
+
const chainDetails = rawChainDetails ? compactForegroundDetails(rawChainDetails) : undefined;
|
|
1783
1882
|
if (chainDetails) rememberForegroundRun(deps.state, { runId, mode: "chain", cwd: effectiveCwd, results: chainDetails.results });
|
|
1784
1883
|
const intercomReceipt = chainDetails && !chainDetails.results.some((result) => result.interrupted || result.detached)
|
|
1785
1884
|
? await maybeBuildForegroundIntercomReceipt({
|
|
@@ -1813,9 +1912,11 @@ interface ForegroundParallelRunInput {
|
|
|
1813
1912
|
sessionDirForIndex: (idx?: number) => string | undefined;
|
|
1814
1913
|
sessionFileForIndex: (idx?: number) => string | undefined;
|
|
1815
1914
|
sessionFileForTask: (agentName: string, idx?: number) => string | undefined;
|
|
1915
|
+
thinkingOverrideForTask: (agentName: string, idx?: number) => AgentConfig["thinking"] | undefined;
|
|
1816
1916
|
shareEnabled: boolean;
|
|
1817
1917
|
artifactConfig: ArtifactConfig;
|
|
1818
1918
|
artifactsDir: string;
|
|
1919
|
+
outputBaseDir: string;
|
|
1819
1920
|
maxOutput?: MaxOutputConfig;
|
|
1820
1921
|
paramsCwd: string;
|
|
1821
1922
|
progressDir: string;
|
|
@@ -1830,6 +1931,7 @@ interface ForegroundParallelRunInput {
|
|
|
1830
1931
|
orchestratorIntercomTarget?: string;
|
|
1831
1932
|
foregroundControl?: SubagentState["foregroundControls"] extends Map<string, infer T> ? T : never;
|
|
1832
1933
|
concurrencyLimit: number;
|
|
1934
|
+
globalSemaphore?: Semaphore;
|
|
1833
1935
|
liveResults: (SingleResult | undefined)[];
|
|
1834
1936
|
liveProgress: (AgentProgress | undefined)[];
|
|
1835
1937
|
onUpdate?: (r: AgentToolResult<Details>) => void;
|
|
@@ -1853,6 +1955,7 @@ function createParallelWorktreeSetup(
|
|
|
1853
1955
|
tasks: TaskParam[],
|
|
1854
1956
|
setupHook: ExtensionConfig["worktreeSetupHook"],
|
|
1855
1957
|
setupHookTimeoutMs: ExtensionConfig["worktreeSetupHookTimeoutMs"],
|
|
1958
|
+
baseDir: ExtensionConfig["worktreeBaseDir"],
|
|
1856
1959
|
): { setup?: WorktreeSetup; errorResult?: AgentToolResult<Details> } {
|
|
1857
1960
|
if (!enabled) return {};
|
|
1858
1961
|
try {
|
|
@@ -1862,6 +1965,7 @@ function createParallelWorktreeSetup(
|
|
|
1862
1965
|
setupHook: setupHook
|
|
1863
1966
|
? { hookPath: setupHook, timeoutMs: setupHookTimeoutMs }
|
|
1864
1967
|
: undefined,
|
|
1968
|
+
baseDir,
|
|
1865
1969
|
}),
|
|
1866
1970
|
};
|
|
1867
1971
|
} catch (error) {
|
|
@@ -1879,6 +1983,12 @@ function buildParallelWorktreeTaskCwdError(
|
|
|
1879
1983
|
return formatWorktreeTaskCwdConflict(conflict, sharedCwd);
|
|
1880
1984
|
}
|
|
1881
1985
|
|
|
1986
|
+
function resolveSingleRunOutputBaseDir(deps: ExecutorDeps, artifactsDir: string, runId: string): string {
|
|
1987
|
+
return deps.config.singleRunOutputBaseDir
|
|
1988
|
+
? path.resolve(deps.expandTilde(deps.config.singleRunOutputBaseDir))
|
|
1989
|
+
: path.join(artifactsDir, "outputs", runId);
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1882
1992
|
function buildChainWorktreeTaskCwdError(chain: ChainStep[], sharedCwd: string): string | undefined {
|
|
1883
1993
|
for (let stepIndex = 0; stepIndex < chain.length; stepIndex++) {
|
|
1884
1994
|
const step = chain[stepIndex]!;
|
|
@@ -1918,6 +2028,7 @@ function findDuplicateParallelOutputPath(input: {
|
|
|
1918
2028
|
behaviors: ResolvedStepBehavior[];
|
|
1919
2029
|
paramsCwd: string;
|
|
1920
2030
|
ctxCwd: string;
|
|
2031
|
+
outputBaseDir: string;
|
|
1921
2032
|
worktreeSetup?: WorktreeSetup;
|
|
1922
2033
|
}): string | undefined {
|
|
1923
2034
|
const seen = new Map<string, { index: number; agent: string }>();
|
|
@@ -1926,7 +2037,7 @@ function findDuplicateParallelOutputPath(input: {
|
|
|
1926
2037
|
if (!behavior?.output) continue;
|
|
1927
2038
|
const task = input.tasks[index]!;
|
|
1928
2039
|
const taskCwd = resolveParallelTaskCwd(task, input.paramsCwd, input.worktreeSetup, index);
|
|
1929
|
-
const outputPath = resolveSingleOutputPath(behavior.output, input.ctxCwd, taskCwd);
|
|
2040
|
+
const outputPath = resolveSingleOutputPath(behavior.output, input.ctxCwd, taskCwd, input.outputBaseDir);
|
|
1930
2041
|
if (!outputPath) continue;
|
|
1931
2042
|
const previous = seen.get(outputPath);
|
|
1932
2043
|
if (previous) {
|
|
@@ -1938,6 +2049,12 @@ function findDuplicateParallelOutputPath(input: {
|
|
|
1938
2049
|
}
|
|
1939
2050
|
|
|
1940
2051
|
async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Promise<SingleResult[]> {
|
|
2052
|
+
// Pre-warm fork session files sequentially before concurrent dispatch to avoid
|
|
2053
|
+
// races where multiple workers simultaneously try to branch the same parent session.
|
|
2054
|
+
// sessionFileForIndex caches results, so these calls return instantly inside mapConcurrent.
|
|
2055
|
+
for (let i = 0; i < input.tasks.length; i++) {
|
|
2056
|
+
input.sessionFileForIndex(i);
|
|
2057
|
+
}
|
|
1941
2058
|
return mapConcurrent(input.tasks, input.concurrencyLimit, async (task, index) => {
|
|
1942
2059
|
const behavior = input.behaviors[index];
|
|
1943
2060
|
const effectiveSkills = behavior?.skills;
|
|
@@ -1948,7 +2065,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
1948
2065
|
const progressInstructions = behavior
|
|
1949
2066
|
? buildChainInstructions({ ...behavior, output: false, reads: false }, input.progressDir, index === input.firstProgressIndex)
|
|
1950
2067
|
: { prefix: "", suffix: "" };
|
|
1951
|
-
const outputPath = resolveSingleOutputPath(behavior?.output, input.ctx.cwd, taskCwd);
|
|
2068
|
+
const outputPath = resolveSingleOutputPath(behavior?.output, input.ctx.cwd, taskCwd, input.outputBaseDir);
|
|
1952
2069
|
const taskText = injectSingleOutputInstruction(
|
|
1953
2070
|
`${readInstructions.prefix}${input.taskTexts[index]!}${progressInstructions.suffix}`,
|
|
1954
2071
|
outputPath,
|
|
@@ -1992,6 +2109,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
1992
2109
|
orchestratorIntercomTarget: input.orchestratorIntercomTarget,
|
|
1993
2110
|
nestedRoute: input.foregroundControl?.nestedRoute,
|
|
1994
2111
|
modelOverride: input.modelOverrides[index],
|
|
2112
|
+
thinkingOverride: input.thinkingOverrideForTask(task.agent, index),
|
|
1995
2113
|
availableModels: input.availableModels,
|
|
1996
2114
|
preferredModelProvider: input.ctx.model?.provider,
|
|
1997
2115
|
skills: effectiveSkills === false ? [] : effectiveSkills,
|
|
@@ -2039,7 +2157,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
2039
2157
|
input.foregroundControl.updatedAt = Date.now();
|
|
2040
2158
|
}
|
|
2041
2159
|
});
|
|
2042
|
-
});
|
|
2160
|
+
}, input.globalSemaphore);
|
|
2043
2161
|
}
|
|
2044
2162
|
|
|
2045
2163
|
async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps): Promise<AgentToolResult<Details>> {
|
|
@@ -2053,6 +2171,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2053
2171
|
sessionDirForIndex,
|
|
2054
2172
|
sessionFileForIndex,
|
|
2055
2173
|
sessionFileForTask,
|
|
2174
|
+
thinkingOverrideForTask,
|
|
2056
2175
|
shareEnabled,
|
|
2057
2176
|
artifactConfig,
|
|
2058
2177
|
artifactsDir,
|
|
@@ -2163,9 +2282,6 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2163
2282
|
}
|
|
2164
2283
|
|
|
2165
2284
|
if (result.runInBackground) {
|
|
2166
|
-
if (data.timeoutMs !== undefined) {
|
|
2167
|
-
return buildRequestedModeError(params, "timeoutMs/maxRuntimeMs are only supported for foreground runs; background launch from clarify cannot preserve the timeout.");
|
|
2168
|
-
}
|
|
2169
2285
|
if (!isAsyncAvailable()) {
|
|
2170
2286
|
return {
|
|
2171
2287
|
content: [{ type: "text", text: "Background mode requires upstream jiti for TypeScript execution but it could not be found. Ensure the pi-subagents package dependencies are installed." }],
|
|
@@ -2212,12 +2328,16 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2212
2328
|
sessionRoot,
|
|
2213
2329
|
chainSkills: [],
|
|
2214
2330
|
sessionFilesByFlatIndex: tasks.map((task, index) => sessionFileForTask(task.agent, index)),
|
|
2331
|
+
thinkingOverridesByFlatIndex: tasks.map((task, index) => thinkingOverrideForTask(task.agent, index)),
|
|
2215
2332
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
2216
2333
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
2217
2334
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
2335
|
+
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
2218
2336
|
controlConfig,
|
|
2219
2337
|
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
2220
2338
|
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
|
|
2339
|
+
timeoutMs: data.timeoutMs,
|
|
2340
|
+
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
2221
2341
|
});
|
|
2222
2342
|
}
|
|
2223
2343
|
}
|
|
@@ -2234,21 +2354,24 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2234
2354
|
tasks,
|
|
2235
2355
|
deps.config.worktreeSetupHook,
|
|
2236
2356
|
deps.config.worktreeSetupHookTimeoutMs,
|
|
2357
|
+
deps.config.worktreeBaseDir,
|
|
2237
2358
|
);
|
|
2238
2359
|
if (errorResult) return errorResult;
|
|
2239
2360
|
|
|
2240
2361
|
try {
|
|
2362
|
+
const outputBaseDir = path.join(artifactsDir, "outputs", runId);
|
|
2241
2363
|
const duplicateOutputError = findDuplicateParallelOutputPath({
|
|
2242
2364
|
tasks,
|
|
2243
2365
|
behaviors,
|
|
2244
2366
|
paramsCwd: effectiveCwd,
|
|
2245
2367
|
ctxCwd: ctx.cwd,
|
|
2368
|
+
outputBaseDir,
|
|
2246
2369
|
worktreeSetup,
|
|
2247
2370
|
});
|
|
2248
2371
|
if (duplicateOutputError) return buildParallelModeError(duplicateOutputError);
|
|
2249
2372
|
for (let index = 0; index < tasks.length; index++) {
|
|
2250
2373
|
const taskCwd = resolveParallelTaskCwd(tasks[index]!, effectiveCwd, worktreeSetup, index);
|
|
2251
|
-
const outputPath = resolveSingleOutputPath(behaviors[index]?.output, ctx.cwd, taskCwd);
|
|
2374
|
+
const outputPath = resolveSingleOutputPath(behaviors[index]?.output, ctx.cwd, taskCwd, outputBaseDir);
|
|
2252
2375
|
const validationError = validateFileOnlyOutputMode(behaviors[index]?.outputMode, outputPath, `Parallel task ${index + 1} (${tasks[index]!.agent})`);
|
|
2253
2376
|
if (validationError) return buildParallelModeError(validationError);
|
|
2254
2377
|
}
|
|
@@ -2273,9 +2396,11 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2273
2396
|
sessionDirForIndex,
|
|
2274
2397
|
sessionFileForIndex,
|
|
2275
2398
|
sessionFileForTask,
|
|
2399
|
+
thinkingOverrideForTask,
|
|
2276
2400
|
shareEnabled,
|
|
2277
2401
|
artifactConfig,
|
|
2278
2402
|
artifactsDir,
|
|
2403
|
+
outputBaseDir,
|
|
2279
2404
|
maxOutput: params.maxOutput,
|
|
2280
2405
|
paramsCwd: effectiveCwd,
|
|
2281
2406
|
progressDir: parallelProgressDir,
|
|
@@ -2289,6 +2414,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2289
2414
|
orchestratorIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
2290
2415
|
foregroundControl,
|
|
2291
2416
|
concurrencyLimit: parallelConcurrency,
|
|
2417
|
+
globalSemaphore: new Semaphore(deps.config.globalConcurrencyLimit ?? DEFAULT_GLOBAL_CONCURRENCY_LIMIT),
|
|
2292
2418
|
maxSubagentDepths,
|
|
2293
2419
|
liveResults,
|
|
2294
2420
|
liveProgress,
|
|
@@ -2307,6 +2433,10 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2307
2433
|
if (result.artifactPaths) allArtifactPaths.push(result.artifactPaths);
|
|
2308
2434
|
}
|
|
2309
2435
|
|
|
2436
|
+
if (foregroundControl) {
|
|
2437
|
+
updateForegroundNestedProjection(foregroundControl);
|
|
2438
|
+
attachRootChildrenToSteps(runId, results, foregroundControl.nestedChildren);
|
|
2439
|
+
}
|
|
2310
2440
|
const interrupted = results.find((result) => result.interrupted);
|
|
2311
2441
|
const details = compactForegroundDetails({
|
|
2312
2442
|
mode: "parallel",
|
|
@@ -2314,6 +2444,8 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2314
2444
|
results,
|
|
2315
2445
|
progress: params.includeProgress ? allProgress : undefined,
|
|
2316
2446
|
artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
|
|
2447
|
+
totalChildUsage: sumResultsUsage(results),
|
|
2448
|
+
totalCost: sumResultsCost(results),
|
|
2317
2449
|
});
|
|
2318
2450
|
rememberForegroundRun(deps.state, { runId, mode: "parallel", cwd: effectiveCwd, results: details.results });
|
|
2319
2451
|
if (interrupted) {
|
|
@@ -2385,6 +2517,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2385
2517
|
runId,
|
|
2386
2518
|
sessionDirForIndex,
|
|
2387
2519
|
sessionFileForTask,
|
|
2520
|
+
thinkingOverrideForTask,
|
|
2388
2521
|
shareEnabled,
|
|
2389
2522
|
artifactConfig,
|
|
2390
2523
|
artifactsDir,
|
|
@@ -2455,9 +2588,6 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2455
2588
|
if (override?.skills !== undefined) skillOverride = override.skills;
|
|
2456
2589
|
|
|
2457
2590
|
if (result.runInBackground) {
|
|
2458
|
-
if (data.timeoutMs !== undefined) {
|
|
2459
|
-
return buildRequestedModeError(params, "timeoutMs/maxRuntimeMs are only supported for foreground runs; background launch from clarify cannot preserve the timeout.");
|
|
2460
|
-
}
|
|
2461
2591
|
if (!isAsyncAvailable()) {
|
|
2462
2592
|
return {
|
|
2463
2593
|
content: [{ type: "text", text: "Background mode requires upstream jiti for TypeScript execution but it could not be found. Ensure the pi-subagents package dependencies are installed." }],
|
|
@@ -2490,13 +2620,17 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2490
2620
|
skills: skillOverride === false ? [] : skillOverride,
|
|
2491
2621
|
output: effectiveOutput,
|
|
2492
2622
|
outputMode: effectiveOutputMode,
|
|
2623
|
+
outputBaseDir: resolveSingleRunOutputBaseDir(deps, artifactsDir, id),
|
|
2493
2624
|
modelOverride,
|
|
2625
|
+
thinkingOverride: thinkingOverrideForTask(params.agent!, 0),
|
|
2494
2626
|
maxSubagentDepth,
|
|
2495
2627
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
2496
2628
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
2629
|
+
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
2497
2630
|
controlConfig,
|
|
2498
2631
|
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
2499
2632
|
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
|
|
2633
|
+
timeoutMs: data.timeoutMs,
|
|
2500
2634
|
});
|
|
2501
2635
|
}
|
|
2502
2636
|
}
|
|
@@ -2505,7 +2639,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2505
2639
|
task = wrapForkTask(task);
|
|
2506
2640
|
}
|
|
2507
2641
|
const cleanTask = task;
|
|
2508
|
-
const outputPath = resolveSingleOutputPath(effectiveOutput, ctx.cwd, effectiveCwd);
|
|
2642
|
+
const outputPath = resolveSingleOutputPath(effectiveOutput, ctx.cwd, effectiveCwd, resolveSingleRunOutputBaseDir(deps, artifactsDir, runId));
|
|
2509
2643
|
const validationError = validateFileOnlyOutputMode(effectiveOutputMode, outputPath, `Single run (${params.agent})`);
|
|
2510
2644
|
if (validationError) {
|
|
2511
2645
|
return { content: [{ type: "text", text: validationError }], isError: true, details: { mode: "single", results: [] } };
|
|
@@ -2580,6 +2714,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2580
2714
|
nestedRoute: foregroundControl?.nestedRoute,
|
|
2581
2715
|
index: 0,
|
|
2582
2716
|
modelOverride,
|
|
2717
|
+
thinkingOverride: thinkingOverrideForTask(params.agent!, 0),
|
|
2583
2718
|
availableModels,
|
|
2584
2719
|
preferredModelProvider: currentProvider,
|
|
2585
2720
|
skills: effectiveSkills,
|
|
@@ -2616,6 +2751,10 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2616
2751
|
outputReference: r.outputReference,
|
|
2617
2752
|
saveError: r.outputSaveError,
|
|
2618
2753
|
});
|
|
2754
|
+
if (foregroundControl) {
|
|
2755
|
+
updateForegroundNestedProjection(foregroundControl);
|
|
2756
|
+
attachRootChildrenToSteps(runId, [r], foregroundControl.nestedChildren);
|
|
2757
|
+
}
|
|
2619
2758
|
const details = compactForegroundDetails({
|
|
2620
2759
|
mode: "single",
|
|
2621
2760
|
runId,
|
|
@@ -2623,6 +2762,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2623
2762
|
progress: params.includeProgress ? allProgress : undefined,
|
|
2624
2763
|
artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
|
|
2625
2764
|
truncation: r.truncation,
|
|
2765
|
+
totalChildUsage: sumResultsUsage([r]),
|
|
2766
|
+
totalCost: sumResultsCost([r]),
|
|
2626
2767
|
});
|
|
2627
2768
|
rememberForegroundRun(deps.state, { runId, mode: "single", cwd: effectiveCwd, results: details.results });
|
|
2628
2769
|
|
|
@@ -2741,6 +2882,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
2741
2882
|
orchestratorTarget,
|
|
2742
2883
|
sessionError,
|
|
2743
2884
|
expandTilde: deps.expandTilde,
|
|
2885
|
+
companionPackageLines: deps.companionSuggestionLines?.({
|
|
2886
|
+
surface: "doctor",
|
|
2887
|
+
cwd: requestCwd,
|
|
2888
|
+
context: paramsWithResolvedCwd.context,
|
|
2889
|
+
orchestratorTarget,
|
|
2890
|
+
}),
|
|
2744
2891
|
}),
|
|
2745
2892
|
}],
|
|
2746
2893
|
details: { mode: "management", results: [] },
|
|
@@ -2828,7 +2975,21 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
2828
2975
|
details: { mode: "management" as const, results: [] },
|
|
2829
2976
|
};
|
|
2830
2977
|
}
|
|
2831
|
-
return handleManagementAction(params.action, paramsWithResolvedCwd, {
|
|
2978
|
+
return handleManagementAction(params.action, paramsWithResolvedCwd, {
|
|
2979
|
+
...ctx,
|
|
2980
|
+
cwd: requestCwd,
|
|
2981
|
+
config: deps.config,
|
|
2982
|
+
companionSuggestionLines: () => {
|
|
2983
|
+
if (params.action !== "list" || deps.state.companionSuggestionListShown) return [];
|
|
2984
|
+
const lines = deps.companionSuggestionLines?.({
|
|
2985
|
+
surface: "list",
|
|
2986
|
+
cwd: requestCwd,
|
|
2987
|
+
context: paramsWithResolvedCwd.context,
|
|
2988
|
+
}) ?? [];
|
|
2989
|
+
if (lines.length > 0) deps.state.companionSuggestionListShown = true;
|
|
2990
|
+
return lines;
|
|
2991
|
+
},
|
|
2992
|
+
});
|
|
2832
2993
|
}
|
|
2833
2994
|
|
|
2834
2995
|
const { blocked, depth, maxDepth } = checkSubagentDepth(deps.config.maxSubagentDepth);
|
|
@@ -2901,24 +3062,24 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
2901
3062
|
if (validationError) return validationError;
|
|
2902
3063
|
|
|
2903
3064
|
let forkSessionFileForIndex: (idx?: number) => string | undefined = () => undefined;
|
|
3065
|
+
let forkThinkingOverrideForIndex: (idx?: number) => AgentConfig["thinking"] | undefined = () => undefined;
|
|
2904
3066
|
try {
|
|
2905
|
-
|
|
3067
|
+
const forkContextResolver = createForkContextResolver(ctx.sessionManager, contextPolicy.usesFork ? "fork" : undefined);
|
|
3068
|
+
forkSessionFileForIndex = forkContextResolver.sessionFileForIndex;
|
|
3069
|
+
forkThinkingOverrideForIndex = forkContextResolver.thinkingOverrideForIndex;
|
|
2906
3070
|
} catch (error) {
|
|
2907
3071
|
return toExecutionErrorResult(effectiveParams, error);
|
|
2908
3072
|
}
|
|
2909
3073
|
const requestedAsync = effectiveParams.async ?? deps.asyncByDefault;
|
|
2910
3074
|
const backgroundRequestedWhileClarifying = (hasChain || hasTasks) && requestedAsync && effectiveParams.clarify === true;
|
|
2911
3075
|
const effectiveAsync = requestedAsync && effectiveParams.clarify !== true;
|
|
2912
|
-
if (foregroundTimeout.timeoutMs !== undefined && effectiveAsync) {
|
|
2913
|
-
return buildRequestedModeError(effectiveParams, "timeoutMs/maxRuntimeMs are only supported for foreground runs; set async: false or omit the timeout for background runs.");
|
|
2914
|
-
}
|
|
2915
3076
|
const controlConfig = resolveControlConfig(deps.config.control, effectiveParams.control);
|
|
2916
3077
|
|
|
2917
3078
|
const artifactConfig: ArtifactConfig = {
|
|
2918
3079
|
...DEFAULT_ARTIFACT_CONFIG,
|
|
2919
3080
|
enabled: effectiveParams.artifacts !== false,
|
|
2920
3081
|
};
|
|
2921
|
-
const artifactsDir =
|
|
3082
|
+
const artifactsDir = getArtifactsDir(parentSessionFile, effectiveCwd);
|
|
2922
3083
|
|
|
2923
3084
|
let sessionRoot: string;
|
|
2924
3085
|
if (effectiveParams.sessionDir) {
|
|
@@ -2942,12 +3103,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
2942
3103
|
path.join(sessionRoot, `run-${idx ?? 0}`);
|
|
2943
3104
|
const forkSessionFileForTask = (agentName: string, idx?: number) =>
|
|
2944
3105
|
shouldForkAgent(contextPolicy, agentName) ? forkSessionFileForIndex(idx) : undefined;
|
|
3106
|
+
const forkThinkingOverrideForTask = (agentName: string, idx?: number) =>
|
|
3107
|
+
shouldForkAgent(contextPolicy, agentName) ? forkThinkingOverrideForIndex(idx) : undefined;
|
|
2945
3108
|
const childSessionFileForTask = (agentName: string, idx?: number) =>
|
|
2946
3109
|
forkSessionFileForTask(agentName, idx) ?? path.join(sessionDirForIndex(idx), "session.jsonl");
|
|
2947
3110
|
const childSessionFileForIndex = (idx?: number) =>
|
|
2948
3111
|
path.join(sessionDirForIndex(idx), "session.jsonl");
|
|
2949
3112
|
try {
|
|
2950
|
-
preflightForkSessionsForStaticTasks(effectiveParams, contextPolicy, forkSessionFileForTask);
|
|
3113
|
+
preflightForkSessionsForStaticTasks(effectiveParams, contextPolicy, forkSessionFileForTask, deps.config.chain?.dynamicFanout?.maxItems);
|
|
2951
3114
|
} catch (error) {
|
|
2952
3115
|
return toExecutionErrorResult(effectiveParams, error);
|
|
2953
3116
|
}
|
|
@@ -2958,6 +3121,16 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
2958
3121
|
? (r: AgentToolResult<Details>) => onUpdate(withForkContext(r, effectiveParams.context))
|
|
2959
3122
|
: undefined;
|
|
2960
3123
|
|
|
3124
|
+
const foregroundMode: "single" | "parallel" | "chain" = hasChain ? "chain" : hasTasks ? "parallel" : "single";
|
|
3125
|
+
const spawnLimitError = reserveSubagentSpawns({
|
|
3126
|
+
state: deps.state,
|
|
3127
|
+
config: deps.config,
|
|
3128
|
+
sessionId: deps.state.currentSessionId,
|
|
3129
|
+
requested: countRequestedSubagentSpawns(effectiveParams, deps.config),
|
|
3130
|
+
mode: foregroundMode,
|
|
3131
|
+
});
|
|
3132
|
+
if (spawnLimitError) return spawnLimitError;
|
|
3133
|
+
|
|
2961
3134
|
const execData: ExecutionContextData = {
|
|
2962
3135
|
params: effectiveParams,
|
|
2963
3136
|
effectiveCwd,
|
|
@@ -2971,6 +3144,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
2971
3144
|
sessionDirForIndex,
|
|
2972
3145
|
sessionFileForIndex: childSessionFileForIndex,
|
|
2973
3146
|
sessionFileForTask: childSessionFileForTask,
|
|
3147
|
+
thinkingOverrideForTask: forkThinkingOverrideForTask,
|
|
2974
3148
|
artifactConfig,
|
|
2975
3149
|
artifactsDir,
|
|
2976
3150
|
backgroundRequestedWhileClarifying,
|
|
@@ -2982,7 +3156,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
2982
3156
|
contextPolicy,
|
|
2983
3157
|
};
|
|
2984
3158
|
|
|
2985
|
-
const foregroundMode: "single" | "parallel" | "chain" = hasChain ? "chain" : hasTasks ? "parallel" : "single";
|
|
2986
3159
|
const foregroundControl = effectiveAsync
|
|
2987
3160
|
? undefined
|
|
2988
3161
|
: {
|
|
@@ -3046,6 +3219,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3046
3219
|
startedAt: foregroundControl?.startedAt ?? now,
|
|
3047
3220
|
...(state !== "running" ? { endedAt: now } : {}),
|
|
3048
3221
|
lastUpdate: now,
|
|
3222
|
+
...(details?.totalCost ? { totalCost: details.totalCost } : {}),
|
|
3049
3223
|
...(errorText ? { error: errorText } : {}),
|
|
3050
3224
|
...(details?.results.length ? { steps: details.results.map((child) => ({
|
|
3051
3225
|
agent: child.agent,
|