@narumitw/pi-subagents 0.30.1 → 0.33.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 +31 -15
- package/package.json +7 -7
- package/src/agents.ts +20 -15
- package/src/config-ui.ts +368 -66
- package/src/context.ts +13 -16
- package/src/execution.ts +360 -337
- package/src/limits.ts +8 -2
- package/src/params.ts +26 -9
- package/src/render.ts +2 -4
- package/src/settings.ts +84 -2
- package/src/stateful-prompt.ts +2 -5
- package/src/stateful.ts +25 -47
- package/src/subagents.ts +30 -12
- package/src/workspace.ts +7 -6
package/src/execution.ts
CHANGED
|
@@ -105,375 +105,398 @@ export async function executeSubagent(
|
|
|
105
105
|
onUpdate: AgentToolUpdateCallback<SubagentDetails> | undefined,
|
|
106
106
|
ctx: ExtensionContext,
|
|
107
107
|
): Promise<AgentToolResult<SubagentDetails> & { isError?: boolean }> {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
108
|
+
assertSubagentDepthAllowed();
|
|
109
|
+
const agentScope: AgentScope = params.agentScope ?? "user";
|
|
110
|
+
const aggregator = hasUsableAggregator(params.aggregator) ? params.aggregator : undefined;
|
|
111
|
+
const config = readSubagentSettings();
|
|
112
|
+
const discovery = discoverAgents(ctx.cwd, agentScope, config);
|
|
113
|
+
const agents = discovery.agents;
|
|
114
|
+
const confirmProjectAgents = params.confirmProjectAgents ?? true;
|
|
115
|
+
const resolveTimeoutMs = (agentName: string, localTimeoutMs?: number) =>
|
|
116
|
+
localTimeoutMs ??
|
|
117
|
+
params.timeoutMs ??
|
|
118
|
+
agents.find((agent) => agent.name === agentName)?.timeoutMs ??
|
|
119
|
+
resolveDefaultSubagentTimeoutMs();
|
|
120
|
+
const resolveThinkingLevel = (agentName: string, localThinkingLevel?: SubagentThinkingLevel) =>
|
|
121
|
+
resolveSubagentThinkingLevel(agents, agentName, params.thinkingLevel, localThinkingLevel);
|
|
122
|
+
|
|
123
|
+
const hasChain = (params.chain?.length ?? 0) > 0;
|
|
124
|
+
const hasTasks = (params.tasks?.length ?? 0) > 0;
|
|
125
|
+
const hasSingle = Boolean(params.agent && params.task);
|
|
126
|
+
const modeCount = Number(hasChain) + Number(hasTasks) + Number(hasSingle);
|
|
127
|
+
|
|
128
|
+
const makeDetails =
|
|
129
|
+
(mode: "single" | "parallel" | "chain") =>
|
|
130
|
+
(results: SingleResult[], aggregator?: SingleResult): SubagentDetails => ({
|
|
131
|
+
mode,
|
|
132
|
+
agentScope,
|
|
133
|
+
projectAgentsDir: discovery.projectAgentsDir,
|
|
134
|
+
results,
|
|
135
|
+
aggregator,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
if (modeCount !== 1 || (aggregator && !hasTasks)) {
|
|
139
|
+
const available = agents.map((a) => `${a.name} (${a.source})`).join(", ") || "none";
|
|
140
|
+
const reason =
|
|
141
|
+
modeCount !== 1
|
|
142
|
+
? "Provide exactly one mode."
|
|
143
|
+
: "Aggregator is only valid with parallel tasks.";
|
|
144
|
+
return {
|
|
145
|
+
content: [
|
|
146
|
+
{
|
|
147
|
+
type: "text",
|
|
148
|
+
text: `Invalid parameters. ${reason}\nAvailable agents: ${available}`,
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
details: makeDetails("single")([]),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
154
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const projectAgentsRequested = Array.from(requestedAgentNames)
|
|
163
|
-
.map((name) => agents.find((a) => a.name === name))
|
|
164
|
-
.filter((a): a is AgentConfig => a?.source === "project");
|
|
165
|
-
|
|
166
|
-
if (projectAgentsRequested.length > 0) {
|
|
167
|
-
if (!ctx.isProjectTrusted()) {
|
|
168
|
-
throw new Error("Project-local subagent definitions require a trusted project");
|
|
169
|
-
}
|
|
170
|
-
if (confirmProjectAgents && ctx.hasUI) {
|
|
171
|
-
const names = projectAgentsRequested.map((a) => a.name).join(", ");
|
|
172
|
-
const dir = discovery.projectAgentsDir ?? "(unknown)";
|
|
173
|
-
const ok = await ctx.ui.confirm(
|
|
174
|
-
"Run project-local agents?",
|
|
175
|
-
`Agents: ${names}\nSource: ${dir}\n\nProject agents are repo-controlled. Only continue for trusted repositories.`,
|
|
176
|
-
);
|
|
177
|
-
if (!ok) {
|
|
178
|
-
return {
|
|
179
|
-
content: [{ type: "text", text: "Canceled: project-local agents not approved." }],
|
|
180
|
-
details: makeDetails(hasChain ? "chain" : hasTasks ? "parallel" : "single")([]),
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
155
|
+
if (agentScope === "project" || agentScope === "both") {
|
|
156
|
+
const requestedAgentNames = new Set<string>();
|
|
157
|
+
if (params.chain) for (const step of params.chain) requestedAgentNames.add(step.agent);
|
|
158
|
+
if (params.tasks) for (const t of params.tasks) requestedAgentNames.add(t.agent);
|
|
159
|
+
if (aggregator) requestedAgentNames.add(aggregator.agent);
|
|
160
|
+
if (params.agent) requestedAgentNames.add(params.agent);
|
|
186
161
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const status = startSubagentStatus(ctx, toolCallId, chainStatus(0, params.chain.length));
|
|
191
|
-
|
|
192
|
-
try {
|
|
193
|
-
for (let i = 0; i < params.chain.length; i++) {
|
|
194
|
-
const step = params.chain[i];
|
|
195
|
-
status.update(chainStatus(i + 1, params.chain.length, step.agent));
|
|
196
|
-
const taskWithContext = truncateUtf8(
|
|
197
|
-
step.task.replace(/\{previous\}/g, previousOutput),
|
|
198
|
-
DEFAULT_MAX_CONTEXT_BYTES,
|
|
199
|
-
).text;
|
|
200
|
-
|
|
201
|
-
// Create update callback that includes all previous results
|
|
202
|
-
const chainUpdate: OnUpdateCallback | undefined = onUpdate
|
|
203
|
-
? (partial) => {
|
|
204
|
-
// Combine completed results with current streaming result
|
|
205
|
-
const currentResult = partial.details?.results[0];
|
|
206
|
-
if (currentResult) {
|
|
207
|
-
const allResults = [...results, currentResult];
|
|
208
|
-
onUpdate({
|
|
209
|
-
content: partial.content,
|
|
210
|
-
details: makeDetails("chain")(allResults),
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
: undefined;
|
|
162
|
+
const projectAgentsRequested = Array.from(requestedAgentNames)
|
|
163
|
+
.map((name) => agents.find((a) => a.name === name))
|
|
164
|
+
.filter((a): a is AgentConfig => a?.source === "project");
|
|
215
165
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
);
|
|
229
|
-
results.push(result);
|
|
230
|
-
|
|
231
|
-
const isError = isResultError(result);
|
|
232
|
-
if (isError) {
|
|
233
|
-
const errorMsg = formatResultFailure(result);
|
|
234
|
-
return {
|
|
235
|
-
content: [{ type: "text", text: `Chain stopped at step ${i + 1} (${step.agent}): ${errorMsg}` }],
|
|
236
|
-
details: { ...makeDetails("chain")(results), isError: true },
|
|
237
|
-
isError: true,
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
previousOutput = getResultFinalOutput(result);
|
|
241
|
-
}
|
|
166
|
+
if (projectAgentsRequested.length > 0) {
|
|
167
|
+
if (!ctx.isProjectTrusted()) {
|
|
168
|
+
throw new Error("Project-local subagent definitions require a trusted project");
|
|
169
|
+
}
|
|
170
|
+
if (confirmProjectAgents && ctx.hasUI) {
|
|
171
|
+
const names = projectAgentsRequested.map((a) => a.name).join(", ");
|
|
172
|
+
const dir = discovery.projectAgentsDir ?? "(unknown)";
|
|
173
|
+
const ok = await ctx.ui.confirm(
|
|
174
|
+
"Run project-local agents?",
|
|
175
|
+
`Agents: ${names}\nSource: ${dir}\n\nProject agents are repo-controlled. Only continue for trusted repositories.`,
|
|
176
|
+
);
|
|
177
|
+
if (!ok) {
|
|
242
178
|
return {
|
|
243
|
-
content: [{ type: "text", text:
|
|
244
|
-
details: makeDetails("chain")(
|
|
179
|
+
content: [{ type: "text", text: "Canceled: project-local agents not approved." }],
|
|
180
|
+
details: makeDetails(hasChain ? "chain" : hasTasks ? "parallel" : "single")([]),
|
|
245
181
|
};
|
|
246
|
-
} finally {
|
|
247
|
-
status.clear();
|
|
248
182
|
}
|
|
249
183
|
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
250
186
|
|
|
251
|
-
|
|
252
|
-
|
|
187
|
+
if (params.chain && params.chain.length > 0) {
|
|
188
|
+
const results: SingleResult[] = [];
|
|
189
|
+
let previousOutput = "";
|
|
190
|
+
const status = startSubagentStatus(ctx, toolCallId, chainStatus(0, params.chain.length));
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
for (let i = 0; i < params.chain.length; i++) {
|
|
194
|
+
const step = params.chain[i];
|
|
195
|
+
status.update(chainStatus(i + 1, params.chain.length, step.agent));
|
|
196
|
+
const taskWithContext = truncateUtf8(
|
|
197
|
+
step.task.replace(/\{previous\}/g, previousOutput),
|
|
198
|
+
DEFAULT_MAX_CONTEXT_BYTES,
|
|
199
|
+
).text;
|
|
200
|
+
|
|
201
|
+
// Create update callback that includes all previous results
|
|
202
|
+
const chainUpdate: OnUpdateCallback | undefined = onUpdate
|
|
203
|
+
? (partial) => {
|
|
204
|
+
// Combine completed results with current streaming result
|
|
205
|
+
const currentResult = partial.details?.results[0];
|
|
206
|
+
if (currentResult) {
|
|
207
|
+
const allResults = [...results, currentResult];
|
|
208
|
+
onUpdate({
|
|
209
|
+
content: partial.content,
|
|
210
|
+
details: makeDetails("chain")(allResults),
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
: undefined;
|
|
215
|
+
|
|
216
|
+
const result = await runSingleAgent(
|
|
217
|
+
ctx.cwd,
|
|
218
|
+
agents,
|
|
219
|
+
step.agent,
|
|
220
|
+
taskWithContext,
|
|
221
|
+
step.cwd,
|
|
222
|
+
i + 1,
|
|
223
|
+
signal,
|
|
224
|
+
resolveThinkingLevel(step.agent, step.thinkingLevel),
|
|
225
|
+
resolveTimeoutMs(step.agent, step.timeoutMs),
|
|
226
|
+
chainUpdate,
|
|
227
|
+
makeDetails("chain"),
|
|
228
|
+
);
|
|
229
|
+
results.push(result);
|
|
230
|
+
|
|
231
|
+
const isError = isResultError(result);
|
|
232
|
+
if (isError) {
|
|
233
|
+
const errorMsg = formatResultFailure(result);
|
|
253
234
|
return {
|
|
254
235
|
content: [
|
|
255
|
-
{
|
|
256
|
-
type: "text",
|
|
257
|
-
text: `Too many parallel tasks (${params.tasks.length}). Max is ${MAX_PARALLEL_TASKS}.`,
|
|
258
|
-
},
|
|
236
|
+
{ type: "text", text: `Chain stopped at step ${i + 1} (${step.agent}): ${errorMsg}` },
|
|
259
237
|
],
|
|
260
|
-
details: makeDetails("
|
|
238
|
+
details: { ...makeDetails("chain")(results), isError: true },
|
|
239
|
+
isError: true,
|
|
261
240
|
};
|
|
241
|
+
}
|
|
242
|
+
previousOutput = getResultFinalOutput(result);
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
content: [
|
|
246
|
+
{
|
|
247
|
+
type: "text",
|
|
248
|
+
text: getResultFinalOutput(results[results.length - 1]) || "(no output)",
|
|
249
|
+
},
|
|
250
|
+
],
|
|
251
|
+
details: makeDetails("chain")(results),
|
|
252
|
+
};
|
|
253
|
+
} finally {
|
|
254
|
+
status.clear();
|
|
255
|
+
}
|
|
256
|
+
}
|
|
262
257
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
task: params.tasks[i].task,
|
|
275
|
-
exitCode: -1, // -1 = still running
|
|
276
|
-
messages: [],
|
|
277
|
-
stderr: "",
|
|
278
|
-
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 },
|
|
279
|
-
thinkingLevel: resolveThinkingLevel(params.tasks[i].agent, params.tasks[i].thinkingLevel),
|
|
280
|
-
finalOutput: "",
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
let doneCount = 0;
|
|
285
|
-
let runningCount = params.tasks.length;
|
|
286
|
-
|
|
287
|
-
const emitParallelUpdate = () => {
|
|
288
|
-
status.update(parallelStatus(doneCount, allResults.length, runningCount));
|
|
289
|
-
if (onUpdate) {
|
|
290
|
-
const pendingAggregator: SingleResult | undefined =
|
|
291
|
-
aggregator && !signal?.aborted && doneCount === allResults.length
|
|
292
|
-
? {
|
|
293
|
-
agent: aggregator.agent,
|
|
294
|
-
agentSource:
|
|
295
|
-
agents.find((agent) => agent.name === aggregator.agent)?.source ?? "unknown",
|
|
296
|
-
task: aggregator.task,
|
|
297
|
-
exitCode: -1,
|
|
298
|
-
messages: [],
|
|
299
|
-
stderr: "",
|
|
300
|
-
usage: {
|
|
301
|
-
input: 0,
|
|
302
|
-
output: 0,
|
|
303
|
-
cacheRead: 0,
|
|
304
|
-
cacheWrite: 0,
|
|
305
|
-
cost: 0,
|
|
306
|
-
contextTokens: 0,
|
|
307
|
-
turns: 0,
|
|
308
|
-
},
|
|
309
|
-
thinkingLevel: resolveThinkingLevel(
|
|
310
|
-
aggregator.agent,
|
|
311
|
-
aggregator.thinkingLevel,
|
|
312
|
-
),
|
|
313
|
-
timeoutMs: resolveTimeoutMs(aggregator.agent, aggregator.timeoutMs),
|
|
314
|
-
finalOutput: "",
|
|
315
|
-
}
|
|
316
|
-
: undefined;
|
|
317
|
-
onUpdate({
|
|
318
|
-
content: [
|
|
319
|
-
{
|
|
320
|
-
type: "text",
|
|
321
|
-
text: `Parallel: ${doneCount}/${allResults.length} done, ${runningCount} running...`,
|
|
322
|
-
},
|
|
323
|
-
],
|
|
324
|
-
details: makeDetails("parallel")([...allResults], pendingAggregator),
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
};
|
|
258
|
+
if (params.tasks && params.tasks.length > 0) {
|
|
259
|
+
if (params.tasks.length > MAX_PARALLEL_TASKS)
|
|
260
|
+
return {
|
|
261
|
+
content: [
|
|
262
|
+
{
|
|
263
|
+
type: "text",
|
|
264
|
+
text: `Too many parallel tasks (${params.tasks.length}). Max is ${MAX_PARALLEL_TASKS}.`,
|
|
265
|
+
},
|
|
266
|
+
],
|
|
267
|
+
details: makeDetails("parallel")([]),
|
|
268
|
+
};
|
|
328
269
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
errorMessage: "Subagent was not started because the parent call was aborted",
|
|
362
|
-
};
|
|
363
|
-
allResults[index] = skipped;
|
|
364
|
-
doneCount += 1;
|
|
365
|
-
runningCount -= 1;
|
|
366
|
-
emitParallelUpdate();
|
|
367
|
-
return skipped;
|
|
368
|
-
});
|
|
270
|
+
const status = startSubagentStatus(
|
|
271
|
+
ctx,
|
|
272
|
+
toolCallId,
|
|
273
|
+
parallelStatus(0, params.tasks.length, params.tasks.length),
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
try {
|
|
277
|
+
// Track all results for streaming updates
|
|
278
|
+
const allResults: SingleResult[] = new Array(params.tasks.length);
|
|
279
|
+
|
|
280
|
+
// Initialize placeholder results
|
|
281
|
+
for (let i = 0; i < params.tasks.length; i++) {
|
|
282
|
+
allResults[i] = {
|
|
283
|
+
agent: params.tasks[i].agent,
|
|
284
|
+
agentSource: "unknown",
|
|
285
|
+
task: params.tasks[i].task,
|
|
286
|
+
exitCode: -1, // -1 = still running
|
|
287
|
+
messages: [],
|
|
288
|
+
stderr: "",
|
|
289
|
+
usage: {
|
|
290
|
+
input: 0,
|
|
291
|
+
output: 0,
|
|
292
|
+
cacheRead: 0,
|
|
293
|
+
cacheWrite: 0,
|
|
294
|
+
cost: 0,
|
|
295
|
+
contextTokens: 0,
|
|
296
|
+
turns: 0,
|
|
297
|
+
},
|
|
298
|
+
thinkingLevel: resolveThinkingLevel(params.tasks[i].agent, params.tasks[i].thinkingLevel),
|
|
299
|
+
finalOutput: "",
|
|
300
|
+
};
|
|
301
|
+
}
|
|
369
302
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
303
|
+
let doneCount = 0;
|
|
304
|
+
let runningCount = params.tasks.length;
|
|
305
|
+
|
|
306
|
+
const emitParallelUpdate = () => {
|
|
307
|
+
status.update(parallelStatus(doneCount, allResults.length, runningCount));
|
|
308
|
+
if (onUpdate) {
|
|
309
|
+
const pendingAggregator: SingleResult | undefined =
|
|
310
|
+
aggregator && !signal?.aborted && doneCount === allResults.length
|
|
311
|
+
? {
|
|
312
|
+
agent: aggregator.agent,
|
|
313
|
+
agentSource:
|
|
314
|
+
agents.find((agent) => agent.name === aggregator.agent)?.source ?? "unknown",
|
|
315
|
+
task: aggregator.task,
|
|
316
|
+
exitCode: -1,
|
|
317
|
+
messages: [],
|
|
318
|
+
stderr: "",
|
|
319
|
+
usage: {
|
|
320
|
+
input: 0,
|
|
321
|
+
output: 0,
|
|
322
|
+
cacheRead: 0,
|
|
323
|
+
cacheWrite: 0,
|
|
324
|
+
cost: 0,
|
|
325
|
+
contextTokens: 0,
|
|
326
|
+
turns: 0,
|
|
327
|
+
},
|
|
328
|
+
thinkingLevel: resolveThinkingLevel(aggregator.agent, aggregator.thinkingLevel),
|
|
329
|
+
timeoutMs: resolveTimeoutMs(aggregator.agent, aggregator.timeoutMs),
|
|
330
|
+
finalOutput: "",
|
|
397
331
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
const successCount = results.filter((result) => !isResultError(result)).length;
|
|
404
|
-
const summaries = results.map((result) => {
|
|
405
|
-
const failed = isResultError(result);
|
|
406
|
-
const output = getResultFinalOutput(result);
|
|
407
|
-
const error = result.errorMessage || result.stderr.trim();
|
|
408
|
-
const summaryText = failed ? formatResultFailure(result) : output || error;
|
|
409
|
-
const preview = truncateUtf8(summaryText, 160).text;
|
|
410
|
-
return `[${result.agent}] ${failed ? "failed" : "completed"}: ${preview || "(no output)"}`;
|
|
411
|
-
});
|
|
412
|
-
const aggregatorFailed = aggregatorResult ? isResultError(aggregatorResult) : false;
|
|
413
|
-
const aggregatorOutput = aggregatorResult ? getResultFinalOutput(aggregatorResult) : "";
|
|
414
|
-
const aggregatorError = aggregatorResult?.errorMessage || aggregatorResult?.stderr.trim() || "";
|
|
415
|
-
return {
|
|
332
|
+
: undefined;
|
|
333
|
+
onUpdate({
|
|
416
334
|
content: [
|
|
417
335
|
{
|
|
418
336
|
type: "text",
|
|
419
|
-
text:
|
|
420
|
-
? aggregatorFailed
|
|
421
|
-
? formatResultFailure(aggregatorResult)
|
|
422
|
-
: aggregatorOutput ||
|
|
423
|
-
aggregatorError ||
|
|
424
|
-
`(aggregator ${aggregatorResult.agent} produced no output)`
|
|
425
|
-
: `Parallel: ${successCount}/${results.length} succeeded\n\n${summaries.join("\n\n")}`,
|
|
337
|
+
text: `Parallel: ${doneCount}/${allResults.length} done, ${runningCount} running...`,
|
|
426
338
|
},
|
|
427
339
|
],
|
|
428
|
-
details:
|
|
429
|
-
|
|
430
|
-
isError: aggregatorFailed,
|
|
431
|
-
},
|
|
432
|
-
isError: aggregatorResult ? aggregatorFailed : undefined,
|
|
433
|
-
};
|
|
434
|
-
} finally {
|
|
435
|
-
status.clear();
|
|
340
|
+
details: makeDetails("parallel")([...allResults], pendingAggregator),
|
|
341
|
+
});
|
|
436
342
|
}
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
if (params.agent && params.task) {
|
|
440
|
-
const status = startSubagentStatus(ctx, toolCallId, singleStatus(params.agent));
|
|
343
|
+
};
|
|
441
344
|
|
|
442
|
-
|
|
345
|
+
const results = await mapWithConcurrencyLimit(
|
|
346
|
+
params.tasks,
|
|
347
|
+
MAX_CONCURRENCY,
|
|
348
|
+
async (t, index) => {
|
|
443
349
|
const result = await runSingleAgent(
|
|
444
350
|
ctx.cwd,
|
|
445
351
|
agents,
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
352
|
+
t.agent,
|
|
353
|
+
t.task,
|
|
354
|
+
t.cwd,
|
|
449
355
|
undefined,
|
|
450
356
|
signal,
|
|
451
|
-
resolveThinkingLevel(
|
|
452
|
-
resolveTimeoutMs(
|
|
453
|
-
|
|
454
|
-
|
|
357
|
+
resolveThinkingLevel(t.agent, t.thinkingLevel),
|
|
358
|
+
resolveTimeoutMs(t.agent, t.timeoutMs),
|
|
359
|
+
// Per-task update callback
|
|
360
|
+
(partial) => {
|
|
361
|
+
if (partial.details?.results[0]) {
|
|
362
|
+
allResults[index] = { ...partial.details.results[0], exitCode: -1 };
|
|
363
|
+
emitParallelUpdate();
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
makeDetails("parallel"),
|
|
455
367
|
);
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
368
|
+
allResults[index] = result;
|
|
369
|
+
doneCount += 1;
|
|
370
|
+
runningCount -= 1;
|
|
371
|
+
emitParallelUpdate();
|
|
372
|
+
return result;
|
|
373
|
+
},
|
|
374
|
+
signal,
|
|
375
|
+
(task, index) => {
|
|
376
|
+
const skipped: SingleResult = {
|
|
377
|
+
...allResults[index],
|
|
378
|
+
task: task.task,
|
|
379
|
+
exitCode: 130,
|
|
380
|
+
stopReason: "aborted",
|
|
381
|
+
aborted: true,
|
|
382
|
+
errorMessage: "Subagent was not started because the parent call was aborted",
|
|
468
383
|
};
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
384
|
+
allResults[index] = skipped;
|
|
385
|
+
doneCount += 1;
|
|
386
|
+
runningCount -= 1;
|
|
387
|
+
emitParallelUpdate();
|
|
388
|
+
return skipped;
|
|
389
|
+
},
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
let aggregatorResult: SingleResult | undefined;
|
|
393
|
+
if (aggregator && !signal?.aborted) {
|
|
394
|
+
status.update(fanInStatus(aggregator.agent));
|
|
395
|
+
const fanInContext = buildFanInContext(results);
|
|
396
|
+
const aggregatorTask = truncateUtf8(
|
|
397
|
+
aggregator.task.includes("{previous}")
|
|
398
|
+
? aggregator.task.replace(/\{previous\}/g, fanInContext)
|
|
399
|
+
: `${aggregator.task}\n\nParallel task outputs:\n\n${fanInContext}`,
|
|
400
|
+
DEFAULT_MAX_CONTEXT_BYTES,
|
|
401
|
+
).text;
|
|
402
|
+
aggregatorResult = await runSingleAgent(
|
|
403
|
+
ctx.cwd,
|
|
404
|
+
agents,
|
|
405
|
+
aggregator.agent,
|
|
406
|
+
aggregatorTask,
|
|
407
|
+
aggregator.cwd,
|
|
408
|
+
undefined,
|
|
409
|
+
signal,
|
|
410
|
+
resolveThinkingLevel(aggregator.agent, aggregator.thinkingLevel),
|
|
411
|
+
resolveTimeoutMs(aggregator.agent, aggregator.timeoutMs),
|
|
412
|
+
(partial) => {
|
|
413
|
+
status.update(fanInStatus(aggregator.agent));
|
|
414
|
+
if (onUpdate && partial.details?.results[0]) {
|
|
415
|
+
onUpdate({
|
|
416
|
+
content: partial.content,
|
|
417
|
+
details: makeDetails("parallel")(results, partial.details.results[0]),
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
},
|
|
421
|
+
makeDetails("parallel"),
|
|
422
|
+
);
|
|
472
423
|
}
|
|
473
424
|
|
|
474
|
-
const
|
|
425
|
+
const successCount = results.filter((result) => !isResultError(result)).length;
|
|
426
|
+
const summaries = results.map((result) => {
|
|
427
|
+
const failed = isResultError(result);
|
|
428
|
+
const output = getResultFinalOutput(result);
|
|
429
|
+
const error = result.errorMessage || result.stderr.trim();
|
|
430
|
+
const summaryText = failed ? formatResultFailure(result) : output || error;
|
|
431
|
+
const preview = truncateUtf8(summaryText, 160).text;
|
|
432
|
+
return `[${result.agent}] ${failed ? "failed" : "completed"}: ${preview || "(no output)"}`;
|
|
433
|
+
});
|
|
434
|
+
const aggregatorFailed = aggregatorResult ? isResultError(aggregatorResult) : false;
|
|
435
|
+
const aggregatorOutput = aggregatorResult ? getResultFinalOutput(aggregatorResult) : "";
|
|
436
|
+
const aggregatorError =
|
|
437
|
+
aggregatorResult?.errorMessage || aggregatorResult?.stderr.trim() || "";
|
|
438
|
+
return {
|
|
439
|
+
content: [
|
|
440
|
+
{
|
|
441
|
+
type: "text",
|
|
442
|
+
text: aggregatorResult
|
|
443
|
+
? aggregatorFailed
|
|
444
|
+
? formatResultFailure(aggregatorResult)
|
|
445
|
+
: aggregatorOutput ||
|
|
446
|
+
aggregatorError ||
|
|
447
|
+
`(aggregator ${aggregatorResult.agent} produced no output)`
|
|
448
|
+
: `Parallel: ${successCount}/${results.length} succeeded\n\n${summaries.join("\n\n")}`,
|
|
449
|
+
},
|
|
450
|
+
],
|
|
451
|
+
details: {
|
|
452
|
+
...makeDetails("parallel")(results, aggregatorResult),
|
|
453
|
+
isError: aggregatorFailed,
|
|
454
|
+
},
|
|
455
|
+
isError: aggregatorResult ? aggregatorFailed : undefined,
|
|
456
|
+
};
|
|
457
|
+
} finally {
|
|
458
|
+
status.clear();
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (params.agent && params.task) {
|
|
463
|
+
const status = startSubagentStatus(ctx, toolCallId, singleStatus(params.agent));
|
|
464
|
+
|
|
465
|
+
try {
|
|
466
|
+
const result = await runSingleAgent(
|
|
467
|
+
ctx.cwd,
|
|
468
|
+
agents,
|
|
469
|
+
params.agent,
|
|
470
|
+
params.task,
|
|
471
|
+
params.cwd,
|
|
472
|
+
undefined,
|
|
473
|
+
signal,
|
|
474
|
+
resolveThinkingLevel(params.agent, params.thinkingLevel),
|
|
475
|
+
resolveTimeoutMs(params.agent, params.timeoutMs),
|
|
476
|
+
onUpdate,
|
|
477
|
+
makeDetails("single"),
|
|
478
|
+
);
|
|
479
|
+
const isError = isResultError(result);
|
|
480
|
+
if (isError) {
|
|
481
|
+
const errorMsg = formatResultFailure(result);
|
|
482
|
+
return {
|
|
483
|
+
content: [{ type: "text", text: `Agent ${result.stopReason || "failed"}: ${errorMsg}` }],
|
|
484
|
+
details: { ...makeDetails("single")([result]), isError: true },
|
|
485
|
+
isError: true,
|
|
486
|
+
};
|
|
487
|
+
}
|
|
475
488
|
return {
|
|
476
|
-
content: [{ type: "text", text:
|
|
477
|
-
details: makeDetails("single")([]),
|
|
489
|
+
content: [{ type: "text", text: getResultFinalOutput(result) || "(no output)" }],
|
|
490
|
+
details: makeDetails("single")([result]),
|
|
478
491
|
};
|
|
492
|
+
} finally {
|
|
493
|
+
status.clear();
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const available = agents.map((a) => `${a.name} (${a.source})`).join(", ") || "none";
|
|
498
|
+
return {
|
|
499
|
+
content: [{ type: "text", text: `Invalid parameters. Available agents: ${available}` }],
|
|
500
|
+
details: makeDetails("single")([]),
|
|
501
|
+
};
|
|
479
502
|
}
|