@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.
Files changed (38) hide show
  1. package/helper-apps/cortex-file-handler/package-lock.json +2 -2
  2. package/helper-apps/cortex-file-handler/package.json +1 -1
  3. package/helper-apps/cortex-file-handler/src/index.js +27 -4
  4. package/helper-apps/cortex-file-handler/src/services/storage/AzureStorageProvider.js +74 -10
  5. package/helper-apps/cortex-file-handler/src/services/storage/StorageService.js +23 -2
  6. package/helper-apps/cortex-file-handler/src/start.js +2 -0
  7. package/helper-apps/cortex-file-handler/tests/deleteOperations.test.js +287 -0
  8. package/helper-apps/cortex-file-handler/tests/start.test.js +1 -1
  9. package/lib/entityConstants.js +1 -1
  10. package/lib/fileUtils.js +1481 -0
  11. package/lib/pathwayTools.js +7 -1
  12. package/lib/util.js +2 -313
  13. package/package.json +4 -3
  14. package/pathways/image_qwen.js +1 -1
  15. package/pathways/system/entity/memory/sys_read_memory.js +17 -3
  16. package/pathways/system/entity/memory/sys_save_memory.js +22 -6
  17. package/pathways/system/entity/sys_entity_agent.js +21 -4
  18. package/pathways/system/entity/tools/sys_tool_analyzefile.js +171 -0
  19. package/pathways/system/entity/tools/sys_tool_codingagent.js +38 -4
  20. package/pathways/system/entity/tools/sys_tool_editfile.js +403 -0
  21. package/pathways/system/entity/tools/sys_tool_file_collection.js +433 -0
  22. package/pathways/system/entity/tools/sys_tool_image.js +172 -10
  23. package/pathways/system/entity/tools/sys_tool_image_gemini.js +123 -10
  24. package/pathways/system/entity/tools/sys_tool_readfile.js +217 -124
  25. package/pathways/system/entity/tools/sys_tool_validate_url.js +137 -0
  26. package/pathways/system/entity/tools/sys_tool_writefile.js +211 -0
  27. package/pathways/system/workspaces/run_workspace_prompt.js +4 -3
  28. package/pathways/transcribe_gemini.js +2 -1
  29. package/server/executeWorkspace.js +1 -1
  30. package/server/plugins/neuralSpacePlugin.js +2 -6
  31. package/server/plugins/openAiWhisperPlugin.js +2 -1
  32. package/server/plugins/replicateApiPlugin.js +4 -14
  33. package/server/typeDef.js +10 -1
  34. package/tests/integration/features/tools/fileCollection.test.js +858 -0
  35. package/tests/integration/features/tools/fileOperations.test.js +851 -0
  36. package/tests/integration/features/tools/writefile.test.js +350 -0
  37. package/tests/unit/core/fileCollection.test.js +259 -0
  38. 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
+