@securecoms/agent-bridge 0.28.2 → 0.30.0
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/README.md +288 -81
- package/dist/bin.d.ts +1 -1
- package/dist/bin.js +5112 -508
- package/dist/bridge-agent.d.ts +393 -3
- package/dist/config.d.ts +9 -0
- package/dist/consumer-grants.d.ts +19 -0
- package/dist/grants/binding.d.ts +58 -0
- package/dist/grants/effective.d.ts +47 -0
- package/dist/grants/manifest.d.ts +68 -0
- package/dist/grants/owner-turn.d.ts +52 -0
- package/dist/grants/persona-render.d.ts +30 -0
- package/dist/grants/pin-command.d.ts +31 -0
- package/dist/grants/pin.d.ts +35 -0
- package/dist/grants/room-search-cap.d.ts +15 -0
- package/dist/grants/voice-fence.d.ts +53 -0
- package/dist/http-server.d.ts +1 -0
- package/dist/index.js +4658 -248
- package/dist/memory/note-frames.d.ts +1 -1
- package/dist/memory/unified/chunk.d.ts +12 -0
- package/dist/memory/unified/config.d.ts +7 -0
- package/dist/memory/unified/consumer-unified.d.ts +21 -0
- package/dist/memory/unified/escape.d.ts +23 -0
- package/dist/memory/unified/index-key.d.ts +8 -0
- package/dist/memory/unified/item-book.d.ts +73 -0
- package/dist/memory/unified/item-reembed.d.ts +20 -0
- package/dist/memory/unified/migrate.d.ts +79 -0
- package/dist/memory/unified/owner-scope.d.ts +48 -0
- package/dist/memory/unified/pin-rules.d.ts +18 -0
- package/dist/memory/unified/ranking-client.d.ts +48 -0
- package/dist/memory/unified/ranking-core-constants.d.ts +8 -0
- package/dist/memory/unified/ranking-core.d.ts +117 -0
- package/dist/memory/unified/ranking-inprocess.d.ts +3 -0
- package/dist/memory/unified/ranking-worker.d.ts +1 -0
- package/dist/memory/unified/read-book.d.ts +81 -0
- package/dist/memory/unified/switch.d.ts +8 -0
- package/dist/memory/unified/text.d.ts +15 -0
- package/dist/memory/unified/worker-protocol.d.ts +106 -0
- package/dist/memory-taps.d.ts +1 -1
- package/dist/memory-worker.js +598 -0
- package/dist/service/runtime-config-dir.d.ts +15 -0
- package/dist/version.d.ts +1 -1
- package/dist/ws-server.d.ts +3 -1
- package/package.json +1 -1
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
// src/memory/unified/ranking-worker.ts
|
|
2
|
+
import { parentPort, workerData } from "node:worker_threads";
|
|
3
|
+
import { DatabaseSync } from "node:sqlite";
|
|
4
|
+
|
|
5
|
+
// ../connect-core/dist/envelope/aead.js
|
|
6
|
+
import { gcm } from "@noble/ciphers/aes";
|
|
7
|
+
import { xchacha20poly1305 } from "@noble/ciphers/chacha";
|
|
8
|
+
import { randomBytes } from "@noble/hashes/utils";
|
|
9
|
+
|
|
10
|
+
// ../../node_modules/@agentvault/crypto/dist/chunk-FCTZTCBV.js
|
|
11
|
+
var ENCODER = new TextEncoder();
|
|
12
|
+
|
|
13
|
+
// ../connect-core/dist/base64.js
|
|
14
|
+
var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
15
|
+
var LOOKUP = (() => {
|
|
16
|
+
const t = new Int16Array(256).fill(-1);
|
|
17
|
+
for (let i = 0; i < ALPHABET.length; i++)
|
|
18
|
+
t[ALPHABET.charCodeAt(i)] = i;
|
|
19
|
+
return t;
|
|
20
|
+
})();
|
|
21
|
+
|
|
22
|
+
// ../connect-core/dist/envelope/aead.js
|
|
23
|
+
var APPLICATION_KEY_BYTES = 32;
|
|
24
|
+
var NONCE_BYTES = {
|
|
25
|
+
xchacha20poly1305: 24,
|
|
26
|
+
aes256gcm: 12
|
|
27
|
+
};
|
|
28
|
+
function cipherFor(suite, key, nonce, aad) {
|
|
29
|
+
return suite === "aes256gcm" ? gcm(key, nonce, aad) : xchacha20poly1305(key, nonce, aad);
|
|
30
|
+
}
|
|
31
|
+
function assertKey(key) {
|
|
32
|
+
if (key.length !== APPLICATION_KEY_BYTES) {
|
|
33
|
+
throw new Error(`application key must be ${APPLICATION_KEY_BYTES} bytes (the MLS exporter output); got ${key.length}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function openPayload(p) {
|
|
37
|
+
assertKey(p.key);
|
|
38
|
+
const nlen = NONCE_BYTES[p.suite];
|
|
39
|
+
if (p.ciphertext.length < nlen) {
|
|
40
|
+
throw new Error("openPayload: ciphertext shorter than the AEAD nonce");
|
|
41
|
+
}
|
|
42
|
+
const nonce = p.ciphertext.subarray(0, nlen);
|
|
43
|
+
const body = p.ciphertext.subarray(nlen);
|
|
44
|
+
return cipherFor(p.suite, p.key, nonce, p.associatedData).decrypt(body);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ../connect-mls-store/dist/memory-recall.js
|
|
48
|
+
var RECALL_MAX_DIMS = 1024;
|
|
49
|
+
var enc = new TextEncoder();
|
|
50
|
+
function decodeVec(b) {
|
|
51
|
+
if (b.length === 0 || b.length % 4 !== 0 || b.length / 4 > RECALL_MAX_DIMS)
|
|
52
|
+
return null;
|
|
53
|
+
const dv = new DataView(b.buffer, b.byteOffset, b.length);
|
|
54
|
+
const out = new Float32Array(b.length / 4);
|
|
55
|
+
for (let i = 0; i < out.length; i++) {
|
|
56
|
+
out[i] = dv.getFloat32(i * 4, true);
|
|
57
|
+
if (!Number.isFinite(out[i]))
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
var RECALL_FUTURE_SKEW_MS = 5 * 60 * 1e3;
|
|
63
|
+
var W9_SPENT_HORIZON_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
64
|
+
|
|
65
|
+
// ../connect-mls-store/dist/memory-items.js
|
|
66
|
+
var ITEM_SOURCES = [
|
|
67
|
+
"owner_typed",
|
|
68
|
+
"owner_voice",
|
|
69
|
+
"agent_reply",
|
|
70
|
+
"owner_pin",
|
|
71
|
+
"migrated_note",
|
|
72
|
+
"migrated_recall",
|
|
73
|
+
"imported_history",
|
|
74
|
+
"room_member",
|
|
75
|
+
"room_voice",
|
|
76
|
+
"person",
|
|
77
|
+
"file",
|
|
78
|
+
"room_transcript"
|
|
79
|
+
];
|
|
80
|
+
var OWNER_CAPABLE_SOURCES = /* @__PURE__ */ new Set(["owner_typed", "owner_voice", "owner_pin", "migrated_note", "migrated_recall"]);
|
|
81
|
+
var ITEM_CAP_DEFAULT = 1e5;
|
|
82
|
+
var BYTE_CAP_DEFAULT = 2 * 1024 * 1024 * 1024;
|
|
83
|
+
var ITEM_ROW_OVERHEAD_BYTES = Math.max(48, 2388);
|
|
84
|
+
var TOKEN_ROW_OVERHEAD_BYTES = Math.max(48, 180);
|
|
85
|
+
var NUL = "\0";
|
|
86
|
+
var enc2 = new TextEncoder();
|
|
87
|
+
var noNul = (v, what) => {
|
|
88
|
+
if (v.includes(NUL))
|
|
89
|
+
throw new RangeError(`${what} holds a NUL`);
|
|
90
|
+
};
|
|
91
|
+
function parseOwnerDmScopeId(scopeId) {
|
|
92
|
+
const parts = scopeId.split(NUL);
|
|
93
|
+
return parts.length === 2 && parts[0] && parts[1] ? { channelId: parts[0], ownerIdentityId: parts[1] } : null;
|
|
94
|
+
}
|
|
95
|
+
function assertScope(kind, scopeId) {
|
|
96
|
+
if (kind === "owner_dm") {
|
|
97
|
+
if (parseOwnerDmScopeId(scopeId) === null)
|
|
98
|
+
throw new RangeError("owner_dm scope id must be channel\u20160\u2016owner");
|
|
99
|
+
} else
|
|
100
|
+
noNul(scopeId, "scope id");
|
|
101
|
+
}
|
|
102
|
+
function assertFields(identityId, f) {
|
|
103
|
+
noNul(identityId, "identity id");
|
|
104
|
+
noNul(f.channelId, "channel id");
|
|
105
|
+
noNul(f.originId, "origin id");
|
|
106
|
+
noNul(f.parentOriginId, "parent origin id");
|
|
107
|
+
assertScope(f.scopeKind, f.scopeId);
|
|
108
|
+
if (!Number.isSafeInteger(f.chunkIndex) || f.chunkIndex < 0)
|
|
109
|
+
throw new RangeError("chunk index");
|
|
110
|
+
if (!Number.isSafeInteger(f.originAt) || !Number.isSafeInteger(f.storedAt))
|
|
111
|
+
throw new RangeError("timestamps");
|
|
112
|
+
const chunk = f.chunkIndex > 0;
|
|
113
|
+
if (chunk !== (f.parentId !== null) || chunk !== (f.parentOriginId !== ""))
|
|
114
|
+
throw new RangeError("parent fields must be set exactly for a chunk");
|
|
115
|
+
if (f.parentId !== null && (!Number.isSafeInteger(f.parentId) || f.parentId < 1))
|
|
116
|
+
throw new RangeError("parent id");
|
|
117
|
+
}
|
|
118
|
+
function itemRecordAad(identityId, f) {
|
|
119
|
+
assertFields(identityId, f);
|
|
120
|
+
return enc2.encode([
|
|
121
|
+
"agent_memory_item",
|
|
122
|
+
identityId,
|
|
123
|
+
f.scopeKind,
|
|
124
|
+
f.scopeId,
|
|
125
|
+
f.channelId,
|
|
126
|
+
f.kind,
|
|
127
|
+
f.originId,
|
|
128
|
+
String(f.chunkIndex),
|
|
129
|
+
f.parentOriginId,
|
|
130
|
+
f.parentId === null ? "" : String(f.parentId),
|
|
131
|
+
String(f.originAt),
|
|
132
|
+
String(f.storedAt)
|
|
133
|
+
].join(NUL));
|
|
134
|
+
}
|
|
135
|
+
function itemVecAad(identityId, f, modelId) {
|
|
136
|
+
noNul(identityId, "identity id");
|
|
137
|
+
noNul(f.originId, "origin id");
|
|
138
|
+
noNul(modelId, "model id");
|
|
139
|
+
assertScope(f.scopeKind, f.scopeId);
|
|
140
|
+
if (!Number.isSafeInteger(f.chunkIndex) || f.chunkIndex < 0)
|
|
141
|
+
throw new RangeError("chunk index");
|
|
142
|
+
return enc2.encode(["agent_memory_item_vec", identityId, f.scopeKind, f.scopeId, f.originId, String(f.chunkIndex), modelId].join(NUL));
|
|
143
|
+
}
|
|
144
|
+
function decodeItemRecord(b) {
|
|
145
|
+
let o;
|
|
146
|
+
try {
|
|
147
|
+
o = JSON.parse(new TextDecoder().decode(b));
|
|
148
|
+
} catch {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
if (!o || typeof o !== "object" || Array.isArray(o))
|
|
152
|
+
return null;
|
|
153
|
+
const { v, t, s, tr, sr, c, ro, or } = o;
|
|
154
|
+
if (v !== 1 || typeof t !== "string" || typeof s !== "string" || !ITEM_SOURCES.includes(s))
|
|
155
|
+
return null;
|
|
156
|
+
if (tr !== "owner" && tr !== "reference")
|
|
157
|
+
return null;
|
|
158
|
+
if (tr === "owner" && !OWNER_CAPABLE_SOURCES.has(s))
|
|
159
|
+
return null;
|
|
160
|
+
if (sr !== void 0 && typeof sr !== "string")
|
|
161
|
+
return null;
|
|
162
|
+
if (c !== void 0 && (typeof c !== "number" || !Number.isSafeInteger(c) || c < 1))
|
|
163
|
+
return null;
|
|
164
|
+
if (ro !== void 0 && ro !== "peer" && ro !== "agent")
|
|
165
|
+
return null;
|
|
166
|
+
if (or !== void 0 && or !== "fast_path" && or !== "reply" && or !== "untrusted")
|
|
167
|
+
return null;
|
|
168
|
+
return {
|
|
169
|
+
text: t,
|
|
170
|
+
source: s,
|
|
171
|
+
trust: tr,
|
|
172
|
+
...sr !== void 0 ? { sourceRef: sr } : {},
|
|
173
|
+
...c !== void 0 ? { chunks: c } : {},
|
|
174
|
+
...ro !== void 0 ? { role: ro } : {},
|
|
175
|
+
...or !== void 0 ? { origin: or } : {}
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// src/memory/unified/ranking-core.ts
|
|
180
|
+
var DIMS = 384;
|
|
181
|
+
var OWNER_SCOPE_PRIOR = 0.05;
|
|
182
|
+
var RECENCY_FLOOR = 0.85;
|
|
183
|
+
var RECENCY_SLOPE = 0.05;
|
|
184
|
+
var RRF_K = 60;
|
|
185
|
+
var HOT_TOKEN_MAX_POSTINGS = 5e3;
|
|
186
|
+
var LIST_MAX = 50;
|
|
187
|
+
var FRESHNESS_TIE_EPSILON = 1e-12;
|
|
188
|
+
var HOT_SET_HEADROOM = 1024;
|
|
189
|
+
var HOT_SET_GROWTH = 1.25;
|
|
190
|
+
var DAY_MS = 864e5;
|
|
191
|
+
function recencyWeight(ageDays, slope = RECENCY_SLOPE) {
|
|
192
|
+
return Math.max(RECENCY_FLOOR, 1 - Math.max(0, ageDays) / 365 * slope);
|
|
193
|
+
}
|
|
194
|
+
function hotSetReserve(embeddedCount) {
|
|
195
|
+
return Math.min(Math.max(0, embeddedCount), ITEM_CAP_DEFAULT) + HOT_SET_HEADROOM;
|
|
196
|
+
}
|
|
197
|
+
function hotSetGrowth(cap, need) {
|
|
198
|
+
let next = Math.min(Math.ceil(cap * HOT_SET_GROWTH), ITEM_CAP_DEFAULT);
|
|
199
|
+
if (next <= cap) next = cap + HOT_SET_HEADROOM;
|
|
200
|
+
return Math.max(need, next, HOT_SET_HEADROOM);
|
|
201
|
+
}
|
|
202
|
+
var HotSet = class {
|
|
203
|
+
cap = 0;
|
|
204
|
+
n = 0;
|
|
205
|
+
q = new Int8Array(0);
|
|
206
|
+
scale = new Float32Array(0);
|
|
207
|
+
ids = new Float64Array(0);
|
|
208
|
+
at = new Float64Array(0);
|
|
209
|
+
scopeOf = new Int32Array(0);
|
|
210
|
+
pin = new Uint8Array(0);
|
|
211
|
+
slotOf = /* @__PURE__ */ new Map();
|
|
212
|
+
scopes = [];
|
|
213
|
+
scopeIdx = /* @__PURE__ */ new Map();
|
|
214
|
+
get size() {
|
|
215
|
+
return this.n;
|
|
216
|
+
}
|
|
217
|
+
get capacity() {
|
|
218
|
+
return this.cap;
|
|
219
|
+
}
|
|
220
|
+
vectorBytes() {
|
|
221
|
+
return this.n * DIMS;
|
|
222
|
+
}
|
|
223
|
+
bytes() {
|
|
224
|
+
return this.q.byteLength + this.scale.byteLength + this.ids.byteLength + this.at.byteLength + this.scopeOf.byteLength + this.pin.byteLength;
|
|
225
|
+
}
|
|
226
|
+
/** U1-G (D): sets the capacity to at least `slots` in one allocation (the warm reserve). */
|
|
227
|
+
reserve(slots) {
|
|
228
|
+
if (slots > this.cap) this.resize(slots);
|
|
229
|
+
}
|
|
230
|
+
grow(min) {
|
|
231
|
+
if (min > this.cap) this.resize(hotSetGrowth(this.cap, min));
|
|
232
|
+
}
|
|
233
|
+
/** The old buffers are dropped (unreferenced) as soon as they are copied. */
|
|
234
|
+
resize(cap) {
|
|
235
|
+
const q = new Int8Array(cap * DIMS);
|
|
236
|
+
q.set(this.q);
|
|
237
|
+
this.q = q;
|
|
238
|
+
const sc = new Float32Array(cap);
|
|
239
|
+
sc.set(this.scale);
|
|
240
|
+
this.scale = sc;
|
|
241
|
+
const ids = new Float64Array(cap);
|
|
242
|
+
ids.set(this.ids);
|
|
243
|
+
this.ids = ids;
|
|
244
|
+
const at = new Float64Array(cap);
|
|
245
|
+
at.set(this.at);
|
|
246
|
+
this.at = at;
|
|
247
|
+
const so = new Int32Array(cap);
|
|
248
|
+
so.set(this.scopeOf);
|
|
249
|
+
this.scopeOf = so;
|
|
250
|
+
const p = new Uint8Array(cap);
|
|
251
|
+
p.set(this.pin);
|
|
252
|
+
this.pin = p;
|
|
253
|
+
this.cap = cap;
|
|
254
|
+
}
|
|
255
|
+
add(e) {
|
|
256
|
+
if (e.vec.length !== DIMS) return;
|
|
257
|
+
let slot = this.slotOf.get(e.itemId);
|
|
258
|
+
if (slot === void 0) {
|
|
259
|
+
this.grow(this.n + 1);
|
|
260
|
+
slot = this.n++;
|
|
261
|
+
this.slotOf.set(e.itemId, slot);
|
|
262
|
+
}
|
|
263
|
+
let m = 0;
|
|
264
|
+
for (const x of e.vec) m = Math.max(m, Math.abs(x));
|
|
265
|
+
const s = m > 0 ? m / 127 : 1;
|
|
266
|
+
for (let i = 0; i < DIMS; i++) this.q[slot * DIMS + i] = Math.round(e.vec[i] / s);
|
|
267
|
+
let si = this.scopeIdx.get(e.scopeId);
|
|
268
|
+
if (si === void 0) {
|
|
269
|
+
si = this.scopes.length;
|
|
270
|
+
this.scopes.push(e.scopeId);
|
|
271
|
+
this.scopeIdx.set(e.scopeId, si);
|
|
272
|
+
}
|
|
273
|
+
this.scale[slot] = s;
|
|
274
|
+
this.ids[slot] = e.itemId;
|
|
275
|
+
this.at[slot] = e.originAt;
|
|
276
|
+
this.scopeOf[slot] = si;
|
|
277
|
+
this.pin[slot] = e.pinned ? 1 : 0;
|
|
278
|
+
}
|
|
279
|
+
remove(itemId) {
|
|
280
|
+
const slot = this.slotOf.get(itemId);
|
|
281
|
+
if (slot === void 0) return;
|
|
282
|
+
const last = this.n - 1;
|
|
283
|
+
if (slot !== last) {
|
|
284
|
+
this.q.copyWithin(slot * DIMS, last * DIMS, (last + 1) * DIMS);
|
|
285
|
+
this.scale[slot] = this.scale[last];
|
|
286
|
+
this.ids[slot] = this.ids[last];
|
|
287
|
+
this.at[slot] = this.at[last];
|
|
288
|
+
this.scopeOf[slot] = this.scopeOf[last];
|
|
289
|
+
this.pin[slot] = this.pin[last];
|
|
290
|
+
this.slotOf.set(this.ids[slot], slot);
|
|
291
|
+
}
|
|
292
|
+
this.slotOf.delete(itemId);
|
|
293
|
+
this.n--;
|
|
294
|
+
}
|
|
295
|
+
clear() {
|
|
296
|
+
this.n = 0;
|
|
297
|
+
this.slotOf.clear();
|
|
298
|
+
}
|
|
299
|
+
/** section 8.5: sim (int8 dot x scale), meaning threshold, then (sim + prior) x recency; the top `k` by that score. */
|
|
300
|
+
topK(query, allowed, o) {
|
|
301
|
+
const ok = new Uint8Array(this.scopes.length);
|
|
302
|
+
this.scopes.forEach((s, i) => {
|
|
303
|
+
ok[i] = allowed.has(s) ? 1 : 0;
|
|
304
|
+
});
|
|
305
|
+
const owner = o.ownerScope === null ? -1 : this.scopeIdx.get(o.ownerScope) ?? -2;
|
|
306
|
+
const top = [];
|
|
307
|
+
for (let slot = 0; slot < this.n; slot++) {
|
|
308
|
+
const si = this.scopeOf[slot];
|
|
309
|
+
if (!ok[si]) continue;
|
|
310
|
+
let dot = 0;
|
|
311
|
+
const base = slot * DIMS;
|
|
312
|
+
for (let i = 0; i < DIMS; i++) dot += this.q[base + i] * query[i];
|
|
313
|
+
const sim = dot * this.scale[slot];
|
|
314
|
+
if (sim < o.threshold) continue;
|
|
315
|
+
const prior = si === owner || this.pin[slot] === 1 ? o.prior ?? OWNER_SCOPE_PRIOR : 0;
|
|
316
|
+
const score = (sim + prior) * recencyWeight((o.nowMs - this.at[slot]) / DAY_MS, o.recencySlope);
|
|
317
|
+
if (top.length < o.k) {
|
|
318
|
+
top.push({ itemId: this.ids[slot], sim, score });
|
|
319
|
+
top.sort((a, b) => a.score - b.score);
|
|
320
|
+
} else if (score > top[0].score) {
|
|
321
|
+
top[0] = { itemId: this.ids[slot], sim, score };
|
|
322
|
+
top.sort((a, b) => a.score - b.score);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return top.reverse();
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
var UTF8 = new TextDecoder();
|
|
329
|
+
var SCOPE_B = "CAST(i.scope_id AS BLOB) AS scope_id_b";
|
|
330
|
+
function fixScope(r) {
|
|
331
|
+
if (r && r.scope_id_b) r.scope_id = UTF8.decode(r.scope_id_b);
|
|
332
|
+
return r;
|
|
333
|
+
}
|
|
334
|
+
var RankingCore = class {
|
|
335
|
+
constructor(init2, db) {
|
|
336
|
+
this.init = init2;
|
|
337
|
+
this.db = db;
|
|
338
|
+
}
|
|
339
|
+
hot = new HotSet();
|
|
340
|
+
stmts = /* @__PURE__ */ new Map();
|
|
341
|
+
/** Prepared once per distinct statement text (the keyword statement varies only with the MAC and scope counts). */
|
|
342
|
+
stmt(sql) {
|
|
343
|
+
let st = this.stmts.get(sql);
|
|
344
|
+
if (!st) {
|
|
345
|
+
if (this.stmts.size >= 64) this.stmts.clear();
|
|
346
|
+
st = this.db.prepare(sql);
|
|
347
|
+
this.stmts.set(sql, st);
|
|
348
|
+
}
|
|
349
|
+
return st;
|
|
350
|
+
}
|
|
351
|
+
hotCount() {
|
|
352
|
+
return this.hot.size;
|
|
353
|
+
}
|
|
354
|
+
hotCapacity() {
|
|
355
|
+
return this.hot.capacity;
|
|
356
|
+
}
|
|
357
|
+
/** The meaning list a query would fuse (section 8.5 scores, the init's eval overrides applied when set). */
|
|
358
|
+
meaningScores(vec, scopes, ownerScope, nowMs) {
|
|
359
|
+
return this.hot.topK(vec, new Set(scopes), {
|
|
360
|
+
k: LIST_MAX,
|
|
361
|
+
nowMs,
|
|
362
|
+
ownerScope,
|
|
363
|
+
threshold: this.init.threshold,
|
|
364
|
+
...this.init.priorOverride !== void 0 ? { prior: this.init.priorOverride } : {},
|
|
365
|
+
...this.init.recencySlopeOverride !== void 0 ? { recencySlope: this.init.recencySlopeOverride } : {}
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
hotBytes() {
|
|
369
|
+
return this.hot.bytes();
|
|
370
|
+
}
|
|
371
|
+
open(sealed, aad) {
|
|
372
|
+
try {
|
|
373
|
+
return openPayload({ ciphertext: sealed, key: this.init.storeKey, associatedData: aad, suite: "xchacha20poly1305" });
|
|
374
|
+
} catch {
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
fields(r) {
|
|
379
|
+
return {
|
|
380
|
+
scopeKind: r.scope_kind,
|
|
381
|
+
scopeId: r.scope_id,
|
|
382
|
+
channelId: r.channel_id,
|
|
383
|
+
kind: r.kind,
|
|
384
|
+
originId: r.origin_id,
|
|
385
|
+
chunkIndex: Number(r.chunk_index),
|
|
386
|
+
parentOriginId: r.parent_id === null ? "" : String(r.parent_origin_id ?? ""),
|
|
387
|
+
parentId: r.parent_id === null ? null : Number(r.parent_id),
|
|
388
|
+
originAt: Number(r.origin_at),
|
|
389
|
+
storedAt: Number(r.stored_at)
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
row(itemId) {
|
|
393
|
+
return fixScope(this.stmt(`SELECT i.*, ${SCOPE_B}, p.origin_id AS parent_origin_id FROM agent_memory_item i LEFT JOIN agent_memory_item p
|
|
394
|
+
ON p.identity_id=i.identity_id AND p.item_id=i.parent_id WHERE i.identity_id=? AND i.item_id=?`).get(this.init.identityId, itemId));
|
|
395
|
+
}
|
|
396
|
+
addRow(r) {
|
|
397
|
+
if (r.sealed_vec === null || r.model_id !== this.init.modelId) return;
|
|
398
|
+
const plain = this.open(r.sealed_vec, itemVecAad(this.init.identityId, this.fields(r), r.model_id));
|
|
399
|
+
const vec = plain ? decodeVec(plain) : null;
|
|
400
|
+
if (vec && vec.length === DIMS) this.hot.add({ itemId: Number(r.item_id), vec, scopeId: r.scope_id, originAt: Number(r.origin_at), pinned: r.kind === "pinned" });
|
|
401
|
+
}
|
|
402
|
+
/** section 8.5 warm: one row per read, a yield between rows; returns the hot count. A disabled warm (win32 keyword-only) loads nothing. */
|
|
403
|
+
async warm() {
|
|
404
|
+
if (!this.init.warm) return 0;
|
|
405
|
+
const count = this.stmt("SELECT COUNT(*) AS n FROM agent_memory_item WHERE identity_id=? AND sealed_vec IS NOT NULL AND model_id=?").get(this.init.identityId, this.init.modelId);
|
|
406
|
+
this.hot.reserve(hotSetReserve(Number(count?.n ?? 0)));
|
|
407
|
+
let after = 0;
|
|
408
|
+
const next = this.stmt(`SELECT i.*, ${SCOPE_B}, NULL AS parent_origin_id FROM agent_memory_item i WHERE i.identity_id=? AND i.item_id>? AND i.sealed_vec IS NOT NULL
|
|
409
|
+
AND i.model_id=? ORDER BY i.item_id LIMIT 1`);
|
|
410
|
+
for (; ; ) {
|
|
411
|
+
const r = fixScope(next.get(this.init.identityId, after, this.init.modelId));
|
|
412
|
+
if (!r) return this.hot.size;
|
|
413
|
+
after = Number(r.item_id);
|
|
414
|
+
const full = r.parent_id === null ? r : this.row(after);
|
|
415
|
+
if (full) this.addRow(full);
|
|
416
|
+
await new Promise((res) => setImmediate(res));
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
/** Opens a record; null on any failure or a source_ref mismatch (section 11.3). */
|
|
420
|
+
openRecord(r) {
|
|
421
|
+
const plain = this.open(r.sealed_record, itemRecordAad(this.init.identityId, this.fields(r)));
|
|
422
|
+
const rec = plain ? decodeItemRecord(plain) : null;
|
|
423
|
+
if (!rec || (r.source_ref ?? void 0) !== rec.sourceRef) return null;
|
|
424
|
+
return rec;
|
|
425
|
+
}
|
|
426
|
+
opened(r, rec) {
|
|
427
|
+
return {
|
|
428
|
+
itemId: Number(r.item_id),
|
|
429
|
+
scopeKind: r.scope_kind,
|
|
430
|
+
scopeId: r.scope_id,
|
|
431
|
+
channelId: r.channel_id,
|
|
432
|
+
kind: r.kind,
|
|
433
|
+
originId: r.parent_id === null ? r.origin_id : String(r.parent_origin_id),
|
|
434
|
+
originAt: Number(r.origin_at),
|
|
435
|
+
source: rec.source,
|
|
436
|
+
trust: rec.trust,
|
|
437
|
+
text: rec.text,
|
|
438
|
+
...rec.role !== void 0 ? { role: rec.role } : {},
|
|
439
|
+
...rec.origin !== void 0 ? { origin: rec.origin } : {},
|
|
440
|
+
...rec.sourceRef !== void 0 ? { sourceRef: rec.sourceRef } : {}
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
/** H-7: one batched statement, LIMIT 5,001 per MAC inside the allowed scopes; a MAC that fills its limit is skipped. */
|
|
444
|
+
keyword(macs, scopes) {
|
|
445
|
+
if (macs.length === 0 || scopes.length === 0) return [];
|
|
446
|
+
const ph = scopes.map(() => "?").join(",");
|
|
447
|
+
const part = `SELECT * FROM (SELECT ? AS m, t.item_id AS item_id, i.origin_at AS origin_at FROM agent_memory_token t JOIN agent_memory_item i
|
|
448
|
+
ON i.identity_id=t.identity_id AND i.item_id=t.item_id WHERE t.identity_id=? AND t.token_mac=? AND i.scope_id IN (${ph}) LIMIT ${HOT_TOKEN_MAX_POSTINGS + 1})`;
|
|
449
|
+
const sql = macs.map(() => part).join(" UNION ALL ");
|
|
450
|
+
const args = [];
|
|
451
|
+
macs.forEach((m, i) => args.push(i, this.init.identityId, m, ...scopes));
|
|
452
|
+
const rows = this.stmt(sql).all(...args);
|
|
453
|
+
const per = /* @__PURE__ */ new Map();
|
|
454
|
+
for (const r of rows) per.set(r.m, (per.get(r.m) ?? 0) + 1);
|
|
455
|
+
const hits = /* @__PURE__ */ new Map();
|
|
456
|
+
for (const r of rows) {
|
|
457
|
+
if ((per.get(r.m) ?? 0) > HOT_TOKEN_MAX_POSTINGS) continue;
|
|
458
|
+
const h = hits.get(r.item_id) ?? { hits: 0, originAt: Number(r.origin_at) };
|
|
459
|
+
h.hits++;
|
|
460
|
+
hits.set(r.item_id, h);
|
|
461
|
+
}
|
|
462
|
+
return [...hits].map(([itemId, h]) => ({ itemId, ...h })).sort((a, b) => b.hits - a.hits || b.originAt - a.originAt).slice(0, LIST_MAX);
|
|
463
|
+
}
|
|
464
|
+
/** section 8.1 step 5: message ids of the newest 50 history turns of the conversation, plus the current message. */
|
|
465
|
+
excluded(q) {
|
|
466
|
+
const rows = this.stmt("SELECT message_id FROM agent_memory WHERE identity_id=? AND conversation_id=? AND message_id IS NOT NULL ORDER BY seq DESC LIMIT 50").all(this.init.identityId, q.historyConversation);
|
|
467
|
+
return /* @__PURE__ */ new Set([...rows.map((r) => r.message_id), q.currentMessageId, ...q.excludeOriginIds]);
|
|
468
|
+
}
|
|
469
|
+
rank(q) {
|
|
470
|
+
const allowed = new Set(q.scopes);
|
|
471
|
+
const meaning = q.vec ? this.meaningScores(q.vec, q.scopes, q.ownerScope, q.nowMs) : [];
|
|
472
|
+
const kw = this.keyword(q.macs, q.scopes);
|
|
473
|
+
const fused = /* @__PURE__ */ new Map();
|
|
474
|
+
meaning.forEach((m, rank) => fused.set(m.itemId, 1 / (RRF_K + rank + 1)));
|
|
475
|
+
const keywordOnlyRead = q.vec === null || !this.init.warm;
|
|
476
|
+
const kwOnly = /* @__PURE__ */ new Set();
|
|
477
|
+
kw.forEach((k, rank) => {
|
|
478
|
+
if (!fused.has(k.itemId)) kwOnly.add(k.itemId);
|
|
479
|
+
fused.set(k.itemId, (fused.get(k.itemId) ?? 0) + 1 / (RRF_K + rank + 1));
|
|
480
|
+
});
|
|
481
|
+
const ex = this.excluded(q);
|
|
482
|
+
const order = [...fused.keys()];
|
|
483
|
+
const items = [];
|
|
484
|
+
let chars = 0, openFailed = 0;
|
|
485
|
+
const candidates = [];
|
|
486
|
+
for (const id of order) {
|
|
487
|
+
const r = this.row(id);
|
|
488
|
+
if (!r || !allowed.has(r.scope_id)) continue;
|
|
489
|
+
const pendingEmbed = r.sealed_vec === null || r.model_id !== this.init.modelId;
|
|
490
|
+
if (kwOnly.has(id) && !keywordOnlyRead && !pendingEmbed) continue;
|
|
491
|
+
const fresh = 1 - Math.min(365, Math.max(0, (q.nowMs - Number(r.origin_at)) / DAY_MS)) / 365;
|
|
492
|
+
candidates.push({ r, score: fused.get(id), fresh });
|
|
493
|
+
}
|
|
494
|
+
candidates.sort((a, b) => Math.abs(b.score - a.score) > FRESHNESS_TIE_EPSILON ? b.score - a.score : b.fresh - a.fresh);
|
|
495
|
+
for (const { r } of candidates) {
|
|
496
|
+
if (items.length >= q.maxItems) break;
|
|
497
|
+
const origin = r.parent_id === null ? r.origin_id : String(r.parent_origin_id);
|
|
498
|
+
if (ex.has(origin)) continue;
|
|
499
|
+
const rec = this.openRecord(r);
|
|
500
|
+
if (rec === null) {
|
|
501
|
+
openFailed++;
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
if (rec.chunks !== void 0) continue;
|
|
505
|
+
if (chars + rec.text.length > q.maxChars) break;
|
|
506
|
+
chars += rec.text.length;
|
|
507
|
+
items.push(this.opened(r, rec));
|
|
508
|
+
}
|
|
509
|
+
return { items, openFailed };
|
|
510
|
+
}
|
|
511
|
+
pins(scopeId, maxChars) {
|
|
512
|
+
const rows = this.stmt(`SELECT i.*, ${SCOPE_B}, p.origin_id AS parent_origin_id FROM agent_memory_item i LEFT JOIN agent_memory_item p
|
|
513
|
+
ON p.identity_id=i.identity_id AND p.item_id=i.parent_id WHERE i.identity_id=? AND i.scope_id=? AND i.kind='pinned'
|
|
514
|
+
ORDER BY i.origin_at DESC, COALESCE(i.parent_id, i.item_id) DESC, i.chunk_index ASC`).all(this.init.identityId, scopeId);
|
|
515
|
+
rows.forEach(fixScope);
|
|
516
|
+
const pins = [];
|
|
517
|
+
let chars = 0, openFailed = 0, more = 0;
|
|
518
|
+
for (const r of rows) {
|
|
519
|
+
const rec = this.openRecord(r);
|
|
520
|
+
if (rec === null) {
|
|
521
|
+
openFailed++;
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
if (rec.chunks !== void 0) continue;
|
|
525
|
+
if (more > 0 || chars + rec.text.length > maxChars) {
|
|
526
|
+
more++;
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
chars += rec.text.length;
|
|
530
|
+
pins.push(this.opened(r, rec));
|
|
531
|
+
}
|
|
532
|
+
return { pins, more, openFailed };
|
|
533
|
+
}
|
|
534
|
+
pending(limit) {
|
|
535
|
+
const rows = this.stmt(`SELECT i.*, ${SCOPE_B}, p.origin_id AS parent_origin_id FROM agent_memory_item i LEFT JOIN agent_memory_item p
|
|
536
|
+
ON p.identity_id=i.identity_id AND p.item_id=i.parent_id WHERE i.identity_id=? AND (i.model_id IS NULL OR i.model_id<>?)
|
|
537
|
+
AND NOT EXISTS (SELECT 1 FROM agent_memory_item c WHERE c.identity_id=i.identity_id AND c.parent_id=i.item_id)
|
|
538
|
+
ORDER BY i.stored_at, i.item_id LIMIT ?`).all(this.init.identityId, this.init.modelId, limit);
|
|
539
|
+
rows.forEach(fixScope);
|
|
540
|
+
const out = [];
|
|
541
|
+
for (const r of rows) {
|
|
542
|
+
const rec = this.openRecord(r);
|
|
543
|
+
if (rec === null) continue;
|
|
544
|
+
out.push({ itemId: Number(r.item_id), channelId: r.channel_id, scopeKind: r.scope_kind, scopeId: r.scope_id, originId: r.origin_id, chunkIndex: Number(r.chunk_index), text: rec.text });
|
|
545
|
+
}
|
|
546
|
+
return out;
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Final review bridge M-4: `handle` with a per-message catch. A request that throws (a prepare failure, a decode throw)
|
|
550
|
+
* is answered `failed` instead of becoming an unhandled rejection that kills the worker; a notification that throws is
|
|
551
|
+
* dropped (a later request still warms from the database). Content-free: the error is never sent or logged.
|
|
552
|
+
*/
|
|
553
|
+
async handleSafe(m) {
|
|
554
|
+
try {
|
|
555
|
+
return await this.handle(m);
|
|
556
|
+
} catch {
|
|
557
|
+
return "id" in m ? { type: "failed", id: m.id } : null;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
async handle(m) {
|
|
561
|
+
switch (m.type) {
|
|
562
|
+
case "query": {
|
|
563
|
+
const r = this.rank(m.q);
|
|
564
|
+
return { type: "result", id: m.id, items: r.items, openFailed: r.openFailed };
|
|
565
|
+
}
|
|
566
|
+
case "pins": {
|
|
567
|
+
const r = this.pins(m.scopeId, m.maxChars);
|
|
568
|
+
return { type: "pins", id: m.id, ...r };
|
|
569
|
+
}
|
|
570
|
+
case "pending":
|
|
571
|
+
return { type: "pending", id: m.id, rows: this.pending(m.limit) };
|
|
572
|
+
case "added":
|
|
573
|
+
for (const id of m.itemIds) {
|
|
574
|
+
const r = this.row(id);
|
|
575
|
+
if (r) this.addRow(r);
|
|
576
|
+
}
|
|
577
|
+
return { type: "stats", hot: this.hot.size, hotBytes: this.hot.bytes() };
|
|
578
|
+
case "removed":
|
|
579
|
+
for (const id of m.itemIds) this.hot.remove(id);
|
|
580
|
+
return { type: "stats", hot: this.hot.size, hotBytes: this.hot.bytes() };
|
|
581
|
+
case "reset":
|
|
582
|
+
this.hot.clear();
|
|
583
|
+
await this.warm();
|
|
584
|
+
return { type: "stats", hot: this.hot.size, hotBytes: this.hot.bytes() };
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
// src/memory/unified/ranking-worker.ts
|
|
590
|
+
var init = workerData;
|
|
591
|
+
var core = new RankingCore(init, new DatabaseSync(init.dbPath, { readOnly: true }));
|
|
592
|
+
var hot = await core.warm();
|
|
593
|
+
parentPort.postMessage({ type: "ready", hot, hotBytes: core.hotBytes() });
|
|
594
|
+
parentPort.on("message", (m) => {
|
|
595
|
+
void core.handleSafe(m).then((r) => {
|
|
596
|
+
if (r) parentPort.postMessage(r);
|
|
597
|
+
});
|
|
598
|
+
});
|
|
@@ -13,6 +13,21 @@ export interface FsDeps {
|
|
|
13
13
|
unlink: (p: string) => void;
|
|
14
14
|
}
|
|
15
15
|
export declare function runtimeConfigDir(home: string, identityId: string): string;
|
|
16
|
+
/** Owner grants (spec §7.8): the runtime honours a manifest-marked grant only for a tool listed here. */
|
|
17
|
+
export declare const MANIFEST_POLICY_FILE = "manifest-policy.json";
|
|
18
|
+
export interface ManifestPolicyDoc {
|
|
19
|
+
readonly v: 1;
|
|
20
|
+
readonly manifest_tools: readonly string[];
|
|
21
|
+
readonly secret_path_mode: "enforce";
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Owner grants review H-2: under a manifest whose config signs no llm, a managed llm-source.json (written before the
|
|
25
|
+
* manifest, by an unsigned config) must not stay in force. The one deletion in this directory, by name, bridge-only.
|
|
26
|
+
* ENOENT is success; any other error throws.
|
|
27
|
+
*/
|
|
28
|
+
export declare function removeManagedLlmSource(home: string, identityId: string, deps?: FsDeps): void;
|
|
29
|
+
/** The bridge's own writer for manifest-policy.json (writeManagedFiles refuses the name). Atomic, 0600. Throws. */
|
|
30
|
+
export declare function writeManifestPolicy(home: string, identityId: string, policy: ManifestPolicyDoc, deps?: FsDeps): void;
|
|
16
31
|
export declare function writeManagedFiles(home: string, identityId: string, files: Record<string, string>, deps?: FsDeps): void;
|
|
17
32
|
/** Every path skills.json names, checked on disk. Contents are never read. */
|
|
18
33
|
export declare function checkConfiguredPaths(skillsJson: Record<string, unknown>, statFn?: (p: string) => {
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const BRIDGE_VERSION = "0.
|
|
1
|
+
export declare const BRIDGE_VERSION = "0.30.0";
|
package/dist/ws-server.d.ts
CHANGED
|
@@ -38,6 +38,8 @@ export declare const HELLO_WAIT_MS = 2000;
|
|
|
38
38
|
export declare const MEMORY_NOTES_FEATURE = "memory_notes";
|
|
39
39
|
/** Semantic memory 7b (spec §7.2 Q4): the hello feature a runtime declares to receive `memory.recall`; the bridge echoes it. */
|
|
40
40
|
export declare const MEMORY_RECALL_FEATURE = "memory_recall";
|
|
41
|
+
/** Unified memory (plan Task 27, U6): the hello feature a runtime declares to receive `memory.unified`; echoed only with the part 1 switch on. */
|
|
42
|
+
export declare const MEMORY_UNIFIED_FEATURE = "memory_unified";
|
|
41
43
|
/**
|
|
42
44
|
* Every BridgeAgent member this module touches, directly or through the
|
|
43
45
|
* consumer helpers. The handlers take `WsAgent`, so tsc rejects a call to a
|
|
@@ -46,7 +48,7 @@ export declare const MEMORY_RECALL_FEATURE = "memory_recall";
|
|
|
46
48
|
* fake against this list at run time (7b gate fix: a member added here without
|
|
47
49
|
* a fake made three tests throw "is not a function").
|
|
48
50
|
*/
|
|
49
|
-
export declare const WS_AGENT_MEMBERS: readonly ["ackRuntimeMessages", "action", "admitRuntimeDelivery", "applyMemoryNote", "clearHarnessReport", "clearPerToolConsumer", "isRuntimeMessageRetired", "noteNotesConsumer", "notePerToolConsumer", "noteRecallConsumer", "noteSessionClose", "noteSessionEvicted", "noteSessionOpen", "notesAppliedGeneration", "notesForDelivery", "recallForDelivery", "recordHarnessReport", "recordRuntimeReply", "reportStatusGated", "requeueFailedRuntimeDeliveries", "requeueFront", "runtimeReplyReceipt", "send", "sendA2A", "subscribe", "takeDeliverable", "wakeListeners"];
|
|
51
|
+
export declare const WS_AGENT_MEMBERS: readonly ["ackRuntimeMessages", "action", "admitRuntimeDelivery", "applyMemoryNote", "clearHarnessReport", "clearManifestConsumer", "clearPerToolConsumer", "harnessGrantsWithheld", "isRuntimeMessageRetired", "noteConsumerCapability", "noteManifestConsumer", "noteRoomCapReached", "noteNotesConsumer", "notePerToolConsumer", "noteRecallConsumer", "noteUnifiedConsumer", "noteSessionClose", "noteSessionEvicted", "noteSessionOpen", "notesAppliedGeneration", "notesForDelivery", "recallForDelivery", "unifiedFeatureOn", "unifiedForDelivery", "recordHarnessReport", "recordRuntimeReply", "reportStatusGated", "requeueFailedRuntimeDeliveries", "requeueFront", "runtimeReplyReceipt", "send", "sendA2A", "subscribe", "takeDeliverable", "wakeListeners"];
|
|
50
52
|
export type WsAgent = Pick<BridgeAgent, (typeof WS_AGENT_MEMBERS)[number]>;
|
|
51
53
|
export declare function attachAgentWebSocket(server: HttpServer, agent: WsAgent, opts: AgentWebSocketOptions): {
|
|
52
54
|
close(): void;
|