@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
@@ -25,25 +25,49 @@ var import_vitest = require("vitest");
25
25
  var path = __toESM(require("node:path"), 1);
26
26
  var fs = __toESM(require("node:fs/promises"), 1);
27
27
  var os = __toESM(require("node:os"), 1);
28
+ var import_core = require("@dexto/core");
29
+ var import_filesystem_service = require("./filesystem-service.js");
28
30
  var import_read_file_tool = require("./read-file-tool.js");
29
31
  var import_write_file_tool = require("./write-file-tool.js");
30
32
  var import_edit_file_tool = require("./edit-file-tool.js");
31
- var import_filesystem_service = require("./filesystem-service.js");
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
+ createFileOnlyChild: () => logger,
44
+ setLevel: import_vitest.vi.fn(),
45
+ getLevel: () => "info",
46
+ getLogFilePath: () => null,
47
+ destroy: noopAsync
48
+ };
49
+ return logger;
50
+ };
51
+ function createToolContext(logger, approval) {
52
+ return {
53
+ logger,
54
+ services: {
55
+ approval,
56
+ search: {},
57
+ resources: {},
58
+ prompts: {},
59
+ mcp: {},
60
+ taskForker: null
61
+ }
62
+ };
63
+ }
40
64
  (0, import_vitest.describe)("Directory Approval Integration Tests", () => {
41
65
  let mockLogger;
42
66
  let tempDir;
43
67
  let fileSystemService;
44
- let directoryApproval;
45
- let isSessionApprovedMock;
46
- let addApprovedMock;
68
+ let approvalManager;
69
+ let toolContext;
70
+ const getFileSystemService = async (_context) => fileSystemService;
47
71
  (0, import_vitest.beforeEach)(async () => {
48
72
  mockLogger = createMockLogger();
49
73
  const rawTempDir = await fs.mkdtemp(path.join(os.tmpdir(), "dexto-fs-test-"));
@@ -61,12 +85,14 @@ const createMockLogger = () => ({
61
85
  mockLogger
62
86
  );
63
87
  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
- };
88
+ approvalManager = new import_core.ApprovalManager(
89
+ {
90
+ permissions: { mode: "manual" },
91
+ elicitation: { enabled: true }
92
+ },
93
+ mockLogger
94
+ );
95
+ toolContext = createToolContext(mockLogger, approvalManager);
70
96
  import_vitest.vi.clearAllMocks();
71
97
  });
72
98
  (0, import_vitest.afterEach)(async () => {
@@ -75,397 +101,156 @@ const createMockLogger = () => ({
75
101
  } catch {
76
102
  }
77
103
  });
78
- (0, import_vitest.describe)("Read File Tool", () => {
79
- (0, import_vitest.describe)("getApprovalOverride", () => {
80
- (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
- });
85
- const testFile = path.join(tempDir, "test.txt");
86
- await fs.writeFile(testFile, "test content");
87
- const override = await tool.getApprovalOverride?.({ file_path: testFile });
88
- (0, import_vitest.expect)(override).toBeNull();
89
- });
90
- (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
- });
95
- const externalPath = "/external/project/file.ts";
96
- const override = await tool.getApprovalOverride?.({ file_path: externalPath });
97
- (0, import_vitest.expect)(override).not.toBeNull();
98
- (0, import_vitest.expect)(override?.type).toBe(import_core.ApprovalType.DIRECTORY_ACCESS);
99
- const metadata = override?.metadata;
100
- (0, import_vitest.expect)(metadata?.path).toBe(path.resolve(externalPath));
101
- (0, import_vitest.expect)(metadata?.parentDir).toBe(path.dirname(path.resolve(externalPath)));
102
- (0, import_vitest.expect)(metadata?.operation).toBe("read");
103
- (0, import_vitest.expect)(metadata?.toolName).toBe("read_file");
104
- });
105
- (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
- });
111
- 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?.({});
122
- (0, import_vitest.expect)(override).toBeNull();
123
- });
124
- });
125
- (0, import_vitest.describe)("onApprovalGranted", () => {
126
- (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
- });
131
- 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"
141
- );
142
- });
143
- (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
- });
148
- 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"
158
- );
159
- });
160
- (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
- });
165
- 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"
175
- );
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();
190
- });
104
+ (0, import_vitest.describe)("getApprovalOverride", () => {
105
+ (0, import_vitest.it)("should return null for paths within config-allowed roots", async () => {
106
+ const tool = (0, import_read_file_tool.createReadFileTool)(getFileSystemService);
107
+ const overrideFn = tool.approval?.override;
108
+ (0, import_vitest.expect)(overrideFn).toBeDefined();
109
+ const testFile = path.join(tempDir, "test.txt");
110
+ await fs.writeFile(testFile, "test content");
111
+ const metadata = await overrideFn(
112
+ tool.inputSchema.parse({ file_path: testFile }),
113
+ toolContext
114
+ );
115
+ (0, import_vitest.expect)(metadata).toBeNull();
191
116
  });
192
- (0, import_vitest.describe)("execute", () => {
193
- (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
- });
198
- const testFile = path.join(tempDir, "readable.txt");
199
- await fs.writeFile(testFile, "Hello, world!\nLine 2");
200
- const result = await tool.execute({ file_path: testFile }, {});
201
- (0, import_vitest.expect)(result.content).toBe("Hello, world!\nLine 2");
202
- (0, import_vitest.expect)(result.lines).toBe(2);
117
+ (0, import_vitest.it)("should return directory access metadata for external paths", async () => {
118
+ const tool = (0, import_read_file_tool.createReadFileTool)(getFileSystemService);
119
+ const overrideFn = tool.approval?.override;
120
+ (0, import_vitest.expect)(overrideFn).toBeDefined();
121
+ const externalPath = "/external/project/file.ts";
122
+ const metadata = await overrideFn(
123
+ tool.inputSchema.parse({ file_path: externalPath }),
124
+ toolContext
125
+ );
126
+ (0, import_vitest.expect)(metadata).not.toBeNull();
127
+ (0, import_vitest.expect)(metadata).toMatchObject({
128
+ type: "directory_access",
129
+ metadata: {
130
+ path: path.resolve(externalPath),
131
+ parentDir: path.dirname(path.resolve(externalPath)),
132
+ operation: "read",
133
+ toolName: "read_file"
134
+ }
203
135
  });
204
136
  });
205
- });
206
- (0, import_vitest.describe)("Write File Tool", () => {
207
- (0, import_vitest.describe)("getApprovalOverride", () => {
208
- (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
- });
213
- const testFile = path.join(tempDir, "new-file.txt");
214
- const override = await tool.getApprovalOverride?.({
215
- file_path: testFile,
216
- content: "test"
217
- });
218
- (0, import_vitest.expect)(override).toBeNull();
219
- });
220
- (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
- });
225
- const externalPath = "/external/project/new.ts";
226
- const override = await tool.getApprovalOverride?.({
227
- file_path: externalPath,
228
- content: "test"
229
- });
230
- (0, import_vitest.expect)(override).not.toBeNull();
231
- (0, import_vitest.expect)(override?.type).toBe(import_core.ApprovalType.DIRECTORY_ACCESS);
232
- const metadata = override?.metadata;
233
- (0, import_vitest.expect)(metadata?.operation).toBe("write");
234
- (0, import_vitest.expect)(metadata?.toolName).toBe("write_file");
235
- });
236
- (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
- });
242
- const externalPath = "/external/project/new.ts";
243
- const override = await tool.getApprovalOverride?.({
244
- file_path: externalPath,
245
- content: "test"
246
- });
247
- (0, import_vitest.expect)(override).toBeNull();
248
- });
137
+ (0, import_vitest.it)("should return null when external path is session-approved", async () => {
138
+ approvalManager.addApprovedDirectory("/external/project", "session");
139
+ const tool = (0, import_read_file_tool.createReadFileTool)(getFileSystemService);
140
+ const overrideFn = tool.approval?.override;
141
+ (0, import_vitest.expect)(overrideFn).toBeDefined();
142
+ const externalPath = "/external/project/file.ts";
143
+ const metadata = await overrideFn(
144
+ tool.inputSchema.parse({ file_path: externalPath }),
145
+ toolContext
146
+ );
147
+ (0, import_vitest.expect)(metadata).toBeNull();
249
148
  });
