@dexto/tools-filesystem 1.5.8 → 1.6.1

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 (95) hide show
  1. package/dist/directory-approval.cjs +98 -0
  2. package/dist/directory-approval.d.ts +24 -0
  3. package/dist/directory-approval.d.ts.map +1 -0
  4. package/dist/directory-approval.integration.test.cjs +175 -390
  5. package/dist/directory-approval.integration.test.d.ts +14 -2
  6. package/dist/directory-approval.integration.test.d.ts.map +1 -0
  7. package/dist/directory-approval.integration.test.js +178 -390
  8. package/dist/directory-approval.js +63 -0
  9. package/dist/edit-file-tool.cjs +109 -120
  10. package/dist/edit-file-tool.d.ts +22 -9
  11. package/dist/edit-file-tool.d.ts.map +1 -0
  12. package/dist/edit-file-tool.js +116 -110
  13. package/dist/edit-file-tool.test.cjs +109 -29
  14. package/dist/edit-file-tool.test.d.ts +7 -2
  15. package/dist/edit-file-tool.test.d.ts.map +1 -0
  16. package/dist/edit-file-tool.test.js +109 -29
  17. package/dist/error-codes.cjs +4 -0
  18. package/dist/error-codes.d.ts +6 -3
  19. package/dist/error-codes.d.ts.map +1 -0
  20. package/dist/error-codes.js +4 -0
  21. package/dist/errors.cjs +48 -0
  22. package/dist/errors.d.ts +20 -7
  23. package/dist/errors.d.ts.map +1 -0
  24. package/dist/errors.js +48 -0
  25. package/dist/file-tool-types.d.ts +8 -40
  26. package/dist/file-tool-types.d.ts.map +1 -0
  27. package/dist/filesystem-service.cjs +325 -10
  28. package/dist/filesystem-service.d.ts +41 -12
  29. package/dist/filesystem-service.d.ts.map +1 -0
  30. package/dist/filesystem-service.js +326 -11
  31. package/dist/filesystem-service.test.cjs +10 -2
  32. package/dist/filesystem-service.test.d.ts +7 -2
  33. package/dist/filesystem-service.test.d.ts.map +1 -0
  34. package/dist/filesystem-service.test.js +10 -2
  35. package/dist/glob-files-tool.cjs +32 -46
  36. package/dist/glob-files-tool.d.ts +19 -9
  37. package/dist/glob-files-tool.d.ts.map +1 -0
  38. package/dist/glob-files-tool.js +33 -47
  39. package/dist/grep-content-tool.cjs +40 -45
  40. package/dist/grep-content-tool.d.ts +28 -9
  41. package/dist/grep-content-tool.d.ts.map +1 -0
  42. package/dist/grep-content-tool.js +41 -46
  43. package/dist/index.cjs +6 -3
  44. package/dist/index.d.cts +852 -14
  45. package/dist/index.d.ts +11 -5
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +4 -2
  48. package/dist/path-validator.cjs +28 -2
  49. package/dist/path-validator.d.ts +20 -9
  50. package/dist/path-validator.d.ts.map +1 -0
  51. package/dist/path-validator.js +28 -2
  52. package/dist/path-validator.test.d.ts +7 -2
  53. package/dist/path-validator.test.d.ts.map +1 -0
  54. package/dist/read-file-tool.cjs +26 -59
  55. package/dist/read-file-tool.d.ts +19 -9
  56. package/dist/read-file-tool.d.ts.map +1 -0
  57. package/dist/read-file-tool.js +27 -50
  58. package/dist/tool-factory-config.cjs +61 -0
  59. package/dist/{tool-provider.d.ts → tool-factory-config.d.ts} +13 -30
  60. package/dist/tool-factory-config.d.ts.map +1 -0
  61. package/dist/tool-factory-config.js +36 -0
  62. package/dist/tool-factory.cjs +123 -0
  63. package/dist/tool-factory.d.ts +4 -0
  64. package/dist/tool-factory.d.ts.map +1 -0
  65. package/dist/tool-factory.js +102 -0
  66. package/dist/types.d.ts +82 -18
  67. package/dist/types.d.ts.map +1 -0
  68. package/dist/write-file-tool.cjs +93 -99
  69. package/dist/write-file-tool.d.ts +22 -9
  70. package/dist/write-file-tool.d.ts.map +1 -0
  71. package/dist/write-file-tool.js +97 -91
  72. package/dist/write-file-tool.test.cjs +139 -33
  73. package/dist/write-file-tool.test.d.ts +7 -2
  74. package/dist/write-file-tool.test.d.ts.map +1 -0
  75. package/dist/write-file-tool.test.js +139 -33
  76. package/package.json +5 -4
  77. package/dist/directory-approval.integration.test.d.cts +0 -2
  78. package/dist/edit-file-tool.d.cts +0 -17
  79. package/dist/edit-file-tool.test.d.cts +0 -2
  80. package/dist/error-codes.d.cts +0 -32
  81. package/dist/errors.d.cts +0 -112
  82. package/dist/file-tool-types.d.cts +0 -46
  83. package/dist/filesystem-service.d.cts +0 -112
  84. package/dist/filesystem-service.test.d.cts +0 -2
  85. package/dist/glob-files-tool.d.cts +0 -17
  86. package/dist/grep-content-tool.d.cts +0 -17
  87. package/dist/path-validator.d.cts +0 -97
  88. package/dist/path-validator.test.d.cts +0 -2
  89. package/dist/read-file-tool.d.cts +0 -17
  90. package/dist/tool-provider.cjs +0 -123
  91. package/dist/tool-provider.d.cts +0 -77
  92. package/dist/tool-provider.js +0 -99
  93. package/dist/types.d.cts +0 -178
  94. package/dist/write-file-tool.d.cts +0 -17
  95. package/dist/write-file-tool.test.d.cts +0 -2
