@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.
- package/Dockerfile.package +30 -0
- package/bun.lock +405 -0
- package/dist/cli/index.js +141 -70
- package/dist/db/pg-migrations.d.ts.map +1 -1
- package/dist/generated/storage-kit/health.d.ts +20 -0
- package/dist/generated/storage-kit/health.d.ts.map +1 -0
- package/dist/generated/storage-kit/index.d.ts +8 -0
- package/dist/generated/storage-kit/index.d.ts.map +1 -0
- package/dist/generated/storage-kit/migrations.d.ts +48 -0
- package/dist/generated/storage-kit/migrations.d.ts.map +1 -0
- package/dist/generated/storage-kit/mode.d.ts +48 -0
- package/dist/generated/storage-kit/mode.d.ts.map +1 -0
- package/dist/generated/storage-kit/pool.d.ts +34 -0
- package/dist/generated/storage-kit/pool.d.ts.map +1 -0
- package/dist/generated/storage-kit/query.d.ts +36 -0
- package/dist/generated/storage-kit/query.d.ts.map +1 -0
- package/dist/generated/storage-kit/tls.d.ts +26 -0
- package/dist/generated/storage-kit/tls.d.ts.map +1 -0
- package/dist/index.js +96 -64
- package/dist/mcp/index.js +135 -64
- package/dist/pg-sync-worker.d.ts +2 -0
- package/dist/pg-sync-worker.d.ts.map +1 -0
- package/dist/pg-sync-worker.js +47 -0
- package/dist/sdk/index.d.ts +27 -0
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +25 -6
- package/dist/server/auth.d.ts +11 -0
- package/dist/server/auth.d.ts.map +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +362 -129
- package/dist/server/openapi.d.ts +2 -0
- package/dist/server/openapi.d.ts.map +1 -0
- package/dist/storage.d.ts +36 -3
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +99 -64
- package/docker-entrypoint.sh +46 -0
- package/hasna.contract.json +16 -0
- package/package.json +7 -3
package/dist/server/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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" ?
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
234
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
262
|
-
await this.pool.end();
|
|
263
|
-
});
|
|
233
|
+
this.pool.end();
|
|
264
234
|
}
|
|
265
235
|
transaction(fn) {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
const
|
|
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
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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/
|
|
53983
|
-
|
|
53984
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
57850
|
-
|
|
57851
|
-
|
|
57852
|
-
|
|
57853
|
-
|
|
57854
|
-
|
|
57855
|
-
|
|
57856
|
-
|
|
57857
|
-
|
|
57858
|
-
|
|
57859
|
-
|
|
57860
|
-
|
|
57861
|
-
|
|
57862
|
-
|
|
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 (
|
|
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 (
|
|
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,
|
|
58139
|
+
const matched = matchRoute(req.method, routePath);
|
|
57907
58140
|
if (!matched) {
|
|
57908
|
-
if (
|
|
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 =
|
|
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
|
`);
|