@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/server/index.js
CHANGED
|
@@ -65,6 +65,27 @@ var __export = (target, all) => {
|
|
|
65
65
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
66
66
|
var __require = import.meta.require;
|
|
67
67
|
|
|
68
|
+
// src/generated/storage-kit/mode.ts
|
|
69
|
+
function normalizeStorageMode(value) {
|
|
70
|
+
const normalized = value.trim().toLowerCase().replace(/-/g, "_");
|
|
71
|
+
if (normalized === "local")
|
|
72
|
+
return { mode: "local", deprecatedAlias: null };
|
|
73
|
+
if (normalized === "cloud")
|
|
74
|
+
return { mode: "cloud", deprecatedAlias: null };
|
|
75
|
+
if (DEPRECATED_STORAGE_MODE_ALIASES.includes(normalized)) {
|
|
76
|
+
return { mode: "cloud", deprecatedAlias: normalized };
|
|
77
|
+
}
|
|
78
|
+
throw new Error(`Unknown storage mode: ${value}. Use local or cloud.`);
|
|
79
|
+
}
|
|
80
|
+
var DEPRECATED_STORAGE_MODE_ALIASES;
|
|
81
|
+
var init_mode = __esm(() => {
|
|
82
|
+
DEPRECATED_STORAGE_MODE_ALIASES = [
|
|
83
|
+
"remote",
|
|
84
|
+
"hybrid",
|
|
85
|
+
"self_hosted"
|
|
86
|
+
];
|
|
87
|
+
});
|
|
88
|
+
|
|
68
89
|
// src/storage.ts
|
|
69
90
|
import { Database } from "bun:sqlite";
|
|
70
91
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
@@ -281,18 +302,20 @@ function warnDeprecatedStorageMode(alias) {
|
|
|
281
302
|
warnedDeprecatedModes.add(alias);
|
|
282
303
|
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" });
|
|
283
304
|
}
|
|
284
|
-
function
|
|
285
|
-
if (!value)
|
|
305
|
+
function normalizeStorageMode2(value, source) {
|
|
306
|
+
if (!value || !value.trim())
|
|
286
307
|
return null;
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
308
|
+
let normalized;
|
|
309
|
+
try {
|
|
310
|
+
normalized = normalizeStorageMode(value);
|
|
311
|
+
} catch (error) {
|
|
312
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
313
|
+
throw new Error(`mementos: ${source}=${value} is not a valid mode. ${detail}`);
|
|
290
314
|
}
|
|
291
|
-
if (normalized
|
|
292
|
-
warnDeprecatedStorageMode(normalized);
|
|
293
|
-
return "cloud";
|
|
315
|
+
if (normalized.deprecatedAlias) {
|
|
316
|
+
warnDeprecatedStorageMode(normalized.deprecatedAlias);
|
|
294
317
|
}
|
|
295
|
-
return
|
|
318
|
+
return normalized.mode;
|
|
296
319
|
}
|
|
297
320
|
function readConfigFile() {
|
|
298
321
|
if (!existsSync2(STORAGE_CONFIG_PATH)) {
|
|
@@ -317,7 +340,7 @@ function getStorageDatabaseUrl() {
|
|
|
317
340
|
}
|
|
318
341
|
function getStorageModeOverride() {
|
|
319
342
|
for (const env of MODE_ENV_NAMES) {
|
|
320
|
-
const value =
|
|
343
|
+
const value = normalizeStorageMode2(readEnv(env.name) ?? undefined, env.name);
|
|
321
344
|
if (value)
|
|
322
345
|
return value;
|
|
323
346
|
}
|
|
@@ -327,7 +350,7 @@ function getStorageConfig() {
|
|
|
327
350
|
const fileConfig = readConfigFile();
|
|
328
351
|
const modeOverride = getStorageModeOverride();
|
|
329
352
|
const envConnectionString = getConfiguredConnectionString();
|
|
330
|
-
const fileMode =
|
|
353
|
+
const fileMode = normalizeStorageMode2(fileConfig.mode, `${STORAGE_CONFIG_PATH} "mode"`);
|
|
331
354
|
const merged = {
|
|
332
355
|
...DEFAULT_STORAGE_CONFIG,
|
|
333
356
|
...fileConfig,
|
|
@@ -449,6 +472,7 @@ function getStorageConnectionString(dbName = "mementos") {
|
|
|
449
472
|
}
|
|
450
473
|
var _serverContext = false, PgSyncPool, MEMENTOS_STORAGE_ENV, MEMENTOS_STORAGE_FALLBACK_ENV, LOCAL_DATA_DIR, DEFAULT_STORAGE_CONFIG, STORAGE_CONFIG_DIR, STORAGE_CONFIG_PATH, DATABASE_ENV_NAMES, MODE_ENV_NAMES, warnedDeprecatedModes, SECRET_QUERY_PARAMS;
|
|
451
474
|
var init_storage = __esm(() => {
|
|
475
|
+
init_mode();
|
|
452
476
|
PgSyncPool = class PgSyncPool {
|
|
453
477
|
worker;
|
|
454
478
|
status;
|
|
@@ -568,7 +592,7 @@ import { tmpdir } from "os";
|
|
|
568
592
|
import { join as join3 } from "path";
|
|
569
593
|
import { writeFileSync as writeFileSync3, unlinkSync as unlinkSync2 } from "fs";
|
|
570
594
|
import { randomUUID } from "crypto";
|
|
571
|
-
function firstEnv(
|
|
595
|
+
function firstEnv(keys) {
|
|
572
596
|
for (const k of keys) {
|
|
573
597
|
const v = process.env[k]?.trim();
|
|
574
598
|
if (v)
|
|
@@ -576,8 +600,38 @@ function firstEnv(...keys) {
|
|
|
576
600
|
}
|
|
577
601
|
return;
|
|
578
602
|
}
|
|
603
|
+
function firstEnvKey(keys) {
|
|
604
|
+
for (const k of keys) {
|
|
605
|
+
if (process.env[k]?.trim())
|
|
606
|
+
return k;
|
|
607
|
+
}
|
|
608
|
+
return null;
|
|
609
|
+
}
|
|
579
610
|
function hasDatabaseUrl() {
|
|
580
|
-
return Boolean(firstEnv(
|
|
611
|
+
return Boolean(firstEnv(DATABASE_URL_ENV_KEYS));
|
|
612
|
+
}
|
|
613
|
+
function isLoopbackHost(rawHost) {
|
|
614
|
+
const host = rawHost.replace(/^\[/, "").replace(/\]$/, "").toLowerCase();
|
|
615
|
+
return host === "localhost" || host === "::1" || /^127\./.test(host);
|
|
616
|
+
}
|
|
617
|
+
function assertRequestAllowedUnderTest(baseUrl) {
|
|
618
|
+
if (process.env["NODE_ENV"] !== "test")
|
|
619
|
+
return;
|
|
620
|
+
if (process.env[ALLOW_REMOTE_API_IN_TESTS_ENV]?.trim())
|
|
621
|
+
return;
|
|
622
|
+
let host;
|
|
623
|
+
try {
|
|
624
|
+
host = new URL(baseUrl).hostname;
|
|
625
|
+
} catch {
|
|
626
|
+
host = "";
|
|
627
|
+
}
|
|
628
|
+
if (host && isLoopbackHost(host))
|
|
629
|
+
return;
|
|
630
|
+
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.
|
|
631
|
+
` + ` host : ${host || "(unparseable base URL)"}
|
|
632
|
+
` + ` 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.
|
|
633
|
+
` + " fix : build the child/process env via src/test-support/store-isolation.ts, or point the " + `suite at a loopback stub.
|
|
634
|
+
` + ` override : set ${ALLOW_REMOTE_API_IN_TESTS_ENV}=1 only for a test that must reach a remote endpoint.`);
|
|
581
635
|
}
|
|
582
636
|
function normalizeBase(raw) {
|
|
583
637
|
let base = raw.trim().replace(/\/+$/, "");
|
|
@@ -585,9 +639,24 @@ function normalizeBase(raw) {
|
|
|
585
639
|
return base;
|
|
586
640
|
return `${base}/v1`;
|
|
587
641
|
}
|
|
642
|
+
function assertUnambiguousStoreEnv() {
|
|
643
|
+
if (firstEnvKey(DB_PATH_ENV_KEYS))
|
|
644
|
+
return;
|
|
645
|
+
if (hasDatabaseUrl())
|
|
646
|
+
return;
|
|
647
|
+
const urlKey = firstEnvKey(API_URL_ENV_KEYS);
|
|
648
|
+
const keyKey = firstEnvKey(API_KEY_ENV_KEYS);
|
|
649
|
+
if (urlKey && !keyKey) {
|
|
650
|
+
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.`);
|
|
651
|
+
}
|
|
652
|
+
if (keyKey && !urlKey) {
|
|
653
|
+
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.`);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
588
656
|
function getApiConfig() {
|
|
589
|
-
|
|
590
|
-
const
|
|
657
|
+
assertUnambiguousStoreEnv();
|
|
658
|
+
const rawBase = firstEnv(API_URL_ENV_KEYS);
|
|
659
|
+
const apiKey = firstEnv(API_KEY_ENV_KEYS);
|
|
591
660
|
if (!rawBase || !apiKey)
|
|
592
661
|
return null;
|
|
593
662
|
return { baseUrl: normalizeBase(rawBase), apiKey };
|
|
@@ -601,6 +670,7 @@ function apiRequestRaw(method, path, body) {
|
|
|
601
670
|
const cfg = getApiConfig();
|
|
602
671
|
if (!cfg)
|
|
603
672
|
throw new Error("api-mode: not configured (HASNA_MEMENTOS_API_URL / HASNA_MEMENTOS_API_KEY)");
|
|
673
|
+
assertRequestAllowedUnderTest(cfg.baseUrl);
|
|
604
674
|
const url = `${cfg.baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
|
|
605
675
|
const hasBody = body !== undefined && body !== null;
|
|
606
676
|
const timeout = process.env["HASNA_MEMENTOS_API_TIMEOUT"] || DEFAULT_TIMEOUT_S;
|
|
@@ -667,13 +737,13 @@ x-api-key: ${cfg.apiKey}
|
|
|
667
737
|
}
|
|
668
738
|
return { status, body: respBody };
|
|
669
739
|
}
|
|
670
|
-
function apiJson(method, path, body) {
|
|
740
|
+
function apiJson(method, path, body, options) {
|
|
671
741
|
const raw = apiRequestRaw(method, path, body);
|
|
672
742
|
if (raw.status >= 200 && raw.status < 300) {
|
|
673
743
|
const data = raw.body.trim() ? JSON.parse(raw.body) : undefined;
|
|
674
744
|
return { status: raw.status, data };
|
|
675
745
|
}
|
|
676
|
-
if (raw.status === 404) {
|
|
746
|
+
if (raw.status === 404 && options?.allow404) {
|
|
677
747
|
return { status: 404, data: undefined };
|
|
678
748
|
}
|
|
679
749
|
let msg = `mementos cloud ${method} ${path} \u2192 ${raw.status}`;
|
|
@@ -705,8 +775,19 @@ function toQuery(params) {
|
|
|
705
775
|
const s = sp.toString();
|
|
706
776
|
return s ? `?${s}` : "";
|
|
707
777
|
}
|
|
708
|
-
var ApiRequestError, DEFAULT_TIMEOUT_S = "45";
|
|
778
|
+
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";
|
|
709
779
|
var init_api_mode = __esm(() => {
|
|
780
|
+
API_URL_ENV_KEYS = ["HASNA_MEMENTOS_API_URL", "MEMENTOS_API_URL"];
|
|
781
|
+
API_KEY_ENV_KEYS = ["HASNA_MEMENTOS_API_KEY", "MEMENTOS_API_KEY"];
|
|
782
|
+
DATABASE_URL_ENV_KEYS = ["HASNA_MEMENTOS_DATABASE_URL", "MEMENTOS_DATABASE_URL"];
|
|
783
|
+
DB_PATH_ENV_KEYS = ["HASNA_MEMENTOS_DB_PATH", "MEMENTOS_DB_PATH"];
|
|
784
|
+
MementosStoreConfigError = class MementosStoreConfigError extends Error {
|
|
785
|
+
code = "MEMENTOS_STORE_CONFIG";
|
|
786
|
+
constructor(message) {
|
|
787
|
+
super(message);
|
|
788
|
+
this.name = "MementosStoreConfigError";
|
|
789
|
+
}
|
|
790
|
+
};
|
|
710
791
|
ApiRequestError = class ApiRequestError extends Error {
|
|
711
792
|
status;
|
|
712
793
|
body;
|
|
@@ -720,7 +801,24 @@ var init_api_mode = __esm(() => {
|
|
|
720
801
|
});
|
|
721
802
|
|
|
722
803
|
// src/db/migrations.ts
|
|
723
|
-
var
|
|
804
|
+
var MEMORY_VERSION_SNAPSHOT_TRIGGER = `
|
|
805
|
+
CREATE TRIGGER IF NOT EXISTS memories_version_snapshot
|
|
806
|
+
BEFORE UPDATE ON memories
|
|
807
|
+
WHEN NEW.version > OLD.version
|
|
808
|
+
BEGIN
|
|
809
|
+
INSERT OR IGNORE INTO memory_versions (
|
|
810
|
+
id, memory_id, version, value, importance, scope, category, tags,
|
|
811
|
+
summary, pinned, status, when_to_use, created_at
|
|
812
|
+
) VALUES (
|
|
813
|
+
lower(hex(randomblob(4))) || '-' || lower(hex(randomblob(2))) || '-' ||
|
|
814
|
+
lower(hex(randomblob(2))) || '-' || lower(hex(randomblob(2))) || '-' ||
|
|
815
|
+
lower(hex(randomblob(6))),
|
|
816
|
+
OLD.id, OLD.version, OLD.value, OLD.importance, OLD.scope, OLD.category,
|
|
817
|
+
OLD.tags, OLD.summary, OLD.pinned, OLD.status, OLD.when_to_use,
|
|
818
|
+
OLD.updated_at
|
|
819
|
+
);
|
|
820
|
+
END;
|
|
821
|
+
`, MIGRATIONS;
|
|
724
822
|
var init_migrations = __esm(() => {
|
|
725
823
|
MIGRATIONS = [
|
|
726
824
|
`
|
|
@@ -1618,6 +1716,10 @@ CREATE INDEX IF NOT EXISTS idx_memory_reflection_lessons_memory ON memory_reflec
|
|
|
1618
1716
|
CREATE INDEX IF NOT EXISTS idx_memory_reflection_lessons_kind ON memory_reflection_lessons(kind);
|
|
1619
1717
|
|
|
1620
1718
|
INSERT OR IGNORE INTO _migrations (id) VALUES (35);
|
|
1719
|
+
`,
|
|
1720
|
+
`
|
|
1721
|
+
${MEMORY_VERSION_SNAPSHOT_TRIGGER}
|
|
1722
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (36);
|
|
1621
1723
|
`
|
|
1622
1724
|
];
|
|
1623
1725
|
});
|
|
@@ -1632,6 +1734,7 @@ __export(exports_database, {
|
|
|
1632
1734
|
now: () => now,
|
|
1633
1735
|
getDbPath: () => getDbPath2,
|
|
1634
1736
|
getDatabase: () => getDatabase,
|
|
1737
|
+
escapeLikePrefix: () => escapeLikePrefix,
|
|
1635
1738
|
closeDatabase: () => closeDatabase
|
|
1636
1739
|
});
|
|
1637
1740
|
import { existsSync as existsSync3, mkdirSync as mkdirSync3, cpSync as cpSync2 } from "fs";
|
|
@@ -1806,15 +1909,20 @@ function uuid() {
|
|
|
1806
1909
|
function shortUuid() {
|
|
1807
1910
|
return crypto.randomUUID().slice(0, 8);
|
|
1808
1911
|
}
|
|
1912
|
+
function escapeLikePrefix(s) {
|
|
1913
|
+
return s.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
1914
|
+
}
|
|
1809
1915
|
function resolvePartialId(db, table, partialId) {
|
|
1810
1916
|
if (!ALLOWED_TABLES.has(table)) {
|
|
1811
1917
|
throw new Error(`Invalid table name: ${table}`);
|
|
1812
1918
|
}
|
|
1919
|
+
if (partialId === "")
|
|
1920
|
+
return null;
|
|
1813
1921
|
if (partialId.length >= 36) {
|
|
1814
1922
|
const row = db.query(`SELECT id FROM ${table} WHERE id = ?`).get(partialId);
|
|
1815
1923
|
return row?.id ?? null;
|
|
1816
1924
|
}
|
|
1817
|
-
const rows = db.query(`SELECT id FROM ${table} WHERE id LIKE
|
|
1925
|
+
const rows = db.query(`SELECT id FROM ${table} WHERE id LIKE ? ESCAPE '\\'`).all(`${escapeLikePrefix(partialId)}%`);
|
|
1818
1926
|
if (rows.length === 1) {
|
|
1819
1927
|
return rows[0].id;
|
|
1820
1928
|
}
|
|
@@ -1926,8 +2034,19 @@ var init_hooks = __esm(() => {
|
|
|
1926
2034
|
});
|
|
1927
2035
|
|
|
1928
2036
|
// src/types/index.ts
|
|
1929
|
-
var AgentConflictError, EntityNotFoundError, MemoryNotFoundError, DuplicateMemoryError, VersionConflictError, MemoryConflictError;
|
|
2037
|
+
var MEMORY_SCOPES, MEMORY_CATEGORIES, MEMORY_SOURCES, MEMORY_STATUSES, AgentConflictError, EntityNotFoundError, MemoryNotFoundError, DuplicateMemoryError, VersionConflictError, MemoryConflictError;
|
|
1930
2038
|
var init_types = __esm(() => {
|
|
2039
|
+
MEMORY_SCOPES = ["global", "shared", "private", "working"];
|
|
2040
|
+
MEMORY_CATEGORIES = [
|
|
2041
|
+
"preference",
|
|
2042
|
+
"fact",
|
|
2043
|
+
"knowledge",
|
|
2044
|
+
"history",
|
|
2045
|
+
"procedural",
|
|
2046
|
+
"resource"
|
|
2047
|
+
];
|
|
2048
|
+
MEMORY_SOURCES = ["user", "agent", "system", "auto", "imported"];
|
|
2049
|
+
MEMORY_STATUSES = ["active", "archived", "expired"];
|
|
1931
2050
|
AgentConflictError = class AgentConflictError extends Error {
|
|
1932
2051
|
conflict = true;
|
|
1933
2052
|
existing_id;
|
|
@@ -2090,6 +2209,41 @@ var init_redact = __esm(() => {
|
|
|
2090
2209
|
];
|
|
2091
2210
|
});
|
|
2092
2211
|
|
|
2212
|
+
// src/lib/enum-validation.ts
|
|
2213
|
+
function formatEnumViolation(v) {
|
|
2214
|
+
return `Invalid ${v.field}: "${v.value}". Allowed values: ${v.allowed.join(", ")}.`;
|
|
2215
|
+
}
|
|
2216
|
+
function validateEnumField(field, value) {
|
|
2217
|
+
const allowed = ENUM_FIELDS[field];
|
|
2218
|
+
if (!allowed)
|
|
2219
|
+
return null;
|
|
2220
|
+
if (value === undefined || value === null || value === "")
|
|
2221
|
+
return null;
|
|
2222
|
+
if (typeof value === "string" && allowed.includes(value))
|
|
2223
|
+
return null;
|
|
2224
|
+
return { field, value: String(value), allowed };
|
|
2225
|
+
}
|
|
2226
|
+
function validateMemoryEnums(input) {
|
|
2227
|
+
for (const field of Object.keys(ENUM_FIELDS)) {
|
|
2228
|
+
if (!(field in input))
|
|
2229
|
+
continue;
|
|
2230
|
+
const violation = validateEnumField(field, input[field]);
|
|
2231
|
+
if (violation)
|
|
2232
|
+
return violation;
|
|
2233
|
+
}
|
|
2234
|
+
return null;
|
|
2235
|
+
}
|
|
2236
|
+
var ENUM_FIELDS;
|
|
2237
|
+
var init_enum_validation = __esm(() => {
|
|
2238
|
+
init_types();
|
|
2239
|
+
ENUM_FIELDS = {
|
|
2240
|
+
category: MEMORY_CATEGORIES,
|
|
2241
|
+
scope: MEMORY_SCOPES,
|
|
2242
|
+
source: MEMORY_SOURCES,
|
|
2243
|
+
status: MEMORY_STATUSES
|
|
2244
|
+
};
|
|
2245
|
+
});
|
|
2246
|
+
|
|
2093
2247
|
// src/lib/poisoning.ts
|
|
2094
2248
|
function computeTrustScore(value, key, existingMemories, claimedImportance) {
|
|
2095
2249
|
let score = 1;
|
|
@@ -2320,7 +2474,10 @@ function parseMemoryRow(row) {
|
|
|
2320
2474
|
}
|
|
2321
2475
|
function createMemory(input, dedupeMode = "merge", db) {
|
|
2322
2476
|
if (!db && isApiMode()) {
|
|
2323
|
-
const { data } = apiJson("POST", "/memories", { ...input, dedupe: dedupeMode });
|
|
2477
|
+
const { status, data } = apiJson("POST", "/memories", { ...input, dedupe: dedupeMode });
|
|
2478
|
+
if (!data || !data.id) {
|
|
2479
|
+
throw new ApiRequestError(`mementos cloud POST /memories \u2192 ${status} but no memory was returned; the write did not persist (key: ${input.key})`, status, "");
|
|
2480
|
+
}
|
|
2324
2481
|
return data;
|
|
2325
2482
|
}
|
|
2326
2483
|
const d = db || getDatabase();
|
|
@@ -2459,16 +2616,25 @@ function bulkUpsertMemories(memories, db) {
|
|
|
2459
2616
|
const d = db || getDatabase();
|
|
2460
2617
|
let inserted = 0;
|
|
2461
2618
|
let skipped = 0;
|
|
2619
|
+
let rejected = 0;
|
|
2462
2620
|
const errors = [];
|
|
2463
|
-
const insert = d.prepare(`INSERT
|
|
2464
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2621
|
+
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)
|
|
2622
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2623
|
+
ON CONFLICT DO NOTHING`);
|
|
2465
2624
|
const insertTag = d.prepare("INSERT OR IGNORE INTO memory_tags (memory_id, tag) VALUES (?, ?)");
|
|
2466
2625
|
for (const mem of memories) {
|
|
2467
2626
|
const key = mem["key"];
|
|
2468
2627
|
const id = mem["id"] || uuid();
|
|
2469
2628
|
try {
|
|
2470
2629
|
if (!key) {
|
|
2471
|
-
|
|
2630
|
+
rejected++;
|
|
2631
|
+
errors.push(`rejected row without key (id=${id})`);
|
|
2632
|
+
continue;
|
|
2633
|
+
}
|
|
2634
|
+
const violation = validateMemoryEnums(mem);
|
|
2635
|
+
if (violation) {
|
|
2636
|
+
rejected++;
|
|
2637
|
+
errors.push(`Rejected "${key}": ${formatEnumViolation(violation)}`);
|
|
2472
2638
|
continue;
|
|
2473
2639
|
}
|
|
2474
2640
|
const timestamp = now();
|
|
@@ -2517,10 +2683,11 @@ function bulkUpsertMemories(memories, db) {
|
|
|
2517
2683
|
skipped++;
|
|
2518
2684
|
}
|
|
2519
2685
|
} catch (e) {
|
|
2686
|
+
rejected++;
|
|
2520
2687
|
errors.push(`Failed "${String(key)}": ${e instanceof Error ? e.message : String(e)}`);
|
|
2521
2688
|
}
|
|
2522
2689
|
}
|
|
2523
|
-
return { inserted, skipped, errors, total: memories.length };
|
|
2690
|
+
return { inserted, skipped, rejected, errors, total: memories.length };
|
|
2524
2691
|
}
|
|
2525
2692
|
function ensureMemoryReferences(d, input) {
|
|
2526
2693
|
const t = now();
|
|
@@ -2548,7 +2715,7 @@ function listMemoriesByKey(key, db) {
|
|
|
2548
2715
|
}
|
|
2549
2716
|
function getMemory(id, db) {
|
|
2550
2717
|
if (!db && isApiMode()) {
|
|
2551
|
-
const { status, data } = apiJson("GET", `/memories/${encodeURIComponent(id)}
|
|
2718
|
+
const { status, data } = apiJson("GET", `/memories/${encodeURIComponent(id)}`, undefined, { allow404: true });
|
|
2552
2719
|
return status === 404 ? null : data ?? null;
|
|
2553
2720
|
}
|
|
2554
2721
|
const d = db || getDatabase();
|
|
@@ -2881,36 +3048,22 @@ function getMemoryEmbeddings(ids, db) {
|
|
|
2881
3048
|
}
|
|
2882
3049
|
function updateMemory(id, input, db) {
|
|
2883
3050
|
if (!db && isApiMode()) {
|
|
2884
|
-
const { status, data } = apiJson("PATCH", `/memories/${encodeURIComponent(id)}`, input);
|
|
3051
|
+
const { status, data } = apiJson("PATCH", `/memories/${encodeURIComponent(id)}`, input, { allow404: true });
|
|
2885
3052
|
if (status === 404)
|
|
2886
3053
|
throw new MemoryNotFoundError(id);
|
|
3054
|
+
if (data && typeof input.version === "number" && typeof data.version === "number" && data.version <= input.version) {
|
|
3055
|
+
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.`);
|
|
3056
|
+
}
|
|
2887
3057
|
return data;
|
|
2888
3058
|
}
|
|
2889
3059
|
const d = db || getDatabase();
|
|
2890
3060
|
const existing = getMemory(id, d);
|
|
2891
3061
|
if (!existing)
|
|
2892
3062
|
throw new MemoryNotFoundError(id);
|
|
3063
|
+
const memoryId = existing.id;
|
|
2893
3064
|
if (existing.version !== input.version) {
|
|
2894
3065
|
throw new VersionConflictError(id, input.version, existing.version);
|
|
2895
3066
|
}
|
|
2896
|
-
try {
|
|
2897
|
-
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)
|
|
2898
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
2899
|
-
uuid(),
|
|
2900
|
-
existing.id,
|
|
2901
|
-
existing.version,
|
|
2902
|
-
existing.value,
|
|
2903
|
-
existing.importance,
|
|
2904
|
-
existing.scope,
|
|
2905
|
-
existing.category,
|
|
2906
|
-
JSON.stringify(existing.tags),
|
|
2907
|
-
existing.summary,
|
|
2908
|
-
existing.pinned ? 1 : 0,
|
|
2909
|
-
existing.status,
|
|
2910
|
-
existing.when_to_use || null,
|
|
2911
|
-
existing.updated_at
|
|
2912
|
-
]);
|
|
2913
|
-
} catch {}
|
|
2914
3067
|
const sets = ["version = version + 1", "updated_at = ?"];
|
|
2915
3068
|
const params = [now()];
|
|
2916
3069
|
if (input.value !== undefined) {
|
|
@@ -2960,15 +3113,18 @@ function updateMemory(id, input, db) {
|
|
|
2960
3113
|
if (input.tags !== undefined) {
|
|
2961
3114
|
sets.push("tags = ?");
|
|
2962
3115
|
params.push(JSON.stringify(input.tags));
|
|
2963
|
-
d.run("DELETE FROM memory_tags WHERE memory_id = ?", [
|
|
3116
|
+
d.run("DELETE FROM memory_tags WHERE memory_id = ?", [memoryId]);
|
|
2964
3117
|
const insertTag = d.prepare("INSERT OR IGNORE INTO memory_tags (memory_id, tag) VALUES (?, ?)");
|
|
2965
3118
|
for (const tag of input.tags) {
|
|
2966
|
-
insertTag.run(
|
|
3119
|
+
insertTag.run(memoryId, tag);
|
|
2967
3120
|
}
|
|
2968
3121
|
}
|
|
2969
|
-
params.push(
|
|
2970
|
-
d.run(`UPDATE memories SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
2971
|
-
|
|
3122
|
+
params.push(memoryId);
|
|
3123
|
+
const result = d.run(`UPDATE memories SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
3124
|
+
if (result.changes === 0) {
|
|
3125
|
+
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.`);
|
|
3126
|
+
}
|
|
3127
|
+
const updated = getMemory(memoryId, d);
|
|
2972
3128
|
if (input.value !== undefined) {
|
|
2973
3129
|
try {
|
|
2974
3130
|
const oldLinks = getEntityMemoryLinks(undefined, updated.id, d);
|
|
@@ -2989,14 +3145,15 @@ function updateMemory(id, input, db) {
|
|
|
2989
3145
|
}
|
|
2990
3146
|
function deleteMemory(id, db) {
|
|
2991
3147
|
if (!db && isApiMode()) {
|
|
2992
|
-
const { status } = apiJson("DELETE", `/memories/${encodeURIComponent(id)}
|
|
3148
|
+
const { status } = apiJson("DELETE", `/memories/${encodeURIComponent(id)}`, undefined, { allow404: true });
|
|
2993
3149
|
return status !== 404;
|
|
2994
3150
|
}
|
|
2995
3151
|
const d = db || getDatabase();
|
|
2996
|
-
const
|
|
3152
|
+
const memoryId = resolvePartialId(d, "memories", id) ?? id;
|
|
3153
|
+
const result = d.run("DELETE FROM memories WHERE id = ?", [memoryId]);
|
|
2997
3154
|
if (result.changes > 0) {
|
|
2998
3155
|
hookRegistry.runHooks("PostMemoryDelete", {
|
|
2999
|
-
memoryId
|
|
3156
|
+
memoryId,
|
|
3000
3157
|
timestamp: Date.now()
|
|
3001
3158
|
});
|
|
3002
3159
|
}
|
|
@@ -3010,11 +3167,12 @@ function bulkDeleteMemories(ids, db) {
|
|
|
3010
3167
|
return data?.deleted ?? 0;
|
|
3011
3168
|
}
|
|
3012
3169
|
const d = db || getDatabase();
|
|
3013
|
-
const
|
|
3014
|
-
const
|
|
3170
|
+
const resolvedIds = ids.map((id) => resolvePartialId(d, "memories", id) ?? id);
|
|
3171
|
+
const placeholders = resolvedIds.map(() => "?").join(",");
|
|
3172
|
+
const countRow = d.query(`SELECT COUNT(*) as c FROM memories WHERE id IN (${placeholders})`).get(...resolvedIds);
|
|
3015
3173
|
const count = countRow.c;
|
|
3016
3174
|
if (count > 0) {
|
|
3017
|
-
d.run(`DELETE FROM memories WHERE id IN (${placeholders})`,
|
|
3175
|
+
d.run(`DELETE FROM memories WHERE id IN (${placeholders})`, resolvedIds);
|
|
3018
3176
|
}
|
|
3019
3177
|
return count;
|
|
3020
3178
|
}
|
|
@@ -3141,6 +3299,7 @@ var init_memories = __esm(() => {
|
|
|
3141
3299
|
init_types();
|
|
3142
3300
|
init_database();
|
|
3143
3301
|
init_redact();
|
|
3302
|
+
init_enum_validation();
|
|
3144
3303
|
init_hooks();
|
|
3145
3304
|
init_poisoning();
|
|
3146
3305
|
init_entity_memories();
|
|
@@ -3209,7 +3368,7 @@ function createEntity(input, db) {
|
|
|
3209
3368
|
}
|
|
3210
3369
|
function getEntity(id, db) {
|
|
3211
3370
|
if (!db && isApiMode()) {
|
|
3212
|
-
const { status, data } = apiJson("GET", `/entities/${encodeURIComponent(id)}
|
|
3371
|
+
const { status, data } = apiJson("GET", `/entities/${encodeURIComponent(id)}`, undefined, { allow404: true });
|
|
3213
3372
|
if (status === 404 || !data)
|
|
3214
3373
|
throw new EntityNotFoundError(id);
|
|
3215
3374
|
return data;
|
|
@@ -3292,7 +3451,7 @@ function listEntities(filter = {}, db) {
|
|
|
3292
3451
|
}
|
|
3293
3452
|
function updateEntity(id, input, db) {
|
|
3294
3453
|
if (!db && isApiMode()) {
|
|
3295
|
-
const { status, data } = apiJson("PATCH", `/entities/${encodeURIComponent(id)}`, input);
|
|
3454
|
+
const { status, data } = apiJson("PATCH", `/entities/${encodeURIComponent(id)}`, input, { allow404: true });
|
|
3296
3455
|
if (status === 404 || !data)
|
|
3297
3456
|
throw new EntityNotFoundError(id);
|
|
3298
3457
|
return data;
|
|
@@ -3325,7 +3484,7 @@ function updateEntity(id, input, db) {
|
|
|
3325
3484
|
}
|
|
3326
3485
|
function deleteEntity(id, db) {
|
|
3327
3486
|
if (!db && isApiMode()) {
|
|
3328
|
-
const { status } = apiJson("DELETE", `/entities/${encodeURIComponent(id)}
|
|
3487
|
+
const { status } = apiJson("DELETE", `/entities/${encodeURIComponent(id)}`, undefined, { allow404: true });
|
|
3329
3488
|
if (status === 404)
|
|
3330
3489
|
throw new EntityNotFoundError(id);
|
|
3331
3490
|
return;
|
|
@@ -4253,7 +4412,7 @@ function createRelation(input, db) {
|
|
|
4253
4412
|
}
|
|
4254
4413
|
function getRelation(id, db) {
|
|
4255
4414
|
if (!db && isApiMode()) {
|
|
4256
|
-
const { status, data } = apiJson("GET", `/relations/${encodeURIComponent(id)}
|
|
4415
|
+
const { status, data } = apiJson("GET", `/relations/${encodeURIComponent(id)}`, undefined, { allow404: true });
|
|
4257
4416
|
if (status === 404 || !data)
|
|
4258
4417
|
throw new Error(`Relation not found: ${id}`);
|
|
4259
4418
|
return data;
|
|
@@ -4298,7 +4457,7 @@ function listRelations(filter, db) {
|
|
|
4298
4457
|
}
|
|
4299
4458
|
function deleteRelation(id, db) {
|
|
4300
4459
|
if (!db && isApiMode()) {
|
|
4301
|
-
const { status } = apiJson("DELETE", `/relations/${encodeURIComponent(id)}
|
|
4460
|
+
const { status } = apiJson("DELETE", `/relations/${encodeURIComponent(id)}`, undefined, { allow404: true });
|
|
4302
4461
|
if (status === 404)
|
|
4303
4462
|
throw new Error(`Relation not found: ${id}`);
|
|
4304
4463
|
return;
|
|
@@ -4947,6 +5106,32 @@ class AutoMemoryQueue {
|
|
|
4947
5106
|
getStats() {
|
|
4948
5107
|
return { ...this.stats, pending: this.queue.length };
|
|
4949
5108
|
}
|
|
5109
|
+
async waitForIdleForTests(timeoutMs = 3000) {
|
|
5110
|
+
const start = Date.now();
|
|
5111
|
+
while (Date.now() - start < timeoutMs) {
|
|
5112
|
+
if (this.queue.length === 0 && this.activeCount === 0)
|
|
5113
|
+
return;
|
|
5114
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
5115
|
+
}
|
|
5116
|
+
throw new Error("autoMemoryQueue did not become idle before test reset");
|
|
5117
|
+
}
|
|
5118
|
+
resetForTests(handler) {
|
|
5119
|
+
if (this.activeCount !== 0) {
|
|
5120
|
+
throw new Error("Cannot reset autoMemoryQueue while jobs are processing");
|
|
5121
|
+
}
|
|
5122
|
+
this.queue = [];
|
|
5123
|
+
this.running = false;
|
|
5124
|
+
this.stats = {
|
|
5125
|
+
pending: 0,
|
|
5126
|
+
processing: 0,
|
|
5127
|
+
processed: 0,
|
|
5128
|
+
failed: 0,
|
|
5129
|
+
dropped: 0
|
|
5130
|
+
};
|
|
5131
|
+
if (handler !== undefined) {
|
|
5132
|
+
this.handler = handler;
|
|
5133
|
+
}
|
|
5134
|
+
}
|
|
4950
5135
|
startLoop() {
|
|
4951
5136
|
this.running = true;
|
|
4952
5137
|
this.loop();
|
|
@@ -4992,6 +5177,7 @@ var init_auto_memory_queue = __esm(() => {
|
|
|
4992
5177
|
// src/lib/auto-memory.ts
|
|
4993
5178
|
var exports_auto_memory = {};
|
|
4994
5179
|
__export(exports_auto_memory, {
|
|
5180
|
+
resetAutoMemoryForTests: () => resetAutoMemoryForTests,
|
|
4995
5181
|
processConversationTurn: () => processConversationTurn,
|
|
4996
5182
|
getAutoMemoryStats: () => getAutoMemoryStats,
|
|
4997
5183
|
configureAutoMemory: () => configureAutoMemory
|
|
@@ -5152,6 +5338,12 @@ function getAutoMemoryStats() {
|
|
|
5152
5338
|
function configureAutoMemory(config) {
|
|
5153
5339
|
providerRegistry.configure(config);
|
|
5154
5340
|
}
|
|
5341
|
+
async function resetAutoMemoryForTests() {
|
|
5342
|
+
if (autoMemoryQueue.getStats().processing > 0) {
|
|
5343
|
+
await autoMemoryQueue.waitForIdleForTests();
|
|
5344
|
+
}
|
|
5345
|
+
autoMemoryQueue.resetForTests(processJob);
|
|
5346
|
+
}
|
|
5155
5347
|
var DEDUP_SIMILARITY_THRESHOLD = 0.85;
|
|
5156
5348
|
var init_auto_memory = __esm(() => {
|
|
5157
5349
|
init_memories();
|
|
@@ -52822,7 +53014,7 @@ function createWebhookHook(input, db) {
|
|
|
52822
53014
|
}
|
|
52823
53015
|
function getWebhookHook(id, db) {
|
|
52824
53016
|
if (!db && isApiMode()) {
|
|
52825
|
-
const { status, data } = apiJson("GET", `/webhooks/${encodeURIComponent(id)}
|
|
53017
|
+
const { status, data } = apiJson("GET", `/webhooks/${encodeURIComponent(id)}`, undefined, { allow404: true });
|
|
52826
53018
|
if (status === 404 || !data)
|
|
52827
53019
|
return null;
|
|
52828
53020
|
return data;
|
|
@@ -52858,7 +53050,7 @@ function updateWebhookHook(id, updates, db) {
|
|
|
52858
53050
|
enabled: updates.enabled,
|
|
52859
53051
|
priority: updates.priority,
|
|
52860
53052
|
description: updates.description
|
|
52861
|
-
});
|
|
53053
|
+
}, { allow404: true });
|
|
52862
53054
|
if (status === 404 || !data)
|
|
52863
53055
|
return null;
|
|
52864
53056
|
return data;
|
|
@@ -52889,7 +53081,7 @@ function updateWebhookHook(id, updates, db) {
|
|
|
52889
53081
|
}
|
|
52890
53082
|
function deleteWebhookHook(id, db) {
|
|
52891
53083
|
if (!db && isApiMode()) {
|
|
52892
|
-
const { status } = apiJson("DELETE", `/webhooks/${encodeURIComponent(id)}
|
|
53084
|
+
const { status } = apiJson("DELETE", `/webhooks/${encodeURIComponent(id)}`, undefined, { allow404: true });
|
|
52893
53085
|
return status === 204 || status === 200;
|
|
52894
53086
|
}
|
|
52895
53087
|
const d = db || getDatabase();
|
|
@@ -52924,6 +53116,8 @@ hookRegistry.register({
|
|
|
52924
53116
|
priority: 100,
|
|
52925
53117
|
description: "Trigger async LLM entity extraction when a memory is saved",
|
|
52926
53118
|
handler: async (ctx) => {
|
|
53119
|
+
if (process.env["NODE_ENV"] === "test")
|
|
53120
|
+
return;
|
|
52927
53121
|
if (ctx.wasUpdated)
|
|
52928
53122
|
return;
|
|
52929
53123
|
const processConversationTurn2 = await getAutoMemory();
|
|
@@ -53161,7 +53355,7 @@ function createSessionJob(input, db) {
|
|
|
53161
53355
|
}
|
|
53162
53356
|
function getSessionJob(id, db) {
|
|
53163
53357
|
if (!db && isApiMode()) {
|
|
53164
|
-
const { status, data } = apiJson("GET", `/sessions/jobs/${encodeURIComponent(id)}
|
|
53358
|
+
const { status, data } = apiJson("GET", `/sessions/jobs/${encodeURIComponent(id)}`, undefined, { allow404: true });
|
|
53165
53359
|
if (status === 404 || !data)
|
|
53166
53360
|
return null;
|
|
53167
53361
|
return data;
|
|
@@ -54296,6 +54490,16 @@ function errorResponse(message, status, details) {
|
|
|
54296
54490
|
body["details"] = details;
|
|
54297
54491
|
return json(body, status);
|
|
54298
54492
|
}
|
|
54493
|
+
function describeConstraintViolation(e) {
|
|
54494
|
+
if (!e || typeof e !== "object")
|
|
54495
|
+
return null;
|
|
54496
|
+
const code = String(e.code ?? "");
|
|
54497
|
+
const message = String(e.message ?? "");
|
|
54498
|
+
const isConstraint = code.startsWith("SQLITE_CONSTRAINT") || /^23\d{3}$/.test(code) || /constraint failed/i.test(message);
|
|
54499
|
+
if (!isConstraint)
|
|
54500
|
+
return null;
|
|
54501
|
+
return `Request rejected by a database constraint: ${message || code}. Check that enum fields (category, scope, source, status) and referenced ids are valid.`;
|
|
54502
|
+
}
|
|
54299
54503
|
var MAX_BODY_BYTES = 1 * 1024 * 1024;
|
|
54300
54504
|
async function readJson(req) {
|
|
54301
54505
|
try {
|
|
@@ -54496,6 +54700,7 @@ var FORMAT_UNITS = [
|
|
|
54496
54700
|
|
|
54497
54701
|
// src/server/routes/memories-crud.ts
|
|
54498
54702
|
init_router();
|
|
54703
|
+
init_enum_validation();
|
|
54499
54704
|
init_types();
|
|
54500
54705
|
addRoute("GET", "/api/memories", (_req, url) => {
|
|
54501
54706
|
const q = getSearchParams(url);
|
|
@@ -54544,6 +54749,14 @@ addRoute("POST", "/api/memories", async (req) => {
|
|
|
54544
54749
|
if (!body["key"] || !body["value"]) {
|
|
54545
54750
|
return errorResponse("Missing required fields: key, value", 400);
|
|
54546
54751
|
}
|
|
54752
|
+
const violation = validateMemoryEnums(body);
|
|
54753
|
+
if (violation) {
|
|
54754
|
+
return errorResponse(formatEnumViolation(violation), 400, {
|
|
54755
|
+
field: violation.field,
|
|
54756
|
+
value: violation.value,
|
|
54757
|
+
allowed: violation.allowed
|
|
54758
|
+
});
|
|
54759
|
+
}
|
|
54547
54760
|
try {
|
|
54548
54761
|
if (body["ttl_ms"] !== undefined && typeof body["ttl_ms"] === "string") {
|
|
54549
54762
|
body["ttl_ms"] = parseDuration(body["ttl_ms"]);
|
|
@@ -54573,6 +54786,14 @@ addRoute("PATCH", "/api/memories/:id", async (req, _url, params) => {
|
|
|
54573
54786
|
if (!body) {
|
|
54574
54787
|
return errorResponse("Invalid JSON body", 400);
|
|
54575
54788
|
}
|
|
54789
|
+
const patchViolation = validateMemoryEnums(body);
|
|
54790
|
+
if (patchViolation) {
|
|
54791
|
+
return errorResponse(formatEnumViolation(patchViolation), 400, {
|
|
54792
|
+
field: patchViolation.field,
|
|
54793
|
+
value: patchViolation.value,
|
|
54794
|
+
allowed: patchViolation.allowed
|
|
54795
|
+
});
|
|
54796
|
+
}
|
|
54576
54797
|
const updateBody = { ...body };
|
|
54577
54798
|
if (updateBody["version"] === undefined) {
|
|
54578
54799
|
const existing = getMemory(params["id"]);
|
|
@@ -55602,6 +55823,12 @@ addRoute("POST", "/api/memories/bulk-upsert", async (req) => {
|
|
|
55602
55823
|
}
|
|
55603
55824
|
const memoriesArr = body["memories"];
|
|
55604
55825
|
const result = bulkUpsertMemories(memoriesArr);
|
|
55826
|
+
if (result.rejected > 0) {
|
|
55827
|
+
return json({
|
|
55828
|
+
...result,
|
|
55829
|
+
error: `${result.rejected} of ${result.total} memories were rejected and did not persist. See errors.`
|
|
55830
|
+
}, 400);
|
|
55831
|
+
}
|
|
55605
55832
|
return json(result, 201);
|
|
55606
55833
|
});
|
|
55607
55834
|
|
|
@@ -55985,7 +56212,7 @@ function registerAgent(name, sessionId, description, role, projectId, db) {
|
|
|
55985
56212
|
}
|
|
55986
56213
|
function getAgent(idOrName, db) {
|
|
55987
56214
|
if (!db && isApiMode()) {
|
|
55988
|
-
const { status, data } = apiJson("GET", `/agents/${encodeURIComponent(idOrName)}
|
|
56215
|
+
const { status, data } = apiJson("GET", `/agents/${encodeURIComponent(idOrName)}`, undefined, { allow404: true });
|
|
55989
56216
|
if (status === 404 || !data)
|
|
55990
56217
|
return null;
|
|
55991
56218
|
return data;
|
|
@@ -55997,7 +56224,7 @@ function getAgent(idOrName, db) {
|
|
|
55997
56224
|
row = d.query("SELECT * FROM agents WHERE LOWER(name) = ?").get(idOrName.trim().toLowerCase());
|
|
55998
56225
|
if (row)
|
|
55999
56226
|
return parseAgentRow(row);
|
|
56000
|
-
const rows = d.query("SELECT * FROM agents WHERE id LIKE ?").all(`${idOrName}%`);
|
|
56227
|
+
const rows = d.query("SELECT * FROM agents WHERE id LIKE ? ESCAPE '\\'").all(`${escapeLikePrefix(idOrName)}%`);
|
|
56001
56228
|
if (rows.length === 1)
|
|
56002
56229
|
return parseAgentRow(rows[0]);
|
|
56003
56230
|
return null;
|
|
@@ -56024,7 +56251,7 @@ function listAgentsByProject(projectId, db) {
|
|
|
56024
56251
|
}
|
|
56025
56252
|
function updateAgent(id, updates, db) {
|
|
56026
56253
|
if (!db && isApiMode()) {
|
|
56027
|
-
const { status, data } = apiJson("PATCH", `/agents/${encodeURIComponent(id)}`, updates);
|
|
56254
|
+
const { status, data } = apiJson("PATCH", `/agents/${encodeURIComponent(id)}`, updates, { allow404: true });
|
|
56028
56255
|
if (status === 404 || !data)
|
|
56029
56256
|
return null;
|
|
56030
56257
|
return data;
|
|
@@ -56132,7 +56359,7 @@ function acquireLock(agentId, resourceType, resourceId, lockType = "exclusive",
|
|
|
56132
56359
|
}
|
|
56133
56360
|
function releaseLock(lockId, agentId, db) {
|
|
56134
56361
|
if (!db && isApiMode()) {
|
|
56135
|
-
const { status } = apiJson("DELETE", `/locks/${encodeURIComponent(lockId)}`, { agent_id: agentId });
|
|
56362
|
+
const { status } = apiJson("DELETE", `/locks/${encodeURIComponent(lockId)}`, { agent_id: agentId }, { allow404: true });
|
|
56136
56363
|
return status !== 404;
|
|
56137
56364
|
}
|
|
56138
56365
|
const d = db || getDatabase();
|
|
@@ -56315,7 +56542,7 @@ function registerProject(name, path, description, memoryPrefix, db) {
|
|
|
56315
56542
|
}
|
|
56316
56543
|
function getProject(idOrPath, db) {
|
|
56317
56544
|
if (!db && isApiMode()) {
|
|
56318
|
-
const { status, data } = apiJson("GET", `/projects/${encodeURIComponent(idOrPath)}
|
|
56545
|
+
const { status, data } = apiJson("GET", `/projects/${encodeURIComponent(idOrPath)}`, undefined, { allow404: true });
|
|
56319
56546
|
if (status === 404 || !data)
|
|
56320
56547
|
return null;
|
|
56321
56548
|
return data;
|
|
@@ -59043,7 +59270,7 @@ function createSubscription(input, db) {
|
|
|
59043
59270
|
}
|
|
59044
59271
|
function deleteSubscription(id, db) {
|
|
59045
59272
|
if (!db && isApiMode()) {
|
|
59046
|
-
const { status } = apiJson("DELETE", `/subscriptions/${encodeURIComponent(id)}
|
|
59273
|
+
const { status } = apiJson("DELETE", `/subscriptions/${encodeURIComponent(id)}`, undefined, { allow404: true });
|
|
59047
59274
|
return status !== 404;
|
|
59048
59275
|
}
|
|
59049
59276
|
const d = db || getDatabase();
|
|
@@ -59337,6 +59564,11 @@ function startServer(port) {
|
|
|
59337
59564
|
try {
|
|
59338
59565
|
return await matched.handler(req, url2, matched.params);
|
|
59339
59566
|
} catch (e) {
|
|
59567
|
+
const constraint = describeConstraintViolation(e);
|
|
59568
|
+
if (constraint) {
|
|
59569
|
+
console.warn(`[mementos-serve] ${req.method} ${pathname}: rejected \u2014 ${constraint}`);
|
|
59570
|
+
return errorResponse(constraint, 400);
|
|
59571
|
+
}
|
|
59340
59572
|
console.error(`[mementos-serve] ${req.method} ${pathname}:`, e);
|
|
59341
59573
|
return errorResponse("Internal server error", 500);
|
|
59342
59574
|
}
|