watchmyagents 1.4.1 → 1.4.3

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.
@@ -0,0 +1,56 @@
1
+ // v1.4.3 F-51 (P2 audit) — bounded dedup state for the long-running watch loop.
2
+ //
3
+ // The watch daemon dedupes events by their stable Anthropic event id so a
4
+ // re-fetch of an already-captured session doesn't double-write the NDJSON.
5
+ // Pre-F-51 every id ever seen lived in a single Set that was only ever added
6
+ // to — on a 24/7 daemon at high event volume that Set grows without bound
7
+ // (hundreds of MB within days), and the daemon eventually OOM-kills and stops
8
+ // collecting (a silent monitoring gap).
9
+ //
10
+ // SeenTracker bounds runtime growth WITHOUT weakening dedup:
11
+ // - Preloaded ids (read from on-disk NDJSON at startup) sit in a static set,
12
+ // loaded once. Bounded by what the operator keeps on disk.
13
+ // - Runtime-discovered ids are tracked PER SESSION. When a session
14
+ // terminates it is never re-fetched, so its id set is dropped. Live memory
15
+ // is therefore bounded by ACTIVE sessions, not lifetime event volume.
16
+ //
17
+ // Correctness: Anthropic event ids are globally unique and an event belongs to
18
+ // exactly one session, so checking the preloaded set + that session's set is
19
+ // equivalent to the old global-set membership test (no missed dedup, no
20
+ // missed events).
21
+ export class SeenTracker {
22
+ constructor(preloadedIds = []) {
23
+ this._preloaded = new Set(preloadedIds);
24
+ this._bySession = new Map(); // sessionId -> Set<eventId>
25
+ }
26
+
27
+ has(sessionId, eventId) {
28
+ if (this._preloaded.has(eventId)) return true;
29
+ const s = this._bySession.get(sessionId);
30
+ return s ? s.has(eventId) : false;
31
+ }
32
+
33
+ add(sessionId, eventId) {
34
+ let s = this._bySession.get(sessionId);
35
+ if (!s) { s = new Set(); this._bySession.set(sessionId, s); }
36
+ s.add(eventId);
37
+ }
38
+
39
+ // Drop a terminated session's runtime id set — it will never be re-fetched,
40
+ // so its ids no longer need to be remembered for dedup.
41
+ forgetSession(sessionId) {
42
+ this._bySession.delete(sessionId);
43
+ }
44
+
45
+ // Total tracked ids (preloaded + all live per-session sets). For observability.
46
+ get size() {
47
+ let n = this._preloaded.size;
48
+ for (const s of this._bySession.values()) n += s.size;
49
+ return n;
50
+ }
51
+
52
+ // Number of sessions currently holding a runtime id set.
53
+ get sessionCount() {
54
+ return this._bySession.size;
55
+ }
56
+ }