@dexto/tools-filesystem 1.6.0 → 1.6.2

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 (73) hide show
  1. package/dist/directory-approval.cjs +44 -40
  2. package/dist/directory-approval.d.ts +8 -4
  3. package/dist/directory-approval.d.ts.map +1 -1
  4. package/dist/directory-approval.integration.test.cjs +107 -356
  5. package/dist/directory-approval.integration.test.d.ts +6 -6
  6. package/dist/directory-approval.integration.test.js +109 -360
  7. package/dist/directory-approval.js +45 -41
  8. package/dist/edit-file-tool.cjs +69 -47
  9. package/dist/edit-file-tool.d.ts.map +1 -1
  10. package/dist/edit-file-tool.js +77 -48
  11. package/dist/edit-file-tool.test.cjs +54 -11
  12. package/dist/edit-file-tool.test.js +54 -11
  13. package/dist/error-codes.cjs +4 -0
  14. package/dist/error-codes.d.ts +4 -0
  15. package/dist/error-codes.d.ts.map +1 -1
  16. package/dist/error-codes.js +4 -0
  17. package/dist/errors.cjs +48 -0
  18. package/dist/errors.d.ts +16 -0
  19. package/dist/errors.d.ts.map +1 -1
  20. package/dist/errors.js +48 -0
  21. package/dist/filesystem-service.cjs +307 -9
  22. package/dist/filesystem-service.d.ts +28 -1
  23. package/dist/filesystem-service.d.ts.map +1 -1
  24. package/dist/filesystem-service.js +308 -10
  25. package/dist/glob-files-tool.cjs +12 -1
  26. package/dist/glob-files-tool.d.ts.map +1 -1
  27. package/dist/glob-files-tool.js +13 -2
  28. package/dist/grep-content-tool.cjs +13 -1
  29. package/dist/grep-content-tool.d.ts.map +1 -1
  30. package/dist/grep-content-tool.js +14 -2
  31. package/dist/index.cjs +3 -0
  32. package/dist/index.d.cts +852 -16
  33. package/dist/index.d.ts +2 -1
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +2 -0
  36. package/dist/path-validator.cjs +28 -2
  37. package/dist/path-validator.d.ts +14 -0
  38. package/dist/path-validator.d.ts.map +1 -1
  39. package/dist/path-validator.js +28 -2
  40. package/dist/read-file-tool.cjs +7 -1
  41. package/dist/read-file-tool.d.ts.map +1 -1
  42. package/dist/read-file-tool.js +8 -2
  43. package/dist/tool-factory.cjs +21 -0
  44. package/dist/tool-factory.d.ts.map +1 -1
  45. package/dist/tool-factory.js +21 -0
  46. package/dist/types.d.ts +65 -0
  47. package/dist/types.d.ts.map +1 -1
  48. package/dist/write-file-tool.cjs +60 -38
  49. package/dist/write-file-tool.d.ts +1 -1
  50. package/dist/write-file-tool.d.ts.map +1 -1
  51. package/dist/write-file-tool.js +67 -39
  52. package/dist/write-file-tool.test.cjs +75 -13
  53. package/dist/write-file-tool.test.js +75 -13
  54. package/package.json +6 -6
  55. package/dist/directory-approval.d.cts +0 -22
  56. package/dist/directory-approval.integration.test.d.cts +0 -2
  57. package/dist/edit-file-tool.d.cts +0 -34
  58. package/dist/edit-file-tool.test.d.cts +0 -2
  59. package/dist/error-codes.d.cts +0 -32
  60. package/dist/errors.d.cts +0 -112
  61. package/dist/file-tool-types.d.cts +0 -18
  62. package/dist/filesystem-service.d.cts +0 -117
  63. package/dist/filesystem-service.test.d.cts +0 -2
  64. package/dist/glob-files-tool.d.cts +0 -31
  65. package/dist/grep-content-tool.d.cts +0 -40
  66. package/dist/path-validator.d.cts +0 -97
  67. package/dist/path-validator.test.d.cts +0 -2
  68. package/dist/read-file-tool.d.cts +0 -31
  69. package/dist/tool-factory-config.d.cts +0 -63
  70. package/dist/tool-factory.d.cts +0 -7
  71. package/dist/types.d.cts +0 -178
  72. package/dist/write-file-tool.d.cts +0 -34
  73. package/dist/write-file-tool.test.d.cts +0 -2