250
- (0, import_vitest.describe)("onApprovalGranted", () => {
251
- (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
- });
256
- 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"
266
- );
267
- });
149
+ (0, import_vitest.it)("should still return metadata when external path is once-approved (prompt again)", async () => {
150
+ approvalManager.addApprovedDirectory("/external/project", "once");
151
+ const tool = (0, import_read_file_tool.createReadFileTool)(getFileSystemService);
152
+ const overrideFn = tool.approval?.override;
153
+ (0, import_vitest.expect)(overrideFn).toBeDefined();
154
+ const externalPath = "/external/project/file.ts";
155
+ const metadata = await overrideFn(
156
+ tool.inputSchema.parse({ file_path: externalPath }),
157
+ toolContext
158
+ );
159
+ (0, import_vitest.expect)(metadata).not.toBeNull();
268
160
  });
269
161
  });
270
- (0, import_vitest.describe)("Edit File Tool", () => {
271
- (0, import_vitest.describe)("getApprovalOverride", () => {
272
- (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
- });
277
- 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
- });
283
- (0, import_vitest.expect)(override).toBeNull();
284
- });
285
- (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
- });
290
- 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
- });
296
- (0, import_vitest.expect)(override).not.toBeNull();
297
- (0, import_vitest.expect)(override?.type).toBe(import_core.ApprovalType.DIRECTORY_ACCESS);
298
- const metadata = override?.metadata;
299
- (0, import_vitest.expect)(metadata?.operation).toBe("edit");
300
- (0, import_vitest.expect)(metadata?.toolName).toBe("edit_file");
162
+ (0, import_vitest.describe)("Different tool operations", () => {
163
+ (0, import_vitest.it)("should label write operations correctly", async () => {
164
+ const tool = (0, import_write_file_tool.createWriteFileTool)(getFileSystemService);
165
+ const overrideFn = tool.approval?.override;
166
+ (0, import_vitest.expect)(overrideFn).toBeDefined();
167
+ const externalPath = "/external/project/new.ts";
168
+ const metadata = await overrideFn(
169
+ tool.inputSchema.parse({ file_path: externalPath, content: "test" }),
170
+ toolContext
171
+ );
172
+ (0, import_vitest.expect)(metadata).not.toBeNull();
173
+ (0, import_vitest.expect)(metadata).toMatchObject({
174
+ type: "directory_access",
175
+ metadata: {
176
+ path: path.resolve(externalPath),
177
+ parentDir: path.dirname(path.resolve(externalPath)),
178
+ operation: "write",
179
+ toolName: "write_file"
180
+ }
301
181
  });
302
- (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
- });
308
- const externalPath = "/external/project/existing.ts";
309
- const override = await tool.getApprovalOverride?.({
182
+ });
183
+ (0, import_vitest.it)("should label edit operations correctly", async () => {
184
+ const tool = (0, import_edit_file_tool.createEditFileTool)(getFileSystemService);
185
+ const overrideFn = tool.approval?.override;
186
+ (0, import_vitest.expect)(overrideFn).toBeDefined();
187
+ const externalPath = "/external/project/existing.ts";
188
+ const metadata = await overrideFn(
189
+ tool.inputSchema.parse({
310
190
  file_path: externalPath,
311
191
  old_string: "old",
312
192
  new_string: "new"
313
- });
314
- (0, import_vitest.expect)(override).toBeNull();
315
- });
316
- });
317
- });
318
- (0, import_vitest.describe)("Session vs Once Approval Scenarios", () => {
319
- (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
- });
324
- const externalPath1 = "/external/project/file1.ts";
325
- const externalPath2 = "/external/project/file2.ts";
326
- let override = await tool.getApprovalOverride?.({ file_path: externalPath1 });
327
- (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"
193
+ }),
194
+ toolContext
336
195
  );
