@hasna/mementos 0.14.47 → 0.14.48

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 (38) hide show
  1. package/Dockerfile.package +30 -0
  2. package/bun.lock +405 -0
  3. package/dist/cli/index.js +141 -70
  4. package/dist/db/pg-migrations.d.ts.map +1 -1
  5. package/dist/generated/storage-kit/health.d.ts +20 -0
  6. package/dist/generated/storage-kit/health.d.ts.map +1 -0
  7. package/dist/generated/storage-kit/index.d.ts +8 -0
  8. package/dist/generated/storage-kit/index.d.ts.map +1 -0
  9. package/dist/generated/storage-kit/migrations.d.ts +48 -0
  10. package/dist/generated/storage-kit/migrations.d.ts.map +1 -0
  11. package/dist/generated/storage-kit/mode.d.ts +48 -0
  12. package/dist/generated/storage-kit/mode.d.ts.map +1 -0
  13. package/dist/generated/storage-kit/pool.d.ts +34 -0
  14. package/dist/generated/storage-kit/pool.d.ts.map +1 -0
  15. package/dist/generated/storage-kit/query.d.ts +36 -0
  16. package/dist/generated/storage-kit/query.d.ts.map +1 -0
  17. package/dist/generated/storage-kit/tls.d.ts +26 -0
  18. package/dist/generated/storage-kit/tls.d.ts.map +1 -0
  19. package/dist/index.js +96 -64
  20. package/dist/mcp/index.js +135 -64
  21. package/dist/pg-sync-worker.d.ts +2 -0
  22. package/dist/pg-sync-worker.d.ts.map +1 -0
  23. package/dist/pg-sync-worker.js +47 -0
  24. package/dist/sdk/index.d.ts +27 -0
  25. package/dist/sdk/index.d.ts.map +1 -1
  26. package/dist/sdk/index.js +25 -6
  27. package/dist/server/auth.d.ts +11 -0
  28. package/dist/server/auth.d.ts.map +1 -0
  29. package/dist/server/index.d.ts.map +1 -1
  30. package/dist/server/index.js +362 -129
  31. package/dist/server/openapi.d.ts +2 -0
  32. package/dist/server/openapi.d.ts.map +1 -0
  33. package/dist/storage.d.ts +36 -3
  34. package/dist/storage.d.ts.map +1 -1
  35. package/dist/storage.js +99 -64
  36. package/docker-entrypoint.sh +46 -0
  37. package/hasna.contract.json +16 -0
  38. package/package.json +7 -3
@@ -70,6 +70,8 @@ import { Database } from "bun:sqlite";
70
70
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
71
71
  import { homedir as homedir2 } from "os";
72
72
  import { join as join2 } from "path";
73
+ import { fileURLToPath } from "url";
74
+ import { Worker } from "worker_threads";
73
75
  import pg from "pg";
