mio-agent-runtime 0.3.15 → 0.5.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/package.json +25 -25
- package/server/mio-intelligence-mcp/README.md +6 -1
- package/server/mio-intelligence-mcp/index.js +2011 -1575
|
@@ -1,1587 +1,2023 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict'
|
|
3
|
-
|
|
4
|
-
const fs = require('fs')
|
|
5
|
-
const path = require('path')
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
)
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
entry &&
|
|
62
|
-
typeof entry.
|
|
63
|
-
entry.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
.
|
|
105
|
-
.
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
return
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
.
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
const
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
const
|
|
267
|
-
const
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
(
|
|
278
|
-
|
|
279
|
-
.filter((record) => record
|
|
280
|
-
.filter((record) =>
|
|
281
|
-
.
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
const
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
if (
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
const
|
|
377
|
-
const
|
|
378
|
-
const
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
return
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
const
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
const
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
subscription.
|
|
481
|
-
subscription.
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
existing
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
const
|
|
515
|
-
const
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
const
|
|
535
|
-
const
|
|
536
|
-
const
|
|
537
|
-
const
|
|
538
|
-
|
|
539
|
-
const
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
const
|
|
543
|
-
|
|
544
|
-
const
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
if (
|
|
581
|
-
state[subscription.id] =
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
const
|
|
601
|
-
const
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
const
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
.
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
const
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
)
|
|
664
|
-
const
|
|
665
|
-
const
|
|
666
|
-
const
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
const
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
const
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
if (!
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
if (
|
|
728
|
-
suggestions.push('
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
const
|
|
749
|
-
if (
|
|
750
|
-
const
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
const
|
|
754
|
-
|
|
755
|
-
const
|
|
756
|
-
const
|
|
757
|
-
const
|
|
758
|
-
const
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
record.
|
|
768
|
-
record.
|
|
769
|
-
record.
|
|
770
|
-
record.
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
const
|
|
791
|
-
|
|
792
|
-
const
|
|
793
|
-
|
|
794
|
-
const
|
|
795
|
-
|
|
796
|
-
const
|
|
797
|
-
const
|
|
798
|
-
const
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
if (
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
delete record.
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
record.
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
const
|
|
831
|
-
const
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
guidance
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
guidance.
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
.
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
const
|
|
904
|
-
|
|
905
|
-
const
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
const
|
|
915
|
-
const
|
|
916
|
-
const
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
.
|
|
925
|
-
.
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
if (
|
|
937
|
-
suggestion = '
|
|
938
|
-
} else if (risk >= 0.
|
|
939
|
-
suggestion = '
|
|
940
|
-
} else
|
|
941
|
-
suggestion = '
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
.
|
|
948
|
-
.
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
const
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
if (!
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
const
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
(record.
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
if (!
|
|
1067
|
-
|
|
1068
|
-
if (!
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
entry.
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
const
|
|
1085
|
-
const
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
const
|
|
1133
|
-
|
|
1134
|
-
const
|
|
1135
|
-
const
|
|
1136
|
-
const
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
(
|
|
1196
|
-
|
|
1197
|
-
.
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
if (
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
const fs = require('fs')
|
|
5
|
+
const path = require('path')
|
|
6
|
+
const crypto = require('crypto')
|
|
7
|
+
const readline = require('readline')
|
|
8
|
+
const { execFileSync } = require('child_process')
|
|
9
|
+
|
|
10
|
+
const SERVER_INFO = { name: 'mio-intelligence-mcp', version: '0.1.0' }
|
|
11
|
+
const PROTOCOL_VERSION = '2024-11-05'
|
|
12
|
+
|
|
13
|
+
const dataDir = path.resolve(
|
|
14
|
+
process.env.MIO_DATA_DIR || path.join(process.cwd(), '.mio-intelligence')
|
|
15
|
+
)
|
|
16
|
+
const memoryPath = path.join(dataDir, 'memory.jsonl')
|
|
17
|
+
const tracePath = path.join(dataDir, 'traces.jsonl')
|
|
18
|
+
const experienceReusePath = path.join(dataDir, 'experience_reuse.jsonl')
|
|
19
|
+
const queryPath = path.join(dataDir, 'queries.jsonl')
|
|
20
|
+
const agentsPath = path.join(dataDir, 'agents.jsonl')
|
|
21
|
+
const subscriptionPath = path.join(dataDir, 'subscriptions.jsonl')
|
|
22
|
+
const digestStatePath = path.join(dataDir, 'digest_state.json')
|
|
23
|
+
fs.mkdirSync(dataDir, { recursive: true })
|
|
24
|
+
|
|
25
|
+
const { loadPhase0, summarizePhase0, renderPhase0Markdown } = require('./phase0')
|
|
26
|
+
|
|
27
|
+
let runtimeContext = {}
|
|
28
|
+
try {
|
|
29
|
+
const rawContext = process.env.MIO_CONTEXT
|
|
30
|
+
if (rawContext) {
|
|
31
|
+
const parsed = JSON.parse(rawContext)
|
|
32
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
33
|
+
runtimeContext = parsed
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
} catch (_) {
|
|
37
|
+
runtimeContext = {}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function runtimeAgentId() {
|
|
41
|
+
return runtimeContext.agentId || null
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Phase 0 auto-claim: match memory.query results with a later task_outcome
|
|
45
|
+
// from the same agent+project, so reuse evidence does not depend on the
|
|
46
|
+
// agent self-reporting mio.experience.reuse.
|
|
47
|
+
const REUSE_MATCH_WINDOW_MS = (() => {
|
|
48
|
+
const minutes = Number(process.env.MIO_REUSE_MATCH_WINDOW_MIN)
|
|
49
|
+
return Number.isFinite(minutes) && minutes > 0 ? minutes * 60 * 1000 : 60 * 60 * 1000
|
|
50
|
+
})()
|
|
51
|
+
const MAX_RECENT_QUERIES = 200
|
|
52
|
+
let recentQueries = []
|
|
53
|
+
|
|
54
|
+
function loadRecentQueries() {
|
|
55
|
+
const now = Date.now()
|
|
56
|
+
const entries = readJsonl(queryPath)
|
|
57
|
+
.filter(
|
|
58
|
+
(entry) =>
|
|
59
|
+
entry &&
|
|
60
|
+
typeof entry.agent === 'string' &&
|
|
61
|
+
entry.agent &&
|
|
62
|
+
typeof entry.expiresAt === 'number' &&
|
|
63
|
+
entry.expiresAt > now
|
|
64
|
+
)
|
|
65
|
+
.slice(-MAX_RECENT_QUERIES)
|
|
66
|
+
recentQueries = entries
|
|
67
|
+
return entries
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function persistRecentQueries(entries) {
|
|
71
|
+
const limited = entries.slice(-MAX_RECENT_QUERIES)
|
|
72
|
+
fs.mkdirSync(dataDir, { recursive: true })
|
|
73
|
+
fs.writeFileSync(
|
|
74
|
+
queryPath,
|
|
75
|
+
limited.length > 0 ? limited.map((entry) => JSON.stringify(entry)).join('\n') + '\n' : '',
|
|
76
|
+
'utf8'
|
|
77
|
+
)
|
|
78
|
+
recentQueries = limited
|
|
79
|
+
return limited
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function createId(prefix) {
|
|
83
|
+
return `${prefix}_${Date.now()}_${crypto.randomBytes(6).toString('hex')}`
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function appendJsonl(file, value) {
|
|
87
|
+
fs.appendFileSync(file, `${JSON.stringify(value)}\n`, 'utf8')
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function writeJsonl(file, values) {
|
|
91
|
+
fs.mkdirSync(path.dirname(file), { recursive: true })
|
|
92
|
+
fs.writeFileSync(
|
|
93
|
+
file,
|
|
94
|
+
values.length > 0 ? values.map((value) => JSON.stringify(value)).join('\n') + '\n' : '',
|
|
95
|
+
'utf8'
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function readJsonl(file) {
|
|
100
|
+
if (!fs.existsSync(file)) return []
|
|
101
|
+
return fs
|
|
102
|
+
.readFileSync(file, 'utf8')
|
|
103
|
+
.split(/\r?\n/)
|
|
104
|
+
.filter(Boolean)
|
|
105
|
+
.map((line) => {
|
|
106
|
+
try {
|
|
107
|
+
return JSON.parse(line)
|
|
108
|
+
} catch (_) {
|
|
109
|
+
return null
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
.filter(Boolean)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let lastProjectCwd = null
|
|
116
|
+
let lastProjectName = null
|
|
117
|
+
|
|
118
|
+
function projectName() {
|
|
119
|
+
const currentCwd = process.cwd()
|
|
120
|
+
if (lastProjectCwd === currentCwd && lastProjectName) {
|
|
121
|
+
return lastProjectName
|
|
122
|
+
}
|
|
123
|
+
lastProjectCwd = currentCwd
|
|
124
|
+
if (runtimeContext.project) {
|
|
125
|
+
lastProjectName = runtimeContext.project
|
|
126
|
+
return lastProjectName
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
const commonDir = execFileSync(
|
|
130
|
+
'git',
|
|
131
|
+
['rev-parse', '--path-format=absolute', '--git-common-dir'],
|
|
132
|
+
{
|
|
133
|
+
cwd: currentCwd,
|
|
134
|
+
encoding: 'utf8',
|
|
135
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
136
|
+
}
|
|
137
|
+
).trim()
|
|
138
|
+
if (commonDir) {
|
|
139
|
+
const repoRoot = path.dirname(commonDir)
|
|
140
|
+
const name = path.basename(repoRoot)
|
|
141
|
+
if (name) {
|
|
142
|
+
lastProjectName = name
|
|
143
|
+
return lastProjectName
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
} catch (_) {
|
|
147
|
+
// fall through to cwd basename
|
|
148
|
+
}
|
|
149
|
+
lastProjectName = path.basename(currentCwd)
|
|
150
|
+
return lastProjectName
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function normalizeTags(tags) {
|
|
154
|
+
if (Array.isArray(tags)) return tags.map((tag) => String(tag).trim()).filter(Boolean)
|
|
155
|
+
if (typeof tags === 'string') {
|
|
156
|
+
return tags
|
|
157
|
+
.split(',')
|
|
158
|
+
.map((tag) => tag.trim())
|
|
159
|
+
.filter(Boolean)
|
|
160
|
+
}
|
|
161
|
+
return []
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function tokenize(text) {
|
|
165
|
+
const normalized = String(text || '').toLowerCase()
|
|
166
|
+
const latin = normalized.match(/[a-z0-9]+/g) || []
|
|
167
|
+
const cjk = (normalized.match(/[\u4e00-\u9fff]/g) || []).map((char) => `cjk:${char}`)
|
|
168
|
+
return [...latin, ...cjk]
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function normalizeScope(value) {
|
|
172
|
+
const s = String(value || 'project').trim().toLowerCase()
|
|
173
|
+
if (s === 'global' || s === 'all') return s
|
|
174
|
+
return 'project'
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function isGlobalRecord(record) {
|
|
178
|
+
return String(record.scope || '').trim().toLowerCase() === 'global'
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function matchesProjectScope(record, project, scope) {
|
|
182
|
+
const s = normalizeScope(scope)
|
|
183
|
+
if (s === 'global') return isGlobalRecord(record)
|
|
184
|
+
if (s === 'all') {
|
|
185
|
+
if (isGlobalRecord(record)) return true
|
|
186
|
+
return !project || record.project === project
|
|
187
|
+
}
|
|
188
|
+
// project (default): project-scoped records matching the project only
|
|
189
|
+
if (isGlobalRecord(record)) return false
|
|
190
|
+
return !project || record.project === project
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function latinTokens(text) {
|
|
194
|
+
return String(text || '').toLowerCase().match(/[a-z0-9]+/g) || []
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function cjkBigrams(text) {
|
|
198
|
+
const chars = String(text || '').toLowerCase().match(/[\u4e00-\u9fff]/g) || []
|
|
199
|
+
const bigrams = new Set()
|
|
200
|
+
for (let i = 0; i + 1 < chars.length; i += 1) {
|
|
201
|
+
bigrams.add(`${chars[i]}${chars[i + 1]}`)
|
|
202
|
+
}
|
|
203
|
+
return bigrams
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function scoreRecord(record, queryText, project, evidence) {
|
|
207
|
+
if (project && record.project && record.project !== project) return 0
|
|
208
|
+
const content = `${record.content || ''} ${(record.tags || []).join(' ')} ${record.kind || ''}`
|
|
209
|
+
const haystackTokens = tokenize(content)
|
|
210
|
+
const haystackLatin = latinTokens(content)
|
|
211
|
+
const queryTokens = tokenize(queryText)
|
|
212
|
+
let score = 0
|
|
213
|
+
for (const token of queryTokens) {
|
|
214
|
+
if (haystackTokens.includes(token)) score += 2
|
|
215
|
+
}
|
|
216
|
+
const queryLatin = queryTokens.filter((token) => !token.startsWith('cjk:'))
|
|
217
|
+
for (const token of queryLatin) {
|
|
218
|
+
if (
|
|
219
|
+
token.length >= 3 &&
|
|
220
|
+
haystackLatin.some((item) => item.startsWith(token) || token.startsWith(item))
|
|
221
|
+
) {
|
|
222
|
+
score += 1
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
const queryBigrams = cjkBigrams(queryText)
|
|
226
|
+
const haystackBigrams = cjkBigrams(content)
|
|
227
|
+
if (queryBigrams.size > 0 && haystackBigrams.size > 0) {
|
|
228
|
+
let matched = 0
|
|
229
|
+
for (const bigram of queryBigrams) {
|
|
230
|
+
if (haystackBigrams.has(bigram)) matched += 1
|
|
231
|
+
}
|
|
232
|
+
score += matched * 1.5
|
|
233
|
+
}
|
|
234
|
+
// Recency only boosts ranking among actual matches; it must not enable recall.
|
|
235
|
+
if (score > 0 && record.timestamp) {
|
|
236
|
+
const ageDays = Math.max(0, (Date.now() - Date.parse(record.timestamp)) / 86400000)
|
|
237
|
+
score += Math.max(0, 0.5 - ageDays * 0.05)
|
|
238
|
+
}
|
|
239
|
+
// Evidence weighting (P2): memories successfully reused rank higher; never enables recall.
|
|
240
|
+
if (score > 0 && evidence && evidence.has(record.id)) {
|
|
241
|
+
const ev = evidence.get(record.id)
|
|
242
|
+
score += Math.min(2, ev.reuseCount * 0.6 + (ev.confirmedCount > 0 ? 1 : 0))
|
|
243
|
+
}
|
|
244
|
+
return score
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function matchesMemoryFilters(record, kind, tags) {
|
|
248
|
+
if (kind) {
|
|
249
|
+
const recordKind = String(record.kind || '').trim().toLowerCase()
|
|
250
|
+
if (recordKind !== kind) return false
|
|
251
|
+
}
|
|
252
|
+
if (tags.length > 0) {
|
|
253
|
+
const recordTags = new Set(
|
|
254
|
+
(record.tags || []).map((tag) => String(tag).trim().toLowerCase()),
|
|
255
|
+
)
|
|
256
|
+
for (const tag of tags) {
|
|
257
|
+
if (!recordTags.has(tag)) return false
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return true
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function queryMemory(args = {}) {
|
|
264
|
+
const query = String(args.query || '')
|
|
265
|
+
const project = args.project || projectName()
|
|
266
|
+
const limit = Math.min(Math.max(Number(args.limit) || 5, 1), 20)
|
|
267
|
+
const kind = args.kind ? String(args.kind).trim().toLowerCase() : null
|
|
268
|
+
const tags = Array.isArray(args.tags)
|
|
269
|
+
? args.tags.map((tag) => String(tag).trim().toLowerCase()).filter(Boolean)
|
|
270
|
+
: []
|
|
271
|
+
const scope = normalizeScope(args.scope)
|
|
272
|
+
const evidence = loadEvidenceWeights()
|
|
273
|
+
const scoreFor = (record) =>
|
|
274
|
+
scoreRecord(record, query, project, evidence) +
|
|
275
|
+
(scope === 'all' && isGlobalRecord(record) ? 0.5 : 0)
|
|
276
|
+
const results = readJsonl(memoryPath)
|
|
277
|
+
.filter((record) => record.archived !== true)
|
|
278
|
+
.filter((record) => matchesMemoryFilters(record, kind, tags))
|
|
279
|
+
.filter((record) => matchesProjectScope(record, project, scope))
|
|
280
|
+
.filter((record) => scoreFor(record) > 0)
|
|
281
|
+
.sort((a, b) => {
|
|
282
|
+
return scoreFor(b) - scoreFor(a)
|
|
283
|
+
})
|
|
284
|
+
.slice(0, limit)
|
|
285
|
+
.map((record) => {
|
|
286
|
+
const ev = evidence.get(record.id)
|
|
287
|
+
if (!ev) return record
|
|
288
|
+
return {
|
|
289
|
+
...record,
|
|
290
|
+
evidence: {
|
|
291
|
+
reuseCount: ev.reuseCount,
|
|
292
|
+
confirmedCount: ev.confirmedCount,
|
|
293
|
+
lastReusedAt: ev.lastReusedAt,
|
|
294
|
+
},
|
|
295
|
+
}
|
|
296
|
+
})
|
|
297
|
+
const layerCounts = { project: 0, global: 0 }
|
|
298
|
+
for (const record of results) {
|
|
299
|
+
if (record.scope === 'global' || !record.project) layerCounts.global += 1
|
|
300
|
+
else layerCounts.project += 1
|
|
301
|
+
}
|
|
302
|
+
if (results.length > 0) {
|
|
303
|
+
const timestamp = Date.now()
|
|
304
|
+
const entries = loadRecentQueries()
|
|
305
|
+
entries.push({
|
|
306
|
+
agent: runtimeAgentId() || 'mcp',
|
|
307
|
+
project,
|
|
308
|
+
query,
|
|
309
|
+
resultIds: results.map((record) => record.id).filter(Boolean),
|
|
310
|
+
resultSources: results.map((record) => record.source || null),
|
|
311
|
+
timestamp,
|
|
312
|
+
expiresAt: timestamp + REUSE_MATCH_WINDOW_MS,
|
|
313
|
+
})
|
|
314
|
+
persistRecentQueries(entries)
|
|
315
|
+
}
|
|
316
|
+
return {
|
|
317
|
+
query,
|
|
318
|
+
project,
|
|
319
|
+
scope,
|
|
320
|
+
layers: layerCounts,
|
|
321
|
+
kind: kind || null,
|
|
322
|
+
tags: tags,
|
|
323
|
+
count: results.length,
|
|
324
|
+
results,
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function recordMemory(args = {}) {
|
|
329
|
+
const content = String(args.content || '').trim()
|
|
330
|
+
if (!content) throw new Error('memory.record requires a non-empty content')
|
|
331
|
+
const scope = normalizeScope(args.scope)
|
|
332
|
+
const record = {
|
|
333
|
+
id: createId('mem'),
|
|
334
|
+
timestamp: new Date().toISOString(),
|
|
335
|
+
kind: args.kind || 'note',
|
|
336
|
+
content,
|
|
337
|
+
tags: normalizeTags(args.tags),
|
|
338
|
+
project: scope === 'global' ? null : args.project || projectName(),
|
|
339
|
+
scope,
|
|
340
|
+
source: args.source || runtimeAgentId() || 'mcp',
|
|
341
|
+
}
|
|
342
|
+
appendJsonl(memoryPath, record)
|
|
343
|
+
return record
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function ingestObservation(args = {}) {
|
|
347
|
+
const traceId = String(args.trace_id || '').trim()
|
|
348
|
+
const eventType = String(args.event_type || '').trim()
|
|
349
|
+
if (!traceId) throw new Error('observer.ingest requires trace_id')
|
|
350
|
+
if (!eventType) throw new Error('observer.ingest requires event_type')
|
|
351
|
+
const event = {
|
|
352
|
+
id: createId('trace'),
|
|
353
|
+
timestamp: new Date().toISOString(),
|
|
354
|
+
trace_id: traceId,
|
|
355
|
+
event_type: eventType,
|
|
356
|
+
outcome: args.outcome || null,
|
|
357
|
+
payload: args.payload || {},
|
|
358
|
+
agent: args.agent || runtimeAgentId(),
|
|
359
|
+
host: args.host || 'mcp',
|
|
360
|
+
project: args.project || projectName(),
|
|
361
|
+
}
|
|
362
|
+
appendJsonl(tracePath, event)
|
|
363
|
+
const autoClaims = autoClaimExperienceReuse(event)
|
|
364
|
+
if (autoClaims.length > 0) {
|
|
365
|
+
return { recorded: true, event, autoClaims }
|
|
366
|
+
}
|
|
367
|
+
return { recorded: true, event }
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function autoClaimExperienceReuse(event) {
|
|
371
|
+
if (String(event.event_type || '').toLowerCase() !== 'task_outcome') return []
|
|
372
|
+
const targetAgent = String(event.agent || '').trim()
|
|
373
|
+
if (!targetAgent) return []
|
|
374
|
+
const now = Date.now()
|
|
375
|
+
const claims = []
|
|
376
|
+
const remaining = []
|
|
377
|
+
const entries = loadRecentQueries()
|
|
378
|
+
for (const entry of entries) {
|
|
379
|
+
const expired = now - entry.timestamp > REUSE_MATCH_WINDOW_MS
|
|
380
|
+
const matches =
|
|
381
|
+
entry.agent === targetAgent && (!event.project || entry.project === event.project)
|
|
382
|
+
if (expired || matches) {
|
|
383
|
+
if (matches && !expired) {
|
|
384
|
+
claims.push(buildAutoClaim(entry, event, targetAgent))
|
|
385
|
+
}
|
|
386
|
+
continue
|
|
387
|
+
}
|
|
388
|
+
remaining.push(entry)
|
|
389
|
+
}
|
|
390
|
+
persistRecentQueries(remaining)
|
|
391
|
+
for (const evidence of claims) {
|
|
392
|
+
appendJsonl(experienceReusePath, evidence)
|
|
393
|
+
}
|
|
394
|
+
return claims
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function buildAutoClaim(entry, event, targetAgent) {
|
|
398
|
+
const crossSourceIndex = entry.resultSources.findIndex(
|
|
399
|
+
(source) => source && String(source).toLowerCase() !== targetAgent.toLowerCase()
|
|
400
|
+
)
|
|
401
|
+
const sourceAgent =
|
|
402
|
+
crossSourceIndex >= 0
|
|
403
|
+
? String(entry.resultSources[crossSourceIndex])
|
|
404
|
+
: targetAgent
|
|
405
|
+
const experienceId =
|
|
406
|
+
entry.resultIds[crossSourceIndex >= 0 ? crossSourceIndex : 0] ||
|
|
407
|
+
entry.resultIds[0] ||
|
|
408
|
+
'unknown'
|
|
409
|
+
const outcomeImproved = String(event.outcome || '').toLowerCase() === 'success'
|
|
410
|
+
return {
|
|
411
|
+
id: createId('xfer'),
|
|
412
|
+
timestamp: new Date().toISOString(),
|
|
413
|
+
sourceAgent,
|
|
414
|
+
targetAgent,
|
|
415
|
+
experienceId,
|
|
416
|
+
reuse: true,
|
|
417
|
+
behaviorChanged: false,
|
|
418
|
+
outcomeImproved,
|
|
419
|
+
project: entry.project,
|
|
420
|
+
source: 'auto_claim',
|
|
421
|
+
traceId: event.trace_id || null,
|
|
422
|
+
notes: `Auto-claimed: memory.query "${entry.query}" matched task_outcome ${event.outcome || 'unknown'} in trace ${event.trace_id || 'unknown'}.`,
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const SUBSCRIPTION_DEFAULT_TTL_DAYS = 30
|
|
427
|
+
const MAX_SUBSCRIPTIONS_PER_AGENT = 20
|
|
428
|
+
const MAX_DIGEST_EVENTS = 100
|
|
429
|
+
|
|
430
|
+
function readDigestState() {
|
|
431
|
+
if (!fs.existsSync(digestStatePath)) return {}
|
|
432
|
+
try {
|
|
433
|
+
const parsed = JSON.parse(fs.readFileSync(digestStatePath, 'utf8'))
|
|
434
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {}
|
|
435
|
+
} catch (_) {
|
|
436
|
+
return {}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function writeDigestState(state) {
|
|
441
|
+
fs.mkdirSync(dataDir, { recursive: true })
|
|
442
|
+
fs.writeFileSync(digestStatePath, JSON.stringify(state), 'utf8')
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function subscribeKey(agent, project, eventTypes, topic) {
|
|
446
|
+
return `${agent}|${project}|${eventTypes.join(',')}|${topic || ''}`
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function loadActiveSubscriptions(agent) {
|
|
450
|
+
const now = Date.now()
|
|
451
|
+
return readJsonl(subscriptionPath)
|
|
452
|
+
.filter((subscription) => subscription && subscription.agent === agent)
|
|
453
|
+
.filter((subscription) => {
|
|
454
|
+
if (subscription.expiresAt) {
|
|
455
|
+
const expires = new Date(subscription.expiresAt).getTime()
|
|
456
|
+
if (Number.isFinite(expires) && expires <= now) return false
|
|
457
|
+
}
|
|
458
|
+
return true
|
|
459
|
+
})
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function subscribeObserver(args = {}) {
|
|
463
|
+
const agent = runtimeAgentId() || 'mcp'
|
|
464
|
+
const project = args.project || projectName()
|
|
465
|
+
const eventTypes = Array.isArray(args.eventTypes)
|
|
466
|
+
? args.eventTypes.map((value) => String(value).trim().toLowerCase()).filter(Boolean)
|
|
467
|
+
: []
|
|
468
|
+
const topic = args.topic ? String(args.topic).trim().slice(0, 120) : ''
|
|
469
|
+
const ttlDays = Number(args.ttlDays) || SUBSCRIPTION_DEFAULT_TTL_DAYS
|
|
470
|
+
const ttlMs = Math.max(1, ttlDays) * 24 * 60 * 60 * 1000
|
|
471
|
+
|
|
472
|
+
const subscriptions = readJsonl(subscriptionPath)
|
|
473
|
+
const key = subscribeKey(agent, project, eventTypes, topic)
|
|
474
|
+
let existing = subscriptions.find((subscription) => {
|
|
475
|
+
return (
|
|
476
|
+
subscription &&
|
|
477
|
+
subscribeKey(
|
|
478
|
+
subscription.agent,
|
|
479
|
+
subscription.project,
|
|
480
|
+
subscription.eventTypes || [],
|
|
481
|
+
subscription.topic || '',
|
|
482
|
+
) === key
|
|
483
|
+
)
|
|
484
|
+
})
|
|
485
|
+
const now = new Date()
|
|
486
|
+
const expiresAt = new Date(now.getTime() + ttlMs).toISOString()
|
|
487
|
+
if (existing) {
|
|
488
|
+
existing.updatedAt = now.toISOString()
|
|
489
|
+
existing.expiresAt = expiresAt
|
|
490
|
+
} else {
|
|
491
|
+
existing = {
|
|
492
|
+
id: createId('sub'),
|
|
493
|
+
agent,
|
|
494
|
+
project,
|
|
495
|
+
eventTypes,
|
|
496
|
+
topic: topic || null,
|
|
497
|
+
createdAt: now.toISOString(),
|
|
498
|
+
updatedAt: now.toISOString(),
|
|
499
|
+
expiresAt,
|
|
500
|
+
}
|
|
501
|
+
subscriptions.push(existing)
|
|
502
|
+
}
|
|
503
|
+
const agentSubscriptions = subscriptions.filter((subscription) => subscription.agent === agent)
|
|
504
|
+
if (agentSubscriptions.length > MAX_SUBSCRIPTIONS_PER_AGENT) {
|
|
505
|
+
throw new Error(`Too many subscriptions for agent ${agent} (max ${MAX_SUBSCRIPTIONS_PER_AGENT})`)
|
|
506
|
+
}
|
|
507
|
+
writeJsonl(subscriptionPath, subscriptions)
|
|
508
|
+
return { subscribed: true, subscription: existing, project }
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function observerDigest(args = {}) {
|
|
512
|
+
const agent = runtimeAgentId() || 'mcp'
|
|
513
|
+
const project = args.project || projectName()
|
|
514
|
+
const limit = Math.min(Math.max(Number(args.limit) || 20, 1), MAX_DIGEST_EVENTS)
|
|
515
|
+
const filterEventTypes = Array.isArray(args.eventTypes)
|
|
516
|
+
? args.eventTypes.map((value) => String(value).trim().toLowerCase()).filter(Boolean)
|
|
517
|
+
: null
|
|
518
|
+
|
|
519
|
+
let subscriptions = loadActiveSubscriptions(agent)
|
|
520
|
+
if (args.project) {
|
|
521
|
+
subscriptions = subscriptions.filter((subscription) => subscription.project === project)
|
|
522
|
+
}
|
|
523
|
+
if (filterEventTypes) {
|
|
524
|
+
subscriptions = subscriptions.filter((subscription) => {
|
|
525
|
+
if ((subscription.eventTypes || []).length === 0) return true
|
|
526
|
+
return filterEventTypes.some((eventType) =>
|
|
527
|
+
(subscription.eventTypes || []).includes(eventType),
|
|
528
|
+
)
|
|
529
|
+
})
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const state = readDigestState()
|
|
533
|
+
const traces = readJsonl(tracePath)
|
|
534
|
+
const digestTime = new Date().toISOString()
|
|
535
|
+
const events = []
|
|
536
|
+
const seenIds = new Set()
|
|
537
|
+
const matchedSubscriptionIds = new Set()
|
|
538
|
+
|
|
539
|
+
for (const subscription of subscriptions) {
|
|
540
|
+
const cursor = state[subscription.id] || null
|
|
541
|
+
const cursorTime = cursor ? Date.parse(cursor) : null
|
|
542
|
+
const subscriptionEventTypes = subscription.eventTypes || []
|
|
543
|
+
let lastEventTime = null
|
|
544
|
+
for (const event of traces) {
|
|
545
|
+
if (subscription.project && event.project && event.project !== subscription.project) {
|
|
546
|
+
continue
|
|
547
|
+
}
|
|
548
|
+
if (
|
|
549
|
+
subscriptionEventTypes.length > 0 &&
|
|
550
|
+
!subscriptionEventTypes.includes(String(event.event_type || '').toLowerCase())
|
|
551
|
+
) {
|
|
552
|
+
continue
|
|
553
|
+
}
|
|
554
|
+
if (subscription.topic) {
|
|
555
|
+
const haystack = `${event.event_type || ''} ${JSON.stringify(event.payload || {})}`
|
|
556
|
+
.toLowerCase()
|
|
557
|
+
if (!haystack.includes(subscription.topic.toLowerCase())) continue
|
|
558
|
+
}
|
|
559
|
+
const eventTime = event.timestamp ? Date.parse(event.timestamp) : null
|
|
560
|
+
if (cursorTime !== null && eventTime !== null && eventTime <= cursorTime) continue
|
|
561
|
+
if (eventTime !== null && (lastEventTime === null || eventTime > lastEventTime)) {
|
|
562
|
+
lastEventTime = eventTime
|
|
563
|
+
}
|
|
564
|
+
if (seenIds.has(event.id)) continue
|
|
565
|
+
seenIds.add(event.id)
|
|
566
|
+
events.push({
|
|
567
|
+
id: event.id,
|
|
568
|
+
trace_id: event.trace_id || null,
|
|
569
|
+
event_type: event.event_type,
|
|
570
|
+
outcome: event.outcome || null,
|
|
571
|
+
timestamp: event.timestamp,
|
|
572
|
+
agent: event.agent || null,
|
|
573
|
+
payload: event.payload || null,
|
|
574
|
+
})
|
|
575
|
+
matchedSubscriptionIds.add(subscription.id)
|
|
576
|
+
if (events.length >= limit) break
|
|
577
|
+
}
|
|
578
|
+
if (lastEventTime !== null) {
|
|
579
|
+
state[subscription.id] = new Date(lastEventTime).toISOString()
|
|
580
|
+
} else if (cursor === null) {
|
|
581
|
+
state[subscription.id] = digestTime
|
|
582
|
+
}
|
|
583
|
+
if (events.length >= limit) break
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
writeDigestState(state)
|
|
587
|
+
return {
|
|
588
|
+
project,
|
|
589
|
+
agent,
|
|
590
|
+
subscriptionCount: subscriptions.length,
|
|
591
|
+
matchedSubscriptions: Array.from(matchedSubscriptionIds),
|
|
592
|
+
count: events.length,
|
|
593
|
+
events,
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function contentTokens(value) {
|
|
598
|
+
const text = String(value || '').toLowerCase()
|
|
599
|
+
const latin = text.match(/[a-z0-9]+/g) || []
|
|
600
|
+
const chars = text.match(/[\u4e00-\u9fff]/g) || []
|
|
601
|
+
const bigrams = []
|
|
602
|
+
for (let i = 0; i < chars.length - 1; i++) {
|
|
603
|
+
bigrams.push(`${chars[i]}${chars[i + 1]}`)
|
|
604
|
+
}
|
|
605
|
+
return [...latin, ...bigrams]
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function jaccardSimilarity(a, b) {
|
|
609
|
+
const setA = new Set(contentTokens(a))
|
|
610
|
+
const setB = new Set(contentTokens(b))
|
|
611
|
+
if (setA.size === 0 || setB.size === 0) return 0
|
|
612
|
+
let intersection = 0
|
|
613
|
+
for (const token of setA) {
|
|
614
|
+
if (setB.has(token)) intersection += 1
|
|
615
|
+
}
|
|
616
|
+
const union = setA.size + setB.size - intersection
|
|
617
|
+
return union === 0 ? 0 : intersection / union
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function findDuplicateGroups(records, limit) {
|
|
621
|
+
const parent = new Map()
|
|
622
|
+
for (const record of records) parent.set(record.id, record.id)
|
|
623
|
+
const find = (id) => {
|
|
624
|
+
while (parent.get(id) !== id) {
|
|
625
|
+
parent.set(id, parent.get(parent.get(id)))
|
|
626
|
+
id = parent.get(id)
|
|
627
|
+
}
|
|
628
|
+
return id
|
|
629
|
+
}
|
|
630
|
+
const union = (a, b) => {
|
|
631
|
+
const ra = find(a)
|
|
632
|
+
const rb = find(b)
|
|
633
|
+
if (ra !== rb) parent.set(ra, rb)
|
|
634
|
+
}
|
|
635
|
+
for (let i = 0; i < records.length; i++) {
|
|
636
|
+
for (let j = i + 1; j < records.length; j++) {
|
|
637
|
+
if (
|
|
638
|
+
jaccardSimilarity(records[i].content, records[j].content) >= MEMORY_DUPLICATE_SIMILARITY
|
|
639
|
+
) {
|
|
640
|
+
union(records[i].id, records[j].id)
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
const byRoot = new Map()
|
|
645
|
+
for (const record of records) {
|
|
646
|
+
const root = find(record.id)
|
|
647
|
+
if (!byRoot.has(root)) byRoot.set(root, [])
|
|
648
|
+
byRoot.get(root).push(record)
|
|
649
|
+
}
|
|
650
|
+
return Array.from(byRoot.values())
|
|
651
|
+
.filter((group) => group.length >= 2)
|
|
652
|
+
.sort((a, b) => b.length - a.length)
|
|
653
|
+
.slice(0, limit)
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
function analyzeMemory(args = {}) {
|
|
657
|
+
const project = args.project || projectName()
|
|
658
|
+
const limit = Math.min(Math.max(Number(args.limit) || 5, 1), 20)
|
|
659
|
+
const records = readJsonl(memoryPath).filter(
|
|
660
|
+
(record) => !project || record.project === project || !record.project,
|
|
661
|
+
)
|
|
662
|
+
const archived = records.filter((record) => record.archived === true).length
|
|
663
|
+
const activeRecords = records.filter((record) => record.archived !== true)
|
|
664
|
+
const total = activeRecords.length
|
|
665
|
+
const layerCounts = { project: 0, global: 0 }
|
|
666
|
+
for (const record of activeRecords) {
|
|
667
|
+
if (record.scope === 'global' || !record.project) layerCounts.global += 1
|
|
668
|
+
else layerCounts.project += 1
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
const byKind = {}
|
|
672
|
+
const lowQuality = []
|
|
673
|
+
for (const record of activeRecords) {
|
|
674
|
+
const kind = String(record.kind || '').trim() || 'unknown'
|
|
675
|
+
byKind[kind] = (byKind[kind] || 0) + 1
|
|
676
|
+
const content = String(record.content || '').trim()
|
|
677
|
+
const issues = []
|
|
678
|
+
if (!content) issues.push('missing content')
|
|
679
|
+
else if (content.length < MEMORY_SHORT_CONTENT_MIN) issues.push('content too short')
|
|
680
|
+
if (!record.kind || !String(record.kind).trim()) issues.push('missing kind')
|
|
681
|
+
if (issues.length > 0) {
|
|
682
|
+
lowQuality.push({
|
|
683
|
+
id: record.id,
|
|
684
|
+
timestamp: record.timestamp,
|
|
685
|
+
kind: record.kind || null,
|
|
686
|
+
content: content.slice(0, 200) || null,
|
|
687
|
+
issues,
|
|
688
|
+
})
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
let duplicateGroups = []
|
|
693
|
+
let duplicatesSkipped = false
|
|
694
|
+
if (activeRecords.length > MAX_PAIRWISE_MEMORY) {
|
|
695
|
+
duplicatesSkipped = true
|
|
696
|
+
} else {
|
|
697
|
+
duplicateGroups = findDuplicateGroups(activeRecords, limit).map((group) => ({
|
|
698
|
+
size: group.length,
|
|
699
|
+
records: group.map((record) => ({
|
|
700
|
+
id: record.id,
|
|
701
|
+
timestamp: record.timestamp,
|
|
702
|
+
kind: record.kind || null,
|
|
703
|
+
content: String(record.content || '').slice(0, 200),
|
|
704
|
+
})),
|
|
705
|
+
}))
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
const issues = {
|
|
709
|
+
lowQuality: lowQuality.length,
|
|
710
|
+
duplicateGroups: duplicateGroups.length,
|
|
711
|
+
duplicatesSkipped,
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
const suggestions = []
|
|
715
|
+
if (duplicateGroups.length > 0) {
|
|
716
|
+
suggestions.push(
|
|
717
|
+
`Found ${duplicateGroups.length} duplicate group(s); consider archiving or merging duplicates to keep recall precise.`,
|
|
718
|
+
)
|
|
719
|
+
}
|
|
720
|
+
if (lowQuality.length > 0) {
|
|
721
|
+
suggestions.push(
|
|
722
|
+
`${lowQuality.length} low-quality record(s) (missing kind/content or too short); consider fixing or removing them.`,
|
|
723
|
+
)
|
|
724
|
+
}
|
|
725
|
+
if (total === 0) {
|
|
726
|
+
suggestions.push('No memory records for this project yet.')
|
|
727
|
+
} else if (duplicateGroups.length === 0 && lowQuality.length === 0) {
|
|
728
|
+
suggestions.push('Memory looks healthy; no dedup or quality fixes needed.')
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
return {
|
|
732
|
+
project: project || null,
|
|
733
|
+
total,
|
|
734
|
+
archived,
|
|
735
|
+
layers: layerCounts,
|
|
736
|
+
byKind,
|
|
737
|
+
issues,
|
|
738
|
+
duplicates: duplicateGroups,
|
|
739
|
+
lowQuality: lowQuality.slice(0, limit),
|
|
740
|
+
suggestions,
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
function migrateMemory(args = {}) {
|
|
745
|
+
const idsInput = Array.isArray(args.ids) ? args.ids : []
|
|
746
|
+
const ids = idsInput.map((value) => String(value).trim()).filter(Boolean)
|
|
747
|
+
if (ids.length === 0) throw new Error('memory.migrate requires ids (array of memory record ids)')
|
|
748
|
+
const targetScope = normalizeScope(args.scope)
|
|
749
|
+
if (targetScope === 'all') throw new Error('memory.migrate scope must be project or global')
|
|
750
|
+
const project = args.project || projectName()
|
|
751
|
+
const reason = args.reason ? String(args.reason).trim().slice(0, 200) : null
|
|
752
|
+
|
|
753
|
+
const records = readJsonl(memoryPath)
|
|
754
|
+
const idSet = new Set(ids)
|
|
755
|
+
const updated = []
|
|
756
|
+
const unchanged = []
|
|
757
|
+
const notFound = [...ids]
|
|
758
|
+
for (const record of records) {
|
|
759
|
+
if (!idSet.has(record.id)) continue
|
|
760
|
+
const fromScope = record.scope || 'project'
|
|
761
|
+
const fromProject = record.project || null
|
|
762
|
+
if (fromScope === targetScope) {
|
|
763
|
+
unchanged.push(record.id)
|
|
764
|
+
} else {
|
|
765
|
+
record.scope = targetScope
|
|
766
|
+
record.project = targetScope === 'global' ? null : project
|
|
767
|
+
record.migratedAt = new Date().toISOString()
|
|
768
|
+
record.migratedFrom = fromScope === 'global' ? 'global' : fromProject || 'project'
|
|
769
|
+
if (reason) record.migrateReason = reason
|
|
770
|
+
updated.push(record.id)
|
|
771
|
+
}
|
|
772
|
+
const index = notFound.indexOf(record.id)
|
|
773
|
+
if (index >= 0) notFound.splice(index, 1)
|
|
774
|
+
}
|
|
775
|
+
writeJsonl(memoryPath, records)
|
|
776
|
+
|
|
777
|
+
return {
|
|
778
|
+
project: project || null,
|
|
779
|
+
scope: targetScope,
|
|
780
|
+
updated,
|
|
781
|
+
updatedCount: updated.length,
|
|
782
|
+
unchanged,
|
|
783
|
+
notFound,
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
function archiveMemory(args = {}) {
|
|
787
|
+
const idsInput = Array.isArray(args.ids) ? args.ids : []
|
|
788
|
+
const ids = idsInput.map((value) => String(value).trim()).filter(Boolean)
|
|
789
|
+
if (ids.length === 0) throw new Error('memory.archive requires ids (array of memory record ids)')
|
|
790
|
+
const project = args.project || projectName()
|
|
791
|
+
const restore = args.restore === true || args.restore === 'true'
|
|
792
|
+
const reason = args.reason ? String(args.reason).trim().slice(0, 200) : null
|
|
793
|
+
|
|
794
|
+
const records = readJsonl(memoryPath)
|
|
795
|
+
const idSet = new Set(ids)
|
|
796
|
+
const archived = []
|
|
797
|
+
const notFound = [...ids]
|
|
798
|
+
for (const record of records) {
|
|
799
|
+
if (!idSet.has(record.id)) continue
|
|
800
|
+
if (project && record.project && record.project !== project) continue
|
|
801
|
+
if (restore) {
|
|
802
|
+
if (record.archived === true) {
|
|
803
|
+
delete record.archived
|
|
804
|
+
delete record.archivedAt
|
|
805
|
+
delete record.archiveReason
|
|
806
|
+
archived.push(record.id)
|
|
807
|
+
}
|
|
808
|
+
} else if (record.archived !== true) {
|
|
809
|
+
record.archived = true
|
|
810
|
+
record.archivedAt = new Date().toISOString()
|
|
811
|
+
if (reason) record.archiveReason = reason
|
|
812
|
+
archived.push(record.id)
|
|
813
|
+
}
|
|
814
|
+
const index = notFound.indexOf(record.id)
|
|
815
|
+
if (index >= 0) notFound.splice(index, 1)
|
|
816
|
+
}
|
|
817
|
+
writeJsonl(memoryPath, records)
|
|
818
|
+
|
|
819
|
+
return {
|
|
820
|
+
project: project || null,
|
|
821
|
+
restored: restore,
|
|
822
|
+
archived,
|
|
823
|
+
archivedCount: archived.length,
|
|
824
|
+
notFound,
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
const FAILURE_OUTCOMES = new Set(['failure', 'error', 'aborted', 'retry'])
|
|
829
|
+
const MEMORY_DUPLICATE_SIMILARITY = 0.75
|
|
830
|
+
const MEMORY_SHORT_CONTENT_MIN = 20
|
|
831
|
+
const MAX_PAIRWISE_MEMORY = 1500
|
|
832
|
+
|
|
833
|
+
function isFailureOutcome(value) {
|
|
834
|
+
return FAILURE_OUTCOMES.has(String(value || '').toLowerCase())
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
function summarizeTrace(event) {
|
|
838
|
+
const payload = event.payload
|
|
839
|
+
if (payload && typeof payload === 'object' && !Array.isArray(payload)) {
|
|
840
|
+
if (typeof payload.summary === 'string' && payload.summary.trim()) {
|
|
841
|
+
return payload.summary.trim().slice(0, 160)
|
|
842
|
+
}
|
|
843
|
+
if (typeof payload.tool === 'string' && payload.tool.trim()) {
|
|
844
|
+
return `tool: ${payload.tool.trim().slice(0, 120)}`
|
|
845
|
+
}
|
|
846
|
+
if (typeof payload.error === 'string' && payload.error.trim()) {
|
|
847
|
+
return payload.error.trim().slice(0, 160)
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
return String(event.event_type || 'trace').slice(0, 120)
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
function buildPolicyGuidance({ total, failures, risk, failureExamples, relatedMemories }) {
|
|
854
|
+
const guidance = {
|
|
855
|
+
level: 'none',
|
|
856
|
+
rationale: null,
|
|
857
|
+
saferAlternatives: [],
|
|
858
|
+
verificationSteps: [],
|
|
859
|
+
avoid: [],
|
|
860
|
+
hardGate: false,
|
|
861
|
+
}
|
|
862
|
+
if (total === 0) {
|
|
863
|
+
guidance.rationale =
|
|
864
|
+
'No historical evidence for this action yet. Treat as normal risk and record the outcome.'
|
|
865
|
+
return guidance
|
|
866
|
+
}
|
|
867
|
+
const verifiedMemories = relatedMemories.filter((record) => record.evidence)
|
|
868
|
+
const hasVerified = verifiedMemories.length > 0
|
|
869
|
+
if (risk >= 0.4 && failures >= 2) {
|
|
870
|
+
guidance.level = hasVerified ? 'actionable' : 'advisory'
|
|
871
|
+
guidance.rationale =
|
|
872
|
+
'Repeated historical failures. Prefer an alternative approach and add rollback/verification.'
|
|
873
|
+
guidance.avoid = failureExamples
|
|
874
|
+
.map((example) => example.summary)
|
|
875
|
+
.filter(Boolean)
|
|
876
|
+
.slice(0, 3)
|
|
877
|
+
} else if (risk >= 0.2 || hasVerified) {
|
|
878
|
+
guidance.level = hasVerified ? 'actionable' : 'advisory'
|
|
879
|
+
guidance.rationale = hasVerified
|
|
880
|
+
? 'Verified experience exists for this action; apply it before proceeding.'
|
|
881
|
+
: 'Moderate historical risk. Add verification before proceeding.'
|
|
882
|
+
} else {
|
|
883
|
+
guidance.level = 'advisory'
|
|
884
|
+
guidance.rationale = 'Low historical risk. Proceed with normal checks and record the outcome.'
|
|
885
|
+
}
|
|
886
|
+
if (hasVerified) {
|
|
887
|
+
guidance.saferAlternatives = verifiedMemories.slice(0, 3).map((record) => ({
|
|
888
|
+
memoryId: record.id,
|
|
889
|
+
reuseCount: record.evidence.reuseCount,
|
|
890
|
+
content: String(record.content || '').slice(0, 300),
|
|
891
|
+
}))
|
|
892
|
+
}
|
|
893
|
+
guidance.verificationSteps = [
|
|
894
|
+
'Add an explicit verification step after execution (dry-run, rollback plan, or assertion).',
|
|
895
|
+
'Record the outcome with mio.observer.ingest so the evidence base improves.',
|
|
896
|
+
]
|
|
897
|
+
return guidance
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
function policyCheck(args = {}) {
|
|
901
|
+
const action = String(args.action || '').trim()
|
|
902
|
+
if (!action) throw new Error('policy.check requires action')
|
|
903
|
+
const project = args.project || projectName()
|
|
904
|
+
const actionTokens = tokenize(action)
|
|
905
|
+
const traces = readJsonl(tracePath).filter((event) => {
|
|
906
|
+
if (project && event.project && event.project !== project) return false
|
|
907
|
+
const haystack = tokenize(
|
|
908
|
+
`${event.event_type || ''} ${JSON.stringify(event.payload || {})}`
|
|
909
|
+
)
|
|
910
|
+
return actionTokens.some((token) => haystack.includes(token))
|
|
911
|
+
})
|
|
912
|
+
const total = traces.length
|
|
913
|
+
const failures = traces.filter((event) => isFailureOutcome(event.outcome)).length
|
|
914
|
+
const risk = total > 0 ? failures / total : null
|
|
915
|
+
const riskLevel = total === 0 ? 'unknown' : risk >= 0.4 ? 'high' : risk >= 0.2 ? 'moderate' : 'low'
|
|
916
|
+
const outcomeCounts = traces.reduce((counts, event) => {
|
|
917
|
+
const outcome = String(event.outcome || 'unknown').toLowerCase() || 'unknown'
|
|
918
|
+
counts[outcome] = (counts[outcome] || 0) + 1
|
|
919
|
+
return counts
|
|
920
|
+
}, {})
|
|
921
|
+
const failureExamples = traces
|
|
922
|
+
.filter((event) => isFailureOutcome(event.outcome))
|
|
923
|
+
.sort((a, b) => new Date(b.timestamp || 0).getTime() - new Date(a.timestamp || 0).getTime())
|
|
924
|
+
.slice(0, 3)
|
|
925
|
+
.map((event) => ({
|
|
926
|
+
trace_id: event.trace_id || event.id || null,
|
|
927
|
+
event_type: event.event_type || null,
|
|
928
|
+
outcome: event.outcome || null,
|
|
929
|
+
timestamp: event.timestamp || null,
|
|
930
|
+
agent: event.agent || null,
|
|
931
|
+
summary: summarizeTrace(event),
|
|
932
|
+
}))
|
|
933
|
+
let suggestion
|
|
934
|
+
if (total === 0) {
|
|
935
|
+
suggestion = 'No history for this action. Treat as normal risk and record the outcome.'
|
|
936
|
+
} else if (risk >= 0.4) {
|
|
937
|
+
suggestion = 'Historically risky. Prefer an alternative approach or add rollback/verification.'
|
|
938
|
+
} else if (risk >= 0.2) {
|
|
939
|
+
suggestion = 'Moderate risk. Add verification before proceeding.'
|
|
940
|
+
} else {
|
|
941
|
+
suggestion = 'Low historical risk. Proceed with normal checks.'
|
|
942
|
+
}
|
|
943
|
+
const evidence = loadEvidenceWeights()
|
|
944
|
+
const relatedMemories = readJsonl(memoryPath)
|
|
945
|
+
.filter((record) => scoreRecord(record, action, project, evidence) > 0)
|
|
946
|
+
.sort((a, b) => scoreRecord(b, action, project, evidence) - scoreRecord(a, action, project, evidence))
|
|
947
|
+
.slice(0, 3)
|
|
948
|
+
.map((record) => {
|
|
949
|
+
const ev = evidence.get(record.id)
|
|
950
|
+
return {
|
|
951
|
+
id: record.id,
|
|
952
|
+
timestamp: record.timestamp,
|
|
953
|
+
kind: record.kind,
|
|
954
|
+
content: record.content,
|
|
955
|
+
tags: record.tags,
|
|
956
|
+
evidence: ev
|
|
957
|
+
? { reuseCount: ev.reuseCount, confirmedCount: ev.confirmedCount }
|
|
958
|
+
: null,
|
|
959
|
+
}
|
|
960
|
+
})
|
|
961
|
+
return {
|
|
962
|
+
action,
|
|
963
|
+
project,
|
|
964
|
+
total,
|
|
965
|
+
failures,
|
|
966
|
+
risk: risk === null ? null : Number(risk.toFixed(3)),
|
|
967
|
+
riskLevel,
|
|
968
|
+
outcomeCounts,
|
|
969
|
+
failureExamples,
|
|
970
|
+
suggestion,
|
|
971
|
+
guidance: buildPolicyGuidance({
|
|
972
|
+
total,
|
|
973
|
+
failures,
|
|
974
|
+
risk,
|
|
975
|
+
failureExamples,
|
|
976
|
+
relatedMemories,
|
|
977
|
+
}),
|
|
978
|
+
related_memories: relatedMemories,
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
function recordExperienceReuse(args = {}) {
|
|
983
|
+
const sourceAgent = String(args.sourceAgent || '').trim()
|
|
984
|
+
const targetAgent = String(args.targetAgent || '').trim()
|
|
985
|
+
const experienceId = String(args.experienceId || '').trim()
|
|
986
|
+
if (!sourceAgent) throw new Error('experience.reuse requires sourceAgent')
|
|
987
|
+
if (!targetAgent) throw new Error('experience.reuse requires targetAgent')
|
|
988
|
+
if (!experienceId) throw new Error('experience.reuse requires experienceId')
|
|
989
|
+
|
|
990
|
+
const project = args.project || projectName()
|
|
991
|
+
const evidence = {
|
|
992
|
+
id: createId('xfer'),
|
|
993
|
+
timestamp: new Date().toISOString(),
|
|
994
|
+
sourceAgent,
|
|
995
|
+
targetAgent,
|
|
996
|
+
experienceId,
|
|
997
|
+
reuse: args.reuse === true || args.reuse === 'true',
|
|
998
|
+
behaviorChanged: args.behaviorChanged === true || args.behaviorChanged === 'true',
|
|
999
|
+
outcomeImproved: args.outcomeImproved === true || args.outcomeImproved === 'true',
|
|
1000
|
+
project,
|
|
1001
|
+
source: args.source || 'agent_report',
|
|
1002
|
+
notes: args.notes || null,
|
|
1003
|
+
}
|
|
1004
|
+
appendJsonl(experienceReusePath, evidence)
|
|
1005
|
+
|
|
1006
|
+
const report = loadPhase0(dataDir, project)
|
|
1007
|
+
return {
|
|
1008
|
+
recorded: true,
|
|
1009
|
+
evidence,
|
|
1010
|
+
phase0: summarizePhase0(report),
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
function confirmExperienceReuse(args = {}) {
|
|
1015
|
+
const id = String(args.id || '').trim()
|
|
1016
|
+
if (!id) throw new Error('experience.confirm requires id of an auto-claimed reuse record')
|
|
1017
|
+
const records = readJsonl(experienceReusePath)
|
|
1018
|
+
const index = records.findIndex((record) => record && record.id === id)
|
|
1019
|
+
if (index < 0) {
|
|
1020
|
+
throw new Error(`No experience reuse record found with id ${id}`)
|
|
1021
|
+
}
|
|
1022
|
+
const record = records[index]
|
|
1023
|
+
if (record.source !== 'auto_claim') {
|
|
1024
|
+
throw new Error('experience.confirm only applies to source=auto_claim reuse records')
|
|
1025
|
+
}
|
|
1026
|
+
const outcomeImproved =
|
|
1027
|
+
args.outcomeImproved === undefined || args.outcomeImproved === null
|
|
1028
|
+
? record.outcomeImproved === true
|
|
1029
|
+
: args.outcomeImproved === true || args.outcomeImproved === 'true'
|
|
1030
|
+
const updated = {
|
|
1031
|
+
...record,
|
|
1032
|
+
behaviorChanged: true,
|
|
1033
|
+
outcomeImproved,
|
|
1034
|
+
confirmed: true,
|
|
1035
|
+
confirmedAt: new Date().toISOString(),
|
|
1036
|
+
confirmedBy: String(args.confirmedBy || 'agent').trim() || 'agent',
|
|
1037
|
+
notes: args.notes ? String(args.notes).trim() : record.notes || null,
|
|
1038
|
+
}
|
|
1039
|
+
records[index] = updated
|
|
1040
|
+
writeJsonl(experienceReusePath, records)
|
|
1041
|
+
|
|
1042
|
+
const report = loadPhase0(dataDir, updated.project)
|
|
1043
|
+
return {
|
|
1044
|
+
confirmed: true,
|
|
1045
|
+
evidence: updated,
|
|
1046
|
+
phase0: summarizePhase0(report),
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
const REUSE_STATUS_FILTERS = Object.freeze({
|
|
1051
|
+
pending: (record) => record.source === 'auto_claim' && record.confirmed !== true,
|
|
1052
|
+
confirmed: (record) => record.confirmed === true,
|
|
1053
|
+
verified: (record) =>
|
|
1054
|
+
(record.reuse === true || record.reuse === 'true') &&
|
|
1055
|
+
(record.behaviorChanged === true || record.behaviorChanged === 'true') &&
|
|
1056
|
+
(record.outcomeImproved === true || record.outcomeImproved === 'true'),
|
|
1057
|
+
auto_claim: (record) => record.source === 'auto_claim',
|
|
1058
|
+
agent_report: (record) => record.source !== 'auto_claim',
|
|
1059
|
+
})
|
|
1060
|
+
|
|
1061
|
+
function loadEvidenceWeights() {
|
|
1062
|
+
const weights = new Map()
|
|
1063
|
+
for (const record of readJsonl(experienceReusePath)) {
|
|
1064
|
+
if (!REUSE_STATUS_FILTERS.verified(record)) continue
|
|
1065
|
+
const id = record.experienceId
|
|
1066
|
+
if (!id) continue
|
|
1067
|
+
let entry = weights.get(id)
|
|
1068
|
+
if (!entry) {
|
|
1069
|
+
entry = { reuseCount: 0, confirmedCount: 0, lastReusedAt: null }
|
|
1070
|
+
weights.set(id, entry)
|
|
1071
|
+
}
|
|
1072
|
+
entry.reuseCount += 1
|
|
1073
|
+
if (record.confirmed === true) entry.confirmedCount += 1
|
|
1074
|
+
if (!entry.lastReusedAt || new Date(record.timestamp || 0) > new Date(entry.lastReusedAt)) {
|
|
1075
|
+
entry.lastReusedAt = record.timestamp || null
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
return weights
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
function listExperienceReuse(args = {}) {
|
|
1082
|
+
const project = args.project || projectName()
|
|
1083
|
+
const status = String(args.status || 'all').trim().toLowerCase()
|
|
1084
|
+
const targetAgent = String(args.targetAgent || '').trim().toLowerCase()
|
|
1085
|
+
const limit = Math.min(Math.max(Number(args.limit) || 20, 1), 100)
|
|
1086
|
+
|
|
1087
|
+
let records = readJsonl(experienceReusePath)
|
|
1088
|
+
if (project) records = records.filter((record) => record.project === project)
|
|
1089
|
+
if (targetAgent) {
|
|
1090
|
+
records = records.filter(
|
|
1091
|
+
(record) => String(record.targetAgent || '').toLowerCase() === targetAgent,
|
|
1092
|
+
)
|
|
1093
|
+
}
|
|
1094
|
+
const matcher = REUSE_STATUS_FILTERS[status]
|
|
1095
|
+
if (matcher) records = records.filter(matcher)
|
|
1096
|
+
records.sort((a, b) => {
|
|
1097
|
+
const at = new Date(a.timestamp || 0).getTime()
|
|
1098
|
+
const bt = new Date(b.timestamp || 0).getTime()
|
|
1099
|
+
return bt - at
|
|
1100
|
+
})
|
|
1101
|
+
|
|
1102
|
+
const visible = records.slice(0, limit).map((record) => ({
|
|
1103
|
+
id: record.id,
|
|
1104
|
+
source: record.source,
|
|
1105
|
+
sourceAgent: record.sourceAgent,
|
|
1106
|
+
targetAgent: record.targetAgent,
|
|
1107
|
+
experienceId: record.experienceId,
|
|
1108
|
+
reuse: record.reuse === true || record.reuse === 'true',
|
|
1109
|
+
behaviorChanged: record.behaviorChanged === true || record.behaviorChanged === 'true',
|
|
1110
|
+
outcomeImproved: record.outcomeImproved === true || record.outcomeImproved === 'true',
|
|
1111
|
+
confirmed: record.confirmed === true,
|
|
1112
|
+
confirmedBy: record.confirmedBy || null,
|
|
1113
|
+
traceId: record.traceId || null,
|
|
1114
|
+
timestamp: record.timestamp,
|
|
1115
|
+
project: record.project,
|
|
1116
|
+
notes:
|
|
1117
|
+
typeof record.notes === 'string' && record.notes ? record.notes.slice(0, 300) : null,
|
|
1118
|
+
}))
|
|
1119
|
+
|
|
1120
|
+
return {
|
|
1121
|
+
project: project || null,
|
|
1122
|
+
status,
|
|
1123
|
+
count: visible.length,
|
|
1124
|
+
total: records.length,
|
|
1125
|
+
records: visible,
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
function registerAgent(args = {}) {
|
|
1130
|
+
const agentId = String(args.agentId || '').trim()
|
|
1131
|
+
if (!agentId) throw new Error('agent.register requires a non-empty agentId')
|
|
1132
|
+
const project = args.project || projectName()
|
|
1133
|
+
const hostType = args.hostType || 'mcp'
|
|
1134
|
+
const capabilities = Array.isArray(args.capabilities) ? args.capabilities : []
|
|
1135
|
+
const now = new Date().toISOString()
|
|
1136
|
+
const agents = readJsonl(agentsPath)
|
|
1137
|
+
const existing = agents.find((a) => a.agentId === agentId && a.project === project)
|
|
1138
|
+
let result
|
|
1139
|
+
if (existing) {
|
|
1140
|
+
existing.lastSeenAt = now
|
|
1141
|
+
existing.sessionCount = (existing.sessionCount || 1) + 1
|
|
1142
|
+
existing.hostType = hostType
|
|
1143
|
+
if (capabilities.length > 0) existing.capabilities = capabilities
|
|
1144
|
+
result = existing
|
|
1145
|
+
} else {
|
|
1146
|
+
result = {
|
|
1147
|
+
id: createId('agent'),
|
|
1148
|
+
agentId,
|
|
1149
|
+
hostType,
|
|
1150
|
+
capabilities,
|
|
1151
|
+
registeredAt: now,
|
|
1152
|
+
lastSeenAt: now,
|
|
1153
|
+
sessionCount: 1,
|
|
1154
|
+
project,
|
|
1155
|
+
}
|
|
1156
|
+
agents.push(result)
|
|
1157
|
+
}
|
|
1158
|
+
writeJsonl(agentsPath, agents)
|
|
1159
|
+
return { id: result.id, agentId: result.agentId, registered: !existing, sessionCount: result.sessionCount, hostType: result.hostType, capabilities: result.capabilities }
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
function listAgents(args = {}) {
|
|
1163
|
+
const project = args.project || null
|
|
1164
|
+
let agents = readJsonl(agentsPath)
|
|
1165
|
+
if (project) agents = agents.filter((a) => a.project === project)
|
|
1166
|
+
return {
|
|
1167
|
+
count: agents.length,
|
|
1168
|
+
agents: agents.map((a) => ({
|
|
1169
|
+
id: a.id, agentId: a.agentId, hostType: a.hostType,
|
|
1170
|
+
capabilities: a.capabilities, registeredAt: a.registeredAt,
|
|
1171
|
+
lastSeenAt: a.lastSeenAt, sessionCount: a.sessionCount,
|
|
1172
|
+
taskCount: a.taskCount || 0, successCount: a.successCount || 0, failureCount: a.failureCount || 0,
|
|
1173
|
+
project: a.project,
|
|
1174
|
+
})),
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
function reportAgent(args = {}) {
|
|
1179
|
+
const targetAgent = args.agentId || null
|
|
1180
|
+
const project = args.project || projectName()
|
|
1181
|
+
const agents = readJsonl(agentsPath)
|
|
1182
|
+
const filtered = agents.filter((a) =>
|
|
1183
|
+
(!targetAgent || a.agentId === targetAgent) && (!project || a.project === project)
|
|
1184
|
+
)
|
|
1185
|
+
const traces = readJsonl(tracePath)
|
|
1186
|
+
const memories = readJsonl(memoryPath)
|
|
1187
|
+
const reuses = readJsonl(experienceReusePath)
|
|
1188
|
+
const reports = filtered.map((agent) => {
|
|
1189
|
+
const agentTraces = traces.filter((t) => t.agent === agent.agentId)
|
|
1190
|
+
const outcomes = agentTraces.filter((t) => t.event_type === 'task_outcome')
|
|
1191
|
+
const successes = outcomes.filter((t) => t.outcome === 'success').length
|
|
1192
|
+
const failures = outcomes.filter((t) => t.outcome === 'failure').length
|
|
1193
|
+
const agentMemories = memories.filter((m) => m.source === agent.agentId)
|
|
1194
|
+
const agentReuses = reuses.filter((r) => r.sourceAgent === agent.agentId || r.targetAgent === agent.agentId)
|
|
1195
|
+
const verified = agentReuses.filter((r) => r.confirmed && r.reuse && r.behaviorChanged && r.outcomeImproved)
|
|
1196
|
+
const regTasks = agent.taskCount || 0
|
|
1197
|
+
const regSuccess = agent.successCount || 0
|
|
1198
|
+
const regFail = agent.failureCount || 0
|
|
1199
|
+
const taskTotal = regTasks > 0 ? regTasks : outcomes.length
|
|
1200
|
+
const taskSuccess = regTasks > 0 ? regSuccess : successes
|
|
1201
|
+
const taskFail = regTasks > 0 ? regFail : failures
|
|
1202
|
+
return {
|
|
1203
|
+
agentId: agent.agentId,
|
|
1204
|
+
hostType: agent.hostType,
|
|
1205
|
+
sessionCount: agent.sessionCount,
|
|
1206
|
+
registeredAt: agent.registeredAt,
|
|
1207
|
+
lastSeenAt: agent.lastSeenAt,
|
|
1208
|
+
taskOutcomes: { total: taskTotal, success: taskSuccess, failure: taskFail, successRate: taskTotal > 0 ? Math.round((taskSuccess / taskTotal) * 100) : 0 },
|
|
1209
|
+
memories: agentMemories.length,
|
|
1210
|
+
experienceReuses: { total: agentReuses.length, verified: verified.length },
|
|
1211
|
+
active: (Date.now() - new Date(agent.lastSeenAt).getTime()) < 24 * 60 * 60 * 1000,
|
|
1212
|
+
}
|
|
1213
|
+
})
|
|
1214
|
+
return { project, count: reports.length, reports }
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
function routeTask(args = {}) {
|
|
1218
|
+
const task = String(args.task || '').trim()
|
|
1219
|
+
if (!task) throw new Error('task.route requires a non-empty task')
|
|
1220
|
+
const project = args.project || projectName()
|
|
1221
|
+
const scope = normalizeScope(args.scope)
|
|
1222
|
+
const limit = Math.min(Math.max(Number(args.limit) || 5, 1), 10)
|
|
1223
|
+
|
|
1224
|
+
const memories = readJsonl(memoryPath).filter((record) => record.archived !== true)
|
|
1225
|
+
const memoryById = new Map(memories.map((record) => [record.id, record]))
|
|
1226
|
+
const evidence = loadEvidenceWeights()
|
|
1227
|
+
|
|
1228
|
+
// Match verified reuse experiences whose source memory is relevant to the task.
|
|
1229
|
+
const routeByMemory = new Map()
|
|
1230
|
+
for (const record of readJsonl(experienceReusePath)) {
|
|
1231
|
+
if (!REUSE_STATUS_FILTERS.verified(record)) continue
|
|
1232
|
+
const memory = memoryById.get(record.experienceId)
|
|
1233
|
+
if (!memory || !matchesProjectScope(memory, project, scope)) continue
|
|
1234
|
+
const score = scoreRecord(memory, task, project)
|
|
1235
|
+
if (score <= 0) continue
|
|
1236
|
+
let entry = routeByMemory.get(record.experienceId)
|
|
1237
|
+
if (!entry) {
|
|
1238
|
+
entry = {
|
|
1239
|
+
experienceId: record.experienceId,
|
|
1240
|
+
memory,
|
|
1241
|
+
weight: 0,
|
|
1242
|
+
confirmed: false,
|
|
1243
|
+
sourceAgents: new Set(),
|
|
1244
|
+
targetAgents: new Set(),
|
|
1245
|
+
reuseCount: 0,
|
|
1246
|
+
}
|
|
1247
|
+
routeByMemory.set(record.experienceId, entry)
|
|
1248
|
+
}
|
|
1249
|
+
entry.reuseCount += 1
|
|
1250
|
+
if (record.confirmed === true) entry.confirmed = true
|
|
1251
|
+
if (record.sourceAgent) entry.sourceAgents.add(record.sourceAgent)
|
|
1252
|
+
if (record.targetAgent) entry.targetAgents.add(record.targetAgent)
|
|
1253
|
+
entry.weight = Math.max(entry.weight, score + (entry.confirmed ? 2 : 1.5))
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
const routes = [...routeByMemory.values()]
|
|
1257
|
+
.sort((a, b) => b.weight - a.weight)
|
|
1258
|
+
.slice(0, limit)
|
|
1259
|
+
.map((entry) => ({
|
|
1260
|
+
experienceId: entry.experienceId,
|
|
1261
|
+
reuseCount: entry.reuseCount,
|
|
1262
|
+
confirmed: entry.confirmed,
|
|
1263
|
+
sourceAgents: [...entry.sourceAgents].filter(Boolean),
|
|
1264
|
+
targetAgents: [...entry.targetAgents].filter(Boolean),
|
|
1265
|
+
score: Number(entry.weight.toFixed(2)),
|
|
1266
|
+
memory: {
|
|
1267
|
+
id: entry.memory.id,
|
|
1268
|
+
timestamp: entry.memory.timestamp,
|
|
1269
|
+
kind: entry.memory.kind,
|
|
1270
|
+
content: entry.memory.content,
|
|
1271
|
+
tags: entry.memory.tags || [],
|
|
1272
|
+
project: entry.memory.project || null,
|
|
1273
|
+
scope: entry.memory.scope || 'project',
|
|
1274
|
+
},
|
|
1275
|
+
}))
|
|
1276
|
+
|
|
1277
|
+
// Broader context: top related memories not already routed.
|
|
1278
|
+
const routedIds = new Set(routes.map((route) => route.experienceId))
|
|
1279
|
+
const scoreRelated = (record) =>
|
|
1280
|
+
scoreRecord(record, task, project, evidence) +
|
|
1281
|
+
(scope === 'all' && isGlobalRecord(record) ? 0.5 : 0)
|
|
1282
|
+
const relatedMemories = memories
|
|
1283
|
+
.filter((record) => !routedIds.has(record.id))
|
|
1284
|
+
.filter((record) => matchesProjectScope(record, project, scope))
|
|
1285
|
+
.filter((record) => scoreRelated(record) > 0)
|
|
1286
|
+
.sort((a, b) => scoreRelated(b) - scoreRelated(a))
|
|
1287
|
+
.slice(0, Math.max(limit, 5))
|
|
1288
|
+
.map((record) => {
|
|
1289
|
+
const ev = evidence.get(record.id)
|
|
1290
|
+
return {
|
|
1291
|
+
id: record.id,
|
|
1292
|
+
timestamp: record.timestamp,
|
|
1293
|
+
kind: record.kind,
|
|
1294
|
+
content: record.content,
|
|
1295
|
+
tags: record.tags || [],
|
|
1296
|
+
project: record.project || null,
|
|
1297
|
+
scope: record.scope || 'project',
|
|
1298
|
+
evidence: ev
|
|
1299
|
+
? { reuseCount: ev.reuseCount, confirmedCount: ev.confirmedCount }
|
|
1300
|
+
: null,
|
|
1301
|
+
}
|
|
1302
|
+
})
|
|
1303
|
+
|
|
1304
|
+
// Feed the query log so a later task_outcome can auto-claim route-driven reuse.
|
|
1305
|
+
if (routes.length > 0 || relatedMemories.length > 0) {
|
|
1306
|
+
const now = Date.now()
|
|
1307
|
+
const entries = loadRecentQueries()
|
|
1308
|
+
entries.push({
|
|
1309
|
+
agent: runtimeAgentId() || 'mcp',
|
|
1310
|
+
project,
|
|
1311
|
+
query: task,
|
|
1312
|
+
resultIds: [...routedIds, ...relatedMemories.map((record) => record.id)].filter(Boolean),
|
|
1313
|
+
resultSources: [...routes.map(() => 'experience'), ...relatedMemories.map((record) => record.source || null)],
|
|
1314
|
+
timestamp: now,
|
|
1315
|
+
expiresAt: now + REUSE_MATCH_WINDOW_MS,
|
|
1316
|
+
})
|
|
1317
|
+
persistRecentQueries(entries)
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
let suggestion
|
|
1321
|
+
if (routes.length > 0) {
|
|
1322
|
+
suggestion =
|
|
1323
|
+
`Route ${routes.length} verified experience(s) to this task; apply the top match first and record the outcome with mio.observer.ingest / mio.experience.reuse.`
|
|
1324
|
+
} else if (relatedMemories.length > 0) {
|
|
1325
|
+
suggestion =
|
|
1326
|
+
'No verified experience matches yet. Consult the related memories, then record the outcome so this task can seed future routes.'
|
|
1327
|
+
} else {
|
|
1328
|
+
suggestion =
|
|
1329
|
+
'No matching experience or memory found. Proceed with care and record the outcome to grow the experience base.'
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
return {
|
|
1333
|
+
task,
|
|
1334
|
+
project,
|
|
1335
|
+
scope,
|
|
1336
|
+
count: routes.length,
|
|
1337
|
+
routes,
|
|
1338
|
+
relatedMemories,
|
|
1339
|
+
summary: {
|
|
1340
|
+
verifiedRoutes: routes.length,
|
|
1341
|
+
relatedMemories: relatedMemories.length,
|
|
1342
|
+
suggestion,
|
|
1343
|
+
},
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
function phase0Report(args = {}) {
|
|
1347
|
+
const project = args.project || projectName()
|
|
1348
|
+
const report = loadPhase0(dataDir, project)
|
|
1349
|
+
const format = String(args.format || 'json').trim().toLowerCase()
|
|
1350
|
+
if (format === 'markdown') {
|
|
1351
|
+
return {
|
|
1352
|
+
project,
|
|
1353
|
+
format: 'markdown',
|
|
1354
|
+
markdown: renderPhase0Markdown(report),
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
return {
|
|
1358
|
+
project,
|
|
1359
|
+
...report,
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
const TOOLS = [
|
|
1364
|
+
{
|
|
1365
|
+
name: 'mio.memory.query',
|
|
1366
|
+
description: 'Search local Mio project memory for context, decisions, and historical problems.',
|
|
1367
|
+
inputSchema: {
|
|
1368
|
+
type: 'object',
|
|
1369
|
+
properties: {
|
|
1370
|
+
query: { type: 'string', description: 'Question or topic to recall.' },
|
|
1371
|
+
project: { type: 'string', description: 'Project name filter. Defaults to current directory name.' },
|
|
1372
|
+
limit: { type: 'number', description: 'Maximum results, between 1 and 20. Defaults to 5.' },
|
|
1373
|
+
kind: { type: 'string', description: 'Only return records with this kind (decision, context, problem, note).' },
|
|
1374
|
+
tags: { type: 'array', items: { type: 'string' }, description: 'Only return records containing all of these tags (AND).' },
|
|
1375
|
+
scope: { type: 'string', description: 'Search scope: project (default), global, or all.' },
|
|
1376
|
+
},
|
|
1377
|
+
required: ['query'],
|
|
1378
|
+
},
|
|
1379
|
+
},
|
|
1380
|
+
{
|
|
1381
|
+
name: 'mio.memory.archive',
|
|
1382
|
+
description: 'Archive (or restore) memory records by id. Archived records are excluded from memory.query and memory.analyze.',
|
|
1383
|
+
inputSchema: {
|
|
1384
|
+
type: 'object',
|
|
1385
|
+
properties: {
|
|
1386
|
+
ids: { type: 'array', items: { type: 'string' }, description: 'Memory record ids to archive or restore.' },
|
|
1387
|
+
project: { type: 'string', description: 'Project name filter. Defaults to current repository.' },
|
|
1388
|
+
restore: { type: 'boolean', description: 'When true, un-archive the given ids instead.' },
|
|
1389
|
+
reason: { type: 'string', description: 'Optional archive reason.' },
|
|
1390
|
+
},
|
|
1391
|
+
required: ['ids'],
|
|
1392
|
+
},
|
|
1393
|
+
},
|
|
1394
|
+
{
|
|
1395
|
+
name: 'mio.memory.migrate',
|
|
1396
|
+
description: 'Migrate memory records between project and global layers (P3). Moving to global makes the record reusable across projects (project set to null); moving to project restores project scoping.',
|
|
1397
|
+
inputSchema: {
|
|
1398
|
+
type: 'object',
|
|
1399
|
+
properties: {
|
|
1400
|
+
ids: { type: 'array', items: { type: 'string' }, description: 'Memory record ids to migrate.' },
|
|
1401
|
+
scope: { type: 'string', description: 'Target layer: global or project.' },
|
|
1402
|
+
project: { type: 'string', description: 'Target project for project scope. Defaults to current repository.' },
|
|
1403
|
+
reason: { type: 'string', description: 'Optional migration reason.' },
|
|
1404
|
+
},
|
|
1405
|
+
required: ['ids', 'scope'],
|
|
1406
|
+
},
|
|
1407
|
+
},
|
|
1408
|
+
{
|
|
1409
|
+
name: 'mio.memory.record',
|
|
1410
|
+
description: 'Record a decision, project context, or historical problem into local Mio memory.',
|
|
1411
|
+
inputSchema: {
|
|
1412
|
+
type: 'object',
|
|
1413
|
+
properties: {
|
|
1414
|
+
content: { type: 'string', description: 'Memory content.' },
|
|
1415
|
+
kind: { type: 'string', description: 'Memory kind, such as decision, context, or problem.' },
|
|
1416
|
+
tags: { type: 'array', items: { type: 'string' }, description: 'Optional tags.' },
|
|
1417
|
+
project: { type: 'string', description: 'Project name. Defaults to current directory name.' },
|
|
1418
|
+
scope: { type: 'string', description: 'Record scope: project (default) or global.' },
|
|
1419
|
+
source: { type: 'string', description: 'Recording source. Defaults to mcp.' },
|
|
1420
|
+
},
|
|
1421
|
+
required: ['content'],
|
|
1422
|
+
},
|
|
1423
|
+
},
|
|
1424
|
+
{
|
|
1425
|
+
name: 'mio.observer.ingest',
|
|
1426
|
+
description: 'Ingest an Agent Trace event such as tool_call, error, retry, or task_outcome.',
|
|
1427
|
+
inputSchema: {
|
|
1428
|
+
type: 'object',
|
|
1429
|
+
properties: {
|
|
1430
|
+
trace_id: { type: 'string', description: 'Stable trace identifier.' },
|
|
1431
|
+
event_type: { type: 'string', description: 'Event type, such as tool_call, error, retry, task_outcome.' },
|
|
1432
|
+
payload: { type: 'object', description: 'Event payload.' },
|
|
1433
|
+
outcome: { type: 'string', description: 'Optional outcome: success, failure, error, aborted, retry.' },
|
|
1434
|
+
agent: { type: 'string', description: 'Agent identifier.' },
|
|
1435
|
+
host: { type: 'string', description: 'Host name. Defaults to mcp.' },
|
|
1436
|
+
project: { type: 'string', description: 'Project name. Defaults to current directory name.' },
|
|
1437
|
+
},
|
|
1438
|
+
required: ['trace_id', 'event_type'],
|
|
1439
|
+
},
|
|
1440
|
+
},
|
|
1441
|
+
{
|
|
1442
|
+
name: 'mio.observer.subscribe',
|
|
1443
|
+
description: 'Subscribe to observer events (project, event types, optional topic). New matching events are delivered via mio.observer.digest.',
|
|
1444
|
+
inputSchema: {
|
|
1445
|
+
type: 'object',
|
|
1446
|
+
properties: {
|
|
1447
|
+
eventTypes: { type: 'array', items: { type: 'string' }, description: 'Event types to watch, e.g. error, retry, task_outcome. Empty means all.' },
|
|
1448
|
+
project: { type: 'string', description: 'Project name. Defaults to current repository.' },
|
|
1449
|
+
topic: { type: 'string', description: 'Optional keyword; only events whose payload mentions it match.' },
|
|
1450
|
+
ttlDays: { type: 'number', description: 'Subscription lifetime in days. Defaults to 30.' },
|
|
1451
|
+
},
|
|
1452
|
+
},
|
|
1453
|
+
},
|
|
1454
|
+
{
|
|
1455
|
+
name: 'mio.observer.digest',
|
|
1456
|
+
description: 'Return new observer events matching active subscriptions since the last digest, and advance the cursor.',
|
|
1457
|
+
inputSchema: {
|
|
1458
|
+
type: 'object',
|
|
1459
|
+
properties: {
|
|
1460
|
+
project: { type: 'string', description: 'Project name filter. Defaults to current repository.' },
|
|
1461
|
+
eventTypes: { type: 'array', items: { type: 'string' }, description: 'Optional extra event type filter.' },
|
|
1462
|
+
limit: { type: 'number', description: 'Maximum events to return, 1-100. Defaults to 20.' },
|
|
1463
|
+
},
|
|
1464
|
+
},
|
|
1465
|
+
},
|
|
1466
|
+
{
|
|
1467
|
+
name: 'mio.policy.check',
|
|
1468
|
+
description: 'Check historical risk for an action before executing it, returning risk level, outcome counts, and recent failure examples.',
|
|
1469
|
+
inputSchema: {
|
|
1470
|
+
type: 'object',
|
|
1471
|
+
properties: {
|
|
1472
|
+
action: { type: 'string', description: 'The action or tool operation to check.' },
|
|
1473
|
+
project: { type: 'string', description: 'Project name filter. Defaults to current directory name.' },
|
|
1474
|
+
context: { type: 'string', description: 'Optional additional context.' },
|
|
1475
|
+
},
|
|
1476
|
+
required: ['action'],
|
|
1477
|
+
},
|
|
1478
|
+
},
|
|
1479
|
+
{
|
|
1480
|
+
name: 'mio.memory.analyze',
|
|
1481
|
+
description: 'Analyze local Mio memory quality: kind distribution, duplicate groups, and low-quality records.',
|
|
1482
|
+
inputSchema: {
|
|
1483
|
+
type: 'object',
|
|
1484
|
+
properties: {
|
|
1485
|
+
project: { type: 'string', description: 'Project name filter. Defaults to current repository.' },
|
|
1486
|
+
limit: { type: 'number', description: 'Maximum duplicates/low-quality examples to return, 1-20. Defaults to 5.' },
|
|
1487
|
+
},
|
|
1488
|
+
},
|
|
1489
|
+
},
|
|
1490
|
+
{
|
|
1491
|
+
name: 'mio.experience.reuse',
|
|
1492
|
+
description: 'Record Phase 0 evidence of cross-agent experience reuse.',
|
|
1493
|
+
inputSchema: {
|
|
1494
|
+
type: 'object',
|
|
1495
|
+
properties: {
|
|
1496
|
+
sourceAgent: { type: 'string', description: 'Agent that produced the experience.' },
|
|
1497
|
+
targetAgent: { type: 'string', description: 'Agent that reused the experience.' },
|
|
1498
|
+
experienceId: { type: 'string', description: 'Stable experience or memory id.' },
|
|
1499
|
+
reuse: { type: 'boolean', description: 'True when memory.query returned historical experience and it was reused.' },
|
|
1500
|
+
behaviorChanged: { type: 'boolean', description: 'True when target Agent behavior changed because of the reused experience.' },
|
|
1501
|
+
outcomeImproved: { type: 'boolean', description: 'True when the task outcome improved.' },
|
|
1502
|
+
project: { type: 'string', description: 'Project name filter. Defaults to current repository.' },
|
|
1503
|
+
notes: { type: 'string', description: 'Optional evidence notes.' },
|
|
1504
|
+
},
|
|
1505
|
+
required: ['sourceAgent', 'targetAgent', 'experienceId', 'reuse', 'behaviorChanged', 'outcomeImproved'],
|
|
1506
|
+
},
|
|
1507
|
+
},
|
|
1508
|
+
{
|
|
1509
|
+
name: 'mio.experience.confirm',
|
|
1510
|
+
description: 'Confirm an auto-claimed reuse record as verified reuse evidence.',
|
|
1511
|
+
inputSchema: {
|
|
1512
|
+
type: 'object',
|
|
1513
|
+
properties: {
|
|
1514
|
+
id: { type: 'string', description: 'Id of the auto-claimed reuse record returned by mio.observer.ingest.' },
|
|
1515
|
+
confirmedBy: { type: 'string', description: 'Who confirmed the reuse: agent or human. Defaults to agent.' },
|
|
1516
|
+
outcomeImproved: { type: 'boolean', description: 'Override: true when the reused experience improved the task outcome. Defaults to the auto-claim value.' },
|
|
1517
|
+
notes: { type: 'string', description: 'Optional confirmation notes.' },
|
|
1518
|
+
},
|
|
1519
|
+
required: ['id'],
|
|
1520
|
+
},
|
|
1521
|
+
},
|
|
1522
|
+
{
|
|
1523
|
+
name: 'mio.experience.list',
|
|
1524
|
+
description: 'List Phase 0 experience reuse evidence, optionally filtered by status or target agent.',
|
|
1525
|
+
inputSchema: {
|
|
1526
|
+
type: 'object',
|
|
1527
|
+
properties: {
|
|
1528
|
+
project: { type: 'string', description: 'Project name filter. Defaults to current repository.' },
|
|
1529
|
+
status: { type: 'string', description: 'Filter: pending (auto-claim awaiting confirmation), confirmed, verified, auto_claim, agent_report, all. Defaults to all.' },
|
|
1530
|
+
targetAgent: { type: 'string', description: 'Filter records by target agent.' },
|
|
1531
|
+
limit: { type: 'number', description: 'Maximum records to return, 1-100. Defaults to 20.' },
|
|
1532
|
+
},
|
|
1533
|
+
},
|
|
1534
|
+
},
|
|
1535
|
+
{
|
|
1536
|
+
name: 'mio.phase0.report',
|
|
1537
|
+
description: 'Generate the current Phase 0 cross-agent validation report, as JSON or rendered Markdown.',
|
|
1538
|
+
inputSchema: {
|
|
1539
|
+
type: 'object',
|
|
1540
|
+
properties: {
|
|
1541
|
+
project: { type: 'string', description: 'Project name filter. Defaults to current repository.' },
|
|
1542
|
+
format: { type: 'string', description: 'Output format: json or markdown. Defaults to json.' },
|
|
1543
|
+
},
|
|
1544
|
+
},
|
|
1545
|
+
},
|
|
1546
|
+
{
|
|
1547
|
+
name: 'mio.task.route',
|
|
1548
|
+
description: 'Route an incoming task to the most relevant verified experiences and related memories, turning recall into decision influence (P1).',
|
|
1549
|
+
inputSchema: {
|
|
1550
|
+
type: 'object',
|
|
1551
|
+
properties: {
|
|
1552
|
+
task: { type: 'string', description: 'Task description to route, e.g. \'fix IPC communication bug\'.' },
|
|
1553
|
+
project: { type: 'string', description: 'Project name filter. Defaults to current repository.' },
|
|
1554
|
+
scope: { type: 'string', description: 'Search scope: project (default), global, or all.' },
|
|
1555
|
+
limit: { type: 'number', description: 'Maximum routes, between 1 and 10. Defaults to 5.' },
|
|
1556
|
+
},
|
|
1557
|
+
required: ['task'],
|
|
1558
|
+
},
|
|
1559
|
+
},
|
|
1560
|
+
{
|
|
1561
|
+
name: 'mio.agent.register',
|
|
1562
|
+
description: 'Register or update an agent in the Mio Agent Registry. Upserts by agentId+project; updates lastSeenAt and sessionCount.',
|
|
1563
|
+
inputSchema: {
|
|
1564
|
+
type: 'object',
|
|
1565
|
+
properties: {
|
|
1566
|
+
agentId: { type: 'string', description: 'Unique agent identifier (e.g. codex, opencode).' },
|
|
1567
|
+
hostType: { type: 'string', description: 'Host type: mcp, observer, or plugin. Defaults to mcp.' },
|
|
1568
|
+
capabilities: { type: 'array', items: { type: 'string' }, description: 'Agent capabilities.' },
|
|
1569
|
+
project: { type: 'string', description: 'Project name. Defaults to current directory name.' },
|
|
1570
|
+
},
|
|
1571
|
+
required: ['agentId'],
|
|
1572
|
+
},
|
|
1573
|
+
},
|
|
1574
|
+
{
|
|
1575
|
+
name: 'mio.agent.list',
|
|
1576
|
+
description: 'List all registered agents, optionally filtered by project.',
|
|
1577
|
+
inputSchema: {
|
|
1578
|
+
type: 'object',
|
|
1579
|
+
properties: {
|
|
1580
|
+
project: { type: 'string', description: 'Project name filter. Omit for all agents.' },
|
|
1581
|
+
},
|
|
1582
|
+
},
|
|
1583
|
+
},
|
|
1584
|
+
{
|
|
1585
|
+
name: 'mio.agent.report',
|
|
1586
|
+
description: 'Performance report for one or all agents, aggregating task outcomes, memory, and experience reuse.',
|
|
1587
|
+
inputSchema: {
|
|
1588
|
+
type: 'object',
|
|
1589
|
+
properties: {
|
|
1590
|
+
agentId: { type: 'string', description: 'Agent to report on. Omit for all agents.' },
|
|
1591
|
+
project: { type: 'string', description: 'Project name filter. Defaults to current repository.' },
|
|
1592
|
+
},
|
|
1593
|
+
},
|
|
1594
|
+
},
|
|
1595
|
+
{
|
|
1596
|
+
name: 'mio.evolution.report',
|
|
1597
|
+
description: 'Generate cross-agent evolution report: ecosystem summary, agent performance comparison, cross-agent patterns, memory health, and optimization suggestions (ADR-017 Evolution plane).',
|
|
1598
|
+
inputSchema: {
|
|
1599
|
+
type: 'object',
|
|
1600
|
+
properties: {
|
|
1601
|
+
project: { type: 'string', description: 'Project name filter. Defaults to current repository.' },
|
|
1602
|
+
period: { type: 'string', description: 'Time window: 24h, 7d, 30d, or all (default).' },
|
|
1603
|
+
},
|
|
1604
|
+
},
|
|
1605
|
+
},
|
|
1606
|
+
{
|
|
1607
|
+
name: 'mio.task.record_outcome',
|
|
1608
|
+
description: 'Record task outcome with agent registry update and auto-claim experience reuse in one atomic call. Combines observer.ingest + agent stats + auto-claim for a complete task lifecycle closure.',
|
|
1609
|
+
inputSchema: {
|
|
1610
|
+
type: 'object',
|
|
1611
|
+
properties: {
|
|
1612
|
+
outcome: { type: 'string', description: 'Task outcome: success, failure, or aborted.' },
|
|
1613
|
+
task: { type: 'string', description: 'Brief task description.' },
|
|
1614
|
+
summary: { type: 'string', description: 'Optional task summary.' },
|
|
1615
|
+
verification: { type: 'string', description: 'Optional verification details.' },
|
|
1616
|
+
agentId: { type: 'string', description: 'Agent identifier. Defaults to MIO_CONTEXT agentId.' },
|
|
1617
|
+
project: { type: 'string', description: 'Project name. Defaults to current repository.' },
|
|
1618
|
+
traceId: { type: 'string', description: 'Optional custom trace ID. Auto-generated if omitted.' },
|
|
1619
|
+
},
|
|
1620
|
+
required: ['outcome'],
|
|
1621
|
+
},
|
|
1337
1622
|
},
|
|
1338
1623
|
{
|
|
1339
|
-
name: 'mio.
|
|
1340
|
-
description: '
|
|
1624
|
+
name: 'mio.idea.generate',
|
|
1625
|
+
description: 'Generate creative ideas or multiple candidate solutions based on a given goal, context, and constraints.',
|
|
1341
1626
|
inputSchema: {
|
|
1342
1627
|
type: 'object',
|
|
1628
|
+
required: ['goal'],
|
|
1343
1629
|
properties: {
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
agent: { type: 'string', description: 'Agent identifier.' },
|
|
1349
|
-
host: { type: 'string', description: 'Host name. Defaults to mcp.' },
|
|
1350
|
-
project: { type: 'string', description: 'Project name. Defaults to current directory name.' },
|
|
1630
|
+
goal: { type: 'string', description: 'The goal or problem description for which to generate ideas.' },
|
|
1631
|
+
context: { type: 'string', description: '(Optional) Additional background information, such as prior decisions or known problems.' },
|
|
1632
|
+
constraints: { type: 'array', items: { type: 'string' }, description: '(Optional) List of constraints, e.g. "no new dependencies", "must be implementable as a plugin".' },
|
|
1633
|
+
numIdeas: { type: 'integer', description: '(Optional) Number of ideas to return, default 3.', default: 3, minimum: 1 }
|
|
1351
1634
|
},
|
|
1352
|
-
|
|
1635
|
+
additionalProperties: false
|
|
1353
1636
|
},
|
|
1354
1637
|
},
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1638
|
+
]
|
|
1639
|
+
|
|
1640
|
+
async function callTool(name, args = {}) {
|
|
1641
|
+
switch (name) {
|
|
1642
|
+
case 'mio.memory.query':
|
|
1643
|
+
return queryMemory(args)
|
|
1644
|
+
case 'mio.memory.record':
|
|
1645
|
+
return recordMemory(args)
|
|
1646
|
+
case 'mio.memory.archive':
|
|
1647
|
+
return archiveMemory(args)
|
|
1648
|
+
case 'mio.memory.migrate':
|
|
1649
|
+
return migrateMemory(args)
|
|
1650
|
+
case 'mio.observer.ingest':
|
|
1651
|
+
return ingestObservation(args)
|
|
1652
|
+
case 'mio.observer.subscribe':
|
|
1653
|
+
return subscribeObserver(args)
|
|
1654
|
+
case 'mio.observer.digest':
|
|
1655
|
+
return observerDigest(args)
|
|
1656
|
+
case 'mio.policy.check':
|
|
1657
|
+
return policyCheck(args)
|
|
1658
|
+
case 'mio.memory.analyze':
|
|
1659
|
+
return analyzeMemory(args)
|
|
1660
|
+
case 'mio.experience.reuse':
|
|
1661
|
+
return recordExperienceReuse(args)
|
|
1662
|
+
case 'mio.experience.confirm':
|
|
1663
|
+
return confirmExperienceReuse(args)
|
|
1664
|
+
case 'mio.experience.list':
|
|
1665
|
+
return listExperienceReuse(args)
|
|
1666
|
+
case 'mio.phase0.report':
|
|
1667
|
+
return phase0Report(args)
|
|
1668
|
+
case 'mio.task.route':
|
|
1669
|
+
return routeTask(args)
|
|
1670
|
+
case 'mio.agent.register':
|
|
1671
|
+
return registerAgent(args)
|
|
1672
|
+
case 'mio.agent.list':
|
|
1673
|
+
return listAgents(args)
|
|
1674
|
+
case 'mio.agent.report':
|
|
1675
|
+
return reportAgent(args)
|
|
1676
|
+
case 'mio.evolution.report':
|
|
1677
|
+
return evolutionReport(args)
|
|
1678
|
+
case 'mio.task.record_outcome':
|
|
1679
|
+
return recordTaskOutcome(args)
|
|
1680
|
+
case 'mio.idea.generate':
|
|
1681
|
+
return handleIdeaGenerate(args)
|
|
1682
|
+
default:
|
|
1683
|
+
throw new Error(`Unknown tool: ${name}`)
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
function jsonRpcResult(id, result) {
|
|
1688
|
+
return { jsonrpc: '2.0', id, result }
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
function jsonRpcError(id, code, message, data) {
|
|
1692
|
+
const error = { code, message }
|
|
1693
|
+
if (data !== undefined) error.data = data
|
|
1694
|
+
return { jsonrpc: '2.0', id, error }
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
function send(message) {
|
|
1698
|
+
if (message) process.stdout.write(`${JSON.stringify(message)}\n`)
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
async function handleMessage(message) {
|
|
1702
|
+
if (!message || typeof message !== 'object' || message.jsonrpc !== '2.0') return
|
|
1703
|
+
if (message.method && message.method.startsWith('notifications/')) return
|
|
1704
|
+
if (message.id === undefined) return
|
|
1705
|
+
|
|
1706
|
+
try {
|
|
1707
|
+
if (message.method === 'initialize') {
|
|
1708
|
+
send(
|
|
1709
|
+
jsonRpcResult(message.id, {
|
|
1710
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
1711
|
+
capabilities: { tools: {} },
|
|
1712
|
+
serverInfo: SERVER_INFO,
|
|
1713
|
+
})
|
|
1714
|
+
)
|
|
1715
|
+
return
|
|
1716
|
+
}
|
|
1717
|
+
if (message.method === 'ping') {
|
|
1718
|
+
send(jsonRpcResult(message.id, {}))
|
|
1719
|
+
return
|
|
1720
|
+
}
|
|
1721
|
+
if (message.method === 'tools/list') {
|
|
1722
|
+
send(jsonRpcResult(message.id, { tools: TOOLS }))
|
|
1723
|
+
return
|
|
1724
|
+
}
|
|
1725
|
+
if (message.method === 'tools/call') {
|
|
1726
|
+
const name = message.params && message.params.name
|
|
1727
|
+
const args = (message.params && message.params.arguments) || {}
|
|
1728
|
+
const result = await callTool(name, args)
|
|
1729
|
+
send(
|
|
1730
|
+
jsonRpcResult(message.id, {
|
|
1731
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
1732
|
+
isError: false,
|
|
1733
|
+
})
|
|
1734
|
+
)
|
|
1735
|
+
return
|
|
1736
|
+
}
|
|
1737
|
+
send(jsonRpcError(message.id, -32601, `Method not found: ${message.method}`))
|
|
1738
|
+
} catch (error) {
|
|
1739
|
+
send(jsonRpcError(message.id, -32603, error.message || String(error)))
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
const rl = readline.createInterface({ input: process.stdin, crlfDelay: Infinity })
|
|
1744
|
+
|
|
1745
|
+
rl.on('line', (line) => {
|
|
1746
|
+
let message
|
|
1747
|
+
try {
|
|
1748
|
+
message = JSON.parse(line)
|
|
1749
|
+
} catch (_) {
|
|
1750
|
+
return
|
|
1751
|
+
}
|
|
1752
|
+
handleMessage(message).catch((error) => {
|
|
1753
|
+
send(jsonRpcError(message && message.id, -32603, error.message || String(error)))
|
|
1754
|
+
})
|
|
1755
|
+
})
|
|
1756
|
+
|
|
1757
|
+
rl.on('close', () => {
|
|
1758
|
+
if (require.main === module) {
|
|
1759
|
+
process.exit(0)
|
|
1760
|
+
}
|
|
1761
|
+
})
|
|
1762
|
+
|
|
1763
|
+
|
|
1764
|
+
function evolutionReport(args = {}) {
|
|
1765
|
+
const project = args.project || projectName()
|
|
1766
|
+
const period = String(args.period || 'all').trim()
|
|
1767
|
+
const now = Date.now()
|
|
1768
|
+
const periodMs = period === '24h' ? 86400000 : period === '7d' ? 604800000 : period === '30d' ? 2592000000 : Infinity
|
|
1769
|
+
const cutoff = periodMs === Infinity ? 0 : now - periodMs
|
|
1770
|
+
|
|
1771
|
+
const memories = readJsonl(memoryPath).filter((m) => !project || m.project === project || !m.project)
|
|
1772
|
+
const traces = readJsonl(tracePath).filter((t) => !project || t.project === project)
|
|
1773
|
+
const reuses = readJsonl(experienceReusePath).filter((r) => !project || r.project === project)
|
|
1774
|
+
const agents = readJsonl(agentsPath).filter((a) => !project || a.project === project)
|
|
1775
|
+
const queries = readJsonl(queryPath).filter((q) => !project || q.project === project)
|
|
1776
|
+
|
|
1777
|
+
const filteredTraces = cutoff ? traces.filter((t) => new Date(t.timestamp).getTime() >= cutoff) : traces
|
|
1778
|
+
const filteredMemories = cutoff ? memories.filter((m) => new Date(m.timestamp).getTime() >= cutoff) : memories
|
|
1779
|
+
|
|
1780
|
+
// Ecosystem summary
|
|
1781
|
+
const agentIds = new Set()
|
|
1782
|
+
filteredTraces.forEach((t) => { if (t.agent) agentIds.add(t.agent) })
|
|
1783
|
+
agents.forEach((a) => agentIds.add(a.agentId))
|
|
1784
|
+
const outcomes = filteredTraces.filter((t) => t.event_type === 'task_outcome')
|
|
1785
|
+
const successes = outcomes.filter((t) => t.outcome === 'success').length
|
|
1786
|
+
const failures = outcomes.filter((t) => t.outcome === 'failure').length
|
|
1787
|
+
const verifiedReuses = reuses.filter((r) => r.confirmed && r.reuse && r.behaviorChanged && r.outcomeImproved)
|
|
1788
|
+
const pendingReuses = reuses.filter((r) => r.source === 'auto_claim' && !r.confirmed)
|
|
1789
|
+
|
|
1790
|
+
const ecosystem = {
|
|
1791
|
+
agents: agentIds.size,
|
|
1792
|
+
totalTasks: outcomes.length,
|
|
1793
|
+
successRate: outcomes.length > 0 ? Math.round((successes / outcomes.length) * 100) : 0,
|
|
1794
|
+
totalMemories: filteredMemories.length,
|
|
1795
|
+
totalReuses: reuses.length,
|
|
1796
|
+
verifiedReuses: verifiedReuses.length,
|
|
1797
|
+
pendingReuses: pendingReuses.length,
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
// Agent performance comparison
|
|
1801
|
+
const agentPerf = []
|
|
1802
|
+
for (const agentId of agentIds) {
|
|
1803
|
+
const agentTraces = filteredTraces.filter((t) => t.agent === agentId)
|
|
1804
|
+
const agentOutcomes = agentTraces.filter((t) => t.event_type === 'task_outcome')
|
|
1805
|
+
const agentSuccesses = agentOutcomes.filter((t) => t.outcome === 'success').length
|
|
1806
|
+
const agentFailures = agentOutcomes.filter((t) => t.outcome === 'failure').length
|
|
1807
|
+
const agentMemories = filteredMemories.filter((m) => m.source === agentId)
|
|
1808
|
+
const agentVerified = reuses.filter((r) =>
|
|
1809
|
+
(r.sourceAgent === agentId || r.targetAgent === agentId) && r.confirmed && r.reuse && r.behaviorChanged && r.outcomeImproved
|
|
1810
|
+
)
|
|
1811
|
+
const reg = agents.find((a) => a.agentId === agentId)
|
|
1812
|
+
agentPerf.push({
|
|
1813
|
+
agentId,
|
|
1814
|
+
tasks: {
|
|
1815
|
+
total: agentOutcomes.length,
|
|
1816
|
+
success: agentSuccesses,
|
|
1817
|
+
failure: agentFailures,
|
|
1818
|
+
successRate: agentOutcomes.length > 0 ? Math.round((agentSuccesses / agentOutcomes.length) * 100) : 0,
|
|
1819
|
+
},
|
|
1820
|
+
memories: agentMemories.length,
|
|
1821
|
+
verifiedReuses: agentVerified.length,
|
|
1822
|
+
registered: !!reg,
|
|
1823
|
+
lastActive: reg ? reg.lastSeenAt : (agentTraces.length > 0 ? agentTraces[agentTraces.length - 1].timestamp : null),
|
|
1824
|
+
})
|
|
1825
|
+
}
|
|
1826
|
+
agentPerf.sort((a, b) => b.tasks.successRate - a.tasks.successRate || b.tasks.total - a.tasks.total)
|
|
1827
|
+
|
|
1828
|
+
// Cross-agent patterns
|
|
1829
|
+
const crossPatterns = []
|
|
1830
|
+
const reuseByPair = {}
|
|
1831
|
+
for (const r of reuses) {
|
|
1832
|
+
const key = r.sourceAgent + '->' + r.targetAgent
|
|
1833
|
+
if (!reuseByPair[key]) reuseByPair[key] = { source: r.sourceAgent, target: r.targetAgent, total: 0, verified: 0, improved: 0 }
|
|
1834
|
+
reuseByPair[key].total++
|
|
1835
|
+
if (r.confirmed) reuseByPair[key].verified++
|
|
1836
|
+
if (r.outcomeImproved) reuseByPair[key].improved++
|
|
1837
|
+
}
|
|
1838
|
+
for (const [key, pair] of Object.entries(reuseByPair)) {
|
|
1839
|
+
if (pair.source !== pair.target) {
|
|
1840
|
+
crossPatterns.push({
|
|
1841
|
+
type: 'reuse_path',
|
|
1842
|
+
from: pair.source,
|
|
1843
|
+
to: pair.target,
|
|
1844
|
+
total: pair.total,
|
|
1845
|
+
verified: pair.verified,
|
|
1846
|
+
improved: pair.improved,
|
|
1847
|
+
})
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
crossPatterns.sort((a, b) => b.verified - a.verified || b.total - a.total)
|
|
1851
|
+
|
|
1852
|
+
// Memory health
|
|
1853
|
+
const byKind = {}
|
|
1854
|
+
let shortContent = 0
|
|
1855
|
+
let missingKind = 0
|
|
1856
|
+
for (const m of filteredMemories) {
|
|
1857
|
+
const kind = m.kind || 'unknown'
|
|
1858
|
+
byKind[kind] = (byKind[kind] || 0) + 1
|
|
1859
|
+
const content = String(m.content || '').trim()
|
|
1860
|
+
if (content.length < 20) shortContent++
|
|
1861
|
+
if (!m.kind) missingKind++
|
|
1862
|
+
}
|
|
1863
|
+
const memoryHealth = {
|
|
1864
|
+
total: filteredMemories.length,
|
|
1865
|
+
byKind,
|
|
1866
|
+
shortContent,
|
|
1867
|
+
missingKind,
|
|
1868
|
+
qualityScore: filteredMemories.length > 0
|
|
1869
|
+
? Math.round(((filteredMemories.length - shortContent - missingKind) / filteredMemories.length) * 100)
|
|
1870
|
+
: 100,
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
// Suggestions engine
|
|
1874
|
+
const suggestions = []
|
|
1875
|
+
|
|
1876
|
+
// Routing suggestion: compare agent success rates
|
|
1877
|
+
if (agentPerf.length >= 2) {
|
|
1878
|
+
const best = agentPerf[0]
|
|
1879
|
+
const worst = agentPerf[agentPerf.length - 1]
|
|
1880
|
+
if (best.tasks.total >= 5 && worst.tasks.total >= 5 && best.tasks.successRate - worst.tasks.successRate >= 15) {
|
|
1881
|
+
suggestions.push({
|
|
1882
|
+
category: 'routing',
|
|
1883
|
+
priority: 'high',
|
|
1884
|
+
description: best.agentId + ' has ' + best.tasks.successRate + '% success rate vs ' + worst.agentId + ' at ' + worst.tasks.successRate + '%; prefer routing to ' + best.agentId,
|
|
1885
|
+
basedOn: 'task_outcome comparison',
|
|
1886
|
+
})
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
// Pending auto-claim suggestion
|
|
1891
|
+
if (pendingReuses.length >= 10) {
|
|
1892
|
+
suggestions.push({
|
|
1893
|
+
category: 'evidence',
|
|
1894
|
+
priority: 'medium',
|
|
1895
|
+
description: pendingReuses.length + ' pending auto-claims awaiting confirmation; review cross-agent claims to increase verified reuse count',
|
|
1896
|
+
basedOn: 'experience_reuse.jsonl',
|
|
1897
|
+
})
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
// Memory quality suggestion
|
|
1901
|
+
if (memoryHealth.qualityScore < 80 && filteredMemories.length > 10) {
|
|
1902
|
+
suggestions.push({
|
|
1903
|
+
category: 'memory',
|
|
1904
|
+
priority: 'medium',
|
|
1905
|
+
description: 'Memory quality score is ' + memoryHealth.qualityScore + '%; ' + shortContent + ' short records and ' + missingKind + ' missing kind fields found',
|
|
1906
|
+
basedOn: 'memory.jsonl quality analysis',
|
|
1907
|
+
})
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
// Cross-agent value suggestion
|
|
1911
|
+
if (crossPatterns.length === 0 && agentIds.size >= 2) {
|
|
1912
|
+
suggestions.push({
|
|
1913
|
+
category: 'interop',
|
|
1914
|
+
priority: 'high',
|
|
1915
|
+
description: 'Multiple agents registered but no cross-agent reuse patterns detected; ensure agents query shared memory',
|
|
1916
|
+
basedOn: 'experience_reuse.jsonl',
|
|
1917
|
+
})
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
// Active agent suggestion
|
|
1921
|
+
const inactiveAgents = agentPerf.filter((a) => {
|
|
1922
|
+
if (!a.lastActive) return false
|
|
1923
|
+
return (now - new Date(a.lastActive).getTime()) > 7 * 86400000
|
|
1924
|
+
})
|
|
1925
|
+
if (inactiveAgents.length > 0) {
|
|
1926
|
+
suggestions.push({
|
|
1927
|
+
category: 'coverage',
|
|
1928
|
+
priority: 'low',
|
|
1929
|
+
description: inactiveAgents.map((a) => a.agentId).join(', ') + ' inactive for >7 days; consider removal or reactivation',
|
|
1930
|
+
basedOn: 'agent registry lastSeenAt',
|
|
1931
|
+
})
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
suggestions.sort((a, b) => {
|
|
1935
|
+
const p = { high: 0, medium: 1, low: 2 }
|
|
1936
|
+
return (p[a.priority] || 2) - (p[b.priority] || 2)
|
|
1937
|
+
})
|
|
1938
|
+
|
|
1939
|
+
return {
|
|
1940
|
+
project,
|
|
1941
|
+
generatedAt: new Date().toISOString(),
|
|
1942
|
+
period,
|
|
1943
|
+
ecosystem,
|
|
1944
|
+
agentPerformance: agentPerf,
|
|
1945
|
+
crossAgentPatterns: crossPatterns,
|
|
1946
|
+
memoryHealth,
|
|
1947
|
+
suggestions,
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
|
|
1952
|
+
function recordTaskOutcome(args = {}) {
|
|
1953
|
+
const outcome = String(args.outcome || '').trim().toLowerCase()
|
|
1954
|
+
if (!['success', 'failure', 'aborted'].includes(outcome)) {
|
|
1955
|
+
throw new Error('task.record_outcome requires outcome to be success, failure, or aborted')
|
|
1956
|
+
}
|
|
1957
|
+
const agentId = String(args.agentId || runtimeAgentId() || 'unknown').trim()
|
|
1958
|
+
const project = args.project || projectName()
|
|
1959
|
+
const task = String(args.task || '').trim()
|
|
1960
|
+
const summary = String(args.summary || '').trim()
|
|
1961
|
+
const verification = String(args.verification || '').trim()
|
|
1962
|
+
const traceId = String(args.traceId || '').trim() || createId('trace:' + agentId + ':' + project)
|
|
1963
|
+
|
|
1964
|
+
// 1. Record trace event
|
|
1965
|
+
const event = {
|
|
1966
|
+
id: createId('trace'),
|
|
1967
|
+
timestamp: new Date().toISOString(),
|
|
1968
|
+
trace_id: traceId,
|
|
1969
|
+
event_type: 'task_outcome',
|
|
1970
|
+
outcome,
|
|
1971
|
+
payload: { task, summary, verification },
|
|
1972
|
+
agent: agentId,
|
|
1973
|
+
host: 'mcp',
|
|
1974
|
+
project,
|
|
1975
|
+
}
|
|
1976
|
+
appendJsonl(tracePath, event)
|
|
1977
|
+
|
|
1978
|
+
// 2. Auto-claim experience reuse
|
|
1979
|
+
const autoClaims = autoClaimExperienceReuse(event)
|
|
1980
|
+
|
|
1981
|
+
// 3. Update agent registry
|
|
1982
|
+
const now = new Date().toISOString()
|
|
1983
|
+
const agents = readJsonl(agentsPath)
|
|
1984
|
+
const agent = agents.find((a) => a.agentId === agentId && a.project === project)
|
|
1985
|
+
if (agent) {
|
|
1986
|
+
agent.lastSeenAt = now
|
|
1987
|
+
agent.taskCount = (agent.taskCount || 0) + 1
|
|
1988
|
+
agent.successCount = (agent.successCount || 0) + (outcome === 'success' ? 1 : 0)
|
|
1989
|
+
agent.failureCount = (agent.failureCount || 0) + (outcome === 'failure' ? 1 : 0)
|
|
1990
|
+
writeJsonl(agentsPath, agents)
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1993
|
+
return {
|
|
1994
|
+
recorded: true,
|
|
1995
|
+
event: { id: event.id, trace_id: traceId, outcome, agent: agentId, project },
|
|
1996
|
+
agentUpdated: !!agent,
|
|
1997
|
+
autoClaims: autoClaims.length > 0 ? autoClaims : undefined,
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
|
|
2002
|
+
async function handleIdeaGenerate(args = {}) {
|
|
2003
|
+
const { goal, context = '', constraints = [], numIdeas = 3 } = args
|
|
2004
|
+
const ideas = []
|
|
2005
|
+
for (let i = 0; i < numIdeas; i++) {
|
|
2006
|
+
ideas.push({
|
|
2007
|
+
id: 'idea_' + Date.now() + '_' + i,
|
|
2008
|
+
title: 'Idea #' + (i + 1) + ': ' + goal,
|
|
2009
|
+
description: [
|
|
2010
|
+
'Goal: ' + goal,
|
|
2011
|
+
context ? 'Context: ' + context : '',
|
|
2012
|
+
constraints.length ? 'Constraints: ' + constraints.join(', ') : '',
|
|
2013
|
+
'Suggestion: Consider prototyping the idea and measuring its impact.'
|
|
2014
|
+
].filter(Boolean).join('\n\n')
|
|
2015
|
+
})
|
|
1508
2016
|
}
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
return { jsonrpc: '2.0', id, result }
|
|
1513
|
-
}
|
|
1514
|
-
|
|
1515
|
-
function jsonRpcError(id, code, message, data) {
|
|
1516
|
-
const error = { code, message }
|
|
1517
|
-
if (data !== undefined) error.data = data
|
|
1518
|
-
return { jsonrpc: '2.0', id, error }
|
|
1519
|
-
}
|
|
1520
|
-
|
|
1521
|
-
function send(message) {
|
|
1522
|
-
if (message) process.stdout.write(`${JSON.stringify(message)}\n`)
|
|
1523
|
-
}
|
|
1524
|
-
|
|
1525
|
-
async function handleMessage(message) {
|
|
1526
|
-
if (!message || typeof message !== 'object' || message.jsonrpc !== '2.0') return
|
|
1527
|
-
if (message.method && message.method.startsWith('notifications/')) return
|
|
1528
|
-
if (message.id === undefined) return
|
|
1529
|
-
|
|
1530
|
-
try {
|
|
1531
|
-
if (message.method === 'initialize') {
|
|
1532
|
-
send(
|
|
1533
|
-
jsonRpcResult(message.id, {
|
|
1534
|
-
protocolVersion: PROTOCOL_VERSION,
|
|
1535
|
-
capabilities: { tools: {} },
|
|
1536
|
-
serverInfo: SERVER_INFO,
|
|
1537
|
-
})
|
|
1538
|
-
)
|
|
1539
|
-
return
|
|
1540
|
-
}
|
|
1541
|
-
if (message.method === 'ping') {
|
|
1542
|
-
send(jsonRpcResult(message.id, {}))
|
|
1543
|
-
return
|
|
1544
|
-
}
|
|
1545
|
-
if (message.method === 'tools/list') {
|
|
1546
|
-
send(jsonRpcResult(message.id, { tools: TOOLS }))
|
|
1547
|
-
return
|
|
1548
|
-
}
|
|
1549
|
-
if (message.method === 'tools/call') {
|
|
1550
|
-
const name = message.params && message.params.name
|
|
1551
|
-
const args = (message.params && message.params.arguments) || {}
|
|
1552
|
-
const result = await callTool(name, args)
|
|
1553
|
-
send(
|
|
1554
|
-
jsonRpcResult(message.id, {
|
|
1555
|
-
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
1556
|
-
isError: false,
|
|
1557
|
-
})
|
|
1558
|
-
)
|
|
1559
|
-
return
|
|
1560
|
-
}
|
|
1561
|
-
send(jsonRpcError(message.id, -32601, `Method not found: ${message.method}`))
|
|
1562
|
-
} catch (error) {
|
|
1563
|
-
send(jsonRpcError(message.id, -32603, error.message || String(error)))
|
|
2017
|
+
return {
|
|
2018
|
+
ideas: ideas,
|
|
2019
|
+
generatedAt: new Date().toISOString()
|
|
1564
2020
|
}
|
|
1565
2021
|
}
|
|
1566
2022
|
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
rl.on('line', (line) => {
|
|
1570
|
-
let message
|
|
1571
|
-
try {
|
|
1572
|
-
message = JSON.parse(line)
|
|
1573
|
-
} catch (_) {
|
|
1574
|
-
return
|
|
1575
|
-
}
|
|
1576
|
-
handleMessage(message).catch((error) => {
|
|
1577
|
-
send(jsonRpcError(message && message.id, -32603, error.message || String(error)))
|
|
1578
|
-
})
|
|
1579
|
-
})
|
|
1580
|
-
|
|
1581
|
-
rl.on('close', () => {
|
|
1582
|
-
if (require.main === module) {
|
|
1583
|
-
process.exit(0)
|
|
1584
|
-
}
|
|
1585
|
-
})
|
|
1586
|
-
|
|
1587
|
-
module.exports = { TOOLS, callTool, handleMessage, readJsonl, appendJsonl, rl }
|
|
2023
|
+
module.exports = { TOOLS, callTool, handleMessage, readJsonl, appendJsonl, rl }
|