open-agents-ai 0.96.0 → 0.97.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.
package/dist/index.js CHANGED
@@ -1467,6 +1467,30 @@ ${stderr}` : ""),
1467
1467
  // packages/execution/dist/tools/file-read.js
1468
1468
  import { readFile } from "node:fs/promises";
1469
1469
  import { resolve } from "node:path";
1470
+ function extractPath(args) {
1471
+ if (typeof args["path"] === "string" && args["path"].trim()) {
1472
+ return args["path"].trim();
1473
+ }
1474
+ for (const key of ["file", "file_path", "filename", "filepath", "filePath"]) {
1475
+ if (typeof args[key] === "string" && args[key].trim()) {
1476
+ return args[key].trim();
1477
+ }
1478
+ }
1479
+ if (typeof args["_raw"] === "string") {
1480
+ const raw = args["_raw"].trim();
1481
+ const match = raw.match(/["']?(?:path|file|filename)["']?\s*[:=]\s*["']([^"'\s}]+)["']/i);
1482
+ if (match?.[1])
1483
+ return match[1];
1484
+ if (raw && !raw.includes("{") && !raw.includes(":") && raw.length < 500) {
1485
+ return raw;
1486
+ }
1487
+ }
1488
+ const stringVals = Object.values(args).filter((v) => typeof v === "string" && v.trim().length > 0 && v.length < 500);
1489
+ if (stringVals.length === 1 && /[./]/.test(stringVals[0])) {
1490
+ return stringVals[0].trim();
1491
+ }
1492
+ return null;
1493
+ }
1470
1494
  var FileReadTool;
1471
1495
  var init_file_read = __esm({
1472
1496
  "packages/execution/dist/tools/file-read.js"() {
@@ -1493,15 +1517,15 @@ var init_file_read = __esm({
1493
1517
  this._contextWindowSize = size;
1494
1518
  }
1495
1519
  async execute(args) {
1496
- const filePath = args["path"];
1520
+ const filePath = extractPath(args);
1497
1521
  const offset = args["offset"];
1498
1522
  const limit = args["limit"];
1499
1523
  const start = performance.now();
1500
- if (!filePath || typeof filePath !== "string" || !filePath.trim()) {
1524
+ if (!filePath) {
1501
1525
  return {
1502
1526
  success: false,
1503
1527
  output: "",
1504
- error: "Missing required parameter: path. Provide the file path to read.",
1528
+ error: `file_read requires "path" parameter. Call: file_read({"path": "src/example.ts"})`,
1505
1529
  durationMs: performance.now() - start
1506
1530
  };
1507
1531
  }
@@ -1534,10 +1558,21 @@ var init_file_read = __esm({
1534
1558
  durationMs: performance.now() - start
1535
1559
  };
1536
1560
  } catch (error) {
1561
+ const errMsg = error instanceof Error ? error.message : String(error);
1562
+ if (/ENOENT|no such file/i.test(errMsg)) {
1563
+ const hasSlash = filePath.includes("/");
1564
+ const hint = hasSlash ? `File not found: "${filePath}". Use list_directory on the parent directory to discover valid paths.` : `File not found: "${filePath}". This name is not a valid path. If you saw "${filePath}" inside a directory listing, use the FULL path (e.g., "parent_dir/${filePath}"). If it is a directory, use list_directory("${filePath}") instead of file_read.`;
1565
+ return {
1566
+ success: false,
1567
+ output: "",
1568
+ error: hint,
1569
+ durationMs: performance.now() - start
1570
+ };
1571
+ }
1537
1572
  return {
1538
1573
  success: false,
1539
1574
  output: "",
1540
- error: error instanceof Error ? error.message : String(error),
1575
+ error: errMsg,
1541
1576
  durationMs: performance.now() - start
1542
1577
  };
1543
1578
  }
@@ -1549,6 +1584,14 @@ var init_file_read = __esm({
1549
1584
  // packages/execution/dist/tools/file-write.js
1550
1585
  import { writeFile, mkdir } from "node:fs/promises";
1551
1586
  import { resolve as resolve2, dirname } from "node:path";
1587
+ function extractWritePath(args) {
1588
+ for (const key of ["path", "file", "file_path", "filename", "filepath", "filePath"]) {
1589
+ if (typeof args[key] === "string" && args[key].trim()) {
1590
+ return args[key].trim();
1591
+ }
1592
+ }
1593
+ return null;
1594
+ }
1552
1595
  var FileWriteTool;
1553
1596
  var init_file_write = __esm({
1554
1597
  "packages/execution/dist/tools/file-write.js"() {
@@ -1569,14 +1612,14 @@ var init_file_write = __esm({
1569
1612
  this.workingDir = workingDir;
1570
1613
  }
1571
1614
  async execute(args) {
1572
- const filePath = args["path"];
1573
- const content = args["content"];
1615
+ const filePath = extractWritePath(args);
1616
+ const content = args["content"] ?? args["text"] ?? args["data"];
1574
1617
  const start = performance.now();
1575
- if (!filePath || typeof filePath !== "string" || !filePath.trim()) {
1618
+ if (!filePath) {
1576
1619
  return {
1577
1620
  success: false,
1578
1621
  output: "",
1579
- error: "Missing required parameter: path. Provide the file path to write.",
1622
+ error: `file_write requires "path" parameter. Call: file_write({"path": "src/file.ts", "content": "..."})`,
1580
1623
  durationMs: performance.now() - start
1581
1624
  };
1582
1625
  }
@@ -1584,7 +1627,7 @@ var init_file_write = __esm({
1584
1627
  return {
1585
1628
  success: false,
1586
1629
  output: "",
1587
- error: "Missing required parameter: content. Provide the content to write.",
1630
+ error: `file_write requires "content" parameter. Call: file_write({"path": "${filePath}", "content": "..."})`,
1588
1631
  durationMs: performance.now() - start
1589
1632
  };
1590
1633
  }
@@ -2425,6 +2468,14 @@ Meta: ${metaKeys.map((k) => `${k}="${meta[k]?.slice(0, 80)}"`).join(", ")}`);
2425
2468
  // packages/execution/dist/tools/file-edit.js
