pi-subagents 0.31.0 → 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 +48 -0
- package/README.md +170 -8
- 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 +69 -4
- package/src/extension/schemas.ts +2 -2
- package/src/intercom/intercom-bridge.ts +25 -1
- package/src/profiles/profiles.ts +637 -0
- package/src/runs/background/async-execution.ts +138 -33
- package/src/runs/background/async-job-tracker.ts +77 -1
- package/src/runs/background/async-resume.ts +11 -13
- 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 +227 -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 +459 -113
- package/src/runs/foreground/chain-execution.ts +29 -7
- package/src/runs/foreground/execution.ts +24 -6
- package/src/runs/foreground/subagent-executor.ts +240 -44
- 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 +3 -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 +82 -3
- package/src/shared/utils.ts +99 -14
- package/src/slash/slash-commands.ts +726 -40
- 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,
|
|
@@ -51,8 +52,10 @@ import {
|
|
|
51
52
|
stripDetailsOutputsForIntercomReceipt,
|
|
52
53
|
} from "../../intercom/result-intercom.ts";
|
|
53
54
|
import { buildRevivedAsyncTask, interruptLiveAsyncResumeTarget, resolveAsyncResumeTarget } from "../background/async-resume.ts";
|
|
55
|
+
import { deliverInterruptRequest } from "../background/control-channel.ts";
|
|
56
|
+
import { reconcileAsyncRun } from "../background/stale-run-reconciler.ts";
|
|
54
57
|
import { resolveAsyncRootResultPath } from "../background/chain-root-attachment.ts";
|
|
55
|
-
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";
|
|
56
59
|
import { resolveSubagentRunId, type ResolvedSubagentRunId } from "../background/run-id-resolver.ts";
|
|
57
60
|
import { formatNestedRunStatusLines } from "../shared/nested-render.ts";
|
|
58
61
|
import { inspectSubagentStatus } from "../background/run-status.ts";
|
|
@@ -90,6 +93,7 @@ import {
|
|
|
90
93
|
SUBAGENT_CONTROL_EVENT,
|
|
91
94
|
SUBAGENT_CONTROL_INTERCOM_EVENT,
|
|
92
95
|
checkSubagentDepth,
|
|
96
|
+
resolveMaxSubagentSpawnsPerSession,
|
|
93
97
|
resolveTopLevelParallelConcurrency,
|
|
94
98
|
resolveTopLevelParallelMaxTasks,
|
|
95
99
|
resolveChildMaxSubagentDepth,
|
|
@@ -97,7 +101,6 @@ import {
|
|
|
97
101
|
wrapForkTask,
|
|
98
102
|
} from "../../shared/types.ts";
|
|
99
103
|
|
|
100
|
-
const ASYNC_INTERRUPT_SIGNAL: NodeJS.Signals = process.platform === "win32" ? "SIGBREAK" : "SIGUSR2";
|
|
101
104
|
const MUTATING_MANAGEMENT_ACTIONS = new Set(["create", "update", "delete"]);
|
|
102
105
|
|
|
103
106
|
interface TaskParam {
|
|
@@ -153,6 +156,7 @@ interface ExecutorDeps {
|
|
|
153
156
|
state: SubagentState;
|
|
154
157
|
config: ExtensionConfig;
|
|
155
158
|
asyncByDefault: boolean;
|
|
159
|
+
companionSuggestionLines?: (input: { surface: "list" | "doctor"; cwd: string; context?: "fresh" | "fork"; orchestratorTarget?: string }) => string[];
|
|
156
160
|
tempArtifactsDir: string;
|
|
157
161
|
getSubagentSessionRoot: (parentSessionFile: string | null) => string;
|
|
158
162
|
expandTilde: (p: string) => string;
|
|
@@ -174,6 +178,7 @@ interface ExecutionContextData {
|
|
|
174
178
|
sessionDirForIndex: (idx?: number) => string;
|
|
175
179
|
sessionFileForIndex: (idx?: number) => string | undefined;
|
|
176
180
|
sessionFileForTask: (agentName: string, idx?: number) => string | undefined;
|
|
181
|
+
thinkingOverrideForTask: (agentName: string, idx?: number) => AgentConfig["thinking"] | undefined;
|
|
177
182
|
artifactConfig: ArtifactConfig;
|
|
178
183
|
artifactsDir: string;
|
|
179
184
|
backgroundRequestedWhileClarifying: boolean;
|
|
@@ -232,6 +237,35 @@ function nestedResolutionScopeForExecutor(deps: ExecutorDeps): NestedRunResoluti
|
|
|
232
237
|
};
|
|
233
238
|
}
|
|
234
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
|
+
|
|
235
269
|
function foregroundStatusResult(control: SubagentState["foregroundControls"] extends Map<string, infer T> ? T : never): AgentToolResult<Details> {
|
|
236
270
|
let nestedWarning: string | undefined;
|
|
237
271
|
try {
|
|
@@ -372,7 +406,17 @@ function resolveResumeTarget(params: SubagentParamsLike, state: SubagentState, o
|
|
|
372
406
|
throw new Error("Run not found. Provide id or runId.");
|
|
373
407
|
}
|
|
374
408
|
|
|
375
|
-
function getAsyncInterruptTarget(
|
|
409
|
+
function getAsyncInterruptTarget(
|
|
410
|
+
state: SubagentState,
|
|
411
|
+
runId: string | undefined,
|
|
412
|
+
location?: { asyncDir: string | null; resolvedId?: string },
|
|
413
|
+
): { asyncId: string; asyncDir: string } | undefined {
|
|
414
|
+
if (location?.asyncDir) {
|
|
415
|
+
return {
|
|
416
|
+
asyncId: location.resolvedId ?? runId ?? path.basename(location.asyncDir),
|
|
417
|
+
asyncDir: location.asyncDir,
|
|
418
|
+
};
|
|
419
|
+
}
|
|
376
420
|
if (runId) {
|
|
377
421
|
const direct = state.asyncJobs.get(runId);
|
|
378
422
|
if (direct) return { asyncId: direct.asyncId, asyncDir: direct.asyncDir };
|
|
@@ -415,10 +459,15 @@ function emitControlNotification(input: {
|
|
|
415
459
|
}
|
|
416
460
|
}
|
|
417
461
|
|
|
418
|
-
function interruptAsyncRun(
|
|
419
|
-
|
|
462
|
+
function interruptAsyncRun(
|
|
463
|
+
state: SubagentState,
|
|
464
|
+
runId: string | undefined,
|
|
465
|
+
kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean,
|
|
466
|
+
location?: { asyncDir: string | null; resolvedId?: string },
|
|
467
|
+
): AgentToolResult<Details> | null {
|
|
468
|
+
const target = getAsyncInterruptTarget(state, runId, location);
|
|
420
469
|
if (!target) return null;
|
|
421
|
-
const status =
|
|
470
|
+
const status = reconcileAsyncRun(target.asyncDir, { kill }).status;
|
|
422
471
|
if (!status || status.state !== "running" || typeof status.pid !== "number") {
|
|
423
472
|
return {
|
|
424
473
|
content: [{ type: "text", text: `No running async run with an interrupt-capable pid was found for '${runId ?? "current"}'.` }],
|
|
@@ -427,7 +476,7 @@ function interruptAsyncRun(state: SubagentState, runId: string | undefined, kill
|
|
|
427
476
|
};
|
|
428
477
|
}
|
|
429
478
|
try {
|
|
430
|
-
(
|
|
479
|
+
deliverInterruptRequest({ asyncDir: target.asyncDir, pid: status.pid, kill, source: "interrupt-action" });
|
|
431
480
|
const tracked = state.asyncJobs.get(target.asyncId);
|
|
432
481
|
if (tracked) {
|
|
433
482
|
tracked.activityState = undefined;
|
|
@@ -719,11 +768,11 @@ function directNestedAsyncInterrupt(target: ResolvedSubagentRunId & { kind: "nes
|
|
|
719
768
|
const run = target.match.run;
|
|
720
769
|
const asyncDir = resolveNestedAsyncDir(target.match.rootRunId, run);
|
|
721
770
|
if (!asyncDir) return undefined;
|
|
722
|
-
const status =
|
|
771
|
+
const status = reconcileAsyncRun(asyncDir, { resultsDir: path.join(RESULTS_DIR, "nested", target.match.rootRunId) }).status;
|
|
723
772
|
const pid = typeof status?.pid === "number" && status.pid > 0 ? status.pid : run.pid;
|
|
724
773
|
if (!status || status.state !== "running" || typeof pid !== "number" || pid <= 0) return undefined;
|
|
725
774
|
try {
|
|
726
|
-
|
|
775
|
+
deliverInterruptRequest({ asyncDir, pid, source: "nested-interrupt" });
|
|
727
776
|
return { content: [{ type: "text", text: `Interrupt requested for nested async run ${run.id}.` }], details: { mode: "management", results: [] } };
|
|
728
777
|
} catch (error) {
|
|
729
778
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -800,6 +849,7 @@ async function resumeAsyncRun(input: {
|
|
|
800
849
|
target,
|
|
801
850
|
state: input.deps.state,
|
|
802
851
|
kill: input.deps.kill,
|
|
852
|
+
resultsDir: RESULTS_DIR,
|
|
803
853
|
});
|
|
804
854
|
if (!interrupt.ok) {
|
|
805
855
|
return {
|
|
@@ -904,7 +954,7 @@ async function resumeAsyncRun(input: {
|
|
|
904
954
|
availableModels,
|
|
905
955
|
cwd: effectiveCwd,
|
|
906
956
|
maxOutput: input.params.maxOutput,
|
|
907
|
-
artifactsDir:
|
|
957
|
+
artifactsDir: getArtifactsDir(parentSessionFile, effectiveCwd),
|
|
908
958
|
artifactConfig,
|
|
909
959
|
shareEnabled: input.params.share === true,
|
|
910
960
|
sessionRoot: input.deps.getSubagentSessionRoot(parentSessionFile),
|
|
@@ -913,9 +963,11 @@ async function resumeAsyncRun(input: {
|
|
|
913
963
|
maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
|
|
914
964
|
worktreeSetupHook: input.deps.config.worktreeSetupHook,
|
|
915
965
|
worktreeSetupHookTimeoutMs: input.deps.config.worktreeSetupHookTimeoutMs,
|
|
966
|
+
worktreeBaseDir: input.deps.config.worktreeBaseDir,
|
|
916
967
|
controlConfig: resolveControlConfig(input.deps.config.control, input.params.control),
|
|
917
968
|
controlIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
|
|
918
969
|
childIntercomTarget: intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
|
|
970
|
+
globalConcurrencyLimit: input.deps.config.globalConcurrencyLimit,
|
|
919
971
|
});
|
|
920
972
|
if (result.isError) return result;
|
|
921
973
|
const attachedId = result.details.asyncId ?? runId;
|
|
@@ -931,6 +983,7 @@ async function resumeAsyncRun(input: {
|
|
|
931
983
|
|
|
932
984
|
const runId = randomUUID().slice(0, 8);
|
|
933
985
|
const artifactConfig: ArtifactConfig = { ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false };
|
|
986
|
+
const artifactsDir = getArtifactsDir(parentSessionFile, effectiveCwd);
|
|
934
987
|
const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
935
988
|
const result = executeAsyncSingle(runId, {
|
|
936
989
|
agent: target.agent,
|
|
@@ -946,14 +999,16 @@ async function resumeAsyncRun(input: {
|
|
|
946
999
|
},
|
|
947
1000
|
cwd: effectiveCwd,
|
|
948
1001
|
maxOutput: input.params.maxOutput,
|
|
949
|
-
artifactsDir
|
|
1002
|
+
artifactsDir,
|
|
950
1003
|
artifactConfig,
|
|
951
1004
|
shareEnabled: input.params.share === true,
|
|
952
1005
|
sessionRoot: input.deps.getSubagentSessionRoot(parentSessionFile),
|
|
953
1006
|
sessionFile: target.sessionFile,
|
|
1007
|
+
outputBaseDir: resolveSingleRunOutputBaseDir(input.deps, artifactsDir, runId),
|
|
954
1008
|
maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
|
|
955
1009
|
worktreeSetupHook: input.deps.config.worktreeSetupHook,
|
|
956
1010
|
worktreeSetupHookTimeoutMs: input.deps.config.worktreeSetupHookTimeoutMs,
|
|
1011
|
+
worktreeBaseDir: input.deps.config.worktreeBaseDir,
|
|
957
1012
|
controlConfig: resolveControlConfig(input.deps.config.control, input.params.control),
|
|
958
1013
|
controlIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
|
|
959
1014
|
childIntercomTarget: intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
|
|
@@ -1374,6 +1429,7 @@ function toExecutionErrorResult(params: SubagentParamsLike, error: unknown): Age
|
|
|
1374
1429
|
function collectChainSessionFiles(
|
|
1375
1430
|
chain: ChainStep[],
|
|
1376
1431
|
sessionFileForTask: (agentName: string, idx?: number) => string | undefined,
|
|
1432
|
+
dynamicFanoutMaxItems?: number,
|
|
1377
1433
|
): (string | undefined)[] {
|
|
1378
1434
|
const sessionFiles: (string | undefined)[] = [];
|
|
1379
1435
|
let flatIndex = 0;
|
|
@@ -1386,7 +1442,11 @@ function collectChainSessionFiles(
|
|
|
1386
1442
|
continue;
|
|
1387
1443
|
}
|
|
1388
1444
|
if (isDynamicParallelStep(step)) {
|
|
1389
|
-
|
|
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
|
+
}
|
|
1390
1450
|
continue;
|
|
1391
1451
|
}
|
|
1392
1452
|
sessionFiles.push(sessionFileForTask((step as SequentialStep).agent, flatIndex));
|
|
@@ -1395,6 +1455,35 @@ function collectChainSessionFiles(
|
|
|
1395
1455
|
return sessionFiles;
|
|
1396
1456
|
}
|
|
1397
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
|
+
|
|
1398
1487
|
function wrapChainTasksForFork(chain: ChainStep[], contextPolicy: AgentDefaultContextPolicy): ChainStep[] {
|
|
1399
1488
|
return chain.map((step, stepIndex) => {
|
|
1400
1489
|
if (isParallelStep(step)) {
|
|
@@ -1433,6 +1522,7 @@ function preflightForkSessionsForStaticTasks(
|
|
|
1433
1522
|
params: SubagentParamsLike,
|
|
1434
1523
|
contextPolicy: AgentDefaultContextPolicy,
|
|
1435
1524
|
sessionFileForTask: (agentName: string, idx?: number) => string | undefined,
|
|
1525
|
+
dynamicFanoutMaxItems?: number,
|
|
1436
1526
|
): void {
|
|
1437
1527
|
if (!contextPolicy.usesFork) return;
|
|
1438
1528
|
if (params.agent) {
|
|
@@ -1456,8 +1546,11 @@ function preflightForkSessionsForStaticTasks(
|
|
|
1456
1546
|
continue;
|
|
1457
1547
|
}
|
|
1458
1548
|
if (isDynamicParallelStep(step)) {
|
|
1459
|
-
|
|
1460
|
-
|
|
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;
|
|
1461
1554
|
continue;
|
|
1462
1555
|
}
|
|
1463
1556
|
const sequential = step as SequentialStep;
|
|
@@ -1476,6 +1569,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1476
1569
|
sessionRoot,
|
|
1477
1570
|
sessionFileForIndex,
|
|
1478
1571
|
sessionFileForTask,
|
|
1572
|
+
thinkingOverrideForTask,
|
|
1479
1573
|
artifactConfig,
|
|
1480
1574
|
artifactsDir,
|
|
1481
1575
|
effectiveAsync,
|
|
@@ -1569,13 +1663,17 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1569
1663
|
sessionRoot,
|
|
1570
1664
|
chainSkills: [],
|
|
1571
1665
|
sessionFilesByFlatIndex: params.tasks.map((task, index) => sessionFileForTask(task.agent, index)),
|
|
1666
|
+
thinkingOverridesByFlatIndex: params.tasks.map((task, index) => thinkingOverrideForTask(task.agent, index)),
|
|
1572
1667
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
1573
1668
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1574
1669
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
1670
|
+
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
1575
1671
|
controlConfig,
|
|
1576
1672
|
controlIntercomTarget,
|
|
1577
1673
|
childIntercomTarget,
|
|
1578
1674
|
nestedRoute,
|
|
1675
|
+
timeoutMs: data.timeoutMs,
|
|
1676
|
+
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
1579
1677
|
});
|
|
1580
1678
|
}
|
|
1581
1679
|
|
|
@@ -1596,15 +1694,19 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1596
1694
|
shareEnabled,
|
|
1597
1695
|
sessionRoot,
|
|
1598
1696
|
chainSkills,
|
|
1599
|
-
sessionFilesByFlatIndex: collectChainSessionFiles(chain, sessionFileForTask),
|
|
1697
|
+
sessionFilesByFlatIndex: collectChainSessionFiles(chain, sessionFileForTask, deps.config.chain?.dynamicFanout?.maxItems),
|
|
1698
|
+
thinkingOverridesByFlatIndex: collectChainThinkingOverrides(chain, thinkingOverrideForTask, deps.config.chain?.dynamicFanout?.maxItems),
|
|
1600
1699
|
dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
|
|
1601
1700
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
1602
1701
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1603
1702
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
1703
|
+
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
1604
1704
|
controlConfig,
|
|
1605
1705
|
controlIntercomTarget,
|
|
1606
1706
|
childIntercomTarget,
|
|
1607
1707
|
nestedRoute,
|
|
1708
|
+
timeoutMs: data.timeoutMs,
|
|
1709
|
+
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
1608
1710
|
});
|
|
1609
1711
|
}
|
|
1610
1712
|
|
|
@@ -1640,15 +1742,19 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1640
1742
|
skills,
|
|
1641
1743
|
output: effectiveOutput,
|
|
1642
1744
|
outputMode: effectiveOutputMode,
|
|
1745
|
+
outputBaseDir: resolveSingleRunOutputBaseDir(deps, artifactsDir, id),
|
|
1643
1746
|
modelOverride,
|
|
1747
|
+
thinkingOverride: thinkingOverrideForTask(params.agent!, 0),
|
|
1644
1748
|
maxSubagentDepth,
|
|
1645
1749
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1646
1750
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
1751
|
+
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
1647
1752
|
controlConfig,
|
|
1648
1753
|
controlIntercomTarget,
|
|
1649
1754
|
childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(agent, index) : undefined,
|
|
1650
1755
|
nestedRoute,
|
|
1651
1756
|
acceptance: params.acceptance,
|
|
1757
|
+
timeoutMs: data.timeoutMs,
|
|
1652
1758
|
});
|
|
1653
1759
|
}
|
|
1654
1760
|
|
|
@@ -1667,6 +1773,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
1667
1773
|
sessionDirForIndex,
|
|
1668
1774
|
sessionFileForIndex,
|
|
1669
1775
|
sessionFileForTask,
|
|
1776
|
+
thinkingOverrideForTask,
|
|
1670
1777
|
artifactsDir,
|
|
1671
1778
|
artifactConfig,
|
|
1672
1779
|
onUpdate,
|
|
@@ -1694,6 +1801,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
1694
1801
|
sessionDirForIndex,
|
|
1695
1802
|
sessionFileForIndex,
|
|
1696
1803
|
sessionFileForTask,
|
|
1804
|
+
thinkingOverrideForTask,
|
|
1697
1805
|
artifactsDir,
|
|
1698
1806
|
artifactConfig,
|
|
1699
1807
|
includeProgress: params.includeProgress,
|
|
@@ -1706,19 +1814,18 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
1706
1814
|
foregroundControl,
|
|
1707
1815
|
nestedRoute: foregroundControl?.nestedRoute,
|
|
1708
1816
|
chainSkills,
|
|
1709
|
-
chainDir: params.chainDir,
|
|
1817
|
+
chainDir: params.chainDir ?? getProjectChainRunsDir(effectiveCwd),
|
|
1710
1818
|
dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
|
|
1711
1819
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
1712
1820
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1713
1821
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
1822
|
+
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
1714
1823
|
timeoutMs: data.timeoutMs,
|
|
1715
1824
|
deadlineAt: data.deadlineAt,
|
|
1825
|
+
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
1716
1826
|
});
|
|
1717
1827
|
|
|
1718
1828
|
if (chainResult.requestedAsync) {
|
|
1719
|
-
if (data.timeoutMs !== undefined) {
|
|
1720
|
-
return buildRequestedModeError(params, "timeoutMs/maxRuntimeMs are only supported for foreground runs; background launch from clarify cannot preserve the timeout.");
|
|
1721
|
-
}
|
|
1722
1829
|
if (!isAsyncAvailable()) {
|
|
1723
1830
|
return {
|
|
1724
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." }],
|
|
@@ -1749,20 +1856,29 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
1749
1856
|
shareEnabled,
|
|
1750
1857
|
sessionRoot,
|
|
1751
1858
|
chainSkills: chainResult.requestedAsync.chainSkills,
|
|
1752
|
-
sessionFilesByFlatIndex: collectChainSessionFiles(asyncChain, sessionFileForTask),
|
|
1859
|
+
sessionFilesByFlatIndex: collectChainSessionFiles(asyncChain, sessionFileForTask, deps.config.chain?.dynamicFanout?.maxItems),
|
|
1860
|
+
thinkingOverridesByFlatIndex: collectChainThinkingOverrides(asyncChain, thinkingOverrideForTask, deps.config.chain?.dynamicFanout?.maxItems),
|
|
1753
1861
|
dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
|
|
1754
1862
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
1755
1863
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1756
1864
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
1865
|
+
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
1757
1866
|
controlConfig,
|
|
1758
1867
|
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
1759
1868
|
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
|
|
1760
1869
|
nestedRoute: data.nestedRoute,
|
|
1870
|
+
timeoutMs: data.timeoutMs,
|
|
1871
|
+
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
1761
1872
|
});
|
|
1762
1873
|
}
|
|
1763
1874
|
|
|
1764
|
-
const
|
|
1765
|
-
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;
|
|
1766
1882
|
if (chainDetails) rememberForegroundRun(deps.state, { runId, mode: "chain", cwd: effectiveCwd, results: chainDetails.results });
|
|
1767
1883
|
const intercomReceipt = chainDetails && !chainDetails.results.some((result) => result.interrupted || result.detached)
|
|
1768
1884
|
? await maybeBuildForegroundIntercomReceipt({
|
|
@@ -1796,9 +1912,11 @@ interface ForegroundParallelRunInput {
|
|
|
1796
1912
|
sessionDirForIndex: (idx?: number) => string | undefined;
|
|
1797
1913
|
sessionFileForIndex: (idx?: number) => string | undefined;
|
|
1798
1914
|
sessionFileForTask: (agentName: string, idx?: number) => string | undefined;
|
|
1915
|
+
thinkingOverrideForTask: (agentName: string, idx?: number) => AgentConfig["thinking"] | undefined;
|
|
1799
1916
|
shareEnabled: boolean;
|
|
1800
1917
|
artifactConfig: ArtifactConfig;
|
|
1801
1918
|
artifactsDir: string;
|
|
1919
|
+
outputBaseDir: string;
|
|
1802
1920
|
maxOutput?: MaxOutputConfig;
|
|
1803
1921
|
paramsCwd: string;
|
|
1804
1922
|
progressDir: string;
|
|
@@ -1813,6 +1931,7 @@ interface ForegroundParallelRunInput {
|
|
|
1813
1931
|
orchestratorIntercomTarget?: string;
|
|
1814
1932
|
foregroundControl?: SubagentState["foregroundControls"] extends Map<string, infer T> ? T : never;
|
|
1815
1933
|
concurrencyLimit: number;
|
|
1934
|
+
globalSemaphore?: Semaphore;
|
|
1816
1935
|
liveResults: (SingleResult | undefined)[];
|
|
1817
1936
|
liveProgress: (AgentProgress | undefined)[];
|
|
1818
1937
|
onUpdate?: (r: AgentToolResult<Details>) => void;
|
|
@@ -1836,6 +1955,7 @@ function createParallelWorktreeSetup(
|
|
|
1836
1955
|
tasks: TaskParam[],
|
|
1837
1956
|
setupHook: ExtensionConfig["worktreeSetupHook"],
|
|
1838
1957
|
setupHookTimeoutMs: ExtensionConfig["worktreeSetupHookTimeoutMs"],
|
|
1958
|
+
baseDir: ExtensionConfig["worktreeBaseDir"],
|
|
1839
1959
|
): { setup?: WorktreeSetup; errorResult?: AgentToolResult<Details> } {
|
|
1840
1960
|
if (!enabled) return {};
|
|
1841
1961
|
try {
|
|
@@ -1845,6 +1965,7 @@ function createParallelWorktreeSetup(
|
|
|
1845
1965
|
setupHook: setupHook
|
|
1846
1966
|
? { hookPath: setupHook, timeoutMs: setupHookTimeoutMs }
|
|
1847
1967
|
: undefined,
|
|
1968
|
+
baseDir,
|
|
1848
1969
|
}),
|
|
1849
1970
|
};
|
|
1850
1971
|
} catch (error) {
|
|
@@ -1862,6 +1983,12 @@ function buildParallelWorktreeTaskCwdError(
|
|
|
1862
1983
|
return formatWorktreeTaskCwdConflict(conflict, sharedCwd);
|
|
1863
1984
|
}
|
|
1864
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
|
+
|
|
1865
1992
|
function buildChainWorktreeTaskCwdError(chain: ChainStep[], sharedCwd: string): string | undefined {
|
|
1866
1993
|
for (let stepIndex = 0; stepIndex < chain.length; stepIndex++) {
|
|
1867
1994
|
const step = chain[stepIndex]!;
|
|
@@ -1901,6 +2028,7 @@ function findDuplicateParallelOutputPath(input: {
|
|
|
1901
2028
|
behaviors: ResolvedStepBehavior[];
|
|
1902
2029
|
paramsCwd: string;
|
|
1903
2030
|
ctxCwd: string;
|
|
2031
|
+
outputBaseDir: string;
|
|
1904
2032
|
worktreeSetup?: WorktreeSetup;
|
|
1905
2033
|
}): string | undefined {
|
|
1906
2034
|
const seen = new Map<string, { index: number; agent: string }>();
|
|
@@ -1909,7 +2037,7 @@ function findDuplicateParallelOutputPath(input: {
|
|
|
1909
2037
|
if (!behavior?.output) continue;
|
|
1910
2038
|
const task = input.tasks[index]!;
|
|
1911
2039
|
const taskCwd = resolveParallelTaskCwd(task, input.paramsCwd, input.worktreeSetup, index);
|
|
1912
|
-
const outputPath = resolveSingleOutputPath(behavior.output, input.ctxCwd, taskCwd);
|
|
2040
|
+
const outputPath = resolveSingleOutputPath(behavior.output, input.ctxCwd, taskCwd, input.outputBaseDir);
|
|
1913
2041
|
if (!outputPath) continue;
|
|
1914
2042
|
const previous = seen.get(outputPath);
|
|
1915
2043
|
if (previous) {
|
|
@@ -1921,6 +2049,12 @@ function findDuplicateParallelOutputPath(input: {
|
|
|
1921
2049
|
}
|
|
1922
2050
|
|
|
1923
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
|
+
}
|
|
1924
2058
|
return mapConcurrent(input.tasks, input.concurrencyLimit, async (task, index) => {
|
|
1925
2059
|
const behavior = input.behaviors[index];
|
|
1926
2060
|
const effectiveSkills = behavior?.skills;
|
|
@@ -1931,7 +2065,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
1931
2065
|
const progressInstructions = behavior
|
|
1932
2066
|
? buildChainInstructions({ ...behavior, output: false, reads: false }, input.progressDir, index === input.firstProgressIndex)
|
|
1933
2067
|
: { prefix: "", suffix: "" };
|
|
1934
|
-
const outputPath = resolveSingleOutputPath(behavior?.output, input.ctx.cwd, taskCwd);
|
|
2068
|
+
const outputPath = resolveSingleOutputPath(behavior?.output, input.ctx.cwd, taskCwd, input.outputBaseDir);
|
|
1935
2069
|
const taskText = injectSingleOutputInstruction(
|
|
1936
2070
|
`${readInstructions.prefix}${input.taskTexts[index]!}${progressInstructions.suffix}`,
|
|
1937
2071
|
outputPath,
|
|
@@ -1975,6 +2109,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
1975
2109
|
orchestratorIntercomTarget: input.orchestratorIntercomTarget,
|
|
1976
2110
|
nestedRoute: input.foregroundControl?.nestedRoute,
|
|
1977
2111
|
modelOverride: input.modelOverrides[index],
|
|
2112
|
+
thinkingOverride: input.thinkingOverrideForTask(task.agent, index),
|
|
1978
2113
|
availableModels: input.availableModels,
|
|
1979
2114
|
preferredModelProvider: input.ctx.model?.provider,
|
|
1980
2115
|
skills: effectiveSkills === false ? [] : effectiveSkills,
|
|
@@ -2022,7 +2157,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
2022
2157
|
input.foregroundControl.updatedAt = Date.now();
|
|
2023
2158
|
}
|
|
2024
2159
|
});
|
|
2025
|
-
});
|
|
2160
|
+
}, input.globalSemaphore);
|
|
2026
2161
|
}
|
|
2027
2162
|
|
|
2028
2163
|
async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps): Promise<AgentToolResult<Details>> {
|
|
@@ -2036,6 +2171,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2036
2171
|
sessionDirForIndex,
|
|
2037
2172
|
sessionFileForIndex,
|
|
2038
2173
|
sessionFileForTask,
|
|
2174
|
+
thinkingOverrideForTask,
|
|
2039
2175
|
shareEnabled,
|
|
2040
2176
|
artifactConfig,
|
|
2041
2177
|
artifactsDir,
|
|
@@ -2146,9 +2282,6 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2146
2282
|
}
|
|
2147
2283
|
|
|
2148
2284
|
if (result.runInBackground) {
|
|
2149
|
-
if (data.timeoutMs !== undefined) {
|
|
2150
|
-
return buildRequestedModeError(params, "timeoutMs/maxRuntimeMs are only supported for foreground runs; background launch from clarify cannot preserve the timeout.");
|
|
2151
|
-
}
|
|
2152
2285
|
if (!isAsyncAvailable()) {
|
|
2153
2286
|
return {
|
|
2154
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." }],
|
|
@@ -2195,12 +2328,16 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2195
2328
|
sessionRoot,
|
|
2196
2329
|
chainSkills: [],
|
|
2197
2330
|
sessionFilesByFlatIndex: tasks.map((task, index) => sessionFileForTask(task.agent, index)),
|
|
2331
|
+
thinkingOverridesByFlatIndex: tasks.map((task, index) => thinkingOverrideForTask(task.agent, index)),
|
|
2198
2332
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
2199
2333
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
2200
2334
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
2335
|
+
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
2201
2336
|
controlConfig,
|
|
2202
2337
|
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
2203
2338
|
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
|
|
2339
|
+
timeoutMs: data.timeoutMs,
|
|
2340
|
+
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
2204
2341
|
});
|
|
2205
2342
|
}
|
|
2206
2343
|
}
|
|
@@ -2217,21 +2354,24 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2217
2354
|
tasks,
|
|
2218
2355
|
deps.config.worktreeSetupHook,
|
|
2219
2356
|
deps.config.worktreeSetupHookTimeoutMs,
|
|
2357
|
+
deps.config.worktreeBaseDir,
|
|
2220
2358
|
);
|
|
2221
2359
|
if (errorResult) return errorResult;
|
|
2222
2360
|
|
|
2223
2361
|
try {
|
|
2362
|
+
const outputBaseDir = path.join(artifactsDir, "outputs", runId);
|
|
2224
2363
|
const duplicateOutputError = findDuplicateParallelOutputPath({
|
|
2225
2364
|
tasks,
|
|
2226
2365
|
behaviors,
|
|
2227
2366
|
paramsCwd: effectiveCwd,
|
|
2228
2367
|
ctxCwd: ctx.cwd,
|
|
2368
|
+
outputBaseDir,
|
|
2229
2369
|
worktreeSetup,
|
|
2230
2370
|
});
|
|
2231
2371
|
if (duplicateOutputError) return buildParallelModeError(duplicateOutputError);
|
|
2232
2372
|
for (let index = 0; index < tasks.length; index++) {
|
|
2233
2373
|
const taskCwd = resolveParallelTaskCwd(tasks[index]!, effectiveCwd, worktreeSetup, index);
|
|
2234
|
-
const outputPath = resolveSingleOutputPath(behaviors[index]?.output, ctx.cwd, taskCwd);
|
|
2374
|
+
const outputPath = resolveSingleOutputPath(behaviors[index]?.output, ctx.cwd, taskCwd, outputBaseDir);
|
|
2235
2375
|
const validationError = validateFileOnlyOutputMode(behaviors[index]?.outputMode, outputPath, `Parallel task ${index + 1} (${tasks[index]!.agent})`);
|
|
2236
2376
|
if (validationError) return buildParallelModeError(validationError);
|
|
2237
2377
|
}
|
|
@@ -2256,9 +2396,11 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2256
2396
|
sessionDirForIndex,
|
|
2257
2397
|
sessionFileForIndex,
|
|
2258
2398
|
sessionFileForTask,
|
|
2399
|
+
thinkingOverrideForTask,
|
|
2259
2400
|
shareEnabled,
|
|
2260
2401
|
artifactConfig,
|
|
2261
2402
|
artifactsDir,
|
|
2403
|
+
outputBaseDir,
|
|
2262
2404
|
maxOutput: params.maxOutput,
|
|
2263
2405
|
paramsCwd: effectiveCwd,
|
|
2264
2406
|
progressDir: parallelProgressDir,
|
|
@@ -2272,6 +2414,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2272
2414
|
orchestratorIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
2273
2415
|
foregroundControl,
|
|
2274
2416
|
concurrencyLimit: parallelConcurrency,
|
|
2417
|
+
globalSemaphore: new Semaphore(deps.config.globalConcurrencyLimit ?? DEFAULT_GLOBAL_CONCURRENCY_LIMIT),
|
|
2275
2418
|
maxSubagentDepths,
|
|
2276
2419
|
liveResults,
|
|
2277
2420
|
liveProgress,
|
|
@@ -2290,6 +2433,10 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2290
2433
|
if (result.artifactPaths) allArtifactPaths.push(result.artifactPaths);
|
|
2291
2434
|
}
|
|
2292
2435
|
|
|
2436
|
+
if (foregroundControl) {
|
|
2437
|
+
updateForegroundNestedProjection(foregroundControl);
|
|
2438
|
+
attachRootChildrenToSteps(runId, results, foregroundControl.nestedChildren);
|
|
2439
|
+
}
|
|
2293
2440
|
const interrupted = results.find((result) => result.interrupted);
|
|
2294
2441
|
const details = compactForegroundDetails({
|
|
2295
2442
|
mode: "parallel",
|
|
@@ -2297,6 +2444,8 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2297
2444
|
results,
|
|
2298
2445
|
progress: params.includeProgress ? allProgress : undefined,
|
|
2299
2446
|
artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
|
|
2447
|
+
totalChildUsage: sumResultsUsage(results),
|
|
2448
|
+
totalCost: sumResultsCost(results),
|
|
2300
2449
|
});
|
|
2301
2450
|
rememberForegroundRun(deps.state, { runId, mode: "parallel", cwd: effectiveCwd, results: details.results });
|
|
2302
2451
|
if (interrupted) {
|
|
@@ -2368,6 +2517,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2368
2517
|
runId,
|
|
2369
2518
|
sessionDirForIndex,
|
|
2370
2519
|
sessionFileForTask,
|
|
2520
|
+
thinkingOverrideForTask,
|
|
2371
2521
|
shareEnabled,
|
|
2372
2522
|
artifactConfig,
|
|
2373
2523
|
artifactsDir,
|
|
@@ -2438,9 +2588,6 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2438
2588
|
if (override?.skills !== undefined) skillOverride = override.skills;
|
|
2439
2589
|
|
|
2440
2590
|
if (result.runInBackground) {
|
|
2441
|
-
if (data.timeoutMs !== undefined) {
|
|
2442
|
-
return buildRequestedModeError(params, "timeoutMs/maxRuntimeMs are only supported for foreground runs; background launch from clarify cannot preserve the timeout.");
|
|
2443
|
-
}
|
|
2444
2591
|
if (!isAsyncAvailable()) {
|
|
2445
2592
|
return {
|
|
2446
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." }],
|
|
@@ -2473,13 +2620,17 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2473
2620
|
skills: skillOverride === false ? [] : skillOverride,
|
|
2474
2621
|
output: effectiveOutput,
|
|
2475
2622
|
outputMode: effectiveOutputMode,
|
|
2623
|
+
outputBaseDir: resolveSingleRunOutputBaseDir(deps, artifactsDir, id),
|
|
2476
2624
|
modelOverride,
|
|
2625
|
+
thinkingOverride: thinkingOverrideForTask(params.agent!, 0),
|
|
2477
2626
|
maxSubagentDepth,
|
|
2478
2627
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
2479
2628
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
2629
|
+
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
2480
2630
|
controlConfig,
|
|
2481
2631
|
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
2482
2632
|
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
|
|
2633
|
+
timeoutMs: data.timeoutMs,
|
|
2483
2634
|
});
|
|
2484
2635
|
}
|
|
2485
2636
|
}
|
|
@@ -2488,7 +2639,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2488
2639
|
task = wrapForkTask(task);
|
|
2489
2640
|
}
|
|
2490
2641
|
const cleanTask = task;
|
|
2491
|
-
const outputPath = resolveSingleOutputPath(effectiveOutput, ctx.cwd, effectiveCwd);
|
|
2642
|
+
const outputPath = resolveSingleOutputPath(effectiveOutput, ctx.cwd, effectiveCwd, resolveSingleRunOutputBaseDir(deps, artifactsDir, runId));
|
|
2492
2643
|
const validationError = validateFileOnlyOutputMode(effectiveOutputMode, outputPath, `Single run (${params.agent})`);
|
|
2493
2644
|
if (validationError) {
|
|
2494
2645
|
return { content: [{ type: "text", text: validationError }], isError: true, details: { mode: "single", results: [] } };
|
|
@@ -2563,6 +2714,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2563
2714
|
nestedRoute: foregroundControl?.nestedRoute,
|
|
2564
2715
|
index: 0,
|
|
2565
2716
|
modelOverride,
|
|
2717
|
+
thinkingOverride: thinkingOverrideForTask(params.agent!, 0),
|
|
2566
2718
|
availableModels,
|
|
2567
2719
|
preferredModelProvider: currentProvider,
|
|
2568
2720
|
skills: effectiveSkills,
|
|
@@ -2599,6 +2751,10 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2599
2751
|
outputReference: r.outputReference,
|
|
2600
2752
|
saveError: r.outputSaveError,
|
|
2601
2753
|
});
|
|
2754
|
+
if (foregroundControl) {
|
|
2755
|
+
updateForegroundNestedProjection(foregroundControl);
|
|
2756
|
+
attachRootChildrenToSteps(runId, [r], foregroundControl.nestedChildren);
|
|
2757
|
+
}
|
|
2602
2758
|
const details = compactForegroundDetails({
|
|
2603
2759
|
mode: "single",
|
|
2604
2760
|
runId,
|
|
@@ -2606,6 +2762,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2606
2762
|
progress: params.includeProgress ? allProgress : undefined,
|
|
2607
2763
|
artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
|
|
2608
2764
|
truncation: r.truncation,
|
|
2765
|
+
totalChildUsage: sumResultsUsage([r]),
|
|
2766
|
+
totalCost: sumResultsCost([r]),
|
|
2609
2767
|
});
|
|
2610
2768
|
rememberForegroundRun(deps.state, { runId, mode: "single", cwd: effectiveCwd, results: details.results });
|
|
2611
2769
|
|
|
@@ -2724,6 +2882,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
2724
2882
|
orchestratorTarget,
|
|
2725
2883
|
sessionError,
|
|
2726
2884
|
expandTilde: deps.expandTilde,
|
|
2885
|
+
companionPackageLines: deps.companionSuggestionLines?.({
|
|
2886
|
+
surface: "doctor",
|
|
2887
|
+
cwd: requestCwd,
|
|
2888
|
+
context: paramsWithResolvedCwd.context,
|
|
2889
|
+
orchestratorTarget,
|
|
2890
|
+
}),
|
|
2727
2891
|
}),
|
|
2728
2892
|
}],
|
|
2729
2893
|
details: { mode: "management", results: [] },
|
|
@@ -2784,7 +2948,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
2784
2948
|
details: { mode: "management", results: [] },
|
|
2785
2949
|
};
|
|
2786
2950
|
}
|
|
2787
|
-
const asyncInterruptResult = interruptAsyncRun(
|
|
2951
|
+
const asyncInterruptResult = interruptAsyncRun(
|
|
2952
|
+
deps.state,
|
|
2953
|
+
resolved?.kind === "async" ? resolved.id : targetRunId,
|
|
2954
|
+
deps.kill,
|
|
2955
|
+
resolved?.kind === "async" ? resolved.location : undefined,
|
|
2956
|
+
);
|
|
2788
2957
|
if (asyncInterruptResult) return asyncInterruptResult;
|
|
2789
2958
|
return {
|
|
2790
2959
|
content: [{ type: "text", text: "No interrupt-capable run found in this session." }],
|
|
@@ -2806,7 +2975,21 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
2806
2975
|
details: { mode: "management" as const, results: [] },
|
|
2807
2976
|
};
|
|
2808
2977
|
}
|
|
2809
|
-
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
|
+
});
|
|
2810
2993
|
}
|
|
2811
2994
|
|
|
2812
2995
|
const { blocked, depth, maxDepth } = checkSubagentDepth(deps.config.maxSubagentDepth);
|
|
@@ -2879,24 +3062,24 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
2879
3062
|
if (validationError) return validationError;
|
|
2880
3063
|
|
|
2881
3064
|
let forkSessionFileForIndex: (idx?: number) => string | undefined = () => undefined;
|
|
3065
|
+
let forkThinkingOverrideForIndex: (idx?: number) => AgentConfig["thinking"] | undefined = () => undefined;
|
|
2882
3066
|
try {
|
|
2883
|
-
|
|
3067
|
+
const forkContextResolver = createForkContextResolver(ctx.sessionManager, contextPolicy.usesFork ? "fork" : undefined);
|
|
3068
|
+
forkSessionFileForIndex = forkContextResolver.sessionFileForIndex;
|
|
3069
|
+
forkThinkingOverrideForIndex = forkContextResolver.thinkingOverrideForIndex;
|
|
2884
3070
|
} catch (error) {
|
|
2885
3071
|
return toExecutionErrorResult(effectiveParams, error);
|
|
2886
3072
|
}
|
|
2887
3073
|
const requestedAsync = effectiveParams.async ?? deps.asyncByDefault;
|
|
2888
3074
|
const backgroundRequestedWhileClarifying = (hasChain || hasTasks) && requestedAsync && effectiveParams.clarify === true;
|
|
2889
3075
|
const effectiveAsync = requestedAsync && effectiveParams.clarify !== true;
|
|
2890
|
-
if (foregroundTimeout.timeoutMs !== undefined && effectiveAsync) {
|
|
2891
|
-
return buildRequestedModeError(effectiveParams, "timeoutMs/maxRuntimeMs are only supported for foreground runs; set async: false or omit the timeout for background runs.");
|
|
2892
|
-
}
|
|
2893
3076
|
const controlConfig = resolveControlConfig(deps.config.control, effectiveParams.control);
|
|
2894
3077
|
|
|
2895
3078
|
const artifactConfig: ArtifactConfig = {
|
|
2896
3079
|
...DEFAULT_ARTIFACT_CONFIG,
|
|
2897
3080
|
enabled: effectiveParams.artifacts !== false,
|
|
2898
3081
|
};
|
|
2899
|
-
const artifactsDir =
|
|
3082
|
+
const artifactsDir = getArtifactsDir(parentSessionFile, effectiveCwd);
|
|
2900
3083
|
|
|
2901
3084
|
let sessionRoot: string;
|
|
2902
3085
|
if (effectiveParams.sessionDir) {
|
|
@@ -2920,12 +3103,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
2920
3103
|
path.join(sessionRoot, `run-${idx ?? 0}`);
|
|
2921
3104
|
const forkSessionFileForTask = (agentName: string, idx?: number) =>
|
|
2922
3105
|
shouldForkAgent(contextPolicy, agentName) ? forkSessionFileForIndex(idx) : undefined;
|
|
3106
|
+
const forkThinkingOverrideForTask = (agentName: string, idx?: number) =>
|
|
3107
|
+
shouldForkAgent(contextPolicy, agentName) ? forkThinkingOverrideForIndex(idx) : undefined;
|
|
2923
3108
|
const childSessionFileForTask = (agentName: string, idx?: number) =>
|
|
2924
3109
|
forkSessionFileForTask(agentName, idx) ?? path.join(sessionDirForIndex(idx), "session.jsonl");
|
|
2925
3110
|
const childSessionFileForIndex = (idx?: number) =>
|
|
2926
3111
|
path.join(sessionDirForIndex(idx), "session.jsonl");
|
|
2927
3112
|
try {
|
|
2928
|
-
preflightForkSessionsForStaticTasks(effectiveParams, contextPolicy, forkSessionFileForTask);
|
|
3113
|
+
preflightForkSessionsForStaticTasks(effectiveParams, contextPolicy, forkSessionFileForTask, deps.config.chain?.dynamicFanout?.maxItems);
|
|
2929
3114
|
} catch (error) {
|
|
2930
3115
|
return toExecutionErrorResult(effectiveParams, error);
|
|
2931
3116
|
}
|
|
@@ -2936,6 +3121,16 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
2936
3121
|
? (r: AgentToolResult<Details>) => onUpdate(withForkContext(r, effectiveParams.context))
|
|
2937
3122
|
: undefined;
|
|
2938
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
|
+
|
|
2939
3134
|
const execData: ExecutionContextData = {
|
|
2940
3135
|
params: effectiveParams,
|
|
2941
3136
|
effectiveCwd,
|
|
@@ -2949,6 +3144,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
2949
3144
|
sessionDirForIndex,
|
|
2950
3145
|
sessionFileForIndex: childSessionFileForIndex,
|
|
2951
3146
|
sessionFileForTask: childSessionFileForTask,
|
|
3147
|
+
thinkingOverrideForTask: forkThinkingOverrideForTask,
|
|
2952
3148
|
artifactConfig,
|
|
2953
3149
|
artifactsDir,
|
|
2954
3150
|
backgroundRequestedWhileClarifying,
|
|
@@ -2960,7 +3156,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
2960
3156
|
contextPolicy,
|
|
2961
3157
|
};
|
|
2962
3158
|
|
|
2963
|
-
const foregroundMode: "single" | "parallel" | "chain" = hasChain ? "chain" : hasTasks ? "parallel" : "single";
|
|
2964
3159
|
const foregroundControl = effectiveAsync
|
|
2965
3160
|
? undefined
|
|
2966
3161
|
: {
|
|
@@ -3024,6 +3219,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3024
3219
|
startedAt: foregroundControl?.startedAt ?? now,
|
|
3025
3220
|
...(state !== "running" ? { endedAt: now } : {}),
|
|
3026
3221
|
lastUpdate: now,
|
|
3222
|
+
...(details?.totalCost ? { totalCost: details.totalCost } : {}),
|
|
3027
3223
|
...(errorText ? { error: errorText } : {}),
|
|
3028
3224
|
...(details?.results.length ? { steps: details.results.map((child) => ({
|
|
3029
3225
|
agent: child.agent,
|