@hasna/mementos 0.14.83 → 0.14.85

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 (72) hide show
  1. package/Dockerfile.package +0 -1
  2. package/README.md +13 -10
  3. package/dashboard/bun.lock +519 -0
  4. package/dist/cli/commands/info-history.d.ts.map +1 -1
  5. package/dist/cli/commands/info-stale.d.ts.map +1 -1
  6. package/dist/cli/commands/io-export.d.ts.map +1 -1
  7. package/dist/cli/commands/memory-cmd-list.d.ts.map +1 -1
  8. package/dist/cli/commands/storage.d.ts.map +1 -1
  9. package/dist/cli/helpers.d.ts +31 -0
  10. package/dist/cli/helpers.d.ts.map +1 -1
  11. package/dist/cli/index.js +3781 -1512
  12. package/dist/db/analytics.d.ts +13 -0
  13. package/dist/db/analytics.d.ts.map +1 -1
  14. package/dist/db/api-mode.d.ts +5 -1
  15. package/dist/db/api-mode.d.ts.map +1 -1
  16. package/dist/db/database.d.ts.map +1 -1
  17. package/dist/db/memories.d.ts +59 -3
  18. package/dist/db/memories.d.ts.map +1 -1
  19. package/dist/db/store-backend.d.ts +3 -3
  20. package/dist/db/store-backend.d.ts.map +1 -1
  21. package/dist/diagnostics/historical-project-registration-receipt.d.ts +39 -0
  22. package/dist/diagnostics/historical-project-registration-receipt.d.ts.map +1 -0
  23. package/dist/diagnostics/historical-project-registration-receipt.js +1656 -0
  24. package/dist/generated/storage-kit/backend.d.ts +20 -0
  25. package/dist/generated/storage-kit/backend.d.ts.map +1 -0
  26. package/dist/generated/storage-kit/index.d.ts +2 -2
  27. package/dist/generated/storage-kit/index.d.ts.map +1 -1
  28. package/dist/generated/storage-kit/migrations.d.ts.map +1 -1
  29. package/dist/generated/storage-kit/own.d.ts +11 -0
  30. package/dist/generated/storage-kit/own.d.ts.map +1 -0
  31. package/dist/generated/storage-kit/pool.d.ts +7 -6
  32. package/dist/generated/storage-kit/pool.d.ts.map +1 -1
  33. package/dist/generated/storage-kit/tls.d.ts +30 -3
  34. package/dist/generated/storage-kit/tls.d.ts.map +1 -1
  35. package/dist/index.d.ts +2 -1
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +3628 -1453
  38. package/dist/lib/gatherer.d.ts.map +1 -1
  39. package/dist/lib/retired-storage-mode.d.ts +11 -0
  40. package/dist/lib/retired-storage-mode.d.ts.map +1 -0
  41. package/dist/lib/storage-sync.d.ts +2 -2
  42. package/dist/lib/storage-sync.d.ts.map +1 -1
  43. package/dist/mcp/index.js +3684 -1492
  44. package/dist/mcp/tools/memory-io.d.ts.map +1 -1
  45. package/dist/mcp/tools/project-tools.d.ts.map +1 -1
  46. package/dist/project-registration/authority.d.ts.map +1 -1
  47. package/dist/project-registration/historical-receipt.d.ts +38 -0
  48. package/dist/project-registration/historical-receipt.d.ts.map +1 -0
  49. package/dist/project-registration/identity.d.ts.map +1 -1
  50. package/dist/project-registration/index.d.ts +1 -1
  51. package/dist/project-registration/index.d.ts.map +1 -1
  52. package/dist/project-registration/types.d.ts +26 -0
  53. package/dist/project-registration/types.d.ts.map +1 -1
  54. package/dist/project-registration.js +361 -162
  55. package/dist/sdk/index.d.ts +7 -0
  56. package/dist/sdk/index.d.ts.map +1 -1
  57. package/dist/sdk/index.js +122 -30
  58. package/dist/server/index.js +3698 -1431
  59. package/dist/storage.d.ts +18 -50
  60. package/dist/storage.d.ts.map +1 -1
  61. package/dist/storage.js +151 -122
  62. package/dist/test-support/memories-page-stub-server.d.ts +2 -0
  63. package/dist/test-support/memories-page-stub-server.d.ts.map +1 -0
  64. package/dist/test-support/memories-page-stub.d.ts +28 -0
  65. package/dist/test-support/memories-page-stub.d.ts.map +1 -0
  66. package/dist/test-support/store-isolation.d.ts +5 -0
  67. package/dist/test-support/store-isolation.d.ts.map +1 -1
  68. package/docker-entrypoint.sh +4 -5
  69. package/hasna.contract.json +2 -2
  70. package/package.json +7 -6
  71. package/dist/generated/storage-kit/mode.d.ts +0 -48
  72. package/dist/generated/storage-kit/mode.d.ts.map +0 -1
