wyrm-mcp 7.2.1 → 7.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/LICENSE +26 -667
  2. package/NOTICE +14 -33
  3. package/dist/activation.js +1 -60
  4. package/dist/agent-daemon.js +4 -281
  5. package/dist/agent-loop.js +7 -332
  6. package/dist/analytics.js +13 -236
  7. package/dist/attribution.js +1 -49
  8. package/dist/audit.js +2 -457
  9. package/dist/auto-capture.js +3 -138
  10. package/dist/auto-orchestrator.js +1 -325
  11. package/dist/autoconfig.js +39 -840
  12. package/dist/buddy-runner.js +1 -109
  13. package/dist/buddy.js +14 -564
  14. package/dist/build-flags.js +1 -17
  15. package/dist/capabilities.js +3 -183
  16. package/dist/capture.js +1 -56
  17. package/dist/causality.js +6 -107
  18. package/dist/cli.js +20 -281
  19. package/dist/cloud/cli.js +5 -541
  20. package/dist/cloud/client.js +1 -221
  21. package/dist/cloud/crypto.js +1 -85
  22. package/dist/cloud/machine-id.js +2 -113
  23. package/dist/cloud/recovery.js +1 -60
  24. package/dist/cloud/sync-engine.js +7 -543
  25. package/dist/cloud-backup.js +5 -579
  26. package/dist/cloud-profile.js +1 -138
  27. package/dist/cloud-sync-entrypoint.js +1 -47
  28. package/dist/cloud-sync.js +2 -309
  29. package/dist/constellation.js +12 -168
  30. package/dist/context-build-budgeted.js +4 -144
  31. package/dist/context-ranking.js +1 -69
  32. package/dist/crypto.js +1 -179
  33. package/dist/daemon-write-endpoint.js +1 -290
  34. package/dist/daemon-writer.js +2 -406
  35. package/dist/database.js +43 -1110
  36. package/dist/deprecations.js +2 -162
  37. package/dist/design.js +13 -141
  38. package/dist/event-replication.js +1 -112
  39. package/dist/events-sse.js +7 -43
  40. package/dist/events.js +6 -238
  41. package/dist/failure-patterns.js +42 -659
  42. package/dist/federation.js +12 -236
  43. package/dist/goals.js +13 -101
  44. package/dist/golden.js +3 -355
  45. package/dist/handlers/agent.js +4 -165
  46. package/dist/handlers/alias-adapters.js +1 -129
  47. package/dist/handlers/aliases.js +1 -171
  48. package/dist/handlers/audit.js +1 -87
  49. package/dist/handlers/boundary.js +1 -221
  50. package/dist/handlers/capture.js +73 -1109
  51. package/dist/handlers/causality.js +7 -114
  52. package/dist/handlers/cloud.js +85 -382
  53. package/dist/handlers/companion.js +28 -459
  54. package/dist/handlers/datalake.js +7 -187
  55. package/dist/handlers/dispatch-context.js +0 -22
  56. package/dist/handlers/entity.js +25 -256
  57. package/dist/handlers/events.js +16 -335
  58. package/dist/handlers/failure.js +13 -340
  59. package/dist/handlers/goals.js +4 -296
  60. package/dist/handlers/intelligence.js +126 -674
  61. package/dist/handlers/invoicing.js +1 -70
  62. package/dist/handlers/mcpclient.js +6 -137
  63. package/dist/handlers/orchestration.js +40 -125
  64. package/dist/handlers/output-schemas.js +1 -24
  65. package/dist/handlers/presence.js +3 -99
  66. package/dist/handlers/project.js +28 -182
  67. package/dist/handlers/prompts.js +6 -157
  68. package/dist/handlers/quest.js +4 -224
  69. package/dist/handlers/recall.js +11 -218
  70. package/dist/handlers/registry.js +1 -167
  71. package/dist/handlers/resources.js +1 -288
  72. package/dist/handlers/review.js +11 -74
  73. package/dist/handlers/run.js +17 -487
  74. package/dist/handlers/search.js +15 -326
  75. package/dist/handlers/session.js +28 -615
  76. package/dist/handlers/share.js +8 -184
  77. package/dist/handlers/shims.js +1 -464
  78. package/dist/handlers/skill.js +67 -449
  79. package/dist/handlers/survivors.js +1 -120
  80. package/dist/handlers/symbols.js +8 -109
  81. package/dist/handlers/syncops.js +4 -302
  82. package/dist/handlers/types.js +1 -27
  83. package/dist/harvest.js +5 -191
  84. package/dist/hours.js +7 -156
  85. package/dist/http-auth.js +3 -321
  86. package/dist/http-fast.js +21 -1137
  87. package/dist/icons.js +1 -47
  88. package/dist/index.js +2 -924
  89. package/dist/indexer.js +4 -145
  90. package/dist/intelligence.js +31 -261
  91. package/dist/internal-dispatch.js +3 -212
  92. package/dist/keyset.js +1 -110
  93. package/dist/knowledge-graph.js +12 -176
  94. package/dist/license.js +2 -441
  95. package/dist/logger.js +2 -199
  96. package/dist/maintenance.js +2 -148
  97. package/dist/mcp-client.js +6 -262
  98. package/dist/memory-artifacts.js +30 -449
  99. package/dist/migrate-prompt.js +2 -124
  100. package/dist/migrations.js +40 -655
  101. package/dist/performance.js +1 -228
  102. package/dist/presence.js +11 -140
  103. package/dist/priority-embed.js +5 -164
  104. package/dist/providers/embedding-provider.js +1 -196
  105. package/dist/readonly-gate.js +1 -29
  106. package/dist/rehydration.js +9 -157
  107. package/dist/reindex.js +1 -88
  108. package/dist/render-target.js +21 -514
  109. package/dist/render.js +4 -280
  110. package/dist/repl-guard.js +1 -173
  111. package/dist/replication-daemon-entrypoint.js +1 -31
  112. package/dist/replication-daemon.js +2 -262
  113. package/dist/resilience.js +1 -591
  114. package/dist/reverse-bridge.js +5 -360
  115. package/dist/security.js +1 -244
  116. package/dist/session-seen.js +3 -51
  117. package/dist/setup.js +1 -260
  118. package/dist/skill-author.js +5 -168
  119. package/dist/spec-kit.js +1 -191
  120. package/dist/sqlite-busy.js +1 -154
  121. package/dist/statusline.js +11 -315
  122. package/dist/sub-agent.js +13 -262
  123. package/dist/summarizer.js +13 -139
  124. package/dist/symbols.js +7 -283
  125. package/dist/sync.js +5 -359
  126. package/dist/tasks-dispatch.js +1 -84
  127. package/dist/tasks.js +1 -282
  128. package/dist/token-budget.js +1 -143
  129. package/dist/tool-analytics.js +7 -129
  130. package/dist/tool-annotations.js +1 -365
  131. package/dist/tool-manifest-v2.json +1 -1
  132. package/dist/tool-manifest.json +1 -1
  133. package/dist/tool-profiles.js +1 -75
  134. package/dist/trace-harvest.js +6 -244
  135. package/dist/types.js +1 -30
  136. package/dist/ui-dashboard.js +41 -50
  137. package/dist/ulid.js +1 -81
  138. package/dist/validate.js +1 -129
  139. package/dist/vault.js +1 -534
  140. package/dist/vectors.js +3 -184
  141. package/dist/version-check.js +4 -136
  142. package/dist/visibility.js +19 -155
  143. package/dist/wyrm-cli.js +98 -2464
  144. package/dist/wyrm-guard.js +14 -424
  145. package/dist/wyrm-loop.js +3 -150
  146. package/dist/wyrm-manifest.json +1 -1
  147. package/dist/wyrm-statusline-daemon.js +1 -11
  148. package/dist/wyrm-statusline.js +4 -56
  149. package/dist/wyrm-ui.js +9 -77
  150. package/package.json +4 -2
