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