@hasna/mementos 0.14.88 → 0.15.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +44 -9
  2. package/bun.lock +4 -65
  3. package/dist/cli/commands/info-history.d.ts.map +1 -1
  4. package/dist/cli/commands/info-stale.d.ts.map +1 -1
  5. package/dist/cli/commands/io-export.d.ts.map +1 -1
  6. package/dist/cli/commands/memory-cmd-chain.d.ts.map +1 -1
  7. package/dist/cli/commands/memory-cmd-crud.d.ts.map +1 -1
  8. package/dist/cli/commands/memory-cmd-recall.d.ts.map +1 -1
  9. package/dist/cli/commands/memory-cmd-search.d.ts.map +1 -1
  10. package/dist/cli/commands/memory-cmd-tail.d.ts.map +1 -1
  11. package/dist/cli/commands/memory-cmd-view.d.ts.map +1 -1
  12. package/dist/cli/commands/memory-cmd-when-to-use.d.ts.map +1 -1
  13. package/dist/cli/commands/storage.d.ts.map +1 -1
  14. package/dist/cli/commands/system-doctor.d.ts.map +1 -1
  15. package/dist/cli/commands/system-status.d.ts +43 -0
  16. package/dist/cli/commands/system-status.d.ts.map +1 -0
  17. package/dist/cli/commands/system.d.ts.map +1 -1
  18. package/dist/cli/helpers.d.ts.map +1 -1
  19. package/dist/cli/index.js +3397 -1954
  20. package/dist/cli/register-all.d.ts +1 -1
  21. package/dist/cli/register-all.d.ts.map +1 -1
  22. package/dist/db/api-mode.d.ts +135 -37
  23. package/dist/db/api-mode.d.ts.map +1 -1
  24. package/dist/db/database.d.ts.map +1 -1
  25. package/dist/db/memories.d.ts.map +1 -1
  26. package/dist/db/store-backend.d.ts +8 -2
  27. package/dist/db/store-backend.d.ts.map +1 -1
  28. package/dist/diagnostics/historical-project-registration-receipt.js +85 -50
  29. package/dist/generated/storage-kit/backend.d.ts +12 -2
  30. package/dist/generated/storage-kit/backend.d.ts.map +1 -1
  31. package/dist/generated/storage-kit/index.d.ts +1 -1
  32. package/dist/generated/storage-kit/index.d.ts.map +1 -1
  33. package/dist/generated/storage-kit/migrations.d.ts +21 -0
  34. package/dist/generated/storage-kit/migrations.d.ts.map +1 -1
  35. package/dist/generated/storage-kit/pool.d.ts +2 -5
  36. package/dist/generated/storage-kit/pool.d.ts.map +1 -1
  37. package/dist/generated/storage-kit/query.d.ts +1 -1
  38. package/dist/generated/storage-kit/query.d.ts.map +1 -1
  39. package/dist/index.d.ts +0 -1
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +2811 -1285
  42. package/dist/lib/config.d.ts.map +1 -1
  43. package/dist/lib/env.d.ts +10 -0
  44. package/dist/lib/env.d.ts.map +1 -0
  45. package/dist/lib/gatherer.d.ts.map +1 -1
  46. package/dist/lib/local-opt-in.d.ts +114 -0
  47. package/dist/lib/local-opt-in.d.ts.map +1 -0
  48. package/dist/lib/paths.d.ts +9 -0
  49. package/dist/lib/paths.d.ts.map +1 -1
  50. package/dist/lib/redact.d.ts +70 -10
  51. package/dist/lib/redact.d.ts.map +1 -1
  52. package/dist/lib/reflection.d.ts.map +1 -1
  53. package/dist/mcp/index.d.ts.map +1 -1
  54. package/dist/mcp/index.js +2856 -1291
  55. package/dist/project-registration.js +273 -185
  56. package/dist/sdk/index.d.ts +173 -8
  57. package/dist/sdk/index.d.ts.map +1 -1
  58. package/dist/sdk/index.js +953 -12
  59. package/dist/server/auth.d.ts +2 -0
  60. package/dist/server/auth.d.ts.map +1 -1
  61. package/dist/server/helpers.d.ts +21 -7
  62. package/dist/server/helpers.d.ts.map +1 -1
  63. package/dist/server/index.d.ts.map +1 -1
  64. package/dist/server/index.js +2855 -1351
  65. package/dist/storage.d.ts.map +1 -1
  66. package/dist/storage.js +78 -129
  67. package/dist/test-support/store-isolation.d.ts +50 -11
  68. package/dist/test-support/store-isolation.d.ts.map +1 -1
  69. package/docker-entrypoint.sh +7 -0
  70. package/hasna.contract.json +17 -5
  71. package/package.json +5 -7
  72. package/postinstall.mjs +52 -1
  73. package/dashboard/bun.lock +0 -519
  74. package/dist/cli/brains.d.ts +0 -3
  75. package/dist/cli/brains.d.ts.map +0 -1
  76. package/dist/lib/retired-storage-mode.d.ts +0 -11
  77. package/dist/lib/retired-storage-mode.d.ts.map +0 -1
@@ -72,16 +72,7 @@ function serverDataBackendEnvKeys(name) {
72
72
  databaseUrlKeys: [`HASNA_${token}_DATABASE_URL`, `${token}_DATABASE_URL`]
73
73
  };
74
74
  }