2426
2469
  import { readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
2427
2470
  import { resolve as resolve5 } from "node:path";
2471
+ function extractEditPath(args) {
2472
+ for (const key of ["path", "file", "file_path", "filename", "filepath", "filePath"]) {
2473
+ if (typeof args[key] === "string" && args[key].trim()) {
2474
+ return args[key].trim();
2475
+ }
2476
+ }
2477
+ return null;
2478
+ }
2428
2479
  function countOccurrences(haystack, needle) {
2429
2480
  let count = 0;
2430
2481
  let pos = 0;
@@ -2478,16 +2529,16 @@ var init_file_edit = __esm({
2478
2529
  this.workingDir = workingDir;
2479
2530
  }
2480
2531
  async execute(args) {
2481
- const filePath = args["path"];
2482
- const oldString = args["old_string"];
2483
- const newString = args["new_string"];
2484
- const replaceAll = args["replace_all"] === true;
2532
+ const filePath = extractEditPath(args);
2533
+ const oldString = args["old_string"] ?? args["oldString"] ?? args["search"] ?? args["find"];
2534
+ const newString = args["new_string"] ?? args["newString"] ?? args["replace"] ?? args["replacement"];
2535
+ const replaceAll = args["replace_all"] === true || args["replaceAll"] === true;
2485
2536
  const start = performance.now();
2486
- if (!filePath || typeof filePath !== "string" || !filePath.trim()) {
2537
+ if (!filePath) {
2487
2538
  return {
2488
2539
  success: false,
2489
2540
  output: "",
2490
- error: "Missing required parameter: path. Provide the file path to edit.",
2541
+ error: `file_edit requires "path" parameter. Call: file_edit({"path": "src/file.ts", "old_string": "...", "new_string": "..."})`,
2491
2542
  durationMs: performance.now() - start
2492
2543
  };
2493
2544
  }
@@ -2495,7 +2546,7 @@ var init_file_edit = __esm({
2495
2546
  return {
2496
2547
  success: false,
2497
2548
  output: "",
2498
- error: "Missing required parameter: old_string. Provide the text to find and replace.",
2549
+ error: `file_edit requires "old_string" parameter. Call: file_edit({"path": "${filePath}", "old_string": "text to find", "new_string": "replacement"})`,
2499
2550
  durationMs: performance.now() - start
2500
2551
  };
2501
2552
  }
@@ -2503,7 +2554,7 @@ var init_file_edit = __esm({
2503
2554
  return {
2504
2555
  success: false,
2505
2556
  output: "",
2506
- error: "Missing required parameter: new_string. Provide the replacement text.",
2557
+ error: `file_edit requires "new_string" parameter. Call: file_edit({"path": "${filePath}", "old_string": "...", "new_string": "replacement text"})`,
2507
2558
  durationMs: performance.now() - start
2508
2559
  };
2509
2560
  }
@@ -3153,7 +3204,7 @@ var init_list_directory = __esm({
3153
3204
  MAX_ENTRIES = 100;
3154
3205
  ListDirectoryTool = class {
3155
3206
  name = "list_directory";
3156
- description = "List files and directories at a given path. Shows file sizes and types.";
3207
+ description = "List files and directories at a given path. Shows file sizes and types. Output includes full relative paths you can use directly in subsequent tool calls.";
3157
3208
  parameters = {
3158
3209
  type: "object",
3159
3210
  properties: {
@@ -3177,18 +3228,40 @@ var init_list_directory = __esm({
3177
3228
  const entries = readdirSync(fullPath, { withFileTypes: true });
3178
3229
  const visible = entries.filter((e) => !EXCLUDED.has(e.name));
3179
3230
  const limited = visible.slice(0, MAX_ENTRIES);
3231
+ const dirs = [];
3232
+ const files = [];
3180
3233
  const lines = limited.map((entry) => {
3181
- const prefix = entry.isDirectory() ? "d" : "f";
3234
+ const relPath = dirPath === "." ? entry.name : join7(dirPath, entry.name);
3182
3235
  if (entry.isDirectory()) {
3183
- return `${prefix} ${entry.name}`;
3236
+ dirs.push(relPath);
3237
+ return `d ${relPath}/`;
3184
3238
  }
3185
3239
  let size = 0;
3186
3240
  try {
3187
3241
  size = statSync(join7(fullPath, entry.name)).size;
3188
3242
  } catch {
3189
3243
  }
3190
- return `${prefix} ${entry.name} ${size}`;
3244
+ files.push(relPath);
3245
+ return `f ${relPath} ${size}`;
3191
3246
  });
3247
+ if (dirs.length > 0 || files.length > 0) {
3248
+ lines.push("");
3249
+ lines.push("Next steps \u2014 use these exact paths:");
3250
+ if (dirs.length > 0) {
3251
+ for (const d of dirs.slice(0, 8)) {
3252
+ lines.push(` list_directory("${d}")`);
3253
+ }
3254
+ if (dirs.length > 8)
3255
+ lines.push(` ... and ${dirs.length - 8} more directories`);
3256
+ }
3257
+ if (files.length > 0) {
3258
+ for (const f of files.slice(0, 5)) {
3259
+ lines.push(` file_read("${f}")`);
3260
+ }
3261
+ if (files.length > 5)
3262
+ lines.push(` ... and ${files.length - 5} more files`);
3263
+ }
3264
+ }
3192
3265
  return {
3193
3266
  success: true,
3194
3267
  output: lines.join("\n"),
@@ -17104,14 +17177,15 @@ ${result.output.length > maxLen ? this.foldOutput(result.output, maxLen) : resul
17104
17177
  });
17105
17178
  }
17106
17179
  const enoentTools = /* @__PURE__ */ new Set(["file_read", "list_directory", "find_files"]);
17107
- if (!result.success && enoentTools.has(tc.name) && /ENOENT|no such file|does not exist/i.test(result.error ?? result.output)) {
17180
+ if (!result.success && enoentTools.has(tc.name) && /ENOENT|no such file|does not exist|not found/i.test(result.error ?? result.output)) {
17108
17181
  this._consecutiveEnoent++;
17109
17182
  if (this._consecutiveEnoent >= 2) {
17110
- this.pendingUserMessages.push(`[SYSTEM] You have hit ${this._consecutiveEnoent} consecutive ENOENT errors. STOP guessing paths. Instead:
17111
- 1. Use list_directory on the PROJECT ROOT to discover what actually exists
17112
- 2. If the missing path came from memory, use memory_write to remove the stale reference
17113
- 3. Navigate from the root to find the correct location
17114
- Do NOT continue walking up parent directories.`);
17183
+ const failedPath = String(tc.arguments?.["path"] ?? tc.arguments?.["file"] ?? "unknown");
17184
+ this.pendingUserMessages.push(`[SYSTEM] ${this._consecutiveEnoent} consecutive file-not-found errors (last: "${failedPath}"). You are repeating failed paths. CHANGE YOUR APPROACH:
17185
+ 1. Call list_directory(".") to see what exists at the project root
17186
+ 2. Entries in a directory listing are RELATIVE to that directory. If you listed "parent/" and saw "child", the full path is "parent/child" \u2014 NOT ".child" or just "child"
17187
+ 3. If an entry is a directory (marked "d"), use list_directory on it, NOT file_read
17188
+ 4. Use the FULL relative paths shown in list_directory output \u2014 they are copy-paste ready`);
17115
17189
  }
17116
17190
  } else {
17117
17191
  this._consecutiveEnoent = 0;
@@ -17359,14 +17433,15 @@ Integrate this guidance into your current approach. Continue working on the task
17359
17433
  ${result.output}`;
17360
17434
  this.emit({ type: "tool_result", toolName: tc.name, content: output.slice(0, 200), success: result.success, turn, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
17361
17435
  const enoentTools2 = /* @__PURE__ */ new Set(["file_read", "list_directory", "find_files"]);
17362
- if (!result.success && enoentTools2.has(tc.name) && /ENOENT|no such file|does not exist/i.test(result.error ?? result.output)) {
17436
+ if (!result.success && enoentTools2.has(tc.name) && /ENOENT|no such file|does not exist|not found/i.test(result.error ?? result.output)) {
17363
17437
  this._consecutiveEnoent++;
17364
17438
  if (this._consecutiveEnoent >= 2) {
17365
- this.pendingUserMessages.push(`[SYSTEM] You have hit ${this._consecutiveEnoent} consecutive ENOENT errors. STOP guessing paths. Instead:
17366
- 1. Use list_directory on the PROJECT ROOT to discover what actually exists
17367
- 2. If the missing path came from memory, use memory_write to remove the stale reference
17368
- 3. Navigate from the root to find the correct location
17369
- Do NOT continue walking up parent directories.`);
17439
+ const failedPath2 = String(tc.arguments?.["path"] ?? tc.arguments?.["file"] ?? "unknown");
17440
+ this.pendingUserMessages.push(`[SYSTEM] ${this._consecutiveEnoent} consecutive file-not-found errors (last: "${failedPath2}"). You are repeating failed paths. CHANGE YOUR APPROACH:
17441
+ 1. Call list_directory(".") to see what exists at the project root
17442
+ 2. Entries in a directory listing are RELATIVE to that directory. If you listed "parent/" and saw "child", the full path is "parent/child" \u2014 NOT ".child" or just "child"
17443
+ 3. If an entry is a directory (marked "d"), use list_directory on it, NOT file_read
17444
+ 4. Use the FULL relative paths shown in list_directory output \u2014 they are copy-paste ready`);
17370
17445
  }
17371
17446
  } else {
17372
17447
  this._consecutiveEnoent = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.96.0",
3
+ "version": "0.97.0",
4
4
  "description": "AI coding agent powered by open-source models (Ollama/vLLM) — interactive TUI with agentic tool-calling loop",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -385,3 +385,11 @@ When a file_read, list_directory, or find_files call returns ENOENT (file/direct
385
385
  - If the missing path came from memory, update memory to remove the stale reference
386
386
  - After discovering the real structure, navigate to the correct path
387
387
  - Never make more than 2 consecutive attempts at paths that don't exist
388
+
389
+ ## Directory Listing Path Rules
390
+
391
+ Entries in a directory listing are RELATIVE to the directory you listed.
392
+ - If you call list_directory(".oa") and see "context", the full path is ".oa/context" — NOT ".context" or "context"
393
+ - If an entry is marked "d" (directory), use list_directory on it — NOT file_read
394
+ - list_directory output includes full relative paths you can copy directly into your next tool call
395
+ - Prefer list_directory over shell ls — it shows full relative paths you can copy directly into your next tool call
@@ -42,3 +42,6 @@ NEVER say "I can't do that". ALWAYS attempt the task using your tools. If a tool
42
42
  - You MUST call task_complete when done
43
43
  - Do NOT output long explanations. Focus on tool calls.
44
44
  - If file_read/list_directory returns ENOENT, use list_directory on the project root — do NOT guess parent paths
45
+ - Directory listing entries are RELATIVE to the listed directory. If you list "parent/" and see "child", the full path is "parent/child" — NOT ".child" or just "child"
46
+ - If an entry is a directory (d), use list_directory on it — NOT file_read
47
+ - Prefer list_directory over shell ls — it shows full paths ready for your next tool call
@@ -15,3 +15,5 @@ Rules:
15
15
  - Run tests after every change.
16
16
  - Call task_complete when done.
17
17
  - If ENOENT, list_directory on project root. Don't guess paths.
18
+ - Directory entries are RELATIVE. If you list "parent/" and see "child", the path is "parent/child" — NOT ".child".
19
+ - Use list_directory for directories, NOT file_read. Prefer list_directory over shell ls.