@clude/sdk 3.0.4 → 3.3.0

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.
@@ -8,6 +8,9 @@ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
8
  var __esm = (fn, res) => function __init() {
9
9
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
10
  };
11
+ var __commonJS = (cb, mod) => function __require() {
12
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
13
+ };
11
14
  var __export = (target, all) => {
12
15
  for (var name in all)
13
16
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -688,7 +691,20 @@ var init_config = __esm({
688
691
  accessToken: requiredUnlessSiteOnly("X_ACCESS_TOKEN"),
689
692
  accessSecret: requiredUnlessSiteOnly("X_ACCESS_SECRET"),
690
693
  botUserId: requiredUnlessSiteOnly("X_BOT_USER_ID"),
691
- creatorUserId: optional("CREATOR_USER_ID", "")
694
+ creatorUserId: optional("CREATOR_USER_ID", ""),
695
+ /**
696
+ * Bearer token for the public "$ANSEM LIVE" feed (GET /api/ansem/feed).
697
+ * Reads-only X app bearer — used server-side for GET /2/tweets/search/recent.
698
+ * Empty → the feed endpoint returns { enabled:false } and the frontend panel hides.
699
+ * NEVER sent to the browser.
700
+ */
701
+ searchBearer: optional("X_SEARCH_BEARER", ""),
702
+ /** Min poll gap for the on-demand $ANSEM feed (ms). Idle → no polling → $0. */
703
+ ansemFeedIntervalMs: parseInt(optional("ANSEM_FEED_INTERVAL_MS", "90000"), 10),
704
+ /** Posts older than ~15min must clear this like floor (fresh posts are exempt). */
705
+ ansemFeedMinLikes: parseInt(optional("ANSEM_FEED_MIN_LIKES", "3"), 10),
706
+ /** Hard daily read-cap: once exceeded, serve the stale buffer (cost stop-loss). */
707
+ ansemFeedDailyReadCap: parseInt(optional("ANSEM_FEED_DAILY_READ_CAP", "40000"), 10)
692
708
  },
693
709
  supabase: {
694
710
  url: requiredUnlessSiteOnly("SUPABASE_URL"),
@@ -706,7 +722,9 @@ var init_config = __esm({
706
722
  optional("SOLANA_NETWORK", "mainnet-beta") === "devnet" ? "https://api.devnet.solana.com" : "https://api.mainnet-beta.solana.com"
707
723
  ),
708
724
  botWalletPrivateKey: optional("BOT_WALLET_PRIVATE_KEY", ""),
709
- cludeTokenMint: optional("CLUUDE_TOKEN_MINT", "")
725
+ cludeTokenMint: optional("CLUUDE_TOKEN_MINT", ""),
726
+ /** Deployed memory-registry Anchor program ID. Empty → on-chain registration disabled. */
727
+ memoryRegistryProgramId: optional("CLUDE_PROGRAM_ID", "")
710
728
  },
711
729
  server: {
712
730
  port: parseInt(optional("PORT", "3000"), 10),
@@ -744,6 +762,69 @@ var init_config = __esm({
744
762
  freePromoCreditUsdc: parseFloat(optional("FREE_PROMO_CREDIT_USDC", "1")),
745
763
  freePromoExpiry: optional("FREE_PROMO_EXPIRY", "")
746
764
  },
765
+ memory: {
766
+ /**
767
+ * Memory 3.0 C2: route storeMemory enrichment (embed/link/extract) through the durable
768
+ * memory_write_jobs outbox instead of fire-and-forget. Default OFF — fire-and-forget stays
769
+ * the default until the worker is proven. Requires migrations 044 + 045; degrades gracefully
770
+ * (falls back to fire-and-forget) if unapplied.
771
+ */
772
+ outboxEnabled: optional("MEMORY_OUTBOX", "false") === "true",
773
+ /**
774
+ * Memory 3.0 C1: write-time reconciliation, SHADOW slice (LLM-free). When on, each write records
775
+ * a PROPOSED reconcile op (add / needs_router / skip) into memory_reconciliation_log WITHOUT
776
+ * applying it — the labeled sample the enforce path must earn its turn-on from. Default OFF;
777
+ * runs fully detached after embedMemory (zero write latency); degrades gracefully if migration
778
+ * 046 is unapplied. Disabled under BENCH_MODE. The router is a later slice; only the cosine gate
779
+ * runs here.
780
+ */
781
+ reconcileEnabled: optional("MEMORY_RECONCILE", "false") === "true",
782
+ /** Screen floor passed to match_memories_temporal — LOW so max_cosine is captured for below-LO
783
+ * writes (LO is tuned from the shadow data, not fixed up-front). */
784
+ reconcileFloor: Number(optional("MEMORY_RECONCILE_FLOOR", "0.5")),
785
+ /** "similar enough to reconcile" boundary → needs_router (vs add). A starting probe, not gospel. */
786
+ reconcileLo: Number(optional("MEMORY_RECONCILE_LO", "0.85")),
787
+ /** hi/mid band LABEL boundary for analysis (not a decision boundary). */
788
+ reconcileHi: Number(optional("MEMORY_RECONCILE_HI", "0.95")),
789
+ /**
790
+ * C1 slice 1.5: the LLM router. Its own sub-flag (default OFF, independent of reconcileEnabled)
791
+ * so gate-only instrumentation runs first and LO is calibrated from that data before any LLM
792
+ * spend. When on, a >= LO write is classified add/update/noop by reconcileModel (still SHADOW —
793
+ * the op is logged, never applied). Requires OpenRouter configured.
794
+ */
795
+ reconcileRouter: optional("MEMORY_RECONCILE_ROUTER", "false") === "true",
796
+ /** Router model — an explicit id (NEVER a cognitiveFunction, which would silently override it
797
+ * with the fast llama slot). Haiku-class default: capable enough for dup-vs-update, cheap. */
798
+ reconcileModel: optional("MEMORY_RECONCILE_MODEL", "anthropic/claude-haiku-4.5"),
799
+ /** Per-owner soft daily cap on router calls (approximate, per-process) — bounds LLM spend. */
800
+ reconcileBudget: Number(optional("MEMORY_RECONCILE_BUDGET", "200"))
801
+ },
802
+ oauth: {
803
+ /** HMAC secret for signing OAuth access-token JWTs. Empty disables the OAuth AS — bearer API-key auth still works. */
804
+ signingSecret: optional("OAUTH_SIGNING_SECRET", ""),
805
+ /** Issuer/audience identifier baked into tokens. Falls back to the request origin when empty. */
806
+ issuer: optional("OAUTH_ISSUER", ""),
807
+ /** Access-token lifetime in seconds (default 1h). */
808
+ accessTtlSec: parseInt(optional("OAUTH_ACCESS_TTL_SEC", "3600"), 10),
809
+ /** Refresh-token lifetime in seconds (default 30d). */
810
+ refreshTtlSec: parseInt(optional("OAUTH_REFRESH_TTL_SEC", "2592000"), 10),
811
+ /** Authorization-code lifetime in seconds (default 60s). */
812
+ codeTtlSec: parseInt(optional("OAUTH_CODE_TTL_SEC", "60"), 10)
813
+ },
814
+ stripe: {
815
+ /**
816
+ * Stripe secret API key (sk_live_… / sk_test_…). Empty disables the
817
+ * marketplace Stripe rail — the orchestrator/webhook fail closed without it.
818
+ * SDK/MCP consumers never need this, so it stays optional() (not required()).
819
+ */
820
+ secretKey: optional("STRIPE_SECRET_KEY", ""),
821
+ /**
822
+ * Stripe webhook signing secret (whsec_…) used by stripe.webhooks.constructEvent
823
+ * to verify the raw body BEFORE any DB write (Risk R6). Empty ⇒ every webhook is
824
+ * rejected, so a misconfigured deploy can never process an unverified event.
825
+ */
826
+ webhookSecret: optional("STRIPE_WEBHOOK_SECRET", "")
827
+ },
747
828
  campaign: {
748
829
  startDate: optional("CAMPAIGN_START", "")
749
830
  },
@@ -757,6 +838,51 @@ var init_config = __esm({
757
838
  queryApiKey: optional("EMBEDDING_QUERY_API_KEY", ""),
758
839
  queryModel: optional("EMBEDDING_QUERY_MODEL", "")
759
840
  },
841
+ migration: {
842
+ /**
843
+ * Database backend that getDb() targets during the GCP parallel-run.
844
+ * 'supabase' (default, current live infra) | 'cloudsql' (PostgREST /
845
+ * self-hosted Supabase in front of Cloud SQL). Flip per-layer for the
846
+ * shadow-stack cutover; Supabase stays the source of truth until sunset.
847
+ */
848
+ dbTarget: optional("DB_TARGET", "supabase"),
849
+ /** PostgREST endpoint for the Cloud SQL backend (used only when DB_TARGET=cloudsql). */
850
+ cloudsqlUrl: optional("CLOUDSQL_PGREST_URL", ""),
851
+ /** Service key for the Cloud SQL PostgREST backend (used only when DB_TARGET=cloudsql). */
852
+ cloudsqlServiceKey: optional("CLOUDSQL_SERVICE_KEY", ""),
853
+ /**
854
+ * Active embedding vector space for ingest + recall. 'voyage' (default,
855
+ * current corpus) | 'vertex' (shadow column, only after the LongMemEval gate
856
+ * passes). Separate from EMBEDDING_PROVIDER so the swap is A/B, not one-way.
857
+ */
858
+ embeddingActive: optional("EMBEDDING_ACTIVE", "voyage"),
859
+ /**
860
+ * Whether THIS process runs the in-server singleton timers (recall canary,
861
+ * marketplace delivery poller, title-mint reconciliation). Default true =
862
+ * current Railway behavior. Set false on the Cloud Run server so exactly one
863
+ * owner (the worker) runs them and autoscaling never duplicates them.
864
+ */
865
+ runInProcessTimers: optional("RUN_INPROCESS_TIMERS", "true") === "true"
866
+ },
867
+ vertex: {
868
+ /**
869
+ * Vertex AI embedding backend (the GCP replacement for Voyage), used only when
870
+ * ingest/backfill/recall target the 'vertex' space (EMBEDDING_ACTIVE=vertex or an
871
+ * explicit generateEmbeddingForSpace('vertex') call). Separate from config.embedding
872
+ * so the Vertex space can be backfilled + A/B-gated while Voyage stays live.
873
+ */
874
+ project: optional("VERTEX_PROJECT", optional("GCP_PROJECT", "")),
875
+ location: optional("VERTEX_LOCATION", "us-central1"),
876
+ model: optional("VERTEX_EMBEDDING_MODEL", "gemini-embedding-001"),
877
+ /** Output dims — MRL-truncated to 1024 so vector(1024) columns + HNSW + match_* RPCs are unchanged. */
878
+ dimensions: parseInt(optional("VERTEX_EMBEDDING_DIMENSIONS", "1024"), 10),
879
+ /**
880
+ * Optional static OAuth token override (from `gcloud auth print-access-token`) for
881
+ * local dev / smoke tests. Empty in prod: Cloud Run mints a token from the attached
882
+ * service account via the metadata server (SDK-free), no static key stored.
883
+ */
884
+ accessToken: optional("VERTEX_ACCESS_TOKEN", "")
885
+ },
760
886
  openrouter: {
761
887
  apiKey: optional("OPENROUTER_API_KEY", ""),
762
888
  model: optional("OPENROUTER_MODEL", "meta-llama/llama-3.3-70b-instruct")
@@ -770,6 +896,35 @@ var init_config = __esm({
770
896
  tavily: {
771
897
  apiKey: optional("TAVILY_API_KEY", "")
772
898
  },
899
+ higgsfield: {
900
+ /** Higgsfield API key ID — first half of the V2 `Authorization: Key <id>:<secret>` pair (server-only). Empty → /api/ansem/speak returns 501. */
901
+ apiKey: optional("HIGGSFIELD_API_KEY", ""),
902
+ /** Higgsfield API key SECRET — second half of the V2 `Key <id>:<secret>` pair. */
903
+ apiSecret: optional("HIGGSFIELD_API_SECRET", ""),
904
+ /** REST base URL (Higgsfield V2 API). */
905
+ apiBase: optional("HIGGSFIELD_API_BASE", "https://platform.higgsfield.ai"),
906
+ /**
907
+ * V2 model path for the seed_audio TTS model: create is POST {apiBase}/{ttsEndpoint}.
908
+ * Verified live against the Higgsfield V2 API — bytedance/seed-audio-1.0.
909
+ */
910
+ ttsEndpoint: optional("HIGGSFIELD_TTS_ENDPOINT", "bytedance/seed-audio-1.0"),
911
+ /** Ansem voice — Higgsfield "Sterling" preset (founder's choice). */
912
+ voiceId: optional("ANSEM_VOICE_ID", "dc382508-c8bd-443c-8cb2-46e57b8d2e6f"),
913
+ voiceType: optional("ANSEM_VOICE_TYPE", "preset"),
914
+ /** Deep-voice tuning: seed_audio pitch_rate / speech_rate (integers, default 0). */
915
+ voicePitch: parseInt(optional("ANSEM_VOICE_PITCH", "-9"), 10),
916
+ voiceSpeechRate: parseInt(optional("ANSEM_VOICE_SPEECH_RATE", "-12"), 10),
917
+ /** Output audio format (seed_audio supports wav|mp3|pcm|ogg_opus). */
918
+ audioFormat: optional("ANSEM_VOICE_FORMAT", "mp3")
919
+ },
920
+ elevenlabs: {
921
+ /** ElevenLabs API key (server-only). Set → PRIMARY TTS (~1-3s, no lag); empty → falls back to Higgsfield. */
922
+ apiKey: optional("ELEVENLABS_API_KEY", ""),
923
+ /** Voice id — default "Brian" (deep, resonant, american). Swap via ELEVENLABS_VOICE_ID. */
924
+ voiceId: optional("ELEVENLABS_VOICE_ID", "nPczCjzI2devNBz1zQrb"),
925
+ /** Model — turbo for lowest latency. */
926
+ model: optional("ELEVENLABS_MODEL", "eleven_turbo_v2_5")
927
+ },
773
928
  privy: {
774
929
  appId: optional("PRIVY_APP_ID", ""),
775
930
  appSecret: optional("PRIVY_APP_SECRET", ""),
@@ -846,28 +1001,70 @@ var init_logger = __esm({
846
1001
  }
847
1002
  });
848
1003
 
1004
+ // packages/shared/src/core/migration-profile.ts
1005
+ function defaultSupabaseConnection() {
1006
+ return { url: config.supabase.url, serviceKey: config.supabase.serviceKey };
1007
+ }
1008
+ function resolveDbConnection(profile = config.migration, supabase2 = defaultSupabaseConnection()) {
1009
+ if (!DB_TARGETS.includes(profile.dbTarget)) {
1010
+ throw new Error(
1011
+ `Invalid DB_TARGET '${profile.dbTarget}' (expected 'supabase' | 'cloudsql')`
1012
+ );
1013
+ }
1014
+ if (profile.dbTarget === "cloudsql") {
1015
+ if (!profile.cloudsqlUrl || !profile.cloudsqlServiceKey) {
1016
+ throw new Error(
1017
+ "DB_TARGET=cloudsql requires CLOUDSQL_PGREST_URL and CLOUDSQL_SERVICE_KEY"
1018
+ );
1019
+ }
1020
+ return { url: profile.cloudsqlUrl, serviceKey: profile.cloudsqlServiceKey };
1021
+ }
1022
+ return { url: supabase2.url, serviceKey: supabase2.serviceKey };
1023
+ }
1024
+ function activeEmbeddingSpace(profile = config.migration) {
1025
+ if (!EMBEDDING_SPACES.includes(profile.embeddingActive)) {
1026
+ throw new Error(
1027
+ `Invalid EMBEDDING_ACTIVE '${profile.embeddingActive}' (expected 'voyage' | 'vertex')`
1028
+ );
1029
+ }
1030
+ return profile.embeddingActive;
1031
+ }
1032
+ function vectorRpcName(baseRpc, profile = config.migration) {
1033
+ return activeEmbeddingSpace(profile) === "vertex" ? `${baseRpc}_vertex` : baseRpc;
1034
+ }
1035
+ var DB_TARGETS, EMBEDDING_SPACES;
1036
+ var init_migration_profile = __esm({
1037
+ "packages/shared/src/core/migration-profile.ts"() {
1038
+ "use strict";
1039
+ init_config();
1040
+ DB_TARGETS = ["supabase", "cloudsql"];
1041
+ EMBEDDING_SPACES = ["voyage", "vertex"];
1042
+ }
1043
+ });
1044
+
849
1045
  // packages/shared/src/core/database.ts
850
1046
  var database_exports = {};
851
1047
  __export(database_exports, {
852
1048
  _setDb: () => _setDb,
853
1049
  claimForProcessing: () => claimForProcessing,
854
1050
  getDb: () => getDb,
1051
+ getSchemaDriftReport: () => getSchemaDriftReport,
855
1052
  initDatabase: () => initDatabase2,
856
1053
  isAlreadyProcessed: () => isAlreadyProcessed,
857
1054
  markProcessed: () => markProcessed
858
1055
  });
859
1056
  function getDb() {
860
1057
  if (!supabase) {
861
- supabase = (0, import_supabase_js.createClient)(config.supabase.url, config.supabase.serviceKey);
862
- log.info("Supabase client initialized");
1058
+ const conn = resolveDbConnection();
1059
+ supabase = (0, import_supabase_js.createClient)(conn.url, conn.serviceKey);
1060
+ log.info({ dbTarget: config.migration.dbTarget }, "Database client initialized");
863
1061
  }
864
1062
  return supabase;
865
1063
  }
866
1064
  function _setDb(client) {
867
1065
  supabase = client;
868
1066
  }
869
- async function initDatabase2() {
870
- const db = getDb();
1067
+ async function runBootDdl(db) {
871
1068
  try {
872
1069
  const { error } = await db.rpc("exec_sql", {
873
1070
  query: `
@@ -939,6 +1136,7 @@ async function initDatabase2() {
939
1136
  input_memory_ids BIGINT[] DEFAULT '{}',
940
1137
  output TEXT NOT NULL,
941
1138
  new_memories_created BIGINT[] DEFAULT '{}',
1139
+ owner_wallet TEXT,
942
1140
  created_at TIMESTAMPTZ DEFAULT NOW()
943
1141
  );
944
1142
 
@@ -964,13 +1162,18 @@ async function initDatabase2() {
964
1162
  is_active BOOLEAN DEFAULT TRUE,
965
1163
  metadata JSONB DEFAULT '{}',
966
1164
  owner_wallet TEXT,
967
- privy_did TEXT
1165
+ privy_did TEXT,
1166
+ email TEXT
968
1167
  );
969
1168
 
1169
+ -- Backfill: older deployments may have agent_keys without the email column.
1170
+ ALTER TABLE agent_keys ADD COLUMN IF NOT EXISTS email TEXT;
1171
+
970
1172
  CREATE INDEX IF NOT EXISTS idx_agent_keys_api_key ON agent_keys(api_key);
971
1173
  CREATE INDEX IF NOT EXISTS idx_agent_keys_owner ON agent_keys(owner_wallet);
972
1174
  CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_keys_owner_unique ON agent_keys(owner_wallet) WHERE owner_wallet IS NOT NULL;
973
1175
  CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_keys_privy_did ON agent_keys(privy_did) WHERE privy_did IS NOT NULL;
1176
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_keys_email ON agent_keys(email) WHERE email IS NOT NULL AND is_active = true;
974
1177
 
975
1178
  -- Cortex recall performance: owner_wallet scoped queries
976
1179
  CREATE INDEX IF NOT EXISTS idx_cortex_owner_recall ON memories(owner_wallet, decay_factor DESC, created_at DESC);
@@ -1018,7 +1221,7 @@ async function initDatabase2() {
1018
1221
  target_id BIGINT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
1019
1222
  link_type TEXT NOT NULL CHECK (link_type IN (
1020
1223
  'supports', 'contradicts', 'elaborates', 'causes', 'follows', 'relates', 'resolves',
1021
- 'happens_before', 'happens_after', 'concurrent_with'
1224
+ 'supersedes', 'happens_before', 'happens_after', 'concurrent_with'
1022
1225
  )),
1023
1226
  strength REAL DEFAULT 0.5,
1024
1227
  created_at TIMESTAMPTZ DEFAULT NOW(),
@@ -1053,7 +1256,11 @@ async function initDatabase2() {
1053
1256
  WHERE ml.source_id = ANY(seed_ids)
1054
1257
  AND ml.target_id != ALL(seed_ids)
1055
1258
  AND ml.strength >= min_strength
1056
- AND (filter_owner IS NULL OR m.owner_wallet = filter_owner)
1259
+ AND (
1260
+ filter_owner IS NULL
1261
+ OR (filter_owner = '__BOT_OWN__' AND m.owner_wallet IS NULL)
1262
+ OR m.owner_wallet = filter_owner
1263
+ )
1057
1264
  UNION
1058
1265
  SELECT DISTINCT ON (ml.source_id, ml.link_type)
1059
1266
  ml.source_id AS memory_id,
@@ -1065,7 +1272,11 @@ async function initDatabase2() {
1065
1272
  WHERE ml.target_id = ANY(seed_ids)
1066
1273
  AND ml.source_id != ALL(seed_ids)
1067
1274
  AND ml.strength >= min_strength
1068
- AND (filter_owner IS NULL OR m.owner_wallet = filter_owner)
1275
+ AND (
1276
+ filter_owner IS NULL
1277
+ OR (filter_owner = '__BOT_OWN__' AND m.owner_wallet IS NULL)
1278
+ OR m.owner_wallet = filter_owner
1279
+ )
1069
1280
  ORDER BY strength DESC
1070
1281
  LIMIT max_results;
1071
1282
  $$;
@@ -1093,6 +1304,10 @@ async function initDatabase2() {
1093
1304
  ALTER TABLE dream_logs ADD CONSTRAINT dream_logs_session_type_check
1094
1305
  CHECK (session_type IN ('consolidation', 'reflection', 'emergence', 'compaction', 'decay', 'contradiction_resolution'));
1095
1306
 
1307
+ -- Migration 021: per-owner attribution for dream_logs (fixes totalDreamSessions leak)
1308
+ ALTER TABLE dream_logs ADD COLUMN IF NOT EXISTS owner_wallet TEXT;
1309
+ CREATE INDEX IF NOT EXISTS idx_dream_logs_owner ON dream_logs(owner_wallet);
1310
+
1096
1311
  -- Migration: add 'resolves' + temporal link types
1097
1312
  ALTER TABLE memory_links DROP CONSTRAINT IF EXISTS memory_links_link_type_check;
1098
1313
  ALTER TABLE memory_links ADD CONSTRAINT memory_links_link_type_check
@@ -1274,6 +1489,170 @@ async function initDatabase2() {
1274
1489
  CREATE INDEX IF NOT EXISTS idx_memories_event_date ON memories(event_date)
1275
1490
  WHERE event_date IS NOT NULL;
1276
1491
 
1492
+ -- Lexical index for keyword/BM25 search (encryption \xA79). content_tokens is
1493
+ -- app-maintained; ts_summary (summary-only) is added on fresh deploys that lack it.
1494
+ ALTER TABLE memories ADD COLUMN IF NOT EXISTS content_tokens tsvector;
1495
+ CREATE INDEX IF NOT EXISTS idx_memories_content_tokens ON memories USING GIN(content_tokens);
1496
+ ALTER TABLE memories ADD COLUMN IF NOT EXISTS provider_delegated BOOLEAN DEFAULT TRUE;
1497
+ CREATE INDEX IF NOT EXISTS idx_memories_delegated ON memories(provider_delegated) WHERE encrypted = TRUE;
1498
+ ALTER TABLE memories ADD COLUMN IF NOT EXISTS summary_ciphertext TEXT;
1499
+ ALTER TABLE memories ADD COLUMN IF NOT EXISTS embedding_ciphertext TEXT;
1500
+
1501
+ -- Memory 3.0 Phase 1 (migration 044): bi-temporal validity + provenance (additive/nullable, inert until MEMORY_RECONCILE)
1502
+ ALTER TABLE memories ADD COLUMN IF NOT EXISTS valid_from TIMESTAMPTZ;
1503
+ ALTER TABLE memories ADD COLUMN IF NOT EXISTS invalid_at TIMESTAMPTZ;
1504
+ ALTER TABLE memories ADD COLUMN IF NOT EXISTS superseded_by TEXT;
1505
+ ALTER TABLE memories ADD COLUMN IF NOT EXISTS fact_key TEXT;
1506
+ ALTER TABLE memories ADD COLUMN IF NOT EXISTS extractor_version TEXT;
1507
+ ALTER TABLE memories ADD COLUMN IF NOT EXISTS extraction_confidence REAL;
1508
+ ALTER TABLE memories ADD COLUMN IF NOT EXISTS source_turn_ref JSONB;
1509
+ ALTER TABLE memories ADD COLUMN IF NOT EXISTS hash_id_v2 TEXT;
1510
+ CREATE INDEX IF NOT EXISTS idx_memories_valid ON memories(id) WHERE invalid_at IS NULL;
1511
+ CREATE INDEX IF NOT EXISTS idx_memories_fact_key ON memories(fact_key) WHERE fact_key IS NOT NULL;
1512
+ CREATE INDEX IF NOT EXISTS idx_memories_hash_id_v2 ON memories(hash_id_v2) WHERE hash_id_v2 IS NOT NULL;
1513
+
1514
+ -- Memory 3.0 Phase 1 (migration 044): the C2 durable write outbox.
1515
+ CREATE TABLE IF NOT EXISTS memory_write_jobs (
1516
+ id BIGSERIAL PRIMARY KEY,
1517
+ memory_id BIGINT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
1518
+ job_type TEXT NOT NULL CHECK (job_type IN ('enrich', 'embed', 'link', 'extract', 'reconcile', 'backfill_v2')),
1519
+ status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'running', 'done', 'failed')),
1520
+ attempts INT NOT NULL DEFAULT 0,
1521
+ next_retry_at TIMESTAMPTZ DEFAULT NOW(),
1522
+ last_error TEXT,
1523
+ created_at TIMESTAMPTZ DEFAULT NOW(),
1524
+ updated_at TIMESTAMPTZ DEFAULT NOW()
1525
+ );
1526
+ CREATE INDEX IF NOT EXISTS idx_write_jobs_claim ON memory_write_jobs(status, next_retry_at) WHERE status IN ('pending', 'failed');
1527
+ CREATE INDEX IF NOT EXISTS idx_write_jobs_memory ON memory_write_jobs(memory_id);
1528
+ CREATE INDEX IF NOT EXISTS idx_write_jobs_running ON memory_write_jobs(updated_at) WHERE status = 'running';
1529
+
1530
+ -- Memory 3.0 C2 outbox \u2014 MIRROR of migration 045 (byte-equivalent). The table above is in
1531
+ -- the boot blob, so the claim RPC + idempotency objects MUST be too: otherwise a
1532
+ -- boot-provisioned box gets the table but not the RPC and the worker silently never drains
1533
+ -- (the migration-028 class). Keep in sync with 045.
1534
+ DO $do$
1535
+ BEGIN
1536
+ ALTER TABLE memory_write_jobs DROP CONSTRAINT IF EXISTS memory_write_jobs_job_type_check;
1537
+ ALTER TABLE memory_write_jobs ADD CONSTRAINT memory_write_jobs_job_type_check
1538
+ CHECK (job_type IN ('enrich', 'embed', 'link', 'extract', 'reconcile', 'backfill_v2'));
1539
+ EXCEPTION WHEN undefined_table THEN NULL; END $do$;
1540
+
1541
+ CREATE OR REPLACE FUNCTION claim_memory_write_jobs(
1542
+ p_limit INT DEFAULT 20, p_stale_running INTERVAL DEFAULT INTERVAL '15 minutes', p_owner TEXT DEFAULT NULL
1543
+ )
1544
+ RETURNS TABLE (id BIGINT, memory_id BIGINT, job_type TEXT, attempts INT, owner_wallet TEXT)
1545
+ LANGUAGE plpgsql AS $claimfn$
1546
+ BEGIN
1547
+ RETURN QUERY
1548
+ UPDATE memory_write_jobs j
1549
+ SET status = 'running', attempts = j.attempts + 1, updated_at = NOW()
1550
+ FROM (
1551
+ SELECT jj.id FROM memory_write_jobs jj JOIN memories m ON m.id = jj.memory_id
1552
+ WHERE ((jj.status IN ('pending','failed') AND jj.next_retry_at IS NOT NULL AND jj.next_retry_at <= NOW())
1553
+ OR (jj.status = 'running' AND jj.updated_at < NOW() - p_stale_running))
1554
+ AND (p_owner IS NULL OR m.owner_wallet = p_owner OR (p_owner = '__BOT_OWN__' AND m.owner_wallet IS NULL))
1555
+ ORDER BY jj.next_retry_at ASC NULLS LAST LIMIT p_limit FOR UPDATE SKIP LOCKED
1556
+ ) claimed
1557
+ WHERE j.id = claimed.id
1558
+ RETURNING j.id, j.memory_id, j.job_type, j.attempts,
1559
+ (SELECT mm.owner_wallet FROM memories mm WHERE mm.id = j.memory_id);
1560
+ END; $claimfn$;
1561
+
1562
+ DO $do$
1563
+ BEGIN
1564
+ DELETE FROM memory_links a USING memory_links b
1565
+ WHERE a.ctid < b.ctid AND a.source_id = b.source_id AND a.target_id = b.target_id AND a.link_type = b.link_type;
1566
+ ALTER TABLE memory_links DROP CONSTRAINT IF EXISTS memory_links_unique_edge;
1567
+ ALTER TABLE memory_links ADD CONSTRAINT memory_links_unique_edge UNIQUE (source_id, target_id, link_type);
1568
+ EXCEPTION WHEN undefined_table THEN NULL; END $do$;
1569
+
1570
+ CREATE OR REPLACE FUNCTION upsert_entity_relation(
1571
+ p_src BIGINT, p_tgt BIGINT, p_type TEXT, p_evidence BIGINT DEFAULT NULL, p_strength REAL DEFAULT 0.5
1572
+ )
1573
+ RETURNS VOID LANGUAGE sql AS $uerfn$
1574
+ INSERT INTO entity_relations (source_entity_id, target_entity_id, relation_type, strength, evidence_memory_ids)
1575
+ VALUES (p_src, p_tgt, p_type, LEAST(1.0, p_strength),
1576
+ CASE WHEN p_evidence IS NULL THEN '{}'::bigint[] ELSE ARRAY[p_evidence] END)
1577
+ ON CONFLICT (source_entity_id, target_entity_id, relation_type) DO UPDATE
1578
+ SET strength = LEAST(1.0, entity_relations.strength +
1579
+ CASE WHEN p_evidence IS NULL OR p_evidence = ANY(entity_relations.evidence_memory_ids) THEN 0.0 ELSE 0.1 END),
1580
+ evidence_memory_ids = (SELECT ARRAY(SELECT DISTINCT e FROM unnest(entity_relations.evidence_memory_ids || EXCLUDED.evidence_memory_ids) AS e));
1581
+ $uerfn$;
1582
+
1583
+ -- Memory 3.0 C1 (migration 046): reconciliation SHADOW decision log. MIRROR \u2014 byte-equivalent
1584
+ -- to migration 046. Records a PROPOSED reconcile op per write without applying it, so enforce
1585
+ -- can be greenlit from a labeled sample. Deliberately NOT added to CORE_TABLES (dormant,
1586
+ -- default-off; must not trip SCHEMA DRIFT at boot on an un-migrated prod box \u2014 C2 precedent).
1587
+ CREATE TABLE IF NOT EXISTS memory_reconciliation_log (
1588
+ id BIGSERIAL PRIMARY KEY,
1589
+ memory_id BIGINT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
1590
+ owner_wallet TEXT,
1591
+ mode TEXT NOT NULL DEFAULT 'shadow' CHECK (mode IN ('shadow','enforce')),
1592
+ proposed_op TEXT NOT NULL CHECK (proposed_op IN ('add','update','noop','needs_router','skip')),
1593
+ target_memory_id BIGINT,
1594
+ max_cosine REAL,
1595
+ band TEXT CHECK (band IN ('hi','mid','lo','none')),
1596
+ router_used BOOLEAN NOT NULL DEFAULT false,
1597
+ router_model TEXT,
1598
+ fact_key TEXT,
1599
+ reason TEXT,
1600
+ label TEXT,
1601
+ labeled_at TIMESTAMPTZ,
1602
+ gate_version TEXT,
1603
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
1604
+ );
1605
+ CREATE INDEX IF NOT EXISTS idx_reconcile_log_owner_created ON memory_reconciliation_log(owner_wallet, created_at);
1606
+ CREATE INDEX IF NOT EXISTS idx_reconcile_log_op ON memory_reconciliation_log(proposed_op);
1607
+ CREATE INDEX IF NOT EXISTS idx_reconcile_log_router ON memory_reconciliation_log(mode, router_used);
1608
+
1609
+ DO $do$
1610
+ BEGIN
1611
+ IF NOT EXISTS (
1612
+ SELECT 1 FROM information_schema.columns
1613
+ WHERE table_name = 'memories' AND column_name = 'ts_summary'
1614
+ ) THEN
1615
+ ALTER TABLE memories ADD COLUMN ts_summary tsvector GENERATED ALWAYS AS (
1616
+ setweight(to_tsvector('english', COALESCE(summary, '')), 'A')
1617
+ ) STORED;
1618
+ CREATE INDEX IF NOT EXISTS idx_memories_ts_summary ON memories USING GIN(ts_summary);
1619
+ END IF;
1620
+ END $do$;
1621
+
1622
+ -- Semantic search across memory-level embeddings with metadata filtering (the always-on
1623
+ -- recall vector lane). MIRROR of migration 028 / supabase-schema.sql \u2014 8-arg filter_tags form.
1624
+ CREATE OR REPLACE FUNCTION match_memories(
1625
+ query_embedding vector(1024),
1626
+ match_threshold float DEFAULT 0.3,
1627
+ match_count int DEFAULT 10,
1628
+ filter_types text[] DEFAULT NULL,
1629
+ filter_user text DEFAULT NULL,
1630
+ min_decay float DEFAULT 0.1,
1631
+ filter_owner text DEFAULT NULL,
1632
+ filter_tags text[] DEFAULT NULL
1633
+ )
1634
+ RETURNS TABLE (id bigint, similarity float)
1635
+ LANGUAGE plpgsql AS $$
1636
+ BEGIN
1637
+ RETURN QUERY
1638
+ SELECT m.id, (1 - (m.embedding <=> query_embedding))::float AS similarity
1639
+ FROM memories m
1640
+ WHERE m.embedding IS NOT NULL
1641
+ AND m.decay_factor >= min_decay
1642
+ AND (filter_types IS NULL OR m.memory_type = ANY(filter_types))
1643
+ AND (filter_user IS NULL OR m.related_user = filter_user)
1644
+ AND (
1645
+ filter_owner IS NULL
1646
+ OR (filter_owner = '__BOT_OWN__' AND m.owner_wallet IS NULL)
1647
+ OR m.owner_wallet = filter_owner
1648
+ )
1649
+ AND (filter_tags IS NULL OR m.tags && filter_tags)
1650
+ AND (1 - (m.embedding <=> query_embedding)) > match_threshold
1651
+ ORDER BY m.embedding <=> query_embedding
1652
+ LIMIT match_count;
1653
+ END;
1654
+ $$;
1655
+
1277
1656
  -- Temporal-aware semantic search RPC (Exp 9)
1278
1657
  CREATE OR REPLACE FUNCTION match_memories_temporal(
1279
1658
  query_embedding vector(1024),
@@ -1297,7 +1676,11 @@ async function initDatabase2() {
1297
1676
  AND m.decay_factor >= min_decay
1298
1677
  AND (filter_types IS NULL OR m.memory_type = ANY(filter_types))
1299
1678
  AND (filter_user IS NULL OR m.related_user = filter_user)
1300
- AND (filter_owner IS NULL OR m.owner_wallet = filter_owner)
1679
+ AND (
1680
+ filter_owner IS NULL
1681
+ OR (filter_owner = '__BOT_OWN__' AND m.owner_wallet IS NULL)
1682
+ OR m.owner_wallet = filter_owner
1683
+ )
1301
1684
  AND (filter_tags IS NULL OR m.tags && filter_tags)
1302
1685
  AND (1 - (m.embedding <=> query_embedding)) > match_threshold
1303
1686
  AND (start_date IS NULL OR COALESCE(m.event_date, m.created_at) >= start_date)
@@ -1307,8 +1690,124 @@ async function initDatabase2() {
1307
1690
  END;
1308
1691
  $$;
1309
1692
 
1310
- -- BM25-ranked full-text search RPC (Exp 8)
1311
- -- Note: ts_summary column requires manual migration (GENERATED ALWAYS AS is not ALTER-able)
1693
+ -- Fragment-level semantic search with deduplication to parent memory. Returns the highest
1694
+ -- similarity fragment per memory (the non-skipExpansion recall lane). MIRROR of
1695
+ -- supabase-schema.sql \u2014 6-arg migration-043 form; the 4-arg (migration 009) call still
1696
+ -- resolves against it via parameter defaults, so a boot-provisioned box is never left on a
1697
+ -- stale fragment signature.
1698
+ CREATE OR REPLACE FUNCTION match_memory_fragments(
1699
+ query_embedding vector(1024),
1700
+ match_threshold float DEFAULT 0.3,
1701
+ match_count int DEFAULT 10,
1702
+ filter_owner text DEFAULT NULL,
1703
+ min_decay float DEFAULT 0.0,
1704
+ filter_types text[] DEFAULT NULL
1705
+ )
1706
+ RETURNS TABLE (memory_id bigint, max_similarity float)
1707
+ LANGUAGE plpgsql AS $$
1708
+ BEGIN
1709
+ RETURN QUERY
1710
+ SELECT f.memory_id, MAX((1 - (f.embedding <=> query_embedding))::float) AS max_similarity
1711
+ FROM memory_fragments f
1712
+ JOIN memories m ON m.id = f.memory_id
1713
+ WHERE f.embedding IS NOT NULL
1714
+ AND (1 - (f.embedding <=> query_embedding)) > match_threshold
1715
+ AND m.decay_factor >= min_decay
1716
+ AND (filter_types IS NULL OR m.memory_type = ANY(filter_types))
1717
+ AND (
1718
+ filter_owner IS NULL
1719
+ OR (filter_owner = '__BOT_OWN__' AND m.owner_wallet IS NULL)
1720
+ OR m.owner_wallet = filter_owner
1721
+ )
1722
+ GROUP BY f.memory_id
1723
+ ORDER BY max_similarity DESC
1724
+ LIMIT match_count;
1725
+ END;
1726
+ $$;
1727
+
1728
+ -- Encryption: owner key registry + per-memory wrapped DEKs (encryption \xA79, Plan 1 sync).
1729
+ CREATE TABLE IF NOT EXISTS encryption_keys (
1730
+ owner_wallet TEXT PRIMARY KEY,
1731
+ x25519_pubkey TEXT NOT NULL,
1732
+ verifier_ct TEXT NOT NULL,
1733
+ created_at TIMESTAMPTZ DEFAULT NOW(),
1734
+ updated_at TIMESTAMPTZ DEFAULT NOW()
1735
+ );
1736
+ CREATE TABLE IF NOT EXISTS memory_dek_wraps (
1737
+ memory_id BIGINT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
1738
+ recipient TEXT NOT NULL,
1739
+ wrapped_dek TEXT NOT NULL,
1740
+ wrap_pubkey TEXT NOT NULL,
1741
+ holder_wallet TEXT, -- (034) names the title_holder; NULL for owner/provider
1742
+ created_at TIMESTAMPTZ DEFAULT NOW(),
1743
+ CONSTRAINT memory_dek_wraps_recipient_chk CHECK (recipient IN ('owner', 'provider', 'title_holder')),
1744
+ CONSTRAINT memory_dek_wraps_holder_chk CHECK ((recipient = 'title_holder') = (holder_wallet IS NOT NULL))
1745
+ );
1746
+ -- (036) holder-aware uniqueness: one owner + one provider per memory (NULLS NOT DISTINCT),
1747
+ -- and one title_holder per (memory, holder) so a sale's seller + buyer wraps coexist (RT7).
1748
+ CREATE UNIQUE INDEX IF NOT EXISTS uq_dek_wraps_identity
1749
+ ON memory_dek_wraps (memory_id, recipient, holder_wallet) NULLS NOT DISTINCT;
1750
+ CREATE INDEX IF NOT EXISTS idx_dek_wraps_memory ON memory_dek_wraps(memory_id);
1751
+
1752
+ -- Populate content_tokens from a transient plaintext arg (PostgREST can't express to_tsvector inline).
1753
+ -- setweight 'B' mirrors the old combined ts_summary weighting (summary 'A' > content 'B').
1754
+ CREATE OR REPLACE FUNCTION set_memory_content_tokens(p_memory_id bigint, p_text text)
1755
+ RETURNS void LANGUAGE sql AS $fn$
1756
+ UPDATE memories
1757
+ SET content_tokens = CASE
1758
+ WHEN p_text IS NULL OR p_text = '' THEN NULL
1759
+ ELSE setweight(to_tsvector('english', p_text), 'B')
1760
+ END
1761
+ WHERE id = p_memory_id;
1762
+ $fn$;
1763
+
1764
+ -- Atomic revoke: clear plaintext + drop the provider wrap in one transaction (encryption \xA77).
1765
+ CREATE OR REPLACE FUNCTION revoke_memory(p_memory_id bigint, p_summary_ct text, p_embedding_ct text)
1766
+ RETURNS void LANGUAGE plpgsql AS $rev$
1767
+ BEGIN
1768
+ UPDATE memories SET
1769
+ summary = '',
1770
+ summary_ciphertext = p_summary_ct,
1771
+ embedding = NULL,
1772
+ embedding_ciphertext = NULLIF(p_embedding_ct, ''),
1773
+ content_tokens = NULL,
1774
+ provider_delegated = false
1775
+ WHERE id = p_memory_id;
1776
+ DELETE FROM memory_dek_wraps WHERE memory_id = p_memory_id AND recipient = 'provider';
1777
+ -- Fragment parity (migration 043): fragments hold plaintext + live embeddings.
1778
+ DELETE FROM memory_fragments WHERE memory_id = p_memory_id;
1779
+ END;
1780
+ $rev$;
1781
+
1782
+ -- Atomic re-delegate (encryption \xA77) \u2014 inverse of revoke_memory. Restores plaintext
1783
+ -- summary/embedding, rebuilds content_tokens via the canonical builder, clears ciphertext
1784
+ -- cols, sets provider_delegated=true, (re-)inserts the provider wrap.
1785
+ CREATE OR REPLACE FUNCTION redelegate_memory(
1786
+ p_memory_id bigint,
1787
+ p_summary text,
1788
+ p_embedding text,
1789
+ p_content text,
1790
+ p_wrapped_dek text,
1791
+ p_wrap_pubkey text
1792
+ )
1793
+ RETURNS void LANGUAGE plpgsql AS $redel$
1794
+ BEGIN
1795
+ UPDATE memories SET
1796
+ summary = p_summary,
1797
+ summary_ciphertext = NULL,
1798
+ embedding = NULLIF(p_embedding, '')::vector,
1799
+ embedding_ciphertext = NULL,
1800
+ provider_delegated = true
1801
+ WHERE id = p_memory_id;
1802
+ PERFORM set_memory_content_tokens(p_memory_id, p_content);
1803
+ INSERT INTO memory_dek_wraps (memory_id, recipient, wrapped_dek, wrap_pubkey)
1804
+ VALUES (p_memory_id, 'provider', p_wrapped_dek, p_wrap_pubkey)
1805
+ ON CONFLICT (memory_id, recipient) DO UPDATE
1806
+ SET wrapped_dek = EXCLUDED.wrapped_dek, wrap_pubkey = EXCLUDED.wrap_pubkey;
1807
+ END;
1808
+ $redel$;
1809
+
1810
+ -- BM25-ranked full-text search RPC (Exp 8) \u2014 dual-column (ts_summary + content_tokens)
1312
1811
  CREATE OR REPLACE FUNCTION bm25_search_memories(
1313
1812
  search_query text,
1314
1813
  match_count int DEFAULT 20,
@@ -1327,11 +1826,17 @@ async function initDatabase2() {
1327
1826
  RETURN;
1328
1827
  END IF;
1329
1828
  RETURN QUERY
1330
- SELECT m.id, ts_rank_cd(m.ts_summary, tsquery_val, 32)::float AS rank
1829
+ SELECT m.id,
1830
+ (ts_rank_cd(COALESCE(m.ts_summary, ''::tsvector), tsquery_val, 32)
1831
+ + ts_rank_cd(COALESCE(m.content_tokens, ''::tsvector), tsquery_val, 32))::float AS rank
1331
1832
  FROM memories m
1332
- WHERE m.ts_summary @@ tsquery_val
1833
+ WHERE (m.ts_summary @@ tsquery_val OR m.content_tokens @@ tsquery_val)
1333
1834
  AND m.decay_factor >= min_decay
1334
- AND (filter_owner IS NULL OR m.owner_wallet = filter_owner)
1835
+ AND (
1836
+ filter_owner IS NULL
1837
+ OR (filter_owner = '__BOT_OWN__' AND m.owner_wallet IS NULL)
1838
+ OR m.owner_wallet = filter_owner
1839
+ )
1335
1840
  AND (filter_types IS NULL OR m.memory_type = ANY(filter_types))
1336
1841
  AND (filter_tags IS NULL OR m.tags && filter_tags)
1337
1842
  ORDER BY rank DESC
@@ -1360,9 +1865,31 @@ async function initDatabase2() {
1360
1865
  tokens_prompt INTEGER,
1361
1866
  tokens_completion INTEGER,
1362
1867
  memory_ids INTEGER[],
1868
+ frontier_tokens INTEGER,
1869
+ memories_used INTEGER,
1870
+ tokens_saved INTEGER,
1363
1871
  created_at TIMESTAMPTZ DEFAULT NOW()
1364
1872
  );
1365
1873
  CREATE INDEX IF NOT EXISTS idx_chat_msg_conv ON chat_messages(conversation_id, created_at);
1874
+ CREATE INDEX IF NOT EXISTS idx_chat_msg_saved ON chat_messages(created_at) WHERE tokens_saved IS NOT NULL;
1875
+ -- Proof counter totals. "today" is UTC-based (resets 00:00 UTC). Full-table aggregate:
1876
+ -- callers MUST use a server-side TTL cache (see proof.routes.ts). Mirror of migration 026.
1877
+ CREATE OR REPLACE FUNCTION proof_tokens_saved_totals()
1878
+ RETURNS TABLE(
1879
+ measured_saved bigint,
1880
+ measured_today bigint,
1881
+ measured_frontier bigint,
1882
+ historical_prompt_sum bigint,
1883
+ n bigint
1884
+ ) LANGUAGE sql STABLE AS $$
1885
+ SELECT
1886
+ COALESCE(SUM(tokens_saved) FILTER (WHERE tokens_saved IS NOT NULL), 0)::bigint,
1887
+ COALESCE(SUM(tokens_saved) FILTER (WHERE tokens_saved IS NOT NULL AND created_at >= date_trunc('day', now())), 0)::bigint,
1888
+ COALESCE(SUM(frontier_tokens) FILTER (WHERE tokens_saved IS NOT NULL), 0)::bigint,
1889
+ COALESCE(SUM(tokens_prompt) FILTER (WHERE tokens_saved IS NULL), 0)::bigint,
1890
+ COUNT(*) FILTER (WHERE tokens_saved IS NOT NULL)::bigint
1891
+ FROM chat_messages;
1892
+ $$;
1366
1893
 
1367
1894
  -- Chat billing: balances, top-ups, and per-message usage
1368
1895
  CREATE TABLE IF NOT EXISTS chat_balances (
@@ -1404,6 +1931,63 @@ async function initDatabase2() {
1404
1931
  created_at TIMESTAMPTZ DEFAULT NOW()
1405
1932
  );
1406
1933
  CREATE INDEX IF NOT EXISTS idx_chat_usage_wallet ON chat_usage(wallet_address, created_at DESC);
1934
+
1935
+ -- Wiki pack installations: which packs (Workspace, Compliance, Sales)
1936
+ -- each wallet has installed. Drives the topic rail in /wiki and
1937
+ -- the auto-categorisation rules applied to incoming memories.
1938
+ CREATE TABLE IF NOT EXISTS wiki_pack_installations (
1939
+ id BIGSERIAL PRIMARY KEY,
1940
+ owner_wallet TEXT NOT NULL,
1941
+ pack_id TEXT NOT NULL,
1942
+ installed_at TIMESTAMPTZ DEFAULT NOW(),
1943
+ UNIQUE (owner_wallet, pack_id)
1944
+ );
1945
+ CREATE INDEX IF NOT EXISTS idx_wiki_pack_installations_owner
1946
+ ON wiki_pack_installations(owner_wallet);
1947
+
1948
+ -- Migration 019: corrected access-boost RPC (no importance mutation on read).
1949
+ -- Re-asserted on every boot so the read->rank->read feedback loop can't return.
1950
+ -- importance_boosts is kept for call-site signature compatibility and ignored.
1951
+ -- Drop the stale single-arg overload (migration 003) so the two-arg version is
1952
+ -- unambiguous and the lockdown below can't be bypassed via an unrevoked overload.
1953
+ DROP FUNCTION IF EXISTS batch_boost_memory_access(bigint[]);
1954
+ CREATE OR REPLACE FUNCTION batch_boost_memory_access(
1955
+ memory_ids BIGINT[],
1956
+ importance_boosts DOUBLE PRECISION[] DEFAULT NULL
1957
+ ) RETURNS void LANGUAGE plpgsql AS $fn$
1958
+ BEGIN
1959
+ UPDATE memories
1960
+ SET access_count = access_count + 1,
1961
+ last_accessed = NOW(),
1962
+ decay_factor = LEAST(1.0, decay_factor + 0.1)
1963
+ WHERE id = ANY(memory_ids);
1964
+ END;
1965
+ $fn$;
1966
+
1967
+ -- Migration 020: lock down dangerous SECURITY DEFINER RPCs to service_role only.
1968
+ -- exec_sql/boost RPCs must never be callable by anon/authenticated (the publishable
1969
+ -- key ships in client apps). Wrapped so a not-yet-created function never aborts boot.
1970
+ DO $do$ BEGIN
1971
+ REVOKE EXECUTE ON FUNCTION exec_sql(text) FROM PUBLIC, anon, authenticated;
1972
+ GRANT EXECUTE ON FUNCTION exec_sql(text) TO service_role;
1973
+ EXCEPTION WHEN undefined_function THEN NULL; END $do$;
1974
+
1975
+ DO $do$ BEGIN
1976
+ REVOKE EXECUTE ON FUNCTION batch_boost_memory_access(bigint[], double precision[]) FROM PUBLIC, anon, authenticated;
1977
+ GRANT EXECUTE ON FUNCTION batch_boost_memory_access(bigint[], double precision[]) TO service_role;
1978
+ EXCEPTION WHEN undefined_function THEN NULL; END $do$;
1979
+
1980
+ DO $do$ BEGIN
1981
+ REVOKE EXECUTE ON FUNCTION boost_memory_importance(bigint, double precision, double precision) FROM PUBLIC, anon, authenticated;
1982
+ GRANT EXECUTE ON FUNCTION boost_memory_importance(bigint, double precision, double precision) TO service_role;
1983
+ EXCEPTION WHEN undefined_function THEN NULL; END $do$;
1984
+
1985
+ -- Migration 022 (guard portion): reject empty content on NEW writes in every env.
1986
+ -- NOT VALID enforces on new INSERT/UPDATE without scanning existing rows; the
1987
+ -- one-time cleanup of legacy blank rows + VALIDATE lives in the manual 022 file.
1988
+ DO $do$ BEGIN
1989
+ ALTER TABLE memories ADD CONSTRAINT memories_content_nonempty CHECK (length(btrim(content)) > 0) NOT VALID;
1990
+ EXCEPTION WHEN duplicate_object THEN NULL; END $do$;
1407
1991
  `
1408
1992
  });
1409
1993
  if (error) {
@@ -1412,7 +1996,75 @@ async function initDatabase2() {
1412
1996
  } catch {
1413
1997
  log.warn("rpc exec_sql not available. Create tables via Supabase SQL editor.");
1414
1998
  }
1415
- log.info("Database initialized");
1999
+ }
2000
+ function getSchemaDriftReport() {
2001
+ return lastSchemaReport;
2002
+ }
2003
+ async function verifyCoreSchema(db) {
2004
+ const missingTables = [];
2005
+ const probeErrors = [];
2006
+ for (const table of CORE_TABLES) {
2007
+ try {
2008
+ const { error } = await db.from(table).select("id", { head: true, count: "exact" }).limit(1);
2009
+ if (error) {
2010
+ if (MISSING_RE.test(error.message ?? "")) missingTables.push(table);
2011
+ else probeErrors.push(`${table}: ${error.message}`);
2012
+ }
2013
+ } catch (err) {
2014
+ probeErrors.push(`${table}: ${err instanceof Error ? err.message : String(err)}`);
2015
+ }
2016
+ }
2017
+ const brokenRpcs = [];
2018
+ for (const probe of CORE_RPC_PROBES) {
2019
+ try {
2020
+ const { error } = await db.rpc(probe.name, probe.args);
2021
+ if (error && MISSING_RE.test(error.message ?? "")) brokenRpcs.push(probe.name);
2022
+ } catch (err) {
2023
+ probeErrors.push(`${probe.name}: ${err instanceof Error ? err.message : String(err)}`);
2024
+ }
2025
+ }
2026
+ return { missingTables, brokenRpcs, probeErrors };
2027
+ }
2028
+ async function initDatabase2(dbOverride) {
2029
+ const db = dbOverride ?? getDb();
2030
+ const mode = process.env.INIT_DB_MODE ?? "auto";
2031
+ if (mode === "replay") {
2032
+ await runBootDdl(db);
2033
+ lastSchemaReport = { at: (/* @__PURE__ */ new Date()).toISOString(), status: "replayed", missingTables: [], brokenRpcs: [] };
2034
+ log.info("Database initialized (legacy replay mode)");
2035
+ return;
2036
+ }
2037
+ let { missingTables, brokenRpcs, probeErrors } = await verifyCoreSchema(db);
2038
+ if (mode === "auto" && missingTables.includes("memories")) {
2039
+ log.info("Fresh database detected (no memories table) \u2014 running boot DDL");
2040
+ await runBootDdl(db);
2041
+ ({ missingTables, brokenRpcs, probeErrors } = await verifyCoreSchema(db));
2042
+ const healthy2 = missingTables.length === 0 && brokenRpcs.length === 0;
2043
+ lastSchemaReport = {
2044
+ at: (/* @__PURE__ */ new Date()).toISOString(),
2045
+ status: healthy2 ? "fresh-bootstrapped" : "drift",
2046
+ missingTables,
2047
+ brokenRpcs
2048
+ };
2049
+ if (healthy2) log.info("Database bootstrapped from boot DDL");
2050
+ else log.error({ missingTables, brokenRpcs }, "SCHEMA DRIFT after fresh bootstrap \u2014 check exec_sql availability");
2051
+ return;
2052
+ }
2053
+ if (probeErrors.length > 0 && missingTables.length === 0 && brokenRpcs.length === 0) {
2054
+ lastSchemaReport = { at: (/* @__PURE__ */ new Date()).toISOString(), status: "unknown", missingTables, brokenRpcs };
2055
+ log.warn({ probeErrors: probeErrors.slice(0, 3) }, "Schema verification inconclusive (probe errors); skipping");
2056
+ return;
2057
+ }
2058
+ const healthy = missingTables.length === 0 && brokenRpcs.length === 0;
2059
+ lastSchemaReport = { at: (/* @__PURE__ */ new Date()).toISOString(), status: healthy ? "ok" : "drift", missingTables, brokenRpcs };
2060
+ if (healthy) {
2061
+ log.info("Database schema verified (boot blob frozen; migrations are the single schema writer)");
2062
+ } else {
2063
+ log.error(
2064
+ { missingTables, brokenRpcs },
2065
+ "SCHEMA DRIFT \u2014 core objects missing on an established database; apply the corresponding migration by hand (the boot blob no longer auto-repairs)"
2066
+ );
2067
+ }
1416
2068
  }
1417
2069
  async function isAlreadyProcessed(tweetId) {
1418
2070
  const db = getDb();
@@ -1445,27 +2097,81 @@ async function markProcessed(tweetId, feature, responseTweetId, extra) {
1445
2097
  processed_at: (/* @__PURE__ */ new Date()).toISOString()
1446
2098
  });
1447
2099
  }
1448
- var import_supabase_js, log, supabase;
2100
+ var import_supabase_js, log, supabase, CORE_TABLES, CORE_RPC_PROBES, lastSchemaReport, MISSING_RE;
1449
2101
  var init_database = __esm({
1450
2102
  "packages/shared/src/core/database.ts"() {
1451
2103
  "use strict";
1452
2104
  import_supabase_js = require("@supabase/supabase-js");
1453
2105
  init_config();
1454
2106
  init_logger();
2107
+ init_migration_profile();
1455
2108
  log = createChildLogger("database");
2109
+ CORE_TABLES = [
2110
+ "memories",
2111
+ "memory_fragments",
2112
+ "memory_links",
2113
+ "agent_keys",
2114
+ "dream_logs",
2115
+ "rate_limits",
2116
+ "chat_conversations",
2117
+ "chat_messages"
2118
+ ];
2119
+ CORE_RPC_PROBES = [
2120
+ { name: "get_linked_memories", args: { seed_ids: [], min_strength: 0.1, max_results: 1, filter_owner: null } },
2121
+ { name: "bm25_search_memories", args: { search_query: "", match_count: 1, min_decay: 0.1, filter_owner: null } },
2122
+ // Vector recall lanes (memory.ts recall). match_memories is the always-on memory-level lane;
2123
+ // match_memory_fragments is the fragment lane (non-skipExpansion path). Both are silently absent
2124
+ // from a stale boot blob, so probe them so the gap surfaces as drift instead of a dead lane.
2125
+ // Vector-safe args (null embedding + match_count 0) make each a no-op read. filter_tags pins the
2126
+ // migration-028 8-arg match_memories overload recall calls unconditionally (catches a pre-028 box
2127
+ // where recall would break); the fragment probe stays the 4/6-arg common subset (min_decay +
2128
+ // filter_types are opt-in via MEMORY_FRAGMENT_FILTERS) so it never false-drifts a pre-043 box
2129
+ // still serving the migration-009 4-arg form.
2130
+ { name: "match_memories", args: { query_embedding: null, match_count: 0, filter_owner: null, filter_tags: null } },
2131
+ { name: "match_memory_fragments", args: { query_embedding: null, match_count: 0, filter_owner: null } },
2132
+ // Memory 3.0 C2: probe the outbox claim RPC so a table-without-RPC state (the §6 hazard) is
2133
+ // caught at boot as drift rather than silently never draining.
2134
+ { name: "claim_memory_write_jobs", args: { p_limit: 0 } }
2135
+ ];
2136
+ lastSchemaReport = null;
2137
+ MISSING_RE = /does not exist|could not find|schema cache/i;
1456
2138
  }
1457
2139
  });
1458
2140
 
1459
- // packages/shared/src/utils/format.ts
1460
- function timeAgo(dateStr) {
1461
- const ms = Date.now() - new Date(dateStr).getTime();
1462
- const hours = Math.floor(ms / (1e3 * 60 * 60));
1463
- if (hours < 1) return "just now";
1464
- if (hours < 24) return `${hours}h ago`;
1465
- const days = Math.floor(hours / 24);
1466
- if (days < 7) return `${days}d ago`;
1467
- return `${Math.floor(days / 7)}w ago`;
2141
+ // packages/shared/src/core/memory-grounding.ts
2142
+ function byMemoryDateAsc(a, b) {
2143
+ const ta = a.created_at ? Date.parse(a.created_at) : NaN;
2144
+ const tb = b.created_at ? Date.parse(b.created_at) : NaN;
2145
+ if (Number.isNaN(ta) && Number.isNaN(tb)) return 0;
2146
+ if (Number.isNaN(ta)) return 1;
2147
+ if (Number.isNaN(tb)) return -1;
2148
+ return ta - tb;
2149
+ }
2150
+ function renderGroundedLine(m, suffix = "") {
2151
+ const date = m.created_at ? new Date(m.created_at).toISOString().slice(0, 10) : "";
2152
+ const stamp = date ? `[${date}] ` : "";
2153
+ const summary = (m.summary || "").trim();
2154
+ const content = (m.content || "").trim();
2155
+ let body;
2156
+ if (!content || content === summary) {
2157
+ body = summary || content;
2158
+ } else if (content.length <= GROUNDED_CONTENT_MAX) {
2159
+ body = summary ? `${summary} \u2014 ${content}` : content;
2160
+ } else {
2161
+ const slice = content.slice(0, GROUNDED_CONTENT_MAX);
2162
+ body = summary ? `${summary} \u2014 ${slice}\u2026` : `${slice}\u2026`;
2163
+ }
2164
+ return `- ${stamp}${body}${suffix}`;
1468
2165
  }
2166
+ var GROUNDED_CONTENT_MAX;
2167
+ var init_memory_grounding = __esm({
2168
+ "packages/shared/src/core/memory-grounding.ts"() {
2169
+ "use strict";
2170
+ GROUNDED_CONTENT_MAX = 600;
2171
+ }
2172
+ });
2173
+
2174
+ // packages/shared/src/utils/format.ts
1469
2175
  function clamp(val, min, max) {
1470
2176
  return Math.max(min, Math.min(max, val));
1471
2177
  }
@@ -1483,7 +2189,7 @@ var init_text = __esm({
1483
2189
  });
1484
2190
 
1485
2191
  // packages/shared/src/utils/constants.ts
1486
- var MEMO_PROGRAM_ID, BOT_X_HANDLE, MEMORY_MIN_DECAY, MEMORY_MAX_CONTENT_LENGTH, MEMORY_MAX_SUMMARY_LENGTH, DECAY_RATES, RECENCY_DECAY_BASE, RETRIEVAL_WEIGHT_RECENCY, RETRIEVAL_WEIGHT_RELEVANCE, RETRIEVAL_WEIGHT_IMPORTANCE, RETRIEVAL_WEIGHT_VECTOR, KNOWLEDGE_TYPE_BOOST, VECTOR_MATCH_THRESHOLD, BOND_TYPE_WEIGHTS, RETRIEVAL_WEIGHT_GRAPH, RETRIEVAL_WEIGHT_COOCCURRENCE, LINK_SIMILARITY_THRESHOLD, MAX_AUTO_LINKS, LINK_CO_RETRIEVAL_BOOST, INTERNAL_MEMORY_SOURCES, INTERNAL_IMPORTANCE_BOOST, REFLECTION_MIN_INTERVAL_MS, WHALE_SELL_COOLDOWN_MS, MEMO_MAX_LENGTH;
2192
+ var MEMO_PROGRAM_ID, BOT_X_HANDLE, MEMORY_MIN_DECAY, MEMORY_MAX_CONTENT_LENGTH, MEMORY_MAX_SUMMARY_LENGTH, DECAY_RATES, RECENCY_DECAY_BASE, RETRIEVAL_WEIGHT_RECENCY, RETRIEVAL_WEIGHT_RELEVANCE, RETRIEVAL_WEIGHT_IMPORTANCE, RETRIEVAL_WEIGHT_VECTOR, RETRIEVAL_WEIGHT_BM25, KNOWLEDGE_TYPE_BOOST, VECTOR_MATCH_THRESHOLD, BOND_TYPE_WEIGHTS, RETRIEVAL_WEIGHT_GRAPH, RETRIEVAL_WEIGHT_COOCCURRENCE, LINK_SIMILARITY_THRESHOLD, MAX_AUTO_LINKS, LINK_CO_RETRIEVAL_BOOST, INTERNAL_MEMORY_SOURCES, EMBEDDING_FRAGMENT_MAX_LENGTH, REFLECTION_MIN_INTERVAL_MS, WHALE_SELL_COOLDOWN_MS, MEMO_MAX_LENGTH;
1487
2193
  var init_constants = __esm({
1488
2194
  "packages/shared/src/utils/constants.ts"() {
1489
2195
  "use strict";
@@ -1505,10 +2211,11 @@ var init_constants = __esm({
1505
2211
  // Journal entries persist like knowledge
1506
2212
  };
1507
2213
  RECENCY_DECAY_BASE = 0.995;
1508
- RETRIEVAL_WEIGHT_RECENCY = 1;
1509
- RETRIEVAL_WEIGHT_RELEVANCE = 2;
1510
- RETRIEVAL_WEIGHT_IMPORTANCE = 2;
1511
- RETRIEVAL_WEIGHT_VECTOR = 4;
2214
+ RETRIEVAL_WEIGHT_RECENCY = Number(process.env.RETRIEVAL_WEIGHT_RECENCY ?? "1.0");
2215
+ RETRIEVAL_WEIGHT_RELEVANCE = Number(process.env.RETRIEVAL_WEIGHT_RELEVANCE ?? "2.0");
2216
+ RETRIEVAL_WEIGHT_IMPORTANCE = Number(process.env.RETRIEVAL_WEIGHT_IMPORTANCE ?? "2.0");
2217
+ RETRIEVAL_WEIGHT_VECTOR = Number(process.env.RETRIEVAL_WEIGHT_VECTOR ?? "4.0");
2218
+ RETRIEVAL_WEIGHT_BM25 = Number(process.env.RETRIEVAL_WEIGHT_BM25 ?? "0.15");
1512
2219
  KNOWLEDGE_TYPE_BOOST = {
1513
2220
  semantic: 0.15,
1514
2221
  // Distilled knowledge gets meaningful boost
@@ -1525,6 +2232,8 @@ var init_constants = __esm({
1525
2232
  BOND_TYPE_WEIGHTS = {
1526
2233
  causes: 1,
1527
2234
  supports: 0.9,
2235
+ supersedes: 0.85,
2236
+ // decisive replacement: new fact overrides an older one (Memory 3.0 C1)
1528
2237
  concurrent_with: 0.8,
1529
2238
  resolves: 0.8,
1530
2239
  happens_before: 0.7,
@@ -1549,7 +2258,7 @@ var init_constants = __esm({
1549
2258
  "dream-cycle",
1550
2259
  "meditation"
1551
2260
  ]);
1552
- INTERNAL_IMPORTANCE_BOOST = 5e-3;
2261
+ EMBEDDING_FRAGMENT_MAX_LENGTH = 2e3;
1553
2262
  REFLECTION_MIN_INTERVAL_MS = 30 * 60 * 1e3;
1554
2263
  WHALE_SELL_COOLDOWN_MS = 30 * 60 * 1e3;
1555
2264
  MEMO_MAX_LENGTH = 100;
@@ -1586,6 +2295,17 @@ var init_guardrails = __esm({
1586
2295
  }
1587
2296
  });
1588
2297
 
2298
+ // packages/shared/src/core/model-capabilities.ts
2299
+ function modelRejectsSamplingParams(model) {
2300
+ if (!model) return false;
2301
+ return /opus[-_.]?4[-_.]?[789]\b|opus[-_.]?[5-9]\b/i.test(model);
2302
+ }
2303
+ var init_model_capabilities = __esm({
2304
+ "packages/shared/src/core/model-capabilities.ts"() {
2305
+ "use strict";
2306
+ }
2307
+ });
2308
+
1589
2309
  // packages/shared/src/core/openrouter-client.ts
1590
2310
  function getModelForFunction(fn) {
1591
2311
  return COGNITIVE_MODEL_MAP[fn] || OPENROUTER_MODELS["llama-70b"];
@@ -1652,7 +2372,8 @@ async function generateOpenRouterResponse(opts) {
1652
2372
  model,
1653
2373
  messages,
1654
2374
  max_tokens: maxTokens,
1655
- temperature: opts.temperature ?? 0.7
2375
+ // Opus 4.7+ via OpenRouter rejects temperature/top_p strip for those models.
2376
+ ...modelRejectsSamplingParams(model) ? {} : { temperature: opts.temperature ?? 0.7 }
1656
2377
  })
1657
2378
  });
1658
2379
  if (!response.ok) {
@@ -1683,15 +2404,18 @@ var init_openrouter_client = __esm({
1683
2404
  "packages/shared/src/core/openrouter-client.ts"() {
1684
2405
  "use strict";
1685
2406
  init_logger();
2407
+ init_model_capabilities();
1686
2408
  log3 = createChildLogger("openrouter");
1687
2409
  OPENROUTER_API_URL = "https://openrouter.ai/api/v1";
1688
2410
  OPENROUTER_MODELS = {
1689
2411
  // Frontier (Anthropic)
2412
+ "claude-opus-4.7": "anthropic/claude-opus-4.7",
1690
2413
  "claude-opus-4.6": "anthropic/claude-opus-4.6",
1691
2414
  "claude-sonnet-4.6": "anthropic/claude-sonnet-4.6",
1692
2415
  "claude-opus-4.5": "anthropic/claude-opus-4.5",
1693
2416
  "claude-sonnet-4.5": "anthropic/claude-sonnet-4.5",
1694
2417
  // Frontier (Other providers)
2418
+ "gpt-5.5": "openai/gpt-5.5",
1695
2419
  "gpt-5.4": "openai/gpt-5.4",
1696
2420
  "grok-4.1": "x-ai/grok-4.1-fast",
1697
2421
  "gemini-3-pro": "google/gemini-3-pro-preview",
@@ -1755,10 +2479,11 @@ function getModel() {
1755
2479
  }
1756
2480
  async function generateImportanceScore(description) {
1757
2481
  const importancePrompt = process.env.CLUDE_IMPORTANCE_PROMPT || "You rate the importance of events for an AI agent. Respond with ONLY a single integer from 1 to 10. 1 = purely mundane. 5 = moderately important. 10 = extremely significant.";
2482
+ const scoreModel = getModel();
1758
2483
  const response = await getClient().messages.create({
1759
- model: getModel(),
2484
+ model: scoreModel,
1760
2485
  max_tokens: 10,
1761
- temperature: 0,
2486
+ ...modelRejectsSamplingParams(scoreModel) ? {} : { temperature: 0 },
1762
2487
  system: importancePrompt,
1763
2488
  messages: [{
1764
2489
  role: "user",
@@ -1779,6 +2504,7 @@ var init_claude_client = __esm({
1779
2504
  init_guardrails();
1780
2505
  init_openrouter_client();
1781
2506
  init_constants();
2507
+ init_model_capabilities();
1782
2508
  log4 = createChildLogger("claude-client");
1783
2509
  anthropic = null;
1784
2510
  try {
@@ -1852,8 +2578,8 @@ function deriveRegistryPDA(authority) {
1852
2578
  );
1853
2579
  }
1854
2580
  function anchorDiscriminator(name) {
1855
- const { createHash: createHash3 } = require("crypto");
1856
- const hash = createHash3("sha256").update(`global:${name}`).digest();
2581
+ const { createHash: createHash4 } = require("crypto");
2582
+ const hash = createHash4("sha256").update(`global:${name}`).digest();
1857
2583
  return hash.subarray(0, 8);
1858
2584
  }
1859
2585
  function memoryTypeToU8(type) {
@@ -1925,6 +2651,123 @@ var init_solana_client = __esm({
1925
2651
  }
1926
2652
  });
1927
2653
 
2654
+ // packages/tokenization/src/content-hash.ts
2655
+ function stableStringify(value) {
2656
+ if (value === null) return "null";
2657
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
2658
+ return JSON.stringify(value);
2659
+ }
2660
+ if (Array.isArray(value)) {
2661
+ return "[" + value.map(stableStringify).join(",") + "]";
2662
+ }
2663
+ if (typeof value === "object") {
2664
+ const obj = value;
2665
+ const keys = Object.keys(obj).filter((k) => obj[k] !== void 0).sort();
2666
+ const pairs = keys.map((k) => JSON.stringify(k) + ":" + stableStringify(obj[k]));
2667
+ return "{" + pairs.join(",") + "}";
2668
+ }
2669
+ throw new Error(`stableStringify: unsupported value of type ${typeof value}`);
2670
+ }
2671
+ function normaliseString(s) {
2672
+ return s.normalize("NFC").trim();
2673
+ }
2674
+ function normaliseNullable(s) {
2675
+ return s === null ? null : normaliseString(s);
2676
+ }
2677
+ function normaliseTags(tags) {
2678
+ const trimmed = tags.map(normaliseString).filter((t) => t.length > 0);
2679
+ return Array.from(new Set(trimmed)).sort();
2680
+ }
2681
+ function canonicaliseMemory(input) {
2682
+ const normalised = {
2683
+ algorithm: HASH_ALGORITHM,
2684
+ content: normaliseString(input.content),
2685
+ created_at: input.created_at,
2686
+ memory_type: input.memory_type,
2687
+ owner_wallet: normaliseNullable(input.owner_wallet),
2688
+ related_user: normaliseNullable(input.related_user),
2689
+ related_wallet: normaliseNullable(input.related_wallet),
2690
+ source: normaliseNullable(input.source),
2691
+ tags: normaliseTags(input.tags)
2692
+ };
2693
+ return stableStringify(normalised);
2694
+ }
2695
+ function memoryContentHash(input) {
2696
+ return (0, import_node_crypto.createHash)("sha256").update(canonicaliseMemory(input), "utf8").digest("hex");
2697
+ }
2698
+ var import_node_crypto, HASH_ALGORITHM;
2699
+ var init_content_hash = __esm({
2700
+ "packages/tokenization/src/content-hash.ts"() {
2701
+ "use strict";
2702
+ import_node_crypto = require("node:crypto");
2703
+ HASH_ALGORITHM = "memory-hash-v1";
2704
+ }
2705
+ });
2706
+
2707
+ // packages/tokenization/src/pack-merkle.ts
2708
+ var LEAF_PREFIX, INNER_PREFIX;
2709
+ var init_pack_merkle = __esm({
2710
+ "packages/tokenization/src/pack-merkle.ts"() {
2711
+ "use strict";
2712
+ LEAF_PREFIX = Buffer.from([0]);
2713
+ INNER_PREFIX = Buffer.from([1]);
2714
+ }
2715
+ });
2716
+
2717
+ // packages/tokenization/src/mint-client.ts
2718
+ var init_mint_client = __esm({
2719
+ "packages/tokenization/src/mint-client.ts"() {
2720
+ "use strict";
2721
+ }
2722
+ });
2723
+
2724
+ // packages/tokenization/src/tokenize-memory.ts
2725
+ var init_tokenize_memory = __esm({
2726
+ "packages/tokenization/src/tokenize-memory.ts"() {
2727
+ "use strict";
2728
+ init_content_hash();
2729
+ }
2730
+ });
2731
+
2732
+ // packages/tokenization/src/tokenize-batch.ts
2733
+ var init_tokenize_batch = __esm({
2734
+ "packages/tokenization/src/tokenize-batch.ts"() {
2735
+ "use strict";
2736
+ init_content_hash();
2737
+ init_pack_merkle();
2738
+ }
2739
+ });
2740
+
2741
+ // packages/tokenization/src/tokenize-pack.ts
2742
+ var init_tokenize_pack = __esm({
2743
+ "packages/tokenization/src/tokenize-pack.ts"() {
2744
+ "use strict";
2745
+ init_pack_merkle();
2746
+ }
2747
+ });
2748
+
2749
+ // packages/tokenization/src/verify.ts
2750
+ var init_verify = __esm({
2751
+ "packages/tokenization/src/verify.ts"() {
2752
+ "use strict";
2753
+ init_pack_merkle();
2754
+ }
2755
+ });
2756
+
2757
+ // packages/tokenization/src/index.ts
2758
+ var init_src = __esm({
2759
+ "packages/tokenization/src/index.ts"() {
2760
+ "use strict";
2761
+ init_content_hash();
2762
+ init_pack_merkle();
2763
+ init_mint_client();
2764
+ init_tokenize_memory();
2765
+ init_tokenize_batch();
2766
+ init_tokenize_pack();
2767
+ init_verify();
2768
+ }
2769
+ });
2770
+
1928
2771
  // packages/shared/src/core/embeddings.ts
1929
2772
  function getCachedEmbedding(text) {
1930
2773
  const key = text.slice(0, 500).toLowerCase().trim();
@@ -2063,7 +2906,73 @@ async function generateEmbeddings(texts) {
2063
2906
  return texts.map(() => null);
2064
2907
  }
2065
2908
  }
2066
- var log6, PROVIDERS, _enabled, _overrideConfig, EMBEDDING_CACHE_MAX, EMBEDDING_CACHE_TTL_MS, _embeddingCache;
2909
+ async function _fetchMetadataToken() {
2910
+ const res = await fetch(METADATA_TOKEN_URL, { headers: { "Metadata-Flavor": "Google" } });
2911
+ if (!res.ok) throw new Error(`Vertex metadata token fetch failed: ${res.status}`);
2912
+ const j = await res.json();
2913
+ return { token: j.access_token, expiresInSec: j.expires_in };
2914
+ }
2915
+ async function getVertexAccessToken() {
2916
+ const override = config.vertex.accessToken;
2917
+ if (override) return override;
2918
+ const now = Date.now();
2919
+ if (_vertexToken && _vertexToken.expiresAtMs > now + 6e4) return _vertexToken.token;
2920
+ const { token, expiresInSec } = await _fetchMetadataToken();
2921
+ _vertexToken = { token, expiresAtMs: now + expiresInSec * 1e3 };
2922
+ return token;
2923
+ }
2924
+ function vertexPredictUrl(model) {
2925
+ const { project, location } = config.vertex;
2926
+ return `https://${location}-aiplatform.googleapis.com/v1/projects/${project}/locations/${location}/publishers/google/models/${model}:predict`;
2927
+ }
2928
+ function isVertexConfigured() {
2929
+ return !!config.vertex.project;
2930
+ }
2931
+ async function generateVertexEmbeddings(texts, dimensions) {
2932
+ if (texts.length === 0) return [];
2933
+ const { project, model, dimensions: defaultDims } = config.vertex;
2934
+ if (!project) {
2935
+ log6.warn("Vertex embeddings requested but VERTEX_PROJECT is unset");
2936
+ return texts.map(() => null);
2937
+ }
2938
+ const outputDimensionality = dimensions ?? defaultDims;
2939
+ const startMs = Date.now();
2940
+ try {
2941
+ const token = await getVertexAccessToken();
2942
+ const res = await fetch(vertexPredictUrl(model), {
2943
+ method: "POST",
2944
+ headers: {
2945
+ "Content-Type": "application/json",
2946
+ "Authorization": `Bearer ${token}`
2947
+ },
2948
+ body: JSON.stringify({
2949
+ instances: texts.map((t) => ({ content: t.slice(0, 8e3) })),
2950
+ parameters: { outputDimensionality }
2951
+ })
2952
+ });
2953
+ if (!res.ok) {
2954
+ const errText = await res.text();
2955
+ log6.error({ status: res.status, body: errText.slice(0, 200), provider: "vertex" }, "Vertex embedding API error");
2956
+ return texts.map(() => null);
2957
+ }
2958
+ const data = await res.json();
2959
+ const preds = data.predictions ?? [];
2960
+ log6.debug({ provider: "vertex", model, count: texts.length, elapsed: Date.now() - startMs }, "Vertex embeddings generated");
2961
+ return texts.map((_, i) => preds[i]?.embeddings?.values ?? null);
2962
+ } catch (err) {
2963
+ log6.error({ err, provider: "vertex" }, "Vertex embedding generation failed");
2964
+ return texts.map(() => null);
2965
+ }
2966
+ }
2967
+ async function generateVertexEmbedding(text, dimensions) {
2968
+ const [vec] = await generateVertexEmbeddings([text], dimensions);
2969
+ return vec ?? null;
2970
+ }
2971
+ async function generateQueryEmbeddingForSpace(space, text) {
2972
+ if (space === "vertex") return generateVertexEmbedding(text);
2973
+ return generateQueryEmbedding(text);
2974
+ }
2975
+ var log6, PROVIDERS, _enabled, _overrideConfig, EMBEDDING_CACHE_MAX, EMBEDDING_CACHE_TTL_MS, _embeddingCache, _vertexToken, METADATA_TOKEN_URL;
2067
2976
  var init_embeddings = __esm({
2068
2977
  "packages/shared/src/core/embeddings.ts"() {
2069
2978
  "use strict";
@@ -2096,6 +3005,223 @@ var init_embeddings = __esm({
2096
3005
  EMBEDDING_CACHE_MAX = 200;
2097
3006
  EMBEDDING_CACHE_TTL_MS = 30 * 60 * 1e3;
2098
3007
  _embeddingCache = /* @__PURE__ */ new Map();
3008
+ _vertexToken = null;
3009
+ METADATA_TOKEN_URL = "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token";
3010
+ }
3011
+ });
3012
+
3013
+ // packages/shared/src/wiki-packs.ts
3014
+ function getPackManifest(id) {
3015
+ return ALL_PACK_MANIFESTS.find((p) => p.id === id);
3016
+ }
3017
+ function matchesKeyword(text, phrase) {
3018
+ if (!phrase) return false;
3019
+ const escaped = phrase.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3020
+ const re = new RegExp(`(?<![\\w-])${escaped}(?![\\w-])`, "i");
3021
+ return re.test(text);
3022
+ }
3023
+ function autoCategorizeTags(opts) {
3024
+ const haystack = [
3025
+ opts.content || "",
3026
+ opts.summary || "",
3027
+ ...opts.existingTags || []
3028
+ ].join(" ");
3029
+ const matched = new Set(opts.existingTags || []);
3030
+ const ids = /* @__PURE__ */ new Set([DEFAULT_PACK_ID, ...opts.installedPackIds]);
3031
+ for (const packId of ids) {
3032
+ const pack = getPackManifest(packId);
3033
+ if (!pack) continue;
3034
+ for (const rule of pack.rules) {
3035
+ if (matched.has(rule.topicId)) continue;
3036
+ const hasMatch = rule.keywords.some((kw) => matchesKeyword(haystack, kw));
3037
+ if (!hasMatch) continue;
3038
+ const isVetoed = (rule.excludeKeywords || []).some((kw) => matchesKeyword(haystack, kw));
3039
+ if (isVetoed) continue;
3040
+ matched.add(rule.topicId);
3041
+ }
3042
+ }
3043
+ return Array.from(matched);
3044
+ }
3045
+ function topicEmbedSources(installedPackIds) {
3046
+ const out = [];
3047
+ const seen = /* @__PURE__ */ new Set();
3048
+ const ids = /* @__PURE__ */ new Set([DEFAULT_PACK_ID, ...installedPackIds]);
3049
+ for (const packId of ids) {
3050
+ const pack = getPackManifest(packId);
3051
+ if (!pack) continue;
3052
+ for (const t of pack.topics) {
3053
+ if (seen.has(t.id)) continue;
3054
+ seen.add(t.id);
3055
+ const sectionTitles = (t.sectionTemplates || []).map((s) => s.title).join(". ");
3056
+ const text = [t.name, t.summary, sectionTitles].filter(Boolean).join(" \u2014 ");
3057
+ out.push({ topicId: t.id, packId, text });
3058
+ }
3059
+ }
3060
+ return out;
3061
+ }
3062
+ function cosineSimilarity2(a, b) {
3063
+ if (a.length !== b.length || a.length === 0) return 0;
3064
+ let dot = 0;
3065
+ let normA = 0;
3066
+ let normB = 0;
3067
+ for (let i = 0; i < a.length; i++) {
3068
+ dot += a[i] * b[i];
3069
+ normA += a[i] * a[i];
3070
+ normB += b[i] * b[i];
3071
+ }
3072
+ const denom = Math.sqrt(normA) * Math.sqrt(normB);
3073
+ return denom === 0 ? 0 : dot / denom;
3074
+ }
3075
+ function semanticTagMatches(memoryEmbedding, topicEmbeddings, threshold = EMBEDDING_TAG_THRESHOLD) {
3076
+ const matched = [];
3077
+ for (const { topicId, embedding } of topicEmbeddings) {
3078
+ const sim = cosineSimilarity2(memoryEmbedding, embedding);
3079
+ if (sim >= threshold) matched.push(topicId);
3080
+ }
3081
+ return matched;
3082
+ }
3083
+ var WORKSPACE_PACK, COMPLIANCE_PACK, SALES_PACK, ALL_PACK_MANIFESTS, DEFAULT_PACK_ID, EMBEDDING_TAG_THRESHOLD;
3084
+ var init_wiki_packs = __esm({
3085
+ "packages/shared/src/wiki-packs.ts"() {
3086
+ "use strict";
3087
+ WORKSPACE_PACK = {
3088
+ id: "workspace",
3089
+ name: "Workspace Essentials",
3090
+ vendor: "Clude",
3091
+ vertical: "General",
3092
+ version: "1.0.0",
3093
+ installedByDefault: true,
3094
+ description: "The default knowledge-worker pack. Roadmaps, decisions, customer research, hiring, team process \u2014 everything you'd want a colleague to be able to skim.",
3095
+ topics: [
3096
+ { id: "q3-roadmap", name: "Q3 Roadmap", cluster: "architecture", color: "#2244FF", summary: "What ships this quarter, what got cut, and the calls behind those decisions." },
3097
+ { id: "auth-migration", name: "Auth Migration", cluster: "architecture", color: "#2244FF", summary: "Moving from session cookies to JWT \u2014 incidents, fixes, and the rollback plan." },
3098
+ { id: "customer-research", name: "Customer Research", cluster: "research", color: "#F59E0B", summary: "Patterns surfaced across customer calls." },
3099
+ { id: "pricing-model", name: "Pricing Model", cluster: "product", color: "#10B981", summary: "Per-token vs per-seat \u2014 the active disagreement." },
3100
+ { id: "demo-day-prep", name: "Demo Day Prep", cluster: "product", color: "#10B981", summary: "What works in the live demo, what breaks." },
3101
+ { id: "hiring", name: "Hiring Pipeline", cluster: "product", color: "#10B981", summary: "Candidates in flight and interview-signal patterns." },
3102
+ { id: "team-process", name: "Team Process", cluster: "self", color: "#8B5CF6", summary: "What's working in how the team operates and what isn't." },
3103
+ { id: "design-decisions", name: "Design Decisions", cluster: "research", color: "#F59E0B", summary: "API shapes, naming choices, and the reasons behind them." }
3104
+ ],
3105
+ rules: [
3106
+ { topicId: "q3-roadmap", keywords: ["roadmap", "q3", "quarter", "sprint plan", "priority"] },
3107
+ { topicId: "auth-migration", keywords: ["auth", "jwt", "session cookie", "oauth", "sso", "token migration"] },
3108
+ { topicId: "customer-research", keywords: ["customer call", "interview", "feedback", "user research", "persona"] },
3109
+ { topicId: "pricing-model", keywords: ["pricing", "per-token", "per-seat", "subscription", "billing tier"] },
3110
+ { topicId: "demo-day-prep", keywords: ["demo", "rehearsal", "investor", "pitch"] },
3111
+ {
3112
+ topicId: "hiring",
3113
+ keywords: ["candidate", "interview", "hire", "phone screen", "onsite", "offer"],
3114
+ // Suppress when "hire" is in the wrong context — got fired, rejected an offer,
3115
+ // hire-purchase, etc. The classic substring-match false positive.
3116
+ excludeKeywords: ["fired me", "i was fired", "hire purchase", "rejected my offer", "rejected the offer"]
3117
+ },
3118
+ { topicId: "team-process", keywords: ["standup", "retro", "sprint", "1:1", "process", "team"] },
3119
+ { topicId: "design-decisions", keywords: ["api design", "schema", "naming", "rfc", "design doc"] }
3120
+ ]
3121
+ };
3122
+ COMPLIANCE_PACK = {
3123
+ id: "compliance",
3124
+ name: "Clude Compliance",
3125
+ vendor: "Clude",
3126
+ vertical: "Compliance",
3127
+ version: "1.0.0",
3128
+ description: "Auto-organises every compliance-relevant decision your agents make into an audit-ready wiki. Audit logs, evidence collection, regulator asks, policy decisions \u2014 each with a cryptographic receipt anchored to Solana.",
3129
+ topics: [
3130
+ {
3131
+ id: "audit-logs",
3132
+ name: "Audit Logs",
3133
+ cluster: "architecture",
3134
+ color: "#0EA5E9",
3135
+ summary: "Every flagged agent action with attribution, timestamp, and a Solana-anchored hash a regulator can verify without trusting us.",
3136
+ sectionTemplates: [
3137
+ { id: "recent-events", title: "Recent flagged events", kind: "overview" },
3138
+ { id: "anomalies", title: "Anomalies & escalations", kind: "concern" },
3139
+ { id: "retention", title: "Retention policy", kind: "decision" }
3140
+ ]
3141
+ },
3142
+ {
3143
+ id: "evidence",
3144
+ name: "Evidence Collection",
3145
+ cluster: "product",
3146
+ color: "#06B6D4",
3147
+ summary: "Documentation gathered for SOC2, HIPAA, and ISO 27001 \u2014 what we have, what we still need, and what auditors have already accepted.",
3148
+ sectionTemplates: [
3149
+ { id: "have", title: "What we have", kind: "highlight" },
3150
+ { id: "gaps", title: "Gaps to fill", kind: "action" },
3151
+ { id: "accepted", title: "What auditors accepted", kind: "decision" }
3152
+ ]
3153
+ },
3154
+ {
3155
+ id: "regulator-asks",
3156
+ name: "Regulator Asks",
3157
+ cluster: "research",
3158
+ color: "#F59E0B",
3159
+ summary: "Specific requests from regulators \u2014 owner, deadline, response status. Nothing falls through the cracks.",
3160
+ sectionTemplates: [
3161
+ { id: "open", title: "Open requests", kind: "action" },
3162
+ { id: "responded", title: "Responded", kind: "decision" },
3163
+ { id: "patterns", title: "Recurring patterns", kind: "highlight" }
3164
+ ]
3165
+ },
3166
+ {
3167
+ id: "policy-decisions",
3168
+ name: "Policy Decisions",
3169
+ cluster: "self",
3170
+ color: "#8B5CF6",
3171
+ summary: "Calls made about what the agents are allowed to do \u2014 data handling, escalation triggers, redaction rules \u2014 each anchored on-chain.",
3172
+ sectionTemplates: [
3173
+ { id: "standing", title: "Standing policy", kind: "decision" },
3174
+ { id: "changes", title: "Recent changes", kind: "overview" },
3175
+ { id: "open", title: "Open questions", kind: "question" }
3176
+ ]
3177
+ },
3178
+ {
3179
+ id: "soc2-status",
3180
+ name: "SOC2 Status",
3181
+ cluster: "product",
3182
+ color: "#10B981",
3183
+ summary: "Where we are in the SOC2 audit cycle, what controls are passing, what still needs evidence.",
3184
+ sectionTemplates: [
3185
+ { id: "controls", title: "Control status", kind: "overview" },
3186
+ { id: "next", title: "Up next", kind: "action" }
3187
+ ]
3188
+ }
3189
+ ],
3190
+ rules: [
3191
+ { topicId: "audit-logs", keywords: ["audit", "audit log", "trail", "attribution", "flagged"] },
3192
+ { topicId: "evidence", keywords: ["evidence", "soc2", "hipaa", "iso 27001", "compliance evidence", "control"] },
3193
+ { topicId: "regulator-asks", keywords: ["regulator", "subpoena", "compliance request", "data request"] },
3194
+ { topicId: "policy-decisions", keywords: ["policy", "redaction", "pii", "escalation rule", "data handling"] },
3195
+ { topicId: "soc2-status", keywords: ["soc2", "audit cycle", "control test"] }
3196
+ ]
3197
+ };
3198
+ SALES_PACK = {
3199
+ id: "sales",
3200
+ name: "Sales Intelligence",
3201
+ vendor: "Clude",
3202
+ vertical: "Sales",
3203
+ version: "1.0.0",
3204
+ description: "Auto-organises pipeline conversations, deal blockers, objection patterns, and post-call follow-ups. Built for AEs who hate CRM data entry.",
3205
+ topics: [
3206
+ { id: "pipeline", name: "Pipeline", cluster: "product", color: "#10B981", summary: "Active deals, stages, blockers." },
3207
+ { id: "objections", name: "Objections", cluster: "research", color: "#F59E0B", summary: "Patterns across discovery calls." },
3208
+ { id: "follow-ups", name: "Follow-ups", cluster: "product", color: "#10B981", summary: "What you committed to send and to whom." },
3209
+ { id: "champions", name: "Champions", cluster: "self", color: "#8B5CF6", summary: "Who is championing your product internally at each account." }
3210
+ ],
3211
+ rules: [
3212
+ { topicId: "pipeline", keywords: ["deal", "opportunity", "stage", "close date", "mql", "sql"] },
3213
+ { topicId: "objections", keywords: ["objection", "concern raised", "pushback", "competitor mentioned"] },
3214
+ { topicId: "follow-ups", keywords: ["follow up", "send", "next step", "committed to"] },
3215
+ { topicId: "champions", keywords: ["champion", "sponsor", "advocate", "introduced me to"] }
3216
+ ]
3217
+ };
3218
+ ALL_PACK_MANIFESTS = [
3219
+ WORKSPACE_PACK,
3220
+ COMPLIANCE_PACK,
3221
+ SALES_PACK
3222
+ ];
3223
+ DEFAULT_PACK_ID = "workspace";
3224
+ EMBEDDING_TAG_THRESHOLD = 0.78;
2099
3225
  }
2100
3226
  });
2101
3227
 
@@ -2184,23 +3310,506 @@ var init_bm25_search = __esm({
2184
3310
  }
2185
3311
  });
2186
3312
 
2187
- // packages/shared/src/core/encryption.ts
2188
- function isEncryptionEnabled() {
2189
- return encryptionKey !== null;
2190
- }
2191
- function getEncryptionPubkey() {
2192
- return encryptionPubkey;
3313
+ // packages/brain/src/memory/content-tokens.ts
3314
+ async function setContentTokens(db, memoryId, plaintext) {
3315
+ try {
3316
+ const { error } = await db.rpc("set_memory_content_tokens", {
3317
+ p_memory_id: memoryId,
3318
+ p_text: plaintext
3319
+ });
3320
+ if (error) {
3321
+ log8.warn({ memoryId, error: error.message }, "set_memory_content_tokens failed (keyword recall degraded)");
3322
+ }
3323
+ } catch (err) {
3324
+ log8.warn({ memoryId, err }, "set_memory_content_tokens threw (keyword recall degraded)");
3325
+ }
2193
3326
  }
2194
- function encryptContent(plaintext) {
2195
- if (!encryptionKey) {
2196
- throw new Error("Encryption not configured. Call configureEncryption() first.");
3327
+ var log8;
3328
+ var init_content_tokens = __esm({
3329
+ "packages/brain/src/memory/content-tokens.ts"() {
3330
+ "use strict";
3331
+ init_logger();
3332
+ log8 = createChildLogger("content-tokens");
2197
3333
  }
3334
+ });
3335
+
3336
+ // packages/brain/src/memory/reconcile-router.ts
3337
+ function shortReason(x) {
3338
+ return typeof x === "string" ? x.slice(0, 200) : "";
3339
+ }
3340
+ function parseRouterReply(raw, candidateIds, model) {
3341
+ const fail = (reason) => ({ op: "add", targetId: null, reason, model });
3342
+ let obj;
3343
+ try {
3344
+ obj = JSON.parse(raw);
3345
+ } catch {
3346
+ const m = raw.match(/\{[\s\S]*\}/);
3347
+ if (!m) return fail("router_parse_error");
3348
+ try {
3349
+ obj = JSON.parse(m[0]);
3350
+ } catch {
3351
+ return fail("router_parse_error");
3352
+ }
3353
+ }
3354
+ if (obj === null || typeof obj !== "object" || Array.isArray(obj)) return fail("router_invalid_shape");
3355
+ const rec = obj;
3356
+ const op = rec.op;
3357
+ if (op !== "add" && op !== "update" && op !== "noop") return fail("router_invalid_op");
3358
+ if (op === "add") return { op: "add", targetId: null, reason: shortReason(rec.reason), model };
3359
+ const rawTarget = rec.targetId ?? rec.target_id;
3360
+ const targetId = typeof rawTarget === "number" ? rawTarget : Number(rawTarget);
3361
+ if (!Number.isInteger(targetId) || !candidateIds.has(targetId)) return fail("router_invalid_target");
3362
+ return { op, targetId, reason: shortReason(rec.reason), model };
3363
+ }
3364
+ function buildPayload(newFact, candidates) {
3365
+ const lines = candidates.map(
3366
+ (c) => `[id ${c.id}] date: ${c.eventDate ?? "unknown"} \u2014 ${c.summary}`
3367
+ );
3368
+ return `NEW fact:
3369
+ date: ${newFact.eventDate ?? "unknown"} \u2014 ${newFact.summary}
3370
+
3371
+ Candidate facts:
3372
+ ${lines.join("\n")}`;
3373
+ }
3374
+ function isRouterConfigured() {
3375
+ return config.memory.reconcileRouter && !!config.memory.reconcileModel && isOpenRouterEnabled();
3376
+ }
3377
+ function utcDay() {
3378
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3379
+ }
3380
+ function rollDay() {
3381
+ const d = utcDay();
3382
+ if (d !== budgetDay) {
3383
+ budgetDay = d;
3384
+ budgetByOwner.clear();
3385
+ }
3386
+ }
3387
+ function budgetKey(owner) {
3388
+ return owner ?? "__BOT_OWN__";
3389
+ }
3390
+ function routerBudgetAvailable(owner) {
3391
+ rollDay();
3392
+ return (budgetByOwner.get(budgetKey(owner)) ?? 0) < config.memory.reconcileBudget;
3393
+ }
3394
+ function noteRouterCall(owner) {
3395
+ rollDay();
3396
+ const k = budgetKey(owner);
3397
+ budgetByOwner.set(k, (budgetByOwner.get(k) ?? 0) + 1);
3398
+ }
3399
+ async function routeReconcile(newFact, candidates) {
3400
+ const model = config.memory.reconcileModel;
3401
+ const candidateIds = new Set(candidates.map((c) => c.id));
3402
+ try {
3403
+ const raw = await generateOpenRouterResponse({
3404
+ systemPrompt: ROUTER_SYSTEM,
3405
+ messages: [{ role: "user", content: buildPayload(newFact, candidates) }],
3406
+ model,
3407
+ // explicit — never cognitiveFunction (which overrides model with the fast llama slot)
3408
+ maxTokens: 150,
3409
+ temperature: 0
3410
+ });
3411
+ return parseRouterReply(raw, candidateIds, model);
3412
+ } catch (err) {
3413
+ log9.warn({ err: err?.message }, "reconcile router call failed \u2014 defaulting to add");
3414
+ return { op: "add", targetId: null, reason: "router_error", model };
3415
+ }
3416
+ }
3417
+ var log9, ROUTER_SYSTEM, budgetDay, budgetByOwner;
3418
+ var init_reconcile_router = __esm({
3419
+ "packages/brain/src/memory/reconcile-router.ts"() {
3420
+ "use strict";
3421
+ init_config();
3422
+ init_logger();
3423
+ init_openrouter_client();
3424
+ log9 = createChildLogger("reconcile-router");
3425
+ ROUTER_SYSTEM = 'You are a memory reconciliation classifier. Given a NEW fact and a list of EXISTING candidate facts that all belong to the SAME user, decide the relationship of the NEW fact to the candidates. Respond with ONLY a single JSON object, no prose, no markdown.\nFormat: {"op":"add"|"update"|"noop","targetId":<candidate id or null>,"reason":"<short>"}\n- "noop": NEW states the SAME thing as candidate <targetId> (a duplicate). Set targetId.\n- "update": NEW corrects or SUPERSEDES candidate <targetId> \u2014 same underlying fact, changed value or a newer date. Set targetId to that candidate id.\n- "add": NEW is distinct from every candidate. targetId must be null.\nWhen unsure, choose "add". NEVER invent a targetId that is not in the candidate list.';
3426
+ budgetDay = "";
3427
+ budgetByOwner = /* @__PURE__ */ new Map();
3428
+ }
3429
+ });
3430
+
3431
+ // packages/brain/src/memory/reconcile-shadow.ts
3432
+ function logReconcileDegradeOnce(err, memoryId) {
3433
+ if (reconcileDegradeLogged) return;
3434
+ reconcileDegradeLogged = true;
3435
+ const e = err;
3436
+ log10.warn(
3437
+ { code: e?.code, message: e?.message, memoryId },
3438
+ "Reconcile shadow degraded (migration 046 unapplied?) \u2014 skipping; write unaffected"
3439
+ );
3440
+ }
3441
+ function gateVersion() {
3442
+ const { reconcileFloor: f, reconcileLo: lo, reconcileHi: hi } = config.memory;
3443
+ return `c1-shadow-v1:floor=${f}:lo=${lo}:hi=${hi}`;
3444
+ }
3445
+ async function insertShadowRow(row) {
3446
+ try {
3447
+ const { error } = await getDb().from("memory_reconciliation_log").insert(row);
3448
+ if (error) logReconcileDegradeOnce(error, row.memory_id);
3449
+ } catch (err) {
3450
+ logReconcileDegradeOnce(err, row.memory_id);
3451
+ }
3452
+ }
3453
+ async function runReconcileShadow(memoryId, scope) {
3454
+ if (process.env.BENCH_MODE === "true") return;
3455
+ const ownerWalletForRow = scope === SCOPE_BOT_OWN ? null : scope;
3456
+ const gv = gateVersion();
3457
+ const db = getDb();
3458
+ const skip = (reason) => insertShadowRow({
3459
+ memory_id: memoryId,
3460
+ owner_wallet: ownerWalletForRow,
3461
+ mode: "shadow",
3462
+ proposed_op: "skip",
3463
+ target_memory_id: null,
3464
+ max_cosine: null,
3465
+ band: "none",
3466
+ router_used: false,
3467
+ fact_key: null,
3468
+ reason,
3469
+ gate_version: gv
3470
+ });
3471
+ const add = (reason, maxCosine2, band2) => insertShadowRow({
3472
+ memory_id: memoryId,
3473
+ owner_wallet: ownerWalletForRow,
3474
+ mode: "shadow",
3475
+ proposed_op: "add",
3476
+ target_memory_id: null,
3477
+ max_cosine: maxCosine2,
3478
+ band: band2,
3479
+ router_used: false,
3480
+ fact_key: null,
3481
+ reason,
3482
+ gate_version: gv
3483
+ });
3484
+ let embedding;
3485
+ let newSummary = "";
3486
+ let newEventDate = null;
3487
+ try {
3488
+ const { data, error } = await db.from("memories").select("embedding, summary, event_date, created_at").eq("id", memoryId).maybeSingle();
3489
+ if (error) {
3490
+ logReconcileDegradeOnce(error, memoryId);
3491
+ return;
3492
+ }
3493
+ const row = data;
3494
+ embedding = row?.embedding ?? null;
3495
+ newSummary = row?.summary ?? "";
3496
+ newEventDate = row?.event_date ?? row?.created_at ?? null;
3497
+ } catch (err) {
3498
+ logReconcileDegradeOnce(err, memoryId);
3499
+ return;
3500
+ }
3501
+ if (!embedding) {
3502
+ await skip("no_embedding");
3503
+ return;
3504
+ }
3505
+ const queryEmbedding = typeof embedding === "string" ? embedding : JSON.stringify(embedding);
3506
+ let candidates;
3507
+ try {
3508
+ const { data, error } = await db.rpc("match_memories_temporal", {
3509
+ query_embedding: queryEmbedding,
3510
+ match_threshold: config.memory.reconcileFloor,
3511
+ match_count: K_CANDIDATES,
3512
+ start_date: null,
3513
+ end_date: null,
3514
+ filter_types: null,
3515
+ filter_user: null,
3516
+ min_decay: 0,
3517
+ filter_owner: scope,
3518
+ filter_tags: null
3519
+ });
3520
+ if (error) {
3521
+ logReconcileDegradeOnce(error, memoryId);
3522
+ return;
3523
+ }
3524
+ candidates = data ?? [];
3525
+ } catch (err) {
3526
+ logReconcileDegradeOnce(err, memoryId);
3527
+ return;
3528
+ }
3529
+ candidates = candidates.filter((c) => c.id !== memoryId);
3530
+ if (candidates.length === 0) {
3531
+ await add("no_candidate", null, "none");
3532
+ return;
3533
+ }
3534
+ const ids = candidates.map((c) => c.id);
3535
+ let validIds;
3536
+ const hydratedById = /* @__PURE__ */ new Map();
3537
+ try {
3538
+ const base = db.from("memories").select("id, owner_wallet, invalid_at, summary, event_date, created_at").in("id", ids);
3539
+ const scoped = scope === SCOPE_BOT_OWN ? base.is("owner_wallet", null) : base.eq("owner_wallet", scope);
3540
+ const { data, error } = await scoped;
3541
+ if (error) {
3542
+ logReconcileDegradeOnce(error, memoryId);
3543
+ return;
3544
+ }
3545
+ const rows = data ?? [];
3546
+ const valid = rows.filter((r) => r.invalid_at == null).filter((r) => scope === SCOPE_BOT_OWN ? r.owner_wallet == null : r.owner_wallet === scope);
3547
+ validIds = new Set(valid.map((r) => r.id));
3548
+ for (const r of valid) hydratedById.set(r.id, { summary: r.summary ?? "", eventDate: r.event_date ?? r.created_at ?? null });
3549
+ } catch (err) {
3550
+ logReconcileDegradeOnce(err, memoryId);
3551
+ return;
3552
+ }
3553
+ const surviving = candidates.filter((c) => validIds.has(c.id));
3554
+ if (surviving.length === 0) {
3555
+ await add("no_valid_candidate", null, "none");
3556
+ return;
3557
+ }
3558
+ const maxCosine = surviving[0].similarity;
3559
+ const { reconcileLo: LO, reconcileHi: HI } = config.memory;
3560
+ if (maxCosine < LO) {
3561
+ await add("below_lo", maxCosine, "lo");
3562
+ return;
3563
+ }
3564
+ const band = maxCosine >= HI ? "hi" : "mid";
3565
+ const top = surviving[0];
3566
+ if (isRouterConfigured() && routerBudgetAvailable(ownerWalletForRow)) {
3567
+ noteRouterCall(ownerWalletForRow);
3568
+ const routerCandidates = surviving.map((c) => ({
3569
+ id: c.id,
3570
+ summary: hydratedById.get(c.id)?.summary ?? "",
3571
+ eventDate: hydratedById.get(c.id)?.eventDate ?? null
3572
+ }));
3573
+ const decision = await routeReconcile({ summary: newSummary, eventDate: newEventDate }, routerCandidates);
3574
+ await insertShadowRow({
3575
+ memory_id: memoryId,
3576
+ owner_wallet: ownerWalletForRow,
3577
+ mode: "shadow",
3578
+ proposed_op: decision.op,
3579
+ target_memory_id: decision.op === "add" ? null : decision.targetId,
3580
+ max_cosine: maxCosine,
3581
+ band,
3582
+ router_used: true,
3583
+ router_model: decision.model,
3584
+ fact_key: null,
3585
+ reason: decision.reason || "router",
3586
+ gate_version: gv
3587
+ });
3588
+ return;
3589
+ }
3590
+ await insertShadowRow({
3591
+ memory_id: memoryId,
3592
+ owner_wallet: ownerWalletForRow,
3593
+ mode: "shadow",
3594
+ proposed_op: "needs_router",
3595
+ target_memory_id: top.id,
3596
+ max_cosine: maxCosine,
3597
+ band,
3598
+ router_used: false,
3599
+ fact_key: null,
3600
+ reason: "similar_prior_fact",
3601
+ gate_version: gv
3602
+ });
3603
+ }
3604
+ var log10, SCOPE_BOT_OWN, K_CANDIDATES, reconcileDegradeLogged;
3605
+ var init_reconcile_shadow = __esm({
3606
+ "packages/brain/src/memory/reconcile-shadow.ts"() {
3607
+ "use strict";
3608
+ init_database();
3609
+ init_config();
3610
+ init_logger();
3611
+ init_reconcile_router();
3612
+ log10 = createChildLogger("reconcile-shadow");
3613
+ SCOPE_BOT_OWN = "__BOT_OWN__";
3614
+ K_CANDIDATES = 6;
3615
+ reconcileDegradeLogged = false;
3616
+ }
3617
+ });
3618
+
3619
+ // packages/shared/src/core/owner-key-constants.ts
3620
+ var HKDF_SALT, HKDF_INFO;
3621
+ var init_owner_key_constants = __esm({
3622
+ "packages/shared/src/core/owner-key-constants.ts"() {
3623
+ "use strict";
3624
+ HKDF_SALT = "clude-cortex-v1";
3625
+ HKDF_INFO = "memory-encryption-x25519-v2";
3626
+ }
3627
+ });
3628
+
3629
+ // packages/shared/src/core/memory-envelope.ts
3630
+ function generateDek() {
3631
+ return import_tweetnacl2.default.randomBytes(import_tweetnacl2.default.secretbox.keyLength);
3632
+ }
3633
+ function encryptField(plaintext, dek) {
2198
3634
  const nonce = import_tweetnacl2.default.randomBytes(NONCE_LENGTH);
3635
+ const message = new TextEncoder().encode(plaintext);
3636
+ const ct = import_tweetnacl2.default.secretbox(message, nonce, dek);
3637
+ const combined = new Uint8Array(NONCE_LENGTH + ct.length);
3638
+ combined.set(nonce, 0);
3639
+ combined.set(ct, NONCE_LENGTH);
3640
+ return Buffer.from(combined).toString("base64");
3641
+ }
3642
+ function decryptField(encrypted, dek) {
3643
+ try {
3644
+ const combined = Buffer.from(encrypted, "base64");
3645
+ if (combined.length < NONCE_LENGTH + 1) return null;
3646
+ const nonce = combined.subarray(0, NONCE_LENGTH);
3647
+ const ct = combined.subarray(NONCE_LENGTH);
3648
+ const pt = import_tweetnacl2.default.secretbox.open(ct, nonce, dek);
3649
+ if (!pt) return null;
3650
+ return new TextDecoder().decode(pt);
3651
+ } catch {
3652
+ return null;
3653
+ }
3654
+ }
3655
+ function wrapDek(dek, recipientPubkey) {
3656
+ const ephemeral = import_tweetnacl2.default.box.keyPair();
3657
+ const nonce = import_tweetnacl2.default.randomBytes(BOX_NONCE_LENGTH);
3658
+ const boxed = import_tweetnacl2.default.box(dek, nonce, recipientPubkey, ephemeral.secretKey);
3659
+ const combined = new Uint8Array(BOX_NONCE_LENGTH + boxed.length);
3660
+ combined.set(nonce, 0);
3661
+ combined.set(boxed, BOX_NONCE_LENGTH);
3662
+ return {
3663
+ wrapped: Buffer.from(combined).toString("base64"),
3664
+ wrapPubkey: Buffer.from(ephemeral.publicKey).toString("base64")
3665
+ };
3666
+ }
3667
+ function unwrapDek(wrapped, wrapPubkey, recipientSecretKey) {
3668
+ try {
3669
+ const combined = Buffer.from(wrapped, "base64");
3670
+ if (combined.length < BOX_NONCE_LENGTH + 1) return null;
3671
+ const nonce = combined.subarray(0, BOX_NONCE_LENGTH);
3672
+ const boxed = combined.subarray(BOX_NONCE_LENGTH);
3673
+ const ephemeralPub = Buffer.from(wrapPubkey, "base64");
3674
+ if (ephemeralPub.length !== import_tweetnacl2.default.box.publicKeyLength) return null;
3675
+ const out = import_tweetnacl2.default.box.open(boxed, nonce, ephemeralPub, recipientSecretKey);
3676
+ return out ?? null;
3677
+ } catch {
3678
+ return null;
3679
+ }
3680
+ }
3681
+ async function deriveOwnerEncryptionKeypair(signature) {
3682
+ const seed = await hkdfAsync("sha256", signature, HKDF_SALT, HKDF_INFO, 32);
3683
+ return import_tweetnacl2.default.box.keyPair.fromSecretKey(new Uint8Array(seed));
3684
+ }
3685
+ var import_tweetnacl2, import_crypto, import_util, hkdfAsync, NONCE_LENGTH, BOX_NONCE_LENGTH;
3686
+ var init_memory_envelope = __esm({
3687
+ "packages/shared/src/core/memory-envelope.ts"() {
3688
+ "use strict";
3689
+ import_tweetnacl2 = __toESM(require("tweetnacl"));
3690
+ import_crypto = require("crypto");
3691
+ import_util = require("util");
3692
+ init_owner_key_constants();
3693
+ hkdfAsync = (0, import_util.promisify)(import_crypto.hkdf);
3694
+ NONCE_LENGTH = import_tweetnacl2.default.secretbox.nonceLength;
3695
+ BOX_NONCE_LENGTH = import_tweetnacl2.default.box.nonceLength;
3696
+ }
3697
+ });
3698
+
3699
+ // packages/shared/src/core/encryption-keys.ts
3700
+ function loadProviderKeypair() {
3701
+ if (providerKeypair) return providerKeypair;
3702
+ const b64 = process.env.PMP_PROVIDER_ENC_SECRET;
3703
+ if (!b64) throw new Error("PMP_PROVIDER_ENC_SECRET is not set");
3704
+ const secret = Buffer.from(b64, "base64");
3705
+ if (secret.length !== import_tweetnacl3.default.box.secretKeyLength) {
3706
+ throw new Error(`PMP_PROVIDER_ENC_SECRET must decode to 32 bytes (got ${secret.length})`);
3707
+ }
3708
+ providerKeypair = import_tweetnacl3.default.box.keyPair.fromSecretKey(new Uint8Array(secret));
3709
+ log11.info("Provider encryption keypair loaded");
3710
+ return providerKeypair;
3711
+ }
3712
+ async function getOwnerEncryptionKey(ownerWallet) {
3713
+ const { data, error } = await getDb().from("encryption_keys").select("x25519_pubkey, verifier_ct").eq("owner_wallet", ownerWallet).maybeSingle();
3714
+ if (error) throw new Error(`getOwnerEncryptionKey failed: ${error.message}`);
3715
+ if (!data) return null;
3716
+ return { x25519_pubkey: data.x25519_pubkey, verifier_ct: data.verifier_ct };
3717
+ }
3718
+ var import_tweetnacl3, log11, providerKeypair;
3719
+ var init_encryption_keys = __esm({
3720
+ "packages/shared/src/core/encryption-keys.ts"() {
3721
+ "use strict";
3722
+ import_tweetnacl3 = __toESM(require("tweetnacl"));
3723
+ init_database();
3724
+ init_logger();
3725
+ log11 = createChildLogger("encryption-keys");
3726
+ providerKeypair = null;
3727
+ }
3728
+ });
3729
+
3730
+ // packages/brain/src/memory/memory-encryption.ts
3731
+ function isMemoryEncryptionEnabled() {
3732
+ if (process.env.PMP_ENCRYPTION_ENABLED !== "true") return false;
3733
+ try {
3734
+ loadProviderKeypair();
3735
+ return true;
3736
+ } catch {
3737
+ return false;
3738
+ }
3739
+ }
3740
+ async function getBotOwnerKeypair() {
3741
+ if (botKeypair) return botKeypair;
3742
+ const wallet = getBotWallet();
3743
+ if (!wallet) return null;
3744
+ const sig = import_tweetnacl4.default.sign.detached(new TextEncoder().encode(DERIVE_MESSAGE), wallet.secretKey);
3745
+ botKeypair = await deriveOwnerEncryptionKeypair(sig);
3746
+ return botKeypair;
3747
+ }
3748
+ async function resolveOwnerPublicKey(ownerWallet) {
3749
+ const botWallet2 = getBotWallet();
3750
+ const botAddr = botWallet2?.publicKey.toBase58();
3751
+ if (!ownerWallet || botAddr && ownerWallet === botAddr) {
3752
+ const kp = await getBotOwnerKeypair();
3753
+ return kp ? Buffer.from(kp.publicKey).toString("base64") : null;
3754
+ }
3755
+ const row = await getOwnerEncryptionKey(ownerWallet);
3756
+ return row?.x25519_pubkey ?? null;
3757
+ }
3758
+ async function encryptForStorage(plaintext, ownerWallet) {
3759
+ if (!isMemoryEncryptionEnabled()) return null;
3760
+ const ownerPubkey = await resolveOwnerPublicKey(ownerWallet);
3761
+ if (!ownerPubkey) {
3762
+ log12.warn("No owner encryption key resolvable \u2014 storing plaintext (encrypted:false)");
3763
+ return null;
3764
+ }
3765
+ const dek = generateDek();
3766
+ const ciphertext = encryptField(plaintext, dek);
3767
+ const ownerWrap = wrapDek(dek, Buffer.from(ownerPubkey, "base64"));
3768
+ const provWrap = wrapDek(dek, loadProviderKeypair().publicKey);
3769
+ return {
3770
+ ciphertext,
3771
+ ownerPubkey,
3772
+ wraps: [
3773
+ { recipient: "owner", wrapped_dek: ownerWrap.wrapped, wrap_pubkey: ownerWrap.wrapPubkey },
3774
+ { recipient: "provider", wrapped_dek: provWrap.wrapped, wrap_pubkey: provWrap.wrapPubkey }
3775
+ ]
3776
+ };
3777
+ }
3778
+ function delegationStateForWrite(hasEnvelope) {
3779
+ return hasEnvelope ? true : null;
3780
+ }
3781
+ var import_tweetnacl4, log12, DERIVE_MESSAGE, botKeypair;
3782
+ var init_memory_encryption = __esm({
3783
+ "packages/brain/src/memory/memory-encryption.ts"() {
3784
+ "use strict";
3785
+ import_tweetnacl4 = __toESM(require("tweetnacl"));
3786
+ init_memory_envelope();
3787
+ init_encryption_keys();
3788
+ init_solana_client();
3789
+ init_logger();
3790
+ log12 = createChildLogger("memory-encryption");
3791
+ DERIVE_MESSAGE = "clude-memory-encryption-v1";
3792
+ botKeypair = null;
3793
+ }
3794
+ });
3795
+
3796
+ // packages/shared/src/core/encryption.ts
3797
+ function isEncryptionEnabled() {
3798
+ return encryptionKey !== null;
3799
+ }
3800
+ function getEncryptionPubkey() {
3801
+ return encryptionPubkey;
3802
+ }
3803
+ function encryptContent(plaintext) {
3804
+ if (!encryptionKey) {
3805
+ throw new Error("Encryption not configured. Call configureEncryption() first.");
3806
+ }
3807
+ const nonce = import_tweetnacl5.default.randomBytes(NONCE_LENGTH2);
2199
3808
  const messageBytes = new TextEncoder().encode(plaintext);
2200
- const ciphertext = import_tweetnacl2.default.secretbox(messageBytes, nonce, encryptionKey);
2201
- const combined = new Uint8Array(NONCE_LENGTH + ciphertext.length);
3809
+ const ciphertext = import_tweetnacl5.default.secretbox(messageBytes, nonce, encryptionKey);
3810
+ const combined = new Uint8Array(NONCE_LENGTH2 + ciphertext.length);
2202
3811
  combined.set(nonce, 0);
2203
- combined.set(ciphertext, NONCE_LENGTH);
3812
+ combined.set(ciphertext, NONCE_LENGTH2);
2204
3813
  return Buffer.from(combined).toString("base64");
2205
3814
  }
2206
3815
  function decryptContent(encrypted) {
@@ -2209,12 +3818,12 @@ function decryptContent(encrypted) {
2209
3818
  }
2210
3819
  try {
2211
3820
  const combined = Buffer.from(encrypted, "base64");
2212
- if (combined.length < NONCE_LENGTH + 1) {
3821
+ if (combined.length < NONCE_LENGTH2 + 1) {
2213
3822
  return null;
2214
3823
  }
2215
- const nonce = combined.subarray(0, NONCE_LENGTH);
2216
- const ciphertext = combined.subarray(NONCE_LENGTH);
2217
- const plaintext = import_tweetnacl2.default.secretbox.open(ciphertext, nonce, encryptionKey);
3824
+ const nonce = combined.subarray(0, NONCE_LENGTH2);
3825
+ const ciphertext = combined.subarray(NONCE_LENGTH2);
3826
+ const plaintext = import_tweetnacl5.default.secretbox.open(ciphertext, nonce, encryptionKey);
2218
3827
  if (!plaintext) {
2219
3828
  return null;
2220
3829
  }
@@ -2228,34 +3837,92 @@ function decryptMemoryBatch(memories) {
2228
3837
  for (const mem of memories) {
2229
3838
  if (!mem.encrypted) continue;
2230
3839
  if (mem.encryption_pubkey && mem.encryption_pubkey !== encryptionPubkey) {
2231
- log8.debug({ memPubkey: mem.encryption_pubkey?.slice(0, 12) }, "Skipping memory encrypted by different key");
3840
+ log13.debug({ memPubkey: mem.encryption_pubkey?.slice(0, 12) }, "Skipping memory encrypted by different key");
2232
3841
  continue;
2233
3842
  }
2234
3843
  const decrypted = decryptContent(mem.content);
2235
3844
  if (decrypted !== null) {
2236
3845
  mem.content = decrypted;
2237
3846
  } else {
2238
- log8.warn("Failed to decrypt memory content \u2014 wrong key or corrupted data");
3847
+ log13.warn("Failed to decrypt memory content \u2014 wrong key or corrupted data");
2239
3848
  }
2240
3849
  }
2241
3850
  return memories;
2242
3851
  }
2243
- var import_tweetnacl2, import_crypto, import_util, log8, hkdfAsync, NONCE_LENGTH, encryptionKey, encryptionPubkey;
3852
+ var import_tweetnacl5, import_crypto2, import_util2, log13, hkdfAsync2, NONCE_LENGTH2, encryptionKey, encryptionPubkey;
2244
3853
  var init_encryption = __esm({
2245
3854
  "packages/shared/src/core/encryption.ts"() {
2246
3855
  "use strict";
2247
- import_tweetnacl2 = __toESM(require("tweetnacl"));
2248
- import_crypto = require("crypto");
2249
- import_util = require("util");
3856
+ import_tweetnacl5 = __toESM(require("tweetnacl"));
3857
+ import_crypto2 = require("crypto");
3858
+ import_util2 = require("util");
2250
3859
  init_logger();
2251
- log8 = createChildLogger("encryption");
2252
- hkdfAsync = (0, import_util.promisify)(import_crypto.hkdf);
2253
- NONCE_LENGTH = import_tweetnacl2.default.secretbox.nonceLength;
3860
+ log13 = createChildLogger("encryption");
3861
+ hkdfAsync2 = (0, import_util2.promisify)(import_crypto2.hkdf);
3862
+ NONCE_LENGTH2 = import_tweetnacl5.default.secretbox.nonceLength;
2254
3863
  encryptionKey = null;
2255
3864
  encryptionPubkey = null;
2256
3865
  }
2257
3866
  });
2258
3867
 
3868
+ // packages/brain/src/memory/memory-decryption.ts
3869
+ async function fetchProviderWraps(ids) {
3870
+ const map = /* @__PURE__ */ new Map();
3871
+ if (ids.length === 0) return map;
3872
+ const { data, error } = await getDb().from("memory_dek_wraps").select("memory_id, wrapped_dek, wrap_pubkey").eq("recipient", "provider").in("memory_id", ids);
3873
+ if (error || !data) return map;
3874
+ for (const r of data) {
3875
+ map.set(r.memory_id, { wrapped_dek: r.wrapped_dek, wrap_pubkey: r.wrap_pubkey });
3876
+ }
3877
+ return map;
3878
+ }
3879
+ async function decryptMemories(memories) {
3880
+ if (!memories || memories.length === 0) return memories;
3881
+ const encryptedRows = memories.filter((m) => m.encrypted === true);
3882
+ if (encryptedRows.length === 0) return memories;
3883
+ let providerSecret = null;
3884
+ try {
3885
+ providerSecret = loadProviderKeypair().secretKey;
3886
+ } catch {
3887
+ providerSecret = null;
3888
+ }
3889
+ const wraps = providerSecret ? await fetchProviderWraps(encryptedRows.map((m) => m.id)) : /* @__PURE__ */ new Map();
3890
+ const legacyRows = [];
3891
+ for (const mem of encryptedRows) {
3892
+ const wrap = wraps.get(mem.id);
3893
+ if (providerSecret && wrap) {
3894
+ const dek = unwrapDek(wrap.wrapped_dek, wrap.wrap_pubkey, providerSecret);
3895
+ const plain = dek ? decryptField(mem.content, dek) : null;
3896
+ if (plain !== null) {
3897
+ mem.content = plain;
3898
+ continue;
3899
+ }
3900
+ log14.debug({ id: mem.id }, "Envelope decrypt failed \u2014 leaving ciphertext");
3901
+ continue;
3902
+ }
3903
+ legacyRows.push(mem);
3904
+ }
3905
+ if (legacyRows.length > 0) decryptMemoryBatch(legacyRows);
3906
+ return memories;
3907
+ }
3908
+ async function decryptOneContent(row) {
3909
+ if (row.encrypted !== true) return row.content;
3910
+ const [out] = await decryptMemories([{ ...row }]);
3911
+ return out.content !== row.content ? out.content : null;
3912
+ }
3913
+ var log14;
3914
+ var init_memory_decryption = __esm({
3915
+ "packages/brain/src/memory/memory-decryption.ts"() {
3916
+ "use strict";
3917
+ init_database();
3918
+ init_memory_envelope();
3919
+ init_encryption_keys();
3920
+ init_encryption();
3921
+ init_logger();
3922
+ log14 = createChildLogger("memory-decryption");
3923
+ }
3924
+ });
3925
+
2259
3926
  // packages/brain/src/events/event-bus.ts
2260
3927
  var import_events, TypedEventBus, eventBus;
2261
3928
  var init_event_bus = __esm({
@@ -2304,16 +3971,16 @@ async function findOrCreateEntity(name, entityType, opts) {
2304
3971
  mention_count: 1
2305
3972
  }).select().single();
2306
3973
  if (error) {
2307
- log9.error({ error: error.message, name }, "Failed to create entity");
3974
+ log15.error({ error: error.message, name }, "Failed to create entity");
2308
3975
  return null;
2309
3976
  }
2310
- log9.debug({ id: newEntity.id, name, type: entityType }, "Entity created");
3977
+ log15.debug({ id: newEntity.id, name, type: entityType }, "Entity created");
2311
3978
  embedEntity(newEntity.id, name, opts?.description).catch(
2312
- (err) => log9.debug({ err }, "Entity embedding failed")
3979
+ (err) => log15.debug({ err }, "Entity embedding failed")
2313
3980
  );
2314
3981
  return newEntity;
2315
3982
  } catch (err) {
2316
- log9.error({ err, name }, "Entity findOrCreate failed");
3983
+ log15.error({ err, name }, "Entity findOrCreate failed");
2317
3984
  return null;
2318
3985
  }
2319
3986
  }
@@ -2335,7 +4002,7 @@ async function createEntityMention(entityId, memoryId, context, salience = 0.5)
2335
4002
  salience: Math.max(0, Math.min(1, salience))
2336
4003
  }, { onConflict: "entity_id,memory_id" });
2337
4004
  if (error) {
2338
- log9.debug({ error: error.message, entityId, memoryId }, "Entity mention failed");
4005
+ log15.debug({ error: error.message, entityId, memoryId }, "Entity mention failed");
2339
4006
  }
2340
4007
  }
2341
4008
  async function getMemoriesByEntity(entityId, opts) {
@@ -2347,12 +4014,12 @@ async function getMemoriesByEntity(entityId, opts) {
2347
4014
  `).eq("entity_id", entityId).order("salience", { ascending: false }).limit(opts?.limit || 20);
2348
4015
  const { data, error } = await query;
2349
4016
  if (error) {
2350
- log9.error({ error: error.message, entityId }, "Failed to get memories by entity");
4017
+ log15.error({ error: error.message, entityId }, "Failed to get memories by entity");
2351
4018
  return [];
2352
4019
  }
2353
4020
  const { getOwnerWallet: getOwnerWallet3 } = (init_memory(), __toCommonJS(memory_exports));
2354
4021
  const ownerWallet = getOwnerWallet3();
2355
- const memories = (data || []).map((d) => d.memories).filter((m) => m !== null).filter((m) => !opts?.memoryTypes || opts.memoryTypes.includes(m.memory_type)).filter((m) => !ownerWallet || m.owner_wallet === ownerWallet);
4022
+ const memories = (data || []).map((d) => d.memories).filter((m) => m !== null).filter((m) => m.provider_delegated !== false).filter((m) => !opts?.memoryTypes || opts.memoryTypes.includes(m.memory_type)).filter((m) => !ownerWallet || m.owner_wallet === ownerWallet);
2356
4023
  return memories;
2357
4024
  }
2358
4025
  async function getEntityCooccurrences(entityId, opts) {
@@ -2364,33 +4031,27 @@ async function getEntityCooccurrences(entityId, opts) {
2364
4031
  max_results: opts?.maxResults ?? 10
2365
4032
  });
2366
4033
  if (error || !data) {
2367
- log9.debug({ error: error?.message, entityId }, "Entity co-occurrence lookup failed");
4034
+ log15.debug({ error: error?.message, entityId }, "Entity co-occurrence lookup failed");
2368
4035
  return [];
2369
4036
  }
2370
4037
  return data;
2371
4038
  } catch (err) {
2372
- log9.debug({ err, entityId }, "Entity co-occurrence skipped (RPC unavailable)");
4039
+ log15.debug({ err, entityId }, "Entity co-occurrence skipped (RPC unavailable)");
2373
4040
  return [];
2374
4041
  }
2375
4042
  }
2376
4043
  async function createEntityRelation(sourceEntityId, targetEntityId, relationType, evidenceMemoryId, strength = 0.5) {
2377
4044
  if (sourceEntityId === targetEntityId) return;
2378
4045
  const db = getDb();
2379
- const { data: existing } = await db.from("entity_relations").select("id, strength, evidence_memory_ids").eq("source_entity_id", sourceEntityId).eq("target_entity_id", targetEntityId).eq("relation_type", relationType).single();
2380
- if (existing) {
2381
- const newEvidence = evidenceMemoryId && !existing.evidence_memory_ids.includes(evidenceMemoryId) ? [...existing.evidence_memory_ids, evidenceMemoryId] : existing.evidence_memory_ids;
2382
- await db.from("entity_relations").update({
2383
- strength: Math.min(1, existing.strength + 0.1),
2384
- evidence_memory_ids: newEvidence
2385
- }).eq("id", existing.id);
2386
- } else {
2387
- await db.from("entity_relations").insert({
2388
- source_entity_id: sourceEntityId,
2389
- target_entity_id: targetEntityId,
2390
- relation_type: relationType,
2391
- strength,
2392
- evidence_memory_ids: evidenceMemoryId ? [evidenceMemoryId] : []
2393
- });
4046
+ const { error } = await db.rpc("upsert_entity_relation", {
4047
+ p_src: sourceEntityId,
4048
+ p_tgt: targetEntityId,
4049
+ p_type: relationType,
4050
+ p_evidence: evidenceMemoryId ?? null,
4051
+ p_strength: strength
4052
+ });
4053
+ if (error) {
4054
+ log15.warn({ err: error.message, sourceEntityId, targetEntityId, relationType }, "upsert_entity_relation failed");
2394
4055
  }
2395
4056
  }
2396
4057
  function extractEntitiesFromText(text) {
@@ -2470,11 +4131,11 @@ async function extractAndLinkEntities(memoryId, content, summary, relatedUser) {
2470
4131
  // base co-occurrence strength
2471
4132
  );
2472
4133
  } catch (err) {
2473
- log9.debug({ err, source: entityIds[i].name, target: entityIds[j].name }, "Failed to create entity relation");
4134
+ log15.debug({ err, source: entityIds[i].name, target: entityIds[j].name }, "Failed to create entity relation");
2474
4135
  }
2475
4136
  }
2476
4137
  }
2477
- log9.debug({ memoryId, entityCount: extracted.length, relations: Math.max(0, entityIds.length * (entityIds.length - 1) / 2) }, "Entities extracted, linked, and related");
4138
+ log15.debug({ memoryId, entityCount: extracted.length, relations: Math.max(0, entityIds.length * (entityIds.length - 1) / 2) }, "Entities extracted, linked, and related");
2478
4139
  }
2479
4140
  async function findSimilarEntities(query, opts) {
2480
4141
  if (!isEmbeddingEnabled()) return [];
@@ -2488,7 +4149,7 @@ async function findSimilarEntities(query, opts) {
2488
4149
  filter_types: opts?.entityTypes || null
2489
4150
  });
2490
4151
  if (error) {
2491
- log9.debug({ error: error.message }, "Entity vector search failed");
4152
+ log15.debug({ error: error.message }, "Entity vector search failed");
2492
4153
  return [];
2493
4154
  }
2494
4155
  const ids = (data || []).map((d) => d.id);
@@ -2496,14 +4157,14 @@ async function findSimilarEntities(query, opts) {
2496
4157
  const { data: entities } = await db.from("entities").select("*").in("id", ids);
2497
4158
  return entities || [];
2498
4159
  }
2499
- var log9;
4160
+ var log15;
2500
4161
  var init_graph = __esm({
2501
4162
  "packages/brain/src/memory/graph.ts"() {
2502
4163
  "use strict";
2503
4164
  init_database();
2504
4165
  init_logger();
2505
4166
  init_embeddings();
2506
- log9 = createChildLogger("memory-graph");
4167
+ log15 = createChildLogger("memory-graph");
2507
4168
  }
2508
4169
  });
2509
4170
 
@@ -2523,8 +4184,11 @@ var init_owner_context = __esm({
2523
4184
  // packages/brain/src/memory/memory.ts
2524
4185
  var memory_exports = {};
2525
4186
  __export(memory_exports, {
2526
- SCOPE_BOT_OWN: () => SCOPE_BOT_OWN,
4187
+ SCOPE_BOT_OWN: () => SCOPE_BOT_OWN2,
4188
+ _resetOutboxDegradeLog: () => _resetOutboxDegradeLog,
2527
4189
  _setOwnerWallet: () => _setOwnerWallet,
4190
+ applyOwnerPostGuard: () => applyOwnerPostGuard,
4191
+ buildFragmentRpcArgs: () => buildFragmentRpcArgs,
2528
4192
  calculateImportance: () => calculateImportance,
2529
4193
  createMemoryLink: () => createMemoryLink,
2530
4194
  createMemoryLinksBatch: () => createMemoryLinksBatch,
@@ -2535,11 +4199,15 @@ __export(memory_exports, {
2535
4199
  formatMemoryContext: () => formatMemoryContext,
2536
4200
  generateHashId: () => generateHashId,
2537
4201
  getMemoryStats: () => getMemoryStats,
4202
+ getOwnerScope: () => getOwnerScope,
2538
4203
  getOwnerWallet: () => getOwnerWallet,
2539
4204
  getRecentMemories: () => getRecentMemories,
2540
4205
  getSelfModel: () => getSelfModel,
2541
4206
  hydrateMemories: () => hydrateMemories,
2542
4207
  inferConcepts: () => inferConcepts,
4208
+ invalidateInstalledPacksCache: () => invalidateInstalledPacksCache,
4209
+ isBenchMode: () => isBenchMode,
4210
+ isOwnerScopeFailClosed: () => isOwnerScopeFailClosed,
2543
4211
  isValidHashId: () => isValidHashId,
2544
4212
  listMemories: () => listMemories,
2545
4213
  markJepaQueried: () => markJepaQueried,
@@ -2547,11 +4215,15 @@ __export(memory_exports, {
2547
4215
  moodToValence: () => moodToValence,
2548
4216
  recallMemories: () => recallMemories,
2549
4217
  recallMemorySummaries: () => recallMemorySummaries,
4218
+ runEnrichPipeline: () => runEnrichPipeline,
2550
4219
  scopeToOwner: () => scopeToOwner,
4220
+ scoreImportanceOnWrite: () => scoreImportanceOnWrite,
2551
4221
  scoreImportanceWithLLM: () => scoreImportanceWithLLM,
2552
4222
  scoreMemory: () => scoreMemory,
4223
+ shouldTrackAccess: () => shouldTrackAccess,
2553
4224
  storeDreamLog: () => storeDreamLog,
2554
4225
  storeMemory: () => storeMemory,
4226
+ storeMemoryWithOutbox: () => storeMemoryWithOutbox,
2555
4227
  updateMemory: () => updateMemory
2556
4228
  });
2557
4229
  function getCachedEmbedding2(query) {
@@ -2580,18 +4252,51 @@ function getOwnerWallet() {
2580
4252
  if (contextWallet !== void 0) return contextWallet;
2581
4253
  return _ownerWallet;
2582
4254
  }
2583
- function scopeToOwner(query) {
4255
+ function isOwnerScopeFailClosed() {
4256
+ return process.env.OWNER_SCOPE_FAILCLOSED === "true";
4257
+ }
4258
+ function getOwnerScope() {
2584
4259
  const wallet = getOwnerWallet();
2585
- if (wallet === SCOPE_BOT_OWN) {
4260
+ if (wallet) return wallet;
4261
+ return isOwnerScopeFailClosed() ? SCOPE_BOT_OWN2 : null;
4262
+ }
4263
+ function scopeToOwner(query) {
4264
+ const scope = getOwnerScope();
4265
+ if (scope === SCOPE_BOT_OWN2) {
2586
4266
  return query.is("owner_wallet", null);
2587
4267
  }
2588
- if (wallet) {
2589
- return query.eq("owner_wallet", wallet);
4268
+ if (scope) {
4269
+ return query.eq("owner_wallet", scope);
2590
4270
  }
2591
4271
  return query;
2592
4272
  }
4273
+ function applyOwnerPostGuard(results, scope) {
4274
+ if (!scope) return { kept: results, stripped: 0 };
4275
+ const kept = scope === SCOPE_BOT_OWN2 ? results.filter((m) => m.owner_wallet == null) : results.filter((m) => m.owner_wallet === scope);
4276
+ return { kept, stripped: results.length - kept.length };
4277
+ }
4278
+ function isBenchMode() {
4279
+ return process.env.BENCH_MODE === "true";
4280
+ }
4281
+ function shouldTrackAccess(opts) {
4282
+ if (isBenchMode()) return false;
4283
+ return opts.trackAccess !== false;
4284
+ }
4285
+ function buildFragmentRpcArgs(opts) {
4286
+ const args = {
4287
+ query_embedding: opts.embeddingJson,
4288
+ match_threshold: opts.matchThreshold,
4289
+ match_count: opts.matchCount,
4290
+ filter_owner: getOwnerScope()
4291
+ };
4292
+ if (process.env.MEMORY_FRAGMENT_FILTERS === "true") {
4293
+ args.min_decay = opts.minDecay;
4294
+ args.filter_types = opts.memoryTypes && opts.memoryTypes.length > 0 ? opts.memoryTypes : null;
4295
+ }
4296
+ return args;
4297
+ }
2593
4298
  function generateHashId() {
2594
- return `${HASH_ID_PREFIX}-${(0, import_crypto2.randomBytes)(4).toString("hex")}`;
4299
+ return `${HASH_ID_PREFIX}-${(0, import_crypto3.randomBytes)(4).toString("hex")}`;
2595
4300
  }
2596
4301
  function isValidHashId(id) {
2597
4302
  return /^clude-[a-f0-9]{8}$/.test(id);
@@ -2626,6 +4331,23 @@ function inferConcepts(summary, source, tags) {
2626
4331
  concepts.push("identity_evolution");
2627
4332
  return [...new Set(concepts)];
2628
4333
  }
4334
+ function scoreImportanceOnWrite(opts) {
4335
+ const base = {
4336
+ self_model: 0.75,
4337
+ semantic: 0.7,
4338
+ procedural: 0.65,
4339
+ introspective: 0.6,
4340
+ episodic: 0.45
4341
+ };
4342
+ let score = base[opts.type] ?? 0.5;
4343
+ const len = (opts.content ?? "").trim().length;
4344
+ score += Math.min(len / 1e3, 0.15);
4345
+ if (opts.tags && opts.tags.length) score += 0.05;
4346
+ const concepts = opts.concepts ?? inferConcepts(opts.summary, opts.source, opts.tags || []);
4347
+ if (concepts.length) score += 0.05;
4348
+ if (INTERNAL_MEMORY_SOURCES.has(opts.source)) score -= 0.05;
4349
+ return Math.max(0, Math.min(1, score));
4350
+ }
2629
4351
  function normalizeSummary(summary) {
2630
4352
  return summary.replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, "<id>").replace(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[^\s]*/g, "<ts>").toLowerCase().trim();
2631
4353
  }
@@ -2642,27 +4364,104 @@ function isDuplicateWrite(source, summary) {
2642
4364
  }
2643
4365
  return false;
2644
4366
  }
4367
+ async function getInstalledPackIdsCached(ownerWallet) {
4368
+ const key = ownerWallet ?? "__no_owner__";
4369
+ const now = Date.now();
4370
+ const cached = installedPacksCache.get(key);
4371
+ if (cached && cached.expiresAt > now) return cached.ids;
4372
+ let ids = [DEFAULT_PACK_ID];
4373
+ if (ownerWallet) {
4374
+ try {
4375
+ const db = getDb();
4376
+ const { data, error } = await db.from("wiki_pack_installations").select("pack_id").eq("owner_wallet", ownerWallet);
4377
+ if (!error && data) {
4378
+ const fromDb = data.map((r) => r.pack_id);
4379
+ ids = Array.from(/* @__PURE__ */ new Set([DEFAULT_PACK_ID, ...fromDb]));
4380
+ }
4381
+ } catch (err) {
4382
+ log16.debug({ err }, "Failed to fetch installed packs (using default only)");
4383
+ }
4384
+ }
4385
+ installedPacksCache.set(key, { ids, expiresAt: now + INSTALLED_PACKS_TTL_MS });
4386
+ if (installedPacksCache.size > 200) {
4387
+ for (const [k, v] of installedPacksCache) {
4388
+ if (v.expiresAt <= now) installedPacksCache.delete(k);
4389
+ }
4390
+ }
4391
+ return ids;
4392
+ }
4393
+ function invalidateInstalledPacksCache(ownerWallet) {
4394
+ if (ownerWallet) installedPacksCache.delete(ownerWallet);
4395
+ else installedPacksCache.clear();
4396
+ }
4397
+ async function ensureTopicEmbeddings(installedPackIds) {
4398
+ const sources = topicEmbedSources(installedPackIds);
4399
+ const missing = sources.filter((s) => !topicEmbeddingCache.has(s.topicId));
4400
+ if (missing.length === 0) {
4401
+ return new Map(sources.map((s) => [s.topicId, topicEmbeddingCache.get(s.topicId)]));
4402
+ }
4403
+ if (topicEmbeddingPopulationLock) {
4404
+ await topicEmbeddingPopulationLock;
4405
+ }
4406
+ const stillMissing = sources.filter((s) => !topicEmbeddingCache.has(s.topicId));
4407
+ if (stillMissing.length === 0) {
4408
+ return new Map(sources.map((s) => [s.topicId, topicEmbeddingCache.get(s.topicId)]));
4409
+ }
4410
+ topicEmbeddingPopulationLock = (async () => {
4411
+ try {
4412
+ const texts = stillMissing.map((s) => s.text);
4413
+ const embeddings = await generateEmbeddings(texts);
4414
+ stillMissing.forEach((s, i) => {
4415
+ const emb = embeddings[i];
4416
+ if (emb) topicEmbeddingCache.set(s.topicId, emb);
4417
+ });
4418
+ } catch (err) {
4419
+ log16.warn({ err }, "Failed to populate topic embeddings cache");
4420
+ } finally {
4421
+ topicEmbeddingPopulationLock = null;
4422
+ }
4423
+ })();
4424
+ await topicEmbeddingPopulationLock;
4425
+ return new Map(
4426
+ sources.filter((s) => topicEmbeddingCache.has(s.topicId)).map((s) => [s.topicId, topicEmbeddingCache.get(s.topicId)])
4427
+ );
4428
+ }
2645
4429
  async function storeMemory(opts) {
4430
+ const trimmedContent = (opts.content ?? "").trim();
4431
+ if (!trimmedContent) {
4432
+ log16.warn({ source: opts.source }, "Rejected empty-content memory");
4433
+ return null;
4434
+ }
2646
4435
  if (opts.source.startsWith("shiro_") && isDuplicateWrite(opts.source, opts.summary)) {
2647
- log10.debug({ source: opts.source, summary: opts.summary.slice(0, 60) }, "Skipping duplicate memory write");
4436
+ log16.debug({ source: opts.source, summary: opts.summary.slice(0, 60) }, "Skipping duplicate memory write");
2648
4437
  return null;
2649
4438
  }
2650
4439
  const db = getDb();
4440
+ const ownerWallet = getOwnerWallet();
4441
+ const importance = clamp(opts.importance ?? scoreImportanceOnWrite(opts), 0, 1);
2651
4442
  const concepts = opts.concepts || inferConcepts(opts.summary, opts.source, opts.tags || []);
4443
+ const installedPackIds = await getInstalledPackIdsCached(ownerWallet);
4444
+ const taggedTags = autoCategorizeTags({
4445
+ content: opts.content,
4446
+ summary: opts.summary,
4447
+ existingTags: opts.tags || [],
4448
+ installedPackIds
4449
+ });
2652
4450
  const hashId = generateHashId();
2653
4451
  try {
2654
- const plaintextContent = opts.content.slice(0, MEMORY_MAX_CONTENT_LENGTH);
2655
- const shouldEncrypt = isEncryptionEnabled();
2656
- const storedContent = shouldEncrypt ? encryptContent(plaintextContent) : plaintextContent;
4452
+ const plaintextContent = trimmedContent.slice(0, MEMORY_MAX_CONTENT_LENGTH);
4453
+ const envelope = await encryptForStorage(plaintextContent, ownerWallet || null);
4454
+ const legacyEncrypt = !envelope && isEncryptionEnabled();
4455
+ const storedContent = envelope ? envelope.ciphertext : legacyEncrypt ? encryptContent(plaintextContent) : plaintextContent;
2657
4456
  const { data, error } = await db.from("memories").insert({
2658
4457
  hash_id: hashId,
2659
4458
  memory_type: opts.type,
2660
4459
  content: storedContent,
2661
4460
  summary: opts.summary.slice(0, MEMORY_MAX_SUMMARY_LENGTH),
2662
- tags: opts.tags || [],
4461
+ tags: taggedTags,
2663
4462
  concepts,
2664
4463
  emotional_valence: clamp(opts.emotionalValence ?? 0, -1, 1),
2665
- importance: clamp(opts.importance ?? 0.5, 0, 1),
4464
+ importance,
2666
4465
  source: opts.source,
2667
4466
  source_id: opts.sourceId || null,
2668
4467
  related_user: opts.relatedUser || null,
@@ -2670,43 +4469,83 @@ async function storeMemory(opts) {
2670
4469
  metadata: opts.metadata || {},
2671
4470
  evidence_ids: opts.evidenceIds || [],
2672
4471
  compacted: false,
2673
- encrypted: shouldEncrypt,
2674
- encryption_pubkey: shouldEncrypt ? getEncryptionPubkey() : null,
2675
- owner_wallet: getOwnerWallet() || null
2676
- }).select("id, hash_id").single();
4472
+ encrypted: envelope !== null || legacyEncrypt,
4473
+ encryption_pubkey: envelope ? envelope.ownerPubkey : legacyEncrypt ? getEncryptionPubkey() : null,
4474
+ provider_delegated: delegationStateForWrite(envelope !== null),
4475
+ // true|null at write; false is revoke-only (§7)
4476
+ owner_wallet: ownerWallet || null
4477
+ }).select("id, hash_id, content, memory_type, owner_wallet, created_at, tags, source, related_user, related_wallet").single();
2677
4478
  if (error) {
2678
- log10.error({ error: error.message }, "Failed to store memory");
4479
+ log16.error({ error: error.message }, "Failed to store memory");
2679
4480
  return null;
2680
4481
  }
2681
- log10.debug({
4482
+ log16.debug({
2682
4483
  id: data.id,
2683
4484
  hashId: data.hash_id,
2684
4485
  type: opts.type,
2685
4486
  summary: opts.summary.slice(0, 60),
2686
- importance: opts.importance,
4487
+ importance,
2687
4488
  concepts
2688
4489
  }, "Memory stored");
4490
+ await setContentTokens(db, data.id, plaintextContent);
4491
+ if (envelope) {
4492
+ const { error: wrapErr } = await db.from("memory_dek_wraps").insert(
4493
+ envelope.wraps.map((w) => ({ memory_id: data.id, ...w }))
4494
+ );
4495
+ if (wrapErr) {
4496
+ log16.error({ id: data.id, error: wrapErr.message }, "DEK wrap write failed \u2014 reverting memory to plaintext");
4497
+ await db.from("memories").update({
4498
+ content: plaintextContent,
4499
+ encrypted: false,
4500
+ encryption_pubkey: null,
4501
+ provider_delegated: null
4502
+ }).eq("id", data.id);
4503
+ }
4504
+ }
2689
4505
  eventBus.emit("memory:stored", {
2690
- importance: clamp(opts.importance ?? 0.5, 0, 1),
4506
+ importance,
2691
4507
  memoryType: opts.type,
2692
4508
  source: opts.source
2693
4509
  });
2694
- commitMemoryToChain(data.id, opts).catch((err) => log10.warn({ err }, "On-chain memory commit failed"));
2695
- embedMemory(data.id, opts).catch((err) => log10.warn({ err }, "Embedding generation failed"));
2696
- autoLinkMemory(data.id, opts).catch((err) => log10.warn({ err }, "Auto-linking failed"));
2697
- extractAndLinkEntitiesForMemory(data.id, opts).catch((err) => log10.debug({ err }, "Entity extraction failed"));
4510
+ commitMemoryToChain(data.id, opts, data, plaintextContent, envelope !== null || legacyEncrypt).catch(
4511
+ (err) => log16.warn({ err }, "On-chain memory commit failed")
4512
+ );
4513
+ let enqueued = false;
4514
+ if (config.memory.outboxEnabled) {
4515
+ enqueued = await enqueueEnrichJob(db, data.id);
4516
+ }
4517
+ if (!enqueued) {
4518
+ const embedP = embedMemory(data.id, opts);
4519
+ embedP.catch((err) => log16.warn({ err }, "Embedding generation failed"));
4520
+ autoLinkMemory(data.id, opts).catch((err) => log16.warn({ err }, "Auto-linking failed"));
4521
+ extractAndLinkEntitiesForMemory(data.id, opts).catch((err) => log16.debug({ err }, "Entity extraction failed"));
4522
+ if (config.memory.reconcileEnabled) {
4523
+ const reconcileScope = ownerWallet || SCOPE_BOT_OWN2;
4524
+ embedP.then(() => runReconcileShadow(data.id, reconcileScope)).catch(() => {
4525
+ });
4526
+ }
4527
+ }
2698
4528
  return data.id;
2699
4529
  } catch (err) {
2700
- log10.error({ err }, "Memory store failed");
4530
+ log16.error({ err }, "Memory store failed");
2701
4531
  return null;
2702
4532
  }
2703
4533
  }
2704
- async function commitMemoryToChain(memoryId, opts) {
2705
- if (opts.source === "demo" || opts.source === "demo-maas" || opts.source === "locomo-benchmark" || opts.source === "longmemeval-benchmark") return;
2706
- const contentHashBuf = (0, import_crypto2.createHash)("sha256").update(opts.content).digest();
4534
+ async function commitMemoryToChain(memoryId, opts, row, plaintextContent, encrypted) {
4535
+ if (TOKENISATION_SKIP_SOURCES.has(opts.source)) return;
4536
+ const canonicalHash = memoryContentHash({
4537
+ content: plaintextContent,
4538
+ memory_type: row.memory_type,
4539
+ owner_wallet: row.owner_wallet,
4540
+ created_at: row.created_at,
4541
+ tags: row.tags ?? [],
4542
+ source: row.source,
4543
+ related_user: row.related_user,
4544
+ related_wallet: row.related_wallet
4545
+ });
4546
+ const contentHashBuf = Buffer.from(canonicalHash, "hex");
2707
4547
  let signature = null;
2708
4548
  if (isRegistryEnabled()) {
2709
- const encrypted = isEncryptionEnabled();
2710
4549
  signature = await registerMemoryOnChain(
2711
4550
  contentHashBuf,
2712
4551
  opts.type,
@@ -2716,24 +4555,146 @@ async function commitMemoryToChain(memoryId, opts) {
2716
4555
  );
2717
4556
  }
2718
4557
  if (!signature) {
2719
- const contentHashHex = contentHashBuf.toString("hex");
2720
- const memo = `clude:v1:sha256:${contentHashHex}`;
4558
+ const memo = `clude:v1:sha256:${canonicalHash}`;
2721
4559
  signature = await writeMemo(memo);
2722
4560
  }
2723
- if (!signature) return;
2724
4561
  const db = getDb();
2725
- await db.from("memories").update({ solana_signature: signature }).eq("id", memoryId);
2726
- log10.debug({ memoryId, signature: signature.slice(0, 16) }, "Memory committed on-chain");
4562
+ if (!signature) {
4563
+ await db.from("memories").update({ content_hash: canonicalHash, tokenization_status: "failed" }).eq("id", memoryId);
4564
+ return;
4565
+ }
4566
+ await db.from("memories").update({
4567
+ solana_signature: signature,
4568
+ content_hash: canonicalHash,
4569
+ cnft_address: signature,
4570
+ // PDA-based v0.1 — assetId = tx sig; LightMintClient (v0.2) will use real cNFT mints
4571
+ cnft_tx_sig: signature,
4572
+ cnft_tree: null,
4573
+ cnft_leaf_index: null,
4574
+ tokenization_status: "minted",
4575
+ tokenized_at: (/* @__PURE__ */ new Date()).toISOString()
4576
+ }).eq("id", memoryId);
4577
+ log16.debug({ memoryId, signature: signature.slice(0, 16) }, "Memory committed on-chain (PMP + legacy)");
2727
4578
  }
2728
- async function embedMemory(memoryId, opts) {
4579
+ function logOutboxDegradeOnce(err, memoryId) {
4580
+ if (outboxDegradeLogged) return;
4581
+ outboxDegradeLogged = true;
4582
+ const e = err;
4583
+ log16.warn(
4584
+ { code: e?.code, message: e?.message, memoryId },
4585
+ "Outbox enqueue degraded \u2014 falling back to fire-and-forget (migration 044 unapplied?)"
4586
+ );
4587
+ }
4588
+ async function enqueueEnrichJob(db, memoryId) {
4589
+ try {
4590
+ const { error } = await db.from("memory_write_jobs").insert({ memory_id: memoryId, job_type: "enrich" });
4591
+ if (error) {
4592
+ logOutboxDegradeOnce(error, memoryId);
4593
+ return false;
4594
+ }
4595
+ return true;
4596
+ } catch (err) {
4597
+ logOutboxDegradeOnce(err, memoryId);
4598
+ return false;
4599
+ }
4600
+ }
4601
+ function _resetOutboxDegradeLog() {
4602
+ outboxDegradeLogged = false;
4603
+ }
4604
+ async function runEnrichPipeline(memoryId, opts) {
4605
+ await embedMemory(memoryId, opts, { replaceFragments: true });
4606
+ await autoLinkMemory(memoryId, opts);
4607
+ await extractAndLinkEntitiesForMemory(memoryId, opts);
4608
+ if (config.memory.reconcileEnabled) {
4609
+ await runReconcileShadow(memoryId, getOwnerWallet() || SCOPE_BOT_OWN2);
4610
+ }
4611
+ }
4612
+ async function storeMemoryWithOutbox(opts) {
4613
+ const id = await storeMemory(opts);
4614
+ if (id === null) return null;
4615
+ const db = getDb();
4616
+ const { data: row } = await db.from("memories").select("hash_id").eq("id", id).maybeSingle();
4617
+ const { count } = await db.from("memory_write_jobs").select("id", { count: "exact", head: true }).eq("memory_id", id);
4618
+ return { id, hash_id: row?.hash_id ?? "", jobs_queued: count ?? 0 };
4619
+ }
4620
+ async function embedMemory(memoryId, opts, embedOpts = {}) {
2729
4621
  if (!isEmbeddingEnabled()) return;
2730
4622
  const db = getDb();
2731
- const embeddings = await generateEmbeddings([opts.summary]);
4623
+ const fragments = [];
4624
+ fragments.push({ type: "summary", text: opts.summary });
4625
+ const content = opts.content.slice(0, MEMORY_MAX_CONTENT_LENGTH);
4626
+ if (content.length > EMBEDDING_FRAGMENT_MAX_LENGTH) {
4627
+ const sentences = content.match(/[^.!?\n]+[.!?\n]+/g) || [content];
4628
+ let chunk = "";
4629
+ for (const sentence of sentences) {
4630
+ if (chunk.length + sentence.length > EMBEDDING_FRAGMENT_MAX_LENGTH && chunk.length > 0) {
4631
+ fragments.push({ type: "content_chunk", text: chunk.trim() });
4632
+ chunk = "";
4633
+ }
4634
+ chunk += sentence;
4635
+ }
4636
+ if (chunk.trim()) fragments.push({ type: "content_chunk", text: chunk.trim() });
4637
+ } else {
4638
+ fragments.push({ type: "content_chunk", text: content });
4639
+ }
4640
+ const allLabels = [...opts.tags || [], ...opts.concepts || inferConcepts(opts.summary, opts.source, opts.tags || [])];
4641
+ if (allLabels.length > 0) {
4642
+ fragments.push({ type: "tag_context", text: `Context: ${allLabels.join(", ")}. ${opts.summary}` });
4643
+ }
4644
+ const embeddings = await generateEmbeddings(fragments.map((f) => f.text));
2732
4645
  const summaryEmbedding = embeddings[0];
2733
- if (summaryEmbedding) {
2734
- await db.from("memories").update({ embedding: JSON.stringify(summaryEmbedding) }).eq("id", memoryId);
4646
+ if (!summaryEmbedding) {
4647
+ log16.debug({ memoryId }, "No embedding generated; skipping semantic tagging");
4648
+ return;
4649
+ }
4650
+ const vertexEmbeddings = isVertexConfigured() ? await generateVertexEmbeddings(fragments.map((f) => f.text)) : [];
4651
+ const vertexSummary = vertexEmbeddings[0];
4652
+ await db.from("memories").update({
4653
+ embedding: JSON.stringify(summaryEmbedding),
4654
+ ...vertexSummary ? { embedding_vertex: JSON.stringify(vertexSummary) } : {}
4655
+ }).eq("id", memoryId);
4656
+ const fragmentRows = fragments.map((f, i) => ({
4657
+ memory_id: memoryId,
4658
+ fragment_type: f.type,
4659
+ content: f.text.slice(0, EMBEDDING_FRAGMENT_MAX_LENGTH),
4660
+ embedding: embeddings[i] ? JSON.stringify(embeddings[i]) : null,
4661
+ ...vertexEmbeddings[i] ? { embedding_vertex: JSON.stringify(vertexEmbeddings[i]) } : {}
4662
+ })).filter((r) => r.embedding !== null);
4663
+ if (fragmentRows.length > 0) {
4664
+ if (embedOpts.replaceFragments) {
4665
+ await db.from("memory_fragments").delete().eq("memory_id", memoryId);
4666
+ }
4667
+ const { error } = await db.from("memory_fragments").insert(fragmentRows);
4668
+ if (error) {
4669
+ log16.warn({ err: error.message, memoryId }, "Failed to store memory fragments (summary embedding intact)");
4670
+ }
2735
4671
  }
2736
- log10.debug({ memoryId }, "Memory embedded");
4672
+ try {
4673
+ const ownerWallet = getOwnerWallet();
4674
+ const installedPacks = await getInstalledPackIdsCached(ownerWallet);
4675
+ const topicEmbeddings = await ensureTopicEmbeddings(installedPacks);
4676
+ if (topicEmbeddings.size > 0) {
4677
+ const semantic = semanticTagMatches(
4678
+ summaryEmbedding,
4679
+ Array.from(topicEmbeddings, ([topicId, embedding]) => ({ topicId, embedding }))
4680
+ );
4681
+ if (semantic.length > 0) {
4682
+ const { data: row } = await db.from("memories").select("tags").eq("id", memoryId).maybeSingle();
4683
+ const existing = row?.tags ?? [];
4684
+ const merged = Array.from(/* @__PURE__ */ new Set([...existing, ...semantic]));
4685
+ if (merged.length !== existing.length) {
4686
+ await db.from("memories").update({ tags: merged }).eq("id", memoryId);
4687
+ log16.debug({
4688
+ memoryId,
4689
+ added: semantic.filter((t) => !existing.includes(t))
4690
+ }, "Semantic pack tags appended");
4691
+ }
4692
+ }
4693
+ }
4694
+ } catch (err) {
4695
+ log16.debug({ err, memoryId }, "Semantic tagging skipped");
4696
+ }
4697
+ log16.debug({ memoryId }, "Memory embedded");
2737
4698
  }
2738
4699
  async function expandQuery(query) {
2739
4700
  if (!isOpenRouterEnabled()) return [query];
@@ -2751,10 +4712,10 @@ async function expandQuery(query) {
2751
4712
  new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), 3e3))
2752
4713
  ]);
2753
4714
  const expansions = response.split("\n").map((l) => l.trim()).filter((l) => l.length > 5 && l.length < 200).slice(0, 3);
2754
- log10.debug({ original: query, expansions: expansions.length }, "Query expanded");
4715
+ log16.debug({ original: query, expansions: expansions.length }, "Query expanded");
2755
4716
  return [query, ...expansions];
2756
4717
  } catch (err) {
2757
- log10.debug({ err }, "Query expansion failed, using original");
4718
+ log16.debug({ err }, "Query expansion failed, using original");
2758
4719
  return [query];
2759
4720
  }
2760
4721
  }
@@ -2767,11 +4728,12 @@ async function recallMemories(opts) {
2767
4728
  let vectorScores = opts._vectorScores || /* @__PURE__ */ new Map();
2768
4729
  let primaryQueryEmbedding = null;
2769
4730
  const vectorSearchPromise = queries.length > 0 && isEmbeddingEnabled() && !opts._vectorScores ? (async () => {
4731
+ const space = activeEmbeddingSpace();
2770
4732
  const queryEmbeddings = await Promise.all(
2771
4733
  queries.map(async (q) => {
2772
4734
  const cached = getCachedEmbedding2(q);
2773
4735
  if (cached) return cached;
2774
- const emb = await generateQueryEmbedding(q);
4736
+ const emb = await generateQueryEmbeddingForSpace(space, q);
2775
4737
  if (emb) setCachedEmbedding2(q, emb);
2776
4738
  return emb;
2777
4739
  })
@@ -2779,39 +4741,69 @@ async function recallMemories(opts) {
2779
4741
  const validEmbeddings = queryEmbeddings.filter((e) => e !== null);
2780
4742
  if (validEmbeddings.length > 0) primaryQueryEmbedding = validEmbeddings[0];
2781
4743
  if (validEmbeddings.length === 0) {
2782
- log10.debug("All query embeddings returned null, using keyword-only retrieval");
4744
+ log16.debug("All query embeddings returned null, using keyword-only retrieval");
2783
4745
  return;
2784
4746
  }
2785
4747
  try {
2786
- const allSearches = validEmbeddings.map(
2787
- (emb) => Promise.resolve(db.rpc("match_memories", {
2788
- query_embedding: JSON.stringify(emb),
2789
- match_threshold: VECTOR_MATCH_THRESHOLD,
2790
- match_count: limit * (opts.skipExpansion ? 12 : 4),
2791
- filter_types: opts.memoryTypes || null,
2792
- filter_user: opts.relatedUser || null,
2793
- min_decay: minDecay,
2794
- filter_owner: getOwnerWallet() || null,
2795
- filter_tags: opts.tags && opts.tags.length > 0 ? opts.tags : null
2796
- })).then((r) => r.data || [])
2797
- );
4748
+ const allSearches = validEmbeddings.flatMap((emb) => {
4749
+ const searches = [
4750
+ Promise.resolve(db.rpc(vectorRpcName("match_memories"), {
4751
+ query_embedding: JSON.stringify(emb),
4752
+ match_threshold: VECTOR_MATCH_THRESHOLD,
4753
+ match_count: limit * (opts.skipExpansion ? 12 : 4),
4754
+ filter_types: opts.memoryTypes || null,
4755
+ filter_user: opts.relatedUser || null,
4756
+ min_decay: minDecay,
4757
+ filter_owner: getOwnerScope(),
4758
+ filter_tags: opts.tags && opts.tags.length > 0 ? opts.tags : null
4759
+ })).then((r) => r.data || [])
4760
+ ];
4761
+ if (!opts.skipExpansion) {
4762
+ searches.push(
4763
+ Promise.resolve(db.rpc(vectorRpcName("match_memory_fragments"), buildFragmentRpcArgs({
4764
+ embeddingJson: JSON.stringify(emb),
4765
+ matchThreshold: VECTOR_MATCH_THRESHOLD,
4766
+ matchCount: limit * 2,
4767
+ minDecay,
4768
+ memoryTypes: opts.memoryTypes
4769
+ }))).then((r) => r.data || [])
4770
+ );
4771
+ }
4772
+ return searches;
4773
+ });
2798
4774
  const results2 = await Promise.all(allSearches);
2799
- for (const batch of results2) {
2800
- for (const m of batch) {
2801
- const current = vectorScores.get(m.id) || 0;
2802
- vectorScores.set(m.id, Math.max(current, m.similarity));
4775
+ let fragmentHits = 0;
4776
+ if (opts.skipExpansion) {
4777
+ for (const batch of results2) {
4778
+ for (const m of batch) {
4779
+ const current = vectorScores.get(m.id) || 0;
4780
+ vectorScores.set(m.id, Math.max(current, m.similarity));
4781
+ }
4782
+ }
4783
+ } else {
4784
+ for (let i = 0; i < results2.length; i++) {
4785
+ const isFragmentBatch = i % 2 === 1;
4786
+ for (const r of results2[i]) {
4787
+ const id = isFragmentBatch ? r.memory_id : r.id;
4788
+ const sim = isFragmentBatch ? r.max_similarity : r.similarity;
4789
+ if (!id) continue;
4790
+ const current = vectorScores.get(id) || 0;
4791
+ vectorScores.set(id, Math.max(current, sim));
4792
+ if (isFragmentBatch) fragmentHits++;
4793
+ }
2803
4794
  }
2804
4795
  }
2805
- log10.debug({
4796
+ log16.debug({
2806
4797
  queryVariants: validEmbeddings.length,
2807
4798
  uniqueMemories: vectorScores.size,
4799
+ fragmentHits,
2808
4800
  fastMode: !!opts.skipExpansion
2809
4801
  }, "Vector search completed");
2810
4802
  } catch (err) {
2811
- log10.warn({ err }, "Vector search RPC failed, falling back to keyword retrieval");
4803
+ log16.warn({ err }, "Vector search RPC failed, falling back to keyword retrieval");
2812
4804
  }
2813
4805
  })() : Promise.resolve();
2814
- let importanceQuery = db.from("memories").select("*").gte("decay_factor", minDecay).not("source", "in", '("demo","demo-maas")').order("importance", { ascending: false }).order("created_at", { ascending: false }).limit(limit * 3);
4806
+ let importanceQuery = db.from("memories").select("*").gte("decay_factor", minDecay).not("source", "in", '("demo","demo-maas")').not("provider_delegated", "is", false).order("importance", { ascending: false }).order("created_at", { ascending: false }).limit(limit * 3);
2815
4807
  importanceQuery = scopeToOwner(importanceQuery);
2816
4808
  if (opts.memoryTypes && opts.memoryTypes.length > 0) {
2817
4809
  importanceQuery = importanceQuery.in("memory_type", opts.memoryTypes);
@@ -2834,7 +4826,12 @@ async function recallMemories(opts) {
2834
4826
  return await bm25SearchMemories(opts.query, {
2835
4827
  limit: limit * 2,
2836
4828
  minDecay,
2837
- filterOwner: _ownerWallet || void 0,
4829
+ // Use getOwnerWallet() (AsyncLocalStorage-aware), NOT the bare module-level
4830
+ // _ownerWallet. withOwnerWallet() sets the async context, not the module var,
4831
+ // so the bare var is null under hosted/wrapped calls and BM25 would search the
4832
+ // ENTIRE table unscoped — burying the owner's exact-match facts. This is why
4833
+ // BM25 silently never surfaced bench facts even after the RPC was fixed.
4834
+ filterOwner: getOwnerScope() || void 0,
2838
4835
  filterTypes: opts.memoryTypes || void 0,
2839
4836
  filterTags: opts.tags || void 0
2840
4837
  });
@@ -2888,7 +4885,7 @@ async function recallMemories(opts) {
2888
4885
  }
2889
4886
  }
2890
4887
  }
2891
- if (seedsAdded > 0) log10.debug({ seedsAdded, seedsTotal: knowledgeSeeds.length }, "Knowledge seeds added to candidates");
4888
+ if (seedsAdded > 0) log16.debug({ seedsAdded, seedsTotal: knowledgeSeeds.length }, "Knowledge seeds added to candidates");
2892
4889
  }
2893
4890
  }
2894
4891
  const bm25Scores = /* @__PURE__ */ new Map();
@@ -2909,18 +4906,18 @@ async function recallMemories(opts) {
2909
4906
  }
2910
4907
  }
2911
4908
  }
2912
- log10.debug({ bm25Hits: bm25Results.length, bm25New: bm25MissingIds.length }, "BM25 search added candidates");
4909
+ log16.debug({ bm25Hits: bm25Results.length, bm25New: bm25MissingIds.length }, "BM25 search added candidates");
2913
4910
  }
2914
4911
  if (error) {
2915
- log10.error({ error: error.message }, "Memory recall query failed");
4912
+ log16.error({ error: error.message }, "Memory recall query failed");
2916
4913
  return [];
2917
4914
  }
2918
- let candidates = decryptMemoryBatch(data || []);
4915
+ let candidates = await decryptMemories(data || []);
2919
4916
  if (vectorScores.size > 0) {
2920
4917
  const metadataIds = new Set(candidates.map((m) => m.id));
2921
4918
  const missingIds = [...vectorScores.keys()].filter((id) => !metadataIds.has(id));
2922
4919
  if (missingIds.length > 0) {
2923
- let vectorQuery = db.from("memories").select("*").in("id", missingIds);
4920
+ let vectorQuery = db.from("memories").select("*").in("id", missingIds).gte("decay_factor", minDecay).not("embedding", "is", null);
2924
4921
  vectorQuery = scopeToOwner(vectorQuery);
2925
4922
  if (opts.memoryTypes && opts.memoryTypes.length > 0) {
2926
4923
  vectorQuery = vectorQuery.in("memory_type", opts.memoryTypes);
@@ -2929,7 +4926,7 @@ async function recallMemories(opts) {
2929
4926
  vectorQuery = vectorQuery.overlaps("tags", opts.tags);
2930
4927
  }
2931
4928
  const { data: vectorOnly } = await vectorQuery;
2932
- if (vectorOnly) candidates = [...candidates, ...decryptMemoryBatch(vectorOnly)];
4929
+ if (vectorOnly) candidates = [...candidates, ...await decryptMemories(vectorOnly)];
2933
4930
  }
2934
4931
  }
2935
4932
  if (candidates.length === 0) return [];
@@ -2953,10 +4950,10 @@ async function recallMemories(opts) {
2953
4950
  if (entities.length > 0) {
2954
4951
  const resultIdSet = new Set(results.map((m) => m.id));
2955
4952
  for (const entity of entities) {
2956
- const entityMemories = await getMemoriesByEntity(entity.id, {
4953
+ const entityMemories = await decryptMemories(await getMemoriesByEntity(entity.id, {
2957
4954
  limit: Math.ceil(limit / 2),
2958
4955
  memoryTypes: opts.memoryTypes
2959
- });
4956
+ }));
2960
4957
  for (const mem of entityMemories) {
2961
4958
  addLinkPath(mem.id, "entity");
2962
4959
  if (!resultIdSet.has(mem.id)) {
@@ -2968,17 +4965,17 @@ async function recallMemories(opts) {
2968
4965
  }
2969
4966
  }
2970
4967
  }
2971
- log10.debug({ entities: entities.map((e) => e.name) }, "Entity-aware recall applied");
4968
+ log16.debug({ entities: entities.map((e) => e.name) }, "Entity-aware recall applied");
2972
4969
  let cooccurrenceAdded = 0;
2973
4970
  const cooccurrenceNames = [];
2974
4971
  for (const entity of entities) {
2975
4972
  const cooccurrences = await getEntityCooccurrences(entity.id, { minCooccurrence: 2, maxResults: 3 });
2976
4973
  for (const cooc of cooccurrences) {
2977
4974
  if (cooccurrenceAdded >= limit) break;
2978
- const coMems = await getMemoriesByEntity(cooc.related_entity_id, {
4975
+ const coMems = await decryptMemories(await getMemoriesByEntity(cooc.related_entity_id, {
2979
4976
  limit: 3,
2980
4977
  memoryTypes: opts.memoryTypes
2981
- });
4978
+ }));
2982
4979
  for (const mem of coMems) {
2983
4980
  if (cooccurrenceAdded >= limit) break;
2984
4981
  addLinkPath(mem.id, "entity");
@@ -2998,11 +4995,11 @@ async function recallMemories(opts) {
2998
4995
  }
2999
4996
  }
3000
4997
  if (cooccurrenceAdded > 0) {
3001
- log10.debug({ cooccurrenceAdded, cooccurrenceEntities: cooccurrenceNames.length }, "Entity co-occurrence recall applied");
4998
+ log16.debug({ cooccurrenceAdded, cooccurrenceEntities: cooccurrenceNames.length }, "Entity co-occurrence recall applied");
3002
4999
  }
3003
5000
  }
3004
5001
  } catch (err) {
3005
- log10.debug({ err }, "Entity-aware recall skipped");
5002
+ log16.debug({ err }, "Entity-aware recall skipped");
3006
5003
  }
3007
5004
  }
3008
5005
  if (results.length > 0) {
@@ -3012,17 +5009,17 @@ async function recallMemories(opts) {
3012
5009
  seed_ids: seedIds,
3013
5010
  min_strength: 0.2,
3014
5011
  max_results: limit,
3015
- filter_owner: getOwnerWallet() || null
5012
+ filter_owner: getOwnerScope()
3016
5013
  });
3017
5014
  if (linked && linked.length > 0) {
3018
5015
  const resultIdSet = new Set(seedIds);
3019
5016
  const graphCandidateIds = linked.filter((l) => !resultIdSet.has(l.memory_id)).map((l) => l.memory_id);
3020
5017
  if (graphCandidateIds.length > 0) {
3021
- let graphQuery = db.from("memories").select("*").in("id", graphCandidateIds);
5018
+ let graphQuery = db.from("memories").select("*").in("id", graphCandidateIds).not("provider_delegated", "is", false);
3022
5019
  graphQuery = scopeToOwner(graphQuery);
3023
5020
  const { data: graphMemories } = await graphQuery;
3024
5021
  if (graphMemories && graphMemories.length > 0) {
3025
- decryptMemoryBatch(graphMemories);
5022
+ await decryptMemories(graphMemories);
3026
5023
  const linkBoostMap = /* @__PURE__ */ new Map();
3027
5024
  for (const l of linked) {
3028
5025
  const bondWeight = BOND_TYPE_WEIGHTS[l.link_type] ?? 0.4;
@@ -3036,7 +5033,7 @@ async function recallMemories(opts) {
3036
5033
  _score: scoreMemory(mem, scoredOpts) + RETRIEVAL_WEIGHT_GRAPH * (linkBoostMap.get(mem.id) || 0)
3037
5034
  }));
3038
5035
  results = [...results, ...graphScored].sort((a, b) => b._score - a._score).slice(0, limit);
3039
- log10.debug({
5036
+ log16.debug({
3040
5037
  graphExpanded: graphMemories.length,
3041
5038
  linkedTotal: linked.length,
3042
5039
  bondTypes: [...new Set(linked.map((l) => l.link_type))]
@@ -3045,7 +5042,7 @@ async function recallMemories(opts) {
3045
5042
  }
3046
5043
  }
3047
5044
  } catch (err) {
3048
- log10.debug({ err }, "Graph expansion skipped (RPC unavailable)");
5045
+ log16.debug({ err }, "Graph expansion skipped (RPC unavailable)");
3049
5046
  }
3050
5047
  }
3051
5048
  if (results.length >= 3) {
@@ -3061,23 +5058,23 @@ async function recallMemories(opts) {
3061
5058
  ...results.slice(0, results.length - replaceCount),
3062
5059
  ...diverseCandidates.slice(0, replaceCount)
3063
5060
  ].sort((a, b) => b._score - a._score).slice(0, limit);
3064
- log10.debug({ injectedTypes: diverseCandidates.map((m) => m.memory_type) }, "Type diversity applied");
5061
+ log16.debug({ injectedTypes: diverseCandidates.map((m) => m.memory_type) }, "Type diversity applied");
3065
5062
  }
3066
5063
  }
3067
5064
  }
3068
- const finalOwner = getOwnerWallet();
3069
- if (finalOwner) {
3070
- const beforeCount = results.length;
3071
- results = results.filter((m) => m.owner_wallet === finalOwner);
3072
- if (results.length < beforeCount) {
3073
- log10.warn({ stripped: beforeCount - results.length, owner: finalOwner }, "Owner guard stripped foreign memories from recall results");
5065
+ const finalScope = getOwnerScope();
5066
+ {
5067
+ const { kept, stripped } = applyOwnerPostGuard(results, finalScope);
5068
+ if (stripped > 0) {
5069
+ log16.warn({ stripped, botOwn: finalScope === SCOPE_BOT_OWN2 }, "Owner guard stripped foreign memories from recall results");
3074
5070
  }
5071
+ results = kept;
3075
5072
  }
3076
- if (opts.trackAccess !== false) {
5073
+ if (shouldTrackAccess(opts)) {
3077
5074
  const ids = results.map((m) => m.id);
3078
5075
  const sources = results.map((m) => m.source || "");
3079
- updateMemoryAccess(ids, sources).catch((err) => log10.warn({ err }, "Memory access tracking failed"));
3080
- reinforceCoRetrievedLinks(ids).catch((err) => log10.debug({ err }, "Link reinforcement failed"));
5076
+ updateMemoryAccess(ids, sources).catch((err) => log16.warn({ err }, "Memory access tracking failed"));
5077
+ reinforceCoRetrievedLinks(ids).catch((err) => log16.debug({ err }, "Link reinforcement failed"));
3081
5078
  }
3082
5079
  for (const m of results) {
3083
5080
  const paths = linkPathMap.get(m.id);
@@ -3085,7 +5082,7 @@ async function recallMemories(opts) {
3085
5082
  m.link_path = [...paths];
3086
5083
  }
3087
5084
  }
3088
- log10.debug({
5085
+ log16.debug({
3089
5086
  recalled: results.length,
3090
5087
  topScore: results[0]?._score?.toFixed(3),
3091
5088
  query: opts.query?.slice(0, 40),
@@ -3095,7 +5092,7 @@ async function recallMemories(opts) {
3095
5092
  }, "Memories recalled");
3096
5093
  return results;
3097
5094
  } catch (err) {
3098
- log10.error({ err }, "Memory recall failed");
5095
+ log16.error({ err }, "Memory recall failed");
3099
5096
  return [];
3100
5097
  }
3101
5098
  }
@@ -3109,7 +5106,7 @@ function extractQueryTerms(query) {
3109
5106
  }
3110
5107
  function scoreMemory(mem, opts) {
3111
5108
  const now = Date.now();
3112
- const hoursSinceAccess = (now - new Date(mem.last_accessed).getTime()) / (1e3 * 60 * 60);
5109
+ const hoursSinceAccess = Math.max(0, (now - new Date(mem.last_accessed).getTime()) / (1e3 * 60 * 60));
3113
5110
  const recency = Math.pow(RECENCY_DECAY_BASE, hoursSinceAccess);
3114
5111
  let textScore = 0.5;
3115
5112
  if (opts.query) {
@@ -3145,7 +5142,7 @@ function scoreMemory(mem, opts) {
3145
5142
  }
3146
5143
  const bm25Rank = opts._bm25Scores?.get(mem.id) || 0;
3147
5144
  if (bm25Rank > 0) {
3148
- rawScore += 0.15 * Math.min(bm25Rank, 1);
5145
+ rawScore += RETRIEVAL_WEIGHT_BM25 * Math.min(bm25Rank, 1);
3149
5146
  }
3150
5147
  const typeBoost = KNOWLEDGE_TYPE_BOOST[mem.memory_type] || 0;
3151
5148
  rawScore += typeBoost;
@@ -3166,7 +5163,7 @@ async function recallMemorySummaries(opts) {
3166
5163
  const limit = opts.limit || 10;
3167
5164
  const minDecay = opts.minDecay ?? 0.1;
3168
5165
  try {
3169
- let query = db.from("memories").select("id, memory_type, summary, tags, concepts, importance, decay_factor, created_at, source").gte("decay_factor", minDecay).not("source", "in", '("demo","demo-maas")').order("importance", { ascending: false }).order("created_at", { ascending: false }).limit(limit);
5166
+ let query = db.from("memories").select("id, memory_type, summary, tags, concepts, importance, decay_factor, created_at, source").gte("decay_factor", minDecay).not("source", "in", '("demo","demo-maas")').not("provider_delegated", "is", false).order("importance", { ascending: false }).order("created_at", { ascending: false }).limit(limit);
3170
5167
  query = scopeToOwner(query);
3171
5168
  if (opts.memoryTypes && opts.memoryTypes.length > 0) {
3172
5169
  query = query.in("memory_type", opts.memoryTypes);
@@ -3185,12 +5182,12 @@ async function recallMemorySummaries(opts) {
3185
5182
  }
3186
5183
  const { data, error } = await query;
3187
5184
  if (error) {
3188
- log10.error({ error: error.message }, "Memory summary recall failed");
5185
+ log16.error({ error: error.message }, "Memory summary recall failed");
3189
5186
  return [];
3190
5187
  }
3191
5188
  return data || [];
3192
5189
  } catch (err) {
3193
- log10.error({ err }, "Memory summary recall failed");
5190
+ log16.error({ err }, "Memory summary recall failed");
3194
5191
  return [];
3195
5192
  }
3196
5193
  }
@@ -3202,28 +5199,23 @@ async function hydrateMemories(ids) {
3202
5199
  query = scopeToOwner(query);
3203
5200
  const { data, error } = await query;
3204
5201
  if (error) {
3205
- log10.error({ error: error.message }, "Memory hydration failed");
5202
+ log16.error({ error: error.message }, "Memory hydration failed");
3206
5203
  return [];
3207
5204
  }
3208
- return decryptMemoryBatch(data || []);
5205
+ return await decryptMemories(data || []);
3209
5206
  } catch (err) {
3210
- log10.error({ err }, "Memory hydration failed");
5207
+ log16.error({ err }, "Memory hydration failed");
3211
5208
  return [];
3212
5209
  }
3213
5210
  }
3214
- async function updateMemoryAccess(ids, sources = []) {
5211
+ async function updateMemoryAccess(ids, _sources = []) {
3215
5212
  if (ids.length === 0) return;
3216
5213
  const db = getDb();
3217
- const importanceBoosts = ids.map((_, i) => {
3218
- const source = sources[i] || "";
3219
- return INTERNAL_MEMORY_SOURCES.has(source) ? INTERNAL_IMPORTANCE_BOOST : 0.02;
3220
- });
3221
5214
  const { error } = await db.rpc("batch_boost_memory_access", {
3222
- memory_ids: ids,
3223
- importance_boosts: importanceBoosts
5215
+ memory_ids: ids
3224
5216
  });
3225
5217
  if (error) {
3226
- log10.warn({ error: error.message, ids }, "Batch memory access update failed");
5218
+ log16.warn({ error: error.message, ids }, "Batch memory access update failed");
3227
5219
  }
3228
5220
  }
3229
5221
  async function createMemoryLink(sourceId, targetId, linkType, strength = 0.5) {
@@ -3236,7 +5228,7 @@ async function createMemoryLink(sourceId, targetId, linkType, strength = 0.5) {
3236
5228
  strength: clamp(strength, 0, 1)
3237
5229
  }, { onConflict: "source_id,target_id,link_type" });
3238
5230
  if (error) {
3239
- log10.debug({ error: error.message, sourceId, targetId, linkType }, "Link creation failed");
5231
+ log16.debug({ error: error.message, sourceId, targetId, linkType }, "Link creation failed");
3240
5232
  }
3241
5233
  }
3242
5234
  async function createMemoryLinksBatch(links) {
@@ -3258,7 +5250,7 @@ async function createMemoryLinksBatch(links) {
3258
5250
  const db = getDb();
3259
5251
  const { error } = await db.from("memory_links").upsert(rows, { onConflict: "source_id,target_id,link_type" });
3260
5252
  if (error) {
3261
- log10.debug({ error: error.message, count: rows.length }, "Batch link creation failed");
5253
+ log16.debug({ error: error.message, count: rows.length }, "Batch link creation failed");
3262
5254
  }
3263
5255
  }
3264
5256
  async function autoLinkMemory(memoryId, opts) {
@@ -3277,7 +5269,7 @@ async function autoLinkMemory(memoryId, opts) {
3277
5269
  query_embedding: JSON.stringify(embedding),
3278
5270
  match_threshold: LINK_SIMILARITY_THRESHOLD,
3279
5271
  match_count: MAX_AUTO_LINKS * 2,
3280
- filter_owner: getOwnerWallet() || null
5272
+ filter_owner: getOwnerScope()
3281
5273
  });
3282
5274
  if (similar) {
3283
5275
  const similarIds = similar.map((s) => s.id).filter((id) => id !== memoryId);
@@ -3326,7 +5318,7 @@ async function autoLinkMemory(memoryId, opts) {
3326
5318
  }
3327
5319
  if (pendingLinks.length > 0) {
3328
5320
  await createMemoryLinksBatch(pendingLinks);
3329
- log10.debug({ memoryId, linksCreated: pendingLinks.length }, "Auto-linked memory");
5321
+ log16.debug({ memoryId, linksCreated: pendingLinks.length }, "Auto-linked memory");
3330
5322
  }
3331
5323
  }
3332
5324
  function classifyLinkType(newMem, candidate, newConcepts) {
@@ -3348,16 +5340,16 @@ async function reinforceCoRetrievedLinks(ids) {
3348
5340
  boost_amount: LINK_CO_RETRIEVAL_BOOST
3349
5341
  });
3350
5342
  if (error) {
3351
- log10.debug({ error: error.message }, "Link reinforcement RPC failed");
5343
+ log16.debug({ error: error.message }, "Link reinforcement RPC failed");
3352
5344
  } else if (data && data > 0) {
3353
- log10.debug({ boosted: data }, "Co-retrieval link reinforcement applied");
5345
+ log16.debug({ boosted: data }, "Co-retrieval link reinforcement applied");
3354
5346
  }
3355
5347
  }
3356
5348
  async function extractAndLinkEntitiesForMemory(memoryId, opts) {
3357
5349
  try {
3358
5350
  await extractAndLinkEntities(memoryId, opts.content, opts.summary, opts.relatedUser);
3359
5351
  } catch (err) {
3360
- log10.debug({ err, memoryId }, "Entity extraction failed");
5352
+ log16.debug({ err, memoryId }, "Entity extraction failed");
3361
5353
  }
3362
5354
  }
3363
5355
  async function decayMemories() {
@@ -3373,17 +5365,17 @@ async function decayMemories() {
3373
5365
  cutoff
3374
5366
  });
3375
5367
  if (error) {
3376
- log10.warn({ error: error.message, memType }, "Batch decay failed for type");
5368
+ log16.warn({ error: error.message, memType }, "Batch decay failed for type");
3377
5369
  continue;
3378
5370
  }
3379
5371
  totalDecayed += data || 0;
3380
5372
  }
3381
5373
  if (totalDecayed > 0) {
3382
- log10.info({ decayed: totalDecayed }, "Type-specific memory decay applied");
5374
+ log16.info({ decayed: totalDecayed }, "Type-specific memory decay applied");
3383
5375
  }
3384
5376
  return totalDecayed;
3385
5377
  } catch (err) {
3386
- log10.error({ err }, "Memory decay failed");
5378
+ log16.error({ err }, "Memory decay failed");
3387
5379
  return 0;
3388
5380
  }
3389
5381
  }
@@ -3393,7 +5385,7 @@ async function deleteMemory(id) {
3393
5385
  query = scopeToOwner(query);
3394
5386
  const { error } = await query;
3395
5387
  if (error) {
3396
- log10.error({ error: error.message, id }, "Failed to delete memory");
5388
+ log16.error({ error: error.message, id }, "Failed to delete memory");
3397
5389
  return false;
3398
5390
  }
3399
5391
  return true;
@@ -3411,7 +5403,7 @@ async function updateMemory(id, patches) {
3411
5403
  query = scopeToOwner(query);
3412
5404
  const { error } = await query;
3413
5405
  if (error) {
3414
- log10.error({ error: error.message, id }, "Failed to update memory");
5406
+ log16.error({ error: error.message, id }, "Failed to update memory");
3415
5407
  return false;
3416
5408
  }
3417
5409
  return true;
@@ -3432,10 +5424,10 @@ async function listMemories(opts) {
3432
5424
  if (opts.min_importance !== void 0) dataQ = dataQ.gte("importance", opts.min_importance);
3433
5425
  const { data, error } = await dataQ;
3434
5426
  if (error) {
3435
- log10.error({ error: error.message }, "Failed to list memories");
5427
+ log16.error({ error: error.message }, "Failed to list memories");
3436
5428
  return { memories: [], total: 0 };
3437
5429
  }
3438
- return { memories: decryptMemoryBatch(data || []), total: count ?? 0 };
5430
+ return { memories: await decryptMemories(data || []), total: count ?? 0 };
3439
5431
  }
3440
5432
  async function getMemoryStats() {
3441
5433
  const db = getDb();
@@ -3453,25 +5445,31 @@ async function getMemoryStats() {
3453
5445
  embeddedCount: 0
3454
5446
  };
3455
5447
  try {
3456
- let countQuery = db.from("memories").select("id", { count: "exact", head: true }).gt("decay_factor", MEMORY_MIN_DECAY);
5448
+ let countQuery = db.from("memories").select("id", { count: "exact", head: true });
3457
5449
  countQuery = scopeToOwner(countQuery);
3458
5450
  const { count: totalCount } = await countQuery;
3459
5451
  stats.total = totalCount || 0;
3460
- let embeddedQuery = db.from("memories").select("id", { count: "exact", head: true }).gt("decay_factor", MEMORY_MIN_DECAY).not("embedding", "is", null);
5452
+ let embeddedQuery = db.from("memories").select("id", { count: "exact", head: true }).not("embedding", "is", null);
3461
5453
  embeddedQuery = scopeToOwner(embeddedQuery);
3462
5454
  const { count: embCount } = await embeddedQuery;
3463
5455
  stats.embeddedCount = embCount || 0;
3464
- const PAGE_SIZE = 5e3;
5456
+ const TYPES = ["episodic", "semantic", "procedural", "self_model", "introspective"];
5457
+ await Promise.all(TYPES.map(async (type) => {
5458
+ let q = db.from("memories").select("id", { count: "exact", head: true }).eq("memory_type", type);
5459
+ q = scopeToOwner(q);
5460
+ const { count: count2 } = await q;
5461
+ stats.byType[type] = count2 || 0;
5462
+ }));
5463
+ const PAGE_SIZE = 1e3;
5464
+ const MAX_PAGES = 20;
3465
5465
  let allMemories = [];
3466
- let page = 0;
3467
- while (true) {
3468
- let pageQuery = db.from("memories").select("memory_type, importance, decay_factor, created_at, related_user, tags, concepts").gt("decay_factor", MEMORY_MIN_DECAY).range(page * PAGE_SIZE, (page + 1) * PAGE_SIZE - 1);
5466
+ for (let page = 0; page < MAX_PAGES; page++) {
5467
+ let pageQuery = db.from("memories").select("importance, decay_factor, created_at, related_user, owner_wallet, tags, concepts").order("id", { ascending: true }).range(page * PAGE_SIZE, (page + 1) * PAGE_SIZE - 1);
3469
5468
  pageQuery = scopeToOwner(pageQuery);
3470
5469
  const { data: pageData } = await pageQuery;
3471
5470
  if (!pageData || pageData.length === 0) break;
3472
5471
  allMemories = allMemories.concat(pageData);
3473
5472
  if (pageData.length < PAGE_SIZE) break;
3474
- page++;
3475
5473
  }
3476
5474
  if (allMemories.length > 0) {
3477
5475
  let impSum = 0;
@@ -3480,11 +5478,10 @@ async function getMemoryStats() {
3480
5478
  const conceptCounts = {};
3481
5479
  const users = /* @__PURE__ */ new Set();
3482
5480
  for (const m of allMemories) {
3483
- const type = m.memory_type;
3484
- if (type in stats.byType) stats.byType[type]++;
3485
5481
  impSum += m.importance;
3486
5482
  decaySum += m.decay_factor;
3487
5483
  if (m.related_user) users.add(m.related_user);
5484
+ if (m.owner_wallet) users.add(m.owner_wallet);
3488
5485
  if (m.tags) {
3489
5486
  for (const tag of m.tags) {
3490
5487
  tagCounts[tag] = (tagCounts[tag] || 0) + 1;
@@ -3505,13 +5502,15 @@ async function getMemoryStats() {
3505
5502
  stats.oldestMemory = sorted[0] || null;
3506
5503
  stats.newestMemory = sorted[sorted.length - 1] || null;
3507
5504
  }
3508
- const { count, error: dreamError } = await db.from("dream_logs").select("id", { count: "exact", head: true });
5505
+ let dreamQuery = db.from("dream_logs").select("id", { count: "exact", head: true });
5506
+ dreamQuery = scopeToOwner(dreamQuery);
5507
+ const { count, error: dreamError } = await dreamQuery;
3509
5508
  if (dreamError) {
3510
- log10.warn({ error: dreamError.message }, "Failed to count dream logs");
5509
+ log16.warn({ error: dreamError.message }, "Failed to count dream logs");
3511
5510
  }
3512
5511
  stats.totalDreamSessions = count || 0;
3513
5512
  } catch (err) {
3514
- log10.error({ err }, "Failed to get memory stats");
5513
+ log16.error({ err }, "Failed to get memory stats");
3515
5514
  }
3516
5515
  return stats;
3517
5516
  }
@@ -3525,10 +5524,10 @@ async function getRecentMemories(hours, types, limit) {
3525
5524
  }
3526
5525
  const { data, error } = await query;
3527
5526
  if (error) {
3528
- log10.error({ error: error.message }, "Failed to get recent memories");
5527
+ log16.error({ error: error.message }, "Failed to get recent memories");
3529
5528
  return [];
3530
5529
  }
3531
- return decryptMemoryBatch(data || []);
5530
+ return await decryptMemories(data || []);
3532
5531
  }
3533
5532
  async function getSelfModel() {
3534
5533
  const db = getDb();
@@ -3536,10 +5535,10 @@ async function getSelfModel() {
3536
5535
  query = scopeToOwner(query);
3537
5536
  const { data, error } = await query;
3538
5537
  if (error) {
3539
- log10.error({ error: error.message }, "Failed to get self model");
5538
+ log16.error({ error: error.message }, "Failed to get self model");
3540
5539
  return [];
3541
5540
  }
3542
- return decryptMemoryBatch(data || []);
5541
+ return await decryptMemories(data || []);
3543
5542
  }
3544
5543
  async function storeDreamLog(sessionType, inputMemoryIds, output, newMemoryIds) {
3545
5544
  const db = getDb();
@@ -3547,54 +5546,48 @@ async function storeDreamLog(sessionType, inputMemoryIds, output, newMemoryIds)
3547
5546
  session_type: sessionType,
3548
5547
  input_memory_ids: inputMemoryIds,
3549
5548
  output: output.slice(0, MEMORY_MAX_CONTENT_LENGTH),
3550
- new_memories_created: newMemoryIds
5549
+ new_memories_created: newMemoryIds,
5550
+ // Stamp owner so getMemoryStats can scope the dream count (migration 021).
5551
+ owner_wallet: getOwnerWallet() === SCOPE_BOT_OWN2 ? null : getOwnerWallet()
3551
5552
  });
3552
5553
  if (error) {
3553
- log10.error({ error: error.message }, "Failed to store dream log");
5554
+ log16.error({ error: error.message }, "Failed to store dream log");
3554
5555
  }
3555
5556
  }
3556
5557
  function formatMemoryContext(memories) {
3557
5558
  if (memories.length === 0) return "";
3558
5559
  const lines = ["## Memory Recall"];
3559
- const episodic = memories.filter((m) => m.memory_type === "episodic");
3560
- const semantic = memories.filter((m) => m.memory_type === "semantic");
3561
- const procedural = memories.filter((m) => m.memory_type === "procedural");
3562
- const selfModel = memories.filter((m) => m.memory_type === "self_model");
3563
- const introspective = memories.filter((m) => m.memory_type === "introspective");
5560
+ const episodic = memories.filter((m) => m.memory_type === "episodic").sort(byMemoryDateAsc);
5561
+ const semantic = memories.filter((m) => m.memory_type === "semantic").sort(byMemoryDateAsc);
5562
+ const procedural = memories.filter((m) => m.memory_type === "procedural").sort(byMemoryDateAsc);
5563
+ const selfModel = memories.filter((m) => m.memory_type === "self_model").sort(byMemoryDateAsc);
5564
+ const introspective = memories.filter((m) => m.memory_type === "introspective").sort(byMemoryDateAsc);
3564
5565
  if (episodic.length > 0) {
3565
5566
  lines.push("### Past Interactions");
3566
- for (const m of episodic) {
3567
- lines.push(`- [${timeAgo(m.created_at)}] ${m.summary}`);
3568
- }
5567
+ for (const m of episodic) lines.push(renderGroundedLine(m));
3569
5568
  }
3570
5569
  if (semantic.length > 0) {
3571
5570
  lines.push("### Things You Know");
3572
- for (const m of semantic) {
3573
- lines.push(`- ${m.summary}`);
3574
- }
5571
+ for (const m of semantic) lines.push(renderGroundedLine(m));
3575
5572
  }
3576
5573
  if (procedural.length > 0) {
3577
5574
  lines.push("### Learned Strategies (from past outcomes)");
3578
5575
  for (const m of procedural) {
3579
5576
  const meta = m.metadata;
3580
5577
  const confidence = meta?.positiveRate != null ? ` [${Math.round(meta.positiveRate * 100)}% success rate, based on ${meta.basedOn || "?"} interactions]` : "";
3581
- lines.push(`- ${m.summary}${confidence}`);
5578
+ lines.push(renderGroundedLine(m, confidence));
3582
5579
  }
3583
5580
  }
3584
5581
  if (introspective.length > 0) {
3585
5582
  lines.push("### Your Own Reflections");
3586
- for (const m of introspective) {
3587
- lines.push(`- [${timeAgo(m.created_at)}] ${m.summary}`);
3588
- }
5583
+ for (const m of introspective) lines.push(renderGroundedLine(m));
3589
5584
  }
3590
5585
  if (selfModel.length > 0) {
3591
5586
  lines.push("### Self-Observations");
3592
- for (const m of selfModel) {
3593
- lines.push(`- ${m.summary}`);
3594
- }
5587
+ for (const m of selfModel) lines.push(renderGroundedLine(m));
3595
5588
  }
3596
5589
  lines.push("");
3597
- lines.push("You REMEMBER these interactions and facts. Reference them naturally if relevant.");
5590
+ lines.push(CONTEXT_GROUNDING_RULES);
3598
5591
  if (procedural.length > 0) {
3599
5592
  lines.push("");
3600
5593
  lines.push("IMPORTANT: You MUST follow the Learned Strategies above. They are behavioral rules you derived from analyzing your own past successes and failures. Apply them to this response.");
@@ -3619,10 +5612,10 @@ async function scoreImportanceWithLLM(description, fallbackOpts) {
3619
5612
  if (!isNaN(parsed) && parsed >= 1 && parsed <= 10) {
3620
5613
  return parsed / 10;
3621
5614
  }
3622
- log10.warn({ response }, "LLM importance score unparseable, using fallback");
5615
+ log16.warn({ response }, "LLM importance score unparseable, using fallback");
3623
5616
  return calculateImportance(fallbackOpts || {});
3624
5617
  } catch (err) {
3625
- log10.warn({ err }, "LLM importance scoring failed, using fallback");
5618
+ log16.warn({ err }, "LLM importance scoring failed, using fallback");
3626
5619
  return calculateImportance(fallbackOpts || {});
3627
5620
  }
3628
5621
  }
@@ -3646,7 +5639,8 @@ async function matchByEmbedding(opts) {
3646
5639
  query_embedding: JSON.stringify(opts.embedding),
3647
5640
  match_threshold: opts.threshold,
3648
5641
  match_count: opts.limit,
3649
- filter_owner: opts.ownerWallet ?? null
5642
+ // Explicit ownerWallet wins; otherwise the ambient scope (sentinel-aware, C0).
5643
+ filter_owner: opts.ownerWallet ?? getOwnerScope()
3650
5644
  });
3651
5645
  return (data ?? []).map((r) => ({
3652
5646
  id: r.id,
@@ -3669,32 +5663,52 @@ function moodToValence(mood) {
3669
5663
  return 0;
3670
5664
  }
3671
5665
  }
3672
- var import_crypto2, EMBED_CACHE_MAX, embeddingCache, _ownerWallet, SCOPE_BOT_OWN, HASH_ID_PREFIX, log10, DEDUP_TTL_MS, dedupCache, STOPWORDS;
5666
+ var import_crypto3, EMBED_CACHE_MAX, embeddingCache, _ownerWallet, SCOPE_BOT_OWN2, HASH_ID_PREFIX, log16, DEDUP_TTL_MS, dedupCache, installedPacksCache, INSTALLED_PACKS_TTL_MS, topicEmbeddingCache, topicEmbeddingPopulationLock, TOKENISATION_SKIP_SOURCES, outboxDegradeLogged, STOPWORDS, CONTEXT_GROUNDING_RULES;
3673
5667
  var init_memory = __esm({
3674
5668
  "packages/brain/src/memory/memory.ts"() {
3675
5669
  "use strict";
3676
5670
  init_database();
5671
+ init_config();
3677
5672
  init_logger();
5673
+ init_memory_grounding();
3678
5674
  init_utils();
3679
5675
  init_claude_client();
3680
5676
  init_solana_client();
5677
+ init_src();
3681
5678
  init_embeddings();
5679
+ init_migration_profile();
5680
+ init_wiki_packs();
3682
5681
  init_config2();
3683
5682
  init_bm25_search();
5683
+ init_content_tokens();
5684
+ init_reconcile_shadow();
5685
+ init_memory_encryption();
3684
5686
  init_openrouter_client();
3685
5687
  init_encryption();
5688
+ init_memory_decryption();
3686
5689
  init_event_bus();
3687
- import_crypto2 = require("crypto");
5690
+ import_crypto3 = require("crypto");
3688
5691
  init_graph();
3689
5692
  init_owner_context();
3690
5693
  EMBED_CACHE_MAX = 200;
3691
5694
  embeddingCache = /* @__PURE__ */ new Map();
3692
5695
  _ownerWallet = null;
3693
- SCOPE_BOT_OWN = "__BOT_OWN__";
5696
+ SCOPE_BOT_OWN2 = "__BOT_OWN__";
3694
5697
  HASH_ID_PREFIX = "clude";
3695
- log10 = createChildLogger("memory");
5698
+ log16 = createChildLogger("memory");
3696
5699
  DEDUP_TTL_MS = 10 * 60 * 1e3;
3697
5700
  dedupCache = /* @__PURE__ */ new Map();
5701
+ installedPacksCache = /* @__PURE__ */ new Map();
5702
+ INSTALLED_PACKS_TTL_MS = 6e4;
5703
+ topicEmbeddingCache = /* @__PURE__ */ new Map();
5704
+ topicEmbeddingPopulationLock = null;
5705
+ TOKENISATION_SKIP_SOURCES = /* @__PURE__ */ new Set([
5706
+ "demo",
5707
+ "demo-maas",
5708
+ "locomo-benchmark",
5709
+ "longmemeval-benchmark"
5710
+ ]);
5711
+ outboxDegradeLogged = false;
3698
5712
  STOPWORDS = /* @__PURE__ */ new Set([
3699
5713
  "the",
3700
5714
  "a",
@@ -3779,18 +5793,92 @@ var init_memory = __esm({
3779
5793
  "our",
3780
5794
  "their"
3781
5795
  ]);
5796
+ CONTEXT_GROUNDING_RULES = `Ground specific claims \u2014 facts, preferences, history, dates, numbers, names \u2014 in the memories above; if a detail isn't written here, say you don't remember it rather than guessing. Each line is dated: when two memories disagree, read them as a timeline \u2014 use the most recent value for a question about now, and the value that was in effect at the time for a question about a specific past date ("as of \u2026"); never blend conflicting values. Don't invent memories or details that aren't above. General knowledge is unaffected \u2014 answer those normally.`;
5797
+ }
5798
+ });
5799
+
5800
+ // packages/brain/src/memory/memory-revoke.ts
5801
+ async function revokeMemory(db, memoryId) {
5802
+ const { data: mem } = await db.from("memories").select("summary, embedding, encrypted, provider_delegated").eq("id", memoryId).maybeSingle();
5803
+ if (!mem || mem.encrypted !== true || mem.provider_delegated === false) {
5804
+ return { revoked: false, reason: "not_delegated" };
5805
+ }
5806
+ const { data: wrap } = await db.from("memory_dek_wraps").select("wrapped_dek, wrap_pubkey").eq("memory_id", memoryId).eq("recipient", "provider").maybeSingle();
5807
+ if (!wrap) return { revoked: false, reason: "no_provider_wrap" };
5808
+ let providerSecret;
5809
+ try {
5810
+ providerSecret = loadProviderKeypair().secretKey;
5811
+ } catch {
5812
+ return { revoked: false, reason: "no_provider_key" };
5813
+ }
5814
+ const dek = unwrapDek(wrap.wrapped_dek, wrap.wrap_pubkey, providerSecret);
5815
+ if (!dek) return { revoked: false, reason: "unwrap_failed" };
5816
+ const summaryCt = encryptField(String(mem.summary ?? ""), dek);
5817
+ const embeddingCt = mem.embedding != null ? encryptField(String(mem.embedding), dek) : "";
5818
+ const { error } = await db.rpc("revoke_memory", {
5819
+ p_memory_id: memoryId,
5820
+ p_summary_ct: summaryCt,
5821
+ p_embedding_ct: embeddingCt
5822
+ });
5823
+ if (error) {
5824
+ log17.error({ memoryId, error: error.message }, "revoke_memory RPC failed");
5825
+ return { revoked: false, reason: "rpc_failed" };
5826
+ }
5827
+ log17.info({ memoryId }, "Memory revoked \u2014 provider access destroyed");
5828
+ return { revoked: true };
5829
+ }
5830
+ async function redelegateMemory(db, memoryId, providerWrap) {
5831
+ const { data: mem } = await db.from("memories").select("content, summary_ciphertext, embedding_ciphertext, encrypted").eq("id", memoryId).maybeSingle();
5832
+ if (!mem || mem.encrypted !== true) return { redelegated: false, reason: "not_encrypted" };
5833
+ let providerSecret;
5834
+ try {
5835
+ providerSecret = loadProviderKeypair().secretKey;
5836
+ } catch {
5837
+ return { redelegated: false, reason: "no_provider_key" };
5838
+ }
5839
+ const dek = unwrapDek(providerWrap.wrapped_dek, providerWrap.wrap_pubkey, providerSecret);
5840
+ if (!dek) return { redelegated: false, reason: "invalid_wrap" };
5841
+ const content = decryptField(String(mem.content ?? ""), dek);
5842
+ if (content === null) return { redelegated: false, reason: "invalid_wrap" };
5843
+ const summary = mem.summary_ciphertext ? decryptField(mem.summary_ciphertext, dek) : "";
5844
+ const embedding = mem.embedding_ciphertext ? decryptField(mem.embedding_ciphertext, dek) : "";
5845
+ const { error } = await db.rpc("redelegate_memory", {
5846
+ p_memory_id: memoryId,
5847
+ p_summary: summary ?? "",
5848
+ p_embedding: embedding ?? "",
5849
+ p_content: content,
5850
+ p_wrapped_dek: providerWrap.wrapped_dek,
5851
+ p_wrap_pubkey: providerWrap.wrap_pubkey
5852
+ });
5853
+ if (error) {
5854
+ log17.error({ memoryId, error: error.message }, "redelegate_memory RPC failed");
5855
+ return { redelegated: false, reason: "rpc_failed" };
5856
+ }
5857
+ log17.info({ memoryId }, "Memory re-delegated \u2014 provider access restored");
5858
+ return { redelegated: true };
5859
+ }
5860
+ var log17;
5861
+ var init_memory_revoke = __esm({
5862
+ "packages/brain/src/memory/memory-revoke.ts"() {
5863
+ "use strict";
5864
+ init_memory_envelope();
5865
+ init_encryption_keys();
5866
+ init_logger();
5867
+ log17 = createChildLogger("memory-revoke");
3782
5868
  }
3783
5869
  });
3784
5870
 
3785
5871
  // packages/brain/src/memory/index.ts
3786
5872
  var memory_exports2 = {};
3787
5873
  __export(memory_exports2, {
3788
- SCOPE_BOT_OWN: () => SCOPE_BOT_OWN,
5874
+ SCOPE_BOT_OWN: () => SCOPE_BOT_OWN2,
3789
5875
  _setOwnerWallet: () => _setOwnerWallet,
3790
5876
  calculateImportance: () => calculateImportance,
3791
5877
  createMemoryLink: () => createMemoryLink,
3792
5878
  createMemoryLinksBatch: () => createMemoryLinksBatch,
3793
5879
  decayMemories: () => decayMemories,
5880
+ decryptMemories: () => decryptMemories,
5881
+ decryptOneContent: () => decryptOneContent,
3794
5882
  deleteMemory: () => deleteMemory,
3795
5883
  formatMemoryContext: () => formatMemoryContext,
3796
5884
  generateHashId: () => generateHashId,
@@ -3800,11 +5888,14 @@ __export(memory_exports2, {
3800
5888
  getSelfModel: () => getSelfModel,
3801
5889
  hydrateMemories: () => hydrateMemories,
3802
5890
  inferConcepts: () => inferConcepts,
5891
+ invalidateInstalledPacksCache: () => invalidateInstalledPacksCache,
3803
5892
  isValidHashId: () => isValidHashId,
3804
5893
  listMemories: () => listMemories,
3805
5894
  moodToValence: () => moodToValence,
3806
5895
  recallMemories: () => recallMemories,
3807
5896
  recallMemorySummaries: () => recallMemorySummaries,
5897
+ redelegateMemory: () => redelegateMemory,
5898
+ revokeMemory: () => revokeMemory,
3808
5899
  scopeToOwner: () => scopeToOwner,
3809
5900
  scoreImportanceWithLLM: () => scoreImportanceWithLLM,
3810
5901
  scoreMemory: () => scoreMemory,
@@ -3816,6 +5907,8 @@ var init_memory2 = __esm({
3816
5907
  "packages/brain/src/memory/index.ts"() {
3817
5908
  "use strict";
3818
5909
  init_memory();
5910
+ init_memory_decryption();
5911
+ init_memory_revoke();
3819
5912
  }
3820
5913
  });
3821
5914
 
@@ -3855,7 +5948,7 @@ function evaluateConfidence(memories, opts = {}) {
3855
5948
  };
3856
5949
  if (!sufficient) {
3857
5950
  result.hedgingInstruction = score < 0.15 ? HEDGING_INSTRUCTIONS.noEvidence : HEDGING_INSTRUCTIONS.weakEvidence;
3858
- log11.info({
5951
+ log18.info({
3859
5952
  score: score.toFixed(3),
3860
5953
  threshold,
3861
5954
  components: {
@@ -3872,7 +5965,7 @@ function evaluateConfidence(memories, opts = {}) {
3872
5965
  function filterLowConfidenceMemories(memories, minScore = 0.15) {
3873
5966
  const filtered = memories.filter((m) => m._score >= minScore);
3874
5967
  if (filtered.length < memories.length) {
3875
- log11.debug({
5968
+ log18.debug({
3876
5969
  before: memories.length,
3877
5970
  after: filtered.length,
3878
5971
  minScore
@@ -3880,12 +5973,12 @@ function filterLowConfidenceMemories(memories, minScore = 0.15) {
3880
5973
  }
3881
5974
  return filtered;
3882
5975
  }
3883
- var log11, HEDGING_INSTRUCTIONS;
5976
+ var log18, HEDGING_INSTRUCTIONS;
3884
5977
  var init_confidence_gate = __esm({
3885
5978
  "packages/brain/src/experimental/confidence-gate.ts"() {
3886
5979
  "use strict";
3887
5980
  init_logger();
3888
- log11 = createChildLogger("exp-confidence");
5981
+ log18 = createChildLogger("exp-confidence");
3889
5982
  HEDGING_INSTRUCTIONS = {
3890
5983
  noEvidence: `IMPORTANT: Your memory retrieval returned no strong matches for this query.
3891
5984
  You MUST NOT fabricate or guess information. Instead:
@@ -3903,6 +5996,87 @@ may not directly answer the query. When responding:
3903
5996
  }
3904
5997
  });
3905
5998
 
5999
+ // packages/brain/package.json
6000
+ var require_package = __commonJS({
6001
+ "packages/brain/package.json"(exports2, module2) {
6002
+ module2.exports = {
6003
+ name: "@clude/brain",
6004
+ version: "3.3.0",
6005
+ private: true,
6006
+ description: "Clude brain \u2014 memory, dreams, agents, personality, SDK",
6007
+ main: "dist/index.js",
6008
+ types: "dist/index.d.ts",
6009
+ exports: {
6010
+ ".": {
6011
+ types: "./dist/index.d.ts",
6012
+ default: "./dist/index.js"
6013
+ },
6014
+ "./*": {
6015
+ types: "./dist/*.d.ts",
6016
+ default: "./dist/*.js"
6017
+ },
6018
+ "./memory": {
6019
+ types: "./dist/memory/index.d.ts",
6020
+ default: "./dist/memory/index.js"
6021
+ },
6022
+ "./agents": {
6023
+ types: "./dist/agents/index.d.ts",
6024
+ default: "./dist/agents/index.js"
6025
+ },
6026
+ "./features/compound": {
6027
+ types: "./dist/features/compound/index.d.ts",
6028
+ default: "./dist/features/compound/index.js"
6029
+ },
6030
+ "./sdk": {
6031
+ types: "./dist/sdk/index.d.ts",
6032
+ default: "./dist/sdk/index.js"
6033
+ }
6034
+ },
6035
+ scripts: {
6036
+ build: "tsc -p tsconfig.build.json",
6037
+ typecheck: "tsc --noEmit",
6038
+ test: "vitest run",
6039
+ "test:watch": "vitest"
6040
+ },
6041
+ dependencies: {
6042
+ "@ai-sdk/anthropic": "^3.0.64",
6043
+ "@ai-sdk/google": "^3.0.55",
6044
+ "@ai-sdk/openai": "^3.0.49",
6045
+ "@ai-sdk/xai": "^3.0.75",
6046
+ "@anthropic-ai/sdk": "^0.39.0",
6047
+ "@clude/memorypack": "workspace:*",
6048
+ "@clude/shared": "workspace:*",
6049
+ "@clude/tokenization": "workspace:*",
6050
+ "@huggingface/transformers": "^4.1.0",
6051
+ "@modelcontextprotocol/sdk": "^1.26.0",
6052
+ "@openrouter/ai-sdk-provider": "^2.3.3",
6053
+ "@privy-io/node": "^0.12.0",
6054
+ "@solana/web3.js": "^1.98.4",
6055
+ "@supabase/supabase-js": "^2.95.3",
6056
+ ai: "^6.0.142",
6057
+ "better-sqlite3": "^12.9.0",
6058
+ bs58: "^6.0.0",
6059
+ express: "^4.21.0",
6060
+ jose: "^6.1.3",
6061
+ "node-cron": "^3.0.3",
6062
+ "sqlite-vec": "^0.1.9",
6063
+ "twitter-api-v2": "^1.29.0",
6064
+ unpdf: "^1.4.0",
6065
+ "vercel-minimax-ai-provider": "^0.0.2",
6066
+ zod: "^4.3.6"
6067
+ },
6068
+ devDependencies: {
6069
+ "@types/better-sqlite3": "^7.6.13",
6070
+ "@types/express": "^4.17.25",
6071
+ "@types/node": "^22.19.11",
6072
+ "@types/node-cron": "^3.0.11",
6073
+ typescript: "^5.6.0",
6074
+ vitest: "^4.1.0"
6075
+ }
6076
+ };
6077
+ }
6078
+ });
6079
+
3906
6080
  // packages/brain/src/mcp/local-store.ts
3907
6081
  var local_store_exports = {};
3908
6082
  __export(local_store_exports, {
@@ -4158,7 +6332,7 @@ async function findClinamen(opts) {
4158
6332
  const limit = opts.limit || DEFAULT_LIMIT;
4159
6333
  const minImportance = opts.minImportance ?? MIN_IMPORTANCE;
4160
6334
  const maxRelevance = opts.maxRelevance ?? MAX_RELEVANCE_SIM;
4161
- log12.debug({ context: opts.context.slice(0, 60), limit }, "Searching for clinamen");
6335
+ log19.debug({ context: opts.context.slice(0, 60), limit }, "Searching for clinamen");
4162
6336
  let contextEmbedding = null;
4163
6337
  if (isEmbeddingEnabled()) {
4164
6338
  const cached = getCachedEmbedding(opts.context);
@@ -4170,7 +6344,7 @@ async function findClinamen(opts) {
4170
6344
  }
4171
6345
  }
4172
6346
  if (!contextEmbedding) {
4173
- log12.debug("No embedding available \u2014 falling back to importance-only clinamen");
6347
+ log19.debug("No embedding available \u2014 falling back to importance-only clinamen");
4174
6348
  return importanceOnlyClinamen(db, limit, minImportance, opts.memoryTypes);
4175
6349
  }
4176
6350
  const cutoff = new Date(Date.now() - MIN_AGE_HOURS * 60 * 60 * 1e3).toISOString();
@@ -4181,7 +6355,7 @@ async function findClinamen(opts) {
4181
6355
  }
4182
6356
  const { data: candidates, error } = await query;
4183
6357
  if (error || !candidates || candidates.length === 0) {
4184
- log12.debug({ error: error?.message }, "No clinamen candidates found");
6358
+ log19.debug({ error: error?.message }, "No clinamen candidates found");
4185
6359
  return [];
4186
6360
  }
4187
6361
  const scored = [];
@@ -4207,7 +6381,7 @@ async function findClinamen(opts) {
4207
6381
  }
4208
6382
  scored.sort((a, b) => b._divergence - a._divergence);
4209
6383
  const results = scored.slice(0, limit);
4210
- log12.info({
6384
+ log19.info({
4211
6385
  candidates: candidates.length,
4212
6386
  afterFilter: scored.length,
4213
6387
  returned: results.length,
@@ -4233,7 +6407,7 @@ async function importanceOnlyClinamen(db, limit, minImportance, memoryTypes) {
4233
6407
  _relevanceSim: 0
4234
6408
  }));
4235
6409
  }
4236
- var log12, MIN_IMPORTANCE, MAX_RELEVANCE_SIM, MIN_AGE_HOURS, CANDIDATE_POOL_SIZE, DEFAULT_LIMIT;
6410
+ var log19, MIN_IMPORTANCE, MAX_RELEVANCE_SIM, MIN_AGE_HOURS, CANDIDATE_POOL_SIZE, DEFAULT_LIMIT;
4237
6411
  var init_clinamen = __esm({
4238
6412
  "packages/brain/src/memory/clinamen.ts"() {
4239
6413
  "use strict";
@@ -4241,7 +6415,7 @@ var init_clinamen = __esm({
4241
6415
  init_memory();
4242
6416
  init_embeddings();
4243
6417
  init_logger();
4244
- log12 = createChildLogger("clinamen");
6418
+ log19 = createChildLogger("clinamen");
4245
6419
  MIN_IMPORTANCE = 0.6;
4246
6420
  MAX_RELEVANCE_SIM = 0.35;
4247
6421
  MIN_AGE_HOURS = 24;
@@ -4343,7 +6517,9 @@ function loadSelfHosted() {
4343
6517
  }
4344
6518
  var server = new import_mcp.McpServer({
4345
6519
  name: "clude-memory",
4346
- version: "2.7.0"
6520
+ // Single-sourced from the package manifest (inlined at bundle time) so the
6521
+ // MCP handshake, `clude --version`, and npm always agree.
6522
+ version: require_package().version
4347
6523
  });
4348
6524
  var MEMORY_TYPES = ["episodic", "semantic", "procedural", "self_model", "introspective"];
4349
6525
  server.tool(
@@ -4472,7 +6648,7 @@ server.tool(
4472
6648
  "Store a new memory. Memories persist across conversations and decay over time if not accessed.",
4473
6649
  {
4474
6650
  type: import_zod.z.enum(MEMORY_TYPES).describe("Memory type: episodic (events), semantic (knowledge), procedural (behaviors), self_model (self-awareness), introspective (journal entries)"),
4475
- content: import_zod.z.string().max(5e3, "Content cannot exceed 5000 characters").describe("Full memory content"),
6651
+ content: import_zod.z.string().trim().min(1, "content cannot be empty").max(5e3, "Content cannot exceed 5000 characters").describe("Full memory content"),
4476
6652
  summary: import_zod.z.string().max(500, "Summary cannot exceed 500 characters").describe("Short summary for recall matching"),
4477
6653
  tags: import_zod.z.array(import_zod.z.string()).optional().describe("Tags for filtering"),
4478
6654
  concepts: import_zod.z.array(import_zod.z.string()).optional().describe("Structured concept labels (auto-inferred if omitted)"),
@@ -4868,7 +7044,7 @@ server.tool(
4868
7044
  {
4869
7045
  memories: import_zod.z.array(import_zod.z.object({
4870
7046
  type: import_zod.z.enum(MEMORY_TYPES).describe("Memory type"),
4871
- content: import_zod.z.string().max(5e3).describe("Full memory content"),
7047
+ content: import_zod.z.string().trim().min(1, "content cannot be empty").max(5e3).describe("Full memory content"),
4872
7048
  summary: import_zod.z.string().max(500).describe("Short summary"),
4873
7049
  tags: import_zod.z.array(import_zod.z.string()).optional(),
4874
7050
  concepts: import_zod.z.array(import_zod.z.string()).optional(),