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