@batalabs/virlow-mcp-core 3.83.0 → 3.84.1
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/dist/api.d.ts +6 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +2 -0
- package/dist/api.js.map +1 -1
- package/dist/cjs/api.d.ts +6 -0
- package/dist/cjs/api.js +2 -0
- package/dist/cjs/entity-registry.d.ts +101 -0
- package/dist/cjs/entity-registry.js +270 -0
- package/dist/cjs/index.d.ts +2 -0
- package/dist/cjs/index.js +14 -1
- package/dist/cjs/memories.d.ts +61 -0
- package/dist/cjs/memories.js +191 -3
- package/dist/cjs/memory-graph.d.ts +89 -7
- package/dist/cjs/memory-graph.js +351 -44
- package/dist/cjs/notes.js +13 -4
- package/dist/entity-registry.d.ts +102 -0
- package/dist/entity-registry.d.ts.map +1 -0
- package/dist/entity-registry.js +260 -0
- package/dist/entity-registry.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/memories.d.ts +61 -0
- package/dist/memories.d.ts.map +1 -1
- package/dist/memories.js +192 -4
- package/dist/memories.js.map +1 -1
- package/dist/memory-graph.d.ts +89 -7
- package/dist/memory-graph.d.ts.map +1 -1
- package/dist/memory-graph.js +342 -42
- package/dist/memory-graph.js.map +1 -1
- package/dist/notes.d.ts.map +1 -1
- package/dist/notes.js +13 -4
- package/dist/notes.js.map +1 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +150 -15
- package/dist/tools.js.map +1 -1
- package/package.json +9 -3
package/dist/cjs/memories.js
CHANGED
|
@@ -6,6 +6,7 @@ const virlow_memory_1 = require("@batalabs/virlow-memory");
|
|
|
6
6
|
const api_js_1 = require("./api.js");
|
|
7
7
|
const memory_note_js_1 = require("./memory-note.js");
|
|
8
8
|
const memory_tree_js_1 = require("./memory-tree.js");
|
|
9
|
+
const entity_registry_js_1 = require("./entity-registry.js");
|
|
9
10
|
const memory_graph_js_1 = require("./memory-graph.js");
|
|
10
11
|
const memory_secrets_js_1 = require("./memory-secrets.js");
|
|
11
12
|
/**
|
|
@@ -52,6 +53,12 @@ class MemoryStore {
|
|
|
52
53
|
embeddings = null;
|
|
53
54
|
embeddingsLoaded = false;
|
|
54
55
|
syncInFlight = null;
|
|
56
|
+
/** The entity registry as last read, and the note(s) holding it, oldest
|
|
57
|
+
* first. `registrySupported` is false when the API refused the note type
|
|
58
|
+
* (an API older than the registry): kinds are then only guessed. */
|
|
59
|
+
registry = null;
|
|
60
|
+
registryRows = [];
|
|
61
|
+
registrySupported = null;
|
|
55
62
|
mutationChain = Promise.resolve();
|
|
56
63
|
/** Bumped every time clear() runs (i.e. every lock). Operations capture it
|
|
57
64
|
* at the top and re-check before every cache write, so anything still
|
|
@@ -67,6 +74,9 @@ class MemoryStore {
|
|
|
67
74
|
}
|
|
68
75
|
clear() {
|
|
69
76
|
this.cache.clear();
|
|
77
|
+
this.registry = null;
|
|
78
|
+
this.registryRows = [];
|
|
79
|
+
this.registrySupported = null;
|
|
70
80
|
this.tree = null;
|
|
71
81
|
this.embeddings = null;
|
|
72
82
|
this.embeddingsLoaded = false;
|
|
@@ -115,6 +125,9 @@ class MemoryStore {
|
|
|
115
125
|
// Hidden notes are segregated in the app; they are not memories.
|
|
116
126
|
if (row.hidden === true)
|
|
117
127
|
continue;
|
|
128
|
+
// The entity registry lives in the root but is not a memory.
|
|
129
|
+
if (row.type === entity_registry_js_1.ENTITY_REGISTRY_NOTE_TYPE)
|
|
130
|
+
continue;
|
|
118
131
|
seen.add(row.id);
|
|
119
132
|
const existing = this.cache.get(row.id);
|
|
120
133
|
if (existing && existing.updatedAt === row.updatedAt) {
|
|
@@ -173,8 +186,171 @@ class MemoryStore {
|
|
|
173
186
|
}
|
|
174
187
|
await embeddings.save(key);
|
|
175
188
|
}
|
|
189
|
+
await this.refreshRegistry(key, tree.rootId, startEpoch);
|
|
176
190
|
return result;
|
|
177
191
|
}
|
|
192
|
+
/** Re-reads the entity registry. A failure other than "this API does not
|
|
193
|
+
* know the type" keeps what was read before: kinds are a nicety, and a
|
|
194
|
+
* registry hiccup must never fail a recall. */
|
|
195
|
+
async refreshRegistry(key, rootId, startEpoch) {
|
|
196
|
+
try {
|
|
197
|
+
const fresh = await this.fetchRegistry(key, rootId);
|
|
198
|
+
if (this.epoch !== startEpoch)
|
|
199
|
+
return;
|
|
200
|
+
this.registry = fresh.registry;
|
|
201
|
+
this.registryRows = fresh.rows;
|
|
202
|
+
this.registrySupported = fresh.supported;
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
// Keep the previous registry.
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
async fetchRegistry(key, rootId) {
|
|
209
|
+
let rows;
|
|
210
|
+
try {
|
|
211
|
+
const res = await this.api.listNotes({
|
|
212
|
+
folderId: rootId,
|
|
213
|
+
type: entity_registry_js_1.ENTITY_REGISTRY_NOTE_TYPE,
|
|
214
|
+
deleted: false,
|
|
215
|
+
limit: exports.SYNC_PAGE_LIMIT,
|
|
216
|
+
});
|
|
217
|
+
// Filtered here too: an API that ignored the type filter would hand
|
|
218
|
+
// back every memory in the root.
|
|
219
|
+
rows = res.notes.filter((r) => r.type === entity_registry_js_1.ENTITY_REGISTRY_NOTE_TYPE && r.deleted !== true);
|
|
220
|
+
}
|
|
221
|
+
catch (err) {
|
|
222
|
+
// An API that predates the registry rejects the type in its query
|
|
223
|
+
// schema. Degrade to guessed kinds.
|
|
224
|
+
if (err instanceof api_js_1.ApiError && err.status === 400) {
|
|
225
|
+
return { registry: (0, entity_registry_js_1.emptyRegistry)(), rows: [], supported: false };
|
|
226
|
+
}
|
|
227
|
+
throw err;
|
|
228
|
+
}
|
|
229
|
+
// Two devices that each created the registry at once leave two notes;
|
|
230
|
+
// they are read as one, and the next save folds them into the oldest.
|
|
231
|
+
rows.sort((a, b) => a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id));
|
|
232
|
+
let registry = (0, entity_registry_js_1.emptyRegistry)();
|
|
233
|
+
for (const row of rows) {
|
|
234
|
+
const { content } = await this.decryptRow(key, row);
|
|
235
|
+
registry = (0, entity_registry_js_1.mergeRegistries)(registry, (0, entity_registry_js_1.parseRegistry)(content));
|
|
236
|
+
}
|
|
237
|
+
return { registry, rows, supported: true };
|
|
238
|
+
}
|
|
239
|
+
/** Whether this API stores entity kinds (false: an API older than the
|
|
240
|
+
* registry), and the registry as last read. Requires a prior sync. */
|
|
241
|
+
registryState() {
|
|
242
|
+
return { supported: this.registrySupported !== false, registry: this.registry ?? (0, entity_registry_js_1.emptyRegistry)() };
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Describe entities in the registry: kind, aliases, note. Read, change,
|
|
246
|
+
* save with expectedUpdatedAt; on a 409 (another device saved first) read
|
|
247
|
+
* again and re-apply, so per-key changes from both survive. With
|
|
248
|
+
* `onlyIfUntyped`, a kind is set only where none is stored: how add_memory
|
|
249
|
+
* records the kinds an AI names without overruling the person's choice.
|
|
250
|
+
*
|
|
251
|
+
* `unsupported` when the API does not accept the registry note type yet;
|
|
252
|
+
* nothing is saved then and kinds stay guesses.
|
|
253
|
+
*/
|
|
254
|
+
describeEntities(patches, opts = {}) {
|
|
255
|
+
return this.enqueue(async () => {
|
|
256
|
+
await this.sync();
|
|
257
|
+
const key = this.requireSession();
|
|
258
|
+
const startEpoch = this.epoch;
|
|
259
|
+
const tree = await this.requireTree();
|
|
260
|
+
if (this.registrySupported === false)
|
|
261
|
+
return { changed: [], unsupported: true };
|
|
262
|
+
for (const patch of patches) {
|
|
263
|
+
(0, memory_secrets_js_1.assertNoSecret)(patch.name, patch.note ?? '', ...(patch.aliases ?? []));
|
|
264
|
+
}
|
|
265
|
+
for (let attempt = 0; attempt < 4; attempt++) {
|
|
266
|
+
const fresh = attempt === 0 && this.registry && this.registrySupported
|
|
267
|
+
? { registry: this.registry, rows: this.registryRows, supported: true }
|
|
268
|
+
: await this.fetchRegistry(key, tree.rootId);
|
|
269
|
+
if (!fresh.supported) {
|
|
270
|
+
if (this.epoch === startEpoch)
|
|
271
|
+
this.registrySupported = false;
|
|
272
|
+
return { changed: [], unsupported: true };
|
|
273
|
+
}
|
|
274
|
+
if (!(0, entity_registry_js_1.canWriteRegistry)(fresh.registry)) {
|
|
275
|
+
throw new Error('The entity registry was saved by a newer version of Virlow. Update this connector to change it.');
|
|
276
|
+
}
|
|
277
|
+
const now = new Date().toISOString();
|
|
278
|
+
let next = fresh.registry;
|
|
279
|
+
const changed = [];
|
|
280
|
+
for (const patch of patches) {
|
|
281
|
+
const existing = next.entities[(0, entity_registry_js_1.registryKeyFor)(next, patch.name)];
|
|
282
|
+
const effective = opts.onlyIfUntyped && existing?.kind ? { ...patch, kind: undefined } : patch;
|
|
283
|
+
const after = (0, entity_registry_js_1.describeInRegistry)(next, effective, now);
|
|
284
|
+
if (after !== next)
|
|
285
|
+
changed.push(patch.name);
|
|
286
|
+
next = after;
|
|
287
|
+
}
|
|
288
|
+
if (changed.length === 0)
|
|
289
|
+
return { changed, unsupported: false };
|
|
290
|
+
const { encryptedTitle, encryptedContent, iv } = await (0, virlow_crypto_1.encryptNoteFields)(key, entity_registry_js_1.ENTITY_REGISTRY_TITLE, (0, entity_registry_js_1.serializeRegistry)(next));
|
|
291
|
+
const write = {
|
|
292
|
+
title: '[ENCRYPTED]',
|
|
293
|
+
content: '[ENCRYPTED]',
|
|
294
|
+
encryptedTitle,
|
|
295
|
+
encryptedContent,
|
|
296
|
+
iv,
|
|
297
|
+
salt: (0, virlow_crypto_1.bytesToBase64)(crypto.getRandomValues(new Uint8Array(32))),
|
|
298
|
+
type: entity_registry_js_1.ENTITY_REGISTRY_NOTE_TYPE,
|
|
299
|
+
};
|
|
300
|
+
const [primary, ...extra] = fresh.rows;
|
|
301
|
+
let row;
|
|
302
|
+
try {
|
|
303
|
+
row = primary
|
|
304
|
+
? await this.api.updateNote(primary.id, { ...write, expectedUpdatedAt: primary.updatedAt })
|
|
305
|
+
: await this.api.createNote({ ...write, folderId: tree.rootId });
|
|
306
|
+
}
|
|
307
|
+
catch (err) {
|
|
308
|
+
if (err instanceof api_js_1.ApiError && err.status === 409)
|
|
309
|
+
continue;
|
|
310
|
+
if (err instanceof api_js_1.ApiError && err.status === 400 && !primary) {
|
|
311
|
+
if (this.epoch === startEpoch)
|
|
312
|
+
this.registrySupported = false;
|
|
313
|
+
return { changed: [], unsupported: true };
|
|
314
|
+
}
|
|
315
|
+
throw err;
|
|
316
|
+
}
|
|
317
|
+
// Their entries are in `next` now; trash the duplicates so the
|
|
318
|
+
// registry is one note again. Best effort: a leftover is merged on
|
|
319
|
+
// the next read anyway.
|
|
320
|
+
for (const dup of extra) {
|
|
321
|
+
await this.api.updateNote(dup.id, { deleted: true }).catch(() => undefined);
|
|
322
|
+
}
|
|
323
|
+
if (this.epoch === startEpoch) {
|
|
324
|
+
this.registry = next;
|
|
325
|
+
this.registryRows = [row];
|
|
326
|
+
this.registrySupported = true;
|
|
327
|
+
}
|
|
328
|
+
return { changed, unsupported: false };
|
|
329
|
+
}
|
|
330
|
+
throw new Error('The entity registry kept changing while saving. Try again.');
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Names nothing types yet, most known first, each with a few of the
|
|
335
|
+
* memories that name it and a suggested kind when one signal points one
|
|
336
|
+
* way: what an AI needs to propose kinds, and then call describe_entity.
|
|
337
|
+
*/
|
|
338
|
+
async untyped(namespace, limit = 25) {
|
|
339
|
+
await this.sync();
|
|
340
|
+
this.requireSession();
|
|
341
|
+
const graph = this.graph(namespace);
|
|
342
|
+
const all = (0, memory_graph_js_1.untypedEntities)(graph);
|
|
343
|
+
const entities = all.slice(0, limit).map((entity) => {
|
|
344
|
+
const memories = [...entity.memoryIds]
|
|
345
|
+
.map((id) => this.cache.get(id))
|
|
346
|
+
.filter((m) => m !== undefined)
|
|
347
|
+
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))
|
|
348
|
+
.slice(0, 3)
|
|
349
|
+
.map((m) => ({ id: m.id, label: m.label, text: m.text }));
|
|
350
|
+
return { name: entity.name, facts: entity.memoryIds.size, memories, suggestion: (0, memory_graph_js_1.suggestKind)(graph, entity.name) };
|
|
351
|
+
});
|
|
352
|
+
return { total: all.length, entities, supported: this.registrySupported !== false };
|
|
353
|
+
}
|
|
178
354
|
add(label, text, namespace, meta = {}) {
|
|
179
355
|
return this.enqueue(async () => {
|
|
180
356
|
(0, memory_secrets_js_1.assertNoSecret)(label, text, ...(meta.relations ?? []).map((r) => `${r.s} ${r.p} ${r.o}`));
|
|
@@ -308,7 +484,7 @@ class MemoryStore {
|
|
|
308
484
|
/** The graph over what is cached now. Cheap: rebuilt from the notes on
|
|
309
485
|
* demand, never stored. */
|
|
310
486
|
graph(namespace) {
|
|
311
|
-
return (0, memory_graph_js_1.buildGraph)([...this.cache.values()].filter((m) => namespace === undefined || m.namespace === namespace || m.namespace === undefined));
|
|
487
|
+
return (0, memory_graph_js_1.buildGraph)([...this.cache.values()].filter((m) => namespace === undefined || m.namespace === namespace || m.namespace === undefined), (0, entity_registry_js_1.describedEntities)(this.registry));
|
|
312
488
|
}
|
|
313
489
|
/** New names spelled the way existing memories already spell them, and
|
|
314
490
|
* predicates in snake_case, so one entity does not split in two. */
|
|
@@ -364,16 +540,22 @@ class MemoryStore {
|
|
|
364
540
|
await this.sync();
|
|
365
541
|
this.requireSession();
|
|
366
542
|
const graph = this.graph(namespace);
|
|
367
|
-
const entity =
|
|
543
|
+
const entity = (0, memory_graph_js_1.findEntity)(graph, name);
|
|
368
544
|
if (!entity)
|
|
369
545
|
return null;
|
|
370
546
|
const now = Date.now();
|
|
547
|
+
const described = graph.described.get(entity.key);
|
|
371
548
|
const memories = [...entity.memoryIds]
|
|
372
549
|
.map((id) => this.cache.get(id))
|
|
373
550
|
.filter(Boolean)
|
|
374
551
|
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
375
552
|
return {
|
|
376
553
|
name: entity.name,
|
|
554
|
+
kind: (0, memory_graph_js_1.entityKinds)(graph).get(entity.key) ?? 'other',
|
|
555
|
+
/** Whether the kind is stored (set by a person or an AI) or guessed. */
|
|
556
|
+
kindStored: described?.kind !== undefined,
|
|
557
|
+
aliases: described?.aliases ?? [],
|
|
558
|
+
note: described?.note,
|
|
377
559
|
current: memories.filter((m) => (0, memory_graph_js_1.memoryHeldAt)(m, now)),
|
|
378
560
|
past: memories.filter((m) => !(0, memory_graph_js_1.memoryHeldAt)(m, now)),
|
|
379
561
|
relations: entity.edges.map((e) => ({ ...e })),
|
|
@@ -443,10 +625,16 @@ class MemoryStore {
|
|
|
443
625
|
this.requireSession();
|
|
444
626
|
const now = Date.now();
|
|
445
627
|
const graph = this.graph(namespace);
|
|
628
|
+
const kinds = (0, memory_graph_js_1.entityKinds)(graph);
|
|
446
629
|
const entities = [...graph.entities.values()]
|
|
447
630
|
.sort((a, b) => b.memoryIds.size - a.memoryIds.size)
|
|
448
631
|
.slice(0, 8)
|
|
449
|
-
.map((e) => ({
|
|
632
|
+
.map((e) => ({
|
|
633
|
+
name: e.name,
|
|
634
|
+
facts: e.memoryIds.size,
|
|
635
|
+
kind: kinds.get(e.key) ?? 'other',
|
|
636
|
+
...(graph.described.get(e.key)?.note !== undefined && { note: graph.described.get(e.key).note }),
|
|
637
|
+
}));
|
|
450
638
|
const facts = this.list(namespace)
|
|
451
639
|
.filter((m) => (0, memory_graph_js_1.memoryHeldAt)(m, now))
|
|
452
640
|
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))
|
|
@@ -38,7 +38,37 @@ export interface Entity {
|
|
|
38
38
|
export interface MemoryGraph {
|
|
39
39
|
entities: Map<string, Entity>;
|
|
40
40
|
edges: Edge[];
|
|
41
|
+
/** Alias key to the key of the entity it is another name for, from the
|
|
42
|
+
* entity registry. Empty when there is no registry. */
|
|
43
|
+
aliases: Map<string, string>;
|
|
44
|
+
/** What the registry says about each entity, by entity key: a kind the
|
|
45
|
+
* person or an AI set, aliases and a note. Empty when there is none. */
|
|
46
|
+
described: Map<string, DescribedEntity>;
|
|
41
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* The part of an entity registry entry the graph reads. The registry itself
|
|
50
|
+
* (an encrypted note beside the memories) is `entity-registry.ts`; the graph
|
|
51
|
+
* takes this narrow shape so it stays pure and has no format to know.
|
|
52
|
+
*/
|
|
53
|
+
export interface DescribedEntity {
|
|
54
|
+
/** Display spelling, when one was given. */
|
|
55
|
+
name?: string;
|
|
56
|
+
kind?: EntityKind;
|
|
57
|
+
aliases?: string[];
|
|
58
|
+
note?: string;
|
|
59
|
+
}
|
|
60
|
+
/** The key an entity is filed under in `graph`: its own key, or, when the
|
|
61
|
+
* registry names it as an alias, the key of the entity it is an alias of. */
|
|
62
|
+
export declare function keyOf(graph: Pick<MemoryGraph, 'aliases'>, name: string): string;
|
|
63
|
+
/** The entity a name refers to, through aliases. */
|
|
64
|
+
export declare function findEntity(graph: MemoryGraph, name: string): Entity | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* Alias keys from registry entries. One level only: an alias names the entry
|
|
67
|
+
* it sits on, never a chain. When two entries claim one alias, the first by
|
|
68
|
+
* key wins, so every client resolves it the same way. An alias that is the
|
|
69
|
+
* entry's own key is ignored.
|
|
70
|
+
*/
|
|
71
|
+
export declare function aliasMap(described: Map<string, DescribedEntity>): Map<string, string>;
|
|
42
72
|
/** The matching key for a name: case, spacing and a leading article do not
|
|
43
73
|
* make a different entity. */
|
|
44
74
|
export declare function entityKey(name: string): string;
|
|
@@ -65,7 +95,13 @@ export declare function heldAt(from: string | undefined, to: string | undefined,
|
|
|
65
95
|
* today started in January), else when it was learned.
|
|
66
96
|
*/
|
|
67
97
|
export declare function memoryHeldAt(memory: GraphMemory, at: number): boolean;
|
|
68
|
-
|
|
98
|
+
/**
|
|
99
|
+
* Entities and edges from memories. With `described` (the entity registry,
|
|
100
|
+
* by entity key), names the registry lists as aliases fold into the entity
|
|
101
|
+
* they are aliases of, and the registry's display name, kind and note ride
|
|
102
|
+
* along. The memories themselves are never rewritten for this.
|
|
103
|
+
*/
|
|
104
|
+
export declare function buildGraph(memories: Iterable<GraphMemory>, described?: Map<string, DescribedEntity>): MemoryGraph;
|
|
69
105
|
/** Entities named in a piece of text (a prompt, a query), longest names
|
|
70
106
|
* first so "Dana Whitfield" wins over "Dana". Whole words only. */
|
|
71
107
|
export declare function entitiesMentioned(graph: MemoryGraph, text: string): Entity[];
|
|
@@ -94,8 +130,8 @@ export declare function rankMemories(graph: MemoryGraph, query: string, scored:
|
|
|
94
130
|
export declare function supersededBy(graph: MemoryGraph, relation: MemoryRelation, at: number): Edge[];
|
|
95
131
|
/**
|
|
96
132
|
* Housekeeping findings for a tidy pass: memories linked to nothing, names
|
|
97
|
-
* that look like one entity spelled two ways,
|
|
98
|
-
* holding two values at once.
|
|
133
|
+
* that look like one entity spelled two ways, single-valued predicates
|
|
134
|
+
* holding two values at once, and names with no kind.
|
|
99
135
|
*/
|
|
100
136
|
export declare function graphHealth(graph: MemoryGraph, memories: GraphMemory[], at?: number): {
|
|
101
137
|
unlinked: string[];
|
|
@@ -105,6 +141,7 @@ export declare function graphHealth(graph: MemoryGraph, memories: GraphMemory[],
|
|
|
105
141
|
predicate: string;
|
|
106
142
|
objects: string[];
|
|
107
143
|
}[];
|
|
144
|
+
untyped: string[];
|
|
108
145
|
};
|
|
109
146
|
/**
|
|
110
147
|
* A memory's links with one name swapped for another: how "Dana" becomes
|
|
@@ -119,10 +156,55 @@ export declare function renameEntityInMeta(meta: MemoryMeta, from: string, into:
|
|
|
119
156
|
* Null when the memory does not link it.
|
|
120
157
|
*/
|
|
121
158
|
export declare function unlinkEntityFromMeta(meta: MemoryMeta, name: string): MemoryMeta | null;
|
|
122
|
-
export type EntityKind = 'person' | 'project' | 'tool' | 'place' | 'organisation' | 'other';
|
|
123
159
|
/**
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
160
|
+
* What an entity is. `organization` was spelled `organisation` before the
|
|
161
|
+
* registry existed; readers accept both (see normalizeKind) and writers use
|
|
162
|
+
* this spelling. `other` is the fallback when nothing says.
|
|
163
|
+
*/
|
|
164
|
+
export declare const ENTITY_KINDS: readonly ["person", "organization", "project", "tool", "place", "event", "topic", "product", "media", "pet", "group", "goal", "other"];
|
|
165
|
+
export type EntityKind = (typeof ENTITY_KINDS)[number];
|
|
166
|
+
/** A kind as stored or typed, as one of ENTITY_KINDS; undefined when it is
|
|
167
|
+
* not one. Case and a plural "s" do not matter; `organisation` reads as
|
|
168
|
+
* `organization`. */
|
|
169
|
+
export declare function normalizeKind(value: unknown): EntityKind | undefined;
|
|
170
|
+
/**
|
|
171
|
+
* What each end of a predicate usually is. "Dana works_at Acme" says Dana
|
|
172
|
+
* is a person and Acme an organization; "Atlas uses Postgres" says Atlas is
|
|
173
|
+
* a project and Postgres a tool. Unknown predicates say nothing.
|
|
174
|
+
*/
|
|
175
|
+
export declare const ROLES: Record<string, [EntityKind | null, EntityKind | null]>;
|
|
176
|
+
/**
|
|
177
|
+
* What each entity is. A kind stored in the entity registry (set by the
|
|
178
|
+
* person or by an AI with describe_entity or add_memory) wins; otherwise a
|
|
179
|
+
* best guess from the roles the entity plays in its relations; "other" when
|
|
180
|
+
* nothing says. The guess is never written back.
|
|
127
181
|
*/
|
|
128
182
|
export declare function entityKinds(graph: MemoryGraph): Map<string, EntityKind>;
|
|
183
|
+
/** The kind the registry stores for an entity, if any. */
|
|
184
|
+
export declare function storedKind(graph: MemoryGraph, name: string): EntityKind | undefined;
|
|
185
|
+
/**
|
|
186
|
+
* Entities nothing types: no kind in the registry and no relation role to
|
|
187
|
+
* guess one from. These are what a review asks the person (or an AI) to
|
|
188
|
+
* sort out. Most known first.
|
|
189
|
+
*/
|
|
190
|
+
export declare function untypedEntities(graph: MemoryGraph): Entity[];
|
|
191
|
+
export interface KindSuggestion {
|
|
192
|
+
kind: EntityKind;
|
|
193
|
+
/** Why, in a few words a person can check: "works at in 2 memories". */
|
|
194
|
+
reason: string;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* A suggested kind for an untyped entity, from signals on this device only
|
|
198
|
+
* (memory content is end-to-end encrypted, so no server can suggest
|
|
199
|
+
* anything). Only signals that point one way:
|
|
200
|
+
* - relation roles (ROLES), when the entity plays one;
|
|
201
|
+
* - how typed entities are used: if every typed subject (or object) of a
|
|
202
|
+
* predicate is one kind, an untyped one in the same place is that kind
|
|
203
|
+
* too ("Dana mentors Sam", with Dana a person, makes Lee in "Lee mentors
|
|
204
|
+
* Kim" a person), with at least two typed examples;
|
|
205
|
+
* - the name: a company suffix (Inc, LLC, Ltd, GmbH...) is an organization,
|
|
206
|
+
* and a single name that is the first name of exactly one typed person
|
|
207
|
+
* ("Dana" beside the person "Dana Whitfield") is a person.
|
|
208
|
+
* Null when nothing points one way; a wrong guess costs more than none.
|
|
209
|
+
*/
|
|
210
|
+
export declare function suggestKind(graph: MemoryGraph, name: string): KindSuggestion | null;
|