pi-subagents 0.51.0 → 0.52.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 +41 -0
- package/agents/oracle.md +3 -1
- package/agents/reviewer.md +1 -0
- package/agents/scout.md +2 -2
- package/agents/worker.md +2 -1
- package/docs/agents.md +8 -3
- package/docs/extension-api.md +1 -1
- package/docs/models.md +2 -0
- package/docs/observability.md +37 -1
- package/docs/workflows.md +2 -0
- package/package.json +1 -1
- package/skills/pi-subagents/SKILL.md +1 -0
- package/skills/pi-subagents/references/constraints-and-recipes.md +6 -2
- package/skills/pi-subagents/references/execution-controls.md +12 -1
- package/skills/pi-subagents/references/prompting-and-roles.md +2 -0
- package/src/agents/agent-management.ts +24 -6
- package/src/agents/agents.ts +37 -11
- package/src/agents/skills.ts +1 -1
- package/src/api/preflight.ts +5 -2
- package/src/extension/index.ts +1 -0
- package/src/extension/public-execution.ts +3 -1
- package/src/intercom/native-supervisor-channel.ts +2 -1
- package/src/missions/workflow-state.ts +2 -2
- package/src/runs/background/async-execution.ts +19 -9
- package/src/runs/background/async-resume.ts +19 -2
- package/src/runs/background/async-retention.ts +2 -4
- package/src/runs/background/completion-dedupe.ts +5 -1
- package/src/runs/background/completion-replay.ts +22 -12
- package/src/runs/background/fleet-view.ts +68 -19
- package/src/runs/background/inspect-rpc.ts +443 -0
- package/src/runs/background/notify.ts +28 -4
- package/src/runs/background/result-watcher.ts +44 -0
- package/src/runs/background/resume-guidance.ts +8 -5
- package/src/runs/background/scheduled-runs.ts +2 -0
- package/src/runs/background/subagent-runner.ts +3 -2
- package/src/runs/background/subagent-wait.ts +1 -1
- package/src/runs/background/wait-subscriptions.ts +1 -1
- package/src/runs/foreground/execution.ts +1 -1
- package/src/runs/foreground/subagent-executor.ts +239 -76
- package/src/runs/foreground/workflow-detach-reconcile.ts +194 -0
- package/src/runs/shared/acceptance.ts +4 -4
- package/src/runs/shared/external-job-bridge.ts +0 -6
- package/src/runs/shared/model-fallback.ts +77 -34
- package/src/runs/shared/session-lease.ts +0 -6
- package/src/runs/shared/worktree.ts +2 -1
- package/src/shared/types.ts +11 -0
- package/src/slash/slash-bridge.ts +2 -1
- package/src/slash/slash-commands.ts +18 -0
- package/src/watchdog/change-signature.ts +1 -1
- package/src/watchdog/lsp-diagnostics.ts +1 -0
- package/src/workflows/chat-progress.ts +1 -1
- package/src/workflows/scripted-workflow.ts +13 -2
- package/agents/gpt-pro.md +0 -17
|
@@ -7,7 +7,7 @@ import { findBlockingAgentDiagnostic, resolveAgentName, type AgentConfig, type A
|
|
|
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";
|
|
10
|
-
import { isStorageCapacityError } from "../../shared/file-system-retry.ts";
|
|
10
|
+
import { isRetryableFileSystemError, isStorageCapacityError } from "../../shared/file-system-retry.ts";
|
|
11
11
|
import { resolveEffectiveThinking, toModelInfo, type ModelInfo } from "../../shared/model-info.ts";
|
|
12
12
|
import {
|
|
13
13
|
beginForegroundChild,
|
|
@@ -28,7 +28,7 @@ import { normalizePublicSubagentExecution } from "../../extension/public-executi
|
|
|
28
28
|
import { runSync } from "./execution.ts";
|
|
29
29
|
import { handleWatchdogToolAction, WATCHDOG_TOOL_ACTIONS } from "../../watchdog/tool-actions.ts";
|
|
30
30
|
import type { MainWatchdogRuntime } from "../../watchdog/runtime.ts";
|
|
31
|
-
import { buildModelCandidates, normalizeParentModel, resolveEffectiveSubagentModel, resolveModelCandidate, type ParentModel } from "../shared/model-fallback.ts";
|
|
31
|
+
import { buildModelCandidates, inheritsParentModel, normalizeParentModel, resolveEffectiveSubagentModel, resolveModelCandidate, type ParentModel } from "../shared/model-fallback.ts";
|
|
32
32
|
import { formatRetainedChildren, listRetainedChildren } from "../background/retained-children.ts";
|
|
33
33
|
import type { ModelScopeConfig } from "../shared/model-scope.ts";
|
|
34
34
|
import { aggregateParallelOutputs } from "../shared/parallel-utils.ts";
|
|
@@ -88,7 +88,7 @@ import {
|
|
|
88
88
|
resolveSubagentResultStatus,
|
|
89
89
|
stripDetailsOutputsForIntercomReceipt,
|
|
90
90
|
} from "../../intercom/result-intercom.ts";
|
|
91
|
-
import { applySteeringRecoveryAgentConfig, buildRevivedAsyncTask, resolveAsyncResumeTarget, resolveAsyncRunLocation } from "../background/async-resume.ts";
|
|
91
|
+
import { applySteeringRecoveryAgentConfig, asyncReviveRequiresRecoveryDescriptor, buildRevivedAsyncTask, resolveAsyncResumeTarget, resolveAsyncRunLocation } from "../background/async-resume.ts";
|
|
92
92
|
import { deliverInterruptRequest, readRevivalBriefs, requestAsyncSteer, type SteerDeliveryMode } from "../background/control-channel.ts";
|
|
93
93
|
import { updateSteeringTarget, waitForSteeringAction } from "../background/steering.ts";
|
|
94
94
|
import { canQueueRetainedAsyncFollowUp, steerAsyncRun } from "./async-steering-action.ts";
|
|
@@ -101,6 +101,7 @@ import {
|
|
|
101
101
|
} from "./workflow-foreground-steering.ts";
|
|
102
102
|
import { stopAsyncRun } from "./async-stop-action.ts";
|
|
103
103
|
import { dismissRecoveredWorkflow } from "./async-dismiss-action.ts";
|
|
104
|
+
import { promotePausedWorkflowIfSettled, reconcileDetachedWorkflowChildCompletion } from "./workflow-detach-reconcile.ts";
|
|
104
105
|
import { reconcileAsyncRun } from "../background/stale-run-reconciler.ts";
|
|
105
106
|
import { resolveAsyncRootResultPath, waitForImportedAsyncRoot } from "../background/chain-root-attachment.ts";
|
|
106
107
|
import { resultFilePath, writeAsyncResultFile } from "../background/result-files.ts";
|
|
@@ -171,6 +172,7 @@ import {
|
|
|
171
172
|
resolveCurrentMaxSubagentDepth,
|
|
172
173
|
resolveMaxSubagentSpawnsPerRun,
|
|
173
174
|
wrapForkTask,
|
|
175
|
+
type ScheduleOrigin,
|
|
174
176
|
} from "../../shared/types.ts";
|
|
175
177
|
|
|
176
178
|
const MUTATING_MANAGEMENT_ACTIONS = new Set(["create", "update", "delete", "eject", "disable", "enable", "reset", "grant-spawn-budget", "watchdog.configure", "mission.create", "mission.update", "mission.resolve-decision", "mission.attach-run", "mission.close", "inspector.open", "inspector.close", "project.open", "project.close", "worktree.discard", "refine", "refine.rollback", "dismiss", "schedule.create", "schedule.pause", "schedule.resume", "schedule.run", "schedule.run-due", "schedule.delete"]);
|
|
@@ -292,6 +294,8 @@ export interface SubagentParamsLike {
|
|
|
292
294
|
/** Internal workflow ownership metadata; not part of the public schema. */
|
|
293
295
|
workflowParentRunId?: string;
|
|
294
296
|
workflowKey?: string;
|
|
297
|
+
/** Set by the scheduler so this run's completion can name the schedule that produced it. */
|
|
298
|
+
scheduleOrigin?: ScheduleOrigin;
|
|
295
299
|
workflowChildAsyncId?: string;
|
|
296
300
|
workflowParentDeadlineAt?: number;
|
|
297
301
|
suppressRoutineResultIntercom?: boolean;
|
|
@@ -386,8 +390,8 @@ interface ExecutorDeps {
|
|
|
386
390
|
kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean;
|
|
387
391
|
}
|
|
388
392
|
|
|
389
|
-
type ForkSessionFileForTask = (agentName: string, idx?: number, modelOverride?: string) => string | undefined;
|
|
390
|
-
type ForkThinkingOverrideForTask = (agentName: string, idx?: number, modelOverride?: string) => AgentConfig["thinking"] | undefined;
|
|
393
|
+
type ForkSessionFileForTask = (agentName: string, idx?: number, modelOverride?: string, modelOverrideFromParent?: boolean) => string | undefined;
|
|
394
|
+
type ForkThinkingOverrideForTask = (agentName: string, idx?: number, modelOverride?: string, modelOverrideFromParent?: boolean) => AgentConfig["thinking"] | undefined;
|
|
391
395
|
|
|
392
396
|
interface ExecutionContextData {
|
|
393
397
|
params: SubagentParamsLike;
|
|
@@ -953,11 +957,48 @@ function emitAdvisoryControlEvent(pi: ExtensionAPI, channel: string, payload: un
|
|
|
953
957
|
}
|
|
954
958
|
}
|
|
955
959
|
|
|
960
|
+
function persistAsyncWorkflowControlEvent(input: {
|
|
961
|
+
job: AsyncJobState;
|
|
962
|
+
event: ControlEvent;
|
|
963
|
+
controlConfig: ResolvedControlConfig;
|
|
964
|
+
intercomBridge: IntercomBridgeState;
|
|
965
|
+
childIntercomTarget?: string;
|
|
966
|
+
}): void {
|
|
967
|
+
const channels = input.event.type === "active_long_running"
|
|
968
|
+
? input.controlConfig.notifyChannels.filter((channel) => channel !== "intercom")
|
|
969
|
+
: input.controlConfig.notifyChannels;
|
|
970
|
+
if (channels.length === 0) return;
|
|
971
|
+
const record = {
|
|
972
|
+
ts: Date.now(),
|
|
973
|
+
runId: input.job.asyncId,
|
|
974
|
+
type: "subagent.control",
|
|
975
|
+
event: input.event,
|
|
976
|
+
channels,
|
|
977
|
+
childIntercomTarget: input.childIntercomTarget,
|
|
978
|
+
noticeText: formatControlNoticeMessage(input.event, input.childIntercomTarget),
|
|
979
|
+
...(input.intercomBridge.active && input.intercomBridge.orchestratorTarget && channels.includes("intercom")
|
|
980
|
+
? {
|
|
981
|
+
intercom: {
|
|
982
|
+
to: input.intercomBridge.orchestratorTarget,
|
|
983
|
+
message: formatControlIntercomMessage(input.event, input.childIntercomTarget),
|
|
984
|
+
},
|
|
985
|
+
}
|
|
986
|
+
: {}),
|
|
987
|
+
};
|
|
988
|
+
try {
|
|
989
|
+
fs.appendFileSync(path.join(input.job.asyncDir, "events.jsonl"), `${JSON.stringify(record)}\n`, "utf-8");
|
|
990
|
+
} catch (error) {
|
|
991
|
+
if (!isStorageCapacityError(error)) throw error;
|
|
992
|
+
console.error("Failed to append async workflow control event while storage is full:", error);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
|
|
956
996
|
function emitControlNotification(input: {
|
|
957
997
|
pi: ExtensionAPI;
|
|
958
998
|
controlConfig: ResolvedControlConfig;
|
|
959
999
|
intercomBridge: IntercomBridgeState;
|
|
960
1000
|
event: ControlEvent;
|
|
1001
|
+
source?: "foreground" | "async";
|
|
961
1002
|
}): void {
|
|
962
1003
|
if (!shouldNotifyControlEvent(input.controlConfig, input.event)) return;
|
|
963
1004
|
const childIntercomTarget = input.intercomBridge.active
|
|
@@ -965,7 +1006,7 @@ function emitControlNotification(input: {
|
|
|
965
1006
|
: undefined;
|
|
966
1007
|
const payload = {
|
|
967
1008
|
event: input.event,
|
|
968
|
-
source: "foreground"
|
|
1009
|
+
source: input.source ?? "foreground",
|
|
969
1010
|
childIntercomTarget,
|
|
970
1011
|
noticeText: formatControlNoticeMessage(input.event, childIntercomTarget),
|
|
971
1012
|
};
|
|
@@ -1660,7 +1701,7 @@ async function resumeAsyncRun(input: {
|
|
|
1660
1701
|
if (!revivalSessionFile) {
|
|
1661
1702
|
return { content: [{ type: "text", text: `Async child '${target.runId}' has no persisted session file to resume.` }], isError: true, details: { mode: "management", results: [] } };
|
|
1662
1703
|
}
|
|
1663
|
-
if (target.source === "async" &&
|
|
1704
|
+
if (target.source === "async" && asyncReviveRequiresRecoveryDescriptor(target)) {
|
|
1664
1705
|
return { content: [{ type: "text", text: `Async child '${target.runId}' is missing its required run fan-out recovery identity. Start a new run instead.` }], isError: true, details: { mode: "management", results: [] } };
|
|
1665
1706
|
}
|
|
1666
1707
|
const runId = randomUUID();
|
|
@@ -1725,6 +1766,7 @@ async function resumeAsyncRun(input: {
|
|
|
1725
1766
|
...(input.deps.state.currentSessionId ? { parentSessionId: input.deps.state.currentSessionId } : {}),
|
|
1726
1767
|
},
|
|
1727
1768
|
modelOverride: recoveryDescriptor?.model ?? target.model,
|
|
1769
|
+
modelOverrideFromParent: recoveryDescriptor?.modelOverrideFromParent,
|
|
1728
1770
|
thinkingOverride: recoveryDescriptor?.thinking ?? target.thinking,
|
|
1729
1771
|
outputBaseDir: resolveSingleRunOutputBaseDir(input.deps, artifactsDir, runId),
|
|
1730
1772
|
maxSubagentDepth: recoveryDescriptor?.maxSubagentDepth ?? resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
|
|
@@ -1874,14 +1916,32 @@ function formatFailedSingleRunOutput(result: SingleResult, displayOutput: string
|
|
|
1874
1916
|
return lines.join("\n");
|
|
1875
1917
|
}
|
|
1876
1918
|
|
|
1877
|
-
function createForegroundControlNotifier(data: Pick<ExecutionContextData, "controlConfig" | "intercomBridge">, deps: Pick<ExecutorDeps, "pi" | "state">): (event: ControlEvent) => void {
|
|
1919
|
+
function createForegroundControlNotifier(data: Pick<ExecutionContextData, "controlConfig" | "intercomBridge" | "params">, deps: Pick<ExecutorDeps, "pi" | "state">): (event: ControlEvent) => void {
|
|
1878
1920
|
return (event) => {
|
|
1879
1921
|
applyControlEventToRememberedForegroundRun(deps.state, event);
|
|
1922
|
+
const parentWorkflowRunId = data.params.workflowParentRunId;
|
|
1923
|
+
const asyncWorkflow = typeof parentWorkflowRunId === "string" ? deps.state.asyncJobs.get(parentWorkflowRunId) : undefined;
|
|
1924
|
+
const workflowKey = typeof data.params.workflowKey === "string" && data.params.workflowKey.trim()
|
|
1925
|
+
? data.params.workflowKey.trim()
|
|
1926
|
+
: undefined;
|
|
1927
|
+
const enriched = workflowKey && !event.workflowKey ? { ...event, workflowKey } : event;
|
|
1928
|
+
if (asyncWorkflow) {
|
|
1929
|
+
persistAsyncWorkflowControlEvent({
|
|
1930
|
+
job: asyncWorkflow,
|
|
1931
|
+
event: enriched,
|
|
1932
|
+
controlConfig: data.controlConfig,
|
|
1933
|
+
intercomBridge: data.intercomBridge,
|
|
1934
|
+
childIntercomTarget: data.intercomBridge.active
|
|
1935
|
+
? resolveSubagentIntercomTarget(enriched.runId, enriched.agent, enriched.index)
|
|
1936
|
+
: undefined,
|
|
1937
|
+
});
|
|
1938
|
+
}
|
|
1880
1939
|
emitControlNotification({
|
|
1881
1940
|
pi: deps.pi,
|
|
1882
1941
|
controlConfig: data.controlConfig,
|
|
1883
1942
|
intercomBridge: data.intercomBridge,
|
|
1884
|
-
event,
|
|
1943
|
+
event: enriched,
|
|
1944
|
+
source: asyncWorkflow ? "async" : "foreground",
|
|
1885
1945
|
});
|
|
1886
1946
|
};
|
|
1887
1947
|
}
|
|
@@ -2775,6 +2835,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2775
2835
|
const modelOverride = a.runner?.type === "external-cli" || a.runner?.type === "external-job"
|
|
2776
2836
|
? params.model ?? (externalRunnerWithoutExplicitModel ? undefined : a.model)
|
|
2777
2837
|
: resolveEffectiveSubagentModel(params.model as string | undefined, a.model, parentModel, availableModels, currentProvider, data.modelScope === undefined ? {} : { scope: data.modelScope });
|
|
2838
|
+
const modelOverrideFromParent = inheritsParentModel(params.model as string | undefined, a.model, parentModel);
|
|
2778
2839
|
return executeAsyncSingle(id, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
|
|
2779
2840
|
agent: params.agent!,
|
|
2780
2841
|
task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(params.task ?? "") : (params.task ?? ""),
|
|
@@ -2790,7 +2851,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2790
2851
|
shareEnabled,
|
|
2791
2852
|
activeAsyncCapacity: data.activeAsyncCapacity,
|
|
2792
2853
|
sessionRoot,
|
|
2793
|
-
sessionFile: sessionFileForTask(params.agent!, 0, modelOverride),
|
|
2854
|
+
sessionFile: sessionFileForTask(params.agent!, 0, modelOverride, modelOverrideFromParent),
|
|
2794
2855
|
context: contextPolicy.contextForAgent(params.agent!),
|
|
2795
2856
|
skills,
|
|
2796
2857
|
output: effectiveOutput,
|
|
@@ -2798,7 +2859,8 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2798
2859
|
...(params.reads !== undefined ? { reads: params.reads } : {}),
|
|
2799
2860
|
outputBaseDir: resolveSingleRunOutputBaseDir(deps, artifactsDir, id),
|
|
2800
2861
|
modelOverride,
|
|
2801
|
-
|
|
2862
|
+
modelOverrideFromParent,
|
|
2863
|
+
thinkingOverride: externalRunnerWithoutExplicitModel ? undefined : thinkingOverrideForTask(params.agent!, 0, modelOverride, modelOverrideFromParent),
|
|
2802
2864
|
maxSubagentDepth,
|
|
2803
2865
|
waitToolEnabled: deps.waitToolEnabled,
|
|
2804
2866
|
...(params.worktree === true ? { worktree: true } : {}),
|
|
@@ -2917,9 +2979,19 @@ function resolveWorkflowChildOutputPath(input: {
|
|
|
2917
2979
|
configuredOutputBaseDir?: string;
|
|
2918
2980
|
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[] };
|
|
2919
2981
|
workflowAgentScope?: unknown;
|
|
2982
|
+
state?: SubagentState;
|
|
2920
2983
|
key: string;
|
|
2921
2984
|
params: Record<string, unknown>;
|
|
2922
|
-
}): string
|
|
2985
|
+
}): { path?: string; inherited: boolean } {
|
|
2986
|
+
if (typeof input.params.resume === "string") {
|
|
2987
|
+
if (!input.state) return { path: undefined, inherited: false };
|
|
2988
|
+
const index = input.params.index;
|
|
2989
|
+
const target = resolveResumeTarget({
|
|
2990
|
+
id: input.params.resume.trim(),
|
|
2991
|
+
...(typeof index === "number" && Number.isInteger(index) ? { index } : {}),
|
|
2992
|
+
}, input.state);
|
|
2993
|
+
return { path: "recoveryDescriptor" in target ? target.recoveryDescriptor?.outputPath : undefined, inherited: false };
|
|
2994
|
+
}
|
|
2923
2995
|
const rawOutput = input.params.output;
|
|
2924
2996
|
const hasExplicitOutput = typeof rawOutput === "string" || typeof rawOutput === "boolean";
|
|
2925
2997
|
const childCwd = typeof input.params.cwd === "string" ? resolveChildCwd(input.workflowCwd, input.params.cwd) : input.workflowCwd;
|
|
@@ -2934,7 +3006,10 @@ function resolveWorkflowChildOutputPath(input: {
|
|
|
2934
3006
|
: input.aggregateOutputPath
|
|
2935
3007
|
? workflowChildDefaultOutput(input.aggregateOutputPath, input.artifactsDir, input.workflowRunId, input.key)
|
|
2936
3008
|
: agentOutput;
|
|
2937
|
-
return
|
|
3009
|
+
return {
|
|
3010
|
+
path: resolveSingleOutputPath(output, input.ctxCwd, childCwd, input.configuredOutputBaseDir),
|
|
3011
|
+
inherited: !hasExplicitOutput && !input.aggregateOutputPath && agentOutput !== undefined,
|
|
3012
|
+
};
|
|
2938
3013
|
}
|
|
2939
3014
|
|
|
2940
3015
|
function workflowChildOutputClaims(input: {
|
|
@@ -2946,18 +3021,35 @@ function workflowChildOutputClaims(input: {
|
|
|
2946
3021
|
configuredOutputBaseDir?: string;
|
|
2947
3022
|
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[] };
|
|
2948
3023
|
workflowAgentScope?: unknown;
|
|
3024
|
+
state: SubagentState;
|
|
2949
3025
|
claimedOutputPaths: Map<string, string>;
|
|
2950
3026
|
entries: Array<{ key: string; params: Record<string, unknown> }>;
|
|
2951
|
-
}): { error?: string; claims?: Map<string, string> } {
|
|
3027
|
+
}): { error?: string; claims?: Map<string, string>; overrides?: Map<string, string> } {
|
|
3028
|
+
const resolvedEntries = input.entries.map(({ key, params }) => ({
|
|
3029
|
+
key,
|
|
3030
|
+
...resolveWorkflowChildOutputPath({ ...input, key, params }),
|
|
3031
|
+
}));
|
|
3032
|
+
const paths = new Map<string, number>();
|
|
3033
|
+
for (const claimedPath of input.claimedOutputPaths.keys()) paths.set(claimedPath, 1);
|
|
3034
|
+
for (const { path: resolved } of resolvedEntries) {
|
|
3035
|
+
if (resolved) paths.set(resolved, (paths.get(resolved) ?? 0) + 1);
|
|
3036
|
+
}
|
|
3037
|
+
const overrides = new Map<string, string>();
|
|
3038
|
+
for (const entry of resolvedEntries) {
|
|
3039
|
+
if (entry.inherited && entry.path && (paths.get(entry.path) ?? 0) > 1) {
|
|
3040
|
+
const output = workflowChildDefaultOutput(input.aggregateOutputPath, input.artifactsDir, input.workflowRunId, entry.key);
|
|
3041
|
+
overrides.set(entry.key, output);
|
|
3042
|
+
entry.path = output;
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
2952
3045
|
const claims = new Map(input.claimedOutputPaths);
|
|
2953
|
-
for (const { key,
|
|
2954
|
-
const resolved = resolveWorkflowChildOutputPath({ ...input, key, params });
|
|
3046
|
+
for (const { key, path: resolved } of resolvedEntries) {
|
|
2955
3047
|
if (!resolved) continue;
|
|
2956
3048
|
const previous = claims.get(resolved);
|
|
2957
3049
|
if (previous) return { error: `Workflow children '${previous}' and '${key}' resolve output to the same path: ${resolved}. Use distinct child output paths.` };
|
|
2958
3050
|
claims.set(resolved, key);
|
|
2959
3051
|
}
|
|
2960
|
-
return { claims };
|
|
3052
|
+
return { claims, overrides };
|
|
2961
3053
|
}
|
|
2962
3054
|
|
|
2963
3055
|
function applyWorkflowChildOutputClaims(target: Map<string, string>, claims: Map<string, string>): void {
|
|
@@ -2977,14 +3069,17 @@ function prepareWorkflowChildLaunchParams(input: {
|
|
|
2977
3069
|
configuredOutputBaseDir?: string;
|
|
2978
3070
|
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[] };
|
|
2979
3071
|
workflowAgentScope?: unknown;
|
|
3072
|
+
outputOverride?: string;
|
|
2980
3073
|
options?: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean; runFanoutBudget?: RunFanoutBudgetDescriptor; parentDeadlineAt?: number };
|
|
2981
3074
|
}): SubagentParamsLike {
|
|
2982
3075
|
let childParams = input.childParams;
|
|
2983
|
-
if (input.childParams.output === undefined && input.childParams.resume === undefined && input.
|
|
3076
|
+
if (input.childParams.output === undefined && input.childParams.resume === undefined && input.outputOverride !== undefined) {
|
|
3077
|
+
childParams = { ...input.childParams, output: input.outputOverride };
|
|
3078
|
+
} else if (input.childParams.output === undefined && input.childParams.resume === undefined && input.aggregateOutputPath !== undefined) {
|
|
2984
3079
|
childParams = { ...input.childParams, output: workflowChildDefaultOutput(input.aggregateOutputPath, input.artifactsDir, input.parentWorkflowRunId, input.workflowKey) };
|
|
2985
3080
|
} else if (input.childParams.resume === undefined) {
|
|
2986
3081
|
const resolvedOutput = resolveWorkflowChildOutputPath({ ctxCwd: input.ctxCwd, workflowCwd: input.workflowCwd, artifactsDir: input.artifactsDir, workflowRunId: input.parentWorkflowRunId, aggregateOutputPath: input.aggregateOutputPath, configuredOutputBaseDir: input.configuredOutputBaseDir, discoverAgents: input.discoverAgents, workflowAgentScope: input.workflowAgentScope, key: input.workflowKey, params: input.childParams });
|
|
2987
|
-
if (resolvedOutput) childParams = { ...input.childParams, output: resolvedOutput };
|
|
3082
|
+
if (resolvedOutput.path) childParams = { ...input.childParams, output: resolvedOutput.path };
|
|
2988
3083
|
}
|
|
2989
3084
|
return prepareWorkflowLaunchParams(input.workflowDefaults, childParams, input.parentWorkflowRunId, input.workflowKey, input.options);
|
|
2990
3085
|
}
|
|
@@ -3089,6 +3184,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3089
3184
|
currentProvider,
|
|
3090
3185
|
data.modelScope === undefined ? {} : { scope: data.modelScope },
|
|
3091
3186
|
);
|
|
3187
|
+
const modelOverrideFromParent = inheritsParentModel(params.model as string | undefined, agentConfig.model, parentModel);
|
|
3092
3188
|
let skillOverride: string[] | false | undefined = normalizeSkillInput(params.skill);
|
|
3093
3189
|
let readsOverride: string[] | false | undefined = params.reads;
|
|
3094
3190
|
const rawOutput = params.output !== undefined ? params.output : agentConfig.output;
|
|
@@ -3163,7 +3259,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3163
3259
|
let detachForeground: ((reason?: string) => boolean) | undefined;
|
|
3164
3260
|
const foregroundControl = deps.state.foregroundControls.get(runId);
|
|
3165
3261
|
if (foregroundControl) {
|
|
3166
|
-
const thinking = resolveEffectiveThinking(modelOverride, thinkingOverrideForTask(params.agent!, 0, modelOverride));
|
|
3262
|
+
const thinking = resolveEffectiveThinking(modelOverride, thinkingOverrideForTask(params.agent!, 0, modelOverride, modelOverrideFromParent));
|
|
3167
3263
|
beginForegroundChild(foregroundControl, omitUndefinedProperties({
|
|
3168
3264
|
index: 0,
|
|
3169
3265
|
agent: params.agent!,
|
|
@@ -3208,7 +3304,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3208
3304
|
intercomEvents: deps.pi.events,
|
|
3209
3305
|
runId,
|
|
3210
3306
|
sessionDir: sessionDirForIndex(0),
|
|
3211
|
-
sessionFile: sessionFileForTask(params.agent!, 0, modelOverride),
|
|
3307
|
+
sessionFile: sessionFileForTask(params.agent!, 0, modelOverride, modelOverrideFromParent),
|
|
3212
3308
|
share: shareEnabled,
|
|
3213
3309
|
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
3214
3310
|
artifactConfig,
|
|
@@ -3225,7 +3321,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3225
3321
|
nestedRoute: foregroundControl?.nestedRoute,
|
|
3226
3322
|
index: 0,
|
|
3227
3323
|
modelOverride,
|
|
3228
|
-
|
|
3324
|
+
modelOverrideFromParent,
|
|
3325
|
+
thinkingOverride: thinkingOverrideForTask(params.agent!, 0, modelOverride, modelOverrideFromParent),
|
|
3229
3326
|
availableModels,
|
|
3230
3327
|
preferredModelProvider: currentProvider,
|
|
3231
3328
|
modelScope: data.modelScope,
|
|
@@ -3241,10 +3338,21 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3241
3338
|
onDetachedExit: (result) => {
|
|
3242
3339
|
try {
|
|
3243
3340
|
try {
|
|
3244
|
-
updateRememberedForegroundChild(deps.state, { runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, index: 0, result, events: deps.pi.events, notify:
|
|
3341
|
+
updateRememberedForegroundChild(deps.state, { runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, index: 0, result, events: deps.pi.events, notify: true });
|
|
3245
3342
|
} catch {
|
|
3246
3343
|
// Remembered foreground state is best-effort; run history and cleanup must still complete.
|
|
3247
3344
|
}
|
|
3345
|
+
const workflowParentRunId = params.workflowParentRunId ?? foregroundControl?.parentWorkflowRunId;
|
|
3346
|
+
if (workflowParentRunId) {
|
|
3347
|
+
reconcileDetachedWorkflowChildCompletion({
|
|
3348
|
+
state: deps.state,
|
|
3349
|
+
workflowRunId: workflowParentRunId,
|
|
3350
|
+
childRunId: runId,
|
|
3351
|
+
result,
|
|
3352
|
+
events: deps.pi.events,
|
|
3353
|
+
workflowKey: params.workflowKey ?? foregroundControl?.workflowKey,
|
|
3354
|
+
});
|
|
3355
|
+
}
|
|
3248
3356
|
} finally {
|
|
3249
3357
|
try {
|
|
3250
3358
|
if (!artifactConfig.enabled) cleanupStructuredOutputRuntime(structuredRuntime);
|
|
@@ -3611,6 +3719,7 @@ export function prepareWorkflowLaunchParams(
|
|
|
3611
3719
|
return {
|
|
3612
3720
|
action: "resume",
|
|
3613
3721
|
id: childParams.resume.trim(),
|
|
3722
|
+
...(typeof childParams.index === "number" && Number.isInteger(childParams.index) ? { index: childParams.index } : {}),
|
|
3614
3723
|
message: typeof childParams.task === "string" ? childParams.task.trim() : "",
|
|
3615
3724
|
workflowParentRunId: parentWorkflowRunId,
|
|
3616
3725
|
workflowKey,
|
|
@@ -3896,6 +4005,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3896
4005
|
...(timeout !== undefined ? { deadlineAt: startedAt + timeout, timeoutMs: timeout } : {}),
|
|
3897
4006
|
cwd: workflowCwd,
|
|
3898
4007
|
...(workflowSessionRoot ? { sessionRoot: workflowSessionRoot } : {}),
|
|
4008
|
+
...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}),
|
|
3899
4009
|
pid: process.pid,
|
|
3900
4010
|
steps: [],
|
|
3901
4011
|
workflow: { trace: [], emits: [], console: [] },
|
|
@@ -3905,8 +4015,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3905
4015
|
try {
|
|
3906
4016
|
fs.appendFileSync(eventsPath, `${JSON.stringify({ ts: Date.now(), runId: workflowRunId, ...event })}\n`, "utf-8");
|
|
3907
4017
|
} catch (error) {
|
|
3908
|
-
|
|
3909
|
-
|
|
4018
|
+
// The event log is a journal, not workflow truth. Callers append from
|
|
4019
|
+
// inside run-result handling, so losing an entry to a full disk or to a
|
|
4020
|
+
// transient Windows lock must not fail the run being recorded.
|
|
4021
|
+
if (!isStorageCapacityError(error) && !isRetryableFileSystemError(error)) throw error;
|
|
4022
|
+
console.error(`Failed to append async workflow event '${eventsPath}':`, error);
|
|
3910
4023
|
}
|
|
3911
4024
|
};
|
|
3912
4025
|
let indexedState: AsyncStatus["state"] | undefined;
|
|
@@ -3932,34 +4045,62 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3932
4045
|
: writeAtomicJson(filePath, payload),
|
|
3933
4046
|
});
|
|
3934
4047
|
let initialPersistenceComplete = false;
|
|
3935
|
-
|
|
4048
|
+
let persistClosed = false;
|
|
4049
|
+
let statusPersistenceDegraded = false;
|
|
4050
|
+
// Progress journalling runs from admit, launch, the launch and progress observers,
|
|
4051
|
+
// onTrace and onEmit -- all inside the promises a workflowScript awaits. Those
|
|
4052
|
+
// callers pass tolerateStatusWriteFailure so a transient lock on status.json cannot
|
|
4053
|
+
// mark a finished child failed or abort its still-running siblings. Initial and
|
|
4054
|
+
// terminal writes stay fail-fast on purpose: no child work is at risk by then, and
|
|
4055
|
+
// silently dropping a terminal write would leave status.json and the active-run
|
|
4056
|
+
// index pinned at "running" after the result already says complete.
|
|
4057
|
+
const persist = (options: { tolerateStatusWriteFailure?: boolean } = {}) => {
|
|
4058
|
+
if (persistClosed) return;
|
|
4059
|
+
const liveJob = deps.state.asyncJobs.get(workflowRunId);
|
|
4060
|
+
if (liveJob && (liveJob.status === "complete" || liveJob.status === "failed") && status.state !== "complete" && status.state !== "failed") return;
|
|
3936
4061
|
status.lastUpdate = Date.now();
|
|
3937
|
-
if (initialPersistenceComplete)
|
|
3938
|
-
else {
|
|
4062
|
+
if (!initialPersistenceComplete) {
|
|
3939
4063
|
writeAtomicJson(statusPath, status);
|
|
3940
4064
|
initialPersistenceComplete = true;
|
|
3941
4065
|
queueActiveRunIndex();
|
|
4066
|
+
} else if (options.tolerateStatusWriteFailure) {
|
|
4067
|
+
try {
|
|
4068
|
+
runPersistence.write(statusPath, status);
|
|
4069
|
+
statusPersistenceDegraded = false;
|
|
4070
|
+
} catch (error) {
|
|
4071
|
+
const message = `Failed to persist async workflow state ${statusPath}: ${error instanceof Error ? error.message : String(error)}`;
|
|
4072
|
+
console.error(message, error);
|
|
4073
|
+
if (!statusPersistenceDegraded) {
|
|
4074
|
+
statusPersistenceDegraded = true;
|
|
4075
|
+
try {
|
|
4076
|
+
appendWorkflowEvent({ type: "subagent.workflow.status_write_failed", error: message });
|
|
4077
|
+
} catch (eventError) {
|
|
4078
|
+
console.error(`Failed to record degraded status persistence for '${statusPath}':`, eventError);
|
|
4079
|
+
}
|
|
4080
|
+
}
|
|
4081
|
+
}
|
|
4082
|
+
} else {
|
|
4083
|
+
runPersistence.write(statusPath, status);
|
|
3942
4084
|
}
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
job.currentStep = status.currentStep;
|
|
4085
|
+
if (liveJob) {
|
|
4086
|
+
liveJob.status = status.state;
|
|
4087
|
+
liveJob.updatedAt = status.lastUpdate;
|
|
4088
|
+
liveJob.activityState = status.activityState;
|
|
4089
|
+
liveJob.lastActivityAt = status.lastActivityAt;
|
|
4090
|
+
liveJob.currentTool = status.currentTool;
|
|
4091
|
+
liveJob.currentToolStartedAt = status.currentToolStartedAt;
|
|
4092
|
+
liveJob.currentPath = status.currentPath;
|
|
4093
|
+
liveJob.turnCount = status.turnCount;
|
|
4094
|
+
liveJob.toolCount = status.toolCount;
|
|
4095
|
+
liveJob.currentStep = status.currentStep;
|
|
3955
4096
|
if (status.steps) {
|
|
3956
|
-
|
|
3957
|
-
|
|
4097
|
+
liveJob.steps = status.steps.map((step, index) => ({ ...step, index }));
|
|
4098
|
+
liveJob.agents = status.steps.map((step) => step.agent);
|
|
3958
4099
|
} else {
|
|
3959
|
-
delete
|
|
3960
|
-
delete
|
|
4100
|
+
delete liveJob.steps;
|
|
4101
|
+
delete liveJob.agents;
|
|
3961
4102
|
}
|
|
3962
|
-
|
|
4103
|
+
liveJob.workflow = status.workflow;
|
|
3963
4104
|
}
|
|
3964
4105
|
};
|
|
3965
4106
|
const writeWorkflowResult = (payload: Record<string, unknown>): boolean => {
|
|
@@ -3994,7 +4135,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3994
4135
|
status.toolCount = toolCounts.length > 0 ? toolCounts.reduce((total, count) => total + count, 0) : undefined;
|
|
3995
4136
|
status.currentStep = runningSteps.length === 1 ? steps.indexOf(runningSteps[0]!) : undefined;
|
|
3996
4137
|
};
|
|
3997
|
-
const workflowJob: AsyncJobState = { asyncId: workflowRunId, asyncDir, toolCallId, cwd: workflowCwd, ...(workflowSessionRoot ? { sessionRoot: workflowSessionRoot } : {}), status: "running", sessionId: currentSessionId ?? undefined, mode: "workflow", agents: [], steps: [], startedAt, updatedAt: startedAt, ...(timeout !== undefined ? { timeoutMs: timeout, deadlineAt: startedAt + timeout } : {}), workflow: status.workflow };
|
|
4138
|
+
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 };
|
|
3998
4139
|
deps.state.asyncJobs.set(workflowRunId, workflowJob);
|
|
3999
4140
|
deps.state.fleetJobs ??= new Map();
|
|
4000
4141
|
deps.state.fleetJobs.set(workflowRunId, workflowJob);
|
|
@@ -4019,6 +4160,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4019
4160
|
const configuredOutputBaseDir = resolveConfiguredSingleRunOutputBaseDir(deps);
|
|
4020
4161
|
const workflowAggregateOutputPath = resolveWorkflowAggregateOutputPath(workflowOutput, ctx.cwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, workflowRunId), configuredOutputBaseDir);
|
|
4021
4162
|
const claimedOutputPaths = new Map<string, string>();
|
|
4163
|
+
const childOutputOverrides = new Map<string, string>();
|
|
4022
4164
|
const producedChildOutputPaths = new Set<string>();
|
|
4023
4165
|
const workflowSteps = new Map<string, NonNullable<AsyncStatus["steps"]>[number]>();
|
|
4024
4166
|
let projectedTraceLength = 0;
|
|
@@ -4081,7 +4223,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4081
4223
|
projectedTraceLength = trace.length;
|
|
4082
4224
|
projectedTraceTail = trace.at(-1);
|
|
4083
4225
|
projectWorkflowActivity();
|
|
4084
|
-
persist();
|
|
4226
|
+
persist({ tolerateStatusWriteFailure: true });
|
|
4085
4227
|
appendWorkflowEvent({ type: "subagent.workflow.trace", trace });
|
|
4086
4228
|
};
|
|
4087
4229
|
try {
|
|
@@ -4092,16 +4234,17 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4092
4234
|
...(workflowState ? { state: workflowState } : {}),
|
|
4093
4235
|
onTrace: updateTrace,
|
|
4094
4236
|
admit: (calls) => {
|
|
4095
|
-
const outputClaims = workflowChildOutputClaims({ ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, workflowAgentScope: workflowChildDefaults.agentScope, claimedOutputPaths, entries: calls });
|
|
4237
|
+
const outputClaims = workflowChildOutputClaims({ ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, workflowAgentScope: workflowChildDefaults.agentScope, state: deps.state, claimedOutputPaths, entries: calls });
|
|
4096
4238
|
if (outputClaims.error) throw new Error(outputClaims.error);
|
|
4097
4239
|
status.runFanoutBudget = claimRunFanoutBatch(workflowFanoutBudget, calls.map(({ key }) => `workflow[${key}]`));
|
|
4098
4240
|
if (outputClaims.claims) applyWorkflowChildOutputClaims(claimedOutputPaths, outputClaims.claims);
|
|
4099
|
-
|
|
4241
|
+
if (outputClaims.overrides) for (const [key, output] of outputClaims.overrides) childOutputOverrides.set(key, output);
|
|
4242
|
+
persist({ tolerateStatusWriteFailure: true });
|
|
4100
4243
|
},
|
|
4101
4244
|
onEmit: (emits) => {
|
|
4102
4245
|
// Each emit is validated at the host boundary in runWorkflowScript before onEmit fires.
|
|
4103
4246
|
status.workflow = { ...(status.workflow ?? { trace: [], console: [] }), emits };
|
|
4104
|
-
persist();
|
|
4247
|
+
persist({ tolerateStatusWriteFailure: true });
|
|
4105
4248
|
appendWorkflowEvent({ type: "subagent.workflow.emit", value: emits.at(-1) });
|
|
4106
4249
|
},
|
|
4107
4250
|
launch: async (key, childParams, workflowSignal, admission) => {
|
|
@@ -4119,7 +4262,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4119
4262
|
});
|
|
4120
4263
|
const result = await runMissionWorkflowChild(missionBinding, workflowRunId, key, childPhase, () => {
|
|
4121
4264
|
const childRequest = bindMissionWorkflowChildAsyncLaunch(
|
|
4122
|
-
{ ...prepareWorkflowChildLaunchParams({ workflowDefaults: workflowChildDefaults, childParams, parentWorkflowRunId: workflowRunId, workflowKey: key, ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, workflowAgentScope: workflowChildDefaults.agentScope, options: { missionDetached: detachWorkflowChildMissions, runFanoutBudget: workflowFanoutBudget, parentDeadlineAt: workflowDeadlineAt } }), runFanoutAdmitted: admission.admitted },
|
|
4265
|
+
{ ...prepareWorkflowChildLaunchParams({ workflowDefaults: workflowChildDefaults, childParams, parentWorkflowRunId: workflowRunId, workflowKey: key, ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, workflowAgentScope: workflowChildDefaults.agentScope, outputOverride: childOutputOverrides.get(key), options: { missionDetached: detachWorkflowChildMissions, runFanoutBudget: workflowFanoutBudget, parentDeadlineAt: workflowDeadlineAt } }), runFanoutAdmitted: admission.admitted },
|
|
4123
4266
|
missionBinding,
|
|
4124
4267
|
deps.asyncByDefault,
|
|
4125
4268
|
);
|
|
@@ -4130,7 +4273,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4130
4273
|
step.sessionFile = launch.sessionFile;
|
|
4131
4274
|
step.async = launch.async;
|
|
4132
4275
|
if (launch.runId) step.runId = launch.runId;
|
|
4133
|
-
persist();
|
|
4276
|
+
persist({ tolerateStatusWriteFailure: true });
|
|
4134
4277
|
}
|
|
4135
4278
|
recordMissionWorkflowChild(missionBinding, workflowRunId, key, { status: "running", agent: launch.agent, ...(launch.sessionFile ? { sessionPath: launch.sessionFile } : {}) });
|
|
4136
4279
|
});
|
|
@@ -4153,7 +4296,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4153
4296
|
step.thinking = progress.thinking;
|
|
4154
4297
|
step.error = progress.error;
|
|
4155
4298
|
projectWorkflowActivity();
|
|
4156
|
-
persist();
|
|
4299
|
+
persist({ tolerateStatusWriteFailure: true });
|
|
4157
4300
|
recordMissionWorkflowChild(missionBinding, workflowRunId, key, {
|
|
4158
4301
|
status: step.status,
|
|
4159
4302
|
heartbeat: { status: step.status, ...(childPhase ? { phase: childPhase } : {}) },
|
|
@@ -4198,8 +4341,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4198
4341
|
const resultSummary = appendWorkflowOutputWarning(summary, outputWarning);
|
|
4199
4342
|
const workflowUsage = sumResultsUsage(workflowResults);
|
|
4200
4343
|
status = { ...status, state: "complete", endedAt: Date.now(), workflow: { value: workflow.value, trace: workflow.trace, emits: workflow.emits, console: workflow.console }, totalTokens: { input: workflowUsage.input, output: workflowUsage.output, total: workflowUsage.input + workflowUsage.output }, totalCost: sumResultsCost(workflowResults) };
|
|
4201
|
-
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.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, asyncDir, cwd: workflowCwd, sessionId: currentSessionId, completionOwnerId, timestamp: Date.now(), durationMs: Date.now() - startedAt })) return;
|
|
4344
|
+
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.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, asyncDir, cwd: workflowCwd, sessionId: currentSessionId, completionOwnerId, ...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}), timestamp: Date.now(), durationMs: Date.now() - startedAt })) return;
|
|
4202
4345
|
persist();
|
|
4346
|
+
persistClosed = true;
|
|
4203
4347
|
appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(status.error ? { error: status.error } : {}) });
|
|
4204
4348
|
} catch (error) {
|
|
4205
4349
|
const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
|
|
@@ -4210,17 +4354,31 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4210
4354
|
const state = stopped ? "stopped" : pauseForDetached ? "paused" : "failed";
|
|
4211
4355
|
for (const step of status.steps ?? []) {
|
|
4212
4356
|
if (step.workflowKey && detachedChildKeys.has(step.workflowKey)) {
|
|
4357
|
+
if (step.status === "completed" || step.status === "complete" || step.status === "failed") continue;
|
|
4213
4358
|
step.status = "paused";
|
|
4214
4359
|
step.activityState = "needs_attention";
|
|
4360
|
+
} else if (pauseForDetached && step.status === "running") {
|
|
4361
|
+
step.status = "stopped";
|
|
4362
|
+
step.stopped = true;
|
|
4363
|
+
delete step.activityState;
|
|
4215
4364
|
}
|
|
4216
4365
|
}
|
|
4217
4366
|
status = compactOptional<AsyncStatus>({ ...status, state, stopped: stopped || undefined, activityState: pauseForDetached ? "needs_attention" : undefined, error: error instanceof Error ? error.message : String(error), endedAt: Date.now(), workflow: { trace: partial.trace, emits: partial.emits, console: partial.console } });
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4367
|
+
if (pauseForDetached) {
|
|
4368
|
+
const promoted = promotePausedWorkflowIfSettled(status);
|
|
4369
|
+
if (promoted) status = promoted;
|
|
4370
|
+
}
|
|
4371
|
+
const terminalSummary = status.state === "complete"
|
|
4372
|
+
? "Workflow completed after detached child finished."
|
|
4373
|
+
: status.error ?? (pauseForDetached ? "Workflow paused." : "Workflow failed.");
|
|
4374
|
+
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, terminalSummary, producedChildOutputPaths);
|
|
4375
|
+
const resultSummary = appendWorkflowOutputWarning(terminalSummary, outputWarning);
|
|
4376
|
+
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.detached && status.state !== "complete" ? { detached: true } : {}), ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, asyncDir, cwd: workflowCwd, sessionId: currentSessionId, completionOwnerId, ...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}), timestamp: Date.now(), durationMs: Date.now() - startedAt })) return;
|
|
4221
4377
|
persist();
|
|
4222
|
-
|
|
4378
|
+
persistClosed = true;
|
|
4379
|
+
appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(status.error ? { error: status.error } : {}), ...(status.activityState ? { activityState: status.activityState } : {}) });
|
|
4223
4380
|
} finally {
|
|
4381
|
+
persistClosed = true;
|
|
4224
4382
|
deps.state.workflowControllers?.delete(workflowRunId);
|
|
4225
4383
|
deps.state.activeAsyncCapacity = workflowCapacity?.reconcile(new Set(deps.state.workflowControllers?.keys() ?? []))
|
|
4226
4384
|
?? deps.state.activeAsyncCapacity;
|
|
@@ -4236,6 +4394,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4236
4394
|
const configuredOutputBaseDir = resolveConfiguredSingleRunOutputBaseDir(deps);
|
|
4237
4395
|
const workflowAggregateOutputPath = resolveWorkflowAggregateOutputPath(workflowOutput, ctx.cwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, _id), configuredOutputBaseDir);
|
|
4238
4396
|
const claimedOutputPaths = new Map<string, string>();
|
|
4397
|
+
const childOutputOverrides = new Map<string, string>();
|
|
4239
4398
|
const producedChildOutputPaths = new Set<string>();
|
|
4240
4399
|
const workflowResults: SingleResult[] = [];
|
|
4241
4400
|
const workflowChildRunIds = new Map<string, string>();
|
|
@@ -4256,10 +4415,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4256
4415
|
sendWorkflowProgress();
|
|
4257
4416
|
},
|
|
4258
4417
|
admit: (calls) => {
|
|
4259
|
-
const outputClaims = workflowChildOutputClaims({ ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId: _id, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, workflowAgentScope: workflowChildDefaults.agentScope, claimedOutputPaths, entries: calls });
|
|
4418
|
+
const outputClaims = workflowChildOutputClaims({ ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId: _id, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, workflowAgentScope: workflowChildDefaults.agentScope, state: deps.state, claimedOutputPaths, entries: calls });
|
|
4260
4419
|
if (outputClaims.error) throw new Error(outputClaims.error);
|
|
4261
4420
|
claimRunFanoutBatch(workflowFanoutBudget, calls.map(({ key }) => `workflow[${key}]`));
|
|
4262
4421
|
if (outputClaims.claims) applyWorkflowChildOutputClaims(claimedOutputPaths, outputClaims.claims);
|
|
4422
|
+
if (outputClaims.overrides) for (const [key, output] of outputClaims.overrides) childOutputOverrides.set(key, output);
|
|
4263
4423
|
},
|
|
4264
4424
|
onEmit: (emits) => {
|
|
4265
4425
|
liveWorkflow = { ...liveWorkflow, emits };
|
|
@@ -4280,7 +4440,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4280
4440
|
});
|
|
4281
4441
|
const result = await runMissionWorkflowChild(missionBinding, _id, key, childPhase, () => {
|
|
4282
4442
|
const childRequest = bindMissionWorkflowChildAsyncLaunch(
|
|
4283
|
-
{ ...prepareWorkflowChildLaunchParams({ workflowDefaults: workflowChildDefaults, childParams, parentWorkflowRunId: _id, workflowKey: key, ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, workflowAgentScope: workflowChildDefaults.agentScope, options: { missionDetached: detachWorkflowChildMissions, suppressRoutineResultIntercom: chatProgress.mode === "live-card", runFanoutBudget: workflowFanoutBudget, parentDeadlineAt: workflowDeadlineAt } }), runFanoutAdmitted: admission.admitted },
|
|
4443
|
+
{ ...prepareWorkflowChildLaunchParams({ workflowDefaults: workflowChildDefaults, childParams, parentWorkflowRunId: _id, workflowKey: key, ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, workflowAgentScope: workflowChildDefaults.agentScope, outputOverride: childOutputOverrides.get(key), options: { missionDetached: detachWorkflowChildMissions, suppressRoutineResultIntercom: chatProgress.mode === "live-card", runFanoutBudget: workflowFanoutBudget, parentDeadlineAt: workflowDeadlineAt } }), runFanoutAdmitted: admission.admitted },
|
|
4284
4444
|
missionBinding,
|
|
4285
4445
|
deps.asyncByDefault,
|
|
4286
4446
|
);
|
|
@@ -5018,30 +5178,33 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5018
5178
|
|
|
5019
5179
|
let forkSessionFileForIndex: (idx?: number) => string | undefined = () => undefined;
|
|
5020
5180
|
let forkThinkingOverrideForIndex: (idx?: number) => AgentConfig["thinking"] | undefined = () => undefined;
|
|
5021
|
-
let prepareForkThinking = (_agentName: string, _index: number, _modelOverride?: string): void => {};
|
|
5181
|
+
let prepareForkThinking = (_agentName: string, _index: number, _modelOverride?: string, _modelOverrideFromParent?: boolean): void => {};
|
|
5022
5182
|
const forkThinkingRequirements = new Map<number, boolean>();
|
|
5023
5183
|
const forkThinkingDowngrades = new Map<number, string>();
|
|
5024
5184
|
try {
|
|
5025
5185
|
const forkAvailableModels = contextPolicy.usesFork ? ctx.modelRegistry.getAvailable().map(toModelInfo) : [];
|
|
5026
5186
|
const parentModel = requestParentModel;
|
|
5027
|
-
prepareForkThinking = (agentName, index, modelOverride) => {
|
|
5187
|
+
prepareForkThinking = (agentName, index, modelOverride, modelOverrideFromParent) => {
|
|
5028
5188
|
const agentConfig = agents.find((agent) => agent.name === agentName);
|
|
5029
5189
|
if (agentConfig?.runner?.type === "external-cli" || agentConfig?.runner?.type === "external-job") {
|
|
5030
5190
|
forkThinkingRequirements.set(index, true);
|
|
5031
5191
|
return;
|
|
5032
5192
|
}
|
|
5033
|
-
const primaryModel =
|
|
5034
|
-
modelOverride
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5193
|
+
const primaryModel = modelOverrideFromParent
|
|
5194
|
+
? modelOverride
|
|
5195
|
+
: resolveEffectiveSubagentModel(
|
|
5196
|
+
modelOverride,
|
|
5197
|
+
agentConfig?.model,
|
|
5198
|
+
parentModel,
|
|
5199
|
+
forkAvailableModels,
|
|
5200
|
+
parentModel?.provider,
|
|
5201
|
+
);
|
|
5040
5202
|
const candidates = buildModelCandidates(
|
|
5041
5203
|
primaryModel,
|
|
5042
5204
|
agentConfig?.fallbackModels,
|
|
5043
5205
|
forkAvailableModels,
|
|
5044
5206
|
parentModel?.provider,
|
|
5207
|
+
{ primaryModelFromParent: modelOverrideFromParent ?? inheritsParentModel(modelOverride, agentConfig?.model, parentModel) },
|
|
5045
5208
|
);
|
|
5046
5209
|
forkThinkingRequirements.set(
|
|
5047
5210
|
index,
|
|
@@ -5153,20 +5316,20 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5153
5316
|
}
|
|
5154
5317
|
const sessionDirForIndex = (idx?: number) =>
|
|
5155
5318
|
path.join(sessionRoot, `run-${idx ?? 0}`);
|
|
5156
|
-
const forkSessionFileForTask: ForkSessionFileForTask = (agentName, idx = 0, modelOverride) => {
|
|
5319
|
+
const forkSessionFileForTask: ForkSessionFileForTask = (agentName, idx = 0, modelOverride, modelOverrideFromParent) => {
|
|
5157
5320
|
if (!shouldForkAgent(contextPolicy, agentName)) return undefined;
|
|
5158
|
-
prepareForkThinking(agentName, idx, modelOverride);
|
|
5321
|
+
prepareForkThinking(agentName, idx, modelOverride, modelOverrideFromParent);
|
|
5159
5322
|
return forkSessionFileForIndex(idx);
|
|
5160
5323
|
};
|
|
5161
|
-
const forkThinkingOverrideForTask: ForkThinkingOverrideForTask = (agentName, idx = 0, modelOverride) => {
|
|
5324
|
+
const forkThinkingOverrideForTask: ForkThinkingOverrideForTask = (agentName, idx = 0, modelOverride, modelOverrideFromParent) => {
|
|
5162
5325
|
if (!shouldForkAgent(contextPolicy, agentName)) return delegatedThinkingOverride;
|
|
5163
|
-
prepareForkThinking(agentName, idx, modelOverride);
|
|
5326
|
+
prepareForkThinking(agentName, idx, modelOverride, modelOverrideFromParent);
|
|
5164
5327
|
const override = forkThinkingOverrideForIndex(idx);
|
|
5165
5328
|
if (override === "off") forkThinkingDowngrades.set(idx, agentName);
|
|
5166
5329
|
return override ?? delegatedThinkingOverride;
|
|
5167
5330
|
};
|
|
5168
|
-
const childSessionFileForTask: ForkSessionFileForTask = (agentName, idx, modelOverride) =>
|
|
5169
|
-
forkSessionFileForTask(agentName, idx, modelOverride) ?? path.join(sessionDirForIndex(idx), "session.jsonl");
|
|
5331
|
+
const childSessionFileForTask: ForkSessionFileForTask = (agentName, idx, modelOverride, modelOverrideFromParent) =>
|
|
5332
|
+
forkSessionFileForTask(agentName, idx, modelOverride, modelOverrideFromParent) ?? path.join(sessionDirForIndex(idx), "session.jsonl");
|
|
5170
5333
|
const childSessionFileForIndex = (idx?: number) =>
|
|
5171
5334
|
path.join(sessionDirForIndex(idx), "session.jsonl");
|
|
5172
5335
|
try {
|
|
@@ -5522,7 +5685,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5522
5685
|
onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
|
|
5523
5686
|
ctx: ExtensionContext,
|
|
5524
5687
|
): Promise<AgentToolResult<Details>> => {
|
|
5525
|
-
const normalized = normalizePublicSubagentExecution(params);
|
|
5688
|
+
const normalized = normalizePublicSubagentExecution(params, { asyncByDefault: deps.asyncByDefault });
|
|
5526
5689
|
if (!normalized.ok) {
|
|
5527
5690
|
return Promise.resolve({ content: [{ type: "text", text: normalized.error }], isError: true, details: { mode: normalized.mode, results: [] } });
|
|
5528
5691
|
}
|