@roberttlange/agentlens 0.2.2 → 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 +120 -0
- package/node_modules/@agentlens/core/dist/__tests__/config.test.js +67 -2
- package/node_modules/@agentlens/core/dist/__tests__/config.test.js.map +1 -1
- package/node_modules/@agentlens/core/dist/__tests__/index.test.js +590 -2
- package/node_modules/@agentlens/core/dist/__tests__/index.test.js.map +1 -1
- package/node_modules/@agentlens/core/dist/config.js +95 -5
- package/node_modules/@agentlens/core/dist/config.js.map +1 -1
- package/node_modules/@agentlens/core/dist/generatedPricing.d.ts +3 -0
- package/node_modules/@agentlens/core/dist/generatedPricing.js +131 -0
- package/node_modules/@agentlens/core/dist/generatedPricing.js.map +1 -0
- package/node_modules/@agentlens/core/dist/metrics.d.ts +13 -0
- package/node_modules/@agentlens/core/dist/metrics.js +227 -54
- package/node_modules/@agentlens/core/dist/metrics.js.map +1 -1
- package/node_modules/@agentlens/core/dist/pricing.d.ts +15 -0
- package/node_modules/@agentlens/core/dist/pricing.js +133 -0
- package/node_modules/@agentlens/core/dist/pricing.js.map +1 -0
- package/node_modules/@agentlens/core/dist/pricing.test.d.ts +1 -0
- package/node_modules/@agentlens/core/dist/pricing.test.js +109 -0
- package/node_modules/@agentlens/core/dist/pricing.test.js.map +1 -0
- package/node_modules/@agentlens/core/dist/sourceProfiles.js +7 -67
- package/node_modules/@agentlens/core/dist/sourceProfiles.js.map +1 -1
- package/node_modules/@agentlens/core/dist/traceIndex.d.ts +34 -1
- package/node_modules/@agentlens/core/dist/traceIndex.js +374 -15
- package/node_modules/@agentlens/core/dist/traceIndex.js.map +1 -1
- package/node_modules/@agentlens/server/dist/activity-cache.d.ts +32 -0
- package/node_modules/@agentlens/server/dist/activity-cache.js +63 -0
- package/node_modules/@agentlens/server/dist/activity-cache.js.map +1 -0
- package/node_modules/@agentlens/server/dist/activity-cache.test.d.ts +1 -0
- package/node_modules/@agentlens/server/dist/activity-cache.test.js +170 -0
- package/node_modules/@agentlens/server/dist/activity-cache.test.js.map +1 -0
- package/node_modules/@agentlens/server/dist/activity.d.ts +31 -1
- package/node_modules/@agentlens/server/dist/activity.js +532 -34
- 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 +248 -5
- package/node_modules/@agentlens/server/dist/app.js.map +1 -1
- package/node_modules/@agentlens/server/dist/app.test.js +670 -9
- 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-Ci8okH8M.js +0 -52
- package/node_modules/@agentlens/server/dist/web/assets/index-Cj3kmsFf.css +0 -1
|
@@ -22,8 +22,27 @@ const EVENT_KIND_KEYS = [
|
|
|
22
22
|
const WAITING_INPUT_PATTERN = /\b(?:await(?:ing)?\s+(?:user|input)|waiting\s+for\s+(?:user|input|approval)|user\s+input\s+required|needs?\s+user\s+input|permission\s+required|approval\s+required|confirm(?:ation)?\s+(?:required|needed)|press\s+enter\s+to\s+continue)\b/i;
|
|
23
23
|
const WAITING_PROMPT_PATTERN = /\b(?:do\s+you\s+want(?:\s+me)?|would\s+you\s+like(?:\s+me)?|should\s+i\b|can\s+you\s+confirm|please\s+confirm|let\s+me\s+know\s+if\s+you(?:'d)?\s+like|which\s+(?:option|approach)|choose\s+(?:one|an?\s+option)|pick\s+(?:one|an?\s+option)|approve(?:\s+this)?|permission\s+to)\b/i;
|
|
24
24
|
const ACTIVITY_BIN_COUNT = 12;
|
|
25
|
+
const ACTIVE_IDLE_GAP_MS = 20 * 60_000;
|
|
25
26
|
const MATERIALIZED_TTL_MS = 5 * 60_000;
|
|
26
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
|
+
}
|
|
27
46
|
function emptyEventKindCounts() {
|
|
28
47
|
return {
|
|
29
48
|
system: 0,
|
|
@@ -36,6 +55,58 @@ function emptyEventKindCounts() {
|
|
|
36
55
|
meta: 0,
|
|
37
56
|
};
|
|
38
57
|
}
|
|
58
|
+
function collectTimestampMs(events) {
|
|
59
|
+
const timestamps = [];
|
|
60
|
+
for (const event of events) {
|
|
61
|
+
const timestampMs = event.timestampMs;
|
|
62
|
+
if (timestampMs === null || !Number.isFinite(timestampMs) || timestampMs <= 0)
|
|
63
|
+
continue;
|
|
64
|
+
timestamps.push(timestampMs);
|
|
65
|
+
}
|
|
66
|
+
timestamps.sort((left, right) => left - right);
|
|
67
|
+
return timestamps;
|
|
68
|
+
}
|
|
69
|
+
function buildActiveSegmentsFromEventTimestamps(eventTimestamps) {
|
|
70
|
+
if (eventTimestamps.length === 0)
|
|
71
|
+
return [];
|
|
72
|
+
const segments = [];
|
|
73
|
+
let segmentStartMs = eventTimestamps[0] ?? 0;
|
|
74
|
+
let previousTsMs = segmentStartMs;
|
|
75
|
+
for (let index = 1; index < eventTimestamps.length; index += 1) {
|
|
76
|
+
const nextTsMs = eventTimestamps[index] ?? previousTsMs;
|
|
77
|
+
const gapMs = Math.max(0, nextTsMs - previousTsMs);
|
|
78
|
+
if (gapMs > ACTIVE_IDLE_GAP_MS) {
|
|
79
|
+
segments.push({ startMs: segmentStartMs, endMs: previousTsMs });
|
|
80
|
+
segmentStartMs = nextTsMs;
|
|
81
|
+
}
|
|
82
|
+
previousTsMs = nextTsMs;
|
|
83
|
+
}
|
|
84
|
+
segments.push({ startMs: segmentStartMs, endMs: previousTsMs });
|
|
85
|
+
return segments;
|
|
86
|
+
}
|
|
87
|
+
function buildSessionActivityArtifacts(events) {
|
|
88
|
+
const eventTimestamps = collectTimestampMs(events);
|
|
89
|
+
return {
|
|
90
|
+
eventCount: events.length,
|
|
91
|
+
eventTimestamps,
|
|
92
|
+
activeSegments: buildActiveSegmentsFromEventTimestamps(eventTimestamps),
|
|
93
|
+
};
|
|
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
|
+
}
|
|
39
110
|
function normalizeActivityCounts(counts) {
|
|
40
111
|
let maxCount = 0;
|
|
41
112
|
for (const count of counts) {
|
|
@@ -441,6 +512,7 @@ export class TraceIndex extends EventEmitter {
|
|
|
441
512
|
config;
|
|
442
513
|
entries = new Map();
|
|
443
514
|
pathToId = new Map();
|
|
515
|
+
discoveredFilesById = new Map();
|
|
444
516
|
cursorById = new Map();
|
|
445
517
|
watcher = null;
|
|
446
518
|
timer = null;
|
|
@@ -451,6 +523,11 @@ export class TraceIndex extends EventEmitter {
|
|
|
451
523
|
queuedForceFullRefresh = false;
|
|
452
524
|
dirtyPaths = new Set();
|
|
453
525
|
forceReparsePaths = new Set();
|
|
526
|
+
pendingHydrationFiles = [];
|
|
527
|
+
pendingHydrationIds = new Set();
|
|
528
|
+
pendingHydrationPaths = new Set();
|
|
529
|
+
pendingHydrationCursor = 0;
|
|
530
|
+
startupStatus = createInitialStartupStatus();
|
|
454
531
|
adaptiveIntervalMs;
|
|
455
532
|
perf = {
|
|
456
533
|
refreshCount: 0,
|
|
@@ -494,8 +571,43 @@ export class TraceIndex extends EventEmitter {
|
|
|
494
571
|
}
|
|
495
572
|
async start() {
|
|
496
573
|
this.started = true;
|
|
497
|
-
|
|
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;
|
|
498
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;
|
|
499
611
|
this.scheduleNextRefresh(this.computeBaseIntervalMs());
|
|
500
612
|
}
|
|
501
613
|
stop() {
|
|
@@ -509,6 +621,11 @@ export class TraceIndex extends EventEmitter {
|
|
|
509
621
|
this.watcher = null;
|
|
510
622
|
}
|
|
511
623
|
this.forceReparsePaths.clear();
|
|
624
|
+
this.pendingHydrationFiles = [];
|
|
625
|
+
this.pendingHydrationIds.clear();
|
|
626
|
+
this.pendingHydrationPaths.clear();
|
|
627
|
+
this.pendingHydrationCursor = 0;
|
|
628
|
+
this.discoveredFilesById.clear();
|
|
512
629
|
}
|
|
513
630
|
async refresh() {
|
|
514
631
|
this.queuedForceFullRefresh = true;
|
|
@@ -523,6 +640,43 @@ export class TraceIndex extends EventEmitter {
|
|
|
523
640
|
...stats,
|
|
524
641
|
};
|
|
525
642
|
}
|
|
643
|
+
getStreamVersion() {
|
|
644
|
+
return this.streamVersion;
|
|
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
|
+
}
|
|
526
680
|
getTopTools(limit = 12) {
|
|
527
681
|
const counts = new Map();
|
|
528
682
|
for (const entry of this.entries.values()) {
|
|
@@ -535,6 +689,91 @@ export class TraceIndex extends EventEmitter {
|
|
|
535
689
|
.slice(0, Math.max(1, limit))
|
|
536
690
|
.map(([name, count]) => ({ name, count }));
|
|
537
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
|
+
}
|
|
538
777
|
buildRetentionStats() {
|
|
539
778
|
let hotTraces = 0;
|
|
540
779
|
let warmTraces = 0;
|
|
@@ -584,13 +823,14 @@ export class TraceIndex extends EventEmitter {
|
|
|
584
823
|
if (roots.length === 0)
|
|
585
824
|
return;
|
|
586
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));
|
|
587
827
|
this.watcher = chokidar.watch(roots, {
|
|
588
828
|
ignoreInitial: true,
|
|
589
829
|
persistent: true,
|
|
590
830
|
followSymlinks: false,
|
|
591
831
|
awaitWriteFinish: {
|
|
592
|
-
stabilityThreshold:
|
|
593
|
-
pollInterval:
|
|
832
|
+
stabilityThreshold: writeStabilityMs,
|
|
833
|
+
pollInterval: WATCH_WRITE_POLL_INTERVAL_MS,
|
|
594
834
|
},
|
|
595
835
|
});
|
|
596
836
|
const onDirty = (rawPath) => {
|
|
@@ -610,7 +850,7 @@ export class TraceIndex extends EventEmitter {
|
|
|
610
850
|
}
|
|
611
851
|
}
|
|
612
852
|
this.perf.dirtyPathQueue = this.dirtyPaths.size;
|
|
613
|
-
this.scheduleNextRefresh(
|
|
853
|
+
this.scheduleNextRefresh(DIRTY_REFRESH_DELAY_MS);
|
|
614
854
|
};
|
|
615
855
|
this.watcher.on("add", onDirty);
|
|
616
856
|
this.watcher.on("change", onDirty);
|
|
@@ -711,6 +951,8 @@ export class TraceIndex extends EventEmitter {
|
|
|
711
951
|
return Array.from(matches.values());
|
|
712
952
|
}
|
|
713
953
|
shouldRunFullRefresh(nowMsValue) {
|
|
954
|
+
if (this.hasPendingHydrationWork())
|
|
955
|
+
return false;
|
|
714
956
|
if (this.lastFullRefreshAtMs <= 0)
|
|
715
957
|
return true;
|
|
716
958
|
const fullIntervalMs = Math.max(1_000, this.config.scan.fullRescanIntervalMs);
|
|
@@ -740,6 +982,9 @@ export class TraceIndex extends EventEmitter {
|
|
|
740
982
|
if (this.refreshPending || this.queuedForceFullRefresh) {
|
|
741
983
|
this.scheduleNextRefresh(25);
|
|
742
984
|
}
|
|
985
|
+
else if (this.hasPendingHydrationWork()) {
|
|
986
|
+
this.scheduleNextRefresh(BACKGROUND_HYDRATE_DELAY_MS);
|
|
987
|
+
}
|
|
743
988
|
else if (this.started) {
|
|
744
989
|
this.scheduleNextRefresh();
|
|
745
990
|
}
|
|
@@ -781,31 +1026,45 @@ export class TraceIndex extends EventEmitter {
|
|
|
781
1026
|
if (useFullRefresh) {
|
|
782
1027
|
await this.refreshFull(refreshNowMs, stats);
|
|
783
1028
|
}
|
|
784
|
-
else {
|
|
1029
|
+
else if (this.dirtyPaths.size > 0) {
|
|
785
1030
|
await this.refreshDirty(refreshNowMs, stats);
|
|
786
1031
|
}
|
|
787
|
-
this.
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
this.
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
this.
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
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);
|
|
797
1048
|
return stats;
|
|
798
1049
|
}
|
|
799
1050
|
async refreshFull(refreshNowMs, stats) {
|
|
800
1051
|
const files = await discoverTraceFiles(this.config);
|
|
801
1052
|
this.lastFullRefreshAtMs = refreshNowMs;
|
|
802
1053
|
this.forceReparsePaths.clear();
|
|
1054
|
+
this.pendingHydrationFiles = [];
|
|
1055
|
+
this.pendingHydrationIds.clear();
|
|
1056
|
+
this.pendingHydrationPaths.clear();
|
|
1057
|
+
this.pendingHydrationCursor = 0;
|
|
1058
|
+
this.discoveredFilesById.clear();
|
|
803
1059
|
const nextIds = new Set(files.map((file) => file.id));
|
|
804
1060
|
const nextPathToId = new Map();
|
|
1061
|
+
const discoveredFilesById = new Map();
|
|
805
1062
|
for (const file of files) {
|
|
806
1063
|
nextPathToId.set(file.path, file.id);
|
|
1064
|
+
discoveredFilesById.set(file.id, file);
|
|
807
1065
|
}
|
|
808
1066
|
this.pathToId = nextPathToId;
|
|
1067
|
+
this.discoveredFilesById = discoveredFilesById;
|
|
809
1068
|
for (const existingId of this.entries.keys()) {
|
|
810
1069
|
if (!nextIds.has(existingId)) {
|
|
811
1070
|
this.entries.delete(existingId);
|
|
@@ -820,7 +1079,17 @@ export class TraceIndex extends EventEmitter {
|
|
|
820
1079
|
stats.parsedFileCount += 1;
|
|
821
1080
|
stats.hadFileMutations = true;
|
|
822
1081
|
}
|
|
1082
|
+
this.applyRetention(refreshNowMs);
|
|
823
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
|
+
};
|
|
824
1093
|
}
|
|
825
1094
|
async refreshDirty(refreshNowMs, stats) {
|
|
826
1095
|
if (this.dirtyPaths.size === 0)
|
|
@@ -852,14 +1121,20 @@ export class TraceIndex extends EventEmitter {
|
|
|
852
1121
|
this.emitStream("trace_removed", { id: existingId });
|
|
853
1122
|
stats.hadFileMutations = true;
|
|
854
1123
|
}
|
|
1124
|
+
this.pendingHydrationPaths.delete(normalizedPath);
|
|
1125
|
+
this.pendingHydrationIds.delete(existingId ?? "");
|
|
1126
|
+
if (existingId)
|
|
1127
|
+
this.discoveredFilesById.delete(existingId);
|
|
855
1128
|
this.pathToId.delete(normalizedPath);
|
|
856
1129
|
continue;
|
|
857
1130
|
}
|
|
858
1131
|
this.pathToId.set(file.path, file.id);
|
|
1132
|
+
this.discoveredFilesById.set(file.id, file);
|
|
859
1133
|
if (existingId && existingId !== file.id && this.entries.delete(existingId)) {
|
|
860
1134
|
this.cursorById.delete(existingId);
|
|
861
1135
|
this.emitStream("trace_removed", { id: existingId });
|
|
862
1136
|
stats.hadFileMutations = true;
|
|
1137
|
+
this.discoveredFilesById.delete(existingId);
|
|
863
1138
|
}
|
|
864
1139
|
if (processedIds.has(file.id)) {
|
|
865
1140
|
if (!dirtyEntry.forceReparse)
|
|
@@ -873,6 +1148,33 @@ export class TraceIndex extends EventEmitter {
|
|
|
873
1148
|
stats.parsedFileCount += 1;
|
|
874
1149
|
stats.hadFileMutations = true;
|
|
875
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;
|
|
876
1178
|
}
|
|
877
1179
|
}
|
|
878
1180
|
inferSourceMetadata(filePath) {
|
|
@@ -993,6 +1295,7 @@ export class TraceIndex extends EventEmitter {
|
|
|
993
1295
|
const parsed = await this.parserRegistry.parseFile(file);
|
|
994
1296
|
const summary = summarize(file, parsed.agent, parsed.parser, parsed.sessionId, parsed.events, parsed.parseError, this.config, refreshNowMs);
|
|
995
1297
|
const redactedEvents = redactEvents(parsed.events, this.config.redaction);
|
|
1298
|
+
const activityArtifacts = buildSessionActivityArtifacts(redactedEvents);
|
|
996
1299
|
const previousCount = current?.summary.eventCount ?? 0;
|
|
997
1300
|
this.entries.set(file.id, {
|
|
998
1301
|
file,
|
|
@@ -1000,6 +1303,8 @@ export class TraceIndex extends EventEmitter {
|
|
|
1000
1303
|
residentEvents: redactedEvents,
|
|
1001
1304
|
cachedFullEvents: redactedEvents,
|
|
1002
1305
|
cachedRawEvents: parsed.events,
|
|
1306
|
+
activityArtifacts,
|
|
1307
|
+
usageArtifacts: buildSessionUsageArtifacts(parsed.events, parsed.agent, this.config),
|
|
1003
1308
|
pinnedMaterializedAtMs: current?.pinnedMaterializedAtMs ?? 0,
|
|
1004
1309
|
});
|
|
1005
1310
|
this.cursorById.set(file.id, {
|
|
@@ -1028,6 +1333,8 @@ export class TraceIndex extends EventEmitter {
|
|
|
1028
1333
|
residentEvents: [],
|
|
1029
1334
|
cachedFullEvents: [],
|
|
1030
1335
|
cachedRawEvents: [],
|
|
1336
|
+
activityArtifacts: null,
|
|
1337
|
+
usageArtifacts: null,
|
|
1031
1338
|
pinnedMaterializedAtMs: 0,
|
|
1032
1339
|
});
|
|
1033
1340
|
this.cursorById.set(file.id, {
|
|
@@ -1098,6 +1405,7 @@ export class TraceIndex extends EventEmitter {
|
|
|
1098
1405
|
const rebasedRawEvents = rebaseEvents(parsed.events);
|
|
1099
1406
|
const mergedEvents = current.cachedFullEvents.concat(rebasedEvents);
|
|
1100
1407
|
const mergedRawEvents = current.cachedRawEvents.concat(rebasedRawEvents);
|
|
1408
|
+
const activityArtifacts = buildSessionActivityArtifacts(mergedEvents);
|
|
1101
1409
|
const mergedSummary = summarize(file, current.summary.agent, current.summary.parser, current.summary.sessionId || parsed.sessionId, mergedRawEvents, "", this.config, refreshNowMs);
|
|
1102
1410
|
const summary = {
|
|
1103
1411
|
...mergedSummary,
|
|
@@ -1111,6 +1419,8 @@ export class TraceIndex extends EventEmitter {
|
|
|
1111
1419
|
residentEvents: mergedEvents,
|
|
1112
1420
|
cachedFullEvents: mergedEvents,
|
|
1113
1421
|
cachedRawEvents: mergedRawEvents,
|
|
1422
|
+
activityArtifacts,
|
|
1423
|
+
usageArtifacts: buildSessionUsageArtifacts(mergedRawEvents, current.summary.agent, this.config),
|
|
1114
1424
|
pinnedMaterializedAtMs: current.pinnedMaterializedAtMs,
|
|
1115
1425
|
});
|
|
1116
1426
|
this.cursorById.set(file.id, {
|
|
@@ -1170,6 +1480,9 @@ export class TraceIndex extends EventEmitter {
|
|
|
1170
1480
|
entry.cachedFullEvents = null;
|
|
1171
1481
|
entry.cachedRawEvents = null;
|
|
1172
1482
|
}
|
|
1483
|
+
if (this.config.retention.strategy !== "full_memory" && tier !== "hot" && entry.activityArtifacts) {
|
|
1484
|
+
entry.activityArtifacts = compactActivityArtifacts(entry.activityArtifacts);
|
|
1485
|
+
}
|
|
1173
1486
|
if (this.config.retention.strategy === "full_memory" && !entry.cachedFullEvents) {
|
|
1174
1487
|
entry.cachedFullEvents = sourceEvents;
|
|
1175
1488
|
}
|
|
@@ -1255,6 +1568,8 @@ export class TraceIndex extends EventEmitter {
|
|
|
1255
1568
|
const refreshedSummary = summarize(entry.file, parsed.agent, parsed.parser, parsed.sessionId, parsed.events, parsed.parseError, this.config, nowMs());
|
|
1256
1569
|
entry.cachedFullEvents = redactedEvents;
|
|
1257
1570
|
entry.cachedRawEvents = parsed.events;
|
|
1571
|
+
entry.activityArtifacts = buildSessionActivityArtifacts(redactedEvents);
|
|
1572
|
+
entry.usageArtifacts = buildSessionUsageArtifacts(parsed.events, parsed.agent, this.config);
|
|
1258
1573
|
entry.pinnedMaterializedAtMs = nowMs();
|
|
1259
1574
|
entry.summary = {
|
|
1260
1575
|
...refreshedSummary,
|
|
@@ -1274,6 +1589,50 @@ export class TraceIndex extends EventEmitter {
|
|
|
1274
1589
|
events,
|
|
1275
1590
|
};
|
|
1276
1591
|
}
|
|
1592
|
+
getSessionActivityArtifacts(id) {
|
|
1593
|
+
const found = this.entries.get(id);
|
|
1594
|
+
if (!found) {
|
|
1595
|
+
throw new Error(`unknown trace id: ${id}`);
|
|
1596
|
+
}
|
|
1597
|
+
if (found.activityArtifacts && found.activityArtifacts.eventCount === found.summary.eventCount) {
|
|
1598
|
+
return found.activityArtifacts;
|
|
1599
|
+
}
|
|
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
|
+
}
|
|
1634
|
+
return artifacts;
|
|
1635
|
+
}
|
|
1277
1636
|
getTracePage(id, options = {}) {
|
|
1278
1637
|
const detail = this.getSessionDetail(id);
|
|
1279
1638
|
const includeMeta = options.includeMeta ?? this.config.scan.includeMetaDefault;
|