pi-subagents 0.35.0 → 0.36.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 +59 -0
- package/README.md +132 -24
- package/agents/advisor.md +73 -0
- package/package.json +8 -12
- package/skills/pi-subagents/SKILL.md +22 -9
- package/src/agents/agents.ts +22 -5
- package/src/api/delegation.ts +125 -0
- package/src/extension/config.ts +7 -1
- package/src/extension/index.ts +50 -38
- package/src/extension/rpc.ts +27 -2
- package/src/extension/schemas.ts +22 -2
- package/src/extension/tool-description.ts +2 -2
- package/src/intercom/intercom-bridge.ts +1 -1
- package/src/intercom/native-supervisor-channel.ts +45 -6
- package/src/intercom/result-intercom.ts +7 -0
- package/src/runs/background/async-execution.ts +34 -4
- package/src/runs/background/async-job-tracker.ts +4 -0
- package/src/runs/background/async-resume.ts +27 -5
- package/src/runs/background/async-status.ts +76 -3
- package/src/runs/background/chain-append.ts +2 -0
- package/src/runs/background/completion-batcher.ts +6 -4
- package/src/runs/background/completion-dedupe.ts +2 -11
- package/src/runs/background/fleet-view.ts +9 -4
- package/src/runs/background/notify.ts +132 -120
- package/src/runs/background/result-watcher.ts +138 -78
- package/src/runs/background/run-status.ts +3 -1
- package/src/runs/background/subagent-runner.ts +225 -43
- package/src/runs/background/subagent-wait.ts +130 -4
- package/src/runs/background/wait-tool.ts +2 -2
- package/src/runs/foreground/chain-execution.ts +176 -111
- package/src/runs/foreground/execution.ts +90 -36
- package/src/runs/foreground/foreground-control.ts +90 -0
- package/src/runs/foreground/subagent-executor.ts +394 -163
- package/src/runs/shared/acceptance.ts +55 -13
- package/src/runs/shared/agent-contract.ts +38 -0
- package/src/runs/shared/child-protocol.ts +1 -1
- package/src/runs/shared/completion-guard.ts +36 -5
- package/src/runs/shared/context-mode.ts +44 -0
- package/src/runs/shared/dynamic-fanout.ts +4 -4
- package/src/runs/shared/long-running-guard.ts +4 -0
- package/src/runs/shared/nested-events.ts +27 -2
- package/src/runs/shared/parallel-handoff.ts +154 -0
- package/src/runs/shared/parallel-utils.ts +6 -0
- package/src/runs/shared/pi-args.ts +23 -14
- package/src/runs/shared/run-history.ts +90 -5
- package/src/runs/shared/structured-output.ts +112 -7
- package/src/runs/shared/subagent-control.ts +4 -0
- package/src/runs/shared/subagent-prompt-runtime.ts +17 -18
- package/src/runs/shared/task-intent.ts +10 -5
- package/src/runs/shared/tool-availability.ts +3 -1
- package/src/runs/shared/tool-budget.ts +11 -5
- package/src/runs/shared/turn-budget.ts +2 -1
- package/src/runs/shared/worktree.ts +63 -14
- package/src/shared/accessible-dir.ts +25 -0
- package/src/shared/artifacts.ts +37 -7
- package/src/shared/atomic-json.ts +14 -42
- package/src/shared/child-transcript.ts +52 -0
- package/src/shared/file-system-retry.ts +47 -0
- package/src/shared/settings.ts +9 -1
- package/src/shared/types.ts +211 -22
- package/src/slash/delegation-adapters.ts +152 -5
- package/src/slash/delegation-json.ts +108 -0
- package/src/slash/delegation-request.ts +182 -36
- package/src/slash/prompt-template-bridge.ts +222 -37
- package/src/slash/selector.ts +147 -0
- package/src/slash/slash-commands.ts +14 -5
- package/src/slash/slash-live-state.ts +2 -2
- package/src/slash/subagents-admin.ts +42 -42
- package/src/tui/fleet-status.ts +362 -0
- package/src/tui/fleet-transcript.ts +472 -0
- package/src/tui/fleet.ts +318 -59
- package/src/tui/render.ts +25 -15
- package/src/watchdog/change-signature.ts +105 -12
- package/src/watchdog/review.ts +7 -2
- package/src/watchdog/runtime.ts +5 -3
- package/src/slash/subagents-editor.ts +0 -86
|
@@ -6,7 +6,7 @@ import type { Message } from "@earendil-works/pi-ai";
|
|
|
6
6
|
import { writeAtomicJson } from "../../shared/atomic-json.ts";
|
|
7
7
|
import { createChildTranscriptWriter, type ChildTranscriptWriter } from "../../shared/child-transcript.ts";
|
|
8
8
|
import { closeSteerInbox, consumeInterruptRequest, consumeSteerRequests, deliverInterruptRequest, deliverStopRequest, deliverTimeoutRequest, enqueueStepSteer, steerAcksDir, steerCapabilityPath, stepSteerInboxDir, watchAsyncControlInbox, type SteerAck, type SteerCapability, type SteerRequest } from "./control-channel.ts";
|
|
9
|
-
import { appendJsonl as appendRawJsonl, getArtifactPaths } from "../../shared/artifacts.ts";
|
|
9
|
+
import { appendJsonl as appendRawJsonl, formatOutputArtifactContent, getArtifactPaths } from "../../shared/artifacts.ts";
|
|
10
10
|
import { PI_CODING_AGENT_PACKAGE, getPiSpawnCommand, resolveInstalledPiPackageRoot } from "../shared/pi-spawn.ts";
|
|
11
11
|
import { captureSingleOutputSnapshot, extractChildWrittenOutput, finalizeSingleOutput, formatSavedOutputReference, injectOutputPathSystemPrompt, injectSingleOutputInstruction, resolveSingleOutput, type SingleOutputSnapshot } from "../shared/single-output.ts";
|
|
12
12
|
import {
|
|
@@ -94,10 +94,12 @@ import { resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
|
94
94
|
import { writeInitialProgressFile } from "../../shared/settings.ts";
|
|
95
95
|
import { resolveSubagentIntercomTarget } from "../../intercom/intercom-bridge.ts";
|
|
96
96
|
import { acceptanceFailureMessage, aggregateAcceptanceReport, buildSkippedAcceptanceLedger, evaluateAcceptance, formatAcceptancePrompt, resolveEffectiveAcceptance, stripAcceptanceReport } from "../shared/acceptance.ts";
|
|
97
|
+
import { attachContractProjections, isAgentContractV1 } from "../shared/agent-contract.ts";
|
|
97
98
|
import { waitForImportedAsyncRoot } from "./chain-root-attachment.ts";
|
|
98
99
|
import { appendRunnerStepsToStatus, consumeChainAppendRequests, countPendingChainAppendRequests } from "./chain-append.ts";
|
|
99
100
|
import { appendTurnBudgetSystemPrompt, formatTurnBudgetOutput, initialTurnBudgetState, turnBudgetDecision, turnBudgetDeferredNote, turnBudgetDeferredState, turnBudgetExceededMessage, turnBudgetSoftNote, turnBudgetState } from "../shared/turn-budget.ts";
|
|
100
101
|
import { initialToolBudgetState, toolBudgetState } from "../shared/tool-budget.ts";
|
|
102
|
+
import { formatParallelHandoffError, formatParallelHandoffReference, parallelHandoffPath, writeParallelHandoffGroup } from "../shared/parallel-handoff.ts";
|
|
101
103
|
import { resolveWatchdogConfig } from "../../watchdog/settings.ts";
|
|
102
104
|
import { createBoundedByteTail, createBoundedLineReader, formatProtocolOutputLimit, MAX_CHILD_STDERR_BYTES, projectChildLifecycle, type ChildLifecycleAction, type ProtocolOutputLimit } from "../shared/child-protocol.ts";
|
|
103
105
|
import { acquireSessionLease, type SessionLeaseRequest } from "../shared/session-lease.ts";
|
|
@@ -150,6 +152,7 @@ interface SubagentRunConfig {
|
|
|
150
152
|
|
|
151
153
|
interface StepResult {
|
|
152
154
|
agent: string;
|
|
155
|
+
context?: "fresh" | "fork";
|
|
153
156
|
output: string;
|
|
154
157
|
error?: string;
|
|
155
158
|
protocolError?: ProtocolOutputLimit;
|
|
@@ -174,6 +177,10 @@ interface StepResult {
|
|
|
174
177
|
truncated?: boolean;
|
|
175
178
|
transcriptPath?: string;
|
|
176
179
|
transcriptError?: string;
|
|
180
|
+
agentContract?: import("../../shared/types.ts").AgentContract;
|
|
181
|
+
execution?: import("../../shared/types.ts").ExecutionProjection;
|
|
182
|
+
review?: import("../../shared/types.ts").ReviewProjection;
|
|
183
|
+
effects?: import("../../shared/types.ts").EffectsProjection;
|
|
177
184
|
structuredOutput?: unknown;
|
|
178
185
|
structuredOutputPath?: string;
|
|
179
186
|
structuredOutputSchemaPath?: string;
|
|
@@ -256,6 +263,15 @@ function shouldPersistChildEvent(event: Record<string, unknown>): boolean {
|
|
|
256
263
|
return event.type !== "message_update";
|
|
257
264
|
}
|
|
258
265
|
|
|
266
|
+
function isBlockingSupervisorTool(toolName: string | undefined, args: unknown): boolean {
|
|
267
|
+
if (!args || typeof args !== "object" || Array.isArray(args)) return false;
|
|
268
|
+
if (toolName === "contact_supervisor") {
|
|
269
|
+
const reason = (args as Record<string, unknown>).reason;
|
|
270
|
+
return reason === "need_decision" || reason === "interview_request";
|
|
271
|
+
}
|
|
272
|
+
return toolName === "intercom" && (args as Record<string, unknown>).action === "ask";
|
|
273
|
+
}
|
|
274
|
+
|
|
259
275
|
function findLatestSessionFile(sessionDir: string): string | null {
|
|
260
276
|
try {
|
|
261
277
|
const files = fs
|
|
@@ -951,6 +967,8 @@ async function runSingleStep(
|
|
|
951
967
|
ctx: SingleStepContext,
|
|
952
968
|
): Promise<{
|
|
953
969
|
agent: string;
|
|
970
|
+
context?: "fresh" | "fork";
|
|
971
|
+
agentContract?: import("../../shared/types.ts").AgentContract;
|
|
954
972
|
output: string;
|
|
955
973
|
exitCode: number | null;
|
|
956
974
|
error?: string;
|
|
@@ -971,6 +989,9 @@ async function runSingleStep(
|
|
|
971
989
|
sessionFile?: string;
|
|
972
990
|
intercomTarget?: string;
|
|
973
991
|
completionGuardTriggered?: boolean;
|
|
992
|
+
effects?: import("../../shared/types.ts").EffectsProjection;
|
|
993
|
+
execution?: import("../../shared/types.ts").ExecutionProjection;
|
|
994
|
+
review?: import("../../shared/types.ts").ReviewProjection;
|
|
974
995
|
structuredOutput?: unknown;
|
|
975
996
|
structuredOutputPath?: string;
|
|
976
997
|
structuredOutputSchemaPath?: string;
|
|
@@ -1052,7 +1073,7 @@ async function runSingleStep(
|
|
|
1052
1073
|
if (ctx.outputs) task = resolveOutputReferences(task, ctx.outputs);
|
|
1053
1074
|
const taskForCompletionGuard = task;
|
|
1054
1075
|
if (step.effectiveAcceptance) {
|
|
1055
|
-
const acceptancePrompt = formatAcceptancePrompt(step.effectiveAcceptance);
|
|
1076
|
+
const acceptancePrompt = formatAcceptancePrompt(step.effectiveAcceptance, { reportOptional: isAgentContractV1(step.agentContract) });
|
|
1056
1077
|
if (acceptancePrompt) task = `${task}\n${acceptancePrompt}`;
|
|
1057
1078
|
}
|
|
1058
1079
|
const sessionEnabled = Boolean(step.sessionFile) || ctx.sessionEnabled;
|
|
@@ -1206,7 +1227,7 @@ async function runSingleStep(
|
|
|
1206
1227
|
let structuredOutput: unknown;
|
|
1207
1228
|
let structuredError: string | undefined;
|
|
1208
1229
|
if (effectiveStructuredOutput && run.exitCode === 0 && !run.error && !toolAvailabilityError && !hiddenError?.hasError && !emptyOutputError) {
|
|
1209
|
-
const structured = readStructuredOutput({
|
|
1230
|
+
const structured = await readStructuredOutput({
|
|
1210
1231
|
schema: effectiveStructuredOutput.schema,
|
|
1211
1232
|
schemaPath: effectiveStructuredOutput.schemaPath,
|
|
1212
1233
|
outputPath: effectiveStructuredOutput.outputPath,
|
|
@@ -1214,7 +1235,8 @@ async function runSingleStep(
|
|
|
1214
1235
|
if (structured.error) structuredError = structured.error;
|
|
1215
1236
|
else structuredOutput = structured.value;
|
|
1216
1237
|
}
|
|
1217
|
-
const
|
|
1238
|
+
const completionGuardEnabled = isAgentContractV1(step.agentContract) ? step.completionGuard === true : step.completionGuard !== false;
|
|
1239
|
+
const completionGuard = run.exitCode === 0 && !run.error && !toolAvailabilityError && !hiddenError?.hasError && !emptyOutputError && completionGuardEnabled
|
|
1218
1240
|
? evaluateCompletionMutationGuard({
|
|
1219
1241
|
agent: step.agent,
|
|
1220
1242
|
task: taskForCompletionGuard,
|
|
@@ -1224,10 +1246,18 @@ async function runSingleStep(
|
|
|
1224
1246
|
})
|
|
1225
1247
|
: undefined;
|
|
1226
1248
|
const completionGuardTriggered = completionGuard?.triggered === true && !run.observedMutationAttempt;
|
|
1227
|
-
const
|
|
1249
|
+
const fileMutationEffect = completionGuard
|
|
1250
|
+
? {
|
|
1251
|
+
status: completionGuard.expectedMutation ? completionGuardTriggered ? "missing" as const : "observed" as const : "not-applicable" as const,
|
|
1252
|
+
expected: completionGuard.expectedMutation,
|
|
1253
|
+
attempted: completionGuard.attemptedMutation || run.observedMutationAttempt === true,
|
|
1254
|
+
...(completionGuardTriggered ? { message: "Subagent completed without making edits for an implementation task." } : {}),
|
|
1255
|
+
}
|
|
1256
|
+
: undefined;
|
|
1257
|
+
const completionGuardError = completionGuardTriggered && !isAgentContractV1(step.agentContract)
|
|
1228
1258
|
? "Subagent completed without making edits for an implementation task.\nIt appears to have returned planning or scratchpad output instead of applying changes."
|
|
1229
1259
|
: undefined;
|
|
1230
|
-
const effectiveExitCode = toolAvailabilityError || completionGuardTriggered || structuredError
|
|
1260
|
+
const effectiveExitCode = toolAvailabilityError || (completionGuardTriggered && !isAgentContractV1(step.agentContract)) || structuredError
|
|
1231
1261
|
? 1
|
|
1232
1262
|
: hiddenError?.hasError
|
|
1233
1263
|
? (hiddenError.exitCode ?? 1)
|
|
@@ -1261,7 +1291,7 @@ async function runSingleStep(
|
|
|
1261
1291
|
toolBudgetBlocked = Boolean(blockedMessage);
|
|
1262
1292
|
toolBudget = toolBudgetState(step.toolBudget, toolMessages.length, blockedMessage ? (blockedMessage as { toolName?: string }).toolName : undefined);
|
|
1263
1293
|
}
|
|
1264
|
-
finalResult = { ...run, exitCode: effectiveExitCode, model: candidate ?? run.model, error, structuredOutput } as RunPiStreamingResult & { structuredOutput?: unknown };
|
|
1294
|
+
finalResult = { ...run, exitCode: effectiveExitCode, model: candidate ?? run.model, error, structuredOutput, ...(step.agentContract ? { agentContract: step.agentContract } : {}), ...(fileMutationEffect ? { effects: { fileMutation: fileMutationEffect } } : {}) } as RunPiStreamingResult & { structuredOutput?: unknown; agentContract?: import("../../shared/types.ts").AgentContract; effects?: import("../../shared/types.ts").EffectsProjection };
|
|
1265
1295
|
if (run.turnBudgetExceeded) break;
|
|
1266
1296
|
if (run.stopped || run.timedOut || ctx.timeoutSignal?.aborted || ctx.stopSignal?.aborted || ctx.skipAcceptance?.()) break;
|
|
1267
1297
|
if (attempt.success || completionGuardTriggered) break;
|
|
@@ -1315,6 +1345,7 @@ async function runSingleStep(
|
|
|
1315
1345
|
cwd: step.cwd ?? ctx.cwd,
|
|
1316
1346
|
signal: combinedAbortSignal([ctx.timeoutSignal, ctx.stopSignal]),
|
|
1317
1347
|
abortMessage: ctx.stopSignal?.aborted ? ctx.stopMessage ?? "Subagent stopped by user." : ctx.timeoutMessage ?? "Subagent timed out.",
|
|
1348
|
+
reportOptional: isAgentContractV1(step.agentContract),
|
|
1318
1349
|
})
|
|
1319
1350
|
: undefined;
|
|
1320
1351
|
const stoppedAfterAcceptance = finalResult?.stopped === true || ctx.stopSignal?.aborted === true;
|
|
@@ -1330,7 +1361,7 @@ async function runSingleStep(
|
|
|
1330
1361
|
: acceptance
|
|
1331
1362
|
: undefined;
|
|
1332
1363
|
const acceptanceFailure = effectiveAcceptance ? acceptanceFailureMessage(effectiveAcceptance) : undefined;
|
|
1333
|
-
const acceptanceCanFailRun = acceptanceFailure && effectiveAcceptance?.explicit && (finalResult?.exitCode ?? 1) === 0 && !finalResult?.interrupted && !timedOutAfterAcceptance && !stoppedAfterAcceptance && !turnBudgetExceeded;
|
|
1364
|
+
const acceptanceCanFailRun = acceptanceFailure && effectiveAcceptance?.explicit && (finalResult?.exitCode ?? 1) === 0 && !finalResult?.interrupted && !timedOutAfterAcceptance && !stoppedAfterAcceptance && !turnBudgetExceeded && !isAgentContractV1(step.agentContract);
|
|
1334
1365
|
const effectiveFinalExitCode = timedOutAfterAcceptance || stoppedAfterAcceptance || turnBudgetExceeded ? 1 : acceptanceCanFailRun ? 1 : finalResult?.exitCode ?? 1;
|
|
1335
1366
|
const effectiveFinalError = stoppedAfterAcceptance
|
|
1336
1367
|
? ctx.stopMessage ?? "Subagent stopped by user."
|
|
@@ -1344,7 +1375,12 @@ async function runSingleStep(
|
|
|
1344
1375
|
|
|
1345
1376
|
if (artifactPaths && ctx.artifactConfig?.enabled !== false) {
|
|
1346
1377
|
if (ctx.artifactConfig?.includeOutput !== false) {
|
|
1347
|
-
fs.writeFileSync(artifactPaths.outputPath,
|
|
1378
|
+
fs.writeFileSync(artifactPaths.outputPath, formatOutputArtifactContent({
|
|
1379
|
+
output,
|
|
1380
|
+
error: effectiveFinalError,
|
|
1381
|
+
transcriptPath: transcriptWriter ? artifactPaths.transcriptPath : undefined,
|
|
1382
|
+
metadataPath: ctx.artifactConfig?.includeMetadata === false ? undefined : artifactPaths.metadataPath,
|
|
1383
|
+
}), "utf-8");
|
|
1348
1384
|
}
|
|
1349
1385
|
if (ctx.artifactConfig?.includeMetadata !== false) {
|
|
1350
1386
|
fs.writeFileSync(
|
|
@@ -1369,8 +1405,10 @@ async function runSingleStep(
|
|
|
1369
1405
|
}
|
|
1370
1406
|
}
|
|
1371
1407
|
|
|
1372
|
-
|
|
1408
|
+
const result = {
|
|
1373
1409
|
agent: step.agent,
|
|
1410
|
+
context: step.context,
|
|
1411
|
+
...(step.agentContract ? { agentContract: step.agentContract } : {}),
|
|
1374
1412
|
output: outputForSummary,
|
|
1375
1413
|
exitCode: effectiveFinalExitCode,
|
|
1376
1414
|
error: effectiveFinalError,
|
|
@@ -1393,12 +1431,14 @@ async function runSingleStep(
|
|
|
1393
1431
|
toolBudget,
|
|
1394
1432
|
toolBudgetBlocked: toolBudgetBlocked || undefined,
|
|
1395
1433
|
completionGuardTriggered: completionGuardTriggeredFinal,
|
|
1434
|
+
...((finalResult as (RunPiStreamingResult & { effects?: import("../../shared/types.ts").EffectsProjection }) | undefined)?.effects ? { effects: (finalResult as RunPiStreamingResult & { effects?: import("../../shared/types.ts").EffectsProjection }).effects } : {}),
|
|
1396
1435
|
structuredOutput: timedOutAfterAcceptance || stoppedAfterAcceptance || turnBudgetExceeded ? undefined : (finalResult as (RunPiStreamingResult & { structuredOutput?: unknown }) | undefined)?.structuredOutput,
|
|
1397
1436
|
structuredOutputPath: timedOutAfterAcceptance || stoppedAfterAcceptance || turnBudgetExceeded ? undefined : effectiveStructuredOutput?.outputPath,
|
|
1398
1437
|
structuredOutputSchemaPath: timedOutAfterAcceptance || stoppedAfterAcceptance || turnBudgetExceeded ? undefined : effectiveStructuredOutput?.schemaPath,
|
|
1399
1438
|
acceptance: effectiveAcceptance,
|
|
1400
1439
|
watchdog: finalResult?.watchdog,
|
|
1401
1440
|
};
|
|
1441
|
+
return isAgentContractV1(step.agentContract) ? attachContractProjections(result as unknown as import("../../shared/types.ts").SingleResult) as unknown as typeof result : result;
|
|
1402
1442
|
}
|
|
1403
1443
|
|
|
1404
1444
|
type RunnerStatusStep = NonNullable<AsyncStatus["steps"]>[number] & {
|
|
@@ -1440,7 +1480,7 @@ function markParallelGroupSetupFailure(input: {
|
|
|
1440
1480
|
input.statusPayload.steps[flatTaskIndex].endedAt = input.failedAt;
|
|
1441
1481
|
input.statusPayload.steps[flatTaskIndex].durationMs = 0;
|
|
1442
1482
|
input.statusPayload.steps[flatTaskIndex].exitCode = 1;
|
|
1443
|
-
input.results.push({ agent: input.group.parallel[taskIndex].agent, output: input.setupError, success: false, exitCode: 1, sessionFile: input.group.parallel[taskIndex].sessionFile });
|
|
1483
|
+
input.results.push({ agent: input.group.parallel[taskIndex].agent, context: input.group.parallel[taskIndex].context, output: input.setupError, success: false, exitCode: 1, sessionFile: input.group.parallel[taskIndex].sessionFile });
|
|
1444
1484
|
}
|
|
1445
1485
|
input.statusPayload.currentStep = input.groupStartFlatIndex;
|
|
1446
1486
|
input.statusPayload.lastUpdate = input.failedAt;
|
|
@@ -1505,19 +1545,15 @@ function prepareParallelTaskRun(
|
|
|
1505
1545
|
};
|
|
1506
1546
|
}
|
|
1507
1547
|
|
|
1508
|
-
function
|
|
1509
|
-
|
|
1510
|
-
worktreeSetup: WorktreeSetup | undefined,
|
|
1548
|
+
function captureParallelWorktreeDiffs(
|
|
1549
|
+
worktreeSetup: WorktreeSetup,
|
|
1511
1550
|
asyncDir: string,
|
|
1512
1551
|
stepIndex: number,
|
|
1513
1552
|
group: Extract<RunnerStep, { parallel: SubagentStep[] }>,
|
|
1514
|
-
): string {
|
|
1515
|
-
if (!worktreeSetup) return previousOutput;
|
|
1553
|
+
): { diffs: ReturnType<typeof diffWorktrees>; summary: string } {
|
|
1516
1554
|
const diffsDir = path.join(asyncDir, "worktree-diffs", `step-${stepIndex}`);
|
|
1517
1555
|
const diffs = diffWorktrees(worktreeSetup, group.parallel.map((task) => task.agent), diffsDir);
|
|
1518
|
-
|
|
1519
|
-
if (!diffSummary) return previousOutput;
|
|
1520
|
-
return `${previousOutput}\n\n${diffSummary}`;
|
|
1556
|
+
return { diffs, summary: formatWorktreeDiffSummary(diffs) };
|
|
1521
1557
|
}
|
|
1522
1558
|
|
|
1523
1559
|
function ensureParallelProgressFile(cwd: string, group: Extract<RunnerStep, { parallel: SubagentStep[] }>): void {
|
|
@@ -1612,10 +1648,12 @@ async function runSubagent(
|
|
|
1612
1648
|
const transcriptPath = resolveAsyncStepTranscriptPath({ artifactsDir, artifactConfig, runId: id, agent: task.agent, flatIndex: taskFlatIndex, flatStepCount: initialFlatStepCount });
|
|
1613
1649
|
initialStatusSteps.push({
|
|
1614
1650
|
agent: task.agent,
|
|
1651
|
+
...(task.context ? { context: task.context } : {}),
|
|
1615
1652
|
phase: task.phase,
|
|
1616
1653
|
label: task.label,
|
|
1617
1654
|
outputName: task.outputName,
|
|
1618
1655
|
structured: task.structured,
|
|
1656
|
+
...(task.agentContract ? { agentContract: task.agentContract } : {}),
|
|
1619
1657
|
status: "pending",
|
|
1620
1658
|
...(task.toolBudget ? { toolBudget: initialToolBudgetState(task.toolBudget) } : {}),
|
|
1621
1659
|
...(task.sessionFile ? { sessionFile: task.sessionFile } : {}),
|
|
@@ -1633,10 +1671,12 @@ async function runSubagent(
|
|
|
1633
1671
|
parallelGroups.push({ start: flatStepCount, count: 1, stepIndex });
|
|
1634
1672
|
initialStatusSteps.push({
|
|
1635
1673
|
agent: `expand:${step.parallel.agent}`,
|
|
1674
|
+
...(step.parallel.context ? { context: step.parallel.context } : {}),
|
|
1636
1675
|
phase: step.phase ?? step.parallel.phase,
|
|
1637
1676
|
label: step.label ?? step.parallel.label ?? `Dynamic fanout (${step.collect.as})`,
|
|
1638
1677
|
outputName: step.collect.as,
|
|
1639
1678
|
structured: Boolean(step.collect.outputSchema),
|
|
1679
|
+
...(step.agentContract ? { agentContract: step.agentContract } : {}),
|
|
1640
1680
|
status: "pending",
|
|
1641
1681
|
...(step.parallel.toolBudget ? { toolBudget: initialToolBudgetState(step.parallel.toolBudget) } : {}),
|
|
1642
1682
|
recentTools: [],
|
|
@@ -1648,10 +1688,12 @@ async function runSubagent(
|
|
|
1648
1688
|
const transcriptPath = resolveAsyncStepTranscriptPath({ artifactsDir, artifactConfig, runId: id, agent: step.agent, flatIndex: stepFlatIndex, flatStepCount: initialFlatStepCount });
|
|
1649
1689
|
initialStatusSteps.push({
|
|
1650
1690
|
agent: step.agent,
|
|
1691
|
+
...(step.context ? { context: step.context } : {}),
|
|
1651
1692
|
phase: step.phase,
|
|
1652
1693
|
label: step.label,
|
|
1653
1694
|
outputName: step.outputName,
|
|
1654
1695
|
structured: step.structured,
|
|
1696
|
+
...(step.agentContract ? { agentContract: step.agentContract } : {}),
|
|
1655
1697
|
status: "pending",
|
|
1656
1698
|
...(step.toolBudget ? { toolBudget: initialToolBudgetState(step.toolBudget) } : {}),
|
|
1657
1699
|
...(step.sessionFile ? { sessionFile: step.sessionFile } : {}),
|
|
@@ -1896,21 +1938,24 @@ async function runSubagent(
|
|
|
1896
1938
|
}
|
|
1897
1939
|
}
|
|
1898
1940
|
};
|
|
1899
|
-
const pausedStepResult = (agent: string): SingleStepResult => ({
|
|
1941
|
+
const pausedStepResult = (agent: string, context?: "fresh" | "fork"): SingleStepResult => ({
|
|
1900
1942
|
agent,
|
|
1943
|
+
context,
|
|
1901
1944
|
output: "Paused after interrupt. Waiting for explicit next action.",
|
|
1902
1945
|
exitCode: 0,
|
|
1903
1946
|
interrupted: true,
|
|
1904
1947
|
});
|
|
1905
|
-
const timedOutStepResult = (agent: string): SingleStepResult => ({
|
|
1948
|
+
const timedOutStepResult = (agent: string, context?: "fresh" | "fork"): SingleStepResult => ({
|
|
1906
1949
|
agent,
|
|
1950
|
+
context,
|
|
1907
1951
|
output: timeoutMessage ?? "Subagent timed out.",
|
|
1908
1952
|
error: timeoutMessage ?? "Subagent timed out.",
|
|
1909
1953
|
exitCode: 1,
|
|
1910
1954
|
timedOut: true,
|
|
1911
1955
|
});
|
|
1912
|
-
const stoppedStepResult = (agent: string): SingleStepResult => ({
|
|
1956
|
+
const stoppedStepResult = (agent: string, context?: "fresh" | "fork"): SingleStepResult => ({
|
|
1913
1957
|
agent,
|
|
1958
|
+
context,
|
|
1914
1959
|
output: stopMessage,
|
|
1915
1960
|
error: stopMessage,
|
|
1916
1961
|
exitCode: 1,
|
|
@@ -1980,6 +2025,7 @@ async function runSubagent(
|
|
|
1980
2025
|
const activeLongRunningSteps = new Set<number>();
|
|
1981
2026
|
const mutatingFailureStates = initialStatusSteps.map(() => createMutatingFailureState());
|
|
1982
2027
|
const pendingToolResults: Array<{ tool: string; path?: string; mutates: boolean; startedAt?: number } | undefined> = initialStatusSteps.map(() => undefined);
|
|
2028
|
+
const supervisorAttentionSteps = new Map<number, ActivityState | undefined>();
|
|
1983
2029
|
const mutatingFailureWindowMs = 5 * 60_000;
|
|
1984
2030
|
const appendControlEvent = (event: ReturnType<typeof buildControlEvent>) => {
|
|
1985
2031
|
if (!controlConfig.enabled) return;
|
|
@@ -2010,6 +2056,15 @@ async function runSubagent(
|
|
|
2010
2056
|
statusPayload.currentToolStartedAt = activeStep?.currentToolStartedAt;
|
|
2011
2057
|
statusPayload.currentPath = activeStep?.currentPath;
|
|
2012
2058
|
};
|
|
2059
|
+
const syncAggregateActivityState = (): void => {
|
|
2060
|
+
const nextRunState = statusPayload.steps.some((step) => step.activityState === "needs_attention")
|
|
2061
|
+
? "needs_attention"
|
|
2062
|
+
: statusPayload.steps.some((step) => step.activityState === "active_long_running")
|
|
2063
|
+
? "active_long_running"
|
|
2064
|
+
: undefined;
|
|
2065
|
+
currentActivityState = nextRunState;
|
|
2066
|
+
statusPayload.activityState = nextRunState;
|
|
2067
|
+
};
|
|
2013
2068
|
const maybeEmitActiveLongRunning = (flatIndex: number, now: number): boolean => {
|
|
2014
2069
|
if (!controlConfig.enabled || activeLongRunningSteps.has(flatIndex)) return false;
|
|
2015
2070
|
const step = statusPayload.steps[flatIndex];
|
|
@@ -2280,15 +2335,45 @@ async function runSubagent(
|
|
|
2280
2335
|
pendingToolResults[flatIndex] = { tool: event.toolName, path: currentPath, mutates, startedAt: now };
|
|
2281
2336
|
statusPayload.toolCount = (statusPayload.toolCount ?? 0) + 1;
|
|
2282
2337
|
syncTopLevelCurrentTool();
|
|
2338
|
+
if (controlConfig.enabled && isBlockingSupervisorTool(event.toolName, event.args) && step.activityState !== "needs_attention") {
|
|
2339
|
+
const previous = step.activityState;
|
|
2340
|
+
step.activityState = "needs_attention";
|
|
2341
|
+
supervisorAttentionSteps.set(flatIndex, previous);
|
|
2342
|
+
currentActivityState = "needs_attention";
|
|
2343
|
+
statusPayload.activityState = "needs_attention";
|
|
2344
|
+
appendControlEvent(buildControlEvent({
|
|
2345
|
+
type: "needs_attention",
|
|
2346
|
+
from: previous,
|
|
2347
|
+
to: "needs_attention",
|
|
2348
|
+
runId: id,
|
|
2349
|
+
agent: step.agent,
|
|
2350
|
+
index: flatIndex,
|
|
2351
|
+
ts: now,
|
|
2352
|
+
message: `${step.agent} is waiting for a supervisor reply`,
|
|
2353
|
+
reason: "supervisor_request",
|
|
2354
|
+
turns: step.turnCount,
|
|
2355
|
+
tokens: step.tokens?.total,
|
|
2356
|
+
toolCount: step.toolCount,
|
|
2357
|
+
currentTool: step.currentTool,
|
|
2358
|
+
currentToolDurationMs: 0,
|
|
2359
|
+
currentPath: step.currentPath,
|
|
2360
|
+
}));
|
|
2361
|
+
}
|
|
2283
2362
|
} else if (event.type === "tool_execution_end") {
|
|
2284
2363
|
if (step.currentTool) {
|
|
2285
2364
|
step.recentTools ??= [];
|
|
2286
2365
|
step.recentTools.push({ tool: step.currentTool, args: step.currentToolArgs || "", endMs: now });
|
|
2287
2366
|
}
|
|
2367
|
+
const supervisorPreviousActivity = supervisorAttentionSteps.get(flatIndex);
|
|
2368
|
+
const clearedSupervisorAttention = supervisorAttentionSteps.delete(flatIndex);
|
|
2288
2369
|
step.currentTool = undefined;
|
|
2289
2370
|
step.currentToolArgs = undefined;
|
|
2290
2371
|
step.currentToolStartedAt = undefined;
|
|
2291
2372
|
step.currentPath = undefined;
|
|
2373
|
+
if (clearedSupervisorAttention && step.activityState === "needs_attention") {
|
|
2374
|
+
step.activityState = supervisorPreviousActivity;
|
|
2375
|
+
syncAggregateActivityState();
|
|
2376
|
+
}
|
|
2292
2377
|
syncTopLevelCurrentTool();
|
|
2293
2378
|
} else if (event.type === "tool_result_end" && event.message) {
|
|
2294
2379
|
const toolSnapshot = pendingToolResults[flatIndex];
|
|
@@ -2602,7 +2687,7 @@ async function runSubagent(
|
|
|
2602
2687
|
if (materialized.parallel.length > 1 && step.parallel.outputPath && !step.parallel.namespaceOutputPath) {
|
|
2603
2688
|
throw new DynamicFanoutError(`Dynamic chain step ${stepIndex + 1} materialized ${materialized.parallel.length} items that resolve output to the same path: ${step.parallel.outputPath}. Remove the explicit output path or use an inherited relative agent output so each item can be isolated.`);
|
|
2604
2689
|
}
|
|
2605
|
-
if (materialized.collectedOnEmpty) validateDynamicCollection(step.collect.outputSchema, materialized.collectedOnEmpty);
|
|
2690
|
+
if (materialized.collectedOnEmpty) await validateDynamicCollection(step.collect.outputSchema, materialized.collectedOnEmpty);
|
|
2606
2691
|
} catch (error) {
|
|
2607
2692
|
const now = Date.now();
|
|
2608
2693
|
const message = error instanceof DynamicFanoutError ? error.message : error instanceof Error ? error.message : String(error);
|
|
@@ -2621,7 +2706,7 @@ async function runSubagent(
|
|
|
2621
2706
|
statusPayload.lastUpdate = now;
|
|
2622
2707
|
markDynamicGraphGroup(stepIndex, "failed", message);
|
|
2623
2708
|
writeStatusPayload();
|
|
2624
|
-
results.push({ agent: step.parallel.agent, output: message, error: message, success: false, exitCode: 1 });
|
|
2709
|
+
results.push({ agent: step.parallel.agent, context: step.parallel.context, output: message, error: message, success: false, exitCode: 1 });
|
|
2625
2710
|
break;
|
|
2626
2711
|
}
|
|
2627
2712
|
|
|
@@ -2633,6 +2718,7 @@ async function runSubagent(
|
|
|
2633
2718
|
mode: config.mode,
|
|
2634
2719
|
async: true,
|
|
2635
2720
|
dynamicGroup: true,
|
|
2721
|
+
agentContract: step.agentContract,
|
|
2636
2722
|
});
|
|
2637
2723
|
|
|
2638
2724
|
if (materialized.parallel.length === 0) {
|
|
@@ -2664,13 +2750,14 @@ async function runSubagent(
|
|
|
2664
2750
|
cwd,
|
|
2665
2751
|
signal: combinedAbortSignal([timeoutAbortController.signal, stopAbortController.signal]),
|
|
2666
2752
|
abortMessage: stopAbortController.signal.aborted ? stopMessage : timeoutMessage ?? "Subagent timed out.",
|
|
2753
|
+
reportOptional: isAgentContractV1(step.agentContract),
|
|
2667
2754
|
})
|
|
2668
2755
|
: undefined;
|
|
2669
2756
|
const groupStopped = stopped || stopAbortController.signal.aborted;
|
|
2670
2757
|
const groupTimedOut = !groupStopped && (timedOut || timeoutAbortController.signal.aborted);
|
|
2671
2758
|
const effectiveGroupAcceptance = groupTimedOut || groupStopped ? undefined : groupAcceptance;
|
|
2672
2759
|
if (placeholder && effectiveGroupAcceptance) placeholder.acceptance = effectiveGroupAcceptance;
|
|
2673
|
-
const groupAcceptanceFailure = effectiveGroupAcceptance ? acceptanceFailureMessage(effectiveGroupAcceptance) : undefined;
|
|
2760
|
+
const groupAcceptanceFailure = effectiveGroupAcceptance && (!isAgentContractV1(step.agentContract) || step.gateOn === "acceptance") ? acceptanceFailureMessage(effectiveGroupAcceptance) : undefined;
|
|
2674
2761
|
if (groupTimedOut || groupStopped || groupAcceptanceFailure) {
|
|
2675
2762
|
const errorMessage = groupStopped ? stopMessage : groupTimedOut ? timeoutMessage ?? "Subagent timed out." : groupAcceptanceFailure!;
|
|
2676
2763
|
statusPayload.state = groupStopped ? "stopped" : "failed";
|
|
@@ -2686,7 +2773,7 @@ async function runSubagent(
|
|
|
2686
2773
|
markDynamicGraphGroup(stepIndex, groupStopped ? "stopped" : "failed", errorMessage, effectiveGroupAcceptance);
|
|
2687
2774
|
statusPayload.lastUpdate = Date.now();
|
|
2688
2775
|
writeStatusPayload();
|
|
2689
|
-
results.push({ agent: step.parallel.agent, output: errorMessage, error: errorMessage, success: false, exitCode: 1, timedOut: groupTimedOut ? true : undefined, stopped: groupStopped ? true : undefined, acceptance: effectiveGroupAcceptance });
|
|
2776
|
+
results.push({ agent: step.parallel.agent, context: step.parallel.context, output: errorMessage, error: errorMessage, success: false, exitCode: 1, timedOut: groupTimedOut ? true : undefined, stopped: groupStopped ? true : undefined, acceptance: effectiveGroupAcceptance });
|
|
2690
2777
|
break;
|
|
2691
2778
|
}
|
|
2692
2779
|
flatIndex++;
|
|
@@ -2716,6 +2803,7 @@ async function runSubagent(
|
|
|
2716
2803
|
mode: config.mode,
|
|
2717
2804
|
async: true,
|
|
2718
2805
|
dynamic: step.parallel.acceptanceInput === undefined,
|
|
2806
|
+
agentContract: step.parallel.agentContract ?? step.agentContract,
|
|
2719
2807
|
}),
|
|
2720
2808
|
systemPrompt: step.parallel.namespaceOutputPath ? injectOutputPathSystemPrompt(step.parallel.systemPrompt ?? "", outputPath, step.parallel) : step.parallel.systemPrompt,
|
|
2721
2809
|
outputPath,
|
|
@@ -2735,10 +2823,12 @@ async function runSubagent(
|
|
|
2735
2823
|
const transcriptPath = resolveAsyncStepTranscriptPath({ artifactsDir, artifactConfig, runId: id, agent: task.agent, flatIndex: groupStartFlatIndex + itemIndex, flatStepCount: dynamicFlatStepCount });
|
|
2736
2824
|
return {
|
|
2737
2825
|
agent: task.agent,
|
|
2826
|
+
...(task.context ? { context: task.context } : {}),
|
|
2738
2827
|
phase: task.phase ?? step.phase,
|
|
2739
2828
|
label: task.label,
|
|
2740
2829
|
outputName: undefined,
|
|
2741
2830
|
structured: Boolean(task.structuredOutputSchema),
|
|
2831
|
+
...(task.agentContract ? { agentContract: task.agentContract } : {}),
|
|
2742
2832
|
status: "pending",
|
|
2743
2833
|
...(task.sessionFile ? { sessionFile: task.sessionFile } : {}),
|
|
2744
2834
|
...(transcriptPath ? { transcriptPath } : {}),
|
|
@@ -2798,9 +2888,9 @@ async function runSubagent(
|
|
|
2798
2888
|
let aborted = false;
|
|
2799
2889
|
const parallelResults = await mapConcurrent(dynamicSteps, concurrency, async (task, taskIdx) => {
|
|
2800
2890
|
const fi = groupStartFlatIndex + taskIdx;
|
|
2801
|
-
if (timedOut) return timedOutStepResult(task.agent);
|
|
2802
|
-
if (stopped) return stoppedStepResult(task.agent);
|
|
2803
|
-
if (interrupted) return pausedStepResult(task.agent);
|
|
2891
|
+
if (timedOut) return timedOutStepResult(task.agent, task.context);
|
|
2892
|
+
if (stopped) return stoppedStepResult(task.agent, task.context);
|
|
2893
|
+
if (interrupted) return pausedStepResult(task.agent, task.context);
|
|
2804
2894
|
if (aborted && failFast) {
|
|
2805
2895
|
const skippedAt = Date.now();
|
|
2806
2896
|
statusPayload.steps[fi].status = "failed";
|
|
@@ -2811,7 +2901,7 @@ async function runSubagent(
|
|
|
2811
2901
|
statusPayload.steps[fi].exitCode = -1;
|
|
2812
2902
|
statusPayload.lastUpdate = skippedAt;
|
|
2813
2903
|
writeStatusPayload();
|
|
2814
|
-
return { agent: task.agent, output: "(skipped — fail-fast)", exitCode: -1 as number | null, skipped: true };
|
|
2904
|
+
return { agent: task.agent, context: task.context, output: "(skipped — fail-fast)", exitCode: -1 as number | null, skipped: true };
|
|
2815
2905
|
}
|
|
2816
2906
|
const taskStartTime = Date.now();
|
|
2817
2907
|
statusPayload.currentStep = fi;
|
|
@@ -2883,6 +2973,10 @@ async function runSubagent(
|
|
|
2883
2973
|
statusPayload.steps[fi].error = stopped || childStopped ? stopMessage : timedOut ? (timeoutMessage ?? "Subagent timed out.") : singleResult.error;
|
|
2884
2974
|
statusPayload.steps[fi].transcriptPath = singleResult.transcriptPath ?? statusPayload.steps[fi].transcriptPath;
|
|
2885
2975
|
statusPayload.steps[fi].transcriptError = singleResult.transcriptError;
|
|
2976
|
+
statusPayload.steps[fi].agentContract = singleResult.agentContract;
|
|
2977
|
+
statusPayload.steps[fi].effects = singleResult.effects;
|
|
2978
|
+
statusPayload.steps[fi].execution = singleResult.execution;
|
|
2979
|
+
statusPayload.steps[fi].review = singleResult.review;
|
|
2886
2980
|
statusPayload.steps[fi].structuredOutput = singleResult.structuredOutput;
|
|
2887
2981
|
statusPayload.steps[fi].structuredOutputPath = singleResult.structuredOutputPath;
|
|
2888
2982
|
statusPayload.steps[fi].structuredOutputSchemaPath = singleResult.structuredOutputSchemaPath;
|
|
@@ -2903,6 +2997,8 @@ async function runSubagent(
|
|
|
2903
2997
|
for (const pr of parallelResults) {
|
|
2904
2998
|
results.push({
|
|
2905
2999
|
agent: pr.agent,
|
|
3000
|
+
context: pr.context,
|
|
3001
|
+
agentContract: pr.agentContract,
|
|
2906
3002
|
output: pr.output,
|
|
2907
3003
|
error: pr.error,
|
|
2908
3004
|
protocolError: pr.protocolError,
|
|
@@ -2926,6 +3022,9 @@ async function runSubagent(
|
|
|
2926
3022
|
artifactPaths: pr.artifactPaths,
|
|
2927
3023
|
transcriptPath: pr.transcriptPath,
|
|
2928
3024
|
transcriptError: pr.transcriptError,
|
|
3025
|
+
effects: pr.effects,
|
|
3026
|
+
execution: pr.execution,
|
|
3027
|
+
review: pr.review,
|
|
2929
3028
|
structuredOutput: pr.structuredOutput,
|
|
2930
3029
|
structuredOutputPath: pr.structuredOutputPath,
|
|
2931
3030
|
structuredOutputSchemaPath: pr.structuredOutputSchemaPath,
|
|
@@ -2935,9 +3034,20 @@ async function runSubagent(
|
|
|
2935
3034
|
}
|
|
2936
3035
|
const collection = collectDynamicResults(step as Parameters<typeof collectDynamicResults>[0], materialized.items, parallelResults);
|
|
2937
3036
|
const failures = parallelResults.filter((result) => result.exitCode !== 0 && result.exitCode !== -1);
|
|
2938
|
-
|
|
3037
|
+
const acceptanceFailures = parallelResults
|
|
3038
|
+
.map((result, originalIndex) => ({ result, originalIndex, task: dynamicSteps[originalIndex] }))
|
|
3039
|
+
.filter(({ result, task }) => isAgentContractV1(task?.agentContract ?? step.agentContract) && task?.gateOn === "acceptance" && result.acceptance?.status === "rejected");
|
|
3040
|
+
if (acceptanceFailures.length > 0) {
|
|
3041
|
+
const message = acceptanceFailures
|
|
3042
|
+
.map(({ result, originalIndex }) => `Dynamic item ${originalIndex + 1} (${result.agent}, key ${materialized.items[originalIndex]?.key ?? originalIndex}) acceptance rejected: ${acceptanceFailureMessage(result.acceptance) ?? "acceptance rejected"}`)
|
|
3043
|
+
.join("\n");
|
|
3044
|
+
results.push({ agent: step.parallel.agent, context: step.parallel.context, output: message, error: message, success: false, exitCode: 1, structuredOutput: collection });
|
|
3045
|
+
statusPayload.error = message;
|
|
3046
|
+
markDynamicGraphGroup(stepIndex, "failed", message);
|
|
3047
|
+
}
|
|
3048
|
+
if (failures.length === 0 && acceptanceFailures.length === 0) {
|
|
2939
3049
|
try {
|
|
2940
|
-
validateDynamicCollection(step.collect.outputSchema, collection);
|
|
3050
|
+
await validateDynamicCollection(step.collect.outputSchema, collection);
|
|
2941
3051
|
outputs[step.collect.as] = {
|
|
2942
3052
|
text: JSON.stringify(collection),
|
|
2943
3053
|
structured: collection,
|
|
@@ -2956,12 +3066,13 @@ async function runSubagent(
|
|
|
2956
3066
|
cwd,
|
|
2957
3067
|
signal: combinedAbortSignal([timeoutAbortController.signal, stopAbortController.signal]),
|
|
2958
3068
|
abortMessage: stopAbortController.signal.aborted ? stopMessage : timeoutMessage ?? "Subagent timed out.",
|
|
3069
|
+
reportOptional: isAgentContractV1(step.agentContract),
|
|
2959
3070
|
})
|
|
2960
3071
|
: undefined;
|
|
2961
3072
|
const groupStopped = stopped || stopAbortController.signal.aborted;
|
|
2962
3073
|
const groupTimedOut = !groupStopped && (timedOut || timeoutAbortController.signal.aborted);
|
|
2963
3074
|
const effectiveGroupAcceptance = groupTimedOut || groupStopped ? undefined : groupAcceptance;
|
|
2964
|
-
const groupAcceptanceFailure = effectiveDynamicGroupAcceptance.explicit && effectiveGroupAcceptance ? acceptanceFailureMessage(effectiveGroupAcceptance) : undefined;
|
|
3075
|
+
const groupAcceptanceFailure = effectiveDynamicGroupAcceptance.explicit && effectiveGroupAcceptance && (!isAgentContractV1(step.agentContract) || step.gateOn === "acceptance") ? acceptanceFailureMessage(effectiveGroupAcceptance) : undefined;
|
|
2965
3076
|
const groupError = groupStopped ? stopMessage : groupTimedOut ? timeoutMessage ?? "Subagent timed out." : groupAcceptanceFailure;
|
|
2966
3077
|
markDynamicGraphGroup(stepIndex, groupError ? groupStopped ? "stopped" : "failed" : "completed", groupError, effectiveGroupAcceptance);
|
|
2967
3078
|
if (groupError) {
|
|
@@ -2981,7 +3092,7 @@ async function runSubagent(
|
|
|
2981
3092
|
}
|
|
2982
3093
|
} catch (error) {
|
|
2983
3094
|
const message = error instanceof DynamicFanoutError ? error.message : error instanceof Error ? error.message : String(error);
|
|
2984
|
-
results.push({ agent: step.parallel.agent, output: message, error: message, success: false, exitCode: 1, structuredOutput: collection });
|
|
3095
|
+
results.push({ agent: step.parallel.agent, context: step.parallel.context, output: message, error: message, success: false, exitCode: 1, structuredOutput: collection });
|
|
2985
3096
|
statusPayload.error = message;
|
|
2986
3097
|
markDynamicGraphGroup(stepIndex, "failed", message);
|
|
2987
3098
|
}
|
|
@@ -3001,7 +3112,7 @@ async function runSubagent(
|
|
|
3001
3112
|
ts: Date.now(),
|
|
3002
3113
|
runId: id,
|
|
3003
3114
|
stepIndex,
|
|
3004
|
-
success: failures.length === 0,
|
|
3115
|
+
success: failures.length === 0 && acceptanceFailures.length === 0,
|
|
3005
3116
|
}));
|
|
3006
3117
|
if (failures.length > 0) markDynamicGraphGroup(stepIndex, "failed", failures[0]?.error ?? "Dynamic fanout child failed.");
|
|
3007
3118
|
statusPayload.lastUpdate = Date.now();
|
|
@@ -3017,6 +3128,7 @@ async function runSubagent(
|
|
|
3017
3128
|
const groupStartFlatIndex = flatIndex;
|
|
3018
3129
|
let aborted = false;
|
|
3019
3130
|
let worktreeSetup: WorktreeSetup | undefined;
|
|
3131
|
+
let worktreeFinalized = false;
|
|
3020
3132
|
if (group.worktree) {
|
|
3021
3133
|
const worktreeTaskCwdConflict = findWorktreeTaskCwdConflict(group.parallel, cwd);
|
|
3022
3134
|
if (worktreeTaskCwdConflict) {
|
|
@@ -3085,9 +3197,9 @@ async function runSubagent(
|
|
|
3085
3197
|
concurrency,
|
|
3086
3198
|
async (task, taskIdx) => {
|
|
3087
3199
|
const fi = groupStartFlatIndex + taskIdx;
|
|
3088
|
-
if (timedOut) return timedOutStepResult(task.agent);
|
|
3089
|
-
if (stopped) return stoppedStepResult(task.agent);
|
|
3090
|
-
if (interrupted) return pausedStepResult(task.agent);
|
|
3200
|
+
if (timedOut) return timedOutStepResult(task.agent, task.context);
|
|
3201
|
+
if (stopped) return stoppedStepResult(task.agent, task.context);
|
|
3202
|
+
if (interrupted) return pausedStepResult(task.agent, task.context);
|
|
3091
3203
|
if (aborted && failFast) {
|
|
3092
3204
|
const skippedAt = Date.now();
|
|
3093
3205
|
statusPayload.steps[fi].status = "failed";
|
|
@@ -3102,7 +3214,7 @@ async function runSubagent(
|
|
|
3102
3214
|
appendJsonl(eventsPath, JSON.stringify({
|
|
3103
3215
|
type: "subagent.step.failed", ts: skippedAt, runId: id, stepIndex: fi, agent: task.agent, exitCode: -1, durationMs: 0,
|
|
3104
3216
|
}));
|
|
3105
|
-
return { agent: task.agent, output: "(skipped — fail-fast)", exitCode: -1 as number | null, skipped: true };
|
|
3217
|
+
return { agent: task.agent, context: task.context, output: "(skipped — fail-fast)", exitCode: -1 as number | null, skipped: true };
|
|
3106
3218
|
}
|
|
3107
3219
|
|
|
3108
3220
|
const taskStartTime = Date.now();
|
|
@@ -3192,6 +3304,10 @@ async function runSubagent(
|
|
|
3192
3304
|
statusPayload.steps[fi].error = stopped || childStopped ? stopMessage : timedOut ? (timeoutMessage ?? "Subagent timed out.") : singleResult.error;
|
|
3193
3305
|
statusPayload.steps[fi].transcriptPath = singleResult.transcriptPath ?? statusPayload.steps[fi].transcriptPath;
|
|
3194
3306
|
statusPayload.steps[fi].transcriptError = singleResult.transcriptError;
|
|
3307
|
+
statusPayload.steps[fi].agentContract = singleResult.agentContract;
|
|
3308
|
+
statusPayload.steps[fi].effects = singleResult.effects;
|
|
3309
|
+
statusPayload.steps[fi].execution = singleResult.execution;
|
|
3310
|
+
statusPayload.steps[fi].review = singleResult.review;
|
|
3195
3311
|
statusPayload.steps[fi].structuredOutput = singleResult.structuredOutput;
|
|
3196
3312
|
statusPayload.steps[fi].structuredOutputPath = singleResult.structuredOutputPath;
|
|
3197
3313
|
statusPayload.steps[fi].structuredOutputSchemaPath = singleResult.structuredOutputSchemaPath;
|
|
@@ -3248,6 +3364,8 @@ async function runSubagent(
|
|
|
3248
3364
|
for (const pr of parallelResults) {
|
|
3249
3365
|
results.push({
|
|
3250
3366
|
agent: pr.agent,
|
|
3367
|
+
context: pr.context,
|
|
3368
|
+
agentContract: pr.agentContract,
|
|
3251
3369
|
output: pr.output,
|
|
3252
3370
|
error: pr.error,
|
|
3253
3371
|
protocolError: pr.protocolError,
|
|
@@ -3271,6 +3389,9 @@ async function runSubagent(
|
|
|
3271
3389
|
artifactPaths: pr.artifactPaths,
|
|
3272
3390
|
transcriptPath: pr.transcriptPath,
|
|
3273
3391
|
transcriptError: pr.transcriptError,
|
|
3392
|
+
effects: pr.effects,
|
|
3393
|
+
execution: pr.execution,
|
|
3394
|
+
review: pr.review,
|
|
3274
3395
|
structuredOutput: pr.structuredOutput,
|
|
3275
3396
|
structuredOutputPath: pr.structuredOutputPath,
|
|
3276
3397
|
structuredOutputSchemaPath: pr.structuredOutputSchemaPath,
|
|
@@ -3298,21 +3419,62 @@ async function runSubagent(
|
|
|
3298
3419
|
attemptedModels: r.attemptedModels,
|
|
3299
3420
|
})),
|
|
3300
3421
|
);
|
|
3301
|
-
|
|
3422
|
+
if (worktreeSetup) {
|
|
3423
|
+
const captured = captureParallelWorktreeDiffs(worktreeSetup, asyncDir, stepIndex, group);
|
|
3424
|
+
if (captured.summary) previousOutput = `${previousOutput}\n\n${captured.summary}`;
|
|
3425
|
+
worktreeFinalized = true;
|
|
3426
|
+
const cleanup = cleanupWorktrees(worktreeSetup);
|
|
3427
|
+
try {
|
|
3428
|
+
statusPayload.parallelHandoff = writeParallelHandoffGroup({
|
|
3429
|
+
manifestPath: parallelHandoffPath(asyncDir),
|
|
3430
|
+
runId: id,
|
|
3431
|
+
mode: (config.resultMode ?? statusPayload.mode) === "parallel" ? "parallel" : "chain",
|
|
3432
|
+
source: "async",
|
|
3433
|
+
cwd,
|
|
3434
|
+
stepIndex,
|
|
3435
|
+
flatStartIndex: groupStartFlatIndex,
|
|
3436
|
+
setup: worktreeSetup,
|
|
3437
|
+
diffs: captured.diffs,
|
|
3438
|
+
cleanup,
|
|
3439
|
+
results: parallelResults.map((result) => ({
|
|
3440
|
+
agent: result.agent,
|
|
3441
|
+
status: result.stopped ? "stopped" : result.interrupted ? "paused" : result.exitCode === 0 ? "completed" : "failed",
|
|
3442
|
+
summary: result.output || result.error || "(no output)",
|
|
3443
|
+
...(result.artifactPaths?.outputPath ? { outputPath: result.artifactPaths.outputPath } : {}),
|
|
3444
|
+
...(result.structuredOutput !== undefined ? { structuredOutput: result.structuredOutput } : {}),
|
|
3445
|
+
...(result.structuredOutputPath ? { structuredOutputPath: result.structuredOutputPath } : {}),
|
|
3446
|
+
...(result.sessionFile ? { sessionPath: result.sessionFile } : {}),
|
|
3447
|
+
})),
|
|
3448
|
+
});
|
|
3449
|
+
previousOutput = `${previousOutput}\n\n${formatParallelHandoffReference(statusPayload.parallelHandoff)}`;
|
|
3450
|
+
} catch (error) {
|
|
3451
|
+
previousOutput = `${previousOutput}\n\n${formatParallelHandoffError(error)}`;
|
|
3452
|
+
}
|
|
3453
|
+
writeStatusPayload();
|
|
3454
|
+
}
|
|
3302
3455
|
|
|
3303
3456
|
appendJsonl(eventsPath, JSON.stringify({
|
|
3304
3457
|
type: "subagent.parallel.completed",
|
|
3305
3458
|
ts: Date.now(),
|
|
3306
3459
|
runId: id,
|
|
3307
3460
|
stepIndex,
|
|
3308
|
-
success: parallelResults.every((r) => r.exitCode === 0 || r.exitCode === -1)
|
|
3461
|
+
success: parallelResults.every((r) => r.exitCode === 0 || r.exitCode === -1)
|
|
3462
|
+
&& parallelResults.every((result, index) => !(isAgentContractV1(group.parallel[index]?.agentContract) && group.parallel[index]?.gateOn === "acceptance" && result.acceptance?.status === "rejected")),
|
|
3309
3463
|
}));
|
|
3310
3464
|
|
|
3465
|
+
const acceptanceGateFailure = parallelResults
|
|
3466
|
+
.map((result, index) => ({ result, index, task: group.parallel[index] }))
|
|
3467
|
+
.find(({ result, task }) => isAgentContractV1(task?.agentContract) && task?.gateOn === "acceptance" && result.acceptance?.status === "rejected");
|
|
3468
|
+
if (acceptanceGateFailure) {
|
|
3469
|
+
statusPayload.error = acceptanceFailureMessage(acceptanceGateFailure.result.acceptance) ?? "Parallel acceptance gate rejected the step.";
|
|
3470
|
+
writeStatusPayload();
|
|
3471
|
+
break;
|
|
3472
|
+
}
|
|
3311
3473
|
if (parallelResults.some((r) => r.exitCode !== 0 && r.exitCode !== -1)) {
|
|
3312
3474
|
break;
|
|
3313
3475
|
}
|
|
3314
3476
|
} finally {
|
|
3315
|
-
if (worktreeSetup) cleanupWorktrees(worktreeSetup);
|
|
3477
|
+
if (worktreeSetup && !worktreeFinalized) cleanupWorktrees(worktreeSetup);
|
|
3316
3478
|
}
|
|
3317
3479
|
} else {
|
|
3318
3480
|
const seqStep = step as SubagentStep;
|
|
@@ -3376,6 +3538,8 @@ async function runSubagent(
|
|
|
3376
3538
|
const childStopped = singleResult.stopped === true;
|
|
3377
3539
|
results.push({
|
|
3378
3540
|
agent: singleResult.agent,
|
|
3541
|
+
context: singleResult.context,
|
|
3542
|
+
agentContract: singleResult.agentContract,
|
|
3379
3543
|
output: stopped || childStopped ? stopMessage : timedOut ? (timeoutMessage ?? "Subagent timed out.") : singleResult.output,
|
|
3380
3544
|
error: stopped || childStopped ? stopMessage : timedOut ? (timeoutMessage ?? "Subagent timed out.") : singleResult.error,
|
|
3381
3545
|
protocolError: singleResult.protocolError,
|
|
@@ -3390,6 +3554,9 @@ async function runSubagent(
|
|
|
3390
3554
|
artifactPaths: singleResult.artifactPaths,
|
|
3391
3555
|
transcriptPath: singleResult.transcriptPath,
|
|
3392
3556
|
transcriptError: singleResult.transcriptError,
|
|
3557
|
+
effects: singleResult.effects,
|
|
3558
|
+
execution: singleResult.execution,
|
|
3559
|
+
review: singleResult.review,
|
|
3393
3560
|
structuredOutput: singleResult.structuredOutput,
|
|
3394
3561
|
structuredOutputPath: singleResult.structuredOutputPath,
|
|
3395
3562
|
structuredOutputSchemaPath: singleResult.structuredOutputSchemaPath,
|
|
@@ -3460,6 +3627,10 @@ async function runSubagent(
|
|
|
3460
3627
|
statusPayload.steps[flatIndex].error = stopped || childStopped ? stopMessage : timedOut ? (timeoutMessage ?? "Subagent timed out.") : singleResult.error;
|
|
3461
3628
|
statusPayload.steps[flatIndex].transcriptPath = singleResult.transcriptPath ?? statusPayload.steps[flatIndex].transcriptPath;
|
|
3462
3629
|
statusPayload.steps[flatIndex].transcriptError = singleResult.transcriptError;
|
|
3630
|
+
statusPayload.steps[flatIndex].agentContract = singleResult.agentContract;
|
|
3631
|
+
statusPayload.steps[flatIndex].effects = singleResult.effects;
|
|
3632
|
+
statusPayload.steps[flatIndex].execution = singleResult.execution;
|
|
3633
|
+
statusPayload.steps[flatIndex].review = singleResult.review;
|
|
3463
3634
|
statusPayload.steps[flatIndex].structuredOutput = singleResult.structuredOutput;
|
|
3464
3635
|
statusPayload.steps[flatIndex].structuredOutputPath = singleResult.structuredOutputPath;
|
|
3465
3636
|
statusPayload.steps[flatIndex].structuredOutputSchemaPath = singleResult.structuredOutputSchemaPath;
|
|
@@ -3497,6 +3668,11 @@ async function runSubagent(
|
|
|
3497
3668
|
}
|
|
3498
3669
|
|
|
3499
3670
|
flatIndex++;
|
|
3671
|
+
if (isAgentContractV1(seqStep.agentContract) && seqStep.gateOn === "acceptance" && singleResult.acceptance?.status === "rejected") {
|
|
3672
|
+
statusPayload.error = acceptanceFailureMessage(singleResult.acceptance) ?? "Chain acceptance gate rejected the step.";
|
|
3673
|
+
writeStatusPayload();
|
|
3674
|
+
break;
|
|
3675
|
+
}
|
|
3500
3676
|
if (singleResult.exitCode !== 0) {
|
|
3501
3677
|
break;
|
|
3502
3678
|
}
|
|
@@ -3567,7 +3743,7 @@ async function runSubagent(
|
|
|
3567
3743
|
clearTimeout(timeoutTimer);
|
|
3568
3744
|
timeoutTimer = undefined;
|
|
3569
3745
|
}
|
|
3570
|
-
statusPayload.state = stopped ? "stopped" : timedOut || turnBudgetExceeded ? "failed" : interrupted ? "paused" : results.every((r) => r.success) ? "complete" : "failed";
|
|
3746
|
+
statusPayload.state = stopped ? "stopped" : timedOut || turnBudgetExceeded || statusPayload.error ? "failed" : interrupted ? "paused" : results.every((r) => r.success) ? "complete" : "failed";
|
|
3571
3747
|
closeSteerInbox(asyncDir, statusPayload.state);
|
|
3572
3748
|
disposeControlInbox();
|
|
3573
3749
|
for (const request of consumeSteerRequests(asyncDir)) deliverSteerRequest(request);
|
|
@@ -3663,6 +3839,7 @@ async function runSubagent(
|
|
|
3663
3839
|
...(stopped ? { stopped: true, error: stopMessage } : timedOut ? { timedOut: true, error: timeoutMessage ?? "Subagent timed out." } : turnBudgetExceeded ? { error: statusPayload.error ?? "Subagent exceeded turn budget." } : {}),
|
|
3664
3840
|
results: results.map((r) => ({
|
|
3665
3841
|
agent: r.agent,
|
|
3842
|
+
context: r.context,
|
|
3666
3843
|
output: r.output,
|
|
3667
3844
|
error: r.error,
|
|
3668
3845
|
protocolError: r.protocolError,
|
|
@@ -3686,6 +3863,10 @@ async function runSubagent(
|
|
|
3686
3863
|
truncated: r.truncated,
|
|
3687
3864
|
transcriptPath: r.transcriptPath,
|
|
3688
3865
|
transcriptError: r.transcriptError,
|
|
3866
|
+
agentContract: r.agentContract,
|
|
3867
|
+
execution: r.execution,
|
|
3868
|
+
review: r.review,
|
|
3869
|
+
effects: r.effects,
|
|
3689
3870
|
structuredOutput: r.structuredOutput,
|
|
3690
3871
|
structuredOutputPath: r.structuredOutputPath,
|
|
3691
3872
|
structuredOutputSchemaPath: r.structuredOutputSchemaPath,
|
|
@@ -3694,6 +3875,7 @@ async function runSubagent(
|
|
|
3694
3875
|
})),
|
|
3695
3876
|
outputs,
|
|
3696
3877
|
workflowGraph: statusPayload.workflowGraph,
|
|
3878
|
+
parallelHandoff: statusPayload.parallelHandoff,
|
|
3697
3879
|
exitCode: stopped || timedOut || turnBudgetExceeded ? 1 : interrupted || results.every((r) => r.success) ? 0 : 1,
|
|
3698
3880
|
timestamp: runEndedAt,
|
|
3699
3881
|
durationMs: runEndedAt - overallStartTime,
|