@vibedev-si/dsh-vibedev 0.2.1

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