pi-subagents 0.50.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 +103 -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/async-retention-discovery-worker.mjs +180 -0
- package/docs/agents.md +40 -2
- package/docs/configuration.md +30 -12
- package/docs/extension-api.md +42 -1
- package/docs/models.md +2 -0
- package/docs/observability.md +41 -5
- package/docs/tool-reference.md +38 -39
- package/docs/workflows.md +171 -3
- package/package.json +4 -2
- package/skills/pi-subagents/SKILL.md +6 -4
- package/skills/pi-subagents/references/constraints-and-recipes.md +12 -6
- package/skills/pi-subagents/references/execution-controls.md +29 -15
- package/skills/pi-subagents/references/management-authoring-rpc.md +3 -3
- package/skills/pi-subagents/references/prompting-and-roles.md +4 -2
- package/src/agents/agent-management.ts +124 -351
- package/src/agents/agents.ts +157 -31
- package/src/agents/skills.ts +1 -1
- package/src/api/external-job-provider.ts +185 -0
- package/src/api/preflight.ts +36 -8
- package/src/api/shared-types.ts +2 -0
- package/src/extension/config.ts +3 -3
- package/src/extension/doctor.ts +3 -6
- package/src/extension/fanout-child.ts +2 -2
- package/src/extension/index.ts +166 -88
- package/src/extension/public-execution.ts +31 -2
- package/src/extension/schemas.ts +12 -35
- package/src/extension/tool-description.ts +35 -24
- package/src/inspectors/herdr/actions.ts +2 -2
- package/src/inspectors/herdr/inspector-runner.ts +2 -1
- package/src/inspectors/herdr/project-panes.ts +2 -2
- package/src/intercom/native-supervisor-channel.ts +32 -10
- package/src/missions/lifecycle.ts +6 -1
- package/src/missions/store.ts +4 -9
- package/src/missions/workflow-state.ts +2 -2
- package/src/profiles/profiles.ts +3 -1
- package/src/runs/background/active-run-index.ts +31 -8
- package/src/runs/background/async-execution.ts +68 -40
- package/src/runs/background/async-job-tracker.ts +20 -4
- package/src/runs/background/async-resume.ts +47 -15
- package/src/runs/background/async-retention.ts +886 -0
- package/src/runs/background/async-status.ts +39 -53
- package/src/runs/background/chain-append.ts +3 -33
- package/src/runs/background/completion-dedupe.ts +5 -1
- package/src/runs/background/completion-replay.ts +22 -12
- package/src/runs/background/control-channel.ts +14 -68
- package/src/runs/background/fleet-view.ts +68 -19
- package/src/runs/background/index-segment.ts +59 -0
- package/src/runs/background/inspect-rpc.ts +443 -0
- package/src/runs/background/notify.ts +31 -5
- package/src/runs/background/result-files.ts +158 -90
- package/src/runs/background/result-watcher.ts +116 -20
- package/src/runs/background/resume-guidance.ts +8 -5
- package/src/runs/background/retained-children.ts +13 -3
- package/src/runs/background/run-id-query.ts +7 -0
- package/src/runs/background/run-id-resolver.ts +11 -9
- package/src/runs/background/run-status.ts +27 -18
- package/src/runs/background/scheduled-runs.ts +24 -4
- package/src/runs/background/stale-run-reconciler.ts +6 -3
- package/src/runs/background/steering.ts +11 -1
- package/src/runs/background/subagent-runner.ts +253 -140
- package/src/runs/background/subagent-wait.ts +8 -8
- package/src/runs/background/terminal-run-index.ts +129 -0
- package/src/runs/background/wait-completions.ts +21 -4
- package/src/runs/background/wait-subscriptions.ts +81 -2
- package/src/runs/foreground/async-steering-action.ts +21 -14
- package/src/runs/foreground/execution.ts +3 -1
- package/src/runs/foreground/subagent-executor.ts +666 -1579
- package/src/runs/foreground/workflow-detach-reconcile.ts +194 -0
- package/src/runs/foreground/workflow-foreground-steering.ts +6 -5
- package/src/runs/shared/acceptance.ts +4 -4
- package/src/runs/shared/chain-outputs.ts +1 -3
- package/src/runs/shared/external-job-bridge.ts +444 -0
- package/src/runs/shared/external-job-runner.ts +286 -0
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +14 -0
- package/src/runs/shared/model-fallback.ts +98 -38
- package/src/runs/shared/orca-progress-tabs.ts +84 -22
- package/src/runs/shared/parallel-handoff.ts +46 -4
- package/src/runs/shared/parallel-utils.ts +4 -15
- package/src/runs/shared/permissions.ts +5 -1
- package/src/runs/shared/pi-args.ts +8 -1
- package/src/runs/shared/session-lease.ts +0 -6
- package/src/runs/shared/subagent-control.ts +26 -4
- package/src/runs/shared/subagent-prompt-runtime.ts +12 -6
- package/src/runs/shared/workflow-graph.ts +1 -23
- package/src/runs/shared/worktree.ts +14 -2
- package/src/shared/atomic-json.ts +22 -2
- package/src/shared/capacity-resilient-json.ts +102 -0
- package/src/shared/completion-owner.ts +14 -0
- package/src/shared/file-system-retry.ts +49 -1
- package/src/shared/fork-context.ts +42 -0
- package/src/shared/prompt-resources.ts +0 -40
- package/src/shared/settings.ts +3 -27
- package/src/shared/types.ts +71 -26
- package/src/shared/utils.ts +8 -0
- package/src/shared/watch-strategy.ts +10 -0
- package/src/slash/slash-bridge.ts +2 -1
- package/src/slash/slash-commands.ts +29 -3
- package/src/tui/fleet.ts +63 -15
- package/src/watchdog/change-signature.ts +1 -1
- package/src/watchdog/lsp-diagnostics.ts +1 -0
- package/src/workflows/chat-progress.ts +2 -2
- package/src/workflows/scripted-workflow.ts +220 -67
- package/src/runs/foreground/chain-clarify.ts +0 -1354
- package/src/runs/foreground/chain-execution.ts +0 -1581
|
@@ -3,12 +3,12 @@ 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 { resolveAgentName, type AgentConfig, type AgentScope } from "../../agents/agents.ts";
|
|
6
|
+
import { findBlockingAgentDiagnostic, resolveAgentName, type AgentConfig, type AgentDiscoveryDiagnostic, type AgentScope } 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
|
-
import {
|
|
9
|
+
import { createCapacityResilientJsonWriter } from "../../shared/capacity-resilient-json.ts";
|
|
10
|
+
import { isRetryableFileSystemError, isStorageCapacityError } from "../../shared/file-system-retry.ts";
|
|
10
11
|
import { resolveEffectiveThinking, toModelInfo, type ModelInfo } from "../../shared/model-info.ts";
|
|
11
|
-
import { executeChain } from "./chain-execution.ts";
|
|
12
12
|
import {
|
|
13
13
|
beginForegroundChild,
|
|
14
14
|
finishForegroundChild,
|
|
@@ -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";
|
|
@@ -55,13 +55,15 @@ import {
|
|
|
55
55
|
import { discoverAvailableSkills, normalizeSkillInput } from "../../agents/skills.ts";
|
|
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
|
+
import { steeringReceipt } from "../background/steering.ts";
|
|
58
59
|
import { acquireActiveAsyncCapacity, ActiveAsyncCapacityError, getActiveAsyncCapacitySnapshot, resolveMaxActiveAsyncRunsPerSession, transferActiveAsyncCapacity, type ActiveAsyncCapacityHandle } from "../background/active-async-capacity.ts";
|
|
59
60
|
import { isScheduledRunAction, type ScheduledRunAction } from "../background/scheduled-runs.ts";
|
|
60
61
|
import { enqueueChainAppendRequest, readPendingChainAppendRequests, runnerStepOutputNames } from "../background/chain-append.ts";
|
|
61
62
|
import { ChainOutputValidationError, validateChainOutputBindingsWithContext } from "../shared/chain-outputs.ts";
|
|
62
63
|
import { normalizeGateAcceptance, validateExecutionAcceptance } from "../shared/acceptance.ts";
|
|
63
|
-
import { createForkContextResolver, forkedChildRequiresThinkingOff } from "../../shared/fork-context.ts";
|
|
64
|
+
import { canPreferFork, createForkContextResolver, forkedChildRequiresThinkingOff, resolveSubagentLaunchContext } from "../../shared/fork-context.ts";
|
|
64
65
|
import { resolveCurrentSessionId } from "../../shared/session-identity.ts";
|
|
66
|
+
import { currentCompletionOwnerId } from "../../shared/completion-owner.ts";
|
|
65
67
|
import { applyIntercomBridgeToAgent, INTERCOM_BRIDGE_MARKER, resolveIntercomBridge, resolveIntercomSessionTarget, resolveSubagentIntercomTarget, type IntercomBridgeState } from "../../intercom/intercom-bridge.ts";
|
|
66
68
|
import { formatControlIntercomMessage, formatControlNoticeMessage, resolveControlConfig, shouldNotifyControlEvent } from "../shared/subagent-control.ts";
|
|
67
69
|
import { resolveTurnBudgetConfig } from "../shared/turn-budget.ts";
|
|
@@ -86,10 +88,10 @@ import {
|
|
|
86
88
|
resolveSubagentResultStatus,
|
|
87
89
|
stripDetailsOutputsForIntercomReceipt,
|
|
88
90
|
} from "../../intercom/result-intercom.ts";
|
|
89
|
-
import { applySteeringRecoveryAgentConfig, buildRevivedAsyncTask, resolveAsyncResumeTarget, resolveAsyncRunLocation } from "../background/async-resume.ts";
|
|
90
|
-
import {
|
|
91
|
+
import { applySteeringRecoveryAgentConfig, asyncReviveRequiresRecoveryDescriptor, buildRevivedAsyncTask, resolveAsyncResumeTarget, resolveAsyncRunLocation } from "../background/async-resume.ts";
|
|
92
|
+
import { deliverInterruptRequest, readRevivalBriefs, requestAsyncSteer, type SteerDeliveryMode } from "../background/control-channel.ts";
|
|
91
93
|
import { updateSteeringTarget, waitForSteeringAction } from "../background/steering.ts";
|
|
92
|
-
import { steerAsyncRun } from "./async-steering-action.ts";
|
|
94
|
+
import { canQueueRetainedAsyncFollowUp, steerAsyncRun } from "./async-steering-action.ts";
|
|
93
95
|
import {
|
|
94
96
|
removeWorkflowForegroundSteeringRoute,
|
|
95
97
|
resolveWorkflowForegroundSteeringTarget,
|
|
@@ -99,6 +101,7 @@ import {
|
|
|
99
101
|
} from "./workflow-foreground-steering.ts";
|
|
100
102
|
import { stopAsyncRun } from "./async-stop-action.ts";
|
|
101
103
|
import { dismissRecoveredWorkflow } from "./async-dismiss-action.ts";
|
|
104
|
+
import { promotePausedWorkflowIfSettled, reconcileDetachedWorkflowChildCompletion } from "./workflow-detach-reconcile.ts";
|
|
102
105
|
import { reconcileAsyncRun } from "../background/stale-run-reconciler.ts";
|
|
103
106
|
import { resolveAsyncRootResultPath, waitForImportedAsyncRoot } from "../background/chain-root-attachment.ts";
|
|
104
107
|
import { resultFilePath, writeAsyncResultFile } from "../background/result-files.ts";
|
|
@@ -115,8 +118,7 @@ import { createMissionWorkflowState } from "../../missions/workflow-state.ts";
|
|
|
115
118
|
import { resolveAuthorityDecision } from "../../policy/authority.ts";
|
|
116
119
|
import { handleHerdrInspectorAction, HERDR_INSPECTOR_ACTIONS } from "../../inspectors/herdr/actions.ts";
|
|
117
120
|
import { handleHerdrProjectPaneAction, HERDR_PROJECT_PANE_ACTIONS } from "../../inspectors/herdr/project-panes.ts";
|
|
118
|
-
import { previewSimpleWorkflowRun, runWorkflowScript, WorkflowScriptError, type WorkflowScriptChildResult } from "../../workflows/scripted-workflow.ts";
|
|
119
|
-
import { renderWorkflowPrompt } from "../../shared/prompt-resources.ts";
|
|
121
|
+
import { previewSimpleWorkflowRun, runWorkflowScript, WorkflowScriptError, type WorkflowScriptChildResult, type WorkflowSteerOptions, type WorkflowSteerResult } from "../../workflows/scripted-workflow.ts";
|
|
120
122
|
import { resolveWorkflowChatProgress, type WorkflowChatProgressProjection } from "../../workflows/chat-progress.ts";
|
|
121
123
|
import {
|
|
122
124
|
cleanupWorktrees,
|
|
@@ -170,10 +172,11 @@ import {
|
|
|
170
172
|
resolveCurrentMaxSubagentDepth,
|
|
171
173
|
resolveMaxSubagentSpawnsPerRun,
|
|
172
174
|
wrapForkTask,
|
|
175
|
+
type ScheduleOrigin,
|
|
173
176
|
} from "../../shared/types.ts";
|
|
174
177
|
|
|
175
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"]);
|
|
176
|
-
const DESTRUCTIVE_MANAGEMENT_ACTIONS = new Set(["delete", "eject", "disable", "reset", "mission.close", "worktree.discard", "refine.rollback", "inspector.close", "project.close", "stop", "interrupt", "
|
|
179
|
+
const DESTRUCTIVE_MANAGEMENT_ACTIONS = new Set(["delete", "eject", "disable", "reset", "mission.close", "worktree.discard", "refine.rollback", "inspector.close", "project.close", "stop", "interrupt", "schedule.delete"]);
|
|
177
180
|
|
|
178
181
|
function editDistance(left: string, right: string): number {
|
|
179
182
|
const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
@@ -286,11 +289,15 @@ export interface SubagentParamsLike {
|
|
|
286
289
|
mode?: SteerDeliveryMode;
|
|
287
290
|
workflowScript?: string;
|
|
288
291
|
chatProgress?: "auto" | "off" | "live-card";
|
|
292
|
+
isolation?: "none" | "worktree";
|
|
289
293
|
step?: ChainStep;
|
|
290
294
|
/** Internal workflow ownership metadata; not part of the public schema. */
|
|
291
295
|
workflowParentRunId?: string;
|
|
292
296
|
workflowKey?: string;
|
|
297
|
+
/** Set by the scheduler so this run's completion can name the schedule that produced it. */
|
|
298
|
+
scheduleOrigin?: ScheduleOrigin;
|
|
293
299
|
workflowChildAsyncId?: string;
|
|
300
|
+
workflowParentDeadlineAt?: number;
|
|
294
301
|
suppressRoutineResultIntercom?: boolean;
|
|
295
302
|
/** Internal inherited cumulative run-tree budget. */
|
|
296
303
|
runFanoutBudget?: RunFanoutBudgetDescriptor;
|
|
@@ -339,8 +346,6 @@ export interface SubagentParamsLike {
|
|
|
339
346
|
acceptance?: AcceptanceInput;
|
|
340
347
|
gate?: string;
|
|
341
348
|
agentContract?: AgentContract;
|
|
342
|
-
schedule?: string;
|
|
343
|
-
scheduleName?: string;
|
|
344
349
|
at?: string;
|
|
345
350
|
every?: string;
|
|
346
351
|
on?: string | number;
|
|
@@ -378,13 +383,15 @@ interface ExecutorDeps {
|
|
|
378
383
|
tempArtifactsDir: string;
|
|
379
384
|
getSubagentSessionRoot: (parentSessionFile: string | null) => string;
|
|
380
385
|
expandTilde: (p: string) => string;
|
|
381
|
-
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[]; modelScope?: ModelScopeConfig };
|
|
386
|
+
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[]; agentDiagnostics?: AgentDiscoveryDiagnostic[]; modelScope?: ModelScopeConfig };
|
|
382
387
|
allowMutatingManagementActions?: boolean;
|
|
388
|
+
activateSupervisorTransport?: () => void;
|
|
389
|
+
refreshResultDelivery?: () => void;
|
|
383
390
|
kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean;
|
|
384
391
|
}
|
|
385
392
|
|
|
386
|
-
type ForkSessionFileForTask = (agentName: string, idx?: number, modelOverride?: string) => string | undefined;
|
|
387
|
-
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;
|
|
388
395
|
|
|
389
396
|
interface ExecutionContextData {
|
|
390
397
|
params: SubagentParamsLike;
|
|
@@ -462,6 +469,7 @@ function promptAuditRedoParams(value: unknown, rewrittenTask: string): SubagentP
|
|
|
462
469
|
delete params.workflowParentRunId;
|
|
463
470
|
delete params.workflowKey;
|
|
464
471
|
delete params.workflowChildAsyncId;
|
|
472
|
+
delete params.workflowParentDeadlineAt;
|
|
465
473
|
delete params.suppressRoutineResultIntercom;
|
|
466
474
|
if (params.worktree === true && Array.isArray(params.tasks)) delete params.cwd;
|
|
467
475
|
return params;
|
|
@@ -553,7 +561,7 @@ function countRequestedSubagentSpawns(params: SubagentParamsLike, config: Extens
|
|
|
553
561
|
function staticRunFanoutPaths(params: SubagentParamsLike): string[] {
|
|
554
562
|
if (params.tasks) return params.tasks.map((_, index) => `tasks[${index}]`);
|
|
555
563
|
if (params.chain) return params.chain.flatMap((step, stepIndex) => {
|
|
556
|
-
if (isDynamicParallelStep(step)
|
|
564
|
+
if (isDynamicParallelStep(step)) return [];
|
|
557
565
|
if (isParallelStep(step)) return step.parallel.map((_, itemIndex) => `chain[${stepIndex}].parallel[${itemIndex}]`);
|
|
558
566
|
return [`chain[${stepIndex}]`];
|
|
559
567
|
});
|
|
@@ -627,7 +635,7 @@ function foregroundChildActivityFromProgress(progress: SingleResult["progress"]
|
|
|
627
635
|
};
|
|
628
636
|
}
|
|
629
637
|
|
|
630
|
-
function rememberForegroundRun(state: SubagentState, input: { runId: string; mode: "single" | "parallel" | "chain"; cwd: string; sessionId: string | null; results: SingleResult[]
|
|
638
|
+
function rememberForegroundRun(state: SubagentState, input: { runId: string; mode: "single" | "parallel" | "chain"; cwd: string; sessionId: string | null; results: SingleResult[] }): void {
|
|
631
639
|
state.foregroundRuns ??= new Map();
|
|
632
640
|
const previous = state.foregroundRuns.get(input.runId);
|
|
633
641
|
const updatedAt = Date.now();
|
|
@@ -637,7 +645,6 @@ function rememberForegroundRun(state: SubagentState, input: { runId: string; mod
|
|
|
637
645
|
cwd: input.cwd,
|
|
638
646
|
...(input.sessionId ? { sessionId: input.sessionId } : {}),
|
|
639
647
|
updatedAt,
|
|
640
|
-
...(input.checkpoint ? { checkpoint: input.checkpoint } : {}),
|
|
641
648
|
children: input.results.map((result, index) => {
|
|
642
649
|
const child = {
|
|
643
650
|
agent: result.agent,
|
|
@@ -950,11 +957,48 @@ function emitAdvisoryControlEvent(pi: ExtensionAPI, channel: string, payload: un
|
|
|
950
957
|
}
|
|
951
958
|
}
|
|
952
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
|
+
|
|
953
996
|
function emitControlNotification(input: {
|
|
954
997
|
pi: ExtensionAPI;
|
|
955
998
|
controlConfig: ResolvedControlConfig;
|
|
956
999
|
intercomBridge: IntercomBridgeState;
|
|
957
1000
|
event: ControlEvent;
|
|
1001
|
+
source?: "foreground" | "async";
|
|
958
1002
|
}): void {
|
|
959
1003
|
if (!shouldNotifyControlEvent(input.controlConfig, input.event)) return;
|
|
960
1004
|
const childIntercomTarget = input.intercomBridge.active
|
|
@@ -962,7 +1006,7 @@ function emitControlNotification(input: {
|
|
|
962
1006
|
: undefined;
|
|
963
1007
|
const payload = {
|
|
964
1008
|
event: input.event,
|
|
965
|
-
source: "foreground"
|
|
1009
|
+
source: input.source ?? "foreground",
|
|
966
1010
|
childIntercomTarget,
|
|
967
1011
|
noticeText: formatControlNoticeMessage(input.event, childIntercomTarget),
|
|
968
1012
|
};
|
|
@@ -995,9 +1039,9 @@ function interruptAsyncRun(
|
|
|
995
1039
|
};
|
|
996
1040
|
}
|
|
997
1041
|
const activeSteps = status.steps?.filter((step) => step.status === "running") ?? [];
|
|
998
|
-
if (activeSteps.length > 0 && activeSteps.every((step) => step.runner?.type === "external-cli")) {
|
|
1042
|
+
if (activeSteps.length > 0 && activeSteps.every((step) => step.runner?.type === "external-cli" || step.runner?.type === "external-job")) {
|
|
999
1043
|
return {
|
|
1000
|
-
content: [{ type: "text", text: `Interrupt is unsupported for
|
|
1044
|
+
content: [{ type: "text", text: `Interrupt is unsupported for external async run ${target.asyncId}; use stop instead.` }],
|
|
1001
1045
|
isError: true,
|
|
1002
1046
|
details: { mode: "management", results: [] },
|
|
1003
1047
|
};
|
|
@@ -1214,7 +1258,7 @@ function appendStepToAsyncChain(input: {
|
|
|
1214
1258
|
const startIndex = (status.chainStepCount ?? status.steps?.length ?? 0) + pendingAppendRequests.reduce((total, request) => total + request.steps.length, 0);
|
|
1215
1259
|
const appendPaths = chain.flatMap((step, localIndex) => {
|
|
1216
1260
|
const absoluteIndex = startIndex + localIndex;
|
|
1217
|
-
if (isDynamicParallelStep(step)
|
|
1261
|
+
if (isDynamicParallelStep(step)) return [];
|
|
1218
1262
|
if (isParallelStep(step)) return step.parallel.map((_, itemIndex) => `chain[${absoluteIndex}].parallel[${itemIndex}]`);
|
|
1219
1263
|
return [`chain[${absoluteIndex}]`];
|
|
1220
1264
|
});
|
|
@@ -1372,12 +1416,12 @@ async function directNestedAsyncSteer(input: { target: ResolvedSubagentRunId & {
|
|
|
1372
1416
|
const targets = targetIndexes.map((index) => ({ index, state: steps[index]?.status === "pending" ? "scheduled" as const : "pending" as const }));
|
|
1373
1417
|
if (targets.every((target) => target.state === "scheduled")) {
|
|
1374
1418
|
const scheduled = { requestId, state: "scheduled" as const, deliveryStatus: "queued" as const, sourceRunId: run.id, targets };
|
|
1375
|
-
return { content: [{ type: "text", text: `Steering scheduled for nested async run ${run.id} (request ${requestId}).` }], details: { mode: "management", results: [], steering: scheduled } };
|
|
1419
|
+
return { content: [{ type: "text", text: steeringReceipt(input.message, `Steering scheduled for nested async run ${run.id} (request ${requestId}).`) }], details: { mode: "management", results: [], steering: scheduled } };
|
|
1376
1420
|
}
|
|
1377
1421
|
const waited = await waitForSteeringAction(omitUndefinedProperties({ asyncDir, sourceRunId: run.id, requestId, timeoutMs: 3_000, signal: input.signal }));
|
|
1378
1422
|
const result = waited ?? { requestId, state: "pending" as const, deliveryStatus: "queued" as const, sourceRunId: run.id, targets };
|
|
1379
1423
|
const stateText = result.state === "failed" ? "failed" : result.state === "partial" ? "partial" : result.deliveryStatus === "queued" ? "queued" : result.state === "delivered" ? "delivered" : "pending";
|
|
1380
|
-
return { content: [{ type: "text", text: `Steering ${stateText} for nested async run ${run.id} (request ${requestId}).` }], ...(result.state === "failed" || result.state === "partial" ? { isError: true } : {}), details: { mode: "management", results: [], steering: result } };
|
|
1424
|
+
return { content: [{ type: "text", text: steeringReceipt(input.message, `Steering ${stateText} for nested async run ${run.id} (request ${requestId}).`) }], ...(result.state === "failed" || result.state === "partial" ? { isError: true } : {}), details: { mode: "management", results: [], steering: result } };
|
|
1381
1425
|
}
|
|
1382
1426
|
|
|
1383
1427
|
async function interruptNestedRun(target: ResolvedSubagentRunId & { kind: "nested" }): Promise<AgentToolResult<Details>> {
|
|
@@ -1409,10 +1453,10 @@ async function steerNestedRun(input: { target: ResolvedSubagentRunId & { kind: "
|
|
|
1409
1453
|
|
|
1410
1454
|
function externalRunnerControlError(asyncDir: string, action: "steer" | "resume"): AgentToolResult<Details> | undefined {
|
|
1411
1455
|
const status = readStatus(asyncDir);
|
|
1412
|
-
if (!status?.steps?.length || !status.steps.every((step) => step.runner?.type === "external-cli")) return undefined;
|
|
1456
|
+
if (!status?.steps?.length || !status.steps.every((step) => step.runner?.type === "external-cli" || step.runner?.type === "external-job")) return undefined;
|
|
1413
1457
|
const message = action === "steer"
|
|
1414
|
-
? "
|
|
1415
|
-
: "
|
|
1458
|
+
? "External runners do not accept live steer messages."
|
|
1459
|
+
: "External runners do not persist Pi sessions and cannot be resumed.";
|
|
1416
1460
|
return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
|
|
1417
1461
|
}
|
|
1418
1462
|
|
|
@@ -1565,7 +1609,7 @@ async function resumeAsyncRun(input: {
|
|
|
1565
1609
|
details: { mode: "chain", results: [] },
|
|
1566
1610
|
};
|
|
1567
1611
|
}
|
|
1568
|
-
const runId = randomUUID()
|
|
1612
|
+
const runId = randomUUID();
|
|
1569
1613
|
const topLevelResume = depth === 0 && !resolveInheritedNestedRouteFromEnv() && !input.params.workflowParentRunId;
|
|
1570
1614
|
let activeAsyncCapacity: ActiveAsyncCapacityHandle | undefined;
|
|
1571
1615
|
try {
|
|
@@ -1657,10 +1701,10 @@ async function resumeAsyncRun(input: {
|
|
|
1657
1701
|
if (!revivalSessionFile) {
|
|
1658
1702
|
return { content: [{ type: "text", text: `Async child '${target.runId}' has no persisted session file to resume.` }], isError: true, details: { mode: "management", results: [] } };
|
|
1659
1703
|
}
|
|
1660
|
-
if (target.source === "async" &&
|
|
1704
|
+
if (target.source === "async" && asyncReviveRequiresRecoveryDescriptor(target)) {
|
|
1661
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: [] } };
|
|
1662
1706
|
}
|
|
1663
|
-
const runId = randomUUID()
|
|
1707
|
+
const runId = randomUUID();
|
|
1664
1708
|
const topLevelResume = depth === 0 && !resolveInheritedNestedRouteFromEnv() && !input.params.workflowParentRunId;
|
|
1665
1709
|
let activeAsyncCapacity: ActiveAsyncCapacityHandle | undefined;
|
|
1666
1710
|
try {
|
|
@@ -1722,6 +1766,7 @@ async function resumeAsyncRun(input: {
|
|
|
1722
1766
|
...(input.deps.state.currentSessionId ? { parentSessionId: input.deps.state.currentSessionId } : {}),
|
|
1723
1767
|
},
|
|
1724
1768
|
modelOverride: recoveryDescriptor?.model ?? target.model,
|
|
1769
|
+
modelOverrideFromParent: recoveryDescriptor?.modelOverrideFromParent,
|
|
1725
1770
|
thinkingOverride: recoveryDescriptor?.thinking ?? target.thinking,
|
|
1726
1771
|
outputBaseDir: resolveSingleRunOutputBaseDir(input.deps, artifactsDir, runId),
|
|
1727
1772
|
maxSubagentDepth: recoveryDescriptor?.maxSubagentDepth ?? resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
|
|
@@ -1729,6 +1774,7 @@ async function resumeAsyncRun(input: {
|
|
|
1729
1774
|
worktreeSetupHook: input.deps.config.worktreeSetupHook,
|
|
1730
1775
|
worktreeSetupHookTimeoutMs: input.deps.config.worktreeSetupHookTimeoutMs,
|
|
1731
1776
|
worktreeBaseDir: input.deps.config.worktreeBaseDir,
|
|
1777
|
+
worktree: input.params.worktree === true,
|
|
1732
1778
|
controlConfig: recoveryDescriptor?.controlConfig ?? resolveControlConfig(input.deps.config.control, input.params.control),
|
|
1733
1779
|
intercomBridge: input.params.intercomBridge ?? recoveryDescriptor?.intercomBridge,
|
|
1734
1780
|
controlIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
|
|
@@ -1772,7 +1818,7 @@ async function resumeAsyncRun(input: {
|
|
|
1772
1818
|
const sourceStatus = readStatus(sourceAsyncDir);
|
|
1773
1819
|
if (sourceStatus?.steering) {
|
|
1774
1820
|
for (const brief of queuedBriefs) updateSteeringTarget(sourceStatus.steering, brief.request.id, target.index, "delivered", Date.now());
|
|
1775
|
-
|
|
1821
|
+
createCapacityResilientJsonWriter({ keepAlive: true }).write(path.join(sourceAsyncDir, "status.json"), sourceStatus);
|
|
1776
1822
|
}
|
|
1777
1823
|
}
|
|
1778
1824
|
|
|
@@ -1870,14 +1916,32 @@ function formatFailedSingleRunOutput(result: SingleResult, displayOutput: string
|
|
|
1870
1916
|
return lines.join("\n");
|
|
1871
1917
|
}
|
|
1872
1918
|
|
|
1873
|
-
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 {
|
|
1874
1920
|
return (event) => {
|
|
1875
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
|
+
}
|
|
1876
1939
|
emitControlNotification({
|
|
1877
1940
|
pi: deps.pi,
|
|
1878
1941
|
controlConfig: data.controlConfig,
|
|
1879
1942
|
intercomBridge: data.intercomBridge,
|
|
1880
|
-
event,
|
|
1943
|
+
event: enriched,
|
|
1944
|
+
source: asyncWorkflow ? "async" : "foreground",
|
|
1881
1945
|
});
|
|
1882
1946
|
};
|
|
1883
1947
|
}
|
|
@@ -1963,16 +2027,19 @@ async function maybeBuildForegroundIntercomReceipt(input: {
|
|
|
1963
2027
|
};
|
|
1964
2028
|
}
|
|
1965
2029
|
|
|
1966
|
-
function canonicalizeAgentName(name: string, agents: AgentConfig[]): { name?: string; error?: string } {
|
|
2030
|
+
function canonicalizeAgentName(name: string, agents: AgentConfig[], diagnostics?: AgentDiscoveryDiagnostic[]): { name?: string; error?: string } {
|
|
1967
2031
|
const resolved = resolveAgentName(name, agents);
|
|
2032
|
+
const candidates = resolved.error ? agents.filter((agent) => resolveAgentName(name, [agent]).agent) : resolved.agent;
|
|
2033
|
+
const diagnostic = findBlockingAgentDiagnostic(name, candidates, diagnostics);
|
|
2034
|
+
if (diagnostic) return { error: `Agent '${name}' has invalid configuration: ${diagnostic.error}` };
|
|
1968
2035
|
if (resolved.error) return { error: resolved.error };
|
|
1969
2036
|
if (!resolved.agent) return { error: `Unknown agent: ${name}` };
|
|
1970
2037
|
return { name: resolved.agent.name };
|
|
1971
2038
|
}
|
|
1972
2039
|
|
|
1973
|
-
function canonicalizeExecutionParams(params: SubagentParamsLike, agents: AgentConfig[]): { params?: SubagentParamsLike; error?: string } {
|
|
2040
|
+
function canonicalizeExecutionParams(params: SubagentParamsLike, agents: AgentConfig[], diagnostics?: AgentDiscoveryDiagnostic[]): { params?: SubagentParamsLike; error?: string } {
|
|
1974
2041
|
const resolve = (name: string, location?: string): { name?: string; error?: string } => {
|
|
1975
|
-
const result = canonicalizeAgentName(name, agents);
|
|
2042
|
+
const result = canonicalizeAgentName(name, agents, diagnostics);
|
|
1976
2043
|
return result.error && location ? { error: `${result.error} (${location})` } : result;
|
|
1977
2044
|
};
|
|
1978
2045
|
if (params.agent) {
|
|
@@ -2176,13 +2243,21 @@ interface AgentDefaultContextPolicy {
|
|
|
2176
2243
|
usesFork: boolean;
|
|
2177
2244
|
}
|
|
2178
2245
|
|
|
2179
|
-
function resolveAgentDefaultContextPolicy(
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2246
|
+
function resolveAgentDefaultContextPolicy(
|
|
2247
|
+
params: SubagentParamsLike,
|
|
2248
|
+
agents: AgentConfig[],
|
|
2249
|
+
defaultSubagentContext: ExtensionConfig["defaultSubagentContext"],
|
|
2250
|
+
canUseDefaultFork = false,
|
|
2251
|
+
): AgentDefaultContextPolicy {
|
|
2252
|
+
if (params.context !== undefined) return resolveExplicitContextPolicy(params);
|
|
2183
2253
|
const byName = new Map(agents.map((agent) => [agent.name, agent]));
|
|
2184
2254
|
const contextForAgent = (agentName: string): ContextMode =>
|
|
2185
|
-
|
|
2255
|
+
resolveSubagentLaunchContext({
|
|
2256
|
+
explicitContext: params.context,
|
|
2257
|
+
agentDefaultContext: byName.get(agentName)?.defaultContext,
|
|
2258
|
+
defaultSubagentContext,
|
|
2259
|
+
canUseImplicitFork: canUseDefaultFork,
|
|
2260
|
+
});
|
|
2186
2261
|
const requestedAgentNames = collectRequestedAgentNames(params);
|
|
2187
2262
|
const contextSummary = summarizeContextModes(requestedAgentNames.map((name) => contextForAgent(name)));
|
|
2188
2263
|
const usesFork = contextSummary === "fork" || contextSummary === "mixed";
|
|
@@ -2195,7 +2270,10 @@ function resolveAgentDefaultContextPolicy(params: SubagentParamsLike, agents: Ag
|
|
|
2195
2270
|
}
|
|
2196
2271
|
|
|
2197
2272
|
function resolveExplicitContextPolicy(params: SubagentParamsLike): AgentDefaultContextPolicy {
|
|
2198
|
-
const context =
|
|
2273
|
+
const context = resolveSubagentLaunchContext({
|
|
2274
|
+
explicitContext: params.context,
|
|
2275
|
+
canUseImplicitFork: false,
|
|
2276
|
+
});
|
|
2199
2277
|
return {
|
|
2200
2278
|
params,
|
|
2201
2279
|
contextForAgent: () => context,
|
|
@@ -2235,12 +2313,16 @@ function applySingleAgentLaunchDefaults(params: SubagentParamsLike, agents: Agen
|
|
|
2235
2313
|
if ((params.chain?.length ?? 0) > 0 || (params.tasks?.length ?? 0) > 0 || !params.agent) return params;
|
|
2236
2314
|
const agent = agents.find((candidate) => candidate.name === params.agent);
|
|
2237
2315
|
if (!agent) return params;
|
|
2316
|
+
const parentTimeoutMs = params.timeoutMs === undefined && params.maxRuntimeMs === undefined && agent.defaultTimeoutMs === undefined && params.workflowParentDeadlineAt !== undefined
|
|
2317
|
+
? Math.max(1, params.workflowParentDeadlineAt - Date.now())
|
|
2318
|
+
: undefined;
|
|
2238
2319
|
return {
|
|
2239
2320
|
...params,
|
|
2240
2321
|
...(params.async === undefined && agent.defaultAsync !== undefined ? { async: agent.defaultAsync } : {}),
|
|
2241
2322
|
...(params.timeoutMs === undefined && params.maxRuntimeMs === undefined && agent.defaultTimeoutMs !== undefined
|
|
2242
2323
|
? { timeoutMs: agent.defaultTimeoutMs }
|
|
2243
2324
|
: {}),
|
|
2325
|
+
...(parentTimeoutMs !== undefined ? { timeoutMs: parentTimeoutMs } : {}),
|
|
2244
2326
|
...(params.turnBudget === undefined && agent.defaultTurnBudget !== undefined
|
|
2245
2327
|
? { turnBudget: agent.defaultTurnBudget }
|
|
2246
2328
|
: {}),
|
|
@@ -2511,7 +2593,7 @@ function resolveStaticLaunchSummary(input: {
|
|
|
2511
2593
|
thinkingOverrideForTask: ForkThinkingOverrideForTask;
|
|
2512
2594
|
}): StaticLaunchSummary {
|
|
2513
2595
|
const agentConfig = input.agents.find((agent) => agent.name === input.agent);
|
|
2514
|
-
const externalRunner = agentConfig?.runner?.type === "external-cli";
|
|
2596
|
+
const externalRunner = agentConfig?.runner?.type === "external-cli" || agentConfig?.runner?.type === "external-job";
|
|
2515
2597
|
const model = externalRunner
|
|
2516
2598
|
? undefined
|
|
2517
2599
|
: resolveEffectiveSubagentModel(
|
|
@@ -2704,27 +2786,6 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2704
2786
|
const hasSingle = !hasChain && !hasTasks && Boolean(params.agent);
|
|
2705
2787
|
if (!effectiveAsync) return null;
|
|
2706
2788
|
|
|
2707
|
-
if (hasChain && params.chain) {
|
|
2708
|
-
const chainWorktreeTaskCwdError = buildChainWorktreeTaskCwdError(params.chain as ChainStep[], effectiveCwd);
|
|
2709
|
-
if (chainWorktreeTaskCwdError) {
|
|
2710
|
-
return {
|
|
2711
|
-
content: [{ type: "text", text: chainWorktreeTaskCwdError }],
|
|
2712
|
-
isError: true,
|
|
2713
|
-
details: { mode: "chain" as const, results: [] },
|
|
2714
|
-
};
|
|
2715
|
-
}
|
|
2716
|
-
}
|
|
2717
|
-
|
|
2718
|
-
if (hasTasks && params.tasks) {
|
|
2719
|
-
const maxParallelTasks = resolveTopLevelParallelMaxTasks(deps.config.parallel?.maxTasks);
|
|
2720
|
-
if (params.tasks.length > maxParallelTasks) {
|
|
2721
|
-
return buildParallelModeError(`Max ${maxParallelTasks} tasks`);
|
|
2722
|
-
}
|
|
2723
|
-
if (params.worktree) {
|
|
2724
|
-
const worktreeTaskCwdError = buildParallelWorktreeTaskCwdError(params.tasks, effectiveCwd);
|
|
2725
|
-
if (worktreeTaskCwdError) return buildParallelModeError(worktreeTaskCwdError);
|
|
2726
|
-
}
|
|
2727
|
-
}
|
|
2728
2789
|
|
|
2729
2790
|
if (!isAsyncAvailable()) {
|
|
2730
2791
|
return {
|
|
@@ -2752,118 +2813,6 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2752
2813
|
const controlIntercomTarget = intercomBridge.active ? intercomBridge.orchestratorTarget : undefined;
|
|
2753
2814
|
const childIntercomTarget = intercomBridge.active ? (agent: string, index: number) => resolveSubagentIntercomTarget(id, agent, index) : undefined;
|
|
2754
2815
|
|
|
2755
|
-
if (hasTasks && params.tasks) {
|
|
2756
|
-
const skillOverrides = params.tasks.map((task) => normalizeSkillInput(task.skill));
|
|
2757
|
-
const parallelTasks = params.tasks.map((task, index) => compactOptional<ParallelTaskItem>({
|
|
2758
|
-
agent: task.agent,
|
|
2759
|
-
task: shouldForkAgent(contextPolicy, task.agent) ? wrapForkTask(task.task) : task.task,
|
|
2760
|
-
cwd: task.cwd,
|
|
2761
|
-
...(task.model !== undefined ? { model: task.model } : {}),
|
|
2762
|
-
...(skillOverrides[index] !== undefined ? { skill: skillOverrides[index] } : {}),
|
|
2763
|
-
...(task.output !== undefined && task.output !== true ? { output: task.output } : {}),
|
|
2764
|
-
...(task.outputMode !== undefined ? { outputMode: task.outputMode } : {}),
|
|
2765
|
-
...(task.reads !== undefined && task.reads !== true ? { reads: task.reads } : {}),
|
|
2766
|
-
...(task.progress !== undefined ? { progress: task.progress } : {}),
|
|
2767
|
-
...(task.toolBudget !== undefined ? { toolBudget: task.toolBudget } : {}),
|
|
2768
|
-
...(task.outputSchema !== undefined ? { outputSchema: task.outputSchema } : {}),
|
|
2769
|
-
...(task.agentContract !== undefined ? { agentContract: task.agentContract } : {}),
|
|
2770
|
-
...(task.acceptance !== undefined ? { acceptance: task.acceptance } : {}),
|
|
2771
|
-
}));
|
|
2772
|
-
return executeAsyncChain(id, compactOptional<Parameters<typeof executeAsyncChain>[1]>({
|
|
2773
|
-
chain: [compactOptional<ParallelStep>({
|
|
2774
|
-
parallel: parallelTasks,
|
|
2775
|
-
concurrency: resolveTopLevelParallelConcurrency(params.concurrency, deps.config.parallel?.concurrency),
|
|
2776
|
-
worktree: params.worktree,
|
|
2777
|
-
})],
|
|
2778
|
-
resultMode: "parallel",
|
|
2779
|
-
goal: params.tasks[0]?.task ?? "",
|
|
2780
|
-
agents,
|
|
2781
|
-
ctx: asyncCtx,
|
|
2782
|
-
availableModels,
|
|
2783
|
-
cwd: effectiveCwd,
|
|
2784
|
-
maxOutput: params.maxOutput,
|
|
2785
|
-
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
2786
|
-
artifactConfig,
|
|
2787
|
-
shareEnabled,
|
|
2788
|
-
sessionRoot,
|
|
2789
|
-
chainSkills: [],
|
|
2790
|
-
activeAsyncCapacity: data.activeAsyncCapacity,
|
|
2791
|
-
sessionFilesByFlatIndex: params.tasks.map((task, index) => sessionFileForTask(task.agent, index, task.model)),
|
|
2792
|
-
thinkingOverridesByFlatIndex: params.tasks.map((task, index) => thinkingOverrideForTask(task.agent, index, task.model)),
|
|
2793
|
-
contextForAgent: contextPolicy.contextForAgent,
|
|
2794
|
-
maxSubagentDepth: currentMaxSubagentDepth,
|
|
2795
|
-
waitToolEnabled: deps.waitToolEnabled,
|
|
2796
|
-
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
2797
|
-
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
2798
|
-
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
2799
|
-
controlConfig,
|
|
2800
|
-
agentContract: params.agentContract,
|
|
2801
|
-
controlIntercomTarget,
|
|
2802
|
-
childIntercomTarget,
|
|
2803
|
-
nestedRoute,
|
|
2804
|
-
timeoutMs: data.timeoutMs,
|
|
2805
|
-
turnBudget: data.turnBudget,
|
|
2806
|
-
toolBudget: data.toolBudget,
|
|
2807
|
-
usageBudget: data.usageBudget,
|
|
2808
|
-
configToolBudget: data.configToolBudget,
|
|
2809
|
-
callToolTimeoutMs: data.params?.toolTimeoutMs,
|
|
2810
|
-
configToolTimeoutMs: data.configToolTimeoutMs,
|
|
2811
|
-
capabilityCeiling: data.capabilityCeiling,
|
|
2812
|
-
runFanoutBudget: data.runFanoutBudget,
|
|
2813
|
-
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
2814
|
-
parentWorkflowRunId: params.workflowParentRunId,
|
|
2815
|
-
workflowKey: params.workflowKey,
|
|
2816
|
-
}));
|
|
2817
|
-
}
|
|
2818
|
-
|
|
2819
|
-
if (hasChain && params.chain) {
|
|
2820
|
-
const normalized = normalizeSkillInput(params.skill);
|
|
2821
|
-
const chainSkills = normalized === false ? [] : (normalized ?? []);
|
|
2822
|
-
const rawChain = params.chain as ChainStep[];
|
|
2823
|
-
const chain = wrapChainTasksForFork(rawChain, contextPolicy);
|
|
2824
|
-
return executeAsyncChain(id, compactOptional<Parameters<typeof executeAsyncChain>[1]>({
|
|
2825
|
-
chain,
|
|
2826
|
-
task: params.task,
|
|
2827
|
-
goal: resolveAsyncEventGoal(params.task, rawChain),
|
|
2828
|
-
agents,
|
|
2829
|
-
ctx: asyncCtx,
|
|
2830
|
-
availableModels,
|
|
2831
|
-
cwd: effectiveCwd,
|
|
2832
|
-
maxOutput: params.maxOutput,
|
|
2833
|
-
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
2834
|
-
artifactConfig,
|
|
2835
|
-
shareEnabled,
|
|
2836
|
-
sessionRoot,
|
|
2837
|
-
chainSkills,
|
|
2838
|
-
activeAsyncCapacity: data.activeAsyncCapacity,
|
|
2839
|
-
sessionFilesByFlatIndex: collectChainSessionFiles(chain, sessionFileForTask, deps.config.chain?.dynamicFanout?.maxItems),
|
|
2840
|
-
thinkingOverridesByFlatIndex: collectChainThinkingOverrides(chain, thinkingOverrideForTask, deps.config.chain?.dynamicFanout?.maxItems),
|
|
2841
|
-
contextForAgent: contextPolicy.contextForAgent,
|
|
2842
|
-
dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
|
|
2843
|
-
maxSubagentDepth: currentMaxSubagentDepth,
|
|
2844
|
-
waitToolEnabled: deps.waitToolEnabled,
|
|
2845
|
-
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
2846
|
-
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
2847
|
-
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
2848
|
-
controlConfig,
|
|
2849
|
-
agentContract: params.agentContract,
|
|
2850
|
-
controlIntercomTarget,
|
|
2851
|
-
childIntercomTarget,
|
|
2852
|
-
nestedRoute,
|
|
2853
|
-
timeoutMs: data.timeoutMs,
|
|
2854
|
-
turnBudget: data.turnBudget,
|
|
2855
|
-
toolBudget: data.toolBudget,
|
|
2856
|
-
usageBudget: data.usageBudget,
|
|
2857
|
-
configToolBudget: data.configToolBudget,
|
|
2858
|
-
callToolTimeoutMs: data.params?.toolTimeoutMs,
|
|
2859
|
-
configToolTimeoutMs: data.configToolTimeoutMs,
|
|
2860
|
-
capabilityCeiling: data.capabilityCeiling,
|
|
2861
|
-
runFanoutBudget: data.runFanoutBudget,
|
|
2862
|
-
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
2863
|
-
parentWorkflowRunId: params.workflowParentRunId,
|
|
2864
|
-
workflowKey: params.workflowKey,
|
|
2865
|
-
}));
|
|
2866
|
-
}
|
|
2867
2816
|
|
|
2868
2817
|
if (hasSingle) {
|
|
2869
2818
|
const a = agents.find((x) => x.name === params.agent);
|
|
@@ -2880,12 +2829,13 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2880
2829
|
const normalizedSkills = normalizeSkillInput(params.skill);
|
|
2881
2830
|
const skills = normalizedSkills === false ? [] : normalizedSkills;
|
|
2882
2831
|
const maxSubagentDepth = resolveChildMaxSubagentDepth(currentMaxSubagentDepth, a.maxSubagentDepth);
|
|
2883
|
-
const externalRunnerWithoutExplicitModel = a.runner?.type === "external-cli"
|
|
2832
|
+
const externalRunnerWithoutExplicitModel = (a.runner?.type === "external-cli" || a.runner?.type === "external-job")
|
|
2884
2833
|
&& params.model === undefined
|
|
2885
2834
|
&& (a.model === undefined || (a.modelSource?.type === "subagents.defaultModel" && a.model === a.modelSource.model));
|
|
2886
|
-
const modelOverride = a.runner?.type === "external-cli"
|
|
2835
|
+
const modelOverride = a.runner?.type === "external-cli" || a.runner?.type === "external-job"
|
|
2887
2836
|
? params.model ?? (externalRunnerWithoutExplicitModel ? undefined : a.model)
|
|
2888
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);
|
|
2889
2839
|
return executeAsyncSingle(id, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
|
|
2890
2840
|
agent: params.agent!,
|
|
2891
2841
|
task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(params.task ?? "") : (params.task ?? ""),
|
|
@@ -2901,7 +2851,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2901
2851
|
shareEnabled,
|
|
2902
2852
|
activeAsyncCapacity: data.activeAsyncCapacity,
|
|
2903
2853
|
sessionRoot,
|
|
2904
|
-
sessionFile: sessionFileForTask(params.agent!, 0, modelOverride),
|
|
2854
|
+
sessionFile: sessionFileForTask(params.agent!, 0, modelOverride, modelOverrideFromParent),
|
|
2905
2855
|
context: contextPolicy.contextForAgent(params.agent!),
|
|
2906
2856
|
skills,
|
|
2907
2857
|
output: effectiveOutput,
|
|
@@ -2909,9 +2859,11 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2909
2859
|
...(params.reads !== undefined ? { reads: params.reads } : {}),
|
|
2910
2860
|
outputBaseDir: resolveSingleRunOutputBaseDir(deps, artifactsDir, id),
|
|
2911
2861
|
modelOverride,
|
|
2912
|
-
|
|
2862
|
+
modelOverrideFromParent,
|
|
2863
|
+
thinkingOverride: externalRunnerWithoutExplicitModel ? undefined : thinkingOverrideForTask(params.agent!, 0, modelOverride, modelOverrideFromParent),
|
|
2913
2864
|
maxSubagentDepth,
|
|
2914
2865
|
waitToolEnabled: deps.waitToolEnabled,
|
|
2866
|
+
...(params.worktree === true ? { worktree: true } : {}),
|
|
2915
2867
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
2916
2868
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
2917
2869
|
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
@@ -2940,308 +2892,11 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2940
2892
|
return null;
|
|
2941
2893
|
}
|
|
2942
2894
|
|
|
2943
|
-
|
|
2944
|
-
const {
|
|
2945
|
-
params,
|
|
2946
|
-
effectiveCwd,
|
|
2947
|
-
agents,
|
|
2948
|
-
ctx,
|
|
2949
|
-
signal,
|
|
2950
|
-
runId,
|
|
2951
|
-
shareEnabled,
|
|
2952
|
-
sessionDirForIndex,
|
|
2953
|
-
sessionFileForIndex,
|
|
2954
|
-
sessionFileForTask,
|
|
2955
|
-
thinkingOverrideForTask,
|
|
2956
|
-
artifactsDir,
|
|
2957
|
-
artifactConfig,
|
|
2958
|
-
onUpdate,
|
|
2959
|
-
sessionRoot,
|
|
2960
|
-
controlConfig,
|
|
2961
|
-
contextPolicy,
|
|
2962
|
-
} = data;
|
|
2963
|
-
const onControlEvent = createForegroundControlNotifier(data, deps);
|
|
2964
|
-
const childIntercomTarget = data.intercomBridge.active ? resolveSubagentIntercomTarget : undefined;
|
|
2965
|
-
const foregroundControl = deps.state.foregroundControls.get(runId);
|
|
2966
|
-
const normalized = normalizeSkillInput(params.skill);
|
|
2967
|
-
const chainSkills = normalized === false ? [] : (normalized ?? []);
|
|
2968
|
-
const chain = wrapChainTasksForFork(params.chain as ChainStep[], contextPolicy);
|
|
2969
|
-
const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
|
|
2970
|
-
const chainCtx = normalizeParentModel(ctx.model) || !data.parentModel ? ctx : ({ ...ctx, model: data.parentModel } as ExtensionContext);
|
|
2971
|
-
const chainResult = await executeChain(compactOptional<Parameters<typeof executeChain>[0]>({
|
|
2972
|
-
chain,
|
|
2973
|
-
task: params.task,
|
|
2974
|
-
agents,
|
|
2975
|
-
ctx: chainCtx,
|
|
2976
|
-
modelScope: data.modelScope,
|
|
2977
|
-
intercomEvents: deps.pi.events,
|
|
2978
|
-
signal,
|
|
2979
|
-
runId,
|
|
2980
|
-
cwd: effectiveCwd,
|
|
2981
|
-
shareEnabled,
|
|
2982
|
-
sessionDirForIndex,
|
|
2983
|
-
sessionFileForIndex,
|
|
2984
|
-
sessionFileForTask,
|
|
2985
|
-
thinkingOverrideForTask,
|
|
2986
|
-
contextForAgent: contextPolicy.contextForAgent,
|
|
2987
|
-
artifactsDir,
|
|
2988
|
-
artifactConfig,
|
|
2989
|
-
includeProgress: params.includeProgress,
|
|
2990
|
-
clarify: params.clarify,
|
|
2991
|
-
onUpdate,
|
|
2992
|
-
onControlEvent,
|
|
2993
|
-
controlConfig,
|
|
2994
|
-
agentContract: params.agentContract,
|
|
2995
|
-
childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(runId, agent, index) : undefined,
|
|
2996
|
-
orchestratorIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
2997
|
-
foregroundControl,
|
|
2998
|
-
nestedRoute: foregroundControl?.nestedRoute,
|
|
2999
|
-
chainSkills,
|
|
3000
|
-
chainDir: params.chainDir ?? getChainRunsDir(effectiveCwd, artifactConfig.dir),
|
|
3001
|
-
dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
|
|
3002
|
-
maxSubagentDepth: currentMaxSubagentDepth,
|
|
3003
|
-
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
3004
|
-
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
3005
|
-
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
3006
|
-
timeoutMs: data.timeoutMs,
|
|
3007
|
-
deadlineAt: data.deadlineAt,
|
|
3008
|
-
turnBudget: data.turnBudget,
|
|
3009
|
-
onDetachedExit: (index, result) => {
|
|
3010
|
-
try {
|
|
3011
|
-
updateRememberedForegroundChild(deps.state, { runId, mode: "chain", cwd: effectiveCwd, sessionId: data.parentSessionId, index, result, events: deps.pi.events });
|
|
3012
|
-
} finally {
|
|
3013
|
-
removeForegroundControlIfIdle(deps.state, runId);
|
|
3014
|
-
}
|
|
3015
|
-
},
|
|
3016
|
-
onForegroundChildSettled: () => {
|
|
3017
|
-
removeForegroundControlIfIdle(deps.state, runId);
|
|
3018
|
-
},
|
|
3019
|
-
toolBudget: data.toolBudget,
|
|
3020
|
-
usageBudget: data.usageBudget,
|
|
3021
|
-
configToolBudget: data.configToolBudget,
|
|
3022
|
-
callToolTimeoutMs: data.params?.toolTimeoutMs,
|
|
3023
|
-
configToolTimeoutMs: data.configToolTimeoutMs,
|
|
3024
|
-
permissions: deps.config.permissions,
|
|
3025
|
-
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
3026
|
-
capabilityCeiling: data.capabilityCeiling,
|
|
3027
|
-
runFanoutBudget: data.runFanoutBudget,
|
|
3028
|
-
}));
|
|
3029
|
-
|
|
3030
|
-
if (chainResult.requestedAsync) {
|
|
3031
|
-
if (!isAsyncAvailable()) {
|
|
3032
|
-
return {
|
|
3033
|
-
content: [{ type: "text", text: "Background mode requires upstream jiti for TypeScript execution but it could not be found. Ensure the pi-subagents package dependencies are installed." }],
|
|
3034
|
-
isError: true,
|
|
3035
|
-
details: { mode: "chain" as const, results: [] },
|
|
3036
|
-
};
|
|
3037
|
-
}
|
|
3038
|
-
const id = data.asyncRunId;
|
|
3039
|
-
const capacity = acquireClarifiedBackgroundCapacity(data, deps, "chain", id);
|
|
3040
|
-
if (capacity.errorResult) return capacity.errorResult;
|
|
3041
|
-
const parentModel = data.parentModel;
|
|
3042
|
-
const asyncCtx = compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
3043
|
-
pi: deps.pi,
|
|
3044
|
-
cwd: ctx.cwd,
|
|
3045
|
-
currentSessionId: data.parentSessionId!,
|
|
3046
|
-
parentSessionId: data.parentPiSessionId,
|
|
3047
|
-
currentModelProvider: parentModel?.provider,
|
|
3048
|
-
currentModel: parentModel,
|
|
3049
|
-
modelScope: data.modelScope,
|
|
3050
|
-
interactive: ctx.hasUI,
|
|
3051
|
-
permissions: deps.config.permissions,
|
|
3052
|
-
});
|
|
3053
|
-
const rawAsyncChain = chainResult.requestedAsync.chain;
|
|
3054
|
-
const asyncChain = wrapChainTasksForFork(rawAsyncChain, contextPolicy);
|
|
3055
|
-
const firstAgent = firstChainAgent(rawAsyncChain);
|
|
3056
|
-
try {
|
|
3057
|
-
return executeAsyncChain(id, compactOptional<Parameters<typeof executeAsyncChain>[1]>({
|
|
3058
|
-
chain: asyncChain,
|
|
3059
|
-
task: params.task,
|
|
3060
|
-
goal: resolveAsyncEventGoal(params.task, rawAsyncChain, firstAgent ? shouldForkAgent(contextPolicy, firstAgent) : false),
|
|
3061
|
-
agents,
|
|
3062
|
-
ctx: asyncCtx,
|
|
3063
|
-
availableModels: ctx.modelRegistry.getAvailable().map(toModelInfo),
|
|
3064
|
-
cwd: effectiveCwd,
|
|
3065
|
-
maxOutput: params.maxOutput,
|
|
3066
|
-
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
3067
|
-
artifactConfig,
|
|
3068
|
-
shareEnabled,
|
|
3069
|
-
sessionRoot,
|
|
3070
|
-
chainSkills: chainResult.requestedAsync.chainSkills,
|
|
3071
|
-
activeAsyncCapacity: capacity.activeAsyncCapacity,
|
|
3072
|
-
sessionFilesByFlatIndex: collectChainSessionFiles(asyncChain, sessionFileForTask, deps.config.chain?.dynamicFanout?.maxItems),
|
|
3073
|
-
thinkingOverridesByFlatIndex: collectChainThinkingOverrides(asyncChain, thinkingOverrideForTask, deps.config.chain?.dynamicFanout?.maxItems),
|
|
3074
|
-
contextForAgent: contextPolicy.contextForAgent,
|
|
3075
|
-
dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
|
|
3076
|
-
maxSubagentDepth: currentMaxSubagentDepth,
|
|
3077
|
-
waitToolEnabled: deps.waitToolEnabled,
|
|
3078
|
-
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
3079
|
-
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
3080
|
-
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
3081
|
-
controlConfig,
|
|
3082
|
-
agentContract: params.agentContract,
|
|
3083
|
-
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
3084
|
-
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
|
|
3085
|
-
nestedRoute: data.nestedRoute,
|
|
3086
|
-
timeoutMs: data.timeoutMs,
|
|
3087
|
-
turnBudget: data.turnBudget,
|
|
3088
|
-
toolBudget: data.toolBudget,
|
|
3089
|
-
usageBudget: data.usageBudget,
|
|
3090
|
-
configToolBudget: data.configToolBudget,
|
|
3091
|
-
callToolTimeoutMs: data.params?.toolTimeoutMs,
|
|
3092
|
-
configToolTimeoutMs: data.configToolTimeoutMs,
|
|
3093
|
-
capabilityCeiling: data.capabilityCeiling,
|
|
3094
|
-
runFanoutBudget: data.runFanoutBudget,
|
|
3095
|
-
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
3096
|
-
}));
|
|
3097
|
-
} finally {
|
|
3098
|
-
finalizeClarifiedBackgroundCapacity(data, deps, capacity.activeAsyncCapacity);
|
|
3099
|
-
}
|
|
3100
|
-
}
|
|
3101
|
-
|
|
3102
|
-
const rawChainDetails = chainResult.details ? compactOptional<Details>({ ...chainResult.details, runId, timeoutMs: data.timeoutMs }) : undefined;
|
|
3103
|
-
if (foregroundControl && rawChainDetails) {
|
|
3104
|
-
updateForegroundNestedProjection(foregroundControl);
|
|
3105
|
-
attachRootChildrenToSteps(runId, rawChainDetails.results, foregroundControl.nestedChildren);
|
|
3106
|
-
rawChainDetails.totalCost = sumResultsCost(rawChainDetails.results);
|
|
3107
|
-
const usageBudget = usageBudgetState(data.usageBudget, rawChainDetails.totalCost);
|
|
3108
|
-
if (usageBudget === undefined) delete rawChainDetails.usageBudget;
|
|
3109
|
-
else rawChainDetails.usageBudget = usageBudget;
|
|
3110
|
-
}
|
|
3111
|
-
const chainDetails = rawChainDetails ? compactForegroundDetails(rawChainDetails) : undefined;
|
|
3112
|
-
if (chainDetails) rememberForegroundRun(deps.state, { runId, mode: "chain", cwd: effectiveCwd, sessionId: data.parentSessionId, results: chainDetails.results, checkpoint: chainDetails.checkpoint });
|
|
3113
|
-
const intercomReceipt = chainDetails && !chainDetails.results.some((result) => result.interrupted || result.detached)
|
|
3114
|
-
? await maybeBuildForegroundIntercomReceipt({
|
|
3115
|
-
pi: deps.pi,
|
|
3116
|
-
intercomBridge: data.intercomBridge,
|
|
3117
|
-
runId,
|
|
3118
|
-
mode: "chain",
|
|
3119
|
-
details: chainDetails,
|
|
3120
|
-
...(foregroundControl?.nestedChildren?.length ? { nestedChildren: foregroundControl.nestedChildren } : {}),
|
|
3121
|
-
})
|
|
3122
|
-
: null;
|
|
3123
|
-
if (intercomReceipt) {
|
|
3124
|
-
return {
|
|
3125
|
-
...chainResult,
|
|
3126
|
-
content: [{ type: "text", text: intercomReceipt.text }],
|
|
3127
|
-
details: intercomReceipt.details,
|
|
3128
|
-
};
|
|
3129
|
-
}
|
|
3130
|
-
|
|
3131
|
-
return chainDetails ? { ...chainResult, details: chainDetails } : chainResult;
|
|
3132
|
-
}
|
|
3133
|
-
|
|
3134
|
-
interface ForegroundParallelRunInput {
|
|
3135
|
-
tasks: TaskParam[];
|
|
3136
|
-
taskTexts: string[];
|
|
3137
|
-
taskDescriptions: string[];
|
|
3138
|
-
agents: AgentConfig[];
|
|
3139
|
-
ctx: ExtensionContext;
|
|
3140
|
-
state: SubagentState;
|
|
3141
|
-
intercomEvents: IntercomEventBus;
|
|
3142
|
-
parentSessionId: string | null;
|
|
3143
|
-
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
3144
|
-
runFanoutBudget: RunFanoutBudgetDescriptor;
|
|
3145
|
-
signal: AbortSignal;
|
|
3146
|
-
runId: string;
|
|
3147
|
-
sessionDirForIndex: (idx?: number) => string | undefined;
|
|
3148
|
-
sessionFileForIndex: (idx?: number) => string | undefined;
|
|
3149
|
-
sessionFileForTask: ForkSessionFileForTask;
|
|
3150
|
-
thinkingOverrideForTask: ForkThinkingOverrideForTask;
|
|
3151
|
-
shareEnabled: boolean;
|
|
3152
|
-
artifactConfig: ArtifactConfig;
|
|
3153
|
-
artifactsDir: string;
|
|
3154
|
-
outputBaseDir: string;
|
|
3155
|
-
maxOutput?: MaxOutputConfig;
|
|
3156
|
-
paramsCwd: string;
|
|
3157
|
-
progressDir: string;
|
|
3158
|
-
maxSubagentDepths: number[];
|
|
3159
|
-
waitToolEnabled?: boolean;
|
|
3160
|
-
availableModels: ModelInfo[];
|
|
3161
|
-
modelScope?: ModelScopeConfig;
|
|
3162
|
-
parentModel?: ParentModel;
|
|
3163
|
-
modelOverrides: (string | undefined)[];
|
|
3164
|
-
behaviors: Array<ReturnType<typeof resolveStepBehavior>>;
|
|
3165
|
-
firstProgressIndex: number;
|
|
3166
|
-
controlConfig: ResolvedControlConfig;
|
|
3167
|
-
contextPolicy: AgentDefaultContextPolicy;
|
|
3168
|
-
onControlEvent?: (event: ControlEvent) => void;
|
|
3169
|
-
childIntercomTarget?: (agent: string, index: number) => string | undefined;
|
|
3170
|
-
orchestratorIntercomTarget?: string;
|
|
3171
|
-
foregroundControl?: SubagentState["foregroundControls"] extends Map<string, infer T> ? T : never;
|
|
3172
|
-
concurrencyLimit: number;
|
|
3173
|
-
globalSemaphore?: Semaphore;
|
|
3174
|
-
liveResults: (SingleResult | undefined)[];
|
|
3175
|
-
liveProgress: (AgentProgress | undefined)[];
|
|
3176
|
-
onUpdate?: (r: AgentToolResult<Details>) => void;
|
|
3177
|
-
worktreeSetup?: WorktreeSetup;
|
|
3178
|
-
timeoutMs?: number;
|
|
3179
|
-
deadlineAt?: number;
|
|
3180
|
-
/** Per-call per-tool timeout, resolved separately for each task's agent. */
|
|
3181
|
-
toolTimeoutMs?: number;
|
|
3182
|
-
/** Raw global config.toolTimeoutMs, used by each task's resolver. */
|
|
3183
|
-
configToolTimeoutMs?: number;
|
|
3184
|
-
turnBudget?: ResolvedTurnBudget;
|
|
3185
|
-
usageBudget?: UsageBudgetConfig;
|
|
3186
|
-
toolBudgets: (ResolvedToolBudget | undefined)[];
|
|
3187
|
-
agentContract?: AgentContract;
|
|
3188
|
-
permissions?: ExtensionConfig["permissions"];
|
|
3189
|
-
}
|
|
3190
|
-
function buildParallelModeError(message: string): AgentToolResult<Details> {
|
|
3191
|
-
return {
|
|
3192
|
-
content: [{ type: "text", text: message }],
|
|
3193
|
-
isError: true,
|
|
3194
|
-
details: { mode: "parallel" as const, results: [] },
|
|
3195
|
-
};
|
|
3196
|
-
}
|
|
3197
|
-
|
|
3198
|
-
function acquireClarifiedBackgroundCapacity(
|
|
3199
|
-
data: ExecutionContextData,
|
|
3200
|
-
deps: ExecutorDeps,
|
|
3201
|
-
mode: "single" | "parallel" | "chain",
|
|
3202
|
-
asyncRunId: string,
|
|
3203
|
-
): { activeAsyncCapacity?: ActiveAsyncCapacityHandle; errorResult?: AgentToolResult<Details> } {
|
|
3204
|
-
if (!data.topLevelAsyncCapacityEligible || !data.parentSessionId) return {};
|
|
3205
|
-
const liveWorkflowRunIds = new Set(deps.state.workflowControllers?.keys() ?? []);
|
|
3206
|
-
const activeLimit = resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession);
|
|
3207
|
-
try {
|
|
3208
|
-
const activeAsyncCapacity = acquireActiveAsyncCapacity({
|
|
3209
|
-
sessionId: data.parentSessionId,
|
|
3210
|
-
limit: activeLimit,
|
|
3211
|
-
runId: asyncRunId,
|
|
3212
|
-
kind: "runner",
|
|
3213
|
-
asyncDir: path.join(DIRS.async, asyncRunId),
|
|
3214
|
-
}, { liveWorkflowRunIds });
|
|
3215
|
-
deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(data.parentSessionId, activeLimit, { liveWorkflowRunIds });
|
|
3216
|
-
return { activeAsyncCapacity };
|
|
3217
|
-
} catch (error) {
|
|
3218
|
-
if (error instanceof ActiveAsyncCapacityError) {
|
|
3219
|
-
deps.state.activeAsyncCapacity = error.snapshot;
|
|
3220
|
-
return { errorResult: { content: [{ type: "text", text: error.message }], isError: true, details: { mode, results: [], activeAsyncCapacity: error.snapshot } } };
|
|
3221
|
-
}
|
|
3222
|
-
throw error;
|
|
3223
|
-
}
|
|
3224
|
-
}
|
|
3225
|
-
|
|
3226
|
-
function finalizeClarifiedBackgroundCapacity(
|
|
3227
|
-
data: ExecutionContextData,
|
|
3228
|
-
deps: ExecutorDeps,
|
|
3229
|
-
activeAsyncCapacity: ActiveAsyncCapacityHandle | undefined,
|
|
3230
|
-
): void {
|
|
3231
|
-
if (!activeAsyncCapacity || !data.parentSessionId) return;
|
|
3232
|
-
if (!activeAsyncCapacity.owner.runnerStartedAt) activeAsyncCapacity.rollback();
|
|
3233
|
-
deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(
|
|
3234
|
-
data.parentSessionId,
|
|
3235
|
-
resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession),
|
|
3236
|
-
{ liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) },
|
|
3237
|
-
);
|
|
3238
|
-
}
|
|
3239
|
-
|
|
3240
|
-
function createParallelWorktreeSetup(
|
|
2895
|
+
function createSingleWorktreeSetup(
|
|
3241
2896
|
enabled: boolean | undefined,
|
|
3242
2897
|
cwd: string,
|
|
3243
2898
|
runId: string,
|
|
3244
|
-
|
|
2899
|
+
agent: string,
|
|
3245
2900
|
setupHook: ExtensionConfig["worktreeSetupHook"],
|
|
3246
2901
|
setupHookTimeoutMs: ExtensionConfig["worktreeSetupHookTimeoutMs"],
|
|
3247
2902
|
baseDir: ExtensionConfig["worktreeBaseDir"],
|
|
@@ -3249,8 +2904,8 @@ function createParallelWorktreeSetup(
|
|
|
3249
2904
|
if (!enabled) return {};
|
|
3250
2905
|
try {
|
|
3251
2906
|
return {
|
|
3252
|
-
setup: createWorktrees(cwd, runId,
|
|
3253
|
-
agents:
|
|
2907
|
+
setup: createWorktrees(cwd, runId, 1, omitUndefinedProperties({
|
|
2908
|
+
agents: [agent],
|
|
3254
2909
|
setupHook: setupHook
|
|
3255
2910
|
? { hookPath: setupHook, ...(setupHookTimeoutMs === undefined ? {} : { timeoutMs: setupHookTimeoutMs }) }
|
|
3256
2911
|
: undefined,
|
|
@@ -3259,27 +2914,25 @@ function createParallelWorktreeSetup(
|
|
|
3259
2914
|
};
|
|
3260
2915
|
} catch (error) {
|
|
3261
2916
|
const message = error instanceof Error ? error.message : String(error);
|
|
3262
|
-
return { errorResult:
|
|
2917
|
+
return { errorResult: { content: [{ type: "text", text: message }], isError: true, details: { mode: "single", results: [] } } };
|
|
3263
2918
|
}
|
|
3264
2919
|
}
|
|
3265
2920
|
|
|
3266
|
-
function buildParallelWorktreeTaskCwdError(
|
|
3267
|
-
tasks: ReadonlyArray<{ agent: string; cwd?: string }>,
|
|
3268
|
-
sharedCwd: string,
|
|
3269
|
-
): string | undefined {
|
|
3270
|
-
const conflict = findWorktreeTaskCwdConflict(tasks, sharedCwd);
|
|
3271
|
-
if (!conflict) return undefined;
|
|
3272
|
-
return formatWorktreeTaskCwdConflict(conflict, sharedCwd);
|
|
3273
|
-
}
|
|
3274
|
-
|
|
3275
2921
|
function resolveConfiguredSingleRunOutputBaseDir(deps: ExecutorDeps): string | undefined {
|
|
3276
2922
|
return deps.config.singleRunOutputBaseDir
|
|
3277
2923
|
? path.resolve(deps.expandTilde(deps.config.singleRunOutputBaseDir))
|
|
3278
2924
|
: undefined;
|
|
3279
2925
|
}
|
|
3280
2926
|
|
|
2927
|
+
export function sanitizeRunPathSegment(value: string, maxBytes = 120): string {
|
|
2928
|
+
const sanitized = value.trim().replace(/[^A-Za-z0-9._-]+/g, "_").replace(/^_+|_+$/g, "");
|
|
2929
|
+
if (!sanitized) return "unknown";
|
|
2930
|
+
if (Buffer.byteLength(sanitized, "utf-8") <= maxBytes) return sanitized;
|
|
2931
|
+
return sanitized.slice(0, maxBytes).replace(/_+$/, "") || "unknown";
|
|
2932
|
+
}
|
|
2933
|
+
|
|
3281
2934
|
function resolveSingleRunOutputBaseDir(deps: ExecutorDeps, artifactsDir: string, runId: string): string {
|
|
3282
|
-
return resolveConfiguredSingleRunOutputBaseDir(deps) ?? path.join(artifactsDir, "outputs", runId);
|
|
2935
|
+
return resolveConfiguredSingleRunOutputBaseDir(deps) ?? path.join(artifactsDir, "outputs", sanitizeRunPathSegment(runId));
|
|
3283
2936
|
}
|
|
3284
2937
|
|
|
3285
2938
|
function resolveWorkflowAggregateOutputPath(
|
|
@@ -3298,826 +2951,190 @@ function workflowChildDefaultOutput(aggregateOutputPath: string | undefined, art
|
|
|
3298
2951
|
const parsed = path.parse(aggregateOutputPath);
|
|
3299
2952
|
return path.join(parsed.dir, `${parsed.name}.${workflowKey}${parsed.ext || ".md"}`);
|
|
3300
2953
|
}
|
|
3301
|
-
return path.join(artifactsDir, "outputs", workflowRunId, `${workflowKey}.md`);
|
|
2954
|
+
return path.join(artifactsDir, "outputs", sanitizeRunPathSegment(workflowRunId), `${workflowKey}.md`);
|
|
3302
2955
|
}
|
|
3303
2956
|
|
|
3304
|
-
function writeWorkflowAggregateOutput(outputPath: string | undefined, text: string): string | undefined {
|
|
2957
|
+
function writeWorkflowAggregateOutput(outputPath: string | undefined, text: string, producedChildOutputPaths: ReadonlySet<string>): string | undefined {
|
|
3305
2958
|
if (!outputPath) return undefined;
|
|
3306
2959
|
try {
|
|
2960
|
+
if (producedChildOutputPaths.has(outputPath)) return undefined;
|
|
3307
2961
|
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
3308
2962
|
fs.writeFileSync(outputPath, text, "utf-8");
|
|
3309
2963
|
return undefined;
|
|
3310
2964
|
} catch (error) {
|
|
3311
2965
|
return `Output file error: ${outputPath}\n${error instanceof Error ? error.message : String(error)}`;
|
|
3312
2966
|
}
|
|
3313
|
-
}
|
|
3314
|
-
|
|
3315
|
-
function appendWorkflowOutputWarning(text: string, warning: string | undefined): string {
|
|
3316
|
-
return warning ? `${text}\n\n${warning}` : text;
|
|
3317
|
-
}
|
|
3318
|
-
|
|
3319
|
-
function resolveWorkflowChildOutputPath(input: {
|
|
3320
|
-
ctxCwd: string;
|
|
3321
|
-
workflowCwd: string;
|
|
3322
|
-
artifactsDir: string;
|
|
3323
|
-
workflowRunId: string;
|
|
3324
|
-
aggregateOutputPath?: string;
|
|
3325
|
-
configuredOutputBaseDir?: string;
|
|
3326
|
-
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[] };
|
|
3327
|
-
workflowAgentScope?: unknown;
|
|
3328
|
-
key: string;
|
|
3329
|
-
params: Record<string, unknown>;
|
|
3330
|
-
}): string | undefined {
|
|
3331
|
-
const rawOutput = input.params.output;
|
|
3332
|
-
const hasExplicitOutput = typeof rawOutput === "string" || typeof rawOutput === "boolean";
|
|
3333
|
-
const childCwd = typeof input.params.cwd === "string" ? resolveChildCwd(input.workflowCwd, input.params.cwd) : input.workflowCwd;
|
|
3334
|
-
const agentScope = resolveExecutionAgentScope(input.params.agentScope ?? input.workflowAgentScope);
|
|
3335
|
-
const agents = input.discoverAgents(childCwd, agentScope).agents;
|
|
3336
|
-
const agent = typeof input.params.agent === "string" ? resolveAgentName(input.params.agent, agents).agent : undefined;
|
|
3337
|
-
const agentOutput = typeof agent?.output === "string" ? agent.output : undefined;
|
|
3338
|
-
const output = rawOutput === true || rawOutput === "true"
|
|
3339
|
-
? agentOutput
|
|
3340
|
-
: hasExplicitOutput
|
|
3341
|
-
? rawOutput
|
|
3342
|
-
: workflowChildDefaultOutput(input.aggregateOutputPath, input.artifactsDir, input.workflowRunId, input.key);
|
|
3343
|
-
return resolveSingleOutputPath(output, input.ctxCwd, childCwd, input.configuredOutputBaseDir);
|
|
3344
|
-
}
|
|
3345
|
-
|
|
3346
|
-
function workflowChildOutputClaims(input: {
|
|
3347
|
-
ctxCwd: string;
|
|
3348
|
-
workflowCwd: string;
|
|
3349
|
-
artifactsDir: string;
|
|
3350
|
-
workflowRunId: string;
|
|
3351
|
-
aggregateOutputPath?: string;
|
|
3352
|
-
configuredOutputBaseDir?: string;
|
|
3353
|
-
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[] };
|
|
3354
|
-
workflowAgentScope?: unknown;
|
|
3355
|
-
claimedOutputPaths: Map<string, string>;
|
|
3356
|
-
entries: Array<{ key: string; params: Record<string, unknown> }>;
|
|
3357
|
-
}): { error?: string; claims?: Map<string, string> } {
|
|
3358
|
-
const claims = new Map(input.claimedOutputPaths);
|
|
3359
|
-
for (const { key, params } of input.entries) {
|
|
3360
|
-
const resolved = resolveWorkflowChildOutputPath({ ...input, key, params });
|
|
3361
|
-
if (!resolved) continue;
|
|
3362
|
-
if (input.aggregateOutputPath && resolved === input.aggregateOutputPath) return { error: `Workflow child '${key}' output resolves to the workflow aggregate output path: ${resolved}. Use a distinct child output path.` };
|
|
3363
|
-
const previous = claims.get(resolved);
|
|
3364
|
-
if (previous) return { error: `Workflow children '${previous}' and '${key}' resolve output to the same path: ${resolved}. Use distinct child output paths.` };
|
|
3365
|
-
claims.set(resolved, key);
|
|
3366
|
-
}
|
|
3367
|
-
return { claims };
|
|
3368
|
-
}
|
|
3369
|
-
|
|
3370
|
-
function applyWorkflowChildOutputClaims(target: Map<string, string>, claims: Map<string, string>): void {
|
|
3371
|
-
target.clear();
|
|
3372
|
-
for (const [resolved, key] of claims) target.set(resolved, key);
|
|
3373
|
-
}
|
|
3374
|
-
|
|
3375
|
-
function prepareWorkflowChildLaunchParams(input: {
|
|
3376
|
-
workflowDefaults: SubagentParamsLike;
|
|
3377
|
-
childParams: Record<string, unknown>;
|
|
3378
|
-
parentWorkflowRunId: string;
|
|
3379
|
-
workflowKey: string;
|
|
3380
|
-
ctxCwd: string;
|
|
3381
|
-
workflowCwd: string;
|
|
3382
|
-
artifactsDir: string;
|
|
3383
|
-
aggregateOutputPath?: string;
|
|
3384
|
-
configuredOutputBaseDir?: string;
|
|
3385
|
-
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[] };
|
|
3386
|
-
workflowAgentScope?: unknown;
|
|
3387
|
-
options?: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean; runFanoutBudget?: RunFanoutBudgetDescriptor };
|
|
3388
|
-
}): SubagentParamsLike {
|
|
3389
|
-
let childParams = input.childParams;
|
|
3390
|
-
if (input.childParams.output === undefined && input.childParams.resume === undefined) {
|
|
3391
|
-
childParams = { ...input.childParams, output: workflowChildDefaultOutput(input.aggregateOutputPath, input.artifactsDir, input.parentWorkflowRunId, input.workflowKey) };
|
|
3392
|
-
} else if (input.childParams.resume === undefined) {
|
|
3393
|
-
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 });
|
|
3394
|
-
if (resolvedOutput) childParams = { ...input.childParams, output: resolvedOutput };
|
|
3395
|
-
}
|
|
3396
|
-
return prepareWorkflowLaunchParams(input.workflowDefaults, childParams, input.parentWorkflowRunId, input.workflowKey, input.options);
|
|
3397
|
-
}
|
|
3398
|
-
|
|
3399
|
-
function buildChainWorktreeTaskCwdError(chain: ChainStep[], sharedCwd: string): string | undefined {
|
|
3400
|
-
for (let stepIndex = 0; stepIndex < chain.length; stepIndex++) {
|
|
3401
|
-
const step = chain[stepIndex]!;
|
|
3402
|
-
if (!isParallelStep(step) || !step.worktree) continue;
|
|
3403
|
-
const stepCwd = resolveChildCwd(sharedCwd, step.cwd);
|
|
3404
|
-
const conflict = findWorktreeTaskCwdConflict(step.parallel, stepCwd);
|
|
3405
|
-
if (!conflict) continue;
|
|
3406
|
-
const detail = formatWorktreeTaskCwdConflict(conflict, stepCwd);
|
|
3407
|
-
return `parallel chain step ${stepIndex + 1}: ${detail}`;
|
|
3408
|
-
}
|
|
3409
|
-
return undefined;
|
|
3410
|
-
}
|
|
3411
|
-
|
|
3412
|
-
function resolveParallelTaskCwd(
|
|
3413
|
-
task: TaskParam,
|
|
3414
|
-
paramsCwd: string,
|
|
3415
|
-
worktreeSetup: WorktreeSetup | undefined,
|
|
3416
|
-
index: number,
|
|
3417
|
-
): string {
|
|
3418
|
-
if (worktreeSetup) return worktreeSetup.worktrees[index]!.agentCwd;
|
|
3419
|
-
return resolveChildCwd(paramsCwd, task.cwd);
|
|
3420
|
-
}
|
|
3421
|
-
|
|
3422
|
-
function finalizeParallelWorktreeHandoff(input: {
|
|
3423
|
-
worktreeSetup: WorktreeSetup;
|
|
3424
|
-
artifactsDir: string;
|
|
3425
|
-
runId: string;
|
|
3426
|
-
cwd: string;
|
|
3427
|
-
tasks: TaskParam[];
|
|
3428
|
-
results: SingleResult[];
|
|
3429
|
-
}): { suffix: string; reference?: NonNullable<Details["parallelHandoff"]> } {
|
|
3430
|
-
const diffsDir = path.join(input.artifactsDir, "worktree-diffs", input.runId);
|
|
3431
|
-
const diffs = diffWorktrees(input.worktreeSetup, input.tasks.map((task) => task.agent), diffsDir);
|
|
3432
|
-
const diffSummary = formatWorktreeDiffSummary(diffs);
|
|
3433
|
-
const manifestPath = parallelHandoffPath(input.artifactsDir, input.runId);
|
|
3434
|
-
const handoff = {
|
|
3435
|
-
manifestPath,
|
|
3436
|
-
runId: input.runId,
|
|
3437
|
-
mode: "parallel" as const,
|
|
3438
|
-
source: "foreground" as const,
|
|
3439
|
-
cwd: input.cwd,
|
|
3440
|
-
stepIndex: 0,
|
|
3441
|
-
flatStartIndex: 0,
|
|
3442
|
-
setup: input.worktreeSetup,
|
|
3443
|
-
diffs,
|
|
3444
|
-
results: input.results.map((result) => ({
|
|
3445
|
-
agent: result.agent,
|
|
3446
|
-
status: resolveSubagentResultStatus(omitUndefinedProperties({
|
|
3447
|
-
exitCode: result.exitCode,
|
|
3448
|
-
interrupted: result.interrupted,
|
|
3449
|
-
detached: result.detached,
|
|
3450
|
-
state: result.stopped ? "stopped" : undefined,
|
|
3451
|
-
processSignal: result.processSignal,
|
|
3452
|
-
timedOut: result.timedOut,
|
|
3453
|
-
stopped: result.stopped,
|
|
3454
|
-
turnBudgetExceeded: result.turnBudgetExceeded,
|
|
3455
|
-
})),
|
|
3456
|
-
summary: resultSummaryForIntercom(result),
|
|
3457
|
-
...(result.artifactPaths?.outputPath ? { outputPath: result.artifactPaths.outputPath } : {}),
|
|
3458
|
-
...(result.structuredOutput !== undefined ? { structuredOutput: result.structuredOutput } : {}),
|
|
3459
|
-
...(result.structuredOutputPath ? { structuredOutputPath: result.structuredOutputPath } : {}),
|
|
3460
|
-
...(result.sessionFile ? { sessionPath: result.sessionFile } : {}),
|
|
3461
|
-
})),
|
|
3462
|
-
};
|
|
3463
|
-
try {
|
|
3464
|
-
writeParallelHandoffGroup(handoff);
|
|
3465
|
-
const cleanup = cleanupWorktrees(input.worktreeSetup, { kind: "preserve", capturedDiffs: diffs, handoffManifestPath: manifestPath });
|
|
3466
|
-
const reference = writeParallelHandoffGroup({ ...handoff, cleanup });
|
|
3467
|
-
return {
|
|
3468
|
-
suffix: [diffSummary, formatParallelHandoffReference(reference)].filter(Boolean).join("\n\n"),
|
|
3469
|
-
reference,
|
|
3470
|
-
};
|
|
3471
|
-
} catch (error) {
|
|
3472
|
-
return { suffix: [diffSummary, formatParallelHandoffError(error)].filter(Boolean).join("\n\n") };
|
|
3473
|
-
}
|
|
3474
|
-
}
|
|
3475
|
-
|
|
3476
|
-
function findDuplicateParallelOutputPath(input: {
|
|
3477
|
-
tasks: TaskParam[];
|
|
3478
|
-
behaviors: ResolvedStepBehavior[];
|
|
3479
|
-
paramsCwd: string;
|
|
3480
|
-
ctxCwd: string;
|
|
3481
|
-
outputBaseDir: string;
|
|
3482
|
-
worktreeSetup?: WorktreeSetup;
|
|
3483
|
-
}): string | undefined {
|
|
3484
|
-
const seen = new Map<string, { index: number; agent: string }>();
|
|
3485
|
-
for (let index = 0; index < input.tasks.length; index++) {
|
|
3486
|
-
const behavior = input.behaviors[index];
|
|
3487
|
-
if (!behavior?.output) continue;
|
|
3488
|
-
const task = input.tasks[index]!;
|
|
3489
|
-
const taskCwd = resolveParallelTaskCwd(task, input.paramsCwd, input.worktreeSetup, index);
|
|
3490
|
-
const outputPath = resolveSingleOutputPath(behavior.output, input.ctxCwd, taskCwd, input.outputBaseDir);
|
|
3491
|
-
if (!outputPath) continue;
|
|
3492
|
-
const previous = seen.get(outputPath);
|
|
3493
|
-
if (previous) {
|
|
3494
|
-
return `Parallel tasks ${previous.index + 1} (${previous.agent}) and ${index + 1} (${task.agent}) resolve output to the same path: ${outputPath}. Use distinct output paths.`;
|
|
3495
|
-
}
|
|
3496
|
-
seen.set(outputPath, { index, agent: task.agent });
|
|
3497
|
-
}
|
|
3498
|
-
return undefined;
|
|
3499
|
-
}
|
|
3500
|
-
|
|
3501
|
-
async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Promise<SingleResult[]> {
|
|
3502
|
-
// Pre-warm fork session files sequentially before concurrent dispatch to avoid
|
|
3503
|
-
// races where multiple workers simultaneously try to branch the same parent session.
|
|
3504
|
-
for (let i = 0; i < input.tasks.length; i++) {
|
|
3505
|
-
input.sessionFileForTask(input.tasks[i]!.agent, i, input.modelOverrides[i]);
|
|
3506
|
-
}
|
|
3507
|
-
const completedResults: SingleResult[] = [];
|
|
3508
|
-
if (input.foregroundControl) retainForegroundSchedulingOwner(input.foregroundControl);
|
|
3509
|
-
return mapConcurrent(input.tasks, input.concurrencyLimit, async (task, index) => {
|
|
3510
|
-
const budgetState = usageBudgetState(input.usageBudget, sumResultsCost(completedResults));
|
|
3511
|
-
if (budgetState?.exhausted) {
|
|
3512
|
-
return {
|
|
3513
|
-
index,
|
|
3514
|
-
agent: task.agent,
|
|
3515
|
-
task: input.taskTexts[index] ?? "(skipped)",
|
|
3516
|
-
exitCode: 1,
|
|
3517
|
-
messages: [],
|
|
3518
|
-
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 },
|
|
3519
|
-
error: usageBudgetExceededMessage(budgetState),
|
|
3520
|
-
skipped: true,
|
|
3521
|
-
} as SingleResult;
|
|
3522
|
-
}
|
|
3523
|
-
const behavior = input.behaviors[index];
|
|
3524
|
-
const effectiveSkills = behavior?.skills;
|
|
3525
|
-
const taskCwd = resolveParallelTaskCwd(task, input.paramsCwd, input.worktreeSetup, index);
|
|
3526
|
-
const readInstructions = behavior
|
|
3527
|
-
? buildChainInstructions({ ...behavior, output: false, progress: false }, taskCwd, false)
|
|
3528
|
-
: { prefix: "", suffix: "" };
|
|
3529
|
-
const progressInstructions = behavior
|
|
3530
|
-
? buildChainInstructions({ ...behavior, output: false, reads: false }, input.progressDir, index === input.firstProgressIndex)
|
|
3531
|
-
: { prefix: "", suffix: "" };
|
|
3532
|
-
const outputPath = resolveSingleOutputPath(behavior?.output, input.ctx.cwd, taskCwd, input.outputBaseDir);
|
|
3533
|
-
const agentConfig = input.agents.find((agent) => agent.name === task.agent);
|
|
3534
|
-
const taskText = injectSingleOutputInstruction(
|
|
3535
|
-
`${readInstructions.prefix}${input.taskTexts[index]!}${progressInstructions.suffix}`,
|
|
3536
|
-
outputPath,
|
|
3537
|
-
agentConfig,
|
|
3538
|
-
);
|
|
3539
|
-
const interruptController = new AbortController();
|
|
3540
|
-
if (input.foregroundControl) {
|
|
3541
|
-
const model = input.modelOverrides[index];
|
|
3542
|
-
const thinking = resolveEffectiveThinking(model, input.thinkingOverrideForTask(task.agent, index, model));
|
|
3543
|
-
beginForegroundChild(input.foregroundControl, {
|
|
3544
|
-
index,
|
|
3545
|
-
agent: task.agent,
|
|
3546
|
-
authoredTask: input.taskDescriptions[index] ?? "",
|
|
3547
|
-
effectivePrompt: taskText,
|
|
3548
|
-
cwd: taskCwd,
|
|
3549
|
-
...(outputPath ? { outputPath } : {}),
|
|
3550
|
-
rerun: { params: compactOptional<Record<string, unknown>>({
|
|
3551
|
-
agent: task.agent,
|
|
3552
|
-
task: input.taskDescriptions[index] ?? "",
|
|
3553
|
-
async: false,
|
|
3554
|
-
cwd: input.worktreeSetup ? undefined : taskCwd,
|
|
3555
|
-
worktree: input.worktreeSetup ? true : false,
|
|
3556
|
-
context: input.contextPolicy.contextForAgent(task.agent),
|
|
3557
|
-
share: input.shareEnabled || undefined,
|
|
3558
|
-
maxOutput: input.maxOutput,
|
|
3559
|
-
timeoutMs: input.timeoutMs,
|
|
3560
|
-
toolTimeoutMs: input.toolTimeoutMs,
|
|
3561
|
-
configToolTimeoutMs: input.configToolTimeoutMs,
|
|
3562
|
-
turnBudget: input.turnBudget,
|
|
3563
|
-
...(model ? { model } : {}),
|
|
3564
|
-
...(effectiveSkills !== undefined ? { skill: effectiveSkills } : {}),
|
|
3565
|
-
...(outputPath ? { output: outputPath } : {}),
|
|
3566
|
-
...(behavior?.outputMode !== undefined ? { outputMode: behavior.outputMode } : {}),
|
|
3567
|
-
...(behavior?.reads !== undefined ? { reads: behavior.reads } : {}),
|
|
3568
|
-
...(behavior?.progress !== undefined ? { progress: behavior.progress } : {}),
|
|
3569
|
-
...(task.toolBudget !== undefined ? { toolBudget: task.toolBudget } : {}),
|
|
3570
|
-
...(task.outputSchema !== undefined ? { outputSchema: task.outputSchema } : {}),
|
|
3571
|
-
...(task.acceptance !== undefined ? { acceptance: task.acceptance } : {}),
|
|
3572
|
-
...(task.agentContract !== undefined || input.agentContract !== undefined ? { agentContract: task.agentContract ?? input.agentContract } : {}),
|
|
3573
|
-
}) },
|
|
3574
|
-
description: `${task.agent} child`,
|
|
3575
|
-
...(model ? { model } : {}),
|
|
3576
|
-
...(thinking ? { thinking } : {}),
|
|
3577
|
-
interrupt: () => {
|
|
3578
|
-
if (interruptController.signal.aborted) return false;
|
|
3579
|
-
interruptController.abort();
|
|
3580
|
-
return true;
|
|
3581
|
-
},
|
|
3582
|
-
});
|
|
3583
|
-
}
|
|
3584
|
-
const structuredRuntime = task.outputSchema
|
|
3585
|
-
? createStructuredOutputRuntime(task.outputSchema, path.join(input.artifactsDir, "structured-output", input.runId))
|
|
3586
|
-
: undefined;
|
|
3587
|
-
let detachedReceipt = false;
|
|
3588
|
-
const result = await runSync(input.ctx.cwd, input.agents, task.agent, taskText, compactOptional<Parameters<typeof runSync>[4]>({
|
|
3589
|
-
permissions: input.permissions,
|
|
3590
|
-
parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
|
|
3591
|
-
llmIntentArbiter: createTaskMutationArbiter(input.ctx),
|
|
3592
|
-
...workflowForegroundSteeringLaunchOptions(input.foregroundControl, index),
|
|
3593
|
-
context: input.contextPolicy.contextForAgent(task.agent),
|
|
3594
|
-
runFanoutBudget: { ...input.runFanoutBudget, parentPath: `${input.runFanoutBudget.parentPath ? `${input.runFanoutBudget.parentPath}/` : ""}tasks[${index}]` },
|
|
3595
|
-
cwd: taskCwd,
|
|
3596
|
-
signal: input.signal,
|
|
3597
|
-
interruptSignal: interruptController.signal,
|
|
3598
|
-
allowIntercomDetach: agentConfig?.systemPrompt?.includes(INTERCOM_BRIDGE_MARKER) === true,
|
|
3599
|
-
intercomEvents: input.intercomEvents,
|
|
3600
|
-
runId: input.runId,
|
|
3601
|
-
index,
|
|
3602
|
-
sessionDir: input.sessionDirForIndex(index),
|
|
3603
|
-
sessionFile: input.sessionFileForTask(task.agent, index, input.modelOverrides[index]),
|
|
3604
|
-
share: input.shareEnabled,
|
|
3605
|
-
artifactsDir: input.artifactConfig.enabled ? input.artifactsDir : undefined,
|
|
3606
|
-
artifactConfig: input.artifactConfig,
|
|
3607
|
-
maxOutput: input.maxOutput,
|
|
3608
|
-
outputPath,
|
|
3609
|
-
outputMode: behavior?.outputMode,
|
|
3610
|
-
maxSubagentDepth: input.maxSubagentDepths[index],
|
|
3611
|
-
waitToolEnabled: input.waitToolEnabled,
|
|
3612
|
-
capabilityCeiling: input.capabilityCeiling,
|
|
3613
|
-
controlConfig: input.controlConfig,
|
|
3614
|
-
onControlEvent: input.onControlEvent,
|
|
3615
|
-
onDetachedExit: (result) => {
|
|
3616
|
-
try {
|
|
3617
|
-
updateRememberedForegroundChild(input.state, { runId: input.runId, mode: "parallel", cwd: taskCwd, sessionId: input.parentSessionId, index, result, events: input.intercomEvents });
|
|
3618
|
-
} finally {
|
|
3619
|
-
try {
|
|
3620
|
-
if (input.foregroundControl) finishForegroundChild(input.foregroundControl, index);
|
|
3621
|
-
} finally {
|
|
3622
|
-
removeForegroundControlIfIdle(input.state, input.runId);
|
|
3623
|
-
}
|
|
3624
|
-
}
|
|
3625
|
-
},
|
|
3626
|
-
intercomSessionName: input.childIntercomTarget?.(task.agent, index),
|
|
3627
|
-
orchestratorIntercomTarget: input.orchestratorIntercomTarget,
|
|
3628
|
-
nestedRoute: input.foregroundControl?.nestedRoute,
|
|
3629
|
-
modelOverride: input.modelOverrides[index],
|
|
3630
|
-
thinkingOverride: input.thinkingOverrideForTask(task.agent, index, input.modelOverrides[index]),
|
|
3631
|
-
availableModels: input.availableModels,
|
|
3632
|
-
preferredModelProvider: input.parentModel?.provider,
|
|
3633
|
-
modelScope: input.modelScope,
|
|
3634
|
-
skills: effectiveSkills === false ? [] : effectiveSkills,
|
|
3635
|
-
structuredOutput: structuredRuntime,
|
|
3636
|
-
agentContract: task.agentContract ?? input.agentContract,
|
|
3637
|
-
acceptance: task.acceptance,
|
|
3638
|
-
acceptanceContext: { mode: "parallel" },
|
|
3639
|
-
onEffectivePrompt: input.foregroundControl ? (prompt) => updateLiveEffectivePrompt(input.foregroundControl!, index, prompt) : undefined,
|
|
3640
|
-
timeoutMs: input.timeoutMs,
|
|
3641
|
-
toolTimeoutMs: input.toolTimeoutMs,
|
|
3642
|
-
configToolTimeoutMs: input.configToolTimeoutMs,
|
|
3643
|
-
deadlineAt: input.deadlineAt,
|
|
3644
|
-
turnBudget: input.turnBudget,
|
|
3645
|
-
toolBudget: input.toolBudgets[index],
|
|
3646
|
-
onUpdate: input.onUpdate
|
|
3647
|
-
? (progressUpdate) => {
|
|
3648
|
-
const stepResults = progressUpdate.details?.results || [];
|
|
3649
|
-
const stepProgress = progressUpdate.details?.progress || [];
|
|
3650
|
-
if (input.foregroundControl && stepProgress.length > 0) {
|
|
3651
|
-
updateForegroundChild(input.foregroundControl, index, stepProgress[0]);
|
|
3652
|
-
}
|
|
3653
|
-
if (stepResults.length > 0) input.liveResults[index] = stepResults[0];
|
|
3654
|
-
if (stepProgress.length > 0) input.liveProgress[index] = stepProgress[0];
|
|
3655
|
-
const mergedResults = input.liveResults.filter((result): result is SingleResult => result !== undefined);
|
|
3656
|
-
const mergedProgress = input.liveProgress.filter((progress): progress is AgentProgress => progress !== undefined);
|
|
3657
|
-
input.onUpdate?.({
|
|
3658
|
-
content: progressUpdate.content,
|
|
3659
|
-
details: compactOptional<Details>({
|
|
3660
|
-
mode: "parallel",
|
|
3661
|
-
results: mergedResults,
|
|
3662
|
-
progress: mergedProgress,
|
|
3663
|
-
controlEvents: progressUpdate.details?.controlEvents,
|
|
3664
|
-
totalSteps: input.tasks.length,
|
|
3665
|
-
}),
|
|
3666
|
-
});
|
|
3667
|
-
}
|
|
3668
|
-
: undefined,
|
|
3669
|
-
})).then((result) => {
|
|
3670
|
-
detachedReceipt = result.detached === true;
|
|
3671
|
-
return result;
|
|
3672
|
-
}).finally(() => {
|
|
3673
|
-
// mapConcurrent rejects before siblings settle, so every attached child
|
|
3674
|
-
// attempts idle removal after releasing its own control. Detached receipts
|
|
3675
|
-
// transfer both responsibilities to the authoritative exit callback.
|
|
3676
|
-
if (!detachedReceipt) {
|
|
3677
|
-
if (input.foregroundControl) finishForegroundChild(input.foregroundControl, index);
|
|
3678
|
-
removeForegroundControlIfIdle(input.state, input.runId);
|
|
3679
|
-
}
|
|
3680
|
-
});
|
|
3681
|
-
completedResults.push(result);
|
|
3682
|
-
return result;
|
|
3683
|
-
}, input.globalSemaphore, () => {
|
|
3684
|
-
if (input.foregroundControl) settleForegroundSchedulingOwner(input.foregroundControl);
|
|
3685
|
-
removeForegroundControlIfIdle(input.state, input.runId);
|
|
3686
|
-
});
|
|
3687
|
-
}
|
|
3688
|
-
|
|
3689
|
-
async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps): Promise<AgentToolResult<Details>> {
|
|
3690
|
-
const {
|
|
3691
|
-
params,
|
|
3692
|
-
effectiveCwd,
|
|
3693
|
-
agents,
|
|
3694
|
-
ctx,
|
|
3695
|
-
signal,
|
|
3696
|
-
runId,
|
|
3697
|
-
sessionDirForIndex,
|
|
3698
|
-
sessionFileForIndex,
|
|
3699
|
-
sessionFileForTask,
|
|
3700
|
-
thinkingOverrideForTask,
|
|
3701
|
-
shareEnabled,
|
|
3702
|
-
artifactConfig,
|
|
3703
|
-
artifactsDir,
|
|
3704
|
-
backgroundRequestedWhileClarifying,
|
|
3705
|
-
onUpdate,
|
|
3706
|
-
sessionRoot,
|
|
3707
|
-
controlConfig,
|
|
3708
|
-
contextPolicy,
|
|
3709
|
-
} = data;
|
|
3710
|
-
const onControlEvent = createForegroundControlNotifier(data, deps);
|
|
3711
|
-
const childIntercomTarget = data.intercomBridge.active ? resolveSubagentIntercomTarget : undefined;
|
|
3712
|
-
const allProgress: AgentProgress[] = [];
|
|
3713
|
-
const allArtifactPaths: ArtifactPaths[] = [];
|
|
3714
|
-
const tasks = params.tasks!;
|
|
3715
|
-
const maxParallelTasks = resolveTopLevelParallelMaxTasks(deps.config.parallel?.maxTasks);
|
|
3716
|
-
const parallelConcurrency = resolveTopLevelParallelConcurrency(params.concurrency, deps.config.parallel?.concurrency);
|
|
3717
|
-
|
|
3718
|
-
if (tasks.length > maxParallelTasks)
|
|
3719
|
-
return {
|
|
3720
|
-
content: [{ type: "text", text: `Max ${maxParallelTasks} tasks` }],
|
|
3721
|
-
isError: true,
|
|
3722
|
-
details: { mode: "parallel" as const, results: [] },
|
|
3723
|
-
};
|
|
3724
|
-
|
|
3725
|
-
const agentConfigs: AgentConfig[] = [];
|
|
3726
|
-
for (const t of tasks) {
|
|
3727
|
-
const config = agents.find((a) => a.name === t.agent);
|
|
3728
|
-
if (!config) {
|
|
3729
|
-
return {
|
|
3730
|
-
content: [{ type: "text", text: `Unknown agent: ${t.agent}` }],
|
|
3731
|
-
isError: true,
|
|
3732
|
-
details: { mode: "parallel" as const, results: [] },
|
|
3733
|
-
};
|
|
3734
|
-
}
|
|
3735
|
-
agentConfigs.push(config);
|
|
3736
|
-
}
|
|
3737
|
-
|
|
3738
|
-
const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
|
|
3739
|
-
const maxSubagentDepths = agentConfigs.map((config) =>
|
|
3740
|
-
resolveChildMaxSubagentDepth(currentMaxSubagentDepth, config.maxSubagentDepth),
|
|
3741
|
-
);
|
|
3742
|
-
const toolBudgets: (ResolvedToolBudget | undefined)[] = [];
|
|
3743
|
-
for (let index = 0; index < tasks.length; index++) {
|
|
3744
|
-
const resolved = resolveEffectiveToolBudget(omitUndefinedProperties({ stepBudget: tasks[index]?.toolBudget, runBudget: data.toolBudget, agentBudget: agentConfigs[index]?.toolBudget, configBudget: data.configToolBudget }));
|
|
3745
|
-
if (resolved.error) return buildParallelModeError(resolved.error);
|
|
3746
|
-
toolBudgets.push(resolved.toolBudget);
|
|
3747
|
-
}
|
|
3748
|
-
|
|
3749
|
-
if (params.worktree) {
|
|
3750
|
-
const worktreeTaskCwdError = buildParallelWorktreeTaskCwdError(tasks, effectiveCwd);
|
|
3751
|
-
if (worktreeTaskCwdError) return buildParallelModeError(worktreeTaskCwdError);
|
|
3752
|
-
}
|
|
3753
|
-
|
|
3754
|
-
const parentModel = data.parentModel;
|
|
3755
|
-
const currentProvider = parentModel?.provider;
|
|
3756
|
-
const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
3757
|
-
let taskTexts = tasks.map((t) => t.task);
|
|
3758
|
-
const skillOverrides: (string[] | false | undefined)[] = tasks.map((t) =>
|
|
3759
|
-
normalizeSkillInput(t.skill),
|
|
3760
|
-
);
|
|
3761
|
-
const behaviorOverrides: StepOverrides[] = tasks.map((task, index) => ({
|
|
3762
|
-
...(task.output !== undefined && task.output !== true ? { output: task.output } : {}),
|
|
3763
|
-
...(task.outputMode !== undefined ? { outputMode: task.outputMode } : {}),
|
|
3764
|
-
...(task.reads !== undefined && task.reads !== true ? { reads: task.reads } : {}),
|
|
3765
|
-
...(task.progress !== undefined ? { progress: task.progress } : {}),
|
|
3766
|
-
...(skillOverrides[index] !== undefined ? { skills: skillOverrides[index] } : {}),
|
|
3767
|
-
...(task.model !== undefined ? { model: task.model } : {}),
|
|
3768
|
-
}));
|
|
3769
|
-
const modelOverrides: (string | undefined)[] = tasks.map((_, i) =>
|
|
3770
|
-
agentConfigs[i]?.runner?.type === "external-cli"
|
|
3771
|
-
? undefined
|
|
3772
|
-
: resolveEffectiveSubagentModel(behaviorOverrides[i]?.model, agentConfigs[i]?.model, parentModel, availableModels, currentProvider, data.modelScope === undefined ? {} : { scope: data.modelScope }),
|
|
3773
|
-
);
|
|
3774
|
-
|
|
3775
|
-
if (params.clarify === true && ctx.hasUI) {
|
|
3776
|
-
const behaviors = agentConfigs.map((c, i) =>
|
|
3777
|
-
resolveStepBehavior(c, behaviorOverrides[i]!),
|
|
3778
|
-
);
|
|
3779
|
-
const availableSkills = discoverAvailableSkills(effectiveCwd);
|
|
3780
|
-
|
|
3781
|
-
const result = await ctx.ui.custom<ChainClarifyResult>(
|
|
3782
|
-
(tui, theme, _kb, done) =>
|
|
3783
|
-
new ChainClarifyComponent(
|
|
3784
|
-
tui, theme,
|
|
3785
|
-
agentConfigs,
|
|
3786
|
-
taskTexts,
|
|
3787
|
-
"",
|
|
3788
|
-
undefined,
|
|
3789
|
-
behaviors,
|
|
3790
|
-
availableModels,
|
|
3791
|
-
currentProvider,
|
|
3792
|
-
availableSkills,
|
|
3793
|
-
done,
|
|
3794
|
-
"parallel",
|
|
3795
|
-
),
|
|
3796
|
-
{ overlay: true, overlayOptions: { anchor: "center", width: 84, maxHeight: "80%" } },
|
|
3797
|
-
);
|
|
3798
|
-
|
|
3799
|
-
if (!result || !result.confirmed) {
|
|
3800
|
-
return { content: [{ type: "text", text: "Cancelled" }], details: { mode: "parallel", results: [] } };
|
|
3801
|
-
}
|
|
3802
|
-
|
|
3803
|
-
taskTexts = result.templates;
|
|
3804
|
-
for (let i = 0; i < result.behaviorOverrides.length; i++) {
|
|
3805
|
-
const override = result.behaviorOverrides[i];
|
|
3806
|
-
if (override?.model !== undefined) {
|
|
3807
|
-
modelOverrides[i] = agentConfigs[i]?.runner?.type === "external-cli"
|
|
3808
|
-
? undefined
|
|
3809
|
-
: resolveEffectiveSubagentModel(override.model, agentConfigs[i]?.model, parentModel, availableModels, currentProvider, data.modelScope === undefined ? {} : { scope: data.modelScope });
|
|
3810
|
-
behaviorOverrides[i]!.model = override.model;
|
|
3811
|
-
}
|
|
3812
|
-
if (override?.output !== undefined) behaviorOverrides[i]!.output = override.output;
|
|
3813
|
-
if (override?.reads !== undefined) behaviorOverrides[i]!.reads = override.reads;
|
|
3814
|
-
if (override?.progress !== undefined) behaviorOverrides[i]!.progress = override.progress;
|
|
3815
|
-
if (override?.skills !== undefined) {
|
|
3816
|
-
skillOverrides[i] = override.skills;
|
|
3817
|
-
behaviorOverrides[i]!.skills = override.skills;
|
|
3818
|
-
}
|
|
3819
|
-
}
|
|
3820
|
-
|
|
3821
|
-
if (result.runInBackground) {
|
|
3822
|
-
if (!isAsyncAvailable()) {
|
|
3823
|
-
return {
|
|
3824
|
-
content: [{ type: "text", text: "Background mode requires upstream jiti for TypeScript execution but it could not be found. Ensure the pi-subagents package dependencies are installed." }],
|
|
3825
|
-
isError: true,
|
|
3826
|
-
details: { mode: "parallel" as const, results: [] },
|
|
3827
|
-
};
|
|
3828
|
-
}
|
|
3829
|
-
const id = data.asyncRunId;
|
|
3830
|
-
const capacity = acquireClarifiedBackgroundCapacity(data, deps, "parallel", id);
|
|
3831
|
-
if (capacity.errorResult) return capacity.errorResult;
|
|
3832
|
-
const asyncCtx = compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
3833
|
-
pi: deps.pi,
|
|
3834
|
-
cwd: ctx.cwd,
|
|
3835
|
-
currentSessionId: data.parentSessionId!,
|
|
3836
|
-
parentSessionId: data.parentPiSessionId,
|
|
3837
|
-
currentModelProvider: parentModel?.provider,
|
|
3838
|
-
currentModel: parentModel,
|
|
3839
|
-
modelScope: data.modelScope,
|
|
3840
|
-
interactive: ctx.hasUI,
|
|
3841
|
-
permissions: deps.config.permissions,
|
|
3842
|
-
});
|
|
3843
|
-
const parallelTasks = tasks.map((t, i) => {
|
|
3844
|
-
const taskText = shouldForkAgent(contextPolicy, t.agent) ? wrapForkTask(taskTexts[i]!) : taskTexts[i]!;
|
|
3845
|
-
const progress = taskDisallowsFileUpdates(taskText) ? false : behaviorOverrides[i]?.progress;
|
|
3846
|
-
return compactOptional<ParallelTaskItem>({
|
|
3847
|
-
agent: t.agent,
|
|
3848
|
-
task: taskText,
|
|
3849
|
-
cwd: t.cwd,
|
|
3850
|
-
...(behaviorOverrides[i]?.model !== undefined ? { model: behaviorOverrides[i]!.model } : {}),
|
|
3851
|
-
...(skillOverrides[i] !== undefined ? { skill: skillOverrides[i] } : {}),
|
|
3852
|
-
...(behaviorOverrides[i]?.output !== undefined ? { output: behaviorOverrides[i]!.output } : {}),
|
|
3853
|
-
...(behaviorOverrides[i]?.outputMode !== undefined ? { outputMode: behaviorOverrides[i]!.outputMode } : {}),
|
|
3854
|
-
...(behaviorOverrides[i]?.reads !== undefined ? { reads: behaviorOverrides[i]!.reads } : {}),
|
|
3855
|
-
...(progress !== undefined ? { progress } : {}),
|
|
3856
|
-
...(t.toolBudget !== undefined ? { toolBudget: t.toolBudget } : {}),
|
|
3857
|
-
...(t.outputSchema !== undefined ? { outputSchema: t.outputSchema } : {}),
|
|
3858
|
-
...(t.acceptance !== undefined ? { acceptance: t.acceptance } : {}),
|
|
3859
|
-
...(t.agentContract !== undefined ? { agentContract: t.agentContract } : {}),
|
|
3860
|
-
});
|
|
3861
|
-
});
|
|
3862
|
-
try {
|
|
3863
|
-
return executeAsyncChain(id, compactOptional<Parameters<typeof executeAsyncChain>[1]>({
|
|
3864
|
-
chain: [compactOptional<ParallelStep>({ parallel: parallelTasks, concurrency: parallelConcurrency, worktree: params.worktree })],
|
|
3865
|
-
resultMode: "parallel",
|
|
3866
|
-
goal: taskTexts[0] ?? "",
|
|
3867
|
-
agents,
|
|
3868
|
-
ctx: asyncCtx,
|
|
3869
|
-
availableModels,
|
|
3870
|
-
cwd: effectiveCwd,
|
|
3871
|
-
maxOutput: params.maxOutput,
|
|
3872
|
-
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
3873
|
-
artifactConfig,
|
|
3874
|
-
shareEnabled,
|
|
3875
|
-
sessionRoot,
|
|
3876
|
-
chainSkills: [],
|
|
3877
|
-
activeAsyncCapacity: capacity.activeAsyncCapacity,
|
|
3878
|
-
sessionFilesByFlatIndex: tasks.map((task, index) => sessionFileForTask(task.agent, index, modelOverrides[index])),
|
|
3879
|
-
thinkingOverridesByFlatIndex: tasks.map((task, index) => thinkingOverrideForTask(task.agent, index, modelOverrides[index])),
|
|
3880
|
-
contextForAgent: contextPolicy.contextForAgent,
|
|
3881
|
-
maxSubagentDepth: currentMaxSubagentDepth,
|
|
3882
|
-
waitToolEnabled: deps.waitToolEnabled,
|
|
3883
|
-
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
3884
|
-
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
3885
|
-
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
3886
|
-
controlConfig,
|
|
3887
|
-
agentContract: params.agentContract,
|
|
3888
|
-
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
3889
|
-
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
|
|
3890
|
-
nestedRoute: data.nestedRoute,
|
|
3891
|
-
timeoutMs: data.timeoutMs,
|
|
3892
|
-
turnBudget: data.turnBudget,
|
|
3893
|
-
usageBudget: data.usageBudget,
|
|
3894
|
-
toolBudget: data.toolBudget,
|
|
3895
|
-
configToolBudget: data.configToolBudget,
|
|
3896
|
-
callToolTimeoutMs: data.params?.toolTimeoutMs,
|
|
3897
|
-
configToolTimeoutMs: data.configToolTimeoutMs,
|
|
3898
|
-
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
3899
|
-
runFanoutBudget: data.runFanoutBudget,
|
|
3900
|
-
}));
|
|
3901
|
-
} finally {
|
|
3902
|
-
finalizeClarifiedBackgroundCapacity(data, deps, capacity.activeAsyncCapacity);
|
|
3903
|
-
}
|
|
3904
|
-
}
|
|
3905
|
-
}
|
|
3906
|
-
|
|
3907
|
-
const behaviors = tasks.map((task, index) => {
|
|
3908
|
-
let behavior = suppressProgressForReadOnlyTask(resolveStepBehavior(agentConfigs[index]!, behaviorOverrides[index]!), taskTexts[index]);
|
|
3909
|
-
if (behaviorOverrides[index]?.output === undefined && typeof behavior.output === "string" && !path.isAbsolute(behavior.output)) {
|
|
3910
|
-
behavior = { ...behavior, output: path.join("parallel-0", `${index}-${task.agent}`, behavior.output) };
|
|
3911
|
-
}
|
|
3912
|
-
return behavior;
|
|
3913
|
-
});
|
|
3914
|
-
const firstProgressIndex = behaviors.findIndex((behavior) => behavior.progress);
|
|
3915
|
-
const liveResults: (SingleResult | undefined)[] = new Array(tasks.length).fill(undefined);
|
|
3916
|
-
const liveProgress: (AgentProgress | undefined)[] = new Array(tasks.length).fill(undefined);
|
|
3917
|
-
const foregroundControl = deps.state.foregroundControls.get(runId);
|
|
3918
|
-
const { setup: worktreeSetup, errorResult } = createParallelWorktreeSetup(
|
|
3919
|
-
params.worktree,
|
|
3920
|
-
effectiveCwd,
|
|
3921
|
-
runId,
|
|
3922
|
-
tasks,
|
|
3923
|
-
deps.config.worktreeSetupHook,
|
|
3924
|
-
deps.config.worktreeSetupHookTimeoutMs,
|
|
3925
|
-
deps.config.worktreeBaseDir,
|
|
3926
|
-
);
|
|
3927
|
-
if (errorResult) return errorResult;
|
|
3928
|
-
|
|
3929
|
-
let worktreeCleanupHandled = false;
|
|
3930
|
-
let pendingHandoff: Details["parallelHandoff"];
|
|
3931
|
-
try {
|
|
3932
|
-
if (worktreeSetup) {
|
|
3933
|
-
pendingHandoff = writePendingParallelHandoff({
|
|
3934
|
-
manifestPath: parallelHandoffPath(artifactsDir, runId),
|
|
3935
|
-
runId,
|
|
3936
|
-
mode: "parallel",
|
|
3937
|
-
source: "foreground",
|
|
3938
|
-
cwd: effectiveCwd,
|
|
3939
|
-
stepIndex: 0,
|
|
3940
|
-
flatStartIndex: 0,
|
|
3941
|
-
setup: worktreeSetup,
|
|
3942
|
-
});
|
|
3943
|
-
}
|
|
3944
|
-
const outputBaseDir = path.join(artifactsDir, "outputs", runId);
|
|
3945
|
-
const duplicateOutputError = findDuplicateParallelOutputPath(omitUndefinedProperties({
|
|
3946
|
-
tasks,
|
|
3947
|
-
behaviors,
|
|
3948
|
-
paramsCwd: effectiveCwd,
|
|
3949
|
-
ctxCwd: ctx.cwd,
|
|
3950
|
-
outputBaseDir,
|
|
3951
|
-
worktreeSetup,
|
|
3952
|
-
}));
|
|
3953
|
-
if (duplicateOutputError) return buildParallelModeError(duplicateOutputError);
|
|
3954
|
-
for (let index = 0; index < tasks.length; index++) {
|
|
3955
|
-
const taskCwd = resolveParallelTaskCwd(tasks[index]!, effectiveCwd, worktreeSetup, index);
|
|
3956
|
-
const outputPath = resolveSingleOutputPath(behaviors[index]?.output, ctx.cwd, taskCwd, outputBaseDir);
|
|
3957
|
-
const validationError = validateFileOnlyOutputMode(behaviors[index]?.outputMode, outputPath, `Parallel task ${index + 1} (${tasks[index]!.agent})`);
|
|
3958
|
-
if (validationError) return buildParallelModeError(validationError);
|
|
3959
|
-
}
|
|
3960
|
-
|
|
3961
|
-
const parallelProgressPrecreated = firstProgressIndex !== -1;
|
|
3962
|
-
const parallelProgressDir = path.join(artifactsDir, "progress", runId);
|
|
3963
|
-
if (parallelProgressPrecreated) writeInitialProgressFile(parallelProgressDir);
|
|
3964
|
-
|
|
3965
|
-
const taskDescriptions = taskTexts.map((taskText) => taskText.trim());
|
|
3966
|
-
for (let i = 0; i < taskTexts.length; i++) {
|
|
3967
|
-
if (shouldForkAgent(contextPolicy, tasks[i]!.agent)) taskTexts[i] = wrapForkTask(taskTexts[i]!);
|
|
3968
|
-
}
|
|
3969
|
-
|
|
3970
|
-
const deadlineAt = data.deadlineAt ?? (data.timeoutMs !== undefined ? Date.now() + data.timeoutMs : undefined);
|
|
3971
|
-
const results = await runForegroundParallelTasks(compactOptional<ForegroundParallelRunInput>({
|
|
3972
|
-
tasks,
|
|
3973
|
-
permissions: deps.config.permissions,
|
|
3974
|
-
taskTexts,
|
|
3975
|
-
taskDescriptions,
|
|
3976
|
-
agents,
|
|
3977
|
-
ctx,
|
|
3978
|
-
state: deps.state,
|
|
3979
|
-
intercomEvents: deps.pi.events,
|
|
3980
|
-
parentSessionId: data.parentSessionId,
|
|
3981
|
-
capabilityCeiling: data.capabilityCeiling,
|
|
3982
|
-
runFanoutBudget: data.runFanoutBudget,
|
|
3983
|
-
signal,
|
|
3984
|
-
runId,
|
|
3985
|
-
sessionDirForIndex,
|
|
3986
|
-
sessionFileForIndex,
|
|
3987
|
-
sessionFileForTask,
|
|
3988
|
-
thinkingOverrideForTask,
|
|
3989
|
-
shareEnabled,
|
|
3990
|
-
artifactConfig,
|
|
3991
|
-
artifactsDir,
|
|
3992
|
-
outputBaseDir,
|
|
3993
|
-
maxOutput: params.maxOutput,
|
|
3994
|
-
paramsCwd: effectiveCwd,
|
|
3995
|
-
progressDir: parallelProgressDir,
|
|
3996
|
-
availableModels,
|
|
3997
|
-
modelScope: data.modelScope,
|
|
3998
|
-
parentModel,
|
|
3999
|
-
modelOverrides,
|
|
4000
|
-
behaviors,
|
|
4001
|
-
firstProgressIndex: parallelProgressPrecreated ? -1 : firstProgressIndex,
|
|
4002
|
-
controlConfig,
|
|
4003
|
-
contextPolicy,
|
|
4004
|
-
onControlEvent,
|
|
4005
|
-
childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(runId, agent, index) : undefined,
|
|
4006
|
-
orchestratorIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
4007
|
-
foregroundControl,
|
|
4008
|
-
concurrencyLimit: parallelConcurrency,
|
|
4009
|
-
globalSemaphore: new Semaphore(deps.config.globalConcurrencyLimit ?? DEFAULT_GLOBAL_CONCURRENCY_LIMIT),
|
|
4010
|
-
maxSubagentDepths,
|
|
4011
|
-
waitToolEnabled: deps.waitToolEnabled,
|
|
4012
|
-
liveResults,
|
|
4013
|
-
liveProgress,
|
|
4014
|
-
onUpdate,
|
|
4015
|
-
worktreeSetup,
|
|
4016
|
-
timeoutMs: data.timeoutMs,
|
|
4017
|
-
deadlineAt,
|
|
4018
|
-
toolTimeoutMs: data.params?.toolTimeoutMs,
|
|
4019
|
-
configToolTimeoutMs: data.configToolTimeoutMs,
|
|
4020
|
-
turnBudget: data.turnBudget,
|
|
4021
|
-
usageBudget: data.usageBudget,
|
|
4022
|
-
toolBudgets,
|
|
4023
|
-
agentContract: params.agentContract,
|
|
4024
|
-
}));
|
|
4025
|
-
for (let i = 0; i < results.length; i++) {
|
|
4026
|
-
const run = results[i]!;
|
|
4027
|
-
recordRun(run.agent, taskTexts[i]!, run.exitCode, run.progressSummary?.durationMs ?? 0);
|
|
4028
|
-
}
|
|
2967
|
+
}
|
|
4029
2968
|
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
}
|
|
2969
|
+
function appendWorkflowOutputWarning(text: string, warning: string | undefined): string {
|
|
2970
|
+
return warning ? `${text}\n\n${warning}` : text;
|
|
2971
|
+
}
|
|
4034
2972
|
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
2973
|
+
function resolveWorkflowChildOutputPath(input: {
|
|
2974
|
+
ctxCwd: string;
|
|
2975
|
+
workflowCwd: string;
|
|
2976
|
+
artifactsDir: string;
|
|
2977
|
+
workflowRunId: string;
|
|
2978
|
+
aggregateOutputPath?: string;
|
|
2979
|
+
configuredOutputBaseDir?: string;
|
|
2980
|
+
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[] };
|
|
2981
|
+
workflowAgentScope?: unknown;
|
|
2982
|
+
state?: SubagentState;
|
|
2983
|
+
key: string;
|
|
2984
|
+
params: Record<string, unknown>;
|
|
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
|
+
}
|
|
2995
|
+
const rawOutput = input.params.output;
|
|
2996
|
+
const hasExplicitOutput = typeof rawOutput === "string" || typeof rawOutput === "boolean";
|
|
2997
|
+
const childCwd = typeof input.params.cwd === "string" ? resolveChildCwd(input.workflowCwd, input.params.cwd) : input.workflowCwd;
|
|
2998
|
+
const agentScope = resolveExecutionAgentScope(input.params.agentScope ?? input.workflowAgentScope);
|
|
2999
|
+
const agents = input.discoverAgents(childCwd, agentScope).agents;
|
|
3000
|
+
const agent = typeof input.params.agent === "string" ? resolveAgentName(input.params.agent, agents).agent : undefined;
|
|
3001
|
+
const agentOutput = typeof agent?.output === "string" ? agent.output : undefined;
|
|
3002
|
+
const output = rawOutput === true || rawOutput === "true"
|
|
3003
|
+
? agentOutput
|
|
3004
|
+
: hasExplicitOutput
|
|
3005
|
+
? rawOutput
|
|
3006
|
+
: input.aggregateOutputPath
|
|
3007
|
+
? workflowChildDefaultOutput(input.aggregateOutputPath, input.artifactsDir, input.workflowRunId, input.key)
|
|
3008
|
+
: agentOutput;
|
|
3009
|
+
return {
|
|
3010
|
+
path: resolveSingleOutputPath(output, input.ctxCwd, childCwd, input.configuredOutputBaseDir),
|
|
3011
|
+
inherited: !hasExplicitOutput && !input.aggregateOutputPath && agentOutput !== undefined,
|
|
3012
|
+
};
|
|
3013
|
+
}
|
|
4075
3014
|
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
3015
|
+
function workflowChildOutputClaims(input: {
|
|
3016
|
+
ctxCwd: string;
|
|
3017
|
+
workflowCwd: string;
|
|
3018
|
+
artifactsDir: string;
|
|
3019
|
+
workflowRunId: string;
|
|
3020
|
+
aggregateOutputPath?: string;
|
|
3021
|
+
configuredOutputBaseDir?: string;
|
|
3022
|
+
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[] };
|
|
3023
|
+
workflowAgentScope?: unknown;
|
|
3024
|
+
state: SubagentState;
|
|
3025
|
+
claimedOutputPaths: Map<string, string>;
|
|
3026
|
+
entries: Array<{ key: string; params: Record<string, unknown> }>;
|
|
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;
|
|
4094
3043
|
}
|
|
3044
|
+
}
|
|
3045
|
+
const claims = new Map(input.claimedOutputPaths);
|
|
3046
|
+
for (const { key, path: resolved } of resolvedEntries) {
|
|
3047
|
+
if (!resolved) continue;
|
|
3048
|
+
const previous = claims.get(resolved);
|
|
3049
|
+
if (previous) return { error: `Workflow children '${previous}' and '${key}' resolve output to the same path: ${resolved}. Use distinct child output paths.` };
|
|
3050
|
+
claims.set(resolved, key);
|
|
3051
|
+
}
|
|
3052
|
+
return { claims, overrides };
|
|
3053
|
+
}
|
|
4095
3054
|
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
results.map((result) => ({
|
|
4101
|
-
agent: result.agent,
|
|
4102
|
-
output: result.truncation?.text || getSingleResultOutput(result),
|
|
4103
|
-
exitCode: result.exitCode,
|
|
4104
|
-
...(result.error === undefined ? {} : { error: result.error }),
|
|
4105
|
-
...(result.timedOut === undefined ? {} : { timedOut: result.timedOut }),
|
|
4106
|
-
})),
|
|
4107
|
-
(i, agent) => `=== Task ${i + 1}: ${agent} ===`,
|
|
4108
|
-
);
|
|
3055
|
+
function applyWorkflowChildOutputClaims(target: Map<string, string>, claims: Map<string, string>): void {
|
|
3056
|
+
target.clear();
|
|
3057
|
+
for (const [resolved, key] of claims) target.set(resolved, key);
|
|
3058
|
+
}
|
|
4109
3059
|
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
3060
|
+
function prepareWorkflowChildLaunchParams(input: {
|
|
3061
|
+
workflowDefaults: SubagentParamsLike;
|
|
3062
|
+
childParams: Record<string, unknown>;
|
|
3063
|
+
parentWorkflowRunId: string;
|
|
3064
|
+
workflowKey: string;
|
|
3065
|
+
ctxCwd: string;
|
|
3066
|
+
workflowCwd: string;
|
|
3067
|
+
artifactsDir: string;
|
|
3068
|
+
aggregateOutputPath?: string;
|
|
3069
|
+
configuredOutputBaseDir?: string;
|
|
3070
|
+
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[] };
|
|
3071
|
+
workflowAgentScope?: unknown;
|
|
3072
|
+
outputOverride?: string;
|
|
3073
|
+
options?: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean; runFanoutBudget?: RunFanoutBudgetDescriptor; parentDeadlineAt?: number };
|
|
3074
|
+
}): SubagentParamsLike {
|
|
3075
|
+
let childParams = input.childParams;
|
|
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) {
|
|
3079
|
+
childParams = { ...input.childParams, output: workflowChildDefaultOutput(input.aggregateOutputPath, input.artifactsDir, input.parentWorkflowRunId, input.workflowKey) };
|
|
3080
|
+
} else if (input.childParams.resume === undefined) {
|
|
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 });
|
|
3082
|
+
if (resolvedOutput.path) childParams = { ...input.childParams, output: resolvedOutput.path };
|
|
3083
|
+
}
|
|
3084
|
+
return prepareWorkflowLaunchParams(input.workflowDefaults, childParams, input.parentWorkflowRunId, input.workflowKey, input.options);
|
|
3085
|
+
}
|
|
4114
3086
|
|
|
3087
|
+
function finalizeSingleWorktreeHandoff(input: {
|
|
3088
|
+
worktreeSetup: WorktreeSetup;
|
|
3089
|
+
artifactsDir: string;
|
|
3090
|
+
runId: string;
|
|
3091
|
+
cwd: string;
|
|
3092
|
+
agent: string;
|
|
3093
|
+
result: SingleResult;
|
|
3094
|
+
}): { suffix: string; reference?: NonNullable<Details["parallelHandoff"]> } {
|
|
3095
|
+
const diffsDir = path.join(input.artifactsDir, "worktree-diffs", input.runId);
|
|
3096
|
+
const diffs = diffWorktrees(input.worktreeSetup, [input.agent], diffsDir);
|
|
3097
|
+
const diffSummary = formatWorktreeDiffSummary(diffs);
|
|
3098
|
+
const manifestPath = parallelHandoffPath(input.artifactsDir, input.runId);
|
|
3099
|
+
const handoff = {
|
|
3100
|
+
manifestPath,
|
|
3101
|
+
runId: input.runId,
|
|
3102
|
+
mode: "single" as const,
|
|
3103
|
+
source: "foreground" as const,
|
|
3104
|
+
cwd: input.cwd,
|
|
3105
|
+
stepIndex: 0,
|
|
3106
|
+
flatStartIndex: 0,
|
|
3107
|
+
setup: input.worktreeSetup,
|
|
3108
|
+
diffs,
|
|
3109
|
+
results: [{
|
|
3110
|
+
agent: input.result.agent,
|
|
3111
|
+
status: resolveSubagentResultStatus(omitUndefinedProperties({
|
|
3112
|
+
exitCode: input.result.exitCode,
|
|
3113
|
+
interrupted: input.result.interrupted,
|
|
3114
|
+
detached: input.result.detached,
|
|
3115
|
+
state: input.result.stopped ? "stopped" : undefined,
|
|
3116
|
+
processSignal: input.result.processSignal,
|
|
3117
|
+
timedOut: input.result.timedOut,
|
|
3118
|
+
stopped: input.result.stopped,
|
|
3119
|
+
turnBudgetExceeded: input.result.turnBudgetExceeded,
|
|
3120
|
+
})),
|
|
3121
|
+
summary: resultSummaryForIntercom(input.result),
|
|
3122
|
+
...(input.result.artifactPaths?.outputPath ? { outputPath: input.result.artifactPaths.outputPath } : {}),
|
|
3123
|
+
...(input.result.structuredOutput !== undefined ? { structuredOutput: input.result.structuredOutput } : {}),
|
|
3124
|
+
...(input.result.structuredOutputPath ? { structuredOutputPath: input.result.structuredOutputPath } : {}),
|
|
3125
|
+
...(input.result.sessionFile ? { sessionPath: input.result.sessionFile } : {}),
|
|
3126
|
+
}],
|
|
3127
|
+
};
|
|
3128
|
+
try {
|
|
3129
|
+
writeParallelHandoffGroup(handoff);
|
|
3130
|
+
const cleanup = cleanupWorktrees(input.worktreeSetup, { kind: "preserve", capturedDiffs: diffs, handoffManifestPath: manifestPath });
|
|
3131
|
+
const reference = writeParallelHandoffGroup({ ...handoff, cleanup });
|
|
4115
3132
|
return {
|
|
4116
|
-
|
|
4117
|
-
|
|
3133
|
+
suffix: [diffSummary, formatParallelHandoffReference(reference)].filter(Boolean).join("\n\n"),
|
|
3134
|
+
reference,
|
|
4118
3135
|
};
|
|
4119
|
-
}
|
|
4120
|
-
|
|
3136
|
+
} catch (error) {
|
|
3137
|
+
return { suffix: [diffSummary, formatParallelHandoffError(error)].filter(Boolean).join("\n\n") };
|
|
4121
3138
|
}
|
|
4122
3139
|
}
|
|
4123
3140
|
|
|
@@ -4167,6 +3184,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
4167
3184
|
currentProvider,
|
|
4168
3185
|
data.modelScope === undefined ? {} : { scope: data.modelScope },
|
|
4169
3186
|
);
|
|
3187
|
+
const modelOverrideFromParent = inheritsParentModel(params.model as string | undefined, agentConfig.model, parentModel);
|
|
4170
3188
|
let skillOverride: string[] | false | undefined = normalizeSkillInput(params.skill);
|
|
4171
3189
|
let readsOverride: string[] | false | undefined = params.reads;
|
|
4172
3190
|
const rawOutput = params.output !== undefined ? params.output : agentConfig.output;
|
|
@@ -4175,109 +3193,35 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
4175
3193
|
const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
|
|
4176
3194
|
const maxSubagentDepth = resolveChildMaxSubagentDepth(currentMaxSubagentDepth, agentConfig.maxSubagentDepth);
|
|
4177
3195
|
|
|
4178
|
-
if (params.clarify === true && ctx.hasUI) {
|
|
4179
|
-
const behavior = resolveStepBehavior(agentConfig, omitUndefinedProperties({ output: effectiveOutput, skills: skillOverride }));
|
|
4180
|
-
const availableSkills = discoverAvailableSkills(effectiveCwd);
|
|
4181
|
-
|
|
4182
|
-
const result = await ctx.ui.custom<ChainClarifyResult>(
|
|
4183
|
-
(tui, theme, _kb, done) =>
|
|
4184
|
-
new ChainClarifyComponent(
|
|
4185
|
-
tui, theme,
|
|
4186
|
-
[agentConfig],
|
|
4187
|
-
[task],
|
|
4188
|
-
task,
|
|
4189
|
-
undefined,
|
|
4190
|
-
[behavior],
|
|
4191
|
-
availableModels,
|
|
4192
|
-
currentProvider,
|
|
4193
|
-
availableSkills,
|
|
4194
|
-
done,
|
|
4195
|
-
"single",
|
|
4196
|
-
),
|
|
4197
|
-
{ overlay: true, overlayOptions: { anchor: "center", width: 84, maxHeight: "80%" } },
|
|
4198
|
-
);
|
|
4199
|
-
|
|
4200
|
-
if (!result || !result.confirmed) {
|
|
4201
|
-
return { content: [{ type: "text", text: "Cancelled" }], details: { mode: "single", results: [] } };
|
|
4202
|
-
}
|
|
4203
|
-
|
|
4204
|
-
task = result.templates[0]!;
|
|
4205
|
-
const override = result.behaviorOverrides[0];
|
|
4206
|
-
if (override?.model !== undefined) modelOverride = resolveEffectiveSubagentModel(override.model, agentConfig.model, parentModel, availableModels, currentProvider, data.modelScope === undefined ? {} : { scope: data.modelScope });
|
|
4207
|
-
if (override?.output !== undefined) effectiveOutput = normalizeSingleOutputOverride(override.output, agentConfig.output);
|
|
4208
|
-
if (override?.skills !== undefined) skillOverride = override.skills;
|
|
4209
|
-
if (override?.reads !== undefined) readsOverride = override.reads;
|
|
4210
3196
|
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
3197
|
+
const sourceCwd = effectiveCwd;
|
|
3198
|
+
const { setup: worktreeSetup, errorResult: worktreeSetupError } = createSingleWorktreeSetup(
|
|
3199
|
+
params.worktree,
|
|
3200
|
+
sourceCwd,
|
|
3201
|
+
runId,
|
|
3202
|
+
params.agent!,
|
|
3203
|
+
deps.config.worktreeSetupHook,
|
|
3204
|
+
deps.config.worktreeSetupHookTimeoutMs,
|
|
3205
|
+
deps.config.worktreeBaseDir,
|
|
3206
|
+
);
|
|
3207
|
+
if (worktreeSetupError) return worktreeSetupError;
|
|
3208
|
+
const singleCwd = worktreeSetup?.worktrees[0]?.agentCwd ?? sourceCwd;
|
|
3209
|
+
let pendingHandoff: Details["parallelHandoff"];
|
|
3210
|
+
if (worktreeSetup) {
|
|
3211
|
+
try {
|
|
3212
|
+
pendingHandoff = writePendingParallelHandoff({
|
|
3213
|
+
manifestPath: parallelHandoffPath(artifactsDir, runId),
|
|
3214
|
+
runId,
|
|
3215
|
+
mode: "single",
|
|
3216
|
+
source: "foreground",
|
|
3217
|
+
cwd: sourceCwd,
|
|
3218
|
+
stepIndex: 0,
|
|
3219
|
+
flatStartIndex: 0,
|
|
3220
|
+
setup: worktreeSetup,
|
|
4232
3221
|
});
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(task) : task,
|
|
4237
|
-
goal: task,
|
|
4238
|
-
agentConfig,
|
|
4239
|
-
recoveryAgentConfig: data.recoveryAgents.find((agent) => agent.name === params.agent),
|
|
4240
|
-
ctx: asyncCtx,
|
|
4241
|
-
availableModels,
|
|
4242
|
-
cwd: effectiveCwd,
|
|
4243
|
-
maxOutput: params.maxOutput,
|
|
4244
|
-
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
4245
|
-
artifactConfig,
|
|
4246
|
-
shareEnabled,
|
|
4247
|
-
activeAsyncCapacity: capacity.activeAsyncCapacity,
|
|
4248
|
-
sessionRoot,
|
|
4249
|
-
sessionFile: sessionFileForTask(params.agent!, 0, modelOverride),
|
|
4250
|
-
context: contextPolicy.contextForAgent(params.agent!),
|
|
4251
|
-
skills: skillOverride === false ? [] : skillOverride,
|
|
4252
|
-
output: effectiveOutput,
|
|
4253
|
-
outputMode: effectiveOutputMode,
|
|
4254
|
-
...(readsOverride !== undefined ? { reads: readsOverride } : {}),
|
|
4255
|
-
outputBaseDir: resolveSingleRunOutputBaseDir(deps, artifactsDir, id),
|
|
4256
|
-
modelOverride,
|
|
4257
|
-
thinkingOverride: thinkingOverrideForTask(params.agent!, 0, modelOverride),
|
|
4258
|
-
maxSubagentDepth,
|
|
4259
|
-
waitToolEnabled: deps.waitToolEnabled,
|
|
4260
|
-
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
4261
|
-
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
4262
|
-
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
4263
|
-
controlConfig,
|
|
4264
|
-
intercomBridge: params.intercomBridge,
|
|
4265
|
-
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
4266
|
-
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
|
|
4267
|
-
nestedRoute: data.nestedRoute,
|
|
4268
|
-
agentContract: params.agentContract,
|
|
4269
|
-
structuredOutputSchema: params.outputSchema,
|
|
4270
|
-
acceptance: params.acceptance,
|
|
4271
|
-
timeoutMs: data.timeoutMs,
|
|
4272
|
-
turnBudget: data.turnBudget,
|
|
4273
|
-
toolBudget: effectiveToolBudget.toolBudget,
|
|
4274
|
-
usageBudget: data.usageBudget,
|
|
4275
|
-
allowZeroToolBudget: data.allowZeroToolBudget && effectiveToolBudget.toolBudget === data.toolBudget,
|
|
4276
|
-
runFanoutBudget: data.runFanoutBudget,
|
|
4277
|
-
}));
|
|
4278
|
-
} finally {
|
|
4279
|
-
finalizeClarifiedBackgroundCapacity(data, deps, capacity.activeAsyncCapacity);
|
|
4280
|
-
}
|
|
3222
|
+
} catch (error) {
|
|
3223
|
+
cleanupWorktrees(worktreeSetup);
|
|
3224
|
+
return { content: [{ type: "text", text: formatParallelHandoffError(error) }], isError: true, details: { mode: "single", results: [] } };
|
|
4281
3225
|
}
|
|
4282
3226
|
}
|
|
4283
3227
|
|
|
@@ -4286,9 +3230,10 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
4286
3230
|
task = wrapForkTask(task);
|
|
4287
3231
|
}
|
|
4288
3232
|
const cleanTask = task;
|
|
4289
|
-
const outputPath = resolveSingleOutputPath(effectiveOutput, ctx.cwd,
|
|
3233
|
+
const outputPath = resolveSingleOutputPath(effectiveOutput, ctx.cwd, singleCwd, resolveSingleRunOutputBaseDir(deps, artifactsDir, runId));
|
|
4290
3234
|
const validationError = validateFileOnlyOutputMode(effectiveOutputMode, outputPath, `Single run (${params.agent})`);
|
|
4291
3235
|
if (validationError) {
|
|
3236
|
+
if (worktreeSetup) cleanupWorktrees(worktreeSetup);
|
|
4292
3237
|
return { content: [{ type: "text", text: validationError }], isError: true, details: { mode: "single", results: [] } };
|
|
4293
3238
|
}
|
|
4294
3239
|
const structuredRuntime = params.outputSchema
|
|
@@ -4297,7 +3242,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
4297
3242
|
// Reads: caller override > agent defaultReads > none. `~`/`~/` expand to home;
|
|
4298
3243
|
// absolute paths pass through; relative paths resolve against the child cwd.
|
|
4299
3244
|
const reads = readsOverride !== undefined ? readsOverride : agentConfig.defaultReads ?? false;
|
|
4300
|
-
const readPaths = Array.isArray(reads) ? resolveExistingReadPaths(reads,
|
|
3245
|
+
const readPaths = Array.isArray(reads) ? resolveExistingReadPaths(reads, singleCwd) : [];
|
|
4301
3246
|
const readsInstruction = readPaths.length > 0
|
|
4302
3247
|
? `[Read from: ${readPaths.join(", ")}]\n\n`
|
|
4303
3248
|
: "";
|
|
@@ -4314,13 +3259,13 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
4314
3259
|
let detachForeground: ((reason?: string) => boolean) | undefined;
|
|
4315
3260
|
const foregroundControl = deps.state.foregroundControls.get(runId);
|
|
4316
3261
|
if (foregroundControl) {
|
|
4317
|
-
const thinking = resolveEffectiveThinking(modelOverride, thinkingOverrideForTask(params.agent!, 0, modelOverride));
|
|
3262
|
+
const thinking = resolveEffectiveThinking(modelOverride, thinkingOverrideForTask(params.agent!, 0, modelOverride, modelOverrideFromParent));
|
|
4318
3263
|
beginForegroundChild(foregroundControl, omitUndefinedProperties({
|
|
4319
3264
|
index: 0,
|
|
4320
3265
|
agent: params.agent!,
|
|
4321
3266
|
authoredTask,
|
|
4322
3267
|
effectivePrompt: task,
|
|
4323
|
-
cwd:
|
|
3268
|
+
cwd: singleCwd,
|
|
4324
3269
|
outputPath,
|
|
4325
3270
|
rerun: { params: { ...params, task: authoredTask, async: params.async ?? false } },
|
|
4326
3271
|
description: foregroundControl.description,
|
|
@@ -4352,14 +3297,14 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
4352
3297
|
...workflowForegroundSteeringLaunchOptions(foregroundControl, 0),
|
|
4353
3298
|
context: data.contextPolicy.contextForAgent(params.agent!),
|
|
4354
3299
|
runFanoutBudget: params.runFanoutAdmitted ? data.runFanoutBudget : { ...data.runFanoutBudget, parentPath: `${data.runFanoutBudget.parentPath ? `${data.runFanoutBudget.parentPath}/` : ""}single` },
|
|
4355
|
-
cwd:
|
|
3300
|
+
cwd: singleCwd,
|
|
4356
3301
|
signal,
|
|
4357
3302
|
interruptSignal: interruptController.signal,
|
|
4358
3303
|
allowIntercomDetach: agentConfig.systemPrompt?.includes(INTERCOM_BRIDGE_MARKER) === true,
|
|
4359
3304
|
intercomEvents: deps.pi.events,
|
|
4360
3305
|
runId,
|
|
4361
3306
|
sessionDir: sessionDirForIndex(0),
|
|
4362
|
-
sessionFile: sessionFileForTask(params.agent!, 0, modelOverride),
|
|
3307
|
+
sessionFile: sessionFileForTask(params.agent!, 0, modelOverride, modelOverrideFromParent),
|
|
4363
3308
|
share: shareEnabled,
|
|
4364
3309
|
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
4365
3310
|
artifactConfig,
|
|
@@ -4376,7 +3321,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
4376
3321
|
nestedRoute: foregroundControl?.nestedRoute,
|
|
4377
3322
|
index: 0,
|
|
4378
3323
|
modelOverride,
|
|
4379
|
-
|
|
3324
|
+
modelOverrideFromParent,
|
|
3325
|
+
thinkingOverride: thinkingOverrideForTask(params.agent!, 0, modelOverride, modelOverrideFromParent),
|
|
4380
3326
|
availableModels,
|
|
4381
3327
|
preferredModelProvider: currentProvider,
|
|
4382
3328
|
modelScope: data.modelScope,
|
|
@@ -4392,10 +3338,21 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
4392
3338
|
onDetachedExit: (result) => {
|
|
4393
3339
|
try {
|
|
4394
3340
|
try {
|
|
4395
|
-
updateRememberedForegroundChild(deps.state, { runId, mode: "single", cwd:
|
|
3341
|
+
updateRememberedForegroundChild(deps.state, { runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, index: 0, result, events: deps.pi.events, notify: true });
|
|
4396
3342
|
} catch {
|
|
4397
3343
|
// Remembered foreground state is best-effort; run history and cleanup must still complete.
|
|
4398
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
|
+
}
|
|
4399
3356
|
} finally {
|
|
4400
3357
|
try {
|
|
4401
3358
|
if (!artifactConfig.enabled) cleanupStructuredOutputRuntime(structuredRuntime);
|
|
@@ -4419,6 +3376,9 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
4419
3376
|
capabilityCeiling: data.capabilityCeiling,
|
|
4420
3377
|
allowZeroToolBudget: data.allowZeroToolBudget && effectiveToolBudget.toolBudget === data.toolBudget,
|
|
4421
3378
|
}));
|
|
3379
|
+
} catch (error) {
|
|
3380
|
+
if (worktreeSetup) cleanupWorktrees(worktreeSetup);
|
|
3381
|
+
throw error;
|
|
4422
3382
|
} finally {
|
|
4423
3383
|
// An attached runSync rejection still owns its child and structured runtime.
|
|
4424
3384
|
// A successful detached receipt transfers both to onDetachedExit while the
|
|
@@ -4432,6 +3392,13 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
4432
3392
|
recordRun(params.agent!, cleanTask, r.exitCode, r.progressSummary?.durationMs ?? 0);
|
|
4433
3393
|
}
|
|
4434
3394
|
|
|
3395
|
+
let worktreeHandoff: ReturnType<typeof finalizeSingleWorktreeHandoff> | undefined;
|
|
3396
|
+
if (worktreeSetup) {
|
|
3397
|
+
worktreeHandoff = r.detached
|
|
3398
|
+
? { suffix: pendingHandoff ? formatParallelHandoffReference(pendingHandoff) : "", reference: pendingHandoff }
|
|
3399
|
+
: finalizeSingleWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: sourceCwd, agent: params.agent!, result: r });
|
|
3400
|
+
}
|
|
3401
|
+
|
|
4435
3402
|
if (r.progress) allProgress.push(r.progress);
|
|
4436
3403
|
if (r.artifactPaths) allArtifactPaths.push(r.artifactPaths);
|
|
4437
3404
|
|
|
@@ -4466,8 +3433,9 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
4466
3433
|
totalChildUsage: sumResultsUsage([r]),
|
|
4467
3434
|
totalCost,
|
|
4468
3435
|
usageBudget: usageBudgetState(data.usageBudget, totalCost),
|
|
3436
|
+
...(worktreeHandoff?.reference ? { parallelHandoff: worktreeHandoff.reference } : {}),
|
|
4469
3437
|
}));
|
|
4470
|
-
rememberForegroundRun(deps.state, { runId, mode: "single", cwd:
|
|
3438
|
+
rememberForegroundRun(deps.state, { runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, results: details.results });
|
|
4471
3439
|
|
|
4472
3440
|
const suppressRoutineResultIntercom = shouldSuppressRoutineResultIntercom({ suppressRoutineResultIntercom: params.suppressRoutineResultIntercom, results: [r] });
|
|
4473
3441
|
if (!r.detached && !r.interrupted && !suppressRoutineResultIntercom) {
|
|
@@ -4490,6 +3458,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
4490
3458
|
}
|
|
4491
3459
|
}
|
|
4492
3460
|
|
|
3461
|
+
const worktreeSuffix = worktreeHandoff?.suffix ? `\n\n${worktreeHandoff.suffix}` : "";
|
|
4493
3462
|
if (r.detached) {
|
|
4494
3463
|
const statusRecovery = `subagent({ action: "status", id: "${runId}" }) to recover the result; do not resume or launch a replacement while it remains detached.`;
|
|
4495
3464
|
const blockingRecovery = `subagent_wait({ id: "${runId}" }). Use ${statusRecovery}`;
|
|
@@ -4499,26 +3468,26 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
4499
3468
|
? `Detached at user request: ${params.agent}. The child continues independently. Register a completion wake-up with subagent_wait({ id: "${runId}", nonBlocking: true }), or use ${statusRecovery}`
|
|
4500
3469
|
: `Detached before task completion: ${params.agent}. Wait with ${blockingRecovery}`;
|
|
4501
3470
|
return {
|
|
4502
|
-
content: [{ type: "text", text: message }],
|
|
3471
|
+
content: [{ type: "text", text: `${message}${worktreeSuffix}` }],
|
|
4503
3472
|
details,
|
|
4504
3473
|
};
|
|
4505
3474
|
}
|
|
4506
3475
|
|
|
4507
3476
|
if (r.interrupted) {
|
|
4508
3477
|
return {
|
|
4509
|
-
content: [{ type: "text", text: `Run paused after interrupt (${params.agent}). Waiting for explicit next action
|
|
3478
|
+
content: [{ type: "text", text: `Run paused after interrupt (${params.agent}). Waiting for explicit next action.${worktreeSuffix}` }],
|
|
4510
3479
|
details,
|
|
4511
3480
|
};
|
|
4512
3481
|
}
|
|
4513
3482
|
|
|
4514
3483
|
if (r.exitCode !== 0)
|
|
4515
3484
|
return {
|
|
4516
|
-
content: [{ type: "text", text: formatFailedSingleRunOutput(r, finalizedOutput.displayOutput) }],
|
|
3485
|
+
content: [{ type: "text", text: `${formatFailedSingleRunOutput(r, finalizedOutput.displayOutput)}${worktreeSuffix}` }],
|
|
4517
3486
|
details,
|
|
4518
3487
|
isError: true,
|
|
4519
3488
|
};
|
|
4520
3489
|
return {
|
|
4521
|
-
content: [{ type: "text", text: finalizedOutput.displayOutput || "(no output)" }],
|
|
3490
|
+
content: [{ type: "text", text: `${finalizedOutput.displayOutput || "(no output)"}${worktreeSuffix}` }],
|
|
4522
3491
|
details,
|
|
4523
3492
|
};
|
|
4524
3493
|
}
|
|
@@ -4641,13 +3610,103 @@ function workflowChildResult(key: string, result: AgentToolResult<Details>): Wor
|
|
|
4641
3610
|
};
|
|
4642
3611
|
}
|
|
4643
3612
|
|
|
3613
|
+
function workflowSteerReceipt(key: string, result: AgentToolResult<Details>): WorkflowSteerResult {
|
|
3614
|
+
const steering = result.details.steering;
|
|
3615
|
+
const error = result.content.map((part) => part.type === "text" ? part.text : "").filter(Boolean).join("\n") || undefined;
|
|
3616
|
+
if (!steering) return { key, state: "failed", ...(error ? { error } : {}) };
|
|
3617
|
+
const state = result.isError === true || steering.state === "failed" || steering.state === "partial"
|
|
3618
|
+
? "failed"
|
|
3619
|
+
: steering.deliveryStatus === "delivered" ? "delivered" : "queued";
|
|
3620
|
+
return {
|
|
3621
|
+
key,
|
|
3622
|
+
state,
|
|
3623
|
+
requestId: steering.requestId,
|
|
3624
|
+
deliveryStatus: steering.deliveryStatus,
|
|
3625
|
+
targets: steering.targets.map((target) => ({ index: target.index, state: target.state, ...(target.reason ? { reason: target.reason } : {}) })),
|
|
3626
|
+
...(state === "failed" && error ? { error } : {}),
|
|
3627
|
+
};
|
|
3628
|
+
}
|
|
3629
|
+
|
|
3630
|
+
export async function steerWorkflowChildByKey(input: {
|
|
3631
|
+
state: SubagentState;
|
|
3632
|
+
workflowRunId: string;
|
|
3633
|
+
key: string;
|
|
3634
|
+
message: string;
|
|
3635
|
+
options: WorkflowSteerOptions;
|
|
3636
|
+
signal?: AbortSignal;
|
|
3637
|
+
asyncDirRoot?: string;
|
|
3638
|
+
resolveRunId?: () => string | undefined;
|
|
3639
|
+
}): Promise<WorkflowSteerResult> {
|
|
3640
|
+
const asyncDirRoot = input.asyncDirRoot ?? DIRS.async;
|
|
3641
|
+
const ackTimeoutMs = input.options.ackTimeoutMs ?? 3_000;
|
|
3642
|
+
const deadline = Date.now() + ackTimeoutMs;
|
|
3643
|
+
while (true) {
|
|
3644
|
+
const control = [...input.state.foregroundControls.values()].find((candidate) => candidate.parentWorkflowRunId === input.workflowRunId
|
|
3645
|
+
&& candidate.workflowKey === input.key
|
|
3646
|
+
&& Boolean(candidate.workflowSteeringDir)
|
|
3647
|
+
&& (candidate.activeChildren?.size ?? 0) > 0);
|
|
3648
|
+
if (control) {
|
|
3649
|
+
const result = await steerWorkflowForegroundTarget({
|
|
3650
|
+
target: { control, workflowRunId: input.workflowRunId, sourceRunId: control.runId },
|
|
3651
|
+
message: input.message,
|
|
3652
|
+
mode: input.options.mode,
|
|
3653
|
+
index: input.options.index,
|
|
3654
|
+
ackTimeoutMs: Math.max(1, deadline - Date.now()),
|
|
3655
|
+
signal: input.signal,
|
|
3656
|
+
});
|
|
3657
|
+
return workflowSteerReceipt(input.key, result);
|
|
3658
|
+
}
|
|
3659
|
+
|
|
3660
|
+
const workflowStatus = readStatus(path.join(asyncDirRoot, input.workflowRunId));
|
|
3661
|
+
const step = workflowStatus?.steps?.find((candidate) => candidate.workflowKey === input.key);
|
|
3662
|
+
const childRunId = step?.runId ?? input.resolveRunId?.();
|
|
3663
|
+
if (childRunId) {
|
|
3664
|
+
const asyncDir = path.join(asyncDirRoot, childRunId);
|
|
3665
|
+
const childStatus = reconcileAsyncRun(asyncDir).status;
|
|
3666
|
+
if (childStatus && childStatus.state !== "running" && childStatus.state !== "queued" && (input.options.mode !== "follow_up" || !canQueueRetainedAsyncFollowUp(childStatus, input.options.index))) {
|
|
3667
|
+
return { key: input.key, state: "missed", error: `Workflow child '${input.key}' is ${childStatus.state}.` };
|
|
3668
|
+
}
|
|
3669
|
+
if (childStatus) {
|
|
3670
|
+
const result = await steerAsyncRun({
|
|
3671
|
+
state: input.state,
|
|
3672
|
+
runId: childRunId,
|
|
3673
|
+
message: input.message,
|
|
3674
|
+
mode: input.options.mode,
|
|
3675
|
+
index: input.options.index,
|
|
3676
|
+
ackTimeoutMs: Math.max(1, deadline - Date.now()),
|
|
3677
|
+
location: { asyncDir },
|
|
3678
|
+
signal: input.signal,
|
|
3679
|
+
});
|
|
3680
|
+
return workflowSteerReceipt(input.key, result);
|
|
3681
|
+
}
|
|
3682
|
+
}
|
|
3683
|
+
if (step && step.status !== "running" && step.status !== "pending") {
|
|
3684
|
+
return { key: input.key, state: "missed", error: `Workflow child '${input.key}' is ${step.status}.` };
|
|
3685
|
+
}
|
|
3686
|
+
if (workflowStatus && workflowStatus.state !== "running" && workflowStatus.state !== "queued") {
|
|
3687
|
+
return { key: input.key, state: "missed", error: `Workflow '${input.workflowRunId}' is ${workflowStatus.state}.` };
|
|
3688
|
+
}
|
|
3689
|
+
if (input.signal?.aborted || Date.now() >= deadline) {
|
|
3690
|
+
return { key: input.key, state: "missed", error: `Workflow child '${input.key}' had no live steering route.` };
|
|
3691
|
+
}
|
|
3692
|
+
await new Promise<void>((resolve) => setTimeout(resolve, Math.min(10, Math.max(1, deadline - Date.now()))));
|
|
3693
|
+
}
|
|
3694
|
+
}
|
|
3695
|
+
|
|
4644
3696
|
export function prepareWorkflowLaunchParams(
|
|
4645
3697
|
workflowDefaults: SubagentParamsLike,
|
|
4646
3698
|
childParams: Record<string, unknown>,
|
|
4647
3699
|
parentWorkflowRunId: string,
|
|
4648
3700
|
workflowKey: string,
|
|
4649
|
-
options: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean; runFanoutBudget?: RunFanoutBudgetDescriptor } = {},
|
|
3701
|
+
options: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean; runFanoutBudget?: RunFanoutBudgetDescriptor; parentDeadlineAt?: number } = {},
|
|
4650
3702
|
): SubagentParamsLike {
|
|
3703
|
+
const parentTimeoutMs = options.parentDeadlineAt === undefined
|
|
3704
|
+
|| childParams.timeoutMs !== undefined
|
|
3705
|
+
|| childParams.maxRuntimeMs !== undefined
|
|
3706
|
+
|| workflowDefaults.timeoutMs !== undefined
|
|
3707
|
+
|| workflowDefaults.maxRuntimeMs !== undefined
|
|
3708
|
+
? undefined
|
|
3709
|
+
: Math.max(1, options.parentDeadlineAt - Date.now());
|
|
4651
3710
|
if (typeof childParams.resume === "string") {
|
|
4652
3711
|
if (childParams.gate !== undefined || workflowDefaults.gate !== undefined) {
|
|
4653
3712
|
throw new Error("gate is not supported with retained resume; resume uses the retained child contract.");
|
|
@@ -4656,12 +3715,15 @@ export function prepareWorkflowLaunchParams(
|
|
|
4656
3715
|
const turnBudget = childParams.turnBudget ?? workflowDefaults.turnBudget;
|
|
4657
3716
|
const toolBudget = childParams.toolBudget ?? workflowDefaults.toolBudget;
|
|
4658
3717
|
const intercomBridge = childParams.intercomBridge ?? workflowDefaults.intercomBridge;
|
|
3718
|
+
const worktree = childParams.worktree ?? workflowDefaults.worktree;
|
|
4659
3719
|
return {
|
|
4660
3720
|
action: "resume",
|
|
4661
3721
|
id: childParams.resume.trim(),
|
|
3722
|
+
...(typeof childParams.index === "number" && Number.isInteger(childParams.index) ? { index: childParams.index } : {}),
|
|
4662
3723
|
message: typeof childParams.task === "string" ? childParams.task.trim() : "",
|
|
4663
3724
|
workflowParentRunId: parentWorkflowRunId,
|
|
4664
3725
|
workflowKey,
|
|
3726
|
+
...(worktree !== undefined ? { worktree: worktree as boolean } : {}),
|
|
4665
3727
|
...(options.runFanoutBudget ? { runFanoutBudget: { ...options.runFanoutBudget, parentPath: `${options.runFanoutBudget.parentPath ? `${options.runFanoutBudget.parentPath}/` : ""}workflow[${workflowKey}]` } } : {}),
|
|
4666
3728
|
...(options.missionDetached ? { mission: false } : {}),
|
|
4667
3729
|
...(timeoutMs !== undefined ? { timeoutMs: timeoutMs as number } : {}),
|
|
@@ -4677,12 +3739,13 @@ export function prepareWorkflowLaunchParams(
|
|
|
4677
3739
|
...(options.missionDetached ? { mission: false } : {}),
|
|
4678
3740
|
workflowParentRunId: parentWorkflowRunId,
|
|
4679
3741
|
workflowKey,
|
|
3742
|
+
...(parentTimeoutMs !== undefined ? { workflowParentDeadlineAt: options.parentDeadlineAt } : {}),
|
|
4680
3743
|
...(options.runFanoutBudget ? { runFanoutBudget: { ...options.runFanoutBudget, parentPath: `${options.runFanoutBudget.parentPath ? `${options.runFanoutBudget.parentPath}/` : ""}workflow[${workflowKey}]` } } : {}),
|
|
4681
3744
|
...(options.suppressRoutineResultIntercom ? { suppressRoutineResultIntercom: true } : {}),
|
|
4682
3745
|
} as SubagentParamsLike;
|
|
4683
3746
|
const normalizedGate = normalizeGateParams(launchParams);
|
|
4684
3747
|
if (!normalizedGate.ok) throw new Error(normalizedGate.error);
|
|
4685
|
-
return
|
|
3748
|
+
return normalizedGate.params;
|
|
4686
3749
|
}
|
|
4687
3750
|
|
|
4688
3751
|
type GateParamsNormalizationResult =
|
|
@@ -4700,41 +3763,6 @@ function normalizeGateParams(params: SubagentParamsLike): GateParamsNormalizatio
|
|
|
4700
3763
|
return { ok: true, params: { ...rest, ...(normalized.acceptance !== undefined ? { acceptance: normalized.acceptance } : {}) } };
|
|
4701
3764
|
}
|
|
4702
3765
|
|
|
4703
|
-
function prepareWorkflowChildParams(params: SubagentParamsLike): SubagentParamsLike {
|
|
4704
|
-
if (params.worktree !== true || !params.agent) return params;
|
|
4705
|
-
const {
|
|
4706
|
-
agent,
|
|
4707
|
-
task = "",
|
|
4708
|
-
model,
|
|
4709
|
-
skill,
|
|
4710
|
-
output,
|
|
4711
|
-
outputMode,
|
|
4712
|
-
outputSchema,
|
|
4713
|
-
acceptance,
|
|
4714
|
-
agentContract,
|
|
4715
|
-
toolBudget,
|
|
4716
|
-
reads,
|
|
4717
|
-
...runParams
|
|
4718
|
-
} = params;
|
|
4719
|
-
return {
|
|
4720
|
-
...runParams,
|
|
4721
|
-
worktree: true,
|
|
4722
|
-
tasks: [{
|
|
4723
|
-
agent,
|
|
4724
|
-
task,
|
|
4725
|
-
...(model !== undefined ? { model } : {}),
|
|
4726
|
-
...(skill !== undefined ? { skill } : {}),
|
|
4727
|
-
...(output !== undefined ? { output } : {}),
|
|
4728
|
-
...(reads !== undefined ? { reads } : {}),
|
|
4729
|
-
...(outputMode !== undefined ? { outputMode } : {}),
|
|
4730
|
-
...(outputSchema !== undefined ? { outputSchema } : {}),
|
|
4731
|
-
...(acceptance !== undefined ? { acceptance } : {}),
|
|
4732
|
-
...(agentContract !== undefined ? { agentContract } : {}),
|
|
4733
|
-
...(toolBudget !== undefined ? { toolBudget } : {}),
|
|
4734
|
-
}],
|
|
4735
|
-
};
|
|
4736
|
-
}
|
|
4737
|
-
|
|
4738
3766
|
function formatWorkflowValue(value: unknown): string {
|
|
4739
3767
|
if (value === undefined) return "(undefined)";
|
|
4740
3768
|
if (typeof value === "string") return value;
|
|
@@ -4865,7 +3893,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4865
3893
|
const warning = getProjectArtifactPackagingWarning(workflowCwd);
|
|
4866
3894
|
if (warning) console.warn(`[pi-subagents] ${warning}`);
|
|
4867
3895
|
}
|
|
4868
|
-
const workflowPrompts = { render: (ref: string, vars?: unknown) => renderWorkflowPrompt(ref, vars, workflowCwd) };
|
|
4869
3896
|
const chatProgressResult = resolveWorkflowChatProgress({ requested: requestParams.chatProgress, parentCwd, workflowCwd, background: requestParams.async !== false });
|
|
4870
3897
|
if (chatProgressResult.error) return { content: [{ type: "text", text: chatProgressResult.error }], isError: true, details: { mode: "workflow", results: [] } };
|
|
4871
3898
|
const chatProgress = chatProgressResult.projection!;
|
|
@@ -4942,12 +3969,17 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4942
3969
|
};
|
|
4943
3970
|
if (workflowRunId) {
|
|
4944
3971
|
const toolCallId = _id;
|
|
3972
|
+
const workflowSessionRoot = requestParams.sessionDir
|
|
3973
|
+
? path.resolve(deps.expandTilde(requestParams.sessionDir))
|
|
3974
|
+
: trustedSessionRootsForStatus(ctx, deps)[0];
|
|
4945
3975
|
const asyncDir = path.join(DIRS.async, workflowRunId);
|
|
4946
3976
|
const resultPath = resultFilePath(DIRS.results, workflowRunId);
|
|
4947
3977
|
const statusPath = path.join(asyncDir, "status.json");
|
|
4948
3978
|
const eventsPath = path.join(asyncDir, "events.jsonl");
|
|
4949
3979
|
const startedAt = Date.now();
|
|
4950
3980
|
const currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
3981
|
+
const completionOwnerId = deps.state.completionOwnerId ?? currentCompletionOwnerId();
|
|
3982
|
+
deps.state.completionOwnerId = completionOwnerId;
|
|
4951
3983
|
try {
|
|
4952
3984
|
fs.mkdirSync(asyncDir, { recursive: true });
|
|
4953
3985
|
writeRunFanoutBudgetDescriptor(asyncDir, workflowFanoutBudget);
|
|
@@ -4965,51 +3997,115 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4965
3997
|
runId: workflowRunId,
|
|
4966
3998
|
toolCallId,
|
|
4967
3999
|
sessionId: currentSessionId ?? undefined,
|
|
4000
|
+
completionOwnerId,
|
|
4968
4001
|
mode: "workflow",
|
|
4969
4002
|
state: "running",
|
|
4970
4003
|
startedAt,
|
|
4971
4004
|
lastUpdate: startedAt,
|
|
4972
4005
|
...(timeout !== undefined ? { deadlineAt: startedAt + timeout, timeoutMs: timeout } : {}),
|
|
4973
4006
|
cwd: workflowCwd,
|
|
4007
|
+
...(workflowSessionRoot ? { sessionRoot: workflowSessionRoot } : {}),
|
|
4008
|
+
...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}),
|
|
4974
4009
|
pid: process.pid,
|
|
4975
4010
|
steps: [],
|
|
4976
4011
|
workflow: { trace: [], emits: [], console: [] },
|
|
4977
4012
|
runFanoutBudget: getRunFanoutBudgetSnapshot(workflowFanoutBudget),
|
|
4978
4013
|
};
|
|
4979
|
-
const appendWorkflowEvent = (event: Record<string, unknown>) =>
|
|
4014
|
+
const appendWorkflowEvent = (event: Record<string, unknown>) => {
|
|
4015
|
+
try {
|
|
4016
|
+
fs.appendFileSync(eventsPath, `${JSON.stringify({ ts: Date.now(), runId: workflowRunId, ...event })}\n`, "utf-8");
|
|
4017
|
+
} catch (error) {
|
|
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);
|
|
4023
|
+
}
|
|
4024
|
+
};
|
|
4980
4025
|
let indexedState: AsyncStatus["state"] | undefined;
|
|
4981
|
-
const
|
|
4026
|
+
const indexPersistence = createCapacityResilientJsonWriter({
|
|
4027
|
+
keepAlive: true,
|
|
4028
|
+
onSuccess: (_filePath, payload) => { indexedState = (payload as { state: AsyncStatus["state"] }).state; },
|
|
4029
|
+
onError: (error, filePath) => console.error(`Failed to update async workflow index '${filePath}':`, error),
|
|
4030
|
+
});
|
|
4031
|
+
const queueActiveRunIndex = (): void => {
|
|
4032
|
+
const state = status.state;
|
|
4033
|
+
if (indexedState === state && indexPersistence.pendingCount() === 0) return;
|
|
4034
|
+
indexPersistence.write(asyncDir, { state, toolCallId: status.toolCallId }, (_filePath, payload) => {
|
|
4035
|
+
const indexPayload = payload as { state: AsyncStatus["state"]; toolCallId?: string };
|
|
4036
|
+
updateActiveRunIndex(asyncDir, indexPayload.state, indexPayload.toolCallId, { retryCapacityErrors: true });
|
|
4037
|
+
});
|
|
4038
|
+
};
|
|
4039
|
+
const runPersistence = createCapacityResilientJsonWriter({
|
|
4040
|
+
keepAlive: true,
|
|
4041
|
+
onSuccess: (filePath) => { if (filePath === statusPath) queueActiveRunIndex(); },
|
|
4042
|
+
onError: (error, filePath) => console.error(`Failed to persist async workflow state '${filePath}':`, error),
|
|
4043
|
+
write: (filePath, payload) => filePath === resultPath
|
|
4044
|
+
? writeAsyncResultFile(filePath, payload as Record<string, unknown>)
|
|
4045
|
+
: writeAtomicJson(filePath, payload),
|
|
4046
|
+
});
|
|
4047
|
+
let initialPersistenceComplete = false;
|
|
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;
|
|
4982
4061
|
status.lastUpdate = Date.now();
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4062
|
+
if (!initialPersistenceComplete) {
|
|
4063
|
+
writeAtomicJson(statusPath, status);
|
|
4064
|
+
initialPersistenceComplete = true;
|
|
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);
|
|
4987
4084
|
}
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
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;
|
|
5000
4096
|
if (status.steps) {
|
|
5001
|
-
|
|
5002
|
-
|
|
4097
|
+
liveJob.steps = status.steps.map((step, index) => ({ ...step, index }));
|
|
4098
|
+
liveJob.agents = status.steps.map((step) => step.agent);
|
|
5003
4099
|
} else {
|
|
5004
|
-
delete
|
|
5005
|
-
delete
|
|
4100
|
+
delete liveJob.steps;
|
|
4101
|
+
delete liveJob.agents;
|
|
5006
4102
|
}
|
|
5007
|
-
|
|
4103
|
+
liveJob.workflow = status.workflow;
|
|
5008
4104
|
}
|
|
5009
4105
|
};
|
|
5010
4106
|
const writeWorkflowResult = (payload: Record<string, unknown>): boolean => {
|
|
5011
4107
|
try {
|
|
5012
|
-
|
|
4108
|
+
runPersistence.write(resultPath, payload);
|
|
5013
4109
|
return true;
|
|
5014
4110
|
} catch (error) {
|
|
5015
4111
|
const message = `Failed to write async workflow result ${resultPath}: ${error instanceof Error ? error.message : String(error)}`;
|
|
@@ -5039,20 +4135,33 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5039
4135
|
status.toolCount = toolCounts.length > 0 ? toolCounts.reduce((total, count) => total + count, 0) : undefined;
|
|
5040
4136
|
status.currentStep = runningSteps.length === 1 ? steps.indexOf(runningSteps[0]!) : undefined;
|
|
5041
4137
|
};
|
|
5042
|
-
const workflowJob: AsyncJobState = { asyncId: workflowRunId, asyncDir, toolCallId, cwd: workflowCwd, 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 };
|
|
5043
4139
|
deps.state.asyncJobs.set(workflowRunId, workflowJob);
|
|
5044
4140
|
deps.state.fleetJobs ??= new Map();
|
|
5045
4141
|
deps.state.fleetJobs.set(workflowRunId, workflowJob);
|
|
5046
|
-
|
|
4142
|
+
try {
|
|
4143
|
+
persist();
|
|
4144
|
+
} catch (error) {
|
|
4145
|
+
deps.state.workflowControllers?.delete(workflowRunId);
|
|
4146
|
+
deps.state.asyncJobs.delete(workflowRunId);
|
|
4147
|
+
deps.state.fleetJobs?.delete(workflowRunId);
|
|
4148
|
+
workflowCapacity?.rollback();
|
|
4149
|
+
indexPersistence.dispose();
|
|
4150
|
+
return { content: [{ type: "text", text: `Failed to create async workflow storage: ${error instanceof Error ? error.message : String(error)}` }], isError: true, details: { mode: "workflow", results: [] } };
|
|
4151
|
+
}
|
|
5047
4152
|
appendWorkflowEvent({ type: "subagent.workflow.started" });
|
|
5048
4153
|
const { workflowScript, async: _workflowAsync, chatProgress: _chatProgress, ...workflowRequest } = requestParams;
|
|
5049
4154
|
void Promise.resolve().then(async () => {
|
|
4155
|
+
const workflowDeadlineAt = timeout === undefined ? undefined : Date.now() + timeout;
|
|
5050
4156
|
const workflowResults: SingleResult[] = [];
|
|
4157
|
+
const workflowChildRunIds = new Map<string, string>();
|
|
5051
4158
|
const { action: _action, agent: _agent, task: _task, resume: _resume, tasks: _tasks, chain: _chain, concurrency: _concurrency, foregroundOnly: _foregroundOnly, clarify: _clarify, timeoutMs: _timeoutMs, maxRuntimeMs: _maxRuntimeMs, usageBudget: _usageBudget, missionId: _missionId, mission: _mission, ...workflowChildDefaults } = workflowRequest;
|
|
5052
4159
|
const workflowOutput = typeof workflowChildDefaults.output === "string" || typeof workflowChildDefaults.output === "boolean" ? workflowChildDefaults.output : undefined;
|
|
5053
4160
|
const configuredOutputBaseDir = resolveConfiguredSingleRunOutputBaseDir(deps);
|
|
5054
4161
|
const workflowAggregateOutputPath = resolveWorkflowAggregateOutputPath(workflowOutput, ctx.cwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, workflowRunId), configuredOutputBaseDir);
|
|
5055
4162
|
const claimedOutputPaths = new Map<string, string>();
|
|
4163
|
+
const childOutputOverrides = new Map<string, string>();
|
|
4164
|
+
const producedChildOutputPaths = new Set<string>();
|
|
5056
4165
|
const workflowSteps = new Map<string, NonNullable<AsyncStatus["steps"]>[number]>();
|
|
5057
4166
|
let projectedTraceLength = 0;
|
|
5058
4167
|
let projectedTraceTail: NonNullable<Details["workflow"]>["trace"][number] | undefined;
|
|
@@ -5114,7 +4223,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5114
4223
|
projectedTraceLength = trace.length;
|
|
5115
4224
|
projectedTraceTail = trace.at(-1);
|
|
5116
4225
|
projectWorkflowActivity();
|
|
5117
|
-
persist();
|
|
4226
|
+
persist({ tolerateStatusWriteFailure: true });
|
|
5118
4227
|
appendWorkflowEvent({ type: "subagent.workflow.trace", trace });
|
|
5119
4228
|
};
|
|
5120
4229
|
try {
|
|
@@ -5122,20 +4231,20 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5122
4231
|
script: workflowScript,
|
|
5123
4232
|
timeoutMs: timeout,
|
|
5124
4233
|
signal: controller.signal,
|
|
5125
|
-
prompts: workflowPrompts,
|
|
5126
4234
|
...(workflowState ? { state: workflowState } : {}),
|
|
5127
4235
|
onTrace: updateTrace,
|
|
5128
4236
|
admit: (calls) => {
|
|
5129
|
-
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 });
|
|
5130
4238
|
if (outputClaims.error) throw new Error(outputClaims.error);
|
|
5131
4239
|
status.runFanoutBudget = claimRunFanoutBatch(workflowFanoutBudget, calls.map(({ key }) => `workflow[${key}]`));
|
|
5132
4240
|
if (outputClaims.claims) applyWorkflowChildOutputClaims(claimedOutputPaths, outputClaims.claims);
|
|
5133
|
-
|
|
4241
|
+
if (outputClaims.overrides) for (const [key, output] of outputClaims.overrides) childOutputOverrides.set(key, output);
|
|
4242
|
+
persist({ tolerateStatusWriteFailure: true });
|
|
5134
4243
|
},
|
|
5135
4244
|
onEmit: (emits) => {
|
|
5136
4245
|
// Each emit is validated at the host boundary in runWorkflowScript before onEmit fires.
|
|
5137
4246
|
status.workflow = { ...(status.workflow ?? { trace: [], console: [] }), emits };
|
|
5138
|
-
persist();
|
|
4247
|
+
persist({ tolerateStatusWriteFailure: true });
|
|
5139
4248
|
appendWorkflowEvent({ type: "subagent.workflow.emit", value: emits.at(-1) });
|
|
5140
4249
|
},
|
|
5141
4250
|
launch: async (key, childParams, workflowSignal, admission) => {
|
|
@@ -5153,7 +4262,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5153
4262
|
});
|
|
5154
4263
|
const result = await runMissionWorkflowChild(missionBinding, workflowRunId, key, childPhase, () => {
|
|
5155
4264
|
const childRequest = bindMissionWorkflowChildAsyncLaunch(
|
|
5156
|
-
{ ...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 } }), 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 },
|
|
5157
4266
|
missionBinding,
|
|
5158
4267
|
deps.asyncByDefault,
|
|
5159
4268
|
);
|
|
@@ -5164,7 +4273,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5164
4273
|
step.sessionFile = launch.sessionFile;
|
|
5165
4274
|
step.async = launch.async;
|
|
5166
4275
|
if (launch.runId) step.runId = launch.runId;
|
|
5167
|
-
persist();
|
|
4276
|
+
persist({ tolerateStatusWriteFailure: true });
|
|
5168
4277
|
}
|
|
5169
4278
|
recordMissionWorkflowChild(missionBinding, workflowRunId, key, { status: "running", agent: launch.agent, ...(launch.sessionFile ? { sessionPath: launch.sessionFile } : {}) });
|
|
5170
4279
|
});
|
|
@@ -5187,7 +4296,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5187
4296
|
step.thinking = progress.thinking;
|
|
5188
4297
|
step.error = progress.error;
|
|
5189
4298
|
projectWorkflowActivity();
|
|
5190
|
-
persist();
|
|
4299
|
+
persist({ tolerateStatusWriteFailure: true });
|
|
5191
4300
|
recordMissionWorkflowChild(missionBinding, workflowRunId, key, {
|
|
5192
4301
|
status: step.status,
|
|
5193
4302
|
heartbeat: { status: step.status, ...(childPhase ? { phase: childPhase } : {}) },
|
|
@@ -5195,7 +4304,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5195
4304
|
}, ctx, preserveActiveSession);
|
|
5196
4305
|
});
|
|
5197
4306
|
workflowResults.push(...result.details.results);
|
|
4307
|
+
for (const childResult of result.details.results) {
|
|
4308
|
+
if (childResult.savedOutputPath) producedChildOutputPaths.add(childResult.savedOutputPath);
|
|
4309
|
+
}
|
|
5198
4310
|
const child = workflowChildResult(key, result);
|
|
4311
|
+
if (child.runId) workflowChildRunIds.set(key, child.runId);
|
|
5199
4312
|
const step = status.steps?.find((candidate) => candidate.workflowKey === key);
|
|
5200
4313
|
if (step) {
|
|
5201
4314
|
step.async = Boolean(result.details.asyncId || result.details.asyncDir);
|
|
@@ -5219,16 +4332,18 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5219
4332
|
return child;
|
|
5220
4333
|
},
|
|
5221
4334
|
status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession)),
|
|
4335
|
+
steer: (key, message, options, workflowSignal) => steerWorkflowChildByKey({ state: deps.state, workflowRunId, key, message, options, signal: workflowSignal, resolveRunId: () => workflowChildRunIds.get(key) }),
|
|
5222
4336
|
});
|
|
5223
4337
|
const returnPreview = formatWorkflowValue(workflow.value).slice(0, 1_000);
|
|
5224
4338
|
const emitPreview = workflow.emits.length > 0 ? ` Emitted: ${workflow.emits.map(formatWorkflowValue).join(", ").slice(0, 1_000)}` : "";
|
|
5225
4339
|
const summary = `Workflow completed with ${workflow.children.length} child run(s). Return: ${returnPreview}${emitPreview} Trace: ${workflow.trace.length} event(s).`;
|
|
5226
|
-
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, summary);
|
|
4340
|
+
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, summary, producedChildOutputPaths);
|
|
5227
4341
|
const resultSummary = appendWorkflowOutputWarning(summary, outputWarning);
|
|
5228
4342
|
const workflowUsage = sumResultsUsage(workflowResults);
|
|
5229
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) };
|
|
5230
|
-
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, 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;
|
|
5231
4345
|
persist();
|
|
4346
|
+
persistClosed = true;
|
|
5232
4347
|
appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(status.error ? { error: status.error } : {}) });
|
|
5233
4348
|
} catch (error) {
|
|
5234
4349
|
const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
|
|
@@ -5239,17 +4354,31 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5239
4354
|
const state = stopped ? "stopped" : pauseForDetached ? "paused" : "failed";
|
|
5240
4355
|
for (const step of status.steps ?? []) {
|
|
5241
4356
|
if (step.workflowKey && detachedChildKeys.has(step.workflowKey)) {
|
|
4357
|
+
if (step.status === "completed" || step.status === "complete" || step.status === "failed") continue;
|
|
5242
4358
|
step.status = "paused";
|
|
5243
4359
|
step.activityState = "needs_attention";
|
|
4360
|
+
} else if (pauseForDetached && step.status === "running") {
|
|
4361
|
+
step.status = "stopped";
|
|
4362
|
+
step.stopped = true;
|
|
4363
|
+
delete step.activityState;
|
|
5244
4364
|
}
|
|
5245
4365
|
}
|
|
5246
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 } });
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
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;
|
|
5250
4377
|
persist();
|
|
5251
|
-
|
|
4378
|
+
persistClosed = true;
|
|
4379
|
+
appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(status.error ? { error: status.error } : {}), ...(status.activityState ? { activityState: status.activityState } : {}) });
|
|
5252
4380
|
} finally {
|
|
4381
|
+
persistClosed = true;
|
|
5253
4382
|
deps.state.workflowControllers?.delete(workflowRunId);
|
|
5254
4383
|
deps.state.activeAsyncCapacity = workflowCapacity?.reconcile(new Set(deps.state.workflowControllers?.keys() ?? []))
|
|
5255
4384
|
?? deps.state.activeAsyncCapacity;
|
|
@@ -5265,8 +4394,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5265
4394
|
const configuredOutputBaseDir = resolveConfiguredSingleRunOutputBaseDir(deps);
|
|
5266
4395
|
const workflowAggregateOutputPath = resolveWorkflowAggregateOutputPath(workflowOutput, ctx.cwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, _id), configuredOutputBaseDir);
|
|
5267
4396
|
const claimedOutputPaths = new Map<string, string>();
|
|
4397
|
+
const childOutputOverrides = new Map<string, string>();
|
|
4398
|
+
const producedChildOutputPaths = new Set<string>();
|
|
5268
4399
|
const workflowResults: SingleResult[] = [];
|
|
4400
|
+
const workflowChildRunIds = new Map<string, string>();
|
|
5269
4401
|
let liveWorkflow: NonNullable<Details["workflow"]> = { trace: [], emits: [], console: [] };
|
|
4402
|
+
const workflowDeadlineAt = timeout === undefined ? undefined : Date.now() + timeout;
|
|
5270
4403
|
const sendWorkflowProgress = () => {
|
|
5271
4404
|
const update = workflowChatProgressUpdate(_id, chatProgress, liveWorkflow);
|
|
5272
4405
|
if (update) onUpdate?.(update);
|
|
@@ -5276,17 +4409,17 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5276
4409
|
script: requestParams.workflowScript,
|
|
5277
4410
|
timeoutMs: timeout,
|
|
5278
4411
|
signal,
|
|
5279
|
-
prompts: workflowPrompts,
|
|
5280
4412
|
...(workflowState ? { state: workflowState } : {}),
|
|
5281
4413
|
onTrace: (trace) => {
|
|
5282
4414
|
liveWorkflow = { ...liveWorkflow, trace };
|
|
5283
4415
|
sendWorkflowProgress();
|
|
5284
4416
|
},
|
|
5285
4417
|
admit: (calls) => {
|
|
5286
|
-
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 });
|
|
5287
4419
|
if (outputClaims.error) throw new Error(outputClaims.error);
|
|
5288
4420
|
claimRunFanoutBatch(workflowFanoutBudget, calls.map(({ key }) => `workflow[${key}]`));
|
|
5289
4421
|
if (outputClaims.claims) applyWorkflowChildOutputClaims(claimedOutputPaths, outputClaims.claims);
|
|
4422
|
+
if (outputClaims.overrides) for (const [key, output] of outputClaims.overrides) childOutputOverrides.set(key, output);
|
|
5290
4423
|
},
|
|
5291
4424
|
onEmit: (emits) => {
|
|
5292
4425
|
liveWorkflow = { ...liveWorkflow, emits };
|
|
@@ -5307,7 +4440,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5307
4440
|
});
|
|
5308
4441
|
const result = await runMissionWorkflowChild(missionBinding, _id, key, childPhase, () => {
|
|
5309
4442
|
const childRequest = bindMissionWorkflowChildAsyncLaunch(
|
|
5310
|
-
{ ...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 } }), 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 },
|
|
5311
4444
|
missionBinding,
|
|
5312
4445
|
deps.asyncByDefault,
|
|
5313
4446
|
);
|
|
@@ -5327,8 +4460,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5327
4460
|
}, ctx, preserveActiveSession);
|
|
5328
4461
|
});
|
|
5329
4462
|
workflowResults.push(...result.details.results);
|
|
4463
|
+
for (const childResult of result.details.results) {
|
|
4464
|
+
if (childResult.savedOutputPath) producedChildOutputPaths.add(childResult.savedOutputPath);
|
|
4465
|
+
}
|
|
5330
4466
|
if (result.details.asyncDir && missionBinding) writeMissionAsyncBinding(result.details.asyncDir, missionBinding);
|
|
5331
4467
|
const child = workflowChildResult(key, result);
|
|
4468
|
+
if (child.runId) workflowChildRunIds.set(key, child.runId);
|
|
5332
4469
|
const childStatus = missionWorkflowChildStatus(result);
|
|
5333
4470
|
recordMissionWorkflowChild(missionBinding, _id, key, {
|
|
5334
4471
|
status: childStatus,
|
|
@@ -5342,6 +4479,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5342
4479
|
return child;
|
|
5343
4480
|
},
|
|
5344
4481
|
status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession)),
|
|
4482
|
+
steer: (key, message, options, workflowSignal) => steerWorkflowChildByKey({ state: deps.state, workflowRunId: _id, key, message, options, signal: workflowSignal, resolveRunId: () => workflowChildRunIds.get(key) }),
|
|
5345
4483
|
});
|
|
5346
4484
|
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}` : ""}`);
|
|
5347
4485
|
const sections = ["Workflow completed.", `Return:\n${formatWorkflowValue(workflow.value)}`];
|
|
@@ -5349,7 +4487,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5349
4487
|
if (workflow.console.length > 0) sections.push(`Console:\n${workflow.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
|
|
5350
4488
|
if (traceLines.length > 0) sections.push(`Call trace:\n${traceLines.join("\n")}`);
|
|
5351
4489
|
const workflowText = sections.join("\n\n");
|
|
5352
|
-
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, workflowText);
|
|
4490
|
+
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, workflowText, producedChildOutputPaths);
|
|
5353
4491
|
const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
|
|
5354
4492
|
return attachWorkflowMission(withRunFanoutBudget({
|
|
5355
4493
|
content: [{ type: "text", text: displayText }],
|
|
@@ -5364,7 +4502,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5364
4502
|
if (partial.console.length > 0) sections.push(`Console:\n${partial.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
|
|
5365
4503
|
if (traceLines.length > 0) sections.push(`Call trace:\n${traceLines.join("\n")}`);
|
|
5366
4504
|
const workflowText = sections.join("\n\n");
|
|
5367
|
-
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, workflowText);
|
|
4505
|
+
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, workflowText, producedChildOutputPaths);
|
|
5368
4506
|
const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
|
|
5369
4507
|
return attachWorkflowMission(withRunFanoutBudget({
|
|
5370
4508
|
content: [{ type: "text", text: displayText }],
|
|
@@ -5373,7 +4511,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5373
4511
|
}, workflowFanoutBudget));
|
|
5374
4512
|
}
|
|
5375
4513
|
}
|
|
5376
|
-
const directParams =
|
|
4514
|
+
const directParams = requestParams;
|
|
5377
4515
|
const requestCwd = resolveRequestedCwd(ctx.cwd, directParams.cwd);
|
|
5378
4516
|
const paramsWithResolvedCwd = directParams.cwd === undefined ? directParams : { ...directParams, cwd: requestCwd };
|
|
5379
4517
|
const action = paramsWithResolvedCwd.action;
|
|
@@ -5690,61 +4828,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5690
4828
|
}
|
|
5691
4829
|
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots })));
|
|
5692
4830
|
}
|
|
5693
|
-
|
|
5694
|
-
if (action === "approve-checkpoint" || action === "reject-checkpoint") {
|
|
5695
|
-
deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
5696
|
-
const targetRunId = paramsWithResolvedCwd.runId ?? paramsWithResolvedCwd.id;
|
|
5697
|
-
if (!targetRunId && !paramsWithResolvedCwd.dir) {
|
|
5698
|
-
return { content: [{ type: "text", text: `action='${action}' requires id or dir.` }], isError: true, details: { mode: "management", results: [] } };
|
|
5699
|
-
}
|
|
5700
|
-
try {
|
|
5701
|
-
const resolved = targetRunId ? resolveSubagentRunId(targetRunId, omitUndefinedProperties({ state: deps.state, nested: nestedResolutionScopeForExecutor(deps) })) : undefined;
|
|
5702
|
-
const decision = action === "approve-checkpoint" ? "approved" : "rejected";
|
|
5703
|
-
if (resolved?.kind === "foreground") {
|
|
5704
|
-
const run = deps.state.foregroundRuns?.get(resolved.id);
|
|
5705
|
-
if (!run?.checkpoint || run.checkpoint.status !== "pending") {
|
|
5706
|
-
return { content: [{ type: "text", text: `Run '${resolved.id}' is not paused at an approval checkpoint.` }], isError: true, details: { mode: "management", results: [] } };
|
|
5707
|
-
}
|
|
5708
|
-
if (deps.state.currentSessionId && run.sessionId !== deps.state.currentSessionId) {
|
|
5709
|
-
return { content: [{ type: "text", text: `Run '${resolved.id}' was not found in the active session.` }], isError: true, details: { mode: "management", results: [] } };
|
|
5710
|
-
}
|
|
5711
|
-
const decidedAt = Date.now();
|
|
5712
|
-
const checkpoint: NonNullable<Details["checkpoint"]> = decision === "approved"
|
|
5713
|
-
? { ...run.checkpoint, status: "approved", approvedAt: decidedAt }
|
|
5714
|
-
: { ...run.checkpoint, status: "rejected", rejectedAt: decidedAt };
|
|
5715
|
-
run.checkpoint = checkpoint;
|
|
5716
|
-
run.updatedAt = decidedAt;
|
|
5717
|
-
return {
|
|
5718
|
-
content: [{ type: "text", text: `Checkpoint '${checkpoint.name}' ${decision} for foreground run ${resolved.id}.` }],
|
|
5719
|
-
details: { mode: "management", results: [], checkpoint },
|
|
5720
|
-
};
|
|
5721
|
-
}
|
|
5722
|
-
const location = paramsWithResolvedCwd.dir
|
|
5723
|
-
? resolveAsyncRunLocation(paramsWithResolvedCwd, DIRS.async, DIRS.results)
|
|
5724
|
-
: resolved?.kind === "async"
|
|
5725
|
-
? resolved.location
|
|
5726
|
-
: undefined;
|
|
5727
|
-
if (!location?.asyncDir) {
|
|
5728
|
-
return { content: [{ type: "text", text: `action='${action}' targets paused foreground or async checkpoints. No matching run found.` }], isError: true, details: { mode: "management", results: [] } };
|
|
5729
|
-
}
|
|
5730
|
-
const status = readStatus(location.asyncDir);
|
|
5731
|
-
const runId = status?.runId ?? location.resolvedId ?? targetRunId ?? path.basename(location.asyncDir);
|
|
5732
|
-
if (!status?.checkpoint || status.state !== "paused") {
|
|
5733
|
-
return { content: [{ type: "text", text: `Run '${runId}' is not paused at an approval checkpoint.` }], isError: true, details: { mode: "management", results: [] } };
|
|
5734
|
-
}
|
|
5735
|
-
if (deps.state.currentSessionId && status.sessionId !== deps.state.currentSessionId) {
|
|
5736
|
-
return { content: [{ type: "text", text: `Run '${runId}' was not found in the active session.` }], isError: true, details: { mode: "management", results: [] } };
|
|
5737
|
-
}
|
|
5738
|
-
deliverCheckpointDecisionRequest({ asyncDir: location.asyncDir, decision, source: "subagent-action", ...(paramsWithResolvedCwd.message ? { reason: paramsWithResolvedCwd.message } : {}) });
|
|
5739
|
-
return {
|
|
5740
|
-
content: [{ type: "text", text: `Checkpoint '${status.checkpoint.name}' ${decision} for run ${runId}.` }],
|
|
5741
|
-
details: { mode: "management", results: [], checkpoint: { ...status.checkpoint, status: decision } },
|
|
5742
|
-
};
|
|
5743
|
-
} catch (error) {
|
|
5744
|
-
const text = error instanceof Error ? error.message : String(error);
|
|
5745
|
-
return { content: [{ type: "text", text }], isError: true, details: { mode: "management", results: [] } };
|
|
5746
|
-
}
|
|
5747
|
-
}
|
|
5748
4831
|
if (action === "resume") {
|
|
5749
4832
|
return resumeAsyncRun(omitUndefinedProperties({ params: paramsWithResolvedCwd, requestCwd, ctx, deps, parentModel: requestParentModel, signal }));
|
|
5750
4833
|
}
|
|
@@ -6033,14 +5116,23 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6033
5116
|
const parentSessionFile = ctx.sessionManager.getSessionFile() ?? null;
|
|
6034
5117
|
const discovered = deps.discoverAgents(effectiveCwd, scope);
|
|
6035
5118
|
const discoveredAgents = discovered.agents;
|
|
6036
|
-
const canonicalParams = canonicalizeExecutionParams(effectiveParams, discoveredAgents);
|
|
5119
|
+
const canonicalParams = canonicalizeExecutionParams(effectiveParams, discoveredAgents, discovered.agentDiagnostics);
|
|
6037
5120
|
if (canonicalParams.error) return buildRequestedModeError(effectiveParams, canonicalParams.error);
|
|
6038
5121
|
effectiveParams = canonicalParams.params!;
|
|
6039
5122
|
const modelScope = discovered.modelScope;
|
|
6040
5123
|
effectiveParams = applySingleAgentLaunchDefaults(effectiveParams, discoveredAgents);
|
|
6041
5124
|
const turnBudget = resolveTurnBudgetConfig(effectiveParams.turnBudget ?? deps.config.turnBudget);
|
|
6042
5125
|
if (turnBudget.error) return buildRequestedModeError(effectiveParams, turnBudget.error);
|
|
6043
|
-
|
|
5126
|
+
// An agent-level defaultContext is a preference, unlike an explicit request.
|
|
5127
|
+
// Prefer fork only when the parent session is persisted and has a current leaf;
|
|
5128
|
+
// otherwise use fresh immediately instead of launching a guaranteed-to-fail fork.
|
|
5129
|
+
// Explicit context:"fork" remains strict.
|
|
5130
|
+
const contextPolicy = resolveAgentDefaultContextPolicy(
|
|
5131
|
+
effectiveParams,
|
|
5132
|
+
discoveredAgents,
|
|
5133
|
+
deps.config.defaultSubagentContext,
|
|
5134
|
+
canPreferFork(ctx.sessionManager),
|
|
5135
|
+
);
|
|
6044
5136
|
effectiveParams = contextPolicy.params;
|
|
6045
5137
|
const sessionName = resolveIntercomSessionTarget(deps.pi.getSessionName(), ctx.sessionManager.getSessionId());
|
|
6046
5138
|
const intercomBridge = resolveIntercomBridge({
|
|
@@ -6052,7 +5144,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6052
5144
|
const agents = intercomBridge.active
|
|
6053
5145
|
? discoveredAgents.map((agent) => applyIntercomBridgeToAgent(agent, intercomBridge))
|
|
6054
5146
|
: discoveredAgents;
|
|
6055
|
-
const runId = randomUUID()
|
|
5147
|
+
const runId = randomUUID();
|
|
6056
5148
|
const inheritedNestedRoute = resolveInheritedNestedRouteFromEnv();
|
|
6057
5149
|
const nestedParentAddress = inheritedNestedRoute ? resolveNestedParentAddressFromEnv() : undefined;
|
|
6058
5150
|
const shareEnabled = effectiveParams.share === true;
|
|
@@ -6086,30 +5178,33 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6086
5178
|
|
|
6087
5179
|
let forkSessionFileForIndex: (idx?: number) => string | undefined = () => undefined;
|
|
6088
5180
|
let forkThinkingOverrideForIndex: (idx?: number) => AgentConfig["thinking"] | undefined = () => undefined;
|
|
6089
|
-
let prepareForkThinking = (_agentName: string, _index: number, _modelOverride?: string): void => {};
|
|
5181
|
+
let prepareForkThinking = (_agentName: string, _index: number, _modelOverride?: string, _modelOverrideFromParent?: boolean): void => {};
|
|
6090
5182
|
const forkThinkingRequirements = new Map<number, boolean>();
|
|
6091
5183
|
const forkThinkingDowngrades = new Map<number, string>();
|
|
6092
5184
|
try {
|
|
6093
5185
|
const forkAvailableModels = contextPolicy.usesFork ? ctx.modelRegistry.getAvailable().map(toModelInfo) : [];
|
|
6094
5186
|
const parentModel = requestParentModel;
|
|
6095
|
-
prepareForkThinking = (agentName, index, modelOverride) => {
|
|
5187
|
+
prepareForkThinking = (agentName, index, modelOverride, modelOverrideFromParent) => {
|
|
6096
5188
|
const agentConfig = agents.find((agent) => agent.name === agentName);
|
|
6097
|
-
if (agentConfig?.runner?.type === "external-cli") {
|
|
5189
|
+
if (agentConfig?.runner?.type === "external-cli" || agentConfig?.runner?.type === "external-job") {
|
|
6098
5190
|
forkThinkingRequirements.set(index, true);
|
|
6099
5191
|
return;
|
|
6100
5192
|
}
|
|
6101
|
-
const primaryModel =
|
|
6102
|
-
modelOverride
|
|
6103
|
-
|
|
6104
|
-
|
|
6105
|
-
|
|
6106
|
-
|
|
6107
|
-
|
|
5193
|
+
const primaryModel = modelOverrideFromParent
|
|
5194
|
+
? modelOverride
|
|
5195
|
+
: resolveEffectiveSubagentModel(
|
|
5196
|
+
modelOverride,
|
|
5197
|
+
agentConfig?.model,
|
|
5198
|
+
parentModel,
|
|
5199
|
+
forkAvailableModels,
|
|
5200
|
+
parentModel?.provider,
|
|
5201
|
+
);
|
|
6108
5202
|
const candidates = buildModelCandidates(
|
|
6109
5203
|
primaryModel,
|
|
6110
5204
|
agentConfig?.fallbackModels,
|
|
6111
5205
|
forkAvailableModels,
|
|
6112
5206
|
parentModel?.provider,
|
|
5207
|
+
{ primaryModelFromParent: modelOverrideFromParent ?? inheritsParentModel(modelOverride, agentConfig?.model, parentModel) },
|
|
6113
5208
|
);
|
|
6114
5209
|
forkThinkingRequirements.set(
|
|
6115
5210
|
index,
|
|
@@ -6135,9 +5230,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6135
5230
|
: (effectiveParams.chain ?? []).flatMap((step) => getStepAgents(step as ChainStep));
|
|
6136
5231
|
const externalAgent = selectedAgentNames
|
|
6137
5232
|
.map((name) => agents.find((agent) => agent.name === name))
|
|
6138
|
-
.find((agent) => agent?.runner?.type === "external-cli");
|
|
5233
|
+
.find((agent) => agent?.runner?.type === "external-cli" || agent?.runner?.type === "external-job");
|
|
6139
5234
|
if (externalAgent && (!effectiveAsync || effectiveParams.foregroundOnly === true)) {
|
|
6140
|
-
return buildRequestedModeError(effectiveParams, `Agent '${externalAgent.name}' uses runner.type='
|
|
5235
|
+
return buildRequestedModeError(effectiveParams, `Agent '${externalAgent.name}' uses runner.type='${externalAgent.runner?.type}', which currently supports async/background execution only. Omit async or pass async:true; clarify and foregroundOnly are unsupported.`);
|
|
6141
5236
|
}
|
|
6142
5237
|
const foregroundTimeout = resolveSingleAgentLaunchTimeout(
|
|
6143
5238
|
effectiveParams,
|
|
@@ -6221,20 +5316,20 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6221
5316
|
}
|
|
6222
5317
|
const sessionDirForIndex = (idx?: number) =>
|
|
6223
5318
|
path.join(sessionRoot, `run-${idx ?? 0}`);
|
|
6224
|
-
const forkSessionFileForTask: ForkSessionFileForTask = (agentName, idx = 0, modelOverride) => {
|
|
5319
|
+
const forkSessionFileForTask: ForkSessionFileForTask = (agentName, idx = 0, modelOverride, modelOverrideFromParent) => {
|
|
6225
5320
|
if (!shouldForkAgent(contextPolicy, agentName)) return undefined;
|
|
6226
|
-
prepareForkThinking(agentName, idx, modelOverride);
|
|
5321
|
+
prepareForkThinking(agentName, idx, modelOverride, modelOverrideFromParent);
|
|
6227
5322
|
return forkSessionFileForIndex(idx);
|
|
6228
5323
|
};
|
|
6229
|
-
const forkThinkingOverrideForTask: ForkThinkingOverrideForTask = (agentName, idx = 0, modelOverride) => {
|
|
5324
|
+
const forkThinkingOverrideForTask: ForkThinkingOverrideForTask = (agentName, idx = 0, modelOverride, modelOverrideFromParent) => {
|
|
6230
5325
|
if (!shouldForkAgent(contextPolicy, agentName)) return delegatedThinkingOverride;
|
|
6231
|
-
prepareForkThinking(agentName, idx, modelOverride);
|
|
5326
|
+
prepareForkThinking(agentName, idx, modelOverride, modelOverrideFromParent);
|
|
6232
5327
|
const override = forkThinkingOverrideForIndex(idx);
|
|
6233
5328
|
if (override === "off") forkThinkingDowngrades.set(idx, agentName);
|
|
6234
5329
|
return override ?? delegatedThinkingOverride;
|
|
6235
5330
|
};
|
|
6236
|
-
const childSessionFileForTask: ForkSessionFileForTask = (agentName, idx, modelOverride) =>
|
|
6237
|
-
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");
|
|
6238
5333
|
const childSessionFileForIndex = (idx?: number) =>
|
|
6239
5334
|
path.join(sessionDirForIndex(idx), "session.jsonl");
|
|
6240
5335
|
try {
|
|
@@ -6416,6 +5511,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6416
5511
|
};
|
|
6417
5512
|
deps.state.foregroundControls.set(runId, foregroundControl);
|
|
6418
5513
|
deps.state.lastForegroundControlId = runId;
|
|
5514
|
+
deps.activateSupervisorTransport?.();
|
|
5515
|
+
deps.refreshResultDelivery?.();
|
|
6419
5516
|
}
|
|
6420
5517
|
|
|
6421
5518
|
const writeNestedForegroundEvent = (type: "subagent.nested.started" | "subagent.nested.completed", result?: AgentToolResult<Details>): void => {
|
|
@@ -6532,16 +5629,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6532
5629
|
nestedForegroundStarted = true;
|
|
6533
5630
|
}
|
|
6534
5631
|
const runFanoutAnnotateContent = !delegatedExecution;
|
|
6535
|
-
if (hasChain && effectiveParams.chain) {
|
|
6536
|
-
const result = await runChainPath(execData, deps);
|
|
6537
|
-
writeNestedForegroundEvent("subagent.nested.completed", result);
|
|
6538
|
-
return attachMission(withRunFanoutBudget(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary), runFanoutBudget, { annotateContent: runFanoutAnnotateContent }));
|
|
6539
|
-
}
|
|
6540
|
-
if (hasTasks && effectiveParams.tasks) {
|
|
6541
|
-
const result = await runParallelPath(execData, deps);
|
|
6542
|
-
writeNestedForegroundEvent("subagent.nested.completed", result);
|
|
6543
|
-
return attachMission(withRunFanoutBudget(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary), runFanoutBudget, { annotateContent: runFanoutAnnotateContent }));
|
|
6544
|
-
}
|
|
6545
5632
|
if (hasSingle) {
|
|
6546
5633
|
const result = await runSinglePath(execData, deps);
|
|
6547
5634
|
writeNestedForegroundEvent("subagent.nested.completed", result);
|
|
@@ -6598,7 +5685,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6598
5685
|
onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
|
|
6599
5686
|
ctx: ExtensionContext,
|
|
6600
5687
|
): Promise<AgentToolResult<Details>> => {
|
|
6601
|
-
const normalized = normalizePublicSubagentExecution(params);
|
|
5688
|
+
const normalized = normalizePublicSubagentExecution(params, { asyncByDefault: deps.asyncByDefault });
|
|
6602
5689
|
if (!normalized.ok) {
|
|
6603
5690
|
return Promise.resolve({ content: [{ type: "text", text: normalized.error }], isError: true, details: { mode: normalized.mode, results: [] } });
|
|
6604
5691
|
}
|