@hasna/mementos 0.14.68 → 0.14.70
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/README.md +155 -73
- package/dist/cli/__fixtures__/clean-fallback-stub-server.d.ts +2 -0
- package/dist/cli/__fixtures__/clean-fallback-stub-server.d.ts.map +1 -0
- package/dist/cli/commands/io-clean.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-crud.d.ts.map +1 -1
- package/dist/cli/commands/storage.d.ts.map +1 -1
- package/dist/cli/commands/system-mcp.d.ts.map +1 -1
- package/dist/cli/commands/system-profile.d.ts.map +1 -1
- package/dist/cli/global-options.d.ts +27 -0
- package/dist/cli/global-options.d.ts.map +1 -0
- package/dist/cli/index.js +699 -301
- package/dist/cli/register-all.d.ts +17 -0
- package/dist/cli/register-all.d.ts.map +1 -0
- package/dist/cli/startup-side-effects.d.ts +9 -0
- package/dist/cli/startup-side-effects.d.ts.map +1 -0
- package/dist/db/__fixtures__/fail-closed-stub-server.d.ts +2 -0
- package/dist/db/__fixtures__/fail-closed-stub-server.d.ts.map +1 -0
- package/dist/db/agents.d.ts.map +1 -1
- package/dist/db/api-mode.d.ts +70 -2
- package/dist/db/api-mode.d.ts.map +1 -1
- package/dist/db/database.d.ts +16 -0
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/locks.d.ts.map +1 -1
- package/dist/db/memories.d.ts +24 -6
- package/dist/db/memories.d.ts.map +1 -1
- package/dist/db/migrations.d.ts +1 -0
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/pg-migrations.d.ts.map +1 -1
- package/dist/db/session-jobs.d.ts.map +1 -1
- package/dist/db/store-backend.d.ts +33 -0
- package/dist/db/store-backend.d.ts.map +1 -0
- package/dist/index.js +196 -62
- package/dist/lib/auto-memory-queue.d.ts +2 -0
- package/dist/lib/auto-memory-queue.d.ts.map +1 -1
- package/dist/lib/auto-memory.d.ts +1 -0
- package/dist/lib/auto-memory.d.ts.map +1 -1
- package/dist/lib/built-in-hooks.d.ts.map +1 -1
- package/dist/lib/enum-validation.d.ts +20 -0
- package/dist/lib/enum-validation.d.ts.map +1 -0
- package/dist/lib/gdpr.d.ts.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +301 -78
- package/dist/mcp/tools/system-tools-memory-admin.d.ts.map +1 -1
- package/dist/server/helpers.d.ts +11 -0
- package/dist/server/helpers.d.ts.map +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +304 -72
- package/dist/storage.d.ts +11 -2
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +42 -11
- package/dist/test-support/preload-local-store.d.ts +2 -0
- package/dist/test-support/preload-local-store.d.ts.map +1 -0
- package/dist/test-support/store-isolation.d.ts +83 -0
- package/dist/test-support/store-isolation.d.ts.map +1 -0
- package/dist/types/index.d.ts +8 -4
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/mcp/index.js
CHANGED
|
@@ -66,8 +66,19 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
|
66
66
|
var __require = import.meta.require;
|
|
67
67
|
|
|
68
68
|
// src/types/index.ts
|
|
69
|
-
var AgentConflictError, EntityNotFoundError, MemoryNotFoundError, DuplicateMemoryError, InvalidScopeError, VersionConflictError, MemoryConflictError;
|
|
69
|
+
var MEMORY_SCOPES, MEMORY_CATEGORIES, MEMORY_SOURCES, MEMORY_STATUSES, AgentConflictError, EntityNotFoundError, MemoryNotFoundError, DuplicateMemoryError, InvalidScopeError, VersionConflictError, MemoryConflictError;
|
|
70
70
|
var init_types = __esm(() => {
|
|
71
|
+
MEMORY_SCOPES = ["global", "shared", "private", "working"];
|
|
72
|
+
MEMORY_CATEGORIES = [
|
|
73
|
+
"preference",
|
|
74
|
+
"fact",
|
|
75
|
+
"knowledge",
|
|
76
|
+
"history",
|
|
77
|
+
"procedural",
|
|
78
|
+
"resource"
|
|
79
|
+
];
|
|
80
|
+
MEMORY_SOURCES = ["user", "agent", "system", "auto", "imported"];
|
|
81
|
+
MEMORY_STATUSES = ["active", "archived", "expired"];
|
|
71
82
|
AgentConflictError = class AgentConflictError extends Error {
|
|
72
83
|
conflict = true;
|
|
73
84
|
existing_id;
|
|
@@ -134,6 +145,27 @@ var init_types = __esm(() => {
|
|
|
134
145
|
};
|
|
135
146
|
});
|
|
136
147
|
|
|
148
|
+
// src/generated/storage-kit/mode.ts
|
|
149
|
+
function normalizeStorageMode(value) {
|
|
150
|
+
const normalized = value.trim().toLowerCase().replace(/-/g, "_");
|
|
151
|
+
if (normalized === "local")
|
|
152
|
+
return { mode: "local", deprecatedAlias: null };
|
|
153
|
+
if (normalized === "cloud")
|
|
154
|
+
return { mode: "cloud", deprecatedAlias: null };
|
|
155
|
+
if (DEPRECATED_STORAGE_MODE_ALIASES.includes(normalized)) {
|
|
156
|
+
return { mode: "cloud", deprecatedAlias: normalized };
|
|
157
|
+
}
|
|
158
|
+
throw new Error(`Unknown storage mode: ${value}. Use local or cloud.`);
|
|
159
|
+
}
|
|
160
|
+
var DEPRECATED_STORAGE_MODE_ALIASES;
|
|
161
|
+
var init_mode = __esm(() => {
|
|
162
|
+
DEPRECATED_STORAGE_MODE_ALIASES = [
|
|
163
|
+
"remote",
|
|
164
|
+
"hybrid",
|
|
165
|
+
"self_hosted"
|
|
166
|
+
];
|
|
167
|
+
});
|
|
168
|
+
|
|
137
169
|
// src/storage.ts
|
|
138
170
|
import { Database } from "bun:sqlite";
|
|
139
171
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
@@ -392,18 +424,20 @@ function warnDeprecatedStorageMode(alias) {
|
|
|
392
424
|
warnedDeprecatedModes.add(alias);
|
|
393
425
|
process.emitWarning(`${MEMENTOS_STORAGE_ENV.mode}="${alias}" is deprecated; use "cloud". ` + `"${alias}" now maps to pure-remote cloud storage. The local<->remote ` + `sync path is deprecated and is not the fleet cutover path.`, { type: "DeprecationWarning", code: "MEMENTOS_STORAGE_MODE_ALIAS" });
|
|
394
426
|
}
|
|
395
|
-
function
|
|
396
|
-
if (!value)
|
|
427
|
+
function normalizeStorageMode2(value, source) {
|
|
428
|
+
if (!value || !value.trim())
|
|
397
429
|
return null;
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
430
|
+
let normalized;
|
|
431
|
+
try {
|
|
432
|
+
normalized = normalizeStorageMode(value);
|
|
433
|
+
} catch (error) {
|
|
434
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
435
|
+
throw new Error(`mementos: ${source}=${value} is not a valid mode. ${detail}`);
|
|
401
436
|
}
|
|
402
|
-
if (normalized
|
|
403
|
-
warnDeprecatedStorageMode(normalized);
|
|
404
|
-
return "cloud";
|
|
437
|
+
if (normalized.deprecatedAlias) {
|
|
438
|
+
warnDeprecatedStorageMode(normalized.deprecatedAlias);
|
|
405
439
|
}
|
|
406
|
-
return
|
|
440
|
+
return normalized.mode;
|
|
407
441
|
}
|
|
408
442
|
function readConfigFile() {
|
|
409
443
|
if (!existsSync(STORAGE_CONFIG_PATH)) {
|
|
@@ -433,7 +467,7 @@ function getStorageDatabaseUrl() {
|
|
|
433
467
|
}
|
|
434
468
|
function getStorageModeOverride() {
|
|
435
469
|
for (const env of MODE_ENV_NAMES) {
|
|
436
|
-
const value =
|
|
470
|
+
const value = normalizeStorageMode2(readEnv(env.name) ?? undefined, env.name);
|
|
437
471
|
if (value)
|
|
438
472
|
return value;
|
|
439
473
|
}
|
|
@@ -443,7 +477,7 @@ function getStorageConfig() {
|
|
|
443
477
|
const fileConfig = readConfigFile();
|
|
444
478
|
const modeOverride = getStorageModeOverride();
|
|
445
479
|
const envConnectionString = getConfiguredConnectionString();
|
|
446
|
-
const fileMode =
|
|
480
|
+
const fileMode = normalizeStorageMode2(fileConfig.mode, `${STORAGE_CONFIG_PATH} "mode"`);
|
|
447
481
|
const merged = {
|
|
448
482
|
...DEFAULT_STORAGE_CONFIG,
|
|
449
483
|
...fileConfig,
|
|
@@ -854,6 +888,7 @@ CREATE TABLE IF NOT EXISTS _sync_meta (
|
|
|
854
888
|
direction TEXT DEFAULT 'push'
|
|
855
889
|
)`;
|
|
856
890
|
var init_storage = __esm(() => {
|
|
891
|
+
init_mode();
|
|
857
892
|
PgSyncPool = class PgSyncPool {
|
|
858
893
|
worker;
|
|
859
894
|
status;
|
|
@@ -996,7 +1031,7 @@ import { tmpdir } from "os";
|
|
|
996
1031
|
import { join as join2 } from "path";
|
|
997
1032
|
import { writeFileSync as writeFileSync2, unlinkSync } from "fs";
|
|
998
1033
|
import { randomUUID } from "crypto";
|
|
999
|
-
function firstEnv(
|
|
1034
|
+
function firstEnv(keys) {
|
|
1000
1035
|
for (const k of keys) {
|
|
1001
1036
|
const v = process.env[k]?.trim();
|
|
1002
1037
|
if (v)
|
|
@@ -1004,8 +1039,38 @@ function firstEnv(...keys) {
|
|
|
1004
1039
|
}
|
|
1005
1040
|
return;
|
|
1006
1041
|
}
|
|
1042
|
+
function firstEnvKey(keys) {
|
|
1043
|
+
for (const k of keys) {
|
|
1044
|
+
if (process.env[k]?.trim())
|
|
1045
|
+
return k;
|
|
1046
|
+
}
|
|
1047
|
+
return null;
|
|
1048
|
+
}
|
|
1007
1049
|
function hasDatabaseUrl() {
|
|
1008
|
-
return Boolean(firstEnv(
|
|
1050
|
+
return Boolean(firstEnv(DATABASE_URL_ENV_KEYS));
|
|
1051
|
+
}
|
|
1052
|
+
function isLoopbackHost(rawHost) {
|
|
1053
|
+
const host = rawHost.replace(/^\[/, "").replace(/\]$/, "").toLowerCase();
|
|
1054
|
+
return host === "localhost" || host === "::1" || /^127\./.test(host);
|
|
1055
|
+
}
|
|
1056
|
+
function assertRequestAllowedUnderTest(baseUrl) {
|
|
1057
|
+
if (process.env["NODE_ENV"] !== "test")
|
|
1058
|
+
return;
|
|
1059
|
+
if (process.env[ALLOW_REMOTE_API_IN_TESTS_ENV]?.trim())
|
|
1060
|
+
return;
|
|
1061
|
+
let host;
|
|
1062
|
+
try {
|
|
1063
|
+
host = new URL(baseUrl).hostname;
|
|
1064
|
+
} catch {
|
|
1065
|
+
host = "";
|
|
1066
|
+
}
|
|
1067
|
+
if (host && isLoopbackHost(host))
|
|
1068
|
+
return;
|
|
1069
|
+
throw new Error("api-mode: REFUSING to make a cloud request from a test process \u2014 this would write to or read " + "from the SHARED PRODUCTION memory store, where test fixtures are indistinguishable from real " + `memories.
|
|
1070
|
+
` + ` host : ${host || "(unparseable base URL)"}
|
|
1071
|
+
` + ` how this happens: a selector set at module scope (after the bun test preload ran), or \`bun test\` ` + `invoked from a directory with no bunfig.toml so the preload never loaded.
|
|
1072
|
+
` + " fix : build the child/process env via src/test-support/store-isolation.ts, or point the " + `suite at a loopback stub.
|
|
1073
|
+
` + ` override : set ${ALLOW_REMOTE_API_IN_TESTS_ENV}=1 only for a test that must reach a remote endpoint.`);
|
|
1009
1074
|
}
|
|
1010
1075
|
function normalizeBase(raw) {
|
|
1011
1076
|
let base = raw.trim().replace(/\/+$/, "");
|
|
@@ -1013,9 +1078,24 @@ function normalizeBase(raw) {
|
|
|
1013
1078
|
return base;
|
|
1014
1079
|
return `${base}/v1`;
|
|
1015
1080
|
}
|
|
1081
|
+
function assertUnambiguousStoreEnv() {
|
|
1082
|
+
if (firstEnvKey(DB_PATH_ENV_KEYS))
|
|
1083
|
+
return;
|
|
1084
|
+
if (hasDatabaseUrl())
|
|
1085
|
+
return;
|
|
1086
|
+
const urlKey = firstEnvKey(API_URL_ENV_KEYS);
|
|
1087
|
+
const keyKey = firstEnvKey(API_KEY_ENV_KEYS);
|
|
1088
|
+
if (urlKey && !keyKey) {
|
|
1089
|
+
throw new MementosStoreConfigError(`${urlKey} points at the cloud memory store but ${API_KEY_ENV_KEYS[0]} is not set. ` + `Refusing to serve the on-box SQLite store in its place, because it holds a different ` + `dataset. Set ${API_KEY_ENV_KEYS[0]} to reach the cloud store. If you meant to use the ` + `on-box SQLite store, unset ${urlKey} or set ${DB_PATH_ENV_KEYS[0]} explicitly.`);
|
|
1090
|
+
}
|
|
1091
|
+
if (keyKey && !urlKey) {
|
|
1092
|
+
throw new MementosStoreConfigError(`${keyKey} is set but ${API_URL_ENV_KEYS[0]} is not, so the cloud memory store cannot be ` + `reached. Refusing to serve the on-box SQLite store in its place, because it holds a ` + `different dataset. Set ${API_URL_ENV_KEYS[0]} to reach the cloud store. If you meant to ` + `use the on-box SQLite store, unset ${keyKey} or set ${DB_PATH_ENV_KEYS[0]} explicitly.`);
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1016
1095
|
function getApiConfig() {
|
|
1017
|
-
|
|
1018
|
-
const
|
|
1096
|
+
assertUnambiguousStoreEnv();
|
|
1097
|
+
const rawBase = firstEnv(API_URL_ENV_KEYS);
|
|
1098
|
+
const apiKey = firstEnv(API_KEY_ENV_KEYS);
|
|
1019
1099
|
if (!rawBase || !apiKey)
|
|
1020
1100
|
return null;
|
|
1021
1101
|
return { baseUrl: normalizeBase(rawBase), apiKey };
|
|
@@ -1029,6 +1109,7 @@ function apiRequestRaw(method, path, body) {
|
|
|
1029
1109
|
const cfg = getApiConfig();
|
|
1030
1110
|
if (!cfg)
|
|
1031
1111
|
throw new Error("api-mode: not configured (HASNA_MEMENTOS_API_URL / HASNA_MEMENTOS_API_KEY)");
|
|
1112
|
+
assertRequestAllowedUnderTest(cfg.baseUrl);
|
|
1032
1113
|
const url = `${cfg.baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
|
|
1033
1114
|
const hasBody = body !== undefined && body !== null;
|
|
1034
1115
|
const timeout = process.env["HASNA_MEMENTOS_API_TIMEOUT"] || DEFAULT_TIMEOUT_S;
|
|
@@ -1095,13 +1176,13 @@ x-api-key: ${cfg.apiKey}
|
|
|
1095
1176
|
}
|
|
1096
1177
|
return { status, body: respBody };
|
|
1097
1178
|
}
|
|
1098
|
-
function apiJson(method, path, body) {
|
|
1179
|
+
function apiJson(method, path, body, options) {
|
|
1099
1180
|
const raw = apiRequestRaw(method, path, body);
|
|
1100
1181
|
if (raw.status >= 200 && raw.status < 300) {
|
|
1101
1182
|
const data = raw.body.trim() ? JSON.parse(raw.body) : undefined;
|
|
1102
1183
|
return { status: raw.status, data };
|
|
1103
1184
|
}
|
|
1104
|
-
if (raw.status === 404) {
|
|
1185
|
+
if (raw.status === 404 && options?.allow404) {
|
|
1105
1186
|
return { status: 404, data: undefined };
|
|
1106
1187
|
}
|
|
1107
1188
|
let msg = `mementos cloud ${method} ${path} \u2192 ${raw.status}`;
|
|
@@ -1133,8 +1214,19 @@ function toQuery(params) {
|
|
|
1133
1214
|
const s = sp.toString();
|
|
1134
1215
|
return s ? `?${s}` : "";
|
|
1135
1216
|
}
|
|
1136
|
-
var ApiRequestError, DEFAULT_TIMEOUT_S = "45";
|
|
1217
|
+
var API_URL_ENV_KEYS, API_KEY_ENV_KEYS, DATABASE_URL_ENV_KEYS, ALLOW_REMOTE_API_IN_TESTS_ENV = "MEMENTOS_ALLOW_REMOTE_API_IN_TESTS", DB_PATH_ENV_KEYS, MementosStoreConfigError, ApiRequestError, DEFAULT_TIMEOUT_S = "45";
|
|
1137
1218
|
var init_api_mode = __esm(() => {
|
|
1219
|
+
API_URL_ENV_KEYS = ["HASNA_MEMENTOS_API_URL", "MEMENTOS_API_URL"];
|
|
1220
|
+
API_KEY_ENV_KEYS = ["HASNA_MEMENTOS_API_KEY", "MEMENTOS_API_KEY"];
|
|
1221
|
+
DATABASE_URL_ENV_KEYS = ["HASNA_MEMENTOS_DATABASE_URL", "MEMENTOS_DATABASE_URL"];
|
|
1222
|
+
DB_PATH_ENV_KEYS = ["HASNA_MEMENTOS_DB_PATH", "MEMENTOS_DB_PATH"];
|
|
1223
|
+
MementosStoreConfigError = class MementosStoreConfigError extends Error {
|
|
1224
|
+
code = "MEMENTOS_STORE_CONFIG";
|
|
1225
|
+
constructor(message) {
|
|
1226
|
+
super(message);
|
|
1227
|
+
this.name = "MementosStoreConfigError";
|
|
1228
|
+
}
|
|
1229
|
+
};
|
|
1138
1230
|
ApiRequestError = class ApiRequestError extends Error {
|
|
1139
1231
|
status;
|
|
1140
1232
|
body;
|
|
@@ -1148,7 +1240,24 @@ var init_api_mode = __esm(() => {
|
|
|
1148
1240
|
});
|
|
1149
1241
|
|
|
1150
1242
|
// src/db/migrations.ts
|
|
1151
|
-
var
|
|
1243
|
+
var MEMORY_VERSION_SNAPSHOT_TRIGGER = `
|
|
1244
|
+
CREATE TRIGGER IF NOT EXISTS memories_version_snapshot
|
|
1245
|
+
BEFORE UPDATE ON memories
|
|
1246
|
+
WHEN NEW.version > OLD.version
|
|
1247
|
+
BEGIN
|
|
1248
|
+
INSERT OR IGNORE INTO memory_versions (
|
|
1249
|
+
id, memory_id, version, value, importance, scope, category, tags,
|
|
1250
|
+
summary, pinned, status, when_to_use, created_at
|
|
1251
|
+
) VALUES (
|
|
1252
|
+
lower(hex(randomblob(4))) || '-' || lower(hex(randomblob(2))) || '-' ||
|
|
1253
|
+
lower(hex(randomblob(2))) || '-' || lower(hex(randomblob(2))) || '-' ||
|
|
1254
|
+
lower(hex(randomblob(6))),
|
|
1255
|
+
OLD.id, OLD.version, OLD.value, OLD.importance, OLD.scope, OLD.category,
|
|
1256
|
+
OLD.tags, OLD.summary, OLD.pinned, OLD.status, OLD.when_to_use,
|
|
1257
|
+
OLD.updated_at
|
|
1258
|
+
);
|
|
1259
|
+
END;
|
|
1260
|
+
`, MIGRATIONS;
|
|
1152
1261
|
var init_migrations = __esm(() => {
|
|
1153
1262
|
MIGRATIONS = [
|
|
1154
1263
|
`
|
|
@@ -2046,6 +2155,10 @@ CREATE INDEX IF NOT EXISTS idx_memory_reflection_lessons_memory ON memory_reflec
|
|
|
2046
2155
|
CREATE INDEX IF NOT EXISTS idx_memory_reflection_lessons_kind ON memory_reflection_lessons(kind);
|
|
2047
2156
|
|
|
2048
2157
|
INSERT OR IGNORE INTO _migrations (id) VALUES (35);
|
|
2158
|
+
`,
|
|
2159
|
+
`
|
|
2160
|
+
${MEMORY_VERSION_SNAPSHOT_TRIGGER}
|
|
2161
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (36);
|
|
2049
2162
|
`
|
|
2050
2163
|
];
|
|
2051
2164
|
});
|
|
@@ -2060,6 +2173,7 @@ __export(exports_database, {
|
|
|
2060
2173
|
now: () => now,
|
|
2061
2174
|
getDbPath: () => getDbPath,
|
|
2062
2175
|
getDatabase: () => getDatabase,
|
|
2176
|
+
escapeLikePrefix: () => escapeLikePrefix,
|
|
2063
2177
|
closeDatabase: () => closeDatabase
|
|
2064
2178
|
});
|
|
2065
2179
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2, cpSync } from "fs";
|
|
@@ -2234,15 +2348,20 @@ function uuid() {
|
|
|
2234
2348
|
function shortUuid() {
|
|
2235
2349
|
return crypto.randomUUID().slice(0, 8);
|
|
2236
2350
|
}
|
|
2351
|
+
function escapeLikePrefix(s) {
|
|
2352
|
+
return s.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
2353
|
+
}
|
|
2237
2354
|
function resolvePartialId(db, table, partialId) {
|
|
2238
2355
|
if (!ALLOWED_TABLES.has(table)) {
|
|
2239
2356
|
throw new Error(`Invalid table name: ${table}`);
|
|
2240
2357
|
}
|
|
2358
|
+
if (partialId === "")
|
|
2359
|
+
return null;
|
|
2241
2360
|
if (partialId.length >= 36) {
|
|
2242
2361
|
const row = db.query(`SELECT id FROM ${table} WHERE id = ?`).get(partialId);
|
|
2243
2362
|
return row?.id ?? null;
|
|
2244
2363
|
}
|
|
2245
|
-
const rows = db.query(`SELECT id FROM ${table} WHERE id LIKE
|
|
2364
|
+
const rows = db.query(`SELECT id FROM ${table} WHERE id LIKE ? ESCAPE '\\'`).all(`${escapeLikePrefix(partialId)}%`);
|
|
2246
2365
|
if (rows.length === 1) {
|
|
2247
2366
|
return rows[0].id;
|
|
2248
2367
|
}
|
|
@@ -2379,6 +2498,41 @@ var init_redact = __esm(() => {
|
|
|
2379
2498
|
];
|
|
2380
2499
|
});
|
|
2381
2500
|
|
|
2501
|
+
// src/lib/enum-validation.ts
|
|
2502
|
+
function formatEnumViolation(v) {
|
|
2503
|
+
return `Invalid ${v.field}: "${v.value}". Allowed values: ${v.allowed.join(", ")}.`;
|
|
2504
|
+
}
|
|
2505
|
+
function validateEnumField(field, value) {
|
|
2506
|
+
const allowed = ENUM_FIELDS[field];
|
|
2507
|
+
if (!allowed)
|
|
2508
|
+
return null;
|
|
2509
|
+
if (value === undefined || value === null || value === "")
|
|
2510
|
+
return null;
|
|
2511
|
+
if (typeof value === "string" && allowed.includes(value))
|
|
2512
|
+
return null;
|
|
2513
|
+
return { field, value: String(value), allowed };
|
|
2514
|
+
}
|
|
2515
|
+
function validateMemoryEnums(input) {
|
|
2516
|
+
for (const field of Object.keys(ENUM_FIELDS)) {
|
|
2517
|
+
if (!(field in input))
|
|
2518
|
+
continue;
|
|
2519
|
+
const violation = validateEnumField(field, input[field]);
|
|
2520
|
+
if (violation)
|
|
2521
|
+
return violation;
|
|
2522
|
+
}
|
|
2523
|
+
return null;
|
|
2524
|
+
}
|
|
2525
|
+
var ENUM_FIELDS;
|
|
2526
|
+
var init_enum_validation = __esm(() => {
|
|
2527
|
+
init_types();
|
|
2528
|
+
ENUM_FIELDS = {
|
|
2529
|
+
category: MEMORY_CATEGORIES,
|
|
2530
|
+
scope: MEMORY_SCOPES,
|
|
2531
|
+
source: MEMORY_SOURCES,
|
|
2532
|
+
status: MEMORY_STATUSES
|
|
2533
|
+
};
|
|
2534
|
+
});
|
|
2535
|
+
|
|
2382
2536
|
// src/lib/hooks.ts
|
|
2383
2537
|
function generateHookId() {
|
|
2384
2538
|
return `hook_${++_idCounter}_${Date.now().toString(36)}`;
|
|
@@ -2692,7 +2846,10 @@ function parseMemoryRow(row) {
|
|
|
2692
2846
|
}
|
|
2693
2847
|
function createMemory(input, dedupeMode = "merge", db) {
|
|
2694
2848
|
if (!db && isApiMode()) {
|
|
2695
|
-
const { data } = apiJson("POST", "/memories", { ...input, dedupe: dedupeMode });
|
|
2849
|
+
const { status, data } = apiJson("POST", "/memories", { ...input, dedupe: dedupeMode });
|
|
2850
|
+
if (!data || !data.id) {
|
|
2851
|
+
throw new ApiRequestError(`mementos cloud POST /memories \u2192 ${status} but no memory was returned; the write did not persist (key: ${input.key})`, status, "");
|
|
2852
|
+
}
|
|
2696
2853
|
return data;
|
|
2697
2854
|
}
|
|
2698
2855
|
const d = db || getDatabase();
|
|
@@ -2831,16 +2988,25 @@ function bulkUpsertMemories(memories, db) {
|
|
|
2831
2988
|
const d = db || getDatabase();
|
|
2832
2989
|
let inserted = 0;
|
|
2833
2990
|
let skipped = 0;
|
|
2991
|
+
let rejected = 0;
|
|
2834
2992
|
const errors = [];
|
|
2835
|
-
const insert = d.prepare(`INSERT
|
|
2836
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2993
|
+
const insert = d.prepare(`INSERT INTO memories (id, key, value, category, scope, summary, tags, importance, source, status, pinned, agent_id, project_id, session_id, machine_id, namespace, created_by_agent, when_to_use, sequence_group, sequence_order, metadata, access_count, version, expires_at, valid_from, valid_until, ingested_at, created_at, updated_at)
|
|
2994
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2995
|
+
ON CONFLICT DO NOTHING`);
|
|
2837
2996
|
const insertTag = d.prepare("INSERT OR IGNORE INTO memory_tags (memory_id, tag) VALUES (?, ?)");
|
|
2838
2997
|
for (const mem of memories) {
|
|
2839
2998
|
const key = mem["key"];
|
|
2840
2999
|
const id = mem["id"] || uuid();
|
|
2841
3000
|
try {
|
|
2842
3001
|
if (!key) {
|
|
2843
|
-
|
|
3002
|
+
rejected++;
|
|
3003
|
+
errors.push(`rejected row without key (id=${id})`);
|
|
3004
|
+
continue;
|
|
3005
|
+
}
|
|
3006
|
+
const violation = validateMemoryEnums(mem);
|
|
3007
|
+
if (violation) {
|
|
3008
|
+
rejected++;
|
|
3009
|
+
errors.push(`Rejected "${key}": ${formatEnumViolation(violation)}`);
|
|
2844
3010
|
continue;
|
|
2845
3011
|
}
|
|
2846
3012
|
const timestamp = now();
|
|
@@ -2889,10 +3055,11 @@ function bulkUpsertMemories(memories, db) {
|
|
|
2889
3055
|
skipped++;
|
|
2890
3056
|
}
|
|
2891
3057
|
} catch (e) {
|
|
3058
|
+
rejected++;
|
|
2892
3059
|
errors.push(`Failed "${String(key)}": ${e instanceof Error ? e.message : String(e)}`);
|
|
2893
3060
|
}
|
|
2894
3061
|
}
|
|
2895
|
-
return { inserted, skipped, errors, total: memories.length };
|
|
3062
|
+
return { inserted, skipped, rejected, errors, total: memories.length };
|
|
2896
3063
|
}
|
|
2897
3064
|
function ensureMemoryReferences(d, input) {
|
|
2898
3065
|
const t = now();
|
|
@@ -2920,7 +3087,7 @@ function listMemoriesByKey(key, db) {
|
|
|
2920
3087
|
}
|
|
2921
3088
|
function getMemory(id, db) {
|
|
2922
3089
|
if (!db && isApiMode()) {
|
|
2923
|
-
const { status, data } = apiJson("GET", `/memories/${encodeURIComponent(id)}
|
|
3090
|
+
const { status, data } = apiJson("GET", `/memories/${encodeURIComponent(id)}`, undefined, { allow404: true });
|
|
2924
3091
|
return status === 404 ? null : data ?? null;
|
|
2925
3092
|
}
|
|
2926
3093
|
const d = db || getDatabase();
|
|
@@ -3253,36 +3420,22 @@ function getMemoryEmbeddings(ids, db) {
|
|
|
3253
3420
|
}
|
|
3254
3421
|
function updateMemory(id, input, db) {
|
|
3255
3422
|
if (!db && isApiMode()) {
|
|
3256
|
-
const { status, data } = apiJson("PATCH", `/memories/${encodeURIComponent(id)}`, input);
|
|
3423
|
+
const { status, data } = apiJson("PATCH", `/memories/${encodeURIComponent(id)}`, input, { allow404: true });
|
|
3257
3424
|
if (status === 404)
|
|
3258
3425
|
throw new MemoryNotFoundError(id);
|
|
3426
|
+
if (data && typeof input.version === "number" && typeof data.version === "number" && data.version <= input.version) {
|
|
3427
|
+
throw new Error(`Update did not persist for memory ${id}: the server returned success but the record is unchanged ` + `(version still ${data.version}). Your data was NOT written. ` + `The server is likely running a build predating the partial-id fix \u2014 pass the full 36-character id as a workaround.`);
|
|
3428
|
+
}
|
|
3259
3429
|
return data;
|
|
3260
3430
|
}
|
|
3261
3431
|
const d = db || getDatabase();
|
|
3262
3432
|
const existing = getMemory(id, d);
|
|
3263
3433
|
if (!existing)
|
|
3264
3434
|
throw new MemoryNotFoundError(id);
|
|
3435
|
+
const memoryId = existing.id;
|
|
3265
3436
|
if (existing.version !== input.version) {
|
|
3266
3437
|
throw new VersionConflictError(id, input.version, existing.version);
|
|
3267
3438
|
}
|
|
3268
|
-
try {
|
|
3269
|
-
d.run(`INSERT OR IGNORE INTO memory_versions (id, memory_id, version, value, importance, scope, category, tags, summary, pinned, status, when_to_use, created_at)
|
|
3270
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
3271
|
-
uuid(),
|
|
3272
|
-
existing.id,
|
|
3273
|
-
existing.version,
|
|
3274
|
-
existing.value,
|
|
3275
|
-
existing.importance,
|
|
3276
|
-
existing.scope,
|
|
3277
|
-
existing.category,
|
|
3278
|
-
JSON.stringify(existing.tags),
|
|
3279
|
-
existing.summary,
|
|
3280
|
-
existing.pinned ? 1 : 0,
|
|
3281
|
-
existing.status,
|
|
3282
|
-
existing.when_to_use || null,
|
|
3283
|
-
existing.updated_at
|
|
3284
|
-
]);
|
|
3285
|
-
} catch {}
|
|
3286
3439
|
const sets = ["version = version + 1", "updated_at = ?"];
|
|
3287
3440
|
const params = [now()];
|
|
3288
3441
|
if (input.value !== undefined) {
|
|
@@ -3332,15 +3485,18 @@ function updateMemory(id, input, db) {
|
|
|
3332
3485
|
if (input.tags !== undefined) {
|
|
3333
3486
|
sets.push("tags = ?");
|
|
3334
3487
|
params.push(JSON.stringify(input.tags));
|
|
3335
|
-
d.run("DELETE FROM memory_tags WHERE memory_id = ?", [
|
|
3488
|
+
d.run("DELETE FROM memory_tags WHERE memory_id = ?", [memoryId]);
|
|
3336
3489
|
const insertTag = d.prepare("INSERT OR IGNORE INTO memory_tags (memory_id, tag) VALUES (?, ?)");
|
|
3337
3490
|
for (const tag of input.tags) {
|
|
3338
|
-
insertTag.run(
|
|
3491
|
+
insertTag.run(memoryId, tag);
|
|
3339
3492
|
}
|
|
3340
3493
|
}
|
|
3341
|
-
params.push(
|
|
3342
|
-
d.run(`UPDATE memories SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
3343
|
-
|
|
3494
|
+
params.push(memoryId);
|
|
3495
|
+
const result = d.run(`UPDATE memories SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
3496
|
+
if (result.changes === 0) {
|
|
3497
|
+
throw new Error(`Update affected no rows for memory ${memoryId}: the record was read but not written. ` + `This is a bug in @hasna/mementos, not a bad argument \u2014 please report it.`);
|
|
3498
|
+
}
|
|
3499
|
+
const updated = getMemory(memoryId, d);
|
|
3344
3500
|
if (input.value !== undefined) {
|
|
3345
3501
|
try {
|
|
3346
3502
|
const oldLinks = getEntityMemoryLinks(undefined, updated.id, d);
|
|
@@ -3361,14 +3517,15 @@ function updateMemory(id, input, db) {
|
|
|
3361
3517
|
}
|
|
3362
3518
|
function deleteMemory(id, db) {
|
|
3363
3519
|
if (!db && isApiMode()) {
|
|
3364
|
-
const { status } = apiJson("DELETE", `/memories/${encodeURIComponent(id)}
|
|
3520
|
+
const { status } = apiJson("DELETE", `/memories/${encodeURIComponent(id)}`, undefined, { allow404: true });
|
|
3365
3521
|
return status !== 404;
|
|
3366
3522
|
}
|
|
3367
3523
|
const d = db || getDatabase();
|
|
3368
|
-
const
|
|
3524
|
+
const memoryId = resolvePartialId(d, "memories", id) ?? id;
|
|
3525
|
+
const result = d.run("DELETE FROM memories WHERE id = ?", [memoryId]);
|
|
3369
3526
|
if (result.changes > 0) {
|
|
3370
3527
|
hookRegistry.runHooks("PostMemoryDelete", {
|
|
3371
|
-
memoryId
|
|
3528
|
+
memoryId,
|
|
3372
3529
|
timestamp: Date.now()
|
|
3373
3530
|
});
|
|
3374
3531
|
}
|
|
@@ -3382,11 +3539,12 @@ function bulkDeleteMemories(ids, db) {
|
|
|
3382
3539
|
return data?.deleted ?? 0;
|
|
3383
3540
|
}
|
|
3384
3541
|
const d = db || getDatabase();
|
|
3385
|
-
const
|
|
3386
|
-
const
|
|
3542
|
+
const resolvedIds = ids.map((id) => resolvePartialId(d, "memories", id) ?? id);
|
|
3543
|
+
const placeholders = resolvedIds.map(() => "?").join(",");
|
|
3544
|
+
const countRow = d.query(`SELECT COUNT(*) as c FROM memories WHERE id IN (${placeholders})`).get(...resolvedIds);
|
|
3387
3545
|
const count = countRow.c;
|
|
3388
3546
|
if (count > 0) {
|
|
3389
|
-
d.run(`DELETE FROM memories WHERE id IN (${placeholders})`,
|
|
3547
|
+
d.run(`DELETE FROM memories WHERE id IN (${placeholders})`, resolvedIds);
|
|
3390
3548
|
}
|
|
3391
3549
|
return count;
|
|
3392
3550
|
}
|
|
@@ -3513,6 +3671,7 @@ var init_memories = __esm(() => {
|
|
|
3513
3671
|
init_types();
|
|
3514
3672
|
init_database();
|
|
3515
3673
|
init_redact();
|
|
3674
|
+
init_enum_validation();
|
|
3516
3675
|
init_hooks();
|
|
3517
3676
|
init_poisoning();
|
|
3518
3677
|
init_entity_memories();
|
|
@@ -3660,7 +3819,7 @@ function createWebhookHook(input, db) {
|
|
|
3660
3819
|
}
|
|
3661
3820
|
function getWebhookHook(id, db) {
|
|
3662
3821
|
if (!db && isApiMode()) {
|
|
3663
|
-
const { status, data } = apiJson("GET", `/webhooks/${encodeURIComponent(id)}
|
|
3822
|
+
const { status, data } = apiJson("GET", `/webhooks/${encodeURIComponent(id)}`, undefined, { allow404: true });
|
|
3664
3823
|
if (status === 404 || !data)
|
|
3665
3824
|
return null;
|
|
3666
3825
|
return data;
|
|
@@ -3696,7 +3855,7 @@ function updateWebhookHook(id, updates, db) {
|
|
|
3696
3855
|
enabled: updates.enabled,
|
|
3697
3856
|
priority: updates.priority,
|
|
3698
3857
|
description: updates.description
|
|
3699
|
-
});
|
|
3858
|
+
}, { allow404: true });
|
|
3700
3859
|
if (status === 404 || !data)
|
|
3701
3860
|
return null;
|
|
3702
3861
|
return data;
|
|
@@ -3727,7 +3886,7 @@ function updateWebhookHook(id, updates, db) {
|
|
|
3727
3886
|
}
|
|
3728
3887
|
function deleteWebhookHook(id, db) {
|
|
3729
3888
|
if (!db && isApiMode()) {
|
|
3730
|
-
const { status } = apiJson("DELETE", `/webhooks/${encodeURIComponent(id)}
|
|
3889
|
+
const { status } = apiJson("DELETE", `/webhooks/${encodeURIComponent(id)}`, undefined, { allow404: true });
|
|
3731
3890
|
return status === 204 || status === 200;
|
|
3732
3891
|
}
|
|
3733
3892
|
const d = db || getDatabase();
|
|
@@ -3825,7 +3984,7 @@ function createEntity(input, db) {
|
|
|
3825
3984
|
}
|
|
3826
3985
|
function getEntity(id, db) {
|
|
3827
3986
|
if (!db && isApiMode()) {
|
|
3828
|
-
const { status, data } = apiJson("GET", `/entities/${encodeURIComponent(id)}
|
|
3987
|
+
const { status, data } = apiJson("GET", `/entities/${encodeURIComponent(id)}`, undefined, { allow404: true });
|
|
3829
3988
|
if (status === 404 || !data)
|
|
3830
3989
|
throw new EntityNotFoundError(id);
|
|
3831
3990
|
return data;
|
|
@@ -3908,7 +4067,7 @@ function listEntities(filter = {}, db) {
|
|
|
3908
4067
|
}
|
|
3909
4068
|
function updateEntity(id, input, db) {
|
|
3910
4069
|
if (!db && isApiMode()) {
|
|
3911
|
-
const { status, data } = apiJson("PATCH", `/entities/${encodeURIComponent(id)}`, input);
|
|
4070
|
+
const { status, data } = apiJson("PATCH", `/entities/${encodeURIComponent(id)}`, input, { allow404: true });
|
|
3912
4071
|
if (status === 404 || !data)
|
|
3913
4072
|
throw new EntityNotFoundError(id);
|
|
3914
4073
|
return data;
|
|
@@ -3941,7 +4100,7 @@ function updateEntity(id, input, db) {
|
|
|
3941
4100
|
}
|
|
3942
4101
|
function deleteEntity(id, db) {
|
|
3943
4102
|
if (!db && isApiMode()) {
|
|
3944
|
-
const { status } = apiJson("DELETE", `/entities/${encodeURIComponent(id)}
|
|
4103
|
+
const { status } = apiJson("DELETE", `/entities/${encodeURIComponent(id)}`, undefined, { allow404: true });
|
|
3945
4104
|
if (status === 404)
|
|
3946
4105
|
throw new EntityNotFoundError(id);
|
|
3947
4106
|
return;
|
|
@@ -4920,7 +5079,7 @@ function createRelation(input, db) {
|
|
|
4920
5079
|
}
|
|
4921
5080
|
function getRelation(id, db) {
|
|
4922
5081
|
if (!db && isApiMode()) {
|
|
4923
|
-
const { status, data } = apiJson("GET", `/relations/${encodeURIComponent(id)}
|
|
5082
|
+
const { status, data } = apiJson("GET", `/relations/${encodeURIComponent(id)}`, undefined, { allow404: true });
|
|
4924
5083
|
if (status === 404 || !data)
|
|
4925
5084
|
throw new Error(`Relation not found: ${id}`);
|
|
4926
5085
|
return data;
|
|
@@ -4965,7 +5124,7 @@ function listRelations(filter, db) {
|
|
|
4965
5124
|
}
|
|
4966
5125
|
function deleteRelation(id, db) {
|
|
4967
5126
|
if (!db && isApiMode()) {
|
|
4968
|
-
const { status } = apiJson("DELETE", `/relations/${encodeURIComponent(id)}
|
|
5127
|
+
const { status } = apiJson("DELETE", `/relations/${encodeURIComponent(id)}`, undefined, { allow404: true });
|
|
4969
5128
|
if (status === 404)
|
|
4970
5129
|
throw new Error(`Relation not found: ${id}`);
|
|
4971
5130
|
return;
|
|
@@ -5587,6 +5746,32 @@ class AutoMemoryQueue {
|
|
|
5587
5746
|
getStats() {
|
|
5588
5747
|
return { ...this.stats, pending: this.queue.length };
|
|
5589
5748
|
}
|
|
5749
|
+
async waitForIdleForTests(timeoutMs = 3000) {
|
|
5750
|
+
const start = Date.now();
|
|
5751
|
+
while (Date.now() - start < timeoutMs) {
|
|
5752
|
+
if (this.queue.length === 0 && this.activeCount === 0)
|
|
5753
|
+
return;
|
|
5754
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
5755
|
+
}
|
|
5756
|
+
throw new Error("autoMemoryQueue did not become idle before test reset");
|
|
5757
|
+
}
|
|
5758
|
+
resetForTests(handler) {
|
|
5759
|
+
if (this.activeCount !== 0) {
|
|
5760
|
+
throw new Error("Cannot reset autoMemoryQueue while jobs are processing");
|
|
5761
|
+
}
|
|
5762
|
+
this.queue = [];
|
|
5763
|
+
this.running = false;
|
|
5764
|
+
this.stats = {
|
|
5765
|
+
pending: 0,
|
|
5766
|
+
processing: 0,
|
|
5767
|
+
processed: 0,
|
|
5768
|
+
failed: 0,
|
|
5769
|
+
dropped: 0
|
|
5770
|
+
};
|
|
5771
|
+
if (handler !== undefined) {
|
|
5772
|
+
this.handler = handler;
|
|
5773
|
+
}
|
|
5774
|
+
}
|
|
5590
5775
|
startLoop() {
|
|
5591
5776
|
this.running = true;
|
|
5592
5777
|
this.loop();
|
|
@@ -5632,6 +5817,7 @@ var init_auto_memory_queue = __esm(() => {
|
|
|
5632
5817
|
// src/lib/auto-memory.ts
|
|
5633
5818
|
var exports_auto_memory = {};
|
|
5634
5819
|
__export(exports_auto_memory, {
|
|
5820
|
+
resetAutoMemoryForTests: () => resetAutoMemoryForTests,
|
|
5635
5821
|
processConversationTurn: () => processConversationTurn,
|
|
5636
5822
|
getAutoMemoryStats: () => getAutoMemoryStats,
|
|
5637
5823
|
configureAutoMemory: () => configureAutoMemory
|
|
@@ -5792,6 +5978,12 @@ function getAutoMemoryStats() {
|
|
|
5792
5978
|
function configureAutoMemory(config) {
|
|
5793
5979
|
providerRegistry.configure(config);
|
|
5794
5980
|
}
|
|
5981
|
+
async function resetAutoMemoryForTests() {
|
|
5982
|
+
if (autoMemoryQueue.getStats().processing > 0) {
|
|
5983
|
+
await autoMemoryQueue.waitForIdleForTests();
|
|
5984
|
+
}
|
|
5985
|
+
autoMemoryQueue.resetForTests(processJob);
|
|
5986
|
+
}
|
|
5795
5987
|
var DEDUP_SIMILARITY_THRESHOLD = 0.85;
|
|
5796
5988
|
var init_auto_memory = __esm(() => {
|
|
5797
5989
|
init_memories();
|
|
@@ -6480,6 +6672,8 @@ var init_built_in_hooks = __esm(() => {
|
|
|
6480
6672
|
priority: 100,
|
|
6481
6673
|
description: "Trigger async LLM entity extraction when a memory is saved",
|
|
6482
6674
|
handler: async (ctx) => {
|
|
6675
|
+
if (process.env["NODE_ENV"] === "test")
|
|
6676
|
+
return;
|
|
6483
6677
|
if (ctx.wasUpdated)
|
|
6484
6678
|
return;
|
|
6485
6679
|
const processConversationTurn2 = await getAutoMemory();
|
|
@@ -11854,11 +12048,14 @@ __export(exports_gdpr, {
|
|
|
11854
12048
|
});
|
|
11855
12049
|
function gdprErase(identifier, options = {}, db) {
|
|
11856
12050
|
const d = db || getDatabase();
|
|
12051
|
+
if (identifier.trim() === "") {
|
|
12052
|
+
throw new Error("GDPR erase requires a non-empty identifier: an empty or whitespace-only " + "identifier matches every memory and would redact the entire store");
|
|
12053
|
+
}
|
|
11857
12054
|
const timestamp = now();
|
|
11858
12055
|
const conditions = [
|
|
11859
|
-
"(key LIKE ? OR value LIKE ? OR summary LIKE ? OR tags LIKE ? OR metadata LIKE ?)"
|
|
12056
|
+
"(key LIKE ? ESCAPE '\\' OR value LIKE ? ESCAPE '\\' OR summary LIKE ? ESCAPE '\\' OR tags LIKE ? ESCAPE '\\' OR metadata LIKE ? ESCAPE '\\')"
|
|
11860
12057
|
];
|
|
11861
|
-
const searchParam = `%${identifier}%`;
|
|
12058
|
+
const searchParam = `%${escapeLikePrefix(identifier)}%`;
|
|
11862
12059
|
const params = [searchParam, searchParam, searchParam, searchParam, searchParam];
|
|
11863
12060
|
if (options.project_id) {
|
|
11864
12061
|
conditions.push("project_id = ?");
|
|
@@ -12780,6 +12977,31 @@ var init_pg_migrations = __esm(() => {
|
|
|
12780
12977
|
);
|
|
12781
12978
|
CREATE INDEX IF NOT EXISTS idx_task_comments_task ON task_comments(task_id);
|
|
12782
12979
|
CREATE INDEX IF NOT EXISTS idx_task_comments_agent ON task_comments(agent_id);
|
|
12980
|
+
`,
|
|
12981
|
+
`
|
|
12982
|
+
CREATE OR REPLACE FUNCTION snapshot_memory_version() RETURNS trigger AS $$
|
|
12983
|
+
BEGIN
|
|
12984
|
+
INSERT INTO memory_versions (
|
|
12985
|
+
id, memory_id, version, value, importance, scope, category, tags,
|
|
12986
|
+
summary, pinned, status, when_to_use, created_at
|
|
12987
|
+
) VALUES (
|
|
12988
|
+
gen_random_uuid()::text,
|
|
12989
|
+
OLD.id, OLD.version, OLD.value, OLD.importance, OLD.scope, OLD.category,
|
|
12990
|
+
OLD.tags, OLD.summary, OLD.pinned, OLD.status, OLD.when_to_use,
|
|
12991
|
+
OLD.updated_at
|
|
12992
|
+
) ON CONFLICT DO NOTHING;
|
|
12993
|
+
RETURN NEW;
|
|
12994
|
+
END;
|
|
12995
|
+
$$ LANGUAGE plpgsql;
|
|
12996
|
+
|
|
12997
|
+
DROP TRIGGER IF EXISTS memories_version_snapshot ON memories;
|
|
12998
|
+
CREATE TRIGGER memories_version_snapshot
|
|
12999
|
+
BEFORE UPDATE ON memories
|
|
13000
|
+
FOR EACH ROW
|
|
13001
|
+
WHEN (NEW.version > OLD.version)
|
|
13002
|
+
EXECUTE FUNCTION snapshot_memory_version();
|
|
13003
|
+
|
|
13004
|
+
INSERT INTO _migrations (id) VALUES (36) ON CONFLICT DO NOTHING;
|
|
12783
13005
|
`
|
|
12784
13006
|
];
|
|
12785
13007
|
});
|
|
@@ -55581,7 +55803,7 @@ function registerAgent(name, sessionId, description, role, projectId, db) {
|
|
|
55581
55803
|
}
|
|
55582
55804
|
function getAgent(idOrName, db) {
|
|
55583
55805
|
if (!db && isApiMode()) {
|
|
55584
|
-
const { status, data } = apiJson("GET", `/agents/${encodeURIComponent(idOrName)}
|
|
55806
|
+
const { status, data } = apiJson("GET", `/agents/${encodeURIComponent(idOrName)}`, undefined, { allow404: true });
|
|
55585
55807
|
if (status === 404 || !data)
|
|
55586
55808
|
return null;
|
|
55587
55809
|
return data;
|
|
@@ -55593,7 +55815,7 @@ function getAgent(idOrName, db) {
|
|
|
55593
55815
|
row = d.query("SELECT * FROM agents WHERE LOWER(name) = ?").get(idOrName.trim().toLowerCase());
|
|
55594
55816
|
if (row)
|
|
55595
55817
|
return parseAgentRow(row);
|
|
55596
|
-
const rows = d.query("SELECT * FROM agents WHERE id LIKE ?").all(`${idOrName}%`);
|
|
55818
|
+
const rows = d.query("SELECT * FROM agents WHERE id LIKE ? ESCAPE '\\'").all(`${escapeLikePrefix(idOrName)}%`);
|
|
55597
55819
|
if (rows.length === 1)
|
|
55598
55820
|
return parseAgentRow(rows[0]);
|
|
55599
55821
|
return null;
|
|
@@ -55634,7 +55856,7 @@ function listAgentsByProject(projectId, db) {
|
|
|
55634
55856
|
}
|
|
55635
55857
|
function updateAgent(id, updates, db) {
|
|
55636
55858
|
if (!db && isApiMode()) {
|
|
55637
|
-
const { status, data } = apiJson("PATCH", `/agents/${encodeURIComponent(id)}`, updates);
|
|
55859
|
+
const { status, data } = apiJson("PATCH", `/agents/${encodeURIComponent(id)}`, updates, { allow404: true });
|
|
55638
55860
|
if (status === 404 || !data)
|
|
55639
55861
|
return null;
|
|
55640
55862
|
return data;
|
|
@@ -55719,7 +55941,7 @@ function registerProject(name, path, description, memoryPrefix, db) {
|
|
|
55719
55941
|
}
|
|
55720
55942
|
function getProject(idOrPath, db) {
|
|
55721
55943
|
if (!db && isApiMode()) {
|
|
55722
|
-
const { status, data } = apiJson("GET", `/projects/${encodeURIComponent(idOrPath)}
|
|
55944
|
+
const { status, data } = apiJson("GET", `/projects/${encodeURIComponent(idOrPath)}`, undefined, { allow404: true });
|
|
55723
55945
|
if (status === 404 || !data)
|
|
55724
55946
|
return null;
|
|
55725
55947
|
return data;
|
|
@@ -58856,7 +59078,7 @@ function createSubscription(input, db) {
|
|
|
58856
59078
|
}
|
|
58857
59079
|
function deleteSubscription(id, db) {
|
|
58858
59080
|
if (!db && isApiMode()) {
|
|
58859
|
-
const { status } = apiJson("DELETE", `/subscriptions/${encodeURIComponent(id)}
|
|
59081
|
+
const { status } = apiJson("DELETE", `/subscriptions/${encodeURIComponent(id)}`, undefined, { allow404: true });
|
|
58860
59082
|
return status !== 404;
|
|
58861
59083
|
}
|
|
58862
59084
|
const d = db || getDatabase();
|
|
@@ -60772,7 +60994,7 @@ function acquireLock(agentId, resourceType, resourceId, lockType = "exclusive",
|
|
|
60772
60994
|
}
|
|
60773
60995
|
function releaseLock(lockId, agentId, db) {
|
|
60774
60996
|
if (!db && isApiMode()) {
|
|
60775
|
-
const { status } = apiJson("DELETE", `/locks/${encodeURIComponent(lockId)}`, { agent_id: agentId });
|
|
60997
|
+
const { status } = apiJson("DELETE", `/locks/${encodeURIComponent(lockId)}`, { agent_id: agentId }, { allow404: true });
|
|
60776
60998
|
return status !== 404;
|
|
60777
60999
|
}
|
|
60778
61000
|
const d = db || getDatabase();
|
|
@@ -62517,7 +62739,7 @@ function createSessionJob(input, db) {
|
|
|
62517
62739
|
}
|
|
62518
62740
|
function getSessionJob(id, db) {
|
|
62519
62741
|
if (!db && isApiMode()) {
|
|
62520
|
-
const { status, data } = apiJson("GET", `/sessions/jobs/${encodeURIComponent(id)}
|
|
62742
|
+
const { status, data } = apiJson("GET", `/sessions/jobs/${encodeURIComponent(id)}`, undefined, { allow404: true });
|
|
62521
62743
|
if (status === 404 || !data)
|
|
62522
62744
|
return null;
|
|
62523
62745
|
return data;
|
|
@@ -63645,7 +63867,7 @@ ${lines.join(`
|
|
|
63645
63867
|
}
|
|
63646
63868
|
});
|
|
63647
63869
|
server.tool("memory_gdpr_erase", "GDPR right to be forgotten: erase all memories containing a PII identifier. Replaces content with [REDACTED], preserves anonymized audit trail. IRREVERSIBLE.", {
|
|
63648
|
-
identifier: z.string().describe("PII to search for and erase (name, email, etc.)"),
|
|
63870
|
+
identifier: z.string().min(1).describe("PII to search for and erase (name, email, etc.)"),
|
|
63649
63871
|
project_id: z.string().optional(),
|
|
63650
63872
|
dry_run: z.boolean().optional().describe("Preview what would be erased without actually erasing (default: false)")
|
|
63651
63873
|
}, async (args) => {
|
|
@@ -65310,11 +65532,12 @@ function hasFlag(...flags) {
|
|
|
65310
65532
|
function printHelp() {
|
|
65311
65533
|
process.stdout.write(`Usage: mementos-mcp [options]
|
|
65312
65534
|
|
|
65313
|
-
Mementos MCP server (
|
|
65535
|
+
Mementos MCP server (Streamable HTTP transport by default)
|
|
65314
65536
|
|
|
65315
65537
|
Options:
|
|
65316
|
-
--http Serve MCP over Streamable HTTP (127.0.0.1)
|
|
65317
|
-
--
|
|
65538
|
+
--http Serve MCP over Streamable HTTP (default, 127.0.0.1)
|
|
65539
|
+
--stdio Serve MCP over stdio (env: MCP_STDIO=1)
|
|
65540
|
+
--port <number> HTTP port (default: 8867, env: MCP_HTTP_PORT)
|
|
65318
65541
|
-h, --help Show help
|
|
65319
65542
|
-V, --version Show version
|
|
65320
65543
|
`);
|