@junando/worker 0.10.1 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1177 @@
1
+ const require_rolldown_runtime = require('./rolldown-runtime-CfAboj1T.cjs');
2
+ const require_sdk = require('./sdk-BD78kZ4E.cjs');
3
+ let node_crypto = require("node:crypto");
4
+ node_crypto = require_rolldown_runtime.__toESM(node_crypto, 1);
5
+ let node_fs = require("node:fs");
6
+ node_fs = require_rolldown_runtime.__toESM(node_fs, 1);
7
+ let node_path = require("node:path");
8
+ node_path = require_rolldown_runtime.__toESM(node_path, 1);
9
+ let node_stream = require("node:stream");
10
+ let node_fs_promises = require("node:fs/promises");
11
+ node_fs_promises = require_rolldown_runtime.__toESM(node_fs_promises, 1);
12
+ let node_child_process = require("node:child_process");
13
+ node_child_process = require_rolldown_runtime.__toESM(node_child_process, 1);
14
+ let node_util = require("node:util");
15
+ let node_readline = require("node:readline");
16
+ node_readline = require_rolldown_runtime.__toESM(node_readline, 1);
17
+ let node_stream_promises = require("node:stream/promises");
18
+
19
+ //#region ../../node_modules/.pnpm/@anthropic-ai+sdk@0.102.0_zod@4.4.3/node_modules/@anthropic-ai/sdk/lib/transform-json-schema.mjs
20
+ const SUPPORTED_STRING_FORMATS = new Set([
21
+ "date-time",
22
+ "time",
23
+ "date",
24
+ "duration",
25
+ "email",
26
+ "hostname",
27
+ "uri",
28
+ "ipv4",
29
+ "ipv6",
30
+ "uuid"
31
+ ]);
32
+ function deepClone(obj) {
33
+ return JSON.parse(JSON.stringify(obj));
34
+ }
35
+ function transformJSONSchema(jsonSchema) {
36
+ const workingCopy = deepClone(jsonSchema);
37
+ return _transformJSONSchema(workingCopy);
38
+ }
39
+ function _transformJSONSchema(jsonSchema) {
40
+ const strictSchema = {};
41
+ const ref = require_sdk.pop(jsonSchema, "$ref");
42
+ if (ref !== undefined) {
43
+ strictSchema["$ref"] = ref;
44
+ return strictSchema;
45
+ }
46
+ const defs = require_sdk.pop(jsonSchema, "$defs");
47
+ if (defs !== undefined) {
48
+ const strictDefs = {};
49
+ strictSchema["$defs"] = strictDefs;
50
+ for (const [name, defSchema] of Object.entries(defs)) {
51
+ strictDefs[name] = _transformJSONSchema(defSchema);
52
+ }
53
+ }
54
+ const type = require_sdk.pop(jsonSchema, "type");
55
+ const anyOf = require_sdk.pop(jsonSchema, "anyOf");
56
+ const oneOf = require_sdk.pop(jsonSchema, "oneOf");
57
+ const allOf = require_sdk.pop(jsonSchema, "allOf");
58
+ if (Array.isArray(anyOf)) {
59
+ strictSchema["anyOf"] = anyOf.map((variant) => _transformJSONSchema(variant));
60
+ } else if (Array.isArray(oneOf)) {
61
+ strictSchema["anyOf"] = oneOf.map((variant) => _transformJSONSchema(variant));
62
+ } else if (Array.isArray(allOf)) {
63
+ strictSchema["allOf"] = allOf.map((entry) => _transformJSONSchema(entry));
64
+ } else {
65
+ if (type === undefined) {
66
+ throw new Error("JSON schema must have a type defined if anyOf/oneOf/allOf are not used");
67
+ }
68
+ strictSchema["type"] = type;
69
+ }
70
+ const description = require_sdk.pop(jsonSchema, "description");
71
+ if (description !== undefined) {
72
+ strictSchema["description"] = description;
73
+ }
74
+ const title = require_sdk.pop(jsonSchema, "title");
75
+ if (title !== undefined) {
76
+ strictSchema["title"] = title;
77
+ }
78
+ if (type === "object") {
79
+ const properties = require_sdk.pop(jsonSchema, "properties") || {};
80
+ strictSchema["properties"] = Object.fromEntries(Object.entries(properties).map(([key, propSchema]) => [key, _transformJSONSchema(propSchema)]));
81
+ require_sdk.pop(jsonSchema, "additionalProperties");
82
+ strictSchema["additionalProperties"] = false;
83
+ const required = require_sdk.pop(jsonSchema, "required");
84
+ if (required !== undefined) {
85
+ strictSchema["required"] = required;
86
+ }
87
+ } else if (type === "string") {
88
+ const format = require_sdk.pop(jsonSchema, "format");
89
+ if (format !== undefined && SUPPORTED_STRING_FORMATS.has(format)) {
90
+ strictSchema["format"] = format;
91
+ } else if (format !== undefined) {
92
+ jsonSchema["format"] = format;
93
+ }
94
+ } else if (type === "array") {
95
+ const items = require_sdk.pop(jsonSchema, "items");
96
+ if (items !== undefined) {
97
+ strictSchema["items"] = _transformJSONSchema(items);
98
+ }
99
+ const minItems = require_sdk.pop(jsonSchema, "minItems");
100
+ if (minItems !== undefined && (minItems === 0 || minItems === 1)) {
101
+ strictSchema["minItems"] = minItems;
102
+ } else if (minItems !== undefined) {
103
+ jsonSchema["minItems"] = minItems;
104
+ }
105
+ }
106
+ if (Object.keys(jsonSchema).length > 0) {
107
+ const existingDescription = strictSchema["description"];
108
+ strictSchema["description"] = (existingDescription ? existingDescription + "\n\n" : "") + "{" + Object.entries(jsonSchema).map(([key, value]) => `${key}: ${JSON.stringify(value)}`).join(", ") + "}";
109
+ }
110
+ return strictSchema;
111
+ }
112
+
113
+ //#endregion
114
+ //#region ../../node_modules/.pnpm/@anthropic-ai+sdk@0.102.0_zod@4.4.3/node_modules/@anthropic-ai/sdk/helpers/beta/json-schema.mjs
115
+ /**
116
+ * Creates a Tool with a provided JSON schema that can be passed
117
+ * to the `.toolRunner()` method. The schema is used to automatically validate
118
+ * the input arguments for the tool.
119
+ */
120
+ function betaTool(options) {
121
+ if (options.inputSchema.type !== "object") {
122
+ throw new Error(`JSON schema for tool "${options.name}" must be an object, but got ${options.inputSchema.type}`);
123
+ }
124
+ return {
125
+ type: "custom",
126
+ name: options.name,
127
+ input_schema: options.inputSchema,
128
+ description: options.description,
129
+ run: options.run,
130
+ parse: (content) => content,
131
+ ...options.close ? { close: options.close } : {}
132
+ };
133
+ }
134
+ /**
135
+ * Creates a JSON schema output format object from the given JSON schema.
136
+ * If this is passed to the `.parse()` method then the response message will contain a
137
+ * `.parsed_output` property that is the result of parsing the content with the given JSON schema.
138
+ *
139
+ */
140
+ function betaJSONSchemaOutputFormat(jsonSchema, options) {
141
+ if (jsonSchema.type !== "object") {
142
+ throw new Error(`JSON schema for tool must be an object, but got ${jsonSchema.type}`);
143
+ }
144
+ const transform = options?.transform ?? true;
145
+ if (transform) {
146
+ jsonSchema = transformJSONSchema(jsonSchema);
147
+ }
148
+ return {
149
+ type: "json_schema",
150
+ schema: { ...jsonSchema },
151
+ parse: (content) => {
152
+ try {
153
+ return JSON.parse(content);
154
+ } catch (error) {
155
+ throw new require_sdk.AnthropicError(`Failed to parse structured output: ${error}`);
156
+ }
157
+ }
158
+ };
159
+ }
160
+
161
+ //#endregion
162
+ //#region ../../node_modules/.pnpm/@anthropic-ai+sdk@0.102.0_zod@4.4.3/node_modules/@anthropic-ai/sdk/tools/agent-toolset/fs-util.mjs
163
+ /**
164
+ * Shared, Node-only filesystem helpers for the agent toolset's file tools:
165
+ * path confinement (symlink-aware), an atomic write, and language-independent
166
+ * error messages. Kept out of `node.ts` so the tool implementations stay focused
167
+ * and these helpers can be reused by every file tool.
168
+ */
169
+ /** Mode for directories the file tools create — not world-writable under a 0 umask. */
170
+ const DIR_CREATE_MODE = 493;
171
+ /** Mode for files the file tools create. */
172
+ const FILE_CREATE_MODE = 420;
173
+ /** `realpath` `p`, or return `p` unchanged when it cannot be resolved. */
174
+ async function realpathOrSelf(p) {
175
+ try {
176
+ return await node_fs_promises.realpath(p);
177
+ } catch {
178
+ return p;
179
+ }
180
+ }
181
+ /**
182
+ * Fully resolve `abs`: `realpath` the longest existing ancestor and re-append
183
+ * the rest, but never re-append a component that is itself a symlink — read the
184
+ * link and continue from its target instead. This handles paths being created
185
+ * (write/edit) without letting a symlink leaf (e.g. a dangling one pointing
186
+ * outside a confinement root) slip through unresolved.
187
+ */
188
+ async function canonicalize(abs) {
189
+ const tail = [];
190
+ let prefix = abs;
191
+ for (;;) {
192
+ let real;
193
+ try {
194
+ real = await node_fs_promises.realpath(prefix);
195
+ } catch {
196
+ let isLink = false;
197
+ try {
198
+ isLink = (await node_fs_promises.lstat(prefix)).isSymbolicLink();
199
+ } catch {}
200
+ if (isLink) {
201
+ prefix = node_path.resolve(node_path.dirname(prefix), await node_fs_promises.readlink(prefix));
202
+ continue;
203
+ }
204
+ const parent = node_path.dirname(prefix);
205
+ if (parent === prefix) return abs;
206
+ tail.push(node_path.basename(prefix));
207
+ prefix = parent;
208
+ continue;
209
+ }
210
+ return tail.length ? node_path.join(real, ...tail.reverse()) : real;
211
+ }
212
+ }
213
+ /**
214
+ * Resolve `p` and confine it to `root`.
215
+ *
216
+ * Unless `allowOutside` is set, absolute inputs are rejected and the
217
+ * **canonical** path is returned — every symlink in `p` (including the leaf,
218
+ * even a dangling one) is resolved before the confinement check, and the
219
+ * resolved path is what the caller then operates on, so a symlink inside `root`
220
+ * that points outside it can neither pass the check nor be followed afterwards.
221
+ *
222
+ * Residual TOCTOU: a component could still be swapped for a symlink between this
223
+ * call and the eventual `fs` operation. Closing that fully needs per-component
224
+ * `O_NOFOLLOW`/`openat`, which Node does not expose ergonomically; this is why a
225
+ * sandbox is still recommended for the toolset as a whole.
226
+ */
227
+ async function confineToRoot(root, p, opts) {
228
+ const allowOutside = opts?.allowOutside ?? false;
229
+ if (node_path.isAbsolute(p)) {
230
+ if (!allowOutside) {
231
+ throw new require_sdk.ToolError(`absolute path ${JSON.stringify(p)} not permitted`);
232
+ }
233
+ return node_path.resolve(p);
234
+ }
235
+ const realRoot = await realpathOrSelf(node_path.resolve(root));
236
+ const abs = node_path.resolve(realRoot, p);
237
+ if (allowOutside) return abs;
238
+ const real = await canonicalize(abs);
239
+ const rootSep = realRoot.endsWith(node_path.sep) ? realRoot : realRoot + node_path.sep;
240
+ if (real !== realRoot && !real.startsWith(rootSep)) {
241
+ throw new require_sdk.ToolError(`path ${JSON.stringify(p)} escapes workdir`);
242
+ }
243
+ return real;
244
+ }
245
+ /**
246
+ * Atomically write `content` to `targetPath`: write a sibling temp file, fsync
247
+ * it, then rename over the target. The rename is atomic on most filesystems, so
248
+ * a crash mid-write never leaves the target half-written.
249
+ */
250
+ async function atomicWriteFile(targetPath, content) {
251
+ const dir = node_path.dirname(targetPath);
252
+ const tempPath = node_path.join(dir, `.tmp-${process.pid}-${(0, node_crypto.randomUUID)()}`);
253
+ let handle;
254
+ try {
255
+ handle = await node_fs_promises.open(tempPath, "wx", 420);
256
+ await handle.writeFile(content, "utf-8");
257
+ await handle.sync();
258
+ await handle.close();
259
+ handle = undefined;
260
+ await node_fs_promises.rename(tempPath, targetPath);
261
+ } catch (err) {
262
+ if (handle) await handle.close().catch(() => {});
263
+ await node_fs_promises.unlink(tempPath).catch(() => {});
264
+ throw err;
265
+ }
266
+ }
267
+ /**
268
+ * Map a thrown filesystem error to a consistent, language-independent message,
269
+ * so the model sees the same wording regardless of the runtime (Node's raw
270
+ * `ENOENT: no such file...` text would otherwise leak through). Falls back to
271
+ * the raw error message for codes we don't special-case.
272
+ */
273
+ function fsErrorMessage(err, file) {
274
+ const code = err?.code;
275
+ switch (code) {
276
+ case "ENOENT": return `${file}: no such file or directory`;
277
+ case "EACCES":
278
+ case "EPERM": return `${file}: permission denied`;
279
+ case "ENOTDIR": return `${file}: not a directory`;
280
+ case "EISDIR": return `${file}: is a directory`;
281
+ case "ELOOP": return `${file}: too many levels of symbolic links`;
282
+ case "ENAMETOOLONG": return `${file}: file name too long`;
283
+ case "ENOSPC": return `${file}: no space left on device`;
284
+ case "EMFILE":
285
+ case "ENFILE": return `${file}: too many open files`;
286
+ default: return `${file}: ${err instanceof Error ? err.message : String(err)}`;
287
+ }
288
+ }
289
+
290
+ //#endregion
291
+ //#region ../../node_modules/.pnpm/@anthropic-ai+sdk@0.102.0_zod@4.4.3/node_modules/@anthropic-ai/sdk/tools/agent-toolset/skills.mjs
292
+ /**
293
+ * Node-only skill plumbing for the agent toolset: downloading a session
294
+ * agent's skills into the workdir and extracting the archives. Kept in its own
295
+ * file because it is a distinct concern from the tool implementations in
296
+ * `node.ts` — distinct enough, and large enough, to review on its own.
297
+ */
298
+ const execFileAsync = (0, node_util.promisify)(node_child_process.execFile);
299
+ /**
300
+ * Download the session agent's skills into `{ctx.workdir}/skills/<name>/`.
301
+ *
302
+ * No-op (returns a no-op cleanup) unless both `ctx.client` and `ctx.sessionId`
303
+ * are set. Looks up the session's resolved agent and, for each skill, fetches
304
+ * its files via `client.beta.skills.versions.download` and extracts the archive
305
+ * (a zip or tar.* archive) into a directory named after the skill. A failure on
306
+ * one skill is logged and does not block the others. Call this before starting
307
+ * the session tool runner (e.g. right after the bash session / workdir is
308
+ * ready).
309
+ *
310
+ * Returns a cleanup function that removes the skill directories this call
311
+ * created — call it once the work item is done so downloaded skills do not
312
+ * accumulate in the workdir across sessions.
313
+ */
314
+ async function setupSkills(ctx) {
315
+ const { client, sessionId } = ctx;
316
+ if (!client || !sessionId) return async () => {};
317
+ const log = require_sdk.loggerFor(client);
318
+ const session = await client.beta.sessions.retrieve(sessionId);
319
+ const skillsRoot = node_path.resolve(ctx.workdir, "skills");
320
+ const created = [];
321
+ for (const skill of session.agent.skills) {
322
+ try {
323
+ const versionId = await resolveSkillVersion(client, skill.skill_id, skill.version);
324
+ const version = await client.beta.skills.versions.retrieve(versionId, { skill_id: skill.skill_id });
325
+ let dirname = node_path.basename(version.name.trim());
326
+ if (dirname === "" || dirname === "." || dirname === "..") dirname = skill.skill_id;
327
+ const dest = node_path.resolve(skillsRoot, dirname);
328
+ if (dest !== skillsRoot && !dest.startsWith(skillsRoot + node_path.sep)) {
329
+ log.warn("skill name escapes the skills dir; skipping", {
330
+ component: "agent-tool-context",
331
+ name: version.name
332
+ });
333
+ continue;
334
+ }
335
+ const resp = await client.beta.skills.versions.download(versionId, { skill_id: skill.skill_id });
336
+ await node_fs_promises.rm(dest, {
337
+ recursive: true,
338
+ force: true
339
+ });
340
+ await node_fs_promises.mkdir(dest, {
341
+ recursive: true,
342
+ mode: 493
343
+ });
344
+ created.push(dest);
345
+ await extractSkillArchive(resp, dest);
346
+ log.info("downloaded skill", {
347
+ component: "agent-tool-context",
348
+ skill_id: skill.skill_id,
349
+ version: versionId,
350
+ dest
351
+ });
352
+ } catch (e) {
353
+ log.warn("failed to download skill", {
354
+ component: "agent-tool-context",
355
+ skill_id: skill.skill_id,
356
+ error: String(e)
357
+ });
358
+ }
359
+ }
360
+ return async () => {
361
+ for (const dest of created) {
362
+ await node_fs_promises.rm(dest, {
363
+ recursive: true,
364
+ force: true
365
+ }).catch((e) => {
366
+ log.warn("failed to clean up skill", {
367
+ component: "agent-tool-context",
368
+ dest,
369
+ error: String(e)
370
+ });
371
+ });
372
+ }
373
+ };
374
+ }
375
+ /**
376
+ * Resolve `version` to the concrete numeric timestamp the
377
+ * `/v1/skills/{id}/versions/{version}` endpoints require — `session.agent.skills[].version`
378
+ * can be an alias such as `"latest"`, which those endpoints reject. Numeric
379
+ * versions pass through unchanged.
380
+ */
381
+ async function resolveSkillVersion(client, skillId, version) {
382
+ if (/^\d+$/.test(version)) return version;
383
+ let newest;
384
+ for await (const v of client.beta.skills.versions.list(skillId)) {
385
+ if (/^\d+$/.test(v.version) && (newest === undefined || BigInt(v.version) > BigInt(newest))) {
386
+ newest = v.version;
387
+ }
388
+ }
389
+ if (newest === undefined) {
390
+ throw new require_sdk.AnthropicError(`skill ${JSON.stringify(skillId)} has no concrete version to resolve ${JSON.stringify(version)} against`);
391
+ }
392
+ return newest;
393
+ }
394
+ /** Reject archive members that are absolute or contain a `..` component. */
395
+ function assertSafeMemberNames(names) {
396
+ for (const raw of names.split("\n")) {
397
+ const entry = raw.trim();
398
+ if (!entry) continue;
399
+ if (node_path.isAbsolute(entry) || entry.split(/[\\/]/).includes("..")) {
400
+ throw new require_sdk.AnthropicError(`refusing to extract unsafe archive member: ${entry}`);
401
+ }
402
+ }
403
+ }
404
+ /**
405
+ * Reject archives that contain anything other than regular files and
406
+ * directories. The type char is the first byte of each `ls`-style line emitted
407
+ * by `tar -tvf` / `unzip -Z`: `-` file, `d` dir, `l` symlink, `h` hardlink,
408
+ * `b`/`c` device, `p` fifo, `s` socket. A symlink/hardlink member is how an
409
+ * archive escapes its extraction dir even when no name contains `..`.
410
+ */
411
+ function assertNoSpecialMembers(verboseListing) {
412
+ for (const line of verboseListing.split("\n")) {
413
+ const type = line.trimStart()[0];
414
+ if (type === "l" || type === "h" || type === "b" || type === "c" || type === "p" || type === "s") {
415
+ throw new require_sdk.AnthropicError("refusing to extract archive with symlink/hardlink/device member");
416
+ }
417
+ }
418
+ }
419
+ /**
420
+ * Run an archive CLI (`unzip` for zip archives, `tar` for everything else),
421
+ * returning its stdout. Both binaries must be on `PATH`; a missing one would
422
+ * otherwise surface as an opaque `ENOENT` spawn failure, so it is turned into a
423
+ * clear, specific error naming the missing command.
424
+ */
425
+ async function runArchiveTool(cmd, args) {
426
+ try {
427
+ const { stdout } = await execFileAsync(cmd, args);
428
+ return stdout;
429
+ } catch (e) {
430
+ if (e != null && typeof e === "object" && e.code === "ENOENT") {
431
+ throw new require_sdk.AnthropicError(`skill extraction requires the \`${cmd}\` command, but it was not found on PATH`);
432
+ }
433
+ throw e;
434
+ }
435
+ }
436
+ /**
437
+ * The single top-level directory shared by every entry in a newline-separated
438
+ * archive listing, or `''` if entries don't all live under one common
439
+ * directory. Skill bundles are packaged wrapped in one directory named after
440
+ * the skill (e.g. `pdf/SKILL.md`, `pdf/scripts/...`); the extractor strips it
441
+ * so contents land directly in the skill's dir instead of a redundant nested
442
+ * `<skill>/<skill>/` level. A flat or multi-root archive yields `''`.
443
+ */
444
+ function archiveTopDir(listing) {
445
+ let top;
446
+ let nested = false;
447
+ for (const raw of listing.split("\n")) {
448
+ const parts = raw.trim().split("/").filter((p) => p !== "" && p !== ".");
449
+ if (parts.length === 0) continue;
450
+ const first = parts[0];
451
+ if (top === undefined) top = first;
452
+ else if (first !== top) return "";
453
+ if (parts.length > 1) nested = true;
454
+ }
455
+ return top !== undefined && nested ? top : "";
456
+ }
457
+ /**
458
+ * Extract a skill download (a zip or tar.* archive) into `dest`. Streams the
459
+ * response body straight to a temp file beside `dest` (so the whole archive is
460
+ * never buffered in memory — skills can contain large binaries), then shells out
461
+ * to `unzip`/`tar` — consistent with the rest of the toolset, which already
462
+ * invokes `bash` and `rg`. Both `unzip` and `tar` must be available on `PATH`; a
463
+ * missing binary surfaces as a clear error (see {@link runArchiveTool}). Refuses
464
+ * any member that would escape `dest` (zip-slip / tar-slip), including
465
+ * symlink/hardlink members: skill archives come from the API, but skills can be
466
+ * third-party.
467
+ *
468
+ * The skill bundle's single wrapper directory is stripped: the archive is
469
+ * extracted into a staging dir and the wrapper's contents are promoted into
470
+ * `dest`, so files land at `dest/SKILL.md` rather than a doubled
471
+ * `dest/<skill>/SKILL.md` (`unzip` has no `--strip-components`, so this is
472
+ * done uniformly by staging + promote rather than per-tool flags).
473
+ */
474
+ async function extractSkillArchive(resp, dest) {
475
+ const tmp = node_path.join(dest, `.skill-archive-${process.pid}-${Date.now()}`);
476
+ if (!resp.body) {
477
+ throw new require_sdk.AnthropicError("skill download response had no body");
478
+ }
479
+ await (0, node_stream_promises.pipeline)(node_stream.Readable.fromWeb(resp.body), node_fs.createWriteStream(tmp));
480
+ const stage = node_path.join(node_path.dirname(dest), `.skill-stage-${process.pid}-${Date.now()}`);
481
+ try {
482
+ const head = await readHead(tmp, 4);
483
+ const isZip = head.length >= 4 && head[0] === 80 && head[1] === 75 && head[2] === 3 && head[3] === 4;
484
+ const archiveCmd = isZip ? "unzip" : "tar";
485
+ const listing = await runArchiveTool(archiveCmd, isZip ? ["-Z1", tmp] : ["-tf", tmp]);
486
+ assertSafeMemberNames(listing);
487
+ assertNoSpecialMembers(await runArchiveTool(archiveCmd, isZip ? ["-Z", tmp] : ["-tvf", tmp]));
488
+ const top = archiveTopDir(listing);
489
+ await node_fs_promises.mkdir(stage, {
490
+ recursive: true,
491
+ mode: 493
492
+ });
493
+ await runArchiveTool(archiveCmd, isZip ? [
494
+ "-oq",
495
+ tmp,
496
+ "-d",
497
+ stage
498
+ ] : [
499
+ "-xf",
500
+ tmp,
501
+ "-C",
502
+ stage
503
+ ]);
504
+ const srcRoot = top ? node_path.join(stage, top) : stage;
505
+ for (const entry of await node_fs_promises.readdir(srcRoot)) {
506
+ await node_fs_promises.rename(node_path.join(srcRoot, entry), node_path.join(dest, entry));
507
+ }
508
+ } finally {
509
+ await node_fs_promises.rm(tmp, { force: true });
510
+ await node_fs_promises.rm(stage, {
511
+ recursive: true,
512
+ force: true
513
+ });
514
+ }
515
+ }
516
+ /** Read the first `n` bytes of `file`. */
517
+ async function readHead(file, n) {
518
+ const handle = await node_fs_promises.open(file, "r");
519
+ try {
520
+ const buf = Buffer.alloc(n);
521
+ const { bytesRead } = await handle.read(buf, 0, n, 0);
522
+ return buf.subarray(0, bytesRead);
523
+ } finally {
524
+ await handle.close();
525
+ }
526
+ }
527
+
528
+ //#endregion
529
+ //#region ../../node_modules/.pnpm/@anthropic-ai+sdk@0.102.0_zod@4.4.3/node_modules/@anthropic-ai/sdk/tools/agent-toolset/node.mjs
530
+ /**
531
+ * Node implementation of the `agent_toolset_20260401` tools — `bash`, `read`,
532
+ * `write`, `edit`, `glob`, `grep` — plus the workdir/skills
533
+ * {@link AgentToolContext}.
534
+ *
535
+ * This mirrors `@anthropic-ai/sdk/tools/memory/node`: it is the explicit,
536
+ * Node-only entry point for these implementations. Importing it pulls in
537
+ * `node:child_process`, `node:fs`, etc., so it is kept separate from the rest of
538
+ * the SDK — depending on it is an opt-in.
539
+ *
540
+ * **Node 22+ is required** for this module: the `glob` tool uses the native
541
+ * `fs.glob`, added in Node 22. The rest of the SDK still supports Node 18+; only
542
+ * the agent toolset has this requirement.
543
+ *
544
+ * The result of {@link betaAgentToolset20260401} is a plain `BetaRunnableTool[]`;
545
+ * hand it to any tool runner — `client.beta.messages.toolRunner({ …, tools })`
546
+ * for the Messages API, or `client.beta.sessions.events.toolRunner({ …, tools })`
547
+ * for a managed-agents session:
548
+ *
549
+ * ```ts
550
+ * import { betaAgentToolset20260401 } from '@anthropic-ai/sdk/tools/agent-toolset/node';
551
+ *
552
+ * const tools = betaAgentToolset20260401({ workdir: '/work' });
553
+ * const tools2 = betaAgentToolset20260401({ workdir: '/work' }).filter((t) => t.name !== 'bash');
554
+ * ```
555
+ *
556
+ * Trust model: the file tools confine to `workdir` (symlink-aware) and are safe
557
+ * without a sandbox; `bash` is unrestricted and should run inside one. See
558
+ * {@link AgentToolContext}.
559
+ */
560
+ var _BashSession_instances;
561
+ var _BashSession_proc;
562
+ var _BashSession_buf;
563
+ var _BashSession_truncated;
564
+ var _BashSession_closed;
565
+ var _BashSession_waiting;
566
+ var _BashSession_append;
567
+ const BASH_OUTPUT_LIMIT = 100 * 1024;
568
+ const BASH_DEFAULT_TIMEOUT_MS = 12e4;
569
+ const DEFAULT_MAX_FILE_BYTES = 256 * 1024;
570
+ const GREP_OUTPUT_LIMIT = 100 * 1024;
571
+ const GREP_MAX_LINE_LENGTH = 2e3;
572
+ const GLOB_RESULT_LIMIT = 200;
573
+ const ANSI_RE = /\x1b\[[0-9;?]*[ -/]*[@-~]/g;
574
+ const fsGlob = node_fs_promises.glob;
575
+ function resolveMaxBytes(configured) {
576
+ return configured === undefined ? DEFAULT_MAX_FILE_BYTES : configured;
577
+ }
578
+ /**
579
+ * Returns the `agent_toolset_20260401` implementations bound to `ctx`. The
580
+ * result is a plain array of `BetaRunnableTool`; filter or extend it before
581
+ * handing it to a tool runner:
582
+ *
583
+ * ```ts
584
+ * const tools = [...betaAgentToolset20260401(ctx), myCustomTool];
585
+ * const tools = betaAgentToolset20260401(ctx).filter((t) => t.name !== 'grep');
586
+ * ```
587
+ *
588
+ * Concurrency note: `client.beta.sessions.events.toolRunner` dispatches a
589
+ * session's tool calls serially (the sessions API delivers one `agent.tool_use`
590
+ * at a time). `client.beta.messages.toolRunner` runs a turn's `tool.run` calls
591
+ * via `Promise.all`. The toolset below is safe under either model —
592
+ * {@link betaBashTool} serializes its persistent shell internally and the FS
593
+ * tools are independent per call — but {@link betaEditTool}/{@link betaWriteTool}
594
+ * cannot synchronize concurrent writes to the *same* file across processes, so a
595
+ * multi-edit turn touching one path is still subject to inherent FS lost-update
596
+ * races. Custom tools that close over mutable state should do their own queueing.
597
+ */
598
+ function betaAgentToolset20260401(ctx) {
599
+ return [
600
+ betaBashTool(ctx),
601
+ betaReadTool(ctx),
602
+ betaWriteTool(ctx),
603
+ betaEditTool(ctx),
604
+ betaGlobTool(ctx),
605
+ betaGrepTool(ctx)
606
+ ];
607
+ }
608
+ /**
609
+ * Resolve `p` relative to `ctx.workdir`. Unless `unrestrictedPaths` is set,
610
+ * absolute inputs are rejected and the **canonical** path is returned — every
611
+ * symlink in `p` (including the leaf, even a dangling one) is resolved before
612
+ * the workdir check, and the resolved path is what the tool then operates on, so
613
+ * a symlink inside the workdir that points outside it can neither pass the check
614
+ * nor be followed afterwards. See the trust model on {@link AgentToolContext}.
615
+ *
616
+ * Residual TOCTOU: a component could still be swapped for a symlink between this
617
+ * call and the eventual `fs` operation. Closing that fully needs per-component
618
+ * `O_NOFOLLOW`/`openat`, which Node does not expose ergonomically; the same
619
+ * residual exposure exists in `tools/memory/node` and is why a sandbox is still
620
+ * recommended for the toolset as a whole.
621
+ */
622
+ function resolvePath(ctx, p) {
623
+ return confineToRoot(ctx.workdir, p, { allowOutside: ctx.unrestrictedPaths ?? false });
624
+ }
625
+ /**
626
+ * Build the environment for the spawned bash shell. The runner process holds
627
+ * Anthropic credentials in `ANTHROPIC_*` env vars — the API key, the auth token,
628
+ * and the per-work session token among them. `bash` runs an unrestricted shell,
629
+ * so any command the agent runs could read those straight out of `process.env`;
630
+ * strip the whole `ANTHROPIC_*` namespace from the child's environment.
631
+ * Everything else (PATH, HOME, locale, …) is passed through unchanged.
632
+ *
633
+ * Passing an explicit `env` to {@link AgentToolContext} does NOT add to this
634
+ * default — it FULLY REPLACES it. The provided mapping becomes the entire bash
635
+ * environment verbatim; nothing here is merged in, so callers who want the
636
+ * scrubbed process environment plus extras must build that mapping themselves.
637
+ */
638
+ function scrubbedShellEnv() {
639
+ const env = {};
640
+ for (const [key, value] of Object.entries(process.env)) {
641
+ if (key.startsWith("ANTHROPIC_")) continue;
642
+ env[key] = value;
643
+ }
644
+ return env;
645
+ }
646
+ /**
647
+ * A persistent /bin/bash process. State (cwd, env, background jobs) survives
648
+ * across exec() calls. Uses pipes rather than a PTY so input is never echoed.
649
+ */
650
+ var BashSession = class {
651
+ constructor(dir, env = scrubbedShellEnv()) {
652
+ _BashSession_instances.add(this);
653
+ _BashSession_proc.set(this, void 0);
654
+ _BashSession_buf.set(this, "");
655
+ _BashSession_truncated.set(this, false);
656
+ _BashSession_closed.set(this, false);
657
+ _BashSession_waiting.set(this, null);
658
+ require_sdk.__classPrivateFieldSet(this, _BashSession_proc, node_child_process.spawn("/bin/bash", ["--noprofile", "--norc"], {
659
+ cwd: dir,
660
+ env: {
661
+ ...env,
662
+ PS1: "",
663
+ PS2: "",
664
+ TERM: "dumb"
665
+ },
666
+ stdio: [
667
+ "pipe",
668
+ "pipe",
669
+ "pipe"
670
+ ],
671
+ detached: true
672
+ }), "f");
673
+ require_sdk.__classPrivateFieldGet(this, _BashSession_proc, "f").stdout.setEncoding("utf8");
674
+ require_sdk.__classPrivateFieldGet(this, _BashSession_proc, "f").stderr.setEncoding("utf8");
675
+ require_sdk.__classPrivateFieldGet(this, _BashSession_proc, "f").stdout.on("data", (d) => require_sdk.__classPrivateFieldGet(this, _BashSession_instances, "m", _BashSession_append).call(this, d));
676
+ require_sdk.__classPrivateFieldGet(this, _BashSession_proc, "f").stderr.on("data", (d) => require_sdk.__classPrivateFieldGet(this, _BashSession_instances, "m", _BashSession_append).call(this, d));
677
+ require_sdk.__classPrivateFieldGet(this, _BashSession_proc, "f").once("close", () => {
678
+ require_sdk.__classPrivateFieldSet(this, _BashSession_closed, true, "f");
679
+ const w = require_sdk.__classPrivateFieldGet(this, _BashSession_waiting, "f");
680
+ require_sdk.__classPrivateFieldSet(this, _BashSession_waiting, null, "f");
681
+ w?.resolve();
682
+ });
683
+ }
684
+ /** Whether the underlying shell process has exited. */
685
+ get closed() {
686
+ return require_sdk.__classPrivateFieldGet(this, _BashSession_closed, "f");
687
+ }
688
+ async exec(command, opts = {}) {
689
+ if (require_sdk.__classPrivateFieldGet(this, _BashSession_closed, "f")) {
690
+ throw new require_sdk.AnthropicError("bash session terminated");
691
+ }
692
+ const timeoutMs = opts.timeoutMs ?? BASH_DEFAULT_TIMEOUT_MS;
693
+ const signal = opts.signal;
694
+ if (signal?.aborted) {
695
+ throw new require_sdk.AnthropicError("bash command aborted");
696
+ }
697
+ require_sdk.__classPrivateFieldSet(this, _BashSession_buf, "", "f");
698
+ require_sdk.__classPrivateFieldSet(this, _BashSession_truncated, false, "f");
699
+ const sentinel = `__ANT_CMD_${node_crypto.randomUUID()}_DONE__`;
700
+ const sentinelSplit = `${sentinel.slice(0, 8)}''${sentinel.slice(8)}`;
701
+ const wrapped = `{ ${command}\n} </dev/null 2>&1; printf '\\n${sentinelSplit}%d\\n' $?\n`;
702
+ require_sdk.__classPrivateFieldGet(this, _BashSession_proc, "f").stdin.write(wrapped);
703
+ if (require_sdk.__classPrivateFieldGet(this, _BashSession_buf, "f").indexOf(sentinel) < 0) {
704
+ const { promise: sentinelSeen, resolve } = require_sdk.promiseWithResolvers();
705
+ require_sdk.__classPrivateFieldSet(this, _BashSession_waiting, {
706
+ sentinel,
707
+ resolve
708
+ }, "f");
709
+ let timer;
710
+ let onAbort;
711
+ try {
712
+ await Promise.race([
713
+ sentinelSeen,
714
+ new Promise((_, reject) => {
715
+ timer = setTimeout(() => reject(new require_sdk.AnthropicError(`bash command timed out after ${timeoutMs}ms`)), timeoutMs);
716
+ }),
717
+ new Promise((_, reject) => {
718
+ if (!signal) return;
719
+ onAbort = () => reject(new require_sdk.AnthropicError("bash command aborted"));
720
+ signal.addEventListener("abort", onAbort, { once: true });
721
+ })
722
+ ]);
723
+ } finally {
724
+ if (timer) clearTimeout(timer);
725
+ if (onAbort && signal) signal.removeEventListener("abort", onAbort);
726
+ require_sdk.__classPrivateFieldSet(this, _BashSession_waiting, null, "f");
727
+ }
728
+ }
729
+ const idx = require_sdk.__classPrivateFieldGet(this, _BashSession_buf, "f").indexOf(sentinel);
730
+ if (idx < 0) {
731
+ throw new require_sdk.AnthropicError("bash session terminated");
732
+ }
733
+ const tail = require_sdk.__classPrivateFieldGet(this, _BashSession_buf, "f").slice(idx + sentinel.length);
734
+ const m = tail.match(/^(-?\d+)/);
735
+ const exitCode = m ? parseInt(m[1], 10) : -1;
736
+ let out = require_sdk.__classPrivateFieldGet(this, _BashSession_buf, "f").slice(0, idx).replace(ANSI_RE, "").replace(/\n+$/, "");
737
+ if (require_sdk.__classPrivateFieldGet(this, _BashSession_truncated, "f")) {
738
+ out = `[output truncated]\n${out}`;
739
+ }
740
+ return {
741
+ output: out,
742
+ exitCode
743
+ };
744
+ }
745
+ close() {
746
+ if (require_sdk.__classPrivateFieldGet(this, _BashSession_closed, "f")) return;
747
+ require_sdk.__classPrivateFieldSet(this, _BashSession_closed, true, "f");
748
+ const w = require_sdk.__classPrivateFieldGet(this, _BashSession_waiting, "f");
749
+ require_sdk.__classPrivateFieldSet(this, _BashSession_waiting, null, "f");
750
+ w?.resolve();
751
+ require_sdk.__classPrivateFieldGet(this, _BashSession_proc, "f").stdout.destroy();
752
+ require_sdk.__classPrivateFieldGet(this, _BashSession_proc, "f").stderr.destroy();
753
+ require_sdk.__classPrivateFieldGet(this, _BashSession_proc, "f").stdin.destroy();
754
+ try {
755
+ process.kill(-require_sdk.__classPrivateFieldGet(this, _BashSession_proc, "f").pid, "SIGKILL");
756
+ } catch {
757
+ require_sdk.__classPrivateFieldGet(this, _BashSession_proc, "f").kill("SIGKILL");
758
+ }
759
+ require_sdk.__classPrivateFieldGet(this, _BashSession_proc, "f").unref();
760
+ }
761
+ };
762
+ _BashSession_proc = new WeakMap(), _BashSession_buf = new WeakMap(), _BashSession_truncated = new WeakMap(), _BashSession_closed = new WeakMap(), _BashSession_waiting = new WeakMap(), _BashSession_instances = new WeakSet(), _BashSession_append = function _BashSession_append(d) {
763
+ require_sdk.__classPrivateFieldSet(this, _BashSession_buf, require_sdk.__classPrivateFieldGet(this, _BashSession_buf, "f") + d, "f");
764
+ if (require_sdk.__classPrivateFieldGet(this, _BashSession_buf, "f").length > BASH_OUTPUT_LIMIT) {
765
+ require_sdk.__classPrivateFieldSet(this, _BashSession_buf, require_sdk.__classPrivateFieldGet(this, _BashSession_buf, "f").slice(require_sdk.__classPrivateFieldGet(this, _BashSession_buf, "f").length - BASH_OUTPUT_LIMIT), "f");
766
+ require_sdk.__classPrivateFieldSet(this, _BashSession_truncated, true, "f");
767
+ }
768
+ if (require_sdk.__classPrivateFieldGet(this, _BashSession_waiting, "f") && require_sdk.__classPrivateFieldGet(this, _BashSession_buf, "f").indexOf(require_sdk.__classPrivateFieldGet(this, _BashSession_waiting, "f").sentinel) >= 0) {
769
+ const w = require_sdk.__classPrivateFieldGet(this, _BashSession_waiting, "f");
770
+ require_sdk.__classPrivateFieldSet(this, _BashSession_waiting, null, "f");
771
+ w.resolve();
772
+ }
773
+ };
774
+ function betaBashTool(ctx) {
775
+ let session;
776
+ let tail = Promise.resolve();
777
+ return betaTool({
778
+ name: "bash",
779
+ description: "Run a bash command in a persistent shell. State (cwd, env vars) persists across calls.",
780
+ inputSchema: {
781
+ type: "object",
782
+ properties: {
783
+ command: {
784
+ type: "string",
785
+ description: "The command to run"
786
+ },
787
+ restart: {
788
+ type: "boolean",
789
+ description: "Restart the persistent shell before running"
790
+ },
791
+ timeout_ms: {
792
+ type: "integer",
793
+ description: "Per-call timeout in milliseconds"
794
+ }
795
+ }
796
+ },
797
+ run: async ({ command, restart, timeout_ms }, context) => {
798
+ const prev = tail;
799
+ const gate = require_sdk.promiseWithResolvers();
800
+ tail = gate.promise;
801
+ try {
802
+ await prev;
803
+ } catch {}
804
+ try {
805
+ if (restart) {
806
+ session?.close();
807
+ session = undefined;
808
+ }
809
+ if (!command) {
810
+ if (restart) return "bash session restarted";
811
+ throw new require_sdk.ToolError("bash: command is required");
812
+ }
813
+ session ?? (session = new BashSession(ctx.workdir, ctx.env));
814
+ try {
815
+ const { output, exitCode } = await session.exec(command, {
816
+ timeoutMs: timeout_ms ?? BASH_DEFAULT_TIMEOUT_MS,
817
+ signal: context?.signal
818
+ });
819
+ if (exitCode !== 0) throw new require_sdk.ToolError(output || `exit ${exitCode}`);
820
+ return output;
821
+ } catch (e) {
822
+ if (e instanceof require_sdk.ToolError) throw e;
823
+ session.close();
824
+ session = undefined;
825
+ throw new require_sdk.ToolError(`bash: ${e instanceof Error ? e.message : String(e)}`);
826
+ }
827
+ } finally {
828
+ gate.resolve();
829
+ }
830
+ },
831
+ close: () => {
832
+ session?.close();
833
+ session = undefined;
834
+ }
835
+ });
836
+ }
837
+ function betaReadTool(ctx) {
838
+ return betaTool({
839
+ name: "read",
840
+ description: "Read a UTF-8 text file relative to the workdir.",
841
+ inputSchema: {
842
+ type: "object",
843
+ properties: {
844
+ file_path: { type: "string" },
845
+ view_range: {
846
+ type: "array",
847
+ items: { type: "integer" },
848
+ description: "[start_line, end_line] 1-indexed inclusive"
849
+ }
850
+ },
851
+ required: ["file_path"]
852
+ },
853
+ run: async ({ file_path, view_range }) => {
854
+ if (!file_path) throw new require_sdk.ToolError("read: file_path is required");
855
+ const abs = await resolvePath(ctx, file_path);
856
+ let data;
857
+ try {
858
+ const st = await node_fs_promises.stat(abs);
859
+ if (!st.isFile()) {
860
+ throw new require_sdk.ToolError(`read: ${file_path} is not a regular file`);
861
+ }
862
+ const limit = resolveMaxBytes(ctx.maxFileBytes);
863
+ if (limit !== null && st.size > limit) {
864
+ throw new require_sdk.ToolError(`read: ${file_path} is ${st.size} bytes, exceeds ${limit}-byte limit. ` + "Use bash (head/tail/sed) to read a slice.");
865
+ }
866
+ data = await node_fs_promises.readFile(abs, "utf8");
867
+ } catch (e) {
868
+ if (e instanceof require_sdk.ToolError) throw e;
869
+ throw new require_sdk.ToolError(`read: ${fsErrorMessage(e, file_path)}`);
870
+ }
871
+ if (!view_range) return data;
872
+ if (view_range.length !== 2) throw new require_sdk.ToolError("read: view_range must be [start_line, end_line]");
873
+ const [startLine, endLine] = view_range;
874
+ const lines = data.split("\n");
875
+ const start = Math.max(0, startLine - 1);
876
+ const end = endLine > 0 ? endLine : lines.length;
877
+ return lines.slice(start, end).join("\n");
878
+ }
879
+ });
880
+ }
881
+ function betaWriteTool(ctx) {
882
+ return betaTool({
883
+ name: "write",
884
+ description: "Write a UTF-8 text file relative to the workdir, creating parent directories as needed.",
885
+ inputSchema: {
886
+ type: "object",
887
+ properties: {
888
+ file_path: { type: "string" },
889
+ content: { type: "string" }
890
+ },
891
+ required: ["file_path", "content"]
892
+ },
893
+ run: async ({ file_path, content }) => {
894
+ if (!file_path) throw new require_sdk.ToolError("write: file_path is required");
895
+ const abs = await resolvePath(ctx, file_path);
896
+ try {
897
+ await node_fs_promises.mkdir(node_path.dirname(abs), {
898
+ recursive: true,
899
+ mode: 493
900
+ });
901
+ await atomicWriteFile(abs, content ?? "");
902
+ } catch (e) {
903
+ throw new require_sdk.ToolError(`write: ${fsErrorMessage(e, file_path)}`);
904
+ }
905
+ return `wrote ${Buffer.byteLength(content ?? "")} bytes to ${file_path}`;
906
+ }
907
+ });
908
+ }
909
+ function betaEditTool(ctx) {
910
+ return betaTool({
911
+ name: "edit",
912
+ description: "Replace old_string with new_string in a file. old_string must be unique unless replace_all.",
913
+ inputSchema: {
914
+ type: "object",
915
+ properties: {
916
+ file_path: { type: "string" },
917
+ old_string: { type: "string" },
918
+ new_string: { type: "string" },
919
+ replace_all: { type: "boolean" }
920
+ },
921
+ required: [
922
+ "file_path",
923
+ "old_string",
924
+ "new_string"
925
+ ]
926
+ },
927
+ run: async ({ file_path, old_string, new_string, replace_all }) => {
928
+ if (!file_path) throw new require_sdk.ToolError("edit: file_path is required");
929
+ if (!old_string) throw new require_sdk.ToolError("edit: old_string is required");
930
+ const abs = await resolvePath(ctx, file_path);
931
+ let data;
932
+ try {
933
+ const st = await node_fs_promises.stat(abs);
934
+ if (!st.isFile()) {
935
+ throw new require_sdk.ToolError(`edit: ${file_path} is not a regular file`);
936
+ }
937
+ const limit = resolveMaxBytes(ctx.maxFileBytes);
938
+ if (limit !== null && st.size > limit) {
939
+ throw new require_sdk.ToolError(`edit: ${file_path} is ${st.size} bytes, exceeds ${limit}-byte limit. ` + "Use bash (sed/awk) to edit a large file.");
940
+ }
941
+ data = await node_fs_promises.readFile(abs, "utf8");
942
+ } catch (e) {
943
+ if (e instanceof require_sdk.ToolError) throw e;
944
+ throw new require_sdk.ToolError(`edit: ${fsErrorMessage(e, file_path)}`);
945
+ }
946
+ const count = data.split(old_string).length - 1;
947
+ if (count === 0) throw new require_sdk.ToolError(`edit: old_string not found in ${file_path}`);
948
+ let updated;
949
+ if (replace_all) {
950
+ updated = data.split(old_string).join(new_string);
951
+ } else {
952
+ if (count > 1) throw new require_sdk.ToolError(`edit: old_string appears ${count} times in ${file_path} (must be unique)`);
953
+ updated = data.replace(old_string, () => new_string);
954
+ }
955
+ try {
956
+ await atomicWriteFile(abs, updated);
957
+ } catch (e) {
958
+ throw new require_sdk.ToolError(`edit: write: ${fsErrorMessage(e, file_path)}`);
959
+ }
960
+ return `edited ${file_path} (${replace_all ? count : 1} replacement(s))`;
961
+ }
962
+ });
963
+ }
964
+ function betaGlobTool(ctx) {
965
+ return betaTool({
966
+ name: "glob",
967
+ description: "Match files under the workdir against a glob pattern. Results are mtime-sorted, newest first.",
968
+ inputSchema: {
969
+ type: "object",
970
+ properties: {
971
+ pattern: { type: "string" },
972
+ path: {
973
+ type: "string",
974
+ description: "Directory to search in. Defaults to the workdir."
975
+ }
976
+ },
977
+ required: ["pattern"]
978
+ },
979
+ run: async ({ pattern, path: searchPath }) => {
980
+ if (!pattern) throw new require_sdk.ToolError("glob: pattern is required");
981
+ let root = node_path.resolve(ctx.workdir);
982
+ let pat = pattern;
983
+ if (node_path.isAbsolute(pattern)) {
984
+ if (!ctx.unrestrictedPaths) throw new require_sdk.ToolError("glob: absolute pattern not permitted");
985
+ root = node_path.parse(pattern).root;
986
+ pat = node_path.relative(root, pattern);
987
+ } else if (searchPath) {
988
+ root = await resolvePath(ctx, searchPath);
989
+ }
990
+ if (!ctx.unrestrictedPaths && pat.split(/[\\/]/).includes("..")) {
991
+ throw new require_sdk.ToolError("glob: \"..\" is not permitted in the pattern");
992
+ }
993
+ const matches = [];
994
+ try {
995
+ for await (const entry of fsGlob(pat, {
996
+ cwd: root,
997
+ withFileTypes: true,
998
+ exclude: (d) => d.name === ".git" || d.name === "node_modules"
999
+ })) {
1000
+ if (!entry.isFile()) continue;
1001
+ const full = node_path.join(entry.parentPath, entry.name);
1002
+ if (!ctx.unrestrictedPaths && !isWithin(root, full)) continue;
1003
+ let mtime = 0;
1004
+ try {
1005
+ mtime = (await node_fs_promises.stat(full)).mtimeMs;
1006
+ } catch {}
1007
+ matches.push({
1008
+ path: full,
1009
+ mtime
1010
+ });
1011
+ }
1012
+ } catch (e) {
1013
+ throw new require_sdk.ToolError(`glob: ${e instanceof Error ? e.message : String(e)}`);
1014
+ }
1015
+ if (matches.length === 0) return "no matches";
1016
+ matches.sort((a, b) => b.mtime - a.mtime);
1017
+ return matches.slice(0, GLOB_RESULT_LIMIT).map((m) => m.path).join("\n");
1018
+ }
1019
+ });
1020
+ }
1021
+ function betaGrepTool(ctx) {
1022
+ return betaTool({
1023
+ name: "grep",
1024
+ description: "Search file contents for a regex. Uses ripgrep if available, otherwise a built-in walker.",
1025
+ inputSchema: {
1026
+ type: "object",
1027
+ properties: {
1028
+ pattern: { type: "string" },
1029
+ path: { type: "string" }
1030
+ },
1031
+ required: ["pattern"]
1032
+ },
1033
+ run: async ({ pattern, path: p }, context) => {
1034
+ if (!pattern) throw new require_sdk.ToolError("grep: pattern is required");
1035
+ let searchPath = node_path.resolve(ctx.workdir);
1036
+ if (p) searchPath = await resolvePath(ctx, p);
1037
+ const rg = await findRg();
1038
+ return rg ? runRipgrep(rg, pattern, searchPath, context?.signal) : runWalkGrep(pattern, searchPath, context?.signal);
1039
+ }
1040
+ });
1041
+ }
1042
+ function runRipgrep(rg, pattern, searchPath, signal) {
1043
+ return new Promise((resolve, reject) => {
1044
+ const proc = node_child_process.spawn(rg, [
1045
+ "-n",
1046
+ "--no-heading",
1047
+ "-e",
1048
+ pattern,
1049
+ "--",
1050
+ searchPath
1051
+ ], { ...signal ? { signal } : {} });
1052
+ let out = "";
1053
+ let errOut = "";
1054
+ let truncated = false;
1055
+ proc.stdout.on("data", (d) => {
1056
+ if (truncated) return;
1057
+ out += d;
1058
+ if (out.length > GREP_OUTPUT_LIMIT) {
1059
+ truncated = true;
1060
+ out = out.slice(0, GREP_OUTPUT_LIMIT);
1061
+ proc.kill("SIGKILL");
1062
+ }
1063
+ });
1064
+ proc.stderr.on("data", (d) => errOut += d);
1065
+ proc.on("close", (code) => {
1066
+ if (signal?.aborted) return reject(new require_sdk.ToolError("grep: aborted"));
1067
+ if (truncated) return resolve(out + `\n[output truncated at ${GREP_OUTPUT_LIMIT} bytes]`);
1068
+ if (code === 0) return resolve(out);
1069
+ if (code === 1) return resolve("no matches");
1070
+ reject(new require_sdk.ToolError(`grep: rg failed: ${errOut || `exit ${code}`}`));
1071
+ });
1072
+ proc.on("error", (e) => {
1073
+ if (signal?.aborted) return reject(new require_sdk.ToolError("grep: aborted"));
1074
+ reject(new require_sdk.ToolError(`grep: rg failed: ${e.message}`));
1075
+ });
1076
+ });
1077
+ }
1078
+ async function runWalkGrep(pattern, root, signal) {
1079
+ let re;
1080
+ try {
1081
+ re = new RegExp(pattern);
1082
+ } catch (e) {
1083
+ throw new require_sdk.ToolError(`grep: invalid regex: ${e instanceof Error ? e.message : String(e)}`);
1084
+ }
1085
+ const hits = [];
1086
+ let budget = GREP_OUTPUT_LIMIT;
1087
+ const push = (line) => {
1088
+ budget -= line.length + 1;
1089
+ if (budget < 0) {
1090
+ hits.push(`[output truncated at ${GREP_OUTPUT_LIMIT} bytes]`);
1091
+ return false;
1092
+ }
1093
+ hits.push(line);
1094
+ return true;
1095
+ };
1096
+ const stat = await node_fs_promises.stat(root).catch(() => null);
1097
+ if (stat?.isFile()) {
1098
+ await grepFile(root, re, push);
1099
+ } else {
1100
+ await walk(root, "", (rel) => grepFile(node_path.join(root, rel), re, push), signal);
1101
+ }
1102
+ if (signal?.aborted) throw new require_sdk.ToolError("grep: aborted");
1103
+ if (hits.length === 0) return "no matches";
1104
+ return hits.join("\n");
1105
+ }
1106
+ async function grepFile(file, re, push) {
1107
+ const stream = node_fs.createReadStream(file, { encoding: "utf8" });
1108
+ const rl = node_readline.createInterface({
1109
+ input: stream,
1110
+ crlfDelay: Infinity
1111
+ });
1112
+ let i = 0;
1113
+ try {
1114
+ for await (const line of rl) {
1115
+ i++;
1116
+ if (line.length > GREP_MAX_LINE_LENGTH) continue;
1117
+ if (re.test(line) && !push(`${file}:${i}:${line}`)) return false;
1118
+ }
1119
+ } catch {} finally {
1120
+ stream.destroy();
1121
+ }
1122
+ return true;
1123
+ }
1124
+ /** True when `p` is `root` itself or lexically contained within it. */
1125
+ function isWithin(root, p) {
1126
+ const rel = node_path.relative(root, p);
1127
+ return rel === "" || !rel.startsWith(".." + node_path.sep) && rel !== ".." && !node_path.isAbsolute(rel);
1128
+ }
1129
+ const WALK_MAX_DEPTH = 40;
1130
+ const WALK_MAX_ENTRIES = 5e4;
1131
+ /**
1132
+ * Bounded recursive walk. `fn` may return `false` to abort. Only real
1133
+ * directories are descended into and only real files are handed to `fn` —
1134
+ * symlinks (and devices/fifos/sockets) are skipped entirely so a symlink inside
1135
+ * the root cannot be followed out of it.
1136
+ */
1137
+ async function walk(root, rel, fn, signal) {
1138
+ let remaining = WALK_MAX_ENTRIES;
1139
+ async function inner(rel, depth) {
1140
+ if (depth > WALK_MAX_DEPTH) return true;
1141
+ if (signal?.aborted) return false;
1142
+ let entries;
1143
+ try {
1144
+ entries = await node_fs_promises.readdir(node_path.join(root, rel), { withFileTypes: true });
1145
+ } catch {
1146
+ return true;
1147
+ }
1148
+ for (const e of entries) {
1149
+ if (e.name === ".git" || e.name === "node_modules") continue;
1150
+ if (remaining-- <= 0) return false;
1151
+ if (signal?.aborted) return false;
1152
+ const childRel = rel ? node_path.join(rel, e.name) : e.name;
1153
+ if (e.isDirectory()) {
1154
+ if (!await inner(childRel, depth + 1)) return false;
1155
+ } else if (e.isFile()) {
1156
+ if (await fn(childRel) === false) return false;
1157
+ }
1158
+ }
1159
+ return true;
1160
+ }
1161
+ await inner(rel, 0);
1162
+ }
1163
+ async function findRg() {
1164
+ const dirs = (process.env["PATH"] ?? "").split(node_path.delimiter);
1165
+ for (const d of dirs) {
1166
+ const candidate = node_path.join(d, "rg");
1167
+ try {
1168
+ await node_fs_promises.access(candidate, node_fs.constants.X_OK);
1169
+ return candidate;
1170
+ } catch {}
1171
+ }
1172
+ return null;
1173
+ }
1174
+
1175
+ //#endregion
1176
+ exports.betaAgentToolset20260401 = betaAgentToolset20260401;
1177
+ exports.setupSkills = setupSkills;