@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
@@ -29,13 +29,26 @@ var import_write_file_tool = require("./write-file-tool.js");
29
29
  var import_filesystem_service = require("./filesystem-service.js");
30
30
  var import_core = require("@dexto/core");
31
31
  var import_core2 = require("@dexto/core");
32
- const createMockLogger = () => ({
33
- debug: import_vitest.vi.fn(),
34
- info: import_vitest.vi.fn(),
35
- warn: import_vitest.vi.fn(),
36
- error: import_vitest.vi.fn(),
37
- createChild: import_vitest.vi.fn().mockReturnThis()
38
- });
32
+ const createMockLogger = () => {
33
+ const logger = {
34
+ debug: import_vitest.vi.fn(),
35
+ silly: import_vitest.vi.fn(),
36
+ info: import_vitest.vi.fn(),
37
+ warn: import_vitest.vi.fn(),
38
+ error: import_vitest.vi.fn(),
39
+ trackException: import_vitest.vi.fn(),
40
+ createChild: import_vitest.vi.fn(() => logger),
41
+ createFileOnlyChild: import_vitest.vi.fn(() => logger),
42
+ setLevel: import_vitest.vi.fn(),
43
+ getLevel: import_vitest.vi.fn(() => "debug"),
44
+ getLogFilePath: import_vitest.vi.fn(() => null),
45
+ destroy: import_vitest.vi.fn(async () => void 0)
46
+ };
47
+ return logger;
48
+ };
49
+ function createToolContext(logger, overrides = {}) {
50
+ return { logger, ...overrides };
51
+ }
39
52
  (0, import_vitest.describe)("write_file tool", () => {
40
53
  let mockLogger;
41
54
  let tempDir;
@@ -66,8 +79,38 @@ const createMockLogger = () => ({
66
79
  }
67
80
  });
68
81
  (0, import_vitest.describe)("File Modification Detection - Existing Files", () => {
82
+ (0, import_vitest.it)("should generate preview for existing files outside config-allowed roots (preview read only)", async () => {
83
+ const tool = (0, import_write_file_tool.createWriteFileTool)(async () => fileSystemService);
84
+ const rawExternalDir = await fs.mkdtemp(
85
+ path.join(os.tmpdir(), "dexto-write-outside-allowed-")
86
+ );
87
+ const externalDir = await fs.realpath(rawExternalDir);
88
+ const externalFile = path.join(externalDir, "external.txt");
89
+ try {
90
+ await fs.writeFile(externalFile, "original content");
91
+ const toolCallId = "preview-outside-roots";
92
+ const parsedInput = tool.inputSchema.parse({
93
+ file_path: externalFile,
94
+ content: "new content"
95
+ });
96
+ const preview = await tool.presentation.preview(
97
+ parsedInput,
98
+ createToolContext(mockLogger, { toolCallId })
99
+ );
100
+ (0, import_vitest.expect)(preview).toBeDefined();
101
+ (0, import_vitest.expect)(preview?.type).toBe("diff");
102
+ if (preview?.type === "diff") {
103
+ (0, import_vitest.expect)(preview.title).toBe("Update file");
104
+ (0, import_vitest.expect)(preview.filename).toBe(externalFile);
105
+ } else {
106
+ import_vitest.expect.fail("Expected diff preview");
107
+ }
108
+ } finally {
109
+ await fs.rm(externalDir, { recursive: true, force: true });
110
+ }
111
+ });
69
112
  (0, import_vitest.it)("should succeed when existing file is not modified between preview and execute", async () => {
70
- const tool = (0, import_write_file_tool.createWriteFileTool)({ fileSystemService });
113
+ const tool = (0, import_write_file_tool.createWriteFileTool)(async () => fileSystemService);
71
114
  const testFile = path.join(tempDir, "test.txt");
72
115
  await fs.writeFile(testFile, "original content");
73
116
  const toolCallId = "test-call-123";
@@ -75,17 +118,29 @@ const createMockLogger = () => ({
75
118
  file_path: testFile,
76
119
  content: "new content"
77
120
  };
78
- const preview = await tool.generatePreview(input, { toolCallId });
121
+ const parsedInput = tool.inputSchema.parse(input);
122
+ const preview = await tool.presentation.preview(
123
+ parsedInput,
124
+ createToolContext(mockLogger, { toolCallId })
125
+ );
79
126
  (0, import_vitest.expect)(preview).toBeDefined();
80
127
  (0, import_vitest.expect)(preview?.type).toBe("diff");
81
- const result = await tool.execute(input, { toolCallId });
128
+ if (preview?.type === "diff") {
129
+ (0, import_vitest.expect)(preview.title).toBe("Update file");
130
+ } else {
131
+ import_vitest.expect.fail("Expected diff preview");
132
+ }
133
+ const result = await tool.execute(
134
+ parsedInput,
135
+ createToolContext(mockLogger, { toolCallId })
136
+ );
82
137
  (0, import_vitest.expect)(result.success).toBe(true);
83
138
  (0, import_vitest.expect)(result.path).toBe(testFile);
84
139
  const content = await fs.readFile(testFile, "utf-8");
85
140
  (0, import_vitest.expect)(content).toBe("new content");
86
141
  });
87
142
  (0, import_vitest.it)("should fail when existing file is modified between preview and execute", async () => {
88
- const tool = (0, import_write_file_tool.createWriteFileTool)({ fileSystemService });
143
+ const tool = (0, import_write_file_tool.createWriteFileTool)(async () => fileSystemService);
89
144
  const testFile = path.join(tempDir, "test.txt");
90
145
  await fs.writeFile(testFile, "original content");
91
146
  const toolCallId = "test-call-456";
@@ -93,10 +148,14 @@ const createMockLogger = () => ({
93
148
  file_path: testFile,
94
149
  content: "new content"
95
150
  };
96
- await tool.generatePreview(input, { toolCallId });
151
+ const parsedInput = tool.inputSchema.parse(input);
152
+ await tool.presentation.preview(
153
+ parsedInput,
154
+ createToolContext(mockLogger, { toolCallId })
155
+ );
97
156
  await fs.writeFile(testFile, "user modified this");
98
157
  try {
99
- await tool.execute(input, { toolCallId });
158
+ await tool.execute(parsedInput, createToolContext(mockLogger, { toolCallId }));
100
159
  import_vitest.expect.fail("Should have thrown an error");
101
160
  } catch (error) {
102
161
  (0, import_vitest.expect)(error).toBeInstanceOf(import_core2.DextoRuntimeError);
@@ -108,7 +167,7 @@ const createMockLogger = () => ({
108
167
  (0, import_vitest.expect)(content).toBe("user modified this");
109
168
  });
110
169
  (0, import_vitest.it)("should fail when existing file is deleted between preview and execute", async () => {
111
- const tool = (0, import_write_file_tool.createWriteFileTool)({ fileSystemService });
170
+ const tool = (0, import_write_file_tool.createWriteFileTool)(async () => fileSystemService);
112
171
  const testFile = path.join(tempDir, "test.txt");
113
172
  await fs.writeFile(testFile, "original content");
114
173
  const toolCallId = "test-call-deleted";
@@ -116,10 +175,14 @@ const createMockLogger = () => ({
116
175
  file_path: testFile,
117
176
  content: "new content"
118
177
  };
119
- await tool.generatePreview(input, { toolCallId });
178
+ const parsedInput = tool.inputSchema.parse(input);
179
+ await tool.presentation.preview(
180
+ parsedInput,
181
+ createToolContext(mockLogger, { toolCallId })
182
+ );
120
183
  await fs.unlink(testFile);
121
184
  try {
122
- await tool.execute(input, { toolCallId });
185
+ await tool.execute(parsedInput, createToolContext(mockLogger, { toolCallId }));
123
186
  import_vitest.expect.fail("Should have thrown an error");
124
187
  } catch (error) {
125
188
  (0, import_vitest.expect)(error).toBeInstanceOf(import_core2.DextoRuntimeError);
@@ -131,35 +194,62 @@ const createMockLogger = () => ({
131
194
  });
132
195
  (0, import_vitest.describe)("File Modification Detection - New Files", () => {
133
196
  (0, import_vitest.it)("should succeed when creating new file that still does not exist", async () => {
134
- const tool = (0, import_write_file_tool.createWriteFileTool)({ fileSystemService });
197
+ const tool = (0, import_write_file_tool.createWriteFileTool)(async () => fileSystemService);
135
198
  const testFile = path.join(tempDir, "new-file.txt");
136
199
  const toolCallId = "test-call-new";
137
200
  const input = {
138
201
  file_path: testFile,
139
202
  content: "brand new content"
140
203
  };
141
- const preview = await tool.generatePreview(input, { toolCallId });
204
+ const parsedInput = tool.inputSchema.parse(input);
205
+ const preview = await tool.presentation.preview(
206
+ parsedInput,
207
+ createToolContext(mockLogger, { toolCallId })
208
+ );
142
209
  (0, import_vitest.expect)(preview).toBeDefined();
143
210
  (0, import_vitest.expect)(preview?.type).toBe("file");
144
- (0, import_vitest.expect)(preview.operation).toBe("create");
145
- const result = await tool.execute(input, { toolCallId });
211
+ if (preview?.type === "file") {
212
+ (0, import_vitest.expect)(preview.operation).toBe("create");
213
+ (0, import_vitest.expect)(preview.title).toBe("Create file");
214
+ } else {
215
+ import_vitest.expect.fail("Expected file preview");
216
+ }
217
+ const result = await tool.execute(
218
+ parsedInput,
219
+ createToolContext(mockLogger, { toolCallId })
220
+ );
146
221
  (0, import_vitest.expect)(result.success).toBe(true);
222
+ const display = result._display;
223
+ if (display && typeof display === "object" && "type" in display) {
224
+ (0, import_vitest.expect)(display.type).toBe("file");
225
+ const fileDisplay = display;
226
+ (0, import_vitest.expect)(fileDisplay.title).toBe("Create file");
227
+ (0, import_vitest.expect)(fileDisplay.content).toBe("brand new content");
228
+ } else {
229
+ import_vitest.expect.fail("Expected result._display");
230
+ }
147
231
  const content = await fs.readFile(testFile, "utf-8");
148
232
  (0, import_vitest.expect)(content).toBe("brand new content");
149
233
  });
150
234
  (0, import_vitest.it)("should fail when file is created by someone else between preview and execute", async () => {
151
- const tool = (0, import_write_file_tool.createWriteFileTool)({ fileSystemService });
235
+ const tool = (0, import_write_file_tool.createWriteFileTool)(async () => fileSystemService);
236
+ const previewFn = tool.presentation?.preview;
237
+ (0, import_vitest.expect)(previewFn).toBeDefined();
152
238
  const testFile = path.join(tempDir, "race-condition.txt");
153
239
  const toolCallId = "test-call-race";
154
240
  const input = {
155
241
  file_path: testFile,
156
242
  content: "agent content"
157
243
  };
158
- const preview = await tool.generatePreview(input, { toolCallId });
244
+ const parsedInput = tool.inputSchema.parse(input);
245
+ const preview = await previewFn(
246
+ parsedInput,
247
+ createToolContext(mockLogger, { toolCallId })
248
+ );
159
249
  (0, import_vitest.expect)(preview?.type).toBe("file");
160
250
  await fs.writeFile(testFile, "someone else created this");
161
251
  try {
162
- await tool.execute(input, { toolCallId });
252
+ await tool.execute(parsedInput, createToolContext(mockLogger, { toolCallId }));
163
253
  import_vitest.expect.fail("Should have thrown an error");
164
254
  } catch (error) {
165
255
  (0, import_vitest.expect)(error).toBeInstanceOf(import_core2.DextoRuntimeError);
@@ -173,7 +263,7 @@ const createMockLogger = () => ({
173
263
  });
174
264
  (0, import_vitest.describe)("Cache Cleanup", () => {
175
265
  (0, import_vitest.it)("should clean up hash cache after successful execution", async () => {
176
- const tool = (0, import_write_file_tool.createWriteFileTool)({ fileSystemService });
266
+ const tool = (0, import_write_file_tool.createWriteFileTool)(async () => fileSystemService);
177
267
  const testFile = path.join(tempDir, "test.txt");
178
268
  await fs.writeFile(testFile, "original");
179
269
  const toolCallId = "test-call-cleanup";
@@ -181,20 +271,30 @@ const createMockLogger = () => ({
181
271
  file_path: testFile,
182
272
  content: "first write"
183
273
  };
184
- await tool.generatePreview(input, { toolCallId });
185
- await tool.execute(input, { toolCallId });
274
+ const parsedInput = tool.inputSchema.parse(input);
275
+ await tool.presentation.preview(
276
+ parsedInput,
277
+ createToolContext(mockLogger, { toolCallId })
278
+ );
279
+ await tool.execute(parsedInput, createToolContext(mockLogger, { toolCallId }));
186
280
  const input2 = {
187
281
  file_path: testFile,
188
282
  content: "second write"
189
283
  };
190
- await tool.generatePreview(input2, { toolCallId });
191
- const result = await tool.execute(input2, { toolCallId });
284
+ const parsedInput2 = tool.inputSchema.parse(input2);
285
+ const previewFn2 = tool.presentation?.preview;
286
+ (0, import_vitest.expect)(previewFn2).toBeDefined();
287
+ await previewFn2(parsedInput2, createToolContext(mockLogger, { toolCallId }));
288
+ const result = await tool.execute(
289
+ parsedInput2,
290
+ createToolContext(mockLogger, { toolCallId })
291
+ );
192
292
  (0, import_vitest.expect)(result.success).toBe(true);
193
293
  const content = await fs.readFile(testFile, "utf-8");
194
294
  (0, import_vitest.expect)(content).toBe("second write");
195
295
  });
196
296
  (0, import_vitest.it)("should clean up hash cache after failed execution", async () => {
197
- const tool = (0, import_write_file_tool.createWriteFileTool)({ fileSystemService });
297
+ const tool = (0, import_write_file_tool.createWriteFileTool)(async () => fileSystemService);
198
298
  const testFile = path.join(tempDir, "test.txt");
199
299
  await fs.writeFile(testFile, "original");
200
300
  const toolCallId = "test-call-fail";
@@ -202,15 +302,21 @@ const createMockLogger = () => ({
202
302
  file_path: testFile,
203
303
  content: "new content"
204
304
  };
205
- await tool.generatePreview(input, { toolCallId });
305
+ const parsedInput = tool.inputSchema.parse(input);
306
+ const previewFn = tool.presentation?.preview;
307
+ (0, import_vitest.expect)(previewFn).toBeDefined();
308
+ await previewFn(parsedInput, createToolContext(mockLogger, { toolCallId }));
206
309
  await fs.writeFile(testFile, "modified");
207
310
  try {
208
- await tool.execute(input, { toolCallId });
311
+ await tool.execute(parsedInput, createToolContext(mockLogger, { toolCallId }));
209
312
  } catch {
210
313
  }
211
314
  await fs.writeFile(testFile, "reset content");
212
- await tool.generatePreview(input, { toolCallId });
213
- const result = await tool.execute(input, { toolCallId });
315
+ await previewFn(parsedInput, createToolContext(mockLogger, { toolCallId }));
316
+ const result = await tool.execute(
317
+ parsedInput,
318
+ createToolContext(mockLogger, { toolCallId })
319
+ );
214
320
  (0, import_vitest.expect)(result.success).toBe(true);
215
321
  });
216
322
  });
@@ -1,2 +1,7 @@
1
-
2
- export { }
1
+ /**
2
+ * Write File Tool Tests
3
+ *
4
+ * Tests for the write_file tool including file modification detection.
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=write-file-tool.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write-file-tool.test.d.ts","sourceRoot":"","sources":["../src/write-file-tool.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
@@ -6,13 +6,26 @@ import { createWriteFileTool } from "./write-file-tool.js";
6
6
  import { FileSystemService } from "./filesystem-service.js";
7
7
  import { ToolErrorCode } from "@dexto/core";
8
8
  import { DextoRuntimeError } from "@dexto/core";
9
- const createMockLogger = () => ({
10
- debug: vi.fn(),
11
- info: vi.fn(),
12
- warn: vi.fn(),
13
- error: vi.fn(),
14
- createChild: vi.fn().mockReturnThis()
15
- });
9
+ const createMockLogger = () => {
10
+ const logger = {
11
+ debug: vi.fn(),
12
+ silly: vi.fn(),
13
+ info: vi.fn(),
14
+ warn: vi.fn(),
15
+ error: vi.fn(),
16
+ trackException: vi.fn(),
17
+ createChild: vi.fn(() => logger),
18
+ createFileOnlyChild: vi.fn(() => logger),
19
+ setLevel: vi.fn(),
20
+ getLevel: vi.fn(() => "debug"),
21
+ getLogFilePath: vi.fn(() => null),
22
+ destroy: vi.fn(async () => void 0)
23
+ };
24
+ return logger;
25
+ };
26
+ function createToolContext(logger, overrides = {}) {
27
+ return { logger, ...overrides };
28
+ }
16
29
  describe("write_file tool", () => {
17
30
  let mockLogger;
18
31
  let tempDir;
@@ -43,8 +56,38 @@ describe("write_file tool", () => {
43
56
  }
44
57
  });
45
58
  describe("File Modification Detection - Existing Files", () => {
59
+ it("should generate preview for existing files outside config-allowed roots (preview read only)", async () => {
60
+ const tool = createWriteFileTool(async () => fileSystemService);
61
+ const rawExternalDir = await fs.mkdtemp(
62
+ path.join(os.tmpdir(), "dexto-write-outside-allowed-")
63
+ );
64
+ const externalDir = await fs.realpath(rawExternalDir);
65
+ const externalFile = path.join(externalDir, "external.txt");
66
+ try {
67
+ await fs.writeFile(externalFile, "original content");
68
+ const toolCallId = "preview-outside-roots";
69
+ const parsedInput = tool.inputSchema.parse({
70
+ file_path: externalFile,
71
+ content: "new content"
72
+ });
73
+ const preview = await tool.presentation.preview(
74
+ parsedInput,
75
+ createToolContext(mockLogger, { toolCallId })
76
+ );
77
+ expect(preview).toBeDefined();
78
+ expect(preview?.type).toBe("diff");
79
+ if (preview?.type === "diff") {
80
+ expect(preview.title).toBe("Update file");
81
+ expect(preview.filename).toBe(externalFile);
82
+ } else {
83
+ expect.fail("Expected diff preview");
84
+ }
85
+ } finally {
86
+ await fs.rm(externalDir, { recursive: true, force: true });
87
+ }
88
+ });
46
89
  it("should succeed when existing file is not modified between preview and execute", async () => {
47
- const tool = createWriteFileTool({ fileSystemService });
90
+ const tool = createWriteFileTool(async () => fileSystemService);
48
91
  const testFile = path.join(tempDir, "test.txt");
49
92
  await fs.writeFile(testFile, "original content");
50
93
  const toolCallId = "test-call-123";
@@ -52,17 +95,29 @@ describe("write_file tool", () => {
52
95
  file_path: testFile,
53
96
  content: "new content"
54
97
  };
55
- const preview = await tool.generatePreview(input, { toolCallId });
98
+ const parsedInput = tool.inputSchema.parse(input);
99
+ const preview = await tool.presentation.preview(
100
+ parsedInput,
101
+ createToolContext(mockLogger, { toolCallId })
102
+ );
56
103
  expect(preview).toBeDefined();
57
104
  expect(preview?.type).toBe("diff");
58
- const result = await tool.execute(input, { toolCallId });
105
+ if (preview?.type === "diff") {
106
+ expect(preview.title).toBe("Update file");
107
+ } else {
108
+ expect.fail("Expected diff preview");
109
+ }
110
+ const result = await tool.execute(
111
+ parsedInput,
112
+ createToolContext(mockLogger, { toolCallId })
113
+ );
59
114
  expect(result.success).toBe(true);
60
115
  expect(result.path).toBe(testFile);
61
116
  const content = await fs.readFile(testFile, "utf-8");
62
117
  expect(content).toBe("new content");
63
118
  });
64
119
  it("should fail when existing file is modified between preview and execute", async () => {
65
- const tool = createWriteFileTool({ fileSystemService });
120
+ const tool = createWriteFileTool(async () => fileSystemService);
66
121
  const testFile = path.join(tempDir, "test.txt");
67
122
  await fs.writeFile(testFile, "original content");
68
123
  const toolCallId = "test-call-456";
@@ -70,10 +125,14 @@ describe("write_file tool", () => {
70
125
  file_path: testFile,
71
126
  content: "new content"
72
127
  };
73
- await tool.generatePreview(input, { toolCallId });
128
+ const parsedInput = tool.inputSchema.parse(input);
129
+ await tool.presentation.preview(
130
+ parsedInput,
131
+ createToolContext(mockLogger, { toolCallId })
132
+ );
74
133
  await fs.writeFile(testFile, "user modified this");
75
134
  try {
76
- await tool.execute(input, { toolCallId });
135
+ await tool.execute(parsedInput, createToolContext(mockLogger, { toolCallId }));
77
136
  expect.fail("Should have thrown an error");
78
137
  } catch (error) {
79
138
  expect(error).toBeInstanceOf(DextoRuntimeError);
@@ -85,7 +144,7 @@ describe("write_file tool", () => {
85
144
  expect(content).toBe("user modified this");
86
145
  });
87
146
  it("should fail when existing file is deleted between preview and execute", async () => {
88
- const tool = createWriteFileTool({ fileSystemService });
147
+ const tool = createWriteFileTool(async () => fileSystemService);
89
148
  const testFile = path.join(tempDir, "test.txt");
90
149
  await fs.writeFile(testFile, "original content");
91
150
  const toolCallId = "test-call-deleted";
@@ -93,10 +152,14 @@ describe("write_file tool", () => {
93
152
  file_path: testFile,
94
153
  content: "new content"
95
154
  };
96
- await tool.generatePreview(input, { toolCallId });
155
+ const parsedInput = tool.inputSchema.parse(input);
156
+ await tool.presentation.preview(
157
+ parsedInput,
158
+ createToolContext(mockLogger, { toolCallId })
159
+ );
97
160
  await fs.unlink(testFile);
98
161
  try {
99
- await tool.execute(input, { toolCallId });
162
+ await tool.execute(parsedInput, createToolContext(mockLogger, { toolCallId }));
100
163
  expect.fail("Should have thrown an error");
101
164
  } catch (error) {
102
165
  expect(error).toBeInstanceOf(DextoRuntimeError);
@@ -108,35 +171,62 @@ describe("write_file tool", () => {
108
171
  });
109
172
  describe("File Modification Detection - New Files", () => {
110
173
  it("should succeed when creating new file that still does not exist", async () => {
111
- const tool = createWriteFileTool({ fileSystemService });
174
+ const tool = createWriteFileTool(async () => fileSystemService);
112
175
  const testFile = path.join(tempDir, "new-file.txt");
113
176
  const toolCallId = "test-call-new";
114
177
  const input = {
115
178
  file_path: testFile,
116
179
  content: "brand new content"
117
180
  };
118
- const preview = await tool.generatePreview(input, { toolCallId });
181
+ const parsedInput = tool.inputSchema.parse(input);
182
+ const preview = await tool.presentation.preview(
183
+ parsedInput,
184
+ createToolContext(mockLogger, { toolCallId })
185
+ );
119
186
  expect(preview).toBeDefined();
120
187
  expect(preview?.type).toBe("file");
121
- expect(preview.operation).toBe("create");
122
- const result = await tool.execute(input, { toolCallId });
188
+ if (preview?.type === "file") {
189
+ expect(preview.operation).toBe("create");
190
+ expect(preview.title).toBe("Create file");
191
+ } else {
192
+ expect.fail("Expected file preview");
193
+ }
194
+ const result = await tool.execute(
195
+ parsedInput,
196
+ createToolContext(mockLogger, { toolCallId })
197
+ );
123
198
  expect(result.success).toBe(true);
199
+ const display = result._display;
200
+ if (display && typeof display === "object" && "type" in display) {
201
+ expect(display.type).toBe("file");
202
+ const fileDisplay = display;
203
+ expect(fileDisplay.title).toBe("Create file");
204
+ expect(fileDisplay.content).toBe("brand new content");
205
+ } else {
206
+ expect.fail("Expected result._display");
207
+ }
124
208
  const content = await fs.readFile(testFile, "utf-8");
125
209
  expect(content).toBe("brand new content");
126
210
  });
127
211
  it("should fail when file is created by someone else between preview and execute", async () => {
128
- const tool = createWriteFileTool({ fileSystemService });
212
+ const tool = createWriteFileTool(async () => fileSystemService);
213
+ const previewFn = tool.presentation?.preview;
214
+ expect(previewFn).toBeDefined();
129
215
  const testFile = path.join(tempDir, "race-condition.txt");
130
216
  const toolCallId = "test-call-race";
131
217
  const input = {
132
218
  file_path: testFile,
133
219
  content: "agent content"
134
220
  };
135
- const preview = await tool.generatePreview(input, { toolCallId });
221
+ const parsedInput = tool.inputSchema.parse(input);
222
+ const preview = await previewFn(
223
+ parsedInput,
224
+ createToolContext(mockLogger, { toolCallId })
225
+ );
136
226
  expect(preview?.type).toBe("file");
137
227
  await fs.writeFile(testFile, "someone else created this");
138
228
  try {
139
- await tool.execute(input, { toolCallId });
229
+ await tool.execute(parsedInput, createToolContext(mockLogger, { toolCallId }));
140
230
  expect.fail("Should have thrown an error");
141
231
  } catch (error) {
142
232
  expect(error).toBeInstanceOf(DextoRuntimeError);
@@ -150,7 +240,7 @@ describe("write_file tool", () => {
150
240
  });
151
241
  describe("Cache Cleanup", () => {
152
242
  it("should clean up hash cache after successful execution", async () => {
153
- const tool = createWriteFileTool({ fileSystemService });
243
+ const tool = createWriteFileTool(async () => fileSystemService);
154
244
  const testFile = path.join(tempDir, "test.txt");
155
245
  await fs.writeFile(testFile, "original");
156
246
  const toolCallId = "test-call-cleanup";
@@ -158,20 +248,30 @@ describe("write_file tool", () => {
158
248
  file_path: testFile,
159
249
  content: "first write"
160
250
  };
161
- await tool.generatePreview(input, { toolCallId });
162
- await tool.execute(input, { toolCallId });
251
+ const parsedInput = tool.inputSchema.parse(input);
252
+ await tool.presentation.preview(
253
+ parsedInput,
254
+ createToolContext(mockLogger, { toolCallId })
255
+ );
256
+ await tool.execute(parsedInput, createToolContext(mockLogger, { toolCallId }));
163
257
  const input2 = {
164
258
  file_path: testFile,
165
259
  content: "second write"
166
260
  };
167
- await tool.generatePreview(input2, { toolCallId });
168
- const result = await tool.execute(input2, { toolCallId });
261
+ const parsedInput2 = tool.inputSchema.parse(input2);
262
+ const previewFn2 = tool.presentation?.preview;
263
+ expect(previewFn2).toBeDefined();
264
+ await previewFn2(parsedInput2, createToolContext(mockLogger, { toolCallId }));
265
+ const result = await tool.execute(
266
+ parsedInput2,
267
+ createToolContext(mockLogger, { toolCallId })
268
+ );
169
269
  expect(result.success).toBe(true);
170
270
  const content = await fs.readFile(testFile, "utf-8");
171
271
  expect(content).toBe("second write");
172
272
  });
173
273
  it("should clean up hash cache after failed execution", async () => {
174
- const tool = createWriteFileTool({ fileSystemService });
274
+ const tool = createWriteFileTool(async () => fileSystemService);
175
275
  const testFile = path.join(tempDir, "test.txt");
176
276
  await fs.writeFile(testFile, "original");
177
277
  const toolCallId = "test-call-fail";
@@ -179,15 +279,21 @@ describe("write_file tool", () => {
179
279
  file_path: testFile,
180
280
  content: "new content"
181
281
  };
182
- await tool.generatePreview(input, { toolCallId });
282
+ const parsedInput = tool.inputSchema.parse(input);
283
+ const previewFn = tool.presentation?.preview;
284
+ expect(previewFn).toBeDefined();
285
+ await previewFn(parsedInput, createToolContext(mockLogger, { toolCallId }));
183
286
  await fs.writeFile(testFile, "modified");
184
287
  try {
185
- await tool.execute(input, { toolCallId });
288
+ await tool.execute(parsedInput, createToolContext(mockLogger, { toolCallId }));
186
289
  } catch {
187
290
  }
188
291
  await fs.writeFile(testFile, "reset content");
189
- await tool.generatePreview(input, { toolCallId });
190
- const result = await tool.execute(input, { toolCallId });
292
+ await previewFn(parsedInput, createToolContext(mockLogger, { toolCallId }));
293
+ const result = await tool.execute(
294
+ parsedInput,
295
+ createToolContext(mockLogger, { toolCallId })
296
+ );
191
297
  expect(result.success).toBe(true);
192
298
  });
193
299
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dexto/tools-filesystem",
3
- "version": "1.5.8",
4
- "description": "FileSystem tools provider for Dexto agents",
3
+ "version": "1.6.1",
4
+ "description": "FileSystem tools factory for Dexto agents",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -22,7 +22,8 @@
22
22
  "glob": "^11.1.0",
23
23
  "safe-regex": "^2.1.1",
24
24
  "zod": "^3.25.0",
25
- "@dexto/core": "1.5.8"
25
+ "@dexto/agent-config": "1.6.1",
26
+ "@dexto/core": "1.6.1"
26
27
  },
27
28
  "devDependencies": {
28
29
  "@types/diff": "^5.2.3",
@@ -35,7 +36,7 @@
35
36
  "README.md"
36
37
  ],
37
38
  "scripts": {
38
- "build": "tsup",
39
+ "build": "tsup && node ../../scripts/clean-tsbuildinfo.mjs && tsc -b tsconfig.json --emitDeclarationOnly",
39
40
  "typecheck": "tsc --noEmit",
40
41
  "clean": "rm -rf dist"
41
42
  }
@@ -1,2 +0,0 @@
1
-
2
- export { }
@@ -1,17 +0,0 @@
1
- import { InternalTool } from '@dexto/core';
2
- import { FileToolOptions } from './file-tool-types.cjs';
3
- import './filesystem-service.cjs';
4
- import './types.cjs';
5
-
6
- /**
7
- * Edit File Tool
8
- *
9
- * Internal tool for editing files by replacing text (requires approval)
10
- */
11
-
12
- /**
13
- * Create the edit_file internal tool with directory approval support
14
- */
15
- declare function createEditFileTool(options: FileToolOptions): InternalTool;
16
-
17
- export { createEditFileTool };
@@ -1,2 +0,0 @@
1
-
2
- export { }