@robota-sdk/agent-tools 3.0.0-beta.56 → 3.0.0-beta.57

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/README.md CHANGED
@@ -57,6 +57,10 @@ const agent = new Robota({
57
57
  | `webFetchTool` | WebFetch | Fetch URL content (HTML-to-text conversion) |
58
58
  | `webSearchTool` | WebSearch | Web search via Brave Search API |
59
59
 
60
+ ## Edit and Write Safety
61
+
62
+ Recent file tool updates keep write/edit behavior atomic and make Edit tool results easier for higher layers to display. The Edit tool returns line metadata for changed regions, allowing the CLI to render concise context hunks instead of dumping full files or opaque summaries.
63
+
60
64
  ## Tool Infrastructure
61
65
 
62
66
  | Export | Description |
@@ -1031,9 +1031,33 @@ var readTool = createZodFunctionTool(
1031
1031
  );
1032
1032
 
1033
1033
  // src/builtins/write-tool.ts
1034
+ var import_zod3 = require("zod");
1035
+
1036
+ // src/builtins/atomic-file-write.ts
1037
+ var import_node_crypto = require("crypto");
1034
1038
  var import_promises2 = require("fs/promises");
1035
1039
  var import_node_path = require("path");
1036
- var import_zod3 = require("zod");
1040
+ var TEMP_RANDOM_BYTES = 6;
1041
+ function createTempFilePath(filePath) {
1042
+ const dir = (0, import_node_path.dirname)(filePath);
1043
+ const name = (0, import_node_path.basename)(filePath);
1044
+ const suffix = (0, import_node_crypto.randomBytes)(TEMP_RANDOM_BYTES).toString("hex");
1045
+ return (0, import_node_path.join)(dir, `.${name}.robota-tmp-${process.pid}-${Date.now()}-${suffix}`);
1046
+ }
1047
+ async function atomicWriteUtf8File(filePath, content) {
1048
+ const dir = (0, import_node_path.dirname)(filePath);
1049
+ await (0, import_promises2.mkdir)(dir, { recursive: true });
1050
+ const tempFilePath = createTempFilePath(filePath);
1051
+ try {
1052
+ await (0, import_promises2.writeFile)(tempFilePath, content, "utf8");
1053
+ await (0, import_promises2.rename)(tempFilePath, filePath);
1054
+ } catch (error) {
1055
+ await (0, import_promises2.rm)(tempFilePath, { force: true }).catch(() => void 0);
1056
+ throw error;
1057
+ }
1058
+ }
1059
+
1060
+ // src/builtins/write-tool.ts
1037
1061
  var WriteSchema = import_zod3.z.object({
1038
1062
  filePath: import_zod3.z.string().describe("The absolute path to the file to write"),
1039
1063
  content: import_zod3.z.string().describe("The content to write to the file")
@@ -1041,8 +1065,7 @@ var WriteSchema = import_zod3.z.object({
1041
1065
  async function writeFileTool(args) {
1042
1066
  const { filePath, content } = args;
1043
1067
  try {
1044
- await (0, import_promises2.mkdir)((0, import_node_path.dirname)(filePath), { recursive: true });
1045
- await (0, import_promises2.writeFile)(filePath, content, "utf8");
1068
+ await atomicWriteUtf8File(filePath, content);
1046
1069
  const result = {
1047
1070
  success: true,
1048
1071
  output: `Written ${Buffer.byteLength(content, "utf8")} bytes to ${filePath}`
@@ -1113,7 +1136,7 @@ async function editFileTool(args) {
1113
1136
  }
1114
1137
  const updated = replaceAll ? content.split(oldString).join(newString) : content.slice(0, content.indexOf(oldString)) + newString + content.slice(content.indexOf(oldString) + oldString.length);
1115
1138
  try {
1116
- await (0, import_promises3.writeFile)(filePath, updated, "utf8");
1139
+ await atomicWriteUtf8File(filePath, updated);
1117
1140
  } catch (err) {
1118
1141
  const result2 = {
1119
1142
  success: false,
@@ -981,9 +981,33 @@ var readTool = createZodFunctionTool(
981
981
  );
982
982
 
983
983
  // src/builtins/write-tool.ts
984
- import { writeFile, mkdir } from "fs/promises";
985
- import { dirname } from "path";
986
984
  import { z as z3 } from "zod";
985
+
986
+ // src/builtins/atomic-file-write.ts
987
+ import { randomBytes } from "crypto";
988
+ import { mkdir, rename, rm, writeFile } from "fs/promises";
989
+ import { basename, dirname, join } from "path";
990
+ var TEMP_RANDOM_BYTES = 6;
991
+ function createTempFilePath(filePath) {
992
+ const dir = dirname(filePath);
993
+ const name = basename(filePath);
994
+ const suffix = randomBytes(TEMP_RANDOM_BYTES).toString("hex");
995
+ return join(dir, `.${name}.robota-tmp-${process.pid}-${Date.now()}-${suffix}`);
996
+ }
997
+ async function atomicWriteUtf8File(filePath, content) {
998
+ const dir = dirname(filePath);
999
+ await mkdir(dir, { recursive: true });
1000
+ const tempFilePath = createTempFilePath(filePath);
1001
+ try {
1002
+ await writeFile(tempFilePath, content, "utf8");
1003
+ await rename(tempFilePath, filePath);
1004
+ } catch (error) {
1005
+ await rm(tempFilePath, { force: true }).catch(() => void 0);
1006
+ throw error;
1007
+ }
1008
+ }
1009
+
1010
+ // src/builtins/write-tool.ts
987
1011
  var WriteSchema = z3.object({
988
1012
  filePath: z3.string().describe("The absolute path to the file to write"),
989
1013
  content: z3.string().describe("The content to write to the file")
@@ -991,8 +1015,7 @@ var WriteSchema = z3.object({
991
1015
  async function writeFileTool(args) {
992
1016
  const { filePath, content } = args;
993
1017
  try {
994
- await mkdir(dirname(filePath), { recursive: true });
995
- await writeFile(filePath, content, "utf8");
1018
+ await atomicWriteUtf8File(filePath, content);
996
1019
  const result = {
997
1020
  success: true,
998
1021
  output: `Written ${Buffer.byteLength(content, "utf8")} bytes to ${filePath}`
@@ -1017,7 +1040,7 @@ var writeTool = createZodFunctionTool(
1017
1040
  );
1018
1041
 
1019
1042
  // src/builtins/edit-tool.ts
1020
- import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
1043
+ import { readFile as readFile2 } from "fs/promises";
1021
1044
  import { z as z4 } from "zod";
1022
1045
  var EditSchema = z4.object({
1023
1046
  filePath: z4.string().describe("The absolute path to the file to modify"),
@@ -1063,7 +1086,7 @@ async function editFileTool(args) {
1063
1086
  }
1064
1087
  const updated = replaceAll ? content.split(oldString).join(newString) : content.slice(0, content.indexOf(oldString)) + newString + content.slice(content.indexOf(oldString) + oldString.length);
1065
1088
  try {
1066
- await writeFile2(filePath, updated, "utf8");
1089
+ await atomicWriteUtf8File(filePath, updated);
1067
1090
  } catch (err) {
1068
1091
  const result2 = {
1069
1092
  success: false,
@@ -1165,7 +1188,7 @@ var globTool = createZodFunctionTool(
1165
1188
 
1166
1189
  // src/builtins/grep-tool.ts
1167
1190
  import { readFile as readFile3, readdir, stat as stat3 } from "fs/promises";
1168
- import { join, resolve as resolve2 } from "path";
1191
+ import { join as join2, resolve as resolve2 } from "path";
1169
1192
  import { z as z6 } from "zod";
1170
1193
  var GrepSchema = z6.object({
1171
1194
  pattern: z6.string().describe("The regular expression pattern to search for in file contents"),
@@ -1199,7 +1222,7 @@ async function collectFiles(dirPath, glob) {
1199
1222
  }
1200
1223
  for (const name of entryNames) {
1201
1224
  if (name === "node_modules" || name === ".git") continue;
1202
- const fullPath = join(current, name);
1225
+ const fullPath = join2(current, name);
1203
1226
  let fileStat;
1204
1227
  try {
1205
1228
  fileStat = await stat3(fullPath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robota-sdk/agent-tools",
3
- "version": "3.0.0-beta.56",
3
+ "version": "3.0.0-beta.57",
4
4
  "description": "Tool registry and implementations for Robota SDK",
5
5
  "type": "module",
6
6
  "main": "dist/node/index.js",
@@ -34,7 +34,7 @@
34
34
  "zod": "^3.24.0"
35
35
  },
36
36
  "peerDependencies": {
37
- "@robota-sdk/agent-core": "3.0.0-beta.56"
37
+ "@robota-sdk/agent-core": "3.0.0-beta.57"
38
38
  },
39
39
  "devDependencies": {
40
40
  "openapi-types": "^12.1.3",
@@ -42,7 +42,7 @@
42
42
  "tsup": "^8.0.1",
43
43
  "typescript": "^5.3.3",
44
44
  "vitest": "^1.6.1",
45
- "@robota-sdk/agent-core": "3.0.0-beta.56"
45
+ "@robota-sdk/agent-core": "3.0.0-beta.57"
46
46
  },
47
47
  "license": "MIT",
48
48
  "publishConfig": {
@@ -53,6 +53,7 @@
53
53
  "build:js": "tsup src/index.ts --format esm,cjs --out-dir dist/node --clean",
54
54
  "build:types": "tsup src/index.ts --format esm,cjs --dts-only --out-dir dist/node",
55
55
  "test": "vitest run --passWithNoTests",
56
+ "test:coverage": "vitest run --coverage --passWithNoTests",
56
57
  "typecheck": "tsc --noEmit",
57
58
  "lint": "eslint src/ --ext .ts,.tsx",
58
59
  "lint:fix": "eslint src/ --ext .ts,.tsx --fix",