@junando/webhook 0.12.2 → 0.13.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,2703 @@
1
+ const require_rolldown_runtime = require('./rolldown-runtime-BDXX5GFZ.cjs');
2
+ const require_sdk = require('./sdk-DKBOmAd5.cjs');
3
+ require('./node-CaH-Nh2c.cjs');
4
+ let node_crypto = require("node:crypto");
5
+ node_crypto = require_rolldown_runtime.__toESM(node_crypto, 1);
6
+ let node_fs = require("node:fs");
7
+ node_fs = require_rolldown_runtime.__toESM(node_fs, 1);
8
+ let node_path = require("node:path");
9
+ node_path = require_rolldown_runtime.__toESM(node_path, 1);
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_readline = require("node:readline");
15
+ node_readline = require_rolldown_runtime.__toESM(node_readline, 1);
16
+ let node_util = require("node:util");
17
+ node_util = require_rolldown_runtime.__toESM(node_util, 1);
18
+ let node_stream = require("node:stream");
19
+ node_stream = require_rolldown_runtime.__toESM(node_stream, 1);
20
+
21
+ //#region ../../node_modules/.pnpm/@anthropic-ai+sdk@0.124.0_zod@4.5.4/node_modules/@anthropic-ai/sdk/lib/transform-json-schema.mjs
22
+ const SUPPORTED_STRING_FORMATS = new Set([
23
+ "date-time",
24
+ "time",
25
+ "date",
26
+ "duration",
27
+ "email",
28
+ "hostname",
29
+ "uri",
30
+ "ipv4",
31
+ "ipv6",
32
+ "uuid"
33
+ ]);
34
+ function deepClone(obj) {
35
+ return JSON.parse(JSON.stringify(obj));
36
+ }
37
+ function transformJSONSchema(jsonSchema) {
38
+ const workingCopy = deepClone(jsonSchema);
39
+ return _transformJSONSchema(workingCopy);
40
+ }
41
+ function _transformJSONSchema(jsonSchema) {
42
+ const strictSchema = {};
43
+ const ref = require_sdk.pop(jsonSchema, "$ref");
44
+ if (ref !== undefined) {
45
+ strictSchema["$ref"] = ref;
46
+ return strictSchema;
47
+ }
48
+ const defs = require_sdk.pop(jsonSchema, "$defs");
49
+ if (defs !== undefined) {
50
+ const strictDefs = {};
51
+ strictSchema["$defs"] = strictDefs;
52
+ for (const [name, defSchema] of Object.entries(defs)) {
53
+ strictDefs[name] = _transformJSONSchema(defSchema);
54
+ }
55
+ }
56
+ const type = require_sdk.pop(jsonSchema, "type");
57
+ const anyOf = require_sdk.pop(jsonSchema, "anyOf");
58
+ const oneOf = require_sdk.pop(jsonSchema, "oneOf");
59
+ const allOf = require_sdk.pop(jsonSchema, "allOf");
60
+ if (Array.isArray(anyOf)) {
61
+ strictSchema["anyOf"] = anyOf.map((variant) => _transformJSONSchema(variant));
62
+ } else if (Array.isArray(oneOf)) {
63
+ strictSchema["anyOf"] = oneOf.map((variant) => _transformJSONSchema(variant));
64
+ } else if (Array.isArray(allOf)) {
65
+ strictSchema["allOf"] = allOf.map((entry) => _transformJSONSchema(entry));
66
+ } else {
67
+ if (type === undefined) {
68
+ throw new Error("JSON schema must have a type defined if anyOf/oneOf/allOf are not used");
69
+ }
70
+ strictSchema["type"] = type;
71
+ }
72
+ const description = require_sdk.pop(jsonSchema, "description");
73
+ if (description !== undefined) {
74
+ strictSchema["description"] = description;
75
+ }
76
+ const title = require_sdk.pop(jsonSchema, "title");
77
+ if (title !== undefined) {
78
+ strictSchema["title"] = title;
79
+ }
80
+ if (type === "object") {
81
+ const properties = require_sdk.pop(jsonSchema, "properties") || {};
82
+ strictSchema["properties"] = Object.fromEntries(Object.entries(properties).map(([key, propSchema]) => [key, _transformJSONSchema(propSchema)]));
83
+ require_sdk.pop(jsonSchema, "additionalProperties");
84
+ strictSchema["additionalProperties"] = false;
85
+ const required = require_sdk.pop(jsonSchema, "required");
86
+ if (required !== undefined) {
87
+ strictSchema["required"] = required;
88
+ }
89
+ } else if (type === "string") {
90
+ const format = require_sdk.pop(jsonSchema, "format");
91
+ if (format !== undefined && SUPPORTED_STRING_FORMATS.has(format)) {
92
+ strictSchema["format"] = format;
93
+ } else if (format !== undefined) {
94
+ jsonSchema["format"] = format;
95
+ }
96
+ } else if (type === "array") {
97
+ const items = require_sdk.pop(jsonSchema, "items");
98
+ if (items !== undefined) {
99
+ strictSchema["items"] = _transformJSONSchema(items);
100
+ }
101
+ const minItems = require_sdk.pop(jsonSchema, "minItems");
102
+ if (minItems !== undefined && (minItems === 0 || minItems === 1)) {
103
+ strictSchema["minItems"] = minItems;
104
+ } else if (minItems !== undefined) {
105
+ jsonSchema["minItems"] = minItems;
106
+ }
107
+ }
108
+ if (Object.keys(jsonSchema).length > 0) {
109
+ const existingDescription = strictSchema["description"];
110
+ strictSchema["description"] = (existingDescription ? existingDescription + "\n\n" : "") + "{" + Object.entries(jsonSchema).map(([key, value]) => `${key}: ${JSON.stringify(value)}`).join(", ") + "}";
111
+ }
112
+ return strictSchema;
113
+ }
114
+
115
+ //#endregion
116
+ //#region ../../node_modules/.pnpm/@anthropic-ai+sdk@0.124.0_zod@4.5.4/node_modules/@anthropic-ai/sdk/helpers/beta/json-schema.mjs
117
+ /**
118
+ * Creates a Tool with a provided JSON schema that can be passed
119
+ * to the `.toolRunner()` method. The schema is used to automatically validate
120
+ * the input arguments for the tool.
121
+ */
122
+ function betaTool(options) {
123
+ if (options.inputSchema.type !== "object") {
124
+ throw new Error(`JSON schema for tool "${options.name}" must be an object, but got ${options.inputSchema.type}`);
125
+ }
126
+ return {
127
+ type: "custom",
128
+ name: options.name,
129
+ input_schema: options.inputSchema,
130
+ description: options.description,
131
+ run: options.run,
132
+ parse: (content) => content,
133
+ ...options.close ? { close: options.close } : {}
134
+ };
135
+ }
136
+ /**
137
+ * Creates a JSON schema output format object from the given JSON schema.
138
+ * If this is passed to the `.parse()` method then the response message will contain a
139
+ * `.parsed_output` property that is the result of parsing the content with the given JSON schema.
140
+ *
141
+ */
142
+ function betaJSONSchemaOutputFormat(jsonSchema, options) {
143
+ if (jsonSchema.type !== "object") {
144
+ throw new Error(`JSON schema for tool must be an object, but got ${jsonSchema.type}`);
145
+ }
146
+ const transform = options?.transform ?? true;
147
+ if (transform) {
148
+ jsonSchema = transformJSONSchema(jsonSchema);
149
+ }
150
+ return {
151
+ type: "json_schema",
152
+ schema: { ...jsonSchema },
153
+ parse: (content) => {
154
+ try {
155
+ return JSON.parse(content);
156
+ } catch (error) {
157
+ throw new require_sdk.AnthropicError(`Failed to parse structured output: ${error}`);
158
+ }
159
+ }
160
+ };
161
+ }
162
+
163
+ //#endregion
164
+ //#region ../../node_modules/.pnpm/@anthropic-ai+sdk@0.124.0_zod@4.5.4/node_modules/@anthropic-ai/sdk/tools/agent-toolset/fs-util.mjs
165
+ /**
166
+ * Shared, Node-only filesystem helpers for the agent toolset's file tools:
167
+ * path confinement (symlink-aware), an atomic write, and language-independent
168
+ * error messages. Kept out of `node.ts` so the tool implementations stay focused
169
+ * and these helpers can be reused by every file tool.
170
+ */
171
+ const fs$1 = node_fs.promises;
172
+ /** Mode for directories the file tools create: owner-only under any umask, like the memory tool. */
173
+ const DIR_CREATE_MODE = 448;
174
+ /**
175
+ * Mode for files the file tools create: owner-only, so other local users can't
176
+ * read what an agent wrote. A file that already exists keeps its own mode.
177
+ */
178
+ const FILE_CREATE_MODE = 384;
179
+ /** True when `p` is `root` itself or lexically contained within it. */
180
+ function isWithin(root, p) {
181
+ const rel = node_path.relative(root, p);
182
+ return rel === "" || !rel.startsWith(".." + node_path.sep) && rel !== ".." && !node_path.isAbsolute(rel);
183
+ }
184
+ /**
185
+ * The first entry of `roots` whose canonical form contains the
186
+ * already-canonical `target`, returned as configured; `undefined` when none
187
+ * does. Each root goes through {@link canonicalize} at check time, exactly like
188
+ * the workdir in {@link confineToRoot}, so granting access (`allowedRoots`)
189
+ * and refusing writes (`readOnlyRoots`) can never resolve the same entry two
190
+ * different ways.
191
+ */
192
+ async function containingRoot(roots, target) {
193
+ for (const root of roots) {
194
+ if (isWithin(await canonicalize(node_path.resolve(root)), target)) return root;
195
+ }
196
+ return undefined;
197
+ }
198
+ /** Matches Linux MAXSYMLINKS, the threshold at which `realpath` itself reports ELOOP. */
199
+ const MAX_SYMLINK_HOPS = 40;
200
+ /** The `code` of a Node system error, or `undefined` for anything else. */
201
+ function errnoCode(err) {
202
+ const code = err?.code;
203
+ return typeof code === "string" ? code : undefined;
204
+ }
205
+ /**
206
+ * Fully resolve `abs`: `realpath` the longest existing ancestor and re-append
207
+ * the rest, but never re-append a component that is itself a symlink — read the
208
+ * link and continue from its target instead. This handles paths being created
209
+ * (write/edit) without letting a symlink leaf (e.g. a dangling one pointing
210
+ * outside a confinement root) slip through unresolved.
211
+ *
212
+ * Returns a symlink-free path or throws an errno-carrying error (`ELOOP` for a
213
+ * cycle or more than {@link MAX_SYMLINK_HOPS} links, the `lstat`/`realpath`
214
+ * error for an unreadable component); it never returns `abs` unresolved. Only
215
+ * symlink hops count against the cap, so any depth of not-yet-existing
216
+ * directories still resolves.
217
+ */
218
+ async function canonicalize(abs) {
219
+ const tail = [];
220
+ let prefix = abs;
221
+ let hops = 0;
222
+ for (;;) {
223
+ let real;
224
+ try {
225
+ real = await fs$1.realpath(prefix);
226
+ } catch (realpathErr) {
227
+ let isLink;
228
+ try {
229
+ isLink = (await fs$1.lstat(prefix)).isSymbolicLink();
230
+ } catch (lstatErr) {
231
+ const code = errnoCode(lstatErr);
232
+ if (code !== "ENOENT" && code !== "ENOTDIR") throw lstatErr;
233
+ const parent = node_path.dirname(prefix);
234
+ if (parent === prefix) throw lstatErr;
235
+ tail.push(node_path.basename(prefix));
236
+ prefix = parent;
237
+ continue;
238
+ }
239
+ if (!isLink) throw realpathErr;
240
+ if (++hops > MAX_SYMLINK_HOPS) {
241
+ throw Object.assign(new Error("too many levels of symbolic links"), { code: "ELOOP" });
242
+ }
243
+ prefix = node_path.resolve(node_path.dirname(prefix), await fs$1.readlink(prefix));
244
+ continue;
245
+ }
246
+ return tail.length ? node_path.join(real, ...tail.reverse()) : real;
247
+ }
248
+ }
249
+ /**
250
+ * Resolve `p` against `root` and confine it to `root` or one of `allowedRoots`
251
+ * (absolute paths, resolved at check time exactly like `root`).
252
+ *
253
+ * Absolute and relative inputs go through the same canonicalise-then-contain
254
+ * check — an absolute path that lands inside a permitted root is accepted,
255
+ * only paths that resolve *outside* all of them are rejected. Every symlink in
256
+ * `p` (including the leaf, even a dangling one) is resolved before the
257
+ * confinement check, and the resolved path is what the caller then operates
258
+ * on, so a symlink inside `root` that points outside it can neither pass the
259
+ * check nor be followed afterwards. `..` is collapsed lexically before any
260
+ * symlink is followed. A path that cannot be resolved (symlink loop, unreadable
261
+ * component) is rejected with a `ToolError` naming `p`, never the host's
262
+ * absolute path.
263
+ *
264
+ * Residual TOCTOU: a component could still be swapped for a symlink between this
265
+ * call and the eventual `fs` operation. Closing that fully needs per-component
266
+ * `O_NOFOLLOW`/`openat`, which Node does not expose ergonomically; this is why a
267
+ * sandbox is still recommended for the toolset as a whole.
268
+ */
269
+ async function confineToRoot(root, p, opts) {
270
+ const allowedRoots = opts?.allowedRoots ?? [];
271
+ const realRoot = await canonicalize(node_path.resolve(root));
272
+ let real;
273
+ try {
274
+ real = await canonicalize(node_path.resolve(realRoot, p));
275
+ } catch (err) {
276
+ throw new require_sdk.ToolError(fsErrorMessage(err, `path ${JSON.stringify(p)}`));
277
+ }
278
+ if (isWithin(realRoot, real) || await containingRoot(allowedRoots, real) !== undefined) {
279
+ return real;
280
+ }
281
+ const permitted = allowedRoots.length ? "the session's working directory and its other permitted directories" : "the session's working directory";
282
+ throw new require_sdk.ToolError(`path ${JSON.stringify(p)} is outside ${permitted}`);
283
+ }
284
+ /**
285
+ * Atomically write `content` to `targetPath`: write a sibling temp file, fsync
286
+ * it, then rename over the target. The rename is atomic on most filesystems, so
287
+ * a crash mid-write never leaves the target half-written.
288
+ *
289
+ * A new file is created {@link FILE_CREATE_MODE}; an existing one keeps its
290
+ * permission bits (the rename replaces the inode, so they are copied onto the
291
+ * temp file first) — an edit must not strip `+x` or sharing the owner chose.
292
+ */
293
+ async function atomicWriteFile(targetPath, content) {
294
+ const dir = node_path.dirname(targetPath);
295
+ const tempPath = node_path.join(dir, `.tmp-${process.pid}-${node_crypto.randomUUID()}`);
296
+ const existingMode = await fs$1.stat(targetPath).then((st) => st.mode & 511, () => undefined);
297
+ let handle;
298
+ try {
299
+ handle = await fs$1.open(tempPath, "wx", 384);
300
+ if (existingMode !== undefined) await handle.chmod(existingMode);
301
+ await handle.writeFile(content, "utf-8");
302
+ await handle.sync();
303
+ await handle.close();
304
+ handle = undefined;
305
+ await fs$1.rename(tempPath, targetPath);
306
+ } catch (err) {
307
+ if (handle) await handle.close().catch(() => {});
308
+ await fs$1.unlink(tempPath).catch(() => {});
309
+ throw err;
310
+ }
311
+ }
312
+ /**
313
+ * Map a thrown filesystem error to a consistent, language-independent message,
314
+ * so the model sees the same wording regardless of the runtime (Node's raw
315
+ * `ENOENT: no such file...` text would otherwise leak through). Codes we don't
316
+ * special-case render as the bare code, never Node's message, which embeds the
317
+ * host's absolute path.
318
+ */
319
+ function fsErrorMessage(err, file) {
320
+ const code = errnoCode(err);
321
+ switch (code) {
322
+ case "ENOENT": return `${file}: no such file or directory`;
323
+ case "EACCES":
324
+ case "EPERM": return `${file}: permission denied`;
325
+ case "ENOTDIR": return `${file}: not a directory`;
326
+ case "EISDIR": return `${file}: is a directory`;
327
+ case "ELOOP": return `${file}: too many levels of symbolic links`;
328
+ case "ENAMETOOLONG": return `${file}: file name too long`;
329
+ case "ENOSPC": return `${file}: no space left on device`;
330
+ case "EMFILE":
331
+ case "ENFILE": return `${file}: too many open files`;
332
+ default: return `${file}: ${code !== undefined ? `i/o error (${code})` : "i/o error"}`;
333
+ }
334
+ }
335
+
336
+ //#endregion
337
+ //#region ../../node_modules/.pnpm/@anthropic-ai+sdk@0.124.0_zod@4.5.4/node_modules/@anthropic-ai/sdk/tools/agent-toolset/skills.mjs
338
+ /**
339
+ * Node-only skill plumbing for the agent toolset: downloading a session
340
+ * agent's skills into the workdir and extracting the archives. Kept in its own
341
+ * file because it is a distinct concern from the tool implementations in
342
+ * `node.ts` — distinct enough, and large enough, to review on its own.
343
+ */
344
+ const fs = node_fs.promises;
345
+ const execFileAsync = node_util.promisify(node_child_process.execFile);
346
+ /**
347
+ * Download the session agent's skills into `{ctx.workdir}/skills/<name>/`.
348
+ *
349
+ * No-op (returns a no-op cleanup) unless `ctx.client` is set together with
350
+ * `ctx.session` (or the deprecated `ctx.sessionId`). Reads the resolved agent
351
+ * off the session and, for each skill, fetches its files via
352
+ * `client.beta.skills.versions.download` and extracts the archive (a zip or
353
+ * tar.* archive) into a directory named after the skill. A failure on one skill
354
+ * is logged and does not block the others. Call this before starting the
355
+ * session tool runner (e.g. right after the bash session / workdir is ready).
356
+ *
357
+ * Pass `ctx.session`. A session's resources cannot change while it runs, so the
358
+ * caller fetches it once and shares that snapshot with the memory-store
359
+ * download — the two can then never disagree about the attached resources.
360
+ *
361
+ * `ctx.sessionId` is deprecated: it costs an extra `sessions.retrieve` round
362
+ * trip on every call, and a caller that uses it for both this and the
363
+ * memory-store download fetches the session twice. It remains supported for
364
+ * callers written before `session` existed.
365
+ *
366
+ * Returns a cleanup function that removes the skill directories this call
367
+ * created — call it once the work item is done so downloaded skills do not
368
+ * accumulate in the workdir across sessions.
369
+ */
370
+ async function setupSkills(ctx) {
371
+ const { client, sessionId } = ctx;
372
+ if (!client) return async () => {};
373
+ const log = require_sdk.loggerFor(client);
374
+ let session = ctx.session;
375
+ if (!session) {
376
+ if (sessionId === undefined) return async () => {};
377
+ log.warn("AgentToolContext.sessionId is deprecated and costs an extra session fetch; " + "fetch the session once and set `session` instead", { component: "agent-tool-context" });
378
+ session = await client.beta.sessions.retrieve(sessionId);
379
+ }
380
+ const skillsRoot = node_path.resolve(ctx.workdir, "skills");
381
+ const created = [];
382
+ for (const skill of session.agent.skills) {
383
+ try {
384
+ const version = await client.beta.skills.versions.retrieve(skill.version, { skill_id: skill.skill_id });
385
+ let dirname = node_path.basename(version.name.trim());
386
+ if (dirname === "" || dirname === "." || dirname === "..") dirname = skill.skill_id;
387
+ const dest = node_path.resolve(skillsRoot, dirname);
388
+ if (dest !== skillsRoot && !dest.startsWith(skillsRoot + node_path.sep)) {
389
+ log.warn("skill name escapes the skills dir; skipping", {
390
+ component: "agent-tool-context",
391
+ name: version.name
392
+ });
393
+ continue;
394
+ }
395
+ const resp = await client.beta.skills.versions.download(version.id, { skill_id: skill.skill_id });
396
+ await fs.rm(dest, {
397
+ recursive: true,
398
+ force: true
399
+ });
400
+ await fs.mkdir(dest, {
401
+ recursive: true,
402
+ mode: 448
403
+ });
404
+ created.push(dest);
405
+ await extractSkillArchive(resp, dest);
406
+ log.info("downloaded skill", {
407
+ component: "agent-tool-context",
408
+ skill_id: skill.skill_id,
409
+ version: version.id,
410
+ dest
411
+ });
412
+ } catch (e) {
413
+ log.warn("failed to download skill", {
414
+ component: "agent-tool-context",
415
+ skill_id: skill.skill_id,
416
+ error: String(e)
417
+ });
418
+ }
419
+ }
420
+ return async () => {
421
+ for (const dest of created) {
422
+ await fs.rm(dest, {
423
+ recursive: true,
424
+ force: true
425
+ }).catch((e) => {
426
+ log.warn("failed to clean up skill", {
427
+ component: "agent-tool-context",
428
+ dest,
429
+ error: String(e)
430
+ });
431
+ });
432
+ }
433
+ };
434
+ }
435
+ /** Reject archive members that are absolute or contain a `..` component. */
436
+ function assertSafeMemberNames(names) {
437
+ for (const raw of names) {
438
+ const entry = raw.trim();
439
+ if (!entry) continue;
440
+ if (node_path.isAbsolute(entry) || entry.split(/[\\/]/).includes("..")) {
441
+ throw new require_sdk.AnthropicError(`refusing to extract unsafe archive member: ${entry}`);
442
+ }
443
+ }
444
+ }
445
+ const INCONSISTENT_LISTING = "skill archive listing is inconsistent; refusing to extract";
446
+ /**
447
+ * Type chars (first byte of each `ls`-style line from `unzip -Z` / `tar -tvf`)
448
+ * that denote a regular file or directory. `zipinfo` prints `?` for entries
449
+ * with no Unix type bits, which `unzip` extracts as regular files; GNU tar
450
+ * prints `C` for contiguous files. Everything else — `l` symlink, `h`
451
+ * hardlink, `b`/`c` device, `p` fifo, `s` socket, unknown tar types — is a
452
+ * special member.
453
+ */
454
+ const PLAIN_TYPE_CHARS = {
455
+ unzip: new Set([
456
+ "-",
457
+ "d",
458
+ "?"
459
+ ]),
460
+ tar: new Set([
461
+ "-",
462
+ "d",
463
+ "C"
464
+ ])
465
+ };
466
+ function listingLines(listing) {
467
+ const lines = listing.split("\n");
468
+ if (lines[lines.length - 1] === "") lines.pop();
469
+ return lines;
470
+ }
471
+ /**
472
+ * A special member is excluded by handing its listed name back to the CLI as
473
+ * a pattern, so the name must be byte-identical to what is stored. `tar`,
474
+ * `bsdtar` and `unzip` print bytes they cannot show literally as `\ooo`, `^X`
475
+ * or `#U` escapes, or as raw non-ASCII; any such name cannot be excluded
476
+ * reliably. A leading `-` would let `unzip` parse the pattern as an option.
477
+ */
478
+ function canExcludeVerbatim(cmd, name) {
479
+ return /^[\x20-\x7E]+$/.test(name) && !/[\\^#]/.test(name) && !(cmd === "unzip" && name.startsWith("-"));
480
+ }
481
+ /**
482
+ * Pair an archive's name listing (`unzip -Z1` / `tar -tf`) with its typed
483
+ * listing (`unzip -Z --h --t` / `tar -tvf`) and split the members into plain
484
+ * (regular file or directory) and special (everything else). Special members
485
+ * are excluded from extraction rather than rejected; the archive is refused
486
+ * only when the two listings disagree in length or a special member's name
487
+ * cannot be passed back to the CLI verbatim (see {@link canExcludeVerbatim}).
488
+ */
489
+ function classifyArchiveListing(cmd, names, typed) {
490
+ const nameLines = listingLines(names);
491
+ const typedLines = listingLines(typed);
492
+ if (nameLines.length !== typedLines.length) throw new require_sdk.AnthropicError(INCONSISTENT_LISTING);
493
+ const plain = [];
494
+ const special = [];
495
+ nameLines.forEach((name, i) => {
496
+ if (PLAIN_TYPE_CHARS[cmd].has(typedLines[i].charAt(0))) {
497
+ plain.push(name);
498
+ return;
499
+ }
500
+ if (!canExcludeVerbatim(cmd, name)) {
501
+ throw new require_sdk.AnthropicError(`refusing to extract archive: cannot safely exclude member ${JSON.stringify(name)}`);
502
+ }
503
+ special.push(name);
504
+ });
505
+ return {
506
+ plain,
507
+ special
508
+ };
509
+ }
510
+ /**
511
+ * Walk `dir` with `lstat` semantics and reject anything that is not a regular
512
+ * file or directory. Never follows a link and never descends into anything
513
+ * but a real directory.
514
+ */
515
+ async function assertOnlyPlainEntries(dir) {
516
+ for (const entry of await fs.readdir(dir, { withFileTypes: true })) {
517
+ if (entry.isDirectory()) await assertOnlyPlainEntries(node_path.join(dir, entry.name));
518
+ else if (!entry.isFile()) throw new require_sdk.AnthropicError(INCONSISTENT_LISTING);
519
+ }
520
+ }
521
+ /**
522
+ * Run an archive CLI (`unzip` for zip archives, `tar` for everything else),
523
+ * returning its stdout. Both binaries must be on `PATH`; a missing one would
524
+ * otherwise surface as an opaque `ENOENT` spawn failure, so it is turned into a
525
+ * clear, specific error naming the missing command.
526
+ */
527
+ async function runArchiveTool(cmd, args) {
528
+ try {
529
+ const { stdout } = await execFileAsync(cmd, args);
530
+ return stdout;
531
+ } catch (e) {
532
+ if (errnoCode(e) === "ENOENT") {
533
+ throw new require_sdk.AnthropicError(`skill extraction requires the \`${cmd}\` command, but it was not found on PATH`);
534
+ }
535
+ throw e;
536
+ }
537
+ }
538
+ /**
539
+ * The single top-level directory shared by every entry in an archive listing,
540
+ * or `''` if entries don't all live under one common directory. Skill bundles
541
+ * are packaged wrapped in one directory named after the skill (e.g.
542
+ * `pdf/SKILL.md`, `pdf/scripts/...`); the extractor strips it so contents land
543
+ * directly in the skill's dir instead of a redundant nested `<skill>/<skill>/`
544
+ * level. A flat or multi-root archive yields `''`.
545
+ */
546
+ function archiveTopDir(names) {
547
+ let top;
548
+ let nested = false;
549
+ for (const raw of names) {
550
+ const parts = raw.trim().split("/").filter((p) => p !== "" && p !== ".");
551
+ if (parts.length === 0) continue;
552
+ const first = parts[0];
553
+ if (top === undefined) top = first;
554
+ else if (first !== top) return "";
555
+ if (parts.length > 1) nested = true;
556
+ }
557
+ return top !== undefined && nested ? top : "";
558
+ }
559
+ /**
560
+ * Extract a skill download (a zip or tar.* archive) into `dest`. Streams the
561
+ * response body straight to a temp file beside `dest` (so the whole archive is
562
+ * never buffered in memory — skills can contain large binaries), then shells out
563
+ * to `unzip`/`tar` — consistent with the rest of the toolset, which already
564
+ * invokes `bash` and `rg`. Both `unzip` and `tar` must be available on `PATH`; a
565
+ * missing binary surfaces as a clear error (see {@link runArchiveTool}). Refuses
566
+ * any member that would escape `dest` (zip-slip / tar-slip): skill archives
567
+ * come from the API, but skills can be third-party. Members that are not a
568
+ * regular file or directory (symlink, hardlink, device, fifo) are excluded
569
+ * from extraction rather than rejected; an archive whose special members
570
+ * cannot be excluded reliably is refused (see {@link classifyArchiveListing}).
571
+ * `tar` matches exclusions unanchored, so a plain member sharing a special
572
+ * member's name may be dropped too. The staging tree is verified to hold only
573
+ * regular files and directories before anything is promoted into `dest`.
574
+ *
575
+ * The skill bundle's single wrapper directory is stripped: the archive is
576
+ * extracted into a staging dir and the wrapper's contents are promoted into
577
+ * `dest`, so files land at `dest/SKILL.md` rather than a doubled
578
+ * `dest/<skill>/SKILL.md` (`unzip` has no `--strip-components`, so this is
579
+ * done uniformly by staging + promote rather than per-tool flags).
580
+ */
581
+ async function extractSkillArchive(resp, dest) {
582
+ const tmp = node_path.join(dest, `.skill-archive-${process.pid}-${Date.now()}`);
583
+ if (!resp.body) {
584
+ throw new require_sdk.AnthropicError("skill download response had no body");
585
+ }
586
+ await node_stream.promises.pipeline(node_stream.Readable.fromWeb(resp.body), node_fs.createWriteStream(tmp));
587
+ const stage = node_path.join(node_path.dirname(dest), `.skill-stage-${process.pid}-${Date.now()}`);
588
+ const excludeFile = node_path.join(node_path.dirname(dest), `.skill-exclude-${process.pid}-${Date.now()}`);
589
+ try {
590
+ const head = await readHead(tmp, 4);
591
+ const isZip = head.length >= 4 && head[0] === 80 && head[1] === 75 && head[2] === 3 && head[3] === 4;
592
+ const archiveCmd = isZip ? "unzip" : "tar";
593
+ const names = await runArchiveTool(archiveCmd, isZip ? ["-Z1", tmp] : ["-tf", tmp]);
594
+ const typed = await runArchiveTool(archiveCmd, isZip ? [
595
+ "-Z",
596
+ "--h",
597
+ "--t",
598
+ tmp
599
+ ] : ["-tvf", tmp]);
600
+ const { plain, special } = classifyArchiveListing(archiveCmd, names, typed);
601
+ assertSafeMemberNames([...plain, ...special]);
602
+ const top = archiveTopDir(plain);
603
+ await fs.mkdir(stage, {
604
+ recursive: true,
605
+ mode: 448
606
+ });
607
+ if (plain.length > 0) {
608
+ await runArchiveTool(archiveCmd, await extractArgs(archiveCmd, tmp, stage, special, excludeFile));
609
+ }
610
+ await assertOnlyPlainEntries(stage);
611
+ const srcRoot = top ? node_path.join(stage, top) : stage;
612
+ const entries = await fs.readdir(srcRoot).catch((e) => {
613
+ throw errnoCode(e) === "ENOENT" ? new require_sdk.AnthropicError(INCONSISTENT_LISTING) : e;
614
+ });
615
+ for (const entry of entries) {
616
+ await fs.rename(node_path.join(srcRoot, entry), node_path.join(dest, entry));
617
+ }
618
+ } finally {
619
+ await fs.rm(tmp, { force: true });
620
+ await fs.rm(excludeFile, { force: true });
621
+ await fs.rm(stage, {
622
+ recursive: true,
623
+ force: true
624
+ });
625
+ }
626
+ }
627
+ /**
628
+ * Arguments that extract `archive` into `stage` while excluding every member
629
+ * in `special`. Names are glob-escaped because both CLIs treat exclusions as
630
+ * patterns; `tar` reads them from `excludeFile`, `unzip` takes them after
631
+ * `-x`, which must follow `-d` so no pattern is parsed as an option.
632
+ */
633
+ async function extractArgs(cmd, archive, stage, special, excludeFile) {
634
+ const patterns = special.map((name) => name.replace(/[*?[\\]/g, "\\$&"));
635
+ if (cmd === "unzip") {
636
+ return [
637
+ "-oq",
638
+ archive,
639
+ "-d",
640
+ stage,
641
+ ...patterns.length > 0 ? ["-x", ...patterns] : []
642
+ ];
643
+ }
644
+ if (patterns.length === 0) return [
645
+ "-xf",
646
+ archive,
647
+ "-C",
648
+ stage
649
+ ];
650
+ await fs.writeFile(excludeFile, patterns.join("\n") + "\n", {
651
+ flag: "wx",
652
+ mode: 384
653
+ });
654
+ return [
655
+ "-xf",
656
+ archive,
657
+ "-C",
658
+ stage,
659
+ "-X",
660
+ excludeFile
661
+ ];
662
+ }
663
+ /** Read the first `n` bytes of `file`. */
664
+ async function readHead(file, n) {
665
+ const handle = await fs.open(file, "r");
666
+ try {
667
+ const buf = Buffer.alloc(n);
668
+ const { bytesRead } = await handle.read(buf, 0, n, 0);
669
+ return buf.subarray(0, bytesRead);
670
+ } finally {
671
+ await handle.close();
672
+ }
673
+ }
674
+
675
+ //#endregion
676
+ //#region ../../node_modules/.pnpm/@anthropic-ai+sdk@0.124.0_zod@4.5.4/node_modules/@anthropic-ai/sdk/internal/file-store.mjs
677
+ /**
678
+ * `FileStore` — one confined folder; a relative path cannot escape it.
679
+ *
680
+ * Beta scope: symlinks are refused or skipped wherever the store meets them,
681
+ * but there is no hardening against a process racing the store's own
682
+ * syscalls; fsync durability, non-POSIX hosts, and read-size caps are out of
683
+ * scope.
684
+ */
685
+ const fsp = node_fs.promises;
686
+ const C = node_fs.constants;
687
+ const OWNER_ONLY_DIR_MODE = 448;
688
+ const OWNER_ONLY_FILE_MODE = 384;
689
+ const OWNER_ONLY_EXEC_MODE = 448;
690
+ const O_NOFOLLOW = C.O_NOFOLLOW ?? 0;
691
+ const O_NONBLOCK = C.O_NONBLOCK ?? 0;
692
+ /** A refused operation — input the store will not act on. OS errors propagate with their `.code`. */
693
+ var FileStoreError = class extends Error {
694
+ constructor(reason, relPath) {
695
+ super(`path ${JSON.stringify(relPath)} ${reason}`);
696
+ this.name = "FileStoreError";
697
+ this.reason = reason;
698
+ this.relPath = relPath;
699
+ }
700
+ };
701
+ FileStoreError.ESCAPES_ROOT = "escapes the store root";
702
+ FileStoreError.IS_A_SYMLINK = "is a symlink";
703
+ FileStoreError.NOT_A_FILE = "is not a regular file";
704
+ FileStoreError.NOT_A_DIRECTORY = "is not a directory";
705
+ FileStoreError.NOT_UTF8 = "is not valid utf-8";
706
+ FileStoreError.MOVE_DESTINATION_EXISTS = "already exists";
707
+ /** Resolve `root`; creates nothing — only {@link FileStore.createRoot} makes the folder. */
708
+ async function openFileStore(root, opts) {
709
+ return FileStore.open(root, opts);
710
+ }
711
+ /**
712
+ * True for a path usable verbatim as a store location: absolute, with no `..`
713
+ * components. Paths are judged in POSIX terms — they are wire values naming
714
+ * locations inside a POSIX container, not host-native paths.
715
+ */
716
+ function isPathLegal(p) {
717
+ return p.startsWith("/") && !p.split("/").includes("..");
718
+ }
719
+ /**
720
+ * One confined folder of regular files.
721
+ *
722
+ * Every `relPath` is relative to the root (a leading `/` also means the root)
723
+ * and refused with {@link FileStoreError} when it escapes. The store holds
724
+ * regular files only: symlinks are refused on read and skipped by listings —
725
+ * {@link findSymlinks} reports them. A `relPath` resolving to the root itself
726
+ * is banned by this interface: `put` and `get` refuse it, `move` and `remove`
727
+ * do nothing. A store opened with `utf8: true` refuses binary content the
728
+ * same way — on `put` of such bytes and on `get` of such a file. Only
729
+ * {@link createRoot} makes the root: writes create directories below it,
730
+ * never the root itself, so a root removed while the store is open stays
731
+ * removed and the write fails with `ENOENT`.
732
+ */
733
+ var FileStore = class FileStore {
734
+ /** @internal — use {@link FileStore.open} / {@link openFileStore}. */
735
+ constructor(root, removedOnDispose, utf8Only = false) {
736
+ /** `hashtree`'s advisory cache; every hit re-validates against a fresh stat. */
737
+ this.hashes = new Map();
738
+ this.rootPath = root;
739
+ this.removedOnDispose = removedOnDispose;
740
+ this.decoder = utf8Only ? new TextDecoder("utf-8", { fatal: true }) : undefined;
741
+ }
742
+ /** Resolve `root`; creates nothing — only {@link createRoot} makes the folder. */
743
+ static async open(root, opts) {
744
+ if (!platformSupported()) {
745
+ throw new Error("FileStore requires O_NOFOLLOW support on this platform");
746
+ }
747
+ let removedOnDispose = false;
748
+ try {
749
+ await fsp.lstat(root);
750
+ } catch (e) {
751
+ if (e.code !== "ENOENT") throw e;
752
+ removedOnDispose = true;
753
+ }
754
+ return new FileStore(node_path.resolve(root), removedOnDispose, opts?.utf8 ?? false);
755
+ }
756
+ /** Create the root directory and any missing ancestors; already existing is fine. */
757
+ async createRoot() {
758
+ await makeDirAndAncestors(this.rootPath);
759
+ }
760
+ /** The resolved root, and what {@link dispose} will do to it. */
761
+ root() {
762
+ return {
763
+ path: this.rootPath,
764
+ removedOnDispose: this.removedOnDispose
765
+ };
766
+ }
767
+ /**
768
+ * Remove the root iff `open` created it; pre-existing roots are kept.
769
+ *
770
+ * Wired to `Symbol.asyncDispose` at runtime when the host provides it, so
771
+ * `await using` works on engines with explicit resource management.
772
+ */
773
+ async dispose() {
774
+ if (!this.removedOnDispose) return;
775
+ await fsp.rm(this.rootPath, {
776
+ recursive: true,
777
+ force: true
778
+ });
779
+ }
780
+ /**
781
+ * Write `data` (`string` UTF-8 or bytes) atomically to the file at `relPath`.
782
+ *
783
+ * Missing directories below the root are created; a missing root is not —
784
+ * the write fails with `ENOENT`.
785
+ */
786
+ async put(relPath, data, opts) {
787
+ const tail = relPath.replace(/\\/g, "/");
788
+ if (tail.endsWith("/") || tail.endsWith("/.") || tail === "" || tail === ".") {
789
+ throw new FileStoreError(FileStoreError.NOT_A_FILE, relPath);
790
+ }
791
+ const dest = this.resolveUnderRoot(relPath);
792
+ const payload = typeof data === "string" ? require_sdk.encodeUTF8(data) : data;
793
+ this.requireUtf8(relPath, payload);
794
+ await makeDirsBelowRoot(this.rootPath, node_path.dirname(dest));
795
+ await replaceViaTemp(dest, payload, opts?.executable ?? false);
796
+ }
797
+ /** The file's bytes; `null` when absent. */
798
+ async get(relPath) {
799
+ const dest = this.resolveUnderRoot(relPath);
800
+ let handle;
801
+ try {
802
+ handle = await openRegularFile(relPath, dest);
803
+ } catch (e) {
804
+ if (e.code === "ENOENT") return null;
805
+ throw e;
806
+ }
807
+ let data;
808
+ try {
809
+ const buf = await handle.readFile();
810
+ data = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
811
+ } finally {
812
+ await handle.close();
813
+ }
814
+ this.requireUtf8(relPath, data);
815
+ return data;
816
+ }
817
+ /** The relative path of every file under the directory `under`. */
818
+ async ls(under = "/") {
819
+ const base = this.resolveUnderRoot(under);
820
+ return new Set((await filenamesInDir(this.rootPath, under, base)).map(([rel]) => rel));
821
+ }
822
+ /**
823
+ * Every symlink under `under` — listings skip them and reads refuse them,
824
+ * so a caller that must know they exist asks here.
825
+ */
826
+ async findSymlinks(under = "/") {
827
+ const base = this.resolveUnderRoot(under);
828
+ return symlinksInDir(this.rootPath, under, base);
829
+ }
830
+ /**
831
+ * `{relPath: sha256Hex}` of every file under the directory `under`.
832
+ *
833
+ * Unchanged files — same size, mtime, and ctime since the last call —
834
+ * reuse their recorded hash instead of being re-read.
835
+ */
836
+ async hashtree(under = "/") {
837
+ const base = this.resolveUnderRoot(under);
838
+ const walkStartNs = _internals.nowNs();
839
+ const out = Object.create(null);
840
+ for (const [rel, full] of await filenamesInDir(this.rootPath, under, base)) {
841
+ const sha = await this.hashViaCache(rel, full, walkStartNs);
842
+ if (sha !== null) out[rel] = sha;
843
+ }
844
+ return out;
845
+ }
846
+ /** One file's sha256; `null` when absent. Shares {@link hashtree}'s cache. */
847
+ async hashFile(relPath) {
848
+ const dest = this.resolveUnderRoot(relPath);
849
+ let st;
850
+ try {
851
+ st = await fsp.lstat(dest, { bigint: true });
852
+ } catch (e) {
853
+ if (e.code === "ENOENT") return null;
854
+ throw e;
855
+ }
856
+ if (st.isSymbolicLink()) throw new FileStoreError(FileStoreError.IS_A_SYMLINK, relPath);
857
+ if (!st.isFile()) throw new FileStoreError(FileStoreError.NOT_A_FILE, relPath);
858
+ const rel = node_path.relative(this.rootPath, dest).split(node_path.sep).join("/");
859
+ return this.hashViaCache(rel, dest, _internals.nowNs());
860
+ }
861
+ /**
862
+ * Rename `src` to `dst`; an existing `dst` is refused. The banned store
863
+ * root as either end does nothing.
864
+ */
865
+ async move(src, dst) {
866
+ const s = this.resolveUnderRoot(src);
867
+ const d = this.resolveUnderRoot(dst);
868
+ if (s === this.rootPath || d === this.rootPath) return;
869
+ const dstExists = await fsp.stat(d).then(() => true, () => false);
870
+ if (dstExists) throw new FileStoreError(FileStoreError.MOVE_DESTINATION_EXISTS, dst);
871
+ await makeDirsBelowRoot(this.rootPath, node_path.dirname(d));
872
+ await fsp.rename(s, d);
873
+ }
874
+ /** Delete a file or subtree; absent — and the banned store root — do nothing. */
875
+ async remove(relPath) {
876
+ const dest = this.resolveUnderRoot(relPath);
877
+ if (dest === this.rootPath) return;
878
+ let st;
879
+ try {
880
+ st = await fsp.lstat(dest, { bigint: true });
881
+ } catch (e) {
882
+ if (e.code === "ENOENT") return;
883
+ throw e;
884
+ }
885
+ if (st.isDirectory()) {
886
+ await fsp.rm(dest, {
887
+ recursive: true,
888
+ force: true
889
+ });
890
+ } else {
891
+ try {
892
+ await fsp.unlink(dest);
893
+ } catch (e) {
894
+ if (e.code !== "ENOENT") throw e;
895
+ }
896
+ }
897
+ }
898
+ resolveUnderRoot(relPath) {
899
+ const norm = relPath.replace(/\\/g, "/").replace(/^\/+/, "");
900
+ const parts = norm.split("/").filter((p) => p !== "" && p !== ".");
901
+ if (node_path.posix.isAbsolute(norm) || parts.includes("..")) {
902
+ throw new FileStoreError(FileStoreError.ESCAPES_ROOT, relPath);
903
+ }
904
+ return parts.length === 0 ? this.rootPath : node_path.join(this.rootPath, ...parts);
905
+ }
906
+ requireUtf8(relPath, data) {
907
+ if (!this.decoder) return;
908
+ try {
909
+ this.decoder.decode(data);
910
+ } catch {
911
+ throw new FileStoreError(FileStoreError.NOT_UTF8, relPath);
912
+ }
913
+ }
914
+ async hashViaCache(rel, full, walkStartNs) {
915
+ let st;
916
+ try {
917
+ st = await fsp.lstat(full, { bigint: true });
918
+ } catch (e) {
919
+ if (e.code === "ENOENT") return null;
920
+ throw e;
921
+ }
922
+ if (!st.isFile()) return null;
923
+ const cached = this.hashes.get(rel);
924
+ let sha;
925
+ if (cached !== undefined && unchangedSinceHashed(cached, st)) {
926
+ sha = cached.sha;
927
+ } else {
928
+ try {
929
+ sha = await _internals.hashFile(full);
930
+ } catch (e) {
931
+ const code = e.code;
932
+ if (code === "ENOENT" || e instanceof FileStoreError) return null;
933
+ if (code === "ELOOP" || code === "EMLINK") return null;
934
+ throw e;
935
+ }
936
+ }
937
+ if (oldEnoughToCache(st, walkStartNs)) {
938
+ this.hashes.set(rel, {
939
+ mtimeNs: st.mtimeNs,
940
+ ctimeNs: st.ctimeNs,
941
+ size: st.size,
942
+ sha
943
+ });
944
+ }
945
+ return sha;
946
+ }
947
+ };
948
+ FileStore.isPathLegal = isPathLegal;
949
+ function platformSupported() {
950
+ return O_NOFOLLOW !== 0;
951
+ }
952
+ async function makeDirAndAncestors(dir) {
953
+ const missing = [];
954
+ let current = dir;
955
+ for (;;) {
956
+ try {
957
+ await fsp.stat(current);
958
+ break;
959
+ } catch (e) {
960
+ const code = e.code;
961
+ if (code !== "ENOENT" && code !== "ENOTDIR" && code !== "ELOOP") throw e;
962
+ }
963
+ missing.push(current);
964
+ const parent = node_path.dirname(current);
965
+ if (parent === current) break;
966
+ current = parent;
967
+ }
968
+ for (const directory of missing.reverse()) {
969
+ try {
970
+ await fsp.mkdir(directory, { mode: OWNER_ONLY_DIR_MODE });
971
+ } catch (e) {
972
+ if (e.code !== "EEXIST") throw e;
973
+ }
974
+ }
975
+ }
976
+ async function makeDirsBelowRoot(root, dir) {
977
+ const below = node_path.relative(root, dir);
978
+ if (below === "") return;
979
+ let current = root;
980
+ for (const part of below.split(node_path.sep)) {
981
+ current = node_path.join(current, part);
982
+ try {
983
+ await fsp.mkdir(current, { mode: OWNER_ONLY_DIR_MODE });
984
+ } catch (e) {
985
+ if (e.code !== "EEXIST") throw e;
986
+ }
987
+ }
988
+ }
989
+ async function replaceViaTemp(dest, data, isExecutable) {
990
+ const mode = isExecutable ? OWNER_ONLY_EXEC_MODE : OWNER_ONLY_FILE_MODE;
991
+ const tmp = node_path.join(node_path.dirname(dest), `.fs-${node_crypto.randomBytes(8).toString("hex")}.tmp`);
992
+ let handle;
993
+ try {
994
+ handle = await fsp.open(tmp, C.O_WRONLY | C.O_CREAT | C.O_EXCL | O_NOFOLLOW, mode);
995
+ await handle.writeFile(data);
996
+ await handle.close();
997
+ handle = undefined;
998
+ await fsp.rename(tmp, dest);
999
+ } catch (err) {
1000
+ if (handle) await handle.close().catch(() => {});
1001
+ await fsp.unlink(tmp).catch(() => {});
1002
+ throw err;
1003
+ }
1004
+ }
1005
+ async function openRegularFile(relPath, dest) {
1006
+ let handle;
1007
+ try {
1008
+ handle = await fsp.open(dest, C.O_RDONLY | O_NOFOLLOW | O_NONBLOCK);
1009
+ } catch (e) {
1010
+ const code = e.code;
1011
+ if (code === "ELOOP" || code === "EMLINK") {
1012
+ throw new FileStoreError(FileStoreError.IS_A_SYMLINK, relPath);
1013
+ }
1014
+ throw e;
1015
+ }
1016
+ try {
1017
+ const st = await handle.stat();
1018
+ if (!st.isFile()) throw new FileStoreError(FileStoreError.NOT_A_FILE, relPath);
1019
+ } catch (e) {
1020
+ await handle.close().catch(() => {});
1021
+ throw e;
1022
+ }
1023
+ return handle;
1024
+ }
1025
+ /** sha256 of a file's contents, streamed — constant memory on any file size. */
1026
+ async function hashFile(full) {
1027
+ const digest = node_crypto.createHash("sha256");
1028
+ const handle = await openRegularFile(node_path.basename(full), full);
1029
+ const buf = new Uint8Array(1024 * 1024);
1030
+ try {
1031
+ for (;;) {
1032
+ const { bytesRead } = await handle.read(buf, 0, buf.length);
1033
+ if (bytesRead === 0) break;
1034
+ digest.update(buf.subarray(0, bytesRead));
1035
+ }
1036
+ } finally {
1037
+ await handle.close();
1038
+ }
1039
+ return digest.digest("hex");
1040
+ }
1041
+ /**
1042
+ * `[rel, path]` for every regular file under the directory `base`; an absent
1043
+ * `base` is empty, a present non-directory is refused. The walk never
1044
+ * descends symlinked directories.
1045
+ */
1046
+ async function filenamesInDir(root, under, base) {
1047
+ if (!await requireDir(under, base)) return [];
1048
+ const out = [];
1049
+ await walk$1(base, (full, entry) => {
1050
+ if (entry.isFile()) out.push([node_path.relative(root, full).split(node_path.sep).join("/"), full]);
1051
+ });
1052
+ out.sort();
1053
+ return out;
1054
+ }
1055
+ async function symlinksInDir(root, under, base) {
1056
+ const relOf = (full) => node_path.relative(root, full).split(node_path.sep).join("/");
1057
+ let st;
1058
+ try {
1059
+ st = await fsp.lstat(base, { bigint: true });
1060
+ } catch (e) {
1061
+ const code = e.code;
1062
+ if (code === "ENOENT" || code === "ENOTDIR") return new Set();
1063
+ throw e;
1064
+ }
1065
+ if (st.isSymbolicLink()) return new Set([relOf(base)]);
1066
+ if (!st.isDirectory()) throw new FileStoreError(FileStoreError.NOT_A_DIRECTORY, under);
1067
+ const out = new Set();
1068
+ await walk$1(base, (full, entry) => {
1069
+ if (entry.isSymbolicLink()) out.add(relOf(full));
1070
+ });
1071
+ return out;
1072
+ }
1073
+ /** `false` when `base` is absent, refused when present but not a directory. */
1074
+ async function requireDir(under, base) {
1075
+ let st;
1076
+ try {
1077
+ st = await fsp.lstat(base, { bigint: true });
1078
+ } catch (e) {
1079
+ const code = e.code;
1080
+ if (code === "ENOENT" || code === "ENOTDIR") return false;
1081
+ throw e;
1082
+ }
1083
+ if (!st.isDirectory()) throw new FileStoreError(FileStoreError.NOT_A_DIRECTORY, under);
1084
+ return true;
1085
+ }
1086
+ /** Visit every entry under `base` without descending symlinked directories. */
1087
+ async function walk$1(base, visit) {
1088
+ const stack = [base];
1089
+ while (stack.length) {
1090
+ const dir = stack.pop();
1091
+ let entries;
1092
+ try {
1093
+ entries = await fsp.readdir(dir, { withFileTypes: true });
1094
+ } catch (e) {
1095
+ if (e.code === "ENOENT") continue;
1096
+ throw e;
1097
+ }
1098
+ for (const entry of entries) {
1099
+ const full = node_path.join(dir, entry.name);
1100
+ visit(full, entry);
1101
+ if (entry.isDirectory() && !entry.isSymbolicLink()) stack.push(full);
1102
+ }
1103
+ }
1104
+ }
1105
+ function unchangedSinceHashed(cached, st) {
1106
+ return st.mtimeNs === cached.mtimeNs && st.ctimeNs === cached.ctimeNs && st.size === cached.size;
1107
+ }
1108
+ function oldEnoughToCache(st, walkStartNs) {
1109
+ const newestNs = st.mtimeNs > st.ctimeNs ? st.mtimeNs : st.ctimeNs;
1110
+ return newestNs < walkStartNs - _internals.timestampTrustMarginNs;
1111
+ }
1112
+ const TIMESTAMP_TRUST_MARGIN_NS = 2000000000n;
1113
+ /** Test seam — the hasher, the trust margin, and the walk clock. @internal */
1114
+ const _internals = {
1115
+ hashFile,
1116
+ timestampTrustMarginNs: TIMESTAMP_TRUST_MARGIN_NS,
1117
+ nowNs: () => BigInt(Date.now()) * 1000000n
1118
+ };
1119
+ const LocalFileStore = FileStore;
1120
+ const asyncDispose = Symbol.asyncDispose;
1121
+ if (asyncDispose) {
1122
+ Object.defineProperty(FileStore.prototype, asyncDispose, {
1123
+ value: FileStore.prototype.dispose,
1124
+ configurable: true,
1125
+ writable: true
1126
+ });
1127
+ }
1128
+
1129
+ //#endregion
1130
+ //#region ../../node_modules/.pnpm/@anthropic-ai+sdk@0.124.0_zod@4.5.4/node_modules/@anthropic-ai/sdk/tools/agent-toolset/memories.mjs
1131
+ /**
1132
+ * Session-level memory-store download and sync.
1133
+ *
1134
+ * A session may have several memory stores attached. This module resolves
1135
+ * where each store's folder goes on disk, opens a {@link LocalFileStore}
1136
+ * there, and reconciles each folder with its remote store — the merge rules
1137
+ * live on {@link SessionMemoryStores}.
1138
+ *
1139
+ * Node-only (it sits on the filesystem-backed FileStore); like `skills.ts`,
1140
+ * it is reachable through the shimmed `node.ts` entry point.
1141
+ */
1142
+ var _SessionMemoryStores_instances;
1143
+ var _SessionMemoryStores_client;
1144
+ var _SessionMemoryStores_workdir;
1145
+ var _SessionMemoryStores_syncIntervalMs;
1146
+ var _SessionMemoryStores_syncDeletions;
1147
+ var _SessionMemoryStores_log;
1148
+ var _SessionMemoryStores_lastSyncAt;
1149
+ var _SessionMemoryStores_finished;
1150
+ var _SessionMemoryStores_stores;
1151
+ var _SessionMemoryStores_storeRoot;
1152
+ var _SessionMemoryStores_scanMarker;
1153
+ var _SessionMemoryStores_syncStore;
1154
+ var _SessionMemoryStores_flushStore;
1155
+ var _SessionMemoryStores_recover;
1156
+ var _SessionMemoryStores_stampAndPull;
1157
+ var _SessionMemoryStores_syncPath;
1158
+ var _SessionMemoryStores_removeLocal;
1159
+ var _SessionMemoryStores_write;
1160
+ var _SessionMemoryStores_pullAll;
1161
+ var _SessionMemoryStores_uploadAll;
1162
+ var _SessionMemoryStores_listMemories;
1163
+ var _SessionMemoryStores_upload;
1164
+ var _SessionMemoryStores_corroboratedDelete;
1165
+ var _SessionMemoryStores_deleteRemote;
1166
+ /**
1167
+ * Time bound the worker puts on each teardown pass — the final
1168
+ * {@link SessionMemoryStores.finish}, then {@link SessionMemoryStores.flushWrites} —
1169
+ * so a slow server cannot stall teardown.
1170
+ */
1171
+ const MEMORY_FLUSH_TIMEOUT_MS = 3e4;
1172
+ /**
1173
+ * Marker file stamped into every store folder; a sync trusts the folder only
1174
+ * when it matches. Never itself syncs.
1175
+ */
1176
+ const MARKER_PATH = ".anthropic-memory-store";
1177
+ const MARKER_VERSION = 1;
1178
+ function markerSha(memoryStoreId) {
1179
+ return node_crypto.createHash("sha256").update(`version ${MARKER_VERSION}\n${memoryStoreId}`, "utf-8").digest("hex");
1180
+ }
1181
+ /** How long a file must stay missing locally before its server delete goes out. */
1182
+ const DELETE_CORROBORATION_MS = 3e4;
1183
+ /**
1184
+ * Page sizes for memory listings — the API's maximum per view: `basic` pages
1185
+ * carry up to 100 items, `full` pages are capped by the server.
1186
+ */
1187
+ const LIST_PAGE_SIZE = 100;
1188
+ const FULL_LIST_PAGE_SIZE = 20;
1189
+ /**
1190
+ * How many single-memory content fetches may be in flight at once during one
1191
+ * store's pull pass. A sync rarely pulls more than a handful of memories, so
1192
+ * a higher cap buys nothing in the common case.
1193
+ */
1194
+ const FETCH_CONCURRENCY = 16;
1195
+ /**
1196
+ * How many uploads one store's flush keeps in flight. At ~0.3s per upload,
1197
+ * 32 clears the server's 2000-memories-per-store cap inside
1198
+ * {@link MEMORY_FLUSH_TIMEOUT_MS}.
1199
+ */
1200
+ const UPLOAD_CONCURRENCY = 32;
1201
+ /**
1202
+ * Per-sync remote-delete cap bounds. The floor lets a small store's
1203
+ * deletes clear in one pass; the ceiling caps damage on large ones.
1204
+ */
1205
+ const DELETE_CAP_FLOOR = 8;
1206
+ const DELETE_CAP_CEILING = 50;
1207
+ /**
1208
+ * A session's memory stores could not be mounted.
1209
+ *
1210
+ * Thrown by {@link SessionMemoryStores.download} when a store cannot be
1211
+ * materialised on disk, and by the environment worker when a work item for a
1212
+ * session that has memory stores carried no sessions token to reach them with.
1213
+ */
1214
+ var SessionMemoryError = class extends require_sdk.AnthropicError {
1215
+ constructor(message, cause) {
1216
+ super(message);
1217
+ this.name = "SessionMemoryError";
1218
+ if (cause !== undefined) this.cause = cause;
1219
+ }
1220
+ };
1221
+ /** One sync's remote-delete gate and counters. */
1222
+ var DeletePass = class {
1223
+ constructor(mode, cap, waiveWindow) {
1224
+ this.mode = mode;
1225
+ this.cap = cap;
1226
+ this.waiveWindow = waiveWindow;
1227
+ this.attempted = 0;
1228
+ this.capped = 0;
1229
+ this.suppressed = 0;
1230
+ }
1231
+ takeSlot() {
1232
+ if (this.attempted >= this.cap) {
1233
+ this.capped++;
1234
+ return false;
1235
+ }
1236
+ this.attempted++;
1237
+ return true;
1238
+ }
1239
+ };
1240
+ /**
1241
+ * The memory stores attached to one session, materialised on disk.
1242
+ *
1243
+ * {@link SessionMemoryStores.download} opens a {@link LocalFileStore} at each
1244
+ * attached store's directory (its `mount_path`, or a workdir fallback — see
1245
+ * {@link SessionMemoryStores.download}), pulls its memories, and records each
1246
+ * one's `content_sha256` as the sync baseline. Each sync
1247
+ * ({@link SessionMemoryStores.syncIfDue} on the worker's cadence,
1248
+ * {@link SessionMemoryStores.finish} once at the end) reconciles disk against
1249
+ * server, per store and per path:
1250
+ *
1251
+ * - a memory changed only remotely is written to disk;
1252
+ * - a file changed only locally is uploaded — an update with a
1253
+ * `content_sha256` precondition, or a create for a new file;
1254
+ * - a file changed on both sides logs a warning and takes the server version;
1255
+ * - a file the server refuses (too large, invalid content) is skipped —
1256
+ * warned once and retried only after the file changes; other files keep
1257
+ * syncing;
1258
+ * - a file deleted locally is deleted on the server after a delay and a
1259
+ * re-check — never on the first sync that notices, and only up to a
1260
+ * per-sync cap. `syncDeletions` gates it;
1261
+ * - a memory deleted on the server is deleted on disk — unless the local
1262
+ * file holds un-pushed edits: a writable store re-creates the memory
1263
+ * from the file, a read-only one keeps the file unsynced;
1264
+ * - a store attached read-only pulls but never pushes.
1265
+ *
1266
+ * A download pulls the whole store, so it lists with content included. The
1267
+ * recurring syncs instead run two phases: a content-free listing (paths and
1268
+ * shas) drives the merge decisions, then only the memories actually being
1269
+ * written to disk are fetched, a bounded number at a time. A sync that finds
1270
+ * nothing changed moves no content at all.
1271
+ *
1272
+ * A file whose write to disk failed is never in the baseline, so its absence
1273
+ * reads as a failed download — it is pulled again, never deleted. A write
1274
+ * never re-creates a store folder that vanished mid-sync: it fails, and the
1275
+ * next sync's scan finds whatever is at the path by then — nothing
1276
+ * (re-downloaded) or someone else's files (left alone) — under the rules
1277
+ * below.
1278
+ *
1279
+ * A store folder that loses its {@link MARKER_PATH} marker, is emptied,
1280
+ * or vanishes is re-downloaded rather than treated as a mass local
1281
+ * delete; a folder whose marker names another store is left as found —
1282
+ * nothing pushed, nothing deleted.
1283
+ *
1284
+ * Two things about the store's directory make
1285
+ * {@link SessionMemoryStores.download} refuse the session outright, with
1286
+ * {@link SessionMemoryError}: a `mount_path` that is not a clean absolute
1287
+ * path, and a directory already sitting at that path.
1288
+ *
1289
+ * {@link SessionMemoryStores.download} throws on the first store it cannot
1290
+ * materialise. The syncs never throw: mid-session, one bad store or one bad
1291
+ * file is logged and the rest continue. Instances are not safe for concurrent
1292
+ * use. The worker builds one on its token-scoped sub-client (the memory
1293
+ * endpoints reject the environment key): `syncIfDue` after each tool call,
1294
+ * `finish` once at a clean end, a bounded {@link SessionMemoryStores.flushWrites}
1295
+ * in every teardown, `dispose` last.
1296
+ */
1297
+ var SessionMemoryStores = class {
1298
+ constructor(client, opts) {
1299
+ _SessionMemoryStores_instances.add(this);
1300
+ _SessionMemoryStores_client.set(this, void 0);
1301
+ _SessionMemoryStores_workdir.set(this, void 0);
1302
+ _SessionMemoryStores_syncIntervalMs.set(this, void 0);
1303
+ _SessionMemoryStores_syncDeletions.set(this, void 0);
1304
+ _SessionMemoryStores_log.set(this, void 0);
1305
+ _SessionMemoryStores_lastSyncAt.set(this, void 0);
1306
+ _SessionMemoryStores_finished.set(this, false);
1307
+ _SessionMemoryStores_stores.set(this, []);
1308
+ require_sdk.__classPrivateFieldSet(this, _SessionMemoryStores_client, client, "f");
1309
+ require_sdk.__classPrivateFieldSet(this, _SessionMemoryStores_workdir, opts.workdir, "f");
1310
+ require_sdk.__classPrivateFieldSet(this, _SessionMemoryStores_syncIntervalMs, opts.syncIntervalMs ?? 15e3, "f");
1311
+ require_sdk.checkMemorySyncInterval(require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_syncIntervalMs, "f"), "syncIntervalMs");
1312
+ require_sdk.__classPrivateFieldSet(this, _SessionMemoryStores_syncDeletions, opts.syncDeletions ?? "enabled", "f");
1313
+ require_sdk.__classPrivateFieldSet(this, _SessionMemoryStores_log, require_sdk.loggerFor(client), "f");
1314
+ require_sdk.__classPrivateFieldSet(this, _SessionMemoryStores_lastSyncAt, Date.now(), "f");
1315
+ }
1316
+ /**
1317
+ * Every attached store's root directory.
1318
+ *
1319
+ * The worker lists these as the file tools' allowed roots so a store
1320
+ * mounted outside the workdir stays reachable.
1321
+ */
1322
+ get roots() {
1323
+ return require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_stores, "f").map((s) => s.files.root().path);
1324
+ }
1325
+ /**
1326
+ * Root directories of stores attached read-only.
1327
+ *
1328
+ * The file tools consult this to refuse writes into read-only stores.
1329
+ */
1330
+ get readOnlyRoots() {
1331
+ return require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_stores, "f").filter((s) => s.readOnly).map((s) => s.files.root().path);
1332
+ }
1333
+ /**
1334
+ * Download every attached store's memories to disk.
1335
+ *
1336
+ * `session` arrives already fetched — one snapshot shared with the skills
1337
+ * download, so the two cannot disagree about the resources.
1338
+ */
1339
+ async download(session) {
1340
+ for (const resource of session.resources) {
1341
+ if (resource.type !== "memory_store") continue;
1342
+ const root = require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_instances, "m", _SessionMemoryStores_storeRoot).call(this, resource);
1343
+ let store;
1344
+ try {
1345
+ store = {
1346
+ memoryStoreId: resource.memory_store_id,
1347
+ files: await LocalFileStore.open(root, { utf8: true }),
1348
+ readOnly: resource.access === "read_only",
1349
+ baseline: new Map(),
1350
+ refusedShas: new Map(),
1351
+ pendingDeletes: new Map()
1352
+ };
1353
+ if (!store.files.root().removedOnDispose) {
1354
+ throw new SessionMemoryError(`something already exists at the memory store's path: ${root} ` + `(memory_store_id=${resource.memory_store_id}); ` + "it must not exist when the session starts");
1355
+ }
1356
+ try {
1357
+ await store.files.createRoot();
1358
+ } catch (e) {
1359
+ if (!isErrno(e)) throw e;
1360
+ throw new SessionMemoryError(`cannot create the memory store's folder: ${root} ` + `(memory_store_id=${resource.memory_store_id}): ${e}; ` + "the worker host must make this mount path writable", e);
1361
+ }
1362
+ await require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_instances, "m", _SessionMemoryStores_stampAndPull).call(this, store);
1363
+ require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_log, "f").info("downloaded memories", {
1364
+ count: store.baseline.size,
1365
+ memory_store_id: store.memoryStoreId,
1366
+ dest: store.files.root().path
1367
+ });
1368
+ require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_stores, "f").push(store);
1369
+ } catch (e) {
1370
+ if (store) await store.files.dispose().catch(() => {});
1371
+ if (e instanceof SessionMemoryError) throw e;
1372
+ throw new SessionMemoryError(`failed to download memory store memory_store_id=${resource.memory_store_id}: ${e}`, e);
1373
+ }
1374
+ }
1375
+ require_sdk.__classPrivateFieldSet(this, _SessionMemoryStores_lastSyncAt, Date.now(), "f");
1376
+ }
1377
+ /**
1378
+ * The session's last sync — skips the delete wait, so calling it twice
1379
+ * would undo the protection; it throws instead.
1380
+ */
1381
+ async finish() {
1382
+ if (require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_finished, "f")) {
1383
+ throw new require_sdk.AnthropicError("finish() was already called: it is the session's last sync and runs once");
1384
+ }
1385
+ require_sdk.__classPrivateFieldSet(this, _SessionMemoryStores_finished, true, "f");
1386
+ await this.syncAll(true);
1387
+ }
1388
+ /** @internal — reconcile every store once; the tests' deterministic driver */
1389
+ async syncAll(final) {
1390
+ await Promise.all(require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_stores, "f").map((store) => require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_instances, "m", _SessionMemoryStores_syncStore).call(this, store, final)));
1391
+ require_sdk.__classPrivateFieldSet(this, _SessionMemoryStores_lastSyncAt, Date.now(), "f");
1392
+ }
1393
+ /** Sync when `syncIntervalMs` has elapsed since the last one. Never throws. */
1394
+ async syncIfDue() {
1395
+ if (Date.now() - require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_lastSyncAt, "f") < require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_syncIntervalMs, "f")) return;
1396
+ await this.syncAll(false);
1397
+ }
1398
+ /**
1399
+ * Upload new and changed files; send no deletes and pull nothing.
1400
+ *
1401
+ * The push-only rescue pass for a session ending on an error or
1402
+ * cancel — best-effort, bounded by the caller: once `signal` aborts no
1403
+ * further upload starts, each store cut off part-way logs how many
1404
+ * changed files it had not finished uploading, and this resolves without
1405
+ * waiting for requests already in flight. Each store uploads up to
1406
+ * {@link UPLOAD_CONCURRENCY} files at a time. Skips read-only stores,
1407
+ * refused files, files the server already holds, and folders that fail
1408
+ * the marker check. Never throws.
1409
+ */
1410
+ async flushWrites(signal) {
1411
+ await Promise.all(require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_stores, "f").map((store) => require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_instances, "m", _SessionMemoryStores_flushStore).call(this, store, signal)));
1412
+ }
1413
+ /**
1414
+ * Remove every store directory that {@link SessionMemoryStores.download}
1415
+ * created. Pre-existing directories are left alone — that is
1416
+ * {@link FileStore.dispose}'s own rule. A folder that fails the marker
1417
+ * check is kept too — sync left it as found, so must dispose.
1418
+ */
1419
+ async dispose() {
1420
+ for (const store of require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_stores, "f")) {
1421
+ const root = store.files.root();
1422
+ try {
1423
+ const scan = await require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_instances, "m", _SessionMemoryStores_scanMarker).call(this, store);
1424
+ if (!scan.markerOk && Object.keys(scan.files).length > 0) {
1425
+ require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_log, "f").warn(`${scan.distrustReason}; leaving the memory store folder on disk`, {
1426
+ root: root.path,
1427
+ memory_store_id: store.memoryStoreId
1428
+ });
1429
+ continue;
1430
+ }
1431
+ await store.files.dispose();
1432
+ } catch (e) {
1433
+ if (!(e instanceof FileStoreError) && !isErrno(e)) throw e;
1434
+ require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_log, "f").warn("failed to remove the memory store folder", {
1435
+ root: store.files.root().path,
1436
+ memory_store_id: store.memoryStoreId,
1437
+ error: String(e)
1438
+ });
1439
+ continue;
1440
+ }
1441
+ if (root.removedOnDispose) {
1442
+ require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_log, "f").info("removed memory store dir", {
1443
+ dest: root.path,
1444
+ memory_store_id: store.memoryStoreId
1445
+ });
1446
+ }
1447
+ }
1448
+ }
1449
+ };
1450
+ _SessionMemoryStores_client = new WeakMap(), _SessionMemoryStores_workdir = new WeakMap(), _SessionMemoryStores_syncIntervalMs = new WeakMap(), _SessionMemoryStores_syncDeletions = new WeakMap(), _SessionMemoryStores_log = new WeakMap(), _SessionMemoryStores_lastSyncAt = new WeakMap(), _SessionMemoryStores_finished = new WeakMap(), _SessionMemoryStores_stores = new WeakMap(), _SessionMemoryStores_instances = new WeakSet(), _SessionMemoryStores_storeRoot = function _SessionMemoryStores_storeRoot(resource) {
1451
+ if (resource.mount_path) {
1452
+ if (!isPathLegal(resource.mount_path)) {
1453
+ throw new SessionMemoryError(`memory store mount_path is not a clean absolute path: ${JSON.stringify(resource.mount_path)} ` + `(memory_store_id=${resource.memory_store_id})`);
1454
+ }
1455
+ return resource.mount_path;
1456
+ }
1457
+ return node_path.join(require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_workdir, "f"), "memory", resource.name || resource.memory_store_id);
1458
+ }, _SessionMemoryStores_scanMarker = async function _SessionMemoryStores_scanMarker(store) {
1459
+ const local = await store.files.hashtree();
1460
+ const marker = local[MARKER_PATH];
1461
+ delete local[MARKER_PATH];
1462
+ if (marker === markerSha(store.memoryStoreId)) {
1463
+ return {
1464
+ files: local,
1465
+ markerOk: true,
1466
+ distrustReason: null
1467
+ };
1468
+ }
1469
+ return {
1470
+ files: local,
1471
+ markerOk: false,
1472
+ distrustReason: marker !== undefined ? "the marker file does not match this store" : "the marker file is gone"
1473
+ };
1474
+ }, _SessionMemoryStores_syncStore = async function _SessionMemoryStores_syncStore(store, final) {
1475
+ try {
1476
+ const scan = await require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_instances, "m", _SessionMemoryStores_scanMarker).call(this, store);
1477
+ const local = scan.files;
1478
+ if (!scan.markerOk) {
1479
+ if (Object.keys(local).length > 0) {
1480
+ require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_log, "f").warn(`${scan.distrustReason}; leaving the memory store folder as found and not syncing`, {
1481
+ root: store.files.root().path,
1482
+ memory_store_id: store.memoryStoreId
1483
+ });
1484
+ return;
1485
+ }
1486
+ await require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_instances, "m", _SessionMemoryStores_recover).call(this, store, "the folder or its marker is gone");
1487
+ return;
1488
+ }
1489
+ if (Object.keys(local).length === 0 && store.baseline.size > 1) {
1490
+ await require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_instances, "m", _SessionMemoryStores_recover).call(this, store, "every memory file is gone at once");
1491
+ return;
1492
+ }
1493
+ const remote = new Map();
1494
+ for await (const [rel, item] of require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_instances, "m", _SessionMemoryStores_listMemories).call(this, store.memoryStoreId)) {
1495
+ remote.set(rel, item);
1496
+ }
1497
+ const deletes = new DeletePass(require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_syncDeletions, "f"), Math.max(DELETE_CAP_FLOOR, Math.min(DELETE_CAP_CEILING, Math.floor(store.baseline.size / 4))), final);
1498
+ const pulls = [];
1499
+ const baseline = new Map();
1500
+ const paths = [...new Set([
1501
+ ...remote.keys(),
1502
+ ...Object.keys(local),
1503
+ ...store.baseline.keys()
1504
+ ])].sort();
1505
+ for (const rel of paths) {
1506
+ const remoteItem = remote.get(rel);
1507
+ const localSha = local[rel];
1508
+ const baseSha = store.baseline.get(rel);
1509
+ let sha;
1510
+ if (localSha === undefined && baseSha !== undefined && remoteItem !== undefined && remoteItem.content_sha256 === baseSha && !store.readOnly) {
1511
+ sha = await require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_instances, "m", _SessionMemoryStores_corroboratedDelete).call(this, store, rel, remoteItem, baseSha, deletes);
1512
+ } else {
1513
+ sha = await require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_instances, "m", _SessionMemoryStores_syncPath).call(this, store, rel, remoteItem, localSha, pulls);
1514
+ }
1515
+ if (sha !== undefined) baseline.set(rel, sha);
1516
+ }
1517
+ store.baseline = baseline;
1518
+ await require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_instances, "m", _SessionMemoryStores_pullAll).call(this, store, pulls);
1519
+ if (deletes.suppressed > 0) {
1520
+ require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_log, "f").debug("remote deletes are disabled; locally deleted memories stay on the server", {
1521
+ count: deletes.suppressed,
1522
+ memory_store_id: store.memoryStoreId
1523
+ });
1524
+ }
1525
+ if (deletes.capped > 0) {
1526
+ require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_log, "f").warn(`delete cap reached: ${deletes.mode === "log_only" ? "would send" : "sent"} ` + `${deletes.attempted} deletes, held ${deletes.capped} for later syncs`, { memory_store_id: store.memoryStoreId });
1527
+ }
1528
+ } catch (e) {
1529
+ require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_log, "f").warn("memory sync failed", {
1530
+ memory_store_id: store.memoryStoreId,
1531
+ error: String(e)
1532
+ });
1533
+ }
1534
+ }, _SessionMemoryStores_flushStore = async function _SessionMemoryStores_flushStore(store, signal) {
1535
+ const dirty = new Map();
1536
+ const unsent = new Set();
1537
+ const push = async () => {
1538
+ if (store.readOnly) return;
1539
+ const scan = await require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_instances, "m", _SessionMemoryStores_scanMarker).call(this, store);
1540
+ if (!scan.markerOk) {
1541
+ require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_log, "f").warn(`${scan.distrustReason}; not uploading anything from the memory store folder`, {
1542
+ root: store.files.root().path,
1543
+ memory_store_id: store.memoryStoreId
1544
+ });
1545
+ return;
1546
+ }
1547
+ for (const [rel, sha] of Object.entries(scan.files)) {
1548
+ if (sha !== store.baseline.get(rel) && store.refusedShas.get(rel) !== sha) {
1549
+ dirty.set(rel, sha);
1550
+ unsent.add(rel);
1551
+ }
1552
+ }
1553
+ if (dirty.size === 0 || signal?.aborted) return;
1554
+ const remote = new Map();
1555
+ for await (const [rel, item] of require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_instances, "m", _SessionMemoryStores_listMemories).call(this, store.memoryStoreId)) {
1556
+ if (signal?.aborted) return;
1557
+ remote.set(rel, item);
1558
+ }
1559
+ const uploads = [];
1560
+ for (const rel of [...dirty.keys()].sort()) {
1561
+ const localSha = dirty.get(rel);
1562
+ const baseSha = store.baseline.get(rel);
1563
+ const existing = remote.get(rel);
1564
+ if (existing !== undefined && existing.content_sha256 === localSha) {
1565
+ store.baseline.set(rel, existing.content_sha256);
1566
+ unsent.delete(rel);
1567
+ continue;
1568
+ }
1569
+ if (existing !== undefined && existing.content_sha256 !== baseSha) {
1570
+ require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_log, "f").warn("memory changed both locally and remotely; the flush leaves the remote version", {
1571
+ path: rel,
1572
+ memory_store_id: store.memoryStoreId
1573
+ });
1574
+ unsent.delete(rel);
1575
+ continue;
1576
+ }
1577
+ uploads.push([
1578
+ rel,
1579
+ localSha,
1580
+ existing
1581
+ ]);
1582
+ }
1583
+ await require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_instances, "m", _SessionMemoryStores_uploadAll).call(this, store, uploads, unsent, signal);
1584
+ };
1585
+ try {
1586
+ await settledOrAborted(push(), signal);
1587
+ if (signal?.aborted && unsent.size > 0) {
1588
+ require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_log, "f").warn(`memory flush cut off part-way; ${unsent.size} of ${dirty.size} changed files had not finished uploading`, { memory_store_id: store.memoryStoreId });
1589
+ }
1590
+ } catch (e) {
1591
+ require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_log, "f").warn("memory flush failed", {
1592
+ memory_store_id: store.memoryStoreId,
1593
+ error: String(e)
1594
+ });
1595
+ }
1596
+ }, _SessionMemoryStores_recover = async function _SessionMemoryStores_recover(store, reason) {
1597
+ require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_log, "f").warn(`${reason}; re-downloading the memory store folder instead of syncing`, {
1598
+ root: store.files.root().path,
1599
+ memory_store_id: store.memoryStoreId
1600
+ });
1601
+ await store.files.createRoot();
1602
+ await require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_instances, "m", _SessionMemoryStores_stampAndPull).call(this, store);
1603
+ }, _SessionMemoryStores_stampAndPull = async function _SessionMemoryStores_stampAndPull(store) {
1604
+ store.baseline = new Map();
1605
+ store.pendingDeletes.clear();
1606
+ await store.files.put(MARKER_PATH, `version ${MARKER_VERSION}\n${store.memoryStoreId}`);
1607
+ for await (const [rel, item] of require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_instances, "m", _SessionMemoryStores_listMemories).call(this, store.memoryStoreId, "full")) {
1608
+ if (await require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_instances, "m", _SessionMemoryStores_write).call(this, store, rel, item.content ?? "")) {
1609
+ store.baseline.set(rel, item.content_sha256);
1610
+ }
1611
+ }
1612
+ }, _SessionMemoryStores_syncPath = async function _SessionMemoryStores_syncPath(store, rel, remote, localSha, pulls) {
1613
+ const baseSha = store.baseline.get(rel);
1614
+ if (localSha !== undefined) {
1615
+ store.pendingDeletes.delete(rel);
1616
+ }
1617
+ if (!remote) {
1618
+ if (localSha === undefined) {
1619
+ store.pendingDeletes.delete(rel);
1620
+ return undefined;
1621
+ }
1622
+ if (baseSha !== undefined) {
1623
+ if (localSha === baseSha) {
1624
+ const fresh = await require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_instances, "m", _SessionMemoryStores_removeLocal).call(this, store, rel, baseSha);
1625
+ if (fresh === undefined) return undefined;
1626
+ if (fresh === baseSha) return baseSha;
1627
+ localSha = fresh;
1628
+ }
1629
+ if (store.readOnly) {
1630
+ require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_log, "f").warn("memory deleted remotely but edited locally; keeping the file, " + "which a read-only store cannot push", {
1631
+ path: rel,
1632
+ memory_store_id: store.memoryStoreId
1633
+ });
1634
+ } else if (store.refusedShas.get(rel) !== localSha) {
1635
+ require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_log, "f").info("memory deleted remotely but edited locally; re-creating it from the file", {
1636
+ path: rel,
1637
+ memory_store_id: store.memoryStoreId
1638
+ });
1639
+ }
1640
+ }
1641
+ if (store.readOnly) return undefined;
1642
+ if (store.refusedShas.get(rel) === localSha) return undefined;
1643
+ return await require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_instances, "m", _SessionMemoryStores_upload).call(this, store, rel, localSha, undefined);
1644
+ }
1645
+ const remoteSha = remote.content_sha256;
1646
+ const remoteChanged = remoteSha !== baseSha;
1647
+ const locallyEdited = localSha !== undefined && localSha !== baseSha && localSha !== remoteSha;
1648
+ const localChanged = !store.readOnly && locallyEdited;
1649
+ if (localSha === undefined && baseSha !== undefined) {
1650
+ if (remoteChanged) {
1651
+ require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_log, "f").warn("memory deleted locally but changed remotely; restoring the remote version", {
1652
+ path: rel,
1653
+ memory_store_id: store.memoryStoreId
1654
+ });
1655
+ store.pendingDeletes.delete(rel);
1656
+ pulls.push([rel, remote]);
1657
+ }
1658
+ return baseSha;
1659
+ }
1660
+ if (remoteChanged) {
1661
+ if (localSha === remoteSha) return remoteSha;
1662
+ if (locallyEdited) {
1663
+ require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_log, "f").warn("memory changed both locally and remotely; keeping the remote version", {
1664
+ path: rel,
1665
+ memory_store_id: store.memoryStoreId
1666
+ });
1667
+ }
1668
+ pulls.push([rel, remote]);
1669
+ return baseSha;
1670
+ }
1671
+ if (localChanged) {
1672
+ if (store.refusedShas.get(rel) === localSha) return remoteSha;
1673
+ return await require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_instances, "m", _SessionMemoryStores_upload).call(this, store, rel, localSha, remote) ?? remoteSha;
1674
+ }
1675
+ return remoteSha;
1676
+ }, _SessionMemoryStores_removeLocal = async function _SessionMemoryStores_removeLocal(store, rel, expectSha) {
1677
+ let freshSha;
1678
+ try {
1679
+ freshSha = await store.files.hashFile(rel);
1680
+ } catch (e) {
1681
+ if (!(e instanceof FileStoreError) && !isErrno(e)) throw e;
1682
+ return expectSha;
1683
+ }
1684
+ if (freshSha === null) return undefined;
1685
+ if (freshSha !== expectSha) return freshSha;
1686
+ try {
1687
+ await store.files.remove(rel);
1688
+ } catch (e) {
1689
+ if (!(e instanceof FileStoreError) && !isErrno(e)) throw e;
1690
+ require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_log, "f").warn("failed to remove memory deleted remotely", {
1691
+ path: rel,
1692
+ memory_store_id: store.memoryStoreId,
1693
+ error: String(e)
1694
+ });
1695
+ return expectSha;
1696
+ }
1697
+ return undefined;
1698
+ }, _SessionMemoryStores_write = async function _SessionMemoryStores_write(store, rel, content) {
1699
+ try {
1700
+ await store.files.put(rel, content);
1701
+ } catch (e) {
1702
+ if (!(e instanceof FileStoreError) && !isErrno(e)) throw e;
1703
+ require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_log, "f").warn("failed to write memory", {
1704
+ path: rel,
1705
+ memory_store_id: store.memoryStoreId,
1706
+ error: String(e)
1707
+ });
1708
+ return false;
1709
+ }
1710
+ return true;
1711
+ }, _SessionMemoryStores_pullAll = async function _SessionMemoryStores_pullAll(store, pulls) {
1712
+ if (pulls.length === 0) return;
1713
+ const pullOne = async (rel, listed) => {
1714
+ let item;
1715
+ try {
1716
+ item = await require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_client, "f").beta.memoryStores.memories.retrieve(listed.id, {
1717
+ memory_store_id: store.memoryStoreId,
1718
+ view: "full"
1719
+ });
1720
+ } catch (e) {
1721
+ if (require_sdk.isStatus(e, 404)) return;
1722
+ require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_log, "f").warn("failed to fetch memory content", {
1723
+ path: rel,
1724
+ memory_store_id: store.memoryStoreId,
1725
+ error: String(e)
1726
+ });
1727
+ return;
1728
+ }
1729
+ if (await require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_instances, "m", _SessionMemoryStores_write).call(this, store, rel, item.content ?? "")) {
1730
+ store.baseline.set(rel, item.content_sha256);
1731
+ }
1732
+ };
1733
+ const queue = pulls[Symbol.iterator]();
1734
+ const worker = async () => {
1735
+ for (const [rel, listed] of queue) await pullOne(rel, listed);
1736
+ };
1737
+ await Promise.all(Array.from({ length: Math.min(FETCH_CONCURRENCY, pulls.length) }, worker));
1738
+ }, _SessionMemoryStores_uploadAll = async function _SessionMemoryStores_uploadAll(store, uploads, unsent, signal) {
1739
+ const queue = uploads[Symbol.iterator]();
1740
+ const worker = async () => {
1741
+ for (const [rel, localSha, existing] of queue) {
1742
+ if (signal?.aborted) return;
1743
+ const sha = await require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_instances, "m", _SessionMemoryStores_upload).call(this, store, rel, localSha, existing);
1744
+ unsent.delete(rel);
1745
+ if (sha !== undefined) store.baseline.set(rel, sha);
1746
+ }
1747
+ };
1748
+ await Promise.all(Array.from({ length: Math.min(32, uploads.length) }, worker));
1749
+ }, _SessionMemoryStores_listMemories = async function* _SessionMemoryStores_listMemories(memoryStoreId, view = "basic") {
1750
+ const limit = view === "basic" ? LIST_PAGE_SIZE : FULL_LIST_PAGE_SIZE;
1751
+ for await (const item of require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_client, "f").beta.memoryStores.memories.list(memoryStoreId, {
1752
+ view,
1753
+ limit
1754
+ })) {
1755
+ if (item.type !== "memory") continue;
1756
+ const rel = item.path.replace(/^\/+/, "");
1757
+ if (rel === ".anthropic-memory-store") {
1758
+ require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_log, "f").warn("the server listed the reserved marker path; skipping", {
1759
+ path: item.path,
1760
+ memory_store_id: memoryStoreId
1761
+ });
1762
+ continue;
1763
+ }
1764
+ yield [rel, item];
1765
+ }
1766
+ }, _SessionMemoryStores_upload = async function _SessionMemoryStores_upload(store, rel, localSha, existing) {
1767
+ try {
1768
+ const data = await store.files.get(rel);
1769
+ if (data === null) return undefined;
1770
+ const content = require_sdk.decodeUTF8(data);
1771
+ const item = existing ? await require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_client, "f").beta.memoryStores.memories.update(existing.id, {
1772
+ memory_store_id: store.memoryStoreId,
1773
+ content,
1774
+ precondition: {
1775
+ type: "content_sha256",
1776
+ content_sha256: existing.content_sha256
1777
+ }
1778
+ }) : await require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_client, "f").beta.memoryStores.memories.create(store.memoryStoreId, {
1779
+ path: "/" + rel,
1780
+ content
1781
+ });
1782
+ store.refusedShas.delete(rel);
1783
+ return item.content_sha256;
1784
+ } catch (e) {
1785
+ if (existing && require_sdk.isStatus(e, 404)) {
1786
+ return await require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_instances, "m", _SessionMemoryStores_upload).call(this, store, rel, localSha, undefined);
1787
+ }
1788
+ const permanent = e instanceof FileStoreError || require_sdk.isStatus(e, 400) || require_sdk.isStatus(e, 413);
1789
+ if (existing && require_sdk.isStatus(e, 409)) {
1790
+ require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_log, "f").warn("memory changed both locally and remotely; the upload was refused and the local edit loses", {
1791
+ path: rel,
1792
+ memory_store_id: store.memoryStoreId
1793
+ });
1794
+ } else if (permanent && localSha !== undefined) {
1795
+ store.refusedShas.set(rel, localSha);
1796
+ require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_log, "f").warn("the server rejected this memory file, so it stays un-synced until its content changes", {
1797
+ path: rel,
1798
+ memory_store_id: store.memoryStoreId,
1799
+ rejection: String(e)
1800
+ });
1801
+ } else {
1802
+ require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_log, "f").warn("failed to upload memory", {
1803
+ path: rel,
1804
+ memory_store_id: store.memoryStoreId,
1805
+ error: String(e)
1806
+ });
1807
+ }
1808
+ return undefined;
1809
+ }
1810
+ }, _SessionMemoryStores_corroboratedDelete = async function _SessionMemoryStores_corroboratedDelete(store, rel, remote, baseSha, deletes) {
1811
+ if (deletes.mode === "disabled") {
1812
+ deletes.suppressed++;
1813
+ return baseSha;
1814
+ }
1815
+ let firstAbsent = store.pendingDeletes.get(rel);
1816
+ if (firstAbsent === undefined) {
1817
+ firstAbsent = Date.now();
1818
+ store.pendingDeletes.set(rel, firstAbsent);
1819
+ }
1820
+ if (!deletes.waiveWindow && Date.now() - firstAbsent < 3e4) {
1821
+ return baseSha;
1822
+ }
1823
+ let markerOk;
1824
+ let stillAbsent;
1825
+ try {
1826
+ markerOk = await store.files.hashFile(MARKER_PATH) === markerSha(store.memoryStoreId);
1827
+ stillAbsent = await store.files.hashFile(rel) === null;
1828
+ } catch (e) {
1829
+ if (!(e instanceof FileStoreError) && !isErrno(e)) throw e;
1830
+ markerOk = stillAbsent = false;
1831
+ }
1832
+ if (!markerOk) return baseSha;
1833
+ if (!stillAbsent) {
1834
+ store.pendingDeletes.delete(rel);
1835
+ return baseSha;
1836
+ }
1837
+ if (!deletes.takeSlot()) return baseSha;
1838
+ if (deletes.mode === "log_only") {
1839
+ require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_log, "f").info("log-only: sync would delete this memory on the server", {
1840
+ path: rel,
1841
+ memory_store_id: store.memoryStoreId
1842
+ });
1843
+ return baseSha;
1844
+ }
1845
+ const sha = await require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_instances, "m", _SessionMemoryStores_deleteRemote).call(this, store, rel, remote, baseSha);
1846
+ if (sha === undefined) {
1847
+ store.pendingDeletes.delete(rel);
1848
+ }
1849
+ return sha;
1850
+ }, _SessionMemoryStores_deleteRemote = async function _SessionMemoryStores_deleteRemote(store, rel, remote, baseSha) {
1851
+ try {
1852
+ await require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_client, "f").beta.memoryStores.memories.delete(remote.id, {
1853
+ memory_store_id: store.memoryStoreId,
1854
+ expected_content_sha256: baseSha
1855
+ });
1856
+ } catch (e) {
1857
+ if (require_sdk.isStatus(e, 404)) return undefined;
1858
+ if (require_sdk.isStatus(e, 409) || require_sdk.isStatus(e, 412)) {
1859
+ require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_log, "f").warn("memory deleted locally but changed remotely; keeping the remote version", {
1860
+ path: rel,
1861
+ memory_store_id: store.memoryStoreId
1862
+ });
1863
+ } else {
1864
+ require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_log, "f").warn("failed to delete memory", {
1865
+ path: rel,
1866
+ memory_store_id: store.memoryStoreId,
1867
+ error: String(e)
1868
+ });
1869
+ }
1870
+ return baseSha;
1871
+ }
1872
+ require_sdk.__classPrivateFieldGet(this, _SessionMemoryStores_log, "f").info("propagated local deletion", {
1873
+ path: rel,
1874
+ memory_store_id: store.memoryStoreId
1875
+ });
1876
+ return undefined;
1877
+ };
1878
+ /**
1879
+ * True for a thrown value shaped like a Node filesystem error. Shape-checked,
1880
+ * not `instanceof Error` — fs errors can come from another realm.
1881
+ */
1882
+ function isErrno(e) {
1883
+ return typeof e === "object" && e !== null && typeof e.code === "string";
1884
+ }
1885
+ /**
1886
+ * Resolve when `p` settles, or as soon as `signal` aborts. A rejection from
1887
+ * `p` before the abort propagates; one after it is dropped.
1888
+ */
1889
+ async function settledOrAborted(p, signal) {
1890
+ if (!signal) {
1891
+ await p;
1892
+ return;
1893
+ }
1894
+ let onAbort;
1895
+ const aborted = new Promise((resolve) => {
1896
+ onAbort = resolve;
1897
+ if (signal.aborted) resolve();
1898
+ });
1899
+ signal.addEventListener("abort", onAbort, { once: true });
1900
+ try {
1901
+ await Promise.race([p, aborted]);
1902
+ } finally {
1903
+ signal.removeEventListener("abort", onAbort);
1904
+ }
1905
+ }
1906
+
1907
+ //#endregion
1908
+ //#region ../../node_modules/.pnpm/@anthropic-ai+sdk@0.124.0_zod@4.5.4/node_modules/@anthropic-ai/sdk/tools/agent-toolset/node.mjs
1909
+ /**
1910
+ * Node implementation of the `agent_toolset_20260401` tools — `bash`, `read`,
1911
+ * `write`, `edit`, `glob`, `grep` — plus the workdir/skills
1912
+ * {@link AgentToolContext}.
1913
+ *
1914
+ * This mirrors `@anthropic-ai/sdk/tools/memory/node`: it is the explicit,
1915
+ * Node-only entry point for these implementations. Importing it pulls in
1916
+ * `node:child_process`, `node:fs`, etc., so it is kept separate from the rest of
1917
+ * the SDK — depending on it is an opt-in.
1918
+ *
1919
+ * **Node 22+ is required** for this module: the `glob` tool uses the native
1920
+ * `fs.glob`, added in Node 22. The rest of the SDK still supports Node 18+; only
1921
+ * the agent toolset has this requirement.
1922
+ *
1923
+ * The result of {@link betaAgentToolset20260401} is a plain `BetaRunnableTool[]`;
1924
+ * hand it to any tool runner — `client.beta.messages.toolRunner({ …, tools })`
1925
+ * for the Messages API, or `client.beta.sessions.events.toolRunner({ …, tools })`
1926
+ * for a managed-agents session:
1927
+ *
1928
+ * ```ts
1929
+ * import { betaAgentToolset20260401 } from '@anthropic-ai/sdk/tools/agent-toolset/node';
1930
+ *
1931
+ * const tools = betaAgentToolset20260401({ workdir: '/work' });
1932
+ * const tools2 = betaAgentToolset20260401({ workdir: '/work' }).filter((t) => t.name !== 'bash');
1933
+ * ```
1934
+ *
1935
+ * Trust model: the file tools confine to `workdir` plus any `allowedRoots`
1936
+ * (symlink-aware) and are safe without a sandbox; `bash` is unrestricted and
1937
+ * should run inside one. See {@link AgentToolContext}.
1938
+ */
1939
+ var _BashSession_instances;
1940
+ var _BashSession_proc;
1941
+ var _BashSession_buf;
1942
+ var _BashSession_truncated;
1943
+ var _BashSession_closed;
1944
+ var _BashSession_waiting;
1945
+ var _BashSession_append;
1946
+ var _LineRangeCollector_instances;
1947
+ var _LineRangeCollector_filePath;
1948
+ var _LineRangeCollector_startLine;
1949
+ var _LineRangeCollector_endLine;
1950
+ var _LineRangeCollector_start;
1951
+ var _LineRangeCollector_end;
1952
+ var _LineRangeCollector_limit;
1953
+ var _LineRangeCollector_line;
1954
+ var _LineRangeCollector_collected;
1955
+ var _LineRangeCollector_collectedBytes;
1956
+ var _LineRangeCollector_collect;
1957
+ var _LineRangeCollector_overLimitError;
1958
+ const BASH_OUTPUT_LIMIT = 100 * 1024;
1959
+ const BASH_DEFAULT_TIMEOUT_MS = 12e4;
1960
+ const DEFAULT_MAX_FILE_BYTES = 256 * 1024;
1961
+ const READ_STREAM_CHUNK_BYTES = 64 * 1024;
1962
+ const NEWLINE = Buffer.from("\n");
1963
+ const GREP_OUTPUT_LIMIT = 100 * 1024;
1964
+ const GREP_MAX_LINE_LENGTH = 2e3;
1965
+ const GLOB_RESULT_LIMIT = 200;
1966
+ /**
1967
+ * A bash command exceeded its `timeoutMs`. Carries the timeout so a caller can
1968
+ * tell it apart from an abort without matching on the message text.
1969
+ */
1970
+ var BashTimeoutError = class extends require_sdk.AnthropicError {
1971
+ constructor(timeoutMs) {
1972
+ super(`bash command timed out after ${timeoutMs}ms`);
1973
+ this.name = "BashTimeoutError";
1974
+ this.timeoutMs = timeoutMs;
1975
+ }
1976
+ };
1977
+ const ANSI_RE = /\x1b\[[0-9;?]*[ -/]*[@-~]/g;
1978
+ const fsGlob = node_fs_promises.glob;
1979
+ function resolveMaxBytes(configured) {
1980
+ return configured === undefined ? DEFAULT_MAX_FILE_BYTES : configured;
1981
+ }
1982
+ /**
1983
+ * Throw when the deprecated {@link AgentToolContext.unrestrictedPaths} was
1984
+ * passed at all. Nothing else reads that property.
1985
+ */
1986
+ function rejectUnrestrictedPaths(value) {
1987
+ if (value === undefined) return;
1988
+ throw new require_sdk.AnthropicError("The `unrestrictedPaths` option you passed to the agent toolset (AgentToolContext) is no longer " + "supported. The toolset's file tools (read, write, edit, glob, grep) are now always confined to " + "the working directory plus the directories listed in `allowedRoots`. Remove `unrestrictedPaths` " + "from your context; to let the file tools reach any other directory, add it to `allowedRoots`.");
1989
+ }
1990
+ /**
1991
+ * Returns the `agent_toolset_20260401` implementations bound to `ctx`. The
1992
+ * result is a plain array of `BetaRunnableTool`; filter or extend it before
1993
+ * handing it to a tool runner:
1994
+ *
1995
+ * ```ts
1996
+ * const tools = [...betaAgentToolset20260401(ctx), myCustomTool];
1997
+ * const tools = betaAgentToolset20260401(ctx).filter((t) => t.name !== 'grep');
1998
+ * ```
1999
+ *
2000
+ * Concurrency note: `client.beta.sessions.events.toolRunner` dispatches a
2001
+ * session's tool calls serially (the sessions API delivers one `agent.tool_use`
2002
+ * at a time). `client.beta.messages.toolRunner` runs a turn's `tool.run` calls
2003
+ * via `Promise.all`. The toolset below is safe under either model —
2004
+ * {@link betaBashTool} serializes its persistent shell internally and the FS
2005
+ * tools are independent per call — but {@link betaEditTool}/{@link betaWriteTool}
2006
+ * cannot synchronize concurrent writes to the *same* file across processes, so a
2007
+ * multi-edit turn touching one path is still subject to inherent FS lost-update
2008
+ * races. Custom tools that close over mutable state should do their own queueing.
2009
+ */
2010
+ function betaAgentToolset20260401(ctx) {
2011
+ return [
2012
+ betaBashTool(ctx),
2013
+ betaReadTool(ctx),
2014
+ betaWriteTool(ctx),
2015
+ betaEditTool(ctx),
2016
+ betaGlobTool(ctx),
2017
+ betaGrepTool(ctx)
2018
+ ];
2019
+ }
2020
+ /**
2021
+ * Resolve `p` against `ctx.workdir`; reject results outside `ctx.workdir` and
2022
+ * `ctx.allowedRoots`. Absolute and relative inputs go through the same
2023
+ * canonicalise-then-contain check — an absolute path that lands inside a
2024
+ * permitted root is accepted, only paths that resolve *outside* all of them
2025
+ * are rejected. Every symlink in `p` (including the leaf, even a dangling one)
2026
+ * is resolved before the check, and the resolved path is what the tool then
2027
+ * operates on, so a symlink inside the workdir that points outside it can
2028
+ * neither pass the check nor be followed afterwards. See the trust model on
2029
+ * {@link AgentToolContext}.
2030
+ *
2031
+ * Residual TOCTOU: a component could still be swapped for a symlink between this
2032
+ * call and the eventual `fs` operation. Closing that fully needs per-component
2033
+ * `O_NOFOLLOW`/`openat`, which Node does not expose ergonomically; the same
2034
+ * residual exposure exists in `tools/memory/node` and is why a sandbox is still
2035
+ * recommended for the toolset as a whole.
2036
+ */
2037
+ async function resolvePath(ctx, p) {
2038
+ rejectUnrestrictedPaths(ctx.unrestrictedPaths);
2039
+ return confineToRoot(ctx.workdir, p, { allowedRoots: ctx.allowedRoots ?? [] });
2040
+ }
2041
+ /**
2042
+ * The read-only root `target` falls under, or `undefined`. `target` arrives
2043
+ * fully canonicalized (from {@link resolvePath}), so each root is
2044
+ * canonicalized too — a root recorded through a symlinked workdir must still
2045
+ * match the resolved write target.
2046
+ */
2047
+ function readOnlyRootFor(ctx, target) {
2048
+ return containingRoot(ctx.readOnlyRoots ?? [], target);
2049
+ }
2050
+ /**
2051
+ * Build the environment for the spawned bash shell. The runner process holds
2052
+ * Anthropic credentials in `ANTHROPIC_*` env vars — the API key, the auth token,
2053
+ * and the per-work session token among them. `bash` runs an unrestricted shell,
2054
+ * so any command the agent runs could read those straight out of `process.env`;
2055
+ * strip the whole `ANTHROPIC_*` namespace from the child's environment.
2056
+ * Everything else (PATH, HOME, locale, …) is passed through unchanged.
2057
+ *
2058
+ * Passing an explicit `env` to {@link AgentToolContext} does NOT add to this
2059
+ * default — it FULLY REPLACES it. The provided mapping becomes the entire bash
2060
+ * environment verbatim; nothing here is merged in, so callers who want the
2061
+ * scrubbed process environment plus extras must build that mapping themselves.
2062
+ */
2063
+ function scrubbedShellEnv() {
2064
+ const env = {};
2065
+ for (const [key, value] of Object.entries(process.env)) {
2066
+ if (key.startsWith("ANTHROPIC_")) continue;
2067
+ env[key] = value;
2068
+ }
2069
+ return env;
2070
+ }
2071
+ /**
2072
+ * A persistent /bin/bash process. State (cwd, env, background jobs) survives
2073
+ * across exec() calls. Uses pipes rather than a PTY so input is never echoed.
2074
+ */
2075
+ var BashSession = class {
2076
+ constructor(dir, env = scrubbedShellEnv()) {
2077
+ _BashSession_instances.add(this);
2078
+ _BashSession_proc.set(this, void 0);
2079
+ _BashSession_buf.set(this, "");
2080
+ _BashSession_truncated.set(this, false);
2081
+ _BashSession_closed.set(this, false);
2082
+ _BashSession_waiting.set(this, null);
2083
+ require_sdk.__classPrivateFieldSet(this, _BashSession_proc, node_child_process.spawn("/bin/bash", ["--noprofile", "--norc"], {
2084
+ cwd: dir,
2085
+ env: {
2086
+ ...env,
2087
+ PS1: "",
2088
+ PS2: "",
2089
+ TERM: "dumb"
2090
+ },
2091
+ stdio: [
2092
+ "pipe",
2093
+ "pipe",
2094
+ "pipe"
2095
+ ],
2096
+ detached: true
2097
+ }), "f");
2098
+ require_sdk.__classPrivateFieldGet(this, _BashSession_proc, "f").stdout.setEncoding("utf8");
2099
+ require_sdk.__classPrivateFieldGet(this, _BashSession_proc, "f").stderr.setEncoding("utf8");
2100
+ require_sdk.__classPrivateFieldGet(this, _BashSession_proc, "f").stdout.on("data", (d) => require_sdk.__classPrivateFieldGet(this, _BashSession_instances, "m", _BashSession_append).call(this, d));
2101
+ require_sdk.__classPrivateFieldGet(this, _BashSession_proc, "f").stderr.on("data", (d) => require_sdk.__classPrivateFieldGet(this, _BashSession_instances, "m", _BashSession_append).call(this, d));
2102
+ require_sdk.__classPrivateFieldGet(this, _BashSession_proc, "f").once("close", () => {
2103
+ require_sdk.__classPrivateFieldSet(this, _BashSession_closed, true, "f");
2104
+ const w = require_sdk.__classPrivateFieldGet(this, _BashSession_waiting, "f");
2105
+ require_sdk.__classPrivateFieldSet(this, _BashSession_waiting, null, "f");
2106
+ w?.resolve();
2107
+ });
2108
+ }
2109
+ /** Whether the underlying shell process has exited. */
2110
+ get closed() {
2111
+ return require_sdk.__classPrivateFieldGet(this, _BashSession_closed, "f");
2112
+ }
2113
+ async exec(command, opts = {}) {
2114
+ if (require_sdk.__classPrivateFieldGet(this, _BashSession_closed, "f")) {
2115
+ throw new require_sdk.AnthropicError("bash session terminated");
2116
+ }
2117
+ const timeoutMs = opts.timeoutMs ?? BASH_DEFAULT_TIMEOUT_MS;
2118
+ const signal = opts.signal;
2119
+ signal?.throwIfAborted();
2120
+ require_sdk.__classPrivateFieldSet(this, _BashSession_buf, "", "f");
2121
+ require_sdk.__classPrivateFieldSet(this, _BashSession_truncated, false, "f");
2122
+ const sentinel = `__ANT_CMD_${node_crypto.randomUUID()}_DONE__`;
2123
+ const sentinelSplit = `${sentinel.slice(0, 8)}''${sentinel.slice(8)}`;
2124
+ const wrapped = `{ ${command}\n} </dev/null 2>&1; printf '\\n${sentinelSplit}%d\\n' $?\n`;
2125
+ require_sdk.__classPrivateFieldGet(this, _BashSession_proc, "f").stdin.write(wrapped);
2126
+ if (require_sdk.__classPrivateFieldGet(this, _BashSession_buf, "f").indexOf(sentinel) < 0) {
2127
+ const { promise: sentinelSeen, resolve } = require_sdk.promiseWithResolvers();
2128
+ require_sdk.__classPrivateFieldSet(this, _BashSession_waiting, {
2129
+ sentinel,
2130
+ resolve
2131
+ }, "f");
2132
+ let timer;
2133
+ let onAbort;
2134
+ try {
2135
+ await Promise.race([
2136
+ sentinelSeen,
2137
+ new Promise((_, reject) => {
2138
+ timer = setTimeout(() => reject(new BashTimeoutError(timeoutMs)), timeoutMs);
2139
+ }),
2140
+ new Promise((_, reject) => {
2141
+ if (!signal) return;
2142
+ onAbort = () => reject(signal.reason);
2143
+ signal.addEventListener("abort", onAbort, { once: true });
2144
+ })
2145
+ ]);
2146
+ } finally {
2147
+ if (timer) clearTimeout(timer);
2148
+ if (onAbort && signal) signal.removeEventListener("abort", onAbort);
2149
+ require_sdk.__classPrivateFieldSet(this, _BashSession_waiting, null, "f");
2150
+ }
2151
+ }
2152
+ const idx = require_sdk.__classPrivateFieldGet(this, _BashSession_buf, "f").indexOf(sentinel);
2153
+ if (idx < 0) {
2154
+ throw new require_sdk.AnthropicError("bash session terminated");
2155
+ }
2156
+ const tail = require_sdk.__classPrivateFieldGet(this, _BashSession_buf, "f").slice(idx + sentinel.length);
2157
+ const m = tail.match(/^(-?\d+)/);
2158
+ const exitCode = m ? parseInt(m[1], 10) : -1;
2159
+ let out = require_sdk.__classPrivateFieldGet(this, _BashSession_buf, "f").slice(0, idx).replace(ANSI_RE, "").replace(/\n+$/, "");
2160
+ if (require_sdk.__classPrivateFieldGet(this, _BashSession_truncated, "f")) {
2161
+ out = `[output truncated]\n${out}`;
2162
+ }
2163
+ return {
2164
+ output: out,
2165
+ exitCode
2166
+ };
2167
+ }
2168
+ close() {
2169
+ if (require_sdk.__classPrivateFieldGet(this, _BashSession_closed, "f")) return;
2170
+ require_sdk.__classPrivateFieldSet(this, _BashSession_closed, true, "f");
2171
+ const w = require_sdk.__classPrivateFieldGet(this, _BashSession_waiting, "f");
2172
+ require_sdk.__classPrivateFieldSet(this, _BashSession_waiting, null, "f");
2173
+ w?.resolve();
2174
+ require_sdk.__classPrivateFieldGet(this, _BashSession_proc, "f").stdout.destroy();
2175
+ require_sdk.__classPrivateFieldGet(this, _BashSession_proc, "f").stderr.destroy();
2176
+ require_sdk.__classPrivateFieldGet(this, _BashSession_proc, "f").stdin.destroy();
2177
+ try {
2178
+ process.kill(-require_sdk.__classPrivateFieldGet(this, _BashSession_proc, "f").pid, "SIGKILL");
2179
+ } catch {
2180
+ require_sdk.__classPrivateFieldGet(this, _BashSession_proc, "f").kill("SIGKILL");
2181
+ }
2182
+ require_sdk.__classPrivateFieldGet(this, _BashSession_proc, "f").unref();
2183
+ }
2184
+ };
2185
+ _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) {
2186
+ require_sdk.__classPrivateFieldSet(this, _BashSession_buf, require_sdk.__classPrivateFieldGet(this, _BashSession_buf, "f") + d, "f");
2187
+ if (require_sdk.__classPrivateFieldGet(this, _BashSession_buf, "f").length > BASH_OUTPUT_LIMIT) {
2188
+ 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");
2189
+ require_sdk.__classPrivateFieldSet(this, _BashSession_truncated, true, "f");
2190
+ }
2191
+ 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) {
2192
+ const w = require_sdk.__classPrivateFieldGet(this, _BashSession_waiting, "f");
2193
+ require_sdk.__classPrivateFieldSet(this, _BashSession_waiting, null, "f");
2194
+ w.resolve();
2195
+ }
2196
+ };
2197
+ function betaBashTool(ctx) {
2198
+ rejectUnrestrictedPaths(ctx.unrestrictedPaths);
2199
+ let session;
2200
+ let tail = Promise.resolve();
2201
+ return betaTool({
2202
+ name: "bash",
2203
+ description: "Run a bash command in a persistent shell. State (cwd, env vars) persists across calls.",
2204
+ inputSchema: {
2205
+ type: "object",
2206
+ properties: {
2207
+ command: {
2208
+ type: "string",
2209
+ description: "The command to run"
2210
+ },
2211
+ restart: {
2212
+ type: "boolean",
2213
+ description: "Restart the persistent shell before running"
2214
+ },
2215
+ timeout_ms: {
2216
+ type: "integer",
2217
+ description: "Per-call timeout in milliseconds"
2218
+ }
2219
+ }
2220
+ },
2221
+ run: async ({ command, restart, timeout_ms }, context) => {
2222
+ const prev = tail;
2223
+ const gate = require_sdk.promiseWithResolvers();
2224
+ tail = gate.promise;
2225
+ try {
2226
+ await prev;
2227
+ } catch {}
2228
+ try {
2229
+ if (restart) {
2230
+ session?.close();
2231
+ session = undefined;
2232
+ }
2233
+ if (!command) {
2234
+ if (restart) return "bash session restarted";
2235
+ throw new require_sdk.ToolError("bash: command is required");
2236
+ }
2237
+ session ?? (session = new BashSession(ctx.workdir, ctx.env));
2238
+ try {
2239
+ const { output, exitCode } = await session.exec(command, {
2240
+ timeoutMs: timeout_ms ?? BASH_DEFAULT_TIMEOUT_MS,
2241
+ signal: context?.signal
2242
+ });
2243
+ if (exitCode !== 0) throw new require_sdk.ToolError(output || `exit ${exitCode}`);
2244
+ return output;
2245
+ } catch (e) {
2246
+ if (e instanceof require_sdk.ToolError) throw e;
2247
+ session.close();
2248
+ session = undefined;
2249
+ throw new require_sdk.ToolError(`bash: ${e instanceof Error ? e.message : String(e)}`);
2250
+ }
2251
+ } finally {
2252
+ gate.resolve();
2253
+ }
2254
+ },
2255
+ close: () => {
2256
+ session?.close();
2257
+ session = undefined;
2258
+ }
2259
+ });
2260
+ }
2261
+ function betaReadTool(ctx) {
2262
+ rejectUnrestrictedPaths(ctx.unrestrictedPaths);
2263
+ return betaTool({
2264
+ name: "read",
2265
+ description: "Read a UTF-8 text file relative to the workdir.",
2266
+ inputSchema: {
2267
+ type: "object",
2268
+ properties: {
2269
+ file_path: { type: "string" },
2270
+ view_range: {
2271
+ type: "array",
2272
+ items: { type: "integer" },
2273
+ description: "[start_line, end_line] 1-indexed inclusive"
2274
+ }
2275
+ },
2276
+ required: ["file_path"]
2277
+ },
2278
+ run: async ({ file_path, view_range }) => {
2279
+ if (!file_path) throw new require_sdk.ToolError("read: file_path is required");
2280
+ const abs = await resolvePath(ctx, file_path);
2281
+ if (view_range?.length && view_range.length !== 2) {
2282
+ throw new require_sdk.ToolError("read: view_range must be [start_line, end_line]");
2283
+ }
2284
+ let data;
2285
+ try {
2286
+ const st = await node_fs_promises.stat(abs);
2287
+ if (!st.isFile()) {
2288
+ throw new require_sdk.ToolError(`read: ${file_path} is not a regular file`);
2289
+ }
2290
+ const limit = resolveMaxBytes(ctx.maxFileBytes);
2291
+ if (limit !== null && st.size > limit) {
2292
+ if (!view_range?.length) {
2293
+ throw new require_sdk.ToolError(`read: ${file_path} is ${st.size} bytes, exceeds ${limit}-byte limit. ` + "Use the view_range parameter to read specific line ranges, e.g. view_range: [1, 500].");
2294
+ }
2295
+ const [startLine, endLine] = view_range;
2296
+ return await readRangeStreaming(abs, file_path, startLine, endLine, limit);
2297
+ }
2298
+ data = await node_fs_promises.readFile(abs, "utf8");
2299
+ } catch (e) {
2300
+ if (e instanceof require_sdk.ToolError) throw e;
2301
+ throw new require_sdk.ToolError(`read: ${fsErrorMessage(e, file_path)}`);
2302
+ }
2303
+ if (!view_range?.length) return data;
2304
+ const [startLine, endLine] = view_range;
2305
+ const lines = data.split("\n");
2306
+ const start = Math.max(0, startLine - 1);
2307
+ const end = endLine > 0 ? endLine : lines.length;
2308
+ return lines.slice(start, end).join("\n");
2309
+ }
2310
+ });
2311
+ }
2312
+ /** Returns lines `[startLine, endLine]` of the file at `abs`, capping the selected bytes at `limit`. */
2313
+ async function readRangeStreaming(abs, filePath, startLine, endLine, limit) {
2314
+ const lines = new LineRangeCollector(filePath, startLine, endLine, limit);
2315
+ if (lines.rangeIsEmpty()) return "";
2316
+ const stream = node_fs.createReadStream(abs, { highWaterMark: READ_STREAM_CHUNK_BYTES });
2317
+ try {
2318
+ for await (const chunk of stream) {
2319
+ lines.collectFrom(chunk);
2320
+ if (lines.rangeIsCollected()) break;
2321
+ }
2322
+ } finally {
2323
+ stream.destroy();
2324
+ }
2325
+ return lines.text();
2326
+ }
2327
+ /** Collects the bytes of lines `[startLine, endLine]` from consecutive file chunks, capped at `limit`. */
2328
+ var LineRangeCollector = class {
2329
+ constructor(filePath, startLine, endLine, limit) {
2330
+ _LineRangeCollector_instances.add(this);
2331
+ _LineRangeCollector_filePath.set(this, void 0);
2332
+ _LineRangeCollector_startLine.set(this, void 0);
2333
+ _LineRangeCollector_endLine.set(this, void 0);
2334
+ _LineRangeCollector_start.set(this, void 0);
2335
+ _LineRangeCollector_end.set(this, void 0);
2336
+ _LineRangeCollector_limit.set(this, void 0);
2337
+ _LineRangeCollector_line.set(this, 0);
2338
+ _LineRangeCollector_collected.set(this, []);
2339
+ _LineRangeCollector_collectedBytes.set(this, 0);
2340
+ require_sdk.__classPrivateFieldSet(this, _LineRangeCollector_filePath, filePath, "f");
2341
+ require_sdk.__classPrivateFieldSet(this, _LineRangeCollector_startLine, startLine, "f");
2342
+ require_sdk.__classPrivateFieldSet(this, _LineRangeCollector_endLine, endLine, "f");
2343
+ require_sdk.__classPrivateFieldSet(this, _LineRangeCollector_start, Math.max(0, startLine - 1), "f");
2344
+ require_sdk.__classPrivateFieldSet(this, _LineRangeCollector_end, endLine > 0 ? endLine : Infinity, "f");
2345
+ require_sdk.__classPrivateFieldSet(this, _LineRangeCollector_limit, limit, "f");
2346
+ }
2347
+ rangeIsEmpty() {
2348
+ return require_sdk.__classPrivateFieldGet(this, _LineRangeCollector_end, "f") <= require_sdk.__classPrivateFieldGet(this, _LineRangeCollector_start, "f");
2349
+ }
2350
+ rangeIsCollected() {
2351
+ return require_sdk.__classPrivateFieldGet(this, _LineRangeCollector_line, "f") >= require_sdk.__classPrivateFieldGet(this, _LineRangeCollector_end, "f");
2352
+ }
2353
+ collectFrom(chunk) {
2354
+ var _a;
2355
+ let lineStart = 0;
2356
+ while (lineStart < chunk.length && !this.rangeIsCollected()) {
2357
+ const newline = chunk.indexOf(10, lineStart);
2358
+ const lineEnd = newline < 0 ? chunk.length : newline;
2359
+ if (require_sdk.__classPrivateFieldGet(this, _LineRangeCollector_line, "f") >= require_sdk.__classPrivateFieldGet(this, _LineRangeCollector_start, "f")) {
2360
+ require_sdk.__classPrivateFieldGet(this, _LineRangeCollector_instances, "m", _LineRangeCollector_collect).call(this, chunk.subarray(lineStart, lineEnd), newline >= 0);
2361
+ }
2362
+ if (newline < 0) break;
2363
+ require_sdk.__classPrivateFieldSet(this, _LineRangeCollector_line, (_a = require_sdk.__classPrivateFieldGet(this, _LineRangeCollector_line, "f"), _a++, _a), "f");
2364
+ lineStart = newline + 1;
2365
+ }
2366
+ }
2367
+ text() {
2368
+ return Buffer.concat(require_sdk.__classPrivateFieldGet(this, _LineRangeCollector_collected, "f"), require_sdk.__classPrivateFieldGet(this, _LineRangeCollector_collectedBytes, "f")).toString("utf8");
2369
+ }
2370
+ };
2371
+ _LineRangeCollector_filePath = new WeakMap(), _LineRangeCollector_startLine = new WeakMap(), _LineRangeCollector_endLine = new WeakMap(), _LineRangeCollector_start = new WeakMap(), _LineRangeCollector_end = new WeakMap(), _LineRangeCollector_limit = new WeakMap(), _LineRangeCollector_line = new WeakMap(), _LineRangeCollector_collected = new WeakMap(), _LineRangeCollector_collectedBytes = new WeakMap(), _LineRangeCollector_instances = new WeakSet(), _LineRangeCollector_collect = function _LineRangeCollector_collect(lineBytes, newlineTerminated) {
2372
+ require_sdk.__classPrivateFieldGet(this, _LineRangeCollector_collected, "f").push(lineBytes);
2373
+ require_sdk.__classPrivateFieldSet(this, _LineRangeCollector_collectedBytes, require_sdk.__classPrivateFieldGet(this, _LineRangeCollector_collectedBytes, "f") + lineBytes.length, "f");
2374
+ if (newlineTerminated && require_sdk.__classPrivateFieldGet(this, _LineRangeCollector_line, "f") + 1 < require_sdk.__classPrivateFieldGet(this, _LineRangeCollector_end, "f")) {
2375
+ require_sdk.__classPrivateFieldGet(this, _LineRangeCollector_collected, "f").push(NEWLINE);
2376
+ require_sdk.__classPrivateFieldSet(this, _LineRangeCollector_collectedBytes, require_sdk.__classPrivateFieldGet(this, _LineRangeCollector_collectedBytes, "f") + NEWLINE.length, "f");
2377
+ }
2378
+ if (require_sdk.__classPrivateFieldGet(this, _LineRangeCollector_collectedBytes, "f") > require_sdk.__classPrivateFieldGet(this, _LineRangeCollector_limit, "f")) throw require_sdk.__classPrivateFieldGet(this, _LineRangeCollector_instances, "m", _LineRangeCollector_overLimitError).call(this);
2379
+ }, _LineRangeCollector_overLimitError = function _LineRangeCollector_overLimitError() {
2380
+ if (require_sdk.__classPrivateFieldGet(this, _LineRangeCollector_end, "f") - require_sdk.__classPrivateFieldGet(this, _LineRangeCollector_start, "f") === 1) {
2381
+ return new require_sdk.ToolError(`read: line ${require_sdk.__classPrivateFieldGet(this, _LineRangeCollector_start, "f") + 1} of ${require_sdk.__classPrivateFieldGet(this, _LineRangeCollector_filePath, "f")} alone exceeds ${require_sdk.__classPrivateFieldGet(this, _LineRangeCollector_limit, "f")}-byte limit. ` + "The read tool cannot return part of a line, so view_range cannot narrow this further.");
2382
+ }
2383
+ return new require_sdk.ToolError(`read: view_range [${require_sdk.__classPrivateFieldGet(this, _LineRangeCollector_startLine, "f")}, ${require_sdk.__classPrivateFieldGet(this, _LineRangeCollector_endLine, "f")}] of ${require_sdk.__classPrivateFieldGet(this, _LineRangeCollector_filePath, "f")} exceeds ${require_sdk.__classPrivateFieldGet(this, _LineRangeCollector_limit, "f")}-byte limit. ` + "Narrow the view_range to read a smaller portion.");
2384
+ };
2385
+ function betaWriteTool(ctx) {
2386
+ rejectUnrestrictedPaths(ctx.unrestrictedPaths);
2387
+ return betaTool({
2388
+ name: "write",
2389
+ description: "Write a UTF-8 text file relative to the workdir, creating parent directories as needed.",
2390
+ inputSchema: {
2391
+ type: "object",
2392
+ properties: {
2393
+ file_path: { type: "string" },
2394
+ content: { type: "string" }
2395
+ },
2396
+ required: ["file_path", "content"]
2397
+ },
2398
+ run: async ({ file_path, content }) => {
2399
+ if (!file_path) throw new require_sdk.ToolError("write: file_path is required");
2400
+ const abs = await resolvePath(ctx, file_path);
2401
+ const ro = await readOnlyRootFor(ctx, abs);
2402
+ if (ro !== undefined) {
2403
+ throw new require_sdk.ToolError(`write: ${file_path} is inside read-only directory ${ro}`);
2404
+ }
2405
+ try {
2406
+ await node_fs_promises.mkdir(node_path.dirname(abs), {
2407
+ recursive: true,
2408
+ mode: 448
2409
+ });
2410
+ await atomicWriteFile(abs, content ?? "");
2411
+ } catch (e) {
2412
+ throw new require_sdk.ToolError(`write: ${fsErrorMessage(e, file_path)}`);
2413
+ }
2414
+ return `wrote ${Buffer.byteLength(content ?? "")} bytes to ${file_path}`;
2415
+ }
2416
+ });
2417
+ }
2418
+ function betaEditTool(ctx) {
2419
+ rejectUnrestrictedPaths(ctx.unrestrictedPaths);
2420
+ return betaTool({
2421
+ name: "edit",
2422
+ description: "Replace old_string with new_string in a file. old_string must be unique unless replace_all.",
2423
+ inputSchema: {
2424
+ type: "object",
2425
+ properties: {
2426
+ file_path: { type: "string" },
2427
+ old_string: { type: "string" },
2428
+ new_string: { type: "string" },
2429
+ replace_all: { type: "boolean" }
2430
+ },
2431
+ required: [
2432
+ "file_path",
2433
+ "old_string",
2434
+ "new_string"
2435
+ ]
2436
+ },
2437
+ run: async ({ file_path, old_string, new_string, replace_all }) => {
2438
+ if (!file_path) throw new require_sdk.ToolError("edit: file_path is required");
2439
+ if (!old_string) throw new require_sdk.ToolError("edit: old_string is required");
2440
+ const abs = await resolvePath(ctx, file_path);
2441
+ const ro = await readOnlyRootFor(ctx, abs);
2442
+ if (ro !== undefined) {
2443
+ throw new require_sdk.ToolError(`edit: ${file_path} is inside read-only directory ${ro}`);
2444
+ }
2445
+ let data;
2446
+ try {
2447
+ const st = await node_fs_promises.stat(abs);
2448
+ if (!st.isFile()) {
2449
+ throw new require_sdk.ToolError(`edit: ${file_path} is not a regular file`);
2450
+ }
2451
+ const limit = resolveMaxBytes(ctx.maxFileBytes);
2452
+ if (limit !== null && st.size > limit) {
2453
+ throw new require_sdk.ToolError(`edit: ${file_path} is ${st.size} bytes, exceeds ${limit}-byte limit. ` + "The edit tool loads the whole file and cannot modify a file this large.");
2454
+ }
2455
+ data = await node_fs_promises.readFile(abs, "utf8");
2456
+ } catch (e) {
2457
+ if (e instanceof require_sdk.ToolError) throw e;
2458
+ throw new require_sdk.ToolError(`edit: ${fsErrorMessage(e, file_path)}`);
2459
+ }
2460
+ const count = data.split(old_string).length - 1;
2461
+ if (count === 0) throw new require_sdk.ToolError(`edit: old_string not found in ${file_path}`);
2462
+ let updated;
2463
+ if (replace_all) {
2464
+ updated = data.split(old_string).join(new_string);
2465
+ } else {
2466
+ if (count > 1) throw new require_sdk.ToolError(`edit: old_string appears ${count} times in ${file_path} (must be unique)`);
2467
+ updated = data.replace(old_string, () => new_string);
2468
+ }
2469
+ try {
2470
+ await atomicWriteFile(abs, updated);
2471
+ } catch (e) {
2472
+ throw new require_sdk.ToolError(`edit: write: ${fsErrorMessage(e, file_path)}`);
2473
+ }
2474
+ return `edited ${file_path} (${replace_all ? count : 1} replacement(s))`;
2475
+ }
2476
+ });
2477
+ }
2478
+ /**
2479
+ * Best-effort: stops `fs.glob` from walking out of the root via a literal or
2480
+ * brace-expanded `..`. The realpath post-filter in {@link betaGlobTool} is the
2481
+ * boundary; this only avoids the walk.
2482
+ */
2483
+ function patternCanAscend(pattern) {
2484
+ return pattern.split(/[\\/{},]/).includes("..");
2485
+ }
2486
+ function betaGlobTool(ctx) {
2487
+ rejectUnrestrictedPaths(ctx.unrestrictedPaths);
2488
+ return betaTool({
2489
+ name: "glob",
2490
+ description: "Match files under the workdir against a glob pattern. Results are mtime-sorted, newest first.",
2491
+ inputSchema: {
2492
+ type: "object",
2493
+ properties: {
2494
+ pattern: { type: "string" },
2495
+ path: {
2496
+ type: "string",
2497
+ description: "Directory to search in. Defaults to the workdir."
2498
+ }
2499
+ },
2500
+ required: ["pattern"]
2501
+ },
2502
+ run: async ({ pattern, path: searchPath }) => {
2503
+ if (!pattern) throw new require_sdk.ToolError("glob: pattern is required");
2504
+ if (node_path.isAbsolute(pattern)) {
2505
+ throw new require_sdk.ToolError("glob: absolute pattern not permitted; pass a relative pattern (and optionally path)");
2506
+ }
2507
+ if (patternCanAscend(pattern)) {
2508
+ throw new require_sdk.ToolError("glob: \"..\" is not permitted in the pattern");
2509
+ }
2510
+ const root = searchPath ? await resolvePath(ctx, searchPath) : node_path.resolve(ctx.workdir);
2511
+ const realRoot = searchPath ? root : await canonicalize(root);
2512
+ const matches = [];
2513
+ let remaining = WALK_MAX_ENTRIES;
2514
+ try {
2515
+ for await (const entry of fsGlob(pattern, {
2516
+ cwd: root,
2517
+ withFileTypes: true,
2518
+ exclude: (d) => d.name === ".git" || d.name === "node_modules"
2519
+ })) {
2520
+ if (remaining-- <= 0) break;
2521
+ if (!entry.isFile()) continue;
2522
+ const full = node_path.join(entry.parentPath, entry.name);
2523
+ let real;
2524
+ try {
2525
+ real = await node_fs_promises.realpath(full);
2526
+ } catch {
2527
+ continue;
2528
+ }
2529
+ if (!isWithin(realRoot, real)) continue;
2530
+ let mtime = 0;
2531
+ try {
2532
+ mtime = (await node_fs_promises.stat(full)).mtimeMs;
2533
+ } catch {}
2534
+ matches.push({
2535
+ path: full,
2536
+ mtime
2537
+ });
2538
+ }
2539
+ } catch (e) {
2540
+ throw new require_sdk.ToolError(`glob: ${e instanceof Error ? e.message : String(e)}`);
2541
+ }
2542
+ if (matches.length === 0) return "no matches";
2543
+ matches.sort((a, b) => b.mtime - a.mtime);
2544
+ return matches.slice(0, GLOB_RESULT_LIMIT).map((m) => m.path).join("\n");
2545
+ }
2546
+ });
2547
+ }
2548
+ function betaGrepTool(ctx) {
2549
+ rejectUnrestrictedPaths(ctx.unrestrictedPaths);
2550
+ return betaTool({
2551
+ name: "grep",
2552
+ description: "Search file contents for a regex. Uses ripgrep if available, otherwise a built-in walker.",
2553
+ inputSchema: {
2554
+ type: "object",
2555
+ properties: {
2556
+ pattern: { type: "string" },
2557
+ path: { type: "string" }
2558
+ },
2559
+ required: ["pattern"]
2560
+ },
2561
+ run: async ({ pattern, path: p }, context) => {
2562
+ if (!pattern) throw new require_sdk.ToolError("grep: pattern is required");
2563
+ let searchPath = node_path.resolve(ctx.workdir);
2564
+ if (p) searchPath = await resolvePath(ctx, p);
2565
+ const rg = await findRg();
2566
+ return rg ? runRipgrep(rg, pattern, searchPath, context?.signal) : runWalkGrep(pattern, searchPath, context?.signal);
2567
+ }
2568
+ });
2569
+ }
2570
+ function runRipgrep(rg, pattern, searchPath, signal) {
2571
+ return new Promise((resolve, reject) => {
2572
+ const proc = node_child_process.spawn(rg, [
2573
+ "-n",
2574
+ "--no-heading",
2575
+ "-e",
2576
+ pattern,
2577
+ "--",
2578
+ searchPath
2579
+ ], { ...signal ? { signal } : {} });
2580
+ let out = "";
2581
+ let errOut = "";
2582
+ let truncated = false;
2583
+ proc.stdout.on("data", (d) => {
2584
+ if (truncated) return;
2585
+ out += d;
2586
+ if (out.length > GREP_OUTPUT_LIMIT) {
2587
+ truncated = true;
2588
+ out = out.slice(0, GREP_OUTPUT_LIMIT);
2589
+ proc.kill("SIGKILL");
2590
+ }
2591
+ });
2592
+ proc.stderr.on("data", (d) => errOut += d);
2593
+ proc.on("close", (code) => {
2594
+ if (signal?.aborted) return reject(new require_sdk.ToolError("grep: aborted"));
2595
+ if (truncated) return resolve(out + `\n[output truncated at ${GREP_OUTPUT_LIMIT} bytes]`);
2596
+ if (code === 0) return resolve(out);
2597
+ if (code === 1) return resolve("no matches");
2598
+ reject(new require_sdk.ToolError(`grep: rg failed: ${errOut || `exit ${code}`}`));
2599
+ });
2600
+ proc.on("error", (e) => {
2601
+ if (signal?.aborted) return reject(new require_sdk.ToolError("grep: aborted"));
2602
+ reject(new require_sdk.ToolError(`grep: rg failed: ${e.message}`));
2603
+ });
2604
+ });
2605
+ }
2606
+ async function runWalkGrep(pattern, root, signal) {
2607
+ let re;
2608
+ try {
2609
+ re = new RegExp(pattern);
2610
+ } catch (e) {
2611
+ throw new require_sdk.ToolError(`grep: invalid regex: ${e instanceof Error ? e.message : String(e)}`);
2612
+ }
2613
+ const hits = [];
2614
+ let budget = GREP_OUTPUT_LIMIT;
2615
+ const push = (line) => {
2616
+ budget -= line.length + 1;
2617
+ if (budget < 0) {
2618
+ hits.push(`[output truncated at ${GREP_OUTPUT_LIMIT} bytes]`);
2619
+ return false;
2620
+ }
2621
+ hits.push(line);
2622
+ return true;
2623
+ };
2624
+ const stat = await node_fs_promises.stat(root).catch(() => null);
2625
+ if (stat?.isFile()) {
2626
+ await grepFile(root, re, push);
2627
+ } else {
2628
+ await walk(root, "", (rel) => grepFile(node_path.join(root, rel), re, push), signal);
2629
+ }
2630
+ if (signal?.aborted) throw new require_sdk.ToolError("grep: aborted");
2631
+ if (hits.length === 0) return "no matches";
2632
+ return hits.join("\n");
2633
+ }
2634
+ async function grepFile(file, re, push) {
2635
+ const stream = node_fs.createReadStream(file, { encoding: "utf8" });
2636
+ const rl = node_readline.createInterface({
2637
+ input: stream,
2638
+ crlfDelay: Infinity
2639
+ });
2640
+ let i = 0;
2641
+ try {
2642
+ for await (const line of rl) {
2643
+ i++;
2644
+ if (line.length > GREP_MAX_LINE_LENGTH) continue;
2645
+ if (re.test(line) && !push(`${file}:${i}:${line}`)) return false;
2646
+ }
2647
+ } catch {} finally {
2648
+ stream.destroy();
2649
+ }
2650
+ return true;
2651
+ }
2652
+ const WALK_MAX_DEPTH = 40;
2653
+ const WALK_MAX_ENTRIES = 5e4;
2654
+ /**
2655
+ * Bounded recursive walk. `fn` may return `false` to abort. Only real
2656
+ * directories are descended into and only real files are handed to `fn` —
2657
+ * symlinks (and devices/fifos/sockets) are skipped entirely so a symlink inside
2658
+ * the root cannot be followed out of it.
2659
+ */
2660
+ async function walk(root, rel, fn, signal) {
2661
+ let remaining = WALK_MAX_ENTRIES;
2662
+ async function inner(rel, depth) {
2663
+ if (depth > WALK_MAX_DEPTH) return true;
2664
+ if (signal?.aborted) return false;
2665
+ let entries;
2666
+ try {
2667
+ entries = await node_fs_promises.readdir(node_path.join(root, rel), { withFileTypes: true });
2668
+ } catch {
2669
+ return true;
2670
+ }
2671
+ for (const e of entries) {
2672
+ if (e.name === ".git" || e.name === "node_modules") continue;
2673
+ if (remaining-- <= 0) return false;
2674
+ if (signal?.aborted) return false;
2675
+ const childRel = rel ? node_path.join(rel, e.name) : e.name;
2676
+ if (e.isDirectory()) {
2677
+ if (!await inner(childRel, depth + 1)) return false;
2678
+ } else if (e.isFile()) {
2679
+ if (await fn(childRel) === false) return false;
2680
+ }
2681
+ }
2682
+ return true;
2683
+ }
2684
+ await inner(rel, 0);
2685
+ }
2686
+ async function findRg() {
2687
+ const dirs = (process.env["PATH"] ?? "").split(node_path.delimiter);
2688
+ for (const d of dirs) {
2689
+ const candidate = node_path.join(d, "rg");
2690
+ try {
2691
+ await node_fs_promises.access(candidate, node_fs.constants.X_OK);
2692
+ return candidate;
2693
+ } catch {}
2694
+ }
2695
+ return null;
2696
+ }
2697
+
2698
+ //#endregion
2699
+ exports.MEMORY_FLUSH_TIMEOUT_MS = MEMORY_FLUSH_TIMEOUT_MS;
2700
+ exports.SessionMemoryError = SessionMemoryError;
2701
+ exports.SessionMemoryStores = SessionMemoryStores;
2702
+ exports.betaAgentToolset20260401 = betaAgentToolset20260401;
2703
+ exports.setupSkills = setupSkills;