pi-subagents 0.30.0 → 0.31.1
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 +38 -0
- package/README.md +189 -18
- 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 +58 -4
- package/src/extension/schemas.ts +10 -76
- package/src/intercom/intercom-bridge.ts +27 -4
- package/src/profiles/profiles.ts +637 -0
- package/src/runs/background/async-execution.ts +14 -4
- package/src/runs/background/async-job-tracker.ts +56 -11
- package/src/runs/background/async-resume.ts +11 -13
- package/src/runs/background/control-channel.ts +177 -0
- 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 +86 -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 +356 -86
- 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/runs/shared/subagent-prompt-runtime.ts +1 -0
- package/src/shared/settings.ts +1 -0
- package/src/shared/types.ts +9 -2
- package/src/shared/utils.ts +19 -1
- package/src/slash/prompt-template-bridge.ts +26 -3
- package/src/slash/slash-commands.ts +642 -43
- 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";
|
|
@@ -50,6 +51,8 @@ import {
|
|
|
50
51
|
stripDetailsOutputsForIntercomReceipt,
|
|
51
52
|
} from "../../intercom/result-intercom.ts";
|
|
52
53
|
import { buildRevivedAsyncTask, interruptLiveAsyncResumeTarget, resolveAsyncResumeTarget } from "../background/async-resume.ts";
|
|
54
|
+
import { deliverInterruptRequest } from "../background/control-channel.ts";
|
|
55
|
+
import { reconcileAsyncRun } from "../background/stale-run-reconciler.ts";
|
|
53
56
|
import { resolveAsyncRootResultPath } from "../background/chain-root-attachment.ts";
|
|
54
57
|
import { createNestedRoute, readNestedControlResults, resolveInheritedNestedRouteFromEnv, resolveNestedAsyncDir, resolveNestedParentAddressFromEnv, updateForegroundNestedProjection, writeNestedControlRequest, writeNestedEvent, type NestedRunResolutionScope } from "../shared/nested-events.ts";
|
|
55
58
|
import { resolveSubagentRunId, type ResolvedSubagentRunId } from "../background/run-id-resolver.ts";
|
|
@@ -96,7 +99,6 @@ import {
|
|
|
96
99
|
wrapForkTask,
|
|
97
100
|
} from "../../shared/types.ts";
|
|
98
101
|
|
|
99
|
-
const ASYNC_INTERRUPT_SIGNAL: NodeJS.Signals = process.platform === "win32" ? "SIGBREAK" : "SIGUSR2";
|
|
100
102
|
const MUTATING_MANAGEMENT_ACTIONS = new Set(["create", "update", "delete"]);
|
|
101
103
|
|
|
102
104
|
interface TaskParam {
|
|
@@ -128,6 +130,8 @@ export interface SubagentParamsLike {
|
|
|
128
130
|
worktree?: boolean;
|
|
129
131
|
context?: "fresh" | "fork";
|
|
130
132
|
async?: boolean;
|
|
133
|
+
timeoutMs?: number;
|
|
134
|
+
maxRuntimeMs?: number;
|
|
131
135
|
clarify?: boolean;
|
|
132
136
|
share?: boolean;
|
|
133
137
|
control?: ControlConfig;
|
|
@@ -170,6 +174,7 @@ interface ExecutionContextData {
|
|
|
170
174
|
sessionRoot: string;
|
|
171
175
|
sessionDirForIndex: (idx?: number) => string;
|
|
172
176
|
sessionFileForIndex: (idx?: number) => string | undefined;
|
|
177
|
+
sessionFileForTask: (agentName: string, idx?: number) => string | undefined;
|
|
173
178
|
artifactConfig: ArtifactConfig;
|
|
174
179
|
artifactsDir: string;
|
|
175
180
|
backgroundRequestedWhileClarifying: boolean;
|
|
@@ -177,6 +182,9 @@ interface ExecutionContextData {
|
|
|
177
182
|
controlConfig: ResolvedControlConfig;
|
|
178
183
|
intercomBridge: IntercomBridgeState;
|
|
179
184
|
nestedRoute?: NestedRouteInfo;
|
|
185
|
+
timeoutMs?: number;
|
|
186
|
+
deadlineAt?: number;
|
|
187
|
+
contextPolicy: AgentDefaultContextPolicy;
|
|
180
188
|
}
|
|
181
189
|
|
|
182
190
|
function resolveRequestedCwd(runtimeCwd: string, requestedCwd: string | undefined): string {
|
|
@@ -365,7 +373,17 @@ function resolveResumeTarget(params: SubagentParamsLike, state: SubagentState, o
|
|
|
365
373
|
throw new Error("Run not found. Provide id or runId.");
|
|
366
374
|
}
|
|
367
375
|
|
|
368
|
-
function getAsyncInterruptTarget(
|
|
376
|
+
function getAsyncInterruptTarget(
|
|
377
|
+
state: SubagentState,
|
|
378
|
+
runId: string | undefined,
|
|
379
|
+
location?: { asyncDir: string | null; resolvedId?: string },
|
|
380
|
+
): { asyncId: string; asyncDir: string } | undefined {
|
|
381
|
+
if (location?.asyncDir) {
|
|
382
|
+
return {
|
|
383
|
+
asyncId: location.resolvedId ?? runId ?? path.basename(location.asyncDir),
|
|
384
|
+
asyncDir: location.asyncDir,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
369
387
|
if (runId) {
|
|
370
388
|
const direct = state.asyncJobs.get(runId);
|
|
371
389
|
if (direct) return { asyncId: direct.asyncId, asyncDir: direct.asyncDir };
|
|
@@ -408,10 +426,15 @@ function emitControlNotification(input: {
|
|
|
408
426
|
}
|
|
409
427
|
}
|
|
410
428
|
|
|
411
|
-
function interruptAsyncRun(
|
|
412
|
-
|
|
429
|
+
function interruptAsyncRun(
|
|
430
|
+
state: SubagentState,
|
|
431
|
+
runId: string | undefined,
|
|
432
|
+
kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean,
|
|
433
|
+
location?: { asyncDir: string | null; resolvedId?: string },
|
|
434
|
+
): AgentToolResult<Details> | null {
|
|
435
|
+
const target = getAsyncInterruptTarget(state, runId, location);
|
|
413
436
|
if (!target) return null;
|
|
414
|
-
const status =
|
|
437
|
+
const status = reconcileAsyncRun(target.asyncDir, { kill }).status;
|
|
415
438
|
if (!status || status.state !== "running" || typeof status.pid !== "number") {
|
|
416
439
|
return {
|
|
417
440
|
content: [{ type: "text", text: `No running async run with an interrupt-capable pid was found for '${runId ?? "current"}'.` }],
|
|
@@ -420,7 +443,7 @@ function interruptAsyncRun(state: SubagentState, runId: string | undefined, kill
|
|
|
420
443
|
};
|
|
421
444
|
}
|
|
422
445
|
try {
|
|
423
|
-
(
|
|
446
|
+
deliverInterruptRequest({ asyncDir: target.asyncDir, pid: status.pid, kill, source: "interrupt-action" });
|
|
424
447
|
const tracked = state.asyncJobs.get(target.asyncId);
|
|
425
448
|
if (tracked) {
|
|
426
449
|
tracked.activityState = undefined;
|
|
@@ -471,6 +494,14 @@ function appendStepToAsyncChain(input: {
|
|
|
471
494
|
details: { mode: "management", results: [] },
|
|
472
495
|
};
|
|
473
496
|
}
|
|
497
|
+
const acceptanceErrors = validateExecutionAcceptance(input.params);
|
|
498
|
+
if (acceptanceErrors.length > 0) {
|
|
499
|
+
return {
|
|
500
|
+
content: [{ type: "text", text: `Cannot append step: ${acceptanceErrors.join(" ")}` }],
|
|
501
|
+
isError: true,
|
|
502
|
+
details: { mode: "management", results: [] },
|
|
503
|
+
};
|
|
504
|
+
}
|
|
474
505
|
|
|
475
506
|
let resolved: ResolvedSubagentRunId | undefined;
|
|
476
507
|
try {
|
|
@@ -547,17 +578,19 @@ function appendStepToAsyncChain(input: {
|
|
|
547
578
|
|
|
548
579
|
const scope: AgentScope = resolveExecutionAgentScope(input.params.agentScope);
|
|
549
580
|
const agents = input.deps.discoverAgents(input.requestCwd, scope).agents;
|
|
581
|
+
const contextPolicy = resolveExplicitContextPolicy(input.params);
|
|
550
582
|
const chainSkillInput = normalizeSkillInput(input.params.skill);
|
|
551
583
|
const chainSkills = chainSkillInput === false ? [] : (chainSkillInput ?? []);
|
|
552
584
|
const asyncCtx = {
|
|
553
585
|
pi: input.deps.pi,
|
|
554
586
|
cwd: input.ctx.cwd,
|
|
555
587
|
currentSessionId: resolveCurrentSessionId(input.ctx.sessionManager),
|
|
588
|
+
parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
|
|
556
589
|
currentModelProvider: input.ctx.model?.provider,
|
|
557
590
|
currentModel: input.ctx.model,
|
|
558
591
|
};
|
|
559
592
|
const built = buildAsyncRunnerSteps(resolved.id, {
|
|
560
|
-
chain: wrapChainTasksForFork(input.params.chain,
|
|
593
|
+
chain: wrapChainTasksForFork(input.params.chain, contextPolicy),
|
|
561
594
|
task: input.params.task,
|
|
562
595
|
resultMode: "chain",
|
|
563
596
|
agents,
|
|
@@ -702,11 +735,11 @@ function directNestedAsyncInterrupt(target: ResolvedSubagentRunId & { kind: "nes
|
|
|
702
735
|
const run = target.match.run;
|
|
703
736
|
const asyncDir = resolveNestedAsyncDir(target.match.rootRunId, run);
|
|
704
737
|
if (!asyncDir) return undefined;
|
|
705
|
-
const status =
|
|
738
|
+
const status = reconcileAsyncRun(asyncDir, { resultsDir: path.join(RESULTS_DIR, "nested", target.match.rootRunId) }).status;
|
|
706
739
|
const pid = typeof status?.pid === "number" && status.pid > 0 ? status.pid : run.pid;
|
|
707
740
|
if (!status || status.state !== "running" || typeof pid !== "number" || pid <= 0) return undefined;
|
|
708
741
|
try {
|
|
709
|
-
|
|
742
|
+
deliverInterruptRequest({ asyncDir, pid, source: "nested-interrupt" });
|
|
710
743
|
return { content: [{ type: "text", text: `Interrupt requested for nested async run ${run.id}.` }], details: { mode: "management", results: [] } };
|
|
711
744
|
} catch (error) {
|
|
712
745
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -783,6 +816,7 @@ async function resumeAsyncRun(input: {
|
|
|
783
816
|
target,
|
|
784
817
|
state: input.deps.state,
|
|
785
818
|
kill: input.deps.kill,
|
|
819
|
+
resultsDir: RESULTS_DIR,
|
|
786
820
|
});
|
|
787
821
|
if (!interrupt.ok) {
|
|
788
822
|
return {
|
|
@@ -861,7 +895,8 @@ async function resumeAsyncRun(input: {
|
|
|
861
895
|
const runId = randomUUID().slice(0, 8);
|
|
862
896
|
const artifactConfig: ArtifactConfig = { ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false };
|
|
863
897
|
const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
864
|
-
const
|
|
898
|
+
const contextPolicy = resolveExplicitContextPolicy(input.params);
|
|
899
|
+
const chain = wrapChainTasksForFork(attachChain, contextPolicy);
|
|
865
900
|
const normalized = normalizeSkillInput(input.params.skill);
|
|
866
901
|
const result = executeAsyncChain(runId, {
|
|
867
902
|
chain,
|
|
@@ -879,6 +914,7 @@ async function resumeAsyncRun(input: {
|
|
|
879
914
|
pi: input.deps.pi,
|
|
880
915
|
cwd: input.requestCwd,
|
|
881
916
|
currentSessionId: input.deps.state.currentSessionId,
|
|
917
|
+
parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
|
|
882
918
|
currentModelProvider: input.ctx.model?.provider,
|
|
883
919
|
currentModel: input.ctx.model,
|
|
884
920
|
},
|
|
@@ -921,6 +957,7 @@ async function resumeAsyncRun(input: {
|
|
|
921
957
|
pi: input.deps.pi,
|
|
922
958
|
cwd: input.requestCwd,
|
|
923
959
|
currentSessionId: input.deps.state.currentSessionId,
|
|
960
|
+
parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
|
|
924
961
|
currentModelProvider: input.ctx.model?.provider,
|
|
925
962
|
currentModel: input.ctx.model,
|
|
926
963
|
},
|
|
@@ -1068,6 +1105,15 @@ function validateExecutionInput(
|
|
|
1068
1105
|
};
|
|
1069
1106
|
}
|
|
1070
1107
|
|
|
1108
|
+
const acceptanceErrors = validateExecutionAcceptance(params);
|
|
1109
|
+
if (acceptanceErrors.length > 0) {
|
|
1110
|
+
return {
|
|
1111
|
+
content: [{ type: "text", text: acceptanceErrors.join(" ") }],
|
|
1112
|
+
isError: true,
|
|
1113
|
+
details: { mode: getRequestedModeLabel(params), results: [] },
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1071
1117
|
if (hasSingle && params.agent && !agents.find((agent) => agent.name === params.agent)) {
|
|
1072
1118
|
return {
|
|
1073
1119
|
content: [{ type: "text", text: `Unknown agent: ${params.agent}` }],
|
|
@@ -1145,6 +1191,42 @@ function validateExecutionInput(
|
|
|
1145
1191
|
return null;
|
|
1146
1192
|
}
|
|
1147
1193
|
|
|
1194
|
+
function validateExecutionChainBindings(params: SubagentParamsLike, dynamicFanoutMaxItems?: number): AgentToolResult<Details> | null {
|
|
1195
|
+
if ((params.chain?.length ?? 0) === 0) return null;
|
|
1196
|
+
try {
|
|
1197
|
+
validateChainOutputBindingsWithContext(params.chain as ChainStep[], { maxItems: dynamicFanoutMaxItems });
|
|
1198
|
+
} catch (error) {
|
|
1199
|
+
if (error instanceof ChainOutputValidationError) {
|
|
1200
|
+
return {
|
|
1201
|
+
content: [{ type: "text", text: error.message }],
|
|
1202
|
+
isError: true,
|
|
1203
|
+
details: { mode: "chain" as const, results: [] },
|
|
1204
|
+
};
|
|
1205
|
+
}
|
|
1206
|
+
throw error;
|
|
1207
|
+
}
|
|
1208
|
+
return null;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
function validateExecutionAcceptance(params: SubagentParamsLike): string[] {
|
|
1212
|
+
const errors: string[] = [];
|
|
1213
|
+
errors.push(...validateAcceptanceInput(params.acceptance, "acceptance"));
|
|
1214
|
+
for (const [index, task] of (params.tasks ?? []).entries()) {
|
|
1215
|
+
errors.push(...validateAcceptanceInput(task.acceptance, `tasks[${index}].acceptance`));
|
|
1216
|
+
}
|
|
1217
|
+
for (const [stepIndex, step] of (params.chain ?? []).entries()) {
|
|
1218
|
+
errors.push(...validateAcceptanceInput((step as { acceptance?: unknown }).acceptance, `chain[${stepIndex}].acceptance`));
|
|
1219
|
+
if (isParallelStep(step)) {
|
|
1220
|
+
for (const [taskIndex, task] of step.parallel.entries()) {
|
|
1221
|
+
errors.push(...validateAcceptanceInput(task.acceptance, `chain[${stepIndex}].parallel[${taskIndex}].acceptance`));
|
|
1222
|
+
}
|
|
1223
|
+
} else if (isDynamicParallelStep(step)) {
|
|
1224
|
+
errors.push(...validateAcceptanceInput(step.parallel.acceptance, `chain[${stepIndex}].parallel.acceptance`));
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
return errors;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1148
1230
|
function getRequestedModeLabel(params: SubagentParamsLike): Details["mode"] {
|
|
1149
1231
|
if ((params.chain?.length ?? 0) > 0) return "chain";
|
|
1150
1232
|
if ((params.tasks?.length ?? 0) > 0) return "parallel";
|
|
@@ -1152,16 +1234,46 @@ function getRequestedModeLabel(params: SubagentParamsLike): Details["mode"] {
|
|
|
1152
1234
|
return "single";
|
|
1153
1235
|
}
|
|
1154
1236
|
|
|
1155
|
-
|
|
1156
|
-
|
|
1237
|
+
interface AgentDefaultContextPolicy {
|
|
1238
|
+
params: SubagentParamsLike;
|
|
1239
|
+
contextForAgent(agentName: string): "fresh" | "fork";
|
|
1240
|
+
usesFork: boolean;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
function resolveAgentDefaultContextPolicy(params: SubagentParamsLike, agents: AgentConfig[]): AgentDefaultContextPolicy {
|
|
1244
|
+
if (params.context !== undefined) {
|
|
1245
|
+
return resolveExplicitContextPolicy(params);
|
|
1246
|
+
}
|
|
1157
1247
|
const byName = new Map(agents.map((agent) => [agent.name, agent]));
|
|
1248
|
+
const contextForAgent = (agentName: string): "fresh" | "fork" =>
|
|
1249
|
+
byName.get(agentName)?.defaultContext === "fork" ? "fork" : "fresh";
|
|
1250
|
+
const usesFork = collectRequestedAgentNames(params).some((name) => contextForAgent(name) === "fork");
|
|
1251
|
+
return {
|
|
1252
|
+
params: usesFork ? { ...params, context: "fork" } : params,
|
|
1253
|
+
contextForAgent,
|
|
1254
|
+
usesFork,
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
function resolveExplicitContextPolicy(params: SubagentParamsLike): AgentDefaultContextPolicy {
|
|
1259
|
+
const context = params.context === "fork" ? "fork" : "fresh";
|
|
1260
|
+
return {
|
|
1261
|
+
params,
|
|
1262
|
+
contextForAgent: () => context,
|
|
1263
|
+
usesFork: context === "fork",
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
function collectRequestedAgentNames(params: SubagentParamsLike): string[] {
|
|
1158
1268
|
const names: string[] = [];
|
|
1159
1269
|
if (params.agent) names.push(params.agent);
|
|
1160
1270
|
for (const task of params.tasks ?? []) names.push(task.agent);
|
|
1161
1271
|
for (const step of params.chain ?? []) names.push(...getStepAgents(step));
|
|
1162
|
-
return names
|
|
1163
|
-
|
|
1164
|
-
|
|
1272
|
+
return names;
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
function shouldForkAgent(contextPolicy: AgentDefaultContextPolicy, agentName: string): boolean {
|
|
1276
|
+
return contextPolicy.contextForAgent(agentName) === "fork";
|
|
1165
1277
|
}
|
|
1166
1278
|
|
|
1167
1279
|
function buildRequestedModeError(params: SubagentParamsLike, message: string): AgentToolResult<Details> {
|
|
@@ -1175,6 +1287,22 @@ function buildRequestedModeError(params: SubagentParamsLike, message: string): A
|
|
|
1175
1287
|
);
|
|
1176
1288
|
}
|
|
1177
1289
|
|
|
1290
|
+
function resolveForegroundTimeout(params: SubagentParamsLike): { timeoutMs?: number; error?: string } {
|
|
1291
|
+
const rawTimeout = params.timeoutMs;
|
|
1292
|
+
const rawMaxRuntime = params.maxRuntimeMs;
|
|
1293
|
+
if (rawTimeout === undefined && rawMaxRuntime === undefined) return {};
|
|
1294
|
+
for (const [name, value] of [["timeoutMs", rawTimeout], ["maxRuntimeMs", rawMaxRuntime]] as const) {
|
|
1295
|
+
if (value === undefined) continue;
|
|
1296
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
|
|
1297
|
+
return { error: `${name} must be a positive integer.` };
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
if (rawTimeout !== undefined && rawMaxRuntime !== undefined && rawTimeout !== rawMaxRuntime) {
|
|
1301
|
+
return { error: "timeoutMs and maxRuntimeMs are aliases; provide only one value or use the same value for both." };
|
|
1302
|
+
}
|
|
1303
|
+
return { timeoutMs: rawTimeout ?? rawMaxRuntime };
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1178
1306
|
function expandTopLevelTaskCounts(tasks: TaskParam[]): { tasks?: TaskParam[]; error?: string } {
|
|
1179
1307
|
const expanded: TaskParam[] = [];
|
|
1180
1308
|
for (let taskIndex = 0; taskIndex < tasks.length; taskIndex++) {
|
|
@@ -1262,14 +1390,14 @@ function toExecutionErrorResult(params: SubagentParamsLike, error: unknown): Age
|
|
|
1262
1390
|
|
|
1263
1391
|
function collectChainSessionFiles(
|
|
1264
1392
|
chain: ChainStep[],
|
|
1265
|
-
|
|
1393
|
+
sessionFileForTask: (agentName: string, idx?: number) => string | undefined,
|
|
1266
1394
|
): (string | undefined)[] {
|
|
1267
1395
|
const sessionFiles: (string | undefined)[] = [];
|
|
1268
1396
|
let flatIndex = 0;
|
|
1269
1397
|
for (const step of chain) {
|
|
1270
1398
|
if (isParallelStep(step)) {
|
|
1271
|
-
for (
|
|
1272
|
-
sessionFiles.push(
|
|
1399
|
+
for (const task of step.parallel) {
|
|
1400
|
+
sessionFiles.push(sessionFileForTask(task.agent, flatIndex));
|
|
1273
1401
|
flatIndex++;
|
|
1274
1402
|
}
|
|
1275
1403
|
continue;
|
|
@@ -1278,21 +1406,22 @@ function collectChainSessionFiles(
|
|
|
1278
1406
|
sessionFiles.push(undefined);
|
|
1279
1407
|
continue;
|
|
1280
1408
|
}
|
|
1281
|
-
sessionFiles.push(
|
|
1409
|
+
sessionFiles.push(sessionFileForTask((step as SequentialStep).agent, flatIndex));
|
|
1282
1410
|
flatIndex++;
|
|
1283
1411
|
}
|
|
1284
1412
|
return sessionFiles;
|
|
1285
1413
|
}
|
|
1286
1414
|
|
|
1287
|
-
function wrapChainTasksForFork(chain: ChainStep[],
|
|
1288
|
-
if (context !== "fork") return chain;
|
|
1415
|
+
function wrapChainTasksForFork(chain: ChainStep[], contextPolicy: AgentDefaultContextPolicy): ChainStep[] {
|
|
1289
1416
|
return chain.map((step, stepIndex) => {
|
|
1290
1417
|
if (isParallelStep(step)) {
|
|
1291
1418
|
return {
|
|
1292
1419
|
...step,
|
|
1293
1420
|
parallel: step.parallel.map((task) => ({
|
|
1294
1421
|
...task,
|
|
1295
|
-
task:
|
|
1422
|
+
task: shouldForkAgent(contextPolicy, task.agent)
|
|
1423
|
+
? wrapForkTask(task.task ?? "{previous}")
|
|
1424
|
+
: task.task,
|
|
1296
1425
|
})),
|
|
1297
1426
|
};
|
|
1298
1427
|
}
|
|
@@ -1301,18 +1430,59 @@ function wrapChainTasksForFork(chain: ChainStep[], context: SubagentParamsLike["
|
|
|
1301
1430
|
...step,
|
|
1302
1431
|
parallel: {
|
|
1303
1432
|
...step.parallel,
|
|
1304
|
-
task:
|
|
1433
|
+
task: shouldForkAgent(contextPolicy, step.parallel.agent)
|
|
1434
|
+
? wrapForkTask(step.parallel.task ?? "{previous}")
|
|
1435
|
+
: step.parallel.task,
|
|
1305
1436
|
},
|
|
1306
1437
|
};
|
|
1307
1438
|
}
|
|
1308
1439
|
const sequential = step as SequentialStep;
|
|
1309
1440
|
return {
|
|
1310
1441
|
...sequential,
|
|
1311
|
-
task:
|
|
1442
|
+
task: shouldForkAgent(contextPolicy, sequential.agent)
|
|
1443
|
+
? wrapForkTask(sequential.task ?? (stepIndex === 0 ? "{task}" : "{previous}"))
|
|
1444
|
+
: sequential.task,
|
|
1312
1445
|
};
|
|
1313
1446
|
});
|
|
1314
1447
|
}
|
|
1315
1448
|
|
|
1449
|
+
function preflightForkSessionsForStaticTasks(
|
|
1450
|
+
params: SubagentParamsLike,
|
|
1451
|
+
contextPolicy: AgentDefaultContextPolicy,
|
|
1452
|
+
sessionFileForTask: (agentName: string, idx?: number) => string | undefined,
|
|
1453
|
+
): void {
|
|
1454
|
+
if (!contextPolicy.usesFork) return;
|
|
1455
|
+
if (params.agent) {
|
|
1456
|
+
if (shouldForkAgent(contextPolicy, params.agent)) sessionFileForTask(params.agent, 0);
|
|
1457
|
+
return;
|
|
1458
|
+
}
|
|
1459
|
+
if (params.tasks) {
|
|
1460
|
+
params.tasks.forEach((task, index) => {
|
|
1461
|
+
if (shouldForkAgent(contextPolicy, task.agent)) sessionFileForTask(task.agent, index);
|
|
1462
|
+
});
|
|
1463
|
+
return;
|
|
1464
|
+
}
|
|
1465
|
+
if (!params.chain?.length) return;
|
|
1466
|
+
let flatIndex = 0;
|
|
1467
|
+
for (const step of params.chain) {
|
|
1468
|
+
if (isParallelStep(step)) {
|
|
1469
|
+
for (const task of step.parallel) {
|
|
1470
|
+
if (shouldForkAgent(contextPolicy, task.agent)) sessionFileForTask(task.agent, flatIndex);
|
|
1471
|
+
flatIndex++;
|
|
1472
|
+
}
|
|
1473
|
+
continue;
|
|
1474
|
+
}
|
|
1475
|
+
if (isDynamicParallelStep(step)) {
|
|
1476
|
+
if (shouldForkAgent(contextPolicy, step.parallel.agent)) sessionFileForTask(step.parallel.agent, flatIndex);
|
|
1477
|
+
flatIndex++;
|
|
1478
|
+
continue;
|
|
1479
|
+
}
|
|
1480
|
+
const sequential = step as SequentialStep;
|
|
1481
|
+
if (shouldForkAgent(contextPolicy, sequential.agent)) sessionFileForTask(sequential.agent, flatIndex);
|
|
1482
|
+
flatIndex++;
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1316
1486
|
function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentToolResult<Details> | null {
|
|
1317
1487
|
const {
|
|
1318
1488
|
params,
|
|
@@ -1322,12 +1492,14 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1322
1492
|
shareEnabled,
|
|
1323
1493
|
sessionRoot,
|
|
1324
1494
|
sessionFileForIndex,
|
|
1495
|
+
sessionFileForTask,
|
|
1325
1496
|
artifactConfig,
|
|
1326
1497
|
artifactsDir,
|
|
1327
1498
|
effectiveAsync,
|
|
1328
1499
|
controlConfig,
|
|
1329
1500
|
intercomBridge,
|
|
1330
1501
|
nestedRoute,
|
|
1502
|
+
contextPolicy,
|
|
1331
1503
|
} = data;
|
|
1332
1504
|
const hasChain = (params.chain?.length ?? 0) > 0;
|
|
1333
1505
|
const hasTasks = (params.tasks?.length ?? 0) > 0;
|
|
@@ -1368,6 +1540,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1368
1540
|
pi: deps.pi,
|
|
1369
1541
|
cwd: ctx.cwd,
|
|
1370
1542
|
currentSessionId: deps.state.currentSessionId!,
|
|
1543
|
+
parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
|
|
1371
1544
|
currentModelProvider: ctx.model?.provider,
|
|
1372
1545
|
currentModel: ctx.model,
|
|
1373
1546
|
};
|
|
@@ -1385,7 +1558,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1385
1558
|
const skillOverrides = params.tasks.map((task) => normalizeSkillInput(task.skill));
|
|
1386
1559
|
const parallelTasks = params.tasks.map((task, index) => ({
|
|
1387
1560
|
agent: task.agent,
|
|
1388
|
-
task:
|
|
1561
|
+
task: shouldForkAgent(contextPolicy, task.agent) ? wrapForkTask(task.task) : task.task,
|
|
1389
1562
|
cwd: task.cwd,
|
|
1390
1563
|
...(modelOverrides[index] ? { model: modelOverrides[index] } : {}),
|
|
1391
1564
|
...(skillOverrides[index] !== undefined ? { skill: skillOverrides[index] } : {}),
|
|
@@ -1412,7 +1585,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1412
1585
|
shareEnabled,
|
|
1413
1586
|
sessionRoot,
|
|
1414
1587
|
chainSkills: [],
|
|
1415
|
-
sessionFilesByFlatIndex: params.tasks.map((
|
|
1588
|
+
sessionFilesByFlatIndex: params.tasks.map((task, index) => sessionFileForTask(task.agent, index)),
|
|
1416
1589
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
1417
1590
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1418
1591
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
@@ -1426,7 +1599,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1426
1599
|
if (hasChain && params.chain) {
|
|
1427
1600
|
const normalized = normalizeSkillInput(params.skill);
|
|
1428
1601
|
const chainSkills = normalized === false ? [] : (normalized ?? []);
|
|
1429
|
-
const chain = wrapChainTasksForFork(params.chain as ChainStep[],
|
|
1602
|
+
const chain = wrapChainTasksForFork(params.chain as ChainStep[], contextPolicy);
|
|
1430
1603
|
return executeAsyncChain(id, {
|
|
1431
1604
|
chain,
|
|
1432
1605
|
task: params.task,
|
|
@@ -1440,7 +1613,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1440
1613
|
shareEnabled,
|
|
1441
1614
|
sessionRoot,
|
|
1442
1615
|
chainSkills,
|
|
1443
|
-
sessionFilesByFlatIndex: collectChainSessionFiles(chain,
|
|
1616
|
+
sessionFilesByFlatIndex: collectChainSessionFiles(chain, sessionFileForTask),
|
|
1444
1617
|
dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
|
|
1445
1618
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
1446
1619
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
@@ -1470,7 +1643,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1470
1643
|
const modelOverride = resolveSubagentModelOverride((params.model as string | undefined) ?? a.model, ctx.model, availableModels, currentProvider);
|
|
1471
1644
|
return executeAsyncSingle(id, {
|
|
1472
1645
|
agent: params.agent!,
|
|
1473
|
-
task: params.
|
|
1646
|
+
task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(params.task ?? "") : (params.task ?? ""),
|
|
1474
1647
|
agentConfig: a,
|
|
1475
1648
|
ctx: asyncCtx,
|
|
1476
1649
|
availableModels,
|
|
@@ -1480,7 +1653,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1480
1653
|
artifactConfig,
|
|
1481
1654
|
shareEnabled,
|
|
1482
1655
|
sessionRoot,
|
|
1483
|
-
sessionFile:
|
|
1656
|
+
sessionFile: sessionFileForTask(params.agent!, 0),
|
|
1484
1657
|
skills,
|
|
1485
1658
|
output: effectiveOutput,
|
|
1486
1659
|
outputMode: effectiveOutputMode,
|
|
@@ -1510,18 +1683,20 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
1510
1683
|
shareEnabled,
|
|
1511
1684
|
sessionDirForIndex,
|
|
1512
1685
|
sessionFileForIndex,
|
|
1686
|
+
sessionFileForTask,
|
|
1513
1687
|
artifactsDir,
|
|
1514
1688
|
artifactConfig,
|
|
1515
1689
|
onUpdate,
|
|
1516
1690
|
sessionRoot,
|
|
1517
1691
|
controlConfig,
|
|
1692
|
+
contextPolicy,
|
|
1518
1693
|
} = data;
|
|
1519
1694
|
const onControlEvent = createForegroundControlNotifier(data, deps);
|
|
1520
1695
|
const childIntercomTarget = data.intercomBridge.active ? resolveSubagentIntercomTarget : undefined;
|
|
1521
1696
|
const foregroundControl = deps.state.foregroundControls.get(runId);
|
|
1522
1697
|
const normalized = normalizeSkillInput(params.skill);
|
|
1523
1698
|
const chainSkills = normalized === false ? [] : (normalized ?? []);
|
|
1524
|
-
const chain = wrapChainTasksForFork(params.chain as ChainStep[],
|
|
1699
|
+
const chain = wrapChainTasksForFork(params.chain as ChainStep[], contextPolicy);
|
|
1525
1700
|
const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
|
|
1526
1701
|
const chainResult = await executeChain({
|
|
1527
1702
|
chain,
|
|
@@ -1535,6 +1710,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
1535
1710
|
shareEnabled,
|
|
1536
1711
|
sessionDirForIndex,
|
|
1537
1712
|
sessionFileForIndex,
|
|
1713
|
+
sessionFileForTask,
|
|
1538
1714
|
artifactsDir,
|
|
1539
1715
|
artifactConfig,
|
|
1540
1716
|
includeProgress: params.includeProgress,
|
|
@@ -1552,9 +1728,14 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
1552
1728
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
1553
1729
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1554
1730
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
1731
|
+
timeoutMs: data.timeoutMs,
|
|
1732
|
+
deadlineAt: data.deadlineAt,
|
|
1555
1733
|
});
|
|
1556
1734
|
|
|
1557
1735
|
if (chainResult.requestedAsync) {
|
|
1736
|
+
if (data.timeoutMs !== undefined) {
|
|
1737
|
+
return buildRequestedModeError(params, "timeoutMs/maxRuntimeMs are only supported for foreground runs; background launch from clarify cannot preserve the timeout.");
|
|
1738
|
+
}
|
|
1558
1739
|
if (!isAsyncAvailable()) {
|
|
1559
1740
|
return {
|
|
1560
1741
|
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 +1748,11 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
1567
1748
|
pi: deps.pi,
|
|
1568
1749
|
cwd: ctx.cwd,
|
|
1569
1750
|
currentSessionId: deps.state.currentSessionId!,
|
|
1751
|
+
parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
|
|
1570
1752
|
currentModelProvider: ctx.model?.provider,
|
|
1571
1753
|
currentModel: ctx.model,
|
|
1572
1754
|
};
|
|
1573
|
-
const asyncChain = wrapChainTasksForFork(chainResult.requestedAsync.chain,
|
|
1755
|
+
const asyncChain = wrapChainTasksForFork(chainResult.requestedAsync.chain, contextPolicy);
|
|
1574
1756
|
return executeAsyncChain(id, {
|
|
1575
1757
|
chain: asyncChain,
|
|
1576
1758
|
task: params.task,
|
|
@@ -1584,7 +1766,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
1584
1766
|
shareEnabled,
|
|
1585
1767
|
sessionRoot,
|
|
1586
1768
|
chainSkills: chainResult.requestedAsync.chainSkills,
|
|
1587
|
-
sessionFilesByFlatIndex: collectChainSessionFiles(asyncChain,
|
|
1769
|
+
sessionFilesByFlatIndex: collectChainSessionFiles(asyncChain, sessionFileForTask),
|
|
1588
1770
|
dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
|
|
1589
1771
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
1590
1772
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
@@ -1630,11 +1812,13 @@ interface ForegroundParallelRunInput {
|
|
|
1630
1812
|
runId: string;
|
|
1631
1813
|
sessionDirForIndex: (idx?: number) => string | undefined;
|
|
1632
1814
|
sessionFileForIndex: (idx?: number) => string | undefined;
|
|
1815
|
+
sessionFileForTask: (agentName: string, idx?: number) => string | undefined;
|
|
1633
1816
|
shareEnabled: boolean;
|
|
1634
1817
|
artifactConfig: ArtifactConfig;
|
|
1635
1818
|
artifactsDir: string;
|
|
1636
1819
|
maxOutput?: MaxOutputConfig;
|
|
1637
1820
|
paramsCwd: string;
|
|
1821
|
+
progressDir: string;
|
|
1638
1822
|
maxSubagentDepths: number[];
|
|
1639
1823
|
availableModels: ModelInfo[];
|
|
1640
1824
|
modelOverrides: (string | undefined)[];
|
|
@@ -1650,6 +1834,8 @@ interface ForegroundParallelRunInput {
|
|
|
1650
1834
|
liveProgress: (AgentProgress | undefined)[];
|
|
1651
1835
|
onUpdate?: (r: AgentToolResult<Details>) => void;
|
|
1652
1836
|
worktreeSetup?: WorktreeSetup;
|
|
1837
|
+
timeoutMs?: number;
|
|
1838
|
+
deadlineAt?: number;
|
|
1653
1839
|
}
|
|
1654
1840
|
|
|
1655
1841
|
function buildParallelModeError(message: string): AgentToolResult<Details> {
|
|
@@ -1760,7 +1946,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
1760
1946
|
? buildChainInstructions({ ...behavior, output: false, progress: false }, taskCwd, false)
|
|
1761
1947
|
: { prefix: "", suffix: "" };
|
|
1762
1948
|
const progressInstructions = behavior
|
|
1763
|
-
? buildChainInstructions({ ...behavior, output: false, reads: false }, input.
|
|
1949
|
+
? buildChainInstructions({ ...behavior, output: false, reads: false }, input.progressDir, index === input.firstProgressIndex)
|
|
1764
1950
|
: { prefix: "", suffix: "" };
|
|
1765
1951
|
const outputPath = resolveSingleOutputPath(behavior?.output, input.ctx.cwd, taskCwd);
|
|
1766
1952
|
const taskText = injectSingleOutputInstruction(
|
|
@@ -1783,6 +1969,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
1783
1969
|
}
|
|
1784
1970
|
const agentConfig = input.agents.find((agent) => agent.name === task.agent);
|
|
1785
1971
|
return runSync(input.ctx.cwd, input.agents, task.agent, taskText, {
|
|
1972
|
+
parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
|
|
1786
1973
|
cwd: taskCwd,
|
|
1787
1974
|
signal: input.signal,
|
|
1788
1975
|
interruptSignal: interruptController.signal,
|
|
@@ -1791,7 +1978,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
1791
1978
|
runId: input.runId,
|
|
1792
1979
|
index,
|
|
1793
1980
|
sessionDir: input.sessionDirForIndex(index),
|
|
1794
|
-
sessionFile: input.
|
|
1981
|
+
sessionFile: input.sessionFileForTask(task.agent, index),
|
|
1795
1982
|
share: input.shareEnabled,
|
|
1796
1983
|
artifactsDir: input.artifactConfig.enabled ? input.artifactsDir : undefined,
|
|
1797
1984
|
artifactConfig: input.artifactConfig,
|
|
@@ -1810,39 +1997,41 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
1810
1997
|
skills: effectiveSkills === false ? [] : effectiveSkills,
|
|
1811
1998
|
acceptance: task.acceptance,
|
|
1812
1999
|
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
|
-
});
|
|
2000
|
+
timeoutMs: input.timeoutMs,
|
|
2001
|
+
deadlineAt: input.deadlineAt,
|
|
2002
|
+
onUpdate: input.onUpdate
|
|
2003
|
+
? (progressUpdate) => {
|
|
2004
|
+
const stepResults = progressUpdate.details?.results || [];
|
|
2005
|
+
const stepProgress = progressUpdate.details?.progress || [];
|
|
2006
|
+
if (input.foregroundControl && stepProgress.length > 0) {
|
|
2007
|
+
const current = stepProgress[0];
|
|
2008
|
+
input.foregroundControl.currentAgent = task.agent;
|
|
2009
|
+
input.foregroundControl.currentIndex = index;
|
|
2010
|
+
input.foregroundControl.currentActivityState = current?.activityState;
|
|
2011
|
+
input.foregroundControl.lastActivityAt = current?.lastActivityAt;
|
|
2012
|
+
input.foregroundControl.currentTool = current?.currentTool;
|
|
2013
|
+
input.foregroundControl.currentToolStartedAt = current?.currentToolStartedAt;
|
|
2014
|
+
input.foregroundControl.currentPath = current?.currentPath;
|
|
2015
|
+
input.foregroundControl.turnCount = current?.turnCount;
|
|
2016
|
+
input.foregroundControl.tokens = current?.tokens;
|
|
2017
|
+
input.foregroundControl.toolCount = current?.toolCount;
|
|
2018
|
+
input.foregroundControl.updatedAt = Date.now();
|
|
1845
2019
|
}
|
|
2020
|
+
if (stepResults.length > 0) input.liveResults[index] = stepResults[0];
|
|
2021
|
+
if (stepProgress.length > 0) input.liveProgress[index] = stepProgress[0];
|
|
2022
|
+
const mergedResults = input.liveResults.filter((result): result is SingleResult => result !== undefined);
|
|
2023
|
+
const mergedProgress = input.liveProgress.filter((progress): progress is AgentProgress => progress !== undefined);
|
|
2024
|
+
input.onUpdate?.({
|
|
2025
|
+
content: progressUpdate.content,
|
|
2026
|
+
details: {
|
|
2027
|
+
mode: "parallel",
|
|
2028
|
+
results: mergedResults,
|
|
2029
|
+
progress: mergedProgress,
|
|
2030
|
+
controlEvents: progressUpdate.details?.controlEvents,
|
|
2031
|
+
totalSteps: input.tasks.length,
|
|
2032
|
+
},
|
|
2033
|
+
});
|
|
2034
|
+
}
|
|
1846
2035
|
: undefined,
|
|
1847
2036
|
}).finally(() => {
|
|
1848
2037
|
if (input.foregroundControl?.currentIndex === index) {
|
|
@@ -1863,6 +2052,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
1863
2052
|
runId,
|
|
1864
2053
|
sessionDirForIndex,
|
|
1865
2054
|
sessionFileForIndex,
|
|
2055
|
+
sessionFileForTask,
|
|
1866
2056
|
shareEnabled,
|
|
1867
2057
|
artifactConfig,
|
|
1868
2058
|
artifactsDir,
|
|
@@ -1870,6 +2060,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
1870
2060
|
onUpdate,
|
|
1871
2061
|
sessionRoot,
|
|
1872
2062
|
controlConfig,
|
|
2063
|
+
contextPolicy,
|
|
1873
2064
|
} = data;
|
|
1874
2065
|
const onControlEvent = createForegroundControlNotifier(data, deps);
|
|
1875
2066
|
const childIntercomTarget = data.intercomBridge.active ? resolveSubagentIntercomTarget : undefined;
|
|
@@ -1972,6 +2163,9 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
1972
2163
|
}
|
|
1973
2164
|
|
|
1974
2165
|
if (result.runInBackground) {
|
|
2166
|
+
if (data.timeoutMs !== undefined) {
|
|
2167
|
+
return buildRequestedModeError(params, "timeoutMs/maxRuntimeMs are only supported for foreground runs; background launch from clarify cannot preserve the timeout.");
|
|
2168
|
+
}
|
|
1975
2169
|
if (!isAsyncAvailable()) {
|
|
1976
2170
|
return {
|
|
1977
2171
|
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 +2178,12 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
1984
2178
|
pi: deps.pi,
|
|
1985
2179
|
cwd: ctx.cwd,
|
|
1986
2180
|
currentSessionId: deps.state.currentSessionId!,
|
|
2181
|
+
parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
|
|
1987
2182
|
currentModelProvider: ctx.model?.provider,
|
|
1988
2183
|
currentModel: ctx.model,
|
|
1989
2184
|
};
|
|
1990
2185
|
const parallelTasks = tasks.map((t, i) => {
|
|
1991
|
-
const taskText =
|
|
2186
|
+
const taskText = shouldForkAgent(contextPolicy, t.agent) ? wrapForkTask(taskTexts[i]!) : taskTexts[i]!;
|
|
1992
2187
|
const progress = taskDisallowsFileUpdates(taskText) ? false : behaviorOverrides[i]?.progress;
|
|
1993
2188
|
return {
|
|
1994
2189
|
agent: t.agent,
|
|
@@ -2016,7 +2211,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2016
2211
|
shareEnabled,
|
|
2017
2212
|
sessionRoot,
|
|
2018
2213
|
chainSkills: [],
|
|
2019
|
-
sessionFilesByFlatIndex: tasks.map((
|
|
2214
|
+
sessionFilesByFlatIndex: tasks.map((task, index) => sessionFileForTask(task.agent, index)),
|
|
2020
2215
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
2021
2216
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
2022
2217
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
@@ -2059,14 +2254,14 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2059
2254
|
}
|
|
2060
2255
|
|
|
2061
2256
|
const parallelProgressPrecreated = firstProgressIndex !== -1;
|
|
2062
|
-
|
|
2257
|
+
const parallelProgressDir = path.join(artifactsDir, "progress", runId);
|
|
2258
|
+
if (parallelProgressPrecreated) writeInitialProgressFile(parallelProgressDir);
|
|
2063
2259
|
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
taskTexts[i] = wrapForkTask(taskTexts[i]!);
|
|
2067
|
-
}
|
|
2260
|
+
for (let i = 0; i < taskTexts.length; i++) {
|
|
2261
|
+
if (shouldForkAgent(contextPolicy, tasks[i]!.agent)) taskTexts[i] = wrapForkTask(taskTexts[i]!);
|
|
2068
2262
|
}
|
|
2069
2263
|
|
|
2264
|
+
const deadlineAt = data.deadlineAt ?? (data.timeoutMs !== undefined ? Date.now() + data.timeoutMs : undefined);
|
|
2070
2265
|
const results = await runForegroundParallelTasks({
|
|
2071
2266
|
tasks,
|
|
2072
2267
|
taskTexts,
|
|
@@ -2077,11 +2272,13 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2077
2272
|
runId,
|
|
2078
2273
|
sessionDirForIndex,
|
|
2079
2274
|
sessionFileForIndex,
|
|
2275
|
+
sessionFileForTask,
|
|
2080
2276
|
shareEnabled,
|
|
2081
2277
|
artifactConfig,
|
|
2082
2278
|
artifactsDir,
|
|
2083
2279
|
maxOutput: params.maxOutput,
|
|
2084
2280
|
paramsCwd: effectiveCwd,
|
|
2281
|
+
progressDir: parallelProgressDir,
|
|
2085
2282
|
availableModels,
|
|
2086
2283
|
modelOverrides,
|
|
2087
2284
|
behaviors,
|
|
@@ -2097,6 +2294,8 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2097
2294
|
liveProgress,
|
|
2098
2295
|
onUpdate,
|
|
2099
2296
|
worktreeSetup,
|
|
2297
|
+
timeoutMs: data.timeoutMs,
|
|
2298
|
+
deadlineAt,
|
|
2100
2299
|
});
|
|
2101
2300
|
for (let i = 0; i < results.length; i++) {
|
|
2102
2301
|
const run = results[i]!;
|
|
@@ -2157,6 +2356,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2157
2356
|
output: result.truncation?.text || getSingleResultOutput(result),
|
|
2158
2357
|
exitCode: result.exitCode,
|
|
2159
2358
|
error: result.error,
|
|
2359
|
+
timedOut: result.timedOut,
|
|
2160
2360
|
})),
|
|
2161
2361
|
(i, agent) => `=== Task ${i + 1}: ${agent} ===`,
|
|
2162
2362
|
);
|
|
@@ -2184,13 +2384,14 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2184
2384
|
signal,
|
|
2185
2385
|
runId,
|
|
2186
2386
|
sessionDirForIndex,
|
|
2187
|
-
|
|
2387
|
+
sessionFileForTask,
|
|
2188
2388
|
shareEnabled,
|
|
2189
2389
|
artifactConfig,
|
|
2190
2390
|
artifactsDir,
|
|
2191
2391
|
onUpdate,
|
|
2192
2392
|
sessionRoot,
|
|
2193
2393
|
controlConfig,
|
|
2394
|
+
contextPolicy,
|
|
2194
2395
|
} = data;
|
|
2195
2396
|
const onControlEvent = createForegroundControlNotifier(data, deps);
|
|
2196
2397
|
const childIntercomTarget = data.intercomBridge.active ? resolveSubagentIntercomTarget(runId, params.agent!, 0) : undefined;
|
|
@@ -2254,6 +2455,9 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2254
2455
|
if (override?.skills !== undefined) skillOverride = override.skills;
|
|
2255
2456
|
|
|
2256
2457
|
if (result.runInBackground) {
|
|
2458
|
+
if (data.timeoutMs !== undefined) {
|
|
2459
|
+
return buildRequestedModeError(params, "timeoutMs/maxRuntimeMs are only supported for foreground runs; background launch from clarify cannot preserve the timeout.");
|
|
2460
|
+
}
|
|
2257
2461
|
if (!isAsyncAvailable()) {
|
|
2258
2462
|
return {
|
|
2259
2463
|
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 +2470,13 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2266
2470
|
pi: deps.pi,
|
|
2267
2471
|
cwd: ctx.cwd,
|
|
2268
2472
|
currentSessionId: deps.state.currentSessionId!,
|
|
2473
|
+
parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
|
|
2269
2474
|
currentModelProvider: ctx.model?.provider,
|
|
2270
2475
|
currentModel: ctx.model,
|
|
2271
2476
|
};
|
|
2272
2477
|
return executeAsyncSingle(id, {
|
|
2273
2478
|
agent: params.agent!,
|
|
2274
|
-
task: params.
|
|
2479
|
+
task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(task) : task,
|
|
2275
2480
|
agentConfig,
|
|
2276
2481
|
ctx: asyncCtx,
|
|
2277
2482
|
availableModels,
|
|
@@ -2281,7 +2486,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2281
2486
|
artifactConfig,
|
|
2282
2487
|
shareEnabled,
|
|
2283
2488
|
sessionRoot,
|
|
2284
|
-
sessionFile:
|
|
2489
|
+
sessionFile: sessionFileForTask(params.agent!, 0),
|
|
2285
2490
|
skills: skillOverride === false ? [] : skillOverride,
|
|
2286
2491
|
output: effectiveOutput,
|
|
2287
2492
|
outputMode: effectiveOutputMode,
|
|
@@ -2296,7 +2501,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2296
2501
|
}
|
|
2297
2502
|
}
|
|
2298
2503
|
|
|
2299
|
-
if (params.
|
|
2504
|
+
if (shouldForkAgent(contextPolicy, params.agent!)) {
|
|
2300
2505
|
task = wrapForkTask(task);
|
|
2301
2506
|
}
|
|
2302
2507
|
const cleanTask = task;
|
|
@@ -2349,7 +2554,9 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2349
2554
|
}
|
|
2350
2555
|
: undefined;
|
|
2351
2556
|
|
|
2557
|
+
const deadlineAt = data.deadlineAt ?? (data.timeoutMs !== undefined ? Date.now() + data.timeoutMs : undefined);
|
|
2352
2558
|
const r = await runSync(ctx.cwd, agents, params.agent!, task, {
|
|
2559
|
+
parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
|
|
2353
2560
|
cwd: effectiveCwd,
|
|
2354
2561
|
signal,
|
|
2355
2562
|
interruptSignal: interruptController.signal,
|
|
@@ -2357,7 +2564,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2357
2564
|
intercomEvents: deps.pi.events,
|
|
2358
2565
|
runId,
|
|
2359
2566
|
sessionDir: sessionDirForIndex(0),
|
|
2360
|
-
sessionFile:
|
|
2567
|
+
sessionFile: sessionFileForTask(params.agent!, 0),
|
|
2361
2568
|
share: shareEnabled,
|
|
2362
2569
|
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
2363
2570
|
artifactConfig,
|
|
@@ -2378,6 +2585,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2378
2585
|
skills: effectiveSkills,
|
|
2379
2586
|
acceptance: params.acceptance,
|
|
2380
2587
|
acceptanceContext: { mode: "single" },
|
|
2588
|
+
timeoutMs: data.timeoutMs,
|
|
2589
|
+
deadlineAt,
|
|
2381
2590
|
});
|
|
2382
2591
|
if (foregroundControl?.currentIndex === 0) {
|
|
2383
2592
|
foregroundControl.interrupt = undefined;
|
|
@@ -2462,6 +2671,23 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2462
2671
|
};
|
|
2463
2672
|
}
|
|
2464
2673
|
|
|
2674
|
+
function inferExecutionMode(params: SubagentParamsLike): SubagentRunMode {
|
|
2675
|
+
if ((params.chain?.length ?? 0) > 0) return "chain";
|
|
2676
|
+
if ((params.tasks?.length ?? 0) > 0) return "parallel";
|
|
2677
|
+
return "single";
|
|
2678
|
+
}
|
|
2679
|
+
|
|
2680
|
+
function duplicateSubagentCallResult(params: SubagentParamsLike): AgentToolResult<Details> {
|
|
2681
|
+
return {
|
|
2682
|
+
content: [{
|
|
2683
|
+
type: "text",
|
|
2684
|
+
text: "Rejected: a subagent call is already in progress. Issue exactly ONE subagent call per turn.",
|
|
2685
|
+
}],
|
|
2686
|
+
isError: true,
|
|
2687
|
+
details: { mode: inferExecutionMode(params), results: [] },
|
|
2688
|
+
};
|
|
2689
|
+
}
|
|
2690
|
+
|
|
2465
2691
|
export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
2466
2692
|
execute: (
|
|
2467
2693
|
id: string,
|
|
@@ -2498,7 +2724,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
2498
2724
|
let orchestratorTarget: string | undefined;
|
|
2499
2725
|
try {
|
|
2500
2726
|
orchestratorTarget = resolveIntercomSessionTarget(deps.pi.getSessionName(), ctx.sessionManager.getSessionId());
|
|
2501
|
-
} catch {
|
|
2727
|
+
} catch (error) {
|
|
2728
|
+
if (!sessionError) sessionError = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
2729
|
+
}
|
|
2502
2730
|
return {
|
|
2503
2731
|
content: [{
|
|
2504
2732
|
type: "text",
|
|
@@ -2573,7 +2801,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
2573
2801
|
details: { mode: "management", results: [] },
|
|
2574
2802
|
};
|
|
2575
2803
|
}
|
|
2576
|
-
const asyncInterruptResult = interruptAsyncRun(
|
|
2804
|
+
const asyncInterruptResult = interruptAsyncRun(
|
|
2805
|
+
deps.state,
|
|
2806
|
+
resolved?.kind === "async" ? resolved.id : targetRunId,
|
|
2807
|
+
deps.kill,
|
|
2808
|
+
resolved?.kind === "async" ? resolved.location : undefined,
|
|
2809
|
+
);
|
|
2577
2810
|
if (asyncInterruptResult) return asyncInterruptResult;
|
|
2578
2811
|
return {
|
|
2579
2812
|
content: [{ type: "text", text: "No interrupt-capable run found in this session." }],
|
|
@@ -2624,13 +2857,16 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
2624
2857
|
depth,
|
|
2625
2858
|
deps.config.forceTopLevelAsync === true,
|
|
2626
2859
|
);
|
|
2860
|
+
const foregroundTimeout = resolveForegroundTimeout(effectiveParams);
|
|
2861
|
+
if (foregroundTimeout.error) return buildRequestedModeError(effectiveParams, foregroundTimeout.error);
|
|
2627
2862
|
|
|
2628
2863
|
const scope: AgentScope = resolveExecutionAgentScope(effectiveParams.agentScope);
|
|
2629
2864
|
const effectiveCwd = effectiveParams.cwd ?? ctx.cwd;
|
|
2630
2865
|
const parentSessionFile = ctx.sessionManager.getSessionFile() ?? null;
|
|
2631
2866
|
deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
2632
2867
|
const discoveredAgents = deps.discoverAgents(effectiveCwd, scope).agents;
|
|
2633
|
-
|
|
2868
|
+
const contextPolicy = resolveAgentDefaultContextPolicy(effectiveParams, discoveredAgents);
|
|
2869
|
+
effectiveParams = contextPolicy.params;
|
|
2634
2870
|
const sessionName = resolveIntercomSessionTarget(deps.pi.getSessionName(), ctx.sessionManager.getSessionId());
|
|
2635
2871
|
const intercomBridge = resolveIntercomBridge({
|
|
2636
2872
|
config: deps.config.intercomBridge,
|
|
@@ -2664,15 +2900,18 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
2664
2900
|
);
|
|
2665
2901
|
if (validationError) return validationError;
|
|
2666
2902
|
|
|
2667
|
-
let
|
|
2903
|
+
let forkSessionFileForIndex: (idx?: number) => string | undefined = () => undefined;
|
|
2668
2904
|
try {
|
|
2669
|
-
|
|
2905
|
+
forkSessionFileForIndex = createForkContextResolver(ctx.sessionManager, contextPolicy.usesFork ? "fork" : undefined).sessionFileForIndex;
|
|
2670
2906
|
} catch (error) {
|
|
2671
2907
|
return toExecutionErrorResult(effectiveParams, error);
|
|
2672
2908
|
}
|
|
2673
2909
|
const requestedAsync = effectiveParams.async ?? deps.asyncByDefault;
|
|
2674
2910
|
const backgroundRequestedWhileClarifying = (hasChain || hasTasks) && requestedAsync && effectiveParams.clarify === true;
|
|
2675
2911
|
const effectiveAsync = requestedAsync && effectiveParams.clarify !== true;
|
|
2912
|
+
if (foregroundTimeout.timeoutMs !== undefined && effectiveAsync) {
|
|
2913
|
+
return buildRequestedModeError(effectiveParams, "timeoutMs/maxRuntimeMs are only supported for foreground runs; set async: false or omit the timeout for background runs.");
|
|
2914
|
+
}
|
|
2676
2915
|
const controlConfig = resolveControlConfig(deps.config.control, effectiveParams.control);
|
|
2677
2916
|
|
|
2678
2917
|
const artifactConfig: ArtifactConfig = {
|
|
@@ -2701,8 +2940,19 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
2701
2940
|
}
|
|
2702
2941
|
const sessionDirForIndex = (idx?: number) =>
|
|
2703
2942
|
path.join(sessionRoot, `run-${idx ?? 0}`);
|
|
2943
|
+
const forkSessionFileForTask = (agentName: string, idx?: number) =>
|
|
2944
|
+
shouldForkAgent(contextPolicy, agentName) ? forkSessionFileForIndex(idx) : undefined;
|
|
2945
|
+
const childSessionFileForTask = (agentName: string, idx?: number) =>
|
|
2946
|
+
forkSessionFileForTask(agentName, idx) ?? path.join(sessionDirForIndex(idx), "session.jsonl");
|
|
2704
2947
|
const childSessionFileForIndex = (idx?: number) =>
|
|
2705
|
-
|
|
2948
|
+
path.join(sessionDirForIndex(idx), "session.jsonl");
|
|
2949
|
+
try {
|
|
2950
|
+
preflightForkSessionsForStaticTasks(effectiveParams, contextPolicy, forkSessionFileForTask);
|
|
2951
|
+
} catch (error) {
|
|
2952
|
+
return toExecutionErrorResult(effectiveParams, error);
|
|
2953
|
+
}
|
|
2954
|
+
const chainBindingsError = validateExecutionChainBindings(effectiveParams, deps.config.chain?.dynamicFanout?.maxItems);
|
|
2955
|
+
if (chainBindingsError) return chainBindingsError;
|
|
2706
2956
|
|
|
2707
2957
|
const onUpdateWithContext = onUpdate
|
|
2708
2958
|
? (r: AgentToolResult<Details>) => onUpdate(withForkContext(r, effectiveParams.context))
|
|
@@ -2720,6 +2970,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
2720
2970
|
sessionRoot,
|
|
2721
2971
|
sessionDirForIndex,
|
|
2722
2972
|
sessionFileForIndex: childSessionFileForIndex,
|
|
2973
|
+
sessionFileForTask: childSessionFileForTask,
|
|
2723
2974
|
artifactConfig,
|
|
2724
2975
|
artifactsDir,
|
|
2725
2976
|
backgroundRequestedWhileClarifying,
|
|
@@ -2727,6 +2978,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
2727
2978
|
controlConfig,
|
|
2728
2979
|
intercomBridge,
|
|
2729
2980
|
nestedRoute,
|
|
2981
|
+
timeoutMs: foregroundTimeout.timeoutMs,
|
|
2982
|
+
contextPolicy,
|
|
2730
2983
|
};
|
|
2731
2984
|
|
|
2732
2985
|
const foregroundMode: "single" | "parallel" | "chain" = hasChain ? "chain" : hasTasks ? "parallel" : "single";
|
|
@@ -2851,5 +3104,22 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
2851
3104
|
}, effectiveParams.context);
|
|
2852
3105
|
};
|
|
2853
3106
|
|
|
2854
|
-
|
|
3107
|
+
const executeWithSingleDispatchGuard = async (
|
|
3108
|
+
id: string,
|
|
3109
|
+
params: SubagentParamsLike,
|
|
3110
|
+
signal: AbortSignal,
|
|
3111
|
+
onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
|
|
3112
|
+
ctx: ExtensionContext,
|
|
3113
|
+
): Promise<AgentToolResult<Details>> => {
|
|
3114
|
+
if (params.action) return execute(id, params, signal, onUpdate, ctx);
|
|
3115
|
+
if (deps.state.subagentInProgress === true) return duplicateSubagentCallResult(params);
|
|
3116
|
+
deps.state.subagentInProgress = true;
|
|
3117
|
+
try {
|
|
3118
|
+
return await execute(id, params, signal, onUpdate, ctx);
|
|
3119
|
+
} finally {
|
|
3120
|
+
deps.state.subagentInProgress = false;
|
|
3121
|
+
}
|
|
3122
|
+
};
|
|
3123
|
+
|
|
3124
|
+
return { execute: executeWithSingleDispatchGuard };
|
|
2855
3125
|
}
|