@ddlqhd/agent-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +53 -0
  2. package/dist/chunk-5QMA2YBY.cjs +2880 -0
  3. package/dist/chunk-5QMA2YBY.cjs.map +1 -0
  4. package/dist/chunk-5Y56A64C.cjs +5 -0
  5. package/dist/chunk-5Y56A64C.cjs.map +1 -0
  6. package/dist/chunk-A3S3AGE3.js +3 -0
  7. package/dist/chunk-A3S3AGE3.js.map +1 -0
  8. package/dist/chunk-CNSGZVRN.cjs +152 -0
  9. package/dist/chunk-CNSGZVRN.cjs.map +1 -0
  10. package/dist/chunk-JF5AJQMU.cjs +2788 -0
  11. package/dist/chunk-JF5AJQMU.cjs.map +1 -0
  12. package/dist/chunk-NDSL7NPN.js +807 -0
  13. package/dist/chunk-NDSL7NPN.js.map +1 -0
  14. package/dist/chunk-OHXW2YM6.js +2708 -0
  15. package/dist/chunk-OHXW2YM6.js.map +1 -0
  16. package/dist/chunk-Q3SOMX26.js +2854 -0
  17. package/dist/chunk-Q3SOMX26.js.map +1 -0
  18. package/dist/chunk-WH3APNQ5.js +147 -0
  19. package/dist/chunk-WH3APNQ5.js.map +1 -0
  20. package/dist/chunk-X35MHWXE.cjs +817 -0
  21. package/dist/chunk-X35MHWXE.cjs.map +1 -0
  22. package/dist/cli/index.cjs +926 -0
  23. package/dist/cli/index.cjs.map +1 -0
  24. package/dist/cli/index.d.cts +24 -0
  25. package/dist/cli/index.d.ts +24 -0
  26. package/dist/cli/index.js +916 -0
  27. package/dist/cli/index.js.map +1 -0
  28. package/dist/index-DPsZ1zat.d.ts +447 -0
  29. package/dist/index-RTPmFjMp.d.cts +447 -0
  30. package/dist/index.cjs +508 -0
  31. package/dist/index.cjs.map +1 -0
  32. package/dist/index.d.cts +664 -0
  33. package/dist/index.d.ts +664 -0
  34. package/dist/index.js +204 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/models/index.cjs +62 -0
  37. package/dist/models/index.cjs.map +1 -0
  38. package/dist/models/index.d.cts +165 -0
  39. package/dist/models/index.d.ts +165 -0
  40. package/dist/models/index.js +5 -0
  41. package/dist/models/index.js.map +1 -0
  42. package/dist/tools/index.cjs +207 -0
  43. package/dist/tools/index.cjs.map +1 -0
  44. package/dist/tools/index.d.cts +108 -0
  45. package/dist/tools/index.d.ts +108 -0
  46. package/dist/tools/index.js +6 -0
  47. package/dist/tools/index.js.map +1 -0
  48. package/dist/types-C0aX_Qdp.d.cts +917 -0
  49. package/dist/types-C0aX_Qdp.d.ts +917 -0
  50. package/package.json +80 -0
