pi-subagents 0.55.0 → 0.57.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 +81 -3
- package/agents/claude-code-writer.md +15 -0
- package/agents/claude-code.md +15 -0
- package/agents/codex-exec-writer.md +15 -0
- package/agents/codex-exec.md +15 -0
- package/agents/cursor-agent-writer.md +14 -0
- package/agents/cursor-agent.md +14 -0
- package/docs/agents.md +111 -15
- package/docs/configuration.md +37 -0
- package/docs/extension-api.md +2 -2
- package/docs/models.md +6 -0
- package/docs/observability.md +8 -7
- package/docs/tool-reference.md +11 -1
- package/docs/workflows.md +20 -0
- package/package.json +1 -1
- package/skills/pi-subagents/references/execution-controls.md +1 -1
- package/src/agents/agent-management.ts +13 -5
- package/src/agents/agent-refinements.ts +4 -4
- package/src/agents/agent-serializer.ts +2 -0
- package/src/agents/agents.ts +242 -49
- package/src/agents/builtin-names.ts +6 -0
- package/src/agents/runtime-agent-registry.ts +8 -2
- package/src/api/preflight.ts +22 -3
- package/src/extension/config.ts +70 -0
- package/src/extension/doctor.ts +3 -3
- package/src/extension/index.ts +18 -4
- package/src/extension/public-execution.ts +29 -13
- package/src/extension/rpc.ts +20 -5
- package/src/extension/schemas.ts +9 -3
- package/src/extension/tool-description.ts +11 -9
- package/src/inspectors/herdr/actions.ts +2 -1
- package/src/inspectors/herdr/inspector-runner.ts +2 -10
- package/src/inspectors/herdr/session-roots-codec.ts +42 -0
- package/src/integrations/herdr-status.ts +46 -3
- package/src/runs/background/active-async-capacity.ts +77 -10
- package/src/runs/background/async-execution.ts +148 -27
- package/src/runs/background/async-job-tracker.ts +5 -0
- package/src/runs/background/async-resume.ts +3 -0
- package/src/runs/background/async-retention.ts +20 -3
- package/src/runs/background/async-status.ts +5 -0
- package/src/runs/background/chain-root-attachment.ts +15 -1
- package/src/runs/background/fleet-view.ts +16 -10
- package/src/runs/background/inspect-rpc.ts +8 -8
- package/src/runs/background/result-files.ts +27 -14
- package/src/runs/background/result-watcher.ts +9 -5
- package/src/runs/background/run-status.ts +33 -6
- package/src/runs/background/scheduled-runs.ts +7 -1
- package/src/runs/background/subagent-runner.ts +254 -49
- package/src/runs/background/wait-completions.ts +4 -0
- package/src/runs/foreground/execution.ts +107 -16
- package/src/runs/foreground/foreground-control.ts +6 -0
- package/src/runs/foreground/foreground-history.ts +25 -3
- package/src/runs/foreground/subagent-executor.ts +324 -84
- package/src/runs/foreground/workflow-detach-reconcile.ts +31 -7
- package/src/runs/shared/acceptance.ts +10 -5
- package/src/runs/shared/agent-contract.ts +1 -1
- package/src/runs/shared/child-protocol.ts +21 -7
- package/src/runs/shared/claude-code-adapter.ts +129 -0
- package/src/runs/shared/codex-exec-adapter.ts +129 -0
- package/src/runs/shared/completion-guard.ts +3 -2
- package/src/runs/shared/cursor-agent-adapter.ts +114 -0
- package/src/runs/shared/dynamic-fanout.ts +1 -1
- package/src/runs/shared/extension-bindings.ts +78 -0
- package/src/runs/shared/external-cli-contract.ts +167 -0
- package/src/runs/shared/external-cli-preflight.ts +122 -0
- package/src/runs/shared/external-cli-runner.ts +349 -54
- package/src/runs/shared/fast-mode-extension.ts +10 -0
- package/src/runs/shared/model-exclusions.ts +59 -8
- package/src/runs/shared/model-fallback.ts +25 -8
- package/src/runs/shared/mutation-evidence.ts +150 -0
- package/src/runs/shared/nested-events.ts +3 -1
- package/src/runs/shared/nested-render.ts +2 -2
- package/src/runs/shared/parallel-utils.ts +3 -0
- package/src/runs/shared/pi-args.ts +47 -0
- package/src/runs/shared/process-signal.ts +13 -0
- package/src/runs/shared/run-history.ts +21 -1
- package/src/runs/shared/structured-output.ts +18 -4
- package/src/runs/shared/subagent-prompt-runtime.ts +9 -5
- package/src/shared/fork-context.ts +21 -0
- package/src/shared/formatters.ts +7 -1
- package/src/shared/launch-contract.ts +6 -0
- package/src/shared/pruned-fork.ts +450 -0
- package/src/shared/session-file-trust.ts +19 -0
- package/src/shared/session-tokens.ts +14 -3
- package/src/shared/settings.ts +16 -3
- package/src/shared/shortcuts.ts +17 -0
- package/src/shared/types.ts +166 -9
- package/src/shared/workflow-child-permit.ts +116 -0
- package/src/slash/delegation-adapters.ts +0 -1
- package/src/slash/slash-commands.ts +8 -6
- package/src/tui/fleet-status.ts +27 -10
- package/src/tui/fleet-transcript.ts +11 -5
- package/src/tui/fleet.ts +28 -13
- package/src/tui/render.ts +47 -21
- package/src/workflows/scripted-workflow.ts +298 -30
- package/src/workflows/workflow-child-summary.ts +117 -0
- package/src/workflows/workflow-receipt.ts +119 -4
|
@@ -3,7 +3,7 @@ import * as fs from "node:fs";
|
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
5
5
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
6
|
-
import { findBlockingAgentDiagnostic, resolveAgentName, type AgentConfig, type AgentDiscoveryDiagnostic, type AgentScope } from "../../agents/agents.ts";
|
|
6
|
+
import { discoverAgents, findBlockingAgentDiagnostic, formatUnknownAgentError, resolveAgentName, unknownAgentDiagnosticContext, type AgentConfig, type AgentDiscoveryDiagnostic, type AgentScope, type UnknownAgentDiagnosticContext } from "../../agents/agents.ts";
|
|
7
7
|
import { getArtifactsDir, getChainRunsDir, getProjectArtifactPackagingWarning, getProjectSubagentsDir } from "../../shared/artifacts.ts";
|
|
8
8
|
import { writeAtomicJson } from "../../shared/atomic-json.ts";
|
|
9
9
|
import { createCapacityResilientJsonWriter } from "../../shared/capacity-resilient-json.ts";
|
|
@@ -56,12 +56,13 @@ import { discoverAvailableSkills, normalizeSkillInput } from "../../agents/skill
|
|
|
56
56
|
import { buildAsyncRunnerSteps, DEFAULT_ASYNC_TIMEOUT_MS, executeAsyncChain, executeAsyncSingle, formatAsyncStartedMessage, isAsyncAvailable, workflowAwaitedAsyncResultPath } from "../background/async-execution.ts";
|
|
57
57
|
import { updateActiveRunIndex } from "../background/active-run-index.ts";
|
|
58
58
|
import { steeringReceipt } from "../background/steering.ts";
|
|
59
|
-
import { acquireActiveAsyncCapacity, ActiveAsyncCapacityError, getActiveAsyncCapacitySnapshot, resolveMaxActiveAsyncRunsPerSession, transferActiveAsyncCapacity, type ActiveAsyncCapacityHandle } from "../background/active-async-capacity.ts";
|
|
59
|
+
import { acquireActiveAsyncCapacity, ActiveAsyncCapacityError, getActiveAsyncCapacitySnapshot, resolveAbandonedSlotReleaseAfterMs, resolveMaxActiveAsyncRunsPerSession, transferActiveAsyncCapacity, type ActiveAsyncCapacityHandle } from "../background/active-async-capacity.ts";
|
|
60
60
|
import { isScheduledRunAction, type ScheduledRunAction } from "../background/scheduled-runs.ts";
|
|
61
61
|
import { enqueueChainAppendRequest, readPendingChainAppendRequests, runnerStepOutputNames } from "../background/chain-append.ts";
|
|
62
62
|
import { ChainOutputValidationError, validateChainOutputBindingsWithContext } from "../shared/chain-outputs.ts";
|
|
63
63
|
import { normalizeGateAcceptance, validateExecutionAcceptance } from "../shared/acceptance.ts";
|
|
64
64
|
import { canPreferFork, createForkContextResolver, forkedChildRequiresThinkingOff, resolveSubagentLaunchContext } from "../../shared/fork-context.ts";
|
|
65
|
+
import { createPrunedForkSessionWriter } from "../../shared/pruned-fork.ts";
|
|
65
66
|
import { resolveCurrentSessionId } from "../../shared/session-identity.ts";
|
|
66
67
|
import { currentCompletionOwnerId } from "../../shared/completion-owner.ts";
|
|
67
68
|
import { applyIntercomBridgeToAgent, INTERCOM_BRIDGE_MARKER, resolveIntercomBridge, resolveIntercomSessionTarget, resolveSubagentIntercomTarget, type IntercomBridgeState } from "../../intercom/intercom-bridge.ts";
|
|
@@ -73,8 +74,9 @@ import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
|
|
|
73
74
|
import { usageBudgetExceededMessage, usageBudgetState, validateUsageBudgetConfig } from "../shared/usage-budget.ts";
|
|
74
75
|
import { intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
|
|
75
76
|
import { isAgentContractV1 } from "../shared/agent-contract.ts";
|
|
77
|
+
import { normalizeExtensionBindings, type ExtensionBindings } from "../shared/extension-bindings.ts";
|
|
76
78
|
import { finalizeSingleOutput, injectSingleOutputInstruction, normalizeSingleOutputOverride, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
|
|
77
|
-
import { cleanupStructuredOutputRuntime, createStructuredOutputRuntime } from "../shared/structured-output.ts";
|
|
79
|
+
import { assertJsonSchemaObject, cleanupStructuredOutputRuntime, createStructuredOutputRuntime } from "../shared/structured-output.ts";
|
|
78
80
|
import { compactForegroundDetails, getSingleResultOutput, mapConcurrent, readStatus, resolveChildCwd, sumResultsCost, sumResultsUsage } from "../../shared/utils.ts";
|
|
79
81
|
import { createTaskMutationArbiter } from "../shared/llm-intent-arbiter.ts";
|
|
80
82
|
import { DEFAULT_GLOBAL_CONCURRENCY_LIMIT, Semaphore } from "../shared/parallel-utils.ts";
|
|
@@ -88,7 +90,7 @@ import {
|
|
|
88
90
|
resolveSubagentResultStatus,
|
|
89
91
|
stripDetailsOutputsForIntercomReceipt,
|
|
90
92
|
} from "../../intercom/result-intercom.ts";
|
|
91
|
-
import { applySteeringRecoveryAgentConfig, asyncReviveRequiresRecoveryDescriptor, buildRevivedAsyncTask, resolveAsyncResumeTarget, resolveAsyncRunLocation } from "../background/async-resume.ts";
|
|
93
|
+
import { applySteeringRecoveryAgentConfig, asyncReviveRequiresRecoveryDescriptor, buildRevivedAsyncTask, readAsyncRecoveryDescriptor, resolveAsyncResumeTarget, resolveAsyncRunLocation } from "../background/async-resume.ts";
|
|
92
94
|
import { deliverInterruptRequest, readRevivalBriefs, requestAsyncSteer, type SteerDeliveryMode } from "../background/control-channel.ts";
|
|
93
95
|
import { updateSteeringTarget, waitForSteeringAction } from "../background/steering.ts";
|
|
94
96
|
import { canQueueRetainedAsyncFollowUp, steerAsyncRun } from "./async-steering-action.ts";
|
|
@@ -112,6 +114,7 @@ import { isStoppableAsyncStatusStep, resolveAsyncStatusChild } from "../shared/c
|
|
|
112
114
|
import { inspectSubagentStatus } from "../background/run-status.ts";
|
|
113
115
|
import { getExternalJobProvider } from "../../api/external-job-provider.ts";
|
|
114
116
|
import { externalJobFollowUpRequestDigest, externalJobFollowUpRequestId, externalJobFollowUpRunId, externalJobPromptDigest, externalJobStableJson } from "../shared/external-job-runner.ts";
|
|
117
|
+
import { externalCliReceiptMetadata, normalizeExternalCliRunnerStatus } from "../shared/external-cli-contract.ts";
|
|
115
118
|
import { applyForceTopLevelAsyncOverride } from "../background/top-level-async.ts";
|
|
116
119
|
import { handleMissionAction, MISSION_ACTIONS } from "../../missions/actions.ts";
|
|
117
120
|
import { attachMissionToLaunchResult, prepareMissionLaunch, writeMissionAsyncBinding, type MissionLaunchBinding } from "../../missions/lifecycle.ts";
|
|
@@ -121,9 +124,11 @@ import { createMissionWorkflowState } from "../../missions/workflow-state.ts";
|
|
|
121
124
|
import { resolveAuthorityDecision } from "../../policy/authority.ts";
|
|
122
125
|
import { handleHerdrInspectorAction, HERDR_INSPECTOR_ACTIONS } from "../../inspectors/herdr/actions.ts";
|
|
123
126
|
import { handleHerdrProjectPaneAction, HERDR_PROJECT_PANE_ACTIONS } from "../../inspectors/herdr/project-panes.ts";
|
|
124
|
-
import { previewSimpleWorkflowRun, runWorkflowScript, WorkflowScriptError, type WorkflowReceiptResumeReference, type WorkflowScriptChildResult, type WorkflowSteerOptions, type WorkflowSteerResult } from "../../workflows/scripted-workflow.ts";
|
|
127
|
+
import { previewSimpleWorkflowRun, runWorkflowScript, validateWorkflowScript, WorkflowScriptError, type WorkflowReceiptResumeReference, type WorkflowScriptChildResult, type WorkflowSteerOptions, type WorkflowSteerResult } from "../../workflows/scripted-workflow.ts";
|
|
125
128
|
import { buildWorkflowReceipt, resolveWorkflowReceiptResumeEntry, writeWorkflowReceipt, type WorkflowReceipt, type WorkflowReceiptState } from "../../workflows/workflow-receipt.ts";
|
|
129
|
+
import { workflowChildSummary } from "../../workflows/workflow-child-summary.ts";
|
|
126
130
|
import { resolveWorkflowChatProgress, type WorkflowChatProgressProjection } from "../../workflows/chat-progress.ts";
|
|
131
|
+
import { claimWorkflowChildPermit, validateWorkflowChildPermitRoot, type WorkflowChildPermit, type WorkflowChildPermitContext } from "../../shared/workflow-child-permit.ts";
|
|
127
132
|
import {
|
|
128
133
|
cleanupWorktrees,
|
|
129
134
|
createWorktrees,
|
|
@@ -145,6 +150,7 @@ import {
|
|
|
145
150
|
type ControlEvent,
|
|
146
151
|
type Details,
|
|
147
152
|
type ExtensionConfig,
|
|
153
|
+
type ForegroundResumeChild,
|
|
148
154
|
type ForegroundRunControl,
|
|
149
155
|
type IntercomBridgeConfig,
|
|
150
156
|
type IntercomEventBus,
|
|
@@ -152,10 +158,12 @@ import {
|
|
|
152
158
|
type MaxOutputConfig,
|
|
153
159
|
type NestedRouteInfo,
|
|
154
160
|
type NestedRunSummary,
|
|
161
|
+
type OutputMode,
|
|
155
162
|
type ResolvedControlConfig,
|
|
156
163
|
type ResolvedTurnBudget,
|
|
157
164
|
type ResolvedToolBudget,
|
|
158
165
|
type RunFanoutBudgetDescriptor,
|
|
166
|
+
type SteeringRecoveryDescriptor,
|
|
159
167
|
type SingleResult,
|
|
160
168
|
type SubagentChildStatusEvent,
|
|
161
169
|
type ToolBudgetConfig,
|
|
@@ -264,6 +272,7 @@ interface TaskParam {
|
|
|
264
272
|
reads?: string[] | boolean;
|
|
265
273
|
progress?: boolean;
|
|
266
274
|
model?: string;
|
|
275
|
+
fast?: boolean;
|
|
267
276
|
skill?: string | string[] | boolean;
|
|
268
277
|
outputSchema?: JsonSchemaObject;
|
|
269
278
|
acceptance?: AcceptanceInput;
|
|
@@ -288,12 +297,14 @@ export interface SubagentParamsLike {
|
|
|
288
297
|
type?: string;
|
|
289
298
|
agent?: string;
|
|
290
299
|
task?: string;
|
|
300
|
+
extensionBindings?: ExtensionBindings;
|
|
291
301
|
/** Retained async child run id. Valid only on workflow runs.run items. */
|
|
292
302
|
resume?: string;
|
|
293
303
|
message?: string;
|
|
294
304
|
steeringRecovery?: boolean;
|
|
295
305
|
mode?: SteerDeliveryMode;
|
|
296
306
|
workflowScript?: string;
|
|
307
|
+
workflowScriptPath?: string;
|
|
297
308
|
chatProgress?: "auto" | "off" | "live-card";
|
|
298
309
|
isolation?: "none" | "worktree";
|
|
299
310
|
step?: ChainStep;
|
|
@@ -340,6 +351,7 @@ export interface SubagentParamsLike {
|
|
|
340
351
|
artifacts?: boolean;
|
|
341
352
|
includeProgress?: boolean;
|
|
342
353
|
model?: string;
|
|
354
|
+
fast?: boolean;
|
|
343
355
|
thinking?: string | false;
|
|
344
356
|
scope?: string;
|
|
345
357
|
target?: string;
|
|
@@ -392,7 +404,7 @@ interface ExecutorDeps {
|
|
|
392
404
|
tempArtifactsDir: string;
|
|
393
405
|
getSubagentSessionRoot: (parentSessionFile: string | null) => string;
|
|
394
406
|
expandTilde: (p: string) => string;
|
|
395
|
-
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[]; agentDiagnostics?: AgentDiscoveryDiagnostic[]; modelScope?: ModelScopeConfig; maxThinking?: AgentConfig["maxThinking"] };
|
|
407
|
+
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[]; agentDiagnostics?: AgentDiscoveryDiagnostic[]; modelScope?: ModelScopeConfig; maxThinking?: AgentConfig["maxThinking"]; cwd?: string; scope?: AgentScope; directories?: UnknownAgentDiagnosticContext["directories"] };
|
|
396
408
|
allowMutatingManagementActions?: boolean;
|
|
397
409
|
activateSupervisorTransport?: () => void;
|
|
398
410
|
refreshResultDelivery?: () => void;
|
|
@@ -400,6 +412,7 @@ interface ExecutorDeps {
|
|
|
400
412
|
}
|
|
401
413
|
|
|
402
414
|
type ForkSessionFileForTask = (agentName: string, idx?: number, modelOverride?: string, modelOverrideFromParent?: boolean) => string | undefined;
|
|
415
|
+
type PrepareForkSessionForTask = (agentName: string, idx?: number, modelOverride?: string, modelOverrideFromParent?: boolean) => Promise<void>;
|
|
403
416
|
type ForkThinkingOverrideForTask = (agentName: string, idx?: number, modelOverride?: string, modelOverrideFromParent?: boolean) => AgentConfig["thinking"] | undefined;
|
|
404
417
|
|
|
405
418
|
interface ExecutionContextData {
|
|
@@ -409,6 +422,8 @@ interface ExecutionContextData {
|
|
|
409
422
|
signal: AbortSignal;
|
|
410
423
|
onUpdate?: (r: AgentToolResult<Details>) => void;
|
|
411
424
|
agents: AgentConfig[];
|
|
425
|
+
/** Exact filesystem discovery provenance retained through defensive execution backstops. */
|
|
426
|
+
unknownAgentDiagnosticContext: UnknownAgentDiagnosticContext;
|
|
412
427
|
/** Discovered agent definitions before per-run bridge injection. */
|
|
413
428
|
recoveryAgents: AgentConfig[];
|
|
414
429
|
runId: string;
|
|
@@ -444,12 +459,27 @@ interface ExecutionContextData {
|
|
|
444
459
|
runFanoutBudget: RunFanoutBudgetDescriptor;
|
|
445
460
|
topLevelAsyncCapacityEligible: boolean;
|
|
446
461
|
activeAsyncCapacity?: ActiveAsyncCapacityHandle;
|
|
462
|
+
workflowChildPermitLaunch?: WorkflowChildPermitContext;
|
|
447
463
|
}
|
|
448
464
|
|
|
449
465
|
function resolveRequestedCwd(runtimeCwd: string, requestedCwd: string | undefined): string {
|
|
450
466
|
return requestedCwd ? path.resolve(runtimeCwd, requestedCwd) : runtimeCwd;
|
|
451
467
|
}
|
|
452
468
|
|
|
469
|
+
function loadWorkflowScriptPath(params: SubagentParamsLike, runtimeCwd: string): { params?: SubagentParamsLike; error?: string } {
|
|
470
|
+
if (params.workflowScriptPath === undefined) return { params };
|
|
471
|
+
const scriptPath = path.resolve(resolveRequestedCwd(runtimeCwd, params.cwd), params.workflowScriptPath);
|
|
472
|
+
let workflowScript: string;
|
|
473
|
+
try {
|
|
474
|
+
workflowScript = fs.readFileSync(scriptPath, "utf8");
|
|
475
|
+
} catch (error) {
|
|
476
|
+
return { error: `Failed to read workflowScriptPath '${scriptPath}': ${error instanceof Error ? error.message : String(error)}` };
|
|
477
|
+
}
|
|
478
|
+
if (!workflowScript.trim()) return { error: `workflowScriptPath file '${scriptPath}' is empty.` };
|
|
479
|
+
const { workflowScriptPath: _workflowScriptPath, ...rest } = params;
|
|
480
|
+
return { params: { ...rest, workflowScript } };
|
|
481
|
+
}
|
|
482
|
+
|
|
453
483
|
function removeForegroundControlIfIdle(state: SubagentState, runId: string): boolean {
|
|
454
484
|
const control = state.foregroundControls.get(runId);
|
|
455
485
|
if (control && (!foregroundSchedulingSettled(control) || (control.activeChildren?.size ?? 0) > 0)) return false;
|
|
@@ -538,6 +568,7 @@ function withSpawnBudgetStatus(
|
|
|
538
568
|
const activeAsyncCapacity = sessionId
|
|
539
569
|
? getActiveAsyncCapacitySnapshot(sessionId, resolveMaxActiveAsyncRunsPerSession(config.maxActiveAsyncRunsPerSession), {
|
|
540
570
|
liveWorkflowRunIds: new Set(state.workflowControllers?.keys() ?? []),
|
|
571
|
+
abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(config.capacity?.abandonedSlotReleaseAfterMs),
|
|
541
572
|
})
|
|
542
573
|
: { used: 0, limit: resolveMaxActiveAsyncRunsPerSession(config.maxActiveAsyncRunsPerSession) ?? 0 };
|
|
543
574
|
state.activeAsyncCapacity = activeAsyncCapacity;
|
|
@@ -640,11 +671,13 @@ function foregroundChildActivityFromProgress(progress: SingleResult["progress"]
|
|
|
640
671
|
...(progress?.currentPath ? { currentPath: progress.currentPath } : {}),
|
|
641
672
|
...(progress?.turnCount !== undefined ? { turnCount: progress.turnCount } : {}),
|
|
642
673
|
...(progress?.tokens !== undefined ? { tokens: progress.tokens } : {}),
|
|
674
|
+
...(progress?.window !== undefined ? { window: progress.window } : {}),
|
|
675
|
+
...(progress?.windowPeak !== undefined ? { windowPeak: progress.windowPeak } : {}),
|
|
643
676
|
...(progress?.toolCount !== undefined ? { toolCount: progress.toolCount } : {}),
|
|
644
677
|
};
|
|
645
678
|
}
|
|
646
679
|
|
|
647
|
-
function rememberForegroundRun(state: SubagentState, input: { runId: string; mode: "single" | "parallel" | "chain"; cwd: string; sessionId: string | null; results: SingleResult[] }): void {
|
|
680
|
+
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 {
|
|
648
681
|
state.foregroundRuns ??= new Map();
|
|
649
682
|
const previous = state.foregroundRuns.get(input.runId);
|
|
650
683
|
const updatedAt = Date.now();
|
|
@@ -655,6 +688,13 @@ function rememberForegroundRun(state: SubagentState, input: { runId: string; mod
|
|
|
655
688
|
...(input.sessionId ? { sessionId: input.sessionId } : {}),
|
|
656
689
|
updatedAt,
|
|
657
690
|
children: input.results.map((result, index) => {
|
|
691
|
+
const resumeContract = omitUndefinedProperties({
|
|
692
|
+
outputSchema: input.params.outputSchema,
|
|
693
|
+
agentContract: input.params.agentContract,
|
|
694
|
+
acceptance: input.params.acceptance,
|
|
695
|
+
output: input.effectiveOutput,
|
|
696
|
+
outputMode: input.effectiveOutputMode,
|
|
697
|
+
});
|
|
658
698
|
const child = {
|
|
659
699
|
agent: result.agent,
|
|
660
700
|
index,
|
|
@@ -685,7 +725,9 @@ function rememberForegroundRun(state: SubagentState, input: { runId: string; mod
|
|
|
685
725
|
...(result.transcriptError ? { transcriptError: result.transcriptError } : {}),
|
|
686
726
|
...(result.detachedReason ? { detachedReason: result.detachedReason } : {}),
|
|
687
727
|
...(result.acceptance ? { acceptance: result.acceptance } : {}),
|
|
728
|
+
...(Object.keys(resumeContract).length ? { resumeContract } : {}),
|
|
688
729
|
...(result.launchContractDigest ? { launchContractDigest: result.launchContractDigest } : {}),
|
|
730
|
+
...(input.extensionBindings ? { extensionBindings: input.extensionBindings } : {}),
|
|
689
731
|
...(result.launchResolvedExtensions ? { launchResolvedExtensions: result.launchResolvedExtensions } : {}),
|
|
690
732
|
...(result.runtimeAcknowledgedExtensions ? { runtimeAcknowledgedExtensions: result.runtimeAcknowledgedExtensions } : {}),
|
|
691
733
|
...(result.capabilityCeiling ? { capabilityCeiling: result.capabilityCeiling } : {}),
|
|
@@ -804,7 +846,7 @@ function updateRememberedForegroundChild(state: SubagentState, input: { runId: s
|
|
|
804
846
|
});
|
|
805
847
|
}
|
|
806
848
|
|
|
807
|
-
function resolveForegroundResumeTarget(params: SubagentParamsLike, state: SubagentState, options: { exactOnly?: boolean } = {}): { runId: string; mode: SubagentRunMode; state: "complete"; agent: string; index: number; cwd: string; sessionFile: string; model?: string; thinking?: string; launchContractDigest?: string; capabilityCeiling?: ResolvedSubagentCapabilityCeiling } | undefined {
|
|
849
|
+
function resolveForegroundResumeTarget(params: SubagentParamsLike, state: SubagentState, options: { exactOnly?: boolean } = {}): { runId: string; mode: SubagentRunMode; state: "complete"; agent: string; index: number; cwd: string; sessionFile: string; model?: string; thinking?: string; launchContractDigest?: string; resumeContract?: ForegroundResumeChild["resumeContract"]; extensionBindings?: ExtensionBindings; capabilityCeiling?: ResolvedSubagentCapabilityCeiling } | undefined {
|
|
808
850
|
const requested = (params.id ?? params.runId)?.trim();
|
|
809
851
|
if (!requested || !state.foregroundRuns?.size || !state.currentSessionId) return undefined;
|
|
810
852
|
const direct = state.foregroundRuns.get(requested);
|
|
@@ -835,6 +877,8 @@ function resolveForegroundResumeTarget(params: SubagentParamsLike, state: Subage
|
|
|
835
877
|
...(child.model ? { model: child.model } : {}),
|
|
836
878
|
...(child.thinking ? { thinking: child.thinking } : {}),
|
|
837
879
|
...(child.launchContractDigest ? { launchContractDigest: child.launchContractDigest } : {}),
|
|
880
|
+
...(child.resumeContract ? { resumeContract: child.resumeContract } : {}),
|
|
881
|
+
...(child.extensionBindings ? { extensionBindings: normalizeExtensionBindings(child.extensionBindings)!.value } : {}),
|
|
838
882
|
...(child.capabilityCeiling ? { capabilityCeiling: child.capabilityCeiling } : {}),
|
|
839
883
|
};
|
|
840
884
|
}
|
|
@@ -854,6 +898,7 @@ type NestedResumeSourceTarget = {
|
|
|
854
898
|
thinking?: AgentConfig["thinking"];
|
|
855
899
|
launchContractDigest?: string;
|
|
856
900
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
901
|
+
recoveryDescriptor?: SteeringRecoveryDescriptor;
|
|
857
902
|
};
|
|
858
903
|
type ResumeSourceTarget = AsyncResumeSourceTarget | ForegroundResumeSourceTarget | NestedResumeSourceTarget;
|
|
859
904
|
|
|
@@ -1231,6 +1276,7 @@ function appendStepToAsyncChain(input: {
|
|
|
1231
1276
|
agents,
|
|
1232
1277
|
ctx: asyncCtx,
|
|
1233
1278
|
availableModels: input.ctx.modelRegistry.getAvailable().map(toModelInfo),
|
|
1279
|
+
unknownAgentDiagnosticContext: diagnosticContextFromDiscovery(discoveredForAppend, input.requestCwd, scope),
|
|
1234
1280
|
cwd: status.cwd ?? input.requestCwd,
|
|
1235
1281
|
chainSkills,
|
|
1236
1282
|
dynamicFanoutMaxItems: input.deps.config.chain?.dynamicFanout?.maxItems,
|
|
@@ -1339,6 +1385,14 @@ function validateNestedSessionFile(run: NestedRunSummary, trustedSessionRoots: s
|
|
|
1339
1385
|
return realSessionFile;
|
|
1340
1386
|
}
|
|
1341
1387
|
|
|
1388
|
+
export function readNestedRecoveryDescriptor(asyncDir: string | undefined, runId: string, agent: string): SteeringRecoveryDescriptor | undefined {
|
|
1389
|
+
const recoveryDescriptor = readAsyncRecoveryDescriptor(asyncDir);
|
|
1390
|
+
if (!recoveryDescriptor) return undefined;
|
|
1391
|
+
if (recoveryDescriptor.sourceRunId !== runId) throw new Error(`Nested run '${runId}' has a recovery descriptor for a different source run.`);
|
|
1392
|
+
if (recoveryDescriptor.agent !== agent) throw new Error(`Nested run '${runId}' has a recovery descriptor for a different agent.`);
|
|
1393
|
+
return recoveryDescriptor;
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1342
1396
|
function resolveNestedResumeTarget(match: ResolvedSubagentRunId & { kind: "nested" }, trustedSessionRoots: string[]): NestedResumeSourceTarget {
|
|
1343
1397
|
const run = match.match.run;
|
|
1344
1398
|
if (run.state === "running" || run.state === "queued") throw new Error(`Nested run '${run.id}' is live; route the follow-up to the owner process instead.`);
|
|
@@ -1347,6 +1401,7 @@ function resolveNestedResumeTarget(match: ResolvedSubagentRunId & { kind: "neste
|
|
|
1347
1401
|
if (!agent) throw new Error(`Could not determine child agent for nested run '${run.id}'.`);
|
|
1348
1402
|
const state = run.state === "complete" || run.state === "failed" || run.state === "paused" ? run.state : "failed";
|
|
1349
1403
|
const asyncDir = resolveNestedAsyncDir(match.match.rootRunId, run);
|
|
1404
|
+
const recoveryDescriptor = readNestedRecoveryDescriptor(asyncDir, run.id, agent);
|
|
1350
1405
|
return compactOptional<NestedResumeSourceTarget>({
|
|
1351
1406
|
kind: "revive",
|
|
1352
1407
|
source: "nested",
|
|
@@ -1357,6 +1412,7 @@ function resolveNestedResumeTarget(match: ResolvedSubagentRunId & { kind: "neste
|
|
|
1357
1412
|
cwd: asyncDir ? path.dirname(asyncDir) : undefined,
|
|
1358
1413
|
sessionFile: validateNestedSessionFile(run, trustedSessionRoots),
|
|
1359
1414
|
...(run.capabilityCeiling ? { capabilityCeiling: run.capabilityCeiling } : {}),
|
|
1415
|
+
...(recoveryDescriptor ? { recoveryDescriptor } : {}),
|
|
1360
1416
|
});
|
|
1361
1417
|
}
|
|
1362
1418
|
|
|
@@ -1496,9 +1552,14 @@ function externalJobFollowUpStarted(input: { sourceRunId: string; runId: string;
|
|
|
1496
1552
|
function externalRunnerControlError(asyncDir: string, action: "steer" | "resume"): AgentToolResult<Details> | undefined {
|
|
1497
1553
|
const status = readStatus(asyncDir);
|
|
1498
1554
|
if (!status?.steps?.length || !status.steps.every((step) => step.runner?.type === "external-cli" || step.runner?.type === "external-job")) return undefined;
|
|
1499
|
-
const
|
|
1500
|
-
|
|
1501
|
-
|
|
1555
|
+
const externalCli = normalizeExternalCliRunnerStatus(status.steps.find((step) => step.runner?.type === "external-cli")?.runner);
|
|
1556
|
+
const message = externalCli
|
|
1557
|
+
? action === "steer"
|
|
1558
|
+
? `External adapter '${externalCli.adapter.id}' does not support runs.steer: ${externalCli.unsupportedReasons.steer}`
|
|
1559
|
+
: `External adapter '${externalCli.adapter.id}' cannot resume: ${externalCli.nonResumableReason}`
|
|
1560
|
+
: action === "steer"
|
|
1561
|
+
? "External runners do not accept live steer messages."
|
|
1562
|
+
: "External runners do not persist Pi sessions and cannot be resumed.";
|
|
1502
1563
|
return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
|
|
1503
1564
|
}
|
|
1504
1565
|
|
|
@@ -1554,7 +1615,7 @@ async function resumeExternalJobFollowUp(input: {
|
|
|
1554
1615
|
runId,
|
|
1555
1616
|
kind: "runner",
|
|
1556
1617
|
asyncDir,
|
|
1557
|
-
}, { liveWorkflowRunIds: new Set(input.deps.state.workflowControllers?.keys() ?? []) }) : undefined;
|
|
1618
|
+
}, { liveWorkflowRunIds: new Set(input.deps.state.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(input.deps.config.capacity?.abandonedSlotReleaseAfterMs) }) : undefined;
|
|
1558
1619
|
} catch (error) {
|
|
1559
1620
|
if (error instanceof ActiveAsyncCapacityError) return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: "single", results: [], activeAsyncCapacity: error.snapshot } };
|
|
1560
1621
|
return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "single", results: [] } };
|
|
@@ -1711,6 +1772,7 @@ async function resumeAsyncRun(input: {
|
|
|
1711
1772
|
const scope: AgentScope = resolveExecutionAgentScope(input.params.agentScope);
|
|
1712
1773
|
const discovered = input.deps.discoverAgents(effectiveCwd, scope);
|
|
1713
1774
|
const discoveredAgents = discovered.agents;
|
|
1775
|
+
const unknownAgentDiagnosticContext = diagnosticContextFromDiscovery(discovered, effectiveCwd, scope);
|
|
1714
1776
|
const modelScope = discovered.modelScope;
|
|
1715
1777
|
const sessionName = resolveIntercomSessionTarget(input.deps.pi.getSessionName(), input.ctx.sessionManager.getSessionId());
|
|
1716
1778
|
const recoveryDescriptor = "recoveryDescriptor" in target ? target.recoveryDescriptor : undefined;
|
|
@@ -1738,13 +1800,17 @@ async function resumeAsyncRun(input: {
|
|
|
1738
1800
|
} : undefined);
|
|
1739
1801
|
if (!baseAgentConfig) {
|
|
1740
1802
|
return {
|
|
1741
|
-
content: [{ type: "text", text:
|
|
1803
|
+
content: [{ type: "text", text: formatUnknownAgentError(target.agent, unknownAgentDiagnosticContext, "Unknown agent for resume") }],
|
|
1742
1804
|
isError: true,
|
|
1743
1805
|
details: { mode: "management", results: [] },
|
|
1744
1806
|
};
|
|
1745
1807
|
}
|
|
1746
1808
|
if (target.source === "async" && target.runner?.type === "external-cli") {
|
|
1747
|
-
|
|
1809
|
+
const runner = normalizeExternalCliRunnerStatus(target.runner);
|
|
1810
|
+
const message = runner
|
|
1811
|
+
? `External adapter '${runner.adapter.id}' cannot resume: ${runner.nonResumableReason}`
|
|
1812
|
+
: "External runners do not persist Pi sessions and cannot be resumed.";
|
|
1813
|
+
return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
|
|
1748
1814
|
}
|
|
1749
1815
|
if (target.source === "async" && target.runner?.type === "external-job") {
|
|
1750
1816
|
if (attachChain) return { content: [{ type: "text", text: "External-job follow-up does not support chain attachment. Use action='resume' with message instead." }], isError: true, details: { mode: "management", results: [] } };
|
|
@@ -1789,7 +1855,7 @@ async function resumeAsyncRun(input: {
|
|
|
1789
1855
|
runId,
|
|
1790
1856
|
kind: "runner",
|
|
1791
1857
|
asyncDir: path.join(DIRS.async, runId),
|
|
1792
|
-
}, { liveWorkflowRunIds: new Set(input.deps.state.workflowControllers?.keys() ?? []) }) : undefined;
|
|
1858
|
+
}, { liveWorkflowRunIds: new Set(input.deps.state.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(input.deps.config.capacity?.abandonedSlotReleaseAfterMs) }) : undefined;
|
|
1793
1859
|
} catch (error) {
|
|
1794
1860
|
if (error instanceof ActiveAsyncCapacityError) return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: "chain", results: [], activeAsyncCapacity: error.snapshot } };
|
|
1795
1861
|
throw error;
|
|
@@ -1815,6 +1881,7 @@ async function resumeAsyncRun(input: {
|
|
|
1815
1881
|
label: `Attached ${target.runId}`,
|
|
1816
1882
|
},
|
|
1817
1883
|
agents,
|
|
1884
|
+
unknownAgentDiagnosticContext,
|
|
1818
1885
|
ctx: compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
1819
1886
|
pi: input.deps.pi,
|
|
1820
1887
|
cwd: input.requestCwd,
|
|
@@ -1835,6 +1902,7 @@ async function resumeAsyncRun(input: {
|
|
|
1835
1902
|
sessionRoot: input.deps.getSubagentSessionRoot(parentSessionFile),
|
|
1836
1903
|
chainSkills: normalized === false ? [] : (normalized ?? []),
|
|
1837
1904
|
agentContract: input.params.agentContract,
|
|
1905
|
+
fast: input.params.fast,
|
|
1838
1906
|
dynamicFanoutMaxItems: input.deps.config.chain?.dynamicFanout?.maxItems,
|
|
1839
1907
|
maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
|
|
1840
1908
|
waitToolEnabled: input.deps.waitToolEnabled,
|
|
@@ -1886,20 +1954,23 @@ async function resumeAsyncRun(input: {
|
|
|
1886
1954
|
sourceRunId: target.runId,
|
|
1887
1955
|
runId,
|
|
1888
1956
|
asyncDir: path.join(DIRS.async, runId),
|
|
1889
|
-
})
|
|
1957
|
+
}, { abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(input.deps.config.capacity?.abandonedSlotReleaseAfterMs) })
|
|
1890
1958
|
: acquireActiveAsyncCapacity({
|
|
1891
1959
|
sessionId: input.deps.state.currentSessionId!,
|
|
1892
1960
|
limit: resolveMaxActiveAsyncRunsPerSession(input.deps.config.maxActiveAsyncRunsPerSession),
|
|
1893
1961
|
runId,
|
|
1894
1962
|
kind: "runner",
|
|
1895
1963
|
asyncDir: path.join(DIRS.async, runId),
|
|
1896
|
-
});
|
|
1964
|
+
}, { abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(input.deps.config.capacity?.abandonedSlotReleaseAfterMs) });
|
|
1897
1965
|
} catch (error) {
|
|
1898
1966
|
if (error instanceof ActiveAsyncCapacityError) return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: "single", results: [], activeAsyncCapacity: error.snapshot } };
|
|
1899
1967
|
return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "single", results: [] } };
|
|
1900
1968
|
}
|
|
1901
1969
|
const recoveryAgentConfig = recoveryDescriptor ? applySteeringRecoveryAgentConfig(baseAgentConfig, recoveryDescriptor) : baseAgentConfig;
|
|
1902
1970
|
const agentConfig = intercomBridge.active ? applyIntercomBridgeToAgent(recoveryAgentConfig, intercomBridge) : recoveryAgentConfig;
|
|
1971
|
+
const foregroundContract = target.source === "foreground" ? target.resumeContract : undefined;
|
|
1972
|
+
const outputSchema = input.params.outputSchema ?? foregroundContract?.outputSchema ?? recoveryDescriptor?.structuredOutputSchema;
|
|
1973
|
+
const agentContract = input.params.agentContract ?? foregroundContract?.agentContract ?? recoveryDescriptor?.agentContract;
|
|
1903
1974
|
const artifactConfig: ArtifactConfig = recoveryDescriptor?.artifactConfig ?? omitUndefinedProperties({ ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false, dir: input.deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir });
|
|
1904
1975
|
const artifactsDir = recoveryDescriptor?.artifactsDir ?? getArtifactsDir(parentSessionFile, effectiveCwd, artifactConfig.dir);
|
|
1905
1976
|
const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
@@ -1938,9 +2009,11 @@ async function resumeAsyncRun(input: {
|
|
|
1938
2009
|
},
|
|
1939
2010
|
context: recoveryContext,
|
|
1940
2011
|
modelOverride: recoveryDescriptor?.model ?? target.model,
|
|
2012
|
+
fast: recoveryDescriptor?.fast,
|
|
1941
2013
|
modelOverrideFromParent: recoveryDescriptor?.modelOverrideFromParent,
|
|
1942
2014
|
thinkingOverride: recoveryDescriptor?.thinking ?? target.thinking,
|
|
1943
2015
|
thinkingCeiling: recoveryDescriptor?.thinkingCeiling ?? ("thinkingCeiling" in target ? target.thinkingCeiling : undefined),
|
|
2016
|
+
extensionBindings: recoveryDescriptor?.extensionBindings ?? ("extensionBindings" in target ? target.extensionBindings : undefined),
|
|
1944
2017
|
outputBaseDir: resolveSingleRunOutputBaseDir(input.deps, artifactsDir, runId),
|
|
1945
2018
|
maxSubagentDepth: recoveryDescriptor?.maxSubagentDepth ?? resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
|
|
1946
2019
|
waitToolEnabled: input.deps.waitToolEnabled,
|
|
@@ -1953,12 +2026,12 @@ async function resumeAsyncRun(input: {
|
|
|
1953
2026
|
controlIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
|
|
1954
2027
|
childIntercomTarget: intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
|
|
1955
2028
|
availableModels,
|
|
1956
|
-
output:
|
|
1957
|
-
outputMode: input.params.outputMode ?? recoveryDescriptor?.outputMode,
|
|
1958
|
-
...(
|
|
1959
|
-
...(
|
|
2029
|
+
output: input.params.output !== undefined ? input.params.output : foregroundContract?.output ?? recoveryDescriptor?.outputPath,
|
|
2030
|
+
outputMode: input.params.outputMode ?? foregroundContract?.outputMode ?? recoveryDescriptor?.outputMode,
|
|
2031
|
+
...(agentContract ? { agentContract } : {}),
|
|
2032
|
+
...(outputSchema ? { structuredOutputSchema: outputSchema } : {}),
|
|
1960
2033
|
...(recoveryDescriptor?.skills ? { skills: [...recoveryDescriptor.skills] } : {}),
|
|
1961
|
-
...(
|
|
2034
|
+
...(input.params.acceptance !== undefined ? { acceptance: input.params.acceptance } : foregroundContract?.acceptance !== undefined ? { acceptance: foregroundContract.acceptance } : recoveryDescriptor?.acceptance !== undefined ? { acceptance: recoveryDescriptor.acceptance } : {}),
|
|
1962
2035
|
...(input.params.timeoutMs !== undefined ? { timeoutMs: input.params.timeoutMs } : {}),
|
|
1963
2036
|
...(input.absoluteDeadlineAt !== undefined ? { absoluteDeadlineAt: input.absoluteDeadlineAt } : {}),
|
|
1964
2037
|
...(input.params.turnBudget !== undefined ? { turnBudget: input.params.turnBudget } : {}),
|
|
@@ -2037,9 +2110,13 @@ async function resumeAsyncRun(input: {
|
|
|
2037
2110
|
...(completed.structuredOutputPath ? { structuredOutputPath: completed.structuredOutputPath } : {}),
|
|
2038
2111
|
...(completed.structuredOutputSchemaPath ? { structuredOutputSchemaPath: completed.structuredOutputSchemaPath } : {}),
|
|
2039
2112
|
...(completed.acceptance ? { acceptance: completed.acceptance } : {}),
|
|
2113
|
+
...(completed.artifactPaths ? { artifactPaths: completed.artifactPaths } : {}),
|
|
2114
|
+
...(completed.outputSaveError ? { outputSaveError: completed.outputSaveError } : {}),
|
|
2115
|
+
...(completed.transcriptPath ? { transcriptPath: completed.transcriptPath } : {}),
|
|
2116
|
+
...(completed.transcriptError ? { transcriptError: completed.transcriptError } : {}),
|
|
2040
2117
|
};
|
|
2041
2118
|
return {
|
|
2042
|
-
content: [{ type: "text", text: completed.output || completed.error || `Revived ${target.source} subagent ${revivedId} completed without output.` }],
|
|
2119
|
+
content: [{ type: "text", text: completed.success ? completed.output || completed.error || `Revived ${target.source} subagent ${revivedId} completed without output.` : completed.error || completed.output || `Revived ${target.source} subagent ${revivedId} completed without output.` }],
|
|
2043
2120
|
...(completed.success ? {} : { isError: true }),
|
|
2044
2121
|
details: {
|
|
2045
2122
|
...result.details,
|
|
@@ -2204,25 +2281,43 @@ async function maybeBuildForegroundIntercomReceipt(input: {
|
|
|
2204
2281
|
};
|
|
2205
2282
|
}
|
|
2206
2283
|
|
|
2207
|
-
function
|
|
2284
|
+
function diagnosticContextFromDiscovery(
|
|
2285
|
+
discovered: { agents: AgentConfig[]; cwd?: string; scope?: AgentScope; directories?: UnknownAgentDiagnosticContext["directories"] },
|
|
2286
|
+
cwd: string,
|
|
2287
|
+
scope: AgentScope,
|
|
2288
|
+
): UnknownAgentDiagnosticContext {
|
|
2289
|
+
if (discovered.cwd && discovered.scope && discovered.directories) return unknownAgentDiagnosticContext({ ...discovered, cwd: discovered.cwd, scope: discovered.scope, directories: discovered.directories });
|
|
2290
|
+
return unknownAgentDiagnosticContext(discoverAgents(path.resolve(cwd), scope));
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2293
|
+
function canonicalizeAgentName(name: string, agents: AgentConfig[], diagnostics: AgentDiscoveryDiagnostic[] | undefined, context: UnknownAgentDiagnosticContext): { name?: string; error?: string } {
|
|
2208
2294
|
const resolved = resolveAgentName(name, agents);
|
|
2209
2295
|
const candidates = resolved.error ? agents.filter((agent) => resolveAgentName(name, [agent]).agent) : resolved.agent;
|
|
2210
2296
|
const diagnostic = findBlockingAgentDiagnostic(name, candidates, diagnostics);
|
|
2211
2297
|
if (diagnostic) return { error: `Agent '${name}' has invalid configuration: ${diagnostic.error}` };
|
|
2212
2298
|
if (resolved.error) return { error: resolved.error };
|
|
2213
|
-
if (!resolved.agent) return { error:
|
|
2299
|
+
if (!resolved.agent) return { error: formatUnknownAgentError(name, context) };
|
|
2214
2300
|
return { name: resolved.agent.name };
|
|
2215
2301
|
}
|
|
2216
2302
|
|
|
2217
|
-
function canonicalizeExecutionParams(params: SubagentParamsLike, agents: AgentConfig[], diagnostics
|
|
2303
|
+
function canonicalizeExecutionParams(params: SubagentParamsLike, agents: AgentConfig[], diagnostics: AgentDiscoveryDiagnostic[] | undefined, context: UnknownAgentDiagnosticContext): { params?: SubagentParamsLike; error?: string } {
|
|
2218
2304
|
const resolve = (name: string, location?: string): { name?: string; error?: string } => {
|
|
2219
|
-
const result = canonicalizeAgentName(name, agents, diagnostics);
|
|
2305
|
+
const result = canonicalizeAgentName(name, agents, diagnostics, context);
|
|
2220
2306
|
return result.error && location ? { error: `${result.error} (${location})` } : result;
|
|
2221
2307
|
};
|
|
2222
2308
|
if (params.agent) {
|
|
2223
2309
|
const result = resolve(params.agent);
|
|
2224
2310
|
if (result.error) return { error: result.error };
|
|
2225
2311
|
params = omitUndefinedProperties({ ...params, agent: result.name });
|
|
2312
|
+
const agent = agents.find((candidate) => candidate.name === result.name);
|
|
2313
|
+
if (params.extensionBindings !== undefined && (agent?.runner?.type === "external-cli" || agent?.runner?.type === "external-job")) return { error: `extensionBindings is not supported for runner.type='${agent.runner.type}'.` };
|
|
2314
|
+
}
|
|
2315
|
+
if (params.extensionBindings !== undefined) {
|
|
2316
|
+
try {
|
|
2317
|
+
params = { ...params, extensionBindings: normalizeExtensionBindings(params.extensionBindings)!.value };
|
|
2318
|
+
} catch (error) {
|
|
2319
|
+
return { error: error instanceof Error ? error.message : String(error) };
|
|
2320
|
+
}
|
|
2226
2321
|
}
|
|
2227
2322
|
if (params.tasks) {
|
|
2228
2323
|
const tasks: TaskParam[] = [];
|
|
@@ -2275,6 +2370,7 @@ function validateExecutionInput(
|
|
|
2275
2370
|
hasTasks: boolean,
|
|
2276
2371
|
hasSingle: boolean,
|
|
2277
2372
|
allowClarifyTaskPrompt: boolean,
|
|
2373
|
+
context: UnknownAgentDiagnosticContext,
|
|
2278
2374
|
): AgentToolResult<Details> | null {
|
|
2279
2375
|
if (Number(hasChain) + Number(hasTasks) + Number(hasSingle) !== 1) {
|
|
2280
2376
|
return {
|
|
@@ -2300,7 +2396,7 @@ function validateExecutionInput(
|
|
|
2300
2396
|
|
|
2301
2397
|
if (hasSingle && params.agent && !agents.find((agent) => agent.name === params.agent)) {
|
|
2302
2398
|
return {
|
|
2303
|
-
content: [{ type: "text", text:
|
|
2399
|
+
content: [{ type: "text", text: formatUnknownAgentError(params.agent, context) }],
|
|
2304
2400
|
isError: true,
|
|
2305
2401
|
details: { mode: "single" as const, results: [] },
|
|
2306
2402
|
};
|
|
@@ -2311,7 +2407,7 @@ function validateExecutionInput(
|
|
|
2311
2407
|
const task = params.tasks[i]!;
|
|
2312
2408
|
if (!agents.find((agent) => agent.name === task.agent)) {
|
|
2313
2409
|
return {
|
|
2314
|
-
content: [{ type: "text", text:
|
|
2410
|
+
content: [{ type: "text", text: `${formatUnknownAgentError(task.agent, context)} (task ${i + 1})` }],
|
|
2315
2411
|
isError: true,
|
|
2316
2412
|
details: { mode: "parallel" as const, results: [] },
|
|
2317
2413
|
};
|
|
@@ -2356,7 +2452,7 @@ function validateExecutionInput(
|
|
|
2356
2452
|
for (const agentName of stepAgents) {
|
|
2357
2453
|
if (!agents.find((a) => a.name === agentName)) {
|
|
2358
2454
|
return {
|
|
2359
|
-
content: [{ type: "text", text:
|
|
2455
|
+
content: [{ type: "text", text: `${formatUnknownAgentError(agentName, context)} (step ${i + 1})` }],
|
|
2360
2456
|
isError: true,
|
|
2361
2457
|
details: { mode: "chain" as const, results: [] },
|
|
2362
2458
|
};
|
|
@@ -2947,21 +3043,21 @@ function wrapChainTasksForFork(chain: ChainStep[], contextPolicy: AgentDefaultCo
|
|
|
2947
3043
|
});
|
|
2948
3044
|
}
|
|
2949
3045
|
|
|
2950
|
-
function preflightForkSessionsForStaticTasks(
|
|
3046
|
+
async function preflightForkSessionsForStaticTasks(
|
|
2951
3047
|
params: SubagentParamsLike,
|
|
2952
3048
|
contextPolicy: AgentDefaultContextPolicy,
|
|
2953
|
-
|
|
3049
|
+
prepareSessionForTask: PrepareForkSessionForTask,
|
|
2954
3050
|
dynamicFanoutMaxItems?: number,
|
|
2955
|
-
): void {
|
|
3051
|
+
): Promise<void> {
|
|
2956
3052
|
if (!contextPolicy.usesFork) return;
|
|
2957
3053
|
if (params.agent) {
|
|
2958
|
-
if (shouldForkAgent(contextPolicy, params.agent))
|
|
3054
|
+
if (shouldForkAgent(contextPolicy, params.agent)) await prepareSessionForTask(params.agent, 0, params.model);
|
|
2959
3055
|
return;
|
|
2960
3056
|
}
|
|
2961
3057
|
if (params.tasks) {
|
|
2962
|
-
params.tasks.
|
|
2963
|
-
if (shouldForkAgent(contextPolicy, task.agent))
|
|
2964
|
-
}
|
|
3058
|
+
for (const [index, task] of params.tasks.entries()) {
|
|
3059
|
+
if (shouldForkAgent(contextPolicy, task.agent)) await prepareSessionForTask(task.agent, index, task.model);
|
|
3060
|
+
}
|
|
2965
3061
|
return;
|
|
2966
3062
|
}
|
|
2967
3063
|
if (!params.chain?.length) return;
|
|
@@ -2969,7 +3065,7 @@ function preflightForkSessionsForStaticTasks(
|
|
|
2969
3065
|
for (const step of params.chain) {
|
|
2970
3066
|
if (isParallelStep(step)) {
|
|
2971
3067
|
for (const task of step.parallel) {
|
|
2972
|
-
if (shouldForkAgent(contextPolicy, task.agent))
|
|
3068
|
+
if (shouldForkAgent(contextPolicy, task.agent)) await prepareSessionForTask(task.agent, flatIndex, task.model);
|
|
2973
3069
|
flatIndex++;
|
|
2974
3070
|
}
|
|
2975
3071
|
continue;
|
|
@@ -2977,13 +3073,13 @@ function preflightForkSessionsForStaticTasks(
|
|
|
2977
3073
|
if (isDynamicParallelStep(step)) {
|
|
2978
3074
|
const maxItems = step.expand.maxItems ?? dynamicFanoutMaxItems ?? 0;
|
|
2979
3075
|
if (shouldForkAgent(contextPolicy, step.parallel.agent)) {
|
|
2980
|
-
for (let itemIndex = 0; itemIndex < maxItems; itemIndex++)
|
|
3076
|
+
for (let itemIndex = 0; itemIndex < maxItems; itemIndex++) await prepareSessionForTask(step.parallel.agent, flatIndex + itemIndex, step.parallel.model);
|
|
2981
3077
|
}
|
|
2982
3078
|
flatIndex += maxItems;
|
|
2983
3079
|
continue;
|
|
2984
3080
|
}
|
|
2985
3081
|
const sequential = step as SequentialStep;
|
|
2986
|
-
if (shouldForkAgent(contextPolicy, sequential.agent))
|
|
3082
|
+
if (shouldForkAgent(contextPolicy, sequential.agent)) await prepareSessionForTask(sequential.agent, flatIndex, sequential.model);
|
|
2987
3083
|
flatIndex++;
|
|
2988
3084
|
}
|
|
2989
3085
|
}
|
|
@@ -3037,9 +3133,13 @@ async function waitForWorkflowAsyncSingleResult(
|
|
|
3037
3133
|
...(completed.structuredOutputPath ? { structuredOutputPath: completed.structuredOutputPath } : {}),
|
|
3038
3134
|
...(completed.structuredOutputSchemaPath ? { structuredOutputSchemaPath: completed.structuredOutputSchemaPath } : {}),
|
|
3039
3135
|
...(completed.acceptance ? { acceptance: completed.acceptance } : {}),
|
|
3136
|
+
...(completed.artifactPaths ? { artifactPaths: completed.artifactPaths } : {}),
|
|
3137
|
+
...(completed.outputSaveError ? { outputSaveError: completed.outputSaveError } : {}),
|
|
3138
|
+
...(completed.transcriptPath ? { transcriptPath: completed.transcriptPath } : {}),
|
|
3139
|
+
...(completed.transcriptError ? { transcriptError: completed.transcriptError } : {}),
|
|
3040
3140
|
});
|
|
3041
3141
|
return {
|
|
3042
|
-
content: [{ type: "text", text: completed.output || completed.error || `Async workflow child ${options.runId} completed without output.` }],
|
|
3142
|
+
content: [{ type: "text", text: completed.success ? completed.output || completed.error || `Async workflow child ${options.runId} completed without output.` : completed.error || completed.output || `Async workflow child ${options.runId} completed without output.` }],
|
|
3043
3143
|
...(completed.success ? {} : { isError: true }),
|
|
3044
3144
|
details: {
|
|
3045
3145
|
...launchResult.details,
|
|
@@ -3067,6 +3167,7 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
3067
3167
|
intercomBridge,
|
|
3068
3168
|
nestedRoute,
|
|
3069
3169
|
contextPolicy,
|
|
3170
|
+
unknownAgentDiagnosticContext,
|
|
3070
3171
|
} = data;
|
|
3071
3172
|
const hasChain = (params.chain?.length ?? 0) > 0;
|
|
3072
3173
|
const hasTasks = (params.tasks?.length ?? 0) > 0;
|
|
@@ -3105,7 +3206,7 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
3105
3206
|
const a = agents.find((x) => x.name === params.agent);
|
|
3106
3207
|
if (!a) {
|
|
3107
3208
|
return {
|
|
3108
|
-
content: [{ type: "text", text:
|
|
3209
|
+
content: [{ type: "text", text: formatUnknownAgentError(params.agent!, unknownAgentDiagnosticContext) }],
|
|
3109
3210
|
isError: true,
|
|
3110
3211
|
details: { mode: "single" as const, results: [] },
|
|
3111
3212
|
};
|
|
@@ -3119,6 +3220,9 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
3119
3220
|
const externalRunnerWithoutExplicitModel = (a.runner?.type === "external-cli" || a.runner?.type === "external-job")
|
|
3120
3221
|
&& params.model === undefined
|
|
3121
3222
|
&& (a.model === undefined || (a.modelSource?.type === "subagents.defaultModel" && a.model === a.modelSource.model));
|
|
3223
|
+
if ((a.runner?.type === "external-cli" || a.runner?.type === "external-job") && (params.fast ?? a.fast) === true) {
|
|
3224
|
+
return buildRequestedModeError(params, `Agent '${a.name}' uses runner.type='${a.runner.type}' and does not support fast mode.`);
|
|
3225
|
+
}
|
|
3122
3226
|
const modelScopes = resolveModelScopesForAgent(data.modelScope, a.name, parentModel);
|
|
3123
3227
|
const modelOverride = a.runner?.type === "external-cli" || a.runner?.type === "external-job"
|
|
3124
3228
|
? params.model ?? (externalRunnerWithoutExplicitModel ? undefined : a.model)
|
|
@@ -3147,6 +3251,7 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
3147
3251
|
...(params.reads !== undefined ? { reads: params.reads } : {}),
|
|
3148
3252
|
outputBaseDir: resolveSingleRunOutputBaseDir(deps, artifactsDir, id),
|
|
3149
3253
|
modelOverride,
|
|
3254
|
+
fast: params.fast,
|
|
3150
3255
|
modelOverrideFromParent,
|
|
3151
3256
|
thinkingOverride: externalRunnerWithoutExplicitModel ? undefined : thinkingOverrideForTask(params.agent!, 0, modelOverride, modelOverrideFromParent),
|
|
3152
3257
|
thinkingCeiling: a.maxThinking,
|
|
@@ -3163,6 +3268,7 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
3163
3268
|
nestedRoute,
|
|
3164
3269
|
agentContract: params.agentContract,
|
|
3165
3270
|
structuredOutputSchema: params.outputSchema,
|
|
3271
|
+
extensionBindings: params.extensionBindings,
|
|
3166
3272
|
acceptance: params.acceptance,
|
|
3167
3273
|
timeoutMs: data.timeoutMs,
|
|
3168
3274
|
turnBudget: data.turnBudget,
|
|
@@ -3466,7 +3572,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3466
3572
|
const agentConfig = agents.find((a) => a.name === params.agent);
|
|
3467
3573
|
if (!agentConfig) {
|
|
3468
3574
|
return {
|
|
3469
|
-
content: [{ type: "text", text:
|
|
3575
|
+
content: [{ type: "text", text: formatUnknownAgentError(params.agent!, data.unknownAgentDiagnosticContext) }],
|
|
3470
3576
|
isError: true,
|
|
3471
3577
|
details: { mode: "single", results: [] },
|
|
3472
3578
|
};
|
|
@@ -3540,7 +3646,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3540
3646
|
return { content: [{ type: "text", text: validationError }], isError: true, details: { mode: "single", results: [] } };
|
|
3541
3647
|
}
|
|
3542
3648
|
const structuredRuntime = params.outputSchema
|
|
3543
|
-
? createStructuredOutputRuntime(params.outputSchema, artifactConfig.enabled ? path.join(artifactsDir, "structured-output", runId) : undefined)
|
|
3649
|
+
? createStructuredOutputRuntime(params.outputSchema, artifactConfig.enabled ? path.join(artifactsDir, "structured-output", runId) : undefined, { captureAcceptanceReport: params.acceptance !== false })
|
|
3544
3650
|
: undefined;
|
|
3545
3651
|
// Reads: caller override > agent defaultReads > none. `~`/`~/` expand to home;
|
|
3546
3652
|
// absolute paths pass through; relative paths resolve against the child cwd.
|
|
@@ -3599,6 +3705,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3599
3705
|
llmIntentArbiter: createTaskMutationArbiter(ctx),
|
|
3600
3706
|
...workflowForegroundSteeringLaunchOptions(foregroundControl, 0),
|
|
3601
3707
|
context: data.contextPolicy.contextForAgent(params.agent!),
|
|
3708
|
+
unknownAgentDiagnosticContext: data.unknownAgentDiagnosticContext,
|
|
3602
3709
|
runFanoutBudget: params.runFanoutAdmitted ? data.runFanoutBudget : { ...data.runFanoutBudget, parentPath: `${data.runFanoutBudget.parentPath ? `${data.runFanoutBudget.parentPath}/` : ""}single` },
|
|
3603
3710
|
cwd: singleCwd,
|
|
3604
3711
|
signal,
|
|
@@ -3624,9 +3731,11 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3624
3731
|
nestedRoute: foregroundControl?.nestedRoute,
|
|
3625
3732
|
index: 0,
|
|
3626
3733
|
modelOverride,
|
|
3734
|
+
fast: params.fast,
|
|
3627
3735
|
modelOverrideFromParent,
|
|
3628
3736
|
thinkingOverride: thinkingOverrideForTask(params.agent!, 0, modelOverride, modelOverrideFromParent),
|
|
3629
3737
|
thinkingCeiling: agentConfig.maxThinking,
|
|
3738
|
+
extensionBindings: params.extensionBindings,
|
|
3630
3739
|
availableModels,
|
|
3631
3740
|
preferredModelProvider: currentProvider,
|
|
3632
3741
|
modelScope: modelScopes,
|
|
@@ -3635,6 +3744,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3635
3744
|
agentContract: params.agentContract,
|
|
3636
3745
|
acceptance: params.acceptance,
|
|
3637
3746
|
acceptanceContext: { mode: "single" },
|
|
3747
|
+
workflowChildPermitLaunch: data.workflowChildPermitLaunch,
|
|
3638
3748
|
onEffectivePrompt: foregroundControl ? (prompt) => updateLiveEffectivePrompt(foregroundControl, 0, prompt) : undefined,
|
|
3639
3749
|
onDetachReady: (detach) => {
|
|
3640
3750
|
detachForeground = detach;
|
|
@@ -3668,7 +3778,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3668
3778
|
}
|
|
3669
3779
|
}
|
|
3670
3780
|
}
|
|
3671
|
-
recordRun(params.agent!, cleanTask, result.exitCode, result.progressSummary?.durationMs ?? 0);
|
|
3781
|
+
recordRun(params.agent!, cleanTask, result.exitCode, result.progressSummary?.durationMs ?? 0, result);
|
|
3672
3782
|
},
|
|
3673
3783
|
timeoutMs: data.timeoutMs,
|
|
3674
3784
|
deadlineAt,
|
|
@@ -3693,7 +3803,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3693
3803
|
}
|
|
3694
3804
|
}
|
|
3695
3805
|
if (!r.detached) {
|
|
3696
|
-
recordRun(params.agent!, cleanTask, r.exitCode, r.progressSummary?.durationMs ?? 0);
|
|
3806
|
+
recordRun(params.agent!, cleanTask, r.exitCode, r.progressSummary?.durationMs ?? 0, r);
|
|
3697
3807
|
}
|
|
3698
3808
|
|
|
3699
3809
|
let worktreeHandoff: ReturnType<typeof finalizeSingleWorktreeHandoff> | undefined;
|
|
@@ -3739,7 +3849,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3739
3849
|
usageBudget: usageBudgetState(data.usageBudget, totalCost),
|
|
3740
3850
|
...(worktreeHandoff?.reference ? { parallelHandoff: worktreeHandoff.reference } : {}),
|
|
3741
3851
|
}));
|
|
3742
|
-
rememberForegroundRun(deps.state, { runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, results: details.results });
|
|
3852
|
+
rememberForegroundRun(deps.state, { runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, results: details.results, params, effectiveOutput, effectiveOutputMode, extensionBindings: params.extensionBindings });
|
|
3743
3853
|
|
|
3744
3854
|
const suppressRoutineResultIntercom = shouldSuppressRoutineResultIntercom({ suppressRoutineResultIntercom: params.suppressRoutineResultIntercom, results: [r] });
|
|
3745
3855
|
if (!r.detached && !r.interrupted && !suppressRoutineResultIntercom) {
|
|
@@ -3893,9 +4003,14 @@ function workflowChildResult(
|
|
|
3893
4003
|
const output = result.details.results.length === 1 && result.details.results[0]?.finalOutput !== undefined
|
|
3894
4004
|
? result.details.results[0].finalOutput
|
|
3895
4005
|
: receiptOutput;
|
|
4006
|
+
const childError = result.details.results.map((child) => child.error).find((error): error is string => Boolean(error));
|
|
4007
|
+
const failureError = childError && receiptOutput && receiptOutput !== childError
|
|
4008
|
+
? `${childError}\n\n${receiptOutput}`
|
|
4009
|
+
: childError || receiptOutput || output || "Child run failed.";
|
|
3896
4010
|
const detached = result.details.results.some((child) => child.detached);
|
|
4011
|
+
const interrupted = result.details.results.some((child) => child.interrupted);
|
|
3897
4012
|
const stopped = result.details.results.some((child) => child.stopped);
|
|
3898
|
-
const ok = result.isError !== true && !detached && !stopped;
|
|
4013
|
+
const ok = result.isError !== true && !detached && !interrupted && !stopped;
|
|
3899
4014
|
const artifactPaths = new Set<string>();
|
|
3900
4015
|
if (result.details.asyncDir) artifactPaths.add(result.details.asyncDir);
|
|
3901
4016
|
if (result.details.parallelHandoff?.path) artifactPaths.add(result.details.parallelHandoff.path);
|
|
@@ -3927,19 +4042,28 @@ function workflowChildResult(
|
|
|
3927
4042
|
const continuationRunIds = [...new Set([resumeSourceRunId, runId].filter((value): value is string => Boolean(value)))];
|
|
3928
4043
|
const outputReference = result.details.results.find((child) => child.savedOutputPath)?.savedOutputPath
|
|
3929
4044
|
?? result.details.results.find((child) => child.outputReference?.path)?.outputReference?.path;
|
|
4045
|
+
const externalResult = result.details.results.length === 1 && result.details.results[0]?.runner?.type === "external-cli" ? result.details.results[0] : undefined;
|
|
4046
|
+
const externalStatus = result.details.asyncDir ? readStatus(result.details.asyncDir) : undefined;
|
|
4047
|
+
const externalStatusStep = externalStatus?.steps?.length === 1 && externalStatus.steps[0]?.runner?.type === "external-cli" ? externalStatus.steps[0] : undefined;
|
|
4048
|
+
const externalRunner = normalizeExternalCliRunnerStatus(externalResult?.runner ?? externalStatusStep?.runner);
|
|
4049
|
+
const externalProcess = externalResult?.externalProcess ?? externalStatusStep?.externalProcess;
|
|
4050
|
+
const externalAdapter = externalRunner ? externalCliReceiptMetadata({ runner: externalRunner, externalProcess, outputReference }) : undefined;
|
|
4051
|
+
if (externalAdapter) resumability = { state: "not-resumable", reason: externalAdapter.nonResumableReason };
|
|
3930
4052
|
return {
|
|
3931
4053
|
key,
|
|
3932
4054
|
ok,
|
|
3933
4055
|
...(resolvedAgents.length === 1 ? { agent: resolvedAgents[0] } : {}),
|
|
3934
4056
|
...(runId ? { runId } : {}),
|
|
3935
4057
|
output,
|
|
3936
|
-
...(!ok ? { error:
|
|
4058
|
+
...(!ok ? { error: failureError } : {}),
|
|
3937
4059
|
...(detached ? { detached: true } : {}),
|
|
4060
|
+
...(interrupted ? { interrupted: true } : {}),
|
|
3938
4061
|
...(stopped ? { stopped: true } : {}),
|
|
3939
4062
|
...(structured.length === 1 ? { structuredOutput: structured[0] } : structured.length > 1 ? { structuredOutput: structured } : {}),
|
|
3940
4063
|
...(requestedContext ? { requestedContext } : {}),
|
|
3941
4064
|
...(resolvedContext ? { resolvedContext } : {}),
|
|
3942
4065
|
...(outputReference ? { outputReference } : {}),
|
|
4066
|
+
...(externalAdapter ? { externalAdapter } : {}),
|
|
3943
4067
|
resumability,
|
|
3944
4068
|
continuation: { runIds: continuationRunIds },
|
|
3945
4069
|
artifactPaths: [...artifactPaths],
|
|
@@ -3985,8 +4109,18 @@ function terminalWorkflowReceipt(
|
|
|
3985
4109
|
workflowRunId: string,
|
|
3986
4110
|
state: WorkflowReceiptState,
|
|
3987
4111
|
children: WorkflowScriptChildResult[],
|
|
4112
|
+
workflowChildren?: WorkflowReceipt["workflowChildren"],
|
|
3988
4113
|
): WorkflowReceipt {
|
|
3989
|
-
return buildWorkflowReceipt({ workflowRunId, state, children });
|
|
4114
|
+
return buildWorkflowReceipt({ workflowRunId, state, children, workflowChildren });
|
|
4115
|
+
}
|
|
4116
|
+
|
|
4117
|
+
function workflowFailureMessage(error: unknown, workflowRunId: string, children: WorkflowScriptChildResult[]): string {
|
|
4118
|
+
const text = error instanceof Error ? error.message : String(error);
|
|
4119
|
+
const validationPrefix = "workflowScript validation failed before child launch; no children launched.";
|
|
4120
|
+
if (children.length === 0 && text.includes(validationPrefix)) {
|
|
4121
|
+
return `Workflow '${workflowRunId}' validation failed before child launch; no children launched.${text.slice(text.indexOf(validationPrefix) + validationPrefix.length)}`;
|
|
4122
|
+
}
|
|
4123
|
+
return text;
|
|
3990
4124
|
}
|
|
3991
4125
|
|
|
3992
4126
|
export async function steerWorkflowChildByKey(input: {
|
|
@@ -4029,6 +4163,8 @@ export async function steerWorkflowChildByKey(input: {
|
|
|
4029
4163
|
return { key: input.key, state: "missed", error: `Workflow child '${input.key}' is ${childStatus.state}.` };
|
|
4030
4164
|
}
|
|
4031
4165
|
if (childStatus) {
|
|
4166
|
+
const unsupported = externalRunnerControlError(asyncDir, "steer");
|
|
4167
|
+
if (unsupported) return workflowSteerReceipt(input.key, unsupported);
|
|
4032
4168
|
const result = await steerAsyncRun({
|
|
4033
4169
|
state: input.state,
|
|
4034
4170
|
runId: childRunId,
|
|
@@ -4071,6 +4207,9 @@ export function prepareWorkflowLaunchParams(
|
|
|
4071
4207
|
? undefined
|
|
4072
4208
|
: Math.max(1, options.parentDeadlineAt - Date.now());
|
|
4073
4209
|
if (typeof childParams.resume === "string") {
|
|
4210
|
+
if (childParams.extensionBindings !== undefined || workflowDefaults.extensionBindings !== undefined) {
|
|
4211
|
+
throw new Error("extensionBindings is not supported with retained resume; resume uses the original retained child binding.");
|
|
4212
|
+
}
|
|
4074
4213
|
if (childParams.gate !== undefined || workflowDefaults.gate !== undefined) {
|
|
4075
4214
|
throw new Error("gate is not supported with retained resume; resume uses the retained child contract.");
|
|
4076
4215
|
}
|
|
@@ -4079,6 +4218,15 @@ export function prepareWorkflowLaunchParams(
|
|
|
4079
4218
|
const toolBudget = childParams.toolBudget ?? workflowDefaults.toolBudget;
|
|
4080
4219
|
const intercomBridge = childParams.intercomBridge ?? workflowDefaults.intercomBridge;
|
|
4081
4220
|
const worktree = childParams.worktree ?? workflowDefaults.worktree;
|
|
4221
|
+
const outputSchema = Object.hasOwn(childParams, "outputSchema") ? childParams.outputSchema : workflowDefaults.outputSchema;
|
|
4222
|
+
if (outputSchema !== undefined) assertJsonSchemaObject(outputSchema, "outputSchema");
|
|
4223
|
+
const agentContract = Object.hasOwn(childParams, "agentContract") ? childParams.agentContract : workflowDefaults.agentContract;
|
|
4224
|
+
if (agentContract !== undefined && !isAgentContractV1(agentContract as AgentContract)) throw new Error("agentContract must be { version: 1 }.");
|
|
4225
|
+
const acceptance = Object.hasOwn(childParams, "acceptance") ? childParams.acceptance : workflowDefaults.acceptance;
|
|
4226
|
+
const output = Object.hasOwn(childParams, "output") ? childParams.output : workflowDefaults.output;
|
|
4227
|
+
if (output !== undefined && typeof output !== "string" && typeof output !== "boolean") throw new Error("output must be a path string or boolean.");
|
|
4228
|
+
const outputMode = Object.hasOwn(childParams, "outputMode") ? childParams.outputMode : workflowDefaults.outputMode;
|
|
4229
|
+
if (outputMode !== undefined && outputMode !== "inline" && outputMode !== "file-only") throw new Error("outputMode must be 'inline' or 'file-only'.");
|
|
4082
4230
|
return {
|
|
4083
4231
|
action: "resume",
|
|
4084
4232
|
id: childParams.resume.trim(),
|
|
@@ -4087,6 +4235,11 @@ export function prepareWorkflowLaunchParams(
|
|
|
4087
4235
|
workflowParentRunId: parentWorkflowRunId,
|
|
4088
4236
|
workflowKey,
|
|
4089
4237
|
...(worktree !== undefined ? { worktree: worktree as boolean } : {}),
|
|
4238
|
+
...(outputSchema !== undefined ? { outputSchema: outputSchema as JsonSchemaObject } : {}),
|
|
4239
|
+
...(agentContract !== undefined ? { agentContract: agentContract as AgentContract } : {}),
|
|
4240
|
+
...(acceptance !== undefined ? { acceptance: acceptance as AcceptanceInput } : {}),
|
|
4241
|
+
...(output !== undefined ? { output: output as string | boolean } : {}),
|
|
4242
|
+
...(outputMode !== undefined ? { outputMode: outputMode as OutputMode } : {}),
|
|
4090
4243
|
...(options.runFanoutBudget ? { runFanoutBudget: { ...options.runFanoutBudget, parentPath: `${options.runFanoutBudget.parentPath ? `${options.runFanoutBudget.parentPath}/` : ""}workflow[${workflowKey}]` } } : {}),
|
|
4091
4244
|
...(options.missionDetached ? { mission: false } : {}),
|
|
4092
4245
|
...(timeoutMs !== undefined ? { timeoutMs: timeoutMs as number } : {}),
|
|
@@ -4109,6 +4262,7 @@ export function prepareWorkflowLaunchParams(
|
|
|
4109
4262
|
...(options.suppressRoutineResultIntercom ? { suppressRoutineResultIntercom: true } : {}),
|
|
4110
4263
|
...(capabilityCeiling ? { capabilityCeiling } : {}),
|
|
4111
4264
|
} as SubagentParamsLike;
|
|
4265
|
+
if (launchParams.extensionBindings !== undefined) launchParams.extensionBindings = normalizeExtensionBindings(launchParams.extensionBindings)!.value;
|
|
4112
4266
|
const normalizedGate = normalizeGateParams(launchParams);
|
|
4113
4267
|
if (!normalizedGate.ok) throw new Error(normalizedGate.error);
|
|
4114
4268
|
return normalizedGate.params;
|
|
@@ -4143,11 +4297,12 @@ function workflowChatProgressUpdate(
|
|
|
4143
4297
|
runId: string,
|
|
4144
4298
|
chatProgress: WorkflowChatProgressProjection,
|
|
4145
4299
|
workflow: NonNullable<Details["workflow"]>,
|
|
4300
|
+
workflowChildren?: Details["workflowChildren"],
|
|
4146
4301
|
): AgentToolResult<Details> | undefined {
|
|
4147
4302
|
if (chatProgress.mode !== "live-card") return undefined;
|
|
4148
4303
|
return {
|
|
4149
4304
|
content: [{ type: "text", text: "Workflow running." }],
|
|
4150
|
-
details: { mode: "workflow", runId, results: [], workflow, chatProgress },
|
|
4305
|
+
details: { mode: "workflow", runId, results: [], workflow, ...(workflowChildren ? { workflowChildren } : {}), chatProgress },
|
|
4151
4306
|
};
|
|
4152
4307
|
}
|
|
4153
4308
|
|
|
@@ -4158,10 +4313,12 @@ function createScheduledOwnerState(source: SubagentState, ownerSessionId: string
|
|
|
4158
4313
|
grantHistory: [...(source.subagentSpawns.grantHistory ?? [])],
|
|
4159
4314
|
}
|
|
4160
4315
|
: undefined;
|
|
4316
|
+
const ownerParentModel = source.currentSessionId === ownerSessionId ? source.lastParentModel : undefined;
|
|
4161
4317
|
return {
|
|
4162
4318
|
...source,
|
|
4163
4319
|
baseCwd: ctx.cwd,
|
|
4164
4320
|
currentSessionId: ownerSessionId,
|
|
4321
|
+
lastParentModel: ownerParentModel,
|
|
4165
4322
|
parentSessionFile: ctx.sessionManager.getSessionFile() ?? null,
|
|
4166
4323
|
subagentInProgress: false,
|
|
4167
4324
|
...(ownerSpawns ? { subagentSpawns: ownerSpawns } : { subagentSpawns: undefined }),
|
|
@@ -4222,6 +4379,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4222
4379
|
const delegatedThinkingOverrides = new WeakMap<object, AgentConfig["thinking"]>();
|
|
4223
4380
|
const delegatedZeroToolBudgets = new WeakSet<object>();
|
|
4224
4381
|
const delegatedExecutions = new WeakSet<object>();
|
|
4382
|
+
const workflowPermitContexts = new WeakMap<object, { root: WorkflowChildPermit } | { child: WorkflowChildPermitContext }>();
|
|
4225
4383
|
const warnedArtifactPackageDirs = new Set<string>();
|
|
4226
4384
|
const scheduledOwnerExecutors = new Map<string, ReturnType<typeof createSubagentExecutor>>();
|
|
4227
4385
|
const execute = async (
|
|
@@ -4231,11 +4389,23 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4231
4389
|
onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
|
|
4232
4390
|
ctx: ExtensionContext,
|
|
4233
4391
|
preserveActiveSession = false,
|
|
4392
|
+
parentModelOverride?: ParentModel | null,
|
|
4234
4393
|
): Promise<AgentToolResult<Details>> => {
|
|
4394
|
+
if (params.action?.trim() === "validate") {
|
|
4395
|
+
const validation = validateWorkflowScript(params.workflowScript ?? "");
|
|
4396
|
+
return {
|
|
4397
|
+
content: [{ type: "text", text: JSON.stringify(validation) }],
|
|
4398
|
+
...(validation.ok ? {} : { isError: true }),
|
|
4399
|
+
details: { mode: "management", results: [] },
|
|
4400
|
+
};
|
|
4401
|
+
}
|
|
4235
4402
|
const workflowLaunchObserver = workflowLaunchObservers.get(params);
|
|
4236
4403
|
const delegatedThinkingOverride = delegatedThinkingOverrides.get(params);
|
|
4237
4404
|
const allowZeroToolBudget = delegatedZeroToolBudgets.has(params);
|
|
4238
4405
|
const delegatedExecution = delegatedExecutions.has(params);
|
|
4406
|
+
const workflowPermitContext = workflowPermitContexts.get(params);
|
|
4407
|
+
const delegatedWorkflowPermit = workflowPermitContext && "root" in workflowPermitContext ? workflowPermitContext.root : undefined;
|
|
4408
|
+
const workflowChildPermitLaunch = workflowPermitContext && "child" in workflowPermitContext ? workflowPermitContext.child : undefined;
|
|
4239
4409
|
if (!preserveActiveSession) deps.state.baseCwd = ctx.cwd;
|
|
4240
4410
|
deps.state.foregroundRuns ??= new Map();
|
|
4241
4411
|
deps.state.foregroundControls ??= new Map();
|
|
@@ -4244,7 +4414,28 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4244
4414
|
if (!normalizedGate.ok) return buildRequestedModeError(params, normalizedGate.error);
|
|
4245
4415
|
const requestParams = normalizedGate.params;
|
|
4246
4416
|
const normalizedAction = typeof requestParams.action === "string" ? requestParams.action.trim() : requestParams.action;
|
|
4417
|
+
if (normalizedAction === "resume" && requestParams.extensionBindings !== undefined) return buildRequestedModeError(requestParams, "extensionBindings is not supported with action='resume'; resume uses the original retained child binding.");
|
|
4247
4418
|
if (requestParams.workflowScript !== undefined && normalizedAction === undefined) {
|
|
4419
|
+
if (delegatedWorkflowPermit) {
|
|
4420
|
+
const permitError = validateWorkflowChildPermitRoot(delegatedWorkflowPermit, _id);
|
|
4421
|
+
if (permitError) return buildRequestedModeError(requestParams, permitError);
|
|
4422
|
+
if (requestParams.async !== false) return buildRequestedModeError(requestParams, "Workflow child permit supports foreground workflow roots only; set async:false.");
|
|
4423
|
+
}
|
|
4424
|
+
const workflowParentModel = parentModelOverride !== undefined
|
|
4425
|
+
? parentModelOverride
|
|
4426
|
+
: (() => {
|
|
4427
|
+
const currentParentModel = normalizeParentModel(ctx.model);
|
|
4428
|
+
return (preserveActiveSession
|
|
4429
|
+
? currentParentModel
|
|
4430
|
+
: rememberParentModel(deps.state, resolveCurrentSessionId(ctx.sessionManager), currentParentModel)) ?? null;
|
|
4431
|
+
})();
|
|
4432
|
+
if (requestParams.extensionBindings !== undefined) {
|
|
4433
|
+
try {
|
|
4434
|
+
requestParams.extensionBindings = normalizeExtensionBindings(requestParams.extensionBindings)!.value;
|
|
4435
|
+
} catch (error) {
|
|
4436
|
+
return buildRequestedModeError(requestParams, error instanceof Error ? error.message : String(error));
|
|
4437
|
+
}
|
|
4438
|
+
}
|
|
4248
4439
|
const parentCwd = ctx.cwd;
|
|
4249
4440
|
const timeout = requestParams.timeoutMs ?? requestParams.maxRuntimeMs ?? (requestParams.async === false ? resolveConfigDefaultTimeoutMs(deps.config.timeoutMs) ?? DEFAULT_FOREGROUND_TIMEOUT_MS : undefined);
|
|
4250
4441
|
const workflowUsageBudget = validateUsageBudgetConfig(requestParams.usageBudget ?? deps.config.usageBudget, requestParams.usageBudget ? "usageBudget" : "config.usageBudget");
|
|
@@ -4289,7 +4480,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4289
4480
|
runId: workflowRunId,
|
|
4290
4481
|
kind: "workflow",
|
|
4291
4482
|
asyncDir: path.join(DIRS.async, workflowRunId),
|
|
4292
|
-
}, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) });
|
|
4483
|
+
}, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) });
|
|
4293
4484
|
} catch (error) {
|
|
4294
4485
|
if (error instanceof ActiveAsyncCapacityError) {
|
|
4295
4486
|
deps.state.activeAsyncCapacity = error.snapshot;
|
|
@@ -4363,7 +4554,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4363
4554
|
deps.state.workflowChildStops ??= new Map();
|
|
4364
4555
|
deps.state.workflowControllers.set(workflowRunId, controller);
|
|
4365
4556
|
workflowCapacity?.markWorkflowStarted();
|
|
4366
|
-
if (workflowCapacity) deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(currentSessionId, resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession), { liveWorkflowRunIds: new Set(deps.state.workflowControllers.keys()) });
|
|
4557
|
+
if (workflowCapacity) deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(currentSessionId, resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession), { liveWorkflowRunIds: new Set(deps.state.workflowControllers.keys()), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) });
|
|
4367
4558
|
let status: AsyncStatus = {
|
|
4368
4559
|
runId: workflowRunId,
|
|
4369
4560
|
toolCallId,
|
|
@@ -4380,6 +4571,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4380
4571
|
pid: process.pid,
|
|
4381
4572
|
steps: [],
|
|
4382
4573
|
workflow: { trace: [], emits: [], console: [] },
|
|
4574
|
+
workflowChildren: workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState: "running", inventoryComplete: false }),
|
|
4383
4575
|
runFanoutBudget: getRunFanoutBudgetSnapshot(workflowFanoutBudget),
|
|
4384
4576
|
};
|
|
4385
4577
|
const appendWorkflowEvent = (event: Record<string, unknown>) => {
|
|
@@ -4428,6 +4620,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4428
4620
|
if (persistClosed) return;
|
|
4429
4621
|
const liveJob = deps.state.asyncJobs.get(workflowRunId);
|
|
4430
4622
|
if (liveJob && (liveJob.status === "complete" || liveJob.status === "failed") && status.state !== "complete" && status.state !== "failed") return;
|
|
4623
|
+
const workflowState = status.state === "complete" ? "completed" : status.state === "failed" || status.state === "rejected" ? "failed" : status.state === "paused" ? "paused" : status.state === "stopped" ? "stopped" : "running";
|
|
4624
|
+
status.workflowChildren = workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState, inventoryComplete: workflowState !== "running", trace: status.workflow?.trace, steps: status.steps });
|
|
4431
4625
|
status.lastUpdate = Date.now();
|
|
4432
4626
|
if (!initialPersistenceComplete) {
|
|
4433
4627
|
writeAtomicJson(statusPath, status);
|
|
@@ -4471,6 +4665,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4471
4665
|
delete liveJob.agents;
|
|
4472
4666
|
}
|
|
4473
4667
|
liveJob.workflow = status.workflow;
|
|
4668
|
+
liveJob.workflowChildren = status.workflowChildren;
|
|
4474
4669
|
}
|
|
4475
4670
|
};
|
|
4476
4671
|
const writeWorkflowResult = (payload: Record<string, unknown>): boolean => {
|
|
@@ -4505,7 +4700,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4505
4700
|
status.toolCount = toolCounts.length > 0 ? toolCounts.reduce((total, count) => total + count, 0) : undefined;
|
|
4506
4701
|
status.currentStep = runningSteps.length === 1 ? steps.indexOf(runningSteps[0]!) : undefined;
|
|
4507
4702
|
};
|
|
4508
|
-
const workflowJob: AsyncJobState = { asyncId: workflowRunId, asyncDir, toolCallId, cwd: workflowCwd, ...(workflowSessionRoot ? { sessionRoot: workflowSessionRoot } : {}), status: "running", sessionId: currentSessionId ?? undefined, mode: "workflow", agents: [], steps: [], startedAt, updatedAt: startedAt, ...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}), ...(timeout !== undefined ? { timeoutMs: timeout, deadlineAt: startedAt + timeout } : {}), workflow: status.workflow };
|
|
4703
|
+
const workflowJob: AsyncJobState = { asyncId: workflowRunId, asyncDir, toolCallId, cwd: workflowCwd, ...(workflowSessionRoot ? { sessionRoot: workflowSessionRoot } : {}), status: "running", sessionId: currentSessionId ?? undefined, mode: "workflow", agents: [], steps: [], startedAt, updatedAt: startedAt, ...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}), ...(timeout !== undefined ? { timeoutMs: timeout, deadlineAt: startedAt + timeout } : {}), workflow: status.workflow, workflowChildren: status.workflowChildren };
|
|
4509
4704
|
deps.state.asyncJobs.set(workflowRunId, workflowJob);
|
|
4510
4705
|
deps.state.fleetJobs ??= new Map();
|
|
4511
4706
|
deps.state.fleetJobs.set(workflowRunId, workflowJob);
|
|
@@ -4692,7 +4887,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4692
4887
|
status: step.status,
|
|
4693
4888
|
heartbeat: { status: step.status, ...(childPhase ? { phase: childPhase } : {}) },
|
|
4694
4889
|
});
|
|
4695
|
-
}, ctx, preserveActiveSession);
|
|
4890
|
+
}, ctx, preserveActiveSession, workflowParentModel);
|
|
4696
4891
|
});
|
|
4697
4892
|
workflowResults.push(...result.details.results);
|
|
4698
4893
|
for (const childResult of result.details.results) {
|
|
@@ -4722,7 +4917,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4722
4917
|
}
|
|
4723
4918
|
return child;
|
|
4724
4919
|
},
|
|
4725
|
-
status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession)),
|
|
4920
|
+
status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession, workflowParentModel)),
|
|
4726
4921
|
resolveResume: (reference) => resolveKeyedWorkflowResume(reference, deps.state),
|
|
4727
4922
|
steer: (key, message, options, workflowSignal) => steerWorkflowChildByKey({ state: deps.state, workflowRunId, key, message, options, signal: workflowSignal, resolveRunId: () => workflowChildRunIds.get(key) }),
|
|
4728
4923
|
});
|
|
@@ -4732,15 +4927,16 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4732
4927
|
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, summary, producedChildOutputPaths);
|
|
4733
4928
|
const resultSummary = appendWorkflowOutputWarning(summary, outputWarning);
|
|
4734
4929
|
const workflowUsage = sumResultsUsage(workflowResults);
|
|
4735
|
-
|
|
4736
|
-
|
|
4930
|
+
const workflowChildren = workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState: "completed", inventoryComplete: true, trace: workflow.trace, children: workflow.children, steps: status.steps });
|
|
4931
|
+
status = { ...status, state: "complete", endedAt: Date.now(), workflow: { value: workflow.value, trace: workflow.trace, emits: workflow.emits, console: workflow.console }, workflowChildren, totalTokens: { input: workflowUsage.input, output: workflowUsage.output, total: workflowUsage.input + workflowUsage.output }, totalCost: sumResultsCost(workflowResults) };
|
|
4932
|
+
const receipt = terminalWorkflowReceipt(workflowRunId, "complete", workflow.children, workflowChildren);
|
|
4737
4933
|
let workflowReceipt: { path: string; receipt: WorkflowReceipt } | undefined;
|
|
4738
4934
|
try {
|
|
4739
4935
|
workflowReceipt = { path: writeWorkflowReceipt(asyncDir, receipt), receipt };
|
|
4740
4936
|
} catch (receiptError) {
|
|
4741
4937
|
appendWorkflowEvent({ type: "subagent.workflow.receipt_write_failed", error: `Failed to persist async workflow receipt: ${receiptError instanceof Error ? receiptError.message : String(receiptError)}` });
|
|
4742
4938
|
}
|
|
4743
|
-
if (!writeWorkflowResult({ id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: true, state: "complete", summary: resultSummary, output: resultSummary, results: workflow.children.map((child) => ({ workflowKey: child.key, ...(child.agent ? { agent: child.agent } : {}), ...(child.runId ? { runId: child.runId } : {}), output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, success: child.ok, ...(child.stopped ? { stopped: 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;
|
|
4939
|
+
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 } : {}), output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, success: child.ok, ...(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;
|
|
4744
4940
|
persist();
|
|
4745
4941
|
persistClosed = true;
|
|
4746
4942
|
appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(status.error ? { error: status.error } : {}) });
|
|
@@ -4762,7 +4958,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4762
4958
|
delete step.activityState;
|
|
4763
4959
|
}
|
|
4764
4960
|
}
|
|
4765
|
-
|
|
4961
|
+
const workflowState = state === "paused" ? "paused" : state === "stopped" ? "stopped" : "failed";
|
|
4962
|
+
const workflowChildren = workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState, inventoryComplete: true, trace: partial.trace, children: partial.children, steps: status.steps });
|
|
4963
|
+
status = compactOptional<AsyncStatus>({ ...status, state, stopped: stopped || undefined, activityState: pauseForDetached ? "needs_attention" : undefined, error: workflowFailureMessage(error, workflowRunId, partial.children), endedAt: Date.now(), workflow: { trace: partial.trace, emits: partial.emits, console: partial.console }, workflowChildren });
|
|
4766
4964
|
if (pauseForDetached) {
|
|
4767
4965
|
const promoted = promotePausedWorkflowIfSettled(status);
|
|
4768
4966
|
if (promoted) status = promoted;
|
|
@@ -4773,14 +4971,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4773
4971
|
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, terminalSummary, producedChildOutputPaths);
|
|
4774
4972
|
const resultSummary = appendWorkflowOutputWarning(terminalSummary, outputWarning);
|
|
4775
4973
|
const receiptState: WorkflowReceiptState = status.state === "complete" ? "complete" : status.state === "paused" ? "paused" : status.state === "stopped" ? "stopped" : "failed";
|
|
4776
|
-
const receipt = terminalWorkflowReceipt(workflowRunId, receiptState, partial.children);
|
|
4974
|
+
const receipt = terminalWorkflowReceipt(workflowRunId, receiptState, partial.children, workflowChildren);
|
|
4777
4975
|
let workflowReceipt: { path: string; receipt: WorkflowReceipt } | undefined;
|
|
4778
4976
|
try {
|
|
4779
4977
|
workflowReceipt = { path: writeWorkflowReceipt(asyncDir, receipt), receipt };
|
|
4780
4978
|
} catch (receiptError) {
|
|
4781
4979
|
appendWorkflowEvent({ type: "subagent.workflow.receipt_write_failed", error: `Failed to persist async workflow receipt: ${receiptError instanceof Error ? receiptError.message : String(receiptError)}` });
|
|
4782
4980
|
}
|
|
4783
|
-
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, results: partial.children.map((child) => ({ workflowKey: child.key, ...(child.agent ? { agent: child.agent } : {}), ...(child.runId ? { runId: child.runId } : {}), output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, success: child.ok, ...(child.stopped ? { stopped: 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;
|
|
4981
|
+
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, results: partial.children.map((child) => ({ workflowKey: child.key, ...(child.agent ? { agent: child.agent } : {}), ...(child.runId ? { runId: child.runId } : {}), output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, success: child.ok, ...(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;
|
|
4784
4982
|
persist();
|
|
4785
4983
|
persistClosed = true;
|
|
4786
4984
|
appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(status.error ? { error: status.error } : {}), ...(status.activityState ? { activityState: status.activityState } : {}) });
|
|
@@ -4794,7 +4992,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4794
4992
|
});
|
|
4795
4993
|
return attachWorkflowMission(withRunFanoutBudget({
|
|
4796
4994
|
content: [{ type: "text", text: formatAsyncStartedMessage(`Async workflow [${workflowRunId}]`, ctx.hasUI === true) }],
|
|
4797
|
-
details: { mode: "workflow", runId: workflowRunId, toolCallId, asyncId: workflowRunId, asyncDir, results: [], chatProgress, ...(deps.state.activeAsyncCapacity ? { activeAsyncCapacity: deps.state.activeAsyncCapacity } : {}) },
|
|
4995
|
+
details: { mode: "workflow", runId: workflowRunId, toolCallId, asyncId: workflowRunId, asyncDir, results: [], workflowChildren: status.workflowChildren, chatProgress, ...(deps.state.activeAsyncCapacity ? { activeAsyncCapacity: deps.state.activeAsyncCapacity } : {}) },
|
|
4798
4996
|
}, workflowFanoutBudget));
|
|
4799
4997
|
}
|
|
4800
4998
|
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, ...workflowChildDefaults } = requestParams;
|
|
@@ -4807,20 +5005,23 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4807
5005
|
const workflowResults: SingleResult[] = [];
|
|
4808
5006
|
const workflowChildRunIds = new Map<string, string>();
|
|
4809
5007
|
let liveWorkflow: NonNullable<Details["workflow"]> = { trace: [], emits: [], console: [] };
|
|
5008
|
+
let liveWorkflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: _id, workflowState: "running", inventoryComplete: false });
|
|
4810
5009
|
const workflowDeadlineAt = timeout === undefined ? undefined : Date.now() + timeout;
|
|
4811
5010
|
const workflowCapabilityCeiling = intersectSubagentCapabilityCeilings(requestParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(resolveCurrentSessionId(ctx.sessionManager)));
|
|
4812
5011
|
const sendWorkflowProgress = () => {
|
|
4813
|
-
const update = workflowChatProgressUpdate(_id, chatProgress, liveWorkflow);
|
|
5012
|
+
const update = workflowChatProgressUpdate(_id, chatProgress, liveWorkflow, liveWorkflowChildren);
|
|
4814
5013
|
if (update) onUpdate?.(update);
|
|
4815
5014
|
};
|
|
4816
5015
|
try {
|
|
4817
5016
|
const workflow = await runWorkflowScript({
|
|
4818
5017
|
script: requestParams.workflowScript,
|
|
5018
|
+
...(delegatedWorkflowPermit ? { oneUsePermit: { claim: (key: string) => claimWorkflowChildPermit(delegatedWorkflowPermit, _id, key) } } : {}),
|
|
4819
5019
|
timeoutMs: timeout,
|
|
4820
5020
|
signal,
|
|
4821
5021
|
...(workflowState ? { state: workflowState } : {}),
|
|
4822
5022
|
onTrace: (trace) => {
|
|
4823
5023
|
liveWorkflow = { ...liveWorkflow, trace };
|
|
5024
|
+
liveWorkflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: _id, workflowState: "running", inventoryComplete: false, trace });
|
|
4824
5025
|
sendWorkflowProgress();
|
|
4825
5026
|
},
|
|
4826
5027
|
admit: (calls) => {
|
|
@@ -4835,6 +5036,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4835
5036
|
sendWorkflowProgress();
|
|
4836
5037
|
},
|
|
4837
5038
|
launch: async (key, childParams, workflowSignal, admission) => {
|
|
5039
|
+
if (delegatedWorkflowPermit && admission.batch) throw new Error("Workflow child permit does not support runs.all.");
|
|
5040
|
+
if (delegatedWorkflowPermit && childParams.resume !== undefined) throw new Error("Workflow child permit does not support retained resume.");
|
|
5041
|
+
if (delegatedWorkflowPermit && childParams.async === true) throw new Error("Workflow child permit supports one foreground child only.");
|
|
4838
5042
|
if (workflowUsageBudget.budget && childParams.async === true) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, "workflow usageBudget does not support async runs.run launches."), childParams, deps.state);
|
|
4839
5043
|
const budgetState = usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults));
|
|
4840
5044
|
if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)), childParams, deps.state);
|
|
@@ -4853,6 +5057,15 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4853
5057
|
missionBinding,
|
|
4854
5058
|
deps.asyncByDefault,
|
|
4855
5059
|
);
|
|
5060
|
+
if (delegatedWorkflowPermit) {
|
|
5061
|
+
if (childRequest.async !== false) throw new Error("Workflow child permit supports one foreground child only.");
|
|
5062
|
+
const childCwd = resolveRequestedCwd(workflowCwd, childRequest.cwd);
|
|
5063
|
+
const childAgent = typeof childRequest.agent === "string"
|
|
5064
|
+
? resolveAgentName(childRequest.agent, deps.discoverAgents(childCwd, resolveExecutionAgentScope(childRequest.agentScope)).agents).agent
|
|
5065
|
+
: undefined;
|
|
5066
|
+
if (childAgent?.runner?.type === "external-cli" || childAgent?.runner?.type === "external-job") throw new Error("Workflow child permit supports native Pi children only.");
|
|
5067
|
+
workflowPermitContexts.set(childRequest, { child: { permit: delegatedWorkflowPermit, workflowRunId: _id, childKey: key } });
|
|
5068
|
+
}
|
|
4856
5069
|
workflowLaunchObservers.set(childRequest, (launch) => recordMissionWorkflowChild(missionBinding, _id, key, {
|
|
4857
5070
|
status: "running",
|
|
4858
5071
|
agent: launch.agent,
|
|
@@ -4866,7 +5079,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4866
5079
|
status: progressStatus,
|
|
4867
5080
|
heartbeat: { status: progressStatus, ...(childPhase ? { phase: childPhase } : {}) },
|
|
4868
5081
|
});
|
|
4869
|
-
}, ctx, preserveActiveSession);
|
|
5082
|
+
}, ctx, preserveActiveSession, workflowParentModel);
|
|
4870
5083
|
});
|
|
4871
5084
|
workflowResults.push(...result.details.results);
|
|
4872
5085
|
for (const childResult of result.details.results) {
|
|
@@ -4887,11 +5100,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4887
5100
|
});
|
|
4888
5101
|
return child;
|
|
4889
5102
|
},
|
|
4890
|
-
status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession)),
|
|
5103
|
+
status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession, workflowParentModel)),
|
|
4891
5104
|
resolveResume: (reference) => resolveKeyedWorkflowResume(reference, deps.state),
|
|
4892
5105
|
steer: (key, message, options, workflowSignal) => steerWorkflowChildByKey({ state: deps.state, workflowRunId: _id, key, message, options, signal: workflowSignal, resolveRunId: () => workflowChildRunIds.get(key) }),
|
|
4893
5106
|
});
|
|
4894
|
-
const
|
|
5107
|
+
const workflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: _id, workflowState: "completed", inventoryComplete: true, trace: workflow.trace, children: workflow.children });
|
|
5108
|
+
const receipt = terminalWorkflowReceipt(_id, "complete", workflow.children, workflowChildren);
|
|
4895
5109
|
const traceLines = workflow.trace.map((entry) => `- ${entry.operation} ${entry.key}: ${entry.state}${entry.runId ? ` (${entry.runId})` : ""}${entry.durationMs !== undefined ? ` in ${entry.durationMs}ms` : ""}${entry.error ? ` — ${entry.error}` : ""}`);
|
|
4896
5110
|
const sections = ["Workflow completed.", `Return:\n${formatWorkflowValue(workflow.value)}`];
|
|
4897
5111
|
if (workflow.emits.length > 0) sections.push(`Emitted:\n${workflow.emits.map(formatWorkflowValue).join("\n")}`);
|
|
@@ -4902,11 +5116,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4902
5116
|
const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
|
|
4903
5117
|
return attachWorkflowMission(withRunFanoutBudget({
|
|
4904
5118
|
content: [{ type: "text", text: displayText }],
|
|
4905
|
-
details: compactOptional<Details>({ mode: "workflow", runId: _id, results: workflow.children.flatMap((child) => (child.results ?? []) as SingleResult[]), totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { value: workflow.value, trace: workflow.trace, emits: workflow.emits, console: workflow.console, receipt }, chatProgress }),
|
|
5119
|
+
details: compactOptional<Details>({ mode: "workflow", runId: _id, results: workflow.children.flatMap((child) => (child.results ?? []) as SingleResult[]), workflowChildren, totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { value: workflow.value, trace: workflow.trace, emits: workflow.emits, console: workflow.console, receipt }, chatProgress }),
|
|
4906
5120
|
}, workflowFanoutBudget));
|
|
4907
5121
|
} catch (error) {
|
|
4908
5122
|
const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
|
|
4909
|
-
const text = error
|
|
5123
|
+
const text = workflowFailureMessage(error, _id, partial.children);
|
|
4910
5124
|
const traceLines = partial.trace.map((entry) => `- ${entry.operation} ${entry.key}: ${entry.state}${entry.runId ? ` (${entry.runId})` : ""}${entry.error ? ` — ${entry.error}` : ""}`);
|
|
4911
5125
|
const sections = [`Workflow failed: ${text}`];
|
|
4912
5126
|
if (partial.emits.length > 0) sections.push(`Emitted:\n${partial.emits.map(formatWorkflowValue).join("\n")}`);
|
|
@@ -4915,11 +5129,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4915
5129
|
const workflowText = sections.join("\n\n");
|
|
4916
5130
|
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, workflowText, producedChildOutputPaths);
|
|
4917
5131
|
const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
|
|
4918
|
-
const
|
|
5132
|
+
const workflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: _id, workflowState: "failed", inventoryComplete: true, trace: partial.trace, children: partial.children });
|
|
5133
|
+
const receipt = terminalWorkflowReceipt(_id, "failed", partial.children, workflowChildren);
|
|
4919
5134
|
return attachWorkflowMission(withRunFanoutBudget({
|
|
4920
5135
|
content: [{ type: "text", text: displayText }],
|
|
4921
5136
|
isError: true,
|
|
4922
|
-
details: compactOptional<Details>({ mode: "workflow", runId: _id, results: partial.children.flatMap((child) => (child.results ?? []) as SingleResult[]), totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { trace: partial.trace, emits: partial.emits, console: partial.console, receipt }, chatProgress }),
|
|
5137
|
+
details: compactOptional<Details>({ mode: "workflow", runId: _id, results: partial.children.flatMap((child) => (child.results ?? []) as SingleResult[]), workflowChildren, totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { trace: partial.trace, emits: partial.emits, console: partial.console, receipt }, chatProgress }),
|
|
4923
5138
|
}, workflowFanoutBudget));
|
|
4924
5139
|
}
|
|
4925
5140
|
}
|
|
@@ -4933,9 +5148,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4933
5148
|
try {
|
|
4934
5149
|
requestSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
4935
5150
|
requestPiSessionId = ctx.sessionManager.getSessionId() ?? undefined;
|
|
4936
|
-
requestParentModel =
|
|
4937
|
-
?
|
|
4938
|
-
:
|
|
5151
|
+
requestParentModel = parentModelOverride !== undefined
|
|
5152
|
+
? parentModelOverride ?? undefined
|
|
5153
|
+
: preserveActiveSession
|
|
5154
|
+
? normalizeParentModel(ctx.model)
|
|
5155
|
+
: rememberParentModel(deps.state, requestSessionId, ctx.model);
|
|
4939
5156
|
} catch (error) {
|
|
4940
5157
|
if (action?.toLowerCase() !== "doctor" && action?.toLowerCase() !== "guide") throw error;
|
|
4941
5158
|
requestParentModel = normalizeParentModel(ctx.model);
|
|
@@ -5160,7 +5377,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5160
5377
|
}
|
|
5161
5378
|
const spawnBudget = getSpawnBudgetSnapshot(deps.state, deps.config, currentSessionId);
|
|
5162
5379
|
const activeAsyncCapacity = currentSessionId
|
|
5163
|
-
? getActiveAsyncCapacitySnapshot(currentSessionId, resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession), { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) })
|
|
5380
|
+
? getActiveAsyncCapacitySnapshot(currentSessionId, resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession), { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })
|
|
5164
5381
|
: { used: 0, limit: resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession) ?? 0 };
|
|
5165
5382
|
deps.state.activeAsyncCapacity = activeAsyncCapacity;
|
|
5166
5383
|
return {
|
|
@@ -5205,10 +5422,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5205
5422
|
if (paramsWithResolvedCwd.view) {
|
|
5206
5423
|
return withBudget({ content: [{ type: "text", text: "action='debug.run' does not support status views." }], isError: true, details: { mode: "management", results: [] } });
|
|
5207
5424
|
}
|
|
5208
|
-
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots })));
|
|
5425
|
+
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots, abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })));
|
|
5209
5426
|
}
|
|
5210
5427
|
if (paramsWithResolvedCwd.view === "fleet") {
|
|
5211
|
-
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots })));
|
|
5428
|
+
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots, abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })));
|
|
5212
5429
|
}
|
|
5213
5430
|
if (targetRunId) {
|
|
5214
5431
|
try {
|
|
@@ -5239,7 +5456,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5239
5456
|
});
|
|
5240
5457
|
}
|
|
5241
5458
|
}
|
|
5242
|
-
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots })));
|
|
5459
|
+
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots, abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })));
|
|
5243
5460
|
}
|
|
5244
5461
|
if (action === "resume") {
|
|
5245
5462
|
return resumeAsyncRun(omitUndefinedProperties({ params: paramsWithResolvedCwd, requestCwd, ctx, deps, parentModel: requestParentModel, signal }));
|
|
@@ -5565,7 +5782,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5565
5782
|
const parentSessionFile = ctx.sessionManager.getSessionFile() ?? null;
|
|
5566
5783
|
const discovered = deps.discoverAgents(effectiveCwd, scope);
|
|
5567
5784
|
const discoveredAgents = discovered.agents;
|
|
5568
|
-
const
|
|
5785
|
+
const unknownAgentDiagnosticContext = diagnosticContextFromDiscovery(discovered, effectiveCwd, scope);
|
|
5786
|
+
const canonicalParams = canonicalizeExecutionParams(effectiveParams, discoveredAgents, discovered.agentDiagnostics, unknownAgentDiagnosticContext);
|
|
5569
5787
|
if (canonicalParams.error) return buildRequestedModeError(effectiveParams, canonicalParams.error);
|
|
5570
5788
|
effectiveParams = canonicalParams.params!;
|
|
5571
5789
|
const modelScope = discovered.modelScope;
|
|
@@ -5614,6 +5832,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5614
5832
|
hasTasks,
|
|
5615
5833
|
hasSingle,
|
|
5616
5834
|
allowClarifyTaskPrompt,
|
|
5835
|
+
unknownAgentDiagnosticContext,
|
|
5617
5836
|
);
|
|
5618
5837
|
if (validationError) return validationError;
|
|
5619
5838
|
|
|
@@ -5628,6 +5847,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5628
5847
|
if (spawnPreflight.error) return spawnBudgetErrorResult(spawnPreflight.error, foregroundMode);
|
|
5629
5848
|
|
|
5630
5849
|
let forkSessionFileForIndex: (idx?: number) => string | undefined = () => undefined;
|
|
5850
|
+
let prepareForkSessionForIndex: (idx?: number) => Promise<void> = async () => {};
|
|
5631
5851
|
let forkThinkingOverrideForIndex: (idx?: number) => AgentConfig["thinking"] | undefined = () => undefined;
|
|
5632
5852
|
let prepareForkThinking = (_agentName: string, _index: number, _modelOverride?: string, _modelOverrideFromParent?: boolean): void => {};
|
|
5633
5853
|
const forkThinkingRequirements = new Map<number, boolean>();
|
|
@@ -5663,9 +5883,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5663
5883
|
|| candidates.some((candidate) => forkedChildRequiresThinkingOff(candidate, forkAvailableModels, parentModel?.provider)),
|
|
5664
5884
|
);
|
|
5665
5885
|
};
|
|
5886
|
+
const pruneSession = contextPolicy.usesFork && deps.config.forkContext?.mode === "pruned"
|
|
5887
|
+
? await createPrunedForkSessionWriter(ctx, deps.config.forkContext, signal)
|
|
5888
|
+
: undefined;
|
|
5666
5889
|
const forkContextResolver = createForkContextResolver(ctx.sessionManager, contextPolicy.usesFork ? "fork" : undefined, {
|
|
5667
5890
|
forceThinkingOffForIndex: (index) => forkThinkingRequirements.get(index) ?? true,
|
|
5891
|
+
...(pruneSession ? { pruneSession } : {}),
|
|
5668
5892
|
});
|
|
5893
|
+
prepareForkSessionForIndex = forkContextResolver.prepareSessionForIndex;
|
|
5669
5894
|
forkSessionFileForIndex = forkContextResolver.sessionFileForIndex;
|
|
5670
5895
|
forkThinkingOverrideForIndex = forkContextResolver.thinkingOverrideForIndex;
|
|
5671
5896
|
} catch (error) {
|
|
@@ -5709,8 +5934,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5709
5934
|
runId: asyncRunId,
|
|
5710
5935
|
kind: "runner",
|
|
5711
5936
|
asyncDir: path.join(DIRS.async, asyncRunId),
|
|
5712
|
-
}, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) });
|
|
5713
|
-
deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(requestSessionId, activeLimit, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) });
|
|
5937
|
+
}, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) });
|
|
5938
|
+
deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(requestSessionId, activeLimit, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) });
|
|
5714
5939
|
} catch (error) {
|
|
5715
5940
|
if (error instanceof ActiveAsyncCapacityError) {
|
|
5716
5941
|
deps.state.activeAsyncCapacity = error.snapshot;
|
|
@@ -5773,6 +5998,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5773
5998
|
prepareForkThinking(agentName, idx, modelOverride, modelOverrideFromParent);
|
|
5774
5999
|
return forkSessionFileForIndex(idx);
|
|
5775
6000
|
};
|
|
6001
|
+
const prepareForkSessionForTask: PrepareForkSessionForTask = async (agentName, idx = 0, modelOverride, modelOverrideFromParent) => {
|
|
6002
|
+
if (!shouldForkAgent(contextPolicy, agentName)) return;
|
|
6003
|
+
prepareForkThinking(agentName, idx, modelOverride, modelOverrideFromParent);
|
|
6004
|
+
await prepareForkSessionForIndex(idx);
|
|
6005
|
+
};
|
|
5776
6006
|
const forkThinkingOverrideForTask: ForkThinkingOverrideForTask = (agentName, idx = 0, modelOverride, modelOverrideFromParent) => {
|
|
5777
6007
|
if (!shouldForkAgent(contextPolicy, agentName)) return delegatedThinkingOverride;
|
|
5778
6008
|
prepareForkThinking(agentName, idx, modelOverride, modelOverrideFromParent);
|
|
@@ -5785,8 +6015,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5785
6015
|
const childSessionFileForIndex = (idx?: number) =>
|
|
5786
6016
|
path.join(sessionDirForIndex(idx), "session.jsonl");
|
|
5787
6017
|
try {
|
|
5788
|
-
if (!(effectiveParams.clarify === true && ctx.hasUI)) {
|
|
5789
|
-
preflightForkSessionsForStaticTasks(effectiveParams, contextPolicy,
|
|
6018
|
+
if (!(effectiveParams.clarify === true && ctx.hasUI) || deps.config.forkContext?.mode === "pruned") {
|
|
6019
|
+
await preflightForkSessionsForStaticTasks(effectiveParams, contextPolicy, prepareForkSessionForTask, deps.config.chain?.dynamicFanout?.maxItems);
|
|
5790
6020
|
}
|
|
5791
6021
|
} catch (error) {
|
|
5792
6022
|
activeAsyncCapacity?.rollback();
|
|
@@ -5859,6 +6089,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5859
6089
|
signal,
|
|
5860
6090
|
onUpdate: onUpdateWithContext,
|
|
5861
6091
|
agents,
|
|
6092
|
+
unknownAgentDiagnosticContext,
|
|
5862
6093
|
recoveryAgents: discoveredAgents,
|
|
5863
6094
|
runId,
|
|
5864
6095
|
shareEnabled,
|
|
@@ -5891,6 +6122,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5891
6122
|
runFanoutBudget,
|
|
5892
6123
|
topLevelAsyncCapacityEligible,
|
|
5893
6124
|
activeAsyncCapacity,
|
|
6125
|
+
workflowChildPermitLaunch,
|
|
5894
6126
|
});
|
|
5895
6127
|
|
|
5896
6128
|
const foregroundDescription = selectedAgentNames.length === 1
|
|
@@ -6141,7 +6373,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6141
6373
|
if (!normalized.ok) {
|
|
6142
6374
|
return Promise.resolve({ content: [{ type: "text", text: normalized.error }], isError: true, details: { mode: normalized.mode, results: [] } });
|
|
6143
6375
|
}
|
|
6144
|
-
|
|
6376
|
+
const loaded = loadWorkflowScriptPath(normalized.params, ctx.cwd);
|
|
6377
|
+
if (loaded.error) {
|
|
6378
|
+
return Promise.resolve({ content: [{ type: "text", text: loaded.error }], isError: true, details: { mode: normalized.params.action ? "management" : "workflow", results: [] } });
|
|
6379
|
+
}
|
|
6380
|
+
return executeWithSingleDispatchGuard(id, loaded.params!, signal, onUpdate, ctx);
|
|
6145
6381
|
};
|
|
6146
6382
|
|
|
6147
6383
|
const executeDelegated = async (
|
|
@@ -6155,13 +6391,17 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6155
6391
|
const privateParams = delegatedParams as SubagentParamsLike & {
|
|
6156
6392
|
delegatedThinkingOverride?: AgentConfig["thinking"];
|
|
6157
6393
|
delegatedAllowZeroToolBudget?: true;
|
|
6394
|
+
delegatedWorkflowPermit?: WorkflowChildPermit;
|
|
6158
6395
|
};
|
|
6159
6396
|
const thinkingOverride = privateParams.delegatedThinkingOverride;
|
|
6160
6397
|
const allowZeroToolBudget = privateParams.delegatedAllowZeroToolBudget === true;
|
|
6398
|
+
const workflowPermit = privateParams.delegatedWorkflowPermit;
|
|
6161
6399
|
delete privateParams.delegatedThinkingOverride;
|
|
6162
6400
|
delete privateParams.delegatedAllowZeroToolBudget;
|
|
6401
|
+
delete privateParams.delegatedWorkflowPermit;
|
|
6163
6402
|
if (thinkingOverride !== undefined) delegatedThinkingOverrides.set(delegatedParams, thinkingOverride);
|
|
6164
6403
|
if (allowZeroToolBudget) delegatedZeroToolBudgets.add(delegatedParams);
|
|
6404
|
+
if (workflowPermit) workflowPermitContexts.set(delegatedParams, { root: workflowPermit });
|
|
6165
6405
|
delegatedExecutions.add(delegatedParams);
|
|
6166
6406
|
return execute(id, delegatedParams, signal, onUpdate, ctx);
|
|
6167
6407
|
};
|