75
- function legacyModeKeys(name) {
76
- const token = envToken(name);
77
- return [
78
- `HASNA_${token}_STORAGE_MODE`,
79
- `HASNA_${token}_MODE`,
80
- `${token}_STORAGE_MODE`,
81
- `${token}_MODE`
82
- ];
83
- }
84
- function firstEnv(env, keys) {
75
+ function firstDatabaseUrl(env, keys) {
85
76
  for (const key of keys) {
86
77
  const value = ownString(env, key)?.trim();
87
78
  if (value)
@@ -89,23 +80,8 @@ function firstEnv(env, keys) {
89
80
  }
90
81
  return null;
91
82
  }
92
- function firstDefinedEnvKey(env, keys) {
93
- for (const key of keys) {
94
- if (Object.hasOwn(env, key) && env[key] !== undefined)
95
- return key;
96
- }
97
- return null;
98
- }
99
- function assertNoLegacyStorageMode(name, env = process.env) {
100
- const legacyKey = firstDefinedEnvKey(env, legacyModeKeys(name));
101
- if (!legacyKey)
102
- return;
103
- const canonicalDatabaseUrl = serverDataBackendEnvKeys(name).databaseUrlKeys[0];
104
- throw new Error(`${legacyKey} was removed. Delete the storage-mode variable; ` + `set ${canonicalDatabaseUrl} to select the postgresql server backend, ` + `or leave it unset for sqlite.`);
105
- }
106
83
  function resolveServerDataBackend(name, env = process.env) {
107
- assertNoLegacyStorageMode(name, env);
108
- const databaseUrl = firstEnv(env, serverDataBackendEnvKeys(name).databaseUrlKeys);
84
+ const databaseUrl = firstDatabaseUrl(env, serverDataBackendEnvKeys(name).databaseUrlKeys);
109
85
  if (!databaseUrl) {
110
86
  return {
111
87
  backend: "sqlite",
@@ -122,113 +98,69 @@ function resolveServerDataBackend(name, env = process.env) {
122
98
  };
123
99
  }
124
100
  function resolveDatabaseUrl(name, env = process.env) {
125
- assertNoLegacyStorageMode(name, env);
126
- const hit = firstEnv(env, serverDataBackendEnvKeys(name).databaseUrlKeys);
127
- return hit?.value ?? null;
101
+ return firstDatabaseUrl(env, serverDataBackendEnvKeys(name).databaseUrlKeys)?.value ?? null;
128
102
  }
129
103
  var init_backend = () => {};
130
104
 
131
- // src/lib/retired-storage-mode.ts
132
- function firstDefinedEnvKey2(env, keys) {
133
- for (const key of keys) {
134
- if (Object.hasOwn(env, key) && env[key] !== undefined)
135
- return key;
136
- }
137
- return null;
138
- }
139
- function assertNoLegacyStorageMode2(env = process.env) {
140
- const legacyKey = firstDefinedEnvKey2(env, LEGACY_STORAGE_MODE_KEYS);
141
- if (!legacyKey)
142
- return;
143
- throw new Error(`${legacyKey} was removed. Deployment modes no longer exist: delete the storage-mode variable. ` + `The client uses the local SQLite store, or the HTTP API selected by ` + `HASNA_MEMENTOS_API_URL + HASNA_MEMENTOS_API_KEY. ` + `On the server, set HASNA_MEMENTOS_DATABASE_URL to select the postgresql backend, ` + `or leave it unset for sqlite.`);
144
- }
145
- var LEGACY_STORAGE_MODE_KEYS;
146
- var init_retired_storage_mode = __esm(() => {
147
- LEGACY_STORAGE_MODE_KEYS = [
148
- "HASNA_MEMENTOS_STORAGE_MODE",
149
- "HASNA_MEMENTOS_MODE",
150
- "MEMENTOS_STORAGE_MODE",
151
- "MEMENTOS_MODE"
152
- ];
153
- });
154
-
155
- // ../../node_modules/.bun/@hasna+paths@0.1.0/node_modules/@hasna/paths/dist/index.js
105
+ // src/lib/paths.ts
106
+ import { existsSync } from "fs";
156
107
  import { homedir } from "os";
157
- import { join } from "path";
158
- function assertApp(app) {
108
+ import { join, resolve } from "path";
109
+ import { homedir as pathsResolverHomedir } from "os";
110
+ import { join as pathsResolverJoin } from "path";
111
+ function pathsResolverAssertApp(app) {
159
112
  if (typeof app !== "string" || app.length === 0) {
160
113
  throw new TypeError("paths: app must be a non-empty string");
161
114
  }
162
- if (!APP_SLUG_RE.test(app)) {
115
+ if (!PATHS_RESOLVER_APP_SLUG_RE.test(app)) {
163
116
  throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
164
117
  }
165
118
  }
166
- function envOf(options) {
167
- return options.env ?? process.env;
168
- }
169
- function envValue(options, kind) {
170
- const value = envOf(options)[KIND_ENV[kind]];
171
- return typeof value === "string" && value.length > 0 ? value : undefined;
172
- }
173
- function isMacOS(platform) {
174
- return platform === "darwin";
119
+ function pathsResolverAssertKind(kind) {
120
+ if (!Object.keys(PATHS_RESOLVER_KIND_ENV).includes(kind)) {
121
+ throw new TypeError(`paths: invalid path kind "${kind}" \u2014 expected one of ${Object.keys(PATHS_RESOLVER_KIND_ENV).join(", ")}`);
122
+ }
175
123
  }
176
- function baseDir(kind, options) {
177
- const override = envValue(options, kind);
178
- if (override)
124
+ function pathsResolverBaseDir(kind, options) {
125
+ pathsResolverAssertKind(kind);
126
+ const env = options.env ?? process.env;
127
+ const override = env[PATHS_RESOLVER_KIND_ENV[kind]];
128
+ if (typeof override === "string" && override.length > 0)
179
129
  return override;
180
- const home = options.home ?? homedir();
130
+ const home = options.home ?? pathsResolverHomedir();
181
131
  const platform = options.platform ?? process.platform;
182
- if (isMacOS(platform)) {
132
+ if (platform === "darwin") {
183
133
  switch (kind) {
184
- case "config":
185
134
  case "data":
186
- return join(home, "Library", "Application Support", "Hasna");
135
+ return pathsResolverJoin(home, "Library", "Application Support", "Hasna");
187
136
  case "cache":
188
- return join(home, "Library", "Caches", "Hasna");
137
+ return pathsResolverJoin(home, "Library", "Caches", "Hasna");
189
138
  case "state":
190
- return join(home, "Library", "Logs", "Hasna");
139
+ return pathsResolverJoin(home, "Library", "Logs", "Hasna");
191
140
  }
192
141
  }
193
142
  switch (kind) {
194
- case "config":
195
- return join(home, ".config", "hasna");
196
143
  case "data":
197
- return join(home, ".local", "share", "hasna");
144
+ return pathsResolverJoin(home, ".local", "share", "hasna");
198
145
  case "state":
199
- return join(home, ".local", "state", "hasna");
146
+ return pathsResolverJoin(home, ".local", "state", "hasna");
200
147
  case "cache":
201
- return join(home, ".cache", "hasna");
148
+ return pathsResolverJoin(home, ".cache", "hasna");
202
149
  }
203
150
  }
204
- function resolvePath(kind, options) {
205
- assertApp(options.app);
206
- const appSegment = options.internal === true ? join("internal", options.app) : options.app;
207
- return join(baseDir(kind, options), appSegment);
151
+ function pathsResolverResolve(kind, options) {
152
+ pathsResolverAssertApp(options.app);
153
+ const appSegment = options.internal === true ? pathsResolverJoin("internal", options.app) : options.app;
154
+ return pathsResolverJoin(pathsResolverBaseDir(kind, options), appSegment);
208
155
  }
209
156
  function dataDir(options) {
210
- return resolvePath("data", options);
157
+ return pathsResolverResolve("data", options);
211
158
  }
212
- var KIND_ENV, APP_SLUG_RE;
213
- var init_dist = __esm(() => {
214
- KIND_ENV = {
215
- config: "HASNA_CONFIG_HOME",
216
- data: "HASNA_DATA_HOME",
217
- state: "HASNA_STATE_HOME",
218
- cache: "HASNA_CACHE_HOME"
219
- };
220
- APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
221
- });
222
-
223
- // src/lib/paths.ts
224
- import { existsSync } from "fs";
225
- import { homedir as homedir2 } from "os";
226
- import { join as join2, resolve } from "path";
227
159
  function effectiveHome() {
228
- return process.env["HOME"] || process.env["USERPROFILE"] || homedir2() || "/tmp";
160
+ return process.env["HOME"] || process.env["USERPROFILE"] || homedir() || "/tmp";
229
161
  }
230
162
  function legacyDataRoot() {
231
- return join2(effectiveHome(), ".hasna", "mementos");
163
+ return join(effectiveHome(), ".hasna", "mementos");
232
164
  }
233
165
  function resolverDataRoot() {
234
166
  return dataDir({
@@ -240,7 +172,7 @@ function adoptResolverDataRoot(resolved, env = process.env) {
240
172
  const dataOverride = env.HASNA_DATA_HOME;
241
173
  if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
242
174
  return true;
243
- return existsSync(join2(resolved, "mementos.db"));
175
+ return existsSync(join(resolved, "mementos.db"));
244
176
  }
245
177
  function exactDataRoot() {
246
178
  for (const key of ["HASNA_MEMENTOS_HOME", "MEMENTOS_HOME"]) {
@@ -257,14 +189,34 @@ function getDataRoot() {
257
189
  const resolved = resolverDataRoot();
258
190
  return adoptResolverDataRoot(resolved) ? resolve(resolved) : resolve(legacyDataRoot());
259
191
  }
192
+ var PATHS_RESOLVER_KIND_ENV, PATHS_RESOLVER_APP_SLUG_RE;
260
193
  var init_paths = __esm(() => {
261
- init_dist();
194
+ PATHS_RESOLVER_KIND_ENV = {
195
+ data: "HASNA_DATA_HOME",
196
+ state: "HASNA_STATE_HOME",
197
+ cache: "HASNA_CACHE_HOME"
198
+ };
199
+ PATHS_RESOLVER_APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
200
+ });
201
+
202
+ // src/lib/env.ts
203
+ var alias = (canonical, legacy) => process.env[canonical] ?? process.env[legacy], env;
204
+ var init_env = __esm(() => {
205
+ env = {
206
+ apiKey: () => alias("HASNA_MEMENTOS_API_KEY", "MEMENTOS_API_KEY"),
207
+ defaultScope: () => alias("HASNA_MEMENTOS_DEFAULT_SCOPE", "MEMENTOS_DEFAULT_SCOPE"),
208
+ defaultCategory: () => alias("HASNA_MEMENTOS_DEFAULT_CATEGORY", "MEMENTOS_DEFAULT_CATEGORY"),
209
+ defaultImportance: () => alias("HASNA_MEMENTOS_DEFAULT_IMPORTANCE", "MEMENTOS_DEFAULT_IMPORTANCE"),
210
+ pgsyncQueryTimeoutMs: () => alias("HASNA_MEMENTOS_PGSYNC_QUERY_TIMEOUT_MS", "MEMENTOS_PGSYNC_QUERY_TIMEOUT_MS"),
211
+ reflectProvider: () => alias("HASNA_MEMENTOS_REFLECT_PROVIDER", "MEMENTOS_REFLECT_PROVIDER"),
212
+ reflectModel: () => alias("HASNA_MEMENTOS_REFLECT_MODEL", "MEMENTOS_REFLECT_MODEL")
213
+ };
262
214
  });
263
215
 
264
216
  // src/storage.ts
265
217
  import { Database } from "bun:sqlite";
266
218
  import { existsSync as existsSync2, mkdirSync, readFileSync, writeFileSync } from "fs";
267
- import { join as join3 } from "path";
219
+ import { join as join2 } from "path";
268
220
  import { fileURLToPath } from "url";
269
221
  import { Worker } from "worker_threads";
270
222
  import pg from "pg";
@@ -289,6 +241,7 @@ class SqliteAdapter {
289
241
  db;
290
242
  constructor(path) {
291
243
  this.db = new Database(path, { create: true });
244
+ this.db.exec("PRAGMA busy_timeout = 5000");
292
245
  this.db.exec("PRAGMA journal_mode = WAL");
293
246
  this.db.exec("PRAGMA foreign_keys = ON");
294
247
  }
@@ -539,9 +492,9 @@ function getConfigPath() {
539
492
  return STORAGE_CONFIG_PATH;
540
493
  }
541
494
  function getStorageDatabaseEnv() {
542
- for (const env of DATABASE_ENV_NAMES) {
543
- if (readEnv(env.name))
544
- return env;
495
+ for (const env2 of DATABASE_ENV_NAMES) {
496
+ if (readEnv(env2.name))
497
+ return env2;
545
498
  }
546
499
  return null;
547
500
  }
@@ -551,20 +504,19 @@ function getStorageEnvName(key) {
551
504
  return readEnv(canonical) || !readEnv(fallback) ? canonical : fallback;
552
505
  }
553
506
  function getStorageDatabaseUrl() {
554
- const env = getStorageDatabaseEnv();
555
- return env ? readEnv(env.name) : null;
507
+ const env2 = getStorageDatabaseEnv();
508
+ return env2 ? readEnv(env2.name) : null;
556
509
  }
557
510
  function getStorageDatabaseEnvName() {
558
511
  return getStorageEnvName("databaseUrl");
559
512
  }
560
- function getStorageBackend(env = process.env) {
561
- return resolveServerDataBackend("mementos", env).backend;
513
+ function getStorageBackend(env2 = process.env) {
514
+ return resolveServerDataBackend("mementos", env2).backend;
562
515
  }
563
- function getStorageBackendDatabaseUrl(env = process.env) {
564
- return resolveDatabaseUrl("mementos", env);
516
+ function getStorageBackendDatabaseUrl(env2 = process.env) {
517
+ return resolveDatabaseUrl("mementos", env2);
565
518
  }
566
519
  function getStorageConfig() {
567
- assertNoLegacyStorageMode2();
568
520
  const fileConfig = readConfigFile();
569
521
  const merged = {
570
522
  ...DEFAULT_STORAGE_CONFIG,
@@ -651,14 +603,14 @@ function storageEnvStatus(key) {
651
603
  };
652
604
  }
653
605
  function postgresBackendStatus(config) {
654
- const env = getStorageDatabaseEnv();
655
- const envUrl = env ? readEnv(env.name) : null;
656
- if (env && envUrl) {
606
+ const env2 = getStorageDatabaseEnv();
607
+ const envUrl = env2 ? readEnv(env2.name) : null;
608
+ if (env2 && envUrl) {
657
609
  const validation = validatePostgresConnectionString(envUrl);
658
610
  return {
659
611
  configured: validation.ok,
660
612
  source: "env",
661
- env_name: env.name,
613
+ env_name: env2.name,
662
614
  redacted_url: validation.redacted_url,
663
615
  missing: [],
664
616
  issues: validation.issues,
@@ -704,7 +656,6 @@ function getSafeStorageConfigSummary(config = getStorageConfig()) {
704
656
  };
705
657
  }
706
658
  function getStorageStatus() {
707
- assertNoLegacyStorageMode2();
708
659
  const config = getStorageConfig();
709
660
  const backend = getStorageBackend();
710
661
  const postgresRequested = backend === "postgresql";
@@ -814,14 +765,12 @@ function resolveConfiguredConnectionString(dbName) {
814
765
  return `postgres://${username}:${encodeURIComponent(password)}@${host}:${port}/${dbName}${sslParam}`;
815
766
  }
816
767
  function getStorageConnectionString(dbName = "mementos") {
817
- assertNoLegacyStorageMode2();
818
768
  if (!isServerContext()) {
819
769
  throw new Error("Refusing to construct an RDS Postgres DSN outside the mementos-serve server. " + "The raw database DSN is NEVER distributed to client machines. " + "Clients must use the HTTP API: set HASNA_MEMENTOS_API_URL and " + "HASNA_MEMENTOS_API_KEY (and unset HASNA_MEMENTOS_DATABASE_URL).");
820
770
  }
821
771
  return resolveConfiguredConnectionString(dbName);
822
772
  }
823
773
  function getStorageConnectionStringForOperator(dbName = "mementos") {
824
- assertNoLegacyStorageMode2();
825
774
  return resolveConfiguredConnectionString(dbName);
826
775
  }
827
776
  function isSyncExcludedTable(table) {
@@ -992,8 +941,8 @@ CREATE TABLE IF NOT EXISTS _sync_meta (
992
941
  )`;
993
942
  var init_storage = __esm(() => {
994
943
  init_backend();
995
- init_retired_storage_mode();
996
944
  init_paths();
945
+ init_env();
997
946
  PgSyncPool = class PgSyncPool {
998
947
  worker;
999
948
  status;
@@ -1003,7 +952,7 @@ var init_storage = __esm(() => {
1003
952
  generation = 0;
1004
953
  static DATA_BYTES = 128 * 1024 * 1024;
1005
954
  static queryTimeoutMs() {
1006
- const raw = process.env["MEMENTOS_PGSYNC_QUERY_TIMEOUT_MS"]?.trim();
955
+ const raw = env.pgsyncQueryTimeoutMs()?.trim();
1007
956
  const parsed = raw ? Number(raw) : Number.NaN;
1008
957
  return Number.isFinite(parsed) && parsed > 0 ? parsed : 60000;
1009
958
  }
@@ -1011,9 +960,9 @@ var init_storage = __esm(() => {
1011
960
  const ext = import.meta.url.endsWith(".ts") ? ".ts" : ".js";
1012
961
  const here = fileURLToPath(new URL(".", import.meta.url));
1013
962
  const candidates = [
1014
- join3(here, `pg-sync-worker${ext}`),
1015
- join3(here, "..", `pg-sync-worker${ext}`),
1016
- join3(here, "..", "..", `pg-sync-worker${ext}`)
963
+ join2(here, `pg-sync-worker${ext}`),
964
+ join2(here, "..", `pg-sync-worker${ext}`),
965
+ join2(here, "..", "..", `pg-sync-worker${ext}`)
1017
966
  ];
1018
967
  for (const candidate of candidates) {
1019
968
  if (existsSync2(candidate))
@@ -1119,8 +1068,8 @@ var init_storage = __esm(() => {
1119
1068
  schedule_minutes: 0
1120
1069
  }
1121
1070
  };
1122
- STORAGE_CONFIG_DIR = join3(LOCAL_DATA_DIR, "storage");
1123
- STORAGE_CONFIG_PATH = join3(STORAGE_CONFIG_DIR, "config.json");
1071
+ STORAGE_CONFIG_DIR = join2(LOCAL_DATA_DIR, "storage");
1072
+ STORAGE_CONFIG_PATH = join2(STORAGE_CONFIG_DIR, "config.json");
1124
1073
  DATABASE_ENV_NAMES = [
1125
1074
  { name: MEMENTOS_STORAGE_ENV.databaseUrl, deprecated: false },
1126
1075
  { name: MEMENTOS_STORAGE_FALLBACK_ENV.databaseUrl, deprecated: false }
@@ -1144,28 +1093,96 @@ var init_storage = __esm(() => {
1144
1093
  ];
1145
1094
  });
1146
1095
 
1096
+ // src/lib/local-opt-in.ts
1097
+ import {
1098
+ clientTransportEnvKeys,
1099
+ credentialOverrideEnvKey,
1100
+ credentialPointerEnvKey,
1101
+ CREDENTIAL_PROFILE_ENV_KEY
1102
+ } from "@hasna/contracts/client";
1103
+ function isMementosLocalOptIn(env2 = process.env) {
1104
+ return MEMENTOS_LOCAL_OPT_IN_ENV_KEYS.some((key) => (env2[key] ?? "").trim() !== "");
1105
+ }
1106
+ function hasExplicitLocalDbPath(env2 = process.env) {
1107
+ return MEMENTOS_DB_PATH_ENV_KEYS.some((key) => (env2[key] ?? "").trim() !== "");
1108
+ }
1109
+ function mementosAuthorityEnvKeys() {
1110
+ const keys = clientTransportEnvKeys("mementos");
1111
+ return [
1112
+ ...keys.apiUrlKeys,
1113
+ ...keys.apiKeyKeys,
1114
+ credentialOverrideEnvKey("mementos"),
1115
+ credentialPointerEnvKey("mementos"),
1116
+ CREDENTIAL_PROFILE_ENV_KEY
1117
+ ];
1118
+ }
1119
+ function hasMementosEnvAuthorityIntent(env2 = process.env) {
1120
+ return mementosAuthorityEnvKeys().some((key) => (env2[key] ?? "").trim() !== "");
1121
+ }
1122
+ function selectsMementosLocalStore(env2 = process.env) {
1123
+ if (hasExplicitLocalDbPath(env2))
1124
+ return true;
1125
+ return !hasMementosEnvAuthorityIntent(env2) && isMementosLocalOptIn(env2);
1126
+ }
1127
+ function mementosResolverEnv(env2) {
1128
+ const blanks = mementosAuthorityEnvKeys().filter((key) => (key in env2) && (env2[key] ?? "").trim() === "");
1129
+ if (blanks.length === 0)
1130
+ return env2;
1131
+ const next = { ...env2 };
1132
+ for (const key of blanks)
1133
+ delete next[key];
1134
+ return next;
1135
+ }
1136
+ function isAmbientMementosEnv(env2) {
1137
+ if (typeof process !== "undefined" && env2 === process.env)
1138
+ return true;
1139
+ return env2[CONTRACTS_AMBIENT_ENVIRONMENT] === true;
1140
+ }
1141
+ function mementosResolverInputs(env2, credentials = {}) {
1142
+ const normalised = mementosResolverEnv(env2);
1143
+ if (normalised === env2)
1144
+ return { env: normalised, credentials };
1145
+ const keychain = { ...credentials.keychain };
1146
+ if (keychain.enabled === undefined && keychain.run === undefined) {
1147
+ keychain.enabled = isAmbientMementosEnv(env2);
1148
+ }
1149
+ return { env: normalised, credentials: { ...credentials, keychain } };
1150
+ }
1151
+ var MEMENTOS_LOCAL_OPT_IN_ENV_KEYS, MEMENTOS_DB_PATH_ENV_KEYS, CONTRACTS_AMBIENT_ENVIRONMENT;
1152
+ var init_local_opt_in = __esm(() => {
1153
+ MEMENTOS_LOCAL_OPT_IN_ENV_KEYS = ["HASNA_MEMENTOS_LOCAL", "MEMENTOS_LOCAL"];
1154
+ MEMENTOS_DB_PATH_ENV_KEYS = ["HASNA_MEMENTOS_DB_PATH", "MEMENTOS_DB_PATH"];
1155
+ CONTRACTS_AMBIENT_ENVIRONMENT = Symbol.for("hasna:contracts:ambientClientEnvironment");
1156
+ });
1157
+
1147
1158
  // src/db/api-mode.ts
1148
1159
  import { tmpdir } from "os";
1149
- import { join as join4 } from "path";
1160
+ import { join as join3 } from "path";
1150
1161
  import { writeFileSync as writeFileSync2, unlinkSync } from "fs";
1151
1162
  import { randomUUID } from "crypto";
1152
- function firstEnv2(keys) {
1163
+ import {
1164
+ clientTransportEnvKeys as clientTransportEnvKeys2,
1165
+ resolveClientTransport,
1166
+ resolveCredential,
1167
+ toV1BaseUrl,
1168
+ ClientTransportConfigurationError
1169
+ } from "@hasna/contracts/client";
1170
+ function firstEnv(keys, env2 = process.env) {
1153
1171
  for (const k of keys) {
1154
- const v = process.env[k]?.trim();
1172
+ const v = env2[k]?.trim();
1155
1173
  if (v)
1156
1174
  return v;
1157
1175
  }
1158
1176
  return;
1159
1177
  }
1160
- function firstEnvKey(keys) {
1161
- for (const k of keys) {
1162
- if (process.env[k]?.trim())
1163
- return k;
1164
- }
1165
- return null;
1178
+ function hasDatabaseUrl(env2 = process.env) {
1179
+ return Boolean(firstEnv(DATABASE_URL_ENV_KEYS, env2));
1166
1180
  }
1167
- function hasDatabaseUrl() {
1168
- return Boolean(firstEnv2(DATABASE_URL_ENV_KEYS));
1181
+ function assertCleanResolvedBase(baseUrl) {
1182
+ if (/[?#]/.test(baseUrl)) {
1183
+ throw new Error("mementos base URL must not contain userinfo, query, or fragment data");
1184
+ }
1185
+ return baseUrl;
1169
1186
  }
1170
1187
  function isLoopbackHost(rawHost) {
1171
1188
  const host = rawHost.replace(/^\[/, "").replace(/\]$/, "").toLowerCase();
@@ -1186,50 +1203,61 @@ function assertRequestAllowedUnderTest(baseUrl) {
1186
1203
  return;
1187
1204
  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.
1188
1205
  ` + ` host : ${host || "(unparseable base URL)"}
1189
- ` + ` 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.
1206
+ ` + " 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.
1190
1207
  ` + " fix : build the child/process env via src/test-support/store-isolation.ts, or point the " + `suite at a loopback stub.
1191
1208
  ` + ` override : set ${ALLOW_REMOTE_API_IN_TESTS_ENV}=1 only for a test that must reach a remote endpoint.`);
1192
1209
  }
1193
- function normalizeBase(raw) {
1194
- let base = raw.trim().replace(/\/+$/, "");
1195
- if (/\/(v1|api)$/.test(base))
1196
- return base;
1197
- return `${base}/v1`;
1198
- }
1199
- function assertUnambiguousStoreEnv(env = process.env) {
1200
- assertNoLegacyStorageMode2(env);
1201
- if (firstEnvKey(DB_PATH_ENV_KEYS))
1202
- return;
1203
- if (hasDatabaseUrl())
1204
- return;
1205
- const urlKey = firstEnvKey(API_URL_ENV_KEYS);
1206
- const keyKey = firstEnvKey(API_KEY_ENV_KEYS);
1207
- if (urlKey && !keyKey) {
1208
- 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.`);
1209
- }
1210
- if (keyKey && !urlKey) {
1211
- 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.`);
1212
- }
1210
+ function toStoreConfigError(error) {
1211
+ const message = error instanceof Error ? error.message : String(error);
1212
+ return new MementosStoreConfigError(`${message} Local SQLite is opt-in only (${DB_PATH_ENV_KEYS[0]}, or ${MEMENTOS_LOCAL_OPT_IN_ENV_KEYS[0]} = 1) and is disabled by default \u2014 failing closed`);
1213
1213
  }
1214
- function getApiConfig() {
1215
- assertUnambiguousStoreEnv();
1216
- if (firstEnvKey(DB_PATH_ENV_KEYS))
1214
+ function getApiConfig(env2 = process.env, options = {}) {
1215
+ if (hasDatabaseUrl(env2))
1217
1216
  return null;
1218
- const rawBase = firstEnv2(API_URL_ENV_KEYS);
1219
- const apiKey = firstEnv2(API_KEY_ENV_KEYS);
1220
- if (!rawBase || !apiKey)
1217
+ if (selectsMementosLocalStore(env2))
1221
1218
  return null;
1222
- return { baseUrl: normalizeBase(rawBase), apiKey };
1219
+ const inputs = mementosResolverInputs(env2, options.credentials);
1220
+ let credential;
1221
+ try {
1222
+ credential = resolveCredential("mementos", inputs.env, inputs.credentials);
1223
+ } catch (error) {
1224
+ throw toStoreConfigError(error);
1225
+ }
1226
+ let resolution;
1227
+ try {
1228
+ resolution = resolveClientTransport("mementos", inputs.env, {
1229
+ credentials: credential ? { ...inputs.credentials, apiKey: credential.apiKey } : inputs.credentials
1230
+ });
1231
+ } catch (error) {
1232
+ if (error instanceof ClientTransportConfigurationError && credential === null && /is not set and no API key could be resolved/.test(error.message)) {
1233
+ return null;
1234
+ }
1235
+ throw toStoreConfigError(error);
1236
+ }
1237
+ return { baseUrl: assertCleanResolvedBase(resolution.baseUrl), apiKey: credential.apiKey };
1223
1238
  }
1224
1239
  function isApiMode() {
1225
1240
  if (hasDatabaseUrl())
1226
1241
  return false;
1227
1242
  return getApiConfig() !== null;
1228
1243
  }
1244
+ function assertClientStoreConfigured(env2 = process.env, options = {}) {
1245
+ if (selectsMementosLocalStore(env2))
1246
+ return;
1247
+ if (hasDatabaseUrl(env2))
1248
+ return;
1249
+ const config = getApiConfig(env2, options);
1250
+ if (config !== null)
1251
+ return;
1252
+ throw new MementosStoreConfigError(unconfiguredStoreMessage());
1253
+ }
1254
+ function unconfiguredStoreMessage() {
1255
+ return "mementos is not configured to reach any memory store, and will NOT fall back to the " + "on-box SQLite store (~/.hasna/mementos/mementos.db). " + "No credential could be resolved from the Keychain item hasna.credentials.mementos.api-key " + "(macOS only), ~/.hasna/mementos/config/credentials, or " + `${API_KEY_ENV_KEYS[0]}; the authority would be the fleet gateway ${"https://api.hasna.com/mementos"} ` + `(or ${API_URL_ENV_KEYS[0]} if set). ` + `Set ${API_URL_ENV_KEYS[0]} and ${API_KEY_ENV_KEYS[0]} (the resolver also accepts the legacy ` + `${API_URL_ENV_KEYS[1]} / ${API_KEY_ENV_KEYS[1]} aliases for one release) to use the fleet memory ` + `API, or opt into an explicit local SQLite file with ${DB_PATH_ENV_KEYS[0]} / ` + `${DB_PATH_ENV_KEYS[1]} (or ${MEMENTOS_LOCAL_OPT_IN_ENV_KEYS[0]}=1).`;
1256
+ }
1229
1257
  function apiRequestRaw(method, path, body) {
1230
1258
  const cfg = getApiConfig();
1231
1259
  if (!cfg)
1232
- throw new Error("api-mode: not configured (HASNA_MEMENTOS_API_URL / HASNA_MEMENTOS_API_KEY)");
1260
+ throw new Error("api-mode: not configured (no Hasna mementos credential resolved)");
1233
1261
  assertRequestAllowedUnderTest(cfg.baseUrl);
1234
1262
  const url = `${cfg.baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
1235
1263
  const hasBody = body !== undefined && body !== null;
@@ -1256,7 +1284,7 @@ x-api-key: ${cfg.apiKey}
1256
1284
  ];
1257
1285
  let bodyFile;
1258
1286
  if (hasBody) {
1259
- bodyFile = join4(tmpdir(), `mem-req-${process.pid}-${randomUUID()}.json`);
1287
+ bodyFile = join3(tmpdir(), `mem-req-${process.pid}-${randomUUID()}.json`);
1260
1288
  writeFileSync2(bodyFile, JSON.stringify(body), { mode: 384 });
1261
1289
  args.push("--data-binary", `@${bodyFile}`);
1262
1290
  }
@@ -1344,13 +1372,13 @@ function toQuery(params) {
1344
1372
  const s = sp.toString();
1345
1373
  return s ? `?${s}` : "";
1346
1374
  }
1347
- 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";
1375
+ var API_URL_ENV_KEYS, API_KEY_ENV_KEYS, DATABASE_URL_ENV_KEYS, DB_PATH_ENV_KEYS, ALLOW_REMOTE_API_IN_TESTS_ENV = "MEMENTOS_ALLOW_REMOTE_API_IN_TESTS", MementosStoreConfigError, ApiRequestError, DEFAULT_TIMEOUT_S = "45";
1348
1376
  var init_api_mode = __esm(() => {
1349
- init_retired_storage_mode();
1350
- API_URL_ENV_KEYS = ["HASNA_MEMENTOS_API_URL", "MEMENTOS_API_URL"];
1351
- API_KEY_ENV_KEYS = ["HASNA_MEMENTOS_API_KEY", "MEMENTOS_API_KEY"];
1377
+ init_local_opt_in();
1378
+ API_URL_ENV_KEYS = clientTransportEnvKeys2("mementos").apiUrlKeys;
1379
+ API_KEY_ENV_KEYS = clientTransportEnvKeys2("mementos").apiKeyKeys;
1352
1380
  DATABASE_URL_ENV_KEYS = ["HASNA_MEMENTOS_DATABASE_URL", "MEMENTOS_DATABASE_URL"];
1353
- DB_PATH_ENV_KEYS = ["HASNA_MEMENTOS_DB_PATH", "MEMENTOS_DB_PATH"];
1381
+ DB_PATH_ENV_KEYS = MEMENTOS_DB_PATH_ENV_KEYS;
1354
1382
  MementosStoreConfigError = class MementosStoreConfigError extends Error {
1355
1383
  code = "MEMENTOS_STORE_CONFIG";
1356
1384
  constructor(message) {
@@ -2777,7 +2805,7 @@ __export(exports_database, {
2777
2805
  closeDatabase: () => closeDatabase
2778
2806
  });
2779
2807
  import { existsSync as existsSync3, mkdirSync as mkdirSync2, cpSync } from "fs";
2780
- import { dirname, join as join5, resolve as resolve2 } from "path";
2808
+ import { dirname, join as join4, resolve as resolve2 } from "path";
2781
2809
  function isInMemoryDb(path) {
2782
2810
  return path === ":memory:" || path.startsWith("file::memory:");
2783
2811
  }
@@ -2786,7 +2814,7 @@ function findNearestMementosDb(startDir) {
2786
2814
  const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
2787
2815
  const legacyHomeDb = resolve2(home, ".mementos", "mementos.db");
2788
2816
  while (true) {
2789
- const candidate = join5(dir, ".mementos", "mementos.db");
2817
+ const candidate = join4(dir, ".mementos", "mementos.db");
2790
2818
  if (existsSync3(candidate) && resolve2(candidate) !== legacyHomeDb)
2791
2819
  return candidate;
2792
2820
  const parent = dirname(dir);
@@ -2799,7 +2827,7 @@ function findNearestMementosDb(startDir) {
2799
2827
  function findGitRoot(startDir) {
2800
2828
  let dir = resolve2(startDir);
2801
2829
  while (true) {
2802
- if (existsSync3(join5(dir, ".git")))
2830
+ if (existsSync3(join4(dir, ".git")))
2803
2831
  return dir;
2804
2832
  const parent = dirname(dir);
2805
2833
  if (parent === dir)
@@ -2809,9 +2837,11 @@ function findGitRoot(startDir) {
2809
2837
  return null;
2810
2838
  }
2811
2839
  function migrateGlobalDir() {
2840
+ if (!selectsMementosLocalStore())
2841
+ return;
2812
2842
  const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
2813
2843
  const newDir = getDataRoot();
2814
- const oldDir = join5(home, ".mementos");
2844
+ const oldDir = join4(home, ".mementos");
2815
2845
  if (!existsSync3(newDir) && existsSync3(oldDir)) {
2816
2846
  mkdirSync2(dirname(newDir), { recursive: true });
2817
2847
  cpSync(oldDir, newDir, { recursive: true });
@@ -2830,11 +2860,11 @@ function getDbPath() {
2830
2860
  if (process.env["MEMENTOS_DB_SCOPE"] === "project") {
2831
2861
  const gitRoot = findGitRoot(cwd);
2832
2862
  if (gitRoot) {
2833
- return join5(gitRoot, ".mementos", "mementos.db");
2863
+ return join4(gitRoot, ".mementos", "mementos.db");
2834
2864
  }
2835
2865
  }
2836
2866
  migrateGlobalDir();
2837
- return join5(getDataRoot(), "mementos.db");
2867
+ return join4(getDataRoot(), "mementos.db");
2838
2868
  }
2839
2869
  function ensureDir(filePath) {
2840
2870
  if (isInMemoryDb(filePath))
@@ -2870,6 +2900,9 @@ function getDatabase(dbPath) {
2870
2900
  throw new Error("REFUSING-UNPINNED-TEST-OPEN: a test process tried to open the default local " + `SQLite store (${getDbPath()}) with no explicit dbPath argument and no ` + `${DB_PATH_ENV_KEYS.join("/")} set. Pass an explicit dbPath (e.g. ":memory:" ` + "or a scratch file), or set one of those env keys to a scratch path, before " + "calling getDatabase(). This guard exists because an unpinned test process " + "would otherwise silently read and write the real, shared, on-disk memory " + "store (todos 57b8b8c5).");
2871
2901
  }
2872
2902
  }
2903
+ if (process.env["NODE_ENV"] !== "test" && !isServerContext()) {
2904
+ assertClientStoreConfigured();
2905
+ }
2873
2906
  }
2874
2907
  const path = dbPath || getDbPath();
2875
2908
  if (_db) {
@@ -2882,7 +2915,6 @@ function getDatabase(dbPath) {
2882
2915
  ensureDir(path);
2883
2916
  _db = new SqliteAdapter(path);
2884
2917
  _db.run("PRAGMA journal_mode = WAL");
2885
- _db.run("PRAGMA busy_timeout = 5000");
2886
2918
  _db.run("PRAGMA foreign_keys = ON");
2887
2919
  _db.run("PRAGMA wal_autocheckpoint = 100");
2888
2920
  runMigrations(_db);
@@ -2979,6 +3011,7 @@ var init_database = __esm(() => {
2979
3011
  init_api_mode();
2980
3012
  init_migrations();
2981
3013
  init_paths();
3014
+ init_local_opt_in();
2982
3015
  ALLOWED_TABLES = new Set([
2983
3016
  "memories",
2984
3017
  "agents",
@@ -3007,13 +3040,13 @@ import { createHash } from "crypto";
3007
3040
 
3008
3041
  // src/lib/package-version.ts
3009
3042
  import { readFileSync as readFileSync2 } from "fs";
3010
- import { dirname as dirname2, join as join6 } from "path";
3043
+ import { dirname as dirname2, join as join5 } from "path";
3011
3044
  import { fileURLToPath as fileURLToPath2 } from "url";
3012
3045
  function getMementosPackageVersion() {
3013
3046
  const here = dirname2(fileURLToPath2(import.meta.url));
3014
3047
  for (const candidate of [
3015
- join6(here, "..", "..", "package.json"),
3016
- join6(here, "..", "package.json")
3048
+ join5(here, "..", "..", "package.json"),
3049
+ join5(here, "..", "package.json")
3017
3050
  ]) {
3018
3051
  try {
3019
3052
  const parsed = JSON.parse(readFileSync2(candidate, "utf8"));
@@ -5200,6 +5233,17 @@ var SECRET_PATTERNS = [
5200
5233
  { name: "jwt", pattern: /eyJ[a-zA-Z0-9_-]{10,}\.eyJ[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}/g },
5201
5234
  { name: "hex_secret", pattern: /(?<=(?:key|token|secret|password|hash)\s*[:=]\s*["']?)[0-9a-f]{32,}(?=["']?)/gi }
5202
5235
  ];
5236
+ var KEY_TAG_SECRET_PATTERNS = SECRET_PATTERNS.filter((p) => new Set([
5237
+ "openai_key",
5238
+ "anthropic_key",
5239
+ "aws_key",
5240
+ "github_token",
5241
+ "github_oauth",
5242
+ "npm_token",
5243
+ "stripe_key",
5244
+ "slack_token",
5245
+ "jwt"
5246
+ ]).has(p.name));
5203
5247
  function redactSecrets(text) {
5204
5248
  let result = text;
5205
5249
  for (const { pattern } of SECRET_PATTERNS) {
@@ -5208,6 +5252,14 @@ function redactSecrets(text) {
5208
5252
  }
5209
5253
  return result;
5210
5254
  }
5255
+ function redactCredentialKey(text) {
5256
+ let result = text;
5257
+ for (const { pattern } of KEY_TAG_SECRET_PATTERNS) {
5258
+ pattern.lastIndex = 0;
5259
+ result = result.replace(pattern, REDACTED);
5260
+ }
5261
+ return result;
5262
+ }
5211
5263
  function containsSecrets(text) {
5212
5264
  for (const { pattern } of SECRET_PATTERNS) {
5213
5265
  pattern.lastIndex = 0;
@@ -5216,6 +5268,40 @@ function containsSecrets(text) {
5216
5268
  }
5217
5269
  return false;
5218
5270
  }
5271
+ function redactValueTree(value) {
5272
+ if (typeof value === "string")
5273
+ return redactSecrets(value);
5274
+ if (Array.isArray(value))
5275
+ return value.map(redactValueTree);
5276
+ if (value !== null && typeof value === "object") {
5277
+ const out = {};
5278
+ for (const [k, v] of Object.entries(value)) {
5279
+ out[k] = redactValueTree(v);
5280
+ }
5281
+ return out;
5282
+ }
5283
+ return value;
5284
+ }
5285
+ function redactMemoryWriteInput(input) {
5286
+ const out = { ...input };
5287
+ if (typeof out.key === "string")
5288
+ out.key = redactCredentialKey(out.key);
5289
+ if (typeof out.value === "string")
5290
+ out.value = redactSecrets(out.value);
5291
+ if (out.summary !== undefined && out.summary !== null) {
5292
+ out.summary = redactSecrets(String(out.summary));
5293
+ }
5294
+ if (Array.isArray(out.tags)) {
5295
+ out.tags = out.tags.map((t) => redactCredentialKey(String(t)));
5296
+ }
5297
+ if (typeof out.when_to_use === "string") {
5298
+ out.when_to_use = redactSecrets(out.when_to_use);
5299
+ }
5300
+ if (out.metadata !== undefined) {
5301
+ out.metadata = redactValueTree(out.metadata);
5302
+ }
5303
+ return out;
5304
+ }
5219
5305
 
5220
5306
  // src/lib/hooks.ts
5221
5307
  var _idCounter = 0;
@@ -5528,6 +5614,7 @@ function createMemory(input, dedupeMode = "merge", db) {
5528
5614
  if (reservedViolation) {
5529
5615
  throw new Error(reservedViolation);
5530
5616
  }
5617
+ input = redactMemoryWriteInput(input);
5531
5618
  if (!db && isApiMode()) {
5532
5619
  const { status, data } = apiJson("POST", "/memories", { ...input, dedupe: dedupeMode });
5533
5620
  if (!data || !data.id) {
@@ -5985,6 +6072,7 @@ function listMemoriesBounded(filter = {}, target, db) {
5985
6072
  };
5986
6073
  }
5987
6074
  function updateMemory(id, input, db) {
6075
+ input = redactMemoryWriteInput(input);
5988
6076
  if (!db && isApiMode()) {
5989
6077
  const { status, data } = apiJson("PATCH", `/memories/${encodeURIComponent(id)}`, input, { allow404: true });
5990
6078
  if (status === 404)