commit-insights 0.1.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.
@@ -0,0 +1,871 @@
1
+ import {
2
+ AnthropicError,
3
+ ToolError,
4
+ __classPrivateFieldGet,
5
+ __classPrivateFieldSet,
6
+ loggerFor,
7
+ promiseWithResolvers
8
+ } from "./chunk-2A73LZK7.js";
9
+ import "./chunk-JSBRDJBE.js";
10
+
11
+ // node_modules/@anthropic-ai/sdk/tools/agent-toolset/node.mjs
12
+ import * as fs3 from "fs/promises";
13
+ import * as fssync2 from "fs";
14
+ import * as path3 from "path";
15
+ import * as cp from "child_process";
16
+ import * as crypto from "crypto";
17
+ import * as readline from "readline";
18
+
19
+ // node_modules/@anthropic-ai/sdk/helpers/beta/json-schema.mjs
20
+ function betaTool(options) {
21
+ if (options.inputSchema.type !== "object") {
22
+ throw new Error(`JSON schema for tool "${options.name}" must be an object, but got ${options.inputSchema.type}`);
23
+ }
24
+ return {
25
+ type: "custom",
26
+ name: options.name,
27
+ input_schema: options.inputSchema,
28
+ description: options.description,
29
+ run: options.run,
30
+ parse: (content) => content,
31
+ ...options.close ? { close: options.close } : {}
32
+ };
33
+ }
34
+
35
+ // node_modules/@anthropic-ai/sdk/tools/agent-toolset/fs-util.mjs
36
+ import * as fs from "fs/promises";
37
+ import * as path from "path";
38
+ import { randomUUID } from "crypto";
39
+ var DIR_CREATE_MODE = 493;
40
+ var FILE_CREATE_MODE = 420;
41
+ async function realpathOrSelf(p) {
42
+ try {
43
+ return await fs.realpath(p);
44
+ } catch {
45
+ return p;
46
+ }
47
+ }
48
+ async function canonicalize(abs) {
49
+ const tail = [];
50
+ let prefix = abs;
51
+ for (; ; ) {
52
+ let real;
53
+ try {
54
+ real = await fs.realpath(prefix);
55
+ } catch {
56
+ let isLink = false;
57
+ try {
58
+ isLink = (await fs.lstat(prefix)).isSymbolicLink();
59
+ } catch {
60
+ }
61
+ if (isLink) {
62
+ prefix = path.resolve(path.dirname(prefix), await fs.readlink(prefix));
63
+ continue;
64
+ }
65
+ const parent = path.dirname(prefix);
66
+ if (parent === prefix)
67
+ return abs;
68
+ tail.push(path.basename(prefix));
69
+ prefix = parent;
70
+ continue;
71
+ }
72
+ return tail.length ? path.join(real, ...tail.reverse()) : real;
73
+ }
74
+ }
75
+ async function confineToRoot(root, p, opts) {
76
+ const allowOutside = opts?.allowOutside ?? false;
77
+ if (path.isAbsolute(p)) {
78
+ if (!allowOutside) {
79
+ throw new ToolError(`absolute path ${JSON.stringify(p)} not permitted`);
80
+ }
81
+ return path.resolve(p);
82
+ }
83
+ const realRoot = await realpathOrSelf(path.resolve(root));
84
+ const abs = path.resolve(realRoot, p);
85
+ if (allowOutside)
86
+ return abs;
87
+ const real = await canonicalize(abs);
88
+ const rootSep = realRoot.endsWith(path.sep) ? realRoot : realRoot + path.sep;
89
+ if (real !== realRoot && !real.startsWith(rootSep)) {
90
+ throw new ToolError(`path ${JSON.stringify(p)} escapes workdir`);
91
+ }
92
+ return real;
93
+ }
94
+ async function atomicWriteFile(targetPath, content) {
95
+ const dir = path.dirname(targetPath);
96
+ const tempPath = path.join(dir, `.tmp-${process.pid}-${randomUUID()}`);
97
+ let handle;
98
+ try {
99
+ handle = await fs.open(tempPath, "wx", FILE_CREATE_MODE);
100
+ await handle.writeFile(content, "utf-8");
101
+ await handle.sync();
102
+ await handle.close();
103
+ handle = void 0;
104
+ await fs.rename(tempPath, targetPath);
105
+ } catch (err) {
106
+ if (handle)
107
+ await handle.close().catch(() => {
108
+ });
109
+ await fs.unlink(tempPath).catch(() => {
110
+ });
111
+ throw err;
112
+ }
113
+ }
114
+ function fsErrorMessage(err, file) {
115
+ const code = err?.code;
116
+ switch (code) {
117
+ case "ENOENT":
118
+ return `${file}: no such file or directory`;
119
+ case "EACCES":
120
+ case "EPERM":
121
+ return `${file}: permission denied`;
122
+ case "ENOTDIR":
123
+ return `${file}: not a directory`;
124
+ case "EISDIR":
125
+ return `${file}: is a directory`;
126
+ case "ELOOP":
127
+ return `${file}: too many levels of symbolic links`;
128
+ case "ENAMETOOLONG":
129
+ return `${file}: file name too long`;
130
+ case "ENOSPC":
131
+ return `${file}: no space left on device`;
132
+ case "EMFILE":
133
+ case "ENFILE":
134
+ return `${file}: too many open files`;
135
+ default:
136
+ return `${file}: ${err instanceof Error ? err.message : String(err)}`;
137
+ }
138
+ }
139
+
140
+ // node_modules/@anthropic-ai/sdk/tools/agent-toolset/skills.mjs
141
+ import * as fs2 from "fs/promises";
142
+ import * as fssync from "fs";
143
+ import * as path2 from "path";
144
+ import { execFile } from "child_process";
145
+ import { promisify } from "util";
146
+ import { Readable } from "stream";
147
+ import { pipeline } from "stream/promises";
148
+ var execFileAsync = promisify(execFile);
149
+ async function setupSkills(ctx) {
150
+ const { client, sessionId } = ctx;
151
+ if (!client || !sessionId)
152
+ return async () => {
153
+ };
154
+ const log = loggerFor(client);
155
+ const session = await client.beta.sessions.retrieve(sessionId);
156
+ const skillsRoot = path2.resolve(ctx.workdir, "skills");
157
+ const created = [];
158
+ for (const skill of session.agent.skills) {
159
+ try {
160
+ const versionId = await resolveSkillVersion(client, skill.skill_id, skill.version);
161
+ const version = await client.beta.skills.versions.retrieve(versionId, { skill_id: skill.skill_id });
162
+ let dirname4 = path2.basename(version.name.trim());
163
+ if (dirname4 === "" || dirname4 === "." || dirname4 === "..")
164
+ dirname4 = skill.skill_id;
165
+ const dest = path2.resolve(skillsRoot, dirname4);
166
+ if (dest !== skillsRoot && !dest.startsWith(skillsRoot + path2.sep)) {
167
+ log.warn("skill name escapes the skills dir; skipping", {
168
+ component: "agent-tool-context",
169
+ name: version.name
170
+ });
171
+ continue;
172
+ }
173
+ const resp = await client.beta.skills.versions.download(versionId, { skill_id: skill.skill_id });
174
+ await fs2.rm(dest, { recursive: true, force: true });
175
+ await fs2.mkdir(dest, { recursive: true, mode: DIR_CREATE_MODE });
176
+ created.push(dest);
177
+ await extractSkillArchive(resp, dest);
178
+ log.info("downloaded skill", {
179
+ component: "agent-tool-context",
180
+ skill_id: skill.skill_id,
181
+ version: versionId,
182
+ dest
183
+ });
184
+ } catch (e) {
185
+ log.warn("failed to download skill", {
186
+ component: "agent-tool-context",
187
+ skill_id: skill.skill_id,
188
+ error: String(e)
189
+ });
190
+ }
191
+ }
192
+ return async () => {
193
+ for (const dest of created) {
194
+ await fs2.rm(dest, { recursive: true, force: true }).catch((e) => {
195
+ log.warn("failed to clean up skill", { component: "agent-tool-context", dest, error: String(e) });
196
+ });
197
+ }
198
+ };
199
+ }
200
+ async function resolveSkillVersion(client, skillId, version) {
201
+ if (/^\d+$/.test(version))
202
+ return version;
203
+ let newest;
204
+ for await (const v of client.beta.skills.versions.list(skillId)) {
205
+ if (/^\d+$/.test(v.version) && (newest === void 0 || BigInt(v.version) > BigInt(newest))) {
206
+ newest = v.version;
207
+ }
208
+ }
209
+ if (newest === void 0) {
210
+ throw new AnthropicError(`skill ${JSON.stringify(skillId)} has no concrete version to resolve ${JSON.stringify(version)} against`);
211
+ }
212
+ return newest;
213
+ }
214
+ function assertSafeMemberNames(names) {
215
+ for (const raw of names.split("\n")) {
216
+ const entry = raw.trim();
217
+ if (!entry)
218
+ continue;
219
+ if (path2.isAbsolute(entry) || entry.split(/[\\/]/).includes("..")) {
220
+ throw new AnthropicError(`refusing to extract unsafe archive member: ${entry}`);
221
+ }
222
+ }
223
+ }
224
+ function assertNoSpecialMembers(verboseListing) {
225
+ for (const line of verboseListing.split("\n")) {
226
+ const type = line.trimStart()[0];
227
+ if (type === "l" || type === "h" || type === "b" || type === "c" || type === "p" || type === "s") {
228
+ throw new AnthropicError("refusing to extract archive with symlink/hardlink/device member");
229
+ }
230
+ }
231
+ }
232
+ async function runArchiveTool(cmd, args) {
233
+ try {
234
+ const { stdout } = await execFileAsync(cmd, args);
235
+ return stdout;
236
+ } catch (e) {
237
+ if (e != null && typeof e === "object" && e.code === "ENOENT") {
238
+ throw new AnthropicError(`skill extraction requires the \`${cmd}\` command, but it was not found on PATH`);
239
+ }
240
+ throw e;
241
+ }
242
+ }
243
+ function archiveTopDir(listing) {
244
+ let top;
245
+ let nested = false;
246
+ for (const raw of listing.split("\n")) {
247
+ const parts = raw.trim().split("/").filter((p) => p !== "" && p !== ".");
248
+ if (parts.length === 0)
249
+ continue;
250
+ const first = parts[0];
251
+ if (top === void 0)
252
+ top = first;
253
+ else if (first !== top)
254
+ return "";
255
+ if (parts.length > 1)
256
+ nested = true;
257
+ }
258
+ return top !== void 0 && nested ? top : "";
259
+ }
260
+ async function extractSkillArchive(resp, dest) {
261
+ const tmp = path2.join(dest, `.skill-archive-${process.pid}-${Date.now()}`);
262
+ if (!resp.body) {
263
+ throw new AnthropicError("skill download response had no body");
264
+ }
265
+ await pipeline(Readable.fromWeb(resp.body), fssync.createWriteStream(tmp));
266
+ const stage = path2.join(path2.dirname(dest), `.skill-stage-${process.pid}-${Date.now()}`);
267
+ try {
268
+ const head = await readHead(tmp, 4);
269
+ const isZip = head.length >= 4 && head[0] === 80 && head[1] === 75 && head[2] === 3 && head[3] === 4;
270
+ const archiveCmd = isZip ? "unzip" : "tar";
271
+ const listing = await runArchiveTool(archiveCmd, isZip ? ["-Z1", tmp] : ["-tf", tmp]);
272
+ assertSafeMemberNames(listing);
273
+ assertNoSpecialMembers(await runArchiveTool(archiveCmd, isZip ? ["-Z", tmp] : ["-tvf", tmp]));
274
+ const top = archiveTopDir(listing);
275
+ await fs2.mkdir(stage, { recursive: true, mode: DIR_CREATE_MODE });
276
+ await runArchiveTool(archiveCmd, isZip ? ["-oq", tmp, "-d", stage] : ["-xf", tmp, "-C", stage]);
277
+ const srcRoot = top ? path2.join(stage, top) : stage;
278
+ for (const entry of await fs2.readdir(srcRoot)) {
279
+ await fs2.rename(path2.join(srcRoot, entry), path2.join(dest, entry));
280
+ }
281
+ } finally {
282
+ await fs2.rm(tmp, { force: true });
283
+ await fs2.rm(stage, { recursive: true, force: true });
284
+ }
285
+ }
286
+ async function readHead(file, n) {
287
+ const handle = await fs2.open(file, "r");
288
+ try {
289
+ const buf = Buffer.alloc(n);
290
+ const { bytesRead } = await handle.read(buf, 0, n, 0);
291
+ return buf.subarray(0, bytesRead);
292
+ } finally {
293
+ await handle.close();
294
+ }
295
+ }
296
+
297
+ // node_modules/@anthropic-ai/sdk/tools/agent-toolset/node.mjs
298
+ var _BashSession_instances;
299
+ var _BashSession_proc;
300
+ var _BashSession_buf;
301
+ var _BashSession_truncated;
302
+ var _BashSession_closed;
303
+ var _BashSession_waiting;
304
+ var _BashSession_append;
305
+ var BASH_OUTPUT_LIMIT = 100 * 1024;
306
+ var BASH_DEFAULT_TIMEOUT_MS = 12e4;
307
+ var DEFAULT_MAX_FILE_BYTES = 256 * 1024;
308
+ var GREP_OUTPUT_LIMIT = 100 * 1024;
309
+ var GREP_MAX_LINE_LENGTH = 2e3;
310
+ var GLOB_RESULT_LIMIT = 200;
311
+ var ANSI_RE = /\x1b\[[0-9;?]*[ -/]*[@-~]/g;
312
+ var fsGlob = fs3.glob;
313
+ function resolveMaxBytes(configured) {
314
+ return configured === void 0 ? DEFAULT_MAX_FILE_BYTES : configured;
315
+ }
316
+ function betaAgentToolset20260401(ctx) {
317
+ return [
318
+ betaBashTool(ctx),
319
+ betaReadTool(ctx),
320
+ betaWriteTool(ctx),
321
+ betaEditTool(ctx),
322
+ betaGlobTool(ctx),
323
+ betaGrepTool(ctx)
324
+ ];
325
+ }
326
+ function resolvePath(ctx, p) {
327
+ return confineToRoot(ctx.workdir, p, { allowOutside: ctx.unrestrictedPaths ?? false });
328
+ }
329
+ function scrubbedShellEnv() {
330
+ const env = {};
331
+ for (const [key, value] of Object.entries(process.env)) {
332
+ if (key.startsWith("ANTHROPIC_"))
333
+ continue;
334
+ env[key] = value;
335
+ }
336
+ return env;
337
+ }
338
+ var BashSession = class {
339
+ constructor(dir, env = scrubbedShellEnv()) {
340
+ _BashSession_instances.add(this);
341
+ _BashSession_proc.set(this, void 0);
342
+ _BashSession_buf.set(this, "");
343
+ _BashSession_truncated.set(this, false);
344
+ _BashSession_closed.set(this, false);
345
+ _BashSession_waiting.set(this, null);
346
+ __classPrivateFieldSet(this, _BashSession_proc, cp.spawn("/bin/bash", ["--noprofile", "--norc"], {
347
+ cwd: dir,
348
+ // `env` is the full base environment (the scrubbed process env by
349
+ // default, or the verbatim replacement from `AgentToolContext.env`).
350
+ // PS1/PS2/TERM are shell-control settings BashSession always applies so
351
+ // the pipe-based sentinel exec parsing works — not part of the
352
+ // user-facing environment.
353
+ env: { ...env, PS1: "", PS2: "", TERM: "dumb" },
354
+ stdio: ["pipe", "pipe", "pipe"],
355
+ detached: true
356
+ }), "f");
357
+ __classPrivateFieldGet(this, _BashSession_proc, "f").stdout.setEncoding("utf8");
358
+ __classPrivateFieldGet(this, _BashSession_proc, "f").stderr.setEncoding("utf8");
359
+ __classPrivateFieldGet(this, _BashSession_proc, "f").stdout.on("data", (d) => __classPrivateFieldGet(this, _BashSession_instances, "m", _BashSession_append).call(this, d));
360
+ __classPrivateFieldGet(this, _BashSession_proc, "f").stderr.on("data", (d) => __classPrivateFieldGet(this, _BashSession_instances, "m", _BashSession_append).call(this, d));
361
+ __classPrivateFieldGet(this, _BashSession_proc, "f").once("close", () => {
362
+ __classPrivateFieldSet(this, _BashSession_closed, true, "f");
363
+ const w = __classPrivateFieldGet(this, _BashSession_waiting, "f");
364
+ __classPrivateFieldSet(this, _BashSession_waiting, null, "f");
365
+ w?.resolve();
366
+ });
367
+ }
368
+ /** Whether the underlying shell process has exited. */
369
+ get closed() {
370
+ return __classPrivateFieldGet(this, _BashSession_closed, "f");
371
+ }
372
+ async exec(command, opts = {}) {
373
+ if (__classPrivateFieldGet(this, _BashSession_closed, "f")) {
374
+ throw new AnthropicError("bash session terminated");
375
+ }
376
+ const timeoutMs = opts.timeoutMs ?? BASH_DEFAULT_TIMEOUT_MS;
377
+ const signal = opts.signal;
378
+ if (signal?.aborted) {
379
+ throw new AnthropicError("bash command aborted");
380
+ }
381
+ __classPrivateFieldSet(this, _BashSession_buf, "", "f");
382
+ __classPrivateFieldSet(this, _BashSession_truncated, false, "f");
383
+ const sentinel = `__ANT_CMD_${crypto.randomUUID()}_DONE__`;
384
+ const sentinelSplit = `${sentinel.slice(0, 8)}''${sentinel.slice(8)}`;
385
+ const wrapped = `{ ${command}
386
+ } </dev/null 2>&1; printf '\\n${sentinelSplit}%d\\n' $?
387
+ `;
388
+ __classPrivateFieldGet(this, _BashSession_proc, "f").stdin.write(wrapped);
389
+ if (__classPrivateFieldGet(this, _BashSession_buf, "f").indexOf(sentinel) < 0) {
390
+ const { promise: sentinelSeen, resolve: resolve4 } = promiseWithResolvers();
391
+ __classPrivateFieldSet(this, _BashSession_waiting, { sentinel, resolve: resolve4 }, "f");
392
+ let timer;
393
+ let onAbort;
394
+ try {
395
+ await Promise.race([
396
+ sentinelSeen,
397
+ new Promise((_, reject) => {
398
+ timer = setTimeout(() => reject(new AnthropicError(`bash command timed out after ${timeoutMs}ms`)), timeoutMs);
399
+ }),
400
+ new Promise((_, reject) => {
401
+ if (!signal)
402
+ return;
403
+ onAbort = () => reject(new AnthropicError("bash command aborted"));
404
+ signal.addEventListener("abort", onAbort, { once: true });
405
+ })
406
+ ]);
407
+ } finally {
408
+ if (timer)
409
+ clearTimeout(timer);
410
+ if (onAbort && signal)
411
+ signal.removeEventListener("abort", onAbort);
412
+ __classPrivateFieldSet(this, _BashSession_waiting, null, "f");
413
+ }
414
+ }
415
+ const idx = __classPrivateFieldGet(this, _BashSession_buf, "f").indexOf(sentinel);
416
+ if (idx < 0) {
417
+ throw new AnthropicError("bash session terminated");
418
+ }
419
+ const tail = __classPrivateFieldGet(this, _BashSession_buf, "f").slice(idx + sentinel.length);
420
+ const m = tail.match(/^(-?\d+)/);
421
+ const exitCode = m ? parseInt(m[1], 10) : -1;
422
+ let out = __classPrivateFieldGet(this, _BashSession_buf, "f").slice(0, idx).replace(ANSI_RE, "").replace(/\n+$/, "");
423
+ if (__classPrivateFieldGet(this, _BashSession_truncated, "f")) {
424
+ out = `[output truncated]
425
+ ${out}`;
426
+ }
427
+ return { output: out, exitCode };
428
+ }
429
+ close() {
430
+ if (__classPrivateFieldGet(this, _BashSession_closed, "f"))
431
+ return;
432
+ __classPrivateFieldSet(this, _BashSession_closed, true, "f");
433
+ const w = __classPrivateFieldGet(this, _BashSession_waiting, "f");
434
+ __classPrivateFieldSet(this, _BashSession_waiting, null, "f");
435
+ w?.resolve();
436
+ __classPrivateFieldGet(this, _BashSession_proc, "f").stdout.destroy();
437
+ __classPrivateFieldGet(this, _BashSession_proc, "f").stderr.destroy();
438
+ __classPrivateFieldGet(this, _BashSession_proc, "f").stdin.destroy();
439
+ try {
440
+ process.kill(-__classPrivateFieldGet(this, _BashSession_proc, "f").pid, "SIGKILL");
441
+ } catch {
442
+ __classPrivateFieldGet(this, _BashSession_proc, "f").kill("SIGKILL");
443
+ }
444
+ __classPrivateFieldGet(this, _BashSession_proc, "f").unref();
445
+ }
446
+ };
447
+ _BashSession_proc = /* @__PURE__ */ new WeakMap(), _BashSession_buf = /* @__PURE__ */ new WeakMap(), _BashSession_truncated = /* @__PURE__ */ new WeakMap(), _BashSession_closed = /* @__PURE__ */ new WeakMap(), _BashSession_waiting = /* @__PURE__ */ new WeakMap(), _BashSession_instances = /* @__PURE__ */ new WeakSet(), _BashSession_append = function _BashSession_append2(d) {
448
+ __classPrivateFieldSet(this, _BashSession_buf, __classPrivateFieldGet(this, _BashSession_buf, "f") + d, "f");
449
+ if (__classPrivateFieldGet(this, _BashSession_buf, "f").length > BASH_OUTPUT_LIMIT) {
450
+ __classPrivateFieldSet(this, _BashSession_buf, __classPrivateFieldGet(this, _BashSession_buf, "f").slice(__classPrivateFieldGet(this, _BashSession_buf, "f").length - BASH_OUTPUT_LIMIT), "f");
451
+ __classPrivateFieldSet(this, _BashSession_truncated, true, "f");
452
+ }
453
+ if (__classPrivateFieldGet(this, _BashSession_waiting, "f") && __classPrivateFieldGet(this, _BashSession_buf, "f").indexOf(__classPrivateFieldGet(this, _BashSession_waiting, "f").sentinel) >= 0) {
454
+ const w = __classPrivateFieldGet(this, _BashSession_waiting, "f");
455
+ __classPrivateFieldSet(this, _BashSession_waiting, null, "f");
456
+ w.resolve();
457
+ }
458
+ };
459
+ function betaBashTool(ctx) {
460
+ let session;
461
+ let tail = Promise.resolve();
462
+ return betaTool({
463
+ name: "bash",
464
+ description: "Run a bash command in a persistent shell. State (cwd, env vars) persists across calls.",
465
+ inputSchema: {
466
+ type: "object",
467
+ properties: {
468
+ command: { type: "string", description: "The command to run" },
469
+ restart: { type: "boolean", description: "Restart the persistent shell before running" },
470
+ timeout_ms: { type: "integer", description: "Per-call timeout in milliseconds" }
471
+ }
472
+ },
473
+ run: async ({ command, restart, timeout_ms }, context) => {
474
+ const prev = tail;
475
+ const gate = promiseWithResolvers();
476
+ tail = gate.promise;
477
+ try {
478
+ await prev;
479
+ } catch {
480
+ }
481
+ try {
482
+ if (restart) {
483
+ session?.close();
484
+ session = void 0;
485
+ }
486
+ if (!command) {
487
+ if (restart)
488
+ return "bash session restarted";
489
+ throw new ToolError("bash: command is required");
490
+ }
491
+ session ?? (session = new BashSession(ctx.workdir, ctx.env));
492
+ try {
493
+ const { output, exitCode } = await session.exec(command, {
494
+ timeoutMs: timeout_ms ?? BASH_DEFAULT_TIMEOUT_MS,
495
+ signal: context?.signal
496
+ });
497
+ if (exitCode !== 0)
498
+ throw new ToolError(output || `exit ${exitCode}`);
499
+ return output;
500
+ } catch (e) {
501
+ if (e instanceof ToolError)
502
+ throw e;
503
+ session.close();
504
+ session = void 0;
505
+ throw new ToolError(`bash: ${e instanceof Error ? e.message : String(e)}`);
506
+ }
507
+ } finally {
508
+ gate.resolve();
509
+ }
510
+ },
511
+ close: () => {
512
+ session?.close();
513
+ session = void 0;
514
+ }
515
+ });
516
+ }
517
+ function betaReadTool(ctx) {
518
+ return betaTool({
519
+ name: "read",
520
+ description: "Read a UTF-8 text file relative to the workdir.",
521
+ inputSchema: {
522
+ type: "object",
523
+ properties: {
524
+ file_path: { type: "string" },
525
+ view_range: {
526
+ type: "array",
527
+ items: { type: "integer" },
528
+ description: "[start_line, end_line] 1-indexed inclusive"
529
+ }
530
+ },
531
+ required: ["file_path"]
532
+ },
533
+ run: async ({ file_path, view_range }) => {
534
+ if (!file_path)
535
+ throw new ToolError("read: file_path is required");
536
+ const abs = await resolvePath(ctx, file_path);
537
+ let data;
538
+ try {
539
+ const st = await fs3.stat(abs);
540
+ if (!st.isFile()) {
541
+ throw new ToolError(`read: ${file_path} is not a regular file`);
542
+ }
543
+ const limit = resolveMaxBytes(ctx.maxFileBytes);
544
+ if (limit !== null && st.size > limit) {
545
+ throw new ToolError(`read: ${file_path} is ${st.size} bytes, exceeds ${limit}-byte limit. Use bash (head/tail/sed) to read a slice.`);
546
+ }
547
+ data = await fs3.readFile(abs, "utf8");
548
+ } catch (e) {
549
+ if (e instanceof ToolError)
550
+ throw e;
551
+ throw new ToolError(`read: ${fsErrorMessage(e, file_path)}`);
552
+ }
553
+ if (!view_range)
554
+ return data;
555
+ if (view_range.length !== 2)
556
+ throw new ToolError("read: view_range must be [start_line, end_line]");
557
+ const [startLine, endLine] = view_range;
558
+ const lines = data.split("\n");
559
+ const start = Math.max(0, startLine - 1);
560
+ const end = endLine > 0 ? endLine : lines.length;
561
+ return lines.slice(start, end).join("\n");
562
+ }
563
+ });
564
+ }
565
+ function betaWriteTool(ctx) {
566
+ return betaTool({
567
+ name: "write",
568
+ description: "Write a UTF-8 text file relative to the workdir, creating parent directories as needed.",
569
+ inputSchema: {
570
+ type: "object",
571
+ properties: { file_path: { type: "string" }, content: { type: "string" } },
572
+ required: ["file_path", "content"]
573
+ },
574
+ run: async ({ file_path, content }) => {
575
+ if (!file_path)
576
+ throw new ToolError("write: file_path is required");
577
+ const abs = await resolvePath(ctx, file_path);
578
+ try {
579
+ await fs3.mkdir(path3.dirname(abs), { recursive: true, mode: DIR_CREATE_MODE });
580
+ await atomicWriteFile(abs, content ?? "");
581
+ } catch (e) {
582
+ throw new ToolError(`write: ${fsErrorMessage(e, file_path)}`);
583
+ }
584
+ return `wrote ${Buffer.byteLength(content ?? "")} bytes to ${file_path}`;
585
+ }
586
+ });
587
+ }
588
+ function betaEditTool(ctx) {
589
+ return betaTool({
590
+ name: "edit",
591
+ description: "Replace old_string with new_string in a file. old_string must be unique unless replace_all.",
592
+ inputSchema: {
593
+ type: "object",
594
+ properties: {
595
+ file_path: { type: "string" },
596
+ old_string: { type: "string" },
597
+ new_string: { type: "string" },
598
+ replace_all: { type: "boolean" }
599
+ },
600
+ required: ["file_path", "old_string", "new_string"]
601
+ },
602
+ run: async ({ file_path, old_string, new_string, replace_all }) => {
603
+ if (!file_path)
604
+ throw new ToolError("edit: file_path is required");
605
+ if (!old_string)
606
+ throw new ToolError("edit: old_string is required");
607
+ const abs = await resolvePath(ctx, file_path);
608
+ let data;
609
+ try {
610
+ const st = await fs3.stat(abs);
611
+ if (!st.isFile()) {
612
+ throw new ToolError(`edit: ${file_path} is not a regular file`);
613
+ }
614
+ const limit = resolveMaxBytes(ctx.maxFileBytes);
615
+ if (limit !== null && st.size > limit) {
616
+ throw new ToolError(`edit: ${file_path} is ${st.size} bytes, exceeds ${limit}-byte limit. Use bash (sed/awk) to edit a large file.`);
617
+ }
618
+ data = await fs3.readFile(abs, "utf8");
619
+ } catch (e) {
620
+ if (e instanceof ToolError)
621
+ throw e;
622
+ throw new ToolError(`edit: ${fsErrorMessage(e, file_path)}`);
623
+ }
624
+ const count = data.split(old_string).length - 1;
625
+ if (count === 0)
626
+ throw new ToolError(`edit: old_string not found in ${file_path}`);
627
+ let updated;
628
+ if (replace_all) {
629
+ updated = data.split(old_string).join(new_string);
630
+ } else {
631
+ if (count > 1)
632
+ throw new ToolError(`edit: old_string appears ${count} times in ${file_path} (must be unique)`);
633
+ updated = data.replace(old_string, () => new_string);
634
+ }
635
+ try {
636
+ await atomicWriteFile(abs, updated);
637
+ } catch (e) {
638
+ throw new ToolError(`edit: write: ${fsErrorMessage(e, file_path)}`);
639
+ }
640
+ return `edited ${file_path} (${replace_all ? count : 1} replacement(s))`;
641
+ }
642
+ });
643
+ }
644
+ function betaGlobTool(ctx) {
645
+ return betaTool({
646
+ name: "glob",
647
+ description: "Match files under the workdir against a glob pattern. Results are mtime-sorted, newest first.",
648
+ inputSchema: {
649
+ type: "object",
650
+ properties: {
651
+ pattern: { type: "string" },
652
+ path: { type: "string", description: "Directory to search in. Defaults to the workdir." }
653
+ },
654
+ required: ["pattern"]
655
+ },
656
+ run: async ({ pattern, path: searchPath }) => {
657
+ if (!pattern)
658
+ throw new ToolError("glob: pattern is required");
659
+ let root = path3.resolve(ctx.workdir);
660
+ let pat = pattern;
661
+ if (path3.isAbsolute(pattern)) {
662
+ if (!ctx.unrestrictedPaths)
663
+ throw new ToolError("glob: absolute pattern not permitted");
664
+ root = path3.parse(pattern).root;
665
+ pat = path3.relative(root, pattern);
666
+ } else if (searchPath) {
667
+ root = await resolvePath(ctx, searchPath);
668
+ }
669
+ if (!ctx.unrestrictedPaths && pat.split(/[\\/]/).includes("..")) {
670
+ throw new ToolError('glob: ".." is not permitted in the pattern');
671
+ }
672
+ const matches = [];
673
+ try {
674
+ for await (const entry of fsGlob(pat, {
675
+ cwd: root,
676
+ withFileTypes: true,
677
+ exclude: (d) => d.name === ".git" || d.name === "node_modules"
678
+ })) {
679
+ if (!entry.isFile())
680
+ continue;
681
+ const full = path3.join(entry.parentPath, entry.name);
682
+ if (!ctx.unrestrictedPaths && !isWithin(root, full))
683
+ continue;
684
+ let mtime = 0;
685
+ try {
686
+ mtime = (await fs3.stat(full)).mtimeMs;
687
+ } catch {
688
+ }
689
+ matches.push({ path: full, mtime });
690
+ }
691
+ } catch (e) {
692
+ throw new ToolError(`glob: ${e instanceof Error ? e.message : String(e)}`);
693
+ }
694
+ if (matches.length === 0)
695
+ return "no matches";
696
+ matches.sort((a, b) => b.mtime - a.mtime);
697
+ return matches.slice(0, GLOB_RESULT_LIMIT).map((m) => m.path).join("\n");
698
+ }
699
+ });
700
+ }
701
+ function betaGrepTool(ctx) {
702
+ return betaTool({
703
+ name: "grep",
704
+ description: "Search file contents for a regex. Uses ripgrep if available, otherwise a built-in walker.",
705
+ inputSchema: {
706
+ type: "object",
707
+ properties: { pattern: { type: "string" }, path: { type: "string" } },
708
+ required: ["pattern"]
709
+ },
710
+ run: async ({ pattern, path: p }, context) => {
711
+ if (!pattern)
712
+ throw new ToolError("grep: pattern is required");
713
+ let searchPath = path3.resolve(ctx.workdir);
714
+ if (p)
715
+ searchPath = await resolvePath(ctx, p);
716
+ const rg = await findRg();
717
+ return rg ? runRipgrep(rg, pattern, searchPath, context?.signal) : runWalkGrep(pattern, searchPath, context?.signal);
718
+ }
719
+ });
720
+ }
721
+ function runRipgrep(rg, pattern, searchPath, signal) {
722
+ return new Promise((resolve4, reject) => {
723
+ const proc = cp.spawn(rg, ["-n", "--no-heading", "-e", pattern, "--", searchPath], {
724
+ ...signal ? { signal } : {}
725
+ });
726
+ let out = "";
727
+ let errOut = "";
728
+ let truncated = false;
729
+ proc.stdout.on("data", (d) => {
730
+ if (truncated)
731
+ return;
732
+ out += d;
733
+ if (out.length > GREP_OUTPUT_LIMIT) {
734
+ truncated = true;
735
+ out = out.slice(0, GREP_OUTPUT_LIMIT);
736
+ proc.kill("SIGKILL");
737
+ }
738
+ });
739
+ proc.stderr.on("data", (d) => errOut += d);
740
+ proc.on("close", (code) => {
741
+ if (signal?.aborted)
742
+ return reject(new ToolError("grep: aborted"));
743
+ if (truncated)
744
+ return resolve4(out + `
745
+ [output truncated at ${GREP_OUTPUT_LIMIT} bytes]`);
746
+ if (code === 0)
747
+ return resolve4(out);
748
+ if (code === 1)
749
+ return resolve4("no matches");
750
+ reject(new ToolError(`grep: rg failed: ${errOut || `exit ${code}`}`));
751
+ });
752
+ proc.on("error", (e) => {
753
+ if (signal?.aborted)
754
+ return reject(new ToolError("grep: aborted"));
755
+ reject(new ToolError(`grep: rg failed: ${e.message}`));
756
+ });
757
+ });
758
+ }
759
+ async function runWalkGrep(pattern, root, signal) {
760
+ let re;
761
+ try {
762
+ re = new RegExp(pattern);
763
+ } catch (e) {
764
+ throw new ToolError(`grep: invalid regex: ${e instanceof Error ? e.message : String(e)}`);
765
+ }
766
+ const hits = [];
767
+ let budget = GREP_OUTPUT_LIMIT;
768
+ const push = (line) => {
769
+ budget -= line.length + 1;
770
+ if (budget < 0) {
771
+ hits.push(`[output truncated at ${GREP_OUTPUT_LIMIT} bytes]`);
772
+ return false;
773
+ }
774
+ hits.push(line);
775
+ return true;
776
+ };
777
+ const stat2 = await fs3.stat(root).catch(() => null);
778
+ if (stat2?.isFile()) {
779
+ await grepFile(root, re, push);
780
+ } else {
781
+ await walk(root, "", (rel) => grepFile(path3.join(root, rel), re, push), signal);
782
+ }
783
+ if (signal?.aborted)
784
+ throw new ToolError("grep: aborted");
785
+ if (hits.length === 0)
786
+ return "no matches";
787
+ return hits.join("\n");
788
+ }
789
+ async function grepFile(file, re, push) {
790
+ const stream = fssync2.createReadStream(file, { encoding: "utf8" });
791
+ const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
792
+ let i = 0;
793
+ try {
794
+ for await (const line of rl) {
795
+ i++;
796
+ if (line.length > GREP_MAX_LINE_LENGTH)
797
+ continue;
798
+ if (re.test(line) && !push(`${file}:${i}:${line}`))
799
+ return false;
800
+ }
801
+ } catch {
802
+ } finally {
803
+ stream.destroy();
804
+ }
805
+ return true;
806
+ }
807
+ function isWithin(root, p) {
808
+ const rel = path3.relative(root, p);
809
+ return rel === "" || !rel.startsWith(".." + path3.sep) && rel !== ".." && !path3.isAbsolute(rel);
810
+ }
811
+ var WALK_MAX_DEPTH = 40;
812
+ var WALK_MAX_ENTRIES = 5e4;
813
+ async function walk(root, rel, fn, signal) {
814
+ let remaining = WALK_MAX_ENTRIES;
815
+ async function inner(rel2, depth) {
816
+ if (depth > WALK_MAX_DEPTH)
817
+ return true;
818
+ if (signal?.aborted)
819
+ return false;
820
+ let entries;
821
+ try {
822
+ entries = await fs3.readdir(path3.join(root, rel2), { withFileTypes: true });
823
+ } catch {
824
+ return true;
825
+ }
826
+ for (const e of entries) {
827
+ if (e.name === ".git" || e.name === "node_modules")
828
+ continue;
829
+ if (remaining-- <= 0)
830
+ return false;
831
+ if (signal?.aborted)
832
+ return false;
833
+ const childRel = rel2 ? path3.join(rel2, e.name) : e.name;
834
+ if (e.isDirectory()) {
835
+ if (!await inner(childRel, depth + 1))
836
+ return false;
837
+ } else if (e.isFile()) {
838
+ if (await fn(childRel) === false)
839
+ return false;
840
+ }
841
+ }
842
+ return true;
843
+ }
844
+ await inner(rel, 0);
845
+ }
846
+ async function findRg() {
847
+ const dirs = (process.env["PATH"] ?? "").split(path3.delimiter);
848
+ for (const d of dirs) {
849
+ const candidate = path3.join(d, "rg");
850
+ try {
851
+ await fs3.access(candidate, fssync2.constants.X_OK);
852
+ return candidate;
853
+ } catch {
854
+ }
855
+ }
856
+ return null;
857
+ }
858
+ export {
859
+ BashSession,
860
+ betaAgentToolset20260401,
861
+ betaBashTool,
862
+ betaEditTool,
863
+ betaGlobTool,
864
+ betaGrepTool,
865
+ betaReadTool,
866
+ betaWriteTool,
867
+ extractSkillArchive,
868
+ resolvePath,
869
+ resolveSkillVersion,
870
+ setupSkills
871
+ };