74
76
  function normalizeParams(params) {
75
77
  const flat = params.length === 1 && Array.isArray(params[0]) ? params[0] : params;
@@ -132,13 +134,15 @@ class SqliteAdapter {
132
134
  function translateSql(sql) {
133
135
  let parameterIndex = 0;
134
136
  let translated = sql.replace(/\?/g, () => `$${++parameterIndex}`);
135
- translated = translated.replace(/datetime\s*\(\s*'now'\s*\)/gi, "NOW()");
137
+ const ISO_FMT = `'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'`;
138
+ translated = translated.replace(/datetime\s*\(\s*'now'\s*\)/gi, `to_char(now() AT TIME ZONE 'UTC', ${ISO_FMT})`);
136
139
  translated = translated.replace(/datetime\s*\(\s*'now'\s*,\s*'(-?\d+)\s+(minutes?|hours?|days?|seconds?)'\s*\)/gi, (_match, amount, unit) => {
137
140
  const parsed = parseInt(String(amount), 10);
138
141
  const absolute = Math.abs(parsed);
139
142
  const normalizedUnit = String(unit).toLowerCase().replace(/s$/, "");
140
143
  const pluralUnit = absolute === 1 ? normalizedUnit : `${normalizedUnit}s`;
141
- return parsed < 0 ? `NOW() - INTERVAL '${absolute} ${pluralUnit}'` : `NOW() + INTERVAL '${absolute} ${pluralUnit}'`;
144
+ const op = parsed < 0 ? "-" : "+";
145
+ return `to_char((now() ${op} INTERVAL '${absolute} ${pluralUnit}') AT TIME ZONE 'UTC', ${ISO_FMT})`;
142
146
  });
143
147
  translated = translated.replace(/lower\s*\(\s*hex\s*\(\s*randomblob\s*\(\s*\d+\s*\)\s*\)\s*\)/gi, "gen_random_uuid()::text");
144
148
  translated = translated.replace(/\bIFNULL\s*\(/gi, "COALESCE(");
@@ -195,56 +199,24 @@ function makePool(connectionString) {
195
199
  class PgAdapter {
196
200
  pool;
197
201
  constructor(input) {
198
- this.pool = typeof input === "string" ? makePool(input) : input;
199
- }
200
- runSync(fn) {
201
- let result;
202
- let error;
203
- let done = false;
204
- fn().then((value) => {
205
- result = value;
206
- done = true;
207
- }).catch((caught) => {
208
- error = caught;
209
- done = true;
210
- });
211
- const deadline = Date.now() + 30000;
212
- while (!done && Date.now() < deadline) {
213
- Bun.sleepSync(1);
214
- }
215
- if (error) {
216
- throw error;
217
- }
218
- if (!done) {
219
- throw new Error("PostgreSQL query timed out after 30s");
220
- }
221
- return result;
202
+ this.pool = typeof input === "string" ? new PgSyncPool(input) : input;
222
203
  }
223
204
  run(sql, ...params) {
224
- return this.runSync(async () => {
225
- const result = await this.pool.query(translateSql(sql), normalizeParams(params));
226
- return {
227
- changes: result.rowCount ?? 0,
228
- lastInsertRowid: result.rows?.[0]?.id ?? 0
229
- };
230
- });
205
+ const result = this.pool.query(translateSql(sql), normalizeParams(params));
206
+ return {
207
+ changes: result.rowCount ?? 0,
208
+ lastInsertRowid: result.rows?.[0]?.id ?? 0
209
+ };
231
210
  }
232
211
  get(sql, ...params) {
233
- return this.runSync(async () => {
234
- const result = await this.pool.query(translateSql(sql), normalizeParams(params));
235
- return result.rows[0] ?? null;
236
- });
212
+ const result = this.pool.query(translateSql(sql), normalizeParams(params));
213
+ return result.rows[0] ?? null;
237
214
  }
238
215
  all(sql, ...params) {
239
- return this.runSync(async () => {
240
- const result = await this.pool.query(translateSql(sql), normalizeParams(params));
241
- return result.rows;
242
- });
216
+ return this.pool.query(translateSql(sql), normalizeParams(params)).rows;
243
217
  }
244
218
  exec(sql) {
245
- this.runSync(async () => {
246
- await this.pool.query(sql);
247
- });
219
+ this.pool.query(sql, []);
248
220
  }
249
221
  prepare(sql) {
250
222
  return {
@@ -258,28 +230,20 @@ class PgAdapter {
258
230
  return this.prepare(sql);
259
231
  }
260
232
  close() {
261
- this.runSync(async () => {
262
- await this.pool.end();
263
- });
233
+ this.pool.end();
264
234
  }
265
235
  transaction(fn) {
266
- return this.runSync(async () => {
267
- const client = await this.pool.connect();
268
- const originalQuery = this.pool.query.bind(this.pool);
236
+ this.pool.query("BEGIN", []);
237
+ try {
238
+ const value = fn();
239
+ this.pool.query("COMMIT", []);
240
+ return value;
241
+ } catch (error) {
269
242
  try {
270
- await client.query("BEGIN");
271
- this.pool.query = client.query.bind(client);
272
- const value = fn();
273
- await client.query("COMMIT");
274
- return value;
275
- } catch (error) {
276
- await client.query("ROLLBACK");
277
- throw error;
278
- } finally {
279
- this.pool.query = originalQuery;
280
- client.release();
281
- }
282
- });
243
+ this.pool.query("ROLLBACK", []);
244
+ } catch {}
245
+ throw error;
246
+ }
283
247
  }
284
248
  get raw() {
285
249
  return this.pool;
@@ -385,8 +349,76 @@ function getStorageConnectionString(dbName = "mementos") {
385
349
  const sslParam = ssl ? "?sslmode=require" : "";
386
350
  return `postgres://${username}:${encodeURIComponent(password)}@${host}:${port}/${dbName}${sslParam}`;
387
351
  }
388
- var MEMENTOS_STORAGE_ENV, MEMENTOS_STORAGE_FALLBACK_ENV, DEFAULT_STORAGE_CONFIG, STORAGE_CONFIG_DIR, STORAGE_CONFIG_PATH, DATABASE_ENV_NAMES, MODE_ENV_NAMES, warnedDeprecatedModes;
352
+ var PgSyncPool, MEMENTOS_STORAGE_ENV, MEMENTOS_STORAGE_FALLBACK_ENV, DEFAULT_STORAGE_CONFIG, STORAGE_CONFIG_DIR, STORAGE_CONFIG_PATH, DATABASE_ENV_NAMES, MODE_ENV_NAMES, warnedDeprecatedModes;
389
353
  var init_storage = __esm(() => {
354
+ PgSyncPool = class PgSyncPool {
355
+ worker;
356
+ status;
357
+ data;
358
+ closed = false;
359
+ lastError = null;
360
+ static DATA_BYTES = 128 * 1024 * 1024;
361
+ static QUERY_TIMEOUT_MS = 60000;
362
+ static resolveWorkerPath() {
363
+ const ext = import.meta.url.endsWith(".ts") ? ".ts" : ".js";
364
+ const here = fileURLToPath(new URL(".", import.meta.url));
365
+ const candidates = [
366
+ join2(here, `pg-sync-worker${ext}`),
367
+ join2(here, "..", `pg-sync-worker${ext}`),
368
+ join2(here, "..", "..", `pg-sync-worker${ext}`)
369
+ ];
370
+ for (const candidate of candidates) {
371
+ if (existsSync2(candidate))
372
+ return candidate;
373
+ }
374
+ return candidates[0];
375
+ }
376
+ constructor(connectionString) {
377
+ const control = new SharedArrayBuffer(8);
378
+ const dataSab = new SharedArrayBuffer(PgSyncPool.DATA_BYTES);
379
+ this.status = new Int32Array(control);
380
+ this.data = new Uint8Array(dataSab);
381
+ this.worker = new Worker(PgSyncPool.resolveWorkerPath(), {
382
+ workerData: {
383
+ dsn: stripSslParams(connectionString),
384
+ ssl: sslConfigFor(connectionString),
385
+ control,
386
+ data: dataSab
387
+ }
388
+ });
389
+ this.worker.unref();
390
+ this.worker.on("error", (err) => {
391
+ this.lastError = err;
392
+ });
393
+ }
394
+ query(sql, params) {
395
+ if (this.closed)
396
+ throw new Error("PgSyncPool is closed");
397
+ if (this.lastError)
398
+ throw this.lastError;
399
+ Atomics.store(this.status, 0, 0);
400
+ this.worker.postMessage({ sql, params });
401
+ const waitResult = Atomics.wait(this.status, 0, 0, PgSyncPool.QUERY_TIMEOUT_MS);
402
+ const code = Atomics.load(this.status, 0);
403
+ if (code === 0 || waitResult === "timed-out") {
404
+ if (this.lastError)
405
+ throw this.lastError;
406
+ throw new Error("PostgreSQL query timed out after 60s");
407
+ }
408
+ const len = Atomics.load(this.status, 1);
409
+ const payload = JSON.parse(new TextDecoder().decode(this.data.subarray(0, len)));
410
+ if (code === 2) {
411
+ throw new Error(payload.message ?? "PostgreSQL error");
412
+ }
413
+ return payload;
414
+ }
415
+ end() {
416
+ if (this.closed)
417
+ return;
418
+ this.closed = true;
419
+ this.worker.terminate();
420
+ }
421
+ };
390
422
  MEMENTOS_STORAGE_ENV = {
391
423
  databaseUrl: "HASNA_MEMENTOS_DATABASE_URL",
392
424
  mode: "HASNA_MEMENTOS_STORAGE_MODE"
@@ -4799,6 +4831,51 @@ var init_contradiction = __esm(() => {
4799
4831
  init_registry();
4800
4832
  });
4801
4833
 
4834
+ // src/server/router.ts
4835
+ function computeSpecificity(path) {
4836
+ return path.split("/").filter(Boolean).reduce((score, segment) => score + (segment.startsWith(":") ? 1 : 10), 0);
4837
+ }
4838
+ function addRoute(method, path, handler) {
4839
+ const paramNames = [];
4840
+ const patternStr = path.replace(/:(\w+)/g, (_match, name) => {
4841
+ paramNames.push(name);
4842
+ return "([^/]+)";
4843
+ });
4844
+ routes.push({
4845
+ method,
4846
+ path,
4847
+ pattern: new RegExp(`^${patternStr}$`),
4848
+ paramNames,
4849
+ specificity: computeSpecificity(path),
4850
+ order: nextRouteOrder++,
4851
+ handler
4852
+ });
4853
+ }
4854
+ function matchRoute(method, pathname) {
4855
+ let bestMatch = null;
4856
+ for (const route of routes) {
4857
+ if (route.method !== method)
4858
+ continue;
4859
+ const match = pathname.match(route.pattern);
4860
+ if (match) {
4861
+ const params = {};
4862
+ route.paramNames.forEach((name, i) => {
4863
+ params[name] = match[i + 1];
4864
+ });
4865
+ if (!bestMatch || route.specificity > bestMatch.route.specificity || route.specificity === bestMatch.route.specificity && route.order < bestMatch.route.order) {
4866
+ bestMatch = { route, params };
4867
+ }
4868
+ }
4869
+ }
4870
+ if (!bestMatch)
4871
+ return null;
4872
+ return { handler: bestMatch.route.handler, params: bestMatch.params };
4873
+ }
4874
+ var routes, nextRouteOrder = 0;
4875
+ var init_router = __esm(() => {
4876
+ routes = [];
4877
+ });
4878
+
4802
4879
  // node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/util.js
4803
4880
  var util, objectUtil, ZodParsedType, getParsedType = (data) => {
4804
4881
  const t = typeof data;
@@ -52391,8 +52468,88 @@ var init_dist8 = __esm(() => {
52391
52468
  };
52392
52469
  });
52393
52470
 
52471
+ // src/server/openapi.ts
52472
+ var exports_openapi = {};
52473
+ __export(exports_openapi, {
52474
+ buildOpenApiDocument: () => buildOpenApiDocument
52475
+ });
52476
+ function toV1Path(path) {
52477
+ return path.replace(/^\/api/, "/v1").replace(/:(\w+)/g, "{$1}");
52478
+ }
52479
+ function buildOpenApiDocument(version2) {
52480
+ const paths = {};
52481
+ for (const p of ["/health", "/ready", "/version"]) {
52482
+ paths[p] = {
52483
+ get: {
52484
+ summary: `Service ${p.slice(1)} probe`,
52485
+ security: [],
52486
+ responses: {
52487
+ "200": {
52488
+ description: "OK",
52489
+ content: {
52490
+ "application/json": {
52491
+ schema: {
52492
+ type: "object",
52493
+ properties: {
52494
+ status: { type: "string" },
52495
+ version: { type: "string" },
52496
+ mode: { type: "string", enum: ["local", "cloud"] }
52497
+ }
52498
+ }
52499
+ }
52500
+ }
52501
+ }
52502
+ }
52503
+ }
52504
+ };
52505
+ }
52506
+ for (const route of routes) {
52507
+ const p = toV1Path(route.path);
52508
+ const method = route.method.toLowerCase();
52509
+ const params = route.paramNames.map((name24) => ({
52510
+ name: name24,
52511
+ in: "path",
52512
+ required: true,
52513
+ schema: { type: "string" }
52514
+ }));
52515
+ paths[p] = paths[p] ?? {};
52516
+ paths[p][method] = {
52517
+ summary: `${route.method} ${p}`,
52518
+ operationId: `${method}_${p.replace(/[^a-zA-Z0-9]+/g, "_").replace(/^_|_$/g, "")}`,
52519
+ ...params.length ? { parameters: params } : {},
52520
+ responses: {
52521
+ "200": { description: "OK" },
52522
+ "401": { description: "Unauthorized" },
52523
+ "403": { description: "Forbidden" },
52524
+ "404": { description: "Not found" }
52525
+ }
52526
+ };
52527
+ }
52528
+ return {
52529
+ openapi: "3.1.0",
52530
+ info: {
52531
+ title: "@hasna/mementos serve API",
52532
+ version: version2,
52533
+ description: "Universal memory system for AI agents \u2014 REST API (self_hosted)."
52534
+ },
52535
+ servers: [{ url: "/v1" }, { url: "/api" }],
52536
+ components: {
52537
+ securitySchemes: {
52538
+ bearerAuth: { type: "http", scheme: "bearer" },
52539
+ apiKeyAuth: { type: "apiKey", in: "header", name: "x-api-key" }
52540
+ }
52541
+ },
52542
+ security: [{ bearerAuth: [] }, { apiKeyAuth: [] }],
52543
+ paths
52544
+ };
52545
+ }
52546
+ var init_openapi = __esm(() => {
52547
+ init_router();
52548
+ });
52549
+
52394
52550
  // src/server/index.ts
52395
52551
  import { existsSync as existsSync5 } from "fs";
52552
+ import { createRequire } from "module";
52396
52553
  import { join as join5, resolve as resolve4, sep } from "path";
52397
52554
 
52398
52555
  // src/lib/config.ts
@@ -53979,53 +54136,14 @@ async function _tick() {
53979
54136
  }
53980
54137
  }
53981
54138
 
53982
- // src/server/router.ts
53983
- var routes = [];
53984
- var nextRouteOrder = 0;
53985
- function computeSpecificity(path) {
53986
- return path.split("/").filter(Boolean).reduce((score, segment) => score + (segment.startsWith(":") ? 1 : 10), 0);
53987
- }
53988
- function addRoute(method, path, handler) {
53989
- const paramNames = [];
53990
- const patternStr = path.replace(/:(\w+)/g, (_match, name) => {
53991
- paramNames.push(name);
53992
- return "([^/]+)";
53993
- });
53994
- routes.push({
53995
- method,
53996
- path,
53997
- pattern: new RegExp(`^${patternStr}$`),
53998
- paramNames,
53999
- specificity: computeSpecificity(path),
54000
- order: nextRouteOrder++,
54001
- handler
54002
- });
54003
- }
54004
- function matchRoute(method, pathname) {
54005
- let bestMatch = null;
54006
- for (const route of routes) {
54007
- if (route.method !== method)
54008
- continue;
54009
- const match = pathname.match(route.pattern);
54010
- if (match) {
54011
- const params = {};
54012
- route.paramNames.forEach((name, i) => {
54013
- params[name] = match[i + 1];
54014
- });
54015
- if (!bestMatch || route.specificity > bestMatch.route.specificity || route.specificity === bestMatch.route.specificity && route.order < bestMatch.route.order) {
54016
- bestMatch = { route, params };
54017
- }
54018
- }
54019
- }
54020
- if (!bestMatch)
54021
- return null;
54022
- return { handler: bestMatch.route.handler, params: bestMatch.params };
54023
- }
54139
+ // src/server/index.ts
54140
+ init_storage();
54141
+ init_router();
54024
54142
 
54025
54143
  // src/server/helpers.ts
54026
54144
  import { existsSync as existsSync4 } from "fs";
54027
54145
  import { dirname as dirname3, extname, join as join4 } from "path";
54028
- import { fileURLToPath } from "url";
54146
+ import { fileURLToPath as fileURLToPath2 } from "url";
54029
54147
  var CORS_HEADERS = {
54030
54148
  "Access-Control-Allow-Origin": process.env["MEMENTOS_CORS_ORIGIN"] ?? "http://localhost:19428",
54031
54149
  "Access-Control-Allow-Methods": "GET, POST, PATCH, DELETE, OPTIONS",
@@ -54108,7 +54226,7 @@ function getSearchParams(url) {
54108
54226
  function resolveDashboardDir() {
54109
54227
  const candidates = [];
54110
54228
  try {
54111
- const scriptDir = dirname3(fileURLToPath(import.meta.url));
54229
+ const scriptDir = dirname3(fileURLToPath2(import.meta.url));
54112
54230
  candidates.push(join4(scriptDir, "..", "dashboard", "dist"));
54113
54231
  candidates.push(join4(scriptDir, "..", "..", "dashboard", "dist"));
54114
54232
  } catch {}
@@ -54133,6 +54251,80 @@ function serveStaticFile(filePath) {
54133
54251
  });
54134
54252
  }
54135
54253
 
54254
+ // src/server/auth.ts
54255
+ init_storage();
54256
+ import {
54257
+ verifyApiKey,
54258
+ ApiKeyStore
54259
+ } from "@hasna/contracts/auth";
54260
+ var APP = "mementos";
54261
+ var _verifier;
54262
+ var _store = null;
54263
+ var _schemaReady = null;
54264
+ function signingSecret() {
54265
+ return process.env["API_KEY_SIGNING_SECRET"]?.trim() || process.env["HASNA_MEMENTOS_API_SIGNING_KEY"]?.trim() || process.env["HASNA_API_SIGNING_KEY"]?.trim() || undefined;
54266
+ }
54267
+ function makeAuthClient() {
54268
+ let dsn;
54269
+ try {
54270
+ dsn = getStorageConnectionString(APP);
54271
+ } catch {
54272
+ return null;
54273
+ }
54274
+ let pool;
54275
+ try {
54276
+ pool = makePool(dsn);
54277
+ } catch {
54278
+ return null;
54279
+ }
54280
+ return {
54281
+ many: async (sql, params = []) => (await pool.query(sql, params)).rows,
54282
+ get: async (sql, params = []) => (await pool.query(sql, params)).rows[0] ?? null,
54283
+ execute: async (sql, params = []) => {
54284
+ await pool.query(sql, params);
54285
+ }
54286
+ };
54287
+ }
54288
+ function getApiKeyVerifier() {
54289
+ if (_verifier !== undefined)
54290
+ return _verifier;
54291
+ const secret = signingSecret();
54292
+ if (!secret) {
54293
+ _verifier = null;
54294
+ return null;
54295
+ }
54296
+ const client = makeAuthClient();
54297
+ if (client) {
54298
+ _store = new ApiKeyStore(client);
54299
+ _schemaReady = _store.ensureSchema().catch((e) => {
54300
+ console.warn(`[mementos-serve] api_keys ensureSchema failed: ${e instanceof Error ? e.message : e}`);
54301
+ });
54302
+ }
54303
+ _verifier = verifyApiKey({
54304
+ app: APP,
54305
+ signingSecret: secret,
54306
+ isRevoked: _store ? _store.isRevoked : undefined,
54307
+ audit: (e) => {
54308
+ if (e.outcome === "deny") {
54309
+ console.warn(`[mementos-serve] auth deny kid=${e.kid ?? "-"} reason=${e.reason} ${e.method ?? ""} ${e.path ?? ""}`);
54310
+ }
54311
+ }
54312
+ });
54313
+ return _verifier;
54314
+ }
54315
+ async function checkApiKey(req, method, path, requiredScopes) {
54316
+ const verifier = getApiKeyVerifier();
54317
+ if (!verifier) {
54318
+ return authenticateRequest(req);
54319
+ }
54320
+ if (_schemaReady)
54321
+ await _schemaReady;
54322
+ const decision = await verifier.authenticate(req.headers, { method, path, requiredScopes });
54323
+ if (decision.ok)
54324
+ return null;
54325
+ return json({ error: decision.message, reason: decision.reason }, decision.status);
54326
+ }
54327
+
54136
54328
  // src/server/routes/memories-crud.ts
54137
54329
  init_memories();
54138
54330
 
@@ -54180,6 +54372,7 @@ var FORMAT_UNITS = [
54180
54372
  ];
54181
54373
 
54182
54374
  // src/server/routes/memories-crud.ts
54375
+ init_router();
54183
54376
  init_types();
54184
54377
  addRoute("GET", "/api/memories", (_req, url) => {
54185
54378
  const q = getSearchParams(url);
@@ -54290,6 +54483,7 @@ addRoute("DELETE", "/api/memories/:id", (_req, _url, params) => {
54290
54483
 
54291
54484
  // src/server/routes/memories-stats.ts
54292
54485
  init_database();
54486
+ init_router();
54293
54487
  addRoute("GET", "/api/memories/stats", (_req) => {
54294
54488
  const db = getDatabase();
54295
54489
  const total = db.query("SELECT COUNT(*) as c FROM memories WHERE status = 'active'").get().c;
@@ -54449,6 +54643,7 @@ addRoute("GET", "/api/report", (_req, url) => {
54449
54643
 
54450
54644
  // src/server/routes/memories-search.ts
54451
54645
  init_search();
54646
+ init_router();
54452
54647
  addRoute("POST", "/api/memories/search", async (req) => {
54453
54648
  const body = await readJson(req);
54454
54649
  if (!body || typeof body["query"] !== "string") {
@@ -54525,6 +54720,7 @@ addRoute("POST", "/api/memories/search/bm25", async (req) => {
54525
54720
 
54526
54721
  // src/server/routes/memories-bulk.ts
54527
54722
  init_memories();
54723
+ init_router();
54528
54724
  addRoute("POST", "/api/memories/bulk-forget", async (req) => {
54529
54725
  const body = await readJson(req);
54530
54726
  if (!body || !Array.isArray(body["ids"])) {
@@ -54567,6 +54763,7 @@ addRoute("POST", "/api/memories/bulk-update", async (req) => {
54567
54763
 
54568
54764
  // src/server/routes/memories-io.ts
54569
54765
  init_memories();
54766
+ init_router();
54570
54767
  addRoute("POST", "/api/memories/export", async (req) => {
54571
54768
  const body = await readJson(req) || {};
54572
54769
  const filter = {};
@@ -54629,6 +54826,7 @@ function visibleToMachineFilter(machineId, db) {
54629
54826
  }
54630
54827
 
54631
54828
  // src/server/routes/memories-misc.ts
54829
+ init_router();
54632
54830
  addRoute("GET", "/api/health", () => {
54633
54831
  return json({ ok: true, version: "1", db: getDbPath() });
54634
54832
  });
@@ -55007,6 +55205,7 @@ function cleanExpiredLocks(db) {
55007
55205
  }
55008
55206
 
55009
55207
  // src/server/routes/agents.ts
55208
+ init_router();
55010
55209
  addRoute("GET", "/api/agents", (_req, url) => {
55011
55210
  const q = getSearchParams(url);
55012
55211
  const agents = q["project_id"] ? listAgentsByProject(q["project_id"]) : listAgents();
@@ -55148,6 +55347,7 @@ function listProjects(db) {
55148
55347
  }
55149
55348
 
55150
55349
  // src/server/routes/projects.ts
55350
+ init_router();
55151
55351
  addRoute("GET", "/api/projects", (_req, url) => {
55152
55352
  const q = getSearchParams(url);
55153
55353
  const projects = listProjects();
@@ -55186,6 +55386,7 @@ init_relations();
55186
55386
  init_entity_memories();
55187
55387
  init_database();
55188
55388
  init_types();
55389
+ init_router();
55189
55390
  addRoute("GET", "/api/entities", (_req, url) => {
55190
55391
  const q = getSearchParams(url);
55191
55392
  const filter = {};
@@ -55407,6 +55608,7 @@ addRoute("GET", "/api/graph/:entityId", (_req, url, params) => {
55407
55608
 
55408
55609
  // src/server/routes/tasks.ts
55409
55610
  init_database();
55611
+ init_router();
55410
55612
  addRoute("POST", "/api/tasks", async (req, _url) => {
55411
55613
  const body = await readJson(req);
55412
55614
  if (!body)
@@ -55483,6 +55685,7 @@ addRoute("DELETE", "/api/tasks/:id/comments/:commentId", async (_req, _url, para
55483
55685
  // src/server/routes/system-auto-memory.ts
55484
55686
  init_auto_memory();
55485
55687
  init_registry();
55688
+ init_router();
55486
55689
  function registerSystemAutoMemoryRoutes() {
55487
55690
  addRoute("POST", "/api/auto-memory/process", async (req) => {
55488
55691
  const body = await readJson(req);
@@ -55544,6 +55747,7 @@ function registerSystemAutoMemoryRoutes() {
55544
55747
 
55545
55748
  // src/server/routes/system-hooks.ts
55546
55749
  init_hooks();
55750
+ init_router();
55547
55751
  function registerSystemHookRoutes() {
55548
55752
  addRoute("GET", "/api/hooks", (_req, url) => {
55549
55753
  const type = url.searchParams.get("type") ?? undefined;
@@ -56517,6 +56721,7 @@ function getSynthesisStatus(runId, projectId, db) {
56517
56721
  // src/server/routes/system-synthesis.ts
56518
56722
  init_synthesis();
56519
56723
  init_profile_synthesizer();
56724
+ init_router();
56520
56725
  function registerSystemSynthesisRoutes() {
56521
56726
  addRoute("POST", "/api/synthesis/run", async (req) => {
56522
56727
  const body = await readJson(req) ?? {};
@@ -56638,6 +56843,7 @@ function autoResolveAgentProject(metadata, db) {
56638
56843
  }
56639
56844
 
56640
56845
  // src/server/routes/system-sessions.ts
56846
+ init_router();
56641
56847
  function registerSystemSessionRoutes() {
56642
56848
  addRoute("POST", "/api/sessions/ingest", async (req) => {
56643
56849
  const body = await readJson(req) ?? {};
@@ -56684,6 +56890,7 @@ function registerSystemSessionRoutes() {
56684
56890
  }
56685
56891
 
56686
56892
  // src/server/routes/system-tools.ts
56893
+ init_router();
56687
56894
  function registerSystemToolRoutes() {
56688
56895
  addRoute("POST", "/api/tool-events", async (req) => {
56689
56896
  const body = await readJson(req);
@@ -56728,6 +56935,7 @@ function registerSystemToolRoutes() {
56728
56935
 
56729
56936
  // src/server/routes/system-chain.ts
56730
56937
  init_database();
56938
+ init_router();
56731
56939
  function registerSystemChainRoutes() {
56732
56940
  addRoute("GET", "/api/chains/:sequence_group", (_req, _url, params) => {
56733
56941
  const db = getDatabase();
@@ -57680,6 +57888,7 @@ async function reflectOnTrajectory(options) {
57680
57888
  }
57681
57889
 
57682
57890
  // src/server/routes/system-consolidation.ts
57891
+ init_router();
57683
57892
  function numberOption(value) {
57684
57893
  if (value === undefined || value === null || value === "")
57685
57894
  return;
@@ -57743,6 +57952,10 @@ registerSystemChainRoutes();
57743
57952
  registerSystemConsolidationRoutes();
57744
57953
 
57745
57954
  // src/server/index.ts
57955
+ function pkgVersion() {
57956
+ const req = createRequire(import.meta.url);
57957
+ return req("../../package.json").version;
57958
+ }
57746
57959
  async function findFreePort(start) {
57747
57960
  const net = await import("net");
57748
57961
  return new Promise((resolve5) => {
@@ -57844,30 +58057,50 @@ function startServer(port) {
57844
58057
  }
57845
58058
  return new Response(null, { status: 204, headers: getCorsHeaders(req) });
57846
58059
  }
57847
- if (pathname === "/api/health" || pathname === "/health") {
58060
+ const mode = getStorageMode();
58061
+ if (pathname === "/version" || pathname === "/api/version" || pathname === "/v1/version") {
58062
+ return json({ status: "ok", version: pkgVersion(), mode });
58063
+ }
58064
+ if (pathname === "/ready" || pathname === "/api/ready" || pathname === "/v1/ready") {
58065
+ try {
58066
+ getDatabase().query("SELECT 1 AS ok").get();
58067
+ return json({ status: "ready", version: pkgVersion(), mode });
58068
+ } catch (e) {
58069
+ return json({ status: "not_ready", version: pkgVersion(), mode, error: e instanceof Error ? e.message : String(e) }, 503);
58070
+ }
58071
+ }
58072
+ if (pathname === "/health" || pathname === "/api/health" || pathname === "/v1/health") {
57848
58073
  const profile = getActiveProfile();
57849
- const { createRequire } = await import("module");
57850
- const req2 = createRequire(import.meta.url);
57851
- const pkg = req2("../../package.json");
57852
- const db = getDatabase();
57853
- const total = db.query("SELECT COUNT(*) as c FROM memories WHERE status = 'active'").get().c;
57854
- const expired = db.query("SELECT COUNT(*) as c FROM memories WHERE status = 'expired' OR (expires_at IS NOT NULL AND expires_at < datetime('now'))").get().c;
57855
- const pinned = db.query("SELECT COUNT(*) as c FROM memories WHERE status = 'active' AND pinned = 1").get().c;
57856
- const agents = db.query("SELECT COUNT(*) as c FROM agents").get().c;
57857
- const projects = db.query("SELECT COUNT(*) as c FROM projects").get().c;
57858
- const status = expired > 50 ? "warn" : "ok";
57859
- return json({ status, version: pkg.version, profile: profile ?? "default", db_path: getDbPath(), hostname: hostname3, memories: { total, expired, pinned }, agents, projects });
57860
- }
57861
- if (pathname.startsWith("/api/") && pathname !== "/api/health") {
57862
- const authError = authenticateRequest(req);
58074
+ try {
58075
+ const db = getDatabase();
58076
+ const total = db.query("SELECT COUNT(*) as c FROM memories WHERE status = 'active'").get().c;
58077
+ const expired = db.query("SELECT COUNT(*) as c FROM memories WHERE status = 'expired' OR (expires_at IS NOT NULL AND expires_at < datetime('now'))").get().c;
58078
+ const pinned = db.query("SELECT COUNT(*) as c FROM memories WHERE status = 'active' AND pinned = 1").get().c;
58079
+ const agents = db.query("SELECT COUNT(*) as c FROM agents").get().c;
58080
+ const projects = db.query("SELECT COUNT(*) as c FROM projects").get().c;
58081
+ const status = expired > 50 ? "warn" : "ok";
58082
+ return json({ status, version: pkgVersion(), mode, profile: profile ?? "default", db_path: getDbPath(), hostname: hostname3, memories: { total, expired, pinned }, agents, projects });
58083
+ } catch (e) {
58084
+ return json({ status: "error", version: pkgVersion(), mode, error: e instanceof Error ? e.message : String(e) }, 503);
58085
+ }
58086
+ }
58087
+ const isV1 = pathname === "/v1" || pathname.startsWith("/v1/");
58088
+ const routePath = isV1 ? `/api${pathname.slice(3)}` : pathname;
58089
+ const isApi = routePath.startsWith("/api/");
58090
+ if (pathname === "/openapi.json" || pathname === "/v1/openapi.json" || pathname === "/api/openapi.json") {
58091
+ const { buildOpenApiDocument: buildOpenApiDocument2 } = await Promise.resolve().then(() => (init_openapi(), exports_openapi));
58092
+ return json(buildOpenApiDocument2(pkgVersion()));
58093
+ }
58094
+ if (isApi) {
58095
+ const authError = await checkApiKey(req, req.method, routePath);
57863
58096
  if (authError)
57864
58097
  return authError;
57865
58098
  }
57866
- if (pathname === "/api/profile" && req.method === "GET") {
58099
+ if (routePath === "/api/profile" && req.method === "GET") {
57867
58100
  const profile = getActiveProfile();
57868
58101
  return json({ active: profile ?? null, profiles: listProfiles(), db_path: getDbPath() });
57869
58102
  }
57870
- if (pathname === "/api/memories/stream" && req.method === "GET") {
58103
+ if (routePath === "/api/memories/stream" && req.method === "GET") {
57871
58104
  const stream = new ReadableStream({
57872
58105
  start(controller) {
57873
58106
  const encoder2 = new TextEncoder;
@@ -57903,9 +58136,9 @@ function startServer(port) {
57903
58136
  }
57904
58137
  });
57905
58138
  }
57906
- const matched = matchRoute(req.method, pathname);
58139
+ const matched = matchRoute(req.method, routePath);
57907
58140
  if (!matched) {
57908
- if (pathname.startsWith("/api/")) {
58141
+ if (isApi || isV1) {
57909
58142
  return errorResponse("Not found", 404);
57910
58143
  }
57911
58144
  const dashDir = resolveDashboardDir();
@@ -57941,8 +58174,8 @@ async function main() {
57941
58174
  return;
57942
58175
  }
57943
58176
  if (hasFlag("--version", "-V")) {
57944
- const { createRequire } = await import("module");
57945
- const req = createRequire(import.meta.url);
58177
+ const { createRequire: createRequire2 } = await import("module");
58178
+ const req = createRequire2(import.meta.url);
57946
58179
  const pkg = req("../../package.json");
57947
58180
  process.stdout.write(`${pkg.version}
57948
58181
  `);