@@ -40,50 +40,54 @@ function resolveFilePath(workingDirectory, filePath) {
40
40
  }
41
41
  function createDirectoryAccessApprovalHandlers(options) {
42
42
  return {
43
- async getApprovalOverride(input, context) {
44
- const resolvedFileSystemService = await options.getFileSystemService(context);
45
- const paths = options.resolvePaths(input, resolvedFileSystemService);
46
- const isAllowed = await resolvedFileSystemService.isPathWithinConfigAllowed(paths.path);
47
- if (isAllowed) {
48
- return null;
49
- }
50
- const approvalManager = context.services?.approval;
51
- if (!approvalManager) {
52
- throw import_core.ToolError.configInvalid(
53
- `${options.toolName} requires ToolExecutionContext.services.approval`
43
+ approval: {
44
+ async override(input, context) {
45
+ const resolvedFileSystemService = await options.getFileSystemService(context);
46
+ const paths = options.resolvePaths(input, resolvedFileSystemService);
47
+ const isAllowed = await resolvedFileSystemService.isPathWithinConfigAllowed(
48
+ paths.path
54
49
  );
55
- }
56
- if (approvalManager.isDirectorySessionApproved(paths.path)) {
57
- return null;
58
- }
59
- return {
60
- type: import_core.ApprovalType.DIRECTORY_ACCESS,
61
- metadata: {
62
- path: paths.path,
63
- parentDir: paths.parentDir,
64
- operation: options.operation,
65
- toolName: options.toolName
50
+ if (isAllowed) {
51
+ return null;
66
52
  }
67
- };
68
- },
69
- onApprovalGranted(response, context, approvalRequest) {
70
- if (approvalRequest.type !== import_core.ApprovalType.DIRECTORY_ACCESS) {
71
- return;
72
- }
73
- const metadata = approvalRequest.metadata;
74
- const parentDir = typeof metadata?.parentDir === "string" ? metadata.parentDir : null;
75
- if (!parentDir) {
76
- return;
77
- }
78
- const data = response.data;
79
- const rememberDirectory = data?.rememberDirectory ?? false;
80
- const approvalManager = context.services?.approval;
81
- if (!approvalManager) {
82
- throw import_core.ToolError.configInvalid(
83
- `${options.toolName} requires ToolExecutionContext.services.approval`
53
+ const approvalManager = context.services?.approval;
54
+ if (!approvalManager) {
55
+ throw import_core.ToolError.configInvalid(
56
+ `${options.toolName} requires ToolExecutionContext.services.approval`
57
+ );
58
+ }
59
+ if (approvalManager.isDirectorySessionApproved(paths.path)) {
60
+ return null;
61
+ }
62
+ return {
63
+ type: import_core.ApprovalType.DIRECTORY_ACCESS,
64
+ metadata: {
65
+ path: paths.path,
66
+ parentDir: paths.parentDir,
67
+ operation: options.operation,
68
+ toolName: options.toolName
69
+ }
70
+ };
71
+ },
72
+ async onGranted(response, context, approvalRequest) {
73
+ const approvalManager = context.services?.approval;
74
+ if (!approvalManager) {
75
+ return;
76
+ }
77
+ if (response.status !== import_core.ApprovalStatus.APPROVED) {
78
+ return;
79
+ }
80
+ const data = response.data;
81
+ const rememberDirectory = data?.rememberDirectory ?? false;
82
+ const metadata = approvalRequest.metadata;
83
+ if (!metadata?.parentDir) {
84
+ return;
85
+ }
86
+ approvalManager.addApprovedDirectory(
87
+ metadata.parentDir,
88
+ rememberDirectory ? "session" : "once"
84
89
  );
85
90
  }
86
- approvalManager.addApprovedDirectory(parentDir, rememberDirectory ? "session" : "once");
87
91
  }
88
92
  };
89
93
  }
@@ -1,3 +1,4 @@
1
+ import type { z, ZodTypeAny } from 'zod';
1
2
  import type { ApprovalRequestDetails, ApprovalResponse, ToolExecutionContext } from '@dexto/core';
2
3
  import type { FileSystemService } from './filesystem-service.js';
3
4
  import type { FileSystemServiceGetter } from './file-tool-types.js';
@@ -7,14 +8,17 @@ type DirectoryApprovalPaths = {
7
8
  parentDir: string;
8
9
  };
9
10
  export declare function resolveFilePath(workingDirectory: string, filePath: string): DirectoryApprovalPaths;
10
- export declare function createDirectoryAccessApprovalHandlers<TInput>(options: {
11
+ export declare function createDirectoryAccessApprovalHandlers<const TSchema extends ZodTypeAny>(options: {
11
12
  toolName: string;
12
13
  operation: DirectoryApprovalOperation;
14
+ inputSchema: TSchema;
13
15
  getFileSystemService: FileSystemServiceGetter;
14
- resolvePaths: (input: TInput, fileSystemService: FileSystemService) => DirectoryApprovalPaths;
16
+ resolvePaths: (input: z.output<TSchema>, fileSystemService: FileSystemService) => DirectoryApprovalPaths;
15
17
  }): {
16
- getApprovalOverride: (input: TInput, context: ToolExecutionContext) => Promise<ApprovalRequestDetails | null>;
17
- onApprovalGranted: (response: ApprovalResponse, context: ToolExecutionContext, approvalRequest: ApprovalRequestDetails) => void;
18
+ approval: {
19
+ override: (input: z.output<TSchema>, context: ToolExecutionContext) => Promise<ApprovalRequestDetails | null>;
20
+ onGranted: (response: ApprovalResponse, context: ToolExecutionContext, approvalRequest: ApprovalRequestDetails) => Promise<void>;
21
+ };
18
22
  };
19
23
  export {};
20
24
  //# sourceMappingURL=directory-approval.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"directory-approval.d.ts","sourceRoot":"","sources":["../src/directory-approval.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAClG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAEpE,KAAK,0BAA0B,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AAE5D,KAAK,sBAAsB,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,wBAAgB,eAAe,CAC3B,gBAAgB,EAAE,MAAM,EACxB,QAAQ,EAAE,MAAM,GACjB,sBAAsB,CAKxB;AAED,wBAAgB,qCAAqC,CAAC,MAAM,EAAE,OAAO,EAAE;IACnE,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,0BAA0B,CAAC;IACtC,oBAAoB,EAAE,uBAAuB,CAAC;IAC9C,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,iBAAiB,KAAK,sBAAsB,CAAC;CACjG,GAAG;IACA,mBAAmB,EAAE,CACjB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,oBAAoB,KAC5B,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC,CAAC;IAC5C,iBAAiB,EAAE,CACf,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,EAAE,oBAAoB,EAC7B,eAAe,EAAE,sBAAsB,KACtC,IAAI,CAAC;CACb,CA+DA"}
1
+ {"version":3,"file":"directory-approval.d.ts","sourceRoot":"","sources":["../src/directory-approval.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,CAAC,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AAEzC,OAAO,KAAK,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAClG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAEpE,KAAK,0BAA0B,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AAE5D,KAAK,sBAAsB,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,wBAAgB,eAAe,CAC3B,gBAAgB,EAAE,MAAM,EACxB,QAAQ,EAAE,MAAM,GACjB,sBAAsB,CAKxB;AAED,wBAAgB,qCAAqC,CAAC,KAAK,CAAC,OAAO,SAAS,UAAU,EAAE,OAAO,EAAE;IAC7F,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,0BAA0B,CAAC;IACtC,WAAW,EAAE,OAAO,CAAC;IACrB,oBAAoB,EAAE,uBAAuB,CAAC;IAC9C,YAAY,EAAE,CACV,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,EACxB,iBAAiB,EAAE,iBAAiB,KACnC,sBAAsB,CAAC;CAC/B,GAAG;IACA,QAAQ,EAAE;QACN,QAAQ,EAAE,CACN,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,EACxB,OAAO,EAAE,oBAAoB,KAC5B,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC,CAAC;QAC5C,SAAS,EAAE,CACP,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,EAAE,oBAAoB,EAC7B,eAAe,EAAE,sBAAsB,KACtC,OAAO,CAAC,IAAI,CAAC,CAAC;KACtB,CAAC;CACL,CA4DA"}
@@ -25,11 +25,11 @@ 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
33
  const createMockLogger = () => {
34
34
  const noopAsync = async () => void 0;
35
35
  const logger = {
@@ -40,6 +40,7 @@ const createMockLogger = () => {
40
40
  error: import_vitest.vi.fn(),
41
41
  trackException: import_vitest.vi.fn(),
42
42
  createChild: () => logger,
43
+ createFileOnlyChild: () => logger,
43
44
  setLevel: import_vitest.vi.fn(),
44
45
  getLevel: () => "info",
45
46
  getLogFilePath: () => null,
@@ -100,402 +101,152 @@ function createToolContext(logger, approval) {
100
101
  } catch {
101
102
  }
102
103
  });
103
- (0, import_vitest.describe)("Read File Tool", () => {
104
- (0, import_vitest.describe)("getApprovalOverride", () => {
105
- (0, import_vitest.it)("should return null for paths within working directory (no prompt needed)", async () => {
106
- const tool = (0, import_read_file_tool.createReadFileTool)(getFileSystemService);
107
- const testFile = path.join(tempDir, "test.txt");
108
- await fs.writeFile(testFile, "test content");
109
- const override = await tool.getApprovalOverride?.(
110
- tool.inputSchema.parse({ file_path: testFile }),
111
- toolContext
112
- );
113
- (0, import_vitest.expect)(override).toBeNull();
114
- });
115
- (0, import_vitest.it)("should return directory access approval for external paths", async () => {
116
- const tool = (0, import_read_file_tool.createReadFileTool)(getFileSystemService);
117
- const externalPath = "/external/project/file.ts";
118
- const override = await tool.getApprovalOverride?.(
119
- tool.inputSchema.parse({ file_path: externalPath }),
120
- toolContext
121
- );
122
- (0, import_vitest.expect)(override).not.toBeNull();
123
- (0, import_vitest.expect)(override?.type).toBe(import_core.ApprovalType.DIRECTORY_ACCESS);
124
- const metadata = override?.metadata;
125
- (0, import_vitest.expect)(metadata?.path).toBe(path.resolve(externalPath));
126
- (0, import_vitest.expect)(metadata?.parentDir).toBe(path.dirname(path.resolve(externalPath)));
127
- (0, import_vitest.expect)(metadata?.operation).toBe("read");
128
- (0, import_vitest.expect)(metadata?.toolName).toBe("read_file");
129
- });
130
- (0, import_vitest.it)("should return null when external path is session-approved", async () => {
131
- approvalManager.addApprovedDirectory("/external/project", "session");
132
- const tool = (0, import_read_file_tool.createReadFileTool)(getFileSystemService);
133
- const externalPath = "/external/project/file.ts";
134
- const override = await tool.getApprovalOverride?.(
135
- tool.inputSchema.parse({ file_path: externalPath }),
136
- toolContext
137
- );
138
- (0, import_vitest.expect)(override).toBeNull();
139
- });
140
- });
141
- (0, import_vitest.describe)("onApprovalGranted", () => {
142
- (0, import_vitest.it)("should add directory as session-approved when rememberDirectory is true", async () => {
143
- const tool = (0, import_read_file_tool.createReadFileTool)(getFileSystemService);
144
- const externalPath = "/external/project/file.ts";
145
- const approvalRequest = await tool.getApprovalOverride?.(
146
- tool.inputSchema.parse({ file_path: externalPath }),
147
- toolContext
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");
165
- });
166
- (0, import_vitest.it)("should add directory as once-approved when rememberDirectory is false", async () => {
167
- const tool = (0, import_read_file_tool.createReadFileTool)(getFileSystemService);
168
- const externalPath = "/external/project/file.ts";
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
185
- );
186
- (0, import_vitest.expect)(
187
- approvalManager.getApprovedDirectories().get(path.dirname(path.resolve(externalPath)))
188
- ).toBe("once");
189
- });
190
- (0, import_vitest.it)("should default to once-approved when rememberDirectory is not specified", async () => {
191
- const tool = (0, import_read_file_tool.createReadFileTool)(getFileSystemService);
192
- const externalPath = "/external/project/file.ts";
193
- const approvalRequest = await tool.getApprovalOverride?.(
194
- tool.inputSchema.parse({ file_path: externalPath }),
195
- toolContext
196
- );
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");
213
- });
214
- });
215
- (0, import_vitest.describe)("execute", () => {
216
- (0, import_vitest.it)("should read file contents within working directory", async () => {
217
- const tool = (0, import_read_file_tool.createReadFileTool)(getFileSystemService);
218
- const testFile = path.join(tempDir, "readable.txt");
219
- await fs.writeFile(testFile, "Hello, world!\nLine 2");
220
- const result = await tool.execute({ file_path: testFile }, toolContext);
221
- (0, import_vitest.expect)(result.content).toBe("Hello, world!\nLine 2");
222
- (0, import_vitest.expect)(result.lines).toBe(2);
223
- });
224
- });
225
- });
226
- (0, import_vitest.describe)("Write File Tool", () => {
227
- (0, import_vitest.describe)("getApprovalOverride", () => {
228
- (0, import_vitest.it)("should return null for paths within working directory", async () => {
229
- const tool = (0, import_write_file_tool.createWriteFileTool)(getFileSystemService);
230
- const testFile = path.join(tempDir, "new-file.txt");
231
- const override = await tool.getApprovalOverride?.(
232
- tool.inputSchema.parse({ file_path: testFile, content: "test" }),
233
- toolContext
234
- );
235
- (0, import_vitest.expect)(override).toBeNull();
236
- });
237
- (0, import_vitest.it)("should return directory access approval for external paths", async () => {
238
- const tool = (0, import_write_file_tool.createWriteFileTool)(getFileSystemService);
239
- const externalPath = "/external/project/new.ts";
240
- const override = await tool.getApprovalOverride?.(
241
- tool.inputSchema.parse({ file_path: externalPath, content: "test" }),
242
- toolContext
243
- );
244
- (0, import_vitest.expect)(override).not.toBeNull();
245
- (0, import_vitest.expect)(override?.type).toBe(import_core.ApprovalType.DIRECTORY_ACCESS);
246
- const metadata = override?.metadata;
247
- (0, import_vitest.expect)(metadata?.operation).toBe("write");
248
- (0, import_vitest.expect)(metadata?.toolName).toBe("write_file");
249
- });
250
- (0, import_vitest.it)("should return null when external path is session-approved", async () => {
251
- approvalManager.addApprovedDirectory("/external/project", "session");
252
- const tool = (0, import_write_file_tool.createWriteFileTool)(getFileSystemService);
253
- const externalPath = "/external/project/new.ts";
254
- const override = await tool.getApprovalOverride?.(
255
- tool.inputSchema.parse({ file_path: externalPath, content: "test" }),
256
- toolContext
257
- );
258
- (0, import_vitest.expect)(override).toBeNull();
259
- });
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();
260
116
  });
261
- (0, import_vitest.describe)("onApprovalGranted", () => {
262
- (0, import_vitest.it)("should add directory as session-approved when rememberDirectory is true", async () => {
263
- const tool = (0, import_write_file_tool.createWriteFileTool)(getFileSystemService);
264
- const externalPath = "/external/project/new.ts";
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");
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"
272
134
  }
273
- tool.onApprovalGranted?.(
274
- {
275
- approvalId: "test-approval",
276
- status: import_core.ApprovalStatus.APPROVED,
277
- data: { rememberDirectory: true }
278
- },
279
- toolContext,
280
- approvalRequest
281
- );
282
- (0, import_vitest.expect)(
283
- approvalManager.getApprovedDirectories().get(path.dirname(path.resolve(externalPath)))
284
- ).toBe("session");
285
135
  });
286
136
  });
287
- });
288
- (0, import_vitest.describe)("Edit File Tool", () => {
289
- (0, import_vitest.describe)("getApprovalOverride", () => {
290
- (0, import_vitest.it)("should return null for paths within working directory", async () => {
291
- const tool = (0, import_edit_file_tool.createEditFileTool)(getFileSystemService);
292
- const testFile = path.join(tempDir, "existing.txt");
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
- );
301
- (0, import_vitest.expect)(override).toBeNull();
302
- });
303
- (0, import_vitest.it)("should return directory access approval for external paths", async () => {
304
- const tool = (0, import_edit_file_tool.createEditFileTool)(getFileSystemService);
305
- const externalPath = "/external/project/existing.ts";
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
- );
314
- (0, import_vitest.expect)(override).not.toBeNull();
315
- (0, import_vitest.expect)(override?.type).toBe(import_core.ApprovalType.DIRECTORY_ACCESS);
316
- const metadata = override?.metadata;
317
- (0, import_vitest.expect)(metadata?.operation).toBe("edit");
318
- (0, import_vitest.expect)(metadata?.toolName).toBe("edit_file");
319
- });
320
- (0, import_vitest.it)("should return null when external path is session-approved", async () => {
321
- approvalManager.addApprovedDirectory("/external/project", "session");
322
- const tool = (0, import_edit_file_tool.createEditFileTool)(getFileSystemService);
323
- const externalPath = "/external/project/existing.ts";
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
- );
332
- (0, import_vitest.expect)(override).toBeNull();
333
- });
334
- });
335
- });
336
- (0, import_vitest.describe)("Session vs Once Approval Scenarios", () => {
337
- (0, import_vitest.it)("should not prompt for subsequent requests after session approval", async () => {
137
+ (0, import_vitest.it)("should return null when external path is session-approved", async () => {
138
+ approvalManager.addApprovedDirectory("/external/project", "session");
338
139
  const tool = (0, import_read_file_tool.createReadFileTool)(getFileSystemService);
339
- const externalPath1 = "/external/project/file1.ts";
340
- const externalPath2 = "/external/project/file2.ts";
341
- let override = await tool.getApprovalOverride?.(
342
- tool.inputSchema.parse({ file_path: externalPath1 }),
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 }),
343
145
  toolContext
344
146
  );
345
- (0, import_vitest.expect)(override).not.toBeNull();
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
364
- );
365
- (0, import_vitest.expect)(override).toBeNull();
147
+ (0, import_vitest.expect)(metadata).toBeNull();
366
148
  });
367
- (0, import_vitest.it)("should prompt for subsequent requests after once approval", async () => {
149
+ (0, import_vitest.it)("should still return metadata when external path is once-approved (prompt again)", async () => {
150
+ approvalManager.addApprovedDirectory("/external/project", "once");
368
151
  const tool = (0, import_read_file_tool.createReadFileTool)(getFileSystemService);
369
- const externalPath1 = "/external/project/file1.ts";
370
- const externalPath2 = "/external/project/file2.ts";
371
- let override = await tool.getApprovalOverride?.(
372
- tool.inputSchema.parse({ file_path: externalPath1 }),
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 }),
373
157
  toolContext
374
158
  );
375
- (0, import_vitest.expect)(override).not.toBeNull();
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
159
+ (0, import_vitest.expect)(metadata).not.toBeNull();
160
+ });
161
+ });
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
387
171
  );
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 }),
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
+ }
181
+ });
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({
190
+ file_path: externalPath,
191
+ old_string: "old",
192
+ new_string: "new"
193
+ }),
393
194
  toolContext
