ahok-skill 1.3.1

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 (141) hide show
  1. package/.prettierrc +8 -0
  2. package/Dockerfile +59 -0
  3. package/RAW_SKILL.md +219 -0
  4. package/README.md +277 -0
  5. package/SKILL.md +58 -0
  6. package/bin/opm.js +268 -0
  7. package/data/openmemory.sqlite +0 -0
  8. package/data/openmemory.sqlite-shm +0 -0
  9. package/data/openmemory.sqlite-wal +0 -0
  10. package/dist/ai/graph.js +293 -0
  11. package/dist/ai/mcp.js +397 -0
  12. package/dist/cli.js +78 -0
  13. package/dist/core/cfg.js +87 -0
  14. package/dist/core/db.js +636 -0
  15. package/dist/core/memory.js +116 -0
  16. package/dist/core/migrate.js +227 -0
  17. package/dist/core/models.js +105 -0
  18. package/dist/core/telemetry.js +57 -0
  19. package/dist/core/types.js +2 -0
  20. package/dist/core/vector/postgres.js +52 -0
  21. package/dist/core/vector/valkey.js +246 -0
  22. package/dist/core/vector_store.js +2 -0
  23. package/dist/index.js +44 -0
  24. package/dist/memory/decay.js +301 -0
  25. package/dist/memory/embed.js +675 -0
  26. package/dist/memory/hsg.js +959 -0
  27. package/dist/memory/reflect.js +131 -0
  28. package/dist/memory/user_summary.js +99 -0
  29. package/dist/migrate.js +9 -0
  30. package/dist/ops/compress.js +255 -0
  31. package/dist/ops/dynamics.js +189 -0
  32. package/dist/ops/extract.js +333 -0
  33. package/dist/ops/ingest.js +214 -0
  34. package/dist/server/index.js +109 -0
  35. package/dist/server/middleware/auth.js +137 -0
  36. package/dist/server/routes/auth.js +186 -0
  37. package/dist/server/routes/compression.js +108 -0
  38. package/dist/server/routes/dashboard.js +399 -0
  39. package/dist/server/routes/docs.js +241 -0
  40. package/dist/server/routes/dynamics.js +312 -0
  41. package/dist/server/routes/ide.js +280 -0
  42. package/dist/server/routes/index.js +33 -0
  43. package/dist/server/routes/keys.js +132 -0
  44. package/dist/server/routes/langgraph.js +61 -0
  45. package/dist/server/routes/memory.js +213 -0
  46. package/dist/server/routes/sources.js +140 -0
  47. package/dist/server/routes/system.js +63 -0
  48. package/dist/server/routes/temporal.js +293 -0
  49. package/dist/server/routes/users.js +101 -0
  50. package/dist/server/routes/vercel.js +57 -0
  51. package/dist/server/server.js +211 -0
  52. package/dist/server.js +3 -0
  53. package/dist/sources/base.js +223 -0
  54. package/dist/sources/github.js +171 -0
  55. package/dist/sources/google_drive.js +166 -0
  56. package/dist/sources/google_sheets.js +112 -0
  57. package/dist/sources/google_slides.js +139 -0
  58. package/dist/sources/index.js +34 -0
  59. package/dist/sources/notion.js +165 -0
  60. package/dist/sources/onedrive.js +143 -0
  61. package/dist/sources/web_crawler.js +166 -0
  62. package/dist/temporal_graph/index.js +20 -0
  63. package/dist/temporal_graph/query.js +240 -0
  64. package/dist/temporal_graph/store.js +116 -0
  65. package/dist/temporal_graph/timeline.js +241 -0
  66. package/dist/temporal_graph/types.js +2 -0
  67. package/dist/utils/chunking.js +60 -0
  68. package/dist/utils/index.js +31 -0
  69. package/dist/utils/keyword.js +94 -0
  70. package/dist/utils/text.js +120 -0
  71. package/nodemon.json +7 -0
  72. package/package.json +50 -0
  73. package/references/api_reference.md +66 -0
  74. package/references/examples.md +45 -0
  75. package/src/ai/graph.ts +363 -0
  76. package/src/ai/mcp.ts +494 -0
  77. package/src/cli.ts +94 -0
  78. package/src/core/cfg.ts +110 -0
  79. package/src/core/db.ts +1052 -0
  80. package/src/core/memory.ts +99 -0
  81. package/src/core/migrate.ts +302 -0
  82. package/src/core/models.ts +107 -0
  83. package/src/core/telemetry.ts +47 -0
  84. package/src/core/types.ts +130 -0
  85. package/src/core/vector/postgres.ts +61 -0
  86. package/src/core/vector/valkey.ts +261 -0
  87. package/src/core/vector_store.ts +9 -0
  88. package/src/index.ts +5 -0
  89. package/src/memory/decay.ts +427 -0
  90. package/src/memory/embed.ts +707 -0
  91. package/src/memory/hsg.ts +1245 -0
  92. package/src/memory/reflect.ts +158 -0
  93. package/src/memory/user_summary.ts +110 -0
  94. package/src/migrate.ts +8 -0
  95. package/src/ops/compress.ts +296 -0
  96. package/src/ops/dynamics.ts +272 -0
  97. package/src/ops/extract.ts +360 -0
  98. package/src/ops/ingest.ts +286 -0
  99. package/src/server/index.ts +159 -0
  100. package/src/server/middleware/auth.ts +156 -0
  101. package/src/server/routes/auth.ts +223 -0
  102. package/src/server/routes/compression.ts +106 -0
  103. package/src/server/routes/dashboard.ts +420 -0
  104. package/src/server/routes/docs.ts +380 -0
  105. package/src/server/routes/dynamics.ts +516 -0
  106. package/src/server/routes/ide.ts +283 -0
  107. package/src/server/routes/index.ts +32 -0
  108. package/src/server/routes/keys.ts +131 -0
  109. package/src/server/routes/langgraph.ts +71 -0
  110. package/src/server/routes/memory.ts +440 -0
  111. package/src/server/routes/sources.ts +111 -0
  112. package/src/server/routes/system.ts +68 -0
  113. package/src/server/routes/temporal.ts +335 -0
  114. package/src/server/routes/users.ts +111 -0
  115. package/src/server/routes/vercel.ts +55 -0
  116. package/src/server/server.js +215 -0
  117. package/src/server.ts +1 -0
  118. package/src/sources/base.ts +257 -0
  119. package/src/sources/github.ts +156 -0
  120. package/src/sources/google_drive.ts +144 -0
  121. package/src/sources/google_sheets.ts +85 -0
  122. package/src/sources/google_slides.ts +115 -0
  123. package/src/sources/index.ts +19 -0
  124. package/src/sources/notion.ts +148 -0
  125. package/src/sources/onedrive.ts +131 -0
  126. package/src/sources/web_crawler.ts +161 -0
  127. package/src/temporal_graph/index.ts +4 -0
  128. package/src/temporal_graph/query.ts +299 -0
  129. package/src/temporal_graph/store.ts +156 -0
  130. package/src/temporal_graph/timeline.ts +319 -0
  131. package/src/temporal_graph/types.ts +41 -0
  132. package/src/utils/chunking.ts +66 -0
  133. package/src/utils/index.ts +25 -0
  134. package/src/utils/keyword.ts +137 -0
  135. package/src/utils/text.ts +115 -0
  136. package/tests/test_api_workspace_management.ts +413 -0
  137. package/tests/test_bulk_delete.ts +267 -0
  138. package/tests/test_omnibus.ts +166 -0
  139. package/tests/test_workspace_management.ts +278 -0
  140. package/tests/verify.ts +104 -0
  141. package/tsconfig.json +15 -0
