@robota-sdk/agent-tools 3.0.0-beta.58 → 3.0.0-beta.59

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
@@ -59,7 +59,7 @@ const agent = new Robota({
59
59
 
60
60
  ## Edit and Write Safety
61
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.
62
+ Recent file tool updates keep write/edit behavior atomic and make Edit tool results easier for higher layers to display. Atomic replacements preserve existing target mode bits, so executable scripts remain executable after Write or Edit updates. 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
63
 
64
64
  ## Tool Infrastructure
65
65
 
@@ -1038,18 +1038,36 @@ var import_node_crypto = require("crypto");
1038
1038
  var import_promises2 = require("fs/promises");
1039
1039
  var import_node_path = require("path");
1040
1040
  var TEMP_RANDOM_BYTES = 6;
1041
+ var PRESERVED_MODE_BITS = 4095;
1042
+ var MISSING_FILE_ERROR_CODE = "ENOENT";
1041
1043
  function createTempFilePath(filePath) {
1042
1044
  const dir = (0, import_node_path.dirname)(filePath);
1043
1045
  const name = (0, import_node_path.basename)(filePath);
1044
1046
  const suffix = (0, import_node_crypto.randomBytes)(TEMP_RANDOM_BYTES).toString("hex");
1045
1047
  return (0, import_node_path.join)(dir, `.${name}.robota-tmp-${process.pid}-${Date.now()}-${suffix}`);
1046
1048
  }
1049
+ async function readExistingMode(filePath) {
1050
+ try {
1051
+ const fileStats = await (0, import_promises2.stat)(filePath);
1052
+ return fileStats.mode & PRESERVED_MODE_BITS;
1053
+ } catch (error) {
1054
+ if (error instanceof Error && hasErrorCode(error, MISSING_FILE_ERROR_CODE)) return void 0;
1055
+ throw error;
1056
+ }
1057
+ }
1058
+ function hasErrorCode(error, code) {
1059
+ return "code" in error && error.code === code;
1060
+ }
1047
1061
  async function atomicWriteUtf8File(filePath, content) {
1048
1062
  const dir = (0, import_node_path.dirname)(filePath);
1049
1063
  await (0, import_promises2.mkdir)(dir, { recursive: true });
1064
+ const existingMode = await readExistingMode(filePath);
1050
1065
  const tempFilePath = createTempFilePath(filePath);
1051
1066
  try {
1052
1067
  await (0, import_promises2.writeFile)(tempFilePath, content, "utf8");
1068
+ if (existingMode !== void 0) {
1069
+ await (0, import_promises2.chmod)(tempFilePath, existingMode);
1070
+ }
1053
1071
  await (0, import_promises2.rename)(tempFilePath, filePath);
1054
1072
  } catch (error) {
1055
1073
  await (0, import_promises2.rm)(tempFilePath, { force: true }).catch(() => void 0);
@@ -985,21 +985,39 @@ import { z as z3 } from "zod";
985
985
 
986
986
  // src/builtins/atomic-file-write.ts
987
987
  import { randomBytes } from "crypto";
988
- import { mkdir, rename, rm, writeFile } from "fs/promises";
988
+ import { chmod, mkdir, rename, rm, stat as stat2, writeFile } from "fs/promises";
989
989
  import { basename, dirname, join } from "path";
990
990
  var TEMP_RANDOM_BYTES = 6;
991
+ var PRESERVED_MODE_BITS = 4095;
992
+ var MISSING_FILE_ERROR_CODE = "ENOENT";
991
993
  function createTempFilePath(filePath) {
992
994
  const dir = dirname(filePath);
993
995
  const name = basename(filePath);
994
996
  const suffix = randomBytes(TEMP_RANDOM_BYTES).toString("hex");
995
997
  return join(dir, `.${name}.robota-tmp-${process.pid}-${Date.now()}-${suffix}`);
996
998
  }
999
+ async function readExistingMode(filePath) {
1000
+ try {
1001
+ const fileStats = await stat2(filePath);
1002
+ return fileStats.mode & PRESERVED_MODE_BITS;
1003
+ } catch (error) {
1004
+ if (error instanceof Error && hasErrorCode(error, MISSING_FILE_ERROR_CODE)) return void 0;
1005
+ throw error;
1006
+ }
1007
+ }
1008
+ function hasErrorCode(error, code) {
1009
+ return "code" in error && error.code === code;
1010
+ }
997
1011
  async function atomicWriteUtf8File(filePath, content) {
998
1012
  const dir = dirname(filePath);
999
1013
  await mkdir(dir, { recursive: true });
1014
+ const existingMode = await readExistingMode(filePath);
1000
1015
  const tempFilePath = createTempFilePath(filePath);
1001
1016
  try {
1002
1017
  await writeFile(tempFilePath, content, "utf8");
1018
+ if (existingMode !== void 0) {
1019
+ await chmod(tempFilePath, existingMode);
1020
+ }
1003
1021
  await rename(tempFilePath, filePath);
1004
1022
  } catch (error) {
1005
1023
  await rm(tempFilePath, { force: true }).catch(() => void 0);
@@ -1115,7 +1133,7 @@ var editTool = createZodFunctionTool(
1115
1133
  );
1116
1134
 
1117
1135
  // src/builtins/glob-tool.ts
1118
- import { stat as stat2 } from "fs/promises";
1136
+ import { stat as stat3 } from "fs/promises";
1119
1137
  import { resolve } from "path";
1120
1138
  import fg from "fast-glob";
1121
1139
  import { z as z5 } from "zod";
@@ -1152,7 +1170,7 @@ async function globFileTool(args) {
1152
1170
  matches.map(async (p) => {
1153
1171
  const absPath = resolve(cwd, p);
1154
1172
  try {
1155
- const s = await stat2(absPath);
1173
+ const s = await stat3(absPath);
1156
1174
  return { path: p, mtime: s.mtimeMs };
1157
1175
  } catch {
1158
1176
  return { path: p, mtime: 0 };
@@ -1187,7 +1205,7 @@ var globTool = createZodFunctionTool(
1187
1205
  );
1188
1206
 
1189
1207
  // src/builtins/grep-tool.ts
1190
- import { readFile as readFile3, readdir, stat as stat3 } from "fs/promises";
1208
+ import { readFile as readFile3, readdir, stat as stat4 } from "fs/promises";
1191
1209
  import { join as join2, resolve as resolve2 } from "path";
1192
1210
  import { z as z6 } from "zod";
1193
1211
  var GrepSchema = z6.object({
@@ -1225,7 +1243,7 @@ async function collectFiles(dirPath, glob) {
1225
1243
  const fullPath = join2(current, name);
1226
1244
  let fileStat;
1227
1245
  try {
1228
- fileStat = await stat3(fullPath);
1246
+ fileStat = await stat4(fullPath);
1229
1247
  } catch {
1230
1248
  continue;
1231
1249
  }
@@ -1295,7 +1313,7 @@ async function grepFileTool(args) {
1295
1313
  }
1296
1314
  let targetStat;
1297
1315
  try {
1298
- targetStat = await stat3(targetPath);
1316
+ targetStat = await stat4(targetPath);
1299
1317
  } catch {
1300
1318
  const result2 = {
1301
1319
  success: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robota-sdk/agent-tools",
3
- "version": "3.0.0-beta.58",
3
+ "version": "3.0.0-beta.59",
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.58"
37
+ "@robota-sdk/agent-core": "3.0.0-beta.59"
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.58"
45
+ "@robota-sdk/agent-core": "3.0.0-beta.59"
46
46
  },
47
47
  "license": "MIT",
48
48
  "publishConfig": {