@@ -1,9 +1,7 @@
1
1
  "use strict";
2
- var __create = Object.create;
3
2
  var __defProp = Object.defineProperty;
4
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
6
  var __export = (target, all) => {
9
7
  for (var name in all)
@@ -17,33 +15,25 @@ var __copyProps = (to, from, except, desc) => {
17
15
  }
18
16
  return to;
19
17
  };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
19
  var write_file_tool_exports = {};
30
20
  __export(write_file_tool_exports, {
31
21
  createWriteFileTool: () => createWriteFileTool
32
22
  });
33
23
  module.exports = __toCommonJS(write_file_tool_exports);
34
- var path = __toESM(require("node:path"), 1);
35
24
  var import_node_crypto = require("node:crypto");
36
25
  var import_zod = require("zod");
37
26
  var import_diff = require("diff");
38
27
  var import_core = require("@dexto/core");
39
28
  var import_error_codes = require("./error-codes.js");
29
+ var import_directory_approval = require("./directory-approval.js");
40
30
  const previewContentHashCache = /* @__PURE__ */ new Map();
41
31
  const FILE_NOT_EXISTS_MARKER = null;
42
32
  function computeContentHash(content) {
43
33
  return (0, import_node_crypto.createHash)("sha256").update(content, "utf8").digest("hex");
44
34
  }