package/src/ai/mcp.ts ADDED
@@ -0,0 +1,494 @@
1
+ import type { IncomingMessage, ServerResponse } from "http";
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import { z } from "zod";
6
+ import { env } from "../core/cfg";
7
+ import {
8
+ add_hsg_memory,
9
+ hsg_query,
10
+ reinforce_memory,
11
+ sector_configs,
12
+ } from "../memory/hsg";
13
+ import { q, all_async, memories_table, vector_store } from "../core/db";
14
+ import { getEmbeddingInfo } from "../memory/embed";
15
+ import { j, p } from "../utils";
16
+ import type { sector_type, mem_row, rpc_err_code } from "../core/types";
17
+ import { update_user_summary } from "../memory/user_summary";
18
+
19
+ const sec_enum = z.enum([
20
+ "episodic",
21
+ "semantic",
22
+ "procedural",
23
+ "emotional",
24
+ "reflective",
25
+ ] as const);
26
+
27
+ const trunc = (val: string, max = 200) =>
28
+ val.length <= max ? val : `${val.slice(0, max).trimEnd()}...`;
29
+
30
+ const build_mem_snap = (row: mem_row) => ({
31
+ id: row.id,
32
+ primary_sector: row.primary_sector,
33
+ salience: Number(row.salience.toFixed(3)),
34
+ last_seen_at: row.last_seen_at,
35
+ user_id: row.user_id,
36
+ content_preview: trunc(row.content, 240),
37
+ });
38
+
39
+ const fmt_matches = (matches: Awaited<ReturnType<typeof hsg_query>>) =>
40
+ matches
41
+ .map((m: any, idx: any) => {
42
+ const prev = trunc(m.content.replace(/\s+/g, " ").trim(), 200);
43
+ return `${idx + 1}. [${m.primary_sector}] score=${m.score.toFixed(3)} salience=${m.salience.toFixed(3)} id=${m.id}\n${prev}`;
44
+ })
45
+ .join("\n\n");
46
+
47
+ const set_hdrs = (res: ServerResponse) => {
48
+ res.setHeader("Content-Type", "application/json");
49
+ res.setHeader("Access-Control-Allow-Origin", "*");
50
+ res.setHeader("Access-Control-Allow-Methods", "POST,OPTIONS");
51
+ res.setHeader(
52
+ "Access-Control-Allow-Headers",
53
+ "Content-Type,Authorization,Mcp-Session-Id",
54
+ );
55
+ };
56
+
57
+ const send_err = (
58
+ res: ServerResponse,
59
+ code: rpc_err_code,
60
+ msg: string,
61
+ id: number | string | null = null,
62
+ status = 400,
63
+ ) => {
64
+ if (!res.headersSent) {
65
+ res.statusCode = status;
66
+ set_hdrs(res);
67
+ res.end(
68
+ JSON.stringify({
69
+ jsonrpc: "2.0",
70
+ error: { code, message: msg },
71
+ id,
72
+ }),
73
+ );
74
+ }
75
+ };
76
+
77
+ const uid = (val?: string | null) => (val?.trim() ? val.trim() : undefined);
78
+
79
+ export const create_mcp_srv = () => {
80
+ const srv = new McpServer(
81
+ {
82
+ name: "openmemory-mcp",
83
+ version: "2.1.0",
84
+ },
85
+ { capabilities: { tools: {}, resources: {}, logging: {} } },
86
+ );
87
+
88
+ srv.tool(
89
+ "openmemory_query",
90
+ "Run a semantic retrieval against OpenMemory",
91
+ {
92
+ query: z
93
+ .string()
94
+ .min(1, "query text is required")
95
+ .describe("Free-form search text"),
96
+ k: z
97
+ .number()
98
+ .int()
99
+ .min(1)
100
+ .max(32)
101
+ .default(8)
102
+ .describe("Maximum results to return"),
103
+ sector: sec_enum
104
+ .optional()
105
+ .describe("Restrict search to a specific sector"),
106
+ min_salience: z
107
+ .number()
108
+ .min(0)
109
+ .max(1)
110
+ .optional()
111
+ .describe("Minimum salience threshold"),
112
+ user_id: z
113
+ .string()
114
+ .trim()
115
+ .min(1)
116
+ .optional()
117
+ .describe("Isolate results to a specific user identifier"),
118
+ },
119
+ async ({ query, k, sector, min_salience, user_id }) => {
120
+ const u = uid(user_id);
121
+ const flt =
122
+ sector || min_salience !== undefined || u
123
+ ? {
124
+ ...(sector
125
+ ? { sectors: [sector as sector_type] }
126
+ : {}),
127
+ ...(min_salience !== undefined
128
+ ? { minSalience: min_salience }
129
+ : {}),
130
+ ...(u ? { user_id: u } : {}),
131
+ }
132
+ : undefined;
133
+ const matches = await hsg_query(query, k ?? 8, flt);
134
+ const summ = matches.length
135
+ ? fmt_matches(matches)
136
+ : "No memories matched the supplied query.";
137
+ const pay = matches.map((m: any) => ({
138
+ id: m.id,
139
+ score: Number(m.score.toFixed(4)),
140
+ primary_sector: m.primary_sector,
141
+ sectors: m.sectors,
142
+ salience: Number(m.salience.toFixed(4)),
143
+ last_seen_at: m.last_seen_at,
144
+ path: m.path,
145
+ content: m.content,
146
+ }));
147
+ return {
148
+ content: [
149
+ { type: "text", text: summ },
150
+ {
151
+ type: "text",
152
+ text: JSON.stringify({ query, matches: pay }, null, 2),
153
+ },
154
+ ],
155
+ };
156
+ },
157
+ );
158
+
159
+ srv.tool(
160
+ "openmemory_store",
161
+ "Persist new content into OpenMemory",
162
+ {
163
+ content: z.string().min(1).describe("Raw memory text to store"),
164
+ tags: z.array(z.string()).optional().describe("Optional tag list"),
165
+ metadata: z
166
+ .record(z.any())
167
+ .optional()
168
+ .describe("Arbitrary metadata blob"),
169
+ user_id: z
170
+ .string()
171
+ .trim()
172
+ .min(1)
173
+ .optional()
174
+ .describe(
175
+ "Associate the memory with a specific user identifier",
176
+ ),
177
+ },
178
+ async ({ content, tags, metadata, user_id }) => {
179
+ const u = uid(user_id);
180
+ const res = await add_hsg_memory(
181
+ content,
182
+ j(tags || []),
183
+ metadata,
184
+ u,
185
+ );
186
+ if (u)
187
+ update_user_summary(u).catch((err) =>
188
+ console.error("[MCP] user summary update failed:", err),
189
+ );
190
+ const txt = `Stored memory ${res.id} (primary=${res.primary_sector}) across sectors: ${res.sectors.join(", ")}${u ? ` [user=${u}]` : ""}`;
191
+ const payload = {
192
+ id: res.id,
193
+ primary_sector: res.primary_sector,
194
+ sectors: res.sectors,
195
+ user_id: u ?? null,
196
+ };
197
+ return {
198
+ content: [
199
+ { type: "text", text: txt },
200
+ { type: "text", text: JSON.stringify(payload, null, 2) },
201
+ ],
202
+ };
203
+ },
204
+ );
205
+
206
+ srv.tool(
207
+ "openmemory_reinforce",
208
+ "Boost salience for an existing memory",
209
+ {
210
+ id: z.string().min(1).describe("Memory identifier to reinforce"),
211
+ boost: z
212
+ .number()
213
+ .min(0.01)
214
+ .max(1)
215
+ .default(0.1)
216
+ .describe("Salience boost amount (default 0.1)"),
217
+ },
218
+ async ({ id, boost }) => {
219
+ await reinforce_memory(id, boost);
220
+ return {
221
+ content: [
222
+ {
223
+ type: "text",
224
+ text: `Reinforced memory ${id} by ${boost}`,
225
+ },
226
+ ],
227
+ };
228
+ },
229
+ );
230
+
231
+ srv.tool(
232
+ "openmemory_list",
233
+ "List recent memories for quick inspection",
234
+ {
235
+ limit: z
236
+ .number()
237
+ .int()
238
+ .min(1)
239
+ .max(50)
240
+ .default(10)
241
+ .describe("Number of memories to return"),
242
+ sector: sec_enum
243
+ .optional()
244
+ .describe("Optionally limit to a sector"),
245
+ user_id: z
246
+ .string()
247
+ .trim()
248
+ .min(1)
249
+ .optional()
250
+ .describe("Restrict results to a specific user identifier"),
251
+ },
252
+ async ({ limit, sector, user_id }) => {
253
+ const u = uid(user_id);
254
+ let rows: mem_row[];
255
+ if (u) {
256
+ const all = await q.all_mem_by_user.all(u, limit ?? 10, 0);
257
+ rows = sector
258
+ ? all.filter((row) => row.primary_sector === sector)
259
+ : all;
260
+ } else {
261
+ rows = sector
262
+ ? await q.all_mem_by_sector.all(sector, limit ?? 10, 0)
263
+ : await q.all_mem.all(limit ?? 10, 0);
264
+ }
265
+ const items = rows.map((row) => ({
266
+ ...build_mem_snap(row),
267
+ tags: p(row.tags || "[]") as string[],
268
+ metadata: p(row.meta || "{}") as Record<string, unknown>,
269
+ }));
270
+ const lns = items.map(
271
+ (item, idx) =>
272
+ `${idx + 1}. [${item.primary_sector}] salience=${item.salience} id=${item.id}${item.tags.length ? ` tags=${item.tags.join(", ")}` : ""}${item.user_id ? ` user=${item.user_id}` : ""}\n${item.content_preview}`,
273
+ );
274
+ return {
275
+ content: [
276
+ {
277
+ type: "text",
278
+ text: lns.join("\n\n") || "No memories stored yet.",
279
+ },
280
+ { type: "text", text: JSON.stringify({ items }, null, 2) },
281
+ ],
282
+ };
283
+ },
284
+ );
285
+
286
+ srv.tool(
287
+ "openmemory_get",
288
+ "Fetch a single memory by identifier",
289
+ {
290
+ id: z.string().min(1).describe("Memory identifier to load"),
291
+ include_vectors: z
292
+ .boolean()
293
+ .default(false)
294
+ .describe("Include sector vector metadata"),
295
+ user_id: z
296
+ .string()
297
+ .trim()
298
+ .min(1)
299
+ .optional()
300
+ .describe(
301
+ "Validate ownership against a specific user identifier",
302
+ ),
303
+ },
304
+ async ({ id, include_vectors, user_id }) => {
305
+ const u = uid(user_id);
306
+ const mem = await q.get_mem.get(id);
307
+ if (!mem)
308
+ return {
309
+ content: [
310
+ { type: "text", text: `Memory ${id} not found.` },
311
+ ],
312
+ };
313
+ if (u && mem.user_id !== u)
314
+ return {
315
+ content: [
316
+ {
317
+ type: "text",
318
+ text: `Memory ${id} not found for user ${u}.`,
319
+ },
320
+ ],
321
+ };
322
+ const vecs = include_vectors
323
+ ? await vector_store.getVectorsById(id)
324
+ : [];
325
+ const pay = {
326
+ id: mem.id,
327
+ content: mem.content,
328
+ primary_sector: mem.primary_sector,
329
+ salience: mem.salience,
330
+ decay_lambda: mem.decay_lambda,
331
+ created_at: mem.created_at,
332
+ updated_at: mem.updated_at,
333
+ last_seen_at: mem.last_seen_at,
334
+ user_id: mem.user_id,
335
+ tags: p(mem.tags || "[]"),
336
+ metadata: p(mem.meta || "{}"),
337
+ sectors: include_vectors
338
+ ? vecs.map((v) => v.sector)
339
+ : undefined,
340
+ };
341
+ return {
342
+ content: [{ type: "text", text: JSON.stringify(pay, null, 2) }],
343
+ };
344
+ },
345
+ );
346
+
347
+ srv.resource(
348
+ "openmemory-config",
349
+ "openmemory://config",
350
+ {
351
+ mimeType: "application/json",
352
+ description:
353
+ "Runtime configuration snapshot for the OpenMemory MCP server",
354
+ },
355
+ async () => {
356
+ const stats = await all_async(
357
+ `select primary_sector as sector, count(*) as count, avg(salience) as avg_salience from ${memories_table} group by primary_sector`,
358
+ );
359
+ const pay = {
360
+ mode: env.mode,
361
+ sectors: sector_configs,
362
+ stats,
363
+ embeddings: getEmbeddingInfo(),
364
+ server: { version: "2.1.0", protocol: "2025-06-18" },
365
+ available_tools: [
366
+ "openmemory_query",
367
+ "openmemory_store",
368
+ "openmemory_reinforce",
369
+ "openmemory_list",
370
+ "openmemory_get",
371
+ ],
372
+ };
373
+ return {
374
+ contents: [
375
+ {
376
+ uri: "openmemory://config",
377
+ text: JSON.stringify(pay, null, 2),
378
+ },
379
+ ],
380
+ };
381
+ },
382
+ );
383
+
384
+ srv.server.oninitialized = () => {
385
+ // Use stderr for debug output, not stdout
386
+ console.error(
387
+ "[MCP] initialization completed with client:",
388
+ srv.server.getClientVersion(),
389
+ );
390
+ };
391
+ return srv;
392
+ };
393
+
394
+ const extract_pay = async (req: IncomingMessage & { body?: any }) => {
395
+ if (req.body !== undefined) {
396
+ if (typeof req.body === "string") {
397
+ if (!req.body.trim()) return undefined;
398
+ return JSON.parse(req.body);
399
+ }
400
+ if (typeof req.body === "object" && req.body !== null) return req.body;
401
+ return undefined;
402
+ }
403
+ const raw = await new Promise<string>((resolve, reject) => {
404
+ let buf = "";
405
+ req.on("data", (chunk) => {
406
+ buf += chunk;
407
+ });
408
+ req.on("end", () => resolve(buf));
409
+ req.on("error", reject);
410
+ });
411
+ if (!raw.trim()) return undefined;
412
+ return JSON.parse(raw);
413
+ };
414
+
415
+ export const mcp = (app: any) => {
416
+ const srv = create_mcp_srv();
417
+ const trans = new StreamableHTTPServerTransport({
418
+ sessionIdGenerator: undefined,
419
+ enableJsonResponse: true,
420
+ });
421
+ const srv_ready = srv
422
+ .connect(trans)
423
+ .then(() => {
424
+ console.error("[MCP] Server started and transport connected");
425
+ })
426
+ .catch((error) => {
427
+ console.error("[MCP] Failed to initialize transport:", error);
428
+ throw error;
429
+ });
430
+
431
+ const handle_req = async (req: any, res: any) => {
432
+ try {
433
+ await srv_ready;
434
+ const pay = await extract_pay(req);
435
+ if (!pay || typeof pay !== "object") {
436
+ send_err(res, -32600, "Request body must be a JSON object");
437
+ return;
438
+ }
439
+ console.error("[MCP] Incoming request:", JSON.stringify(pay));
440
+ set_hdrs(res);
441
+ await trans.handleRequest(req, res, pay);
442
+ } catch (error) {
443
+ console.error("[MCP] Error handling request:", error);
444
+ if (error instanceof SyntaxError) {
445
+ send_err(res, -32600, "Invalid JSON payload");
446
+ return;
447
+ }
448
+ if (!res.headersSent)
449
+ send_err(
450
+ res,
451
+ -32603,
452
+ "Internal server error",
453
+ (error as any)?.id ?? null,
454
+ 500,
455
+ );
456
+ }
457
+ };
458
+
459
+ app.post("/mcp", (req: any, res: any) => {
460
+ void handle_req(req, res);
461
+ });
462
+ app.options("/mcp", (_req: any, res: any) => {
463
+ res.statusCode = 204;
464
+ set_hdrs(res);
465
+ res.end();
466
+ });
467
+
468
+ const method_not_allowed = (_req: IncomingMessage, res: ServerResponse) => {
469
+ send_err(
470
+ res,
471
+ -32600,
472
+ "Method not supported. Use POST /mcp with JSON payload.",
473
+ null,
474
+ 405,
475
+ );
476
+ };
477
+ app.get("/mcp", method_not_allowed);
478
+ app.delete("/mcp", method_not_allowed);
479
+ app.put("/mcp", method_not_allowed);
480
+ };
481
+
482
+ export const start_mcp_stdio = async () => {
483
+ const srv = create_mcp_srv();
484
+ const trans = new StdioServerTransport();
485
+ await srv.connect(trans);
486
+ // console.error("[MCP] STDIO transport connected"); // Use stderr for debug output, not stdout
487
+ };
488
+
489
+ if (typeof require !== "undefined" && require.main === module) {
490
+ void start_mcp_stdio().catch((error) => {
491
+ console.error("[MCP] STDIO startup failed:", error);
492
+ process.exitCode = 1;
493
+ });
494
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env node
2
+ import { run_async, all_async } from "./core/db";
3
+
4
+ const SCHEMA_DEFINITIONS = {
5
+ memories: `create table if not exists memories(id text primary key,user_id text,segment integer default 0,content text not null,simhash text,primary_sector text not null,tags text,meta text,created_at integer,updated_at integer,last_seen_at integer,salience real,decay_lambda real,version integer default 1,mean_dim integer,mean_vec blob,compressed_vec blob,feedback_score real default 0)`,
6
+ vectors: `create table if not exists vectors(id text not null,sector text not null,user_id text,v blob not null,dim integer not null,primary key(id,sector))`,
7
+ waypoints: `create table if not exists waypoints(src_id text,dst_id text not null,user_id text,weight real not null,created_at integer,updated_at integer,primary key(src_id,user_id))`,
8
+ embed_logs: `create table if not exists embed_logs(id text primary key,model text,status text,ts integer,err text)`,
9
+ users: `create table if not exists users(user_id text primary key,summary text,reflection_count integer default 0,created_at integer,updated_at integer)`,
10
+ stats: `create table if not exists stats(id integer primary key autoincrement,type text not null,count integer default 1,ts integer not null)`,
11
+ temporal_facts: `create table if not exists temporal_facts(id text primary key,subject text not null,predicate text not null,object text not null,valid_from integer not null,valid_to integer,confidence real not null check(confidence >= 0 and confidence <= 1),last_updated integer not null,metadata text,unique(subject,predicate,object,valid_from))`,
12
+ temporal_edges: `create table if not exists temporal_edges(id text primary key,source_id text not null,target_id text not null,relation_type text not null,valid_from integer not null,valid_to integer,weight real not null,metadata text,foreign key(source_id) references temporal_facts(id),foreign key(target_id) references temporal_facts(id))`,
13
+ };
14
+
15
+ const INDEX_DEFINITIONS = [
16
+ "create index if not exists idx_memories_sector on memories(primary_sector)",
17
+ "create index if not exists idx_memories_segment on memories(segment)",
18
+ "create index if not exists idx_memories_simhash on memories(simhash)",
19
+ "create index if not exists idx_memories_ts on memories(last_seen_at)",
20
+ "create index if not exists idx_memories_user on memories(user_id)",
21
+ "create index if not exists idx_vectors_user on vectors(user_id)",
22
+ "create index if not exists idx_waypoints_src on waypoints(src_id)",
23
+ "create index if not exists idx_waypoints_dst on waypoints(dst_id)",
24
+ "create index if not exists idx_waypoints_user on waypoints(user_id)",
25
+ "create index if not exists idx_stats_ts on stats(ts)",
26
+ "create index if not exists idx_stats_type on stats(type)",
27
+ "create index if not exists idx_temporal_subject on temporal_facts(subject)",
28
+ "create index if not exists idx_temporal_predicate on temporal_facts(predicate)",
29
+ "create index if not exists idx_temporal_validity on temporal_facts(valid_from,valid_to)",
30
+ "create index if not exists idx_temporal_composite on temporal_facts(subject,predicate,valid_from,valid_to)",
31
+ "create index if not exists idx_edges_source on temporal_edges(source_id)",
32
+ "create index if not exists idx_edges_target on temporal_edges(target_id)",
33
+ "create index if not exists idx_edges_validity on temporal_edges(valid_from,valid_to)",
34
+ ];
35
+
36
+ async function get_existing_tables(): Promise<Set<string>> {
37
+ const tables = await all_async(
38
+ `SELECT name FROM sqlite_master WHERE type='table'`,
39
+ );
40
+ return new Set(tables.map((t) => t.name));
41
+ }
42
+
43
+ async function get_existing_indexes(): Promise<Set<string>> {
44
+ const indexes = await all_async(
45
+ `SELECT name FROM sqlite_master WHERE type='index' AND name NOT LIKE 'sqlite_%'`,
46
+ );
47
+ return new Set(indexes.map((i) => i.name));
48
+ }
49
+
50
+ async function run_migrations() {
51
+ console.log("[MIGRATE] Starting automatic migration...");
52
+
53
+ const existing_tables = await get_existing_tables();
54
+ const existing_indexes = await get_existing_indexes();
55
+
56
+ let created_tables = 0;
57
+ let created_indexes = 0;
58
+
59
+ for (const [table_name, schema] of Object.entries(SCHEMA_DEFINITIONS)) {
60
+ if (!existing_tables.has(table_name)) {
61
+ console.log(`[MIGRATE] Creating table: ${table_name}`);
62
+ const statements = schema.split(";").filter((s) => s.trim());
63
+ for (const stmt of statements) {
64
+ if (stmt.trim()) {
65
+ await run_async(stmt.trim());
66
+ }
67
+ }
68
+ created_tables++;
69
+ }
70
+ }
71
+
72
+ for (const index_sql of INDEX_DEFINITIONS) {
73
+ const match = index_sql.match(/create index if not exists (\w+)/);
74
+ const index_name = match ? match[1] : null;
75
+ if (index_name && !existing_indexes.has(index_name)) {
76
+ console.log(`[MIGRATE] Creating index: ${index_name}`);
77
+ await run_async(index_sql);
78
+ created_indexes++;
79
+ }
80
+ }
81
+
82
+ console.log(
83
+ `[MIGRATE] Migration complete: ${created_tables} tables, ${created_indexes} indexes created`,
84
+ );
85
+
86
+ const final_tables = await get_existing_tables();
87
+ console.log(`[MIGRATE] Total tables: ${final_tables.size}`);
88
+ console.log(`[MIGRATE] Tables: ${Array.from(final_tables).join(", ")}`);
89
+ }
90
+
91
+ run_migrations().catch((err) => {
92
+ console.error("[MIGRATE] Error:", err);
93
+ process.exit(1);
94
+ });
@@ -0,0 +1,110 @@
1
+ import path from "path";
2
+ import dotenv from "dotenv";
3
+
4
+ dotenv.config({ path: path.resolve(__dirname, "../../../../.env") });
5
+ const num = (v: string | undefined, d: number) => Number(v) || d;
6
+ const str = (v: string | undefined, d: string) => v || d;
7
+ const bool = (v: string | undefined) => v === "true";
8
+ type tier = "fast" | "smart" | "deep" | "hybrid";
9
+
10
+ const get_tier = (): tier => {
11
+ const man = process.env.OM_TIER as tier;
12
+ if (man && ["fast", "smart", "deep", "hybrid"].includes(man)) return man;
13
+ console.warn(
14
+ "[OpenMemory] OM_TIER not set! Please set OM_TIER=hybrid|fast|smart|deep in .env",
15
+ );
16
+ return "hybrid";
17
+ };
18
+ export const tier = get_tier();
19
+ const tier_dims = { fast: 1536, smart: 1536, deep: 1536, hybrid: 1536 };
20
+ const tier_cache = { fast: 2, smart: 3, deep: 5, hybrid: 3 };
21
+ const tier_max_active = { fast: 32, smart: 64, deep: 128, hybrid: 64 };
22
+
23
+ export const env = {
24
+ port: num(process.env.OM_PORT, 8080),
25
+ db_path: str(
26
+ process.env.OM_DB_PATH,
27
+ path.resolve(__dirname, "../../data/openmemory.sqlite"),
28
+ ),
29
+ api_key: process.env.OM_API_KEY,
30
+ rate_limit_enabled: bool(process.env.OM_RATE_LIMIT_ENABLED),
31
+ rate_limit_window_ms: num(process.env.OM_RATE_LIMIT_WINDOW_MS, 60000),
32
+ rate_limit_max_requests: num(process.env.OM_RATE_LIMIT_MAX_REQUESTS, 100),
33
+ compression_enabled: bool(process.env.OM_COMPRESSION_ENABLED),
34
+ compression_algorithm: str(process.env.OM_COMPRESSION_ALGORITHM, "auto") as
35
+ | "semantic"
36
+ | "syntactic"
37
+ | "aggressive"
38
+ | "auto",
39
+ compression_min_length: num(process.env.OM_COMPRESSION_MIN_LENGTH, 100),
40
+ emb_kind: str(process.env.OM_EMBEDDINGS, "synthetic"),
41
+ embedding_fallback: str(process.env.OM_EMBEDDING_FALLBACK, "synthetic")
42
+ .split(",")
43
+ .map((s) => s.trim())
44
+ .filter(Boolean),
45
+ embed_mode: str(process.env.OM_EMBED_MODE, "simple"),
46
+ adv_embed_parallel: bool(process.env.OM_ADV_EMBED_PARALLEL),
47
+ embed_delay_ms: num(process.env.OM_EMBED_DELAY_MS, 200),
48
+ openai_key:
49
+ process.env.OPENAI_API_KEY || process.env.OM_OPENAI_API_KEY || "",
50
+ openai_base_url: str(
51
+ process.env.OM_OPENAI_BASE_URL,
52
+ "https://api.openai.com/v1",
53
+ ),
54
+ openai_model: process.env.OM_OPENAI_MODEL,
55
+ gemini_key:
56
+ process.env.GEMINI_API_KEY || process.env.OM_GEMINI_API_KEY || "",
57
+ AWS_REGION: process.env.AWS_REGION || "",
58
+ AWS_ACCESS_KEY_ID: process.env.AWS_ACCESS_KEY_ID || "",
59
+ AWS_SECRET_ACCESS_KEY: process.env.AWS_SECRET_ACCESS_KEY || "",
60
+ ollama_url: str(
61
+ process.env.OLLAMA_URL || process.env.OM_OLLAMA_URL,
62
+ "http://localhost:11434",
63
+ ),
64
+ local_model_path:
65
+ process.env.LOCAL_MODEL_PATH || process.env.OM_LOCAL_MODEL_PATH || "",
66
+ vec_dim: num(process.env.OM_VEC_DIM, tier_dims[tier]),
67
+ min_score: num(process.env.OM_MIN_SCORE, 0.3),
68
+ decay_lambda: num(process.env.OM_DECAY_LAMBDA, 0.02),
69
+ decay_interval_minutes: num(process.env.OM_DECAY_INTERVAL_MINUTES, 1440),
70
+ max_payload_size: num(process.env.OM_MAX_PAYLOAD_SIZE, 1_000_000),
71
+ mode: str(process.env.OM_MODE, "standard").toLowerCase(),
72
+ lg_namespace: str(process.env.OM_LG_NAMESPACE, "default"),
73
+ lg_max_context: num(process.env.OM_LG_MAX_CONTEXT, 50),
74
+ lg_reflective: (process.env.OM_LG_REFLECTIVE ?? "true") !== "false",
75
+ metadata_backend: str(
76
+ process.env.OM_METADATA_BACKEND,
77
+ "sqlite",
78
+ ).toLowerCase(),
79
+ vector_backend: str(process.env.OM_VECTOR_BACKEND, "postgres").toLowerCase(),
80
+ valkey_host: str(process.env.OM_VALKEY_HOST, "localhost"),
81
+ valkey_port: num(process.env.OM_VALKEY_PORT, 6379),
82
+ valkey_password: process.env.OM_VALKEY_PASSWORD,
83
+ ide_mode: bool(process.env.OM_IDE_MODE),
84
+ ide_allowed_origins: str(
85
+ process.env.OM_IDE_ALLOWED_ORIGINS,
86
+ "http://localhost:5173,http://localhost:3000",
87
+ ).split(","),
88
+ auto_reflect: bool(process.env.OM_AUTO_REFLECT),
89
+ reflect_interval: num(process.env.OM_REFLECT_INTERVAL, 10),
90
+ reflect_min: num(process.env.OM_REFLECT_MIN_MEMORIES, 20),
91
+ user_summary_interval: num(process.env.OM_USER_SUMMARY_INTERVAL, 30),
92
+ use_summary_only: (process.env.OM_USE_SUMMARY_ONLY ?? "true") !== "false",
93
+ summary_max_length: num(process.env.OM_SUMMARY_MAX_LENGTH, 200),
94
+ seg_size: num(process.env.OM_SEG_SIZE, 10000),
95
+ cache_segments: num(process.env.OM_CACHE_SEGMENTS, tier_cache[tier]),
96
+ max_active: num(process.env.OM_MAX_ACTIVE, tier_max_active[tier]),
97
+ decay_ratio: num(process.env.OM_DECAY_RATIO, 0.03),
98
+ decay_sleep_ms: num(process.env.OM_DECAY_SLEEP_MS, 200),
99
+ decay_threads: num(process.env.OM_DECAY_THREADS, 3),
100
+ decay_cold_threshold: num(process.env.OM_DECAY_COLD_THRESHOLD, 0.25),
101
+ decay_reinforce_on_query:
102
+ (process.env.OM_DECAY_REINFORCE_ON_QUERY ?? "true") !== "false",
103
+ regeneration_enabled:
104
+ (process.env.OM_REGENERATION_ENABLED ?? "true") !== "false",
105
+ max_vector_dim: num(process.env.OM_MAX_VECTOR_DIM, tier_dims[tier]),
106
+ min_vector_dim: num(process.env.OM_MIN_VECTOR_DIM, 64),
107
+ summary_layers: num(process.env.OM_SUMMARY_LAYERS, 3),
108
+ keyword_boost: num(process.env.OM_KEYWORD_BOOST, 2.5),
109
+ keyword_min_length: num(process.env.OM_KEYWORD_MIN_LENGTH, 3),
110
+ };