@pi-agents/orchid 0.1.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/LICENSE +21 -0
- package/README.md +246 -0
- package/agents/AGENTS-MANIFEST.md +42 -0
- package/agents/brain.md +42 -0
- package/agents/context-builder.md +46 -0
- package/agents/delegate.md +12 -0
- package/agents/dev-1.md +42 -0
- package/agents/oracle.md +73 -0
- package/agents/planner.md +55 -0
- package/agents/researcher.md +52 -0
- package/agents/reviewer.md +79 -0
- package/agents/scout.md +50 -0
- package/agents/tester.md +45 -0
- package/agents/worker.md +55 -0
- package/extensions/ralph.ts +1 -0
- package/extensions/reviewer-extension.ts +125 -0
- package/extensions/task-orchestrator.ts +28 -0
- package/package.json +63 -0
- package/prompts/gather-context-and-clarify.md +13 -0
- package/prompts/parallel-cleanup.md +59 -0
- package/prompts/parallel-context-build.md +53 -0
- package/prompts/parallel-handoff-plan.md +59 -0
- package/prompts/parallel-research.md +50 -0
- package/prompts/parallel-review.md +54 -0
- package/prompts/review-loop.md +41 -0
- package/skills/orchid/SKILL.md +214 -0
- package/skills/orchid/orchid-cleanup/SKILL.md +122 -0
- package/skills/orchid/orchid-converge/SKILL.md +124 -0
- package/skills/orchid/orchid-decompose/SKILL.md +201 -0
- package/skills/orchid/orchid-doctor/SKILL.md +162 -0
- package/skills/orchid/orchid-investigate/SKILL.md +102 -0
- package/skills/orchid/orchid-launch/SKILL.md +147 -0
- package/skills/ralph/SKILL.md +73 -0
- package/skills/subagents/pi-subagents/SKILL.md +813 -0
- package/src/index.ts +7 -0
- package/src/orchestrator/abort.ts +534 -0
- package/src/orchestrator/agent-bridge-extension.ts +1020 -0
- package/src/orchestrator/agent-host.ts +954 -0
- package/src/orchestrator/cleanup.ts +776 -0
- package/src/orchestrator/config-loader.ts +1412 -0
- package/src/orchestrator/config-schema.ts +690 -0
- package/src/orchestrator/config.ts +81 -0
- package/src/orchestrator/context-window.ts +66 -0
- package/src/orchestrator/diagnostic-reports.ts +475 -0
- package/src/orchestrator/diagnostics.ts +394 -0
- package/src/orchestrator/discovery.ts +1833 -0
- package/src/orchestrator/engine-worker.ts +415 -0
- package/src/orchestrator/engine.ts +5940 -0
- package/src/orchestrator/execution.ts +3104 -0
- package/src/orchestrator/extension.ts +5934 -0
- package/src/orchestrator/formatting.ts +785 -0
- package/src/orchestrator/git.ts +88 -0
- package/src/orchestrator/index.ts +28 -0
- package/src/orchestrator/lane-runner.ts +1787 -0
- package/src/orchestrator/mailbox.ts +780 -0
- package/src/orchestrator/merge.ts +3414 -0
- package/src/orchestrator/messages.ts +1062 -0
- package/src/orchestrator/migrations.ts +278 -0
- package/src/orchestrator/naming.ts +117 -0
- package/src/orchestrator/path-resolver.ts +275 -0
- package/src/orchestrator/persistence.ts +2625 -0
- package/src/orchestrator/process-registry.ts +452 -0
- package/src/orchestrator/quality-gate.ts +1085 -0
- package/src/orchestrator/resume.ts +3488 -0
- package/src/orchestrator/sessions.ts +57 -0
- package/src/orchestrator/settings-loader.ts +136 -0
- package/src/orchestrator/settings-tui.ts +2208 -0
- package/src/orchestrator/sidecar-telemetry.ts +267 -0
- package/src/orchestrator/supervisor.ts +4548 -0
- package/src/orchestrator/task-executor-core.ts +675 -0
- package/src/orchestrator/tmux-compat.ts +37 -0
- package/src/orchestrator/tool-allowlist-constants.ts +37 -0
- package/src/orchestrator/types.ts +4465 -0
- package/src/orchestrator/verification.ts +547 -0
- package/src/orchestrator/waves.ts +1564 -0
- package/src/orchestrator/workspace.ts +707 -0
- package/src/orchestrator/worktree.ts +2725 -0
- package/src/ralph/index.ts +825 -0
- package/src/subagents/agents/agent-management.ts +648 -0
- package/src/subagents/agents/agent-scope.ts +6 -0
- package/src/subagents/agents/agent-selection.ts +23 -0
- package/src/subagents/agents/agent-serializer.ts +86 -0
- package/src/subagents/agents/agents.ts +832 -0
- package/src/subagents/agents/chain-serializer.ts +137 -0
- package/src/subagents/agents/frontmatter.ts +29 -0
- package/src/subagents/agents/identity.ts +30 -0
- package/src/subagents/agents/skills.ts +632 -0
- package/src/subagents/extension/config.ts +16 -0
- package/src/subagents/extension/control-notices.ts +92 -0
- package/src/subagents/extension/doctor.ts +199 -0
- package/src/subagents/extension/fanout-child.ts +170 -0
- package/src/subagents/extension/index.ts +573 -0
- package/src/subagents/extension/schemas.ts +168 -0
- package/src/subagents/intercom/intercom-bridge.ts +379 -0
- package/src/subagents/intercom/result-intercom.ts +377 -0
- package/src/subagents/runs/background/async-execution.ts +712 -0
- package/src/subagents/runs/background/async-job-tracker.ts +310 -0
- package/src/subagents/runs/background/async-resume.ts +345 -0
- package/src/subagents/runs/background/async-status.ts +325 -0
- package/src/subagents/runs/background/completion-dedupe.ts +63 -0
- package/src/subagents/runs/background/notify.ts +108 -0
- package/src/subagents/runs/background/parallel-groups.ts +45 -0
- package/src/subagents/runs/background/result-watcher.ts +307 -0
- package/src/subagents/runs/background/run-id-resolver.ts +83 -0
- package/src/subagents/runs/background/run-status.ts +269 -0
- package/src/subagents/runs/background/stale-run-reconciler.ts +336 -0
- package/src/subagents/runs/background/subagent-runner.ts +1808 -0
- package/src/subagents/runs/background/top-level-async.ts +13 -0
- package/src/subagents/runs/foreground/chain-clarify.ts +1333 -0
- package/src/subagents/runs/foreground/chain-execution.ts +938 -0
- package/src/subagents/runs/foreground/execution.ts +918 -0
- package/src/subagents/runs/foreground/subagent-executor.ts +2527 -0
- package/src/subagents/runs/shared/completion-guard.ts +147 -0
- package/src/subagents/runs/shared/long-running-guard.ts +175 -0
- package/src/subagents/runs/shared/mcp-direct-tool-allowlist.ts +365 -0
- package/src/subagents/runs/shared/model-fallback.ts +103 -0
- package/src/subagents/runs/shared/nested-events.ts +819 -0
- package/src/subagents/runs/shared/nested-path.ts +52 -0
- package/src/subagents/runs/shared/nested-render.ts +115 -0
- package/src/subagents/runs/shared/parallel-utils.ts +109 -0
- package/src/subagents/runs/shared/pi-args.ts +220 -0
- package/src/subagents/runs/shared/pi-spawn.ts +115 -0
- package/src/subagents/runs/shared/run-history.ts +60 -0
- package/src/subagents/runs/shared/single-output.ts +164 -0
- package/src/subagents/runs/shared/subagent-control.ts +226 -0
- package/src/subagents/runs/shared/subagent-prompt-runtime.ts +170 -0
- package/src/subagents/runs/shared/worktree.ts +577 -0
- package/src/subagents/shared/artifacts.ts +98 -0
- package/src/subagents/shared/atomic-json.ts +16 -0
- package/src/subagents/shared/file-coalescer.ts +40 -0
- package/src/subagents/shared/fork-context.ts +76 -0
- package/src/subagents/shared/formatters.ts +133 -0
- package/src/subagents/shared/jsonl-writer.ts +81 -0
- package/src/subagents/shared/model-info.ts +78 -0
- package/src/subagents/shared/post-exit-stdio-guard.ts +85 -0
- package/src/subagents/shared/session-identity.ts +10 -0
- package/src/subagents/shared/session-tokens.ts +44 -0
- package/src/subagents/shared/settings.ts +397 -0
- package/src/subagents/shared/status-format.ts +49 -0
- package/src/subagents/shared/types.ts +822 -0
- package/src/subagents/shared/utils.ts +450 -0
- package/src/subagents/slash/prompt-template-bridge.ts +397 -0
- package/src/subagents/slash/slash-bridge.ts +174 -0
- package/src/subagents/slash/slash-commands.ts +528 -0
- package/src/subagents/slash/slash-live-state.ts +292 -0
- package/src/subagents/tui/render-helpers.ts +80 -0
- package/src/subagents/tui/render.ts +1358 -0
- package/templates/agents/local/supervisor.md +33 -0
- package/templates/agents/local/task-merger.md +27 -0
- package/templates/agents/local/task-reviewer.md +30 -0
- package/templates/agents/local/task-worker.md +34 -0
- package/templates/agents/supervisor-routing.md +92 -0
- package/templates/agents/supervisor.md +229 -0
- package/templates/agents/task-merger.md +214 -0
- package/templates/agents/task-reviewer.md +260 -0
- package/templates/agents/task-worker-segment.md +44 -0
- package/templates/agents/task-worker.md +557 -0
- package/templates/tasks/CONTEXT.md +30 -0
- package/templates/tasks/EXAMPLE-001-hello-world/PROMPT.md +98 -0
- package/templates/tasks/EXAMPLE-001-hello-world/STATUS.md +73 -0
- package/templates/tasks/EXAMPLE-002-parallel-smoke/PROMPT.md +97 -0
- package/templates/tasks/EXAMPLE-002-parallel-smoke/STATUS.md +73 -0
|
@@ -0,0 +1,2527 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
5
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
6
|
+
import { type AgentConfig, type AgentScope } from "../../agents/agents.ts";
|
|
7
|
+
import { getArtifactsDir } from "../../shared/artifacts.ts";
|
|
8
|
+
import { ChainClarifyComponent, type ChainClarifyResult } from "./chain-clarify.ts";
|
|
9
|
+
import { toModelInfo, type ModelInfo } from "../../shared/model-info.ts";
|
|
10
|
+
import { executeChain } from "./chain-execution.ts";
|
|
11
|
+
import { resolveExecutionAgentScope } from "../../agents/agent-scope.ts";
|
|
12
|
+
import { handleManagementAction } from "../../agents/agent-management.ts";
|
|
13
|
+
import { buildDoctorReport } from "../../extension/doctor.ts";
|
|
14
|
+
import { clearPendingForegroundControlNotices } from "../../extension/control-notices.ts";
|
|
15
|
+
import { runSync } from "./execution.ts";
|
|
16
|
+
import { resolveModelCandidate } from "../shared/model-fallback.ts";
|
|
17
|
+
import { aggregateParallelOutputs } from "../shared/parallel-utils.ts";
|
|
18
|
+
import { recordRun } from "../shared/run-history.ts";
|
|
19
|
+
import {
|
|
20
|
+
buildChainInstructions,
|
|
21
|
+
writeInitialProgressFile,
|
|
22
|
+
getStepAgents,
|
|
23
|
+
isParallelStep,
|
|
24
|
+
resolveStepBehavior,
|
|
25
|
+
suppressProgressForReadOnlyTask,
|
|
26
|
+
taskDisallowsFileUpdates,
|
|
27
|
+
type ChainStep,
|
|
28
|
+
type ResolvedStepBehavior,
|
|
29
|
+
type SequentialStep,
|
|
30
|
+
type StepOverrides,
|
|
31
|
+
} from "../../shared/settings.ts";
|
|
32
|
+
import { discoverAvailableSkills, normalizeSkillInput } from "../../agents/skills.ts";
|
|
33
|
+
import { executeAsyncChain, executeAsyncSingle, formatAsyncStartedMessage, isAsyncAvailable } from "../background/async-execution.ts";
|
|
34
|
+
import { createForkContextResolver } from "../../shared/fork-context.ts";
|
|
35
|
+
import { resolveCurrentSessionId } from "../../shared/session-identity.ts";
|
|
36
|
+
import { applyIntercomBridgeToAgent, INTERCOM_BRIDGE_MARKER, resolveIntercomBridge, resolveIntercomSessionTarget, resolveSubagentIntercomTarget, type IntercomBridgeState } from "../../intercom/intercom-bridge.ts";
|
|
37
|
+
import { formatControlIntercomMessage, formatControlNoticeMessage, resolveControlConfig, shouldNotifyControlEvent } from "../shared/subagent-control.ts";
|
|
38
|
+
import { finalizeSingleOutput, injectSingleOutputInstruction, normalizeSingleOutputOverride, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
|
|
39
|
+
import { compactForegroundDetails, getSingleResultOutput, mapConcurrent, readStatus, resolveChildCwd } from "../../shared/utils.ts";
|
|
40
|
+
import {
|
|
41
|
+
attachNestedChildrenToResultChildren,
|
|
42
|
+
buildSubagentResultIntercomPayload,
|
|
43
|
+
deliverSubagentIntercomMessageEvent,
|
|
44
|
+
deliverSubagentResultIntercomEvent,
|
|
45
|
+
formatSubagentResultReceipt,
|
|
46
|
+
resolveSubagentResultStatus,
|
|
47
|
+
stripDetailsOutputsForIntercomReceipt,
|
|
48
|
+
} from "../../intercom/result-intercom.ts";
|
|
49
|
+
import { buildRevivedAsyncTask, resolveAsyncResumeTarget } from "../background/async-resume.ts";
|
|
50
|
+
import { createNestedRoute, readNestedControlResults, resolveInheritedNestedRouteFromEnv, resolveNestedAsyncDir, resolveNestedParentAddressFromEnv, updateForegroundNestedProjection, writeNestedControlRequest, writeNestedEvent, type NestedRunResolutionScope } from "../shared/nested-events.ts";
|
|
51
|
+
import { resolveSubagentRunId, type ResolvedSubagentRunId } from "../background/run-id-resolver.ts";
|
|
52
|
+
import { formatNestedRunStatusLines } from "../shared/nested-render.ts";
|
|
53
|
+
import { inspectSubagentStatus } from "../background/run-status.ts";
|
|
54
|
+
import { applyForceTopLevelAsyncOverride } from "../background/top-level-async.ts";
|
|
55
|
+
import {
|
|
56
|
+
cleanupWorktrees,
|
|
57
|
+
createWorktrees,
|
|
58
|
+
diffWorktrees,
|
|
59
|
+
findWorktreeTaskCwdConflict,
|
|
60
|
+
formatWorktreeDiffSummary,
|
|
61
|
+
formatWorktreeTaskCwdConflict,
|
|
62
|
+
type WorktreeSetup,
|
|
63
|
+
} from "../shared/worktree.ts";
|
|
64
|
+
import {
|
|
65
|
+
type AgentProgress,
|
|
66
|
+
type ArtifactConfig,
|
|
67
|
+
type ArtifactPaths,
|
|
68
|
+
type ControlConfig,
|
|
69
|
+
type ControlEvent,
|
|
70
|
+
type Details,
|
|
71
|
+
type ExtensionConfig,
|
|
72
|
+
type IntercomEventBus,
|
|
73
|
+
type MaxOutputConfig,
|
|
74
|
+
type NestedRouteInfo,
|
|
75
|
+
type NestedRunSummary,
|
|
76
|
+
type ResolvedControlConfig,
|
|
77
|
+
type SingleResult,
|
|
78
|
+
type SubagentRunMode,
|
|
79
|
+
type SubagentState,
|
|
80
|
+
DEFAULT_ARTIFACT_CONFIG,
|
|
81
|
+
SUBAGENT_ACTIONS,
|
|
82
|
+
SUBAGENT_CONTROL_EVENT,
|
|
83
|
+
SUBAGENT_CONTROL_INTERCOM_EVENT,
|
|
84
|
+
checkSubagentDepth,
|
|
85
|
+
resolveTopLevelParallelConcurrency,
|
|
86
|
+
resolveTopLevelParallelMaxTasks,
|
|
87
|
+
resolveChildMaxSubagentDepth,
|
|
88
|
+
resolveCurrentMaxSubagentDepth,
|
|
89
|
+
wrapForkTask,
|
|
90
|
+
} from "../../shared/types.ts";
|
|
91
|
+
|
|
92
|
+
const ASYNC_INTERRUPT_SIGNAL: NodeJS.Signals = process.platform === "win32" ? "SIGBREAK" : "SIGUSR2";
|
|
93
|
+
const MUTATING_MANAGEMENT_ACTIONS = new Set(["create", "update", "delete"]);
|
|
94
|
+
|
|
95
|
+
interface TaskParam {
|
|
96
|
+
agent: string;
|
|
97
|
+
task: string;
|
|
98
|
+
cwd?: string;
|
|
99
|
+
count?: number;
|
|
100
|
+
output?: string | boolean;
|
|
101
|
+
outputMode?: "inline" | "file-only";
|
|
102
|
+
reads?: string[] | boolean;
|
|
103
|
+
progress?: boolean;
|
|
104
|
+
model?: string;
|
|
105
|
+
skill?: string | string[] | boolean;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface SubagentParamsLike {
|
|
109
|
+
action?: string;
|
|
110
|
+
id?: string;
|
|
111
|
+
runId?: string;
|
|
112
|
+
dir?: string;
|
|
113
|
+
index?: number;
|
|
114
|
+
agent?: string;
|
|
115
|
+
task?: string;
|
|
116
|
+
message?: string;
|
|
117
|
+
chain?: ChainStep[];
|
|
118
|
+
tasks?: TaskParam[];
|
|
119
|
+
concurrency?: number;
|
|
120
|
+
worktree?: boolean;
|
|
121
|
+
context?: "fresh" | "fork";
|
|
122
|
+
async?: boolean;
|
|
123
|
+
clarify?: boolean;
|
|
124
|
+
share?: boolean;
|
|
125
|
+
control?: ControlConfig;
|
|
126
|
+
sessionDir?: string;
|
|
127
|
+
cwd?: string;
|
|
128
|
+
maxOutput?: MaxOutputConfig;
|
|
129
|
+
artifacts?: boolean;
|
|
130
|
+
includeProgress?: boolean;
|
|
131
|
+
model?: string;
|
|
132
|
+
skill?: string | string[] | boolean;
|
|
133
|
+
output?: string | boolean;
|
|
134
|
+
outputMode?: "inline" | "file-only";
|
|
135
|
+
agentScope?: unknown;
|
|
136
|
+
chainDir?: string;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
interface ExecutorDeps {
|
|
140
|
+
pi: ExtensionAPI;
|
|
141
|
+
state: SubagentState;
|
|
142
|
+
config: ExtensionConfig;
|
|
143
|
+
asyncByDefault: boolean;
|
|
144
|
+
tempArtifactsDir: string;
|
|
145
|
+
getSubagentSessionRoot: (parentSessionFile: string | null) => string;
|
|
146
|
+
expandTilde: (p: string) => string;
|
|
147
|
+
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[] };
|
|
148
|
+
allowMutatingManagementActions?: boolean;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
interface ExecutionContextData {
|
|
152
|
+
params: SubagentParamsLike;
|
|
153
|
+
effectiveCwd: string;
|
|
154
|
+
ctx: ExtensionContext;
|
|
155
|
+
signal: AbortSignal;
|
|
156
|
+
onUpdate?: (r: AgentToolResult<Details>) => void;
|
|
157
|
+
agents: AgentConfig[];
|
|
158
|
+
runId: string;
|
|
159
|
+
shareEnabled: boolean;
|
|
160
|
+
sessionRoot: string;
|
|
161
|
+
sessionDirForIndex: (idx?: number) => string;
|
|
162
|
+
sessionFileForIndex: (idx?: number) => string | undefined;
|
|
163
|
+
artifactConfig: ArtifactConfig;
|
|
164
|
+
artifactsDir: string;
|
|
165
|
+
backgroundRequestedWhileClarifying: boolean;
|
|
166
|
+
effectiveAsync: boolean;
|
|
167
|
+
controlConfig: ResolvedControlConfig;
|
|
168
|
+
intercomBridge: IntercomBridgeState;
|
|
169
|
+
nestedRoute?: NestedRouteInfo;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function resolveRequestedCwd(runtimeCwd: string, requestedCwd: string | undefined): string {
|
|
173
|
+
return requestedCwd ? path.resolve(runtimeCwd, requestedCwd) : runtimeCwd;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function getForegroundControl(state: SubagentState, runId: string | undefined) {
|
|
177
|
+
if (runId) return state.foregroundControls.get(runId);
|
|
178
|
+
if (state.lastForegroundControlId) {
|
|
179
|
+
const latest = state.foregroundControls.get(state.lastForegroundControlId);
|
|
180
|
+
if (latest) return latest;
|
|
181
|
+
}
|
|
182
|
+
let newest: (SubagentState["foregroundControls"] extends Map<string, infer T> ? T : never) | undefined;
|
|
183
|
+
for (const control of state.foregroundControls.values()) {
|
|
184
|
+
if (!newest || control.updatedAt > newest.updatedAt) newest = control;
|
|
185
|
+
}
|
|
186
|
+
return newest;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function formatForegroundActivity(control: SubagentState["foregroundControls"] extends Map<string, infer T> ? T : never): string | undefined {
|
|
190
|
+
const facts: string[] = [];
|
|
191
|
+
if (control.currentTool && control.currentToolStartedAt) facts.push(`tool ${control.currentTool} for ${Math.floor(Math.max(0, Date.now() - control.currentToolStartedAt) / 1000)}s`);
|
|
192
|
+
else if (control.currentTool) facts.push(`tool ${control.currentTool}`);
|
|
193
|
+
if (control.currentPath) facts.push(`path ${control.currentPath}`);
|
|
194
|
+
if (control.turnCount !== undefined) facts.push(`${control.turnCount} turns`);
|
|
195
|
+
if (control.tokens !== undefined) facts.push(`${control.tokens} tokens`);
|
|
196
|
+
if (control.toolCount !== undefined) facts.push(`${control.toolCount} tools`);
|
|
197
|
+
if (!control.lastActivityAt) {
|
|
198
|
+
if (control.currentActivityState === "needs_attention") return ["needs attention", ...facts].join(" | ");
|
|
199
|
+
if (control.currentActivityState === "active_long_running") return ["active but long-running", ...facts].join(" | ");
|
|
200
|
+
return facts.length ? facts.join(" | ") : undefined;
|
|
201
|
+
}
|
|
202
|
+
const seconds = Math.floor(Math.max(0, Date.now() - control.lastActivityAt) / 1000);
|
|
203
|
+
if (control.currentActivityState === "needs_attention") return [`no activity for ${seconds}s`, ...facts].join(" | ");
|
|
204
|
+
if (control.currentActivityState === "active_long_running") return [`active but long-running; last activity ${seconds}s ago`, ...facts].join(" | ");
|
|
205
|
+
return [`active ${seconds}s ago`, ...facts].join(" | ");
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function nestedResolutionScopeForExecutor(deps: ExecutorDeps): NestedRunResolutionScope | undefined {
|
|
209
|
+
if (deps.allowMutatingManagementActions !== false) return undefined;
|
|
210
|
+
const route = resolveInheritedNestedRouteFromEnv();
|
|
211
|
+
const address = route ? resolveNestedParentAddressFromEnv() : undefined;
|
|
212
|
+
return {
|
|
213
|
+
routes: route ? [route] : [],
|
|
214
|
+
...(address ? { descendantOf: { parentRunId: address.parentRunId, ...(address.parentStepIndex !== undefined ? { parentStepIndex: address.parentStepIndex } : {}) } } : {}),
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function foregroundStatusResult(control: SubagentState["foregroundControls"] extends Map<string, infer T> ? T : never): AgentToolResult<Details> {
|
|
219
|
+
let nestedWarning: string | undefined;
|
|
220
|
+
try {
|
|
221
|
+
updateForegroundNestedProjection(control);
|
|
222
|
+
} catch (error) {
|
|
223
|
+
nestedWarning = `Nested status unavailable: ${error instanceof Error ? error.message : String(error)}`;
|
|
224
|
+
}
|
|
225
|
+
const activity = formatForegroundActivity(control);
|
|
226
|
+
const lines = [
|
|
227
|
+
`Run: ${control.runId}`,
|
|
228
|
+
"State: running",
|
|
229
|
+
`Mode: ${control.mode}`,
|
|
230
|
+
control.currentAgent ? `Current: ${control.currentAgent}${control.currentIndex !== undefined ? ` step ${control.currentIndex + 1}` : ""}` : undefined,
|
|
231
|
+
activity ? `Activity: ${activity}` : undefined,
|
|
232
|
+
].filter((line): line is string => Boolean(line));
|
|
233
|
+
lines.push(...formatNestedRunStatusLines(control.nestedChildren, { indent: "", commandHints: true, maxLines: 20 }));
|
|
234
|
+
if (nestedWarning) lines.push(`Warning: ${nestedWarning}`);
|
|
235
|
+
return { content: [{ type: "text", text: lines.join("\n") }], details: { mode: "management", results: [] } };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function rememberForegroundRun(state: SubagentState, input: { runId: string; mode: "single" | "parallel" | "chain"; cwd: string; results: SingleResult[] }): void {
|
|
239
|
+
state.foregroundRuns ??= new Map();
|
|
240
|
+
state.foregroundRuns.set(input.runId, {
|
|
241
|
+
runId: input.runId,
|
|
242
|
+
mode: input.mode,
|
|
243
|
+
cwd: input.cwd,
|
|
244
|
+
updatedAt: Date.now(),
|
|
245
|
+
children: input.results.map((result, index) => ({
|
|
246
|
+
agent: result.agent,
|
|
247
|
+
index,
|
|
248
|
+
status: resolveSubagentResultStatus({ exitCode: result.exitCode, interrupted: result.interrupted, detached: result.detached }),
|
|
249
|
+
...(result.sessionFile ? { sessionFile: result.sessionFile } : {}),
|
|
250
|
+
})),
|
|
251
|
+
});
|
|
252
|
+
while (state.foregroundRuns.size > 50) {
|
|
253
|
+
const oldest = [...state.foregroundRuns.values()].sort((left, right) => left.updatedAt - right.updatedAt)[0];
|
|
254
|
+
if (!oldest) break;
|
|
255
|
+
state.foregroundRuns.delete(oldest.runId);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function resolveForegroundResumeTarget(params: SubagentParamsLike, state: SubagentState): { runId: string; mode: "single" | "parallel" | "chain"; state: "complete"; agent: string; index: number; intercomTarget: string; cwd: string; sessionFile: string } | undefined {
|
|
260
|
+
const requested = (params.id ?? params.runId)?.trim();
|
|
261
|
+
if (!requested || !state.foregroundRuns?.size) return undefined;
|
|
262
|
+
const direct = state.foregroundRuns.get(requested);
|
|
263
|
+
const matches = direct ? [direct] : [...state.foregroundRuns.values()].filter((run) => run.runId.startsWith(requested));
|
|
264
|
+
if (matches.length === 0) return undefined;
|
|
265
|
+
if (matches.length > 1) throw new Error(`Ambiguous foreground run id prefix '${requested}' matched: ${matches.map((run) => run.runId).join(", ")}. Provide a longer id.`);
|
|
266
|
+
const run = matches[0]!;
|
|
267
|
+
if (run.children.length > 1 && params.index === undefined) throw new Error(`Foreground run '${run.runId}' has ${run.children.length} children. Provide index to choose one.`);
|
|
268
|
+
const index = params.index ?? 0;
|
|
269
|
+
if (!Number.isInteger(index)) throw new Error(`Foreground run '${run.runId}' index must be an integer.`);
|
|
270
|
+
if (index < 0 || index >= run.children.length) throw new Error(`Foreground run '${run.runId}' has ${run.children.length} children. Index ${index} is out of range.`);
|
|
271
|
+
const child = run.children[index]!;
|
|
272
|
+
if (child.status === "detached") throw new Error(`Foreground run '${run.runId}' child ${index} is detached for intercom coordination and cannot be revived safely from the remembered foreground state. Reply to the supervisor request first; after the child exits, start a fresh follow-up if needed.`);
|
|
273
|
+
if (!child.sessionFile) throw new Error(`Foreground run '${run.runId}' child ${index} does not have a persisted session file to resume from.`);
|
|
274
|
+
if (path.extname(child.sessionFile) !== ".jsonl") throw new Error(`Foreground run '${run.runId}' child ${index} session file must be a .jsonl file: ${child.sessionFile}`);
|
|
275
|
+
const sessionFile = path.resolve(child.sessionFile);
|
|
276
|
+
if (!fs.existsSync(sessionFile)) throw new Error(`Foreground run '${run.runId}' child ${index} session file does not exist: ${child.sessionFile}`);
|
|
277
|
+
return { runId: run.runId, mode: run.mode, state: "complete", agent: child.agent, index, intercomTarget: resolveSubagentIntercomTarget(run.runId, child.agent, index), cwd: run.cwd, sessionFile };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
type AsyncResumeSourceTarget = ReturnType<typeof resolveAsyncResumeTarget> & { source: "async" };
|
|
281
|
+
type ForegroundResumeSourceTarget = NonNullable<ReturnType<typeof resolveForegroundResumeTarget>> & { kind: "revive"; source: "foreground" };
|
|
282
|
+
type NestedResumeSourceTarget = {
|
|
283
|
+
kind: "revive";
|
|
284
|
+
source: "nested";
|
|
285
|
+
runId: string;
|
|
286
|
+
state: "complete" | "failed" | "paused";
|
|
287
|
+
agent: string;
|
|
288
|
+
index: number;
|
|
289
|
+
intercomTarget: string;
|
|
290
|
+
cwd?: string;
|
|
291
|
+
sessionFile: string;
|
|
292
|
+
};
|
|
293
|
+
type ResumeSourceTarget = AsyncResumeSourceTarget | ForegroundResumeSourceTarget | NestedResumeSourceTarget;
|
|
294
|
+
|
|
295
|
+
function isAsyncRunNotFound(error: unknown): boolean {
|
|
296
|
+
return error instanceof Error && error.message.startsWith("Async run not found.");
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function isResumeAmbiguity(error: unknown): boolean {
|
|
300
|
+
return error instanceof Error && /Ambiguous .*run id prefix/.test(error.message);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function resumeTargetExact(target: { runId: string } | undefined, requested: string): boolean {
|
|
304
|
+
return target?.runId === requested;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function escapeRegExp(value: string): string {
|
|
308
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function isExactResumeError(error: unknown, source: "async" | "foreground", requested: string): boolean {
|
|
312
|
+
if (!(error instanceof Error) || !requested) return false;
|
|
313
|
+
return new RegExp(`\\b${source} run '${escapeRegExp(requested)}'`, "i").test(error.message);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function resolveResumeTarget(params: SubagentParamsLike, state: SubagentState): ResumeSourceTarget {
|
|
317
|
+
const requested = (params.id ?? params.runId)?.trim() ?? "";
|
|
318
|
+
let foregroundTarget: ForegroundResumeSourceTarget | undefined;
|
|
319
|
+
let foregroundError: unknown;
|
|
320
|
+
let asyncTarget: AsyncResumeSourceTarget | undefined;
|
|
321
|
+
let asyncError: unknown;
|
|
322
|
+
|
|
323
|
+
try {
|
|
324
|
+
const target = resolveForegroundResumeTarget(params, state);
|
|
325
|
+
if (target) foregroundTarget = { kind: "revive", source: "foreground", ...target };
|
|
326
|
+
} catch (error) {
|
|
327
|
+
foregroundError = error;
|
|
328
|
+
}
|
|
329
|
+
try {
|
|
330
|
+
asyncTarget = { source: "async", ...resolveAsyncResumeTarget(params) };
|
|
331
|
+
} catch (error) {
|
|
332
|
+
asyncError = error;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (foregroundTarget && asyncTarget) {
|
|
336
|
+
const foregroundExact = resumeTargetExact(foregroundTarget, requested);
|
|
337
|
+
const asyncExact = resumeTargetExact(asyncTarget, requested);
|
|
338
|
+
if (foregroundExact && !asyncExact) return foregroundTarget;
|
|
339
|
+
if (asyncExact && !foregroundExact) return asyncTarget;
|
|
340
|
+
throw new Error(`Resume id '${requested}' is ambiguous between foreground run '${foregroundTarget.runId}' and async run '${asyncTarget.runId}'. Provide a full run id.`);
|
|
341
|
+
}
|
|
342
|
+
if (foregroundTarget) {
|
|
343
|
+
if (isExactResumeError(asyncError, "async", requested)) throw asyncError;
|
|
344
|
+
if (isResumeAmbiguity(asyncError) && !resumeTargetExact(foregroundTarget, requested)) throw asyncError;
|
|
345
|
+
return foregroundTarget;
|
|
346
|
+
}
|
|
347
|
+
if (asyncTarget) {
|
|
348
|
+
if (isExactResumeError(foregroundError, "foreground", requested)) throw foregroundError;
|
|
349
|
+
if (isResumeAmbiguity(foregroundError) && !resumeTargetExact(asyncTarget, requested)) throw foregroundError;
|
|
350
|
+
return asyncTarget;
|
|
351
|
+
}
|
|
352
|
+
if (foregroundError && !isAsyncRunNotFound(asyncError)) throw foregroundError;
|
|
353
|
+
if (foregroundError) throw foregroundError;
|
|
354
|
+
if (asyncError) throw asyncError;
|
|
355
|
+
throw new Error("Run not found. Provide id or runId.");
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function getAsyncInterruptTarget(state: SubagentState, runId: string | undefined): { asyncId: string; asyncDir: string } | undefined {
|
|
359
|
+
if (runId) {
|
|
360
|
+
const direct = state.asyncJobs.get(runId);
|
|
361
|
+
if (direct) return { asyncId: direct.asyncId, asyncDir: direct.asyncDir };
|
|
362
|
+
}
|
|
363
|
+
let newest: { asyncId: string; asyncDir: string; updatedAt: number } | undefined;
|
|
364
|
+
for (const job of state.asyncJobs.values()) {
|
|
365
|
+
if (job.status !== "running") continue;
|
|
366
|
+
if (!newest || (job.updatedAt ?? 0) > newest.updatedAt) {
|
|
367
|
+
newest = { asyncId: job.asyncId, asyncDir: job.asyncDir, updatedAt: job.updatedAt ?? 0 };
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return newest ? { asyncId: newest.asyncId, asyncDir: newest.asyncDir } : undefined;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function emitControlNotification(input: {
|
|
374
|
+
pi: ExtensionAPI;
|
|
375
|
+
controlConfig: ResolvedControlConfig;
|
|
376
|
+
intercomBridge: IntercomBridgeState;
|
|
377
|
+
event: ControlEvent;
|
|
378
|
+
}): void {
|
|
379
|
+
if (!shouldNotifyControlEvent(input.controlConfig, input.event)) return;
|
|
380
|
+
const childIntercomTarget = input.intercomBridge.active
|
|
381
|
+
? resolveSubagentIntercomTarget(input.event.runId, input.event.agent, input.event.index)
|
|
382
|
+
: undefined;
|
|
383
|
+
const payload = {
|
|
384
|
+
event: input.event,
|
|
385
|
+
source: "foreground" as const,
|
|
386
|
+
childIntercomTarget,
|
|
387
|
+
noticeText: formatControlNoticeMessage(input.event, childIntercomTarget),
|
|
388
|
+
};
|
|
389
|
+
if (input.controlConfig.notifyChannels.includes("event")) {
|
|
390
|
+
input.pi.events.emit(SUBAGENT_CONTROL_EVENT, payload);
|
|
391
|
+
}
|
|
392
|
+
if (input.event.type !== "active_long_running" && input.controlConfig.notifyChannels.includes("intercom") && input.intercomBridge.active && input.intercomBridge.orchestratorTarget) {
|
|
393
|
+
input.pi.events.emit(SUBAGENT_CONTROL_INTERCOM_EVENT, {
|
|
394
|
+
...payload,
|
|
395
|
+
to: input.intercomBridge.orchestratorTarget,
|
|
396
|
+
message: formatControlIntercomMessage(input.event, childIntercomTarget),
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function interruptAsyncRun(state: SubagentState, runId: string | undefined): AgentToolResult<Details> | null {
|
|
402
|
+
const target = getAsyncInterruptTarget(state, runId);
|
|
403
|
+
if (!target) return null;
|
|
404
|
+
const status = readStatus(target.asyncDir);
|
|
405
|
+
if (!status || status.state !== "running" || typeof status.pid !== "number") {
|
|
406
|
+
return {
|
|
407
|
+
content: [{ type: "text", text: `No running async run with an interrupt-capable pid was found for '${runId ?? "current"}'.` }],
|
|
408
|
+
isError: true,
|
|
409
|
+
details: { mode: "management", results: [] },
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
try {
|
|
413
|
+
process.kill(status.pid, ASYNC_INTERRUPT_SIGNAL);
|
|
414
|
+
const tracked = state.asyncJobs.get(target.asyncId);
|
|
415
|
+
if (tracked) {
|
|
416
|
+
tracked.activityState = undefined;
|
|
417
|
+
tracked.updatedAt = Date.now();
|
|
418
|
+
}
|
|
419
|
+
return {
|
|
420
|
+
content: [{ type: "text", text: `Interrupt requested for async run ${target.asyncId}.` }],
|
|
421
|
+
details: { mode: "management", results: [] },
|
|
422
|
+
};
|
|
423
|
+
} catch (error) {
|
|
424
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
425
|
+
return {
|
|
426
|
+
content: [{ type: "text", text: `Failed to interrupt async run ${target.asyncId}: ${message}` }],
|
|
427
|
+
isError: true,
|
|
428
|
+
details: { mode: "management", results: [] },
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function nestedRunSessionFile(run: NestedRunSummary): string | undefined {
|
|
434
|
+
return run.sessionFile ?? (run.steps?.length === 1 ? run.steps[0]?.sessionFile : undefined);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function nestedRunAgent(run: NestedRunSummary): string | undefined {
|
|
438
|
+
return run.agent ?? run.agents?.[0] ?? (run.steps?.length === 1 ? run.steps[0]?.agent : undefined);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function pathWithin(base: string, candidate: string): boolean {
|
|
442
|
+
const resolvedBase = path.resolve(base);
|
|
443
|
+
const resolvedCandidate = path.resolve(candidate);
|
|
444
|
+
return resolvedCandidate === resolvedBase || resolvedCandidate.startsWith(`${resolvedBase}${path.sep}`);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function validateNestedSessionFile(run: NestedRunSummary, trustedSessionRoots: string[]): string {
|
|
448
|
+
const sessionFile = nestedRunSessionFile(run);
|
|
449
|
+
if (!sessionFile) throw new Error(`Nested run '${run.id}' does not have a persisted session file to resume from.`);
|
|
450
|
+
if (path.extname(sessionFile) !== ".jsonl") throw new Error(`Nested run '${run.id}' session file must be a .jsonl file: ${sessionFile}`);
|
|
451
|
+
const resolved = path.resolve(sessionFile);
|
|
452
|
+
if (!path.isAbsolute(sessionFile)) throw new Error(`Nested run '${run.id}' session file must be absolute: ${sessionFile}`);
|
|
453
|
+
if (!fs.existsSync(resolved)) throw new Error(`Nested run '${run.id}' session file does not exist: ${sessionFile}`);
|
|
454
|
+
const stat = fs.lstatSync(resolved);
|
|
455
|
+
if (!stat.isFile() || stat.isSymbolicLink()) throw new Error(`Nested run '${run.id}' session file is not a regular file: ${sessionFile}`);
|
|
456
|
+
const realSessionFile = fs.realpathSync(resolved);
|
|
457
|
+
const trustedRoots = trustedSessionRoots
|
|
458
|
+
.filter((root) => fs.existsSync(root))
|
|
459
|
+
.map((root) => fs.realpathSync(root));
|
|
460
|
+
if (!trustedRoots.some((root) => pathWithin(root, realSessionFile))) {
|
|
461
|
+
throw new Error(`Nested run '${run.id}' session file is outside trusted nested session roots: ${sessionFile}`);
|
|
462
|
+
}
|
|
463
|
+
if (!realSessionFile.split(path.sep).includes(run.id)) {
|
|
464
|
+
throw new Error(`Nested run '${run.id}' session file is not under that nested run's session directory: ${sessionFile}`);
|
|
465
|
+
}
|
|
466
|
+
return realSessionFile;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function resolveNestedResumeTarget(match: ResolvedSubagentRunId & { kind: "nested" }, trustedSessionRoots: string[]): NestedResumeSourceTarget {
|
|
470
|
+
const run = match.match.run;
|
|
471
|
+
if (run.state === "running" || run.state === "queued") throw new Error(`Nested run '${run.id}' is live; route the follow-up to the owner process instead.`);
|
|
472
|
+
const agent = nestedRunAgent(run);
|
|
473
|
+
if (!agent) throw new Error(`Could not determine child agent for nested run '${run.id}'.`);
|
|
474
|
+
const state = run.state === "complete" || run.state === "failed" || run.state === "paused" ? run.state : "failed";
|
|
475
|
+
const asyncDir = resolveNestedAsyncDir(match.match.rootRunId, run);
|
|
476
|
+
return {
|
|
477
|
+
kind: "revive",
|
|
478
|
+
source: "nested",
|
|
479
|
+
runId: run.id,
|
|
480
|
+
state,
|
|
481
|
+
agent,
|
|
482
|
+
index: 0,
|
|
483
|
+
intercomTarget: resolveSubagentIntercomTarget(run.id, agent, 0),
|
|
484
|
+
cwd: asyncDir ? path.dirname(asyncDir) : undefined,
|
|
485
|
+
sessionFile: validateNestedSessionFile(run, trustedSessionRoots),
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
async function waitForNestedControlResult(target: ResolvedSubagentRunId & { kind: "nested" }, requestId: string, timeoutMs = 1_000) {
|
|
490
|
+
const deadline = Date.now() + timeoutMs;
|
|
491
|
+
while (Date.now() < deadline) {
|
|
492
|
+
const result = readNestedControlResults(target.match.route).find((candidate) => candidate.requestId === requestId && candidate.targetRunId === target.match.run.id);
|
|
493
|
+
if (result) return result;
|
|
494
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
495
|
+
}
|
|
496
|
+
return undefined;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
async function sendNestedControlRequest(target: ResolvedSubagentRunId & { kind: "nested" }, action: "interrupt" | "resume", message?: string) {
|
|
500
|
+
const requestId = randomUUID();
|
|
501
|
+
writeNestedControlRequest(target.match.route, {
|
|
502
|
+
ts: Date.now(),
|
|
503
|
+
requestId,
|
|
504
|
+
targetRunId: target.match.run.id,
|
|
505
|
+
action,
|
|
506
|
+
...(message ? { message } : {}),
|
|
507
|
+
});
|
|
508
|
+
return waitForNestedControlResult(target, requestId);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function directNestedAsyncInterrupt(target: ResolvedSubagentRunId & { kind: "nested" }): AgentToolResult<Details> | undefined {
|
|
512
|
+
const run = target.match.run;
|
|
513
|
+
const asyncDir = resolveNestedAsyncDir(target.match.rootRunId, run);
|
|
514
|
+
if (!asyncDir) return undefined;
|
|
515
|
+
const status = readStatus(asyncDir);
|
|
516
|
+
const pid = typeof status?.pid === "number" && status.pid > 0 ? status.pid : run.pid;
|
|
517
|
+
if (!status || status.state !== "running" || typeof pid !== "number" || pid <= 0) return undefined;
|
|
518
|
+
try {
|
|
519
|
+
process.kill(pid, ASYNC_INTERRUPT_SIGNAL);
|
|
520
|
+
return { content: [{ type: "text", text: `Interrupt requested for nested async run ${run.id}.` }], details: { mode: "management", results: [] } };
|
|
521
|
+
} catch (error) {
|
|
522
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
523
|
+
return { content: [{ type: "text", text: `Failed to interrupt nested async run ${run.id}: ${message}` }], isError: true, details: { mode: "management", results: [] } };
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
async function interruptNestedRun(target: ResolvedSubagentRunId & { kind: "nested" }): Promise<AgentToolResult<Details>> {
|
|
528
|
+
const run = target.match.run;
|
|
529
|
+
if (run.state === "complete") return { content: [{ type: "text", text: `Nested run ${run.id} is already complete and cannot be interrupted.` }], isError: true, details: { mode: "management", results: [] } };
|
|
530
|
+
if (run.state === "failed") return { content: [{ type: "text", text: `Nested run ${run.id} has failed and cannot be interrupted.` }], isError: true, details: { mode: "management", results: [] } };
|
|
531
|
+
if (run.state === "paused") return { content: [{ type: "text", text: `Nested run ${run.id} is already paused.` }], isError: true, details: { mode: "management", results: [] } };
|
|
532
|
+
const result = await sendNestedControlRequest(target, "interrupt");
|
|
533
|
+
if (result) return { content: [{ type: "text", text: result.message }], isError: result.ok ? undefined : true, details: { mode: "management", results: [] } };
|
|
534
|
+
const direct = directNestedAsyncInterrupt(target);
|
|
535
|
+
if (direct) return direct;
|
|
536
|
+
return { content: [{ type: "text", text: `Nested run ${run.id} owner is not reachable and no safe direct async interrupt fallback is available.` }], isError: true, details: { mode: "management", results: [] } };
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
async function resumeLiveNestedRun(input: { target: ResolvedSubagentRunId & { kind: "nested" }; message: string }): Promise<AgentToolResult<Details>> {
|
|
540
|
+
const run = input.target.match.run;
|
|
541
|
+
const result = await sendNestedControlRequest(input.target, "resume", input.message);
|
|
542
|
+
if (result) return { content: [{ type: "text", text: result.message }], isError: result.ok ? undefined : true, details: { mode: "management", results: [] } };
|
|
543
|
+
return { content: [{ type: "text", text: `Nested run ${run.id} appears live but its owner route is not reachable. Wait for completion, then retry action='resume'.` }], isError: true, details: { mode: "management", results: [] } };
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
async function resumeAsyncRun(input: {
|
|
547
|
+
params: SubagentParamsLike;
|
|
548
|
+
requestCwd: string;
|
|
549
|
+
ctx: ExtensionContext;
|
|
550
|
+
deps: ExecutorDeps;
|
|
551
|
+
}): Promise<AgentToolResult<Details>> {
|
|
552
|
+
const followUp = (input.params.message ?? input.params.task ?? "").trim();
|
|
553
|
+
if (!followUp) {
|
|
554
|
+
return {
|
|
555
|
+
content: [{ type: "text", text: "action='resume' requires message." }],
|
|
556
|
+
isError: true,
|
|
557
|
+
details: { mode: "management", results: [] },
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
let target: ResumeSourceTarget;
|
|
562
|
+
const parentSessionFile = input.ctx.sessionManager.getSessionFile() ?? null;
|
|
563
|
+
try {
|
|
564
|
+
const requestedId = input.params.id ?? input.params.runId;
|
|
565
|
+
const resolved = requestedId ? resolveSubagentRunId(requestedId, { state: input.deps.state, nested: nestedResolutionScopeForExecutor(input.deps) }) : undefined;
|
|
566
|
+
if (resolved?.kind === "nested") {
|
|
567
|
+
if (resolved.match.run.state === "running" || resolved.match.run.state === "queued") {
|
|
568
|
+
return resumeLiveNestedRun({ target: resolved, message: followUp });
|
|
569
|
+
}
|
|
570
|
+
const trustedSessionRoots = [
|
|
571
|
+
...(input.deps.config.defaultSessionDir ? [path.resolve(input.deps.expandTilde(input.deps.config.defaultSessionDir))] : []),
|
|
572
|
+
...(parentSessionFile ? [input.deps.getSubagentSessionRoot(parentSessionFile)] : []),
|
|
573
|
+
];
|
|
574
|
+
target = resolveNestedResumeTarget(resolved, trustedSessionRoots);
|
|
575
|
+
} else {
|
|
576
|
+
target = resolveResumeTarget(input.params, input.deps.state);
|
|
577
|
+
}
|
|
578
|
+
} catch (error) {
|
|
579
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
580
|
+
return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
if (target.kind === "live") {
|
|
584
|
+
const delivered = await deliverSubagentIntercomMessageEvent(
|
|
585
|
+
input.deps.pi.events,
|
|
586
|
+
target.intercomTarget,
|
|
587
|
+
`Follow-up for async run ${target.runId} (${target.agent}):\n\n${followUp}`,
|
|
588
|
+
500,
|
|
589
|
+
{ source: "async-resume", runId: target.runId, agent: target.agent, index: target.index },
|
|
590
|
+
);
|
|
591
|
+
if (delivered) {
|
|
592
|
+
return {
|
|
593
|
+
content: [{ type: "text", text: [`Delivered follow-up to live async child.`, `Run: ${target.runId}`, `Intercom target: ${target.intercomTarget}`].join("\n") }],
|
|
594
|
+
details: { mode: "management", results: [] },
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
return {
|
|
598
|
+
content: [{ type: "text", text: [`Async child appears live but its intercom target is not registered.`, `Run: ${target.runId}`, `Intercom target: ${target.intercomTarget}`, `Wait for completion, then retry action='resume'.`].join("\n") }],
|
|
599
|
+
isError: true,
|
|
600
|
+
details: { mode: "management", results: [] },
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const { blocked, depth, maxDepth } = checkSubagentDepth(input.deps.config.maxSubagentDepth);
|
|
605
|
+
if (blocked) {
|
|
606
|
+
return {
|
|
607
|
+
content: [{ type: "text", text: `Nested subagent resume blocked (depth=${depth}, max=${maxDepth}). Complete the follow-up directly instead.` }],
|
|
608
|
+
isError: true,
|
|
609
|
+
details: { mode: "management", results: [] },
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
input.deps.state.currentSessionId = resolveCurrentSessionId(input.ctx.sessionManager);
|
|
614
|
+
const effectiveCwd = target.cwd ?? input.requestCwd;
|
|
615
|
+
const scope: AgentScope = resolveExecutionAgentScope(input.params.agentScope);
|
|
616
|
+
const discoveredAgents = input.deps.discoverAgents(effectiveCwd, scope).agents;
|
|
617
|
+
const sessionName = resolveIntercomSessionTarget(input.deps.pi.getSessionName(), input.ctx.sessionManager.getSessionId());
|
|
618
|
+
const intercomBridge = resolveIntercomBridge({
|
|
619
|
+
config: input.deps.config.intercomBridge,
|
|
620
|
+
context: input.params.context,
|
|
621
|
+
orchestratorTarget: sessionName,
|
|
622
|
+
cwd: effectiveCwd,
|
|
623
|
+
});
|
|
624
|
+
const agents = intercomBridge.active
|
|
625
|
+
? discoveredAgents.map((agent) => applyIntercomBridgeToAgent(agent, intercomBridge))
|
|
626
|
+
: discoveredAgents;
|
|
627
|
+
const agentConfig = agents.find((agent) => agent.name === target.agent);
|
|
628
|
+
if (!agentConfig) {
|
|
629
|
+
return {
|
|
630
|
+
content: [{ type: "text", text: `Unknown agent for resume: ${target.agent}` }],
|
|
631
|
+
isError: true,
|
|
632
|
+
details: { mode: "management", results: [] },
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const runId = randomUUID().slice(0, 8);
|
|
637
|
+
const artifactConfig: ArtifactConfig = { ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false };
|
|
638
|
+
const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
639
|
+
const result = executeAsyncSingle(runId, {
|
|
640
|
+
agent: target.agent,
|
|
641
|
+
task: buildRevivedAsyncTask(target, followUp),
|
|
642
|
+
agentConfig,
|
|
643
|
+
ctx: {
|
|
644
|
+
pi: input.deps.pi,
|
|
645
|
+
cwd: input.requestCwd,
|
|
646
|
+
currentSessionId: input.deps.state.currentSessionId,
|
|
647
|
+
currentModelProvider: input.ctx.model?.provider,
|
|
648
|
+
},
|
|
649
|
+
cwd: effectiveCwd,
|
|
650
|
+
maxOutput: input.params.maxOutput,
|
|
651
|
+
artifactsDir: input.deps.tempArtifactsDir,
|
|
652
|
+
artifactConfig,
|
|
653
|
+
shareEnabled: input.params.share === true,
|
|
654
|
+
sessionRoot: input.deps.getSubagentSessionRoot(parentSessionFile),
|
|
655
|
+
sessionFile: target.sessionFile,
|
|
656
|
+
maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
|
|
657
|
+
worktreeSetupHook: input.deps.config.worktreeSetupHook,
|
|
658
|
+
worktreeSetupHookTimeoutMs: input.deps.config.worktreeSetupHookTimeoutMs,
|
|
659
|
+
controlConfig: resolveControlConfig(input.deps.config.control, input.params.control),
|
|
660
|
+
controlIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
|
|
661
|
+
childIntercomTarget: intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
|
|
662
|
+
availableModels,
|
|
663
|
+
});
|
|
664
|
+
if (result.isError) return result;
|
|
665
|
+
|
|
666
|
+
const revivedId = result.details.asyncId ?? runId;
|
|
667
|
+
const revivedTarget = intercomBridge.active ? resolveSubagentIntercomTarget(revivedId, target.agent, 0) : undefined;
|
|
668
|
+
const sourceLabel = target.source;
|
|
669
|
+
const lines = [
|
|
670
|
+
`Revived ${sourceLabel} subagent from ${target.runId}.`,
|
|
671
|
+
`Revived run: ${revivedId}`,
|
|
672
|
+
`Agent: ${target.agent}`,
|
|
673
|
+
`Session: ${target.sessionFile}`,
|
|
674
|
+
result.details.asyncDir ? `Async dir: ${result.details.asyncDir}` : undefined,
|
|
675
|
+
revivedTarget ? `Intercom target: ${revivedTarget} (if registered)` : undefined,
|
|
676
|
+
`Status if needed: subagent({ action: "status", id: "${revivedId}" })`,
|
|
677
|
+
].filter((line): line is string => Boolean(line));
|
|
678
|
+
return { content: [{ type: "text", text: formatAsyncStartedMessage(lines.join("\n")) }], details: result.details };
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
function resultSummaryForIntercom(result: SingleResult): string {
|
|
682
|
+
const output = getSingleResultOutput(result);
|
|
683
|
+
if (result.exitCode !== 0 && result.error) {
|
|
684
|
+
return output ? `${result.error}\n\nOutput:\n${output}` : result.error;
|
|
685
|
+
}
|
|
686
|
+
return output || result.error || "(no output)";
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
function createForegroundControlNotifier(data: Pick<ExecutionContextData, "controlConfig" | "intercomBridge">, deps: Pick<ExecutorDeps, "pi">): (event: ControlEvent) => void {
|
|
690
|
+
return (event) => emitControlNotification({
|
|
691
|
+
pi: deps.pi,
|
|
692
|
+
controlConfig: data.controlConfig,
|
|
693
|
+
intercomBridge: data.intercomBridge,
|
|
694
|
+
event,
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
async function emitForegroundResultIntercom(input: {
|
|
699
|
+
pi: ExtensionAPI;
|
|
700
|
+
intercomBridge: IntercomBridgeState;
|
|
701
|
+
runId: string;
|
|
702
|
+
mode: SubagentRunMode;
|
|
703
|
+
results: SingleResult[];
|
|
704
|
+
chainSteps?: number;
|
|
705
|
+
nestedChildren?: NestedRunSummary[];
|
|
706
|
+
}): Promise<ReturnType<typeof buildSubagentResultIntercomPayload> | null> {
|
|
707
|
+
if (!input.intercomBridge.active || !input.intercomBridge.orchestratorTarget) return null;
|
|
708
|
+
const children = input.results.flatMap((result, index) => result.detached ? [] : [{
|
|
709
|
+
agent: result.agent,
|
|
710
|
+
status: resolveSubagentResultStatus({
|
|
711
|
+
exitCode: result.exitCode,
|
|
712
|
+
interrupted: result.interrupted,
|
|
713
|
+
detached: result.detached,
|
|
714
|
+
}),
|
|
715
|
+
summary: resultSummaryForIntercom(result),
|
|
716
|
+
index,
|
|
717
|
+
artifactPath: result.artifactPaths?.outputPath,
|
|
718
|
+
sessionPath: result.sessionFile,
|
|
719
|
+
intercomTarget: resolveSubagentIntercomTarget(input.runId, result.agent, index),
|
|
720
|
+
}]);
|
|
721
|
+
if (children.length === 0) return null;
|
|
722
|
+
const payload = buildSubagentResultIntercomPayload({
|
|
723
|
+
to: input.intercomBridge.orchestratorTarget,
|
|
724
|
+
runId: input.runId,
|
|
725
|
+
mode: input.mode,
|
|
726
|
+
source: "foreground",
|
|
727
|
+
children: attachNestedChildrenToResultChildren(input.runId, children, input.nestedChildren),
|
|
728
|
+
...(typeof input.chainSteps === "number" ? { chainSteps: input.chainSteps } : {}),
|
|
729
|
+
});
|
|
730
|
+
const delivered = await deliverSubagentResultIntercomEvent(input.pi.events, payload);
|
|
731
|
+
if (!delivered) return null;
|
|
732
|
+
return payload;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
async function maybeBuildForegroundIntercomReceipt(input: {
|
|
736
|
+
pi: ExtensionAPI;
|
|
737
|
+
intercomBridge: IntercomBridgeState;
|
|
738
|
+
runId: string;
|
|
739
|
+
mode: SubagentRunMode;
|
|
740
|
+
details: Details;
|
|
741
|
+
nestedChildren?: NestedRunSummary[];
|
|
742
|
+
}): Promise<{ text: string; details: Details } | null> {
|
|
743
|
+
const payload = await emitForegroundResultIntercom({
|
|
744
|
+
pi: input.pi,
|
|
745
|
+
intercomBridge: input.intercomBridge,
|
|
746
|
+
runId: input.runId,
|
|
747
|
+
mode: input.mode,
|
|
748
|
+
results: input.details.results,
|
|
749
|
+
...(typeof input.details.totalSteps === "number" ? { chainSteps: input.details.totalSteps } : {}),
|
|
750
|
+
...(input.nestedChildren?.length ? { nestedChildren: input.nestedChildren } : {}),
|
|
751
|
+
});
|
|
752
|
+
if (!payload) return null;
|
|
753
|
+
return {
|
|
754
|
+
text: formatSubagentResultReceipt({ mode: input.mode, runId: input.runId, payload }),
|
|
755
|
+
details: stripDetailsOutputsForIntercomReceipt(input.details),
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
function validateExecutionInput(
|
|
760
|
+
params: SubagentParamsLike,
|
|
761
|
+
agents: AgentConfig[],
|
|
762
|
+
hasChain: boolean,
|
|
763
|
+
hasTasks: boolean,
|
|
764
|
+
hasSingle: boolean,
|
|
765
|
+
allowClarifyTaskPrompt: boolean,
|
|
766
|
+
): AgentToolResult<Details> | null {
|
|
767
|
+
if (Number(hasChain) + Number(hasTasks) + Number(hasSingle) !== 1) {
|
|
768
|
+
return {
|
|
769
|
+
content: [
|
|
770
|
+
{
|
|
771
|
+
type: "text",
|
|
772
|
+
text: `Provide exactly one mode. Agents: ${agents.map((a) => a.name).join(", ") || "none"}`,
|
|
773
|
+
},
|
|
774
|
+
],
|
|
775
|
+
isError: true,
|
|
776
|
+
details: { mode: "single" as const, results: [] },
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
if (hasSingle && params.agent && !agents.find((agent) => agent.name === params.agent)) {
|
|
781
|
+
return {
|
|
782
|
+
content: [{ type: "text", text: `Unknown agent: ${params.agent}` }],
|
|
783
|
+
isError: true,
|
|
784
|
+
details: { mode: "single" as const, results: [] },
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
if (hasTasks && params.tasks) {
|
|
789
|
+
for (let i = 0; i < params.tasks.length; i++) {
|
|
790
|
+
const task = params.tasks[i]!;
|
|
791
|
+
if (!agents.find((agent) => agent.name === task.agent)) {
|
|
792
|
+
return {
|
|
793
|
+
content: [{ type: "text", text: `Unknown agent: ${task.agent} (task ${i + 1})` }],
|
|
794
|
+
isError: true,
|
|
795
|
+
details: { mode: "parallel" as const, results: [] },
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
if (hasChain && params.chain) {
|
|
802
|
+
if (params.chain.length === 0) {
|
|
803
|
+
return {
|
|
804
|
+
content: [{ type: "text", text: "Chain must have at least one step" }],
|
|
805
|
+
isError: true,
|
|
806
|
+
details: { mode: "chain" as const, results: [] },
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
const firstStep = params.chain[0] as ChainStep;
|
|
810
|
+
if (isParallelStep(firstStep)) {
|
|
811
|
+
const missingTaskIndex = firstStep.parallel.findIndex((t) => !t.task);
|
|
812
|
+
if (missingTaskIndex !== -1) {
|
|
813
|
+
return {
|
|
814
|
+
content: [{ type: "text", text: `First parallel step: task ${missingTaskIndex + 1} must have a task (no previous output to reference)` }],
|
|
815
|
+
isError: true,
|
|
816
|
+
details: { mode: "chain" as const, results: [] },
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
} else if (!(firstStep as SequentialStep).task && !params.task && !allowClarifyTaskPrompt) {
|
|
820
|
+
return {
|
|
821
|
+
content: [{ type: "text", text: "First step in chain must have a task" }],
|
|
822
|
+
isError: true,
|
|
823
|
+
details: { mode: "chain" as const, results: [] },
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
for (let i = 0; i < params.chain.length; i++) {
|
|
827
|
+
const step = params.chain[i] as ChainStep;
|
|
828
|
+
const stepAgents = getStepAgents(step);
|
|
829
|
+
for (const agentName of stepAgents) {
|
|
830
|
+
if (!agents.find((a) => a.name === agentName)) {
|
|
831
|
+
return {
|
|
832
|
+
content: [{ type: "text", text: `Unknown agent: ${agentName} (step ${i + 1})` }],
|
|
833
|
+
isError: true,
|
|
834
|
+
details: { mode: "chain" as const, results: [] },
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
if (isParallelStep(step) && step.parallel.length === 0) {
|
|
839
|
+
return {
|
|
840
|
+
content: [{ type: "text", text: `Parallel step ${i + 1} must have at least one task` }],
|
|
841
|
+
isError: true,
|
|
842
|
+
details: { mode: "chain" as const, results: [] },
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
return null;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
function getRequestedModeLabel(params: SubagentParamsLike): Details["mode"] {
|
|
852
|
+
if ((params.chain?.length ?? 0) > 0) return "chain";
|
|
853
|
+
if ((params.tasks?.length ?? 0) > 0) return "parallel";
|
|
854
|
+
if (params.agent) return "single";
|
|
855
|
+
return "single";
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
function applyAgentDefaultContext(params: SubagentParamsLike, agents: AgentConfig[]): SubagentParamsLike {
|
|
859
|
+
if (params.context !== undefined) return params;
|
|
860
|
+
const byName = new Map(agents.map((agent) => [agent.name, agent]));
|
|
861
|
+
const names: string[] = [];
|
|
862
|
+
if (params.agent) names.push(params.agent);
|
|
863
|
+
for (const task of params.tasks ?? []) names.push(task.agent);
|
|
864
|
+
for (const step of params.chain ?? []) names.push(...getStepAgents(step));
|
|
865
|
+
return names.some((name) => byName.get(name)?.defaultContext === "fork")
|
|
866
|
+
? { ...params, context: "fork" }
|
|
867
|
+
: params;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
function buildRequestedModeError(params: SubagentParamsLike, message: string): AgentToolResult<Details> {
|
|
871
|
+
return withForkContext(
|
|
872
|
+
{
|
|
873
|
+
content: [{ type: "text", text: message }],
|
|
874
|
+
isError: true,
|
|
875
|
+
details: { mode: getRequestedModeLabel(params), results: [] },
|
|
876
|
+
},
|
|
877
|
+
params.context,
|
|
878
|
+
);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
function expandTopLevelTaskCounts(tasks: TaskParam[]): { tasks?: TaskParam[]; error?: string } {
|
|
882
|
+
const expanded: TaskParam[] = [];
|
|
883
|
+
for (let taskIndex = 0; taskIndex < tasks.length; taskIndex++) {
|
|
884
|
+
const task = tasks[taskIndex]!;
|
|
885
|
+
const rawCount = (task as TaskParam & { count?: unknown }).count;
|
|
886
|
+
if (rawCount !== undefined && (typeof rawCount !== "number" || !Number.isInteger(rawCount) || rawCount < 1)) {
|
|
887
|
+
return { error: `tasks[${taskIndex}].count must be an integer >= 1` };
|
|
888
|
+
}
|
|
889
|
+
const { count, ...concreteTask } = task;
|
|
890
|
+
for (let repeat = 0; repeat < (rawCount ?? 1); repeat++) {
|
|
891
|
+
expanded.push({ ...concreteTask });
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
return { tasks: expanded };
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
function expandChainParallelCounts(chain: ChainStep[]): { chain?: ChainStep[]; error?: string } {
|
|
898
|
+
const expandedChain: ChainStep[] = [];
|
|
899
|
+
for (let stepIndex = 0; stepIndex < chain.length; stepIndex++) {
|
|
900
|
+
const step = chain[stepIndex]!;
|
|
901
|
+
if (!isParallelStep(step)) {
|
|
902
|
+
expandedChain.push(step);
|
|
903
|
+
continue;
|
|
904
|
+
}
|
|
905
|
+
const expandedParallel = [];
|
|
906
|
+
for (let taskIndex = 0; taskIndex < step.parallel.length; taskIndex++) {
|
|
907
|
+
const task = step.parallel[taskIndex]!;
|
|
908
|
+
const rawCount = (task as typeof task & { count?: unknown }).count;
|
|
909
|
+
if (rawCount !== undefined && (typeof rawCount !== "number" || !Number.isInteger(rawCount) || rawCount < 1)) {
|
|
910
|
+
return { error: `chain[${stepIndex}].parallel[${taskIndex}].count must be an integer >= 1` };
|
|
911
|
+
}
|
|
912
|
+
const { count, ...concreteTask } = task;
|
|
913
|
+
for (let repeat = 0; repeat < (rawCount ?? 1); repeat++) {
|
|
914
|
+
expandedParallel.push({ ...concreteTask });
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
expandedChain.push({ ...step, parallel: expandedParallel });
|
|
918
|
+
}
|
|
919
|
+
return { chain: expandedChain };
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
function normalizeRepeatedParallelCounts(params: SubagentParamsLike): { params?: SubagentParamsLike; error?: AgentToolResult<Details> } {
|
|
923
|
+
if (params.tasks) {
|
|
924
|
+
const expandedTasks = expandTopLevelTaskCounts(params.tasks);
|
|
925
|
+
if (expandedTasks.error) {
|
|
926
|
+
return { error: buildRequestedModeError(params, expandedTasks.error) };
|
|
927
|
+
}
|
|
928
|
+
return { params: { ...params, tasks: expandedTasks.tasks } };
|
|
929
|
+
}
|
|
930
|
+
if (params.chain) {
|
|
931
|
+
const expandedChain = expandChainParallelCounts(params.chain);
|
|
932
|
+
if (expandedChain.error) {
|
|
933
|
+
return { error: buildRequestedModeError(params, expandedChain.error) };
|
|
934
|
+
}
|
|
935
|
+
return { params: { ...params, chain: expandedChain.chain } };
|
|
936
|
+
}
|
|
937
|
+
return { params };
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
function withForkContext(
|
|
941
|
+
result: AgentToolResult<Details>,
|
|
942
|
+
context: SubagentParamsLike["context"],
|
|
943
|
+
): AgentToolResult<Details> {
|
|
944
|
+
if (context !== "fork" || !result.details) return result;
|
|
945
|
+
return {
|
|
946
|
+
...result,
|
|
947
|
+
details: {
|
|
948
|
+
...result.details,
|
|
949
|
+
context: "fork",
|
|
950
|
+
},
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
function toExecutionErrorResult(params: SubagentParamsLike, error: unknown): AgentToolResult<Details> {
|
|
955
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
956
|
+
return withForkContext(
|
|
957
|
+
{
|
|
958
|
+
content: [{ type: "text", text: message }],
|
|
959
|
+
isError: true,
|
|
960
|
+
details: { mode: getRequestedModeLabel(params), results: [] },
|
|
961
|
+
},
|
|
962
|
+
params.context,
|
|
963
|
+
);
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
function collectChainSessionFiles(
|
|
967
|
+
chain: ChainStep[],
|
|
968
|
+
sessionFileForIndex: (idx?: number) => string | undefined,
|
|
969
|
+
): (string | undefined)[] {
|
|
970
|
+
const sessionFiles: (string | undefined)[] = [];
|
|
971
|
+
let flatIndex = 0;
|
|
972
|
+
for (const step of chain) {
|
|
973
|
+
if (isParallelStep(step)) {
|
|
974
|
+
for (let i = 0; i < step.parallel.length; i++) {
|
|
975
|
+
sessionFiles.push(sessionFileForIndex(flatIndex));
|
|
976
|
+
flatIndex++;
|
|
977
|
+
}
|
|
978
|
+
continue;
|
|
979
|
+
}
|
|
980
|
+
sessionFiles.push(sessionFileForIndex(flatIndex));
|
|
981
|
+
flatIndex++;
|
|
982
|
+
}
|
|
983
|
+
return sessionFiles;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
function wrapChainTasksForFork(chain: ChainStep[], context: SubagentParamsLike["context"]): ChainStep[] {
|
|
987
|
+
if (context !== "fork") return chain;
|
|
988
|
+
return chain.map((step, stepIndex) => {
|
|
989
|
+
if (isParallelStep(step)) {
|
|
990
|
+
return {
|
|
991
|
+
...step,
|
|
992
|
+
parallel: step.parallel.map((task) => ({
|
|
993
|
+
...task,
|
|
994
|
+
task: wrapForkTask(task.task ?? "{previous}"),
|
|
995
|
+
})),
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
const sequential = step as SequentialStep;
|
|
999
|
+
return {
|
|
1000
|
+
...sequential,
|
|
1001
|
+
task: wrapForkTask(sequential.task ?? (stepIndex === 0 ? "{task}" : "{previous}")),
|
|
1002
|
+
};
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentToolResult<Details> | null {
|
|
1007
|
+
const {
|
|
1008
|
+
params,
|
|
1009
|
+
effectiveCwd,
|
|
1010
|
+
agents,
|
|
1011
|
+
ctx,
|
|
1012
|
+
shareEnabled,
|
|
1013
|
+
sessionRoot,
|
|
1014
|
+
sessionFileForIndex,
|
|
1015
|
+
artifactConfig,
|
|
1016
|
+
artifactsDir,
|
|
1017
|
+
effectiveAsync,
|
|
1018
|
+
controlConfig,
|
|
1019
|
+
intercomBridge,
|
|
1020
|
+
nestedRoute,
|
|
1021
|
+
} = data;
|
|
1022
|
+
const hasChain = (params.chain?.length ?? 0) > 0;
|
|
1023
|
+
const hasTasks = (params.tasks?.length ?? 0) > 0;
|
|
1024
|
+
const hasSingle = !hasChain && !hasTasks && Boolean(params.agent);
|
|
1025
|
+
if (!effectiveAsync) return null;
|
|
1026
|
+
|
|
1027
|
+
if (hasChain && params.chain) {
|
|
1028
|
+
const chainWorktreeTaskCwdError = buildChainWorktreeTaskCwdError(params.chain as ChainStep[], effectiveCwd);
|
|
1029
|
+
if (chainWorktreeTaskCwdError) {
|
|
1030
|
+
return {
|
|
1031
|
+
content: [{ type: "text", text: chainWorktreeTaskCwdError }],
|
|
1032
|
+
isError: true,
|
|
1033
|
+
details: { mode: "chain" as const, results: [] },
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
if (hasTasks && params.tasks) {
|
|
1039
|
+
const maxParallelTasks = resolveTopLevelParallelMaxTasks(deps.config.parallel?.maxTasks);
|
|
1040
|
+
if (params.tasks.length > maxParallelTasks) {
|
|
1041
|
+
return buildParallelModeError(`Max ${maxParallelTasks} tasks`);
|
|
1042
|
+
}
|
|
1043
|
+
if (params.worktree) {
|
|
1044
|
+
const worktreeTaskCwdError = buildParallelWorktreeTaskCwdError(params.tasks, effectiveCwd);
|
|
1045
|
+
if (worktreeTaskCwdError) return buildParallelModeError(worktreeTaskCwdError);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
if (!isAsyncAvailable()) {
|
|
1050
|
+
return {
|
|
1051
|
+
content: [{ type: "text", text: "Async mode requires upstream jiti for TypeScript execution but it could not be found. Ensure the pi-subagents package dependencies are installed." }],
|
|
1052
|
+
isError: true,
|
|
1053
|
+
details: { mode: "single" as const, results: [] },
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
const id = randomUUID();
|
|
1057
|
+
const asyncCtx = {
|
|
1058
|
+
pi: deps.pi,
|
|
1059
|
+
cwd: ctx.cwd,
|
|
1060
|
+
currentSessionId: deps.state.currentSessionId!,
|
|
1061
|
+
currentModelProvider: ctx.model?.provider,
|
|
1062
|
+
};
|
|
1063
|
+
const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
1064
|
+
const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
|
|
1065
|
+
const currentProvider = ctx.model?.provider;
|
|
1066
|
+
const controlIntercomTarget = intercomBridge.active ? intercomBridge.orchestratorTarget : undefined;
|
|
1067
|
+
const childIntercomTarget = intercomBridge.active ? (agent: string, index: number) => resolveSubagentIntercomTarget(id, agent, index) : undefined;
|
|
1068
|
+
|
|
1069
|
+
if (hasTasks && params.tasks) {
|
|
1070
|
+
const agentConfigs = params.tasks.map((task) => agents.find((agent) => agent.name === task.agent));
|
|
1071
|
+
const modelOverrides = params.tasks.map((task, index) =>
|
|
1072
|
+
resolveModelCandidate(task.model ?? agentConfigs[index]?.model, availableModels, currentProvider),
|
|
1073
|
+
);
|
|
1074
|
+
const skillOverrides = params.tasks.map((task) => normalizeSkillInput(task.skill));
|
|
1075
|
+
const parallelTasks = params.tasks.map((task, index) => ({
|
|
1076
|
+
agent: task.agent,
|
|
1077
|
+
task: params.context === "fork" ? wrapForkTask(task.task) : task.task,
|
|
1078
|
+
cwd: task.cwd,
|
|
1079
|
+
...(modelOverrides[index] ? { model: modelOverrides[index] } : {}),
|
|
1080
|
+
...(skillOverrides[index] !== undefined ? { skill: skillOverrides[index] } : {}),
|
|
1081
|
+
...(task.output === true ? (agentConfigs[index]?.output ? { output: agentConfigs[index]!.output } : {}) : task.output !== undefined ? { output: task.output } : {}),
|
|
1082
|
+
...(task.outputMode !== undefined ? { outputMode: task.outputMode } : {}),
|
|
1083
|
+
...(task.reads !== undefined && task.reads !== true ? { reads: task.reads } : {}),
|
|
1084
|
+
...(task.progress !== undefined ? { progress: task.progress } : {}),
|
|
1085
|
+
}));
|
|
1086
|
+
return executeAsyncChain(id, {
|
|
1087
|
+
chain: [{
|
|
1088
|
+
parallel: parallelTasks,
|
|
1089
|
+
concurrency: resolveTopLevelParallelConcurrency(params.concurrency, deps.config.parallel?.concurrency),
|
|
1090
|
+
worktree: params.worktree,
|
|
1091
|
+
}],
|
|
1092
|
+
resultMode: "parallel",
|
|
1093
|
+
agents,
|
|
1094
|
+
ctx: asyncCtx,
|
|
1095
|
+
availableModels,
|
|
1096
|
+
cwd: effectiveCwd,
|
|
1097
|
+
maxOutput: params.maxOutput,
|
|
1098
|
+
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
1099
|
+
artifactConfig,
|
|
1100
|
+
shareEnabled,
|
|
1101
|
+
sessionRoot,
|
|
1102
|
+
chainSkills: [],
|
|
1103
|
+
sessionFilesByFlatIndex: params.tasks.map((_, index) => sessionFileForIndex(index)),
|
|
1104
|
+
maxSubagentDepth: currentMaxSubagentDepth,
|
|
1105
|
+
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1106
|
+
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
1107
|
+
controlConfig,
|
|
1108
|
+
controlIntercomTarget,
|
|
1109
|
+
childIntercomTarget,
|
|
1110
|
+
nestedRoute,
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
if (hasChain && params.chain) {
|
|
1115
|
+
const normalized = normalizeSkillInput(params.skill);
|
|
1116
|
+
const chainSkills = normalized === false ? [] : (normalized ?? []);
|
|
1117
|
+
const chain = wrapChainTasksForFork(params.chain as ChainStep[], params.context);
|
|
1118
|
+
return executeAsyncChain(id, {
|
|
1119
|
+
chain,
|
|
1120
|
+
task: params.task,
|
|
1121
|
+
agents,
|
|
1122
|
+
ctx: asyncCtx,
|
|
1123
|
+
availableModels,
|
|
1124
|
+
cwd: effectiveCwd,
|
|
1125
|
+
maxOutput: params.maxOutput,
|
|
1126
|
+
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
1127
|
+
artifactConfig,
|
|
1128
|
+
shareEnabled,
|
|
1129
|
+
sessionRoot,
|
|
1130
|
+
chainSkills,
|
|
1131
|
+
sessionFilesByFlatIndex: collectChainSessionFiles(chain, sessionFileForIndex),
|
|
1132
|
+
maxSubagentDepth: currentMaxSubagentDepth,
|
|
1133
|
+
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1134
|
+
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
1135
|
+
controlConfig,
|
|
1136
|
+
controlIntercomTarget,
|
|
1137
|
+
childIntercomTarget,
|
|
1138
|
+
nestedRoute,
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
if (hasSingle) {
|
|
1143
|
+
const a = agents.find((x) => x.name === params.agent);
|
|
1144
|
+
if (!a) {
|
|
1145
|
+
return {
|
|
1146
|
+
content: [{ type: "text", text: `Unknown agent: ${params.agent}` }],
|
|
1147
|
+
isError: true,
|
|
1148
|
+
details: { mode: "single" as const, results: [] },
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
const rawOutput = params.output !== undefined ? params.output : a.output;
|
|
1152
|
+
const effectiveOutput = normalizeSingleOutputOverride(rawOutput, a.output);
|
|
1153
|
+
const effectiveOutputMode = params.outputMode ?? "inline";
|
|
1154
|
+
const normalizedSkills = normalizeSkillInput(params.skill);
|
|
1155
|
+
const skills = normalizedSkills === false ? [] : normalizedSkills;
|
|
1156
|
+
const maxSubagentDepth = resolveChildMaxSubagentDepth(currentMaxSubagentDepth, a.maxSubagentDepth);
|
|
1157
|
+
const modelOverride = resolveModelCandidate((params.model as string | undefined) ?? a.model, availableModels, currentProvider);
|
|
1158
|
+
return executeAsyncSingle(id, {
|
|
1159
|
+
agent: params.agent!,
|
|
1160
|
+
task: params.context === "fork" ? wrapForkTask(params.task ?? "") : (params.task ?? ""),
|
|
1161
|
+
agentConfig: a,
|
|
1162
|
+
ctx: asyncCtx,
|
|
1163
|
+
availableModels,
|
|
1164
|
+
cwd: effectiveCwd,
|
|
1165
|
+
maxOutput: params.maxOutput,
|
|
1166
|
+
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
1167
|
+
artifactConfig,
|
|
1168
|
+
shareEnabled,
|
|
1169
|
+
sessionRoot,
|
|
1170
|
+
sessionFile: sessionFileForIndex(0),
|
|
1171
|
+
skills,
|
|
1172
|
+
output: effectiveOutput,
|
|
1173
|
+
outputMode: effectiveOutputMode,
|
|
1174
|
+
modelOverride,
|
|
1175
|
+
maxSubagentDepth,
|
|
1176
|
+
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1177
|
+
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
1178
|
+
controlConfig,
|
|
1179
|
+
controlIntercomTarget,
|
|
1180
|
+
childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(agent, index) : undefined,
|
|
1181
|
+
nestedRoute,
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
return null;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Promise<AgentToolResult<Details>> {
|
|
1189
|
+
const {
|
|
1190
|
+
params,
|
|
1191
|
+
effectiveCwd,
|
|
1192
|
+
agents,
|
|
1193
|
+
ctx,
|
|
1194
|
+
signal,
|
|
1195
|
+
runId,
|
|
1196
|
+
shareEnabled,
|
|
1197
|
+
sessionDirForIndex,
|
|
1198
|
+
sessionFileForIndex,
|
|
1199
|
+
artifactsDir,
|
|
1200
|
+
artifactConfig,
|
|
1201
|
+
onUpdate,
|
|
1202
|
+
sessionRoot,
|
|
1203
|
+
controlConfig,
|
|
1204
|
+
} = data;
|
|
1205
|
+
const onControlEvent = createForegroundControlNotifier(data, deps);
|
|
1206
|
+
const childIntercomTarget = data.intercomBridge.active ? resolveSubagentIntercomTarget : undefined;
|
|
1207
|
+
const foregroundControl = deps.state.foregroundControls.get(runId);
|
|
1208
|
+
const normalized = normalizeSkillInput(params.skill);
|
|
1209
|
+
const chainSkills = normalized === false ? [] : (normalized ?? []);
|
|
1210
|
+
const chain = wrapChainTasksForFork(params.chain as ChainStep[], params.context);
|
|
1211
|
+
const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
|
|
1212
|
+
const chainResult = await executeChain({
|
|
1213
|
+
chain,
|
|
1214
|
+
task: params.task,
|
|
1215
|
+
agents,
|
|
1216
|
+
ctx,
|
|
1217
|
+
intercomEvents: deps.pi.events,
|
|
1218
|
+
signal,
|
|
1219
|
+
runId,
|
|
1220
|
+
cwd: effectiveCwd,
|
|
1221
|
+
shareEnabled,
|
|
1222
|
+
sessionDirForIndex,
|
|
1223
|
+
sessionFileForIndex,
|
|
1224
|
+
artifactsDir,
|
|
1225
|
+
artifactConfig,
|
|
1226
|
+
includeProgress: params.includeProgress,
|
|
1227
|
+
clarify: params.clarify,
|
|
1228
|
+
onUpdate,
|
|
1229
|
+
onControlEvent,
|
|
1230
|
+
controlConfig,
|
|
1231
|
+
childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(runId, agent, index) : undefined,
|
|
1232
|
+
orchestratorIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
1233
|
+
foregroundControl,
|
|
1234
|
+
nestedRoute: foregroundControl?.nestedRoute,
|
|
1235
|
+
chainSkills,
|
|
1236
|
+
chainDir: params.chainDir,
|
|
1237
|
+
maxSubagentDepth: currentMaxSubagentDepth,
|
|
1238
|
+
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1239
|
+
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
1240
|
+
});
|
|
1241
|
+
|
|
1242
|
+
if (chainResult.requestedAsync) {
|
|
1243
|
+
if (!isAsyncAvailable()) {
|
|
1244
|
+
return {
|
|
1245
|
+
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." }],
|
|
1246
|
+
isError: true,
|
|
1247
|
+
details: { mode: "chain" as const, results: [] },
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1250
|
+
const id = randomUUID();
|
|
1251
|
+
const asyncCtx = {
|
|
1252
|
+
pi: deps.pi,
|
|
1253
|
+
cwd: ctx.cwd,
|
|
1254
|
+
currentSessionId: deps.state.currentSessionId!,
|
|
1255
|
+
currentModelProvider: ctx.model?.provider,
|
|
1256
|
+
};
|
|
1257
|
+
const asyncChain = wrapChainTasksForFork(chainResult.requestedAsync.chain, params.context);
|
|
1258
|
+
return executeAsyncChain(id, {
|
|
1259
|
+
chain: asyncChain,
|
|
1260
|
+
task: params.task,
|
|
1261
|
+
agents,
|
|
1262
|
+
ctx: asyncCtx,
|
|
1263
|
+
availableModels: ctx.modelRegistry.getAvailable().map(toModelInfo),
|
|
1264
|
+
cwd: effectiveCwd,
|
|
1265
|
+
maxOutput: params.maxOutput,
|
|
1266
|
+
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
1267
|
+
artifactConfig,
|
|
1268
|
+
shareEnabled,
|
|
1269
|
+
sessionRoot,
|
|
1270
|
+
chainSkills: chainResult.requestedAsync.chainSkills,
|
|
1271
|
+
sessionFilesByFlatIndex: collectChainSessionFiles(asyncChain, sessionFileForIndex),
|
|
1272
|
+
maxSubagentDepth: currentMaxSubagentDepth,
|
|
1273
|
+
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1274
|
+
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
1275
|
+
controlConfig,
|
|
1276
|
+
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
1277
|
+
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
|
|
1278
|
+
nestedRoute: data.nestedRoute,
|
|
1279
|
+
});
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
const chainDetails = chainResult.details ? compactForegroundDetails({ ...chainResult.details, runId }) : undefined;
|
|
1283
|
+
if (foregroundControl) updateForegroundNestedProjection(foregroundControl);
|
|
1284
|
+
if (chainDetails) rememberForegroundRun(deps.state, { runId, mode: "chain", cwd: effectiveCwd, results: chainDetails.results });
|
|
1285
|
+
const intercomReceipt = chainDetails && !chainDetails.results.some((result) => result.interrupted || result.detached)
|
|
1286
|
+
? await maybeBuildForegroundIntercomReceipt({
|
|
1287
|
+
pi: deps.pi,
|
|
1288
|
+
intercomBridge: data.intercomBridge,
|
|
1289
|
+
runId,
|
|
1290
|
+
mode: "chain",
|
|
1291
|
+
details: chainDetails,
|
|
1292
|
+
...(foregroundControl?.nestedChildren?.length ? { nestedChildren: foregroundControl.nestedChildren } : {}),
|
|
1293
|
+
})
|
|
1294
|
+
: null;
|
|
1295
|
+
if (intercomReceipt) {
|
|
1296
|
+
return {
|
|
1297
|
+
...chainResult,
|
|
1298
|
+
content: [{ type: "text", text: intercomReceipt.text }],
|
|
1299
|
+
details: intercomReceipt.details,
|
|
1300
|
+
};
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
return chainDetails ? { ...chainResult, details: chainDetails } : chainResult;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
interface ForegroundParallelRunInput {
|
|
1307
|
+
tasks: TaskParam[];
|
|
1308
|
+
taskTexts: string[];
|
|
1309
|
+
agents: AgentConfig[];
|
|
1310
|
+
ctx: ExtensionContext;
|
|
1311
|
+
intercomEvents: IntercomEventBus;
|
|
1312
|
+
signal: AbortSignal;
|
|
1313
|
+
runId: string;
|
|
1314
|
+
sessionDirForIndex: (idx?: number) => string | undefined;
|
|
1315
|
+
sessionFileForIndex: (idx?: number) => string | undefined;
|
|
1316
|
+
shareEnabled: boolean;
|
|
1317
|
+
artifactConfig: ArtifactConfig;
|
|
1318
|
+
artifactsDir: string;
|
|
1319
|
+
maxOutput?: MaxOutputConfig;
|
|
1320
|
+
paramsCwd: string;
|
|
1321
|
+
maxSubagentDepths: number[];
|
|
1322
|
+
availableModels: ModelInfo[];
|
|
1323
|
+
modelOverrides: (string | undefined)[];
|
|
1324
|
+
behaviors: Array<ReturnType<typeof resolveStepBehavior>>;
|
|
1325
|
+
firstProgressIndex: number;
|
|
1326
|
+
controlConfig: ResolvedControlConfig;
|
|
1327
|
+
onControlEvent?: (event: ControlEvent) => void;
|
|
1328
|
+
childIntercomTarget?: (agent: string, index: number) => string | undefined;
|
|
1329
|
+
orchestratorIntercomTarget?: string;
|
|
1330
|
+
foregroundControl?: SubagentState["foregroundControls"] extends Map<string, infer T> ? T : never;
|
|
1331
|
+
concurrencyLimit: number;
|
|
1332
|
+
liveResults: (SingleResult | undefined)[];
|
|
1333
|
+
liveProgress: (AgentProgress | undefined)[];
|
|
1334
|
+
onUpdate?: (r: AgentToolResult<Details>) => void;
|
|
1335
|
+
worktreeSetup?: WorktreeSetup;
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
function buildParallelModeError(message: string): AgentToolResult<Details> {
|
|
1339
|
+
return {
|
|
1340
|
+
content: [{ type: "text", text: message }],
|
|
1341
|
+
isError: true,
|
|
1342
|
+
details: { mode: "parallel" as const, results: [] },
|
|
1343
|
+
};
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
function createParallelWorktreeSetup(
|
|
1347
|
+
enabled: boolean | undefined,
|
|
1348
|
+
cwd: string,
|
|
1349
|
+
runId: string,
|
|
1350
|
+
tasks: TaskParam[],
|
|
1351
|
+
setupHook: ExtensionConfig["worktreeSetupHook"],
|
|
1352
|
+
setupHookTimeoutMs: ExtensionConfig["worktreeSetupHookTimeoutMs"],
|
|
1353
|
+
): { setup?: WorktreeSetup; errorResult?: AgentToolResult<Details> } {
|
|
1354
|
+
if (!enabled) return {};
|
|
1355
|
+
try {
|
|
1356
|
+
return {
|
|
1357
|
+
setup: createWorktrees(cwd, runId, tasks.length, {
|
|
1358
|
+
agents: tasks.map((task) => task.agent),
|
|
1359
|
+
setupHook: setupHook
|
|
1360
|
+
? { hookPath: setupHook, timeoutMs: setupHookTimeoutMs }
|
|
1361
|
+
: undefined,
|
|
1362
|
+
}),
|
|
1363
|
+
};
|
|
1364
|
+
} catch (error) {
|
|
1365
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1366
|
+
return { errorResult: buildParallelModeError(message) };
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
function buildParallelWorktreeTaskCwdError(
|
|
1371
|
+
tasks: ReadonlyArray<{ agent: string; cwd?: string }>,
|
|
1372
|
+
sharedCwd: string,
|
|
1373
|
+
): string | undefined {
|
|
1374
|
+
const conflict = findWorktreeTaskCwdConflict(tasks, sharedCwd);
|
|
1375
|
+
if (!conflict) return undefined;
|
|
1376
|
+
return formatWorktreeTaskCwdConflict(conflict, sharedCwd);
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
function buildChainWorktreeTaskCwdError(chain: ChainStep[], sharedCwd: string): string | undefined {
|
|
1380
|
+
for (let stepIndex = 0; stepIndex < chain.length; stepIndex++) {
|
|
1381
|
+
const step = chain[stepIndex]!;
|
|
1382
|
+
if (!isParallelStep(step) || !step.worktree) continue;
|
|
1383
|
+
const stepCwd = resolveChildCwd(sharedCwd, step.cwd);
|
|
1384
|
+
const conflict = findWorktreeTaskCwdConflict(step.parallel, stepCwd);
|
|
1385
|
+
if (!conflict) continue;
|
|
1386
|
+
const detail = formatWorktreeTaskCwdConflict(conflict, stepCwd);
|
|
1387
|
+
return `parallel chain step ${stepIndex + 1}: ${detail}`;
|
|
1388
|
+
}
|
|
1389
|
+
return undefined;
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
function resolveParallelTaskCwd(
|
|
1393
|
+
task: TaskParam,
|
|
1394
|
+
paramsCwd: string,
|
|
1395
|
+
worktreeSetup: WorktreeSetup | undefined,
|
|
1396
|
+
index: number,
|
|
1397
|
+
): string {
|
|
1398
|
+
if (worktreeSetup) return worktreeSetup.worktrees[index]!.agentCwd;
|
|
1399
|
+
return resolveChildCwd(paramsCwd, task.cwd);
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
function buildParallelWorktreeSuffix(
|
|
1403
|
+
worktreeSetup: WorktreeSetup | undefined,
|
|
1404
|
+
artifactsDir: string,
|
|
1405
|
+
tasks: TaskParam[],
|
|
1406
|
+
): string {
|
|
1407
|
+
if (!worktreeSetup) return "";
|
|
1408
|
+
const diffsDir = path.join(artifactsDir, "worktree-diffs");
|
|
1409
|
+
const diffs = diffWorktrees(worktreeSetup, tasks.map((task) => task.agent), diffsDir);
|
|
1410
|
+
return formatWorktreeDiffSummary(diffs);
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
function findDuplicateParallelOutputPath(input: {
|
|
1414
|
+
tasks: TaskParam[];
|
|
1415
|
+
behaviors: ResolvedStepBehavior[];
|
|
1416
|
+
paramsCwd: string;
|
|
1417
|
+
ctxCwd: string;
|
|
1418
|
+
worktreeSetup?: WorktreeSetup;
|
|
1419
|
+
}): string | undefined {
|
|
1420
|
+
const seen = new Map<string, { index: number; agent: string }>();
|
|
1421
|
+
for (let index = 0; index < input.tasks.length; index++) {
|
|
1422
|
+
const behavior = input.behaviors[index];
|
|
1423
|
+
if (!behavior?.output) continue;
|
|
1424
|
+
const task = input.tasks[index]!;
|
|
1425
|
+
const taskCwd = resolveParallelTaskCwd(task, input.paramsCwd, input.worktreeSetup, index);
|
|
1426
|
+
const outputPath = resolveSingleOutputPath(behavior.output, input.ctxCwd, taskCwd);
|
|
1427
|
+
if (!outputPath) continue;
|
|
1428
|
+
const previous = seen.get(outputPath);
|
|
1429
|
+
if (previous) {
|
|
1430
|
+
return `Parallel tasks ${previous.index + 1} (${previous.agent}) and ${index + 1} (${task.agent}) resolve output to the same path: ${outputPath}. Use distinct output paths.`;
|
|
1431
|
+
}
|
|
1432
|
+
seen.set(outputPath, { index, agent: task.agent });
|
|
1433
|
+
}
|
|
1434
|
+
return undefined;
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Promise<SingleResult[]> {
|
|
1438
|
+
return mapConcurrent(input.tasks, input.concurrencyLimit, async (task, index) => {
|
|
1439
|
+
const behavior = input.behaviors[index];
|
|
1440
|
+
const effectiveSkills = behavior?.skills;
|
|
1441
|
+
const taskCwd = resolveParallelTaskCwd(task, input.paramsCwd, input.worktreeSetup, index);
|
|
1442
|
+
const readInstructions = behavior
|
|
1443
|
+
? buildChainInstructions({ ...behavior, output: false, progress: false }, taskCwd, false)
|
|
1444
|
+
: { prefix: "", suffix: "" };
|
|
1445
|
+
const progressInstructions = behavior
|
|
1446
|
+
? buildChainInstructions({ ...behavior, output: false, reads: false }, input.paramsCwd, index === input.firstProgressIndex)
|
|
1447
|
+
: { prefix: "", suffix: "" };
|
|
1448
|
+
const outputPath = resolveSingleOutputPath(behavior?.output, input.ctx.cwd, taskCwd);
|
|
1449
|
+
const taskText = injectSingleOutputInstruction(
|
|
1450
|
+
`${readInstructions.prefix}${input.taskTexts[index]!}${progressInstructions.suffix}`,
|
|
1451
|
+
outputPath,
|
|
1452
|
+
);
|
|
1453
|
+
const interruptController = new AbortController();
|
|
1454
|
+
if (input.foregroundControl) {
|
|
1455
|
+
input.foregroundControl.currentAgent = task.agent;
|
|
1456
|
+
input.foregroundControl.currentIndex = index;
|
|
1457
|
+
input.foregroundControl.currentActivityState = undefined;
|
|
1458
|
+
input.foregroundControl.updatedAt = Date.now();
|
|
1459
|
+
input.foregroundControl.interrupt = () => {
|
|
1460
|
+
if (interruptController.signal.aborted) return false;
|
|
1461
|
+
interruptController.abort();
|
|
1462
|
+
input.foregroundControl!.currentActivityState = undefined;
|
|
1463
|
+
input.foregroundControl!.updatedAt = Date.now();
|
|
1464
|
+
return true;
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1467
|
+
const agentConfig = input.agents.find((agent) => agent.name === task.agent);
|
|
1468
|
+
return runSync(input.ctx.cwd, input.agents, task.agent, taskText, {
|
|
1469
|
+
cwd: taskCwd,
|
|
1470
|
+
signal: input.signal,
|
|
1471
|
+
interruptSignal: interruptController.signal,
|
|
1472
|
+
allowIntercomDetach: agentConfig?.systemPrompt?.includes(INTERCOM_BRIDGE_MARKER) === true,
|
|
1473
|
+
intercomEvents: input.intercomEvents,
|
|
1474
|
+
runId: input.runId,
|
|
1475
|
+
index,
|
|
1476
|
+
sessionDir: input.sessionDirForIndex(index),
|
|
1477
|
+
sessionFile: input.sessionFileForIndex(index),
|
|
1478
|
+
share: input.shareEnabled,
|
|
1479
|
+
artifactsDir: input.artifactConfig.enabled ? input.artifactsDir : undefined,
|
|
1480
|
+
artifactConfig: input.artifactConfig,
|
|
1481
|
+
maxOutput: input.maxOutput,
|
|
1482
|
+
outputPath,
|
|
1483
|
+
outputMode: behavior?.outputMode,
|
|
1484
|
+
maxSubagentDepth: input.maxSubagentDepths[index],
|
|
1485
|
+
controlConfig: input.controlConfig,
|
|
1486
|
+
onControlEvent: input.onControlEvent,
|
|
1487
|
+
intercomSessionName: input.childIntercomTarget?.(task.agent, index),
|
|
1488
|
+
orchestratorIntercomTarget: input.orchestratorIntercomTarget,
|
|
1489
|
+
nestedRoute: input.foregroundControl?.nestedRoute,
|
|
1490
|
+
modelOverride: input.modelOverrides[index],
|
|
1491
|
+
availableModels: input.availableModels,
|
|
1492
|
+
preferredModelProvider: input.ctx.model?.provider,
|
|
1493
|
+
skills: effectiveSkills === false ? [] : effectiveSkills,
|
|
1494
|
+
onUpdate: input.onUpdate
|
|
1495
|
+
? (progressUpdate) => {
|
|
1496
|
+
const stepResults = progressUpdate.details?.results || [];
|
|
1497
|
+
const stepProgress = progressUpdate.details?.progress || [];
|
|
1498
|
+
if (input.foregroundControl && stepProgress.length > 0) {
|
|
1499
|
+
const current = stepProgress[0];
|
|
1500
|
+
input.foregroundControl.currentAgent = task.agent;
|
|
1501
|
+
input.foregroundControl.currentIndex = index;
|
|
1502
|
+
input.foregroundControl.currentActivityState = current?.activityState;
|
|
1503
|
+
input.foregroundControl.lastActivityAt = current?.lastActivityAt;
|
|
1504
|
+
input.foregroundControl.currentTool = current?.currentTool;
|
|
1505
|
+
input.foregroundControl.currentToolStartedAt = current?.currentToolStartedAt;
|
|
1506
|
+
input.foregroundControl.currentPath = current?.currentPath;
|
|
1507
|
+
input.foregroundControl.turnCount = current?.turnCount;
|
|
1508
|
+
input.foregroundControl.tokens = current?.tokens;
|
|
1509
|
+
input.foregroundControl.toolCount = current?.toolCount;
|
|
1510
|
+
input.foregroundControl.updatedAt = Date.now();
|
|
1511
|
+
}
|
|
1512
|
+
if (stepResults.length > 0) input.liveResults[index] = stepResults[0];
|
|
1513
|
+
if (stepProgress.length > 0) input.liveProgress[index] = stepProgress[0];
|
|
1514
|
+
const mergedResults = input.liveResults.filter((result): result is SingleResult => result !== undefined);
|
|
1515
|
+
const mergedProgress = input.liveProgress.filter((progress): progress is AgentProgress => progress !== undefined);
|
|
1516
|
+
input.onUpdate?.({
|
|
1517
|
+
content: progressUpdate.content,
|
|
1518
|
+
details: {
|
|
1519
|
+
mode: "parallel",
|
|
1520
|
+
results: mergedResults,
|
|
1521
|
+
progress: mergedProgress,
|
|
1522
|
+
controlEvents: progressUpdate.details?.controlEvents,
|
|
1523
|
+
totalSteps: input.tasks.length,
|
|
1524
|
+
},
|
|
1525
|
+
});
|
|
1526
|
+
}
|
|
1527
|
+
: undefined,
|
|
1528
|
+
}).finally(() => {
|
|
1529
|
+
if (input.foregroundControl?.currentIndex === index) {
|
|
1530
|
+
input.foregroundControl.interrupt = undefined;
|
|
1531
|
+
input.foregroundControl.updatedAt = Date.now();
|
|
1532
|
+
}
|
|
1533
|
+
});
|
|
1534
|
+
});
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps): Promise<AgentToolResult<Details>> {
|
|
1538
|
+
const {
|
|
1539
|
+
params,
|
|
1540
|
+
effectiveCwd,
|
|
1541
|
+
agents,
|
|
1542
|
+
ctx,
|
|
1543
|
+
signal,
|
|
1544
|
+
runId,
|
|
1545
|
+
sessionDirForIndex,
|
|
1546
|
+
sessionFileForIndex,
|
|
1547
|
+
shareEnabled,
|
|
1548
|
+
artifactConfig,
|
|
1549
|
+
artifactsDir,
|
|
1550
|
+
backgroundRequestedWhileClarifying,
|
|
1551
|
+
onUpdate,
|
|
1552
|
+
sessionRoot,
|
|
1553
|
+
controlConfig,
|
|
1554
|
+
} = data;
|
|
1555
|
+
const onControlEvent = createForegroundControlNotifier(data, deps);
|
|
1556
|
+
const childIntercomTarget = data.intercomBridge.active ? resolveSubagentIntercomTarget : undefined;
|
|
1557
|
+
const allProgress: AgentProgress[] = [];
|
|
1558
|
+
const allArtifactPaths: ArtifactPaths[] = [];
|
|
1559
|
+
const tasks = params.tasks!;
|
|
1560
|
+
const maxParallelTasks = resolveTopLevelParallelMaxTasks(deps.config.parallel?.maxTasks);
|
|
1561
|
+
const parallelConcurrency = resolveTopLevelParallelConcurrency(params.concurrency, deps.config.parallel?.concurrency);
|
|
1562
|
+
|
|
1563
|
+
if (tasks.length > maxParallelTasks)
|
|
1564
|
+
return {
|
|
1565
|
+
content: [{ type: "text", text: `Max ${maxParallelTasks} tasks` }],
|
|
1566
|
+
isError: true,
|
|
1567
|
+
details: { mode: "parallel" as const, results: [] },
|
|
1568
|
+
};
|
|
1569
|
+
|
|
1570
|
+
const agentConfigs: AgentConfig[] = [];
|
|
1571
|
+
for (const t of tasks) {
|
|
1572
|
+
const config = agents.find((a) => a.name === t.agent);
|
|
1573
|
+
if (!config) {
|
|
1574
|
+
return {
|
|
1575
|
+
content: [{ type: "text", text: `Unknown agent: ${t.agent}` }],
|
|
1576
|
+
isError: true,
|
|
1577
|
+
details: { mode: "parallel" as const, results: [] },
|
|
1578
|
+
};
|
|
1579
|
+
}
|
|
1580
|
+
agentConfigs.push(config);
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
|
|
1584
|
+
const maxSubagentDepths = agentConfigs.map((config) =>
|
|
1585
|
+
resolveChildMaxSubagentDepth(currentMaxSubagentDepth, config.maxSubagentDepth),
|
|
1586
|
+
);
|
|
1587
|
+
|
|
1588
|
+
if (params.worktree) {
|
|
1589
|
+
const worktreeTaskCwdError = buildParallelWorktreeTaskCwdError(tasks, effectiveCwd);
|
|
1590
|
+
if (worktreeTaskCwdError) return buildParallelModeError(worktreeTaskCwdError);
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
const currentProvider = ctx.model?.provider;
|
|
1594
|
+
const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
1595
|
+
let taskTexts = tasks.map((t) => t.task);
|
|
1596
|
+
const skillOverrides: (string[] | false | undefined)[] = tasks.map((t) =>
|
|
1597
|
+
normalizeSkillInput(t.skill),
|
|
1598
|
+
);
|
|
1599
|
+
const behaviorOverrides: StepOverrides[] = tasks.map((task, index) => ({
|
|
1600
|
+
...(task.output !== undefined ? { output: task.output === true ? agentConfigs[index]?.output ?? false : task.output } : {}),
|
|
1601
|
+
...(task.outputMode !== undefined ? { outputMode: task.outputMode } : {}),
|
|
1602
|
+
...(task.reads !== undefined && task.reads !== true ? { reads: task.reads } : {}),
|
|
1603
|
+
...(task.progress !== undefined ? { progress: task.progress } : {}),
|
|
1604
|
+
...(skillOverrides[index] !== undefined ? { skills: skillOverrides[index] } : {}),
|
|
1605
|
+
...(task.model ? { model: task.model } : {}),
|
|
1606
|
+
}));
|
|
1607
|
+
const modelOverrides: (string | undefined)[] = tasks.map((_, i) =>
|
|
1608
|
+
resolveModelCandidate(behaviorOverrides[i]?.model ?? agentConfigs[i]?.model, availableModels, currentProvider),
|
|
1609
|
+
);
|
|
1610
|
+
|
|
1611
|
+
if (params.clarify === true && ctx.hasUI) {
|
|
1612
|
+
const behaviors = agentConfigs.map((c, i) =>
|
|
1613
|
+
resolveStepBehavior(c, behaviorOverrides[i]!),
|
|
1614
|
+
);
|
|
1615
|
+
const availableSkills = discoverAvailableSkills(effectiveCwd);
|
|
1616
|
+
|
|
1617
|
+
const result = await ctx.ui.custom<ChainClarifyResult>(
|
|
1618
|
+
(tui, theme, _kb, done) =>
|
|
1619
|
+
new ChainClarifyComponent(
|
|
1620
|
+
tui, theme,
|
|
1621
|
+
agentConfigs,
|
|
1622
|
+
taskTexts,
|
|
1623
|
+
"",
|
|
1624
|
+
undefined,
|
|
1625
|
+
behaviors,
|
|
1626
|
+
availableModels,
|
|
1627
|
+
currentProvider,
|
|
1628
|
+
availableSkills,
|
|
1629
|
+
done,
|
|
1630
|
+
"parallel",
|
|
1631
|
+
),
|
|
1632
|
+
{ overlay: true, overlayOptions: { anchor: "center", width: 84, maxHeight: "80%" } },
|
|
1633
|
+
);
|
|
1634
|
+
|
|
1635
|
+
if (!result || !result.confirmed) {
|
|
1636
|
+
return { content: [{ type: "text", text: "Cancelled" }], details: { mode: "parallel", results: [] } };
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
taskTexts = result.templates;
|
|
1640
|
+
for (let i = 0; i < result.behaviorOverrides.length; i++) {
|
|
1641
|
+
const override = result.behaviorOverrides[i];
|
|
1642
|
+
if (override?.model) {
|
|
1643
|
+
modelOverrides[i] = override.model;
|
|
1644
|
+
behaviorOverrides[i]!.model = override.model;
|
|
1645
|
+
}
|
|
1646
|
+
if (override?.output !== undefined) behaviorOverrides[i]!.output = override.output;
|
|
1647
|
+
if (override?.reads !== undefined) behaviorOverrides[i]!.reads = override.reads;
|
|
1648
|
+
if (override?.progress !== undefined) behaviorOverrides[i]!.progress = override.progress;
|
|
1649
|
+
if (override?.skills !== undefined) {
|
|
1650
|
+
skillOverrides[i] = override.skills;
|
|
1651
|
+
behaviorOverrides[i]!.skills = override.skills;
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
if (result.runInBackground) {
|
|
1656
|
+
if (!isAsyncAvailable()) {
|
|
1657
|
+
return {
|
|
1658
|
+
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." }],
|
|
1659
|
+
isError: true,
|
|
1660
|
+
details: { mode: "parallel" as const, results: [] },
|
|
1661
|
+
};
|
|
1662
|
+
}
|
|
1663
|
+
const id = randomUUID();
|
|
1664
|
+
const asyncCtx = {
|
|
1665
|
+
pi: deps.pi,
|
|
1666
|
+
cwd: ctx.cwd,
|
|
1667
|
+
currentSessionId: deps.state.currentSessionId!,
|
|
1668
|
+
currentModelProvider: ctx.model?.provider,
|
|
1669
|
+
};
|
|
1670
|
+
const parallelTasks = tasks.map((t, i) => {
|
|
1671
|
+
const taskText = params.context === "fork" ? wrapForkTask(taskTexts[i]!) : taskTexts[i]!;
|
|
1672
|
+
const progress = taskDisallowsFileUpdates(taskText) ? false : behaviorOverrides[i]?.progress;
|
|
1673
|
+
return {
|
|
1674
|
+
agent: t.agent,
|
|
1675
|
+
task: taskText,
|
|
1676
|
+
cwd: t.cwd,
|
|
1677
|
+
...(modelOverrides[i] ? { model: modelOverrides[i] } : {}),
|
|
1678
|
+
...(skillOverrides[i] !== undefined ? { skill: skillOverrides[i] } : {}),
|
|
1679
|
+
...(behaviorOverrides[i]?.output !== undefined ? { output: behaviorOverrides[i]!.output } : {}),
|
|
1680
|
+
...(behaviorOverrides[i]?.outputMode !== undefined ? { outputMode: behaviorOverrides[i]!.outputMode } : {}),
|
|
1681
|
+
...(behaviorOverrides[i]?.reads !== undefined ? { reads: behaviorOverrides[i]!.reads } : {}),
|
|
1682
|
+
...(progress !== undefined ? { progress } : {}),
|
|
1683
|
+
};
|
|
1684
|
+
});
|
|
1685
|
+
return executeAsyncChain(id, {
|
|
1686
|
+
chain: [{ parallel: parallelTasks, concurrency: parallelConcurrency, worktree: params.worktree }],
|
|
1687
|
+
resultMode: "parallel",
|
|
1688
|
+
agents,
|
|
1689
|
+
ctx: asyncCtx,
|
|
1690
|
+
availableModels,
|
|
1691
|
+
cwd: effectiveCwd,
|
|
1692
|
+
maxOutput: params.maxOutput,
|
|
1693
|
+
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
1694
|
+
artifactConfig,
|
|
1695
|
+
shareEnabled,
|
|
1696
|
+
sessionRoot,
|
|
1697
|
+
chainSkills: [],
|
|
1698
|
+
sessionFilesByFlatIndex: tasks.map((_, index) => sessionFileForIndex(index)),
|
|
1699
|
+
maxSubagentDepth: currentMaxSubagentDepth,
|
|
1700
|
+
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1701
|
+
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
1702
|
+
controlConfig,
|
|
1703
|
+
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
1704
|
+
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
|
|
1705
|
+
});
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
const behaviors = agentConfigs.map((config, index) => suppressProgressForReadOnlyTask(resolveStepBehavior(config, behaviorOverrides[index]!), taskTexts[index]));
|
|
1710
|
+
const firstProgressIndex = behaviors.findIndex((behavior) => behavior.progress);
|
|
1711
|
+
const liveResults: (SingleResult | undefined)[] = new Array(tasks.length).fill(undefined);
|
|
1712
|
+
const liveProgress: (AgentProgress | undefined)[] = new Array(tasks.length).fill(undefined);
|
|
1713
|
+
const foregroundControl = deps.state.foregroundControls.get(runId);
|
|
1714
|
+
const { setup: worktreeSetup, errorResult } = createParallelWorktreeSetup(
|
|
1715
|
+
params.worktree,
|
|
1716
|
+
effectiveCwd,
|
|
1717
|
+
runId,
|
|
1718
|
+
tasks,
|
|
1719
|
+
deps.config.worktreeSetupHook,
|
|
1720
|
+
deps.config.worktreeSetupHookTimeoutMs,
|
|
1721
|
+
);
|
|
1722
|
+
if (errorResult) return errorResult;
|
|
1723
|
+
|
|
1724
|
+
try {
|
|
1725
|
+
const duplicateOutputError = findDuplicateParallelOutputPath({
|
|
1726
|
+
tasks,
|
|
1727
|
+
behaviors,
|
|
1728
|
+
paramsCwd: effectiveCwd,
|
|
1729
|
+
ctxCwd: ctx.cwd,
|
|
1730
|
+
worktreeSetup,
|
|
1731
|
+
});
|
|
1732
|
+
if (duplicateOutputError) return buildParallelModeError(duplicateOutputError);
|
|
1733
|
+
for (let index = 0; index < tasks.length; index++) {
|
|
1734
|
+
const taskCwd = resolveParallelTaskCwd(tasks[index]!, effectiveCwd, worktreeSetup, index);
|
|
1735
|
+
const outputPath = resolveSingleOutputPath(behaviors[index]?.output, ctx.cwd, taskCwd);
|
|
1736
|
+
const validationError = validateFileOnlyOutputMode(behaviors[index]?.outputMode, outputPath, `Parallel task ${index + 1} (${tasks[index]!.agent})`);
|
|
1737
|
+
if (validationError) return buildParallelModeError(validationError);
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
const parallelProgressPrecreated = firstProgressIndex !== -1;
|
|
1741
|
+
if (parallelProgressPrecreated) writeInitialProgressFile(effectiveCwd);
|
|
1742
|
+
|
|
1743
|
+
if (params.context === "fork") {
|
|
1744
|
+
for (let i = 0; i < taskTexts.length; i++) {
|
|
1745
|
+
taskTexts[i] = wrapForkTask(taskTexts[i]!);
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
const results = await runForegroundParallelTasks({
|
|
1750
|
+
tasks,
|
|
1751
|
+
taskTexts,
|
|
1752
|
+
agents,
|
|
1753
|
+
ctx,
|
|
1754
|
+
intercomEvents: deps.pi.events,
|
|
1755
|
+
signal,
|
|
1756
|
+
runId,
|
|
1757
|
+
sessionDirForIndex,
|
|
1758
|
+
sessionFileForIndex,
|
|
1759
|
+
shareEnabled,
|
|
1760
|
+
artifactConfig,
|
|
1761
|
+
artifactsDir,
|
|
1762
|
+
maxOutput: params.maxOutput,
|
|
1763
|
+
paramsCwd: effectiveCwd,
|
|
1764
|
+
availableModels,
|
|
1765
|
+
modelOverrides,
|
|
1766
|
+
behaviors,
|
|
1767
|
+
firstProgressIndex: parallelProgressPrecreated ? -1 : firstProgressIndex,
|
|
1768
|
+
controlConfig,
|
|
1769
|
+
onControlEvent,
|
|
1770
|
+
childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(runId, agent, index) : undefined,
|
|
1771
|
+
orchestratorIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
1772
|
+
foregroundControl,
|
|
1773
|
+
concurrencyLimit: parallelConcurrency,
|
|
1774
|
+
maxSubagentDepths,
|
|
1775
|
+
liveResults,
|
|
1776
|
+
liveProgress,
|
|
1777
|
+
onUpdate,
|
|
1778
|
+
worktreeSetup,
|
|
1779
|
+
});
|
|
1780
|
+
for (let i = 0; i < results.length; i++) {
|
|
1781
|
+
const run = results[i]!;
|
|
1782
|
+
recordRun(run.agent, taskTexts[i]!, run.exitCode, run.progressSummary?.durationMs ?? 0);
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
for (const result of results) {
|
|
1786
|
+
if (result.progress) allProgress.push(result.progress);
|
|
1787
|
+
if (result.artifactPaths) allArtifactPaths.push(result.artifactPaths);
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
const interrupted = results.find((result) => result.interrupted);
|
|
1791
|
+
const details = compactForegroundDetails({
|
|
1792
|
+
mode: "parallel",
|
|
1793
|
+
runId,
|
|
1794
|
+
results,
|
|
1795
|
+
progress: params.includeProgress ? allProgress : undefined,
|
|
1796
|
+
artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
|
|
1797
|
+
});
|
|
1798
|
+
rememberForegroundRun(deps.state, { runId, mode: "parallel", cwd: effectiveCwd, results: details.results });
|
|
1799
|
+
if (interrupted) {
|
|
1800
|
+
return {
|
|
1801
|
+
content: [{ type: "text", text: `Parallel run paused after interrupt (${interrupted.agent}). Waiting for explicit next action.` }],
|
|
1802
|
+
details,
|
|
1803
|
+
};
|
|
1804
|
+
}
|
|
1805
|
+
const detachedIndex = results.findIndex((result) => result.detached);
|
|
1806
|
+
const detached = detachedIndex >= 0 ? results[detachedIndex] : undefined;
|
|
1807
|
+
if (detached) {
|
|
1808
|
+
return {
|
|
1809
|
+
content: [{ type: "text", text: `Parallel run detached for intercom coordination (${detached.agent}). Reply to the supervisor request first. After the child exits, start a fresh follow-up if needed.` }],
|
|
1810
|
+
details,
|
|
1811
|
+
};
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
if (foregroundControl) updateForegroundNestedProjection(foregroundControl);
|
|
1815
|
+
const intercomReceipt = await maybeBuildForegroundIntercomReceipt({
|
|
1816
|
+
pi: deps.pi,
|
|
1817
|
+
intercomBridge: data.intercomBridge,
|
|
1818
|
+
runId,
|
|
1819
|
+
mode: "parallel",
|
|
1820
|
+
details,
|
|
1821
|
+
...(foregroundControl?.nestedChildren?.length ? { nestedChildren: foregroundControl.nestedChildren } : {}),
|
|
1822
|
+
});
|
|
1823
|
+
if (intercomReceipt) {
|
|
1824
|
+
return {
|
|
1825
|
+
content: [{ type: "text", text: intercomReceipt.text }],
|
|
1826
|
+
details: intercomReceipt.details,
|
|
1827
|
+
};
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
const worktreeSuffix = buildParallelWorktreeSuffix(worktreeSetup, artifactsDir, tasks);
|
|
1831
|
+
const ok = results.filter((result) => result.exitCode === 0).length;
|
|
1832
|
+
const downgradeNote = backgroundRequestedWhileClarifying ? " (background requested, but clarify kept this run foreground)" : "";
|
|
1833
|
+
const aggregatedOutput = aggregateParallelOutputs(
|
|
1834
|
+
results.map((result) => ({
|
|
1835
|
+
agent: result.agent,
|
|
1836
|
+
output: result.truncation?.text || getSingleResultOutput(result),
|
|
1837
|
+
exitCode: result.exitCode,
|
|
1838
|
+
error: result.error,
|
|
1839
|
+
})),
|
|
1840
|
+
(i, agent) => `=== Task ${i + 1}: ${agent} ===`,
|
|
1841
|
+
);
|
|
1842
|
+
|
|
1843
|
+
const summary = `${ok}/${results.length} succeeded${downgradeNote}`;
|
|
1844
|
+
const fullContent = worktreeSuffix
|
|
1845
|
+
? `${summary}\n\n${aggregatedOutput}\n\n${worktreeSuffix}`
|
|
1846
|
+
: `${summary}\n\n${aggregatedOutput}`;
|
|
1847
|
+
|
|
1848
|
+
return {
|
|
1849
|
+
content: [{ type: "text", text: fullContent }],
|
|
1850
|
+
details,
|
|
1851
|
+
};
|
|
1852
|
+
} finally {
|
|
1853
|
+
if (worktreeSetup) cleanupWorktrees(worktreeSetup);
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Promise<AgentToolResult<Details>> {
|
|
1858
|
+
const {
|
|
1859
|
+
params,
|
|
1860
|
+
effectiveCwd,
|
|
1861
|
+
agents,
|
|
1862
|
+
ctx,
|
|
1863
|
+
signal,
|
|
1864
|
+
runId,
|
|
1865
|
+
sessionDirForIndex,
|
|
1866
|
+
sessionFileForIndex,
|
|
1867
|
+
shareEnabled,
|
|
1868
|
+
artifactConfig,
|
|
1869
|
+
artifactsDir,
|
|
1870
|
+
onUpdate,
|
|
1871
|
+
sessionRoot,
|
|
1872
|
+
controlConfig,
|
|
1873
|
+
} = data;
|
|
1874
|
+
const onControlEvent = createForegroundControlNotifier(data, deps);
|
|
1875
|
+
const childIntercomTarget = data.intercomBridge.active ? resolveSubagentIntercomTarget(runId, params.agent!, 0) : undefined;
|
|
1876
|
+
const allProgress: AgentProgress[] = [];
|
|
1877
|
+
const allArtifactPaths: ArtifactPaths[] = [];
|
|
1878
|
+
const agentConfig = agents.find((a) => a.name === params.agent);
|
|
1879
|
+
if (!agentConfig) {
|
|
1880
|
+
return {
|
|
1881
|
+
content: [{ type: "text", text: `Unknown agent: ${params.agent}` }],
|
|
1882
|
+
isError: true,
|
|
1883
|
+
details: { mode: "single", results: [] },
|
|
1884
|
+
};
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
const currentProvider = ctx.model?.provider;
|
|
1888
|
+
const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
1889
|
+
let task = params.task ?? "";
|
|
1890
|
+
let modelOverride: string | undefined = resolveModelCandidate(
|
|
1891
|
+
(params.model as string | undefined) ?? agentConfig.model,
|
|
1892
|
+
availableModels,
|
|
1893
|
+
currentProvider,
|
|
1894
|
+
);
|
|
1895
|
+
let skillOverride: string[] | false | undefined = normalizeSkillInput(params.skill);
|
|
1896
|
+
const rawOutput = params.output !== undefined ? params.output : agentConfig.output;
|
|
1897
|
+
let effectiveOutput = normalizeSingleOutputOverride(rawOutput, agentConfig.output);
|
|
1898
|
+
const effectiveOutputMode = params.outputMode ?? "inline";
|
|
1899
|
+
const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
|
|
1900
|
+
const maxSubagentDepth = resolveChildMaxSubagentDepth(currentMaxSubagentDepth, agentConfig.maxSubagentDepth);
|
|
1901
|
+
|
|
1902
|
+
if (params.clarify === true && ctx.hasUI) {
|
|
1903
|
+
const behavior = resolveStepBehavior(agentConfig, { output: effectiveOutput, skills: skillOverride });
|
|
1904
|
+
const availableSkills = discoverAvailableSkills(effectiveCwd);
|
|
1905
|
+
|
|
1906
|
+
const result = await ctx.ui.custom<ChainClarifyResult>(
|
|
1907
|
+
(tui, theme, _kb, done) =>
|
|
1908
|
+
new ChainClarifyComponent(
|
|
1909
|
+
tui, theme,
|
|
1910
|
+
[agentConfig],
|
|
1911
|
+
[task],
|
|
1912
|
+
task,
|
|
1913
|
+
undefined,
|
|
1914
|
+
[behavior],
|
|
1915
|
+
availableModels,
|
|
1916
|
+
currentProvider,
|
|
1917
|
+
availableSkills,
|
|
1918
|
+
done,
|
|
1919
|
+
"single",
|
|
1920
|
+
),
|
|
1921
|
+
{ overlay: true, overlayOptions: { anchor: "center", width: 84, maxHeight: "80%" } },
|
|
1922
|
+
);
|
|
1923
|
+
|
|
1924
|
+
if (!result || !result.confirmed) {
|
|
1925
|
+
return { content: [{ type: "text", text: "Cancelled" }], details: { mode: "single", results: [] } };
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
task = result.templates[0]!;
|
|
1929
|
+
const override = result.behaviorOverrides[0];
|
|
1930
|
+
if (override?.model) modelOverride = override.model;
|
|
1931
|
+
if (override?.output !== undefined) effectiveOutput = normalizeSingleOutputOverride(override.output, agentConfig.output);
|
|
1932
|
+
if (override?.skills !== undefined) skillOverride = override.skills;
|
|
1933
|
+
|
|
1934
|
+
if (result.runInBackground) {
|
|
1935
|
+
if (!isAsyncAvailable()) {
|
|
1936
|
+
return {
|
|
1937
|
+
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." }],
|
|
1938
|
+
isError: true,
|
|
1939
|
+
details: { mode: "single" as const, results: [] },
|
|
1940
|
+
};
|
|
1941
|
+
}
|
|
1942
|
+
const id = randomUUID();
|
|
1943
|
+
const asyncCtx = {
|
|
1944
|
+
pi: deps.pi,
|
|
1945
|
+
cwd: ctx.cwd,
|
|
1946
|
+
currentSessionId: deps.state.currentSessionId!,
|
|
1947
|
+
currentModelProvider: ctx.model?.provider,
|
|
1948
|
+
};
|
|
1949
|
+
return executeAsyncSingle(id, {
|
|
1950
|
+
agent: params.agent!,
|
|
1951
|
+
task: params.context === "fork" ? wrapForkTask(task) : task,
|
|
1952
|
+
agentConfig,
|
|
1953
|
+
ctx: asyncCtx,
|
|
1954
|
+
availableModels,
|
|
1955
|
+
cwd: effectiveCwd,
|
|
1956
|
+
maxOutput: params.maxOutput,
|
|
1957
|
+
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
1958
|
+
artifactConfig,
|
|
1959
|
+
shareEnabled,
|
|
1960
|
+
sessionRoot,
|
|
1961
|
+
sessionFile: sessionFileForIndex(0),
|
|
1962
|
+
skills: skillOverride === false ? [] : skillOverride,
|
|
1963
|
+
output: effectiveOutput,
|
|
1964
|
+
outputMode: effectiveOutputMode,
|
|
1965
|
+
modelOverride,
|
|
1966
|
+
maxSubagentDepth,
|
|
1967
|
+
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1968
|
+
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
1969
|
+
controlConfig,
|
|
1970
|
+
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
1971
|
+
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
|
|
1972
|
+
});
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
if (params.context === "fork") {
|
|
1977
|
+
task = wrapForkTask(task);
|
|
1978
|
+
}
|
|
1979
|
+
const cleanTask = task;
|
|
1980
|
+
const outputPath = resolveSingleOutputPath(effectiveOutput, ctx.cwd, effectiveCwd);
|
|
1981
|
+
const validationError = validateFileOnlyOutputMode(effectiveOutputMode, outputPath, `Single run (${params.agent})`);
|
|
1982
|
+
if (validationError) {
|
|
1983
|
+
return { content: [{ type: "text", text: validationError }], isError: true, details: { mode: "single", results: [] } };
|
|
1984
|
+
}
|
|
1985
|
+
task = injectSingleOutputInstruction(task, outputPath);
|
|
1986
|
+
|
|
1987
|
+
let effectiveSkills: string[] | undefined;
|
|
1988
|
+
if (skillOverride === false) {
|
|
1989
|
+
effectiveSkills = [];
|
|
1990
|
+
} else {
|
|
1991
|
+
effectiveSkills = skillOverride;
|
|
1992
|
+
}
|
|
1993
|
+
const interruptController = new AbortController();
|
|
1994
|
+
const foregroundControl = deps.state.foregroundControls.get(runId);
|
|
1995
|
+
if (foregroundControl) {
|
|
1996
|
+
foregroundControl.currentAgent = params.agent;
|
|
1997
|
+
foregroundControl.currentIndex = 0;
|
|
1998
|
+
foregroundControl.currentActivityState = undefined;
|
|
1999
|
+
foregroundControl.updatedAt = Date.now();
|
|
2000
|
+
foregroundControl.interrupt = () => {
|
|
2001
|
+
if (interruptController.signal.aborted) return false;
|
|
2002
|
+
interruptController.abort();
|
|
2003
|
+
foregroundControl.currentActivityState = undefined;
|
|
2004
|
+
foregroundControl.updatedAt = Date.now();
|
|
2005
|
+
return true;
|
|
2006
|
+
};
|
|
2007
|
+
}
|
|
2008
|
+
|
|
2009
|
+
const forwardSingleUpdate = onUpdate
|
|
2010
|
+
? (update: AgentToolResult<Details>) => {
|
|
2011
|
+
if (foregroundControl) {
|
|
2012
|
+
const firstProgress = update.details?.progress?.[0];
|
|
2013
|
+
foregroundControl.currentAgent = params.agent;
|
|
2014
|
+
foregroundControl.currentIndex = firstProgress?.index ?? 0;
|
|
2015
|
+
foregroundControl.currentActivityState = firstProgress?.activityState;
|
|
2016
|
+
foregroundControl.lastActivityAt = firstProgress?.lastActivityAt;
|
|
2017
|
+
foregroundControl.currentTool = firstProgress?.currentTool;
|
|
2018
|
+
foregroundControl.currentToolStartedAt = firstProgress?.currentToolStartedAt;
|
|
2019
|
+
foregroundControl.currentPath = firstProgress?.currentPath;
|
|
2020
|
+
foregroundControl.turnCount = firstProgress?.turnCount;
|
|
2021
|
+
foregroundControl.tokens = firstProgress?.tokens;
|
|
2022
|
+
foregroundControl.toolCount = firstProgress?.toolCount;
|
|
2023
|
+
foregroundControl.updatedAt = Date.now();
|
|
2024
|
+
}
|
|
2025
|
+
onUpdate(update);
|
|
2026
|
+
}
|
|
2027
|
+
: undefined;
|
|
2028
|
+
|
|
2029
|
+
const r = await runSync(ctx.cwd, agents, params.agent!, task, {
|
|
2030
|
+
cwd: effectiveCwd,
|
|
2031
|
+
signal,
|
|
2032
|
+
interruptSignal: interruptController.signal,
|
|
2033
|
+
allowIntercomDetach: agentConfig.systemPrompt?.includes(INTERCOM_BRIDGE_MARKER) === true,
|
|
2034
|
+
intercomEvents: deps.pi.events,
|
|
2035
|
+
runId,
|
|
2036
|
+
sessionDir: sessionDirForIndex(0),
|
|
2037
|
+
sessionFile: sessionFileForIndex(0),
|
|
2038
|
+
share: shareEnabled,
|
|
2039
|
+
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
2040
|
+
artifactConfig,
|
|
2041
|
+
maxOutput: params.maxOutput,
|
|
2042
|
+
outputPath,
|
|
2043
|
+
outputMode: effectiveOutputMode,
|
|
2044
|
+
maxSubagentDepth,
|
|
2045
|
+
onUpdate: forwardSingleUpdate,
|
|
2046
|
+
controlConfig,
|
|
2047
|
+
onControlEvent,
|
|
2048
|
+
intercomSessionName: childIntercomTarget,
|
|
2049
|
+
orchestratorIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
2050
|
+
nestedRoute: foregroundControl?.nestedRoute,
|
|
2051
|
+
index: 0,
|
|
2052
|
+
modelOverride,
|
|
2053
|
+
availableModels,
|
|
2054
|
+
preferredModelProvider: currentProvider,
|
|
2055
|
+
skills: effectiveSkills,
|
|
2056
|
+
});
|
|
2057
|
+
if (foregroundControl?.currentIndex === 0) {
|
|
2058
|
+
foregroundControl.interrupt = undefined;
|
|
2059
|
+
foregroundControl.currentActivityState = r.progress?.activityState;
|
|
2060
|
+
foregroundControl.lastActivityAt = r.progress?.lastActivityAt;
|
|
2061
|
+
foregroundControl.currentTool = r.progress?.currentTool;
|
|
2062
|
+
foregroundControl.currentToolStartedAt = r.progress?.currentToolStartedAt;
|
|
2063
|
+
foregroundControl.currentPath = r.progress?.currentPath;
|
|
2064
|
+
foregroundControl.turnCount = r.progress?.turnCount;
|
|
2065
|
+
foregroundControl.tokens = r.progress?.tokens;
|
|
2066
|
+
foregroundControl.toolCount = r.progress?.toolCount;
|
|
2067
|
+
foregroundControl.updatedAt = Date.now();
|
|
2068
|
+
}
|
|
2069
|
+
recordRun(params.agent!, cleanTask, r.exitCode, r.progressSummary?.durationMs ?? 0);
|
|
2070
|
+
|
|
2071
|
+
if (r.progress) allProgress.push(r.progress);
|
|
2072
|
+
if (r.artifactPaths) allArtifactPaths.push(r.artifactPaths);
|
|
2073
|
+
|
|
2074
|
+
const fullOutput = getSingleResultOutput(r);
|
|
2075
|
+
const finalizedOutput = finalizeSingleOutput({
|
|
2076
|
+
fullOutput,
|
|
2077
|
+
truncatedOutput: r.truncation?.text,
|
|
2078
|
+
outputPath,
|
|
2079
|
+
outputMode: r.outputMode,
|
|
2080
|
+
exitCode: r.exitCode,
|
|
2081
|
+
savedPath: r.savedOutputPath,
|
|
2082
|
+
outputReference: r.outputReference,
|
|
2083
|
+
saveError: r.outputSaveError,
|
|
2084
|
+
});
|
|
2085
|
+
const details = compactForegroundDetails({
|
|
2086
|
+
mode: "single",
|
|
2087
|
+
runId,
|
|
2088
|
+
results: [r],
|
|
2089
|
+
progress: params.includeProgress ? allProgress : undefined,
|
|
2090
|
+
artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
|
|
2091
|
+
truncation: r.truncation,
|
|
2092
|
+
});
|
|
2093
|
+
rememberForegroundRun(deps.state, { runId, mode: "single", cwd: effectiveCwd, results: details.results });
|
|
2094
|
+
|
|
2095
|
+
if (!r.detached && !r.interrupted) {
|
|
2096
|
+
if (foregroundControl) updateForegroundNestedProjection(foregroundControl);
|
|
2097
|
+
const intercomReceipt = await maybeBuildForegroundIntercomReceipt({
|
|
2098
|
+
pi: deps.pi,
|
|
2099
|
+
intercomBridge: data.intercomBridge,
|
|
2100
|
+
runId,
|
|
2101
|
+
mode: "single",
|
|
2102
|
+
details,
|
|
2103
|
+
...(foregroundControl?.nestedChildren?.length ? { nestedChildren: foregroundControl.nestedChildren } : {}),
|
|
2104
|
+
});
|
|
2105
|
+
if (intercomReceipt) {
|
|
2106
|
+
return {
|
|
2107
|
+
content: [{ type: "text", text: intercomReceipt.text }],
|
|
2108
|
+
details: intercomReceipt.details,
|
|
2109
|
+
...(r.exitCode !== 0 ? { isError: true } : {}),
|
|
2110
|
+
};
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
if (r.detached) {
|
|
2115
|
+
return {
|
|
2116
|
+
content: [{ type: "text", text: `Detached for intercom coordination: ${params.agent}. Reply to the supervisor request first. After the child exits, start a fresh follow-up if needed.` }],
|
|
2117
|
+
details,
|
|
2118
|
+
};
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2121
|
+
if (r.interrupted) {
|
|
2122
|
+
return {
|
|
2123
|
+
content: [{ type: "text", text: `Run paused after interrupt (${params.agent}). Waiting for explicit next action.` }],
|
|
2124
|
+
details,
|
|
2125
|
+
};
|
|
2126
|
+
}
|
|
2127
|
+
|
|
2128
|
+
if (r.exitCode !== 0)
|
|
2129
|
+
return {
|
|
2130
|
+
content: [{ type: "text", text: r.error || "Failed" }],
|
|
2131
|
+
details,
|
|
2132
|
+
isError: true,
|
|
2133
|
+
};
|
|
2134
|
+
return {
|
|
2135
|
+
content: [{ type: "text", text: finalizedOutput.displayOutput || "(no output)" }],
|
|
2136
|
+
details,
|
|
2137
|
+
};
|
|
2138
|
+
}
|
|
2139
|
+
|
|
2140
|
+
export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
2141
|
+
execute: (
|
|
2142
|
+
id: string,
|
|
2143
|
+
params: SubagentParamsLike,
|
|
2144
|
+
signal: AbortSignal,
|
|
2145
|
+
onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
|
|
2146
|
+
ctx: ExtensionContext,
|
|
2147
|
+
) => Promise<AgentToolResult<Details>>;
|
|
2148
|
+
} {
|
|
2149
|
+
const execute = async (
|
|
2150
|
+
_id: string,
|
|
2151
|
+
params: SubagentParamsLike,
|
|
2152
|
+
signal: AbortSignal,
|
|
2153
|
+
onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
|
|
2154
|
+
ctx: ExtensionContext,
|
|
2155
|
+
): Promise<AgentToolResult<Details>> => {
|
|
2156
|
+
deps.state.baseCwd = ctx.cwd;
|
|
2157
|
+
deps.state.foregroundRuns ??= new Map();
|
|
2158
|
+
deps.state.foregroundControls ??= new Map();
|
|
2159
|
+
deps.state.lastForegroundControlId ??= null;
|
|
2160
|
+
const requestCwd = resolveRequestedCwd(ctx.cwd, params.cwd);
|
|
2161
|
+
const paramsWithResolvedCwd = params.cwd === undefined ? params : { ...params, cwd: requestCwd };
|
|
2162
|
+
if (params.action) {
|
|
2163
|
+
if (params.action === "doctor") {
|
|
2164
|
+
let currentSessionFile: string | null = null;
|
|
2165
|
+
let currentSessionId = deps.state.currentSessionId;
|
|
2166
|
+
let sessionError: string | undefined;
|
|
2167
|
+
try {
|
|
2168
|
+
currentSessionFile = ctx.sessionManager.getSessionFile() ?? null;
|
|
2169
|
+
currentSessionId = ctx.sessionManager.getSessionId();
|
|
2170
|
+
} catch (error) {
|
|
2171
|
+
sessionError = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
2172
|
+
}
|
|
2173
|
+
let orchestratorTarget: string | undefined;
|
|
2174
|
+
try {
|
|
2175
|
+
orchestratorTarget = resolveIntercomSessionTarget(deps.pi.getSessionName(), ctx.sessionManager.getSessionId());
|
|
2176
|
+
} catch {}
|
|
2177
|
+
return {
|
|
2178
|
+
content: [{
|
|
2179
|
+
type: "text",
|
|
2180
|
+
text: buildDoctorReport({
|
|
2181
|
+
cwd: requestCwd,
|
|
2182
|
+
config: deps.config,
|
|
2183
|
+
state: deps.state,
|
|
2184
|
+
context: paramsWithResolvedCwd.context,
|
|
2185
|
+
requestedSessionDir: paramsWithResolvedCwd.sessionDir,
|
|
2186
|
+
currentSessionFile,
|
|
2187
|
+
currentSessionId,
|
|
2188
|
+
orchestratorTarget,
|
|
2189
|
+
sessionError,
|
|
2190
|
+
expandTilde: deps.expandTilde,
|
|
2191
|
+
}),
|
|
2192
|
+
}],
|
|
2193
|
+
details: { mode: "management", results: [] },
|
|
2194
|
+
};
|
|
2195
|
+
}
|
|
2196
|
+
if (params.action === "status") {
|
|
2197
|
+
const targetRunId = paramsWithResolvedCwd.id ?? paramsWithResolvedCwd.runId;
|
|
2198
|
+
if (targetRunId) {
|
|
2199
|
+
try {
|
|
2200
|
+
const nestedScope = nestedResolutionScopeForExecutor(deps);
|
|
2201
|
+
const resolved = resolveSubagentRunId(targetRunId, { state: deps.state, nested: nestedScope });
|
|
2202
|
+
if (resolved?.kind === "foreground") {
|
|
2203
|
+
const foreground = getForegroundControl(deps.state, resolved.id);
|
|
2204
|
+
if (foreground) return foregroundStatusResult(foreground);
|
|
2205
|
+
}
|
|
2206
|
+
} catch (error) {
|
|
2207
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2208
|
+
return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
|
|
2209
|
+
}
|
|
2210
|
+
} else {
|
|
2211
|
+
const foreground = getForegroundControl(deps.state, undefined);
|
|
2212
|
+
if (foreground) return foregroundStatusResult(foreground);
|
|
2213
|
+
}
|
|
2214
|
+
return inspectSubagentStatus(paramsWithResolvedCwd, { state: deps.state, nested: nestedResolutionScopeForExecutor(deps) });
|
|
2215
|
+
}
|
|
2216
|
+
if (params.action === "resume") {
|
|
2217
|
+
return resumeAsyncRun({ params: paramsWithResolvedCwd, requestCwd, ctx, deps });
|
|
2218
|
+
}
|
|
2219
|
+
if (params.action === "interrupt") {
|
|
2220
|
+
const targetRunId = paramsWithResolvedCwd.runId ?? paramsWithResolvedCwd.id;
|
|
2221
|
+
let resolved: ResolvedSubagentRunId | undefined;
|
|
2222
|
+
if (targetRunId) {
|
|
2223
|
+
try {
|
|
2224
|
+
resolved = resolveSubagentRunId(targetRunId, { state: deps.state, nested: nestedResolutionScopeForExecutor(deps) });
|
|
2225
|
+
} catch (error) {
|
|
2226
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2227
|
+
return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
if (resolved?.kind === "nested") return interruptNestedRun(resolved);
|
|
2231
|
+
const foreground = getForegroundControl(deps.state, resolved?.kind === "foreground" ? resolved.id : targetRunId);
|
|
2232
|
+
if (foreground?.interrupt) {
|
|
2233
|
+
const interrupted = foreground.interrupt();
|
|
2234
|
+
if (interrupted) {
|
|
2235
|
+
foreground.updatedAt = Date.now();
|
|
2236
|
+
foreground.currentActivityState = undefined;
|
|
2237
|
+
return {
|
|
2238
|
+
content: [{ type: "text", text: `Interrupt requested for foreground run ${foreground.runId}.` }],
|
|
2239
|
+
details: { mode: "management", results: [] },
|
|
2240
|
+
};
|
|
2241
|
+
}
|
|
2242
|
+
return {
|
|
2243
|
+
content: [{ type: "text", text: `Foreground run ${foreground.runId} has no active child step to interrupt.` }],
|
|
2244
|
+
isError: true,
|
|
2245
|
+
details: { mode: "management", results: [] },
|
|
2246
|
+
};
|
|
2247
|
+
}
|
|
2248
|
+
const asyncInterruptResult = interruptAsyncRun(deps.state, resolved?.kind === "async" ? resolved.id : targetRunId);
|
|
2249
|
+
if (asyncInterruptResult) return asyncInterruptResult;
|
|
2250
|
+
return {
|
|
2251
|
+
content: [{ type: "text", text: "No interrupt-capable run found in this session." }],
|
|
2252
|
+
isError: true,
|
|
2253
|
+
details: { mode: "management", results: [] },
|
|
2254
|
+
};
|
|
2255
|
+
}
|
|
2256
|
+
if (!(SUBAGENT_ACTIONS as readonly string[]).includes(params.action)) {
|
|
2257
|
+
return {
|
|
2258
|
+
content: [{ type: "text", text: `Unknown action: ${params.action}. Valid: ${SUBAGENT_ACTIONS.join(", ")}` }],
|
|
2259
|
+
isError: true,
|
|
2260
|
+
details: { mode: "management" as const, results: [] },
|
|
2261
|
+
};
|
|
2262
|
+
}
|
|
2263
|
+
if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(params.action)) {
|
|
2264
|
+
return {
|
|
2265
|
+
content: [{ type: "text", text: `Action '${params.action}' is not available from child-safe subagent fanout mode.` }],
|
|
2266
|
+
isError: true,
|
|
2267
|
+
details: { mode: "management" as const, results: [] },
|
|
2268
|
+
};
|
|
2269
|
+
}
|
|
2270
|
+
return handleManagementAction(params.action, paramsWithResolvedCwd, { ...ctx, cwd: requestCwd });
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2273
|
+
const { blocked, depth, maxDepth } = checkSubagentDepth(deps.config.maxSubagentDepth);
|
|
2274
|
+
if (blocked) {
|
|
2275
|
+
return {
|
|
2276
|
+
content: [
|
|
2277
|
+
{
|
|
2278
|
+
type: "text",
|
|
2279
|
+
text:
|
|
2280
|
+
`Nested subagent call blocked (depth=${depth}, max=${maxDepth}). ` +
|
|
2281
|
+
"You are running at the maximum subagent nesting depth. " +
|
|
2282
|
+
"Complete your current task directly without delegating to further subagents.",
|
|
2283
|
+
},
|
|
2284
|
+
],
|
|
2285
|
+
isError: true,
|
|
2286
|
+
details: { mode: "single" as const, results: [] },
|
|
2287
|
+
};
|
|
2288
|
+
}
|
|
2289
|
+
|
|
2290
|
+
const normalized = normalizeRepeatedParallelCounts(paramsWithResolvedCwd);
|
|
2291
|
+
if (normalized.error) return normalized.error;
|
|
2292
|
+
const normalizedParams = normalized.params!;
|
|
2293
|
+
|
|
2294
|
+
let effectiveParams = applyForceTopLevelAsyncOverride(
|
|
2295
|
+
normalizedParams,
|
|
2296
|
+
depth,
|
|
2297
|
+
deps.config.forceTopLevelAsync === true,
|
|
2298
|
+
);
|
|
2299
|
+
|
|
2300
|
+
const scope: AgentScope = resolveExecutionAgentScope(effectiveParams.agentScope);
|
|
2301
|
+
const effectiveCwd = effectiveParams.cwd ?? ctx.cwd;
|
|
2302
|
+
const parentSessionFile = ctx.sessionManager.getSessionFile() ?? null;
|
|
2303
|
+
deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
2304
|
+
const discoveredAgents = deps.discoverAgents(effectiveCwd, scope).agents;
|
|
2305
|
+
effectiveParams = applyAgentDefaultContext(effectiveParams, discoveredAgents);
|
|
2306
|
+
const sessionName = resolveIntercomSessionTarget(deps.pi.getSessionName(), ctx.sessionManager.getSessionId());
|
|
2307
|
+
const intercomBridge = resolveIntercomBridge({
|
|
2308
|
+
config: deps.config.intercomBridge,
|
|
2309
|
+
context: effectiveParams.context,
|
|
2310
|
+
orchestratorTarget: sessionName,
|
|
2311
|
+
cwd: effectiveCwd,
|
|
2312
|
+
});
|
|
2313
|
+
const agents = intercomBridge.active
|
|
2314
|
+
? discoveredAgents.map((agent) => applyIntercomBridgeToAgent(agent, intercomBridge))
|
|
2315
|
+
: discoveredAgents;
|
|
2316
|
+
const runId = randomUUID().slice(0, 8);
|
|
2317
|
+
const inheritedNestedRoute = resolveInheritedNestedRouteFromEnv();
|
|
2318
|
+
const nestedParentAddress = inheritedNestedRoute ? resolveNestedParentAddressFromEnv() : undefined;
|
|
2319
|
+
const nestedRoute = inheritedNestedRoute ?? createNestedRoute(runId);
|
|
2320
|
+
const shareEnabled = effectiveParams.share === true;
|
|
2321
|
+
const hasChain = (effectiveParams.chain?.length ?? 0) > 0;
|
|
2322
|
+
const hasTasks = (effectiveParams.tasks?.length ?? 0) > 0;
|
|
2323
|
+
const hasSingle = !hasChain && !hasTasks && Boolean(effectiveParams.agent);
|
|
2324
|
+
const allowClarifyTaskPrompt = hasChain
|
|
2325
|
+
&& effectiveParams.clarify === true
|
|
2326
|
+
&& ctx.hasUI
|
|
2327
|
+
&& !(effectiveParams.chain?.some(isParallelStep) ?? false);
|
|
2328
|
+
|
|
2329
|
+
const validationError = validateExecutionInput(
|
|
2330
|
+
effectiveParams,
|
|
2331
|
+
agents,
|
|
2332
|
+
hasChain,
|
|
2333
|
+
hasTasks,
|
|
2334
|
+
hasSingle,
|
|
2335
|
+
allowClarifyTaskPrompt,
|
|
2336
|
+
);
|
|
2337
|
+
if (validationError) return validationError;
|
|
2338
|
+
|
|
2339
|
+
let sessionFileForIndex: (idx?: number) => string | undefined = () => undefined;
|
|
2340
|
+
try {
|
|
2341
|
+
sessionFileForIndex = createForkContextResolver(ctx.sessionManager, effectiveParams.context).sessionFileForIndex;
|
|
2342
|
+
} catch (error) {
|
|
2343
|
+
return toExecutionErrorResult(effectiveParams, error);
|
|
2344
|
+
}
|
|
2345
|
+
const requestedAsync = effectiveParams.async ?? deps.asyncByDefault;
|
|
2346
|
+
const backgroundRequestedWhileClarifying = (hasChain || hasTasks) && requestedAsync && effectiveParams.clarify === true;
|
|
2347
|
+
const effectiveAsync = requestedAsync && effectiveParams.clarify !== true;
|
|
2348
|
+
const controlConfig = resolveControlConfig(deps.config.control, effectiveParams.control);
|
|
2349
|
+
|
|
2350
|
+
const artifactConfig: ArtifactConfig = {
|
|
2351
|
+
...DEFAULT_ARTIFACT_CONFIG,
|
|
2352
|
+
enabled: effectiveParams.artifacts !== false,
|
|
2353
|
+
};
|
|
2354
|
+
const artifactsDir = effectiveAsync ? deps.tempArtifactsDir : getArtifactsDir(parentSessionFile);
|
|
2355
|
+
|
|
2356
|
+
let sessionRoot: string;
|
|
2357
|
+
if (effectiveParams.sessionDir) {
|
|
2358
|
+
sessionRoot = path.resolve(deps.expandTilde(effectiveParams.sessionDir));
|
|
2359
|
+
} else {
|
|
2360
|
+
const baseSessionRoot = deps.config.defaultSessionDir
|
|
2361
|
+
? path.resolve(deps.expandTilde(deps.config.defaultSessionDir))
|
|
2362
|
+
: deps.getSubagentSessionRoot(parentSessionFile);
|
|
2363
|
+
sessionRoot = path.join(baseSessionRoot, runId);
|
|
2364
|
+
}
|
|
2365
|
+
try {
|
|
2366
|
+
fs.mkdirSync(sessionRoot, { recursive: true });
|
|
2367
|
+
} catch (error) {
|
|
2368
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2369
|
+
return toExecutionErrorResult(
|
|
2370
|
+
effectiveParams,
|
|
2371
|
+
new Error(`Failed to create session directory '${sessionRoot}': ${message}`),
|
|
2372
|
+
);
|
|
2373
|
+
}
|
|
2374
|
+
const sessionDirForIndex = (idx?: number) =>
|
|
2375
|
+
path.join(sessionRoot, `run-${idx ?? 0}`);
|
|
2376
|
+
const childSessionFileForIndex = (idx?: number) =>
|
|
2377
|
+
sessionFileForIndex(idx) ?? path.join(sessionDirForIndex(idx), "session.jsonl");
|
|
2378
|
+
|
|
2379
|
+
const onUpdateWithContext = onUpdate
|
|
2380
|
+
? (r: AgentToolResult<Details>) => onUpdate(withForkContext(r, effectiveParams.context))
|
|
2381
|
+
: undefined;
|
|
2382
|
+
|
|
2383
|
+
const execData: ExecutionContextData = {
|
|
2384
|
+
params: effectiveParams,
|
|
2385
|
+
effectiveCwd,
|
|
2386
|
+
ctx,
|
|
2387
|
+
signal,
|
|
2388
|
+
onUpdate: onUpdateWithContext,
|
|
2389
|
+
agents,
|
|
2390
|
+
runId,
|
|
2391
|
+
shareEnabled,
|
|
2392
|
+
sessionRoot,
|
|
2393
|
+
sessionDirForIndex,
|
|
2394
|
+
sessionFileForIndex: childSessionFileForIndex,
|
|
2395
|
+
artifactConfig,
|
|
2396
|
+
artifactsDir,
|
|
2397
|
+
backgroundRequestedWhileClarifying,
|
|
2398
|
+
effectiveAsync,
|
|
2399
|
+
controlConfig,
|
|
2400
|
+
intercomBridge,
|
|
2401
|
+
nestedRoute,
|
|
2402
|
+
};
|
|
2403
|
+
|
|
2404
|
+
const foregroundMode: "single" | "parallel" | "chain" = hasChain ? "chain" : hasTasks ? "parallel" : "single";
|
|
2405
|
+
const foregroundControl = effectiveAsync
|
|
2406
|
+
? undefined
|
|
2407
|
+
: {
|
|
2408
|
+
runId,
|
|
2409
|
+
mode: foregroundMode,
|
|
2410
|
+
startedAt: Date.now(),
|
|
2411
|
+
updatedAt: Date.now(),
|
|
2412
|
+
currentAgent: undefined,
|
|
2413
|
+
currentIndex: undefined,
|
|
2414
|
+
currentActivityState: undefined,
|
|
2415
|
+
nestedRoute,
|
|
2416
|
+
interrupt: undefined,
|
|
2417
|
+
};
|
|
2418
|
+
if (foregroundControl) {
|
|
2419
|
+
deps.state.foregroundControls.set(runId, foregroundControl);
|
|
2420
|
+
deps.state.lastForegroundControlId = runId;
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
const writeNestedForegroundEvent = (type: "subagent.nested.started" | "subagent.nested.completed", result?: AgentToolResult<Details>): void => {
|
|
2424
|
+
if (!inheritedNestedRoute || !nestedParentAddress) return;
|
|
2425
|
+
const now = Date.now();
|
|
2426
|
+
const details = result?.details;
|
|
2427
|
+
const state = type === "subagent.nested.started"
|
|
2428
|
+
? "running"
|
|
2429
|
+
: result?.isError || details?.results.some((child) => child.exitCode !== 0)
|
|
2430
|
+
? "failed"
|
|
2431
|
+
: details?.results.some((child) => child.interrupted)
|
|
2432
|
+
? "paused"
|
|
2433
|
+
: "complete";
|
|
2434
|
+
const errorText = result?.isError
|
|
2435
|
+
? result.content.find((item) => item.type === "text")?.text
|
|
2436
|
+
: undefined;
|
|
2437
|
+
const agentsForSummary = hasTasks && effectiveParams.tasks
|
|
2438
|
+
? effectiveParams.tasks.map((task) => task.agent)
|
|
2439
|
+
: hasChain && effectiveParams.chain
|
|
2440
|
+
? effectiveParams.chain.flatMap((step) => isParallelStep(step) ? step.parallel.map((task) => task.agent) : [(step as SequentialStep).agent])
|
|
2441
|
+
: effectiveParams.agent ? [effectiveParams.agent] : [];
|
|
2442
|
+
const leafIntercomTarget = intercomBridge.active && agentsForSummary[0]
|
|
2443
|
+
? resolveSubagentIntercomTarget(runId, agentsForSummary[0], 0)
|
|
2444
|
+
: undefined;
|
|
2445
|
+
try {
|
|
2446
|
+
writeNestedEvent(inheritedNestedRoute, {
|
|
2447
|
+
type,
|
|
2448
|
+
ts: now,
|
|
2449
|
+
parentRunId: nestedParentAddress.parentRunId,
|
|
2450
|
+
parentStepIndex: nestedParentAddress.parentStepIndex,
|
|
2451
|
+
child: {
|
|
2452
|
+
id: runId,
|
|
2453
|
+
parentRunId: nestedParentAddress.parentRunId,
|
|
2454
|
+
parentStepIndex: nestedParentAddress.parentStepIndex,
|
|
2455
|
+
depth: nestedParentAddress.depth,
|
|
2456
|
+
path: nestedParentAddress.path,
|
|
2457
|
+
ownerIntercomTarget: process.env.PI_SUBAGENT_INTERCOM_SESSION_NAME,
|
|
2458
|
+
leafIntercomTarget,
|
|
2459
|
+
intercomTarget: leafIntercomTarget,
|
|
2460
|
+
ownerState: state === "running" ? "live" : "gone",
|
|
2461
|
+
mode: foregroundMode,
|
|
2462
|
+
state,
|
|
2463
|
+
agent: agentsForSummary[0],
|
|
2464
|
+
agents: agentsForSummary,
|
|
2465
|
+
startedAt: foregroundControl?.startedAt ?? now,
|
|
2466
|
+
...(state !== "running" ? { endedAt: now } : {}),
|
|
2467
|
+
lastUpdate: now,
|
|
2468
|
+
...(errorText ? { error: errorText } : {}),
|
|
2469
|
+
...(details?.results.length ? { steps: details.results.map((child) => ({
|
|
2470
|
+
agent: child.agent,
|
|
2471
|
+
status: child.interrupted ? "paused" : child.exitCode === 0 ? "complete" : "failed",
|
|
2472
|
+
...(child.sessionFile ? { sessionFile: child.sessionFile } : {}),
|
|
2473
|
+
...(child.error ? { error: child.error } : {}),
|
|
2474
|
+
})) } : {}),
|
|
2475
|
+
},
|
|
2476
|
+
});
|
|
2477
|
+
} catch (error) {
|
|
2478
|
+
console.error("Failed to emit nested foreground status event:", error);
|
|
2479
|
+
}
|
|
2480
|
+
};
|
|
2481
|
+
|
|
2482
|
+
let nestedForegroundStarted = false;
|
|
2483
|
+
try {
|
|
2484
|
+
const asyncResult = runAsyncPath(execData, deps);
|
|
2485
|
+
if (asyncResult) return withForkContext(asyncResult, effectiveParams.context);
|
|
2486
|
+
if (foregroundControl) {
|
|
2487
|
+
writeNestedForegroundEvent("subagent.nested.started");
|
|
2488
|
+
nestedForegroundStarted = true;
|
|
2489
|
+
}
|
|
2490
|
+
if (hasChain && effectiveParams.chain) {
|
|
2491
|
+
const result = await runChainPath(execData, deps);
|
|
2492
|
+
writeNestedForegroundEvent("subagent.nested.completed", result);
|
|
2493
|
+
return withForkContext(result, effectiveParams.context);
|
|
2494
|
+
}
|
|
2495
|
+
if (hasTasks && effectiveParams.tasks) {
|
|
2496
|
+
const result = await runParallelPath(execData, deps);
|
|
2497
|
+
writeNestedForegroundEvent("subagent.nested.completed", result);
|
|
2498
|
+
return withForkContext(result, effectiveParams.context);
|
|
2499
|
+
}
|
|
2500
|
+
if (hasSingle) {
|
|
2501
|
+
const result = await runSinglePath(execData, deps);
|
|
2502
|
+
writeNestedForegroundEvent("subagent.nested.completed", result);
|
|
2503
|
+
return withForkContext(result, effectiveParams.context);
|
|
2504
|
+
}
|
|
2505
|
+
} catch (error) {
|
|
2506
|
+
const errorResult = toExecutionErrorResult(effectiveParams, error);
|
|
2507
|
+
if (nestedForegroundStarted) writeNestedForegroundEvent("subagent.nested.completed", errorResult);
|
|
2508
|
+
return errorResult;
|
|
2509
|
+
} finally {
|
|
2510
|
+
if (foregroundControl) {
|
|
2511
|
+
clearPendingForegroundControlNotices(deps.state, runId);
|
|
2512
|
+
deps.state.foregroundControls.delete(runId);
|
|
2513
|
+
if (deps.state.lastForegroundControlId === runId) {
|
|
2514
|
+
deps.state.lastForegroundControlId = null;
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
|
|
2519
|
+
return withForkContext({
|
|
2520
|
+
content: [{ type: "text", text: "Invalid params" }],
|
|
2521
|
+
isError: true,
|
|
2522
|
+
details: { mode: "single" as const, results: [] },
|
|
2523
|
+
}, effectiveParams.context);
|
|
2524
|
+
};
|
|
2525
|
+
|
|
2526
|
+
return { execute };
|
|
2527
|
+
}
|