45
35
  const WriteFileInputSchema = import_zod.z.object({
46
- file_path: import_zod.z.string().describe("Absolute path where the file should be written"),
36
+ file_path: import_zod.z.string().min(1).describe("Absolute path where the file should be written"),
47
37
  content: import_zod.z.string().describe("Content to write to the file"),
48
38
  create_dirs: import_zod.z.boolean().optional().default(false).describe("Create parent directories if they don't exist (default: false)"),
49
39
  encoding: import_zod.z.enum(["utf-8", "ascii", "latin1", "utf16le"]).optional().default("utf-8").describe("File encoding (default: utf-8)")
@@ -56,101 +46,97 @@ function generateDiffPreview(filePath, originalContent, newContent) {
56
46
  const deletions = (unified.match(/^-[^-]/gm) || []).length;
57
47
  return {
58
48
  type: "diff",
49
+ title: "Update file",
59
50
  unified,
60
51
  filename: filePath,
61
52
  additions,
62
53
  deletions
63
54
  };
64
55
  }
65
- function createWriteFileTool(options) {
66
- const { fileSystemService, directoryApproval } = options;
67
- let pendingApprovalParentDir;
68
- return {
56
+ function createWriteFileTool(getFileSystemService) {
57
+ return (0, import_core.defineTool)({
69
58
  id: "write_file",
59
+ aliases: ["write"],
70
60
  description: "Write content to a file. Creates a new file or overwrites existing file. Automatically creates backup of existing files before overwriting. Use create_dirs to create parent directories. Requires approval for all write operations. Returns success status, path, bytes written, and backup path if applicable.",
71
61
  inputSchema: WriteFileInputSchema,
72
- /**
73
- * Check if this write operation needs directory access approval.
74
- * Returns custom approval request if the file is outside allowed paths.
75
- */
76
- getApprovalOverride: async (args) => {
77
- const { file_path } = args;
78
- if (!file_path) return null;
79
- const isAllowed = await fileSystemService.isPathWithinConfigAllowed(file_path);
80
- if (isAllowed) {
81
- return null;
82
- }
83
- if (directoryApproval?.isSessionApproved(file_path)) {
84
- return null;
85
- }
86
- const absolutePath = path.resolve(file_path);
87
- const parentDir = path.dirname(absolutePath);
88
- pendingApprovalParentDir = parentDir;
89
- return {
90
- type: import_core.ApprovalType.DIRECTORY_ACCESS,
91
- metadata: {
92
- path: absolutePath,
93
- parentDir,
94
- operation: "write",
95
- toolName: "write_file"
96
- }
97
- };
98
- },
99
- /**
100
- * Handle approved directory access - remember the directory for session
101
- */
102
- onApprovalGranted: (response) => {
103
- if (!directoryApproval || !pendingApprovalParentDir) return;
104
- const data = response.data;
105
- const rememberDirectory = data?.rememberDirectory ?? false;
106
- directoryApproval.addApproved(
107
- pendingApprovalParentDir,
108
- rememberDirectory ? "session" : "once"
109
- );
110
- pendingApprovalParentDir = void 0;
111
- },
112
- /**
113
- * Generate preview for approval UI - shows diff or file creation info
114
- * Stores content hash for change detection in execute phase.
115
- */
116
- generatePreview: async (input, context) => {
117
- const { file_path, content } = input;
118
- try {
119
- const originalFile = await fileSystemService.readFile(file_path);
120
- const originalContent = originalFile.content;
121
- if (context?.toolCallId) {
122
- previewContentHashCache.set(
123
- context.toolCallId,
124
- computeContentHash(originalContent)
125
- );
126
- }
127
- return generateDiffPreview(file_path, originalContent, content);
128
- } catch (error) {
129
- if (error instanceof import_core.DextoRuntimeError && error.code === import_error_codes.FileSystemErrorCode.FILE_NOT_FOUND) {
130
- if (context?.toolCallId) {
131
- previewContentHashCache.set(context.toolCallId, FILE_NOT_EXISTS_MARKER);
62
+ ...(0, import_directory_approval.createDirectoryAccessApprovalHandlers)({
63
+ toolName: "write_file",
64
+ operation: "write",
65
+ inputSchema: WriteFileInputSchema,
66
+ getFileSystemService,
67
+ resolvePaths: (input, fileSystemService) => (0, import_directory_approval.resolveFilePath)(fileSystemService.getWorkingDirectory(), input.file_path)
68
+ }),
69
+ presentation: {
70
+ describeHeader: (input) => (0, import_core.createLocalToolCallHeader)({
71
+ title: "Write",
72
+ argsText: (0, import_core.truncateForHeader)(input.file_path, 140)
73
+ }),
74
+ /**
75
+ * Generate preview for approval UI - shows diff or file creation info
76
+ * Stores content hash for change detection in execute phase.
77
+ */
78
+ preview: async (input, context) => {
79
+ const { file_path, content } = input;
80
+ const resolvedFileSystemService = await getFileSystemService(context);
81
+ const { path: resolvedPath } = (0, import_directory_approval.resolveFilePath)(
82
+ resolvedFileSystemService.getWorkingDirectory(),
83
+ file_path
84
+ );
85
+ try {
86
+ let originalContent;
87
+ try {
88
+ const originalFile = await resolvedFileSystemService.readFile(resolvedPath);
89
+ originalContent = originalFile.content;
90
+ } catch (error) {
91
+ if (error instanceof import_core.DextoRuntimeError && error.code === import_error_codes.FileSystemErrorCode.INVALID_PATH) {
92
+ const originalFile = await resolvedFileSystemService.readFileForToolPreview(
93
+ resolvedPath
94
+ );
95
+ originalContent = originalFile.content;
96
+ } else {
97
+ throw error;
98
+ }
99
+ }
100
+ if (context.toolCallId) {
101
+ previewContentHashCache.set(
102
+ context.toolCallId,
103
+ computeContentHash(originalContent)
104
+ );
105
+ }
106
+ return generateDiffPreview(resolvedPath, originalContent, content);
107
+ } catch (error) {
108
+ if (error instanceof import_core.DextoRuntimeError && error.code === import_error_codes.FileSystemErrorCode.FILE_NOT_FOUND) {
109
+ if (context.toolCallId) {
110
+ previewContentHashCache.set(context.toolCallId, FILE_NOT_EXISTS_MARKER);
111
+ }
112
+ const lineCount = content.split("\n").length;
113
+ const preview = {
114
+ type: "file",
115
+ title: "Create file",
116
+ path: resolvedPath,
117
+ operation: "create",
118
+ size: Buffer.byteLength(content, "utf8"),
119
+ lineCount,
120
+ content
121
+ // Include content for approval preview
122
+ };
123
+ return preview;
132
124
  }
133
- const lineCount = content.split("\n").length;
134
- const preview = {
135
- type: "file",
136
- path: file_path,
137
- operation: "create",
138
- size: content.length,
139
- lineCount,
140
- content
141
- // Include content for approval preview
142
- };
143
- return preview;
125
+ throw error;
144
126
  }
145
- throw error;
146
127
  }
147
128
  },
148
- execute: async (input, context) => {
129
+ async execute(input, context) {
130
+ const resolvedFileSystemService = await getFileSystemService(context);
149
131
  const { file_path, content, create_dirs, encoding } = input;
132
+ const { path: resolvedPath } = (0, import_directory_approval.resolveFilePath)(
133
+ resolvedFileSystemService.getWorkingDirectory(),
134
+ file_path
135
+ );
150
136
  let originalContent = null;
151
137
  let fileExistsNow = false;
152
138
  try {
153
- const originalFile = await fileSystemService.readFile(file_path);
139
+ const originalFile = await resolvedFileSystemService.readFile(resolvedPath);
154
140
  originalContent = originalFile.content;
155
141
  fileExistsNow = true;
156
142
  } catch (error) {
@@ -161,24 +147,30 @@ function createWriteFileTool(options) {
161
147
  throw error;
162
148
  }
163
149
  }
164
- if (context?.toolCallId && previewContentHashCache.has(context.toolCallId)) {
150
+ if (context.toolCallId && previewContentHashCache.has(context.toolCallId)) {
165
151
  const expectedHash = previewContentHashCache.get(context.toolCallId);
166
152
  previewContentHashCache.delete(context.toolCallId);
167
153
  if (expectedHash === FILE_NOT_EXISTS_MARKER) {
168
154
  if (fileExistsNow) {
169
- throw import_core.ToolError.fileModifiedSincePreview("write_file", file_path);
155
+ throw import_core.ToolError.fileModifiedSincePreview("write_file", resolvedPath);
170
156
  }
171
157
  } else if (expectedHash !== null) {
172
158
  if (!fileExistsNow) {
173
- throw import_core.ToolError.fileModifiedSincePreview("write_file", file_path);
159
+ throw import_core.ToolError.fileModifiedSincePreview("write_file", resolvedPath);
160
+ }
161
+ if (originalContent === null) {
162
+ throw import_core.ToolError.executionFailed(
163
+ "write_file",
164
+ "Expected original file content when fileExistsNow is true"
165
+ );
174
166
  }
175
167
  const currentHash = computeContentHash(originalContent);
176
168
  if (expectedHash !== currentHash) {
177
- throw import_core.ToolError.fileModifiedSincePreview("write_file", file_path);
169
+ throw import_core.ToolError.fileModifiedSincePreview("write_file", resolvedPath);
178
170
  }
179
171
  }
180
172
  }
181
- const result = await fileSystemService.writeFile(file_path, content, {
173
+ const result = await resolvedFileSystemService.writeFile(resolvedPath, content, {
182
174
  createDirs: create_dirs,
183
175
  encoding
184
176
  });
@@ -187,13 +179,15 @@ function createWriteFileTool(options) {
187
179
  const lineCount = content.split("\n").length;
188
180
  _display = {
189
181
  type: "file",
190
- path: file_path,
182
+ title: "Create file",
183
+ path: resolvedPath,
191
184
  operation: "create",
192
185
  size: result.bytesWritten,
193
- lineCount
186
+ lineCount,
187
+ content
194
188
  };
195
189
  } else {
196
- _display = generateDiffPreview(file_path, originalContent, content);
190
+ _display = generateDiffPreview(resolvedPath, originalContent, content);
197
191
  }
198
192
  return {
199
193
  success: result.success,
@@ -203,7 +197,7 @@ function createWriteFileTool(options) {
203
197
  _display
204
198
  };
205
199
  }
206
- };
200
+ });
207
201
  }
208
202
  // Annotate the CommonJS export names for ESM import in node:
209
203
  0 && (module.exports = {
@@ -1,17 +1,30 @@
1
- import { InternalTool } from '@dexto/core';
2
- import { FileToolOptions } from './file-tool-types.js';
3
- import './filesystem-service.js';
4
- import './types.js';
5
-
6
1
  /**
7
2
  * Write File Tool
8
3
  *
9
4
  * Internal tool for writing content to files (requires approval)
10
5
  */
11
-
6
+ import { z } from 'zod';
7
+ import type { Tool } from '@dexto/core';
8
+ import type { FileSystemServiceGetter } from './file-tool-types.js';
9
+ declare const WriteFileInputSchema: z.ZodObject<{
10
+ file_path: z.ZodString;
11
+ content: z.ZodString;
12
+ create_dirs: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
13
+ encoding: z.ZodDefault<z.ZodOptional<z.ZodEnum<["utf-8", "ascii", "latin1", "utf16le"]>>>;
14
+ }, "strict", z.ZodTypeAny, {
15
+ content: string;
16
+ encoding: "ascii" | "utf-8" | "utf16le" | "latin1";
17
+ file_path: string;
18
+ create_dirs: boolean;
19
+ }, {
20
+ content: string;
21
+ file_path: string;
22
+ encoding?: "ascii" | "utf-8" | "utf16le" | "latin1" | undefined;
23
+ create_dirs?: boolean | undefined;
24
+ }>;
12
25
  /**
13
26
  * Create the write_file internal tool with directory approval support
14
27
  */
15
- declare function createWriteFileTool(options: FileToolOptions): InternalTool;
16
-
17
- export { createWriteFileTool };
28
+ export declare function createWriteFileTool(getFileSystemService: FileSystemServiceGetter): Tool<typeof WriteFileInputSchema>;
29
+ export {};
30
+ //# sourceMappingURL=write-file-tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write-file-tool.d.ts","sourceRoot":"","sources":["../src/write-file-tool.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAUxB,OAAO,KAAK,EAAE,IAAI,EAAwB,MAAM,aAAa,CAAC;AAG9D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAuBpE,QAAA,MAAM,oBAAoB;;;;;;;;;;;;;;;EAeb,CAAC;AA0Bd;;GAEG;AACH,wBAAgB,mBAAmB,CAC/B,oBAAoB,EAAE,uBAAuB,GAC9C,IAAI,CAAC,OAAO,oBAAoB,CAAC,CAqMnC"}
@@ -1,20 +1,22 @@
1
- import * as path from "node:path";
2
1
  import { createHash } from "node:crypto";
3
2
  import { z } from "zod";
4
3
  import { createPatch } from "diff";
5
4
  import {
5
+ createLocalToolCallHeader,
6
6
  DextoRuntimeError,
7
- ApprovalType,
8
- ToolError
7
+ ToolError,
8
+ defineTool,
9
+ truncateForHeader
9
10
  } from "@dexto/core";
10
11
  import { FileSystemErrorCode } from "./error-codes.js";
12
+ import { createDirectoryAccessApprovalHandlers, resolveFilePath } from "./directory-approval.js";
11
13
  const previewContentHashCache = /* @__PURE__ */ new Map();
12
14
  const FILE_NOT_EXISTS_MARKER = null;
13
15
  function computeContentHash(content) {
14
16
  return createHash("sha256").update(content, "utf8").digest("hex");
15
17
  }
16
18
  const WriteFileInputSchema = z.object({
17
- file_path: z.string().describe("Absolute path where the file should be written"),
19
+ file_path: z.string().min(1).describe("Absolute path where the file should be written"),
18
20
  content: z.string().describe("Content to write to the file"),
19
21
  create_dirs: z.boolean().optional().default(false).describe("Create parent directories if they don't exist (default: false)"),
20
22
  encoding: z.enum(["utf-8", "ascii", "latin1", "utf16le"]).optional().default("utf-8").describe("File encoding (default: utf-8)")
@@ -27,101 +29,97 @@ function generateDiffPreview(filePath, originalContent, newContent) {
27
29
  const deletions = (unified.match(/^-[^-]/gm) || []).length;
28
30
  return {
29
31
  type: "diff",
32
+ title: "Update file",
30
33
  unified,
31
34
  filename: filePath,
32
35
  additions,
33
36
  deletions
34
37
  };
35
38
  }
36
- function createWriteFileTool(options) {
37
- const { fileSystemService, directoryApproval } = options;
38
- let pendingApprovalParentDir;
39
- return {
39
+ function createWriteFileTool(getFileSystemService) {
40
+ return defineTool({
40
41
  id: "write_file",
42
+ aliases: ["write"],
41
43
  description: "Write content to a file. Creates a new file or overwrites existing file. Automatically creates backup of existing files before overwriting. Use create_dirs to create parent directories. Requires approval for all write operations. Returns success status, path, bytes written, and backup path if applicable.",
42
44
  inputSchema: WriteFileInputSchema,
43
- /**
44
- * Check if this write operation needs directory access approval.
45
- * Returns custom approval request if the file is outside allowed paths.
46
- */
47
- getApprovalOverride: async (args) => {
48
- const { file_path } = args;
49
- if (!file_path) return null;
50
- const isAllowed = await fileSystemService.isPathWithinConfigAllowed(file_path);
51
- if (isAllowed) {
52
- return null;
53
- }
54
- if (directoryApproval?.isSessionApproved(file_path)) {
55
- return null;
56
- }
57
- const absolutePath = path.resolve(file_path);
58
- const parentDir = path.dirname(absolutePath);
59
- pendingApprovalParentDir = parentDir;
60
- return {
61
- type: ApprovalType.DIRECTORY_ACCESS,
62
- metadata: {
63
- path: absolutePath,
64
- parentDir,
65
- operation: "write",
66
- toolName: "write_file"
67
- }
68
- };
69
- },
70
- /**
71
- * Handle approved directory access - remember the directory for session
72
- */
73
- onApprovalGranted: (response) => {
74
- if (!directoryApproval || !pendingApprovalParentDir) return;
75
- const data = response.data;
76
- const rememberDirectory = data?.rememberDirectory ?? false;
77
- directoryApproval.addApproved(
78
- pendingApprovalParentDir,
79
- rememberDirectory ? "session" : "once"
80
- );
81
- pendingApprovalParentDir = void 0;
82
- },
83
- /**
84
- * Generate preview for approval UI - shows diff or file creation info
85
- * Stores content hash for change detection in execute phase.
86
- */
87
- generatePreview: async (input, context) => {
88
- const { file_path, content } = input;
89
- try {
90
- const originalFile = await fileSystemService.readFile(file_path);
91
- const originalContent = originalFile.content;
92
- if (context?.toolCallId) {
93
- previewContentHashCache.set(
94
- context.toolCallId,
95
- computeContentHash(originalContent)
96
- );
97
- }
98
- return generateDiffPreview(file_path, originalContent, content);
99
- } catch (error) {
100
- if (error instanceof DextoRuntimeError && error.code === FileSystemErrorCode.FILE_NOT_FOUND) {
101
- if (context?.toolCallId) {
102
- previewContentHashCache.set(context.toolCallId, FILE_NOT_EXISTS_MARKER);
45
+ ...createDirectoryAccessApprovalHandlers({
46
+ toolName: "write_file",
47
+ operation: "write",
48
+ inputSchema: WriteFileInputSchema,
49
+ getFileSystemService,
50
+ resolvePaths: (input, fileSystemService) => resolveFilePath(fileSystemService.getWorkingDirectory(), input.file_path)
51
+ }),
52
+ presentation: {
53
+ describeHeader: (input) => createLocalToolCallHeader({
54
+ title: "Write",
55
+ argsText: truncateForHeader(input.file_path, 140)
56
+ }),
57
+ /**
58
+ * Generate preview for approval UI - shows diff or file creation info
59
+ * Stores content hash for change detection in execute phase.
60
+ */
61
+ preview: async (input, context) => {
62
+ const { file_path, content } = input;
63
+ const resolvedFileSystemService = await getFileSystemService(context);
64
+ const { path: resolvedPath } = resolveFilePath(
65
+ resolvedFileSystemService.getWorkingDirectory(),
66
+ file_path
67
+ );
68
+ try {
69
+ let originalContent;
70
+ try {
71
+ const originalFile = await resolvedFileSystemService.readFile(resolvedPath);
72
+ originalContent = originalFile.content;
73
+ } catch (error) {
74
+ if (error instanceof DextoRuntimeError && error.code === FileSystemErrorCode.INVALID_PATH) {
75
+ const originalFile = await resolvedFileSystemService.readFileForToolPreview(
76
+ resolvedPath
77
+ );
78
+ originalContent = originalFile.content;
79
+ } else {
80
+ throw error;
81
+ }
82
+ }
83
+ if (context.toolCallId) {
84
+ previewContentHashCache.set(
85
+ context.toolCallId,
86
+ computeContentHash(originalContent)
87
+ );
88
+ }
89
+ return generateDiffPreview(resolvedPath, originalContent, content);
90
+ } catch (error) {
91
+ if (error instanceof DextoRuntimeError && error.code === FileSystemErrorCode.FILE_NOT_FOUND) {
92
+ if (context.toolCallId) {
93
+ previewContentHashCache.set(context.toolCallId, FILE_NOT_EXISTS_MARKER);
94
+ }
95
+ const lineCount = content.split("\n").length;
96
+ const preview = {
97
+ type: "file",
98
+ title: "Create file",
99
+ path: resolvedPath,
100
+ operation: "create",
101
+ size: Buffer.byteLength(content, "utf8"),
102
+ lineCount,
103
+ content
104
+ // Include content for approval preview
105
+ };
106
+ return preview;
103
107
  }
104
- const lineCount = content.split("\n").length;
105
- const preview = {
106
- type: "file",
107
- path: file_path,
108
- operation: "create",
109
- size: content.length,
110
- lineCount,
111
- content
112
- // Include content for approval preview
113
- };
114
- return preview;
108
+ throw error;
115
109
  }
116
- throw error;
117
110
  }
118
111
  },
119
- execute: async (input, context) => {
112
+ async execute(input, context) {
113
+ const resolvedFileSystemService = await getFileSystemService(context);
120
114
  const { file_path, content, create_dirs, encoding } = input;
115
+ const { path: resolvedPath } = resolveFilePath(
116
+ resolvedFileSystemService.getWorkingDirectory(),
117
+ file_path
118
+ );
121
119
  let originalContent = null;
122
120
  let fileExistsNow = false;
123
121
  try {
124
- const originalFile = await fileSystemService.readFile(file_path);
122
+ const originalFile = await resolvedFileSystemService.readFile(resolvedPath);
125
123
  originalContent = originalFile.content;
126
124
  fileExistsNow = true;
127
125
  } catch (error) {
@@ -132,24 +130,30 @@ function createWriteFileTool(options) {
132
130
  throw error;
133
131
  }
134
132
  }
135
- if (context?.toolCallId && previewContentHashCache.has(context.toolCallId)) {
133
+ if (context.toolCallId && previewContentHashCache.has(context.toolCallId)) {
136
134
  const expectedHash = previewContentHashCache.get(context.toolCallId);
137
135
  previewContentHashCache.delete(context.toolCallId);
138
136
  if (expectedHash === FILE_NOT_EXISTS_MARKER) {
139
137
  if (fileExistsNow) {
140
- throw ToolError.fileModifiedSincePreview("write_file", file_path);
138
+ throw ToolError.fileModifiedSincePreview("write_file", resolvedPath);
141
139
  }
142
140
  } else if (expectedHash !== null) {
143
141
  if (!fileExistsNow) {
144
- throw ToolError.fileModifiedSincePreview("write_file", file_path);
142
+ throw ToolError.fileModifiedSincePreview("write_file", resolvedPath);
143
+ }
144
+ if (originalContent === null) {
145
+ throw ToolError.executionFailed(
146
+ "write_file",
147
+ "Expected original file content when fileExistsNow is true"
148
+ );
145
149
  }
146
150
  const currentHash = computeContentHash(originalContent);
147
151
  if (expectedHash !== currentHash) {
148
- throw ToolError.fileModifiedSincePreview("write_file", file_path);
152
+ throw ToolError.fileModifiedSincePreview("write_file", resolvedPath);
149
153
  }
150
154
  }
151
155
  }
152
- const result = await fileSystemService.writeFile(file_path, content, {
156
+ const result = await resolvedFileSystemService.writeFile(resolvedPath, content, {
153
157
  createDirs: create_dirs,
154
158
  encoding
155
159
  });
@@ -158,13 +162,15 @@ function createWriteFileTool(options) {
158
162
  const lineCount = content.split("\n").length;
159
163
  _display = {
160
164
  type: "file",
161
- path: file_path,
165
+ title: "Create file",
166
+ path: resolvedPath,
162
167
  operation: "create",
163
168
  size: result.bytesWritten,
164
- lineCount
169
+ lineCount,
170
+ content
165
171
  };
166
172
  } else {
167
- _display = generateDiffPreview(file_path, originalContent, content);
173
+ _display = generateDiffPreview(resolvedPath, originalContent, content);
168
174
  }
169
175
  return {
170
176
  success: result.success,
@@ -174,7 +180,7 @@ function createWriteFileTool(options) {
174
180
  _display
175
181
  };
176
182
  }
177
- };
183
+ });
178
184
  }
179
185
  export {
180
186
  createWriteFileTool