@agenticmail/enterprise 0.5.370 → 0.5.371
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-tools-AR3FPB24.js +14008 -0
- package/dist/chunk-F6SI2DE3.js +5101 -0
- package/dist/chunk-JXI6PCUZ.js +4946 -0
- package/dist/chunk-L7OG7R6F.js +1727 -0
- package/dist/cli-agent-A4IFO5ML.js +2483 -0
- package/dist/cli-serve-WFOG5TRD.js +281 -0
- package/dist/cli.js +3 -3
- package/dist/index.js +3 -3
- package/dist/local-2ASES4SZ.js +1179 -0
- package/dist/runtime-RDULR4UE.js +45 -0
- package/dist/server-XRW327IX.js +28 -0
- package/dist/setup-DKOGTOIG.js +20 -0
- package/logs/cloudflared-error.log +10 -0
- package/logs/enterprise-out.log +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1179 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createDependencyManagerTools
|
|
3
|
+
} from "./chunk-HOYZOJMP.js";
|
|
4
|
+
import "./chunk-KFQGP6VL.js";
|
|
5
|
+
|
|
6
|
+
// src/agent-tools/tools/local/file-read.ts
|
|
7
|
+
import { readFile } from "fs/promises";
|
|
8
|
+
|
|
9
|
+
// src/agent-tools/tools/local/resolve-path.ts
|
|
10
|
+
import { join, resolve } from "path";
|
|
11
|
+
import { homedir } from "os";
|
|
12
|
+
function resolvePath(inputPath, sandbox) {
|
|
13
|
+
var expanded = inputPath.startsWith("~") ? join(homedir(), inputPath.slice(1)) : inputPath;
|
|
14
|
+
var resolved = resolve(expanded);
|
|
15
|
+
if (sandbox) {
|
|
16
|
+
var sandboxResolved = resolve(sandbox);
|
|
17
|
+
if (!resolved.startsWith(sandboxResolved)) {
|
|
18
|
+
throw new Error("Access denied: path is outside the allowed directory (" + sandbox + ")");
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return resolved;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// src/agent-tools/tools/local/file-read.ts
|
|
25
|
+
function createFileReadTool(sandbox) {
|
|
26
|
+
return {
|
|
27
|
+
name: "file_read",
|
|
28
|
+
description: "Read file contents. Use offset/limit for large files.",
|
|
29
|
+
input_schema: {
|
|
30
|
+
type: "object",
|
|
31
|
+
properties: {
|
|
32
|
+
path: { type: "string" },
|
|
33
|
+
offset: { type: "number", description: "Start line (1-indexed)" },
|
|
34
|
+
limit: { type: "number", description: "Max lines" }
|
|
35
|
+
},
|
|
36
|
+
required: ["path"]
|
|
37
|
+
},
|
|
38
|
+
execute: async (input) => {
|
|
39
|
+
var filePath = resolvePath(input.path, sandbox);
|
|
40
|
+
var content = await readFile(filePath, "utf-8");
|
|
41
|
+
if (input.offset || input.limit) {
|
|
42
|
+
var lines = content.split("\n");
|
|
43
|
+
var start = Math.max(0, (input.offset || 1) - 1);
|
|
44
|
+
var end = input.limit ? start + input.limit : lines.length;
|
|
45
|
+
content = lines.slice(start, end).join("\n");
|
|
46
|
+
}
|
|
47
|
+
if (content.length > 5e4) {
|
|
48
|
+
content = content.slice(0, 5e4) + "\n\n[...truncated at 50KB]";
|
|
49
|
+
}
|
|
50
|
+
return { content, path: filePath, size: content.length };
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// src/agent-tools/tools/local/file-write.ts
|
|
56
|
+
import { writeFile, mkdir } from "fs/promises";
|
|
57
|
+
import { dirname } from "path";
|
|
58
|
+
function createFileWriteTool(sandbox) {
|
|
59
|
+
return {
|
|
60
|
+
name: "file_write",
|
|
61
|
+
description: "Write content to a file. Creates parent directories. Overwrites if exists.",
|
|
62
|
+
input_schema: {
|
|
63
|
+
type: "object",
|
|
64
|
+
properties: {
|
|
65
|
+
path: { type: "string" },
|
|
66
|
+
content: { type: "string" }
|
|
67
|
+
},
|
|
68
|
+
required: ["path", "content"]
|
|
69
|
+
},
|
|
70
|
+
execute: async (input) => {
|
|
71
|
+
var filePath = resolvePath(input.path, sandbox);
|
|
72
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
73
|
+
await writeFile(filePath, input.content, "utf-8");
|
|
74
|
+
return { ok: true, path: filePath, bytesWritten: Buffer.byteLength(input.content, "utf-8") };
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// src/agent-tools/tools/local/file-edit.ts
|
|
80
|
+
import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
81
|
+
function createFileEditTool(sandbox) {
|
|
82
|
+
return {
|
|
83
|
+
name: "file_edit",
|
|
84
|
+
description: "Edit a file by replacing exact text match.",
|
|
85
|
+
input_schema: {
|
|
86
|
+
type: "object",
|
|
87
|
+
properties: {
|
|
88
|
+
path: { type: "string" },
|
|
89
|
+
old_text: { type: "string", description: "Exact text to find" },
|
|
90
|
+
new_text: { type: "string", description: "Replacement text" }
|
|
91
|
+
},
|
|
92
|
+
required: ["path", "old_text", "new_text"]
|
|
93
|
+
},
|
|
94
|
+
execute: async (input) => {
|
|
95
|
+
var filePath = resolvePath(input.path, sandbox);
|
|
96
|
+
var content = await readFile2(filePath, "utf-8");
|
|
97
|
+
if (!content.includes(input.old_text)) {
|
|
98
|
+
return { error: "old_text not found in file. Must match exactly including whitespace." };
|
|
99
|
+
}
|
|
100
|
+
await writeFile2(filePath, content.replace(input.old_text, input.new_text), "utf-8");
|
|
101
|
+
return { ok: true, path: filePath };
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// src/agent-tools/tools/local/file-list.ts
|
|
107
|
+
import { readdir, stat } from "fs/promises";
|
|
108
|
+
import { join as join2 } from "path";
|
|
109
|
+
function createFileListTool(sandbox) {
|
|
110
|
+
return {
|
|
111
|
+
name: "file_list",
|
|
112
|
+
description: "List files and directories at a path.",
|
|
113
|
+
input_schema: {
|
|
114
|
+
type: "object",
|
|
115
|
+
properties: {
|
|
116
|
+
path: { type: "string" }
|
|
117
|
+
},
|
|
118
|
+
required: ["path"]
|
|
119
|
+
},
|
|
120
|
+
execute: async (input) => {
|
|
121
|
+
var dirPath = resolvePath(input.path, sandbox);
|
|
122
|
+
var entries = await readdir(dirPath, { withFileTypes: true });
|
|
123
|
+
var results = [];
|
|
124
|
+
for (var entry of entries) {
|
|
125
|
+
var info = { name: entry.name, type: entry.isDirectory() ? "directory" : "file" };
|
|
126
|
+
try {
|
|
127
|
+
var s = await stat(join2(dirPath, entry.name));
|
|
128
|
+
info.size = s.size;
|
|
129
|
+
info.modified = s.mtime.toISOString();
|
|
130
|
+
} catch {
|
|
131
|
+
}
|
|
132
|
+
results.push(info);
|
|
133
|
+
if (results.length >= 500) break;
|
|
134
|
+
}
|
|
135
|
+
return { path: dirPath, entries: results, count: results.length };
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// src/agent-tools/tools/local/file-search.ts
|
|
141
|
+
import { readdir as readdir2 } from "fs/promises";
|
|
142
|
+
import { join as join3, relative } from "path";
|
|
143
|
+
function createFileSearchTool(sandbox) {
|
|
144
|
+
return {
|
|
145
|
+
name: "file_search",
|
|
146
|
+
description: "Search for files by name pattern (* wildcard supported).",
|
|
147
|
+
input_schema: {
|
|
148
|
+
type: "object",
|
|
149
|
+
properties: {
|
|
150
|
+
path: { type: "string" },
|
|
151
|
+
pattern: { type: "string", description: "Filename pattern (e.g. *.ts)" },
|
|
152
|
+
maxResults: { type: "number" }
|
|
153
|
+
},
|
|
154
|
+
required: ["path", "pattern"]
|
|
155
|
+
},
|
|
156
|
+
execute: async (input) => {
|
|
157
|
+
var dirPath = resolvePath(input.path, sandbox);
|
|
158
|
+
var max = input.maxResults || 50;
|
|
159
|
+
var regex = new RegExp("^" + input.pattern.replace(/\*/g, ".*").replace(/\?/g, ".") + "$", "i");
|
|
160
|
+
var results = [];
|
|
161
|
+
async function walk(dir, depth) {
|
|
162
|
+
if (depth > 10 || results.length >= max) return;
|
|
163
|
+
try {
|
|
164
|
+
var entries = await readdir2(dir, { withFileTypes: true });
|
|
165
|
+
for (var entry of entries) {
|
|
166
|
+
if (results.length >= max) break;
|
|
167
|
+
if (regex.test(entry.name)) results.push(relative(dirPath, join3(dir, entry.name)));
|
|
168
|
+
if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
169
|
+
await walk(join3(dir, entry.name), depth + 1);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
} catch {
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
await walk(dirPath, 0);
|
|
176
|
+
return { path: dirPath, matches: results, count: results.length };
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// src/agent-tools/tools/local/file-ops.ts
|
|
182
|
+
import { rename, unlink, mkdir as mkdir2 } from "fs/promises";
|
|
183
|
+
import { dirname as dirname2 } from "path";
|
|
184
|
+
function createFileMoveTool(sandbox) {
|
|
185
|
+
return {
|
|
186
|
+
name: "file_move",
|
|
187
|
+
description: "Move or rename a file.",
|
|
188
|
+
input_schema: {
|
|
189
|
+
type: "object",
|
|
190
|
+
properties: {
|
|
191
|
+
from: { type: "string" },
|
|
192
|
+
to: { type: "string" }
|
|
193
|
+
},
|
|
194
|
+
required: ["from", "to"]
|
|
195
|
+
},
|
|
196
|
+
execute: async (input) => {
|
|
197
|
+
var fromPath = resolvePath(input.from, sandbox);
|
|
198
|
+
var toPath = resolvePath(input.to, sandbox);
|
|
199
|
+
await mkdir2(dirname2(toPath), { recursive: true });
|
|
200
|
+
await rename(fromPath, toPath);
|
|
201
|
+
return { ok: true, from: fromPath, to: toPath };
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function createFileDeleteTool(sandbox) {
|
|
206
|
+
return {
|
|
207
|
+
name: "file_delete",
|
|
208
|
+
description: "Delete a file. Use with caution.",
|
|
209
|
+
input_schema: {
|
|
210
|
+
type: "object",
|
|
211
|
+
properties: {
|
|
212
|
+
path: { type: "string" }
|
|
213
|
+
},
|
|
214
|
+
required: ["path"]
|
|
215
|
+
},
|
|
216
|
+
execute: async (input) => {
|
|
217
|
+
var filePath = resolvePath(input.path, sandbox);
|
|
218
|
+
await unlink(filePath);
|
|
219
|
+
return { ok: true, deleted: filePath };
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// src/agent-tools/tools/local/shell.ts
|
|
225
|
+
import { exec as cpExec, spawn } from "child_process";
|
|
226
|
+
import { promisify } from "util";
|
|
227
|
+
import { platform } from "os";
|
|
228
|
+
var execAsync = promisify(cpExec);
|
|
229
|
+
var activeSessions = /* @__PURE__ */ new Map();
|
|
230
|
+
setInterval(() => {
|
|
231
|
+
var now = Date.now();
|
|
232
|
+
for (var [id, s] of activeSessions) {
|
|
233
|
+
if (now - s.startedAt > 30 * 60 * 1e3) {
|
|
234
|
+
try {
|
|
235
|
+
s.proc?.kill();
|
|
236
|
+
} catch {
|
|
237
|
+
}
|
|
238
|
+
activeSessions.delete(id);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}, 6e4);
|
|
242
|
+
function createShellTools(opts) {
|
|
243
|
+
return [
|
|
244
|
+
{
|
|
245
|
+
name: "shell_exec",
|
|
246
|
+
description: "Execute a shell command. For interactive commands (sudo, installers), use shell_interactive instead.",
|
|
247
|
+
input_schema: {
|
|
248
|
+
type: "object",
|
|
249
|
+
properties: {
|
|
250
|
+
command: { type: "string" },
|
|
251
|
+
cwd: { type: "string" },
|
|
252
|
+
timeout: { type: "number", description: "Seconds (default 30)" }
|
|
253
|
+
},
|
|
254
|
+
required: ["command"]
|
|
255
|
+
},
|
|
256
|
+
execute: async (input) => {
|
|
257
|
+
var _cmdLow = (input.command || "").trim().toLowerCase();
|
|
258
|
+
var _pkgPatterns = [
|
|
259
|
+
/^(sudo\s+)?brew\s+install\s/,
|
|
260
|
+
/^(sudo\s+)?apt(-get)?\s+install\s/,
|
|
261
|
+
/^(sudo\s+)?dnf\s+install\s/,
|
|
262
|
+
/^(sudo\s+)?pacman\s+-S\s/,
|
|
263
|
+
/^(sudo\s+)?snap\s+install\s/,
|
|
264
|
+
/^choco\s+install\s/,
|
|
265
|
+
/^winget\s+install\s/,
|
|
266
|
+
/^scoop\s+install\s/,
|
|
267
|
+
/^(sudo\s+)?pip3?\s+install\s(?!-r\s|--requirement)/,
|
|
268
|
+
/^npm\s+install\s+-g\s/
|
|
269
|
+
];
|
|
270
|
+
if (_pkgPatterns.some((p) => p.test(_cmdLow))) {
|
|
271
|
+
return { stdout: "", stderr: 'POLICY: Package installation via shell is not allowed. Use the install_dependency tool instead.\nExample: install_dependency({ command: "wget" })', exitCode: 1 };
|
|
272
|
+
}
|
|
273
|
+
var timeoutMs = (input.timeout || opts?.timeout || 30) * 1e3;
|
|
274
|
+
var cwd = input.cwd || opts?.cwd || process.cwd();
|
|
275
|
+
try {
|
|
276
|
+
var r = await execAsync(input.command, {
|
|
277
|
+
cwd,
|
|
278
|
+
timeout: timeoutMs,
|
|
279
|
+
maxBuffer: 1024 * 1024,
|
|
280
|
+
env: { ...process.env, TERM: "dumb", DEBIAN_FRONTEND: "noninteractive" }
|
|
281
|
+
});
|
|
282
|
+
var stdout = r.stdout || "";
|
|
283
|
+
var stderr = r.stderr || "";
|
|
284
|
+
if (stdout.length > 5e4) stdout = stdout.slice(0, 5e4) + "\n[...truncated]";
|
|
285
|
+
if (stderr.length > 1e4) stderr = stderr.slice(0, 1e4) + "\n[...truncated]";
|
|
286
|
+
return { stdout, stderr, exitCode: 0 };
|
|
287
|
+
} catch (err) {
|
|
288
|
+
return { stdout: (err.stdout || "").slice(0, 5e4), stderr: (err.stderr || err.message || "").slice(0, 1e4), exitCode: err.code || 1 };
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
name: "shell_interactive",
|
|
294
|
+
description: "Run an interactive command with PTY (sudo, apt install, ssh, etc.). Can send input like passwords. Returns session ID for follow-up.",
|
|
295
|
+
input_schema: {
|
|
296
|
+
type: "object",
|
|
297
|
+
properties: {
|
|
298
|
+
command: { type: "string", description: 'Command to run (e.g. "sudo apt install nginx")' },
|
|
299
|
+
input: { type: "string", description: "Text to send to stdin (e.g. password). Sent after command starts." },
|
|
300
|
+
inputDelay: { type: "number", description: "ms to wait before sending input (default 500)" },
|
|
301
|
+
cwd: { type: "string" },
|
|
302
|
+
timeout: { type: "number", description: "Seconds to wait for output (default 30)" },
|
|
303
|
+
sessionId: { type: "string", description: "Resume existing session instead of starting new command" }
|
|
304
|
+
},
|
|
305
|
+
required: []
|
|
306
|
+
},
|
|
307
|
+
execute: async (input) => {
|
|
308
|
+
if (input.sessionId) {
|
|
309
|
+
var session = activeSessions.get(input.sessionId);
|
|
310
|
+
if (!session) return { error: "Session not found or expired" };
|
|
311
|
+
if (input.input) {
|
|
312
|
+
try {
|
|
313
|
+
session.proc.stdin.write(input.input + "\n");
|
|
314
|
+
} catch {
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
await new Promise((r) => setTimeout(r, (input.timeout || 3) * 1e3));
|
|
318
|
+
var output = session.output;
|
|
319
|
+
session.output = "";
|
|
320
|
+
if (output.length > 5e4) output = output.slice(-5e4);
|
|
321
|
+
return { sessionId: input.sessionId, output, exitCode: session.exitCode, done: session.exitCode !== null };
|
|
322
|
+
}
|
|
323
|
+
var sessionId = "sh_" + Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
|
|
324
|
+
var shell = platform() === "win32" ? "powershell.exe" : "/bin/bash";
|
|
325
|
+
var args = platform() === "win32" ? ["-Command", input.command || "echo ready"] : ["-c", input.command || "echo ready"];
|
|
326
|
+
var cwd = input.cwd || opts?.cwd || process.cwd();
|
|
327
|
+
var proc = spawn(shell, args, {
|
|
328
|
+
cwd,
|
|
329
|
+
env: { ...process.env, TERM: "xterm-256color" },
|
|
330
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
331
|
+
});
|
|
332
|
+
var entry = { proc, output: "", exitCode: null, startedAt: Date.now() };
|
|
333
|
+
activeSessions.set(sessionId, entry);
|
|
334
|
+
proc.stdout?.on("data", (d) => {
|
|
335
|
+
entry.output += d.toString();
|
|
336
|
+
});
|
|
337
|
+
proc.stderr?.on("data", (d) => {
|
|
338
|
+
entry.output += d.toString();
|
|
339
|
+
});
|
|
340
|
+
proc.on("close", (code) => {
|
|
341
|
+
entry.exitCode = code ?? 0;
|
|
342
|
+
});
|
|
343
|
+
proc.on("error", (err) => {
|
|
344
|
+
entry.output += "\n[ERROR] " + err.message;
|
|
345
|
+
entry.exitCode = 1;
|
|
346
|
+
});
|
|
347
|
+
if (input.input) {
|
|
348
|
+
var delay = input.inputDelay || 500;
|
|
349
|
+
setTimeout(() => {
|
|
350
|
+
try {
|
|
351
|
+
proc.stdin.write(input.input + "\n");
|
|
352
|
+
} catch {
|
|
353
|
+
}
|
|
354
|
+
}, delay);
|
|
355
|
+
}
|
|
356
|
+
var waitMs = Math.min((input.timeout || 30) * 1e3, 6e4);
|
|
357
|
+
await new Promise((resolve2) => {
|
|
358
|
+
var timer = setTimeout(resolve2, waitMs);
|
|
359
|
+
proc.on("close", () => {
|
|
360
|
+
clearTimeout(timer);
|
|
361
|
+
setTimeout(resolve2, 200);
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
var output = entry.output;
|
|
365
|
+
entry.output = "";
|
|
366
|
+
if (output.length > 5e4) output = output.slice(-5e4);
|
|
367
|
+
return { sessionId, output, exitCode: entry.exitCode, done: entry.exitCode !== null };
|
|
368
|
+
}
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
name: "shell_sudo",
|
|
372
|
+
description: "Run a command with sudo. Handles password prompt automatically.",
|
|
373
|
+
input_schema: {
|
|
374
|
+
type: "object",
|
|
375
|
+
properties: {
|
|
376
|
+
command: { type: "string", description: "Command to run (without sudo prefix)" },
|
|
377
|
+
password: { type: "string", description: "User password for sudo" },
|
|
378
|
+
cwd: { type: "string" },
|
|
379
|
+
timeout: { type: "number", description: "Seconds (default 60)" }
|
|
380
|
+
},
|
|
381
|
+
required: ["command", "password"]
|
|
382
|
+
},
|
|
383
|
+
execute: async (input) => {
|
|
384
|
+
if (platform() === "win32") {
|
|
385
|
+
return { error: 'Use shell_interactive with "Start-Process ... -Verb RunAs" for Windows elevation' };
|
|
386
|
+
}
|
|
387
|
+
var timeoutMs = (input.timeout || 60) * 1e3;
|
|
388
|
+
var cwd = input.cwd || opts?.cwd || process.cwd();
|
|
389
|
+
var cmd = `echo '${input.password.replace(/'/g, "'\\''")}' | sudo -S ${input.command}`;
|
|
390
|
+
try {
|
|
391
|
+
var r = await execAsync(cmd, {
|
|
392
|
+
cwd,
|
|
393
|
+
timeout: timeoutMs,
|
|
394
|
+
maxBuffer: 2 * 1024 * 1024,
|
|
395
|
+
env: { ...process.env, TERM: "dumb", DEBIAN_FRONTEND: "noninteractive" }
|
|
396
|
+
});
|
|
397
|
+
var stdout = r.stdout || "";
|
|
398
|
+
var stderr = (r.stderr || "").replace(/\[sudo\].*\n?/g, "");
|
|
399
|
+
if (stdout.length > 5e4) stdout = stdout.slice(0, 5e4) + "\n[...truncated]";
|
|
400
|
+
return { stdout, stderr, exitCode: 0 };
|
|
401
|
+
} catch (err) {
|
|
402
|
+
var stderrMsg = (err.stderr || err.message || "").replace(/\[sudo\].*\n?/g, "");
|
|
403
|
+
return { stdout: (err.stdout || "").slice(0, 5e4), stderr: stderrMsg.slice(0, 1e4), exitCode: err.code || 1 };
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
name: "shell_install",
|
|
409
|
+
description: "Install a package using the system package manager. Auto-detects apt/yum/dnf/pacman/brew/choco/winget.",
|
|
410
|
+
input_schema: {
|
|
411
|
+
type: "object",
|
|
412
|
+
properties: {
|
|
413
|
+
package: { type: "string", description: "Package name(s) to install" },
|
|
414
|
+
password: { type: "string", description: "Sudo password (not needed for brew/choco/winget)" }
|
|
415
|
+
},
|
|
416
|
+
required: ["package"]
|
|
417
|
+
},
|
|
418
|
+
execute: async (input) => {
|
|
419
|
+
var os = platform();
|
|
420
|
+
var pkg = input.package;
|
|
421
|
+
var cmd;
|
|
422
|
+
var needsSudo = true;
|
|
423
|
+
if (os === "darwin") {
|
|
424
|
+
cmd = `brew install ${pkg}`;
|
|
425
|
+
needsSudo = false;
|
|
426
|
+
} else if (os === "win32") {
|
|
427
|
+
cmd = `winget install ${pkg}`;
|
|
428
|
+
needsSudo = false;
|
|
429
|
+
} else {
|
|
430
|
+
try {
|
|
431
|
+
await execAsync("which apt-get");
|
|
432
|
+
cmd = `apt-get install -y ${pkg}`;
|
|
433
|
+
} catch {
|
|
434
|
+
try {
|
|
435
|
+
await execAsync("which dnf");
|
|
436
|
+
cmd = `dnf install -y ${pkg}`;
|
|
437
|
+
} catch {
|
|
438
|
+
try {
|
|
439
|
+
await execAsync("which yum");
|
|
440
|
+
cmd = `yum install -y ${pkg}`;
|
|
441
|
+
} catch {
|
|
442
|
+
try {
|
|
443
|
+
await execAsync("which pacman");
|
|
444
|
+
cmd = `pacman -S --noconfirm ${pkg}`;
|
|
445
|
+
} catch {
|
|
446
|
+
try {
|
|
447
|
+
await execAsync("which apk");
|
|
448
|
+
cmd = `apk add ${pkg}`;
|
|
449
|
+
} catch {
|
|
450
|
+
return { error: "No supported package manager found (apt/dnf/yum/pacman/apk)" };
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
if (needsSudo && input.password) {
|
|
458
|
+
cmd = `echo '${input.password.replace(/'/g, "'\\''")}' | sudo -S ${cmd}`;
|
|
459
|
+
} else if (needsSudo) {
|
|
460
|
+
cmd = `sudo ${cmd}`;
|
|
461
|
+
}
|
|
462
|
+
try {
|
|
463
|
+
var r = await execAsync(cmd, {
|
|
464
|
+
timeout: 3e5,
|
|
465
|
+
maxBuffer: 2 * 1024 * 1024,
|
|
466
|
+
env: { ...process.env, DEBIAN_FRONTEND: "noninteractive", TERM: "dumb" }
|
|
467
|
+
});
|
|
468
|
+
return { ok: true, stdout: (r.stdout || "").slice(-5e3), stderr: (r.stderr || "").slice(-2e3) };
|
|
469
|
+
} catch (err) {
|
|
470
|
+
return { error: (err.stderr || err.message || "").slice(0, 1e4), exitCode: err.code || 1 };
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
name: "shell_session_list",
|
|
476
|
+
description: "List active interactive shell sessions.",
|
|
477
|
+
input_schema: { type: "object", properties: {}, required: [] },
|
|
478
|
+
execute: async () => ({
|
|
479
|
+
sessions: Array.from(activeSessions.entries()).map(([id, s]) => ({
|
|
480
|
+
id,
|
|
481
|
+
done: s.exitCode !== null,
|
|
482
|
+
exitCode: s.exitCode,
|
|
483
|
+
age: Math.round((Date.now() - s.startedAt) / 1e3) + "s",
|
|
484
|
+
buffered: s.output.length + " chars"
|
|
485
|
+
}))
|
|
486
|
+
})
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
name: "shell_session_kill",
|
|
490
|
+
description: "Kill an interactive shell session.",
|
|
491
|
+
input_schema: {
|
|
492
|
+
type: "object",
|
|
493
|
+
properties: { sessionId: { type: "string" } },
|
|
494
|
+
required: ["sessionId"]
|
|
495
|
+
},
|
|
496
|
+
execute: async (input) => {
|
|
497
|
+
var s = activeSessions.get(input.sessionId);
|
|
498
|
+
if (!s) return { error: "Not found" };
|
|
499
|
+
try {
|
|
500
|
+
s.proc.kill("SIGKILL");
|
|
501
|
+
} catch {
|
|
502
|
+
}
|
|
503
|
+
activeSessions.delete(input.sessionId);
|
|
504
|
+
return { ok: true };
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
];
|
|
508
|
+
}
|
|
509
|
+
function createShellExecTool(opts) {
|
|
510
|
+
return createShellTools(opts)[0];
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// src/agent-tools/tools/local/system-info.ts
|
|
514
|
+
import { hostname, platform as platform2, arch, cpus, totalmem, freemem, uptime, homedir as homedir2, userInfo } from "os";
|
|
515
|
+
function createSystemInfoTool() {
|
|
516
|
+
return {
|
|
517
|
+
name: "system_info",
|
|
518
|
+
description: "Get host system info (OS, CPU, memory, uptime).",
|
|
519
|
+
input_schema: { type: "object", properties: {} },
|
|
520
|
+
execute: async () => {
|
|
521
|
+
return {
|
|
522
|
+
hostname: hostname(),
|
|
523
|
+
platform: platform2(),
|
|
524
|
+
arch: arch(),
|
|
525
|
+
cpuCount: cpus().length,
|
|
526
|
+
cpuModel: cpus()[0]?.model || "unknown",
|
|
527
|
+
totalMemoryGB: +(totalmem() / 1073741824).toFixed(1),
|
|
528
|
+
freeMemoryGB: +(freemem() / 1073741824).toFixed(1),
|
|
529
|
+
uptimeHours: +(uptime() / 3600).toFixed(1),
|
|
530
|
+
homeDir: homedir2(),
|
|
531
|
+
user: userInfo().username,
|
|
532
|
+
cwd: process.cwd(),
|
|
533
|
+
nodeVersion: process.version
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// src/agent-tools/tools/local/coding.ts
|
|
540
|
+
import { readFile as readFile3, writeFile as writeFile3, readdir as readdir3, access } from "fs/promises";
|
|
541
|
+
import { exec as cpExec2 } from "child_process";
|
|
542
|
+
import { promisify as promisify2 } from "util";
|
|
543
|
+
import { join as join4, relative as relative2, extname, dirname as dirname3 } from "path";
|
|
544
|
+
var execAsync2 = promisify2(cpExec2);
|
|
545
|
+
async function exists(p) {
|
|
546
|
+
try {
|
|
547
|
+
await access(p);
|
|
548
|
+
return true;
|
|
549
|
+
} catch {
|
|
550
|
+
return false;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
async function detectProjectRoot(startDir) {
|
|
554
|
+
var dir = startDir;
|
|
555
|
+
for (var i = 0; i < 10; i++) {
|
|
556
|
+
for (var marker of ["package.json", "Cargo.toml", "go.mod", "pyproject.toml", "pom.xml", ".git"]) {
|
|
557
|
+
if (await exists(join4(dir, marker))) return dir;
|
|
558
|
+
}
|
|
559
|
+
var parent = dirname3(dir);
|
|
560
|
+
if (parent === dir) break;
|
|
561
|
+
dir = parent;
|
|
562
|
+
}
|
|
563
|
+
return startDir;
|
|
564
|
+
}
|
|
565
|
+
async function getProjectType(root) {
|
|
566
|
+
if (await exists(join4(root, "package.json"))) {
|
|
567
|
+
try {
|
|
568
|
+
var pkg = JSON.parse(await readFile3(join4(root, "package.json"), "utf-8"));
|
|
569
|
+
return {
|
|
570
|
+
type: "node",
|
|
571
|
+
buildCmd: pkg.scripts?.build ? "npm run build" : void 0,
|
|
572
|
+
testCmd: pkg.scripts?.test ? "npm test" : void 0,
|
|
573
|
+
lintCmd: pkg.scripts?.lint ? "npm run lint" : void 0
|
|
574
|
+
};
|
|
575
|
+
} catch {
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
if (await exists(join4(root, "Cargo.toml"))) return { type: "rust", buildCmd: "cargo build", testCmd: "cargo test" };
|
|
579
|
+
if (await exists(join4(root, "go.mod"))) return { type: "go", buildCmd: "go build ./...", testCmd: "go test ./..." };
|
|
580
|
+
if (await exists(join4(root, "pyproject.toml"))) return { type: "python", testCmd: "pytest" };
|
|
581
|
+
return { type: "unknown" };
|
|
582
|
+
}
|
|
583
|
+
function parseBuildErrors(output) {
|
|
584
|
+
var errors = [];
|
|
585
|
+
var tsPattern = /([^\s]+\.[tj]sx?)[:(](\d+)[,:](\d+)[):]?\s*[-–]?\s*(error\s+\w+:\s*.+)/gi;
|
|
586
|
+
var m;
|
|
587
|
+
while (m = tsPattern.exec(output)) {
|
|
588
|
+
errors.push({ file: m[1], line: parseInt(m[2]), message: m[4] });
|
|
589
|
+
}
|
|
590
|
+
var genericPattern = /(?:error|Error)[:\s]+(.+)/g;
|
|
591
|
+
if (errors.length === 0) {
|
|
592
|
+
while (m = genericPattern.exec(output)) {
|
|
593
|
+
if (!m[1].includes("node_modules")) errors.push({ message: m[1].trim() });
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
var rustPattern = /error\[(\w+)\]:\s*(.+)\n\s*-->\s*([^:]+):(\d+)/g;
|
|
597
|
+
while (m = rustPattern.exec(output)) {
|
|
598
|
+
errors.push({ file: m[3], line: parseInt(m[4]), message: `${m[1]}: ${m[2]}` });
|
|
599
|
+
}
|
|
600
|
+
return errors.slice(0, 20);
|
|
601
|
+
}
|
|
602
|
+
function createCodingTools(opts) {
|
|
603
|
+
var defaultCwd = opts?.cwd || process.cwd();
|
|
604
|
+
var sandbox = opts?.sandbox;
|
|
605
|
+
return [
|
|
606
|
+
// ─── 1. Code Plan ─────────────────────────────────
|
|
607
|
+
{
|
|
608
|
+
name: "code_plan",
|
|
609
|
+
description: "Create a structured implementation plan before writing code. Analyzes the codebase, identifies files to change, and outputs a step-by-step plan. ALWAYS use this before multi-file changes.",
|
|
610
|
+
input_schema: {
|
|
611
|
+
type: "object",
|
|
612
|
+
properties: {
|
|
613
|
+
task: { type: "string", description: "What you need to implement" },
|
|
614
|
+
cwd: { type: "string", description: "Project root directory" },
|
|
615
|
+
context_files: {
|
|
616
|
+
type: "array",
|
|
617
|
+
items: { type: "string" },
|
|
618
|
+
description: "Key files to read for context before planning"
|
|
619
|
+
}
|
|
620
|
+
},
|
|
621
|
+
required: ["task"]
|
|
622
|
+
},
|
|
623
|
+
execute: async (input) => {
|
|
624
|
+
var cwd = input.cwd || defaultCwd;
|
|
625
|
+
var root = await detectProjectRoot(cwd);
|
|
626
|
+
var project = await getProjectType(root);
|
|
627
|
+
var contexts = [];
|
|
628
|
+
if (input.context_files) {
|
|
629
|
+
for (var f of input.context_files.slice(0, 10)) {
|
|
630
|
+
try {
|
|
631
|
+
var fp = resolvePath(f, sandbox);
|
|
632
|
+
var content = await readFile3(fp, "utf-8");
|
|
633
|
+
var lines = content.split("\n");
|
|
634
|
+
contexts.push({
|
|
635
|
+
path: relative2(root, fp),
|
|
636
|
+
snippet: lines.slice(0, 200).join("\n") + (lines.length > 200 ? `
|
|
637
|
+
[...${lines.length - 200} more lines]` : "")
|
|
638
|
+
});
|
|
639
|
+
} catch {
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
var tree = [];
|
|
644
|
+
async function walk(dir, depth, prefix) {
|
|
645
|
+
if (depth > 2) return;
|
|
646
|
+
try {
|
|
647
|
+
var entries = await readdir3(dir, { withFileTypes: true });
|
|
648
|
+
var filtered = entries.filter(
|
|
649
|
+
(e2) => !["node_modules", ".git", "dist", "build", ".next", "__pycache__", "target", ".cache", "coverage"].includes(e2.name) && !e2.name.startsWith(".")
|
|
650
|
+
).sort((a, b) => {
|
|
651
|
+
if (a.isDirectory() !== b.isDirectory()) return a.isDirectory() ? -1 : 1;
|
|
652
|
+
return a.name.localeCompare(b.name);
|
|
653
|
+
});
|
|
654
|
+
for (var e of filtered.slice(0, 50)) {
|
|
655
|
+
tree.push(`${prefix}${e.isDirectory() ? "\u{1F4C1} " : " "}${e.name}`);
|
|
656
|
+
if (e.isDirectory()) await walk(join4(dir, e.name), depth + 1, prefix + " ");
|
|
657
|
+
}
|
|
658
|
+
} catch {
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
await walk(root, 0, "");
|
|
662
|
+
return {
|
|
663
|
+
projectRoot: root,
|
|
664
|
+
projectType: project.type,
|
|
665
|
+
buildCommand: project.buildCmd || "unknown",
|
|
666
|
+
testCommand: project.testCmd || "unknown",
|
|
667
|
+
structure: tree.join("\n"),
|
|
668
|
+
contextFiles: contexts,
|
|
669
|
+
instructions: [
|
|
670
|
+
"Based on the project info above, create your plan:",
|
|
671
|
+
"1. List ALL files you need to create or modify",
|
|
672
|
+
"2. For each file, describe the specific changes",
|
|
673
|
+
"3. Identify the order of changes (dependencies first)",
|
|
674
|
+
"4. Note any imports/exports that need updating",
|
|
675
|
+
"5. Plan your build + test verification step",
|
|
676
|
+
`6. Build command: ${project.buildCmd || "determine from project"}`
|
|
677
|
+
].join("\n")
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
},
|
|
681
|
+
// ─── 2. Code Search (ripgrep-powered) ─────────────
|
|
682
|
+
{
|
|
683
|
+
name: "code_search",
|
|
684
|
+
description: "Search codebase for pattern matches. Uses ripgrep if available, falls back to grep. Returns matches with file, line number, and context.",
|
|
685
|
+
input_schema: {
|
|
686
|
+
type: "object",
|
|
687
|
+
properties: {
|
|
688
|
+
pattern: { type: "string", description: "Search pattern (regex supported)" },
|
|
689
|
+
path: { type: "string", description: "Directory or file to search (default: project root)" },
|
|
690
|
+
file_pattern: { type: "string", description: 'File glob filter (e.g. "*.ts", "*.py")' },
|
|
691
|
+
context_lines: { type: "number", description: "Lines of context around matches (default 2)" },
|
|
692
|
+
max_results: { type: "number", description: "Max matches (default 30)" },
|
|
693
|
+
case_sensitive: { type: "boolean", description: "Case sensitive (default false)" },
|
|
694
|
+
whole_word: { type: "boolean", description: "Match whole words only" },
|
|
695
|
+
fixed_string: { type: "boolean", description: "Treat pattern as literal string, not regex" }
|
|
696
|
+
},
|
|
697
|
+
required: ["pattern"]
|
|
698
|
+
},
|
|
699
|
+
execute: async (input) => {
|
|
700
|
+
var searchPath = resolvePath(input.path || defaultCwd, sandbox);
|
|
701
|
+
var ctx = input.context_lines ?? 2;
|
|
702
|
+
var max = input.max_results || 30;
|
|
703
|
+
var flags = [];
|
|
704
|
+
if (!input.case_sensitive) flags.push("-i");
|
|
705
|
+
if (input.whole_word) flags.push("-w");
|
|
706
|
+
if (input.fixed_string) flags.push("-F");
|
|
707
|
+
var useRg = false;
|
|
708
|
+
try {
|
|
709
|
+
await execAsync2("which rg");
|
|
710
|
+
useRg = true;
|
|
711
|
+
} catch {
|
|
712
|
+
}
|
|
713
|
+
var cmd;
|
|
714
|
+
if (useRg) {
|
|
715
|
+
cmd = `rg --json -C ${ctx} -m ${max} ${flags.join(" ")}`;
|
|
716
|
+
if (input.file_pattern) cmd += ` -g '${input.file_pattern}'`;
|
|
717
|
+
cmd += ` -- '${input.pattern.replace(/'/g, "'\\''")}' '${searchPath}'`;
|
|
718
|
+
} else {
|
|
719
|
+
cmd = `grep -rn ${flags.join(" ")} -C ${ctx}`;
|
|
720
|
+
if (input.file_pattern) cmd += ` --include='${input.file_pattern}'`;
|
|
721
|
+
cmd += ` -- '${input.pattern.replace(/'/g, "'\\''")}' '${searchPath}'`;
|
|
722
|
+
cmd += ` | head -${max * (ctx * 2 + 3)}`;
|
|
723
|
+
}
|
|
724
|
+
try {
|
|
725
|
+
var { stdout, stderr } = await execAsync2(cmd, {
|
|
726
|
+
timeout: 15e3,
|
|
727
|
+
maxBuffer: 512 * 1024,
|
|
728
|
+
cwd: searchPath
|
|
729
|
+
});
|
|
730
|
+
if (useRg && stdout) {
|
|
731
|
+
var matches = [];
|
|
732
|
+
var _currentFile = "";
|
|
733
|
+
var _contextBuf = [];
|
|
734
|
+
for (var line of stdout.split("\n").filter(Boolean)) {
|
|
735
|
+
try {
|
|
736
|
+
var j = JSON.parse(line);
|
|
737
|
+
if (j.type === "match") {
|
|
738
|
+
matches.push({
|
|
739
|
+
file: relative2(searchPath, j.data.path.text),
|
|
740
|
+
line: j.data.line_number,
|
|
741
|
+
text: j.data.lines.text.trimEnd()
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
} catch {
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
return { matches: matches.slice(0, max), total: matches.length, engine: "ripgrep" };
|
|
748
|
+
}
|
|
749
|
+
return { output: stdout.slice(0, 5e4), engine: "grep" };
|
|
750
|
+
} catch (err) {
|
|
751
|
+
if (err.code === 1) return { matches: [], message: "No matches found" };
|
|
752
|
+
return { error: (err.stderr || err.message).slice(0, 5e3) };
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
},
|
|
756
|
+
// ─── 3. Code Read (with line numbers + ranges) ────
|
|
757
|
+
{
|
|
758
|
+
name: "code_read",
|
|
759
|
+
description: "Read a file with line numbers. Supports reading specific line ranges. Better than file_read for code \u2014 always shows line numbers.",
|
|
760
|
+
input_schema: {
|
|
761
|
+
type: "object",
|
|
762
|
+
properties: {
|
|
763
|
+
path: { type: "string" },
|
|
764
|
+
from_line: { type: "number", description: "Start line (1-indexed, default 1)" },
|
|
765
|
+
to_line: { type: "number", description: "End line (default: from_line + 200 or EOF)" },
|
|
766
|
+
symbols: { type: "boolean", description: "Show only function/class/type definitions (outline mode)" }
|
|
767
|
+
},
|
|
768
|
+
required: ["path"]
|
|
769
|
+
},
|
|
770
|
+
execute: async (input) => {
|
|
771
|
+
var fp = resolvePath(input.path, sandbox);
|
|
772
|
+
var content = await readFile3(fp, "utf-8");
|
|
773
|
+
var lines = content.split("\n");
|
|
774
|
+
var totalLines = lines.length;
|
|
775
|
+
if (input.symbols) {
|
|
776
|
+
var ext = extname(fp).toLowerCase();
|
|
777
|
+
var symbolPattern;
|
|
778
|
+
if ([".ts", ".tsx", ".js", ".jsx"].includes(ext)) {
|
|
779
|
+
symbolPattern = /^(?:export\s+)?(?:async\s+)?(?:function|class|type|interface|enum|const|var|let)\s+\w+|^\s*(?:static\s+)?(?:async\s+)?(?:get\s+|set\s+)?\w+\s*\(/;
|
|
780
|
+
} else if ([".py"].includes(ext)) {
|
|
781
|
+
symbolPattern = /^(?:class|def|async def)\s+\w+/;
|
|
782
|
+
} else if ([".rs"].includes(ext)) {
|
|
783
|
+
symbolPattern = /^(?:pub\s+)?(?:fn|struct|enum|trait|impl|type|const|static)\s+/;
|
|
784
|
+
} else if ([".go"].includes(ext)) {
|
|
785
|
+
symbolPattern = /^(?:func|type|var|const)\s+/;
|
|
786
|
+
} else {
|
|
787
|
+
symbolPattern = /^(?:function|class|def|fn|type|interface|struct|enum)\s+/;
|
|
788
|
+
}
|
|
789
|
+
var symbols = [];
|
|
790
|
+
for (var i = 0; i < lines.length; i++) {
|
|
791
|
+
if (symbolPattern.test(lines[i])) {
|
|
792
|
+
symbols.push(`${String(i + 1).padStart(4)}\u2502 ${lines[i]}`);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
return { path: fp, totalLines, symbols: symbols.join("\n") || "No symbols found" };
|
|
796
|
+
}
|
|
797
|
+
var from = Math.max(1, input.from_line || 1);
|
|
798
|
+
var to = Math.min(totalLines, input.to_line || from + 199);
|
|
799
|
+
var slice = lines.slice(from - 1, to);
|
|
800
|
+
var numbered = slice.map((l, i2) => `${String(from + i2).padStart(4)}\u2502 ${l}`).join("\n");
|
|
801
|
+
return {
|
|
802
|
+
path: fp,
|
|
803
|
+
totalLines,
|
|
804
|
+
range: `${from}-${to}`,
|
|
805
|
+
content: numbered,
|
|
806
|
+
hasMore: to < totalLines
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
},
|
|
810
|
+
// ─── 4. Code Multi-Edit (batch edits in one call) ─
|
|
811
|
+
{
|
|
812
|
+
name: "code_multi_edit",
|
|
813
|
+
description: "Apply multiple edits to one or more files in a single call. More efficient than repeated file_edit calls. Edits are applied in order.",
|
|
814
|
+
input_schema: {
|
|
815
|
+
type: "object",
|
|
816
|
+
properties: {
|
|
817
|
+
edits: {
|
|
818
|
+
type: "array",
|
|
819
|
+
items: {
|
|
820
|
+
type: "object",
|
|
821
|
+
properties: {
|
|
822
|
+
path: { type: "string" },
|
|
823
|
+
old_text: { type: "string" },
|
|
824
|
+
new_text: { type: "string" }
|
|
825
|
+
},
|
|
826
|
+
required: ["path", "old_text", "new_text"]
|
|
827
|
+
},
|
|
828
|
+
description: "Array of {path, old_text, new_text} edits"
|
|
829
|
+
}
|
|
830
|
+
},
|
|
831
|
+
required: ["edits"]
|
|
832
|
+
},
|
|
833
|
+
execute: async (input) => {
|
|
834
|
+
var results = [];
|
|
835
|
+
var byFile = /* @__PURE__ */ new Map();
|
|
836
|
+
for (var edit of input.edits) {
|
|
837
|
+
var fp = resolvePath(edit.path, sandbox);
|
|
838
|
+
if (!byFile.has(fp)) byFile.set(fp, []);
|
|
839
|
+
byFile.get(fp).push({ old_text: edit.old_text, new_text: edit.new_text });
|
|
840
|
+
}
|
|
841
|
+
for (var [filePath, edits] of byFile) {
|
|
842
|
+
try {
|
|
843
|
+
var content = await readFile3(filePath, "utf-8");
|
|
844
|
+
for (var e of edits) {
|
|
845
|
+
if (!content.includes(e.old_text)) {
|
|
846
|
+
results.push({ path: filePath, ok: false, error: `old_text not found: "${e.old_text.slice(0, 60)}..."` });
|
|
847
|
+
continue;
|
|
848
|
+
}
|
|
849
|
+
content = content.replace(e.old_text, e.new_text);
|
|
850
|
+
results.push({ path: filePath, ok: true });
|
|
851
|
+
}
|
|
852
|
+
await writeFile3(filePath, content, "utf-8");
|
|
853
|
+
} catch (err) {
|
|
854
|
+
results.push({ path: filePath, ok: false, error: err.message });
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
var success = results.filter((r) => r.ok).length;
|
|
858
|
+
var failed = results.filter((r) => !r.ok);
|
|
859
|
+
return { total: results.length, success, failed: failed.length > 0 ? failed : void 0 };
|
|
860
|
+
}
|
|
861
|
+
},
|
|
862
|
+
// ─── 5. Code Build (with error parsing) ───────────
|
|
863
|
+
{
|
|
864
|
+
name: "code_build",
|
|
865
|
+
description: "Build the project and parse errors. Auto-detects build command from package.json/Cargo.toml/etc. Returns structured error list with file + line.",
|
|
866
|
+
input_schema: {
|
|
867
|
+
type: "object",
|
|
868
|
+
properties: {
|
|
869
|
+
cwd: { type: "string", description: "Project root (auto-detected if omitted)" },
|
|
870
|
+
command: { type: "string", description: "Custom build command (overrides auto-detect)" },
|
|
871
|
+
clean: { type: "boolean", description: "Clean dist/build before building" }
|
|
872
|
+
},
|
|
873
|
+
required: []
|
|
874
|
+
},
|
|
875
|
+
execute: async (input) => {
|
|
876
|
+
var cwd = input.cwd || defaultCwd;
|
|
877
|
+
var root = await detectProjectRoot(cwd);
|
|
878
|
+
var project = await getProjectType(root);
|
|
879
|
+
var buildCmd = input.command || project.buildCmd;
|
|
880
|
+
if (!buildCmd) return { error: `Cannot auto-detect build command for ${project.type} project. Specify command manually.` };
|
|
881
|
+
if (input.clean) {
|
|
882
|
+
try {
|
|
883
|
+
if (project.type === "node") await execAsync2("rm -rf dist build .next", { cwd: root });
|
|
884
|
+
else if (project.type === "rust") await execAsync2("cargo clean", { cwd: root });
|
|
885
|
+
} catch {
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
var startTime = Date.now();
|
|
889
|
+
try {
|
|
890
|
+
var { stdout, stderr } = await execAsync2(buildCmd, {
|
|
891
|
+
cwd: root,
|
|
892
|
+
timeout: 12e4,
|
|
893
|
+
maxBuffer: 2 * 1024 * 1024,
|
|
894
|
+
env: { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" }
|
|
895
|
+
});
|
|
896
|
+
var duration = Date.now() - startTime;
|
|
897
|
+
return {
|
|
898
|
+
ok: true,
|
|
899
|
+
duration: `${duration}ms`,
|
|
900
|
+
output: (stdout + "\n" + stderr).trim().slice(-5e3)
|
|
901
|
+
};
|
|
902
|
+
} catch (err) {
|
|
903
|
+
var duration = Date.now() - startTime;
|
|
904
|
+
var output = ((err.stdout || "") + "\n" + (err.stderr || "")).trim();
|
|
905
|
+
var errors = parseBuildErrors(output);
|
|
906
|
+
return {
|
|
907
|
+
ok: false,
|
|
908
|
+
duration: `${duration}ms`,
|
|
909
|
+
exitCode: err.code,
|
|
910
|
+
errors: errors.length > 0 ? errors : void 0,
|
|
911
|
+
output: output.slice(-1e4)
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
},
|
|
916
|
+
// ─── 6. Code Test ─────────────────────────────────
|
|
917
|
+
{
|
|
918
|
+
name: "code_test",
|
|
919
|
+
description: "Run project tests and parse results. Auto-detects test framework.",
|
|
920
|
+
input_schema: {
|
|
921
|
+
type: "object",
|
|
922
|
+
properties: {
|
|
923
|
+
cwd: { type: "string" },
|
|
924
|
+
command: { type: "string", description: "Custom test command" },
|
|
925
|
+
filter: { type: "string", description: "Test name/file filter pattern" },
|
|
926
|
+
coverage: { type: "boolean", description: "Run with coverage" }
|
|
927
|
+
},
|
|
928
|
+
required: []
|
|
929
|
+
},
|
|
930
|
+
execute: async (input) => {
|
|
931
|
+
var cwd = input.cwd || defaultCwd;
|
|
932
|
+
var root = await detectProjectRoot(cwd);
|
|
933
|
+
var project = await getProjectType(root);
|
|
934
|
+
var testCmd = input.command || project.testCmd;
|
|
935
|
+
if (!testCmd) return { error: "No test command found. Specify one manually." };
|
|
936
|
+
if (input.filter) {
|
|
937
|
+
if (project.type === "node") testCmd += ` -- --grep "${input.filter}"`;
|
|
938
|
+
else if (project.type === "rust") testCmd += ` ${input.filter}`;
|
|
939
|
+
else if (project.type === "python") testCmd += ` -k "${input.filter}"`;
|
|
940
|
+
}
|
|
941
|
+
if (input.coverage && project.type === "node") testCmd += " -- --coverage";
|
|
942
|
+
try {
|
|
943
|
+
var { stdout, stderr } = await execAsync2(testCmd, {
|
|
944
|
+
cwd: root,
|
|
945
|
+
timeout: 3e5,
|
|
946
|
+
maxBuffer: 2 * 1024 * 1024,
|
|
947
|
+
env: { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1", CI: "1" }
|
|
948
|
+
});
|
|
949
|
+
return { ok: true, output: (stdout + "\n" + stderr).trim().slice(-1e4) };
|
|
950
|
+
} catch (err) {
|
|
951
|
+
return {
|
|
952
|
+
ok: false,
|
|
953
|
+
exitCode: err.code,
|
|
954
|
+
output: ((err.stdout || "") + "\n" + (err.stderr || "")).trim().slice(-1e4)
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
},
|
|
959
|
+
// ─── 7. Code Git ──────────────────────────────────
|
|
960
|
+
{
|
|
961
|
+
name: "code_git",
|
|
962
|
+
description: "Git operations: status, diff, log, commit, push, branch, stash, blame.",
|
|
963
|
+
input_schema: {
|
|
964
|
+
type: "object",
|
|
965
|
+
properties: {
|
|
966
|
+
action: { type: "string", description: "status|diff|log|commit|push|pull|branch|checkout|stash|blame|add" },
|
|
967
|
+
args: { type: "string", description: "Additional arguments (e.g. file path for diff/blame, message for commit)" },
|
|
968
|
+
cwd: { type: "string" }
|
|
969
|
+
},
|
|
970
|
+
required: ["action"]
|
|
971
|
+
},
|
|
972
|
+
execute: async (input) => {
|
|
973
|
+
var cwd = input.cwd || defaultCwd;
|
|
974
|
+
var root = await detectProjectRoot(cwd);
|
|
975
|
+
var args = input.args || "";
|
|
976
|
+
var cmdMap = {
|
|
977
|
+
status: "git status --short",
|
|
978
|
+
diff: `git diff ${args}`,
|
|
979
|
+
log: `git log --oneline -20 ${args}`,
|
|
980
|
+
commit: `git commit ${args.startsWith("-") ? args : `-m "${args.replace(/"/g, '\\"')}"`}`,
|
|
981
|
+
push: `git push ${args}`,
|
|
982
|
+
pull: `git pull ${args}`,
|
|
983
|
+
branch: args ? `git checkout -b ${args}` : "git branch -a",
|
|
984
|
+
checkout: `git checkout ${args}`,
|
|
985
|
+
stash: `git stash ${args || "push"}`,
|
|
986
|
+
blame: `git blame ${args}`,
|
|
987
|
+
add: `git add ${args || "."}`
|
|
988
|
+
};
|
|
989
|
+
var cmd = cmdMap[input.action];
|
|
990
|
+
if (!cmd) return { error: `Unknown action: ${input.action}. Use: ${Object.keys(cmdMap).join(", ")}` };
|
|
991
|
+
try {
|
|
992
|
+
var { stdout, stderr } = await execAsync2(cmd, { cwd: root, timeout: 3e4 });
|
|
993
|
+
return { output: (stdout || stderr || "").trim().slice(0, 5e4) };
|
|
994
|
+
} catch (err) {
|
|
995
|
+
return { error: (err.stderr || err.message).slice(0, 5e3), exitCode: err.code };
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
},
|
|
999
|
+
// ─── 8. Code Create File (with directory creation) ─
|
|
1000
|
+
{
|
|
1001
|
+
name: "code_create",
|
|
1002
|
+
description: "Create a new file with content. Automatically creates parent directories. Better for new files than file_write.",
|
|
1003
|
+
input_schema: {
|
|
1004
|
+
type: "object",
|
|
1005
|
+
properties: {
|
|
1006
|
+
path: { type: "string" },
|
|
1007
|
+
content: { type: "string" },
|
|
1008
|
+
overwrite: { type: "boolean", description: "Overwrite if exists (default false)" }
|
|
1009
|
+
},
|
|
1010
|
+
required: ["path", "content"]
|
|
1011
|
+
},
|
|
1012
|
+
execute: async (input) => {
|
|
1013
|
+
var fp = resolvePath(input.path, sandbox);
|
|
1014
|
+
if (!input.overwrite && await exists(fp)) {
|
|
1015
|
+
return { error: `File already exists: ${fp}. Set overwrite: true to replace.` };
|
|
1016
|
+
}
|
|
1017
|
+
var { mkdir: mkdir3 } = await import("fs/promises");
|
|
1018
|
+
await mkdir3(dirname3(fp), { recursive: true });
|
|
1019
|
+
await writeFile3(fp, input.content, "utf-8");
|
|
1020
|
+
var lines = input.content.split("\n").length;
|
|
1021
|
+
return { ok: true, path: fp, lines };
|
|
1022
|
+
}
|
|
1023
|
+
},
|
|
1024
|
+
// ─── 9. Code Diff Preview ─────────────────────────
|
|
1025
|
+
{
|
|
1026
|
+
name: "code_diff",
|
|
1027
|
+
description: "Preview what a file edit would look like as a unified diff, without applying it. Use to verify changes before committing.",
|
|
1028
|
+
input_schema: {
|
|
1029
|
+
type: "object",
|
|
1030
|
+
properties: {
|
|
1031
|
+
path: { type: "string" },
|
|
1032
|
+
old_text: { type: "string" },
|
|
1033
|
+
new_text: { type: "string" }
|
|
1034
|
+
},
|
|
1035
|
+
required: ["path", "old_text", "new_text"]
|
|
1036
|
+
},
|
|
1037
|
+
execute: async (input) => {
|
|
1038
|
+
var fp = resolvePath(input.path, sandbox);
|
|
1039
|
+
var content = await readFile3(fp, "utf-8");
|
|
1040
|
+
if (!content.includes(input.old_text)) {
|
|
1041
|
+
return { error: "old_text not found in file" };
|
|
1042
|
+
}
|
|
1043
|
+
var newContent = content.replace(input.old_text, input.new_text);
|
|
1044
|
+
var oldLines = content.split("\n");
|
|
1045
|
+
var newLines = newContent.split("\n");
|
|
1046
|
+
var diff = [`--- ${input.path}`, `+++ ${input.path}`];
|
|
1047
|
+
var start = 0;
|
|
1048
|
+
while (start < oldLines.length && start < newLines.length && oldLines[start] === newLines[start]) start++;
|
|
1049
|
+
var endOld = oldLines.length - 1;
|
|
1050
|
+
var endNew = newLines.length - 1;
|
|
1051
|
+
while (endOld > start && endNew > start && oldLines[endOld] === newLines[endNew]) {
|
|
1052
|
+
endOld--;
|
|
1053
|
+
endNew--;
|
|
1054
|
+
}
|
|
1055
|
+
diff.push(`@@ -${start + 1},${endOld - start + 1} +${start + 1},${endNew - start + 1} @@`);
|
|
1056
|
+
for (var i = start; i <= endOld; i++) diff.push(`-${oldLines[i]}`);
|
|
1057
|
+
for (var i = start; i <= endNew; i++) diff.push(`+${newLines[i]}`);
|
|
1058
|
+
return { diff: diff.join("\n") };
|
|
1059
|
+
}
|
|
1060
|
+
},
|
|
1061
|
+
// ─── 10. Process Manager ──────────────────────────
|
|
1062
|
+
{
|
|
1063
|
+
name: "code_pm2",
|
|
1064
|
+
description: "Manage pm2 processes: list, restart, logs, stop, start.",
|
|
1065
|
+
input_schema: {
|
|
1066
|
+
type: "object",
|
|
1067
|
+
properties: {
|
|
1068
|
+
action: { type: "string", description: "list|restart|logs|stop|start|status" },
|
|
1069
|
+
name: { type: "string", description: "Process name (for restart/logs/stop)" },
|
|
1070
|
+
lines: { type: "number", description: "Number of log lines (default 30)" }
|
|
1071
|
+
},
|
|
1072
|
+
required: ["action"]
|
|
1073
|
+
},
|
|
1074
|
+
execute: async (input) => {
|
|
1075
|
+
var cmdMap = {
|
|
1076
|
+
list: "pm2 jlist",
|
|
1077
|
+
restart: `pm2 restart ${input.name || "all"}`,
|
|
1078
|
+
logs: `pm2 logs ${input.name || ""} --nostream --lines ${input.lines || 30}`,
|
|
1079
|
+
stop: `pm2 stop ${input.name}`,
|
|
1080
|
+
start: `pm2 start ${input.name}`,
|
|
1081
|
+
status: "pm2 jlist"
|
|
1082
|
+
};
|
|
1083
|
+
var cmd = cmdMap[input.action];
|
|
1084
|
+
if (!cmd) return { error: `Unknown action. Use: ${Object.keys(cmdMap).join(", ")}` };
|
|
1085
|
+
try {
|
|
1086
|
+
var { stdout, stderr } = await execAsync2(cmd, { timeout: 15e3 });
|
|
1087
|
+
if (input.action === "list" || input.action === "status") {
|
|
1088
|
+
try {
|
|
1089
|
+
var procs = JSON.parse(stdout);
|
|
1090
|
+
return {
|
|
1091
|
+
processes: procs.map((p) => ({
|
|
1092
|
+
name: p.name,
|
|
1093
|
+
id: p.pm_id,
|
|
1094
|
+
status: p.pm2_env?.status,
|
|
1095
|
+
cpu: p.monit?.cpu,
|
|
1096
|
+
memory: Math.round((p.monit?.memory || 0) / 1024 / 1024) + "MB",
|
|
1097
|
+
restarts: p.pm2_env?.restart_time,
|
|
1098
|
+
uptime: p.pm2_env?.pm_uptime
|
|
1099
|
+
}))
|
|
1100
|
+
};
|
|
1101
|
+
} catch {
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
return { output: (stdout + "\n" + stderr).trim().slice(-1e4) };
|
|
1105
|
+
} catch (err) {
|
|
1106
|
+
return { error: (err.stderr || err.message).slice(0, 5e3) };
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
];
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
// src/agent-tools/tools/local/agent-control.ts
|
|
1114
|
+
function createAgentControlTools(options) {
|
|
1115
|
+
return [
|
|
1116
|
+
{
|
|
1117
|
+
name: "agent_stop",
|
|
1118
|
+
description: 'Stop the current session. Use when the user says "stop", "cancel", "abort", or wants you to immediately stop what you are doing. This terminates the current conversation loop \u2014 the agent process stays running for future messages.',
|
|
1119
|
+
input_schema: {
|
|
1120
|
+
type: "object",
|
|
1121
|
+
properties: {
|
|
1122
|
+
reason: { type: "string", description: "Why the session is being stopped" }
|
|
1123
|
+
},
|
|
1124
|
+
required: []
|
|
1125
|
+
},
|
|
1126
|
+
execute: async (_id, params) => {
|
|
1127
|
+
const reason = params?.reason || "User requested stop";
|
|
1128
|
+
const sessionId = options?.runtimeRef?.getCurrentSessionId?.();
|
|
1129
|
+
if (sessionId && options?.runtimeRef?.terminateSession) {
|
|
1130
|
+
console.log(`[agent-control] Terminating session ${sessionId}: ${reason}`);
|
|
1131
|
+
setTimeout(async () => {
|
|
1132
|
+
try {
|
|
1133
|
+
await options.runtimeRef.terminateSession(sessionId);
|
|
1134
|
+
} catch (e) {
|
|
1135
|
+
console.warn(`[agent-control] terminateSession error: ${e.message}`);
|
|
1136
|
+
}
|
|
1137
|
+
}, 500);
|
|
1138
|
+
return { content: [{ type: "text", text: `Session stopped. ${reason}` }] };
|
|
1139
|
+
}
|
|
1140
|
+
return { content: [{ type: "text", text: `Stopped. ${reason}` }] };
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
];
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
// src/agent-tools/tools/local/index.ts
|
|
1147
|
+
function createLocalSystemTools(config) {
|
|
1148
|
+
var sandbox = config?.sandboxRoot;
|
|
1149
|
+
return [
|
|
1150
|
+
createFileReadTool(sandbox),
|
|
1151
|
+
createFileWriteTool(sandbox),
|
|
1152
|
+
createFileEditTool(sandbox),
|
|
1153
|
+
createFileListTool(sandbox),
|
|
1154
|
+
createFileSearchTool(sandbox),
|
|
1155
|
+
createFileMoveTool(sandbox),
|
|
1156
|
+
createFileDeleteTool(sandbox),
|
|
1157
|
+
...createShellTools({ cwd: config?.shellCwd, timeout: config?.shellTimeout }),
|
|
1158
|
+
...createDependencyManagerTools(),
|
|
1159
|
+
createSystemInfoTool(),
|
|
1160
|
+
...createCodingTools({ cwd: config?.shellCwd, sandbox }),
|
|
1161
|
+
...createAgentControlTools(config?.toolOptions)
|
|
1162
|
+
];
|
|
1163
|
+
}
|
|
1164
|
+
export {
|
|
1165
|
+
createAgentControlTools,
|
|
1166
|
+
createCodingTools,
|
|
1167
|
+
createDependencyManagerTools,
|
|
1168
|
+
createFileDeleteTool,
|
|
1169
|
+
createFileEditTool,
|
|
1170
|
+
createFileListTool,
|
|
1171
|
+
createFileMoveTool,
|
|
1172
|
+
createFileReadTool,
|
|
1173
|
+
createFileSearchTool,
|
|
1174
|
+
createFileWriteTool,
|
|
1175
|
+
createLocalSystemTools,
|
|
1176
|
+
createShellExecTool,
|
|
1177
|
+
createShellTools,
|
|
1178
|
+
createSystemInfoTool
|
|
1179
|
+
};
|