pi-subagents 0.65.0 → 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 +87 -0
- package/README.md +1 -1
- package/agents/researcher.md +23 -13
- package/docs/agents.md +17 -3
- package/docs/configuration.md +34 -0
- package/docs/extension-api.md +94 -0
- package/docs/models.md +58 -1
- package/docs/observability.md +42 -2
- package/docs/tool-reference.md +11 -5
- package/docs/workflows.md +22 -7
- package/package.json +4 -1
- package/runner-server-preload.mjs +13 -0
- package/skills/pi-subagents/SKILL.md +2 -1
- package/skills/pi-subagents/references/execution-controls.md +19 -2
- package/skills/pi-subagents/references/management-authoring-rpc.md +2 -1
- package/skills/pi-subagents/references/multi-lane-orchestration.md +2 -0
- 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/preflight.ts +5 -1
- package/src/api/shared-types.ts +1 -1
- package/src/api/workflow-resources.ts +6 -0
- package/src/extension/config.ts +4 -2
- package/src/extension/index.ts +71 -4
- package/src/extension/public-execution.ts +0 -1
- package/src/extension/rpc.ts +4 -21
- package/src/extension/schemas.ts +9 -7
- package/src/extension/tool-description.ts +10 -5
- package/src/integrations/pi-web-session-liveness.ts +73 -0
- package/src/intercom/native-supervisor-channel.ts +102 -88
- package/src/intercom/supervisor-ui.ts +3 -2
- package/src/missions/workflow-state.ts +37 -16
- package/src/runs/background/active-async-capacity.ts +18 -18
- package/src/runs/background/async-execution.ts +8 -1
- package/src/runs/background/async-job-tracker.ts +35 -3
- package/src/runs/background/async-resume.ts +3 -1
- package/src/runs/background/async-retention.ts +9 -0
- 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 +88 -12
- package/src/runs/background/owned-process-tree.ts +6 -6
- package/src/runs/background/process-terminal.ts +23 -23
- package/src/runs/background/retained-nested-route-tracker.ts +96 -0
- package/src/runs/background/run-child-session.ts +62 -33
- package/src/runs/background/run-status.ts +75 -5
- package/src/runs/background/runner-aliases.ts +46 -8
- 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 +430 -208
- 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 +116 -26
- package/src/runs/foreground/foreground-history.ts +3 -1
- package/src/runs/foreground/prompt-audit.ts +9 -5
- package/src/runs/foreground/subagent-executor.ts +531 -227
- 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 +16 -3
- 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 +55 -24
- 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 +30 -20
- package/src/runs/shared/model-exclusions.ts +2 -1
- package/src/runs/shared/model-fallback.ts +41 -8
- package/src/runs/shared/nested-events.ts +8 -8
- 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 +3 -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-cleanup-plan.ts +6 -3
- package/src/runs/shared/worktree-setup-command.ts +190 -0
- package/src/runs/shared/worktree.ts +403 -210
- package/src/shared/model-response-aliases.ts +13 -0
- package/src/shared/types.ts +89 -60
- package/src/shared/utils.ts +10 -2
- package/src/shared/watch-strategy.ts +2 -0
- package/src/shared/workflow-child-permit.ts +18 -13
- package/src/tui/fleet-status.ts +1 -1
- package/src/tui/fleet.ts +11 -5
- package/src/tui/render.ts +44 -15
- package/src/workflows/chat-progress.ts +3 -3
- package/src/workflows/scripted-workflow.ts +70 -16
- package/src/workflows/workflow-checklist.ts +15 -18
- 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
|
@@ -49,9 +49,10 @@ import { updateActiveRunIndex } from "../background/active-run-index.ts";
|
|
|
49
49
|
import { steeringReceipt } from "../background/steering.ts";
|
|
50
50
|
import { acquireActiveAsyncCapacity, ActiveAsyncCapacityError, getActiveAsyncCapacitySnapshot, resolveAbandonedSlotReleaseAfterMs, resolveMaxActiveAsyncRunsPerSession, transferActiveAsyncCapacity, type ActiveAsyncCapacityHandle } from "../background/active-async-capacity.ts";
|
|
51
51
|
import { isScheduledRunAction } from "../background/scheduled-runs.ts";
|
|
52
|
+
import { encodeIndexSegment } from "../background/index-segment.ts";
|
|
52
53
|
import { enqueueChainAppendRequest, readPendingChainAppendRequests, runnerStepOutputNames } from "../background/chain-append.ts";
|
|
53
54
|
import { ChainOutputValidationError, validateChainOutputBindingsWithContext } from "../shared/chain-outputs.ts";
|
|
54
|
-
import { normalizeGateAcceptance, resolveAcceptanceReportMode, validateExecutionAcceptance } from "../shared/acceptance.ts";
|
|
55
|
+
import { normalizeGateAcceptance, resolveAcceptanceReportMode, validateAcceptanceInput, validateExecutionAcceptance } from "../shared/acceptance.ts";
|
|
55
56
|
import { canPreferFork, createForkContextResolver, forkedChildRequiresThinkingOff, resolveSubagentLaunchContext } from "../../shared/fork-context.ts";
|
|
56
57
|
import { createPrunedForkSessionWriter } from "../../shared/pruned-fork.ts";
|
|
57
58
|
import { resolveCurrentSessionId } from "../../shared/session-identity.ts";
|
|
@@ -60,16 +61,17 @@ import { applyIntercomBridgeToAgent, INTERCOM_BRIDGE_MARKER, resolveIntercomBrid
|
|
|
60
61
|
import { formatControlIntercomMessage, formatControlNoticeMessage, resolveControlConfig, shouldNotifyControlEvent } from "../shared/subagent-control.ts";
|
|
61
62
|
import { formatSpawnBudget, getSpawnBudgetSnapshot, grantSpawnBudget, preflightSpawnBudget, preflightSpawnBudgetGrant, reserveSpawnBudget } from "../shared/spawn-budget.ts";
|
|
62
63
|
import { claimRunFanoutBatch, claimRunFanoutBatchWithCommit, createRunFanoutBudget, formatRunFanoutBudget, getRunFanoutBudgetSnapshot, readRunFanoutBudgetDescriptor, RunFanoutLimitError, writeRunFanoutBudgetDescriptor } from "../shared/run-fanout-budget.ts";
|
|
64
|
+
import { retainLiveForegroundNestedRoute } from "../../integrations/pi-web-session-liveness.ts";
|
|
63
65
|
import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
|
|
64
66
|
import { usageBudgetExceededMessage, usageBudgetState, validateUsageBudgetConfig } from "../shared/usage-budget.ts";
|
|
65
67
|
import { intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
|
|
66
|
-
import {
|
|
68
|
+
import { isAgentContract } from "../shared/agent-contract.ts";
|
|
67
69
|
import { normalizeExtensionBindings, type ExtensionBindings } from "../shared/extension-bindings.ts";
|
|
68
70
|
import { finalizeSingleOutput, injectSingleOutputInstruction, normalizeSingleOutputOverride, outputPathMappingFromTask, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
|
|
69
71
|
import { assertJsonSchemaObject, cleanupStructuredOutputRuntime, createStructuredOutputRuntime } from "../shared/structured-output.ts";
|
|
70
72
|
import { compactForegroundDetails, getSingleResultOutput, readStatus, resolveChildCwd, sumResultsCost, sumResultsUsage, toAgentToolUsage } from "../../shared/utils.ts";
|
|
71
73
|
import { createTaskMutationArbiter } from "../shared/llm-intent-arbiter.ts";
|
|
72
|
-
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";
|
|
73
75
|
import { summarizeContextModes, type ContextMode, type ContextSummary } from "../shared/context-mode.ts";
|
|
74
76
|
import {
|
|
75
77
|
attachNestedChildrenToResultChildren,
|
|
@@ -80,12 +82,13 @@ import {
|
|
|
80
82
|
stripDetailsOutputsForIntercomReceipt,
|
|
81
83
|
} from "../../intercom/result-intercom.ts";
|
|
82
84
|
import { applySteeringRecoveryAgentConfig, asyncReviveRequiresRecoveryDescriptor, buildRevivedAsyncTask, readAsyncRecoveryDescriptor, resolveAsyncResumeTarget, resolveAsyncRunLocation } from "../background/async-resume.ts";
|
|
83
|
-
import { deliverInterruptRequest, readRevivalBriefs, requestAsyncSteer, type SteerDeliveryMode } from "../background/control-channel.ts";
|
|
84
|
-
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";
|
|
85
87
|
import { canQueueRetainedAsyncFollowUp, steerAsyncRun } from "./async-steering-action.ts";
|
|
86
88
|
import {
|
|
87
89
|
resolveWorkflowForegroundSteeringTarget,
|
|
88
90
|
steerWorkflowForegroundTarget,
|
|
91
|
+
steerWorkflowRun,
|
|
89
92
|
} from "./workflow-foreground-steering.ts";
|
|
90
93
|
import { stopAsyncRun } from "./async-stop-action.ts";
|
|
91
94
|
import { dismissRecoveredWorkflow } from "./async-dismiss-action.ts";
|
|
@@ -113,10 +116,10 @@ import { handleHerdrInspectorAction, HERDR_INSPECTOR_ACTIONS } from "../../inspe
|
|
|
113
116
|
import { handleHerdrProjectPaneAction, HERDR_PROJECT_PANE_ACTIONS } from "../../inspectors/herdr/project-panes.ts";
|
|
114
117
|
import { previewSimpleWorkflowRun, runWorkflowScript, validateWorkflowScript, WorkflowScriptError, type WorkflowLanePlan, type WorkflowReceiptResumeReference, type WorkflowScriptChildResult, type WorkflowScriptTraceEntry, type WorkflowSteerOptions, type WorkflowSteerResult } from "../../workflows/scripted-workflow.ts";
|
|
115
118
|
import { executeWorkflowHostCommand, resolveWorkflowHostOutputClaimPath, type WorkflowHostCommandParams, type WorkflowHostCommandResult } from "../../workflows/host-command.ts";
|
|
116
|
-
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";
|
|
117
120
|
import { upsertHostStep, validHostStepNodes } from "../shared/host-step-status.ts";
|
|
118
121
|
import { assertWorkflowLaneKey, normalizeWorkflowLaneMetadata } from "../shared/lane-metadata.ts";
|
|
119
|
-
import { parseWorkflowChildSummary, workflowChildSummary } from "../../workflows/workflow-child-summary.ts";
|
|
122
|
+
import { parseWorkflowChildSummary, workflowChildProgress, workflowChildSummary } from "../../workflows/workflow-child-summary.ts";
|
|
120
123
|
import { resolveWorkflowChatProgress, type WorkflowChatProgressProjection } from "../../workflows/chat-progress.ts";
|
|
121
124
|
import { annotateWorkflowPreflightTrace, formatWorkflowPreflight, formatWorkflowPreflightWarnings, normalizeWorkflowPreflight, workflowPreflightWarnings } from "../../workflows/workflow-preflight.ts";
|
|
122
125
|
import {
|
|
@@ -133,6 +136,8 @@ import { resolveWorkflowResource } from "../../workflows/workflow-resources.ts";
|
|
|
133
136
|
import {
|
|
134
137
|
cleanupWorktrees,
|
|
135
138
|
createWorktrees,
|
|
139
|
+
withWorktreeTransaction,
|
|
140
|
+
type WorktreeSetupProgress,
|
|
136
141
|
diffWorktrees,
|
|
137
142
|
formatWorktreeDiffSummary,
|
|
138
143
|
type WorktreeSetup,
|
|
@@ -155,7 +160,7 @@ import {
|
|
|
155
160
|
type ForegroundSteerInput,
|
|
156
161
|
type ForegroundSteerOutcome,
|
|
157
162
|
type ForegroundRunControl,
|
|
158
|
-
type
|
|
163
|
+
type HostStepNode,
|
|
159
164
|
type IntercomBridgeConfig,
|
|
160
165
|
type IntercomEventBus,
|
|
161
166
|
type JsonSchemaObject,
|
|
@@ -172,7 +177,7 @@ import {
|
|
|
172
177
|
type ToolBudgetConfig,
|
|
173
178
|
type Usage,
|
|
174
179
|
type UsageBudgetConfig,
|
|
175
|
-
type
|
|
180
|
+
type WorkflowResourceProvenance,
|
|
176
181
|
type WorkflowGraphNode,
|
|
177
182
|
type WorkflowGraphSnapshot,
|
|
178
183
|
type WorkflowNodeStatus,
|
|
@@ -192,6 +197,7 @@ import {
|
|
|
192
197
|
resolveMaxSubagentSpawnsPerRun,
|
|
193
198
|
wrapForkTask,
|
|
194
199
|
type ScheduleOrigin,
|
|
200
|
+
type SteeringTargetState,
|
|
195
201
|
} from "../../shared/types.ts";
|
|
196
202
|
import { deriveChildSessionName } from "../../shared/child-session-name.ts";
|
|
197
203
|
|
|
@@ -324,7 +330,7 @@ export interface SubagentParamsLike {
|
|
|
324
330
|
workflowScriptPath?: string;
|
|
325
331
|
globalConcurrencyLimit?: number;
|
|
326
332
|
maxSubagentSpawnsPerRun?: number;
|
|
327
|
-
preflight?: import("../../shared/types.ts").
|
|
333
|
+
preflight?: import("../../shared/types.ts").WorkflowPreflight;
|
|
328
334
|
chatProgress?: "auto" | "off" | "live-card";
|
|
329
335
|
isolation?: "none" | "worktree";
|
|
330
336
|
step?: ChainStep;
|
|
@@ -435,9 +441,12 @@ interface ExecutorDeps {
|
|
|
435
441
|
getSubagentSessionRoot: (parentSessionFile: string | null) => string;
|
|
436
442
|
expandTilde: (p: string) => string;
|
|
437
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;
|
|
438
445
|
allowMutatingManagementActions?: boolean;
|
|
439
446
|
activateSupervisorTransport?: () => void;
|
|
447
|
+
findPendingAsks?: Parameters<typeof steerAsyncRun>[0]["findPendingAsks"];
|
|
440
448
|
refreshResultDelivery?: () => void;
|
|
449
|
+
trackRetainedNestedRoute?: (rootRunId: string) => void;
|
|
441
450
|
kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean;
|
|
442
451
|
/** Set when this executor runs inside a child session; carries the runtime settings the host passes instead of environment variables. */
|
|
443
452
|
childRuntime?: ChildRuntimeConfig;
|
|
@@ -494,6 +503,8 @@ interface ExecutionContextData {
|
|
|
494
503
|
configToolTimeoutMs?: number;
|
|
495
504
|
toolBudget?: ResolvedToolBudget;
|
|
496
505
|
usageBudget?: UsageBudgetConfig;
|
|
506
|
+
/** Parent workflow owns this allowance; presence vetoes unverifiable child continuation. */
|
|
507
|
+
inheritedUsageBudget?: UsageBudgetConfig;
|
|
497
508
|
allowZeroToolBudget?: boolean;
|
|
498
509
|
configToolBudget?: ResolvedToolBudget;
|
|
499
510
|
contextPolicy: AgentDefaultContextPolicy;
|
|
@@ -526,9 +537,16 @@ function loadWorkflowScriptPath(params: SubagentParamsLike, runtimeCwd: string):
|
|
|
526
537
|
return { params: { ...rest, workflowScript } };
|
|
527
538
|
}
|
|
528
539
|
|
|
529
|
-
function removeForegroundControlIfIdle(state: SubagentState, runId: string): boolean {
|
|
540
|
+
export function removeForegroundControlIfIdle(state: SubagentState, runId: string, trackRetainedNestedRoute?: (rootRunId: string) => void): boolean {
|
|
530
541
|
const control = state.foregroundControls.get(runId);
|
|
531
542
|
if (control && (!foregroundSchedulingSettled(control) || (control.activeChildren?.size ?? 0) > 0)) return false;
|
|
543
|
+
if (control?.nestedRoute && trackRetainedNestedRoute) {
|
|
544
|
+
try {
|
|
545
|
+
if (retainLiveForegroundNestedRoute(state, control.nestedRoute)) trackRetainedNestedRoute(runId);
|
|
546
|
+
} catch (error) {
|
|
547
|
+
console.error(`Failed to retain live nested descendants for foreground run '${runId}':`, error);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
532
550
|
state.foregroundControls.delete(runId);
|
|
533
551
|
if (state.lastForegroundControlId === runId) state.lastForegroundControlId = null;
|
|
534
552
|
return true;
|
|
@@ -723,7 +741,7 @@ function foregroundChildActivityFromProgress(progress: SingleResult["progress"]
|
|
|
723
741
|
};
|
|
724
742
|
}
|
|
725
743
|
|
|
726
|
-
function rememberForegroundRun(state: SubagentState, input: { runId: string; mode: "single" | "parallel" | "chain"; cwd: string; sessionId: string | null; results: SingleResult[]; params: SubagentParamsLike; effectiveOutput?: string | boolean; effectiveOutputMode: OutputMode; extensionBindings?: ExtensionBindings }): void {
|
|
744
|
+
function rememberForegroundRun(state: SubagentState, input: { modelResponseAliases?: Record<string, string[]>; runId: string; mode: "single" | "parallel" | "chain"; cwd: string; sessionId: string | null; results: SingleResult[]; params: SubagentParamsLike; effectiveOutput?: string | boolean; effectiveOutputMode: OutputMode; extensionBindings?: ExtensionBindings }): void {
|
|
727
745
|
state.foregroundRuns ??= new Map();
|
|
728
746
|
const previous = state.foregroundRuns.get(input.runId);
|
|
729
747
|
const updatedAt = Date.now();
|
|
@@ -735,6 +753,7 @@ function rememberForegroundRun(state: SubagentState, input: { runId: string; mod
|
|
|
735
753
|
updatedAt,
|
|
736
754
|
children: input.results.map((result, index) => {
|
|
737
755
|
const resumeContract = omitUndefinedProperties({
|
|
756
|
+
modelResponseAliases: input.modelResponseAliases,
|
|
738
757
|
outputSchema: input.params.outputSchema,
|
|
739
758
|
agentContract: input.params.agentContract,
|
|
740
759
|
acceptance: input.params.acceptance,
|
|
@@ -1313,6 +1332,7 @@ function appendStepToAsyncChain(input: {
|
|
|
1313
1332
|
currentModelProvider: parentModel?.provider,
|
|
1314
1333
|
currentModel: parentModel,
|
|
1315
1334
|
modelScope: discoveredForAppend.modelScope,
|
|
1335
|
+
modelResponseAliases: input.deps.config.modelResponseAliases,
|
|
1316
1336
|
interactive: input.ctx.hasUI,
|
|
1317
1337
|
permissions: input.deps.config.permissions,
|
|
1318
1338
|
childRuntime: input.deps.childRuntime,
|
|
@@ -1694,6 +1714,7 @@ async function resumeExternalJobFollowUp(input: {
|
|
|
1694
1714
|
currentModelProvider: parentModel?.provider,
|
|
1695
1715
|
currentModel: parentModel,
|
|
1696
1716
|
modelScope: input.modelScope,
|
|
1717
|
+
modelResponseAliases: input.deps.config.modelResponseAliases,
|
|
1697
1718
|
interactive: input.ctx.hasUI,
|
|
1698
1719
|
permissions: input.deps.config.permissions,
|
|
1699
1720
|
childRuntime: input.deps.childRuntime,
|
|
@@ -1729,6 +1750,28 @@ async function resumeExternalJobFollowUp(input: {
|
|
|
1729
1750
|
return externalJobFollowUpStarted({ sourceRunId: input.target.runId, runId: result.details.asyncId ?? runId, asyncDir: result.details.asyncDir ?? asyncDir, interactive: input.ctx.hasUI });
|
|
1730
1751
|
}
|
|
1731
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
|
+
|
|
1732
1775
|
async function resumeAsyncRun(input: {
|
|
1733
1776
|
params: SubagentParamsLike;
|
|
1734
1777
|
requestCwd: string;
|
|
@@ -1767,34 +1810,9 @@ async function resumeAsyncRun(input: {
|
|
|
1767
1810
|
let target: ResumeSourceTarget;
|
|
1768
1811
|
const parentSessionFile = input.ctx.sessionManager.getSessionFile() ?? null;
|
|
1769
1812
|
try {
|
|
1770
|
-
const
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
resolved = requestedId ? resolveSubagentRunId(requestedId, omitUndefinedProperties({ state: input.deps.state, nested: nestedResolutionScopeForExecutor(input.deps) })) : undefined;
|
|
1774
|
-
} catch (error) {
|
|
1775
|
-
const message = error instanceof Error ? error.message : "";
|
|
1776
|
-
const asyncMatches = message.match(/async:/g)?.length ?? 0;
|
|
1777
|
-
if (!isResumeAmbiguity(error) || !message.includes("foreground:") || asyncMatches !== 1) throw error;
|
|
1778
|
-
}
|
|
1779
|
-
if (resolved?.kind === "nested") {
|
|
1780
|
-
if (attachChain) {
|
|
1781
|
-
return {
|
|
1782
|
-
content: [{ type: "text", text: "Attaching a running subagent as a chain root is currently available for top-level async runs only." }],
|
|
1783
|
-
isError: true,
|
|
1784
|
-
details: { mode: "management", results: [] },
|
|
1785
|
-
};
|
|
1786
|
-
}
|
|
1787
|
-
if (resolved.match.run.state === "running" || resolved.match.run.state === "queued") {
|
|
1788
|
-
return resumeLiveNestedRun({ target: resolved, message: followUp });
|
|
1789
|
-
}
|
|
1790
|
-
const trustedSessionRoots = [
|
|
1791
|
-
...(input.deps.config.defaultSessionDir ? [path.resolve(input.deps.expandTilde(input.deps.config.defaultSessionDir))] : []),
|
|
1792
|
-
...(parentSessionFile ? [input.deps.getSubagentSessionRoot(parentSessionFile)] : []),
|
|
1793
|
-
];
|
|
1794
|
-
target = resolveNestedResumeTarget(resolved, trustedSessionRoots);
|
|
1795
|
-
} else {
|
|
1796
|
-
target = resolveResumeTarget(input.params, input.deps.state, { asyncRequireSessionFile: false });
|
|
1797
|
-
}
|
|
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;
|
|
1798
1816
|
} catch (error) {
|
|
1799
1817
|
const message = error instanceof Error ? error.message : String(error);
|
|
1800
1818
|
return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
|
|
@@ -1947,6 +1965,7 @@ async function resumeAsyncRun(input: {
|
|
|
1947
1965
|
currentModelProvider: parentModel?.provider,
|
|
1948
1966
|
currentModel: parentModel,
|
|
1949
1967
|
modelScope,
|
|
1968
|
+
modelResponseAliases: input.deps.config.modelResponseAliases,
|
|
1950
1969
|
interactive: input.ctx.hasUI,
|
|
1951
1970
|
permissions: input.deps.config.permissions,
|
|
1952
1971
|
childRuntime: input.deps.childRuntime,
|
|
@@ -2055,6 +2074,8 @@ async function resumeAsyncRun(input: {
|
|
|
2055
2074
|
currentModelProvider: parentModel?.provider,
|
|
2056
2075
|
currentModel: parentModel,
|
|
2057
2076
|
modelScope,
|
|
2077
|
+
// Absence in the retained contract is meaningful; never acquire current aliases.
|
|
2078
|
+
modelResponseAliases: recoveryDescriptor ? recoveryDescriptor.modelResponseAliases : foregroundContract?.modelResponseAliases,
|
|
2058
2079
|
interactive: input.ctx.hasUI,
|
|
2059
2080
|
permissions: input.deps.config.permissions,
|
|
2060
2081
|
childRuntime: input.deps.childRuntime,
|
|
@@ -2102,6 +2123,7 @@ async function resumeAsyncRun(input: {
|
|
|
2102
2123
|
availableModels,
|
|
2103
2124
|
output: input.params.output !== undefined ? input.params.output : foregroundContract?.output ?? recoveryDescriptor?.outputPath,
|
|
2104
2125
|
outputMode: input.params.outputMode ?? foregroundContract?.outputMode ?? recoveryDescriptor?.outputMode,
|
|
2126
|
+
outputClaimPath: input.params.workflowOutputClaimPath,
|
|
2105
2127
|
...(agentContract ? { agentContract } : {}),
|
|
2106
2128
|
...(outputSchema ? { structuredOutputSchema: outputSchema } : {}),
|
|
2107
2129
|
...(recoveryDescriptor?.skills ? { skills: [...recoveryDescriptor.skills] } : {}),
|
|
@@ -2178,6 +2200,7 @@ async function resumeAsyncRun(input: {
|
|
|
2178
2200
|
...(completed.structuredOutputSchemaPath ? { structuredOutputSchemaPath: completed.structuredOutputSchemaPath } : {}),
|
|
2179
2201
|
...(completed.acceptance ? { acceptance: completed.acceptance } : {}),
|
|
2180
2202
|
...(completed.artifactPaths ? { artifactPaths: completed.artifactPaths } : {}),
|
|
2203
|
+
...(completed.savedOutputPath ? { savedOutputPath: completed.savedOutputPath } : {}),
|
|
2181
2204
|
...(completed.outputSaveError ? { outputSaveError: completed.outputSaveError } : {}),
|
|
2182
2205
|
...(completed.transcriptPath ? { transcriptPath: completed.transcriptPath } : {}),
|
|
2183
2206
|
...(completed.transcriptError ? { transcriptError: completed.transcriptError } : {}),
|
|
@@ -3153,6 +3176,7 @@ async function waitForWorkflowAsyncSingleResult(
|
|
|
3153
3176
|
...(completed.structuredOutputSchemaPath ? { structuredOutputSchemaPath: completed.structuredOutputSchemaPath } : {}),
|
|
3154
3177
|
...(completed.acceptance ? { acceptance: completed.acceptance } : {}),
|
|
3155
3178
|
...(completed.artifactPaths ? { artifactPaths: completed.artifactPaths } : {}),
|
|
3179
|
+
...(completed.savedOutputPath ? { savedOutputPath: completed.savedOutputPath } : {}),
|
|
3156
3180
|
...(completed.outputSaveError ? { outputSaveError: completed.outputSaveError } : {}),
|
|
3157
3181
|
...(completed.transcriptPath ? { transcriptPath: completed.transcriptPath } : {}),
|
|
3158
3182
|
...(completed.transcriptError ? { transcriptError: completed.transcriptError } : {}),
|
|
@@ -3210,6 +3234,7 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
3210
3234
|
currentModelProvider: parentModel?.provider,
|
|
3211
3235
|
currentModel: parentModel,
|
|
3212
3236
|
modelScope: data.modelScope,
|
|
3237
|
+
modelResponseAliases: deps.config.modelResponseAliases,
|
|
3213
3238
|
interactive: ctx.hasUI,
|
|
3214
3239
|
permissions: deps.config.permissions,
|
|
3215
3240
|
childRuntime: deps.childRuntime,
|
|
@@ -3326,8 +3351,7 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
3326
3351
|
return null;
|
|
3327
3352
|
}
|
|
3328
3353
|
|
|
3329
|
-
function createSingleWorktreeSetup(
|
|
3330
|
-
enabled: boolean | undefined,
|
|
3354
|
+
async function createSingleWorktreeSetup(
|
|
3331
3355
|
cwd: string,
|
|
3332
3356
|
runId: string,
|
|
3333
3357
|
agent: string,
|
|
@@ -3339,12 +3363,13 @@ function createSingleWorktreeSetup(
|
|
|
3339
3363
|
branchPrefix: ExtensionConfig["worktreeBranchPrefix"],
|
|
3340
3364
|
label?: string,
|
|
3341
3365
|
task?: string,
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3366
|
+
onProgress?: (snapshot: WorktreeSetupProgress) => void,
|
|
3367
|
+
signal?: AbortSignal,
|
|
3368
|
+
deadlineAt?: number,
|
|
3369
|
+
): Promise<{ setup?: WorktreeSetup; errorResult?: AgentToolResult<Details> }> {
|
|
3345
3370
|
try {
|
|
3346
3371
|
return {
|
|
3347
|
-
setup: createWorktrees(cwd, runId, 1, omitUndefinedProperties({
|
|
3372
|
+
setup: await createWorktrees(cwd, runId, 1, omitUndefinedProperties({
|
|
3348
3373
|
agents: [agent],
|
|
3349
3374
|
setupHook: setupHook
|
|
3350
3375
|
? { hookPath: setupHook, ...(setupHookTimeoutMs === undefined ? {} : { timeoutMs: setupHookTimeoutMs }) }
|
|
@@ -3355,7 +3380,9 @@ function createSingleWorktreeSetup(
|
|
|
3355
3380
|
branchPrefix,
|
|
3356
3381
|
labels: [label],
|
|
3357
3382
|
tasks: [task],
|
|
3358
|
-
|
|
3383
|
+
onProgress,
|
|
3384
|
+
signal,
|
|
3385
|
+
deadlineAt,
|
|
3359
3386
|
})),
|
|
3360
3387
|
};
|
|
3361
3388
|
} catch (error) {
|
|
@@ -3381,15 +3408,6 @@ function resolveSingleRunOutputBaseDir(deps: ExecutorDeps, artifactsDir: string,
|
|
|
3381
3408
|
return resolveConfiguredSingleRunOutputBaseDir(deps) ?? path.join(artifactsDir, "outputs", sanitizeRunPathSegment(runId));
|
|
3382
3409
|
}
|
|
3383
3410
|
|
|
3384
|
-
function resolveWorkflowAggregateOutputPath(
|
|
3385
|
-
output: string | boolean | undefined,
|
|
3386
|
-
ctxCwd: string,
|
|
3387
|
-
workflowCwd: string,
|
|
3388
|
-
outputBaseDir: string,
|
|
3389
|
-
): string | undefined {
|
|
3390
|
-
return resolveSingleOutputPath(output, ctxCwd, workflowCwd, outputBaseDir);
|
|
3391
|
-
}
|
|
3392
|
-
|
|
3393
3411
|
function workflowChildDefaultOutput(aggregateOutputPath: string | undefined, artifactsDir: string, workflowRunId: string, workflowKey: string): string {
|
|
3394
3412
|
if (aggregateOutputPath) {
|
|
3395
3413
|
const parsed = path.parse(aggregateOutputPath);
|
|
@@ -3451,7 +3469,9 @@ function resolveWorkflowChildOutputPath(input: {
|
|
|
3451
3469
|
key: string;
|
|
3452
3470
|
params: Record<string, unknown>;
|
|
3453
3471
|
}): { path?: string; inherited: boolean } {
|
|
3454
|
-
|
|
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")) {
|
|
3455
3475
|
if (!input.state) return { path: undefined, inherited: false };
|
|
3456
3476
|
const index = input.params.index;
|
|
3457
3477
|
const target = resolveResumeTarget({
|
|
@@ -3460,15 +3480,16 @@ function resolveWorkflowChildOutputPath(input: {
|
|
|
3460
3480
|
}, input.state);
|
|
3461
3481
|
return { path: "recoveryDescriptor" in target ? target.recoveryDescriptor?.outputPath : undefined, inherited: false };
|
|
3462
3482
|
}
|
|
3463
|
-
const rawOutput = input.params.output;
|
|
3464
|
-
const hasExplicitOutput = typeof rawOutput === "string" || typeof rawOutput === "boolean";
|
|
3465
3483
|
const childCwd = typeof input.params.cwd === "string" ? resolveChildCwd(input.workflowCwd, input.params.cwd) : input.workflowCwd;
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
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
|
+
}
|
|
3472
3493
|
const output = rawOutput === true || rawOutput === "true"
|
|
3473
3494
|
? agentOutput
|
|
3474
3495
|
: hasExplicitOutput
|
|
@@ -3557,7 +3578,7 @@ function prepareWorkflowChildLaunchParams(input: {
|
|
|
3557
3578
|
childParams = { ...input.childParams, output: input.outputOverride };
|
|
3558
3579
|
} else if (usesDefaultOutput && input.aggregateOutputPath !== undefined) {
|
|
3559
3580
|
childParams = { ...input.childParams, output: workflowChildDefaultOutput(input.aggregateOutputPath, input.artifactsDir, input.parentWorkflowRunId, input.workflowKey) };
|
|
3560
|
-
} else if (input.childParams.resume === undefined) {
|
|
3581
|
+
} else if (input.childParams.resume === undefined || input.childParams.output !== undefined) {
|
|
3561
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 });
|
|
3562
3583
|
if (resolvedOutput.path) childParams = { ...input.childParams, output: resolvedOutput.path };
|
|
3563
3584
|
}
|
|
@@ -3571,7 +3592,7 @@ function prepareWorkflowChildLaunchParams(input: {
|
|
|
3571
3592
|
return prepareWorkflowLaunchParams(input.workflowDefaults, childParams, input.parentWorkflowRunId, input.workflowKey, { ...input.options, externalAsyncRequired, outputClaimPath: input.outputClaimPath });
|
|
3572
3593
|
}
|
|
3573
3594
|
|
|
3574
|
-
function finalizeSingleWorktreeHandoff(input: {
|
|
3595
|
+
async function finalizeSingleWorktreeHandoff(input: {
|
|
3575
3596
|
worktreeSetup: WorktreeSetup;
|
|
3576
3597
|
artifactsDir: string;
|
|
3577
3598
|
runId: string;
|
|
@@ -3580,55 +3601,78 @@ function finalizeSingleWorktreeHandoff(input: {
|
|
|
3580
3601
|
result: SingleResult;
|
|
3581
3602
|
workflowKey?: string;
|
|
3582
3603
|
lane?: import("../../shared/types.ts").WorkflowLaneMetadata;
|
|
3583
|
-
}): { suffix: string; reference?: NonNullable<Details["parallelHandoff"]> } {
|
|
3584
|
-
|
|
3585
|
-
const diffs = diffWorktrees(input.worktreeSetup, [input.agent], diffsDir);
|
|
3586
|
-
const diffSummary = formatWorktreeDiffSummary(diffs);
|
|
3587
|
-
const manifestPath = parallelHandoffPath(input.artifactsDir, input.runId);
|
|
3588
|
-
const handoff = {
|
|
3589
|
-
manifestPath,
|
|
3590
|
-
runId: input.runId,
|
|
3591
|
-
mode: "single" as const,
|
|
3592
|
-
source: "foreground" as const,
|
|
3593
|
-
cwd: input.cwd,
|
|
3594
|
-
stepIndex: 0,
|
|
3595
|
-
flatStartIndex: 0,
|
|
3596
|
-
setup: input.worktreeSetup,
|
|
3597
|
-
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,
|
|
3598
|
-
diffs,
|
|
3599
|
-
results: [{
|
|
3600
|
-
agent: input.result.agent,
|
|
3601
|
-
...(input.workflowKey ? { workflowKey: input.workflowKey } : {}),
|
|
3602
|
-
...(input.workflowKey ? { runId: input.runId } : {}),
|
|
3603
|
-
...(input.lane ? { lane: input.lane } : {}),
|
|
3604
|
-
status: resolveSubagentResultStatus(omitUndefinedProperties({
|
|
3605
|
-
exitCode: input.result.exitCode,
|
|
3606
|
-
interrupted: input.result.interrupted,
|
|
3607
|
-
detached: input.result.detached,
|
|
3608
|
-
state: input.result.stopped ? "stopped" : undefined,
|
|
3609
|
-
processSignal: input.result.processSignal,
|
|
3610
|
-
timedOut: input.result.timedOut,
|
|
3611
|
-
stopped: input.result.stopped,
|
|
3612
|
-
turnBudgetExceeded: input.result.turnBudgetExceeded,
|
|
3613
|
-
})),
|
|
3614
|
-
summary: resultSummaryForIntercom(input.result),
|
|
3615
|
-
...(input.result.artifactPaths?.outputPath ? { outputPath: input.result.artifactPaths.outputPath } : {}),
|
|
3616
|
-
...(input.result.structuredOutput !== undefined ? { structuredOutput: input.result.structuredOutput } : {}),
|
|
3617
|
-
...(input.result.structuredOutputPath ? { structuredOutputPath: input.result.structuredOutputPath } : {}),
|
|
3618
|
-
...(input.result.sessionFile ? { sessionPath: input.result.sessionFile } : {}),
|
|
3619
|
-
}],
|
|
3620
|
-
};
|
|
3604
|
+
}): Promise<{ suffix: string; reference?: NonNullable<Details["parallelHandoff"]> }> {
|
|
3605
|
+
let admitted = false;
|
|
3621
3606
|
try {
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
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
|
+
});
|
|
3629
3658
|
} catch (error) {
|
|
3630
|
-
|
|
3631
|
-
|
|
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
|
+
});
|
|
3632
3676
|
}
|
|
3633
3677
|
|
|
3634
3678
|
async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Promise<AgentToolResult<Details>> {
|
|
@@ -3709,8 +3753,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3709
3753
|
|
|
3710
3754
|
const sourceCwd = effectiveCwd;
|
|
3711
3755
|
let pendingHandoff: Details["parallelHandoff"];
|
|
3712
|
-
const { setup: worktreeSetup, errorResult: worktreeSetupError } = createSingleWorktreeSetup(
|
|
3713
|
-
params.worktree,
|
|
3756
|
+
const { setup: worktreeSetup, errorResult: worktreeSetupError } = params.worktree ? await createSingleWorktreeSetup(
|
|
3714
3757
|
sourceCwd,
|
|
3715
3758
|
runId,
|
|
3716
3759
|
params.agent!,
|
|
@@ -3722,8 +3765,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3722
3765
|
deps.config.worktreeBranchPrefix,
|
|
3723
3766
|
params.lane?.key ?? params.workflowKey,
|
|
3724
3767
|
task,
|
|
3725
|
-
(
|
|
3726
|
-
pendingHandoff =
|
|
3768
|
+
(progress) => {
|
|
3769
|
+
pendingHandoff = writeWorktreeSetupHandoff({
|
|
3727
3770
|
manifestPath: parallelHandoffPath(artifactsDir, runId),
|
|
3728
3771
|
runId,
|
|
3729
3772
|
mode: "single",
|
|
@@ -3731,13 +3774,42 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3731
3774
|
cwd: sourceCwd,
|
|
3732
3775
|
stepIndex: 0,
|
|
3733
3776
|
flatStartIndex: 0,
|
|
3734
|
-
|
|
3777
|
+
progress,
|
|
3735
3778
|
laneBindings: params.workflowKey || lane ? [{ index: 0, taskIndex: 0, ...(params.workflowKey ? { workflowKey: params.workflowKey, runId } : {}), ...(lane ? { lane } : {}) }] : undefined,
|
|
3736
3779
|
});
|
|
3737
3780
|
},
|
|
3738
|
-
|
|
3739
|
-
|
|
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
|
+
}
|
|
3740
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
|
+
}
|
|
3741
3813
|
|
|
3742
3814
|
const authoredTask = task;
|
|
3743
3815
|
if (shouldForkAgent(contextPolicy, params.agent!)) {
|
|
@@ -3747,8 +3819,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3747
3819
|
const outputPath = resolveSingleOutputPath(effectiveOutput, ctx.cwd, singleCwd, resolveSingleRunOutputBaseDir(deps, artifactsDir, runId));
|
|
3748
3820
|
const validationError = validateFileOnlyOutputMode(effectiveOutputMode, outputPath, `Single run (${params.agent})`);
|
|
3749
3821
|
if (validationError) {
|
|
3750
|
-
|
|
3751
|
-
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 } };
|
|
3752
3824
|
}
|
|
3753
3825
|
const structuredRuntime = params.outputSchema
|
|
3754
3826
|
? createStructuredOutputRuntime(params.outputSchema, artifactConfig.enabled ? path.join(artifactsDir, "structured-output", runId) : undefined, { acceptanceReport: resolveAcceptanceReportMode(params.acceptance) })
|
|
@@ -3808,6 +3880,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3808
3880
|
}));
|
|
3809
3881
|
}
|
|
3810
3882
|
|
|
3883
|
+
const modelResponseAliases = deps.config.modelResponseAliases === undefined ? undefined : structuredClone(deps.config.modelResponseAliases);
|
|
3811
3884
|
const forwardSingleUpdate = onUpdate
|
|
3812
3885
|
? (update: AgentToolResult<Details>) => {
|
|
3813
3886
|
if (foregroundControl) updateForegroundChild(foregroundControl, 0, update.details?.progress?.[0]);
|
|
@@ -3867,6 +3940,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3867
3940
|
thinkingCeiling: agentConfig.maxThinking,
|
|
3868
3941
|
extensionBindings: params.extensionBindings,
|
|
3869
3942
|
availableModels,
|
|
3943
|
+
modelResponseAliases,
|
|
3870
3944
|
preferredModelProvider: currentProvider,
|
|
3871
3945
|
modelScope: modelScopes,
|
|
3872
3946
|
skills: effectiveSkills,
|
|
@@ -3879,14 +3953,14 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3879
3953
|
onDetachReady: (detach) => {
|
|
3880
3954
|
detachForeground = detach;
|
|
3881
3955
|
},
|
|
3882
|
-
onDetachedExit: (result) => {
|
|
3956
|
+
onDetachedExit: async (result) => {
|
|
3883
3957
|
if (resolveDetachedWorkflowChild) {
|
|
3884
3958
|
resolveDetachedWorkflowChild(result);
|
|
3885
3959
|
return;
|
|
3886
3960
|
}
|
|
3887
3961
|
try {
|
|
3888
3962
|
if (worktreeSetup) {
|
|
3889
|
-
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 });
|
|
3890
3964
|
}
|
|
3891
3965
|
try {
|
|
3892
3966
|
updateRememberedForegroundChild(deps.state, { runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, index: 0, result, events: deps.pi.events, notify: true });
|
|
@@ -3911,7 +3985,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3911
3985
|
try {
|
|
3912
3986
|
if (foregroundControl) finishForegroundChild(foregroundControl, 0);
|
|
3913
3987
|
} finally {
|
|
3914
|
-
removeForegroundControlIfIdle(deps.state, runId);
|
|
3988
|
+
removeForegroundControlIfIdle(deps.state, runId, deps.trackRetainedNestedRoute);
|
|
3915
3989
|
}
|
|
3916
3990
|
}
|
|
3917
3991
|
}
|
|
@@ -3922,12 +3996,13 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3922
3996
|
toolTimeoutMs: params.toolTimeoutMs,
|
|
3923
3997
|
configToolTimeoutMs: data.configToolTimeoutMs,
|
|
3924
3998
|
toolBudget: effectiveToolBudget.toolBudget,
|
|
3999
|
+
usageBudget: data.usageBudget ?? data.inheritedUsageBudget,
|
|
3925
4000
|
capabilityCeiling: data.capabilityCeiling,
|
|
3926
4001
|
allowZeroToolBudget: data.allowZeroToolBudget && effectiveToolBudget.toolBudget === data.toolBudget,
|
|
3927
4002
|
}));
|
|
3928
4003
|
r = launched.detached && detachedWorkflowChild ? await detachedWorkflowChild : launched;
|
|
3929
4004
|
} catch (error) {
|
|
3930
|
-
|
|
4005
|
+
await cleanupSingleWorktree();
|
|
3931
4006
|
throw error;
|
|
3932
4007
|
} finally {
|
|
3933
4008
|
// An attached runSync rejection still owns its child and structured runtime.
|
|
@@ -3942,11 +4017,11 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3942
4017
|
recordRun(params.agent!, cleanTask, r.exitCode, r.progressSummary?.durationMs ?? 0, r);
|
|
3943
4018
|
}
|
|
3944
4019
|
|
|
3945
|
-
let worktreeHandoff: ReturnType<typeof finalizeSingleWorktreeHandoff
|
|
4020
|
+
let worktreeHandoff: Awaited<ReturnType<typeof finalizeSingleWorktreeHandoff>> | undefined;
|
|
3946
4021
|
if (worktreeSetup) {
|
|
3947
4022
|
worktreeHandoff = r.detached
|
|
3948
4023
|
? { suffix: pendingHandoff ? formatParallelHandoffReference(pendingHandoff) : "", reference: pendingHandoff }
|
|
3949
|
-
: 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 });
|
|
3950
4025
|
}
|
|
3951
4026
|
|
|
3952
4027
|
if (r.progress) allProgress.push(r.progress);
|
|
@@ -3984,7 +4059,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3984
4059
|
usageBudget: usageBudgetState(data.usageBudget, totalCost),
|
|
3985
4060
|
...(worktreeHandoff?.reference ? { parallelHandoff: worktreeHandoff.reference } : {}),
|
|
3986
4061
|
}));
|
|
3987
|
-
rememberForegroundRun(deps.state, { runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, results: details.results, params, effectiveOutput, effectiveOutputMode, extensionBindings: params.extensionBindings });
|
|
4062
|
+
rememberForegroundRun(deps.state, { modelResponseAliases, runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, results: details.results, params, effectiveOutput, effectiveOutputMode, extensionBindings: params.extensionBindings });
|
|
3988
4063
|
|
|
3989
4064
|
const suppressRoutineResultIntercom = shouldSuppressRoutineResultIntercom({ suppressRoutineResultIntercom: params.suppressRoutineResultIntercom, results: [r] });
|
|
3990
4065
|
if (!r.detached && !r.interrupted && !suppressRoutineResultIntercom) {
|
|
@@ -4060,6 +4135,7 @@ function duplicateSubagentCallResult(params: SubagentParamsLike): AgentToolResul
|
|
|
4060
4135
|
}
|
|
4061
4136
|
|
|
4062
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>();
|
|
4063
4139
|
|
|
4064
4140
|
/**
|
|
4065
4141
|
* Terminal-mission retention can remove a mission while its children still
|
|
@@ -4140,9 +4216,13 @@ function workflowChildResult(
|
|
|
4140
4216
|
? result.details.results[0].finalOutput
|
|
4141
4217
|
: receiptOutput;
|
|
4142
4218
|
const childError = result.details.results.map((child) => child.error).find((error): error is string => Boolean(error));
|
|
4143
|
-
const
|
|
4144
|
-
? `${childError}\n\n${receiptOutput}`
|
|
4219
|
+
const failureErrorBase = childError && receiptOutput
|
|
4220
|
+
? receiptOutput.includes(childError) ? receiptOutput : `${childError}\n\n${receiptOutput}`
|
|
4145
4221
|
: childError || receiptOutput || output || "Child run failed.";
|
|
4222
|
+
const savedOutputEvidence = [...new Set(result.details.results.map((child) => child.savedOutputPath).filter((value): value is string => Boolean(value)))]
|
|
4223
|
+
.filter((savedOutputPath) => !failureErrorBase.includes(savedOutputPath))
|
|
4224
|
+
.map((savedOutputPath) => `Saved output: ${savedOutputPath}`);
|
|
4225
|
+
const failureError = [failureErrorBase, ...savedOutputEvidence].join("\n");
|
|
4146
4226
|
const detached = result.details.results.some((child) => child.detached);
|
|
4147
4227
|
const interrupted = result.details.results.some((child) => child.interrupted);
|
|
4148
4228
|
const stopped = result.details.results.some((child) => child.stopped);
|
|
@@ -4181,8 +4261,6 @@ function workflowChildResult(
|
|
|
4181
4261
|
}
|
|
4182
4262
|
const requestedContext = childParams.context === "fresh" || childParams.context === "fork" ? childParams.context : undefined;
|
|
4183
4263
|
const resolvedContext = result.details.context ?? (resolvedContexts.length === 1 ? resolvedContexts[0] : resolvedContexts.length > 1 ? "mixed" : undefined);
|
|
4184
|
-
const resumeSourceRunId = typeof childParams.resume === "string" && childParams.resume.trim() ? childParams.resume.trim() : undefined;
|
|
4185
|
-
const continuationRunIds = [...new Set([resumeSourceRunId, runId].filter((value): value is string => Boolean(value)))];
|
|
4186
4264
|
const outputReference = result.details.results.find((child) => child.savedOutputPath)?.savedOutputPath
|
|
4187
4265
|
?? result.details.results.find((child) => child.outputReference?.path)?.outputReference?.path;
|
|
4188
4266
|
const outputPathMapping = typeof childParams.task === "string" ? outputPathMappingFromTask(childParams.task, outputReference) : undefined;
|
|
@@ -4215,7 +4293,7 @@ function workflowChildResult(
|
|
|
4215
4293
|
...(outputPathMapping ? { outputPathMapping } : {}),
|
|
4216
4294
|
...(externalAdapter ? { externalAdapter } : {}),
|
|
4217
4295
|
resumability,
|
|
4218
|
-
continuation: { runIds:
|
|
4296
|
+
continuation: { runIds: runId ? [runId] : [] },
|
|
4219
4297
|
artifactPaths: [...artifactPaths],
|
|
4220
4298
|
results: result.details.results,
|
|
4221
4299
|
};
|
|
@@ -4328,10 +4406,33 @@ function missingWorkflowReceiptResumeHint(reference: WorkflowReceiptResumeRefere
|
|
|
4328
4406
|
}
|
|
4329
4407
|
}
|
|
4330
4408
|
|
|
4331
|
-
function
|
|
4332
|
-
reference: WorkflowReceiptResumeReference,
|
|
4333
|
-
|
|
4334
|
-
|
|
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
|
+
}
|
|
4335
4436
|
try {
|
|
4336
4437
|
const entry = resolveWorkflowReceiptResumeEntry({
|
|
4337
4438
|
reference,
|
|
@@ -4341,9 +4442,7 @@ function resolveKeyedWorkflowResume(
|
|
|
4341
4442
|
if (target.kind !== "revive") throw new Error(`Workflow receipt child '${reference.key}' latest run '${runId}' is still running.`);
|
|
4342
4443
|
},
|
|
4343
4444
|
});
|
|
4344
|
-
|
|
4345
|
-
if (!runId) throw new Error(`Workflow receipt child '${reference.key}' has no retained run id.`);
|
|
4346
|
-
return { runId, runIds: entry.continuation.runIds };
|
|
4445
|
+
return { runId: entry.latestRunId, runIds: entry.continuation.runIds };
|
|
4347
4446
|
} catch (error) {
|
|
4348
4447
|
const workflowRunId = reference.workflowRunId.trim();
|
|
4349
4448
|
if (isMissingWorkflowReceiptDiagnostic(error, workflowRunId)) {
|
|
@@ -4482,7 +4581,7 @@ export function prepareWorkflowLaunchParams(
|
|
|
4482
4581
|
const outputSchema = Object.hasOwn(childParams, "outputSchema") ? childParams.outputSchema : workflowDefaults.outputSchema;
|
|
4483
4582
|
if (outputSchema !== undefined) assertJsonSchemaObject(outputSchema, "outputSchema");
|
|
4484
4583
|
const agentContract = Object.hasOwn(childParams, "agentContract") ? childParams.agentContract : workflowDefaults.agentContract;
|
|
4485
|
-
if (agentContract !== undefined && !
|
|
4584
|
+
if (agentContract !== undefined && !isAgentContract(agentContract as AgentContract)) throw new Error("agentContract must be { version: 1 }.");
|
|
4486
4585
|
const acceptance = Object.hasOwn(childParams, "acceptance") ? childParams.acceptance : workflowDefaults.acceptance;
|
|
4487
4586
|
const output = Object.hasOwn(childParams, "output") ? childParams.output : workflowDefaults.output;
|
|
4488
4587
|
if (output !== undefined && typeof output !== "string" && typeof output !== "boolean") throw new Error("output must be a path string or boolean.");
|
|
@@ -4496,6 +4595,7 @@ export function prepareWorkflowLaunchParams(
|
|
|
4496
4595
|
message: typeof childParams.task === "string" ? childParams.task.trim() : "",
|
|
4497
4596
|
workflowParentRunId: parentWorkflowRunId,
|
|
4498
4597
|
workflowKey,
|
|
4598
|
+
...(options.outputClaimPath ? { workflowOutputClaimPath: options.outputClaimPath } : {}),
|
|
4499
4599
|
...(lane ? { lane } : {}),
|
|
4500
4600
|
...(worktree !== undefined ? { worktree: worktree as boolean } : {}),
|
|
4501
4601
|
...(baseRef !== undefined ? { baseRef: baseRef as string } : {}),
|
|
@@ -4514,12 +4614,13 @@ export function prepareWorkflowLaunchParams(
|
|
|
4514
4614
|
};
|
|
4515
4615
|
}
|
|
4516
4616
|
const control = mergeWorkflowControlOverrides(workflowDefaults.control, childParams.control as ControlConfig | undefined);
|
|
4617
|
+
const asyncOmitted = childParams.async === undefined && workflowDefaults.async === undefined;
|
|
4517
4618
|
const launchParams = {
|
|
4518
4619
|
...workflowDefaults,
|
|
4519
|
-
|
|
4620
|
+
...(options.externalAsyncRequired === true && asyncOmitted ? { async: true } : {}),
|
|
4520
4621
|
...childParams,
|
|
4521
4622
|
...(control !== undefined ? { control } : {}),
|
|
4522
|
-
...(
|
|
4623
|
+
...(asyncOmitted ? { workflowAwaitAsync: true } : {}),
|
|
4523
4624
|
...(options.missionDetached ? { mission: false } : {}),
|
|
4524
4625
|
workflowParentRunId: parentWorkflowRunId,
|
|
4525
4626
|
workflowKey,
|
|
@@ -4657,14 +4758,14 @@ function applyWorkflowLaneTrace(graph: WorkflowGraphSnapshot, trace: WorkflowScr
|
|
|
4657
4758
|
else delete graph.currentNodeId;
|
|
4658
4759
|
}
|
|
4659
4760
|
|
|
4660
|
-
function
|
|
4761
|
+
function workflowProgressUpdate(
|
|
4661
4762
|
runId: string,
|
|
4662
4763
|
chatProgress: WorkflowChatProgressProjection,
|
|
4663
4764
|
workflow: NonNullable<Details["workflow"]>,
|
|
4664
4765
|
workflowChildren?: Details["workflowChildren"],
|
|
4665
|
-
preflight?: import("../../shared/types.ts").
|
|
4666
|
-
): AgentToolResult<Details>
|
|
4667
|
-
|
|
4766
|
+
preflight?: import("../../shared/types.ts").WorkflowPreflight,
|
|
4767
|
+
): AgentToolResult<Details> {
|
|
4768
|
+
// chatProgress controls the TUI projection, not transport-level progress.
|
|
4668
4769
|
return {
|
|
4669
4770
|
content: [{ type: "text", text: "Workflow running." }],
|
|
4670
4771
|
details: { mode: "workflow", runId, results: [], workflow, ...(preflight ? { preflight } : {}), ...(workflowChildren ? { workflowChildren } : {}), chatProgress },
|
|
@@ -4740,6 +4841,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4740
4841
|
signal: AbortSignal,
|
|
4741
4842
|
ctx: ExtensionContext,
|
|
4742
4843
|
) => Promise<AgentToolResult<Details>>;
|
|
4844
|
+
/** Scheduled state visible to the current runtime supervisor owner only. */
|
|
4845
|
+
getCurrentSupervisorOwnerStates: () => Iterable<SubagentState>;
|
|
4743
4846
|
} {
|
|
4744
4847
|
const delegatedThinkingOverrides = new WeakMap<object, AgentConfig["thinking"]>();
|
|
4745
4848
|
const delegatedZeroToolBudgets = new WeakSet<object>();
|
|
@@ -4748,7 +4851,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4748
4851
|
const workflowResourcePermits = new WeakMap<object, WorkflowResourcePermit>();
|
|
4749
4852
|
const workflowPermitContexts = new WeakMap<object, { root: WorkflowChildPermit } | { child: WorkflowChildPermitContext }>();
|
|
4750
4853
|
const warnedArtifactPackageDirs = new Set<string>();
|
|
4751
|
-
const scheduledOwnerExecutors = new Map<string, ReturnType<typeof createSubagentExecutor>>();
|
|
4854
|
+
const scheduledOwnerExecutors = new Map<string | null, Map<string, { state: SubagentState; executor: ReturnType<typeof createSubagentExecutor> }>>();
|
|
4752
4855
|
const execute = async (
|
|
4753
4856
|
_id: string,
|
|
4754
4857
|
params: SubagentParamsLike,
|
|
@@ -4759,6 +4862,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4759
4862
|
parentModelOverride?: ParentModel | null,
|
|
4760
4863
|
): Promise<AgentToolResult<Details>> => {
|
|
4761
4864
|
const workflowLaunchObserver = workflowLaunchObservers.get(params);
|
|
4865
|
+
const inheritedUsageBudget = workflowOwnedUsageBudgets.get(params);
|
|
4762
4866
|
const delegatedThinkingOverride = delegatedThinkingOverrides.get(params);
|
|
4763
4867
|
const allowZeroToolBudget = delegatedZeroToolBudgets.has(params);
|
|
4764
4868
|
const delegatedExecution = delegatedExecutions.has(params);
|
|
@@ -4776,7 +4880,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4776
4880
|
let requestParams = normalizedGate.params;
|
|
4777
4881
|
const capacityOverrideError = validateWorkflowCapacityOverrides(requestParams);
|
|
4778
4882
|
if (capacityOverrideError) return buildRequestedModeError(requestParams, capacityOverrideError);
|
|
4779
|
-
let workflowPreflight: import("../../shared/types.ts").
|
|
4883
|
+
let workflowPreflight: import("../../shared/types.ts").WorkflowPreflight | undefined;
|
|
4780
4884
|
try {
|
|
4781
4885
|
if (requestParams.preflight !== undefined && requestParams.workflowScript === undefined && requestParams.workflowScriptPath === undefined) {
|
|
4782
4886
|
throw new Error("preflight requires workflowScript or workflowScriptPath.");
|
|
@@ -4807,7 +4911,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4807
4911
|
}
|
|
4808
4912
|
const normalizedAction = typeof requestParams.action === "string" ? requestParams.action.trim() : requestParams.action;
|
|
4809
4913
|
if (normalizedAction === "resume" && requestParams.extensionBindings !== undefined) return buildRequestedModeError(requestParams, "extensionBindings is not supported with action='resume'; resume uses the original retained child binding.");
|
|
4810
|
-
let workflowResource: { permit: WorkflowResourcePermit; provenance:
|
|
4914
|
+
let workflowResource: { permit: WorkflowResourcePermit; provenance: WorkflowResourceProvenance; authority: WorkflowResourceAuthority } | undefined;
|
|
4811
4915
|
if (workflowResourcePermit) {
|
|
4812
4916
|
if (typeof requestParams.workflowScript !== "string") return buildRequestedModeError(requestParams, "Resolved workflow resource is missing its workflow script.");
|
|
4813
4917
|
const consumed = consumeWorkflowResourcePermit(workflowResourcePermit, requestParams.workflowScript);
|
|
@@ -4815,8 +4919,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4815
4919
|
workflowResource = { permit: workflowResourcePermit, ...consumed };
|
|
4816
4920
|
}
|
|
4817
4921
|
if (requestParams.workflowScript !== undefined && normalizedAction === undefined) {
|
|
4922
|
+
const acceptanceErrors = validateAcceptanceInput(requestParams.acceptance);
|
|
4923
|
+
if (acceptanceErrors.length > 0) return buildRequestedModeError(requestParams, acceptanceErrors.join(" "));
|
|
4924
|
+
const foregroundWorkflowRunId = encodeIndexSegment(_id);
|
|
4818
4925
|
if (delegatedWorkflowPermit) {
|
|
4819
|
-
const permitError = validateWorkflowChildPermitRoot(delegatedWorkflowPermit,
|
|
4926
|
+
const permitError = validateWorkflowChildPermitRoot(delegatedWorkflowPermit, foregroundWorkflowRunId);
|
|
4820
4927
|
if (permitError) return buildRequestedModeError(requestParams, permitError);
|
|
4821
4928
|
if (requestParams.async !== false) return buildRequestedModeError(requestParams, "Workflow child permit supports foreground workflow roots only; set async:false.");
|
|
4822
4929
|
}
|
|
@@ -4991,18 +5098,31 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4991
5098
|
onSuccess: (_filePath, payload) => { indexedState = (payload as { state: AsyncStatus["state"] }).state; },
|
|
4992
5099
|
onError: (error, filePath) => console.error(`Failed to update async workflow index '${filePath}':`, error),
|
|
4993
5100
|
});
|
|
4994
|
-
const queueActiveRunIndex = (): void => {
|
|
4995
|
-
const state =
|
|
5101
|
+
const queueActiveRunIndex = (snapshot: AsyncStatus = status): void => {
|
|
5102
|
+
const state = snapshot.state;
|
|
4996
5103
|
if (indexedState === state && indexPersistence.pendingCount() === 0) return;
|
|
4997
|
-
indexPersistence.write(asyncDir, { state, toolCallId:
|
|
5104
|
+
indexPersistence.write(asyncDir, { state, toolCallId: snapshot.toolCallId }, (_filePath, payload) => {
|
|
4998
5105
|
const indexPayload = payload as { state: AsyncStatus["state"]; toolCallId?: string };
|
|
4999
5106
|
updateActiveRunIndex(asyncDir, indexPayload.state, indexPayload.toolCallId, { retryCapacityErrors: true });
|
|
5000
5107
|
});
|
|
5001
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
|
+
};
|
|
5002
5116
|
const runPersistence = createCapacityResilientJsonWriter({
|
|
5003
5117
|
keepAlive: true,
|
|
5004
|
-
onSuccess: (filePath) => {
|
|
5005
|
-
|
|
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
|
+
},
|
|
5006
5126
|
write: (filePath, payload) => filePath === resultPath
|
|
5007
5127
|
? writeAsyncResultFile(filePath, payload as Record<string, unknown>)
|
|
5008
5128
|
: writeAtomicJson(filePath, payload),
|
|
@@ -5030,7 +5150,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5030
5150
|
queueActiveRunIndex();
|
|
5031
5151
|
} else if (options.tolerateStatusWriteFailure) {
|
|
5032
5152
|
try {
|
|
5033
|
-
runPersistence.write(statusPath, status);
|
|
5153
|
+
runPersistence.write(statusPath, { ...status });
|
|
5034
5154
|
statusPersistenceDegraded = false;
|
|
5035
5155
|
} catch (error) {
|
|
5036
5156
|
const message = `Failed to persist async workflow state ${statusPath}: ${error instanceof Error ? error.message : String(error)}`;
|
|
@@ -5045,7 +5165,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5045
5165
|
}
|
|
5046
5166
|
}
|
|
5047
5167
|
} else {
|
|
5048
|
-
runPersistence.write(statusPath, status);
|
|
5168
|
+
runPersistence.write(statusPath, { ...status });
|
|
5049
5169
|
}
|
|
5050
5170
|
if (liveJob) {
|
|
5051
5171
|
liveJob.status = status.state;
|
|
@@ -5073,12 +5193,15 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5073
5193
|
};
|
|
5074
5194
|
const writeWorkflowResult = (payload: Record<string, unknown>): boolean => {
|
|
5075
5195
|
try {
|
|
5196
|
+
let resultPublished = false;
|
|
5197
|
+
settleResultPublication = (published) => { resultPublished = published; };
|
|
5198
|
+
pendingResultPublication = undefined;
|
|
5076
5199
|
runPersistence.write(resultPath, payload);
|
|
5200
|
+
// Only capacity deferral yields; ordinary publication remains synchronous.
|
|
5201
|
+
if (!resultPublished) pendingResultPublication = new Promise<boolean>((resolve) => { settleResultPublication = resolve; });
|
|
5077
5202
|
return true;
|
|
5078
5203
|
} catch (error) {
|
|
5079
|
-
|
|
5080
|
-
console.error(message, error);
|
|
5081
|
-
appendWorkflowEvent({ type: "subagent.workflow.result_write_failed", error: message });
|
|
5204
|
+
reportResultWriteFailure(error);
|
|
5082
5205
|
return false;
|
|
5083
5206
|
}
|
|
5084
5207
|
};
|
|
@@ -5118,7 +5241,148 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5118
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: [] } };
|
|
5119
5242
|
}
|
|
5120
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?.();
|
|
5121
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
|
+
};
|
|
5122
5386
|
void Promise.resolve().then(async () => {
|
|
5123
5387
|
const workflowDeadlineAt = timeout === undefined ? undefined : Date.now() + timeout;
|
|
5124
5388
|
const workflowResults: SingleResult[] = [];
|
|
@@ -5126,7 +5390,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5126
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;
|
|
5127
5391
|
const workflowOutput = typeof workflowChildDefaults.output === "string" || typeof workflowChildDefaults.output === "boolean" ? workflowChildDefaults.output : undefined;
|
|
5128
5392
|
const configuredOutputBaseDir = resolveConfiguredSingleRunOutputBaseDir(deps);
|
|
5129
|
-
const workflowAggregateOutputPath =
|
|
5393
|
+
const workflowAggregateOutputPath = resolveSingleOutputPath(workflowOutput, parentCwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, workflowRunId));
|
|
5130
5394
|
const claimedOutputPaths = new Map<string, string>();
|
|
5131
5395
|
const childOutputOverrides = new Map<string, string>();
|
|
5132
5396
|
const childOutputClaimPaths = new Map<string, string>();
|
|
@@ -5311,6 +5575,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5311
5575
|
deps.asyncByDefault,
|
|
5312
5576
|
);
|
|
5313
5577
|
preparedChildParams = childRequest;
|
|
5578
|
+
if (workflowUsageBudget.budget) workflowOwnedUsageBudgets.set(childRequest, workflowUsageBudget.budget);
|
|
5314
5579
|
workflowLaunchObservers.set(childRequest, (launch) => {
|
|
5315
5580
|
const step = status.steps?.find((candidate) => candidate.workflowKey === key);
|
|
5316
5581
|
if (step) {
|
|
@@ -5359,7 +5624,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5359
5624
|
if (child.runId) workflowChildRunIds.set(key, child.runId);
|
|
5360
5625
|
const step = status.steps?.find((candidate) => candidate.workflowKey === key);
|
|
5361
5626
|
if (step) {
|
|
5362
|
-
step.async = Boolean(result.details.asyncId || result.details.asyncDir);
|
|
5627
|
+
step.async = step.async === true || Boolean(result.details.asyncId || result.details.asyncDir);
|
|
5363
5628
|
if (child.runId) step.runId = child.runId;
|
|
5364
5629
|
if (child.lane) step.lane = child.lane;
|
|
5365
5630
|
}
|
|
@@ -5381,7 +5646,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5381
5646
|
return child;
|
|
5382
5647
|
},
|
|
5383
5648
|
status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession, workflowParentModel)),
|
|
5384
|
-
resolveResume: (reference) =>
|
|
5649
|
+
resolveResume: (reference, _signal, index) => resolveWorkflowResume(reference, deps, ctx.sessionManager.getSessionFile() ?? null, index),
|
|
5385
5650
|
steer: (key, message, options, workflowSignal) => steerWorkflowChildByKey({ state: deps.state, workflowRunId, key, message, options, signal: workflowSignal, resolveRunId: () => workflowChildRunIds.get(key) }),
|
|
5386
5651
|
});
|
|
5387
5652
|
const finalPreflightWarnings = workflowPreflightWarnings(workflowPreflight, workflow.trace, { settled: true });
|
|
@@ -5392,16 +5657,20 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5392
5657
|
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, summary, producedChildOutputPaths);
|
|
5393
5658
|
const resultSummary = appendWorkflowOutputWarning(summary, outputWarning);
|
|
5394
5659
|
const workflowUsage = sumResultsUsage(workflowResults);
|
|
5660
|
+
settleWorkflowSteerInbox("complete");
|
|
5395
5661
|
const workflowChildren = workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState: "completed", inventoryComplete: true, trace: workflow.trace, children: workflow.children, steps: status.steps });
|
|
5396
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) };
|
|
5397
5663
|
const receipt = terminalWorkflowReceipt(workflowRunId, "complete", workflow.children, workflowChildren, undefined, validHostStepNodes(status.workflowGraph), workflowResource?.provenance);
|
|
5664
|
+
delete status.workflowReceiptPath;
|
|
5398
5665
|
let workflowReceipt: { path: string; receipt: WorkflowReceipt } | undefined;
|
|
5399
5666
|
try {
|
|
5400
5667
|
workflowReceipt = { path: writeWorkflowReceipt(asyncDir, receipt), receipt };
|
|
5668
|
+
status.workflowReceiptPath = workflowReceipt.path;
|
|
5401
5669
|
} catch (receiptError) {
|
|
5402
5670
|
appendWorkflowEvent({ type: "subagent.workflow.receipt_write_failed", error: `Failed to persist async workflow receipt: ${receiptError instanceof Error ? receiptError.message : String(receiptError)}` });
|
|
5403
5671
|
}
|
|
5404
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;
|
|
5405
5674
|
persist();
|
|
5406
5675
|
deps.refreshResultDelivery?.();
|
|
5407
5676
|
persistClosed = true;
|
|
@@ -5425,10 +5694,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5425
5694
|
}
|
|
5426
5695
|
}
|
|
5427
5696
|
const workflowState = state === "paused" ? "paused" : state === "stopped" ? "stopped" : "failed";
|
|
5697
|
+
settleWorkflowSteerInbox(state);
|
|
5428
5698
|
const workflowChildren = workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState, inventoryComplete: true, trace: partial.trace, children: partial.children, steps: status.steps });
|
|
5429
5699
|
const finalPreflightWarnings = workflowPreflightWarnings(workflowPreflight, partial.trace, { settled: true });
|
|
5430
5700
|
const finalPreflightTrace = annotateWorkflowPreflightTrace(partial.trace, workflowPreflight);
|
|
5431
|
-
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 });
|
|
5432
5702
|
if (pauseForDetached) {
|
|
5433
5703
|
const promoted = promotePausedWorkflowIfSettled(status);
|
|
5434
5704
|
if (promoted) status = promoted;
|
|
@@ -5441,18 +5711,27 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5441
5711
|
const receiptState: WorkflowReceiptState = status.state === "complete" ? "complete" : status.state === "paused" ? "paused" : status.state === "stopped" ? "stopped" : "failed";
|
|
5442
5712
|
const terminalOutcome = workflowFailureTerminalOutcome(error, partial.children, usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)));
|
|
5443
5713
|
const receipt = terminalWorkflowReceipt(workflowRunId, receiptState, partial.children, workflowChildren, terminalOutcome, validHostStepNodes(status.workflowGraph), workflowResource?.provenance);
|
|
5714
|
+
delete status.workflowReceiptPath;
|
|
5444
5715
|
let workflowReceipt: { path: string; receipt: WorkflowReceipt } | undefined;
|
|
5445
5716
|
try {
|
|
5446
5717
|
workflowReceipt = { path: writeWorkflowReceipt(asyncDir, receipt), receipt };
|
|
5718
|
+
status.workflowReceiptPath = workflowReceipt.path;
|
|
5447
5719
|
} catch (receiptError) {
|
|
5448
5720
|
appendWorkflowEvent({ type: "subagent.workflow.receipt_write_failed", error: `Failed to persist async workflow receipt: ${receiptError instanceof Error ? receiptError.message : String(receiptError)}` });
|
|
5449
5721
|
}
|
|
5450
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;
|
|
5451
5724
|
persist();
|
|
5452
5725
|
deps.refreshResultDelivery?.();
|
|
5453
5726
|
persistClosed = true;
|
|
5454
5727
|
appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(terminalOutcome ? { terminalOutcome } : {}), ...(status.error ? { error: status.error } : {}), ...(status.activityState ? { activityState: status.activityState } : {}) });
|
|
5455
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
|
+
}
|
|
5456
5735
|
persistClosed = true;
|
|
5457
5736
|
deps.state.workflowControllers?.delete(workflowRunId);
|
|
5458
5737
|
deps.state.workflowChildStops?.delete(workflowRunId);
|
|
@@ -5468,7 +5747,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5468
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;
|
|
5469
5748
|
const workflowOutput = typeof workflowChildDefaults.output === "string" || typeof workflowChildDefaults.output === "boolean" ? workflowChildDefaults.output : undefined;
|
|
5470
5749
|
const configuredOutputBaseDir = resolveConfiguredSingleRunOutputBaseDir(deps);
|
|
5471
|
-
const workflowAggregateOutputPath =
|
|
5750
|
+
const workflowAggregateOutputPath = resolveSingleOutputPath(workflowOutput, ctx.cwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, foregroundWorkflowRunId));
|
|
5472
5751
|
const claimedOutputPaths = new Map<string, string>();
|
|
5473
5752
|
const childOutputOverrides = new Map<string, string>();
|
|
5474
5753
|
const childOutputClaimPaths = new Map<string, string>();
|
|
@@ -5478,7 +5757,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5478
5757
|
const runHostCommand = workflowHostCommandRunner({
|
|
5479
5758
|
workflowCwd,
|
|
5480
5759
|
artifactsDir: workflowArtifactsDir,
|
|
5481
|
-
workflowRunId:
|
|
5760
|
+
workflowRunId: foregroundWorkflowRunId,
|
|
5482
5761
|
claimedOutputPaths,
|
|
5483
5762
|
producedOutputPaths: producedChildOutputPaths,
|
|
5484
5763
|
...(workflowResource ? { authorize: (key, params) => authorizeWorkflowResourceHost(workflowResource!.permit, key, params.command) } : {}),
|
|
@@ -5488,19 +5767,26 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5488
5767
|
: workflowResource
|
|
5489
5768
|
? workflowResource.authority.host ? runHostCommand : undefined
|
|
5490
5769
|
: publicExecution ? undefined : runHostCommand;
|
|
5491
|
-
const workflowHostSteps = new Map<string,
|
|
5770
|
+
const workflowHostSteps = new Map<string, HostStepNode>();
|
|
5492
5771
|
let liveWorkflow: NonNullable<Details["workflow"]> = { trace: [], emits: [], console: [], ...(workflowResource ? { resource: workflowResource.provenance } : {}) };
|
|
5493
|
-
|
|
5772
|
+
const childProgress = new Map<string, ReturnType<typeof workflowChildProgress>>();
|
|
5773
|
+
let activityTimer: ReturnType<typeof setTimeout> | undefined;
|
|
5774
|
+
let lastProgressAt = 0;
|
|
5775
|
+
let workflowProgressClosed = false;
|
|
5494
5776
|
const workflowDeadlineAt = timeout === undefined ? undefined : Date.now() + timeout;
|
|
5495
5777
|
const workflowCapabilityCeiling = intersectSubagentCapabilityCeilings(requestParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(resolveCurrentSessionId(ctx.sessionManager)));
|
|
5496
5778
|
const sendWorkflowProgress = () => {
|
|
5497
|
-
|
|
5498
|
-
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));
|
|
5499
5785
|
};
|
|
5500
5786
|
try {
|
|
5501
5787
|
const workflow = await runWorkflowScript({
|
|
5502
5788
|
script: requestParams.workflowScript,
|
|
5503
|
-
...(delegatedWorkflowPermit ? { oneUsePermit: { claim: (key: string) => claimWorkflowChildPermit(delegatedWorkflowPermit,
|
|
5789
|
+
...(delegatedWorkflowPermit ? { oneUsePermit: { claim: (key: string) => claimWorkflowChildPermit(delegatedWorkflowPermit, foregroundWorkflowRunId, key) } } : {}),
|
|
5504
5790
|
globalConcurrencyLimit: requestParams.globalConcurrencyLimit ?? deps.config.globalConcurrencyLimit,
|
|
5505
5791
|
timeoutMs: timeout,
|
|
5506
5792
|
signal,
|
|
@@ -5513,11 +5799,13 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5513
5799
|
const projectedTrace = annotateWorkflowPreflightTrace(trace, workflowPreflight);
|
|
5514
5800
|
const preflightWarnings = workflowPreflightWarnings(workflowPreflight, trace);
|
|
5515
5801
|
liveWorkflow = { ...liveWorkflow, trace: projectedTrace, ...(preflightWarnings.length ? { preflightWarnings } : {}) };
|
|
5516
|
-
|
|
5802
|
+
for (const entry of trace) {
|
|
5803
|
+
if (entry.operation === "run" && ["completed", "failed", "stopped", "detached"].includes(entry.state)) childProgress.delete(entry.key);
|
|
5804
|
+
}
|
|
5517
5805
|
sendWorkflowProgress();
|
|
5518
5806
|
},
|
|
5519
5807
|
admit: (calls) => {
|
|
5520
|
-
const outputClaims = workflowChildOutputClaims({ ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId:
|
|
5808
|
+
const outputClaims = workflowChildOutputClaims({ ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId: foregroundWorkflowRunId, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: discoverWorkflowAgents, agents: workflowAgents, workflowAgentScope: workflowChildDefaults.agentScope, state: deps.state, claimedOutputPaths, entries: calls });
|
|
5521
5809
|
if (outputClaims.error) throw new Error(outputClaims.error);
|
|
5522
5810
|
claimRunFanoutBatch(workflowFanoutBudget, calls.map(({ key }) => `workflow[${key}]`));
|
|
5523
5811
|
if (outputClaims.claims) applyWorkflowChildOutputClaims(claimedOutputPaths, outputClaims.claims);
|
|
@@ -5537,7 +5825,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5537
5825
|
if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)), childParams, deps.state, { state: "partial", reason: "budget_exhausted" });
|
|
5538
5826
|
const childPhase = typeof childParams.phase === "string" && childParams.phase.trim() ? childParams.phase.trim() : undefined;
|
|
5539
5827
|
const childLabel = typeof childParams.label === "string" && childParams.label.trim() ? childParams.label.trim() : undefined;
|
|
5540
|
-
recordMissionWorkflowChild(missionBinding,
|
|
5828
|
+
recordMissionWorkflowChild(missionBinding, foregroundWorkflowRunId, key, {
|
|
5541
5829
|
status: "running",
|
|
5542
5830
|
...(typeof childParams.agent === "string" && childParams.agent.trim() ? { agent: childParams.agent.trim() } : {}),
|
|
5543
5831
|
...(childLabel ? { label: childLabel } : {}),
|
|
@@ -5545,13 +5833,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5545
5833
|
heartbeat: { status: "running", ...(childPhase ? { phase: childPhase } : {}) },
|
|
5546
5834
|
});
|
|
5547
5835
|
let preparedChildParams: SubagentParamsLike | undefined;
|
|
5548
|
-
const result = await runMissionWorkflowChild(missionBinding,
|
|
5836
|
+
const result = await runMissionWorkflowChild(missionBinding, foregroundWorkflowRunId, key, childPhase, () => {
|
|
5549
5837
|
const childRequest = bindMissionWorkflowChildAsyncLaunch(
|
|
5550
|
-
{ ...prepareWorkflowChildLaunchParams({ workflowDefaults: workflowChildDefaults, childParams, parentWorkflowRunId:
|
|
5838
|
+
{ ...prepareWorkflowChildLaunchParams({ workflowDefaults: workflowChildDefaults, childParams: delegatedWorkflowPermit ? { ...childParams, async: false } : childParams, parentWorkflowRunId: foregroundWorkflowRunId, workflowKey: key, ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: discoverWorkflowAgents, agents: workflowAgents, workflowAgentScope: workflowChildDefaults.agentScope, outputOverride: childOutputOverrides.get(key), outputClaimPath: childOutputClaimPaths.get(key), options: { missionDetached: detachWorkflowChildMissions, suppressRoutineResultIntercom: chatProgress.mode === "live-card", runFanoutBudget: workflowFanoutBudget, parentDeadlineAt: workflowDeadlineAt, capabilityCeiling: workflowCapabilityCeiling } }), runFanoutAdmitted: admission.admitted },
|
|
5551
5839
|
missionBinding,
|
|
5552
5840
|
deps.asyncByDefault,
|
|
5553
5841
|
);
|
|
5554
5842
|
preparedChildParams = childRequest;
|
|
5843
|
+
if (workflowUsageBudget.budget) workflowOwnedUsageBudgets.set(childRequest, workflowUsageBudget.budget);
|
|
5555
5844
|
if (delegatedWorkflowPermit) {
|
|
5556
5845
|
if (childRequest.async !== false) throw new Error("Workflow child permit supports one foreground child only.");
|
|
5557
5846
|
const childCwd = resolveRequestedCwd(workflowCwd, childRequest.cwd);
|
|
@@ -5559,9 +5848,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5559
5848
|
? resolveAgentName(childRequest.agent, discoverWorkflowAgents(childCwd, resolveExecutionAgentScope(childRequest.agentScope)).agents).agent
|
|
5560
5849
|
: undefined;
|
|
5561
5850
|
if (childAgent?.runner?.type === "external-cli" || childAgent?.runner?.type === "external-job") throw new Error("Workflow child permit supports native Pi children only.");
|
|
5562
|
-
workflowPermitContexts.set(childRequest, { child: { permit: delegatedWorkflowPermit, workflowRunId:
|
|
5851
|
+
workflowPermitContexts.set(childRequest, { child: { permit: delegatedWorkflowPermit, workflowRunId: foregroundWorkflowRunId, childKey: key } });
|
|
5563
5852
|
}
|
|
5564
|
-
workflowLaunchObservers.set(childRequest, (launch) => recordMissionWorkflowChild(missionBinding,
|
|
5853
|
+
workflowLaunchObservers.set(childRequest, (launch) => recordMissionWorkflowChild(missionBinding, foregroundWorkflowRunId, key, {
|
|
5565
5854
|
status: "running",
|
|
5566
5855
|
agent: launch.agent,
|
|
5567
5856
|
...(launch.sessionFile ? { sessionPath: launch.sessionFile } : {}),
|
|
@@ -5569,8 +5858,17 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5569
5858
|
return execute(randomUUID(), childRequest, workflowSignal, (update) => {
|
|
5570
5859
|
const progress = update.details.progress?.[0];
|
|
5571
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
|
+
}
|
|
5572
5870
|
const progressStatus = progress.status === "completed" ? "completed" : progress.status === "failed" ? "failed" : "running";
|
|
5573
|
-
recordMissionWorkflowChild(missionBinding,
|
|
5871
|
+
recordMissionWorkflowChild(missionBinding, foregroundWorkflowRunId, key, {
|
|
5574
5872
|
status: progressStatus,
|
|
5575
5873
|
heartbeat: { status: progressStatus, ...(childPhase ? { phase: childPhase } : {}) },
|
|
5576
5874
|
});
|
|
@@ -5584,7 +5882,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5584
5882
|
const child = workflowChildResult(key, result, preparedChildParams ?? childParams, deps.state);
|
|
5585
5883
|
if (child.runId) workflowChildRunIds.set(key, child.runId);
|
|
5586
5884
|
const childStatus = missionWorkflowChildStatus(result);
|
|
5587
|
-
recordMissionWorkflowChild(missionBinding,
|
|
5885
|
+
recordMissionWorkflowChild(missionBinding, foregroundWorkflowRunId, key, {
|
|
5588
5886
|
status: childStatus,
|
|
5589
5887
|
...(child.runId ? { runId: child.runId } : {}),
|
|
5590
5888
|
...(result.details.results[0]?.agent ? { agent: result.details.results[0].agent } : {}),
|
|
@@ -5596,13 +5894,13 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5596
5894
|
return child;
|
|
5597
5895
|
},
|
|
5598
5896
|
status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession, workflowParentModel)),
|
|
5599
|
-
resolveResume: (reference) =>
|
|
5600
|
-
steer: (key, message, options, workflowSignal) => steerWorkflowChildByKey({ state: deps.state, workflowRunId:
|
|
5897
|
+
resolveResume: (reference, _signal, index) => resolveWorkflowResume(reference, deps, ctx.sessionManager.getSessionFile() ?? null, index),
|
|
5898
|
+
steer: (key, message, options, workflowSignal) => steerWorkflowChildByKey({ state: deps.state, workflowRunId: foregroundWorkflowRunId, key, message, options, signal: workflowSignal, resolveRunId: () => workflowChildRunIds.get(key) }),
|
|
5601
5899
|
});
|
|
5602
5900
|
const finalPreflightWarnings = workflowPreflightWarnings(workflowPreflight, workflow.trace, { settled: true });
|
|
5603
5901
|
const finalPreflightTrace = annotateWorkflowPreflightTrace(workflow.trace, workflowPreflight);
|
|
5604
|
-
const workflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId:
|
|
5605
|
-
const receipt = terminalWorkflowReceipt(
|
|
5902
|
+
const workflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: foregroundWorkflowRunId, workflowState: "completed", inventoryComplete: true, trace: workflow.trace, children: workflow.children });
|
|
5903
|
+
const receipt = terminalWorkflowReceipt(foregroundWorkflowRunId, "complete", workflow.children, workflowChildren, undefined, [...workflowHostSteps.values()], workflowResource?.provenance);
|
|
5606
5904
|
const traceLines = finalPreflightTrace.map((entry) => `- ${entry.operation} ${entry.key}: ${entry.state}${entry.runId ? ` (${entry.runId})` : ""}${entry.durationMs !== undefined ? ` in ${entry.durationMs}ms` : ""}${entry.warning ? ` — ${entry.warning}` : ""}${entry.error ? ` — ${entry.error}` : ""}`);
|
|
5607
5905
|
const sections = [
|
|
5608
5906
|
...(workflowPreflight ? [formatWorkflowPreflight(workflowPreflight)] : []),
|
|
@@ -5620,11 +5918,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5620
5918
|
const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
|
|
5621
5919
|
return attachWorkflowMission(withRunFanoutBudget({
|
|
5622
5920
|
content: [{ type: "text", text: displayText }],
|
|
5623
|
-
details: compactOptional<Details>({ mode: "workflow", runId:
|
|
5921
|
+
details: compactOptional<Details>({ mode: "workflow", runId: foregroundWorkflowRunId, results: workflowDetailsResults(workflow.children), ...(workflowPreflight ? { preflight: workflowPreflight } : {}), workflowChildren, totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { value: workflow.value, trace: finalPreflightTrace, emits: workflow.emits, console: workflow.console, ...(workflowResource ? { resource: workflowResource.provenance } : {}), ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}), receipt }, chatProgress }),
|
|
5624
5922
|
}, workflowFanoutBudget));
|
|
5625
5923
|
} catch (error) {
|
|
5626
5924
|
const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
|
|
5627
|
-
const text = workflowFailureMessage(error,
|
|
5925
|
+
const text = workflowFailureMessage(error, foregroundWorkflowRunId, partial.children);
|
|
5628
5926
|
const finalPreflightWarnings = workflowPreflightWarnings(workflowPreflight, partial.trace, { settled: true });
|
|
5629
5927
|
const finalPreflightTrace = annotateWorkflowPreflightTrace(partial.trace, workflowPreflight);
|
|
5630
5928
|
const traceLines = finalPreflightTrace.map((entry) => `- ${entry.operation} ${entry.key}: ${entry.state}${entry.runId ? ` (${entry.runId})` : ""}${entry.warning ? ` — ${entry.warning}` : ""}${entry.error ? ` — ${entry.error}` : ""}`);
|
|
@@ -5641,14 +5939,18 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5641
5939
|
const workflowText = sections.join("\n\n");
|
|
5642
5940
|
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, workflowText, producedChildOutputPaths);
|
|
5643
5941
|
const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
|
|
5644
|
-
const workflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId:
|
|
5942
|
+
const workflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: foregroundWorkflowRunId, workflowState: "failed", inventoryComplete: true, trace: partial.trace, children: partial.children });
|
|
5645
5943
|
const terminalOutcome = workflowFailureTerminalOutcome(error, partial.children, usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)));
|
|
5646
|
-
const receipt = terminalWorkflowReceipt(
|
|
5944
|
+
const receipt = terminalWorkflowReceipt(foregroundWorkflowRunId, "failed", partial.children, workflowChildren, terminalOutcome, [...workflowHostSteps.values()], workflowResource?.provenance);
|
|
5647
5945
|
return attachWorkflowMission(withRunFanoutBudget({
|
|
5648
5946
|
content: [{ type: "text", text: displayText }],
|
|
5649
5947
|
isError: true,
|
|
5650
|
-
details: compactOptional<Details>({ mode: "workflow", runId:
|
|
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 }),
|
|
5651
5949
|
}, workflowFanoutBudget));
|
|
5950
|
+
} finally {
|
|
5951
|
+
workflowProgressClosed = true;
|
|
5952
|
+
if (activityTimer) clearTimeout(activityTimer);
|
|
5953
|
+
childProgress.clear();
|
|
5652
5954
|
}
|
|
5653
5955
|
}
|
|
5654
5956
|
const directParams = requestParams;
|
|
@@ -5719,10 +6021,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5719
6021
|
}
|
|
5720
6022
|
if (!confirmed) return { content: [{ type: "text", text: "Worktree discard canceled; preserved worktrees were not changed." }], details: { mode: "management", results: [] } };
|
|
5721
6023
|
try {
|
|
5722
|
-
const
|
|
5723
|
-
|
|
6024
|
+
const manifestPath = path.isAbsolute(paramsWithResolvedCwd.handoffPath) ? paramsWithResolvedCwd.handoffPath : path.resolve(requestCwd, paramsWithResolvedCwd.handoffPath);
|
|
6025
|
+
const discarded = await withWorktreeTransaction(() => discardPreservedWorktrees(
|
|
6026
|
+
manifestPath,
|
|
5724
6027
|
{ kind: decision === "confirm" ? "confirmed" : "policy", ...(deps.config.authorityPolicy ? { policy: deps.config.authorityPolicy } : {}) },
|
|
5725
|
-
);
|
|
6028
|
+
));
|
|
5726
6029
|
return { content: [{ type: "text", text: discarded.text }], details: { mode: "management", results: [] } };
|
|
5727
6030
|
} catch (error) {
|
|
5728
6031
|
return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "management", results: [] } };
|
|
@@ -5991,7 +6294,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5991
6294
|
}
|
|
5992
6295
|
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots, abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })));
|
|
5993
6296
|
}
|
|
5994
|
-
if (paramsWithResolvedCwd.view === "fleet") {
|
|
6297
|
+
if (paramsWithResolvedCwd.view === "fleet" || paramsWithResolvedCwd.view === "transcript") {
|
|
5995
6298
|
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots, abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })));
|
|
5996
6299
|
}
|
|
5997
6300
|
if (targetRunId) {
|
|
@@ -6000,12 +6303,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6000
6303
|
if (resolved?.kind === "foreground") {
|
|
6001
6304
|
const foreground = getForegroundControl(deps.state, resolved.id);
|
|
6002
6305
|
if (foreground) {
|
|
6003
|
-
if (paramsWithResolvedCwd.view === "transcript") {
|
|
6004
|
-
return withBudget({
|
|
6005
|
-
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." }],
|
|
6006
|
-
details: { mode: "management", results: [] },
|
|
6007
|
-
});
|
|
6008
|
-
}
|
|
6009
6306
|
return withBudget(foregroundStatusResult(foreground));
|
|
6010
6307
|
}
|
|
6011
6308
|
}
|
|
@@ -6015,13 +6312,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6015
6312
|
}
|
|
6016
6313
|
} else if (!hasDirectoryTarget) {
|
|
6017
6314
|
const foreground = getForegroundControl(deps.state, undefined);
|
|
6018
|
-
if (foreground
|
|
6019
|
-
if (foreground && paramsWithResolvedCwd.view === "transcript") {
|
|
6020
|
-
return withBudget({
|
|
6021
|
-
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." }],
|
|
6022
|
-
details: { mode: "management", results: [] },
|
|
6023
|
-
});
|
|
6024
|
-
}
|
|
6315
|
+
if (foreground) return withBudget(foregroundStatusResult(foreground));
|
|
6025
6316
|
}
|
|
6026
6317
|
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots, abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })));
|
|
6027
6318
|
}
|
|
@@ -6042,9 +6333,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6042
6333
|
const runId = location.resolvedId ?? targetRunId ?? path.basename(location.asyncDir ?? paramsWithResolvedCwd.dir);
|
|
6043
6334
|
const directoryStatus = location.asyncDir ? readStatus(location.asyncDir) : null;
|
|
6044
6335
|
if (directoryStatus?.mode === "workflow") {
|
|
6045
|
-
|
|
6046
|
-
if (!route.ok) return { content: [{ type: "text", text: route.message }], isError: true, details: { mode: "management", results: [] } };
|
|
6047
|
-
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 });
|
|
6048
6337
|
}
|
|
6049
6338
|
if (location.asyncDir) {
|
|
6050
6339
|
const unsupported = externalRunnerControlError(location.asyncDir, "steer");
|
|
@@ -6052,6 +6341,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6052
6341
|
}
|
|
6053
6342
|
return steerAsyncRun(compactOptional<Parameters<typeof steerAsyncRun>[0]>({
|
|
6054
6343
|
state: deps.state,
|
|
6344
|
+
findPendingAsks: deps.findPendingAsks,
|
|
6055
6345
|
runId,
|
|
6056
6346
|
message,
|
|
6057
6347
|
mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode),
|
|
@@ -6089,9 +6379,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6089
6379
|
if (resolved?.kind !== "async") return { content: [{ type: "text", text: `No async run found for '${targetRunId}'.` }], isError: true, details: { mode: "management", results: [] } };
|
|
6090
6380
|
const resolvedStatus = resolved.location.asyncDir ? readStatus(resolved.location.asyncDir) : null;
|
|
6091
6381
|
if (resolvedStatus?.mode === "workflow") {
|
|
6092
|
-
|
|
6093
|
-
if (!route.ok) return { content: [{ type: "text", text: route.message }], isError: true, details: { mode: "management", results: [] } };
|
|
6094
|
-
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 });
|
|
6095
6383
|
}
|
|
6096
6384
|
if (resolved.location.asyncDir) {
|
|
6097
6385
|
const unsupported = externalRunnerControlError(resolved.location.asyncDir, "steer");
|
|
@@ -6099,6 +6387,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6099
6387
|
}
|
|
6100
6388
|
return steerAsyncRun(compactOptional<Parameters<typeof steerAsyncRun>[0]>({
|
|
6101
6389
|
state: deps.state,
|
|
6390
|
+
findPendingAsks: deps.findPendingAsks,
|
|
6102
6391
|
runId: resolved.id,
|
|
6103
6392
|
message,
|
|
6104
6393
|
mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode),
|
|
@@ -6300,6 +6589,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6300
6589
|
config: deps.config,
|
|
6301
6590
|
currentSessionId: deps.state.currentSessionId ?? ctx.sessionManager.getSessionId() ?? undefined,
|
|
6302
6591
|
runtimeAgentOwner: deps.pi,
|
|
6592
|
+
onAgentsChanged: deps.onAgentsChanged,
|
|
6303
6593
|
});
|
|
6304
6594
|
}
|
|
6305
6595
|
|
|
@@ -6551,7 +6841,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6551
6841
|
|
|
6552
6842
|
let sessionRoot: string;
|
|
6553
6843
|
if (effectiveParams.sessionDir) {
|
|
6554
|
-
|
|
6844
|
+
// An explicit sessionDir is a root keyed by this launch's run id so
|
|
6845
|
+
// concurrent children resolve distinct per-child session files.
|
|
6846
|
+
sessionRoot = path.join(path.resolve(deps.expandTilde(effectiveParams.sessionDir)), runId);
|
|
6555
6847
|
} else {
|
|
6556
6848
|
const baseSessionRoot = deps.config.defaultSessionDir
|
|
6557
6849
|
? path.resolve(deps.expandTilde(deps.config.defaultSessionDir))
|
|
@@ -6689,6 +6981,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6689
6981
|
timeoutMs: foregroundTimeout.timeoutMs,
|
|
6690
6982
|
toolBudget: runToolBudget.toolBudget,
|
|
6691
6983
|
usageBudget: usageBudget.budget,
|
|
6984
|
+
inheritedUsageBudget,
|
|
6692
6985
|
allowZeroToolBudget,
|
|
6693
6986
|
configToolBudget: configToolBudget.toolBudget,
|
|
6694
6987
|
configToolTimeoutMs: deps.config.toolTimeoutMs,
|
|
@@ -6902,7 +7195,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6902
7195
|
if (activeAsyncCapacity && !activeAsyncCapacity.owner.runnerStartedAt) activeAsyncCapacity.rollback();
|
|
6903
7196
|
if (foregroundControl) {
|
|
6904
7197
|
settleForegroundSchedulingOwner(foregroundControl);
|
|
6905
|
-
removeForegroundControlIfIdle(deps.state, runId);
|
|
7198
|
+
removeForegroundControlIfIdle(deps.state, runId, deps.trackRetainedNestedRoute);
|
|
6906
7199
|
}
|
|
6907
7200
|
}
|
|
6908
7201
|
|
|
@@ -6949,7 +7242,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6949
7242
|
}
|
|
6950
7243
|
let publicParams = normalized.params as SubagentParamsLike;
|
|
6951
7244
|
if (publicParams.workflow !== undefined) {
|
|
6952
|
-
const resolved = resolveWorkflowResource(publicParams.workflow, publicParams.args);
|
|
7245
|
+
const resolved = resolveWorkflowResource(publicParams.workflow, publicParams.args, ctx.sessionManager.getSessionId() ?? undefined);
|
|
6953
7246
|
if (!resolved.ok) return Promise.resolve({ content: [{ type: "text", text: resolved.error }], isError: true, details: { mode: "workflow", results: [] } });
|
|
6954
7247
|
const { workflow: _workflow, args: _args, ...withoutResourceInput } = publicParams;
|
|
6955
7248
|
publicParams = { ...withoutResourceInput, workflowScript: resolved.resource.script };
|
|
@@ -6996,16 +7289,27 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6996
7289
|
ctx: ExtensionContext,
|
|
6997
7290
|
) => {
|
|
6998
7291
|
const ownerSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
6999
|
-
|
|
7000
|
-
|
|
7001
|
-
|
|
7002
|
-
|
|
7003
|
-
|
|
7004
|
-
});
|
|
7005
|
-
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);
|
|
7006
7297
|
}
|
|
7007
|
-
|
|
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);
|
|
7008
7305
|
};
|
|
7009
7306
|
|
|
7010
|
-
|
|
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 };
|
|
7011
7315
|
}
|