@compilr-dev/agents 0.2.1 → 0.3.1
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 +11 -0
- package/dist/agent.js +210 -122
- 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/providers/ollama.d.ts +2 -2
- package/dist/providers/ollama.js +3 -3
- package/dist/skills/index.js +476 -235
- 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
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backlog Tools - File-based project backlog management
|
|
3
|
+
*
|
|
4
|
+
* Provides backlog_read and backlog_write tools for managing project backlogs.
|
|
5
|
+
* Uses a JSON file stored in .compilr/backlog.json (or backlog.json in project root).
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Read with filters (status, type, search, limit)
|
|
9
|
+
* - Write/update individual items or full backlog
|
|
10
|
+
* - Support for common item types (feature, bug, chore, spike)
|
|
11
|
+
* - Priority levels (critical, high, medium, low)
|
|
12
|
+
*/
|
|
13
|
+
import * as fs from 'fs';
|
|
14
|
+
import * as path from 'path';
|
|
15
|
+
import { defineTool, createSuccessResult, createErrorResult } from '../define.js';
|
|
16
|
+
// =============================================================================
|
|
17
|
+
// File Operations
|
|
18
|
+
// =============================================================================
|
|
19
|
+
const BACKLOG_PATHS = ['.compilr/backlog.json', 'backlog.json', '.claude/backlog.json'];
|
|
20
|
+
function findBacklogFile(cwd = process.cwd()) {
|
|
21
|
+
for (const relPath of BACKLOG_PATHS) {
|
|
22
|
+
const fullPath = path.join(cwd, relPath);
|
|
23
|
+
if (fs.existsSync(fullPath)) {
|
|
24
|
+
return fullPath;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
function getDefaultBacklogPath(cwd = process.cwd()) {
|
|
30
|
+
// Prefer .compilr directory if it exists
|
|
31
|
+
const compilrDir = path.join(cwd, '.compilr');
|
|
32
|
+
if (fs.existsSync(compilrDir)) {
|
|
33
|
+
return path.join(compilrDir, 'backlog.json');
|
|
34
|
+
}
|
|
35
|
+
// Otherwise use project root
|
|
36
|
+
return path.join(cwd, 'backlog.json');
|
|
37
|
+
}
|
|
38
|
+
function loadBacklog(filePath) {
|
|
39
|
+
try {
|
|
40
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
41
|
+
const data = JSON.parse(content);
|
|
42
|
+
return {
|
|
43
|
+
version: data.version || '1.0',
|
|
44
|
+
items: Array.isArray(data.items) ? data.items : [],
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return { version: '1.0', items: [] };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function saveBacklog(filePath, backlog) {
|
|
52
|
+
// Ensure directory exists
|
|
53
|
+
const dir = path.dirname(filePath);
|
|
54
|
+
if (!fs.existsSync(dir)) {
|
|
55
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
56
|
+
}
|
|
57
|
+
// Update timestamps
|
|
58
|
+
const now = new Date().toISOString();
|
|
59
|
+
backlog.items = backlog.items.map((item) => ({
|
|
60
|
+
...item,
|
|
61
|
+
updatedAt: now,
|
|
62
|
+
createdAt: item.createdAt || now,
|
|
63
|
+
}));
|
|
64
|
+
fs.writeFileSync(filePath, JSON.stringify(backlog, null, 2), 'utf-8');
|
|
65
|
+
}
|
|
66
|
+
function generateItemId(type, items) {
|
|
67
|
+
const prefix = {
|
|
68
|
+
feature: 'FEAT',
|
|
69
|
+
bug: 'BUG',
|
|
70
|
+
chore: 'CHORE',
|
|
71
|
+
spike: 'SPIKE',
|
|
72
|
+
}[type];
|
|
73
|
+
// Find highest number for this type
|
|
74
|
+
const existingIds = items
|
|
75
|
+
.filter((i) => i.id.startsWith(prefix))
|
|
76
|
+
.map((i) => parseInt(i.id.split('-')[1], 10))
|
|
77
|
+
.filter((n) => !isNaN(n));
|
|
78
|
+
const nextNum = existingIds.length > 0 ? Math.max(...existingIds) + 1 : 1;
|
|
79
|
+
return `${prefix}-${String(nextNum).padStart(3, '0')}`;
|
|
80
|
+
}
|
|
81
|
+
// =============================================================================
|
|
82
|
+
// Backlog Read Tool
|
|
83
|
+
// =============================================================================
|
|
84
|
+
/**
|
|
85
|
+
* Read backlog items with optional filters
|
|
86
|
+
*/
|
|
87
|
+
export const backlogReadTool = defineTool({
|
|
88
|
+
name: 'backlog_read',
|
|
89
|
+
description: 'Read backlog items from the project. ' +
|
|
90
|
+
'Use filters to narrow results. Use id parameter to get a specific item. ' +
|
|
91
|
+
'Returns items sorted by priority (critical first) then by creation date.',
|
|
92
|
+
inputSchema: {
|
|
93
|
+
type: 'object',
|
|
94
|
+
properties: {
|
|
95
|
+
id: {
|
|
96
|
+
type: 'string',
|
|
97
|
+
description: 'Get a specific item by ID (e.g., "FEAT-001")',
|
|
98
|
+
},
|
|
99
|
+
status: {
|
|
100
|
+
type: 'string',
|
|
101
|
+
enum: ['backlog', 'in-progress', 'done', 'blocked'],
|
|
102
|
+
description: 'Filter by status',
|
|
103
|
+
},
|
|
104
|
+
type: {
|
|
105
|
+
type: 'string',
|
|
106
|
+
enum: ['feature', 'bug', 'chore', 'spike'],
|
|
107
|
+
description: 'Filter by item type',
|
|
108
|
+
},
|
|
109
|
+
search: {
|
|
110
|
+
type: 'string',
|
|
111
|
+
description: 'Search in title and description',
|
|
112
|
+
},
|
|
113
|
+
priority: {
|
|
114
|
+
type: 'string',
|
|
115
|
+
enum: ['critical', 'high', 'medium', 'low'],
|
|
116
|
+
description: 'Filter by priority',
|
|
117
|
+
},
|
|
118
|
+
limit: {
|
|
119
|
+
type: 'number',
|
|
120
|
+
description: 'Maximum items to return (default: 20)',
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
125
|
+
execute: async (input) => {
|
|
126
|
+
try {
|
|
127
|
+
const filePath = findBacklogFile();
|
|
128
|
+
if (!filePath) {
|
|
129
|
+
return createSuccessResult({
|
|
130
|
+
items: [],
|
|
131
|
+
total: 0,
|
|
132
|
+
filtered: 0,
|
|
133
|
+
message: 'No backlog file found. Use backlog_write to create one.',
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
const backlog = loadBacklog(filePath);
|
|
137
|
+
let items = [...backlog.items];
|
|
138
|
+
const total = items.length;
|
|
139
|
+
// Filter by ID (exact match)
|
|
140
|
+
if (input.id) {
|
|
141
|
+
items = items.filter((i) => i.id === input.id);
|
|
142
|
+
}
|
|
143
|
+
// Filter by status
|
|
144
|
+
if (input.status) {
|
|
145
|
+
items = items.filter((i) => i.status === input.status);
|
|
146
|
+
}
|
|
147
|
+
// Filter by type
|
|
148
|
+
if (input.type) {
|
|
149
|
+
items = items.filter((i) => i.type === input.type);
|
|
150
|
+
}
|
|
151
|
+
// Filter by priority
|
|
152
|
+
if (input.priority) {
|
|
153
|
+
items = items.filter((i) => i.priority === input.priority);
|
|
154
|
+
}
|
|
155
|
+
// Search in title and description
|
|
156
|
+
if (input.search) {
|
|
157
|
+
const query = input.search.toLowerCase();
|
|
158
|
+
items = items.filter((i) => i.title.toLowerCase().includes(query) || i.description?.toLowerCase().includes(query));
|
|
159
|
+
}
|
|
160
|
+
// Sort by priority then by creation date
|
|
161
|
+
const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
162
|
+
items.sort((a, b) => {
|
|
163
|
+
const pa = priorityOrder[a.priority || 'medium'];
|
|
164
|
+
const pb = priorityOrder[b.priority || 'medium'];
|
|
165
|
+
if (pa !== pb)
|
|
166
|
+
return pa - pb;
|
|
167
|
+
return (a.createdAt || '').localeCompare(b.createdAt || '');
|
|
168
|
+
});
|
|
169
|
+
// Apply limit
|
|
170
|
+
const limit = input.limit ?? 20;
|
|
171
|
+
const filtered = items.length;
|
|
172
|
+
items = items.slice(0, limit);
|
|
173
|
+
return createSuccessResult({
|
|
174
|
+
items,
|
|
175
|
+
total,
|
|
176
|
+
filtered,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
return createErrorResult(error instanceof Error ? error.message : String(error));
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
// =============================================================================
|
|
185
|
+
// Backlog Write Tool
|
|
186
|
+
// =============================================================================
|
|
187
|
+
/**
|
|
188
|
+
* Write/update backlog items
|
|
189
|
+
*/
|
|
190
|
+
export const backlogWriteTool = defineTool({
|
|
191
|
+
name: 'backlog_write',
|
|
192
|
+
description: 'Create, update, or delete backlog items. ' +
|
|
193
|
+
'Actions: add (new item), update (modify existing), delete (remove), replace (full list). ' +
|
|
194
|
+
'IDs are auto-generated for new items based on type (e.g., FEAT-001, BUG-002).',
|
|
195
|
+
inputSchema: {
|
|
196
|
+
type: 'object',
|
|
197
|
+
properties: {
|
|
198
|
+
action: {
|
|
199
|
+
type: 'string',
|
|
200
|
+
enum: ['add', 'update', 'delete', 'replace'],
|
|
201
|
+
description: 'Action to perform',
|
|
202
|
+
},
|
|
203
|
+
item: {
|
|
204
|
+
type: 'object',
|
|
205
|
+
description: 'Item to add or update (for add/update actions)',
|
|
206
|
+
properties: {
|
|
207
|
+
id: { type: 'string', description: 'Item ID (required for update)' },
|
|
208
|
+
type: {
|
|
209
|
+
type: 'string',
|
|
210
|
+
enum: ['feature', 'bug', 'chore', 'spike'],
|
|
211
|
+
description: 'Item type (required for add)',
|
|
212
|
+
},
|
|
213
|
+
title: { type: 'string', description: 'Item title' },
|
|
214
|
+
description: { type: 'string', description: 'Detailed description' },
|
|
215
|
+
status: {
|
|
216
|
+
type: 'string',
|
|
217
|
+
enum: ['backlog', 'in-progress', 'done', 'blocked'],
|
|
218
|
+
description: 'Item status',
|
|
219
|
+
},
|
|
220
|
+
priority: {
|
|
221
|
+
type: 'string',
|
|
222
|
+
enum: ['critical', 'high', 'medium', 'low'],
|
|
223
|
+
description: 'Priority level',
|
|
224
|
+
},
|
|
225
|
+
acceptanceCriteria: {
|
|
226
|
+
type: 'array',
|
|
227
|
+
items: { type: 'string' },
|
|
228
|
+
description: 'Acceptance criteria list',
|
|
229
|
+
},
|
|
230
|
+
dependencies: {
|
|
231
|
+
type: 'array',
|
|
232
|
+
items: { type: 'string' },
|
|
233
|
+
description: 'IDs of items this depends on',
|
|
234
|
+
},
|
|
235
|
+
labels: {
|
|
236
|
+
type: 'array',
|
|
237
|
+
items: { type: 'string' },
|
|
238
|
+
description: 'Labels/tags',
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
deleteId: {
|
|
243
|
+
type: 'string',
|
|
244
|
+
description: 'Item ID to delete (for delete action)',
|
|
245
|
+
},
|
|
246
|
+
items: {
|
|
247
|
+
type: 'array',
|
|
248
|
+
description: 'Full list of items (for replace action)',
|
|
249
|
+
items: {
|
|
250
|
+
type: 'object',
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
required: ['action'],
|
|
255
|
+
},
|
|
256
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
257
|
+
execute: async (input) => {
|
|
258
|
+
try {
|
|
259
|
+
const existingPath = findBacklogFile();
|
|
260
|
+
const filePath = existingPath || getDefaultBacklogPath();
|
|
261
|
+
const backlog = existingPath ? loadBacklog(existingPath) : { version: '1.0', items: [] };
|
|
262
|
+
switch (input.action) {
|
|
263
|
+
case 'add': {
|
|
264
|
+
if (!input.item) {
|
|
265
|
+
return createErrorResult('Item is required for add action');
|
|
266
|
+
}
|
|
267
|
+
const itemType = input.item.type;
|
|
268
|
+
const itemTitle = input.item.title;
|
|
269
|
+
if (!itemType) {
|
|
270
|
+
return createErrorResult('Item type is required for add action');
|
|
271
|
+
}
|
|
272
|
+
if (!itemTitle) {
|
|
273
|
+
return createErrorResult('Item title is required for add action');
|
|
274
|
+
}
|
|
275
|
+
const newItem = {
|
|
276
|
+
id: generateItemId(itemType, backlog.items),
|
|
277
|
+
type: itemType,
|
|
278
|
+
title: itemTitle,
|
|
279
|
+
description: input.item.description,
|
|
280
|
+
status: input.item.status ?? 'backlog',
|
|
281
|
+
priority: input.item.priority,
|
|
282
|
+
acceptanceCriteria: input.item.acceptanceCriteria,
|
|
283
|
+
dependencies: input.item.dependencies,
|
|
284
|
+
labels: input.item.labels,
|
|
285
|
+
};
|
|
286
|
+
backlog.items.push(newItem);
|
|
287
|
+
saveBacklog(filePath, backlog);
|
|
288
|
+
return createSuccessResult({
|
|
289
|
+
success: true,
|
|
290
|
+
action: 'add',
|
|
291
|
+
itemId: newItem.id,
|
|
292
|
+
itemCount: backlog.items.length,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
case 'update': {
|
|
296
|
+
const updateItem = input.item;
|
|
297
|
+
const updateId = updateItem?.id;
|
|
298
|
+
if (!updateItem || !updateId) {
|
|
299
|
+
return createErrorResult('Item ID is required for update action');
|
|
300
|
+
}
|
|
301
|
+
const idx = backlog.items.findIndex((i) => i.id === updateId);
|
|
302
|
+
if (idx === -1) {
|
|
303
|
+
return createErrorResult(`Item not found: ${updateId}`);
|
|
304
|
+
}
|
|
305
|
+
// Merge updates
|
|
306
|
+
backlog.items[idx] = {
|
|
307
|
+
...backlog.items[idx],
|
|
308
|
+
...updateItem,
|
|
309
|
+
id: backlog.items[idx].id, // Preserve original ID
|
|
310
|
+
};
|
|
311
|
+
saveBacklog(filePath, backlog);
|
|
312
|
+
return createSuccessResult({
|
|
313
|
+
success: true,
|
|
314
|
+
action: 'update',
|
|
315
|
+
itemId: updateId,
|
|
316
|
+
itemCount: backlog.items.length,
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
case 'delete': {
|
|
320
|
+
const deleteId = input.deleteId || input.item?.id;
|
|
321
|
+
if (!deleteId) {
|
|
322
|
+
return createErrorResult('deleteId or item.id is required for delete action');
|
|
323
|
+
}
|
|
324
|
+
const initialCount = backlog.items.length;
|
|
325
|
+
backlog.items = backlog.items.filter((i) => i.id !== deleteId);
|
|
326
|
+
if (backlog.items.length === initialCount) {
|
|
327
|
+
return createErrorResult(`Item not found: ${deleteId}`);
|
|
328
|
+
}
|
|
329
|
+
saveBacklog(filePath, backlog);
|
|
330
|
+
return createSuccessResult({
|
|
331
|
+
success: true,
|
|
332
|
+
action: 'delete',
|
|
333
|
+
itemId: deleteId,
|
|
334
|
+
itemCount: backlog.items.length,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
case 'replace': {
|
|
338
|
+
if (!input.items) {
|
|
339
|
+
return createErrorResult('Items array is required for replace action');
|
|
340
|
+
}
|
|
341
|
+
backlog.items = input.items;
|
|
342
|
+
saveBacklog(filePath, backlog);
|
|
343
|
+
return createSuccessResult({
|
|
344
|
+
success: true,
|
|
345
|
+
action: 'replace',
|
|
346
|
+
itemCount: backlog.items.length,
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
default:
|
|
350
|
+
return createErrorResult(`Unknown action: ${input.action}`);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
catch (error) {
|
|
354
|
+
return createErrorResult(error instanceof Error ? error.message : String(error));
|
|
355
|
+
}
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
/**
|
|
359
|
+
* Create backlog tools with custom options
|
|
360
|
+
*/
|
|
361
|
+
export function createBacklogTools(_options = {}) {
|
|
362
|
+
// For now, just return the default tools
|
|
363
|
+
// Future: could customize file paths, storage backend, etc.
|
|
364
|
+
return {
|
|
365
|
+
backlogRead: backlogReadTool,
|
|
366
|
+
backlogWrite: backlogWriteTool,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
@@ -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
|
];
|