@aj-archipelago/cortex 1.4.6 → 1.4.7
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/helper-apps/cortex-file-handler/package-lock.json +2 -2
- package/helper-apps/cortex-file-handler/package.json +1 -1
- package/helper-apps/cortex-file-handler/src/index.js +27 -4
- package/helper-apps/cortex-file-handler/src/services/storage/AzureStorageProvider.js +74 -10
- package/helper-apps/cortex-file-handler/src/services/storage/StorageService.js +23 -2
- package/helper-apps/cortex-file-handler/src/start.js +2 -0
- package/helper-apps/cortex-file-handler/tests/deleteOperations.test.js +287 -0
- package/helper-apps/cortex-file-handler/tests/start.test.js +1 -1
- package/lib/entityConstants.js +1 -1
- package/lib/fileUtils.js +1481 -0
- package/lib/pathwayTools.js +7 -1
- package/lib/util.js +2 -313
- package/package.json +4 -3
- package/pathways/image_qwen.js +1 -1
- package/pathways/system/entity/memory/sys_read_memory.js +17 -3
- package/pathways/system/entity/memory/sys_save_memory.js +22 -6
- package/pathways/system/entity/sys_entity_agent.js +21 -4
- package/pathways/system/entity/tools/sys_tool_analyzefile.js +171 -0
- package/pathways/system/entity/tools/sys_tool_codingagent.js +38 -4
- package/pathways/system/entity/tools/sys_tool_editfile.js +403 -0
- package/pathways/system/entity/tools/sys_tool_file_collection.js +433 -0
- package/pathways/system/entity/tools/sys_tool_image.js +172 -10
- package/pathways/system/entity/tools/sys_tool_image_gemini.js +123 -10
- package/pathways/system/entity/tools/sys_tool_readfile.js +217 -124
- package/pathways/system/entity/tools/sys_tool_validate_url.js +137 -0
- package/pathways/system/entity/tools/sys_tool_writefile.js +211 -0
- package/pathways/system/workspaces/run_workspace_prompt.js +4 -3
- package/pathways/transcribe_gemini.js +2 -1
- package/server/executeWorkspace.js +1 -1
- package/server/plugins/neuralSpacePlugin.js +2 -6
- package/server/plugins/openAiWhisperPlugin.js +2 -1
- package/server/plugins/replicateApiPlugin.js +4 -14
- package/server/typeDef.js +10 -1
- package/tests/integration/features/tools/fileCollection.test.js +858 -0
- package/tests/integration/features/tools/fileOperations.test.js +851 -0
- package/tests/integration/features/tools/writefile.test.js +350 -0
- package/tests/unit/core/fileCollection.test.js +259 -0
- package/tests/unit/core/util.test.js +320 -1
|
@@ -0,0 +1,851 @@
|
|
|
1
|
+
// fileOperations.test.js
|
|
2
|
+
// Integration tests for ReadFile, WriteFile, and EditFile tools
|
|
3
|
+
|
|
4
|
+
import test from 'ava';
|
|
5
|
+
import serverFactory from '../../../../index.js';
|
|
6
|
+
import { callPathway } from '../../../../lib/pathwayTools.js';
|
|
7
|
+
|
|
8
|
+
let testServer;
|
|
9
|
+
|
|
10
|
+
test.before(async () => {
|
|
11
|
+
const { server, startServer } = await serverFactory();
|
|
12
|
+
if (startServer) {
|
|
13
|
+
await startServer();
|
|
14
|
+
}
|
|
15
|
+
testServer = server;
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test.after.always('cleanup', async () => {
|
|
19
|
+
if (testServer) {
|
|
20
|
+
await testServer.stop();
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Helper to create a test context
|
|
25
|
+
const createTestContext = () => {
|
|
26
|
+
const contextId = `test-fileops-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
27
|
+
return contextId;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Helper to clean up test data
|
|
31
|
+
const cleanup = async (contextId, contextKey = null) => {
|
|
32
|
+
try {
|
|
33
|
+
const { keyValueStorageClient } = await import('../../../../lib/keyValueStorageClient.js');
|
|
34
|
+
await keyValueStorageClient.delete(`${contextId}-memoryFiles`);
|
|
35
|
+
} catch (e) {
|
|
36
|
+
// Ignore cleanup errors
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// ========== WriteFile Tests ==========
|
|
41
|
+
|
|
42
|
+
test('WriteFile: Write and upload text file', async t => {
|
|
43
|
+
const contextId = createTestContext();
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const content = 'Hello, world!\nThis is a test file.';
|
|
47
|
+
const filename = 'test.txt';
|
|
48
|
+
|
|
49
|
+
const result = await callPathway('sys_tool_writefile', {
|
|
50
|
+
contextId,
|
|
51
|
+
content,
|
|
52
|
+
filename,
|
|
53
|
+
userMessage: 'Writing test file'
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const parsed = JSON.parse(result);
|
|
57
|
+
|
|
58
|
+
// Skip test if file handler is not configured
|
|
59
|
+
if (!parsed.success && parsed.error?.includes('WHISPER_MEDIA_API_URL')) {
|
|
60
|
+
t.log('Test skipped - file handler URL not configured');
|
|
61
|
+
t.pass();
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
t.is(parsed.success, true);
|
|
66
|
+
t.is(parsed.filename, filename);
|
|
67
|
+
t.truthy(parsed.url);
|
|
68
|
+
t.is(parsed.size, Buffer.byteLength(content, 'utf8'));
|
|
69
|
+
t.true(parsed.message.includes('written and uploaded successfully'));
|
|
70
|
+
} finally {
|
|
71
|
+
await cleanup(contextId);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('WriteFile: Write JSON file', async t => {
|
|
76
|
+
const contextId = createTestContext();
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
const content = JSON.stringify({ name: 'Test', value: 42 }, null, 2);
|
|
80
|
+
const filename = 'data.json';
|
|
81
|
+
|
|
82
|
+
const result = await callPathway('sys_tool_writefile', {
|
|
83
|
+
contextId,
|
|
84
|
+
content,
|
|
85
|
+
filename,
|
|
86
|
+
userMessage: 'Writing JSON file'
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const parsed = JSON.parse(result);
|
|
90
|
+
|
|
91
|
+
if (!parsed.success && parsed.error?.includes('WHISPER_MEDIA_API_URL')) {
|
|
92
|
+
t.log('Test skipped - file handler URL not configured');
|
|
93
|
+
t.pass();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
t.is(parsed.success, true);
|
|
98
|
+
t.is(parsed.filename, filename);
|
|
99
|
+
t.truthy(parsed.url);
|
|
100
|
+
t.truthy(parsed.hash);
|
|
101
|
+
} finally {
|
|
102
|
+
await cleanup(contextId);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// ========== ReadFile Tests ==========
|
|
107
|
+
|
|
108
|
+
test('ReadFile: Read entire file', async t => {
|
|
109
|
+
const contextId = createTestContext();
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
// First write a file
|
|
113
|
+
const content = 'Line 1\nLine 2\nLine 3\nLine 4\nLine 5';
|
|
114
|
+
const writeResult = await callPathway('sys_tool_writefile', {
|
|
115
|
+
contextId,
|
|
116
|
+
content,
|
|
117
|
+
filename: 'readtest.txt',
|
|
118
|
+
userMessage: 'Writing file for read test'
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const writeParsed = JSON.parse(writeResult);
|
|
122
|
+
|
|
123
|
+
if (!writeParsed.success && writeParsed.error?.includes('WHISPER_MEDIA_API_URL')) {
|
|
124
|
+
t.log('Test skipped - file handler URL not configured');
|
|
125
|
+
t.pass();
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Wait a moment for file to be available
|
|
130
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
131
|
+
|
|
132
|
+
// Now read it
|
|
133
|
+
const readResult = await callPathway('sys_tool_readfile', {
|
|
134
|
+
contextId,
|
|
135
|
+
file: writeParsed.fileId || 'readtest.txt',
|
|
136
|
+
userMessage: 'Reading entire file'
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const readParsed = JSON.parse(readResult);
|
|
140
|
+
t.is(readParsed.success, true);
|
|
141
|
+
t.is(readParsed.totalLines, 5);
|
|
142
|
+
t.is(readParsed.content, content);
|
|
143
|
+
t.is(readParsed.returnedLines, 5);
|
|
144
|
+
} finally {
|
|
145
|
+
await cleanup(contextId);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test('ReadFile: Read line range', async t => {
|
|
150
|
+
const contextId = createTestContext();
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
// First write a file
|
|
154
|
+
const content = 'Line 1\nLine 2\nLine 3\nLine 4\nLine 5';
|
|
155
|
+
const writeResult = await callPathway('sys_tool_writefile', {
|
|
156
|
+
contextId,
|
|
157
|
+
content,
|
|
158
|
+
filename: 'rangetest.txt',
|
|
159
|
+
userMessage: 'Writing file for range read test'
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const writeParsed = JSON.parse(writeResult);
|
|
163
|
+
|
|
164
|
+
if (!writeParsed.success && writeParsed.error?.includes('WHISPER_MEDIA_API_URL')) {
|
|
165
|
+
t.log('Test skipped - file handler URL not configured');
|
|
166
|
+
t.pass();
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
171
|
+
|
|
172
|
+
// Read lines 2-4
|
|
173
|
+
const readResult = await callPathway('sys_tool_readfile', {
|
|
174
|
+
contextId,
|
|
175
|
+
file: writeParsed.fileId || 'rangetest.txt',
|
|
176
|
+
startLine: 2,
|
|
177
|
+
endLine: 4,
|
|
178
|
+
userMessage: 'Reading line range'
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const readParsed = JSON.parse(readResult);
|
|
182
|
+
t.is(readParsed.success, true);
|
|
183
|
+
t.is(readParsed.totalLines, 5);
|
|
184
|
+
t.is(readParsed.startLine, 2);
|
|
185
|
+
t.is(readParsed.endLine, 4);
|
|
186
|
+
t.is(readParsed.returnedLines, 3);
|
|
187
|
+
t.is(readParsed.content, 'Line 2\nLine 3\nLine 4');
|
|
188
|
+
} finally {
|
|
189
|
+
await cleanup(contextId);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test('ReadFile: Read with maxLines limit', async t => {
|
|
194
|
+
const contextId = createTestContext();
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
// Write a large file
|
|
198
|
+
const lines = Array.from({ length: 100 }, (_, i) => `Line ${i + 1}`);
|
|
199
|
+
const content = lines.join('\n');
|
|
200
|
+
|
|
201
|
+
const writeResult = await callPathway('sys_tool_writefile', {
|
|
202
|
+
contextId,
|
|
203
|
+
content,
|
|
204
|
+
filename: 'largetest.txt',
|
|
205
|
+
userMessage: 'Writing large file'
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const writeParsed = JSON.parse(writeResult);
|
|
209
|
+
|
|
210
|
+
if (!writeParsed.success && writeParsed.error?.includes('WHISPER_MEDIA_API_URL')) {
|
|
211
|
+
t.log('Test skipped - file handler URL not configured');
|
|
212
|
+
t.pass();
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
217
|
+
|
|
218
|
+
// Read with maxLines limit
|
|
219
|
+
const readResult = await callPathway('sys_tool_readfile', {
|
|
220
|
+
contextId,
|
|
221
|
+
file: writeParsed.fileId || 'largetest.txt',
|
|
222
|
+
maxLines: 10,
|
|
223
|
+
userMessage: 'Reading with limit'
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
const readParsed = JSON.parse(readResult);
|
|
227
|
+
t.is(readParsed.success, true);
|
|
228
|
+
t.is(readParsed.totalLines, 100);
|
|
229
|
+
t.is(readParsed.returnedLines, 10);
|
|
230
|
+
t.true(readParsed.truncated);
|
|
231
|
+
} finally {
|
|
232
|
+
await cleanup(contextId);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// ========== EditFileByLine Tests ==========
|
|
237
|
+
|
|
238
|
+
test('EditFileByLine: Replace single line', async t => {
|
|
239
|
+
const contextId = createTestContext();
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
// Write initial file
|
|
243
|
+
const initialContent = 'Line 1\nLine 2\nLine 3\nLine 4\nLine 5';
|
|
244
|
+
const writeResult = await callPathway('sys_tool_writefile', {
|
|
245
|
+
contextId,
|
|
246
|
+
content: initialContent,
|
|
247
|
+
filename: 'modifytest.txt',
|
|
248
|
+
userMessage: 'Writing file for modify test'
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
const writeParsed = JSON.parse(writeResult);
|
|
252
|
+
|
|
253
|
+
if (!writeParsed.success && writeParsed.error?.includes('WHISPER_MEDIA_API_URL')) {
|
|
254
|
+
t.log('Test skipped - file handler URL not configured');
|
|
255
|
+
t.pass();
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
t.is(writeParsed.success, true, 'File should be written successfully');
|
|
260
|
+
t.truthy(writeParsed.url, 'File should have a URL');
|
|
261
|
+
|
|
262
|
+
// Wait for file to be available (increased wait for reliability)
|
|
263
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
264
|
+
|
|
265
|
+
// Modify line 3
|
|
266
|
+
const modifyResult = await callPathway('sys_tool_editfile', {
|
|
267
|
+
contextId,
|
|
268
|
+
file: writeParsed.fileId || 'modifytest.txt',
|
|
269
|
+
startLine: 3,
|
|
270
|
+
endLine: 3,
|
|
271
|
+
content: 'Modified Line 3',
|
|
272
|
+
userMessage: 'Modifying line 3'
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
const modifyParsed = JSON.parse(modifyResult);
|
|
276
|
+
t.is(modifyParsed.success, true);
|
|
277
|
+
t.is(modifyParsed.replacedLines, 1);
|
|
278
|
+
t.is(modifyParsed.insertedLines, 1);
|
|
279
|
+
|
|
280
|
+
// Read back to verify
|
|
281
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
282
|
+
const readResult = await callPathway('sys_tool_readfile', {
|
|
283
|
+
contextId,
|
|
284
|
+
file: modifyParsed.fileId || 'modifytest.txt',
|
|
285
|
+
userMessage: 'Reading modified file'
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
const readParsed = JSON.parse(readResult);
|
|
289
|
+
t.is(readParsed.success, true);
|
|
290
|
+
const lines = readParsed.content.split('\n');
|
|
291
|
+
t.is(lines[2], 'Modified Line 3'); // Line 3 is index 2 (0-indexed)
|
|
292
|
+
} finally {
|
|
293
|
+
await cleanup(contextId);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
test('EditFileByLine: Replace multiple lines', async t => {
|
|
298
|
+
const contextId = createTestContext();
|
|
299
|
+
|
|
300
|
+
try {
|
|
301
|
+
// Write initial file
|
|
302
|
+
const initialContent = 'Line 1\nLine 2\nLine 3\nLine 4\nLine 5';
|
|
303
|
+
const writeResult = await callPathway('sys_tool_writefile', {
|
|
304
|
+
contextId,
|
|
305
|
+
content: initialContent,
|
|
306
|
+
filename: 'multimodify.txt',
|
|
307
|
+
userMessage: 'Writing file for multi-line modify'
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
const writeParsed = JSON.parse(writeResult);
|
|
311
|
+
|
|
312
|
+
if (!writeParsed.success && writeParsed.error?.includes('WHISPER_MEDIA_API_URL')) {
|
|
313
|
+
t.log('Test skipped - file handler URL not configured');
|
|
314
|
+
t.pass();
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
319
|
+
|
|
320
|
+
// Replace lines 2-4 with new content
|
|
321
|
+
const modifyResult = await callPathway('sys_tool_editfile', {
|
|
322
|
+
contextId,
|
|
323
|
+
file: writeParsed.fileId || 'multimodify.txt',
|
|
324
|
+
startLine: 2,
|
|
325
|
+
endLine: 4,
|
|
326
|
+
content: 'New Line 2\nNew Line 3\nNew Line 4',
|
|
327
|
+
userMessage: 'Replacing multiple lines'
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
const modifyParsed = JSON.parse(modifyResult);
|
|
331
|
+
t.is(modifyParsed.success, true);
|
|
332
|
+
t.is(modifyParsed.replacedLines, 3);
|
|
333
|
+
t.is(modifyParsed.insertedLines, 3);
|
|
334
|
+
|
|
335
|
+
// Read back to verify
|
|
336
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
337
|
+
const readResult = await callPathway('sys_tool_readfile', {
|
|
338
|
+
contextId,
|
|
339
|
+
file: modifyParsed.fileId || 'multimodify.txt',
|
|
340
|
+
userMessage: 'Reading modified file'
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
const readParsed = JSON.parse(readResult);
|
|
344
|
+
t.is(readParsed.success, true);
|
|
345
|
+
const lines = readParsed.content.split('\n');
|
|
346
|
+
t.is(lines[0], 'Line 1');
|
|
347
|
+
t.is(lines[1], 'New Line 2');
|
|
348
|
+
t.is(lines[2], 'New Line 3');
|
|
349
|
+
t.is(lines[3], 'New Line 4');
|
|
350
|
+
t.is(lines[4], 'Line 5');
|
|
351
|
+
} finally {
|
|
352
|
+
await cleanup(contextId);
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
test('EditFileByLine: Insert content (replace with more lines)', async t => {
|
|
357
|
+
const contextId = createTestContext();
|
|
358
|
+
|
|
359
|
+
try {
|
|
360
|
+
// Write initial file
|
|
361
|
+
const initialContent = 'Line 1\nLine 2\nLine 3';
|
|
362
|
+
const writeResult = await callPathway('sys_tool_writefile', {
|
|
363
|
+
contextId,
|
|
364
|
+
content: initialContent,
|
|
365
|
+
filename: 'inserttest.txt',
|
|
366
|
+
userMessage: 'Writing file for insert test'
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
const writeParsed = JSON.parse(writeResult);
|
|
370
|
+
|
|
371
|
+
if (!writeParsed.success && writeParsed.error?.includes('WHISPER_MEDIA_API_URL')) {
|
|
372
|
+
t.log('Test skipped - file handler URL not configured');
|
|
373
|
+
t.pass();
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
378
|
+
|
|
379
|
+
// Replace line 2 with 3 new lines
|
|
380
|
+
const modifyResult = await callPathway('sys_tool_editfile', {
|
|
381
|
+
contextId,
|
|
382
|
+
file: writeParsed.fileId || 'inserttest.txt',
|
|
383
|
+
startLine: 2,
|
|
384
|
+
endLine: 2,
|
|
385
|
+
content: 'New Line 2a\nNew Line 2b\nNew Line 2c',
|
|
386
|
+
userMessage: 'Inserting multiple lines'
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
const modifyParsed = JSON.parse(modifyResult);
|
|
390
|
+
t.is(modifyParsed.success, true);
|
|
391
|
+
t.is(modifyParsed.replacedLines, 1);
|
|
392
|
+
t.is(modifyParsed.insertedLines, 3);
|
|
393
|
+
t.is(modifyParsed.modifiedLines, 5); // 1 + 3 + 1 = 5 lines
|
|
394
|
+
|
|
395
|
+
// Read back to verify
|
|
396
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
397
|
+
const readResult = await callPathway('sys_tool_readfile', {
|
|
398
|
+
contextId,
|
|
399
|
+
file: modifyParsed.fileId || 'inserttest.txt',
|
|
400
|
+
userMessage: 'Reading modified file'
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
const readParsed = JSON.parse(readResult);
|
|
404
|
+
t.is(readParsed.success, true);
|
|
405
|
+
t.is(readParsed.totalLines, 5);
|
|
406
|
+
const lines = readParsed.content.split('\n');
|
|
407
|
+
t.is(lines[0], 'Line 1');
|
|
408
|
+
t.is(lines[1], 'New Line 2a');
|
|
409
|
+
t.is(lines[2], 'New Line 2b');
|
|
410
|
+
t.is(lines[3], 'New Line 2c');
|
|
411
|
+
t.is(lines[4], 'Line 3');
|
|
412
|
+
} finally {
|
|
413
|
+
await cleanup(contextId);
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
test('EditFileByLine: Delete content (replace with fewer lines)', async t => {
|
|
418
|
+
const contextId = createTestContext();
|
|
419
|
+
|
|
420
|
+
try {
|
|
421
|
+
// Write initial file
|
|
422
|
+
const initialContent = 'Line 1\nLine 2\nLine 3\nLine 4\nLine 5';
|
|
423
|
+
const writeResult = await callPathway('sys_tool_writefile', {
|
|
424
|
+
contextId,
|
|
425
|
+
content: initialContent,
|
|
426
|
+
filename: 'deletetest.txt',
|
|
427
|
+
userMessage: 'Writing file for delete test'
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
const writeParsed = JSON.parse(writeResult);
|
|
431
|
+
|
|
432
|
+
if (!writeParsed.success && writeParsed.error?.includes('WHISPER_MEDIA_API_URL')) {
|
|
433
|
+
t.log('Test skipped - file handler URL not configured');
|
|
434
|
+
t.pass();
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
439
|
+
|
|
440
|
+
// Replace lines 2-4 with a single line
|
|
441
|
+
const modifyResult = await callPathway('sys_tool_editfile', {
|
|
442
|
+
contextId,
|
|
443
|
+
file: writeParsed.fileId || 'deletetest.txt',
|
|
444
|
+
startLine: 2,
|
|
445
|
+
endLine: 4,
|
|
446
|
+
content: 'Replacement Line',
|
|
447
|
+
userMessage: 'Deleting multiple lines'
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
const modifyParsed = JSON.parse(modifyResult);
|
|
451
|
+
t.is(modifyParsed.success, true);
|
|
452
|
+
t.is(modifyParsed.replacedLines, 3);
|
|
453
|
+
t.is(modifyParsed.insertedLines, 1);
|
|
454
|
+
t.is(modifyParsed.modifiedLines, 3); // 1 + 1 + 1 = 3 lines
|
|
455
|
+
|
|
456
|
+
// Read back to verify
|
|
457
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
458
|
+
const readResult = await callPathway('sys_tool_readfile', {
|
|
459
|
+
contextId,
|
|
460
|
+
file: modifyParsed.fileId || 'deletetest.txt',
|
|
461
|
+
userMessage: 'Reading modified file'
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
const readParsed = JSON.parse(readResult);
|
|
465
|
+
t.is(readParsed.success, true);
|
|
466
|
+
t.is(readParsed.totalLines, 3);
|
|
467
|
+
const lines = readParsed.content.split('\n');
|
|
468
|
+
t.is(lines[0], 'Line 1');
|
|
469
|
+
t.is(lines[1], 'Replacement Line');
|
|
470
|
+
t.is(lines[2], 'Line 5');
|
|
471
|
+
} finally {
|
|
472
|
+
await cleanup(contextId);
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
test('EditFileByLine: Error handling - file not found', async t => {
|
|
477
|
+
const contextId = createTestContext();
|
|
478
|
+
|
|
479
|
+
try {
|
|
480
|
+
const result = await callPathway('sys_tool_editfile', {
|
|
481
|
+
contextId,
|
|
482
|
+
file: 'nonexistent.txt',
|
|
483
|
+
startLine: 1,
|
|
484
|
+
endLine: 1,
|
|
485
|
+
content: 'test',
|
|
486
|
+
userMessage: 'Trying to modify nonexistent file'
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
const parsed = JSON.parse(result);
|
|
490
|
+
t.is(parsed.success, false);
|
|
491
|
+
t.true(parsed.error?.includes('not found') || parsed.error?.includes('File not found'));
|
|
492
|
+
} finally {
|
|
493
|
+
await cleanup(contextId);
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
test('EditFileByLine: Error handling - invalid line range', async t => {
|
|
498
|
+
const contextId = createTestContext();
|
|
499
|
+
|
|
500
|
+
try {
|
|
501
|
+
// Write a file first
|
|
502
|
+
const writeResult = await callPathway('sys_tool_writefile', {
|
|
503
|
+
contextId,
|
|
504
|
+
content: 'Line 1\nLine 2',
|
|
505
|
+
filename: 'rangetest.txt',
|
|
506
|
+
userMessage: 'Writing test file'
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
const writeParsed = JSON.parse(writeResult);
|
|
510
|
+
|
|
511
|
+
if (!writeParsed.success && writeParsed.error?.includes('WHISPER_MEDIA_API_URL')) {
|
|
512
|
+
t.log('Test skipped - file handler URL not configured');
|
|
513
|
+
t.pass();
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
518
|
+
|
|
519
|
+
// Try invalid range (endLine < startLine)
|
|
520
|
+
const result = await callPathway('sys_tool_editfile', {
|
|
521
|
+
contextId,
|
|
522
|
+
file: writeParsed.fileId || 'rangetest.txt',
|
|
523
|
+
startLine: 5,
|
|
524
|
+
endLine: 3,
|
|
525
|
+
content: 'test',
|
|
526
|
+
userMessage: 'Invalid range test'
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
const parsed = JSON.parse(result);
|
|
530
|
+
t.is(parsed.success, false);
|
|
531
|
+
t.true(parsed.error?.includes('endLine must be >= startLine'));
|
|
532
|
+
} finally {
|
|
533
|
+
await cleanup(contextId);
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
test('EditFileByLine: Error handling - line out of range', async t => {
|
|
538
|
+
const contextId = createTestContext();
|
|
539
|
+
|
|
540
|
+
try {
|
|
541
|
+
// Write a file with 2 lines
|
|
542
|
+
const writeResult = await callPathway('sys_tool_writefile', {
|
|
543
|
+
contextId,
|
|
544
|
+
content: 'Line 1\nLine 2',
|
|
545
|
+
filename: 'rangetest2.txt',
|
|
546
|
+
userMessage: 'Writing test file'
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
const writeParsed = JSON.parse(writeResult);
|
|
550
|
+
|
|
551
|
+
if (!writeParsed.success && writeParsed.error?.includes('WHISPER_MEDIA_API_URL')) {
|
|
552
|
+
t.log('Test skipped - file handler URL not configured');
|
|
553
|
+
t.pass();
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
558
|
+
|
|
559
|
+
// Try to modify line 10 (doesn't exist)
|
|
560
|
+
const result = await callPathway('sys_tool_editfile', {
|
|
561
|
+
contextId,
|
|
562
|
+
file: writeParsed.fileId || 'rangetest2.txt',
|
|
563
|
+
startLine: 10,
|
|
564
|
+
endLine: 10,
|
|
565
|
+
content: 'test',
|
|
566
|
+
userMessage: 'Out of range test'
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
const parsed = JSON.parse(result);
|
|
570
|
+
t.is(parsed.success, false);
|
|
571
|
+
t.true(parsed.error?.includes('exceeds file length'));
|
|
572
|
+
} finally {
|
|
573
|
+
await cleanup(contextId);
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
// ========== EditFileBySearchAndReplace Tests ==========
|
|
578
|
+
|
|
579
|
+
test('EditFileBySearchAndReplace: Replace first occurrence', async t => {
|
|
580
|
+
const contextId = createTestContext();
|
|
581
|
+
|
|
582
|
+
try {
|
|
583
|
+
// Write initial file
|
|
584
|
+
const initialContent = 'Hello world\nThis is a test\nHello again';
|
|
585
|
+
const writeResult = await callPathway('sys_tool_writefile', {
|
|
586
|
+
contextId,
|
|
587
|
+
content: initialContent,
|
|
588
|
+
filename: 'searchreplace.txt',
|
|
589
|
+
userMessage: 'Writing file for search replace test'
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
const writeParsed = JSON.parse(writeResult);
|
|
593
|
+
|
|
594
|
+
if (!writeParsed.success && writeParsed.error?.includes('WHISPER_MEDIA_API_URL')) {
|
|
595
|
+
t.log('Test skipped - file handler URL not configured');
|
|
596
|
+
t.pass();
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
601
|
+
|
|
602
|
+
// Replace first occurrence of "Hello"
|
|
603
|
+
const modifyResult = await callPathway('sys_tool_editfile', {
|
|
604
|
+
contextId,
|
|
605
|
+
file: writeParsed.fileId || 'searchreplace.txt',
|
|
606
|
+
oldString: 'Hello',
|
|
607
|
+
newString: 'Hi',
|
|
608
|
+
replaceAll: false,
|
|
609
|
+
userMessage: 'Replacing first occurrence'
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
const modifyParsed = JSON.parse(modifyResult);
|
|
613
|
+
t.is(modifyParsed.success, true);
|
|
614
|
+
t.is(modifyParsed.mode, 'string-based');
|
|
615
|
+
t.is(modifyParsed.replaceAll, false);
|
|
616
|
+
t.is(modifyParsed.occurrencesReplaced, 1);
|
|
617
|
+
t.is(modifyParsed.totalOccurrences, 2);
|
|
618
|
+
|
|
619
|
+
// Read back to verify
|
|
620
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
621
|
+
const readResult = await callPathway('sys_tool_readfile', {
|
|
622
|
+
contextId,
|
|
623
|
+
file: modifyParsed.fileId || 'searchreplace.txt',
|
|
624
|
+
userMessage: 'Reading modified file'
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
const readParsed = JSON.parse(readResult);
|
|
628
|
+
t.is(readParsed.success, true);
|
|
629
|
+
t.is(readParsed.content, 'Hi world\nThis is a test\nHello again');
|
|
630
|
+
} finally {
|
|
631
|
+
await cleanup(contextId);
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
test('EditFileBySearchAndReplace: Replace all occurrences', async t => {
|
|
636
|
+
const contextId = createTestContext();
|
|
637
|
+
|
|
638
|
+
try {
|
|
639
|
+
// Write initial file
|
|
640
|
+
const initialContent = 'Hello world\nThis is a test\nHello again';
|
|
641
|
+
const writeResult = await callPathway('sys_tool_writefile', {
|
|
642
|
+
contextId,
|
|
643
|
+
content: initialContent,
|
|
644
|
+
filename: 'searchreplaceall.txt',
|
|
645
|
+
userMessage: 'Writing file for search replace all test'
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
const writeParsed = JSON.parse(writeResult);
|
|
649
|
+
|
|
650
|
+
if (!writeParsed.success && writeParsed.error?.includes('WHISPER_MEDIA_API_URL')) {
|
|
651
|
+
t.log('Test skipped - file handler URL not configured');
|
|
652
|
+
t.pass();
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
657
|
+
|
|
658
|
+
// Replace all occurrences of "Hello"
|
|
659
|
+
const modifyResult = await callPathway('sys_tool_editfile', {
|
|
660
|
+
contextId,
|
|
661
|
+
file: writeParsed.fileId || 'searchreplaceall.txt',
|
|
662
|
+
oldString: 'Hello',
|
|
663
|
+
newString: 'Hi',
|
|
664
|
+
replaceAll: true,
|
|
665
|
+
userMessage: 'Replacing all occurrences'
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
const modifyParsed = JSON.parse(modifyResult);
|
|
669
|
+
t.is(modifyParsed.success, true);
|
|
670
|
+
t.is(modifyParsed.mode, 'string-based');
|
|
671
|
+
t.is(modifyParsed.replaceAll, true);
|
|
672
|
+
t.is(modifyParsed.occurrencesReplaced, 2);
|
|
673
|
+
|
|
674
|
+
// Read back to verify
|
|
675
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
676
|
+
const readResult = await callPathway('sys_tool_readfile', {
|
|
677
|
+
contextId,
|
|
678
|
+
file: modifyParsed.fileId || 'searchreplaceall.txt',
|
|
679
|
+
userMessage: 'Reading modified file'
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
const readParsed = JSON.parse(readResult);
|
|
683
|
+
t.is(readParsed.success, true);
|
|
684
|
+
t.is(readParsed.content, 'Hi world\nThis is a test\nHi again');
|
|
685
|
+
} finally {
|
|
686
|
+
await cleanup(contextId);
|
|
687
|
+
}
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
test('EditFileBySearchAndReplace: Replace multiline string', async t => {
|
|
691
|
+
const contextId = createTestContext();
|
|
692
|
+
|
|
693
|
+
try {
|
|
694
|
+
// Write initial file
|
|
695
|
+
const initialContent = 'Line 1\nLine 2\nLine 3\nLine 2\nLine 4';
|
|
696
|
+
const writeResult = await callPathway('sys_tool_writefile', {
|
|
697
|
+
contextId,
|
|
698
|
+
content: initialContent,
|
|
699
|
+
filename: 'multiline.txt',
|
|
700
|
+
userMessage: 'Writing file for multiline replace test'
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
const writeParsed = JSON.parse(writeResult);
|
|
704
|
+
|
|
705
|
+
if (!writeParsed.success && writeParsed.error?.includes('WHISPER_MEDIA_API_URL')) {
|
|
706
|
+
t.log('Test skipped - file handler URL not configured');
|
|
707
|
+
t.pass();
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
712
|
+
|
|
713
|
+
// Replace multiline string
|
|
714
|
+
const modifyResult = await callPathway('sys_tool_editfile', {
|
|
715
|
+
contextId,
|
|
716
|
+
file: writeParsed.fileId || 'multiline.txt',
|
|
717
|
+
oldString: 'Line 2\nLine 3',
|
|
718
|
+
newString: 'Replaced 2\nReplaced 3',
|
|
719
|
+
replaceAll: false,
|
|
720
|
+
userMessage: 'Replacing multiline string'
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
const modifyParsed = JSON.parse(modifyResult);
|
|
724
|
+
t.is(modifyParsed.success, true);
|
|
725
|
+
|
|
726
|
+
// Read back to verify
|
|
727
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
728
|
+
const readResult = await callPathway('sys_tool_readfile', {
|
|
729
|
+
contextId,
|
|
730
|
+
file: modifyParsed.fileId || 'multiline.txt',
|
|
731
|
+
userMessage: 'Reading modified file'
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
const readParsed = JSON.parse(readResult);
|
|
735
|
+
t.is(readParsed.success, true);
|
|
736
|
+
t.is(readParsed.content, 'Line 1\nReplaced 2\nReplaced 3\nLine 2\nLine 4');
|
|
737
|
+
} finally {
|
|
738
|
+
await cleanup(contextId);
|
|
739
|
+
}
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
test('EditFileBySearchAndReplace: Error handling - string not found', async t => {
|
|
743
|
+
const contextId = createTestContext();
|
|
744
|
+
|
|
745
|
+
try {
|
|
746
|
+
// Write a file
|
|
747
|
+
const writeResult = await callPathway('sys_tool_writefile', {
|
|
748
|
+
contextId,
|
|
749
|
+
content: 'Line 1\nLine 2',
|
|
750
|
+
filename: 'notfound.txt',
|
|
751
|
+
userMessage: 'Writing test file'
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
const writeParsed = JSON.parse(writeResult);
|
|
755
|
+
|
|
756
|
+
if (!writeParsed.success && writeParsed.error?.includes('WHISPER_MEDIA_API_URL')) {
|
|
757
|
+
t.log('Test skipped - file handler URL not configured');
|
|
758
|
+
t.pass();
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
763
|
+
|
|
764
|
+
// Try to replace a string that doesn't exist
|
|
765
|
+
const result = await callPathway('sys_tool_editfile', {
|
|
766
|
+
contextId,
|
|
767
|
+
file: writeParsed.fileId || 'notfound.txt',
|
|
768
|
+
oldString: 'This string does not exist',
|
|
769
|
+
newString: 'replacement',
|
|
770
|
+
userMessage: 'Trying to replace non-existent string'
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
const parsed = JSON.parse(result);
|
|
774
|
+
t.is(parsed.success, false);
|
|
775
|
+
t.true(parsed.error?.includes('not found in file') || parsed.error?.includes('oldString not found'));
|
|
776
|
+
} finally {
|
|
777
|
+
await cleanup(contextId);
|
|
778
|
+
}
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
// ========== Integration Tests ==========
|
|
782
|
+
|
|
783
|
+
test('File Operations: Write, Read, Modify workflow', async t => {
|
|
784
|
+
const contextId = createTestContext();
|
|
785
|
+
|
|
786
|
+
try {
|
|
787
|
+
// 1. Write a file
|
|
788
|
+
const writeResult = await callPathway('sys_tool_writefile', {
|
|
789
|
+
contextId,
|
|
790
|
+
content: 'Initial content\nLine 2\nLine 3',
|
|
791
|
+
filename: 'workflow.txt',
|
|
792
|
+
userMessage: 'Writing initial file'
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
const writeParsed = JSON.parse(writeResult);
|
|
796
|
+
|
|
797
|
+
if (!writeParsed.success && writeParsed.error?.includes('WHISPER_MEDIA_API_URL')) {
|
|
798
|
+
t.log('Test skipped - file handler URL not configured');
|
|
799
|
+
t.pass();
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
t.is(writeParsed.success, true);
|
|
804
|
+
const fileId = writeParsed.fileId;
|
|
805
|
+
|
|
806
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
807
|
+
|
|
808
|
+
// 2. Read the file
|
|
809
|
+
const readResult = await callPathway('sys_tool_readfile', {
|
|
810
|
+
contextId,
|
|
811
|
+
file: fileId,
|
|
812
|
+
userMessage: 'Reading file'
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
const readParsed = JSON.parse(readResult);
|
|
816
|
+
t.is(readParsed.success, true);
|
|
817
|
+
t.is(readParsed.totalLines, 3);
|
|
818
|
+
|
|
819
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
820
|
+
|
|
821
|
+
// 3. Modify the file
|
|
822
|
+
const modifyResult = await callPathway('sys_tool_editfile', {
|
|
823
|
+
contextId,
|
|
824
|
+
file: fileId,
|
|
825
|
+
startLine: 2,
|
|
826
|
+
endLine: 2,
|
|
827
|
+
content: 'Modified Line 2',
|
|
828
|
+
userMessage: 'Modifying file'
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
const modifyParsed = JSON.parse(modifyResult);
|
|
832
|
+
t.is(modifyParsed.success, true);
|
|
833
|
+
|
|
834
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
835
|
+
|
|
836
|
+
// 4. Read again to verify modification
|
|
837
|
+
const readResult2 = await callPathway('sys_tool_readfile', {
|
|
838
|
+
contextId,
|
|
839
|
+
file: fileId,
|
|
840
|
+
userMessage: 'Reading modified file'
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
const readParsed2 = JSON.parse(readResult2);
|
|
844
|
+
t.is(readParsed2.success, true);
|
|
845
|
+
const lines = readParsed2.content.split('\n');
|
|
846
|
+
t.is(lines[1], 'Modified Line 2');
|
|
847
|
+
} finally {
|
|
848
|
+
await cleanup(contextId);
|
|
849
|
+
}
|
|
850
|
+
});
|
|
851
|
+
|