@leing2021/super-pi 0.22.0 → 0.23.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/README.md +38 -209
- package/extensions/ce-core/index.ts +190 -7
- package/extensions/ce-core/tools/parallel-subagent.ts +3 -3
- package/extensions/ce-core/tools/subagent.ts +2 -2
- package/package.json +4 -18
- package/rules/common/naming.md +154 -0
- package/skills/02-plan/SKILL.md +3 -2
- package/skills/03-work/SKILL.md +9 -6
- package/skills/04-review/SKILL.md +3 -2
- package/skills/06-next/SKILL.md +38 -10
- package/skills/06-next/references/recommendation-logic.md +2 -2
- package/skills/07-worktree/SKILL.md +9 -8
- package/skills/{09-help → 08-help}/SKILL.md +3 -3
- package/skills/{09-help → 08-help}/references/workflow-sequence.md +2 -1
- package/skills/references/language-detection.md +44 -0
- package/extensions/subagent/agent-management.ts +0 -595
- package/extensions/subagent/agent-manager-chain-detail.ts +0 -162
- package/extensions/subagent/agent-manager-detail.ts +0 -231
- package/extensions/subagent/agent-manager-edit.ts +0 -390
- package/extensions/subagent/agent-manager-list.ts +0 -278
- package/extensions/subagent/agent-manager-parallel.ts +0 -304
- package/extensions/subagent/agent-manager.ts +0 -705
- package/extensions/subagent/agent-scope.ts +0 -8
- package/extensions/subagent/agent-selection.ts +0 -25
- package/extensions/subagent/agent-serializer.ts +0 -123
- package/extensions/subagent/agent-templates.ts +0 -62
- package/extensions/subagent/agents/context-builder.md +0 -37
- package/extensions/subagent/agents/delegate.md +0 -9
- package/extensions/subagent/agents/oracle.md +0 -73
- package/extensions/subagent/agents/planner.md +0 -52
- package/extensions/subagent/agents/researcher.md +0 -50
- package/extensions/subagent/agents/reviewer.md +0 -38
- package/extensions/subagent/agents/scout.md +0 -48
- package/extensions/subagent/agents/worker.md +0 -52
- package/extensions/subagent/agents.ts +0 -761
- package/extensions/subagent/artifacts.ts +0 -100
- package/extensions/subagent/async-execution.ts +0 -520
- package/extensions/subagent/async-job-tracker.ts +0 -216
- package/extensions/subagent/async-status.ts +0 -241
- package/extensions/subagent/chain-clarify.ts +0 -1364
- package/extensions/subagent/chain-execution.ts +0 -853
- package/extensions/subagent/chain-serializer.ts +0 -126
- package/extensions/subagent/completion-dedupe.ts +0 -65
- package/extensions/subagent/doctor.ts +0 -200
- package/extensions/subagent/execution.ts +0 -738
- package/extensions/subagent/file-coalescer.ts +0 -42
- package/extensions/subagent/fork-context.ts +0 -63
- package/extensions/subagent/formatters.ts +0 -122
- package/extensions/subagent/frontmatter.ts +0 -31
- package/extensions/subagent/index.ts +0 -585
- package/extensions/subagent/intercom-bridge.ts +0 -240
- package/extensions/subagent/jsonl-writer.ts +0 -83
- package/extensions/subagent/model-fallback.ts +0 -108
- package/extensions/subagent/notify.ts +0 -110
- package/extensions/subagent/parallel-utils.ts +0 -108
- package/extensions/subagent/pi-args.ts +0 -138
- package/extensions/subagent/pi-spawn.ts +0 -100
- package/extensions/subagent/post-exit-stdio-guard.ts +0 -87
- package/extensions/subagent/prompt-template-bridge.ts +0 -399
- package/extensions/subagent/prompts/gather-context-and-clarify.md +0 -13
- package/extensions/subagent/prompts/parallel-cleanup.md +0 -42
- package/extensions/subagent/prompts/parallel-research.md +0 -50
- package/extensions/subagent/prompts/parallel-review.md +0 -40
- package/extensions/subagent/render-helpers.ts +0 -82
- package/extensions/subagent/render.ts +0 -836
- package/extensions/subagent/result-intercom.ts +0 -237
- package/extensions/subagent/result-watcher.ts +0 -171
- package/extensions/subagent/run-history.ts +0 -57
- package/extensions/subagent/run-status.ts +0 -136
- package/extensions/subagent/schemas.ts +0 -164
- package/extensions/subagent/session-tokens.ts +0 -50
- package/extensions/subagent/settings.ts +0 -367
- package/extensions/subagent/single-output.ts +0 -97
- package/extensions/subagent/skills.ts +0 -626
- package/extensions/subagent/slash-bridge.ts +0 -176
- package/extensions/subagent/slash-commands.ts +0 -303
- package/extensions/subagent/slash-live-state.ts +0 -294
- package/extensions/subagent/subagent-control.ts +0 -150
- package/extensions/subagent/subagent-executor.ts +0 -1899
- package/extensions/subagent/subagent-prompt-runtime.ts +0 -75
- package/extensions/subagent/subagent-runner.ts +0 -1470
- package/extensions/subagent/subagents-status.ts +0 -472
- package/extensions/subagent/text-editor.ts +0 -272
- package/extensions/subagent/top-level-async.ts +0 -15
- package/extensions/subagent/types.ts +0 -623
- package/extensions/subagent/utils.ts +0 -456
- package/extensions/subagent/worktree.ts +0 -579
- package/extensions/super-pi-extension/agents/ce-oracle.md +0 -109
- package/extensions/super-pi-extension/agents/ce-planner.md +0 -116
- package/extensions/super-pi-extension/agents/ce-reviewer.md +0 -99
- package/extensions/super-pi-extension/agents/ce-scout.md +0 -80
- package/extensions/super-pi-extension/agents/ce-worker.md +0 -78
- package/extensions/super-pi-extension/chains/ce-parallel-review.chain.md +0 -34
- package/extensions/super-pi-extension/chains/ce-review-only.chain.md +0 -12
- package/extensions/super-pi-extension/chains/ce-standard.chain.md +0 -28
- package/extensions/super-pi-extension/index.ts +0 -129
- package/extensions/super-pi-extension/model-sync.ts +0 -204
- package/skills/08-status/SKILL.md +0 -41
- package/skills/08-status/references/artifact-locations.md +0 -10
- package/skills/10-rules/SKILL.md +0 -76
- package/skills/pi-subagents/SKILL.md +0 -566
|
@@ -1,1899 +0,0 @@
|
|
|
1
|
-
// Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
|
|
2
|
-
// MIT License
|
|
3
|
-
import { randomUUID } from "node:crypto";
|
|
4
|
-
import * as fs from "node:fs";
|
|
5
|
-
import * as path from "node:path";
|
|
6
|
-
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
|
7
|
-
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
8
|
-
import { type AgentConfig, type AgentScope } from "./agents.ts";
|
|
9
|
-
import { getArtifactsDir } from "./artifacts.ts";
|
|
10
|
-
import { ChainClarifyComponent, type ChainClarifyResult, type ModelInfo } from "./chain-clarify.ts";
|
|
11
|
-
import { executeChain } from "./chain-execution.ts";
|
|
12
|
-
import { resolveExecutionAgentScope } from "./agent-scope.ts";
|
|
13
|
-
import { handleManagementAction } from "./agent-management.ts";
|
|
14
|
-
import { buildDoctorReport } from "./doctor.ts";
|
|
15
|
-
import { runSync } from "./execution.ts";
|
|
16
|
-
import { resolveModelCandidate } from "./model-fallback.ts";
|
|
17
|
-
import { aggregateParallelOutputs } from "./parallel-utils.ts";
|
|
18
|
-
import { recordRun } from "./run-history.ts";
|
|
19
|
-
import {
|
|
20
|
-
buildChainInstructions,
|
|
21
|
-
writeInitialProgressFile,
|
|
22
|
-
getStepAgents,
|
|
23
|
-
isParallelStep,
|
|
24
|
-
resolveStepBehavior,
|
|
25
|
-
type ChainStep,
|
|
26
|
-
type ResolvedStepBehavior,
|
|
27
|
-
type SequentialStep,
|
|
28
|
-
type StepOverrides,
|
|
29
|
-
} from "./settings.ts";
|
|
30
|
-
import { discoverAvailableSkills, normalizeSkillInput } from "./skills.ts";
|
|
31
|
-
import { executeAsyncChain, executeAsyncSingle, isAsyncAvailable } from "./async-execution.ts";
|
|
32
|
-
import { createForkContextResolver } from "./fork-context.ts";
|
|
33
|
-
import { applyIntercomBridgeToAgent, INTERCOM_BRIDGE_MARKER, resolveIntercomBridge, resolveIntercomSessionTarget, resolveSubagentIntercomTarget, type IntercomBridgeState } from "./intercom-bridge.ts";
|
|
34
|
-
import { formatControlIntercomMessage, formatControlNoticeMessage, resolveControlConfig, shouldNotifyControlEvent } from "./subagent-control.ts";
|
|
35
|
-
import { finalizeSingleOutput, injectSingleOutputInstruction, resolveSingleOutputPath } from "./single-output.ts";
|
|
36
|
-
import { compactForegroundDetails, getSingleResultOutput, mapConcurrent, readStatus, resolveChildCwd } from "./utils.ts";
|
|
37
|
-
import {
|
|
38
|
-
buildSubagentResultIntercomPayload,
|
|
39
|
-
deliverSubagentResultIntercomEvent,
|
|
40
|
-
formatSubagentResultReceipt,
|
|
41
|
-
resolveSubagentResultStatus,
|
|
42
|
-
stripDetailsOutputsForIntercomReceipt,
|
|
43
|
-
} from "./result-intercom.ts";
|
|
44
|
-
import { inspectSubagentStatus } from "./run-status.ts";
|
|
45
|
-
import { applyForceTopLevelAsyncOverride } from "./top-level-async.ts";
|
|
46
|
-
import {
|
|
47
|
-
cleanupWorktrees,
|
|
48
|
-
createWorktrees,
|
|
49
|
-
diffWorktrees,
|
|
50
|
-
findWorktreeTaskCwdConflict,
|
|
51
|
-
formatWorktreeDiffSummary,
|
|
52
|
-
formatWorktreeTaskCwdConflict,
|
|
53
|
-
type WorktreeSetup,
|
|
54
|
-
} from "./worktree.ts";
|
|
55
|
-
import {
|
|
56
|
-
type AgentProgress,
|
|
57
|
-
type ArtifactConfig,
|
|
58
|
-
type ArtifactPaths,
|
|
59
|
-
type ControlConfig,
|
|
60
|
-
type ControlEvent,
|
|
61
|
-
type Details,
|
|
62
|
-
type ExtensionConfig,
|
|
63
|
-
type IntercomEventBus,
|
|
64
|
-
type MaxOutputConfig,
|
|
65
|
-
type ResolvedControlConfig,
|
|
66
|
-
type SingleResult,
|
|
67
|
-
type SubagentState,
|
|
68
|
-
DEFAULT_ARTIFACT_CONFIG,
|
|
69
|
-
SUBAGENT_CONTROL_EVENT,
|
|
70
|
-
SUBAGENT_CONTROL_INTERCOM_EVENT,
|
|
71
|
-
checkSubagentDepth,
|
|
72
|
-
resolveTopLevelParallelConcurrency,
|
|
73
|
-
resolveTopLevelParallelMaxTasks,
|
|
74
|
-
resolveChildMaxSubagentDepth,
|
|
75
|
-
resolveCurrentMaxSubagentDepth,
|
|
76
|
-
wrapForkTask,
|
|
77
|
-
} from "./types.ts";
|
|
78
|
-
|
|
79
|
-
const ASYNC_INTERRUPT_SIGNAL: NodeJS.Signals = process.platform === "win32" ? "SIGBREAK" : "SIGUSR2";
|
|
80
|
-
|
|
81
|
-
interface TaskParam {
|
|
82
|
-
agent: string;
|
|
83
|
-
task: string;
|
|
84
|
-
cwd?: string;
|
|
85
|
-
count?: number;
|
|
86
|
-
output?: string | boolean;
|
|
87
|
-
reads?: string[] | boolean;
|
|
88
|
-
progress?: boolean;
|
|
89
|
-
model?: string;
|
|
90
|
-
skill?: string | string[] | boolean;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export interface SubagentParamsLike {
|
|
94
|
-
action?: string;
|
|
95
|
-
id?: string;
|
|
96
|
-
runId?: string;
|
|
97
|
-
dir?: string;
|
|
98
|
-
agent?: string;
|
|
99
|
-
task?: string;
|
|
100
|
-
chain?: ChainStep[];
|
|
101
|
-
tasks?: TaskParam[];
|
|
102
|
-
concurrency?: number;
|
|
103
|
-
worktree?: boolean;
|
|
104
|
-
context?: "fresh" | "fork";
|
|
105
|
-
async?: boolean;
|
|
106
|
-
clarify?: boolean;
|
|
107
|
-
share?: boolean;
|
|
108
|
-
control?: ControlConfig;
|
|
109
|
-
sessionDir?: string;
|
|
110
|
-
cwd?: string;
|
|
111
|
-
maxOutput?: MaxOutputConfig;
|
|
112
|
-
artifacts?: boolean;
|
|
113
|
-
includeProgress?: boolean;
|
|
114
|
-
model?: string;
|
|
115
|
-
skill?: string | string[] | boolean;
|
|
116
|
-
output?: string | boolean;
|
|
117
|
-
agentScope?: unknown;
|
|
118
|
-
chainDir?: string;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
interface ExecutorDeps {
|
|
122
|
-
pi: ExtensionAPI;
|
|
123
|
-
state: SubagentState;
|
|
124
|
-
config: ExtensionConfig;
|
|
125
|
-
asyncByDefault: boolean;
|
|
126
|
-
tempArtifactsDir: string;
|
|
127
|
-
getSubagentSessionRoot: (parentSessionFile: string | null) => string;
|
|
128
|
-
expandTilde: (p: string) => string;
|
|
129
|
-
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[] };
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
interface ExecutionContextData {
|
|
133
|
-
params: SubagentParamsLike;
|
|
134
|
-
effectiveCwd: string;
|
|
135
|
-
ctx: ExtensionContext;
|
|
136
|
-
signal: AbortSignal;
|
|
137
|
-
onUpdate?: (r: AgentToolResult<Details>) => void;
|
|
138
|
-
agents: AgentConfig[];
|
|
139
|
-
runId: string;
|
|
140
|
-
shareEnabled: boolean;
|
|
141
|
-
sessionRoot: string;
|
|
142
|
-
sessionDirForIndex: (idx?: number) => string;
|
|
143
|
-
sessionFileForIndex: (idx?: number) => string | undefined;
|
|
144
|
-
artifactConfig: ArtifactConfig;
|
|
145
|
-
artifactsDir: string;
|
|
146
|
-
backgroundRequestedWhileClarifying: boolean;
|
|
147
|
-
effectiveAsync: boolean;
|
|
148
|
-
controlConfig: ResolvedControlConfig;
|
|
149
|
-
intercomBridge: IntercomBridgeState;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function resolveRequestedCwd(runtimeCwd: string, requestedCwd: string | undefined): string {
|
|
153
|
-
return requestedCwd ? path.resolve(runtimeCwd, requestedCwd) : runtimeCwd;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function getForegroundControl(state: SubagentState, runId: string | undefined) {
|
|
157
|
-
if (runId) return state.foregroundControls.get(runId);
|
|
158
|
-
if (state.lastForegroundControlId) {
|
|
159
|
-
const latest = state.foregroundControls.get(state.lastForegroundControlId);
|
|
160
|
-
if (latest) return latest;
|
|
161
|
-
}
|
|
162
|
-
let newest: (SubagentState["foregroundControls"] extends Map<string, infer T> ? T : never) | undefined;
|
|
163
|
-
for (const control of state.foregroundControls.values()) {
|
|
164
|
-
if (!newest || control.updatedAt > newest.updatedAt) newest = control;
|
|
165
|
-
}
|
|
166
|
-
return newest;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function formatForegroundActivity(control: SubagentState["foregroundControls"] extends Map<string, infer T> ? T : never): string | undefined {
|
|
170
|
-
if (control.currentTool && control.currentToolStartedAt) {
|
|
171
|
-
return `tool ${control.currentTool} for ${Math.floor(Math.max(0, Date.now() - control.currentToolStartedAt) / 1000)}s`;
|
|
172
|
-
}
|
|
173
|
-
if (!control.lastActivityAt) return control.currentActivityState === "needs_attention" ? "needs attention" : undefined;
|
|
174
|
-
const seconds = Math.floor(Math.max(0, Date.now() - control.lastActivityAt) / 1000);
|
|
175
|
-
return control.currentActivityState === "needs_attention" ? `no activity for ${seconds}s` : `active ${seconds}s ago`;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function foregroundStatusResult(control: SubagentState["foregroundControls"] extends Map<string, infer T> ? T : never): AgentToolResult<Details> {
|
|
179
|
-
const lines = [
|
|
180
|
-
`Run: ${control.runId}`,
|
|
181
|
-
"State: running",
|
|
182
|
-
`Mode: ${control.mode}`,
|
|
183
|
-
control.currentAgent ? `Current: ${control.currentAgent}${control.currentIndex !== undefined ? ` step ${control.currentIndex + 1}` : ""}` : undefined,
|
|
184
|
-
formatForegroundActivity(control) ? `Activity: ${formatForegroundActivity(control)}` : undefined,
|
|
185
|
-
].filter((line): line is string => Boolean(line));
|
|
186
|
-
return { content: [{ type: "text", text: lines.join("\n") }], details: { mode: "management", results: [] } };
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function getAsyncInterruptTarget(state: SubagentState, runId: string | undefined): { asyncId: string; asyncDir: string } | undefined {
|
|
190
|
-
if (runId) {
|
|
191
|
-
const direct = state.asyncJobs.get(runId);
|
|
192
|
-
if (direct) return { asyncId: direct.asyncId, asyncDir: direct.asyncDir };
|
|
193
|
-
}
|
|
194
|
-
let newest: { asyncId: string; asyncDir: string; updatedAt: number } | undefined;
|
|
195
|
-
for (const job of state.asyncJobs.values()) {
|
|
196
|
-
if (job.status !== "running") continue;
|
|
197
|
-
if (!newest || (job.updatedAt ?? 0) > newest.updatedAt) {
|
|
198
|
-
newest = { asyncId: job.asyncId, asyncDir: job.asyncDir, updatedAt: job.updatedAt ?? 0 };
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
return newest ? { asyncId: newest.asyncId, asyncDir: newest.asyncDir } : undefined;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function emitControlNotification(input: {
|
|
205
|
-
pi: ExtensionAPI;
|
|
206
|
-
controlConfig: ResolvedControlConfig;
|
|
207
|
-
intercomBridge: IntercomBridgeState;
|
|
208
|
-
event: ControlEvent;
|
|
209
|
-
}): void {
|
|
210
|
-
if (!shouldNotifyControlEvent(input.controlConfig, input.event)) return;
|
|
211
|
-
const childIntercomTarget = input.intercomBridge.active
|
|
212
|
-
? resolveSubagentIntercomTarget(input.event.runId, input.event.agent, input.event.index)
|
|
213
|
-
: undefined;
|
|
214
|
-
const payload = {
|
|
215
|
-
event: input.event,
|
|
216
|
-
source: "foreground" as const,
|
|
217
|
-
childIntercomTarget,
|
|
218
|
-
noticeText: formatControlNoticeMessage(input.event, childIntercomTarget),
|
|
219
|
-
};
|
|
220
|
-
if (input.controlConfig.notifyChannels.includes("event")) {
|
|
221
|
-
input.pi.events.emit(SUBAGENT_CONTROL_EVENT, payload);
|
|
222
|
-
}
|
|
223
|
-
if (input.controlConfig.notifyChannels.includes("intercom") && input.intercomBridge.active && input.intercomBridge.orchestratorTarget) {
|
|
224
|
-
input.pi.events.emit(SUBAGENT_CONTROL_INTERCOM_EVENT, {
|
|
225
|
-
...payload,
|
|
226
|
-
to: input.intercomBridge.orchestratorTarget,
|
|
227
|
-
message: formatControlIntercomMessage(input.event, childIntercomTarget),
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
function interruptAsyncRun(state: SubagentState, runId: string | undefined): AgentToolResult<Details> | null {
|
|
233
|
-
const target = getAsyncInterruptTarget(state, runId);
|
|
234
|
-
if (!target) return null;
|
|
235
|
-
const status = readStatus(target.asyncDir);
|
|
236
|
-
if (!status || status.state !== "running" || typeof status.pid !== "number") {
|
|
237
|
-
return {
|
|
238
|
-
content: [{ type: "text", text: `No running async run with an interrupt-capable pid was found for '${runId ?? "current"}'.` }],
|
|
239
|
-
isError: true,
|
|
240
|
-
details: { mode: "management", results: [] },
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
try {
|
|
244
|
-
process.kill(status.pid, ASYNC_INTERRUPT_SIGNAL);
|
|
245
|
-
const tracked = state.asyncJobs.get(target.asyncId);
|
|
246
|
-
if (tracked) {
|
|
247
|
-
tracked.activityState = undefined;
|
|
248
|
-
tracked.updatedAt = Date.now();
|
|
249
|
-
}
|
|
250
|
-
return {
|
|
251
|
-
content: [{ type: "text", text: `Interrupt requested for async run ${target.asyncId}.` }],
|
|
252
|
-
details: { mode: "management", results: [] },
|
|
253
|
-
};
|
|
254
|
-
} catch (error) {
|
|
255
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
256
|
-
return {
|
|
257
|
-
content: [{ type: "text", text: `Failed to interrupt async run ${target.asyncId}: ${message}` }],
|
|
258
|
-
isError: true,
|
|
259
|
-
details: { mode: "management", results: [] },
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
function createForegroundControlNotifier(data: Pick<ExecutionContextData, "controlConfig" | "intercomBridge">, deps: Pick<ExecutorDeps, "pi">): (event: ControlEvent) => void {
|
|
265
|
-
return (event) => emitControlNotification({
|
|
266
|
-
pi: deps.pi,
|
|
267
|
-
controlConfig: data.controlConfig,
|
|
268
|
-
intercomBridge: data.intercomBridge,
|
|
269
|
-
event,
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
async function emitForegroundResultIntercom(input: {
|
|
274
|
-
pi: ExtensionAPI;
|
|
275
|
-
intercomBridge: IntercomBridgeState;
|
|
276
|
-
runId: string;
|
|
277
|
-
mode: "single" | "parallel" | "chain";
|
|
278
|
-
results: SingleResult[];
|
|
279
|
-
chainSteps?: number;
|
|
280
|
-
}): Promise<ReturnType<typeof buildSubagentResultIntercomPayload> | null> {
|
|
281
|
-
if (!input.intercomBridge.active || !input.intercomBridge.orchestratorTarget) return null;
|
|
282
|
-
const children = input.results.flatMap((result, index) => result.detached ? [] : [{
|
|
283
|
-
agent: result.agent,
|
|
284
|
-
status: resolveSubagentResultStatus({
|
|
285
|
-
exitCode: result.exitCode,
|
|
286
|
-
interrupted: result.interrupted,
|
|
287
|
-
detached: result.detached,
|
|
288
|
-
}),
|
|
289
|
-
summary: getSingleResultOutput(result) || result.error || "(no output)",
|
|
290
|
-
index,
|
|
291
|
-
artifactPath: result.artifactPaths?.outputPath,
|
|
292
|
-
sessionPath: result.sessionFile,
|
|
293
|
-
intercomTarget: resolveSubagentIntercomTarget(input.runId, result.agent, index),
|
|
294
|
-
}]);
|
|
295
|
-
if (children.length === 0) return null;
|
|
296
|
-
const payload = buildSubagentResultIntercomPayload({
|
|
297
|
-
to: input.intercomBridge.orchestratorTarget,
|
|
298
|
-
runId: input.runId,
|
|
299
|
-
mode: input.mode,
|
|
300
|
-
source: "foreground",
|
|
301
|
-
children,
|
|
302
|
-
...(typeof input.chainSteps === "number" ? { chainSteps: input.chainSteps } : {}),
|
|
303
|
-
});
|
|
304
|
-
const delivered = await deliverSubagentResultIntercomEvent(input.pi.events, payload);
|
|
305
|
-
if (!delivered) return null;
|
|
306
|
-
return payload;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
async function maybeBuildForegroundIntercomReceipt(input: {
|
|
310
|
-
pi: ExtensionAPI;
|
|
311
|
-
intercomBridge: IntercomBridgeState;
|
|
312
|
-
runId: string;
|
|
313
|
-
mode: "single" | "parallel" | "chain";
|
|
314
|
-
details: Details;
|
|
315
|
-
}): Promise<{ text: string; details: Details } | null> {
|
|
316
|
-
const payload = await emitForegroundResultIntercom({
|
|
317
|
-
pi: input.pi,
|
|
318
|
-
intercomBridge: input.intercomBridge,
|
|
319
|
-
runId: input.runId,
|
|
320
|
-
mode: input.mode,
|
|
321
|
-
results: input.details.results,
|
|
322
|
-
...(typeof input.details.totalSteps === "number" ? { chainSteps: input.details.totalSteps } : {}),
|
|
323
|
-
});
|
|
324
|
-
if (!payload) return null;
|
|
325
|
-
return {
|
|
326
|
-
text: formatSubagentResultReceipt({ mode: input.mode, runId: input.runId, payload }),
|
|
327
|
-
details: stripDetailsOutputsForIntercomReceipt(input.details),
|
|
328
|
-
};
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
function validateExecutionInput(
|
|
332
|
-
params: SubagentParamsLike,
|
|
333
|
-
agents: AgentConfig[],
|
|
334
|
-
hasChain: boolean,
|
|
335
|
-
hasTasks: boolean,
|
|
336
|
-
hasSingle: boolean,
|
|
337
|
-
allowClarifyTaskPrompt: boolean,
|
|
338
|
-
): AgentToolResult<Details> | null {
|
|
339
|
-
if (Number(hasChain) + Number(hasTasks) + Number(hasSingle) !== 1) {
|
|
340
|
-
return {
|
|
341
|
-
content: [
|
|
342
|
-
{
|
|
343
|
-
type: "text",
|
|
344
|
-
text: `Provide exactly one mode. Agents: ${agents.map((a) => a.name).join(", ") || "none"}`,
|
|
345
|
-
},
|
|
346
|
-
],
|
|
347
|
-
isError: true,
|
|
348
|
-
details: { mode: "single" as const, results: [] },
|
|
349
|
-
};
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
if (hasChain && params.chain) {
|
|
353
|
-
if (params.chain.length === 0) {
|
|
354
|
-
return {
|
|
355
|
-
content: [{ type: "text", text: "Chain must have at least one step" }],
|
|
356
|
-
isError: true,
|
|
357
|
-
details: { mode: "chain" as const, results: [] },
|
|
358
|
-
};
|
|
359
|
-
}
|
|
360
|
-
const firstStep = params.chain[0] as ChainStep;
|
|
361
|
-
if (isParallelStep(firstStep)) {
|
|
362
|
-
const missingTaskIndex = firstStep.parallel.findIndex((t) => !t.task);
|
|
363
|
-
if (missingTaskIndex !== -1) {
|
|
364
|
-
return {
|
|
365
|
-
content: [{ type: "text", text: `First parallel step: task ${missingTaskIndex + 1} must have a task (no previous output to reference)` }],
|
|
366
|
-
isError: true,
|
|
367
|
-
details: { mode: "chain" as const, results: [] },
|
|
368
|
-
};
|
|
369
|
-
}
|
|
370
|
-
} else if (!(firstStep as SequentialStep).task && !params.task && !allowClarifyTaskPrompt) {
|
|
371
|
-
return {
|
|
372
|
-
content: [{ type: "text", text: "First step in chain must have a task" }],
|
|
373
|
-
isError: true,
|
|
374
|
-
details: { mode: "chain" as const, results: [] },
|
|
375
|
-
};
|
|
376
|
-
}
|
|
377
|
-
for (let i = 0; i < params.chain.length; i++) {
|
|
378
|
-
const step = params.chain[i] as ChainStep;
|
|
379
|
-
const stepAgents = getStepAgents(step);
|
|
380
|
-
for (const agentName of stepAgents) {
|
|
381
|
-
if (!agents.find((a) => a.name === agentName)) {
|
|
382
|
-
return {
|
|
383
|
-
content: [{ type: "text", text: `Unknown agent: ${agentName} (step ${i + 1})` }],
|
|
384
|
-
isError: true,
|
|
385
|
-
details: { mode: "chain" as const, results: [] },
|
|
386
|
-
};
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
if (isParallelStep(step) && step.parallel.length === 0) {
|
|
390
|
-
return {
|
|
391
|
-
content: [{ type: "text", text: `Parallel step ${i + 1} must have at least one task` }],
|
|
392
|
-
isError: true,
|
|
393
|
-
details: { mode: "chain" as const, results: [] },
|
|
394
|
-
};
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
return null;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
function getRequestedModeLabel(params: SubagentParamsLike): Details["mode"] {
|
|
403
|
-
if ((params.chain?.length ?? 0) > 0) return "chain";
|
|
404
|
-
if ((params.tasks?.length ?? 0) > 0) return "parallel";
|
|
405
|
-
if (params.agent) return "single";
|
|
406
|
-
return "single";
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
function buildRequestedModeError(params: SubagentParamsLike, message: string): AgentToolResult<Details> {
|
|
410
|
-
return withForkContext(
|
|
411
|
-
{
|
|
412
|
-
content: [{ type: "text", text: message }],
|
|
413
|
-
isError: true,
|
|
414
|
-
details: { mode: getRequestedModeLabel(params), results: [] },
|
|
415
|
-
},
|
|
416
|
-
params.context,
|
|
417
|
-
);
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
function expandTopLevelTaskCounts(tasks: TaskParam[]): { tasks?: TaskParam[]; error?: string } {
|
|
421
|
-
const expanded: TaskParam[] = [];
|
|
422
|
-
for (let taskIndex = 0; taskIndex < tasks.length; taskIndex++) {
|
|
423
|
-
const task = tasks[taskIndex]!;
|
|
424
|
-
const rawCount = (task as TaskParam & { count?: unknown }).count;
|
|
425
|
-
if (rawCount !== undefined && (typeof rawCount !== "number" || !Number.isInteger(rawCount) || rawCount < 1)) {
|
|
426
|
-
return { error: `tasks[${taskIndex}].count must be an integer >= 1` };
|
|
427
|
-
}
|
|
428
|
-
const { count, ...concreteTask } = task;
|
|
429
|
-
for (let repeat = 0; repeat < (rawCount ?? 1); repeat++) {
|
|
430
|
-
expanded.push({ ...concreteTask });
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
return { tasks: expanded };
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
function expandChainParallelCounts(chain: ChainStep[]): { chain?: ChainStep[]; error?: string } {
|
|
437
|
-
const expandedChain: ChainStep[] = [];
|
|
438
|
-
for (let stepIndex = 0; stepIndex < chain.length; stepIndex++) {
|
|
439
|
-
const step = chain[stepIndex]!;
|
|
440
|
-
if (!isParallelStep(step)) {
|
|
441
|
-
expandedChain.push(step);
|
|
442
|
-
continue;
|
|
443
|
-
}
|
|
444
|
-
const expandedParallel = [];
|
|
445
|
-
for (let taskIndex = 0; taskIndex < step.parallel.length; taskIndex++) {
|
|
446
|
-
const task = step.parallel[taskIndex]!;
|
|
447
|
-
const rawCount = (task as typeof task & { count?: unknown }).count;
|
|
448
|
-
if (rawCount !== undefined && (typeof rawCount !== "number" || !Number.isInteger(rawCount) || rawCount < 1)) {
|
|
449
|
-
return { error: `chain[${stepIndex}].parallel[${taskIndex}].count must be an integer >= 1` };
|
|
450
|
-
}
|
|
451
|
-
const { count, ...concreteTask } = task;
|
|
452
|
-
for (let repeat = 0; repeat < (rawCount ?? 1); repeat++) {
|
|
453
|
-
expandedParallel.push({ ...concreteTask });
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
expandedChain.push({ ...step, parallel: expandedParallel });
|
|
457
|
-
}
|
|
458
|
-
return { chain: expandedChain };
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
function normalizeRepeatedParallelCounts(params: SubagentParamsLike): { params?: SubagentParamsLike; error?: AgentToolResult<Details> } {
|
|
462
|
-
if (params.tasks) {
|
|
463
|
-
const expandedTasks = expandTopLevelTaskCounts(params.tasks);
|
|
464
|
-
if (expandedTasks.error) {
|
|
465
|
-
return { error: buildRequestedModeError(params, expandedTasks.error) };
|
|
466
|
-
}
|
|
467
|
-
return { params: { ...params, tasks: expandedTasks.tasks } };
|
|
468
|
-
}
|
|
469
|
-
if (params.chain) {
|
|
470
|
-
const expandedChain = expandChainParallelCounts(params.chain);
|
|
471
|
-
if (expandedChain.error) {
|
|
472
|
-
return { error: buildRequestedModeError(params, expandedChain.error) };
|
|
473
|
-
}
|
|
474
|
-
return { params: { ...params, chain: expandedChain.chain } };
|
|
475
|
-
}
|
|
476
|
-
return { params };
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
function withForkContext(
|
|
480
|
-
result: AgentToolResult<Details>,
|
|
481
|
-
context: SubagentParamsLike["context"],
|
|
482
|
-
): AgentToolResult<Details> {
|
|
483
|
-
if (context !== "fork" || !result.details) return result;
|
|
484
|
-
return {
|
|
485
|
-
...result,
|
|
486
|
-
details: {
|
|
487
|
-
...result.details,
|
|
488
|
-
context: "fork",
|
|
489
|
-
},
|
|
490
|
-
};
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
function toExecutionErrorResult(params: SubagentParamsLike, error: unknown): AgentToolResult<Details> {
|
|
494
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
495
|
-
return withForkContext(
|
|
496
|
-
{
|
|
497
|
-
content: [{ type: "text", text: message }],
|
|
498
|
-
isError: true,
|
|
499
|
-
details: { mode: getRequestedModeLabel(params), results: [] },
|
|
500
|
-
},
|
|
501
|
-
params.context,
|
|
502
|
-
);
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
function collectChainSessionFiles(
|
|
506
|
-
chain: ChainStep[],
|
|
507
|
-
sessionFileForIndex: (idx?: number) => string | undefined,
|
|
508
|
-
): (string | undefined)[] {
|
|
509
|
-
const sessionFiles: (string | undefined)[] = [];
|
|
510
|
-
let flatIndex = 0;
|
|
511
|
-
for (const step of chain) {
|
|
512
|
-
if (isParallelStep(step)) {
|
|
513
|
-
for (let i = 0; i < step.parallel.length; i++) {
|
|
514
|
-
sessionFiles.push(sessionFileForIndex(flatIndex));
|
|
515
|
-
flatIndex++;
|
|
516
|
-
}
|
|
517
|
-
continue;
|
|
518
|
-
}
|
|
519
|
-
sessionFiles.push(sessionFileForIndex(flatIndex));
|
|
520
|
-
flatIndex++;
|
|
521
|
-
}
|
|
522
|
-
return sessionFiles;
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
function wrapChainTasksForFork(chain: ChainStep[], context: SubagentParamsLike["context"]): ChainStep[] {
|
|
526
|
-
if (context !== "fork") return chain;
|
|
527
|
-
return chain.map((step, stepIndex) => {
|
|
528
|
-
if (isParallelStep(step)) {
|
|
529
|
-
return {
|
|
530
|
-
...step,
|
|
531
|
-
parallel: step.parallel.map((task) => ({
|
|
532
|
-
...task,
|
|
533
|
-
task: wrapForkTask(task.task ?? "{previous}"),
|
|
534
|
-
})),
|
|
535
|
-
};
|
|
536
|
-
}
|
|
537
|
-
const sequential = step as SequentialStep;
|
|
538
|
-
return {
|
|
539
|
-
...sequential,
|
|
540
|
-
task: wrapForkTask(sequential.task ?? (stepIndex === 0 ? "{task}" : "{previous}")),
|
|
541
|
-
};
|
|
542
|
-
});
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentToolResult<Details> | null {
|
|
546
|
-
const {
|
|
547
|
-
params,
|
|
548
|
-
effectiveCwd,
|
|
549
|
-
agents,
|
|
550
|
-
ctx,
|
|
551
|
-
shareEnabled,
|
|
552
|
-
sessionRoot,
|
|
553
|
-
sessionFileForIndex,
|
|
554
|
-
artifactConfig,
|
|
555
|
-
artifactsDir,
|
|
556
|
-
effectiveAsync,
|
|
557
|
-
controlConfig,
|
|
558
|
-
intercomBridge,
|
|
559
|
-
} = data;
|
|
560
|
-
const hasChain = (params.chain?.length ?? 0) > 0;
|
|
561
|
-
const hasTasks = (params.tasks?.length ?? 0) > 0;
|
|
562
|
-
const hasSingle = !hasChain && !hasTasks && Boolean(params.agent);
|
|
563
|
-
if (!effectiveAsync) return null;
|
|
564
|
-
|
|
565
|
-
if (hasChain && params.chain) {
|
|
566
|
-
const chainWorktreeTaskCwdError = buildChainWorktreeTaskCwdError(params.chain as ChainStep[], effectiveCwd);
|
|
567
|
-
if (chainWorktreeTaskCwdError) {
|
|
568
|
-
return {
|
|
569
|
-
content: [{ type: "text", text: chainWorktreeTaskCwdError }],
|
|
570
|
-
isError: true,
|
|
571
|
-
details: { mode: "chain" as const, results: [] },
|
|
572
|
-
};
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
if (hasTasks && params.tasks) {
|
|
577
|
-
const maxParallelTasks = resolveTopLevelParallelMaxTasks(deps.config.parallel?.maxTasks);
|
|
578
|
-
if (params.tasks.length > maxParallelTasks) {
|
|
579
|
-
return buildParallelModeError(`Max ${maxParallelTasks} tasks`);
|
|
580
|
-
}
|
|
581
|
-
if (params.worktree) {
|
|
582
|
-
const worktreeTaskCwdError = buildParallelWorktreeTaskCwdError(params.tasks, effectiveCwd);
|
|
583
|
-
if (worktreeTaskCwdError) return buildParallelModeError(worktreeTaskCwdError);
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
if (!isAsyncAvailable()) {
|
|
588
|
-
return {
|
|
589
|
-
content: [{ type: "text", text: "Async mode requires jiti for TypeScript execution but it could not be found. Install globally: npm install -g jiti" }],
|
|
590
|
-
isError: true,
|
|
591
|
-
details: { mode: "single" as const, results: [] },
|
|
592
|
-
};
|
|
593
|
-
}
|
|
594
|
-
const id = randomUUID();
|
|
595
|
-
const asyncCtx = {
|
|
596
|
-
pi: deps.pi,
|
|
597
|
-
cwd: ctx.cwd,
|
|
598
|
-
currentSessionId: deps.state.currentSessionId!,
|
|
599
|
-
currentModelProvider: ctx.model?.provider,
|
|
600
|
-
};
|
|
601
|
-
const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map((m) => ({
|
|
602
|
-
provider: m.provider,
|
|
603
|
-
id: m.id,
|
|
604
|
-
fullId: `${m.provider}/${m.id}`,
|
|
605
|
-
}));
|
|
606
|
-
const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
|
|
607
|
-
const currentProvider = ctx.model?.provider;
|
|
608
|
-
const controlIntercomTarget = intercomBridge.active ? intercomBridge.orchestratorTarget : undefined;
|
|
609
|
-
const childIntercomTarget = intercomBridge.active ? (agent: string, index: number) => resolveSubagentIntercomTarget(id, agent, index) : undefined;
|
|
610
|
-
|
|
611
|
-
if (hasTasks && params.tasks) {
|
|
612
|
-
const agentConfigs = params.tasks.map((task) => agents.find((agent) => agent.name === task.agent));
|
|
613
|
-
const modelOverrides = params.tasks.map((task, index) =>
|
|
614
|
-
resolveModelCandidate(task.model ?? agentConfigs[index]?.model, availableModels, currentProvider),
|
|
615
|
-
);
|
|
616
|
-
const skillOverrides = params.tasks.map((task) => normalizeSkillInput(task.skill));
|
|
617
|
-
const parallelTasks = params.tasks.map((task, index) => ({
|
|
618
|
-
agent: task.agent,
|
|
619
|
-
task: params.context === "fork" ? wrapForkTask(task.task) : task.task,
|
|
620
|
-
cwd: task.cwd,
|
|
621
|
-
...(modelOverrides[index] ? { model: modelOverrides[index] } : {}),
|
|
622
|
-
...(skillOverrides[index] !== undefined ? { skill: skillOverrides[index] } : {}),
|
|
623
|
-
...(task.output === true ? (agentConfigs[index]?.output ? { output: agentConfigs[index]!.output } : {}) : task.output !== undefined ? { output: task.output } : {}),
|
|
624
|
-
...(task.reads !== undefined && task.reads !== true ? { reads: task.reads } : {}),
|
|
625
|
-
...(task.progress !== undefined ? { progress: task.progress } : {}),
|
|
626
|
-
}));
|
|
627
|
-
return executeAsyncChain(id, {
|
|
628
|
-
chain: [{
|
|
629
|
-
parallel: parallelTasks,
|
|
630
|
-
concurrency: resolveTopLevelParallelConcurrency(params.concurrency, deps.config.parallel?.concurrency),
|
|
631
|
-
worktree: params.worktree,
|
|
632
|
-
}],
|
|
633
|
-
resultMode: "parallel",
|
|
634
|
-
agents,
|
|
635
|
-
ctx: asyncCtx,
|
|
636
|
-
availableModels,
|
|
637
|
-
cwd: effectiveCwd,
|
|
638
|
-
maxOutput: params.maxOutput,
|
|
639
|
-
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
640
|
-
artifactConfig,
|
|
641
|
-
shareEnabled,
|
|
642
|
-
sessionRoot,
|
|
643
|
-
chainSkills: [],
|
|
644
|
-
sessionFilesByFlatIndex: params.tasks.map((_, index) => sessionFileForIndex(index)),
|
|
645
|
-
maxSubagentDepth: currentMaxSubagentDepth,
|
|
646
|
-
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
647
|
-
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
648
|
-
controlConfig,
|
|
649
|
-
controlIntercomTarget,
|
|
650
|
-
childIntercomTarget,
|
|
651
|
-
});
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
if (hasChain && params.chain) {
|
|
655
|
-
const normalized = normalizeSkillInput(params.skill);
|
|
656
|
-
const chainSkills = normalized === false ? [] : (normalized ?? []);
|
|
657
|
-
const chain = wrapChainTasksForFork(params.chain as ChainStep[], params.context);
|
|
658
|
-
return executeAsyncChain(id, {
|
|
659
|
-
chain,
|
|
660
|
-
agents,
|
|
661
|
-
ctx: asyncCtx,
|
|
662
|
-
availableModels,
|
|
663
|
-
cwd: effectiveCwd,
|
|
664
|
-
maxOutput: params.maxOutput,
|
|
665
|
-
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
666
|
-
artifactConfig,
|
|
667
|
-
shareEnabled,
|
|
668
|
-
sessionRoot,
|
|
669
|
-
chainSkills,
|
|
670
|
-
sessionFilesByFlatIndex: collectChainSessionFiles(chain, sessionFileForIndex),
|
|
671
|
-
maxSubagentDepth: currentMaxSubagentDepth,
|
|
672
|
-
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
673
|
-
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
674
|
-
controlConfig,
|
|
675
|
-
controlIntercomTarget,
|
|
676
|
-
childIntercomTarget,
|
|
677
|
-
});
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
if (hasSingle) {
|
|
681
|
-
const a = agents.find((x) => x.name === params.agent);
|
|
682
|
-
if (!a) {
|
|
683
|
-
return {
|
|
684
|
-
content: [{ type: "text", text: `Unknown agent: ${params.agent}` }],
|
|
685
|
-
isError: true,
|
|
686
|
-
details: { mode: "single" as const, results: [] },
|
|
687
|
-
};
|
|
688
|
-
}
|
|
689
|
-
const rawOutput = params.output !== undefined ? params.output : a.output;
|
|
690
|
-
const effectiveOutput: string | false | undefined = rawOutput === true ? a.output : (rawOutput as string | false | undefined);
|
|
691
|
-
const normalizedSkills = normalizeSkillInput(params.skill);
|
|
692
|
-
const skills = normalizedSkills === false ? [] : normalizedSkills;
|
|
693
|
-
const maxSubagentDepth = resolveChildMaxSubagentDepth(currentMaxSubagentDepth, a.maxSubagentDepth);
|
|
694
|
-
const modelOverride = resolveModelCandidate((params.model as string | undefined) ?? a.model, availableModels, currentProvider);
|
|
695
|
-
return executeAsyncSingle(id, {
|
|
696
|
-
agent: params.agent!,
|
|
697
|
-
task: params.context === "fork" ? wrapForkTask(params.task ?? "") : (params.task ?? ""),
|
|
698
|
-
agentConfig: a,
|
|
699
|
-
ctx: asyncCtx,
|
|
700
|
-
availableModels,
|
|
701
|
-
cwd: effectiveCwd,
|
|
702
|
-
maxOutput: params.maxOutput,
|
|
703
|
-
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
704
|
-
artifactConfig,
|
|
705
|
-
shareEnabled,
|
|
706
|
-
sessionRoot,
|
|
707
|
-
sessionFile: sessionFileForIndex(0),
|
|
708
|
-
skills,
|
|
709
|
-
output: effectiveOutput,
|
|
710
|
-
modelOverride,
|
|
711
|
-
maxSubagentDepth,
|
|
712
|
-
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
713
|
-
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
714
|
-
controlConfig,
|
|
715
|
-
controlIntercomTarget,
|
|
716
|
-
childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(agent, index) : undefined,
|
|
717
|
-
});
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
return null;
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Promise<AgentToolResult<Details>> {
|
|
724
|
-
const {
|
|
725
|
-
params,
|
|
726
|
-
effectiveCwd,
|
|
727
|
-
agents,
|
|
728
|
-
ctx,
|
|
729
|
-
signal,
|
|
730
|
-
runId,
|
|
731
|
-
shareEnabled,
|
|
732
|
-
sessionDirForIndex,
|
|
733
|
-
sessionFileForIndex,
|
|
734
|
-
artifactsDir,
|
|
735
|
-
artifactConfig,
|
|
736
|
-
onUpdate,
|
|
737
|
-
sessionRoot,
|
|
738
|
-
controlConfig,
|
|
739
|
-
} = data;
|
|
740
|
-
const onControlEvent = createForegroundControlNotifier(data, deps);
|
|
741
|
-
const childIntercomTarget = data.intercomBridge.active ? resolveSubagentIntercomTarget : undefined;
|
|
742
|
-
const foregroundControl = deps.state.foregroundControls.get(runId);
|
|
743
|
-
const normalized = normalizeSkillInput(params.skill);
|
|
744
|
-
const chainSkills = normalized === false ? [] : (normalized ?? []);
|
|
745
|
-
const chain = wrapChainTasksForFork(params.chain as ChainStep[], params.context);
|
|
746
|
-
const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
|
|
747
|
-
const chainResult = await executeChain({
|
|
748
|
-
chain,
|
|
749
|
-
task: params.task,
|
|
750
|
-
agents,
|
|
751
|
-
ctx,
|
|
752
|
-
intercomEvents: deps.pi.events,
|
|
753
|
-
signal,
|
|
754
|
-
runId,
|
|
755
|
-
cwd: effectiveCwd,
|
|
756
|
-
shareEnabled,
|
|
757
|
-
sessionDirForIndex,
|
|
758
|
-
sessionFileForIndex,
|
|
759
|
-
artifactsDir,
|
|
760
|
-
artifactConfig,
|
|
761
|
-
includeProgress: params.includeProgress,
|
|
762
|
-
clarify: params.clarify,
|
|
763
|
-
onUpdate,
|
|
764
|
-
onControlEvent,
|
|
765
|
-
controlConfig,
|
|
766
|
-
childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(runId, agent, index) : undefined,
|
|
767
|
-
foregroundControl,
|
|
768
|
-
chainSkills,
|
|
769
|
-
chainDir: params.chainDir,
|
|
770
|
-
maxSubagentDepth: currentMaxSubagentDepth,
|
|
771
|
-
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
772
|
-
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
773
|
-
});
|
|
774
|
-
|
|
775
|
-
if (chainResult.requestedAsync) {
|
|
776
|
-
if (!isAsyncAvailable()) {
|
|
777
|
-
return {
|
|
778
|
-
content: [{ type: "text", text: "Background mode requires jiti for TypeScript execution but it could not be found." }],
|
|
779
|
-
isError: true,
|
|
780
|
-
details: { mode: "chain" as const, results: [] },
|
|
781
|
-
};
|
|
782
|
-
}
|
|
783
|
-
const id = randomUUID();
|
|
784
|
-
const asyncCtx = {
|
|
785
|
-
pi: deps.pi,
|
|
786
|
-
cwd: ctx.cwd,
|
|
787
|
-
currentSessionId: deps.state.currentSessionId!,
|
|
788
|
-
currentModelProvider: ctx.model?.provider,
|
|
789
|
-
};
|
|
790
|
-
const asyncChain = wrapChainTasksForFork(chainResult.requestedAsync.chain, params.context);
|
|
791
|
-
return executeAsyncChain(id, {
|
|
792
|
-
chain: asyncChain,
|
|
793
|
-
agents,
|
|
794
|
-
ctx: asyncCtx,
|
|
795
|
-
availableModels: ctx.modelRegistry.getAvailable().map((m) => ({
|
|
796
|
-
provider: m.provider,
|
|
797
|
-
id: m.id,
|
|
798
|
-
fullId: `${m.provider}/${m.id}`,
|
|
799
|
-
})),
|
|
800
|
-
cwd: effectiveCwd,
|
|
801
|
-
maxOutput: params.maxOutput,
|
|
802
|
-
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
803
|
-
artifactConfig,
|
|
804
|
-
shareEnabled,
|
|
805
|
-
sessionRoot,
|
|
806
|
-
chainSkills: chainResult.requestedAsync.chainSkills,
|
|
807
|
-
sessionFilesByFlatIndex: collectChainSessionFiles(asyncChain, sessionFileForIndex),
|
|
808
|
-
maxSubagentDepth: currentMaxSubagentDepth,
|
|
809
|
-
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
810
|
-
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
811
|
-
controlConfig,
|
|
812
|
-
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
813
|
-
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
|
|
814
|
-
});
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
const chainDetails = chainResult.details ? compactForegroundDetails(chainResult.details) : undefined;
|
|
818
|
-
const intercomReceipt = chainDetails && !chainDetails.results.some((result) => result.interrupted)
|
|
819
|
-
? await maybeBuildForegroundIntercomReceipt({
|
|
820
|
-
pi: deps.pi,
|
|
821
|
-
intercomBridge: data.intercomBridge,
|
|
822
|
-
runId,
|
|
823
|
-
mode: "chain",
|
|
824
|
-
details: chainDetails,
|
|
825
|
-
})
|
|
826
|
-
: null;
|
|
827
|
-
if (intercomReceipt) {
|
|
828
|
-
return {
|
|
829
|
-
...chainResult,
|
|
830
|
-
content: [{ type: "text", text: intercomReceipt.text }],
|
|
831
|
-
details: intercomReceipt.details,
|
|
832
|
-
};
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
return chainResult;
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
interface ForegroundParallelRunInput {
|
|
839
|
-
tasks: TaskParam[];
|
|
840
|
-
taskTexts: string[];
|
|
841
|
-
agents: AgentConfig[];
|
|
842
|
-
ctx: ExtensionContext;
|
|
843
|
-
intercomEvents: IntercomEventBus;
|
|
844
|
-
signal: AbortSignal;
|
|
845
|
-
runId: string;
|
|
846
|
-
sessionDirForIndex: (idx?: number) => string | undefined;
|
|
847
|
-
sessionFileForIndex: (idx?: number) => string | undefined;
|
|
848
|
-
shareEnabled: boolean;
|
|
849
|
-
artifactConfig: ArtifactConfig;
|
|
850
|
-
artifactsDir: string;
|
|
851
|
-
maxOutput?: MaxOutputConfig;
|
|
852
|
-
paramsCwd: string;
|
|
853
|
-
maxSubagentDepths: number[];
|
|
854
|
-
availableModels: ModelInfo[];
|
|
855
|
-
modelOverrides: (string | undefined)[];
|
|
856
|
-
behaviors: Array<ReturnType<typeof resolveStepBehavior>>;
|
|
857
|
-
firstProgressIndex: number;
|
|
858
|
-
controlConfig: ResolvedControlConfig;
|
|
859
|
-
onControlEvent?: (event: ControlEvent) => void;
|
|
860
|
-
childIntercomTarget?: (agent: string, index: number) => string | undefined;
|
|
861
|
-
foregroundControl?: SubagentState["foregroundControls"] extends Map<string, infer T> ? T : never;
|
|
862
|
-
concurrencyLimit: number;
|
|
863
|
-
liveResults: (SingleResult | undefined)[];
|
|
864
|
-
liveProgress: (AgentProgress | undefined)[];
|
|
865
|
-
onUpdate?: (r: AgentToolResult<Details>) => void;
|
|
866
|
-
worktreeSetup?: WorktreeSetup;
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
function buildParallelModeError(message: string): AgentToolResult<Details> {
|
|
870
|
-
return {
|
|
871
|
-
content: [{ type: "text", text: message }],
|
|
872
|
-
isError: true,
|
|
873
|
-
details: { mode: "parallel" as const, results: [] },
|
|
874
|
-
};
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
function createParallelWorktreeSetup(
|
|
878
|
-
enabled: boolean | undefined,
|
|
879
|
-
cwd: string,
|
|
880
|
-
runId: string,
|
|
881
|
-
tasks: TaskParam[],
|
|
882
|
-
setupHook: ExtensionConfig["worktreeSetupHook"],
|
|
883
|
-
setupHookTimeoutMs: ExtensionConfig["worktreeSetupHookTimeoutMs"],
|
|
884
|
-
): { setup?: WorktreeSetup; errorResult?: AgentToolResult<Details> } {
|
|
885
|
-
if (!enabled) return {};
|
|
886
|
-
try {
|
|
887
|
-
return {
|
|
888
|
-
setup: createWorktrees(cwd, runId, tasks.length, {
|
|
889
|
-
agents: tasks.map((task) => task.agent),
|
|
890
|
-
setupHook: setupHook
|
|
891
|
-
? { hookPath: setupHook, timeoutMs: setupHookTimeoutMs }
|
|
892
|
-
: undefined,
|
|
893
|
-
}),
|
|
894
|
-
};
|
|
895
|
-
} catch (error) {
|
|
896
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
897
|
-
return { errorResult: buildParallelModeError(message) };
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
function buildParallelWorktreeTaskCwdError(
|
|
902
|
-
tasks: ReadonlyArray<{ agent: string; cwd?: string }>,
|
|
903
|
-
sharedCwd: string,
|
|
904
|
-
): string | undefined {
|
|
905
|
-
const conflict = findWorktreeTaskCwdConflict(tasks, sharedCwd);
|
|
906
|
-
if (!conflict) return undefined;
|
|
907
|
-
return formatWorktreeTaskCwdConflict(conflict, sharedCwd);
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
function buildChainWorktreeTaskCwdError(chain: ChainStep[], sharedCwd: string): string | undefined {
|
|
911
|
-
for (let stepIndex = 0; stepIndex < chain.length; stepIndex++) {
|
|
912
|
-
const step = chain[stepIndex]!;
|
|
913
|
-
if (!isParallelStep(step) || !step.worktree) continue;
|
|
914
|
-
const stepCwd = resolveChildCwd(sharedCwd, step.cwd);
|
|
915
|
-
const conflict = findWorktreeTaskCwdConflict(step.parallel, stepCwd);
|
|
916
|
-
if (!conflict) continue;
|
|
917
|
-
const detail = formatWorktreeTaskCwdConflict(conflict, stepCwd);
|
|
918
|
-
return `parallel chain step ${stepIndex + 1}: ${detail}`;
|
|
919
|
-
}
|
|
920
|
-
return undefined;
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
function resolveParallelTaskCwd(
|
|
924
|
-
task: TaskParam,
|
|
925
|
-
paramsCwd: string,
|
|
926
|
-
worktreeSetup: WorktreeSetup | undefined,
|
|
927
|
-
index: number,
|
|
928
|
-
): string {
|
|
929
|
-
if (worktreeSetup) return worktreeSetup.worktrees[index]!.agentCwd;
|
|
930
|
-
return resolveChildCwd(paramsCwd, task.cwd);
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
function buildParallelWorktreeSuffix(
|
|
934
|
-
worktreeSetup: WorktreeSetup | undefined,
|
|
935
|
-
artifactsDir: string,
|
|
936
|
-
tasks: TaskParam[],
|
|
937
|
-
): string {
|
|
938
|
-
if (!worktreeSetup) return "";
|
|
939
|
-
const diffsDir = path.join(artifactsDir, "worktree-diffs");
|
|
940
|
-
const diffs = diffWorktrees(worktreeSetup, tasks.map((task) => task.agent), diffsDir);
|
|
941
|
-
return formatWorktreeDiffSummary(diffs);
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
function findDuplicateParallelOutputPath(input: {
|
|
945
|
-
tasks: TaskParam[];
|
|
946
|
-
behaviors: ResolvedStepBehavior[];
|
|
947
|
-
paramsCwd: string;
|
|
948
|
-
ctxCwd: string;
|
|
949
|
-
worktreeSetup?: WorktreeSetup;
|
|
950
|
-
}): string | undefined {
|
|
951
|
-
const seen = new Map<string, { index: number; agent: string }>();
|
|
952
|
-
for (let index = 0; index < input.tasks.length; index++) {
|
|
953
|
-
const behavior = input.behaviors[index];
|
|
954
|
-
if (!behavior?.output) continue;
|
|
955
|
-
const task = input.tasks[index]!;
|
|
956
|
-
const taskCwd = resolveParallelTaskCwd(task, input.paramsCwd, input.worktreeSetup, index);
|
|
957
|
-
const outputPath = resolveSingleOutputPath(behavior.output, input.ctxCwd, taskCwd);
|
|
958
|
-
if (!outputPath) continue;
|
|
959
|
-
const previous = seen.get(outputPath);
|
|
960
|
-
if (previous) {
|
|
961
|
-
return `Parallel tasks ${previous.index + 1} (${previous.agent}) and ${index + 1} (${task.agent}) resolve output to the same path: ${outputPath}. Use distinct output paths.`;
|
|
962
|
-
}
|
|
963
|
-
seen.set(outputPath, { index, agent: task.agent });
|
|
964
|
-
}
|
|
965
|
-
return undefined;
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Promise<SingleResult[]> {
|
|
969
|
-
return mapConcurrent(input.tasks, input.concurrencyLimit, async (task, index) => {
|
|
970
|
-
const behavior = input.behaviors[index];
|
|
971
|
-
const effectiveSkills = behavior?.skills;
|
|
972
|
-
const taskCwd = resolveParallelTaskCwd(task, input.paramsCwd, input.worktreeSetup, index);
|
|
973
|
-
const readInstructions = behavior
|
|
974
|
-
? buildChainInstructions({ ...behavior, output: false, progress: false }, taskCwd, false)
|
|
975
|
-
: { prefix: "", suffix: "" };
|
|
976
|
-
const progressInstructions = behavior
|
|
977
|
-
? buildChainInstructions({ ...behavior, output: false, reads: false }, input.paramsCwd, index === input.firstProgressIndex)
|
|
978
|
-
: { prefix: "", suffix: "" };
|
|
979
|
-
const outputPath = resolveSingleOutputPath(behavior?.output, input.ctx.cwd, taskCwd);
|
|
980
|
-
const taskText = injectSingleOutputInstruction(
|
|
981
|
-
`${readInstructions.prefix}${input.taskTexts[index]!}${progressInstructions.suffix}`,
|
|
982
|
-
outputPath,
|
|
983
|
-
);
|
|
984
|
-
const interruptController = new AbortController();
|
|
985
|
-
if (input.foregroundControl) {
|
|
986
|
-
input.foregroundControl.currentAgent = task.agent;
|
|
987
|
-
input.foregroundControl.currentIndex = index;
|
|
988
|
-
input.foregroundControl.currentActivityState = undefined;
|
|
989
|
-
input.foregroundControl.updatedAt = Date.now();
|
|
990
|
-
input.foregroundControl.interrupt = () => {
|
|
991
|
-
if (interruptController.signal.aborted) return false;
|
|
992
|
-
interruptController.abort();
|
|
993
|
-
input.foregroundControl!.currentActivityState = undefined;
|
|
994
|
-
input.foregroundControl!.updatedAt = Date.now();
|
|
995
|
-
return true;
|
|
996
|
-
};
|
|
997
|
-
}
|
|
998
|
-
const agentConfig = input.agents.find((agent) => agent.name === task.agent);
|
|
999
|
-
return runSync(input.ctx.cwd, input.agents, task.agent, taskText, {
|
|
1000
|
-
cwd: taskCwd,
|
|
1001
|
-
signal: input.signal,
|
|
1002
|
-
interruptSignal: interruptController.signal,
|
|
1003
|
-
allowIntercomDetach: agentConfig?.systemPrompt?.includes(INTERCOM_BRIDGE_MARKER) === true,
|
|
1004
|
-
intercomEvents: input.intercomEvents,
|
|
1005
|
-
runId: input.runId,
|
|
1006
|
-
index,
|
|
1007
|
-
sessionDir: input.sessionDirForIndex(index),
|
|
1008
|
-
sessionFile: input.sessionFileForIndex(index),
|
|
1009
|
-
share: input.shareEnabled,
|
|
1010
|
-
artifactsDir: input.artifactConfig.enabled ? input.artifactsDir : undefined,
|
|
1011
|
-
artifactConfig: input.artifactConfig,
|
|
1012
|
-
maxOutput: input.maxOutput,
|
|
1013
|
-
outputPath,
|
|
1014
|
-
maxSubagentDepth: input.maxSubagentDepths[index],
|
|
1015
|
-
controlConfig: input.controlConfig,
|
|
1016
|
-
onControlEvent: input.onControlEvent,
|
|
1017
|
-
intercomSessionName: input.childIntercomTarget?.(task.agent, index),
|
|
1018
|
-
modelOverride: input.modelOverrides[index],
|
|
1019
|
-
availableModels: input.availableModels,
|
|
1020
|
-
preferredModelProvider: input.ctx.model?.provider,
|
|
1021
|
-
skills: effectiveSkills === false ? [] : effectiveSkills,
|
|
1022
|
-
onUpdate: input.onUpdate
|
|
1023
|
-
? (progressUpdate) => {
|
|
1024
|
-
const stepResults = progressUpdate.details?.results || [];
|
|
1025
|
-
const stepProgress = progressUpdate.details?.progress || [];
|
|
1026
|
-
if (input.foregroundControl && stepProgress.length > 0) {
|
|
1027
|
-
const current = stepProgress[0];
|
|
1028
|
-
input.foregroundControl.currentAgent = task.agent;
|
|
1029
|
-
input.foregroundControl.currentIndex = index;
|
|
1030
|
-
input.foregroundControl.currentActivityState = current?.activityState;
|
|
1031
|
-
input.foregroundControl.lastActivityAt = current?.lastActivityAt;
|
|
1032
|
-
input.foregroundControl.currentTool = current?.currentTool;
|
|
1033
|
-
input.foregroundControl.currentToolStartedAt = current?.currentToolStartedAt;
|
|
1034
|
-
input.foregroundControl.updatedAt = Date.now();
|
|
1035
|
-
}
|
|
1036
|
-
if (stepResults.length > 0) input.liveResults[index] = stepResults[0];
|
|
1037
|
-
if (stepProgress.length > 0) input.liveProgress[index] = stepProgress[0];
|
|
1038
|
-
const mergedResults = input.liveResults.filter((result): result is SingleResult => result !== undefined);
|
|
1039
|
-
const mergedProgress = input.liveProgress.filter((progress): progress is AgentProgress => progress !== undefined);
|
|
1040
|
-
input.onUpdate?.({
|
|
1041
|
-
content: progressUpdate.content,
|
|
1042
|
-
details: {
|
|
1043
|
-
mode: "parallel",
|
|
1044
|
-
results: mergedResults,
|
|
1045
|
-
progress: mergedProgress,
|
|
1046
|
-
controlEvents: progressUpdate.details?.controlEvents,
|
|
1047
|
-
totalSteps: input.tasks.length,
|
|
1048
|
-
},
|
|
1049
|
-
});
|
|
1050
|
-
}
|
|
1051
|
-
: undefined,
|
|
1052
|
-
}).finally(() => {
|
|
1053
|
-
if (input.foregroundControl?.currentIndex === index) {
|
|
1054
|
-
input.foregroundControl.interrupt = undefined;
|
|
1055
|
-
input.foregroundControl.updatedAt = Date.now();
|
|
1056
|
-
}
|
|
1057
|
-
});
|
|
1058
|
-
});
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps): Promise<AgentToolResult<Details>> {
|
|
1062
|
-
const {
|
|
1063
|
-
params,
|
|
1064
|
-
effectiveCwd,
|
|
1065
|
-
agents,
|
|
1066
|
-
ctx,
|
|
1067
|
-
signal,
|
|
1068
|
-
runId,
|
|
1069
|
-
sessionDirForIndex,
|
|
1070
|
-
sessionFileForIndex,
|
|
1071
|
-
shareEnabled,
|
|
1072
|
-
artifactConfig,
|
|
1073
|
-
artifactsDir,
|
|
1074
|
-
backgroundRequestedWhileClarifying,
|
|
1075
|
-
onUpdate,
|
|
1076
|
-
sessionRoot,
|
|
1077
|
-
controlConfig,
|
|
1078
|
-
} = data;
|
|
1079
|
-
const onControlEvent = createForegroundControlNotifier(data, deps);
|
|
1080
|
-
const childIntercomTarget = data.intercomBridge.active ? resolveSubagentIntercomTarget : undefined;
|
|
1081
|
-
const allProgress: AgentProgress[] = [];
|
|
1082
|
-
const allArtifactPaths: ArtifactPaths[] = [];
|
|
1083
|
-
const tasks = params.tasks!;
|
|
1084
|
-
const maxParallelTasks = resolveTopLevelParallelMaxTasks(deps.config.parallel?.maxTasks);
|
|
1085
|
-
const parallelConcurrency = resolveTopLevelParallelConcurrency(params.concurrency, deps.config.parallel?.concurrency);
|
|
1086
|
-
|
|
1087
|
-
if (tasks.length > maxParallelTasks)
|
|
1088
|
-
return {
|
|
1089
|
-
content: [{ type: "text", text: `Max ${maxParallelTasks} tasks` }],
|
|
1090
|
-
isError: true,
|
|
1091
|
-
details: { mode: "parallel" as const, results: [] },
|
|
1092
|
-
};
|
|
1093
|
-
|
|
1094
|
-
const agentConfigs: AgentConfig[] = [];
|
|
1095
|
-
for (const t of tasks) {
|
|
1096
|
-
const config = agents.find((a) => a.name === t.agent);
|
|
1097
|
-
if (!config) {
|
|
1098
|
-
return {
|
|
1099
|
-
content: [{ type: "text", text: `Unknown agent: ${t.agent}` }],
|
|
1100
|
-
isError: true,
|
|
1101
|
-
details: { mode: "parallel" as const, results: [] },
|
|
1102
|
-
};
|
|
1103
|
-
}
|
|
1104
|
-
agentConfigs.push(config);
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
|
|
1108
|
-
const maxSubagentDepths = agentConfigs.map((config) =>
|
|
1109
|
-
resolveChildMaxSubagentDepth(currentMaxSubagentDepth, config.maxSubagentDepth),
|
|
1110
|
-
);
|
|
1111
|
-
|
|
1112
|
-
if (params.worktree) {
|
|
1113
|
-
const worktreeTaskCwdError = buildParallelWorktreeTaskCwdError(tasks, effectiveCwd);
|
|
1114
|
-
if (worktreeTaskCwdError) return buildParallelModeError(worktreeTaskCwdError);
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
const currentProvider = ctx.model?.provider;
|
|
1118
|
-
const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map((m) => ({
|
|
1119
|
-
provider: m.provider,
|
|
1120
|
-
id: m.id,
|
|
1121
|
-
fullId: `${m.provider}/${m.id}`,
|
|
1122
|
-
}));
|
|
1123
|
-
let taskTexts = tasks.map((t) => t.task);
|
|
1124
|
-
const skillOverrides: (string[] | false | undefined)[] = tasks.map((t) =>
|
|
1125
|
-
normalizeSkillInput(t.skill),
|
|
1126
|
-
);
|
|
1127
|
-
const behaviorOverrides: StepOverrides[] = tasks.map((task, index) => ({
|
|
1128
|
-
...(task.output !== undefined ? { output: task.output === true ? agentConfigs[index]?.output ?? false : task.output } : {}),
|
|
1129
|
-
...(task.reads !== undefined && task.reads !== true ? { reads: task.reads } : {}),
|
|
1130
|
-
...(task.progress !== undefined ? { progress: task.progress } : {}),
|
|
1131
|
-
...(skillOverrides[index] !== undefined ? { skills: skillOverrides[index] } : {}),
|
|
1132
|
-
...(task.model ? { model: task.model } : {}),
|
|
1133
|
-
}));
|
|
1134
|
-
const modelOverrides: (string | undefined)[] = tasks.map((_, i) =>
|
|
1135
|
-
resolveModelCandidate(behaviorOverrides[i]?.model ?? agentConfigs[i]?.model, availableModels, currentProvider),
|
|
1136
|
-
);
|
|
1137
|
-
|
|
1138
|
-
if (params.clarify === true && ctx.hasUI) {
|
|
1139
|
-
const behaviors = agentConfigs.map((c, i) =>
|
|
1140
|
-
resolveStepBehavior(c, behaviorOverrides[i]!),
|
|
1141
|
-
);
|
|
1142
|
-
const availableSkills = discoverAvailableSkills(effectiveCwd);
|
|
1143
|
-
|
|
1144
|
-
const result = await ctx.ui.custom<ChainClarifyResult>(
|
|
1145
|
-
(tui, theme, _kb, done) =>
|
|
1146
|
-
new ChainClarifyComponent(
|
|
1147
|
-
tui, theme,
|
|
1148
|
-
agentConfigs,
|
|
1149
|
-
taskTexts,
|
|
1150
|
-
"",
|
|
1151
|
-
undefined,
|
|
1152
|
-
behaviors,
|
|
1153
|
-
availableModels,
|
|
1154
|
-
currentProvider,
|
|
1155
|
-
availableSkills,
|
|
1156
|
-
done,
|
|
1157
|
-
"parallel",
|
|
1158
|
-
),
|
|
1159
|
-
{ overlay: true, overlayOptions: { anchor: "center", width: 84, maxHeight: "80%" } },
|
|
1160
|
-
);
|
|
1161
|
-
|
|
1162
|
-
if (!result || !result.confirmed) {
|
|
1163
|
-
return { content: [{ type: "text", text: "Cancelled" }], details: { mode: "parallel", results: [] } };
|
|
1164
|
-
}
|
|
1165
|
-
|
|
1166
|
-
taskTexts = result.templates;
|
|
1167
|
-
for (let i = 0; i < result.behaviorOverrides.length; i++) {
|
|
1168
|
-
const override = result.behaviorOverrides[i];
|
|
1169
|
-
if (override?.model) {
|
|
1170
|
-
modelOverrides[i] = override.model;
|
|
1171
|
-
behaviorOverrides[i]!.model = override.model;
|
|
1172
|
-
}
|
|
1173
|
-
if (override?.output !== undefined) behaviorOverrides[i]!.output = override.output;
|
|
1174
|
-
if (override?.reads !== undefined) behaviorOverrides[i]!.reads = override.reads;
|
|
1175
|
-
if (override?.progress !== undefined) behaviorOverrides[i]!.progress = override.progress;
|
|
1176
|
-
if (override?.skills !== undefined) {
|
|
1177
|
-
skillOverrides[i] = override.skills;
|
|
1178
|
-
behaviorOverrides[i]!.skills = override.skills;
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
if (result.runInBackground) {
|
|
1183
|
-
if (!isAsyncAvailable()) {
|
|
1184
|
-
return {
|
|
1185
|
-
content: [{ type: "text", text: "Background mode requires jiti for TypeScript execution but it could not be found." }],
|
|
1186
|
-
isError: true,
|
|
1187
|
-
details: { mode: "parallel" as const, results: [] },
|
|
1188
|
-
};
|
|
1189
|
-
}
|
|
1190
|
-
const id = randomUUID();
|
|
1191
|
-
const asyncCtx = {
|
|
1192
|
-
pi: deps.pi,
|
|
1193
|
-
cwd: ctx.cwd,
|
|
1194
|
-
currentSessionId: deps.state.currentSessionId!,
|
|
1195
|
-
currentModelProvider: ctx.model?.provider,
|
|
1196
|
-
};
|
|
1197
|
-
const parallelTasks = tasks.map((t, i) => ({
|
|
1198
|
-
agent: t.agent,
|
|
1199
|
-
task: params.context === "fork" ? wrapForkTask(taskTexts[i]!) : taskTexts[i]!,
|
|
1200
|
-
cwd: t.cwd,
|
|
1201
|
-
...(modelOverrides[i] ? { model: modelOverrides[i] } : {}),
|
|
1202
|
-
...(skillOverrides[i] !== undefined ? { skill: skillOverrides[i] } : {}),
|
|
1203
|
-
...(behaviorOverrides[i]?.output !== undefined ? { output: behaviorOverrides[i]!.output } : {}),
|
|
1204
|
-
...(behaviorOverrides[i]?.reads !== undefined ? { reads: behaviorOverrides[i]!.reads } : {}),
|
|
1205
|
-
...(behaviorOverrides[i]?.progress !== undefined ? { progress: behaviorOverrides[i]!.progress } : {}),
|
|
1206
|
-
}));
|
|
1207
|
-
return executeAsyncChain(id, {
|
|
1208
|
-
chain: [{ parallel: parallelTasks, concurrency: parallelConcurrency, worktree: params.worktree }],
|
|
1209
|
-
resultMode: "parallel",
|
|
1210
|
-
agents,
|
|
1211
|
-
ctx: asyncCtx,
|
|
1212
|
-
availableModels,
|
|
1213
|
-
cwd: effectiveCwd,
|
|
1214
|
-
maxOutput: params.maxOutput,
|
|
1215
|
-
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
1216
|
-
artifactConfig,
|
|
1217
|
-
shareEnabled,
|
|
1218
|
-
sessionRoot,
|
|
1219
|
-
chainSkills: [],
|
|
1220
|
-
sessionFilesByFlatIndex: tasks.map((_, index) => sessionFileForIndex(index)),
|
|
1221
|
-
maxSubagentDepth: currentMaxSubagentDepth,
|
|
1222
|
-
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1223
|
-
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
1224
|
-
controlConfig,
|
|
1225
|
-
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
1226
|
-
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
|
|
1227
|
-
});
|
|
1228
|
-
}
|
|
1229
|
-
}
|
|
1230
|
-
|
|
1231
|
-
const behaviors = agentConfigs.map((config, index) => resolveStepBehavior(config, behaviorOverrides[index]!));
|
|
1232
|
-
const firstProgressIndex = behaviors.findIndex((behavior) => behavior.progress);
|
|
1233
|
-
const liveResults: (SingleResult | undefined)[] = new Array(tasks.length).fill(undefined);
|
|
1234
|
-
const liveProgress: (AgentProgress | undefined)[] = new Array(tasks.length).fill(undefined);
|
|
1235
|
-
const foregroundControl = deps.state.foregroundControls.get(runId);
|
|
1236
|
-
const { setup: worktreeSetup, errorResult } = createParallelWorktreeSetup(
|
|
1237
|
-
params.worktree,
|
|
1238
|
-
effectiveCwd,
|
|
1239
|
-
runId,
|
|
1240
|
-
tasks,
|
|
1241
|
-
deps.config.worktreeSetupHook,
|
|
1242
|
-
deps.config.worktreeSetupHookTimeoutMs,
|
|
1243
|
-
);
|
|
1244
|
-
if (errorResult) return errorResult;
|
|
1245
|
-
|
|
1246
|
-
try {
|
|
1247
|
-
const duplicateOutputError = findDuplicateParallelOutputPath({
|
|
1248
|
-
tasks,
|
|
1249
|
-
behaviors,
|
|
1250
|
-
paramsCwd: effectiveCwd,
|
|
1251
|
-
ctxCwd: ctx.cwd,
|
|
1252
|
-
worktreeSetup,
|
|
1253
|
-
});
|
|
1254
|
-
if (duplicateOutputError) return buildParallelModeError(duplicateOutputError);
|
|
1255
|
-
|
|
1256
|
-
const parallelProgressPrecreated = firstProgressIndex !== -1;
|
|
1257
|
-
if (parallelProgressPrecreated) writeInitialProgressFile(effectiveCwd);
|
|
1258
|
-
|
|
1259
|
-
if (params.context === "fork") {
|
|
1260
|
-
for (let i = 0; i < taskTexts.length; i++) {
|
|
1261
|
-
taskTexts[i] = wrapForkTask(taskTexts[i]!);
|
|
1262
|
-
}
|
|
1263
|
-
}
|
|
1264
|
-
|
|
1265
|
-
const results = await runForegroundParallelTasks({
|
|
1266
|
-
tasks,
|
|
1267
|
-
taskTexts,
|
|
1268
|
-
agents,
|
|
1269
|
-
ctx,
|
|
1270
|
-
intercomEvents: deps.pi.events,
|
|
1271
|
-
signal,
|
|
1272
|
-
runId,
|
|
1273
|
-
sessionDirForIndex,
|
|
1274
|
-
sessionFileForIndex,
|
|
1275
|
-
shareEnabled,
|
|
1276
|
-
artifactConfig,
|
|
1277
|
-
artifactsDir,
|
|
1278
|
-
maxOutput: params.maxOutput,
|
|
1279
|
-
paramsCwd: effectiveCwd,
|
|
1280
|
-
availableModels,
|
|
1281
|
-
modelOverrides,
|
|
1282
|
-
behaviors,
|
|
1283
|
-
firstProgressIndex: parallelProgressPrecreated ? -1 : firstProgressIndex,
|
|
1284
|
-
controlConfig,
|
|
1285
|
-
onControlEvent,
|
|
1286
|
-
childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(runId, agent, index) : undefined,
|
|
1287
|
-
foregroundControl,
|
|
1288
|
-
concurrencyLimit: parallelConcurrency,
|
|
1289
|
-
maxSubagentDepths,
|
|
1290
|
-
liveResults,
|
|
1291
|
-
liveProgress,
|
|
1292
|
-
onUpdate,
|
|
1293
|
-
worktreeSetup,
|
|
1294
|
-
});
|
|
1295
|
-
for (let i = 0; i < results.length; i++) {
|
|
1296
|
-
const run = results[i]!;
|
|
1297
|
-
recordRun(run.agent, taskTexts[i]!, run.exitCode, run.progressSummary?.durationMs ?? 0);
|
|
1298
|
-
}
|
|
1299
|
-
|
|
1300
|
-
for (const result of results) {
|
|
1301
|
-
if (result.progress) allProgress.push(result.progress);
|
|
1302
|
-
if (result.artifactPaths) allArtifactPaths.push(result.artifactPaths);
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1305
|
-
const interrupted = results.find((result) => result.interrupted);
|
|
1306
|
-
const details = compactForegroundDetails({
|
|
1307
|
-
mode: "parallel",
|
|
1308
|
-
results,
|
|
1309
|
-
progress: params.includeProgress ? allProgress : undefined,
|
|
1310
|
-
artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
|
|
1311
|
-
});
|
|
1312
|
-
if (interrupted) {
|
|
1313
|
-
return {
|
|
1314
|
-
content: [{ type: "text", text: `Parallel run paused after interrupt (${interrupted.agent}). Waiting for explicit next action.` }],
|
|
1315
|
-
details,
|
|
1316
|
-
};
|
|
1317
|
-
}
|
|
1318
|
-
|
|
1319
|
-
const intercomReceipt = await maybeBuildForegroundIntercomReceipt({
|
|
1320
|
-
pi: deps.pi,
|
|
1321
|
-
intercomBridge: data.intercomBridge,
|
|
1322
|
-
runId,
|
|
1323
|
-
mode: "parallel",
|
|
1324
|
-
details,
|
|
1325
|
-
});
|
|
1326
|
-
if (intercomReceipt) {
|
|
1327
|
-
return {
|
|
1328
|
-
content: [{ type: "text", text: intercomReceipt.text }],
|
|
1329
|
-
details: intercomReceipt.details,
|
|
1330
|
-
};
|
|
1331
|
-
}
|
|
1332
|
-
|
|
1333
|
-
const worktreeSuffix = buildParallelWorktreeSuffix(worktreeSetup, artifactsDir, tasks);
|
|
1334
|
-
const ok = results.filter((result) => result.exitCode === 0).length;
|
|
1335
|
-
const downgradeNote = backgroundRequestedWhileClarifying ? " (background requested, but clarify kept this run foreground)" : "";
|
|
1336
|
-
const aggregatedOutput = aggregateParallelOutputs(
|
|
1337
|
-
results.map((result) => ({
|
|
1338
|
-
agent: result.agent,
|
|
1339
|
-
output: result.truncation?.text || getSingleResultOutput(result),
|
|
1340
|
-
exitCode: result.exitCode,
|
|
1341
|
-
error: result.error,
|
|
1342
|
-
})),
|
|
1343
|
-
(i, agent) => `=== Task ${i + 1}: ${agent} ===`,
|
|
1344
|
-
);
|
|
1345
|
-
|
|
1346
|
-
const summary = `${ok}/${results.length} succeeded${downgradeNote}`;
|
|
1347
|
-
const fullContent = worktreeSuffix
|
|
1348
|
-
? `${summary}\n\n${aggregatedOutput}\n\n${worktreeSuffix}`
|
|
1349
|
-
: `${summary}\n\n${aggregatedOutput}`;
|
|
1350
|
-
|
|
1351
|
-
return {
|
|
1352
|
-
content: [{ type: "text", text: fullContent }],
|
|
1353
|
-
details,
|
|
1354
|
-
};
|
|
1355
|
-
} finally {
|
|
1356
|
-
if (worktreeSetup) cleanupWorktrees(worktreeSetup);
|
|
1357
|
-
}
|
|
1358
|
-
}
|
|
1359
|
-
|
|
1360
|
-
async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Promise<AgentToolResult<Details>> {
|
|
1361
|
-
const {
|
|
1362
|
-
params,
|
|
1363
|
-
effectiveCwd,
|
|
1364
|
-
agents,
|
|
1365
|
-
ctx,
|
|
1366
|
-
signal,
|
|
1367
|
-
runId,
|
|
1368
|
-
sessionDirForIndex,
|
|
1369
|
-
sessionFileForIndex,
|
|
1370
|
-
shareEnabled,
|
|
1371
|
-
artifactConfig,
|
|
1372
|
-
artifactsDir,
|
|
1373
|
-
onUpdate,
|
|
1374
|
-
sessionRoot,
|
|
1375
|
-
controlConfig,
|
|
1376
|
-
} = data;
|
|
1377
|
-
const onControlEvent = createForegroundControlNotifier(data, deps);
|
|
1378
|
-
const childIntercomTarget = data.intercomBridge.active ? resolveSubagentIntercomTarget(runId, params.agent!, undefined) : undefined;
|
|
1379
|
-
const allProgress: AgentProgress[] = [];
|
|
1380
|
-
const allArtifactPaths: ArtifactPaths[] = [];
|
|
1381
|
-
const agentConfig = agents.find((a) => a.name === params.agent);
|
|
1382
|
-
if (!agentConfig) {
|
|
1383
|
-
return {
|
|
1384
|
-
content: [{ type: "text", text: `Unknown agent: ${params.agent}` }],
|
|
1385
|
-
isError: true,
|
|
1386
|
-
details: { mode: "single", results: [] },
|
|
1387
|
-
};
|
|
1388
|
-
}
|
|
1389
|
-
|
|
1390
|
-
const currentProvider = ctx.model?.provider;
|
|
1391
|
-
const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map((m) => ({
|
|
1392
|
-
provider: m.provider,
|
|
1393
|
-
id: m.id,
|
|
1394
|
-
fullId: `${m.provider}/${m.id}`,
|
|
1395
|
-
}));
|
|
1396
|
-
let task = params.task ?? "";
|
|
1397
|
-
let modelOverride: string | undefined = resolveModelCandidate(
|
|
1398
|
-
(params.model as string | undefined) ?? agentConfig.model,
|
|
1399
|
-
availableModels,
|
|
1400
|
-
currentProvider,
|
|
1401
|
-
);
|
|
1402
|
-
let skillOverride: string[] | false | undefined = normalizeSkillInput(params.skill);
|
|
1403
|
-
const rawOutput = params.output !== undefined ? params.output : agentConfig.output;
|
|
1404
|
-
let effectiveOutput: string | false | undefined = rawOutput === true ? agentConfig.output : (rawOutput as string | false | undefined);
|
|
1405
|
-
const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
|
|
1406
|
-
const maxSubagentDepth = resolveChildMaxSubagentDepth(currentMaxSubagentDepth, agentConfig.maxSubagentDepth);
|
|
1407
|
-
|
|
1408
|
-
if (params.clarify === true && ctx.hasUI) {
|
|
1409
|
-
const behavior = resolveStepBehavior(agentConfig, { output: effectiveOutput, skills: skillOverride });
|
|
1410
|
-
const availableSkills = discoverAvailableSkills(effectiveCwd);
|
|
1411
|
-
|
|
1412
|
-
const result = await ctx.ui.custom<ChainClarifyResult>(
|
|
1413
|
-
(tui, theme, _kb, done) =>
|
|
1414
|
-
new ChainClarifyComponent(
|
|
1415
|
-
tui, theme,
|
|
1416
|
-
[agentConfig],
|
|
1417
|
-
[task],
|
|
1418
|
-
task,
|
|
1419
|
-
undefined,
|
|
1420
|
-
[behavior],
|
|
1421
|
-
availableModels,
|
|
1422
|
-
currentProvider,
|
|
1423
|
-
availableSkills,
|
|
1424
|
-
done,
|
|
1425
|
-
"single",
|
|
1426
|
-
),
|
|
1427
|
-
{ overlay: true, overlayOptions: { anchor: "center", width: 84, maxHeight: "80%" } },
|
|
1428
|
-
);
|
|
1429
|
-
|
|
1430
|
-
if (!result || !result.confirmed) {
|
|
1431
|
-
return { content: [{ type: "text", text: "Cancelled" }], details: { mode: "single", results: [] } };
|
|
1432
|
-
}
|
|
1433
|
-
|
|
1434
|
-
task = result.templates[0]!;
|
|
1435
|
-
const override = result.behaviorOverrides[0];
|
|
1436
|
-
if (override?.model) modelOverride = override.model;
|
|
1437
|
-
if (override?.output !== undefined) effectiveOutput = override.output;
|
|
1438
|
-
if (override?.skills !== undefined) skillOverride = override.skills;
|
|
1439
|
-
|
|
1440
|
-
if (result.runInBackground) {
|
|
1441
|
-
if (!isAsyncAvailable()) {
|
|
1442
|
-
return {
|
|
1443
|
-
content: [{ type: "text", text: "Background mode requires jiti for TypeScript execution but it could not be found." }],
|
|
1444
|
-
isError: true,
|
|
1445
|
-
details: { mode: "single" as const, results: [] },
|
|
1446
|
-
};
|
|
1447
|
-
}
|
|
1448
|
-
const id = randomUUID();
|
|
1449
|
-
const asyncCtx = {
|
|
1450
|
-
pi: deps.pi,
|
|
1451
|
-
cwd: ctx.cwd,
|
|
1452
|
-
currentSessionId: deps.state.currentSessionId!,
|
|
1453
|
-
currentModelProvider: ctx.model?.provider,
|
|
1454
|
-
};
|
|
1455
|
-
return executeAsyncSingle(id, {
|
|
1456
|
-
agent: params.agent!,
|
|
1457
|
-
task: params.context === "fork" ? wrapForkTask(task) : task,
|
|
1458
|
-
agentConfig,
|
|
1459
|
-
ctx: asyncCtx,
|
|
1460
|
-
availableModels,
|
|
1461
|
-
cwd: effectiveCwd,
|
|
1462
|
-
maxOutput: params.maxOutput,
|
|
1463
|
-
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
1464
|
-
artifactConfig,
|
|
1465
|
-
shareEnabled,
|
|
1466
|
-
sessionRoot,
|
|
1467
|
-
sessionFile: sessionFileForIndex(0),
|
|
1468
|
-
skills: skillOverride === false ? [] : skillOverride,
|
|
1469
|
-
output: effectiveOutput,
|
|
1470
|
-
modelOverride,
|
|
1471
|
-
maxSubagentDepth,
|
|
1472
|
-
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1473
|
-
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
1474
|
-
controlConfig,
|
|
1475
|
-
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
1476
|
-
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
|
|
1477
|
-
});
|
|
1478
|
-
}
|
|
1479
|
-
}
|
|
1480
|
-
|
|
1481
|
-
if (params.context === "fork") {
|
|
1482
|
-
task = wrapForkTask(task);
|
|
1483
|
-
}
|
|
1484
|
-
const cleanTask = task;
|
|
1485
|
-
const outputPath = resolveSingleOutputPath(effectiveOutput, ctx.cwd, effectiveCwd);
|
|
1486
|
-
task = injectSingleOutputInstruction(task, outputPath);
|
|
1487
|
-
|
|
1488
|
-
let effectiveSkills: string[] | undefined;
|
|
1489
|
-
if (skillOverride === false) {
|
|
1490
|
-
effectiveSkills = [];
|
|
1491
|
-
} else {
|
|
1492
|
-
effectiveSkills = skillOverride;
|
|
1493
|
-
}
|
|
1494
|
-
const interruptController = new AbortController();
|
|
1495
|
-
const foregroundControl = deps.state.foregroundControls.get(runId);
|
|
1496
|
-
if (foregroundControl) {
|
|
1497
|
-
foregroundControl.currentAgent = params.agent;
|
|
1498
|
-
foregroundControl.currentIndex = 0;
|
|
1499
|
-
foregroundControl.currentActivityState = undefined;
|
|
1500
|
-
foregroundControl.updatedAt = Date.now();
|
|
1501
|
-
foregroundControl.interrupt = () => {
|
|
1502
|
-
if (interruptController.signal.aborted) return false;
|
|
1503
|
-
interruptController.abort();
|
|
1504
|
-
foregroundControl.currentActivityState = undefined;
|
|
1505
|
-
foregroundControl.updatedAt = Date.now();
|
|
1506
|
-
return true;
|
|
1507
|
-
};
|
|
1508
|
-
}
|
|
1509
|
-
|
|
1510
|
-
const forwardSingleUpdate = onUpdate
|
|
1511
|
-
? (update: AgentToolResult<Details>) => {
|
|
1512
|
-
if (foregroundControl) {
|
|
1513
|
-
const firstProgress = update.details?.progress?.[0];
|
|
1514
|
-
foregroundControl.currentAgent = params.agent;
|
|
1515
|
-
foregroundControl.currentIndex = firstProgress?.index ?? 0;
|
|
1516
|
-
foregroundControl.currentActivityState = firstProgress?.activityState;
|
|
1517
|
-
foregroundControl.lastActivityAt = firstProgress?.lastActivityAt;
|
|
1518
|
-
foregroundControl.currentTool = firstProgress?.currentTool;
|
|
1519
|
-
foregroundControl.currentToolStartedAt = firstProgress?.currentToolStartedAt;
|
|
1520
|
-
foregroundControl.updatedAt = Date.now();
|
|
1521
|
-
}
|
|
1522
|
-
onUpdate(update);
|
|
1523
|
-
}
|
|
1524
|
-
: undefined;
|
|
1525
|
-
|
|
1526
|
-
const r = await runSync(ctx.cwd, agents, params.agent!, task, {
|
|
1527
|
-
cwd: effectiveCwd,
|
|
1528
|
-
signal,
|
|
1529
|
-
interruptSignal: interruptController.signal,
|
|
1530
|
-
allowIntercomDetach: agentConfig.systemPrompt?.includes(INTERCOM_BRIDGE_MARKER) === true,
|
|
1531
|
-
intercomEvents: deps.pi.events,
|
|
1532
|
-
runId,
|
|
1533
|
-
sessionDir: sessionDirForIndex(0),
|
|
1534
|
-
sessionFile: sessionFileForIndex(0),
|
|
1535
|
-
share: shareEnabled,
|
|
1536
|
-
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
1537
|
-
artifactConfig,
|
|
1538
|
-
maxOutput: params.maxOutput,
|
|
1539
|
-
outputPath,
|
|
1540
|
-
maxSubagentDepth,
|
|
1541
|
-
onUpdate: forwardSingleUpdate,
|
|
1542
|
-
controlConfig,
|
|
1543
|
-
onControlEvent,
|
|
1544
|
-
intercomSessionName: childIntercomTarget,
|
|
1545
|
-
modelOverride,
|
|
1546
|
-
availableModels,
|
|
1547
|
-
preferredModelProvider: currentProvider,
|
|
1548
|
-
skills: effectiveSkills,
|
|
1549
|
-
});
|
|
1550
|
-
if (foregroundControl?.currentIndex === 0) {
|
|
1551
|
-
foregroundControl.interrupt = undefined;
|
|
1552
|
-
foregroundControl.currentActivityState = r.progress?.activityState;
|
|
1553
|
-
foregroundControl.lastActivityAt = r.progress?.lastActivityAt;
|
|
1554
|
-
foregroundControl.currentTool = r.progress?.currentTool;
|
|
1555
|
-
foregroundControl.currentToolStartedAt = r.progress?.currentToolStartedAt;
|
|
1556
|
-
foregroundControl.updatedAt = Date.now();
|
|
1557
|
-
}
|
|
1558
|
-
recordRun(params.agent!, cleanTask, r.exitCode, r.progressSummary?.durationMs ?? 0);
|
|
1559
|
-
|
|
1560
|
-
if (r.progress) allProgress.push(r.progress);
|
|
1561
|
-
if (r.artifactPaths) allArtifactPaths.push(r.artifactPaths);
|
|
1562
|
-
|
|
1563
|
-
const fullOutput = getSingleResultOutput(r);
|
|
1564
|
-
const finalizedOutput = finalizeSingleOutput({
|
|
1565
|
-
fullOutput,
|
|
1566
|
-
truncatedOutput: r.truncation?.text,
|
|
1567
|
-
outputPath,
|
|
1568
|
-
exitCode: r.exitCode,
|
|
1569
|
-
savedPath: r.savedOutputPath,
|
|
1570
|
-
saveError: r.outputSaveError,
|
|
1571
|
-
});
|
|
1572
|
-
const details = compactForegroundDetails({
|
|
1573
|
-
mode: "single",
|
|
1574
|
-
results: [r],
|
|
1575
|
-
progress: params.includeProgress ? allProgress : undefined,
|
|
1576
|
-
artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
|
|
1577
|
-
truncation: r.truncation,
|
|
1578
|
-
});
|
|
1579
|
-
|
|
1580
|
-
if (!r.detached && !r.interrupted) {
|
|
1581
|
-
const intercomReceipt = await maybeBuildForegroundIntercomReceipt({
|
|
1582
|
-
pi: deps.pi,
|
|
1583
|
-
intercomBridge: data.intercomBridge,
|
|
1584
|
-
runId,
|
|
1585
|
-
mode: "single",
|
|
1586
|
-
details,
|
|
1587
|
-
});
|
|
1588
|
-
if (intercomReceipt) {
|
|
1589
|
-
return {
|
|
1590
|
-
content: [{ type: "text", text: intercomReceipt.text }],
|
|
1591
|
-
details: intercomReceipt.details,
|
|
1592
|
-
...(r.exitCode !== 0 ? { isError: true } : {}),
|
|
1593
|
-
};
|
|
1594
|
-
}
|
|
1595
|
-
}
|
|
1596
|
-
|
|
1597
|
-
if (r.detached) {
|
|
1598
|
-
return {
|
|
1599
|
-
content: [{ type: "text", text: `Detached for intercom coordination: ${params.agent}` }],
|
|
1600
|
-
details,
|
|
1601
|
-
};
|
|
1602
|
-
}
|
|
1603
|
-
|
|
1604
|
-
if (r.interrupted) {
|
|
1605
|
-
return {
|
|
1606
|
-
content: [{ type: "text", text: `Run paused after interrupt (${params.agent}). Waiting for explicit next action.` }],
|
|
1607
|
-
details,
|
|
1608
|
-
};
|
|
1609
|
-
}
|
|
1610
|
-
|
|
1611
|
-
if (r.exitCode !== 0)
|
|
1612
|
-
return {
|
|
1613
|
-
content: [{ type: "text", text: r.error || "Failed" }],
|
|
1614
|
-
details,
|
|
1615
|
-
isError: true,
|
|
1616
|
-
};
|
|
1617
|
-
return {
|
|
1618
|
-
content: [{ type: "text", text: finalizedOutput.displayOutput || "(no output)" }],
|
|
1619
|
-
details,
|
|
1620
|
-
};
|
|
1621
|
-
}
|
|
1622
|
-
|
|
1623
|
-
export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
1624
|
-
execute: (
|
|
1625
|
-
id: string,
|
|
1626
|
-
params: SubagentParamsLike,
|
|
1627
|
-
signal: AbortSignal,
|
|
1628
|
-
onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
|
|
1629
|
-
ctx: ExtensionContext,
|
|
1630
|
-
) => Promise<AgentToolResult<Details>>;
|
|
1631
|
-
} {
|
|
1632
|
-
const execute = async (
|
|
1633
|
-
_id: string,
|
|
1634
|
-
params: SubagentParamsLike,
|
|
1635
|
-
signal: AbortSignal,
|
|
1636
|
-
onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
|
|
1637
|
-
ctx: ExtensionContext,
|
|
1638
|
-
): Promise<AgentToolResult<Details>> => {
|
|
1639
|
-
deps.state.baseCwd = ctx.cwd;
|
|
1640
|
-
deps.state.foregroundControls ??= new Map();
|
|
1641
|
-
deps.state.lastForegroundControlId ??= null;
|
|
1642
|
-
const requestCwd = resolveRequestedCwd(ctx.cwd, params.cwd);
|
|
1643
|
-
const paramsWithResolvedCwd = params.cwd === undefined ? params : { ...params, cwd: requestCwd };
|
|
1644
|
-
if (params.action) {
|
|
1645
|
-
if (params.action === "doctor") {
|
|
1646
|
-
let currentSessionFile: string | null = null;
|
|
1647
|
-
let currentSessionId = deps.state.currentSessionId;
|
|
1648
|
-
let sessionError: string | undefined;
|
|
1649
|
-
try {
|
|
1650
|
-
currentSessionFile = ctx.sessionManager.getSessionFile() ?? null;
|
|
1651
|
-
currentSessionId = ctx.sessionManager.getSessionId();
|
|
1652
|
-
} catch (error) {
|
|
1653
|
-
sessionError = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
1654
|
-
}
|
|
1655
|
-
let orchestratorTarget: string | undefined;
|
|
1656
|
-
try {
|
|
1657
|
-
orchestratorTarget = resolveIntercomSessionTarget(deps.pi.getSessionName(), ctx.sessionManager.getSessionId());
|
|
1658
|
-
} catch {}
|
|
1659
|
-
return {
|
|
1660
|
-
content: [{
|
|
1661
|
-
type: "text",
|
|
1662
|
-
text: buildDoctorReport({
|
|
1663
|
-
cwd: requestCwd,
|
|
1664
|
-
config: deps.config,
|
|
1665
|
-
state: deps.state,
|
|
1666
|
-
context: paramsWithResolvedCwd.context,
|
|
1667
|
-
requestedSessionDir: paramsWithResolvedCwd.sessionDir,
|
|
1668
|
-
currentSessionFile,
|
|
1669
|
-
currentSessionId,
|
|
1670
|
-
orchestratorTarget,
|
|
1671
|
-
sessionError,
|
|
1672
|
-
expandTilde: deps.expandTilde,
|
|
1673
|
-
}),
|
|
1674
|
-
}],
|
|
1675
|
-
details: { mode: "management", results: [] },
|
|
1676
|
-
};
|
|
1677
|
-
}
|
|
1678
|
-
if (params.action === "status") {
|
|
1679
|
-
const foreground = getForegroundControl(deps.state, paramsWithResolvedCwd.id ?? paramsWithResolvedCwd.runId);
|
|
1680
|
-
if (foreground) return foregroundStatusResult(foreground);
|
|
1681
|
-
return inspectSubagentStatus(paramsWithResolvedCwd);
|
|
1682
|
-
}
|
|
1683
|
-
if (params.action === "interrupt") {
|
|
1684
|
-
const targetRunId = paramsWithResolvedCwd.runId ?? paramsWithResolvedCwd.id;
|
|
1685
|
-
const foreground = getForegroundControl(deps.state, targetRunId);
|
|
1686
|
-
if (foreground?.interrupt) {
|
|
1687
|
-
const interrupted = foreground.interrupt();
|
|
1688
|
-
if (interrupted) {
|
|
1689
|
-
foreground.updatedAt = Date.now();
|
|
1690
|
-
foreground.currentActivityState = undefined;
|
|
1691
|
-
return {
|
|
1692
|
-
content: [{ type: "text", text: `Interrupt requested for foreground run ${foreground.runId}.` }],
|
|
1693
|
-
details: { mode: "management", results: [] },
|
|
1694
|
-
};
|
|
1695
|
-
}
|
|
1696
|
-
return {
|
|
1697
|
-
content: [{ type: "text", text: `Foreground run ${foreground.runId} has no active child step to interrupt.` }],
|
|
1698
|
-
isError: true,
|
|
1699
|
-
details: { mode: "management", results: [] },
|
|
1700
|
-
};
|
|
1701
|
-
}
|
|
1702
|
-
const asyncInterruptResult = interruptAsyncRun(deps.state, targetRunId);
|
|
1703
|
-
if (asyncInterruptResult) return asyncInterruptResult;
|
|
1704
|
-
return {
|
|
1705
|
-
content: [{ type: "text", text: "No interrupt-capable run found in this session." }],
|
|
1706
|
-
isError: true,
|
|
1707
|
-
details: { mode: "management", results: [] },
|
|
1708
|
-
};
|
|
1709
|
-
}
|
|
1710
|
-
const validActions = ["list", "get", "create", "update", "delete", "status", "interrupt", "doctor"];
|
|
1711
|
-
if (!validActions.includes(params.action)) {
|
|
1712
|
-
return {
|
|
1713
|
-
content: [{ type: "text", text: `Unknown action: ${params.action}. Valid: ${validActions.join(", ")}` }],
|
|
1714
|
-
isError: true,
|
|
1715
|
-
details: { mode: "management" as const, results: [] },
|
|
1716
|
-
};
|
|
1717
|
-
}
|
|
1718
|
-
return handleManagementAction(params.action, paramsWithResolvedCwd, { ...ctx, cwd: requestCwd });
|
|
1719
|
-
}
|
|
1720
|
-
|
|
1721
|
-
const { blocked, depth, maxDepth } = checkSubagentDepth(deps.config.maxSubagentDepth);
|
|
1722
|
-
if (blocked) {
|
|
1723
|
-
return {
|
|
1724
|
-
content: [
|
|
1725
|
-
{
|
|
1726
|
-
type: "text",
|
|
1727
|
-
text:
|
|
1728
|
-
`Nested subagent call blocked (depth=${depth}, max=${maxDepth}). ` +
|
|
1729
|
-
"You are running at the maximum subagent nesting depth. " +
|
|
1730
|
-
"Complete your current task directly without delegating to further subagents.",
|
|
1731
|
-
},
|
|
1732
|
-
],
|
|
1733
|
-
isError: true,
|
|
1734
|
-
details: { mode: "single" as const, results: [] },
|
|
1735
|
-
};
|
|
1736
|
-
}
|
|
1737
|
-
|
|
1738
|
-
const normalized = normalizeRepeatedParallelCounts(paramsWithResolvedCwd);
|
|
1739
|
-
if (normalized.error) return normalized.error;
|
|
1740
|
-
const normalizedParams = normalized.params!;
|
|
1741
|
-
|
|
1742
|
-
const effectiveParams = applyForceTopLevelAsyncOverride(
|
|
1743
|
-
normalizedParams,
|
|
1744
|
-
depth,
|
|
1745
|
-
deps.config.forceTopLevelAsync === true,
|
|
1746
|
-
);
|
|
1747
|
-
|
|
1748
|
-
const scope: AgentScope = resolveExecutionAgentScope(effectiveParams.agentScope);
|
|
1749
|
-
const effectiveCwd = effectiveParams.cwd ?? ctx.cwd;
|
|
1750
|
-
const parentSessionFile = ctx.sessionManager.getSessionFile() ?? null;
|
|
1751
|
-
deps.state.currentSessionId = parentSessionFile ?? `session-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
1752
|
-
const discoveredAgents = deps.discoverAgents(effectiveCwd, scope).agents;
|
|
1753
|
-
const sessionName = resolveIntercomSessionTarget(deps.pi.getSessionName(), ctx.sessionManager.getSessionId());
|
|
1754
|
-
const intercomBridge = resolveIntercomBridge({
|
|
1755
|
-
config: deps.config.intercomBridge,
|
|
1756
|
-
context: effectiveParams.context,
|
|
1757
|
-
orchestratorTarget: sessionName,
|
|
1758
|
-
});
|
|
1759
|
-
const agents = intercomBridge.active
|
|
1760
|
-
? discoveredAgents.map((agent) => applyIntercomBridgeToAgent(agent, intercomBridge))
|
|
1761
|
-
: discoveredAgents;
|
|
1762
|
-
const runId = randomUUID().slice(0, 8);
|
|
1763
|
-
const shareEnabled = effectiveParams.share === true;
|
|
1764
|
-
const hasChain = (effectiveParams.chain?.length ?? 0) > 0;
|
|
1765
|
-
const hasTasks = (effectiveParams.tasks?.length ?? 0) > 0;
|
|
1766
|
-
const hasSingle = !hasChain && !hasTasks && Boolean(effectiveParams.agent);
|
|
1767
|
-
const allowClarifyTaskPrompt = hasChain
|
|
1768
|
-
&& effectiveParams.clarify === true
|
|
1769
|
-
&& ctx.hasUI
|
|
1770
|
-
&& !(effectiveParams.chain?.some(isParallelStep) ?? false);
|
|
1771
|
-
|
|
1772
|
-
const validationError = validateExecutionInput(
|
|
1773
|
-
effectiveParams,
|
|
1774
|
-
agents,
|
|
1775
|
-
hasChain,
|
|
1776
|
-
hasTasks,
|
|
1777
|
-
hasSingle,
|
|
1778
|
-
allowClarifyTaskPrompt,
|
|
1779
|
-
);
|
|
1780
|
-
if (validationError) return validationError;
|
|
1781
|
-
|
|
1782
|
-
let sessionFileForIndex: (idx?: number) => string | undefined = () => undefined;
|
|
1783
|
-
try {
|
|
1784
|
-
sessionFileForIndex = createForkContextResolver(ctx.sessionManager, effectiveParams.context).sessionFileForIndex;
|
|
1785
|
-
} catch (error) {
|
|
1786
|
-
return toExecutionErrorResult(effectiveParams, error);
|
|
1787
|
-
}
|
|
1788
|
-
const requestedAsync = effectiveParams.async ?? deps.asyncByDefault;
|
|
1789
|
-
const backgroundRequestedWhileClarifying = hasTasks && requestedAsync && effectiveParams.clarify === true;
|
|
1790
|
-
const effectiveAsync = requestedAsync
|
|
1791
|
-
&& (hasChain ? effectiveParams.clarify === false : effectiveParams.clarify !== true);
|
|
1792
|
-
const controlConfig = resolveControlConfig(deps.config.control, effectiveParams.control);
|
|
1793
|
-
|
|
1794
|
-
const artifactConfig: ArtifactConfig = {
|
|
1795
|
-
...DEFAULT_ARTIFACT_CONFIG,
|
|
1796
|
-
enabled: effectiveParams.artifacts !== false,
|
|
1797
|
-
};
|
|
1798
|
-
const artifactsDir = effectiveAsync ? deps.tempArtifactsDir : getArtifactsDir(parentSessionFile);
|
|
1799
|
-
|
|
1800
|
-
let sessionRoot: string;
|
|
1801
|
-
if (effectiveParams.sessionDir) {
|
|
1802
|
-
sessionRoot = path.resolve(deps.expandTilde(effectiveParams.sessionDir));
|
|
1803
|
-
} else {
|
|
1804
|
-
const baseSessionRoot = deps.config.defaultSessionDir
|
|
1805
|
-
? path.resolve(deps.expandTilde(deps.config.defaultSessionDir))
|
|
1806
|
-
: deps.getSubagentSessionRoot(parentSessionFile);
|
|
1807
|
-
sessionRoot = path.join(baseSessionRoot, runId);
|
|
1808
|
-
}
|
|
1809
|
-
try {
|
|
1810
|
-
fs.mkdirSync(sessionRoot, { recursive: true });
|
|
1811
|
-
} catch (error) {
|
|
1812
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1813
|
-
return toExecutionErrorResult(
|
|
1814
|
-
effectiveParams,
|
|
1815
|
-
new Error(`Failed to create session directory '${sessionRoot}': ${message}`),
|
|
1816
|
-
);
|
|
1817
|
-
}
|
|
1818
|
-
const sessionDirForIndex = (idx?: number) =>
|
|
1819
|
-
path.join(sessionRoot, `run-${idx ?? 0}`);
|
|
1820
|
-
const childSessionFileForIndex = (idx?: number) =>
|
|
1821
|
-
sessionFileForIndex(idx) ?? path.join(sessionDirForIndex(idx), "session.jsonl");
|
|
1822
|
-
|
|
1823
|
-
const onUpdateWithContext = onUpdate
|
|
1824
|
-
? (r: AgentToolResult<Details>) => onUpdate(withForkContext(r, effectiveParams.context))
|
|
1825
|
-
: undefined;
|
|
1826
|
-
|
|
1827
|
-
const execData: ExecutionContextData = {
|
|
1828
|
-
params: effectiveParams,
|
|
1829
|
-
effectiveCwd,
|
|
1830
|
-
ctx,
|
|
1831
|
-
signal,
|
|
1832
|
-
onUpdate: onUpdateWithContext,
|
|
1833
|
-
agents,
|
|
1834
|
-
runId,
|
|
1835
|
-
shareEnabled,
|
|
1836
|
-
sessionRoot,
|
|
1837
|
-
sessionDirForIndex,
|
|
1838
|
-
sessionFileForIndex: childSessionFileForIndex,
|
|
1839
|
-
artifactConfig,
|
|
1840
|
-
artifactsDir,
|
|
1841
|
-
backgroundRequestedWhileClarifying,
|
|
1842
|
-
effectiveAsync,
|
|
1843
|
-
controlConfig,
|
|
1844
|
-
intercomBridge,
|
|
1845
|
-
};
|
|
1846
|
-
|
|
1847
|
-
const foregroundMode: "single" | "parallel" | "chain" = hasChain ? "chain" : hasTasks ? "parallel" : "single";
|
|
1848
|
-
const foregroundControl = effectiveAsync
|
|
1849
|
-
? undefined
|
|
1850
|
-
: {
|
|
1851
|
-
runId,
|
|
1852
|
-
mode: foregroundMode,
|
|
1853
|
-
startedAt: Date.now(),
|
|
1854
|
-
updatedAt: Date.now(),
|
|
1855
|
-
currentAgent: undefined,
|
|
1856
|
-
currentIndex: undefined,
|
|
1857
|
-
currentActivityState: undefined,
|
|
1858
|
-
interrupt: undefined,
|
|
1859
|
-
};
|
|
1860
|
-
if (foregroundControl) {
|
|
1861
|
-
deps.state.foregroundControls.set(runId, foregroundControl);
|
|
1862
|
-
deps.state.lastForegroundControlId = runId;
|
|
1863
|
-
}
|
|
1864
|
-
|
|
1865
|
-
try {
|
|
1866
|
-
const asyncResult = runAsyncPath(execData, deps);
|
|
1867
|
-
if (asyncResult) return withForkContext(asyncResult, effectiveParams.context);
|
|
1868
|
-
|
|
1869
|
-
if (hasChain && effectiveParams.chain) {
|
|
1870
|
-
return withForkContext(await runChainPath(execData, deps), effectiveParams.context);
|
|
1871
|
-
}
|
|
1872
|
-
|
|
1873
|
-
if (hasTasks && effectiveParams.tasks) {
|
|
1874
|
-
return withForkContext(await runParallelPath(execData, deps), effectiveParams.context);
|
|
1875
|
-
}
|
|
1876
|
-
|
|
1877
|
-
if (hasSingle) {
|
|
1878
|
-
return withForkContext(await runSinglePath(execData, deps), effectiveParams.context);
|
|
1879
|
-
}
|
|
1880
|
-
} catch (error) {
|
|
1881
|
-
return toExecutionErrorResult(normalizedParams, error);
|
|
1882
|
-
} finally {
|
|
1883
|
-
if (foregroundControl) {
|
|
1884
|
-
deps.state.foregroundControls.delete(runId);
|
|
1885
|
-
if (deps.state.lastForegroundControlId === runId) {
|
|
1886
|
-
deps.state.lastForegroundControlId = null;
|
|
1887
|
-
}
|
|
1888
|
-
}
|
|
1889
|
-
}
|
|
1890
|
-
|
|
1891
|
-
return withForkContext({
|
|
1892
|
-
content: [{ type: "text", text: "Invalid params" }],
|
|
1893
|
-
isError: true,
|
|
1894
|
-
details: { mode: "single" as const, results: [] },
|
|
1895
|
-
}, effectiveParams.context);
|
|
1896
|
-
};
|
|
1897
|
-
|
|
1898
|
-
return { execute };
|
|
1899
|
-
}
|