@yugenlab/vaayu 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 +21 -0
- package/README.md +365 -0
- package/chunks/chunk-E5A3SCDJ.js +246 -0
- package/chunks/chunk-G5VYCA6O.js +69 -0
- package/chunks/chunk-H76V36OF.js +1029 -0
- package/chunks/chunk-HAPVUJ6A.js +238 -0
- package/chunks/chunk-IEKAYVA3.js +137 -0
- package/chunks/chunk-IGKYKEKT.js +43 -0
- package/chunks/chunk-IIET2K6D.js +7728 -0
- package/chunks/chunk-ITIVYGUG.js +347 -0
- package/chunks/chunk-JAWZ7ANC.js +208 -0
- package/chunks/chunk-JZU37VQ5.js +714 -0
- package/chunks/chunk-KC6NRZ7U.js +198 -0
- package/chunks/chunk-KDRROLVN.js +433 -0
- package/chunks/chunk-L7JICQBW.js +1006 -0
- package/chunks/chunk-MINFB5LT.js +1479 -0
- package/chunks/chunk-MJ74G5RB.js +5816 -0
- package/chunks/chunk-S4TBVCL2.js +2158 -0
- package/chunks/chunk-SMVJRPAH.js +2753 -0
- package/chunks/chunk-U6OLJ36B.js +438 -0
- package/chunks/chunk-URGEODS5.js +752 -0
- package/chunks/chunk-YSU3BWV6.js +123 -0
- package/chunks/consolidation-indexer-TOTTDZXW.js +21 -0
- package/chunks/day-consolidation-NKO63HZQ.js +24 -0
- package/chunks/graphrag-ZI2FSU7S.js +13 -0
- package/chunks/hierarchical-temporal-search-ZD46UMKR.js +8 -0
- package/chunks/hybrid-search-ZVLZVGFS.js +19 -0
- package/chunks/memory-store-KNJPMBLQ.js +17 -0
- package/chunks/periodic-consolidation-BPKOZDGB.js +10 -0
- package/chunks/postgres-3ZXBYTPC.js +8 -0
- package/chunks/recall-GMVHWQWW.js +20 -0
- package/chunks/search-7HZETVMZ.js +18 -0
- package/chunks/session-store-XKPGKXUS.js +44 -0
- package/chunks/sqlite-JPF5TICX.js +152 -0
- package/chunks/src-6GVZTUH6.js +12 -0
- package/chunks/src-QAXOD5SB.js +273 -0
- package/chunks/suncalc-NOHGYHDU.js +186 -0
- package/chunks/tree-RSHKDTCR.js +10 -0
- package/gateway.js +61944 -0
- package/package.json +51 -0
- package/pair-cli.js +133 -0
|
@@ -0,0 +1,714 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EmbeddingService,
|
|
3
|
+
cosineSimilarity,
|
|
4
|
+
estimateTokens
|
|
5
|
+
} from "./chunk-JAWZ7ANC.js";
|
|
6
|
+
import {
|
|
7
|
+
listSessions,
|
|
8
|
+
loadSession
|
|
9
|
+
} from "./chunk-L7JICQBW.js";
|
|
10
|
+
import {
|
|
11
|
+
DatabaseManager,
|
|
12
|
+
initVectorsSchema
|
|
13
|
+
} from "./chunk-U6OLJ36B.js";
|
|
14
|
+
import {
|
|
15
|
+
getChitraguptaHome
|
|
16
|
+
} from "./chunk-KC6NRZ7U.js";
|
|
17
|
+
|
|
18
|
+
// ../chitragupta/packages/smriti/src/recall.ts
|
|
19
|
+
import fs2 from "fs";
|
|
20
|
+
import path2 from "path";
|
|
21
|
+
|
|
22
|
+
// ../chitragupta/packages/smriti/src/streams.ts
|
|
23
|
+
import fs from "fs";
|
|
24
|
+
import path from "path";
|
|
25
|
+
var STREAM_CONFIGS = {
|
|
26
|
+
identity: {
|
|
27
|
+
type: "identity",
|
|
28
|
+
filename: "identity.md",
|
|
29
|
+
preservation: 0.95,
|
|
30
|
+
description: "WHO \u2014 personal preferences, corrections, facts, style. Near-immutable."
|
|
31
|
+
},
|
|
32
|
+
projects: {
|
|
33
|
+
type: "projects",
|
|
34
|
+
filename: "projects.md",
|
|
35
|
+
preservation: 0.8,
|
|
36
|
+
description: "WHAT \u2014 active projects, decisions, stack, architecture notes."
|
|
37
|
+
},
|
|
38
|
+
tasks: {
|
|
39
|
+
type: "tasks",
|
|
40
|
+
filename: "tasks.md",
|
|
41
|
+
preservation: 0.7,
|
|
42
|
+
description: "TODO \u2014 pending tasks, completed items, archived entries."
|
|
43
|
+
},
|
|
44
|
+
flow: {
|
|
45
|
+
type: "flow",
|
|
46
|
+
filename: "flow/default.md",
|
|
47
|
+
preservation: 0.3,
|
|
48
|
+
description: "HOW \u2014 ephemeral context, current topic, mood, open questions. Per-device."
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
var STREAM_ORDER = ["identity", "projects", "tasks", "flow"];
|
|
52
|
+
var PRESERVATION_RATIOS = STREAM_ORDER.map(
|
|
53
|
+
(s) => STREAM_CONFIGS[s].preservation
|
|
54
|
+
);
|
|
55
|
+
function getStreamsRoot() {
|
|
56
|
+
return path.join(getChitraguptaHome(), "smriti", "streams");
|
|
57
|
+
}
|
|
58
|
+
function getStreamPath(streamType, deviceId) {
|
|
59
|
+
const root = getStreamsRoot();
|
|
60
|
+
if (streamType === "flow") {
|
|
61
|
+
const id = deviceId ?? "default";
|
|
62
|
+
return path.join(root, "flow", `${id}.md`);
|
|
63
|
+
}
|
|
64
|
+
return path.join(root, STREAM_CONFIGS[streamType].filename);
|
|
65
|
+
}
|
|
66
|
+
function buildStreamHeader(streamType, deviceId) {
|
|
67
|
+
const config = STREAM_CONFIGS[streamType];
|
|
68
|
+
const lines = [];
|
|
69
|
+
lines.push(`# ${streamType.charAt(0).toUpperCase() + streamType.slice(1)} Stream`);
|
|
70
|
+
lines.push("");
|
|
71
|
+
lines.push(`> ${config.description}`);
|
|
72
|
+
lines.push(`> Preservation ratio: ${config.preservation}`);
|
|
73
|
+
if (streamType === "flow" && deviceId) {
|
|
74
|
+
lines.push(`> Device: ${deviceId}`);
|
|
75
|
+
}
|
|
76
|
+
lines.push("");
|
|
77
|
+
return lines.join("\n");
|
|
78
|
+
}
|
|
79
|
+
function buildStreamFooter(content) {
|
|
80
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
81
|
+
const tokens = estimateTokens(content);
|
|
82
|
+
const lines = [];
|
|
83
|
+
lines.push("");
|
|
84
|
+
lines.push("---");
|
|
85
|
+
lines.push("");
|
|
86
|
+
lines.push("## Meta");
|
|
87
|
+
lines.push("");
|
|
88
|
+
lines.push(`- last_updated: ${now}`);
|
|
89
|
+
lines.push(`- token_count: ${tokens}`);
|
|
90
|
+
lines.push("");
|
|
91
|
+
return lines.join("\n");
|
|
92
|
+
}
|
|
93
|
+
function parseStreamFile(raw) {
|
|
94
|
+
const footerMarker = "\n---\n\n## Meta";
|
|
95
|
+
const footerIdx = raw.lastIndexOf(footerMarker);
|
|
96
|
+
let bodyWithHeader;
|
|
97
|
+
let footer;
|
|
98
|
+
if (footerIdx !== -1) {
|
|
99
|
+
bodyWithHeader = raw.slice(0, footerIdx);
|
|
100
|
+
footer = raw.slice(footerIdx);
|
|
101
|
+
} else {
|
|
102
|
+
bodyWithHeader = raw;
|
|
103
|
+
footer = "";
|
|
104
|
+
}
|
|
105
|
+
const headerEndPattern = /^(?:#[^\n]*\n\n(?:>[^\n]*\n)*)\n/m;
|
|
106
|
+
const headerMatch = bodyWithHeader.match(headerEndPattern);
|
|
107
|
+
let header;
|
|
108
|
+
let content;
|
|
109
|
+
if (headerMatch) {
|
|
110
|
+
header = headerMatch[0];
|
|
111
|
+
content = bodyWithHeader.slice(header.length);
|
|
112
|
+
} else {
|
|
113
|
+
const lines = bodyWithHeader.split("\n");
|
|
114
|
+
let headerEndLine = 0;
|
|
115
|
+
let pastTitle = false;
|
|
116
|
+
for (let i = 0; i < lines.length; i++) {
|
|
117
|
+
if (lines[i].startsWith("#")) {
|
|
118
|
+
pastTitle = true;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (pastTitle && !lines[i].startsWith(">") && lines[i].trim() === "") {
|
|
122
|
+
headerEndLine = i + 1;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
if (pastTitle && !lines[i].startsWith(">") && lines[i].trim() !== "") {
|
|
126
|
+
headerEndLine = i;
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
header = lines.slice(0, headerEndLine).join("\n") + "\n";
|
|
131
|
+
content = lines.slice(headerEndLine).join("\n");
|
|
132
|
+
}
|
|
133
|
+
return { header, content: content.trim(), footer };
|
|
134
|
+
}
|
|
135
|
+
var StreamManager = class {
|
|
136
|
+
root;
|
|
137
|
+
constructor() {
|
|
138
|
+
this.root = getStreamsRoot();
|
|
139
|
+
}
|
|
140
|
+
// ─── Ensure Directories ─────────────────────────────────────────
|
|
141
|
+
/**
|
|
142
|
+
* Ensure the streams directory structure exists.
|
|
143
|
+
*/
|
|
144
|
+
ensureDirs() {
|
|
145
|
+
fs.mkdirSync(this.root, { recursive: true });
|
|
146
|
+
fs.mkdirSync(path.join(this.root, "flow"), { recursive: true });
|
|
147
|
+
}
|
|
148
|
+
// ─── Read ────────────────────────────────────────────────────────
|
|
149
|
+
/**
|
|
150
|
+
* Read the full content of a stream file.
|
|
151
|
+
* Returns empty string if the stream file does not exist.
|
|
152
|
+
*/
|
|
153
|
+
read(streamType, deviceId) {
|
|
154
|
+
const filePath = getStreamPath(streamType, deviceId);
|
|
155
|
+
try {
|
|
156
|
+
if (fs.existsSync(filePath)) {
|
|
157
|
+
return fs.readFileSync(filePath, "utf-8");
|
|
158
|
+
}
|
|
159
|
+
} catch {
|
|
160
|
+
}
|
|
161
|
+
return "";
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Read just the content body of a stream (without header/footer).
|
|
165
|
+
*/
|
|
166
|
+
readContent(streamType, deviceId) {
|
|
167
|
+
const raw = this.read(streamType, deviceId);
|
|
168
|
+
if (!raw) return "";
|
|
169
|
+
const { content } = parseStreamFile(raw);
|
|
170
|
+
return content;
|
|
171
|
+
}
|
|
172
|
+
// ─── Write ───────────────────────────────────────────────────────
|
|
173
|
+
/**
|
|
174
|
+
* Overwrite a stream file with new content.
|
|
175
|
+
* Automatically adds the header and meta footer.
|
|
176
|
+
*/
|
|
177
|
+
write(streamType, content, deviceId) {
|
|
178
|
+
this.ensureDirs();
|
|
179
|
+
const filePath = getStreamPath(streamType, deviceId);
|
|
180
|
+
const header = buildStreamHeader(streamType, deviceId);
|
|
181
|
+
const fullContent = header + content;
|
|
182
|
+
const footer = buildStreamFooter(fullContent);
|
|
183
|
+
fs.writeFileSync(filePath, fullContent + footer, "utf-8");
|
|
184
|
+
}
|
|
185
|
+
// ─── Append ──────────────────────────────────────────────────────
|
|
186
|
+
/**
|
|
187
|
+
* Append content to a stream file.
|
|
188
|
+
* If the stream doesn't exist, creates it with a header.
|
|
189
|
+
* Updates the meta footer.
|
|
190
|
+
*/
|
|
191
|
+
append(streamType, entry, deviceId) {
|
|
192
|
+
this.ensureDirs();
|
|
193
|
+
const filePath = getStreamPath(streamType, deviceId);
|
|
194
|
+
const existing = this.read(streamType, deviceId);
|
|
195
|
+
if (!existing) {
|
|
196
|
+
const header2 = buildStreamHeader(streamType, deviceId);
|
|
197
|
+
const content2 = header2 + entry + "\n";
|
|
198
|
+
const footer2 = buildStreamFooter(content2);
|
|
199
|
+
fs.writeFileSync(filePath, content2 + footer2, "utf-8");
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const { header, content } = parseStreamFile(existing);
|
|
203
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
204
|
+
const separator = content ? "\n\n" : "";
|
|
205
|
+
const newContent = content + separator + `*${timestamp}*
|
|
206
|
+
|
|
207
|
+
${entry}`;
|
|
208
|
+
const fullContent = header + newContent + "\n";
|
|
209
|
+
const footer = buildStreamFooter(fullContent);
|
|
210
|
+
fs.writeFileSync(filePath, fullContent + footer, "utf-8");
|
|
211
|
+
}
|
|
212
|
+
// ─── Token Counting ──────────────────────────────────────────────
|
|
213
|
+
/**
|
|
214
|
+
* Get the estimated token count for a stream.
|
|
215
|
+
*/
|
|
216
|
+
getTokenCount(streamType, deviceId) {
|
|
217
|
+
const raw = this.read(streamType, deviceId);
|
|
218
|
+
return estimateTokens(raw);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Get token counts for all streams (flow uses default device unless specified).
|
|
222
|
+
*/
|
|
223
|
+
getAllTokenCounts(deviceId) {
|
|
224
|
+
return {
|
|
225
|
+
identity: this.getTokenCount("identity"),
|
|
226
|
+
projects: this.getTokenCount("projects"),
|
|
227
|
+
tasks: this.getTokenCount("tasks"),
|
|
228
|
+
flow: this.getTokenCount("flow", deviceId)
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
// ─── Budget Allocation ───────────────────────────────────────────
|
|
232
|
+
/**
|
|
233
|
+
* Compute per-stream token budgets from a total budget.
|
|
234
|
+
*
|
|
235
|
+
* Budget allocation is proportional to preservation ratios:
|
|
236
|
+
* budget_i = totalBudget * (preservation_i / sum(preservation))
|
|
237
|
+
*
|
|
238
|
+
* This ensures higher-preservation streams (identity) get more budget
|
|
239
|
+
* than ephemeral streams (flow).
|
|
240
|
+
*/
|
|
241
|
+
getStreamBudgets(totalBudget) {
|
|
242
|
+
const totalPreservation = STREAM_ORDER.reduce(
|
|
243
|
+
(sum, s) => sum + STREAM_CONFIGS[s].preservation,
|
|
244
|
+
0
|
|
245
|
+
);
|
|
246
|
+
const budgets = {};
|
|
247
|
+
for (const stream of STREAM_ORDER) {
|
|
248
|
+
const ratio = STREAM_CONFIGS[stream].preservation / totalPreservation;
|
|
249
|
+
budgets[stream] = Math.floor(totalBudget * ratio);
|
|
250
|
+
}
|
|
251
|
+
const allocated = Object.values(budgets).reduce((a, b) => a + b, 0);
|
|
252
|
+
const remainder = totalBudget - allocated;
|
|
253
|
+
if (remainder > 0) {
|
|
254
|
+
budgets.identity += remainder;
|
|
255
|
+
}
|
|
256
|
+
return budgets;
|
|
257
|
+
}
|
|
258
|
+
// ─── Preservation Enforcement ────────────────────────────────────
|
|
259
|
+
/**
|
|
260
|
+
* Trim a stream's content to fit within its preservation budget.
|
|
261
|
+
* Removes the oldest entries first (from the top of the content section).
|
|
262
|
+
*
|
|
263
|
+
* @param streamType - Which stream to trim
|
|
264
|
+
* @param maxTokens - Maximum token budget for this stream
|
|
265
|
+
* @param deviceId - Device ID for flow streams
|
|
266
|
+
* @returns Number of tokens trimmed
|
|
267
|
+
*/
|
|
268
|
+
enforcePreservation(streamType, maxTokens, deviceId) {
|
|
269
|
+
const raw = this.read(streamType, deviceId);
|
|
270
|
+
if (!raw) return 0;
|
|
271
|
+
const currentTokens = estimateTokens(raw);
|
|
272
|
+
if (currentTokens <= maxTokens) return 0;
|
|
273
|
+
const { content } = parseStreamFile(raw);
|
|
274
|
+
const ENTRY_SEP = /(\n\n\*\d{4}-)/;
|
|
275
|
+
const parts = content.split(ENTRY_SEP);
|
|
276
|
+
if (parts.length <= 1) {
|
|
277
|
+
return 0;
|
|
278
|
+
}
|
|
279
|
+
const entries = [parts[0]];
|
|
280
|
+
for (let i = 1; i < parts.length; i += 2) {
|
|
281
|
+
entries.push((parts[i] ?? "") + (parts[i + 1] ?? ""));
|
|
282
|
+
}
|
|
283
|
+
if (entries.length <= 1) {
|
|
284
|
+
return 0;
|
|
285
|
+
}
|
|
286
|
+
let trimmedEntries = [...entries];
|
|
287
|
+
let currentContent = trimmedEntries.join("");
|
|
288
|
+
let tokens = estimateTokens(currentContent);
|
|
289
|
+
let totalTrimmed = 0;
|
|
290
|
+
while (tokens > maxTokens && trimmedEntries.length > 1) {
|
|
291
|
+
const removed = trimmedEntries.shift();
|
|
292
|
+
totalTrimmed += estimateTokens(removed);
|
|
293
|
+
currentContent = trimmedEntries.join("");
|
|
294
|
+
tokens = estimateTokens(currentContent);
|
|
295
|
+
}
|
|
296
|
+
if (totalTrimmed > 0) {
|
|
297
|
+
this.write(streamType, trimmedEntries.join(""), deviceId);
|
|
298
|
+
}
|
|
299
|
+
return totalTrimmed;
|
|
300
|
+
}
|
|
301
|
+
// ─── Stream Existence ────────────────────────────────────────────
|
|
302
|
+
/**
|
|
303
|
+
* Check if a stream file exists on disk.
|
|
304
|
+
*/
|
|
305
|
+
exists(streamType, deviceId) {
|
|
306
|
+
const filePath = getStreamPath(streamType, deviceId);
|
|
307
|
+
return fs.existsSync(filePath);
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* List all flow device files.
|
|
311
|
+
*/
|
|
312
|
+
listFlowDevices() {
|
|
313
|
+
const flowDir = path.join(this.root, "flow");
|
|
314
|
+
if (!fs.existsSync(flowDir)) return [];
|
|
315
|
+
try {
|
|
316
|
+
return fs.readdirSync(flowDir).filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, ""));
|
|
317
|
+
} catch {
|
|
318
|
+
return [];
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Get the streams root directory path.
|
|
323
|
+
*/
|
|
324
|
+
getRoot() {
|
|
325
|
+
return this.root;
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
// ../chitragupta/packages/smriti/src/recall-scoring.ts
|
|
330
|
+
var _embeddingService = new EmbeddingService();
|
|
331
|
+
function configureRecallScoring(options) {
|
|
332
|
+
if (options.embeddingService) {
|
|
333
|
+
_embeddingService = options.embeddingService;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
async function getEmbedding(text) {
|
|
337
|
+
return _embeddingService.getEmbedding(text);
|
|
338
|
+
}
|
|
339
|
+
function summarizeSession(session) {
|
|
340
|
+
const parts = [];
|
|
341
|
+
for (const turn of session.turns) {
|
|
342
|
+
if (turn.role === "user") {
|
|
343
|
+
parts.push(turn.content.slice(0, 200));
|
|
344
|
+
}
|
|
345
|
+
if (parts.join(" ").length > 400) break;
|
|
346
|
+
}
|
|
347
|
+
return parts.join(" | ").slice(0, 500);
|
|
348
|
+
}
|
|
349
|
+
function extractIndexText(session) {
|
|
350
|
+
const parts = [];
|
|
351
|
+
parts.push(session.meta.title);
|
|
352
|
+
parts.push(session.meta.title);
|
|
353
|
+
parts.push(session.meta.tags.join(" "));
|
|
354
|
+
for (const turn of session.turns) {
|
|
355
|
+
parts.push(turn.content.slice(0, 1e3));
|
|
356
|
+
if (turn.toolCalls) {
|
|
357
|
+
for (const tc of turn.toolCalls) {
|
|
358
|
+
parts.push(tc.name);
|
|
359
|
+
parts.push(tc.input.slice(0, 200));
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return parts.join("\n").slice(0, 8e3);
|
|
364
|
+
}
|
|
365
|
+
function resetOllamaAvailability() {
|
|
366
|
+
_embeddingService.resetAvailability();
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// ../chitragupta/packages/smriti/src/recall.ts
|
|
370
|
+
var DEFAULT_TOP_K = 10;
|
|
371
|
+
var DEFAULT_THRESHOLD = 0.3;
|
|
372
|
+
function getIndexDir() {
|
|
373
|
+
return path2.join(getChitraguptaHome(), "smriti", "index");
|
|
374
|
+
}
|
|
375
|
+
function getEmbeddingsPath() {
|
|
376
|
+
return path2.join(getIndexDir(), "embeddings.json");
|
|
377
|
+
}
|
|
378
|
+
function vectorToBlob(vector) {
|
|
379
|
+
const float32 = new Float32Array(vector);
|
|
380
|
+
return Buffer.from(float32.buffer);
|
|
381
|
+
}
|
|
382
|
+
function blobToVector(blob) {
|
|
383
|
+
const float32 = new Float32Array(
|
|
384
|
+
blob.buffer,
|
|
385
|
+
blob.byteOffset,
|
|
386
|
+
blob.byteLength / 4
|
|
387
|
+
);
|
|
388
|
+
return Array.from(float32);
|
|
389
|
+
}
|
|
390
|
+
var _dbInitialized = false;
|
|
391
|
+
function _resetRecallDbInit() {
|
|
392
|
+
_dbInitialized = false;
|
|
393
|
+
}
|
|
394
|
+
var RecallEngine = class {
|
|
395
|
+
entries = [];
|
|
396
|
+
loaded = false;
|
|
397
|
+
constructor() {
|
|
398
|
+
this.loadIndex();
|
|
399
|
+
}
|
|
400
|
+
// ─── SQLite Access ────────────────────────────────────────────────
|
|
401
|
+
getVectorsDb() {
|
|
402
|
+
const dbm = DatabaseManager.instance();
|
|
403
|
+
if (!_dbInitialized) {
|
|
404
|
+
try {
|
|
405
|
+
initVectorsSchema(dbm);
|
|
406
|
+
_dbInitialized = true;
|
|
407
|
+
} catch (err) {
|
|
408
|
+
process.stderr.write(`[chitragupta] vectors DB schema init failed: ${err instanceof Error ? err.message : err}
|
|
409
|
+
`);
|
|
410
|
+
throw err;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return dbm.get("vectors");
|
|
414
|
+
}
|
|
415
|
+
// ─── Index Management ────────────────────────────────────────────
|
|
416
|
+
loadIndex() {
|
|
417
|
+
try {
|
|
418
|
+
const db = this.getVectorsDb();
|
|
419
|
+
const rows = db.prepare("SELECT * FROM embeddings").all();
|
|
420
|
+
this.entries = rows.map((row) => {
|
|
421
|
+
let metadata = {};
|
|
422
|
+
try {
|
|
423
|
+
metadata = JSON.parse(row.metadata ?? "{}");
|
|
424
|
+
} catch {
|
|
425
|
+
}
|
|
426
|
+
return {
|
|
427
|
+
id: row.id,
|
|
428
|
+
vector: blobToVector(row.vector),
|
|
429
|
+
source: row.source_type,
|
|
430
|
+
sourceId: row.source_id,
|
|
431
|
+
title: metadata.title ?? "",
|
|
432
|
+
text: row.text,
|
|
433
|
+
summary: metadata.summary ?? "",
|
|
434
|
+
tags: metadata.tags ?? [],
|
|
435
|
+
date: metadata.date ?? new Date(row.created_at).toISOString(),
|
|
436
|
+
deviceId: metadata.deviceId
|
|
437
|
+
};
|
|
438
|
+
});
|
|
439
|
+
} catch {
|
|
440
|
+
this.loadIndexJson();
|
|
441
|
+
}
|
|
442
|
+
this.loaded = true;
|
|
443
|
+
}
|
|
444
|
+
saveIndex() {
|
|
445
|
+
try {
|
|
446
|
+
const db = this.getVectorsDb();
|
|
447
|
+
const txn = db.transaction(() => {
|
|
448
|
+
db.prepare("DELETE FROM embeddings").run();
|
|
449
|
+
const insert = db.prepare(`
|
|
450
|
+
INSERT INTO embeddings (id, vector, text, source_type, source_id, dimensions, metadata, created_at)
|
|
451
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
452
|
+
`);
|
|
453
|
+
for (const entry of this.entries) {
|
|
454
|
+
insert.run(
|
|
455
|
+
entry.id,
|
|
456
|
+
vectorToBlob(entry.vector),
|
|
457
|
+
entry.text,
|
|
458
|
+
entry.source,
|
|
459
|
+
entry.sourceId,
|
|
460
|
+
entry.vector.length,
|
|
461
|
+
JSON.stringify({
|
|
462
|
+
title: entry.title,
|
|
463
|
+
summary: entry.summary,
|
|
464
|
+
tags: entry.tags,
|
|
465
|
+
date: entry.date,
|
|
466
|
+
deviceId: entry.deviceId
|
|
467
|
+
}),
|
|
468
|
+
new Date(entry.date).getTime()
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
txn();
|
|
473
|
+
} catch {
|
|
474
|
+
this.saveIndexJson();
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
// ─── JSON Fallback (legacy) ────────────────────────────────────────
|
|
478
|
+
loadIndexJson() {
|
|
479
|
+
try {
|
|
480
|
+
const indexPath = getEmbeddingsPath();
|
|
481
|
+
if (fs2.existsSync(indexPath)) {
|
|
482
|
+
const raw = fs2.readFileSync(indexPath, "utf-8");
|
|
483
|
+
this.entries = JSON.parse(raw);
|
|
484
|
+
}
|
|
485
|
+
} catch {
|
|
486
|
+
this.entries = [];
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
saveIndexJson() {
|
|
490
|
+
try {
|
|
491
|
+
const dir = getIndexDir();
|
|
492
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
493
|
+
fs2.writeFileSync(
|
|
494
|
+
getEmbeddingsPath(),
|
|
495
|
+
JSON.stringify(this.entries, null, " "),
|
|
496
|
+
"utf-8"
|
|
497
|
+
);
|
|
498
|
+
} catch {
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
// ─── Session Indexing ────────────────────────────────────────────
|
|
502
|
+
async indexSession(session) {
|
|
503
|
+
if (!this.loaded) this.loadIndex();
|
|
504
|
+
this.entries = this.entries.filter(
|
|
505
|
+
(e) => !(e.source === "session" && e.sourceId === session.meta.id)
|
|
506
|
+
);
|
|
507
|
+
const indexText = extractIndexText(session);
|
|
508
|
+
const summary = summarizeSession(session);
|
|
509
|
+
const chunkSize = 4e3;
|
|
510
|
+
const chunks = [];
|
|
511
|
+
if (indexText.length <= chunkSize) {
|
|
512
|
+
chunks.push(indexText);
|
|
513
|
+
} else {
|
|
514
|
+
for (let i = 0; i < indexText.length; i += chunkSize - 500) {
|
|
515
|
+
chunks.push(indexText.slice(i, i + chunkSize));
|
|
516
|
+
if (i + chunkSize >= indexText.length) break;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
520
|
+
const vector = await getEmbedding(chunks[i]);
|
|
521
|
+
this.entries.push({
|
|
522
|
+
id: chunks.length > 1 ? `${session.meta.id}-chunk-${i}` : session.meta.id,
|
|
523
|
+
vector,
|
|
524
|
+
source: "session",
|
|
525
|
+
sourceId: session.meta.id,
|
|
526
|
+
title: session.meta.title,
|
|
527
|
+
text: chunks[i],
|
|
528
|
+
summary,
|
|
529
|
+
tags: session.meta.tags,
|
|
530
|
+
date: session.meta.updated
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
this.saveIndex();
|
|
534
|
+
}
|
|
535
|
+
async indexStream(streamType, content, deviceId) {
|
|
536
|
+
if (!this.loaded) this.loadIndex();
|
|
537
|
+
const sourceId = deviceId ? `stream-${streamType}-${deviceId}` : `stream-${streamType}`;
|
|
538
|
+
this.entries = this.entries.filter(
|
|
539
|
+
(e) => !(e.source === "stream" && e.sourceId === sourceId)
|
|
540
|
+
);
|
|
541
|
+
if (!content || content.trim().length === 0) return;
|
|
542
|
+
const vector = await getEmbedding(content.slice(0, 8e3));
|
|
543
|
+
this.entries.push({
|
|
544
|
+
id: sourceId,
|
|
545
|
+
vector,
|
|
546
|
+
source: "stream",
|
|
547
|
+
sourceId,
|
|
548
|
+
title: `${streamType} stream${deviceId ? ` (${deviceId})` : ""}`,
|
|
549
|
+
text: content.slice(0, 4e3),
|
|
550
|
+
summary: content.slice(0, 300),
|
|
551
|
+
tags: [streamType],
|
|
552
|
+
date: (/* @__PURE__ */ new Date()).toISOString(),
|
|
553
|
+
deviceId
|
|
554
|
+
});
|
|
555
|
+
this.saveIndex();
|
|
556
|
+
}
|
|
557
|
+
// ─── Recall (Search) ─────────────────────────────────────────────
|
|
558
|
+
async recall(query, options) {
|
|
559
|
+
if (!this.loaded) this.loadIndex();
|
|
560
|
+
if (this.entries.length === 0) return [];
|
|
561
|
+
const topK = options?.topK ?? DEFAULT_TOP_K;
|
|
562
|
+
const threshold = options?.threshold ?? DEFAULT_THRESHOLD;
|
|
563
|
+
const queryVector = await getEmbedding(query);
|
|
564
|
+
const scored = [];
|
|
565
|
+
for (const entry of this.entries) {
|
|
566
|
+
if (options?.deviceFilter && entry.deviceId !== options.deviceFilter) continue;
|
|
567
|
+
if (options?.dateRange) {
|
|
568
|
+
const entryDate = new Date(entry.date).getTime();
|
|
569
|
+
const rangeStart = new Date(options.dateRange[0]).getTime();
|
|
570
|
+
const rangeEnd = new Date(options.dateRange[1]).getTime();
|
|
571
|
+
if (entryDate < rangeStart || entryDate > rangeEnd) continue;
|
|
572
|
+
}
|
|
573
|
+
if (options?.tagFilter && options.tagFilter.length > 0) {
|
|
574
|
+
const hasTag = options.tagFilter.some((tag) => entry.tags.includes(tag));
|
|
575
|
+
if (!hasTag) continue;
|
|
576
|
+
}
|
|
577
|
+
const score = cosineSimilarity(queryVector, entry.vector);
|
|
578
|
+
if (score >= threshold) {
|
|
579
|
+
scored.push({ entry, score });
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
scored.sort((a, b) => b.score - a.score);
|
|
583
|
+
const seen = /* @__PURE__ */ new Set();
|
|
584
|
+
const results = [];
|
|
585
|
+
for (const { entry, score } of scored) {
|
|
586
|
+
if (results.length >= topK) break;
|
|
587
|
+
if (seen.has(entry.sourceId)) continue;
|
|
588
|
+
seen.add(entry.sourceId);
|
|
589
|
+
results.push({
|
|
590
|
+
sessionId: entry.sourceId,
|
|
591
|
+
title: entry.title,
|
|
592
|
+
relevance: Math.max(0, Math.min(1, score)),
|
|
593
|
+
summary: entry.summary,
|
|
594
|
+
source: entry.source,
|
|
595
|
+
matchedContent: entry.text.slice(0, 1e3)
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
return results;
|
|
599
|
+
}
|
|
600
|
+
// ─── Re-index ────────────────────────────────────────────────────
|
|
601
|
+
async reindexAll() {
|
|
602
|
+
this.entries = [];
|
|
603
|
+
const allMetas = listSessions();
|
|
604
|
+
for (const meta of allMetas) {
|
|
605
|
+
try {
|
|
606
|
+
const session = loadSession(meta.id, meta.project);
|
|
607
|
+
await this.indexSession(session);
|
|
608
|
+
} catch {
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
const streamManager = new StreamManager();
|
|
612
|
+
for (const streamType of STREAM_ORDER) {
|
|
613
|
+
if (streamType === "flow") {
|
|
614
|
+
const devices = streamManager.listFlowDevices();
|
|
615
|
+
for (const device of devices) {
|
|
616
|
+
const content = streamManager.readContent("flow", device);
|
|
617
|
+
if (content) {
|
|
618
|
+
await this.indexStream("flow", content, device);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
} else {
|
|
622
|
+
const content = streamManager.readContent(streamType);
|
|
623
|
+
if (content) {
|
|
624
|
+
await this.indexStream(streamType, content);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
this.saveIndex();
|
|
629
|
+
}
|
|
630
|
+
getIndexSize() {
|
|
631
|
+
return this.entries.length;
|
|
632
|
+
}
|
|
633
|
+
resetOllamaAvailability() {
|
|
634
|
+
resetOllamaAvailability();
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
function migrateEmbeddingsJson() {
|
|
638
|
+
const jsonPath = getEmbeddingsPath();
|
|
639
|
+
if (!fs2.existsSync(jsonPath)) {
|
|
640
|
+
return { migrated: 0, skipped: 0 };
|
|
641
|
+
}
|
|
642
|
+
let entries;
|
|
643
|
+
try {
|
|
644
|
+
const raw = fs2.readFileSync(jsonPath, "utf-8");
|
|
645
|
+
entries = JSON.parse(raw);
|
|
646
|
+
} catch {
|
|
647
|
+
return { migrated: 0, skipped: 0 };
|
|
648
|
+
}
|
|
649
|
+
if (!entries || entries.length === 0) {
|
|
650
|
+
return { migrated: 0, skipped: 0 };
|
|
651
|
+
}
|
|
652
|
+
let migrated = 0;
|
|
653
|
+
let skipped = 0;
|
|
654
|
+
try {
|
|
655
|
+
const dbm = DatabaseManager.instance();
|
|
656
|
+
initVectorsSchema(dbm);
|
|
657
|
+
const db = dbm.get("vectors");
|
|
658
|
+
const txn = db.transaction(() => {
|
|
659
|
+
const insert = db.prepare(`
|
|
660
|
+
INSERT OR IGNORE INTO embeddings (id, vector, text, source_type, source_id, dimensions, metadata, created_at)
|
|
661
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
662
|
+
`);
|
|
663
|
+
for (const entry of entries) {
|
|
664
|
+
try {
|
|
665
|
+
const result = insert.run(
|
|
666
|
+
entry.id,
|
|
667
|
+
vectorToBlob(entry.vector),
|
|
668
|
+
entry.text,
|
|
669
|
+
entry.source,
|
|
670
|
+
entry.sourceId,
|
|
671
|
+
entry.vector.length,
|
|
672
|
+
JSON.stringify({
|
|
673
|
+
title: entry.title,
|
|
674
|
+
summary: entry.summary,
|
|
675
|
+
tags: entry.tags,
|
|
676
|
+
date: entry.date,
|
|
677
|
+
deviceId: entry.deviceId
|
|
678
|
+
}),
|
|
679
|
+
new Date(entry.date).getTime()
|
|
680
|
+
);
|
|
681
|
+
if (result.changes > 0) {
|
|
682
|
+
migrated++;
|
|
683
|
+
} else {
|
|
684
|
+
skipped++;
|
|
685
|
+
}
|
|
686
|
+
} catch {
|
|
687
|
+
skipped++;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
});
|
|
691
|
+
txn();
|
|
692
|
+
try {
|
|
693
|
+
fs2.renameSync(jsonPath, jsonPath + ".bak");
|
|
694
|
+
} catch {
|
|
695
|
+
}
|
|
696
|
+
} catch {
|
|
697
|
+
return { migrated: 0, skipped: entries.length };
|
|
698
|
+
}
|
|
699
|
+
return { migrated, skipped };
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
export {
|
|
703
|
+
STREAM_CONFIGS,
|
|
704
|
+
STREAM_ORDER,
|
|
705
|
+
PRESERVATION_RATIOS,
|
|
706
|
+
StreamManager,
|
|
707
|
+
configureRecallScoring,
|
|
708
|
+
vectorToBlob,
|
|
709
|
+
blobToVector,
|
|
710
|
+
_resetRecallDbInit,
|
|
711
|
+
RecallEngine,
|
|
712
|
+
migrateEmbeddingsJson
|
|
713
|
+
};
|
|
714
|
+
//# sourceMappingURL=chunk-JZU37VQ5.js.map
|