package/dist/events.js CHANGED
@@ -1,250 +1,18 @@
1
- /**
2
- * Live Memory v6.4 — append-only event log (Phase 1: local node).
3
- *
4
- * Every canonical write emits an event here as a FAILURE-ISOLATED side effect:
5
- * the emit runs AFTER the canonical write and any failure — or the feature being
6
- * disabled — logs-and-drops. It NEVER throws and NEVER rolls back the real write.
7
- * "Strictly additive" means the existing write path cannot regress even if this
8
- * table or its migration is broken.
9
- *
10
- * Feature flag `WYRM_LIVE_MEMORY` — DEFAULT ON (operator decision, v6.4).
11
- * Disable with `WYRM_LIVE_MEMORY=0` (or `false` / `off`).
12
- *
13
- * Privacy: a denormalized payload is written ONLY for shareable rows; protected
14
- * or private rows are reference-only (`payload_json = NULL`) so the event log
15
- * never becomes a copy of data the encryption/federation layer protects.
16
- *
17
- * @copyright 2026 Ghost Protocol (Pvt) Ltd.
18
- * @license AGPL-3.0-or-later — dual-licensed; commercial terms: ghosts.lk@proton.me. See LICENSE.
19
- */
20
- import { randomUUID } from 'node:crypto';
21
- import { hostname } from 'node:os';
22
- import { logger } from './logger.js';
23
- import { getActor } from './handlers/boundary.js';
24
- /** The closed set of valid event kinds — ingest rejects anything else (anti-spoof). */
25
- const KNOWN_KINDS = new Set([
26
- 'capture', 'session_update', 'quest', 'truth', 'presence', 'tool_call',
27
- 'failure', 'decision',
28
- ]);
29
- // Ingest bounds for peer-supplied events (defends storage + ordering against a hostile peer).
30
- const MAX_PAYLOAD_BYTES = 64 * 1024; // oversized payloads degrade to reference-only
31
- const MAX_DEVICE_LEN = 80;
32
- const MAX_FIELD_LEN = 256;
33
- const DAY_MS = 86_400_000;
34
- const isSafeNonNegInt = (n) => typeof n === 'number' && Number.isInteger(n) && n >= 0 && n <= Number.MAX_SAFE_INTEGER;
35
- const clampStr = (s, max) => typeof s === 'string' ? s.slice(0, max) : null;
36
- /** Default ON in v6.4 (operator decision). Disable with WYRM_LIVE_MEMORY=0|false|off. */
37
- export function isLiveMemoryEnabled() {
38
- const v = (process.env.WYRM_LIVE_MEMORY || '').toLowerCase();
39
- return v !== '0' && v !== 'false' && v !== 'off';
40
- }
41
- // Per-DB cache (not a single module global): a process that opens more than one
42
- // WyrmDB — e.g. cross-device replication tests, or a future multi-DB host — must
43
- // get a DISTINCT device id per DB, or echo-suppression collapses.
44
- const deviceIdCache = new WeakMap();
45
- /** Stable per-node device id, persisted in wyrm_meta. Lazy + cached per DB. */
46
- export function getDeviceId(db) {
47
- const hit = deviceIdCache.get(db);
48
- if (hit)
49
- return hit;
50
- const read = () => db.prepare(`SELECT value FROM wyrm_meta WHERE key = 'device_id'`).get()?.value;
51
- let id = read();
52
- if (!id) {
53
- const candidate = `${hostname().slice(0, 24)}-${randomUUID().slice(0, 8)}`;
54
- db.prepare(`INSERT OR IGNORE INTO wyrm_meta (key, value) VALUES ('device_id', ?)`).run(candidate);
55
- id = read() || candidate; // re-read in case another process won the race
56
- }
57
- deviceIdCache.set(db, id);
58
- return id;
59
- }
60
- function clamp(n, lo, hi, dflt) {
61
- const v = typeof n === 'number' && Number.isFinite(n) ? Math.floor(n) : dflt;
62
- return Math.max(lo, Math.min(hi, v));
63
- }
64
- /**
65
- * Emit an event for a canonical write. NEVER throws; no-op when disabled.
66
- * Call AFTER the canonical row is committed.
67
- */
68
- export function emitEvent(db, ev) {
69
- if (!isLiveMemoryEnabled())
70
- return;
71
- try {
72
- const device = getDeviceId(db);
73
- const isShared = ev.isShared ? 1 : 0;
74
- let payloadJson = null;
75
- if (isShared && ev.payload !== undefined) {
76
- // Isolate stringify: a non-serializable payload (circular / BigInt) degrades
77
- // to reference-only — the event still emits rather than being dropped whole.
78
- try {
79
- payloadJson = JSON.stringify(ev.payload);
80
- }
81
- catch {
82
- payloadJson = null;
83
- }
84
- }
85
- // v7 F2 (T009): machine attribution from the ambient actor envelope unless
86
- // the caller passed it explicitly. The 6.x `actor` display label keeps being
87
- // written exactly as before — agent_id/run_id land ALONGSIDE it.
88
- const ambient = getActor();
89
- const agentId = ev.agentId !== undefined ? ev.agentId : ambient.agent_id;
90
- const runId = ev.runId !== undefined ? ev.runId : ambient.run_id;
91
- // Atomic origin_seq allocation: the MAX(origin_seq)+1 is computed INSIDE the
92
- // INSERT so it runs under the write lock — no read-then-write race between the
93
- // server, the CLI, and the trace hook all writing to one DB.
94
- db.prepare(`
1
+ import{randomUUID as g}from"node:crypto";import{hostname as m}from"node:os";import{logger as l}from"./logger.js";import{getActor as R}from"./handlers/boundary.js";const I=new Set(["capture","session_update","quest","truth","presence","tool_call","failure","decision"]),T=64*1024,y=80,d=256,_=864e5,p=r=>typeof r=="number"&&Number.isInteger(r)&&r>=0&&r<=Number.MAX_SAFE_INTEGER,s=(r,t)=>typeof r=="string"?r.slice(0,t):null;function O(){const r=(process.env.WYRM_LIVE_MEMORY||"").toLowerCase();return r!=="0"&&r!=="false"&&r!=="off"}const f=new WeakMap;function S(r){const t=f.get(r);if(t)return t;const e=()=>r.prepare("SELECT value FROM wyrm_meta WHERE key = 'device_id'").get()?.value;let n=e();if(!n){const o=`${m().slice(0,24)}-${g().slice(0,8)}`;r.prepare("INSERT OR IGNORE INTO wyrm_meta (key, value) VALUES ('device_id', ?)").run(o),n=e()||o}return f.set(r,n),n}function a(r,t,e,n){const o=typeof r=="number"&&Number.isFinite(r)?Math.floor(r):n;return Math.max(t,Math.min(e,o))}function C(r,t){if(O())try{const e=S(r),n=t.isShared?1:0;let o=null;if(n&&t.payload!==void 0)try{o=JSON.stringify(t.payload)}catch{o=null}const i=R(),c=t.agentId!==void 0?t.agentId:i.agent_id,u=t.runId!==void 0?t.runId:i.run_id;r.prepare(`
95
2
  INSERT INTO events
96
3
  (origin_device, origin_seq, project_id, kind, ref_table, ref_id, actor, is_shared, payload_json, created_at, agent_id, run_id)
97
4
  VALUES (?, (SELECT COALESCE(MAX(origin_seq), 0) + 1 FROM events WHERE origin_device = ?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
98
- `).run(device, device, String(ev.projectId), ev.kind, ev.refTable ?? null, ev.refId != null ? String(ev.refId) : null, ev.actor ?? null, isShared, payloadJson, Date.now(), agentId, runId);
99
- }
100
- catch (err) {
101
- // log-and-drop: the canonical write already succeeded; the event is best-effort.
102
- logger.warn('live-memory: emitEvent dropped', { err: err instanceof Error ? err.message : String(err) });
103
- }
104
- }
105
- /** One-shot pull of events newer than a cursor, for a project. Idempotent. */
106
- export function eventsSince(db, projectId, sinceCursor = 0, limit = 100) {
107
- const lim = clamp(limit, 1, 1000, 100);
108
- const since = clamp(sinceCursor, 0, Number.MAX_SAFE_INTEGER, 0);
109
- return db
110
- .prepare(`SELECT * FROM events WHERE project_id = ? AND cursor > ? ORDER BY cursor ASC LIMIT ?`)
111
- .all(String(projectId), since, lim);
112
- }
113
- /** Initial subscribe: current head cursor + a recent window (chronological). */
114
- export function subscribeEvents(db, projectId, window = 20) {
115
- const win = clamp(window, 1, 200, 20);
116
- const recent = db
117
- .prepare(`SELECT * FROM events WHERE project_id = ? ORDER BY cursor DESC LIMIT ?`)
118
- .all(String(projectId), win);
119
- recent.reverse();
120
- const head = db
121
- .prepare(`SELECT COALESCE(MAX(cursor), 0) AS c FROM events WHERE project_id = ?`)
122
- .get(String(projectId));
123
- return { cursor: head.c, recent };
124
- }
125
- /**
126
- * Ingest one event received from a peer (replication PULL). Failure-isolated.
127
- * - **Echo-suppression:** an event whose `origin_device` is THIS device is
128
- * dropped (`'echo'`) — our own events bouncing back never re-insert.
129
- * - **Idempotent dedup:** `INSERT OR IGNORE` on `UNIQUE(origin_device, origin_seq)`
130
- * → re-delivering the same event is a no-op (`'duplicate'`).
131
- * - `localProjectId` files the event under a LOCAL project (peer project ids are
132
- * not assumed to match ours — the local node decides the mapping).
133
- * The remote `(origin_device, origin_seq)` identity is preserved verbatim so the
134
- * dedup key stays globally stable; only the local `cursor` is freshly minted.
135
- */
136
- export function ingestRemoteEvent(db, localProjectId, ev) {
137
- try {
138
- // ── Validate + bound every peer-supplied field (hostile peer hardening) ──
139
- if (!ev || typeof ev.origin_device !== 'string' || !isSafeNonNegInt(ev.origin_seq)) {
140
- return 'duplicate'; // malformed identity — drop, never throw
141
- }
142
- const device = clampStr(ev.origin_device, MAX_DEVICE_LEN);
143
- if (!device)
144
- return 'duplicate';
145
- if (device === getDeviceId(db))
146
- return 'echo'; // our own event bounced back
147
- // Reject unknown kinds — stops a peer injecting arbitrary `event:` types downstream.
148
- if (typeof ev.kind !== 'string' || !KNOWN_KINDS.has(ev.kind))
149
- return 'duplicate';
150
- const isShared = ev.is_shared ? 1 : 0;
151
- // Privacy consistency: a non-shared row NEVER carries a payload (mirrors emit's invariant),
152
- // so a peer can't smuggle a private-flagged blob. Oversized shared payloads degrade to
153
- // reference-only rather than bloating the store unboundedly.
154
- let payloadJson = isShared && typeof ev.payload_json === 'string' ? ev.payload_json : null;
155
- if (payloadJson && payloadJson.length > MAX_PAYLOAD_BYTES)
156
- payloadJson = null;
157
- const now = Date.now();
158
- const createdAt = isSafeNonNegInt(ev.created_at) && ev.created_at <= now + DAY_MS ? ev.created_at : now;
159
- const cloudCursor = isSafeNonNegInt(ev.cloud_cursor) ? ev.cloud_cursor : null;
160
- // v7 F2 (T009): the remote author's attribution is preserved (clamped like
161
- // every peer-supplied field) — the LOCAL actor envelope is deliberately NOT
162
- // stamped on ingested events: provenance belongs to whoever wrote the row,
163
- // and NULL stays NULL so replication round-trips are byte-faithful.
164
- const info = db.prepare(`
5
+ `).run(e,e,String(t.projectId),t.kind,t.refTable??null,t.refId!=null?String(t.refId):null,t.actor??null,n,o,Date.now(),c,u)}catch(e){l.warn("live-memory: emitEvent dropped",{err:e instanceof Error?e.message:String(e)})}}function D(r,t,e=0,n=100){const o=a(n,1,1e3,100),i=a(e,0,Number.MAX_SAFE_INTEGER,0);return r.prepare("SELECT * FROM events WHERE project_id = ? AND cursor > ? ORDER BY cursor ASC LIMIT ?").all(String(t),i,o)}function F(r,t,e=20){const n=a(e,1,200,20),o=r.prepare("SELECT * FROM events WHERE project_id = ? ORDER BY cursor DESC LIMIT ?").all(String(t),n);return o.reverse(),{cursor:r.prepare("SELECT COALESCE(MAX(cursor), 0) AS c FROM events WHERE project_id = ?").get(String(t)).c,recent:o}}function j(r,t,e){try{if(!e||typeof e.origin_device!="string"||!p(e.origin_seq))return"duplicate";const n=s(e.origin_device,y);if(!n)return"duplicate";if(n===S(r))return"echo";if(typeof e.kind!="string"||!I.has(e.kind))return"duplicate";const o=e.is_shared?1:0;let i=o&&typeof e.payload_json=="string"?e.payload_json:null;i&&i.length>T&&(i=null);const c=Date.now(),u=p(e.created_at)&&e.created_at<=c+_?e.created_at:c,E=p(e.cloud_cursor)?e.cloud_cursor:null;return r.prepare(`
165
6
  INSERT OR IGNORE INTO events
166
7
  (origin_device, origin_seq, cloud_cursor, project_id, kind, ref_table, ref_id, actor, is_shared, payload_json, created_at, agent_id, run_id)
167
8
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
168
- `).run(device, ev.origin_seq, cloudCursor, String(localProjectId), ev.kind, clampStr(ev.ref_table, MAX_FIELD_LEN), clampStr(ev.ref_id, MAX_FIELD_LEN), clampStr(ev.actor, MAX_FIELD_LEN), isShared, payloadJson, createdAt, clampStr(ev.agent_id, MAX_FIELD_LEN), clampStr(ev.run_id, MAX_FIELD_LEN));
169
- return info.changes > 0 ? 'inserted' : 'duplicate';
170
- }
171
- catch (err) {
172
- logger.warn('live-memory: ingestRemoteEvent dropped', { err: err instanceof Error ? err.message : String(err) });
173
- return 'duplicate';
174
- }
175
- }
176
- /**
177
- * Shared events past a local-cursor watermark, for PUSH up to a peer/cloud.
178
- * **Privacy gate:** only `is_shared = 1` rows are eligible to leave the box;
179
- * reference-only rows keep `payload_json = NULL` exactly as stored, so protected
180
- * data never replicates even by accident.
181
- */
182
- export function eventsForPush(db, projectId, sinceCursor = 0, limit = 200) {
183
- const lim = clamp(limit, 1, 1000, 200);
184
- const since = clamp(sinceCursor, 0, Number.MAX_SAFE_INTEGER, 0);
185
- // [grove isolation] Live Memory replication is a separate egress channel; gate
186
- // it by grove too. Only a 'team' grove's events may leave the box. We match the
187
- // event's project_id against BOTH projects.id and projects.path (events store a
188
- // path-or-id key), so a private grove (e.g. GHOST PROTOCOL) never replicates.
189
- // If the projects.sync_policy column is absent (pre-migration), the grove
190
- // feature is off and we fall back to the is_shared-only gate.
191
- try {
192
- const pol = db.prepare(`SELECT sync_policy AS p FROM projects WHERE CAST(id AS TEXT) = ? OR path = ? LIMIT 1`).get(String(projectId), String(projectId));
193
- if (pol && pol.p && pol.p !== 'team')
194
- return [];
195
- }
196
- catch { /* sync_policy column absent (pre-migration): grove gate off */ }
197
- return db.prepare(`
9
+ `).run(n,e.origin_seq,E,String(t),e.kind,s(e.ref_table,d),s(e.ref_id,d),s(e.actor,d),o,i,u,s(e.agent_id,d),s(e.run_id,d)).changes>0?"inserted":"duplicate"}catch(n){return l.warn("live-memory: ingestRemoteEvent dropped",{err:n instanceof Error?n.message:String(n)}),"duplicate"}}function v(r,t,e=0,n=200){const o=a(n,1,1e3,200),i=a(e,0,Number.MAX_SAFE_INTEGER,0);try{const c=r.prepare("SELECT sync_policy AS p FROM projects WHERE CAST(id AS TEXT) = ? OR path = ? LIMIT 1").get(String(t),String(t));if(c&&c.p&&c.p!=="team")return[]}catch{}return r.prepare(`
198
10
  SELECT * FROM events
199
11
  WHERE project_id = ? AND is_shared = 1 AND cursor > ?
200
12
  ORDER BY cursor ASC LIMIT ?
201
- `).all(String(projectId), since, lim);
202
- }
203
- /**
204
- * Retention for the append-only event log. Live Memory is a DERIVED stream, not
205
- * the system of record — trimming old events is lossless for memory; it only
206
- * shortens how far back the live tail / replication catch-up can reach. Bounds
207
- * the table two ways: (1) drop events older than `olderThanDays` (old enough that
208
- * any peer has long since synced), then (2) cap each project to its most recent
209
- * `maxPerProject` by cursor. Failure-isolated — a retention hiccup never throws.
210
- *
211
- * Performance note: this is why widening producers (truths/captures/failures/
212
- * decisions all emit now) can't grow the DB unbounded — maintenance trims it.
213
- */
214
- export function pruneEvents(db, opts = {}) {
215
- const olderThanDays = clamp(opts.olderThanDays, 1, 3650, 90);
216
- const maxPerProject = clamp(opts.maxPerProject, 1, 1_000_000, 5000);
217
- try {
218
- let deleted = 0;
219
- // (1) Age-based sweep.
220
- const cutoff = Date.now() - olderThanDays * DAY_MS;
221
- deleted += db.prepare(`DELETE FROM events WHERE created_at < ?`).run(cutoff).changes;
222
- // (2) Per-project count cap: keep the most recent `maxPerProject` by cursor.
223
- // The OFFSET row is the (max+1)-th newest cursor; deleting <= it keeps exactly
224
- // `maxPerProject`. Projects under the cap return NULL → `cursor <= NULL` deletes
225
- // nothing (safe no-op).
226
- const overflow = db.prepare(`SELECT project_id FROM events GROUP BY project_id HAVING COUNT(*) > ?`).all(maxPerProject);
227
- const trim = db.prepare(`
13
+ `).all(String(t),i,o)}function w(r,t={}){const e=a(t.olderThanDays,1,3650,90),n=a(t.maxPerProject,1,1e6,5e3);try{let o=0;const i=Date.now()-e*_;o+=r.prepare("DELETE FROM events WHERE created_at < ?").run(i).changes;const c=r.prepare("SELECT project_id FROM events GROUP BY project_id HAVING COUNT(*) > ?").all(n),u=r.prepare(`
228
14
  DELETE FROM events WHERE project_id = ? AND cursor <= (
229
15
  SELECT cursor FROM events WHERE project_id = ? ORDER BY cursor DESC LIMIT 1 OFFSET ?
230
16
  )
231
- `);
232
- for (const { project_id } of overflow) {
233
- deleted += trim.run(project_id, project_id, maxPerProject).changes;
234
- }
235
- return { deleted };
236
- }
237
- catch (err) {
238
- logger.warn('live-memory: pruneEvents failed', { err: err instanceof Error ? err.message : String(err) });
239
- return { deleted: 0 };
240
- }
241
- }
242
- /** Tiny persistent KV in wyrm_meta — used for replication watermarks. */
243
- export function getMeta(db, key) {
244
- return db.prepare(`SELECT value FROM wyrm_meta WHERE key = ?`).get(key)?.value;
245
- }
246
- export function setMeta(db, key, value) {
247
- db.prepare(`INSERT INTO wyrm_meta (key, value) VALUES (?, ?)
248
- ON CONFLICT(key) DO UPDATE SET value = excluded.value`).run(key, value);
249
- }
250
- //# sourceMappingURL=events.js.map
17
+ `);for(const{project_id:E}of c)o+=u.run(E,E,n).changes;return{deleted:o}}catch(o){return l.warn("live-memory: pruneEvents failed",{err:o instanceof Error?o.message:String(o)}),{deleted:0}}}function x(r,t){return r.prepare("SELECT value FROM wyrm_meta WHERE key = ?").get(t)?.value}function W(r,t,e){r.prepare(`INSERT INTO wyrm_meta (key, value) VALUES (?, ?)
18
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value`).run(t,e)}export{C as emitEvent,v as eventsForPush,D as eventsSince,S as getDeviceId,x as getMeta,j as ingestRemoteEvent,O as isLiveMemoryEnabled,w as pruneEvents,W as setMeta,F as subscribeEvents};