@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.
- package/dist/directory-approval.cjs +44 -40
- package/dist/directory-approval.d.ts +8 -4
- package/dist/directory-approval.d.ts.map +1 -1
- package/dist/directory-approval.integration.test.cjs +107 -356
- package/dist/directory-approval.integration.test.d.ts +6 -6
- package/dist/directory-approval.integration.test.js +109 -360
- package/dist/directory-approval.js +45 -41
- package/dist/edit-file-tool.cjs +69 -47
- package/dist/edit-file-tool.d.ts.map +1 -1
- package/dist/edit-file-tool.js +77 -48
- package/dist/edit-file-tool.test.cjs +54 -11
- package/dist/edit-file-tool.test.js +54 -11
- package/dist/error-codes.cjs +4 -0
- package/dist/error-codes.d.ts +4 -0
- package/dist/error-codes.d.ts.map +1 -1
- package/dist/error-codes.js +4 -0
- package/dist/errors.cjs +48 -0
- package/dist/errors.d.ts +16 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +48 -0
- package/dist/filesystem-service.cjs +307 -9
- package/dist/filesystem-service.d.ts +28 -1
- package/dist/filesystem-service.d.ts.map +1 -1
- package/dist/filesystem-service.js +308 -10
- package/dist/glob-files-tool.cjs +12 -1
- package/dist/glob-files-tool.d.ts.map +1 -1
- package/dist/glob-files-tool.js +13 -2
- package/dist/grep-content-tool.cjs +13 -1
- package/dist/grep-content-tool.d.ts.map +1 -1
- package/dist/grep-content-tool.js +14 -2
- package/dist/index.cjs +3 -0
- package/dist/index.d.cts +852 -16
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/path-validator.cjs +28 -2
- package/dist/path-validator.d.ts +14 -0
- package/dist/path-validator.d.ts.map +1 -1
- package/dist/path-validator.js +28 -2
- package/dist/read-file-tool.cjs +7 -1
- package/dist/read-file-tool.d.ts.map +1 -1
- package/dist/read-file-tool.js +8 -2
- package/dist/tool-factory.cjs +21 -0
- package/dist/tool-factory.d.ts.map +1 -1
- package/dist/tool-factory.js +21 -0
- package/dist/types.d.ts +65 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/write-file-tool.cjs +60 -38
- package/dist/write-file-tool.d.ts +1 -1
- package/dist/write-file-tool.d.ts.map +1 -1
- package/dist/write-file-tool.js +67 -39
- package/dist/write-file-tool.test.cjs +75 -13
- package/dist/write-file-tool.test.js +75 -13
- package/package.json +6 -6
- package/dist/directory-approval.d.cts +0 -22
- package/dist/directory-approval.integration.test.d.cts +0 -2
- package/dist/edit-file-tool.d.cts +0 -34
- package/dist/edit-file-tool.test.d.cts +0 -2
- package/dist/error-codes.d.cts +0 -32
- package/dist/errors.d.cts +0 -112
- package/dist/file-tool-types.d.cts +0 -18
- package/dist/filesystem-service.d.cts +0 -117
- package/dist/filesystem-service.test.d.cts +0 -2
- package/dist/glob-files-tool.d.cts +0 -31
- package/dist/grep-content-tool.d.cts +0 -40
- package/dist/path-validator.d.cts +0 -97
- package/dist/path-validator.test.d.cts +0 -2
- package/dist/read-file-tool.d.cts +0 -31
- package/dist/tool-factory-config.d.cts +0 -63
- package/dist/tool-factory.d.cts +0 -7
- package/dist/types.d.cts +0 -178
- package/dist/write-file-tool.d.cts +0 -34
- 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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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<
|
|
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:
|
|
16
|
+
resolvePaths: (input: z.output<TSchema>, fileSystemService: FileSystemService) => DirectoryApprovalPaths;
|
|
15
17
|
}): {
|
|
16
|
-
|
|
17
|
-
|
|
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":"
|
|
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)("
|
|
104
|
-
(0, import_vitest.
|
|
105
|
-
(0,
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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.
|
|
262
|
-
(0,
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
-
|
|
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
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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)(
|
|
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
|
|
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
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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)(
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
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
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
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)(
|
|
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
|
|
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
|
-
|
|
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)(
|
|
407
|
-
|
|
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)(
|
|
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
|
-
|
|
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)(
|
|
421
|
-
|
|
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)(
|
|
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
|
-
|
|
249
|
+
overrideFn(
|
|
499
250
|
tool.inputSchema.parse({ file_path: "/external/project/file.ts" }),
|
|
500
251
|
contextWithoutApprovalManager
|
|
501
252
|
)
|