pi-subagents 0.65.1 → 0.66.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 +55 -0
- package/README.md +1 -1
- package/agents/researcher.md +23 -13
- package/docs/agents.md +16 -2
- package/docs/configuration.md +18 -0
- package/docs/extension-api.md +91 -0
- package/docs/models.md +58 -1
- package/docs/observability.md +42 -2
- package/docs/tool-reference.md +3 -3
- package/docs/workflows.md +14 -7
- package/package.json +2 -1
- package/skills/pi-subagents/references/execution-controls.md +14 -1
- package/skills/pi-subagents/references/management-authoring-rpc.md +2 -1
- package/src/agents/advertised-agent-prompt.ts +63 -0
- package/src/agents/agent-management.ts +14 -1
- package/src/agents/agent-serializer.ts +2 -0
- package/src/agents/agents.ts +8 -0
- package/src/api/shared-types.ts +1 -1
- package/src/api/workflow-resources.ts +6 -0
- package/src/extension/index.ts +40 -2
- package/src/extension/public-execution.ts +0 -1
- package/src/extension/rpc.ts +4 -21
- package/src/extension/schemas.ts +8 -6
- package/src/extension/tool-description.ts +6 -5
- package/src/intercom/native-supervisor-channel.ts +82 -54
- package/src/runs/background/active-async-capacity.ts +18 -18
- package/src/runs/background/async-job-tracker.ts +35 -3
- package/src/runs/background/async-status-snapshot.ts +10 -12
- package/src/runs/background/async-status.ts +17 -9
- package/src/runs/background/auto-drain.ts +40 -29
- package/src/runs/background/chain-root-attachment.ts +8 -0
- package/src/runs/background/control-channel.ts +78 -44
- package/src/runs/background/notify.ts +86 -12
- package/src/runs/background/owned-process-tree.ts +6 -6
- package/src/runs/background/process-terminal.ts +23 -23
- package/src/runs/background/run-child-session.ts +60 -32
- package/src/runs/background/run-status.ts +75 -5
- package/src/runs/background/runner-aliases.ts +18 -7
- package/src/runs/background/runner-child-launch.ts +86 -0
- package/src/runs/background/stale-run-reconciler.ts +3 -1
- package/src/runs/background/subagent-runner.ts +412 -209
- package/src/runs/background/subagent-wait.ts +3 -0
- package/src/runs/background/wait-completions.ts +4 -0
- package/src/runs/foreground/async-steering-action.ts +19 -0
- package/src/runs/foreground/execution.ts +101 -25
- package/src/runs/foreground/subagent-executor.ts +474 -197
- package/src/runs/foreground/workflow-detach-reconcile.ts +8 -5
- package/src/runs/foreground/workflow-foreground-steering.ts +56 -2
- package/src/runs/shared/acceptance.ts +2 -2
- package/src/runs/shared/agent-contract.ts +1 -1
- package/src/runs/shared/async-status-projection.ts +47 -47
- package/src/runs/shared/child-hooks.ts +151 -2
- package/src/runs/shared/child-launch.ts +18 -13
- package/src/runs/shared/child-session.ts +42 -6
- package/src/runs/shared/child-tool-plan.ts +2 -2
- package/src/runs/shared/completion-evidence.ts +2 -2
- package/src/runs/shared/completion-guard.ts +1 -0
- package/src/runs/shared/host-step-status.ts +11 -11
- package/src/runs/shared/llm-intent-arbiter.ts +10 -9
- package/src/runs/shared/model-fallback.ts +10 -6
- package/src/runs/shared/nested-events.ts +5 -5
- package/src/runs/shared/orca-progress-tabs.ts +6 -0
- package/src/runs/shared/parallel-handoff.ts +57 -12
- package/src/runs/shared/parallel-utils.ts +2 -2
- package/src/runs/shared/readonly-drain-observation.ts +42 -0
- package/src/runs/shared/readonly-model-continuation.ts +69 -0
- package/src/runs/shared/readonly-session-evidence.ts +307 -0
- package/src/runs/shared/run-fanout-budget.ts +8 -8
- package/src/runs/shared/runtime-acknowledged-extensions.ts +3 -3
- package/src/runs/shared/subagent-prompt-runtime.ts +13 -3
- package/src/runs/shared/worktree-setup-command.ts +190 -0
- package/src/runs/shared/worktree.ts +332 -204
- package/src/shared/types.ts +81 -60
- package/src/shared/utils.ts +7 -2
- package/src/shared/workflow-child-permit.ts +18 -13
- package/src/tui/fleet.ts +11 -5
- package/src/tui/render.ts +23 -5
- package/src/workflows/chat-progress.ts +3 -3
- package/src/workflows/scripted-workflow.ts +38 -10
- package/src/workflows/workflow-checklist.ts +11 -15
- package/src/workflows/workflow-child-summary.ts +57 -8
- package/src/workflows/workflow-preflight.ts +19 -19
- package/src/workflows/workflow-receipt.ts +3 -3
- package/src/workflows/workflow-resources.ts +96 -21
- package/src/workflows/workflow-settlement.ts +3 -0
|
@@ -52,7 +52,7 @@ import { isScheduledRunAction } from "../background/scheduled-runs.ts";
|
|
|
52
52
|
import { encodeIndexSegment } from "../background/index-segment.ts";
|
|
53
53
|
import { enqueueChainAppendRequest, readPendingChainAppendRequests, runnerStepOutputNames } from "../background/chain-append.ts";
|
|
54
54
|
import { ChainOutputValidationError, validateChainOutputBindingsWithContext } from "../shared/chain-outputs.ts";
|
|
55
|
-
import { normalizeGateAcceptance, resolveAcceptanceReportMode, validateExecutionAcceptance } from "../shared/acceptance.ts";
|
|
55
|
+
import { normalizeGateAcceptance, resolveAcceptanceReportMode, validateAcceptanceInput, validateExecutionAcceptance } from "../shared/acceptance.ts";
|
|
56
56
|
import { canPreferFork, createForkContextResolver, forkedChildRequiresThinkingOff, resolveSubagentLaunchContext } from "../../shared/fork-context.ts";
|
|
57
57
|
import { createPrunedForkSessionWriter } from "../../shared/pruned-fork.ts";
|
|
58
58
|
import { resolveCurrentSessionId } from "../../shared/session-identity.ts";
|
|
@@ -65,13 +65,13 @@ import { retainLiveForegroundNestedRoute } from "../../integrations/pi-web-sessi
|
|
|
65
65
|
import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
|
|
66
66
|
import { usageBudgetExceededMessage, usageBudgetState, validateUsageBudgetConfig } from "../shared/usage-budget.ts";
|
|
67
67
|
import { intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
|
|
68
|
-
import {
|
|
68
|
+
import { isAgentContract } from "../shared/agent-contract.ts";
|
|
69
69
|
import { normalizeExtensionBindings, type ExtensionBindings } from "../shared/extension-bindings.ts";
|
|
70
70
|
import { finalizeSingleOutput, injectSingleOutputInstruction, normalizeSingleOutputOverride, outputPathMappingFromTask, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
|
|
71
71
|
import { assertJsonSchemaObject, cleanupStructuredOutputRuntime, createStructuredOutputRuntime } from "../shared/structured-output.ts";
|
|
72
72
|
import { compactForegroundDetails, getSingleResultOutput, readStatus, resolveChildCwd, sumResultsCost, sumResultsUsage, toAgentToolUsage } from "../../shared/utils.ts";
|
|
73
73
|
import { createTaskMutationArbiter } from "../shared/llm-intent-arbiter.ts";
|
|
74
|
-
import { discardPreservedWorktrees, formatParallelHandoffError, formatParallelHandoffReference, formatStoredParallelHandoffCleanup, parallelHandoffPath, readParallelHandoffManifest, recordParallelHandoffMerge, recordParallelHandoffSupersession, writeParallelHandoffGroup,
|
|
74
|
+
import { discardPreservedWorktrees, formatParallelHandoffError, formatParallelHandoffReference, formatStoredParallelHandoffCleanup, parallelHandoffPath, readParallelHandoffManifest, recordParallelHandoffMerge, recordParallelHandoffSupersession, writeParallelHandoffGroup, writeWorktreeSetupHandoff } from "../shared/parallel-handoff.ts";
|
|
75
75
|
import { summarizeContextModes, type ContextMode, type ContextSummary } from "../shared/context-mode.ts";
|
|
76
76
|
import {
|
|
77
77
|
attachNestedChildrenToResultChildren,
|
|
@@ -82,12 +82,13 @@ import {
|
|
|
82
82
|
stripDetailsOutputsForIntercomReceipt,
|
|
83
83
|
} from "../../intercom/result-intercom.ts";
|
|
84
84
|
import { applySteeringRecoveryAgentConfig, asyncReviveRequiresRecoveryDescriptor, buildRevivedAsyncTask, readAsyncRecoveryDescriptor, resolveAsyncResumeTarget, resolveAsyncRunLocation } from "../background/async-resume.ts";
|
|
85
|
-
import { deliverInterruptRequest, readRevivalBriefs, requestAsyncSteer, type SteerDeliveryMode } from "../background/control-channel.ts";
|
|
86
|
-
import { updateSteeringTarget, waitForSteeringAction } from "../background/steering.ts";
|
|
85
|
+
import { closeSteerInbox, consumeSteerRequests, deliverInterruptRequest, readRevivalBriefs, requestAsyncSteer, watchAsyncControlInbox, type SteerDeliveryMode, type SteerRequest } from "../background/control-channel.ts";
|
|
86
|
+
import { createSteeringStatus, recordSteeringRequest, steeringStatus, updateSteeringTarget, waitForSteeringAction } from "../background/steering.ts";
|
|
87
87
|
import { canQueueRetainedAsyncFollowUp, steerAsyncRun } from "./async-steering-action.ts";
|
|
88
88
|
import {
|
|
89
89
|
resolveWorkflowForegroundSteeringTarget,
|
|
90
90
|
steerWorkflowForegroundTarget,
|
|
91
|
+
steerWorkflowRun,
|
|
91
92
|
} from "./workflow-foreground-steering.ts";
|
|
92
93
|
import { stopAsyncRun } from "./async-stop-action.ts";
|
|
93
94
|
import { dismissRecoveredWorkflow } from "./async-dismiss-action.ts";
|
|
@@ -115,10 +116,10 @@ import { handleHerdrInspectorAction, HERDR_INSPECTOR_ACTIONS } from "../../inspe
|
|
|
115
116
|
import { handleHerdrProjectPaneAction, HERDR_PROJECT_PANE_ACTIONS } from "../../inspectors/herdr/project-panes.ts";
|
|
116
117
|
import { previewSimpleWorkflowRun, runWorkflowScript, validateWorkflowScript, WorkflowScriptError, type WorkflowLanePlan, type WorkflowReceiptResumeReference, type WorkflowScriptChildResult, type WorkflowScriptTraceEntry, type WorkflowSteerOptions, type WorkflowSteerResult } from "../../workflows/scripted-workflow.ts";
|
|
117
118
|
import { executeWorkflowHostCommand, resolveWorkflowHostOutputClaimPath, type WorkflowHostCommandParams, type WorkflowHostCommandResult } from "../../workflows/host-command.ts";
|
|
118
|
-
import { buildWorkflowReceipt, resolveWorkflowReceiptResumeEntry, writeWorkflowReceipt, type WorkflowReceipt, type WorkflowReceiptState } from "../../workflows/workflow-receipt.ts";
|
|
119
|
+
import { buildWorkflowReceipt, readWorkflowReceipt, workflowReceiptPath, resolveWorkflowReceiptResumeEntry, writeWorkflowReceipt, type WorkflowReceipt, type WorkflowReceiptState } from "../../workflows/workflow-receipt.ts";
|
|
119
120
|
import { upsertHostStep, validHostStepNodes } from "../shared/host-step-status.ts";
|
|
120
121
|
import { assertWorkflowLaneKey, normalizeWorkflowLaneMetadata } from "../shared/lane-metadata.ts";
|
|
121
|
-
import { parseWorkflowChildSummary, workflowChildSummary } from "../../workflows/workflow-child-summary.ts";
|
|
122
|
+
import { parseWorkflowChildSummary, workflowChildProgress, workflowChildSummary } from "../../workflows/workflow-child-summary.ts";
|
|
122
123
|
import { resolveWorkflowChatProgress, type WorkflowChatProgressProjection } from "../../workflows/chat-progress.ts";
|
|
123
124
|
import { annotateWorkflowPreflightTrace, formatWorkflowPreflight, formatWorkflowPreflightWarnings, normalizeWorkflowPreflight, workflowPreflightWarnings } from "../../workflows/workflow-preflight.ts";
|
|
124
125
|
import {
|
|
@@ -135,6 +136,8 @@ import { resolveWorkflowResource } from "../../workflows/workflow-resources.ts";
|
|
|
135
136
|
import {
|
|
136
137
|
cleanupWorktrees,
|
|
137
138
|
createWorktrees,
|
|
139
|
+
withWorktreeTransaction,
|
|
140
|
+
type WorktreeSetupProgress,
|
|
138
141
|
diffWorktrees,
|
|
139
142
|
formatWorktreeDiffSummary,
|
|
140
143
|
type WorktreeSetup,
|
|
@@ -157,7 +160,7 @@ import {
|
|
|
157
160
|
type ForegroundSteerInput,
|
|
158
161
|
type ForegroundSteerOutcome,
|
|
159
162
|
type ForegroundRunControl,
|
|
160
|
-
type
|
|
163
|
+
type HostStepNode,
|
|
161
164
|
type IntercomBridgeConfig,
|
|
162
165
|
type IntercomEventBus,
|
|
163
166
|
type JsonSchemaObject,
|
|
@@ -174,7 +177,7 @@ import {
|
|
|
174
177
|
type ToolBudgetConfig,
|
|
175
178
|
type Usage,
|
|
176
179
|
type UsageBudgetConfig,
|
|
177
|
-
type
|
|
180
|
+
type WorkflowResourceProvenance,
|
|
178
181
|
type WorkflowGraphNode,
|
|
179
182
|
type WorkflowGraphSnapshot,
|
|
180
183
|
type WorkflowNodeStatus,
|
|
@@ -194,6 +197,7 @@ import {
|
|
|
194
197
|
resolveMaxSubagentSpawnsPerRun,
|
|
195
198
|
wrapForkTask,
|
|
196
199
|
type ScheduleOrigin,
|
|
200
|
+
type SteeringTargetState,
|
|
197
201
|
} from "../../shared/types.ts";
|
|
198
202
|
import { deriveChildSessionName } from "../../shared/child-session-name.ts";
|
|
199
203
|
|
|
@@ -326,7 +330,7 @@ export interface SubagentParamsLike {
|
|
|
326
330
|
workflowScriptPath?: string;
|
|
327
331
|
globalConcurrencyLimit?: number;
|
|
328
332
|
maxSubagentSpawnsPerRun?: number;
|
|
329
|
-
preflight?: import("../../shared/types.ts").
|
|
333
|
+
preflight?: import("../../shared/types.ts").WorkflowPreflight;
|
|
330
334
|
chatProgress?: "auto" | "off" | "live-card";
|
|
331
335
|
isolation?: "none" | "worktree";
|
|
332
336
|
step?: ChainStep;
|
|
@@ -437,8 +441,10 @@ interface ExecutorDeps {
|
|
|
437
441
|
getSubagentSessionRoot: (parentSessionFile: string | null) => string;
|
|
438
442
|
expandTilde: (p: string) => string;
|
|
439
443
|
discoverAgents: (cwd: string, scope: AgentScope, preferredModelProvider?: string) => { agents: AgentConfig[]; agentDiagnostics?: AgentDiscoveryDiagnostic[]; modelScope?: ModelScopeConfig; maxThinking?: AgentConfig["maxThinking"]; cwd?: string; scope?: AgentScope; directories?: UnknownAgentDiagnosticContext["directories"] };
|
|
444
|
+
onAgentsChanged?: () => void;
|
|
440
445
|
allowMutatingManagementActions?: boolean;
|
|
441
446
|
activateSupervisorTransport?: () => void;
|
|
447
|
+
findPendingAsks?: Parameters<typeof steerAsyncRun>[0]["findPendingAsks"];
|
|
442
448
|
refreshResultDelivery?: () => void;
|
|
443
449
|
trackRetainedNestedRoute?: (rootRunId: string) => void;
|
|
444
450
|
kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean;
|
|
@@ -497,6 +503,8 @@ interface ExecutionContextData {
|
|
|
497
503
|
configToolTimeoutMs?: number;
|
|
498
504
|
toolBudget?: ResolvedToolBudget;
|
|
499
505
|
usageBudget?: UsageBudgetConfig;
|
|
506
|
+
/** Parent workflow owns this allowance; presence vetoes unverifiable child continuation. */
|
|
507
|
+
inheritedUsageBudget?: UsageBudgetConfig;
|
|
500
508
|
allowZeroToolBudget?: boolean;
|
|
501
509
|
configToolBudget?: ResolvedToolBudget;
|
|
502
510
|
contextPolicy: AgentDefaultContextPolicy;
|
|
@@ -1742,6 +1750,28 @@ async function resumeExternalJobFollowUp(input: {
|
|
|
1742
1750
|
return externalJobFollowUpStarted({ sourceRunId: input.target.runId, runId: result.details.asyncId ?? runId, asyncDir: result.details.asyncDir ?? asyncDir, interactive: input.ctx.hasUI });
|
|
1743
1751
|
}
|
|
1744
1752
|
|
|
1753
|
+
function resolveRequestedResumeTarget(params: SubagentParamsLike, deps: ExecutorDeps, parentSessionFile: string | null): ResumeSourceTarget | { kind: "live-nested"; target: ResolvedSubagentRunId & { kind: "nested" } } {
|
|
1754
|
+
const requestedId = params.id ?? params.runId;
|
|
1755
|
+
let resolved: ResolvedSubagentRunId | undefined;
|
|
1756
|
+
try {
|
|
1757
|
+
resolved = requestedId ? resolveSubagentRunId(requestedId, omitUndefinedProperties({ state: deps.state, nested: nestedResolutionScopeForExecutor(deps) })) : undefined;
|
|
1758
|
+
} catch (error) {
|
|
1759
|
+
const message = error instanceof Error ? error.message : "";
|
|
1760
|
+
const asyncMatches = message.match(/async:/g)?.length ?? 0;
|
|
1761
|
+
if (!isResumeAmbiguity(error) || !message.includes("foreground:") || asyncMatches !== 1) throw error;
|
|
1762
|
+
}
|
|
1763
|
+
if (resolved?.kind === "nested") {
|
|
1764
|
+
if (params.chain?.length) throw new Error("Attaching a running subagent as a chain root is currently available for top-level async runs only.");
|
|
1765
|
+
if (resolved.match.run.state === "running" || resolved.match.run.state === "queued") return { kind: "live-nested", target: resolved };
|
|
1766
|
+
const trustedSessionRoots = [
|
|
1767
|
+
...(deps.config.defaultSessionDir ? [path.resolve(deps.expandTilde(deps.config.defaultSessionDir))] : []),
|
|
1768
|
+
...(parentSessionFile ? [deps.getSubagentSessionRoot(parentSessionFile)] : []),
|
|
1769
|
+
];
|
|
1770
|
+
return resolveNestedResumeTarget(resolved, trustedSessionRoots);
|
|
1771
|
+
}
|
|
1772
|
+
return resolveResumeTarget(params, deps.state, { asyncRequireSessionFile: false });
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1745
1775
|
async function resumeAsyncRun(input: {
|
|
1746
1776
|
params: SubagentParamsLike;
|
|
1747
1777
|
requestCwd: string;
|
|
@@ -1780,34 +1810,9 @@ async function resumeAsyncRun(input: {
|
|
|
1780
1810
|
let target: ResumeSourceTarget;
|
|
1781
1811
|
const parentSessionFile = input.ctx.sessionManager.getSessionFile() ?? null;
|
|
1782
1812
|
try {
|
|
1783
|
-
const
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
resolved = requestedId ? resolveSubagentRunId(requestedId, omitUndefinedProperties({ state: input.deps.state, nested: nestedResolutionScopeForExecutor(input.deps) })) : undefined;
|
|
1787
|
-
} catch (error) {
|
|
1788
|
-
const message = error instanceof Error ? error.message : "";
|
|
1789
|
-
const asyncMatches = message.match(/async:/g)?.length ?? 0;
|
|
1790
|
-
if (!isResumeAmbiguity(error) || !message.includes("foreground:") || asyncMatches !== 1) throw error;
|
|
1791
|
-
}
|
|
1792
|
-
if (resolved?.kind === "nested") {
|
|
1793
|
-
if (attachChain) {
|
|
1794
|
-
return {
|
|
1795
|
-
content: [{ type: "text", text: "Attaching a running subagent as a chain root is currently available for top-level async runs only." }],
|
|
1796
|
-
isError: true,
|
|
1797
|
-
details: { mode: "management", results: [] },
|
|
1798
|
-
};
|
|
1799
|
-
}
|
|
1800
|
-
if (resolved.match.run.state === "running" || resolved.match.run.state === "queued") {
|
|
1801
|
-
return resumeLiveNestedRun({ target: resolved, message: followUp });
|
|
1802
|
-
}
|
|
1803
|
-
const trustedSessionRoots = [
|
|
1804
|
-
...(input.deps.config.defaultSessionDir ? [path.resolve(input.deps.expandTilde(input.deps.config.defaultSessionDir))] : []),
|
|
1805
|
-
...(parentSessionFile ? [input.deps.getSubagentSessionRoot(parentSessionFile)] : []),
|
|
1806
|
-
];
|
|
1807
|
-
target = resolveNestedResumeTarget(resolved, trustedSessionRoots);
|
|
1808
|
-
} else {
|
|
1809
|
-
target = resolveResumeTarget(input.params, input.deps.state, { asyncRequireSessionFile: false });
|
|
1810
|
-
}
|
|
1813
|
+
const resolved = resolveRequestedResumeTarget(input.params, input.deps, parentSessionFile);
|
|
1814
|
+
if (resolved.kind === "live-nested") return resumeLiveNestedRun({ target: resolved.target, message: followUp });
|
|
1815
|
+
target = resolved;
|
|
1811
1816
|
} catch (error) {
|
|
1812
1817
|
const message = error instanceof Error ? error.message : String(error);
|
|
1813
1818
|
return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
|
|
@@ -2118,6 +2123,7 @@ async function resumeAsyncRun(input: {
|
|
|
2118
2123
|
availableModels,
|
|
2119
2124
|
output: input.params.output !== undefined ? input.params.output : foregroundContract?.output ?? recoveryDescriptor?.outputPath,
|
|
2120
2125
|
outputMode: input.params.outputMode ?? foregroundContract?.outputMode ?? recoveryDescriptor?.outputMode,
|
|
2126
|
+
outputClaimPath: input.params.workflowOutputClaimPath,
|
|
2121
2127
|
...(agentContract ? { agentContract } : {}),
|
|
2122
2128
|
...(outputSchema ? { structuredOutputSchema: outputSchema } : {}),
|
|
2123
2129
|
...(recoveryDescriptor?.skills ? { skills: [...recoveryDescriptor.skills] } : {}),
|
|
@@ -2194,6 +2200,7 @@ async function resumeAsyncRun(input: {
|
|
|
2194
2200
|
...(completed.structuredOutputSchemaPath ? { structuredOutputSchemaPath: completed.structuredOutputSchemaPath } : {}),
|
|
2195
2201
|
...(completed.acceptance ? { acceptance: completed.acceptance } : {}),
|
|
2196
2202
|
...(completed.artifactPaths ? { artifactPaths: completed.artifactPaths } : {}),
|
|
2203
|
+
...(completed.savedOutputPath ? { savedOutputPath: completed.savedOutputPath } : {}),
|
|
2197
2204
|
...(completed.outputSaveError ? { outputSaveError: completed.outputSaveError } : {}),
|
|
2198
2205
|
...(completed.transcriptPath ? { transcriptPath: completed.transcriptPath } : {}),
|
|
2199
2206
|
...(completed.transcriptError ? { transcriptError: completed.transcriptError } : {}),
|
|
@@ -3169,6 +3176,7 @@ async function waitForWorkflowAsyncSingleResult(
|
|
|
3169
3176
|
...(completed.structuredOutputSchemaPath ? { structuredOutputSchemaPath: completed.structuredOutputSchemaPath } : {}),
|
|
3170
3177
|
...(completed.acceptance ? { acceptance: completed.acceptance } : {}),
|
|
3171
3178
|
...(completed.artifactPaths ? { artifactPaths: completed.artifactPaths } : {}),
|
|
3179
|
+
...(completed.savedOutputPath ? { savedOutputPath: completed.savedOutputPath } : {}),
|
|
3172
3180
|
...(completed.outputSaveError ? { outputSaveError: completed.outputSaveError } : {}),
|
|
3173
3181
|
...(completed.transcriptPath ? { transcriptPath: completed.transcriptPath } : {}),
|
|
3174
3182
|
...(completed.transcriptError ? { transcriptError: completed.transcriptError } : {}),
|
|
@@ -3343,8 +3351,7 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
3343
3351
|
return null;
|
|
3344
3352
|
}
|
|
3345
3353
|
|
|
3346
|
-
function createSingleWorktreeSetup(
|
|
3347
|
-
enabled: boolean | undefined,
|
|
3354
|
+
async function createSingleWorktreeSetup(
|
|
3348
3355
|
cwd: string,
|
|
3349
3356
|
runId: string,
|
|
3350
3357
|
agent: string,
|
|
@@ -3356,12 +3363,13 @@ function createSingleWorktreeSetup(
|
|
|
3356
3363
|
branchPrefix: ExtensionConfig["worktreeBranchPrefix"],
|
|
3357
3364
|
label?: string,
|
|
3358
3365
|
task?: string,
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3366
|
+
onProgress?: (snapshot: WorktreeSetupProgress) => void,
|
|
3367
|
+
signal?: AbortSignal,
|
|
3368
|
+
deadlineAt?: number,
|
|
3369
|
+
): Promise<{ setup?: WorktreeSetup; errorResult?: AgentToolResult<Details> }> {
|
|
3362
3370
|
try {
|
|
3363
3371
|
return {
|
|
3364
|
-
setup: createWorktrees(cwd, runId, 1, omitUndefinedProperties({
|
|
3372
|
+
setup: await createWorktrees(cwd, runId, 1, omitUndefinedProperties({
|
|
3365
3373
|
agents: [agent],
|
|
3366
3374
|
setupHook: setupHook
|
|
3367
3375
|
? { hookPath: setupHook, ...(setupHookTimeoutMs === undefined ? {} : { timeoutMs: setupHookTimeoutMs }) }
|
|
@@ -3372,7 +3380,9 @@ function createSingleWorktreeSetup(
|
|
|
3372
3380
|
branchPrefix,
|
|
3373
3381
|
labels: [label],
|
|
3374
3382
|
tasks: [task],
|
|
3375
|
-
|
|
3383
|
+
onProgress,
|
|
3384
|
+
signal,
|
|
3385
|
+
deadlineAt,
|
|
3376
3386
|
})),
|
|
3377
3387
|
};
|
|
3378
3388
|
} catch (error) {
|
|
@@ -3398,15 +3408,6 @@ function resolveSingleRunOutputBaseDir(deps: ExecutorDeps, artifactsDir: string,
|
|
|
3398
3408
|
return resolveConfiguredSingleRunOutputBaseDir(deps) ?? path.join(artifactsDir, "outputs", sanitizeRunPathSegment(runId));
|
|
3399
3409
|
}
|
|
3400
3410
|
|
|
3401
|
-
function resolveWorkflowAggregateOutputPath(
|
|
3402
|
-
output: string | boolean | undefined,
|
|
3403
|
-
ctxCwd: string,
|
|
3404
|
-
workflowCwd: string,
|
|
3405
|
-
outputBaseDir: string,
|
|
3406
|
-
): string | undefined {
|
|
3407
|
-
return resolveSingleOutputPath(output, ctxCwd, workflowCwd, outputBaseDir);
|
|
3408
|
-
}
|
|
3409
|
-
|
|
3410
3411
|
function workflowChildDefaultOutput(aggregateOutputPath: string | undefined, artifactsDir: string, workflowRunId: string, workflowKey: string): string {
|
|
3411
3412
|
if (aggregateOutputPath) {
|
|
3412
3413
|
const parsed = path.parse(aggregateOutputPath);
|
|
@@ -3468,7 +3469,9 @@ function resolveWorkflowChildOutputPath(input: {
|
|
|
3468
3469
|
key: string;
|
|
3469
3470
|
params: Record<string, unknown>;
|
|
3470
3471
|
}): { path?: string; inherited: boolean } {
|
|
3471
|
-
|
|
3472
|
+
const rawOutput = input.params.output;
|
|
3473
|
+
const hasExplicitOutput = typeof rawOutput === "string" || typeof rawOutput === "boolean";
|
|
3474
|
+
if (typeof input.params.resume === "string" && (!hasExplicitOutput || rawOutput === true || rawOutput === "true")) {
|
|
3472
3475
|
if (!input.state) return { path: undefined, inherited: false };
|
|
3473
3476
|
const index = input.params.index;
|
|
3474
3477
|
const target = resolveResumeTarget({
|
|
@@ -3477,15 +3480,16 @@ function resolveWorkflowChildOutputPath(input: {
|
|
|
3477
3480
|
}, input.state);
|
|
3478
3481
|
return { path: "recoveryDescriptor" in target ? target.recoveryDescriptor?.outputPath : undefined, inherited: false };
|
|
3479
3482
|
}
|
|
3480
|
-
const rawOutput = input.params.output;
|
|
3481
|
-
const hasExplicitOutput = typeof rawOutput === "string" || typeof rawOutput === "boolean";
|
|
3482
3483
|
const childCwd = typeof input.params.cwd === "string" ? resolveChildCwd(input.workflowCwd, input.params.cwd) : input.workflowCwd;
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3484
|
+
let agentOutput: string | undefined;
|
|
3485
|
+
if (rawOutput === true || rawOutput === "true" || (!hasExplicitOutput && !input.aggregateOutputPath)) {
|
|
3486
|
+
const agentScope = resolveExecutionAgentScope(input.params.agentScope ?? input.workflowAgentScope);
|
|
3487
|
+
const discoveredAgents = input.discoverAgents(childCwd, agentScope).agents;
|
|
3488
|
+
const agent = typeof input.params.agent === "string"
|
|
3489
|
+
? resolveAgentName(input.params.agent, discoveredAgents).agent ?? resolveAgentName(input.params.agent, input.agents).agent
|
|
3490
|
+
: undefined;
|
|
3491
|
+
agentOutput = typeof agent?.output === "string" ? agent.output : undefined;
|
|
3492
|
+
}
|
|
3489
3493
|
const output = rawOutput === true || rawOutput === "true"
|
|
3490
3494
|
? agentOutput
|
|
3491
3495
|
: hasExplicitOutput
|
|
@@ -3574,7 +3578,7 @@ function prepareWorkflowChildLaunchParams(input: {
|
|
|
3574
3578
|
childParams = { ...input.childParams, output: input.outputOverride };
|
|
3575
3579
|
} else if (usesDefaultOutput && input.aggregateOutputPath !== undefined) {
|
|
3576
3580
|
childParams = { ...input.childParams, output: workflowChildDefaultOutput(input.aggregateOutputPath, input.artifactsDir, input.parentWorkflowRunId, input.workflowKey) };
|
|
3577
|
-
} else if (input.childParams.resume === undefined) {
|
|
3581
|
+
} else if (input.childParams.resume === undefined || input.childParams.output !== undefined) {
|
|
3578
3582
|
const resolvedOutput = resolveWorkflowChildOutputPath({ ctxCwd: input.ctxCwd, workflowCwd: input.workflowCwd, artifactsDir: input.artifactsDir, workflowRunId: input.parentWorkflowRunId, aggregateOutputPath: input.aggregateOutputPath, configuredOutputBaseDir: input.configuredOutputBaseDir, discoverAgents: input.discoverAgents, agents: input.agents, workflowAgentScope: input.workflowAgentScope, key: input.workflowKey, params: input.childParams });
|
|
3579
3583
|
if (resolvedOutput.path) childParams = { ...input.childParams, output: resolvedOutput.path };
|
|
3580
3584
|
}
|
|
@@ -3588,7 +3592,7 @@ function prepareWorkflowChildLaunchParams(input: {
|
|
|
3588
3592
|
return prepareWorkflowLaunchParams(input.workflowDefaults, childParams, input.parentWorkflowRunId, input.workflowKey, { ...input.options, externalAsyncRequired, outputClaimPath: input.outputClaimPath });
|
|
3589
3593
|
}
|
|
3590
3594
|
|
|
3591
|
-
function finalizeSingleWorktreeHandoff(input: {
|
|
3595
|
+
async function finalizeSingleWorktreeHandoff(input: {
|
|
3592
3596
|
worktreeSetup: WorktreeSetup;
|
|
3593
3597
|
artifactsDir: string;
|
|
3594
3598
|
runId: string;
|
|
@@ -3597,55 +3601,78 @@ function finalizeSingleWorktreeHandoff(input: {
|
|
|
3597
3601
|
result: SingleResult;
|
|
3598
3602
|
workflowKey?: string;
|
|
3599
3603
|
lane?: import("../../shared/types.ts").WorkflowLaneMetadata;
|
|
3600
|
-
}): { suffix: string; reference?: NonNullable<Details["parallelHandoff"]> } {
|
|
3601
|
-
|
|
3602
|
-
const diffs = diffWorktrees(input.worktreeSetup, [input.agent], diffsDir);
|
|
3603
|
-
const diffSummary = formatWorktreeDiffSummary(diffs);
|
|
3604
|
-
const manifestPath = parallelHandoffPath(input.artifactsDir, input.runId);
|
|
3605
|
-
const handoff = {
|
|
3606
|
-
manifestPath,
|
|
3607
|
-
runId: input.runId,
|
|
3608
|
-
mode: "single" as const,
|
|
3609
|
-
source: "foreground" as const,
|
|
3610
|
-
cwd: input.cwd,
|
|
3611
|
-
stepIndex: 0,
|
|
3612
|
-
flatStartIndex: 0,
|
|
3613
|
-
setup: input.worktreeSetup,
|
|
3614
|
-
laneBindings: input.workflowKey || input.lane ? [{ index: 0, taskIndex: 0, ...(input.workflowKey ? { workflowKey: input.workflowKey } : {}), runId: input.workflowKey ? input.runId : undefined, ...(input.lane ? { lane: input.lane } : {}) }] : undefined,
|
|
3615
|
-
diffs,
|
|
3616
|
-
results: [{
|
|
3617
|
-
agent: input.result.agent,
|
|
3618
|
-
...(input.workflowKey ? { workflowKey: input.workflowKey } : {}),
|
|
3619
|
-
...(input.workflowKey ? { runId: input.runId } : {}),
|
|
3620
|
-
...(input.lane ? { lane: input.lane } : {}),
|
|
3621
|
-
status: resolveSubagentResultStatus(omitUndefinedProperties({
|
|
3622
|
-
exitCode: input.result.exitCode,
|
|
3623
|
-
interrupted: input.result.interrupted,
|
|
3624
|
-
detached: input.result.detached,
|
|
3625
|
-
state: input.result.stopped ? "stopped" : undefined,
|
|
3626
|
-
processSignal: input.result.processSignal,
|
|
3627
|
-
timedOut: input.result.timedOut,
|
|
3628
|
-
stopped: input.result.stopped,
|
|
3629
|
-
turnBudgetExceeded: input.result.turnBudgetExceeded,
|
|
3630
|
-
})),
|
|
3631
|
-
summary: resultSummaryForIntercom(input.result),
|
|
3632
|
-
...(input.result.artifactPaths?.outputPath ? { outputPath: input.result.artifactPaths.outputPath } : {}),
|
|
3633
|
-
...(input.result.structuredOutput !== undefined ? { structuredOutput: input.result.structuredOutput } : {}),
|
|
3634
|
-
...(input.result.structuredOutputPath ? { structuredOutputPath: input.result.structuredOutputPath } : {}),
|
|
3635
|
-
...(input.result.sessionFile ? { sessionPath: input.result.sessionFile } : {}),
|
|
3636
|
-
}],
|
|
3637
|
-
};
|
|
3604
|
+
}): Promise<{ suffix: string; reference?: NonNullable<Details["parallelHandoff"]> }> {
|
|
3605
|
+
let admitted = false;
|
|
3638
3606
|
try {
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3607
|
+
return await withWorktreeTransaction(() => {
|
|
3608
|
+
admitted = true;
|
|
3609
|
+
const diffsDir = path.join(input.artifactsDir, "worktree-diffs", input.runId);
|
|
3610
|
+
const diffs = diffWorktrees(input.worktreeSetup, [input.agent], diffsDir);
|
|
3611
|
+
const diffSummary = formatWorktreeDiffSummary(diffs);
|
|
3612
|
+
const manifestPath = parallelHandoffPath(input.artifactsDir, input.runId);
|
|
3613
|
+
const handoff = {
|
|
3614
|
+
manifestPath,
|
|
3615
|
+
runId: input.runId,
|
|
3616
|
+
mode: "single" as const,
|
|
3617
|
+
source: "foreground" as const,
|
|
3618
|
+
cwd: input.cwd,
|
|
3619
|
+
stepIndex: 0,
|
|
3620
|
+
flatStartIndex: 0,
|
|
3621
|
+
setup: input.worktreeSetup,
|
|
3622
|
+
laneBindings: input.workflowKey || input.lane ? [{ index: 0, taskIndex: 0, ...(input.workflowKey ? { workflowKey: input.workflowKey } : {}), runId: input.workflowKey ? input.runId : undefined, ...(input.lane ? { lane: input.lane } : {}) }] : undefined,
|
|
3623
|
+
diffs,
|
|
3624
|
+
results: [{
|
|
3625
|
+
agent: input.result.agent,
|
|
3626
|
+
...(input.workflowKey ? { workflowKey: input.workflowKey } : {}),
|
|
3627
|
+
...(input.workflowKey ? { runId: input.runId } : {}),
|
|
3628
|
+
...(input.lane ? { lane: input.lane } : {}),
|
|
3629
|
+
status: resolveSubagentResultStatus(omitUndefinedProperties({
|
|
3630
|
+
exitCode: input.result.exitCode,
|
|
3631
|
+
interrupted: input.result.interrupted,
|
|
3632
|
+
detached: input.result.detached,
|
|
3633
|
+
state: input.result.stopped ? "stopped" : undefined,
|
|
3634
|
+
processSignal: input.result.processSignal,
|
|
3635
|
+
timedOut: input.result.timedOut,
|
|
3636
|
+
stopped: input.result.stopped,
|
|
3637
|
+
turnBudgetExceeded: input.result.turnBudgetExceeded,
|
|
3638
|
+
})),
|
|
3639
|
+
summary: resultSummaryForIntercom(input.result),
|
|
3640
|
+
...(input.result.artifactPaths?.outputPath ? { outputPath: input.result.artifactPaths.outputPath } : {}),
|
|
3641
|
+
...(input.result.structuredOutput !== undefined ? { structuredOutput: input.result.structuredOutput } : {}),
|
|
3642
|
+
...(input.result.structuredOutputPath ? { structuredOutputPath: input.result.structuredOutputPath } : {}),
|
|
3643
|
+
...(input.result.sessionFile ? { sessionPath: input.result.sessionFile } : {}),
|
|
3644
|
+
}],
|
|
3645
|
+
};
|
|
3646
|
+
try {
|
|
3647
|
+
writeParallelHandoffGroup(handoff);
|
|
3648
|
+
const cleanup = cleanupWorktrees(input.worktreeSetup, { kind: "preserve", capturedDiffs: diffs, handoffManifestPath: manifestPath });
|
|
3649
|
+
const reference = writeParallelHandoffGroup({ ...handoff, cleanup });
|
|
3650
|
+
return {
|
|
3651
|
+
suffix: [diffSummary, formatParallelHandoffReference(reference)].filter(Boolean).join("\n\n"),
|
|
3652
|
+
reference,
|
|
3653
|
+
};
|
|
3654
|
+
} catch (error) {
|
|
3655
|
+
return { suffix: [diffSummary, formatParallelHandoffError(error)].filter(Boolean).join("\n\n") };
|
|
3656
|
+
}
|
|
3657
|
+
});
|
|
3646
3658
|
} catch (error) {
|
|
3647
|
-
|
|
3648
|
-
|
|
3659
|
+
if (admitted) throw error;
|
|
3660
|
+
const reference = retainSingleWorktreeHandoff(input, error);
|
|
3661
|
+
return { suffix: `${formatParallelHandoffError(error)}\n\n${formatParallelHandoffReference(reference)}`, reference };
|
|
3662
|
+
}
|
|
3663
|
+
}
|
|
3664
|
+
|
|
3665
|
+
function retainSingleWorktreeHandoff(input: { worktreeSetup: WorktreeSetup; artifactsDir: string; runId: string; cwd: string }, error: unknown): NonNullable<Details["parallelHandoff"]> {
|
|
3666
|
+
const reason = `Worktree finalization retained; manual reconciliation required: ${error instanceof Error ? error.message : String(error)}`;
|
|
3667
|
+
return writeParallelHandoffGroup({
|
|
3668
|
+
manifestPath: parallelHandoffPath(input.artifactsDir, input.runId), runId: input.runId,
|
|
3669
|
+
mode: "single", source: "foreground", cwd: input.cwd, stepIndex: 0, flatStartIndex: 0,
|
|
3670
|
+
setup: input.worktreeSetup, diffs: [], results: [],
|
|
3671
|
+
cleanup: { state: "partial", pruned: false, errors: [reason], tasks: input.worktreeSetup.worktrees.map((worktree) => ({
|
|
3672
|
+
index: worktree.index, path: worktree.path, branch: worktree.branch, provider: worktree.provider, naming: worktree.naming,
|
|
3673
|
+
worktreeRemoved: false, branchRemoved: false, preserved: true, reason,
|
|
3674
|
+
})) },
|
|
3675
|
+
});
|
|
3649
3676
|
}
|
|
3650
3677
|
|
|
3651
3678
|
async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Promise<AgentToolResult<Details>> {
|
|
@@ -3726,8 +3753,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3726
3753
|
|
|
3727
3754
|
const sourceCwd = effectiveCwd;
|
|
3728
3755
|
let pendingHandoff: Details["parallelHandoff"];
|
|
3729
|
-
const { setup: worktreeSetup, errorResult: worktreeSetupError } = createSingleWorktreeSetup(
|
|
3730
|
-
params.worktree,
|
|
3756
|
+
const { setup: worktreeSetup, errorResult: worktreeSetupError } = params.worktree ? await createSingleWorktreeSetup(
|
|
3731
3757
|
sourceCwd,
|
|
3732
3758
|
runId,
|
|
3733
3759
|
params.agent!,
|
|
@@ -3739,8 +3765,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3739
3765
|
deps.config.worktreeBranchPrefix,
|
|
3740
3766
|
params.lane?.key ?? params.workflowKey,
|
|
3741
3767
|
task,
|
|
3742
|
-
(
|
|
3743
|
-
pendingHandoff =
|
|
3768
|
+
(progress) => {
|
|
3769
|
+
pendingHandoff = writeWorktreeSetupHandoff({
|
|
3744
3770
|
manifestPath: parallelHandoffPath(artifactsDir, runId),
|
|
3745
3771
|
runId,
|
|
3746
3772
|
mode: "single",
|
|
@@ -3748,13 +3774,42 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3748
3774
|
cwd: sourceCwd,
|
|
3749
3775
|
stepIndex: 0,
|
|
3750
3776
|
flatStartIndex: 0,
|
|
3751
|
-
|
|
3777
|
+
progress,
|
|
3752
3778
|
laneBindings: params.workflowKey || lane ? [{ index: 0, taskIndex: 0, ...(params.workflowKey ? { workflowKey: params.workflowKey, runId } : {}), ...(lane ? { lane } : {}) }] : undefined,
|
|
3753
3779
|
});
|
|
3754
3780
|
},
|
|
3755
|
-
|
|
3756
|
-
|
|
3781
|
+
signal,
|
|
3782
|
+
data.deadlineAt,
|
|
3783
|
+
) : {};
|
|
3784
|
+
if (worktreeSetupError) {
|
|
3785
|
+
if (pendingHandoff) {
|
|
3786
|
+
worktreeSetupError.details!.parallelHandoff = pendingHandoff;
|
|
3787
|
+
worktreeSetupError.content.push({ type: "text", text: formatParallelHandoffReference(pendingHandoff) });
|
|
3788
|
+
}
|
|
3789
|
+
return worktreeSetupError;
|
|
3790
|
+
}
|
|
3757
3791
|
const singleCwd = worktreeSetup?.worktrees[0]?.agentCwd ?? sourceCwd;
|
|
3792
|
+
const cleanupSingleWorktree = async () => {
|
|
3793
|
+
if (!worktreeSetup) return;
|
|
3794
|
+
let admitted = false;
|
|
3795
|
+
try { await withWorktreeTransaction(() => {
|
|
3796
|
+
if (data.deadlineAt !== undefined && Date.now() >= data.deadlineAt) throw new Error("Run deadline expired before unlaunched worktree cleanup");
|
|
3797
|
+
admitted = true;
|
|
3798
|
+
const cleanup = cleanupWorktrees(worktreeSetup);
|
|
3799
|
+
pendingHandoff = writeParallelHandoffGroup({
|
|
3800
|
+
manifestPath: parallelHandoffPath(artifactsDir, runId), runId, mode: "single", source: "foreground",
|
|
3801
|
+
cwd: sourceCwd, stepIndex: 0, flatStartIndex: 0, setup: worktreeSetup, diffs: [], results: [], cleanup,
|
|
3802
|
+
});
|
|
3803
|
+
}); }
|
|
3804
|
+
catch (error) {
|
|
3805
|
+
if (admitted) throw error;
|
|
3806
|
+
pendingHandoff = retainSingleWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: sourceCwd }, error);
|
|
3807
|
+
}
|
|
3808
|
+
};
|
|
3809
|
+
if (worktreeSetup && (signal?.aborted || (data.deadlineAt !== undefined && Date.now() >= data.deadlineAt))) {
|
|
3810
|
+
await cleanupSingleWorktree();
|
|
3811
|
+
return { content: [{ type: "text", text: "Subagent setup canceled before child launch." }], isError: true, details: { mode: "single", results: [], parallelHandoff: pendingHandoff } };
|
|
3812
|
+
}
|
|
3758
3813
|
|
|
3759
3814
|
const authoredTask = task;
|
|
3760
3815
|
if (shouldForkAgent(contextPolicy, params.agent!)) {
|
|
@@ -3764,8 +3819,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3764
3819
|
const outputPath = resolveSingleOutputPath(effectiveOutput, ctx.cwd, singleCwd, resolveSingleRunOutputBaseDir(deps, artifactsDir, runId));
|
|
3765
3820
|
const validationError = validateFileOnlyOutputMode(effectiveOutputMode, outputPath, `Single run (${params.agent})`);
|
|
3766
3821
|
if (validationError) {
|
|
3767
|
-
|
|
3768
|
-
return { content: [{ type: "text", text: validationError }], isError: true, details: { mode: "single", results: [] } };
|
|
3822
|
+
await cleanupSingleWorktree();
|
|
3823
|
+
return { content: [{ type: "text", text: validationError }], isError: true, details: { mode: "single", results: [], parallelHandoff: pendingHandoff } };
|
|
3769
3824
|
}
|
|
3770
3825
|
const structuredRuntime = params.outputSchema
|
|
3771
3826
|
? createStructuredOutputRuntime(params.outputSchema, artifactConfig.enabled ? path.join(artifactsDir, "structured-output", runId) : undefined, { acceptanceReport: resolveAcceptanceReportMode(params.acceptance) })
|
|
@@ -3898,14 +3953,14 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3898
3953
|
onDetachReady: (detach) => {
|
|
3899
3954
|
detachForeground = detach;
|
|
3900
3955
|
},
|
|
3901
|
-
onDetachedExit: (result) => {
|
|
3956
|
+
onDetachedExit: async (result) => {
|
|
3902
3957
|
if (resolveDetachedWorkflowChild) {
|
|
3903
3958
|
resolveDetachedWorkflowChild(result);
|
|
3904
3959
|
return;
|
|
3905
3960
|
}
|
|
3906
3961
|
try {
|
|
3907
3962
|
if (worktreeSetup) {
|
|
3908
|
-
finalizeSingleWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: sourceCwd, agent: params.agent!, result, workflowKey: params.workflowKey, lane });
|
|
3963
|
+
await finalizeSingleWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: sourceCwd, agent: params.agent!, result, workflowKey: params.workflowKey, lane });
|
|
3909
3964
|
}
|
|
3910
3965
|
try {
|
|
3911
3966
|
updateRememberedForegroundChild(deps.state, { runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, index: 0, result, events: deps.pi.events, notify: true });
|
|
@@ -3941,12 +3996,13 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3941
3996
|
toolTimeoutMs: params.toolTimeoutMs,
|
|
3942
3997
|
configToolTimeoutMs: data.configToolTimeoutMs,
|
|
3943
3998
|
toolBudget: effectiveToolBudget.toolBudget,
|
|
3999
|
+
usageBudget: data.usageBudget ?? data.inheritedUsageBudget,
|
|
3944
4000
|
capabilityCeiling: data.capabilityCeiling,
|
|
3945
4001
|
allowZeroToolBudget: data.allowZeroToolBudget && effectiveToolBudget.toolBudget === data.toolBudget,
|
|
3946
4002
|
}));
|
|
3947
4003
|
r = launched.detached && detachedWorkflowChild ? await detachedWorkflowChild : launched;
|
|
3948
4004
|
} catch (error) {
|
|
3949
|
-
|
|
4005
|
+
await cleanupSingleWorktree();
|
|
3950
4006
|
throw error;
|
|
3951
4007
|
} finally {
|
|
3952
4008
|
// An attached runSync rejection still owns its child and structured runtime.
|
|
@@ -3961,11 +4017,11 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3961
4017
|
recordRun(params.agent!, cleanTask, r.exitCode, r.progressSummary?.durationMs ?? 0, r);
|
|
3962
4018
|
}
|
|
3963
4019
|
|
|
3964
|
-
let worktreeHandoff: ReturnType<typeof finalizeSingleWorktreeHandoff
|
|
4020
|
+
let worktreeHandoff: Awaited<ReturnType<typeof finalizeSingleWorktreeHandoff>> | undefined;
|
|
3965
4021
|
if (worktreeSetup) {
|
|
3966
4022
|
worktreeHandoff = r.detached
|
|
3967
4023
|
? { suffix: pendingHandoff ? formatParallelHandoffReference(pendingHandoff) : "", reference: pendingHandoff }
|
|
3968
|
-
: finalizeSingleWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: sourceCwd, agent: params.agent!, result: r, workflowKey: params.workflowKey, lane });
|
|
4024
|
+
: await finalizeSingleWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: sourceCwd, agent: params.agent!, result: r, workflowKey: params.workflowKey, lane });
|
|
3969
4025
|
}
|
|
3970
4026
|
|
|
3971
4027
|
if (r.progress) allProgress.push(r.progress);
|
|
@@ -4079,6 +4135,7 @@ function duplicateSubagentCallResult(params: SubagentParamsLike): AgentToolResul
|
|
|
4079
4135
|
}
|
|
4080
4136
|
|
|
4081
4137
|
const workflowLaunchObservers = new WeakMap<object, (launch: { agent: string; sessionName?: string; sessionFile?: string; async: boolean; runId?: string }) => void>();
|
|
4138
|
+
const workflowOwnedUsageBudgets = new WeakMap<object, UsageBudgetConfig>();
|
|
4082
4139
|
|
|
4083
4140
|
/**
|
|
4084
4141
|
* Terminal-mission retention can remove a mission while its children still
|
|
@@ -4204,8 +4261,6 @@ function workflowChildResult(
|
|
|
4204
4261
|
}
|
|
4205
4262
|
const requestedContext = childParams.context === "fresh" || childParams.context === "fork" ? childParams.context : undefined;
|
|
4206
4263
|
const resolvedContext = result.details.context ?? (resolvedContexts.length === 1 ? resolvedContexts[0] : resolvedContexts.length > 1 ? "mixed" : undefined);
|
|
4207
|
-
const resumeSourceRunId = typeof childParams.resume === "string" && childParams.resume.trim() ? childParams.resume.trim() : undefined;
|
|
4208
|
-
const continuationRunIds = [...new Set([resumeSourceRunId, runId].filter((value): value is string => Boolean(value)))];
|
|
4209
4264
|
const outputReference = result.details.results.find((child) => child.savedOutputPath)?.savedOutputPath
|
|
4210
4265
|
?? result.details.results.find((child) => child.outputReference?.path)?.outputReference?.path;
|
|
4211
4266
|
const outputPathMapping = typeof childParams.task === "string" ? outputPathMappingFromTask(childParams.task, outputReference) : undefined;
|
|
@@ -4238,7 +4293,7 @@ function workflowChildResult(
|
|
|
4238
4293
|
...(outputPathMapping ? { outputPathMapping } : {}),
|
|
4239
4294
|
...(externalAdapter ? { externalAdapter } : {}),
|
|
4240
4295
|
resumability,
|
|
4241
|
-
continuation: { runIds:
|
|
4296
|
+
continuation: { runIds: runId ? [runId] : [] },
|
|
4242
4297
|
artifactPaths: [...artifactPaths],
|
|
4243
4298
|
results: result.details.results,
|
|
4244
4299
|
};
|
|
@@ -4351,10 +4406,33 @@ function missingWorkflowReceiptResumeHint(reference: WorkflowReceiptResumeRefere
|
|
|
4351
4406
|
}
|
|
4352
4407
|
}
|
|
4353
4408
|
|
|
4354
|
-
function
|
|
4355
|
-
reference: WorkflowReceiptResumeReference,
|
|
4356
|
-
|
|
4357
|
-
|
|
4409
|
+
function resolveWorkflowResume(
|
|
4410
|
+
reference: WorkflowReceiptResumeReference | string,
|
|
4411
|
+
deps: ExecutorDeps,
|
|
4412
|
+
parentSessionFile: string | null,
|
|
4413
|
+
index?: number,
|
|
4414
|
+
): string | { runId: string; runIds: string[] } {
|
|
4415
|
+
const state = deps.state;
|
|
4416
|
+
if (typeof reference === "string") {
|
|
4417
|
+
const target = resolveRequestedResumeTarget({ id: reference.trim(), ...(index !== undefined ? { index } : {}) }, deps, parentSessionFile);
|
|
4418
|
+
// Live routing stays with action=resume and does not claim retained lineage.
|
|
4419
|
+
if (target.kind !== "revive") return reference.trim();
|
|
4420
|
+
const runId = target.runId;
|
|
4421
|
+
const status = target.source === "async" && target.asyncDir ? readStatus(target.asyncDir) : undefined;
|
|
4422
|
+
if (status?.runId === runId && status.parentWorkflowRunId && status.workflowKey) {
|
|
4423
|
+
// Only follow the validated child's recorded owner; never search other workflows.
|
|
4424
|
+
const receiptPath = workflowReceiptPath(DIRS.async, status.parentWorkflowRunId);
|
|
4425
|
+
try {
|
|
4426
|
+
fs.statSync(receiptPath);
|
|
4427
|
+
} catch (error) {
|
|
4428
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return { runId, runIds: [runId] };
|
|
4429
|
+
throw error;
|
|
4430
|
+
}
|
|
4431
|
+
const entry = readWorkflowReceipt(DIRS.async, status.parentWorkflowRunId).entries[status.workflowKey];
|
|
4432
|
+
if (entry?.latestRunId === runId) return { runId, runIds: entry.continuation.runIds };
|
|
4433
|
+
}
|
|
4434
|
+
return { runId, runIds: [runId] };
|
|
4435
|
+
}
|
|
4358
4436
|
try {
|
|
4359
4437
|
const entry = resolveWorkflowReceiptResumeEntry({
|
|
4360
4438
|
reference,
|
|
@@ -4364,9 +4442,7 @@ function resolveKeyedWorkflowResume(
|
|
|
4364
4442
|
if (target.kind !== "revive") throw new Error(`Workflow receipt child '${reference.key}' latest run '${runId}' is still running.`);
|
|
4365
4443
|
},
|
|
4366
4444
|
});
|
|
4367
|
-
|
|
4368
|
-
if (!runId) throw new Error(`Workflow receipt child '${reference.key}' has no retained run id.`);
|
|
4369
|
-
return { runId, runIds: entry.continuation.runIds };
|
|
4445
|
+
return { runId: entry.latestRunId, runIds: entry.continuation.runIds };
|
|
4370
4446
|
} catch (error) {
|
|
4371
4447
|
const workflowRunId = reference.workflowRunId.trim();
|
|
4372
4448
|
if (isMissingWorkflowReceiptDiagnostic(error, workflowRunId)) {
|
|
@@ -4505,7 +4581,7 @@ export function prepareWorkflowLaunchParams(
|
|
|
4505
4581
|
const outputSchema = Object.hasOwn(childParams, "outputSchema") ? childParams.outputSchema : workflowDefaults.outputSchema;
|
|
4506
4582
|
if (outputSchema !== undefined) assertJsonSchemaObject(outputSchema, "outputSchema");
|
|
4507
4583
|
const agentContract = Object.hasOwn(childParams, "agentContract") ? childParams.agentContract : workflowDefaults.agentContract;
|
|
4508
|
-
if (agentContract !== undefined && !
|
|
4584
|
+
if (agentContract !== undefined && !isAgentContract(agentContract as AgentContract)) throw new Error("agentContract must be { version: 1 }.");
|
|
4509
4585
|
const acceptance = Object.hasOwn(childParams, "acceptance") ? childParams.acceptance : workflowDefaults.acceptance;
|
|
4510
4586
|
const output = Object.hasOwn(childParams, "output") ? childParams.output : workflowDefaults.output;
|
|
4511
4587
|
if (output !== undefined && typeof output !== "string" && typeof output !== "boolean") throw new Error("output must be a path string or boolean.");
|
|
@@ -4519,6 +4595,7 @@ export function prepareWorkflowLaunchParams(
|
|
|
4519
4595
|
message: typeof childParams.task === "string" ? childParams.task.trim() : "",
|
|
4520
4596
|
workflowParentRunId: parentWorkflowRunId,
|
|
4521
4597
|
workflowKey,
|
|
4598
|
+
...(options.outputClaimPath ? { workflowOutputClaimPath: options.outputClaimPath } : {}),
|
|
4522
4599
|
...(lane ? { lane } : {}),
|
|
4523
4600
|
...(worktree !== undefined ? { worktree: worktree as boolean } : {}),
|
|
4524
4601
|
...(baseRef !== undefined ? { baseRef: baseRef as string } : {}),
|
|
@@ -4681,14 +4758,14 @@ function applyWorkflowLaneTrace(graph: WorkflowGraphSnapshot, trace: WorkflowScr
|
|
|
4681
4758
|
else delete graph.currentNodeId;
|
|
4682
4759
|
}
|
|
4683
4760
|
|
|
4684
|
-
function
|
|
4761
|
+
function workflowProgressUpdate(
|
|
4685
4762
|
runId: string,
|
|
4686
4763
|
chatProgress: WorkflowChatProgressProjection,
|
|
4687
4764
|
workflow: NonNullable<Details["workflow"]>,
|
|
4688
4765
|
workflowChildren?: Details["workflowChildren"],
|
|
4689
|
-
preflight?: import("../../shared/types.ts").
|
|
4690
|
-
): AgentToolResult<Details>
|
|
4691
|
-
|
|
4766
|
+
preflight?: import("../../shared/types.ts").WorkflowPreflight,
|
|
4767
|
+
): AgentToolResult<Details> {
|
|
4768
|
+
// chatProgress controls the TUI projection, not transport-level progress.
|
|
4692
4769
|
return {
|
|
4693
4770
|
content: [{ type: "text", text: "Workflow running." }],
|
|
4694
4771
|
details: { mode: "workflow", runId, results: [], workflow, ...(preflight ? { preflight } : {}), ...(workflowChildren ? { workflowChildren } : {}), chatProgress },
|
|
@@ -4764,6 +4841,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4764
4841
|
signal: AbortSignal,
|
|
4765
4842
|
ctx: ExtensionContext,
|
|
4766
4843
|
) => Promise<AgentToolResult<Details>>;
|
|
4844
|
+
/** Scheduled state visible to the current runtime supervisor owner only. */
|
|
4845
|
+
getCurrentSupervisorOwnerStates: () => Iterable<SubagentState>;
|
|
4767
4846
|
} {
|
|
4768
4847
|
const delegatedThinkingOverrides = new WeakMap<object, AgentConfig["thinking"]>();
|
|
4769
4848
|
const delegatedZeroToolBudgets = new WeakSet<object>();
|
|
@@ -4772,7 +4851,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4772
4851
|
const workflowResourcePermits = new WeakMap<object, WorkflowResourcePermit>();
|
|
4773
4852
|
const workflowPermitContexts = new WeakMap<object, { root: WorkflowChildPermit } | { child: WorkflowChildPermitContext }>();
|
|
4774
4853
|
const warnedArtifactPackageDirs = new Set<string>();
|
|
4775
|
-
const scheduledOwnerExecutors = new Map<string, ReturnType<typeof createSubagentExecutor>>();
|
|
4854
|
+
const scheduledOwnerExecutors = new Map<string | null, Map<string, { state: SubagentState; executor: ReturnType<typeof createSubagentExecutor> }>>();
|
|
4776
4855
|
const execute = async (
|
|
4777
4856
|
_id: string,
|
|
4778
4857
|
params: SubagentParamsLike,
|
|
@@ -4783,6 +4862,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4783
4862
|
parentModelOverride?: ParentModel | null,
|
|
4784
4863
|
): Promise<AgentToolResult<Details>> => {
|
|
4785
4864
|
const workflowLaunchObserver = workflowLaunchObservers.get(params);
|
|
4865
|
+
const inheritedUsageBudget = workflowOwnedUsageBudgets.get(params);
|
|
4786
4866
|
const delegatedThinkingOverride = delegatedThinkingOverrides.get(params);
|
|
4787
4867
|
const allowZeroToolBudget = delegatedZeroToolBudgets.has(params);
|
|
4788
4868
|
const delegatedExecution = delegatedExecutions.has(params);
|
|
@@ -4800,7 +4880,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4800
4880
|
let requestParams = normalizedGate.params;
|
|
4801
4881
|
const capacityOverrideError = validateWorkflowCapacityOverrides(requestParams);
|
|
4802
4882
|
if (capacityOverrideError) return buildRequestedModeError(requestParams, capacityOverrideError);
|
|
4803
|
-
let workflowPreflight: import("../../shared/types.ts").
|
|
4883
|
+
let workflowPreflight: import("../../shared/types.ts").WorkflowPreflight | undefined;
|
|
4804
4884
|
try {
|
|
4805
4885
|
if (requestParams.preflight !== undefined && requestParams.workflowScript === undefined && requestParams.workflowScriptPath === undefined) {
|
|
4806
4886
|
throw new Error("preflight requires workflowScript or workflowScriptPath.");
|
|
@@ -4831,7 +4911,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4831
4911
|
}
|
|
4832
4912
|
const normalizedAction = typeof requestParams.action === "string" ? requestParams.action.trim() : requestParams.action;
|
|
4833
4913
|
if (normalizedAction === "resume" && requestParams.extensionBindings !== undefined) return buildRequestedModeError(requestParams, "extensionBindings is not supported with action='resume'; resume uses the original retained child binding.");
|
|
4834
|
-
let workflowResource: { permit: WorkflowResourcePermit; provenance:
|
|
4914
|
+
let workflowResource: { permit: WorkflowResourcePermit; provenance: WorkflowResourceProvenance; authority: WorkflowResourceAuthority } | undefined;
|
|
4835
4915
|
if (workflowResourcePermit) {
|
|
4836
4916
|
if (typeof requestParams.workflowScript !== "string") return buildRequestedModeError(requestParams, "Resolved workflow resource is missing its workflow script.");
|
|
4837
4917
|
const consumed = consumeWorkflowResourcePermit(workflowResourcePermit, requestParams.workflowScript);
|
|
@@ -4839,6 +4919,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4839
4919
|
workflowResource = { permit: workflowResourcePermit, ...consumed };
|
|
4840
4920
|
}
|
|
4841
4921
|
if (requestParams.workflowScript !== undefined && normalizedAction === undefined) {
|
|
4922
|
+
const acceptanceErrors = validateAcceptanceInput(requestParams.acceptance);
|
|
4923
|
+
if (acceptanceErrors.length > 0) return buildRequestedModeError(requestParams, acceptanceErrors.join(" "));
|
|
4842
4924
|
const foregroundWorkflowRunId = encodeIndexSegment(_id);
|
|
4843
4925
|
if (delegatedWorkflowPermit) {
|
|
4844
4926
|
const permitError = validateWorkflowChildPermitRoot(delegatedWorkflowPermit, foregroundWorkflowRunId);
|
|
@@ -5016,18 +5098,31 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5016
5098
|
onSuccess: (_filePath, payload) => { indexedState = (payload as { state: AsyncStatus["state"] }).state; },
|
|
5017
5099
|
onError: (error, filePath) => console.error(`Failed to update async workflow index '${filePath}':`, error),
|
|
5018
5100
|
});
|
|
5019
|
-
const queueActiveRunIndex = (): void => {
|
|
5020
|
-
const state =
|
|
5101
|
+
const queueActiveRunIndex = (snapshot: AsyncStatus = status): void => {
|
|
5102
|
+
const state = snapshot.state;
|
|
5021
5103
|
if (indexedState === state && indexPersistence.pendingCount() === 0) return;
|
|
5022
|
-
indexPersistence.write(asyncDir, { state, toolCallId:
|
|
5104
|
+
indexPersistence.write(asyncDir, { state, toolCallId: snapshot.toolCallId }, (_filePath, payload) => {
|
|
5023
5105
|
const indexPayload = payload as { state: AsyncStatus["state"]; toolCallId?: string };
|
|
5024
5106
|
updateActiveRunIndex(asyncDir, indexPayload.state, indexPayload.toolCallId, { retryCapacityErrors: true });
|
|
5025
5107
|
});
|
|
5026
5108
|
};
|
|
5109
|
+
let pendingResultPublication: Promise<boolean> | undefined;
|
|
5110
|
+
let settleResultPublication: ((published: boolean) => void) | undefined;
|
|
5111
|
+
const reportResultWriteFailure = (error: unknown): void => {
|
|
5112
|
+
const message = `Failed to write async workflow result ${resultPath}: ${error instanceof Error ? error.message : String(error)}`;
|
|
5113
|
+
console.error(message, error);
|
|
5114
|
+
appendWorkflowEvent({ type: "subagent.workflow.result_write_failed", error: message });
|
|
5115
|
+
};
|
|
5027
5116
|
const runPersistence = createCapacityResilientJsonWriter({
|
|
5028
5117
|
keepAlive: true,
|
|
5029
|
-
onSuccess: (filePath) => {
|
|
5030
|
-
|
|
5118
|
+
onSuccess: (filePath, payload) => {
|
|
5119
|
+
if (filePath === statusPath) queueActiveRunIndex(payload as AsyncStatus);
|
|
5120
|
+
if (filePath === resultPath) settleResultPublication?.(true);
|
|
5121
|
+
},
|
|
5122
|
+
onError: (error, filePath) => {
|
|
5123
|
+
if (filePath === resultPath) { reportResultWriteFailure(error); settleResultPublication?.(false); }
|
|
5124
|
+
else console.error(`Failed to persist async workflow state '${filePath}':`, error);
|
|
5125
|
+
},
|
|
5031
5126
|
write: (filePath, payload) => filePath === resultPath
|
|
5032
5127
|
? writeAsyncResultFile(filePath, payload as Record<string, unknown>)
|
|
5033
5128
|
: writeAtomicJson(filePath, payload),
|
|
@@ -5055,7 +5150,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5055
5150
|
queueActiveRunIndex();
|
|
5056
5151
|
} else if (options.tolerateStatusWriteFailure) {
|
|
5057
5152
|
try {
|
|
5058
|
-
runPersistence.write(statusPath, status);
|
|
5153
|
+
runPersistence.write(statusPath, { ...status });
|
|
5059
5154
|
statusPersistenceDegraded = false;
|
|
5060
5155
|
} catch (error) {
|
|
5061
5156
|
const message = `Failed to persist async workflow state ${statusPath}: ${error instanceof Error ? error.message : String(error)}`;
|
|
@@ -5070,7 +5165,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5070
5165
|
}
|
|
5071
5166
|
}
|
|
5072
5167
|
} else {
|
|
5073
|
-
runPersistence.write(statusPath, status);
|
|
5168
|
+
runPersistence.write(statusPath, { ...status });
|
|
5074
5169
|
}
|
|
5075
5170
|
if (liveJob) {
|
|
5076
5171
|
liveJob.status = status.state;
|
|
@@ -5098,12 +5193,15 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5098
5193
|
};
|
|
5099
5194
|
const writeWorkflowResult = (payload: Record<string, unknown>): boolean => {
|
|
5100
5195
|
try {
|
|
5196
|
+
let resultPublished = false;
|
|
5197
|
+
settleResultPublication = (published) => { resultPublished = published; };
|
|
5198
|
+
pendingResultPublication = undefined;
|
|
5101
5199
|
runPersistence.write(resultPath, payload);
|
|
5200
|
+
// Only capacity deferral yields; ordinary publication remains synchronous.
|
|
5201
|
+
if (!resultPublished) pendingResultPublication = new Promise<boolean>((resolve) => { settleResultPublication = resolve; });
|
|
5102
5202
|
return true;
|
|
5103
5203
|
} catch (error) {
|
|
5104
|
-
|
|
5105
|
-
console.error(message, error);
|
|
5106
|
-
appendWorkflowEvent({ type: "subagent.workflow.result_write_failed", error: message });
|
|
5204
|
+
reportResultWriteFailure(error);
|
|
5107
5205
|
return false;
|
|
5108
5206
|
}
|
|
5109
5207
|
};
|
|
@@ -5143,7 +5241,148 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5143
5241
|
return { content: [{ type: "text", text: `Failed to create async workflow storage: ${error instanceof Error ? error.message : String(error)}` }], isError: true, details: { mode: "workflow", results: [] } };
|
|
5144
5242
|
}
|
|
5145
5243
|
appendWorkflowEvent({ type: "subagent.workflow.started" });
|
|
5244
|
+
// The runner does not execute workflow scripts: this active owner must consume its own
|
|
5245
|
+
// durable steer inbox, deliver through in-process child controls, and persist receipts.
|
|
5246
|
+
const emitWorkflowSteerEvent = (type: string, requestId: string, index?: number, extra: Record<string, unknown> = {}): void => {
|
|
5247
|
+
appendWorkflowEvent({ type, requestId, ...(index !== undefined ? { index } : {}), ...extra });
|
|
5248
|
+
};
|
|
5249
|
+
const recordWorkflowSteerTargets = (request: SteerRequest, targets: Array<{ index: number; state: SteeringTargetState; reason?: string }>): void => {
|
|
5250
|
+
status.steering ??= createSteeringStatus();
|
|
5251
|
+
recordSteeringRequest(steeringStatus(status), {
|
|
5252
|
+
id: request.id,
|
|
5253
|
+
requestedAt: request.ts,
|
|
5254
|
+
...(request.source ? { source: request.source } : {}),
|
|
5255
|
+
message: request.message,
|
|
5256
|
+
targets,
|
|
5257
|
+
});
|
|
5258
|
+
};
|
|
5259
|
+
const explicitWorkflowSteerIndexes = (request: SteerRequest): number[] | undefined => request.targetIndex !== undefined ? [request.targetIndex] : request.targetIndexes;
|
|
5260
|
+
const failWorkflowSteer = (request: SteerRequest, indexes: number[], reason: string): void => {
|
|
5261
|
+
const targets = indexes.map((index) => ({ index, state: "failed" as const, reason }));
|
|
5262
|
+
recordWorkflowSteerTargets(request, targets);
|
|
5263
|
+
emitWorkflowSteerEvent("subagent.steer.requested", request.id, undefined, { targets });
|
|
5264
|
+
for (const index of indexes) emitWorkflowSteerEvent("subagent.steer.failed", request.id, index, { reason });
|
|
5265
|
+
persist({ tolerateStatusWriteFailure: true });
|
|
5266
|
+
};
|
|
5267
|
+
const pendingWorkflowSteers = new Set<(state: "delivered" | "queued" | "failed", reason?: string) => void>();
|
|
5268
|
+
const deliverWorkflowSteerRequest = (request: SteerRequest): void => {
|
|
5269
|
+
let indexes = explicitWorkflowSteerIndexes(request);
|
|
5270
|
+
if (status.state !== "running") {
|
|
5271
|
+
failWorkflowSteer(request, indexes ?? [0], `run became ${status.state} before steering request was consumed`);
|
|
5272
|
+
return;
|
|
5273
|
+
}
|
|
5274
|
+
const liveControls = [...deps.state.foregroundControls.values()].filter((candidate) => candidate.parentWorkflowRunId === workflowRunId && (candidate.activeChildren?.size ?? 0) > 0);
|
|
5275
|
+
if (indexes === undefined) {
|
|
5276
|
+
if (liveControls.length !== 1) {
|
|
5277
|
+
failWorkflowSteer(request, [0], liveControls.length > 1
|
|
5278
|
+
? `workflow has ${liveControls.length} live children; steer a child run id instead`
|
|
5279
|
+
: "workflow child is not live in this process");
|
|
5280
|
+
return;
|
|
5281
|
+
}
|
|
5282
|
+
const index = status.steps?.findIndex((step) => !!step.workflowKey && step.workflowKey === liveControls[0]!.workflowKey) ?? -1;
|
|
5283
|
+
if (index < 0) {
|
|
5284
|
+
failWorkflowSteer(request, [0], "live workflow child has no projected step");
|
|
5285
|
+
return;
|
|
5286
|
+
}
|
|
5287
|
+
indexes = [index];
|
|
5288
|
+
}
|
|
5289
|
+
// Normalize and record the whole set once: recordSteeringRequest is idempotent by id.
|
|
5290
|
+
// Explicit indexes resolve only by their projected key, never by a live sibling.
|
|
5291
|
+
const resolved = indexes.map((index) => {
|
|
5292
|
+
const step = status.steps?.[index];
|
|
5293
|
+
const control = step?.workflowKey ? liveControls.find((candidate) => candidate.workflowKey === step.workflowKey) : undefined;
|
|
5294
|
+
return { index, control, reason: !step ? "child index out of range" : !control ? "workflow child is not live in this process" : undefined };
|
|
5295
|
+
});
|
|
5296
|
+
const targets = resolved.map(({ index, reason }) => ({ index, state: reason ? "failed" as const : "routed" as const, ...(reason ? { reason } : {}) }));
|
|
5297
|
+
recordWorkflowSteerTargets(request, targets);
|
|
5298
|
+
emitWorkflowSteerEvent("subagent.steer.requested", request.id, undefined, { targets });
|
|
5299
|
+
persist({ tolerateStatusWriteFailure: true });
|
|
5300
|
+
for (const { index, control, reason } of resolved) {
|
|
5301
|
+
if (!control) {
|
|
5302
|
+
emitWorkflowSteerEvent("subagent.steer.failed", request.id, index, { reason });
|
|
5303
|
+
continue;
|
|
5304
|
+
}
|
|
5305
|
+
emitWorkflowSteerEvent("subagent.steer.routed", request.id, index);
|
|
5306
|
+
const applyWorkflowSteerDelivery = (state: "delivered" | "queued" | "failed", reason?: string): void => {
|
|
5307
|
+
// Shutdown settles unresolved receipts synchronously; late SDK callbacks cannot rewrite them.
|
|
5308
|
+
if (!pendingWorkflowSteers.has(applyWorkflowSteerDelivery)) return;
|
|
5309
|
+
// Queuing is not confirmation of delivery: retain ownership until terminal settlement.
|
|
5310
|
+
if (state !== "queued") pendingWorkflowSteers.delete(applyWorkflowSteerDelivery);
|
|
5311
|
+
const now = Date.now();
|
|
5312
|
+
const steering = steeringStatus(status);
|
|
5313
|
+
// This callback owns a pending target, even after bounded display history
|
|
5314
|
+
// evicts its request. Account independently; queued still represents one pending target.
|
|
5315
|
+
if (state !== "queued") steering.pending = Math.max(0, steering.pending - 1);
|
|
5316
|
+
if (state === "delivered") {
|
|
5317
|
+
steering.delivered++;
|
|
5318
|
+
steering.lastDeliveredAt = now;
|
|
5319
|
+
} else if (state === "failed") steering.failed++;
|
|
5320
|
+
const receipt = steering.recent.find((candidate) => candidate.id === request.id)?.targets.find((target) => target.index === index);
|
|
5321
|
+
if (receipt) {
|
|
5322
|
+
receipt.state = state;
|
|
5323
|
+
if (state === "delivered") receipt.deliveredAt = now;
|
|
5324
|
+
if (state === "failed") receipt.failedAt = now;
|
|
5325
|
+
if (reason) receipt.reason = reason;
|
|
5326
|
+
}
|
|
5327
|
+
if (state === "failed") {
|
|
5328
|
+
const failedStep = status.steps?.[index];
|
|
5329
|
+
if (failedStep) failedStep.activityState = "needs_attention";
|
|
5330
|
+
status.activityState = "needs_attention";
|
|
5331
|
+
}
|
|
5332
|
+
emitWorkflowSteerEvent(`subagent.steer.${state}`, request.id, index, state === "failed" ? { reason } : { deliveryStatus: state, message: request.message });
|
|
5333
|
+
persist({ tolerateStatusWriteFailure: true });
|
|
5334
|
+
};
|
|
5335
|
+
pendingWorkflowSteers.add(applyWorkflowSteerDelivery);
|
|
5336
|
+
void steerWorkflowForegroundTarget({
|
|
5337
|
+
target: { control, workflowRunId, sourceRunId: workflowRunId },
|
|
5338
|
+
message: request.message,
|
|
5339
|
+
...(request.mode ? { mode: request.mode } : {}),
|
|
5340
|
+
}).then(
|
|
5341
|
+
(result) => {
|
|
5342
|
+
const target = result.details.steering?.targets[0];
|
|
5343
|
+
applyWorkflowSteerDelivery(target?.state === "delivered" ? "delivered" : target?.state === "queued" ? "queued" : "failed", target?.reason ?? (target ? undefined : "steering produced no target receipt"));
|
|
5344
|
+
},
|
|
5345
|
+
(error) => applyWorkflowSteerDelivery("failed", error instanceof Error ? error.message : String(error)),
|
|
5346
|
+
);
|
|
5347
|
+
}
|
|
5348
|
+
};
|
|
5349
|
+
let disposeWorkflowSteerInbox: (() => void) | undefined;
|
|
5350
|
+
try {
|
|
5351
|
+
disposeWorkflowSteerInbox = watchAsyncControlInbox(asyncDir, {
|
|
5352
|
+
onSteer: deliverWorkflowSteerRequest,
|
|
5353
|
+
onError: (error, phase, request) => {
|
|
5354
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
5355
|
+
appendWorkflowEvent({ type: "subagent.workflow.steer_inbox_unavailable", phase, error: reason });
|
|
5356
|
+
if (request) failWorkflowSteer(request, explicitWorkflowSteerIndexes(request) ?? [0], `steer consumer failed: ${reason}`);
|
|
5357
|
+
},
|
|
5358
|
+
});
|
|
5359
|
+
} catch (error) {
|
|
5360
|
+
// Steering is an optional control surface, so a watcher that cannot install must not fail the run.
|
|
5361
|
+
appendWorkflowEvent({ type: "subagent.workflow.steer_inbox_unavailable", error: error instanceof Error ? error.message : String(error) });
|
|
5362
|
+
}
|
|
5363
|
+
deps.activateSupervisorTransport?.();
|
|
5146
5364
|
const { workflowScript, async: _workflowAsync, chatProgress: _chatProgress, ...workflowRequest } = requestParams;
|
|
5365
|
+
let workflowSteerInboxClosed = false;
|
|
5366
|
+
const settleWorkflowSteerInbox = (outcome = status.state): void => {
|
|
5367
|
+
if (workflowSteerInboxClosed) return;
|
|
5368
|
+
workflowSteerInboxClosed = true;
|
|
5369
|
+
disposeWorkflowSteerInbox?.();
|
|
5370
|
+
// Consumed deliveries are not files anymore. Fail unresolved SDK operations without awaiting them.
|
|
5371
|
+
for (const settle of pendingWorkflowSteers) settle("failed", `run became ${outcome} before steering delivery settled; delivery unconfirmed`);
|
|
5372
|
+
try {
|
|
5373
|
+
closeSteerInbox(asyncDir, outcome);
|
|
5374
|
+
// Separately drain requests that never reached the consumer.
|
|
5375
|
+
const undelivered = consumeSteerRequests(asyncDir, undefined, (error) => {
|
|
5376
|
+
appendWorkflowEvent({ type: "subagent.workflow.steer_inbox_unavailable", phase: "drain", error: error instanceof Error ? error.message : String(error) });
|
|
5377
|
+
});
|
|
5378
|
+
for (const request of undelivered) failWorkflowSteer(request, explicitWorkflowSteerIndexes(request) ?? [0], `run became ${outcome} before steering request was consumed`);
|
|
5379
|
+
} catch (error) {
|
|
5380
|
+
appendWorkflowEvent({ type: "subagent.workflow.steer_inbox_unavailable", phase: "close", error: error instanceof Error ? error.message : String(error) });
|
|
5381
|
+
}
|
|
5382
|
+
// Commit receipts while run status is still nonterminal. Result/index publication
|
|
5383
|
+
// can fail next; teardown must not publish the subsequently computed terminal status.
|
|
5384
|
+
persist();
|
|
5385
|
+
};
|
|
5147
5386
|
void Promise.resolve().then(async () => {
|
|
5148
5387
|
const workflowDeadlineAt = timeout === undefined ? undefined : Date.now() + timeout;
|
|
5149
5388
|
const workflowResults: SingleResult[] = [];
|
|
@@ -5151,7 +5390,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5151
5390
|
const { action: _action, agent: _agent, task: _task, resume: _resume, tasks: _tasks, chain: _chain, concurrency: _concurrency, foregroundOnly: _foregroundOnly, clarify: _clarify, timeoutMs: _timeoutMs, maxRuntimeMs: _maxRuntimeMs, usageBudget: _usageBudget, missionId: _missionId, mission: _mission, preflight: _preflight, globalConcurrencyLimit: _globalConcurrencyLimit, maxSubagentSpawnsPerRun: _maxSubagentSpawnsPerRun, ...workflowChildDefaults } = workflowRequest;
|
|
5152
5391
|
const workflowOutput = typeof workflowChildDefaults.output === "string" || typeof workflowChildDefaults.output === "boolean" ? workflowChildDefaults.output : undefined;
|
|
5153
5392
|
const configuredOutputBaseDir = resolveConfiguredSingleRunOutputBaseDir(deps);
|
|
5154
|
-
const workflowAggregateOutputPath =
|
|
5393
|
+
const workflowAggregateOutputPath = resolveSingleOutputPath(workflowOutput, parentCwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, workflowRunId));
|
|
5155
5394
|
const claimedOutputPaths = new Map<string, string>();
|
|
5156
5395
|
const childOutputOverrides = new Map<string, string>();
|
|
5157
5396
|
const childOutputClaimPaths = new Map<string, string>();
|
|
@@ -5336,6 +5575,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5336
5575
|
deps.asyncByDefault,
|
|
5337
5576
|
);
|
|
5338
5577
|
preparedChildParams = childRequest;
|
|
5578
|
+
if (workflowUsageBudget.budget) workflowOwnedUsageBudgets.set(childRequest, workflowUsageBudget.budget);
|
|
5339
5579
|
workflowLaunchObservers.set(childRequest, (launch) => {
|
|
5340
5580
|
const step = status.steps?.find((candidate) => candidate.workflowKey === key);
|
|
5341
5581
|
if (step) {
|
|
@@ -5406,7 +5646,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5406
5646
|
return child;
|
|
5407
5647
|
},
|
|
5408
5648
|
status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession, workflowParentModel)),
|
|
5409
|
-
resolveResume: (reference) =>
|
|
5649
|
+
resolveResume: (reference, _signal, index) => resolveWorkflowResume(reference, deps, ctx.sessionManager.getSessionFile() ?? null, index),
|
|
5410
5650
|
steer: (key, message, options, workflowSignal) => steerWorkflowChildByKey({ state: deps.state, workflowRunId, key, message, options, signal: workflowSignal, resolveRunId: () => workflowChildRunIds.get(key) }),
|
|
5411
5651
|
});
|
|
5412
5652
|
const finalPreflightWarnings = workflowPreflightWarnings(workflowPreflight, workflow.trace, { settled: true });
|
|
@@ -5417,16 +5657,20 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5417
5657
|
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, summary, producedChildOutputPaths);
|
|
5418
5658
|
const resultSummary = appendWorkflowOutputWarning(summary, outputWarning);
|
|
5419
5659
|
const workflowUsage = sumResultsUsage(workflowResults);
|
|
5660
|
+
settleWorkflowSteerInbox("complete");
|
|
5420
5661
|
const workflowChildren = workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState: "completed", inventoryComplete: true, trace: workflow.trace, children: workflow.children, steps: status.steps });
|
|
5421
5662
|
status = { ...status, state: "complete", endedAt: Date.now(), workflow: { value: workflow.value, trace: finalPreflightTrace, emits: workflow.emits, console: workflow.console, ...(workflowResource ? { resource: workflowResource.provenance } : {}), ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}) }, workflowChildren, totalTokens: { input: workflowUsage.input, output: workflowUsage.output, total: workflowUsage.input + workflowUsage.output }, totalCost: sumResultsCost(workflowResults) };
|
|
5422
5663
|
const receipt = terminalWorkflowReceipt(workflowRunId, "complete", workflow.children, workflowChildren, undefined, validHostStepNodes(status.workflowGraph), workflowResource?.provenance);
|
|
5664
|
+
delete status.workflowReceiptPath;
|
|
5423
5665
|
let workflowReceipt: { path: string; receipt: WorkflowReceipt } | undefined;
|
|
5424
5666
|
try {
|
|
5425
5667
|
workflowReceipt = { path: writeWorkflowReceipt(asyncDir, receipt), receipt };
|
|
5668
|
+
status.workflowReceiptPath = workflowReceipt.path;
|
|
5426
5669
|
} catch (receiptError) {
|
|
5427
5670
|
appendWorkflowEvent({ type: "subagent.workflow.receipt_write_failed", error: `Failed to persist async workflow receipt: ${receiptError instanceof Error ? receiptError.message : String(receiptError)}` });
|
|
5428
5671
|
}
|
|
5429
5672
|
if (!writeWorkflowResult({ id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: true, state: "complete", summary: resultSummary, output: resultSummary, workflowChildren, results: workflow.children.map((child) => ({ workflowKey: child.key, ...(child.agent ? { agent: child.agent } : {}), ...(child.runId ? { runId: child.runId } : {}), ...(status.steps?.find((step) => step.workflowKey === child.key)?.sessionName ? { sessionName: status.steps?.find((step) => step.workflowKey === child.key)?.sessionName } : {}), ...workflowChildAccountingFields(child), output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, success: child.ok, ...(child.outputReference ? { outputReference: child.outputReference } : {}), ...(child.outputPathMapping ? { outputPathMapping: child.outputPathMapping } : {}), ...(child.stopped ? { stopped: true } : {}), ...(child.interrupted ? { interrupted: true } : {}), ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, ...(workflowReceipt ? { workflowReceipt } : {}), asyncDir, cwd: workflowCwd, sessionId: currentSessionId, completionOwnerId, ...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}), timestamp: Date.now(), durationMs: Date.now() - startedAt })) return;
|
|
5673
|
+
if (pendingResultPublication && !await pendingResultPublication) return;
|
|
5430
5674
|
persist();
|
|
5431
5675
|
deps.refreshResultDelivery?.();
|
|
5432
5676
|
persistClosed = true;
|
|
@@ -5450,10 +5694,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5450
5694
|
}
|
|
5451
5695
|
}
|
|
5452
5696
|
const workflowState = state === "paused" ? "paused" : state === "stopped" ? "stopped" : "failed";
|
|
5697
|
+
settleWorkflowSteerInbox(state);
|
|
5453
5698
|
const workflowChildren = workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState, inventoryComplete: true, trace: partial.trace, children: partial.children, steps: status.steps });
|
|
5454
5699
|
const finalPreflightWarnings = workflowPreflightWarnings(workflowPreflight, partial.trace, { settled: true });
|
|
5455
5700
|
const finalPreflightTrace = annotateWorkflowPreflightTrace(partial.trace, workflowPreflight);
|
|
5456
|
-
status = compactOptional<AsyncStatus>({ ...status, state, stopped: stopped || undefined, activityState: pauseForDetached ? "needs_attention" : undefined, error: workflowFailureMessage(error, workflowRunId, partial.children), endedAt: Date.now(), workflow: { trace: finalPreflightTrace, emits: partial.emits, console: partial.console, ...(workflowResource ? { resource: workflowResource.provenance } : {}), ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}) }, workflowChildren });
|
|
5701
|
+
status = compactOptional<AsyncStatus>({ ...status, state, stopped: stopped || undefined, activityState: pauseForDetached || status.activityState === "needs_attention" ? "needs_attention" : undefined, error: workflowFailureMessage(error, workflowRunId, partial.children), endedAt: Date.now(), workflow: { trace: finalPreflightTrace, emits: partial.emits, console: partial.console, ...(workflowResource ? { resource: workflowResource.provenance } : {}), ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}) }, workflowChildren });
|
|
5457
5702
|
if (pauseForDetached) {
|
|
5458
5703
|
const promoted = promotePausedWorkflowIfSettled(status);
|
|
5459
5704
|
if (promoted) status = promoted;
|
|
@@ -5466,18 +5711,27 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5466
5711
|
const receiptState: WorkflowReceiptState = status.state === "complete" ? "complete" : status.state === "paused" ? "paused" : status.state === "stopped" ? "stopped" : "failed";
|
|
5467
5712
|
const terminalOutcome = workflowFailureTerminalOutcome(error, partial.children, usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)));
|
|
5468
5713
|
const receipt = terminalWorkflowReceipt(workflowRunId, receiptState, partial.children, workflowChildren, terminalOutcome, validHostStepNodes(status.workflowGraph), workflowResource?.provenance);
|
|
5714
|
+
delete status.workflowReceiptPath;
|
|
5469
5715
|
let workflowReceipt: { path: string; receipt: WorkflowReceipt } | undefined;
|
|
5470
5716
|
try {
|
|
5471
5717
|
workflowReceipt = { path: writeWorkflowReceipt(asyncDir, receipt), receipt };
|
|
5718
|
+
status.workflowReceiptPath = workflowReceipt.path;
|
|
5472
5719
|
} catch (receiptError) {
|
|
5473
5720
|
appendWorkflowEvent({ type: "subagent.workflow.receipt_write_failed", error: `Failed to persist async workflow receipt: ${receiptError instanceof Error ? receiptError.message : String(receiptError)}` });
|
|
5474
5721
|
}
|
|
5475
5722
|
if (!writeWorkflowResult({ id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: status.state === "complete", state: status.state, summary: resultSummary, error: status.state === "complete" ? undefined : status.error, stopped: status.stopped, activityState: status.activityState, workflowChildren, ...(terminalOutcome ? { terminalOutcome } : {}), results: partial.children.map((child) => ({ workflowKey: child.key, ...(child.agent ? { agent: child.agent } : {}), ...(child.runId ? { runId: child.runId } : {}), ...(status.steps?.find((step) => step.workflowKey === child.key)?.sessionName ? { sessionName: status.steps?.find((step) => step.workflowKey === child.key)?.sessionName } : {}), ...workflowChildAccountingFields(child), output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, success: child.ok, ...(child.outputReference ? { outputReference: child.outputReference } : {}), ...(child.terminalOutcome ? { terminalOutcome: child.terminalOutcome } : {}), ...(child.outputPathMapping ? { outputPathMapping: child.outputPathMapping } : {}), ...(child.stopped ? { stopped: true } : {}), ...(child.interrupted ? { interrupted: true } : {}), ...(child.detached && status.state !== "complete" ? { detached: true } : {}), ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, ...(workflowReceipt ? { workflowReceipt } : {}), asyncDir, cwd: workflowCwd, sessionId: currentSessionId, completionOwnerId, ...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}), timestamp: Date.now(), durationMs: Date.now() - startedAt })) return;
|
|
5723
|
+
if (pendingResultPublication && !await pendingResultPublication) return;
|
|
5476
5724
|
persist();
|
|
5477
5725
|
deps.refreshResultDelivery?.();
|
|
5478
5726
|
persistClosed = true;
|
|
5479
5727
|
appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(terminalOutcome ? { terminalOutcome } : {}), ...(status.error ? { error: status.error } : {}), ...(status.activityState ? { activityState: status.activityState } : {}) });
|
|
5480
5728
|
} finally {
|
|
5729
|
+
// Idempotent cleanup only: a failed result/index write must not authorize terminal status.
|
|
5730
|
+
try {
|
|
5731
|
+
settleWorkflowSteerInbox();
|
|
5732
|
+
} catch (error) {
|
|
5733
|
+
console.error(`Failed to close async workflow steer inbox '${asyncDir}':`, error);
|
|
5734
|
+
}
|
|
5481
5735
|
persistClosed = true;
|
|
5482
5736
|
deps.state.workflowControllers?.delete(workflowRunId);
|
|
5483
5737
|
deps.state.workflowChildStops?.delete(workflowRunId);
|
|
@@ -5493,7 +5747,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5493
5747
|
const { workflowScript: _workflowScript, action: _action, agent: _agent, task: _task, resume: _resume, tasks: _tasks, chain: _chain, concurrency: _concurrency, async: _async, foregroundOnly: _foregroundOnly, clarify: _clarify, timeoutMs: _timeoutMs, maxRuntimeMs: _maxRuntimeMs, usageBudget: _usageBudget, chatProgress: _chatProgress, missionId: _missionId, mission: _mission, preflight: _preflight, globalConcurrencyLimit: _globalConcurrencyLimit, maxSubagentSpawnsPerRun: _maxSubagentSpawnsPerRun, ...workflowChildDefaults } = requestParams;
|
|
5494
5748
|
const workflowOutput = typeof workflowChildDefaults.output === "string" || typeof workflowChildDefaults.output === "boolean" ? workflowChildDefaults.output : undefined;
|
|
5495
5749
|
const configuredOutputBaseDir = resolveConfiguredSingleRunOutputBaseDir(deps);
|
|
5496
|
-
const workflowAggregateOutputPath =
|
|
5750
|
+
const workflowAggregateOutputPath = resolveSingleOutputPath(workflowOutput, ctx.cwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, foregroundWorkflowRunId));
|
|
5497
5751
|
const claimedOutputPaths = new Map<string, string>();
|
|
5498
5752
|
const childOutputOverrides = new Map<string, string>();
|
|
5499
5753
|
const childOutputClaimPaths = new Map<string, string>();
|
|
@@ -5513,14 +5767,21 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5513
5767
|
: workflowResource
|
|
5514
5768
|
? workflowResource.authority.host ? runHostCommand : undefined
|
|
5515
5769
|
: publicExecution ? undefined : runHostCommand;
|
|
5516
|
-
const workflowHostSteps = new Map<string,
|
|
5770
|
+
const workflowHostSteps = new Map<string, HostStepNode>();
|
|
5517
5771
|
let liveWorkflow: NonNullable<Details["workflow"]> = { trace: [], emits: [], console: [], ...(workflowResource ? { resource: workflowResource.provenance } : {}) };
|
|
5518
|
-
|
|
5772
|
+
const childProgress = new Map<string, ReturnType<typeof workflowChildProgress>>();
|
|
5773
|
+
let activityTimer: ReturnType<typeof setTimeout> | undefined;
|
|
5774
|
+
let lastProgressAt = 0;
|
|
5775
|
+
let workflowProgressClosed = false;
|
|
5519
5776
|
const workflowDeadlineAt = timeout === undefined ? undefined : Date.now() + timeout;
|
|
5520
5777
|
const workflowCapabilityCeiling = intersectSubagentCapabilityCeilings(requestParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(resolveCurrentSessionId(ctx.sessionManager)));
|
|
5521
5778
|
const sendWorkflowProgress = () => {
|
|
5522
|
-
|
|
5523
|
-
if (
|
|
5779
|
+
if (workflowProgressClosed) return;
|
|
5780
|
+
if (activityTimer) clearTimeout(activityTimer);
|
|
5781
|
+
activityTimer = undefined;
|
|
5782
|
+
lastProgressAt = Date.now();
|
|
5783
|
+
const liveWorkflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: foregroundWorkflowRunId, workflowState: "running", inventoryComplete: false, trace: liveWorkflow.trace, progress: childProgress });
|
|
5784
|
+
onUpdate?.(workflowProgressUpdate(foregroundWorkflowRunId, chatProgress, liveWorkflow, liveWorkflowChildren, workflowPreflight));
|
|
5524
5785
|
};
|
|
5525
5786
|
try {
|
|
5526
5787
|
const workflow = await runWorkflowScript({
|
|
@@ -5538,7 +5799,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5538
5799
|
const projectedTrace = annotateWorkflowPreflightTrace(trace, workflowPreflight);
|
|
5539
5800
|
const preflightWarnings = workflowPreflightWarnings(workflowPreflight, trace);
|
|
5540
5801
|
liveWorkflow = { ...liveWorkflow, trace: projectedTrace, ...(preflightWarnings.length ? { preflightWarnings } : {}) };
|
|
5541
|
-
|
|
5802
|
+
for (const entry of trace) {
|
|
5803
|
+
if (entry.operation === "run" && ["completed", "failed", "stopped", "detached"].includes(entry.state)) childProgress.delete(entry.key);
|
|
5804
|
+
}
|
|
5542
5805
|
sendWorkflowProgress();
|
|
5543
5806
|
},
|
|
5544
5807
|
admit: (calls) => {
|
|
@@ -5577,6 +5840,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5577
5840
|
deps.asyncByDefault,
|
|
5578
5841
|
);
|
|
5579
5842
|
preparedChildParams = childRequest;
|
|
5843
|
+
if (workflowUsageBudget.budget) workflowOwnedUsageBudgets.set(childRequest, workflowUsageBudget.budget);
|
|
5580
5844
|
if (delegatedWorkflowPermit) {
|
|
5581
5845
|
if (childRequest.async !== false) throw new Error("Workflow child permit supports one foreground child only.");
|
|
5582
5846
|
const childCwd = resolveRequestedCwd(workflowCwd, childRequest.cwd);
|
|
@@ -5594,6 +5858,15 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5594
5858
|
return execute(randomUUID(), childRequest, workflowSignal, (update) => {
|
|
5595
5859
|
const progress = update.details.progress?.[0];
|
|
5596
5860
|
if (!progress) return;
|
|
5861
|
+
if (onUpdate && !workflowProgressClosed) {
|
|
5862
|
+
if (progress.status === "running") childProgress.set(key, workflowChildProgress(progress));
|
|
5863
|
+
else childProgress.delete(key);
|
|
5864
|
+
if (!activityTimer) {
|
|
5865
|
+
const delay = 100 - (Date.now() - lastProgressAt);
|
|
5866
|
+
if (delay <= 0) sendWorkflowProgress();
|
|
5867
|
+
else activityTimer = setTimeout(sendWorkflowProgress, delay);
|
|
5868
|
+
}
|
|
5869
|
+
}
|
|
5597
5870
|
const progressStatus = progress.status === "completed" ? "completed" : progress.status === "failed" ? "failed" : "running";
|
|
5598
5871
|
recordMissionWorkflowChild(missionBinding, foregroundWorkflowRunId, key, {
|
|
5599
5872
|
status: progressStatus,
|
|
@@ -5621,7 +5894,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5621
5894
|
return child;
|
|
5622
5895
|
},
|
|
5623
5896
|
status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession, workflowParentModel)),
|
|
5624
|
-
resolveResume: (reference) =>
|
|
5897
|
+
resolveResume: (reference, _signal, index) => resolveWorkflowResume(reference, deps, ctx.sessionManager.getSessionFile() ?? null, index),
|
|
5625
5898
|
steer: (key, message, options, workflowSignal) => steerWorkflowChildByKey({ state: deps.state, workflowRunId: foregroundWorkflowRunId, key, message, options, signal: workflowSignal, resolveRunId: () => workflowChildRunIds.get(key) }),
|
|
5626
5899
|
});
|
|
5627
5900
|
const finalPreflightWarnings = workflowPreflightWarnings(workflowPreflight, workflow.trace, { settled: true });
|
|
@@ -5674,6 +5947,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5674
5947
|
isError: true,
|
|
5675
5948
|
details: compactOptional<Details>({ mode: "workflow", runId: foregroundWorkflowRunId, results: workflowDetailsResults(partial.children), ...(workflowPreflight ? { preflight: workflowPreflight } : {}), workflowChildren, totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { trace: finalPreflightTrace, emits: partial.emits, console: partial.console, ...(workflowResource ? { resource: workflowResource.provenance } : {}), ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}), receipt }, chatProgress }),
|
|
5676
5949
|
}, workflowFanoutBudget));
|
|
5950
|
+
} finally {
|
|
5951
|
+
workflowProgressClosed = true;
|
|
5952
|
+
if (activityTimer) clearTimeout(activityTimer);
|
|
5953
|
+
childProgress.clear();
|
|
5677
5954
|
}
|
|
5678
5955
|
}
|
|
5679
5956
|
const directParams = requestParams;
|
|
@@ -5744,10 +6021,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5744
6021
|
}
|
|
5745
6022
|
if (!confirmed) return { content: [{ type: "text", text: "Worktree discard canceled; preserved worktrees were not changed." }], details: { mode: "management", results: [] } };
|
|
5746
6023
|
try {
|
|
5747
|
-
const
|
|
5748
|
-
|
|
6024
|
+
const manifestPath = path.isAbsolute(paramsWithResolvedCwd.handoffPath) ? paramsWithResolvedCwd.handoffPath : path.resolve(requestCwd, paramsWithResolvedCwd.handoffPath);
|
|
6025
|
+
const discarded = await withWorktreeTransaction(() => discardPreservedWorktrees(
|
|
6026
|
+
manifestPath,
|
|
5749
6027
|
{ kind: decision === "confirm" ? "confirmed" : "policy", ...(deps.config.authorityPolicy ? { policy: deps.config.authorityPolicy } : {}) },
|
|
5750
|
-
);
|
|
6028
|
+
));
|
|
5751
6029
|
return { content: [{ type: "text", text: discarded.text }], details: { mode: "management", results: [] } };
|
|
5752
6030
|
} catch (error) {
|
|
5753
6031
|
return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "management", results: [] } };
|
|
@@ -6016,7 +6294,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6016
6294
|
}
|
|
6017
6295
|
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots, abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })));
|
|
6018
6296
|
}
|
|
6019
|
-
if (paramsWithResolvedCwd.view === "fleet") {
|
|
6297
|
+
if (paramsWithResolvedCwd.view === "fleet" || paramsWithResolvedCwd.view === "transcript") {
|
|
6020
6298
|
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots, abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })));
|
|
6021
6299
|
}
|
|
6022
6300
|
if (targetRunId) {
|
|
@@ -6025,12 +6303,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6025
6303
|
if (resolved?.kind === "foreground") {
|
|
6026
6304
|
const foreground = getForegroundControl(deps.state, resolved.id);
|
|
6027
6305
|
if (foreground) {
|
|
6028
|
-
if (paramsWithResolvedCwd.view === "transcript") {
|
|
6029
|
-
return withBudget({
|
|
6030
|
-
content: [{ type: "text", text: "Live foreground transcript is already visible in the expanded running subagent result. Persisted session transcript becomes inspectable after the foreground run completes when sessions are enabled." }],
|
|
6031
|
-
details: { mode: "management", results: [] },
|
|
6032
|
-
});
|
|
6033
|
-
}
|
|
6034
6306
|
return withBudget(foregroundStatusResult(foreground));
|
|
6035
6307
|
}
|
|
6036
6308
|
}
|
|
@@ -6040,13 +6312,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6040
6312
|
}
|
|
6041
6313
|
} else if (!hasDirectoryTarget) {
|
|
6042
6314
|
const foreground = getForegroundControl(deps.state, undefined);
|
|
6043
|
-
if (foreground
|
|
6044
|
-
if (foreground && paramsWithResolvedCwd.view === "transcript") {
|
|
6045
|
-
return withBudget({
|
|
6046
|
-
content: [{ type: "text", text: "Live foreground transcript is already visible in the expanded running subagent result. Pass an async run id to inspect a background transcript." }],
|
|
6047
|
-
details: { mode: "management", results: [] },
|
|
6048
|
-
});
|
|
6049
|
-
}
|
|
6315
|
+
if (foreground) return withBudget(foregroundStatusResult(foreground));
|
|
6050
6316
|
}
|
|
6051
6317
|
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots, abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })));
|
|
6052
6318
|
}
|
|
@@ -6067,9 +6333,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6067
6333
|
const runId = location.resolvedId ?? targetRunId ?? path.basename(location.asyncDir ?? paramsWithResolvedCwd.dir);
|
|
6068
6334
|
const directoryStatus = location.asyncDir ? readStatus(location.asyncDir) : null;
|
|
6069
6335
|
if (directoryStatus?.mode === "workflow") {
|
|
6070
|
-
|
|
6071
|
-
if (!route.ok) return { content: [{ type: "text", text: route.message }], isError: true, details: { mode: "management", results: [] } };
|
|
6072
|
-
return steerWorkflowForegroundTarget({ target: route.target, message, mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode), index: paramsWithResolvedCwd.index });
|
|
6336
|
+
return steerWorkflowRun({ state: deps.state, runId, asyncDir: location.asyncDir!, message, mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode), index: paramsWithResolvedCwd.index, signal });
|
|
6073
6337
|
}
|
|
6074
6338
|
if (location.asyncDir) {
|
|
6075
6339
|
const unsupported = externalRunnerControlError(location.asyncDir, "steer");
|
|
@@ -6077,6 +6341,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6077
6341
|
}
|
|
6078
6342
|
return steerAsyncRun(compactOptional<Parameters<typeof steerAsyncRun>[0]>({
|
|
6079
6343
|
state: deps.state,
|
|
6344
|
+
findPendingAsks: deps.findPendingAsks,
|
|
6080
6345
|
runId,
|
|
6081
6346
|
message,
|
|
6082
6347
|
mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode),
|
|
@@ -6114,9 +6379,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6114
6379
|
if (resolved?.kind !== "async") return { content: [{ type: "text", text: `No async run found for '${targetRunId}'.` }], isError: true, details: { mode: "management", results: [] } };
|
|
6115
6380
|
const resolvedStatus = resolved.location.asyncDir ? readStatus(resolved.location.asyncDir) : null;
|
|
6116
6381
|
if (resolvedStatus?.mode === "workflow") {
|
|
6117
|
-
|
|
6118
|
-
if (!route.ok) return { content: [{ type: "text", text: route.message }], isError: true, details: { mode: "management", results: [] } };
|
|
6119
|
-
return steerWorkflowForegroundTarget({ target: route.target, message, mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode), index: paramsWithResolvedCwd.index });
|
|
6382
|
+
return steerWorkflowRun({ state: deps.state, runId: resolved.id, asyncDir: resolved.location.asyncDir!, message, mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode), index: paramsWithResolvedCwd.index, signal });
|
|
6120
6383
|
}
|
|
6121
6384
|
if (resolved.location.asyncDir) {
|
|
6122
6385
|
const unsupported = externalRunnerControlError(resolved.location.asyncDir, "steer");
|
|
@@ -6124,6 +6387,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6124
6387
|
}
|
|
6125
6388
|
return steerAsyncRun(compactOptional<Parameters<typeof steerAsyncRun>[0]>({
|
|
6126
6389
|
state: deps.state,
|
|
6390
|
+
findPendingAsks: deps.findPendingAsks,
|
|
6127
6391
|
runId: resolved.id,
|
|
6128
6392
|
message,
|
|
6129
6393
|
mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode),
|
|
@@ -6325,6 +6589,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6325
6589
|
config: deps.config,
|
|
6326
6590
|
currentSessionId: deps.state.currentSessionId ?? ctx.sessionManager.getSessionId() ?? undefined,
|
|
6327
6591
|
runtimeAgentOwner: deps.pi,
|
|
6592
|
+
onAgentsChanged: deps.onAgentsChanged,
|
|
6328
6593
|
});
|
|
6329
6594
|
}
|
|
6330
6595
|
|
|
@@ -6716,6 +6981,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6716
6981
|
timeoutMs: foregroundTimeout.timeoutMs,
|
|
6717
6982
|
toolBudget: runToolBudget.toolBudget,
|
|
6718
6983
|
usageBudget: usageBudget.budget,
|
|
6984
|
+
inheritedUsageBudget,
|
|
6719
6985
|
allowZeroToolBudget,
|
|
6720
6986
|
configToolBudget: configToolBudget.toolBudget,
|
|
6721
6987
|
configToolTimeoutMs: deps.config.toolTimeoutMs,
|
|
@@ -6976,7 +7242,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6976
7242
|
}
|
|
6977
7243
|
let publicParams = normalized.params as SubagentParamsLike;
|
|
6978
7244
|
if (publicParams.workflow !== undefined) {
|
|
6979
|
-
const resolved = resolveWorkflowResource(publicParams.workflow, publicParams.args);
|
|
7245
|
+
const resolved = resolveWorkflowResource(publicParams.workflow, publicParams.args, ctx.sessionManager.getSessionId() ?? undefined);
|
|
6980
7246
|
if (!resolved.ok) return Promise.resolve({ content: [{ type: "text", text: resolved.error }], isError: true, details: { mode: "workflow", results: [] } });
|
|
6981
7247
|
const { workflow: _workflow, args: _args, ...withoutResourceInput } = publicParams;
|
|
6982
7248
|
publicParams = { ...withoutResourceInput, workflowScript: resolved.resource.script };
|
|
@@ -7023,16 +7289,27 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
7023
7289
|
ctx: ExtensionContext,
|
|
7024
7290
|
) => {
|
|
7025
7291
|
const ownerSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
7026
|
-
|
|
7027
|
-
|
|
7028
|
-
|
|
7029
|
-
|
|
7030
|
-
|
|
7031
|
-
});
|
|
7032
|
-
scheduledOwnerExecutors.set(ownerSessionId, ownerExecutor);
|
|
7292
|
+
const runtimeOwnerId = ctx.sessionManager.getSessionId() || null;
|
|
7293
|
+
let ownerExecutors = scheduledOwnerExecutors.get(runtimeOwnerId);
|
|
7294
|
+
if (!ownerExecutors) {
|
|
7295
|
+
ownerExecutors = new Map();
|
|
7296
|
+
scheduledOwnerExecutors.set(runtimeOwnerId, ownerExecutors);
|
|
7033
7297
|
}
|
|
7034
|
-
|
|
7298
|
+
let owner = ownerExecutors.get(ownerSessionId);
|
|
7299
|
+
if (!owner) {
|
|
7300
|
+
const state = createScheduledOwnerState(deps.state, ownerSessionId, ctx);
|
|
7301
|
+
owner = { state, executor: createSubagentExecutor({ ...deps, state }) };
|
|
7302
|
+
ownerExecutors.set(ownerSessionId, owner);
|
|
7303
|
+
}
|
|
7304
|
+
return owner.executor.executePublic(id, params, signal, undefined, ctx);
|
|
7035
7305
|
};
|
|
7036
7306
|
|
|
7037
|
-
|
|
7307
|
+
function* getCurrentSupervisorOwnerStates(): Iterable<SubagentState> {
|
|
7308
|
+
const ownerId = deps.state.supervisorOwnerSessionId;
|
|
7309
|
+
if (!ownerId) return;
|
|
7310
|
+
// File transitions remain separate entries; other runtime owners are never scanned.
|
|
7311
|
+
for (const owner of scheduledOwnerExecutors.get(ownerId)?.values() ?? []) yield owner.state;
|
|
7312
|
+
}
|
|
7313
|
+
|
|
7314
|
+
return { execute: executeWithSingleDispatchGuard, executePublic, executeDelegated, executeScheduled, getCurrentSupervisorOwnerStates };
|
|
7038
7315
|
}
|