@roberttlange/agentlens 0.2.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser.js +154 -20
- package/dist/browser.js.map +1 -1
- package/dist/main.test.js +138 -1
- package/dist/main.test.js.map +1 -1
- package/node_modules/@agentlens/contracts/dist/index.d.ts +109 -0
- package/node_modules/@agentlens/core/dist/__tests__/config.test.js +18 -0
- package/node_modules/@agentlens/core/dist/__tests__/config.test.js.map +1 -1
- package/node_modules/@agentlens/core/dist/__tests__/index.test.js +370 -2
- package/node_modules/@agentlens/core/dist/__tests__/index.test.js.map +1 -1
- package/node_modules/@agentlens/core/dist/config.js +33 -0
- package/node_modules/@agentlens/core/dist/config.js.map +1 -1
- package/node_modules/@agentlens/core/dist/metrics.d.ts +13 -0
- package/node_modules/@agentlens/core/dist/metrics.js +98 -2
- package/node_modules/@agentlens/core/dist/metrics.js.map +1 -1
- package/node_modules/@agentlens/core/dist/sourceProfiles.js +4 -0
- package/node_modules/@agentlens/core/dist/sourceProfiles.js.map +1 -1
- package/node_modules/@agentlens/core/dist/traceIndex.d.ts +22 -1
- package/node_modules/@agentlens/core/dist/traceIndex.js +322 -21
- package/node_modules/@agentlens/core/dist/traceIndex.js.map +1 -1
- package/node_modules/@agentlens/server/dist/activity-cache.d.ts +6 -3
- package/node_modules/@agentlens/server/dist/activity-cache.js +6 -0
- package/node_modules/@agentlens/server/dist/activity-cache.js.map +1 -1
- package/node_modules/@agentlens/server/dist/activity-cache.test.js +86 -0
- package/node_modules/@agentlens/server/dist/activity-cache.test.js.map +1 -1
- package/node_modules/@agentlens/server/dist/activity.d.ts +20 -1
- package/node_modules/@agentlens/server/dist/activity.js +482 -6
- package/node_modules/@agentlens/server/dist/activity.js.map +1 -1
- package/node_modules/@agentlens/server/dist/app.d.ts +4 -2
- package/node_modules/@agentlens/server/dist/app.js +242 -5
- package/node_modules/@agentlens/server/dist/app.js.map +1 -1
- package/node_modules/@agentlens/server/dist/app.test.js +669 -8
- package/node_modules/@agentlens/server/dist/app.test.js.map +1 -1
- package/node_modules/@agentlens/server/dist/web/assets/index-CTFOBaBt.css +1 -0
- package/node_modules/@agentlens/server/dist/web/assets/index-CVf00w06.js +52 -0
- package/node_modules/@agentlens/server/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/node_modules/@agentlens/server/dist/web/assets/index-DjwZvHl6.css +0 -1
- package/node_modules/@agentlens/server/dist/web/assets/index-Ei_qfgA9.js +0 -52
|
@@ -25,6 +25,24 @@ const ACTIVITY_BIN_COUNT = 12;
|
|
|
25
25
|
const ACTIVE_IDLE_GAP_MS = 20 * 60_000;
|
|
26
26
|
const MATERIALIZED_TTL_MS = 5 * 60_000;
|
|
27
27
|
const DIRTY_BATCH_LIMIT = 64;
|
|
28
|
+
const DIRTY_REFRESH_DELAY_MS = 25;
|
|
29
|
+
const WATCH_WRITE_STABILITY_MIN_MS = 35;
|
|
30
|
+
const WATCH_WRITE_STABILITY_MAX_MS = 75;
|
|
31
|
+
const WATCH_WRITE_POLL_INTERVAL_MS = 20;
|
|
32
|
+
const STARTUP_RECENT_TRACE_LIMIT = 120;
|
|
33
|
+
const BACKGROUND_HYDRATE_BATCH_SIZE = 25;
|
|
34
|
+
const BACKGROUND_HYDRATE_DELAY_MS = 25;
|
|
35
|
+
function createInitialStartupStatus() {
|
|
36
|
+
return {
|
|
37
|
+
phase: "cold",
|
|
38
|
+
inspectorReady: false,
|
|
39
|
+
fullReady: false,
|
|
40
|
+
isPartial: false,
|
|
41
|
+
discoveredTraceCount: 0,
|
|
42
|
+
hydratedTraceCount: 0,
|
|
43
|
+
startupError: "",
|
|
44
|
+
};
|
|
45
|
+
}
|
|
28
46
|
function emptyEventKindCounts() {
|
|
29
47
|
return {
|
|
30
48
|
system: 0,
|
|
@@ -74,6 +92,21 @@ function buildSessionActivityArtifacts(events) {
|
|
|
74
92
|
activeSegments: buildActiveSegmentsFromEventTimestamps(eventTimestamps),
|
|
75
93
|
};
|
|
76
94
|
}
|
|
95
|
+
function buildSessionUsageArtifacts(events, agent, config) {
|
|
96
|
+
return {
|
|
97
|
+
eventCount: events.length,
|
|
98
|
+
usagePoints: deriveSessionMetrics(events, agent, config).usagePoints,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function compactActivityArtifacts(artifacts) {
|
|
102
|
+
if (artifacts.eventTimestamps.length === 0)
|
|
103
|
+
return artifacts;
|
|
104
|
+
return {
|
|
105
|
+
eventCount: artifacts.eventCount,
|
|
106
|
+
eventTimestamps: [],
|
|
107
|
+
activeSegments: artifacts.activeSegments,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
77
110
|
function normalizeActivityCounts(counts) {
|
|
78
111
|
let maxCount = 0;
|
|
79
112
|
for (const count of counts) {
|
|
@@ -479,6 +512,7 @@ export class TraceIndex extends EventEmitter {
|
|
|
479
512
|
config;
|
|
480
513
|
entries = new Map();
|
|
481
514
|
pathToId = new Map();
|
|
515
|
+
discoveredFilesById = new Map();
|
|
482
516
|
cursorById = new Map();
|
|
483
517
|
watcher = null;
|
|
484
518
|
timer = null;
|
|
@@ -489,6 +523,11 @@ export class TraceIndex extends EventEmitter {
|
|
|
489
523
|
queuedForceFullRefresh = false;
|
|
490
524
|
dirtyPaths = new Set();
|
|
491
525
|
forceReparsePaths = new Set();
|
|
526
|
+
pendingHydrationFiles = [];
|
|
527
|
+
pendingHydrationIds = new Set();
|
|
528
|
+
pendingHydrationPaths = new Set();
|
|
529
|
+
pendingHydrationCursor = 0;
|
|
530
|
+
startupStatus = createInitialStartupStatus();
|
|
492
531
|
adaptiveIntervalMs;
|
|
493
532
|
perf = {
|
|
494
533
|
refreshCount: 0,
|
|
@@ -532,8 +571,43 @@ export class TraceIndex extends EventEmitter {
|
|
|
532
571
|
}
|
|
533
572
|
async start() {
|
|
534
573
|
this.started = true;
|
|
535
|
-
|
|
574
|
+
this.startupStatus = {
|
|
575
|
+
phase: "bootstrapping",
|
|
576
|
+
inspectorReady: false,
|
|
577
|
+
fullReady: false,
|
|
578
|
+
isPartial: false,
|
|
579
|
+
discoveredTraceCount: 0,
|
|
580
|
+
hydratedTraceCount: 0,
|
|
581
|
+
startupError: "",
|
|
582
|
+
};
|
|
583
|
+
this.pendingHydrationFiles = [];
|
|
584
|
+
this.pendingHydrationIds.clear();
|
|
585
|
+
this.pendingHydrationPaths.clear();
|
|
586
|
+
this.pendingHydrationCursor = 0;
|
|
536
587
|
await this.restartWatcher();
|
|
588
|
+
if (!this.started) {
|
|
589
|
+
if (this.watcher) {
|
|
590
|
+
await this.watcher.close();
|
|
591
|
+
this.watcher = null;
|
|
592
|
+
}
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
try {
|
|
596
|
+
await this.bootstrapRecentTraces();
|
|
597
|
+
}
|
|
598
|
+
catch (error) {
|
|
599
|
+
this.startupStatus = {
|
|
600
|
+
...this.startupStatus,
|
|
601
|
+
phase: "failed",
|
|
602
|
+
inspectorReady: false,
|
|
603
|
+
fullReady: false,
|
|
604
|
+
isPartial: false,
|
|
605
|
+
startupError: error instanceof Error ? error.message : String(error),
|
|
606
|
+
};
|
|
607
|
+
throw error;
|
|
608
|
+
}
|
|
609
|
+
if (!this.started)
|
|
610
|
+
return;
|
|
537
611
|
this.scheduleNextRefresh(this.computeBaseIntervalMs());
|
|
538
612
|
}
|
|
539
613
|
stop() {
|
|
@@ -547,6 +621,11 @@ export class TraceIndex extends EventEmitter {
|
|
|
547
621
|
this.watcher = null;
|
|
548
622
|
}
|
|
549
623
|
this.forceReparsePaths.clear();
|
|
624
|
+
this.pendingHydrationFiles = [];
|
|
625
|
+
this.pendingHydrationIds.clear();
|
|
626
|
+
this.pendingHydrationPaths.clear();
|
|
627
|
+
this.pendingHydrationCursor = 0;
|
|
628
|
+
this.discoveredFilesById.clear();
|
|
550
629
|
}
|
|
551
630
|
async refresh() {
|
|
552
631
|
this.queuedForceFullRefresh = true;
|
|
@@ -564,6 +643,40 @@ export class TraceIndex extends EventEmitter {
|
|
|
564
643
|
getStreamVersion() {
|
|
565
644
|
return this.streamVersion;
|
|
566
645
|
}
|
|
646
|
+
getStartupStatus() {
|
|
647
|
+
return { ...this.startupStatus };
|
|
648
|
+
}
|
|
649
|
+
getStartupState() {
|
|
650
|
+
return this.getStartupStatus();
|
|
651
|
+
}
|
|
652
|
+
getHydrationProgress(windowStartMs) {
|
|
653
|
+
if (!this.startupStatus.inspectorReady) {
|
|
654
|
+
return {
|
|
655
|
+
ready: false,
|
|
656
|
+
relevantDiscoveredCount: 0,
|
|
657
|
+
relevantHydratedCount: 0,
|
|
658
|
+
percent: 0,
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
let relevantDiscoveredCount = 0;
|
|
662
|
+
let relevantHydratedCount = 0;
|
|
663
|
+
for (const file of this.discoveredFilesById.values()) {
|
|
664
|
+
if (file.mtimeMs < windowStartMs)
|
|
665
|
+
continue;
|
|
666
|
+
relevantDiscoveredCount += 1;
|
|
667
|
+
if (!this.pendingHydrationIds.has(file.id)) {
|
|
668
|
+
relevantHydratedCount += 1;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
const ready = relevantHydratedCount >= relevantDiscoveredCount;
|
|
672
|
+
const percent = relevantDiscoveredCount === 0 ? 100 : Math.round((relevantHydratedCount / relevantDiscoveredCount) * 100);
|
|
673
|
+
return {
|
|
674
|
+
ready,
|
|
675
|
+
relevantDiscoveredCount,
|
|
676
|
+
relevantHydratedCount,
|
|
677
|
+
percent,
|
|
678
|
+
};
|
|
679
|
+
}
|
|
567
680
|
getTopTools(limit = 12) {
|
|
568
681
|
const counts = new Map();
|
|
569
682
|
for (const entry of this.entries.values()) {
|
|
@@ -576,6 +689,91 @@ export class TraceIndex extends EventEmitter {
|
|
|
576
689
|
.slice(0, Math.max(1, limit))
|
|
577
690
|
.map(([name, count]) => ({ name, count }));
|
|
578
691
|
}
|
|
692
|
+
async bootstrapRecentTraces() {
|
|
693
|
+
const bootstrapNowMs = nowMs();
|
|
694
|
+
const files = await discoverTraceFiles(this.config);
|
|
695
|
+
this.lastFullRefreshAtMs = bootstrapNowMs;
|
|
696
|
+
this.forceReparsePaths.clear();
|
|
697
|
+
const nextPathToId = new Map();
|
|
698
|
+
const discoveredFilesById = new Map();
|
|
699
|
+
for (const file of files) {
|
|
700
|
+
nextPathToId.set(file.path, file.id);
|
|
701
|
+
discoveredFilesById.set(file.id, file);
|
|
702
|
+
}
|
|
703
|
+
this.pathToId = nextPathToId;
|
|
704
|
+
this.discoveredFilesById = discoveredFilesById;
|
|
705
|
+
const bootstrapFiles = files.slice(0, STARTUP_RECENT_TRACE_LIMIT);
|
|
706
|
+
const pendingFiles = files.slice(bootstrapFiles.length);
|
|
707
|
+
this.pendingHydrationFiles = pendingFiles;
|
|
708
|
+
this.pendingHydrationIds = new Set(pendingFiles.map((file) => file.id));
|
|
709
|
+
this.pendingHydrationPaths = new Set(pendingFiles.map((file) => file.path));
|
|
710
|
+
this.pendingHydrationCursor = 0;
|
|
711
|
+
this.startupStatus = {
|
|
712
|
+
phase: bootstrapFiles.length > 0 ? "bootstrapping" : pendingFiles.length > 0 ? "hydrating" : "ready",
|
|
713
|
+
inspectorReady: false,
|
|
714
|
+
fullReady: pendingFiles.length === 0,
|
|
715
|
+
isPartial: pendingFiles.length > 0,
|
|
716
|
+
discoveredTraceCount: files.length,
|
|
717
|
+
hydratedTraceCount: 0,
|
|
718
|
+
startupError: "",
|
|
719
|
+
};
|
|
720
|
+
const stats = {
|
|
721
|
+
parsedFileCount: 0,
|
|
722
|
+
dirtyPathCount: 0,
|
|
723
|
+
usedFullRefresh: false,
|
|
724
|
+
hadFileMutations: false,
|
|
725
|
+
};
|
|
726
|
+
for (const file of bootstrapFiles) {
|
|
727
|
+
const changed = await this.upsertFile(file, bootstrapNowMs);
|
|
728
|
+
if (changed) {
|
|
729
|
+
stats.parsedFileCount += 1;
|
|
730
|
+
stats.hadFileMutations = true;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
this.startupStatus = {
|
|
734
|
+
...this.startupStatus,
|
|
735
|
+
phase: pendingFiles.length > 0 ? "hydrating" : "ready",
|
|
736
|
+
inspectorReady: true,
|
|
737
|
+
fullReady: pendingFiles.length === 0,
|
|
738
|
+
isPartial: pendingFiles.length > 0,
|
|
739
|
+
hydratedTraceCount: this.entries.size,
|
|
740
|
+
};
|
|
741
|
+
this.finishMutationBatch(bootstrapNowMs, stats);
|
|
742
|
+
}
|
|
743
|
+
finishMutationBatch(refreshNowMs, stats) {
|
|
744
|
+
this.applyRetention(refreshNowMs);
|
|
745
|
+
this.refreshActivityStatus(refreshNowMs, stats);
|
|
746
|
+
this.perf.trackedFiles = this.entries.size;
|
|
747
|
+
this.perf.dirtyPathQueue = this.dirtyPaths.size;
|
|
748
|
+
const retentionStats = this.buildRetentionStats();
|
|
749
|
+
this.perf.hotTraces = retentionStats.hotTraces;
|
|
750
|
+
this.perf.warmTraces = retentionStats.warmTraces;
|
|
751
|
+
this.perf.coldTraces = retentionStats.coldTraces;
|
|
752
|
+
this.perf.materializedTraces = retentionStats.materializedTraces;
|
|
753
|
+
this.emitOverviewUpdated();
|
|
754
|
+
}
|
|
755
|
+
emitOverviewUpdated() {
|
|
756
|
+
this.emitStream("overview_updated", {
|
|
757
|
+
overview: this.getOverview(),
|
|
758
|
+
startup: this.getStartupStatus(),
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
hasPendingHydrationWork() {
|
|
762
|
+
return this.pendingHydrationIds.size > 0;
|
|
763
|
+
}
|
|
764
|
+
updateStartupProgress() {
|
|
765
|
+
const remainingCount = this.pendingHydrationIds.size;
|
|
766
|
+
const discoveredTraceCount = this.startupStatus.discoveredTraceCount;
|
|
767
|
+
this.startupStatus = {
|
|
768
|
+
...this.startupStatus,
|
|
769
|
+
phase: this.startupStatus.startupError ? "failed" : remainingCount > 0 ? "hydrating" : "ready",
|
|
770
|
+
inspectorReady: true,
|
|
771
|
+
fullReady: remainingCount === 0,
|
|
772
|
+
isPartial: remainingCount > 0,
|
|
773
|
+
discoveredTraceCount,
|
|
774
|
+
hydratedTraceCount: Math.max(0, discoveredTraceCount - remainingCount),
|
|
775
|
+
};
|
|
776
|
+
}
|
|
579
777
|
buildRetentionStats() {
|
|
580
778
|
let hotTraces = 0;
|
|
581
779
|
let warmTraces = 0;
|
|
@@ -625,13 +823,14 @@ export class TraceIndex extends EventEmitter {
|
|
|
625
823
|
if (roots.length === 0)
|
|
626
824
|
return;
|
|
627
825
|
const debounceMs = Math.max(50, this.config.scan.batchDebounceMs);
|
|
826
|
+
const writeStabilityMs = Math.max(WATCH_WRITE_STABILITY_MIN_MS, Math.min(WATCH_WRITE_STABILITY_MAX_MS, debounceMs));
|
|
628
827
|
this.watcher = chokidar.watch(roots, {
|
|
629
828
|
ignoreInitial: true,
|
|
630
829
|
persistent: true,
|
|
631
830
|
followSymlinks: false,
|
|
632
831
|
awaitWriteFinish: {
|
|
633
|
-
stabilityThreshold:
|
|
634
|
-
pollInterval:
|
|
832
|
+
stabilityThreshold: writeStabilityMs,
|
|
833
|
+
pollInterval: WATCH_WRITE_POLL_INTERVAL_MS,
|
|
635
834
|
},
|
|
636
835
|
});
|
|
637
836
|
const onDirty = (rawPath) => {
|
|
@@ -651,7 +850,7 @@ export class TraceIndex extends EventEmitter {
|
|
|
651
850
|
}
|
|
652
851
|
}
|
|
653
852
|
this.perf.dirtyPathQueue = this.dirtyPaths.size;
|
|
654
|
-
this.scheduleNextRefresh(
|
|
853
|
+
this.scheduleNextRefresh(DIRTY_REFRESH_DELAY_MS);
|
|
655
854
|
};
|
|
656
855
|
this.watcher.on("add", onDirty);
|
|
657
856
|
this.watcher.on("change", onDirty);
|
|
@@ -752,6 +951,8 @@ export class TraceIndex extends EventEmitter {
|
|
|
752
951
|
return Array.from(matches.values());
|
|
753
952
|
}
|
|
754
953
|
shouldRunFullRefresh(nowMsValue) {
|
|
954
|
+
if (this.hasPendingHydrationWork())
|
|
955
|
+
return false;
|
|
755
956
|
if (this.lastFullRefreshAtMs <= 0)
|
|
756
957
|
return true;
|
|
757
958
|
const fullIntervalMs = Math.max(1_000, this.config.scan.fullRescanIntervalMs);
|
|
@@ -781,6 +982,9 @@ export class TraceIndex extends EventEmitter {
|
|
|
781
982
|
if (this.refreshPending || this.queuedForceFullRefresh) {
|
|
782
983
|
this.scheduleNextRefresh(25);
|
|
783
984
|
}
|
|
985
|
+
else if (this.hasPendingHydrationWork()) {
|
|
986
|
+
this.scheduleNextRefresh(BACKGROUND_HYDRATE_DELAY_MS);
|
|
987
|
+
}
|
|
784
988
|
else if (this.started) {
|
|
785
989
|
this.scheduleNextRefresh();
|
|
786
990
|
}
|
|
@@ -822,31 +1026,45 @@ export class TraceIndex extends EventEmitter {
|
|
|
822
1026
|
if (useFullRefresh) {
|
|
823
1027
|
await this.refreshFull(refreshNowMs, stats);
|
|
824
1028
|
}
|
|
825
|
-
else {
|
|
1029
|
+
else if (this.dirtyPaths.size > 0) {
|
|
826
1030
|
await this.refreshDirty(refreshNowMs, stats);
|
|
827
1031
|
}
|
|
828
|
-
this.
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
this.
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
this.
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
1032
|
+
else if (this.hasPendingHydrationWork()) {
|
|
1033
|
+
await this.hydratePendingBatch(refreshNowMs, stats);
|
|
1034
|
+
}
|
|
1035
|
+
if (this.startupStatus.inspectorReady && this.hasPendingHydrationWork()) {
|
|
1036
|
+
this.updateStartupProgress();
|
|
1037
|
+
}
|
|
1038
|
+
else if (this.startupStatus.inspectorReady && !this.startupStatus.fullReady) {
|
|
1039
|
+
this.startupStatus = {
|
|
1040
|
+
...this.startupStatus,
|
|
1041
|
+
phase: "ready",
|
|
1042
|
+
fullReady: true,
|
|
1043
|
+
isPartial: false,
|
|
1044
|
+
hydratedTraceCount: this.startupStatus.discoveredTraceCount,
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
this.finishMutationBatch(refreshNowMs, stats);
|
|
838
1048
|
return stats;
|
|
839
1049
|
}
|
|
840
1050
|
async refreshFull(refreshNowMs, stats) {
|
|
841
1051
|
const files = await discoverTraceFiles(this.config);
|
|
842
1052
|
this.lastFullRefreshAtMs = refreshNowMs;
|
|
843
1053
|
this.forceReparsePaths.clear();
|
|
1054
|
+
this.pendingHydrationFiles = [];
|
|
1055
|
+
this.pendingHydrationIds.clear();
|
|
1056
|
+
this.pendingHydrationPaths.clear();
|
|
1057
|
+
this.pendingHydrationCursor = 0;
|
|
1058
|
+
this.discoveredFilesById.clear();
|
|
844
1059
|
const nextIds = new Set(files.map((file) => file.id));
|
|
845
1060
|
const nextPathToId = new Map();
|
|
1061
|
+
const discoveredFilesById = new Map();
|
|
846
1062
|
for (const file of files) {
|
|
847
1063
|
nextPathToId.set(file.path, file.id);
|
|
1064
|
+
discoveredFilesById.set(file.id, file);
|
|
848
1065
|
}
|
|
849
1066
|
this.pathToId = nextPathToId;
|
|
1067
|
+
this.discoveredFilesById = discoveredFilesById;
|
|
850
1068
|
for (const existingId of this.entries.keys()) {
|
|
851
1069
|
if (!nextIds.has(existingId)) {
|
|
852
1070
|
this.entries.delete(existingId);
|
|
@@ -861,7 +1079,17 @@ export class TraceIndex extends EventEmitter {
|
|
|
861
1079
|
stats.parsedFileCount += 1;
|
|
862
1080
|
stats.hadFileMutations = true;
|
|
863
1081
|
}
|
|
1082
|
+
this.applyRetention(refreshNowMs);
|
|
864
1083
|
}
|
|
1084
|
+
this.startupStatus = {
|
|
1085
|
+
phase: "ready",
|
|
1086
|
+
inspectorReady: true,
|
|
1087
|
+
fullReady: true,
|
|
1088
|
+
isPartial: false,
|
|
1089
|
+
discoveredTraceCount: files.length,
|
|
1090
|
+
hydratedTraceCount: files.length,
|
|
1091
|
+
startupError: "",
|
|
1092
|
+
};
|
|
865
1093
|
}
|
|
866
1094
|
async refreshDirty(refreshNowMs, stats) {
|
|
867
1095
|
if (this.dirtyPaths.size === 0)
|
|
@@ -893,14 +1121,20 @@ export class TraceIndex extends EventEmitter {
|
|
|
893
1121
|
this.emitStream("trace_removed", { id: existingId });
|
|
894
1122
|
stats.hadFileMutations = true;
|
|
895
1123
|
}
|
|
1124
|
+
this.pendingHydrationPaths.delete(normalizedPath);
|
|
1125
|
+
this.pendingHydrationIds.delete(existingId ?? "");
|
|
1126
|
+
if (existingId)
|
|
1127
|
+
this.discoveredFilesById.delete(existingId);
|
|
896
1128
|
this.pathToId.delete(normalizedPath);
|
|
897
1129
|
continue;
|
|
898
1130
|
}
|
|
899
1131
|
this.pathToId.set(file.path, file.id);
|
|
1132
|
+
this.discoveredFilesById.set(file.id, file);
|
|
900
1133
|
if (existingId && existingId !== file.id && this.entries.delete(existingId)) {
|
|
901
1134
|
this.cursorById.delete(existingId);
|
|
902
1135
|
this.emitStream("trace_removed", { id: existingId });
|
|
903
1136
|
stats.hadFileMutations = true;
|
|
1137
|
+
this.discoveredFilesById.delete(existingId);
|
|
904
1138
|
}
|
|
905
1139
|
if (processedIds.has(file.id)) {
|
|
906
1140
|
if (!dirtyEntry.forceReparse)
|
|
@@ -914,6 +1148,33 @@ export class TraceIndex extends EventEmitter {
|
|
|
914
1148
|
stats.parsedFileCount += 1;
|
|
915
1149
|
stats.hadFileMutations = true;
|
|
916
1150
|
}
|
|
1151
|
+
this.pendingHydrationIds.delete(file.id);
|
|
1152
|
+
this.pendingHydrationPaths.delete(file.path);
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
async hydratePendingBatch(refreshNowMs, stats) {
|
|
1156
|
+
if (!this.hasPendingHydrationWork())
|
|
1157
|
+
return;
|
|
1158
|
+
let processedCount = 0;
|
|
1159
|
+
while (this.pendingHydrationCursor < this.pendingHydrationFiles.length && processedCount < BACKGROUND_HYDRATE_BATCH_SIZE) {
|
|
1160
|
+
const file = this.pendingHydrationFiles[this.pendingHydrationCursor];
|
|
1161
|
+
this.pendingHydrationCursor += 1;
|
|
1162
|
+
if (!file)
|
|
1163
|
+
continue;
|
|
1164
|
+
if (!this.pendingHydrationIds.has(file.id) || !this.pendingHydrationPaths.has(file.path))
|
|
1165
|
+
continue;
|
|
1166
|
+
this.pendingHydrationIds.delete(file.id);
|
|
1167
|
+
this.pendingHydrationPaths.delete(file.path);
|
|
1168
|
+
const changed = await this.upsertFile(file, refreshNowMs);
|
|
1169
|
+
if (changed) {
|
|
1170
|
+
stats.parsedFileCount += 1;
|
|
1171
|
+
stats.hadFileMutations = true;
|
|
1172
|
+
}
|
|
1173
|
+
processedCount += 1;
|
|
1174
|
+
}
|
|
1175
|
+
if (this.pendingHydrationCursor >= this.pendingHydrationFiles.length) {
|
|
1176
|
+
this.pendingHydrationFiles = [];
|
|
1177
|
+
this.pendingHydrationCursor = 0;
|
|
917
1178
|
}
|
|
918
1179
|
}
|
|
919
1180
|
inferSourceMetadata(filePath) {
|
|
@@ -1034,6 +1295,7 @@ export class TraceIndex extends EventEmitter {
|
|
|
1034
1295
|
const parsed = await this.parserRegistry.parseFile(file);
|
|
1035
1296
|
const summary = summarize(file, parsed.agent, parsed.parser, parsed.sessionId, parsed.events, parsed.parseError, this.config, refreshNowMs);
|
|
1036
1297
|
const redactedEvents = redactEvents(parsed.events, this.config.redaction);
|
|
1298
|
+
const activityArtifacts = buildSessionActivityArtifacts(redactedEvents);
|
|
1037
1299
|
const previousCount = current?.summary.eventCount ?? 0;
|
|
1038
1300
|
this.entries.set(file.id, {
|
|
1039
1301
|
file,
|
|
@@ -1041,7 +1303,8 @@ export class TraceIndex extends EventEmitter {
|
|
|
1041
1303
|
residentEvents: redactedEvents,
|
|
1042
1304
|
cachedFullEvents: redactedEvents,
|
|
1043
1305
|
cachedRawEvents: parsed.events,
|
|
1044
|
-
activityArtifacts
|
|
1306
|
+
activityArtifacts,
|
|
1307
|
+
usageArtifacts: buildSessionUsageArtifacts(parsed.events, parsed.agent, this.config),
|
|
1045
1308
|
pinnedMaterializedAtMs: current?.pinnedMaterializedAtMs ?? 0,
|
|
1046
1309
|
});
|
|
1047
1310
|
this.cursorById.set(file.id, {
|
|
@@ -1071,6 +1334,7 @@ export class TraceIndex extends EventEmitter {
|
|
|
1071
1334
|
cachedFullEvents: [],
|
|
1072
1335
|
cachedRawEvents: [],
|
|
1073
1336
|
activityArtifacts: null,
|
|
1337
|
+
usageArtifacts: null,
|
|
1074
1338
|
pinnedMaterializedAtMs: 0,
|
|
1075
1339
|
});
|
|
1076
1340
|
this.cursorById.set(file.id, {
|
|
@@ -1141,6 +1405,7 @@ export class TraceIndex extends EventEmitter {
|
|
|
1141
1405
|
const rebasedRawEvents = rebaseEvents(parsed.events);
|
|
1142
1406
|
const mergedEvents = current.cachedFullEvents.concat(rebasedEvents);
|
|
1143
1407
|
const mergedRawEvents = current.cachedRawEvents.concat(rebasedRawEvents);
|
|
1408
|
+
const activityArtifacts = buildSessionActivityArtifacts(mergedEvents);
|
|
1144
1409
|
const mergedSummary = summarize(file, current.summary.agent, current.summary.parser, current.summary.sessionId || parsed.sessionId, mergedRawEvents, "", this.config, refreshNowMs);
|
|
1145
1410
|
const summary = {
|
|
1146
1411
|
...mergedSummary,
|
|
@@ -1154,7 +1419,8 @@ export class TraceIndex extends EventEmitter {
|
|
|
1154
1419
|
residentEvents: mergedEvents,
|
|
1155
1420
|
cachedFullEvents: mergedEvents,
|
|
1156
1421
|
cachedRawEvents: mergedRawEvents,
|
|
1157
|
-
activityArtifacts
|
|
1422
|
+
activityArtifacts,
|
|
1423
|
+
usageArtifacts: buildSessionUsageArtifacts(mergedRawEvents, current.summary.agent, this.config),
|
|
1158
1424
|
pinnedMaterializedAtMs: current.pinnedMaterializedAtMs,
|
|
1159
1425
|
});
|
|
1160
1426
|
this.cursorById.set(file.id, {
|
|
@@ -1214,6 +1480,9 @@ export class TraceIndex extends EventEmitter {
|
|
|
1214
1480
|
entry.cachedFullEvents = null;
|
|
1215
1481
|
entry.cachedRawEvents = null;
|
|
1216
1482
|
}
|
|
1483
|
+
if (this.config.retention.strategy !== "full_memory" && tier !== "hot" && entry.activityArtifacts) {
|
|
1484
|
+
entry.activityArtifacts = compactActivityArtifacts(entry.activityArtifacts);
|
|
1485
|
+
}
|
|
1217
1486
|
if (this.config.retention.strategy === "full_memory" && !entry.cachedFullEvents) {
|
|
1218
1487
|
entry.cachedFullEvents = sourceEvents;
|
|
1219
1488
|
}
|
|
@@ -1299,7 +1568,8 @@ export class TraceIndex extends EventEmitter {
|
|
|
1299
1568
|
const refreshedSummary = summarize(entry.file, parsed.agent, parsed.parser, parsed.sessionId, parsed.events, parsed.parseError, this.config, nowMs());
|
|
1300
1569
|
entry.cachedFullEvents = redactedEvents;
|
|
1301
1570
|
entry.cachedRawEvents = parsed.events;
|
|
1302
|
-
entry.activityArtifacts =
|
|
1571
|
+
entry.activityArtifacts = buildSessionActivityArtifacts(redactedEvents);
|
|
1572
|
+
entry.usageArtifacts = buildSessionUsageArtifacts(parsed.events, parsed.agent, this.config);
|
|
1303
1573
|
entry.pinnedMaterializedAtMs = nowMs();
|
|
1304
1574
|
entry.summary = {
|
|
1305
1575
|
...refreshedSummary,
|
|
@@ -1327,9 +1597,40 @@ export class TraceIndex extends EventEmitter {
|
|
|
1327
1597
|
if (found.activityArtifacts && found.activityArtifacts.eventCount === found.summary.eventCount) {
|
|
1328
1598
|
return found.activityArtifacts;
|
|
1329
1599
|
}
|
|
1330
|
-
const
|
|
1331
|
-
|
|
1332
|
-
|
|
1600
|
+
const artifacts = (() => {
|
|
1601
|
+
if (found.cachedFullEvents && found.cachedFullEvents.length >= found.summary.eventCount) {
|
|
1602
|
+
return buildSessionActivityArtifacts(found.cachedFullEvents);
|
|
1603
|
+
}
|
|
1604
|
+
if (found.residentEvents.length >= found.summary.eventCount) {
|
|
1605
|
+
return buildSessionActivityArtifacts(found.residentEvents);
|
|
1606
|
+
}
|
|
1607
|
+
const parsed = this.parserRegistry.parseFileSync(found.file, found.summary.parser);
|
|
1608
|
+
const redactedEvents = redactEvents(parsed.events, this.config.redaction);
|
|
1609
|
+
return buildSessionActivityArtifacts(redactedEvents);
|
|
1610
|
+
})();
|
|
1611
|
+
if (this.config.retention.strategy === "full_memory" || found.summary.residentTier === "hot") {
|
|
1612
|
+
found.activityArtifacts = artifacts;
|
|
1613
|
+
}
|
|
1614
|
+
return artifacts;
|
|
1615
|
+
}
|
|
1616
|
+
getSessionUsageArtifacts(id) {
|
|
1617
|
+
const found = this.entries.get(id);
|
|
1618
|
+
if (!found) {
|
|
1619
|
+
throw new Error(`unknown trace id: ${id}`);
|
|
1620
|
+
}
|
|
1621
|
+
if (found.usageArtifacts && found.usageArtifacts.eventCount === found.summary.eventCount) {
|
|
1622
|
+
return found.usageArtifacts;
|
|
1623
|
+
}
|
|
1624
|
+
const artifacts = (() => {
|
|
1625
|
+
if (found.cachedRawEvents && found.cachedRawEvents.length >= found.summary.eventCount) {
|
|
1626
|
+
return buildSessionUsageArtifacts(found.cachedRawEvents, found.summary.agent, this.config);
|
|
1627
|
+
}
|
|
1628
|
+
const parsed = this.parserRegistry.parseFileSync(found.file, found.summary.parser);
|
|
1629
|
+
return buildSessionUsageArtifacts(parsed.events, parsed.agent, this.config);
|
|
1630
|
+
})();
|
|
1631
|
+
if (this.config.retention.strategy === "full_memory" || found.summary.residentTier === "hot") {
|
|
1632
|
+
found.usageArtifacts = artifacts;
|
|
1633
|
+
}
|
|
1333
1634
|
return artifacts;
|
|
1334
1635
|
}
|
|
1335
1636
|
getTracePage(id, options = {}) {
|