@gencode/agents 0.0.4 → 0.0.6

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.
Files changed (111) hide show
  1. package/dist/commands/new.js +6 -6
  2. package/dist/commands/new.js.map +1 -1
  3. package/dist/config/types.d.ts +2 -2
  4. package/dist/config/types.d.ts.map +1 -1
  5. package/dist/plugins/hooks.d.ts +20 -1
  6. package/dist/plugins/hooks.d.ts.map +1 -1
  7. package/dist/plugins/hooks.js.map +1 -1
  8. package/dist/plugins/index.d.ts +1 -1
  9. package/dist/plugins/index.d.ts.map +1 -1
  10. package/dist/plugins/index.js.map +1 -1
  11. package/dist/runner/agent-runtime.d.ts +62 -0
  12. package/dist/runner/agent-runtime.d.ts.map +1 -0
  13. package/dist/runner/agent-runtime.js +179 -0
  14. package/dist/runner/agent-runtime.js.map +1 -0
  15. package/dist/runner/announce-loop.d.ts +41 -0
  16. package/dist/runner/announce-loop.d.ts.map +1 -0
  17. package/dist/runner/announce-loop.js +94 -0
  18. package/dist/runner/announce-loop.js.map +1 -0
  19. package/dist/runner/event-dispatcher.d.ts +31 -0
  20. package/dist/runner/event-dispatcher.d.ts.map +1 -0
  21. package/dist/runner/event-dispatcher.js +87 -0
  22. package/dist/runner/event-dispatcher.js.map +1 -0
  23. package/dist/runner/finalizer.d.ts +30 -0
  24. package/dist/runner/finalizer.d.ts.map +1 -0
  25. package/dist/runner/finalizer.js +75 -0
  26. package/dist/runner/finalizer.js.map +1 -0
  27. package/dist/runner/invocation-resolver.d.ts +67 -0
  28. package/dist/runner/invocation-resolver.d.ts.map +1 -0
  29. package/dist/runner/invocation-resolver.js +224 -0
  30. package/dist/runner/invocation-resolver.js.map +1 -0
  31. package/dist/runner/plugin-context.d.ts +18 -0
  32. package/dist/runner/plugin-context.d.ts.map +1 -0
  33. package/dist/runner/plugin-context.js +26 -0
  34. package/dist/runner/plugin-context.js.map +1 -0
  35. package/dist/runner/run-context.d.ts +38 -0
  36. package/dist/runner/run-context.d.ts.map +1 -0
  37. package/dist/runner/run-context.js +159 -0
  38. package/dist/runner/run-context.js.map +1 -0
  39. package/dist/runner/runner-session.d.ts +34 -0
  40. package/dist/runner/runner-session.d.ts.map +1 -0
  41. package/dist/runner/runner-session.js +61 -0
  42. package/dist/runner/runner-session.js.map +1 -0
  43. package/dist/runner/runner.d.ts +1 -2
  44. package/dist/runner/runner.d.ts.map +1 -1
  45. package/dist/runner/runner.js +115 -889
  46. package/dist/runner/runner.js.map +1 -1
  47. package/dist/runner/runtime.d.ts +7 -0
  48. package/dist/runner/runtime.d.ts.map +1 -0
  49. package/dist/runner/runtime.js +21 -0
  50. package/dist/runner/runtime.js.map +1 -0
  51. package/dist/runner/session-lifecycle.d.ts +31 -0
  52. package/dist/runner/session-lifecycle.d.ts.map +1 -0
  53. package/dist/runner/session-lifecycle.js +46 -0
  54. package/dist/runner/session-lifecycle.js.map +1 -0
  55. package/dist/runner/title.d.ts +3 -0
  56. package/dist/runner/title.d.ts.map +1 -0
  57. package/dist/runner/title.js +6 -0
  58. package/dist/runner/title.js.map +1 -0
  59. package/dist/runner/turn-executor.d.ts +51 -0
  60. package/dist/runner/turn-executor.d.ts.map +1 -0
  61. package/dist/runner/turn-executor.js +255 -0
  62. package/dist/runner/turn-executor.js.map +1 -0
  63. package/dist/tools/cron.d.ts +15 -22
  64. package/dist/tools/cron.d.ts.map +1 -1
  65. package/dist/tools/cron.js +20 -40
  66. package/dist/tools/cron.js.map +1 -1
  67. package/dist/types.d.ts +1 -1
  68. package/dist/types.d.ts.map +1 -1
  69. package/package.json +1 -1
  70. package/dist/config-DJX-VM7S.js +0 -198
  71. package/dist/config-DJX-VM7S.js.map +0 -1
  72. package/dist/index-JD6Ye-N5.d.ts +0 -149
  73. package/dist/index-JD6Ye-N5.d.ts.map +0 -1
  74. package/dist/manager-qXa-NP0p.js +0 -1651
  75. package/dist/manager-qXa-NP0p.js.map +0 -1
  76. package/dist/message.d.ts +0 -11
  77. package/dist/message.d.ts.map +0 -1
  78. package/dist/message.js +0 -46
  79. package/dist/message.js.map +0 -1
  80. package/dist/security/command-dangerous-rules.d.ts +0 -4
  81. package/dist/security/command-dangerous-rules.d.ts.map +0 -1
  82. package/dist/security/command-dangerous-rules.js +0 -26
  83. package/dist/security/command-dangerous-rules.js.map +0 -1
  84. package/dist/security/command-parser.d.ts +0 -3
  85. package/dist/security/command-parser.d.ts.map +0 -1
  86. package/dist/security/command-parser.js +0 -191
  87. package/dist/security/command-parser.js.map +0 -1
  88. package/dist/security/command-path-guard.d.ts +0 -10
  89. package/dist/security/command-path-guard.d.ts.map +0 -1
  90. package/dist/security/command-path-guard.js +0 -126
  91. package/dist/security/command-path-guard.js.map +0 -1
  92. package/dist/security/command-policy-config.d.ts +0 -5
  93. package/dist/security/command-policy-config.d.ts.map +0 -1
  94. package/dist/security/command-policy-config.js +0 -212
  95. package/dist/security/command-policy-config.js.map +0 -1
  96. package/dist/security/command-policy-engine.d.ts +0 -8
  97. package/dist/security/command-policy-engine.d.ts.map +0 -1
  98. package/dist/security/command-policy-engine.js +0 -122
  99. package/dist/security/command-policy-engine.js.map +0 -1
  100. package/dist/security/command-policy-types.d.ts +0 -67
  101. package/dist/security/command-policy-types.d.ts.map +0 -1
  102. package/dist/security/command-policy-types.js +0 -2
  103. package/dist/security/command-policy-types.js.map +0 -1
  104. package/dist/security/command-safe-bins.d.ts +0 -4
  105. package/dist/security/command-safe-bins.d.ts.map +0 -1
  106. package/dist/security/command-safe-bins.js +0 -84
  107. package/dist/security/command-safe-bins.js.map +0 -1
  108. package/dist/security/command-trusted-executables.d.ts +0 -6
  109. package/dist/security/command-trusted-executables.d.ts.map +0 -1
  110. package/dist/security/command-trusted-executables.js +0 -57
  111. package/dist/security/command-trusted-executables.js.map +0 -1
