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