@persql/context 0.1.0 → 1.0.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 +83 -36
- package/dist/{chunk-RMG66FDI.js → chunk-HRHBXOPL.js} +91 -113
- package/dist/chunk-HRHBXOPL.js.map +1 -0
- package/dist/core.cjs +93 -116
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +54 -48
- package/dist/core.d.ts +54 -48
- package/dist/core.js +7 -9
- package/dist/index.cjs +211 -178
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -40
- package/dist/index.d.ts +34 -40
- package/dist/index.js +124 -71
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/dist/chunk-RMG66FDI.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -26,65 +26,63 @@ __export(index_exports, {
|
|
|
26
26
|
SCHEMA_SQL: () => SCHEMA_SQL,
|
|
27
27
|
SCHEMA_VERSION: () => SCHEMA_VERSION,
|
|
28
28
|
TABLE_PREFIX: () => TABLE_PREFIX,
|
|
29
|
-
|
|
29
|
+
buildDetectLegacySchema: () => buildDetectLegacySchema,
|
|
30
|
+
buildDropLegacy: () => buildDropLegacy,
|
|
30
31
|
buildEdgesAmong: () => buildEdgesAmong,
|
|
31
32
|
buildExtractionMessages: () => buildExtractionMessages,
|
|
32
33
|
buildForget: () => buildForget,
|
|
33
34
|
buildGet: () => buildGet,
|
|
35
|
+
buildIndex: () => buildIndex,
|
|
34
36
|
buildInit: () => buildInit,
|
|
35
37
|
buildNeighborEntities: () => buildNeighborEntities,
|
|
36
38
|
buildRecall: () => buildRecall,
|
|
37
|
-
buildRecent: () => buildRecent,
|
|
38
39
|
buildRemember: () => buildRemember,
|
|
39
40
|
buildStats: () => buildStats,
|
|
40
|
-
buildSupersede: () => buildSupersede,
|
|
41
41
|
buildUpsertEdge: () => buildUpsertEdge,
|
|
42
42
|
buildUpsertEntity: () => buildUpsertEntity,
|
|
43
43
|
context: () => context,
|
|
44
44
|
entityId: () => entityId,
|
|
45
|
-
|
|
45
|
+
memoryTools: () => memoryTools,
|
|
46
46
|
toFtsQuery: () => toFtsQuery
|
|
47
47
|
});
|
|
48
48
|
module.exports = __toCommonJS(index_exports);
|
|
49
49
|
|
|
50
50
|
// src/core.ts
|
|
51
|
-
var SCHEMA_VERSION =
|
|
51
|
+
var SCHEMA_VERSION = 2;
|
|
52
52
|
var TABLE_PREFIX = "ctx_";
|
|
53
53
|
var T = TABLE_PREFIX;
|
|
54
54
|
var SCHEMA_SQL = [
|
|
55
55
|
`CREATE TABLE IF NOT EXISTS ${T}memory (
|
|
56
56
|
id TEXT PRIMARY KEY,
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
name TEXT NOT NULL,
|
|
58
|
+
description TEXT NOT NULL,
|
|
59
|
+
type TEXT NOT NULL DEFAULT 'project',
|
|
59
60
|
body TEXT NOT NULL,
|
|
60
|
-
tags TEXT,
|
|
61
61
|
source TEXT,
|
|
62
62
|
created_at INTEGER NOT NULL,
|
|
63
|
-
|
|
63
|
+
updated_at INTEGER NOT NULL
|
|
64
64
|
)`,
|
|
65
|
-
`CREATE INDEX IF NOT EXISTS ${T}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
// External-content FTS index: body text is not duplicated, only indexed.
|
|
69
|
-
// Porter stemmer so "invoice" recalls "invoicing" without embeddings.
|
|
65
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS ${T}memory_name ON ${T}memory(name)`,
|
|
66
|
+
// External-content FTS on name+description+body. Porter stemmer so
|
|
67
|
+
// "invoice" recalls "invoicing" without embeddings.
|
|
70
68
|
`CREATE VIRTUAL TABLE IF NOT EXISTS ${T}memory_fts USING fts5(
|
|
71
|
-
|
|
69
|
+
name, description, body,
|
|
72
70
|
content='${T}memory', content_rowid='rowid',
|
|
73
71
|
tokenize='porter unicode61'
|
|
74
72
|
)`,
|
|
75
73
|
`CREATE TRIGGER IF NOT EXISTS ${T}memory_ai AFTER INSERT ON ${T}memory BEGIN
|
|
76
|
-
INSERT INTO ${T}memory_fts(rowid,
|
|
77
|
-
VALUES (new.rowid, new.
|
|
74
|
+
INSERT INTO ${T}memory_fts(rowid, name, description, body)
|
|
75
|
+
VALUES (new.rowid, new.name, new.description, new.body);
|
|
78
76
|
END`,
|
|
79
77
|
`CREATE TRIGGER IF NOT EXISTS ${T}memory_ad AFTER DELETE ON ${T}memory BEGIN
|
|
80
|
-
INSERT INTO ${T}memory_fts(${T}memory_fts, rowid,
|
|
81
|
-
VALUES ('delete', old.rowid, old.
|
|
78
|
+
INSERT INTO ${T}memory_fts(${T}memory_fts, rowid, name, description, body)
|
|
79
|
+
VALUES ('delete', old.rowid, old.name, old.description, old.body);
|
|
82
80
|
END`,
|
|
83
81
|
`CREATE TRIGGER IF NOT EXISTS ${T}memory_au AFTER UPDATE ON ${T}memory BEGIN
|
|
84
|
-
INSERT INTO ${T}memory_fts(${T}memory_fts, rowid,
|
|
85
|
-
VALUES ('delete', old.rowid, old.
|
|
86
|
-
INSERT INTO ${T}memory_fts(rowid,
|
|
87
|
-
VALUES (new.rowid, new.
|
|
82
|
+
INSERT INTO ${T}memory_fts(${T}memory_fts, rowid, name, description, body)
|
|
83
|
+
VALUES ('delete', old.rowid, old.name, old.description, old.body);
|
|
84
|
+
INSERT INTO ${T}memory_fts(rowid, name, description, body)
|
|
85
|
+
VALUES (new.rowid, new.name, new.description, new.body);
|
|
88
86
|
END`,
|
|
89
87
|
`CREATE TABLE IF NOT EXISTS ${T}entity (
|
|
90
88
|
id TEXT PRIMARY KEY,
|
|
@@ -107,15 +105,21 @@ var SCHEMA_SQL = [
|
|
|
107
105
|
function buildInit() {
|
|
108
106
|
return SCHEMA_SQL.map((sql) => ({ sql, params: [] }));
|
|
109
107
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
new Set(arr.map((t) => t.trim().toLowerCase()).filter(Boolean))
|
|
116
|
-
);
|
|
117
|
-
return norm.length ? norm.join(" ") : null;
|
|
108
|
+
function buildDetectLegacySchema() {
|
|
109
|
+
return {
|
|
110
|
+
sql: `SELECT count(*) AS n FROM pragma_table_info('${T}memory') WHERE name='topic'`,
|
|
111
|
+
params: []
|
|
112
|
+
};
|
|
118
113
|
}
|
|
114
|
+
function buildDropLegacy() {
|
|
115
|
+
return [
|
|
116
|
+
{ sql: `DROP TABLE IF EXISTS ${T}memory_fts`, params: [] },
|
|
117
|
+
{ sql: `DROP TABLE IF EXISTS ${T}memory`, params: [] },
|
|
118
|
+
{ sql: `DROP TABLE IF EXISTS ${T}entity`, params: [] },
|
|
119
|
+
{ sql: `DROP TABLE IF EXISTS ${T}edge`, params: [] }
|
|
120
|
+
];
|
|
121
|
+
}
|
|
122
|
+
var FTS_OPERATORS = /* @__PURE__ */ new Set(["and", "or", "not", "near"]);
|
|
119
123
|
function toFtsQuery(input, opts = {}) {
|
|
120
124
|
if (opts.mode === "raw") return input.trim() || null;
|
|
121
125
|
const tokens = (input.match(/[\p{L}\p{N}_]+/gu) ?? []).filter(
|
|
@@ -138,103 +142,71 @@ function resolveEntityRef(ref) {
|
|
|
138
142
|
function edgeId(srcId, rel, dstId) {
|
|
139
143
|
return slugId("x_", `${srcId}|${rel}|${dstId}`);
|
|
140
144
|
}
|
|
141
|
-
var MEMORY_COLS = "id,
|
|
145
|
+
var MEMORY_COLS = "id, name, description, type, body, source, created_at AS createdAt, updated_at AS updatedAt";
|
|
142
146
|
function buildRemember(input, now, id) {
|
|
143
147
|
return {
|
|
144
|
-
sql: `INSERT INTO ${T}memory (id,
|
|
145
|
-
VALUES (?, ?, ?, ?, ?, ?, ?,
|
|
148
|
+
sql: `INSERT INTO ${T}memory (id, name, description, type, body, source, created_at, updated_at)
|
|
149
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
150
|
+
ON CONFLICT(name) DO UPDATE SET
|
|
151
|
+
description = excluded.description,
|
|
152
|
+
body = excluded.body,
|
|
153
|
+
type = excluded.type,
|
|
154
|
+
source = excluded.source,
|
|
155
|
+
updated_at = excluded.updated_at`,
|
|
146
156
|
params: [
|
|
147
157
|
id,
|
|
148
|
-
input.
|
|
149
|
-
input.
|
|
158
|
+
input.name,
|
|
159
|
+
input.description,
|
|
160
|
+
input.type ?? "project",
|
|
150
161
|
input.body,
|
|
151
|
-
normalizeTags(input.tags),
|
|
152
162
|
input.source ?? null,
|
|
163
|
+
now,
|
|
153
164
|
now
|
|
154
165
|
]
|
|
155
166
|
};
|
|
156
167
|
}
|
|
157
|
-
function
|
|
158
|
-
return {
|
|
159
|
-
sql: `UPDATE ${T}memory SET superseded_by = ? WHERE id = ? AND superseded_by IS NULL`,
|
|
160
|
-
params: [newId, oldId]
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
function buildForget(id) {
|
|
164
|
-
return { sql: `DELETE FROM ${T}memory WHERE id = ?`, params: [id] };
|
|
168
|
+
function buildForget(name) {
|
|
169
|
+
return { sql: `DELETE FROM ${T}memory WHERE name = ?`, params: [name] };
|
|
165
170
|
}
|
|
166
|
-
function buildGet(
|
|
167
|
-
return {
|
|
168
|
-
sql: `SELECT ${MEMORY_COLS} FROM ${T}memory WHERE id = ?`,
|
|
169
|
-
params: [id]
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
function buildRecall(query, opts = {}) {
|
|
173
|
-
const match = toFtsQuery(query, { operator: opts.operator, mode: opts.mode });
|
|
174
|
-
if (!match) return null;
|
|
175
|
-
const where = [`${T}memory_fts MATCH ?`];
|
|
176
|
-
const params = [match];
|
|
177
|
-
if (!opts.includeSuperseded) where.push("m.superseded_by IS NULL");
|
|
178
|
-
if (opts.kind) {
|
|
179
|
-
where.push("m.kind = ?");
|
|
180
|
-
params.push(opts.kind);
|
|
181
|
-
}
|
|
182
|
-
if (opts.sinceMs != null) {
|
|
183
|
-
where.push("m.created_at >= ?");
|
|
184
|
-
params.push(opts.sinceMs);
|
|
185
|
-
}
|
|
186
|
-
params.push(opts.limit ?? 8);
|
|
171
|
+
function buildGet(name) {
|
|
187
172
|
return {
|
|
188
|
-
sql: `SELECT
|
|
189
|
-
|
|
190
|
-
FROM ${T}memory_fts
|
|
191
|
-
JOIN ${T}memory m ON m.rowid = ${T}memory_fts.rowid
|
|
192
|
-
WHERE ${where.join(" AND ")}
|
|
193
|
-
ORDER BY ${T}memory_fts.rank
|
|
194
|
-
LIMIT ?`,
|
|
195
|
-
params
|
|
173
|
+
sql: `SELECT ${MEMORY_COLS} FROM ${T}memory WHERE name = ?`,
|
|
174
|
+
params: [name]
|
|
196
175
|
};
|
|
197
176
|
}
|
|
198
|
-
function
|
|
177
|
+
function buildIndex(opts = {}) {
|
|
199
178
|
const where = [];
|
|
200
179
|
const params = [];
|
|
201
|
-
if (
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
params.push(opts.kind);
|
|
180
|
+
if (opts.type) {
|
|
181
|
+
where.push("type = ?");
|
|
182
|
+
params.push(opts.type);
|
|
205
183
|
}
|
|
206
|
-
params.push(opts.limit ??
|
|
184
|
+
params.push(opts.limit ?? 50);
|
|
207
185
|
return {
|
|
208
|
-
sql: `SELECT
|
|
186
|
+
sql: `SELECT id, name, description, type, updated_at AS updatedAt FROM ${T}memory
|
|
209
187
|
${where.length ? "WHERE " + where.join(" AND ") : ""}
|
|
210
|
-
ORDER BY
|
|
211
|
-
LIMIT ?`,
|
|
188
|
+
ORDER BY updated_at DESC LIMIT ?`,
|
|
212
189
|
params
|
|
213
190
|
};
|
|
214
191
|
}
|
|
215
|
-
function
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
const params = [`% ${t} %`];
|
|
219
|
-
if (!opts.includeSuperseded) where.push("superseded_by IS NULL");
|
|
220
|
-
if (opts.kind) {
|
|
221
|
-
where.push("kind = ?");
|
|
222
|
-
params.push(opts.kind);
|
|
223
|
-
}
|
|
224
|
-
params.push(opts.limit ?? 20);
|
|
192
|
+
function buildRecall(query, opts = {}) {
|
|
193
|
+
const match = toFtsQuery(query, { operator: opts.operator, mode: opts.mode });
|
|
194
|
+
if (!match) return null;
|
|
225
195
|
return {
|
|
226
|
-
sql: `SELECT
|
|
227
|
-
|
|
228
|
-
|
|
196
|
+
sql: `SELECT m.id, m.name, m.description, m.type, m.body, m.source,
|
|
197
|
+
m.created_at AS createdAt, m.updated_at AS updatedAt
|
|
198
|
+
FROM ${T}memory_fts
|
|
199
|
+
JOIN ${T}memory m ON m.rowid = ${T}memory_fts.rowid
|
|
200
|
+
WHERE ${T}memory_fts MATCH ?
|
|
201
|
+
ORDER BY ${T}memory_fts.rank
|
|
229
202
|
LIMIT ?`,
|
|
230
|
-
params
|
|
203
|
+
params: [match, opts.limit ?? 8]
|
|
231
204
|
};
|
|
232
205
|
}
|
|
233
206
|
function buildStats() {
|
|
234
207
|
return {
|
|
235
208
|
sql: `SELECT
|
|
236
|
-
(SELECT count(*) FROM ${T}memory
|
|
237
|
-
(SELECT count(*) FROM ${T}memory) AS facts_total,
|
|
209
|
+
(SELECT count(*) FROM ${T}memory) AS memories,
|
|
238
210
|
(SELECT count(*) FROM ${T}entity) AS entities,
|
|
239
211
|
(SELECT count(*) FROM ${T}edge) AS edges`,
|
|
240
212
|
params: []
|
|
@@ -283,8 +255,7 @@ function buildNeighborEntities(seedId, depth, limit) {
|
|
|
283
255
|
GROUP BY en.id
|
|
284
256
|
ORDER BY depth, en.name
|
|
285
257
|
LIMIT ?`,
|
|
286
|
-
// The seed
|
|
287
|
-
// exclude it explicitly so it isn't listed as its own neighbor.
|
|
258
|
+
// The seed appears at depth >0 on undirected round-trips — exclude it.
|
|
288
259
|
params: [seedId, depth, seedId, limit]
|
|
289
260
|
};
|
|
290
261
|
}
|
|
@@ -300,14 +271,21 @@ function buildEdgesAmong(ids) {
|
|
|
300
271
|
params: [...ids, ...ids]
|
|
301
272
|
};
|
|
302
273
|
}
|
|
303
|
-
var EXTRACTION_SYSTEM_PROMPT = `You extract durable,
|
|
274
|
+
var EXTRACTION_SYSTEM_PROMPT = `You extract durable, structured memories from a conversation or document for an AI agent that will recall them across sessions.
|
|
304
275
|
|
|
305
|
-
Return JSON: { "
|
|
306
|
-
-
|
|
307
|
-
|
|
308
|
-
|
|
276
|
+
Return JSON: { "memories": [...], "entities": [...], "edges": [...] }.
|
|
277
|
+
- memories: facts worth remembering across sessions \u2014 decisions, conventions, constraints, identifiers, preferences, schema details. Each:
|
|
278
|
+
{
|
|
279
|
+
"name": string (kebab-case slug, unique key \u2014 e.g. "auth-provider-choice", "todos-table-schema", "user-prefers-metrics"),
|
|
280
|
+
"description": string (one-liner shown in the always-loaded index),
|
|
281
|
+
"type": "user"|"feedback"|"project"|"reference",
|
|
282
|
+
"body": string (full markdown content with all relevant detail)
|
|
283
|
+
}
|
|
284
|
+
Types: user=who they are/preferences; feedback=guidance for agents; project=ongoing work/decisions; reference=where to find things.
|
|
285
|
+
- entities: named things \u2014 services, people, tables, repos. Each: { "name": string, "kind"?: string, "body"?: string }
|
|
286
|
+
- edges: relationships between entities. Each: { "src": name, "rel": string, "dst": name }
|
|
309
287
|
|
|
310
|
-
Be conservative
|
|
288
|
+
Be conservative. Use unique, meaningful names. If nothing durable is found, return empty arrays.`;
|
|
311
289
|
function buildExtractionMessages(raw, opts = {}) {
|
|
312
290
|
return [
|
|
313
291
|
{ role: "system", content: EXTRACTION_SYSTEM_PROMPT },
|
|
@@ -322,16 +300,17 @@ function buildExtractionMessages(raw, opts = {}) {
|
|
|
322
300
|
var EXTRACTION_JSON_SCHEMA = {
|
|
323
301
|
type: "object",
|
|
324
302
|
properties: {
|
|
325
|
-
|
|
303
|
+
memories: {
|
|
326
304
|
type: "array",
|
|
327
305
|
items: {
|
|
328
306
|
type: "object",
|
|
329
307
|
properties: {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
308
|
+
name: { type: "string" },
|
|
309
|
+
description: { type: "string" },
|
|
310
|
+
type: { type: "string", enum: ["user", "feedback", "project", "reference"] },
|
|
311
|
+
body: { type: "string" }
|
|
333
312
|
},
|
|
334
|
-
required: ["body"]
|
|
313
|
+
required: ["name", "description", "body"]
|
|
335
314
|
}
|
|
336
315
|
},
|
|
337
316
|
entities: {
|
|
@@ -377,44 +356,29 @@ var ContextStore = class {
|
|
|
377
356
|
await this.db.batch(buildInit());
|
|
378
357
|
}
|
|
379
358
|
// --- writes -------------------------------------------------------------
|
|
380
|
-
/**
|
|
359
|
+
/** UPSERT a named memory. Same name → update; new name → insert. */
|
|
381
360
|
async remember(input) {
|
|
382
|
-
const id =
|
|
361
|
+
const id = uid("m_");
|
|
383
362
|
const now = Date.now();
|
|
384
|
-
const
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
await this.db.
|
|
390
|
-
return id;
|
|
391
|
-
}
|
|
392
|
-
/**
|
|
393
|
-
* Extract durable context from raw text and store it. The LLM runs on the
|
|
394
|
-
* write path; pass `extract` here or to the constructor (or use the hosted
|
|
395
|
-
* Context MCP, which runs extraction server-side and meters it).
|
|
396
|
-
*/
|
|
397
|
-
async rememberRaw(raw, opts = {}) {
|
|
398
|
-
const extract = opts.extract ?? this.opts.extract;
|
|
399
|
-
if (!extract) {
|
|
400
|
-
throw new Error(
|
|
401
|
-
"ContextStore.rememberRaw needs an extractor: pass { extract } here or to the constructor, or use the hosted PerSQL Context MCP."
|
|
402
|
-
);
|
|
403
|
-
}
|
|
404
|
-
const extracted = await extract(raw, { hint: opts.hint });
|
|
405
|
-
return this.write(extracted, { source: opts.source });
|
|
363
|
+
const s = buildRemember(
|
|
364
|
+
{ ...input, source: input.source ?? this.opts.source },
|
|
365
|
+
now,
|
|
366
|
+
id
|
|
367
|
+
);
|
|
368
|
+
await this.db.query(s.sql, s.params);
|
|
406
369
|
}
|
|
407
|
-
/** Persist an already-extracted bundle of
|
|
370
|
+
/** Persist an already-extracted bundle of memories, entities, and edges. */
|
|
408
371
|
async write(extracted, opts = {}) {
|
|
409
372
|
const now = Date.now();
|
|
410
373
|
const source = opts.source ?? this.opts.source;
|
|
411
374
|
const stmts = [];
|
|
412
|
-
const
|
|
375
|
+
const names = [];
|
|
413
376
|
const entityIds = /* @__PURE__ */ new Set();
|
|
414
|
-
for (const
|
|
377
|
+
for (const m of extracted.memories ?? []) {
|
|
378
|
+
if (!m?.name || !m?.body) continue;
|
|
415
379
|
const id = uid("m_");
|
|
416
|
-
|
|
417
|
-
stmts.push(buildRemember({ ...
|
|
380
|
+
names.push(m.name);
|
|
381
|
+
stmts.push(buildRemember({ ...m, source }, now, id));
|
|
418
382
|
}
|
|
419
383
|
for (const e of extracted.entities ?? []) {
|
|
420
384
|
stmts.push(buildUpsertEntity(e, now));
|
|
@@ -429,19 +393,14 @@ var ContextStore = class {
|
|
|
429
393
|
}
|
|
430
394
|
if (stmts.length) await this.db.transaction(stmts);
|
|
431
395
|
return {
|
|
432
|
-
|
|
396
|
+
names,
|
|
433
397
|
entityIds: [...entityIds],
|
|
434
398
|
edgeCount: (extracted.edges ?? []).length
|
|
435
399
|
};
|
|
436
400
|
}
|
|
437
|
-
/**
|
|
438
|
-
async
|
|
439
|
-
const
|
|
440
|
-
await this.db.transaction(ids.map((o) => buildSupersede(o, newId)));
|
|
441
|
-
}
|
|
442
|
-
/** Hard-delete a memory row. */
|
|
443
|
-
async forget(id) {
|
|
444
|
-
const s = buildForget(id);
|
|
401
|
+
/** Hard-delete a memory row by name. */
|
|
402
|
+
async forget(name) {
|
|
403
|
+
const s = buildForget(name);
|
|
445
404
|
await this.db.query(s.sql, s.params);
|
|
446
405
|
}
|
|
447
406
|
/** Add a relationship between two entities (created if absent). */
|
|
@@ -457,35 +416,25 @@ var ContextStore = class {
|
|
|
457
416
|
]);
|
|
458
417
|
}
|
|
459
418
|
// --- reads (no AI, pure lexical SQL) ------------------------------------
|
|
460
|
-
/**
|
|
461
|
-
async
|
|
462
|
-
const
|
|
463
|
-
if (opts.withinDays != null && opts.sinceMs == null) {
|
|
464
|
-
resolved.sinceMs = Date.now() - opts.withinDays * 864e5;
|
|
465
|
-
}
|
|
466
|
-
const s = buildRecall(query, resolved);
|
|
467
|
-
if (!s) return [];
|
|
419
|
+
/** Always-loaded index: name+description+type, newest first. */
|
|
420
|
+
async index(opts = {}) {
|
|
421
|
+
const s = buildIndex(opts);
|
|
468
422
|
const { data } = await this.db.query(s.sql, s.params);
|
|
469
423
|
return data;
|
|
470
424
|
}
|
|
471
|
-
/**
|
|
472
|
-
async
|
|
473
|
-
const s =
|
|
425
|
+
/** Fetch one memory by name (with full body). */
|
|
426
|
+
async get(name) {
|
|
427
|
+
const s = buildGet(name);
|
|
474
428
|
const { data } = await this.db.query(s.sql, s.params);
|
|
475
|
-
return data;
|
|
429
|
+
return data[0] ?? null;
|
|
476
430
|
}
|
|
477
|
-
/**
|
|
478
|
-
async
|
|
479
|
-
const s =
|
|
431
|
+
/** Keyword recall, BM25-ranked, most relevant first. */
|
|
432
|
+
async recall(query, opts = {}) {
|
|
433
|
+
const s = buildRecall(query, opts);
|
|
434
|
+
if (!s) return [];
|
|
480
435
|
const { data } = await this.db.query(s.sql, s.params);
|
|
481
436
|
return data;
|
|
482
437
|
}
|
|
483
|
-
/** Fetch one memory row by id, or null. */
|
|
484
|
-
async get(id) {
|
|
485
|
-
const s = buildGet(id);
|
|
486
|
-
const { data } = await this.db.query(s.sql, s.params);
|
|
487
|
-
return data[0] ?? null;
|
|
488
|
-
}
|
|
489
438
|
/** The subgraph around an entity: reachable entities + the edges among them. */
|
|
490
439
|
async neighbors(name, opts = {}) {
|
|
491
440
|
const seed = name.startsWith("e_") ? name : entityId(name);
|
|
@@ -503,14 +452,13 @@ var ContextStore = class {
|
|
|
503
452
|
);
|
|
504
453
|
return { entities, edges };
|
|
505
454
|
}
|
|
506
|
-
/** Counts:
|
|
455
|
+
/** Counts: memories, entities, edges. */
|
|
507
456
|
async stats() {
|
|
508
457
|
const s = buildStats();
|
|
509
458
|
const { data } = await this.db.query(s.sql, s.params);
|
|
510
459
|
const r = data[0];
|
|
511
460
|
return {
|
|
512
|
-
|
|
513
|
-
factsTotal: r?.facts_total ?? 0,
|
|
461
|
+
memories: r?.memories ?? 0,
|
|
514
462
|
entities: r?.entities ?? 0,
|
|
515
463
|
edges: r?.edges ?? 0
|
|
516
464
|
};
|
|
@@ -519,6 +467,91 @@ var ContextStore = class {
|
|
|
519
467
|
function context(db, opts) {
|
|
520
468
|
return new ContextStore(db, opts);
|
|
521
469
|
}
|
|
470
|
+
function memoryTools(store) {
|
|
471
|
+
return [
|
|
472
|
+
{
|
|
473
|
+
type: "function",
|
|
474
|
+
name: "remember_memory",
|
|
475
|
+
description: "Save or update a named memory. Use for facts worth keeping across sessions: schema details, user preferences, decisions. Same name overwrites the previous entry.",
|
|
476
|
+
parameters: {
|
|
477
|
+
type: "object",
|
|
478
|
+
properties: {
|
|
479
|
+
name: {
|
|
480
|
+
type: "string",
|
|
481
|
+
description: "Short kebab-case key, e.g. 'billing-preference'"
|
|
482
|
+
},
|
|
483
|
+
description: {
|
|
484
|
+
type: "string",
|
|
485
|
+
description: "One-line summary shown in the memory index"
|
|
486
|
+
},
|
|
487
|
+
type: {
|
|
488
|
+
type: "string",
|
|
489
|
+
enum: ["user", "feedback", "project", "reference"],
|
|
490
|
+
description: "Memory category"
|
|
491
|
+
},
|
|
492
|
+
body: {
|
|
493
|
+
type: "string",
|
|
494
|
+
description: "Full content of the memory"
|
|
495
|
+
}
|
|
496
|
+
},
|
|
497
|
+
required: ["name", "description", "body"],
|
|
498
|
+
additionalProperties: false
|
|
499
|
+
},
|
|
500
|
+
invoke: async (input) => {
|
|
501
|
+
await store.remember({
|
|
502
|
+
name: String(input.name),
|
|
503
|
+
description: String(input.description),
|
|
504
|
+
type: input.type ?? "project",
|
|
505
|
+
body: String(input.body)
|
|
506
|
+
});
|
|
507
|
+
return { saved: input.name };
|
|
508
|
+
}
|
|
509
|
+
},
|
|
510
|
+
{
|
|
511
|
+
type: "function",
|
|
512
|
+
name: "recall_memory",
|
|
513
|
+
description: "Search memories by keyword. Returns the most relevant entries (BM25-ranked). Use when the answer might be in memory before querying a live data source.",
|
|
514
|
+
parameters: {
|
|
515
|
+
type: "object",
|
|
516
|
+
properties: {
|
|
517
|
+
query: {
|
|
518
|
+
type: "string",
|
|
519
|
+
description: "Keywords to search for"
|
|
520
|
+
},
|
|
521
|
+
limit: {
|
|
522
|
+
type: "number",
|
|
523
|
+
description: "Max results (default 10)"
|
|
524
|
+
}
|
|
525
|
+
},
|
|
526
|
+
required: ["query"],
|
|
527
|
+
additionalProperties: false
|
|
528
|
+
},
|
|
529
|
+
invoke: async (input) => {
|
|
530
|
+
const rows = await store.recall(String(input.query), {
|
|
531
|
+
limit: typeof input.limit === "number" ? input.limit : 10
|
|
532
|
+
});
|
|
533
|
+
return { memories: rows };
|
|
534
|
+
}
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
type: "function",
|
|
538
|
+
name: "forget_memory",
|
|
539
|
+
description: "Delete a saved memory by name.",
|
|
540
|
+
parameters: {
|
|
541
|
+
type: "object",
|
|
542
|
+
properties: {
|
|
543
|
+
name: { type: "string", description: "Name of the memory to delete" }
|
|
544
|
+
},
|
|
545
|
+
required: ["name"],
|
|
546
|
+
additionalProperties: false
|
|
547
|
+
},
|
|
548
|
+
invoke: async (input) => {
|
|
549
|
+
await store.forget(String(input.name));
|
|
550
|
+
return { deleted: input.name };
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
];
|
|
554
|
+
}
|
|
522
555
|
// Annotate the CommonJS export names for ESM import in node:
|
|
523
556
|
0 && (module.exports = {
|
|
524
557
|
ContextStore,
|
|
@@ -527,23 +560,23 @@ function context(db, opts) {
|
|
|
527
560
|
SCHEMA_SQL,
|
|
528
561
|
SCHEMA_VERSION,
|
|
529
562
|
TABLE_PREFIX,
|
|
530
|
-
|
|
563
|
+
buildDetectLegacySchema,
|
|
564
|
+
buildDropLegacy,
|
|
531
565
|
buildEdgesAmong,
|
|
532
566
|
buildExtractionMessages,
|
|
533
567
|
buildForget,
|
|
534
568
|
buildGet,
|
|
569
|
+
buildIndex,
|
|
535
570
|
buildInit,
|
|
536
571
|
buildNeighborEntities,
|
|
537
572
|
buildRecall,
|
|
538
|
-
buildRecent,
|
|
539
573
|
buildRemember,
|
|
540
574
|
buildStats,
|
|
541
|
-
buildSupersede,
|
|
542
575
|
buildUpsertEdge,
|
|
543
576
|
buildUpsertEntity,
|
|
544
577
|
context,
|
|
545
578
|
entityId,
|
|
546
|
-
|
|
579
|
+
memoryTools,
|
|
547
580
|
toFtsQuery
|
|
548
581
|
});
|
|
549
582
|
//# sourceMappingURL=index.cjs.map
|