@dexto/tools-filesystem 1.5.7 → 1.6.0

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 (83) hide show
  1. package/dist/directory-approval.cjs +94 -0
  2. package/dist/directory-approval.d.cts +22 -0
  3. package/dist/directory-approval.d.ts +20 -0
  4. package/dist/directory-approval.d.ts.map +1 -0
  5. package/dist/directory-approval.integration.test.cjs +303 -269
  6. package/dist/directory-approval.integration.test.d.ts +14 -2
  7. package/dist/directory-approval.integration.test.d.ts.map +1 -0
  8. package/dist/directory-approval.integration.test.js +309 -270
  9. package/dist/directory-approval.js +59 -0
  10. package/dist/edit-file-tool.cjs +57 -90
  11. package/dist/edit-file-tool.d.cts +20 -3
  12. package/dist/edit-file-tool.d.ts +22 -9
  13. package/dist/edit-file-tool.d.ts.map +1 -0
  14. package/dist/edit-file-tool.js +53 -76
  15. package/dist/edit-file-tool.test.cjs +66 -29
  16. package/dist/edit-file-tool.test.d.ts +7 -2
  17. package/dist/edit-file-tool.test.d.ts.map +1 -0
  18. package/dist/edit-file-tool.test.js +66 -29
  19. package/dist/error-codes.d.ts +2 -3
  20. package/dist/error-codes.d.ts.map +1 -0
  21. package/dist/errors.d.ts +4 -7
  22. package/dist/errors.d.ts.map +1 -0
  23. package/dist/file-tool-types.d.cts +7 -35
  24. package/dist/file-tool-types.d.ts +8 -40
  25. package/dist/file-tool-types.d.ts.map +1 -0
  26. package/dist/filesystem-service.cjs +18 -1
  27. package/dist/filesystem-service.d.cts +11 -6
  28. package/dist/filesystem-service.d.ts +14 -12
  29. package/dist/filesystem-service.d.ts.map +1 -0
  30. package/dist/filesystem-service.js +18 -1
  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 +22 -47
  36. package/dist/glob-files-tool.d.cts +17 -3
  37. package/dist/glob-files-tool.d.ts +19 -9
  38. package/dist/glob-files-tool.d.ts.map +1 -0
  39. package/dist/glob-files-tool.js +23 -48
  40. package/dist/grep-content-tool.cjs +29 -46
  41. package/dist/grep-content-tool.d.cts +26 -3
  42. package/dist/grep-content-tool.d.ts +28 -9
  43. package/dist/grep-content-tool.d.ts.map +1 -0
  44. package/dist/grep-content-tool.js +30 -47
  45. package/dist/index.cjs +3 -3
  46. package/dist/index.d.cts +4 -2
  47. package/dist/index.d.ts +10 -5
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +2 -2
  50. package/dist/path-validator.d.cts +2 -2
  51. package/dist/path-validator.d.ts +6 -9
  52. package/dist/path-validator.d.ts.map +1 -0
  53. package/dist/path-validator.test.d.ts +7 -2
  54. package/dist/path-validator.test.d.ts.map +1 -0
  55. package/dist/read-file-tool.cjs +21 -60
  56. package/dist/read-file-tool.d.cts +17 -3
  57. package/dist/read-file-tool.d.ts +19 -9
  58. package/dist/read-file-tool.d.ts.map +1 -0
  59. package/dist/read-file-tool.js +22 -51
  60. package/dist/tool-factory-config.cjs +61 -0
  61. package/dist/{tool-provider.d.ts → tool-factory-config.d.cts} +9 -23
  62. package/dist/{tool-provider.d.cts → tool-factory-config.d.ts} +13 -30
  63. package/dist/tool-factory-config.d.ts.map +1 -0
  64. package/dist/tool-factory-config.js +36 -0
  65. package/dist/tool-factory.cjs +102 -0
  66. package/dist/tool-factory.d.cts +7 -0
  67. package/dist/tool-factory.d.ts +4 -0
  68. package/dist/tool-factory.d.ts.map +1 -0
  69. package/dist/tool-factory.js +81 -0
  70. package/dist/types.d.ts +17 -18
  71. package/dist/types.d.ts.map +1 -0
  72. package/dist/write-file-tool.cjs +45 -73
  73. package/dist/write-file-tool.d.cts +20 -3
  74. package/dist/write-file-tool.d.ts +22 -9
  75. package/dist/write-file-tool.d.ts.map +1 -0
  76. package/dist/write-file-tool.js +46 -68
  77. package/dist/write-file-tool.test.cjs +76 -32
  78. package/dist/write-file-tool.test.d.ts +7 -2
  79. package/dist/write-file-tool.test.d.ts.map +1 -0
  80. package/dist/write-file-tool.test.js +76 -32
  81. package/package.json +4 -3
  82. package/dist/tool-provider.cjs +0 -123
  83. package/dist/tool-provider.js +0 -99
@@ -30,20 +30,43 @@ var import_write_file_tool = require("./write-file-tool.js");
30
30
  var import_edit_file_tool = require("./edit-file-tool.js");
