pi-subagents 0.30.0 → 0.31.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 +26 -0
- package/README.md +116 -17
- package/agents/context-builder.md +3 -3
- package/agents/planner.md +1 -1
- package/agents/researcher.md +1 -1
- package/agents/scout.md +1 -1
- package/package.json +7 -7
- package/skills/pi-subagents/SKILL.md +5 -0
- package/src/agents/agent-management.ts +170 -6
- package/src/agents/agent-serializer.ts +31 -13
- package/src/agents/agents.ts +207 -23
- package/src/agents/frontmatter.ts +66 -2
- package/src/agents/skills.ts +117 -20
- package/src/extension/doctor.ts +20 -0
- package/src/extension/fanout-child.ts +1 -0
- package/src/extension/index.ts +47 -4
- package/src/extension/schemas.ts +10 -76
- package/src/intercom/intercom-bridge.ts +2 -3
- package/src/runs/background/async-execution.ts +14 -4
- package/src/runs/background/async-job-tracker.ts +56 -11
- package/src/runs/background/result-watcher.ts +11 -2
- package/src/runs/background/stale-run-reconciler.ts +9 -4
- package/src/runs/background/subagent-runner.ts +79 -3
- package/src/runs/foreground/chain-execution.ts +26 -2
- package/src/runs/foreground/execution.ts +113 -8
- package/src/runs/foreground/subagent-executor.ts +325 -77
- package/src/runs/shared/acceptance.ts +285 -34
- package/src/runs/shared/completion-guard.ts +1 -1
- package/src/runs/shared/dynamic-fanout.ts +4 -2
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +2 -2
- package/src/runs/shared/parallel-utils.ts +6 -1
- package/src/runs/shared/pi-args.ts +9 -1
- package/src/runs/shared/single-output.ts +15 -1
- package/src/shared/settings.ts +1 -0
- package/src/shared/types.ts +8 -2
- package/src/shared/utils.ts +19 -1
- package/src/slash/prompt-template-bridge.ts +26 -3
- package/src/slash/slash-commands.ts +33 -3
- package/src/tui/render.ts +265 -13
|
@@ -34,6 +34,7 @@ import { discoverAvailableSkills, normalizeSkillInput } from "../../agents/skill
|
|
|
34
34
|
import { buildAsyncRunnerSteps, executeAsyncChain, executeAsyncSingle, formatAsyncStartedMessage, isAsyncAvailable } from "../background/async-execution.ts";
|
|
35
35
|
import { enqueueChainAppendRequest, readPendingChainAppendRequests, runnerStepOutputNames } from "../background/chain-append.ts";
|
|
36
36
|
import { ChainOutputValidationError, validateChainOutputBindingsWithContext } from "../shared/chain-outputs.ts";
|
|
37
|
+
import { validateAcceptanceInput } from "../shared/acceptance.ts";
|
|
37
38
|
import { createForkContextResolver } from "../../shared/fork-context.ts";
|
|
38
39
|
import { resolveCurrentSessionId } from "../../shared/session-identity.ts";
|
|
39
40
|
import { applyIntercomBridgeToAgent, INTERCOM_BRIDGE_MARKER, resolveIntercomBridge, resolveIntercomSessionTarget, resolveSubagentIntercomTarget, type IntercomBridgeState } from "../../intercom/intercom-bridge.ts";
|
|
@@ -128,6 +129,8 @@ export interface SubagentParamsLike {
|
|
|
128
129
|
worktree?: boolean;
|
|
129
130
|
context?: "fresh" | "fork";
|
|
130
131
|
async?: boolean;
|
|
132
|
+
timeoutMs?: number;
|
|
133
|
+
maxRuntimeMs?: number;
|
|
131
134
|
clarify?: boolean;
|
|
132
135
|
share?: boolean;
|
|
133
136
|
control?: ControlConfig;
|
|
@@ -170,6 +173,7 @@ interface ExecutionContextData {
|
|
|
170
173
|
sessionRoot: string;
|
|
171
174
|
sessionDirForIndex: (idx?: number) => string;
|
|
172
175
|
sessionFileForIndex: (idx?: number) => string | undefined;
|
|
176
|
+
sessionFileForTask: (agentName: string, idx?: number) => string | undefined;
|
|
173
177
|
artifactConfig: ArtifactConfig;
|
|
174
178
|
artifactsDir: string;
|
|
175
179
|
backgroundRequestedWhileClarifying: boolean;
|
|
@@ -177,6 +181,9 @@ interface ExecutionContextData {
|
|
|
177
181
|
controlConfig: ResolvedControlConfig;
|
|
178
182
|
intercomBridge: IntercomBridgeState;
|
|
179
183
|
nestedRoute?: NestedRouteInfo;
|
|
184
|
+
timeoutMs?: number;
|
|
185
|
+
deadlineAt?: number;
|
|
186
|
+
contextPolicy: AgentDefaultContextPolicy;
|
|
180
187
|
}
|
|
181
188
|
|
|
182
189
|
function resolveRequestedCwd(runtimeCwd: string, requestedCwd: string | undefined): string {
|
|
@@ -471,6 +478,14 @@ function appendStepToAsyncChain(input: {
|
|
|
471
478
|
details: { mode: "management", results: [] },
|
|
472
479
|
};
|
|
473
480
|
}
|
|
481
|
+
const acceptanceErrors = validateExecutionAcceptance(input.params);
|
|
482
|
+
if (acceptanceErrors.length > 0) {
|
|
483
|
+
return {
|
|
484
|
+
content: [{ type: "text", text: `Cannot append step: ${acceptanceErrors.join(" ")}` }],
|
|
485
|
+
isError: true,
|
|
486
|
+
details: { mode: "management", results: [] },
|
|
487
|
+
};
|
|
488
|
+
}
|
|
474
489
|
|
|
475
490
|
let resolved: ResolvedSubagentRunId | undefined;
|
|
476
491
|
try {
|
|
@@ -547,17 +562,19 @@ function appendStepToAsyncChain(input: {
|
|
|
547
562
|
|
|
548
563
|
const scope: AgentScope = resolveExecutionAgentScope(input.params.agentScope);
|
|
549
564
|
const agents = input.deps.discoverAgents(input.requestCwd, scope).agents;
|
|
565
|
+
const contextPolicy = resolveExplicitContextPolicy(input.params);
|
|
550
566
|
const chainSkillInput = normalizeSkillInput(input.params.skill);
|
|
551
567
|
const chainSkills = chainSkillInput === false ? [] : (chainSkillInput ?? []);
|
|
552
568
|
const asyncCtx = {
|
|
553
569
|
pi: input.deps.pi,
|
|
554
570
|
cwd: input.ctx.cwd,
|
|
555
571
|
currentSessionId: resolveCurrentSessionId(input.ctx.sessionManager),
|
|
572
|
+
parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
|
|
556
573
|
currentModelProvider: input.ctx.model?.provider,
|
|
557
574
|
currentModel: input.ctx.model,
|
|
558
575
|
};
|
|
559
576
|
const built = buildAsyncRunnerSteps(resolved.id, {
|
|
560
|
-
chain: wrapChainTasksForFork(input.params.chain,
|
|
577
|
+
chain: wrapChainTasksForFork(input.params.chain, contextPolicy),
|
|
561
578
|
task: input.params.task,
|
|
562
579
|
resultMode: "chain",
|
|
563
580
|
agents,
|
|
@@ -861,7 +878,8 @@ async function resumeAsyncRun(input: {
|
|
|
861
878
|
const runId = randomUUID().slice(0, 8);
|
|
862
879
|
const artifactConfig: ArtifactConfig = { ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false };
|
|
863
880
|
const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
864
|
-
const
|
|
881
|
+
const contextPolicy = resolveExplicitContextPolicy(input.params);
|
|
882
|
+
const chain = wrapChainTasksForFork(attachChain, contextPolicy);
|
|
865
883
|
const normalized = normalizeSkillInput(input.params.skill);
|
|
866
884
|
const result = executeAsyncChain(runId, {
|
|
867
885
|
chain,
|
|
@@ -879,6 +897,7 @@ async function resumeAsyncRun(input: {
|
|
|
879
897
|
pi: input.deps.pi,
|
|
880
898
|
cwd: input.requestCwd,
|
|
881
899
|
currentSessionId: input.deps.state.currentSessionId,
|
|
900
|
+
parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
|
|
882
901
|
currentModelProvider: input.ctx.model?.provider,
|
|
883
902
|
currentModel: input.ctx.model,
|
|
884
903
|
},
|
|
@@ -921,6 +940,7 @@ async function resumeAsyncRun(input: {
|
|
|
921
940
|
pi: input.deps.pi,
|
|
922
941
|
cwd: input.requestCwd,
|
|
923
942
|
currentSessionId: input.deps.state.currentSessionId,
|
|
943
|
+
parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
|
|
924
944
|
currentModelProvider: input.ctx.model?.provider,
|
|
925
945
|
currentModel: input.ctx.model,
|
|
926
946
|
},
|
|
@@ -1068,6 +1088,15 @@ function validateExecutionInput(
|
|
|
1068
1088
|
};
|
|
1069
1089
|
}
|
|
1070
1090
|
|
|
1091
|
+
const acceptanceErrors = validateExecutionAcceptance(params);
|
|
1092
|
+
if (acceptanceErrors.length > 0) {
|
|
1093
|
+
return {
|
|
1094
|
+
content: [{ type: "text", text: acceptanceErrors.join(" ") }],
|
|
1095
|
+
isError: true,
|
|
1096
|
+
details: { mode: getRequestedModeLabel(params), results: [] },
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1071
1100
|
if (hasSingle && params.agent && !agents.find((agent) => agent.name === params.agent)) {
|
|
1072
1101
|
return {
|
|
1073
1102
|
content: [{ type: "text", text: `Unknown agent: ${params.agent}` }],
|
|
@@ -1145,6 +1174,42 @@ function validateExecutionInput(
|
|
|
1145
1174
|
return null;
|
|
1146
1175
|
}
|
|
1147
1176
|
|
|
1177
|
+
function validateExecutionChainBindings(params: SubagentParamsLike, dynamicFanoutMaxItems?: number): AgentToolResult<Details> | null {
|
|
1178
|
+
if ((params.chain?.length ?? 0) === 0) return null;
|
|
1179
|
+
try {
|
|
1180
|
+
validateChainOutputBindingsWithContext(params.chain as ChainStep[], { maxItems: dynamicFanoutMaxItems });
|
|
1181
|
+
} catch (error) {
|
|
1182
|
+
if (error instanceof ChainOutputValidationError) {
|
|
1183
|
+
return {
|
|
1184
|
+
content: [{ type: "text", text: error.message }],
|
|
1185
|
+
isError: true,
|
|
1186
|
+
details: { mode: "chain" as const, results: [] },
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
throw error;
|
|
1190
|
+
}
|
|
1191
|
+
return null;
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
function validateExecutionAcceptance(params: SubagentParamsLike): string[] {
|
|
1195
|
+
const errors: string[] = [];
|
|
1196
|
+
errors.push(...validateAcceptanceInput(params.acceptance, "acceptance"));
|
|
1197
|
+
for (const [index, task] of (params.tasks ?? []).entries()) {
|
|
1198
|
+
errors.push(...validateAcceptanceInput(task.acceptance, `tasks[${index}].acceptance`));
|
|
1199
|
+
}
|
|
1200
|
+
for (const [stepIndex, step] of (params.chain ?? []).entries()) {
|
|
1201
|
+
errors.push(...validateAcceptanceInput((step as { acceptance?: unknown }).acceptance, `chain[${stepIndex}].acceptance`));
|
|
1202
|
+
if (isParallelStep(step)) {
|
|
1203
|
+
for (const [taskIndex, task] of step.parallel.entries()) {
|
|
1204
|
+
errors.push(...validateAcceptanceInput(task.acceptance, `chain[${stepIndex}].parallel[${taskIndex}].acceptance`));
|
|
1205
|
+
}
|
|
1206
|
+
} else if (isDynamicParallelStep(step)) {
|
|
1207
|
+
errors.push(...validateAcceptanceInput(step.parallel.acceptance, `chain[${stepIndex}].parallel.acceptance`));
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
return errors;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1148
1213
|
function getRequestedModeLabel(params: SubagentParamsLike): Details["mode"] {
|
|
1149
1214
|
if ((params.chain?.length ?? 0) > 0) return "chain";
|
|
1150
1215
|
if ((params.tasks?.length ?? 0) > 0) return "parallel";
|
|
@@ -1152,16 +1217,46 @@ function getRequestedModeLabel(params: SubagentParamsLike): Details["mode"] {
|
|
|
1152
1217
|
return "single";
|
|
1153
1218
|
}
|
|
1154
1219
|
|
|
1155
|
-
|
|
1156
|
-
|
|
1220
|
+
interface AgentDefaultContextPolicy {
|
|
1221
|
+
params: SubagentParamsLike;
|
|
1222
|
+
contextForAgent(agentName: string): "fresh" | "fork";
|
|
1223
|
+
usesFork: boolean;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
function resolveAgentDefaultContextPolicy(params: SubagentParamsLike, agents: AgentConfig[]): AgentDefaultContextPolicy {
|
|
1227
|
+
if (params.context !== undefined) {
|
|
1228
|
+
return resolveExplicitContextPolicy(params);
|
|
1229
|
+
}
|
|
1157
1230
|
const byName = new Map(agents.map((agent) => [agent.name, agent]));
|
|
1231
|
+
const contextForAgent = (agentName: string): "fresh" | "fork" =>
|
|
1232
|
+
byName.get(agentName)?.defaultContext === "fork" ? "fork" : "fresh";
|
|
1233
|
+
const usesFork = collectRequestedAgentNames(params).some((name) => contextForAgent(name) === "fork");
|
|
1234
|
+
return {
|
|
1235
|
+
params: usesFork ? { ...params, context: "fork" } : params,
|
|
1236
|
+
contextForAgent,
|
|
1237
|
+
usesFork,
|
|
1238
|
+
};
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
function resolveExplicitContextPolicy(params: SubagentParamsLike): AgentDefaultContextPolicy {
|
|
1242
|
+
const context = params.context === "fork" ? "fork" : "fresh";
|
|
1243
|
+
return {
|
|
1244
|
+
params,
|
|
1245
|
+
contextForAgent: () => context,
|
|
1246
|
+
usesFork: context === "fork",
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
function collectRequestedAgentNames(params: SubagentParamsLike): string[] {
|
|
1158
1251
|
const names: string[] = [];
|
|
1159
1252
|
if (params.agent) names.push(params.agent);
|
|
1160
1253
|
for (const task of params.tasks ?? []) names.push(task.agent);
|
|
1161
1254
|
for (const step of params.chain ?? []) names.push(...getStepAgents(step));
|
|
1162
|
-
return names
|
|
1163
|
-
|
|
1164
|
-
|
|
1255
|
+
return names;
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
function shouldForkAgent(contextPolicy: AgentDefaultContextPolicy, agentName: string): boolean {
|
|
1259
|
+
return contextPolicy.contextForAgent(agentName) === "fork";
|
|
1165
1260
|
}
|
|
1166
1261
|
|
|
1167
1262
|
function buildRequestedModeError(params: SubagentParamsLike, message: string): AgentToolResult<Details> {
|
|
@@ -1175,6 +1270,22 @@ function buildRequestedModeError(params: SubagentParamsLike, message: string): A
|
|
|
1175
1270
|
);
|
|
1176
1271
|
}
|
|
1177
1272
|
|
|
1273
|
+
function resolveForegroundTimeout(params: SubagentParamsLike): { timeoutMs?: number; error?: string } {
|
|
1274
|
+
const rawTimeout = params.timeoutMs;
|
|
1275
|
+
const rawMaxRuntime = params.maxRuntimeMs;
|
|
1276
|
+
if (rawTimeout === undefined && rawMaxRuntime === undefined) return {};
|
|
1277
|
+
for (const [name, value] of [["timeoutMs", rawTimeout], ["maxRuntimeMs", rawMaxRuntime]] as const) {
|
|
1278
|
+
if (value === undefined) continue;
|
|
1279
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
|
|
1280
|
+
return { error: `${name} must be a positive integer.` };
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
if (rawTimeout !== undefined && rawMaxRuntime !== undefined && rawTimeout !== rawMaxRuntime) {
|
|
1284
|
+
return { error: "timeoutMs and maxRuntimeMs are aliases; provide only one value or use the same value for both." };
|
|
1285
|
+
}
|
|
1286
|
+
return { timeoutMs: rawTimeout ?? rawMaxRuntime };
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1178
1289
|
function expandTopLevelTaskCounts(tasks: TaskParam[]): { tasks?: TaskParam[]; error?: string } {
|
|
1179
1290
|
const expanded: TaskParam[] = [];
|
|
1180
1291
|
for (let taskIndex = 0; taskIndex < tasks.length; taskIndex++) {
|
|
@@ -1262,14 +1373,14 @@ function toExecutionErrorResult(params: SubagentParamsLike, error: unknown): Age
|
|
|
1262
1373
|
|
|
1263
1374
|
function collectChainSessionFiles(
|
|
1264
1375
|
chain: ChainStep[],
|
|
1265
|
-
|
|
1376
|
+
sessionFileForTask: (agentName: string, idx?: number) => string | undefined,
|
|
1266
1377
|
): (string | undefined)[] {
|
|
1267
1378
|
const sessionFiles: (string | undefined)[] = [];
|
|
1268
1379
|
let flatIndex = 0;
|
|
1269
1380
|
for (const step of chain) {
|
|
1270
1381
|
if (isParallelStep(step)) {
|
|
1271
|
-
for (
|
|
1272
|
-
sessionFiles.push(
|
|
1382
|
+
for (const task of step.parallel) {
|
|
1383
|
+
sessionFiles.push(sessionFileForTask(task.agent, flatIndex));
|
|
1273
1384
|
flatIndex++;
|
|
1274
1385
|
}
|
|
1275
1386
|
continue;
|
|
@@ -1278,21 +1389,22 @@ function collectChainSessionFiles(
|
|
|
1278
1389
|
sessionFiles.push(undefined);
|
|
1279
1390
|
continue;
|
|
1280
1391
|
}
|
|
1281
|
-
sessionFiles.push(
|
|
1392
|
+
sessionFiles.push(sessionFileForTask((step as SequentialStep).agent, flatIndex));
|
|
1282
1393
|
flatIndex++;
|
|
1283
1394
|
}
|
|
1284
1395
|
return sessionFiles;
|
|
1285
1396
|
}
|
|
1286
1397
|
|
|
1287
|
-
function wrapChainTasksForFork(chain: ChainStep[],
|
|
1288
|
-
if (context !== "fork") return chain;
|
|
1398
|
+
function wrapChainTasksForFork(chain: ChainStep[], contextPolicy: AgentDefaultContextPolicy): ChainStep[] {
|
|
1289
1399
|
return chain.map((step, stepIndex) => {
|
|
1290
1400
|
if (isParallelStep(step)) {
|
|
1291
1401
|
return {
|
|
1292
1402
|
...step,
|
|
1293
1403
|
parallel: step.parallel.map((task) => ({
|
|
1294
1404
|
...task,
|
|
1295
|
-
task:
|
|
1405
|
+
task: shouldForkAgent(contextPolicy, task.agent)
|
|
1406
|
+
? wrapForkTask(task.task ?? "{previous}")
|
|
1407
|
+
: task.task,
|
|
1296
1408
|
})),
|
|
1297
1409
|
};
|
|
1298
1410
|
}
|
|
@@ -1301,18 +1413,59 @@ function wrapChainTasksForFork(chain: ChainStep[], context: SubagentParamsLike["
|
|
|
1301
1413
|
...step,
|
|
1302
1414
|
parallel: {
|
|
1303
1415
|
...step.parallel,
|
|
1304
|
-
task:
|
|
1416
|
+
task: shouldForkAgent(contextPolicy, step.parallel.agent)
|
|
1417
|
+
? wrapForkTask(step.parallel.task ?? "{previous}")
|
|
1418
|
+
: step.parallel.task,
|
|
1305
1419
|
},
|
|
1306
1420
|
};
|
|
1307
1421
|
}
|
|
1308
1422
|
const sequential = step as SequentialStep;
|
|
1309
1423
|
return {
|
|
1310
1424
|
...sequential,
|
|
1311
|
-
task:
|
|
1425
|
+
task: shouldForkAgent(contextPolicy, sequential.agent)
|
|
1426
|
+
? wrapForkTask(sequential.task ?? (stepIndex === 0 ? "{task}" : "{previous}"))
|
|
1427
|
+
: sequential.task,
|
|
1312
1428
|
};
|
|
1313
1429
|
});
|
|
1314
1430
|
}
|
|
1315
1431
|
|
|
1432
|
+
function preflightForkSessionsForStaticTasks(
|
|
1433
|
+
params: SubagentParamsLike,
|
|
1434
|
+
contextPolicy: AgentDefaultContextPolicy,
|
|
1435
|
+
sessionFileForTask: (agentName: string, idx?: number) => string | undefined,
|
|
1436
|
+
): void {
|
|
1437
|
+
if (!contextPolicy.usesFork) return;
|
|
1438
|
+
if (params.agent) {
|
|
1439
|
+
if (shouldForkAgent(contextPolicy, params.agent)) sessionFileForTask(params.agent, 0);
|
|
1440
|
+
return;
|
|
1441
|
+
}
|
|
1442
|
+
if (params.tasks) {
|
|
1443
|
+
params.tasks.forEach((task, index) => {
|
|
1444
|
+
if (shouldForkAgent(contextPolicy, task.agent)) sessionFileForTask(task.agent, index);
|
|
1445
|
+
});
|
|
1446
|
+
return;
|
|
1447
|
+
}
|
|
1448
|
+
if (!params.chain?.length) return;
|
|
1449
|
+
let flatIndex = 0;
|
|
1450
|
+
for (const step of params.chain) {
|
|
1451
|
+
if (isParallelStep(step)) {
|
|
1452
|
+
for (const task of step.parallel) {
|
|
1453
|
+
if (shouldForkAgent(contextPolicy, task.agent)) sessionFileForTask(task.agent, flatIndex);
|
|
1454
|
+
flatIndex++;
|
|
1455
|
+
}
|
|
1456
|
+
continue;
|
|
1457
|
+
}
|
|
1458
|
+
if (isDynamicParallelStep(step)) {
|
|
1459
|
+
if (shouldForkAgent(contextPolicy, step.parallel.agent)) sessionFileForTask(step.parallel.agent, flatIndex);
|
|
1460
|
+
flatIndex++;
|
|
1461
|
+
continue;
|
|
1462
|
+
}
|
|
1463
|
+
const sequential = step as SequentialStep;
|
|
1464
|
+
if (shouldForkAgent(contextPolicy, sequential.agent)) sessionFileForTask(sequential.agent, flatIndex);
|
|
1465
|
+
flatIndex++;
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1316
1469
|
function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentToolResult<Details> | null {
|
|
1317
1470
|
const {
|
|
1318
1471
|
params,
|
|
@@ -1322,12 +1475,14 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1322
1475
|
shareEnabled,
|
|
1323
1476
|
sessionRoot,
|
|
1324
1477
|
sessionFileForIndex,
|
|
1478
|
+
sessionFileForTask,
|
|
1325
1479
|
artifactConfig,
|
|
1326
1480
|
artifactsDir,
|
|
1327
1481
|
effectiveAsync,
|
|
1328
1482
|
controlConfig,
|
|
1329
1483
|
intercomBridge,
|
|
1330
1484
|
nestedRoute,
|
|
1485
|
+
contextPolicy,
|
|
1331
1486
|
} = data;
|
|
1332
1487
|
const hasChain = (params.chain?.length ?? 0) > 0;
|
|
1333
1488
|
const hasTasks = (params.tasks?.length ?? 0) > 0;
|
|
@@ -1368,6 +1523,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1368
1523
|
pi: deps.pi,
|
|
1369
1524
|
cwd: ctx.cwd,
|
|
1370
1525
|
currentSessionId: deps.state.currentSessionId!,
|
|
1526
|
+
parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
|
|
1371
1527
|
currentModelProvider: ctx.model?.provider,
|
|
1372
1528
|
currentModel: ctx.model,
|
|
1373
1529
|
};
|
|
@@ -1385,7 +1541,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1385
1541
|
const skillOverrides = params.tasks.map((task) => normalizeSkillInput(task.skill));
|
|
1386
1542
|
const parallelTasks = params.tasks.map((task, index) => ({
|
|
1387
1543
|
agent: task.agent,
|
|
1388
|
-
task:
|
|
1544
|
+
task: shouldForkAgent(contextPolicy, task.agent) ? wrapForkTask(task.task) : task.task,
|
|
1389
1545
|
cwd: task.cwd,
|
|
1390
1546
|
...(modelOverrides[index] ? { model: modelOverrides[index] } : {}),
|
|
1391
1547
|
...(skillOverrides[index] !== undefined ? { skill: skillOverrides[index] } : {}),
|
|
@@ -1412,7 +1568,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1412
1568
|
shareEnabled,
|
|
1413
1569
|
sessionRoot,
|
|
1414
1570
|
chainSkills: [],
|
|
1415
|
-
sessionFilesByFlatIndex: params.tasks.map((
|
|
1571
|
+
sessionFilesByFlatIndex: params.tasks.map((task, index) => sessionFileForTask(task.agent, index)),
|
|
1416
1572
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
1417
1573
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1418
1574
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
@@ -1426,7 +1582,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1426
1582
|
if (hasChain && params.chain) {
|
|
1427
1583
|
const normalized = normalizeSkillInput(params.skill);
|
|
1428
1584
|
const chainSkills = normalized === false ? [] : (normalized ?? []);
|
|
1429
|
-
const chain = wrapChainTasksForFork(params.chain as ChainStep[],
|
|
1585
|
+
const chain = wrapChainTasksForFork(params.chain as ChainStep[], contextPolicy);
|
|
1430
1586
|
return executeAsyncChain(id, {
|
|
1431
1587
|
chain,
|
|
1432
1588
|
task: params.task,
|
|
@@ -1440,7 +1596,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1440
1596
|
shareEnabled,
|
|
1441
1597
|
sessionRoot,
|
|
1442
1598
|
chainSkills,
|
|
1443
|
-
sessionFilesByFlatIndex: collectChainSessionFiles(chain,
|
|
1599
|
+
sessionFilesByFlatIndex: collectChainSessionFiles(chain, sessionFileForTask),
|
|
1444
1600
|
dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
|
|
1445
1601
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
1446
1602
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
@@ -1470,7 +1626,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1470
1626
|
const modelOverride = resolveSubagentModelOverride((params.model as string | undefined) ?? a.model, ctx.model, availableModels, currentProvider);
|
|
1471
1627
|
return executeAsyncSingle(id, {
|
|
1472
1628
|
agent: params.agent!,
|
|
1473
|
-
task: params.
|
|
1629
|
+
task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(params.task ?? "") : (params.task ?? ""),
|
|
1474
1630
|
agentConfig: a,
|
|
1475
1631
|
ctx: asyncCtx,
|
|
1476
1632
|
availableModels,
|
|
@@ -1480,7 +1636,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1480
1636
|
artifactConfig,
|
|
1481
1637
|
shareEnabled,
|
|
1482
1638
|
sessionRoot,
|
|
1483
|
-
sessionFile:
|
|
1639
|
+
sessionFile: sessionFileForTask(params.agent!, 0),
|
|
1484
1640
|
skills,
|
|
1485
1641
|
output: effectiveOutput,
|
|
1486
1642
|
outputMode: effectiveOutputMode,
|
|
@@ -1510,18 +1666,20 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
1510
1666
|
shareEnabled,
|
|
1511
1667
|
sessionDirForIndex,
|
|
1512
1668
|
sessionFileForIndex,
|
|
1669
|
+
sessionFileForTask,
|
|
1513
1670
|
artifactsDir,
|
|
1514
1671
|
artifactConfig,
|
|
1515
1672
|
onUpdate,
|
|
1516
1673
|
sessionRoot,
|
|
1517
1674
|
controlConfig,
|
|
1675
|
+
contextPolicy,
|
|
1518
1676
|
} = data;
|
|
1519
1677
|
const onControlEvent = createForegroundControlNotifier(data, deps);
|
|
1520
1678
|
const childIntercomTarget = data.intercomBridge.active ? resolveSubagentIntercomTarget : undefined;
|
|
1521
1679
|
const foregroundControl = deps.state.foregroundControls.get(runId);
|
|
1522
1680
|
const normalized = normalizeSkillInput(params.skill);
|
|
1523
1681
|
const chainSkills = normalized === false ? [] : (normalized ?? []);
|
|
1524
|
-
const chain = wrapChainTasksForFork(params.chain as ChainStep[],
|
|
1682
|
+
const chain = wrapChainTasksForFork(params.chain as ChainStep[], contextPolicy);
|
|
1525
1683
|
const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
|
|
1526
1684
|
const chainResult = await executeChain({
|
|
1527
1685
|
chain,
|
|
@@ -1535,6 +1693,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
1535
1693
|
shareEnabled,
|
|
1536
1694
|
sessionDirForIndex,
|
|
1537
1695
|
sessionFileForIndex,
|
|
1696
|
+
sessionFileForTask,
|
|
1538
1697
|
artifactsDir,
|
|
1539
1698
|
artifactConfig,
|
|
1540
1699
|
includeProgress: params.includeProgress,
|
|
@@ -1552,9 +1711,14 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
1552
1711
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
1553
1712
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1554
1713
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
1714
|
+
timeoutMs: data.timeoutMs,
|
|
1715
|
+
deadlineAt: data.deadlineAt,
|
|
1555
1716
|
});
|
|
1556
1717
|
|
|
1557
1718
|
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
|
+
}
|
|
1558
1722
|
if (!isAsyncAvailable()) {
|
|
1559
1723
|
return {
|
|
1560
1724
|
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." }],
|
|
@@ -1567,10 +1731,11 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
1567
1731
|
pi: deps.pi,
|
|
1568
1732
|
cwd: ctx.cwd,
|
|
1569
1733
|
currentSessionId: deps.state.currentSessionId!,
|
|
1734
|
+
parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
|
|
1570
1735
|
currentModelProvider: ctx.model?.provider,
|
|
1571
1736
|
currentModel: ctx.model,
|
|
1572
1737
|
};
|
|
1573
|
-
const asyncChain = wrapChainTasksForFork(chainResult.requestedAsync.chain,
|
|
1738
|
+
const asyncChain = wrapChainTasksForFork(chainResult.requestedAsync.chain, contextPolicy);
|
|
1574
1739
|
return executeAsyncChain(id, {
|
|
1575
1740
|
chain: asyncChain,
|
|
1576
1741
|
task: params.task,
|
|
@@ -1584,7 +1749,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
1584
1749
|
shareEnabled,
|
|
1585
1750
|
sessionRoot,
|
|
1586
1751
|
chainSkills: chainResult.requestedAsync.chainSkills,
|
|
1587
|
-
sessionFilesByFlatIndex: collectChainSessionFiles(asyncChain,
|
|
1752
|
+
sessionFilesByFlatIndex: collectChainSessionFiles(asyncChain, sessionFileForTask),
|
|
1588
1753
|
dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
|
|
1589
1754
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
1590
1755
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
@@ -1630,11 +1795,13 @@ interface ForegroundParallelRunInput {
|
|
|
1630
1795
|
runId: string;
|
|
1631
1796
|
sessionDirForIndex: (idx?: number) => string | undefined;
|
|
1632
1797
|
sessionFileForIndex: (idx?: number) => string | undefined;
|
|
1798
|
+
sessionFileForTask: (agentName: string, idx?: number) => string | undefined;
|
|
1633
1799
|
shareEnabled: boolean;
|
|
1634
1800
|
artifactConfig: ArtifactConfig;
|
|
1635
1801
|
artifactsDir: string;
|
|
1636
1802
|
maxOutput?: MaxOutputConfig;
|
|
1637
1803
|
paramsCwd: string;
|
|
1804
|
+
progressDir: string;
|
|
1638
1805
|
maxSubagentDepths: number[];
|
|
1639
1806
|
availableModels: ModelInfo[];
|
|
1640
1807
|
modelOverrides: (string | undefined)[];
|
|
@@ -1650,6 +1817,8 @@ interface ForegroundParallelRunInput {
|
|
|
1650
1817
|
liveProgress: (AgentProgress | undefined)[];
|
|
1651
1818
|
onUpdate?: (r: AgentToolResult<Details>) => void;
|
|
1652
1819
|
worktreeSetup?: WorktreeSetup;
|
|
1820
|
+
timeoutMs?: number;
|
|
1821
|
+
deadlineAt?: number;
|
|
1653
1822
|
}
|
|
1654
1823
|
|
|
1655
1824
|
function buildParallelModeError(message: string): AgentToolResult<Details> {
|
|
@@ -1760,7 +1929,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
1760
1929
|
? buildChainInstructions({ ...behavior, output: false, progress: false }, taskCwd, false)
|
|
1761
1930
|
: { prefix: "", suffix: "" };
|
|
1762
1931
|
const progressInstructions = behavior
|
|
1763
|
-
? buildChainInstructions({ ...behavior, output: false, reads: false }, input.
|
|
1932
|
+
? buildChainInstructions({ ...behavior, output: false, reads: false }, input.progressDir, index === input.firstProgressIndex)
|
|
1764
1933
|
: { prefix: "", suffix: "" };
|
|
1765
1934
|
const outputPath = resolveSingleOutputPath(behavior?.output, input.ctx.cwd, taskCwd);
|
|
1766
1935
|
const taskText = injectSingleOutputInstruction(
|
|
@@ -1783,6 +1952,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
1783
1952
|
}
|
|
1784
1953
|
const agentConfig = input.agents.find((agent) => agent.name === task.agent);
|
|
1785
1954
|
return runSync(input.ctx.cwd, input.agents, task.agent, taskText, {
|
|
1955
|
+
parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
|
|
1786
1956
|
cwd: taskCwd,
|
|
1787
1957
|
signal: input.signal,
|
|
1788
1958
|
interruptSignal: interruptController.signal,
|
|
@@ -1791,7 +1961,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
1791
1961
|
runId: input.runId,
|
|
1792
1962
|
index,
|
|
1793
1963
|
sessionDir: input.sessionDirForIndex(index),
|
|
1794
|
-
sessionFile: input.
|
|
1964
|
+
sessionFile: input.sessionFileForTask(task.agent, index),
|
|
1795
1965
|
share: input.shareEnabled,
|
|
1796
1966
|
artifactsDir: input.artifactConfig.enabled ? input.artifactsDir : undefined,
|
|
1797
1967
|
artifactConfig: input.artifactConfig,
|
|
@@ -1810,39 +1980,41 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
1810
1980
|
skills: effectiveSkills === false ? [] : effectiveSkills,
|
|
1811
1981
|
acceptance: task.acceptance,
|
|
1812
1982
|
acceptanceContext: { mode: "parallel" },
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
if (stepProgress.length > 0) input.liveProgress[index] = stepProgress[0];
|
|
1833
|
-
const mergedResults = input.liveResults.filter((result): result is SingleResult => result !== undefined);
|
|
1834
|
-
const mergedProgress = input.liveProgress.filter((progress): progress is AgentProgress => progress !== undefined);
|
|
1835
|
-
input.onUpdate?.({
|
|
1836
|
-
content: progressUpdate.content,
|
|
1837
|
-
details: {
|
|
1838
|
-
mode: "parallel",
|
|
1839
|
-
results: mergedResults,
|
|
1840
|
-
progress: mergedProgress,
|
|
1841
|
-
controlEvents: progressUpdate.details?.controlEvents,
|
|
1842
|
-
totalSteps: input.tasks.length,
|
|
1843
|
-
},
|
|
1844
|
-
});
|
|
1983
|
+
timeoutMs: input.timeoutMs,
|
|
1984
|
+
deadlineAt: input.deadlineAt,
|
|
1985
|
+
onUpdate: input.onUpdate
|
|
1986
|
+
? (progressUpdate) => {
|
|
1987
|
+
const stepResults = progressUpdate.details?.results || [];
|
|
1988
|
+
const stepProgress = progressUpdate.details?.progress || [];
|
|
1989
|
+
if (input.foregroundControl && stepProgress.length > 0) {
|
|
1990
|
+
const current = stepProgress[0];
|
|
1991
|
+
input.foregroundControl.currentAgent = task.agent;
|
|
1992
|
+
input.foregroundControl.currentIndex = index;
|
|
1993
|
+
input.foregroundControl.currentActivityState = current?.activityState;
|
|
1994
|
+
input.foregroundControl.lastActivityAt = current?.lastActivityAt;
|
|
1995
|
+
input.foregroundControl.currentTool = current?.currentTool;
|
|
1996
|
+
input.foregroundControl.currentToolStartedAt = current?.currentToolStartedAt;
|
|
1997
|
+
input.foregroundControl.currentPath = current?.currentPath;
|
|
1998
|
+
input.foregroundControl.turnCount = current?.turnCount;
|
|
1999
|
+
input.foregroundControl.tokens = current?.tokens;
|
|
2000
|
+
input.foregroundControl.toolCount = current?.toolCount;
|
|
2001
|
+
input.foregroundControl.updatedAt = Date.now();
|
|
1845
2002
|
}
|
|
2003
|
+
if (stepResults.length > 0) input.liveResults[index] = stepResults[0];
|
|
2004
|
+
if (stepProgress.length > 0) input.liveProgress[index] = stepProgress[0];
|
|
2005
|
+
const mergedResults = input.liveResults.filter((result): result is SingleResult => result !== undefined);
|
|
2006
|
+
const mergedProgress = input.liveProgress.filter((progress): progress is AgentProgress => progress !== undefined);
|
|
2007
|
+
input.onUpdate?.({
|
|
2008
|
+
content: progressUpdate.content,
|
|
2009
|
+
details: {
|
|
2010
|
+
mode: "parallel",
|
|
2011
|
+
results: mergedResults,
|
|
2012
|
+
progress: mergedProgress,
|
|
2013
|
+
controlEvents: progressUpdate.details?.controlEvents,
|
|
2014
|
+
totalSteps: input.tasks.length,
|
|
2015
|
+
},
|
|
2016
|
+
});
|
|
2017
|
+
}
|
|
1846
2018
|
: undefined,
|
|
1847
2019
|
}).finally(() => {
|
|
1848
2020
|
if (input.foregroundControl?.currentIndex === index) {
|
|
@@ -1863,6 +2035,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
1863
2035
|
runId,
|
|
1864
2036
|
sessionDirForIndex,
|
|
1865
2037
|
sessionFileForIndex,
|
|
2038
|
+
sessionFileForTask,
|
|
1866
2039
|
shareEnabled,
|
|
1867
2040
|
artifactConfig,
|
|
1868
2041
|
artifactsDir,
|
|
@@ -1870,6 +2043,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
1870
2043
|
onUpdate,
|
|
1871
2044
|
sessionRoot,
|
|
1872
2045
|
controlConfig,
|
|
2046
|
+
contextPolicy,
|
|
1873
2047
|
} = data;
|
|
1874
2048
|
const onControlEvent = createForegroundControlNotifier(data, deps);
|
|
1875
2049
|
const childIntercomTarget = data.intercomBridge.active ? resolveSubagentIntercomTarget : undefined;
|
|
@@ -1972,6 +2146,9 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
1972
2146
|
}
|
|
1973
2147
|
|
|
1974
2148
|
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
|
+
}
|
|
1975
2152
|
if (!isAsyncAvailable()) {
|
|
1976
2153
|
return {
|
|
1977
2154
|
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." }],
|
|
@@ -1984,11 +2161,12 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
1984
2161
|
pi: deps.pi,
|
|
1985
2162
|
cwd: ctx.cwd,
|
|
1986
2163
|
currentSessionId: deps.state.currentSessionId!,
|
|
2164
|
+
parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
|
|
1987
2165
|
currentModelProvider: ctx.model?.provider,
|
|
1988
2166
|
currentModel: ctx.model,
|
|
1989
2167
|
};
|
|
1990
2168
|
const parallelTasks = tasks.map((t, i) => {
|
|
1991
|
-
const taskText =
|
|
2169
|
+
const taskText = shouldForkAgent(contextPolicy, t.agent) ? wrapForkTask(taskTexts[i]!) : taskTexts[i]!;
|
|
1992
2170
|
const progress = taskDisallowsFileUpdates(taskText) ? false : behaviorOverrides[i]?.progress;
|
|
1993
2171
|
return {
|
|
1994
2172
|
agent: t.agent,
|
|
@@ -2016,7 +2194,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2016
2194
|
shareEnabled,
|
|
2017
2195
|
sessionRoot,
|
|
2018
2196
|
chainSkills: [],
|
|
2019
|
-
sessionFilesByFlatIndex: tasks.map((
|
|
2197
|
+
sessionFilesByFlatIndex: tasks.map((task, index) => sessionFileForTask(task.agent, index)),
|
|
2020
2198
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
2021
2199
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
2022
2200
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
@@ -2059,14 +2237,14 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2059
2237
|
}
|
|
2060
2238
|
|
|
2061
2239
|
const parallelProgressPrecreated = firstProgressIndex !== -1;
|
|
2062
|
-
|
|
2240
|
+
const parallelProgressDir = path.join(artifactsDir, "progress", runId);
|
|
2241
|
+
if (parallelProgressPrecreated) writeInitialProgressFile(parallelProgressDir);
|
|
2063
2242
|
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
taskTexts[i] = wrapForkTask(taskTexts[i]!);
|
|
2067
|
-
}
|
|
2243
|
+
for (let i = 0; i < taskTexts.length; i++) {
|
|
2244
|
+
if (shouldForkAgent(contextPolicy, tasks[i]!.agent)) taskTexts[i] = wrapForkTask(taskTexts[i]!);
|
|
2068
2245
|
}
|
|
2069
2246
|
|
|
2247
|
+
const deadlineAt = data.deadlineAt ?? (data.timeoutMs !== undefined ? Date.now() + data.timeoutMs : undefined);
|
|
2070
2248
|
const results = await runForegroundParallelTasks({
|
|
2071
2249
|
tasks,
|
|
2072
2250
|
taskTexts,
|
|
@@ -2077,11 +2255,13 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2077
2255
|
runId,
|
|
2078
2256
|
sessionDirForIndex,
|
|
2079
2257
|
sessionFileForIndex,
|
|
2258
|
+
sessionFileForTask,
|
|
2080
2259
|
shareEnabled,
|
|
2081
2260
|
artifactConfig,
|
|
2082
2261
|
artifactsDir,
|
|
2083
2262
|
maxOutput: params.maxOutput,
|
|
2084
2263
|
paramsCwd: effectiveCwd,
|
|
2264
|
+
progressDir: parallelProgressDir,
|
|
2085
2265
|
availableModels,
|
|
2086
2266
|
modelOverrides,
|
|
2087
2267
|
behaviors,
|
|
@@ -2097,6 +2277,8 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2097
2277
|
liveProgress,
|
|
2098
2278
|
onUpdate,
|
|
2099
2279
|
worktreeSetup,
|
|
2280
|
+
timeoutMs: data.timeoutMs,
|
|
2281
|
+
deadlineAt,
|
|
2100
2282
|
});
|
|
2101
2283
|
for (let i = 0; i < results.length; i++) {
|
|
2102
2284
|
const run = results[i]!;
|
|
@@ -2157,6 +2339,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2157
2339
|
output: result.truncation?.text || getSingleResultOutput(result),
|
|
2158
2340
|
exitCode: result.exitCode,
|
|
2159
2341
|
error: result.error,
|
|
2342
|
+
timedOut: result.timedOut,
|
|
2160
2343
|
})),
|
|
2161
2344
|
(i, agent) => `=== Task ${i + 1}: ${agent} ===`,
|
|
2162
2345
|
);
|
|
@@ -2184,13 +2367,14 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2184
2367
|
signal,
|
|
2185
2368
|
runId,
|
|
2186
2369
|
sessionDirForIndex,
|
|
2187
|
-
|
|
2370
|
+
sessionFileForTask,
|
|
2188
2371
|
shareEnabled,
|
|
2189
2372
|
artifactConfig,
|
|
2190
2373
|
artifactsDir,
|
|
2191
2374
|
onUpdate,
|
|
2192
2375
|
sessionRoot,
|
|
2193
2376
|
controlConfig,
|
|
2377
|
+
contextPolicy,
|
|
2194
2378
|
} = data;
|
|
2195
2379
|
const onControlEvent = createForegroundControlNotifier(data, deps);
|
|
2196
2380
|
const childIntercomTarget = data.intercomBridge.active ? resolveSubagentIntercomTarget(runId, params.agent!, 0) : undefined;
|
|
@@ -2254,6 +2438,9 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2254
2438
|
if (override?.skills !== undefined) skillOverride = override.skills;
|
|
2255
2439
|
|
|
2256
2440
|
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
|
+
}
|
|
2257
2444
|
if (!isAsyncAvailable()) {
|
|
2258
2445
|
return {
|
|
2259
2446
|
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." }],
|
|
@@ -2266,12 +2453,13 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2266
2453
|
pi: deps.pi,
|
|
2267
2454
|
cwd: ctx.cwd,
|
|
2268
2455
|
currentSessionId: deps.state.currentSessionId!,
|
|
2456
|
+
parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
|
|
2269
2457
|
currentModelProvider: ctx.model?.provider,
|
|
2270
2458
|
currentModel: ctx.model,
|
|
2271
2459
|
};
|
|
2272
2460
|
return executeAsyncSingle(id, {
|
|
2273
2461
|
agent: params.agent!,
|
|
2274
|
-
task: params.
|
|
2462
|
+
task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(task) : task,
|
|
2275
2463
|
agentConfig,
|
|
2276
2464
|
ctx: asyncCtx,
|
|
2277
2465
|
availableModels,
|
|
@@ -2281,7 +2469,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2281
2469
|
artifactConfig,
|
|
2282
2470
|
shareEnabled,
|
|
2283
2471
|
sessionRoot,
|
|
2284
|
-
sessionFile:
|
|
2472
|
+
sessionFile: sessionFileForTask(params.agent!, 0),
|
|
2285
2473
|
skills: skillOverride === false ? [] : skillOverride,
|
|
2286
2474
|
output: effectiveOutput,
|
|
2287
2475
|
outputMode: effectiveOutputMode,
|
|
@@ -2296,7 +2484,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2296
2484
|
}
|
|
2297
2485
|
}
|
|
2298
2486
|
|
|
2299
|
-
if (params.
|
|
2487
|
+
if (shouldForkAgent(contextPolicy, params.agent!)) {
|
|
2300
2488
|
task = wrapForkTask(task);
|
|
2301
2489
|
}
|
|
2302
2490
|
const cleanTask = task;
|
|
@@ -2349,7 +2537,9 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2349
2537
|
}
|
|
2350
2538
|
: undefined;
|
|
2351
2539
|
|
|
2540
|
+
const deadlineAt = data.deadlineAt ?? (data.timeoutMs !== undefined ? Date.now() + data.timeoutMs : undefined);
|
|
2352
2541
|
const r = await runSync(ctx.cwd, agents, params.agent!, task, {
|
|
2542
|
+
parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
|
|
2353
2543
|
cwd: effectiveCwd,
|
|
2354
2544
|
signal,
|
|
2355
2545
|
interruptSignal: interruptController.signal,
|
|
@@ -2357,7 +2547,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2357
2547
|
intercomEvents: deps.pi.events,
|
|
2358
2548
|
runId,
|
|
2359
2549
|
sessionDir: sessionDirForIndex(0),
|
|
2360
|
-
sessionFile:
|
|
2550
|
+
sessionFile: sessionFileForTask(params.agent!, 0),
|
|
2361
2551
|
share: shareEnabled,
|
|
2362
2552
|
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
2363
2553
|
artifactConfig,
|
|
@@ -2378,6 +2568,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2378
2568
|
skills: effectiveSkills,
|
|
2379
2569
|
acceptance: params.acceptance,
|
|
2380
2570
|
acceptanceContext: { mode: "single" },
|
|
2571
|
+
timeoutMs: data.timeoutMs,
|
|
2572
|
+
deadlineAt,
|
|
2381
2573
|
});
|
|
2382
2574
|
if (foregroundControl?.currentIndex === 0) {
|
|
2383
2575
|
foregroundControl.interrupt = undefined;
|
|
@@ -2462,6 +2654,23 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2462
2654
|
};
|
|
2463
2655
|
}
|
|
2464
2656
|
|
|
2657
|
+
function inferExecutionMode(params: SubagentParamsLike): SubagentRunMode {
|
|
2658
|
+
if ((params.chain?.length ?? 0) > 0) return "chain";
|
|
2659
|
+
if ((params.tasks?.length ?? 0) > 0) return "parallel";
|
|
2660
|
+
return "single";
|
|
2661
|
+
}
|
|
2662
|
+
|
|
2663
|
+
function duplicateSubagentCallResult(params: SubagentParamsLike): AgentToolResult<Details> {
|
|
2664
|
+
return {
|
|
2665
|
+
content: [{
|
|
2666
|
+
type: "text",
|
|
2667
|
+
text: "Rejected: a subagent call is already in progress. Issue exactly ONE subagent call per turn.",
|
|
2668
|
+
}],
|
|
2669
|
+
isError: true,
|
|
2670
|
+
details: { mode: inferExecutionMode(params), results: [] },
|
|
2671
|
+
};
|
|
2672
|
+
}
|
|
2673
|
+
|
|
2465
2674
|
export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
2466
2675
|
execute: (
|
|
2467
2676
|
id: string,
|
|
@@ -2498,7 +2707,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
2498
2707
|
let orchestratorTarget: string | undefined;
|
|
2499
2708
|
try {
|
|
2500
2709
|
orchestratorTarget = resolveIntercomSessionTarget(deps.pi.getSessionName(), ctx.sessionManager.getSessionId());
|
|
2501
|
-
} catch {
|
|
2710
|
+
} catch (error) {
|
|
2711
|
+
if (!sessionError) sessionError = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
2712
|
+
}
|
|
2502
2713
|
return {
|
|
2503
2714
|
content: [{
|
|
2504
2715
|
type: "text",
|
|
@@ -2624,13 +2835,16 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
2624
2835
|
depth,
|
|
2625
2836
|
deps.config.forceTopLevelAsync === true,
|
|
2626
2837
|
);
|
|
2838
|
+
const foregroundTimeout = resolveForegroundTimeout(effectiveParams);
|
|
2839
|
+
if (foregroundTimeout.error) return buildRequestedModeError(effectiveParams, foregroundTimeout.error);
|
|
2627
2840
|
|
|
2628
2841
|
const scope: AgentScope = resolveExecutionAgentScope(effectiveParams.agentScope);
|
|
2629
2842
|
const effectiveCwd = effectiveParams.cwd ?? ctx.cwd;
|
|
2630
2843
|
const parentSessionFile = ctx.sessionManager.getSessionFile() ?? null;
|
|
2631
2844
|
deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
2632
2845
|
const discoveredAgents = deps.discoverAgents(effectiveCwd, scope).agents;
|
|
2633
|
-
|
|
2846
|
+
const contextPolicy = resolveAgentDefaultContextPolicy(effectiveParams, discoveredAgents);
|
|
2847
|
+
effectiveParams = contextPolicy.params;
|
|
2634
2848
|
const sessionName = resolveIntercomSessionTarget(deps.pi.getSessionName(), ctx.sessionManager.getSessionId());
|
|
2635
2849
|
const intercomBridge = resolveIntercomBridge({
|
|
2636
2850
|
config: deps.config.intercomBridge,
|
|
@@ -2664,15 +2878,18 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
2664
2878
|
);
|
|
2665
2879
|
if (validationError) return validationError;
|
|
2666
2880
|
|
|
2667
|
-
let
|
|
2881
|
+
let forkSessionFileForIndex: (idx?: number) => string | undefined = () => undefined;
|
|
2668
2882
|
try {
|
|
2669
|
-
|
|
2883
|
+
forkSessionFileForIndex = createForkContextResolver(ctx.sessionManager, contextPolicy.usesFork ? "fork" : undefined).sessionFileForIndex;
|
|
2670
2884
|
} catch (error) {
|
|
2671
2885
|
return toExecutionErrorResult(effectiveParams, error);
|
|
2672
2886
|
}
|
|
2673
2887
|
const requestedAsync = effectiveParams.async ?? deps.asyncByDefault;
|
|
2674
2888
|
const backgroundRequestedWhileClarifying = (hasChain || hasTasks) && requestedAsync && effectiveParams.clarify === true;
|
|
2675
2889
|
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
|
+
}
|
|
2676
2893
|
const controlConfig = resolveControlConfig(deps.config.control, effectiveParams.control);
|
|
2677
2894
|
|
|
2678
2895
|
const artifactConfig: ArtifactConfig = {
|
|
@@ -2701,8 +2918,19 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
2701
2918
|
}
|
|
2702
2919
|
const sessionDirForIndex = (idx?: number) =>
|
|
2703
2920
|
path.join(sessionRoot, `run-${idx ?? 0}`);
|
|
2921
|
+
const forkSessionFileForTask = (agentName: string, idx?: number) =>
|
|
2922
|
+
shouldForkAgent(contextPolicy, agentName) ? forkSessionFileForIndex(idx) : undefined;
|
|
2923
|
+
const childSessionFileForTask = (agentName: string, idx?: number) =>
|
|
2924
|
+
forkSessionFileForTask(agentName, idx) ?? path.join(sessionDirForIndex(idx), "session.jsonl");
|
|
2704
2925
|
const childSessionFileForIndex = (idx?: number) =>
|
|
2705
|
-
|
|
2926
|
+
path.join(sessionDirForIndex(idx), "session.jsonl");
|
|
2927
|
+
try {
|
|
2928
|
+
preflightForkSessionsForStaticTasks(effectiveParams, contextPolicy, forkSessionFileForTask);
|
|
2929
|
+
} catch (error) {
|
|
2930
|
+
return toExecutionErrorResult(effectiveParams, error);
|
|
2931
|
+
}
|
|
2932
|
+
const chainBindingsError = validateExecutionChainBindings(effectiveParams, deps.config.chain?.dynamicFanout?.maxItems);
|
|
2933
|
+
if (chainBindingsError) return chainBindingsError;
|
|
2706
2934
|
|
|
2707
2935
|
const onUpdateWithContext = onUpdate
|
|
2708
2936
|
? (r: AgentToolResult<Details>) => onUpdate(withForkContext(r, effectiveParams.context))
|
|
@@ -2720,6 +2948,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
2720
2948
|
sessionRoot,
|
|
2721
2949
|
sessionDirForIndex,
|
|
2722
2950
|
sessionFileForIndex: childSessionFileForIndex,
|
|
2951
|
+
sessionFileForTask: childSessionFileForTask,
|
|
2723
2952
|
artifactConfig,
|
|
2724
2953
|
artifactsDir,
|
|
2725
2954
|
backgroundRequestedWhileClarifying,
|
|
@@ -2727,6 +2956,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
2727
2956
|
controlConfig,
|
|
2728
2957
|
intercomBridge,
|
|
2729
2958
|
nestedRoute,
|
|
2959
|
+
timeoutMs: foregroundTimeout.timeoutMs,
|
|
2960
|
+
contextPolicy,
|
|
2730
2961
|
};
|
|
2731
2962
|
|
|
2732
2963
|
const foregroundMode: "single" | "parallel" | "chain" = hasChain ? "chain" : hasTasks ? "parallel" : "single";
|
|
@@ -2851,5 +3082,22 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
2851
3082
|
}, effectiveParams.context);
|
|
2852
3083
|
};
|
|
2853
3084
|
|
|
2854
|
-
|
|
3085
|
+
const executeWithSingleDispatchGuard = async (
|
|
3086
|
+
id: string,
|
|
3087
|
+
params: SubagentParamsLike,
|
|
3088
|
+
signal: AbortSignal,
|
|
3089
|
+
onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
|
|
3090
|
+
ctx: ExtensionContext,
|
|
3091
|
+
): Promise<AgentToolResult<Details>> => {
|
|
3092
|
+
if (params.action) return execute(id, params, signal, onUpdate, ctx);
|
|
3093
|
+
if (deps.state.subagentInProgress === true) return duplicateSubagentCallResult(params);
|
|
3094
|
+
deps.state.subagentInProgress = true;
|
|
3095
|
+
try {
|
|
3096
|
+
return await execute(id, params, signal, onUpdate, ctx);
|
|
3097
|
+
} finally {
|
|
3098
|
+
deps.state.subagentInProgress = false;
|
|
3099
|
+
}
|
|
3100
|
+
};
|
|
3101
|
+
|
|
3102
|
+
return { execute: executeWithSingleDispatchGuard };
|
|
2855
3103
|
}
|