@@ -46,24 +46,109 @@ var __export = (target, all) => {
46
46
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
47
47
  var __require = import.meta.require;
48
48
 
49
- // src/generated/storage-kit/mode.ts
50
- function normalizeStorageMode(value) {
51
- const normalized = value.trim().toLowerCase().replace(/-/g, "_");
52
- if (normalized === "local")
53
- return { mode: "local", deprecatedAlias: null };
54
- if (normalized === "cloud")
55
- return { mode: "cloud", deprecatedAlias: null };
56
- if (DEPRECATED_STORAGE_MODE_ALIASES.includes(normalized)) {
57
- return { mode: "cloud", deprecatedAlias: normalized };
58
- }
59
- throw new Error(`Unknown storage mode: ${value}. Use local or cloud.`);
60
- }
61
- var DEPRECATED_STORAGE_MODE_ALIASES;
62
- var init_mode = __esm(() => {
63
- DEPRECATED_STORAGE_MODE_ALIASES = [
64
- "remote",
65
- "hybrid",
66
- "self_hosted"
49
+ // src/generated/storage-kit/own.ts
50
+ function ownProp(source, key) {
51
+ if (source === null || source === undefined)
52
+ return;
53
+ const kind = typeof source;
54
+ if (kind !== "object" && kind !== "function")
55
+ return;
56
+ if (!Object.hasOwn(source, key))
57
+ return;
58
+ return source[key];
59
+ }
60
+ function ownString(source, key) {
61
+ const value = ownProp(source, key);
62
+ return typeof value === "string" ? value : undefined;
63
+ }
64
+
65
+ // src/generated/storage-kit/backend.ts
66
+ function envToken(name) {
67
+ return name.toUpperCase().replace(/-/g, "_");
68
+ }
69
+ function serverDataBackendEnvKeys(name) {
70
+ const token = envToken(name);
71
+ return {
72
+ databaseUrlKeys: [`HASNA_${token}_DATABASE_URL`, `${token}_DATABASE_URL`]
73
+ };
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) {
85
+ for (const key of keys) {
86
+ const value = ownString(env, key)?.trim();
87
+ if (value)
88
+ return { key, value };
89
+ }
90
+ return null;
91
+ }
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
+ function resolveServerDataBackend(name, env = process.env) {
107
+ assertNoLegacyStorageMode(name, env);
108
+ const databaseUrl = firstEnv(env, serverDataBackendEnvKeys(name).databaseUrlKeys);
109
+ if (!databaseUrl) {
110
+ return {
111
+ backend: "sqlite",
112
+ source: "default",
113
+ databaseUrlPresent: false,
114
+ databaseUrlSource: null
115
+ };
116
+ }
117
+ return {
118
+ backend: "postgresql",
119
+ source: databaseUrl.key,
120
+ databaseUrlPresent: true,
121
+ databaseUrlSource: databaseUrl.key
122
+ };
123
+ }
124
+ function resolveDatabaseUrl(name, env = process.env) {
125
+ assertNoLegacyStorageMode(name, env);
126
+ const hit = firstEnv(env, serverDataBackendEnvKeys(name).databaseUrlKeys);
127
+ return hit?.value ?? null;
128
+ }
129
+ var init_backend = () => {};
130
+
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"
67
152
  ];
68
153
  });
69
154
 
@@ -328,27 +413,6 @@ function readEnv(name) {
328
413
  const value = process.env[name]?.trim();
329
414
  return value ? value : null;
330
415
  }
331
- function warnDeprecatedStorageMode(alias) {
332
- if (warnedDeprecatedModes.has(alias))
333
- return;
334
- warnedDeprecatedModes.add(alias);
335
- 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" });
336
- }
337
- function normalizeStorageMode2(value, source) {
338
- if (!value || !value.trim())
339
- return null;
340
- let normalized;
341
- try {
342
- normalized = normalizeStorageMode(value);
343
- } catch (error) {
344
- const detail = error instanceof Error ? error.message : String(error);
345
- throw new Error(`mementos: ${source}=${value} is not a valid mode. ${detail}`);
346
- }
347
- if (normalized.deprecatedAlias) {
348
- warnDeprecatedStorageMode(normalized.deprecatedAlias);
349
- }
350
- return normalized.mode;
351
- }
352
416
  function readConfigFile() {
353
417
  if (!existsSync(STORAGE_CONFIG_PATH)) {
354
418
  return {};
@@ -384,19 +448,15 @@ function getStorageDatabaseUrl() {
384
448
  function getStorageDatabaseEnvName() {
385
449
  return getStorageEnvName("databaseUrl");
386
450
  }
387
- function getStorageModeOverride() {
388
- for (const env of MODE_ENV_NAMES) {
389
- const value = normalizeStorageMode2(readEnv(env.name) ?? undefined, env.name);
390
- if (value)
391
- return value;
392
- }
393
- return null;
451
+ function getStorageBackend(env = process.env) {
452
+ return resolveServerDataBackend("mementos", env).backend;
453
+ }
454
+ function getStorageBackendDatabaseUrl(env = process.env) {
455
+ return resolveDatabaseUrl("mementos", env);
394
456
  }
395
457
  function getStorageConfig() {
458
+ assertNoLegacyStorageMode2();
396
459
  const fileConfig = readConfigFile();
397
- const modeOverride = getStorageModeOverride();
398
- const envConnectionString = getConfiguredConnectionString();
399
- const fileMode = normalizeStorageMode2(fileConfig.mode, `${STORAGE_CONFIG_PATH} "mode"`);
400
460
  const merged = {
401
461
  ...DEFAULT_STORAGE_CONFIG,
402
462
  ...fileConfig,
@@ -407,19 +467,10 @@ function getStorageConfig() {
407
467
  sync: {
408
468
  ...DEFAULT_STORAGE_CONFIG.sync,
409
469
  ...fileConfig.sync ?? {}
410
- },
411
- mode: fileMode ?? DEFAULT_STORAGE_CONFIG.mode
470
+ }
412
471
  };
413
- if (modeOverride) {
414
- merged.mode = modeOverride;
415
- } else if (envConnectionString && merged.mode === "local") {
416
- merged.mode = "cloud";
417
- }
418
472
  return merged;
419
473
  }
420
- function getStorageMode() {
421
- return getStorageConfig().mode;
422
- }
423
474
  function isSecretQueryParam(key) {
424
475
  const normalized = key.trim().toLowerCase();
425
476
  return SECRET_QUERY_PARAMS.has(normalized) || /(?:secret|token|password|passphrase|credential|api[_-]?key|apikey|private[_-]?key|auth|session)/i.test(normalized);
@@ -490,7 +541,7 @@ function storageEnvStatus(key) {
490
541
  configured: readEnv(activeName) !== null
491
542
  };
492
543
  }
493
- function remoteDatabaseConfigStatus(config) {
544
+ function postgresBackendStatus(config) {
494
545
  const env = getStorageDatabaseEnv();
495
546
  const envUrl = env ? readEnv(env.name) : null;
496
547
  if (env && envUrl) {
@@ -528,12 +579,8 @@ function remoteDatabaseConfigStatus(config) {
528
579
  rds_compatible: configured
529
580
  };
530
581
  }
531
- function runtimeKindFor(mode) {
532
- return mode === "cloud" ? "cloud-postgres" : "local-sqlite";
533
- }
534
582
  function getSafeStorageConfigSummary(config = getStorageConfig()) {
535
583
  return {
536
- mode: config.mode,
537
584
  auto_sync_interval_minutes: config.auto_sync_interval_minutes,
538
585
  feedback_endpoint_configured: config.feedback_endpoint.trim() !== "",
539
586
  sync: { ...config.sync },
@@ -548,29 +595,30 @@ function getSafeStorageConfigSummary(config = getStorageConfig()) {
548
595
  };
549
596
  }
550
597
  function getStorageStatus() {
598
+ assertNoLegacyStorageMode2();
551
599
  const config = getStorageConfig();
552
- const mode = config.mode;
553
- const remoteRequested = mode === "cloud";
554
- const remote = remoteDatabaseConfigStatus(config);
600
+ const backend = getStorageBackend();
601
+ const postgresRequested = backend === "postgresql";
602
+ const postgres = postgresBackendStatus(config);
555
603
  const issues = [];
556
604
  const warnings = [];
557
- if (remoteRequested && !remote.configured) {
558
- issues.push(`Cloud PostgreSQL/RDS storage is requested but not configured. ${remote.issues.join(" ")}`);
605
+ if (postgresRequested && !postgres.configured) {
606
+ issues.push(`PostgreSQL is selected (HASNA_MEMENTOS_DATABASE_URL) but not configured. ${postgres.issues.join(" ")}`);
559
607
  }
560
- if (mode === "local" && remote.issues.length > 0 && remote.source !== "none") {
561
- warnings.push(`Cloud PostgreSQL/RDS configuration is present but invalid; cloud runtime stays disabled until fixed. ${remote.issues.join(" ")}`);
608
+ if (!postgresRequested && postgres.issues.length > 0 && postgres.source !== "none") {
609
+ warnings.push(`PostgreSQL configuration is present but invalid; the postgresql backend stays disabled until fixed. ${postgres.issues.join(" ")}`);
562
610
  }
563
- if (mode === "local" && remote.configured) {
564
- warnings.push("Cloud PostgreSQL/RDS configuration is present, but storage mode is local; cloud runtime stays disabled until mode is cloud.");
611
+ if (!postgresRequested && postgres.configured) {
612
+ warnings.push("PostgreSQL configuration is present, but the selected backend is sqlite; the postgresql backend stays disabled until HASNA_MEMENTOS_DATABASE_URL is set.");
565
613
  }
566
- const failClosed = remoteRequested && !remote.configured;
614
+ const failClosed = postgresRequested && !postgres.configured;
567
615
  const runtime = {
568
- contract: "mementos-cloud-runtime-v1",
569
- kind: runtimeKindFor(mode),
616
+ contract: "mementos-storage-runtime-v1",
617
+ kind: backend,
570
618
  fail_closed: failClosed,
571
619
  local: {
572
620
  adapter: "sqlite",
573
- primary_runtime: mode === "local",
621
+ primary_runtime: backend === "sqlite",
574
622
  data_dir: LOCAL_DATA_DIR,
575
623
  config_path: STORAGE_CONFIG_PATH,
576
624
  local_file_sync: {
@@ -578,34 +626,22 @@ function getStorageStatus() {
578
626
  reason: "Mementos stores local state in SQLite; it does not sync raw local data files."
579
627
  }
580
628
  },
581
- remote: {
629
+ backend: {
582
630
  adapter: "postgres",
583
- purpose: "primary-runtime",
584
- requested: remoteRequested,
585
- configured: remote.configured,
586
- source: remote.source,
587
- env_name: remote.env_name,
588
- redacted_url: remote.redacted_url,
589
- rds_compatible: remote.rds_compatible,
631
+ requested: postgresRequested,
632
+ configured: postgres.configured,
633
+ source: postgres.source,
634
+ env_name: postgres.env_name,
635
+ redacted_url: postgres.redacted_url,
636
+ rds_compatible: postgres.rds_compatible,
590
637
  fail_closed: failClosed,
591
- missing: remote.missing
592
- },
593
- object_storage: {
594
- s3: {
595
- supported: false,
596
- mutation_allowed: false,
597
- reason: "No S3 object-storage adapter is part of this runtime."
598
- },
599
- aws: {
600
- mutation_allowed: false,
601
- reason: "Diagnostics do not store sensitive values, change AWS resources, deploy, or mutate production data."
602
- }
638
+ missing: postgres.missing
603
639
  },
604
640
  migrations: {
605
641
  target: "postgres-rds-compatible",
606
642
  command: "mementos storage migrate",
607
643
  dry_run_command: "mementos storage migrate --dry-run",
608
- configured: remote.configured,
644
+ configured: postgres.configured,
609
645
  mutates_remote_on_apply: true,
610
646
  requires_approval_for_live_run: true
611
647
  }
@@ -613,21 +649,19 @@ function getStorageStatus() {
613
649
  return {
614
650
  ok: issues.length === 0,
615
651
  service: "mementos",
616
- mode,
617
- local_default: mode === "local",
618
- remote_enabled: remoteRequested,
652
+ backend,
653
+ local_default: backend === "sqlite",
619
654
  runtime,
620
655
  database: {
621
- configured: remote.configured,
622
- redacted_url: remote.redacted_url,
623
- source: remote.source,
624
- env_name: remote.env_name,
625
- rds_compatible: remote.rds_compatible
656
+ configured: postgres.configured,
657
+ redacted_url: postgres.redacted_url,
658
+ source: postgres.source,
659
+ env_name: postgres.env_name,
660
+ rds_compatible: postgres.rds_compatible
626
661
  },
627
662
  tables: MEMENTOS_STORAGE_TABLES,
628
663
  env: {
629
- databaseUrl: storageEnvStatus("databaseUrl"),
630
- mode: storageEnvStatus("mode")
664
+ databaseUrl: storageEnvStatus("databaseUrl")
631
665
  },
632
666
  issues,
633
667
  warnings,
@@ -643,8 +677,9 @@ function getConfiguredConnectionString() {
643
677
  return getStorageDatabaseUrl() ?? undefined;
644
678
  }
645
679
  function getStorageConnectionString(dbName = "mementos") {
680
+ assertNoLegacyStorageMode2();
646
681
  if (!isServerContext()) {
647
- 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 self-hosted HTTP API: set HASNA_MEMENTOS_API_URL and " + "HASNA_MEMENTOS_API_KEY (and unset HASNA_MEMENTOS_DATABASE_URL / HASNA_MEMENTOS_STORAGE_MODE).");
682
+ 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).");
648
683
  }
649
684
  const envConnectionString = getConfiguredConnectionString();
650
685
  if (envConnectionString) {
@@ -815,7 +850,7 @@ function resetAllSyncMeta(db) {
815
850
  ensureSyncMetaTable(db);
816
851
  db.run("DELETE FROM _sync_meta");
817
852
  }
818
- var _serverContext = false, PgSyncPool, MEMENTOS_STORAGE_TABLES, STORAGE_TABLES, 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, getMementosStorageStatus, SYNC_EXCLUDED_TABLE_PATTERNS, SYNC_META_TABLE_SQL = `
853
+ var _serverContext = false, PgSyncPool, MEMENTOS_STORAGE_TABLES, STORAGE_TABLES, MEMENTOS_STORAGE_ENV, MEMENTOS_STORAGE_FALLBACK_ENV, LOCAL_DATA_DIR, DEFAULT_STORAGE_CONFIG, STORAGE_CONFIG_DIR, STORAGE_CONFIG_PATH, DATABASE_ENV_NAMES, SECRET_QUERY_PARAMS, getMementosStorageStatus, SYNC_EXCLUDED_TABLE_PATTERNS, SYNC_META_TABLE_SQL = `
819
854
  CREATE TABLE IF NOT EXISTS _sync_meta (
820
855
  table_name TEXT PRIMARY KEY,
821
856
  last_synced_at TEXT,
@@ -823,7 +858,8 @@ CREATE TABLE IF NOT EXISTS _sync_meta (
823
858
  direction TEXT DEFAULT 'push'
824
859
  )`;
825
860
  var init_storage = __esm(() => {
826
- init_mode();
861
+ init_backend();
862
+ init_retired_storage_mode();
827
863
  PgSyncPool = class PgSyncPool {
828
864
  worker;
829
865
  status;
@@ -910,12 +946,10 @@ var init_storage = __esm(() => {
910
946
  ];
911
947
  STORAGE_TABLES = MEMENTOS_STORAGE_TABLES;
912
948
  MEMENTOS_STORAGE_ENV = {
913
- databaseUrl: "HASNA_MEMENTOS_DATABASE_URL",
914
- mode: "HASNA_MEMENTOS_STORAGE_MODE"
949
+ databaseUrl: "HASNA_MEMENTOS_DATABASE_URL"
915
950
  };
916
951
  MEMENTOS_STORAGE_FALLBACK_ENV = {
917
- databaseUrl: "MEMENTOS_DATABASE_URL",
918
- mode: "MEMENTOS_STORAGE_MODE"
952
+ databaseUrl: "MEMENTOS_DATABASE_URL"
919
953
  };
920
954
  LOCAL_DATA_DIR = join(homedir(), ".hasna", "mementos");
921
955
  DEFAULT_STORAGE_CONFIG = {
@@ -926,7 +960,6 @@ var init_storage = __esm(() => {
926
960
  password_env: "MEMENTOS_DATABASE_PASSWORD",
927
961
  ssl: true
928
962
  },
929
- mode: "local",
930
963
  auto_sync_interval_minutes: 0,
931
964
  feedback_endpoint: "",
932
965
  sync: {
@@ -939,11 +972,6 @@ var init_storage = __esm(() => {
939
972
  { name: MEMENTOS_STORAGE_ENV.databaseUrl, deprecated: false },
940
973
  { name: MEMENTOS_STORAGE_FALLBACK_ENV.databaseUrl, deprecated: false }
941
974
  ];
942
- MODE_ENV_NAMES = [
943
- { name: MEMENTOS_STORAGE_ENV.mode, deprecated: false },
944
- { name: MEMENTOS_STORAGE_FALLBACK_ENV.mode, deprecated: false }
945
- ];
946
- warnedDeprecatedModes = new Set;
947
975
  SECRET_QUERY_PARAMS = new Set([
948
976
  "password",
949
977
  "pass",
@@ -968,7 +996,7 @@ import { tmpdir } from "os";
968
996
  import { join as join2 } from "path";
969
997
  import { writeFileSync as writeFileSync2, unlinkSync } from "fs";
970
998
  import { randomUUID } from "crypto";
971
- function firstEnv(keys) {
999
+ function firstEnv2(keys) {
972
1000
  for (const k of keys) {
973
1001
  const v = process.env[k]?.trim();
974
1002
  if (v)
@@ -984,7 +1012,7 @@ function firstEnvKey(keys) {
984
1012
  return null;
985
1013
  }
986
1014
  function hasDatabaseUrl() {
987
- return Boolean(firstEnv(DATABASE_URL_ENV_KEYS));
1015
+ return Boolean(firstEnv2(DATABASE_URL_ENV_KEYS));
988
1016
  }
989
1017
  function isLoopbackHost(rawHost) {
990
1018
  const host = rawHost.replace(/^\[/, "").replace(/\]$/, "").toLowerCase();
@@ -1015,7 +1043,8 @@ function normalizeBase(raw) {
1015
1043
  return base;
1016
1044
  return `${base}/v1`;
1017
1045
  }
1018
- function assertUnambiguousStoreEnv() {
1046
+ function assertUnambiguousStoreEnv(env = process.env) {
1047
+ assertNoLegacyStorageMode2(env);
1019
1048
  if (firstEnvKey(DB_PATH_ENV_KEYS))
1020
1049
  return;
1021
1050
  if (hasDatabaseUrl())
@@ -1033,8 +1062,8 @@ function getApiConfig() {
1033
1062
  assertUnambiguousStoreEnv();
1034
1063
  if (firstEnvKey(DB_PATH_ENV_KEYS))
1035
1064
  return null;
1036
- const rawBase = firstEnv(API_URL_ENV_KEYS);
1037
- const apiKey = firstEnv(API_KEY_ENV_KEYS);
1065
+ const rawBase = firstEnv2(API_URL_ENV_KEYS);
1066
+ const apiKey = firstEnv2(API_KEY_ENV_KEYS);
1038
1067
  if (!rawBase || !apiKey)
1039
1068
  return null;
1040
1069
  return { baseUrl: normalizeBase(rawBase), apiKey };
@@ -1118,7 +1147,16 @@ x-api-key: ${cfg.apiKey}
1118
1147
  function apiJson(method, path, body, options) {
1119
1148
  const raw = apiRequestRaw(method, path, body);
1120
1149
  if (raw.status >= 200 && raw.status < 300) {
1121
- const data = raw.body.trim() ? JSON.parse(raw.body) : undefined;
1150
+ let data;
1151
+ if (raw.body.trim()) {
1152
+ try {
1153
+ data = JSON.parse(raw.body);
1154
+ } catch (e) {
1155
+ throw new ApiRequestError(`mementos cloud ${method} ${path} returned status ${raw.status} with a body that is not valid JSON (${e instanceof Error ? e.message : String(e)}) \u2014 the response is truncated or the server is unhealthy`, raw.status, raw.body.slice(0, 500));
1156
+ }
1157
+ } else {
1158
+ data = undefined;
1159
+ }
1122
1160
  return { status: raw.status, data };
1123
1161
  }
1124
1162
  if (raw.status === 404 && options?.allow404) {
@@ -1155,6 +1193,7 @@ function toQuery(params) {
1155
1193
  }
1156
1194
  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";
1157
1195
  var init_api_mode = __esm(() => {
1196
+ init_retired_storage_mode();
1158
1197
  API_URL_ENV_KEYS = ["HASNA_MEMENTOS_API_URL", "MEMENTOS_API_URL"];
1159
1198
  API_KEY_ENV_KEYS = ["HASNA_MEMENTOS_API_KEY", "MEMENTOS_API_KEY"];
1160
1199
  DATABASE_URL_ENV_KEYS = ["HASNA_MEMENTOS_DATABASE_URL", "MEMENTOS_DATABASE_URL"];
@@ -2657,7 +2696,7 @@ function getCloudDatabase() {
2657
2696
  if (_pg)
2658
2697
  return _pg;
2659
2698
  if (!isServerContext()) {
2660
- throw new Error("Direct Postgres (cloud) storage is server-only. A client must use the " + "self-hosted HTTP API (HASNA_MEMENTOS_API_URL + HASNA_MEMENTOS_API_KEY), " + "never a database DSN. Unset HASNA_MEMENTOS_DATABASE_URL / HASNA_MEMENTOS_STORAGE_MODE " + "on this machine to use local SQLite, or configure the API client to reach the cloud.");
2699
+ throw new Error("Direct PostgreSQL storage is server-only. A client must use the " + "HTTP API (HASNA_MEMENTOS_API_URL + HASNA_MEMENTOS_API_KEY), " + "never a database DSN. Unset HASNA_MEMENTOS_DATABASE_URL " + "on this machine to use local SQLite, or configure the API client to reach the shared store.");
2661
2700
  }
2662
2701
  const connectionString = getStorageConnectionString();
2663
2702
  _pg = new PgAdapter(connectionString);
@@ -2670,7 +2709,7 @@ function getDatabase(dbPath) {
2670
2709
  if (isApiMode()) {
2671
2710
  throw new Error("mementos is in API mode (HASNA_MEMENTOS_API_URL + HASNA_MEMENTOS_API_KEY set) " + "but this operation tried to open a local SQLite database. That would create a " + "split-brain local island instead of using the shared cloud store. This op must " + "route through the API client (src/db/api-mode.ts); if it needs a server endpoint " + "that does not exist yet, add it to src/server and redeploy. To use local storage " + "instead, unset HASNA_MEMENTOS_API_URL / HASNA_MEMENTOS_API_KEY.");
2672
2711
  }
2673
- if (getStorageMode() === "cloud") {
2712
+ if (getStorageBackend() === "postgresql") {
2674
2713
  return getCloudDatabase();
2675
2714
  }
2676
2715
  if (process.env["NODE_ENV"] === "test") {
@@ -2856,6 +2895,90 @@ class MementosProjectRegistrationError extends Error {
2856
2895
  }
2857
2896
  }
2858
2897
 
2898
+ // src/project-registration/historical-receipt.ts
2899
+ var ROOT_TENANT_ID = "adfd95c7-ee8b-52cb-ae47-4ae65dae3313";
2900
+ var FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION = {
2901
+ authority: "mementos",
2902
+ authority_route: "mementos.project-registration.v1",
2903
+ package_version: "1.0.0-rc.3",
2904
+ authority_id: "mementos",
2905
+ tenant_id: "default",
2906
+ corpus_id: "default",
2907
+ operation_id: "op-cli-register-full",
2908
+ step_id: "mementos_project",
2909
+ resource_kind: "project",
2910
+ direction: "forward",
2911
+ target_selector: "wks_005285827590a93b70e5",
2912
+ target_id: "mm_project_f75606ef14e51fb577a15882ba0ab8ed333b2c29",
2913
+ idempotency_key: "prk_786cdc6babddeae1be8d5078d0f75322d1ea1010ac5d8a50",
2914
+ receipt_id: "mmpr_a647f9908a33bb64bf137f584202590f91b72a33",
2915
+ request_digest: "8eb0e3fee26fdb7edb8a444aeddaaedb79a84a5a36e742ae03616f62b19f5c22",
2916
+ precondition_digest: "0b6c22fcf75d835f9f27308b1a9d40e8902e48a97da370fd56df5acacfcd968e",
2917
+ result_revision: "2026-08-09T13:37:43.068Z",
2918
+ result_digest: "26fb0dd5c5e20909d3653ab0ddd8fa45e5142728e4e66d595e2419f7ed40b036",
2919
+ created_at: "2026-08-09T13:37:43.068Z",
2920
+ identity_digest: "1108dd972c90e4989036fb6ecec7dde10f54641a697bd53cfcd64017f589ace7",
2921
+ target_digest: "72789fcd60668eeb66ee068936d3afa96deadcb007228830c64b6de783b6dcef",
2922
+ operation_digest: "d7e37bfb2c0d28f8c4086c53224cd855d14694f32807b74c102b61a99963f3c0",
2923
+ idempotency_digest: "b56d8ec37efb818759cdd2ee3b58bc1401ac13384c8d64c1813bb5c198b1e376",
2924
+ receipt_digest: "52ce90c5de6817989f107d9d27e06156962a2b9c365e34e16a57aa37886501a2",
2925
+ response_digest: "2fa3ed508f5f192c18753b1205ced996340fdf02f735420d32db897fc239805c"
2926
+ };
2927
+ var FLEET_RESOURCES_HISTORICAL_RECEIPT = {
2928
+ receipt_id: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.receipt_id,
2929
+ authority: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.authority,
2930
+ route: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.authority_route,
2931
+ package_version: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.package_version,
2932
+ authority_id: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.authority_id,
2933
+ tenant_id: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.tenant_id,
2934
+ corpus_id: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.corpus_id,
2935
+ operation_id: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.operation_id,
2936
+ step_id: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.step_id,
2937
+ resource_kind: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.resource_kind,
2938
+ direction: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.direction,
2939
+ idempotency_key: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.idempotency_key,
2940
+ request_digest: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.request_digest,
2941
+ precondition_digest: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.precondition_digest,
2942
+ outcome: "accepted",
2943
+ reason: null,
2944
+ target_id: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.target_id,
2945
+ result_revision: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.result_revision,
2946
+ result_digest: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.result_digest,
2947
+ duplicate_of_receipt_id: null,
2948
+ accepted_receipt_id: null,
2949
+ created_by_operation: true,
2950
+ created_at: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.created_at
2951
+ };
2952
+ var FLEET_RESOURCES_CURRENT_PROJECT_REGISTRATION_IDENTITY = {
2953
+ authority_id: "mementos",
2954
+ tenant_id: ROOT_TENANT_ID,
2955
+ corpus_id: "mementos:postgresql"
2956
+ };
2957
+ var FLEET_RESOURCES_HISTORICAL_LOOKUP_IDENTITY = {
2958
+ source: {
2959
+ authority: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.authority,
2960
+ authority_route: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.authority_route,
2961
+ package_version: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.package_version,
2962
+ authority_id: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.authority_id,
2963
+ tenant_id: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.tenant_id,
2964
+ corpus_id: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.corpus_id,
2965
+ operation_id: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.operation_id,
2966
+ step_id: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.step_id,
2967
+ resource_kind: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.resource_kind,
2968
+ direction: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.direction,
2969
+ target_selector: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.target_selector,
2970
+ target_id: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.target_id,
2971
+ idempotency_key: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.idempotency_key,
2972
+ receipt_id: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.receipt_id
2973
+ },
2974
+ destination: FLEET_RESOURCES_CURRENT_PROJECT_REGISTRATION_IDENTITY,
2975
+ lookup_only: true,
2976
+ immutable_receipt: true
2977
+ };
2978
+ function supportsFleetResourcesHistoricalReceiptLookup(capability) {
2979
+ return capability.authority_id === FLEET_RESOURCES_CURRENT_PROJECT_REGISTRATION_IDENTITY.authority_id && capability.tenant_id === FLEET_RESOURCES_CURRENT_PROJECT_REGISTRATION_IDENTITY.tenant_id && capability.corpus_id === FLEET_RESOURCES_CURRENT_PROJECT_REGISTRATION_IDENTITY.corpus_id;
2980
+ }
2981
+
2859
2982
  // src/project-registration/identity.ts
2860
2983
  var MEMENTOS_PROJECT_AUTHORITY_ENV = {
2861
2984
  authorityId: "MEMENTOS_PROJECT_AUTHORITY_ID",
@@ -2897,12 +3020,13 @@ function resolveMementosProjectAuthorityIdentity(options = {}) {
2897
3020
  }
2898
3021
  function buildMementosProjectRegistrationCapability(options = {}) {
2899
3022
  const identity = resolveMementosProjectAuthorityIdentity(options);
2900
- return {
3023
+ const capability = {
2901
3024
  authority: "mementos",
2902
3025
  route: MEMENTOS_PROJECT_REGISTRATION_ROUTE,
2903
3026
  package_version: options.packageVersion ?? getMementosPackageVersion(),
2904
3027
  ...identity,
2905
3028
  supported_resources: ["project"],
3029
+ supported_historical_lookup_identities: [],
2906
3030
  conditional_create: true,
2907
3031
  immutable_receipts: true,
2908
3032
  exact_terminal_lookup: true,
@@ -2921,6 +3045,12 @@ function buildMementosProjectRegistrationCapability(options = {}) {
2921
3045
  stable_keyset_pagination: true,
2922
3046
  explicit_membership_only: true
2923
3047
  };
3048
+ if (supportsFleetResourcesHistoricalReceiptLookup(capability)) {
3049
+ capability.supported_historical_lookup_identities = [
3050
+ FLEET_RESOURCES_HISTORICAL_LOOKUP_IDENTITY
3051
+ ];
3052
+ }
3053
+ return capability;
2924
3054
  }
2925
3055
 
2926
3056
  // src/db/projects.ts
@@ -3998,12 +4128,21 @@ function assertInverseRequest(request, capability) {
3998
4128
  }
3999
4129
  return accepted;
4000
4130
  }
4131
+ function matchesCurrentLookupIdentity(request, capability) {
4132
+ return request.authority_route === capability.route && request.package_version === capability.package_version && request.authority_id === capability.authority_id && request.tenant_id === capability.tenant_id && request.corpus_id === capability.corpus_id;
4133
+ }
4134
+ function matchesFleetResourcesHistoricalLookup(request, capability) {
4135
+ if (!supportsFleetResourcesHistoricalReceiptLookup(capability))
4136
+ return false;
4137
+ const source = FLEET_RESOURCES_HISTORICAL_LOOKUP_IDENTITY.source;
4138
+ return request.authority === source.authority && request.authority_route === source.authority_route && request.package_version === source.package_version && request.authority_id === source.authority_id && request.tenant_id === source.tenant_id && request.corpus_id === source.corpus_id && request.operation_id === source.operation_id && request.step_id === source.step_id && request.resource_kind === source.resource_kind && request.direction === source.direction && request.target_selector === source.target_selector && request.target_id === source.target_id && request.idempotency_key === source.idempotency_key;
4139
+ }
4001
4140
  function assertLookup(request, capability) {
4002
4141
  assertBounds(request);
4003
4142
  if (request.max_items !== 1) {
4004
4143
  throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_BOUNDS", "terminal receipt lookup requires max_items exactly 1");
4005
4144
  }
4006
- if (request.authority !== "mementos" || request.resource_kind !== "project" || request.authority_route !== capability.route || request.package_version !== capability.package_version || request.authority_id !== capability.authority_id || request.tenant_id !== capability.tenant_id || request.corpus_id !== capability.corpus_id) {
4145
+ if (request.authority !== "mementos" || request.resource_kind !== "project" || !matchesCurrentLookupIdentity(request, capability) && !matchesFleetResourcesHistoricalLookup(request, capability)) {
4007
4146
  throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_CAPABILITY_MISMATCH", "receipt lookup does not match this authority capability identity");
4008
4147
  }
4009
4148
  requireString(request.operation_id, "operation_id", { pattern: OPERATION_PATTERN });
@@ -4021,6 +4160,7 @@ function assertLookup(request, capability) {
4021
4160
  pattern: PROJECT_ID_PATTERN
4022
4161
  });
4023
4162
  }
4163
+ return matchesCurrentLookupIdentity(request, capability) ? "current" : "fleet_resources_historical";
4024
4164
  }
4025
4165
 
4026
4166
  class PackageOwnedMementosProjectRegistrationAuthority {
@@ -4285,11 +4425,14 @@ class PackageOwnedMementosProjectRegistrationAuthority {
4285
4425
  }
4286
4426
  async lookupReceipt(request) {
4287
4427
  const startedAt = Date.now();
4288
- assertLookup(request, this.capabilityValue);
4428
+ const lookupIdentity = assertLookup(request, this.capabilityValue);
4289
4429
  const receipt = getReceiptForLookup(this.db, request);
4290
4430
  if (!receipt || request.target_id !== undefined && receipt.target_id !== request.target_id) {
4291
4431
  throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_RECEIPT_NOT_FOUND", "exact immutable terminal receipt was not found");
4292
4432
  }
4433
+ if (lookupIdentity === "fleet_resources_historical" && (receipt.receipt_id !== FLEET_RESOURCES_HISTORICAL_LOOKUP_IDENTITY.source.receipt_id || canonicalMementosProjectRegistrationJson(publicReceipt(receipt)) !== canonicalMementosProjectRegistrationJson(FLEET_RESOURCES_HISTORICAL_RECEIPT))) {
4434
+ throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_RECEIPT_NOT_FOUND", "exact immutable historical terminal receipt was not found");
4435
+ }
4293
4436
  return withResponseControl(publicReceipt(receipt), request, startedAt);
4294
4437
  }
4295
4438
  async compensate(request) {
@@ -4889,12 +5032,12 @@ function deserializeEmbedding(raw) {
4889
5032
  var REDACTED = "[REDACTED]";
4890
5033
  var SECRET_PATTERNS = [
4891
5034
  { name: "openai_key", pattern: /sk-[a-zA-Z0-9_-]{20,}/g },
4892
- { name: "anthropic_key", pattern: /sk-ant-[a-zA-Z0-9_-]{20,}/g },
5035
+ { name: "anthropic_key", pattern: /sk[-]ant[-][a-zA-Z0-9_-]{20,}/g },
4893
5036
  { name: "generic_key", pattern: /(?:pk|tok|key|token|api[_-]?key)[_-][a-zA-Z0-9_-]{16,}/gi },
4894
5037
  { name: "aws_key", pattern: /AKIA[A-Z0-9]{16}/g },
4895
5038
  { name: "aws_secret", pattern: /(?<=AWS_SECRET_ACCESS_KEY\s*=\s*)[A-Za-z0-9/+=]{40}/g },
4896
5039
  { name: "github_token", pattern: /gh[ps]_[a-zA-Z0-9]{36,}/g },
4897
- { name: "github_oauth", pattern: /gho_[a-zA-Z0-9]{36,}/g },
5040
+ { name: "github_oauth", pattern: /gho[_][a-zA-Z0-9]{36,}/g },
4898
5041
  { name: "npm_token", pattern: /npm_[a-zA-Z0-9]{36,}/g },
4899
5042
  { name: "bearer", pattern: /Bearer\s+[a-zA-Z0-9_\-.]{20,}/g },
4900
5043
  { name: "conn_string", pattern: /(?:postgres|postgresql|mysql|mongodb|redis|amqp|mqtt):\/\/[^\s"'`]+@[^\s"'`]+/gi },
@@ -5446,29 +5589,7 @@ function getMemoriesByKey(key, scope, agentId, projectId, db) {
5446
5589
  const rows = d.query(sql).all(...params);
5447
5590
  return rows.map(parseMemoryRow);
5448
5591
  }
5449
- function listMemories(filter, db) {
5450
- if (!db && isApiMode()) {
5451
- const f = filter || {};
5452
- const q = toQuery({
5453
- key: f.key,
5454
- scope: f.scope,
5455
- category: f.category,
5456
- status: f.status,
5457
- tags: f.tags,
5458
- min_importance: f.min_importance,
5459
- pinned: f.pinned,
5460
- agent_id: f.agent_id,
5461
- project_id: f.project_id,
5462
- session_id: f.session_id,
5463
- namespace: f.namespace,
5464
- as_of: f.as_of,
5465
- limit: f.limit,
5466
- offset: f.offset
5467
- });
5468
- const { data } = apiJson("GET", `/memories${q}`);
5469
- return data?.memories ?? [];
5470
- }
5471
- const d = db || getDatabase();
5592
+ function buildMemoryListConditions(filter) {
5472
5593
  const conditions = [];
5473
5594
  const params = [];
5474
5595
  if (filter) {
@@ -5580,21 +5701,99 @@ function listMemories(filter, db) {
5580
5701
  } else {
5581
5702
  conditions.push("status = 'active'");
5582
5703
  }
5704
+ return { conditions, params };
5705
+ }
5706
+ function listMemoriesPage(filter, db) {
5707
+ const f = filter || {};
5708
+ if (!db && isApiMode()) {
5709
+ const q = toQuery({
5710
+ key: f.key,
5711
+ scope: f.scope,
5712
+ category: f.category,
5713
+ status: f.status,
5714
+ tags: f.tags,
5715
+ min_importance: f.min_importance,
5716
+ pinned: f.pinned,
5717
+ agent_id: f.agent_id,
5718
+ project_id: f.project_id,
5719
+ session_id: f.session_id,
5720
+ namespace: f.namespace,
5721
+ as_of: f.as_of,
5722
+ limit: f.limit,
5723
+ offset: f.offset
5724
+ });
5725
+ const { data } = apiJson("GET", `/memories${q}`);
5726
+ return {
5727
+ rows: data?.memories ?? [],
5728
+ has_more: data?.has_more,
5729
+ next_cursor: data?.next_cursor ?? null
5730
+ };
5731
+ }
5732
+ const d = db || getDatabase();
5733
+ const { conditions, params } = buildMemoryListConditions(f);
5583
5734
  let sql = "SELECT * FROM memories";
5584
5735
  if (conditions.length > 0) {
5585
5736
  sql += ` WHERE ${conditions.join(" AND ")}`;
5586
5737
  }
5587
5738
  sql += " ORDER BY importance DESC, created_at DESC";
5588
- if (filter?.limit) {
5739
+ if (f.limit) {
5589
5740
  sql += " LIMIT ?";
5590
- params.push(filter.limit);
5741
+ params.push(f.limit);
5591
5742
  }
5592
- if (filter?.offset) {
5743
+ if (f.offset) {
5593
5744
  sql += " OFFSET ?";
5594
- params.push(filter.offset);
5745
+ params.push(f.offset);
5595
5746
  }
5596
5747
  const rows = d.query(sql).all(...params);
5597
- return rows.map(parseMemoryRow);
5748
+ const parsed = rows.map(parseMemoryRow);
5749
+ const hasMore = f.limit !== undefined && parsed.length === f.limit;
5750
+ return {
5751
+ rows: parsed,
5752
+ has_more: hasMore,
5753
+ next_cursor: hasMore ? (f.offset ?? 0) + parsed.length : null
5754
+ };
5755
+ }
5756
+ function listMemories(filter, db) {
5757
+ return listMemoriesPage(filter, db).rows;
5758
+ }
5759
+ function listMemoriesBounded(filter = {}, target, db) {
5760
+ const pageSize = 1000;
5761
+ const maxPages = 1000;
5762
+ const want = target === undefined ? undefined : target + 1;
5763
+ const rows = [];
5764
+ const seenCursors = new Set;
5765
+ let cursor = filter.offset ?? 0;
5766
+ let pages = 0;
5767
+ for (;; ) {
5768
+ if (want !== undefined && rows.length >= want)
5769
+ break;
5770
+ if (++pages > maxPages) {
5771
+ throw new Error(`memories list traversal exceeded ${maxPages} pages while collecting rows \u2014 aborting (cursor cycle?)`);
5772
+ }
5773
+ const limit = Math.min(want === undefined ? pageSize : want - rows.length, pageSize);
5774
+ const page = listMemoriesPage({ ...filter, limit, offset: cursor }, db);
5775
+ rows.push(...page.rows);
5776
+ if (page.has_more === false)
5777
+ break;
5778
+ if (page.has_more === undefined && page.rows.length < limit)
5779
+ break;
5780
+ if (page.has_more === true && (page.next_cursor === null || page.next_cursor === undefined)) {
5781
+ throw new Error("memories list page claimed more results without a next cursor \u2014 aborting");
5782
+ }
5783
+ const next = page.next_cursor ?? cursor + page.rows.length;
5784
+ if (seenCursors.has(next)) {
5785
+ throw new Error("memories list traversal repeated a cursor \u2014 aborting");
5786
+ }
5787
+ seenCursors.add(next);
5788
+ cursor = next;
5789
+ }
5790
+ const hasMore = target !== undefined && rows.length > target;
5791
+ const trimmed = hasMore ? rows.slice(0, target) : rows;
5792
+ return {
5793
+ rows: trimmed,
5794
+ has_more: hasMore,
5795
+ next_cursor: hasMore ? (filter.offset ?? 0) + trimmed.length : null
5796
+ };
5598
5797
  }
5599
5798
  function updateMemory(id, input, db) {
5600
5799
  if (!db && isApiMode()) {