394
195
  );
395
- (0, import_vitest.expect)(override).not.toBeNull();
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
+ }
205
+ });
396
206
  });
397
207
  });
398
- (0, import_vitest.describe)("Path Containment Scenarios", () => {
208
+ (0, import_vitest.describe)("Path containment scenarios", () => {
399
209
  (0, import_vitest.it)("should cover child paths when parent directory is session-approved", async () => {
400
210
  const tool = (0, import_read_file_tool.createReadFileTool)(getFileSystemService);
211
+ const overrideFn = tool.approval?.override;
212
+ (0, import_vitest.expect)(overrideFn).toBeDefined();
401
213
  approvalManager.addApprovedDirectory("/external/project", "session");
402
- let override = await tool.getApprovalOverride?.(
214
+ const metadata1 = await overrideFn(
403
215
  tool.inputSchema.parse({ file_path: "/external/project/file.ts" }),
404
216
  toolContext
405
217
  );
406
- (0, import_vitest.expect)(override).toBeNull();
407
- override = await tool.getApprovalOverride?.(
218
+ (0, import_vitest.expect)(metadata1).toBeNull();
219
+ const metadata2 = await overrideFn(
408
220
  tool.inputSchema.parse({ file_path: "/external/project/deep/nested/file.ts" }),
409
221
  toolContext
410
222
  );
411
- (0, import_vitest.expect)(override).toBeNull();
223
+ (0, import_vitest.expect)(metadata2).toBeNull();
412
224
  });
413
225
  (0, import_vitest.it)("should NOT cover sibling directories", async () => {
414
226
  const tool = (0, import_read_file_tool.createReadFileTool)(getFileSystemService);
227
+ const overrideFn = tool.approval?.override;
228
+ (0, import_vitest.expect)(overrideFn).toBeDefined();
415
229
  approvalManager.addApprovedDirectory("/external/sub", "session");
416
- let override = await tool.getApprovalOverride?.(
230
+ const metadata1 = await overrideFn(
417
231
  tool.inputSchema.parse({ file_path: "/external/sub/file.ts" }),
418
232
  toolContext
419
233
  );
420
- (0, import_vitest.expect)(override).toBeNull();
421
- override = await tool.getApprovalOverride?.(
234
+ (0, import_vitest.expect)(metadata1).toBeNull();
235
+ const metadata2 = await overrideFn(
422
236
  tool.inputSchema.parse({ file_path: "/external/other/file.ts" }),
423
237
  toolContext
424
238
  );
425
- (0, import_vitest.expect)(override).not.toBeNull();
426
- });
427
- });
428
- (0, import_vitest.describe)("Different External Directories Scenarios", () => {
429
- (0, import_vitest.it)("should require separate approval for different external directories", async () => {
430
- const tool = (0, import_read_file_tool.createReadFileTool)(getFileSystemService);
431
- const dir1Path = "/external/project1/file.ts";
432
- const dir2Path = "/external/project2/file.ts";
433
- const override1 = await tool.getApprovalOverride?.(
434
- tool.inputSchema.parse({ file_path: dir1Path }),
435
- toolContext
436
- );
437
- (0, import_vitest.expect)(override1).not.toBeNull();
438
- const metadata1 = override1?.metadata;
439
- (0, import_vitest.expect)(metadata1?.parentDir).toBe("/external/project1");
440
- const override2 = await tool.getApprovalOverride?.(
441
- tool.inputSchema.parse({ file_path: dir2Path }),
442
- toolContext
443
- );
444
- (0, import_vitest.expect)(override2).not.toBeNull();
445
- const metadata2 = override2?.metadata;
446
- (0, import_vitest.expect)(metadata2?.parentDir).toBe("/external/project2");
447
- });
448
- });
449
- (0, import_vitest.describe)("Mixed Operations Scenarios", () => {
450
- (0, import_vitest.it)("should share directory approval across different file operations", async () => {
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);
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
- );
472
- (0, import_vitest.expect)(
473
- await writeTool.getApprovalOverride?.(
474
- writeTool.inputSchema.parse({
475
- file_path: `${externalDir}/file2.ts`,
476
- content: "test"
477
- }),
478
- toolContext
479
- )
480
- ).toBeNull();
481
- (0, import_vitest.expect)(
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
- )
490
- ).toBeNull();
239
+ (0, import_vitest.expect)(metadata2).not.toBeNull();
491
240
  });
492
241
  });
493
242
  (0, import_vitest.describe)("Without ApprovalManager in context", () => {
494
243
  (0, import_vitest.it)("should throw for external paths", async () => {
495
244
  const tool = (0, import_read_file_tool.createReadFileTool)(getFileSystemService);
245
+ const overrideFn = tool.approval?.override;
246
+ (0, import_vitest.expect)(overrideFn).toBeDefined();
496
247
  const contextWithoutApprovalManager = { logger: mockLogger };
497
248
  await (0, import_vitest.expect)(
498
- tool.getApprovalOverride?.(
249
+ overrideFn(
499
250
  tool.inputSchema.parse({ file_path: "/external/project/file.ts" }),
500
251
  contextWithoutApprovalManager
501
252
  )