@@ -1,1651 +0,0 @@
1
- import { createRequire } from "node:module";
2
- import fs from "node:fs/promises";
3
- import path from "node:path";
4
- import crypto, { randomUUID } from "node:crypto";
5
- import fsSync from "node:fs";
6
-
7
- //#region \0rolldown/runtime.js
8
- var __defProp = Object.defineProperty;
9
- var __exportAll = (all, no_symbols) => {
10
- let target = {};
11
- for (var name in all) {
12
- __defProp(target, name, {
13
- get: all[name],
14
- enumerable: true
15
- });
16
- }
17
- if (!no_symbols) {
18
- __defProp(target, Symbol.toStringTag, { value: "Module" });
19
- }
20
- return target;
21
- };
22
-
23
- //#endregion
24
- //#region src/session/session.ts
25
- /** Resolves the sessions directory path within a data directory */
26
- function sessionsDir(dataDir) {
27
- return path.join(dataDir, ".pingclaw", "sessions");
28
- }
29
- /** Resolves the directory for a specific session */
30
- function sessionDir(dataDir, sessionId) {
31
- return path.join(sessionsDir(dataDir), sessionId);
32
- }
33
- /** Resolves the transcript file path for a session */
34
- function transcriptPath(dataDir, sessionId) {
35
- return path.join(sessionDir(dataDir, sessionId), "transcript.jsonl");
36
- }
37
- /** Resolves the metadata file path for a session */
38
- function metadataPath(dataDir, sessionId) {
39
- return path.join(sessionDir(dataDir, sessionId), "session.json");
40
- }
41
- /** Creates a new session with a generated ID */
42
- async function createSession(dataDir, channel) {
43
- const sessionId = randomUUID();
44
- const dir = sessionDir(dataDir, sessionId);
45
- await fs.mkdir(dir, { recursive: true });
46
- const channelPath = path.join(dir, ".channel");
47
- await fs.writeFile(channelPath, channel, "utf-8");
48
- return sessionId;
49
- }
50
- /** Ensures a session directory exists (for resuming an existing session) */
51
- async function ensureSession(dataDir, sessionId) {
52
- const dir = sessionDir(dataDir, sessionId);
53
- await fs.mkdir(dir, { recursive: true });
54
- }
55
- /** Loads all transcript entries from a session */
56
- async function loadTranscript(dataDir, sessionId) {
57
- const filePath = transcriptPath(dataDir, sessionId);
58
- let content;
59
- try {
60
- content = await fs.readFile(filePath, "utf-8");
61
- } catch (err) {
62
- if (err.code === "ENOENT") return [];
63
- throw err;
64
- }
65
- const entries = [];
66
- for (const line of content.split("\n")) {
67
- const trimmed = line.trim();
68
- if (!trimmed) continue;
69
- try {
70
- entries.push(JSON.parse(trimmed));
71
- } catch {}
72
- }
73
- return entries;
74
- }
75
- /** Appends a single entry to the transcript file */
76
- async function appendTranscriptEntry(dataDir, sessionId, entry) {
77
- await ensureSession(dataDir, sessionId);
78
- const filePath = transcriptPath(dataDir, sessionId);
79
- const line = JSON.stringify(entry) + "\n";
80
- await fs.appendFile(filePath, line, "utf-8");
81
- try {
82
- const { MemoryIndexManager } = await Promise.resolve().then(() => manager_exports);
83
- MemoryIndexManager.get(dataDir).noteSessionUpdate(filePath);
84
- } catch {}
85
- }
86
- /** Lists all session IDs within a data directory */
87
- async function listSessions(dataDir) {
88
- const dir = sessionsDir(dataDir);
89
- try {
90
- return (await fs.readdir(dir, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
91
- } catch (err) {
92
- if (err.code === "ENOENT") return [];
93
- throw err;
94
- }
95
- }
96
- /** Saves session metadata to disk */
97
- async function saveSessionMetadata(dataDir, metadata) {
98
- await ensureSession(dataDir, metadata.id);
99
- const filePath = metadataPath(dataDir, metadata.id);
100
- await fs.writeFile(filePath, JSON.stringify(metadata, null, 2), "utf-8");
101
- }
102
- /** Loads session metadata from disk; returns null if not found */
103
- async function loadSessionMetadata(dataDir, sessionId) {
104
- const filePath = metadataPath(dataDir, sessionId);
105
- try {
106
- const content = await fs.readFile(filePath, "utf-8");
107
- const meta = JSON.parse(content);
108
- if (!meta.channel) {
109
- const channelPath = path.join(sessionDir(dataDir, sessionId), ".channel");
110
- try {
111
- meta.channel = (await fs.readFile(channelPath, "utf-8")).trim();
112
- } catch {
113
- meta.channel = "WEB";
114
- }
115
- }
116
- return meta;
117
- } catch (err) {
118
- if (err.code === "ENOENT") return null;
119
- throw err;
120
- }
121
- }
122
- /** Lists all sessions with their metadata summaries, optionally filtered by channel */
123
- async function listSessionSummaries(dataDir, channel) {
124
- const ids = await listSessions(dataDir);
125
- const summaries = await Promise.all(ids.map(async (id) => {
126
- const meta = await loadSessionMetadata(dataDir, id);
127
- return {
128
- id,
129
- title: meta?.title ?? id,
130
- channel: meta?.channel ?? "WEB",
131
- createdAt: meta?.createdAt ?? "",
132
- updatedAt: meta?.updatedAt ?? ""
133
- };
134
- }));
135
- return channel ? summaries.filter((s) => s.channel === channel) : summaries;
136
- }
137
-
138
- //#endregion
139
- //#region src/memory/embeddings.ts
140
- const DEFAULT_DIMENSIONS = 384;
141
- function tokenize$1(text) {
142
- return (text.toLowerCase().match(/[\p{L}\p{N}_]+/gu) ?? []).filter(Boolean);
143
- }
144
- function hashToken(token) {
145
- return crypto.createHash("sha256").update(token).digest().readUInt32BE(0);
146
- }
147
- function buildEmbedding(text, dims = DEFAULT_DIMENSIONS) {
148
- const vec = new Array(dims).fill(0);
149
- const tokens = tokenize$1(text);
150
- if (tokens.length === 0) return vec;
151
- for (const token of tokens) {
152
- const idx = hashToken(token) % dims;
153
- vec[idx] += 1;
154
- }
155
- const norm = Math.sqrt(vec.reduce((sum, v) => sum + v * v, 0));
156
- if (norm > 0) for (let i = 0; i < vec.length; i += 1) vec[i] = vec[i] / norm;
157
- return vec;
158
- }
159
- function createMockEmbeddingProvider(params) {
160
- const model = params?.model?.trim() || "mock-embedding-v1";
161
- const dimensions = params?.dimensions ?? DEFAULT_DIMENSIONS;
162
- return {
163
- id: "external-mock",
164
- model,
165
- embedQuery: async (text) => buildEmbedding(text, dimensions),
166
- embedBatch: async (texts) => texts.map((text) => buildEmbedding(text, dimensions))
167
- };
168
- }
169
-
170
- //#endregion
171
- //#region src/memory/fs-utils.ts
172
- function isFileMissingError(err) {
173
- return Boolean(err && typeof err === "object" && "code" in err && err.code === "ENOENT");
174
- }
175
- async function statRegularFile(absPath) {
176
- let stat;
177
- try {
178
- stat = await fs.lstat(absPath);
179
- } catch (err) {
180
- if (isFileMissingError(err)) return { missing: true };
181
- throw err;
182
- }
183
- if (stat.isSymbolicLink() || !stat.isFile()) throw new Error("path required");
184
- return {
185
- missing: false,
186
- stat
187
- };
188
- }
189
-
190
- //#endregion
191
- //#region src/memory/internal.ts
192
- function ensureDir(dir) {
193
- try {
194
- fsSync.mkdirSync(dir, { recursive: true });
195
- } catch {}
196
- return dir;
197
- }
198
- function normalizeRelPath(value) {
199
- return value.trim().replace(/^[./]+/, "").replace(/\\/g, "/");
200
- }
201
- function isMemoryPath(relPath) {
202
- const normalized = normalizeRelPath(relPath);
203
- if (!normalized) return false;
204
- if (normalized === "MEMORY.md" || normalized === "memory.md") return true;
205
- return normalized.startsWith("memory/");
206
- }
207
- async function walkDir(dir, files) {
208
- const entries = await fs.readdir(dir, { withFileTypes: true });
209
- for (const entry of entries) {
210
- const full = path.join(dir, entry.name);
211
- if (entry.isSymbolicLink()) continue;
212
- if (entry.isDirectory()) {
213
- await walkDir(full, files);
214
- continue;
215
- }
216
- if (!entry.isFile()) continue;
217
- if (!entry.name.endsWith(".md")) continue;
218
- files.push(full);
219
- }
220
- }
221
- async function listMemoryFiles(memoryRoot) {
222
- const result = [];
223
- const memoryFile = path.join(memoryRoot, "MEMORY.md");
224
- const altMemoryFile = path.join(memoryRoot, "memory.md");
225
- const memoryDir = path.join(memoryRoot, "memory");
226
- const addMarkdownFile = async (absPath) => {
227
- try {
228
- const stat = await fs.lstat(absPath);
229
- if (stat.isSymbolicLink() || !stat.isFile()) return;
230
- if (!absPath.endsWith(".md")) return;
231
- result.push(absPath);
232
- } catch {}
233
- };
234
- await addMarkdownFile(memoryFile);
235
- await addMarkdownFile(altMemoryFile);
236
- try {
237
- const dirStat = await fs.lstat(memoryDir);
238
- if (!dirStat.isSymbolicLink() && dirStat.isDirectory()) await walkDir(memoryDir, result);
239
- } catch {}
240
- if (result.length <= 1) return result;
241
- const seen = /* @__PURE__ */ new Set();
242
- const deduped = [];
243
- for (const entry of result) {
244
- let key = entry;
245
- try {
246
- key = await fs.realpath(entry);
247
- } catch {}
248
- if (seen.has(key)) continue;
249
- seen.add(key);
250
- deduped.push(entry);
251
- }
252
- return deduped;
253
- }
254
- function hashText(value) {
255
- return crypto.createHash("sha256").update(value).digest("hex");
256
- }
257
- async function buildFileEntry(absPath, memoryRoot) {
258
- let stat;
259
- try {
260
- stat = await fs.stat(absPath);
261
- } catch (err) {
262
- if (isFileMissingError(err)) return null;
263
- throw err;
264
- }
265
- let content;
266
- try {
267
- content = await fs.readFile(absPath, "utf-8");
268
- } catch (err) {
269
- if (isFileMissingError(err)) return null;
270
- throw err;
271
- }
272
- const hash = hashText(content);
273
- return {
274
- path: path.relative(memoryRoot, absPath).replace(/\\/g, "/"),
275
- absPath,
276
- mtimeMs: stat.mtimeMs,
277
- size: stat.size,
278
- hash
279
- };
280
- }
281
- function chunkMarkdown(content, chunking) {
282
- const lines = content.split("\n");
283
- if (lines.length === 0) return [];
284
- const maxChars = Math.max(32, chunking.tokens * 4);
285
- const overlapChars = Math.max(0, chunking.overlap * 4);
286
- const chunks = [];
287
- let current = [];
288
- let currentChars = 0;
289
- const flush = () => {
290
- if (current.length === 0) return;
291
- const firstEntry = current[0];
292
- const lastEntry = current[current.length - 1];
293
- if (!firstEntry || !lastEntry) return;
294
- const text = current.map((entry) => entry.line).join("\n");
295
- const startLine = firstEntry.lineNo;
296
- const endLine = lastEntry.lineNo;
297
- chunks.push({
298
- startLine,
299
- endLine,
300
- text,
301
- hash: hashText(text)
302
- });
303
- };
304
- const carryOverlap = () => {
305
- if (overlapChars <= 0 || current.length === 0) {
306
- current = [];
307
- currentChars = 0;
308
- return;
309
- }
310
- let acc = 0;
311
- const kept = [];
312
- for (let i = current.length - 1; i >= 0; i -= 1) {
313
- const entry = current[i];
314
- if (!entry) continue;
315
- acc += entry.line.length + 1;
316
- kept.unshift(entry);
317
- if (acc >= overlapChars) break;
318
- }
319
- current = kept;
320
- currentChars = kept.reduce((sum, entry) => sum + entry.line.length + 1, 0);
321
- };
322
- for (let i = 0; i < lines.length; i += 1) {
323
- const line = lines[i] ?? "";
324
- const lineNo = i + 1;
325
- const segments = [];
326
- if (line.length === 0) segments.push("");
327
- else for (let start = 0; start < line.length; start += maxChars) segments.push(line.slice(start, start + maxChars));
328
- for (const segment of segments) {
329
- const lineSize = segment.length + 1;
330
- if (currentChars + lineSize > maxChars && current.length > 0) {
331
- flush();
332
- carryOverlap();
333
- }
334
- current.push({
335
- line: segment,
336
- lineNo
337
- });
338
- currentChars += lineSize;
339
- }
340
- }
341
- flush();
342
- return chunks;
343
- }
344
- function remapChunkLines(chunks, lineMap) {
345
- if (!lineMap || lineMap.length === 0) return;
346
- for (const chunk of chunks) {
347
- chunk.startLine = lineMap[chunk.startLine - 1] ?? chunk.startLine;
348
- chunk.endLine = lineMap[chunk.endLine - 1] ?? chunk.endLine;
349
- }
350
- }
351
- function parseEmbedding(raw) {
352
- try {
353
- const parsed = JSON.parse(raw);
354
- return Array.isArray(parsed) ? parsed : [];
355
- } catch {
356
- return [];
357
- }
358
- }
359
- function cosineSimilarity(a, b) {
360
- if (a.length === 0 || b.length === 0) return 0;
361
- const len = Math.min(a.length, b.length);
362
- let dot = 0;
363
- let normA = 0;
364
- let normB = 0;
365
- for (let i = 0; i < len; i += 1) {
366
- const av = a[i] ?? 0;
367
- const bv = b[i] ?? 0;
368
- dot += av * bv;
369
- normA += av * av;
370
- normB += bv * bv;
371
- }
372
- if (normA === 0 || normB === 0) return 0;
373
- return dot / (Math.sqrt(normA) * Math.sqrt(normB));
374
- }
375
-
376
- //#endregion
377
- //#region src/memory/mmr.ts
378
- const DEFAULT_MMR_CONFIG = {
379
- enabled: false,
380
- lambda: .7
381
- };
382
- function tokenize(text) {
383
- const tokens = text.toLowerCase().match(/[a-z0-9_]+/g) ?? [];
384
- return new Set(tokens);
385
- }
386
- function jaccardSimilarity(setA, setB) {
387
- if (setA.size === 0 && setB.size === 0) return 1;
388
- if (setA.size === 0 || setB.size === 0) return 0;
389
- let intersectionSize = 0;
390
- const smaller = setA.size <= setB.size ? setA : setB;
391
- const larger = setA.size <= setB.size ? setB : setA;
392
- for (const token of smaller) if (larger.has(token)) intersectionSize++;
393
- const unionSize = setA.size + setB.size - intersectionSize;
394
- return unionSize === 0 ? 0 : intersectionSize / unionSize;
395
- }
396
- function maxSimilarityToSelected(item, selectedItems, tokenCache) {
397
- if (selectedItems.length === 0) return 0;
398
- let maxSim = 0;
399
- const itemTokens = tokenCache.get(item.id) ?? tokenize(item.content);
400
- for (const selected of selectedItems) {
401
- const sim = jaccardSimilarity(itemTokens, tokenCache.get(selected.id) ?? tokenize(selected.content));
402
- if (sim > maxSim) maxSim = sim;
403
- }
404
- return maxSim;
405
- }
406
- function computeMMRScore(relevance, maxSimilarity, lambda) {
407
- return lambda * relevance - (1 - lambda) * maxSimilarity;
408
- }
409
- function mmrRerank(items, config = {}) {
410
- const { enabled = DEFAULT_MMR_CONFIG.enabled, lambda = DEFAULT_MMR_CONFIG.lambda } = config;
411
- if (!enabled || items.length <= 1) return [...items];
412
- const clampedLambda = Math.max(0, Math.min(1, lambda));
413
- if (clampedLambda === 1) return items.slice().sort((a, b) => b.score - a.score);
414
- const tokenCache = /* @__PURE__ */ new Map();
415
- for (const item of items) tokenCache.set(item.id, tokenize(item.content));
416
- const maxScore = Math.max(...items.map((i) => i.score));
417
- const minScore = Math.min(...items.map((i) => i.score));
418
- const scoreRange = maxScore - minScore;
419
- const normalizeScore = (score) => {
420
- if (scoreRange === 0) return 1;
421
- return (score - minScore) / scoreRange;
422
- };
423
- const selected = [];
424
- const remaining = new Set(items);
425
- while (remaining.size > 0) {
426
- let bestItem = null;
427
- let bestMMRScore = -Infinity;
428
- for (const candidate of remaining) {
429
- const mmrScore = computeMMRScore(normalizeScore(candidate.score), maxSimilarityToSelected(candidate, selected, tokenCache), clampedLambda);
430
- if (mmrScore > bestMMRScore || mmrScore === bestMMRScore && candidate.score > (bestItem?.score ?? -Infinity)) {
431
- bestMMRScore = mmrScore;
432
- bestItem = candidate;
433
- }
434
- }
435
- if (bestItem) {
436
- selected.push(bestItem);
437
- remaining.delete(bestItem);
438
- } else break;
439
- }
440
- return selected;
441
- }
442
- function applyMMRToHybridResults(results, config = {}) {
443
- if (results.length === 0) return results;
444
- const itemById = /* @__PURE__ */ new Map();
445
- return mmrRerank(results.map((r, index) => {
446
- const id = `${r.path}:${r.startLine}:${index}`;
447
- itemById.set(id, r);
448
- return {
449
- id,
450
- score: r.score,
451
- content: r.snippet
452
- };
453
- }), config).map((item) => itemById.get(item.id));
454
- }
455
-
456
- //#endregion
457
- //#region src/memory/temporal-decay.ts
458
- const DEFAULT_TEMPORAL_DECAY_CONFIG = {
459
- enabled: false,
460
- halfLifeDays: 30
461
- };
462
- const DAY_MS = 1440 * 60 * 1e3;
463
- const DATED_MEMORY_PATH_RE = /(?:^|\/)(?:memory)\/(\d{4})-(\d{2})-(\d{2})\.md$/;
464
- function toDecayLambda(halfLifeDays) {
465
- if (!Number.isFinite(halfLifeDays) || halfLifeDays <= 0) return 0;
466
- return Math.LN2 / halfLifeDays;
467
- }
468
- function calculateTemporalDecayMultiplier(params) {
469
- const lambda = toDecayLambda(params.halfLifeDays);
470
- const clampedAge = Math.max(0, params.ageInDays);
471
- if (lambda <= 0 || !Number.isFinite(clampedAge)) return 1;
472
- return Math.exp(-lambda * clampedAge);
473
- }
474
- function applyTemporalDecayToScore(params) {
475
- return params.score * calculateTemporalDecayMultiplier(params);
476
- }
477
- function parseMemoryDateFromPath(filePath) {
478
- const normalized = filePath.replaceAll("\\", "/").replace(/^\.\//, "");
479
- const match = DATED_MEMORY_PATH_RE.exec(normalized);
480
- if (!match) return null;
481
- const year = Number(match[1]);
482
- const month = Number(match[2]);
483
- const day = Number(match[3]);
484
- if (!Number.isInteger(year) || !Number.isInteger(month) || !Number.isInteger(day)) return null;
485
- const timestamp = Date.UTC(year, month - 1, day);
486
- const parsed = new Date(timestamp);
487
- if (parsed.getUTCFullYear() !== year || parsed.getUTCMonth() !== month - 1 || parsed.getUTCDate() !== day) return null;
488
- return parsed;
489
- }
490
- function isEvergreenMemoryPath(filePath) {
491
- const normalized = filePath.replaceAll("\\", "/").replace(/^\.\//, "");
492
- if (normalized === "MEMORY.md" || normalized === "memory.md") return true;
493
- if (!normalized.startsWith("memory/")) return false;
494
- return !DATED_MEMORY_PATH_RE.test(normalized);
495
- }
496
- async function extractTimestamp(params) {
497
- const fromPath = parseMemoryDateFromPath(params.filePath);
498
- if (fromPath) return fromPath;
499
- if (params.source === "memory" && isEvergreenMemoryPath(params.filePath)) return null;
500
- if (!params.memoryRoot) return null;
501
- const absolutePath = path.isAbsolute(params.filePath) ? params.filePath : path.resolve(params.memoryRoot, params.filePath);
502
- try {
503
- const stat = await fs.stat(absolutePath);
504
- if (!Number.isFinite(stat.mtimeMs)) return null;
505
- return new Date(stat.mtimeMs);
506
- } catch {
507
- return null;
508
- }
509
- }
510
- function ageInDaysFromTimestamp(timestamp, nowMs) {
511
- return Math.max(0, nowMs - timestamp.getTime()) / DAY_MS;
512
- }
513
- async function applyTemporalDecayToHybridResults(params) {
514
- const config = {
515
- ...DEFAULT_TEMPORAL_DECAY_CONFIG,
516
- ...params.temporalDecay
517
- };
518
- if (!config.enabled) return [...params.results];
519
- const nowMs = params.nowMs ?? Date.now();
520
- const timestampPromiseCache = /* @__PURE__ */ new Map();
521
- return Promise.all(params.results.map(async (entry) => {
522
- const cacheKey = `${entry.source}:${entry.path}`;
523
- let timestampPromise = timestampPromiseCache.get(cacheKey);
524
- if (!timestampPromise) {
525
- timestampPromise = extractTimestamp({
526
- filePath: entry.path,
527
- source: entry.source,
528
- memoryRoot: params.memoryRoot
529
- });
530
- timestampPromiseCache.set(cacheKey, timestampPromise);
531
- }
532
- const timestamp = await timestampPromise;
533
- if (!timestamp) return entry;
534
- const decayedScore = applyTemporalDecayToScore({
535
- score: entry.score,
536
- ageInDays: ageInDaysFromTimestamp(timestamp, nowMs),
537
- halfLifeDays: config.halfLifeDays
538
- });
539
- return {
540
- ...entry,
541
- score: decayedScore
542
- };
543
- }));
544
- }
545
-
546
- //#endregion
547
- //#region src/memory/hybrid.ts
548
- function buildFtsQuery(raw) {
549
- const tokens = raw.match(/[\p{L}\p{N}_]+/gu)?.map((t) => t.trim()).filter(Boolean) ?? [];
550
- if (tokens.length === 0) return null;
551
- return tokens.map((t) => `"${t.replaceAll("\"", "")}"`).join(" AND ");
552
- }
553
- function bm25RankToScore(rank) {
554
- return 1 / (1 + (Number.isFinite(rank) ? Math.max(0, rank) : 999));
555
- }
556
- async function mergeHybridResults(params) {
557
- const byId = /* @__PURE__ */ new Map();
558
- for (const r of params.vector) byId.set(r.id, {
559
- id: r.id,
560
- path: r.path,
561
- startLine: r.startLine,
562
- endLine: r.endLine,
563
- source: r.source,
564
- snippet: r.snippet,
565
- vectorScore: r.vectorScore,
566
- textScore: 0
567
- });
568
- for (const r of params.keyword) {
569
- const existing = byId.get(r.id);
570
- if (existing) {
571
- existing.textScore = r.textScore;
572
- if (r.snippet && r.snippet.length > 0) existing.snippet = r.snippet;
573
- } else byId.set(r.id, {
574
- id: r.id,
575
- path: r.path,
576
- startLine: r.startLine,
577
- endLine: r.endLine,
578
- source: r.source,
579
- snippet: r.snippet,
580
- vectorScore: 0,
581
- textScore: r.textScore
582
- });
583
- }
584
- const sorted = (await applyTemporalDecayToHybridResults({
585
- results: Array.from(byId.values()).map((entry) => {
586
- const score = params.vectorWeight * entry.vectorScore + params.textWeight * entry.textScore;
587
- return {
588
- path: entry.path,
589
- startLine: entry.startLine,
590
- endLine: entry.endLine,
591
- score,
592
- snippet: entry.snippet,
593
- source: entry.source
594
- };
595
- }),
596
- temporalDecay: {
597
- ...DEFAULT_TEMPORAL_DECAY_CONFIG,
598
- ...params.temporalDecay
599
- },
600
- memoryRoot: params.memoryRoot,
601
- nowMs: params.nowMs
602
- })).slice().sort((a, b) => b.score - a.score);
603
- const mmrConfig = {
604
- ...DEFAULT_MMR_CONFIG,
605
- ...params.mmr
606
- };
607
- if (mmrConfig.enabled) return applyMMRToHybridResults(sorted, mmrConfig);
608
- return sorted;
609
- }
610
-
611
- //#endregion
612
- //#region src/memory/memory-schema.ts
613
- function ensureMemoryIndexSchema(params) {
614
- params.db.exec(`
615
- CREATE TABLE IF NOT EXISTS meta (
616
- key TEXT PRIMARY KEY,
617
- value TEXT NOT NULL
618
- );
619
- `);
620
- params.db.exec(`
621
- CREATE TABLE IF NOT EXISTS files (
622
- path TEXT PRIMARY KEY,
623
- source TEXT NOT NULL DEFAULT 'memory',
624
- hash TEXT NOT NULL,
625
- mtime INTEGER NOT NULL,
626
- size INTEGER NOT NULL
627
- );
628
- `);
629
- params.db.exec(`
630
- CREATE TABLE IF NOT EXISTS chunks (
631
- id TEXT PRIMARY KEY,
632
- path TEXT NOT NULL,
633
- source TEXT NOT NULL DEFAULT 'memory',
634
- start_line INTEGER NOT NULL,
635
- end_line INTEGER NOT NULL,
636
- hash TEXT NOT NULL,
637
- model TEXT NOT NULL,
638
- text TEXT NOT NULL,
639
- embedding TEXT NOT NULL,
640
- updated_at INTEGER NOT NULL
641
- );
642
- `);
643
- params.db.exec(`
644
- CREATE TABLE IF NOT EXISTS ${params.embeddingCacheTable} (
645
- provider TEXT NOT NULL,
646
- model TEXT NOT NULL,
647
- provider_key TEXT NOT NULL,
648
- hash TEXT NOT NULL,
649
- embedding TEXT NOT NULL,
650
- dims INTEGER,
651
- updated_at INTEGER NOT NULL,
652
- PRIMARY KEY (provider, model, provider_key, hash)
653
- );
654
- `);
655
- params.db.exec(`CREATE INDEX IF NOT EXISTS idx_embedding_cache_updated_at ON ${params.embeddingCacheTable}(updated_at);`);
656
- let ftsAvailable = false;
657
- let ftsError;
658
- if (params.ftsEnabled) try {
659
- params.db.exec(`CREATE VIRTUAL TABLE IF NOT EXISTS ${params.ftsTable} USING fts5(\n text,\n id UNINDEXED,\n path UNINDEXED,\n source UNINDEXED,\n model UNINDEXED,\n start_line UNINDEXED,\n end_line UNINDEXED\n);`);
660
- ftsAvailable = true;
661
- } catch (err) {
662
- const message = err instanceof Error ? err.message : String(err);
663
- ftsAvailable = false;
664
- ftsError = message;
665
- }
666
- ensureColumn(params.db, "files", "source", "TEXT NOT NULL DEFAULT 'memory'");
667
- ensureColumn(params.db, "chunks", "source", "TEXT NOT NULL DEFAULT 'memory'");
668
- params.db.exec(`CREATE INDEX IF NOT EXISTS idx_chunks_path ON chunks(path);`);
669
- params.db.exec(`CREATE INDEX IF NOT EXISTS idx_chunks_source ON chunks(source);`);
670
- return {
671
- ftsAvailable,
672
- ...ftsError ? { ftsError } : {}
673
- };
674
- }
675
- function ensureColumn(db, table, column, definition) {
676
- if (db.prepare(`PRAGMA table_info(${table})`).all().some((row) => row.name === column)) return;
677
- db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
678
- }
679
-
680
- //#endregion
681
- //#region src/memory/manager-search.ts
682
- function truncateUtf16Safe(input, maxChars) {
683
- if (input.length <= maxChars) return input;
684
- return input.slice(0, maxChars);
685
- }
686
- const vectorToBlob = (embedding) => Buffer.from(new Float32Array(embedding).buffer);
687
- async function searchVector(params) {
688
- if (params.queryVec.length === 0 || params.limit <= 0) return [];
689
- if (await params.ensureVectorReady(params.queryVec.length)) return params.db.prepare(`SELECT c.id, c.path, c.start_line, c.end_line, c.text,
690
- c.source,
691
- vec_distance_cosine(v.embedding, ?) AS dist
692
- FROM ${params.vectorTable} v\n JOIN chunks c ON c.id = v.id\n WHERE c.model = ?${params.sourceFilterVec.sql}\n ORDER BY dist ASC\n LIMIT ?`).all(vectorToBlob(params.queryVec), params.providerModel, ...params.sourceFilterVec.params, params.limit).map((row) => ({
693
- id: row.id,
694
- path: row.path,
695
- startLine: row.start_line,
696
- endLine: row.end_line,
697
- score: 1 - row.dist,
698
- snippet: truncateUtf16Safe(row.text, params.snippetMaxChars),
699
- source: row.source
700
- }));
701
- return listChunks({
702
- db: params.db,
703
- providerModel: params.providerModel,
704
- sourceFilter: params.sourceFilterChunks
705
- }).map((chunk) => ({
706
- chunk,
707
- score: cosineSimilarity(params.queryVec, chunk.embedding)
708
- })).filter((entry) => Number.isFinite(entry.score)).slice().sort((a, b) => b.score - a.score).slice(0, params.limit).map((entry) => ({
709
- id: entry.chunk.id,
710
- path: entry.chunk.path,
711
- startLine: entry.chunk.startLine,
712
- endLine: entry.chunk.endLine,
713
- score: entry.score,
714
- snippet: truncateUtf16Safe(entry.chunk.text, params.snippetMaxChars),
715
- source: entry.chunk.source
716
- }));
717
- }
718
- function listChunks(params) {
719
- return params.db.prepare(`SELECT id, path, start_line, end_line, text, embedding, source
720
- FROM chunks
721
- WHERE model = ?${params.sourceFilter.sql}`).all(params.providerModel, ...params.sourceFilter.params).map((row) => ({
722
- id: row.id,
723
- path: row.path,
724
- startLine: row.start_line,
725
- endLine: row.end_line,
726
- text: row.text,
727
- embedding: parseEmbedding(row.embedding),
728
- source: row.source
729
- }));
730
- }
731
- async function searchKeyword(params) {
732
- if (params.limit <= 0) return [];
733
- const ftsQuery = params.buildFtsQuery(params.query);
734
- if (!ftsQuery) return [];
735
- const modelClause = params.providerModel ? " AND model = ?" : "";
736
- const modelParams = params.providerModel ? [params.providerModel] : [];
737
- return params.db.prepare(`SELECT id, path, source, start_line, end_line, text,\n bm25(${params.ftsTable}) AS rank\n FROM ${params.ftsTable}\n WHERE ${params.ftsTable} MATCH ?${modelClause}${params.sourceFilter.sql}\n ORDER BY rank ASC\n LIMIT ?`).all(ftsQuery, ...modelParams, ...params.sourceFilter.params, params.limit).map((row) => {
738
- const textScore = params.bm25RankToScore(row.rank);
739
- return {
740
- id: row.id,
741
- path: row.path,
742
- startLine: row.start_line,
743
- endLine: row.end_line,
744
- score: textScore,
745
- textScore,
746
- snippet: truncateUtf16Safe(row.text, params.snippetMaxChars),
747
- source: row.source
748
- };
749
- });
750
- }
751
-
752
- //#endregion
753
- //#region src/memory/sqlite.ts
754
- const require = createRequire(import.meta.url);
755
- function requireNodeSqlite() {
756
- try {
757
- return require("node:sqlite");
758
- } catch (err) {
759
- const message = err instanceof Error ? err.message : String(err);
760
- throw new Error(`SQLite support is unavailable in this Node runtime (missing node:sqlite). ${message}`, { cause: err });
761
- }
762
- }
763
-
764
- //#endregion
765
- //#region src/memory/config.ts
766
- const DEFAULT_MEMORY_SEARCH_CONFIG = {
767
- chunkTokens: 400,
768
- chunkOverlap: 80,
769
- maxResults: 6,
770
- minScore: .35,
771
- experimental: { sessionMemory: false },
772
- sources: ["memory"],
773
- store: { vector: { enabled: true } },
774
- hybrid: {
775
- enabled: true,
776
- vectorWeight: .7,
777
- textWeight: .3,
778
- candidateMultiplier: 4,
779
- mmr: {
780
- enabled: false,
781
- lambda: .7
782
- },
783
- temporalDecay: {
784
- enabled: false,
785
- halfLifeDays: 30
786
- }
787
- },
788
- cache: {
789
- enabled: true,
790
- maxEntries: 5e4
791
- },
792
- sync: {
793
- onSessionStart: true,
794
- onSearch: true,
795
- watch: true,
796
- watchDebounceMs: 1500,
797
- sessions: {
798
- deltaBytes: 1e5,
799
- deltaMessages: 50
800
- }
801
- },
802
- citations: "auto"
803
- };
804
-
805
- //#endregion
806
- //#region src/memory/sqlite-vec.ts
807
- async function loadSqliteVecExtension(params) {
808
- try {
809
- const sqliteVec = await import("sqlite-vec");
810
- const resolvedPath = params.extensionPath?.trim() ? params.extensionPath.trim() : void 0;
811
- const extensionPath = resolvedPath ?? sqliteVec.getLoadablePath();
812
- params.db.enableLoadExtension(true);
813
- if (resolvedPath) params.db.loadExtension(extensionPath);
814
- else sqliteVec.load(params.db);
815
- return {
816
- ok: true,
817
- extensionPath
818
- };
819
- } catch (err) {
820
- return {
821
- ok: false,
822
- error: err instanceof Error ? err.message : String(err)
823
- };
824
- }
825
- }
826
-
827
- //#endregion
828
- //#region src/memory/session-files.ts
829
- function sessionPathForFile(dataDir, absPath) {
830
- const root = sessionsDir(dataDir);
831
- return path.relative(root, absPath).replace(/\\/g, "/");
832
- }
833
- async function listSessionFilesForAgent(dataDir) {
834
- const root = sessionsDir(dataDir);
835
- try {
836
- const entries = await fs.readdir(root, { withFileTypes: true });
837
- const files = [];
838
- for (const entry of entries) {
839
- if (!entry.isDirectory()) continue;
840
- const transcript = path.join(root, entry.name, "transcript.jsonl");
841
- try {
842
- if ((await fs.stat(transcript)).isFile()) files.push(transcript);
843
- } catch {}
844
- }
845
- return files;
846
- } catch (err) {
847
- if (isFileMissingError(err)) return [];
848
- throw err;
849
- }
850
- }
851
- function stringifyContent(raw) {
852
- if (typeof raw === "string") return raw;
853
- if (Array.isArray(raw)) return raw.map((item) => {
854
- if (typeof item === "string") return item;
855
- if (item && typeof item === "object" && "text" in item) return String(item.text ?? "");
856
- return "";
857
- }).filter(Boolean).join(" ");
858
- return raw ? String(raw) : "";
859
- }
860
- async function buildSessionEntry(dataDir, absPath) {
861
- let stat;
862
- try {
863
- stat = await fs.stat(absPath);
864
- } catch (err) {
865
- if (isFileMissingError(err)) return null;
866
- throw err;
867
- }
868
- let content;
869
- try {
870
- content = await fs.readFile(absPath, "utf-8");
871
- } catch (err) {
872
- if (isFileMissingError(err)) return null;
873
- throw err;
874
- }
875
- const lines = [];
876
- const lineMap = [];
877
- const rawLines = content.split("\n");
878
- for (let i = 0; i < rawLines.length; i += 1) {
879
- const trimmed = rawLines[i]?.trim();
880
- if (!trimmed) continue;
881
- try {
882
- const parsed = JSON.parse(trimmed);
883
- const role = parsed.role ?? "unknown";
884
- const text = stringifyContent(parsed.content);
885
- if (!text) continue;
886
- const entryLines = text.split("\n");
887
- for (const entryLine of entryLines) {
888
- lines.push(`[${role}] ${entryLine}`);
889
- lineMap.push(i + 1);
890
- }
891
- } catch {}
892
- }
893
- const flattened = lines.join("\n");
894
- const hash = hashText(flattened);
895
- return {
896
- path: path.join("sessions", sessionPathForFile(dataDir, absPath)).replace(/\\/g, "/"),
897
- absPath,
898
- mtimeMs: stat.mtimeMs,
899
- size: stat.size,
900
- hash,
901
- content: flattened,
902
- lineMap
903
- };
904
- }
905
-
906
- //#endregion
907
- //#region src/memory/manager.ts
908
- var manager_exports = /* @__PURE__ */ __exportAll({ MemoryIndexManager: () => MemoryIndexManager });
909
- const SNIPPET_MAX_CHARS = 700;
910
- const VECTOR_TABLE = "chunks_vec";
911
- const FTS_TABLE = "chunks_fts";
912
- const EMBEDDING_CACHE_TABLE = "embedding_cache";
913
- const META_KEY = "memory_index_meta_v1";
914
- const VECTOR_LOAD_TIMEOUT_MS = 3e4;
915
- const MANAGER_CACHE = /* @__PURE__ */ new Map();
916
- function resolveConfig(base, override) {
917
- const config = {
918
- ...base,
919
- ...override,
920
- hybrid: {
921
- ...base.hybrid,
922
- ...override?.hybrid ?? {}
923
- },
924
- cache: {
925
- ...base.cache,
926
- ...override?.cache ?? {}
927
- },
928
- sync: {
929
- ...base.sync,
930
- ...override?.sync ?? {}
931
- },
932
- experimental: {
933
- ...base.experimental,
934
- ...override?.experimental ?? {}
935
- },
936
- sources: override?.sources ?? base.sources,
937
- store: {
938
- ...base.store,
939
- ...override?.store ?? {},
940
- vector: {
941
- ...base.store.vector,
942
- ...override?.store?.vector ?? {}
943
- }
944
- }
945
- };
946
- if (config.experimental.sessionMemory && !config.sources.includes("sessions")) config.sources = [...config.sources, "sessions"];
947
- return config;
948
- }
949
- var MemoryIndexManager = class MemoryIndexManager {
950
- dataDir;
951
- memoryRoot;
952
- storePath;
953
- config;
954
- provider;
955
- providerKey;
956
- db;
957
- dirty = true;
958
- ftsAvailable = false;
959
- ftsError;
960
- vector = {
961
- enabled: true,
962
- available: null,
963
- extensionPath: void 0,
964
- loadError: void 0,
965
- dims: void 0
966
- };
967
- vectorReady = null;
968
- watchers = [];
969
- watchTimer = null;
970
- sessionWatchTimer = null;
971
- sessionsDirty = false;
972
- sessionsDirtyFiles = /* @__PURE__ */ new Set();
973
- sessionPendingFiles = /* @__PURE__ */ new Set();
974
- sessionDeltas = /* @__PURE__ */ new Map();
975
- static get(dataDir, config) {
976
- const memoryRoot = path.join(dataDir, ".pingclaw", "memory");
977
- const existing = MANAGER_CACHE.get(memoryRoot);
978
- if (existing) {
979
- if (config) existing.applyConfig(config);
980
- return existing;
981
- }
982
- const manager = new MemoryIndexManager(dataDir, memoryRoot, config);
983
- MANAGER_CACHE.set(memoryRoot, manager);
984
- return manager;
985
- }
986
- constructor(dataDir, memoryRoot, config) {
987
- this.dataDir = dataDir;
988
- this.memoryRoot = memoryRoot;
989
- this.storePath = path.join(memoryRoot, ".index.sqlite");
990
- this.config = resolveConfig(DEFAULT_MEMORY_SEARCH_CONFIG, config);
991
- this.provider = createMockEmbeddingProvider();
992
- this.providerKey = this.computeProviderKey();
993
- this.vector.enabled = this.config.store.vector.enabled;
994
- this.vector.extensionPath = this.config.store.vector.extensionPath;
995
- this.db = this.openDatabase();
996
- this.ensureSchema();
997
- this.ensureWatcher();
998
- this.ensureSessionListener();
999
- this.dirty = true;
1000
- }
1001
- applyConfig(config) {
1002
- this.config = resolveConfig(this.config, config);
1003
- this.vector.enabled = this.config.store.vector.enabled;
1004
- this.vector.extensionPath = this.config.store.vector.extensionPath;
1005
- this.ensureWatcher();
1006
- this.ensureSessionListener();
1007
- }
1008
- warmSession() {
1009
- if (!this.config.sync.onSessionStart) return;
1010
- this.sync({ reason: "session-start" }).catch(() => {});
1011
- }
1012
- noteSessionUpdate(sessionFile) {
1013
- this.scheduleSessionDirty(sessionFile);
1014
- }
1015
- status() {
1016
- const sourceFilter = this.buildSourceFilter();
1017
- const files = this.db.prepare(`SELECT COUNT(*) as c FROM files WHERE 1=1${sourceFilter.sql}`).get(...sourceFilter.params);
1018
- const chunks = this.db.prepare(`SELECT COUNT(*) as c FROM chunks WHERE 1=1${sourceFilter.sql}`).get(...sourceFilter.params);
1019
- const sourceCounts = (() => {
1020
- const sources = Array.from(this.config.sources);
1021
- if (sources.length === 0) return [];
1022
- const bySource = /* @__PURE__ */ new Map();
1023
- for (const source of sources) bySource.set(source, {
1024
- files: 0,
1025
- chunks: 0
1026
- });
1027
- const fileRows = this.db.prepare(`SELECT source, COUNT(*) as c FROM files WHERE 1=1${sourceFilter.sql} GROUP BY source`).all(...sourceFilter.params);
1028
- for (const row of fileRows) {
1029
- const entry = bySource.get(row.source) ?? {
1030
- files: 0,
1031
- chunks: 0
1032
- };
1033
- entry.files = row.c ?? 0;
1034
- bySource.set(row.source, entry);
1035
- }
1036
- const chunkRows = this.db.prepare(`SELECT source, COUNT(*) as c FROM chunks WHERE 1=1${sourceFilter.sql} GROUP BY source`).all(...sourceFilter.params);
1037
- for (const row of chunkRows) {
1038
- const entry = bySource.get(row.source) ?? {
1039
- files: 0,
1040
- chunks: 0
1041
- };
1042
- entry.chunks = row.c ?? 0;
1043
- bySource.set(row.source, entry);
1044
- }
1045
- return sources.map((source) => Object.assign({ source }, bySource.get(source)));
1046
- })();
1047
- const searchMode = this.provider ? "hybrid" : "fts-only";
1048
- const cacheEntries = this.config.cache.enabled ? this.db.prepare(`SELECT COUNT(*) as c FROM ${EMBEDDING_CACHE_TABLE}`).get()?.c ?? 0 : void 0;
1049
- return {
1050
- backend: "builtin",
1051
- files: files?.c ?? 0,
1052
- chunks: chunks?.c ?? 0,
1053
- dirty: this.dirty || this.sessionsDirty,
1054
- workspaceDir: this.memoryRoot,
1055
- dbPath: this.storePath,
1056
- provider: this.provider.id,
1057
- model: this.provider.model,
1058
- sources: Array.from(this.config.sources),
1059
- sourceCounts,
1060
- cache: this.config.cache.enabled ? {
1061
- enabled: true,
1062
- entries: cacheEntries,
1063
- maxEntries: this.config.cache.maxEntries
1064
- } : {
1065
- enabled: false,
1066
- maxEntries: this.config.cache.maxEntries
1067
- },
1068
- fts: {
1069
- enabled: this.config.hybrid.enabled,
1070
- available: this.ftsAvailable,
1071
- error: this.ftsError
1072
- },
1073
- vector: {
1074
- enabled: this.vector.enabled,
1075
- available: this.vector.available ?? void 0,
1076
- extensionPath: this.vector.extensionPath,
1077
- loadError: this.vector.loadError,
1078
- dims: this.vector.dims
1079
- },
1080
- custom: { searchMode }
1081
- };
1082
- }
1083
- async probeEmbeddingAvailability() {
1084
- try {
1085
- await this.provider.embedBatch(["ping"]);
1086
- return { ok: true };
1087
- } catch (err) {
1088
- return {
1089
- ok: false,
1090
- error: err instanceof Error ? err.message : String(err)
1091
- };
1092
- }
1093
- }
1094
- async probeVectorAvailability() {
1095
- if (!this.vector.enabled) return false;
1096
- return await this.ensureVectorReady();
1097
- }
1098
- async search(query) {
1099
- const cleaned = query.trim();
1100
- if (!cleaned) return [];
1101
- if (!this.readMeta()) await this.sync({ reason: "boot" });
1102
- else if (this.config.sync.onSearch && (this.dirty || this.sessionsDirty)) this.sync({ reason: "search" }).catch(() => {});
1103
- const hybrid = this.config.hybrid;
1104
- const candidates = Math.min(200, Math.max(1, Math.floor(this.config.maxResults * hybrid.candidateMultiplier)));
1105
- const keywordResults = hybrid.enabled && this.ftsAvailable ? await this.searchKeyword(cleaned, candidates).catch(() => []) : [];
1106
- const queryVec = await this.provider.embedQuery(cleaned);
1107
- const vectorResults = queryVec.some((v) => v !== 0) ? await this.searchVector(queryVec, candidates).catch(() => []) : [];
1108
- if (!hybrid.enabled || !this.ftsAvailable) return vectorResults.filter((entry) => entry.score >= this.config.minScore).slice(0, this.config.maxResults);
1109
- const merged = await mergeHybridResults({
1110
- vector: vectorResults.map((r) => ({
1111
- id: `${r.path}:${r.startLine}:${r.endLine}:${r.source}`,
1112
- path: r.path,
1113
- startLine: r.startLine,
1114
- endLine: r.endLine,
1115
- source: r.source,
1116
- snippet: r.snippet,
1117
- vectorScore: r.score
1118
- })),
1119
- keyword: keywordResults.map((r) => ({
1120
- id: `${r.path}:${r.startLine}:${r.endLine}:${r.source}`,
1121
- path: r.path,
1122
- startLine: r.startLine,
1123
- endLine: r.endLine,
1124
- source: r.source,
1125
- snippet: r.snippet,
1126
- textScore: r.textScore
1127
- })),
1128
- vectorWeight: hybrid.vectorWeight,
1129
- textWeight: hybrid.textWeight,
1130
- mmr: hybrid.mmr,
1131
- temporalDecay: hybrid.temporalDecay,
1132
- memoryRoot: this.memoryRoot
1133
- });
1134
- const strict = merged.filter((entry) => entry.score >= this.config.minScore);
1135
- if (strict.length > 0 || keywordResults.length === 0) return this.decorateCitations(strict.slice(0, this.config.maxResults));
1136
- const relaxedMinScore = Math.min(this.config.minScore, hybrid.textWeight);
1137
- const relaxedResults = merged.filter((entry) => entry.score >= relaxedMinScore).slice(0, this.config.maxResults);
1138
- return this.decorateCitations(relaxedResults);
1139
- }
1140
- async readFile(params) {
1141
- const rawPath = params.relPath.trim();
1142
- if (!rawPath) throw new Error("path required");
1143
- const absPath = path.isAbsolute(rawPath) ? path.resolve(rawPath) : path.resolve(this.memoryRoot, rawPath);
1144
- const relPath = path.relative(this.memoryRoot, absPath).replace(/\\/g, "/");
1145
- if (!(relPath.length > 0 && !relPath.startsWith("..") && !path.isAbsolute(relPath) && isMemoryPath(relPath))) throw new Error("path required");
1146
- if (!absPath.endsWith(".md")) throw new Error("path required");
1147
- if ((await statRegularFile(absPath)).missing) return {
1148
- text: "",
1149
- path: relPath
1150
- };
1151
- let content;
1152
- try {
1153
- content = await fs.readFile(absPath, "utf-8");
1154
- } catch (err) {
1155
- if (isFileMissingError(err)) return {
1156
- text: "",
1157
- path: relPath
1158
- };
1159
- throw err;
1160
- }
1161
- if (!params.from && !params.lines) return {
1162
- text: content,
1163
- path: relPath
1164
- };
1165
- const lines = content.split("\n");
1166
- const start = Math.max(1, params.from ?? 1);
1167
- const count = Math.max(1, params.lines ?? lines.length);
1168
- return {
1169
- text: lines.slice(start - 1, start - 1 + count).join("\n"),
1170
- path: relPath
1171
- };
1172
- }
1173
- async appendToMemory(content) {
1174
- const dir = this.memoryRoot;
1175
- await fs.mkdir(dir, { recursive: true });
1176
- const filePath = path.join(dir, "MEMORY.md");
1177
- const entry = content.endsWith("\n") ? content : `${content}\n`;
1178
- await fs.appendFile(filePath, entry, "utf-8");
1179
- this.dirty = true;
1180
- }
1181
- openDatabase() {
1182
- ensureDir(path.dirname(this.storePath));
1183
- const { DatabaseSync } = requireNodeSqlite();
1184
- return new DatabaseSync(this.storePath, { allowExtension: this.vector.enabled });
1185
- }
1186
- ensureSchema() {
1187
- const result = ensureMemoryIndexSchema({
1188
- db: this.db,
1189
- embeddingCacheTable: EMBEDDING_CACHE_TABLE,
1190
- ftsTable: FTS_TABLE,
1191
- ftsEnabled: this.config.hybrid.enabled
1192
- });
1193
- this.ftsAvailable = result.ftsAvailable;
1194
- if (result.ftsError) this.ftsError = result.ftsError;
1195
- }
1196
- async ensureVectorReady(dimensions) {
1197
- if (!this.vector.enabled) return false;
1198
- if (!this.vectorReady) this.vectorReady = this.withTimeout(this.loadVectorExtension(), VECTOR_LOAD_TIMEOUT_MS, `sqlite-vec load timed out after ${Math.round(VECTOR_LOAD_TIMEOUT_MS / 1e3)}s`);
1199
- let ready = false;
1200
- try {
1201
- ready = await this.vectorReady || false;
1202
- } catch (err) {
1203
- const message = err instanceof Error ? err.message : String(err);
1204
- this.vector.available = false;
1205
- this.vector.loadError = message;
1206
- this.vectorReady = null;
1207
- return false;
1208
- }
1209
- if (ready && typeof dimensions === "number" && dimensions > 0) this.ensureVectorTable(dimensions);
1210
- return ready;
1211
- }
1212
- async loadVectorExtension() {
1213
- if (this.vector.available !== null) return this.vector.available;
1214
- if (!this.vector.enabled) {
1215
- this.vector.available = false;
1216
- return false;
1217
- }
1218
- try {
1219
- const resolvedPath = this.vector.extensionPath?.trim() ? this.vector.extensionPath.trim() : void 0;
1220
- const loaded = await loadSqliteVecExtension({
1221
- db: this.db,
1222
- extensionPath: resolvedPath
1223
- });
1224
- if (!loaded.ok) throw new Error(loaded.error ?? "unknown sqlite-vec load error");
1225
- this.vector.extensionPath = loaded.extensionPath;
1226
- this.vector.available = true;
1227
- return true;
1228
- } catch (err) {
1229
- const message = err instanceof Error ? err.message : String(err);
1230
- this.vector.available = false;
1231
- this.vector.loadError = message;
1232
- return false;
1233
- }
1234
- }
1235
- ensureVectorTable(dimensions) {
1236
- if (this.vector.dims === dimensions) return;
1237
- if (this.vector.dims && this.vector.dims !== dimensions) this.dropVectorTable();
1238
- this.db.exec(`CREATE VIRTUAL TABLE IF NOT EXISTS ${VECTOR_TABLE} USING vec0(\n id TEXT PRIMARY KEY,\n embedding FLOAT[${dimensions}]\n)`);
1239
- this.vector.dims = dimensions;
1240
- }
1241
- dropVectorTable() {
1242
- try {
1243
- this.db.exec(`DROP TABLE IF EXISTS ${VECTOR_TABLE}`);
1244
- } catch {}
1245
- }
1246
- async withTimeout(promise, timeoutMs, message) {
1247
- if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return await promise;
1248
- let timer = null;
1249
- const timeoutPromise = new Promise((_, reject) => {
1250
- timer = setTimeout(() => reject(new Error(message)), timeoutMs);
1251
- });
1252
- try {
1253
- return await Promise.race([promise, timeoutPromise]);
1254
- } finally {
1255
- if (timer) clearTimeout(timer);
1256
- }
1257
- }
1258
- ensureWatcher() {
1259
- if (!this.config.sync.watch || this.watchers.length > 0) return;
1260
- const watchTargets = [this.memoryRoot, path.join(this.memoryRoot, "memory")];
1261
- const onChange = () => {
1262
- this.dirty = true;
1263
- this.scheduleWatchSync();
1264
- };
1265
- for (const target of watchTargets) try {
1266
- const watcher = fsSync.watch(target, { recursive: false }, onChange);
1267
- this.watchers.push(watcher);
1268
- } catch {}
1269
- }
1270
- scheduleWatchSync() {
1271
- if (!this.config.sync.watch) return;
1272
- if (this.watchTimer) clearTimeout(this.watchTimer);
1273
- this.watchTimer = setTimeout(() => {
1274
- this.watchTimer = null;
1275
- this.sync({ reason: "watch" }).catch(() => {});
1276
- }, this.config.sync.watchDebounceMs);
1277
- }
1278
- ensureSessionListener() {
1279
- if (!this.config.experimental.sessionMemory) return;
1280
- const sessionsRoot = path.join(this.dataDir, ".pingclaw", "sessions");
1281
- try {
1282
- const watcher = fsSync.watch(sessionsRoot, { recursive: true }, (_event, filename) => {
1283
- if (!filename || !filename.endsWith("transcript.jsonl")) return;
1284
- const abs = path.join(sessionsRoot, filename);
1285
- this.scheduleSessionDirty(abs);
1286
- });
1287
- this.watchers.push(watcher);
1288
- } catch {}
1289
- }
1290
- scheduleSessionDirty(sessionFile) {
1291
- if (!this.config.experimental.sessionMemory) return;
1292
- this.sessionPendingFiles.add(sessionFile);
1293
- if (this.sessionWatchTimer) return;
1294
- this.sessionWatchTimer = setTimeout(() => {
1295
- this.sessionWatchTimer = null;
1296
- this.processSessionDeltaBatch().catch(() => {});
1297
- }, 5e3);
1298
- }
1299
- async processSessionDeltaBatch() {
1300
- if (this.sessionPendingFiles.size === 0) return;
1301
- const pending = Array.from(this.sessionPendingFiles);
1302
- this.sessionPendingFiles.clear();
1303
- let shouldSync = false;
1304
- for (const sessionFile of pending) {
1305
- const delta = await this.updateSessionDelta(sessionFile);
1306
- if (!delta) continue;
1307
- const bytesThreshold = delta.deltaBytes;
1308
- const messagesThreshold = delta.deltaMessages;
1309
- const bytesHit = bytesThreshold <= 0 ? delta.pendingBytes > 0 : delta.pendingBytes >= bytesThreshold;
1310
- const messagesHit = messagesThreshold <= 0 ? delta.pendingMessages > 0 : delta.pendingMessages >= messagesThreshold;
1311
- if (!bytesHit && !messagesHit) continue;
1312
- this.sessionsDirtyFiles.add(sessionFile);
1313
- this.sessionsDirty = true;
1314
- delta.pendingBytes = bytesThreshold > 0 ? Math.max(0, delta.pendingBytes - bytesThreshold) : 0;
1315
- delta.pendingMessages = messagesThreshold > 0 ? Math.max(0, delta.pendingMessages - messagesThreshold) : 0;
1316
- shouldSync = true;
1317
- }
1318
- if (shouldSync) this.sync({ reason: "session-delta" }).catch(() => {});
1319
- }
1320
- async updateSessionDelta(sessionFile) {
1321
- const thresholds = this.config.sync.sessions;
1322
- if (!thresholds) return null;
1323
- let stat;
1324
- try {
1325
- stat = await fs.stat(sessionFile);
1326
- } catch {
1327
- return null;
1328
- }
1329
- const size = stat.size;
1330
- let state = this.sessionDeltas.get(sessionFile);
1331
- if (!state) {
1332
- state = {
1333
- lastSize: 0,
1334
- pendingBytes: 0,
1335
- pendingMessages: 0
1336
- };
1337
- this.sessionDeltas.set(sessionFile, state);
1338
- }
1339
- const deltaBytes = Math.max(0, size - state.lastSize);
1340
- if (deltaBytes === 0 && size === state.lastSize) return {
1341
- deltaBytes: thresholds.deltaBytes,
1342
- deltaMessages: thresholds.deltaMessages,
1343
- pendingBytes: state.pendingBytes,
1344
- pendingMessages: state.pendingMessages
1345
- };
1346
- if (size < state.lastSize) {
1347
- state.lastSize = size;
1348
- state.pendingBytes += size;
1349
- if (thresholds.deltaMessages > 0 && (thresholds.deltaBytes <= 0 || state.pendingBytes < thresholds.deltaBytes)) state.pendingMessages += await this.countNewlines(sessionFile, 0, size);
1350
- } else {
1351
- state.pendingBytes += deltaBytes;
1352
- if (thresholds.deltaMessages > 0 && (thresholds.deltaBytes <= 0 || state.pendingBytes < thresholds.deltaBytes)) state.pendingMessages += await this.countNewlines(sessionFile, state.lastSize, size);
1353
- state.lastSize = size;
1354
- }
1355
- this.sessionDeltas.set(sessionFile, state);
1356
- return {
1357
- deltaBytes: thresholds.deltaBytes,
1358
- deltaMessages: thresholds.deltaMessages,
1359
- pendingBytes: state.pendingBytes,
1360
- pendingMessages: state.pendingMessages
1361
- };
1362
- }
1363
- async countNewlines(absPath, start, end) {
1364
- if (end <= start) return 0;
1365
- let handle;
1366
- try {
1367
- handle = await fs.open(absPath, "r");
1368
- } catch {
1369
- return 0;
1370
- }
1371
- try {
1372
- let offset = start;
1373
- let count = 0;
1374
- const buffer = Buffer.alloc(64 * 1024);
1375
- while (offset < end) {
1376
- const toRead = Math.min(buffer.length, end - offset);
1377
- const { bytesRead } = await handle.read(buffer, 0, toRead, offset);
1378
- if (bytesRead <= 0) break;
1379
- for (let i = 0; i < bytesRead; i += 1) if (buffer[i] === 10) count += 1;
1380
- offset += bytesRead;
1381
- }
1382
- return count;
1383
- } finally {
1384
- await handle.close();
1385
- }
1386
- }
1387
- computeProviderKey() {
1388
- return hashText(JSON.stringify({
1389
- provider: this.provider.id,
1390
- model: this.provider.model
1391
- }));
1392
- }
1393
- readMeta() {
1394
- const row = this.db.prepare(`SELECT value FROM meta WHERE key = ?`).get(META_KEY);
1395
- if (!row?.value) return null;
1396
- try {
1397
- return JSON.parse(row.value);
1398
- } catch {
1399
- return null;
1400
- }
1401
- }
1402
- writeMeta(meta) {
1403
- const value = JSON.stringify(meta);
1404
- this.db.prepare(`INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value=excluded.value`).run(META_KEY, value);
1405
- }
1406
- loadEmbeddingCache(hashes) {
1407
- if (!this.config.cache.enabled || hashes.length === 0) return /* @__PURE__ */ new Map();
1408
- const unique = Array.from(new Set(hashes.filter(Boolean)));
1409
- if (unique.length === 0) return /* @__PURE__ */ new Map();
1410
- const out = /* @__PURE__ */ new Map();
1411
- const baseParams = [
1412
- this.provider.id,
1413
- this.provider.model,
1414
- this.providerKey
1415
- ];
1416
- const batchSize = 400;
1417
- for (let start = 0; start < unique.length; start += batchSize) {
1418
- const batch = unique.slice(start, start + batchSize);
1419
- const placeholders = batch.map(() => "?").join(", ");
1420
- const rows = this.db.prepare(`SELECT hash, embedding FROM ${EMBEDDING_CACHE_TABLE}\n WHERE provider = ? AND model = ? AND provider_key = ? AND hash IN (${placeholders})`).all(...baseParams, ...batch);
1421
- for (const row of rows) out.set(row.hash, parseEmbedding(row.embedding));
1422
- }
1423
- return out;
1424
- }
1425
- upsertEmbeddingCache(entries) {
1426
- if (!this.config.cache.enabled || entries.length === 0) return;
1427
- const now = Date.now();
1428
- const stmt = this.db.prepare(`INSERT INTO ${EMBEDDING_CACHE_TABLE} (provider, model, provider_key, hash, embedding, dims, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(provider, model, provider_key, hash) DO UPDATE SET\n embedding=excluded.embedding,\n dims=excluded.dims,\n updated_at=excluded.updated_at`);
1429
- for (const entry of entries) {
1430
- const embedding = entry.embedding ?? [];
1431
- stmt.run(this.provider.id, this.provider.model, this.providerKey, entry.hash, JSON.stringify(embedding), embedding.length, now);
1432
- }
1433
- }
1434
- async embedChunks(chunks) {
1435
- if (chunks.length === 0) return [];
1436
- const cached = this.loadEmbeddingCache(chunks.map((chunk) => chunk.hash));
1437
- const embeddings = Array.from({ length: chunks.length }, () => []);
1438
- const missing = [];
1439
- for (let i = 0; i < chunks.length; i += 1) {
1440
- const chunk = chunks[i];
1441
- const hit = chunk?.hash ? cached.get(chunk.hash) : void 0;
1442
- if (hit && hit.length > 0) embeddings[i] = hit;
1443
- else if (chunk) missing.push({
1444
- index: i,
1445
- chunk
1446
- });
1447
- }
1448
- if (missing.length === 0) return embeddings;
1449
- const batchEmbeddings = await this.provider.embedBatch(missing.map((m) => m.chunk.text));
1450
- const toCache = [];
1451
- for (let i = 0; i < missing.length; i += 1) {
1452
- const item = missing[i];
1453
- const embedding = batchEmbeddings[i] ?? [];
1454
- embeddings[item.index] = embedding;
1455
- toCache.push({
1456
- hash: item.chunk.hash,
1457
- embedding
1458
- });
1459
- }
1460
- this.upsertEmbeddingCache(toCache);
1461
- return embeddings;
1462
- }
1463
- async indexFile(entry, options) {
1464
- const chunks = chunkMarkdown(options.content ?? await fs.readFile(entry.absPath, "utf-8"), {
1465
- tokens: this.config.chunkTokens,
1466
- overlap: this.config.chunkOverlap
1467
- }).filter((chunk) => chunk.text.trim().length > 0);
1468
- if (options.source === "sessions") remapChunkLines(chunks, options.lineMap);
1469
- const embeddings = await this.embedChunks(chunks);
1470
- const sample = embeddings.find((embedding) => embedding.length > 0);
1471
- const vectorReady = sample ? await this.ensureVectorReady(sample.length) : false;
1472
- const now = Date.now();
1473
- if (vectorReady) try {
1474
- this.db.prepare(`DELETE FROM ${VECTOR_TABLE} WHERE id IN (SELECT id FROM chunks WHERE path = ? AND source = ?)`).run(entry.path, options.source);
1475
- } catch {}
1476
- if (this.ftsAvailable) try {
1477
- this.db.prepare(`DELETE FROM ${FTS_TABLE} WHERE path = ? AND source = ? AND model = ?`).run(entry.path, options.source, this.provider.model);
1478
- } catch {}
1479
- this.db.prepare(`DELETE FROM chunks WHERE path = ? AND source = ?`).run(entry.path, options.source);
1480
- for (let i = 0; i < chunks.length; i += 1) {
1481
- const chunk = chunks[i];
1482
- const embedding = embeddings[i] ?? [];
1483
- const id = hashText(`${options.source}:${entry.path}:${chunk.startLine}:${chunk.endLine}:${chunk.hash}:${this.provider.model}`);
1484
- this.db.prepare(`INSERT INTO chunks (id, path, source, start_line, end_line, hash, model, text, embedding, updated_at)
1485
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1486
- ON CONFLICT(id) DO UPDATE SET
1487
- hash=excluded.hash,
1488
- model=excluded.model,
1489
- text=excluded.text,
1490
- embedding=excluded.embedding,
1491
- updated_at=excluded.updated_at`).run(id, entry.path, options.source, chunk.startLine, chunk.endLine, chunk.hash, this.provider.model, chunk.text, JSON.stringify(embedding), now);
1492
- if (vectorReady && embedding.length > 0) {
1493
- try {
1494
- this.db.prepare(`DELETE FROM ${VECTOR_TABLE} WHERE id = ?`).run(id);
1495
- } catch {}
1496
- this.db.prepare(`INSERT INTO ${VECTOR_TABLE} (id, embedding) VALUES (?, ?)`).run(id, Buffer.from(new Float32Array(embedding).buffer));
1497
- }
1498
- if (this.ftsAvailable) this.db.prepare(`INSERT INTO ${FTS_TABLE} (text, id, path, source, model, start_line, end_line)\n VALUES (?, ?, ?, ?, ?, ?, ?)`).run(chunk.text, id, entry.path, options.source, this.provider.model, chunk.startLine, chunk.endLine);
1499
- }
1500
- this.db.prepare(`INSERT INTO files (path, source, hash, mtime, size) VALUES (?, ?, ?, ?, ?)
1501
- ON CONFLICT(path) DO UPDATE SET
1502
- source=excluded.source,
1503
- hash=excluded.hash,
1504
- mtime=excluded.mtime,
1505
- size=excluded.size`).run(entry.path, options.source, entry.hash, entry.mtimeMs, entry.size);
1506
- }
1507
- buildSourceFilter() {
1508
- const sources = this.config.sources;
1509
- if (!sources.length) return {
1510
- sql: "",
1511
- params: []
1512
- };
1513
- return {
1514
- sql: ` AND source IN (${sources.map(() => "?").join(", ")})`,
1515
- params: sources
1516
- };
1517
- }
1518
- async searchVector(queryVec, limit) {
1519
- return await searchVector({
1520
- db: this.db,
1521
- vectorTable: VECTOR_TABLE,
1522
- providerModel: this.provider.model,
1523
- queryVec,
1524
- limit,
1525
- snippetMaxChars: SNIPPET_MAX_CHARS,
1526
- ensureVectorReady: async (dimensions) => await this.ensureVectorReady(dimensions),
1527
- sourceFilterVec: this.buildSourceFilter(),
1528
- sourceFilterChunks: this.buildSourceFilter()
1529
- });
1530
- }
1531
- async searchKeyword(query, limit) {
1532
- if (!this.ftsAvailable) return [];
1533
- return await searchKeyword({
1534
- db: this.db,
1535
- ftsTable: FTS_TABLE,
1536
- providerModel: this.provider.model,
1537
- query,
1538
- limit,
1539
- snippetMaxChars: SNIPPET_MAX_CHARS,
1540
- sourceFilter: this.buildSourceFilter(),
1541
- buildFtsQuery: (raw) => buildFtsQuery(raw),
1542
- bm25RankToScore
1543
- });
1544
- }
1545
- resetIndex() {
1546
- this.db.exec(`DELETE FROM files`);
1547
- this.db.exec(`DELETE FROM chunks`);
1548
- if (this.ftsAvailable) try {
1549
- this.db.exec(`DELETE FROM ${FTS_TABLE}`);
1550
- } catch {}
1551
- this.dropVectorTable();
1552
- this.vector.dims = void 0;
1553
- this.sessionsDirtyFiles.clear();
1554
- }
1555
- async sync(params) {
1556
- const meta = this.readMeta();
1557
- const needsFullReindex = !meta || meta.model !== this.provider.model || meta.provider !== this.provider.id || meta.providerKey !== this.providerKey || meta.chunkTokens !== this.config.chunkTokens || meta.chunkOverlap !== this.config.chunkOverlap || this.vector.available && !meta?.vectorDims;
1558
- if (needsFullReindex) this.resetIndex();
1559
- const files = await listMemoryFiles(this.memoryRoot);
1560
- const fileEntries = (await Promise.all(files.map(async (file) => buildFileEntry(file, this.memoryRoot)))).filter((entry) => entry !== null);
1561
- const activePaths = new Set(fileEntries.map((entry) => entry.path));
1562
- const tasks = fileEntries.map((entry) => async () => {
1563
- const record = this.db.prepare(`SELECT hash FROM files WHERE path = ? AND source = ?`).get(entry.path, "memory");
1564
- if (!needsFullReindex && record?.hash === entry.hash) return;
1565
- await this.indexFile(entry, { source: "memory" });
1566
- });
1567
- for (const task of tasks) await task();
1568
- const staleRows = this.db.prepare(`SELECT path FROM files WHERE source = ?`).all("memory");
1569
- for (const stale of staleRows) {
1570
- if (activePaths.has(stale.path)) continue;
1571
- this.db.prepare(`DELETE FROM files WHERE path = ? AND source = ?`).run(stale.path, "memory");
1572
- try {
1573
- this.db.prepare(`DELETE FROM ${VECTOR_TABLE} WHERE id IN (SELECT id FROM chunks WHERE path = ? AND source = ?)`).run(stale.path, "memory");
1574
- } catch {}
1575
- this.db.prepare(`DELETE FROM chunks WHERE path = ? AND source = ?`).run(stale.path, "memory");
1576
- if (this.ftsAvailable) try {
1577
- this.db.prepare(`DELETE FROM ${FTS_TABLE} WHERE path = ? AND source = ? AND model = ?`).run(stale.path, "memory", this.provider.model);
1578
- } catch {}
1579
- }
1580
- if (this.config.experimental.sessionMemory && (needsFullReindex || this.sessionsDirty)) await this.syncSessionFiles(needsFullReindex);
1581
- this.writeMeta({
1582
- model: this.provider.model,
1583
- provider: this.provider.id,
1584
- providerKey: this.providerKey,
1585
- chunkTokens: this.config.chunkTokens,
1586
- chunkOverlap: this.config.chunkOverlap,
1587
- ...this.vector.available && this.vector.dims ? { vectorDims: this.vector.dims } : {}
1588
- });
1589
- this.dirty = false;
1590
- }
1591
- async syncSessionFiles(needsFullReindex) {
1592
- const files = await listSessionFilesForAgent(this.dataDir);
1593
- const entries = (await Promise.all(files.map(async (absPath) => buildSessionEntry(this.dataDir, absPath)))).filter((entry) => entry !== null);
1594
- const activePaths = new Set(entries.map((entry) => entry.path));
1595
- const indexAll = needsFullReindex || this.sessionsDirtyFiles.size === 0;
1596
- for (const entry of entries) {
1597
- if (!indexAll && !this.sessionsDirtyFiles.has(entry.absPath)) continue;
1598
- const record = this.db.prepare(`SELECT hash FROM files WHERE path = ? AND source = ?`).get(entry.path, "sessions");
1599
- if (!needsFullReindex && record?.hash === entry.hash) {
1600
- this.resetSessionDelta(entry.absPath, entry.size);
1601
- continue;
1602
- }
1603
- await this.indexFile(entry, {
1604
- source: "sessions",
1605
- content: entry.content,
1606
- lineMap: entry.lineMap
1607
- });
1608
- this.resetSessionDelta(entry.absPath, entry.size);
1609
- }
1610
- const staleRows = this.db.prepare(`SELECT path FROM files WHERE source = ?`).all("sessions");
1611
- for (const stale of staleRows) {
1612
- if (activePaths.has(stale.path)) continue;
1613
- this.db.prepare(`DELETE FROM files WHERE path = ? AND source = ?`).run(stale.path, "sessions");
1614
- try {
1615
- this.db.prepare(`DELETE FROM ${VECTOR_TABLE} WHERE id IN (SELECT id FROM chunks WHERE path = ? AND source = ?)`).run(stale.path, "sessions");
1616
- } catch {}
1617
- this.db.prepare(`DELETE FROM chunks WHERE path = ? AND source = ?`).run(stale.path, "sessions");
1618
- if (this.ftsAvailable) try {
1619
- this.db.prepare(`DELETE FROM ${FTS_TABLE} WHERE path = ? AND source = ? AND model = ?`).run(stale.path, "sessions", this.provider.model);
1620
- } catch {}
1621
- }
1622
- this.sessionsDirty = false;
1623
- this.sessionsDirtyFiles.clear();
1624
- }
1625
- resetSessionDelta(absPath, size) {
1626
- const state = this.sessionDeltas.get(absPath);
1627
- if (!state) return;
1628
- state.lastSize = size;
1629
- state.pendingBytes = 0;
1630
- state.pendingMessages = 0;
1631
- }
1632
- decorateCitations(results) {
1633
- if (this.config.citations === "off") return results;
1634
- return results.map((entry) => {
1635
- const citation = `Source: ${entry.path}#L${entry.startLine}-L${entry.endLine}`;
1636
- if (this.config.citations === "on") return {
1637
- ...entry,
1638
- citation,
1639
- snippet: `${entry.snippet}\n\n${citation}`
1640
- };
1641
- return {
1642
- ...entry,
1643
- citation
1644
- };
1645
- });
1646
- }
1647
- };
1648
-
1649
- //#endregion
1650
- export { createSession as a, listSessions as c, metadataPath as d, saveSessionMetadata as f, transcriptPath as h, appendTranscriptEntry as i, loadSessionMetadata as l, sessionsDir as m, manager_exports as n, ensureSession as o, sessionDir as p, listMemoryFiles as r, listSessionSummaries as s, MemoryIndexManager as t, loadTranscript as u };
1651
- //# sourceMappingURL=manager-qXa-NP0p.js.map