@ai-setting/roy-agent-core 1.5.30 → 1.5.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/env/index.js CHANGED
@@ -48,7 +48,7 @@ import"../shared/@ai-setting/roy-agent-core-92z6t4he.js";
48
48
  import"../shared/@ai-setting/roy-agent-core-8gxth0eh.js";
49
49
  import {
50
50
  McpComponent
51
- } from "../shared/@ai-setting/roy-agent-core-pjr12nnd.js";
51
+ } from "../shared/@ai-setting/roy-agent-core-7nwwzxf6.js";
52
52
  import"../shared/@ai-setting/roy-agent-core-ctdhjv68.js";
53
53
  import"../shared/@ai-setting/roy-agent-core-1akcqxj9.js";
54
54
  import"../shared/@ai-setting/roy-agent-core-eajcvp4e.js";
@@ -8,7 +8,7 @@ import {
8
8
  McpServerLocalConfigSchema,
9
9
  McpServerRemoteConfigSchema,
10
10
  getMcpPathPriority
11
- } from "../../shared/@ai-setting/roy-agent-core-pjr12nnd.js";
11
+ } from "../../shared/@ai-setting/roy-agent-core-7nwwzxf6.js";
12
12
  import"../../shared/@ai-setting/roy-agent-core-ctdhjv68.js";
13
13
  import {
14
14
  adaptMcpTool,
@@ -8,7 +8,7 @@ import {
8
8
  grepTool,
9
9
  readFileTool,
10
10
  writeFileTool
11
- } from "../../../shared/@ai-setting/roy-agent-core-satmq6sh.js";
11
+ } from "../../../shared/@ai-setting/roy-agent-core-p46v1kr2.js";
12
12
  import"../../../shared/@ai-setting/roy-agent-core-xs5rsgat.js";
13
13
  import"../../../shared/@ai-setting/roy-agent-core-psv4v63c.js";
14
14
  import"../../../shared/@ai-setting/roy-agent-core-fs0mn2jk.js";
@@ -2,7 +2,7 @@ import {
2
2
  ToolComponent,
3
3
  ToolRegistry,
4
4
  ToolValidator
5
- } from "../../shared/@ai-setting/roy-agent-core-qqceba6k.js";
5
+ } from "../../shared/@ai-setting/roy-agent-core-q27e6dhw.js";
6
6
  import {
7
7
  bashTool,
8
8
  echoTool,
@@ -13,7 +13,7 @@ import {
13
13
  grepTool,
14
14
  readFileTool,
15
15
  writeFileTool
16
- } from "../../shared/@ai-setting/roy-agent-core-satmq6sh.js";
16
+ } from "../../shared/@ai-setting/roy-agent-core-p46v1kr2.js";
17
17
  import"../../shared/@ai-setting/roy-agent-core-e25xkv53.js";
18
18
  import"../../shared/@ai-setting/roy-agent-core-qxhq8ven.js";
19
19
  import"../../shared/@ai-setting/roy-agent-core-kkbwepqb.js";
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  ToolComponent,
10
10
  ToolRegistry,
11
11
  ToolValidator
