@theokit/sdk 4.61.0 → 4.63.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/CHANGELOG.md +139 -0
- package/dist/{agent-P42GA6RM.js → agent-536OOESZ.js} +8 -6
- package/dist/{agent-P42GA6RM.js.map → agent-536OOESZ.js.map} +1 -1
- package/dist/{agent-M53C3PAA.cjs → agent-5KYMG63I.cjs} +9 -7
- package/dist/{agent-M53C3PAA.cjs.map → agent-5KYMG63I.cjs.map} +1 -1
- package/dist/{agent-DIu6FooJ.d.ts → agent-BFno7Sfn.d.ts} +36 -21
- package/dist/{agent-D3Xr_-6Z.d.cts → agent-X0DKA943.d.cts} +36 -21
- package/dist/chunk-A4RAL2ER.cjs +51 -0
- package/dist/chunk-A4RAL2ER.cjs.map +1 -0
- package/dist/chunk-A4VHSE56.cjs +21 -0
- package/dist/chunk-A4VHSE56.cjs.map +1 -0
- package/dist/{chunk-CQ2TQ32Y.js → chunk-AMFXSENK.js} +3 -48
- package/dist/chunk-AMFXSENK.js.map +1 -0
- package/dist/chunk-C7QPML3L.js +831 -0
- package/dist/chunk-C7QPML3L.js.map +1 -0
- package/dist/{chunk-5IT6DUOO.cjs → chunk-CASUEY42.cjs} +4 -4
- package/dist/{chunk-5IT6DUOO.cjs.map → chunk-CASUEY42.cjs.map} +1 -1
- package/dist/{chunk-GQZKDGZM.js → chunk-CHKH5452.js} +90 -70
- package/dist/chunk-CHKH5452.js.map +1 -0
- package/dist/chunk-EHJEZAOO.cjs +864 -0
- package/dist/chunk-EHJEZAOO.cjs.map +1 -0
- package/dist/chunk-IRCJ7EHV.cjs +201 -0
- package/dist/chunk-IRCJ7EHV.cjs.map +1 -0
- package/dist/chunk-LW7G5DYW.js +19 -0
- package/dist/chunk-LW7G5DYW.js.map +1 -0
- package/dist/{chunk-532CSLYU.js → chunk-MG3SOM3M.js} +3 -3
- package/dist/{chunk-532CSLYU.js.map → chunk-MG3SOM3M.js.map} +1 -1
- package/dist/{chunk-R7YIVL3K.cjs → chunk-NLXOGBRJ.cjs} +98 -78
- package/dist/chunk-NLXOGBRJ.cjs.map +1 -0
- package/dist/{chunk-SKXBJ2NU.cjs → chunk-NXH4GPAQ.cjs} +2 -49
- package/dist/chunk-NXH4GPAQ.cjs.map +1 -0
- package/dist/chunk-R6TA2DRJ.js +194 -0
- package/dist/chunk-R6TA2DRJ.js.map +1 -0
- package/dist/{chunk-6OBIWHDR.cjs → chunk-UUP3MUZ6.cjs} +20 -361
- package/dist/chunk-UUP3MUZ6.cjs.map +1 -0
- package/dist/chunk-WE22OXQA.js +48 -0
- package/dist/chunk-WE22OXQA.js.map +1 -0
- package/dist/{chunk-QEKI3YKI.js → chunk-YTR3RBUZ.js} +13 -348
- package/dist/chunk-YTR3RBUZ.js.map +1 -0
- package/dist/{cron-BcWmzWzT.d.cts → cron-BuiRPrvt.d.cts} +1 -1
- package/dist/{cron-De6hzWCF.d.ts → cron-C2SIi31n.d.ts} +1 -1
- package/dist/cron.cjs +8 -6
- package/dist/cron.d.cts +2 -2
- package/dist/cron.d.ts +2 -2
- package/dist/cron.js +7 -5
- package/dist/eval.cjs +7 -5
- package/dist/eval.cjs.map +1 -1
- package/dist/eval.js +6 -4
- package/dist/eval.js.map +1 -1
- package/dist/{index-manager-RHPFVFSC.cjs → index-manager-6TEEIWM7.cjs} +7 -6
- package/dist/{index-manager-RHPFVFSC.cjs.map → index-manager-6TEEIWM7.cjs.map} +1 -1
- package/dist/{index-manager-AHAYJ33H.js → index-manager-ACEQG3IJ.js} +6 -5
- package/dist/{index-manager-AHAYJ33H.js.map → index-manager-ACEQG3IJ.js.map} +1 -1
- package/dist/index.cjs +35 -208
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -3
- package/dist/index.d.ts +8 -3
- package/dist/index.js +22 -195
- package/dist/index.js.map +1 -1
- package/dist/internal/memory/active-memory.d.ts +8 -1
- package/dist/internal/memory/dreaming/diary.d.ts +39 -2
- package/dist/internal/memory/index-db.d.ts +44 -1
- package/dist/internal/memory/index-manager-contract.d.ts +10 -0
- package/dist/internal/memory/index-manager-helpers.d.ts +20 -1
- package/dist/internal/memory/index-manager.d.ts +1 -1
- package/dist/internal/memory/lance-index.d.ts +15 -1
- package/dist/internal/memory/migrate-sqlite-to-lance.d.ts +43 -0
- package/dist/internal/memory/storage/index.cjs +95 -12
- package/dist/internal/memory/storage/index.d.cts +11 -1
- package/dist/internal/memory/storage/index.d.ts +11 -1
- package/dist/internal/memory/storage/index.js +4 -1
- package/dist/internal/memory/storage/markdown-store.d.cts +13 -42
- package/dist/internal/memory/storage/markdown-store.d.ts +13 -42
- package/dist/internal/memory/storage/memory-root.d.cts +93 -0
- package/dist/internal/memory/storage/memory-root.d.ts +93 -0
- package/dist/internal/memory/storage/session-loader.d.cts +35 -1
- package/dist/internal/memory/storage/session-loader.d.ts +35 -1
- package/dist/internal/memory/storage/session-summary-writer.d.cts +50 -2
- package/dist/internal/memory/storage/session-summary-writer.d.ts +50 -2
- package/dist/internal/memory/storage/transcript-store.d.cts +38 -1
- package/dist/internal/memory/storage/transcript-store.d.ts +38 -1
- package/dist/internal/memory/storage/wiki-loader.d.cts +27 -2
- package/dist/internal/memory/storage/wiki-loader.d.ts +27 -2
- package/dist/internal/memory/tools.d.ts +8 -1
- package/dist/internal/memory/types.d.ts +5 -0
- package/dist/internal/persistence/index.cjs +12 -11
- package/dist/internal/persistence/index.cjs.map +1 -1
- package/dist/internal/persistence/index.js +2 -1
- package/dist/internal/persistence/index.js.map +1 -1
- package/dist/internal/persistence/session-dir.d.cts +0 -15
- package/dist/internal/persistence/session-dir.d.ts +0 -15
- package/dist/internal/runtime/memory/memory-store.d.ts +2 -2
- package/dist/persistence.cjs +9 -8
- package/dist/persistence.cjs.map +1 -1
- package/dist/persistence.js +2 -1
- package/dist/persistence.js.map +1 -1
- package/dist/subagents-loader.d.cts +1 -1
- package/dist/subagents-loader.d.ts +1 -1
- package/dist/types/agent.d.ts +11 -0
- package/dist/types/memory-provider.d.ts +8 -0
- package/docs/error-codes.md +10 -9
- package/docs/harness-capability-map.md +57 -17
- package/docs/memory-decisions.md +44 -7
- package/package.json +1 -1
- package/dist/chunk-2UCFUSPW.cjs +0 -468
- package/dist/chunk-2UCFUSPW.cjs.map +0 -1
- package/dist/chunk-6OBIWHDR.cjs.map +0 -1
- package/dist/chunk-CQ2TQ32Y.js.map +0 -1
- package/dist/chunk-GQZKDGZM.js.map +0 -1
- package/dist/chunk-QEKI3YKI.js.map +0 -1
- package/dist/chunk-R7YIVL3K.cjs.map +0 -1
- package/dist/chunk-SKXBJ2NU.cjs.map +0 -1
- package/dist/chunk-WCLDJSMY.js +0 -456
- package/dist/chunk-WCLDJSMY.js.map +0 -1
|
@@ -0,0 +1,831 @@
|
|
|
1
|
+
import { splitFrontmatter, parseSimpleYaml } from './chunk-KVNWIAO4.js';
|
|
2
|
+
import { sanitizeIdentifier, safePathJoin, safeFilenameForId } from './chunk-QARJGQSA.js';
|
|
3
|
+
import { encodeProjectDir } from './chunk-FKMUFNQE.js';
|
|
4
|
+
import { openSqliteResilient } from './chunk-AMFXSENK.js';
|
|
5
|
+
import { withCwdMutex } from './chunk-Q5EWJPRY.js';
|
|
6
|
+
import { replaceFileAtomic } from './chunk-VF7EWVDG.js';
|
|
7
|
+
import { ConfigurationError } from './chunk-IDCKSLYH.js';
|
|
8
|
+
import { redactSecrets } from './chunk-T7XEKOVW.js';
|
|
9
|
+
import { diag } from './chunk-T7O6K6PX.js';
|
|
10
|
+
import { homedir } from 'os';
|
|
11
|
+
import { join, isAbsolute, sep, resolve, relative } from 'path';
|
|
12
|
+
import { readdir, readFile, mkdir, stat } from 'fs/promises';
|
|
13
|
+
import { mkdirSync } from 'fs';
|
|
14
|
+
import { createRequire } from 'module';
|
|
15
|
+
import { createHash } from 'crypto';
|
|
16
|
+
|
|
17
|
+
var DEFAULT_SUBPATH = [".theokit", "memory"];
|
|
18
|
+
function asMemoryRoot(dir) {
|
|
19
|
+
return dir;
|
|
20
|
+
}
|
|
21
|
+
function projectMemoryDir(cwd) {
|
|
22
|
+
return join(cwd, ...DEFAULT_SUBPATH);
|
|
23
|
+
}
|
|
24
|
+
function claudeProjectMemoryDir(cwd) {
|
|
25
|
+
const home = process.env.CLAUDE_CONFIG_DIR?.trim();
|
|
26
|
+
const root = home !== void 0 && home.length > 0 ? home : join(homedir(), ".claude");
|
|
27
|
+
return join(root, "projects", encodeProjectDir(cwd), "memory");
|
|
28
|
+
}
|
|
29
|
+
function resolveMemoryRoot(cwd, config) {
|
|
30
|
+
const declared = config?.directory;
|
|
31
|
+
if (declared === void 0) return projectMemoryDir(cwd);
|
|
32
|
+
const trimmed = declared.trim();
|
|
33
|
+
if (trimmed.startsWith("~/")) return join(homedir(), trimmed.slice(2));
|
|
34
|
+
if (trimmed.length === 0 || !isAbsolute(trimmed)) {
|
|
35
|
+
throw new ConfigurationError(
|
|
36
|
+
`memory.directory must be an absolute path or start with "~/". Received: ${JSON.stringify(declared)}`,
|
|
37
|
+
{ code: "invalid_memory_directory" }
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
return trimmed;
|
|
41
|
+
}
|
|
42
|
+
var MEMORY_INDEX_MAX_LINES = 200;
|
|
43
|
+
var MEMORY_INDEX_MAX_BYTES = 25 * 1024;
|
|
44
|
+
function indexBudgetWarning(index, root) {
|
|
45
|
+
if (root !== claudeProjectMemoryDirFor(root)) return void 0;
|
|
46
|
+
const lines = index.split("\n").length;
|
|
47
|
+
const bytes = Buffer.byteLength(index, "utf8");
|
|
48
|
+
const over = [];
|
|
49
|
+
if (lines > MEMORY_INDEX_MAX_LINES) over.push(`${lines} lines (limit ${MEMORY_INDEX_MAX_LINES})`);
|
|
50
|
+
if (bytes > MEMORY_INDEX_MAX_BYTES) {
|
|
51
|
+
over.push(`${Math.round(bytes / 1024)} KB (limit ${MEMORY_INDEX_MAX_BYTES / 1024} KB)`);
|
|
52
|
+
}
|
|
53
|
+
if (over.length === 0) return void 0;
|
|
54
|
+
return `MEMORY.md at ${root} is ${over.join(" and ")}. The Claude Code CLI loads only the first ${MEMORY_INDEX_MAX_LINES} lines / ${MEMORY_INDEX_MAX_BYTES / 1024} KB of an index and drops the rest without saying so, which is where this store is written. Shorten the entries or move detail into the per-memory files. Recall through this SDK is unaffected \u2014 it reads the files, not the index.`;
|
|
55
|
+
}
|
|
56
|
+
function claudeProjectMemoryDirFor(root) {
|
|
57
|
+
const home = process.env.CLAUDE_CONFIG_DIR?.trim();
|
|
58
|
+
const claudeHome = home !== void 0 && home.length > 0 ? home : join(homedir(), ".claude");
|
|
59
|
+
const prefix = join(claudeHome, "projects");
|
|
60
|
+
const isUnderProjects = root.startsWith(`${prefix}${sep}`);
|
|
61
|
+
return isUnderProjects && root.endsWith(`${sep}memory`) ? root : "";
|
|
62
|
+
}
|
|
63
|
+
function memoryReadRoots(cwd, config) {
|
|
64
|
+
return [
|
|
65
|
+
.../* @__PURE__ */ new Set([
|
|
66
|
+
resolveMemoryRoot(cwd, config),
|
|
67
|
+
projectMemoryDir(cwd),
|
|
68
|
+
claudeProjectMemoryDir(cwd)
|
|
69
|
+
])
|
|
70
|
+
];
|
|
71
|
+
}
|
|
72
|
+
var MEMORY_KINDS = ["user", "feedback", "project", "reference"];
|
|
73
|
+
function legacyMemoryJsonPath(cwd, config) {
|
|
74
|
+
if (config.storePath !== void 0) {
|
|
75
|
+
return resolve(cwd, config.storePath);
|
|
76
|
+
}
|
|
77
|
+
const namespace = sanitizeIdentifier(config.namespace ?? "default");
|
|
78
|
+
const scope = sanitizeIdentifier(config.scope ?? "agent", { maxLen: 16 });
|
|
79
|
+
const userId = sanitizeIdentifier(config.userId ?? "default");
|
|
80
|
+
return safePathJoin(cwd, ".theokit", "memory", namespace, `${scope}-${userId}.json`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/internal/memory/storage/memory-file.ts
|
|
84
|
+
var SAFE_SLUG = /^[a-z0-9][a-z0-9_-]*$/;
|
|
85
|
+
var MAX_SLUG_LENGTH = 64;
|
|
86
|
+
var TARGET_SLUG_LENGTH = 32;
|
|
87
|
+
var TITLE_MAX_LENGTH = 40;
|
|
88
|
+
var TITLE_MAX_WORDS = 4;
|
|
89
|
+
var SLUG_STOPWORDS = /* @__PURE__ */ new Set([
|
|
90
|
+
"the",
|
|
91
|
+
"a",
|
|
92
|
+
"an",
|
|
93
|
+
"and",
|
|
94
|
+
"or",
|
|
95
|
+
"but",
|
|
96
|
+
"is",
|
|
97
|
+
"are",
|
|
98
|
+
"was",
|
|
99
|
+
"were",
|
|
100
|
+
"be",
|
|
101
|
+
"been",
|
|
102
|
+
"being",
|
|
103
|
+
"for",
|
|
104
|
+
"of",
|
|
105
|
+
"to",
|
|
106
|
+
"in",
|
|
107
|
+
"on",
|
|
108
|
+
"at",
|
|
109
|
+
"by",
|
|
110
|
+
"with",
|
|
111
|
+
"from",
|
|
112
|
+
"as",
|
|
113
|
+
"that",
|
|
114
|
+
"this",
|
|
115
|
+
"these",
|
|
116
|
+
"those",
|
|
117
|
+
"it",
|
|
118
|
+
"its",
|
|
119
|
+
"he",
|
|
120
|
+
"she",
|
|
121
|
+
"they",
|
|
122
|
+
"we",
|
|
123
|
+
"you",
|
|
124
|
+
"i",
|
|
125
|
+
"do",
|
|
126
|
+
"does",
|
|
127
|
+
"did",
|
|
128
|
+
"has",
|
|
129
|
+
"have",
|
|
130
|
+
"had",
|
|
131
|
+
"will",
|
|
132
|
+
"would",
|
|
133
|
+
"can",
|
|
134
|
+
"could",
|
|
135
|
+
"should",
|
|
136
|
+
"must",
|
|
137
|
+
"not",
|
|
138
|
+
"no",
|
|
139
|
+
"over",
|
|
140
|
+
"under",
|
|
141
|
+
"into",
|
|
142
|
+
"onto",
|
|
143
|
+
"than",
|
|
144
|
+
"then",
|
|
145
|
+
"when",
|
|
146
|
+
"where",
|
|
147
|
+
"which",
|
|
148
|
+
"who",
|
|
149
|
+
"why",
|
|
150
|
+
"how",
|
|
151
|
+
"all",
|
|
152
|
+
"any",
|
|
153
|
+
"each",
|
|
154
|
+
"more",
|
|
155
|
+
"most",
|
|
156
|
+
"other",
|
|
157
|
+
"some",
|
|
158
|
+
"such",
|
|
159
|
+
"only",
|
|
160
|
+
"own",
|
|
161
|
+
"same",
|
|
162
|
+
"so",
|
|
163
|
+
"too",
|
|
164
|
+
"very",
|
|
165
|
+
"just",
|
|
166
|
+
"also",
|
|
167
|
+
"about",
|
|
168
|
+
"after",
|
|
169
|
+
"before",
|
|
170
|
+
"between",
|
|
171
|
+
"during",
|
|
172
|
+
"up",
|
|
173
|
+
"down",
|
|
174
|
+
"out",
|
|
175
|
+
"off",
|
|
176
|
+
"again",
|
|
177
|
+
"once",
|
|
178
|
+
"here",
|
|
179
|
+
"there",
|
|
180
|
+
"if",
|
|
181
|
+
"because",
|
|
182
|
+
"while"
|
|
183
|
+
]);
|
|
184
|
+
function topicWords(text) {
|
|
185
|
+
return text.toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").split(/[^a-z0-9]+/).filter((w) => w.length >= 2 && !SLUG_STOPWORDS.has(w));
|
|
186
|
+
}
|
|
187
|
+
function slugForFact(text) {
|
|
188
|
+
const words = topicWords(text);
|
|
189
|
+
let slug = "";
|
|
190
|
+
for (const w of words) {
|
|
191
|
+
const next = slug.length === 0 ? w : `${slug}-${w}`;
|
|
192
|
+
if (next.length > TARGET_SLUG_LENGTH && slug.length > 0) break;
|
|
193
|
+
if (next.length > MAX_SLUG_LENGTH) break;
|
|
194
|
+
slug = next;
|
|
195
|
+
}
|
|
196
|
+
if (slug.length === 0) {
|
|
197
|
+
slug = text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, MAX_SLUG_LENGTH).replace(/-+$/, "");
|
|
198
|
+
}
|
|
199
|
+
return SAFE_SLUG.test(slug) ? slug : safeFilenameForId(text);
|
|
200
|
+
}
|
|
201
|
+
function titleForFact(text) {
|
|
202
|
+
const words = topicWords(text);
|
|
203
|
+
if (words.length === 0) return text.trim().slice(0, TITLE_MAX_LENGTH);
|
|
204
|
+
let title = "";
|
|
205
|
+
let used = 0;
|
|
206
|
+
for (const w of words) {
|
|
207
|
+
if (used >= TITLE_MAX_WORDS) break;
|
|
208
|
+
const next = title.length === 0 ? w : `${title} ${w}`;
|
|
209
|
+
if (next.length > TITLE_MAX_LENGTH && title.length > 0) break;
|
|
210
|
+
title = next;
|
|
211
|
+
used += 1;
|
|
212
|
+
}
|
|
213
|
+
if (title.length === 0) title = words[0];
|
|
214
|
+
return title.charAt(0).toUpperCase() + title.slice(1);
|
|
215
|
+
}
|
|
216
|
+
function readMetadata(raw) {
|
|
217
|
+
const metadata = raw ?? {};
|
|
218
|
+
const rawKind = metadata.type;
|
|
219
|
+
const kind = typeof rawKind === "string" && MEMORY_KINDS.includes(rawKind) ? rawKind : void 0;
|
|
220
|
+
const modified = typeof metadata.modified === "string" ? metadata.modified : void 0;
|
|
221
|
+
const observations = readObservationCount(metadata.observations);
|
|
222
|
+
return {
|
|
223
|
+
...kind !== void 0 ? { kind } : {},
|
|
224
|
+
...modified !== void 0 ? { modified } : {},
|
|
225
|
+
...observations !== void 0 ? { observations } : {}
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
function readObservationCount(raw) {
|
|
229
|
+
if (typeof raw !== "number") return void 0;
|
|
230
|
+
if (!Number.isInteger(raw) || raw <= 0) return void 0;
|
|
231
|
+
return raw;
|
|
232
|
+
}
|
|
233
|
+
function renderMemoryFile(fields) {
|
|
234
|
+
const metadata = [" node_type: memory"];
|
|
235
|
+
if (fields.kind !== void 0) metadata.push(` type: ${fields.kind}`);
|
|
236
|
+
if (fields.modified !== void 0) metadata.push(` modified: ${fields.modified}`);
|
|
237
|
+
if (fields.observations !== void 0) {
|
|
238
|
+
metadata.push(` observations: ${fields.observations}`);
|
|
239
|
+
}
|
|
240
|
+
return [
|
|
241
|
+
"---",
|
|
242
|
+
`name: ${fields.name}`,
|
|
243
|
+
`description: ${JSON.stringify(fields.description)}`,
|
|
244
|
+
"metadata:",
|
|
245
|
+
...metadata,
|
|
246
|
+
"---",
|
|
247
|
+
"",
|
|
248
|
+
fields.body,
|
|
249
|
+
""
|
|
250
|
+
].join("\n");
|
|
251
|
+
}
|
|
252
|
+
function parseMemoryFile(raw) {
|
|
253
|
+
const { yaml, body } = splitFrontmatter(raw);
|
|
254
|
+
if (yaml === void 0) return void 0;
|
|
255
|
+
let fields;
|
|
256
|
+
try {
|
|
257
|
+
fields = parseSimpleYaml(yaml);
|
|
258
|
+
} catch {
|
|
259
|
+
return void 0;
|
|
260
|
+
}
|
|
261
|
+
const name = fields.name;
|
|
262
|
+
const description = fields.description;
|
|
263
|
+
if (typeof name !== "string" || typeof description !== "string") return void 0;
|
|
264
|
+
return {
|
|
265
|
+
name,
|
|
266
|
+
description,
|
|
267
|
+
...readMetadata(fields.metadata),
|
|
268
|
+
body: body.trim()
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// src/internal/memory/storage/threat-scan.ts
|
|
273
|
+
var INVISIBLE_CLASS = "\\u200B-\\u200F\\u202A-\\u202E\\u2066-\\u2069\\uFEFF";
|
|
274
|
+
var THREAT_PATTERNS = [
|
|
275
|
+
{
|
|
276
|
+
id: "invisible_unicode",
|
|
277
|
+
// Text that renders as one thing to a reviewer and another to a parser is the whole
|
|
278
|
+
// technique.
|
|
279
|
+
test: new RegExp(`[${INVISIBLE_CLASS}]`, "u"),
|
|
280
|
+
why: "contains invisible or bidirectional control characters"
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
id: "instruction_override",
|
|
284
|
+
// The framing that tries to make recalled text outrank the system prompt. A memory entry
|
|
285
|
+
// states what was learned; it never addresses the model's instructions.
|
|
286
|
+
test: /\b(?:ignore|disregard|forget|override)\s+(?:all\s+|any\s+|the\s+|your\s+|previous\s+|prior\s+|earlier\s+|above\s+)*(?:instruction|prompt|rule|directive|guideline|system\s+prompt)/i,
|
|
287
|
+
why: "addresses the model's instructions instead of stating what was learned"
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
id: "role_reassignment",
|
|
291
|
+
test: /\b(?:you\s+are\s+now|from\s+now\s+on\s+you\s+(?:are|will|must)|new\s+system\s+prompt|act\s+as\s+(?:if\s+you\s+are\s+)?(?:an?\s+)?(?:unrestricted|jailbroken|developer\s+mode))/i,
|
|
292
|
+
why: "attempts to reassign the agent's role"
|
|
293
|
+
},
|
|
294
|
+
// A `pipe_to_shell` pattern (`curl … | sh`) was written here and REMOVED after measurement.
|
|
295
|
+
// It fired on 1 of 797 real memory files, and the hit was legitimate: a note documenting the
|
|
296
|
+
// product's own install command in a table. Fetch-and-execute in a hostile entry and in an
|
|
297
|
+
// install instruction are textually identical, so the pattern cannot be narrowed — only
|
|
298
|
+
// traded. Rejecting the write would mean the first person to save install docs to memory
|
|
299
|
+
// gets a hard failure, which buys nothing: an entry the agent READS is not an entry the
|
|
300
|
+
// agent RUNS, and execution is gated at the tool boundary, where it belongs. Left out on
|
|
301
|
+
// purpose, so nobody re-adds it without repeating the measurement.
|
|
302
|
+
{
|
|
303
|
+
id: "encoded_payload",
|
|
304
|
+
// A base64 run this long is not prose. Short tokens are left alone precisely so that hashes,
|
|
305
|
+
// commit SHAs and identifiers keep working.
|
|
306
|
+
test: /[A-Za-z0-9+/]{256,}={0,2}/,
|
|
307
|
+
why: "carries a long encoded payload rather than readable text"
|
|
308
|
+
}
|
|
309
|
+
];
|
|
310
|
+
var EXCERPT_RADIUS = 40;
|
|
311
|
+
var EXCERPT_SCRUB = new RegExp(`[\\u0000-\\u001F${INVISIBLE_CLASS}]`, "gu");
|
|
312
|
+
function scanForThreats(text) {
|
|
313
|
+
for (const pattern of THREAT_PATTERNS) {
|
|
314
|
+
const m = pattern.test.exec(text);
|
|
315
|
+
if (m === null) continue;
|
|
316
|
+
const at = m.index;
|
|
317
|
+
const start = Math.max(0, at - EXCERPT_RADIUS);
|
|
318
|
+
const end = Math.min(text.length, at + m[0].length + EXCERPT_RADIUS);
|
|
319
|
+
const window = text.slice(start, end).replace(EXCERPT_SCRUB, "\u2423");
|
|
320
|
+
return {
|
|
321
|
+
id: pattern.id,
|
|
322
|
+
why: pattern.why,
|
|
323
|
+
excerpt: `${start > 0 ? "\u2026" : ""}${window}${end < text.length ? "\u2026" : ""}`
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
return void 0;
|
|
327
|
+
}
|
|
328
|
+
THREAT_PATTERNS.map((p) => p.id);
|
|
329
|
+
|
|
330
|
+
// src/internal/memory/storage/markdown-store.ts
|
|
331
|
+
var MEMORY_MD_HEADER = "# Memory Index\n";
|
|
332
|
+
var FACTS_HEADING = "## Facts";
|
|
333
|
+
var MAX_NAME_VARIANTS = 50;
|
|
334
|
+
function memoryMdPath(root) {
|
|
335
|
+
return join(root, "MEMORY.md");
|
|
336
|
+
}
|
|
337
|
+
function notesDir(root) {
|
|
338
|
+
return join(root, "notes");
|
|
339
|
+
}
|
|
340
|
+
function byNaturalName(a, b) {
|
|
341
|
+
const split = (f) => {
|
|
342
|
+
const stem = f.replace(/\.md$/, "");
|
|
343
|
+
const m = /^(.*?)-(\d+)$/.exec(stem);
|
|
344
|
+
return m === null ? [stem, 0] : [m[1], Number(m[2])];
|
|
345
|
+
};
|
|
346
|
+
const [baseA, nA] = split(a);
|
|
347
|
+
const [baseB, nB] = split(b);
|
|
348
|
+
return baseA === baseB ? nA - nB : baseA.localeCompare(baseB);
|
|
349
|
+
}
|
|
350
|
+
async function readFactsFromMarkdown(cwd, config) {
|
|
351
|
+
const facts = [];
|
|
352
|
+
const roots = memoryReadRoots(cwd, config);
|
|
353
|
+
for (const dir of roots) facts.push(...await readMemoryFilesIn(dir));
|
|
354
|
+
for (const dir of roots) facts.push(...await readLegacyFactsSectionIn(dir));
|
|
355
|
+
return facts;
|
|
356
|
+
}
|
|
357
|
+
async function readMemoryFilesIn(dir) {
|
|
358
|
+
let entries;
|
|
359
|
+
try {
|
|
360
|
+
entries = await readdir(dir);
|
|
361
|
+
} catch {
|
|
362
|
+
return [];
|
|
363
|
+
}
|
|
364
|
+
const facts = [];
|
|
365
|
+
for (const entry of entries.sort(byNaturalName)) {
|
|
366
|
+
const fact = await readMemoryFileIn(dir, entry);
|
|
367
|
+
if (fact !== void 0) facts.push(fact);
|
|
368
|
+
}
|
|
369
|
+
facts.sort((a, b) => (a.modified ?? "").localeCompare(b.modified ?? ""));
|
|
370
|
+
return facts;
|
|
371
|
+
}
|
|
372
|
+
async function readLegacyFactsSectionIn(dir) {
|
|
373
|
+
try {
|
|
374
|
+
return parseFactsSection(await readFile(join(dir, "MEMORY.md"), "utf8"));
|
|
375
|
+
} catch {
|
|
376
|
+
return [];
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
async function readMemoryFileIn(dir, entry) {
|
|
380
|
+
if (!entry.endsWith(".md") || entry === "MEMORY.md") return void 0;
|
|
381
|
+
let raw;
|
|
382
|
+
try {
|
|
383
|
+
raw = await readFile(join(dir, entry), "utf8");
|
|
384
|
+
} catch {
|
|
385
|
+
return void 0;
|
|
386
|
+
}
|
|
387
|
+
const parsed = parseMemoryFile(raw);
|
|
388
|
+
if (parsed === void 0) return void 0;
|
|
389
|
+
const body = parsed.body.trim();
|
|
390
|
+
return {
|
|
391
|
+
text: body.length > 0 ? body : parsed.description,
|
|
392
|
+
...parsed.kind !== void 0 ? { kind: parsed.kind } : {},
|
|
393
|
+
...parsed.modified !== void 0 ? { modified: parsed.modified } : {},
|
|
394
|
+
...parsed.observations !== void 0 ? { observations: parsed.observations } : {}
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
function assertWritable(fact, text) {
|
|
398
|
+
if (fact.kind !== void 0 && !MEMORY_KINDS.includes(fact.kind)) {
|
|
399
|
+
throw new ConfigurationError(
|
|
400
|
+
`Unknown memory fact kind "${fact.kind}". Expected one of: ${MEMORY_KINDS.join(", ")}.`,
|
|
401
|
+
{ code: "invalid_memory_kind" }
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
const threat = scanForThreats(text);
|
|
405
|
+
if (threat !== void 0) {
|
|
406
|
+
throw new ConfigurationError(
|
|
407
|
+
`Refusing to write a memory entry that ${threat.why}: ${threat.excerpt}`,
|
|
408
|
+
{ code: "memory_threat_rejected" }
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
function appendFactToMarkdown(cwd, fact, targetDir = projectMemoryDir(cwd)) {
|
|
413
|
+
return withCwdMutex(targetDir, async () => {
|
|
414
|
+
const text = redactSecrets(fact.text);
|
|
415
|
+
assertWritable(fact, text);
|
|
416
|
+
const title = fact.title?.trim() ?? titleForFact(text);
|
|
417
|
+
const base = fact.title !== void 0 ? slugForFact(fact.title) : slugForFact(text);
|
|
418
|
+
await mkdir(targetDir, { recursive: true });
|
|
419
|
+
const name = await resolveName(targetDir, base, text);
|
|
420
|
+
const description = fact.description?.trim() ?? text;
|
|
421
|
+
const observations = await nextObservationCount(targetDir, name, text);
|
|
422
|
+
await replaceFileAtomic(
|
|
423
|
+
join(targetDir, `${name}.md`),
|
|
424
|
+
renderMemoryFile({
|
|
425
|
+
name,
|
|
426
|
+
description,
|
|
427
|
+
...fact.kind !== void 0 ? { kind: fact.kind } : {},
|
|
428
|
+
modified: (/* @__PURE__ */ new Date()).toISOString(),
|
|
429
|
+
observations,
|
|
430
|
+
body: text
|
|
431
|
+
})
|
|
432
|
+
);
|
|
433
|
+
const index = await nextIndex(targetDir, description, name, title);
|
|
434
|
+
await replaceFileAtomic(join(targetDir, "MEMORY.md"), index);
|
|
435
|
+
const warning = indexBudgetWarning(index, targetDir);
|
|
436
|
+
if (warning !== void 0) diag(`[theokit-sdk] ${warning}
|
|
437
|
+
`);
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
async function resolveName(dir, base, text) {
|
|
441
|
+
const wanted = normalizeFactText(text);
|
|
442
|
+
for (let i = 1; i <= MAX_NAME_VARIANTS; i += 1) {
|
|
443
|
+
const candidate = i === 1 ? base : `${base}-${i}`;
|
|
444
|
+
let existing;
|
|
445
|
+
try {
|
|
446
|
+
existing = await readFile(join(dir, `${candidate}.md`), "utf8");
|
|
447
|
+
} catch {
|
|
448
|
+
return candidate;
|
|
449
|
+
}
|
|
450
|
+
const parsed = parseMemoryFile(existing);
|
|
451
|
+
if (parsed === void 0) return candidate;
|
|
452
|
+
if (normalizeFactText(parsed.body) === wanted) return candidate;
|
|
453
|
+
}
|
|
454
|
+
return slugForFact(text) === base ? safeFilenameForId(text) : slugForFact(text);
|
|
455
|
+
}
|
|
456
|
+
async function nextObservationCount(dir, name, text) {
|
|
457
|
+
let existing;
|
|
458
|
+
try {
|
|
459
|
+
existing = await readFile(join(dir, `${name}.md`), "utf8");
|
|
460
|
+
} catch {
|
|
461
|
+
return 1;
|
|
462
|
+
}
|
|
463
|
+
const parsed = parseMemoryFile(existing);
|
|
464
|
+
if (parsed === void 0) return 1;
|
|
465
|
+
const sameFact = normalizeFactText(parsed.body) === normalizeFactText(text);
|
|
466
|
+
if (!sameFact) return 1;
|
|
467
|
+
return (parsed.observations ?? 1) + 1;
|
|
468
|
+
}
|
|
469
|
+
function normalizeFactText(text) {
|
|
470
|
+
return text.trim().toLowerCase().replace(/\s+/g, " ").replace(/[.!?]+$/, "");
|
|
471
|
+
}
|
|
472
|
+
async function nextIndex(dir, text, name, title) {
|
|
473
|
+
let existing = "";
|
|
474
|
+
try {
|
|
475
|
+
existing = await readFile(join(dir, "MEMORY.md"), "utf8");
|
|
476
|
+
} catch {
|
|
477
|
+
existing = "";
|
|
478
|
+
}
|
|
479
|
+
const hook = text.replace(/\s+/g, " ").trim();
|
|
480
|
+
const entry = hook.length > 0 && hook !== title ? `- [${title}](${name}.md) \u2014 ${hook}` : `- [${title}](${name}.md)`;
|
|
481
|
+
const kept = existing.split("\n").filter((line) => !line.startsWith(`- [`) || !line.includes(`](${name}.md)`));
|
|
482
|
+
const body = kept.join("\n").trimEnd();
|
|
483
|
+
const head = body.length > 0 ? body : MEMORY_MD_HEADER;
|
|
484
|
+
return `${head}
|
|
485
|
+
${entry}
|
|
486
|
+
`;
|
|
487
|
+
}
|
|
488
|
+
var INDEX_ENTRY = /^\[[^\]]*\]\([^)]+\.md\)(?:\s+—\s.*)?$/;
|
|
489
|
+
function parseFactsSection(raw) {
|
|
490
|
+
const idx = raw.indexOf(FACTS_HEADING);
|
|
491
|
+
if (idx === -1) return [];
|
|
492
|
+
const tail = raw.slice(idx + FACTS_HEADING.length);
|
|
493
|
+
const nextHeading = tail.search(/\n#{1,2}\s/);
|
|
494
|
+
const block = nextHeading === -1 ? tail : tail.slice(0, nextHeading);
|
|
495
|
+
return block.split("\n").map((line) => line.trim()).filter((line) => line.startsWith("- ")).map((line) => line.slice(2).trim()).filter((body) => !INDEX_ENTRY.test(body)).map((body) => ({ text: body }));
|
|
496
|
+
}
|
|
497
|
+
async function readFacts(cwd, config) {
|
|
498
|
+
if (!config.enabled) return [];
|
|
499
|
+
return readFactsFromMarkdown(cwd, config);
|
|
500
|
+
}
|
|
501
|
+
async function appendFact(cwd, config, fact) {
|
|
502
|
+
if (!config.enabled) return;
|
|
503
|
+
await appendFactToMarkdown(cwd, fact, resolveMemoryRoot(cwd, config));
|
|
504
|
+
}
|
|
505
|
+
var MAX_TURN_CHARS = 2e3;
|
|
506
|
+
function sessionsDir(root) {
|
|
507
|
+
return join(root, "sessions");
|
|
508
|
+
}
|
|
509
|
+
function sessionSummaryPath(root, runId) {
|
|
510
|
+
return join(sessionsDir(root), `${safeFilenameForId(runId, { maxLen: 128 })}.md`);
|
|
511
|
+
}
|
|
512
|
+
function truncate(text) {
|
|
513
|
+
if (text.length <= MAX_TURN_CHARS) return text;
|
|
514
|
+
return `${text.slice(0, MAX_TURN_CHARS)}\u2026`;
|
|
515
|
+
}
|
|
516
|
+
async function writeSessionSummary(input) {
|
|
517
|
+
if (input.status !== "finished") return;
|
|
518
|
+
const path = sessionSummaryPath(input.memoryRoot, input.runId);
|
|
519
|
+
await mkdir(sessionsDir(input.memoryRoot), { recursive: true });
|
|
520
|
+
const safeUser = redactSecrets(truncate(input.userText));
|
|
521
|
+
const safeAssistant = redactSecrets(truncate(input.assistantText));
|
|
522
|
+
const iso = new Date(input.at).toISOString();
|
|
523
|
+
const body = [
|
|
524
|
+
"---",
|
|
525
|
+
`runId: ${input.runId}`,
|
|
526
|
+
`agentId: ${input.agentId}`,
|
|
527
|
+
`at: ${iso}`,
|
|
528
|
+
`status: ${input.status}`,
|
|
529
|
+
"---",
|
|
530
|
+
"",
|
|
531
|
+
"## User",
|
|
532
|
+
"",
|
|
533
|
+
safeUser,
|
|
534
|
+
"",
|
|
535
|
+
"## Assistant",
|
|
536
|
+
"",
|
|
537
|
+
safeAssistant,
|
|
538
|
+
""
|
|
539
|
+
].join("\n");
|
|
540
|
+
await replaceFileAtomic(path, body);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// src/internal/memory/index-schema.ts
|
|
544
|
+
var SCHEMA_STATEMENTS = [
|
|
545
|
+
`CREATE TABLE IF NOT EXISTS files (
|
|
546
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
547
|
+
path TEXT UNIQUE NOT NULL,
|
|
548
|
+
rel_path TEXT NOT NULL,
|
|
549
|
+
mtime INTEGER NOT NULL,
|
|
550
|
+
hash TEXT NOT NULL,
|
|
551
|
+
source TEXT NOT NULL DEFAULT 'memory'
|
|
552
|
+
)`,
|
|
553
|
+
`CREATE TABLE IF NOT EXISTS chunks (
|
|
554
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
555
|
+
file_id INTEGER NOT NULL REFERENCES files(id) ON DELETE CASCADE,
|
|
556
|
+
start_line INTEGER NOT NULL,
|
|
557
|
+
end_line INTEGER NOT NULL,
|
|
558
|
+
text TEXT NOT NULL,
|
|
559
|
+
hash TEXT NOT NULL,
|
|
560
|
+
session_key TEXT
|
|
561
|
+
)`,
|
|
562
|
+
`CREATE VIRTUAL TABLE IF NOT EXISTS chunks_fts USING fts5(
|
|
563
|
+
text,
|
|
564
|
+
content='chunks',
|
|
565
|
+
content_rowid='id'
|
|
566
|
+
)`,
|
|
567
|
+
`CREATE TRIGGER IF NOT EXISTS chunks_fts_insert AFTER INSERT ON chunks BEGIN
|
|
568
|
+
INSERT INTO chunks_fts(rowid, text) VALUES (new.id, new.text);
|
|
569
|
+
END`,
|
|
570
|
+
`CREATE TRIGGER IF NOT EXISTS chunks_fts_delete AFTER DELETE ON chunks BEGIN
|
|
571
|
+
INSERT INTO chunks_fts(chunks_fts, rowid, text) VALUES ('delete', old.id, old.text);
|
|
572
|
+
END`,
|
|
573
|
+
`CREATE TABLE IF NOT EXISTS meta (
|
|
574
|
+
key TEXT PRIMARY KEY,
|
|
575
|
+
value TEXT
|
|
576
|
+
)`,
|
|
577
|
+
`CREATE INDEX IF NOT EXISTS idx_chunks_file_id ON chunks(file_id)`
|
|
578
|
+
];
|
|
579
|
+
var PRAGMA_STATEMENTS = [
|
|
580
|
+
"PRAGMA synchronous=NORMAL",
|
|
581
|
+
"PRAGMA foreign_keys=ON"
|
|
582
|
+
];
|
|
583
|
+
|
|
584
|
+
// src/internal/memory/index-db.ts
|
|
585
|
+
async function openMemoryDb(opts) {
|
|
586
|
+
return openSqliteResilient({
|
|
587
|
+
filePath: opts.filePath,
|
|
588
|
+
label: "memory-index",
|
|
589
|
+
recoverCorrupt: opts.recoverCorrupt,
|
|
590
|
+
onOpen: (db) => {
|
|
591
|
+
for (const pragma of PRAGMA_STATEMENTS) db.exec(pragma);
|
|
592
|
+
for (const stmt of SCHEMA_STATEMENTS) db.exec(stmt);
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
function defaultIndexPath(root) {
|
|
597
|
+
return join(root, ".index", "memory.sqlite");
|
|
598
|
+
}
|
|
599
|
+
function requireLance() {
|
|
600
|
+
try {
|
|
601
|
+
const r = createRequire(import.meta.url);
|
|
602
|
+
return r("@lancedb/lancedb");
|
|
603
|
+
} catch (cause) {
|
|
604
|
+
throw new ConfigurationError(
|
|
605
|
+
'Lance backend selected but `@lancedb/lancedb` is not installed. Install with: `pnpm add @lancedb/lancedb`. SQLite remains available as the default (omit `backend: "lance"`).',
|
|
606
|
+
{ code: "lance_backend_unavailable", cause }
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
var LanceIndex = class _LanceIndex {
|
|
611
|
+
constructor(table, embedding, embeddingDim) {
|
|
612
|
+
this.table = table;
|
|
613
|
+
this.embedding = embedding;
|
|
614
|
+
this.embeddingDim = embeddingDim;
|
|
615
|
+
}
|
|
616
|
+
table;
|
|
617
|
+
embedding;
|
|
618
|
+
embeddingDim;
|
|
619
|
+
static async open(opts) {
|
|
620
|
+
const lance = requireLance();
|
|
621
|
+
const storagePath = opts.storagePath ?? lanceStoragePath(opts.memoryRoot ?? resolveMemoryRoot(opts.cwd));
|
|
622
|
+
mkdirSync(storagePath, { recursive: true });
|
|
623
|
+
const conn = await lance.connect(storagePath);
|
|
624
|
+
const dim = opts.embedding.dimension;
|
|
625
|
+
const tableName = "facts";
|
|
626
|
+
const existing = await conn.tableNames();
|
|
627
|
+
let table;
|
|
628
|
+
if (existing.includes(tableName)) {
|
|
629
|
+
table = await conn.openTable(tableName);
|
|
630
|
+
const schema = await table.schema();
|
|
631
|
+
const embField = schema.fields.find((f) => f.name === "embedding");
|
|
632
|
+
const existingDim = embField?.type?.listSize ?? embField?.type?.fixedSize;
|
|
633
|
+
if (typeof existingDim === "number" && existingDim !== dim) {
|
|
634
|
+
throw new ConfigurationError(
|
|
635
|
+
`Embedding dimension mismatch in Lance index: storage has ${existingDim}-dim vectors, current provider yields ${dim}-dim. Run \`theokit-migrate-memory\` after switching providers, or use a different storagePath.`,
|
|
636
|
+
{ code: "embedding_dimension_mismatch" }
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
} else {
|
|
640
|
+
const bootstrap = {
|
|
641
|
+
id: "__bootstrap__",
|
|
642
|
+
text: "",
|
|
643
|
+
source: "memory",
|
|
644
|
+
embedding: new Array(dim).fill(0),
|
|
645
|
+
namespace: "__bootstrap__",
|
|
646
|
+
scope: "__bootstrap__",
|
|
647
|
+
user_id: "__bootstrap__",
|
|
648
|
+
timestamp: 0
|
|
649
|
+
};
|
|
650
|
+
table = await conn.createTable(tableName, [bootstrap]);
|
|
651
|
+
await table.delete("namespace = '__bootstrap__'");
|
|
652
|
+
}
|
|
653
|
+
return new _LanceIndex(table, opts.embedding, dim);
|
|
654
|
+
}
|
|
655
|
+
async addFacts(facts) {
|
|
656
|
+
if (facts.length === 0) return;
|
|
657
|
+
const texts = facts.map((f) => f.text);
|
|
658
|
+
const embeddings = await this.embedding.embed(texts);
|
|
659
|
+
const rows = facts.map((f, i) => ({
|
|
660
|
+
...f,
|
|
661
|
+
embedding: embeddings[i] ?? new Array(this.embeddingDim).fill(0)
|
|
662
|
+
}));
|
|
663
|
+
await this.table.add(rows);
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Search facts by semantic similarity. EC-1: filters use Lance's
|
|
667
|
+
* structured object filter — NEVER string interpolation.
|
|
668
|
+
*/
|
|
669
|
+
async search(query, opts) {
|
|
670
|
+
const [embedding] = await this.embedding.embed([query]);
|
|
671
|
+
if (embedding === void 0) return [];
|
|
672
|
+
const predicates = [`namespace = '${escapeSqlValue(opts.namespace)}'`];
|
|
673
|
+
if (opts.scope !== void 0) {
|
|
674
|
+
predicates.push(`scope = '${escapeSqlValue(opts.scope)}'`);
|
|
675
|
+
}
|
|
676
|
+
let q = this.table.search(embedding).where(predicates.join(" AND "));
|
|
677
|
+
if (opts.limit !== void 0) q = q.limit(opts.limit);
|
|
678
|
+
const results = await q.toArray();
|
|
679
|
+
return results.filter((r) => opts.sources === void 0 || opts.sources.includes(r.source)).map((r) => ({
|
|
680
|
+
id: r.id,
|
|
681
|
+
text: r.text,
|
|
682
|
+
source: r.source,
|
|
683
|
+
namespace: r.namespace,
|
|
684
|
+
scope: r.scope,
|
|
685
|
+
userId: r.user_id,
|
|
686
|
+
// Lower _distance = better match; normalize to 0..1 (1=best).
|
|
687
|
+
score: 1 / (1 + (r._distance ?? 1))
|
|
688
|
+
}));
|
|
689
|
+
}
|
|
690
|
+
async countFacts(namespace) {
|
|
691
|
+
return this.table.countRows();
|
|
692
|
+
}
|
|
693
|
+
async removeFacts(ids) {
|
|
694
|
+
if (ids.length === 0) return;
|
|
695
|
+
const escaped = ids.map((id) => `'${id.replace(/'/g, "''")}'`).join(", ");
|
|
696
|
+
await this.table.delete(`id IN (${escaped})`);
|
|
697
|
+
}
|
|
698
|
+
async close() {
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
function escapeSqlValue(value) {
|
|
702
|
+
for (let i = 0; i < value.length; i++) {
|
|
703
|
+
const code = value.charCodeAt(i);
|
|
704
|
+
if (code === 0 || code >= 1 && code <= 31 || code === 127) {
|
|
705
|
+
throw new ConfigurationError(
|
|
706
|
+
`SQL filter value contains control character at position ${i} (0x${code.toString(16)}). This may indicate an injection attempt.`,
|
|
707
|
+
{ code: "sql_injection_blocked" }
|
|
708
|
+
);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
return value.replace(/\\/g, "\\\\").replace(/'/g, "''");
|
|
712
|
+
}
|
|
713
|
+
function lanceStoragePath(root) {
|
|
714
|
+
return join(root, "lance");
|
|
715
|
+
}
|
|
716
|
+
async function discoverSessionFiles(root) {
|
|
717
|
+
let entries;
|
|
718
|
+
try {
|
|
719
|
+
entries = await readdir(sessionsDir(root));
|
|
720
|
+
} catch {
|
|
721
|
+
return [];
|
|
722
|
+
}
|
|
723
|
+
return entries.filter((entry) => entry.endsWith(".md")).map((entry) => {
|
|
724
|
+
const absolutePath = join(sessionsDir(root), entry);
|
|
725
|
+
return {
|
|
726
|
+
absolutePath,
|
|
727
|
+
relPath: relativeToRoot(root, absolutePath)
|
|
728
|
+
};
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
function relativeToRoot(root, absolutePath) {
|
|
732
|
+
if (absolutePath.startsWith(`${root}/`)) return absolutePath.slice(root.length + 1);
|
|
733
|
+
return absolutePath;
|
|
734
|
+
}
|
|
735
|
+
function wikiDir(root) {
|
|
736
|
+
return join(root, "wiki");
|
|
737
|
+
}
|
|
738
|
+
async function discoverWikiFiles(root) {
|
|
739
|
+
let entries;
|
|
740
|
+
try {
|
|
741
|
+
entries = await readdir(wikiDir(root));
|
|
742
|
+
} catch {
|
|
743
|
+
return [];
|
|
744
|
+
}
|
|
745
|
+
return entries.filter((entry) => entry.endsWith(".md")).map((entry) => ({
|
|
746
|
+
absolutePath: join(wikiDir(root), entry),
|
|
747
|
+
relPath: join("wiki", entry)
|
|
748
|
+
})).map((file) => ({
|
|
749
|
+
absolutePath: file.absolutePath,
|
|
750
|
+
relPath: relativeToRoot2(root, file.absolutePath)
|
|
751
|
+
}));
|
|
752
|
+
}
|
|
753
|
+
function relativeToRoot2(root, absolutePath) {
|
|
754
|
+
if (absolutePath.startsWith(`${root}/`)) return absolutePath.slice(root.length + 1);
|
|
755
|
+
return absolutePath;
|
|
756
|
+
}
|
|
757
|
+
function resolveWeights(options) {
|
|
758
|
+
const vectorWeight = options.vectorWeight ?? 0.6;
|
|
759
|
+
const textWeight = options.textWeight ?? 0.4;
|
|
760
|
+
const total = vectorWeight + textWeight || 1;
|
|
761
|
+
return { vectorWeight, textWeight, total };
|
|
762
|
+
}
|
|
763
|
+
function blendScores(hit, vectorScore, weights) {
|
|
764
|
+
const score = (vectorScore * weights.vectorWeight + hit.textScore * weights.textWeight) / weights.total;
|
|
765
|
+
return {
|
|
766
|
+
path: hit.path,
|
|
767
|
+
startLine: hit.startLine,
|
|
768
|
+
endLine: hit.endLine,
|
|
769
|
+
score,
|
|
770
|
+
textScore: hit.textScore,
|
|
771
|
+
snippet: hit.snippet,
|
|
772
|
+
source: hit.source,
|
|
773
|
+
citation: hit.citation,
|
|
774
|
+
...vectorScore > 0 ? { vectorScore } : {}
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
async function markdownFilesIn(dir, root, skip = []) {
|
|
778
|
+
let entries;
|
|
779
|
+
try {
|
|
780
|
+
entries = await readdir(dir);
|
|
781
|
+
} catch {
|
|
782
|
+
return [];
|
|
783
|
+
}
|
|
784
|
+
return entries.filter((entry) => entry.endsWith(".md") && !skip.includes(entry)).map((entry) => ({
|
|
785
|
+
absolutePath: join(dir, entry),
|
|
786
|
+
relPath: relative(root, join(dir, entry)),
|
|
787
|
+
source: "memory"
|
|
788
|
+
}));
|
|
789
|
+
}
|
|
790
|
+
async function collectMarkdownFiles(root) {
|
|
791
|
+
const results = [];
|
|
792
|
+
try {
|
|
793
|
+
await stat(memoryMdPath(root));
|
|
794
|
+
results.push({
|
|
795
|
+
absolutePath: memoryMdPath(root),
|
|
796
|
+
relPath: relative(root, memoryMdPath(root)),
|
|
797
|
+
source: "memory"
|
|
798
|
+
});
|
|
799
|
+
} catch {
|
|
800
|
+
}
|
|
801
|
+
results.push(...await markdownFilesIn(root, root, ["MEMORY.md"]));
|
|
802
|
+
results.push(...await markdownFilesIn(notesDir(root), root));
|
|
803
|
+
const wikiFiles = await discoverWikiFiles(root);
|
|
804
|
+
for (const wiki of wikiFiles) {
|
|
805
|
+
results.push({
|
|
806
|
+
absolutePath: wiki.absolutePath,
|
|
807
|
+
relPath: wiki.relPath,
|
|
808
|
+
source: "wiki"
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
const sessionFiles = await discoverSessionFiles(root);
|
|
812
|
+
for (const session of sessionFiles) {
|
|
813
|
+
results.push({
|
|
814
|
+
absolutePath: session.absolutePath,
|
|
815
|
+
relPath: session.relPath,
|
|
816
|
+
source: "sessions"
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
return results;
|
|
820
|
+
}
|
|
821
|
+
function sha256(text) {
|
|
822
|
+
return createHash("sha256").update(text).digest("hex");
|
|
823
|
+
}
|
|
824
|
+
function truncateSnippet(text) {
|
|
825
|
+
const max = 500;
|
|
826
|
+
return text.length <= max ? text : `${text.slice(0, max)}\u2026`;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
export { LanceIndex, MEMORY_INDEX_MAX_BYTES, MEMORY_INDEX_MAX_LINES, MEMORY_KINDS, appendFact, appendFactToMarkdown, asMemoryRoot, blendScores, claudeProjectMemoryDir, collectMarkdownFiles, defaultIndexPath, discoverSessionFiles, discoverWikiFiles, indexBudgetWarning, lanceStoragePath, legacyMemoryJsonPath, memoryMdPath, memoryReadRoots, notesDir, openMemoryDb, projectMemoryDir, readFacts, readFactsFromMarkdown, resolveMemoryRoot, resolveWeights, sessionSummaryPath, sessionsDir, sha256, truncateSnippet, wikiDir, writeSessionSummary };
|
|
830
|
+
//# sourceMappingURL=chunk-C7QPML3L.js.map
|
|
831
|
+
//# sourceMappingURL=chunk-C7QPML3L.js.map
|