pi-subagents 0.56.0 → 0.57.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +53 -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 +111 -15
- package/docs/configuration.md +37 -0
- package/docs/extension-api.md +2 -2
- package/docs/observability.md +8 -7
- package/docs/tool-reference.md +11 -1
- package/docs/workflows.md +20 -0
- package/package.json +1 -1
- package/skills/pi-subagents/references/execution-controls.md +1 -1
- package/src/agents/agent-management.ts +13 -5
- package/src/agents/agent-refinements.ts +4 -4
- package/src/agents/agents.ts +219 -48
- package/src/agents/builtin-names.ts +6 -0
- package/src/agents/runtime-agent-registry.ts +8 -2
- package/src/api/preflight.ts +2 -2
- package/src/extension/config.ts +70 -0
- package/src/extension/doctor.ts +3 -3
- package/src/extension/index.ts +18 -4
- package/src/extension/public-execution.ts +29 -13
- package/src/extension/rpc.ts +20 -5
- package/src/extension/schemas.ts +4 -3
- package/src/extension/tool-description.ts +11 -9
- package/src/inspectors/herdr/actions.ts +2 -1
- package/src/inspectors/herdr/inspector-runner.ts +2 -10
- package/src/inspectors/herdr/session-roots-codec.ts +42 -0
- package/src/integrations/herdr-status.ts +46 -3
- package/src/runs/background/active-async-capacity.ts +77 -10
- package/src/runs/background/async-execution.ts +100 -9
- package/src/runs/background/async-job-tracker.ts +5 -0
- package/src/runs/background/async-retention.ts +20 -3
- package/src/runs/background/async-status.ts +5 -0
- package/src/runs/background/chain-root-attachment.ts +15 -1
- package/src/runs/background/fleet-view.ts +16 -10
- package/src/runs/background/inspect-rpc.ts +8 -8
- package/src/runs/background/result-files.ts +27 -14
- package/src/runs/background/result-watcher.ts +9 -5
- package/src/runs/background/run-status.ts +33 -6
- package/src/runs/background/scheduled-runs.ts +7 -1
- package/src/runs/background/subagent-runner.ts +177 -44
- package/src/runs/background/wait-completions.ts +4 -0
- package/src/runs/foreground/execution.ts +72 -10
- 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 +270 -81
- package/src/runs/foreground/workflow-detach-reconcile.ts +16 -1
- package/src/runs/shared/child-protocol.ts +21 -7
- package/src/runs/shared/claude-code-adapter.ts +129 -0
- package/src/runs/shared/codex-exec-adapter.ts +129 -0
- package/src/runs/shared/cursor-agent-adapter.ts +114 -0
- package/src/runs/shared/external-cli-contract.ts +167 -0
- package/src/runs/shared/external-cli-preflight.ts +122 -0
- package/src/runs/shared/external-cli-runner.ts +348 -55
- package/src/runs/shared/model-exclusions.ts +57 -7
- package/src/runs/shared/model-fallback.ts +23 -1
- 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/process-signal.ts +13 -0
- package/src/runs/shared/run-history.ts +21 -1
- package/src/shared/fork-context.ts +21 -0
- package/src/shared/formatters.ts +7 -1
- 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 +118 -9
- package/src/shared/workflow-child-permit.ts +116 -0
- package/src/slash/delegation-adapters.ts +0 -1
- package/src/slash/slash-commands.ts +8 -6
- package/src/tui/fleet-status.ts +27 -10
- package/src/tui/fleet-transcript.ts +11 -5
- package/src/tui/fleet.ts +28 -13
- package/src/tui/render.ts +47 -21
- package/src/workflows/scripted-workflow.ts +248 -27
- package/src/workflows/workflow-child-summary.ts +117 -0
- package/src/workflows/workflow-receipt.ts +119 -4
|
@@ -3,7 +3,7 @@ import * as fs from "node:fs";
|
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
5
5
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
6
|
-
import { findBlockingAgentDiagnostic, resolveAgentName, type AgentConfig, type AgentDiscoveryDiagnostic, type AgentScope } from "../../agents/agents.ts";
|
|
6
|
+
import { discoverAgents, findBlockingAgentDiagnostic, formatUnknownAgentError, resolveAgentName, unknownAgentDiagnosticContext, type AgentConfig, type AgentDiscoveryDiagnostic, type AgentScope, type UnknownAgentDiagnosticContext } from "../../agents/agents.ts";
|
|
7
7
|
import { getArtifactsDir, getChainRunsDir, getProjectArtifactPackagingWarning, getProjectSubagentsDir } from "../../shared/artifacts.ts";
|
|
8
8
|
import { writeAtomicJson } from "../../shared/atomic-json.ts";
|
|
9
9
|
import { createCapacityResilientJsonWriter } from "../../shared/capacity-resilient-json.ts";
|
|
@@ -56,12 +56,13 @@ import { discoverAvailableSkills, normalizeSkillInput } from "../../agents/skill
|
|
|
56
56
|
import { buildAsyncRunnerSteps, DEFAULT_ASYNC_TIMEOUT_MS, executeAsyncChain, executeAsyncSingle, formatAsyncStartedMessage, isAsyncAvailable, workflowAwaitedAsyncResultPath } from "../background/async-execution.ts";
|
|
57
57
|
import { updateActiveRunIndex } from "../background/active-run-index.ts";
|
|
58
58
|
import { steeringReceipt } from "../background/steering.ts";
|
|
59
|
-
import { acquireActiveAsyncCapacity, ActiveAsyncCapacityError, getActiveAsyncCapacitySnapshot, resolveMaxActiveAsyncRunsPerSession, transferActiveAsyncCapacity, type ActiveAsyncCapacityHandle } from "../background/active-async-capacity.ts";
|
|
59
|
+
import { acquireActiveAsyncCapacity, ActiveAsyncCapacityError, getActiveAsyncCapacitySnapshot, resolveAbandonedSlotReleaseAfterMs, resolveMaxActiveAsyncRunsPerSession, transferActiveAsyncCapacity, type ActiveAsyncCapacityHandle } from "../background/active-async-capacity.ts";
|
|
60
60
|
import { isScheduledRunAction, type ScheduledRunAction } from "../background/scheduled-runs.ts";
|
|
61
61
|
import { enqueueChainAppendRequest, readPendingChainAppendRequests, runnerStepOutputNames } from "../background/chain-append.ts";
|
|
62
62
|
import { ChainOutputValidationError, validateChainOutputBindingsWithContext } from "../shared/chain-outputs.ts";
|
|
63
63
|
import { normalizeGateAcceptance, validateExecutionAcceptance } from "../shared/acceptance.ts";
|
|
64
64
|
import { canPreferFork, createForkContextResolver, forkedChildRequiresThinkingOff, resolveSubagentLaunchContext } from "../../shared/fork-context.ts";
|
|
65
|
+
import { createPrunedForkSessionWriter } from "../../shared/pruned-fork.ts";
|
|
65
66
|
import { resolveCurrentSessionId } from "../../shared/session-identity.ts";
|
|
66
67
|
import { currentCompletionOwnerId } from "../../shared/completion-owner.ts";
|
|
67
68
|
import { applyIntercomBridgeToAgent, INTERCOM_BRIDGE_MARKER, resolveIntercomBridge, resolveIntercomSessionTarget, resolveSubagentIntercomTarget, type IntercomBridgeState } from "../../intercom/intercom-bridge.ts";
|
|
@@ -75,7 +76,7 @@ import { intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCe
|
|
|
75
76
|
import { isAgentContractV1 } from "../shared/agent-contract.ts";
|
|
76
77
|
import { normalizeExtensionBindings, type ExtensionBindings } from "../shared/extension-bindings.ts";
|
|
77
78
|
import { finalizeSingleOutput, injectSingleOutputInstruction, normalizeSingleOutputOverride, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
|
|
78
|
-
import { cleanupStructuredOutputRuntime, createStructuredOutputRuntime } from "../shared/structured-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,
|
|
@@ -298,6 +304,7 @@ export interface SubagentParamsLike {
|
|
|
298
304
|
steeringRecovery?: boolean;
|
|
299
305
|
mode?: SteerDeliveryMode;
|
|
300
306
|
workflowScript?: string;
|
|
307
|
+
workflowScriptPath?: string;
|
|
301
308
|
chatProgress?: "auto" | "off" | "live-card";
|
|
302
309
|
isolation?: "none" | "worktree";
|
|
303
310
|
step?: ChainStep;
|
|
@@ -397,7 +404,7 @@ interface ExecutorDeps {
|
|
|
397
404
|
tempArtifactsDir: string;
|
|
398
405
|
getSubagentSessionRoot: (parentSessionFile: string | null) => string;
|
|
399
406
|
expandTilde: (p: string) => string;
|
|
400
|
-
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[]; agentDiagnostics?: AgentDiscoveryDiagnostic[]; modelScope?: ModelScopeConfig; maxThinking?: AgentConfig["maxThinking"] };
|
|
407
|
+
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[]; agentDiagnostics?: AgentDiscoveryDiagnostic[]; modelScope?: ModelScopeConfig; maxThinking?: AgentConfig["maxThinking"]; cwd?: string; scope?: AgentScope; directories?: UnknownAgentDiagnosticContext["directories"] };
|
|
401
408
|
allowMutatingManagementActions?: boolean;
|
|
402
409
|
activateSupervisorTransport?: () => void;
|
|
403
410
|
refreshResultDelivery?: () => void;
|
|
@@ -405,6 +412,7 @@ interface ExecutorDeps {
|
|
|
405
412
|
}
|
|
406
413
|
|
|
407
414
|
type ForkSessionFileForTask = (agentName: string, idx?: number, modelOverride?: string, modelOverrideFromParent?: boolean) => string | undefined;
|
|
415
|
+
type PrepareForkSessionForTask = (agentName: string, idx?: number, modelOverride?: string, modelOverrideFromParent?: boolean) => Promise<void>;
|
|
408
416
|
type ForkThinkingOverrideForTask = (agentName: string, idx?: number, modelOverride?: string, modelOverrideFromParent?: boolean) => AgentConfig["thinking"] | undefined;
|
|
409
417
|
|
|
410
418
|
interface ExecutionContextData {
|
|
@@ -414,6 +422,8 @@ interface ExecutionContextData {
|
|
|
414
422
|
signal: AbortSignal;
|
|
415
423
|
onUpdate?: (r: AgentToolResult<Details>) => void;
|
|
416
424
|
agents: AgentConfig[];
|
|
425
|
+
/** Exact filesystem discovery provenance retained through defensive execution backstops. */
|
|
426
|
+
unknownAgentDiagnosticContext: UnknownAgentDiagnosticContext;
|
|
417
427
|
/** Discovered agent definitions before per-run bridge injection. */
|
|
418
428
|
recoveryAgents: AgentConfig[];
|
|
419
429
|
runId: string;
|
|
@@ -449,12 +459,27 @@ interface ExecutionContextData {
|
|
|
449
459
|
runFanoutBudget: RunFanoutBudgetDescriptor;
|
|
450
460
|
topLevelAsyncCapacityEligible: boolean;
|
|
451
461
|
activeAsyncCapacity?: ActiveAsyncCapacityHandle;
|
|
462
|
+
workflowChildPermitLaunch?: WorkflowChildPermitContext;
|
|
452
463
|
}
|
|
453
464
|
|
|
454
465
|
function resolveRequestedCwd(runtimeCwd: string, requestedCwd: string | undefined): string {
|
|
455
466
|
return requestedCwd ? path.resolve(runtimeCwd, requestedCwd) : runtimeCwd;
|
|
456
467
|
}
|
|
457
468
|
|
|
469
|
+
function loadWorkflowScriptPath(params: SubagentParamsLike, runtimeCwd: string): { params?: SubagentParamsLike; error?: string } {
|
|
470
|
+
if (params.workflowScriptPath === undefined) return { params };
|
|
471
|
+
const scriptPath = path.resolve(resolveRequestedCwd(runtimeCwd, params.cwd), params.workflowScriptPath);
|
|
472
|
+
let workflowScript: string;
|
|
473
|
+
try {
|
|
474
|
+
workflowScript = fs.readFileSync(scriptPath, "utf8");
|
|
475
|
+
} catch (error) {
|
|
476
|
+
return { error: `Failed to read workflowScriptPath '${scriptPath}': ${error instanceof Error ? error.message : String(error)}` };
|
|
477
|
+
}
|
|
478
|
+
if (!workflowScript.trim()) return { error: `workflowScriptPath file '${scriptPath}' is empty.` };
|
|
479
|
+
const { workflowScriptPath: _workflowScriptPath, ...rest } = params;
|
|
480
|
+
return { params: { ...rest, workflowScript } };
|
|
481
|
+
}
|
|
482
|
+
|
|
458
483
|
function removeForegroundControlIfIdle(state: SubagentState, runId: string): boolean {
|
|
459
484
|
const control = state.foregroundControls.get(runId);
|
|
460
485
|
if (control && (!foregroundSchedulingSettled(control) || (control.activeChildren?.size ?? 0) > 0)) return false;
|
|
@@ -543,6 +568,7 @@ function withSpawnBudgetStatus(
|
|
|
543
568
|
const activeAsyncCapacity = sessionId
|
|
544
569
|
? getActiveAsyncCapacitySnapshot(sessionId, resolveMaxActiveAsyncRunsPerSession(config.maxActiveAsyncRunsPerSession), {
|
|
545
570
|
liveWorkflowRunIds: new Set(state.workflowControllers?.keys() ?? []),
|
|
571
|
+
abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(config.capacity?.abandonedSlotReleaseAfterMs),
|
|
546
572
|
})
|
|
547
573
|
: { used: 0, limit: resolveMaxActiveAsyncRunsPerSession(config.maxActiveAsyncRunsPerSession) ?? 0 };
|
|
548
574
|
state.activeAsyncCapacity = activeAsyncCapacity;
|
|
@@ -645,11 +671,13 @@ function foregroundChildActivityFromProgress(progress: SingleResult["progress"]
|
|
|
645
671
|
...(progress?.currentPath ? { currentPath: progress.currentPath } : {}),
|
|
646
672
|
...(progress?.turnCount !== undefined ? { turnCount: progress.turnCount } : {}),
|
|
647
673
|
...(progress?.tokens !== undefined ? { tokens: progress.tokens } : {}),
|
|
674
|
+
...(progress?.window !== undefined ? { window: progress.window } : {}),
|
|
675
|
+
...(progress?.windowPeak !== undefined ? { windowPeak: progress.windowPeak } : {}),
|
|
648
676
|
...(progress?.toolCount !== undefined ? { toolCount: progress.toolCount } : {}),
|
|
649
677
|
};
|
|
650
678
|
}
|
|
651
679
|
|
|
652
|
-
function rememberForegroundRun(state: SubagentState, input: { runId: string; mode: "single" | "parallel" | "chain"; cwd: string; sessionId: string | null; results: SingleResult[]; extensionBindings?: ExtensionBindings }): void {
|
|
680
|
+
function rememberForegroundRun(state: SubagentState, input: { runId: string; mode: "single" | "parallel" | "chain"; cwd: string; sessionId: string | null; results: SingleResult[]; params: SubagentParamsLike; effectiveOutput?: string | boolean; effectiveOutputMode: OutputMode; extensionBindings?: ExtensionBindings }): void {
|
|
653
681
|
state.foregroundRuns ??= new Map();
|
|
654
682
|
const previous = state.foregroundRuns.get(input.runId);
|
|
655
683
|
const updatedAt = Date.now();
|
|
@@ -660,6 +688,13 @@ function rememberForegroundRun(state: SubagentState, input: { runId: string; mod
|
|
|
660
688
|
...(input.sessionId ? { sessionId: input.sessionId } : {}),
|
|
661
689
|
updatedAt,
|
|
662
690
|
children: input.results.map((result, index) => {
|
|
691
|
+
const resumeContract = omitUndefinedProperties({
|
|
692
|
+
outputSchema: input.params.outputSchema,
|
|
693
|
+
agentContract: input.params.agentContract,
|
|
694
|
+
acceptance: input.params.acceptance,
|
|
695
|
+
output: input.effectiveOutput,
|
|
696
|
+
outputMode: input.effectiveOutputMode,
|
|
697
|
+
});
|
|
663
698
|
const child = {
|
|
664
699
|
agent: result.agent,
|
|
665
700
|
index,
|
|
@@ -690,6 +725,7 @@ function rememberForegroundRun(state: SubagentState, input: { runId: string; mod
|
|
|
690
725
|
...(result.transcriptError ? { transcriptError: result.transcriptError } : {}),
|
|
691
726
|
...(result.detachedReason ? { detachedReason: result.detachedReason } : {}),
|
|
692
727
|
...(result.acceptance ? { acceptance: result.acceptance } : {}),
|
|
728
|
+
...(Object.keys(resumeContract).length ? { resumeContract } : {}),
|
|
693
729
|
...(result.launchContractDigest ? { launchContractDigest: result.launchContractDigest } : {}),
|
|
694
730
|
...(input.extensionBindings ? { extensionBindings: input.extensionBindings } : {}),
|
|
695
731
|
...(result.launchResolvedExtensions ? { launchResolvedExtensions: result.launchResolvedExtensions } : {}),
|
|
@@ -810,7 +846,7 @@ function updateRememberedForegroundChild(state: SubagentState, input: { runId: s
|
|
|
810
846
|
});
|
|
811
847
|
}
|
|
812
848
|
|
|
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 {
|
|
849
|
+
function resolveForegroundResumeTarget(params: SubagentParamsLike, state: SubagentState, options: { exactOnly?: boolean } = {}): { runId: string; mode: SubagentRunMode; state: "complete"; agent: string; index: number; cwd: string; sessionFile: string; model?: string; thinking?: string; launchContractDigest?: string; resumeContract?: ForegroundResumeChild["resumeContract"]; extensionBindings?: ExtensionBindings; capabilityCeiling?: ResolvedSubagentCapabilityCeiling } | undefined {
|
|
814
850
|
const requested = (params.id ?? params.runId)?.trim();
|
|
815
851
|
if (!requested || !state.foregroundRuns?.size || !state.currentSessionId) return undefined;
|
|
816
852
|
const direct = state.foregroundRuns.get(requested);
|
|
@@ -841,6 +877,7 @@ function resolveForegroundResumeTarget(params: SubagentParamsLike, state: Subage
|
|
|
841
877
|
...(child.model ? { model: child.model } : {}),
|
|
842
878
|
...(child.thinking ? { thinking: child.thinking } : {}),
|
|
843
879
|
...(child.launchContractDigest ? { launchContractDigest: child.launchContractDigest } : {}),
|
|
880
|
+
...(child.resumeContract ? { resumeContract: child.resumeContract } : {}),
|
|
844
881
|
...(child.extensionBindings ? { extensionBindings: normalizeExtensionBindings(child.extensionBindings)!.value } : {}),
|
|
845
882
|
...(child.capabilityCeiling ? { capabilityCeiling: child.capabilityCeiling } : {}),
|
|
846
883
|
};
|
|
@@ -1239,6 +1276,7 @@ function appendStepToAsyncChain(input: {
|
|
|
1239
1276
|
agents,
|
|
1240
1277
|
ctx: asyncCtx,
|
|
1241
1278
|
availableModels: input.ctx.modelRegistry.getAvailable().map(toModelInfo),
|
|
1279
|
+
unknownAgentDiagnosticContext: diagnosticContextFromDiscovery(discoveredForAppend, input.requestCwd, scope),
|
|
1242
1280
|
cwd: status.cwd ?? input.requestCwd,
|
|
1243
1281
|
chainSkills,
|
|
1244
1282
|
dynamicFanoutMaxItems: input.deps.config.chain?.dynamicFanout?.maxItems,
|
|
@@ -1514,9 +1552,14 @@ function externalJobFollowUpStarted(input: { sourceRunId: string; runId: string;
|
|
|
1514
1552
|
function externalRunnerControlError(asyncDir: string, action: "steer" | "resume"): AgentToolResult<Details> | undefined {
|
|
1515
1553
|
const status = readStatus(asyncDir);
|
|
1516
1554
|
if (!status?.steps?.length || !status.steps.every((step) => step.runner?.type === "external-cli" || step.runner?.type === "external-job")) return undefined;
|
|
1517
|
-
const
|
|
1518
|
-
|
|
1519
|
-
|
|
1555
|
+
const externalCli = normalizeExternalCliRunnerStatus(status.steps.find((step) => step.runner?.type === "external-cli")?.runner);
|
|
1556
|
+
const message = externalCli
|
|
1557
|
+
? action === "steer"
|
|
1558
|
+
? `External adapter '${externalCli.adapter.id}' does not support runs.steer: ${externalCli.unsupportedReasons.steer}`
|
|
1559
|
+
: `External adapter '${externalCli.adapter.id}' cannot resume: ${externalCli.nonResumableReason}`
|
|
1560
|
+
: action === "steer"
|
|
1561
|
+
? "External runners do not accept live steer messages."
|
|
1562
|
+
: "External runners do not persist Pi sessions and cannot be resumed.";
|
|
1520
1563
|
return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
|
|
1521
1564
|
}
|
|
1522
1565
|
|
|
@@ -1572,7 +1615,7 @@ async function resumeExternalJobFollowUp(input: {
|
|
|
1572
1615
|
runId,
|
|
1573
1616
|
kind: "runner",
|
|
1574
1617
|
asyncDir,
|
|
1575
|
-
}, { liveWorkflowRunIds: new Set(input.deps.state.workflowControllers?.keys() ?? []) }) : undefined;
|
|
1618
|
+
}, { liveWorkflowRunIds: new Set(input.deps.state.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(input.deps.config.capacity?.abandonedSlotReleaseAfterMs) }) : undefined;
|
|
1576
1619
|
} catch (error) {
|
|
1577
1620
|
if (error instanceof ActiveAsyncCapacityError) return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: "single", results: [], activeAsyncCapacity: error.snapshot } };
|
|
1578
1621
|
return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "single", results: [] } };
|
|
@@ -1729,6 +1772,7 @@ async function resumeAsyncRun(input: {
|
|
|
1729
1772
|
const scope: AgentScope = resolveExecutionAgentScope(input.params.agentScope);
|
|
1730
1773
|
const discovered = input.deps.discoverAgents(effectiveCwd, scope);
|
|
1731
1774
|
const discoveredAgents = discovered.agents;
|
|
1775
|
+
const unknownAgentDiagnosticContext = diagnosticContextFromDiscovery(discovered, effectiveCwd, scope);
|
|
1732
1776
|
const modelScope = discovered.modelScope;
|
|
1733
1777
|
const sessionName = resolveIntercomSessionTarget(input.deps.pi.getSessionName(), input.ctx.sessionManager.getSessionId());
|
|
1734
1778
|
const recoveryDescriptor = "recoveryDescriptor" in target ? target.recoveryDescriptor : undefined;
|
|
@@ -1756,13 +1800,17 @@ async function resumeAsyncRun(input: {
|
|
|
1756
1800
|
} : undefined);
|
|
1757
1801
|
if (!baseAgentConfig) {
|
|
1758
1802
|
return {
|
|
1759
|
-
content: [{ type: "text", text:
|
|
1803
|
+
content: [{ type: "text", text: formatUnknownAgentError(target.agent, unknownAgentDiagnosticContext, "Unknown agent for resume") }],
|
|
1760
1804
|
isError: true,
|
|
1761
1805
|
details: { mode: "management", results: [] },
|
|
1762
1806
|
};
|
|
1763
1807
|
}
|
|
1764
1808
|
if (target.source === "async" && target.runner?.type === "external-cli") {
|
|
1765
|
-
|
|
1809
|
+
const runner = normalizeExternalCliRunnerStatus(target.runner);
|
|
1810
|
+
const message = runner
|
|
1811
|
+
? `External adapter '${runner.adapter.id}' cannot resume: ${runner.nonResumableReason}`
|
|
1812
|
+
: "External runners do not persist Pi sessions and cannot be resumed.";
|
|
1813
|
+
return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
|
|
1766
1814
|
}
|
|
1767
1815
|
if (target.source === "async" && target.runner?.type === "external-job") {
|
|
1768
1816
|
if (attachChain) return { content: [{ type: "text", text: "External-job follow-up does not support chain attachment. Use action='resume' with message instead." }], isError: true, details: { mode: "management", results: [] } };
|
|
@@ -1807,7 +1855,7 @@ async function resumeAsyncRun(input: {
|
|
|
1807
1855
|
runId,
|
|
1808
1856
|
kind: "runner",
|
|
1809
1857
|
asyncDir: path.join(DIRS.async, runId),
|
|
1810
|
-
}, { liveWorkflowRunIds: new Set(input.deps.state.workflowControllers?.keys() ?? []) }) : undefined;
|
|
1858
|
+
}, { liveWorkflowRunIds: new Set(input.deps.state.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(input.deps.config.capacity?.abandonedSlotReleaseAfterMs) }) : undefined;
|
|
1811
1859
|
} catch (error) {
|
|
1812
1860
|
if (error instanceof ActiveAsyncCapacityError) return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: "chain", results: [], activeAsyncCapacity: error.snapshot } };
|
|
1813
1861
|
throw error;
|
|
@@ -1833,6 +1881,7 @@ async function resumeAsyncRun(input: {
|
|
|
1833
1881
|
label: `Attached ${target.runId}`,
|
|
1834
1882
|
},
|
|
1835
1883
|
agents,
|
|
1884
|
+
unknownAgentDiagnosticContext,
|
|
1836
1885
|
ctx: compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
1837
1886
|
pi: input.deps.pi,
|
|
1838
1887
|
cwd: input.requestCwd,
|
|
@@ -1905,20 +1954,23 @@ async function resumeAsyncRun(input: {
|
|
|
1905
1954
|
sourceRunId: target.runId,
|
|
1906
1955
|
runId,
|
|
1907
1956
|
asyncDir: path.join(DIRS.async, runId),
|
|
1908
|
-
})
|
|
1957
|
+
}, { abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(input.deps.config.capacity?.abandonedSlotReleaseAfterMs) })
|
|
1909
1958
|
: acquireActiveAsyncCapacity({
|
|
1910
1959
|
sessionId: input.deps.state.currentSessionId!,
|
|
1911
1960
|
limit: resolveMaxActiveAsyncRunsPerSession(input.deps.config.maxActiveAsyncRunsPerSession),
|
|
1912
1961
|
runId,
|
|
1913
1962
|
kind: "runner",
|
|
1914
1963
|
asyncDir: path.join(DIRS.async, runId),
|
|
1915
|
-
});
|
|
1964
|
+
}, { abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(input.deps.config.capacity?.abandonedSlotReleaseAfterMs) });
|
|
1916
1965
|
} catch (error) {
|
|
1917
1966
|
if (error instanceof ActiveAsyncCapacityError) return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: "single", results: [], activeAsyncCapacity: error.snapshot } };
|
|
1918
1967
|
return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "single", results: [] } };
|
|
1919
1968
|
}
|
|
1920
1969
|
const recoveryAgentConfig = recoveryDescriptor ? applySteeringRecoveryAgentConfig(baseAgentConfig, recoveryDescriptor) : baseAgentConfig;
|
|
1921
1970
|
const agentConfig = intercomBridge.active ? applyIntercomBridgeToAgent(recoveryAgentConfig, intercomBridge) : recoveryAgentConfig;
|
|
1971
|
+
const foregroundContract = target.source === "foreground" ? target.resumeContract : undefined;
|
|
1972
|
+
const outputSchema = input.params.outputSchema ?? foregroundContract?.outputSchema ?? recoveryDescriptor?.structuredOutputSchema;
|
|
1973
|
+
const agentContract = input.params.agentContract ?? foregroundContract?.agentContract ?? recoveryDescriptor?.agentContract;
|
|
1922
1974
|
const artifactConfig: ArtifactConfig = recoveryDescriptor?.artifactConfig ?? omitUndefinedProperties({ ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false, dir: input.deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir });
|
|
1923
1975
|
const artifactsDir = recoveryDescriptor?.artifactsDir ?? getArtifactsDir(parentSessionFile, effectiveCwd, artifactConfig.dir);
|
|
1924
1976
|
const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
@@ -1974,12 +2026,12 @@ async function resumeAsyncRun(input: {
|
|
|
1974
2026
|
controlIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
|
|
1975
2027
|
childIntercomTarget: intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
|
|
1976
2028
|
availableModels,
|
|
1977
|
-
output:
|
|
1978
|
-
outputMode: input.params.outputMode ?? recoveryDescriptor?.outputMode,
|
|
1979
|
-
...(
|
|
1980
|
-
...(
|
|
2029
|
+
output: input.params.output !== undefined ? input.params.output : foregroundContract?.output ?? recoveryDescriptor?.outputPath,
|
|
2030
|
+
outputMode: input.params.outputMode ?? foregroundContract?.outputMode ?? recoveryDescriptor?.outputMode,
|
|
2031
|
+
...(agentContract ? { agentContract } : {}),
|
|
2032
|
+
...(outputSchema ? { structuredOutputSchema: outputSchema } : {}),
|
|
1981
2033
|
...(recoveryDescriptor?.skills ? { skills: [...recoveryDescriptor.skills] } : {}),
|
|
1982
|
-
...(
|
|
2034
|
+
...(input.params.acceptance !== undefined ? { acceptance: input.params.acceptance } : foregroundContract?.acceptance !== undefined ? { acceptance: foregroundContract.acceptance } : recoveryDescriptor?.acceptance !== undefined ? { acceptance: recoveryDescriptor.acceptance } : {}),
|
|
1983
2035
|
...(input.params.timeoutMs !== undefined ? { timeoutMs: input.params.timeoutMs } : {}),
|
|
1984
2036
|
...(input.absoluteDeadlineAt !== undefined ? { absoluteDeadlineAt: input.absoluteDeadlineAt } : {}),
|
|
1985
2037
|
...(input.params.turnBudget !== undefined ? { turnBudget: input.params.turnBudget } : {}),
|
|
@@ -2058,9 +2110,13 @@ async function resumeAsyncRun(input: {
|
|
|
2058
2110
|
...(completed.structuredOutputPath ? { structuredOutputPath: completed.structuredOutputPath } : {}),
|
|
2059
2111
|
...(completed.structuredOutputSchemaPath ? { structuredOutputSchemaPath: completed.structuredOutputSchemaPath } : {}),
|
|
2060
2112
|
...(completed.acceptance ? { acceptance: completed.acceptance } : {}),
|
|
2113
|
+
...(completed.artifactPaths ? { artifactPaths: completed.artifactPaths } : {}),
|
|
2114
|
+
...(completed.outputSaveError ? { outputSaveError: completed.outputSaveError } : {}),
|
|
2115
|
+
...(completed.transcriptPath ? { transcriptPath: completed.transcriptPath } : {}),
|
|
2116
|
+
...(completed.transcriptError ? { transcriptError: completed.transcriptError } : {}),
|
|
2061
2117
|
};
|
|
2062
2118
|
return {
|
|
2063
|
-
content: [{ type: "text", text: completed.output || completed.error || `Revived ${target.source} subagent ${revivedId} completed without output.` }],
|
|
2119
|
+
content: [{ type: "text", text: completed.success ? completed.output || completed.error || `Revived ${target.source} subagent ${revivedId} completed without output.` : completed.error || completed.output || `Revived ${target.source} subagent ${revivedId} completed without output.` }],
|
|
2064
2120
|
...(completed.success ? {} : { isError: true }),
|
|
2065
2121
|
details: {
|
|
2066
2122
|
...result.details,
|
|
@@ -2225,19 +2281,28 @@ async function maybeBuildForegroundIntercomReceipt(input: {
|
|
|
2225
2281
|
};
|
|
2226
2282
|
}
|
|
2227
2283
|
|
|
2228
|
-
function
|
|
2284
|
+
function diagnosticContextFromDiscovery(
|
|
2285
|
+
discovered: { agents: AgentConfig[]; cwd?: string; scope?: AgentScope; directories?: UnknownAgentDiagnosticContext["directories"] },
|
|
2286
|
+
cwd: string,
|
|
2287
|
+
scope: AgentScope,
|
|
2288
|
+
): UnknownAgentDiagnosticContext {
|
|
2289
|
+
if (discovered.cwd && discovered.scope && discovered.directories) return unknownAgentDiagnosticContext({ ...discovered, cwd: discovered.cwd, scope: discovered.scope, directories: discovered.directories });
|
|
2290
|
+
return unknownAgentDiagnosticContext(discoverAgents(path.resolve(cwd), scope));
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2293
|
+
function canonicalizeAgentName(name: string, agents: AgentConfig[], diagnostics: AgentDiscoveryDiagnostic[] | undefined, context: UnknownAgentDiagnosticContext): { name?: string; error?: string } {
|
|
2229
2294
|
const resolved = resolveAgentName(name, agents);
|
|
2230
2295
|
const candidates = resolved.error ? agents.filter((agent) => resolveAgentName(name, [agent]).agent) : resolved.agent;
|
|
2231
2296
|
const diagnostic = findBlockingAgentDiagnostic(name, candidates, diagnostics);
|
|
2232
2297
|
if (diagnostic) return { error: `Agent '${name}' has invalid configuration: ${diagnostic.error}` };
|
|
2233
2298
|
if (resolved.error) return { error: resolved.error };
|
|
2234
|
-
if (!resolved.agent) return { error:
|
|
2299
|
+
if (!resolved.agent) return { error: formatUnknownAgentError(name, context) };
|
|
2235
2300
|
return { name: resolved.agent.name };
|
|
2236
2301
|
}
|
|
2237
2302
|
|
|
2238
|
-
function canonicalizeExecutionParams(params: SubagentParamsLike, agents: AgentConfig[], diagnostics
|
|
2303
|
+
function canonicalizeExecutionParams(params: SubagentParamsLike, agents: AgentConfig[], diagnostics: AgentDiscoveryDiagnostic[] | undefined, context: UnknownAgentDiagnosticContext): { params?: SubagentParamsLike; error?: string } {
|
|
2239
2304
|
const resolve = (name: string, location?: string): { name?: string; error?: string } => {
|
|
2240
|
-
const result = canonicalizeAgentName(name, agents, diagnostics);
|
|
2305
|
+
const result = canonicalizeAgentName(name, agents, diagnostics, context);
|
|
2241
2306
|
return result.error && location ? { error: `${result.error} (${location})` } : result;
|
|
2242
2307
|
};
|
|
2243
2308
|
if (params.agent) {
|
|
@@ -2305,6 +2370,7 @@ function validateExecutionInput(
|
|
|
2305
2370
|
hasTasks: boolean,
|
|
2306
2371
|
hasSingle: boolean,
|
|
2307
2372
|
allowClarifyTaskPrompt: boolean,
|
|
2373
|
+
context: UnknownAgentDiagnosticContext,
|
|
2308
2374
|
): AgentToolResult<Details> | null {
|
|
2309
2375
|
if (Number(hasChain) + Number(hasTasks) + Number(hasSingle) !== 1) {
|
|
2310
2376
|
return {
|
|
@@ -2330,7 +2396,7 @@ function validateExecutionInput(
|
|
|
2330
2396
|
|
|
2331
2397
|
if (hasSingle && params.agent && !agents.find((agent) => agent.name === params.agent)) {
|
|
2332
2398
|
return {
|
|
2333
|
-
content: [{ type: "text", text:
|
|
2399
|
+
content: [{ type: "text", text: formatUnknownAgentError(params.agent, context) }],
|
|
2334
2400
|
isError: true,
|
|
2335
2401
|
details: { mode: "single" as const, results: [] },
|
|
2336
2402
|
};
|
|
@@ -2341,7 +2407,7 @@ function validateExecutionInput(
|
|
|
2341
2407
|
const task = params.tasks[i]!;
|
|
2342
2408
|
if (!agents.find((agent) => agent.name === task.agent)) {
|
|
2343
2409
|
return {
|
|
2344
|
-
content: [{ type: "text", text:
|
|
2410
|
+
content: [{ type: "text", text: `${formatUnknownAgentError(task.agent, context)} (task ${i + 1})` }],
|
|
2345
2411
|
isError: true,
|
|
2346
2412
|
details: { mode: "parallel" as const, results: [] },
|
|
2347
2413
|
};
|
|
@@ -2386,7 +2452,7 @@ function validateExecutionInput(
|
|
|
2386
2452
|
for (const agentName of stepAgents) {
|
|
2387
2453
|
if (!agents.find((a) => a.name === agentName)) {
|
|
2388
2454
|
return {
|
|
2389
|
-
content: [{ type: "text", text:
|
|
2455
|
+
content: [{ type: "text", text: `${formatUnknownAgentError(agentName, context)} (step ${i + 1})` }],
|
|
2390
2456
|
isError: true,
|
|
2391
2457
|
details: { mode: "chain" as const, results: [] },
|
|
2392
2458
|
};
|
|
@@ -2977,21 +3043,21 @@ function wrapChainTasksForFork(chain: ChainStep[], contextPolicy: AgentDefaultCo
|
|
|
2977
3043
|
});
|
|
2978
3044
|
}
|
|
2979
3045
|
|
|
2980
|
-
function preflightForkSessionsForStaticTasks(
|
|
3046
|
+
async function preflightForkSessionsForStaticTasks(
|
|
2981
3047
|
params: SubagentParamsLike,
|
|
2982
3048
|
contextPolicy: AgentDefaultContextPolicy,
|
|
2983
|
-
|
|
3049
|
+
prepareSessionForTask: PrepareForkSessionForTask,
|
|
2984
3050
|
dynamicFanoutMaxItems?: number,
|
|
2985
|
-
): void {
|
|
3051
|
+
): Promise<void> {
|
|
2986
3052
|
if (!contextPolicy.usesFork) return;
|
|
2987
3053
|
if (params.agent) {
|
|
2988
|
-
if (shouldForkAgent(contextPolicy, params.agent))
|
|
3054
|
+
if (shouldForkAgent(contextPolicy, params.agent)) await prepareSessionForTask(params.agent, 0, params.model);
|
|
2989
3055
|
return;
|
|
2990
3056
|
}
|
|
2991
3057
|
if (params.tasks) {
|
|
2992
|
-
params.tasks.
|
|
2993
|
-
if (shouldForkAgent(contextPolicy, task.agent))
|
|
2994
|
-
}
|
|
3058
|
+
for (const [index, task] of params.tasks.entries()) {
|
|
3059
|
+
if (shouldForkAgent(contextPolicy, task.agent)) await prepareSessionForTask(task.agent, index, task.model);
|
|
3060
|
+
}
|
|
2995
3061
|
return;
|
|
2996
3062
|
}
|
|
2997
3063
|
if (!params.chain?.length) return;
|
|
@@ -2999,7 +3065,7 @@ function preflightForkSessionsForStaticTasks(
|
|
|
2999
3065
|
for (const step of params.chain) {
|
|
3000
3066
|
if (isParallelStep(step)) {
|
|
3001
3067
|
for (const task of step.parallel) {
|
|
3002
|
-
if (shouldForkAgent(contextPolicy, task.agent))
|
|
3068
|
+
if (shouldForkAgent(contextPolicy, task.agent)) await prepareSessionForTask(task.agent, flatIndex, task.model);
|
|
3003
3069
|
flatIndex++;
|
|
3004
3070
|
}
|
|
3005
3071
|
continue;
|
|
@@ -3007,13 +3073,13 @@ function preflightForkSessionsForStaticTasks(
|
|
|
3007
3073
|
if (isDynamicParallelStep(step)) {
|
|
3008
3074
|
const maxItems = step.expand.maxItems ?? dynamicFanoutMaxItems ?? 0;
|
|
3009
3075
|
if (shouldForkAgent(contextPolicy, step.parallel.agent)) {
|
|
3010
|
-
for (let itemIndex = 0; itemIndex < maxItems; itemIndex++)
|
|
3076
|
+
for (let itemIndex = 0; itemIndex < maxItems; itemIndex++) await prepareSessionForTask(step.parallel.agent, flatIndex + itemIndex, step.parallel.model);
|
|
3011
3077
|
}
|
|
3012
3078
|
flatIndex += maxItems;
|
|
3013
3079
|
continue;
|
|
3014
3080
|
}
|
|
3015
3081
|
const sequential = step as SequentialStep;
|
|
3016
|
-
if (shouldForkAgent(contextPolicy, sequential.agent))
|
|
3082
|
+
if (shouldForkAgent(contextPolicy, sequential.agent)) await prepareSessionForTask(sequential.agent, flatIndex, sequential.model);
|
|
3017
3083
|
flatIndex++;
|
|
3018
3084
|
}
|
|
3019
3085
|
}
|
|
@@ -3067,9 +3133,13 @@ async function waitForWorkflowAsyncSingleResult(
|
|
|
3067
3133
|
...(completed.structuredOutputPath ? { structuredOutputPath: completed.structuredOutputPath } : {}),
|
|
3068
3134
|
...(completed.structuredOutputSchemaPath ? { structuredOutputSchemaPath: completed.structuredOutputSchemaPath } : {}),
|
|
3069
3135
|
...(completed.acceptance ? { acceptance: completed.acceptance } : {}),
|
|
3136
|
+
...(completed.artifactPaths ? { artifactPaths: completed.artifactPaths } : {}),
|
|
3137
|
+
...(completed.outputSaveError ? { outputSaveError: completed.outputSaveError } : {}),
|
|
3138
|
+
...(completed.transcriptPath ? { transcriptPath: completed.transcriptPath } : {}),
|
|
3139
|
+
...(completed.transcriptError ? { transcriptError: completed.transcriptError } : {}),
|
|
3070
3140
|
});
|
|
3071
3141
|
return {
|
|
3072
|
-
content: [{ type: "text", text: completed.output || completed.error || `Async workflow child ${options.runId} completed without output.` }],
|
|
3142
|
+
content: [{ type: "text", text: completed.success ? completed.output || completed.error || `Async workflow child ${options.runId} completed without output.` : completed.error || completed.output || `Async workflow child ${options.runId} completed without output.` }],
|
|
3073
3143
|
...(completed.success ? {} : { isError: true }),
|
|
3074
3144
|
details: {
|
|
3075
3145
|
...launchResult.details,
|
|
@@ -3097,6 +3167,7 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
3097
3167
|
intercomBridge,
|
|
3098
3168
|
nestedRoute,
|
|
3099
3169
|
contextPolicy,
|
|
3170
|
+
unknownAgentDiagnosticContext,
|
|
3100
3171
|
} = data;
|
|
3101
3172
|
const hasChain = (params.chain?.length ?? 0) > 0;
|
|
3102
3173
|
const hasTasks = (params.tasks?.length ?? 0) > 0;
|
|
@@ -3135,7 +3206,7 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
3135
3206
|
const a = agents.find((x) => x.name === params.agent);
|
|
3136
3207
|
if (!a) {
|
|
3137
3208
|
return {
|
|
3138
|
-
content: [{ type: "text", text:
|
|
3209
|
+
content: [{ type: "text", text: formatUnknownAgentError(params.agent!, unknownAgentDiagnosticContext) }],
|
|
3139
3210
|
isError: true,
|
|
3140
3211
|
details: { mode: "single" as const, results: [] },
|
|
3141
3212
|
};
|
|
@@ -3501,7 +3572,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3501
3572
|
const agentConfig = agents.find((a) => a.name === params.agent);
|
|
3502
3573
|
if (!agentConfig) {
|
|
3503
3574
|
return {
|
|
3504
|
-
content: [{ type: "text", text:
|
|
3575
|
+
content: [{ type: "text", text: formatUnknownAgentError(params.agent!, data.unknownAgentDiagnosticContext) }],
|
|
3505
3576
|
isError: true,
|
|
3506
3577
|
details: { mode: "single", results: [] },
|
|
3507
3578
|
};
|
|
@@ -3634,6 +3705,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3634
3705
|
llmIntentArbiter: createTaskMutationArbiter(ctx),
|
|
3635
3706
|
...workflowForegroundSteeringLaunchOptions(foregroundControl, 0),
|
|
3636
3707
|
context: data.contextPolicy.contextForAgent(params.agent!),
|
|
3708
|
+
unknownAgentDiagnosticContext: data.unknownAgentDiagnosticContext,
|
|
3637
3709
|
runFanoutBudget: params.runFanoutAdmitted ? data.runFanoutBudget : { ...data.runFanoutBudget, parentPath: `${data.runFanoutBudget.parentPath ? `${data.runFanoutBudget.parentPath}/` : ""}single` },
|
|
3638
3710
|
cwd: singleCwd,
|
|
3639
3711
|
signal,
|
|
@@ -3672,6 +3744,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3672
3744
|
agentContract: params.agentContract,
|
|
3673
3745
|
acceptance: params.acceptance,
|
|
3674
3746
|
acceptanceContext: { mode: "single" },
|
|
3747
|
+
workflowChildPermitLaunch: data.workflowChildPermitLaunch,
|
|
3675
3748
|
onEffectivePrompt: foregroundControl ? (prompt) => updateLiveEffectivePrompt(foregroundControl, 0, prompt) : undefined,
|
|
3676
3749
|
onDetachReady: (detach) => {
|
|
3677
3750
|
detachForeground = detach;
|
|
@@ -3705,7 +3778,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3705
3778
|
}
|
|
3706
3779
|
}
|
|
3707
3780
|
}
|
|
3708
|
-
recordRun(params.agent!, cleanTask, result.exitCode, result.progressSummary?.durationMs ?? 0);
|
|
3781
|
+
recordRun(params.agent!, cleanTask, result.exitCode, result.progressSummary?.durationMs ?? 0, result);
|
|
3709
3782
|
},
|
|
3710
3783
|
timeoutMs: data.timeoutMs,
|
|
3711
3784
|
deadlineAt,
|
|
@@ -3730,7 +3803,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3730
3803
|
}
|
|
3731
3804
|
}
|
|
3732
3805
|
if (!r.detached) {
|
|
3733
|
-
recordRun(params.agent!, cleanTask, r.exitCode, r.progressSummary?.durationMs ?? 0);
|
|
3806
|
+
recordRun(params.agent!, cleanTask, r.exitCode, r.progressSummary?.durationMs ?? 0, r);
|
|
3734
3807
|
}
|
|
3735
3808
|
|
|
3736
3809
|
let worktreeHandoff: ReturnType<typeof finalizeSingleWorktreeHandoff> | undefined;
|
|
@@ -3776,7 +3849,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3776
3849
|
usageBudget: usageBudgetState(data.usageBudget, totalCost),
|
|
3777
3850
|
...(worktreeHandoff?.reference ? { parallelHandoff: worktreeHandoff.reference } : {}),
|
|
3778
3851
|
}));
|
|
3779
|
-
rememberForegroundRun(deps.state, { runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, results: details.results, extensionBindings: params.extensionBindings });
|
|
3852
|
+
rememberForegroundRun(deps.state, { runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, results: details.results, params, effectiveOutput, effectiveOutputMode, extensionBindings: params.extensionBindings });
|
|
3780
3853
|
|
|
3781
3854
|
const suppressRoutineResultIntercom = shouldSuppressRoutineResultIntercom({ suppressRoutineResultIntercom: params.suppressRoutineResultIntercom, results: [r] });
|
|
3782
3855
|
if (!r.detached && !r.interrupted && !suppressRoutineResultIntercom) {
|
|
@@ -3930,6 +4003,10 @@ function workflowChildResult(
|
|
|
3930
4003
|
const output = result.details.results.length === 1 && result.details.results[0]?.finalOutput !== undefined
|
|
3931
4004
|
? result.details.results[0].finalOutput
|
|
3932
4005
|
: receiptOutput;
|
|
4006
|
+
const childError = result.details.results.map((child) => child.error).find((error): error is string => Boolean(error));
|
|
4007
|
+
const failureError = childError && receiptOutput && receiptOutput !== childError
|
|
4008
|
+
? `${childError}\n\n${receiptOutput}`
|
|
4009
|
+
: childError || receiptOutput || output || "Child run failed.";
|
|
3933
4010
|
const detached = result.details.results.some((child) => child.detached);
|
|
3934
4011
|
const interrupted = result.details.results.some((child) => child.interrupted);
|
|
3935
4012
|
const stopped = result.details.results.some((child) => child.stopped);
|
|
@@ -3965,13 +4042,20 @@ function workflowChildResult(
|
|
|
3965
4042
|
const continuationRunIds = [...new Set([resumeSourceRunId, runId].filter((value): value is string => Boolean(value)))];
|
|
3966
4043
|
const outputReference = result.details.results.find((child) => child.savedOutputPath)?.savedOutputPath
|
|
3967
4044
|
?? result.details.results.find((child) => child.outputReference?.path)?.outputReference?.path;
|
|
4045
|
+
const externalResult = result.details.results.length === 1 && result.details.results[0]?.runner?.type === "external-cli" ? result.details.results[0] : undefined;
|
|
4046
|
+
const externalStatus = result.details.asyncDir ? readStatus(result.details.asyncDir) : undefined;
|
|
4047
|
+
const externalStatusStep = externalStatus?.steps?.length === 1 && externalStatus.steps[0]?.runner?.type === "external-cli" ? externalStatus.steps[0] : undefined;
|
|
4048
|
+
const externalRunner = normalizeExternalCliRunnerStatus(externalResult?.runner ?? externalStatusStep?.runner);
|
|
4049
|
+
const externalProcess = externalResult?.externalProcess ?? externalStatusStep?.externalProcess;
|
|
4050
|
+
const externalAdapter = externalRunner ? externalCliReceiptMetadata({ runner: externalRunner, externalProcess, outputReference }) : undefined;
|
|
4051
|
+
if (externalAdapter) resumability = { state: "not-resumable", reason: externalAdapter.nonResumableReason };
|
|
3968
4052
|
return {
|
|
3969
4053
|
key,
|
|
3970
4054
|
ok,
|
|
3971
4055
|
...(resolvedAgents.length === 1 ? { agent: resolvedAgents[0] } : {}),
|
|
3972
4056
|
...(runId ? { runId } : {}),
|
|
3973
4057
|
output,
|
|
3974
|
-
...(!ok ? { error:
|
|
4058
|
+
...(!ok ? { error: failureError } : {}),
|
|
3975
4059
|
...(detached ? { detached: true } : {}),
|
|
3976
4060
|
...(interrupted ? { interrupted: true } : {}),
|
|
3977
4061
|
...(stopped ? { stopped: true } : {}),
|
|
@@ -3979,6 +4063,7 @@ function workflowChildResult(
|
|
|
3979
4063
|
...(requestedContext ? { requestedContext } : {}),
|
|
3980
4064
|
...(resolvedContext ? { resolvedContext } : {}),
|
|
3981
4065
|
...(outputReference ? { outputReference } : {}),
|
|
4066
|
+
...(externalAdapter ? { externalAdapter } : {}),
|
|
3982
4067
|
resumability,
|
|
3983
4068
|
continuation: { runIds: continuationRunIds },
|
|
3984
4069
|
artifactPaths: [...artifactPaths],
|
|
@@ -4024,8 +4109,18 @@ function terminalWorkflowReceipt(
|
|
|
4024
4109
|
workflowRunId: string,
|
|
4025
4110
|
state: WorkflowReceiptState,
|
|
4026
4111
|
children: WorkflowScriptChildResult[],
|
|
4112
|
+
workflowChildren?: WorkflowReceipt["workflowChildren"],
|
|
4027
4113
|
): WorkflowReceipt {
|
|
4028
|
-
return buildWorkflowReceipt({ workflowRunId, state, children });
|
|
4114
|
+
return buildWorkflowReceipt({ workflowRunId, state, children, workflowChildren });
|
|
4115
|
+
}
|
|
4116
|
+
|
|
4117
|
+
function workflowFailureMessage(error: unknown, workflowRunId: string, children: WorkflowScriptChildResult[]): string {
|
|
4118
|
+
const text = error instanceof Error ? error.message : String(error);
|
|
4119
|
+
const validationPrefix = "workflowScript validation failed before child launch; no children launched.";
|
|
4120
|
+
if (children.length === 0 && text.includes(validationPrefix)) {
|
|
4121
|
+
return `Workflow '${workflowRunId}' validation failed before child launch; no children launched.${text.slice(text.indexOf(validationPrefix) + validationPrefix.length)}`;
|
|
4122
|
+
}
|
|
4123
|
+
return text;
|
|
4029
4124
|
}
|
|
4030
4125
|
|
|
4031
4126
|
export async function steerWorkflowChildByKey(input: {
|
|
@@ -4068,6 +4163,8 @@ export async function steerWorkflowChildByKey(input: {
|
|
|
4068
4163
|
return { key: input.key, state: "missed", error: `Workflow child '${input.key}' is ${childStatus.state}.` };
|
|
4069
4164
|
}
|
|
4070
4165
|
if (childStatus) {
|
|
4166
|
+
const unsupported = externalRunnerControlError(asyncDir, "steer");
|
|
4167
|
+
if (unsupported) return workflowSteerReceipt(input.key, unsupported);
|
|
4071
4168
|
const result = await steerAsyncRun({
|
|
4072
4169
|
state: input.state,
|
|
4073
4170
|
runId: childRunId,
|
|
@@ -4121,6 +4218,15 @@ export function prepareWorkflowLaunchParams(
|
|
|
4121
4218
|
const toolBudget = childParams.toolBudget ?? workflowDefaults.toolBudget;
|
|
4122
4219
|
const intercomBridge = childParams.intercomBridge ?? workflowDefaults.intercomBridge;
|
|
4123
4220
|
const worktree = childParams.worktree ?? workflowDefaults.worktree;
|
|
4221
|
+
const outputSchema = Object.hasOwn(childParams, "outputSchema") ? childParams.outputSchema : workflowDefaults.outputSchema;
|
|
4222
|
+
if (outputSchema !== undefined) assertJsonSchemaObject(outputSchema, "outputSchema");
|
|
4223
|
+
const agentContract = Object.hasOwn(childParams, "agentContract") ? childParams.agentContract : workflowDefaults.agentContract;
|
|
4224
|
+
if (agentContract !== undefined && !isAgentContractV1(agentContract as AgentContract)) throw new Error("agentContract must be { version: 1 }.");
|
|
4225
|
+
const acceptance = Object.hasOwn(childParams, "acceptance") ? childParams.acceptance : workflowDefaults.acceptance;
|
|
4226
|
+
const output = Object.hasOwn(childParams, "output") ? childParams.output : workflowDefaults.output;
|
|
4227
|
+
if (output !== undefined && typeof output !== "string" && typeof output !== "boolean") throw new Error("output must be a path string or boolean.");
|
|
4228
|
+
const outputMode = Object.hasOwn(childParams, "outputMode") ? childParams.outputMode : workflowDefaults.outputMode;
|
|
4229
|
+
if (outputMode !== undefined && outputMode !== "inline" && outputMode !== "file-only") throw new Error("outputMode must be 'inline' or 'file-only'.");
|
|
4124
4230
|
return {
|
|
4125
4231
|
action: "resume",
|
|
4126
4232
|
id: childParams.resume.trim(),
|
|
@@ -4129,6 +4235,11 @@ export function prepareWorkflowLaunchParams(
|
|
|
4129
4235
|
workflowParentRunId: parentWorkflowRunId,
|
|
4130
4236
|
workflowKey,
|
|
4131
4237
|
...(worktree !== undefined ? { worktree: worktree as boolean } : {}),
|
|
4238
|
+
...(outputSchema !== undefined ? { outputSchema: outputSchema as JsonSchemaObject } : {}),
|
|
4239
|
+
...(agentContract !== undefined ? { agentContract: agentContract as AgentContract } : {}),
|
|
4240
|
+
...(acceptance !== undefined ? { acceptance: acceptance as AcceptanceInput } : {}),
|
|
4241
|
+
...(output !== undefined ? { output: output as string | boolean } : {}),
|
|
4242
|
+
...(outputMode !== undefined ? { outputMode: outputMode as OutputMode } : {}),
|
|
4132
4243
|
...(options.runFanoutBudget ? { runFanoutBudget: { ...options.runFanoutBudget, parentPath: `${options.runFanoutBudget.parentPath ? `${options.runFanoutBudget.parentPath}/` : ""}workflow[${workflowKey}]` } } : {}),
|
|
4133
4244
|
...(options.missionDetached ? { mission: false } : {}),
|
|
4134
4245
|
...(timeoutMs !== undefined ? { timeoutMs: timeoutMs as number } : {}),
|
|
@@ -4186,11 +4297,12 @@ function workflowChatProgressUpdate(
|
|
|
4186
4297
|
runId: string,
|
|
4187
4298
|
chatProgress: WorkflowChatProgressProjection,
|
|
4188
4299
|
workflow: NonNullable<Details["workflow"]>,
|
|
4300
|
+
workflowChildren?: Details["workflowChildren"],
|
|
4189
4301
|
): AgentToolResult<Details> | undefined {
|
|
4190
4302
|
if (chatProgress.mode !== "live-card") return undefined;
|
|
4191
4303
|
return {
|
|
4192
4304
|
content: [{ type: "text", text: "Workflow running." }],
|
|
4193
|
-
details: { mode: "workflow", runId, results: [], workflow, chatProgress },
|
|
4305
|
+
details: { mode: "workflow", runId, results: [], workflow, ...(workflowChildren ? { workflowChildren } : {}), chatProgress },
|
|
4194
4306
|
};
|
|
4195
4307
|
}
|
|
4196
4308
|
|
|
@@ -4201,10 +4313,12 @@ function createScheduledOwnerState(source: SubagentState, ownerSessionId: string
|
|
|
4201
4313
|
grantHistory: [...(source.subagentSpawns.grantHistory ?? [])],
|
|
4202
4314
|
}
|
|
4203
4315
|
: undefined;
|
|
4316
|
+
const ownerParentModel = source.currentSessionId === ownerSessionId ? source.lastParentModel : undefined;
|
|
4204
4317
|
return {
|
|
4205
4318
|
...source,
|
|
4206
4319
|
baseCwd: ctx.cwd,
|
|
4207
4320
|
currentSessionId: ownerSessionId,
|
|
4321
|
+
lastParentModel: ownerParentModel,
|
|
4208
4322
|
parentSessionFile: ctx.sessionManager.getSessionFile() ?? null,
|
|
4209
4323
|
subagentInProgress: false,
|
|
4210
4324
|
...(ownerSpawns ? { subagentSpawns: ownerSpawns } : { subagentSpawns: undefined }),
|
|
@@ -4265,6 +4379,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4265
4379
|
const delegatedThinkingOverrides = new WeakMap<object, AgentConfig["thinking"]>();
|
|
4266
4380
|
const delegatedZeroToolBudgets = new WeakSet<object>();
|
|
4267
4381
|
const delegatedExecutions = new WeakSet<object>();
|
|
4382
|
+
const workflowPermitContexts = new WeakMap<object, { root: WorkflowChildPermit } | { child: WorkflowChildPermitContext }>();
|
|
4268
4383
|
const warnedArtifactPackageDirs = new Set<string>();
|
|
4269
4384
|
const scheduledOwnerExecutors = new Map<string, ReturnType<typeof createSubagentExecutor>>();
|
|
4270
4385
|
const execute = async (
|
|
@@ -4274,11 +4389,23 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4274
4389
|
onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
|
|
4275
4390
|
ctx: ExtensionContext,
|
|
4276
4391
|
preserveActiveSession = false,
|
|
4392
|
+
parentModelOverride?: ParentModel | null,
|
|
4277
4393
|
): Promise<AgentToolResult<Details>> => {
|
|
4394
|
+
if (params.action?.trim() === "validate") {
|
|
4395
|
+
const validation = validateWorkflowScript(params.workflowScript ?? "");
|
|
4396
|
+
return {
|
|
4397
|
+
content: [{ type: "text", text: JSON.stringify(validation) }],
|
|
4398
|
+
...(validation.ok ? {} : { isError: true }),
|
|
4399
|
+
details: { mode: "management", results: [] },
|
|
4400
|
+
};
|
|
4401
|
+
}
|
|
4278
4402
|
const workflowLaunchObserver = workflowLaunchObservers.get(params);
|
|
4279
4403
|
const delegatedThinkingOverride = delegatedThinkingOverrides.get(params);
|
|
4280
4404
|
const allowZeroToolBudget = delegatedZeroToolBudgets.has(params);
|
|
4281
4405
|
const delegatedExecution = delegatedExecutions.has(params);
|
|
4406
|
+
const workflowPermitContext = workflowPermitContexts.get(params);
|
|
4407
|
+
const delegatedWorkflowPermit = workflowPermitContext && "root" in workflowPermitContext ? workflowPermitContext.root : undefined;
|
|
4408
|
+
const workflowChildPermitLaunch = workflowPermitContext && "child" in workflowPermitContext ? workflowPermitContext.child : undefined;
|
|
4282
4409
|
if (!preserveActiveSession) deps.state.baseCwd = ctx.cwd;
|
|
4283
4410
|
deps.state.foregroundRuns ??= new Map();
|
|
4284
4411
|
deps.state.foregroundControls ??= new Map();
|
|
@@ -4289,6 +4416,19 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4289
4416
|
const normalizedAction = typeof requestParams.action === "string" ? requestParams.action.trim() : requestParams.action;
|
|
4290
4417
|
if (normalizedAction === "resume" && requestParams.extensionBindings !== undefined) return buildRequestedModeError(requestParams, "extensionBindings is not supported with action='resume'; resume uses the original retained child binding.");
|
|
4291
4418
|
if (requestParams.workflowScript !== undefined && normalizedAction === undefined) {
|
|
4419
|
+
if (delegatedWorkflowPermit) {
|
|
4420
|
+
const permitError = validateWorkflowChildPermitRoot(delegatedWorkflowPermit, _id);
|
|
4421
|
+
if (permitError) return buildRequestedModeError(requestParams, permitError);
|
|
4422
|
+
if (requestParams.async !== false) return buildRequestedModeError(requestParams, "Workflow child permit supports foreground workflow roots only; set async:false.");
|
|
4423
|
+
}
|
|
4424
|
+
const workflowParentModel = parentModelOverride !== undefined
|
|
4425
|
+
? parentModelOverride
|
|
4426
|
+
: (() => {
|
|
4427
|
+
const currentParentModel = normalizeParentModel(ctx.model);
|
|
4428
|
+
return (preserveActiveSession
|
|
4429
|
+
? currentParentModel
|
|
4430
|
+
: rememberParentModel(deps.state, resolveCurrentSessionId(ctx.sessionManager), currentParentModel)) ?? null;
|
|
4431
|
+
})();
|
|
4292
4432
|
if (requestParams.extensionBindings !== undefined) {
|
|
4293
4433
|
try {
|
|
4294
4434
|
requestParams.extensionBindings = normalizeExtensionBindings(requestParams.extensionBindings)!.value;
|
|
@@ -4340,7 +4480,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4340
4480
|
runId: workflowRunId,
|
|
4341
4481
|
kind: "workflow",
|
|
4342
4482
|
asyncDir: path.join(DIRS.async, workflowRunId),
|
|
4343
|
-
}, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) });
|
|
4483
|
+
}, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) });
|
|
4344
4484
|
} catch (error) {
|
|
4345
4485
|
if (error instanceof ActiveAsyncCapacityError) {
|
|
4346
4486
|
deps.state.activeAsyncCapacity = error.snapshot;
|
|
@@ -4414,7 +4554,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4414
4554
|
deps.state.workflowChildStops ??= new Map();
|
|
4415
4555
|
deps.state.workflowControllers.set(workflowRunId, controller);
|
|
4416
4556
|
workflowCapacity?.markWorkflowStarted();
|
|
4417
|
-
if (workflowCapacity) deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(currentSessionId, resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession), { liveWorkflowRunIds: new Set(deps.state.workflowControllers.keys()) });
|
|
4557
|
+
if (workflowCapacity) deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(currentSessionId, resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession), { liveWorkflowRunIds: new Set(deps.state.workflowControllers.keys()), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) });
|
|
4418
4558
|
let status: AsyncStatus = {
|
|
4419
4559
|
runId: workflowRunId,
|
|
4420
4560
|
toolCallId,
|
|
@@ -4431,6 +4571,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4431
4571
|
pid: process.pid,
|
|
4432
4572
|
steps: [],
|
|
4433
4573
|
workflow: { trace: [], emits: [], console: [] },
|
|
4574
|
+
workflowChildren: workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState: "running", inventoryComplete: false }),
|
|
4434
4575
|
runFanoutBudget: getRunFanoutBudgetSnapshot(workflowFanoutBudget),
|
|
4435
4576
|
};
|
|
4436
4577
|
const appendWorkflowEvent = (event: Record<string, unknown>) => {
|
|
@@ -4479,6 +4620,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4479
4620
|
if (persistClosed) return;
|
|
4480
4621
|
const liveJob = deps.state.asyncJobs.get(workflowRunId);
|
|
4481
4622
|
if (liveJob && (liveJob.status === "complete" || liveJob.status === "failed") && status.state !== "complete" && status.state !== "failed") return;
|
|
4623
|
+
const workflowState = status.state === "complete" ? "completed" : status.state === "failed" || status.state === "rejected" ? "failed" : status.state === "paused" ? "paused" : status.state === "stopped" ? "stopped" : "running";
|
|
4624
|
+
status.workflowChildren = workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState, inventoryComplete: workflowState !== "running", trace: status.workflow?.trace, steps: status.steps });
|
|
4482
4625
|
status.lastUpdate = Date.now();
|
|
4483
4626
|
if (!initialPersistenceComplete) {
|
|
4484
4627
|
writeAtomicJson(statusPath, status);
|
|
@@ -4522,6 +4665,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4522
4665
|
delete liveJob.agents;
|
|
4523
4666
|
}
|
|
4524
4667
|
liveJob.workflow = status.workflow;
|
|
4668
|
+
liveJob.workflowChildren = status.workflowChildren;
|
|
4525
4669
|
}
|
|
4526
4670
|
};
|
|
4527
4671
|
const writeWorkflowResult = (payload: Record<string, unknown>): boolean => {
|
|
@@ -4556,7 +4700,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4556
4700
|
status.toolCount = toolCounts.length > 0 ? toolCounts.reduce((total, count) => total + count, 0) : undefined;
|
|
4557
4701
|
status.currentStep = runningSteps.length === 1 ? steps.indexOf(runningSteps[0]!) : undefined;
|
|
4558
4702
|
};
|
|
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 };
|
|
4703
|
+
const workflowJob: AsyncJobState = { asyncId: workflowRunId, asyncDir, toolCallId, cwd: workflowCwd, ...(workflowSessionRoot ? { sessionRoot: workflowSessionRoot } : {}), status: "running", sessionId: currentSessionId ?? undefined, mode: "workflow", agents: [], steps: [], startedAt, updatedAt: startedAt, ...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}), ...(timeout !== undefined ? { timeoutMs: timeout, deadlineAt: startedAt + timeout } : {}), workflow: status.workflow, workflowChildren: status.workflowChildren };
|
|
4560
4704
|
deps.state.asyncJobs.set(workflowRunId, workflowJob);
|
|
4561
4705
|
deps.state.fleetJobs ??= new Map();
|
|
4562
4706
|
deps.state.fleetJobs.set(workflowRunId, workflowJob);
|
|
@@ -4743,7 +4887,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4743
4887
|
status: step.status,
|
|
4744
4888
|
heartbeat: { status: step.status, ...(childPhase ? { phase: childPhase } : {}) },
|
|
4745
4889
|
});
|
|
4746
|
-
}, ctx, preserveActiveSession);
|
|
4890
|
+
}, ctx, preserveActiveSession, workflowParentModel);
|
|
4747
4891
|
});
|
|
4748
4892
|
workflowResults.push(...result.details.results);
|
|
4749
4893
|
for (const childResult of result.details.results) {
|
|
@@ -4773,7 +4917,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4773
4917
|
}
|
|
4774
4918
|
return child;
|
|
4775
4919
|
},
|
|
4776
|
-
status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession)),
|
|
4920
|
+
status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession, workflowParentModel)),
|
|
4777
4921
|
resolveResume: (reference) => resolveKeyedWorkflowResume(reference, deps.state),
|
|
4778
4922
|
steer: (key, message, options, workflowSignal) => steerWorkflowChildByKey({ state: deps.state, workflowRunId, key, message, options, signal: workflowSignal, resolveRunId: () => workflowChildRunIds.get(key) }),
|
|
4779
4923
|
});
|
|
@@ -4783,15 +4927,16 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4783
4927
|
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, summary, producedChildOutputPaths);
|
|
4784
4928
|
const resultSummary = appendWorkflowOutputWarning(summary, outputWarning);
|
|
4785
4929
|
const workflowUsage = sumResultsUsage(workflowResults);
|
|
4786
|
-
|
|
4787
|
-
|
|
4930
|
+
const workflowChildren = workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState: "completed", inventoryComplete: true, trace: workflow.trace, children: workflow.children, steps: status.steps });
|
|
4931
|
+
status = { ...status, state: "complete", endedAt: Date.now(), workflow: { value: workflow.value, trace: workflow.trace, emits: workflow.emits, console: workflow.console }, workflowChildren, totalTokens: { input: workflowUsage.input, output: workflowUsage.output, total: workflowUsage.input + workflowUsage.output }, totalCost: sumResultsCost(workflowResults) };
|
|
4932
|
+
const receipt = terminalWorkflowReceipt(workflowRunId, "complete", workflow.children, workflowChildren);
|
|
4788
4933
|
let workflowReceipt: { path: string; receipt: WorkflowReceipt } | undefined;
|
|
4789
4934
|
try {
|
|
4790
4935
|
workflowReceipt = { path: writeWorkflowReceipt(asyncDir, receipt), receipt };
|
|
4791
4936
|
} catch (receiptError) {
|
|
4792
4937
|
appendWorkflowEvent({ type: "subagent.workflow.receipt_write_failed", error: `Failed to persist async workflow receipt: ${receiptError instanceof Error ? receiptError.message : String(receiptError)}` });
|
|
4793
4938
|
}
|
|
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;
|
|
4939
|
+
if (!writeWorkflowResult({ id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: true, state: "complete", summary: resultSummary, output: resultSummary, workflowChildren, results: workflow.children.map((child) => ({ workflowKey: child.key, ...(child.agent ? { agent: child.agent } : {}), ...(child.runId ? { runId: child.runId } : {}), output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, success: child.ok, ...(child.stopped ? { stopped: true } : {}), ...(child.interrupted ? { interrupted: true } : {}), ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, ...(workflowReceipt ? { workflowReceipt } : {}), asyncDir, cwd: workflowCwd, sessionId: currentSessionId, completionOwnerId, ...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}), timestamp: Date.now(), durationMs: Date.now() - startedAt })) return;
|
|
4795
4940
|
persist();
|
|
4796
4941
|
persistClosed = true;
|
|
4797
4942
|
appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(status.error ? { error: status.error } : {}) });
|
|
@@ -4813,7 +4958,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4813
4958
|
delete step.activityState;
|
|
4814
4959
|
}
|
|
4815
4960
|
}
|
|
4816
|
-
|
|
4961
|
+
const workflowState = state === "paused" ? "paused" : state === "stopped" ? "stopped" : "failed";
|
|
4962
|
+
const workflowChildren = workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState, inventoryComplete: true, trace: partial.trace, children: partial.children, steps: status.steps });
|
|
4963
|
+
status = compactOptional<AsyncStatus>({ ...status, state, stopped: stopped || undefined, activityState: pauseForDetached ? "needs_attention" : undefined, error: workflowFailureMessage(error, workflowRunId, partial.children), endedAt: Date.now(), workflow: { trace: partial.trace, emits: partial.emits, console: partial.console }, workflowChildren });
|
|
4817
4964
|
if (pauseForDetached) {
|
|
4818
4965
|
const promoted = promotePausedWorkflowIfSettled(status);
|
|
4819
4966
|
if (promoted) status = promoted;
|
|
@@ -4824,14 +4971,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4824
4971
|
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, terminalSummary, producedChildOutputPaths);
|
|
4825
4972
|
const resultSummary = appendWorkflowOutputWarning(terminalSummary, outputWarning);
|
|
4826
4973
|
const receiptState: WorkflowReceiptState = status.state === "complete" ? "complete" : status.state === "paused" ? "paused" : status.state === "stopped" ? "stopped" : "failed";
|
|
4827
|
-
const receipt = terminalWorkflowReceipt(workflowRunId, receiptState, partial.children);
|
|
4974
|
+
const receipt = terminalWorkflowReceipt(workflowRunId, receiptState, partial.children, workflowChildren);
|
|
4828
4975
|
let workflowReceipt: { path: string; receipt: WorkflowReceipt } | undefined;
|
|
4829
4976
|
try {
|
|
4830
4977
|
workflowReceipt = { path: writeWorkflowReceipt(asyncDir, receipt), receipt };
|
|
4831
4978
|
} catch (receiptError) {
|
|
4832
4979
|
appendWorkflowEvent({ type: "subagent.workflow.receipt_write_failed", error: `Failed to persist async workflow receipt: ${receiptError instanceof Error ? receiptError.message : String(receiptError)}` });
|
|
4833
4980
|
}
|
|
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;
|
|
4981
|
+
if (!writeWorkflowResult({ id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: status.state === "complete", state: status.state, summary: resultSummary, error: status.state === "complete" ? undefined : status.error, stopped: status.stopped, activityState: status.activityState, workflowChildren, results: partial.children.map((child) => ({ workflowKey: child.key, ...(child.agent ? { agent: child.agent } : {}), ...(child.runId ? { runId: child.runId } : {}), output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, success: child.ok, ...(child.stopped ? { stopped: true } : {}), ...(child.interrupted ? { interrupted: true } : {}), ...(child.detached && status.state !== "complete" ? { detached: true } : {}), ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, ...(workflowReceipt ? { workflowReceipt } : {}), asyncDir, cwd: workflowCwd, sessionId: currentSessionId, completionOwnerId, ...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}), timestamp: Date.now(), durationMs: Date.now() - startedAt })) return;
|
|
4835
4982
|
persist();
|
|
4836
4983
|
persistClosed = true;
|
|
4837
4984
|
appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(status.error ? { error: status.error } : {}), ...(status.activityState ? { activityState: status.activityState } : {}) });
|
|
@@ -4845,7 +4992,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4845
4992
|
});
|
|
4846
4993
|
return attachWorkflowMission(withRunFanoutBudget({
|
|
4847
4994
|
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 } : {}) },
|
|
4995
|
+
details: { mode: "workflow", runId: workflowRunId, toolCallId, asyncId: workflowRunId, asyncDir, results: [], workflowChildren: status.workflowChildren, chatProgress, ...(deps.state.activeAsyncCapacity ? { activeAsyncCapacity: deps.state.activeAsyncCapacity } : {}) },
|
|
4849
4996
|
}, workflowFanoutBudget));
|
|
4850
4997
|
}
|
|
4851
4998
|
const { workflowScript: _workflowScript, action: _action, agent: _agent, task: _task, resume: _resume, tasks: _tasks, chain: _chain, concurrency: _concurrency, async: _async, foregroundOnly: _foregroundOnly, clarify: _clarify, timeoutMs: _timeoutMs, maxRuntimeMs: _maxRuntimeMs, usageBudget: _usageBudget, chatProgress: _chatProgress, missionId: _missionId, mission: _mission, ...workflowChildDefaults } = requestParams;
|
|
@@ -4858,20 +5005,23 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4858
5005
|
const workflowResults: SingleResult[] = [];
|
|
4859
5006
|
const workflowChildRunIds = new Map<string, string>();
|
|
4860
5007
|
let liveWorkflow: NonNullable<Details["workflow"]> = { trace: [], emits: [], console: [] };
|
|
5008
|
+
let liveWorkflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: _id, workflowState: "running", inventoryComplete: false });
|
|
4861
5009
|
const workflowDeadlineAt = timeout === undefined ? undefined : Date.now() + timeout;
|
|
4862
5010
|
const workflowCapabilityCeiling = intersectSubagentCapabilityCeilings(requestParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(resolveCurrentSessionId(ctx.sessionManager)));
|
|
4863
5011
|
const sendWorkflowProgress = () => {
|
|
4864
|
-
const update = workflowChatProgressUpdate(_id, chatProgress, liveWorkflow);
|
|
5012
|
+
const update = workflowChatProgressUpdate(_id, chatProgress, liveWorkflow, liveWorkflowChildren);
|
|
4865
5013
|
if (update) onUpdate?.(update);
|
|
4866
5014
|
};
|
|
4867
5015
|
try {
|
|
4868
5016
|
const workflow = await runWorkflowScript({
|
|
4869
5017
|
script: requestParams.workflowScript,
|
|
5018
|
+
...(delegatedWorkflowPermit ? { oneUsePermit: { claim: (key: string) => claimWorkflowChildPermit(delegatedWorkflowPermit, _id, key) } } : {}),
|
|
4870
5019
|
timeoutMs: timeout,
|
|
4871
5020
|
signal,
|
|
4872
5021
|
...(workflowState ? { state: workflowState } : {}),
|
|
4873
5022
|
onTrace: (trace) => {
|
|
4874
5023
|
liveWorkflow = { ...liveWorkflow, trace };
|
|
5024
|
+
liveWorkflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: _id, workflowState: "running", inventoryComplete: false, trace });
|
|
4875
5025
|
sendWorkflowProgress();
|
|
4876
5026
|
},
|
|
4877
5027
|
admit: (calls) => {
|
|
@@ -4886,6 +5036,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4886
5036
|
sendWorkflowProgress();
|
|
4887
5037
|
},
|
|
4888
5038
|
launch: async (key, childParams, workflowSignal, admission) => {
|
|
5039
|
+
if (delegatedWorkflowPermit && admission.batch) throw new Error("Workflow child permit does not support runs.all.");
|
|
5040
|
+
if (delegatedWorkflowPermit && childParams.resume !== undefined) throw new Error("Workflow child permit does not support retained resume.");
|
|
5041
|
+
if (delegatedWorkflowPermit && childParams.async === true) throw new Error("Workflow child permit supports one foreground child only.");
|
|
4889
5042
|
if (workflowUsageBudget.budget && childParams.async === true) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, "workflow usageBudget does not support async runs.run launches."), childParams, deps.state);
|
|
4890
5043
|
const budgetState = usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults));
|
|
4891
5044
|
if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)), childParams, deps.state);
|
|
@@ -4904,6 +5057,15 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4904
5057
|
missionBinding,
|
|
4905
5058
|
deps.asyncByDefault,
|
|
4906
5059
|
);
|
|
5060
|
+
if (delegatedWorkflowPermit) {
|
|
5061
|
+
if (childRequest.async !== false) throw new Error("Workflow child permit supports one foreground child only.");
|
|
5062
|
+
const childCwd = resolveRequestedCwd(workflowCwd, childRequest.cwd);
|
|
5063
|
+
const childAgent = typeof childRequest.agent === "string"
|
|
5064
|
+
? resolveAgentName(childRequest.agent, deps.discoverAgents(childCwd, resolveExecutionAgentScope(childRequest.agentScope)).agents).agent
|
|
5065
|
+
: undefined;
|
|
5066
|
+
if (childAgent?.runner?.type === "external-cli" || childAgent?.runner?.type === "external-job") throw new Error("Workflow child permit supports native Pi children only.");
|
|
5067
|
+
workflowPermitContexts.set(childRequest, { child: { permit: delegatedWorkflowPermit, workflowRunId: _id, childKey: key } });
|
|
5068
|
+
}
|
|
4907
5069
|
workflowLaunchObservers.set(childRequest, (launch) => recordMissionWorkflowChild(missionBinding, _id, key, {
|
|
4908
5070
|
status: "running",
|
|
4909
5071
|
agent: launch.agent,
|
|
@@ -4917,7 +5079,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4917
5079
|
status: progressStatus,
|
|
4918
5080
|
heartbeat: { status: progressStatus, ...(childPhase ? { phase: childPhase } : {}) },
|
|
4919
5081
|
});
|
|
4920
|
-
}, ctx, preserveActiveSession);
|
|
5082
|
+
}, ctx, preserveActiveSession, workflowParentModel);
|
|
4921
5083
|
});
|
|
4922
5084
|
workflowResults.push(...result.details.results);
|
|
4923
5085
|
for (const childResult of result.details.results) {
|
|
@@ -4938,11 +5100,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4938
5100
|
});
|
|
4939
5101
|
return child;
|
|
4940
5102
|
},
|
|
4941
|
-
status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession)),
|
|
5103
|
+
status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession, workflowParentModel)),
|
|
4942
5104
|
resolveResume: (reference) => resolveKeyedWorkflowResume(reference, deps.state),
|
|
4943
5105
|
steer: (key, message, options, workflowSignal) => steerWorkflowChildByKey({ state: deps.state, workflowRunId: _id, key, message, options, signal: workflowSignal, resolveRunId: () => workflowChildRunIds.get(key) }),
|
|
4944
5106
|
});
|
|
4945
|
-
const
|
|
5107
|
+
const workflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: _id, workflowState: "completed", inventoryComplete: true, trace: workflow.trace, children: workflow.children });
|
|
5108
|
+
const receipt = terminalWorkflowReceipt(_id, "complete", workflow.children, workflowChildren);
|
|
4946
5109
|
const traceLines = workflow.trace.map((entry) => `- ${entry.operation} ${entry.key}: ${entry.state}${entry.runId ? ` (${entry.runId})` : ""}${entry.durationMs !== undefined ? ` in ${entry.durationMs}ms` : ""}${entry.error ? ` — ${entry.error}` : ""}`);
|
|
4947
5110
|
const sections = ["Workflow completed.", `Return:\n${formatWorkflowValue(workflow.value)}`];
|
|
4948
5111
|
if (workflow.emits.length > 0) sections.push(`Emitted:\n${workflow.emits.map(formatWorkflowValue).join("\n")}`);
|
|
@@ -4953,11 +5116,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4953
5116
|
const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
|
|
4954
5117
|
return attachWorkflowMission(withRunFanoutBudget({
|
|
4955
5118
|
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 }),
|
|
5119
|
+
details: compactOptional<Details>({ mode: "workflow", runId: _id, results: workflow.children.flatMap((child) => (child.results ?? []) as SingleResult[]), workflowChildren, totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { value: workflow.value, trace: workflow.trace, emits: workflow.emits, console: workflow.console, receipt }, chatProgress }),
|
|
4957
5120
|
}, workflowFanoutBudget));
|
|
4958
5121
|
} catch (error) {
|
|
4959
5122
|
const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
|
|
4960
|
-
const text = error
|
|
5123
|
+
const text = workflowFailureMessage(error, _id, partial.children);
|
|
4961
5124
|
const traceLines = partial.trace.map((entry) => `- ${entry.operation} ${entry.key}: ${entry.state}${entry.runId ? ` (${entry.runId})` : ""}${entry.error ? ` — ${entry.error}` : ""}`);
|
|
4962
5125
|
const sections = [`Workflow failed: ${text}`];
|
|
4963
5126
|
if (partial.emits.length > 0) sections.push(`Emitted:\n${partial.emits.map(formatWorkflowValue).join("\n")}`);
|
|
@@ -4966,11 +5129,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4966
5129
|
const workflowText = sections.join("\n\n");
|
|
4967
5130
|
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, workflowText, producedChildOutputPaths);
|
|
4968
5131
|
const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
|
|
4969
|
-
const
|
|
5132
|
+
const workflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: _id, workflowState: "failed", inventoryComplete: true, trace: partial.trace, children: partial.children });
|
|
5133
|
+
const receipt = terminalWorkflowReceipt(_id, "failed", partial.children, workflowChildren);
|
|
4970
5134
|
return attachWorkflowMission(withRunFanoutBudget({
|
|
4971
5135
|
content: [{ type: "text", text: displayText }],
|
|
4972
5136
|
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 }),
|
|
5137
|
+
details: compactOptional<Details>({ mode: "workflow", runId: _id, results: partial.children.flatMap((child) => (child.results ?? []) as SingleResult[]), workflowChildren, totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { trace: partial.trace, emits: partial.emits, console: partial.console, receipt }, chatProgress }),
|
|
4974
5138
|
}, workflowFanoutBudget));
|
|
4975
5139
|
}
|
|
4976
5140
|
}
|
|
@@ -4984,9 +5148,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4984
5148
|
try {
|
|
4985
5149
|
requestSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
4986
5150
|
requestPiSessionId = ctx.sessionManager.getSessionId() ?? undefined;
|
|
4987
|
-
requestParentModel =
|
|
4988
|
-
?
|
|
4989
|
-
:
|
|
5151
|
+
requestParentModel = parentModelOverride !== undefined
|
|
5152
|
+
? parentModelOverride ?? undefined
|
|
5153
|
+
: preserveActiveSession
|
|
5154
|
+
? normalizeParentModel(ctx.model)
|
|
5155
|
+
: rememberParentModel(deps.state, requestSessionId, ctx.model);
|
|
4990
5156
|
} catch (error) {
|
|
4991
5157
|
if (action?.toLowerCase() !== "doctor" && action?.toLowerCase() !== "guide") throw error;
|
|
4992
5158
|
requestParentModel = normalizeParentModel(ctx.model);
|
|
@@ -5211,7 +5377,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5211
5377
|
}
|
|
5212
5378
|
const spawnBudget = getSpawnBudgetSnapshot(deps.state, deps.config, currentSessionId);
|
|
5213
5379
|
const activeAsyncCapacity = currentSessionId
|
|
5214
|
-
? getActiveAsyncCapacitySnapshot(currentSessionId, resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession), { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) })
|
|
5380
|
+
? getActiveAsyncCapacitySnapshot(currentSessionId, resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession), { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })
|
|
5215
5381
|
: { used: 0, limit: resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession) ?? 0 };
|
|
5216
5382
|
deps.state.activeAsyncCapacity = activeAsyncCapacity;
|
|
5217
5383
|
return {
|
|
@@ -5256,10 +5422,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5256
5422
|
if (paramsWithResolvedCwd.view) {
|
|
5257
5423
|
return withBudget({ content: [{ type: "text", text: "action='debug.run' does not support status views." }], isError: true, details: { mode: "management", results: [] } });
|
|
5258
5424
|
}
|
|
5259
|
-
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots })));
|
|
5425
|
+
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots, abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })));
|
|
5260
5426
|
}
|
|
5261
5427
|
if (paramsWithResolvedCwd.view === "fleet") {
|
|
5262
|
-
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots })));
|
|
5428
|
+
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots, abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })));
|
|
5263
5429
|
}
|
|
5264
5430
|
if (targetRunId) {
|
|
5265
5431
|
try {
|
|
@@ -5290,7 +5456,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5290
5456
|
});
|
|
5291
5457
|
}
|
|
5292
5458
|
}
|
|
5293
|
-
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots })));
|
|
5459
|
+
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots, abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })));
|
|
5294
5460
|
}
|
|
5295
5461
|
if (action === "resume") {
|
|
5296
5462
|
return resumeAsyncRun(omitUndefinedProperties({ params: paramsWithResolvedCwd, requestCwd, ctx, deps, parentModel: requestParentModel, signal }));
|
|
@@ -5616,7 +5782,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5616
5782
|
const parentSessionFile = ctx.sessionManager.getSessionFile() ?? null;
|
|
5617
5783
|
const discovered = deps.discoverAgents(effectiveCwd, scope);
|
|
5618
5784
|
const discoveredAgents = discovered.agents;
|
|
5619
|
-
const
|
|
5785
|
+
const unknownAgentDiagnosticContext = diagnosticContextFromDiscovery(discovered, effectiveCwd, scope);
|
|
5786
|
+
const canonicalParams = canonicalizeExecutionParams(effectiveParams, discoveredAgents, discovered.agentDiagnostics, unknownAgentDiagnosticContext);
|
|
5620
5787
|
if (canonicalParams.error) return buildRequestedModeError(effectiveParams, canonicalParams.error);
|
|
5621
5788
|
effectiveParams = canonicalParams.params!;
|
|
5622
5789
|
const modelScope = discovered.modelScope;
|
|
@@ -5665,6 +5832,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5665
5832
|
hasTasks,
|
|
5666
5833
|
hasSingle,
|
|
5667
5834
|
allowClarifyTaskPrompt,
|
|
5835
|
+
unknownAgentDiagnosticContext,
|
|
5668
5836
|
);
|
|
5669
5837
|
if (validationError) return validationError;
|
|
5670
5838
|
|
|
@@ -5679,6 +5847,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5679
5847
|
if (spawnPreflight.error) return spawnBudgetErrorResult(spawnPreflight.error, foregroundMode);
|
|
5680
5848
|
|
|
5681
5849
|
let forkSessionFileForIndex: (idx?: number) => string | undefined = () => undefined;
|
|
5850
|
+
let prepareForkSessionForIndex: (idx?: number) => Promise<void> = async () => {};
|
|
5682
5851
|
let forkThinkingOverrideForIndex: (idx?: number) => AgentConfig["thinking"] | undefined = () => undefined;
|
|
5683
5852
|
let prepareForkThinking = (_agentName: string, _index: number, _modelOverride?: string, _modelOverrideFromParent?: boolean): void => {};
|
|
5684
5853
|
const forkThinkingRequirements = new Map<number, boolean>();
|
|
@@ -5714,9 +5883,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5714
5883
|
|| candidates.some((candidate) => forkedChildRequiresThinkingOff(candidate, forkAvailableModels, parentModel?.provider)),
|
|
5715
5884
|
);
|
|
5716
5885
|
};
|
|
5886
|
+
const pruneSession = contextPolicy.usesFork && deps.config.forkContext?.mode === "pruned"
|
|
5887
|
+
? await createPrunedForkSessionWriter(ctx, deps.config.forkContext, signal)
|
|
5888
|
+
: undefined;
|
|
5717
5889
|
const forkContextResolver = createForkContextResolver(ctx.sessionManager, contextPolicy.usesFork ? "fork" : undefined, {
|
|
5718
5890
|
forceThinkingOffForIndex: (index) => forkThinkingRequirements.get(index) ?? true,
|
|
5891
|
+
...(pruneSession ? { pruneSession } : {}),
|
|
5719
5892
|
});
|
|
5893
|
+
prepareForkSessionForIndex = forkContextResolver.prepareSessionForIndex;
|
|
5720
5894
|
forkSessionFileForIndex = forkContextResolver.sessionFileForIndex;
|
|
5721
5895
|
forkThinkingOverrideForIndex = forkContextResolver.thinkingOverrideForIndex;
|
|
5722
5896
|
} catch (error) {
|
|
@@ -5760,8 +5934,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5760
5934
|
runId: asyncRunId,
|
|
5761
5935
|
kind: "runner",
|
|
5762
5936
|
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() ?? []) });
|
|
5937
|
+
}, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) });
|
|
5938
|
+
deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(requestSessionId, activeLimit, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) });
|
|
5765
5939
|
} catch (error) {
|
|
5766
5940
|
if (error instanceof ActiveAsyncCapacityError) {
|
|
5767
5941
|
deps.state.activeAsyncCapacity = error.snapshot;
|
|
@@ -5824,6 +5998,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5824
5998
|
prepareForkThinking(agentName, idx, modelOverride, modelOverrideFromParent);
|
|
5825
5999
|
return forkSessionFileForIndex(idx);
|
|
5826
6000
|
};
|
|
6001
|
+
const prepareForkSessionForTask: PrepareForkSessionForTask = async (agentName, idx = 0, modelOverride, modelOverrideFromParent) => {
|
|
6002
|
+
if (!shouldForkAgent(contextPolicy, agentName)) return;
|
|
6003
|
+
prepareForkThinking(agentName, idx, modelOverride, modelOverrideFromParent);
|
|
6004
|
+
await prepareForkSessionForIndex(idx);
|
|
6005
|
+
};
|
|
5827
6006
|
const forkThinkingOverrideForTask: ForkThinkingOverrideForTask = (agentName, idx = 0, modelOverride, modelOverrideFromParent) => {
|
|
5828
6007
|
if (!shouldForkAgent(contextPolicy, agentName)) return delegatedThinkingOverride;
|
|
5829
6008
|
prepareForkThinking(agentName, idx, modelOverride, modelOverrideFromParent);
|
|
@@ -5836,8 +6015,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5836
6015
|
const childSessionFileForIndex = (idx?: number) =>
|
|
5837
6016
|
path.join(sessionDirForIndex(idx), "session.jsonl");
|
|
5838
6017
|
try {
|
|
5839
|
-
if (!(effectiveParams.clarify === true && ctx.hasUI)) {
|
|
5840
|
-
preflightForkSessionsForStaticTasks(effectiveParams, contextPolicy,
|
|
6018
|
+
if (!(effectiveParams.clarify === true && ctx.hasUI) || deps.config.forkContext?.mode === "pruned") {
|
|
6019
|
+
await preflightForkSessionsForStaticTasks(effectiveParams, contextPolicy, prepareForkSessionForTask, deps.config.chain?.dynamicFanout?.maxItems);
|
|
5841
6020
|
}
|
|
5842
6021
|
} catch (error) {
|
|
5843
6022
|
activeAsyncCapacity?.rollback();
|
|
@@ -5910,6 +6089,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5910
6089
|
signal,
|
|
5911
6090
|
onUpdate: onUpdateWithContext,
|
|
5912
6091
|
agents,
|
|
6092
|
+
unknownAgentDiagnosticContext,
|
|
5913
6093
|
recoveryAgents: discoveredAgents,
|
|
5914
6094
|
runId,
|
|
5915
6095
|
shareEnabled,
|
|
@@ -5942,6 +6122,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5942
6122
|
runFanoutBudget,
|
|
5943
6123
|
topLevelAsyncCapacityEligible,
|
|
5944
6124
|
activeAsyncCapacity,
|
|
6125
|
+
workflowChildPermitLaunch,
|
|
5945
6126
|
});
|
|
5946
6127
|
|
|
5947
6128
|
const foregroundDescription = selectedAgentNames.length === 1
|
|
@@ -6192,7 +6373,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6192
6373
|
if (!normalized.ok) {
|
|
6193
6374
|
return Promise.resolve({ content: [{ type: "text", text: normalized.error }], isError: true, details: { mode: normalized.mode, results: [] } });
|
|
6194
6375
|
}
|
|
6195
|
-
|
|
6376
|
+
const loaded = loadWorkflowScriptPath(normalized.params, ctx.cwd);
|
|
6377
|
+
if (loaded.error) {
|
|
6378
|
+
return Promise.resolve({ content: [{ type: "text", text: loaded.error }], isError: true, details: { mode: normalized.params.action ? "management" : "workflow", results: [] } });
|
|
6379
|
+
}
|
|
6380
|
+
return executeWithSingleDispatchGuard(id, loaded.params!, signal, onUpdate, ctx);
|
|
6196
6381
|
};
|
|
6197
6382
|
|
|
6198
6383
|
const executeDelegated = async (
|
|
@@ -6206,13 +6391,17 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6206
6391
|
const privateParams = delegatedParams as SubagentParamsLike & {
|
|
6207
6392
|
delegatedThinkingOverride?: AgentConfig["thinking"];
|
|
6208
6393
|
delegatedAllowZeroToolBudget?: true;
|
|
6394
|
+
delegatedWorkflowPermit?: WorkflowChildPermit;
|
|
6209
6395
|
};
|
|
6210
6396
|
const thinkingOverride = privateParams.delegatedThinkingOverride;
|
|
6211
6397
|
const allowZeroToolBudget = privateParams.delegatedAllowZeroToolBudget === true;
|
|
6398
|
+
const workflowPermit = privateParams.delegatedWorkflowPermit;
|
|
6212
6399
|
delete privateParams.delegatedThinkingOverride;
|
|
6213
6400
|
delete privateParams.delegatedAllowZeroToolBudget;
|
|
6401
|
+
delete privateParams.delegatedWorkflowPermit;
|
|
6214
6402
|
if (thinkingOverride !== undefined) delegatedThinkingOverrides.set(delegatedParams, thinkingOverride);
|
|
6215
6403
|
if (allowZeroToolBudget) delegatedZeroToolBudgets.add(delegatedParams);
|
|
6404
|
+
if (workflowPermit) workflowPermitContexts.set(delegatedParams, { root: workflowPermit });
|
|
6216
6405
|
delegatedExecutions.add(delegatedParams);
|
|
6217
6406
|
return execute(id, delegatedParams, signal, onUpdate, ctx);
|
|
6218
6407
|
};
|