pi-subagents 0.29.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 +43 -0
- package/README.md +125 -19
- 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 +30 -0
- package/src/agents/agent-management.ts +189 -8
- package/src/agents/agent-serializer.ts +35 -12
- package/src/agents/agents.ts +243 -24
- package/src/agents/frontmatter.ts +66 -2
- package/src/agents/proactive-skills.ts +191 -0
- package/src/agents/skills.ts +117 -20
- package/src/extension/doctor.ts +20 -0
- package/src/extension/fanout-child.ts +2 -1
- package/src/extension/index.ts +50 -5
- package/src/extension/schemas.ts +40 -79
- package/src/intercom/intercom-bridge.ts +2 -3
- package/src/runs/background/async-execution.ts +180 -67
- package/src/runs/background/async-job-tracker.ts +56 -11
- package/src/runs/background/async-resume.ts +53 -5
- package/src/runs/background/async-status.ts +4 -1
- package/src/runs/background/chain-append.ts +282 -0
- package/src/runs/background/chain-root-attachment.ts +161 -0
- package/src/runs/background/result-watcher.ts +11 -2
- package/src/runs/background/run-status.ts +1 -0
- package/src/runs/background/stale-run-reconciler.ts +9 -4
- package/src/runs/background/subagent-runner.ts +158 -11
- package/src/runs/foreground/chain-execution.ts +26 -2
- package/src/runs/foreground/execution.ts +114 -8
- package/src/runs/foreground/subagent-executor.ts +611 -87
- package/src/runs/shared/acceptance.ts +285 -34
- package/src/runs/shared/chain-outputs.ts +23 -8
- package/src/runs/shared/completion-guard.ts +1 -1
- package/src/runs/shared/dynamic-fanout.ts +5 -3
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +2 -2
- package/src/runs/shared/parallel-utils.ts +13 -1
- package/src/runs/shared/pi-args.ts +12 -3
- package/src/runs/shared/single-output.ts +15 -1
- package/src/runs/shared/subagent-control.ts +8 -11
- package/src/shared/settings.ts +1 -0
- package/src/shared/types.ts +17 -2
- package/src/shared/utils.ts +19 -1
- package/src/slash/prompt-template-bridge.ts +26 -3
- package/src/slash/slash-bridge.ts +3 -1
- package/src/slash/slash-commands.ts +34 -4
- package/src/tui/render.ts +265 -13
|
@@ -21,7 +21,7 @@ import { projectNestedRegistryForRoot, sanitizeSummary } from "../shared/nested-
|
|
|
21
21
|
const WATCHER_RESTART_DELAY_MS = 3000;
|
|
22
22
|
const POLL_INTERVAL_MS = 3000;
|
|
23
23
|
|
|
24
|
-
type ResultWatcherFs = Pick<typeof fs, "existsSync" | "readFileSync" | "unlinkSync" | "readdirSync" | "mkdirSync" | "watch">;
|
|
24
|
+
type ResultWatcherFs = Pick<typeof fs, "existsSync" | "readFileSync" | "unlinkSync" | "readdirSync" | "mkdirSync" | "realpathSync" | "watch">;
|
|
25
25
|
|
|
26
26
|
type ResultWatcherTimers = {
|
|
27
27
|
setTimeout: typeof setTimeout;
|
|
@@ -91,6 +91,14 @@ function shouldFallBackToPolling(error: unknown): boolean {
|
|
|
91
91
|
return code === "EMFILE" || code === "ENOSPC";
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
function resolveNativeWatchDir(fsApi: ResultWatcherFs, resultsDir: string): string {
|
|
95
|
+
try {
|
|
96
|
+
return fsApi.realpathSync.native(resultsDir);
|
|
97
|
+
} catch {
|
|
98
|
+
return resultsDir;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
94
102
|
export function createResultWatcher(
|
|
95
103
|
pi: { events: IntercomEventBus },
|
|
96
104
|
state: SubagentState,
|
|
@@ -264,7 +272,8 @@ export function createResultWatcher(
|
|
|
264
272
|
state.watcherRestartTimer = null;
|
|
265
273
|
}
|
|
266
274
|
try {
|
|
267
|
-
|
|
275
|
+
const watchDir = resolveNativeWatchDir(fsApi, resultsDir);
|
|
276
|
+
state.watcher = fsApi.watch(watchDir, (ev, file) => {
|
|
268
277
|
if (ev !== "rename" || !file) return;
|
|
269
278
|
const fileName = file.toString();
|
|
270
279
|
if (!fileName.endsWith(".json")) return;
|
|
@@ -205,6 +205,7 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
205
205
|
statusActivityText ? `Activity: ${statusActivityText}` : undefined,
|
|
206
206
|
`Mode: ${status.mode}`,
|
|
207
207
|
`Progress: ${progressLabel}`,
|
|
208
|
+
status.pendingAppends ? `Pending appends: ${status.pendingAppends}` : undefined,
|
|
208
209
|
`Started: ${started}`,
|
|
209
210
|
`Updated: ${updated}`,
|
|
210
211
|
`Dir: ${asyncDir}`,
|
|
@@ -48,9 +48,14 @@ function isNotFoundError(error: unknown): boolean {
|
|
|
48
48
|
&& (error as NodeJS.ErrnoException).code === "ENOENT";
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
function
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
function appendJsonlBestEffort(filePath: string, payload: object): void {
|
|
52
|
+
try {
|
|
53
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
54
|
+
fs.appendFileSync(filePath, `${JSON.stringify(payload)}\n`, "utf-8");
|
|
55
|
+
} catch {
|
|
56
|
+
// Repair status/result writes are the important path. A broken or full
|
|
57
|
+
// diagnostic event log must not make stale-run reconciliation fail.
|
|
58
|
+
}
|
|
54
59
|
}
|
|
55
60
|
|
|
56
61
|
function readStatusFile(asyncDir: string): AsyncStatus | null {
|
|
@@ -223,7 +228,7 @@ function writeFailedRepair(asyncDir: string, status: AsyncStatus, resultPath: st
|
|
|
223
228
|
const repair = buildFailedRepair(status, asyncDir, now, reason);
|
|
224
229
|
writeAtomicJson(resultPath, repair.result);
|
|
225
230
|
writeAtomicJson(path.join(asyncDir, "status.json"), repair.status);
|
|
226
|
-
|
|
231
|
+
appendJsonlBestEffort(path.join(asyncDir, "events.jsonl"), {
|
|
227
232
|
type: "subagent.run.repaired_stale",
|
|
228
233
|
ts: now,
|
|
229
234
|
runId: repair.status.runId,
|
|
@@ -4,7 +4,7 @@ import * as path from "node:path";
|
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
5
|
import type { Message } from "@earendil-works/pi-ai";
|
|
6
6
|
import { writeAtomicJson } from "../../shared/atomic-json.ts";
|
|
7
|
-
import { appendJsonl, getArtifactPaths } from "../../shared/artifacts.ts";
|
|
7
|
+
import { appendJsonl as appendRawJsonl, getArtifactPaths } from "../../shared/artifacts.ts";
|
|
8
8
|
import { PI_CODING_AGENT_PACKAGE, getPiSpawnCommand, resolveInstalledPiPackageRoot } from "../shared/pi-spawn.ts";
|
|
9
9
|
import { captureSingleOutputSnapshot, finalizeSingleOutput, formatSavedOutputReference, resolveSingleOutput, type SingleOutputSnapshot } from "../shared/single-output.ts";
|
|
10
10
|
import {
|
|
@@ -78,6 +78,8 @@ import { resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
|
78
78
|
import { writeInitialProgressFile } from "../../shared/settings.ts";
|
|
79
79
|
import { resolveSubagentIntercomTarget } from "../../intercom/intercom-bridge.ts";
|
|
80
80
|
import { acceptanceFailureMessage, aggregateAcceptanceReport, evaluateAcceptance, formatAcceptancePrompt, stripAcceptanceReport } from "../shared/acceptance.ts";
|
|
81
|
+
import { waitForImportedAsyncRoot } from "./chain-root-attachment.ts";
|
|
82
|
+
import { appendRunnerStepsToStatus, consumeChainAppendRequests, countPendingChainAppendRequests } from "./chain-append.ts";
|
|
81
83
|
|
|
82
84
|
interface SubagentRunConfig {
|
|
83
85
|
id: string;
|
|
@@ -129,6 +131,79 @@ interface StepResult {
|
|
|
129
131
|
}
|
|
130
132
|
|
|
131
133
|
const ASYNC_INTERRUPT_SIGNAL: NodeJS.Signals = process.platform === "win32" ? "SIGBREAK" : "SIGUSR2";
|
|
134
|
+
const DEFAULT_MAX_ASYNC_EVENTS_BYTES = 50 * 1024 * 1024;
|
|
135
|
+
const ASYNC_EVENTS_MAX_BYTES_ENV = "PI_SUBAGENT_ASYNC_EVENTS_MAX_BYTES";
|
|
136
|
+
const TRUNCATED_EVENT_TYPE = "subagent.events.truncated";
|
|
137
|
+
const TRUNCATION_MARKER_RESERVE_BYTES = 512;
|
|
138
|
+
|
|
139
|
+
interface AsyncEventLogState {
|
|
140
|
+
bytes: number;
|
|
141
|
+
diagnosticsTruncated: boolean;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const asyncEventLogStates = new Map<string, AsyncEventLogState>();
|
|
145
|
+
|
|
146
|
+
function maxAsyncEventsBytes(): number {
|
|
147
|
+
const raw = process.env[ASYNC_EVENTS_MAX_BYTES_ENV];
|
|
148
|
+
if (!raw) return DEFAULT_MAX_ASYNC_EVENTS_BYTES;
|
|
149
|
+
const parsed = Number(raw);
|
|
150
|
+
if (!Number.isFinite(parsed) || parsed < 0) return DEFAULT_MAX_ASYNC_EVENTS_BYTES;
|
|
151
|
+
return Math.floor(parsed);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function eventLogState(filePath: string): AsyncEventLogState {
|
|
155
|
+
let state = asyncEventLogStates.get(filePath);
|
|
156
|
+
if (state) return state;
|
|
157
|
+
let bytes = 0;
|
|
158
|
+
try {
|
|
159
|
+
bytes = fs.statSync(filePath).size;
|
|
160
|
+
} catch (error) {
|
|
161
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
|
|
162
|
+
// Diagnostic event accounting is best-effort; writes below are also safe.
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
state = { bytes, diagnosticsTruncated: false };
|
|
166
|
+
asyncEventLogStates.set(filePath, state);
|
|
167
|
+
return state;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function appendJsonl(filePath: string, line: string): void {
|
|
171
|
+
try {
|
|
172
|
+
appendRawJsonl(filePath, line);
|
|
173
|
+
const state = asyncEventLogStates.get(filePath);
|
|
174
|
+
if (state) state.bytes += Buffer.byteLength(`${line}\n`, "utf-8");
|
|
175
|
+
} catch {
|
|
176
|
+
// Async event logging is diagnostic and must not fail the run.
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function appendDiagnosticJsonl(filePath: string, line: string, droppedEventType?: string): void {
|
|
181
|
+
if (!line.trim()) return;
|
|
182
|
+
const state = eventLogState(filePath);
|
|
183
|
+
if (state.diagnosticsTruncated) return;
|
|
184
|
+
const maxBytes = maxAsyncEventsBytes();
|
|
185
|
+
const chunkBytes = Buffer.byteLength(`${line}\n`, "utf-8");
|
|
186
|
+
const diagnosticBudget = Math.max(0, maxBytes - TRUNCATION_MARKER_RESERVE_BYTES);
|
|
187
|
+
if (state.bytes + chunkBytes <= diagnosticBudget) {
|
|
188
|
+
appendJsonl(filePath, line);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const marker = JSON.stringify({
|
|
193
|
+
type: TRUNCATED_EVENT_TYPE,
|
|
194
|
+
ts: Date.now(),
|
|
195
|
+
maxBytes,
|
|
196
|
+
droppedEventType,
|
|
197
|
+
});
|
|
198
|
+
if (state.bytes + Buffer.byteLength(`${marker}\n`, "utf-8") <= maxBytes) {
|
|
199
|
+
appendJsonl(filePath, marker);
|
|
200
|
+
}
|
|
201
|
+
state.diagnosticsTruncated = true;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function shouldPersistChildEvent(event: Record<string, unknown>): boolean {
|
|
205
|
+
return event.type !== "message_update";
|
|
206
|
+
}
|
|
132
207
|
|
|
133
208
|
function findLatestSessionFile(sessionDir: string): string | null {
|
|
134
209
|
try {
|
|
@@ -272,14 +347,15 @@ function runPiStreaming(
|
|
|
272
347
|
|
|
273
348
|
const appendChildEvent = (event: Record<string, unknown>) => {
|
|
274
349
|
if (!childEventContext) return;
|
|
275
|
-
|
|
350
|
+
if (!shouldPersistChildEvent(event)) return;
|
|
351
|
+
appendDiagnosticJsonl(childEventContext.eventsPath, JSON.stringify({
|
|
276
352
|
...event,
|
|
277
353
|
subagentSource: "child",
|
|
278
354
|
subagentRunId: childEventContext.runId,
|
|
279
355
|
subagentStepIndex: childEventContext.stepIndex,
|
|
280
356
|
subagentAgent: childEventContext.agent,
|
|
281
357
|
observedAt: Date.now(),
|
|
282
|
-
}));
|
|
358
|
+
}), typeof event.type === "string" ? event.type : undefined);
|
|
283
359
|
};
|
|
284
360
|
|
|
285
361
|
const appendChildLine = (type: "subagent.child.stdout" | "subagent.child.stderr", line: string) => {
|
|
@@ -601,6 +677,30 @@ async function runSingleStep(
|
|
|
601
677
|
structuredOutputSchemaPath?: string;
|
|
602
678
|
acceptance?: import("../../shared/types.ts").AcceptanceLedger;
|
|
603
679
|
}> {
|
|
680
|
+
if (step.importAsyncRoot) {
|
|
681
|
+
const imported = await waitForImportedAsyncRoot(step.importAsyncRoot);
|
|
682
|
+
try {
|
|
683
|
+
fs.writeFileSync(ctx.outputFile, imported.output, "utf-8");
|
|
684
|
+
} catch {
|
|
685
|
+
// Output files are observability only for imported roots.
|
|
686
|
+
}
|
|
687
|
+
return {
|
|
688
|
+
agent: imported.agent,
|
|
689
|
+
output: imported.output,
|
|
690
|
+
exitCode: imported.exitCode,
|
|
691
|
+
error: imported.error,
|
|
692
|
+
sessionFile: imported.sessionFile,
|
|
693
|
+
intercomTarget: imported.intercomTarget,
|
|
694
|
+
model: imported.model,
|
|
695
|
+
attemptedModels: imported.attemptedModels,
|
|
696
|
+
modelAttempts: imported.modelAttempts,
|
|
697
|
+
structuredOutput: imported.structuredOutput,
|
|
698
|
+
structuredOutputPath: imported.structuredOutputPath,
|
|
699
|
+
structuredOutputSchemaPath: imported.structuredOutputSchemaPath,
|
|
700
|
+
acceptance: imported.acceptance,
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
|
|
604
704
|
const effectiveStructuredOutput = step.structuredOutput ?? (step.structuredOutputSchema
|
|
605
705
|
? createStructuredOutputRuntime(step.structuredOutputSchema, path.join(path.dirname(ctx.outputFile), "structured-output"))
|
|
606
706
|
: undefined);
|
|
@@ -650,6 +750,7 @@ async function runSingleStep(
|
|
|
650
750
|
}
|
|
651
751
|
}
|
|
652
752
|
const { args, env, tempDir } = buildPiArgs({
|
|
753
|
+
parentSessionId: step.parentSessionId,
|
|
653
754
|
baseArgs: ["--mode", "json", "-p"],
|
|
654
755
|
task,
|
|
655
756
|
sessionEnabled,
|
|
@@ -658,8 +759,10 @@ async function runSingleStep(
|
|
|
658
759
|
model: candidate,
|
|
659
760
|
inheritProjectContext: step.inheritProjectContext,
|
|
660
761
|
inheritSkills: step.inheritSkills,
|
|
762
|
+
requireReadTool: Boolean(step.skills?.length),
|
|
661
763
|
tools: step.tools,
|
|
662
764
|
extensions: step.extensions,
|
|
765
|
+
subagentOnlyExtensions: step.subagentOnlyExtensions,
|
|
663
766
|
systemPrompt: step.systemPrompt,
|
|
664
767
|
systemPromptMode: step.systemPromptMode,
|
|
665
768
|
mcpDirectTools: step.mcpDirectTools,
|
|
@@ -1112,6 +1215,45 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1112
1215
|
writeAtomicJson(statusPath, statusPayload);
|
|
1113
1216
|
emitNestedSelfEvent(statusPayload.state === "running" || statusPayload.state === "queued" ? "subagent.nested.updated" : "subagent.nested.completed");
|
|
1114
1217
|
};
|
|
1218
|
+
const consumePendingAppendRequests = (): void => {
|
|
1219
|
+
if (statusPayload.mode !== "chain" || statusPayload.state !== "running") return;
|
|
1220
|
+
const requests = consumeChainAppendRequests(asyncDir);
|
|
1221
|
+
if (requests.length === 0) {
|
|
1222
|
+
const pendingAppends = countPendingChainAppendRequests(asyncDir);
|
|
1223
|
+
if ((statusPayload.pendingAppends ?? 0) !== pendingAppends) {
|
|
1224
|
+
statusPayload.pendingAppends = pendingAppends;
|
|
1225
|
+
statusPayload.lastUpdate = Date.now();
|
|
1226
|
+
writeStatusPayload();
|
|
1227
|
+
}
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
const appendedSteps = requests.flatMap((request) => request.steps);
|
|
1231
|
+
steps.push(...appendedSteps);
|
|
1232
|
+
const now = Date.now();
|
|
1233
|
+
const pendingAppends = countPendingChainAppendRequests(asyncDir);
|
|
1234
|
+
const added = appendRunnerStepsToStatus({
|
|
1235
|
+
status: statusPayload,
|
|
1236
|
+
steps: appendedSteps,
|
|
1237
|
+
now,
|
|
1238
|
+
pendingAppends,
|
|
1239
|
+
});
|
|
1240
|
+
mutatingFailureStates.push(...Array.from({ length: added.addedFlatSteps }, () => createMutatingFailureState()));
|
|
1241
|
+
pendingToolResults.push(...Array.from({ length: added.addedFlatSteps }, () => undefined));
|
|
1242
|
+
if (config.childIntercomTargets) {
|
|
1243
|
+
config.childIntercomTargets = statusPayload.steps.map((statusStep, index) => resolveSubagentIntercomTarget(id, statusStep.agent, index));
|
|
1244
|
+
}
|
|
1245
|
+
writeStatusPayload();
|
|
1246
|
+
for (const request of requests) {
|
|
1247
|
+
appendJsonl(eventsPath, JSON.stringify({
|
|
1248
|
+
type: "subagent.chain.append.accepted",
|
|
1249
|
+
ts: now,
|
|
1250
|
+
runId: id,
|
|
1251
|
+
requestId: request.id,
|
|
1252
|
+
stepCount: request.steps.length,
|
|
1253
|
+
pendingAppends,
|
|
1254
|
+
}));
|
|
1255
|
+
}
|
|
1256
|
+
};
|
|
1115
1257
|
const markDynamicGraphGroup = (stepIndex: number, status: "completed" | "failed" | "running", error?: string, acceptance?: import("../../shared/types.ts").AcceptanceLedger): void => {
|
|
1116
1258
|
const groupNode = statusPayload.workflowGraph?.nodes.find((node) => node.id === `step-${stepIndex}`);
|
|
1117
1259
|
if (!groupNode) return;
|
|
@@ -1403,10 +1545,14 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1403
1545
|
);
|
|
1404
1546
|
|
|
1405
1547
|
let flatIndex = 0;
|
|
1548
|
+
let stepCursor = 0;
|
|
1406
1549
|
|
|
1407
|
-
|
|
1550
|
+
while (true) {
|
|
1408
1551
|
if (interrupted) break;
|
|
1409
|
-
|
|
1552
|
+
consumePendingAppendRequests();
|
|
1553
|
+
if (stepCursor >= steps.length) break;
|
|
1554
|
+
const stepIndex = stepCursor++;
|
|
1555
|
+
const step = steps[stepIndex]!;
|
|
1410
1556
|
|
|
1411
1557
|
if (isDynamicRunnerGroup(step)) {
|
|
1412
1558
|
const groupStartFlatIndex = flatIndex;
|
|
@@ -1835,7 +1981,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1835
1981
|
outputs,
|
|
1836
1982
|
sessionDir: taskSessionDir,
|
|
1837
1983
|
artifactsDir, artifactConfig, id,
|
|
1838
|
-
flatIndex: fi, flatStepCount:
|
|
1984
|
+
flatIndex: fi, flatStepCount: Math.max(statusPayload.steps.length, 1),
|
|
1839
1985
|
outputFile: path.join(asyncDir, `output-${fi}.log`),
|
|
1840
1986
|
piPackageRoot: config.piPackageRoot,
|
|
1841
1987
|
piArgv1: config.piArgv1,
|
|
@@ -2000,7 +2146,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2000
2146
|
outputs,
|
|
2001
2147
|
sessionDir: config.sessionDir,
|
|
2002
2148
|
artifactsDir, artifactConfig, id,
|
|
2003
|
-
flatIndex, flatStepCount:
|
|
2149
|
+
flatIndex, flatStepCount: Math.max(statusPayload.steps.length, 1),
|
|
2004
2150
|
outputFile: path.join(asyncDir, `output-${flatIndex}.log`),
|
|
2005
2151
|
piPackageRoot: config.piPackageRoot,
|
|
2006
2152
|
piArgv1: config.piArgv1,
|
|
@@ -2131,11 +2277,12 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2131
2277
|
}
|
|
2132
2278
|
|
|
2133
2279
|
const resultMode = config.resultMode ?? statusPayload.mode;
|
|
2134
|
-
const
|
|
2135
|
-
|
|
2280
|
+
const finalFlatAgents = statusPayload.steps.map((step) => step.agent);
|
|
2281
|
+
const agentName = finalFlatAgents.length === 1
|
|
2282
|
+
? finalFlatAgents[0]!
|
|
2136
2283
|
: resultMode === "parallel"
|
|
2137
|
-
? `parallel:${
|
|
2138
|
-
: `chain:${
|
|
2284
|
+
? `parallel:${finalFlatAgents.join("+")}`
|
|
2285
|
+
: `chain:${finalFlatAgents.join("->")}`;
|
|
2139
2286
|
let sessionFile: string | undefined;
|
|
2140
2287
|
let shareUrl: string | undefined;
|
|
2141
2288
|
let gistUrl: string | undefined;
|
|
@@ -102,6 +102,7 @@ interface ParallelChainRunInput {
|
|
|
102
102
|
globalTaskIndex: number;
|
|
103
103
|
sessionDirForIndex: (idx?: number) => string | undefined;
|
|
104
104
|
sessionFileForIndex?: (idx?: number) => string | undefined;
|
|
105
|
+
sessionFileForTask?: (agentName: string, idx?: number) => string | undefined;
|
|
105
106
|
shareEnabled: boolean;
|
|
106
107
|
artifactConfig: ArtifactConfig;
|
|
107
108
|
artifactsDir: string;
|
|
@@ -136,6 +137,8 @@ interface ParallelChainRunInput {
|
|
|
136
137
|
worktreeSetup?: WorktreeSetup;
|
|
137
138
|
maxSubagentDepth: number;
|
|
138
139
|
nestedRoute?: NestedRouteInfo;
|
|
140
|
+
timeoutMs?: number;
|
|
141
|
+
deadlineAt?: number;
|
|
139
142
|
}
|
|
140
143
|
|
|
141
144
|
function buildChainExecutionDetails(input: ChainExecutionDetailsInput): Details {
|
|
@@ -266,6 +269,7 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
|
|
|
266
269
|
? createStructuredOutputRuntime(task.outputSchema, path.join(input.chainDir, "structured-output"))
|
|
267
270
|
: undefined;
|
|
268
271
|
const result = await runSync(input.ctx.cwd, input.agents, task.agent, taskStr, {
|
|
272
|
+
parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
|
|
269
273
|
cwd: taskCwd,
|
|
270
274
|
signal: input.signal,
|
|
271
275
|
interruptSignal: interruptController.signal,
|
|
@@ -274,7 +278,8 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
|
|
|
274
278
|
runId: input.runId,
|
|
275
279
|
index: input.globalTaskIndex + taskIndex,
|
|
276
280
|
sessionDir: input.sessionDirForIndex(input.globalTaskIndex + taskIndex),
|
|
277
|
-
sessionFile: input.
|
|
281
|
+
sessionFile: input.sessionFileForTask?.(task.agent, input.globalTaskIndex + taskIndex)
|
|
282
|
+
?? input.sessionFileForIndex?.(input.globalTaskIndex + taskIndex),
|
|
278
283
|
share: input.shareEnabled,
|
|
279
284
|
artifactsDir: input.artifactConfig.enabled ? input.artifactsDir : undefined,
|
|
280
285
|
artifactConfig: input.artifactConfig,
|
|
@@ -293,6 +298,8 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
|
|
|
293
298
|
structuredOutput: structuredRuntime,
|
|
294
299
|
acceptance: task.acceptance,
|
|
295
300
|
acceptanceContext: { mode: "chain" },
|
|
301
|
+
timeoutMs: input.timeoutMs,
|
|
302
|
+
deadlineAt: input.deadlineAt,
|
|
296
303
|
onUpdate: input.onUpdate
|
|
297
304
|
? (progressUpdate) => {
|
|
298
305
|
const stepResults = progressUpdate.details?.results || [];
|
|
@@ -365,6 +372,7 @@ interface ChainExecutionParams {
|
|
|
365
372
|
shareEnabled: boolean;
|
|
366
373
|
sessionDirForIndex: (idx?: number) => string | undefined;
|
|
367
374
|
sessionFileForIndex?: (idx?: number) => string | undefined;
|
|
375
|
+
sessionFileForTask?: (agentName: string, idx?: number) => string | undefined;
|
|
368
376
|
artifactsDir: string;
|
|
369
377
|
artifactConfig: ArtifactConfig;
|
|
370
378
|
includeProgress?: boolean;
|
|
@@ -395,6 +403,8 @@ interface ChainExecutionParams {
|
|
|
395
403
|
nestedRoute?: NestedRouteInfo;
|
|
396
404
|
worktreeSetupHook?: string;
|
|
397
405
|
worktreeSetupHookTimeoutMs?: number;
|
|
406
|
+
timeoutMs?: number;
|
|
407
|
+
deadlineAt?: number;
|
|
398
408
|
}
|
|
399
409
|
|
|
400
410
|
interface ChainExecutionResult {
|
|
@@ -422,6 +432,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
422
432
|
shareEnabled,
|
|
423
433
|
sessionDirForIndex,
|
|
424
434
|
sessionFileForIndex,
|
|
435
|
+
sessionFileForTask,
|
|
425
436
|
artifactsDir,
|
|
426
437
|
artifactConfig,
|
|
427
438
|
includeProgress,
|
|
@@ -584,6 +595,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
584
595
|
tuiBehaviorOverrides = result.behaviorOverrides;
|
|
585
596
|
}
|
|
586
597
|
|
|
598
|
+
const deadlineAt = params.deadlineAt ?? (params.timeoutMs !== undefined ? Date.now() + params.timeoutMs : undefined);
|
|
587
599
|
let prev = "";
|
|
588
600
|
let globalTaskIndex = 0;
|
|
589
601
|
let progressCreated = false;
|
|
@@ -649,6 +661,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
649
661
|
globalTaskIndex,
|
|
650
662
|
sessionDirForIndex,
|
|
651
663
|
sessionFileForIndex,
|
|
664
|
+
sessionFileForTask,
|
|
652
665
|
shareEnabled,
|
|
653
666
|
artifactConfig,
|
|
654
667
|
artifactsDir,
|
|
@@ -670,6 +683,8 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
670
683
|
nestedRoute: params.nestedRoute,
|
|
671
684
|
worktreeSetup,
|
|
672
685
|
maxSubagentDepth: params.maxSubagentDepth,
|
|
686
|
+
timeoutMs: params.timeoutMs,
|
|
687
|
+
deadlineAt,
|
|
673
688
|
});
|
|
674
689
|
globalTaskIndex += step.parallel.length;
|
|
675
690
|
|
|
@@ -739,6 +754,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
739
754
|
output: getSingleResultOutput(result),
|
|
740
755
|
exitCode: result.exitCode,
|
|
741
756
|
error: result.error,
|
|
757
|
+
timedOut: result.timedOut,
|
|
742
758
|
outputTargetPath,
|
|
743
759
|
outputTargetExists: outputTargetPath ? fs.existsSync(outputTargetPath) : undefined,
|
|
744
760
|
};
|
|
@@ -855,6 +871,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
855
871
|
globalTaskIndex,
|
|
856
872
|
sessionDirForIndex,
|
|
857
873
|
sessionFileForIndex,
|
|
874
|
+
sessionFileForTask,
|
|
858
875
|
shareEnabled,
|
|
859
876
|
artifactConfig,
|
|
860
877
|
artifactsDir,
|
|
@@ -875,6 +892,8 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
875
892
|
foregroundControl,
|
|
876
893
|
nestedRoute: params.nestedRoute,
|
|
877
894
|
maxSubagentDepth: params.maxSubagentDepth,
|
|
895
|
+
timeoutMs: params.timeoutMs,
|
|
896
|
+
deadlineAt,
|
|
878
897
|
});
|
|
879
898
|
globalTaskIndex += dynamicParallelStep.parallel.length;
|
|
880
899
|
|
|
@@ -970,6 +989,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
970
989
|
output: getSingleResultOutput(result),
|
|
971
990
|
exitCode: result.exitCode,
|
|
972
991
|
error: result.error,
|
|
992
|
+
timedOut: result.timedOut,
|
|
973
993
|
}));
|
|
974
994
|
prev = aggregateParallelOutputs(taskResults, (i, agent) => `=== Dynamic Item ${i + 1} (${agent}, key ${materialized.items[i]?.key ?? i}) ===`);
|
|
975
995
|
} else {
|
|
@@ -1054,6 +1074,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
1054
1074
|
? createStructuredOutputRuntime(seqStep.outputSchema, path.join(chainDir, "structured-output"))
|
|
1055
1075
|
: undefined;
|
|
1056
1076
|
const r = await runSync(ctx.cwd, agents, seqStep.agent, stepTask, {
|
|
1077
|
+
parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
|
|
1057
1078
|
cwd: resolveChildCwd(cwd ?? ctx.cwd, seqStep.cwd),
|
|
1058
1079
|
signal,
|
|
1059
1080
|
interruptSignal: interruptController.signal,
|
|
@@ -1062,7 +1083,8 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
1062
1083
|
runId,
|
|
1063
1084
|
index: globalTaskIndex,
|
|
1064
1085
|
sessionDir: sessionDirForIndex(globalTaskIndex),
|
|
1065
|
-
sessionFile:
|
|
1086
|
+
sessionFile: sessionFileForTask?.(seqStep.agent, globalTaskIndex)
|
|
1087
|
+
?? sessionFileForIndex?.(globalTaskIndex),
|
|
1066
1088
|
share: shareEnabled,
|
|
1067
1089
|
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
1068
1090
|
artifactConfig,
|
|
@@ -1081,6 +1103,8 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
1081
1103
|
structuredOutput: structuredRuntime,
|
|
1082
1104
|
acceptance: seqStep.acceptance,
|
|
1083
1105
|
acceptanceContext: { mode: "chain" },
|
|
1106
|
+
timeoutMs: params.timeoutMs,
|
|
1107
|
+
deadlineAt,
|
|
1084
1108
|
onUpdate: onUpdate
|
|
1085
1109
|
? (p) => {
|
|
1086
1110
|
const stepResults = p.details?.results || [];
|