12
- } from "./shared/@ai-setting/roy-agent-core-qqceba6k.js";
12
+ } from "./shared/@ai-setting/roy-agent-core-q27e6dhw.js";
13
13
  import {
14
14
  bashTool,
15
15
  editFileTool,
@@ -19,7 +19,7 @@ import {
19
19
  grepTool,
20
20
  readFileTool,
21
21
  writeFileTool
22
- } from "./shared/@ai-setting/roy-agent-core-satmq6sh.js";
22
+ } from "./shared/@ai-setting/roy-agent-core-p46v1kr2.js";
23
23
  import {
24
24
  PromptComponent,
25
25
  getBuiltInPrompt,
@@ -134,7 +134,7 @@ import {
134
134
  import"./shared/@ai-setting/roy-agent-core-psvxt4c9.js";
135
135
  import {
136
136
  McpComponent
137
- } from "./shared/@ai-setting/roy-agent-core-pjr12nnd.js";
137
+ } from "./shared/@ai-setting/roy-agent-core-7nwwzxf6.js";
138
138
  import {
139
139
  AgentError,
140
140
  ComponentError,
@@ -222,10 +222,14 @@ class McpScanner {
222
222
 
223
223
  // src/env/mcp/loader.ts
224
224
  init_logger();
225
+ import fs2 from "fs/promises";
226
+ import path3 from "path";
227
+ import os2 from "os";
225
228
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
226
229
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
227
230
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
228
231
  var logger2 = createLogger("mcp-loader");
232
+ var AUTH_JSON_PATH = path3.join(os2.homedir(), ".roy-agent", "auth.json");
229
233
 
230
234
  class McpLoader {
231
235
  clients = new Map;
@@ -286,10 +290,12 @@ class McpLoader {
286
290
  let transport;
287
291
  if (config.type === "local") {
288
292
  const localConfig = config;
293
+ const env = { ...localConfig.environment };
294
+ await this.injectMinimaxEnv(env);
289
295
  transport = new StdioClientTransport({
290
296
  command: localConfig.command[0],
291
297
  args: localConfig.command.slice(1),
292
- env: localConfig.environment
298
+ env
293
299
  });
294
300
  client = new Client({ name, version: "1.0.0" });
295
301
  await client.connect(transport);
@@ -332,18 +338,38 @@ class McpLoader {
332
338
  isConnected(name) {
333
339
  return this.clients.has(name);
334
340
  }
341
+ async loadMinimaxApiKey() {
342
+ try {
343
+ const content = await fs2.readFile(AUTH_JSON_PATH, "utf-8");
344
+ const auth = JSON.parse(content);
345
+ return auth.providers?.minimax?.apiKey;
346
+ } catch (error) {
347
+ logger2.debug("[McpLoader] Failed to load minimax API key from auth.json", { error });
348
+ return;
349
+ }
350
+ }
351
+ async injectMinimaxEnv(env) {
352
+ if (env["MINIMAX_API_KEY"]) {
353
+ return;
354
+ }
355
+ const apiKey = await this.loadMinimaxApiKey();
356
+ if (apiKey) {
357
+ env["MINIMAX_API_KEY"] = apiKey;
358
+ env["MINIMAX_API_HOST"] = "https://api.minimaxi.com";
359
+ }
360
+ }
335
361
  }
336
362
 
337
363
  // src/env/mcp/mcp-config-registration.ts
338
- import path3 from "path";
339
- import os2 from "os";
364
+ import path4 from "path";
365
+ import os3 from "os";
340
366
  var MCP_DEFAULTS = {
341
367
  "mcp.enabled": true,
342
368
  "mcp.timeout": 30000,
343
369
  "mcp.mcpPaths": [
344
370
  {
345
371
  type: "user",
346
- path: path3.join(os2.homedir(), ".config", "roy-agent", "mcp")
372
+ path: path4.join(os3.homedir(), ".config", "roy-agent", "mcp")
347
373
  },
348
374
  {
349
375
  type: "project",
@@ -11,9 +11,100 @@ import {
11
11
  // src/env/tool/built-in/bash.ts
12
12
  init_propagation();
13
13
  import { z } from "zod";
14
- import { exec } from "child_process";
15
- import { promisify } from "util";
16
- var execAsync = promisify(exec);
14
+ import { spawn } from "child_process";
15
+ import path from "path";
16
+ function getPlatform() {
17
+ return process.platform;
18
+ }
19
+ function isWindows() {
20
+ return getPlatform() === "win32";
21
+ }
22
+ function getShellCommand(command) {
23
+ if (isWindows()) {
24
+ return {
25
+ shell: "cmd.exe",
26
+ args: ["/c", command]
27
+ };
28
+ }
29
+ return {
30
+ shell: "/bin/sh",
31
+ args: ["-c", command]
32
+ };
33
+ }
34
+ function normalizeCwd(cwd) {
35
+ if (!cwd)
36
+ return;
37
+ return path.normalize(cwd);
38
+ }
39
+ function executeCommand(command, options) {
40
+ return new Promise((resolve) => {
41
+ const { shell, args } = getShellCommand(command);
42
+ let stdout = "";
43
+ let stderr = "";
44
+ let timedOut = false;
45
+ let resolved = false;
46
+ let timer = null;
47
+ let forceKillTimer = null;
48
+ const child = spawn(shell, args, {
49
+ cwd: normalizeCwd(options.cwd),
50
+ env: options.env,
51
+ stdio: ["ignore", "pipe", "pipe"],
52
+ windowsHide: true
53
+ });
54
+ const cleanup = () => {
55
+ if (timer)
56
+ clearTimeout(timer);
57
+ if (forceKillTimer)
58
+ clearTimeout(forceKillTimer);
59
+ };
60
+ const doResolve = (result) => {
61
+ if (!resolved) {
62
+ resolved = true;
63
+ cleanup();
64
+ resolve(result);
65
+ }
66
+ };
67
+ if (options.timeout) {
68
+ timer = setTimeout(() => {
69
+ timedOut = true;
70
+ if (isWindows()) {
71
+ child.kill("SIGTERM");
72
+ forceKillTimer = setTimeout(() => {
73
+ if (!child.killed) {
74
+ try {
75
+ process.kill(child.pid, "SIGKILL");
76
+ } catch {}
77
+ }
78
+ }, 500);
79
+ } else {
80
+ child.kill("SIGKILL");
81
+ }
82
+ }, options.timeout);
83
+ }
84
+ child.stdout?.on("data", (data) => {
85
+ stdout += data.toString();
86
+ });
87
+ child.stderr?.on("data", (data) => {
88
+ stderr += data.toString();
89
+ });
90
+ child.on("close", (code) => {
91
+ doResolve({
92
+ stdout: stdout.trim(),
93
+ stderr: stderr.trim(),
94
+ exitCode: timedOut ? -1 : code ?? 0,
95
+ timedOut
96
+ });
97
+ });
98
+ child.on("error", (err) => {
99
+ doResolve({
100
+ stdout: stdout.trim(),
101
+ stderr: err.message,
102
+ exitCode: 1,
103
+ timedOut: false
104
+ });
105
+ });
106
+ });
107
+ }
17
108
  function getCurrentSpanContext() {
18
109
  try {
19
110
  const { getTracerProvider } = (init_tracer_provider(), __toCommonJS(exports_tracer_provider));
@@ -61,19 +152,14 @@ var bashTool = {
61
152
  metadata: {
62
153
  category: "system",
63
154
  tags: ["shell", "command", "terminal", "exec"],
64
- version: "1.2.0"
155
+ version: "1.3.0"
65
156
  },
66
157
  execute: async (args, ctx) => {
67
158
  const startTime = Date.now();
68
159
  const { command, workdir, timeout = 60000, env } = args;
69
160
  const currentSpanCtx = getCurrentSpanContext();
70
161
  const traceId = currentSpanCtx?.traceId || getCurrentTraceId();
71
- const options = {
72
- timeout
73
- };
74
- if (workdir || ctx.workdir) {
75
- options.cwd = workdir || ctx.workdir;
76
- }
162
+ const cwd = workdir || ctx.workdir;
77
163
  const childEnv = {};
78
164
  for (const [key, value] of Object.entries(process.env)) {
79
165
  if (value !== undefined) {
@@ -96,7 +182,6 @@ var bashTool = {
96
182
  if (env) {
97
183
  Object.assign(childEnv, env);
98
184
  }
99
- options.env = childEnv;
100
185
  let otelSpan = undefined;
101
186
  try {
102
187
  const { getTracerProvider } = (init_tracer_provider(), __toCommonJS(exports_tracer_provider));
@@ -105,51 +190,67 @@ var bashTool = {
105
190
  otelSpan = tracer.startSpan(`bash: ${command.slice(0, 50)}${command.length > 50 ? "..." : ""}`, {
106
191
  attributes: {
107
192
  "bash.command": command.slice(0, 200),
108
- "bash.workdir": workdir || ctx.workdir || "",
109
- "bash.timeout": timeout
193
+ "bash.workdir": cwd || "",
194
+ "bash.timeout": timeout,
195
+ "bash.platform": process.platform
110
196
  }
111
197
  });
112
198
  } catch {}
113
199
  const spanId = otelSpan?.spanContext?.spanId;
114
200
  try {
115
- const { stdout, stderr } = await execAsync(command, options);
201
+ const result = await executeCommand(command, {
202
+ cwd,
203
+ timeout,
204
+ env: childEnv
205
+ });
116
206
  if (otelSpan) {
117
- otelSpan.end({ stdout, stderr });
207
+ if (result.timedOut) {
208
+ const execError = new Error(`Command timed out after ${timeout}ms`);
209
+ execError.name = "BashExecutionError";
210
+ otelSpan.end(undefined, execError);
211
+ } else if (result.exitCode !== 0) {
212
+ const execError = new Error(result.stderr || "Command failed");
213
+ execError.name = "BashExecutionError";
214
+ otelSpan.end(undefined, execError);
215
+ } else {
216
+ otelSpan.end({ stdout: result.stdout, stderr: result.stderr });
217
+ }
118
218
  }
119
219
  return {
120
- success: true,
121
- output: stdout || "(no output)",
220
+ success: !result.timedOut && result.exitCode === 0,
221
+ output: result.stdout || "(no output)",
222
+ error: result.timedOut ? `Command timed out after ${timeout}ms` : result.exitCode !== 0 ? result.stderr : undefined,
122
223
  metadata: {
123
224
  execution_time_ms: Date.now() - startTime,
124
- stdout,
125
- stderr,
126
- exit_code: 0,
225
+ stdout: result.stdout,
226
+ stderr: result.stderr,
227
+ exit_code: result.exitCode,
228
+ timed_out: result.timedOut,
127
229
  trace_id: traceId,
128
230
  parent_span_id: currentSpanCtx?.spanId,
129
- span_id: spanId
231
+ span_id: spanId,
232
+ platform: process.platform
130
233
  }
131
234
  };
132
235
  } catch (error) {
133
- const exitCode = error.code || 1;
134
- const stdout = error.stdout || "";
135
- const stderr = error.stderr || error.message;
136
- const execError = new Error(stderr);
236
+ const errorMessage = error instanceof Error ? error.message : String(error);
237
+ const execError = new Error(errorMessage);
137
238
  execError.name = "BashExecutionError";
138
239
  if (otelSpan) {
139
240
  otelSpan.end(undefined, execError);
140
241
  }
141
242
  return {
142
- success: exitCode === 0,
143
- output: stdout || "(no output)",
144
- error: stderr,
243
+ success: false,
244
+ output: "",
245
+ error: errorMessage,
145
246
  metadata: {
146
247
  execution_time_ms: Date.now() - startTime,
147
- stdout,
148
- stderr,
149
- exit_code: exitCode,
248
+ exit_code: 1,
249
+ timed_out: false,
150
250
  trace_id: traceId,
151
251
  parent_span_id: currentSpanCtx?.spanId,
152
- span_id: spanId
252
+ span_id: spanId,
253
+ platform: process.platform
153
254
  }
154
255
  };
155
256
  }
@@ -182,13 +283,15 @@ var echoTool = {
182
283
  // src/env/tool/built-in/glob.ts
183
284
  import { z as z2 } from "zod";
184
285
  import { glob as globAsync } from "glob";
286
+ import { stat } from "fs/promises";
287
+ import path2 from "path";
185
288
  var globTool = {
186
289
  name: "glob",
187
290
  description: "Search for files matching a glob pattern. Use this to find files by name patterns like **/*.ts or **/*.md.",
188
291
  parameters: z2.object({
189
- pattern: z2.string().describe("Glob pattern to match (e.g., **/*.ts, **/*.md)"),
292
+ pattern: z2.string().min(1).describe("Glob pattern to match (e.g., **/*.ts, **/*.md)"),
190
293
  cwd: z2.string().optional().describe("Working directory to search in"),
191
- maxResults: z2.number().int().positive().optional().describe("Maximum number of results to return"),
294
+ maxResults: z2.number().int().positive().default(100).optional().describe("Maximum number of results to return"),
192
295
  ignore: z2.array(z2.string()).optional().describe("Patterns to ignore")
193
296
  }),
194
297
  sandbox: {
@@ -197,20 +300,66 @@ var globTool = {
197
300
  metadata: {
198
301
  category: "file",
199
302
  tags: ["search", "file", "glob", "pattern"],
200
- version: "1.0.0"
303
+ version: "1.1.0"
201
304
  },
202
305
  execute: async (args, ctx) => {
203
306
  const startTime = Date.now();
204
307
  const { pattern, cwd, maxResults = 100, ignore } = args;
205
308
  const searchDir = cwd || ctx.workdir || process.cwd();
206
309
  try {
207
- const files = await globAsync(pattern, {
310
+ const dirStats = await stat(searchDir);
311
+ if (!dirStats.isDirectory()) {
312
+ return {
313
+ success: false,
314
+ output: "",
315
+ error: `Path is not a directory: ${searchDir}`,
316
+ metadata: { execution_time_ms: Date.now() - startTime }
317
+ };
318
+ }
319
+ } catch (error) {
320
+ return {
321
+ success: false,
322
+ output: "",
323
+ error: error instanceof Error ? error.message : String(error),
324
+ metadata: { execution_time_ms: Date.now() - startTime }
325
+ };
326
+ }
327
+ try {
328
+ const absolutePattern = path2.isAbsolute(pattern) ? pattern : pattern;
329
+ const files = await globAsync(absolutePattern, {
208
330
  cwd: searchDir,
209
331
  ignore: ignore || ["**/node_modules/**", "**/.git/**"],
210
- maxDepth: 20
332
+ maxDepth: 20,
333
+ absolute: false,
334
+ nodir: true
211
335
  });
212
- const limitedFiles = files.slice(0, maxResults);
213
- const output = limitedFiles.join(`
336
+ const filesWithMtime = [];
337
+ for (const file of files) {
338
+ try {
339
+ const fullPath = path2.join(searchDir, file);
340
+ const stats = await stat(fullPath);
341
+ if (stats.isFile()) {
342
+ filesWithMtime.push({
343
+ path: file,
344
+ mtime: stats.mtimeMs
345
+ });
346
+ }
347
+ } catch {}
348
+ }
349
+ filesWithMtime.sort((a, b) => b.mtime - a.mtime);
350
+ const truncated = filesWithMtime.length > maxResults;
351
+ const limitedFiles = filesWithMtime.slice(0, maxResults);
352
+ const outputLines = [];
353
+ if (limitedFiles.length === 0) {
354
+ outputLines.push("No files found");
355
+ } else {
356
+ outputLines.push(...limitedFiles.map((f) => f.path));
357
+ if (truncated) {
358
+ outputLines.push("");
359
+ outputLines.push(`(Results are truncated: showing first ${maxResults} results. Consider using a more specific path or pattern.)`);
360
+ }
361
+ }
362
+ const output = outputLines.join(`
214
363
  `);
215
364
  return {
216
365
  success: true,
@@ -218,8 +367,9 @@ var globTool = {
218
367
  metadata: {
219
368
  execution_time_ms: Date.now() - startTime,
220
369
  output_size: output.length,
221
- total_matches: files.length,
222
- returned_matches: limitedFiles.length
370
+ total_matches: filesWithMtime.length,
371
+ returned_matches: limitedFiles.length,
372
+ truncated
223
373
  }
224
374
  };
225
375
  } catch (error) {
@@ -237,7 +387,8 @@ var globTool = {
237
387
 
238
388
  // src/env/tool/built-in/read-file.ts
239
389
  import { z as z3 } from "zod";
240
- import { readFile } from "fs/promises";
390
+ import { readFile, stat as stat2 } from "fs/promises";
391
+ import path3 from "path";
241
392
  var readFileTool = {
242
393
  name: "read_file",
243
394
  description: "Read the contents of a file. Use this to read source code, configuration files, or any text files.",
@@ -261,21 +412,67 @@ var readFileTool = {
261
412
  metadata: {
262
413
  category: "file",
263
414
  tags: ["read", "file", "io"],
264
- version: "1.0.0"
415
+ version: "1.1.0"
265
416
  },
266
417
  execute: async (args, ctx) => {
267
418
  const startTime = Date.now();
268
419
  const { path: filePath, offset, limit, encoding = "utf-8" } = args;
420
+ if (!filePath || typeof filePath !== "string") {
421
+ return {
422
+ success: false,
423
+ output: "",
424
+ error: "Invalid file path provided",
425
+ metadata: { execution_time_ms: Date.now() - startTime }
426
+ };
427
+ }
428
+ const normalizedPath = path3.normalize(filePath);
429
+ try {
430
+ const stats = await stat2(normalizedPath);
431
+ if (stats.isDirectory()) {
432
+ return {
433
+ success: false,
434
+ output: "",
435
+ error: `Path is a directory, not a file: ${normalizedPath}`,
436
+ metadata: { execution_time_ms: Date.now() - startTime }
437
+ };
438
+ }
439
+ } catch (error) {
440
+ if (error.code === "ENOENT") {
441
+ return {
442
+ success: false,
443
+ output: "",
444
+ error: `File not found: ${normalizedPath}`,
445
+ metadata: { execution_time_ms: Date.now() - startTime }
446
+ };
447
+ }
448
+ return {
449
+ success: false,
450
+ output: "",
451
+ error: error instanceof Error ? error.message : String(error),
452
+ metadata: { execution_time_ms: Date.now() - startTime }
453
+ };
454
+ }
269
455
  try {
270
- const content = await readFile(filePath, encoding);
456
+ const contentBuffer = await readFile(normalizedPath);
457
+ let contentStr;
458
+ if (encoding === "base64") {
459
+ contentStr = contentBuffer.toString("base64");
460
+ } else {
461
+ contentStr = contentBuffer.toString("utf-8");
462
+ }
271
463
  let output;
272
- const contentBuffer = content;
273
- const contentStr = typeof contentBuffer === "string" ? contentBuffer : contentBuffer.toString(encoding);
274
464
  if (offset !== undefined || limit !== undefined) {
275
- const lines = contentStr.split(`
276
- `);
465
+ const lines = contentStr.split(/\r?\n/);
277
466
  const startLine = offset || 0;
278
- const endLine = limit ? startLine + limit : lines.length;
467
+ const endLine = limit !== undefined ? Math.min(startLine + limit, lines.length) : lines.length;
468
+ if (startLine >= lines.length) {
469
+ return {
470
+ success: false,
471
+ output: "",
472
+ error: `Offset ${offset} is beyond file length (${lines.length} lines)`,
473
+ metadata: { execution_time_ms: Date.now() - startTime }
474
+ };
475
+ }
279
476
  output = lines.slice(startLine, endLine).join(`
280
477
  `);
281
478
  } else {
@@ -287,7 +484,7 @@ var readFileTool = {
287
484
  metadata: {
288
485
  execution_time_ms: Date.now() - startTime,
289
486
  output_size: output.length,
290
- file_path: filePath
487
+ file_path: normalizedPath
291
488
  }
292
489
  };
293
490
  } catch (error) {
@@ -305,8 +502,8 @@ var readFileTool = {
305
502
 
306
503
  // src/env/tool/built-in/write-file.ts
307
504
  import { z as z4 } from "zod";
308
- import { writeFile, mkdir } from "fs/promises";
309
- import { dirname } from "path";
505
+ import { writeFile, mkdir, stat as stat3 } from "fs/promises";
506
+ import path4 from "path";
310
507
  var writeFileTool = {
311
508
  name: "write_file",
312
509
  description: "Write content to a file. Use this to create new files or overwrite existing ones.",
@@ -331,24 +528,53 @@ var writeFileTool = {
331
528
  metadata: {
332
529
  category: "file",
333
530
  tags: ["write", "file", "io", "create"],
334
- version: "1.0.0"
531
+ version: "1.1.0"
335
532
  },
336
533
  execute: async (args, ctx) => {
337
534
  const startTime = Date.now();
338
535
  const { path: filePath, content, createDirs = false, append = false } = args;
536
+ if (!filePath || typeof filePath !== "string") {
537
+ return {
538
+ success: false,
539
+ output: "",
540
+ error: "Invalid file path provided",
541
+ metadata: { execution_time_ms: Date.now() - startTime }
542
+ };
543
+ }
544
+ const normalizedPath = path4.normalize(filePath);
545
+ try {
546
+ const stats = await stat3(normalizedPath);
547
+ if (stats.isDirectory()) {
548
+ return {
549
+ success: false,
550
+ output: "",
551
+ error: `Path is a directory, not a file: ${normalizedPath}`,
552
+ metadata: { execution_time_ms: Date.now() - startTime }
553
+ };
554
+ }
555
+ } catch (error) {
556
+ if (error.code !== "ENOENT") {
557
+ return {
558
+ success: false,
559
+ output: "",
560
+ error: error instanceof Error ? error.message : String(error),
561
+ metadata: { execution_time_ms: Date.now() - startTime }
562
+ };
563
+ }
564
+ }
339
565
  try {
340
566
  if (createDirs) {
341
- await mkdir(dirname(filePath), { recursive: true });
567
+ await mkdir(path4.dirname(normalizedPath), { recursive: true });
342
568
  }
343
569
  const flags = append ? "a" : "w";
344
- await writeFile(filePath, content, { flag: flags });
570
+ await writeFile(normalizedPath, content, { flag: flags });
345
571
  return {
346
572
  success: true,
347
- output: append ? `Content appended to: ${filePath}` : `File written: ${filePath}`,
573
+ output: append ? `Content appended to: ${normalizedPath}` : `File written: ${normalizedPath}`,
348
574
  metadata: {
349
575
  execution_time_ms: Date.now() - startTime,
350
576
  output_size: content.length,
351
- file_path: filePath
577
+ file_path: normalizedPath
352
578
  }
353
579
  };
354
580
  } catch (error) {
@@ -366,7 +592,9 @@ var writeFileTool = {
366
592
 
367
593
  // src/env/tool/built-in/edit-file.ts
368
594
  import { z as z5 } from "zod";
369
- import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
595
+ import { readFile as readFile2, writeFile as writeFile2, stat as stat4, unlink } from "fs/promises";
596
+ import path5 from "path";
597
+ import { randomUUID } from "crypto";
370
598
  var editFileTool = {
371
599
  name: "edit_file",
372
600
  description: "Edit a file by replacing specific text. Use this to make targeted changes to files by specifying what to find and what to replace.",
@@ -391,32 +619,75 @@ var editFileTool = {
391
619
  metadata: {
392
620
  category: "file",
393
621
  tags: ["edit", "file", "replace", "modify"],
394
- version: "1.0.0"
622
+ version: "1.1.0"
395
623
  },
396
624
  execute: async (args, ctx) => {
397
625
  const startTime = Date.now();
398
626
  const { path: filePath, oldString, newString, replaceAll = false } = args;
627
+ if (!filePath || typeof filePath !== "string") {
628
+ return {
629
+ success: false,
630
+ output: "",
631
+ error: "Invalid file path provided",
632
+ metadata: { execution_time_ms: Date.now() - startTime }
633
+ };
634
+ }
635
+ const normalizedPath = path5.normalize(filePath);
636
+ try {
637
+ const stats = await stat4(normalizedPath);
638
+ if (stats.isDirectory()) {
639
+ return {
640
+ success: false,
641
+ output: "",
642
+ error: `Path is a directory, not a file: ${normalizedPath}`,
643
+ metadata: { execution_time_ms: Date.now() - startTime }
644
+ };
645
+ }
646
+ } catch (error) {
647
+ if (error.code === "ENOENT") {
648
+ return {
649
+ success: false,
650
+ output: "",
651
+ error: `File not found: ${normalizedPath}`,
652
+ metadata: { execution_time_ms: Date.now() - startTime }
653
+ };
654
+ }
655
+ return {
656
+ success: false,
657
+ output: "",
658
+ error: error instanceof Error ? error.message : String(error),
659
+ metadata: { execution_time_ms: Date.now() - startTime }
660
+ };
661
+ }
399
662
  try {
400
- const content = await readFile2(filePath, "utf-8");
401
- const searchPattern = replaceAll ? new RegExp(escapeRegExp(oldString), "g") : new RegExp(escapeRegExp(oldString));
663
+ const content = await readFile2(normalizedPath, "utf-8");
664
+ const escapedPattern = escapeRegExp(oldString);
665
+ const searchPattern = replaceAll ? new RegExp(escapedPattern, "g") : new RegExp(escapedPattern);
402
666
  if (!searchPattern.test(content)) {
403
667
  return {
404
668
  success: false,
405
669
  output: "",
406
- error: `Could not find "${oldString}" in file ${filePath}`,
670
+ error: `Could not find "${oldString}" in file ${normalizedPath}`,
407
671
  metadata: {
408
672
  execution_time_ms: Date.now() - startTime
409
673
  }
410
674
  };
411
675
  }
412
- const newContent = replaceAll ? content.replace(new RegExp(escapeRegExp(oldString), "g"), newString) : content.replace(oldString, newString);
413
- await writeFile2(filePath, newContent, "utf-8");
676
+ const newContent = replaceAll ? content.replace(new RegExp(escapedPattern, "g"), newString) : content.replace(oldString, newString);
677
+ const tempPath = `${normalizedPath}.${randomUUID()}.tmp`;
678
+ await writeFile2(tempPath, newContent, "utf-8");
679
+ await Bun.write(normalizedPath, newContent);
680
+ try {
681
+ await unlink(tempPath);
682
+ } catch {}
683
+ const count = replaceAll ? (content.match(new RegExp(escapedPattern, "g")) || []).length : content.includes(oldString) ? 1 : 0;
414
684
  return {
415
685
  success: true,
416
- output: replaceAll ? `Replaced all occurrences in: ${filePath}` : `Replaced text in: ${filePath}`,
686
+ output: replaceAll ? `Replaced ${count} occurrences in: ${normalizedPath}` : `Replaced text in: ${normalizedPath}`,
417
687
  metadata: {
418
688
  execution_time_ms: Date.now() - startTime,
419
- file_path: filePath
689
+ file_path: normalizedPath,
690
+ replacements: count
420
691
  }
421
692
  };
422
693
  } catch (error) {
@@ -437,13 +708,22 @@ function escapeRegExp(string) {
437
708
 
438
709
  // src/env/tool/built-in/grep.ts
439
710
  import { z as z6 } from "zod";
440
- import { readFile as readFile3 } from "fs/promises";
711
+ import { readFile as readFile3, stat as stat5 } from "fs/promises";
441
712
  import { glob as globAsync2 } from "glob";
713
+ import path6 from "path";
714
+ function isBinaryContent(content) {
715
+ for (let i = 0;i < Math.min(content.length, 8192); i++) {
716
+ if (content[i] === 0) {
717
+ return true;
718
+ }
719
+ }
720
+ return false;
721
+ }
442
722
  var grepTool = {
443
723
  name: "grep",
444
724
  description: "Search for text patterns in files. Use this to find specific strings, code patterns, or content across multiple files.",
445
725
  parameters: z6.object({
446
- pattern: z6.string().describe("The pattern to search for"),
726
+ pattern: z6.string().min(1).describe("The pattern to search for"),
447
727
  path: z6.string().optional().describe("File or directory path to search in"),
448
728
  include: z6.string().optional().describe("Glob pattern for files to include (e.g., *.ts, *.js)"),
449
729
  caseSensitive: z6.boolean().default(true).optional().describe("Whether the search should be case sensitive"),
@@ -456,7 +736,7 @@ var grepTool = {
456
736
  metadata: {
457
737
  category: "file",
458
738
  tags: ["search", "grep", "find", "pattern"],
459
- version: "1.0.0"
739
+ version: "1.1.0"
460
740
  },
461
741
  execute: async (args, ctx) => {
462
742
  const startTime = Date.now();
@@ -470,34 +750,69 @@ var grepTool = {
470
750
  } = args;
471
751
  const basePath = searchPath || ctx.workdir || process.cwd();
472
752
  try {
753
+ const stats = await stat5(basePath);
754
+ if (!stats.isDirectory() && !stats.isFile()) {
755
+ return {
756
+ success: false,
757
+ output: "",
758
+ error: `Path is neither a file nor a directory: ${basePath}`,
759
+ metadata: { execution_time_ms: Date.now() - startTime }
760
+ };
761
+ }
762
+ } catch (error) {
763
+ return {
764
+ success: false,
765
+ output: "",
766
+ error: error instanceof Error ? error.message : String(error),
767
+ metadata: { execution_time_ms: Date.now() - startTime }
768
+ };
769
+ }
770
+ try {
771
+ const isFile = (await stat5(basePath)).isFile();
473
772
  let files = [];
474
- if (include) {
475
- files = await globAsync2(include, {
476
- cwd: basePath,
477
- ignore: ["**/node_modules/**", "**/.git/**"],
478
- maxDepth: 10
479
- });
773
+ if (isFile) {
774
+ files = [basePath];
480
775
  } else {
481
- files = await globAsync2("**/*", {
776
+ const globPattern = include || "**/*";
777
+ files = await globAsync2(globPattern, {
482
778
  cwd: basePath,
483
- ignore: ["**/node_modules/**", "**/.git/**"],
484
- maxDepth: 10
779
+ ignore: ["**/node_modules/**", "**/.git/**", "**/*.min.js", "**/*.map"],
780
+ maxDepth: 10,
781
+ nodir: true
485
782
  });
783
+ files = files.map((f) => path6.join(basePath, f));
486
784
  }
487
785
  const results = [];
488
786
  const regexFlags = caseSensitive ? "" : "i";
489
- const regex = new RegExp(pattern, regexFlags);
787
+ let regex;
788
+ try {
789
+ regex = new RegExp(pattern, regexFlags);
790
+ } catch {
791
+ return {
792
+ success: false,
793
+ output: "",
794
+ error: `Invalid regex pattern: ${pattern}`,
795
+ metadata: { execution_time_ms: Date.now() - startTime }
796
+ };
797
+ }
490
798
  for (const file of files) {
491
799
  if (results.length >= maxResults)
492
800
  break;
493
801
  try {
494
- const content = await readFile3(`${basePath}/${file}`, "utf-8");
495
- const lines = content.split(`
496
- `);
802
+ const fileStats = await stat5(file);
803
+ if (fileStats.isDirectory())
804
+ continue;
805
+ const content = await readFile3(file);
806
+ if (isBinaryContent(content))
807
+ continue;
808
+ const textContent = content.toString("utf-8");
809
+ const lines = textContent.split(/\r?\n/);
497
810
  for (let i = 0;i < lines.length; i++) {
811
+ regex.lastIndex = 0;
498
812
  if (regex.test(lines[i])) {
499
813
  const lineNum = showLineNumbers ? `${i + 1}:` : "";
500
- results.push(`${file}:${lineNum}${lines[i]}`);
814
+ const relativePath = path6.relative(basePath, file);
815
+ results.push(`${relativePath}:${lineNum}${lines[i]}`);
501
816
  if (results.length >= maxResults)
502
817
  break;
503
818
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-setting/roy-agent-core",
3
- "version": "1.5.30",
3
+ "version": "1.5.32",
4
4
  "type": "module",
5
5
  "description": "Core SDK for roy-agent - Environment, Components, Tools, Sessions, Tasks",
6
6
  "main": "./dist/index.js",