@dexto/tools-filesystem 1.5.2 → 1.5.3

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 (37) hide show
  1. package/dist/directory-approval.integration.test.cjs +36 -32
  2. package/dist/directory-approval.integration.test.js +36 -32
  3. package/dist/edit-file-tool.cjs +43 -19
  4. package/dist/edit-file-tool.js +43 -19
  5. package/dist/edit-file-tool.test.cjs +203 -0
  6. package/dist/edit-file-tool.test.d.cts +2 -0
  7. package/dist/edit-file-tool.test.d.ts +2 -0
  8. package/dist/edit-file-tool.test.js +180 -0
  9. package/dist/filesystem-service.cjs +17 -14
  10. package/dist/filesystem-service.d.cts +3 -3
  11. package/dist/filesystem-service.d.ts +3 -3
  12. package/dist/filesystem-service.js +17 -14
  13. package/dist/filesystem-service.test.cjs +233 -0
  14. package/dist/filesystem-service.test.d.cts +2 -0
  15. package/dist/filesystem-service.test.d.ts +2 -0
  16. package/dist/filesystem-service.test.js +210 -0
  17. package/dist/path-validator.cjs +29 -20
  18. package/dist/path-validator.d.cts +9 -2
  19. package/dist/path-validator.d.ts +9 -2
  20. package/dist/path-validator.js +29 -20
  21. package/dist/path-validator.test.cjs +54 -48
  22. package/dist/path-validator.test.js +54 -48
  23. package/dist/read-file-tool.cjs +2 -2
  24. package/dist/read-file-tool.js +2 -2
  25. package/dist/tool-provider.cjs +22 -7
  26. package/dist/tool-provider.d.cts +4 -1
  27. package/dist/tool-provider.d.ts +4 -1
  28. package/dist/tool-provider.js +22 -7
  29. package/dist/types.d.cts +6 -0
  30. package/dist/types.d.ts +6 -0
  31. package/dist/write-file-tool.cjs +41 -7
  32. package/dist/write-file-tool.js +46 -8
  33. package/dist/write-file-tool.test.cjs +217 -0
  34. package/dist/write-file-tool.test.d.cts +2 -0
  35. package/dist/write-file-tool.test.d.ts +2 -0
  36. package/dist/write-file-tool.test.js +194 -0
  37. package/package.json +2 -2
@@ -84,7 +84,7 @@ const createMockLogger = () => ({
84
84
  });
85
85
  const testFile = path.join(tempDir, "test.txt");
86
86
  await fs.writeFile(testFile, "test content");
87
- const override = tool.getApprovalOverride?.({ file_path: testFile });
87
+ const override = await tool.getApprovalOverride?.({ file_path: testFile });
88
88
  (0, import_vitest.expect)(override).toBeNull();
89
89
  });
90
90
  (0, import_vitest.it)("should return directory access approval for external paths", async () => {
@@ -93,7 +93,7 @@ const createMockLogger = () => ({
93
93
  directoryApproval
94
94
  });
95
95
  const externalPath = "/external/project/file.ts";
96
- const override = tool.getApprovalOverride?.({ file_path: externalPath });
96
+ const override = await tool.getApprovalOverride?.({ file_path: externalPath });
97
97
  (0, import_vitest.expect)(override).not.toBeNull();
98
98
  (0, import_vitest.expect)(override?.type).toBe(import_core.ApprovalType.DIRECTORY_ACCESS);
99
99
  const metadata = override?.metadata;
@@ -109,7 +109,7 @@ const createMockLogger = () => ({
109
109
  directoryApproval
110
110
  });
111
111
  const externalPath = "/external/project/file.ts";
112
- const override = tool.getApprovalOverride?.({ file_path: externalPath });
112
+ const override = await tool.getApprovalOverride?.({ file_path: externalPath });
113
113
  (0, import_vitest.expect)(override).toBeNull();
114
114
  (0, import_vitest.expect)(isSessionApprovedMock).toHaveBeenCalledWith(externalPath);
115
115
  });
@@ -118,7 +118,7 @@ const createMockLogger = () => ({
118
118
  fileSystemService,
119
119
  directoryApproval
120
120
  });
121
- const override = tool.getApprovalOverride?.({});
121
+ const override = await tool.getApprovalOverride?.({});
122
122
  (0, import_vitest.expect)(override).toBeNull();
123
123
  });
124
124
  });
