pi-subagents 0.56.0 → 0.58.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 +92 -0
- 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 +124 -21
- package/docs/configuration.md +37 -0
- package/docs/extension-api.md +41 -2
- package/docs/models.md +3 -3
- package/docs/observability.md +8 -7
- package/docs/tool-reference.md +14 -3
- package/docs/workflows.md +44 -0
- package/package.json +1 -1
- package/skills/pi-subagents/SKILL.md +2 -0
- package/skills/pi-subagents/references/execution-controls.md +2 -2
- package/skills/pi-subagents/references/management-authoring-rpc.md +1 -0
- package/skills/pi-subagents/references/prompting-and-roles.md +2 -2
- package/src/agents/agent-management.ts +36 -5
- package/src/agents/agent-refinements.ts +4 -4
- package/src/agents/agent-serializer.ts +5 -0
- package/src/agents/agents.ts +257 -51
- package/src/agents/builtin-names.ts +6 -0
- package/src/agents/runtime-agent-events.ts +70 -0
- package/src/agents/runtime-agent-registry.ts +18 -4
- package/src/api/agents.ts +10 -5
- package/src/api/preflight.ts +28 -3
- package/src/extension/config.ts +70 -0
- package/src/extension/doctor.ts +3 -3
- package/src/extension/index.ts +33 -8
- package/src/extension/public-execution.ts +29 -13
- package/src/extension/rpc.ts +55 -19
- package/src/extension/schemas.ts +6 -5
- package/src/extension/tool-description.ts +18 -12
- 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 +51 -3
- package/src/runs/background/active-async-capacity.ts +77 -10
- package/src/runs/background/async-execution.ts +127 -19
- package/src/runs/background/async-job-tracker.ts +5 -0
- package/src/runs/background/async-resume.ts +6 -2
- package/src/runs/background/async-retention.ts +20 -3
- package/src/runs/background/async-status.ts +7 -0
- package/src/runs/background/chain-append.ts +2 -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/notify.ts +26 -3
- package/src/runs/background/result-delivery-ownership.ts +45 -0
- package/src/runs/background/result-files.ts +27 -14
- package/src/runs/background/result-watcher.ts +36 -15
- 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 +239 -56
- package/src/runs/background/wait-completions.ts +4 -0
- package/src/runs/foreground/execution.ts +92 -11
- package/src/runs/foreground/foreground-control.ts +6 -0
- package/src/runs/foreground/foreground-history.ts +22 -1
- package/src/runs/foreground/subagent-executor.ts +322 -102
- package/src/runs/foreground/workflow-detach-reconcile.ts +144 -18
- 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 +4 -3
- package/src/runs/shared/cursor-agent-adapter.ts +114 -0
- package/src/runs/shared/dynamic-fanout.ts +3 -3
- 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 +348 -55
- package/src/runs/shared/fast-mode-extension.ts +5 -5
- package/src/runs/shared/launch-cwd.ts +16 -0
- package/src/runs/shared/long-running-guard.ts +2 -1
- package/src/runs/shared/mcp-config-sources.ts +386 -0
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +155 -42
- package/src/runs/shared/model-exclusions.ts +69 -7
- package/src/runs/shared/model-fallback.ts +39 -5
- package/src/runs/shared/mutation-evidence.ts +7 -2
- 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 +8 -1
- package/src/runs/shared/pi-args.ts +61 -6
- package/src/runs/shared/process-signal.ts +13 -0
- package/src/runs/shared/run-history.ts +21 -1
- package/src/runs/shared/single-output.ts +17 -0
- package/src/runs/shared/subagent-prompt-runtime.ts +85 -9
- package/src/shared/fork-context.ts +21 -0
- package/src/shared/formatters.ts +13 -1
- package/src/shared/launch-contract.ts +4 -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 +10 -2
- package/src/shared/shortcuts.ts +17 -0
- package/src/shared/types.ts +160 -10
- package/src/shared/utils.ts +6 -29
- 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/slash/subagents-admin.ts +3 -0
- 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 +55 -21
- package/src/workflows/scripted-workflow.ts +299 -31
- package/src/workflows/workflow-child-summary.ts +117 -0
- package/src/workflows/workflow-receipt.ts +155 -5
|
@@ -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";
|
|
@@ -74,8 +75,8 @@ import { usageBudgetExceededMessage, usageBudgetState, validateUsageBudgetConfig
|
|
|
74
75
|
import { intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
|
|
75
76
|
import { isAgentContractV1 } from "../shared/agent-contract.ts";
|
|
76
77
|
import { normalizeExtensionBindings, type ExtensionBindings } from "../shared/extension-bindings.ts";
|
|
77
|
-
import { finalizeSingleOutput, injectSingleOutputInstruction, normalizeSingleOutputOverride, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
|
|
78
|
-
import { cleanupStructuredOutputRuntime, createStructuredOutputRuntime } from "../shared/structured-output.ts";
|
|
78
|
+
import { finalizeSingleOutput, injectSingleOutputInstruction, normalizeSingleOutputOverride, outputPathMappingFromTask, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
|
|
79
|
+
import { assertJsonSchemaObject, cleanupStructuredOutputRuntime, createStructuredOutputRuntime } from "../shared/structured-output.ts";
|
|
79
80
|
import { compactForegroundDetails, getSingleResultOutput, mapConcurrent, readStatus, resolveChildCwd, sumResultsCost, sumResultsUsage } from "../../shared/utils.ts";
|
|
80
81
|
import { createTaskMutationArbiter } from "../shared/llm-intent-arbiter.ts";
|
|
81
82
|
import { DEFAULT_GLOBAL_CONCURRENCY_LIMIT, Semaphore } from "../shared/parallel-utils.ts";
|
|
@@ -113,6 +114,7 @@ import { isStoppableAsyncStatusStep, resolveAsyncStatusChild } from "../shared/c
|
|
|
113
114
|
import { inspectSubagentStatus } from "../background/run-status.ts";
|
|
114
115
|
import { getExternalJobProvider } from "../../api/external-job-provider.ts";
|
|
115
116
|
import { externalJobFollowUpRequestDigest, externalJobFollowUpRequestId, externalJobFollowUpRunId, externalJobPromptDigest, externalJobStableJson } from "../shared/external-job-runner.ts";
|
|
117
|
+
import { externalCliReceiptMetadata, normalizeExternalCliRunnerStatus } from "../shared/external-cli-contract.ts";
|
|
116
118
|
import { applyForceTopLevelAsyncOverride } from "../background/top-level-async.ts";
|
|
117
119
|
import { handleMissionAction, MISSION_ACTIONS } from "../../missions/actions.ts";
|
|
118
120
|
import { attachMissionToLaunchResult, prepareMissionLaunch, writeMissionAsyncBinding, type MissionLaunchBinding } from "../../missions/lifecycle.ts";
|
|
@@ -122,9 +124,11 @@ import { createMissionWorkflowState } from "../../missions/workflow-state.ts";
|
|
|
122
124
|
import { resolveAuthorityDecision } from "../../policy/authority.ts";
|
|
123
125
|
import { handleHerdrInspectorAction, HERDR_INSPECTOR_ACTIONS } from "../../inspectors/herdr/actions.ts";
|
|
124
126
|
import { handleHerdrProjectPaneAction, HERDR_PROJECT_PANE_ACTIONS } from "../../inspectors/herdr/project-panes.ts";
|
|
125
|
-
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";
|
|
126
128
|
import { buildWorkflowReceipt, resolveWorkflowReceiptResumeEntry, writeWorkflowReceipt, type WorkflowReceipt, type WorkflowReceiptState } from "../../workflows/workflow-receipt.ts";
|
|
129
|
+
import { workflowChildSummary } from "../../workflows/workflow-child-summary.ts";
|
|
127
130
|
import { resolveWorkflowChatProgress, type WorkflowChatProgressProjection } from "../../workflows/chat-progress.ts";
|
|
131
|
+
import { claimWorkflowChildPermit, validateWorkflowChildPermitRoot, type WorkflowChildPermit, type WorkflowChildPermitContext } from "../../shared/workflow-child-permit.ts";
|
|
128
132
|
import {
|
|
129
133
|
cleanupWorktrees,
|
|
130
134
|
createWorktrees,
|
|
@@ -146,6 +150,7 @@ import {
|
|
|
146
150
|
type ControlEvent,
|
|
147
151
|
type Details,
|
|
148
152
|
type ExtensionConfig,
|
|
153
|
+
type ForegroundResumeChild,
|
|
149
154
|
type ForegroundRunControl,
|
|
150
155
|
type IntercomBridgeConfig,
|
|
151
156
|
type IntercomEventBus,
|
|
@@ -153,6 +158,7 @@ import {
|
|
|
153
158
|
type MaxOutputConfig,
|
|
154
159
|
type NestedRouteInfo,
|
|
155
160
|
type NestedRunSummary,
|
|
161
|
+
type OutputMode,
|
|
156
162
|
type ResolvedControlConfig,
|
|
157
163
|
type ResolvedTurnBudget,
|
|
158
164
|
type ResolvedToolBudget,
|
|
@@ -163,6 +169,7 @@ import {
|
|
|
163
169
|
type ToolBudgetConfig,
|
|
164
170
|
type TurnBudgetConfig,
|
|
165
171
|
type UsageBudgetConfig,
|
|
172
|
+
type WorkflowTerminalOutcome,
|
|
166
173
|
type SubagentRunMode,
|
|
167
174
|
type SubagentState,
|
|
168
175
|
DIRS,
|
|
@@ -298,6 +305,7 @@ export interface SubagentParamsLike {
|
|
|
298
305
|
steeringRecovery?: boolean;
|
|
299
306
|
mode?: SteerDeliveryMode;
|
|
300
307
|
workflowScript?: string;
|
|
308
|
+
workflowScriptPath?: string;
|
|
301
309
|
chatProgress?: "auto" | "off" | "live-card";
|
|
302
310
|
isolation?: "none" | "worktree";
|
|
303
311
|
step?: ChainStep;
|
|
@@ -397,7 +405,7 @@ interface ExecutorDeps {
|
|
|
397
405
|
tempArtifactsDir: string;
|
|
398
406
|
getSubagentSessionRoot: (parentSessionFile: string | null) => string;
|
|
399
407
|
expandTilde: (p: string) => string;
|
|
400
|
-
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[]; agentDiagnostics?: AgentDiscoveryDiagnostic[]; modelScope?: ModelScopeConfig; maxThinking?: AgentConfig["maxThinking"] };
|
|
408
|
+
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[]; agentDiagnostics?: AgentDiscoveryDiagnostic[]; modelScope?: ModelScopeConfig; maxThinking?: AgentConfig["maxThinking"]; cwd?: string; scope?: AgentScope; directories?: UnknownAgentDiagnosticContext["directories"] };
|
|
401
409
|
allowMutatingManagementActions?: boolean;
|
|
402
410
|
activateSupervisorTransport?: () => void;
|
|
403
411
|
refreshResultDelivery?: () => void;
|
|
@@ -405,15 +413,19 @@ interface ExecutorDeps {
|
|
|
405
413
|
}
|
|
406
414
|
|
|
407
415
|
type ForkSessionFileForTask = (agentName: string, idx?: number, modelOverride?: string, modelOverrideFromParent?: boolean) => string | undefined;
|
|
416
|
+
type PrepareForkSessionForTask = (agentName: string, idx?: number, modelOverride?: string, modelOverrideFromParent?: boolean) => Promise<void>;
|
|
408
417
|
type ForkThinkingOverrideForTask = (agentName: string, idx?: number, modelOverride?: string, modelOverrideFromParent?: boolean) => AgentConfig["thinking"] | undefined;
|
|
409
418
|
|
|
410
419
|
interface ExecutionContextData {
|
|
411
420
|
params: SubagentParamsLike;
|
|
412
421
|
effectiveCwd: string;
|
|
422
|
+
requestedCwd?: string;
|
|
413
423
|
ctx: ExtensionContext;
|
|
414
424
|
signal: AbortSignal;
|
|
415
425
|
onUpdate?: (r: AgentToolResult<Details>) => void;
|
|
416
426
|
agents: AgentConfig[];
|
|
427
|
+
/** Exact filesystem discovery provenance retained through defensive execution backstops. */
|
|
428
|
+
unknownAgentDiagnosticContext: UnknownAgentDiagnosticContext;
|
|
417
429
|
/** Discovered agent definitions before per-run bridge injection. */
|
|
418
430
|
recoveryAgents: AgentConfig[];
|
|
419
431
|
runId: string;
|
|
@@ -449,12 +461,27 @@ interface ExecutionContextData {
|
|
|
449
461
|
runFanoutBudget: RunFanoutBudgetDescriptor;
|
|
450
462
|
topLevelAsyncCapacityEligible: boolean;
|
|
451
463
|
activeAsyncCapacity?: ActiveAsyncCapacityHandle;
|
|
464
|
+
workflowChildPermitLaunch?: WorkflowChildPermitContext;
|
|
452
465
|
}
|
|
453
466
|
|
|
454
467
|
function resolveRequestedCwd(runtimeCwd: string, requestedCwd: string | undefined): string {
|
|
455
468
|
return requestedCwd ? path.resolve(runtimeCwd, requestedCwd) : runtimeCwd;
|
|
456
469
|
}
|
|
457
470
|
|
|
471
|
+
function loadWorkflowScriptPath(params: SubagentParamsLike, runtimeCwd: string): { params?: SubagentParamsLike; error?: string } {
|
|
472
|
+
if (params.workflowScriptPath === undefined) return { params };
|
|
473
|
+
const scriptPath = path.resolve(resolveRequestedCwd(runtimeCwd, params.cwd), params.workflowScriptPath);
|
|
474
|
+
let workflowScript: string;
|
|
475
|
+
try {
|
|
476
|
+
workflowScript = fs.readFileSync(scriptPath, "utf8");
|
|
477
|
+
} catch (error) {
|
|
478
|
+
return { error: `Failed to read workflowScriptPath '${scriptPath}': ${error instanceof Error ? error.message : String(error)}` };
|
|
479
|
+
}
|
|
480
|
+
if (!workflowScript.trim()) return { error: `workflowScriptPath file '${scriptPath}' is empty.` };
|
|
481
|
+
const { workflowScriptPath: _workflowScriptPath, ...rest } = params;
|
|
482
|
+
return { params: { ...rest, workflowScript } };
|
|
483
|
+
}
|
|
484
|
+
|
|
458
485
|
function removeForegroundControlIfIdle(state: SubagentState, runId: string): boolean {
|
|
459
486
|
const control = state.foregroundControls.get(runId);
|
|
460
487
|
if (control && (!foregroundSchedulingSettled(control) || (control.activeChildren?.size ?? 0) > 0)) return false;
|
|
@@ -543,6 +570,7 @@ function withSpawnBudgetStatus(
|
|
|
543
570
|
const activeAsyncCapacity = sessionId
|
|
544
571
|
? getActiveAsyncCapacitySnapshot(sessionId, resolveMaxActiveAsyncRunsPerSession(config.maxActiveAsyncRunsPerSession), {
|
|
545
572
|
liveWorkflowRunIds: new Set(state.workflowControllers?.keys() ?? []),
|
|
573
|
+
abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(config.capacity?.abandonedSlotReleaseAfterMs),
|
|
546
574
|
})
|
|
547
575
|
: { used: 0, limit: resolveMaxActiveAsyncRunsPerSession(config.maxActiveAsyncRunsPerSession) ?? 0 };
|
|
548
576
|
state.activeAsyncCapacity = activeAsyncCapacity;
|
|
@@ -645,11 +673,13 @@ function foregroundChildActivityFromProgress(progress: SingleResult["progress"]
|
|
|
645
673
|
...(progress?.currentPath ? { currentPath: progress.currentPath } : {}),
|
|
646
674
|
...(progress?.turnCount !== undefined ? { turnCount: progress.turnCount } : {}),
|
|
647
675
|
...(progress?.tokens !== undefined ? { tokens: progress.tokens } : {}),
|
|
676
|
+
...(progress?.window !== undefined ? { window: progress.window } : {}),
|
|
677
|
+
...(progress?.windowPeak !== undefined ? { windowPeak: progress.windowPeak } : {}),
|
|
648
678
|
...(progress?.toolCount !== undefined ? { toolCount: progress.toolCount } : {}),
|
|
649
679
|
};
|
|
650
680
|
}
|
|
651
681
|
|
|
652
|
-
function rememberForegroundRun(state: SubagentState, input: { runId: string; mode: "single" | "parallel" | "chain"; cwd: string; sessionId: string | null; results: SingleResult[]; extensionBindings?: ExtensionBindings }): void {
|
|
682
|
+
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 {
|
|
653
683
|
state.foregroundRuns ??= new Map();
|
|
654
684
|
const previous = state.foregroundRuns.get(input.runId);
|
|
655
685
|
const updatedAt = Date.now();
|
|
@@ -660,6 +690,13 @@ function rememberForegroundRun(state: SubagentState, input: { runId: string; mod
|
|
|
660
690
|
...(input.sessionId ? { sessionId: input.sessionId } : {}),
|
|
661
691
|
updatedAt,
|
|
662
692
|
children: input.results.map((result, index) => {
|
|
693
|
+
const resumeContract = omitUndefinedProperties({
|
|
694
|
+
outputSchema: input.params.outputSchema,
|
|
695
|
+
agentContract: input.params.agentContract,
|
|
696
|
+
acceptance: input.params.acceptance,
|
|
697
|
+
output: input.effectiveOutput,
|
|
698
|
+
outputMode: input.effectiveOutputMode,
|
|
699
|
+
});
|
|
663
700
|
const child = {
|
|
664
701
|
agent: result.agent,
|
|
665
702
|
index,
|
|
@@ -690,6 +727,7 @@ function rememberForegroundRun(state: SubagentState, input: { runId: string; mod
|
|
|
690
727
|
...(result.transcriptError ? { transcriptError: result.transcriptError } : {}),
|
|
691
728
|
...(result.detachedReason ? { detachedReason: result.detachedReason } : {}),
|
|
692
729
|
...(result.acceptance ? { acceptance: result.acceptance } : {}),
|
|
730
|
+
...(Object.keys(resumeContract).length ? { resumeContract } : {}),
|
|
693
731
|
...(result.launchContractDigest ? { launchContractDigest: result.launchContractDigest } : {}),
|
|
694
732
|
...(input.extensionBindings ? { extensionBindings: input.extensionBindings } : {}),
|
|
695
733
|
...(result.launchResolvedExtensions ? { launchResolvedExtensions: result.launchResolvedExtensions } : {}),
|
|
@@ -810,7 +848,7 @@ function updateRememberedForegroundChild(state: SubagentState, input: { runId: s
|
|
|
810
848
|
});
|
|
811
849
|
}
|
|
812
850
|
|
|
813
|
-
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; extensionBindings?: ExtensionBindings; capabilityCeiling?: ResolvedSubagentCapabilityCeiling } | undefined {
|
|
851
|
+
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 {
|
|
814
852
|
const requested = (params.id ?? params.runId)?.trim();
|
|
815
853
|
if (!requested || !state.foregroundRuns?.size || !state.currentSessionId) return undefined;
|
|
816
854
|
const direct = state.foregroundRuns.get(requested);
|
|
@@ -841,6 +879,7 @@ function resolveForegroundResumeTarget(params: SubagentParamsLike, state: Subage
|
|
|
841
879
|
...(child.model ? { model: child.model } : {}),
|
|
842
880
|
...(child.thinking ? { thinking: child.thinking } : {}),
|
|
843
881
|
...(child.launchContractDigest ? { launchContractDigest: child.launchContractDigest } : {}),
|
|
882
|
+
...(child.resumeContract ? { resumeContract: child.resumeContract } : {}),
|
|
844
883
|
...(child.extensionBindings ? { extensionBindings: normalizeExtensionBindings(child.extensionBindings)!.value } : {}),
|
|
845
884
|
...(child.capabilityCeiling ? { capabilityCeiling: child.capabilityCeiling } : {}),
|
|
846
885
|
};
|
|
@@ -1239,6 +1278,7 @@ function appendStepToAsyncChain(input: {
|
|
|
1239
1278
|
agents,
|
|
1240
1279
|
ctx: asyncCtx,
|
|
1241
1280
|
availableModels: input.ctx.modelRegistry.getAvailable().map(toModelInfo),
|
|
1281
|
+
unknownAgentDiagnosticContext: diagnosticContextFromDiscovery(discoveredForAppend, input.requestCwd, scope),
|
|
1242
1282
|
cwd: status.cwd ?? input.requestCwd,
|
|
1243
1283
|
chainSkills,
|
|
1244
1284
|
dynamicFanoutMaxItems: input.deps.config.chain?.dynamicFanout?.maxItems,
|
|
@@ -1514,9 +1554,14 @@ function externalJobFollowUpStarted(input: { sourceRunId: string; runId: string;
|
|
|
1514
1554
|
function externalRunnerControlError(asyncDir: string, action: "steer" | "resume"): AgentToolResult<Details> | undefined {
|
|
1515
1555
|
const status = readStatus(asyncDir);
|
|
1516
1556
|
if (!status?.steps?.length || !status.steps.every((step) => step.runner?.type === "external-cli" || step.runner?.type === "external-job")) return undefined;
|
|
1517
|
-
const
|
|
1518
|
-
|
|
1519
|
-
|
|
1557
|
+
const externalCli = normalizeExternalCliRunnerStatus(status.steps.find((step) => step.runner?.type === "external-cli")?.runner);
|
|
1558
|
+
const message = externalCli
|
|
1559
|
+
? action === "steer"
|
|
1560
|
+
? `External adapter '${externalCli.adapter.id}' does not support runs.steer: ${externalCli.unsupportedReasons.steer}`
|
|
1561
|
+
: `External adapter '${externalCli.adapter.id}' cannot resume: ${externalCli.nonResumableReason}`
|
|
1562
|
+
: action === "steer"
|
|
1563
|
+
? "External runners do not accept live steer messages."
|
|
1564
|
+
: "External runners do not persist Pi sessions and cannot be resumed.";
|
|
1520
1565
|
return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
|
|
1521
1566
|
}
|
|
1522
1567
|
|
|
@@ -1572,7 +1617,7 @@ async function resumeExternalJobFollowUp(input: {
|
|
|
1572
1617
|
runId,
|
|
1573
1618
|
kind: "runner",
|
|
1574
1619
|
asyncDir,
|
|
1575
|
-
}, { liveWorkflowRunIds: new Set(input.deps.state.workflowControllers?.keys() ?? []) }) : undefined;
|
|
1620
|
+
}, { liveWorkflowRunIds: new Set(input.deps.state.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(input.deps.config.capacity?.abandonedSlotReleaseAfterMs) }) : undefined;
|
|
1576
1621
|
} catch (error) {
|
|
1577
1622
|
if (error instanceof ActiveAsyncCapacityError) return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: "single", results: [], activeAsyncCapacity: error.snapshot } };
|
|
1578
1623
|
return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "single", results: [] } };
|
|
@@ -1729,6 +1774,7 @@ async function resumeAsyncRun(input: {
|
|
|
1729
1774
|
const scope: AgentScope = resolveExecutionAgentScope(input.params.agentScope);
|
|
1730
1775
|
const discovered = input.deps.discoverAgents(effectiveCwd, scope);
|
|
1731
1776
|
const discoveredAgents = discovered.agents;
|
|
1777
|
+
const unknownAgentDiagnosticContext = diagnosticContextFromDiscovery(discovered, effectiveCwd, scope);
|
|
1732
1778
|
const modelScope = discovered.modelScope;
|
|
1733
1779
|
const sessionName = resolveIntercomSessionTarget(input.deps.pi.getSessionName(), input.ctx.sessionManager.getSessionId());
|
|
1734
1780
|
const recoveryDescriptor = "recoveryDescriptor" in target ? target.recoveryDescriptor : undefined;
|
|
@@ -1749,6 +1795,7 @@ async function resumeAsyncRun(input: {
|
|
|
1749
1795
|
systemPrompt: "",
|
|
1750
1796
|
systemPromptMode: recoveryDescriptor.systemPromptMode,
|
|
1751
1797
|
inheritProjectContext: recoveryDescriptor.inheritProjectContext,
|
|
1798
|
+
inheritGlobalContext: recoveryDescriptor.inheritGlobalContext,
|
|
1752
1799
|
inheritSkills: recoveryDescriptor.inheritSkills,
|
|
1753
1800
|
source: "project",
|
|
1754
1801
|
filePath: recoveryDescriptor.agentFilePath ?? path.join(getProjectSubagentsDir(recoveryDescriptor.cwd), "recovery-agent"),
|
|
@@ -1756,13 +1803,17 @@ async function resumeAsyncRun(input: {
|
|
|
1756
1803
|
} : undefined);
|
|
1757
1804
|
if (!baseAgentConfig) {
|
|
1758
1805
|
return {
|
|
1759
|
-
content: [{ type: "text", text:
|
|
1806
|
+
content: [{ type: "text", text: formatUnknownAgentError(target.agent, unknownAgentDiagnosticContext, "Unknown agent for resume") }],
|
|
1760
1807
|
isError: true,
|
|
1761
1808
|
details: { mode: "management", results: [] },
|
|
1762
1809
|
};
|
|
1763
1810
|
}
|
|
1764
1811
|
if (target.source === "async" && target.runner?.type === "external-cli") {
|
|
1765
|
-
|
|
1812
|
+
const runner = normalizeExternalCliRunnerStatus(target.runner);
|
|
1813
|
+
const message = runner
|
|
1814
|
+
? `External adapter '${runner.adapter.id}' cannot resume: ${runner.nonResumableReason}`
|
|
1815
|
+
: "External runners do not persist Pi sessions and cannot be resumed.";
|
|
1816
|
+
return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
|
|
1766
1817
|
}
|
|
1767
1818
|
if (target.source === "async" && target.runner?.type === "external-job") {
|
|
1768
1819
|
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: [] } };
|
|
@@ -1807,7 +1858,7 @@ async function resumeAsyncRun(input: {
|
|
|
1807
1858
|
runId,
|
|
1808
1859
|
kind: "runner",
|
|
1809
1860
|
asyncDir: path.join(DIRS.async, runId),
|
|
1810
|
-
}, { liveWorkflowRunIds: new Set(input.deps.state.workflowControllers?.keys() ?? []) }) : undefined;
|
|
1861
|
+
}, { liveWorkflowRunIds: new Set(input.deps.state.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(input.deps.config.capacity?.abandonedSlotReleaseAfterMs) }) : undefined;
|
|
1811
1862
|
} catch (error) {
|
|
1812
1863
|
if (error instanceof ActiveAsyncCapacityError) return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: "chain", results: [], activeAsyncCapacity: error.snapshot } };
|
|
1813
1864
|
throw error;
|
|
@@ -1833,6 +1884,7 @@ async function resumeAsyncRun(input: {
|
|
|
1833
1884
|
label: `Attached ${target.runId}`,
|
|
1834
1885
|
},
|
|
1835
1886
|
agents,
|
|
1887
|
+
unknownAgentDiagnosticContext,
|
|
1836
1888
|
ctx: compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
1837
1889
|
pi: input.deps.pi,
|
|
1838
1890
|
cwd: input.requestCwd,
|
|
@@ -1905,20 +1957,23 @@ async function resumeAsyncRun(input: {
|
|
|
1905
1957
|
sourceRunId: target.runId,
|
|
1906
1958
|
runId,
|
|
1907
1959
|
asyncDir: path.join(DIRS.async, runId),
|
|
1908
|
-
})
|
|
1960
|
+
}, { abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(input.deps.config.capacity?.abandonedSlotReleaseAfterMs) })
|
|
1909
1961
|
: acquireActiveAsyncCapacity({
|
|
1910
1962
|
sessionId: input.deps.state.currentSessionId!,
|
|
1911
1963
|
limit: resolveMaxActiveAsyncRunsPerSession(input.deps.config.maxActiveAsyncRunsPerSession),
|
|
1912
1964
|
runId,
|
|
1913
1965
|
kind: "runner",
|
|
1914
1966
|
asyncDir: path.join(DIRS.async, runId),
|
|
1915
|
-
});
|
|
1967
|
+
}, { abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(input.deps.config.capacity?.abandonedSlotReleaseAfterMs) });
|
|
1916
1968
|
} catch (error) {
|
|
1917
1969
|
if (error instanceof ActiveAsyncCapacityError) return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: "single", results: [], activeAsyncCapacity: error.snapshot } };
|
|
1918
1970
|
return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "single", results: [] } };
|
|
1919
1971
|
}
|
|
1920
1972
|
const recoveryAgentConfig = recoveryDescriptor ? applySteeringRecoveryAgentConfig(baseAgentConfig, recoveryDescriptor) : baseAgentConfig;
|
|
1921
1973
|
const agentConfig = intercomBridge.active ? applyIntercomBridgeToAgent(recoveryAgentConfig, intercomBridge) : recoveryAgentConfig;
|
|
1974
|
+
const foregroundContract = target.source === "foreground" ? target.resumeContract : undefined;
|
|
1975
|
+
const outputSchema = input.params.outputSchema ?? foregroundContract?.outputSchema ?? recoveryDescriptor?.structuredOutputSchema;
|
|
1976
|
+
const agentContract = input.params.agentContract ?? foregroundContract?.agentContract ?? recoveryDescriptor?.agentContract;
|
|
1922
1977
|
const artifactConfig: ArtifactConfig = recoveryDescriptor?.artifactConfig ?? omitUndefinedProperties({ ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false, dir: input.deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir });
|
|
1923
1978
|
const artifactsDir = recoveryDescriptor?.artifactsDir ?? getArtifactsDir(parentSessionFile, effectiveCwd, artifactConfig.dir);
|
|
1924
1979
|
const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
@@ -1974,12 +2029,12 @@ async function resumeAsyncRun(input: {
|
|
|
1974
2029
|
controlIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
|
|
1975
2030
|
childIntercomTarget: intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
|
|
1976
2031
|
availableModels,
|
|
1977
|
-
output:
|
|
1978
|
-
outputMode: input.params.outputMode ?? recoveryDescriptor?.outputMode,
|
|
1979
|
-
...(
|
|
1980
|
-
...(
|
|
2032
|
+
output: input.params.output !== undefined ? input.params.output : foregroundContract?.output ?? recoveryDescriptor?.outputPath,
|
|
2033
|
+
outputMode: input.params.outputMode ?? foregroundContract?.outputMode ?? recoveryDescriptor?.outputMode,
|
|
2034
|
+
...(agentContract ? { agentContract } : {}),
|
|
2035
|
+
...(outputSchema ? { structuredOutputSchema: outputSchema } : {}),
|
|
1981
2036
|
...(recoveryDescriptor?.skills ? { skills: [...recoveryDescriptor.skills] } : {}),
|
|
1982
|
-
...(
|
|
2037
|
+
...(input.params.acceptance !== undefined ? { acceptance: input.params.acceptance } : foregroundContract?.acceptance !== undefined ? { acceptance: foregroundContract.acceptance } : recoveryDescriptor?.acceptance !== undefined ? { acceptance: recoveryDescriptor.acceptance } : {}),
|
|
1983
2038
|
...(input.params.timeoutMs !== undefined ? { timeoutMs: input.params.timeoutMs } : {}),
|
|
1984
2039
|
...(input.absoluteDeadlineAt !== undefined ? { absoluteDeadlineAt: input.absoluteDeadlineAt } : {}),
|
|
1985
2040
|
...(input.params.turnBudget !== undefined ? { turnBudget: input.params.turnBudget } : {}),
|
|
@@ -2058,9 +2113,13 @@ async function resumeAsyncRun(input: {
|
|
|
2058
2113
|
...(completed.structuredOutputPath ? { structuredOutputPath: completed.structuredOutputPath } : {}),
|
|
2059
2114
|
...(completed.structuredOutputSchemaPath ? { structuredOutputSchemaPath: completed.structuredOutputSchemaPath } : {}),
|
|
2060
2115
|
...(completed.acceptance ? { acceptance: completed.acceptance } : {}),
|
|
2116
|
+
...(completed.artifactPaths ? { artifactPaths: completed.artifactPaths } : {}),
|
|
2117
|
+
...(completed.outputSaveError ? { outputSaveError: completed.outputSaveError } : {}),
|
|
2118
|
+
...(completed.transcriptPath ? { transcriptPath: completed.transcriptPath } : {}),
|
|
2119
|
+
...(completed.transcriptError ? { transcriptError: completed.transcriptError } : {}),
|
|
2061
2120
|
};
|
|
2062
2121
|
return {
|
|
2063
|
-
content: [{ type: "text", text: completed.output || completed.error || `Revived ${target.source} subagent ${revivedId} completed without output.` }],
|
|
2122
|
+
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.` }],
|
|
2064
2123
|
...(completed.success ? {} : { isError: true }),
|
|
2065
2124
|
details: {
|
|
2066
2125
|
...result.details,
|
|
@@ -2225,19 +2284,28 @@ async function maybeBuildForegroundIntercomReceipt(input: {
|
|
|
2225
2284
|
};
|
|
2226
2285
|
}
|
|
2227
2286
|
|
|
2228
|
-
function
|
|
2287
|
+
function diagnosticContextFromDiscovery(
|
|
2288
|
+
discovered: { agents: AgentConfig[]; cwd?: string; scope?: AgentScope; directories?: UnknownAgentDiagnosticContext["directories"] },
|
|
2289
|
+
cwd: string,
|
|
2290
|
+
scope: AgentScope,
|
|
2291
|
+
): UnknownAgentDiagnosticContext {
|
|
2292
|
+
if (discovered.cwd && discovered.scope && discovered.directories) return unknownAgentDiagnosticContext({ ...discovered, cwd: discovered.cwd, scope: discovered.scope, directories: discovered.directories });
|
|
2293
|
+
return unknownAgentDiagnosticContext(discoverAgents(path.resolve(cwd), scope));
|
|
2294
|
+
}
|
|
2295
|
+
|
|
2296
|
+
function canonicalizeAgentName(name: string, agents: AgentConfig[], diagnostics: AgentDiscoveryDiagnostic[] | undefined, context: UnknownAgentDiagnosticContext): { name?: string; error?: string } {
|
|
2229
2297
|
const resolved = resolveAgentName(name, agents);
|
|
2230
2298
|
const candidates = resolved.error ? agents.filter((agent) => resolveAgentName(name, [agent]).agent) : resolved.agent;
|
|
2231
2299
|
const diagnostic = findBlockingAgentDiagnostic(name, candidates, diagnostics);
|
|
2232
2300
|
if (diagnostic) return { error: `Agent '${name}' has invalid configuration: ${diagnostic.error}` };
|
|
2233
2301
|
if (resolved.error) return { error: resolved.error };
|
|
2234
|
-
if (!resolved.agent) return { error:
|
|
2302
|
+
if (!resolved.agent) return { error: formatUnknownAgentError(name, context) };
|
|
2235
2303
|
return { name: resolved.agent.name };
|
|
2236
2304
|
}
|
|
2237
2305
|
|
|
2238
|
-
function canonicalizeExecutionParams(params: SubagentParamsLike, agents: AgentConfig[], diagnostics
|
|
2306
|
+
function canonicalizeExecutionParams(params: SubagentParamsLike, agents: AgentConfig[], diagnostics: AgentDiscoveryDiagnostic[] | undefined, context: UnknownAgentDiagnosticContext): { params?: SubagentParamsLike; error?: string } {
|
|
2239
2307
|
const resolve = (name: string, location?: string): { name?: string; error?: string } => {
|
|
2240
|
-
const result = canonicalizeAgentName(name, agents, diagnostics);
|
|
2308
|
+
const result = canonicalizeAgentName(name, agents, diagnostics, context);
|
|
2241
2309
|
return result.error && location ? { error: `${result.error} (${location})` } : result;
|
|
2242
2310
|
};
|
|
2243
2311
|
if (params.agent) {
|
|
@@ -2305,6 +2373,7 @@ function validateExecutionInput(
|
|
|
2305
2373
|
hasTasks: boolean,
|
|
2306
2374
|
hasSingle: boolean,
|
|
2307
2375
|
allowClarifyTaskPrompt: boolean,
|
|
2376
|
+
context: UnknownAgentDiagnosticContext,
|
|
2308
2377
|
): AgentToolResult<Details> | null {
|
|
2309
2378
|
if (Number(hasChain) + Number(hasTasks) + Number(hasSingle) !== 1) {
|
|
2310
2379
|
return {
|
|
@@ -2330,7 +2399,7 @@ function validateExecutionInput(
|
|
|
2330
2399
|
|
|
2331
2400
|
if (hasSingle && params.agent && !agents.find((agent) => agent.name === params.agent)) {
|
|
2332
2401
|
return {
|
|
2333
|
-
content: [{ type: "text", text:
|
|
2402
|
+
content: [{ type: "text", text: formatUnknownAgentError(params.agent, context) }],
|
|
2334
2403
|
isError: true,
|
|
2335
2404
|
details: { mode: "single" as const, results: [] },
|
|
2336
2405
|
};
|
|
@@ -2341,7 +2410,7 @@ function validateExecutionInput(
|
|
|
2341
2410
|
const task = params.tasks[i]!;
|
|
2342
2411
|
if (!agents.find((agent) => agent.name === task.agent)) {
|
|
2343
2412
|
return {
|
|
2344
|
-
content: [{ type: "text", text:
|
|
2413
|
+
content: [{ type: "text", text: `${formatUnknownAgentError(task.agent, context)} (task ${i + 1})` }],
|
|
2345
2414
|
isError: true,
|
|
2346
2415
|
details: { mode: "parallel" as const, results: [] },
|
|
2347
2416
|
};
|
|
@@ -2386,7 +2455,7 @@ function validateExecutionInput(
|
|
|
2386
2455
|
for (const agentName of stepAgents) {
|
|
2387
2456
|
if (!agents.find((a) => a.name === agentName)) {
|
|
2388
2457
|
return {
|
|
2389
|
-
content: [{ type: "text", text:
|
|
2458
|
+
content: [{ type: "text", text: `${formatUnknownAgentError(agentName, context)} (step ${i + 1})` }],
|
|
2390
2459
|
isError: true,
|
|
2391
2460
|
details: { mode: "chain" as const, results: [] },
|
|
2392
2461
|
};
|
|
@@ -2977,21 +3046,21 @@ function wrapChainTasksForFork(chain: ChainStep[], contextPolicy: AgentDefaultCo
|
|
|
2977
3046
|
});
|
|
2978
3047
|
}
|
|
2979
3048
|
|
|
2980
|
-
function preflightForkSessionsForStaticTasks(
|
|
3049
|
+
async function preflightForkSessionsForStaticTasks(
|
|
2981
3050
|
params: SubagentParamsLike,
|
|
2982
3051
|
contextPolicy: AgentDefaultContextPolicy,
|
|
2983
|
-
|
|
3052
|
+
prepareSessionForTask: PrepareForkSessionForTask,
|
|
2984
3053
|
dynamicFanoutMaxItems?: number,
|
|
2985
|
-
): void {
|
|
3054
|
+
): Promise<void> {
|
|
2986
3055
|
if (!contextPolicy.usesFork) return;
|
|
2987
3056
|
if (params.agent) {
|
|
2988
|
-
if (shouldForkAgent(contextPolicy, params.agent))
|
|
3057
|
+
if (shouldForkAgent(contextPolicy, params.agent)) await prepareSessionForTask(params.agent, 0, params.model);
|
|
2989
3058
|
return;
|
|
2990
3059
|
}
|
|
2991
3060
|
if (params.tasks) {
|
|
2992
|
-
params.tasks.
|
|
2993
|
-
if (shouldForkAgent(contextPolicy, task.agent))
|
|
2994
|
-
}
|
|
3061
|
+
for (const [index, task] of params.tasks.entries()) {
|
|
3062
|
+
if (shouldForkAgent(contextPolicy, task.agent)) await prepareSessionForTask(task.agent, index, task.model);
|
|
3063
|
+
}
|
|
2995
3064
|
return;
|
|
2996
3065
|
}
|
|
2997
3066
|
if (!params.chain?.length) return;
|
|
@@ -2999,7 +3068,7 @@ function preflightForkSessionsForStaticTasks(
|
|
|
2999
3068
|
for (const step of params.chain) {
|
|
3000
3069
|
if (isParallelStep(step)) {
|
|
3001
3070
|
for (const task of step.parallel) {
|
|
3002
|
-
if (shouldForkAgent(contextPolicy, task.agent))
|
|
3071
|
+
if (shouldForkAgent(contextPolicy, task.agent)) await prepareSessionForTask(task.agent, flatIndex, task.model);
|
|
3003
3072
|
flatIndex++;
|
|
3004
3073
|
}
|
|
3005
3074
|
continue;
|
|
@@ -3007,13 +3076,13 @@ function preflightForkSessionsForStaticTasks(
|
|
|
3007
3076
|
if (isDynamicParallelStep(step)) {
|
|
3008
3077
|
const maxItems = step.expand.maxItems ?? dynamicFanoutMaxItems ?? 0;
|
|
3009
3078
|
if (shouldForkAgent(contextPolicy, step.parallel.agent)) {
|
|
3010
|
-
for (let itemIndex = 0; itemIndex < maxItems; itemIndex++)
|
|
3079
|
+
for (let itemIndex = 0; itemIndex < maxItems; itemIndex++) await prepareSessionForTask(step.parallel.agent, flatIndex + itemIndex, step.parallel.model);
|
|
3011
3080
|
}
|
|
3012
3081
|
flatIndex += maxItems;
|
|
3013
3082
|
continue;
|
|
3014
3083
|
}
|
|
3015
3084
|
const sequential = step as SequentialStep;
|
|
3016
|
-
if (shouldForkAgent(contextPolicy, sequential.agent))
|
|
3085
|
+
if (shouldForkAgent(contextPolicy, sequential.agent)) await prepareSessionForTask(sequential.agent, flatIndex, sequential.model);
|
|
3017
3086
|
flatIndex++;
|
|
3018
3087
|
}
|
|
3019
3088
|
}
|
|
@@ -3067,9 +3136,13 @@ async function waitForWorkflowAsyncSingleResult(
|
|
|
3067
3136
|
...(completed.structuredOutputPath ? { structuredOutputPath: completed.structuredOutputPath } : {}),
|
|
3068
3137
|
...(completed.structuredOutputSchemaPath ? { structuredOutputSchemaPath: completed.structuredOutputSchemaPath } : {}),
|
|
3069
3138
|
...(completed.acceptance ? { acceptance: completed.acceptance } : {}),
|
|
3139
|
+
...(completed.artifactPaths ? { artifactPaths: completed.artifactPaths } : {}),
|
|
3140
|
+
...(completed.outputSaveError ? { outputSaveError: completed.outputSaveError } : {}),
|
|
3141
|
+
...(completed.transcriptPath ? { transcriptPath: completed.transcriptPath } : {}),
|
|
3142
|
+
...(completed.transcriptError ? { transcriptError: completed.transcriptError } : {}),
|
|
3070
3143
|
});
|
|
3071
3144
|
return {
|
|
3072
|
-
content: [{ type: "text", text: completed.output || completed.error || `Async workflow child ${options.runId} completed without output.` }],
|
|
3145
|
+
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.` }],
|
|
3073
3146
|
...(completed.success ? {} : { isError: true }),
|
|
3074
3147
|
details: {
|
|
3075
3148
|
...launchResult.details,
|
|
@@ -3097,6 +3170,7 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
3097
3170
|
intercomBridge,
|
|
3098
3171
|
nestedRoute,
|
|
3099
3172
|
contextPolicy,
|
|
3173
|
+
unknownAgentDiagnosticContext,
|
|
3100
3174
|
} = data;
|
|
3101
3175
|
const hasChain = (params.chain?.length ?? 0) > 0;
|
|
3102
3176
|
const hasTasks = (params.tasks?.length ?? 0) > 0;
|
|
@@ -3135,7 +3209,7 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
3135
3209
|
const a = agents.find((x) => x.name === params.agent);
|
|
3136
3210
|
if (!a) {
|
|
3137
3211
|
return {
|
|
3138
|
-
content: [{ type: "text", text:
|
|
3212
|
+
content: [{ type: "text", text: formatUnknownAgentError(params.agent!, unknownAgentDiagnosticContext) }],
|
|
3139
3213
|
isError: true,
|
|
3140
3214
|
details: { mode: "single" as const, results: [] },
|
|
3141
3215
|
};
|
|
@@ -3166,6 +3240,7 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
3166
3240
|
ctx: asyncCtx,
|
|
3167
3241
|
availableModels,
|
|
3168
3242
|
cwd: effectiveCwd,
|
|
3243
|
+
requestedCwd: data.requestedCwd,
|
|
3169
3244
|
maxOutput: params.maxOutput,
|
|
3170
3245
|
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
3171
3246
|
artifactConfig,
|
|
@@ -3501,7 +3576,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3501
3576
|
const agentConfig = agents.find((a) => a.name === params.agent);
|
|
3502
3577
|
if (!agentConfig) {
|
|
3503
3578
|
return {
|
|
3504
|
-
content: [{ type: "text", text:
|
|
3579
|
+
content: [{ type: "text", text: formatUnknownAgentError(params.agent!, data.unknownAgentDiagnosticContext) }],
|
|
3505
3580
|
isError: true,
|
|
3506
3581
|
details: { mode: "single", results: [] },
|
|
3507
3582
|
};
|
|
@@ -3630,12 +3705,15 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3630
3705
|
try {
|
|
3631
3706
|
r = await runSync(ctx.cwd, agents, params.agent!, task, compactOptional<Parameters<typeof runSync>[4]>({
|
|
3632
3707
|
permissions: deps.config.permissions,
|
|
3708
|
+
runtimeSnapshotHost: deps.pi,
|
|
3633
3709
|
parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
|
|
3634
3710
|
llmIntentArbiter: createTaskMutationArbiter(ctx),
|
|
3635
3711
|
...workflowForegroundSteeringLaunchOptions(foregroundControl, 0),
|
|
3636
3712
|
context: data.contextPolicy.contextForAgent(params.agent!),
|
|
3713
|
+
unknownAgentDiagnosticContext: data.unknownAgentDiagnosticContext,
|
|
3637
3714
|
runFanoutBudget: params.runFanoutAdmitted ? data.runFanoutBudget : { ...data.runFanoutBudget, parentPath: `${data.runFanoutBudget.parentPath ? `${data.runFanoutBudget.parentPath}/` : ""}single` },
|
|
3638
3715
|
cwd: singleCwd,
|
|
3716
|
+
requestedCwd: data.requestedCwd,
|
|
3639
3717
|
signal,
|
|
3640
3718
|
interruptSignal: interruptController.signal,
|
|
3641
3719
|
allowIntercomDetach: agentConfig.systemPrompt?.includes(INTERCOM_BRIDGE_MARKER) === true,
|
|
@@ -3672,12 +3750,16 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3672
3750
|
agentContract: params.agentContract,
|
|
3673
3751
|
acceptance: params.acceptance,
|
|
3674
3752
|
acceptanceContext: { mode: "single" },
|
|
3753
|
+
workflowChildPermitLaunch: data.workflowChildPermitLaunch,
|
|
3675
3754
|
onEffectivePrompt: foregroundControl ? (prompt) => updateLiveEffectivePrompt(foregroundControl, 0, prompt) : undefined,
|
|
3676
3755
|
onDetachReady: (detach) => {
|
|
3677
3756
|
detachForeground = detach;
|
|
3678
3757
|
},
|
|
3679
3758
|
onDetachedExit: (result) => {
|
|
3680
3759
|
try {
|
|
3760
|
+
if (worktreeSetup) {
|
|
3761
|
+
finalizeSingleWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: sourceCwd, agent: params.agent!, result });
|
|
3762
|
+
}
|
|
3681
3763
|
try {
|
|
3682
3764
|
updateRememberedForegroundChild(deps.state, { runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, index: 0, result, events: deps.pi.events, notify: true });
|
|
3683
3765
|
} catch {
|
|
@@ -3705,7 +3787,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3705
3787
|
}
|
|
3706
3788
|
}
|
|
3707
3789
|
}
|
|
3708
|
-
recordRun(params.agent!, cleanTask, result.exitCode, result.progressSummary?.durationMs ?? 0);
|
|
3790
|
+
recordRun(params.agent!, cleanTask, result.exitCode, result.progressSummary?.durationMs ?? 0, result);
|
|
3709
3791
|
},
|
|
3710
3792
|
timeoutMs: data.timeoutMs,
|
|
3711
3793
|
deadlineAt,
|
|
@@ -3730,7 +3812,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3730
3812
|
}
|
|
3731
3813
|
}
|
|
3732
3814
|
if (!r.detached) {
|
|
3733
|
-
recordRun(params.agent!, cleanTask, r.exitCode, r.progressSummary?.durationMs ?? 0);
|
|
3815
|
+
recordRun(params.agent!, cleanTask, r.exitCode, r.progressSummary?.durationMs ?? 0, r);
|
|
3734
3816
|
}
|
|
3735
3817
|
|
|
3736
3818
|
let worktreeHandoff: ReturnType<typeof finalizeSingleWorktreeHandoff> | undefined;
|
|
@@ -3776,7 +3858,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3776
3858
|
usageBudget: usageBudgetState(data.usageBudget, totalCost),
|
|
3777
3859
|
...(worktreeHandoff?.reference ? { parallelHandoff: worktreeHandoff.reference } : {}),
|
|
3778
3860
|
}));
|
|
3779
|
-
rememberForegroundRun(deps.state, { runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, results: details.results, extensionBindings: params.extensionBindings });
|
|
3861
|
+
rememberForegroundRun(deps.state, { runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, results: details.results, params, effectiveOutput, effectiveOutputMode, extensionBindings: params.extensionBindings });
|
|
3780
3862
|
|
|
3781
3863
|
const suppressRoutineResultIntercom = shouldSuppressRoutineResultIntercom({ suppressRoutineResultIntercom: params.suppressRoutineResultIntercom, results: [r] });
|
|
3782
3864
|
if (!r.detached && !r.interrupted && !suppressRoutineResultIntercom) {
|
|
@@ -3925,14 +4007,25 @@ function workflowChildResult(
|
|
|
3925
4007
|
result: AgentToolResult<Details>,
|
|
3926
4008
|
childParams: Record<string, unknown> = {},
|
|
3927
4009
|
resumeState?: SubagentState,
|
|
4010
|
+
forcedTerminalOutcome?: WorkflowTerminalOutcome,
|
|
3928
4011
|
): WorkflowScriptChildResult {
|
|
3929
4012
|
const receiptOutput = result.content.map((part) => part.type === "text" ? part.text : "").filter(Boolean).join("\n");
|
|
3930
4013
|
const output = result.details.results.length === 1 && result.details.results[0]?.finalOutput !== undefined
|
|
3931
4014
|
? result.details.results[0].finalOutput
|
|
3932
4015
|
: receiptOutput;
|
|
4016
|
+
const childError = result.details.results.map((child) => child.error).find((error): error is string => Boolean(error));
|
|
4017
|
+
const failureError = childError && receiptOutput && receiptOutput !== childError
|
|
4018
|
+
? `${childError}\n\n${receiptOutput}`
|
|
4019
|
+
: childError || receiptOutput || output || "Child run failed.";
|
|
3933
4020
|
const detached = result.details.results.some((child) => child.detached);
|
|
3934
4021
|
const interrupted = result.details.results.some((child) => child.interrupted);
|
|
3935
4022
|
const stopped = result.details.results.some((child) => child.stopped);
|
|
4023
|
+
const terminalOutcome = forcedTerminalOutcome
|
|
4024
|
+
?? (result.details.results.some((child) => child.timedOut)
|
|
4025
|
+
? { state: "partial" as const, reason: "timeout" as const }
|
|
4026
|
+
: result.details.usageBudget?.exhausted || result.details.results.some((child) => child.turnBudgetExceeded || child.toolBudgetBlocked)
|
|
4027
|
+
? { state: "partial" as const, reason: "budget_exhausted" as const }
|
|
4028
|
+
: undefined);
|
|
3936
4029
|
const ok = result.isError !== true && !detached && !interrupted && !stopped;
|
|
3937
4030
|
const artifactPaths = new Set<string>();
|
|
3938
4031
|
if (result.details.asyncDir) artifactPaths.add(result.details.asyncDir);
|
|
@@ -3965,13 +4058,22 @@ function workflowChildResult(
|
|
|
3965
4058
|
const continuationRunIds = [...new Set([resumeSourceRunId, runId].filter((value): value is string => Boolean(value)))];
|
|
3966
4059
|
const outputReference = result.details.results.find((child) => child.savedOutputPath)?.savedOutputPath
|
|
3967
4060
|
?? result.details.results.find((child) => child.outputReference?.path)?.outputReference?.path;
|
|
4061
|
+
const outputPathMapping = typeof childParams.task === "string" ? outputPathMappingFromTask(childParams.task, outputReference) : undefined;
|
|
4062
|
+
const externalResult = result.details.results.length === 1 && result.details.results[0]?.runner?.type === "external-cli" ? result.details.results[0] : undefined;
|
|
4063
|
+
const externalStatus = result.details.asyncDir ? readStatus(result.details.asyncDir) : undefined;
|
|
4064
|
+
const externalStatusStep = externalStatus?.steps?.length === 1 && externalStatus.steps[0]?.runner?.type === "external-cli" ? externalStatus.steps[0] : undefined;
|
|
4065
|
+
const externalRunner = normalizeExternalCliRunnerStatus(externalResult?.runner ?? externalStatusStep?.runner);
|
|
4066
|
+
const externalProcess = externalResult?.externalProcess ?? externalStatusStep?.externalProcess;
|
|
4067
|
+
const externalAdapter = externalRunner ? externalCliReceiptMetadata({ runner: externalRunner, externalProcess, outputReference }) : undefined;
|
|
4068
|
+
if (externalAdapter) resumability = { state: "not-resumable", reason: externalAdapter.nonResumableReason };
|
|
3968
4069
|
return {
|
|
3969
4070
|
key,
|
|
3970
4071
|
ok,
|
|
4072
|
+
...(terminalOutcome ? { terminalOutcome } : {}),
|
|
3971
4073
|
...(resolvedAgents.length === 1 ? { agent: resolvedAgents[0] } : {}),
|
|
3972
4074
|
...(runId ? { runId } : {}),
|
|
3973
4075
|
output,
|
|
3974
|
-
...(!ok ? { error:
|
|
4076
|
+
...(!ok ? { error: failureError } : {}),
|
|
3975
4077
|
...(detached ? { detached: true } : {}),
|
|
3976
4078
|
...(interrupted ? { interrupted: true } : {}),
|
|
3977
4079
|
...(stopped ? { stopped: true } : {}),
|
|
@@ -3979,6 +4081,8 @@ function workflowChildResult(
|
|
|
3979
4081
|
...(requestedContext ? { requestedContext } : {}),
|
|
3980
4082
|
...(resolvedContext ? { resolvedContext } : {}),
|
|
3981
4083
|
...(outputReference ? { outputReference } : {}),
|
|
4084
|
+
...(outputPathMapping ? { outputPathMapping } : {}),
|
|
4085
|
+
...(externalAdapter ? { externalAdapter } : {}),
|
|
3982
4086
|
resumability,
|
|
3983
4087
|
continuation: { runIds: continuationRunIds },
|
|
3984
4088
|
artifactPaths: [...artifactPaths],
|
|
@@ -3986,6 +4090,13 @@ function workflowChildResult(
|
|
|
3986
4090
|
};
|
|
3987
4091
|
}
|
|
3988
4092
|
|
|
4093
|
+
function workflowOutputPathMappingSummary(children: WorkflowScriptChildResult[]): string {
|
|
4094
|
+
const mappings = children.flatMap((child) => child.outputPathMapping
|
|
4095
|
+
? [`'${child.key}': requested ${child.outputPathMapping.requestedPath} -> saved ${child.outputPathMapping.savedPath}`]
|
|
4096
|
+
: []);
|
|
4097
|
+
return mappings.length > 0 ? ` Output path mappings: ${mappings.join("; ")}.` : "";
|
|
4098
|
+
}
|
|
4099
|
+
|
|
3989
4100
|
function workflowSteerReceipt(key: string, result: AgentToolResult<Details>): WorkflowSteerResult {
|
|
3990
4101
|
const steering = result.details.steering;
|
|
3991
4102
|
const error = result.content.map((part) => part.type === "text" ? part.text : "").filter(Boolean).join("\n") || undefined;
|
|
@@ -4024,8 +4135,24 @@ function terminalWorkflowReceipt(
|
|
|
4024
4135
|
workflowRunId: string,
|
|
4025
4136
|
state: WorkflowReceiptState,
|
|
4026
4137
|
children: WorkflowScriptChildResult[],
|
|
4138
|
+
workflowChildren?: WorkflowReceipt["workflowChildren"],
|
|
4139
|
+
terminalOutcome?: WorkflowTerminalOutcome,
|
|
4027
4140
|
): WorkflowReceipt {
|
|
4028
|
-
return buildWorkflowReceipt({ workflowRunId, state, children });
|
|
4141
|
+
return buildWorkflowReceipt({ workflowRunId, state, children, workflowChildren, terminalOutcome });
|
|
4142
|
+
}
|
|
4143
|
+
|
|
4144
|
+
function workflowFailureTerminalOutcome(error: unknown, _children: WorkflowScriptChildResult[], usageBudget: ReturnType<typeof usageBudgetState>): WorkflowTerminalOutcome | undefined {
|
|
4145
|
+
if (usageBudget?.exhausted) return { state: "partial", reason: "budget_exhausted" };
|
|
4146
|
+
return error instanceof WorkflowScriptError && error.errorKind === "timeout" ? { state: "partial", reason: "timeout" } : undefined;
|
|
4147
|
+
}
|
|
4148
|
+
|
|
4149
|
+
function workflowFailureMessage(error: unknown, workflowRunId: string, children: WorkflowScriptChildResult[]): string {
|
|
4150
|
+
const text = error instanceof Error ? error.message : String(error);
|
|
4151
|
+
const validationPrefix = "workflowScript validation failed before child launch; no children launched.";
|
|
4152
|
+
if (children.length === 0 && text.includes(validationPrefix)) {
|
|
4153
|
+
return `Workflow '${workflowRunId}' validation failed before child launch; no children launched.${text.slice(text.indexOf(validationPrefix) + validationPrefix.length)}`;
|
|
4154
|
+
}
|
|
4155
|
+
return text;
|
|
4029
4156
|
}
|
|
4030
4157
|
|
|
4031
4158
|
export async function steerWorkflowChildByKey(input: {
|
|
@@ -4068,6 +4195,8 @@ export async function steerWorkflowChildByKey(input: {
|
|
|
4068
4195
|
return { key: input.key, state: "missed", error: `Workflow child '${input.key}' is ${childStatus.state}.` };
|
|
4069
4196
|
}
|
|
4070
4197
|
if (childStatus) {
|
|
4198
|
+
const unsupported = externalRunnerControlError(asyncDir, "steer");
|
|
4199
|
+
if (unsupported) return workflowSteerReceipt(input.key, unsupported);
|
|
4071
4200
|
const result = await steerAsyncRun({
|
|
4072
4201
|
state: input.state,
|
|
4073
4202
|
runId: childRunId,
|
|
@@ -4121,6 +4250,15 @@ export function prepareWorkflowLaunchParams(
|
|
|
4121
4250
|
const toolBudget = childParams.toolBudget ?? workflowDefaults.toolBudget;
|
|
4122
4251
|
const intercomBridge = childParams.intercomBridge ?? workflowDefaults.intercomBridge;
|
|
4123
4252
|
const worktree = childParams.worktree ?? workflowDefaults.worktree;
|
|
4253
|
+
const outputSchema = Object.hasOwn(childParams, "outputSchema") ? childParams.outputSchema : workflowDefaults.outputSchema;
|
|
4254
|
+
if (outputSchema !== undefined) assertJsonSchemaObject(outputSchema, "outputSchema");
|
|
4255
|
+
const agentContract = Object.hasOwn(childParams, "agentContract") ? childParams.agentContract : workflowDefaults.agentContract;
|
|
4256
|
+
if (agentContract !== undefined && !isAgentContractV1(agentContract as AgentContract)) throw new Error("agentContract must be { version: 1 }.");
|
|
4257
|
+
const acceptance = Object.hasOwn(childParams, "acceptance") ? childParams.acceptance : workflowDefaults.acceptance;
|
|
4258
|
+
const output = Object.hasOwn(childParams, "output") ? childParams.output : workflowDefaults.output;
|
|
4259
|
+
if (output !== undefined && typeof output !== "string" && typeof output !== "boolean") throw new Error("output must be a path string or boolean.");
|
|
4260
|
+
const outputMode = Object.hasOwn(childParams, "outputMode") ? childParams.outputMode : workflowDefaults.outputMode;
|
|
4261
|
+
if (outputMode !== undefined && outputMode !== "inline" && outputMode !== "file-only") throw new Error("outputMode must be 'inline' or 'file-only'.");
|
|
4124
4262
|
return {
|
|
4125
4263
|
action: "resume",
|
|
4126
4264
|
id: childParams.resume.trim(),
|
|
@@ -4129,6 +4267,11 @@ export function prepareWorkflowLaunchParams(
|
|
|
4129
4267
|
workflowParentRunId: parentWorkflowRunId,
|
|
4130
4268
|
workflowKey,
|
|
4131
4269
|
...(worktree !== undefined ? { worktree: worktree as boolean } : {}),
|
|
4270
|
+
...(outputSchema !== undefined ? { outputSchema: outputSchema as JsonSchemaObject } : {}),
|
|
4271
|
+
...(agentContract !== undefined ? { agentContract: agentContract as AgentContract } : {}),
|
|
4272
|
+
...(acceptance !== undefined ? { acceptance: acceptance as AcceptanceInput } : {}),
|
|
4273
|
+
...(output !== undefined ? { output: output as string | boolean } : {}),
|
|
4274
|
+
...(outputMode !== undefined ? { outputMode: outputMode as OutputMode } : {}),
|
|
4132
4275
|
...(options.runFanoutBudget ? { runFanoutBudget: { ...options.runFanoutBudget, parentPath: `${options.runFanoutBudget.parentPath ? `${options.runFanoutBudget.parentPath}/` : ""}workflow[${workflowKey}]` } } : {}),
|
|
4133
4276
|
...(options.missionDetached ? { mission: false } : {}),
|
|
4134
4277
|
...(timeoutMs !== undefined ? { timeoutMs: timeoutMs as number } : {}),
|
|
@@ -4186,11 +4329,12 @@ function workflowChatProgressUpdate(
|
|
|
4186
4329
|
runId: string,
|
|
4187
4330
|
chatProgress: WorkflowChatProgressProjection,
|
|
4188
4331
|
workflow: NonNullable<Details["workflow"]>,
|
|
4332
|
+
workflowChildren?: Details["workflowChildren"],
|
|
4189
4333
|
): AgentToolResult<Details> | undefined {
|
|
4190
4334
|
if (chatProgress.mode !== "live-card") return undefined;
|
|
4191
4335
|
return {
|
|
4192
4336
|
content: [{ type: "text", text: "Workflow running." }],
|
|
4193
|
-
details: { mode: "workflow", runId, results: [], workflow, chatProgress },
|
|
4337
|
+
details: { mode: "workflow", runId, results: [], workflow, ...(workflowChildren ? { workflowChildren } : {}), chatProgress },
|
|
4194
4338
|
};
|
|
4195
4339
|
}
|
|
4196
4340
|
|
|
@@ -4201,10 +4345,12 @@ function createScheduledOwnerState(source: SubagentState, ownerSessionId: string
|
|
|
4201
4345
|
grantHistory: [...(source.subagentSpawns.grantHistory ?? [])],
|
|
4202
4346
|
}
|
|
4203
4347
|
: undefined;
|
|
4348
|
+
const ownerParentModel = source.currentSessionId === ownerSessionId ? source.lastParentModel : undefined;
|
|
4204
4349
|
return {
|
|
4205
4350
|
...source,
|
|
4206
4351
|
baseCwd: ctx.cwd,
|
|
4207
4352
|
currentSessionId: ownerSessionId,
|
|
4353
|
+
lastParentModel: ownerParentModel,
|
|
4208
4354
|
parentSessionFile: ctx.sessionManager.getSessionFile() ?? null,
|
|
4209
4355
|
subagentInProgress: false,
|
|
4210
4356
|
...(ownerSpawns ? { subagentSpawns: ownerSpawns } : { subagentSpawns: undefined }),
|
|
@@ -4265,6 +4411,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4265
4411
|
const delegatedThinkingOverrides = new WeakMap<object, AgentConfig["thinking"]>();
|
|
4266
4412
|
const delegatedZeroToolBudgets = new WeakSet<object>();
|
|
4267
4413
|
const delegatedExecutions = new WeakSet<object>();
|
|
4414
|
+
const workflowPermitContexts = new WeakMap<object, { root: WorkflowChildPermit } | { child: WorkflowChildPermitContext }>();
|
|
4268
4415
|
const warnedArtifactPackageDirs = new Set<string>();
|
|
4269
4416
|
const scheduledOwnerExecutors = new Map<string, ReturnType<typeof createSubagentExecutor>>();
|
|
4270
4417
|
const execute = async (
|
|
@@ -4274,11 +4421,23 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4274
4421
|
onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
|
|
4275
4422
|
ctx: ExtensionContext,
|
|
4276
4423
|
preserveActiveSession = false,
|
|
4424
|
+
parentModelOverride?: ParentModel | null,
|
|
4277
4425
|
): Promise<AgentToolResult<Details>> => {
|
|
4426
|
+
if (params.action?.trim() === "validate") {
|
|
4427
|
+
const validation = validateWorkflowScript(params.workflowScript ?? "");
|
|
4428
|
+
return {
|
|
4429
|
+
content: [{ type: "text", text: JSON.stringify(validation) }],
|
|
4430
|
+
...(validation.ok ? {} : { isError: true }),
|
|
4431
|
+
details: { mode: "management", results: [] },
|
|
4432
|
+
};
|
|
4433
|
+
}
|
|
4278
4434
|
const workflowLaunchObserver = workflowLaunchObservers.get(params);
|
|
4279
4435
|
const delegatedThinkingOverride = delegatedThinkingOverrides.get(params);
|
|
4280
4436
|
const allowZeroToolBudget = delegatedZeroToolBudgets.has(params);
|
|
4281
4437
|
const delegatedExecution = delegatedExecutions.has(params);
|
|
4438
|
+
const workflowPermitContext = workflowPermitContexts.get(params);
|
|
4439
|
+
const delegatedWorkflowPermit = workflowPermitContext && "root" in workflowPermitContext ? workflowPermitContext.root : undefined;
|
|
4440
|
+
const workflowChildPermitLaunch = workflowPermitContext && "child" in workflowPermitContext ? workflowPermitContext.child : undefined;
|
|
4282
4441
|
if (!preserveActiveSession) deps.state.baseCwd = ctx.cwd;
|
|
4283
4442
|
deps.state.foregroundRuns ??= new Map();
|
|
4284
4443
|
deps.state.foregroundControls ??= new Map();
|
|
@@ -4289,6 +4448,19 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4289
4448
|
const normalizedAction = typeof requestParams.action === "string" ? requestParams.action.trim() : requestParams.action;
|
|
4290
4449
|
if (normalizedAction === "resume" && requestParams.extensionBindings !== undefined) return buildRequestedModeError(requestParams, "extensionBindings is not supported with action='resume'; resume uses the original retained child binding.");
|
|
4291
4450
|
if (requestParams.workflowScript !== undefined && normalizedAction === undefined) {
|
|
4451
|
+
if (delegatedWorkflowPermit) {
|
|
4452
|
+
const permitError = validateWorkflowChildPermitRoot(delegatedWorkflowPermit, _id);
|
|
4453
|
+
if (permitError) return buildRequestedModeError(requestParams, permitError);
|
|
4454
|
+
if (requestParams.async !== false) return buildRequestedModeError(requestParams, "Workflow child permit supports foreground workflow roots only; set async:false.");
|
|
4455
|
+
}
|
|
4456
|
+
const workflowParentModel = parentModelOverride !== undefined
|
|
4457
|
+
? parentModelOverride
|
|
4458
|
+
: (() => {
|
|
4459
|
+
const currentParentModel = normalizeParentModel(ctx.model);
|
|
4460
|
+
return (preserveActiveSession
|
|
4461
|
+
? currentParentModel
|
|
4462
|
+
: rememberParentModel(deps.state, resolveCurrentSessionId(ctx.sessionManager), currentParentModel)) ?? null;
|
|
4463
|
+
})();
|
|
4292
4464
|
if (requestParams.extensionBindings !== undefined) {
|
|
4293
4465
|
try {
|
|
4294
4466
|
requestParams.extensionBindings = normalizeExtensionBindings(requestParams.extensionBindings)!.value;
|
|
@@ -4340,7 +4512,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4340
4512
|
runId: workflowRunId,
|
|
4341
4513
|
kind: "workflow",
|
|
4342
4514
|
asyncDir: path.join(DIRS.async, workflowRunId),
|
|
4343
|
-
}, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) });
|
|
4515
|
+
}, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) });
|
|
4344
4516
|
} catch (error) {
|
|
4345
4517
|
if (error instanceof ActiveAsyncCapacityError) {
|
|
4346
4518
|
deps.state.activeAsyncCapacity = error.snapshot;
|
|
@@ -4414,7 +4586,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4414
4586
|
deps.state.workflowChildStops ??= new Map();
|
|
4415
4587
|
deps.state.workflowControllers.set(workflowRunId, controller);
|
|
4416
4588
|
workflowCapacity?.markWorkflowStarted();
|
|
4417
|
-
if (workflowCapacity) deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(currentSessionId, resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession), { liveWorkflowRunIds: new Set(deps.state.workflowControllers.keys()) });
|
|
4589
|
+
if (workflowCapacity) deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(currentSessionId, resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession), { liveWorkflowRunIds: new Set(deps.state.workflowControllers.keys()), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) });
|
|
4418
4590
|
let status: AsyncStatus = {
|
|
4419
4591
|
runId: workflowRunId,
|
|
4420
4592
|
toolCallId,
|
|
@@ -4431,6 +4603,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4431
4603
|
pid: process.pid,
|
|
4432
4604
|
steps: [],
|
|
4433
4605
|
workflow: { trace: [], emits: [], console: [] },
|
|
4606
|
+
workflowChildren: workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState: "running", inventoryComplete: false }),
|
|
4434
4607
|
runFanoutBudget: getRunFanoutBudgetSnapshot(workflowFanoutBudget),
|
|
4435
4608
|
};
|
|
4436
4609
|
const appendWorkflowEvent = (event: Record<string, unknown>) => {
|
|
@@ -4479,6 +4652,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4479
4652
|
if (persistClosed) return;
|
|
4480
4653
|
const liveJob = deps.state.asyncJobs.get(workflowRunId);
|
|
4481
4654
|
if (liveJob && (liveJob.status === "complete" || liveJob.status === "failed") && status.state !== "complete" && status.state !== "failed") return;
|
|
4655
|
+
const workflowState = status.state === "complete" ? "completed" : status.state === "failed" || status.state === "rejected" ? "failed" : status.state === "paused" ? "paused" : status.state === "stopped" ? "stopped" : "running";
|
|
4656
|
+
status.workflowChildren = workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState, inventoryComplete: workflowState !== "running", trace: status.workflow?.trace, steps: status.steps });
|
|
4482
4657
|
status.lastUpdate = Date.now();
|
|
4483
4658
|
if (!initialPersistenceComplete) {
|
|
4484
4659
|
writeAtomicJson(statusPath, status);
|
|
@@ -4522,6 +4697,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4522
4697
|
delete liveJob.agents;
|
|
4523
4698
|
}
|
|
4524
4699
|
liveJob.workflow = status.workflow;
|
|
4700
|
+
liveJob.workflowChildren = status.workflowChildren;
|
|
4525
4701
|
}
|
|
4526
4702
|
};
|
|
4527
4703
|
const writeWorkflowResult = (payload: Record<string, unknown>): boolean => {
|
|
@@ -4556,7 +4732,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4556
4732
|
status.toolCount = toolCounts.length > 0 ? toolCounts.reduce((total, count) => total + count, 0) : undefined;
|
|
4557
4733
|
status.currentStep = runningSteps.length === 1 ? steps.indexOf(runningSteps[0]!) : undefined;
|
|
4558
4734
|
};
|
|
4559
|
-
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 };
|
|
4735
|
+
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 };
|
|
4560
4736
|
deps.state.asyncJobs.set(workflowRunId, workflowJob);
|
|
4561
4737
|
deps.state.fleetJobs ??= new Map();
|
|
4562
4738
|
deps.state.fleetJobs.set(workflowRunId, workflowJob);
|
|
@@ -4647,7 +4823,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4647
4823
|
version: 1,
|
|
4648
4824
|
childId: entry.key,
|
|
4649
4825
|
status: "stopped",
|
|
4650
|
-
reason: "
|
|
4826
|
+
reason: "subagent-action",
|
|
4651
4827
|
source: "async",
|
|
4652
4828
|
stepIndex: status.steps?.indexOf(projectedStep),
|
|
4653
4829
|
agent: projectedStep.agent,
|
|
@@ -4692,7 +4868,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4692
4868
|
launch: async (key, childParams, workflowSignal, admission) => {
|
|
4693
4869
|
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);
|
|
4694
4870
|
const budgetState = usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults));
|
|
4695
|
-
if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)), childParams, deps.state);
|
|
4871
|
+
if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)), childParams, deps.state, { state: "partial", reason: "budget_exhausted" });
|
|
4696
4872
|
const childPhase = typeof childParams.phase === "string" && childParams.phase.trim() ? childParams.phase.trim() : undefined;
|
|
4697
4873
|
const childLabel = typeof childParams.label === "string" && childParams.label.trim() ? childParams.label.trim() : undefined;
|
|
4698
4874
|
recordMissionWorkflowChild(missionBinding, workflowRunId, key, {
|
|
@@ -4743,7 +4919,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4743
4919
|
status: step.status,
|
|
4744
4920
|
heartbeat: { status: step.status, ...(childPhase ? { phase: childPhase } : {}) },
|
|
4745
4921
|
});
|
|
4746
|
-
}, ctx, preserveActiveSession);
|
|
4922
|
+
}, ctx, preserveActiveSession, workflowParentModel);
|
|
4747
4923
|
});
|
|
4748
4924
|
workflowResults.push(...result.details.results);
|
|
4749
4925
|
for (const childResult of result.details.results) {
|
|
@@ -4773,25 +4949,26 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4773
4949
|
}
|
|
4774
4950
|
return child;
|
|
4775
4951
|
},
|
|
4776
|
-
status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession)),
|
|
4952
|
+
status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession, workflowParentModel)),
|
|
4777
4953
|
resolveResume: (reference) => resolveKeyedWorkflowResume(reference, deps.state),
|
|
4778
4954
|
steer: (key, message, options, workflowSignal) => steerWorkflowChildByKey({ state: deps.state, workflowRunId, key, message, options, signal: workflowSignal, resolveRunId: () => workflowChildRunIds.get(key) }),
|
|
4779
4955
|
});
|
|
4780
4956
|
const returnPreview = formatWorkflowValue(workflow.value).slice(0, 1_000);
|
|
4781
4957
|
const emitPreview = workflow.emits.length > 0 ? ` Emitted: ${workflow.emits.map(formatWorkflowValue).join(", ").slice(0, 1_000)}` : "";
|
|
4782
|
-
const summary = `Workflow completed with ${workflow.children.length} child run(s). Return: ${returnPreview}${emitPreview} Trace: ${workflow.trace.length} event(s)
|
|
4958
|
+
const summary = `Workflow completed with ${workflow.children.length} child run(s). Return: ${returnPreview}${emitPreview} Trace: ${workflow.trace.length} event(s).${workflowOutputPathMappingSummary(workflow.children)}`;
|
|
4783
4959
|
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, summary, producedChildOutputPaths);
|
|
4784
4960
|
const resultSummary = appendWorkflowOutputWarning(summary, outputWarning);
|
|
4785
4961
|
const workflowUsage = sumResultsUsage(workflowResults);
|
|
4786
|
-
|
|
4787
|
-
|
|
4962
|
+
const workflowChildren = workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState: "completed", inventoryComplete: true, trace: workflow.trace, children: workflow.children, steps: status.steps });
|
|
4963
|
+
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) };
|
|
4964
|
+
const receipt = terminalWorkflowReceipt(workflowRunId, "complete", workflow.children, workflowChildren);
|
|
4788
4965
|
let workflowReceipt: { path: string; receipt: WorkflowReceipt } | undefined;
|
|
4789
4966
|
try {
|
|
4790
4967
|
workflowReceipt = { path: writeWorkflowReceipt(asyncDir, receipt), receipt };
|
|
4791
4968
|
} catch (receiptError) {
|
|
4792
4969
|
appendWorkflowEvent({ type: "subagent.workflow.receipt_write_failed", error: `Failed to persist async workflow receipt: ${receiptError instanceof Error ? receiptError.message : String(receiptError)}` });
|
|
4793
4970
|
}
|
|
4794
|
-
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.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;
|
|
4971
|
+
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.outputPathMapping ? { outputPathMapping: child.outputPathMapping } : {}), ...(child.stopped ? { stopped: true } : {}), ...(child.interrupted ? { interrupted: true } : {}), ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, ...(workflowReceipt ? { workflowReceipt } : {}), asyncDir, cwd: workflowCwd, sessionId: currentSessionId, completionOwnerId, ...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}), timestamp: Date.now(), durationMs: Date.now() - startedAt })) return;
|
|
4795
4972
|
persist();
|
|
4796
4973
|
persistClosed = true;
|
|
4797
4974
|
appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(status.error ? { error: status.error } : {}) });
|
|
@@ -4813,28 +4990,31 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4813
4990
|
delete step.activityState;
|
|
4814
4991
|
}
|
|
4815
4992
|
}
|
|
4816
|
-
|
|
4993
|
+
const workflowState = state === "paused" ? "paused" : state === "stopped" ? "stopped" : "failed";
|
|
4994
|
+
const workflowChildren = workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState, inventoryComplete: true, trace: partial.trace, children: partial.children, steps: status.steps });
|
|
4995
|
+
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 });
|
|
4817
4996
|
if (pauseForDetached) {
|
|
4818
4997
|
const promoted = promotePausedWorkflowIfSettled(status);
|
|
4819
4998
|
if (promoted) status = promoted;
|
|
4820
4999
|
}
|
|
4821
|
-
const terminalSummary = status.state === "complete"
|
|
5000
|
+
const terminalSummary = `${status.state === "complete"
|
|
4822
5001
|
? "Workflow completed after detached child finished."
|
|
4823
|
-
: status.error ?? (pauseForDetached ? "Workflow paused." : "Workflow failed.")
|
|
5002
|
+
: status.error ?? (pauseForDetached ? "Workflow paused." : "Workflow failed.")}${workflowOutputPathMappingSummary(partial.children)}`;
|
|
4824
5003
|
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, terminalSummary, producedChildOutputPaths);
|
|
4825
5004
|
const resultSummary = appendWorkflowOutputWarning(terminalSummary, outputWarning);
|
|
4826
5005
|
const receiptState: WorkflowReceiptState = status.state === "complete" ? "complete" : status.state === "paused" ? "paused" : status.state === "stopped" ? "stopped" : "failed";
|
|
4827
|
-
const
|
|
5006
|
+
const terminalOutcome = workflowFailureTerminalOutcome(error, partial.children, usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)));
|
|
5007
|
+
const receipt = terminalWorkflowReceipt(workflowRunId, receiptState, partial.children, workflowChildren, terminalOutcome);
|
|
4828
5008
|
let workflowReceipt: { path: string; receipt: WorkflowReceipt } | undefined;
|
|
4829
5009
|
try {
|
|
4830
5010
|
workflowReceipt = { path: writeWorkflowReceipt(asyncDir, receipt), receipt };
|
|
4831
5011
|
} catch (receiptError) {
|
|
4832
5012
|
appendWorkflowEvent({ type: "subagent.workflow.receipt_write_failed", error: `Failed to persist async workflow receipt: ${receiptError instanceof Error ? receiptError.message : String(receiptError)}` });
|
|
4833
5013
|
}
|
|
4834
|
-
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.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;
|
|
5014
|
+
if (!writeWorkflowResult({ id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: status.state === "complete", state: status.state, summary: resultSummary, error: status.state === "complete" ? undefined : status.error, stopped: status.stopped, activityState: status.activityState, workflowChildren, ...(terminalOutcome ? { terminalOutcome } : {}), results: partial.children.map((child) => ({ workflowKey: child.key, ...(child.agent ? { agent: child.agent } : {}), ...(child.runId ? { runId: child.runId } : {}), output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, success: child.ok, ...(child.terminalOutcome ? { terminalOutcome: child.terminalOutcome } : {}), ...(child.outputPathMapping ? { outputPathMapping: child.outputPathMapping } : {}), ...(child.stopped ? { stopped: true } : {}), ...(child.interrupted ? { interrupted: true } : {}), ...(child.detached && status.state !== "complete" ? { detached: true } : {}), ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, ...(workflowReceipt ? { workflowReceipt } : {}), asyncDir, cwd: workflowCwd, sessionId: currentSessionId, completionOwnerId, ...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}), timestamp: Date.now(), durationMs: Date.now() - startedAt })) return;
|
|
4835
5015
|
persist();
|
|
4836
5016
|
persistClosed = true;
|
|
4837
|
-
appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(status.error ? { error: status.error } : {}), ...(status.activityState ? { activityState: status.activityState } : {}) });
|
|
5017
|
+
appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(terminalOutcome ? { terminalOutcome } : {}), ...(status.error ? { error: status.error } : {}), ...(status.activityState ? { activityState: status.activityState } : {}) });
|
|
4838
5018
|
} finally {
|
|
4839
5019
|
persistClosed = true;
|
|
4840
5020
|
deps.state.workflowControllers?.delete(workflowRunId);
|
|
@@ -4845,7 +5025,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4845
5025
|
});
|
|
4846
5026
|
return attachWorkflowMission(withRunFanoutBudget({
|
|
4847
5027
|
content: [{ type: "text", text: formatAsyncStartedMessage(`Async workflow [${workflowRunId}]`, ctx.hasUI === true) }],
|
|
4848
|
-
details: { mode: "workflow", runId: workflowRunId, toolCallId, asyncId: workflowRunId, asyncDir, results: [], chatProgress, ...(deps.state.activeAsyncCapacity ? { activeAsyncCapacity: deps.state.activeAsyncCapacity } : {}) },
|
|
5028
|
+
details: { mode: "workflow", runId: workflowRunId, toolCallId, asyncId: workflowRunId, asyncDir, results: [], workflowChildren: status.workflowChildren, chatProgress, ...(deps.state.activeAsyncCapacity ? { activeAsyncCapacity: deps.state.activeAsyncCapacity } : {}) },
|
|
4849
5029
|
}, workflowFanoutBudget));
|
|
4850
5030
|
}
|
|
4851
5031
|
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;
|
|
@@ -4858,20 +5038,23 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4858
5038
|
const workflowResults: SingleResult[] = [];
|
|
4859
5039
|
const workflowChildRunIds = new Map<string, string>();
|
|
4860
5040
|
let liveWorkflow: NonNullable<Details["workflow"]> = { trace: [], emits: [], console: [] };
|
|
5041
|
+
let liveWorkflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: _id, workflowState: "running", inventoryComplete: false });
|
|
4861
5042
|
const workflowDeadlineAt = timeout === undefined ? undefined : Date.now() + timeout;
|
|
4862
5043
|
const workflowCapabilityCeiling = intersectSubagentCapabilityCeilings(requestParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(resolveCurrentSessionId(ctx.sessionManager)));
|
|
4863
5044
|
const sendWorkflowProgress = () => {
|
|
4864
|
-
const update = workflowChatProgressUpdate(_id, chatProgress, liveWorkflow);
|
|
5045
|
+
const update = workflowChatProgressUpdate(_id, chatProgress, liveWorkflow, liveWorkflowChildren);
|
|
4865
5046
|
if (update) onUpdate?.(update);
|
|
4866
5047
|
};
|
|
4867
5048
|
try {
|
|
4868
5049
|
const workflow = await runWorkflowScript({
|
|
4869
5050
|
script: requestParams.workflowScript,
|
|
5051
|
+
...(delegatedWorkflowPermit ? { oneUsePermit: { claim: (key: string) => claimWorkflowChildPermit(delegatedWorkflowPermit, _id, key) } } : {}),
|
|
4870
5052
|
timeoutMs: timeout,
|
|
4871
5053
|
signal,
|
|
4872
5054
|
...(workflowState ? { state: workflowState } : {}),
|
|
4873
5055
|
onTrace: (trace) => {
|
|
4874
5056
|
liveWorkflow = { ...liveWorkflow, trace };
|
|
5057
|
+
liveWorkflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: _id, workflowState: "running", inventoryComplete: false, trace });
|
|
4875
5058
|
sendWorkflowProgress();
|
|
4876
5059
|
},
|
|
4877
5060
|
admit: (calls) => {
|
|
@@ -4886,9 +5069,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4886
5069
|
sendWorkflowProgress();
|
|
4887
5070
|
},
|
|
4888
5071
|
launch: async (key, childParams, workflowSignal, admission) => {
|
|
5072
|
+
if (delegatedWorkflowPermit && admission.batch) throw new Error("Workflow child permit does not support runs.all.");
|
|
5073
|
+
if (delegatedWorkflowPermit && childParams.resume !== undefined) throw new Error("Workflow child permit does not support retained resume.");
|
|
5074
|
+
if (delegatedWorkflowPermit && childParams.async === true) throw new Error("Workflow child permit supports one foreground child only.");
|
|
4889
5075
|
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);
|
|
4890
5076
|
const budgetState = usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults));
|
|
4891
|
-
if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)), childParams, deps.state);
|
|
5077
|
+
if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)), childParams, deps.state, { state: "partial", reason: "budget_exhausted" });
|
|
4892
5078
|
const childPhase = typeof childParams.phase === "string" && childParams.phase.trim() ? childParams.phase.trim() : undefined;
|
|
4893
5079
|
const childLabel = typeof childParams.label === "string" && childParams.label.trim() ? childParams.label.trim() : undefined;
|
|
4894
5080
|
recordMissionWorkflowChild(missionBinding, _id, key, {
|
|
@@ -4904,6 +5090,15 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4904
5090
|
missionBinding,
|
|
4905
5091
|
deps.asyncByDefault,
|
|
4906
5092
|
);
|
|
5093
|
+
if (delegatedWorkflowPermit) {
|
|
5094
|
+
if (childRequest.async !== false) throw new Error("Workflow child permit supports one foreground child only.");
|
|
5095
|
+
const childCwd = resolveRequestedCwd(workflowCwd, childRequest.cwd);
|
|
5096
|
+
const childAgent = typeof childRequest.agent === "string"
|
|
5097
|
+
? resolveAgentName(childRequest.agent, deps.discoverAgents(childCwd, resolveExecutionAgentScope(childRequest.agentScope)).agents).agent
|
|
5098
|
+
: undefined;
|
|
5099
|
+
if (childAgent?.runner?.type === "external-cli" || childAgent?.runner?.type === "external-job") throw new Error("Workflow child permit supports native Pi children only.");
|
|
5100
|
+
workflowPermitContexts.set(childRequest, { child: { permit: delegatedWorkflowPermit, workflowRunId: _id, childKey: key } });
|
|
5101
|
+
}
|
|
4907
5102
|
workflowLaunchObservers.set(childRequest, (launch) => recordMissionWorkflowChild(missionBinding, _id, key, {
|
|
4908
5103
|
status: "running",
|
|
4909
5104
|
agent: launch.agent,
|
|
@@ -4917,7 +5112,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4917
5112
|
status: progressStatus,
|
|
4918
5113
|
heartbeat: { status: progressStatus, ...(childPhase ? { phase: childPhase } : {}) },
|
|
4919
5114
|
});
|
|
4920
|
-
}, ctx, preserveActiveSession);
|
|
5115
|
+
}, ctx, preserveActiveSession, workflowParentModel);
|
|
4921
5116
|
});
|
|
4922
5117
|
workflowResults.push(...result.details.results);
|
|
4923
5118
|
for (const childResult of result.details.results) {
|
|
@@ -4938,43 +5133,51 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4938
5133
|
});
|
|
4939
5134
|
return child;
|
|
4940
5135
|
},
|
|
4941
|
-
status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession)),
|
|
5136
|
+
status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession, workflowParentModel)),
|
|
4942
5137
|
resolveResume: (reference) => resolveKeyedWorkflowResume(reference, deps.state),
|
|
4943
5138
|
steer: (key, message, options, workflowSignal) => steerWorkflowChildByKey({ state: deps.state, workflowRunId: _id, key, message, options, signal: workflowSignal, resolveRunId: () => workflowChildRunIds.get(key) }),
|
|
4944
5139
|
});
|
|
4945
|
-
const
|
|
5140
|
+
const workflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: _id, workflowState: "completed", inventoryComplete: true, trace: workflow.trace, children: workflow.children });
|
|
5141
|
+
const receipt = terminalWorkflowReceipt(_id, "complete", workflow.children, workflowChildren);
|
|
4946
5142
|
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}` : ""}`);
|
|
4947
5143
|
const sections = ["Workflow completed.", `Return:\n${formatWorkflowValue(workflow.value)}`];
|
|
4948
5144
|
if (workflow.emits.length > 0) sections.push(`Emitted:\n${workflow.emits.map(formatWorkflowValue).join("\n")}`);
|
|
4949
5145
|
if (workflow.console.length > 0) sections.push(`Console:\n${workflow.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
|
|
4950
5146
|
if (traceLines.length > 0) sections.push(`Call trace:\n${traceLines.join("\n")}`);
|
|
5147
|
+
const outputMappings = workflowOutputPathMappingSummary(workflow.children).trim();
|
|
5148
|
+
if (outputMappings) sections.push(outputMappings);
|
|
4951
5149
|
const workflowText = sections.join("\n\n");
|
|
4952
5150
|
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, workflowText, producedChildOutputPaths);
|
|
4953
5151
|
const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
|
|
4954
5152
|
return attachWorkflowMission(withRunFanoutBudget({
|
|
4955
5153
|
content: [{ type: "text", text: displayText }],
|
|
4956
|
-
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 }),
|
|
5154
|
+
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 }),
|
|
4957
5155
|
}, workflowFanoutBudget));
|
|
4958
5156
|
} catch (error) {
|
|
4959
5157
|
const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
|
|
4960
|
-
const text = error
|
|
5158
|
+
const text = workflowFailureMessage(error, _id, partial.children);
|
|
4961
5159
|
const traceLines = partial.trace.map((entry) => `- ${entry.operation} ${entry.key}: ${entry.state}${entry.runId ? ` (${entry.runId})` : ""}${entry.error ? ` — ${entry.error}` : ""}`);
|
|
4962
5160
|
const sections = [`Workflow failed: ${text}`];
|
|
4963
5161
|
if (partial.emits.length > 0) sections.push(`Emitted:\n${partial.emits.map(formatWorkflowValue).join("\n")}`);
|
|
4964
5162
|
if (partial.console.length > 0) sections.push(`Console:\n${partial.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
|
|
4965
5163
|
if (traceLines.length > 0) sections.push(`Call trace:\n${traceLines.join("\n")}`);
|
|
5164
|
+
const outputMappings = workflowOutputPathMappingSummary(partial.children).trim();
|
|
5165
|
+
if (outputMappings) sections.push(outputMappings);
|
|
4966
5166
|
const workflowText = sections.join("\n\n");
|
|
4967
5167
|
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, workflowText, producedChildOutputPaths);
|
|
4968
5168
|
const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
|
|
4969
|
-
const
|
|
5169
|
+
const workflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: _id, workflowState: "failed", inventoryComplete: true, trace: partial.trace, children: partial.children });
|
|
5170
|
+
const terminalOutcome = workflowFailureTerminalOutcome(error, partial.children, usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)));
|
|
5171
|
+
const receipt = terminalWorkflowReceipt(_id, "failed", partial.children, workflowChildren, terminalOutcome);
|
|
4970
5172
|
return attachWorkflowMission(withRunFanoutBudget({
|
|
4971
5173
|
content: [{ type: "text", text: displayText }],
|
|
4972
5174
|
isError: true,
|
|
4973
|
-
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 }),
|
|
5175
|
+
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 }),
|
|
4974
5176
|
}, workflowFanoutBudget));
|
|
4975
5177
|
}
|
|
4976
5178
|
}
|
|
4977
5179
|
const directParams = requestParams;
|
|
5180
|
+
const requestedCwd = directParams.cwd;
|
|
4978
5181
|
const requestCwd = resolveRequestedCwd(ctx.cwd, directParams.cwd);
|
|
4979
5182
|
const paramsWithResolvedCwd = directParams.cwd === undefined ? directParams : { ...directParams, cwd: requestCwd };
|
|
4980
5183
|
const action = paramsWithResolvedCwd.action;
|
|
@@ -4984,9 +5187,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4984
5187
|
try {
|
|
4985
5188
|
requestSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
4986
5189
|
requestPiSessionId = ctx.sessionManager.getSessionId() ?? undefined;
|
|
4987
|
-
requestParentModel =
|
|
4988
|
-
?
|
|
4989
|
-
:
|
|
5190
|
+
requestParentModel = parentModelOverride !== undefined
|
|
5191
|
+
? parentModelOverride ?? undefined
|
|
5192
|
+
: preserveActiveSession
|
|
5193
|
+
? normalizeParentModel(ctx.model)
|
|
5194
|
+
: rememberParentModel(deps.state, requestSessionId, ctx.model);
|
|
4990
5195
|
} catch (error) {
|
|
4991
5196
|
if (action?.toLowerCase() !== "doctor" && action?.toLowerCase() !== "guide") throw error;
|
|
4992
5197
|
requestParentModel = normalizeParentModel(ctx.model);
|
|
@@ -5211,7 +5416,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5211
5416
|
}
|
|
5212
5417
|
const spawnBudget = getSpawnBudgetSnapshot(deps.state, deps.config, currentSessionId);
|
|
5213
5418
|
const activeAsyncCapacity = currentSessionId
|
|
5214
|
-
? getActiveAsyncCapacitySnapshot(currentSessionId, resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession), { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) })
|
|
5419
|
+
? getActiveAsyncCapacitySnapshot(currentSessionId, resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession), { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })
|
|
5215
5420
|
: { used: 0, limit: resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession) ?? 0 };
|
|
5216
5421
|
deps.state.activeAsyncCapacity = activeAsyncCapacity;
|
|
5217
5422
|
return {
|
|
@@ -5256,10 +5461,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5256
5461
|
if (paramsWithResolvedCwd.view) {
|
|
5257
5462
|
return withBudget({ content: [{ type: "text", text: "action='debug.run' does not support status views." }], isError: true, details: { mode: "management", results: [] } });
|
|
5258
5463
|
}
|
|
5259
|
-
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots })));
|
|
5464
|
+
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots, abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })));
|
|
5260
5465
|
}
|
|
5261
5466
|
if (paramsWithResolvedCwd.view === "fleet") {
|
|
5262
|
-
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots })));
|
|
5467
|
+
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots, abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })));
|
|
5263
5468
|
}
|
|
5264
5469
|
if (targetRunId) {
|
|
5265
5470
|
try {
|
|
@@ -5290,7 +5495,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5290
5495
|
});
|
|
5291
5496
|
}
|
|
5292
5497
|
}
|
|
5293
|
-
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots })));
|
|
5498
|
+
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots, abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })));
|
|
5294
5499
|
}
|
|
5295
5500
|
if (action === "resume") {
|
|
5296
5501
|
return resumeAsyncRun(omitUndefinedProperties({ params: paramsWithResolvedCwd, requestCwd, ctx, deps, parentModel: requestParentModel, signal }));
|
|
@@ -5446,7 +5651,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5446
5651
|
if (!isStoppableAsyncStatusStep(resolution.child.step)) return { content: [{ type: "text", text: `Child '${paramsWithResolvedCwd.childId}' in async run '${targetRunId}' is ${resolution.child.step.status}; stop only supports pending or running children.` }], isError: true, details: { mode: "management", results: [] } };
|
|
5447
5652
|
const stopChild = deps.state.workflowChildStops?.get(targetRunId!);
|
|
5448
5653
|
if (!stopChild) return { content: [{ type: "text", text: `Workflow ${targetRunId} child stop is unavailable in this extension runtime.` }], isError: true, details: { mode: "management", results: [] } };
|
|
5449
|
-
if (!stopChild(resolution.child.id)) return { content: [{ type: "text", text: `Child '${paramsWithResolvedCwd.childId}' in workflow ${workflowRunId} is not available to stop.` }], isError: true, details: { mode: "management", results: [] } };
|
|
5654
|
+
if (!stopChild(resolution.child.id, `Workflow child '${resolution.child.id}' stopped.`)) return { content: [{ type: "text", text: `Child '${paramsWithResolvedCwd.childId}' in workflow ${workflowRunId} is not available to stop.` }], isError: true, details: { mode: "management", results: [] } };
|
|
5450
5655
|
try {
|
|
5451
5656
|
fs.appendFileSync(path.join(asyncJob.asyncDir, "events.jsonl"), `${JSON.stringify({
|
|
5452
5657
|
type: "subagent.child-status",
|
|
@@ -5455,7 +5660,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5455
5660
|
childId: resolution.child.id,
|
|
5456
5661
|
status: "stopping",
|
|
5457
5662
|
ts: Date.now(),
|
|
5458
|
-
reason: "
|
|
5663
|
+
reason: "subagent-action",
|
|
5459
5664
|
source: "async",
|
|
5460
5665
|
stepIndex: resolution.child.index,
|
|
5461
5666
|
agent: resolution.child.step.agent,
|
|
@@ -5469,17 +5674,13 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5469
5674
|
}
|
|
5470
5675
|
return { content: [{ type: "text", text: `Stop requested for child ${resolution.child.id} in async workflow ${workflowRunId}.` }], details: { mode: "management", results: [] } };
|
|
5471
5676
|
}
|
|
5472
|
-
workflowController.abort(new Error("Workflow stopped
|
|
5677
|
+
workflowController.abort(new Error("Workflow stopped."));
|
|
5473
5678
|
return { content: [{ type: "text", text: `Stop requested for async workflow ${targetRunId}.` }], details: { mode: "management", results: [] } };
|
|
5474
5679
|
}
|
|
5475
5680
|
let resolved: ResolvedSubagentRunId | undefined;
|
|
5476
5681
|
if (paramsWithResolvedCwd.dir) {
|
|
5477
5682
|
try {
|
|
5478
5683
|
const location = resolveAsyncRunLocation(paramsWithResolvedCwd, DIRS.async, DIRS.results);
|
|
5479
|
-
const existingStatus = readStatus(location.asyncDir ?? "");
|
|
5480
|
-
if (existingStatus?.mode === "workflow" && existingStatus.state === "running") {
|
|
5481
|
-
return { content: [{ type: "text", text: `Workflow ${existingStatus.runId} is not controlled by this extension runtime; reload recovery cannot stop it safely.` }], isError: true, details: { mode: "management", results: [] } };
|
|
5482
|
-
}
|
|
5483
5684
|
const stopResult = stopAsyncRun(deps.state, location.resolvedId ?? targetRunId ?? path.basename(location.asyncDir ?? paramsWithResolvedCwd.dir), deps.kill, location, paramsWithResolvedCwd.childId);
|
|
5484
5685
|
return stopResult ?? { content: [{ type: "text", text: `No running or queued async run was found for '${targetRunId ?? paramsWithResolvedCwd.dir}'.` }], isError: true, details: { mode: "management", results: [] } };
|
|
5485
5686
|
} catch (error) {
|
|
@@ -5496,12 +5697,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5496
5697
|
}
|
|
5497
5698
|
if (resolved?.kind === "nested") return { content: [{ type: "text", text: "action='stop' supports current-session top-level async runs only." }], isError: true, details: { mode: "management", results: [] } };
|
|
5498
5699
|
if (resolved?.kind === "foreground") return { content: [{ type: "text", text: "action='stop' supports async runs only. Use action='interrupt' for foreground runs." }], isError: true, details: { mode: "management", results: [] } };
|
|
5499
|
-
if (resolved?.kind === "async") {
|
|
5500
|
-
const existingStatus = readStatus(resolved.location.asyncDir ?? "");
|
|
5501
|
-
if (existingStatus?.mode === "workflow" && existingStatus.state === "running") {
|
|
5502
|
-
return { content: [{ type: "text", text: `Workflow ${resolved.id} is not controlled by this extension runtime; reload recovery cannot stop it safely.` }], isError: true, details: { mode: "management", results: [] } };
|
|
5503
|
-
}
|
|
5504
|
-
}
|
|
5505
5700
|
const stopResult = stopAsyncRun(
|
|
5506
5701
|
deps.state,
|
|
5507
5702
|
resolved?.kind === "async" ? resolved.id : targetRunId,
|
|
@@ -5616,7 +5811,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5616
5811
|
const parentSessionFile = ctx.sessionManager.getSessionFile() ?? null;
|
|
5617
5812
|
const discovered = deps.discoverAgents(effectiveCwd, scope);
|
|
5618
5813
|
const discoveredAgents = discovered.agents;
|
|
5619
|
-
const
|
|
5814
|
+
const unknownAgentDiagnosticContext = diagnosticContextFromDiscovery(discovered, effectiveCwd, scope);
|
|
5815
|
+
const canonicalParams = canonicalizeExecutionParams(effectiveParams, discoveredAgents, discovered.agentDiagnostics, unknownAgentDiagnosticContext);
|
|
5620
5816
|
if (canonicalParams.error) return buildRequestedModeError(effectiveParams, canonicalParams.error);
|
|
5621
5817
|
effectiveParams = canonicalParams.params!;
|
|
5622
5818
|
const modelScope = discovered.modelScope;
|
|
@@ -5665,6 +5861,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5665
5861
|
hasTasks,
|
|
5666
5862
|
hasSingle,
|
|
5667
5863
|
allowClarifyTaskPrompt,
|
|
5864
|
+
unknownAgentDiagnosticContext,
|
|
5668
5865
|
);
|
|
5669
5866
|
if (validationError) return validationError;
|
|
5670
5867
|
|
|
@@ -5679,6 +5876,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5679
5876
|
if (spawnPreflight.error) return spawnBudgetErrorResult(spawnPreflight.error, foregroundMode);
|
|
5680
5877
|
|
|
5681
5878
|
let forkSessionFileForIndex: (idx?: number) => string | undefined = () => undefined;
|
|
5879
|
+
let prepareForkSessionForIndex: (idx?: number) => Promise<void> = async () => {};
|
|
5682
5880
|
let forkThinkingOverrideForIndex: (idx?: number) => AgentConfig["thinking"] | undefined = () => undefined;
|
|
5683
5881
|
let prepareForkThinking = (_agentName: string, _index: number, _modelOverride?: string, _modelOverrideFromParent?: boolean): void => {};
|
|
5684
5882
|
const forkThinkingRequirements = new Map<number, boolean>();
|
|
@@ -5700,6 +5898,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5700
5898
|
parentModel,
|
|
5701
5899
|
forkAvailableModels,
|
|
5702
5900
|
agentConfig?.modelProvider ?? parentModel?.provider,
|
|
5901
|
+
{ source: "inherited" },
|
|
5703
5902
|
);
|
|
5704
5903
|
const candidates = buildModelCandidates(
|
|
5705
5904
|
primaryModel,
|
|
@@ -5714,9 +5913,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5714
5913
|
|| candidates.some((candidate) => forkedChildRequiresThinkingOff(candidate, forkAvailableModels, parentModel?.provider)),
|
|
5715
5914
|
);
|
|
5716
5915
|
};
|
|
5916
|
+
const pruneSession = contextPolicy.usesFork && deps.config.forkContext?.mode === "pruned"
|
|
5917
|
+
? await createPrunedForkSessionWriter(ctx, deps.config.forkContext, signal)
|
|
5918
|
+
: undefined;
|
|
5717
5919
|
const forkContextResolver = createForkContextResolver(ctx.sessionManager, contextPolicy.usesFork ? "fork" : undefined, {
|
|
5718
5920
|
forceThinkingOffForIndex: (index) => forkThinkingRequirements.get(index) ?? true,
|
|
5921
|
+
...(pruneSession ? { pruneSession } : {}),
|
|
5719
5922
|
});
|
|
5923
|
+
prepareForkSessionForIndex = forkContextResolver.prepareSessionForIndex;
|
|
5720
5924
|
forkSessionFileForIndex = forkContextResolver.sessionFileForIndex;
|
|
5721
5925
|
forkThinkingOverrideForIndex = forkContextResolver.thinkingOverrideForIndex;
|
|
5722
5926
|
} catch (error) {
|
|
@@ -5760,8 +5964,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5760
5964
|
runId: asyncRunId,
|
|
5761
5965
|
kind: "runner",
|
|
5762
5966
|
asyncDir: path.join(DIRS.async, asyncRunId),
|
|
5763
|
-
}, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) });
|
|
5764
|
-
deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(requestSessionId, activeLimit, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) });
|
|
5967
|
+
}, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) });
|
|
5968
|
+
deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(requestSessionId, activeLimit, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) });
|
|
5765
5969
|
} catch (error) {
|
|
5766
5970
|
if (error instanceof ActiveAsyncCapacityError) {
|
|
5767
5971
|
deps.state.activeAsyncCapacity = error.snapshot;
|
|
@@ -5824,6 +6028,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5824
6028
|
prepareForkThinking(agentName, idx, modelOverride, modelOverrideFromParent);
|
|
5825
6029
|
return forkSessionFileForIndex(idx);
|
|
5826
6030
|
};
|
|
6031
|
+
const prepareForkSessionForTask: PrepareForkSessionForTask = async (agentName, idx = 0, modelOverride, modelOverrideFromParent) => {
|
|
6032
|
+
if (!shouldForkAgent(contextPolicy, agentName)) return;
|
|
6033
|
+
prepareForkThinking(agentName, idx, modelOverride, modelOverrideFromParent);
|
|
6034
|
+
await prepareForkSessionForIndex(idx);
|
|
6035
|
+
};
|
|
5827
6036
|
const forkThinkingOverrideForTask: ForkThinkingOverrideForTask = (agentName, idx = 0, modelOverride, modelOverrideFromParent) => {
|
|
5828
6037
|
if (!shouldForkAgent(contextPolicy, agentName)) return delegatedThinkingOverride;
|
|
5829
6038
|
prepareForkThinking(agentName, idx, modelOverride, modelOverrideFromParent);
|
|
@@ -5836,8 +6045,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5836
6045
|
const childSessionFileForIndex = (idx?: number) =>
|
|
5837
6046
|
path.join(sessionDirForIndex(idx), "session.jsonl");
|
|
5838
6047
|
try {
|
|
5839
|
-
if (!(effectiveParams.clarify === true && ctx.hasUI)) {
|
|
5840
|
-
preflightForkSessionsForStaticTasks(effectiveParams, contextPolicy,
|
|
6048
|
+
if (!(effectiveParams.clarify === true && ctx.hasUI) || deps.config.forkContext?.mode === "pruned") {
|
|
6049
|
+
await preflightForkSessionsForStaticTasks(effectiveParams, contextPolicy, prepareForkSessionForTask, deps.config.chain?.dynamicFanout?.maxItems);
|
|
5841
6050
|
}
|
|
5842
6051
|
} catch (error) {
|
|
5843
6052
|
activeAsyncCapacity?.rollback();
|
|
@@ -5906,10 +6115,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5906
6115
|
const execData: ExecutionContextData = omitUndefinedProperties({
|
|
5907
6116
|
params: effectiveParams,
|
|
5908
6117
|
effectiveCwd,
|
|
6118
|
+
requestedCwd,
|
|
5909
6119
|
ctx,
|
|
5910
6120
|
signal,
|
|
5911
6121
|
onUpdate: onUpdateWithContext,
|
|
5912
6122
|
agents,
|
|
6123
|
+
unknownAgentDiagnosticContext,
|
|
5913
6124
|
recoveryAgents: discoveredAgents,
|
|
5914
6125
|
runId,
|
|
5915
6126
|
shareEnabled,
|
|
@@ -5942,6 +6153,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5942
6153
|
runFanoutBudget,
|
|
5943
6154
|
topLevelAsyncCapacityEligible,
|
|
5944
6155
|
activeAsyncCapacity,
|
|
6156
|
+
workflowChildPermitLaunch,
|
|
5945
6157
|
});
|
|
5946
6158
|
|
|
5947
6159
|
const foregroundDescription = selectedAgentNames.length === 1
|
|
@@ -6192,7 +6404,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6192
6404
|
if (!normalized.ok) {
|
|
6193
6405
|
return Promise.resolve({ content: [{ type: "text", text: normalized.error }], isError: true, details: { mode: normalized.mode, results: [] } });
|
|
6194
6406
|
}
|
|
6195
|
-
|
|
6407
|
+
const loaded = loadWorkflowScriptPath(normalized.params, ctx.cwd);
|
|
6408
|
+
if (loaded.error) {
|
|
6409
|
+
return Promise.resolve({ content: [{ type: "text", text: loaded.error }], isError: true, details: { mode: normalized.params.action ? "management" : "workflow", results: [] } });
|
|
6410
|
+
}
|
|
6411
|
+
return executeWithSingleDispatchGuard(id, loaded.params!, signal, onUpdate, ctx);
|
|
6196
6412
|
};
|
|
6197
6413
|
|
|
6198
6414
|
const executeDelegated = async (
|
|
@@ -6206,13 +6422,17 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6206
6422
|
const privateParams = delegatedParams as SubagentParamsLike & {
|
|
6207
6423
|
delegatedThinkingOverride?: AgentConfig["thinking"];
|
|
6208
6424
|
delegatedAllowZeroToolBudget?: true;
|
|
6425
|
+
delegatedWorkflowPermit?: WorkflowChildPermit;
|
|
6209
6426
|
};
|
|
6210
6427
|
const thinkingOverride = privateParams.delegatedThinkingOverride;
|
|
6211
6428
|
const allowZeroToolBudget = privateParams.delegatedAllowZeroToolBudget === true;
|
|
6429
|
+
const workflowPermit = privateParams.delegatedWorkflowPermit;
|
|
6212
6430
|
delete privateParams.delegatedThinkingOverride;
|
|
6213
6431
|
delete privateParams.delegatedAllowZeroToolBudget;
|
|
6432
|
+
delete privateParams.delegatedWorkflowPermit;
|
|
6214
6433
|
if (thinkingOverride !== undefined) delegatedThinkingOverrides.set(delegatedParams, thinkingOverride);
|
|
6215
6434
|
if (allowZeroToolBudget) delegatedZeroToolBudgets.add(delegatedParams);
|
|
6435
|
+
if (workflowPermit) workflowPermitContexts.set(delegatedParams, { root: workflowPermit });
|
|
6216
6436
|
delegatedExecutions.add(delegatedParams);
|
|
6217
6437
|
return execute(id, delegatedParams, signal, onUpdate, ctx);
|
|
6218
6438
|
};
|