zidane 1.0.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +723 -0
- package/dist/agent-DvZm8U14.d.ts +313 -0
- package/dist/chunk-26LIQARN.js +109 -0
- package/dist/chunk-27EP7HB3.js +1005 -0
- package/dist/chunk-34KXKPNN.js +45 -0
- package/dist/chunk-LMSOIIAT.js +274 -0
- package/dist/chunk-LS57GDAV.js +365 -0
- package/dist/chunk-PNKVD2UK.js +26 -0
- package/dist/harnesses.d.ts +7 -0
- package/dist/harnesses.js +13 -0
- package/dist/index.d.ts +56 -0
- package/dist/index.js +57 -0
- package/dist/mcp.d.ts +7 -0
- package/dist/mcp.js +11 -0
- package/dist/providers.d.ts +65 -0
- package/dist/providers.js +257 -0
- package/dist/session.d.ts +167 -0
- package/dist/session.js +27 -0
- package/dist/spawn-pP2grsVp.d.ts +63 -0
- package/dist/tools.d.ts +28 -0
- package/dist/tools.js +20 -0
- package/dist/types-4CFQ-6Qu.d.ts +94 -0
- package/package.json +69 -6
- package/index.js +0 -1
- package/zidane.jpeg +0 -0
|
@@ -0,0 +1,1005 @@
|
|
|
1
|
+
import {
|
|
2
|
+
connectMcpServers,
|
|
3
|
+
init_mcp
|
|
4
|
+
} from "./chunk-26LIQARN.js";
|
|
5
|
+
import {
|
|
6
|
+
__esm,
|
|
7
|
+
__export
|
|
8
|
+
} from "./chunk-PNKVD2UK.js";
|
|
9
|
+
|
|
10
|
+
// src/contexts/docker.ts
|
|
11
|
+
function createDockerContext(config) {
|
|
12
|
+
let counter = 0;
|
|
13
|
+
const containers = /* @__PURE__ */ new Map();
|
|
14
|
+
const defaultImage = config?.image ?? "oven/bun:latest";
|
|
15
|
+
const defaultCwd = config?.cwd ?? "/workspace";
|
|
16
|
+
const defaultEnv = config?.env;
|
|
17
|
+
const defaultLimits = config?.limits;
|
|
18
|
+
async function getDockerode() {
|
|
19
|
+
try {
|
|
20
|
+
const Dockerode = (await import("dockerode")).default;
|
|
21
|
+
return new Dockerode();
|
|
22
|
+
} catch {
|
|
23
|
+
throw new Error("dockerode is required for Docker execution context. Install it with: bun add dockerode");
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const ctx = {
|
|
27
|
+
type: "docker",
|
|
28
|
+
capabilities: {
|
|
29
|
+
shell: true,
|
|
30
|
+
filesystem: true,
|
|
31
|
+
network: true,
|
|
32
|
+
gpu: false
|
|
33
|
+
},
|
|
34
|
+
async spawn(overrides) {
|
|
35
|
+
const docker = await getDockerode();
|
|
36
|
+
const id = `docker-${++counter}`;
|
|
37
|
+
const image = overrides?.image ?? defaultImage;
|
|
38
|
+
const cwd5 = overrides?.cwd ?? defaultCwd;
|
|
39
|
+
try {
|
|
40
|
+
await docker.getImage(image).inspect();
|
|
41
|
+
} catch {
|
|
42
|
+
await new Promise((resolve5, reject) => {
|
|
43
|
+
docker.pull(image, (err, stream) => {
|
|
44
|
+
if (err)
|
|
45
|
+
return reject(err);
|
|
46
|
+
docker.modem.followProgress(stream, (err2) => {
|
|
47
|
+
err2 ? reject(err2) : resolve5();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
const limits = { ...defaultLimits, ...overrides?.limits };
|
|
53
|
+
const hostConfig = {};
|
|
54
|
+
if (limits?.memory) {
|
|
55
|
+
hostConfig.Memory = limits.memory * 1024 * 1024;
|
|
56
|
+
}
|
|
57
|
+
if (limits?.cpu) {
|
|
58
|
+
hostConfig.NanoCpus = Number.parseFloat(limits.cpu) * 1e9;
|
|
59
|
+
}
|
|
60
|
+
const env = { ...defaultEnv, ...overrides?.env };
|
|
61
|
+
const container = await docker.createContainer({
|
|
62
|
+
Image: image,
|
|
63
|
+
Cmd: ["sleep", "infinity"],
|
|
64
|
+
WorkingDir: cwd5,
|
|
65
|
+
Env: Object.entries(env).map(([k, v]) => `${k}=${v}`),
|
|
66
|
+
HostConfig: hostConfig
|
|
67
|
+
});
|
|
68
|
+
await container.start();
|
|
69
|
+
const handle = { id, type: "docker", cwd: cwd5 };
|
|
70
|
+
containers.set(id, { handle, container, docker });
|
|
71
|
+
return handle;
|
|
72
|
+
},
|
|
73
|
+
async exec(handle, command, options) {
|
|
74
|
+
const ref = containers.get(handle.id);
|
|
75
|
+
if (!ref)
|
|
76
|
+
throw new Error(`Container ${handle.id} not found`);
|
|
77
|
+
const execCwd = options?.cwd ?? handle.cwd;
|
|
78
|
+
const env = options?.env ? Object.entries(options.env).map(([k, v]) => `${k}=${v}`) : [];
|
|
79
|
+
const exec = await ref.container.exec({
|
|
80
|
+
Cmd: ["sh", "-c", command],
|
|
81
|
+
WorkingDir: execCwd,
|
|
82
|
+
Env: env,
|
|
83
|
+
AttachStdout: true,
|
|
84
|
+
AttachStderr: true
|
|
85
|
+
});
|
|
86
|
+
const stream = await exec.start({ Detach: false });
|
|
87
|
+
return new Promise((resolve5) => {
|
|
88
|
+
let stdout = "";
|
|
89
|
+
const stderr = "";
|
|
90
|
+
const timeout = options?.timeout ?? defaultLimits?.timeout ?? 30;
|
|
91
|
+
const timer = setTimeout(() => {
|
|
92
|
+
resolve5({ stdout, stderr: `${stderr}
|
|
93
|
+
[timeout]`, exitCode: 124 });
|
|
94
|
+
}, timeout * 1e3);
|
|
95
|
+
stream.on("data", (chunk) => {
|
|
96
|
+
stdout += chunk.toString("utf-8");
|
|
97
|
+
});
|
|
98
|
+
stream.on("end", async () => {
|
|
99
|
+
clearTimeout(timer);
|
|
100
|
+
const inspect = await exec.inspect();
|
|
101
|
+
resolve5({ stdout, stderr, exitCode: inspect.ExitCode ?? 0 });
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
},
|
|
105
|
+
async readFile(handle, path) {
|
|
106
|
+
const result = await ctx.exec(handle, `cat ${JSON.stringify(path)}`);
|
|
107
|
+
if (result.exitCode !== 0)
|
|
108
|
+
throw new Error(`Failed to read file: ${result.stderr}`);
|
|
109
|
+
return result.stdout;
|
|
110
|
+
},
|
|
111
|
+
async writeFile(handle, path, content) {
|
|
112
|
+
const escaped = content.replace(SINGLE_QUOTE_RE, String.raw`'\''`);
|
|
113
|
+
const result = await ctx.exec(handle, `mkdir -p "$(dirname ${JSON.stringify(path)})" && printf '%s' '${escaped}' > ${JSON.stringify(path)}`);
|
|
114
|
+
if (result.exitCode !== 0)
|
|
115
|
+
throw new Error(`Failed to write file: ${result.stderr}`);
|
|
116
|
+
},
|
|
117
|
+
async listFiles(handle, path) {
|
|
118
|
+
const result = await ctx.exec(handle, `ls -1 ${JSON.stringify(path)}`);
|
|
119
|
+
if (result.exitCode !== 0)
|
|
120
|
+
return [];
|
|
121
|
+
return result.stdout.trim().split("\n").filter(Boolean);
|
|
122
|
+
},
|
|
123
|
+
async destroy(handle) {
|
|
124
|
+
const ref = containers.get(handle.id);
|
|
125
|
+
if (!ref)
|
|
126
|
+
return;
|
|
127
|
+
try {
|
|
128
|
+
await ref.container.stop({ t: 5 });
|
|
129
|
+
await ref.container.remove({ force: true });
|
|
130
|
+
} catch {
|
|
131
|
+
}
|
|
132
|
+
containers.delete(handle.id);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
return ctx;
|
|
136
|
+
}
|
|
137
|
+
var SINGLE_QUOTE_RE;
|
|
138
|
+
var init_docker = __esm({
|
|
139
|
+
"src/contexts/docker.ts"() {
|
|
140
|
+
"use strict";
|
|
141
|
+
SINGLE_QUOTE_RE = /'/g;
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// src/contexts/process.ts
|
|
146
|
+
import { exec as execCb } from "child_process";
|
|
147
|
+
import { mkdir, readdir, readFile as readFile2, writeFile } from "fs/promises";
|
|
148
|
+
import { dirname, resolve as resolve3 } from "path";
|
|
149
|
+
import { promisify } from "util";
|
|
150
|
+
function createProcessContext(config) {
|
|
151
|
+
let counter = 0;
|
|
152
|
+
const handles = /* @__PURE__ */ new Map();
|
|
153
|
+
const defaultCwd = config?.cwd ?? process.cwd();
|
|
154
|
+
const defaultEnv = config?.env;
|
|
155
|
+
return {
|
|
156
|
+
type: "process",
|
|
157
|
+
capabilities: {
|
|
158
|
+
shell: true,
|
|
159
|
+
filesystem: true,
|
|
160
|
+
network: true,
|
|
161
|
+
gpu: false
|
|
162
|
+
},
|
|
163
|
+
async spawn(overrides) {
|
|
164
|
+
const id = `process-${++counter}`;
|
|
165
|
+
const cwd5 = overrides?.cwd ?? defaultCwd;
|
|
166
|
+
await mkdir(cwd5, { recursive: true });
|
|
167
|
+
const handle = { id, type: "process", cwd: cwd5 };
|
|
168
|
+
handles.set(id, handle);
|
|
169
|
+
return handle;
|
|
170
|
+
},
|
|
171
|
+
async exec(handle, command, options) {
|
|
172
|
+
const cwd5 = options?.cwd ? resolve3(handle.cwd, options.cwd) : handle.cwd;
|
|
173
|
+
try {
|
|
174
|
+
const { stdout, stderr } = await execAsync(command, {
|
|
175
|
+
cwd: cwd5,
|
|
176
|
+
env: { ...process.env, ...defaultEnv, ...options?.env },
|
|
177
|
+
timeout: (options?.timeout ?? config?.limits?.timeout ?? 30) * 1e3,
|
|
178
|
+
maxBuffer: 10 * 1024 * 1024
|
|
179
|
+
});
|
|
180
|
+
return { stdout, stderr, exitCode: 0 };
|
|
181
|
+
} catch (err) {
|
|
182
|
+
return {
|
|
183
|
+
stdout: err.stdout ?? "",
|
|
184
|
+
stderr: err.stderr ?? err.message,
|
|
185
|
+
exitCode: err.code ?? 1
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
async readFile(handle, path) {
|
|
190
|
+
return readFile2(resolve3(handle.cwd, path), "utf-8");
|
|
191
|
+
},
|
|
192
|
+
async writeFile(handle, path, content) {
|
|
193
|
+
const fullPath = resolve3(handle.cwd, path);
|
|
194
|
+
await mkdir(dirname(fullPath), { recursive: true });
|
|
195
|
+
await writeFile(fullPath, content, "utf-8");
|
|
196
|
+
},
|
|
197
|
+
async listFiles(handle, path) {
|
|
198
|
+
return readdir(resolve3(handle.cwd, path));
|
|
199
|
+
},
|
|
200
|
+
async destroy(handle) {
|
|
201
|
+
handles.delete(handle.id);
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
var execAsync;
|
|
206
|
+
var init_process = __esm({
|
|
207
|
+
"src/contexts/process.ts"() {
|
|
208
|
+
"use strict";
|
|
209
|
+
execAsync = promisify(execCb);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// src/contexts/sandbox.ts
|
|
214
|
+
function createSandboxContext(provider) {
|
|
215
|
+
const sandboxes = /* @__PURE__ */ new Map();
|
|
216
|
+
function getSandboxId(handle) {
|
|
217
|
+
const id = sandboxes.get(handle.id);
|
|
218
|
+
if (!id)
|
|
219
|
+
throw new Error(`Sandbox ${handle.id} not found`);
|
|
220
|
+
return id;
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
type: "sandbox",
|
|
224
|
+
capabilities: {
|
|
225
|
+
shell: true,
|
|
226
|
+
filesystem: true,
|
|
227
|
+
network: true,
|
|
228
|
+
gpu: false
|
|
229
|
+
},
|
|
230
|
+
async spawn(config) {
|
|
231
|
+
const result = await provider.spawn(config ?? {});
|
|
232
|
+
const handle = { id: result.id, type: "sandbox", cwd: result.cwd };
|
|
233
|
+
sandboxes.set(handle.id, result.id);
|
|
234
|
+
return handle;
|
|
235
|
+
},
|
|
236
|
+
async exec(handle, command, options) {
|
|
237
|
+
return provider.exec(getSandboxId(handle), command, options);
|
|
238
|
+
},
|
|
239
|
+
async readFile(handle, path) {
|
|
240
|
+
return provider.readFile(getSandboxId(handle), path);
|
|
241
|
+
},
|
|
242
|
+
async writeFile(handle, path, content) {
|
|
243
|
+
return provider.writeFile(getSandboxId(handle), path, content);
|
|
244
|
+
},
|
|
245
|
+
async listFiles(handle, path) {
|
|
246
|
+
return provider.listFiles(getSandboxId(handle), path);
|
|
247
|
+
},
|
|
248
|
+
async destroy(handle) {
|
|
249
|
+
const id = sandboxes.get(handle.id);
|
|
250
|
+
if (!id)
|
|
251
|
+
return;
|
|
252
|
+
await provider.destroy(id);
|
|
253
|
+
sandboxes.delete(handle.id);
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
var init_sandbox = __esm({
|
|
258
|
+
"src/contexts/sandbox.ts"() {
|
|
259
|
+
"use strict";
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
// src/contexts/index.ts
|
|
264
|
+
var init_contexts = __esm({
|
|
265
|
+
"src/contexts/index.ts"() {
|
|
266
|
+
"use strict";
|
|
267
|
+
init_docker();
|
|
268
|
+
init_process();
|
|
269
|
+
init_sandbox();
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// src/tools/validation.ts
|
|
274
|
+
function validateToolArgs(input, schema) {
|
|
275
|
+
const required = schema.required ?? [];
|
|
276
|
+
for (const field of required) {
|
|
277
|
+
if (!(field in input) || input[field] === void 0 || input[field] === null) {
|
|
278
|
+
return { valid: false, error: `Missing required field: ${field}` };
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return { valid: true };
|
|
282
|
+
}
|
|
283
|
+
var init_validation = __esm({
|
|
284
|
+
"src/tools/validation.ts"() {
|
|
285
|
+
"use strict";
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// src/loop.ts
|
|
290
|
+
async function runLoop(ctx) {
|
|
291
|
+
let totalIn = 0;
|
|
292
|
+
let totalOut = 0;
|
|
293
|
+
const turnUsages = [];
|
|
294
|
+
const startTime = Date.now();
|
|
295
|
+
const maxTurns = 50;
|
|
296
|
+
for (let turn = 0; turn < maxTurns; turn++) {
|
|
297
|
+
if (ctx.signal.aborted) {
|
|
298
|
+
await ctx.hooks.callHook("agent:abort", {});
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
const result = await executeTurn(ctx, turn);
|
|
302
|
+
totalIn += result.usage.input;
|
|
303
|
+
totalOut += result.usage.output;
|
|
304
|
+
turnUsages.push(result.usage);
|
|
305
|
+
if (ctx.signal.aborted) {
|
|
306
|
+
await ctx.hooks.callHook("agent:abort", {});
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
if (ctx.steeringQueue.length > 0) {
|
|
310
|
+
const steerMsg = ctx.steeringQueue.shift();
|
|
311
|
+
await ctx.hooks.callHook("steer:inject", { message: steerMsg });
|
|
312
|
+
ctx.messages.push(ctx.provider.userMessage(steerMsg));
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
if (result.ended) {
|
|
316
|
+
if (ctx.followUpQueue.length > 0) {
|
|
317
|
+
const followUp = ctx.followUpQueue.shift();
|
|
318
|
+
await ctx.hooks.callHook("steer:inject", { message: followUp });
|
|
319
|
+
ctx.messages.push(ctx.provider.userMessage(followUp));
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
return { totalIn, totalOut, turns: turn + 1, elapsed: Date.now() - startTime, turnUsage: turnUsages };
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
const stats = { totalIn, totalOut, turns: maxTurns, elapsed: Date.now() - startTime, turnUsage: turnUsages };
|
|
326
|
+
await ctx.hooks.callHook("agent:done", stats);
|
|
327
|
+
return stats;
|
|
328
|
+
}
|
|
329
|
+
async function executeTurn(ctx, turn) {
|
|
330
|
+
const streamOptions = {
|
|
331
|
+
model: ctx.model,
|
|
332
|
+
system: ctx.system,
|
|
333
|
+
tools: ctx.formattedTools,
|
|
334
|
+
messages: ctx.messages,
|
|
335
|
+
maxTokens: 16384,
|
|
336
|
+
thinking: ctx.thinking,
|
|
337
|
+
signal: ctx.signal
|
|
338
|
+
};
|
|
339
|
+
await ctx.hooks.callHook("context:transform", { messages: ctx.messages });
|
|
340
|
+
await ctx.hooks.callHook("turn:before", { turn, options: streamOptions });
|
|
341
|
+
let currentText = "";
|
|
342
|
+
const result = await ctx.provider.stream(
|
|
343
|
+
streamOptions,
|
|
344
|
+
{
|
|
345
|
+
onText(delta) {
|
|
346
|
+
currentText += delta;
|
|
347
|
+
ctx.hooks.callHook("stream:text", { delta, text: currentText });
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
);
|
|
351
|
+
if (currentText) {
|
|
352
|
+
await ctx.hooks.callHook("stream:end", { text: currentText });
|
|
353
|
+
}
|
|
354
|
+
await ctx.hooks.callHook("turn:after", { turn, usage: result.usage });
|
|
355
|
+
if (result.done) {
|
|
356
|
+
return { ended: true, usage: result.usage };
|
|
357
|
+
}
|
|
358
|
+
ctx.messages.push(result.assistantMessage);
|
|
359
|
+
const toolResults = ctx.toolExecution === "parallel" ? await executeToolsParallel(ctx, result.toolCalls) : await executeToolsSequential(ctx, result.toolCalls);
|
|
360
|
+
ctx.messages.push(ctx.provider.toolResultsMessage(toolResults));
|
|
361
|
+
return { ended: false, usage: result.usage };
|
|
362
|
+
}
|
|
363
|
+
async function executeSingleTool(ctx, call) {
|
|
364
|
+
const toolDef = ctx.tools[call.name];
|
|
365
|
+
const gateCtx = { name: call.name, input: call.input, block: false, reason: "Tool execution was blocked" };
|
|
366
|
+
await ctx.hooks.callHook("tool:gate", gateCtx);
|
|
367
|
+
if (gateCtx.block) {
|
|
368
|
+
return { result: { id: call.id, content: `Blocked: ${gateCtx.reason}` }, steered: false };
|
|
369
|
+
}
|
|
370
|
+
if (!toolDef) {
|
|
371
|
+
const err = new Error(`Unknown tool: ${call.name}`);
|
|
372
|
+
await ctx.hooks.callHook("tool:error", { name: call.name, input: call.input, error: err });
|
|
373
|
+
return { result: { id: call.id, content: `Tool error: ${err.message}` }, steered: false };
|
|
374
|
+
}
|
|
375
|
+
const validation = validateToolArgs(call.input, toolDef.spec.input_schema);
|
|
376
|
+
if (!validation.valid) {
|
|
377
|
+
return { result: { id: call.id, content: `Validation error: ${validation.error}` }, steered: false };
|
|
378
|
+
}
|
|
379
|
+
await ctx.hooks.callHook("tool:before", { name: call.name, input: call.input });
|
|
380
|
+
let output;
|
|
381
|
+
let isError = false;
|
|
382
|
+
try {
|
|
383
|
+
const toolCtx = {
|
|
384
|
+
provider: ctx.provider,
|
|
385
|
+
signal: ctx.signal,
|
|
386
|
+
execution: ctx.execution,
|
|
387
|
+
handle: ctx.handle,
|
|
388
|
+
hooks: ctx.hooks,
|
|
389
|
+
harness: ctx.harness
|
|
390
|
+
};
|
|
391
|
+
output = await toolDef.execute(call.input, toolCtx);
|
|
392
|
+
} catch (err) {
|
|
393
|
+
await ctx.hooks.callHook("tool:error", { name: call.name, input: call.input, error: err });
|
|
394
|
+
output = `Tool error: ${err.message}`;
|
|
395
|
+
isError = true;
|
|
396
|
+
}
|
|
397
|
+
const transformCtx = { name: call.name, input: call.input, result: output, isError };
|
|
398
|
+
await ctx.hooks.callHook("tool:transform", transformCtx);
|
|
399
|
+
output = transformCtx.result;
|
|
400
|
+
isError = transformCtx.isError;
|
|
401
|
+
await ctx.hooks.callHook("tool:after", { name: call.name, input: call.input, result: output });
|
|
402
|
+
return { result: { id: call.id, content: output }, steered: false };
|
|
403
|
+
}
|
|
404
|
+
async function executeToolsSequential(ctx, toolCalls) {
|
|
405
|
+
const results = [];
|
|
406
|
+
for (const call of toolCalls) {
|
|
407
|
+
if (ctx.signal.aborted)
|
|
408
|
+
break;
|
|
409
|
+
if (ctx.steeringQueue.length > 0) {
|
|
410
|
+
const steerMsg = ctx.steeringQueue.shift();
|
|
411
|
+
await ctx.hooks.callHook("steer:inject", { message: steerMsg });
|
|
412
|
+
for (const skipped of toolCalls.slice(toolCalls.indexOf(call))) {
|
|
413
|
+
results.push({ id: skipped.id, content: "Skipped: steering message received" });
|
|
414
|
+
}
|
|
415
|
+
ctx.messages.push(ctx.provider.toolResultsMessage(results));
|
|
416
|
+
ctx.messages.push(ctx.provider.userMessage(steerMsg));
|
|
417
|
+
return [];
|
|
418
|
+
}
|
|
419
|
+
const { result } = await executeSingleTool(ctx, call);
|
|
420
|
+
results.push(result);
|
|
421
|
+
}
|
|
422
|
+
return results;
|
|
423
|
+
}
|
|
424
|
+
async function executeToolsParallel(ctx, toolCalls) {
|
|
425
|
+
const executions = toolCalls.map((call) => executeSingleTool(ctx, call));
|
|
426
|
+
const settled = await Promise.all(executions);
|
|
427
|
+
return settled.map((s) => s.result);
|
|
428
|
+
}
|
|
429
|
+
var init_loop = __esm({
|
|
430
|
+
"src/loop.ts"() {
|
|
431
|
+
"use strict";
|
|
432
|
+
init_validation();
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
// src/agent.ts
|
|
437
|
+
import { createHooks } from "hookable";
|
|
438
|
+
function createAgent({ harness, provider, toolExecution = "sequential", execution, mcpServers, session, _mcpConnector }) {
|
|
439
|
+
const hooks = createHooks();
|
|
440
|
+
const executionContext = execution ?? createProcessContext();
|
|
441
|
+
let abortController;
|
|
442
|
+
let running = false;
|
|
443
|
+
let idleResolve;
|
|
444
|
+
let idlePromise;
|
|
445
|
+
let executionHandle = null;
|
|
446
|
+
let mcpConnection = null;
|
|
447
|
+
const allMcpServers = [...harness.mcpServers ?? [], ...mcpServers ?? []];
|
|
448
|
+
const steeringQueue = [];
|
|
449
|
+
const followUpQueue = [];
|
|
450
|
+
let conversationMessages = session?.messages ?? [];
|
|
451
|
+
let runCounter = 0;
|
|
452
|
+
async function run(options) {
|
|
453
|
+
if (running) {
|
|
454
|
+
throw new Error("Agent is already running. Use steer() or followUp() to queue messages, or waitForIdle().");
|
|
455
|
+
}
|
|
456
|
+
running = true;
|
|
457
|
+
abortController = new AbortController();
|
|
458
|
+
const runId = `run_${++runCounter}`;
|
|
459
|
+
session?.startRun(runId, options.prompt);
|
|
460
|
+
if (session)
|
|
461
|
+
await hooks.callHook("session:start", { sessionId: session.id, runId, prompt: options.prompt });
|
|
462
|
+
if (options.signal) {
|
|
463
|
+
if (options.signal.aborted) {
|
|
464
|
+
abortController.abort();
|
|
465
|
+
} else {
|
|
466
|
+
const onExternalAbort = () => abortController?.abort();
|
|
467
|
+
options.signal.addEventListener("abort", onExternalAbort, { once: true });
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
idlePromise = new Promise((resolve5) => {
|
|
471
|
+
idleResolve = resolve5;
|
|
472
|
+
});
|
|
473
|
+
const childrenStats = [];
|
|
474
|
+
const unregisterSpawnHook = hooks.hook("spawn:complete", (ctx) => {
|
|
475
|
+
childrenStats.push(ctx);
|
|
476
|
+
});
|
|
477
|
+
if (!executionHandle) {
|
|
478
|
+
executionHandle = await executionContext.spawn();
|
|
479
|
+
}
|
|
480
|
+
if (allMcpServers.length > 0 && !mcpConnection) {
|
|
481
|
+
if (_mcpConnector) {
|
|
482
|
+
mcpConnection = await _mcpConnector(allMcpServers);
|
|
483
|
+
} else {
|
|
484
|
+
mcpConnection = await connectMcpServers(allMcpServers, void 0, hooks);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
const thinking = options.thinking ?? "off";
|
|
488
|
+
const model = options.model ?? provider.meta.defaultModel;
|
|
489
|
+
const system = options.system || harness.system || "You are a helpful assistant.";
|
|
490
|
+
const tools = mcpConnection ? { ...harness.tools, ...mcpConnection.tools } : harness.tools;
|
|
491
|
+
const toolSpecs = Object.values(tools).map(
|
|
492
|
+
(t) => ({
|
|
493
|
+
name: t.spec.name,
|
|
494
|
+
description: t.spec.description || "",
|
|
495
|
+
input_schema: t.spec.input_schema
|
|
496
|
+
})
|
|
497
|
+
);
|
|
498
|
+
const formattedTools = provider.formatTools(toolSpecs);
|
|
499
|
+
const messages = [];
|
|
500
|
+
if (options.system) {
|
|
501
|
+
await hooks.callHook("system:before", { system: options.system });
|
|
502
|
+
messages.push(provider.userMessage(options.system));
|
|
503
|
+
messages.push(provider.assistantMessage("Understood. I will proceed with these instructions above the rest of my system prompt."));
|
|
504
|
+
}
|
|
505
|
+
messages.push(provider.userMessage(options.prompt, options.images));
|
|
506
|
+
conversationMessages = messages;
|
|
507
|
+
session?.setMessages(messages);
|
|
508
|
+
const unregisterSessionSync = session ? hooks.hook("turn:after", () => {
|
|
509
|
+
session.setMessages(messages);
|
|
510
|
+
hooks.callHook("session:messages", { sessionId: session.id, count: messages.length });
|
|
511
|
+
}) : void 0;
|
|
512
|
+
try {
|
|
513
|
+
const stats = await runLoop({
|
|
514
|
+
provider,
|
|
515
|
+
hooks,
|
|
516
|
+
harness,
|
|
517
|
+
tools,
|
|
518
|
+
toolSpecs,
|
|
519
|
+
formattedTools,
|
|
520
|
+
model,
|
|
521
|
+
system,
|
|
522
|
+
thinking,
|
|
523
|
+
toolExecution,
|
|
524
|
+
signal: abortController.signal,
|
|
525
|
+
execution: executionContext,
|
|
526
|
+
handle: executionHandle,
|
|
527
|
+
steeringQueue,
|
|
528
|
+
followUpQueue,
|
|
529
|
+
messages
|
|
530
|
+
});
|
|
531
|
+
const finalStats = {
|
|
532
|
+
...stats,
|
|
533
|
+
children: childrenStats.length > 0 ? childrenStats : void 0
|
|
534
|
+
};
|
|
535
|
+
if (abortController.signal.aborted) {
|
|
536
|
+
session?.abortRun(runId);
|
|
537
|
+
if (session)
|
|
538
|
+
await hooks.callHook("session:end", { sessionId: session.id, runId, status: "aborted" });
|
|
539
|
+
await hooks.callHook("agent:done", finalStats);
|
|
540
|
+
return finalStats;
|
|
541
|
+
}
|
|
542
|
+
const totalCost = finalStats.turnUsage?.reduce((sum, t) => sum + (t.cost ?? 0), 0) || void 0;
|
|
543
|
+
if (totalCost)
|
|
544
|
+
finalStats.cost = totalCost;
|
|
545
|
+
session?.setMessages(messages);
|
|
546
|
+
session?.completeRun(runId, {
|
|
547
|
+
turns: finalStats.turns,
|
|
548
|
+
tokensIn: finalStats.totalIn,
|
|
549
|
+
tokensOut: finalStats.totalOut,
|
|
550
|
+
turnUsage: finalStats.turnUsage,
|
|
551
|
+
cost: totalCost
|
|
552
|
+
});
|
|
553
|
+
if (session)
|
|
554
|
+
await hooks.callHook("session:end", { sessionId: session.id, runId, status: "completed" });
|
|
555
|
+
await hooks.callHook("agent:done", finalStats);
|
|
556
|
+
return finalStats;
|
|
557
|
+
} catch (err) {
|
|
558
|
+
if (abortController.signal.aborted) {
|
|
559
|
+
session?.abortRun(runId);
|
|
560
|
+
if (session)
|
|
561
|
+
await hooks.callHook("session:end", { sessionId: session.id, runId, status: "aborted" });
|
|
562
|
+
const stats = { totalIn: 0, totalOut: 0, turns: 0, elapsed: 0 };
|
|
563
|
+
await hooks.callHook("agent:done", stats);
|
|
564
|
+
return stats;
|
|
565
|
+
}
|
|
566
|
+
session?.errorRun(runId, err.message);
|
|
567
|
+
if (session)
|
|
568
|
+
await hooks.callHook("session:end", { sessionId: session.id, runId, status: "error" });
|
|
569
|
+
throw err;
|
|
570
|
+
} finally {
|
|
571
|
+
unregisterSpawnHook();
|
|
572
|
+
unregisterSessionSync?.();
|
|
573
|
+
running = false;
|
|
574
|
+
abortController = void 0;
|
|
575
|
+
steeringQueue.length = 0;
|
|
576
|
+
followUpQueue.length = 0;
|
|
577
|
+
idleResolve?.();
|
|
578
|
+
idlePromise = void 0;
|
|
579
|
+
idleResolve = void 0;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
function abort() {
|
|
583
|
+
abortController?.abort();
|
|
584
|
+
}
|
|
585
|
+
function steer(message) {
|
|
586
|
+
steeringQueue.push(message);
|
|
587
|
+
}
|
|
588
|
+
function followUpFn(message) {
|
|
589
|
+
followUpQueue.push(message);
|
|
590
|
+
}
|
|
591
|
+
function waitForIdle() {
|
|
592
|
+
return idlePromise ?? Promise.resolve();
|
|
593
|
+
}
|
|
594
|
+
function reset() {
|
|
595
|
+
conversationMessages = [];
|
|
596
|
+
steeringQueue.length = 0;
|
|
597
|
+
followUpQueue.length = 0;
|
|
598
|
+
}
|
|
599
|
+
if (session) {
|
|
600
|
+
const originalSave = session.save.bind(session);
|
|
601
|
+
const originalSetMeta = session.setMeta.bind(session);
|
|
602
|
+
session.save = async () => {
|
|
603
|
+
await originalSave();
|
|
604
|
+
await hooks.callHook("session:save", { sessionId: session.id });
|
|
605
|
+
};
|
|
606
|
+
session.setMeta = (key, value) => {
|
|
607
|
+
originalSetMeta(key, value);
|
|
608
|
+
hooks.callHook("session:meta", { sessionId: session.id, key, value });
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
async function destroy() {
|
|
612
|
+
if (mcpConnection) {
|
|
613
|
+
await mcpConnection.close();
|
|
614
|
+
mcpConnection = null;
|
|
615
|
+
}
|
|
616
|
+
if (executionHandle) {
|
|
617
|
+
await executionContext.destroy(executionHandle);
|
|
618
|
+
executionHandle = null;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
return {
|
|
622
|
+
hooks,
|
|
623
|
+
run,
|
|
624
|
+
abort,
|
|
625
|
+
steer,
|
|
626
|
+
followUp: followUpFn,
|
|
627
|
+
waitForIdle,
|
|
628
|
+
reset,
|
|
629
|
+
destroy,
|
|
630
|
+
get isRunning() {
|
|
631
|
+
return running;
|
|
632
|
+
},
|
|
633
|
+
get messages() {
|
|
634
|
+
return conversationMessages;
|
|
635
|
+
},
|
|
636
|
+
get execution() {
|
|
637
|
+
return executionContext;
|
|
638
|
+
},
|
|
639
|
+
get handle() {
|
|
640
|
+
return executionHandle;
|
|
641
|
+
},
|
|
642
|
+
get session() {
|
|
643
|
+
return session ?? null;
|
|
644
|
+
},
|
|
645
|
+
meta: provider.meta
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
var init_agent = __esm({
|
|
649
|
+
"src/agent.ts"() {
|
|
650
|
+
"use strict";
|
|
651
|
+
init_contexts();
|
|
652
|
+
init_loop();
|
|
653
|
+
init_mcp();
|
|
654
|
+
}
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
// src/tools/spawn.ts
|
|
658
|
+
var spawn_exports = {};
|
|
659
|
+
__export(spawn_exports, {
|
|
660
|
+
createSpawnTool: () => createSpawnTool,
|
|
661
|
+
spawn: () => spawn
|
|
662
|
+
});
|
|
663
|
+
function createSpawnTool(options = {}) {
|
|
664
|
+
const localChildren = /* @__PURE__ */ new Map();
|
|
665
|
+
let localCounter = 0;
|
|
666
|
+
let localActiveCount = 0;
|
|
667
|
+
const maxConcurrent = options.maxConcurrent ?? 3;
|
|
668
|
+
const localStats = {
|
|
669
|
+
totalIn: 0,
|
|
670
|
+
totalOut: 0,
|
|
671
|
+
turns: 0,
|
|
672
|
+
elapsed: 0
|
|
673
|
+
};
|
|
674
|
+
return {
|
|
675
|
+
get children() {
|
|
676
|
+
return localChildren;
|
|
677
|
+
},
|
|
678
|
+
get totalChildStats() {
|
|
679
|
+
return { ...localStats };
|
|
680
|
+
},
|
|
681
|
+
spec: {
|
|
682
|
+
name: "spawn",
|
|
683
|
+
description: "Spawn a sub-agent to work on a specific task. The sub-agent runs independently with its own tool access and returns its final response. Use this to delegate work, parallelize tasks, or isolate concerns.",
|
|
684
|
+
input_schema: {
|
|
685
|
+
type: "object",
|
|
686
|
+
properties: {
|
|
687
|
+
task: {
|
|
688
|
+
type: "string",
|
|
689
|
+
description: "The task prompt for the sub-agent. Be specific about what you want it to accomplish."
|
|
690
|
+
},
|
|
691
|
+
system: {
|
|
692
|
+
type: "string",
|
|
693
|
+
description: "Optional system prompt override for this specific sub-agent."
|
|
694
|
+
}
|
|
695
|
+
},
|
|
696
|
+
required: ["task"]
|
|
697
|
+
}
|
|
698
|
+
},
|
|
699
|
+
async execute(input, ctx) {
|
|
700
|
+
const task = input.task;
|
|
701
|
+
const systemOverride = input.system;
|
|
702
|
+
if (localActiveCount >= maxConcurrent) {
|
|
703
|
+
return `Cannot spawn: ${localActiveCount}/${maxConcurrent} sub-agents already running. Wait for one to complete.`;
|
|
704
|
+
}
|
|
705
|
+
const id = `child-${++localCounter}`;
|
|
706
|
+
const child = { id, task, startedAt: Date.now() };
|
|
707
|
+
const agent = createAgent({
|
|
708
|
+
harness: options.harness ?? ctx.harness,
|
|
709
|
+
provider: ctx.provider,
|
|
710
|
+
execution: ctx.execution
|
|
711
|
+
});
|
|
712
|
+
localChildren.set(id, child);
|
|
713
|
+
localActiveCount++;
|
|
714
|
+
options.onSpawn?.(child);
|
|
715
|
+
await ctx.hooks.callHook("spawn:before", { id, task });
|
|
716
|
+
try {
|
|
717
|
+
const stats = await agent.run({
|
|
718
|
+
prompt: task,
|
|
719
|
+
model: options.model,
|
|
720
|
+
system: systemOverride ?? options.system,
|
|
721
|
+
thinking: options.thinking,
|
|
722
|
+
signal: ctx.signal
|
|
723
|
+
});
|
|
724
|
+
localStats.totalIn += stats.totalIn;
|
|
725
|
+
localStats.totalOut += stats.totalOut;
|
|
726
|
+
localStats.turns += stats.turns;
|
|
727
|
+
localStats.elapsed += stats.elapsed;
|
|
728
|
+
options.onComplete?.(child, stats);
|
|
729
|
+
await ctx.hooks.callHook("spawn:complete", {
|
|
730
|
+
id,
|
|
731
|
+
task,
|
|
732
|
+
stats
|
|
733
|
+
});
|
|
734
|
+
const response = extractText(agent.messages.at(-1));
|
|
735
|
+
return [
|
|
736
|
+
`[sub-agent ${id}] Completed in ${stats.turns} turns (${stats.elapsed}ms)`,
|
|
737
|
+
`Tokens: ${stats.totalIn} in / ${stats.totalOut} out`,
|
|
738
|
+
"",
|
|
739
|
+
response || "(no text response)"
|
|
740
|
+
].join("\n");
|
|
741
|
+
} catch (err) {
|
|
742
|
+
await ctx.hooks.callHook("spawn:error", { id, task, error: err });
|
|
743
|
+
return `[sub-agent ${id}] Error: ${err.message}`;
|
|
744
|
+
} finally {
|
|
745
|
+
localActiveCount--;
|
|
746
|
+
await agent.destroy();
|
|
747
|
+
localChildren.delete(id);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
function extractText(message) {
|
|
753
|
+
if (!message || typeof message !== "object")
|
|
754
|
+
return "";
|
|
755
|
+
const msg = message;
|
|
756
|
+
if (typeof msg.content === "string")
|
|
757
|
+
return msg.content;
|
|
758
|
+
if (Array.isArray(msg.content)) {
|
|
759
|
+
return msg.content.filter((block) => block.type === "text").map((block) => block.text).join("\n");
|
|
760
|
+
}
|
|
761
|
+
return "";
|
|
762
|
+
}
|
|
763
|
+
var children, childCounter, activeCount, MAX_CONCURRENT, _totalChildStats, spawn;
|
|
764
|
+
var init_spawn = __esm({
|
|
765
|
+
"src/tools/spawn.ts"() {
|
|
766
|
+
"use strict";
|
|
767
|
+
init_agent();
|
|
768
|
+
children = /* @__PURE__ */ new Map();
|
|
769
|
+
childCounter = 0;
|
|
770
|
+
activeCount = 0;
|
|
771
|
+
MAX_CONCURRENT = 3;
|
|
772
|
+
_totalChildStats = {
|
|
773
|
+
totalIn: 0,
|
|
774
|
+
totalOut: 0,
|
|
775
|
+
turns: 0,
|
|
776
|
+
elapsed: 0
|
|
777
|
+
};
|
|
778
|
+
spawn = {
|
|
779
|
+
get children() {
|
|
780
|
+
return children;
|
|
781
|
+
},
|
|
782
|
+
get totalChildStats() {
|
|
783
|
+
return { ..._totalChildStats };
|
|
784
|
+
},
|
|
785
|
+
spec: {
|
|
786
|
+
name: "spawn",
|
|
787
|
+
description: "Spawn a sub-agent to work on a specific task. The sub-agent runs independently with its own tool access and returns its final response. Use this to delegate work, parallelize tasks, or isolate concerns.",
|
|
788
|
+
input_schema: {
|
|
789
|
+
type: "object",
|
|
790
|
+
properties: {
|
|
791
|
+
task: {
|
|
792
|
+
type: "string",
|
|
793
|
+
description: "The task prompt for the sub-agent. Be specific about what you want it to accomplish."
|
|
794
|
+
},
|
|
795
|
+
system: {
|
|
796
|
+
type: "string",
|
|
797
|
+
description: "Optional system prompt override for this specific sub-agent."
|
|
798
|
+
}
|
|
799
|
+
},
|
|
800
|
+
required: ["task"]
|
|
801
|
+
}
|
|
802
|
+
},
|
|
803
|
+
async execute(input, ctx) {
|
|
804
|
+
const task = input.task;
|
|
805
|
+
const systemOverride = input.system;
|
|
806
|
+
if (activeCount >= MAX_CONCURRENT) {
|
|
807
|
+
return `Cannot spawn: ${activeCount}/${MAX_CONCURRENT} sub-agents already running. Wait for one to complete.`;
|
|
808
|
+
}
|
|
809
|
+
const id = `child-${++childCounter}`;
|
|
810
|
+
const child = { id, task, startedAt: Date.now() };
|
|
811
|
+
const agent = createAgent({
|
|
812
|
+
harness: ctx.harness,
|
|
813
|
+
provider: ctx.provider,
|
|
814
|
+
execution: ctx.execution
|
|
815
|
+
});
|
|
816
|
+
children.set(id, child);
|
|
817
|
+
activeCount++;
|
|
818
|
+
await ctx.hooks.callHook("spawn:before", { id, task });
|
|
819
|
+
try {
|
|
820
|
+
const stats = await agent.run({
|
|
821
|
+
prompt: task,
|
|
822
|
+
system: systemOverride,
|
|
823
|
+
signal: ctx.signal
|
|
824
|
+
});
|
|
825
|
+
_totalChildStats.totalIn += stats.totalIn;
|
|
826
|
+
_totalChildStats.totalOut += stats.totalOut;
|
|
827
|
+
_totalChildStats.turns += stats.turns;
|
|
828
|
+
_totalChildStats.elapsed += stats.elapsed;
|
|
829
|
+
await ctx.hooks.callHook("spawn:complete", {
|
|
830
|
+
id,
|
|
831
|
+
task,
|
|
832
|
+
stats
|
|
833
|
+
});
|
|
834
|
+
const response = extractText(agent.messages.at(-1));
|
|
835
|
+
return [
|
|
836
|
+
`[sub-agent ${id}] Completed in ${stats.turns} turns (${stats.elapsed}ms)`,
|
|
837
|
+
`Tokens: ${stats.totalIn} in / ${stats.totalOut} out`,
|
|
838
|
+
"",
|
|
839
|
+
response || "(no text response)"
|
|
840
|
+
].join("\n");
|
|
841
|
+
} catch (err) {
|
|
842
|
+
await ctx.hooks.callHook("spawn:error", { id, task, error: err });
|
|
843
|
+
return `[sub-agent ${id}] Error: ${err.message}`;
|
|
844
|
+
} finally {
|
|
845
|
+
activeCount--;
|
|
846
|
+
await agent.destroy();
|
|
847
|
+
children.delete(id);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
// src/tools/list-files.ts
|
|
855
|
+
import { existsSync, readdirSync, statSync } from "fs";
|
|
856
|
+
import { resolve } from "path";
|
|
857
|
+
var cwd = process.cwd();
|
|
858
|
+
function safePath(p) {
|
|
859
|
+
const resolved = resolve(cwd, p);
|
|
860
|
+
if (!resolved.startsWith(cwd))
|
|
861
|
+
throw new Error(`Path escapes working directory: ${p}`);
|
|
862
|
+
return resolved;
|
|
863
|
+
}
|
|
864
|
+
var listFiles = {
|
|
865
|
+
spec: {
|
|
866
|
+
name: "list_files",
|
|
867
|
+
description: "List files and directories at the given path (relative to project root).",
|
|
868
|
+
input_schema: {
|
|
869
|
+
type: "object",
|
|
870
|
+
properties: {
|
|
871
|
+
path: { type: "string", description: 'Relative directory path (default: ".")' }
|
|
872
|
+
},
|
|
873
|
+
required: []
|
|
874
|
+
}
|
|
875
|
+
},
|
|
876
|
+
async execute({ path }, _ctx) {
|
|
877
|
+
const target = safePath(path || ".");
|
|
878
|
+
if (!existsSync(target))
|
|
879
|
+
return `Directory not found: ${path}`;
|
|
880
|
+
const entries = readdirSync(target);
|
|
881
|
+
return entries.map((name) => {
|
|
882
|
+
const full = resolve(target, name);
|
|
883
|
+
const isDir = statSync(full).isDirectory();
|
|
884
|
+
return `${isDir ? "\u{1F4C1}" : "\u{1F4C4}"} ${name}`;
|
|
885
|
+
}).join("\n");
|
|
886
|
+
}
|
|
887
|
+
};
|
|
888
|
+
|
|
889
|
+
// src/tools/read-file.ts
|
|
890
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
891
|
+
import { resolve as resolve2 } from "path";
|
|
892
|
+
var cwd2 = process.cwd();
|
|
893
|
+
function safePath2(p) {
|
|
894
|
+
const resolved = resolve2(cwd2, p);
|
|
895
|
+
if (!resolved.startsWith(cwd2))
|
|
896
|
+
throw new Error(`Path escapes working directory: ${p}`);
|
|
897
|
+
return resolved;
|
|
898
|
+
}
|
|
899
|
+
var readFile = {
|
|
900
|
+
spec: {
|
|
901
|
+
name: "read_file",
|
|
902
|
+
description: "Read the contents of a file at the given path (relative to project root).",
|
|
903
|
+
input_schema: {
|
|
904
|
+
type: "object",
|
|
905
|
+
properties: {
|
|
906
|
+
path: { type: "string", description: "Relative file path" }
|
|
907
|
+
},
|
|
908
|
+
required: ["path"]
|
|
909
|
+
}
|
|
910
|
+
},
|
|
911
|
+
async execute({ path }, _ctx) {
|
|
912
|
+
const target = safePath2(path);
|
|
913
|
+
if (!existsSync2(target))
|
|
914
|
+
return `File not found: ${path}`;
|
|
915
|
+
return readFileSync(target, "utf-8");
|
|
916
|
+
}
|
|
917
|
+
};
|
|
918
|
+
|
|
919
|
+
// src/tools/shell.ts
|
|
920
|
+
import { execSync } from "child_process";
|
|
921
|
+
var cwd3 = process.cwd();
|
|
922
|
+
var shell = {
|
|
923
|
+
spec: {
|
|
924
|
+
name: "shell",
|
|
925
|
+
description: "Execute a shell command and return stdout+stderr. Runs in the project root.",
|
|
926
|
+
input_schema: {
|
|
927
|
+
type: "object",
|
|
928
|
+
properties: {
|
|
929
|
+
command: { type: "string", description: "The shell command to run" }
|
|
930
|
+
},
|
|
931
|
+
required: ["command"]
|
|
932
|
+
}
|
|
933
|
+
},
|
|
934
|
+
async execute({ command }, _ctx) {
|
|
935
|
+
try {
|
|
936
|
+
const out = execSync(command, {
|
|
937
|
+
cwd: cwd3,
|
|
938
|
+
encoding: "utf-8",
|
|
939
|
+
timeout: 3e4,
|
|
940
|
+
maxBuffer: 1024 * 1024,
|
|
941
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
942
|
+
});
|
|
943
|
+
return out || "(no output)";
|
|
944
|
+
} catch (err) {
|
|
945
|
+
const stderr = err.stderr?.toString() ?? "";
|
|
946
|
+
const stdout = err.stdout?.toString() ?? "";
|
|
947
|
+
return `Exit code ${err.status ?? 1}
|
|
948
|
+
${stdout}
|
|
949
|
+
${stderr}`.trim();
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
};
|
|
953
|
+
|
|
954
|
+
// src/tools/index.ts
|
|
955
|
+
init_spawn();
|
|
956
|
+
init_validation();
|
|
957
|
+
|
|
958
|
+
// src/tools/write-file.ts
|
|
959
|
+
import { mkdirSync, writeFileSync } from "fs";
|
|
960
|
+
import { dirname as dirname2, resolve as resolve4 } from "path";
|
|
961
|
+
var cwd4 = process.cwd();
|
|
962
|
+
function safePath3(p) {
|
|
963
|
+
const resolved = resolve4(cwd4, p);
|
|
964
|
+
if (!resolved.startsWith(cwd4))
|
|
965
|
+
throw new Error(`Path escapes working directory: ${p}`);
|
|
966
|
+
return resolved;
|
|
967
|
+
}
|
|
968
|
+
var writeFile2 = {
|
|
969
|
+
spec: {
|
|
970
|
+
name: "write_file",
|
|
971
|
+
description: "Write content to a file. Creates parent directories if needed.",
|
|
972
|
+
input_schema: {
|
|
973
|
+
type: "object",
|
|
974
|
+
properties: {
|
|
975
|
+
path: { type: "string", description: "Relative file path" },
|
|
976
|
+
content: { type: "string", description: "File content to write" }
|
|
977
|
+
},
|
|
978
|
+
required: ["path", "content"]
|
|
979
|
+
}
|
|
980
|
+
},
|
|
981
|
+
async execute({ path, content }, _ctx) {
|
|
982
|
+
const target = safePath3(path);
|
|
983
|
+
mkdirSync(dirname2(target), { recursive: true });
|
|
984
|
+
writeFileSync(target, content);
|
|
985
|
+
return `Wrote ${content.length} bytes to ${path}`;
|
|
986
|
+
}
|
|
987
|
+
};
|
|
988
|
+
|
|
989
|
+
export {
|
|
990
|
+
createDockerContext,
|
|
991
|
+
createProcessContext,
|
|
992
|
+
createSandboxContext,
|
|
993
|
+
init_contexts,
|
|
994
|
+
validateToolArgs,
|
|
995
|
+
createAgent,
|
|
996
|
+
init_agent,
|
|
997
|
+
listFiles,
|
|
998
|
+
readFile,
|
|
999
|
+
shell,
|
|
1000
|
+
spawn,
|
|
1001
|
+
createSpawnTool,
|
|
1002
|
+
spawn_exports,
|
|
1003
|
+
init_spawn,
|
|
1004
|
+
writeFile2 as writeFile
|
|
1005
|
+
};
|