pi-subagents 0.25.0 → 0.28.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 +34 -0
- package/README.md +175 -19
- package/package.json +1 -1
- package/prompts/parallel-context-build.md +3 -1
- package/prompts/parallel-handoff-plan.md +3 -1
- package/skills/pi-subagents/SKILL.md +60 -17
- package/src/agents/agent-management.ts +71 -15
- package/src/agents/agent-serializer.ts +13 -2
- package/src/agents/agents.ts +88 -17
- package/src/agents/chain-serializer.ts +120 -0
- package/src/extension/fanout-child.ts +2 -0
- package/src/extension/index.ts +5 -2
- package/src/extension/schemas.ts +132 -6
- package/src/intercom/result-intercom.ts +5 -0
- package/src/runs/background/async-execution.ts +88 -6
- package/src/runs/background/async-status.ts +11 -1
- package/src/runs/background/run-status.ts +10 -1
- package/src/runs/background/subagent-runner.ts +665 -39
- package/src/runs/foreground/chain-execution.ts +369 -118
- package/src/runs/foreground/execution.ts +392 -19
- package/src/runs/foreground/subagent-executor.ts +126 -3
- package/src/runs/shared/acceptance-contract.ts +318 -0
- package/src/runs/shared/acceptance-evaluation.ts +221 -0
- package/src/runs/shared/acceptance-finalization.ts +173 -0
- package/src/runs/shared/acceptance-reports.ts +127 -0
- package/src/runs/shared/acceptance.ts +22 -0
- package/src/runs/shared/chain-outputs.ts +101 -0
- package/src/runs/shared/completion-guard.ts +26 -3
- package/src/runs/shared/dynamic-fanout.ts +293 -0
- package/src/runs/shared/parallel-utils.ts +33 -1
- package/src/runs/shared/pi-args.ts +11 -0
- package/src/runs/shared/structured-output.ts +77 -0
- package/src/runs/shared/subagent-prompt-runtime.ts +53 -3
- package/src/runs/shared/workflow-graph.ts +210 -0
- package/src/shared/formatters.ts +2 -2
- package/src/shared/settings.ts +53 -4
- package/src/shared/types.ts +265 -1
- package/src/shared/utils.ts +7 -0
- package/src/slash/slash-commands.ts +41 -3
- package/src/tui/render.ts +178 -45
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
writeInitialProgressFile,
|
|
22
22
|
getStepAgents,
|
|
23
23
|
isParallelStep,
|
|
24
|
+
isDynamicParallelStep,
|
|
24
25
|
resolveStepBehavior,
|
|
25
26
|
suppressProgressForReadOnlyTask,
|
|
26
27
|
taskDisallowsFileUpdates,
|
|
@@ -52,6 +53,7 @@ import { resolveSubagentRunId, type ResolvedSubagentRunId } from "../background/
|
|
|
52
53
|
import { formatNestedRunStatusLines } from "../shared/nested-render.ts";
|
|
53
54
|
import { inspectSubagentStatus } from "../background/run-status.ts";
|
|
54
55
|
import { applyForceTopLevelAsyncOverride } from "../background/top-level-async.ts";
|
|
56
|
+
import { validateAcceptanceInput } from "../shared/acceptance.ts";
|
|
55
57
|
import {
|
|
56
58
|
cleanupWorktrees,
|
|
57
59
|
createWorktrees,
|
|
@@ -63,6 +65,7 @@ import {
|
|
|
63
65
|
} from "../shared/worktree.ts";
|
|
64
66
|
import {
|
|
65
67
|
type AgentProgress,
|
|
68
|
+
type AcceptanceInput,
|
|
66
69
|
type ArtifactConfig,
|
|
67
70
|
type ArtifactPaths,
|
|
68
71
|
type ControlConfig,
|
|
@@ -103,6 +106,7 @@ interface TaskParam {
|
|
|
103
106
|
progress?: boolean;
|
|
104
107
|
model?: string;
|
|
105
108
|
skill?: string | string[] | boolean;
|
|
109
|
+
acceptance?: AcceptanceInput;
|
|
106
110
|
}
|
|
107
111
|
|
|
108
112
|
export interface SubagentParamsLike {
|
|
@@ -117,6 +121,8 @@ export interface SubagentParamsLike {
|
|
|
117
121
|
chain?: ChainStep[];
|
|
118
122
|
tasks?: TaskParam[];
|
|
119
123
|
concurrency?: number;
|
|
124
|
+
timeoutMs?: number;
|
|
125
|
+
maxRuntimeMs?: number;
|
|
120
126
|
worktree?: boolean;
|
|
121
127
|
context?: "fresh" | "fork";
|
|
122
128
|
async?: boolean;
|
|
@@ -134,6 +140,7 @@ export interface SubagentParamsLike {
|
|
|
134
140
|
outputMode?: "inline" | "file-only";
|
|
135
141
|
agentScope?: unknown;
|
|
136
142
|
chainDir?: string;
|
|
143
|
+
acceptance?: AcceptanceInput;
|
|
137
144
|
}
|
|
138
145
|
|
|
139
146
|
interface ExecutorDeps {
|
|
@@ -164,6 +171,7 @@ interface ExecutionContextData {
|
|
|
164
171
|
artifactsDir: string;
|
|
165
172
|
backgroundRequestedWhileClarifying: boolean;
|
|
166
173
|
effectiveAsync: boolean;
|
|
174
|
+
foregroundTimeoutMs?: number;
|
|
167
175
|
controlConfig: ResolvedControlConfig;
|
|
168
176
|
intercomBridge: IntercomBridgeState;
|
|
169
177
|
nestedRoute?: NestedRouteInfo;
|
|
@@ -245,7 +253,7 @@ function rememberForegroundRun(state: SubagentState, input: { runId: string; mod
|
|
|
245
253
|
children: input.results.map((result, index) => ({
|
|
246
254
|
agent: result.agent,
|
|
247
255
|
index,
|
|
248
|
-
status: resolveSubagentResultStatus({ exitCode: result.exitCode, interrupted: result.interrupted, detached: result.detached }),
|
|
256
|
+
status: resolveSubagentResultStatus({ exitCode: result.exitCode, interrupted: result.interrupted, detached: result.detached, timedOut: result.timedOut }),
|
|
249
257
|
...(result.sessionFile ? { sessionFile: result.sessionFile } : {}),
|
|
250
258
|
})),
|
|
251
259
|
});
|
|
@@ -711,6 +719,7 @@ async function emitForegroundResultIntercom(input: {
|
|
|
711
719
|
exitCode: result.exitCode,
|
|
712
720
|
interrupted: result.interrupted,
|
|
713
721
|
detached: result.detached,
|
|
722
|
+
timedOut: result.timedOut,
|
|
714
723
|
}),
|
|
715
724
|
summary: resultSummaryForIntercom(result),
|
|
716
725
|
index,
|
|
@@ -756,6 +765,51 @@ async function maybeBuildForegroundIntercomReceipt(input: {
|
|
|
756
765
|
};
|
|
757
766
|
}
|
|
758
767
|
|
|
768
|
+
function validationErrorResult(mode: Details["mode"], text: string): AgentToolResult<Details> {
|
|
769
|
+
return { content: [{ type: "text", text }], isError: true, details: { mode, results: [] } };
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
function resolveForegroundTimeoutMs(params: SubagentParamsLike): { timeoutMs?: number; error?: string } {
|
|
773
|
+
const rawTimeout = (params as { timeoutMs?: unknown }).timeoutMs;
|
|
774
|
+
const rawMaxRuntime = (params as { maxRuntimeMs?: unknown }).maxRuntimeMs;
|
|
775
|
+
for (const [name, value] of [["timeoutMs", rawTimeout], ["maxRuntimeMs", rawMaxRuntime]] as const) {
|
|
776
|
+
if (value !== undefined && (typeof value !== "number" || !Number.isInteger(value) || value < 1)) {
|
|
777
|
+
return { error: `${name} must be a positive integer.` };
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
if (rawTimeout !== undefined && rawMaxRuntime !== undefined && rawTimeout !== rawMaxRuntime) {
|
|
781
|
+
return { error: "timeoutMs and maxRuntimeMs are aliases; provide only one or use identical values." };
|
|
782
|
+
}
|
|
783
|
+
const timeoutMs = rawTimeout ?? rawMaxRuntime;
|
|
784
|
+
return timeoutMs === undefined ? {} : { timeoutMs };
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
function validateAcceptanceForExecution(params: SubagentParamsLike): AgentToolResult<Details> | null {
|
|
788
|
+
const topLevelErrors = validateAcceptanceInput(params.acceptance);
|
|
789
|
+
if (topLevelErrors.length > 0) return validationErrorResult("single", topLevelErrors.join(" "));
|
|
790
|
+
for (const [index, task] of (params.tasks ?? []).entries()) {
|
|
791
|
+
const errors = validateAcceptanceInput(task.acceptance, `tasks[${index}].acceptance`);
|
|
792
|
+
if (errors.length > 0) return validationErrorResult("parallel", errors.join(" "));
|
|
793
|
+
}
|
|
794
|
+
for (const [stepIndex, step] of (params.chain ?? []).entries()) {
|
|
795
|
+
if (isParallelStep(step)) {
|
|
796
|
+
if (Object.hasOwn(step, "acceptance")) return validationErrorResult("chain", `chain[${stepIndex}].acceptance is not supported on static parallel groups; set acceptance on each parallel task.`);
|
|
797
|
+
for (const [taskIndex, task] of step.parallel.entries()) {
|
|
798
|
+
const errors = validateAcceptanceInput(task.acceptance, `chain[${stepIndex}].parallel[${taskIndex}].acceptance`);
|
|
799
|
+
if (errors.length > 0) return validationErrorResult("chain", errors.join(" "));
|
|
800
|
+
}
|
|
801
|
+
} else if (isDynamicParallelStep(step)) {
|
|
802
|
+
if (Object.hasOwn(step, "acceptance")) return validationErrorResult("chain", `chain[${stepIndex}].acceptance is not supported on dynamic fanout groups; set acceptance on chain[${stepIndex}].parallel.acceptance for each materialized child.`);
|
|
803
|
+
const errors = validateAcceptanceInput(step.parallel.acceptance, `chain[${stepIndex}].parallel.acceptance`);
|
|
804
|
+
if (errors.length > 0) return validationErrorResult("chain", errors.join(" "));
|
|
805
|
+
} else {
|
|
806
|
+
const stepErrors = validateAcceptanceInput(step.acceptance, `chain[${stepIndex}].acceptance`);
|
|
807
|
+
if (stepErrors.length > 0) return validationErrorResult("chain", stepErrors.join(" "));
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
return null;
|
|
811
|
+
}
|
|
812
|
+
|
|
759
813
|
function validateExecutionInput(
|
|
760
814
|
params: SubagentParamsLike,
|
|
761
815
|
agents: AgentConfig[],
|
|
@@ -764,6 +818,9 @@ function validateExecutionInput(
|
|
|
764
818
|
hasSingle: boolean,
|
|
765
819
|
allowClarifyTaskPrompt: boolean,
|
|
766
820
|
): AgentToolResult<Details> | null {
|
|
821
|
+
const acceptanceError = validateAcceptanceForExecution(params);
|
|
822
|
+
if (acceptanceError) return acceptanceError;
|
|
823
|
+
|
|
767
824
|
if (Number(hasChain) + Number(hasTasks) + Number(hasSingle) !== 1) {
|
|
768
825
|
return {
|
|
769
826
|
content: [
|
|
@@ -777,6 +834,9 @@ function validateExecutionInput(
|
|
|
777
834
|
};
|
|
778
835
|
}
|
|
779
836
|
|
|
837
|
+
const timeoutResolution = resolveForegroundTimeoutMs(params);
|
|
838
|
+
if (timeoutResolution.error) return validationErrorResult(getRequestedModeLabel(params), timeoutResolution.error);
|
|
839
|
+
|
|
780
840
|
if (hasSingle && params.agent && !agents.find((agent) => agent.name === params.agent)) {
|
|
781
841
|
return {
|
|
782
842
|
content: [{ type: "text", text: `Unknown agent: ${params.agent}` }],
|
|
@@ -816,6 +876,12 @@ function validateExecutionInput(
|
|
|
816
876
|
details: { mode: "chain" as const, results: [] },
|
|
817
877
|
};
|
|
818
878
|
}
|
|
879
|
+
} else if (isDynamicParallelStep(firstStep)) {
|
|
880
|
+
return {
|
|
881
|
+
content: [{ type: "text", text: "First step in chain cannot be dynamic fanout; expand.from requires a prior structured named output" }],
|
|
882
|
+
isError: true,
|
|
883
|
+
details: { mode: "chain" as const, results: [] },
|
|
884
|
+
};
|
|
819
885
|
} else if (!(firstStep as SequentialStep).task && !params.task && !allowClarifyTaskPrompt) {
|
|
820
886
|
return {
|
|
821
887
|
content: [{ type: "text", text: "First step in chain must have a task" }],
|
|
@@ -977,6 +1043,10 @@ function collectChainSessionFiles(
|
|
|
977
1043
|
}
|
|
978
1044
|
continue;
|
|
979
1045
|
}
|
|
1046
|
+
if (isDynamicParallelStep(step)) {
|
|
1047
|
+
sessionFiles.push(undefined);
|
|
1048
|
+
continue;
|
|
1049
|
+
}
|
|
980
1050
|
sessionFiles.push(sessionFileForIndex(flatIndex));
|
|
981
1051
|
flatIndex++;
|
|
982
1052
|
}
|
|
@@ -995,6 +1065,15 @@ function wrapChainTasksForFork(chain: ChainStep[], context: SubagentParamsLike["
|
|
|
995
1065
|
})),
|
|
996
1066
|
};
|
|
997
1067
|
}
|
|
1068
|
+
if (isDynamicParallelStep(step)) {
|
|
1069
|
+
return {
|
|
1070
|
+
...step,
|
|
1071
|
+
parallel: {
|
|
1072
|
+
...step.parallel,
|
|
1073
|
+
task: wrapForkTask(step.parallel.task ?? "{previous}"),
|
|
1074
|
+
},
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
998
1077
|
const sequential = step as SequentialStep;
|
|
999
1078
|
return {
|
|
1000
1079
|
...sequential,
|
|
@@ -1082,6 +1161,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1082
1161
|
...(task.outputMode !== undefined ? { outputMode: task.outputMode } : {}),
|
|
1083
1162
|
...(task.reads !== undefined && task.reads !== true ? { reads: task.reads } : {}),
|
|
1084
1163
|
...(task.progress !== undefined ? { progress: task.progress } : {}),
|
|
1164
|
+
...(task.acceptance !== undefined ? { acceptance: task.acceptance } : {}),
|
|
1085
1165
|
}));
|
|
1086
1166
|
return executeAsyncChain(id, {
|
|
1087
1167
|
chain: [{
|
|
@@ -1129,6 +1209,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1129
1209
|
sessionRoot,
|
|
1130
1210
|
chainSkills,
|
|
1131
1211
|
sessionFilesByFlatIndex: collectChainSessionFiles(chain, sessionFileForIndex),
|
|
1212
|
+
dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
|
|
1132
1213
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
1133
1214
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1134
1215
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
@@ -1179,6 +1260,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1179
1260
|
controlIntercomTarget,
|
|
1180
1261
|
childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(agent, index) : undefined,
|
|
1181
1262
|
nestedRoute,
|
|
1263
|
+
acceptance: params.acceptance,
|
|
1182
1264
|
});
|
|
1183
1265
|
}
|
|
1184
1266
|
|
|
@@ -1228,12 +1310,14 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
1228
1310
|
onUpdate,
|
|
1229
1311
|
onControlEvent,
|
|
1230
1312
|
controlConfig,
|
|
1313
|
+
...(data.foregroundTimeoutMs !== undefined ? { timeoutMs: data.foregroundTimeoutMs } : {}),
|
|
1231
1314
|
childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(runId, agent, index) : undefined,
|
|
1232
1315
|
orchestratorIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
1233
1316
|
foregroundControl,
|
|
1234
1317
|
nestedRoute: foregroundControl?.nestedRoute,
|
|
1235
1318
|
chainSkills,
|
|
1236
1319
|
chainDir: params.chainDir,
|
|
1320
|
+
dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
|
|
1237
1321
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
1238
1322
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1239
1323
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
@@ -1269,6 +1353,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
1269
1353
|
sessionRoot,
|
|
1270
1354
|
chainSkills: chainResult.requestedAsync.chainSkills,
|
|
1271
1355
|
sessionFilesByFlatIndex: collectChainSessionFiles(asyncChain, sessionFileForIndex),
|
|
1356
|
+
dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
|
|
1272
1357
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
1273
1358
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1274
1359
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
@@ -1282,7 +1367,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
1282
1367
|
const chainDetails = chainResult.details ? compactForegroundDetails({ ...chainResult.details, runId }) : undefined;
|
|
1283
1368
|
if (foregroundControl) updateForegroundNestedProjection(foregroundControl);
|
|
1284
1369
|
if (chainDetails) rememberForegroundRun(deps.state, { runId, mode: "chain", cwd: effectiveCwd, results: chainDetails.results });
|
|
1285
|
-
const intercomReceipt = chainDetails && !chainDetails.results.some((result) => result.interrupted || result.detached)
|
|
1370
|
+
const intercomReceipt = chainDetails && !chainDetails.results.some((result) => result.interrupted || result.detached || result.timedOut)
|
|
1286
1371
|
? await maybeBuildForegroundIntercomReceipt({
|
|
1287
1372
|
pi: deps.pi,
|
|
1288
1373
|
intercomBridge: data.intercomBridge,
|
|
@@ -1317,6 +1402,8 @@ interface ForegroundParallelRunInput {
|
|
|
1317
1402
|
artifactConfig: ArtifactConfig;
|
|
1318
1403
|
artifactsDir: string;
|
|
1319
1404
|
maxOutput?: MaxOutputConfig;
|
|
1405
|
+
timeoutMs?: number;
|
|
1406
|
+
timeoutAt?: number;
|
|
1320
1407
|
paramsCwd: string;
|
|
1321
1408
|
maxSubagentDepths: number[];
|
|
1322
1409
|
availableModels: ModelInfo[];
|
|
@@ -1469,6 +1556,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
1469
1556
|
cwd: taskCwd,
|
|
1470
1557
|
signal: input.signal,
|
|
1471
1558
|
interruptSignal: interruptController.signal,
|
|
1559
|
+
...(input.timeoutMs !== undefined && input.timeoutAt !== undefined ? { timeoutMs: input.timeoutMs, timeoutAt: input.timeoutAt } : {}),
|
|
1472
1560
|
allowIntercomDetach: agentConfig?.systemPrompt?.includes(INTERCOM_BRIDGE_MARKER) === true,
|
|
1473
1561
|
intercomEvents: input.intercomEvents,
|
|
1474
1562
|
runId: input.runId,
|
|
@@ -1482,6 +1570,8 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
1482
1570
|
outputPath,
|
|
1483
1571
|
outputMode: behavior?.outputMode,
|
|
1484
1572
|
maxSubagentDepth: input.maxSubagentDepths[index],
|
|
1573
|
+
maxExecutionTimeMs: agentConfig?.maxExecutionTimeMs,
|
|
1574
|
+
maxTokens: agentConfig?.maxTokens,
|
|
1485
1575
|
controlConfig: input.controlConfig,
|
|
1486
1576
|
onControlEvent: input.onControlEvent,
|
|
1487
1577
|
intercomSessionName: input.childIntercomTarget?.(task.agent, index),
|
|
@@ -1491,6 +1581,8 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
1491
1581
|
availableModels: input.availableModels,
|
|
1492
1582
|
preferredModelProvider: input.ctx.model?.provider,
|
|
1493
1583
|
skills: effectiveSkills === false ? [] : effectiveSkills,
|
|
1584
|
+
acceptance: task.acceptance,
|
|
1585
|
+
acceptanceContext: { mode: "parallel" },
|
|
1494
1586
|
onUpdate: input.onUpdate
|
|
1495
1587
|
? (progressUpdate) => {
|
|
1496
1588
|
const stepResults = progressUpdate.details?.results || [];
|
|
@@ -1680,6 +1772,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
1680
1772
|
...(behaviorOverrides[i]?.outputMode !== undefined ? { outputMode: behaviorOverrides[i]!.outputMode } : {}),
|
|
1681
1773
|
...(behaviorOverrides[i]?.reads !== undefined ? { reads: behaviorOverrides[i]!.reads } : {}),
|
|
1682
1774
|
...(progress !== undefined ? { progress } : {}),
|
|
1775
|
+
...(t.acceptance !== undefined ? { acceptance: t.acceptance } : {}),
|
|
1683
1776
|
};
|
|
1684
1777
|
});
|
|
1685
1778
|
return executeAsyncChain(id, {
|
|
@@ -1746,6 +1839,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
1746
1839
|
}
|
|
1747
1840
|
}
|
|
1748
1841
|
|
|
1842
|
+
const timeoutAt = data.foregroundTimeoutMs !== undefined ? Date.now() + data.foregroundTimeoutMs : undefined;
|
|
1749
1843
|
const results = await runForegroundParallelTasks({
|
|
1750
1844
|
tasks,
|
|
1751
1845
|
taskTexts,
|
|
@@ -1760,6 +1854,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
1760
1854
|
artifactConfig,
|
|
1761
1855
|
artifactsDir,
|
|
1762
1856
|
maxOutput: params.maxOutput,
|
|
1857
|
+
...(data.foregroundTimeoutMs !== undefined && timeoutAt !== undefined ? { timeoutMs: data.foregroundTimeoutMs, timeoutAt } : {}),
|
|
1763
1858
|
paramsCwd: effectiveCwd,
|
|
1764
1859
|
availableModels,
|
|
1765
1860
|
modelOverrides,
|
|
@@ -1787,6 +1882,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
1787
1882
|
if (result.artifactPaths) allArtifactPaths.push(result.artifactPaths);
|
|
1788
1883
|
}
|
|
1789
1884
|
|
|
1885
|
+
const timedOut = results.find((result) => result.timedOut);
|
|
1790
1886
|
const interrupted = results.find((result) => result.interrupted);
|
|
1791
1887
|
const details = compactForegroundDetails({
|
|
1792
1888
|
mode: "parallel",
|
|
@@ -1796,6 +1892,13 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
1796
1892
|
artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
|
|
1797
1893
|
});
|
|
1798
1894
|
rememberForegroundRun(deps.state, { runId, mode: "parallel", cwd: effectiveCwd, results: details.results });
|
|
1895
|
+
if (timedOut) {
|
|
1896
|
+
return {
|
|
1897
|
+
content: [{ type: "text", text: `Parallel run timed out (${timedOut.agent}): ${timedOut.error ?? "timeout expired"}` }],
|
|
1898
|
+
details,
|
|
1899
|
+
isError: true,
|
|
1900
|
+
};
|
|
1901
|
+
}
|
|
1799
1902
|
if (interrupted) {
|
|
1800
1903
|
return {
|
|
1801
1904
|
content: [{ type: "text", text: `Parallel run paused after interrupt (${interrupted.agent}). Waiting for explicit next action.` }],
|
|
@@ -2026,10 +2129,12 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2026
2129
|
}
|
|
2027
2130
|
: undefined;
|
|
2028
2131
|
|
|
2132
|
+
const timeoutAt = data.foregroundTimeoutMs !== undefined ? Date.now() + data.foregroundTimeoutMs : undefined;
|
|
2029
2133
|
const r = await runSync(ctx.cwd, agents, params.agent!, task, {
|
|
2030
2134
|
cwd: effectiveCwd,
|
|
2031
2135
|
signal,
|
|
2032
2136
|
interruptSignal: interruptController.signal,
|
|
2137
|
+
...(data.foregroundTimeoutMs !== undefined && timeoutAt !== undefined ? { timeoutMs: data.foregroundTimeoutMs, timeoutAt } : {}),
|
|
2033
2138
|
allowIntercomDetach: agentConfig.systemPrompt?.includes(INTERCOM_BRIDGE_MARKER) === true,
|
|
2034
2139
|
intercomEvents: deps.pi.events,
|
|
2035
2140
|
runId,
|
|
@@ -2042,6 +2147,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2042
2147
|
outputPath,
|
|
2043
2148
|
outputMode: effectiveOutputMode,
|
|
2044
2149
|
maxSubagentDepth,
|
|
2150
|
+
maxExecutionTimeMs: agentConfig.maxExecutionTimeMs,
|
|
2151
|
+
maxTokens: agentConfig.maxTokens,
|
|
2045
2152
|
onUpdate: forwardSingleUpdate,
|
|
2046
2153
|
controlConfig,
|
|
2047
2154
|
onControlEvent,
|
|
@@ -2053,6 +2160,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2053
2160
|
availableModels,
|
|
2054
2161
|
preferredModelProvider: currentProvider,
|
|
2055
2162
|
skills: effectiveSkills,
|
|
2163
|
+
acceptance: params.acceptance,
|
|
2164
|
+
acceptanceContext: { mode: "single" },
|
|
2056
2165
|
});
|
|
2057
2166
|
if (foregroundControl?.currentIndex === 0) {
|
|
2058
2167
|
foregroundControl.interrupt = undefined;
|
|
@@ -2092,7 +2201,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2092
2201
|
});
|
|
2093
2202
|
rememberForegroundRun(deps.state, { runId, mode: "single", cwd: effectiveCwd, results: details.results });
|
|
2094
2203
|
|
|
2095
|
-
if (!r.detached && !r.interrupted) {
|
|
2204
|
+
if (!r.detached && !r.interrupted && !r.timedOut) {
|
|
2096
2205
|
if (foregroundControl) updateForegroundNestedProjection(foregroundControl);
|
|
2097
2206
|
const intercomReceipt = await maybeBuildForegroundIntercomReceipt({
|
|
2098
2207
|
pi: deps.pi,
|
|
@@ -2118,6 +2227,14 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2118
2227
|
};
|
|
2119
2228
|
}
|
|
2120
2229
|
|
|
2230
|
+
if (r.timedOut) {
|
|
2231
|
+
return {
|
|
2232
|
+
content: [{ type: "text", text: `Run timed out (${params.agent}): ${r.error ?? "timeout expired"}` }],
|
|
2233
|
+
details,
|
|
2234
|
+
isError: true,
|
|
2235
|
+
};
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2121
2238
|
if (r.interrupted) {
|
|
2122
2239
|
return {
|
|
2123
2240
|
content: [{ type: "text", text: `Run paused after interrupt (${params.agent}). Waiting for explicit next action.` }],
|
|
@@ -2345,6 +2462,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
2345
2462
|
const requestedAsync = effectiveParams.async ?? deps.asyncByDefault;
|
|
2346
2463
|
const backgroundRequestedWhileClarifying = (hasChain || hasTasks) && requestedAsync && effectiveParams.clarify === true;
|
|
2347
2464
|
const effectiveAsync = requestedAsync && effectiveParams.clarify !== true;
|
|
2465
|
+
const foregroundTimeout = resolveForegroundTimeoutMs(effectiveParams);
|
|
2466
|
+
if (foregroundTimeout.error) return buildRequestedModeError(effectiveParams, foregroundTimeout.error);
|
|
2467
|
+
if (effectiveAsync && foregroundTimeout.timeoutMs !== undefined) {
|
|
2468
|
+
return buildRequestedModeError(effectiveParams, "timeoutMs/maxRuntimeMs only applies to foreground subagent runs. Omit async:true or use action:'interrupt' for background runs.");
|
|
2469
|
+
}
|
|
2348
2470
|
const controlConfig = resolveControlConfig(deps.config.control, effectiveParams.control);
|
|
2349
2471
|
|
|
2350
2472
|
const artifactConfig: ArtifactConfig = {
|
|
@@ -2396,6 +2518,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
2396
2518
|
artifactsDir,
|
|
2397
2519
|
backgroundRequestedWhileClarifying,
|
|
2398
2520
|
effectiveAsync,
|
|
2521
|
+
...(foregroundTimeout.timeoutMs !== undefined ? { foregroundTimeoutMs: foregroundTimeout.timeoutMs } : {}),
|
|
2399
2522
|
controlConfig,
|
|
2400
2523
|
intercomBridge,
|
|
2401
2524
|
nestedRoute,
|