@@ -0,0 +1,2788 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ var chunkCNSGZVRN_cjs = require('./chunk-CNSGZVRN.cjs');
5
+ var promises = require('fs/promises');
6
+ var path = require('path');
7
+ var os = require('os');
8
+ var zod = require('zod');
9
+ var iconv = require('iconv-lite');
10
+ var fg = require('fast-glob');
11
+ var chardet = require('chardet');
12
+ var child_process = require('child_process');
13
+ var fs = require('fs');
14
+ var ignore = require('ignore');
15
+ var micromatch = require('micromatch');
16
+ var dns = require('dns');
17
+ var ipaddr = require('ipaddr.js');
18
+ var readability = require('@mozilla/readability');
19
+ var linkedom = require('linkedom');
20
+ var TurndownService = require('turndown');
21
+
22
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
23
+
24
+ function _interopNamespace(e) {
25
+ if (e && e.__esModule) return e;
26
+ var n = Object.create(null);
27
+ if (e) {
28
+ Object.keys(e).forEach(function (k) {
29
+ if (k !== 'default') {
30
+ var d = Object.getOwnPropertyDescriptor(e, k);
31
+ Object.defineProperty(n, k, d.get ? d : {
32
+ enumerable: true,
33
+ get: function () { return e[k]; }
34
+ });
35
+ }
36
+ });
37
+ }
38
+ n.default = e;
39
+ return Object.freeze(n);
40
+ }
41
+
42
+ var iconv__default = /*#__PURE__*/_interopDefault(iconv);
43
+ var fg__default = /*#__PURE__*/_interopDefault(fg);
44
+ var ignore__default = /*#__PURE__*/_interopDefault(ignore);
45
+ var micromatch__default = /*#__PURE__*/_interopDefault(micromatch);
46
+ var dns__namespace = /*#__PURE__*/_interopNamespace(dns);
47
+ var ipaddr__default = /*#__PURE__*/_interopDefault(ipaddr);
48
+ var TurndownService__default = /*#__PURE__*/_interopDefault(TurndownService);
49
+
50
+ var OUTPUT_CONFIG = {
51
+ /** 直接返回的最大字符数 (~12k tokens) */
52
+ maxDirectOutput: 5e4,
53
+ /** 保存到文件的最大大小 */
54
+ maxStorageSize: 1e7,
55
+ /** 摘要显示的行数 */
56
+ summaryHeadLines: 100,
57
+ summaryTailLines: 100,
58
+ /** 智能截断保留的行数 */
59
+ truncateHeadLines: 500,
60
+ truncateTailLines: 500,
61
+ /** 存储目录 */
62
+ storageDir: ".claude/tool-outputs/"
63
+ };
64
+ var FileStorageStrategy = class {
65
+ userBasePath;
66
+ constructor(userBasePath) {
67
+ this.userBasePath = userBasePath || os.homedir();
68
+ }
69
+ async handle(content, toolName, context) {
70
+ const basePath = context?.userBasePath || this.userBasePath;
71
+ const timestamp = Date.now();
72
+ const safeName = toolName.replace(/[^a-zA-Z0-9_-]/g, "_");
73
+ const filename = `${safeName}-${timestamp}.txt`;
74
+ const storageDir = path.join(basePath, OUTPUT_CONFIG.storageDir);
75
+ const filepath = path.join(storageDir, filename);
76
+ try {
77
+ await promises.mkdir(path.dirname(filepath), { recursive: true });
78
+ await promises.writeFile(filepath, content, "utf-8");
79
+ } catch (error) {
80
+ const errorMessage = error instanceof Error ? error.message : String(error);
81
+ const lines2 = content.split("\n");
82
+ return {
83
+ content: `Output too large (${lines2.length} lines)
84
+
85
+ Failed to save to file: ${errorMessage}
86
+
87
+ Truncated output:
88
+ ${content.slice(0, OUTPUT_CONFIG.maxDirectOutput)}`,
89
+ metadata: {
90
+ truncated: true,
91
+ originalLength: content.length,
92
+ lineCount: lines2.length
93
+ }
94
+ };
95
+ }
96
+ const lines = content.split("\n");
97
+ const summary = this.generateSummary(content, lines);
98
+ const sizeKB = (content.length / 1024).toFixed(1);
99
+ return {
100
+ content: `Output too large (${sizeKB} KB, ${lines.length} lines)
101
+
102
+ Summary:
103
+ ${summary}
104
+
105
+ Full output saved to: ${filepath}
106
+ Use 'Read' with offset/limit to view specific sections.`,
107
+ metadata: {
108
+ truncated: true,
109
+ originalLength: content.length,
110
+ storagePath: filepath,
111
+ lineCount: lines.length
112
+ }
113
+ };
114
+ }
115
+ generateSummary(content, lines) {
116
+ const { summaryHeadLines, summaryTailLines } = OUTPUT_CONFIG;
117
+ if (lines.length <= summaryHeadLines + summaryTailLines) {
118
+ return content;
119
+ }
120
+ const head = lines.slice(0, summaryHeadLines).join("\n");
121
+ const tail = lines.slice(-summaryTailLines).join("\n");
122
+ const omitted = lines.length - summaryHeadLines - summaryTailLines;
123
+ return `${head}
124
+
125
+ ... (${omitted} lines omitted) ...
126
+
127
+ ${tail}`;
128
+ }
129
+ };
130
+ var PaginationHintStrategy = class {
131
+ async handle(content, _toolName, context) {
132
+ const lines = content.split("\n");
133
+ const sizeKB = (content.length / 1024).toFixed(1);
134
+ const previewLines = OUTPUT_CONFIG.summaryHeadLines;
135
+ const filePath = this.extractFilePath(context?.args);
136
+ let hint = `Content is too large (${lines.length} lines, ${sizeKB} KB)
137
+
138
+ `;
139
+ if (filePath) {
140
+ hint += `To read efficiently:
141
+ `;
142
+ hint += `1. Use 'Read' with offset and limit:
143
+ `;
144
+ hint += ` Read(file_path="${filePath}", offset=1, limit=500)
145
+
146
+ `;
147
+ hint += `2. Use 'Grep' to search for patterns:
148
+ `;
149
+ hint += ` Grep(pattern="keyword", path="${filePath}")
150
+
151
+ `;
152
+ }
153
+ hint += `First ${previewLines} lines preview:
154
+ `;
155
+ hint += lines.slice(0, previewLines).join("\n");
156
+ if (lines.length > previewLines) {
157
+ hint += `
158
+
159
+ ... (${lines.length - previewLines} more lines)`;
160
+ }
161
+ return {
162
+ content: hint,
163
+ metadata: {
164
+ truncated: true,
165
+ originalLength: content.length,
166
+ lineCount: lines.length
167
+ }
168
+ };
169
+ }
170
+ extractFilePath(args) {
171
+ if (typeof args === "object" && args !== null) {
172
+ const a = args;
173
+ if (typeof a.path === "string") return a.path;
174
+ if (typeof a.file_path === "string") return a.file_path;
175
+ }
176
+ return null;
177
+ }
178
+ };
179
+ var SmartTruncateStrategy = class {
180
+ async handle(content, _toolName, _context) {
181
+ const lines = content.split("\n");
182
+ const { truncateHeadLines, truncateTailLines, maxDirectOutput } = OUTPUT_CONFIG;
183
+ if (lines.length <= truncateHeadLines + truncateTailLines) {
184
+ const truncated = content.slice(0, maxDirectOutput) + `
185
+
186
+ ... [truncated, ${content.length} total chars]`;
187
+ return {
188
+ content: truncated,
189
+ metadata: {
190
+ truncated: true,
191
+ originalLength: content.length,
192
+ lineCount: lines.length
193
+ }
194
+ };
195
+ }
196
+ const head = lines.slice(0, truncateHeadLines);
197
+ const tail = lines.slice(-truncateTailLines);
198
+ const omitted = lines.length - truncateHeadLines - truncateTailLines;
199
+ const result = head.join("\n") + `
200
+
201
+ ... [${omitted} lines omitted] ...
202
+
203
+ ` + tail.join("\n");
204
+ return {
205
+ content: result,
206
+ metadata: {
207
+ truncated: true,
208
+ originalLength: content.length,
209
+ originalLineCount: lines.length,
210
+ displayedLineCount: truncateHeadLines + truncateTailLines
211
+ }
212
+ };
213
+ }
214
+ };
215
+ var OutputHandler = class {
216
+ strategies = /* @__PURE__ */ new Map();
217
+ defaultStrategy;
218
+ constructor(userBasePath) {
219
+ this.strategies.set("shell", new FileStorageStrategy(userBasePath));
220
+ this.strategies.set("mcp", new FileStorageStrategy(userBasePath));
221
+ this.strategies.set("web", new FileStorageStrategy(userBasePath));
222
+ this.strategies.set("filesystem", new PaginationHintStrategy());
223
+ this.strategies.set("search", new SmartTruncateStrategy());
224
+ this.defaultStrategy = new SmartTruncateStrategy();
225
+ }
226
+ /**
227
+ * 处理工具输出
228
+ * @param content 工具输出内容
229
+ * @param toolName 工具名称
230
+ * @param category 工具类别
231
+ * @param context 上下文信息
232
+ */
233
+ async handle(content, toolName, category, context) {
234
+ if (content.length <= OUTPUT_CONFIG.maxDirectOutput) {
235
+ return { content };
236
+ }
237
+ const strategy = this.strategies.get(category || "") || this.defaultStrategy;
238
+ return strategy.handle(content, toolName, context);
239
+ }
240
+ /**
241
+ * 注册自定义策略
242
+ */
243
+ registerStrategy(category, strategy) {
244
+ this.strategies.set(category, strategy);
245
+ }
246
+ /**
247
+ * 检查内容是否需要处理
248
+ */
249
+ needsHandling(content) {
250
+ return content.length > OUTPUT_CONFIG.maxDirectOutput;
251
+ }
252
+ };
253
+ function createOutputHandler(userBasePath) {
254
+ return new OutputHandler(userBasePath);
255
+ }
256
+ var ToolRegistry = class {
257
+ tools = /* @__PURE__ */ new Map();
258
+ categories = /* @__PURE__ */ new Map();
259
+ outputHandler;
260
+ hookManager = null;
261
+ executionPolicy;
262
+ constructor(config) {
263
+ const enableOutputHandler = config?.enableOutputHandler !== false;
264
+ this.outputHandler = enableOutputHandler ? createOutputHandler(config?.userBasePath) : null;
265
+ this.executionPolicy = config?.executionPolicy;
266
+ }
267
+ /**
268
+ * 工具名是否在 {@link ToolExecutionPolicy.disallowedTools} 中(无策略时为 false)。
269
+ */
270
+ isDisallowed(name) {
271
+ return this.executionPolicy?.disallowedTools?.includes(name) ?? false;
272
+ }
273
+ /**
274
+ * `allowedTools` 未设置时视为全部自动批准;已设置时仅列表内自动批准。
275
+ */
276
+ isAutoApproved(name) {
277
+ const allowed = this.executionPolicy?.allowedTools;
278
+ if (allowed === void 0) {
279
+ return true;
280
+ }
281
+ return allowed.includes(name);
282
+ }
283
+ async checkExecutionPolicy(name, args) {
284
+ const policy = this.executionPolicy;
285
+ if (!policy) {
286
+ return null;
287
+ }
288
+ if (this.isDisallowed(name)) {
289
+ return {
290
+ content: `Tool "${name}" is disallowed by configuration`,
291
+ isError: true
292
+ };
293
+ }
294
+ if (this.isAutoApproved(name)) {
295
+ return null;
296
+ }
297
+ const canUse = policy.canUseTool;
298
+ if (!canUse) {
299
+ return {
300
+ content: `Tool "${name}" requires approval: configure allowedTools or canUseTool`,
301
+ isError: true
302
+ };
303
+ }
304
+ const raw = args;
305
+ const input = raw !== null && raw !== void 0 && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
306
+ const ok = await canUse(name, input);
307
+ if (!ok) {
308
+ return {
309
+ content: `Tool "${name}" was denied by canUseTool`,
310
+ isError: true
311
+ };
312
+ }
313
+ return null;
314
+ }
315
+ setHookManager(manager) {
316
+ this.hookManager = manager;
317
+ }
318
+ getHookManager() {
319
+ return this.hookManager;
320
+ }
321
+ buildHookContext(event, name, toolInput, options, extra = {}) {
322
+ return {
323
+ eventType: event,
324
+ toolName: name,
325
+ toolInput,
326
+ timestamp: Date.now(),
327
+ projectDir: options?.projectDir,
328
+ toolCallId: options?.toolCallId,
329
+ ...extra
330
+ };
331
+ }
332
+ /**
333
+ * 注册工具
334
+ */
335
+ register(tool) {
336
+ if (this.isDisallowed(tool.name)) {
337
+ throw new Error(
338
+ `Cannot register tool "${tool.name}": it is listed in disallowedTools`
339
+ );
340
+ }
341
+ if (this.tools.has(tool.name)) {
342
+ throw new Error(`Tool "${tool.name}" is already registered`);
343
+ }
344
+ this.tools.set(tool.name, tool);
345
+ }
346
+ /**
347
+ * 注册多个工具
348
+ */
349
+ registerMany(tools) {
350
+ for (const tool of tools) {
351
+ this.register(tool);
352
+ }
353
+ }
354
+ /**
355
+ * 注销工具
356
+ */
357
+ unregister(name) {
358
+ return this.tools.delete(name);
359
+ }
360
+ /**
361
+ * 获取工具定义
362
+ */
363
+ get(name) {
364
+ return this.tools.get(name);
365
+ }
366
+ /**
367
+ * 获取所有工具定义
368
+ */
369
+ getAll() {
370
+ return Array.from(this.tools.values());
371
+ }
372
+ /**
373
+ * 获取工具名称列表
374
+ */
375
+ getNames() {
376
+ return Array.from(this.tools.keys());
377
+ }
378
+ /**
379
+ * 检查工具是否存在
380
+ */
381
+ has(name) {
382
+ return this.tools.has(name);
383
+ }
384
+ /**
385
+ * 获取工具数量
386
+ */
387
+ get size() {
388
+ return this.tools.size;
389
+ }
390
+ /**
391
+ * 执行工具
392
+ */
393
+ async execute(name, args, options) {
394
+ const hookMgr = this.hookManager;
395
+ const rawArgsObj = typeof args === "object" && args !== null ? args : {};
396
+ const policyBlock = await this.checkExecutionPolicy(name, args);
397
+ if (policyBlock) {
398
+ return policyBlock;
399
+ }
400
+ const tool = this.tools.get(name);
401
+ if (!tool) {
402
+ const ctx = this.buildHookContext("postToolUseFailure", name, rawArgsObj, options, {
403
+ errorMessage: `Tool "${name}" not found`,
404
+ failureKind: "tool_error"
405
+ });
406
+ await hookMgr?.executePostToolUseFailure(ctx);
407
+ return {
408
+ content: `Tool "${name}" not found`,
409
+ isError: true
410
+ };
411
+ }
412
+ let workingInput = rawArgsObj;
413
+ try {
414
+ workingInput = tool.parameters.parse(args);
415
+ if (hookMgr) {
416
+ const pre = await hookMgr.executePreToolUse(
417
+ this.buildHookContext("preToolUse", name, workingInput, options)
418
+ );
419
+ if (!pre.allowed) {
420
+ return {
421
+ content: pre.reason ?? "Blocked by hook",
422
+ isError: true
423
+ };
424
+ }
425
+ try {
426
+ workingInput = tool.parameters.parse(pre.updatedInput ?? workingInput);
427
+ } catch (err) {
428
+ if (err instanceof zod.z.ZodError) {
429
+ const msg = `Invalid arguments after hook merge for tool "${name}": ${err.errors.map((e) => e.message).join(", ")}`;
430
+ await hookMgr.executePostToolUseFailure(
431
+ this.buildHookContext("postToolUseFailure", name, workingInput, options, {
432
+ errorMessage: msg,
433
+ failureKind: "validation"
434
+ })
435
+ );
436
+ return { content: msg, isError: true };
437
+ }
438
+ throw err;
439
+ }
440
+ }
441
+ const handlerArgs = workingInput;
442
+ const executionContext = {
443
+ toolCallId: options?.toolCallId,
444
+ projectDir: options?.projectDir,
445
+ agentDepth: options?.agentDepth
446
+ };
447
+ const result = await tool.handler(handlerArgs, executionContext);
448
+ const toolResultRaw = result;
449
+ if (result.isError) {
450
+ await hookMgr?.executePostToolUseFailure(
451
+ this.buildHookContext("postToolUseFailure", name, workingInput, options, {
452
+ errorMessage: result.content,
453
+ failureKind: "tool_error"
454
+ })
455
+ );
456
+ return result;
457
+ }
458
+ let finalResult = result;
459
+ if (this.outputHandler && this.outputHandler.needsHandling(result.content)) {
460
+ finalResult = await this.outputHandler.handle(
461
+ result.content,
462
+ name,
463
+ tool.category,
464
+ { args: handlerArgs }
465
+ );
466
+ }
467
+ await hookMgr?.executePostToolUse(
468
+ this.buildHookContext("postToolUse", name, workingInput, options, {
469
+ toolResultRaw,
470
+ toolResultFinal: finalResult
471
+ })
472
+ );
473
+ return finalResult;
474
+ } catch (error) {
475
+ if (error instanceof zod.z.ZodError) {
476
+ const msg2 = `Invalid arguments for tool "${name}": ${error.errors.map((e) => e.message).join(", ")}`;
477
+ await hookMgr?.executePostToolUseFailure(
478
+ this.buildHookContext("postToolUseFailure", name, rawArgsObj, options, {
479
+ errorMessage: msg2,
480
+ failureKind: "validation"
481
+ })
482
+ );
483
+ return {
484
+ content: msg2,
485
+ isError: true
486
+ };
487
+ }
488
+ const msg = `Error executing tool "${name}": ${error instanceof Error ? error.message : String(error)}`;
489
+ await hookMgr?.executePostToolUseFailure(
490
+ this.buildHookContext("postToolUseFailure", name, workingInput, options, {
491
+ errorMessage: msg,
492
+ failureKind: "handler_throw"
493
+ })
494
+ );
495
+ return {
496
+ content: msg,
497
+ isError: true
498
+ };
499
+ }
500
+ }
501
+ /**
502
+ * 获取工具 Schema (用于模型调用)
503
+ */
504
+ toSchema() {
505
+ return this.getAll().map((tool) => ({
506
+ name: tool.name,
507
+ description: tool.description,
508
+ parameters: chunkCNSGZVRN_cjs.zodToJsonSchema(tool.parameters)
509
+ }));
510
+ }
511
+ /**
512
+ * 清空所有工具
513
+ */
514
+ clear() {
515
+ this.tools.clear();
516
+ this.categories.clear();
517
+ }
518
+ /**
519
+ * 按类别注册工具
520
+ */
521
+ registerWithCategory(category, tool) {
522
+ this.register(tool);
523
+ if (!this.categories.has(category)) {
524
+ this.categories.set(category, /* @__PURE__ */ new Set());
525
+ }
526
+ this.categories.get(category).add(tool.name);
527
+ }
528
+ /**
529
+ * 获取类别下的工具
530
+ */
531
+ getByCategory(category) {
532
+ const toolNames = this.categories.get(category);
533
+ if (!toolNames) return [];
534
+ return Array.from(toolNames).map((name) => this.tools.get(name)).filter((tool) => tool !== void 0);
535
+ }
536
+ /**
537
+ * 获取所有类别
538
+ */
539
+ getCategories() {
540
+ return Array.from(this.categories.keys());
541
+ }
542
+ /**
543
+ * 过滤工具
544
+ */
545
+ filter(predicate) {
546
+ return this.getAll().filter(predicate);
547
+ }
548
+ /**
549
+ * 搜索工具
550
+ */
551
+ search(query) {
552
+ const lowerQuery = query.toLowerCase();
553
+ return this.filter(
554
+ (tool) => tool.name.toLowerCase().includes(lowerQuery) || tool.description.toLowerCase().includes(lowerQuery)
555
+ );
556
+ }
557
+ /**
558
+ * 导出工具配置
559
+ */
560
+ export() {
561
+ return this.getAll().map((tool) => ({
562
+ name: tool.name,
563
+ description: tool.description,
564
+ parameters: chunkCNSGZVRN_cjs.zodToJsonSchema(tool.parameters)
565
+ }));
566
+ }
567
+ };
568
+ function createTool(config) {
569
+ return {
570
+ name: config.name,
571
+ description: config.description,
572
+ parameters: config.parameters,
573
+ handler: config.handler,
574
+ isDangerous: config.isDangerous,
575
+ category: config.category
576
+ };
577
+ }
578
+ var globalRegistry = null;
579
+ function getGlobalRegistry() {
580
+ if (!globalRegistry) {
581
+ globalRegistry = new ToolRegistry();
582
+ }
583
+ return globalRegistry;
584
+ }
585
+ var SAMPLE_BYTES = 64 * 1024;
586
+ var CJK_TIE_BREAK_ORDER = [
587
+ "GB18030",
588
+ "Big5",
589
+ "EUC-KR",
590
+ "EUC-JP",
591
+ "Shift_JIS"
592
+ ];
593
+ function cjkTieBreakRank(name) {
594
+ const i = CJK_TIE_BREAK_ORDER.indexOf(name);
595
+ return i === -1 ? 100 : i;
596
+ }
597
+ function isCjkChardetName(name) {
598
+ switch (name) {
599
+ case "GB18030":
600
+ case "GBK":
601
+ case "Big5":
602
+ case "EUC-JP":
603
+ case "EUC-KR":
604
+ case "Shift_JIS":
605
+ case "ISO-2022-CN":
606
+ case "ISO-2022-JP":
607
+ case "ISO-2022-KR":
608
+ return true;
609
+ default:
610
+ return false;
611
+ }
612
+ }
613
+ function plausibleGbkDoubleBytePairCount(buf) {
614
+ let n = 0;
615
+ for (let i = 0; i < buf.length - 1; i++) {
616
+ const a = buf[i];
617
+ const b = buf[i + 1];
618
+ if (a >= 129 && a <= 254 && b >= 64 && b <= 254 && b !== 127) {
619
+ n++;
620
+ i++;
621
+ }
622
+ }
623
+ return n;
624
+ }
625
+ function preferGb18030OverChardetTop(buf, matches) {
626
+ if (matches.length === 0) return false;
627
+ if (plausibleGbkDoubleBytePairCount(buf) < 2) return false;
628
+ const top = matches[0];
629
+ if (!top || isCjkChardetName(top.name)) return false;
630
+ const maxConf = Math.max(...matches.map((m) => m.confidence));
631
+ const gb = matches.find((m) => {
632
+ const n = m.name;
633
+ return n === "GB18030" || n === "GBK";
634
+ });
635
+ if (!gb) return false;
636
+ const margin = 8;
637
+ if (maxConf - gb.confidence > margin) return false;
638
+ return true;
639
+ }
640
+ function isValidUtf8(buf) {
641
+ if (buf.length === 0) return true;
642
+ try {
643
+ new TextDecoder("utf-8", { fatal: true }).decode(buf);
644
+ return true;
645
+ } catch {
646
+ return false;
647
+ }
648
+ }
649
+ function langAffinityScore(name, lang) {
650
+ if (!lang) return 0;
651
+ const zhEnc = name === "GB18030" || name === "Big5";
652
+ const jaEnc = name === "Shift_JIS" || name === "EUC-JP";
653
+ if (zhEnc && lang === "zh") return 2;
654
+ if (jaEnc && lang === "ja") return 2;
655
+ if (name === "EUC-KR" && lang === "ko") return 2;
656
+ if (zhEnc && (lang === "ja" || lang === "ko")) return -1;
657
+ if (jaEnc && (lang === "zh" || lang === "ko")) return -1;
658
+ if (name === "EUC-KR" && (lang === "zh" || lang === "ja")) return -1;
659
+ return 0;
660
+ }
661
+ function encodingFromBom(buf) {
662
+ if (buf.length >= 3 && buf[0] === 239 && buf[1] === 187 && buf[2] === 191) {
663
+ return "utf8";
664
+ }
665
+ if (buf.length >= 4 && buf[0] === 0 && buf[1] === 0 && buf[2] === 254 && buf[3] === 255) {
666
+ return "utf-32be";
667
+ }
668
+ if (buf.length >= 4 && buf[0] === 255 && buf[1] === 254 && buf[2] === 0 && buf[3] === 0) {
669
+ return "utf-32le";
670
+ }
671
+ if (buf.length >= 2 && buf[0] === 255 && buf[1] === 254) {
672
+ return "utf16le";
673
+ }
674
+ if (buf.length >= 2 && buf[0] === 254 && buf[1] === 255) {
675
+ return "utf-16be";
676
+ }
677
+ return null;
678
+ }
679
+ function chardetNameToReadEncoding(name) {
680
+ if (name === "mbcs") return null;
681
+ const table = {
682
+ ASCII: "utf8",
683
+ "UTF-8": "utf8",
684
+ "UTF-16LE": "utf16le",
685
+ "UTF-16BE": "utf-16be",
686
+ "UTF-32LE": "utf-32le",
687
+ "UTF-32BE": "utf-32be",
688
+ "UTF-32": "utf-32le",
689
+ GB18030: "gb18030",
690
+ GBK: "gbk",
691
+ Big5: "big5",
692
+ "EUC-JP": "euc-jp",
693
+ "EUC-KR": "euc-kr",
694
+ Shift_JIS: "shift_jis",
695
+ "ISO-8859-1": "latin1",
696
+ "ISO-8859-2": "latin2",
697
+ "ISO-8859-5": "iso88595",
698
+ "ISO-8859-6": "iso88596",
699
+ "ISO-8859-7": "iso88597",
700
+ "ISO-8859-8": "iso88598",
701
+ "ISO-8859-9": "latin5",
702
+ "ISO-2022-CN": "iso-2022-cn",
703
+ "ISO-2022-JP": "iso-2022-jp",
704
+ "ISO-2022-KR": "iso-2022-kr",
705
+ ISO_2022: "iso-2022-jp",
706
+ "KOI8-R": "koi8-r",
707
+ "windows-1250": "cp1250",
708
+ "windows-1251": "cp1251",
709
+ "windows-1252": "cp1252",
710
+ "windows-1253": "cp1253",
711
+ "windows-1254": "cp1254",
712
+ "windows-1255": "cp1255",
713
+ "windows-1256": "cp1256"
714
+ };
715
+ const mapped = table[name];
716
+ if (mapped) {
717
+ if (mapped === "utf8" || mapped === "utf16le" || mapped === "latin1") {
718
+ return mapped;
719
+ }
720
+ if (iconv__default.default.encodingExists(mapped)) {
721
+ return mapped;
722
+ }
723
+ }
724
+ const lower = name.toLowerCase().replace(/_/g, "-");
725
+ if (lower !== "mbcs" && iconv__default.default.encodingExists(lower)) {
726
+ return lower;
727
+ }
728
+ const compact = name.replace(/-/g, "").toLowerCase();
729
+ if (iconv__default.default.encodingExists(compact)) {
730
+ return compact;
731
+ }
732
+ return null;
733
+ }
734
+ async function readEncodingSample(filePath, fileSize) {
735
+ const fs = await import('fs/promises');
736
+ const fh = await fs.open(filePath, "r");
737
+ try {
738
+ const size = fileSize !== void 0 ? fileSize : (await fh.stat()).size;
739
+ const len = Math.min(SAMPLE_BYTES, size);
740
+ if (len === 0) {
741
+ return Buffer.alloc(0);
742
+ }
743
+ const buf = Buffer.allocUnsafe(len);
744
+ const { bytesRead } = await fh.read(buf, 0, len, 0);
745
+ return bytesRead < len ? buf.subarray(0, bytesRead) : buf;
746
+ } finally {
747
+ await fh.close();
748
+ }
749
+ }
750
+ function detectEncodingFromSample(buf) {
751
+ if (buf.length === 0) {
752
+ return "utf8";
753
+ }
754
+ const bom = encodingFromBom(buf);
755
+ if (bom) {
756
+ return bom;
757
+ }
758
+ if (isValidUtf8(buf)) {
759
+ return "utf8";
760
+ }
761
+ const matches = chardet.analyse(buf).filter(
762
+ (m) => m.confidence > 0 && m.name !== "ASCII" && m.name !== "mbcs"
763
+ );
764
+ matches.sort((a, b) => {
765
+ if (b.confidence !== a.confidence) {
766
+ return b.confidence - a.confidence;
767
+ }
768
+ const langDiff = langAffinityScore(b.name, b.lang) - langAffinityScore(a.name, a.lang);
769
+ if (langDiff !== 0) {
770
+ return langDiff;
771
+ }
772
+ return cjkTieBreakRank(a.name) - cjkTieBreakRank(b.name);
773
+ });
774
+ if (preferGb18030OverChardetTop(buf, matches)) {
775
+ const gb = matches.find((m) => {
776
+ const n = m.name;
777
+ return n === "GB18030" || n === "GBK";
778
+ });
779
+ if (gb) {
780
+ const enc = chardetNameToReadEncoding(gb.name);
781
+ if (enc && (isNativeReadEncoding(enc) || iconv__default.default.encodingExists(enc))) {
782
+ return enc;
783
+ }
784
+ }
785
+ }
786
+ for (const m of matches) {
787
+ const enc = chardetNameToReadEncoding(m.name);
788
+ if (enc && (isNativeReadEncoding(enc) || iconv__default.default.encodingExists(enc))) {
789
+ return enc;
790
+ }
791
+ }
792
+ return "utf8";
793
+ }
794
+ function isNativeReadEncoding(enc) {
795
+ return enc === "utf8" || enc === "utf16le" || enc === "latin1";
796
+ }
797
+ function normalizeFilesystemEncoding(encoding) {
798
+ const raw = encoding?.trim() ?? "";
799
+ if (raw === "") return "utf8";
800
+ let e = raw.toLowerCase();
801
+ if (e === "utf-8") e = "utf8";
802
+ if (e === "cp936") e = "gbk";
803
+ return e;
804
+ }
805
+ function isFilesystemEncodingSupported(normalized) {
806
+ return isNativeReadEncoding(normalized) || iconv__default.default.encodingExists(normalized);
807
+ }
808
+ async function readFileAsUnicodeString(filePath, normalized) {
809
+ const fs = await import('fs/promises');
810
+ if (isNativeReadEncoding(normalized)) {
811
+ return fs.readFile(filePath, {
812
+ encoding: normalized
813
+ });
814
+ }
815
+ const buf = await fs.readFile(filePath);
816
+ return iconv__default.default.decode(buf, normalized);
817
+ }
818
+ async function writeFileFromUnicodeString(filePath, text, normalized) {
819
+ const fs = await import('fs/promises');
820
+ if (isNativeReadEncoding(normalized)) {
821
+ await fs.writeFile(filePath, text, {
822
+ encoding: normalized
823
+ });
824
+ } else {
825
+ await fs.writeFile(filePath, iconv__default.default.encode(text, normalized));
826
+ }
827
+ }
828
+
829
+ // src/tools/builtin/filesystem.ts
830
+ var DEFAULT_READ_LIMIT = 2e3;
831
+ var MAX_LINE_LENGTH = 2e3;
832
+ var MAX_LINE_SUFFIX = `... (line truncated to ${MAX_LINE_LENGTH} chars)`;
833
+ var MAX_BYTES = 50 * 1024;
834
+ var MAX_BYTES_LABEL = `${MAX_BYTES / 1024} KB`;
835
+ var readFileTool = createTool({
836
+ name: "Read",
837
+ category: "filesystem",
838
+ description: `Reads a file from the local filesystem. You can access any file directly by using this tool.
839
+
840
+ Usage:
841
+ - The file_path parameter must be an absolute path, not a relative path
842
+ - By default, it reads up to 2000 lines starting from the beginning of the file
843
+ - You can optionally specify a line offset and limit (especially handy for long files), but it's recommended to read the whole file by not providing these parameters
844
+ - Results are returned using cat -n style, with line numbers starting at 1
845
+ - Lines longer than 2000 characters are truncated
846
+ - Use the offset and limit parameters to read specific line ranges of large files
847
+ - If you read a file that exists but has empty contents you will receive an error message
848
+ - Text encoding is detected automatically from the file (BOM, UTF-8 validity, charset analysis). You rarely need to set encoding; use it only to override detection (e.g. gbk, gb18030, cp936 maps to gbk)`,
849
+ parameters: zod.z.object({
850
+ file_path: zod.z.string().describe("The absolute path to the file to read"),
851
+ encoding: zod.z.string().optional().describe(
852
+ 'Optional. Omit or use "auto" for automatic detection (default). Set only to force a specific encoding (utf8, gbk, gb18030, latin1, etc.; cp936 is treated as gbk).'
853
+ ),
854
+ offset: zod.z.number().int().min(1).optional().describe("The line number to start reading from (1-indexed). Only provide if the file is too large to read at once"),
855
+ limit: zod.z.number().int().min(1).optional().describe("The number of lines to read. Only provide if the file is too large to read at once")
856
+ }),
857
+ handler: async ({ file_path, encoding, offset, limit }) => {
858
+ try {
859
+ const fs = await import('fs/promises');
860
+ const { createReadStream } = await import('fs');
861
+ const { createInterface } = await import('readline');
862
+ const stat = await fs.stat(file_path);
863
+ if (!stat.isFile()) {
864
+ return {
865
+ content: `Error: ${file_path} is not a file`,
866
+ isError: true
867
+ };
868
+ }
869
+ const encTrim = encoding?.trim() ?? "";
870
+ const useAuto = encTrim === "" || encTrim.toLowerCase() === "auto";
871
+ let normalized;
872
+ let autoDetected = false;
873
+ if (useAuto) {
874
+ const sample = await readEncodingSample(file_path, stat.size);
875
+ normalized = detectEncodingFromSample(sample);
876
+ autoDetected = true;
877
+ } else {
878
+ normalized = normalizeFilesystemEncoding(encTrim);
879
+ if (!isFilesystemEncodingSupported(normalized)) {
880
+ return {
881
+ content: `Error: unsupported encoding: ${encTrim}`,
882
+ isError: true
883
+ };
884
+ }
885
+ }
886
+ const startLine = offset ? offset - 1 : 0;
887
+ const maxLines = limit ?? DEFAULT_READ_LIMIT;
888
+ const toDestroy = [];
889
+ let lineInput;
890
+ if (isNativeReadEncoding(normalized)) {
891
+ const stream = createReadStream(file_path, {
892
+ encoding: normalized
893
+ });
894
+ toDestroy.push(stream);
895
+ lineInput = stream;
896
+ } else {
897
+ const raw = createReadStream(file_path);
898
+ const decoded = raw.pipe(iconv__default.default.decodeStream(normalized));
899
+ toDestroy.push(raw, decoded);
900
+ lineInput = decoded;
901
+ }
902
+ const rl = createInterface({
903
+ input: lineInput,
904
+ crlfDelay: Infinity
905
+ });
906
+ const selectedLines = [];
907
+ let totalLines = 0;
908
+ let totalBytes = 0;
909
+ let truncatedByBytes = false;
910
+ let hasMoreLines = false;
911
+ try {
912
+ for await (const line of rl) {
913
+ totalLines++;
914
+ if (totalLines <= startLine) continue;
915
+ if (selectedLines.length >= maxLines) {
916
+ hasMoreLines = true;
917
+ continue;
918
+ }
919
+ const processedLine = line.length > MAX_LINE_LENGTH ? line.substring(0, MAX_LINE_LENGTH) + MAX_LINE_SUFFIX : line;
920
+ const lineBytes = Buffer.byteLength(processedLine, "utf-8") + 1;
921
+ if (totalBytes + lineBytes > MAX_BYTES) {
922
+ truncatedByBytes = true;
923
+ hasMoreLines = true;
924
+ break;
925
+ }
926
+ selectedLines.push(processedLine);
927
+ totalBytes += lineBytes;
928
+ }
929
+ } finally {
930
+ rl.close();
931
+ for (const s of toDestroy) {
932
+ s.destroy();
933
+ }
934
+ }
935
+ if (totalLines < startLine && !(totalLines === 0 && startLine === 0)) {
936
+ return {
937
+ content: `Error: Offset ${offset} is out of range for this file (${totalLines} lines)`,
938
+ isError: true
939
+ };
940
+ }
941
+ const numbered = selectedLines.map((line, i) => `${String(startLine + i + 1).padStart(5)} ${line}`).join("\n");
942
+ const lastReadLine = startLine + selectedLines.length;
943
+ const nextOffset = lastReadLine + 1;
944
+ let suffix;
945
+ if (truncatedByBytes) {
946
+ suffix = `
947
+
948
+ (Output capped at ${MAX_BYTES_LABEL}. Showing lines ${offset ?? 1}-${lastReadLine}. Use offset=${nextOffset} to continue.)`;
949
+ } else if (hasMoreLines) {
950
+ suffix = `
951
+
952
+ (Showing lines ${offset ?? 1}-${lastReadLine} of ${totalLines}. Use offset=${nextOffset} to continue.)`;
953
+ } else {
954
+ suffix = `
955
+
956
+ (End of file - total ${totalLines} lines)`;
957
+ }
958
+ if (autoDetected) {
959
+ suffix += `
960
+
961
+ (Auto-detected encoding: ${normalized}.)`;
962
+ }
963
+ return { content: numbered + suffix };
964
+ } catch (error) {
965
+ return {
966
+ content: `Error reading file: ${error instanceof Error ? error.message : String(error)}`,
967
+ isError: true
968
+ };
969
+ }
970
+ }
971
+ });
972
+ var writeFileTool = createTool({
973
+ name: "Write",
974
+ category: "filesystem",
975
+ description: `Writes a file to the local filesystem.
976
+
977
+ Usage:
978
+ - This tool will overwrite the existing file if there is one at the provided path
979
+ - If this is an existing file, you MUST use the Read tool first to read the file's contents. This tool will fail if you did not read the file first
980
+ - Prefer the Edit tool for modifying existing files \u2014 it only sends the diff. Only use this tool to create new files or for complete rewrites
981
+ - NEVER create documentation files (*.md) or README files unless explicitly requested by the User
982
+ - Only use emojis if the user explicitly requests it. Avoid adding emojis to files unless asked
983
+ - For non-UTF-8 files (e.g. GBK on Windows), set encoding to match what you use with Read; default is utf8`,
984
+ parameters: zod.z.object({
985
+ file_path: zod.z.string().describe("The absolute path to the file to write (must be absolute, not relative)"),
986
+ content: zod.z.string().describe("The content to write to the file"),
987
+ encoding: zod.z.string().optional().describe(
988
+ "File character encoding. Default utf8. Use gbk or gb18030 for legacy Chinese ANSI text; cp936 is treated as gbk."
989
+ )
990
+ }),
991
+ handler: async ({ file_path, content, encoding }) => {
992
+ try {
993
+ const fs = await import('fs/promises');
994
+ const pathModule = await import('path');
995
+ const normalized = normalizeFilesystemEncoding(encoding);
996
+ if (!isFilesystemEncodingSupported(normalized)) {
997
+ return {
998
+ content: `Error: unsupported encoding: ${encoding?.trim() || "utf8"}`,
999
+ isError: true
1000
+ };
1001
+ }
1002
+ const dir = pathModule.dirname(file_path);
1003
+ await fs.mkdir(dir, { recursive: true });
1004
+ await writeFileFromUnicodeString(file_path, content, normalized);
1005
+ return { content: `Successfully wrote to ${file_path}` };
1006
+ } catch (error) {
1007
+ return {
1008
+ content: `Error writing file: ${error instanceof Error ? error.message : String(error)}`,
1009
+ isError: true
1010
+ };
1011
+ }
1012
+ }
1013
+ });
1014
+ var editTool = createTool({
1015
+ name: "Edit",
1016
+ category: "filesystem",
1017
+ description: `Performs exact string replacements in files.
1018
+
1019
+ Usage:
1020
+ - You must use the Read tool at least once in the conversation before editing. This tool will error if you attempt an edit without reading the file
1021
+ - When editing text from Read tool output, ensure you preserve the exact indentation (tabs/spaces) as it appears AFTER the line number prefix. The line number prefix format is: line number + tab. Everything after that is the actual file content to match. Never include any part of the line number prefix in the old_string or new_string
1022
+ - ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required
1023
+ - Only use emojis if the user explicitly requests it. Avoid adding emojis to files unless asked
1024
+ - The edit will FAIL if old_string is not unique in the file. Either provide a larger string with more surrounding context to make it unique or use replace_all to change every instance of old_string
1025
+ - Use replace_all for replacing and renaming strings across the file. This parameter is useful if you want to rename a variable for instance
1026
+ - For non-UTF-8 files, set encoding to the same value you used with Read (default is utf8)`,
1027
+ parameters: zod.z.object({
1028
+ file_path: zod.z.string().describe("The absolute path to the file to modify"),
1029
+ old_string: zod.z.string().describe("The text to replace"),
1030
+ new_string: zod.z.string().describe("The text to replace it with (must be different from old_string)"),
1031
+ replace_all: zod.z.boolean().default(false).describe("Replace all occurrences of old_string (default false)"),
1032
+ encoding: zod.z.string().optional().describe(
1033
+ "File character encoding. Default utf8. Use gbk or gb18030 for legacy Chinese ANSI text; cp936 is treated as gbk."
1034
+ )
1035
+ }),
1036
+ handler: async ({ file_path, old_string, new_string, replace_all, encoding }) => {
1037
+ try {
1038
+ if (old_string === new_string) {
1039
+ return {
1040
+ content: "old_string and new_string must be different",
1041
+ isError: true
1042
+ };
1043
+ }
1044
+ const normalized = normalizeFilesystemEncoding(encoding);
1045
+ if (!isFilesystemEncodingSupported(normalized)) {
1046
+ return {
1047
+ content: `Error: unsupported encoding: ${encoding?.trim() || "utf8"}`,
1048
+ isError: true
1049
+ };
1050
+ }
1051
+ const content = await readFileAsUnicodeString(file_path, normalized);
1052
+ if (!content.includes(old_string)) {
1053
+ return {
1054
+ content: `old_string not found in ${file_path}`,
1055
+ isError: true
1056
+ };
1057
+ }
1058
+ if (!replace_all) {
1059
+ const occurrences2 = content.split(old_string).length - 1;
1060
+ if (occurrences2 > 1) {
1061
+ return {
1062
+ content: `Found ${occurrences2} matches for old_string. Provide more context to make it unique, or set replace_all to true.`,
1063
+ isError: true
1064
+ };
1065
+ }
1066
+ }
1067
+ const newContent = replace_all ? content.replaceAll(old_string, new_string) : content.replace(old_string, new_string);
1068
+ await writeFileFromUnicodeString(file_path, newContent, normalized);
1069
+ const occurrences = replace_all ? content.split(old_string).length - 1 : 1;
1070
+ return {
1071
+ content: `Successfully edited ${file_path} (${occurrences} replacement${occurrences > 1 ? "s" : ""})`
1072
+ };
1073
+ } catch (error) {
1074
+ return {
1075
+ content: `Error editing file: ${error instanceof Error ? error.message : String(error)}`,
1076
+ isError: true
1077
+ };
1078
+ }
1079
+ }
1080
+ });
1081
+ var globTool = createTool({
1082
+ name: "Glob",
1083
+ category: "filesystem",
1084
+ description: `- Fast file pattern matching tool that works with any codebase size
1085
+ - Supports glob patterns like "**/*.js" or "src/**/*.ts" (use forward slashes in patterns; works on Windows)
1086
+ - Returns matching file paths as absolute paths, sorted by modification time (newest first)
1087
+ - Dotfiles and dot-directories are excluded unless the pattern targets them (starts with "." or contains "/.")
1088
+ - Use this tool when you need to find files by name patterns
1089
+ - When you are doing an open ended search that may require multiple rounds of globbing and grepping, use the Agent tool instead`,
1090
+ parameters: zod.z.object({
1091
+ pattern: zod.z.string().describe("The glob pattern to match files against"),
1092
+ path: zod.z.string().optional().describe("The directory to search in. If omitted, uses the agent working directory when available, otherwise the current process directory. IMPORTANT: Omit this field to use the default directory. Must be a valid directory path if provided.")
1093
+ }),
1094
+ handler: async ({ pattern, path: searchPath }, context) => {
1095
+ try {
1096
+ const fs = await import('fs/promises');
1097
+ const pathModule = await import('path');
1098
+ const rootDir = pathModule.resolve(searchPath || context?.projectDir || ".");
1099
+ const normalizedPattern = pattern.replace(/\\/g, "/");
1100
+ const includeDotfiles = normalizedPattern.startsWith(".") || normalizedPattern.includes("/.");
1101
+ const entries = await fg__default.default(normalizedPattern, {
1102
+ cwd: rootDir,
1103
+ onlyFiles: true,
1104
+ absolute: true,
1105
+ dot: includeDotfiles,
1106
+ suppressErrors: true
1107
+ });
1108
+ const matches = [];
1109
+ for (const filePath of entries) {
1110
+ const nativePath = pathModule.normalize(filePath);
1111
+ try {
1112
+ const stat = await fs.stat(nativePath);
1113
+ if (stat.isFile()) {
1114
+ matches.push({ path: nativePath, mtime: stat.mtimeMs });
1115
+ }
1116
+ } catch {
1117
+ }
1118
+ }
1119
+ matches.sort((a, b) => b.mtime - a.mtime);
1120
+ return {
1121
+ content: matches.length > 0 ? matches.map((m) => m.path).join("\n") : "No files found"
1122
+ };
1123
+ } catch (error) {
1124
+ return {
1125
+ content: `Error searching files: ${error instanceof Error ? error.message : String(error)}`,
1126
+ isError: true
1127
+ };
1128
+ }
1129
+ }
1130
+ });
1131
+ function getFileSystemTools() {
1132
+ return [
1133
+ readFileTool,
1134
+ writeFileTool,
1135
+ editTool,
1136
+ globTool
1137
+ ];
1138
+ }
1139
+ var cachedShellPath = null;
1140
+ function findInPath(executable) {
1141
+ try {
1142
+ const result = child_process.execSync(`where ${executable}`, {
1143
+ encoding: "utf-8",
1144
+ timeout: 1e3
1145
+ }).trim().split("\n")[0];
1146
+ return result && fs.existsSync(result) ? result : null;
1147
+ } catch {
1148
+ return null;
1149
+ }
1150
+ }
1151
+ function getShellPath() {
1152
+ if (cachedShellPath !== null) {
1153
+ return cachedShellPath;
1154
+ }
1155
+ if (process.platform === "win32") {
1156
+ const bashPath = findInPath("bash");
1157
+ if (bashPath) {
1158
+ cachedShellPath = bashPath;
1159
+ return bashPath;
1160
+ }
1161
+ const gitBashPaths = [
1162
+ "C:\\Program Files\\Git\\bin\\bash.exe",
1163
+ "C:\\Program Files (x86)\\Git\\bin\\bash.exe"
1164
+ ];
1165
+ for (const path of gitBashPaths) {
1166
+ if (fs.existsSync(path)) {
1167
+ cachedShellPath = path;
1168
+ return path;
1169
+ }
1170
+ }
1171
+ const pwshPath = findInPath("pwsh");
1172
+ if (pwshPath) {
1173
+ cachedShellPath = "pwsh";
1174
+ return "pwsh";
1175
+ }
1176
+ cachedShellPath = "powershell.exe";
1177
+ return "powershell.exe";
1178
+ }
1179
+ cachedShellPath = process.env.SHELL || "/bin/bash";
1180
+ return cachedShellPath;
1181
+ }
1182
+ function getEnvironmentInfo(cwd) {
1183
+ let isGitRepo = false;
1184
+ try {
1185
+ child_process.execSync("git rev-parse --is-inside-work-tree", {
1186
+ cwd,
1187
+ stdio: "pipe",
1188
+ timeout: 2e3
1189
+ });
1190
+ isGitRepo = true;
1191
+ } catch {
1192
+ }
1193
+ const shellPath = getShellPath();
1194
+ const shell = shellPath.split(/[/\\]/).pop()?.replace(/\.exe$/i, "");
1195
+ return {
1196
+ cwd,
1197
+ platform: process.platform,
1198
+ date: (/* @__PURE__ */ new Date()).toLocaleDateString("en-US", {
1199
+ weekday: "short",
1200
+ year: "numeric",
1201
+ month: "short",
1202
+ day: "numeric"
1203
+ }),
1204
+ isGitRepo,
1205
+ shell
1206
+ };
1207
+ }
1208
+ function formatEnvironmentSection(info) {
1209
+ const shellLine = info.shell ? `
1210
+ Shell: ${info.shell}` : "";
1211
+ return `
1212
+ ## Environment
1213
+
1214
+ <env>
1215
+ Working directory: ${info.cwd}
1216
+ Platform: ${info.platform}
1217
+ Today's date: ${info.date}
1218
+ Is git repo: ${info.isGitRepo ? "yes" : "no"}${shellLine}
1219
+ </env>`;
1220
+ }
1221
+
1222
+ // src/tools/builtin/shell.ts
1223
+ var MAX_OUTPUT_SIZE = 10 * 1024 * 1024;
1224
+ var KILL_DELAY = 5e3;
1225
+ var bashTool = createTool({
1226
+ name: "Bash",
1227
+ category: "shell",
1228
+ description: `Executes a given bash command and returns its output.
1229
+
1230
+ The working directory persists between commands, but shell state does not. The shell environment is initialized from the user's profile (bash or zsh).
1231
+
1232
+ IMPORTANT: Avoid using this tool to run find, grep, cat, head, tail, sed, awk, or echo commands, unless explicitly instructed or after you have verified that a dedicated tool cannot accomplish your task. Instead, use the appropriate dedicated tool as this will provide a much better experience for the user:
1233
+
1234
+ - File search: Use Glob (NOT find or ls)
1235
+ - Content search: Use Grep (NOT grep or rg)
1236
+ - Read files: Use Read (NOT cat/head/tail)
1237
+ - Edit files: Use Edit (NOT sed/awk)
1238
+ - Write files: Use Write (NOT echo >file)
1239
+ - Communication: Output text directly (NOT echo/printf)
1240
+
1241
+ # Instructions
1242
+ - If your command will create new directories or files, first use this tool to run ls to verify the parent directory exists and is the correct location
1243
+ - Always quote file paths that contain spaces with double quotes in your command (e.g., cd "path with spaces/file.txt")
1244
+ - If cwd is omitted, the command runs in the agent working directory when available
1245
+ - You may specify an optional timeout in milliseconds (up to 600000ms / 10 minutes). By default, your command will timeout after 120000ms (2 minutes).`,
1246
+ parameters: zod.z.object({
1247
+ command: zod.z.string().describe("The command to execute"),
1248
+ description: zod.z.string().optional().describe("Clear, concise description of what this command does in active voice. Keep it brief (5-10 words)."),
1249
+ cwd: zod.z.string().optional().describe("Working directory for the command"),
1250
+ timeout: zod.z.number().int().min(1).max(6e5).optional().describe("Optional timeout in milliseconds (max 600000)")
1251
+ }),
1252
+ isDangerous: true,
1253
+ handler: async ({ command, description: desc, cwd, timeout }, context) => {
1254
+ return new Promise((resolve) => {
1255
+ const shellPath = getShellPath();
1256
+ let stdout = "";
1257
+ let stderr = "";
1258
+ let outputTruncated = false;
1259
+ const effectiveTimeout = timeout ?? 12e4;
1260
+ const child = child_process.spawn(command, [], {
1261
+ shell: shellPath,
1262
+ cwd: cwd ?? context?.projectDir,
1263
+ env: { ...process.env }
1264
+ });
1265
+ const timer = setTimeout(() => {
1266
+ child.kill("SIGTERM");
1267
+ const killTimer = setTimeout(() => {
1268
+ try {
1269
+ child.kill("SIGKILL");
1270
+ } catch {
1271
+ }
1272
+ }, KILL_DELAY);
1273
+ child.on("exit", () => clearTimeout(killTimer));
1274
+ resolve({
1275
+ content: `${desc ? `[${desc}]
1276
+ ` : ""}Command timed out after ${effectiveTimeout}ms`,
1277
+ isError: true
1278
+ });
1279
+ }, effectiveTimeout);
1280
+ child.stdout.on("data", (data) => {
1281
+ if (!outputTruncated && stdout.length < MAX_OUTPUT_SIZE) {
1282
+ stdout += data.toString();
1283
+ if (stdout.length >= MAX_OUTPUT_SIZE) {
1284
+ stdout += "\n[Output truncated due to size limit]";
1285
+ outputTruncated = true;
1286
+ }
1287
+ }
1288
+ });
1289
+ child.stderr.on("data", (data) => {
1290
+ if (!outputTruncated && stderr.length < MAX_OUTPUT_SIZE) {
1291
+ stderr += data.toString();
1292
+ if (stderr.length >= MAX_OUTPUT_SIZE) {
1293
+ stderr += "\n[Output truncated due to size limit]";
1294
+ outputTruncated = true;
1295
+ }
1296
+ }
1297
+ });
1298
+ child.on("error", (error) => {
1299
+ clearTimeout(timer);
1300
+ resolve({
1301
+ content: `${desc ? `[${desc}]
1302
+ ` : ""}Command failed: ${error.message}`,
1303
+ isError: true
1304
+ });
1305
+ });
1306
+ child.on("close", (code) => {
1307
+ clearTimeout(timer);
1308
+ const output = [];
1309
+ if (stdout) output.push(stdout);
1310
+ if (stderr) output.push(`STDERR:
1311
+ ${stderr}`);
1312
+ const prefix = desc ? `[${desc}]
1313
+ ` : "";
1314
+ if (code === 0) {
1315
+ resolve({
1316
+ content: prefix + (output.join("\n") || "Command executed successfully (no output)")
1317
+ });
1318
+ } else {
1319
+ resolve({
1320
+ content: `${prefix}Command failed (exit code ${code})
1321
+ ${output.join("\n")}`,
1322
+ isError: true
1323
+ });
1324
+ }
1325
+ });
1326
+ });
1327
+ }
1328
+ });
1329
+ function getShellTools() {
1330
+ return [bashTool];
1331
+ }
1332
+ var DEFAULT_GREP_HEAD_LIMIT = 250;
1333
+ var MAX_LINE_LENGTH2 = 2e3;
1334
+ var FG_IGNORE = [
1335
+ "**/node_modules/**",
1336
+ "**/.git/**",
1337
+ "**/dist/**",
1338
+ "**/build/**",
1339
+ "**/__pycache__/**"
1340
+ ];
1341
+ function truncateMatchLineForDisplay(line, regex) {
1342
+ const max = MAX_LINE_LENGTH2;
1343
+ if (line.length <= max) {
1344
+ return line;
1345
+ }
1346
+ const safeFlags = regex.flags.replace(/g/g, "").replace(/y/g, "");
1347
+ const re = new RegExp(regex.source, safeFlags);
1348
+ const m = re.exec(line);
1349
+ if (!m || m.index === void 0) {
1350
+ return line.slice(0, max) + "...";
1351
+ }
1352
+ const matchStart = m.index;
1353
+ const matchEnd = m.index + m[0].length;
1354
+ const matchLen = matchEnd - matchStart;
1355
+ if (matchLen >= max) {
1356
+ return m[0].slice(0, max) + "...";
1357
+ }
1358
+ let start = matchStart - Math.floor((max - matchLen) / 2);
1359
+ let end = start + max;
1360
+ if (start < 0) {
1361
+ start = 0;
1362
+ end = max;
1363
+ }
1364
+ if (end > line.length) {
1365
+ end = line.length;
1366
+ start = end - max;
1367
+ }
1368
+ if (start < 0) {
1369
+ start = 0;
1370
+ }
1371
+ if (matchStart < start) {
1372
+ start = Math.max(0, matchEnd - max);
1373
+ end = Math.min(line.length, start + max);
1374
+ }
1375
+ if (matchEnd > end) {
1376
+ end = line.length;
1377
+ start = Math.max(0, end - max);
1378
+ }
1379
+ const slice = line.slice(start, end);
1380
+ return (start > 0 ? "..." : "") + slice + (end < line.length ? "..." : "");
1381
+ }
1382
+ var grepTool = createTool({
1383
+ name: "Grep",
1384
+ category: "search",
1385
+ description: `Search file contents with ECMAScript regular expressions (line-by-line, pure Node.js; does not spawn ripgrep/grep).
1386
+
1387
+ Usage:
1388
+ - ALWAYS use Grep for search tasks. NEVER invoke grep or rg as a Bash command.
1389
+ - Uses JavaScript RegExp semantics (not PCRE/ripgrep); very complex patterns may differ from rg.
1390
+ - Filter files with glob (e.g. "*.js", "**/*.{ts,tsx}"); brace expansion is supported.
1391
+ - Root directory search respects a .gitignore file at the search root when present.
1392
+ - Long match lines are truncated with a window centered on the match (max ${MAX_LINE_LENGTH2} chars).
1393
+ - Use the Agent tool for open-ended searches requiring multiple rounds.
1394
+ - Output: matching lines with file paths and line numbers`,
1395
+ parameters: zod.z.object({
1396
+ pattern: zod.z.string().describe("The regular expression pattern to search for in file contents"),
1397
+ path: zod.z.string().optional().describe("File or directory to search in. Defaults to the agent working directory when available, otherwise current process directory."),
1398
+ glob: zod.z.string().optional().describe('Glob pattern to filter files (e.g. "*.js", "**/*.{ts,tsx}")'),
1399
+ case_insensitive: zod.z.boolean().default(false).describe("Case insensitive search"),
1400
+ context: zod.z.number().int().min(0).default(0).describe("Number of lines to show before and after each match"),
1401
+ head_limit: zod.z.number().int().min(1).default(DEFAULT_GREP_HEAD_LIMIT).describe(`Limit to the first N matching lines. Defaults to ${DEFAULT_GREP_HEAD_LIMIT}.`)
1402
+ }),
1403
+ handler: async ({ pattern, path: searchPath, glob: globParam, case_insensitive, context, head_limit }, toolContext) => {
1404
+ try {
1405
+ const fs = await import('fs/promises');
1406
+ const pathModule = await import('path');
1407
+ const projectBase = pathModule.resolve(toolContext?.projectDir ?? process.cwd());
1408
+ const rootPathRaw = searchPath || toolContext?.projectDir || ".";
1409
+ const resolvedRoot = pathModule.resolve(rootPathRaw);
1410
+ let stat;
1411
+ try {
1412
+ stat = await fs.stat(resolvedRoot);
1413
+ } catch {
1414
+ return {
1415
+ content: `Path does not exist: ${rootPathRaw}`,
1416
+ isError: true
1417
+ };
1418
+ }
1419
+ let regex;
1420
+ try {
1421
+ regex = new RegExp(pattern, case_insensitive ? "i" : "");
1422
+ } catch (e) {
1423
+ return {
1424
+ content: `Invalid regex pattern: ${e instanceof Error ? e.message : String(e)}`,
1425
+ isError: true
1426
+ };
1427
+ }
1428
+ let filesToSearch = [];
1429
+ if (stat.isFile()) {
1430
+ if (globParam) {
1431
+ const rel = pathModule.relative(projectBase, resolvedRoot).split(pathModule.sep).join("/");
1432
+ if (!micromatch__default.default.isMatch(rel, globParam, { posix: true })) {
1433
+ return {
1434
+ content: "No matches found (path does not match glob filter)"
1435
+ };
1436
+ }
1437
+ }
1438
+ filesToSearch = [resolvedRoot];
1439
+ } else {
1440
+ const absolutePaths = await fg__default.default.glob(globParam ?? "**/*", {
1441
+ cwd: resolvedRoot,
1442
+ onlyFiles: true,
1443
+ dot: false,
1444
+ ignore: [...FG_IGNORE],
1445
+ absolute: true
1446
+ });
1447
+ let ig = null;
1448
+ try {
1449
+ const giContent = await fs.readFile(pathModule.join(resolvedRoot, ".gitignore"), "utf-8");
1450
+ ig = ignore__default.default().add(giContent);
1451
+ } catch {
1452
+ }
1453
+ filesToSearch = absolutePaths.filter((abs) => {
1454
+ const rel = pathModule.relative(resolvedRoot, abs).split(pathModule.sep).join("/");
1455
+ if (!rel || rel.startsWith("..")) {
1456
+ return true;
1457
+ }
1458
+ return !(ig && ig.ignores(rel));
1459
+ });
1460
+ }
1461
+ const results = [];
1462
+ let totalMatches = 0;
1463
+ for (const filePath of filesToSearch) {
1464
+ let content;
1465
+ try {
1466
+ content = await fs.readFile(filePath, "utf-8");
1467
+ } catch {
1468
+ continue;
1469
+ }
1470
+ const lines = content.split("\n");
1471
+ const relDisplay = pathModule.relative(resolvedRoot, filePath).split(pathModule.sep).join("/");
1472
+ const displayPath = relDisplay || pathModule.basename(filePath);
1473
+ for (let i = 0; i < lines.length; i++) {
1474
+ if (regex.test(lines[i])) {
1475
+ totalMatches++;
1476
+ const matchLineOut = truncateMatchLineForDisplay(lines[i], regex);
1477
+ if (context > 0) {
1478
+ const start = Math.max(0, i - context);
1479
+ const end = Math.min(lines.length - 1, i + context);
1480
+ if (start < i) {
1481
+ for (let j = start; j < i; j++) {
1482
+ results.push(`${displayPath}:${j + 1}-${lines[j]}`);
1483
+ }
1484
+ }
1485
+ results.push(`${displayPath}:${i + 1}:${matchLineOut}`);
1486
+ if (end > i) {
1487
+ for (let j = i + 1; j <= end; j++) {
1488
+ results.push(`${displayPath}:${j + 1}-${lines[j]}`);
1489
+ }
1490
+ }
1491
+ results.push("--");
1492
+ } else {
1493
+ results.push(`${displayPath}:${i + 1}:${matchLineOut}`);
1494
+ }
1495
+ if (totalMatches >= head_limit) break;
1496
+ }
1497
+ }
1498
+ if (totalMatches >= head_limit) break;
1499
+ }
1500
+ if (results.length === 0) {
1501
+ return { content: "No matches found" };
1502
+ }
1503
+ if (results[results.length - 1] === "--") {
1504
+ results.pop();
1505
+ }
1506
+ const output = results.join("\n");
1507
+ const suffix = totalMatches >= head_limit ? `
1508
+
1509
+ (Showing first ${head_limit} matches)` : `
1510
+
1511
+ (${totalMatches} matches)`;
1512
+ return { content: output + suffix };
1513
+ } catch (error) {
1514
+ return {
1515
+ content: `Error searching: ${error instanceof Error ? error.message : String(error)}`,
1516
+ isError: true
1517
+ };
1518
+ }
1519
+ }
1520
+ });
1521
+ function getGrepTools() {
1522
+ return [grepTool];
1523
+ }
1524
+
1525
+ // src/tools/builtin/tavily-search.ts
1526
+ var TAVILY_SEARCH_URL = "https://api.tavily.com/search";
1527
+ var TAVILY_SEARCH_TIMEOUT_MS = 3e4;
1528
+ var TAVILY_MAX_INCLUDE_DOMAINS = 300;
1529
+ var TAVILY_MAX_EXCLUDE_DOMAINS = 150;
1530
+ var SEARCH_DEPTHS = /* @__PURE__ */ new Set(["basic", "advanced", "fast", "ultra-fast"]);
1531
+ function parseSearchDepthFromEnv() {
1532
+ const raw = process.env.TAVILY_SEARCH_DEPTH?.trim().toLowerCase();
1533
+ if (raw && SEARCH_DEPTHS.has(raw)) {
1534
+ return raw;
1535
+ }
1536
+ return "basic";
1537
+ }
1538
+ function parseMaxResultsFromEnv() {
1539
+ const raw = process.env.TAVILY_MAX_RESULTS?.trim();
1540
+ if (!raw) {
1541
+ return 5;
1542
+ }
1543
+ const n = Number.parseInt(raw, 10);
1544
+ if (!Number.isFinite(n) || n < 1 || n > 20) {
1545
+ return 5;
1546
+ }
1547
+ return n;
1548
+ }
1549
+ function truncateDomains(domains, max) {
1550
+ if (!domains?.length) {
1551
+ return [];
1552
+ }
1553
+ return domains.slice(0, max).map((d) => d.trim()).filter(Boolean);
1554
+ }
1555
+ function escapeMarkdownLinkLabel(text) {
1556
+ return text.replace(/\\/g, "\\\\").replace(/\[/g, "\\[").replace(/\]/g, "\\]");
1557
+ }
1558
+ function formatTavilyResultsMarkdown(data) {
1559
+ const lines = [];
1560
+ if (data.answer) {
1561
+ lines.push("**Summary**", "", data.answer, "");
1562
+ }
1563
+ const results = data.results ?? [];
1564
+ if (results.length === 0) {
1565
+ lines.push("No search results returned.");
1566
+ return lines.join("\n").trim();
1567
+ }
1568
+ lines.push("**Results**", "");
1569
+ for (const r of results) {
1570
+ const title = (r.title ?? "Untitled").trim();
1571
+ const url = (r.url ?? "").trim();
1572
+ const snippet = (r.content ?? "").trim();
1573
+ if (url) {
1574
+ lines.push(`- [${escapeMarkdownLinkLabel(title)}](${url})`);
1575
+ } else {
1576
+ lines.push(`- ${escapeMarkdownLinkLabel(title)}`);
1577
+ }
1578
+ if (snippet) {
1579
+ lines.push(` ${snippet.replace(/\n+/g, " ")}`);
1580
+ }
1581
+ lines.push("");
1582
+ }
1583
+ return lines.join("\n").trim();
1584
+ }
1585
+ function extractErrorMessage(status, body) {
1586
+ if (body && typeof body === "object" && "detail" in body) {
1587
+ const detail = body.detail;
1588
+ if (detail && typeof detail === "object" && detail !== null && "error" in detail) {
1589
+ const err = detail.error;
1590
+ if (typeof err === "string") {
1591
+ return err;
1592
+ }
1593
+ }
1594
+ if (typeof detail === "string") {
1595
+ return detail;
1596
+ }
1597
+ if (Array.isArray(detail) && detail.length > 0) {
1598
+ const first = detail[0];
1599
+ if (first && typeof first === "object" && first !== null && "msg" in first) {
1600
+ const msg = first.msg;
1601
+ if (typeof msg === "string") {
1602
+ return msg;
1603
+ }
1604
+ }
1605
+ }
1606
+ }
1607
+ if (status === 401) {
1608
+ return "Unauthorized: missing or invalid API key.";
1609
+ }
1610
+ if (status === 429) {
1611
+ return "Rate limit exceeded. Try again later.";
1612
+ }
1613
+ return `Tavily search failed (HTTP ${status}).`;
1614
+ }
1615
+ async function tavilyWebSearch(input) {
1616
+ const {
1617
+ query,
1618
+ apiKey,
1619
+ allowed_domains,
1620
+ blocked_domains,
1621
+ searchDepth = parseSearchDepthFromEnv(),
1622
+ maxResults = parseMaxResultsFromEnv()
1623
+ } = input;
1624
+ const include_domains = truncateDomains(allowed_domains, TAVILY_MAX_INCLUDE_DOMAINS);
1625
+ const exclude_domains = truncateDomains(blocked_domains, TAVILY_MAX_EXCLUDE_DOMAINS);
1626
+ const body = {
1627
+ api_key: apiKey,
1628
+ query,
1629
+ search_depth: searchDepth,
1630
+ max_results: maxResults,
1631
+ include_domains,
1632
+ exclude_domains
1633
+ };
1634
+ try {
1635
+ const res = await fetch(TAVILY_SEARCH_URL, {
1636
+ method: "POST",
1637
+ headers: { "Content-Type": "application/json" },
1638
+ body: JSON.stringify(body),
1639
+ signal: AbortSignal.timeout(TAVILY_SEARCH_TIMEOUT_MS)
1640
+ });
1641
+ const text = await res.text();
1642
+ let json;
1643
+ try {
1644
+ json = text ? JSON.parse(text) : {};
1645
+ } catch {
1646
+ return {
1647
+ content: `Tavily search failed: invalid JSON response (HTTP ${res.status}).`,
1648
+ isError: true
1649
+ };
1650
+ }
1651
+ if (!res.ok) {
1652
+ const msg = extractErrorMessage(res.status, json);
1653
+ return {
1654
+ content: `Tavily search error: ${msg}`,
1655
+ isError: true
1656
+ };
1657
+ }
1658
+ const data = json;
1659
+ const markdown = formatTavilyResultsMarkdown(data);
1660
+ return { content: markdown };
1661
+ } catch (error) {
1662
+ const message = error instanceof Error ? error.message : String(error);
1663
+ return {
1664
+ content: `Tavily search failed: ${message}`,
1665
+ isError: true
1666
+ };
1667
+ }
1668
+ }
1669
+ var WEB_FETCH_DEFAULT_TIMEOUT_MS = 3e4;
1670
+ var WEB_FETCH_MAX_RESPONSE_BYTES = 2 * 1024 * 1024;
1671
+ var WEB_FETCH_MAX_OUTPUT_CHARS = 512e3;
1672
+ var WEB_FETCH_MAX_REDIRECTS = 5;
1673
+ var USER_AGENT = "Agent-SDK-WebFetch/0.1 (+https://github.com/)";
1674
+ function isBlockedAddress(addr) {
1675
+ if (addr.kind() === "ipv4") {
1676
+ return addr.range() !== "unicast";
1677
+ }
1678
+ const v6 = addr;
1679
+ if (v6.isIPv4MappedAddress()) {
1680
+ const v4 = v6.toIPv4Address();
1681
+ return v4.range() !== "unicast";
1682
+ }
1683
+ return v6.range() !== "unicast";
1684
+ }
1685
+ function isDangerousIp(ip) {
1686
+ try {
1687
+ if (!ipaddr__default.default.isValid(ip)) {
1688
+ return true;
1689
+ }
1690
+ const addr = ipaddr__default.default.parse(ip);
1691
+ return isBlockedAddress(addr);
1692
+ } catch {
1693
+ return true;
1694
+ }
1695
+ }
1696
+ function isBlockedHostname(hostname) {
1697
+ const h = hostname.toLowerCase();
1698
+ if (h === "localhost" || h === "metadata") {
1699
+ return true;
1700
+ }
1701
+ if (h.endsWith(".localhost")) {
1702
+ return true;
1703
+ }
1704
+ if (h === "metadata.google.internal") {
1705
+ return true;
1706
+ }
1707
+ if (h.endsWith(".internal")) {
1708
+ return true;
1709
+ }
1710
+ if (h.endsWith(".local")) {
1711
+ return true;
1712
+ }
1713
+ return false;
1714
+ }
1715
+ function assertHttpUrl(url) {
1716
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
1717
+ throw new Error(`Only http and https URLs are allowed, got: ${url.protocol}`);
1718
+ }
1719
+ }
1720
+ async function assertResolvableHostSafe(hostname, lookup = dns__namespace.promises.lookup) {
1721
+ const results = await lookup(hostname, { all: true, verbatim: true });
1722
+ if (!results.length) {
1723
+ throw new Error("DNS lookup returned no addresses");
1724
+ }
1725
+ for (const { address } of results) {
1726
+ if (isDangerousIp(address)) {
1727
+ throw new Error(`Refused to connect: address "${address}" is not a public endpoint`);
1728
+ }
1729
+ }
1730
+ }
1731
+ async function assertUrlSafeForFetch(url, lookup = dns__namespace.promises.lookup) {
1732
+ assertHttpUrl(url);
1733
+ const host = url.hostname;
1734
+ if (!host) {
1735
+ throw new Error("URL has no hostname");
1736
+ }
1737
+ if (isBlockedHostname(host)) {
1738
+ throw new Error(`Refused to connect: hostname "${host}" is blocked`);
1739
+ }
1740
+ if (ipaddr__default.default.isValid(host)) {
1741
+ if (isDangerousIp(host)) {
1742
+ throw new Error(`Refused to connect: address "${host}" is not a public endpoint`);
1743
+ }
1744
+ return;
1745
+ }
1746
+ await assertResolvableHostSafe(host, lookup);
1747
+ }
1748
+ async function readResponseBodyWithCap(response, maxBytes) {
1749
+ const cl = response.headers.get("content-length");
1750
+ if (cl !== null && cl !== "") {
1751
+ const n = Number.parseInt(cl, 10);
1752
+ if (Number.isFinite(n) && n > maxBytes) {
1753
+ throw new Error(`Response too large (Content-Length: ${n} bytes, max ${maxBytes})`);
1754
+ }
1755
+ }
1756
+ if (!response.body) {
1757
+ const buf = new Uint8Array(await response.arrayBuffer());
1758
+ return decodeWithCap(buf, maxBytes);
1759
+ }
1760
+ const reader = response.body.getReader();
1761
+ const chunks = [];
1762
+ let total = 0;
1763
+ let truncated = false;
1764
+ try {
1765
+ for (; ; ) {
1766
+ const { done, value } = await reader.read();
1767
+ if (done) {
1768
+ break;
1769
+ }
1770
+ if (!value || value.length === 0) {
1771
+ continue;
1772
+ }
1773
+ if (total + value.length <= maxBytes) {
1774
+ chunks.push(value);
1775
+ total += value.length;
1776
+ } else {
1777
+ const rest = maxBytes - total;
1778
+ if (rest > 0) {
1779
+ chunks.push(value.slice(0, rest));
1780
+ total += rest;
1781
+ }
1782
+ truncated = true;
1783
+ await reader.cancel();
1784
+ break;
1785
+ }
1786
+ }
1787
+ } finally {
1788
+ reader.releaseLock();
1789
+ }
1790
+ const merged = mergeChunks(chunks, total);
1791
+ const dec = decodeWithCap(merged, maxBytes);
1792
+ return { text: dec.text, truncated: dec.truncated || truncated };
1793
+ }
1794
+ function mergeChunks(chunks, total) {
1795
+ const out = new Uint8Array(total);
1796
+ let offset = 0;
1797
+ for (const c of chunks) {
1798
+ out.set(c, offset);
1799
+ offset += c.length;
1800
+ }
1801
+ return out;
1802
+ }
1803
+ function decodeWithCap(buf, maxBytes) {
1804
+ const truncated = buf.length >= maxBytes;
1805
+ const decoder = new TextDecoder("utf-8", { fatal: false });
1806
+ return { text: decoder.decode(buf), truncated };
1807
+ }
1808
+ function primaryMimeType(contentType) {
1809
+ if (!contentType) {
1810
+ return "";
1811
+ }
1812
+ return contentType.split(";")[0]?.trim().toLowerCase() ?? "";
1813
+ }
1814
+ function htmlToMarkdown(html) {
1815
+ const { document } = linkedom.parseHTML(html);
1816
+ const reader = new readability.Readability(document);
1817
+ const article = reader.parse();
1818
+ let htmlContent = article?.content?.trim() ?? "";
1819
+ if (!htmlContent && document.body) {
1820
+ htmlContent = document.body.innerHTML ?? "";
1821
+ }
1822
+ if (!htmlContent) {
1823
+ return "";
1824
+ }
1825
+ const turndown = new TurndownService__default.default({
1826
+ headingStyle: "atx",
1827
+ codeBlockStyle: "fenced"
1828
+ });
1829
+ return turndown.turndown(htmlContent).trim();
1830
+ }
1831
+ function truncateOutput(text, maxChars) {
1832
+ if (text.length <= maxChars) {
1833
+ return text;
1834
+ }
1835
+ return `${text.slice(0, maxChars)}
1836
+
1837
+ [Output truncated to ${maxChars} characters]`;
1838
+ }
1839
+ function formatJsonText(raw) {
1840
+ try {
1841
+ const v = JSON.parse(raw);
1842
+ return JSON.stringify(v, null, 2);
1843
+ } catch {
1844
+ return raw;
1845
+ }
1846
+ }
1847
+ async function fetchUrlToReadableContent(urlString, options = {}) {
1848
+ const timeoutMs = options.timeoutMs ?? WEB_FETCH_DEFAULT_TIMEOUT_MS;
1849
+ const maxResponseBytes = options.maxResponseBytes ?? WEB_FETCH_MAX_RESPONSE_BYTES;
1850
+ const maxOutputChars = options.maxOutputChars ?? WEB_FETCH_MAX_OUTPUT_CHARS;
1851
+ const maxRedirects = options.maxRedirects ?? WEB_FETCH_MAX_REDIRECTS;
1852
+ const lookup = options.dnsLookup ?? dns__namespace.promises.lookup;
1853
+ let currentUrl;
1854
+ try {
1855
+ currentUrl = new URL(urlString);
1856
+ } catch {
1857
+ return { content: "Invalid URL", isError: true };
1858
+ }
1859
+ try {
1860
+ let redirectCount = 0;
1861
+ for (; ; ) {
1862
+ await assertUrlSafeForFetch(currentUrl, lookup);
1863
+ const response = await fetch(currentUrl, {
1864
+ method: "GET",
1865
+ redirect: "manual",
1866
+ signal: AbortSignal.timeout(timeoutMs),
1867
+ headers: {
1868
+ Accept: "text/html,application/json,text/plain;q=0.9,*/*;q=0.8",
1869
+ "User-Agent": USER_AGENT
1870
+ }
1871
+ });
1872
+ const status = response.status;
1873
+ if (status >= 300 && status < 400) {
1874
+ const loc = response.headers.get("location");
1875
+ if (!loc || redirectCount >= maxRedirects) {
1876
+ return {
1877
+ content: loc ? `Too many redirects or missing Location (HTTP ${status})` : `Redirect without Location (HTTP ${status})`,
1878
+ isError: true
1879
+ };
1880
+ }
1881
+ redirectCount++;
1882
+ currentUrl = new URL(loc, currentUrl);
1883
+ continue;
1884
+ }
1885
+ if (!response.ok) {
1886
+ return {
1887
+ content: `Failed to fetch: ${status} ${response.statusText}`,
1888
+ isError: true
1889
+ };
1890
+ }
1891
+ const mime = primaryMimeType(response.headers.get("content-type"));
1892
+ const { text: rawText, truncated: bodyTruncated } = await readResponseBodyWithCap(
1893
+ response,
1894
+ maxResponseBytes
1895
+ );
1896
+ let content;
1897
+ if (mime.includes("html") || mime === "" || mime === "application/xhtml+xml") {
1898
+ content = htmlToMarkdown(rawText);
1899
+ } else if (mime.includes("json") || mime.endsWith("+json")) {
1900
+ content = formatJsonText(rawText);
1901
+ } else {
1902
+ content = rawText;
1903
+ }
1904
+ if (bodyTruncated) {
1905
+ content = `${content}
1906
+
1907
+ [Response body truncated at ${maxResponseBytes} bytes]`;
1908
+ }
1909
+ content = truncateOutput(content, maxOutputChars);
1910
+ return { content, isError: false };
1911
+ }
1912
+ } catch (error) {
1913
+ return {
1914
+ content: `Error fetching webpage: ${formatNetworkError(error)}`,
1915
+ isError: true
1916
+ };
1917
+ }
1918
+ }
1919
+ function formatNetworkError(error) {
1920
+ if (!(error instanceof Error)) {
1921
+ return String(error);
1922
+ }
1923
+ const parts = [error.message];
1924
+ let cursor = error.cause;
1925
+ let depth = 0;
1926
+ while (cursor instanceof Error && depth < 4) {
1927
+ parts.push(cursor.message);
1928
+ cursor = cursor.cause;
1929
+ depth++;
1930
+ }
1931
+ if (typeof cursor === "object" && cursor !== null && "code" in cursor) {
1932
+ const code = cursor.code;
1933
+ if (code && !parts.join(" ").includes(code)) {
1934
+ parts.push(`code=${code}`);
1935
+ }
1936
+ }
1937
+ return parts.join(" \u2014 ");
1938
+ }
1939
+
1940
+ // src/tools/builtin/web.ts
1941
+ var webFetchTool = createTool({
1942
+ name: "WebFetch",
1943
+ category: "web",
1944
+ description: `Fetches public http(s) URLs and returns readable markdown (HTML), formatted JSON, or plain text.
1945
+
1946
+ Limits and behavior:
1947
+ - Only http:// and https:// are allowed; private IPs, loopback, link-local, and common metadata/local hostnames are blocked (SSRF protection).
1948
+ - Requests time out, responses are size-capped, and very long markdown may be truncated before return.
1949
+ - If the result exceeds the direct context limit, the full text is saved under your user tool-outputs directory; the tool response will include the file path\u2014use **Read** with offset and limit parameters to page through it (same pattern as large shell output).
1950
+ - Authenticated or private pages usually cannot be fetched; prefer specialized MCP tools when available.
1951
+ - Use https URLs directly when possible (no automatic http\u2192https upgrade).
1952
+ - For GitHub URLs, prefer the gh CLI via Bash when appropriate (e.g., gh pr view, gh api).`,
1953
+ parameters: zod.z.object({
1954
+ url: zod.z.string().describe("The URL to fetch content from")
1955
+ }),
1956
+ handler: async ({ url }) => {
1957
+ const result = await fetchUrlToReadableContent(url);
1958
+ return result.isError ? { content: result.content, isError: true } : { content: result.content };
1959
+ }
1960
+ });
1961
+ var webSearchTool = createTool({
1962
+ name: "WebSearch",
1963
+ category: "web",
1964
+ description: `- Allows searching the web and use the results to inform responses
1965
+ - Provides up-to-date information for current events and recent data
1966
+ - Returns search result information with links as markdown hyperlinks
1967
+ - Use this tool for accessing information beyond the knowledge cutoff
1968
+ - When \`TAVILY_API_KEY\` is set, uses the Tavily Search API; otherwise returns a configuration message`,
1969
+ parameters: zod.z.object({
1970
+ query: zod.z.string().min(2).describe("The search query to use"),
1971
+ allowed_domains: zod.z.array(zod.z.string()).optional().describe("Only include search results from these domains"),
1972
+ blocked_domains: zod.z.array(zod.z.string()).optional().describe("Never include search results from these domains")
1973
+ }),
1974
+ handler: async ({ query, allowed_domains, blocked_domains }) => {
1975
+ const apiKey = process.env.TAVILY_API_KEY?.trim();
1976
+ if (!apiKey) {
1977
+ return {
1978
+ content: `Web search is not configured. Set the TAVILY_API_KEY environment variable to enable Tavily Search, or register a custom WebSearch tool with another provider. Query was: "${query}"`,
1979
+ isError: true
1980
+ };
1981
+ }
1982
+ return tavilyWebSearch({
1983
+ query,
1984
+ apiKey,
1985
+ allowed_domains,
1986
+ blocked_domains
1987
+ });
1988
+ }
1989
+ });
1990
+ function getWebTools() {
1991
+ return [webFetchTool, webSearchTool];
1992
+ }
1993
+ var tasks = /* @__PURE__ */ new Map();
1994
+ var nextId = 1;
1995
+ var taskCreateTool = createTool({
1996
+ name: "TaskCreate",
1997
+ category: "planning",
1998
+ description: `Use this tool to create a structured task list for your current coding session. This helps you track progress, organize complex tasks, and demonstrate thoroughness to the user.
1999
+
2000
+ ## When to Use This Tool
2001
+
2002
+ - Complex multi-step tasks - When a task requires 3 or more distinct steps or actions
2003
+ - Non-trivial and complex tasks - Tasks that require careful planning or multiple operations
2004
+ - User explicitly requests todo list - When the user directly asks you to use the task list
2005
+ - User provides multiple tasks - When users provide a list of things to be done
2006
+
2007
+ ## When NOT to Use
2008
+
2009
+ - There is only a single, straightforward task
2010
+ - The task is trivial and tracking it provides no organizational benefit
2011
+ - The task can be completed in less than 3 trivial steps
2012
+
2013
+ All tasks are created with status pending.`,
2014
+ parameters: zod.z.object({
2015
+ subject: zod.z.string().describe("A brief title for the task"),
2016
+ description: zod.z.string().describe("What needs to be done"),
2017
+ activeForm: zod.z.string().optional().describe('Present continuous form shown in spinner when in_progress (e.g., "Running tests")')
2018
+ }),
2019
+ handler: async ({ subject, description, activeForm }) => {
2020
+ const id = String(nextId++);
2021
+ const task = {
2022
+ id,
2023
+ subject,
2024
+ description,
2025
+ status: "pending",
2026
+ ...activeForm && { activeForm }
2027
+ };
2028
+ tasks.set(id, task);
2029
+ return {
2030
+ content: `Task created [${id}]: ${subject}`,
2031
+ metadata: { task }
2032
+ };
2033
+ }
2034
+ });
2035
+ var taskUpdateTool = createTool({
2036
+ name: "TaskUpdate",
2037
+ category: "planning",
2038
+ description: `Use this tool to update a task in the task list.
2039
+
2040
+ ## When to Use This Tool
2041
+
2042
+ **Mark tasks as completed:**
2043
+ - When you have completed the work described in a task
2044
+ - IMPORTANT: Always mark your assigned tasks as completed when you finish them
2045
+
2046
+ **Delete tasks:**
2047
+ - When a task is no longer relevant or was created in error
2048
+
2049
+ ## Status Workflow
2050
+
2051
+ Status progresses: pending \u2192 in_progress \u2192 completed`,
2052
+ parameters: zod.z.object({
2053
+ taskId: zod.z.string().describe("The ID of the task to update"),
2054
+ status: zod.z.enum(["pending", "in_progress", "completed", "deleted"]).optional().describe("New status for the task"),
2055
+ subject: zod.z.string().optional().describe("New subject for the task"),
2056
+ description: zod.z.string().optional().describe("New description for the task"),
2057
+ activeForm: zod.z.string().optional().describe("Present continuous form shown in spinner when in_progress")
2058
+ }),
2059
+ handler: async ({ taskId, status, subject, description, activeForm }) => {
2060
+ const task = tasks.get(taskId);
2061
+ if (!task) {
2062
+ return {
2063
+ content: `Task ${taskId} not found`,
2064
+ isError: true
2065
+ };
2066
+ }
2067
+ if (status === "deleted") {
2068
+ tasks.delete(taskId);
2069
+ return { content: `Task ${taskId} deleted` };
2070
+ }
2071
+ if (status) task.status = status;
2072
+ if (subject) task.subject = subject;
2073
+ if (description) task.description = description;
2074
+ if (activeForm) task.activeForm = activeForm;
2075
+ const icon = task.status === "completed" ? "x" : task.status === "in_progress" ? ">" : " ";
2076
+ return {
2077
+ content: `Task [${taskId}] updated: [${icon}] ${task.subject}`,
2078
+ metadata: { task }
2079
+ };
2080
+ }
2081
+ });
2082
+ var taskListTool = createTool({
2083
+ name: "TaskList",
2084
+ category: "planning",
2085
+ description: `Use this tool to list all tasks in the task list.
2086
+
2087
+ ## Output
2088
+
2089
+ Returns a summary of each task:
2090
+ - id: Task identifier (use with TaskUpdate)
2091
+ - subject: Brief description of the task
2092
+ - status: pending, in_progress, or completed`,
2093
+ parameters: zod.z.object({}),
2094
+ handler: async () => {
2095
+ if (tasks.size === 0) {
2096
+ return { content: "No tasks in the list." };
2097
+ }
2098
+ const lines = [];
2099
+ for (const [, task] of tasks) {
2100
+ const icon = task.status === "completed" ? "x" : task.status === "in_progress" ? ">" : " ";
2101
+ lines.push(`[${icon}] [${task.id}] ${task.subject}`);
2102
+ }
2103
+ const pending = [...tasks.values()].filter((t) => t.status === "pending").length;
2104
+ const inProgress = [...tasks.values()].filter((t) => t.status === "in_progress").length;
2105
+ const completed = [...tasks.values()].filter((t) => t.status === "completed").length;
2106
+ return {
2107
+ content: `Tasks (${completed} completed, ${inProgress} in progress, ${pending} pending):
2108
+
2109
+ ${lines.join("\n")}`
2110
+ };
2111
+ }
2112
+ });
2113
+ function getTaskTools() {
2114
+ return [taskCreateTool, taskUpdateTool, taskListTool];
2115
+ }
2116
+ var questionsSchema = zod.z.object({
2117
+ questions: zod.z.array(
2118
+ zod.z.object({
2119
+ question: zod.z.string().describe(
2120
+ "The complete question to ask the user. Should be clear, specific, and end with a question mark."
2121
+ ),
2122
+ header: zod.z.string().describe("Very short label displayed as a chip/tag (max 30 chars)."),
2123
+ options: zod.z.array(
2124
+ zod.z.object({
2125
+ label: zod.z.string().describe("The display text for this option. Should be concise (1-5 words)."),
2126
+ description: zod.z.string().describe("Explanation of what this option means or what will happen if chosen.")
2127
+ })
2128
+ ).min(2).max(4).describe("The available choices for this question. Must have 2-4 options."),
2129
+ multiSelect: zod.z.boolean().default(false).describe("Set to true to allow the user to select multiple options.")
2130
+ })
2131
+ ).min(1).max(4).describe("Questions to ask the user (1-4 questions)")
2132
+ });
2133
+ function formatAskUserQuestionPrompt(questions) {
2134
+ const lines = [];
2135
+ for (const q of questions) {
2136
+ lines.push(`[${q.header}] ${q.question}
2137
+ `);
2138
+ q.options.forEach((opt, i) => {
2139
+ lines.push(` ${i + 1}. ${opt.label} \u2014 ${opt.description}`);
2140
+ });
2141
+ if (q.multiSelect) {
2142
+ lines.push("\n(Select one or more options)");
2143
+ } else {
2144
+ lines.push("\n(Select one option)");
2145
+ }
2146
+ lines.push("");
2147
+ }
2148
+ return lines.join("\n");
2149
+ }
2150
+ function formatAnswerSummary(questions, answers) {
2151
+ const lines = ["", "--- User responses ---"];
2152
+ for (const a of answers) {
2153
+ const q = questions[a.questionIndex];
2154
+ if (a.otherText !== void 0) {
2155
+ lines.push(
2156
+ `[${q.header}] Other: ${a.otherText.trim() === "" ? "(empty)" : a.otherText}`
2157
+ );
2158
+ } else if (a.selectedLabels.length > 0) {
2159
+ lines.push(`[${q.header}] ${a.selectedLabels.join(", ")}`);
2160
+ } else {
2161
+ lines.push(`[${q.header}] (no selection)`);
2162
+ }
2163
+ }
2164
+ return lines.join("\n");
2165
+ }
2166
+ function createAskUserQuestionTool(options) {
2167
+ const resolve = options?.resolve;
2168
+ return createTool({
2169
+ name: "AskUserQuestion",
2170
+ category: "interaction",
2171
+ description: `Use this tool when you need to ask the user questions during execution. This allows you to:
2172
+ 1. Gather user preferences or requirements
2173
+ 2. Clarify ambiguous instructions
2174
+ 3. Get decisions on implementation choices as you work
2175
+ 4. Offer choices to the user about what direction to take.
2176
+
2177
+ The host application must provide a resolver that collects answers; without it, only the question text is returned.
2178
+
2179
+ Usage notes:
2180
+ - Users will always be able to select "Other" to provide custom text input
2181
+ - Use multiSelect: true to allow multiple answers to be selected for a question
2182
+ - If you recommend a specific option, make that the first option in the list and add "(Recommended)" at the end of the label`,
2183
+ parameters: questionsSchema,
2184
+ handler: async ({ questions }) => {
2185
+ const promptText = formatAskUserQuestionPrompt(questions);
2186
+ if (!resolve) {
2187
+ return {
2188
+ content: promptText,
2189
+ metadata: { questions }
2190
+ };
2191
+ }
2192
+ let answers;
2193
+ try {
2194
+ answers = await resolve(questions);
2195
+ } catch (error) {
2196
+ const msg = error instanceof Error ? error.message : String(error);
2197
+ return {
2198
+ content: `AskUserQuestion failed: ${msg}`,
2199
+ isError: true,
2200
+ metadata: { questions }
2201
+ };
2202
+ }
2203
+ const summary = formatAnswerSummary(questions, answers);
2204
+ return {
2205
+ content: promptText + summary,
2206
+ metadata: { questions, answers }
2207
+ };
2208
+ }
2209
+ });
2210
+ }
2211
+ var questionTool = createAskUserQuestionTool();
2212
+ function getInteractionTools(options) {
2213
+ return [createAskUserQuestionTool(options)];
2214
+ }
2215
+ function createSkillTool(skillRegistry) {
2216
+ return createTool({
2217
+ name: "Skill",
2218
+ category: "skills",
2219
+ description: `Execute a skill within the main conversation.
2220
+
2221
+ When users ask you to perform tasks, check if any of the available skills match. Skills provide specialized capabilities and domain knowledge.
2222
+
2223
+ How to invoke:
2224
+ - Use this tool with the skill name and optional arguments
2225
+ - Available skills are listed in system-reminder messages in the conversation
2226
+ - When a skill matches the user's request, invoke the relevant Skill tool BEFORE generating any other response about the task
2227
+ - Do not invoke a skill that is already running`,
2228
+ parameters: zod.z.object({
2229
+ skill: zod.z.string().describe('The skill name. E.g., "commit", "review-pr", or "pdf"'),
2230
+ args: zod.z.string().optional().describe("Optional arguments for the skill")
2231
+ }),
2232
+ handler: async ({ skill: skillName }) => {
2233
+ try {
2234
+ if (!skillRegistry.has(skillName)) {
2235
+ const available = skillRegistry.getMetadataList();
2236
+ const availableList = available.length > 0 ? available.map((s) => `- ${s.name}: ${s.description}`).join("\n") : "No skills available.";
2237
+ return {
2238
+ content: `Skill "${skillName}" not found.
2239
+
2240
+ Available skills:
2241
+ ${availableList}`,
2242
+ isError: true
2243
+ };
2244
+ }
2245
+ const fullContent = await skillRegistry.loadFullContent(skillName);
2246
+ const skill = skillRegistry.get(skillName);
2247
+ const sections = [];
2248
+ sections.push(`# Skill: ${skill?.metadata.name || skillName}`);
2249
+ sections.push(`Description: ${skill?.metadata.description || ""}`);
2250
+ sections.push(`Base Path: ${skill?.path || ""}`);
2251
+ sections.push("");
2252
+ sections.push("---");
2253
+ sections.push("");
2254
+ sections.push(fullContent);
2255
+ return {
2256
+ content: sections.join("\n")
2257
+ };
2258
+ } catch (error) {
2259
+ return {
2260
+ content: `Error activating skill "${skillName}": ${error instanceof Error ? error.message : String(error)}`,
2261
+ isError: true
2262
+ };
2263
+ }
2264
+ }
2265
+ });
2266
+ }
2267
+ function getSkillTools(skillRegistry) {
2268
+ return [createSkillTool(skillRegistry)];
2269
+ }
2270
+ var subagentRequestSchema = zod.z.object({
2271
+ prompt: zod.z.string().min(1).describe("Task prompt for the subagent. Include all required context."),
2272
+ description: zod.z.string().optional().describe("Short 3-5 words task label for logs and metadata."),
2273
+ subagent_type: zod.z.enum(["general-purpose", "explore"]).default("general-purpose").describe("Subagent profile/type to use."),
2274
+ allowed_tools: zod.z.array(zod.z.string().min(1)).optional().describe("Optional allowlist of tool names for the subagent."),
2275
+ max_iterations: zod.z.number().int().min(1).optional().describe("Maximum reasoning iterations for the subagent."),
2276
+ timeout_ms: zod.z.number().int().min(1e3).optional().describe("Timeout for this subagent run in milliseconds."),
2277
+ system_prompt: zod.z.string().optional().describe("Optional system prompt appended for the subagent run.")
2278
+ });
2279
+ function createAgentTool(options) {
2280
+ return createTool({
2281
+ name: "Agent",
2282
+ category: "planning",
2283
+ description: `Launch a new subagent to handle complex, multi-step tasks autonomously.
2284
+
2285
+ The Agent tool delegates work to a dedicated subagent that runs in isolated context and returns a final result back to the parent agent.
2286
+
2287
+ Use this tool when:
2288
+ - The task requires broader exploration or multi-step research
2289
+ - You want to keep the parent context focused and concise
2290
+ - You need a specific subagent profile (for example, explore vs general-purpose)
2291
+
2292
+ When NOT to use this tool:
2293
+ - Reading a known file path (use Read directly)
2294
+ - Simple symbol or text lookup (use Grep/Glob directly)
2295
+ - Small scoped changes in 1-3 files that do not benefit from delegation
2296
+
2297
+ Usage notes:
2298
+ - Always pass a short description and a complete prompt with all required context
2299
+ - Use subagent_type to select behavior; default is general-purpose
2300
+ - Subagents do not inherit parent conversation history, only the prompt you provide
2301
+ - Subagents cannot spawn other subagents (no nested Agent calls)`,
2302
+ parameters: subagentRequestSchema,
2303
+ handler: async (args, context) => {
2304
+ return options.runner(args, context);
2305
+ }
2306
+ });
2307
+ }
2308
+ var agentTool = createAgentTool({
2309
+ runner: async () => ({
2310
+ content: "Agent tool runner is not configured in this runtime",
2311
+ isError: true
2312
+ })
2313
+ });
2314
+ function getSubagentTools() {
2315
+ return [agentTool];
2316
+ }
2317
+
2318
+ // src/tools/builtin/index.ts
2319
+ function getAllBuiltinTools(skillRegistry, interactionOptions) {
2320
+ return [
2321
+ ...getFileSystemTools(),
2322
+ ...getShellTools(),
2323
+ ...getGrepTools(),
2324
+ ...getWebTools(),
2325
+ ...getTaskTools(),
2326
+ ...getInteractionTools(interactionOptions),
2327
+ ...getSubagentTools(),
2328
+ ...getSkillTools(skillRegistry)
2329
+ ];
2330
+ }
2331
+ function getSafeBuiltinTools(skillRegistry, interactionOptions) {
2332
+ return getAllBuiltinTools(skillRegistry, interactionOptions).filter((tool) => !tool.isDangerous);
2333
+ }
2334
+ var EVENT_JSON_TO_RUNTIME = {
2335
+ PreToolUse: "preToolUse",
2336
+ PostToolUse: "postToolUse",
2337
+ PostToolUseFailure: "postToolUseFailure"
2338
+ };
2339
+ function emptyHooksSettings() {
2340
+ return {
2341
+ hooks: {
2342
+ preToolUse: [],
2343
+ postToolUse: [],
2344
+ postToolUseFailure: []
2345
+ }
2346
+ };
2347
+ }
2348
+ function parseHooksSettingsFile(raw) {
2349
+ if (!raw || typeof raw !== "object") {
2350
+ return emptyHooksSettings();
2351
+ }
2352
+ const file = raw;
2353
+ const base = emptyHooksSettings();
2354
+ base.disableAllHooks = file.disableAllHooks;
2355
+ if (!file.hooks || typeof file.hooks !== "object") {
2356
+ return base;
2357
+ }
2358
+ for (const [jsonKey, eventType] of Object.entries(EVENT_JSON_TO_RUNTIME)) {
2359
+ const groups = file.hooks[jsonKey];
2360
+ if (!Array.isArray(groups)) continue;
2361
+ for (const g of groups) {
2362
+ if (!g || typeof g !== "object") continue;
2363
+ const group = g;
2364
+ const hookList = Array.isArray(group.hooks) ? group.hooks : [];
2365
+ const normalized = {
2366
+ matcher: typeof group.matcher === "string" ? group.matcher : void 0,
2367
+ hooks: []
2368
+ };
2369
+ for (const h of hookList) {
2370
+ if (!h || typeof h !== "object") continue;
2371
+ const c = h;
2372
+ if (c.type !== "command" || typeof c.command !== "string") continue;
2373
+ normalized.hooks.push({
2374
+ id: typeof c.id === "string" ? c.id : void 0,
2375
+ type: "command",
2376
+ command: c.command,
2377
+ timeout: typeof c.timeout === "number" ? c.timeout : void 0,
2378
+ async: typeof c.async === "boolean" ? c.async : void 0
2379
+ });
2380
+ }
2381
+ if (normalized.hooks.length > 0) {
2382
+ base.hooks[eventType].push(normalized);
2383
+ }
2384
+ }
2385
+ }
2386
+ return base;
2387
+ }
2388
+ async function loadHooksSettingsFromProject(projectDir) {
2389
+ const path$1 = path.join(projectDir, ".claude", "settings.json");
2390
+ try {
2391
+ const text = await promises.readFile(path$1, "utf-8");
2392
+ const json = JSON.parse(text);
2393
+ return parseHooksSettingsFile(json);
2394
+ } catch {
2395
+ return emptyHooksSettings();
2396
+ }
2397
+ }
2398
+ async function loadHooksSettingsFromUser() {
2399
+ const path$1 = path.join(os.homedir(), ".claude", "settings.json");
2400
+ try {
2401
+ const text = await promises.readFile(path$1, "utf-8");
2402
+ const json = JSON.parse(text);
2403
+ return parseHooksSettingsFile(json);
2404
+ } catch {
2405
+ return emptyHooksSettings();
2406
+ }
2407
+ }
2408
+ function emptyHooksRecord() {
2409
+ return {
2410
+ preToolUse: [],
2411
+ postToolUse: [],
2412
+ postToolUseFailure: []
2413
+ };
2414
+ }
2415
+ function matchTool(toolName, matcher) {
2416
+ if (!matcher || matcher === "*") return true;
2417
+ try {
2418
+ return new RegExp(matcher).test(toolName);
2419
+ } catch {
2420
+ return false;
2421
+ }
2422
+ }
2423
+ function toUpperSnake(paramName) {
2424
+ return paramName.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[\s-]+/g, "_").toUpperCase();
2425
+ }
2426
+ function buildHookEnv(context) {
2427
+ const env = { ...process.env };
2428
+ env.CLAUDE_TOOL_NAME = context.toolName;
2429
+ if (context.toolCallId) env.CLAUDE_TOOL_CALL_ID = context.toolCallId;
2430
+ if (context.projectDir) env.CLAUDE_PROJECT_DIR = context.projectDir;
2431
+ env.CLAUDE_HOOK_EVENT = context.eventType;
2432
+ for (const [k, v] of Object.entries(context.toolInput)) {
2433
+ const key = `CLAUDE_TOOL_INPUT_${toUpperSnake(k)}`;
2434
+ env[key] = typeof v === "string" ? v : JSON.stringify(v);
2435
+ }
2436
+ return env;
2437
+ }
2438
+ function buildStdinPayload(context) {
2439
+ const payload = {
2440
+ hook_event: context.eventType,
2441
+ tool_name: context.toolName,
2442
+ tool_input: context.toolInput
2443
+ };
2444
+ if (context.toolCallId) payload.tool_call_id = context.toolCallId;
2445
+ if (context.projectDir) payload.project_dir = context.projectDir;
2446
+ if (context.toolResultRaw) {
2447
+ payload.tool_result_raw = {
2448
+ content: context.toolResultRaw.content,
2449
+ isError: context.toolResultRaw.isError
2450
+ };
2451
+ }
2452
+ if (context.toolResultFinal) {
2453
+ payload.tool_result_final = {
2454
+ content: context.toolResultFinal.content,
2455
+ isError: context.toolResultFinal.isError
2456
+ };
2457
+ }
2458
+ if (context.errorMessage) payload.error_message = context.errorMessage;
2459
+ if (context.failureKind) payload.failure_kind = context.failureKind;
2460
+ return payload;
2461
+ }
2462
+ function flattenGroups(groups) {
2463
+ const out = [];
2464
+ for (const g of groups) {
2465
+ for (const hook of g.hooks) {
2466
+ out.push({ matcher: g.matcher, hook });
2467
+ }
2468
+ }
2469
+ return out;
2470
+ }
2471
+ function mergeCommandHookLayers(project, user) {
2472
+ const projectFlat = flattenGroups(project);
2473
+ const userFlat = flattenGroups(user);
2474
+ const projectNoId = [];
2475
+ const idMap = /* @__PURE__ */ new Map();
2476
+ for (const e of projectFlat) {
2477
+ if (e.hook.id) {
2478
+ idMap.set(e.hook.id, e);
2479
+ } else {
2480
+ projectNoId.push(e);
2481
+ }
2482
+ }
2483
+ const userNoId = [];
2484
+ for (const e of userFlat) {
2485
+ if (e.hook.id) {
2486
+ idMap.set(e.hook.id, e);
2487
+ } else {
2488
+ userNoId.push(e);
2489
+ }
2490
+ }
2491
+ return [...projectNoId, ...userNoId, ...idMap.values()];
2492
+ }
2493
+ function mergeDisableAll(project, user) {
2494
+ return project === true || user === true;
2495
+ }
2496
+ function runSpawnWithStdin(command, env, stdinBody, timeoutSec) {
2497
+ return new Promise((resolve, reject) => {
2498
+ const child = child_process.spawn(command, {
2499
+ shell: true,
2500
+ env,
2501
+ stdio: ["pipe", "pipe", "pipe"]
2502
+ });
2503
+ let stderr = "";
2504
+ child.stderr?.on("data", (d) => {
2505
+ stderr += d.toString("utf-8");
2506
+ });
2507
+ child.stdout?.on("data", () => {
2508
+ });
2509
+ const timeoutMs = Math.max(1, timeoutSec) * 1e3;
2510
+ const timer = setTimeout(() => {
2511
+ child.kill("SIGTERM");
2512
+ }, timeoutMs);
2513
+ child.on("error", (err) => {
2514
+ clearTimeout(timer);
2515
+ reject(err);
2516
+ });
2517
+ child.on("close", (code) => {
2518
+ clearTimeout(timer);
2519
+ resolve({ code, stderr });
2520
+ });
2521
+ try {
2522
+ child.stdin?.write(stdinBody, "utf-8");
2523
+ child.stdin?.end();
2524
+ } catch (e) {
2525
+ clearTimeout(timer);
2526
+ reject(e);
2527
+ }
2528
+ });
2529
+ }
2530
+ function parseReasonFromStderr(stderr) {
2531
+ const trimmed = stderr.trim();
2532
+ if (!trimmed) return void 0;
2533
+ try {
2534
+ const j = JSON.parse(trimmed);
2535
+ if (typeof j.reason === "string") return j.reason;
2536
+ } catch {
2537
+ }
2538
+ return trimmed;
2539
+ }
2540
+ var HookManager = class _HookManager {
2541
+ runtimeEnabled = true;
2542
+ mergedDisableAll = false;
2543
+ mergedHooks = {
2544
+ preToolUse: [],
2545
+ postToolUse: [],
2546
+ postToolUseFailure: []
2547
+ };
2548
+ functionHooks = /* @__PURE__ */ new Map();
2549
+ projectHooks = this.emptySettings();
2550
+ userHooks = this.emptySettings();
2551
+ emptySettings() {
2552
+ return {
2553
+ disableAllHooks: false,
2554
+ hooks: emptyHooksRecord()
2555
+ };
2556
+ }
2557
+ rebuildMerged() {
2558
+ const events = ["preToolUse", "postToolUse", "postToolUseFailure"];
2559
+ for (const ev of events) {
2560
+ this.mergedHooks[ev] = mergeCommandHookLayers(
2561
+ this.projectHooks.hooks[ev],
2562
+ this.userHooks.hooks[ev]
2563
+ );
2564
+ }
2565
+ this.mergedDisableAll = mergeDisableAll(
2566
+ this.projectHooks.disableAllHooks,
2567
+ this.userHooks.disableAllHooks
2568
+ );
2569
+ }
2570
+ shouldRunHooks() {
2571
+ if (!this.runtimeEnabled) return false;
2572
+ if (this.mergedDisableAll) return false;
2573
+ return true;
2574
+ }
2575
+ getHooksForEvent(event) {
2576
+ return this.mergedHooks[event] ?? [];
2577
+ }
2578
+ getFunctionHooksForEvent(event) {
2579
+ return [...this.functionHooks.values()].filter((h) => h.event === event);
2580
+ }
2581
+ async runCommandHook(cmd, context, phase) {
2582
+ const env = buildHookEnv(context);
2583
+ const stdinBody = JSON.stringify(buildStdinPayload(context));
2584
+ const timeoutSec = cmd.timeout ?? 30;
2585
+ try {
2586
+ const { code, stderr } = await runSpawnWithStdin(cmd.command, env, stdinBody, timeoutSec);
2587
+ if (phase === "pre") {
2588
+ if (code === 0) return { allowed: true };
2589
+ if (code === 2) {
2590
+ return {
2591
+ allowed: false,
2592
+ reason: parseReasonFromStderr(stderr) ?? "Hook blocked tool execution"
2593
+ };
2594
+ }
2595
+ console.error(
2596
+ `[HookManager] PreToolUse command exited with code ${code}: ${stderr || cmd.command}`
2597
+ );
2598
+ return {
2599
+ allowed: false,
2600
+ reason: parseReasonFromStderr(stderr) ?? `Hook process exited with code ${code}`
2601
+ };
2602
+ }
2603
+ if (code !== 0 && code !== null) {
2604
+ console.error(`[HookManager] Post* hook non-zero exit (${code}): ${stderr || cmd.command}`);
2605
+ }
2606
+ } catch (err) {
2607
+ console.error(`[HookManager] Command hook failed: ${cmd.command}`, err);
2608
+ if (phase === "pre") {
2609
+ return {
2610
+ allowed: false,
2611
+ reason: err instanceof Error ? err.message : String(err)
2612
+ };
2613
+ }
2614
+ }
2615
+ }
2616
+ fireAsyncCommandHook(cmd, context) {
2617
+ const env = buildHookEnv(context);
2618
+ const stdinBody = JSON.stringify(buildStdinPayload(context));
2619
+ const timeoutSec = cmd.timeout ?? 30;
2620
+ void runSpawnWithStdin(cmd.command, env, stdinBody, timeoutSec).then(({ code, stderr }) => {
2621
+ if (code !== 0 && code !== null) {
2622
+ console.error(`[HookManager] async hook exit (${code}): ${stderr || cmd.command}`);
2623
+ }
2624
+ }).catch((err) => {
2625
+ console.error(`[HookManager] async hook error: ${cmd.command}`, err);
2626
+ });
2627
+ }
2628
+ async runFunctionHookSafe(hook, context, phase) {
2629
+ try {
2630
+ return await hook.handler(context);
2631
+ } catch (err) {
2632
+ console.error(`[HookManager] Function hook "${hook.id}" threw`, err);
2633
+ if (phase === "pre") {
2634
+ return {
2635
+ allowed: false,
2636
+ reason: err instanceof Error ? err.message : String(err)
2637
+ };
2638
+ }
2639
+ }
2640
+ }
2641
+ register(hook) {
2642
+ this.functionHooks.set(hook.id, hook);
2643
+ }
2644
+ unregister(id) {
2645
+ return this.functionHooks.delete(id);
2646
+ }
2647
+ async loadProjectConfig(projectDir) {
2648
+ this.projectHooks = await loadHooksSettingsFromProject(projectDir);
2649
+ this.rebuildMerged();
2650
+ }
2651
+ async loadUserConfig() {
2652
+ this.userHooks = await loadHooksSettingsFromUser();
2653
+ this.rebuildMerged();
2654
+ }
2655
+ async discoverAndLoad(projectDir) {
2656
+ const dir = projectDir ?? process.cwd();
2657
+ await this.loadProjectConfig(dir);
2658
+ await this.loadUserConfig();
2659
+ }
2660
+ setEnabled(enabled) {
2661
+ this.runtimeEnabled = enabled;
2662
+ }
2663
+ isEnabled() {
2664
+ return this.runtimeEnabled;
2665
+ }
2666
+ async executePreToolUse(context) {
2667
+ if (!this.shouldRunHooks()) {
2668
+ return { allowed: true, updatedInput: { ...context.toolInput } };
2669
+ }
2670
+ let workingInput = { ...context.toolInput };
2671
+ for (const entry of this.getHooksForEvent("preToolUse")) {
2672
+ if (!matchTool(context.toolName, entry.matcher)) continue;
2673
+ const ctx = { ...context, toolInput: workingInput };
2674
+ const result = await this.runCommandHook(entry.hook, ctx, "pre");
2675
+ if (result?.allowed === false) {
2676
+ return { allowed: false, reason: result.reason };
2677
+ }
2678
+ }
2679
+ for (const fh of this.getFunctionHooksForEvent("preToolUse")) {
2680
+ if (!matchTool(context.toolName, fh.matcher)) continue;
2681
+ const ctx = { ...context, toolInput: workingInput };
2682
+ const result = await this.runFunctionHookSafe(fh, ctx, "pre");
2683
+ if (result?.allowed === false) {
2684
+ return { allowed: false, reason: result.reason };
2685
+ }
2686
+ if (result?.updatedInput) {
2687
+ workingInput = { ...workingInput, ...result.updatedInput };
2688
+ }
2689
+ }
2690
+ return { allowed: true, updatedInput: workingInput };
2691
+ }
2692
+ async executePostToolUse(context) {
2693
+ if (!this.shouldRunHooks()) return;
2694
+ for (const entry of this.getHooksForEvent("postToolUse")) {
2695
+ if (!matchTool(context.toolName, entry.matcher)) continue;
2696
+ if (entry.hook.async) {
2697
+ this.fireAsyncCommandHook(entry.hook, context);
2698
+ continue;
2699
+ }
2700
+ await this.runCommandHook(entry.hook, context, "post");
2701
+ }
2702
+ for (const fh of this.getFunctionHooksForEvent("postToolUse")) {
2703
+ if (!matchTool(context.toolName, fh.matcher)) continue;
2704
+ await this.runFunctionHookSafe(fh, context, "post");
2705
+ }
2706
+ }
2707
+ async executePostToolUseFailure(context) {
2708
+ if (!this.shouldRunHooks()) return;
2709
+ for (const entry of this.getHooksForEvent("postToolUseFailure")) {
2710
+ if (!matchTool(context.toolName, entry.matcher)) continue;
2711
+ if (entry.hook.async) {
2712
+ this.fireAsyncCommandHook(entry.hook, context);
2713
+ continue;
2714
+ }
2715
+ await this.runCommandHook(entry.hook, context, "post");
2716
+ }
2717
+ for (const fh of this.getFunctionHooksForEvent("postToolUseFailure")) {
2718
+ if (!matchTool(context.toolName, fh.matcher)) continue;
2719
+ await this.runFunctionHookSafe(fh, context, "post");
2720
+ }
2721
+ }
2722
+ static create() {
2723
+ return new _HookManager();
2724
+ }
2725
+ };
2726
+ function createFunctionHook(config) {
2727
+ return {
2728
+ id: config.id,
2729
+ event: config.event,
2730
+ matcher: config.matcher,
2731
+ description: config.description,
2732
+ handler: config.handler
2733
+ };
2734
+ }
2735
+
2736
+ exports.DEFAULT_GREP_HEAD_LIMIT = DEFAULT_GREP_HEAD_LIMIT;
2737
+ exports.FileStorageStrategy = FileStorageStrategy;
2738
+ exports.HookManager = HookManager;
2739
+ exports.MAX_LINE_LENGTH = MAX_LINE_LENGTH2;
2740
+ exports.OUTPUT_CONFIG = OUTPUT_CONFIG;
2741
+ exports.OutputHandler = OutputHandler;
2742
+ exports.PaginationHintStrategy = PaginationHintStrategy;
2743
+ exports.SmartTruncateStrategy = SmartTruncateStrategy;
2744
+ exports.ToolRegistry = ToolRegistry;
2745
+ exports.agentTool = agentTool;
2746
+ exports.bashTool = bashTool;
2747
+ exports.buildHookEnv = buildHookEnv;
2748
+ exports.createAgentTool = createAgentTool;
2749
+ exports.createAskUserQuestionTool = createAskUserQuestionTool;
2750
+ exports.createFunctionHook = createFunctionHook;
2751
+ exports.createOutputHandler = createOutputHandler;
2752
+ exports.createSkillTool = createSkillTool;
2753
+ exports.createTool = createTool;
2754
+ exports.editTool = editTool;
2755
+ exports.formatAnswerSummary = formatAnswerSummary;
2756
+ exports.formatAskUserQuestionPrompt = formatAskUserQuestionPrompt;
2757
+ exports.formatEnvironmentSection = formatEnvironmentSection;
2758
+ exports.getAllBuiltinTools = getAllBuiltinTools;
2759
+ exports.getEnvironmentInfo = getEnvironmentInfo;
2760
+ exports.getFileSystemTools = getFileSystemTools;
2761
+ exports.getGlobalRegistry = getGlobalRegistry;
2762
+ exports.getGrepTools = getGrepTools;
2763
+ exports.getInteractionTools = getInteractionTools;
2764
+ exports.getSafeBuiltinTools = getSafeBuiltinTools;
2765
+ exports.getShellTools = getShellTools;
2766
+ exports.getSkillTools = getSkillTools;
2767
+ exports.getSubagentTools = getSubagentTools;
2768
+ exports.getTaskTools = getTaskTools;
2769
+ exports.getWebTools = getWebTools;
2770
+ exports.globTool = globTool;
2771
+ exports.grepTool = grepTool;
2772
+ exports.loadHooksSettingsFromProject = loadHooksSettingsFromProject;
2773
+ exports.loadHooksSettingsFromUser = loadHooksSettingsFromUser;
2774
+ exports.matchTool = matchTool;
2775
+ exports.mergeCommandHookLayers = mergeCommandHookLayers;
2776
+ exports.parseHooksSettingsFile = parseHooksSettingsFile;
2777
+ exports.questionTool = questionTool;
2778
+ exports.readFileTool = readFileTool;
2779
+ exports.subagentRequestSchema = subagentRequestSchema;
2780
+ exports.taskCreateTool = taskCreateTool;
2781
+ exports.taskListTool = taskListTool;
2782
+ exports.taskUpdateTool = taskUpdateTool;
2783
+ exports.truncateMatchLineForDisplay = truncateMatchLineForDisplay;
2784
+ exports.webFetchTool = webFetchTool;
2785
+ exports.webSearchTool = webSearchTool;
2786
+ exports.writeFileTool = writeFileTool;
2787
+ //# sourceMappingURL=chunk-JF5AJQMU.cjs.map
2788
+ //# sourceMappingURL=chunk-JF5AJQMU.cjs.map