@@ -129,7 +129,7 @@ const createMockLogger = () => ({
129
129
  directoryApproval
130
130
  });
131
131
  const externalPath = "/external/project/file.ts";
132
- tool.getApprovalOverride?.({ file_path: externalPath });
132
+ await tool.getApprovalOverride?.({ file_path: externalPath });
133
133
  tool.onApprovalGranted?.({
134
134
  approvalId: "test-approval",
135
135
  status: import_core.ApprovalStatus.APPROVED,
@@ -146,7 +146,7 @@ const createMockLogger = () => ({
146
146
  directoryApproval
147
147
  });
148
148
  const externalPath = "/external/project/file.ts";
149
- tool.getApprovalOverride?.({ file_path: externalPath });
149
+ await tool.getApprovalOverride?.({ file_path: externalPath });
150
150
  tool.onApprovalGranted?.({
151
151
  approvalId: "test-approval",
152
152
  status: import_core.ApprovalStatus.APPROVED,
@@ -163,7 +163,7 @@ const createMockLogger = () => ({
163
163
  directoryApproval
164
164
  });
165
165
  const externalPath = "/external/project/file.ts";
166
- tool.getApprovalOverride?.({ file_path: externalPath });
166
+ await tool.getApprovalOverride?.({ file_path: externalPath });
167
167
  tool.onApprovalGranted?.({
168
168
  approvalId: "test-approval",
169
169
  status: import_core.ApprovalStatus.APPROVED,
@@ -180,7 +180,7 @@ const createMockLogger = () => ({
180
180
  directoryApproval: void 0
181
181
  });
182
182
  const externalPath = "/external/project/file.ts";
183
- tool.getApprovalOverride?.({ file_path: externalPath });
183
+ await tool.getApprovalOverride?.({ file_path: externalPath });
184
184
  tool.onApprovalGranted?.({
185
185
  approvalId: "test-approval",
186
186
  status: import_core.ApprovalStatus.APPROVED,
@@ -211,7 +211,7 @@ const createMockLogger = () => ({
211
211
  directoryApproval
212
212
  });
213
213
  const testFile = path.join(tempDir, "new-file.txt");
214
- const override = tool.getApprovalOverride?.({
214
+ const override = await tool.getApprovalOverride?.({
215
215
  file_path: testFile,
216
216
  content: "test"
217
217
  });
@@ -223,7 +223,7 @@ const createMockLogger = () => ({
223
223
  directoryApproval
224
224
  });
225
225
  const externalPath = "/external/project/new.ts";
226
- const override = tool.getApprovalOverride?.({
226
+ const override = await tool.getApprovalOverride?.({
227
227
  file_path: externalPath,
228
228
  content: "test"
229
229
  });
@@ -240,7 +240,7 @@ const createMockLogger = () => ({
240
240
  directoryApproval
241
241
  });
242
242
  const externalPath = "/external/project/new.ts";
243
- const override = tool.getApprovalOverride?.({
243
+ const override = await tool.getApprovalOverride?.({
244
244
  file_path: externalPath,
245
245
  content: "test"
246
246
  });
@@ -254,7 +254,7 @@ const createMockLogger = () => ({
254
254
  directoryApproval
255
255
  });
256
256
  const externalPath = "/external/project/new.ts";
257
- tool.getApprovalOverride?.({ file_path: externalPath, content: "test" });
257
+ await tool.getApprovalOverride?.({ file_path: externalPath, content: "test" });
258
258
  tool.onApprovalGranted?.({
259
259
  approvalId: "test-approval",
260
260
  status: import_core.ApprovalStatus.APPROVED,
@@ -275,7 +275,7 @@ const createMockLogger = () => ({
275
275
  directoryApproval
276
276
  });
277
277
  const testFile = path.join(tempDir, "existing.txt");
278
- const override = tool.getApprovalOverride?.({
278
+ const override = await tool.getApprovalOverride?.({
279
279
  file_path: testFile,
280
280
  old_string: "old",
281
281
  new_string: "new"
@@ -288,7 +288,7 @@ const createMockLogger = () => ({
288
288
  directoryApproval
289
289
  });
290
290
  const externalPath = "/external/project/existing.ts";
291
- const override = tool.getApprovalOverride?.({
291
+ const override = await tool.getApprovalOverride?.({
292
292
  file_path: externalPath,
293
293
  old_string: "old",
294
294
  new_string: "new"
@@ -306,7 +306,7 @@ const createMockLogger = () => ({
306
306
  directoryApproval
307
307
  });
308
308
  const externalPath = "/external/project/existing.ts";
309
- const override = tool.getApprovalOverride?.({
309
+ const override = await tool.getApprovalOverride?.({
310
310
  file_path: externalPath,
311
311
  old_string: "old",
312
312
  new_string: "new"
@@ -323,7 +323,7 @@ const createMockLogger = () => ({
323
323
  });
324
324
  const externalPath1 = "/external/project/file1.ts";
325
325
  const externalPath2 = "/external/project/file2.ts";
326
- let override = tool.getApprovalOverride?.({ file_path: externalPath1 });
326
+ let override = await tool.getApprovalOverride?.({ file_path: externalPath1 });
327
327
  (0, import_vitest.expect)(override).not.toBeNull();
328
328
  tool.onApprovalGranted?.({
329
329
  approvalId: "approval-1",
@@ -335,7 +335,7 @@ const createMockLogger = () => ({
335
335
  "session"
336
336
  );
337
337
  isSessionApprovedMock.mockReturnValue(true);
338
- override = tool.getApprovalOverride?.({ file_path: externalPath2 });
338
+ override = await tool.getApprovalOverride?.({ file_path: externalPath2 });
339
339
  (0, import_vitest.expect)(override).toBeNull();
340
340
  });
341
341
  (0, import_vitest.it)("should prompt for subsequent requests after once approval", async () => {
@@ -345,7 +345,7 @@ const createMockLogger = () => ({
345
345
  });
346
346
  const externalPath1 = "/external/project/file1.ts";
347
347
  const externalPath2 = "/external/project/file2.ts";
348
- let override = tool.getApprovalOverride?.({ file_path: externalPath1 });
348
+ let override = await tool.getApprovalOverride?.({ file_path: externalPath1 });
349
349
  (0, import_vitest.expect)(override).not.toBeNull();
350
350
  tool.onApprovalGranted?.({
351
351
  approvalId: "approval-1",
@@ -357,7 +357,7 @@ const createMockLogger = () => ({
357
357
  "once"
358
358
  );
359
359
  isSessionApprovedMock.mockReturnValue(false);
360
- override = tool.getApprovalOverride?.({ file_path: externalPath2 });
360
+ override = await tool.getApprovalOverride?.({ file_path: externalPath2 });
361
361
  (0, import_vitest.expect)(override).not.toBeNull();
362
362
  });
363
363
  });
@@ -372,9 +372,11 @@ const createMockLogger = () => ({
372
372
  const approvedDir = "/external/project";
373
373
  return normalizedPath.startsWith(approvedDir + path.sep) || normalizedPath === approvedDir;
374
374
  });
375
- let override = tool.getApprovalOverride?.({ file_path: "/external/project/file.ts" });
375
+ let override = await tool.getApprovalOverride?.({
376
+ file_path: "/external/project/file.ts"
377
+ });
376
378
  (0, import_vitest.expect)(override).toBeNull();
377
- override = tool.getApprovalOverride?.({
379
+ override = await tool.getApprovalOverride?.({
378
380
  file_path: "/external/project/deep/nested/file.ts"
379
381
  });
380
382
  (0, import_vitest.expect)(override).toBeNull();
@@ -389,9 +391,9 @@ const createMockLogger = () => ({
389
391
  const approvedDir = "/external/sub";
390
392
  return normalizedPath.startsWith(approvedDir + path.sep) || normalizedPath === approvedDir;
391
393
  });
392
- let override = tool.getApprovalOverride?.({ file_path: "/external/sub/file.ts" });
394
+ let override = await tool.getApprovalOverride?.({ file_path: "/external/sub/file.ts" });
393
395
  (0, import_vitest.expect)(override).toBeNull();
394
- override = tool.getApprovalOverride?.({ file_path: "/external/other/file.ts" });
396
+ override = await tool.getApprovalOverride?.({ file_path: "/external/other/file.ts" });
395
397
  (0, import_vitest.expect)(override).not.toBeNull();
396
398
  });
397
399
  });
@@ -403,11 +405,11 @@ const createMockLogger = () => ({
403
405
  });
404
406
  const dir1Path = "/external/project1/file.ts";
405
407
  const dir2Path = "/external/project2/file.ts";
406
- const override1 = tool.getApprovalOverride?.({ file_path: dir1Path });
408
+ const override1 = await tool.getApprovalOverride?.({ file_path: dir1Path });
407
409
  (0, import_vitest.expect)(override1).not.toBeNull();
408
410
  const metadata1 = override1?.metadata;
409
411
  (0, import_vitest.expect)(metadata1?.parentDir).toBe("/external/project1");
410
- const override2 = tool.getApprovalOverride?.({ file_path: dir2Path });
412
+ const override2 = await tool.getApprovalOverride?.({ file_path: dir2Path });
411
413
  (0, import_vitest.expect)(override2).not.toBeNull();
412
414
  const metadata2 = override2?.metadata;
413
415
  (0, import_vitest.expect)(metadata2?.parentDir).toBe("/external/project2");
@@ -420,16 +422,16 @@ const createMockLogger = () => ({
420
422
  const editTool = (0, import_edit_file_tool.createEditFileTool)({ fileSystemService, directoryApproval });
421
423
  const externalDir = "/external/project";
422
424
  (0, import_vitest.expect)(
423
- readTool.getApprovalOverride?.({ file_path: `${externalDir}/file1.ts` })
425
+ await readTool.getApprovalOverride?.({ file_path: `${externalDir}/file1.ts` })
424
426
  ).not.toBeNull();
425
427
  (0, import_vitest.expect)(
426
- writeTool.getApprovalOverride?.({
428
+ await writeTool.getApprovalOverride?.({
427
429
  file_path: `${externalDir}/file2.ts`,
428
430
  content: "test"
429
431
  })
430
432
  ).not.toBeNull();
431
433
  (0, import_vitest.expect)(
432
- editTool.getApprovalOverride?.({
434
+ await editTool.getApprovalOverride?.({
433
435
  file_path: `${externalDir}/file3.ts`,
434
436
  old_string: "a",
435
437
  new_string: "b"
@@ -437,16 +439,16 @@ const createMockLogger = () => ({
437
439
  ).not.toBeNull();
438
440
  isSessionApprovedMock.mockReturnValue(true);
439
441
  (0, import_vitest.expect)(
440
- readTool.getApprovalOverride?.({ file_path: `${externalDir}/file1.ts` })
442
+ await readTool.getApprovalOverride?.({ file_path: `${externalDir}/file1.ts` })
441
443
  ).toBeNull();
442
444
  (0, import_vitest.expect)(
443
- writeTool.getApprovalOverride?.({
445
+ await writeTool.getApprovalOverride?.({
444
446
  file_path: `${externalDir}/file2.ts`,
445
447
  content: "test"
446
448
  })
447
449
  ).toBeNull();
448
450
  (0, import_vitest.expect)(
449
- editTool.getApprovalOverride?.({
451
+ await editTool.getApprovalOverride?.({
450
452
  file_path: `${externalDir}/file3.ts`,
451
453
  old_string: "a",
452
454
  new_string: "b"
@@ -460,7 +462,9 @@ const createMockLogger = () => ({
460
462
  fileSystemService,
461
463
  directoryApproval: void 0
462
464
  });
463
- const override = tool.getApprovalOverride?.({ file_path: "/external/project/file.ts" });
465
+ const override = await tool.getApprovalOverride?.({
466
+ file_path: "/external/project/file.ts"
467
+ });
464
468
  (0, import_vitest.expect)(override).not.toBeNull();
465
469
  });
466
470
  });
@@ -61,7 +61,7 @@ describe("Directory Approval Integration Tests", () => {
61
61
  });
62
62
  const testFile = path.join(tempDir, "test.txt");
63
63
  await fs.writeFile(testFile, "test content");
64
- const override = tool.getApprovalOverride?.({ file_path: testFile });
64
+ const override = await tool.getApprovalOverride?.({ file_path: testFile });
65
65
  expect(override).toBeNull();
66
66
  });
67
67
  it("should return directory access approval for external paths", async () => {
@@ -70,7 +70,7 @@ describe("Directory Approval Integration Tests", () => {
70
70
  directoryApproval
71
71
  });
72
72
  const externalPath = "/external/project/file.ts";
73
- const override = tool.getApprovalOverride?.({ file_path: externalPath });
73
+ const override = await tool.getApprovalOverride?.({ file_path: externalPath });
74
74
  expect(override).not.toBeNull();
75
75
  expect(override?.type).toBe(ApprovalType.DIRECTORY_ACCESS);
76
76
  const metadata = override?.metadata;
@@ -86,7 +86,7 @@ describe("Directory Approval Integration Tests", () => {
86
86
  directoryApproval
87
87
  });
88
88
  const externalPath = "/external/project/file.ts";
89
- const override = tool.getApprovalOverride?.({ file_path: externalPath });
89
+ const override = await tool.getApprovalOverride?.({ file_path: externalPath });
90
90
  expect(override).toBeNull();
91
91
  expect(isSessionApprovedMock).toHaveBeenCalledWith(externalPath);
92
92
  });
@@ -95,7 +95,7 @@ describe("Directory Approval Integration Tests", () => {
95
95
  fileSystemService,
96
96
  directoryApproval
97
97
  });
98
- const override = tool.getApprovalOverride?.({});
98
+ const override = await tool.getApprovalOverride?.({});
99
99
  expect(override).toBeNull();
100
100
  });
101
101
  });
@@ -106,7 +106,7 @@ describe("Directory Approval Integration Tests", () => {
106
106
  directoryApproval
107
107
  });
108
108
  const externalPath = "/external/project/file.ts";
109
- tool.getApprovalOverride?.({ file_path: externalPath });
109
+ await tool.getApprovalOverride?.({ file_path: externalPath });
110
110
  tool.onApprovalGranted?.({
111
111
  approvalId: "test-approval",
112
112
  status: ApprovalStatus.APPROVED,
@@ -123,7 +123,7 @@ describe("Directory Approval Integration Tests", () => {
123
123
  directoryApproval
124
124
  });
125
125
  const externalPath = "/external/project/file.ts";
126
- tool.getApprovalOverride?.({ file_path: externalPath });
126
+ await tool.getApprovalOverride?.({ file_path: externalPath });
127
127
  tool.onApprovalGranted?.({
128
128
  approvalId: "test-approval",
129
129
  status: ApprovalStatus.APPROVED,
@@ -140,7 +140,7 @@ describe("Directory Approval Integration Tests", () => {
140
140
  directoryApproval
141
141
  });
142
142
  const externalPath = "/external/project/file.ts";
143
- tool.getApprovalOverride?.({ file_path: externalPath });
143
+ await tool.getApprovalOverride?.({ file_path: externalPath });
144
144
  tool.onApprovalGranted?.({
145
145
  approvalId: "test-approval",
146
146
  status: ApprovalStatus.APPROVED,
@@ -157,7 +157,7 @@ describe("Directory Approval Integration Tests", () => {
157
157
  directoryApproval: void 0
158
158
  });
159
159
  const externalPath = "/external/project/file.ts";
160
- tool.getApprovalOverride?.({ file_path: externalPath });
160
+ await tool.getApprovalOverride?.({ file_path: externalPath });
161
161
  tool.onApprovalGranted?.({
162
162
  approvalId: "test-approval",
163
163
  status: ApprovalStatus.APPROVED,
@@ -188,7 +188,7 @@ describe("Directory Approval Integration Tests", () => {
188
188
  directoryApproval
189
189
  });
190
190
  const testFile = path.join(tempDir, "new-file.txt");
191
- const override = tool.getApprovalOverride?.({
191
+ const override = await tool.getApprovalOverride?.({
192
192
  file_path: testFile,
193
193
  content: "test"
194
194
  });
@@ -200,7 +200,7 @@ describe("Directory Approval Integration Tests", () => {
200
200
  directoryApproval
201
201
  });
202
202
  const externalPath = "/external/project/new.ts";
203
- const override = tool.getApprovalOverride?.({
203
+ const override = await tool.getApprovalOverride?.({
204
204
  file_path: externalPath,
205
205
  content: "test"
206
206
  });
@@ -217,7 +217,7 @@ describe("Directory Approval Integration Tests", () => {
217
217
  directoryApproval
218
218
  });
219
219
  const externalPath = "/external/project/new.ts";
220
- const override = tool.getApprovalOverride?.({
220
+ const override = await tool.getApprovalOverride?.({
221
221
  file_path: externalPath,
222
222
  content: "test"
223
223
  });
@@ -231,7 +231,7 @@ describe("Directory Approval Integration Tests", () => {
231
231
  directoryApproval
232
232
  });
233
233
  const externalPath = "/external/project/new.ts";
234
- tool.getApprovalOverride?.({ file_path: externalPath, content: "test" });
234
+ await tool.getApprovalOverride?.({ file_path: externalPath, content: "test" });
235
235
  tool.onApprovalGranted?.({
236
236
  approvalId: "test-approval",
237
237
  status: ApprovalStatus.APPROVED,
@@ -252,7 +252,7 @@ describe("Directory Approval Integration Tests", () => {
252
252
  directoryApproval
253
253
  });
254
254
  const testFile = path.join(tempDir, "existing.txt");
255
- const override = tool.getApprovalOverride?.({
255
+ const override = await tool.getApprovalOverride?.({
256
256
  file_path: testFile,
257
257
  old_string: "old",
258
258
  new_string: "new"
@@ -265,7 +265,7 @@ describe("Directory Approval Integration Tests", () => {
265
265
  directoryApproval
266
266
  });
267
267
  const externalPath = "/external/project/existing.ts";
268
- const override = tool.getApprovalOverride?.({
268
+ const override = await tool.getApprovalOverride?.({
269
269
  file_path: externalPath,
270
270
  old_string: "old",
271
271
  new_string: "new"
@@ -283,7 +283,7 @@ describe("Directory Approval Integration Tests", () => {
283
283
  directoryApproval
284
284
  });
285
285
  const externalPath = "/external/project/existing.ts";
286
- const override = tool.getApprovalOverride?.({
286
+ const override = await tool.getApprovalOverride?.({
287
287
  file_path: externalPath,
288
288
  old_string: "old",
289
289
  new_string: "new"
@@ -300,7 +300,7 @@ describe("Directory Approval Integration Tests", () => {
300
300
  });
301
301
  const externalPath1 = "/external/project/file1.ts";
302
302
  const externalPath2 = "/external/project/file2.ts";
303
- let override = tool.getApprovalOverride?.({ file_path: externalPath1 });
303
+ let override = await tool.getApprovalOverride?.({ file_path: externalPath1 });
304
304
  expect(override).not.toBeNull();
305
305
  tool.onApprovalGranted?.({
306
306
  approvalId: "approval-1",
@@ -312,7 +312,7 @@ describe("Directory Approval Integration Tests", () => {
312
312
  "session"
313
313
  );
314
314
  isSessionApprovedMock.mockReturnValue(true);
315
- override = tool.getApprovalOverride?.({ file_path: externalPath2 });
315
+ override = await tool.getApprovalOverride?.({ file_path: externalPath2 });
316
316
  expect(override).toBeNull();
317
317
  });
318
318
  it("should prompt for subsequent requests after once approval", async () => {
@@ -322,7 +322,7 @@ describe("Directory Approval Integration Tests", () => {
322
322
  });
323
323
  const externalPath1 = "/external/project/file1.ts";
324
324
  const externalPath2 = "/external/project/file2.ts";
325
- let override = tool.getApprovalOverride?.({ file_path: externalPath1 });
325
+ let override = await tool.getApprovalOverride?.({ file_path: externalPath1 });
326
326
  expect(override).not.toBeNull();
327
327
  tool.onApprovalGranted?.({
328
328
  approvalId: "approval-1",
@@ -334,7 +334,7 @@ describe("Directory Approval Integration Tests", () => {
334
334
  "once"
335
335
  );
336
336
  isSessionApprovedMock.mockReturnValue(false);
337
- override = tool.getApprovalOverride?.({ file_path: externalPath2 });
337
+ override = await tool.getApprovalOverride?.({ file_path: externalPath2 });
338
338
  expect(override).not.toBeNull();
339
339
  });
340
340
  });
@@ -349,9 +349,11 @@ describe("Directory Approval Integration Tests", () => {
349
349
  const approvedDir = "/external/project";
350
350
  return normalizedPath.startsWith(approvedDir + path.sep) || normalizedPath === approvedDir;
351
351
  });
352
- let override = tool.getApprovalOverride?.({ file_path: "/external/project/file.ts" });
352
+ let override = await tool.getApprovalOverride?.({
353
+ file_path: "/external/project/file.ts"
354
+ });
353
355
  expect(override).toBeNull();
354
- override = tool.getApprovalOverride?.({
356
+ override = await tool.getApprovalOverride?.({
355
357
  file_path: "/external/project/deep/nested/file.ts"
356
358
  });
357
359
  expect(override).toBeNull();
@@ -366,9 +368,9 @@ describe("Directory Approval Integration Tests", () => {
366
368
  const approvedDir = "/external/sub";
367
369
  return normalizedPath.startsWith(approvedDir + path.sep) || normalizedPath === approvedDir;
368
370
  });
369
- let override = tool.getApprovalOverride?.({ file_path: "/external/sub/file.ts" });
371
+ let override = await tool.getApprovalOverride?.({ file_path: "/external/sub/file.ts" });
370
372
  expect(override).toBeNull();
371
- override = tool.getApprovalOverride?.({ file_path: "/external/other/file.ts" });
373
+ override = await tool.getApprovalOverride?.({ file_path: "/external/other/file.ts" });
372
374
  expect(override).not.toBeNull();
373
375
  });
374
376
  });
@@ -380,11 +382,11 @@ describe("Directory Approval Integration Tests", () => {
380
382
  });
381
383
  const dir1Path = "/external/project1/file.ts";
382
384
  const dir2Path = "/external/project2/file.ts";
383
- const override1 = tool.getApprovalOverride?.({ file_path: dir1Path });
385
+ const override1 = await tool.getApprovalOverride?.({ file_path: dir1Path });
384
386
  expect(override1).not.toBeNull();
385
387
  const metadata1 = override1?.metadata;
386
388
  expect(metadata1?.parentDir).toBe("/external/project1");
387
- const override2 = tool.getApprovalOverride?.({ file_path: dir2Path });
389
+ const override2 = await tool.getApprovalOverride?.({ file_path: dir2Path });
388
390
  expect(override2).not.toBeNull();
389
391
  const metadata2 = override2?.metadata;
390
392
  expect(metadata2?.parentDir).toBe("/external/project2");
@@ -397,16 +399,16 @@ describe("Directory Approval Integration Tests", () => {
397
399
  const editTool = createEditFileTool({ fileSystemService, directoryApproval });
398
400
  const externalDir = "/external/project";
399
401
  expect(
400
- readTool.getApprovalOverride?.({ file_path: `${externalDir}/file1.ts` })
402
+ await readTool.getApprovalOverride?.({ file_path: `${externalDir}/file1.ts` })
401
403
  ).not.toBeNull();
402
404
  expect(
403
- writeTool.getApprovalOverride?.({
405
+ await writeTool.getApprovalOverride?.({
404
406
  file_path: `${externalDir}/file2.ts`,
405
407
  content: "test"
406
408
  })
407
409
  ).not.toBeNull();
408
410
  expect(
409
- editTool.getApprovalOverride?.({
411
+ await editTool.getApprovalOverride?.({
410
412
  file_path: `${externalDir}/file3.ts`,
411
413
  old_string: "a",
412
414
  new_string: "b"
@@ -414,16 +416,16 @@ describe("Directory Approval Integration Tests", () => {
414
416
  ).not.toBeNull();
415
417
  isSessionApprovedMock.mockReturnValue(true);
416
418
  expect(
417
- readTool.getApprovalOverride?.({ file_path: `${externalDir}/file1.ts` })
419
+ await readTool.getApprovalOverride?.({ file_path: `${externalDir}/file1.ts` })
418
420
  ).toBeNull();
419
421
  expect(
420
- writeTool.getApprovalOverride?.({
422
+ await writeTool.getApprovalOverride?.({
421
423
  file_path: `${externalDir}/file2.ts`,
422
424
  content: "test"
423
425
  })
424
426
  ).toBeNull();
425
427
  expect(
426
- editTool.getApprovalOverride?.({
428
+ await editTool.getApprovalOverride?.({
427
429
  file_path: `${externalDir}/file3.ts`,
428
430
  old_string: "a",
429
431
  new_string: "b"
@@ -437,7 +439,9 @@ describe("Directory Approval Integration Tests", () => {
437
439
  fileSystemService,
438
440
  directoryApproval: void 0
439
441
  });
440
- const override = tool.getApprovalOverride?.({ file_path: "/external/project/file.ts" });
442
+ const override = await tool.getApprovalOverride?.({
443
+ file_path: "/external/project/file.ts"
444
+ });
441
445
  expect(override).not.toBeNull();
442
446
  });
443
447
  });
@@ -32,12 +32,18 @@ __export(edit_file_tool_exports, {
32
32
  });
33
33
  module.exports = __toCommonJS(edit_file_tool_exports);
34
34
  var path = __toESM(require("node:path"), 1);
35
+ var import_node_crypto = require("node:crypto");
35
36
  var import_zod = require("zod");
36
37
  var import_diff = require("diff");
37
38
  var import_core = require("@dexto/core");
38
39
  var import_core2 = require("@dexto/core");
39
40
  var import_core3 = require("@dexto/core");
40
41
  var import_core4 = require("@dexto/core");
42
+ var import_error_codes = require("./error-codes.js");
43
+ const previewContentHashCache = /* @__PURE__ */ new Map();
44
+ function computeContentHash(content) {
45
+ return (0, import_node_crypto.createHash)("sha256").update(content, "utf8").digest("hex");
46
+ }
41
47
  const EditFileInputSchema = import_zod.z.object({
42
48
  file_path: import_zod.z.string().describe("Absolute path to the file to edit"),
43
49
  old_string: import_zod.z.string().describe("Text to replace (must be unique unless replace_all is true)"),
@@ -69,10 +75,10 @@ function createEditFileTool(options) {
69
75
  * Check if this edit operation needs directory access approval.
70
76
  * Returns custom approval request if the file is outside allowed paths.
71
77
  */
72
- getApprovalOverride: (args) => {
78
+ getApprovalOverride: async (args) => {
73
79
  const { file_path } = args;
74
80
  if (!file_path) return null;
75
- const isAllowed = fileSystemService.isPathWithinConfigAllowed(file_path);
81
+ const isAllowed = await fileSystemService.isPathWithinConfigAllowed(file_path);
76
82
  if (isAllowed) {
77
83
  return null;
78
84
  }
@@ -108,12 +114,19 @@ function createEditFileTool(options) {
108
114
  /**
109
115
  * Generate preview for approval UI - shows diff without modifying file
110
116
  * Throws ToolError.validationFailed() for validation errors (file not found, string not found)
117
+ * Stores content hash for change detection in execute phase.
111
118
  */
112
- generatePreview: async (input, _context) => {
119
+ generatePreview: async (input, context) => {
113
120
  const { file_path, old_string, new_string, replace_all } = input;
114
121
  try {
115
122
  const originalFile = await fileSystemService.readFile(file_path);
116
123
  const originalContent = originalFile.content;
124
+ if (context?.toolCallId) {
125
+ previewContentHashCache.set(
126
+ context.toolCallId,
127
+ computeContentHash(originalContent)
128
+ );
129
+ }
117
130
  if (!replace_all) {
118
131
  const occurrences = originalContent.split(old_string).length - 1;
119
132
  if (occurrences > 1) {
@@ -146,25 +159,36 @@ function createEditFileTool(options) {
146
159
  return null;
147
160
  }
148
161
  },
149
- execute: async (input, _context) => {
162
+ execute: async (input, context) => {
150
163
  const { file_path, old_string, new_string, replace_all } = input;
151
- const originalFile = await fileSystemService.readFile(file_path);
152
- const originalContent = originalFile.content;
153
- const result = await fileSystemService.editFile(
154
- file_path,
155
- {
156
- oldString: old_string,
157
- newString: new_string,
158
- replaceAll: replace_all
159
- },
160
- {
161
- backup: true
162
- // Always create backup for internal tools
164
+ if (context?.toolCallId && previewContentHashCache.has(context.toolCallId)) {
165
+ const expectedHash = previewContentHashCache.get(context.toolCallId);
166
+ previewContentHashCache.delete(context.toolCallId);
167
+ let currentContent;
168
+ try {
169
+ const currentFile = await fileSystemService.readFile(file_path);
170
+ currentContent = currentFile.content;
171
+ } catch (error) {
172
+ if (error instanceof import_core4.DextoRuntimeError && error.code === import_error_codes.FileSystemErrorCode.FILE_NOT_FOUND) {
173
+ throw import_core2.ToolError.fileModifiedSincePreview("edit_file", file_path);
174
+ }
175
+ throw error;
176
+ }
177
+ const currentHash = computeContentHash(currentContent);
178
+ if (expectedHash !== currentHash) {
179
+ throw import_core2.ToolError.fileModifiedSincePreview("edit_file", file_path);
163
180
  }
181
+ }
182
+ const result = await fileSystemService.editFile(file_path, {
183
+ oldString: old_string,
184
+ newString: new_string,
185
+ replaceAll: replace_all
186
+ });
187
+ const _display = generateDiffPreview(
188
+ file_path,
189
+ result.originalContent,
190
+ result.newContent
164
191
  );
165
- const newFile = await fileSystemService.readFile(file_path);
166
- const newContent = newFile.content;
167
- const _display = generateDiffPreview(file_path, originalContent, newContent);
168
192
  return {
169
193
  success: result.success,
170
194
  path: result.path,