@sumeru/server 0.1.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/LICENSE +18 -0
- package/dist/.build-fingerprint +1 -0
- package/dist/config.d.ts +14 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +142 -0
- package/dist/config.js.map +1 -0
- package/dist/envelope.d.ts +28 -0
- package/dist/envelope.d.ts.map +1 -0
- package/dist/envelope.js +43 -0
- package/dist/envelope.js.map +1 -0
- package/dist/export/bundle.d.ts +28 -0
- package/dist/export/bundle.d.ts.map +1 -0
- package/dist/export/bundle.js +78 -0
- package/dist/export/bundle.js.map +1 -0
- package/dist/export/handler.d.ts +24 -0
- package/dist/export/handler.d.ts.map +1 -0
- package/dist/export/handler.js +102 -0
- package/dist/export/handler.js.map +1 -0
- package/dist/export/index.d.ts +3 -0
- package/dist/export/index.d.ts.map +1 -0
- package/dist/export/index.js +3 -0
- package/dist/export/index.js.map +1 -0
- package/dist/handler.d.ts +24 -0
- package/dist/handler.d.ts.map +1 -0
- package/dist/handler.js +622 -0
- package/dist/handler.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/ocas/index.d.ts +3 -0
- package/dist/ocas/index.d.ts.map +1 -0
- package/dist/ocas/index.js +3 -0
- package/dist/ocas/index.js.map +1 -0
- package/dist/ocas/schemas.d.ts +41 -0
- package/dist/ocas/schemas.d.ts.map +1 -0
- package/dist/ocas/schemas.js +108 -0
- package/dist/ocas/schemas.js.map +1 -0
- package/dist/ocas/store.d.ts +58 -0
- package/dist/ocas/store.d.ts.map +1 -0
- package/dist/ocas/store.js +139 -0
- package/dist/ocas/store.js.map +1 -0
- package/dist/search/handler.d.ts +54 -0
- package/dist/search/handler.d.ts.map +1 -0
- package/dist/search/handler.js +178 -0
- package/dist/search/handler.js.map +1 -0
- package/dist/search/index.d.ts +4 -0
- package/dist/search/index.d.ts.map +1 -0
- package/dist/search/index.js +3 -0
- package/dist/search/index.js.map +1 -0
- package/dist/search/sqlite-index.d.ts +49 -0
- package/dist/search/sqlite-index.d.ts.map +1 -0
- package/dist/search/sqlite-index.js +508 -0
- package/dist/search/sqlite-index.js.map +1 -0
- package/dist/search/types.d.ts +143 -0
- package/dist/search/types.d.ts.map +1 -0
- package/dist/search/types.js +10 -0
- package/dist/search/types.js.map +1 -0
- package/dist/session/cwd.d.ts +31 -0
- package/dist/session/cwd.d.ts.map +1 -0
- package/dist/session/cwd.js +54 -0
- package/dist/session/cwd.js.map +1 -0
- package/dist/session/id.d.ts +12 -0
- package/dist/session/id.d.ts.map +1 -0
- package/dist/session/id.js +76 -0
- package/dist/session/id.js.map +1 -0
- package/dist/session/index.d.ts +5 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/index.js +4 -0
- package/dist/session/index.js.map +1 -0
- package/dist/session/store.d.ts +89 -0
- package/dist/session/store.d.ts.map +1 -0
- package/dist/session/store.js +258 -0
- package/dist/session/store.js.map +1 -0
- package/dist/sse/buffer.d.ts +53 -0
- package/dist/sse/buffer.d.ts.map +1 -0
- package/dist/sse/buffer.js +119 -0
- package/dist/sse/buffer.js.map +1 -0
- package/dist/sse/index.d.ts +3 -0
- package/dist/sse/index.d.ts.map +1 -0
- package/dist/sse/index.js +3 -0
- package/dist/sse/index.js.map +1 -0
- package/dist/sse/messages.d.ts +30 -0
- package/dist/sse/messages.d.ts.map +1 -0
- package/dist/sse/messages.js +489 -0
- package/dist/sse/messages.js.map +1 -0
- package/dist/start.d.ts +22 -0
- package/dist/start.d.ts.map +1 -0
- package/dist/start.js +86 -0
- package/dist/start.js.map +1 -0
- package/dist/types.d.ts +252 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -0
- package/package.json +31 -0
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 5 — FTS5 search index.
|
|
3
|
+
*
|
|
4
|
+
* This module owns the SQLite FTS5 schema that backs Sumeru's session search.
|
|
5
|
+
* The index lives in `<ocasDir>/_store.db` — the same file `@ocas/fs` opens
|
|
6
|
+
* for variables and tags. We open a second `DatabaseSync` handle on the same
|
|
7
|
+
* file (safe because `@ocas/fs` enables WAL).
|
|
8
|
+
*
|
|
9
|
+
* The schema:
|
|
10
|
+
* - `sumeru_turn_index` — one row per turn (PK = turn ocas hash)
|
|
11
|
+
* - `sumeru_turn_fts` — contentless FTS5 virtual table (mirrors content)
|
|
12
|
+
* - `sumeru_session_index` — one row per session (PK = session id)
|
|
13
|
+
*
|
|
14
|
+
* Triggers keep `sumeru_turn_fts` in lockstep with `sumeru_turn_index`.
|
|
15
|
+
*
|
|
16
|
+
* All write paths are idempotent on the turn hash / session id, so re-indexing
|
|
17
|
+
* the same node is a no-op.
|
|
18
|
+
*/
|
|
19
|
+
import { DatabaseSync } from "node:sqlite";
|
|
20
|
+
const SCHEMA_DDL = `
|
|
21
|
+
CREATE TABLE IF NOT EXISTS sumeru_turn_index (
|
|
22
|
+
turn_hash TEXT PRIMARY KEY,
|
|
23
|
+
session_id TEXT NOT NULL,
|
|
24
|
+
gateway TEXT NOT NULL,
|
|
25
|
+
turn_index INTEGER NOT NULL,
|
|
26
|
+
role TEXT NOT NULL,
|
|
27
|
+
content TEXT NOT NULL,
|
|
28
|
+
created_at TEXT NOT NULL
|
|
29
|
+
);
|
|
30
|
+
CREATE INDEX IF NOT EXISTS idx_sumeru_turn_index_session
|
|
31
|
+
ON sumeru_turn_index(session_id);
|
|
32
|
+
CREATE INDEX IF NOT EXISTS idx_sumeru_turn_index_gateway
|
|
33
|
+
ON sumeru_turn_index(gateway);
|
|
34
|
+
|
|
35
|
+
CREATE TABLE IF NOT EXISTS sumeru_session_index (
|
|
36
|
+
session_id TEXT PRIMARY KEY,
|
|
37
|
+
gateway TEXT NOT NULL,
|
|
38
|
+
adapter TEXT NOT NULL,
|
|
39
|
+
status TEXT NOT NULL,
|
|
40
|
+
created_at TEXT NOT NULL,
|
|
41
|
+
last_active_at TEXT NOT NULL,
|
|
42
|
+
turn_count INTEGER NOT NULL DEFAULT 0,
|
|
43
|
+
meta_hash TEXT
|
|
44
|
+
);
|
|
45
|
+
CREATE INDEX IF NOT EXISTS idx_sumeru_session_index_gateway
|
|
46
|
+
ON sumeru_session_index(gateway);
|
|
47
|
+
CREATE INDEX IF NOT EXISTS idx_sumeru_session_index_last_active
|
|
48
|
+
ON sumeru_session_index(last_active_at DESC);
|
|
49
|
+
|
|
50
|
+
-- Phase 6 (Refs #399): durable, ordered, per-session turn-list pointer. This
|
|
51
|
+
-- is the canonical list the session store rehydrates on boot. It is NOT owned
|
|
52
|
+
-- by FTS and is never cleared by rebuild().
|
|
53
|
+
CREATE TABLE IF NOT EXISTS sumeru_session_turns (
|
|
54
|
+
session_id TEXT NOT NULL,
|
|
55
|
+
turn_index INTEGER NOT NULL,
|
|
56
|
+
turn_hash TEXT NOT NULL,
|
|
57
|
+
PRIMARY KEY (session_id, turn_index)
|
|
58
|
+
);
|
|
59
|
+
CREATE INDEX IF NOT EXISTS idx_sumeru_session_turns_session
|
|
60
|
+
ON sumeru_session_turns(session_id);
|
|
61
|
+
|
|
62
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS sumeru_turn_fts USING fts5(
|
|
63
|
+
content,
|
|
64
|
+
content='sumeru_turn_index',
|
|
65
|
+
content_rowid='rowid',
|
|
66
|
+
tokenize='unicode61 remove_diacritics 2'
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
CREATE TRIGGER IF NOT EXISTS sumeru_turn_fts_ai AFTER INSERT ON sumeru_turn_index BEGIN
|
|
70
|
+
INSERT INTO sumeru_turn_fts(rowid, content) VALUES (new.rowid, new.content);
|
|
71
|
+
END;
|
|
72
|
+
CREATE TRIGGER IF NOT EXISTS sumeru_turn_fts_ad AFTER DELETE ON sumeru_turn_index BEGIN
|
|
73
|
+
INSERT INTO sumeru_turn_fts(sumeru_turn_fts, rowid, content) VALUES('delete', old.rowid, old.content);
|
|
74
|
+
END;
|
|
75
|
+
`;
|
|
76
|
+
/**
|
|
77
|
+
* Open a second SQLite handle on the same `_store.db` file `@ocas/fs` writes
|
|
78
|
+
* vars/tags into. Creates the FTS5 schema if not present. Retries up to 3×
|
|
79
|
+
* on `SQLITE_BUSY` with a 50 ms backoff.
|
|
80
|
+
*
|
|
81
|
+
* Throws `failed to create FTS5 index: <cause>` on persistent failure — the
|
|
82
|
+
* caller (`openSumeruOcas`) prepends `failed to open ocas store at <dir>: `
|
|
83
|
+
* so all boot failures share the same prefix.
|
|
84
|
+
*/
|
|
85
|
+
export function createSearchIndex(dbPath) {
|
|
86
|
+
const db = openWithRetry(dbPath);
|
|
87
|
+
try {
|
|
88
|
+
db.exec("BEGIN");
|
|
89
|
+
db.exec(SCHEMA_DDL);
|
|
90
|
+
migrateMetaHashColumn(db);
|
|
91
|
+
db.exec("COMMIT");
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
try {
|
|
95
|
+
db.exec("ROLLBACK");
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// best-effort
|
|
99
|
+
}
|
|
100
|
+
const cause = err instanceof Error ? err.message : String(err);
|
|
101
|
+
throw new Error(`failed to create FTS5 index: ${cause}`);
|
|
102
|
+
}
|
|
103
|
+
const insertSession = db.prepare(`
|
|
104
|
+
INSERT INTO sumeru_session_index
|
|
105
|
+
(session_id, gateway, adapter, status, created_at, last_active_at, turn_count, meta_hash)
|
|
106
|
+
VALUES (?, ?, ?, 'idle', ?, ?, 0, ?)
|
|
107
|
+
ON CONFLICT(session_id) DO NOTHING
|
|
108
|
+
`);
|
|
109
|
+
const insertTurn = db.prepare(`
|
|
110
|
+
INSERT INTO sumeru_turn_index
|
|
111
|
+
(turn_hash, session_id, gateway, turn_index, role, content, created_at)
|
|
112
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
113
|
+
ON CONFLICT(turn_hash) DO NOTHING
|
|
114
|
+
`);
|
|
115
|
+
const bumpSession = db.prepare(`
|
|
116
|
+
UPDATE sumeru_session_index
|
|
117
|
+
SET last_active_at = ?,
|
|
118
|
+
turn_count = turn_count + 1
|
|
119
|
+
WHERE session_id = ?
|
|
120
|
+
`);
|
|
121
|
+
const closeSession = db.prepare(`
|
|
122
|
+
UPDATE sumeru_session_index
|
|
123
|
+
SET status = 'closed'
|
|
124
|
+
WHERE session_id = ?
|
|
125
|
+
`);
|
|
126
|
+
const countTurns = db.prepare(`SELECT COUNT(*) AS c FROM sumeru_turn_index`);
|
|
127
|
+
// Phase 6 (Refs #399): turn-list pointer statements.
|
|
128
|
+
const appendTurn = db.prepare(`
|
|
129
|
+
INSERT INTO sumeru_session_turns (session_id, turn_index, turn_hash)
|
|
130
|
+
VALUES (?, ?, ?)
|
|
131
|
+
ON CONFLICT(session_id, turn_index) DO NOTHING
|
|
132
|
+
`);
|
|
133
|
+
const selectTurnsForSession = db.prepare(`
|
|
134
|
+
SELECT turn_hash
|
|
135
|
+
FROM sumeru_session_turns
|
|
136
|
+
WHERE session_id = ?
|
|
137
|
+
ORDER BY turn_index ASC
|
|
138
|
+
`);
|
|
139
|
+
const selectAllTurns = db.prepare(`
|
|
140
|
+
SELECT session_id, turn_hash
|
|
141
|
+
FROM sumeru_session_turns
|
|
142
|
+
ORDER BY session_id, turn_index ASC
|
|
143
|
+
`);
|
|
144
|
+
const selectAllSessions = db.prepare(`
|
|
145
|
+
SELECT session_id, gateway, adapter, status,
|
|
146
|
+
created_at, last_active_at, turn_count, meta_hash
|
|
147
|
+
FROM sumeru_session_index
|
|
148
|
+
ORDER BY created_at ASC
|
|
149
|
+
`);
|
|
150
|
+
function indexSessionMeta(meta) {
|
|
151
|
+
insertSession.run(meta.sessionId, meta.gateway, meta.adapter, meta.createdAt, meta.createdAt, meta.metaHash);
|
|
152
|
+
}
|
|
153
|
+
function appendSessionTurn(sessionId, turnIndex, turnHash) {
|
|
154
|
+
appendTurn.run(sessionId, turnIndex, turnHash);
|
|
155
|
+
}
|
|
156
|
+
function listSessionTurns(sessionId) {
|
|
157
|
+
const rows = selectTurnsForSession.all(sessionId);
|
|
158
|
+
return rows.map((r) => r.turn_hash);
|
|
159
|
+
}
|
|
160
|
+
function loadSessionTurnsBulk() {
|
|
161
|
+
const rows = selectAllTurns.all();
|
|
162
|
+
const out = new Map();
|
|
163
|
+
for (const row of rows) {
|
|
164
|
+
let list = out.get(row.session_id);
|
|
165
|
+
if (list === undefined) {
|
|
166
|
+
list = [];
|
|
167
|
+
out.set(row.session_id, list);
|
|
168
|
+
}
|
|
169
|
+
list.push(row.turn_hash);
|
|
170
|
+
}
|
|
171
|
+
return out;
|
|
172
|
+
}
|
|
173
|
+
function loadSessionRows() {
|
|
174
|
+
const rows = selectAllSessions.all();
|
|
175
|
+
return rows.map((r) => ({
|
|
176
|
+
sessionId: r.session_id,
|
|
177
|
+
gateway: r.gateway,
|
|
178
|
+
adapter: r.adapter,
|
|
179
|
+
status: normalizeStatus(r.status),
|
|
180
|
+
createdAt: r.created_at,
|
|
181
|
+
lastActiveAt: r.last_active_at,
|
|
182
|
+
turnCount: Number(r.turn_count ?? 0),
|
|
183
|
+
metaHash: r.meta_hash === null ? null : r.meta_hash,
|
|
184
|
+
}));
|
|
185
|
+
}
|
|
186
|
+
function indexTurn(input) {
|
|
187
|
+
db.exec("BEGIN");
|
|
188
|
+
try {
|
|
189
|
+
const result = insertTurn.run(input.turnHash, input.sessionId, input.gateway, input.turnIndex, input.role, input.content, input.createdAt);
|
|
190
|
+
// On INSERT-vs-conflict: only bump turn_count when a new row was
|
|
191
|
+
// actually inserted. node:sqlite's run() returns { changes } where
|
|
192
|
+
// changes === 0 means the conflict path took DO NOTHING.
|
|
193
|
+
if (Number(result.changes ?? 0) > 0) {
|
|
194
|
+
bumpSession.run(input.createdAt, input.sessionId);
|
|
195
|
+
}
|
|
196
|
+
db.exec("COMMIT");
|
|
197
|
+
}
|
|
198
|
+
catch (err) {
|
|
199
|
+
try {
|
|
200
|
+
db.exec("ROLLBACK");
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
// best-effort
|
|
204
|
+
}
|
|
205
|
+
throw err;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function markSessionClosed(sessionId) {
|
|
209
|
+
try {
|
|
210
|
+
closeSession.run(sessionId);
|
|
211
|
+
}
|
|
212
|
+
catch (err) {
|
|
213
|
+
const cause = err instanceof Error ? err.message : String(err);
|
|
214
|
+
console.warn(`[sumeru] search index update failed: ${cause}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
function search(opts) {
|
|
218
|
+
const trimmed = opts.query.trim();
|
|
219
|
+
if (trimmed.length === 0) {
|
|
220
|
+
return { query: "", results: [], total: 0 };
|
|
221
|
+
}
|
|
222
|
+
const matchExpr = quoteFtsPhrase(trimmed);
|
|
223
|
+
const limit = clamp(opts.limit, 1, 100);
|
|
224
|
+
const offset = Math.max(0, opts.offset);
|
|
225
|
+
// FTS5 auxiliary functions (bm25, snippet) only work in queries that
|
|
226
|
+
// reference the FTS5 virtual table at the top level; they don't work
|
|
227
|
+
// through CTEs. So we collect raw matches first, then aggregate in JS.
|
|
228
|
+
const matchStmt = db.prepare(`
|
|
229
|
+
SELECT t.session_id AS session_id,
|
|
230
|
+
snippet(sumeru_turn_fts, 0, '<<', '>>', '…', 24) AS snip,
|
|
231
|
+
bm25(sumeru_turn_fts) AS score
|
|
232
|
+
FROM sumeru_turn_fts
|
|
233
|
+
JOIN sumeru_turn_index t ON t.rowid = sumeru_turn_fts.rowid
|
|
234
|
+
WHERE sumeru_turn_fts MATCH ?
|
|
235
|
+
AND ( ?2 IS NULL OR t.gateway = ?2 )
|
|
236
|
+
ORDER BY score ASC
|
|
237
|
+
`);
|
|
238
|
+
let matchRows;
|
|
239
|
+
try {
|
|
240
|
+
matchRows = matchStmt.all(matchExpr, opts.gateway);
|
|
241
|
+
}
|
|
242
|
+
catch (err) {
|
|
243
|
+
const cause = err instanceof Error ? err.message : String(err);
|
|
244
|
+
console.warn(`[sumeru] search index query failed: ${cause}`);
|
|
245
|
+
return { query: trimmed, results: [], total: 0 };
|
|
246
|
+
}
|
|
247
|
+
// Aggregate to session granularity — keep best (lowest BM25) per session.
|
|
248
|
+
// `matchRows` is already sorted ASC by score, so the first occurrence per
|
|
249
|
+
// session is its best.
|
|
250
|
+
const bestBySession = new Map();
|
|
251
|
+
for (const row of matchRows) {
|
|
252
|
+
if (!bestBySession.has(row.session_id)) {
|
|
253
|
+
bestBySession.set(row.session_id, {
|
|
254
|
+
score: Number(row.score),
|
|
255
|
+
snip: String(row.snip),
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
const total = bestBySession.size;
|
|
260
|
+
if (total === 0) {
|
|
261
|
+
return { query: trimmed, results: [], total: 0 };
|
|
262
|
+
}
|
|
263
|
+
// Read session rows in one IN-clause query, then re-order in JS by best
|
|
264
|
+
// score ASC, last_active_at DESC for stable tie-break.
|
|
265
|
+
const ids = Array.from(bestBySession.keys());
|
|
266
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
267
|
+
const sessionStmt = db.prepare(`
|
|
268
|
+
SELECT session_id, gateway, status, last_active_at, turn_count
|
|
269
|
+
FROM sumeru_session_index
|
|
270
|
+
WHERE session_id IN (${placeholders})
|
|
271
|
+
`);
|
|
272
|
+
const sessionRows = sessionStmt.all(...ids);
|
|
273
|
+
const merged = [];
|
|
274
|
+
for (const sess of sessionRows) {
|
|
275
|
+
const best = bestBySession.get(sess.session_id);
|
|
276
|
+
if (best === undefined)
|
|
277
|
+
continue;
|
|
278
|
+
merged.push({ best, session: sess });
|
|
279
|
+
}
|
|
280
|
+
merged.sort((a, b) => {
|
|
281
|
+
if (a.best.score !== b.best.score)
|
|
282
|
+
return a.best.score - b.best.score;
|
|
283
|
+
// Tie-break by last_active_at DESC (newer first).
|
|
284
|
+
return a.session.last_active_at > b.session.last_active_at
|
|
285
|
+
? -1
|
|
286
|
+
: a.session.last_active_at < b.session.last_active_at
|
|
287
|
+
? 1
|
|
288
|
+
: 0;
|
|
289
|
+
});
|
|
290
|
+
const paged = merged.slice(offset, offset + limit);
|
|
291
|
+
const results = [];
|
|
292
|
+
for (const r of paged) {
|
|
293
|
+
const score = r.best.score;
|
|
294
|
+
// FTS5 BM25 is negative-weighted by default in SQLite (lower = better,
|
|
295
|
+
// negative numbers are common). Use abs for normalization so the
|
|
296
|
+
// 1/(1+|s|) formula keeps the (0, 1] mapping regardless of sign.
|
|
297
|
+
const relevance = 1 / (1 + Math.abs(score));
|
|
298
|
+
const rawSnip = r.best.snip;
|
|
299
|
+
const matchContext = opts.stripHighlights
|
|
300
|
+
? rawSnip.replace(/<<|>>/g, "")
|
|
301
|
+
: rawSnip;
|
|
302
|
+
results.push({
|
|
303
|
+
id: r.session.session_id,
|
|
304
|
+
gateway: r.session.gateway,
|
|
305
|
+
status: r.session.status,
|
|
306
|
+
relevance,
|
|
307
|
+
matchContext,
|
|
308
|
+
turns: Number(r.session.turn_count ?? 0),
|
|
309
|
+
lastActiveAt: r.session.last_active_at,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
return { query: trimmed, results, total };
|
|
313
|
+
}
|
|
314
|
+
function rebuild(ocas) {
|
|
315
|
+
// Step 1: Read sumeru_session_turns BEFORE any deletes to build
|
|
316
|
+
// turnHash → sessionId lookup (this table survives the rebuild).
|
|
317
|
+
const turnAssocRows = selectAllTurns.all();
|
|
318
|
+
const turnHashToSessionId = new Map();
|
|
319
|
+
for (const row of turnAssocRows) {
|
|
320
|
+
turnHashToSessionId.set(row.turn_hash, row.session_id);
|
|
321
|
+
}
|
|
322
|
+
// Step 2: DELETE from FTS index tables (sumeru_session_turns is NOT touched).
|
|
323
|
+
db.exec("BEGIN");
|
|
324
|
+
try {
|
|
325
|
+
db.exec("DELETE FROM sumeru_turn_index");
|
|
326
|
+
db.exec("DELETE FROM sumeru_session_index");
|
|
327
|
+
db.exec("COMMIT");
|
|
328
|
+
}
|
|
329
|
+
catch (err) {
|
|
330
|
+
try {
|
|
331
|
+
db.exec("ROLLBACK");
|
|
332
|
+
}
|
|
333
|
+
catch {
|
|
334
|
+
// best-effort
|
|
335
|
+
}
|
|
336
|
+
throw err;
|
|
337
|
+
}
|
|
338
|
+
// Step 3: Enumerate all session-meta nodes from ocas store.
|
|
339
|
+
const sessionMetaEntries = ocas.store.cas.listByType(ocas.sessionMetaSchemaHash);
|
|
340
|
+
// Step 4-6: Index each session-meta and build sessionId → gateway map.
|
|
341
|
+
const sessionIdToGateway = new Map();
|
|
342
|
+
for (const entry of sessionMetaEntries) {
|
|
343
|
+
const node = ocas.store.cas.get(entry.hash);
|
|
344
|
+
if (node === null)
|
|
345
|
+
continue;
|
|
346
|
+
const payload = node.payload;
|
|
347
|
+
indexSessionMeta({
|
|
348
|
+
sessionId: payload.id,
|
|
349
|
+
gateway: payload.gateway,
|
|
350
|
+
adapter: payload.adapter,
|
|
351
|
+
createdAt: payload.createdAt,
|
|
352
|
+
metaHash: entry.hash,
|
|
353
|
+
});
|
|
354
|
+
sessionIdToGateway.set(payload.id, payload.gateway);
|
|
355
|
+
}
|
|
356
|
+
// Step 7-8: Enumerate all turn nodes and index them.
|
|
357
|
+
const turnEntries = ocas.store.cas.listByType(ocas.turnSchemaHash);
|
|
358
|
+
for (const entry of turnEntries) {
|
|
359
|
+
const node = ocas.store.cas.get(entry.hash);
|
|
360
|
+
if (node === null)
|
|
361
|
+
continue;
|
|
362
|
+
const payload = node.payload;
|
|
363
|
+
// Look up sessionId from the turnHash → sessionId map.
|
|
364
|
+
const sessionId = turnHashToSessionId.get(entry.hash);
|
|
365
|
+
if (sessionId === undefined) {
|
|
366
|
+
console.warn(`[sumeru] rebuild: skipping orphaned turn ${entry.hash}`);
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
// Look up gateway from the sessionId → gateway map.
|
|
370
|
+
const gateway = sessionIdToGateway.get(sessionId);
|
|
371
|
+
if (gateway === undefined) {
|
|
372
|
+
console.warn(`[sumeru] rebuild: skipping orphaned turn ${entry.hash}`);
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
indexTurn({
|
|
376
|
+
turnHash: entry.hash,
|
|
377
|
+
sessionId,
|
|
378
|
+
gateway,
|
|
379
|
+
turnIndex: payload.index,
|
|
380
|
+
role: payload.role,
|
|
381
|
+
content: payload.content,
|
|
382
|
+
createdAt: payload.timestamp,
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
// Step 9: Corrective UPDATE to fix turn_count and last_active_at.
|
|
386
|
+
db.exec(`
|
|
387
|
+
UPDATE sumeru_session_index
|
|
388
|
+
SET turn_count = (
|
|
389
|
+
SELECT COUNT(*) FROM sumeru_turn_index
|
|
390
|
+
WHERE sumeru_turn_index.session_id = sumeru_session_index.session_id
|
|
391
|
+
),
|
|
392
|
+
last_active_at = COALESCE(
|
|
393
|
+
(SELECT MAX(created_at) FROM sumeru_turn_index
|
|
394
|
+
WHERE sumeru_turn_index.session_id = sumeru_session_index.session_id),
|
|
395
|
+
sumeru_session_index.created_at
|
|
396
|
+
)
|
|
397
|
+
`);
|
|
398
|
+
}
|
|
399
|
+
function turnCount() {
|
|
400
|
+
const row = countTurns.get();
|
|
401
|
+
return Number(row?.c ?? 0);
|
|
402
|
+
}
|
|
403
|
+
function close() {
|
|
404
|
+
db.close();
|
|
405
|
+
}
|
|
406
|
+
return {
|
|
407
|
+
indexSessionMeta,
|
|
408
|
+
indexTurn,
|
|
409
|
+
markSessionClosed,
|
|
410
|
+
appendSessionTurn,
|
|
411
|
+
listSessionTurns,
|
|
412
|
+
loadSessionTurnsBulk,
|
|
413
|
+
loadSessionRows,
|
|
414
|
+
search,
|
|
415
|
+
rebuild,
|
|
416
|
+
turnCount,
|
|
417
|
+
close,
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Walk every `@sumeru/session-meta` and `@sumeru/turn` node in the ocas store
|
|
422
|
+
* and rebuild the FTS5 index from scratch. The internal `rebuild` closure
|
|
423
|
+
* handles full enumeration via `listByType` and uses `sumeru_session_turns`
|
|
424
|
+
* for turn→session association.
|
|
425
|
+
*
|
|
426
|
+
* Callers no longer need to supply `roots` — the store is the source of truth.
|
|
427
|
+
*/
|
|
428
|
+
export function rebuildSearchIndex(index, ocas) {
|
|
429
|
+
index.rebuild(ocas);
|
|
430
|
+
}
|
|
431
|
+
/** Wrap a query in `"..."` and double internal `"` to force FTS5 phrase mode. */
|
|
432
|
+
export function quoteFtsPhrase(raw) {
|
|
433
|
+
return `"${raw.replace(/"/g, '""')}"`;
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Public wrapper that exposes the same `searchSessions` signature the spec
|
|
437
|
+
* documents. Equivalent to calling `index.search(opts)` with `stripHighlights`
|
|
438
|
+
* defaulted to `false`.
|
|
439
|
+
*/
|
|
440
|
+
export function searchSessions(index, opts) {
|
|
441
|
+
return index.search({
|
|
442
|
+
query: opts.query,
|
|
443
|
+
gateway: opts.gateway,
|
|
444
|
+
limit: opts.limit,
|
|
445
|
+
offset: opts.offset,
|
|
446
|
+
stripHighlights: opts.stripHighlights ?? false,
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
function clamp(n, min, max) {
|
|
450
|
+
if (Number.isNaN(n))
|
|
451
|
+
return min;
|
|
452
|
+
if (n < min)
|
|
453
|
+
return min;
|
|
454
|
+
if (n > max)
|
|
455
|
+
return max;
|
|
456
|
+
return n;
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Phase 6 (Refs #399): add the nullable `meta_hash` column to a pre-existing
|
|
460
|
+
* `sumeru_session_index` that predates it. SQLite has no
|
|
461
|
+
* `ADD COLUMN IF NOT EXISTS`, so we probe `PRAGMA table_info` first and only
|
|
462
|
+
* `ALTER` when the column is absent. Runs inside the open transaction; a no-op
|
|
463
|
+
* when the column already exists (fresh DBs already define it in the DDL).
|
|
464
|
+
*/
|
|
465
|
+
function migrateMetaHashColumn(db) {
|
|
466
|
+
const cols = db
|
|
467
|
+
.prepare("PRAGMA table_info(sumeru_session_index)")
|
|
468
|
+
.all();
|
|
469
|
+
const hasMetaHash = cols.some((c) => c.name === "meta_hash");
|
|
470
|
+
if (!hasMetaHash) {
|
|
471
|
+
db.exec("ALTER TABLE sumeru_session_index ADD COLUMN meta_hash TEXT");
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Normalize a persisted status column into a `SessionStatus` for rehydration.
|
|
476
|
+
* `closed` is preserved; everything else (idle, active, or any unexpected
|
|
477
|
+
* value) folds to `idle` — a process restart can never leave a send in flight,
|
|
478
|
+
* so `active` is a transient in-memory state that must not be restored.
|
|
479
|
+
*/
|
|
480
|
+
function normalizeStatus(raw) {
|
|
481
|
+
return raw === "closed" ? "closed" : "idle";
|
|
482
|
+
}
|
|
483
|
+
/** Open a SQLite handle, retrying on SQLITE_BUSY up to 3× with 50 ms backoff. */
|
|
484
|
+
function openWithRetry(dbPath) {
|
|
485
|
+
let lastErr = null;
|
|
486
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
487
|
+
try {
|
|
488
|
+
const db = new DatabaseSync(dbPath);
|
|
489
|
+
db.exec("PRAGMA journal_mode = WAL");
|
|
490
|
+
db.exec("PRAGMA foreign_keys = ON");
|
|
491
|
+
return db;
|
|
492
|
+
}
|
|
493
|
+
catch (err) {
|
|
494
|
+
lastErr = err;
|
|
495
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
496
|
+
if (!/busy/i.test(msg) && !/locked/i.test(msg))
|
|
497
|
+
throw err;
|
|
498
|
+
// 50 ms busy-wait
|
|
499
|
+
const start = Date.now();
|
|
500
|
+
while (Date.now() - start < 50) {
|
|
501
|
+
/* spin */
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
const cause = lastErr instanceof Error ? lastErr.message : String(lastErr);
|
|
506
|
+
throw new Error(`failed to create FTS5 index: ${cause}`);
|
|
507
|
+
}
|
|
508
|
+
//# sourceMappingURL=sqlite-index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite-index.js","sourceRoot":"","sources":["../../src/search/sqlite-index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 5 — search index types.
|
|
3
|
+
*
|
|
4
|
+
* These types are shared by the index module and the HTTP endpoints. The
|
|
5
|
+
* `SearchIndex` is a closure carrying the SQLite handle; consumers obtain
|
|
6
|
+
* one from `openSumeruOcas` via the new `searchIndex` slice on the result
|
|
7
|
+
* (mirrored on `OcasConfig`).
|
|
8
|
+
*/
|
|
9
|
+
import type { Hash, Store } from "@ocas/core";
|
|
10
|
+
import type { SessionStatus } from "../types.js";
|
|
11
|
+
/**
|
|
12
|
+
* Opaque handle to the FTS5 search index. Carries the SQLite database
|
|
13
|
+
* handle plus the prepared statements; created by `createSearchIndex`.
|
|
14
|
+
*/
|
|
15
|
+
export type SearchIndex = {
|
|
16
|
+
/**
|
|
17
|
+
* Insert a row into `sumeru_session_index`. Idempotent
|
|
18
|
+
* (`ON CONFLICT DO NOTHING`).
|
|
19
|
+
*/
|
|
20
|
+
indexSessionMeta: (meta: SessionMetaInput) => void;
|
|
21
|
+
/**
|
|
22
|
+
* Insert a row into `sumeru_turn_index` and bump the matching session
|
|
23
|
+
* row's `turn_count` and `last_active_at`. Idempotent on the turn hash.
|
|
24
|
+
*/
|
|
25
|
+
indexTurn: (input: IndexTurnInput) => void;
|
|
26
|
+
/** Update `sumeru_session_index.status` for a session. Best-effort. */
|
|
27
|
+
markSessionClosed: (sessionId: string) => void;
|
|
28
|
+
/**
|
|
29
|
+
* Phase 6 (Refs #399): persist one durable row in `sumeru_session_turns`
|
|
30
|
+
* recording `(session_id, turn_index, turn_hash)`. Idempotent on
|
|
31
|
+
* `(session_id, turn_index)` (`ON CONFLICT DO NOTHING`). This is the
|
|
32
|
+
* canonical turn-list pointer the session store rehydrates from on boot;
|
|
33
|
+
* it is NOT touched by `rebuild`.
|
|
34
|
+
*/
|
|
35
|
+
appendSessionTurn: (sessionId: string, turnIndex: number, turnHash: Hash) => void;
|
|
36
|
+
/**
|
|
37
|
+
* Phase 6 (Refs #399): read one session's ordered turn hashes from
|
|
38
|
+
* `sumeru_session_turns`, ordered by `turn_index ASC`. Returns `[]` for an
|
|
39
|
+
* unknown session.
|
|
40
|
+
*/
|
|
41
|
+
listSessionTurns: (sessionId: string) => Hash[];
|
|
42
|
+
/**
|
|
43
|
+
* Phase 6 (Refs #399): bulk-load every session's ordered turn hashes in a
|
|
44
|
+
* single query, grouped by `session_id` and ordered by `turn_index ASC`.
|
|
45
|
+
* Used at store construction so rehydration is O(1) queries.
|
|
46
|
+
*/
|
|
47
|
+
loadSessionTurnsBulk: () => Map<string, Hash[]>;
|
|
48
|
+
/**
|
|
49
|
+
* Phase 6 (Refs #399): read every persisted session row from
|
|
50
|
+
* `sumeru_session_index` (ordered by `created_at ASC`) so the store can
|
|
51
|
+
* rebuild its in-memory maps on boot.
|
|
52
|
+
*/
|
|
53
|
+
loadSessionRows: () => PersistedSessionRow[];
|
|
54
|
+
/** Run a search and return aggregated session-granularity hits. */
|
|
55
|
+
search: (opts: SearchOptions) => SearchResult;
|
|
56
|
+
/** Walk the ocas store and rebuild every index row from scratch. */
|
|
57
|
+
rebuild: (ocas: SearchRebuildOcas) => void;
|
|
58
|
+
/** Number of rows currently in `sumeru_turn_index`. */
|
|
59
|
+
turnCount: () => number;
|
|
60
|
+
/** Close the underlying SQLite handle (test-only — never used by server). */
|
|
61
|
+
close: () => void;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Payload accepted by `indexSessionMeta`. Mirrors the public
|
|
65
|
+
* `@sumeru/session-meta` schema with `status` defaulted to `idle`.
|
|
66
|
+
*
|
|
67
|
+
* Phase 6 (Refs #399): carries `metaHash` so the persisted
|
|
68
|
+
* `sumeru_session_index` row records the immutable `@sumeru/session-meta`
|
|
69
|
+
* node hash. On boot the store re-reads `config` from that node. `null` is
|
|
70
|
+
* accepted for callers/tests that have no meta node (the column is nullable).
|
|
71
|
+
*/
|
|
72
|
+
export type SessionMetaInput = {
|
|
73
|
+
sessionId: string;
|
|
74
|
+
gateway: string;
|
|
75
|
+
adapter: string;
|
|
76
|
+
createdAt: string;
|
|
77
|
+
metaHash: Hash | null;
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* One row read back from `sumeru_session_index` at boot (Refs #399). Used by
|
|
81
|
+
* the session store to rebuild its in-memory maps. `metaHash` is `null` for
|
|
82
|
+
* rows that predate the `meta_hash` column.
|
|
83
|
+
*/
|
|
84
|
+
export type PersistedSessionRow = {
|
|
85
|
+
sessionId: string;
|
|
86
|
+
gateway: string;
|
|
87
|
+
adapter: string;
|
|
88
|
+
status: SessionStatus;
|
|
89
|
+
createdAt: string;
|
|
90
|
+
lastActiveAt: string;
|
|
91
|
+
turnCount: number;
|
|
92
|
+
metaHash: Hash | null;
|
|
93
|
+
};
|
|
94
|
+
/** Payload accepted by `indexTurn`. */
|
|
95
|
+
export type IndexTurnInput = {
|
|
96
|
+
turnHash: Hash;
|
|
97
|
+
sessionId: string;
|
|
98
|
+
gateway: string;
|
|
99
|
+
turnIndex: number;
|
|
100
|
+
role: "user" | "assistant" | "system";
|
|
101
|
+
content: string;
|
|
102
|
+
createdAt: string;
|
|
103
|
+
};
|
|
104
|
+
/** Options accepted by `searchSessions` (and `search`). */
|
|
105
|
+
export type SearchOptions = {
|
|
106
|
+
/** Raw user query — `searchSessions` trims and quotes it before binding. */
|
|
107
|
+
query: string;
|
|
108
|
+
/** Filter by gateway. `null` means cross-gateway. */
|
|
109
|
+
gateway: string | null;
|
|
110
|
+
/** 1 ≤ limit ≤ 100. */
|
|
111
|
+
limit: number;
|
|
112
|
+
/** ≥ 0. */
|
|
113
|
+
offset: number;
|
|
114
|
+
/** When true, FTS5 highlight markers (`<<` / `>>`) are stripped. */
|
|
115
|
+
stripHighlights: boolean;
|
|
116
|
+
};
|
|
117
|
+
/** Single hit in `SearchResult.results`. */
|
|
118
|
+
export type SearchHit = {
|
|
119
|
+
id: string;
|
|
120
|
+
gateway: string;
|
|
121
|
+
status: SessionStatus;
|
|
122
|
+
relevance: number;
|
|
123
|
+
matchContext: string;
|
|
124
|
+
turns: number;
|
|
125
|
+
lastActiveAt: string;
|
|
126
|
+
};
|
|
127
|
+
/** Internal-only result of `searchSessions`. */
|
|
128
|
+
export type SearchResult = {
|
|
129
|
+
query: string;
|
|
130
|
+
results: SearchHit[];
|
|
131
|
+
/** Total distinct sessions matching `query` (before limit/offset). */
|
|
132
|
+
total: number;
|
|
133
|
+
};
|
|
134
|
+
/**
|
|
135
|
+
* Subset of the ocas store exposed to `rebuild`. The rebuild walker needs
|
|
136
|
+
* only `cas` reads and the two schema hashes.
|
|
137
|
+
*/
|
|
138
|
+
export type SearchRebuildOcas = {
|
|
139
|
+
store: Store;
|
|
140
|
+
turnSchemaHash: Hash;
|
|
141
|
+
sessionMetaSchemaHash: Hash;
|
|
142
|
+
};
|
|
143
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/search/types.ts"],"names":[],"mappings":"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"}
|