@compilr-dev/agents 0.2.1 → 0.3.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/dist/agent.d.ts +8 -0
- package/dist/agent.js +187 -121
- package/dist/anchors/manager.d.ts +34 -0
- package/dist/anchors/manager.js +86 -0
- package/dist/anchors/types.d.ts +29 -0
- package/dist/skills/index.js +2 -179
- package/dist/tools/builtin/ask-user-simple.d.ts +64 -0
- package/dist/tools/builtin/ask-user-simple.js +149 -0
- package/dist/tools/builtin/ask-user.d.ts +85 -0
- package/dist/tools/builtin/ask-user.js +195 -0
- package/dist/tools/builtin/backlog.d.ts +121 -0
- package/dist/tools/builtin/backlog.js +368 -0
- package/dist/tools/builtin/bash.js +180 -13
- package/dist/tools/builtin/index.d.ts +11 -1
- package/dist/tools/builtin/index.js +16 -0
- package/dist/tools/builtin/task.d.ts +14 -4
- package/dist/tools/builtin/task.js +9 -9
- package/dist/tools/define.d.ts +7 -0
- package/dist/tools/define.js +1 -0
- package/dist/tools/registry.d.ts +3 -2
- package/dist/tools/registry.js +19 -6
- package/dist/tools/types.d.ts +29 -2
- package/package.json +1 -1
|
@@ -116,7 +116,7 @@ export const bashTool = defineTool({
|
|
|
116
116
|
/**
|
|
117
117
|
* Execute the bash tool
|
|
118
118
|
*/
|
|
119
|
-
async function executeBash(input) {
|
|
119
|
+
async function executeBash(input, context) {
|
|
120
120
|
const { command, cwd, timeout = DEFAULT_TIMEOUT, env, run_in_background } = input;
|
|
121
121
|
// Detect FIFO usage and add warning
|
|
122
122
|
const fifoCheck = detectFifoUsage(command);
|
|
@@ -124,6 +124,17 @@ async function executeBash(input) {
|
|
|
124
124
|
if (run_in_background) {
|
|
125
125
|
return executeInBackground(command, { cwd, env });
|
|
126
126
|
}
|
|
127
|
+
// Use streaming execution if context.onOutput is provided
|
|
128
|
+
if (context?.onOutput) {
|
|
129
|
+
return executeWithStreaming(command, {
|
|
130
|
+
cwd,
|
|
131
|
+
timeout,
|
|
132
|
+
env,
|
|
133
|
+
onOutput: context.onOutput,
|
|
134
|
+
fifoCheck,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
// Non-streaming execution (original behavior)
|
|
127
138
|
try {
|
|
128
139
|
const result = await execAsync(command, {
|
|
129
140
|
cwd,
|
|
@@ -164,6 +175,154 @@ async function executeBash(input) {
|
|
|
164
175
|
return createErrorResult(error instanceof Error ? error.message : String(error));
|
|
165
176
|
}
|
|
166
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* Execute command with streaming output to onOutput callback
|
|
180
|
+
*/
|
|
181
|
+
async function executeWithStreaming(command, options) {
|
|
182
|
+
const { cwd, timeout = DEFAULT_TIMEOUT, env, onOutput, fifoCheck } = options;
|
|
183
|
+
return new Promise((resolve) => {
|
|
184
|
+
const child = spawn(command, [], {
|
|
185
|
+
cwd,
|
|
186
|
+
shell: '/bin/bash',
|
|
187
|
+
env: env ? { ...process.env, ...env } : undefined,
|
|
188
|
+
});
|
|
189
|
+
let stdoutBuffer = '';
|
|
190
|
+
let stderrBuffer = '';
|
|
191
|
+
let timedOut = false;
|
|
192
|
+
// Set up timeout
|
|
193
|
+
const timeoutId = timeout > 0
|
|
194
|
+
? setTimeout(() => {
|
|
195
|
+
timedOut = true;
|
|
196
|
+
child.kill('SIGTERM');
|
|
197
|
+
// Force kill after 5 seconds if not dead
|
|
198
|
+
setTimeout(() => child.kill('SIGKILL'), 5000);
|
|
199
|
+
}, timeout)
|
|
200
|
+
: undefined;
|
|
201
|
+
// Stream stdout
|
|
202
|
+
child.stdout.on('data', (data) => {
|
|
203
|
+
const text = data.toString();
|
|
204
|
+
stdoutBuffer += text;
|
|
205
|
+
// Emit each line separately for better UI updates
|
|
206
|
+
const lines = text.split('\n');
|
|
207
|
+
for (const line of lines) {
|
|
208
|
+
if (line.length > 0) {
|
|
209
|
+
onOutput(line, 'stdout');
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
// Stream stderr
|
|
214
|
+
child.stderr.on('data', (data) => {
|
|
215
|
+
const text = data.toString();
|
|
216
|
+
stderrBuffer += text;
|
|
217
|
+
// Emit each line separately
|
|
218
|
+
const lines = text.split('\n');
|
|
219
|
+
for (const line of lines) {
|
|
220
|
+
if (line.length > 0) {
|
|
221
|
+
onOutput(line, 'stderr');
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
// Handle completion
|
|
226
|
+
child.on('close', (code) => {
|
|
227
|
+
if (timeoutId)
|
|
228
|
+
clearTimeout(timeoutId);
|
|
229
|
+
if (timedOut) {
|
|
230
|
+
resolve(createErrorResult(`Command timed out after ${String(timeout)}ms`));
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
// Truncate for final result
|
|
234
|
+
const stdout = truncateOutput(stdoutBuffer, DEFAULT_MAX_OUTPUT_SIZE);
|
|
235
|
+
const stderr = truncateOutput(stderrBuffer, DEFAULT_MAX_OUTPUT_SIZE);
|
|
236
|
+
resolve(createSuccessResult({
|
|
237
|
+
stdout: stdout.content,
|
|
238
|
+
stderr: stderr.content,
|
|
239
|
+
exitCode: code ?? 0,
|
|
240
|
+
truncated: stdout.truncated || stderr.truncated,
|
|
241
|
+
...(fifoCheck.detected && { fifoWarning: fifoCheck.warning }),
|
|
242
|
+
}));
|
|
243
|
+
});
|
|
244
|
+
// Handle spawn errors
|
|
245
|
+
child.on('error', (error) => {
|
|
246
|
+
if (timeoutId)
|
|
247
|
+
clearTimeout(timeoutId);
|
|
248
|
+
resolve(createErrorResult(error.message));
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Execute command with streaming output (custom options version for createBashTool)
|
|
254
|
+
*/
|
|
255
|
+
async function executeWithStreamingCustom(command, options) {
|
|
256
|
+
const { cwd, timeout = DEFAULT_TIMEOUT, env, onOutput, fifoCheck, maxOutputSize, shell, } = options;
|
|
257
|
+
return new Promise((resolve) => {
|
|
258
|
+
const child = spawn(command, [], {
|
|
259
|
+
cwd,
|
|
260
|
+
shell,
|
|
261
|
+
env: env ? { ...process.env, ...env } : undefined,
|
|
262
|
+
});
|
|
263
|
+
let stdoutBuffer = '';
|
|
264
|
+
let stderrBuffer = '';
|
|
265
|
+
let timedOut = false;
|
|
266
|
+
// Set up timeout
|
|
267
|
+
const timeoutId = timeout > 0
|
|
268
|
+
? setTimeout(() => {
|
|
269
|
+
timedOut = true;
|
|
270
|
+
child.kill('SIGTERM');
|
|
271
|
+
// Force kill after 5 seconds if not dead
|
|
272
|
+
setTimeout(() => child.kill('SIGKILL'), 5000);
|
|
273
|
+
}, timeout)
|
|
274
|
+
: undefined;
|
|
275
|
+
// Stream stdout
|
|
276
|
+
child.stdout.on('data', (data) => {
|
|
277
|
+
const text = data.toString();
|
|
278
|
+
stdoutBuffer += text;
|
|
279
|
+
// Emit each line separately for better UI updates
|
|
280
|
+
const lines = text.split('\n');
|
|
281
|
+
for (const line of lines) {
|
|
282
|
+
if (line.length > 0) {
|
|
283
|
+
onOutput(line, 'stdout');
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
// Stream stderr
|
|
288
|
+
child.stderr.on('data', (data) => {
|
|
289
|
+
const text = data.toString();
|
|
290
|
+
stderrBuffer += text;
|
|
291
|
+
// Emit each line separately
|
|
292
|
+
const lines = text.split('\n');
|
|
293
|
+
for (const line of lines) {
|
|
294
|
+
if (line.length > 0) {
|
|
295
|
+
onOutput(line, 'stderr');
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
// Handle completion
|
|
300
|
+
child.on('close', (code) => {
|
|
301
|
+
if (timeoutId)
|
|
302
|
+
clearTimeout(timeoutId);
|
|
303
|
+
if (timedOut) {
|
|
304
|
+
resolve(createErrorResult(`Command timed out after ${String(timeout)}ms`));
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
// Truncate for final result
|
|
308
|
+
const stdout = truncateOutput(stdoutBuffer, maxOutputSize);
|
|
309
|
+
const stderr = truncateOutput(stderrBuffer, maxOutputSize);
|
|
310
|
+
resolve(createSuccessResult({
|
|
311
|
+
stdout: stdout.content,
|
|
312
|
+
stderr: stderr.content,
|
|
313
|
+
exitCode: code ?? 0,
|
|
314
|
+
truncated: stdout.truncated || stderr.truncated,
|
|
315
|
+
...(fifoCheck.detected && { fifoWarning: fifoCheck.warning }),
|
|
316
|
+
}));
|
|
317
|
+
});
|
|
318
|
+
// Handle spawn errors
|
|
319
|
+
child.on('error', (error) => {
|
|
320
|
+
if (timeoutId)
|
|
321
|
+
clearTimeout(timeoutId);
|
|
322
|
+
resolve(createErrorResult(error.message));
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
}
|
|
167
326
|
/**
|
|
168
327
|
* Execute command in background and return shell ID
|
|
169
328
|
*/
|
|
@@ -234,7 +393,7 @@ export function createBashTool(options) {
|
|
|
234
393
|
},
|
|
235
394
|
required: ['command'],
|
|
236
395
|
},
|
|
237
|
-
execute: async (input) => {
|
|
396
|
+
execute: async (input, context) => {
|
|
238
397
|
const { cwd: defaultCwd, timeout: defaultTimeout = DEFAULT_TIMEOUT, maxOutputSize = DEFAULT_MAX_OUTPUT_SIZE, blockedCommands = [], restrictToAllowed = false, allowedCommands = [], shell = '/bin/bash', shellManager, fifoMode = 'warn', } = options ?? {};
|
|
239
398
|
const command = input.command.trim();
|
|
240
399
|
// Detect FIFO usage
|
|
@@ -274,19 +433,27 @@ export function createBashTool(options) {
|
|
|
274
433
|
return createErrorResult(error instanceof Error ? error.message : String(error));
|
|
275
434
|
}
|
|
276
435
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
436
|
+
const timeout = input.timeout ?? defaultTimeout;
|
|
437
|
+
// Use streaming execution if context.onOutput is provided
|
|
438
|
+
if (context?.onOutput) {
|
|
439
|
+
return executeWithStreamingCustom(command, {
|
|
440
|
+
cwd: input.cwd ?? defaultCwd,
|
|
441
|
+
timeout,
|
|
442
|
+
env: input.env,
|
|
443
|
+
onOutput: context.onOutput,
|
|
444
|
+
fifoCheck,
|
|
445
|
+
maxOutputSize,
|
|
446
|
+
shell,
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
// Execute with merged options (non-streaming)
|
|
283
450
|
try {
|
|
284
|
-
const result = await execAsync(
|
|
285
|
-
cwd:
|
|
286
|
-
timeout
|
|
451
|
+
const result = await execAsync(command, {
|
|
452
|
+
cwd: input.cwd ?? defaultCwd,
|
|
453
|
+
timeout,
|
|
287
454
|
maxBuffer: MAX_BUFFER_SIZE,
|
|
288
455
|
shell,
|
|
289
|
-
env:
|
|
456
|
+
env: input.env ? { ...process.env, ...input.env } : undefined,
|
|
290
457
|
});
|
|
291
458
|
// Truncate output to prevent memory bloat
|
|
292
459
|
const stdout = truncateOutput(result.stdout, maxOutputSize);
|
|
@@ -302,7 +469,7 @@ export function createBashTool(options) {
|
|
|
302
469
|
catch (error) {
|
|
303
470
|
if (isExecError(error)) {
|
|
304
471
|
if (error.killed) {
|
|
305
|
-
return createErrorResult(`Command timed out after ${String(
|
|
472
|
+
return createErrorResult(`Command timed out after ${String(timeout)}ms`);
|
|
306
473
|
}
|
|
307
474
|
// Truncate output even on error
|
|
308
475
|
const stdout = truncateOutput(error.stdout ?? '', maxOutputSize);
|
|
@@ -29,6 +29,12 @@ export { webFetchTool, createWebFetchTool } from './web-fetch.js';
|
|
|
29
29
|
export type { WebFetchInput, WebFetchResult, WebFetchOptions } from './web-fetch.js';
|
|
30
30
|
export { suggestTool, createSuggestTool } from './suggest.js';
|
|
31
31
|
export type { SuggestInput, SuggestToolOptions } from './suggest.js';
|
|
32
|
+
export { askUserTool, createAskUserTool } from './ask-user.js';
|
|
33
|
+
export type { AskUserInput, AskUserResult, AskUserQuestion, AskUserOption, AskUserToolOptions, } from './ask-user.js';
|
|
34
|
+
export { askUserSimpleTool, createAskUserSimpleTool } from './ask-user-simple.js';
|
|
35
|
+
export type { AskUserSimpleInput, AskUserSimpleResult, AskUserSimpleToolOptions, } from './ask-user-simple.js';
|
|
36
|
+
export { backlogReadTool, backlogWriteTool, createBacklogTools } from './backlog.js';
|
|
37
|
+
export type { BacklogItem, BacklogStatus, BacklogItemType, BacklogPriority, BacklogReadInput, BacklogReadResult, BacklogWriteInput, BacklogWriteResult, BacklogToolOptions, } from './backlog.js';
|
|
32
38
|
export declare const builtinTools: {
|
|
33
39
|
readonly readFile: import("../types.js").Tool<import("./read-file.js").ReadFileInput>;
|
|
34
40
|
readonly writeFile: import("../types.js").Tool<import("./write-file.js").WriteFileInput>;
|
|
@@ -42,8 +48,12 @@ export declare const builtinTools: {
|
|
|
42
48
|
readonly todoRead: import("../types.js").Tool<import("./todo.js").TodoReadInput>;
|
|
43
49
|
readonly webFetch: import("../types.js").Tool<import("./web-fetch.js").WebFetchInput>;
|
|
44
50
|
readonly suggest: import("../types.js").Tool<import("./suggest.js").SuggestInput>;
|
|
51
|
+
readonly askUser: import("../types.js").Tool<import("./ask-user.js").AskUserInput>;
|
|
52
|
+
readonly askUserSimple: import("../types.js").Tool<import("./ask-user-simple.js").AskUserSimpleInput>;
|
|
53
|
+
readonly backlogRead: import("../types.js").Tool<import("./backlog.js").BacklogReadInput>;
|
|
54
|
+
readonly backlogWrite: import("../types.js").Tool<import("./backlog.js").BacklogWriteInput>;
|
|
45
55
|
};
|
|
46
56
|
/**
|
|
47
57
|
* Array of all built-in tools
|
|
48
58
|
*/
|
|
49
|
-
export declare const allBuiltinTools: readonly [import("../types.js").Tool<import("./read-file.js").ReadFileInput>, import("../types.js").Tool<import("./write-file.js").WriteFileInput>, import("../types.js").Tool<import("./bash.js").BashInput>, import("../types.js").Tool<import("./bash-output.js").BashOutputInput>, import("../types.js").Tool<import("./kill-shell.js").KillShellInput>, import("../types.js").Tool<import("./grep.js").GrepInput>, import("../types.js").Tool<import("./glob.js").GlobInput>, import("../types.js").Tool<import("./edit.js").EditInput>, import("../types.js").Tool<import("./todo.js").TodoWriteInput>, import("../types.js").Tool<import("./todo.js").TodoReadInput>, import("../types.js").Tool<import("./web-fetch.js").WebFetchInput>, import("../types.js").Tool<import("./suggest.js").SuggestInput>];
|
|
59
|
+
export declare const allBuiltinTools: readonly [import("../types.js").Tool<import("./read-file.js").ReadFileInput>, import("../types.js").Tool<import("./write-file.js").WriteFileInput>, import("../types.js").Tool<import("./bash.js").BashInput>, import("../types.js").Tool<import("./bash-output.js").BashOutputInput>, import("../types.js").Tool<import("./kill-shell.js").KillShellInput>, import("../types.js").Tool<import("./grep.js").GrepInput>, import("../types.js").Tool<import("./glob.js").GlobInput>, import("../types.js").Tool<import("./edit.js").EditInput>, import("../types.js").Tool<import("./todo.js").TodoWriteInput>, import("../types.js").Tool<import("./todo.js").TodoReadInput>, import("../types.js").Tool<import("./web-fetch.js").WebFetchInput>, import("../types.js").Tool<import("./suggest.js").SuggestInput>, import("../types.js").Tool<import("./ask-user.js").AskUserInput>, import("../types.js").Tool<import("./ask-user-simple.js").AskUserSimpleInput>, import("../types.js").Tool<import("./backlog.js").BacklogReadInput>, import("../types.js").Tool<import("./backlog.js").BacklogWriteInput>];
|
|
@@ -29,6 +29,11 @@ export { createTaskTool, defaultAgentTypes } from './task.js';
|
|
|
29
29
|
export { webFetchTool, createWebFetchTool } from './web-fetch.js';
|
|
30
30
|
// Suggest tool (next action suggestions)
|
|
31
31
|
export { suggestTool, createSuggestTool } from './suggest.js';
|
|
32
|
+
// Ask user tools (user interaction)
|
|
33
|
+
export { askUserTool, createAskUserTool } from './ask-user.js';
|
|
34
|
+
export { askUserSimpleTool, createAskUserSimpleTool } from './ask-user-simple.js';
|
|
35
|
+
// Backlog tools (file-based project backlog)
|
|
36
|
+
export { backlogReadTool, backlogWriteTool, createBacklogTools } from './backlog.js';
|
|
32
37
|
/**
|
|
33
38
|
* Collection of all built-in tools for easy registration
|
|
34
39
|
*/
|
|
@@ -43,6 +48,9 @@ import { editTool } from './edit.js';
|
|
|
43
48
|
import { todoWriteTool, todoReadTool } from './todo.js';
|
|
44
49
|
import { webFetchTool } from './web-fetch.js';
|
|
45
50
|
import { suggestTool } from './suggest.js';
|
|
51
|
+
import { askUserTool } from './ask-user.js';
|
|
52
|
+
import { askUserSimpleTool } from './ask-user-simple.js';
|
|
53
|
+
import { backlogReadTool, backlogWriteTool } from './backlog.js';
|
|
46
54
|
export const builtinTools = {
|
|
47
55
|
readFile: readFileTool,
|
|
48
56
|
writeFile: writeFileTool,
|
|
@@ -56,6 +64,10 @@ export const builtinTools = {
|
|
|
56
64
|
todoRead: todoReadTool,
|
|
57
65
|
webFetch: webFetchTool,
|
|
58
66
|
suggest: suggestTool,
|
|
67
|
+
askUser: askUserTool,
|
|
68
|
+
askUserSimple: askUserSimpleTool,
|
|
69
|
+
backlogRead: backlogReadTool,
|
|
70
|
+
backlogWrite: backlogWriteTool,
|
|
59
71
|
};
|
|
60
72
|
/**
|
|
61
73
|
* Array of all built-in tools
|
|
@@ -73,4 +85,8 @@ export const allBuiltinTools = [
|
|
|
73
85
|
todoReadTool,
|
|
74
86
|
webFetchTool,
|
|
75
87
|
suggestTool,
|
|
88
|
+
askUserTool,
|
|
89
|
+
askUserSimpleTool,
|
|
90
|
+
backlogReadTool,
|
|
91
|
+
backlogWriteTool,
|
|
76
92
|
];
|
|
@@ -143,6 +143,10 @@ export interface SubAgentEventInfo {
|
|
|
143
143
|
* Type of the sub-agent
|
|
144
144
|
*/
|
|
145
145
|
agentType: string;
|
|
146
|
+
/**
|
|
147
|
+
* Tool use ID for correlation with parent tool call
|
|
148
|
+
*/
|
|
149
|
+
toolUseId?: string;
|
|
146
150
|
/**
|
|
147
151
|
* The original event from the sub-agent
|
|
148
152
|
*/
|
|
@@ -174,13 +178,19 @@ export interface TaskToolOptions {
|
|
|
174
178
|
*/
|
|
175
179
|
defaultTimeout?: number;
|
|
176
180
|
/**
|
|
177
|
-
* Called when a sub-agent is spawned
|
|
181
|
+
* Called when a sub-agent is spawned.
|
|
182
|
+
* @param agentType - The type of agent being spawned
|
|
183
|
+
* @param description - Description of the task
|
|
184
|
+
* @param toolUseId - Tool use ID for correlation (enables parallel tracking)
|
|
178
185
|
*/
|
|
179
|
-
onSpawn?: (agentType: string, description: string) => void;
|
|
186
|
+
onSpawn?: (agentType: string, description: string, toolUseId?: string) => void;
|
|
180
187
|
/**
|
|
181
|
-
* Called when a sub-agent completes
|
|
188
|
+
* Called when a sub-agent completes.
|
|
189
|
+
* @param agentType - The type of agent that completed
|
|
190
|
+
* @param result - The task result
|
|
191
|
+
* @param toolUseId - Tool use ID for correlation (enables parallel tracking)
|
|
182
192
|
*/
|
|
183
|
-
onComplete?: (agentType: string, result: TaskResult) => void;
|
|
193
|
+
onComplete?: (agentType: string, result: TaskResult, toolUseId?: string) => void;
|
|
184
194
|
/**
|
|
185
195
|
* Called when a sub-agent emits an event (for real-time streaming)
|
|
186
196
|
* Only called if enableEventStreaming is true
|
|
@@ -114,7 +114,8 @@ export function createTaskTool(options) {
|
|
|
114
114
|
},
|
|
115
115
|
required: ['description', 'prompt', 'subagent_type'],
|
|
116
116
|
},
|
|
117
|
-
execute: async (input) => {
|
|
117
|
+
execute: async (input, context) => {
|
|
118
|
+
const toolUseId = context?.toolUseId;
|
|
118
119
|
const { description, prompt, subagent_type, model, context_mode, thoroughness = 'medium', } = input;
|
|
119
120
|
// Validate agent type exists
|
|
120
121
|
if (!Object.hasOwn(agentTypes, subagent_type)) {
|
|
@@ -124,16 +125,14 @@ export function createTaskTool(options) {
|
|
|
124
125
|
const agentConfig = agentTypes[subagent_type];
|
|
125
126
|
// Check concurrent limit
|
|
126
127
|
if (activeCount >= maxConcurrent) {
|
|
127
|
-
console.error(`[task-tool] BLOCKED: activeCount=${String(activeCount)}, maxConcurrent=${String(maxConcurrent)}`);
|
|
128
128
|
return createErrorResult(`Maximum concurrent sub-agents (${String(maxConcurrent)}) reached. ` +
|
|
129
129
|
`Wait for existing tasks to complete.`);
|
|
130
130
|
}
|
|
131
|
-
// Notify spawn
|
|
131
|
+
// Notify spawn (include toolUseId for parallel execution tracking)
|
|
132
132
|
if (onSpawn) {
|
|
133
|
-
onSpawn(subagent_type, description);
|
|
133
|
+
onSpawn(subagent_type, description, toolUseId);
|
|
134
134
|
}
|
|
135
135
|
activeCount++;
|
|
136
|
-
console.error(`[task-tool] SPAWN: ${subagent_type}, activeCount=${String(activeCount)}`);
|
|
137
136
|
try {
|
|
138
137
|
// Note: Sub-agents currently use the parent's provider
|
|
139
138
|
// Future enhancement: support model switching via providerFactory
|
|
@@ -178,6 +177,7 @@ export function createTaskTool(options) {
|
|
|
178
177
|
onSubAgentEvent({
|
|
179
178
|
agentName: subAgentName,
|
|
180
179
|
agentType: subagent_type,
|
|
180
|
+
toolUseId,
|
|
181
181
|
event,
|
|
182
182
|
});
|
|
183
183
|
};
|
|
@@ -194,21 +194,21 @@ export function createTaskTool(options) {
|
|
|
194
194
|
iterations: result.iterations,
|
|
195
195
|
toolCalls: result.toolCalls.length,
|
|
196
196
|
};
|
|
197
|
-
// Notify completion
|
|
197
|
+
// Notify completion (include toolUseId for parallel execution tracking)
|
|
198
198
|
if (onComplete) {
|
|
199
|
-
onComplete(subagent_type, taskResult);
|
|
199
|
+
onComplete(subagent_type, taskResult, toolUseId);
|
|
200
200
|
}
|
|
201
201
|
return createSuccessResult(taskResult);
|
|
202
202
|
}
|
|
203
203
|
catch (error) {
|
|
204
|
-
console.error(`[task-tool] ERROR: ${subagent_type}, error=${error instanceof Error ? error.message : String(error)}`);
|
|
205
204
|
return createErrorResult(`Sub-agent failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
206
205
|
}
|
|
207
206
|
finally {
|
|
208
207
|
activeCount--;
|
|
209
|
-
console.error(`[task-tool] DONE: ${subagent_type}, activeCount=${String(activeCount)}`);
|
|
210
208
|
}
|
|
211
209
|
},
|
|
210
|
+
// Task tool can run in parallel since sub-agents are independent
|
|
211
|
+
parallel: true,
|
|
212
212
|
});
|
|
213
213
|
}
|
|
214
214
|
/**
|
package/dist/tools/define.d.ts
CHANGED
|
@@ -22,6 +22,13 @@ export interface DefineToolOptions<T extends object> {
|
|
|
22
22
|
* Function that executes the tool
|
|
23
23
|
*/
|
|
24
24
|
execute: (input: T) => Promise<ToolExecutionResult>;
|
|
25
|
+
/**
|
|
26
|
+
* If true, multiple calls to this tool can execute in parallel.
|
|
27
|
+
* When the LLM requests multiple parallel-safe tools in one response,
|
|
28
|
+
* they will be executed concurrently using Promise.all.
|
|
29
|
+
* Default: false (sequential execution)
|
|
30
|
+
*/
|
|
31
|
+
parallel?: boolean;
|
|
25
32
|
}
|
|
26
33
|
/**
|
|
27
34
|
* Define a tool with type-safe input handling
|
package/dist/tools/define.js
CHANGED
package/dist/tools/registry.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ToolRegistry - Manages available tools for an agent
|
|
3
3
|
*/
|
|
4
|
-
import type { Tool, ToolDefinition, ToolRegistry as IToolRegistry, ToolExecutionResult } from './types.js';
|
|
4
|
+
import type { Tool, ToolDefinition, ToolRegistry as IToolRegistry, ToolExecutionResult, ToolExecutionContext } from './types.js';
|
|
5
5
|
/**
|
|
6
6
|
* Options for creating a DefaultToolRegistry
|
|
7
7
|
*/
|
|
@@ -61,9 +61,10 @@ export declare class DefaultToolRegistry implements IToolRegistry {
|
|
|
61
61
|
*
|
|
62
62
|
* @param name - Tool name
|
|
63
63
|
* @param input - Tool input parameters
|
|
64
|
+
* @param contextOrTimeout - Optional execution context or timeout override
|
|
64
65
|
* @param timeoutMs - Optional timeout override (uses default if not provided)
|
|
65
66
|
*/
|
|
66
|
-
execute(name: string, input: Record<string, unknown>, timeoutMs?: number): Promise<ToolExecutionResult>;
|
|
67
|
+
execute(name: string, input: Record<string, unknown>, contextOrTimeout?: ToolExecutionContext | number, timeoutMs?: number): Promise<ToolExecutionResult>;
|
|
67
68
|
/**
|
|
68
69
|
* Execute a tool with a timeout
|
|
69
70
|
*/
|
package/dist/tools/registry.js
CHANGED
|
@@ -75,9 +75,10 @@ export class DefaultToolRegistry {
|
|
|
75
75
|
*
|
|
76
76
|
* @param name - Tool name
|
|
77
77
|
* @param input - Tool input parameters
|
|
78
|
+
* @param contextOrTimeout - Optional execution context or timeout override
|
|
78
79
|
* @param timeoutMs - Optional timeout override (uses default if not provided)
|
|
79
80
|
*/
|
|
80
|
-
async execute(name, input, timeoutMs) {
|
|
81
|
+
async execute(name, input, contextOrTimeout, timeoutMs) {
|
|
81
82
|
const tool = this.tools.get(name);
|
|
82
83
|
if (!tool) {
|
|
83
84
|
return {
|
|
@@ -85,15 +86,27 @@ export class DefaultToolRegistry {
|
|
|
85
86
|
error: `Tool not found: ${name}`,
|
|
86
87
|
};
|
|
87
88
|
}
|
|
89
|
+
// Handle backwards compatibility: contextOrTimeout can be number (old API) or context (new API)
|
|
90
|
+
let context;
|
|
91
|
+
let explicitTimeout;
|
|
92
|
+
if (typeof contextOrTimeout === 'number') {
|
|
93
|
+
// Old API: execute(name, input, timeoutMs)
|
|
94
|
+
explicitTimeout = contextOrTimeout;
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
// New API: execute(name, input, context?, timeoutMs?)
|
|
98
|
+
context = contextOrTimeout;
|
|
99
|
+
explicitTimeout = timeoutMs;
|
|
100
|
+
}
|
|
88
101
|
// Determine timeout: explicit param > per-tool config > default
|
|
89
102
|
const perToolTimeout = Object.hasOwn(this.toolTimeouts, name)
|
|
90
103
|
? this.toolTimeouts[name]
|
|
91
104
|
: undefined;
|
|
92
|
-
const timeout =
|
|
105
|
+
const timeout = explicitTimeout ?? perToolTimeout ?? this.defaultTimeoutMs;
|
|
93
106
|
// If timeout is 0 or negative, execute without timeout
|
|
94
107
|
if (timeout <= 0) {
|
|
95
108
|
try {
|
|
96
|
-
return await tool.execute(input);
|
|
109
|
+
return await tool.execute(input, context);
|
|
97
110
|
}
|
|
98
111
|
catch (error) {
|
|
99
112
|
return {
|
|
@@ -104,7 +117,7 @@ export class DefaultToolRegistry {
|
|
|
104
117
|
}
|
|
105
118
|
// Execute with timeout
|
|
106
119
|
try {
|
|
107
|
-
return await this.executeWithTimeout(tool, name, input, timeout);
|
|
120
|
+
return await this.executeWithTimeout(tool, name, input, timeout, context);
|
|
108
121
|
}
|
|
109
122
|
catch (error) {
|
|
110
123
|
if (error instanceof ToolTimeoutError) {
|
|
@@ -122,7 +135,7 @@ export class DefaultToolRegistry {
|
|
|
122
135
|
/**
|
|
123
136
|
* Execute a tool with a timeout
|
|
124
137
|
*/
|
|
125
|
-
async executeWithTimeout(tool, name, input, timeoutMs) {
|
|
138
|
+
async executeWithTimeout(tool, name, input, timeoutMs, context) {
|
|
126
139
|
// Create a promise that rejects after the timeout
|
|
127
140
|
const timeoutPromise = new Promise((_, reject) => {
|
|
128
141
|
setTimeout(() => {
|
|
@@ -130,7 +143,7 @@ export class DefaultToolRegistry {
|
|
|
130
143
|
}, timeoutMs);
|
|
131
144
|
});
|
|
132
145
|
// Race the tool execution against the timeout
|
|
133
|
-
return Promise.race([tool.execute(input), timeoutPromise]);
|
|
146
|
+
return Promise.race([tool.execute(input, context), timeoutPromise]);
|
|
134
147
|
}
|
|
135
148
|
/**
|
|
136
149
|
* Clear all registered tools
|
package/dist/tools/types.d.ts
CHANGED
|
@@ -25,16 +25,40 @@ export interface ToolExecutionResult {
|
|
|
25
25
|
result?: unknown;
|
|
26
26
|
error?: string;
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Context passed to tool execution for streaming output
|
|
30
|
+
*/
|
|
31
|
+
export interface ToolExecutionContext {
|
|
32
|
+
/**
|
|
33
|
+
* Callback for streaming output (e.g., bash stdout/stderr)
|
|
34
|
+
* @param output - The output text
|
|
35
|
+
* @param stream - Which stream the output came from
|
|
36
|
+
*/
|
|
37
|
+
onOutput?: (output: string, stream?: 'stdout' | 'stderr') => void;
|
|
38
|
+
/**
|
|
39
|
+
* Tool use ID for correlation with events
|
|
40
|
+
*/
|
|
41
|
+
toolUseId?: string;
|
|
42
|
+
}
|
|
28
43
|
/**
|
|
29
44
|
* Tool handler function type
|
|
45
|
+
* @param input - The tool input parameters
|
|
46
|
+
* @param context - Optional execution context for streaming
|
|
30
47
|
*/
|
|
31
|
-
export type ToolHandler<T = object> = (input: T) => Promise<ToolExecutionResult>;
|
|
48
|
+
export type ToolHandler<T = object> = (input: T, context?: ToolExecutionContext) => Promise<ToolExecutionResult>;
|
|
32
49
|
/**
|
|
33
50
|
* Tool implementation - combines definition with handler
|
|
34
51
|
*/
|
|
35
52
|
export interface Tool<T = object> {
|
|
36
53
|
definition: ToolDefinition;
|
|
37
54
|
execute: ToolHandler<T>;
|
|
55
|
+
/**
|
|
56
|
+
* If true, multiple calls to this tool can execute in parallel.
|
|
57
|
+
* When the LLM requests multiple parallel-safe tools in one response,
|
|
58
|
+
* they will be executed concurrently using Promise.all.
|
|
59
|
+
* Default: false (sequential execution)
|
|
60
|
+
*/
|
|
61
|
+
parallel?: boolean;
|
|
38
62
|
}
|
|
39
63
|
/**
|
|
40
64
|
* Tool registry for managing available tools
|
|
@@ -54,6 +78,9 @@ export interface ToolRegistry {
|
|
|
54
78
|
getDefinitions(): ToolDefinition[];
|
|
55
79
|
/**
|
|
56
80
|
* Execute a tool by name with given input
|
|
81
|
+
* @param name - Tool name
|
|
82
|
+
* @param input - Tool input parameters
|
|
83
|
+
* @param context - Optional execution context for streaming
|
|
57
84
|
*/
|
|
58
|
-
execute(name: string, input: Record<string, unknown
|
|
85
|
+
execute(name: string, input: Record<string, unknown>, context?: ToolExecutionContext): Promise<ToolExecutionResult>;
|
|
59
86
|
}
|