337
- isSessionApprovedMock.mockReturnValue(true);
338
- override = await tool.getApprovalOverride?.({ file_path: externalPath2 });
339
- (0, import_vitest.expect)(override).toBeNull();
340
- });
341
- (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
- });
346
- const externalPath1 = "/external/project/file1.ts";
347
- const externalPath2 = "/external/project/file2.ts";
348
- let override = await tool.getApprovalOverride?.({ file_path: externalPath1 });
349
- (0, import_vitest.expect)(override).not.toBeNull();
350
- tool.onApprovalGranted?.({
351
- approvalId: "approval-1",
352
- status: import_core.ApprovalStatus.APPROVED,
353
- data: { rememberDirectory: false }
196
+ (0, import_vitest.expect)(metadata).not.toBeNull();
197
+ (0, import_vitest.expect)(metadata).toMatchObject({
198
+ type: "directory_access",
199
+ metadata: {
200
+ path: path.resolve(externalPath),
201
+ parentDir: path.dirname(path.resolve(externalPath)),
202
+ operation: "edit",
203
+ toolName: "edit_file"
204
+ }
354
205
  });
355
- (0, import_vitest.expect)(addApprovedMock).toHaveBeenCalledWith(
356
- path.dirname(path.resolve(externalPath1)),
357
- "once"
358
- );
359
- isSessionApprovedMock.mockReturnValue(false);
360
- override = await tool.getApprovalOverride?.({ file_path: externalPath2 });
361
- (0, import_vitest.expect)(override).not.toBeNull();
362
206
  });
363
207
  });
