wyrm-mcp 7.2.0 → 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 (156) hide show
  1. package/LICENSE +26 -667
  2. package/NOTICE +14 -33
  3. package/dist/activation.d.ts.map +1 -1
  4. package/dist/activation.js +1 -44
  5. package/dist/activation.js.map +1 -1
  6. package/dist/agent-daemon.js +4 -281
  7. package/dist/agent-loop.js +7 -332
  8. package/dist/analytics.js +13 -236
  9. package/dist/attribution.js +1 -49
  10. package/dist/audit.js +2 -457
  11. package/dist/auto-capture.js +3 -138
  12. package/dist/auto-orchestrator.js +1 -325
  13. package/dist/autoconfig.js +39 -840
  14. package/dist/buddy-runner.js +1 -109
  15. package/dist/buddy.js +14 -564
  16. package/dist/build-flags.js +1 -17
  17. package/dist/capabilities.js +3 -183
  18. package/dist/capture.js +1 -56
  19. package/dist/causality.js +6 -107
  20. package/dist/cli.js +20 -281
  21. package/dist/cloud/cli.js +5 -541
  22. package/dist/cloud/client.js +1 -221
  23. package/dist/cloud/crypto.js +1 -85
  24. package/dist/cloud/machine-id.js +2 -113
  25. package/dist/cloud/recovery.js +1 -60
  26. package/dist/cloud/sync-engine.js +7 -543
  27. package/dist/cloud-backup.js +5 -579
  28. package/dist/cloud-profile.js +1 -138
  29. package/dist/cloud-sync-entrypoint.js +1 -47
  30. package/dist/cloud-sync.js +2 -309
  31. package/dist/constellation.js +12 -168
  32. package/dist/context-build-budgeted.js +4 -144
  33. package/dist/context-ranking.js +1 -69
  34. package/dist/crypto.js +1 -179
  35. package/dist/daemon-write-endpoint.js +1 -290
  36. package/dist/daemon-writer.js +2 -406
  37. package/dist/database.js +43 -1110
  38. package/dist/deprecations.js +2 -162
  39. package/dist/design.js +13 -141
  40. package/dist/event-replication.js +1 -112
  41. package/dist/events-sse.js +7 -43
  42. package/dist/events.js +6 -238
  43. package/dist/failure-patterns.js +42 -659
  44. package/dist/federation.js +12 -236
  45. package/dist/goals.js +13 -101
  46. package/dist/golden.js +3 -355
  47. package/dist/handlers/agent.js +4 -165
  48. package/dist/handlers/alias-adapters.js +1 -129
  49. package/dist/handlers/aliases.js +1 -171
  50. package/dist/handlers/audit.js +1 -87
  51. package/dist/handlers/boundary.js +1 -221
  52. package/dist/handlers/capture.js +73 -1109
  53. package/dist/handlers/causality.js +7 -114
  54. package/dist/handlers/cloud.js +85 -382
  55. package/dist/handlers/companion.js +28 -459
  56. package/dist/handlers/datalake.js +7 -187
  57. package/dist/handlers/dispatch-context.js +0 -22
  58. package/dist/handlers/entity.js +25 -256
  59. package/dist/handlers/events.js +16 -335
  60. package/dist/handlers/failure.js +13 -340
  61. package/dist/handlers/goals.js +4 -296
  62. package/dist/handlers/intelligence.js +126 -674
  63. package/dist/handlers/invoicing.js +1 -70
  64. package/dist/handlers/mcpclient.js +6 -137
  65. package/dist/handlers/orchestration.js +40 -125
  66. package/dist/handlers/output-schemas.js +1 -24
  67. package/dist/handlers/presence.js +3 -99
  68. package/dist/handlers/project.js +28 -182
  69. package/dist/handlers/prompts.js +6 -157
  70. package/dist/handlers/quest.js +4 -224
  71. package/dist/handlers/recall.js +11 -218
  72. package/dist/handlers/registry.js +1 -167
  73. package/dist/handlers/resources.js +1 -288
  74. package/dist/handlers/review.js +11 -74
  75. package/dist/handlers/run.js +17 -487
  76. package/dist/handlers/search.js +15 -326
  77. package/dist/handlers/session.js +28 -615
  78. package/dist/handlers/share.js +8 -184
  79. package/dist/handlers/shims.js +1 -464
  80. package/dist/handlers/skill.js +67 -449
  81. package/dist/handlers/survivors.js +1 -120
  82. package/dist/handlers/symbols.js +8 -109
  83. package/dist/handlers/syncops.js +4 -302
  84. package/dist/handlers/types.js +1 -27
  85. package/dist/harvest.js +5 -191
  86. package/dist/hours.js +7 -156
  87. package/dist/http-auth.js +3 -321
  88. package/dist/http-fast.js +21 -1137
  89. package/dist/icons.js +1 -47
  90. package/dist/index.js +2 -924
  91. package/dist/indexer.js +4 -145
  92. package/dist/intelligence.js +31 -261
  93. package/dist/internal-dispatch.js +3 -212
  94. package/dist/keyset.js +1 -110
  95. package/dist/knowledge-graph.js +12 -176
  96. package/dist/license.d.ts +11 -0
  97. package/dist/license.d.ts.map +1 -1
  98. package/dist/license.js +2 -414
  99. package/dist/license.js.map +1 -1
  100. package/dist/logger.js +2 -199
  101. package/dist/maintenance.js +2 -148
  102. package/dist/mcp-client.js +6 -262
  103. package/dist/memory-artifacts.js +30 -449
  104. package/dist/migrate-prompt.js +2 -124
  105. package/dist/migrations.js +40 -655
  106. package/dist/performance.js +1 -228
  107. package/dist/presence.js +11 -140
  108. package/dist/priority-embed.js +5 -164
  109. package/dist/providers/embedding-provider.js +1 -196
  110. package/dist/readonly-gate.js +1 -29
  111. package/dist/rehydration.js +9 -157
  112. package/dist/reindex.js +1 -88
  113. package/dist/render-target.js +21 -514
  114. package/dist/render.js +4 -280
  115. package/dist/repl-guard.js +1 -173
  116. package/dist/replication-daemon-entrypoint.js +1 -31
  117. package/dist/replication-daemon.js +2 -262
  118. package/dist/resilience.js +1 -591
  119. package/dist/reverse-bridge.js +5 -360
  120. package/dist/security.js +1 -244
  121. package/dist/session-seen.js +3 -51
  122. package/dist/setup.js +1 -260
  123. package/dist/skill-author.js +5 -168
  124. package/dist/spec-kit.js +1 -191
  125. package/dist/sqlite-busy.js +1 -154
  126. package/dist/statusline.js +11 -315
  127. package/dist/sub-agent.js +13 -262
  128. package/dist/summarizer.js +13 -139
  129. package/dist/symbols.js +7 -283
  130. package/dist/sync.js +5 -359
  131. package/dist/tasks-dispatch.js +1 -84
  132. package/dist/tasks.js +1 -282
  133. package/dist/token-budget.js +1 -143
  134. package/dist/tool-analytics.js +7 -129
  135. package/dist/tool-annotations.js +1 -365
  136. package/dist/tool-manifest-v2.json +1 -1
  137. package/dist/tool-manifest.json +1 -1
  138. package/dist/tool-profiles.js +1 -75
  139. package/dist/trace-harvest.js +6 -244
  140. package/dist/types.js +1 -30
  141. package/dist/ui-dashboard.js +41 -50
  142. package/dist/ulid.js +1 -81
  143. package/dist/validate.js +1 -129
  144. package/dist/vault.js +1 -534
  145. package/dist/vectors.js +3 -184
  146. package/dist/version-check.js +4 -136
  147. package/dist/visibility.js +19 -155
  148. package/dist/wyrm-cli.js +98 -2451
  149. package/dist/wyrm-cli.js.map +1 -1
  150. package/dist/wyrm-guard.js +14 -424
  151. package/dist/wyrm-loop.js +3 -150
  152. package/dist/wyrm-manifest.json +1 -1
  153. package/dist/wyrm-statusline-daemon.js +1 -11
  154. package/dist/wyrm-statusline.js +4 -56
  155. package/dist/wyrm-ui.js +9 -77
  156. 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};