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