pi-subagents 0.49.0 → 0.51.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 +97 -0
- package/agents/gpt-pro.md +17 -0
- package/agents/oracle.md +7 -5
- package/agents/researcher.md +1 -1
- package/agents/reviewer.md +2 -2
- package/agents/scout.md +1 -1
- package/agents/worker.md +1 -1
- package/async-retention-discovery-worker.mjs +180 -0
- package/docs/agents.md +37 -2
- package/docs/configuration.md +76 -14
- package/docs/extension-api.md +78 -1
- package/docs/missions.md +1 -1
- package/docs/observability.md +20 -4
- package/docs/tool-reference.md +55 -39
- package/docs/workflows.md +171 -5
- package/package.json +4 -2
- package/skills/pi-subagents/SKILL.md +5 -4
- package/skills/pi-subagents/references/constraints-and-recipes.md +9 -6
- package/skills/pi-subagents/references/execution-controls.md +22 -18
- package/skills/pi-subagents/references/management-authoring-rpc.md +5 -5
- package/skills/pi-subagents/references/prompting-and-roles.md +33 -17
- package/src/agents/agent-management.ts +100 -345
- package/src/agents/agent-serializer.ts +2 -0
- package/src/agents/agents.ts +135 -25
- package/src/api/external-job-provider.ts +185 -0
- package/src/api/external-runs.ts +174 -84
- package/src/api/preflight.ts +42 -11
- package/src/api/shared-types.ts +2 -0
- package/src/extension/config.ts +36 -3
- package/src/extension/doctor.ts +3 -6
- package/src/extension/fanout-child.ts +2 -2
- package/src/extension/index.ts +210 -90
- package/src/extension/public-execution.ts +31 -2
- package/src/extension/rpc.ts +5 -1
- package/src/extension/schemas.ts +14 -36
- package/src/extension/tool-description.ts +37 -24
- package/src/inspectors/herdr/actions.ts +5 -9
- package/src/inspectors/herdr/inspector-runner.ts +2 -1
- package/src/inspectors/herdr/project-panes.ts +4 -8
- package/src/inspectors/herdr/shell-command.ts +16 -0
- package/src/intercom/intercom-bridge.ts +2 -3
- package/src/intercom/native-supervisor-channel.ts +49 -51
- package/src/missions/goal-driver.ts +3 -1
- package/src/missions/lifecycle.ts +6 -1
- package/src/missions/store.ts +4 -9
- package/src/profiles/profiles.ts +3 -1
- package/src/runs/background/active-run-index.ts +94 -1
- package/src/runs/background/async-execution.ts +98 -38
- package/src/runs/background/async-job-tracker.ts +21 -4
- package/src/runs/background/async-resume.ts +30 -17
- package/src/runs/background/async-retention.ts +888 -0
- package/src/runs/background/async-status-snapshot.ts +277 -0
- package/src/runs/background/async-status.ts +47 -56
- package/src/runs/background/chain-append.ts +3 -33
- package/src/runs/background/chain-root-attachment.ts +2 -2
- package/src/runs/background/completion-replay.ts +11 -1
- package/src/runs/background/control-channel.ts +14 -68
- package/src/runs/background/fleet-view.ts +3 -1
- package/src/runs/background/index-segment.ts +59 -0
- package/src/runs/background/notify.ts +3 -1
- package/src/runs/background/result-files.ts +505 -0
- package/src/runs/background/result-watcher.ts +250 -51
- package/src/runs/background/retained-children.ts +79 -20
- package/src/runs/background/run-id-query.ts +7 -0
- package/src/runs/background/run-id-resolver.ts +37 -29
- package/src/runs/background/run-status.ts +34 -20
- package/src/runs/background/scheduled-runs.ts +71 -27
- package/src/runs/background/stale-run-reconciler.ts +33 -16
- package/src/runs/background/steering.ts +11 -1
- package/src/runs/background/subagent-runner.ts +535 -161
- package/src/runs/background/subagent-wait.ts +9 -7
- package/src/runs/background/terminal-run-index.ts +129 -0
- package/src/runs/background/wait-completions.ts +22 -2
- package/src/runs/background/wait-subscriptions.ts +80 -1
- package/src/runs/foreground/async-dismiss-action.ts +2 -1
- package/src/runs/foreground/async-steering-action.ts +21 -14
- package/src/runs/foreground/execution.ts +221 -15
- package/src/runs/foreground/subagent-executor.ts +529 -1519
- package/src/runs/foreground/workflow-foreground-steering.ts +6 -5
- package/src/runs/shared/chain-outputs.ts +1 -3
- package/src/runs/shared/completion-guard.ts +96 -6
- package/src/runs/shared/external-cli-runner.ts +4 -0
- package/src/runs/shared/external-job-bridge.ts +450 -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 +34 -3
- package/src/runs/shared/nested-events.ts +66 -62
- package/src/runs/shared/orca-progress-tabs.ts +437 -0
- package/src/runs/shared/parallel-handoff.ts +46 -4
- package/src/runs/shared/parallel-utils.ts +6 -15
- package/src/runs/shared/permissions.ts +5 -1
- package/src/runs/shared/pi-args.ts +8 -1
- package/src/runs/shared/subagent-control.ts +41 -4
- package/src/runs/shared/subagent-prompt-runtime.ts +13 -15
- package/src/runs/shared/subagent-startup-retry.ts +12 -0
- package/src/runs/shared/tool-timeout.ts +93 -0
- package/src/runs/shared/workflow-graph.ts +1 -23
- package/src/runs/shared/worktree.ts +12 -1
- 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 +88 -27
- package/src/shared/utils.ts +8 -0
- package/src/shared/watch-strategy.ts +10 -0
- package/src/slash/slash-commands.ts +45 -28
- package/src/slash/slash-live-state.ts +3 -0
- package/src/tui/fleet-status.ts +160 -45
- package/src/tui/fleet.ts +186 -28
- package/src/tui/render.ts +41 -10
- package/src/workflows/chat-progress.ts +7 -5
- package/src/workflows/scripted-workflow.ts +424 -125
- package/src/runs/foreground/chain-clarify.ts +0 -1354
- package/src/runs/foreground/chain-execution.ts +0 -1565
|
@@ -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 { 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,
|
|
@@ -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";
|
|
@@ -87,9 +89,9 @@ import {
|
|
|
87
89
|
stripDetailsOutputsForIntercomReceipt,
|
|
88
90
|
} from "../../intercom/result-intercom.ts";
|
|
89
91
|
import { applySteeringRecoveryAgentConfig, buildRevivedAsyncTask, resolveAsyncResumeTarget, resolveAsyncRunLocation } from "../background/async-resume.ts";
|
|
90
|
-
import {
|
|
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,
|
|
@@ -101,6 +103,7 @@ import { stopAsyncRun } from "./async-stop-action.ts";
|
|
|
101
103
|
import { dismissRecoveredWorkflow } from "./async-dismiss-action.ts";
|
|
102
104
|
import { reconcileAsyncRun } from "../background/stale-run-reconciler.ts";
|
|
103
105
|
import { resolveAsyncRootResultPath, waitForImportedAsyncRoot } from "../background/chain-root-attachment.ts";
|
|
106
|
+
import { resultFilePath, writeAsyncResultFile } from "../background/result-files.ts";
|
|
104
107
|
import { attachRootChildrenToSteps, createNestedRoute, findNestedControlResult, resolveInheritedNestedRouteFromEnv, resolveNestedAsyncDir, resolveNestedParentAddressFromEnv, snapshotNestedEventFiles, updateForegroundNestedProjection, writeNestedControlRequest, writeNestedEvent, type NestedRunResolutionScope } from "../shared/nested-events.ts";
|
|
105
108
|
import { resolveSubagentRunId, type ResolvedSubagentRunId } from "../background/run-id-resolver.ts";
|
|
106
109
|
import { formatNestedRunStatusLines } from "../shared/nested-render.ts";
|
|
@@ -108,14 +111,13 @@ import { inspectSubagentStatus } from "../background/run-status.ts";
|
|
|
108
111
|
import { applyForceTopLevelAsyncOverride } from "../background/top-level-async.ts";
|
|
109
112
|
import { handleMissionAction, MISSION_ACTIONS } from "../../missions/actions.ts";
|
|
110
113
|
import { attachMissionToLaunchResult, prepareMissionLaunch, writeMissionAsyncBinding, type MissionLaunchBinding } from "../../missions/lifecycle.ts";
|
|
111
|
-
import { updateMission } from "../../missions/store.ts";
|
|
114
|
+
import { MissionNotFoundError, updateMission } from "../../missions/store.ts";
|
|
112
115
|
import type { MissionWorkflowChildUpdate } from "../../missions/types.ts";
|
|
113
116
|
import { createMissionWorkflowState } from "../../missions/workflow-state.ts";
|
|
114
117
|
import { resolveAuthorityDecision } from "../../policy/authority.ts";
|
|
115
118
|
import { handleHerdrInspectorAction, HERDR_INSPECTOR_ACTIONS } from "../../inspectors/herdr/actions.ts";
|
|
116
119
|
import { handleHerdrProjectPaneAction, HERDR_PROJECT_PANE_ACTIONS } from "../../inspectors/herdr/project-panes.ts";
|
|
117
|
-
import { previewSimpleWorkflowRun, runWorkflowScript, WorkflowScriptError, type WorkflowScriptChildResult } from "../../workflows/scripted-workflow.ts";
|
|
118
|
-
import { renderWorkflowPrompt } from "../../shared/prompt-resources.ts";
|
|
120
|
+
import { previewSimpleWorkflowRun, runWorkflowScript, WorkflowScriptError, type WorkflowScriptChildResult, type WorkflowSteerOptions, type WorkflowSteerResult } from "../../workflows/scripted-workflow.ts";
|
|
119
121
|
import { resolveWorkflowChatProgress, type WorkflowChatProgressProjection } from "../../workflows/chat-progress.ts";
|
|
120
122
|
import {
|
|
121
123
|
cleanupWorktrees,
|
|
@@ -172,7 +174,7 @@ import {
|
|
|
172
174
|
} from "../../shared/types.ts";
|
|
173
175
|
|
|
174
176
|
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"]);
|
|
175
|
-
const DESTRUCTIVE_MANAGEMENT_ACTIONS = new Set(["delete", "eject", "disable", "reset", "mission.close", "worktree.discard", "refine.rollback", "inspector.close", "project.close", "stop", "interrupt", "
|
|
177
|
+
const DESTRUCTIVE_MANAGEMENT_ACTIONS = new Set(["delete", "eject", "disable", "reset", "mission.close", "worktree.discard", "refine.rollback", "inspector.close", "project.close", "stop", "interrupt", "schedule.delete"]);
|
|
176
178
|
|
|
177
179
|
function editDistance(left: string, right: string): number {
|
|
178
180
|
const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
@@ -285,11 +287,13 @@ export interface SubagentParamsLike {
|
|
|
285
287
|
mode?: SteerDeliveryMode;
|
|
286
288
|
workflowScript?: string;
|
|
287
289
|
chatProgress?: "auto" | "off" | "live-card";
|
|
290
|
+
isolation?: "none" | "worktree";
|
|
288
291
|
step?: ChainStep;
|
|
289
292
|
/** Internal workflow ownership metadata; not part of the public schema. */
|
|
290
293
|
workflowParentRunId?: string;
|
|
291
294
|
workflowKey?: string;
|
|
292
295
|
workflowChildAsyncId?: string;
|
|
296
|
+
workflowParentDeadlineAt?: number;
|
|
293
297
|
suppressRoutineResultIntercom?: boolean;
|
|
294
298
|
/** Internal inherited cumulative run-tree budget. */
|
|
295
299
|
runFanoutBudget?: RunFanoutBudgetDescriptor;
|
|
@@ -307,6 +311,8 @@ export interface SubagentParamsLike {
|
|
|
307
311
|
foregroundOnly?: boolean;
|
|
308
312
|
timeoutMs?: number;
|
|
309
313
|
maxRuntimeMs?: number;
|
|
314
|
+
/** Optional hard per-tool-call timeout (ms). Known-fast tools also have a default. */
|
|
315
|
+
toolTimeoutMs?: number;
|
|
310
316
|
turnBudget?: TurnBudgetConfig;
|
|
311
317
|
/** Internal-only strict turn-boundary enforcement for versioned foreground delegation. */
|
|
312
318
|
enforceHardTurnLimit?: boolean;
|
|
@@ -336,8 +342,6 @@ export interface SubagentParamsLike {
|
|
|
336
342
|
acceptance?: AcceptanceInput;
|
|
337
343
|
gate?: string;
|
|
338
344
|
agentContract?: AgentContract;
|
|
339
|
-
schedule?: string;
|
|
340
|
-
scheduleName?: string;
|
|
341
345
|
at?: string;
|
|
342
346
|
every?: string;
|
|
343
347
|
on?: string | number;
|
|
@@ -375,8 +379,10 @@ interface ExecutorDeps {
|
|
|
375
379
|
tempArtifactsDir: string;
|
|
376
380
|
getSubagentSessionRoot: (parentSessionFile: string | null) => string;
|
|
377
381
|
expandTilde: (p: string) => string;
|
|
378
|
-
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[]; modelScope?: ModelScopeConfig };
|
|
382
|
+
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[]; agentDiagnostics?: AgentDiscoveryDiagnostic[]; modelScope?: ModelScopeConfig };
|
|
379
383
|
allowMutatingManagementActions?: boolean;
|
|
384
|
+
activateSupervisorTransport?: () => void;
|
|
385
|
+
refreshResultDelivery?: () => void;
|
|
380
386
|
kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean;
|
|
381
387
|
}
|
|
382
388
|
|
|
@@ -409,6 +415,8 @@ interface ExecutionContextData {
|
|
|
409
415
|
nestedRoute?: NestedRouteInfo;
|
|
410
416
|
timeoutMs?: number;
|
|
411
417
|
deadlineAt?: number;
|
|
418
|
+
/** Raw global config.toolTimeoutMs, for per-step resolution in async runners. */
|
|
419
|
+
configToolTimeoutMs?: number;
|
|
412
420
|
turnBudget?: ResolvedTurnBudget;
|
|
413
421
|
toolBudget?: ResolvedToolBudget;
|
|
414
422
|
usageBudget?: UsageBudgetConfig;
|
|
@@ -457,6 +465,7 @@ function promptAuditRedoParams(value: unknown, rewrittenTask: string): SubagentP
|
|
|
457
465
|
delete params.workflowParentRunId;
|
|
458
466
|
delete params.workflowKey;
|
|
459
467
|
delete params.workflowChildAsyncId;
|
|
468
|
+
delete params.workflowParentDeadlineAt;
|
|
460
469
|
delete params.suppressRoutineResultIntercom;
|
|
461
470
|
if (params.worktree === true && Array.isArray(params.tasks)) delete params.cwd;
|
|
462
471
|
return params;
|
|
@@ -548,7 +557,7 @@ function countRequestedSubagentSpawns(params: SubagentParamsLike, config: Extens
|
|
|
548
557
|
function staticRunFanoutPaths(params: SubagentParamsLike): string[] {
|
|
549
558
|
if (params.tasks) return params.tasks.map((_, index) => `tasks[${index}]`);
|
|
550
559
|
if (params.chain) return params.chain.flatMap((step, stepIndex) => {
|
|
551
|
-
if (isDynamicParallelStep(step)
|
|
560
|
+
if (isDynamicParallelStep(step)) return [];
|
|
552
561
|
if (isParallelStep(step)) return step.parallel.map((_, itemIndex) => `chain[${stepIndex}].parallel[${itemIndex}]`);
|
|
553
562
|
return [`chain[${stepIndex}]`];
|
|
554
563
|
});
|
|
@@ -622,7 +631,7 @@ function foregroundChildActivityFromProgress(progress: SingleResult["progress"]
|
|
|
622
631
|
};
|
|
623
632
|
}
|
|
624
633
|
|
|
625
|
-
function rememberForegroundRun(state: SubagentState, input: { runId: string; mode: "single" | "parallel" | "chain"; cwd: string; sessionId: string | null; results: SingleResult[]
|
|
634
|
+
function rememberForegroundRun(state: SubagentState, input: { runId: string; mode: "single" | "parallel" | "chain"; cwd: string; sessionId: string | null; results: SingleResult[] }): void {
|
|
626
635
|
state.foregroundRuns ??= new Map();
|
|
627
636
|
const previous = state.foregroundRuns.get(input.runId);
|
|
628
637
|
const updatedAt = Date.now();
|
|
@@ -632,7 +641,6 @@ function rememberForegroundRun(state: SubagentState, input: { runId: string; mod
|
|
|
632
641
|
cwd: input.cwd,
|
|
633
642
|
...(input.sessionId ? { sessionId: input.sessionId } : {}),
|
|
634
643
|
updatedAt,
|
|
635
|
-
...(input.checkpoint ? { checkpoint: input.checkpoint } : {}),
|
|
636
644
|
children: input.results.map((result, index) => {
|
|
637
645
|
const child = {
|
|
638
646
|
agent: result.agent,
|
|
@@ -990,9 +998,9 @@ function interruptAsyncRun(
|
|
|
990
998
|
};
|
|
991
999
|
}
|
|
992
1000
|
const activeSteps = status.steps?.filter((step) => step.status === "running") ?? [];
|
|
993
|
-
if (activeSteps.length > 0 && activeSteps.every((step) => step.runner?.type === "external-cli")) {
|
|
1001
|
+
if (activeSteps.length > 0 && activeSteps.every((step) => step.runner?.type === "external-cli" || step.runner?.type === "external-job")) {
|
|
994
1002
|
return {
|
|
995
|
-
content: [{ type: "text", text: `Interrupt is unsupported for
|
|
1003
|
+
content: [{ type: "text", text: `Interrupt is unsupported for external async run ${target.asyncId}; use stop instead.` }],
|
|
996
1004
|
isError: true,
|
|
997
1005
|
details: { mode: "management", results: [] },
|
|
998
1006
|
};
|
|
@@ -1209,7 +1217,7 @@ function appendStepToAsyncChain(input: {
|
|
|
1209
1217
|
const startIndex = (status.chainStepCount ?? status.steps?.length ?? 0) + pendingAppendRequests.reduce((total, request) => total + request.steps.length, 0);
|
|
1210
1218
|
const appendPaths = chain.flatMap((step, localIndex) => {
|
|
1211
1219
|
const absoluteIndex = startIndex + localIndex;
|
|
1212
|
-
if (isDynamicParallelStep(step)
|
|
1220
|
+
if (isDynamicParallelStep(step)) return [];
|
|
1213
1221
|
if (isParallelStep(step)) return step.parallel.map((_, itemIndex) => `chain[${absoluteIndex}].parallel[${itemIndex}]`);
|
|
1214
1222
|
return [`chain[${absoluteIndex}]`];
|
|
1215
1223
|
});
|
|
@@ -1367,12 +1375,12 @@ async function directNestedAsyncSteer(input: { target: ResolvedSubagentRunId & {
|
|
|
1367
1375
|
const targets = targetIndexes.map((index) => ({ index, state: steps[index]?.status === "pending" ? "scheduled" as const : "pending" as const }));
|
|
1368
1376
|
if (targets.every((target) => target.state === "scheduled")) {
|
|
1369
1377
|
const scheduled = { requestId, state: "scheduled" as const, deliveryStatus: "queued" as const, sourceRunId: run.id, targets };
|
|
1370
|
-
return { content: [{ type: "text", text: `Steering scheduled for nested async run ${run.id} (request ${requestId}).` }], details: { mode: "management", results: [], steering: scheduled } };
|
|
1378
|
+
return { content: [{ type: "text", text: steeringReceipt(input.message, `Steering scheduled for nested async run ${run.id} (request ${requestId}).`) }], details: { mode: "management", results: [], steering: scheduled } };
|
|
1371
1379
|
}
|
|
1372
1380
|
const waited = await waitForSteeringAction(omitUndefinedProperties({ asyncDir, sourceRunId: run.id, requestId, timeoutMs: 3_000, signal: input.signal }));
|
|
1373
1381
|
const result = waited ?? { requestId, state: "pending" as const, deliveryStatus: "queued" as const, sourceRunId: run.id, targets };
|
|
1374
1382
|
const stateText = result.state === "failed" ? "failed" : result.state === "partial" ? "partial" : result.deliveryStatus === "queued" ? "queued" : result.state === "delivered" ? "delivered" : "pending";
|
|
1375
|
-
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 } };
|
|
1383
|
+
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 } };
|
|
1376
1384
|
}
|
|
1377
1385
|
|
|
1378
1386
|
async function interruptNestedRun(target: ResolvedSubagentRunId & { kind: "nested" }): Promise<AgentToolResult<Details>> {
|
|
@@ -1404,10 +1412,10 @@ async function steerNestedRun(input: { target: ResolvedSubagentRunId & { kind: "
|
|
|
1404
1412
|
|
|
1405
1413
|
function externalRunnerControlError(asyncDir: string, action: "steer" | "resume"): AgentToolResult<Details> | undefined {
|
|
1406
1414
|
const status = readStatus(asyncDir);
|
|
1407
|
-
if (!status?.steps?.length || !status.steps.every((step) => step.runner?.type === "external-cli")) return undefined;
|
|
1415
|
+
if (!status?.steps?.length || !status.steps.every((step) => step.runner?.type === "external-cli" || step.runner?.type === "external-job")) return undefined;
|
|
1408
1416
|
const message = action === "steer"
|
|
1409
|
-
? "
|
|
1410
|
-
: "
|
|
1417
|
+
? "External runners do not accept live steer messages."
|
|
1418
|
+
: "External runners do not persist Pi sessions and cannot be resumed.";
|
|
1411
1419
|
return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
|
|
1412
1420
|
}
|
|
1413
1421
|
|
|
@@ -1560,7 +1568,7 @@ async function resumeAsyncRun(input: {
|
|
|
1560
1568
|
details: { mode: "chain", results: [] },
|
|
1561
1569
|
};
|
|
1562
1570
|
}
|
|
1563
|
-
const runId = randomUUID()
|
|
1571
|
+
const runId = randomUUID();
|
|
1564
1572
|
const topLevelResume = depth === 0 && !resolveInheritedNestedRouteFromEnv() && !input.params.workflowParentRunId;
|
|
1565
1573
|
let activeAsyncCapacity: ActiveAsyncCapacityHandle | undefined;
|
|
1566
1574
|
try {
|
|
@@ -1655,7 +1663,7 @@ async function resumeAsyncRun(input: {
|
|
|
1655
1663
|
if (target.source === "async" && !recoveryDescriptor) {
|
|
1656
1664
|
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: [] } };
|
|
1657
1665
|
}
|
|
1658
|
-
const runId = randomUUID()
|
|
1666
|
+
const runId = randomUUID();
|
|
1659
1667
|
const topLevelResume = depth === 0 && !resolveInheritedNestedRouteFromEnv() && !input.params.workflowParentRunId;
|
|
1660
1668
|
let activeAsyncCapacity: ActiveAsyncCapacityHandle | undefined;
|
|
1661
1669
|
try {
|
|
@@ -1724,6 +1732,7 @@ async function resumeAsyncRun(input: {
|
|
|
1724
1732
|
worktreeSetupHook: input.deps.config.worktreeSetupHook,
|
|
1725
1733
|
worktreeSetupHookTimeoutMs: input.deps.config.worktreeSetupHookTimeoutMs,
|
|
1726
1734
|
worktreeBaseDir: input.deps.config.worktreeBaseDir,
|
|
1735
|
+
worktree: input.params.worktree === true,
|
|
1727
1736
|
controlConfig: recoveryDescriptor?.controlConfig ?? resolveControlConfig(input.deps.config.control, input.params.control),
|
|
1728
1737
|
intercomBridge: input.params.intercomBridge ?? recoveryDescriptor?.intercomBridge,
|
|
1729
1738
|
controlIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
|
|
@@ -1767,7 +1776,7 @@ async function resumeAsyncRun(input: {
|
|
|
1767
1776
|
const sourceStatus = readStatus(sourceAsyncDir);
|
|
1768
1777
|
if (sourceStatus?.steering) {
|
|
1769
1778
|
for (const brief of queuedBriefs) updateSteeringTarget(sourceStatus.steering, brief.request.id, target.index, "delivered", Date.now());
|
|
1770
|
-
|
|
1779
|
+
createCapacityResilientJsonWriter({ keepAlive: true }).write(path.join(sourceAsyncDir, "status.json"), sourceStatus);
|
|
1771
1780
|
}
|
|
1772
1781
|
}
|
|
1773
1782
|
|
|
@@ -1958,16 +1967,19 @@ async function maybeBuildForegroundIntercomReceipt(input: {
|
|
|
1958
1967
|
};
|
|
1959
1968
|
}
|
|
1960
1969
|
|
|
1961
|
-
function canonicalizeAgentName(name: string, agents: AgentConfig[]): { name?: string; error?: string } {
|
|
1970
|
+
function canonicalizeAgentName(name: string, agents: AgentConfig[], diagnostics?: AgentDiscoveryDiagnostic[]): { name?: string; error?: string } {
|
|
1962
1971
|
const resolved = resolveAgentName(name, agents);
|
|
1972
|
+
const candidates = resolved.error ? agents.filter((agent) => resolveAgentName(name, [agent]).agent) : resolved.agent;
|
|
1973
|
+
const diagnostic = findBlockingAgentDiagnostic(name, candidates, diagnostics);
|
|
1974
|
+
if (diagnostic) return { error: `Agent '${name}' has invalid configuration: ${diagnostic.error}` };
|
|
1963
1975
|
if (resolved.error) return { error: resolved.error };
|
|
1964
1976
|
if (!resolved.agent) return { error: `Unknown agent: ${name}` };
|
|
1965
1977
|
return { name: resolved.agent.name };
|
|
1966
1978
|
}
|
|
1967
1979
|
|
|
1968
|
-
function canonicalizeExecutionParams(params: SubagentParamsLike, agents: AgentConfig[]): { params?: SubagentParamsLike; error?: string } {
|
|
1980
|
+
function canonicalizeExecutionParams(params: SubagentParamsLike, agents: AgentConfig[], diagnostics?: AgentDiscoveryDiagnostic[]): { params?: SubagentParamsLike; error?: string } {
|
|
1969
1981
|
const resolve = (name: string, location?: string): { name?: string; error?: string } => {
|
|
1970
|
-
const result = canonicalizeAgentName(name, agents);
|
|
1982
|
+
const result = canonicalizeAgentName(name, agents, diagnostics);
|
|
1971
1983
|
return result.error && location ? { error: `${result.error} (${location})` } : result;
|
|
1972
1984
|
};
|
|
1973
1985
|
if (params.agent) {
|
|
@@ -2171,13 +2183,21 @@ interface AgentDefaultContextPolicy {
|
|
|
2171
2183
|
usesFork: boolean;
|
|
2172
2184
|
}
|
|
2173
2185
|
|
|
2174
|
-
function resolveAgentDefaultContextPolicy(
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2186
|
+
function resolveAgentDefaultContextPolicy(
|
|
2187
|
+
params: SubagentParamsLike,
|
|
2188
|
+
agents: AgentConfig[],
|
|
2189
|
+
defaultSubagentContext: ExtensionConfig["defaultSubagentContext"],
|
|
2190
|
+
canUseDefaultFork = false,
|
|
2191
|
+
): AgentDefaultContextPolicy {
|
|
2192
|
+
if (params.context !== undefined) return resolveExplicitContextPolicy(params);
|
|
2178
2193
|
const byName = new Map(agents.map((agent) => [agent.name, agent]));
|
|
2179
2194
|
const contextForAgent = (agentName: string): ContextMode =>
|
|
2180
|
-
|
|
2195
|
+
resolveSubagentLaunchContext({
|
|
2196
|
+
explicitContext: params.context,
|
|
2197
|
+
agentDefaultContext: byName.get(agentName)?.defaultContext,
|
|
2198
|
+
defaultSubagentContext,
|
|
2199
|
+
canUseImplicitFork: canUseDefaultFork,
|
|
2200
|
+
});
|
|
2181
2201
|
const requestedAgentNames = collectRequestedAgentNames(params);
|
|
2182
2202
|
const contextSummary = summarizeContextModes(requestedAgentNames.map((name) => contextForAgent(name)));
|
|
2183
2203
|
const usesFork = contextSummary === "fork" || contextSummary === "mixed";
|
|
@@ -2190,7 +2210,10 @@ function resolveAgentDefaultContextPolicy(params: SubagentParamsLike, agents: Ag
|
|
|
2190
2210
|
}
|
|
2191
2211
|
|
|
2192
2212
|
function resolveExplicitContextPolicy(params: SubagentParamsLike): AgentDefaultContextPolicy {
|
|
2193
|
-
const context =
|
|
2213
|
+
const context = resolveSubagentLaunchContext({
|
|
2214
|
+
explicitContext: params.context,
|
|
2215
|
+
canUseImplicitFork: false,
|
|
2216
|
+
});
|
|
2194
2217
|
return {
|
|
2195
2218
|
params,
|
|
2196
2219
|
contextForAgent: () => context,
|
|
@@ -2230,12 +2253,16 @@ function applySingleAgentLaunchDefaults(params: SubagentParamsLike, agents: Agen
|
|
|
2230
2253
|
if ((params.chain?.length ?? 0) > 0 || (params.tasks?.length ?? 0) > 0 || !params.agent) return params;
|
|
2231
2254
|
const agent = agents.find((candidate) => candidate.name === params.agent);
|
|
2232
2255
|
if (!agent) return params;
|
|
2256
|
+
const parentTimeoutMs = params.timeoutMs === undefined && params.maxRuntimeMs === undefined && agent.defaultTimeoutMs === undefined && params.workflowParentDeadlineAt !== undefined
|
|
2257
|
+
? Math.max(1, params.workflowParentDeadlineAt - Date.now())
|
|
2258
|
+
: undefined;
|
|
2233
2259
|
return {
|
|
2234
2260
|
...params,
|
|
2235
2261
|
...(params.async === undefined && agent.defaultAsync !== undefined ? { async: agent.defaultAsync } : {}),
|
|
2236
2262
|
...(params.timeoutMs === undefined && params.maxRuntimeMs === undefined && agent.defaultTimeoutMs !== undefined
|
|
2237
2263
|
? { timeoutMs: agent.defaultTimeoutMs }
|
|
2238
2264
|
: {}),
|
|
2265
|
+
...(parentTimeoutMs !== undefined ? { timeoutMs: parentTimeoutMs } : {}),
|
|
2239
2266
|
...(params.turnBudget === undefined && agent.defaultTurnBudget !== undefined
|
|
2240
2267
|
? { turnBudget: agent.defaultTurnBudget }
|
|
2241
2268
|
: {}),
|
|
@@ -2506,16 +2533,19 @@ function resolveStaticLaunchSummary(input: {
|
|
|
2506
2533
|
thinkingOverrideForTask: ForkThinkingOverrideForTask;
|
|
2507
2534
|
}): StaticLaunchSummary {
|
|
2508
2535
|
const agentConfig = input.agents.find((agent) => agent.name === input.agent);
|
|
2509
|
-
const
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2536
|
+
const externalRunner = agentConfig?.runner?.type === "external-cli" || agentConfig?.runner?.type === "external-job";
|
|
2537
|
+
const model = externalRunner
|
|
2538
|
+
? undefined
|
|
2539
|
+
: resolveEffectiveSubagentModel(
|
|
2540
|
+
input.explicitModel,
|
|
2541
|
+
agentConfig?.model,
|
|
2542
|
+
input.parentModel,
|
|
2543
|
+
input.availableModels,
|
|
2544
|
+
input.currentProvider,
|
|
2545
|
+
input.modelScope === undefined ? {} : { scope: input.modelScope },
|
|
2546
|
+
);
|
|
2547
|
+
const thinkingOverride = externalRunner ? undefined : input.thinkingOverrideForTask(input.agent, input.index, model);
|
|
2548
|
+
const thinking = externalRunner ? undefined : resolveEffectiveThinking(model, thinkingOverride ?? agentConfig?.thinking);
|
|
2519
2549
|
return {
|
|
2520
2550
|
agent: input.agent,
|
|
2521
2551
|
...(model ? { model } : {}),
|
|
@@ -2696,27 +2726,6 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2696
2726
|
const hasSingle = !hasChain && !hasTasks && Boolean(params.agent);
|
|
2697
2727
|
if (!effectiveAsync) return null;
|
|
2698
2728
|
|
|
2699
|
-
if (hasChain && params.chain) {
|
|
2700
|
-
const chainWorktreeTaskCwdError = buildChainWorktreeTaskCwdError(params.chain as ChainStep[], effectiveCwd);
|
|
2701
|
-
if (chainWorktreeTaskCwdError) {
|
|
2702
|
-
return {
|
|
2703
|
-
content: [{ type: "text", text: chainWorktreeTaskCwdError }],
|
|
2704
|
-
isError: true,
|
|
2705
|
-
details: { mode: "chain" as const, results: [] },
|
|
2706
|
-
};
|
|
2707
|
-
}
|
|
2708
|
-
}
|
|
2709
|
-
|
|
2710
|
-
if (hasTasks && params.tasks) {
|
|
2711
|
-
const maxParallelTasks = resolveTopLevelParallelMaxTasks(deps.config.parallel?.maxTasks);
|
|
2712
|
-
if (params.tasks.length > maxParallelTasks) {
|
|
2713
|
-
return buildParallelModeError(`Max ${maxParallelTasks} tasks`);
|
|
2714
|
-
}
|
|
2715
|
-
if (params.worktree) {
|
|
2716
|
-
const worktreeTaskCwdError = buildParallelWorktreeTaskCwdError(params.tasks, effectiveCwd);
|
|
2717
|
-
if (worktreeTaskCwdError) return buildParallelModeError(worktreeTaskCwdError);
|
|
2718
|
-
}
|
|
2719
|
-
}
|
|
2720
2729
|
|
|
2721
2730
|
if (!isAsyncAvailable()) {
|
|
2722
2731
|
return {
|
|
@@ -2744,114 +2753,6 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2744
2753
|
const controlIntercomTarget = intercomBridge.active ? intercomBridge.orchestratorTarget : undefined;
|
|
2745
2754
|
const childIntercomTarget = intercomBridge.active ? (agent: string, index: number) => resolveSubagentIntercomTarget(id, agent, index) : undefined;
|
|
2746
2755
|
|
|
2747
|
-
if (hasTasks && params.tasks) {
|
|
2748
|
-
const skillOverrides = params.tasks.map((task) => normalizeSkillInput(task.skill));
|
|
2749
|
-
const parallelTasks = params.tasks.map((task, index) => compactOptional<ParallelTaskItem>({
|
|
2750
|
-
agent: task.agent,
|
|
2751
|
-
task: shouldForkAgent(contextPolicy, task.agent) ? wrapForkTask(task.task) : task.task,
|
|
2752
|
-
cwd: task.cwd,
|
|
2753
|
-
...(task.model !== undefined ? { model: task.model } : {}),
|
|
2754
|
-
...(skillOverrides[index] !== undefined ? { skill: skillOverrides[index] } : {}),
|
|
2755
|
-
...(task.output !== undefined && task.output !== true ? { output: task.output } : {}),
|
|
2756
|
-
...(task.outputMode !== undefined ? { outputMode: task.outputMode } : {}),
|
|
2757
|
-
...(task.reads !== undefined && task.reads !== true ? { reads: task.reads } : {}),
|
|
2758
|
-
...(task.progress !== undefined ? { progress: task.progress } : {}),
|
|
2759
|
-
...(task.toolBudget !== undefined ? { toolBudget: task.toolBudget } : {}),
|
|
2760
|
-
...(task.outputSchema !== undefined ? { outputSchema: task.outputSchema } : {}),
|
|
2761
|
-
...(task.agentContract !== undefined ? { agentContract: task.agentContract } : {}),
|
|
2762
|
-
...(task.acceptance !== undefined ? { acceptance: task.acceptance } : {}),
|
|
2763
|
-
}));
|
|
2764
|
-
return executeAsyncChain(id, compactOptional<Parameters<typeof executeAsyncChain>[1]>({
|
|
2765
|
-
chain: [compactOptional<ParallelStep>({
|
|
2766
|
-
parallel: parallelTasks,
|
|
2767
|
-
concurrency: resolveTopLevelParallelConcurrency(params.concurrency, deps.config.parallel?.concurrency),
|
|
2768
|
-
worktree: params.worktree,
|
|
2769
|
-
})],
|
|
2770
|
-
resultMode: "parallel",
|
|
2771
|
-
goal: params.tasks[0]?.task ?? "",
|
|
2772
|
-
agents,
|
|
2773
|
-
ctx: asyncCtx,
|
|
2774
|
-
availableModels,
|
|
2775
|
-
cwd: effectiveCwd,
|
|
2776
|
-
maxOutput: params.maxOutput,
|
|
2777
|
-
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
2778
|
-
artifactConfig,
|
|
2779
|
-
shareEnabled,
|
|
2780
|
-
sessionRoot,
|
|
2781
|
-
chainSkills: [],
|
|
2782
|
-
activeAsyncCapacity: data.activeAsyncCapacity,
|
|
2783
|
-
sessionFilesByFlatIndex: params.tasks.map((task, index) => sessionFileForTask(task.agent, index, task.model)),
|
|
2784
|
-
thinkingOverridesByFlatIndex: params.tasks.map((task, index) => thinkingOverrideForTask(task.agent, index, task.model)),
|
|
2785
|
-
contextForAgent: contextPolicy.contextForAgent,
|
|
2786
|
-
maxSubagentDepth: currentMaxSubagentDepth,
|
|
2787
|
-
waitToolEnabled: deps.waitToolEnabled,
|
|
2788
|
-
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
2789
|
-
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
2790
|
-
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
2791
|
-
controlConfig,
|
|
2792
|
-
agentContract: params.agentContract,
|
|
2793
|
-
controlIntercomTarget,
|
|
2794
|
-
childIntercomTarget,
|
|
2795
|
-
nestedRoute,
|
|
2796
|
-
timeoutMs: data.timeoutMs,
|
|
2797
|
-
turnBudget: data.turnBudget,
|
|
2798
|
-
toolBudget: data.toolBudget,
|
|
2799
|
-
usageBudget: data.usageBudget,
|
|
2800
|
-
configToolBudget: data.configToolBudget,
|
|
2801
|
-
capabilityCeiling: data.capabilityCeiling,
|
|
2802
|
-
runFanoutBudget: data.runFanoutBudget,
|
|
2803
|
-
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
2804
|
-
parentWorkflowRunId: params.workflowParentRunId,
|
|
2805
|
-
workflowKey: params.workflowKey,
|
|
2806
|
-
}));
|
|
2807
|
-
}
|
|
2808
|
-
|
|
2809
|
-
if (hasChain && params.chain) {
|
|
2810
|
-
const normalized = normalizeSkillInput(params.skill);
|
|
2811
|
-
const chainSkills = normalized === false ? [] : (normalized ?? []);
|
|
2812
|
-
const rawChain = params.chain as ChainStep[];
|
|
2813
|
-
const chain = wrapChainTasksForFork(rawChain, contextPolicy);
|
|
2814
|
-
return executeAsyncChain(id, compactOptional<Parameters<typeof executeAsyncChain>[1]>({
|
|
2815
|
-
chain,
|
|
2816
|
-
task: params.task,
|
|
2817
|
-
goal: resolveAsyncEventGoal(params.task, rawChain),
|
|
2818
|
-
agents,
|
|
2819
|
-
ctx: asyncCtx,
|
|
2820
|
-
availableModels,
|
|
2821
|
-
cwd: effectiveCwd,
|
|
2822
|
-
maxOutput: params.maxOutput,
|
|
2823
|
-
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
2824
|
-
artifactConfig,
|
|
2825
|
-
shareEnabled,
|
|
2826
|
-
sessionRoot,
|
|
2827
|
-
chainSkills,
|
|
2828
|
-
activeAsyncCapacity: data.activeAsyncCapacity,
|
|
2829
|
-
sessionFilesByFlatIndex: collectChainSessionFiles(chain, sessionFileForTask, deps.config.chain?.dynamicFanout?.maxItems),
|
|
2830
|
-
thinkingOverridesByFlatIndex: collectChainThinkingOverrides(chain, thinkingOverrideForTask, deps.config.chain?.dynamicFanout?.maxItems),
|
|
2831
|
-
contextForAgent: contextPolicy.contextForAgent,
|
|
2832
|
-
dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
|
|
2833
|
-
maxSubagentDepth: currentMaxSubagentDepth,
|
|
2834
|
-
waitToolEnabled: deps.waitToolEnabled,
|
|
2835
|
-
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
2836
|
-
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
2837
|
-
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
2838
|
-
controlConfig,
|
|
2839
|
-
agentContract: params.agentContract,
|
|
2840
|
-
controlIntercomTarget,
|
|
2841
|
-
childIntercomTarget,
|
|
2842
|
-
nestedRoute,
|
|
2843
|
-
timeoutMs: data.timeoutMs,
|
|
2844
|
-
turnBudget: data.turnBudget,
|
|
2845
|
-
toolBudget: data.toolBudget,
|
|
2846
|
-
usageBudget: data.usageBudget,
|
|
2847
|
-
configToolBudget: data.configToolBudget,
|
|
2848
|
-
capabilityCeiling: data.capabilityCeiling,
|
|
2849
|
-
runFanoutBudget: data.runFanoutBudget,
|
|
2850
|
-
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
2851
|
-
parentWorkflowRunId: params.workflowParentRunId,
|
|
2852
|
-
workflowKey: params.workflowKey,
|
|
2853
|
-
}));
|
|
2854
|
-
}
|
|
2855
2756
|
|
|
2856
2757
|
if (hasSingle) {
|
|
2857
2758
|
const a = agents.find((x) => x.name === params.agent);
|
|
@@ -2868,11 +2769,11 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2868
2769
|
const normalizedSkills = normalizeSkillInput(params.skill);
|
|
2869
2770
|
const skills = normalizedSkills === false ? [] : normalizedSkills;
|
|
2870
2771
|
const maxSubagentDepth = resolveChildMaxSubagentDepth(currentMaxSubagentDepth, a.maxSubagentDepth);
|
|
2871
|
-
const externalRunnerWithoutExplicitModel = a.runner?.type === "external-cli"
|
|
2772
|
+
const externalRunnerWithoutExplicitModel = (a.runner?.type === "external-cli" || a.runner?.type === "external-job")
|
|
2872
2773
|
&& params.model === undefined
|
|
2873
2774
|
&& (a.model === undefined || (a.modelSource?.type === "subagents.defaultModel" && a.model === a.modelSource.model));
|
|
2874
|
-
const modelOverride =
|
|
2875
|
-
? undefined
|
|
2775
|
+
const modelOverride = a.runner?.type === "external-cli" || a.runner?.type === "external-job"
|
|
2776
|
+
? params.model ?? (externalRunnerWithoutExplicitModel ? undefined : a.model)
|
|
2876
2777
|
: resolveEffectiveSubagentModel(params.model as string | undefined, a.model, parentModel, availableModels, currentProvider, data.modelScope === undefined ? {} : { scope: data.modelScope });
|
|
2877
2778
|
return executeAsyncSingle(id, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
|
|
2878
2779
|
agent: params.agent!,
|
|
@@ -2900,6 +2801,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2900
2801
|
thinkingOverride: externalRunnerWithoutExplicitModel ? undefined : thinkingOverrideForTask(params.agent!, 0, modelOverride),
|
|
2901
2802
|
maxSubagentDepth,
|
|
2902
2803
|
waitToolEnabled: deps.waitToolEnabled,
|
|
2804
|
+
...(params.worktree === true ? { worktree: true } : {}),
|
|
2903
2805
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
2904
2806
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
2905
2807
|
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
@@ -2916,6 +2818,8 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2916
2818
|
toolBudget: data.toolBudget,
|
|
2917
2819
|
usageBudget: data.usageBudget,
|
|
2918
2820
|
configToolBudget: data.configToolBudget,
|
|
2821
|
+
toolTimeoutMs: data.params?.toolTimeoutMs,
|
|
2822
|
+
configToolTimeoutMs: data.configToolTimeoutMs,
|
|
2919
2823
|
capabilityCeiling: data.capabilityCeiling,
|
|
2920
2824
|
runFanoutBudget: data.runFanoutBudget,
|
|
2921
2825
|
parentWorkflowRunId: params.workflowParentRunId,
|
|
@@ -2926,300 +2830,11 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2926
2830
|
return null;
|
|
2927
2831
|
}
|
|
2928
2832
|
|
|
2929
|
-
|
|
2930
|
-
const {
|
|
2931
|
-
params,
|
|
2932
|
-
effectiveCwd,
|
|
2933
|
-
agents,
|
|
2934
|
-
ctx,
|
|
2935
|
-
signal,
|
|
2936
|
-
runId,
|
|
2937
|
-
shareEnabled,
|
|
2938
|
-
sessionDirForIndex,
|
|
2939
|
-
sessionFileForIndex,
|
|
2940
|
-
sessionFileForTask,
|
|
2941
|
-
thinkingOverrideForTask,
|
|
2942
|
-
artifactsDir,
|
|
2943
|
-
artifactConfig,
|
|
2944
|
-
onUpdate,
|
|
2945
|
-
sessionRoot,
|
|
2946
|
-
controlConfig,
|
|
2947
|
-
contextPolicy,
|
|
2948
|
-
} = data;
|
|
2949
|
-
const onControlEvent = createForegroundControlNotifier(data, deps);
|
|
2950
|
-
const childIntercomTarget = data.intercomBridge.active ? resolveSubagentIntercomTarget : undefined;
|
|
2951
|
-
const foregroundControl = deps.state.foregroundControls.get(runId);
|
|
2952
|
-
const normalized = normalizeSkillInput(params.skill);
|
|
2953
|
-
const chainSkills = normalized === false ? [] : (normalized ?? []);
|
|
2954
|
-
const chain = wrapChainTasksForFork(params.chain as ChainStep[], contextPolicy);
|
|
2955
|
-
const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
|
|
2956
|
-
const chainCtx = normalizeParentModel(ctx.model) || !data.parentModel ? ctx : ({ ...ctx, model: data.parentModel } as ExtensionContext);
|
|
2957
|
-
const chainResult = await executeChain(compactOptional<Parameters<typeof executeChain>[0]>({
|
|
2958
|
-
chain,
|
|
2959
|
-
task: params.task,
|
|
2960
|
-
agents,
|
|
2961
|
-
ctx: chainCtx,
|
|
2962
|
-
modelScope: data.modelScope,
|
|
2963
|
-
intercomEvents: deps.pi.events,
|
|
2964
|
-
signal,
|
|
2965
|
-
runId,
|
|
2966
|
-
cwd: effectiveCwd,
|
|
2967
|
-
shareEnabled,
|
|
2968
|
-
sessionDirForIndex,
|
|
2969
|
-
sessionFileForIndex,
|
|
2970
|
-
sessionFileForTask,
|
|
2971
|
-
thinkingOverrideForTask,
|
|
2972
|
-
contextForAgent: contextPolicy.contextForAgent,
|
|
2973
|
-
artifactsDir,
|
|
2974
|
-
artifactConfig,
|
|
2975
|
-
includeProgress: params.includeProgress,
|
|
2976
|
-
clarify: params.clarify,
|
|
2977
|
-
onUpdate,
|
|
2978
|
-
onControlEvent,
|
|
2979
|
-
controlConfig,
|
|
2980
|
-
agentContract: params.agentContract,
|
|
2981
|
-
childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(runId, agent, index) : undefined,
|
|
2982
|
-
orchestratorIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
2983
|
-
foregroundControl,
|
|
2984
|
-
nestedRoute: foregroundControl?.nestedRoute,
|
|
2985
|
-
chainSkills,
|
|
2986
|
-
chainDir: params.chainDir ?? getChainRunsDir(effectiveCwd, artifactConfig.dir),
|
|
2987
|
-
dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
|
|
2988
|
-
maxSubagentDepth: currentMaxSubagentDepth,
|
|
2989
|
-
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
2990
|
-
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
2991
|
-
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
2992
|
-
timeoutMs: data.timeoutMs,
|
|
2993
|
-
deadlineAt: data.deadlineAt,
|
|
2994
|
-
turnBudget: data.turnBudget,
|
|
2995
|
-
onDetachedExit: (index, result) => {
|
|
2996
|
-
try {
|
|
2997
|
-
updateRememberedForegroundChild(deps.state, { runId, mode: "chain", cwd: effectiveCwd, sessionId: data.parentSessionId, index, result, events: deps.pi.events });
|
|
2998
|
-
} finally {
|
|
2999
|
-
removeForegroundControlIfIdle(deps.state, runId);
|
|
3000
|
-
}
|
|
3001
|
-
},
|
|
3002
|
-
onForegroundChildSettled: () => {
|
|
3003
|
-
removeForegroundControlIfIdle(deps.state, runId);
|
|
3004
|
-
},
|
|
3005
|
-
toolBudget: data.toolBudget,
|
|
3006
|
-
usageBudget: data.usageBudget,
|
|
3007
|
-
configToolBudget: data.configToolBudget,
|
|
3008
|
-
permissions: deps.config.permissions,
|
|
3009
|
-
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
3010
|
-
capabilityCeiling: data.capabilityCeiling,
|
|
3011
|
-
runFanoutBudget: data.runFanoutBudget,
|
|
3012
|
-
}));
|
|
3013
|
-
|
|
3014
|
-
if (chainResult.requestedAsync) {
|
|
3015
|
-
if (!isAsyncAvailable()) {
|
|
3016
|
-
return {
|
|
3017
|
-
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." }],
|
|
3018
|
-
isError: true,
|
|
3019
|
-
details: { mode: "chain" as const, results: [] },
|
|
3020
|
-
};
|
|
3021
|
-
}
|
|
3022
|
-
const id = data.asyncRunId;
|
|
3023
|
-
const capacity = acquireClarifiedBackgroundCapacity(data, deps, "chain", id);
|
|
3024
|
-
if (capacity.errorResult) return capacity.errorResult;
|
|
3025
|
-
const parentModel = data.parentModel;
|
|
3026
|
-
const asyncCtx = compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
3027
|
-
pi: deps.pi,
|
|
3028
|
-
cwd: ctx.cwd,
|
|
3029
|
-
currentSessionId: data.parentSessionId!,
|
|
3030
|
-
parentSessionId: data.parentPiSessionId,
|
|
3031
|
-
currentModelProvider: parentModel?.provider,
|
|
3032
|
-
currentModel: parentModel,
|
|
3033
|
-
modelScope: data.modelScope,
|
|
3034
|
-
interactive: ctx.hasUI,
|
|
3035
|
-
permissions: deps.config.permissions,
|
|
3036
|
-
});
|
|
3037
|
-
const rawAsyncChain = chainResult.requestedAsync.chain;
|
|
3038
|
-
const asyncChain = wrapChainTasksForFork(rawAsyncChain, contextPolicy);
|
|
3039
|
-
const firstAgent = firstChainAgent(rawAsyncChain);
|
|
3040
|
-
try {
|
|
3041
|
-
return executeAsyncChain(id, compactOptional<Parameters<typeof executeAsyncChain>[1]>({
|
|
3042
|
-
chain: asyncChain,
|
|
3043
|
-
task: params.task,
|
|
3044
|
-
goal: resolveAsyncEventGoal(params.task, rawAsyncChain, firstAgent ? shouldForkAgent(contextPolicy, firstAgent) : false),
|
|
3045
|
-
agents,
|
|
3046
|
-
ctx: asyncCtx,
|
|
3047
|
-
availableModels: ctx.modelRegistry.getAvailable().map(toModelInfo),
|
|
3048
|
-
cwd: effectiveCwd,
|
|
3049
|
-
maxOutput: params.maxOutput,
|
|
3050
|
-
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
3051
|
-
artifactConfig,
|
|
3052
|
-
shareEnabled,
|
|
3053
|
-
sessionRoot,
|
|
3054
|
-
chainSkills: chainResult.requestedAsync.chainSkills,
|
|
3055
|
-
activeAsyncCapacity: capacity.activeAsyncCapacity,
|
|
3056
|
-
sessionFilesByFlatIndex: collectChainSessionFiles(asyncChain, sessionFileForTask, deps.config.chain?.dynamicFanout?.maxItems),
|
|
3057
|
-
thinkingOverridesByFlatIndex: collectChainThinkingOverrides(asyncChain, thinkingOverrideForTask, deps.config.chain?.dynamicFanout?.maxItems),
|
|
3058
|
-
contextForAgent: contextPolicy.contextForAgent,
|
|
3059
|
-
dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
|
|
3060
|
-
maxSubagentDepth: currentMaxSubagentDepth,
|
|
3061
|
-
waitToolEnabled: deps.waitToolEnabled,
|
|
3062
|
-
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
3063
|
-
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
3064
|
-
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
3065
|
-
controlConfig,
|
|
3066
|
-
agentContract: params.agentContract,
|
|
3067
|
-
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
3068
|
-
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
|
|
3069
|
-
nestedRoute: data.nestedRoute,
|
|
3070
|
-
timeoutMs: data.timeoutMs,
|
|
3071
|
-
turnBudget: data.turnBudget,
|
|
3072
|
-
toolBudget: data.toolBudget,
|
|
3073
|
-
usageBudget: data.usageBudget,
|
|
3074
|
-
configToolBudget: data.configToolBudget,
|
|
3075
|
-
capabilityCeiling: data.capabilityCeiling,
|
|
3076
|
-
runFanoutBudget: data.runFanoutBudget,
|
|
3077
|
-
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
3078
|
-
}));
|
|
3079
|
-
} finally {
|
|
3080
|
-
finalizeClarifiedBackgroundCapacity(data, deps, capacity.activeAsyncCapacity);
|
|
3081
|
-
}
|
|
3082
|
-
}
|
|
3083
|
-
|
|
3084
|
-
const rawChainDetails = chainResult.details ? compactOptional<Details>({ ...chainResult.details, runId, timeoutMs: data.timeoutMs }) : undefined;
|
|
3085
|
-
if (foregroundControl && rawChainDetails) {
|
|
3086
|
-
updateForegroundNestedProjection(foregroundControl);
|
|
3087
|
-
attachRootChildrenToSteps(runId, rawChainDetails.results, foregroundControl.nestedChildren);
|
|
3088
|
-
rawChainDetails.totalCost = sumResultsCost(rawChainDetails.results);
|
|
3089
|
-
const usageBudget = usageBudgetState(data.usageBudget, rawChainDetails.totalCost);
|
|
3090
|
-
if (usageBudget === undefined) delete rawChainDetails.usageBudget;
|
|
3091
|
-
else rawChainDetails.usageBudget = usageBudget;
|
|
3092
|
-
}
|
|
3093
|
-
const chainDetails = rawChainDetails ? compactForegroundDetails(rawChainDetails) : undefined;
|
|
3094
|
-
if (chainDetails) rememberForegroundRun(deps.state, { runId, mode: "chain", cwd: effectiveCwd, sessionId: data.parentSessionId, results: chainDetails.results, checkpoint: chainDetails.checkpoint });
|
|
3095
|
-
const intercomReceipt = chainDetails && !chainDetails.results.some((result) => result.interrupted || result.detached)
|
|
3096
|
-
? await maybeBuildForegroundIntercomReceipt({
|
|
3097
|
-
pi: deps.pi,
|
|
3098
|
-
intercomBridge: data.intercomBridge,
|
|
3099
|
-
runId,
|
|
3100
|
-
mode: "chain",
|
|
3101
|
-
details: chainDetails,
|
|
3102
|
-
...(foregroundControl?.nestedChildren?.length ? { nestedChildren: foregroundControl.nestedChildren } : {}),
|
|
3103
|
-
})
|
|
3104
|
-
: null;
|
|
3105
|
-
if (intercomReceipt) {
|
|
3106
|
-
return {
|
|
3107
|
-
...chainResult,
|
|
3108
|
-
content: [{ type: "text", text: intercomReceipt.text }],
|
|
3109
|
-
details: intercomReceipt.details,
|
|
3110
|
-
};
|
|
3111
|
-
}
|
|
3112
|
-
|
|
3113
|
-
return chainDetails ? { ...chainResult, details: chainDetails } : chainResult;
|
|
3114
|
-
}
|
|
3115
|
-
|
|
3116
|
-
interface ForegroundParallelRunInput {
|
|
3117
|
-
tasks: TaskParam[];
|
|
3118
|
-
taskTexts: string[];
|
|
3119
|
-
taskDescriptions: string[];
|
|
3120
|
-
agents: AgentConfig[];
|
|
3121
|
-
ctx: ExtensionContext;
|
|
3122
|
-
state: SubagentState;
|
|
3123
|
-
intercomEvents: IntercomEventBus;
|
|
3124
|
-
parentSessionId: string | null;
|
|
3125
|
-
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
3126
|
-
runFanoutBudget: RunFanoutBudgetDescriptor;
|
|
3127
|
-
signal: AbortSignal;
|
|
3128
|
-
runId: string;
|
|
3129
|
-
sessionDirForIndex: (idx?: number) => string | undefined;
|
|
3130
|
-
sessionFileForIndex: (idx?: number) => string | undefined;
|
|
3131
|
-
sessionFileForTask: ForkSessionFileForTask;
|
|
3132
|
-
thinkingOverrideForTask: ForkThinkingOverrideForTask;
|
|
3133
|
-
shareEnabled: boolean;
|
|
3134
|
-
artifactConfig: ArtifactConfig;
|
|
3135
|
-
artifactsDir: string;
|
|
3136
|
-
outputBaseDir: string;
|
|
3137
|
-
maxOutput?: MaxOutputConfig;
|
|
3138
|
-
paramsCwd: string;
|
|
3139
|
-
progressDir: string;
|
|
3140
|
-
maxSubagentDepths: number[];
|
|
3141
|
-
waitToolEnabled?: boolean;
|
|
3142
|
-
availableModels: ModelInfo[];
|
|
3143
|
-
modelScope?: ModelScopeConfig;
|
|
3144
|
-
parentModel?: ParentModel;
|
|
3145
|
-
modelOverrides: (string | undefined)[];
|
|
3146
|
-
behaviors: Array<ReturnType<typeof resolveStepBehavior>>;
|
|
3147
|
-
firstProgressIndex: number;
|
|
3148
|
-
controlConfig: ResolvedControlConfig;
|
|
3149
|
-
contextPolicy: AgentDefaultContextPolicy;
|
|
3150
|
-
onControlEvent?: (event: ControlEvent) => void;
|
|
3151
|
-
childIntercomTarget?: (agent: string, index: number) => string | undefined;
|
|
3152
|
-
orchestratorIntercomTarget?: string;
|
|
3153
|
-
foregroundControl?: SubagentState["foregroundControls"] extends Map<string, infer T> ? T : never;
|
|
3154
|
-
concurrencyLimit: number;
|
|
3155
|
-
globalSemaphore?: Semaphore;
|
|
3156
|
-
liveResults: (SingleResult | undefined)[];
|
|
3157
|
-
liveProgress: (AgentProgress | undefined)[];
|
|
3158
|
-
onUpdate?: (r: AgentToolResult<Details>) => void;
|
|
3159
|
-
worktreeSetup?: WorktreeSetup;
|
|
3160
|
-
timeoutMs?: number;
|
|
3161
|
-
deadlineAt?: number;
|
|
3162
|
-
turnBudget?: ResolvedTurnBudget;
|
|
3163
|
-
usageBudget?: UsageBudgetConfig;
|
|
3164
|
-
toolBudgets: (ResolvedToolBudget | undefined)[];
|
|
3165
|
-
agentContract?: AgentContract;
|
|
3166
|
-
permissions?: ExtensionConfig["permissions"];
|
|
3167
|
-
}
|
|
3168
|
-
function buildParallelModeError(message: string): AgentToolResult<Details> {
|
|
3169
|
-
return {
|
|
3170
|
-
content: [{ type: "text", text: message }],
|
|
3171
|
-
isError: true,
|
|
3172
|
-
details: { mode: "parallel" as const, results: [] },
|
|
3173
|
-
};
|
|
3174
|
-
}
|
|
3175
|
-
|
|
3176
|
-
function acquireClarifiedBackgroundCapacity(
|
|
3177
|
-
data: ExecutionContextData,
|
|
3178
|
-
deps: ExecutorDeps,
|
|
3179
|
-
mode: "single" | "parallel" | "chain",
|
|
3180
|
-
asyncRunId: string,
|
|
3181
|
-
): { activeAsyncCapacity?: ActiveAsyncCapacityHandle; errorResult?: AgentToolResult<Details> } {
|
|
3182
|
-
if (!data.topLevelAsyncCapacityEligible || !data.parentSessionId) return {};
|
|
3183
|
-
const liveWorkflowRunIds = new Set(deps.state.workflowControllers?.keys() ?? []);
|
|
3184
|
-
const activeLimit = resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession);
|
|
3185
|
-
try {
|
|
3186
|
-
const activeAsyncCapacity = acquireActiveAsyncCapacity({
|
|
3187
|
-
sessionId: data.parentSessionId,
|
|
3188
|
-
limit: activeLimit,
|
|
3189
|
-
runId: asyncRunId,
|
|
3190
|
-
kind: "runner",
|
|
3191
|
-
asyncDir: path.join(DIRS.async, asyncRunId),
|
|
3192
|
-
}, { liveWorkflowRunIds });
|
|
3193
|
-
deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(data.parentSessionId, activeLimit, { liveWorkflowRunIds });
|
|
3194
|
-
return { activeAsyncCapacity };
|
|
3195
|
-
} catch (error) {
|
|
3196
|
-
if (error instanceof ActiveAsyncCapacityError) {
|
|
3197
|
-
deps.state.activeAsyncCapacity = error.snapshot;
|
|
3198
|
-
return { errorResult: { content: [{ type: "text", text: error.message }], isError: true, details: { mode, results: [], activeAsyncCapacity: error.snapshot } } };
|
|
3199
|
-
}
|
|
3200
|
-
throw error;
|
|
3201
|
-
}
|
|
3202
|
-
}
|
|
3203
|
-
|
|
3204
|
-
function finalizeClarifiedBackgroundCapacity(
|
|
3205
|
-
data: ExecutionContextData,
|
|
3206
|
-
deps: ExecutorDeps,
|
|
3207
|
-
activeAsyncCapacity: ActiveAsyncCapacityHandle | undefined,
|
|
3208
|
-
): void {
|
|
3209
|
-
if (!activeAsyncCapacity || !data.parentSessionId) return;
|
|
3210
|
-
if (!activeAsyncCapacity.owner.runnerStartedAt) activeAsyncCapacity.rollback();
|
|
3211
|
-
deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(
|
|
3212
|
-
data.parentSessionId,
|
|
3213
|
-
resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession),
|
|
3214
|
-
{ liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) },
|
|
3215
|
-
);
|
|
3216
|
-
}
|
|
3217
|
-
|
|
3218
|
-
function createParallelWorktreeSetup(
|
|
2833
|
+
function createSingleWorktreeSetup(
|
|
3219
2834
|
enabled: boolean | undefined,
|
|
3220
2835
|
cwd: string,
|
|
3221
2836
|
runId: string,
|
|
3222
|
-
|
|
2837
|
+
agent: string,
|
|
3223
2838
|
setupHook: ExtensionConfig["worktreeSetupHook"],
|
|
3224
2839
|
setupHookTimeoutMs: ExtensionConfig["worktreeSetupHookTimeoutMs"],
|
|
3225
2840
|
baseDir: ExtensionConfig["worktreeBaseDir"],
|
|
@@ -3227,8 +2842,8 @@ function createParallelWorktreeSetup(
|
|
|
3227
2842
|
if (!enabled) return {};
|
|
3228
2843
|
try {
|
|
3229
2844
|
return {
|
|
3230
|
-
setup: createWorktrees(cwd, runId,
|
|
3231
|
-
agents:
|
|
2845
|
+
setup: createWorktrees(cwd, runId, 1, omitUndefinedProperties({
|
|
2846
|
+
agents: [agent],
|
|
3232
2847
|
setupHook: setupHook
|
|
3233
2848
|
? { hookPath: setupHook, ...(setupHookTimeoutMs === undefined ? {} : { timeoutMs: setupHookTimeoutMs }) }
|
|
3234
2849
|
: undefined,
|
|
@@ -3237,27 +2852,25 @@ function createParallelWorktreeSetup(
|
|
|
3237
2852
|
};
|
|
3238
2853
|
} catch (error) {
|
|
3239
2854
|
const message = error instanceof Error ? error.message : String(error);
|
|
3240
|
-
return { errorResult:
|
|
2855
|
+
return { errorResult: { content: [{ type: "text", text: message }], isError: true, details: { mode: "single", results: [] } } };
|
|
3241
2856
|
}
|
|
3242
2857
|
}
|
|
3243
2858
|
|
|
3244
|
-
function buildParallelWorktreeTaskCwdError(
|
|
3245
|
-
tasks: ReadonlyArray<{ agent: string; cwd?: string }>,
|
|
3246
|
-
sharedCwd: string,
|
|
3247
|
-
): string | undefined {
|
|
3248
|
-
const conflict = findWorktreeTaskCwdConflict(tasks, sharedCwd);
|
|
3249
|
-
if (!conflict) return undefined;
|
|
3250
|
-
return formatWorktreeTaskCwdConflict(conflict, sharedCwd);
|
|
3251
|
-
}
|
|
3252
|
-
|
|
3253
2859
|
function resolveConfiguredSingleRunOutputBaseDir(deps: ExecutorDeps): string | undefined {
|
|
3254
2860
|
return deps.config.singleRunOutputBaseDir
|
|
3255
2861
|
? path.resolve(deps.expandTilde(deps.config.singleRunOutputBaseDir))
|
|
3256
2862
|
: undefined;
|
|
3257
2863
|
}
|
|
3258
2864
|
|
|
2865
|
+
export function sanitizeRunPathSegment(value: string, maxBytes = 120): string {
|
|
2866
|
+
const sanitized = value.trim().replace(/[^A-Za-z0-9._-]+/g, "_").replace(/^_+|_+$/g, "");
|
|
2867
|
+
if (!sanitized) return "unknown";
|
|
2868
|
+
if (Buffer.byteLength(sanitized, "utf-8") <= maxBytes) return sanitized;
|
|
2869
|
+
return sanitized.slice(0, maxBytes).replace(/_+$/, "") || "unknown";
|
|
2870
|
+
}
|
|
2871
|
+
|
|
3259
2872
|
function resolveSingleRunOutputBaseDir(deps: ExecutorDeps, artifactsDir: string, runId: string): string {
|
|
3260
|
-
return resolveConfiguredSingleRunOutputBaseDir(deps) ?? path.join(artifactsDir, "outputs", runId);
|
|
2873
|
+
return resolveConfiguredSingleRunOutputBaseDir(deps) ?? path.join(artifactsDir, "outputs", sanitizeRunPathSegment(runId));
|
|
3261
2874
|
}
|
|
3262
2875
|
|
|
3263
2876
|
function resolveWorkflowAggregateOutputPath(
|
|
@@ -3276,814 +2889,157 @@ function workflowChildDefaultOutput(aggregateOutputPath: string | undefined, art
|
|
|
3276
2889
|
const parsed = path.parse(aggregateOutputPath);
|
|
3277
2890
|
return path.join(parsed.dir, `${parsed.name}.${workflowKey}${parsed.ext || ".md"}`);
|
|
3278
2891
|
}
|
|
3279
|
-
return path.join(artifactsDir, "outputs", workflowRunId, `${workflowKey}.md`);
|
|
2892
|
+
return path.join(artifactsDir, "outputs", sanitizeRunPathSegment(workflowRunId), `${workflowKey}.md`);
|
|
3280
2893
|
}
|
|
3281
2894
|
|
|
3282
|
-
function writeWorkflowAggregateOutput(outputPath: string | undefined, text: string): string | undefined {
|
|
2895
|
+
function writeWorkflowAggregateOutput(outputPath: string | undefined, text: string, producedChildOutputPaths: ReadonlySet<string>): string | undefined {
|
|
3283
2896
|
if (!outputPath) return undefined;
|
|
3284
2897
|
try {
|
|
2898
|
+
if (producedChildOutputPaths.has(outputPath)) return undefined;
|
|
3285
2899
|
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
3286
2900
|
fs.writeFileSync(outputPath, text, "utf-8");
|
|
3287
2901
|
return undefined;
|
|
3288
|
-
} catch (error) {
|
|
3289
|
-
return `Output file error: ${outputPath}\n${error instanceof Error ? error.message : String(error)}`;
|
|
3290
|
-
}
|
|
3291
|
-
}
|
|
3292
|
-
|
|
3293
|
-
function appendWorkflowOutputWarning(text: string, warning: string | undefined): string {
|
|
3294
|
-
return warning ? `${text}\n\n${warning}` : text;
|
|
3295
|
-
}
|
|
3296
|
-
|
|
3297
|
-
function resolveWorkflowChildOutputPath(input: {
|
|
3298
|
-
ctxCwd: string;
|
|
3299
|
-
workflowCwd: string;
|
|
3300
|
-
artifactsDir: string;
|
|
3301
|
-
workflowRunId: string;
|
|
3302
|
-
aggregateOutputPath?: string;
|
|
3303
|
-
configuredOutputBaseDir?: string;
|
|
3304
|
-
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[] };
|
|
3305
|
-
workflowAgentScope?: unknown;
|
|
3306
|
-
key: string;
|
|
3307
|
-
params: Record<string, unknown>;
|
|
3308
|
-
}): string | undefined {
|
|
3309
|
-
const rawOutput = input.params.output;
|
|
3310
|
-
const hasExplicitOutput = typeof rawOutput === "string" || typeof rawOutput === "boolean";
|
|
3311
|
-
const childCwd = typeof input.params.cwd === "string" ? resolveChildCwd(input.workflowCwd, input.params.cwd) : input.workflowCwd;
|
|
3312
|
-
const agentScope = resolveExecutionAgentScope(input.params.agentScope ?? input.workflowAgentScope);
|
|
3313
|
-
const agents = input.discoverAgents(childCwd, agentScope).agents;
|
|
3314
|
-
const agent = typeof input.params.agent === "string" ? resolveAgentName(input.params.agent, agents).agent : undefined;
|
|
3315
|
-
const agentOutput = typeof agent?.output === "string" ? agent.output : undefined;
|
|
3316
|
-
const output = rawOutput === true || rawOutput === "true"
|
|
3317
|
-
? agentOutput
|
|
3318
|
-
: hasExplicitOutput
|
|
3319
|
-
? rawOutput
|
|
3320
|
-
: workflowChildDefaultOutput(input.aggregateOutputPath, input.artifactsDir, input.workflowRunId, input.key);
|
|
3321
|
-
return resolveSingleOutputPath(output, input.ctxCwd, childCwd, input.configuredOutputBaseDir);
|
|
3322
|
-
}
|
|
3323
|
-
|
|
3324
|
-
function workflowChildOutputClaims(input: {
|
|
3325
|
-
ctxCwd: string;
|
|
3326
|
-
workflowCwd: string;
|
|
3327
|
-
artifactsDir: string;
|
|
3328
|
-
workflowRunId: string;
|
|
3329
|
-
aggregateOutputPath?: string;
|
|
3330
|
-
configuredOutputBaseDir?: string;
|
|
3331
|
-
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[] };
|
|
3332
|
-
workflowAgentScope?: unknown;
|
|
3333
|
-
claimedOutputPaths: Map<string, string>;
|
|
3334
|
-
entries: Array<{ key: string; params: Record<string, unknown> }>;
|
|
3335
|
-
}): { error?: string; claims?: Map<string, string> } {
|
|
3336
|
-
const claims = new Map(input.claimedOutputPaths);
|
|
3337
|
-
for (const { key, params } of input.entries) {
|
|
3338
|
-
const resolved = resolveWorkflowChildOutputPath({ ...input, key, params });
|
|
3339
|
-
if (!resolved) continue;
|
|
3340
|
-
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.` };
|
|
3341
|
-
const previous = claims.get(resolved);
|
|
3342
|
-
if (previous) return { error: `Workflow children '${previous}' and '${key}' resolve output to the same path: ${resolved}. Use distinct child output paths.` };
|
|
3343
|
-
claims.set(resolved, key);
|
|
3344
|
-
}
|
|
3345
|
-
return { claims };
|
|
3346
|
-
}
|
|
3347
|
-
|
|
3348
|
-
function applyWorkflowChildOutputClaims(target: Map<string, string>, claims: Map<string, string>): void {
|
|
3349
|
-
target.clear();
|
|
3350
|
-
for (const [resolved, key] of claims) target.set(resolved, key);
|
|
3351
|
-
}
|
|
3352
|
-
|
|
3353
|
-
function prepareWorkflowChildLaunchParams(input: {
|
|
3354
|
-
workflowDefaults: SubagentParamsLike;
|
|
3355
|
-
childParams: Record<string, unknown>;
|
|
3356
|
-
parentWorkflowRunId: string;
|
|
3357
|
-
workflowKey: string;
|
|
3358
|
-
ctxCwd: string;
|
|
3359
|
-
workflowCwd: string;
|
|
3360
|
-
artifactsDir: string;
|
|
3361
|
-
aggregateOutputPath?: string;
|
|
3362
|
-
configuredOutputBaseDir?: string;
|
|
3363
|
-
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[] };
|
|
3364
|
-
workflowAgentScope?: unknown;
|
|
3365
|
-
options?: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean; runFanoutBudget?: RunFanoutBudgetDescriptor };
|
|
3366
|
-
}): SubagentParamsLike {
|
|
3367
|
-
let childParams = input.childParams;
|
|
3368
|
-
if (input.childParams.output === undefined && input.childParams.resume === undefined) {
|
|
3369
|
-
childParams = { ...input.childParams, output: workflowChildDefaultOutput(input.aggregateOutputPath, input.artifactsDir, input.parentWorkflowRunId, input.workflowKey) };
|
|
3370
|
-
} else if (input.childParams.resume === undefined) {
|
|
3371
|
-
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 });
|
|
3372
|
-
if (resolvedOutput) childParams = { ...input.childParams, output: resolvedOutput };
|
|
3373
|
-
}
|
|
3374
|
-
return prepareWorkflowLaunchParams(input.workflowDefaults, childParams, input.parentWorkflowRunId, input.workflowKey, input.options);
|
|
3375
|
-
}
|
|
3376
|
-
|
|
3377
|
-
function buildChainWorktreeTaskCwdError(chain: ChainStep[], sharedCwd: string): string | undefined {
|
|
3378
|
-
for (let stepIndex = 0; stepIndex < chain.length; stepIndex++) {
|
|
3379
|
-
const step = chain[stepIndex]!;
|
|
3380
|
-
if (!isParallelStep(step) || !step.worktree) continue;
|
|
3381
|
-
const stepCwd = resolveChildCwd(sharedCwd, step.cwd);
|
|
3382
|
-
const conflict = findWorktreeTaskCwdConflict(step.parallel, stepCwd);
|
|
3383
|
-
if (!conflict) continue;
|
|
3384
|
-
const detail = formatWorktreeTaskCwdConflict(conflict, stepCwd);
|
|
3385
|
-
return `parallel chain step ${stepIndex + 1}: ${detail}`;
|
|
3386
|
-
}
|
|
3387
|
-
return undefined;
|
|
3388
|
-
}
|
|
3389
|
-
|
|
3390
|
-
function resolveParallelTaskCwd(
|
|
3391
|
-
task: TaskParam,
|
|
3392
|
-
paramsCwd: string,
|
|
3393
|
-
worktreeSetup: WorktreeSetup | undefined,
|
|
3394
|
-
index: number,
|
|
3395
|
-
): string {
|
|
3396
|
-
if (worktreeSetup) return worktreeSetup.worktrees[index]!.agentCwd;
|
|
3397
|
-
return resolveChildCwd(paramsCwd, task.cwd);
|
|
3398
|
-
}
|
|
3399
|
-
|
|
3400
|
-
function finalizeParallelWorktreeHandoff(input: {
|
|
3401
|
-
worktreeSetup: WorktreeSetup;
|
|
3402
|
-
artifactsDir: string;
|
|
3403
|
-
runId: string;
|
|
3404
|
-
cwd: string;
|
|
3405
|
-
tasks: TaskParam[];
|
|
3406
|
-
results: SingleResult[];
|
|
3407
|
-
}): { suffix: string; reference?: NonNullable<Details["parallelHandoff"]> } {
|
|
3408
|
-
const diffsDir = path.join(input.artifactsDir, "worktree-diffs", input.runId);
|
|
3409
|
-
const diffs = diffWorktrees(input.worktreeSetup, input.tasks.map((task) => task.agent), diffsDir);
|
|
3410
|
-
const diffSummary = formatWorktreeDiffSummary(diffs);
|
|
3411
|
-
const manifestPath = parallelHandoffPath(input.artifactsDir, input.runId);
|
|
3412
|
-
const handoff = {
|
|
3413
|
-
manifestPath,
|
|
3414
|
-
runId: input.runId,
|
|
3415
|
-
mode: "parallel" as const,
|
|
3416
|
-
source: "foreground" as const,
|
|
3417
|
-
cwd: input.cwd,
|
|
3418
|
-
stepIndex: 0,
|
|
3419
|
-
flatStartIndex: 0,
|
|
3420
|
-
setup: input.worktreeSetup,
|
|
3421
|
-
diffs,
|
|
3422
|
-
results: input.results.map((result) => ({
|
|
3423
|
-
agent: result.agent,
|
|
3424
|
-
status: resolveSubagentResultStatus(omitUndefinedProperties({
|
|
3425
|
-
exitCode: result.exitCode,
|
|
3426
|
-
interrupted: result.interrupted,
|
|
3427
|
-
detached: result.detached,
|
|
3428
|
-
state: result.stopped ? "stopped" : undefined,
|
|
3429
|
-
processSignal: result.processSignal,
|
|
3430
|
-
timedOut: result.timedOut,
|
|
3431
|
-
stopped: result.stopped,
|
|
3432
|
-
turnBudgetExceeded: result.turnBudgetExceeded,
|
|
3433
|
-
})),
|
|
3434
|
-
summary: resultSummaryForIntercom(result),
|
|
3435
|
-
...(result.artifactPaths?.outputPath ? { outputPath: result.artifactPaths.outputPath } : {}),
|
|
3436
|
-
...(result.structuredOutput !== undefined ? { structuredOutput: result.structuredOutput } : {}),
|
|
3437
|
-
...(result.structuredOutputPath ? { structuredOutputPath: result.structuredOutputPath } : {}),
|
|
3438
|
-
...(result.sessionFile ? { sessionPath: result.sessionFile } : {}),
|
|
3439
|
-
})),
|
|
3440
|
-
};
|
|
3441
|
-
try {
|
|
3442
|
-
writeParallelHandoffGroup(handoff);
|
|
3443
|
-
const cleanup = cleanupWorktrees(input.worktreeSetup, { kind: "preserve", capturedDiffs: diffs, handoffManifestPath: manifestPath });
|
|
3444
|
-
const reference = writeParallelHandoffGroup({ ...handoff, cleanup });
|
|
3445
|
-
return {
|
|
3446
|
-
suffix: [diffSummary, formatParallelHandoffReference(reference)].filter(Boolean).join("\n\n"),
|
|
3447
|
-
reference,
|
|
3448
|
-
};
|
|
3449
|
-
} catch (error) {
|
|
3450
|
-
return { suffix: [diffSummary, formatParallelHandoffError(error)].filter(Boolean).join("\n\n") };
|
|
3451
|
-
}
|
|
3452
|
-
}
|
|
3453
|
-
|
|
3454
|
-
function findDuplicateParallelOutputPath(input: {
|
|
3455
|
-
tasks: TaskParam[];
|
|
3456
|
-
behaviors: ResolvedStepBehavior[];
|
|
3457
|
-
paramsCwd: string;
|
|
3458
|
-
ctxCwd: string;
|
|
3459
|
-
outputBaseDir: string;
|
|
3460
|
-
worktreeSetup?: WorktreeSetup;
|
|
3461
|
-
}): string | undefined {
|
|
3462
|
-
const seen = new Map<string, { index: number; agent: string }>();
|
|
3463
|
-
for (let index = 0; index < input.tasks.length; index++) {
|
|
3464
|
-
const behavior = input.behaviors[index];
|
|
3465
|
-
if (!behavior?.output) continue;
|
|
3466
|
-
const task = input.tasks[index]!;
|
|
3467
|
-
const taskCwd = resolveParallelTaskCwd(task, input.paramsCwd, input.worktreeSetup, index);
|
|
3468
|
-
const outputPath = resolveSingleOutputPath(behavior.output, input.ctxCwd, taskCwd, input.outputBaseDir);
|
|
3469
|
-
if (!outputPath) continue;
|
|
3470
|
-
const previous = seen.get(outputPath);
|
|
3471
|
-
if (previous) {
|
|
3472
|
-
return `Parallel tasks ${previous.index + 1} (${previous.agent}) and ${index + 1} (${task.agent}) resolve output to the same path: ${outputPath}. Use distinct output paths.`;
|
|
3473
|
-
}
|
|
3474
|
-
seen.set(outputPath, { index, agent: task.agent });
|
|
3475
|
-
}
|
|
3476
|
-
return undefined;
|
|
3477
|
-
}
|
|
3478
|
-
|
|
3479
|
-
async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Promise<SingleResult[]> {
|
|
3480
|
-
// Pre-warm fork session files sequentially before concurrent dispatch to avoid
|
|
3481
|
-
// races where multiple workers simultaneously try to branch the same parent session.
|
|
3482
|
-
for (let i = 0; i < input.tasks.length; i++) {
|
|
3483
|
-
input.sessionFileForTask(input.tasks[i]!.agent, i, input.modelOverrides[i]);
|
|
3484
|
-
}
|
|
3485
|
-
const completedResults: SingleResult[] = [];
|
|
3486
|
-
if (input.foregroundControl) retainForegroundSchedulingOwner(input.foregroundControl);
|
|
3487
|
-
return mapConcurrent(input.tasks, input.concurrencyLimit, async (task, index) => {
|
|
3488
|
-
const budgetState = usageBudgetState(input.usageBudget, sumResultsCost(completedResults));
|
|
3489
|
-
if (budgetState?.exhausted) {
|
|
3490
|
-
return {
|
|
3491
|
-
index,
|
|
3492
|
-
agent: task.agent,
|
|
3493
|
-
task: input.taskTexts[index] ?? "(skipped)",
|
|
3494
|
-
exitCode: 1,
|
|
3495
|
-
messages: [],
|
|
3496
|
-
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 },
|
|
3497
|
-
error: usageBudgetExceededMessage(budgetState),
|
|
3498
|
-
skipped: true,
|
|
3499
|
-
} as SingleResult;
|
|
3500
|
-
}
|
|
3501
|
-
const behavior = input.behaviors[index];
|
|
3502
|
-
const effectiveSkills = behavior?.skills;
|
|
3503
|
-
const taskCwd = resolveParallelTaskCwd(task, input.paramsCwd, input.worktreeSetup, index);
|
|
3504
|
-
const readInstructions = behavior
|
|
3505
|
-
? buildChainInstructions({ ...behavior, output: false, progress: false }, taskCwd, false)
|
|
3506
|
-
: { prefix: "", suffix: "" };
|
|
3507
|
-
const progressInstructions = behavior
|
|
3508
|
-
? buildChainInstructions({ ...behavior, output: false, reads: false }, input.progressDir, index === input.firstProgressIndex)
|
|
3509
|
-
: { prefix: "", suffix: "" };
|
|
3510
|
-
const outputPath = resolveSingleOutputPath(behavior?.output, input.ctx.cwd, taskCwd, input.outputBaseDir);
|
|
3511
|
-
const agentConfig = input.agents.find((agent) => agent.name === task.agent);
|
|
3512
|
-
const taskText = injectSingleOutputInstruction(
|
|
3513
|
-
`${readInstructions.prefix}${input.taskTexts[index]!}${progressInstructions.suffix}`,
|
|
3514
|
-
outputPath,
|
|
3515
|
-
agentConfig,
|
|
3516
|
-
);
|
|
3517
|
-
const interruptController = new AbortController();
|
|
3518
|
-
if (input.foregroundControl) {
|
|
3519
|
-
const model = input.modelOverrides[index];
|
|
3520
|
-
const thinking = resolveEffectiveThinking(model, input.thinkingOverrideForTask(task.agent, index, model));
|
|
3521
|
-
beginForegroundChild(input.foregroundControl, {
|
|
3522
|
-
index,
|
|
3523
|
-
agent: task.agent,
|
|
3524
|
-
authoredTask: input.taskDescriptions[index] ?? "",
|
|
3525
|
-
effectivePrompt: taskText,
|
|
3526
|
-
cwd: taskCwd,
|
|
3527
|
-
...(outputPath ? { outputPath } : {}),
|
|
3528
|
-
rerun: { params: compactOptional<Record<string, unknown>>({
|
|
3529
|
-
agent: task.agent,
|
|
3530
|
-
task: input.taskDescriptions[index] ?? "",
|
|
3531
|
-
async: false,
|
|
3532
|
-
cwd: input.worktreeSetup ? undefined : taskCwd,
|
|
3533
|
-
worktree: input.worktreeSetup ? true : false,
|
|
3534
|
-
context: input.contextPolicy.contextForAgent(task.agent),
|
|
3535
|
-
share: input.shareEnabled || undefined,
|
|
3536
|
-
maxOutput: input.maxOutput,
|
|
3537
|
-
timeoutMs: input.timeoutMs,
|
|
3538
|
-
turnBudget: input.turnBudget,
|
|
3539
|
-
...(model ? { model } : {}),
|
|
3540
|
-
...(effectiveSkills !== undefined ? { skill: effectiveSkills } : {}),
|
|
3541
|
-
...(outputPath ? { output: outputPath } : {}),
|
|
3542
|
-
...(behavior?.outputMode !== undefined ? { outputMode: behavior.outputMode } : {}),
|
|
3543
|
-
...(behavior?.reads !== undefined ? { reads: behavior.reads } : {}),
|
|
3544
|
-
...(behavior?.progress !== undefined ? { progress: behavior.progress } : {}),
|
|
3545
|
-
...(task.toolBudget !== undefined ? { toolBudget: task.toolBudget } : {}),
|
|
3546
|
-
...(task.outputSchema !== undefined ? { outputSchema: task.outputSchema } : {}),
|
|
3547
|
-
...(task.acceptance !== undefined ? { acceptance: task.acceptance } : {}),
|
|
3548
|
-
...(task.agentContract !== undefined || input.agentContract !== undefined ? { agentContract: task.agentContract ?? input.agentContract } : {}),
|
|
3549
|
-
}) },
|
|
3550
|
-
description: `${task.agent} child`,
|
|
3551
|
-
...(model ? { model } : {}),
|
|
3552
|
-
...(thinking ? { thinking } : {}),
|
|
3553
|
-
interrupt: () => {
|
|
3554
|
-
if (interruptController.signal.aborted) return false;
|
|
3555
|
-
interruptController.abort();
|
|
3556
|
-
return true;
|
|
3557
|
-
},
|
|
3558
|
-
});
|
|
3559
|
-
}
|
|
3560
|
-
const structuredRuntime = task.outputSchema
|
|
3561
|
-
? createStructuredOutputRuntime(task.outputSchema, path.join(input.artifactsDir, "structured-output", input.runId))
|
|
3562
|
-
: undefined;
|
|
3563
|
-
let detachedReceipt = false;
|
|
3564
|
-
const result = await runSync(input.ctx.cwd, input.agents, task.agent, taskText, compactOptional<Parameters<typeof runSync>[4]>({
|
|
3565
|
-
permissions: input.permissions,
|
|
3566
|
-
parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
|
|
3567
|
-
llmIntentArbiter: createTaskMutationArbiter(input.ctx),
|
|
3568
|
-
...workflowForegroundSteeringLaunchOptions(input.foregroundControl, index),
|
|
3569
|
-
context: input.contextPolicy.contextForAgent(task.agent),
|
|
3570
|
-
runFanoutBudget: { ...input.runFanoutBudget, parentPath: `${input.runFanoutBudget.parentPath ? `${input.runFanoutBudget.parentPath}/` : ""}tasks[${index}]` },
|
|
3571
|
-
cwd: taskCwd,
|
|
3572
|
-
signal: input.signal,
|
|
3573
|
-
interruptSignal: interruptController.signal,
|
|
3574
|
-
allowIntercomDetach: agentConfig?.systemPrompt?.includes(INTERCOM_BRIDGE_MARKER) === true,
|
|
3575
|
-
intercomEvents: input.intercomEvents,
|
|
3576
|
-
runId: input.runId,
|
|
3577
|
-
index,
|
|
3578
|
-
sessionDir: input.sessionDirForIndex(index),
|
|
3579
|
-
sessionFile: input.sessionFileForTask(task.agent, index, input.modelOverrides[index]),
|
|
3580
|
-
share: input.shareEnabled,
|
|
3581
|
-
artifactsDir: input.artifactConfig.enabled ? input.artifactsDir : undefined,
|
|
3582
|
-
artifactConfig: input.artifactConfig,
|
|
3583
|
-
maxOutput: input.maxOutput,
|
|
3584
|
-
outputPath,
|
|
3585
|
-
outputMode: behavior?.outputMode,
|
|
3586
|
-
maxSubagentDepth: input.maxSubagentDepths[index],
|
|
3587
|
-
waitToolEnabled: input.waitToolEnabled,
|
|
3588
|
-
capabilityCeiling: input.capabilityCeiling,
|
|
3589
|
-
controlConfig: input.controlConfig,
|
|
3590
|
-
onControlEvent: input.onControlEvent,
|
|
3591
|
-
onDetachedExit: (result) => {
|
|
3592
|
-
try {
|
|
3593
|
-
updateRememberedForegroundChild(input.state, { runId: input.runId, mode: "parallel", cwd: taskCwd, sessionId: input.parentSessionId, index, result, events: input.intercomEvents });
|
|
3594
|
-
} finally {
|
|
3595
|
-
try {
|
|
3596
|
-
if (input.foregroundControl) finishForegroundChild(input.foregroundControl, index);
|
|
3597
|
-
} finally {
|
|
3598
|
-
removeForegroundControlIfIdle(input.state, input.runId);
|
|
3599
|
-
}
|
|
3600
|
-
}
|
|
3601
|
-
},
|
|
3602
|
-
intercomSessionName: input.childIntercomTarget?.(task.agent, index),
|
|
3603
|
-
orchestratorIntercomTarget: input.orchestratorIntercomTarget,
|
|
3604
|
-
nestedRoute: input.foregroundControl?.nestedRoute,
|
|
3605
|
-
modelOverride: input.modelOverrides[index],
|
|
3606
|
-
thinkingOverride: input.thinkingOverrideForTask(task.agent, index, input.modelOverrides[index]),
|
|
3607
|
-
availableModels: input.availableModels,
|
|
3608
|
-
preferredModelProvider: input.parentModel?.provider,
|
|
3609
|
-
modelScope: input.modelScope,
|
|
3610
|
-
skills: effectiveSkills === false ? [] : effectiveSkills,
|
|
3611
|
-
structuredOutput: structuredRuntime,
|
|
3612
|
-
agentContract: task.agentContract ?? input.agentContract,
|
|
3613
|
-
acceptance: task.acceptance,
|
|
3614
|
-
acceptanceContext: { mode: "parallel" },
|
|
3615
|
-
onEffectivePrompt: input.foregroundControl ? (prompt) => updateLiveEffectivePrompt(input.foregroundControl!, index, prompt) : undefined,
|
|
3616
|
-
timeoutMs: input.timeoutMs,
|
|
3617
|
-
deadlineAt: input.deadlineAt,
|
|
3618
|
-
turnBudget: input.turnBudget,
|
|
3619
|
-
toolBudget: input.toolBudgets[index],
|
|
3620
|
-
onUpdate: input.onUpdate
|
|
3621
|
-
? (progressUpdate) => {
|
|
3622
|
-
const stepResults = progressUpdate.details?.results || [];
|
|
3623
|
-
const stepProgress = progressUpdate.details?.progress || [];
|
|
3624
|
-
if (input.foregroundControl && stepProgress.length > 0) {
|
|
3625
|
-
updateForegroundChild(input.foregroundControl, index, stepProgress[0]);
|
|
3626
|
-
}
|
|
3627
|
-
if (stepResults.length > 0) input.liveResults[index] = stepResults[0];
|
|
3628
|
-
if (stepProgress.length > 0) input.liveProgress[index] = stepProgress[0];
|
|
3629
|
-
const mergedResults = input.liveResults.filter((result): result is SingleResult => result !== undefined);
|
|
3630
|
-
const mergedProgress = input.liveProgress.filter((progress): progress is AgentProgress => progress !== undefined);
|
|
3631
|
-
input.onUpdate?.({
|
|
3632
|
-
content: progressUpdate.content,
|
|
3633
|
-
details: compactOptional<Details>({
|
|
3634
|
-
mode: "parallel",
|
|
3635
|
-
results: mergedResults,
|
|
3636
|
-
progress: mergedProgress,
|
|
3637
|
-
controlEvents: progressUpdate.details?.controlEvents,
|
|
3638
|
-
totalSteps: input.tasks.length,
|
|
3639
|
-
}),
|
|
3640
|
-
});
|
|
3641
|
-
}
|
|
3642
|
-
: undefined,
|
|
3643
|
-
})).then((result) => {
|
|
3644
|
-
detachedReceipt = result.detached === true;
|
|
3645
|
-
return result;
|
|
3646
|
-
}).finally(() => {
|
|
3647
|
-
// mapConcurrent rejects before siblings settle, so every attached child
|
|
3648
|
-
// attempts idle removal after releasing its own control. Detached receipts
|
|
3649
|
-
// transfer both responsibilities to the authoritative exit callback.
|
|
3650
|
-
if (!detachedReceipt) {
|
|
3651
|
-
if (input.foregroundControl) finishForegroundChild(input.foregroundControl, index);
|
|
3652
|
-
removeForegroundControlIfIdle(input.state, input.runId);
|
|
3653
|
-
}
|
|
3654
|
-
});
|
|
3655
|
-
completedResults.push(result);
|
|
3656
|
-
return result;
|
|
3657
|
-
}, input.globalSemaphore, () => {
|
|
3658
|
-
if (input.foregroundControl) settleForegroundSchedulingOwner(input.foregroundControl);
|
|
3659
|
-
removeForegroundControlIfIdle(input.state, input.runId);
|
|
3660
|
-
});
|
|
3661
|
-
}
|
|
3662
|
-
|
|
3663
|
-
async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps): Promise<AgentToolResult<Details>> {
|
|
3664
|
-
const {
|
|
3665
|
-
params,
|
|
3666
|
-
effectiveCwd,
|
|
3667
|
-
agents,
|
|
3668
|
-
ctx,
|
|
3669
|
-
signal,
|
|
3670
|
-
runId,
|
|
3671
|
-
sessionDirForIndex,
|
|
3672
|
-
sessionFileForIndex,
|
|
3673
|
-
sessionFileForTask,
|
|
3674
|
-
thinkingOverrideForTask,
|
|
3675
|
-
shareEnabled,
|
|
3676
|
-
artifactConfig,
|
|
3677
|
-
artifactsDir,
|
|
3678
|
-
backgroundRequestedWhileClarifying,
|
|
3679
|
-
onUpdate,
|
|
3680
|
-
sessionRoot,
|
|
3681
|
-
controlConfig,
|
|
3682
|
-
contextPolicy,
|
|
3683
|
-
} = data;
|
|
3684
|
-
const onControlEvent = createForegroundControlNotifier(data, deps);
|
|
3685
|
-
const childIntercomTarget = data.intercomBridge.active ? resolveSubagentIntercomTarget : undefined;
|
|
3686
|
-
const allProgress: AgentProgress[] = [];
|
|
3687
|
-
const allArtifactPaths: ArtifactPaths[] = [];
|
|
3688
|
-
const tasks = params.tasks!;
|
|
3689
|
-
const maxParallelTasks = resolveTopLevelParallelMaxTasks(deps.config.parallel?.maxTasks);
|
|
3690
|
-
const parallelConcurrency = resolveTopLevelParallelConcurrency(params.concurrency, deps.config.parallel?.concurrency);
|
|
3691
|
-
|
|
3692
|
-
if (tasks.length > maxParallelTasks)
|
|
3693
|
-
return {
|
|
3694
|
-
content: [{ type: "text", text: `Max ${maxParallelTasks} tasks` }],
|
|
3695
|
-
isError: true,
|
|
3696
|
-
details: { mode: "parallel" as const, results: [] },
|
|
3697
|
-
};
|
|
3698
|
-
|
|
3699
|
-
const agentConfigs: AgentConfig[] = [];
|
|
3700
|
-
for (const t of tasks) {
|
|
3701
|
-
const config = agents.find((a) => a.name === t.agent);
|
|
3702
|
-
if (!config) {
|
|
3703
|
-
return {
|
|
3704
|
-
content: [{ type: "text", text: `Unknown agent: ${t.agent}` }],
|
|
3705
|
-
isError: true,
|
|
3706
|
-
details: { mode: "parallel" as const, results: [] },
|
|
3707
|
-
};
|
|
3708
|
-
}
|
|
3709
|
-
agentConfigs.push(config);
|
|
3710
|
-
}
|
|
3711
|
-
|
|
3712
|
-
const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
|
|
3713
|
-
const maxSubagentDepths = agentConfigs.map((config) =>
|
|
3714
|
-
resolveChildMaxSubagentDepth(currentMaxSubagentDepth, config.maxSubagentDepth),
|
|
3715
|
-
);
|
|
3716
|
-
const toolBudgets: (ResolvedToolBudget | undefined)[] = [];
|
|
3717
|
-
for (let index = 0; index < tasks.length; index++) {
|
|
3718
|
-
const resolved = resolveEffectiveToolBudget(omitUndefinedProperties({ stepBudget: tasks[index]?.toolBudget, runBudget: data.toolBudget, agentBudget: agentConfigs[index]?.toolBudget, configBudget: data.configToolBudget }));
|
|
3719
|
-
if (resolved.error) return buildParallelModeError(resolved.error);
|
|
3720
|
-
toolBudgets.push(resolved.toolBudget);
|
|
3721
|
-
}
|
|
3722
|
-
|
|
3723
|
-
if (params.worktree) {
|
|
3724
|
-
const worktreeTaskCwdError = buildParallelWorktreeTaskCwdError(tasks, effectiveCwd);
|
|
3725
|
-
if (worktreeTaskCwdError) return buildParallelModeError(worktreeTaskCwdError);
|
|
3726
|
-
}
|
|
3727
|
-
|
|
3728
|
-
const parentModel = data.parentModel;
|
|
3729
|
-
const currentProvider = parentModel?.provider;
|
|
3730
|
-
const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
3731
|
-
let taskTexts = tasks.map((t) => t.task);
|
|
3732
|
-
const skillOverrides: (string[] | false | undefined)[] = tasks.map((t) =>
|
|
3733
|
-
normalizeSkillInput(t.skill),
|
|
3734
|
-
);
|
|
3735
|
-
const behaviorOverrides: StepOverrides[] = tasks.map((task, index) => ({
|
|
3736
|
-
...(task.output !== undefined && task.output !== true ? { output: task.output } : {}),
|
|
3737
|
-
...(task.outputMode !== undefined ? { outputMode: task.outputMode } : {}),
|
|
3738
|
-
...(task.reads !== undefined && task.reads !== true ? { reads: task.reads } : {}),
|
|
3739
|
-
...(task.progress !== undefined ? { progress: task.progress } : {}),
|
|
3740
|
-
...(skillOverrides[index] !== undefined ? { skills: skillOverrides[index] } : {}),
|
|
3741
|
-
...(task.model !== undefined ? { model: task.model } : {}),
|
|
3742
|
-
}));
|
|
3743
|
-
const modelOverrides: (string | undefined)[] = tasks.map((_, i) =>
|
|
3744
|
-
resolveEffectiveSubagentModel(behaviorOverrides[i]?.model, agentConfigs[i]?.model, parentModel, availableModels, currentProvider, data.modelScope === undefined ? {} : { scope: data.modelScope }),
|
|
3745
|
-
);
|
|
3746
|
-
|
|
3747
|
-
if (params.clarify === true && ctx.hasUI) {
|
|
3748
|
-
const behaviors = agentConfigs.map((c, i) =>
|
|
3749
|
-
resolveStepBehavior(c, behaviorOverrides[i]!),
|
|
3750
|
-
);
|
|
3751
|
-
const availableSkills = discoverAvailableSkills(effectiveCwd);
|
|
3752
|
-
|
|
3753
|
-
const result = await ctx.ui.custom<ChainClarifyResult>(
|
|
3754
|
-
(tui, theme, _kb, done) =>
|
|
3755
|
-
new ChainClarifyComponent(
|
|
3756
|
-
tui, theme,
|
|
3757
|
-
agentConfigs,
|
|
3758
|
-
taskTexts,
|
|
3759
|
-
"",
|
|
3760
|
-
undefined,
|
|
3761
|
-
behaviors,
|
|
3762
|
-
availableModels,
|
|
3763
|
-
currentProvider,
|
|
3764
|
-
availableSkills,
|
|
3765
|
-
done,
|
|
3766
|
-
"parallel",
|
|
3767
|
-
),
|
|
3768
|
-
{ overlay: true, overlayOptions: { anchor: "center", width: 84, maxHeight: "80%" } },
|
|
3769
|
-
);
|
|
3770
|
-
|
|
3771
|
-
if (!result || !result.confirmed) {
|
|
3772
|
-
return { content: [{ type: "text", text: "Cancelled" }], details: { mode: "parallel", results: [] } };
|
|
3773
|
-
}
|
|
3774
|
-
|
|
3775
|
-
taskTexts = result.templates;
|
|
3776
|
-
for (let i = 0; i < result.behaviorOverrides.length; i++) {
|
|
3777
|
-
const override = result.behaviorOverrides[i];
|
|
3778
|
-
if (override?.model !== undefined) {
|
|
3779
|
-
modelOverrides[i] = resolveEffectiveSubagentModel(override.model, agentConfigs[i]?.model, parentModel, availableModels, currentProvider, data.modelScope === undefined ? {} : { scope: data.modelScope });
|
|
3780
|
-
behaviorOverrides[i]!.model = override.model;
|
|
3781
|
-
}
|
|
3782
|
-
if (override?.output !== undefined) behaviorOverrides[i]!.output = override.output;
|
|
3783
|
-
if (override?.reads !== undefined) behaviorOverrides[i]!.reads = override.reads;
|
|
3784
|
-
if (override?.progress !== undefined) behaviorOverrides[i]!.progress = override.progress;
|
|
3785
|
-
if (override?.skills !== undefined) {
|
|
3786
|
-
skillOverrides[i] = override.skills;
|
|
3787
|
-
behaviorOverrides[i]!.skills = override.skills;
|
|
3788
|
-
}
|
|
3789
|
-
}
|
|
3790
|
-
|
|
3791
|
-
if (result.runInBackground) {
|
|
3792
|
-
if (!isAsyncAvailable()) {
|
|
3793
|
-
return {
|
|
3794
|
-
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." }],
|
|
3795
|
-
isError: true,
|
|
3796
|
-
details: { mode: "parallel" as const, results: [] },
|
|
3797
|
-
};
|
|
3798
|
-
}
|
|
3799
|
-
const id = data.asyncRunId;
|
|
3800
|
-
const capacity = acquireClarifiedBackgroundCapacity(data, deps, "parallel", id);
|
|
3801
|
-
if (capacity.errorResult) return capacity.errorResult;
|
|
3802
|
-
const asyncCtx = compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
3803
|
-
pi: deps.pi,
|
|
3804
|
-
cwd: ctx.cwd,
|
|
3805
|
-
currentSessionId: data.parentSessionId!,
|
|
3806
|
-
parentSessionId: data.parentPiSessionId,
|
|
3807
|
-
currentModelProvider: parentModel?.provider,
|
|
3808
|
-
currentModel: parentModel,
|
|
3809
|
-
modelScope: data.modelScope,
|
|
3810
|
-
interactive: ctx.hasUI,
|
|
3811
|
-
permissions: deps.config.permissions,
|
|
3812
|
-
});
|
|
3813
|
-
const parallelTasks = tasks.map((t, i) => {
|
|
3814
|
-
const taskText = shouldForkAgent(contextPolicy, t.agent) ? wrapForkTask(taskTexts[i]!) : taskTexts[i]!;
|
|
3815
|
-
const progress = taskDisallowsFileUpdates(taskText) ? false : behaviorOverrides[i]?.progress;
|
|
3816
|
-
return compactOptional<ParallelTaskItem>({
|
|
3817
|
-
agent: t.agent,
|
|
3818
|
-
task: taskText,
|
|
3819
|
-
cwd: t.cwd,
|
|
3820
|
-
...(behaviorOverrides[i]?.model !== undefined ? { model: behaviorOverrides[i]!.model } : {}),
|
|
3821
|
-
...(skillOverrides[i] !== undefined ? { skill: skillOverrides[i] } : {}),
|
|
3822
|
-
...(behaviorOverrides[i]?.output !== undefined ? { output: behaviorOverrides[i]!.output } : {}),
|
|
3823
|
-
...(behaviorOverrides[i]?.outputMode !== undefined ? { outputMode: behaviorOverrides[i]!.outputMode } : {}),
|
|
3824
|
-
...(behaviorOverrides[i]?.reads !== undefined ? { reads: behaviorOverrides[i]!.reads } : {}),
|
|
3825
|
-
...(progress !== undefined ? { progress } : {}),
|
|
3826
|
-
...(t.toolBudget !== undefined ? { toolBudget: t.toolBudget } : {}),
|
|
3827
|
-
...(t.outputSchema !== undefined ? { outputSchema: t.outputSchema } : {}),
|
|
3828
|
-
...(t.acceptance !== undefined ? { acceptance: t.acceptance } : {}),
|
|
3829
|
-
...(t.agentContract !== undefined ? { agentContract: t.agentContract } : {}),
|
|
3830
|
-
});
|
|
3831
|
-
});
|
|
3832
|
-
try {
|
|
3833
|
-
return executeAsyncChain(id, compactOptional<Parameters<typeof executeAsyncChain>[1]>({
|
|
3834
|
-
chain: [compactOptional<ParallelStep>({ parallel: parallelTasks, concurrency: parallelConcurrency, worktree: params.worktree })],
|
|
3835
|
-
resultMode: "parallel",
|
|
3836
|
-
goal: taskTexts[0] ?? "",
|
|
3837
|
-
agents,
|
|
3838
|
-
ctx: asyncCtx,
|
|
3839
|
-
availableModels,
|
|
3840
|
-
cwd: effectiveCwd,
|
|
3841
|
-
maxOutput: params.maxOutput,
|
|
3842
|
-
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
3843
|
-
artifactConfig,
|
|
3844
|
-
shareEnabled,
|
|
3845
|
-
sessionRoot,
|
|
3846
|
-
chainSkills: [],
|
|
3847
|
-
activeAsyncCapacity: capacity.activeAsyncCapacity,
|
|
3848
|
-
sessionFilesByFlatIndex: tasks.map((task, index) => sessionFileForTask(task.agent, index, modelOverrides[index])),
|
|
3849
|
-
thinkingOverridesByFlatIndex: tasks.map((task, index) => thinkingOverrideForTask(task.agent, index, modelOverrides[index])),
|
|
3850
|
-
contextForAgent: contextPolicy.contextForAgent,
|
|
3851
|
-
maxSubagentDepth: currentMaxSubagentDepth,
|
|
3852
|
-
waitToolEnabled: deps.waitToolEnabled,
|
|
3853
|
-
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
3854
|
-
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
3855
|
-
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
3856
|
-
controlConfig,
|
|
3857
|
-
agentContract: params.agentContract,
|
|
3858
|
-
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
3859
|
-
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
|
|
3860
|
-
nestedRoute: data.nestedRoute,
|
|
3861
|
-
timeoutMs: data.timeoutMs,
|
|
3862
|
-
turnBudget: data.turnBudget,
|
|
3863
|
-
usageBudget: data.usageBudget,
|
|
3864
|
-
toolBudget: data.toolBudget,
|
|
3865
|
-
configToolBudget: data.configToolBudget,
|
|
3866
|
-
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
3867
|
-
runFanoutBudget: data.runFanoutBudget,
|
|
3868
|
-
}));
|
|
3869
|
-
} finally {
|
|
3870
|
-
finalizeClarifiedBackgroundCapacity(data, deps, capacity.activeAsyncCapacity);
|
|
3871
|
-
}
|
|
3872
|
-
}
|
|
3873
|
-
}
|
|
3874
|
-
|
|
3875
|
-
const behaviors = tasks.map((task, index) => {
|
|
3876
|
-
let behavior = suppressProgressForReadOnlyTask(resolveStepBehavior(agentConfigs[index]!, behaviorOverrides[index]!), taskTexts[index]);
|
|
3877
|
-
if (behaviorOverrides[index]?.output === undefined && typeof behavior.output === "string" && !path.isAbsolute(behavior.output)) {
|
|
3878
|
-
behavior = { ...behavior, output: path.join("parallel-0", `${index}-${task.agent}`, behavior.output) };
|
|
3879
|
-
}
|
|
3880
|
-
return behavior;
|
|
3881
|
-
});
|
|
3882
|
-
const firstProgressIndex = behaviors.findIndex((behavior) => behavior.progress);
|
|
3883
|
-
const liveResults: (SingleResult | undefined)[] = new Array(tasks.length).fill(undefined);
|
|
3884
|
-
const liveProgress: (AgentProgress | undefined)[] = new Array(tasks.length).fill(undefined);
|
|
3885
|
-
const foregroundControl = deps.state.foregroundControls.get(runId);
|
|
3886
|
-
const { setup: worktreeSetup, errorResult } = createParallelWorktreeSetup(
|
|
3887
|
-
params.worktree,
|
|
3888
|
-
effectiveCwd,
|
|
3889
|
-
runId,
|
|
3890
|
-
tasks,
|
|
3891
|
-
deps.config.worktreeSetupHook,
|
|
3892
|
-
deps.config.worktreeSetupHookTimeoutMs,
|
|
3893
|
-
deps.config.worktreeBaseDir,
|
|
3894
|
-
);
|
|
3895
|
-
if (errorResult) return errorResult;
|
|
3896
|
-
|
|
3897
|
-
let worktreeCleanupHandled = false;
|
|
3898
|
-
let pendingHandoff: Details["parallelHandoff"];
|
|
3899
|
-
try {
|
|
3900
|
-
if (worktreeSetup) {
|
|
3901
|
-
pendingHandoff = writePendingParallelHandoff({
|
|
3902
|
-
manifestPath: parallelHandoffPath(artifactsDir, runId),
|
|
3903
|
-
runId,
|
|
3904
|
-
mode: "parallel",
|
|
3905
|
-
source: "foreground",
|
|
3906
|
-
cwd: effectiveCwd,
|
|
3907
|
-
stepIndex: 0,
|
|
3908
|
-
flatStartIndex: 0,
|
|
3909
|
-
setup: worktreeSetup,
|
|
3910
|
-
});
|
|
3911
|
-
}
|
|
3912
|
-
const outputBaseDir = path.join(artifactsDir, "outputs", runId);
|
|
3913
|
-
const duplicateOutputError = findDuplicateParallelOutputPath(omitUndefinedProperties({
|
|
3914
|
-
tasks,
|
|
3915
|
-
behaviors,
|
|
3916
|
-
paramsCwd: effectiveCwd,
|
|
3917
|
-
ctxCwd: ctx.cwd,
|
|
3918
|
-
outputBaseDir,
|
|
3919
|
-
worktreeSetup,
|
|
3920
|
-
}));
|
|
3921
|
-
if (duplicateOutputError) return buildParallelModeError(duplicateOutputError);
|
|
3922
|
-
for (let index = 0; index < tasks.length; index++) {
|
|
3923
|
-
const taskCwd = resolveParallelTaskCwd(tasks[index]!, effectiveCwd, worktreeSetup, index);
|
|
3924
|
-
const outputPath = resolveSingleOutputPath(behaviors[index]?.output, ctx.cwd, taskCwd, outputBaseDir);
|
|
3925
|
-
const validationError = validateFileOnlyOutputMode(behaviors[index]?.outputMode, outputPath, `Parallel task ${index + 1} (${tasks[index]!.agent})`);
|
|
3926
|
-
if (validationError) return buildParallelModeError(validationError);
|
|
3927
|
-
}
|
|
3928
|
-
|
|
3929
|
-
const parallelProgressPrecreated = firstProgressIndex !== -1;
|
|
3930
|
-
const parallelProgressDir = path.join(artifactsDir, "progress", runId);
|
|
3931
|
-
if (parallelProgressPrecreated) writeInitialProgressFile(parallelProgressDir);
|
|
3932
|
-
|
|
3933
|
-
const taskDescriptions = taskTexts.map((taskText) => taskText.trim());
|
|
3934
|
-
for (let i = 0; i < taskTexts.length; i++) {
|
|
3935
|
-
if (shouldForkAgent(contextPolicy, tasks[i]!.agent)) taskTexts[i] = wrapForkTask(taskTexts[i]!);
|
|
3936
|
-
}
|
|
3937
|
-
|
|
3938
|
-
const deadlineAt = data.deadlineAt ?? (data.timeoutMs !== undefined ? Date.now() + data.timeoutMs : undefined);
|
|
3939
|
-
const results = await runForegroundParallelTasks(compactOptional<ForegroundParallelRunInput>({
|
|
3940
|
-
tasks,
|
|
3941
|
-
permissions: deps.config.permissions,
|
|
3942
|
-
taskTexts,
|
|
3943
|
-
taskDescriptions,
|
|
3944
|
-
agents,
|
|
3945
|
-
ctx,
|
|
3946
|
-
state: deps.state,
|
|
3947
|
-
intercomEvents: deps.pi.events,
|
|
3948
|
-
parentSessionId: data.parentSessionId,
|
|
3949
|
-
capabilityCeiling: data.capabilityCeiling,
|
|
3950
|
-
runFanoutBudget: data.runFanoutBudget,
|
|
3951
|
-
signal,
|
|
3952
|
-
runId,
|
|
3953
|
-
sessionDirForIndex,
|
|
3954
|
-
sessionFileForIndex,
|
|
3955
|
-
sessionFileForTask,
|
|
3956
|
-
thinkingOverrideForTask,
|
|
3957
|
-
shareEnabled,
|
|
3958
|
-
artifactConfig,
|
|
3959
|
-
artifactsDir,
|
|
3960
|
-
outputBaseDir,
|
|
3961
|
-
maxOutput: params.maxOutput,
|
|
3962
|
-
paramsCwd: effectiveCwd,
|
|
3963
|
-
progressDir: parallelProgressDir,
|
|
3964
|
-
availableModels,
|
|
3965
|
-
modelScope: data.modelScope,
|
|
3966
|
-
parentModel,
|
|
3967
|
-
modelOverrides,
|
|
3968
|
-
behaviors,
|
|
3969
|
-
firstProgressIndex: parallelProgressPrecreated ? -1 : firstProgressIndex,
|
|
3970
|
-
controlConfig,
|
|
3971
|
-
contextPolicy,
|
|
3972
|
-
onControlEvent,
|
|
3973
|
-
childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(runId, agent, index) : undefined,
|
|
3974
|
-
orchestratorIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
3975
|
-
foregroundControl,
|
|
3976
|
-
concurrencyLimit: parallelConcurrency,
|
|
3977
|
-
globalSemaphore: new Semaphore(deps.config.globalConcurrencyLimit ?? DEFAULT_GLOBAL_CONCURRENCY_LIMIT),
|
|
3978
|
-
maxSubagentDepths,
|
|
3979
|
-
waitToolEnabled: deps.waitToolEnabled,
|
|
3980
|
-
liveResults,
|
|
3981
|
-
liveProgress,
|
|
3982
|
-
onUpdate,
|
|
3983
|
-
worktreeSetup,
|
|
3984
|
-
timeoutMs: data.timeoutMs,
|
|
3985
|
-
deadlineAt,
|
|
3986
|
-
turnBudget: data.turnBudget,
|
|
3987
|
-
usageBudget: data.usageBudget,
|
|
3988
|
-
toolBudgets,
|
|
3989
|
-
agentContract: params.agentContract,
|
|
3990
|
-
}));
|
|
3991
|
-
for (let i = 0; i < results.length; i++) {
|
|
3992
|
-
const run = results[i]!;
|
|
3993
|
-
recordRun(run.agent, taskTexts[i]!, run.exitCode, run.progressSummary?.durationMs ?? 0);
|
|
3994
|
-
}
|
|
2902
|
+
} catch (error) {
|
|
2903
|
+
return `Output file error: ${outputPath}\n${error instanceof Error ? error.message : String(error)}`;
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
3995
2906
|
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
}
|
|
2907
|
+
function appendWorkflowOutputWarning(text: string, warning: string | undefined): string {
|
|
2908
|
+
return warning ? `${text}\n\n${warning}` : text;
|
|
2909
|
+
}
|
|
4000
2910
|
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
return {
|
|
4030
|
-
content: [{ type: "text", text: `Parallel run paused after interrupt (${interrupted.agent}). Waiting for explicit next action.` }],
|
|
4031
|
-
details,
|
|
4032
|
-
};
|
|
4033
|
-
}
|
|
4034
|
-
if (detached) {
|
|
4035
|
-
const handoffSuffix = handoff?.suffix ? `\n\n${handoff.suffix}` : "";
|
|
4036
|
-
return {
|
|
4037
|
-
content: [{ type: "text", text: `Parallel run detached for intercom coordination (${detached.agent}). Reply to the supervisor request first, then wait with subagent_wait({ id: "${runId}" }). Use subagent({ action: "status", id: "${runId}" }) to recover the result; do not resume or launch a replacement while it remains detached.${handoffSuffix}` }],
|
|
4038
|
-
details,
|
|
4039
|
-
};
|
|
4040
|
-
}
|
|
2911
|
+
function resolveWorkflowChildOutputPath(input: {
|
|
2912
|
+
ctxCwd: string;
|
|
2913
|
+
workflowCwd: string;
|
|
2914
|
+
artifactsDir: string;
|
|
2915
|
+
workflowRunId: string;
|
|
2916
|
+
aggregateOutputPath?: string;
|
|
2917
|
+
configuredOutputBaseDir?: string;
|
|
2918
|
+
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[] };
|
|
2919
|
+
workflowAgentScope?: unknown;
|
|
2920
|
+
key: string;
|
|
2921
|
+
params: Record<string, unknown>;
|
|
2922
|
+
}): string | undefined {
|
|
2923
|
+
const rawOutput = input.params.output;
|
|
2924
|
+
const hasExplicitOutput = typeof rawOutput === "string" || typeof rawOutput === "boolean";
|
|
2925
|
+
const childCwd = typeof input.params.cwd === "string" ? resolveChildCwd(input.workflowCwd, input.params.cwd) : input.workflowCwd;
|
|
2926
|
+
const agentScope = resolveExecutionAgentScope(input.params.agentScope ?? input.workflowAgentScope);
|
|
2927
|
+
const agents = input.discoverAgents(childCwd, agentScope).agents;
|
|
2928
|
+
const agent = typeof input.params.agent === "string" ? resolveAgentName(input.params.agent, agents).agent : undefined;
|
|
2929
|
+
const agentOutput = typeof agent?.output === "string" ? agent.output : undefined;
|
|
2930
|
+
const output = rawOutput === true || rawOutput === "true"
|
|
2931
|
+
? agentOutput
|
|
2932
|
+
: hasExplicitOutput
|
|
2933
|
+
? rawOutput
|
|
2934
|
+
: input.aggregateOutputPath
|
|
2935
|
+
? workflowChildDefaultOutput(input.aggregateOutputPath, input.artifactsDir, input.workflowRunId, input.key)
|
|
2936
|
+
: agentOutput;
|
|
2937
|
+
return resolveSingleOutputPath(output, input.ctxCwd, childCwd, input.configuredOutputBaseDir);
|
|
2938
|
+
}
|
|
4041
2939
|
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
2940
|
+
function workflowChildOutputClaims(input: {
|
|
2941
|
+
ctxCwd: string;
|
|
2942
|
+
workflowCwd: string;
|
|
2943
|
+
artifactsDir: string;
|
|
2944
|
+
workflowRunId: string;
|
|
2945
|
+
aggregateOutputPath?: string;
|
|
2946
|
+
configuredOutputBaseDir?: string;
|
|
2947
|
+
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[] };
|
|
2948
|
+
workflowAgentScope?: unknown;
|
|
2949
|
+
claimedOutputPaths: Map<string, string>;
|
|
2950
|
+
entries: Array<{ key: string; params: Record<string, unknown> }>;
|
|
2951
|
+
}): { error?: string; claims?: Map<string, string> } {
|
|
2952
|
+
const claims = new Map(input.claimedOutputPaths);
|
|
2953
|
+
for (const { key, params } of input.entries) {
|
|
2954
|
+
const resolved = resolveWorkflowChildOutputPath({ ...input, key, params });
|
|
2955
|
+
if (!resolved) continue;
|
|
2956
|
+
const previous = claims.get(resolved);
|
|
2957
|
+
if (previous) return { error: `Workflow children '${previous}' and '${key}' resolve output to the same path: ${resolved}. Use distinct child output paths.` };
|
|
2958
|
+
claims.set(resolved, key);
|
|
2959
|
+
}
|
|
2960
|
+
return { claims };
|
|
2961
|
+
}
|
|
4061
2962
|
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
results.map((result) => ({
|
|
4067
|
-
agent: result.agent,
|
|
4068
|
-
output: result.truncation?.text || getSingleResultOutput(result),
|
|
4069
|
-
exitCode: result.exitCode,
|
|
4070
|
-
...(result.error === undefined ? {} : { error: result.error }),
|
|
4071
|
-
...(result.timedOut === undefined ? {} : { timedOut: result.timedOut }),
|
|
4072
|
-
})),
|
|
4073
|
-
(i, agent) => `=== Task ${i + 1}: ${agent} ===`,
|
|
4074
|
-
);
|
|
2963
|
+
function applyWorkflowChildOutputClaims(target: Map<string, string>, claims: Map<string, string>): void {
|
|
2964
|
+
target.clear();
|
|
2965
|
+
for (const [resolved, key] of claims) target.set(resolved, key);
|
|
2966
|
+
}
|
|
4075
2967
|
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
2968
|
+
function prepareWorkflowChildLaunchParams(input: {
|
|
2969
|
+
workflowDefaults: SubagentParamsLike;
|
|
2970
|
+
childParams: Record<string, unknown>;
|
|
2971
|
+
parentWorkflowRunId: string;
|
|
2972
|
+
workflowKey: string;
|
|
2973
|
+
ctxCwd: string;
|
|
2974
|
+
workflowCwd: string;
|
|
2975
|
+
artifactsDir: string;
|
|
2976
|
+
aggregateOutputPath?: string;
|
|
2977
|
+
configuredOutputBaseDir?: string;
|
|
2978
|
+
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[] };
|
|
2979
|
+
workflowAgentScope?: unknown;
|
|
2980
|
+
options?: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean; runFanoutBudget?: RunFanoutBudgetDescriptor; parentDeadlineAt?: number };
|
|
2981
|
+
}): SubagentParamsLike {
|
|
2982
|
+
let childParams = input.childParams;
|
|
2983
|
+
if (input.childParams.output === undefined && input.childParams.resume === undefined && input.aggregateOutputPath !== undefined) {
|
|
2984
|
+
childParams = { ...input.childParams, output: workflowChildDefaultOutput(input.aggregateOutputPath, input.artifactsDir, input.parentWorkflowRunId, input.workflowKey) };
|
|
2985
|
+
} else if (input.childParams.resume === undefined) {
|
|
2986
|
+
const resolvedOutput = resolveWorkflowChildOutputPath({ ctxCwd: input.ctxCwd, workflowCwd: input.workflowCwd, artifactsDir: input.artifactsDir, workflowRunId: input.parentWorkflowRunId, aggregateOutputPath: input.aggregateOutputPath, configuredOutputBaseDir: input.configuredOutputBaseDir, discoverAgents: input.discoverAgents, workflowAgentScope: input.workflowAgentScope, key: input.workflowKey, params: input.childParams });
|
|
2987
|
+
if (resolvedOutput) childParams = { ...input.childParams, output: resolvedOutput };
|
|
2988
|
+
}
|
|
2989
|
+
return prepareWorkflowLaunchParams(input.workflowDefaults, childParams, input.parentWorkflowRunId, input.workflowKey, input.options);
|
|
2990
|
+
}
|
|
4080
2991
|
|
|
2992
|
+
function finalizeSingleWorktreeHandoff(input: {
|
|
2993
|
+
worktreeSetup: WorktreeSetup;
|
|
2994
|
+
artifactsDir: string;
|
|
2995
|
+
runId: string;
|
|
2996
|
+
cwd: string;
|
|
2997
|
+
agent: string;
|
|
2998
|
+
result: SingleResult;
|
|
2999
|
+
}): { suffix: string; reference?: NonNullable<Details["parallelHandoff"]> } {
|
|
3000
|
+
const diffsDir = path.join(input.artifactsDir, "worktree-diffs", input.runId);
|
|
3001
|
+
const diffs = diffWorktrees(input.worktreeSetup, [input.agent], diffsDir);
|
|
3002
|
+
const diffSummary = formatWorktreeDiffSummary(diffs);
|
|
3003
|
+
const manifestPath = parallelHandoffPath(input.artifactsDir, input.runId);
|
|
3004
|
+
const handoff = {
|
|
3005
|
+
manifestPath,
|
|
3006
|
+
runId: input.runId,
|
|
3007
|
+
mode: "single" as const,
|
|
3008
|
+
source: "foreground" as const,
|
|
3009
|
+
cwd: input.cwd,
|
|
3010
|
+
stepIndex: 0,
|
|
3011
|
+
flatStartIndex: 0,
|
|
3012
|
+
setup: input.worktreeSetup,
|
|
3013
|
+
diffs,
|
|
3014
|
+
results: [{
|
|
3015
|
+
agent: input.result.agent,
|
|
3016
|
+
status: resolveSubagentResultStatus(omitUndefinedProperties({
|
|
3017
|
+
exitCode: input.result.exitCode,
|
|
3018
|
+
interrupted: input.result.interrupted,
|
|
3019
|
+
detached: input.result.detached,
|
|
3020
|
+
state: input.result.stopped ? "stopped" : undefined,
|
|
3021
|
+
processSignal: input.result.processSignal,
|
|
3022
|
+
timedOut: input.result.timedOut,
|
|
3023
|
+
stopped: input.result.stopped,
|
|
3024
|
+
turnBudgetExceeded: input.result.turnBudgetExceeded,
|
|
3025
|
+
})),
|
|
3026
|
+
summary: resultSummaryForIntercom(input.result),
|
|
3027
|
+
...(input.result.artifactPaths?.outputPath ? { outputPath: input.result.artifactPaths.outputPath } : {}),
|
|
3028
|
+
...(input.result.structuredOutput !== undefined ? { structuredOutput: input.result.structuredOutput } : {}),
|
|
3029
|
+
...(input.result.structuredOutputPath ? { structuredOutputPath: input.result.structuredOutputPath } : {}),
|
|
3030
|
+
...(input.result.sessionFile ? { sessionPath: input.result.sessionFile } : {}),
|
|
3031
|
+
}],
|
|
3032
|
+
};
|
|
3033
|
+
try {
|
|
3034
|
+
writeParallelHandoffGroup(handoff);
|
|
3035
|
+
const cleanup = cleanupWorktrees(input.worktreeSetup, { kind: "preserve", capturedDiffs: diffs, handoffManifestPath: manifestPath });
|
|
3036
|
+
const reference = writeParallelHandoffGroup({ ...handoff, cleanup });
|
|
4081
3037
|
return {
|
|
4082
|
-
|
|
4083
|
-
|
|
3038
|
+
suffix: [diffSummary, formatParallelHandoffReference(reference)].filter(Boolean).join("\n\n"),
|
|
3039
|
+
reference,
|
|
4084
3040
|
};
|
|
4085
|
-
}
|
|
4086
|
-
|
|
3041
|
+
} catch (error) {
|
|
3042
|
+
return { suffix: [diffSummary, formatParallelHandoffError(error)].filter(Boolean).join("\n\n") };
|
|
4087
3043
|
}
|
|
4088
3044
|
}
|
|
4089
3045
|
|
|
@@ -4141,109 +3097,35 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
4141
3097
|
const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
|
|
4142
3098
|
const maxSubagentDepth = resolveChildMaxSubagentDepth(currentMaxSubagentDepth, agentConfig.maxSubagentDepth);
|
|
4143
3099
|
|
|
4144
|
-
if (params.clarify === true && ctx.hasUI) {
|
|
4145
|
-
const behavior = resolveStepBehavior(agentConfig, omitUndefinedProperties({ output: effectiveOutput, skills: skillOverride }));
|
|
4146
|
-
const availableSkills = discoverAvailableSkills(effectiveCwd);
|
|
4147
|
-
|
|
4148
|
-
const result = await ctx.ui.custom<ChainClarifyResult>(
|
|
4149
|
-
(tui, theme, _kb, done) =>
|
|
4150
|
-
new ChainClarifyComponent(
|
|
4151
|
-
tui, theme,
|
|
4152
|
-
[agentConfig],
|
|
4153
|
-
[task],
|
|
4154
|
-
task,
|
|
4155
|
-
undefined,
|
|
4156
|
-
[behavior],
|
|
4157
|
-
availableModels,
|
|
4158
|
-
currentProvider,
|
|
4159
|
-
availableSkills,
|
|
4160
|
-
done,
|
|
4161
|
-
"single",
|
|
4162
|
-
),
|
|
4163
|
-
{ overlay: true, overlayOptions: { anchor: "center", width: 84, maxHeight: "80%" } },
|
|
4164
|
-
);
|
|
4165
|
-
|
|
4166
|
-
if (!result || !result.confirmed) {
|
|
4167
|
-
return { content: [{ type: "text", text: "Cancelled" }], details: { mode: "single", results: [] } };
|
|
4168
|
-
}
|
|
4169
|
-
|
|
4170
|
-
task = result.templates[0]!;
|
|
4171
|
-
const override = result.behaviorOverrides[0];
|
|
4172
|
-
if (override?.model !== undefined) modelOverride = resolveEffectiveSubagentModel(override.model, agentConfig.model, parentModel, availableModels, currentProvider, data.modelScope === undefined ? {} : { scope: data.modelScope });
|
|
4173
|
-
if (override?.output !== undefined) effectiveOutput = normalizeSingleOutputOverride(override.output, agentConfig.output);
|
|
4174
|
-
if (override?.skills !== undefined) skillOverride = override.skills;
|
|
4175
|
-
if (override?.reads !== undefined) readsOverride = override.reads;
|
|
4176
3100
|
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
3101
|
+
const sourceCwd = effectiveCwd;
|
|
3102
|
+
const { setup: worktreeSetup, errorResult: worktreeSetupError } = createSingleWorktreeSetup(
|
|
3103
|
+
params.worktree,
|
|
3104
|
+
sourceCwd,
|
|
3105
|
+
runId,
|
|
3106
|
+
params.agent!,
|
|
3107
|
+
deps.config.worktreeSetupHook,
|
|
3108
|
+
deps.config.worktreeSetupHookTimeoutMs,
|
|
3109
|
+
deps.config.worktreeBaseDir,
|
|
3110
|
+
);
|
|
3111
|
+
if (worktreeSetupError) return worktreeSetupError;
|
|
3112
|
+
const singleCwd = worktreeSetup?.worktrees[0]?.agentCwd ?? sourceCwd;
|
|
3113
|
+
let pendingHandoff: Details["parallelHandoff"];
|
|
3114
|
+
if (worktreeSetup) {
|
|
3115
|
+
try {
|
|
3116
|
+
pendingHandoff = writePendingParallelHandoff({
|
|
3117
|
+
manifestPath: parallelHandoffPath(artifactsDir, runId),
|
|
3118
|
+
runId,
|
|
3119
|
+
mode: "single",
|
|
3120
|
+
source: "foreground",
|
|
3121
|
+
cwd: sourceCwd,
|
|
3122
|
+
stepIndex: 0,
|
|
3123
|
+
flatStartIndex: 0,
|
|
3124
|
+
setup: worktreeSetup,
|
|
4198
3125
|
});
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(task) : task,
|
|
4203
|
-
goal: task,
|
|
4204
|
-
agentConfig,
|
|
4205
|
-
recoveryAgentConfig: data.recoveryAgents.find((agent) => agent.name === params.agent),
|
|
4206
|
-
ctx: asyncCtx,
|
|
4207
|
-
availableModels,
|
|
4208
|
-
cwd: effectiveCwd,
|
|
4209
|
-
maxOutput: params.maxOutput,
|
|
4210
|
-
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
4211
|
-
artifactConfig,
|
|
4212
|
-
shareEnabled,
|
|
4213
|
-
activeAsyncCapacity: capacity.activeAsyncCapacity,
|
|
4214
|
-
sessionRoot,
|
|
4215
|
-
sessionFile: sessionFileForTask(params.agent!, 0, modelOverride),
|
|
4216
|
-
context: contextPolicy.contextForAgent(params.agent!),
|
|
4217
|
-
skills: skillOverride === false ? [] : skillOverride,
|
|
4218
|
-
output: effectiveOutput,
|
|
4219
|
-
outputMode: effectiveOutputMode,
|
|
4220
|
-
...(readsOverride !== undefined ? { reads: readsOverride } : {}),
|
|
4221
|
-
outputBaseDir: resolveSingleRunOutputBaseDir(deps, artifactsDir, id),
|
|
4222
|
-
modelOverride,
|
|
4223
|
-
thinkingOverride: thinkingOverrideForTask(params.agent!, 0, modelOverride),
|
|
4224
|
-
maxSubagentDepth,
|
|
4225
|
-
waitToolEnabled: deps.waitToolEnabled,
|
|
4226
|
-
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
4227
|
-
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
4228
|
-
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
4229
|
-
controlConfig,
|
|
4230
|
-
intercomBridge: params.intercomBridge,
|
|
4231
|
-
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
4232
|
-
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
|
|
4233
|
-
nestedRoute: data.nestedRoute,
|
|
4234
|
-
agentContract: params.agentContract,
|
|
4235
|
-
structuredOutputSchema: params.outputSchema,
|
|
4236
|
-
acceptance: params.acceptance,
|
|
4237
|
-
timeoutMs: data.timeoutMs,
|
|
4238
|
-
turnBudget: data.turnBudget,
|
|
4239
|
-
toolBudget: effectiveToolBudget.toolBudget,
|
|
4240
|
-
usageBudget: data.usageBudget,
|
|
4241
|
-
allowZeroToolBudget: data.allowZeroToolBudget && effectiveToolBudget.toolBudget === data.toolBudget,
|
|
4242
|
-
runFanoutBudget: data.runFanoutBudget,
|
|
4243
|
-
}));
|
|
4244
|
-
} finally {
|
|
4245
|
-
finalizeClarifiedBackgroundCapacity(data, deps, capacity.activeAsyncCapacity);
|
|
4246
|
-
}
|
|
3126
|
+
} catch (error) {
|
|
3127
|
+
cleanupWorktrees(worktreeSetup);
|
|
3128
|
+
return { content: [{ type: "text", text: formatParallelHandoffError(error) }], isError: true, details: { mode: "single", results: [] } };
|
|
4247
3129
|
}
|
|
4248
3130
|
}
|
|
4249
3131
|
|
|
@@ -4252,9 +3134,10 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
4252
3134
|
task = wrapForkTask(task);
|
|
4253
3135
|
}
|
|
4254
3136
|
const cleanTask = task;
|
|
4255
|
-
const outputPath = resolveSingleOutputPath(effectiveOutput, ctx.cwd,
|
|
3137
|
+
const outputPath = resolveSingleOutputPath(effectiveOutput, ctx.cwd, singleCwd, resolveSingleRunOutputBaseDir(deps, artifactsDir, runId));
|
|
4256
3138
|
const validationError = validateFileOnlyOutputMode(effectiveOutputMode, outputPath, `Single run (${params.agent})`);
|
|
4257
3139
|
if (validationError) {
|
|
3140
|
+
if (worktreeSetup) cleanupWorktrees(worktreeSetup);
|
|
4258
3141
|
return { content: [{ type: "text", text: validationError }], isError: true, details: { mode: "single", results: [] } };
|
|
4259
3142
|
}
|
|
4260
3143
|
const structuredRuntime = params.outputSchema
|
|
@@ -4263,7 +3146,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
4263
3146
|
// Reads: caller override > agent defaultReads > none. `~`/`~/` expand to home;
|
|
4264
3147
|
// absolute paths pass through; relative paths resolve against the child cwd.
|
|
4265
3148
|
const reads = readsOverride !== undefined ? readsOverride : agentConfig.defaultReads ?? false;
|
|
4266
|
-
const readPaths = Array.isArray(reads) ? resolveExistingReadPaths(reads,
|
|
3149
|
+
const readPaths = Array.isArray(reads) ? resolveExistingReadPaths(reads, singleCwd) : [];
|
|
4267
3150
|
const readsInstruction = readPaths.length > 0
|
|
4268
3151
|
? `[Read from: ${readPaths.join(", ")}]\n\n`
|
|
4269
3152
|
: "";
|
|
@@ -4286,7 +3169,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
4286
3169
|
agent: params.agent!,
|
|
4287
3170
|
authoredTask,
|
|
4288
3171
|
effectivePrompt: task,
|
|
4289
|
-
cwd:
|
|
3172
|
+
cwd: singleCwd,
|
|
4290
3173
|
outputPath,
|
|
4291
3174
|
rerun: { params: { ...params, task: authoredTask, async: params.async ?? false } },
|
|
4292
3175
|
description: foregroundControl.description,
|
|
@@ -4318,7 +3201,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
4318
3201
|
...workflowForegroundSteeringLaunchOptions(foregroundControl, 0),
|
|
4319
3202
|
context: data.contextPolicy.contextForAgent(params.agent!),
|
|
4320
3203
|
runFanoutBudget: params.runFanoutAdmitted ? data.runFanoutBudget : { ...data.runFanoutBudget, parentPath: `${data.runFanoutBudget.parentPath ? `${data.runFanoutBudget.parentPath}/` : ""}single` },
|
|
4321
|
-
cwd:
|
|
3204
|
+
cwd: singleCwd,
|
|
4322
3205
|
signal,
|
|
4323
3206
|
interruptSignal: interruptController.signal,
|
|
4324
3207
|
allowIntercomDetach: agentConfig.systemPrompt?.includes(INTERCOM_BRIDGE_MARKER) === true,
|
|
@@ -4358,7 +3241,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
4358
3241
|
onDetachedExit: (result) => {
|
|
4359
3242
|
try {
|
|
4360
3243
|
try {
|
|
4361
|
-
updateRememberedForegroundChild(deps.state, { runId, mode: "single", cwd:
|
|
3244
|
+
updateRememberedForegroundChild(deps.state, { runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, index: 0, result, events: deps.pi.events, notify: params.workflowParentRunId === undefined });
|
|
4362
3245
|
} catch {
|
|
4363
3246
|
// Remembered foreground state is best-effort; run history and cleanup must still complete.
|
|
4364
3247
|
}
|
|
@@ -4377,12 +3260,17 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
4377
3260
|
},
|
|
4378
3261
|
timeoutMs: data.timeoutMs,
|
|
4379
3262
|
deadlineAt,
|
|
3263
|
+
toolTimeoutMs: params.toolTimeoutMs,
|
|
3264
|
+
configToolTimeoutMs: data.configToolTimeoutMs,
|
|
4380
3265
|
turnBudget: data.turnBudget,
|
|
4381
3266
|
enforceHardTurnLimit: params.enforceHardTurnLimit,
|
|
4382
3267
|
toolBudget: effectiveToolBudget.toolBudget,
|
|
4383
3268
|
capabilityCeiling: data.capabilityCeiling,
|
|
4384
3269
|
allowZeroToolBudget: data.allowZeroToolBudget && effectiveToolBudget.toolBudget === data.toolBudget,
|
|
4385
3270
|
}));
|
|
3271
|
+
} catch (error) {
|
|
3272
|
+
if (worktreeSetup) cleanupWorktrees(worktreeSetup);
|
|
3273
|
+
throw error;
|
|
4386
3274
|
} finally {
|
|
4387
3275
|
// An attached runSync rejection still owns its child and structured runtime.
|
|
4388
3276
|
// A successful detached receipt transfers both to onDetachedExit while the
|
|
@@ -4396,6 +3284,13 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
4396
3284
|
recordRun(params.agent!, cleanTask, r.exitCode, r.progressSummary?.durationMs ?? 0);
|
|
4397
3285
|
}
|
|
4398
3286
|
|
|
3287
|
+
let worktreeHandoff: ReturnType<typeof finalizeSingleWorktreeHandoff> | undefined;
|
|
3288
|
+
if (worktreeSetup) {
|
|
3289
|
+
worktreeHandoff = r.detached
|
|
3290
|
+
? { suffix: pendingHandoff ? formatParallelHandoffReference(pendingHandoff) : "", reference: pendingHandoff }
|
|
3291
|
+
: finalizeSingleWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: sourceCwd, agent: params.agent!, result: r });
|
|
3292
|
+
}
|
|
3293
|
+
|
|
4399
3294
|
if (r.progress) allProgress.push(r.progress);
|
|
4400
3295
|
if (r.artifactPaths) allArtifactPaths.push(r.artifactPaths);
|
|
4401
3296
|
|
|
@@ -4430,8 +3325,9 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
4430
3325
|
totalChildUsage: sumResultsUsage([r]),
|
|
4431
3326
|
totalCost,
|
|
4432
3327
|
usageBudget: usageBudgetState(data.usageBudget, totalCost),
|
|
3328
|
+
...(worktreeHandoff?.reference ? { parallelHandoff: worktreeHandoff.reference } : {}),
|
|
4433
3329
|
}));
|
|
4434
|
-
rememberForegroundRun(deps.state, { runId, mode: "single", cwd:
|
|
3330
|
+
rememberForegroundRun(deps.state, { runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, results: details.results });
|
|
4435
3331
|
|
|
4436
3332
|
const suppressRoutineResultIntercom = shouldSuppressRoutineResultIntercom({ suppressRoutineResultIntercom: params.suppressRoutineResultIntercom, results: [r] });
|
|
4437
3333
|
if (!r.detached && !r.interrupted && !suppressRoutineResultIntercom) {
|
|
@@ -4454,28 +3350,36 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
4454
3350
|
}
|
|
4455
3351
|
}
|
|
4456
3352
|
|
|
3353
|
+
const worktreeSuffix = worktreeHandoff?.suffix ? `\n\n${worktreeHandoff.suffix}` : "";
|
|
4457
3354
|
if (r.detached) {
|
|
3355
|
+
const statusRecovery = `subagent({ action: "status", id: "${runId}" }) to recover the result; do not resume or launch a replacement while it remains detached.`;
|
|
3356
|
+
const blockingRecovery = `subagent_wait({ id: "${runId}" }). Use ${statusRecovery}`;
|
|
3357
|
+
const message = r.detachedReason === "intercom coordination"
|
|
3358
|
+
? `Detached for intercom coordination: ${params.agent}. Reply to the supervisor request first, then wait with ${blockingRecovery}`
|
|
3359
|
+
: r.detachedReason === "user request"
|
|
3360
|
+
? `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}`
|
|
3361
|
+
: `Detached before task completion: ${params.agent}. Wait with ${blockingRecovery}`;
|
|
4458
3362
|
return {
|
|
4459
|
-
content: [{ type: "text", text:
|
|
3363
|
+
content: [{ type: "text", text: `${message}${worktreeSuffix}` }],
|
|
4460
3364
|
details,
|
|
4461
3365
|
};
|
|
4462
3366
|
}
|
|
4463
3367
|
|
|
4464
3368
|
if (r.interrupted) {
|
|
4465
3369
|
return {
|
|
4466
|
-
content: [{ type: "text", text: `Run paused after interrupt (${params.agent}). Waiting for explicit next action
|
|
3370
|
+
content: [{ type: "text", text: `Run paused after interrupt (${params.agent}). Waiting for explicit next action.${worktreeSuffix}` }],
|
|
4467
3371
|
details,
|
|
4468
3372
|
};
|
|
4469
3373
|
}
|
|
4470
3374
|
|
|
4471
3375
|
if (r.exitCode !== 0)
|
|
4472
3376
|
return {
|
|
4473
|
-
content: [{ type: "text", text: formatFailedSingleRunOutput(r, finalizedOutput.displayOutput) }],
|
|
3377
|
+
content: [{ type: "text", text: `${formatFailedSingleRunOutput(r, finalizedOutput.displayOutput)}${worktreeSuffix}` }],
|
|
4474
3378
|
details,
|
|
4475
3379
|
isError: true,
|
|
4476
3380
|
};
|
|
4477
3381
|
return {
|
|
4478
|
-
content: [{ type: "text", text: finalizedOutput.displayOutput || "(no output)" }],
|
|
3382
|
+
content: [{ type: "text", text: `${finalizedOutput.displayOutput || "(no output)"}${worktreeSuffix}` }],
|
|
4479
3383
|
details,
|
|
4480
3384
|
};
|
|
4481
3385
|
}
|
|
@@ -4500,17 +3404,28 @@ function duplicateSubagentCallResult(params: SubagentParamsLike): AgentToolResul
|
|
|
4500
3404
|
|
|
4501
3405
|
const workflowLaunchObservers = new WeakMap<object, (launch: { agent: string; sessionFile?: string; async: boolean; runId?: string }) => void>();
|
|
4502
3406
|
|
|
3407
|
+
/**
|
|
3408
|
+
* Terminal-mission retention can remove a mission while its children still
|
|
3409
|
+
* report. Remember it to prevent later heartbeats from warning repeatedly.
|
|
3410
|
+
*/
|
|
3411
|
+
const missionsMissingFromStore = new Set<string>();
|
|
3412
|
+
|
|
4503
3413
|
function recordMissionWorkflowChild(
|
|
4504
3414
|
binding: MissionLaunchBinding | undefined,
|
|
4505
3415
|
workflowRunId: string,
|
|
4506
3416
|
key: string,
|
|
4507
3417
|
update: Omit<MissionWorkflowChildUpdate, "workflowRunId" | "key">,
|
|
4508
3418
|
): void {
|
|
4509
|
-
if (!binding) return;
|
|
3419
|
+
if (!binding || missionsMissingFromStore.has(binding.missionId)) return;
|
|
4510
3420
|
const { task: _task, ...durableUpdate } = update;
|
|
4511
3421
|
try {
|
|
4512
3422
|
updateMission(binding.location, binding.missionId, { upsertWorkflowChildren: [{ workflowRunId, key, ...durableUpdate }] });
|
|
4513
3423
|
} catch (error) {
|
|
3424
|
+
if (error instanceof MissionNotFoundError) {
|
|
3425
|
+
missionsMissingFromStore.add(binding.missionId);
|
|
3426
|
+
console.warn(`[pi-subagents] Mission '${binding.missionId}' is no longer in the mission store; stopped recording its workflow children. Terminal-mission retention can prune a mission while its run is still active.`);
|
|
3427
|
+
return;
|
|
3428
|
+
}
|
|
4514
3429
|
console.warn(`[pi-subagents] Failed to record mission workflow child '${key}': ${error instanceof Error ? error.message : String(error)}`);
|
|
4515
3430
|
}
|
|
4516
3431
|
}
|
|
@@ -4580,19 +3495,110 @@ function workflowChildResult(key: string, result: AgentToolResult<Details>): Wor
|
|
|
4580
3495
|
...(result.details.runId || result.details.asyncId ? { runId: result.details.runId ?? result.details.asyncId } : {}),
|
|
4581
3496
|
output,
|
|
4582
3497
|
...(!ok ? { error: receiptOutput || output || "Child run failed." } : {}),
|
|
3498
|
+
...(detached ? { detached: true } : {}),
|
|
4583
3499
|
...(structured.length === 1 ? { structuredOutput: structured[0] } : structured.length > 1 ? { structuredOutput: structured } : {}),
|
|
4584
3500
|
artifactPaths: [...artifactPaths],
|
|
4585
3501
|
results: result.details.results,
|
|
4586
3502
|
};
|
|
4587
3503
|
}
|
|
4588
3504
|
|
|
3505
|
+
function workflowSteerReceipt(key: string, result: AgentToolResult<Details>): WorkflowSteerResult {
|
|
3506
|
+
const steering = result.details.steering;
|
|
3507
|
+
const error = result.content.map((part) => part.type === "text" ? part.text : "").filter(Boolean).join("\n") || undefined;
|
|
3508
|
+
if (!steering) return { key, state: "failed", ...(error ? { error } : {}) };
|
|
3509
|
+
const state = result.isError === true || steering.state === "failed" || steering.state === "partial"
|
|
3510
|
+
? "failed"
|
|
3511
|
+
: steering.deliveryStatus === "delivered" ? "delivered" : "queued";
|
|
3512
|
+
return {
|
|
3513
|
+
key,
|
|
3514
|
+
state,
|
|
3515
|
+
requestId: steering.requestId,
|
|
3516
|
+
deliveryStatus: steering.deliveryStatus,
|
|
3517
|
+
targets: steering.targets.map((target) => ({ index: target.index, state: target.state, ...(target.reason ? { reason: target.reason } : {}) })),
|
|
3518
|
+
...(state === "failed" && error ? { error } : {}),
|
|
3519
|
+
};
|
|
3520
|
+
}
|
|
3521
|
+
|
|
3522
|
+
export async function steerWorkflowChildByKey(input: {
|
|
3523
|
+
state: SubagentState;
|
|
3524
|
+
workflowRunId: string;
|
|
3525
|
+
key: string;
|
|
3526
|
+
message: string;
|
|
3527
|
+
options: WorkflowSteerOptions;
|
|
3528
|
+
signal?: AbortSignal;
|
|
3529
|
+
asyncDirRoot?: string;
|
|
3530
|
+
resolveRunId?: () => string | undefined;
|
|
3531
|
+
}): Promise<WorkflowSteerResult> {
|
|
3532
|
+
const asyncDirRoot = input.asyncDirRoot ?? DIRS.async;
|
|
3533
|
+
const ackTimeoutMs = input.options.ackTimeoutMs ?? 3_000;
|
|
3534
|
+
const deadline = Date.now() + ackTimeoutMs;
|
|
3535
|
+
while (true) {
|
|
3536
|
+
const control = [...input.state.foregroundControls.values()].find((candidate) => candidate.parentWorkflowRunId === input.workflowRunId
|
|
3537
|
+
&& candidate.workflowKey === input.key
|
|
3538
|
+
&& Boolean(candidate.workflowSteeringDir)
|
|
3539
|
+
&& (candidate.activeChildren?.size ?? 0) > 0);
|
|
3540
|
+
if (control) {
|
|
3541
|
+
const result = await steerWorkflowForegroundTarget({
|
|
3542
|
+
target: { control, workflowRunId: input.workflowRunId, sourceRunId: control.runId },
|
|
3543
|
+
message: input.message,
|
|
3544
|
+
mode: input.options.mode,
|
|
3545
|
+
index: input.options.index,
|
|
3546
|
+
ackTimeoutMs: Math.max(1, deadline - Date.now()),
|
|
3547
|
+
signal: input.signal,
|
|
3548
|
+
});
|
|
3549
|
+
return workflowSteerReceipt(input.key, result);
|
|
3550
|
+
}
|
|
3551
|
+
|
|
3552
|
+
const workflowStatus = readStatus(path.join(asyncDirRoot, input.workflowRunId));
|
|
3553
|
+
const step = workflowStatus?.steps?.find((candidate) => candidate.workflowKey === input.key);
|
|
3554
|
+
const childRunId = step?.runId ?? input.resolveRunId?.();
|
|
3555
|
+
if (childRunId) {
|
|
3556
|
+
const asyncDir = path.join(asyncDirRoot, childRunId);
|
|
3557
|
+
const childStatus = reconcileAsyncRun(asyncDir).status;
|
|
3558
|
+
if (childStatus && childStatus.state !== "running" && childStatus.state !== "queued" && (input.options.mode !== "follow_up" || !canQueueRetainedAsyncFollowUp(childStatus, input.options.index))) {
|
|
3559
|
+
return { key: input.key, state: "missed", error: `Workflow child '${input.key}' is ${childStatus.state}.` };
|
|
3560
|
+
}
|
|
3561
|
+
if (childStatus) {
|
|
3562
|
+
const result = await steerAsyncRun({
|
|
3563
|
+
state: input.state,
|
|
3564
|
+
runId: childRunId,
|
|
3565
|
+
message: input.message,
|
|
3566
|
+
mode: input.options.mode,
|
|
3567
|
+
index: input.options.index,
|
|
3568
|
+
ackTimeoutMs: Math.max(1, deadline - Date.now()),
|
|
3569
|
+
location: { asyncDir },
|
|
3570
|
+
signal: input.signal,
|
|
3571
|
+
});
|
|
3572
|
+
return workflowSteerReceipt(input.key, result);
|
|
3573
|
+
}
|
|
3574
|
+
}
|
|
3575
|
+
if (step && step.status !== "running" && step.status !== "pending") {
|
|
3576
|
+
return { key: input.key, state: "missed", error: `Workflow child '${input.key}' is ${step.status}.` };
|
|
3577
|
+
}
|
|
3578
|
+
if (workflowStatus && workflowStatus.state !== "running" && workflowStatus.state !== "queued") {
|
|
3579
|
+
return { key: input.key, state: "missed", error: `Workflow '${input.workflowRunId}' is ${workflowStatus.state}.` };
|
|
3580
|
+
}
|
|
3581
|
+
if (input.signal?.aborted || Date.now() >= deadline) {
|
|
3582
|
+
return { key: input.key, state: "missed", error: `Workflow child '${input.key}' had no live steering route.` };
|
|
3583
|
+
}
|
|
3584
|
+
await new Promise<void>((resolve) => setTimeout(resolve, Math.min(10, Math.max(1, deadline - Date.now()))));
|
|
3585
|
+
}
|
|
3586
|
+
}
|
|
3587
|
+
|
|
4589
3588
|
export function prepareWorkflowLaunchParams(
|
|
4590
3589
|
workflowDefaults: SubagentParamsLike,
|
|
4591
3590
|
childParams: Record<string, unknown>,
|
|
4592
3591
|
parentWorkflowRunId: string,
|
|
4593
3592
|
workflowKey: string,
|
|
4594
|
-
options: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean; runFanoutBudget?: RunFanoutBudgetDescriptor } = {},
|
|
3593
|
+
options: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean; runFanoutBudget?: RunFanoutBudgetDescriptor; parentDeadlineAt?: number } = {},
|
|
4595
3594
|
): SubagentParamsLike {
|
|
3595
|
+
const parentTimeoutMs = options.parentDeadlineAt === undefined
|
|
3596
|
+
|| childParams.timeoutMs !== undefined
|
|
3597
|
+
|| childParams.maxRuntimeMs !== undefined
|
|
3598
|
+
|| workflowDefaults.timeoutMs !== undefined
|
|
3599
|
+
|| workflowDefaults.maxRuntimeMs !== undefined
|
|
3600
|
+
? undefined
|
|
3601
|
+
: Math.max(1, options.parentDeadlineAt - Date.now());
|
|
4596
3602
|
if (typeof childParams.resume === "string") {
|
|
4597
3603
|
if (childParams.gate !== undefined || workflowDefaults.gate !== undefined) {
|
|
4598
3604
|
throw new Error("gate is not supported with retained resume; resume uses the retained child contract.");
|
|
@@ -4601,12 +3607,14 @@ export function prepareWorkflowLaunchParams(
|
|
|
4601
3607
|
const turnBudget = childParams.turnBudget ?? workflowDefaults.turnBudget;
|
|
4602
3608
|
const toolBudget = childParams.toolBudget ?? workflowDefaults.toolBudget;
|
|
4603
3609
|
const intercomBridge = childParams.intercomBridge ?? workflowDefaults.intercomBridge;
|
|
3610
|
+
const worktree = childParams.worktree ?? workflowDefaults.worktree;
|
|
4604
3611
|
return {
|
|
4605
3612
|
action: "resume",
|
|
4606
3613
|
id: childParams.resume.trim(),
|
|
4607
3614
|
message: typeof childParams.task === "string" ? childParams.task.trim() : "",
|
|
4608
3615
|
workflowParentRunId: parentWorkflowRunId,
|
|
4609
3616
|
workflowKey,
|
|
3617
|
+
...(worktree !== undefined ? { worktree: worktree as boolean } : {}),
|
|
4610
3618
|
...(options.runFanoutBudget ? { runFanoutBudget: { ...options.runFanoutBudget, parentPath: `${options.runFanoutBudget.parentPath ? `${options.runFanoutBudget.parentPath}/` : ""}workflow[${workflowKey}]` } } : {}),
|
|
4611
3619
|
...(options.missionDetached ? { mission: false } : {}),
|
|
4612
3620
|
...(timeoutMs !== undefined ? { timeoutMs: timeoutMs as number } : {}),
|
|
@@ -4622,12 +3630,13 @@ export function prepareWorkflowLaunchParams(
|
|
|
4622
3630
|
...(options.missionDetached ? { mission: false } : {}),
|
|
4623
3631
|
workflowParentRunId: parentWorkflowRunId,
|
|
4624
3632
|
workflowKey,
|
|
3633
|
+
...(parentTimeoutMs !== undefined ? { workflowParentDeadlineAt: options.parentDeadlineAt } : {}),
|
|
4625
3634
|
...(options.runFanoutBudget ? { runFanoutBudget: { ...options.runFanoutBudget, parentPath: `${options.runFanoutBudget.parentPath ? `${options.runFanoutBudget.parentPath}/` : ""}workflow[${workflowKey}]` } } : {}),
|
|
4626
3635
|
...(options.suppressRoutineResultIntercom ? { suppressRoutineResultIntercom: true } : {}),
|
|
4627
3636
|
} as SubagentParamsLike;
|
|
4628
3637
|
const normalizedGate = normalizeGateParams(launchParams);
|
|
4629
3638
|
if (!normalizedGate.ok) throw new Error(normalizedGate.error);
|
|
4630
|
-
return
|
|
3639
|
+
return normalizedGate.params;
|
|
4631
3640
|
}
|
|
4632
3641
|
|
|
4633
3642
|
type GateParamsNormalizationResult =
|
|
@@ -4645,41 +3654,6 @@ function normalizeGateParams(params: SubagentParamsLike): GateParamsNormalizatio
|
|
|
4645
3654
|
return { ok: true, params: { ...rest, ...(normalized.acceptance !== undefined ? { acceptance: normalized.acceptance } : {}) } };
|
|
4646
3655
|
}
|
|
4647
3656
|
|
|
4648
|
-
function prepareWorkflowChildParams(params: SubagentParamsLike): SubagentParamsLike {
|
|
4649
|
-
if (params.worktree !== true || !params.agent) return params;
|
|
4650
|
-
const {
|
|
4651
|
-
agent,
|
|
4652
|
-
task = "",
|
|
4653
|
-
model,
|
|
4654
|
-
skill,
|
|
4655
|
-
output,
|
|
4656
|
-
outputMode,
|
|
4657
|
-
outputSchema,
|
|
4658
|
-
acceptance,
|
|
4659
|
-
agentContract,
|
|
4660
|
-
toolBudget,
|
|
4661
|
-
reads,
|
|
4662
|
-
...runParams
|
|
4663
|
-
} = params;
|
|
4664
|
-
return {
|
|
4665
|
-
...runParams,
|
|
4666
|
-
worktree: true,
|
|
4667
|
-
tasks: [{
|
|
4668
|
-
agent,
|
|
4669
|
-
task,
|
|
4670
|
-
...(model !== undefined ? { model } : {}),
|
|
4671
|
-
...(skill !== undefined ? { skill } : {}),
|
|
4672
|
-
...(output !== undefined ? { output } : {}),
|
|
4673
|
-
...(reads !== undefined ? { reads } : {}),
|
|
4674
|
-
...(outputMode !== undefined ? { outputMode } : {}),
|
|
4675
|
-
...(outputSchema !== undefined ? { outputSchema } : {}),
|
|
4676
|
-
...(acceptance !== undefined ? { acceptance } : {}),
|
|
4677
|
-
...(agentContract !== undefined ? { agentContract } : {}),
|
|
4678
|
-
...(toolBudget !== undefined ? { toolBudget } : {}),
|
|
4679
|
-
}],
|
|
4680
|
-
};
|
|
4681
|
-
}
|
|
4682
|
-
|
|
4683
3657
|
function formatWorkflowValue(value: unknown): string {
|
|
4684
3658
|
if (value === undefined) return "(undefined)";
|
|
4685
3659
|
if (typeof value === "string") return value;
|
|
@@ -4810,7 +3784,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4810
3784
|
const warning = getProjectArtifactPackagingWarning(workflowCwd);
|
|
4811
3785
|
if (warning) console.warn(`[pi-subagents] ${warning}`);
|
|
4812
3786
|
}
|
|
4813
|
-
const workflowPrompts = { render: (ref: string, vars?: unknown) => renderWorkflowPrompt(ref, vars, workflowCwd) };
|
|
4814
3787
|
const chatProgressResult = resolveWorkflowChatProgress({ requested: requestParams.chatProgress, parentCwd, workflowCwd, background: requestParams.async !== false });
|
|
4815
3788
|
if (chatProgressResult.error) return { content: [{ type: "text", text: chatProgressResult.error }], isError: true, details: { mode: "workflow", results: [] } };
|
|
4816
3789
|
const chatProgress = chatProgressResult.projection!;
|
|
@@ -4887,12 +3860,17 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4887
3860
|
};
|
|
4888
3861
|
if (workflowRunId) {
|
|
4889
3862
|
const toolCallId = _id;
|
|
3863
|
+
const workflowSessionRoot = requestParams.sessionDir
|
|
3864
|
+
? path.resolve(deps.expandTilde(requestParams.sessionDir))
|
|
3865
|
+
: trustedSessionRootsForStatus(ctx, deps)[0];
|
|
4890
3866
|
const asyncDir = path.join(DIRS.async, workflowRunId);
|
|
4891
|
-
const resultPath =
|
|
3867
|
+
const resultPath = resultFilePath(DIRS.results, workflowRunId);
|
|
4892
3868
|
const statusPath = path.join(asyncDir, "status.json");
|
|
4893
3869
|
const eventsPath = path.join(asyncDir, "events.jsonl");
|
|
4894
3870
|
const startedAt = Date.now();
|
|
4895
3871
|
const currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
3872
|
+
const completionOwnerId = deps.state.completionOwnerId ?? currentCompletionOwnerId();
|
|
3873
|
+
deps.state.completionOwnerId = completionOwnerId;
|
|
4896
3874
|
try {
|
|
4897
3875
|
fs.mkdirSync(asyncDir, { recursive: true });
|
|
4898
3876
|
writeRunFanoutBudgetDescriptor(asyncDir, workflowFanoutBudget);
|
|
@@ -4910,25 +3888,57 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4910
3888
|
runId: workflowRunId,
|
|
4911
3889
|
toolCallId,
|
|
4912
3890
|
sessionId: currentSessionId ?? undefined,
|
|
3891
|
+
completionOwnerId,
|
|
4913
3892
|
mode: "workflow",
|
|
4914
3893
|
state: "running",
|
|
4915
3894
|
startedAt,
|
|
4916
3895
|
lastUpdate: startedAt,
|
|
4917
3896
|
...(timeout !== undefined ? { deadlineAt: startedAt + timeout, timeoutMs: timeout } : {}),
|
|
4918
3897
|
cwd: workflowCwd,
|
|
3898
|
+
...(workflowSessionRoot ? { sessionRoot: workflowSessionRoot } : {}),
|
|
4919
3899
|
pid: process.pid,
|
|
4920
3900
|
steps: [],
|
|
4921
3901
|
workflow: { trace: [], emits: [], console: [] },
|
|
4922
3902
|
runFanoutBudget: getRunFanoutBudgetSnapshot(workflowFanoutBudget),
|
|
4923
3903
|
};
|
|
4924
|
-
const appendWorkflowEvent = (event: Record<string, unknown>) =>
|
|
3904
|
+
const appendWorkflowEvent = (event: Record<string, unknown>) => {
|
|
3905
|
+
try {
|
|
3906
|
+
fs.appendFileSync(eventsPath, `${JSON.stringify({ ts: Date.now(), runId: workflowRunId, ...event })}\n`, "utf-8");
|
|
3907
|
+
} catch (error) {
|
|
3908
|
+
if (!isStorageCapacityError(error)) throw error;
|
|
3909
|
+
console.error(`Failed to append async workflow event while storage is full:`, error);
|
|
3910
|
+
}
|
|
3911
|
+
};
|
|
4925
3912
|
let indexedState: AsyncStatus["state"] | undefined;
|
|
3913
|
+
const indexPersistence = createCapacityResilientJsonWriter({
|
|
3914
|
+
keepAlive: true,
|
|
3915
|
+
onSuccess: (_filePath, payload) => { indexedState = (payload as { state: AsyncStatus["state"] }).state; },
|
|
3916
|
+
onError: (error, filePath) => console.error(`Failed to update async workflow index '${filePath}':`, error),
|
|
3917
|
+
});
|
|
3918
|
+
const queueActiveRunIndex = (): void => {
|
|
3919
|
+
const state = status.state;
|
|
3920
|
+
if (indexedState === state && indexPersistence.pendingCount() === 0) return;
|
|
3921
|
+
indexPersistence.write(asyncDir, { state, toolCallId: status.toolCallId }, (_filePath, payload) => {
|
|
3922
|
+
const indexPayload = payload as { state: AsyncStatus["state"]; toolCallId?: string };
|
|
3923
|
+
updateActiveRunIndex(asyncDir, indexPayload.state, indexPayload.toolCallId, { retryCapacityErrors: true });
|
|
3924
|
+
});
|
|
3925
|
+
};
|
|
3926
|
+
const runPersistence = createCapacityResilientJsonWriter({
|
|
3927
|
+
keepAlive: true,
|
|
3928
|
+
onSuccess: (filePath) => { if (filePath === statusPath) queueActiveRunIndex(); },
|
|
3929
|
+
onError: (error, filePath) => console.error(`Failed to persist async workflow state '${filePath}':`, error),
|
|
3930
|
+
write: (filePath, payload) => filePath === resultPath
|
|
3931
|
+
? writeAsyncResultFile(filePath, payload as Record<string, unknown>)
|
|
3932
|
+
: writeAtomicJson(filePath, payload),
|
|
3933
|
+
});
|
|
3934
|
+
let initialPersistenceComplete = false;
|
|
4926
3935
|
const persist = () => {
|
|
4927
3936
|
status.lastUpdate = Date.now();
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
3937
|
+
if (initialPersistenceComplete) runPersistence.write(statusPath, status);
|
|
3938
|
+
else {
|
|
3939
|
+
writeAtomicJson(statusPath, status);
|
|
3940
|
+
initialPersistenceComplete = true;
|
|
3941
|
+
queueActiveRunIndex();
|
|
4932
3942
|
}
|
|
4933
3943
|
const job = deps.state.asyncJobs.get(workflowRunId);
|
|
4934
3944
|
if (job) {
|
|
@@ -4952,6 +3962,17 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4952
3962
|
job.workflow = status.workflow;
|
|
4953
3963
|
}
|
|
4954
3964
|
};
|
|
3965
|
+
const writeWorkflowResult = (payload: Record<string, unknown>): boolean => {
|
|
3966
|
+
try {
|
|
3967
|
+
runPersistence.write(resultPath, payload);
|
|
3968
|
+
return true;
|
|
3969
|
+
} catch (error) {
|
|
3970
|
+
const message = `Failed to write async workflow result ${resultPath}: ${error instanceof Error ? error.message : String(error)}`;
|
|
3971
|
+
console.error(message, error);
|
|
3972
|
+
appendWorkflowEvent({ type: "subagent.workflow.result_write_failed", error: message });
|
|
3973
|
+
return false;
|
|
3974
|
+
}
|
|
3975
|
+
};
|
|
4955
3976
|
const projectWorkflowActivity = () => {
|
|
4956
3977
|
const steps = status.steps ?? [];
|
|
4957
3978
|
const runningSteps = steps.filter((step) => step.status === "running");
|
|
@@ -4973,20 +3994,32 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4973
3994
|
status.toolCount = toolCounts.length > 0 ? toolCounts.reduce((total, count) => total + count, 0) : undefined;
|
|
4974
3995
|
status.currentStep = runningSteps.length === 1 ? steps.indexOf(runningSteps[0]!) : undefined;
|
|
4975
3996
|
};
|
|
4976
|
-
const workflowJob: AsyncJobState = { asyncId: workflowRunId, asyncDir, cwd: workflowCwd, status: "running", sessionId: currentSessionId ?? undefined, mode: "workflow", agents: [], steps: [], startedAt, updatedAt: startedAt, ...(timeout !== undefined ? { timeoutMs: timeout, deadlineAt: startedAt + timeout } : {}), workflow: status.workflow };
|
|
3997
|
+
const workflowJob: AsyncJobState = { asyncId: workflowRunId, asyncDir, toolCallId, cwd: workflowCwd, ...(workflowSessionRoot ? { sessionRoot: workflowSessionRoot } : {}), status: "running", sessionId: currentSessionId ?? undefined, mode: "workflow", agents: [], steps: [], startedAt, updatedAt: startedAt, ...(timeout !== undefined ? { timeoutMs: timeout, deadlineAt: startedAt + timeout } : {}), workflow: status.workflow };
|
|
4977
3998
|
deps.state.asyncJobs.set(workflowRunId, workflowJob);
|
|
4978
3999
|
deps.state.fleetJobs ??= new Map();
|
|
4979
4000
|
deps.state.fleetJobs.set(workflowRunId, workflowJob);
|
|
4980
|
-
|
|
4001
|
+
try {
|
|
4002
|
+
persist();
|
|
4003
|
+
} catch (error) {
|
|
4004
|
+
deps.state.workflowControllers?.delete(workflowRunId);
|
|
4005
|
+
deps.state.asyncJobs.delete(workflowRunId);
|
|
4006
|
+
deps.state.fleetJobs?.delete(workflowRunId);
|
|
4007
|
+
workflowCapacity?.rollback();
|
|
4008
|
+
indexPersistence.dispose();
|
|
4009
|
+
return { content: [{ type: "text", text: `Failed to create async workflow storage: ${error instanceof Error ? error.message : String(error)}` }], isError: true, details: { mode: "workflow", results: [] } };
|
|
4010
|
+
}
|
|
4981
4011
|
appendWorkflowEvent({ type: "subagent.workflow.started" });
|
|
4982
4012
|
const { workflowScript, async: _workflowAsync, chatProgress: _chatProgress, ...workflowRequest } = requestParams;
|
|
4983
4013
|
void Promise.resolve().then(async () => {
|
|
4014
|
+
const workflowDeadlineAt = timeout === undefined ? undefined : Date.now() + timeout;
|
|
4984
4015
|
const workflowResults: SingleResult[] = [];
|
|
4016
|
+
const workflowChildRunIds = new Map<string, string>();
|
|
4985
4017
|
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;
|
|
4986
4018
|
const workflowOutput = typeof workflowChildDefaults.output === "string" || typeof workflowChildDefaults.output === "boolean" ? workflowChildDefaults.output : undefined;
|
|
4987
4019
|
const configuredOutputBaseDir = resolveConfiguredSingleRunOutputBaseDir(deps);
|
|
4988
4020
|
const workflowAggregateOutputPath = resolveWorkflowAggregateOutputPath(workflowOutput, ctx.cwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, workflowRunId), configuredOutputBaseDir);
|
|
4989
4021
|
const claimedOutputPaths = new Map<string, string>();
|
|
4022
|
+
const producedChildOutputPaths = new Set<string>();
|
|
4990
4023
|
const workflowSteps = new Map<string, NonNullable<AsyncStatus["steps"]>[number]>();
|
|
4991
4024
|
let projectedTraceLength = 0;
|
|
4992
4025
|
let projectedTraceTail: NonNullable<Details["workflow"]>["trace"][number] | undefined;
|
|
@@ -5012,12 +4045,16 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5012
4045
|
? "completed"
|
|
5013
4046
|
: entry.state === "stopped"
|
|
5014
4047
|
? "stopped"
|
|
5015
|
-
: "
|
|
4048
|
+
: entry.state === "detached"
|
|
4049
|
+
? "paused"
|
|
4050
|
+
: "failed";
|
|
5016
4051
|
if (existing) {
|
|
5017
4052
|
existing.status = mapped;
|
|
5018
4053
|
if (entry.agent) existing.agent = entry.agent;
|
|
5019
4054
|
if (entry.runId) existing.runId = entry.runId;
|
|
5020
4055
|
if (entry.state === "failed" && !entry.runId && existing.async === undefined) existing.async = false;
|
|
4056
|
+
if (entry.state === "detached") existing.activityState = "needs_attention";
|
|
4057
|
+
else if (existing.status !== "running") delete existing.activityState;
|
|
5021
4058
|
if (entry.state === "stopped") existing.stopped = true;
|
|
5022
4059
|
else delete existing.stopped;
|
|
5023
4060
|
if (entry.error === undefined) delete existing.error;
|
|
@@ -5034,6 +4071,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5034
4071
|
startedAt: Date.now(),
|
|
5035
4072
|
...(entry.runId ? { runId: entry.runId } : {}),
|
|
5036
4073
|
...(entry.state === "failed" && !entry.runId ? { async: false } : {}),
|
|
4074
|
+
...(entry.state === "detached" ? { activityState: "needs_attention" as const } : {}),
|
|
5037
4075
|
...(entry.state === "stopped" ? { stopped: true } : {}),
|
|
5038
4076
|
};
|
|
5039
4077
|
status.steps?.push(step);
|
|
@@ -5051,7 +4089,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5051
4089
|
script: workflowScript,
|
|
5052
4090
|
timeoutMs: timeout,
|
|
5053
4091
|
signal: controller.signal,
|
|
5054
|
-
prompts: workflowPrompts,
|
|
5055
4092
|
...(workflowState ? { state: workflowState } : {}),
|
|
5056
4093
|
onTrace: updateTrace,
|
|
5057
4094
|
admit: (calls) => {
|
|
@@ -5082,7 +4119,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5082
4119
|
});
|
|
5083
4120
|
const result = await runMissionWorkflowChild(missionBinding, workflowRunId, key, childPhase, () => {
|
|
5084
4121
|
const childRequest = bindMissionWorkflowChildAsyncLaunch(
|
|
5085
|
-
{ ...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 },
|
|
4122
|
+
{ ...prepareWorkflowChildLaunchParams({ workflowDefaults: workflowChildDefaults, childParams, parentWorkflowRunId: workflowRunId, workflowKey: key, ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, workflowAgentScope: workflowChildDefaults.agentScope, options: { missionDetached: detachWorkflowChildMissions, runFanoutBudget: workflowFanoutBudget, parentDeadlineAt: workflowDeadlineAt } }), runFanoutAdmitted: admission.admitted },
|
|
5086
4123
|
missionBinding,
|
|
5087
4124
|
deps.asyncByDefault,
|
|
5088
4125
|
);
|
|
@@ -5124,7 +4161,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5124
4161
|
}, ctx, preserveActiveSession);
|
|
5125
4162
|
});
|
|
5126
4163
|
workflowResults.push(...result.details.results);
|
|
4164
|
+
for (const childResult of result.details.results) {
|
|
4165
|
+
if (childResult.savedOutputPath) producedChildOutputPaths.add(childResult.savedOutputPath);
|
|
4166
|
+
}
|
|
5127
4167
|
const child = workflowChildResult(key, result);
|
|
4168
|
+
if (child.runId) workflowChildRunIds.set(key, child.runId);
|
|
5128
4169
|
const step = status.steps?.find((candidate) => candidate.workflowKey === key);
|
|
5129
4170
|
if (step) {
|
|
5130
4171
|
step.async = Boolean(result.details.asyncId || result.details.asyncDir);
|
|
@@ -5148,26 +4189,37 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5148
4189
|
return child;
|
|
5149
4190
|
},
|
|
5150
4191
|
status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession)),
|
|
4192
|
+
steer: (key, message, options, workflowSignal) => steerWorkflowChildByKey({ state: deps.state, workflowRunId, key, message, options, signal: workflowSignal, resolveRunId: () => workflowChildRunIds.get(key) }),
|
|
5151
4193
|
});
|
|
5152
4194
|
const returnPreview = formatWorkflowValue(workflow.value).slice(0, 1_000);
|
|
5153
4195
|
const emitPreview = workflow.emits.length > 0 ? ` Emitted: ${workflow.emits.map(formatWorkflowValue).join(", ").slice(0, 1_000)}` : "";
|
|
5154
4196
|
const summary = `Workflow completed with ${workflow.children.length} child run(s). Return: ${returnPreview}${emitPreview} Trace: ${workflow.trace.length} event(s).`;
|
|
5155
|
-
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, summary);
|
|
4197
|
+
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, summary, producedChildOutputPaths);
|
|
5156
4198
|
const resultSummary = appendWorkflowOutputWarning(summary, outputWarning);
|
|
5157
4199
|
const workflowUsage = sumResultsUsage(workflowResults);
|
|
5158
4200
|
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) };
|
|
5159
|
-
|
|
4201
|
+
if (!writeWorkflowResult({ id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: true, state: "complete", summary: resultSummary, output: resultSummary, results: workflow.children.map((child) => ({ workflowKey: child.key, ...(child.agent ? { agent: child.agent } : {}), ...(child.runId ? { runId: child.runId } : {}), output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, success: child.ok, ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, asyncDir, cwd: workflowCwd, sessionId: currentSessionId, completionOwnerId, timestamp: Date.now(), durationMs: Date.now() - startedAt })) return;
|
|
5160
4202
|
persist();
|
|
5161
|
-
appendWorkflowEvent({ type: "subagent.workflow.completed", state:
|
|
4203
|
+
appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(status.error ? { error: status.error } : {}) });
|
|
5162
4204
|
} catch (error) {
|
|
5163
4205
|
const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
|
|
5164
4206
|
const stopped = controller.signal.aborted;
|
|
5165
|
-
|
|
5166
|
-
const
|
|
5167
|
-
const
|
|
5168
|
-
|
|
4207
|
+
const detachedChildKeys = new Set(partial.children.filter((child) => child.detached).map((child) => child.key));
|
|
4208
|
+
const hasRealFailedChild = partial.children.some((child) => !child.ok && !child.detached);
|
|
4209
|
+
const pauseForDetached = !stopped && error instanceof WorkflowScriptError && error.errorKind === "detached-child" && detachedChildKeys.size > 0 && !hasRealFailedChild;
|
|
4210
|
+
const state = stopped ? "stopped" : pauseForDetached ? "paused" : "failed";
|
|
4211
|
+
for (const step of status.steps ?? []) {
|
|
4212
|
+
if (step.workflowKey && detachedChildKeys.has(step.workflowKey)) {
|
|
4213
|
+
step.status = "paused";
|
|
4214
|
+
step.activityState = "needs_attention";
|
|
4215
|
+
}
|
|
4216
|
+
}
|
|
4217
|
+
status = compactOptional<AsyncStatus>({ ...status, state, stopped: stopped || undefined, activityState: pauseForDetached ? "needs_attention" : undefined, error: error instanceof Error ? error.message : String(error), endedAt: Date.now(), workflow: { trace: partial.trace, emits: partial.emits, console: partial.console } });
|
|
4218
|
+
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, status.error ?? (pauseForDetached ? "Workflow paused." : "Workflow failed."), producedChildOutputPaths);
|
|
4219
|
+
const resultSummary = appendWorkflowOutputWarning(status.error ?? (pauseForDetached ? "Workflow paused." : "Workflow failed."), outputWarning);
|
|
4220
|
+
if (!writeWorkflowResult({ id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: false, state: status.state, summary: resultSummary, error: 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 ? { detached: true } : {}), ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, asyncDir, cwd: workflowCwd, sessionId: currentSessionId, completionOwnerId, timestamp: Date.now(), durationMs: Date.now() - startedAt })) return;
|
|
5169
4221
|
persist();
|
|
5170
|
-
appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, error: status.error });
|
|
4222
|
+
appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, error: status.error, ...(status.activityState ? { activityState: status.activityState } : {}) });
|
|
5171
4223
|
} finally {
|
|
5172
4224
|
deps.state.workflowControllers?.delete(workflowRunId);
|
|
5173
4225
|
deps.state.activeAsyncCapacity = workflowCapacity?.reconcile(new Set(deps.state.workflowControllers?.keys() ?? []))
|
|
@@ -5184,8 +4236,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5184
4236
|
const configuredOutputBaseDir = resolveConfiguredSingleRunOutputBaseDir(deps);
|
|
5185
4237
|
const workflowAggregateOutputPath = resolveWorkflowAggregateOutputPath(workflowOutput, ctx.cwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, _id), configuredOutputBaseDir);
|
|
5186
4238
|
const claimedOutputPaths = new Map<string, string>();
|
|
4239
|
+
const producedChildOutputPaths = new Set<string>();
|
|
5187
4240
|
const workflowResults: SingleResult[] = [];
|
|
4241
|
+
const workflowChildRunIds = new Map<string, string>();
|
|
5188
4242
|
let liveWorkflow: NonNullable<Details["workflow"]> = { trace: [], emits: [], console: [] };
|
|
4243
|
+
const workflowDeadlineAt = timeout === undefined ? undefined : Date.now() + timeout;
|
|
5189
4244
|
const sendWorkflowProgress = () => {
|
|
5190
4245
|
const update = workflowChatProgressUpdate(_id, chatProgress, liveWorkflow);
|
|
5191
4246
|
if (update) onUpdate?.(update);
|
|
@@ -5195,7 +4250,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5195
4250
|
script: requestParams.workflowScript,
|
|
5196
4251
|
timeoutMs: timeout,
|
|
5197
4252
|
signal,
|
|
5198
|
-
prompts: workflowPrompts,
|
|
5199
4253
|
...(workflowState ? { state: workflowState } : {}),
|
|
5200
4254
|
onTrace: (trace) => {
|
|
5201
4255
|
liveWorkflow = { ...liveWorkflow, trace };
|
|
@@ -5226,7 +4280,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5226
4280
|
});
|
|
5227
4281
|
const result = await runMissionWorkflowChild(missionBinding, _id, key, childPhase, () => {
|
|
5228
4282
|
const childRequest = bindMissionWorkflowChildAsyncLaunch(
|
|
5229
|
-
{ ...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 },
|
|
4283
|
+
{ ...prepareWorkflowChildLaunchParams({ workflowDefaults: workflowChildDefaults, childParams, parentWorkflowRunId: _id, workflowKey: key, ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, workflowAgentScope: workflowChildDefaults.agentScope, options: { missionDetached: detachWorkflowChildMissions, suppressRoutineResultIntercom: chatProgress.mode === "live-card", runFanoutBudget: workflowFanoutBudget, parentDeadlineAt: workflowDeadlineAt } }), runFanoutAdmitted: admission.admitted },
|
|
5230
4284
|
missionBinding,
|
|
5231
4285
|
deps.asyncByDefault,
|
|
5232
4286
|
);
|
|
@@ -5246,8 +4300,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5246
4300
|
}, ctx, preserveActiveSession);
|
|
5247
4301
|
});
|
|
5248
4302
|
workflowResults.push(...result.details.results);
|
|
4303
|
+
for (const childResult of result.details.results) {
|
|
4304
|
+
if (childResult.savedOutputPath) producedChildOutputPaths.add(childResult.savedOutputPath);
|
|
4305
|
+
}
|
|
5249
4306
|
if (result.details.asyncDir && missionBinding) writeMissionAsyncBinding(result.details.asyncDir, missionBinding);
|
|
5250
4307
|
const child = workflowChildResult(key, result);
|
|
4308
|
+
if (child.runId) workflowChildRunIds.set(key, child.runId);
|
|
5251
4309
|
const childStatus = missionWorkflowChildStatus(result);
|
|
5252
4310
|
recordMissionWorkflowChild(missionBinding, _id, key, {
|
|
5253
4311
|
status: childStatus,
|
|
@@ -5261,6 +4319,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5261
4319
|
return child;
|
|
5262
4320
|
},
|
|
5263
4321
|
status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession)),
|
|
4322
|
+
steer: (key, message, options, workflowSignal) => steerWorkflowChildByKey({ state: deps.state, workflowRunId: _id, key, message, options, signal: workflowSignal, resolveRunId: () => workflowChildRunIds.get(key) }),
|
|
5264
4323
|
});
|
|
5265
4324
|
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}` : ""}`);
|
|
5266
4325
|
const sections = ["Workflow completed.", `Return:\n${formatWorkflowValue(workflow.value)}`];
|
|
@@ -5268,7 +4327,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5268
4327
|
if (workflow.console.length > 0) sections.push(`Console:\n${workflow.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
|
|
5269
4328
|
if (traceLines.length > 0) sections.push(`Call trace:\n${traceLines.join("\n")}`);
|
|
5270
4329
|
const workflowText = sections.join("\n\n");
|
|
5271
|
-
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, workflowText);
|
|
4330
|
+
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, workflowText, producedChildOutputPaths);
|
|
5272
4331
|
const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
|
|
5273
4332
|
return attachWorkflowMission(withRunFanoutBudget({
|
|
5274
4333
|
content: [{ type: "text", text: displayText }],
|
|
@@ -5283,7 +4342,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5283
4342
|
if (partial.console.length > 0) sections.push(`Console:\n${partial.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
|
|
5284
4343
|
if (traceLines.length > 0) sections.push(`Call trace:\n${traceLines.join("\n")}`);
|
|
5285
4344
|
const workflowText = sections.join("\n\n");
|
|
5286
|
-
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, workflowText);
|
|
4345
|
+
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, workflowText, producedChildOutputPaths);
|
|
5287
4346
|
const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
|
|
5288
4347
|
return attachWorkflowMission(withRunFanoutBudget({
|
|
5289
4348
|
content: [{ type: "text", text: displayText }],
|
|
@@ -5292,7 +4351,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5292
4351
|
}, workflowFanoutBudget));
|
|
5293
4352
|
}
|
|
5294
4353
|
}
|
|
5295
|
-
const directParams =
|
|
4354
|
+
const directParams = requestParams;
|
|
5296
4355
|
const requestCwd = resolveRequestedCwd(ctx.cwd, directParams.cwd);
|
|
5297
4356
|
const paramsWithResolvedCwd = directParams.cwd === undefined ? directParams : { ...directParams, cwd: requestCwd };
|
|
5298
4357
|
const action = paramsWithResolvedCwd.action;
|
|
@@ -5609,61 +4668,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5609
4668
|
}
|
|
5610
4669
|
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots })));
|
|
5611
4670
|
}
|
|
5612
|
-
|
|
5613
|
-
if (action === "approve-checkpoint" || action === "reject-checkpoint") {
|
|
5614
|
-
deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
5615
|
-
const targetRunId = paramsWithResolvedCwd.runId ?? paramsWithResolvedCwd.id;
|
|
5616
|
-
if (!targetRunId && !paramsWithResolvedCwd.dir) {
|
|
5617
|
-
return { content: [{ type: "text", text: `action='${action}' requires id or dir.` }], isError: true, details: { mode: "management", results: [] } };
|
|
5618
|
-
}
|
|
5619
|
-
try {
|
|
5620
|
-
const resolved = targetRunId ? resolveSubagentRunId(targetRunId, omitUndefinedProperties({ state: deps.state, nested: nestedResolutionScopeForExecutor(deps) })) : undefined;
|
|
5621
|
-
const decision = action === "approve-checkpoint" ? "approved" : "rejected";
|
|
5622
|
-
if (resolved?.kind === "foreground") {
|
|
5623
|
-
const run = deps.state.foregroundRuns?.get(resolved.id);
|
|
5624
|
-
if (!run?.checkpoint || run.checkpoint.status !== "pending") {
|
|
5625
|
-
return { content: [{ type: "text", text: `Run '${resolved.id}' is not paused at an approval checkpoint.` }], isError: true, details: { mode: "management", results: [] } };
|
|
5626
|
-
}
|
|
5627
|
-
if (deps.state.currentSessionId && run.sessionId !== deps.state.currentSessionId) {
|
|
5628
|
-
return { content: [{ type: "text", text: `Run '${resolved.id}' was not found in the active session.` }], isError: true, details: { mode: "management", results: [] } };
|
|
5629
|
-
}
|
|
5630
|
-
const decidedAt = Date.now();
|
|
5631
|
-
const checkpoint: NonNullable<Details["checkpoint"]> = decision === "approved"
|
|
5632
|
-
? { ...run.checkpoint, status: "approved", approvedAt: decidedAt }
|
|
5633
|
-
: { ...run.checkpoint, status: "rejected", rejectedAt: decidedAt };
|
|
5634
|
-
run.checkpoint = checkpoint;
|
|
5635
|
-
run.updatedAt = decidedAt;
|
|
5636
|
-
return {
|
|
5637
|
-
content: [{ type: "text", text: `Checkpoint '${checkpoint.name}' ${decision} for foreground run ${resolved.id}.` }],
|
|
5638
|
-
details: { mode: "management", results: [], checkpoint },
|
|
5639
|
-
};
|
|
5640
|
-
}
|
|
5641
|
-
const location = paramsWithResolvedCwd.dir
|
|
5642
|
-
? resolveAsyncRunLocation(paramsWithResolvedCwd, DIRS.async, DIRS.results)
|
|
5643
|
-
: resolved?.kind === "async"
|
|
5644
|
-
? resolved.location
|
|
5645
|
-
: undefined;
|
|
5646
|
-
if (!location?.asyncDir) {
|
|
5647
|
-
return { content: [{ type: "text", text: `action='${action}' targets paused foreground or async checkpoints. No matching run found.` }], isError: true, details: { mode: "management", results: [] } };
|
|
5648
|
-
}
|
|
5649
|
-
const status = readStatus(location.asyncDir);
|
|
5650
|
-
const runId = status?.runId ?? location.resolvedId ?? targetRunId ?? path.basename(location.asyncDir);
|
|
5651
|
-
if (!status?.checkpoint || status.state !== "paused") {
|
|
5652
|
-
return { content: [{ type: "text", text: `Run '${runId}' is not paused at an approval checkpoint.` }], isError: true, details: { mode: "management", results: [] } };
|
|
5653
|
-
}
|
|
5654
|
-
if (deps.state.currentSessionId && status.sessionId !== deps.state.currentSessionId) {
|
|
5655
|
-
return { content: [{ type: "text", text: `Run '${runId}' was not found in the active session.` }], isError: true, details: { mode: "management", results: [] } };
|
|
5656
|
-
}
|
|
5657
|
-
deliverCheckpointDecisionRequest({ asyncDir: location.asyncDir, decision, source: "subagent-action", ...(paramsWithResolvedCwd.message ? { reason: paramsWithResolvedCwd.message } : {}) });
|
|
5658
|
-
return {
|
|
5659
|
-
content: [{ type: "text", text: `Checkpoint '${status.checkpoint.name}' ${decision} for run ${runId}.` }],
|
|
5660
|
-
details: { mode: "management", results: [], checkpoint: { ...status.checkpoint, status: decision } },
|
|
5661
|
-
};
|
|
5662
|
-
} catch (error) {
|
|
5663
|
-
const text = error instanceof Error ? error.message : String(error);
|
|
5664
|
-
return { content: [{ type: "text", text }], isError: true, details: { mode: "management", results: [] } };
|
|
5665
|
-
}
|
|
5666
|
-
}
|
|
5667
4671
|
if (action === "resume") {
|
|
5668
4672
|
return resumeAsyncRun(omitUndefinedProperties({ params: paramsWithResolvedCwd, requestCwd, ctx, deps, parentModel: requestParentModel, signal }));
|
|
5669
4673
|
}
|
|
@@ -5952,14 +4956,23 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5952
4956
|
const parentSessionFile = ctx.sessionManager.getSessionFile() ?? null;
|
|
5953
4957
|
const discovered = deps.discoverAgents(effectiveCwd, scope);
|
|
5954
4958
|
const discoveredAgents = discovered.agents;
|
|
5955
|
-
const canonicalParams = canonicalizeExecutionParams(effectiveParams, discoveredAgents);
|
|
4959
|
+
const canonicalParams = canonicalizeExecutionParams(effectiveParams, discoveredAgents, discovered.agentDiagnostics);
|
|
5956
4960
|
if (canonicalParams.error) return buildRequestedModeError(effectiveParams, canonicalParams.error);
|
|
5957
4961
|
effectiveParams = canonicalParams.params!;
|
|
5958
4962
|
const modelScope = discovered.modelScope;
|
|
5959
4963
|
effectiveParams = applySingleAgentLaunchDefaults(effectiveParams, discoveredAgents);
|
|
5960
4964
|
const turnBudget = resolveTurnBudgetConfig(effectiveParams.turnBudget ?? deps.config.turnBudget);
|
|
5961
4965
|
if (turnBudget.error) return buildRequestedModeError(effectiveParams, turnBudget.error);
|
|
5962
|
-
|
|
4966
|
+
// An agent-level defaultContext is a preference, unlike an explicit request.
|
|
4967
|
+
// Prefer fork only when the parent session is persisted and has a current leaf;
|
|
4968
|
+
// otherwise use fresh immediately instead of launching a guaranteed-to-fail fork.
|
|
4969
|
+
// Explicit context:"fork" remains strict.
|
|
4970
|
+
const contextPolicy = resolveAgentDefaultContextPolicy(
|
|
4971
|
+
effectiveParams,
|
|
4972
|
+
discoveredAgents,
|
|
4973
|
+
deps.config.defaultSubagentContext,
|
|
4974
|
+
canPreferFork(ctx.sessionManager),
|
|
4975
|
+
);
|
|
5963
4976
|
effectiveParams = contextPolicy.params;
|
|
5964
4977
|
const sessionName = resolveIntercomSessionTarget(deps.pi.getSessionName(), ctx.sessionManager.getSessionId());
|
|
5965
4978
|
const intercomBridge = resolveIntercomBridge({
|
|
@@ -5971,7 +4984,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5971
4984
|
const agents = intercomBridge.active
|
|
5972
4985
|
? discoveredAgents.map((agent) => applyIntercomBridgeToAgent(agent, intercomBridge))
|
|
5973
4986
|
: discoveredAgents;
|
|
5974
|
-
const runId = randomUUID()
|
|
4987
|
+
const runId = randomUUID();
|
|
5975
4988
|
const inheritedNestedRoute = resolveInheritedNestedRouteFromEnv();
|
|
5976
4989
|
const nestedParentAddress = inheritedNestedRoute ? resolveNestedParentAddressFromEnv() : undefined;
|
|
5977
4990
|
const shareEnabled = effectiveParams.share === true;
|
|
@@ -6013,6 +5026,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6013
5026
|
const parentModel = requestParentModel;
|
|
6014
5027
|
prepareForkThinking = (agentName, index, modelOverride) => {
|
|
6015
5028
|
const agentConfig = agents.find((agent) => agent.name === agentName);
|
|
5029
|
+
if (agentConfig?.runner?.type === "external-cli" || agentConfig?.runner?.type === "external-job") {
|
|
5030
|
+
forkThinkingRequirements.set(index, true);
|
|
5031
|
+
return;
|
|
5032
|
+
}
|
|
6016
5033
|
const primaryModel = resolveEffectiveSubagentModel(
|
|
6017
5034
|
modelOverride,
|
|
6018
5035
|
agentConfig?.model,
|
|
@@ -6050,9 +5067,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6050
5067
|
: (effectiveParams.chain ?? []).flatMap((step) => getStepAgents(step as ChainStep));
|
|
6051
5068
|
const externalAgent = selectedAgentNames
|
|
6052
5069
|
.map((name) => agents.find((agent) => agent.name === name))
|
|
6053
|
-
.find((agent) => agent?.runner?.type === "external-cli");
|
|
5070
|
+
.find((agent) => agent?.runner?.type === "external-cli" || agent?.runner?.type === "external-job");
|
|
6054
5071
|
if (externalAgent && (!effectiveAsync || effectiveParams.foregroundOnly === true)) {
|
|
6055
|
-
return buildRequestedModeError(effectiveParams, `Agent '${externalAgent.name}' uses runner.type='
|
|
5072
|
+
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.`);
|
|
6056
5073
|
}
|
|
6057
5074
|
const foregroundTimeout = resolveSingleAgentLaunchTimeout(
|
|
6058
5075
|
effectiveParams,
|
|
@@ -6249,6 +5266,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6249
5266
|
usageBudget: usageBudget.budget,
|
|
6250
5267
|
allowZeroToolBudget,
|
|
6251
5268
|
configToolBudget: configToolBudget.toolBudget,
|
|
5269
|
+
configToolTimeoutMs: deps.config.toolTimeoutMs,
|
|
6252
5270
|
contextPolicy,
|
|
6253
5271
|
modelScope,
|
|
6254
5272
|
parentModel: requestParentModel,
|
|
@@ -6330,6 +5348,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6330
5348
|
};
|
|
6331
5349
|
deps.state.foregroundControls.set(runId, foregroundControl);
|
|
6332
5350
|
deps.state.lastForegroundControlId = runId;
|
|
5351
|
+
deps.activateSupervisorTransport?.();
|
|
5352
|
+
deps.refreshResultDelivery?.();
|
|
6333
5353
|
}
|
|
6334
5354
|
|
|
6335
5355
|
const writeNestedForegroundEvent = (type: "subagent.nested.started" | "subagent.nested.completed", result?: AgentToolResult<Details>): void => {
|
|
@@ -6446,16 +5466,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6446
5466
|
nestedForegroundStarted = true;
|
|
6447
5467
|
}
|
|
6448
5468
|
const runFanoutAnnotateContent = !delegatedExecution;
|
|
6449
|
-
if (hasChain && effectiveParams.chain) {
|
|
6450
|
-
const result = await runChainPath(execData, deps);
|
|
6451
|
-
writeNestedForegroundEvent("subagent.nested.completed", result);
|
|
6452
|
-
return attachMission(withRunFanoutBudget(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary), runFanoutBudget, { annotateContent: runFanoutAnnotateContent }));
|
|
6453
|
-
}
|
|
6454
|
-
if (hasTasks && effectiveParams.tasks) {
|
|
6455
|
-
const result = await runParallelPath(execData, deps);
|
|
6456
|
-
writeNestedForegroundEvent("subagent.nested.completed", result);
|
|
6457
|
-
return attachMission(withRunFanoutBudget(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary), runFanoutBudget, { annotateContent: runFanoutAnnotateContent }));
|
|
6458
|
-
}
|
|
6459
5469
|
if (hasSingle) {
|
|
6460
5470
|
const result = await runSinglePath(execData, deps);
|
|
6461
5471
|
writeNestedForegroundEvent("subagent.nested.completed", result);
|