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
|
@@ -33,9 +33,11 @@ import {
|
|
|
33
33
|
import { discoverAvailableSkills, normalizeSkillInput } from "../../agents/skills.ts";
|
|
34
34
|
import { INTERCOM_BRIDGE_MARKER } from "../../intercom/intercom-bridge.ts";
|
|
35
35
|
import { runSync } from "./execution.ts";
|
|
36
|
+
import { beginForegroundChild, finishForegroundChild, updateForegroundChild } from "./foreground-control.ts";
|
|
36
37
|
import { buildChainSummary } from "../../shared/formatters.ts";
|
|
37
38
|
import { compactForegroundDetails, getSingleResultOutput, mapConcurrent, resolveChildCwd, sumResultsCost, sumResultsUsage } from "../../shared/utils.ts";
|
|
38
39
|
import { DEFAULT_GLOBAL_CONCURRENCY_LIMIT, Semaphore } from "../shared/parallel-utils.ts";
|
|
40
|
+
import { formatParallelHandoffError, formatParallelHandoffReference, parallelHandoffPath, writeParallelHandoffGroup } from "../shared/parallel-handoff.ts";
|
|
39
41
|
import { recordRun } from "../shared/run-history.ts";
|
|
40
42
|
import {
|
|
41
43
|
cleanupWorktrees,
|
|
@@ -48,11 +50,13 @@ import {
|
|
|
48
50
|
} from "../shared/worktree.ts";
|
|
49
51
|
import {
|
|
50
52
|
type ActivityState,
|
|
53
|
+
type AgentContract,
|
|
51
54
|
type AgentProgress,
|
|
52
55
|
type ArtifactConfig,
|
|
53
56
|
type ArtifactPaths,
|
|
54
57
|
type ControlEvent,
|
|
55
58
|
type Details,
|
|
59
|
+
type ForegroundRunControl,
|
|
56
60
|
type IntercomEventBus,
|
|
57
61
|
type NestedRouteInfo,
|
|
58
62
|
type ResolvedControlConfig,
|
|
@@ -71,8 +75,10 @@ import { ChainOutputValidationError, outputEntryFromResult, resolveOutputReferen
|
|
|
71
75
|
import { createStructuredOutputRuntime } from "../shared/structured-output.ts";
|
|
72
76
|
import { collectDynamicResults, DynamicFanoutError, materializeDynamicParallelStep, validateDynamicCollection, type DynamicCollectedResult } from "../shared/dynamic-fanout.ts";
|
|
73
77
|
import { acceptanceFailureMessage, aggregateAcceptanceReport, evaluateAcceptance, resolveEffectiveAcceptance } from "../shared/acceptance.ts";
|
|
78
|
+
import { isAgentContractV1 } from "../shared/agent-contract.ts";
|
|
74
79
|
import type { ChainOutputMap } from "../../shared/types.ts";
|
|
75
80
|
import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
|
|
81
|
+
import type { ContextMode } from "../shared/context-mode.ts";
|
|
76
82
|
|
|
77
83
|
interface ChainExecutionDetailsInput {
|
|
78
84
|
results: SingleResult[];
|
|
@@ -89,6 +95,7 @@ interface ChainExecutionDetailsInput {
|
|
|
89
95
|
currentFlatIndex?: number;
|
|
90
96
|
dynamicChildren?: Record<number, Array<{ agent: string; label?: string; flatIndex: number; itemKey: string; outputName?: string; structured?: boolean; error?: string }>>;
|
|
91
97
|
dynamicGroupStatuses?: Record<number, { status: "pending" | "running" | "completed" | "failed" | "paused" | "stopped" | "detached"; error?: string; acceptance?: SingleResult["acceptance"] }>;
|
|
98
|
+
parallelHandoff?: Details["parallelHandoff"];
|
|
92
99
|
}
|
|
93
100
|
|
|
94
101
|
interface ParallelChainRunInput {
|
|
@@ -111,6 +118,7 @@ interface ParallelChainRunInput {
|
|
|
111
118
|
sessionFileForIndex?: (idx?: number) => string | undefined;
|
|
112
119
|
sessionFileForTask?: (agentName: string, idx?: number, modelOverride?: string) => string | undefined;
|
|
113
120
|
thinkingOverrideForTask?: (agentName: string, idx?: number, modelOverride?: string) => AgentConfig["thinking"] | undefined;
|
|
121
|
+
contextForAgent?: (agentName: string) => ContextMode;
|
|
114
122
|
shareEnabled: boolean;
|
|
115
123
|
artifactConfig: ArtifactConfig;
|
|
116
124
|
artifactsDir: string;
|
|
@@ -118,22 +126,10 @@ interface ParallelChainRunInput {
|
|
|
118
126
|
onUpdate?: (r: AgentToolResult<Details>) => void;
|
|
119
127
|
onControlEvent?: (event: ControlEvent) => void;
|
|
120
128
|
controlConfig: ResolvedControlConfig;
|
|
129
|
+
agentContract?: AgentContract;
|
|
121
130
|
childIntercomTarget?: (agent: string, index: number) => string | undefined;
|
|
122
131
|
orchestratorIntercomTarget?: string;
|
|
123
|
-
foregroundControl?:
|
|
124
|
-
updatedAt: number;
|
|
125
|
-
currentAgent?: string;
|
|
126
|
-
currentIndex?: number;
|
|
127
|
-
currentActivityState?: ActivityState;
|
|
128
|
-
lastActivityAt?: number;
|
|
129
|
-
currentTool?: string;
|
|
130
|
-
currentToolStartedAt?: number;
|
|
131
|
-
currentPath?: string;
|
|
132
|
-
turnCount?: number;
|
|
133
|
-
tokens?: number;
|
|
134
|
-
toolCount?: number;
|
|
135
|
-
interrupt?: () => boolean;
|
|
136
|
-
};
|
|
132
|
+
foregroundControl?: ForegroundRunControl;
|
|
137
133
|
results: SingleResult[];
|
|
138
134
|
allProgress: AgentProgress[];
|
|
139
135
|
outputs: ChainOutputMap;
|
|
@@ -167,6 +163,7 @@ function buildChainExecutionDetails(input: ChainExecutionDetailsInput): Details
|
|
|
167
163
|
outputs: input.outputs,
|
|
168
164
|
totalChildUsage: sumResultsUsage(input.results),
|
|
169
165
|
totalCost: sumResultsCost(input.results),
|
|
166
|
+
...(input.parallelHandoff ? { parallelHandoff: input.parallelHandoff } : {}),
|
|
170
167
|
workflowGraph: buildWorkflowGraphSnapshot({
|
|
171
168
|
runId: input.runId,
|
|
172
169
|
mode: "chain",
|
|
@@ -200,17 +197,48 @@ function ensureParallelProgressFile(
|
|
|
200
197
|
return true;
|
|
201
198
|
}
|
|
202
199
|
|
|
203
|
-
function
|
|
204
|
-
output: string
|
|
205
|
-
worktreeSetup: WorktreeSetup
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
200
|
+
function finalizeParallelWorktreeHandoff(input: {
|
|
201
|
+
output: string;
|
|
202
|
+
worktreeSetup: WorktreeSetup;
|
|
203
|
+
artifactsDir: string;
|
|
204
|
+
runId: string;
|
|
205
|
+
cwd: string;
|
|
206
|
+
stepIndex: number;
|
|
207
|
+
flatStartIndex: number;
|
|
208
|
+
agents: string[];
|
|
209
|
+
results: SingleResult[];
|
|
210
|
+
}): { output: string; reference?: NonNullable<Details["parallelHandoff"]> } {
|
|
211
|
+
const diffs = diffWorktrees(input.worktreeSetup, input.agents, path.join(input.artifactsDir, "worktree-diffs", input.runId, `step-${input.stepIndex}`));
|
|
212
|
+
const cleanup = cleanupWorktrees(input.worktreeSetup);
|
|
211
213
|
const diffSummary = formatWorktreeDiffSummary(diffs);
|
|
212
|
-
|
|
213
|
-
|
|
214
|
+
try {
|
|
215
|
+
const reference = writeParallelHandoffGroup({
|
|
216
|
+
manifestPath: parallelHandoffPath(input.artifactsDir, input.runId),
|
|
217
|
+
runId: input.runId,
|
|
218
|
+
mode: "chain",
|
|
219
|
+
source: "foreground",
|
|
220
|
+
cwd: input.cwd,
|
|
221
|
+
stepIndex: input.stepIndex,
|
|
222
|
+
flatStartIndex: input.flatStartIndex,
|
|
223
|
+
setup: input.worktreeSetup,
|
|
224
|
+
diffs,
|
|
225
|
+
cleanup,
|
|
226
|
+
results: input.results.map((result) => ({
|
|
227
|
+
agent: result.agent,
|
|
228
|
+
status: result.stopped ? "stopped" : result.detached ? "detached" : result.interrupted ? "paused" : result.exitCode === 0 ? "completed" : "failed",
|
|
229
|
+
summary: getSingleResultOutput(result) || result.error || "(no output)",
|
|
230
|
+
...(result.artifactPaths?.outputPath ? { outputPath: result.artifactPaths.outputPath } : {}),
|
|
231
|
+
...(result.structuredOutput !== undefined ? { structuredOutput: result.structuredOutput } : {}),
|
|
232
|
+
...(result.structuredOutputPath ? { structuredOutputPath: result.structuredOutputPath } : {}),
|
|
233
|
+
...(result.sessionFile ? { sessionPath: result.sessionFile } : {}),
|
|
234
|
+
})),
|
|
235
|
+
});
|
|
236
|
+
const suffix = [diffSummary, formatParallelHandoffReference(reference)].filter(Boolean).join("\n\n");
|
|
237
|
+
return { output: suffix ? `${input.output}\n\n${suffix}` : input.output, reference };
|
|
238
|
+
} catch (error) {
|
|
239
|
+
const suffix = [diffSummary, formatParallelHandoffError(error)].filter(Boolean).join("\n\n");
|
|
240
|
+
return { output: suffix ? `${input.output}\n\n${suffix}` : input.output };
|
|
241
|
+
}
|
|
214
242
|
}
|
|
215
243
|
|
|
216
244
|
function resolveChainToolBudget(input: { stepBudget?: ToolBudgetConfig; runBudget?: ResolvedToolBudget; agentBudget?: ToolBudgetConfig; configBudget?: ToolBudgetConfig }): { toolBudget?: ResolvedToolBudget; error?: string } {
|
|
@@ -294,36 +322,38 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
|
|
|
294
322
|
: undefined;
|
|
295
323
|
taskStr = injectSingleOutputInstruction(taskStr, outputPath, taskAgentConfig);
|
|
296
324
|
const interruptController = new AbortController();
|
|
325
|
+
const childIndex = input.globalTaskIndex + taskIndex;
|
|
297
326
|
if (input.foregroundControl) {
|
|
298
|
-
input.foregroundControl
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
};
|
|
327
|
+
beginForegroundChild(input.foregroundControl, {
|
|
328
|
+
index: childIndex,
|
|
329
|
+
agent: task.agent,
|
|
330
|
+
description: cleanTask.trim(),
|
|
331
|
+
interrupt: () => {
|
|
332
|
+
if (interruptController.signal.aborted) return false;
|
|
333
|
+
interruptController.abort();
|
|
334
|
+
return true;
|
|
335
|
+
},
|
|
336
|
+
});
|
|
309
337
|
}
|
|
310
338
|
|
|
311
339
|
const structuredRuntime = task.outputSchema
|
|
312
340
|
? createStructuredOutputRuntime(task.outputSchema, path.join(input.chainDir, "structured-output"))
|
|
313
341
|
: undefined;
|
|
342
|
+
const agentContract = task.agentContract ?? input.step.agentContract ?? input.agentContract;
|
|
314
343
|
const result = await runSync(input.ctx.cwd, input.agents, task.agent, taskStr, {
|
|
315
344
|
parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
|
|
345
|
+
context: input.contextForAgent?.(task.agent),
|
|
316
346
|
cwd: taskCwd,
|
|
317
347
|
signal: input.signal,
|
|
318
348
|
interruptSignal: interruptController.signal,
|
|
319
349
|
allowIntercomDetach: taskAgentConfig?.systemPrompt?.includes(INTERCOM_BRIDGE_MARKER) === true,
|
|
320
350
|
intercomEvents: input.intercomEvents,
|
|
321
351
|
runId: input.runId,
|
|
322
|
-
index:
|
|
323
|
-
sessionDir: input.sessionDirForIndex(
|
|
324
|
-
sessionFile: input.sessionFileForTask?.(task.agent,
|
|
325
|
-
?? input.sessionFileForIndex?.(
|
|
326
|
-
thinkingOverride: input.thinkingOverrideForTask?.(task.agent,
|
|
352
|
+
index: childIndex,
|
|
353
|
+
sessionDir: input.sessionDirForIndex(childIndex),
|
|
354
|
+
sessionFile: input.sessionFileForTask?.(task.agent, childIndex, effectiveModel)
|
|
355
|
+
?? input.sessionFileForIndex?.(childIndex),
|
|
356
|
+
thinkingOverride: input.thinkingOverrideForTask?.(task.agent, childIndex, effectiveModel),
|
|
327
357
|
share: input.shareEnabled,
|
|
328
358
|
artifactsDir: input.artifactConfig.enabled ? input.artifactsDir : undefined,
|
|
329
359
|
artifactConfig: input.artifactConfig,
|
|
@@ -332,7 +362,7 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
|
|
|
332
362
|
maxSubagentDepth,
|
|
333
363
|
controlConfig: input.controlConfig,
|
|
334
364
|
onControlEvent: input.onControlEvent,
|
|
335
|
-
intercomSessionName: input.childIntercomTarget?.(task.agent,
|
|
365
|
+
intercomSessionName: input.childIntercomTarget?.(task.agent, childIndex),
|
|
336
366
|
orchestratorIntercomTarget: input.orchestratorIntercomTarget,
|
|
337
367
|
nestedRoute: input.nestedRoute,
|
|
338
368
|
modelOverride: effectiveModel,
|
|
@@ -341,13 +371,14 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
|
|
|
341
371
|
modelScope: input.modelScope,
|
|
342
372
|
skills: behavior.skills === false ? [] : behavior.skills,
|
|
343
373
|
structuredOutput: structuredRuntime,
|
|
374
|
+
agentContract,
|
|
344
375
|
acceptance: task.acceptance,
|
|
345
376
|
acceptanceContext: { mode: "chain", dynamic: input.dynamic && task.acceptance === undefined },
|
|
346
377
|
timeoutMs: input.timeoutMs,
|
|
347
378
|
deadlineAt: input.deadlineAt,
|
|
348
379
|
turnBudget: input.turnBudget,
|
|
349
380
|
onDetachedExit: input.onDetachedExit
|
|
350
|
-
? (result) => input.onDetachedExit?.(
|
|
381
|
+
? (result) => input.onDetachedExit?.(childIndex, result)
|
|
351
382
|
: undefined,
|
|
352
383
|
toolBudget: toolBudget.toolBudget,
|
|
353
384
|
onUpdate: input.onUpdate
|
|
@@ -355,18 +386,7 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
|
|
|
355
386
|
const stepResults = progressUpdate.details?.results || [];
|
|
356
387
|
const stepProgress = progressUpdate.details?.progress || [];
|
|
357
388
|
if (input.foregroundControl && stepProgress.length > 0) {
|
|
358
|
-
|
|
359
|
-
input.foregroundControl.currentAgent = task.agent;
|
|
360
|
-
input.foregroundControl.currentIndex = input.globalTaskIndex + taskIndex;
|
|
361
|
-
input.foregroundControl.currentActivityState = current?.activityState;
|
|
362
|
-
input.foregroundControl.lastActivityAt = current?.lastActivityAt;
|
|
363
|
-
input.foregroundControl.currentTool = current?.currentTool;
|
|
364
|
-
input.foregroundControl.currentToolStartedAt = current?.currentToolStartedAt;
|
|
365
|
-
input.foregroundControl.currentPath = current?.currentPath;
|
|
366
|
-
input.foregroundControl.turnCount = current?.turnCount;
|
|
367
|
-
input.foregroundControl.tokens = current?.tokens;
|
|
368
|
-
input.foregroundControl.toolCount = current?.toolCount;
|
|
369
|
-
input.foregroundControl.updatedAt = Date.now();
|
|
389
|
+
updateForegroundChild(input.foregroundControl, childIndex, stepProgress[0]);
|
|
370
390
|
}
|
|
371
391
|
input.onUpdate?.({
|
|
372
392
|
...progressUpdate,
|
|
@@ -385,7 +405,7 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
|
|
|
385
405
|
steps: input.chainSteps,
|
|
386
406
|
results: input.results.concat(stepResults),
|
|
387
407
|
currentStepIndex: input.stepIndex,
|
|
388
|
-
currentFlatIndex:
|
|
408
|
+
currentFlatIndex: childIndex,
|
|
389
409
|
dynamicChildren: input.dynamicChildren,
|
|
390
410
|
dynamicGroupStatuses: input.dynamicGroupStatuses,
|
|
391
411
|
}),
|
|
@@ -394,10 +414,7 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
|
|
|
394
414
|
}
|
|
395
415
|
: undefined,
|
|
396
416
|
});
|
|
397
|
-
if (input.foregroundControl
|
|
398
|
-
input.foregroundControl.interrupt = undefined;
|
|
399
|
-
input.foregroundControl.updatedAt = Date.now();
|
|
400
|
-
}
|
|
417
|
+
if (input.foregroundControl) finishForegroundChild(input.foregroundControl, childIndex);
|
|
401
418
|
|
|
402
419
|
if (result.exitCode !== 0 && failFast) {
|
|
403
420
|
aborted = true;
|
|
@@ -425,6 +442,8 @@ interface ChainExecutionParams {
|
|
|
425
442
|
sessionFileForIndex?: (idx?: number) => string | undefined;
|
|
426
443
|
sessionFileForTask?: (agentName: string, idx?: number, modelOverride?: string) => string | undefined;
|
|
427
444
|
thinkingOverrideForTask?: (agentName: string, idx?: number, modelOverride?: string) => AgentConfig["thinking"] | undefined;
|
|
445
|
+
contextForAgent?: (agentName: string) => ContextMode;
|
|
446
|
+
modelScope?: ModelScopeConfig;
|
|
428
447
|
artifactsDir: string;
|
|
429
448
|
artifactConfig: ArtifactConfig;
|
|
430
449
|
includeProgress?: boolean;
|
|
@@ -432,22 +451,10 @@ interface ChainExecutionParams {
|
|
|
432
451
|
onUpdate?: (r: AgentToolResult<Details>) => void;
|
|
433
452
|
onControlEvent?: (event: ControlEvent) => void;
|
|
434
453
|
controlConfig: ResolvedControlConfig;
|
|
454
|
+
agentContract?: AgentContract;
|
|
435
455
|
childIntercomTarget?: (agent: string, index: number) => string | undefined;
|
|
436
456
|
orchestratorIntercomTarget?: string;
|
|
437
|
-
foregroundControl?:
|
|
438
|
-
updatedAt: number;
|
|
439
|
-
currentAgent?: string;
|
|
440
|
-
currentIndex?: number;
|
|
441
|
-
currentActivityState?: ActivityState;
|
|
442
|
-
lastActivityAt?: number;
|
|
443
|
-
currentTool?: string;
|
|
444
|
-
currentToolStartedAt?: number;
|
|
445
|
-
currentPath?: string;
|
|
446
|
-
turnCount?: number;
|
|
447
|
-
tokens?: number;
|
|
448
|
-
toolCount?: number;
|
|
449
|
-
interrupt?: () => boolean;
|
|
450
|
-
};
|
|
457
|
+
foregroundControl?: ForegroundRunControl;
|
|
451
458
|
chainSkills?: string[];
|
|
452
459
|
chainDir?: string;
|
|
453
460
|
dynamicFanoutMaxItems?: number;
|
|
@@ -517,6 +524,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
517
524
|
const dynamicGroupStatuses: ChainExecutionDetailsInput["dynamicGroupStatuses"] = {};
|
|
518
525
|
const allProgress: AgentProgress[] = [];
|
|
519
526
|
const allArtifactPaths: ArtifactPaths[] = [];
|
|
527
|
+
let parallelHandoff: Details["parallelHandoff"];
|
|
520
528
|
|
|
521
529
|
const chainAgents: string[] = chainSteps.map((step) =>
|
|
522
530
|
isParallelStep(step)
|
|
@@ -540,6 +548,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
540
548
|
outputs,
|
|
541
549
|
dynamicChildren,
|
|
542
550
|
dynamicGroupStatuses,
|
|
551
|
+
...(parallelHandoff ? { parallelHandoff } : {}),
|
|
543
552
|
...overrides,
|
|
544
553
|
});
|
|
545
554
|
|
|
@@ -671,6 +680,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
671
680
|
const parallelTemplates = stepTemplates as string[];
|
|
672
681
|
const parallelCwd = resolveChildCwd(cwd ?? ctx.cwd, step.cwd);
|
|
673
682
|
let worktreeSetup: WorktreeSetup | undefined;
|
|
683
|
+
let worktreeFinalized = false;
|
|
674
684
|
if (step.worktree) {
|
|
675
685
|
const worktreeTaskCwdConflict = findWorktreeTaskCwdConflict(step.parallel, parallelCwd);
|
|
676
686
|
if (worktreeTaskCwdConflict) {
|
|
@@ -728,6 +738,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
728
738
|
sessionFileForIndex,
|
|
729
739
|
sessionFileForTask,
|
|
730
740
|
thinkingOverrideForTask,
|
|
741
|
+
contextForAgent: params.contextForAgent,
|
|
731
742
|
shareEnabled,
|
|
732
743
|
artifactConfig,
|
|
733
744
|
artifactsDir,
|
|
@@ -742,6 +753,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
742
753
|
dynamicChildren,
|
|
743
754
|
dynamicGroupStatuses,
|
|
744
755
|
controlConfig,
|
|
756
|
+
agentContract: params.agentContract,
|
|
745
757
|
onControlEvent,
|
|
746
758
|
childIntercomTarget,
|
|
747
759
|
orchestratorIntercomTarget,
|
|
@@ -764,6 +776,23 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
764
776
|
if (result.progress) allProgress.push(result.progress);
|
|
765
777
|
if (result.artifactPaths) allArtifactPaths.push(result.artifactPaths);
|
|
766
778
|
}
|
|
779
|
+
let worktreeSuffix = "";
|
|
780
|
+
if (worktreeSetup) {
|
|
781
|
+
worktreeFinalized = true;
|
|
782
|
+
const finalized = finalizeParallelWorktreeHandoff({
|
|
783
|
+
output: "",
|
|
784
|
+
worktreeSetup,
|
|
785
|
+
artifactsDir,
|
|
786
|
+
runId,
|
|
787
|
+
cwd: parallelCwd,
|
|
788
|
+
stepIndex,
|
|
789
|
+
flatStartIndex: globalTaskIndex - step.parallel.length,
|
|
790
|
+
agents: agentNames,
|
|
791
|
+
results: parallelResults,
|
|
792
|
+
});
|
|
793
|
+
if (finalized.reference) parallelHandoff = finalized.reference;
|
|
794
|
+
worktreeSuffix = finalized.output.trim();
|
|
795
|
+
}
|
|
767
796
|
const interruptedIndexInStep = parallelResults.findIndex((result) => result.interrupted);
|
|
768
797
|
const interrupted = interruptedIndexInStep >= 0 ? parallelResults[interruptedIndexInStep] : undefined;
|
|
769
798
|
if (interrupted) {
|
|
@@ -808,6 +837,24 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
808
837
|
})),
|
|
809
838
|
};
|
|
810
839
|
}
|
|
840
|
+
const acceptanceFailures = parallelResults
|
|
841
|
+
.map((result, originalIndex) => ({ result, originalIndex, task: step.parallel[originalIndex] }))
|
|
842
|
+
.filter(({ result, task }) => isAgentContractV1(task?.agentContract ?? step.agentContract ?? params.agentContract) && (task?.gateOn ?? step.gateOn) === "acceptance" && result.acceptance?.status === "rejected");
|
|
843
|
+
if (acceptanceFailures.length > 0) {
|
|
844
|
+
const acceptanceSummary = acceptanceFailures
|
|
845
|
+
.map(({ result, originalIndex }) => `- Task ${originalIndex + 1} (${result.agent}): ${acceptanceFailureMessage(result.acceptance) ?? "acceptance rejected"}`)
|
|
846
|
+
.join("\n");
|
|
847
|
+
const errorMsg = `Parallel step ${stepIndex + 1} acceptance gate failed:\n${acceptanceSummary}`;
|
|
848
|
+
const summary = buildChainSummary(chainSteps, results, chainDir, "failed", { index: stepIndex, error: errorMsg });
|
|
849
|
+
return {
|
|
850
|
+
content: [{ type: "text", text: summary }],
|
|
851
|
+
isError: true,
|
|
852
|
+
details: buildChainExecutionDetails(makeDetailsInput({
|
|
853
|
+
currentStepIndex: stepIndex,
|
|
854
|
+
currentFlatIndex: globalTaskIndex - step.parallel.length + acceptanceFailures[0]!.originalIndex,
|
|
855
|
+
})),
|
|
856
|
+
};
|
|
857
|
+
}
|
|
811
858
|
|
|
812
859
|
for (let taskIndex = 0; taskIndex < parallelResults.length; taskIndex++) {
|
|
813
860
|
const outputName = step.parallel[taskIndex]?.as;
|
|
@@ -831,14 +878,9 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
831
878
|
};
|
|
832
879
|
});
|
|
833
880
|
prev = aggregateParallelOutputs(taskResults);
|
|
834
|
-
prev =
|
|
835
|
-
prev,
|
|
836
|
-
worktreeSetup,
|
|
837
|
-
path.join(chainDir, "worktree-diffs", `step-${stepIndex}`),
|
|
838
|
-
agentNames,
|
|
839
|
-
);
|
|
881
|
+
if (worktreeSuffix) prev = `${prev}\n\n${worktreeSuffix}`;
|
|
840
882
|
} finally {
|
|
841
|
-
if (worktreeSetup) cleanupWorktrees(worktreeSetup);
|
|
883
|
+
if (worktreeSetup && !worktreeFinalized) cleanupWorktrees(worktreeSetup);
|
|
842
884
|
}
|
|
843
885
|
} else if (isDynamicParallelStep(step)) {
|
|
844
886
|
const dynamicStartIndex = globalTaskIndex;
|
|
@@ -863,7 +905,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
863
905
|
if (materialized.parallel.length === 0) {
|
|
864
906
|
const collection: DynamicCollectedResult[] = [];
|
|
865
907
|
try {
|
|
866
|
-
validateDynamicCollection(step.collect.outputSchema, collection);
|
|
908
|
+
await validateDynamicCollection(step.collect.outputSchema, collection);
|
|
867
909
|
} catch (error) {
|
|
868
910
|
const message = error instanceof DynamicFanoutError ? error.message : error instanceof Error ? error.message : String(error);
|
|
869
911
|
dynamicGroupStatuses[stepIndex] = { status: "failed", error: message };
|
|
@@ -884,6 +926,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
884
926
|
task: (step.parallel.task ?? originalTask ?? "").replace(/\{task\}/g, originalTask ?? ""),
|
|
885
927
|
mode: "chain",
|
|
886
928
|
dynamicGroup: true,
|
|
929
|
+
agentContract: step.agentContract ?? params.agentContract,
|
|
887
930
|
});
|
|
888
931
|
const groupAcceptance = await evaluateAcceptance({
|
|
889
932
|
acceptance: effectiveGroupAcceptance,
|
|
@@ -893,9 +936,10 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
893
936
|
notes: "Dynamic fanout produced 0 results.",
|
|
894
937
|
}),
|
|
895
938
|
cwd: cwd ?? ctx.cwd,
|
|
939
|
+
reportOptional: isAgentContractV1(step.agentContract ?? params.agentContract),
|
|
896
940
|
});
|
|
897
941
|
dynamicGroupStatuses[stepIndex].acceptance = groupAcceptance;
|
|
898
|
-
const groupAcceptanceFailure = acceptanceFailureMessage(groupAcceptance);
|
|
942
|
+
const groupAcceptanceFailure = !isAgentContractV1(step.agentContract ?? params.agentContract) || step.gateOn === "acceptance" ? acceptanceFailureMessage(groupAcceptance) : undefined;
|
|
899
943
|
if (groupAcceptanceFailure) {
|
|
900
944
|
dynamicGroupStatuses[stepIndex] = { status: "failed", error: groupAcceptanceFailure, acceptance: groupAcceptance };
|
|
901
945
|
return buildChainExecutionErrorResult(groupAcceptanceFailure, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex }));
|
|
@@ -910,6 +954,8 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
910
954
|
parallel: materialized.parallel,
|
|
911
955
|
concurrency: step.concurrency,
|
|
912
956
|
failFast: step.failFast,
|
|
957
|
+
agentContract: step.agentContract,
|
|
958
|
+
gateOn: step.gateOn,
|
|
913
959
|
};
|
|
914
960
|
const parallelTemplates = materialized.parallel.map((task) => task.task ?? "{previous}");
|
|
915
961
|
const parallelBehaviors = resolveParallelBehaviors(dynamicParallelStep.parallel, agents, stepIndex, chainSkills)
|
|
@@ -949,6 +995,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
949
995
|
sessionFileForIndex,
|
|
950
996
|
sessionFileForTask,
|
|
951
997
|
thinkingOverrideForTask,
|
|
998
|
+
contextForAgent: params.contextForAgent,
|
|
952
999
|
shareEnabled,
|
|
953
1000
|
artifactConfig,
|
|
954
1001
|
artifactsDir,
|
|
@@ -963,6 +1010,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
963
1010
|
dynamicChildren,
|
|
964
1011
|
dynamicGroupStatuses,
|
|
965
1012
|
controlConfig,
|
|
1013
|
+
agentContract: params.agentContract,
|
|
966
1014
|
onControlEvent,
|
|
967
1015
|
childIntercomTarget,
|
|
968
1016
|
orchestratorIntercomTarget,
|
|
@@ -1030,8 +1078,24 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
1030
1078
|
})),
|
|
1031
1079
|
};
|
|
1032
1080
|
}
|
|
1081
|
+
const acceptanceFailures = parallelResults
|
|
1082
|
+
.map((result, originalIndex) => ({ result, originalIndex, task: dynamicParallelStep.parallel[originalIndex] }))
|
|
1083
|
+
.filter(({ result, task }) => isAgentContractV1(task?.agentContract ?? dynamicParallelStep.agentContract ?? params.agentContract) && (task?.gateOn ?? dynamicParallelStep.gateOn) === "acceptance" && result.acceptance?.status === "rejected");
|
|
1084
|
+
if (acceptanceFailures.length > 0) {
|
|
1085
|
+
const acceptanceSummary = acceptanceFailures
|
|
1086
|
+
.map(({ result, originalIndex }) => `- Item ${originalIndex + 1} (${result.agent}, key ${materialized.items[originalIndex]?.key ?? originalIndex}): ${acceptanceFailureMessage(result.acceptance) ?? "acceptance rejected"}`)
|
|
1087
|
+
.join("\n");
|
|
1088
|
+
const errorMsg = `Dynamic step ${stepIndex + 1} acceptance gate failed:\n${acceptanceSummary}`;
|
|
1089
|
+
dynamicGroupStatuses[stepIndex] = { status: "failed", error: errorMsg };
|
|
1090
|
+
const summary = buildChainSummary(chainSteps, results, chainDir, "failed", { index: stepIndex, error: errorMsg });
|
|
1091
|
+
return {
|
|
1092
|
+
content: [{ type: "text", text: summary }],
|
|
1093
|
+
isError: true,
|
|
1094
|
+
details: buildChainExecutionDetails(makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: dynamicStartIndex + acceptanceFailures[0]!.originalIndex })),
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1033
1097
|
try {
|
|
1034
|
-
validateDynamicCollection(step.collect.outputSchema, collected);
|
|
1098
|
+
await validateDynamicCollection(step.collect.outputSchema, collected);
|
|
1035
1099
|
} catch (error) {
|
|
1036
1100
|
const message = error instanceof DynamicFanoutError ? error.message : error instanceof Error ? error.message : String(error);
|
|
1037
1101
|
dynamicGroupStatuses[stepIndex] = { status: "failed", error: message };
|
|
@@ -1053,6 +1117,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
1053
1117
|
.join("\n"),
|
|
1054
1118
|
mode: "chain",
|
|
1055
1119
|
dynamicGroup: true,
|
|
1120
|
+
agentContract: step.agentContract ?? params.agentContract,
|
|
1056
1121
|
});
|
|
1057
1122
|
const groupAcceptance = await evaluateAcceptance({
|
|
1058
1123
|
acceptance: effectiveGroupAcceptance,
|
|
@@ -1062,9 +1127,10 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
1062
1127
|
notes: `Dynamic fanout collected ${collected.length} result(s) into ${step.collect.as}.`,
|
|
1063
1128
|
}),
|
|
1064
1129
|
cwd: cwd ?? ctx.cwd,
|
|
1130
|
+
reportOptional: isAgentContractV1(step.agentContract ?? params.agentContract),
|
|
1065
1131
|
});
|
|
1066
1132
|
dynamicGroupStatuses[stepIndex].acceptance = groupAcceptance;
|
|
1067
|
-
const groupAcceptanceFailure = effectiveGroupAcceptance.explicit ? acceptanceFailureMessage(groupAcceptance) : undefined;
|
|
1133
|
+
const groupAcceptanceFailure = effectiveGroupAcceptance.explicit && (!isAgentContractV1(step.agentContract ?? params.agentContract) || step.gateOn === "acceptance") ? acceptanceFailureMessage(groupAcceptance) : undefined;
|
|
1068
1134
|
if (groupAcceptanceFailure) {
|
|
1069
1135
|
dynamicGroupStatuses[stepIndex] = { status: "failed", error: groupAcceptanceFailure, acceptance: groupAcceptance };
|
|
1070
1136
|
return buildChainExecutionErrorResult(groupAcceptanceFailure, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex - dynamicParallelStep.parallel.length }));
|
|
@@ -1147,22 +1213,22 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
1147
1213
|
const childIndex = globalTaskIndex;
|
|
1148
1214
|
const interruptController = new AbortController();
|
|
1149
1215
|
if (foregroundControl) {
|
|
1150
|
-
foregroundControl
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
};
|
|
1216
|
+
beginForegroundChild(foregroundControl, {
|
|
1217
|
+
index: childIndex,
|
|
1218
|
+
agent: seqStep.agent,
|
|
1219
|
+
description: cleanTask.trim(),
|
|
1220
|
+
interrupt: () => {
|
|
1221
|
+
if (interruptController.signal.aborted) return false;
|
|
1222
|
+
interruptController.abort();
|
|
1223
|
+
return true;
|
|
1224
|
+
},
|
|
1225
|
+
});
|
|
1161
1226
|
}
|
|
1162
1227
|
|
|
1163
1228
|
const structuredRuntime = seqStep.outputSchema
|
|
1164
1229
|
? createStructuredOutputRuntime(seqStep.outputSchema, path.join(chainDir, "structured-output"))
|
|
1165
1230
|
: undefined;
|
|
1231
|
+
const agentContract = seqStep.agentContract ?? params.agentContract;
|
|
1166
1232
|
const toolBudget = resolveChainToolBudget({ stepBudget: seqStep.toolBudget, runBudget: params.toolBudget, agentBudget: agentConfig?.toolBudget, configBudget: params.configToolBudget });
|
|
1167
1233
|
if (toolBudget.error) return buildChainExecutionErrorResult(toolBudget.error, {
|
|
1168
1234
|
results,
|
|
@@ -1182,6 +1248,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
1182
1248
|
});
|
|
1183
1249
|
const r = await runSync(ctx.cwd, agents, seqStep.agent, stepTask, {
|
|
1184
1250
|
parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
|
|
1251
|
+
context: params.contextForAgent?.(seqStep.agent),
|
|
1185
1252
|
cwd: resolveChildCwd(cwd ?? ctx.cwd, seqStep.cwd),
|
|
1186
1253
|
signal,
|
|
1187
1254
|
interruptSignal: interruptController.signal,
|
|
@@ -1210,6 +1277,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
1210
1277
|
modelScope,
|
|
1211
1278
|
skills: behavior.skills === false ? [] : behavior.skills,
|
|
1212
1279
|
structuredOutput: structuredRuntime,
|
|
1280
|
+
agentContract,
|
|
1213
1281
|
acceptance: seqStep.acceptance,
|
|
1214
1282
|
acceptanceContext: { mode: "chain" },
|
|
1215
1283
|
timeoutMs: params.timeoutMs,
|
|
@@ -1224,18 +1292,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
1224
1292
|
const stepResults = p.details?.results || [];
|
|
1225
1293
|
const stepProgress = p.details?.progress || [];
|
|
1226
1294
|
if (foregroundControl && stepProgress.length > 0) {
|
|
1227
|
-
|
|
1228
|
-
foregroundControl.currentAgent = seqStep.agent;
|
|
1229
|
-
foregroundControl.currentIndex = childIndex;
|
|
1230
|
-
foregroundControl.currentActivityState = current?.activityState;
|
|
1231
|
-
foregroundControl.lastActivityAt = current?.lastActivityAt;
|
|
1232
|
-
foregroundControl.currentTool = current?.currentTool;
|
|
1233
|
-
foregroundControl.currentToolStartedAt = current?.currentToolStartedAt;
|
|
1234
|
-
foregroundControl.currentPath = current?.currentPath;
|
|
1235
|
-
foregroundControl.turnCount = current?.turnCount;
|
|
1236
|
-
foregroundControl.tokens = current?.tokens;
|
|
1237
|
-
foregroundControl.toolCount = current?.toolCount;
|
|
1238
|
-
foregroundControl.updatedAt = Date.now();
|
|
1295
|
+
updateForegroundChild(foregroundControl, childIndex, stepProgress[0]);
|
|
1239
1296
|
}
|
|
1240
1297
|
onUpdate({
|
|
1241
1298
|
...p,
|
|
@@ -1263,10 +1320,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
1263
1320
|
}
|
|
1264
1321
|
: undefined,
|
|
1265
1322
|
});
|
|
1266
|
-
if (foregroundControl
|
|
1267
|
-
foregroundControl.interrupt = undefined;
|
|
1268
|
-
foregroundControl.updatedAt = Date.now();
|
|
1269
|
-
}
|
|
1323
|
+
if (foregroundControl) finishForegroundChild(foregroundControl, childIndex);
|
|
1270
1324
|
recordRun(seqStep.agent, cleanTask, r.exitCode, r.progressSummary?.durationMs ?? 0);
|
|
1271
1325
|
|
|
1272
1326
|
globalTaskIndex++;
|
|
@@ -1298,6 +1352,17 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
1298
1352
|
isError: true,
|
|
1299
1353
|
};
|
|
1300
1354
|
}
|
|
1355
|
+
if (isAgentContractV1(agentContract) && seqStep.gateOn === "acceptance" && r.acceptance?.status === "rejected") {
|
|
1356
|
+
const summary = buildChainSummary(chainSteps, results, chainDir, "failed", {
|
|
1357
|
+
index: stepIndex,
|
|
1358
|
+
error: acceptanceFailureMessage(r.acceptance) ?? "Chain acceptance gate rejected the step.",
|
|
1359
|
+
});
|
|
1360
|
+
return {
|
|
1361
|
+
content: [{ type: "text", text: summary }],
|
|
1362
|
+
details: buildChainExecutionDetails(makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: childIndex })),
|
|
1363
|
+
isError: true,
|
|
1364
|
+
};
|
|
1365
|
+
}
|
|
1301
1366
|
|
|
1302
1367
|
if (behavior.output) {
|
|
1303
1368
|
try {
|