31
31
  var import_filesystem_service = require("./filesystem-service.js");
32
32
  var import_core = require("@dexto/core");
33
- const createMockLogger = () => ({
34
- debug: import_vitest.vi.fn(),
35
- info: import_vitest.vi.fn(),
36
- warn: import_vitest.vi.fn(),
37
- error: import_vitest.vi.fn(),
38
- createChild: import_vitest.vi.fn().mockReturnThis()
39
- });
33
+ const createMockLogger = () => {
34
+ const noopAsync = async () => void 0;
35
+ const logger = {
36
+ debug: import_vitest.vi.fn(),
37
+ silly: import_vitest.vi.fn(),
38
+ info: import_vitest.vi.fn(),
39
+ warn: import_vitest.vi.fn(),
40
+ error: import_vitest.vi.fn(),
41
+ trackException: import_vitest.vi.fn(),
42
+ createChild: () => logger,
43
+ setLevel: import_vitest.vi.fn(),
44
+ getLevel: () => "info",
45
+ getLogFilePath: () => null,
46
+ destroy: noopAsync
47
+ };
48
+ return logger;
49
+ };
50
+ function createToolContext(logger, approval) {
51
+ return {
52
+ logger,
53
+ services: {
54
+ approval,
55
+ search: {},
56
+ resources: {},
57
+ prompts: {},
58
+ mcp: {},
59
+ taskForker: null
60
+ }
61
+ };
62
+ }
40
63
  (0, import_vitest.describe)("Directory Approval Integration Tests", () => {
41
64
  let mockLogger;
42
65
  let tempDir;
43
66
  let fileSystemService;
44
- let directoryApproval;
45
- let isSessionApprovedMock;
46
- let addApprovedMock;
67
+ let approvalManager;
68
+ let toolContext;
69
+ const getFileSystemService = async (_context) => fileSystemService;
47
70
  (0, import_vitest.beforeEach)(async () => {
48
71
  mockLogger = createMockLogger();
49
72
  const rawTempDir = await fs.mkdtemp(path.join(os.tmpdir(), "dexto-fs-test-"));
@@ -61,12 +84,14 @@ const createMockLogger = () => ({
61
84
  mockLogger
62
85
  );
63
86
  await fileSystemService.initialize();
64
- isSessionApprovedMock = import_vitest.vi.fn().mockReturnValue(false);
65
- addApprovedMock = import_vitest.vi.fn();
66
- directoryApproval = {
67
- isSessionApproved: isSessionApprovedMock,
68
- addApproved: addApprovedMock
69
- };
87
+ approvalManager = new import_core.ApprovalManager(
88
+ {
89
+ permissions: { mode: "manual" },
90
+ elicitation: { enabled: true }
91
+ },
92
+ mockLogger
93
+ );
94
+ toolContext = createToolContext(mockLogger, approvalManager);
70
95
  import_vitest.vi.clearAllMocks();
71
96
  });
72
97
  (0, import_vitest.afterEach)(async () => {
@@ -78,22 +103,22 @@ const createMockLogger = () => ({
78
103
  (0, import_vitest.describe)("Read File Tool", () => {
79
104
  (0, import_vitest.describe)("getApprovalOverride", () => {
80
105
  (0, import_vitest.it)("should return null for paths within working directory (no prompt needed)", async () => {
81
- const tool = (0, import_read_file_tool.createReadFileTool)({
82
- fileSystemService,
83
- directoryApproval
84
- });
106
+ const tool = (0, import_read_file_tool.createReadFileTool)(getFileSystemService);
85
107
  const testFile = path.join(tempDir, "test.txt");
86
108
  await fs.writeFile(testFile, "test content");
87
- const override = await tool.getApprovalOverride?.({ file_path: testFile });
109
+ const override = await tool.getApprovalOverride?.(
110
+ tool.inputSchema.parse({ file_path: testFile }),
111
+ toolContext
112
+ );
88
113
  (0, import_vitest.expect)(override).toBeNull();
89
114
  });
90
115
  (0, import_vitest.it)("should return directory access approval for external paths", async () => {
91
- const tool = (0, import_read_file_tool.createReadFileTool)({
92
- fileSystemService,
93
- directoryApproval
94
- });
116
+ const tool = (0, import_read_file_tool.createReadFileTool)(getFileSystemService);
95
117
  const externalPath = "/external/project/file.ts";
96
- const override = await tool.getApprovalOverride?.({ file_path: externalPath });
118
+ const override = await tool.getApprovalOverride?.(
119
+ tool.inputSchema.parse({ file_path: externalPath }),
120
+ toolContext
121
+ );
97
122
  (0, import_vitest.expect)(override).not.toBeNull();
98
123
  (0, import_vitest.expect)(override?.type).toBe(import_core.ApprovalType.DIRECTORY_ACCESS);
99
124
  const metadata = override?.metadata;
@@ -103,101 +128,96 @@ const createMockLogger = () => ({
103
128
  (0, import_vitest.expect)(metadata?.toolName).toBe("read_file");
104
129
  });
105
130
  (0, import_vitest.it)("should return null when external path is session-approved", async () => {
106
- isSessionApprovedMock.mockReturnValue(true);
107
- const tool = (0, import_read_file_tool.createReadFileTool)({
108
- fileSystemService,
109
- directoryApproval
110
- });
131
+ approvalManager.addApprovedDirectory("/external/project", "session");
132
+ const tool = (0, import_read_file_tool.createReadFileTool)(getFileSystemService);
111
133
  const externalPath = "/external/project/file.ts";
112
- const override = await tool.getApprovalOverride?.({ file_path: externalPath });
113
- (0, import_vitest.expect)(override).toBeNull();
114
- (0, import_vitest.expect)(isSessionApprovedMock).toHaveBeenCalledWith(externalPath);
115
- });
116
- (0, import_vitest.it)("should return null when file_path is missing", async () => {
117
- const tool = (0, import_read_file_tool.createReadFileTool)({
118
- fileSystemService,
119
- directoryApproval
120
- });
121
- const override = await tool.getApprovalOverride?.({});
134
+ const override = await tool.getApprovalOverride?.(
135
+ tool.inputSchema.parse({ file_path: externalPath }),
136
+ toolContext
137
+ );
122
138
  (0, import_vitest.expect)(override).toBeNull();
123
139
  });
124
140
  });
125
141
  (0, import_vitest.describe)("onApprovalGranted", () => {
126
142
  (0, import_vitest.it)("should add directory as session-approved when rememberDirectory is true", async () => {
127
- const tool = (0, import_read_file_tool.createReadFileTool)({
128
- fileSystemService,
129
- directoryApproval
130
- });
143
+ const tool = (0, import_read_file_tool.createReadFileTool)(getFileSystemService);
131
144
  const externalPath = "/external/project/file.ts";
132
- await tool.getApprovalOverride?.({ file_path: externalPath });
133
- tool.onApprovalGranted?.({
134
- approvalId: "test-approval",
135
- status: import_core.ApprovalStatus.APPROVED,
136
- data: { rememberDirectory: true }
137
- });
138
- (0, import_vitest.expect)(addApprovedMock).toHaveBeenCalledWith(
139
- path.dirname(path.resolve(externalPath)),
140
- "session"
145
+ const approvalRequest = await tool.getApprovalOverride?.(
146
+ tool.inputSchema.parse({ file_path: externalPath }),
147
+ toolContext
141
148
  );
149
+ (0, import_vitest.expect)(approvalRequest).not.toBeNull();
150
+ if (!approvalRequest) {
151
+ throw new Error("Expected approval request");
152
+ }
153
+ tool.onApprovalGranted?.(
154
+ {
155
+ approvalId: "test-approval",
156
+ status: import_core.ApprovalStatus.APPROVED,
157
+ data: { rememberDirectory: true }
158
+ },
159
+ toolContext,
160
+ approvalRequest
161
+ );
162
+ (0, import_vitest.expect)(
163
+ approvalManager.getApprovedDirectories().get(path.dirname(path.resolve(externalPath)))
164
+ ).toBe("session");
142
165
  });
143
166
  (0, import_vitest.it)("should add directory as once-approved when rememberDirectory is false", async () => {
144
- const tool = (0, import_read_file_tool.createReadFileTool)({
145
- fileSystemService,
146
- directoryApproval
147
- });
167
+ const tool = (0, import_read_file_tool.createReadFileTool)(getFileSystemService);
148
168
  const externalPath = "/external/project/file.ts";
149
- await tool.getApprovalOverride?.({ file_path: externalPath });
150
- tool.onApprovalGranted?.({
151
- approvalId: "test-approval",
152
- status: import_core.ApprovalStatus.APPROVED,
153
- data: { rememberDirectory: false }
154
- });
155
- (0, import_vitest.expect)(addApprovedMock).toHaveBeenCalledWith(
156
- path.dirname(path.resolve(externalPath)),
157
- "once"
169
+ const approvalRequest = await tool.getApprovalOverride?.(
170
+ tool.inputSchema.parse({ file_path: externalPath }),
171
+ toolContext
172
+ );
173
+ (0, import_vitest.expect)(approvalRequest).not.toBeNull();
174
+ if (!approvalRequest) {
175
+ throw new Error("Expected approval request");
176
+ }
177
+ tool.onApprovalGranted?.(
178
+ {
179
+ approvalId: "test-approval",
180
+ status: import_core.ApprovalStatus.APPROVED,
181
+ data: { rememberDirectory: false }
182
+ },
183
+ toolContext,
184
+ approvalRequest
158
185
  );
186
+ (0, import_vitest.expect)(
187
+ approvalManager.getApprovedDirectories().get(path.dirname(path.resolve(externalPath)))
188
+ ).toBe("once");
159
189
  });
160
190
  (0, import_vitest.it)("should default to once-approved when rememberDirectory is not specified", async () => {
161
- const tool = (0, import_read_file_tool.createReadFileTool)({
162
- fileSystemService,
163
- directoryApproval
164
- });
191
+ const tool = (0, import_read_file_tool.createReadFileTool)(getFileSystemService);
165
192
  const externalPath = "/external/project/file.ts";
166
- await tool.getApprovalOverride?.({ file_path: externalPath });
167
- tool.onApprovalGranted?.({
168
- approvalId: "test-approval",
169
- status: import_core.ApprovalStatus.APPROVED,
170
- data: {}
171
- });
172
- (0, import_vitest.expect)(addApprovedMock).toHaveBeenCalledWith(
173
- path.dirname(path.resolve(externalPath)),
174
- "once"
193
+ const approvalRequest = await tool.getApprovalOverride?.(
194
+ tool.inputSchema.parse({ file_path: externalPath }),
195
+ toolContext
175
196
  );
176
- });
177
- (0, import_vitest.it)("should not call addApproved when directoryApproval is not provided", async () => {
178
- const tool = (0, import_read_file_tool.createReadFileTool)({
179
- fileSystemService,
180
- directoryApproval: void 0
181
- });
182
- const externalPath = "/external/project/file.ts";
183
- await tool.getApprovalOverride?.({ file_path: externalPath });
184
- tool.onApprovalGranted?.({
185
- approvalId: "test-approval",
186
- status: import_core.ApprovalStatus.APPROVED,
187
- data: { rememberDirectory: true }
188
- });
189
- (0, import_vitest.expect)(addApprovedMock).not.toHaveBeenCalled();
197
+ (0, import_vitest.expect)(approvalRequest).not.toBeNull();
198
+ if (!approvalRequest) {
199
+ throw new Error("Expected approval request");
200
+ }
201
+ tool.onApprovalGranted?.(
202
+ {
203
+ approvalId: "test-approval",
204
+ status: import_core.ApprovalStatus.APPROVED,
205
+ data: {}
206
+ },
207
+ toolContext,
208
+ approvalRequest
209
+ );
210
+ (0, import_vitest.expect)(
211
+ approvalManager.getApprovedDirectories().get(path.dirname(path.resolve(externalPath)))
212
+ ).toBe("once");
190
213
  });
191
214
  });
192
215
  (0, import_vitest.describe)("execute", () => {
193
216
  (0, import_vitest.it)("should read file contents within working directory", async () => {
194
- const tool = (0, import_read_file_tool.createReadFileTool)({
195
- fileSystemService,
196
- directoryApproval
197
- });
217
+ const tool = (0, import_read_file_tool.createReadFileTool)(getFileSystemService);
198
218
  const testFile = path.join(tempDir, "readable.txt");
199
219
  await fs.writeFile(testFile, "Hello, world!\nLine 2");
200
- const result = await tool.execute({ file_path: testFile }, {});
220
+ const result = await tool.execute({ file_path: testFile }, toolContext);
201
221
  (0, import_vitest.expect)(result.content).toBe("Hello, world!\nLine 2");
202
222
  (0, import_vitest.expect)(result.lines).toBe(2);
203
223
  });
@@ -206,27 +226,21 @@ const createMockLogger = () => ({
206
226
  (0, import_vitest.describe)("Write File Tool", () => {
207
227
  (0, import_vitest.describe)("getApprovalOverride", () => {
208
228
  (0, import_vitest.it)("should return null for paths within working directory", async () => {
209
- const tool = (0, import_write_file_tool.createWriteFileTool)({
210
- fileSystemService,
211
- directoryApproval
212
- });
229
+ const tool = (0, import_write_file_tool.createWriteFileTool)(getFileSystemService);
213
230
  const testFile = path.join(tempDir, "new-file.txt");
214
- const override = await tool.getApprovalOverride?.({
215
- file_path: testFile,
216
- content: "test"
217
- });
231
+ const override = await tool.getApprovalOverride?.(
232
+ tool.inputSchema.parse({ file_path: testFile, content: "test" }),
233
+ toolContext
234
+ );
218
235
  (0, import_vitest.expect)(override).toBeNull();
219
236
  });
220
237
  (0, import_vitest.it)("should return directory access approval for external paths", async () => {
221
- const tool = (0, import_write_file_tool.createWriteFileTool)({
222
- fileSystemService,
223
- directoryApproval
224
- });
238
+ const tool = (0, import_write_file_tool.createWriteFileTool)(getFileSystemService);
225
239
  const externalPath = "/external/project/new.ts";
226
- const override = await tool.getApprovalOverride?.({
227
- file_path: externalPath,
228
- content: "test"
229
- });
240
+ const override = await tool.getApprovalOverride?.(
241
+ tool.inputSchema.parse({ file_path: externalPath, content: "test" }),
242
+ toolContext
243
+ );
230
244
  (0, import_vitest.expect)(override).not.toBeNull();
231
245
  (0, import_vitest.expect)(override?.type).toBe(import_core.ApprovalType.DIRECTORY_ACCESS);
232
246
  const metadata = override?.metadata;
@@ -234,65 +248,69 @@ const createMockLogger = () => ({
234
248
  (0, import_vitest.expect)(metadata?.toolName).toBe("write_file");
235
249
  });
236
250
  (0, import_vitest.it)("should return null when external path is session-approved", async () => {
237
- isSessionApprovedMock.mockReturnValue(true);
238
- const tool = (0, import_write_file_tool.createWriteFileTool)({
239
- fileSystemService,
240
- directoryApproval
241
- });
251
+ approvalManager.addApprovedDirectory("/external/project", "session");
252
+ const tool = (0, import_write_file_tool.createWriteFileTool)(getFileSystemService);
242
253
  const externalPath = "/external/project/new.ts";
243
- const override = await tool.getApprovalOverride?.({
244
- file_path: externalPath,
245
- content: "test"
246
- });
254
+ const override = await tool.getApprovalOverride?.(
255
+ tool.inputSchema.parse({ file_path: externalPath, content: "test" }),
256
+ toolContext
257
+ );
247
258
  (0, import_vitest.expect)(override).toBeNull();
248
259
  });
249
260
  });
250
261
  (0, import_vitest.describe)("onApprovalGranted", () => {
251
262
  (0, import_vitest.it)("should add directory as session-approved when rememberDirectory is true", async () => {
252
- const tool = (0, import_write_file_tool.createWriteFileTool)({
253
- fileSystemService,
254
- directoryApproval
255
- });
263
+ const tool = (0, import_write_file_tool.createWriteFileTool)(getFileSystemService);
256
264
  const externalPath = "/external/project/new.ts";
257
- await tool.getApprovalOverride?.({ file_path: externalPath, content: "test" });
258
- tool.onApprovalGranted?.({
259
- approvalId: "test-approval",
260
- status: import_core.ApprovalStatus.APPROVED,
261
- data: { rememberDirectory: true }
262
- });
263
- (0, import_vitest.expect)(addApprovedMock).toHaveBeenCalledWith(
264
- path.dirname(path.resolve(externalPath)),
265
- "session"
265
+ const approvalRequest = await tool.getApprovalOverride?.(
266
+ tool.inputSchema.parse({ file_path: externalPath, content: "test" }),
267
+ toolContext
268
+ );
269
+ (0, import_vitest.expect)(approvalRequest).not.toBeNull();
270
+ if (!approvalRequest) {
271
+ throw new Error("Expected approval request");
272
+ }
273
+ tool.onApprovalGranted?.(
274
+ {
275
+ approvalId: "test-approval",
276
+ status: import_core.ApprovalStatus.APPROVED,
277
+ data: { rememberDirectory: true }
278
+ },
279
+ toolContext,
280
+ approvalRequest
266
281
  );
282
+ (0, import_vitest.expect)(
283
+ approvalManager.getApprovedDirectories().get(path.dirname(path.resolve(externalPath)))
284
+ ).toBe("session");
267
285
  });
268
286
  });
269
287
  });
270
288
  (0, import_vitest.describe)("Edit File Tool", () => {
271
289
  (0, import_vitest.describe)("getApprovalOverride", () => {
272
290
  (0, import_vitest.it)("should return null for paths within working directory", async () => {
273
- const tool = (0, import_edit_file_tool.createEditFileTool)({
274
- fileSystemService,
275
- directoryApproval
276
- });
291
+ const tool = (0, import_edit_file_tool.createEditFileTool)(getFileSystemService);
277
292
  const testFile = path.join(tempDir, "existing.txt");
278
- const override = await tool.getApprovalOverride?.({
279
- file_path: testFile,
280
- old_string: "old",
281
- new_string: "new"
282
- });
293
+ const override = await tool.getApprovalOverride?.(
294
+ tool.inputSchema.parse({
295
+ file_path: testFile,
296
+ old_string: "old",
297
+ new_string: "new"
298
+ }),
299
+ toolContext
300
+ );
283
301
  (0, import_vitest.expect)(override).toBeNull();
284
302
  });
285
303
  (0, import_vitest.it)("should return directory access approval for external paths", async () => {
286
- const tool = (0, import_edit_file_tool.createEditFileTool)({
287
- fileSystemService,
288
- directoryApproval
289
- });
304
+ const tool = (0, import_edit_file_tool.createEditFileTool)(getFileSystemService);
290
305
  const externalPath = "/external/project/existing.ts";
291
- const override = await tool.getApprovalOverride?.({
292
- file_path: externalPath,
293
- old_string: "old",
294
- new_string: "new"
295
- });
306
+ const override = await tool.getApprovalOverride?.(
307
+ tool.inputSchema.parse({
308
+ file_path: externalPath,
309
+ old_string: "old",
310
+ new_string: "new"
311
+ }),
312
+ toolContext
313
+ );
296
314
  (0, import_vitest.expect)(override).not.toBeNull();
297
315
  (0, import_vitest.expect)(override?.type).toBe(import_core.ApprovalType.DIRECTORY_ACCESS);
298
316
  const metadata = override?.metadata;
@@ -300,116 +318,129 @@ const createMockLogger = () => ({
300
318
  (0, import_vitest.expect)(metadata?.toolName).toBe("edit_file");
301
319
  });
302
320
  (0, import_vitest.it)("should return null when external path is session-approved", async () => {
303
- isSessionApprovedMock.mockReturnValue(true);
304
- const tool = (0, import_edit_file_tool.createEditFileTool)({
305
- fileSystemService,
306
- directoryApproval
307
- });
321
+ approvalManager.addApprovedDirectory("/external/project", "session");
322
+ const tool = (0, import_edit_file_tool.createEditFileTool)(getFileSystemService);
308
323
  const externalPath = "/external/project/existing.ts";
309
- const override = await tool.getApprovalOverride?.({
310
- file_path: externalPath,
311
- old_string: "old",
312
- new_string: "new"
313
- });
324
+ const override = await tool.getApprovalOverride?.(
325
+ tool.inputSchema.parse({
326
+ file_path: externalPath,
327
+ old_string: "old",
328
+ new_string: "new"
329
+ }),
330
+ toolContext
331
+ );
314
332
  (0, import_vitest.expect)(override).toBeNull();
315
333
  });
316
334
  });
317
335
  });
318
336
  (0, import_vitest.describe)("Session vs Once Approval Scenarios", () => {
319
337
  (0, import_vitest.it)("should not prompt for subsequent requests after session approval", async () => {
320
- const tool = (0, import_read_file_tool.createReadFileTool)({
321
- fileSystemService,
322
- directoryApproval
323
- });
338
+ const tool = (0, import_read_file_tool.createReadFileTool)(getFileSystemService);
324
339
  const externalPath1 = "/external/project/file1.ts";
325
340
  const externalPath2 = "/external/project/file2.ts";
326
- let override = await tool.getApprovalOverride?.({ file_path: externalPath1 });
341
+ let override = await tool.getApprovalOverride?.(
342
+ tool.inputSchema.parse({ file_path: externalPath1 }),
343
+ toolContext
344
+ );
327
345
  (0, import_vitest.expect)(override).not.toBeNull();
328
- tool.onApprovalGranted?.({
329
- approvalId: "approval-1",
330
- status: import_core.ApprovalStatus.APPROVED,
331
- data: { rememberDirectory: true }
332
- });
333
- (0, import_vitest.expect)(addApprovedMock).toHaveBeenCalledWith(
334
- path.dirname(path.resolve(externalPath1)),
335
- "session"
346
+ if (!override) {
347
+ throw new Error("Expected approval request");
348
+ }
349
+ tool.onApprovalGranted?.(
350
+ {
351
+ approvalId: "approval-1",
352
+ status: import_core.ApprovalStatus.APPROVED,
353
+ data: { rememberDirectory: true }
354
+ },
355
+ toolContext,
356
+ override
357
+ );
358
+ (0, import_vitest.expect)(
359
+ approvalManager.getApprovedDirectories().get(path.dirname(path.resolve(externalPath1)))
360
+ ).toBe("session");
361
+ override = await tool.getApprovalOverride?.(
362
+ tool.inputSchema.parse({ file_path: externalPath2 }),
363
+ toolContext
336
364
  );
337
- isSessionApprovedMock.mockReturnValue(true);
338
- override = await tool.getApprovalOverride?.({ file_path: externalPath2 });
339
365
  (0, import_vitest.expect)(override).toBeNull();
340
366
  });
341
367
  (0, import_vitest.it)("should prompt for subsequent requests after once approval", async () => {
342
- const tool = (0, import_read_file_tool.createReadFileTool)({
343
- fileSystemService,
344
- directoryApproval
345
- });
368
+ const tool = (0, import_read_file_tool.createReadFileTool)(getFileSystemService);
346
369
  const externalPath1 = "/external/project/file1.ts";
347
370
  const externalPath2 = "/external/project/file2.ts";
348
- let override = await tool.getApprovalOverride?.({ file_path: externalPath1 });
371
+ let override = await tool.getApprovalOverride?.(
372
+ tool.inputSchema.parse({ file_path: externalPath1 }),
373
+ toolContext
374
+ );
349
375
  (0, import_vitest.expect)(override).not.toBeNull();
350
- tool.onApprovalGranted?.({
351
- approvalId: "approval-1",
352
- status: import_core.ApprovalStatus.APPROVED,
353
- data: { rememberDirectory: false }
354
- });
355
- (0, import_vitest.expect)(addApprovedMock).toHaveBeenCalledWith(
356
- path.dirname(path.resolve(externalPath1)),
357
- "once"
376
+ if (!override) {
377
+ throw new Error("Expected approval request");
378
+ }
379
+ tool.onApprovalGranted?.(
380
+ {
381
+ approvalId: "approval-1",
382
+ status: import_core.ApprovalStatus.APPROVED,
383
+ data: { rememberDirectory: false }
384
+ },
385
+ toolContext,
386
+ override
387
+ );
388
+ (0, import_vitest.expect)(
389
+ approvalManager.getApprovedDirectories().get(path.dirname(path.resolve(externalPath1)))
390
+ ).toBe("once");
391
+ override = await tool.getApprovalOverride?.(
392
+ tool.inputSchema.parse({ file_path: externalPath2 }),
393
+ toolContext
358
394
  );
359
- isSessionApprovedMock.mockReturnValue(false);
360
- override = await tool.getApprovalOverride?.({ file_path: externalPath2 });
361
395
  (0, import_vitest.expect)(override).not.toBeNull();
362
396
  });
363
397
  });
364
398
  (0, import_vitest.describe)("Path Containment Scenarios", () => {
365
399
  (0, import_vitest.it)("should cover child paths when parent directory is session-approved", async () => {
366
- const tool = (0, import_read_file_tool.createReadFileTool)({
367
- fileSystemService,
368
- directoryApproval
369
- });
370
- isSessionApprovedMock.mockImplementation((filePath) => {
371
- const normalizedPath = path.resolve(filePath);
372
- const approvedDir = "/external/project";
373
- return normalizedPath.startsWith(approvedDir + path.sep) || normalizedPath === approvedDir;
374
- });
375
- let override = await tool.getApprovalOverride?.({
376
- file_path: "/external/project/file.ts"
377
- });
400
+ const tool = (0, import_read_file_tool.createReadFileTool)(getFileSystemService);
401
+ approvalManager.addApprovedDirectory("/external/project", "session");
402
+ let override = await tool.getApprovalOverride?.(
403
+ tool.inputSchema.parse({ file_path: "/external/project/file.ts" }),
404
+ toolContext
405
+ );
378
406
  (0, import_vitest.expect)(override).toBeNull();
379
- override = await tool.getApprovalOverride?.({
380
- file_path: "/external/project/deep/nested/file.ts"
381
- });
407
+ override = await tool.getApprovalOverride?.(
408
+ tool.inputSchema.parse({ file_path: "/external/project/deep/nested/file.ts" }),
409
+ toolContext
410
+ );
382
411
  (0, import_vitest.expect)(override).toBeNull();
383
412
  });
384
413
  (0, import_vitest.it)("should NOT cover sibling directories", async () => {
385
- const tool = (0, import_read_file_tool.createReadFileTool)({
386
- fileSystemService,
387
- directoryApproval
388
- });
389
- isSessionApprovedMock.mockImplementation((filePath) => {
390
- const normalizedPath = path.resolve(filePath);
391
- const approvedDir = "/external/sub";
392
- return normalizedPath.startsWith(approvedDir + path.sep) || normalizedPath === approvedDir;
393
- });
394
- let override = await tool.getApprovalOverride?.({ file_path: "/external/sub/file.ts" });
414
+ const tool = (0, import_read_file_tool.createReadFileTool)(getFileSystemService);
415
+ approvalManager.addApprovedDirectory("/external/sub", "session");
416
+ let override = await tool.getApprovalOverride?.(
417
+ tool.inputSchema.parse({ file_path: "/external/sub/file.ts" }),
418
+ toolContext
419
+ );
395
420
  (0, import_vitest.expect)(override).toBeNull();
396
- override = await tool.getApprovalOverride?.({ file_path: "/external/other/file.ts" });
421
+ override = await tool.getApprovalOverride?.(
422
+ tool.inputSchema.parse({ file_path: "/external/other/file.ts" }),
423
+ toolContext
424
+ );
397
425
  (0, import_vitest.expect)(override).not.toBeNull();
398
426
  });
399
427
  });
400
428
  (0, import_vitest.describe)("Different External Directories Scenarios", () => {
401
429
  (0, import_vitest.it)("should require separate approval for different external directories", async () => {
402
- const tool = (0, import_read_file_tool.createReadFileTool)({
403
- fileSystemService,
404
- directoryApproval
405
- });
430
+ const tool = (0, import_read_file_tool.createReadFileTool)(getFileSystemService);
406
431
  const dir1Path = "/external/project1/file.ts";
407
432
  const dir2Path = "/external/project2/file.ts";
408
- const override1 = await tool.getApprovalOverride?.({ file_path: dir1Path });
433
+ const override1 = await tool.getApprovalOverride?.(
434
+ tool.inputSchema.parse({ file_path: dir1Path }),
435
+ toolContext
436
+ );
409
437
  (0, import_vitest.expect)(override1).not.toBeNull();
410
438
  const metadata1 = override1?.metadata;
411
439
  (0, import_vitest.expect)(metadata1?.parentDir).toBe("/external/project1");
412
- const override2 = await tool.getApprovalOverride?.({ file_path: dir2Path });
440
+ const override2 = await tool.getApprovalOverride?.(
441
+ tool.inputSchema.parse({ file_path: dir2Path }),
442
+ toolContext
443
+ );
413
444
  (0, import_vitest.expect)(override2).not.toBeNull();
414
445
  const metadata2 = override2?.metadata;
415
446
  (0, import_vitest.expect)(metadata2?.parentDir).toBe("/external/project2");
@@ -417,55 +448,58 @@ const createMockLogger = () => ({
417
448
  });
418
449
  (0, import_vitest.describe)("Mixed Operations Scenarios", () => {
419
450
  (0, import_vitest.it)("should share directory approval across different file operations", async () => {
420
- const readTool = (0, import_read_file_tool.createReadFileTool)({ fileSystemService, directoryApproval });
421
- const writeTool = (0, import_write_file_tool.createWriteFileTool)({ fileSystemService, directoryApproval });
422
- const editTool = (0, import_edit_file_tool.createEditFileTool)({ fileSystemService, directoryApproval });
451
+ const readTool = (0, import_read_file_tool.createReadFileTool)(getFileSystemService);
452
+ const writeTool = (0, import_write_file_tool.createWriteFileTool)(getFileSystemService);
453
+ const editTool = (0, import_edit_file_tool.createEditFileTool)(getFileSystemService);
423
454
  const externalDir = "/external/project";
455
+ const approvalRequest = await readTool.getApprovalOverride?.(
456
+ readTool.inputSchema.parse({ file_path: `${externalDir}/file1.ts` }),
457
+ toolContext
458
+ );
459
+ (0, import_vitest.expect)(approvalRequest).not.toBeNull();
460
+ if (!approvalRequest) {
461
+ throw new Error("Expected approval request");
462
+ }
463
+ readTool.onApprovalGranted?.(
464
+ {
465
+ approvalId: "approval-1",
466
+ status: import_core.ApprovalStatus.APPROVED,
467
+ data: { rememberDirectory: true }
468
+ },
469
+ toolContext,
470
+ approvalRequest
471
+ );
424
472
  (0, import_vitest.expect)(
425
- await readTool.getApprovalOverride?.({ file_path: `${externalDir}/file1.ts` })
426
- ).not.toBeNull();
427
- (0, import_vitest.expect)(
428
- await writeTool.getApprovalOverride?.({
429
- file_path: `${externalDir}/file2.ts`,
430
- content: "test"
431
- })
432
- ).not.toBeNull();
433
- (0, import_vitest.expect)(
434
- await editTool.getApprovalOverride?.({
435
- file_path: `${externalDir}/file3.ts`,
436
- old_string: "a",
437
- new_string: "b"
438
- })
439
- ).not.toBeNull();
440
- isSessionApprovedMock.mockReturnValue(true);
441
- (0, import_vitest.expect)(
442
- await readTool.getApprovalOverride?.({ file_path: `${externalDir}/file1.ts` })
443
- ).toBeNull();
444
- (0, import_vitest.expect)(
445
- await writeTool.getApprovalOverride?.({
446
- file_path: `${externalDir}/file2.ts`,
447
- content: "test"
448
- })
473
+ await writeTool.getApprovalOverride?.(
474
+ writeTool.inputSchema.parse({
475
+ file_path: `${externalDir}/file2.ts`,
476
+ content: "test"
477
+ }),
478
+ toolContext
479
+ )
449
480
  ).toBeNull();
450
481
  (0, import_vitest.expect)(
451
- await editTool.getApprovalOverride?.({
452
- file_path: `${externalDir}/file3.ts`,
453
- old_string: "a",
454
- new_string: "b"
455
- })
482
+ await editTool.getApprovalOverride?.(
483
+ editTool.inputSchema.parse({
484
+ file_path: `${externalDir}/file3.ts`,
485
+ old_string: "a",
486
+ new_string: "b"
487
+ }),
488
+ toolContext
489
+ )
456
490
  ).toBeNull();
457
491
  });
458
492
  });
459
- (0, import_vitest.describe)("Without Directory Approval Callbacks", () => {
460
- (0, import_vitest.it)("should work without directory approval callbacks (all paths need normal tool confirmation)", async () => {
461
- const tool = (0, import_read_file_tool.createReadFileTool)({
462
- fileSystemService,
463
- directoryApproval: void 0
464
- });
465
- const override = await tool.getApprovalOverride?.({
466
- file_path: "/external/project/file.ts"
467
- });
468
- (0, import_vitest.expect)(override).not.toBeNull();
493
+ (0, import_vitest.describe)("Without ApprovalManager in context", () => {
494
+ (0, import_vitest.it)("should throw for external paths", async () => {
495
+ const tool = (0, import_read_file_tool.createReadFileTool)(getFileSystemService);
496
+ const contextWithoutApprovalManager = { logger: mockLogger };
497
+ await (0, import_vitest.expect)(
498
+ tool.getApprovalOverride?.(
499
+ tool.inputSchema.parse({ file_path: "/external/project/file.ts" }),
500
+ contextWithoutApprovalManager
501
+ )
502
+ ).rejects.toBeInstanceOf(import_core.DextoRuntimeError);
469
503
  });
470
504
  });
471
505
  });