364
- (0, import_vitest.describe)("Path Containment Scenarios", () => {
208
+ (0, import_vitest.describe)("Path containment scenarios", () => {
365
209
  (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
- });
378
- (0, import_vitest.expect)(override).toBeNull();
379
- override = await tool.getApprovalOverride?.({
380
- file_path: "/external/project/deep/nested/file.ts"
381
- });
382
- (0, import_vitest.expect)(override).toBeNull();
210
+ const tool = (0, import_read_file_tool.createReadFileTool)(getFileSystemService);
211
+ const overrideFn = tool.approval?.override;
212
+ (0, import_vitest.expect)(overrideFn).toBeDefined();
213
+ approvalManager.addApprovedDirectory("/external/project", "session");
214
+ const metadata1 = await overrideFn(
215
+ tool.inputSchema.parse({ file_path: "/external/project/file.ts" }),
216
+ toolContext
217
+ );
218
+ (0, import_vitest.expect)(metadata1).toBeNull();
219
+ const metadata2 = await overrideFn(
220
+ tool.inputSchema.parse({ file_path: "/external/project/deep/nested/file.ts" }),
221
+ toolContext
222
+ );
223
+ (0, import_vitest.expect)(metadata2).toBeNull();
383
224
  });
384
225
  (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" });
395
- (0, import_vitest.expect)(override).toBeNull();
396
- override = await tool.getApprovalOverride?.({ file_path: "/external/other/file.ts" });
397
- (0, import_vitest.expect)(override).not.toBeNull();
398
- });
399
- });
400
- (0, import_vitest.describe)("Different External Directories Scenarios", () => {
401
- (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
- });
406
- const dir1Path = "/external/project1/file.ts";
407
- const dir2Path = "/external/project2/file.ts";
408
- const override1 = await tool.getApprovalOverride?.({ file_path: dir1Path });
409
- (0, import_vitest.expect)(override1).not.toBeNull();
410
- const metadata1 = override1?.metadata;
411
- (0, import_vitest.expect)(metadata1?.parentDir).toBe("/external/project1");
412
- const override2 = await tool.getApprovalOverride?.({ file_path: dir2Path });
413
- (0, import_vitest.expect)(override2).not.toBeNull();
414
- const metadata2 = override2?.metadata;
415
- (0, import_vitest.expect)(metadata2?.parentDir).toBe("/external/project2");
416
- });
417
- });
418
- (0, import_vitest.describe)("Mixed Operations Scenarios", () => {
419
- (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 });
423
- const externalDir = "/external/project";
424
- (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
- })
449
- ).toBeNull();
450
- (0, import_vitest.expect)(
451
- await editTool.getApprovalOverride?.({
452
- file_path: `${externalDir}/file3.ts`,
453
- old_string: "a",
454
- new_string: "b"
455
- })
456
- ).toBeNull();
226
+ const tool = (0, import_read_file_tool.createReadFileTool)(getFileSystemService);
227
+ const overrideFn = tool.approval?.override;
228
+ (0, import_vitest.expect)(overrideFn).toBeDefined();
229
+ approvalManager.addApprovedDirectory("/external/sub", "session");
230
+ const metadata1 = await overrideFn(
231
+ tool.inputSchema.parse({ file_path: "/external/sub/file.ts" }),
232
+ toolContext
233
+ );
234
+ (0, import_vitest.expect)(metadata1).toBeNull();
235
+ const metadata2 = await overrideFn(
236
+ tool.inputSchema.parse({ file_path: "/external/other/file.ts" }),
237
+ toolContext
238
+ );
239
+ (0, import_vitest.expect)(metadata2).not.toBeNull();
457
240
  });
458
241
  });
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();
242
+ (0, import_vitest.describe)("Without ApprovalManager in context", () => {
243
+ (0, import_vitest.it)("should throw for external paths", async () => {
244
+ const tool = (0, import_read_file_tool.createReadFileTool)(getFileSystemService);
245
+ const overrideFn = tool.approval?.override;
246
+ (0, import_vitest.expect)(overrideFn).toBeDefined();
247
+ const contextWithoutApprovalManager = { logger: mockLogger };
248
+ await (0, import_vitest.expect)(
249
+ overrideFn(
250
+ tool.inputSchema.parse({ file_path: "/external/project/file.ts" }),
251
+ contextWithoutApprovalManager
252
+ )
253
+ ).rejects.toBeInstanceOf(import_core.DextoRuntimeError);
469
254
  });
470
255
  });
471
256
  });
@@ -1,2 +1,14 @@
1
-
2
- export { }
1
+ /**
2
+ * Directory Approval Integration Tests
3
+ *
4
+ * Tests for the directory access permission system integrated into filesystem tools.
5
+ *
6
+ * Key behaviors tested:
7
+ * - Paths within config-allowed roots do not require directory access prompting metadata
8
+ * - Paths outside config-allowed roots return directory access prompting metadata
9
+ * - Session-approved directories do not prompt again
10
+ * - Once-approved directories still prompt again (prompting decision)
11
+ * - Session approvals cover child paths but not sibling directories
12
+ */
13
+ export {};
14
+ //# sourceMappingURL=directory-approval.integration.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"directory-approval.integration.test.d.ts","sourceRoot":"","sources":["../src/directory-approval.integration.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG"}