vektor-slipstream 1.3.6 → 1.3.8

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/CHANGELOG.md ADDED
@@ -0,0 +1,139 @@
1
+ # VEKTOR v1.3.7 — Changelog
2
+
3
+ Released: 2026-04-05
4
+
5
+ ## New features
6
+
7
+ ### memory.forget(id)
8
+ Hard-deletes a memory and cascades to all graph edges. Throws if memory is pinned — must unpin first.
9
+ ```js
10
+ await memory.forget('mem_abc123');
11
+ // → { deleted: true, edgesRemoved: 4 }
12
+ ```
13
+
14
+ ### memory.forgetWhere(query, opts)
15
+ Semantic bulk-delete. Preview with `dryRun: true` before committing.
16
+ ```js
17
+ await memory.forgetWhere('Project Atlas', { dryRun: true });
18
+ await memory.forgetWhere('Project Atlas', { minScore: 0.9, limit: 5 });
19
+ ```
20
+
21
+ ### memory.pin(id) / memory.unpin(id) / memory.listPinned()
22
+ Mark memories as permanent — REM cycle will never compress or delete them.
23
+ ```js
24
+ const { id } = await memory.remember('User is allergic to peanuts');
25
+ await memory.pin(id);
26
+ await memory.listPinned(); // → all pinned memories
27
+ ```
28
+
29
+ ### memory.inspect()
30
+ Full diagnostic snapshot — no more raw SQLite queries to debug state.
31
+ ```js
32
+ const state = memory.inspect();
33
+ state.counts // → { memories: 247, pinned: 3, edges: 7180 }
34
+ state.rem // → { totalDreams: 11, compressionRatio: '0.06' }
35
+ state.graph.topNodes // → 5 most-connected memories
36
+ ```
37
+
38
+ ### memory.auditLog(opts) / memory.auditStats()
39
+ Full AUDN decision history. Nothing disappears silently.
40
+ ```js
41
+ memory.auditLog({ action: 'DELETE', since: '7d' });
42
+ // → [{ action, memory_id, content, reason, similarity, ran_at }]
43
+ ```
44
+
45
+ ### memory.briefing({ since }) — scoped date filtering
46
+ ```js
47
+ await memory.briefing({ since: '7d' });
48
+ await memory.briefing({ since: '12h', minImportance: 0.7 });
49
+ await memory.briefing({ since: '2026-01-01', raw: true });
50
+ ```
51
+
52
+ ### memory.export() / memory.import()
53
+ Full graph serialisation with checksum integrity.
54
+ ```js
55
+ const bundle = memory.export();
56
+ fs.writeFileSync('backup.vektor.json', JSON.stringify(bundle));
57
+
58
+ memory.import(bundle);
59
+ // → { imported: 244, skipped: 3, edgesAdded: 7100 }
60
+
61
+ memory.import(bundle, { dryRun: true }); // preview
62
+ memory.import(bundle, { mode: 'replace' }); // wipe and restore
63
+ ```
64
+
65
+ ### Namespace support
66
+ Scope all reads/writes to prevent project bleed.
67
+ ```js
68
+ const memory = await createMemory({ agentId: 'my-agent', namespace: 'project-atlas' });
69
+ memory.listNamespaces();
70
+ // → [{ namespace: 'project-atlas', memories: 18 }, ...]
71
+ ```
72
+
73
+ ### TypeScript types
74
+ Full `.d.ts` declarations for all public API methods. No more flying blind in your IDE.
75
+ Add to `tsconfig.json`: `"types": ["vektor-slipstream"]`
76
+
77
+ ### Structured error codes
78
+ Replace string catch blocks with reliable `VektorError.code` switching.
79
+ ```js
80
+ import { VektorError, ERR } from 'vektor-slipstream/errors';
81
+
82
+ try {
83
+ await memory.remember(...)
84
+ } catch (e) {
85
+ if (e instanceof VektorError) {
86
+ switch (e.code) {
87
+ case ERR.LICENCE_INVALID: // handle
88
+ case ERR.EMBED_FAILED: // retry
89
+ }
90
+ }
91
+ }
92
+ ```
93
+
94
+ ## Schema changes
95
+ Run `npx vektor migrate` after updating, or migration runs automatically on startup.
96
+
97
+ New columns:
98
+ - `memories.pinned` INTEGER DEFAULT 0
99
+ - `memories.namespace` TEXT DEFAULT 'default'
100
+ - `memories.updated_at` INTEGER
101
+
102
+ New tables:
103
+ - `audn_log` — AUDN decision audit trail
104
+ - `rem_log` — REM cycle run history (if not already present)
105
+ - `vektor_meta` — schema version tracking
106
+
107
+ ## Bug fixes
108
+ - REM cycle now skips pinned memories (previously could compress critical facts)
109
+ - AUDN deletions now logged before execution (previously silent)
110
+ - briefing() no longer returns stale summary when no memories exist in window
111
+
112
+ ## Breaking changes
113
+ None. All new methods are additive. Existing code works unchanged.
114
+ Migration is backward-compatible — new columns have safe defaults.
115
+
116
+ ## Gemini security review fixes (round 1 — applied)
117
+ - `briefing.js` — pinned memory query now has LIMIT 50 (context bomb prevention)
118
+ - `briefing.js` — `parseSince()` guards against `Date.now()` ms being passed as unix seconds
119
+ - `forget.js` — `forgetWhere()` selective error catching — infra errors re-thrown, only expected skips swallowed
120
+ - `namespace.js` — O(N) JS delete loop replaced with bulk SQL subquery (2 statements vs N*2)
121
+ - `inspect.js` — all `SUM()` aggregations wrapped in `IFNULL(..., 0)` — no more `null` in charts
122
+ - `migrate-137.js` — `PRAGMA table_info()` replaces brittle error-message string matching
123
+
124
+ ## Gemini security review fixes (round 2 — applied)
125
+ - `export-import.js` — Fix 1: `IN (...memIds)` placeholders replaced with SQL subqueries — no more 32,766 param limit crash on large exports
126
+ - `export-import.js` — Fix 2: O(N) loop in `replace` mode replaced with bulk subquery deletes
127
+ - `export-import.js` — Fix 3: checksum now hashes full memories payload — content/importance tampering now detectable
128
+ - `export-import.js` — Fix 4: dangling edge guard — edges only inserted if both source and target exist in destination (`WHERE EXISTS`)
129
+ - `export-import.js` — `edgesSkipped` added to import return value with console.warn for partial imports
130
+
131
+ ## Gemini security review fixes (round 3 — applied)
132
+ - `forget.js` — pinned edge orphan guard: blocks deleting a memory that shares
133
+ a graph edge with a pinned memory — prevents silent MAGMA traversal corruption
134
+ - `namespace.js` — SQLITE_BUSY retry loop: 3 attempts × 100ms using
135
+ `Atomics.wait` (correct sync sleep for better-sqlite3) before propagating error
136
+ - `export-import.js` — confirmed: empty bundle guard (line 103) already sits
137
+ before checksum computation (line 107) — correct order, no change needed
138
+
139
+ ## Total fixes across 3 Gemini review rounds: 13
package/audn-log.js ADDED
@@ -0,0 +1,143 @@
1
+ /**
2
+ * VEKTOR AUDN audit log — v1.3.7
3
+ * Records every ADD/UPDATE/DELETE/NO_OP decision AUDN makes
4
+ * before acting on it. Full transparency — nothing disappears silently.
5
+ *
6
+ * Schema additions required:
7
+ * CREATE TABLE IF NOT EXISTS audn_log (
8
+ * id INTEGER PRIMARY KEY AUTOINCREMENT,
9
+ * agent_id TEXT NOT NULL,
10
+ * namespace TEXT NOT NULL,
11
+ * action TEXT NOT NULL, -- ADD | UPDATE | DELETE | NO_OP
12
+ * memory_id TEXT, -- affected memory id (null for ADD before insert)
13
+ * content TEXT, -- snapshot of content at time of action
14
+ * reason TEXT, -- why AUDN made this decision
15
+ * similarity REAL, -- similarity score that triggered the decision
16
+ * ran_at INTEGER DEFAULT (unixepoch())
17
+ * );
18
+ * CREATE INDEX IF NOT EXISTS idx_audn_log_agent ON audn_log(agent_id, namespace, ran_at);
19
+ */
20
+
21
+ /**
22
+ * logAudn — call this inside AUDN before executing each decision
23
+ *
24
+ * @param {object} db
25
+ * @param {string} agentId
26
+ * @param {string} namespace
27
+ * @param {object} entry
28
+ * @param {string} entry.action - ADD | UPDATE | DELETE | NO_OP
29
+ * @param {string} entry.memoryId - memory id (if known)
30
+ * @param {string} entry.content - content snapshot
31
+ * @param {string} entry.reason - human-readable reason
32
+ * @param {number} entry.similarity - cosine score that triggered this
33
+ */
34
+ export function logAudn(db, agentId, namespace, entry) {
35
+ db.prepare(`
36
+ INSERT INTO audn_log (agent_id, namespace, action, memory_id, content, reason, similarity)
37
+ VALUES (?, ?, ?, ?, ?, ?, ?)
38
+ `).run(
39
+ agentId,
40
+ namespace,
41
+ entry.action,
42
+ entry.memoryId || null,
43
+ entry.content ? entry.content.slice(0, 500) : null,
44
+ entry.reason || null,
45
+ entry.similarity || null
46
+ );
47
+ }
48
+
49
+ /**
50
+ * auditLog(opts) — retrieve AUDN decision history
51
+ *
52
+ * @param {object} db
53
+ * @param {string} agentId
54
+ * @param {string} namespace
55
+ * @param {object} opts
56
+ * @param {number} opts.limit - max rows (default 50)
57
+ * @param {string} opts.action - filter to specific action (ADD|UPDATE|DELETE|NO_OP)
58
+ * @param {string} opts.since - time filter — '7d', '24h', ISO date
59
+ * @returns {object[]}
60
+ */
61
+ export function auditLog(db, agentId, namespace, opts = {}) {
62
+ const { limit = 50, action = null, since = null } = opts;
63
+
64
+ let sinceTs = null;
65
+ if (since) {
66
+ const rel = String(since).match(/^(\d+)(d|h|m)$/);
67
+ if (rel) {
68
+ const n = parseInt(rel[1]);
69
+ const unit = rel[2];
70
+ const seconds = unit === 'd' ? n * 86400 : unit === 'h' ? n * 3600 : n * 60;
71
+ sinceTs = Math.floor(Date.now() / 1000) - seconds;
72
+ } else {
73
+ sinceTs = Math.floor(Date.parse(since) / 1000);
74
+ }
75
+ }
76
+
77
+ const actionClause = action ? `AND action = '${action.toUpperCase()}'` : '';
78
+ const sinceClause = sinceTs ? `AND ran_at >= ${sinceTs}` : '';
79
+
80
+ return db.prepare(`
81
+ SELECT id, action, memory_id, content, reason, similarity, ran_at
82
+ FROM audn_log
83
+ WHERE agent_id = ? AND namespace = ?
84
+ ${actionClause}
85
+ ${sinceClause}
86
+ ORDER BY ran_at DESC
87
+ LIMIT ?
88
+ `).all(agentId, namespace, limit);
89
+ }
90
+
91
+ /**
92
+ * auditStats() — summary counts by action type
93
+ */
94
+ export function auditStats(db, agentId, namespace) {
95
+ return db.prepare(`
96
+ SELECT
97
+ action,
98
+ COUNT(*) as count,
99
+ MAX(ran_at) as last_at
100
+ FROM audn_log
101
+ WHERE agent_id = ? AND namespace = ?
102
+ GROUP BY action
103
+ ORDER BY count DESC
104
+ `).all(agentId, namespace);
105
+ }
106
+
107
+ /**
108
+ * pruneAuditLog(days) — keep log from growing forever
109
+ * Call from REM cycle or a scheduled job
110
+ */
111
+ export function pruneAuditLog(db, agentId, namespace, keepDays = 30) {
112
+ const cutoff = Math.floor(Date.now() / 1000) - keepDays * 86400;
113
+ const result = db.prepare(
114
+ `DELETE FROM audn_log WHERE agent_id = ? AND namespace = ? AND ran_at < ?`
115
+ ).run(agentId, namespace, cutoff);
116
+ return { pruned: result.changes };
117
+ }
118
+
119
+ /**
120
+ * Integration into Memory class / AUDN loop:
121
+ *
122
+ * // Inside AUDN decision logic:
123
+ * logAudn(this.db, this.agentId, this.namespace, {
124
+ * action: 'DELETE',
125
+ * memoryId: existing.id,
126
+ * content: existing.content,
127
+ * reason: 'Contradicts newer memory with score 0.94',
128
+ * similarity: 0.94
129
+ * });
130
+ * // then execute the delete
131
+ *
132
+ * // Expose on Memory class:
133
+ * auditLog(opts = {}) {
134
+ * return auditLog(this.db, this.agentId, this.namespace, opts);
135
+ * }
136
+ *
137
+ * Usage:
138
+ * memory.auditLog({ action: 'DELETE', since: '7d' });
139
+ * // → [{ id, action:'DELETE', content:'...', reason:'...', ran_at }]
140
+ *
141
+ * memory.auditLog({ limit: 100 });
142
+ * // → last 100 AUDN decisions
143
+ */
package/briefing.js ADDED
@@ -0,0 +1,150 @@
1
+ /**
2
+ * VEKTOR briefing() date scoping — v1.3.7
3
+ * Adds `since` param to briefing() — filter to last N days/hours
4
+ * or an explicit ISO timestamp.
5
+ *
6
+ * Drop-in replacement for existing briefing() implementation.
7
+ */
8
+
9
+ import { VektorError, ERR } from './errors.js';
10
+
11
+ /**
12
+ * Parse a `since` value into a Unix timestamp (seconds).
13
+ * Accepts:
14
+ * '1d' → last 1 day
15
+ * '7d' → last 7 days
16
+ * '12h' → last 12 hours
17
+ * '30m' → last 30 minutes
18
+ * '2026-01-01T00:00:00Z' → explicit ISO date
19
+ * 1735689600 → raw unix timestamp
20
+ */
21
+ export function parseSince(since) {
22
+ if (!since) return null;
23
+
24
+ // Raw unix timestamp — guard against Date.now() (ms) being passed accidentally
25
+ // If number > 9999999999 it's milliseconds (year ~2001 in seconds) — convert it
26
+ if (typeof since === 'number') {
27
+ return since > 9_999_999_999 ? Math.floor(since / 1000) : since;
28
+ }
29
+
30
+ // Relative shorthand: 7d, 12h, 30m
31
+ const rel = String(since).match(/^(\d+)(d|h|m)$/);
32
+ if (rel) {
33
+ const n = parseInt(rel[1]);
34
+ const unit = rel[2];
35
+ const seconds = unit === 'd' ? n * 86400 : unit === 'h' ? n * 3600 : n * 60;
36
+ return Math.floor(Date.now() / 1000) - seconds;
37
+ }
38
+
39
+ // ISO date string
40
+ const ts = Date.parse(since);
41
+ if (!isNaN(ts)) return Math.floor(ts / 1000);
42
+
43
+ throw new VektorError(
44
+ ERR.MEMORY_READ_FAILED,
45
+ `Invalid since value: "${since}". Use "7d", "12h", "30m", ISO date, or unix timestamp.`
46
+ );
47
+ }
48
+
49
+ /**
50
+ * briefing({ since, limit, minImportance })
51
+ * Returns a structured summary of recent memories for agent context injection.
52
+ *
53
+ * @param {object} db
54
+ * @param {string} agentId
55
+ * @param {string} namespace
56
+ * @param {Function} summariseFn - LLM summarise function (existing REM summariser)
57
+ * @param {object} opts
58
+ * @param {string|number} opts.since - time filter (default: '1d')
59
+ * @param {number} opts.limit - max memories (default: 20)
60
+ * @param {number} opts.minImportance - filter by importance score (default: 0)
61
+ * @param {boolean} opts.raw - return raw memories, skip LLM summary
62
+ * @returns {object} { period, memoryCount, pinned, summary, memories }
63
+ */
64
+ export async function briefing(db, agentId, namespace, summariseFn, opts = {}) {
65
+ const {
66
+ since = '1d',
67
+ limit = 20,
68
+ minImportance = 0,
69
+ raw = false
70
+ } = opts;
71
+
72
+ const sinceTs = parseSince(since);
73
+ const sinceClause = sinceTs ? `AND created_at >= ${sinceTs}` : '';
74
+ const importanceClause = minImportance > 0 ? `AND importance >= ${minImportance}` : '';
75
+
76
+ // Fetch scoped memories
77
+ const memories = db.prepare(`
78
+ SELECT id, content, summary, importance, pinned, created_at
79
+ FROM memories
80
+ WHERE agent_id = ? AND namespace = ?
81
+ ${sinceClause}
82
+ ${importanceClause}
83
+ ORDER BY importance DESC, created_at DESC
84
+ LIMIT ?
85
+ `).all(agentId, namespace, limit);
86
+
87
+ // Always fetch pinned regardless of time window — hard cap 50 to prevent context bomb
88
+ // (an agent running for months could accumulate hundreds of pinned memories)
89
+ const pinnedLimit = 50;
90
+ const pinned = db.prepare(`
91
+ SELECT id, content, summary, importance, created_at
92
+ FROM memories
93
+ WHERE agent_id = ? AND namespace = ? AND pinned = 1
94
+ ORDER BY importance DESC
95
+ LIMIT ${pinnedLimit}
96
+ `).all(agentId, namespace);
97
+
98
+ if (raw) {
99
+ return {
100
+ period: since,
101
+ memoryCount: memories.length,
102
+ pinned,
103
+ memories
104
+ };
105
+ }
106
+
107
+ // Build prompt context for LLM summary
108
+ const context = [
109
+ pinned.length > 0
110
+ ? `PINNED (permanent):\n${pinned.map(m => `- ${m.summary || m.content}`).join('\n')}`
111
+ : null,
112
+ memories.length > 0
113
+ ? `RECENT (${since}):\n${memories.map(m => `- [${m.importance?.toFixed(2)}] ${m.summary || m.content}`).join('\n')}`
114
+ : 'No recent memories in this period.'
115
+ ].filter(Boolean).join('\n\n');
116
+
117
+ const summary = await summariseFn(
118
+ `Produce a concise agent briefing from these memories. Lead with pinned facts, then recent activity.\n\n${context}`
119
+ );
120
+
121
+ return {
122
+ period: since,
123
+ memoryCount: memories.length,
124
+ pinnedCount: pinned.length,
125
+ pinned,
126
+ memories,
127
+ summary
128
+ };
129
+ }
130
+
131
+ /**
132
+ * Integration into Memory class:
133
+ *
134
+ * async briefing(opts = {}) {
135
+ * return briefing(this.db, this.agentId, this.namespace, this._summarise.bind(this), opts);
136
+ * }
137
+ *
138
+ * Usage:
139
+ * await memory.briefing();
140
+ * // → last 24h summary (default)
141
+ *
142
+ * await memory.briefing({ since: '7d' });
143
+ * // → last 7 days
144
+ *
145
+ * await memory.briefing({ since: '2026-01-01', minImportance: 0.7 });
146
+ * // → since Jan 1st, high importance only
147
+ *
148
+ * await memory.briefing({ raw: true, since: '1h' });
149
+ * // → raw memory objects, no LLM call
150
+ */