@oh-my-pi/pi-coding-agent 6.2.0 → 6.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +46 -0
- package/docs/sdk.md +1 -1
- package/package.json +5 -5
- package/scripts/generate-template.ts +6 -6
- package/src/cli/args.ts +3 -0
- package/src/core/agent-session.ts +39 -0
- package/src/core/bash-executor.ts +3 -3
- package/src/core/cursor/exec-bridge.ts +95 -88
- package/src/core/custom-commands/bundled/review/index.ts +142 -145
- package/src/core/custom-commands/bundled/wt/index.ts +68 -66
- package/src/core/custom-commands/loader.ts +4 -6
- package/src/core/custom-tools/index.ts +2 -2
- package/src/core/custom-tools/loader.ts +66 -61
- package/src/core/custom-tools/types.ts +4 -4
- package/src/core/custom-tools/wrapper.ts +61 -25
- package/src/core/event-bus.ts +19 -47
- package/src/core/extensions/index.ts +8 -4
- package/src/core/extensions/loader.ts +160 -120
- package/src/core/extensions/types.ts +4 -4
- package/src/core/extensions/wrapper.ts +149 -100
- package/src/core/hooks/index.ts +1 -1
- package/src/core/hooks/tool-wrapper.ts +96 -70
- package/src/core/hooks/types.ts +1 -2
- package/src/core/index.ts +1 -0
- package/src/core/mcp/index.ts +6 -2
- package/src/core/mcp/json-rpc.ts +88 -0
- package/src/core/mcp/loader.ts +22 -4
- package/src/core/mcp/manager.ts +202 -48
- package/src/core/mcp/tool-bridge.ts +143 -55
- package/src/core/mcp/tool-cache.ts +122 -0
- package/src/core/python-executor.ts +3 -9
- package/src/core/sdk.ts +33 -32
- package/src/core/session-manager.ts +30 -0
- package/src/core/settings-manager.ts +34 -1
- package/src/core/ssh/ssh-executor.ts +6 -84
- package/src/core/streaming-output.ts +107 -53
- package/src/core/tools/ask.ts +92 -93
- package/src/core/tools/bash.ts +103 -94
- package/src/core/tools/calculator.ts +41 -26
- package/src/core/tools/complete.ts +76 -66
- package/src/core/tools/context.ts +22 -24
- package/src/core/tools/exa/index.ts +1 -1
- package/src/core/tools/exa/mcp-client.ts +56 -101
- package/src/core/tools/find.ts +250 -253
- package/src/core/tools/git.ts +39 -33
- package/src/core/tools/grep.ts +440 -427
- package/src/core/tools/index.ts +62 -61
- package/src/core/tools/ls.ts +119 -114
- package/src/core/tools/lsp/clients/biome-client.ts +5 -7
- package/src/core/tools/lsp/clients/index.ts +4 -4
- package/src/core/tools/lsp/clients/lsp-linter-client.ts +5 -7
- package/src/core/tools/lsp/config.ts +2 -2
- package/src/core/tools/lsp/index.ts +604 -578
- package/src/core/tools/notebook.ts +121 -119
- package/src/core/tools/output.ts +163 -147
- package/src/core/tools/patch/applicator.ts +1100 -0
- package/src/core/tools/patch/diff.ts +362 -0
- package/src/core/tools/patch/fuzzy.ts +647 -0
- package/src/core/tools/patch/index.ts +430 -0
- package/src/core/tools/patch/normalize.ts +220 -0
- package/src/core/tools/patch/normative.ts +49 -0
- package/src/core/tools/patch/parser.ts +528 -0
- package/src/core/tools/patch/shared.ts +228 -0
- package/src/core/tools/patch/types.ts +244 -0
- package/src/core/tools/python.ts +139 -136
- package/src/core/tools/read.ts +237 -216
- package/src/core/tools/render-utils.ts +196 -77
- package/src/core/tools/renderers.ts +1 -1
- package/src/core/tools/ssh.ts +99 -80
- package/src/core/tools/task/executor.ts +11 -7
- package/src/core/tools/task/index.ts +352 -343
- package/src/core/tools/task/worker.ts +13 -23
- package/src/core/tools/todo-write.ts +74 -59
- package/src/core/tools/web-fetch.ts +54 -47
- package/src/core/tools/web-search/index.ts +27 -16
- package/src/core/tools/write.ts +73 -44
- package/src/core/ttsr.ts +106 -152
- package/src/core/voice.ts +49 -39
- package/src/index.ts +16 -12
- package/src/lib/worktree/index.ts +1 -9
- package/src/modes/interactive/components/diff.ts +15 -8
- package/src/modes/interactive/components/settings-defs.ts +24 -0
- package/src/modes/interactive/components/tool-execution.ts +34 -6
- package/src/modes/interactive/controllers/event-controller.ts +6 -19
- package/src/modes/interactive/controllers/input-controller.ts +1 -1
- package/src/modes/interactive/utils/ui-helpers.ts +5 -1
- package/src/modes/rpc/rpc-mode.ts +99 -81
- package/src/prompts/tools/patch.md +76 -0
- package/src/prompts/tools/read.md +1 -1
- package/src/prompts/tools/{edit.md → replace.md} +1 -0
- package/src/utils/shell.ts +0 -40
- package/src/core/tools/edit-diff.ts +0 -574
- package/src/core/tools/edit.ts +0 -345
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* - Session artifacts for debugging
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
16
|
+
import type { AgentTool, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
17
17
|
import type { Usage } from "@oh-my-pi/pi-ai";
|
|
18
18
|
import type { Theme } from "../../../modes/interactive/theme/theme";
|
|
19
19
|
import taskDescriptionTemplate from "../../../prompts/tools/task.md" with { type: "text" };
|
|
@@ -105,387 +105,396 @@ async function buildDescription(cwd: string): Promise<string> {
|
|
|
105
105
|
});
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
export async function createTaskTool(
|
|
112
|
-
session: ToolSession,
|
|
113
|
-
): Promise<AgentTool<typeof taskSchema, TaskToolDetails, Theme>> {
|
|
114
|
-
// Check for same-agent blocking (allows other agent types)
|
|
115
|
-
const blockedAgent = process.env.OMP_BLOCKED_AGENT;
|
|
108
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
109
|
+
// Tool Class
|
|
110
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
116
111
|
|
|
117
|
-
|
|
118
|
-
|
|
112
|
+
type TaskParams = {
|
|
113
|
+
agent: string;
|
|
114
|
+
context?: string;
|
|
115
|
+
model?: string;
|
|
116
|
+
output?: unknown;
|
|
117
|
+
tasks: Array<{ id: string; task: string; description: string }>;
|
|
118
|
+
};
|
|
119
119
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
120
|
+
/**
|
|
121
|
+
* Task tool - Delegate tasks to specialized agents.
|
|
122
|
+
*
|
|
123
|
+
* Requires async initialization to discover available agents.
|
|
124
|
+
* Use `TaskTool.create(session)` to instantiate.
|
|
125
|
+
*/
|
|
126
|
+
export class TaskTool implements AgentTool<typeof taskSchema, TaskToolDetails, Theme> {
|
|
127
|
+
public readonly name = "task";
|
|
128
|
+
public readonly label = "Task";
|
|
129
|
+
public readonly description: string;
|
|
130
|
+
public readonly parameters = taskSchema;
|
|
131
|
+
public readonly renderCall = renderCall;
|
|
132
|
+
public readonly renderResult = renderResult;
|
|
133
|
+
|
|
134
|
+
private readonly session: ToolSession;
|
|
135
|
+
private readonly blockedAgent: string | undefined;
|
|
136
|
+
|
|
137
|
+
private constructor(session: ToolSession, description: string) {
|
|
138
|
+
this.session = session;
|
|
139
|
+
this.description = description;
|
|
140
|
+
this.blockedAgent = process.env.OMP_BLOCKED_AGENT;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Create a TaskTool instance with async agent discovery.
|
|
145
|
+
*/
|
|
146
|
+
public static async create(session: ToolSession): Promise<TaskTool> {
|
|
147
|
+
const description = await buildDescription(session.cwd);
|
|
148
|
+
return new TaskTool(session, description);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
public async execute(
|
|
152
|
+
_toolCallId: string,
|
|
153
|
+
params: TaskParams,
|
|
154
|
+
signal?: AbortSignal,
|
|
155
|
+
onUpdate?: AgentToolUpdateCallback<TaskToolDetails>,
|
|
156
|
+
): Promise<AgentToolResult<TaskToolDetails>> {
|
|
157
|
+
const startTime = Date.now();
|
|
158
|
+
const { agents, projectAgentsDir } = await discoverAgents(this.session.cwd);
|
|
159
|
+
const { agent: agentName, context, model, output: outputSchema } = params;
|
|
160
|
+
|
|
161
|
+
const isDefaultModelAlias = (value: string | undefined): boolean => {
|
|
162
|
+
if (!value) return true;
|
|
163
|
+
const normalized = value.trim().toLowerCase();
|
|
164
|
+
return normalized === "default" || normalized === "pi/default" || normalized === "omp/default";
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// Validate agent exists
|
|
168
|
+
const agent = getAgent(agents, agentName);
|
|
169
|
+
if (!agent) {
|
|
170
|
+
const available = agents.map((a) => a.name).join(", ") || "none";
|
|
171
|
+
return {
|
|
172
|
+
content: [
|
|
173
|
+
{
|
|
174
|
+
type: "text",
|
|
175
|
+
text: `Unknown agent "${agentName}". Available: ${available}`,
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
details: {
|
|
179
|
+
projectAgentsDir,
|
|
180
|
+
results: [],
|
|
181
|
+
totalDurationMs: 0,
|
|
182
|
+
},
|
|
136
183
|
};
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const shouldInheritSessionModel = model === undefined && isDefaultModelAlias(agent.model);
|
|
187
|
+
const sessionModel = shouldInheritSessionModel ? this.session.getActiveModelString?.() : undefined;
|
|
188
|
+
const modelOverride = model ?? sessionModel ?? this.session.getModelString?.();
|
|
189
|
+
const thinkingLevelOverride = agent.thinkingLevel;
|
|
190
|
+
|
|
191
|
+
// Output schema priority: agent frontmatter > params > inherited from parent session
|
|
192
|
+
const schemaOverridden = outputSchema !== undefined && agent.output !== undefined;
|
|
193
|
+
const effectiveOutputSchema = agent.output ?? outputSchema ?? this.session.outputSchema;
|
|
194
|
+
|
|
195
|
+
// Handle empty or missing tasks
|
|
196
|
+
if (!params.tasks || params.tasks.length === 0) {
|
|
197
|
+
return {
|
|
198
|
+
content: [
|
|
199
|
+
{
|
|
200
|
+
type: "text",
|
|
201
|
+
text: `No tasks provided. Use: { agent, context, tasks: [{id, task, description}, ...] }`,
|
|
153
202
|
},
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
203
|
+
],
|
|
204
|
+
details: {
|
|
205
|
+
projectAgentsDir,
|
|
206
|
+
results: [],
|
|
207
|
+
totalDurationMs: 0,
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Validate task count
|
|
213
|
+
if (params.tasks.length > MAX_PARALLEL_TASKS) {
|
|
214
|
+
return {
|
|
215
|
+
content: [
|
|
216
|
+
{
|
|
217
|
+
type: "text",
|
|
218
|
+
text: `Too many tasks (${params.tasks.length}). Max is ${MAX_PARALLEL_TASKS}.`,
|
|
219
|
+
},
|
|
220
|
+
],
|
|
221
|
+
details: {
|
|
222
|
+
projectAgentsDir,
|
|
223
|
+
results: [],
|
|
224
|
+
totalDurationMs: 0,
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
}
|
|
161
228
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
229
|
+
const tasks = params.tasks;
|
|
230
|
+
const missingTaskIndexes: number[] = [];
|
|
231
|
+
const idIndexes = new Map<string, number[]>();
|
|
165
232
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
totalDurationMs: 0,
|
|
179
|
-
},
|
|
180
|
-
};
|
|
233
|
+
for (let i = 0; i < tasks.length; i++) {
|
|
234
|
+
const id = tasks[i]?.id;
|
|
235
|
+
if (typeof id !== "string" || id.trim() === "") {
|
|
236
|
+
missingTaskIndexes.push(i);
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
const normalizedId = id.toLowerCase();
|
|
240
|
+
const indexes = idIndexes.get(normalizedId);
|
|
241
|
+
if (indexes) {
|
|
242
|
+
indexes.push(i);
|
|
243
|
+
} else {
|
|
244
|
+
idIndexes.set(normalizedId, [i]);
|
|
181
245
|
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const duplicateIds: Array<{ id: string; indexes: number[] }> = [];
|
|
249
|
+
for (const [normalizedId, indexes] of idIndexes.entries()) {
|
|
250
|
+
if (indexes.length > 1) {
|
|
251
|
+
duplicateIds.push({
|
|
252
|
+
id: tasks[indexes[0]]?.id ?? normalizedId,
|
|
253
|
+
indexes,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
}
|
|
182
257
|
|
|
183
|
-
|
|
184
|
-
|
|
258
|
+
if (missingTaskIndexes.length > 0 || duplicateIds.length > 0) {
|
|
259
|
+
const problems: string[] = [];
|
|
260
|
+
if (missingTaskIndexes.length > 0) {
|
|
261
|
+
problems.push(`Missing task ids at indexes: ${missingTaskIndexes.join(", ")}`);
|
|
262
|
+
}
|
|
263
|
+
if (duplicateIds.length > 0) {
|
|
264
|
+
const details = duplicateIds.map((entry) => `${entry.id} (indexes ${entry.indexes.join(", ")})`).join("; ");
|
|
265
|
+
problems.push(`Duplicate task ids detected (case-insensitive): ${details}`);
|
|
266
|
+
}
|
|
267
|
+
return {
|
|
268
|
+
content: [{ type: "text", text: `Invalid tasks: ${problems.join(". ")}` }],
|
|
269
|
+
details: {
|
|
270
|
+
projectAgentsDir,
|
|
271
|
+
results: [],
|
|
272
|
+
totalDurationMs: 0,
|
|
273
|
+
},
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Derive artifacts directory
|
|
278
|
+
const sessionFile = this.session.getSessionFile();
|
|
279
|
+
const artifactsDir = sessionFile ? getArtifactsDir(sessionFile) : null;
|
|
280
|
+
const tempArtifactsDir = artifactsDir ? null : createTempArtifactsDir();
|
|
281
|
+
const effectiveArtifactsDir = artifactsDir || tempArtifactsDir!;
|
|
282
|
+
|
|
283
|
+
// Initialize progress tracking
|
|
284
|
+
const progressMap = new Map<number, AgentProgress>();
|
|
285
|
+
|
|
286
|
+
// Update callback
|
|
287
|
+
const emitProgress = () => {
|
|
288
|
+
const progress = Array.from(progressMap.values()).sort((a, b) => a.index - b.index);
|
|
289
|
+
onUpdate?.({
|
|
290
|
+
content: [{ type: "text", text: `Running ${params.tasks.length} agents...` }],
|
|
291
|
+
details: {
|
|
292
|
+
projectAgentsDir,
|
|
293
|
+
results: [],
|
|
294
|
+
totalDurationMs: Date.now() - startTime,
|
|
295
|
+
progress,
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
try {
|
|
301
|
+
// Check self-recursion prevention
|
|
302
|
+
if (this.blockedAgent && agentName === this.blockedAgent) {
|
|
185
303
|
return {
|
|
186
304
|
content: [
|
|
187
305
|
{
|
|
188
306
|
type: "text",
|
|
189
|
-
text: `
|
|
307
|
+
text: `Cannot spawn ${this.blockedAgent} agent from within itself (recursion prevention). Use a different agent type.`,
|
|
190
308
|
},
|
|
191
309
|
],
|
|
192
310
|
details: {
|
|
193
311
|
projectAgentsDir,
|
|
194
312
|
results: [],
|
|
195
|
-
totalDurationMs:
|
|
313
|
+
totalDurationMs: Date.now() - startTime,
|
|
196
314
|
},
|
|
197
315
|
};
|
|
198
316
|
}
|
|
199
317
|
|
|
200
|
-
|
|
201
|
-
const
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
continue;
|
|
209
|
-
}
|
|
210
|
-
const normalizedId = id.toLowerCase();
|
|
211
|
-
const indexes = idIndexes.get(normalizedId);
|
|
212
|
-
if (indexes) {
|
|
213
|
-
indexes.push(i);
|
|
214
|
-
} else {
|
|
215
|
-
idIndexes.set(normalizedId, [i]);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
const duplicateIds: Array<{ id: string; indexes: number[] }> = [];
|
|
220
|
-
for (const [normalizedId, indexes] of idIndexes.entries()) {
|
|
221
|
-
if (indexes.length > 1) {
|
|
222
|
-
duplicateIds.push({
|
|
223
|
-
id: tasks[indexes[0]]?.id ?? normalizedId,
|
|
224
|
-
indexes,
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
}
|
|
318
|
+
// Check spawn restrictions from parent
|
|
319
|
+
const parentSpawns = this.session.getSessionSpawns() ?? "*";
|
|
320
|
+
const allowedSpawns = parentSpawns.split(",").map((s) => s.trim());
|
|
321
|
+
const isSpawnAllowed = (): boolean => {
|
|
322
|
+
if (parentSpawns === "") return false; // Empty = deny all
|
|
323
|
+
if (parentSpawns === "*") return true; // Wildcard = allow all
|
|
324
|
+
return allowedSpawns.includes(agentName);
|
|
325
|
+
};
|
|
228
326
|
|
|
229
|
-
if (
|
|
230
|
-
const
|
|
231
|
-
if (missingTaskIndexes.length > 0) {
|
|
232
|
-
problems.push(`Missing task ids at indexes: ${missingTaskIndexes.join(", ")}`);
|
|
233
|
-
}
|
|
234
|
-
if (duplicateIds.length > 0) {
|
|
235
|
-
const details = duplicateIds
|
|
236
|
-
.map((entry) => `${entry.id} (indexes ${entry.indexes.join(", ")})`)
|
|
237
|
-
.join("; ");
|
|
238
|
-
problems.push(`Duplicate task ids detected (case-insensitive): ${details}`);
|
|
239
|
-
}
|
|
327
|
+
if (!isSpawnAllowed()) {
|
|
328
|
+
const allowed = parentSpawns === "" ? "none (spawns disabled for this agent)" : parentSpawns;
|
|
240
329
|
return {
|
|
241
|
-
content: [{ type: "text", text: `
|
|
242
|
-
details: {
|
|
243
|
-
projectAgentsDir,
|
|
244
|
-
results: [],
|
|
245
|
-
totalDurationMs: 0,
|
|
246
|
-
},
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// Derive artifacts directory
|
|
251
|
-
const sessionFile = session.getSessionFile();
|
|
252
|
-
const artifactsDir = sessionFile ? getArtifactsDir(sessionFile) : null;
|
|
253
|
-
const tempArtifactsDir = artifactsDir ? null : createTempArtifactsDir();
|
|
254
|
-
const effectiveArtifactsDir = artifactsDir || tempArtifactsDir!;
|
|
255
|
-
|
|
256
|
-
// Initialize progress tracking
|
|
257
|
-
const progressMap = new Map<number, AgentProgress>();
|
|
258
|
-
|
|
259
|
-
// Update callback
|
|
260
|
-
const emitProgress = () => {
|
|
261
|
-
const progress = Array.from(progressMap.values()).sort((a, b) => a.index - b.index);
|
|
262
|
-
onUpdate?.({
|
|
263
|
-
content: [{ type: "text", text: `Running ${params.tasks.length} agents...` }],
|
|
330
|
+
content: [{ type: "text", text: `Cannot spawn '${agentName}'. Allowed: ${allowed}` }],
|
|
264
331
|
details: {
|
|
265
332
|
projectAgentsDir,
|
|
266
333
|
results: [],
|
|
267
334
|
totalDurationMs: Date.now() - startTime,
|
|
268
|
-
progress,
|
|
269
335
|
},
|
|
270
|
-
});
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
try {
|
|
274
|
-
// Check self-recursion prevention
|
|
275
|
-
if (blockedAgent && agentName === blockedAgent) {
|
|
276
|
-
return {
|
|
277
|
-
content: [
|
|
278
|
-
{
|
|
279
|
-
type: "text",
|
|
280
|
-
text: `Cannot spawn ${blockedAgent} agent from within itself (recursion prevention). Use a different agent type.`,
|
|
281
|
-
},
|
|
282
|
-
],
|
|
283
|
-
details: {
|
|
284
|
-
projectAgentsDir,
|
|
285
|
-
results: [],
|
|
286
|
-
totalDurationMs: Date.now() - startTime,
|
|
287
|
-
},
|
|
288
|
-
};
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Check spawn restrictions from parent
|
|
292
|
-
const parentSpawns = session.getSessionSpawns() ?? "*";
|
|
293
|
-
const allowedSpawns = parentSpawns.split(",").map((s) => s.trim());
|
|
294
|
-
const isSpawnAllowed = (): boolean => {
|
|
295
|
-
if (parentSpawns === "") return false; // Empty = deny all
|
|
296
|
-
if (parentSpawns === "*") return true; // Wildcard = allow all
|
|
297
|
-
return allowedSpawns.includes(agentName);
|
|
298
336
|
};
|
|
337
|
+
}
|
|
299
338
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
339
|
+
// Build full prompts with context prepended
|
|
340
|
+
const tasksWithContext = tasks.map((t) => ({
|
|
341
|
+
task: context ? `${context}\n\n${t.task}` : t.task,
|
|
342
|
+
description: t.description,
|
|
343
|
+
taskId: t.id,
|
|
344
|
+
}));
|
|
345
|
+
|
|
346
|
+
// Initialize progress for all tasks
|
|
347
|
+
for (let i = 0; i < tasksWithContext.length; i++) {
|
|
348
|
+
const t = tasksWithContext[i];
|
|
349
|
+
progressMap.set(i, {
|
|
350
|
+
index: i,
|
|
351
|
+
taskId: t.taskId,
|
|
352
|
+
agent: agentName,
|
|
353
|
+
agentSource: agent.source,
|
|
354
|
+
status: "pending",
|
|
355
|
+
task: t.task,
|
|
356
|
+
recentTools: [],
|
|
357
|
+
recentOutput: [],
|
|
358
|
+
toolCount: 0,
|
|
359
|
+
tokens: 0,
|
|
360
|
+
durationMs: 0,
|
|
361
|
+
modelOverride,
|
|
315
362
|
description: t.description,
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
task: t.task,
|
|
329
|
-
recentTools: [],
|
|
330
|
-
recentOutput: [],
|
|
331
|
-
toolCount: 0,
|
|
332
|
-
tokens: 0,
|
|
333
|
-
durationMs: 0,
|
|
334
|
-
modelOverride,
|
|
335
|
-
description: t.description,
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
emitProgress();
|
|
339
|
-
|
|
340
|
-
// Execute in parallel with concurrency limit
|
|
341
|
-
const { results: partialResults, aborted } = await mapWithConcurrencyLimit(
|
|
342
|
-
tasksWithContext,
|
|
343
|
-
MAX_CONCURRENCY,
|
|
344
|
-
async (task, index) => {
|
|
345
|
-
return runSubprocess({
|
|
346
|
-
cwd: session.cwd,
|
|
347
|
-
agent,
|
|
348
|
-
task: task.task,
|
|
349
|
-
description: task.description,
|
|
350
|
-
index,
|
|
351
|
-
taskId: task.taskId,
|
|
352
|
-
context: undefined, // Already prepended above
|
|
353
|
-
modelOverride,
|
|
354
|
-
thinkingLevel: thinkingLevelOverride,
|
|
355
|
-
outputSchema: effectiveOutputSchema,
|
|
356
|
-
sessionFile,
|
|
357
|
-
persistArtifacts: !!artifactsDir,
|
|
358
|
-
artifactsDir: effectiveArtifactsDir,
|
|
359
|
-
enableLsp: false,
|
|
360
|
-
signal,
|
|
361
|
-
eventBus: undefined,
|
|
362
|
-
onProgress: (progress) => {
|
|
363
|
-
progressMap.set(index, structuredClone(progress));
|
|
364
|
-
emitProgress();
|
|
365
|
-
},
|
|
366
|
-
authStorage: session.authStorage,
|
|
367
|
-
modelRegistry: session.modelRegistry,
|
|
368
|
-
settingsManager: session.settingsManager,
|
|
369
|
-
mcpManager: session.mcpManager,
|
|
370
|
-
});
|
|
371
|
-
},
|
|
372
|
-
signal,
|
|
373
|
-
);
|
|
374
|
-
|
|
375
|
-
// Fill in skipped tasks (undefined entries from abort) with placeholder results
|
|
376
|
-
const results: SingleResult[] = partialResults.map((result, index) => {
|
|
377
|
-
if (result !== undefined) return result;
|
|
378
|
-
const task = tasksWithContext[index];
|
|
379
|
-
return {
|
|
380
|
-
index,
|
|
381
|
-
taskId: task.taskId,
|
|
382
|
-
agent: agentName,
|
|
383
|
-
agentSource: agent.source,
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
emitProgress();
|
|
366
|
+
|
|
367
|
+
// Execute in parallel with concurrency limit
|
|
368
|
+
const { results: partialResults, aborted } = await mapWithConcurrencyLimit(
|
|
369
|
+
tasksWithContext,
|
|
370
|
+
MAX_CONCURRENCY,
|
|
371
|
+
async (task, index) => {
|
|
372
|
+
return runSubprocess({
|
|
373
|
+
cwd: this.session.cwd,
|
|
374
|
+
agent,
|
|
384
375
|
task: task.task,
|
|
385
376
|
description: task.description,
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
truncated: false,
|
|
390
|
-
durationMs: 0,
|
|
391
|
-
tokens: 0,
|
|
377
|
+
index,
|
|
378
|
+
taskId: task.taskId,
|
|
379
|
+
context: undefined, // Already prepended above
|
|
392
380
|
modelOverride,
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
381
|
+
thinkingLevel: thinkingLevelOverride,
|
|
382
|
+
outputSchema: effectiveOutputSchema,
|
|
383
|
+
sessionFile,
|
|
384
|
+
persistArtifacts: !!artifactsDir,
|
|
385
|
+
artifactsDir: effectiveArtifactsDir,
|
|
386
|
+
enableLsp: false,
|
|
387
|
+
signal,
|
|
388
|
+
eventBus: undefined,
|
|
389
|
+
onProgress: (progress) => {
|
|
390
|
+
progressMap.set(index, structuredClone(progress));
|
|
391
|
+
emitProgress();
|
|
392
|
+
},
|
|
393
|
+
authStorage: this.session.authStorage,
|
|
394
|
+
modelRegistry: this.session.modelRegistry,
|
|
395
|
+
settingsManager: this.session.settingsManager,
|
|
396
|
+
mcpManager: this.session.mcpManager,
|
|
397
|
+
});
|
|
398
|
+
},
|
|
399
|
+
signal,
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
// Fill in skipped tasks (undefined entries from abort) with placeholder results
|
|
403
|
+
const results: SingleResult[] = partialResults.map((result, index) => {
|
|
404
|
+
if (result !== undefined) return result;
|
|
405
|
+
const task = tasksWithContext[index];
|
|
406
|
+
return {
|
|
407
|
+
index,
|
|
408
|
+
taskId: task.taskId,
|
|
409
|
+
agent: agentName,
|
|
410
|
+
agentSource: agent.source,
|
|
411
|
+
task: task.task,
|
|
412
|
+
description: task.description,
|
|
413
|
+
exitCode: 1,
|
|
414
|
+
output: "",
|
|
415
|
+
stderr: "Skipped (cancelled before start)",
|
|
416
|
+
truncated: false,
|
|
417
|
+
durationMs: 0,
|
|
418
|
+
tokens: 0,
|
|
419
|
+
modelOverride,
|
|
420
|
+
error: "Skipped",
|
|
421
|
+
aborted: true,
|
|
422
|
+
};
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
// Aggregate usage from executor results (already accumulated incrementally)
|
|
426
|
+
const aggregatedUsage = createUsageTotals();
|
|
427
|
+
let hasAggregatedUsage = false;
|
|
428
|
+
for (const result of results) {
|
|
429
|
+
if (result.usage) {
|
|
430
|
+
addUsageTotals(aggregatedUsage, result.usage);
|
|
431
|
+
hasAggregatedUsage = true;
|
|
406
432
|
}
|
|
433
|
+
}
|
|
407
434
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
}
|
|
435
|
+
// Collect output paths (artifacts already written by executor in real-time)
|
|
436
|
+
const outputPaths: string[] = [];
|
|
437
|
+
for (const result of results) {
|
|
438
|
+
if (result.artifactPaths) {
|
|
439
|
+
outputPaths.push(result.artifactPaths.outputPath);
|
|
414
440
|
}
|
|
441
|
+
}
|
|
415
442
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
: "";
|
|
428
|
-
return `[${r.agent}] ${status}${meta} ${r.taskId}\n${preview}`;
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
const outputIds = results.filter((r) => !r.aborted || r.output.trim()).map((r) => r.taskId);
|
|
432
|
-
const outputHint =
|
|
433
|
-
outputIds.length > 0 ? `\n\nUse output tool for full logs: output ids ${outputIds.join(", ")}` : "";
|
|
434
|
-
const schemaNote = schemaOverridden
|
|
435
|
-
? `\n\nNote: Agent '${agentName}' has a fixed output schema; your 'output' parameter was ignored.\nRequired schema: ${JSON.stringify(agent.output)}`
|
|
443
|
+
// Build final output - match plugin format
|
|
444
|
+
const successCount = results.filter((r) => r.exitCode === 0).length;
|
|
445
|
+
const cancelledCount = results.filter((r) => r.aborted).length;
|
|
446
|
+
const totalDuration = Date.now() - startTime;
|
|
447
|
+
|
|
448
|
+
const summaries = results.map((r) => {
|
|
449
|
+
const status = r.aborted ? "cancelled" : r.exitCode === 0 ? "completed" : `failed (exit ${r.exitCode})`;
|
|
450
|
+
const output = r.output.trim() || r.stderr.trim() || "(no output)";
|
|
451
|
+
const preview = output.split("\n").slice(0, 5).join("\n");
|
|
452
|
+
const meta = r.outputMeta
|
|
453
|
+
? ` [${r.outputMeta.lineCount} lines, ${formatBytes(r.outputMeta.charCount)}]`
|
|
436
454
|
: "";
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
},
|
|
456
|
-
};
|
|
457
|
-
} catch (err) {
|
|
458
|
-
// Cleanup temp directory on error
|
|
459
|
-
if (tempArtifactsDir) {
|
|
460
|
-
await cleanupTempDir(tempArtifactsDir);
|
|
461
|
-
}
|
|
455
|
+
return `[${r.agent}] ${status}${meta} ${r.taskId}\n${preview}`;
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
const outputIds = results.filter((r) => !r.aborted || r.output.trim()).map((r) => r.taskId);
|
|
459
|
+
const outputHint =
|
|
460
|
+
outputIds.length > 0 ? `\n\nUse output tool for full logs: output ids ${outputIds.join(", ")}` : "";
|
|
461
|
+
const schemaNote = schemaOverridden
|
|
462
|
+
? `\n\nNote: Agent '${agentName}' has a fixed output schema; your 'output' parameter was ignored.\nRequired schema: ${JSON.stringify(agent.output)}`
|
|
463
|
+
: "";
|
|
464
|
+
const cancelledNote = aborted && cancelledCount > 0 ? ` (${cancelledCount} cancelled)` : "";
|
|
465
|
+
const summary = `${successCount}/${results.length} succeeded${cancelledNote} [${formatDuration(
|
|
466
|
+
totalDuration,
|
|
467
|
+
)}]\n\n${summaries.join("\n\n---\n\n")}${outputHint}${schemaNote}`;
|
|
468
|
+
|
|
469
|
+
// Cleanup temp directory if used
|
|
470
|
+
if (tempArtifactsDir) {
|
|
471
|
+
await cleanupTempDir(tempArtifactsDir);
|
|
472
|
+
}
|
|
462
473
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
474
|
+
return {
|
|
475
|
+
content: [{ type: "text", text: summary }],
|
|
476
|
+
details: {
|
|
477
|
+
projectAgentsDir,
|
|
478
|
+
results: results,
|
|
479
|
+
totalDurationMs: totalDuration,
|
|
480
|
+
usage: hasAggregatedUsage ? aggregatedUsage : undefined,
|
|
481
|
+
outputPaths,
|
|
482
|
+
},
|
|
483
|
+
};
|
|
484
|
+
} catch (err) {
|
|
485
|
+
// Cleanup temp directory on error
|
|
486
|
+
if (tempArtifactsDir) {
|
|
487
|
+
await cleanupTempDir(tempArtifactsDir);
|
|
471
488
|
}
|
|
472
|
-
},
|
|
473
|
-
};
|
|
474
|
-
}
|
|
475
489
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
results: [],
|
|
488
|
-
totalDurationMs: 0,
|
|
489
|
-
},
|
|
490
|
-
}),
|
|
491
|
-
};
|
|
490
|
+
return {
|
|
491
|
+
content: [{ type: "text", text: `Task execution failed: ${err}` }],
|
|
492
|
+
details: {
|
|
493
|
+
projectAgentsDir,
|
|
494
|
+
results: [],
|
|
495
|
+
totalDurationMs: Date.now() - startTime,
|
|
496
|
+
},
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|