@clude/sdk 3.2.0 → 3.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -936,18 +936,21 @@ async function runZeroConfigSetup() {
936
936
  ${c.bold}Setting up Clude memory...${c.reset}
937
937
  `);
938
938
  const email = await getEmail();
939
- if (!email) {
940
- console.error(` ${c.red}\u2717${c.reset} Email is required. Aborting.
941
- `);
942
- process.exit(1);
943
- }
944
- console.log(` ${c.green}\u2713${c.reset} Detected email ${email}`);
945
- const reg = await registerWithBackend(email);
946
- if (reg.ok) {
947
- console.log(` ${c.green}\u2713${c.reset} Registered ${reg.apiKey.slice(0, 12)}...`);
939
+ if (email) {
940
+ console.log(` ${c.green}\u2713${c.reset} Detected email ${email}`);
948
941
  } else {
949
- console.log(` ${c.yellow}\u26A0${c.reset} Cloud registration failed: ${reg.error}`);
950
- console.log(` Continuing in local-only mode.`);
942
+ console.log(` ${c.yellow}-${c.reset} No email detected continuing in local-only mode`);
943
+ console.log(` ${c.dim}Set CLUDE_SETUP_EMAIL or run interactively to register for cloud sync.${c.reset}`);
944
+ }
945
+ let reg = { ok: false };
946
+ if (email) {
947
+ reg = await registerWithBackend(email);
948
+ if (reg.ok) {
949
+ console.log(` ${c.green}\u2713${c.reset} Registered ${reg.apiKey.slice(0, 12)}...`);
950
+ } else {
951
+ console.log(` ${c.yellow}\u26A0${c.reset} Cloud registration failed: ${reg.error}`);
952
+ console.log(` Continuing in local-only mode.`);
953
+ }
951
954
  }
952
955
  const configDir = path2.join(os2.homedir(), ".clude");
953
956
  fs.mkdirSync(configDir, { recursive: true });
@@ -955,7 +958,7 @@ async function runZeroConfigSetup() {
955
958
  path2.join(configDir, "config.json"),
956
959
  JSON.stringify({
957
960
  apiKey: reg.apiKey ?? "",
958
- email,
961
+ email: email ?? "",
959
962
  wallet: reg.wallet ?? "",
960
963
  agentId: reg.agentId ?? "",
961
964
  did: reg.did ?? "",
@@ -982,7 +985,11 @@ async function runZeroConfigSetup() {
982
985
  for (const ide of ides) {
983
986
  try {
984
987
  const merged = installMcpConfig(ide, { apiKey: reg.apiKey, wallet: reg.wallet });
985
- console.log(` ${c.green}\u2713${c.reset} MCP installed ${ide.name}${merged ? " (merged)" : ""}`);
988
+ const check = JSON.parse(fs.readFileSync(ide.configPath, "utf-8"));
989
+ if (!check?.mcpServers?.["clude-memory"]) {
990
+ throw new Error("entry missing after write");
991
+ }
992
+ console.log(` ${c.green}\u2713${c.reset} MCP installed ${ide.name}${merged ? " (merged)" : ""} ${c.dim}${ide.configPath}${c.reset}`);
986
993
  } catch (err) {
987
994
  console.log(` ${c.yellow}\u26A0${c.reset} MCP install failed for ${ide.name}: ${err.message}`);
988
995
  }
@@ -1495,7 +1502,7 @@ async function getEmail(opts = {}) {
1495
1502
  }
1496
1503
  } catch {
1497
1504
  }
1498
- if (opts.skipPrompt) return null;
1505
+ if (opts.skipPrompt || !process.stdin.isTTY) return null;
1499
1506
  const rl = createPrompt();
1500
1507
  return new Promise((resolve5) => {
1501
1508
  rl.question(" Email: ", (answer) => {
@@ -1508,10 +1515,10 @@ async function getEmail(opts = {}) {
1508
1515
  function detectInstalledIDEs() {
1509
1516
  const ides = [];
1510
1517
  const home = os2.homedir();
1511
- if (fs.existsSync(path2.join(home, ".claude"))) {
1518
+ if (fs.existsSync(path2.join(home, ".claude")) || fs.existsSync(path2.join(home, ".claude.json"))) {
1512
1519
  ides.push({
1513
1520
  name: "Claude Code",
1514
- configPath: path2.join(home, ".claude", ".mcp.json")
1521
+ configPath: path2.join(home, ".claude.json")
1515
1522
  });
1516
1523
  }
1517
1524
  if (fs.existsSync(path2.join(home, ".cursor"))) {
@@ -1535,17 +1542,19 @@ function detectInstalledIDEs() {
1535
1542
  return ides;
1536
1543
  }
1537
1544
  function installMcpConfig(ide, reg) {
1538
- let existing = { mcpServers: {} };
1545
+ let existing = {};
1539
1546
  let merged = false;
1540
1547
  if (fs.existsSync(ide.configPath)) {
1541
1548
  try {
1542
1549
  existing = JSON.parse(fs.readFileSync(ide.configPath, "utf-8"));
1543
- if (!existing.mcpServers) existing.mcpServers = {};
1544
- if (existing.mcpServers["clude-memory"]) merged = true;
1545
1550
  } catch {
1546
- existing = { mcpServers: {} };
1551
+ throw new Error(`${ide.configPath} exists but is not valid JSON \u2014 fix or remove it, then re-run setup`);
1547
1552
  }
1548
1553
  }
1554
+ if (!existing.mcpServers || typeof existing.mcpServers !== "object" || Array.isArray(existing.mcpServers)) {
1555
+ existing.mcpServers = {};
1556
+ }
1557
+ if (existing.mcpServers["clude-memory"]) merged = true;
1549
1558
  const env = {};
1550
1559
  if (reg.apiKey) env.CORTEX_API_KEY = reg.apiKey;
1551
1560
  if (reg.wallet) env.CLUDE_WALLET = reg.wallet;
@@ -2366,7 +2375,7 @@ function writeDesktopConfig(configPath, host, apiKey) {
2366
2375
  existing = JSON.parse(fs3.readFileSync(configPath, "utf8"));
2367
2376
  merged = true;
2368
2377
  } catch {
2369
- existing = {};
2378
+ throw new Error(`${configPath} exists but is not valid JSON \u2014 fix or remove it, then re-run connect`);
2370
2379
  }
2371
2380
  }
2372
2381
  const { name, entry } = buildDesktopEntry(host, apiKey);
@@ -2546,7 +2555,20 @@ var init_config = __esm({
2546
2555
  accessToken: requiredUnlessSiteOnly("X_ACCESS_TOKEN"),
2547
2556
  accessSecret: requiredUnlessSiteOnly("X_ACCESS_SECRET"),
2548
2557
  botUserId: requiredUnlessSiteOnly("X_BOT_USER_ID"),
2549
- creatorUserId: optional("CREATOR_USER_ID", "")
2558
+ creatorUserId: optional("CREATOR_USER_ID", ""),
2559
+ /**
2560
+ * Bearer token for the public "$ANSEM LIVE" feed (GET /api/ansem/feed).
2561
+ * Reads-only X app bearer — used server-side for GET /2/tweets/search/recent.
2562
+ * Empty → the feed endpoint returns { enabled:false } and the frontend panel hides.
2563
+ * NEVER sent to the browser.
2564
+ */
2565
+ searchBearer: optional("X_SEARCH_BEARER", ""),
2566
+ /** Min poll gap for the on-demand $ANSEM feed (ms). Idle → no polling → $0. */
2567
+ ansemFeedIntervalMs: parseInt(optional("ANSEM_FEED_INTERVAL_MS", "90000"), 10),
2568
+ /** Posts older than ~15min must clear this like floor (fresh posts are exempt). */
2569
+ ansemFeedMinLikes: parseInt(optional("ANSEM_FEED_MIN_LIKES", "3"), 10),
2570
+ /** Hard daily read-cap: once exceeded, serve the stale buffer (cost stop-loss). */
2571
+ ansemFeedDailyReadCap: parseInt(optional("ANSEM_FEED_DAILY_READ_CAP", "40000"), 10)
2550
2572
  },
2551
2573
  supabase: {
2552
2574
  url: requiredUnlessSiteOnly("SUPABASE_URL"),
@@ -2564,7 +2586,9 @@ var init_config = __esm({
2564
2586
  optional("SOLANA_NETWORK", "mainnet-beta") === "devnet" ? "https://api.devnet.solana.com" : "https://api.mainnet-beta.solana.com"
2565
2587
  ),
2566
2588
  botWalletPrivateKey: optional("BOT_WALLET_PRIVATE_KEY", ""),
2567
- cludeTokenMint: optional("CLUUDE_TOKEN_MINT", "")
2589
+ cludeTokenMint: optional("CLUUDE_TOKEN_MINT", ""),
2590
+ /** Deployed memory-registry Anchor program ID. Empty → on-chain registration disabled. */
2591
+ memoryRegistryProgramId: optional("CLUDE_PROGRAM_ID", "")
2568
2592
  },
2569
2593
  server: {
2570
2594
  port: parseInt(optional("PORT", "3000"), 10),
@@ -2602,6 +2626,69 @@ var init_config = __esm({
2602
2626
  freePromoCreditUsdc: parseFloat(optional("FREE_PROMO_CREDIT_USDC", "1")),
2603
2627
  freePromoExpiry: optional("FREE_PROMO_EXPIRY", "")
2604
2628
  },
2629
+ memory: {
2630
+ /**
2631
+ * Memory 3.0 C2: route storeMemory enrichment (embed/link/extract) through the durable
2632
+ * memory_write_jobs outbox instead of fire-and-forget. Default OFF — fire-and-forget stays
2633
+ * the default until the worker is proven. Requires migrations 044 + 045; degrades gracefully
2634
+ * (falls back to fire-and-forget) if unapplied.
2635
+ */
2636
+ outboxEnabled: optional("MEMORY_OUTBOX", "false") === "true",
2637
+ /**
2638
+ * Memory 3.0 C1: write-time reconciliation, SHADOW slice (LLM-free). When on, each write records
2639
+ * a PROPOSED reconcile op (add / needs_router / skip) into memory_reconciliation_log WITHOUT
2640
+ * applying it — the labeled sample the enforce path must earn its turn-on from. Default OFF;
2641
+ * runs fully detached after embedMemory (zero write latency); degrades gracefully if migration
2642
+ * 046 is unapplied. Disabled under BENCH_MODE. The router is a later slice; only the cosine gate
2643
+ * runs here.
2644
+ */
2645
+ reconcileEnabled: optional("MEMORY_RECONCILE", "false") === "true",
2646
+ /** Screen floor passed to match_memories_temporal — LOW so max_cosine is captured for below-LO
2647
+ * writes (LO is tuned from the shadow data, not fixed up-front). */
2648
+ reconcileFloor: Number(optional("MEMORY_RECONCILE_FLOOR", "0.5")),
2649
+ /** "similar enough to reconcile" boundary → needs_router (vs add). A starting probe, not gospel. */
2650
+ reconcileLo: Number(optional("MEMORY_RECONCILE_LO", "0.85")),
2651
+ /** hi/mid band LABEL boundary for analysis (not a decision boundary). */
2652
+ reconcileHi: Number(optional("MEMORY_RECONCILE_HI", "0.95")),
2653
+ /**
2654
+ * C1 slice 1.5: the LLM router. Its own sub-flag (default OFF, independent of reconcileEnabled)
2655
+ * so gate-only instrumentation runs first and LO is calibrated from that data before any LLM
2656
+ * spend. When on, a >= LO write is classified add/update/noop by reconcileModel (still SHADOW —
2657
+ * the op is logged, never applied). Requires OpenRouter configured.
2658
+ */
2659
+ reconcileRouter: optional("MEMORY_RECONCILE_ROUTER", "false") === "true",
2660
+ /** Router model — an explicit id (NEVER a cognitiveFunction, which would silently override it
2661
+ * with the fast llama slot). Haiku-class default: capable enough for dup-vs-update, cheap. */
2662
+ reconcileModel: optional("MEMORY_RECONCILE_MODEL", "anthropic/claude-haiku-4.5"),
2663
+ /** Per-owner soft daily cap on router calls (approximate, per-process) — bounds LLM spend. */
2664
+ reconcileBudget: Number(optional("MEMORY_RECONCILE_BUDGET", "200"))
2665
+ },
2666
+ oauth: {
2667
+ /** HMAC secret for signing OAuth access-token JWTs. Empty disables the OAuth AS — bearer API-key auth still works. */
2668
+ signingSecret: optional("OAUTH_SIGNING_SECRET", ""),
2669
+ /** Issuer/audience identifier baked into tokens. Falls back to the request origin when empty. */
2670
+ issuer: optional("OAUTH_ISSUER", ""),
2671
+ /** Access-token lifetime in seconds (default 1h). */
2672
+ accessTtlSec: parseInt(optional("OAUTH_ACCESS_TTL_SEC", "3600"), 10),
2673
+ /** Refresh-token lifetime in seconds (default 30d). */
2674
+ refreshTtlSec: parseInt(optional("OAUTH_REFRESH_TTL_SEC", "2592000"), 10),
2675
+ /** Authorization-code lifetime in seconds (default 60s). */
2676
+ codeTtlSec: parseInt(optional("OAUTH_CODE_TTL_SEC", "60"), 10)
2677
+ },
2678
+ stripe: {
2679
+ /**
2680
+ * Stripe secret API key (sk_live_… / sk_test_…). Empty disables the
2681
+ * marketplace Stripe rail — the orchestrator/webhook fail closed without it.
2682
+ * SDK/MCP consumers never need this, so it stays optional() (not required()).
2683
+ */
2684
+ secretKey: optional("STRIPE_SECRET_KEY", ""),
2685
+ /**
2686
+ * Stripe webhook signing secret (whsec_…) used by stripe.webhooks.constructEvent
2687
+ * to verify the raw body BEFORE any DB write (Risk R6). Empty ⇒ every webhook is
2688
+ * rejected, so a misconfigured deploy can never process an unverified event.
2689
+ */
2690
+ webhookSecret: optional("STRIPE_WEBHOOK_SECRET", "")
2691
+ },
2605
2692
  campaign: {
2606
2693
  startDate: optional("CAMPAIGN_START", "")
2607
2694
  },
@@ -2615,6 +2702,51 @@ var init_config = __esm({
2615
2702
  queryApiKey: optional("EMBEDDING_QUERY_API_KEY", ""),
2616
2703
  queryModel: optional("EMBEDDING_QUERY_MODEL", "")
2617
2704
  },
2705
+ migration: {
2706
+ /**
2707
+ * Database backend that getDb() targets during the GCP parallel-run.
2708
+ * 'supabase' (default, current live infra) | 'cloudsql' (PostgREST /
2709
+ * self-hosted Supabase in front of Cloud SQL). Flip per-layer for the
2710
+ * shadow-stack cutover; Supabase stays the source of truth until sunset.
2711
+ */
2712
+ dbTarget: optional("DB_TARGET", "supabase"),
2713
+ /** PostgREST endpoint for the Cloud SQL backend (used only when DB_TARGET=cloudsql). */
2714
+ cloudsqlUrl: optional("CLOUDSQL_PGREST_URL", ""),
2715
+ /** Service key for the Cloud SQL PostgREST backend (used only when DB_TARGET=cloudsql). */
2716
+ cloudsqlServiceKey: optional("CLOUDSQL_SERVICE_KEY", ""),
2717
+ /**
2718
+ * Active embedding vector space for ingest + recall. 'voyage' (default,
2719
+ * current corpus) | 'vertex' (shadow column, only after the LongMemEval gate
2720
+ * passes). Separate from EMBEDDING_PROVIDER so the swap is A/B, not one-way.
2721
+ */
2722
+ embeddingActive: optional("EMBEDDING_ACTIVE", "voyage"),
2723
+ /**
2724
+ * Whether THIS process runs the in-server singleton timers (recall canary,
2725
+ * marketplace delivery poller, title-mint reconciliation). Default true =
2726
+ * current Railway behavior. Set false on the Cloud Run server so exactly one
2727
+ * owner (the worker) runs them and autoscaling never duplicates them.
2728
+ */
2729
+ runInProcessTimers: optional("RUN_INPROCESS_TIMERS", "true") === "true"
2730
+ },
2731
+ vertex: {
2732
+ /**
2733
+ * Vertex AI embedding backend (the GCP replacement for Voyage), used only when
2734
+ * ingest/backfill/recall target the 'vertex' space (EMBEDDING_ACTIVE=vertex or an
2735
+ * explicit generateEmbeddingForSpace('vertex') call). Separate from config.embedding
2736
+ * so the Vertex space can be backfilled + A/B-gated while Voyage stays live.
2737
+ */
2738
+ project: optional("VERTEX_PROJECT", optional("GCP_PROJECT", "")),
2739
+ location: optional("VERTEX_LOCATION", "us-central1"),
2740
+ model: optional("VERTEX_EMBEDDING_MODEL", "gemini-embedding-001"),
2741
+ /** Output dims — MRL-truncated to 1024 so vector(1024) columns + HNSW + match_* RPCs are unchanged. */
2742
+ dimensions: parseInt(optional("VERTEX_EMBEDDING_DIMENSIONS", "1024"), 10),
2743
+ /**
2744
+ * Optional static OAuth token override (from `gcloud auth print-access-token`) for
2745
+ * local dev / smoke tests. Empty in prod: Cloud Run mints a token from the attached
2746
+ * service account via the metadata server (SDK-free), no static key stored.
2747
+ */
2748
+ accessToken: optional("VERTEX_ACCESS_TOKEN", "")
2749
+ },
2618
2750
  openrouter: {
2619
2751
  apiKey: optional("OPENROUTER_API_KEY", ""),
2620
2752
  model: optional("OPENROUTER_MODEL", "meta-llama/llama-3.3-70b-instruct")
@@ -2628,6 +2760,35 @@ var init_config = __esm({
2628
2760
  tavily: {
2629
2761
  apiKey: optional("TAVILY_API_KEY", "")
2630
2762
  },
2763
+ higgsfield: {
2764
+ /** Higgsfield API key ID — first half of the V2 `Authorization: Key <id>:<secret>` pair (server-only). Empty → /api/ansem/speak returns 501. */
2765
+ apiKey: optional("HIGGSFIELD_API_KEY", ""),
2766
+ /** Higgsfield API key SECRET — second half of the V2 `Key <id>:<secret>` pair. */
2767
+ apiSecret: optional("HIGGSFIELD_API_SECRET", ""),
2768
+ /** REST base URL (Higgsfield V2 API). */
2769
+ apiBase: optional("HIGGSFIELD_API_BASE", "https://platform.higgsfield.ai"),
2770
+ /**
2771
+ * V2 model path for the seed_audio TTS model: create is POST {apiBase}/{ttsEndpoint}.
2772
+ * Verified live against the Higgsfield V2 API — bytedance/seed-audio-1.0.
2773
+ */
2774
+ ttsEndpoint: optional("HIGGSFIELD_TTS_ENDPOINT", "bytedance/seed-audio-1.0"),
2775
+ /** Ansem voice — Higgsfield "Sterling" preset (founder's choice). */
2776
+ voiceId: optional("ANSEM_VOICE_ID", "dc382508-c8bd-443c-8cb2-46e57b8d2e6f"),
2777
+ voiceType: optional("ANSEM_VOICE_TYPE", "preset"),
2778
+ /** Deep-voice tuning: seed_audio pitch_rate / speech_rate (integers, default 0). */
2779
+ voicePitch: parseInt(optional("ANSEM_VOICE_PITCH", "-9"), 10),
2780
+ voiceSpeechRate: parseInt(optional("ANSEM_VOICE_SPEECH_RATE", "-12"), 10),
2781
+ /** Output audio format (seed_audio supports wav|mp3|pcm|ogg_opus). */
2782
+ audioFormat: optional("ANSEM_VOICE_FORMAT", "mp3")
2783
+ },
2784
+ elevenlabs: {
2785
+ /** ElevenLabs API key (server-only). Set → PRIMARY TTS (~1-3s, no lag); empty → falls back to Higgsfield. */
2786
+ apiKey: optional("ELEVENLABS_API_KEY", ""),
2787
+ /** Voice id — default "Brian" (deep, resonant, american). Swap via ELEVENLABS_VOICE_ID. */
2788
+ voiceId: optional("ELEVENLABS_VOICE_ID", "nPczCjzI2devNBz1zQrb"),
2789
+ /** Model — turbo for lowest latency. */
2790
+ model: optional("ELEVENLABS_MODEL", "eleven_turbo_v2_5")
2791
+ },
2631
2792
  privy: {
2632
2793
  appId: optional("PRIVY_APP_ID", ""),
2633
2794
  appSecret: optional("PRIVY_APP_SECRET", ""),
@@ -2836,6 +2997,7 @@ function timeAgo(dateStr) {
2836
2997
  }
2837
2998
  function detectMode() {
2838
2999
  const hasLocal = (0, import_fs.existsSync)(MEMORIES_FILE);
3000
+ const hasSqlite = (0, import_fs.existsSync)(BRAIN_DB);
2839
3001
  const envPath = (0, import_path2.join)(process.cwd(), ".env");
2840
3002
  let hasApiKey = false;
2841
3003
  let hasSupabase = false;
@@ -2852,8 +3014,15 @@ function detectMode() {
2852
3014
  }
2853
3015
  if (process.env.CORTEX_API_KEY) hasApiKey = true;
2854
3016
  if (process.env.SUPABASE_URL) hasSupabase = true;
2855
- if (hasLocal && !hasApiKey && !hasSupabase) {
2856
- return { mode: "local", details: MEMORIES_FILE };
3017
+ if (!hasApiKey && (0, import_fs.existsSync)(CONFIG_FILE)) {
3018
+ try {
3019
+ const cfg = JSON.parse((0, import_fs.readFileSync)(CONFIG_FILE, "utf-8"));
3020
+ if (cfg.apiKey) hasApiKey = true;
3021
+ } catch {
3022
+ }
3023
+ }
3024
+ if ((hasLocal || hasSqlite) && !hasApiKey && !hasSupabase) {
3025
+ return { mode: "local", details: hasSqlite ? BRAIN_DB : MEMORIES_FILE };
2857
3026
  }
2858
3027
  if (hasApiKey) {
2859
3028
  return { mode: "hosted", details: hostUrl || "https://clude.io" };
@@ -2861,12 +3030,31 @@ function detectMode() {
2861
3030
  if (hasSupabase) {
2862
3031
  return { mode: "self-hosted", details: "Supabase" };
2863
3032
  }
2864
- if (hasLocal) {
2865
- return { mode: "local", details: MEMORIES_FILE };
3033
+ if (hasLocal || hasSqlite) {
3034
+ return { mode: "local", details: hasSqlite ? BRAIN_DB : MEMORIES_FILE };
2866
3035
  }
2867
3036
  return { mode: "not configured", details: "Run: npx @clude/sdk setup" };
2868
3037
  }
3038
+ function printSqliteStatus() {
3039
+ const fileStats = (0, import_fs.statSync)(BRAIN_DB);
3040
+ const fileSizeKb = (fileStats.size / 1024).toFixed(1);
3041
+ try {
3042
+ const Database2 = require("better-sqlite3");
3043
+ const db = new Database2(BRAIN_DB, { readonly: true });
3044
+ const row = db.prepare("SELECT COUNT(*) AS n FROM memories").get();
3045
+ db.close();
3046
+ printSuccess(`${row.n} memories in local SQLite store`);
3047
+ } catch {
3048
+ printSuccess("Local SQLite store ready");
3049
+ }
3050
+ printInfo(`${BRAIN_DB} (${fileSizeKb} KB, updated ${timeAgo(fileStats.mtime.toISOString())})
3051
+ `);
3052
+ }
2869
3053
  function printLocalStatus() {
3054
+ if ((0, import_fs.existsSync)(BRAIN_DB)) {
3055
+ printSqliteStatus();
3056
+ return;
3057
+ }
2870
3058
  if (!(0, import_fs.existsSync)(MEMORIES_FILE)) {
2871
3059
  printWarn("No memories file found.");
2872
3060
  printInfo(`Expected: ${MEMORIES_FILE}`);
@@ -2973,16 +3161,18 @@ function printHostedStatus() {
2973
3161
  }
2974
3162
  function checkMcpInstalled() {
2975
3163
  const configs = [
2976
- { name: "Claude Desktop", path: (0, import_path2.join)(process.env.HOME || "", "Library", "Application Support", "Claude", "claude_desktop_config.json") },
2977
- { name: "Cursor", path: (0, import_path2.join)(process.env.HOME || "", ".cursor", "mcp.json") },
2978
- { name: "Claude Code", path: (0, import_path2.join)(process.cwd(), ".mcp.json") }
3164
+ { name: "Claude Desktop", path: (0, import_path2.join)((0, import_os.homedir)(), "Library", "Application Support", "Claude", "claude_desktop_config.json") },
3165
+ { name: "Cursor", path: (0, import_path2.join)((0, import_os.homedir)(), ".cursor", "mcp.json") },
3166
+ { name: "Claude Code (user)", path: (0, import_path2.join)((0, import_os.homedir)(), ".claude.json") },
3167
+ { name: "Claude Code (project)", path: (0, import_path2.join)(process.cwd(), ".mcp.json") }
2979
3168
  ];
2980
3169
  let found = false;
2981
3170
  for (const cfg of configs) {
2982
3171
  if ((0, import_fs.existsSync)(cfg.path)) {
2983
3172
  try {
2984
- const content = (0, import_fs.readFileSync)(cfg.path, "utf-8");
2985
- if (content.includes("clude")) {
3173
+ const parsed = JSON.parse((0, import_fs.readFileSync)(cfg.path, "utf-8"));
3174
+ const servers = parsed?.mcpServers && typeof parsed.mcpServers === "object" ? parsed.mcpServers : {};
3175
+ if (Object.keys(servers).some((name) => name.includes("clude"))) {
2986
3176
  printSuccess(`MCP installed: ${cfg.name}`);
2987
3177
  found = true;
2988
3178
  }
@@ -3028,15 +3218,18 @@ async function runStatus() {
3028
3218
  printDivider();
3029
3219
  console.log("");
3030
3220
  }
3031
- var import_fs, import_path2, CLUDE_DIR, MEMORIES_FILE;
3221
+ var import_fs, import_os, import_path2, CLUDE_DIR, MEMORIES_FILE, BRAIN_DB, CONFIG_FILE;
3032
3222
  var init_status = __esm({
3033
3223
  "packages/brain/src/cli/status.ts"() {
3034
3224
  "use strict";
3035
3225
  import_fs = require("fs");
3226
+ import_os = require("os");
3036
3227
  import_path2 = require("path");
3037
3228
  init_banner();
3038
3229
  CLUDE_DIR = (0, import_path2.join)(process.env.HOME || process.env.USERPROFILE || ".", ".clude");
3039
3230
  MEMORIES_FILE = (0, import_path2.join)(CLUDE_DIR, "memories.json");
3231
+ BRAIN_DB = (0, import_path2.join)(CLUDE_DIR, "brain.db");
3232
+ CONFIG_FILE = (0, import_path2.join)(CLUDE_DIR, "config.json");
3040
3233
  }
3041
3234
  });
3042
3235
 
@@ -3181,10 +3374,9 @@ function serializeRecord(record) {
3181
3374
  function writeMemoryPack(targetPath, records, opts) {
3182
3375
  const format = opts.format ?? "directory";
3183
3376
  if (format === "tarball") {
3184
- writeTarball(targetPath, records, opts);
3185
- return;
3377
+ return writeTarball(targetPath, records, opts);
3186
3378
  }
3187
- writeDirectory(targetPath, records, opts);
3379
+ return writeDirectory(targetPath, records, opts);
3188
3380
  }
3189
3381
  function writeDirectory(dir, records, opts) {
3190
3382
  if ((0, import_fs2.existsSync)(dir)) {
@@ -3204,6 +3396,11 @@ function writeDirectory(dir, records, opts) {
3204
3396
  }
3205
3397
  (0, import_fs2.mkdirSync)(dir, { recursive: true });
3206
3398
  const clock = opts.clock ?? (() => (/* @__PURE__ */ new Date()).toISOString());
3399
+ if (opts.encryption && opts.ownerEncryption) {
3400
+ throw new Error(
3401
+ "writeMemoryPack: `encryption` and `ownerEncryption` are mutually exclusive"
3402
+ );
3403
+ }
3207
3404
  const encryption = opts.encryption ? {
3208
3405
  ...opts.encryption,
3209
3406
  scope: opts.encryption.scope ?? (opts.blobs && opts.blobs.size > 0 ? "records+blobs" : "records")
@@ -3227,12 +3424,21 @@ function writeDirectory(dir, records, opts) {
3227
3424
  anchor_chain: opts.anchor_chain,
3228
3425
  pack_format: "directory",
3229
3426
  blobs_count: opts.blobs && opts.blobs.size > 0 ? opts.blobs.size : void 0,
3230
- encryption: encryption ? {
3427
+ encryption: opts.ownerEncryption ? {
3428
+ // Owner-sealed: records are pre-encrypted ciphertext; the pack DEK is
3429
+ // sealed once to the holder in `owner`. Scope is always `records`.
3430
+ algorithm: "xsalsa20-poly1305",
3431
+ nonce_strategy: "per-record-random",
3432
+ key_derivation: "owner-sealed",
3433
+ scope: "records",
3434
+ owner: opts.ownerEncryption
3435
+ } : encryption ? {
3231
3436
  algorithm: "xsalsa20-poly1305",
3232
3437
  nonce_strategy: "per-record-random",
3233
3438
  key_derivation: "none",
3234
3439
  scope: encryption.scope
3235
- } : void 0
3440
+ } : void 0,
3441
+ pmp: opts.pmp
3236
3442
  };
3237
3443
  (0, import_fs2.writeFileSync)((0, import_path3.join)(dir, "manifest.json"), JSON.stringify(manifest, null, 2));
3238
3444
  const recordLines = [];
@@ -3308,6 +3514,7 @@ function writeDirectory(dir, records, opts) {
3308
3514
  indexEntries.map((e) => JSON.stringify(e)).join("\n") + "\n"
3309
3515
  );
3310
3516
  }
3517
+ return manifest;
3311
3518
  }
3312
3519
  function writeTarball(targetPath, records, opts) {
3313
3520
  const targetAbs = (0, import_path3.resolve)(targetPath);
@@ -3343,11 +3550,12 @@ function writeTarball(targetPath, records, opts) {
3343
3550
  if (!(0, import_fs2.existsSync)(targetAbs) || (0, import_fs2.statSync)(targetAbs).size === 0) {
3344
3551
  throw new Error("MemoryPack: tar produced an empty archive");
3345
3552
  }
3553
+ return manifest;
3346
3554
  } finally {
3347
3555
  (0, import_fs2.rmSync)(tmpRoot, { recursive: true, force: true });
3348
3556
  }
3349
3557
  }
3350
- var import_fs2, import_child_process2, import_path3;
3558
+ var import_fs2, import_child_process2, import_path3, ZSTD_MAGIC;
3351
3559
  var init_writer = __esm({
3352
3560
  "packages/memorypack/src/writer.ts"() {
3353
3561
  "use strict";
@@ -3356,6 +3564,7 @@ var init_writer = __esm({
3356
3564
  import_path3 = require("path");
3357
3565
  init_types();
3358
3566
  init_sign();
3567
+ ZSTD_MAGIC = Buffer.from([40, 181, 47, 253]);
3359
3568
  }
3360
3569
  });
3361
3570
 
@@ -3611,10 +3820,25 @@ function readDirectory(dir, opts) {
3611
3820
  warnings
3612
3821
  };
3613
3822
  }
3823
+ function maxUncompressedBytes() {
3824
+ const n = Number(process.env.MEMORYPACK_MAX_UNCOMPRESSED_BYTES);
3825
+ return Number.isFinite(n) && n > 0 ? n : 64 * 1024 * 1024;
3826
+ }
3827
+ function dirSizeBytes(dir, cap) {
3828
+ let total = 0;
3829
+ for (const entry of (0, import_fs3.readdirSync)(dir, { withFileTypes: true })) {
3830
+ const p = (0, import_path4.join)(dir, entry.name);
3831
+ if (entry.isDirectory()) total += dirSizeBytes(p, cap);
3832
+ else if (entry.isFile()) total += (0, import_fs3.statSync)(p).size;
3833
+ if (total > cap) return total;
3834
+ }
3835
+ return total;
3836
+ }
3614
3837
  function extractTarball(path9) {
3615
3838
  if (!(0, import_fs3.existsSync)(path9)) {
3616
3839
  throw new Error(`MemoryPack: tarball not found: ${path9}`);
3617
3840
  }
3841
+ const cap = maxUncompressedBytes();
3618
3842
  const list = (0, import_child_process3.spawnSync)("tar", ["--zstd", "-tvf", path9], {
3619
3843
  stdio: ["ignore", "pipe", "pipe"]
3620
3844
  });
@@ -3630,6 +3854,7 @@ function extractTarball(path9) {
3630
3854
  );
3631
3855
  }
3632
3856
  const lines = list.stdout.toString().split("\n").filter((l) => l.length > 0);
3857
+ let declaredBytes = 0;
3633
3858
  for (const line of lines) {
3634
3859
  const typeChar = line.charAt(0);
3635
3860
  if (typeChar === "l" || typeChar === "h") {
@@ -3637,7 +3862,17 @@ function extractTarball(path9) {
3637
3862
  `MemoryPack: tarball contains symlink/hardlink \u2014 refusing to extract (${line.slice(0, 80)})`
3638
3863
  );
3639
3864
  }
3640
- const name = line.split(/\s+/).pop() ?? "";
3865
+ const fields = line.split(/\s+/);
3866
+ const declared = Number(fields[2]);
3867
+ if (Number.isFinite(declared) && declared > 0) {
3868
+ declaredBytes += declared;
3869
+ if (declaredBytes > cap) {
3870
+ throw new Error(
3871
+ `MemoryPack: archive declares > ${cap} bytes uncompressed \u2014 refusing (decompression-bomb guard)`
3872
+ );
3873
+ }
3874
+ }
3875
+ const name = fields[fields.length - 1] ?? "";
3641
3876
  if (!name) continue;
3642
3877
  if (name.startsWith("/")) {
3643
3878
  throw new Error(`MemoryPack: tarball contains absolute path \u2014 refusing (${name})`);
@@ -3649,7 +3884,7 @@ function extractTarball(path9) {
3649
3884
  throw new Error(`MemoryPack: tarball member has unsafe characters \u2014 refusing (${name})`);
3650
3885
  }
3651
3886
  }
3652
- const tmp = (0, import_fs3.mkdtempSync)((0, import_path4.join)((0, import_os.tmpdir)(), "mp-extract-"));
3887
+ const tmp = (0, import_fs3.mkdtempSync)((0, import_path4.join)((0, import_os2.tmpdir)(), "mp-extract-"));
3653
3888
  const extract = (0, import_child_process3.spawnSync)(
3654
3889
  "tar",
3655
3890
  [
@@ -3670,6 +3905,12 @@ function extractTarball(path9) {
3670
3905
  `MemoryPack: tar --zstd -xf failed (exit ${extract.status}): ${stderr.trim() || "no stderr"}`
3671
3906
  );
3672
3907
  }
3908
+ if (dirSizeBytes(tmp, cap) > cap) {
3909
+ (0, import_fs3.rmSync)(tmp, { recursive: true, force: true });
3910
+ throw new Error(
3911
+ `MemoryPack: extracted archive exceeds ${cap} bytes \u2014 refusing (decompression-bomb guard)`
3912
+ );
3913
+ }
3673
3914
  if ((0, import_fs3.existsSync)((0, import_path4.join)(tmp, "manifest.json"))) {
3674
3915
  return tmp;
3675
3916
  }
@@ -3686,13 +3927,13 @@ function extractTarball(path9) {
3686
3927
  `MemoryPack: tarball must contain manifest.json at the root or inside a single wrapping directory`
3687
3928
  );
3688
3929
  }
3689
- var import_fs3, import_child_process3, import_os, import_path4, SAFE_MEMBER_RE;
3930
+ var import_fs3, import_child_process3, import_os2, import_path4, SAFE_MEMBER_RE;
3690
3931
  var init_reader = __esm({
3691
3932
  "packages/memorypack/src/reader.ts"() {
3692
3933
  "use strict";
3693
3934
  import_fs3 = require("fs");
3694
3935
  import_child_process3 = require("child_process");
3695
- import_os = require("os");
3936
+ import_os2 = require("os");
3696
3937
  import_path4 = require("path");
3697
3938
  init_sign();
3698
3939
  SAFE_MEMBER_RE = /^[A-Za-z0-9._/-]+$/;
@@ -4098,28 +4339,70 @@ var init_local_store = __esm({
4098
4339
  }
4099
4340
  });
4100
4341
 
4342
+ // packages/shared/src/core/migration-profile.ts
4343
+ function defaultSupabaseConnection() {
4344
+ return { url: config.supabase.url, serviceKey: config.supabase.serviceKey };
4345
+ }
4346
+ function resolveDbConnection(profile = config.migration, supabase2 = defaultSupabaseConnection()) {
4347
+ if (!DB_TARGETS.includes(profile.dbTarget)) {
4348
+ throw new Error(
4349
+ `Invalid DB_TARGET '${profile.dbTarget}' (expected 'supabase' | 'cloudsql')`
4350
+ );
4351
+ }
4352
+ if (profile.dbTarget === "cloudsql") {
4353
+ if (!profile.cloudsqlUrl || !profile.cloudsqlServiceKey) {
4354
+ throw new Error(
4355
+ "DB_TARGET=cloudsql requires CLOUDSQL_PGREST_URL and CLOUDSQL_SERVICE_KEY"
4356
+ );
4357
+ }
4358
+ return { url: profile.cloudsqlUrl, serviceKey: profile.cloudsqlServiceKey };
4359
+ }
4360
+ return { url: supabase2.url, serviceKey: supabase2.serviceKey };
4361
+ }
4362
+ function activeEmbeddingSpace(profile = config.migration) {
4363
+ if (!EMBEDDING_SPACES.includes(profile.embeddingActive)) {
4364
+ throw new Error(
4365
+ `Invalid EMBEDDING_ACTIVE '${profile.embeddingActive}' (expected 'voyage' | 'vertex')`
4366
+ );
4367
+ }
4368
+ return profile.embeddingActive;
4369
+ }
4370
+ function vectorRpcName(baseRpc, profile = config.migration) {
4371
+ return activeEmbeddingSpace(profile) === "vertex" ? `${baseRpc}_vertex` : baseRpc;
4372
+ }
4373
+ var DB_TARGETS, EMBEDDING_SPACES;
4374
+ var init_migration_profile = __esm({
4375
+ "packages/shared/src/core/migration-profile.ts"() {
4376
+ "use strict";
4377
+ init_config();
4378
+ DB_TARGETS = ["supabase", "cloudsql"];
4379
+ EMBEDDING_SPACES = ["voyage", "vertex"];
4380
+ }
4381
+ });
4382
+
4101
4383
  // packages/shared/src/core/database.ts
4102
4384
  var database_exports = {};
4103
4385
  __export(database_exports, {
4104
4386
  _setDb: () => _setDb,
4105
4387
  claimForProcessing: () => claimForProcessing,
4106
4388
  getDb: () => getDb,
4389
+ getSchemaDriftReport: () => getSchemaDriftReport,
4107
4390
  initDatabase: () => initDatabase2,
4108
4391
  isAlreadyProcessed: () => isAlreadyProcessed,
4109
4392
  markProcessed: () => markProcessed
4110
4393
  });
4111
4394
  function getDb() {
4112
4395
  if (!supabase) {
4113
- supabase = (0, import_supabase_js.createClient)(config.supabase.url, config.supabase.serviceKey);
4114
- log2.info("Supabase client initialized");
4396
+ const conn = resolveDbConnection();
4397
+ supabase = (0, import_supabase_js.createClient)(conn.url, conn.serviceKey);
4398
+ log2.info({ dbTarget: config.migration.dbTarget }, "Database client initialized");
4115
4399
  }
4116
4400
  return supabase;
4117
4401
  }
4118
4402
  function _setDb(client) {
4119
4403
  supabase = client;
4120
4404
  }
4121
- async function initDatabase2() {
4122
- const db = getDb();
4405
+ async function runBootDdl(db) {
4123
4406
  try {
4124
4407
  const { error } = await db.rpc("exec_sql", {
4125
4408
  query: `
@@ -4191,6 +4474,7 @@ async function initDatabase2() {
4191
4474
  input_memory_ids BIGINT[] DEFAULT '{}',
4192
4475
  output TEXT NOT NULL,
4193
4476
  new_memories_created BIGINT[] DEFAULT '{}',
4477
+ owner_wallet TEXT,
4194
4478
  created_at TIMESTAMPTZ DEFAULT NOW()
4195
4479
  );
4196
4480
 
@@ -4275,7 +4559,7 @@ async function initDatabase2() {
4275
4559
  target_id BIGINT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
4276
4560
  link_type TEXT NOT NULL CHECK (link_type IN (
4277
4561
  'supports', 'contradicts', 'elaborates', 'causes', 'follows', 'relates', 'resolves',
4278
- 'happens_before', 'happens_after', 'concurrent_with'
4562
+ 'supersedes', 'happens_before', 'happens_after', 'concurrent_with'
4279
4563
  )),
4280
4564
  strength REAL DEFAULT 0.5,
4281
4565
  created_at TIMESTAMPTZ DEFAULT NOW(),
@@ -4310,7 +4594,11 @@ async function initDatabase2() {
4310
4594
  WHERE ml.source_id = ANY(seed_ids)
4311
4595
  AND ml.target_id != ALL(seed_ids)
4312
4596
  AND ml.strength >= min_strength
4313
- AND (filter_owner IS NULL OR m.owner_wallet = filter_owner)
4597
+ AND (
4598
+ filter_owner IS NULL
4599
+ OR (filter_owner = '__BOT_OWN__' AND m.owner_wallet IS NULL)
4600
+ OR m.owner_wallet = filter_owner
4601
+ )
4314
4602
  UNION
4315
4603
  SELECT DISTINCT ON (ml.source_id, ml.link_type)
4316
4604
  ml.source_id AS memory_id,
@@ -4322,7 +4610,11 @@ async function initDatabase2() {
4322
4610
  WHERE ml.target_id = ANY(seed_ids)
4323
4611
  AND ml.source_id != ALL(seed_ids)
4324
4612
  AND ml.strength >= min_strength
4325
- AND (filter_owner IS NULL OR m.owner_wallet = filter_owner)
4613
+ AND (
4614
+ filter_owner IS NULL
4615
+ OR (filter_owner = '__BOT_OWN__' AND m.owner_wallet IS NULL)
4616
+ OR m.owner_wallet = filter_owner
4617
+ )
4326
4618
  ORDER BY strength DESC
4327
4619
  LIMIT max_results;
4328
4620
  $$;
@@ -4350,6 +4642,10 @@ async function initDatabase2() {
4350
4642
  ALTER TABLE dream_logs ADD CONSTRAINT dream_logs_session_type_check
4351
4643
  CHECK (session_type IN ('consolidation', 'reflection', 'emergence', 'compaction', 'decay', 'contradiction_resolution'));
4352
4644
 
4645
+ -- Migration 021: per-owner attribution for dream_logs (fixes totalDreamSessions leak)
4646
+ ALTER TABLE dream_logs ADD COLUMN IF NOT EXISTS owner_wallet TEXT;
4647
+ CREATE INDEX IF NOT EXISTS idx_dream_logs_owner ON dream_logs(owner_wallet);
4648
+
4353
4649
  -- Migration: add 'resolves' + temporal link types
4354
4650
  ALTER TABLE memory_links DROP CONSTRAINT IF EXISTS memory_links_link_type_check;
4355
4651
  ALTER TABLE memory_links ADD CONSTRAINT memory_links_link_type_check
@@ -4531,6 +4827,170 @@ async function initDatabase2() {
4531
4827
  CREATE INDEX IF NOT EXISTS idx_memories_event_date ON memories(event_date)
4532
4828
  WHERE event_date IS NOT NULL;
4533
4829
 
4830
+ -- Lexical index for keyword/BM25 search (encryption \xA79). content_tokens is
4831
+ -- app-maintained; ts_summary (summary-only) is added on fresh deploys that lack it.
4832
+ ALTER TABLE memories ADD COLUMN IF NOT EXISTS content_tokens tsvector;
4833
+ CREATE INDEX IF NOT EXISTS idx_memories_content_tokens ON memories USING GIN(content_tokens);
4834
+ ALTER TABLE memories ADD COLUMN IF NOT EXISTS provider_delegated BOOLEAN DEFAULT TRUE;
4835
+ CREATE INDEX IF NOT EXISTS idx_memories_delegated ON memories(provider_delegated) WHERE encrypted = TRUE;
4836
+ ALTER TABLE memories ADD COLUMN IF NOT EXISTS summary_ciphertext TEXT;
4837
+ ALTER TABLE memories ADD COLUMN IF NOT EXISTS embedding_ciphertext TEXT;
4838
+
4839
+ -- Memory 3.0 Phase 1 (migration 044): bi-temporal validity + provenance (additive/nullable, inert until MEMORY_RECONCILE)
4840
+ ALTER TABLE memories ADD COLUMN IF NOT EXISTS valid_from TIMESTAMPTZ;
4841
+ ALTER TABLE memories ADD COLUMN IF NOT EXISTS invalid_at TIMESTAMPTZ;
4842
+ ALTER TABLE memories ADD COLUMN IF NOT EXISTS superseded_by TEXT;
4843
+ ALTER TABLE memories ADD COLUMN IF NOT EXISTS fact_key TEXT;
4844
+ ALTER TABLE memories ADD COLUMN IF NOT EXISTS extractor_version TEXT;
4845
+ ALTER TABLE memories ADD COLUMN IF NOT EXISTS extraction_confidence REAL;
4846
+ ALTER TABLE memories ADD COLUMN IF NOT EXISTS source_turn_ref JSONB;
4847
+ ALTER TABLE memories ADD COLUMN IF NOT EXISTS hash_id_v2 TEXT;
4848
+ CREATE INDEX IF NOT EXISTS idx_memories_valid ON memories(id) WHERE invalid_at IS NULL;
4849
+ CREATE INDEX IF NOT EXISTS idx_memories_fact_key ON memories(fact_key) WHERE fact_key IS NOT NULL;
4850
+ CREATE INDEX IF NOT EXISTS idx_memories_hash_id_v2 ON memories(hash_id_v2) WHERE hash_id_v2 IS NOT NULL;
4851
+
4852
+ -- Memory 3.0 Phase 1 (migration 044): the C2 durable write outbox.
4853
+ CREATE TABLE IF NOT EXISTS memory_write_jobs (
4854
+ id BIGSERIAL PRIMARY KEY,
4855
+ memory_id BIGINT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
4856
+ job_type TEXT NOT NULL CHECK (job_type IN ('enrich', 'embed', 'link', 'extract', 'reconcile', 'backfill_v2')),
4857
+ status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'running', 'done', 'failed')),
4858
+ attempts INT NOT NULL DEFAULT 0,
4859
+ next_retry_at TIMESTAMPTZ DEFAULT NOW(),
4860
+ last_error TEXT,
4861
+ created_at TIMESTAMPTZ DEFAULT NOW(),
4862
+ updated_at TIMESTAMPTZ DEFAULT NOW()
4863
+ );
4864
+ CREATE INDEX IF NOT EXISTS idx_write_jobs_claim ON memory_write_jobs(status, next_retry_at) WHERE status IN ('pending', 'failed');
4865
+ CREATE INDEX IF NOT EXISTS idx_write_jobs_memory ON memory_write_jobs(memory_id);
4866
+ CREATE INDEX IF NOT EXISTS idx_write_jobs_running ON memory_write_jobs(updated_at) WHERE status = 'running';
4867
+
4868
+ -- Memory 3.0 C2 outbox \u2014 MIRROR of migration 045 (byte-equivalent). The table above is in
4869
+ -- the boot blob, so the claim RPC + idempotency objects MUST be too: otherwise a
4870
+ -- boot-provisioned box gets the table but not the RPC and the worker silently never drains
4871
+ -- (the migration-028 class). Keep in sync with 045.
4872
+ DO $do$
4873
+ BEGIN
4874
+ ALTER TABLE memory_write_jobs DROP CONSTRAINT IF EXISTS memory_write_jobs_job_type_check;
4875
+ ALTER TABLE memory_write_jobs ADD CONSTRAINT memory_write_jobs_job_type_check
4876
+ CHECK (job_type IN ('enrich', 'embed', 'link', 'extract', 'reconcile', 'backfill_v2'));
4877
+ EXCEPTION WHEN undefined_table THEN NULL; END $do$;
4878
+
4879
+ CREATE OR REPLACE FUNCTION claim_memory_write_jobs(
4880
+ p_limit INT DEFAULT 20, p_stale_running INTERVAL DEFAULT INTERVAL '15 minutes', p_owner TEXT DEFAULT NULL
4881
+ )
4882
+ RETURNS TABLE (id BIGINT, memory_id BIGINT, job_type TEXT, attempts INT, owner_wallet TEXT)
4883
+ LANGUAGE plpgsql AS $claimfn$
4884
+ BEGIN
4885
+ RETURN QUERY
4886
+ UPDATE memory_write_jobs j
4887
+ SET status = 'running', attempts = j.attempts + 1, updated_at = NOW()
4888
+ FROM (
4889
+ SELECT jj.id FROM memory_write_jobs jj JOIN memories m ON m.id = jj.memory_id
4890
+ WHERE ((jj.status IN ('pending','failed') AND jj.next_retry_at IS NOT NULL AND jj.next_retry_at <= NOW())
4891
+ OR (jj.status = 'running' AND jj.updated_at < NOW() - p_stale_running))
4892
+ AND (p_owner IS NULL OR m.owner_wallet = p_owner OR (p_owner = '__BOT_OWN__' AND m.owner_wallet IS NULL))
4893
+ ORDER BY jj.next_retry_at ASC NULLS LAST LIMIT p_limit FOR UPDATE SKIP LOCKED
4894
+ ) claimed
4895
+ WHERE j.id = claimed.id
4896
+ RETURNING j.id, j.memory_id, j.job_type, j.attempts,
4897
+ (SELECT mm.owner_wallet FROM memories mm WHERE mm.id = j.memory_id);
4898
+ END; $claimfn$;
4899
+
4900
+ DO $do$
4901
+ BEGIN
4902
+ DELETE FROM memory_links a USING memory_links b
4903
+ 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;
4904
+ ALTER TABLE memory_links DROP CONSTRAINT IF EXISTS memory_links_unique_edge;
4905
+ ALTER TABLE memory_links ADD CONSTRAINT memory_links_unique_edge UNIQUE (source_id, target_id, link_type);
4906
+ EXCEPTION WHEN undefined_table THEN NULL; END $do$;
4907
+
4908
+ CREATE OR REPLACE FUNCTION upsert_entity_relation(
4909
+ p_src BIGINT, p_tgt BIGINT, p_type TEXT, p_evidence BIGINT DEFAULT NULL, p_strength REAL DEFAULT 0.5
4910
+ )
4911
+ RETURNS VOID LANGUAGE sql AS $uerfn$
4912
+ INSERT INTO entity_relations (source_entity_id, target_entity_id, relation_type, strength, evidence_memory_ids)
4913
+ VALUES (p_src, p_tgt, p_type, LEAST(1.0, p_strength),
4914
+ CASE WHEN p_evidence IS NULL THEN '{}'::bigint[] ELSE ARRAY[p_evidence] END)
4915
+ ON CONFLICT (source_entity_id, target_entity_id, relation_type) DO UPDATE
4916
+ SET strength = LEAST(1.0, entity_relations.strength +
4917
+ CASE WHEN p_evidence IS NULL OR p_evidence = ANY(entity_relations.evidence_memory_ids) THEN 0.0 ELSE 0.1 END),
4918
+ evidence_memory_ids = (SELECT ARRAY(SELECT DISTINCT e FROM unnest(entity_relations.evidence_memory_ids || EXCLUDED.evidence_memory_ids) AS e));
4919
+ $uerfn$;
4920
+
4921
+ -- Memory 3.0 C1 (migration 046): reconciliation SHADOW decision log. MIRROR \u2014 byte-equivalent
4922
+ -- to migration 046. Records a PROPOSED reconcile op per write without applying it, so enforce
4923
+ -- can be greenlit from a labeled sample. Deliberately NOT added to CORE_TABLES (dormant,
4924
+ -- default-off; must not trip SCHEMA DRIFT at boot on an un-migrated prod box \u2014 C2 precedent).
4925
+ CREATE TABLE IF NOT EXISTS memory_reconciliation_log (
4926
+ id BIGSERIAL PRIMARY KEY,
4927
+ memory_id BIGINT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
4928
+ owner_wallet TEXT,
4929
+ mode TEXT NOT NULL DEFAULT 'shadow' CHECK (mode IN ('shadow','enforce')),
4930
+ proposed_op TEXT NOT NULL CHECK (proposed_op IN ('add','update','noop','needs_router','skip')),
4931
+ target_memory_id BIGINT,
4932
+ max_cosine REAL,
4933
+ band TEXT CHECK (band IN ('hi','mid','lo','none')),
4934
+ router_used BOOLEAN NOT NULL DEFAULT false,
4935
+ router_model TEXT,
4936
+ fact_key TEXT,
4937
+ reason TEXT,
4938
+ label TEXT,
4939
+ labeled_at TIMESTAMPTZ,
4940
+ gate_version TEXT,
4941
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
4942
+ );
4943
+ CREATE INDEX IF NOT EXISTS idx_reconcile_log_owner_created ON memory_reconciliation_log(owner_wallet, created_at);
4944
+ CREATE INDEX IF NOT EXISTS idx_reconcile_log_op ON memory_reconciliation_log(proposed_op);
4945
+ CREATE INDEX IF NOT EXISTS idx_reconcile_log_router ON memory_reconciliation_log(mode, router_used);
4946
+
4947
+ DO $do$
4948
+ BEGIN
4949
+ IF NOT EXISTS (
4950
+ SELECT 1 FROM information_schema.columns
4951
+ WHERE table_name = 'memories' AND column_name = 'ts_summary'
4952
+ ) THEN
4953
+ ALTER TABLE memories ADD COLUMN ts_summary tsvector GENERATED ALWAYS AS (
4954
+ setweight(to_tsvector('english', COALESCE(summary, '')), 'A')
4955
+ ) STORED;
4956
+ CREATE INDEX IF NOT EXISTS idx_memories_ts_summary ON memories USING GIN(ts_summary);
4957
+ END IF;
4958
+ END $do$;
4959
+
4960
+ -- Semantic search across memory-level embeddings with metadata filtering (the always-on
4961
+ -- recall vector lane). MIRROR of migration 028 / supabase-schema.sql \u2014 8-arg filter_tags form.
4962
+ CREATE OR REPLACE FUNCTION match_memories(
4963
+ query_embedding vector(1024),
4964
+ match_threshold float DEFAULT 0.3,
4965
+ match_count int DEFAULT 10,
4966
+ filter_types text[] DEFAULT NULL,
4967
+ filter_user text DEFAULT NULL,
4968
+ min_decay float DEFAULT 0.1,
4969
+ filter_owner text DEFAULT NULL,
4970
+ filter_tags text[] DEFAULT NULL
4971
+ )
4972
+ RETURNS TABLE (id bigint, similarity float)
4973
+ LANGUAGE plpgsql AS $$
4974
+ BEGIN
4975
+ RETURN QUERY
4976
+ SELECT m.id, (1 - (m.embedding <=> query_embedding))::float AS similarity
4977
+ FROM memories m
4978
+ WHERE m.embedding IS NOT NULL
4979
+ AND m.decay_factor >= min_decay
4980
+ AND (filter_types IS NULL OR m.memory_type = ANY(filter_types))
4981
+ AND (filter_user IS NULL OR m.related_user = filter_user)
4982
+ AND (
4983
+ filter_owner IS NULL
4984
+ OR (filter_owner = '__BOT_OWN__' AND m.owner_wallet IS NULL)
4985
+ OR m.owner_wallet = filter_owner
4986
+ )
4987
+ AND (filter_tags IS NULL OR m.tags && filter_tags)
4988
+ AND (1 - (m.embedding <=> query_embedding)) > match_threshold
4989
+ ORDER BY m.embedding <=> query_embedding
4990
+ LIMIT match_count;
4991
+ END;
4992
+ $$;
4993
+
4534
4994
  -- Temporal-aware semantic search RPC (Exp 9)
4535
4995
  CREATE OR REPLACE FUNCTION match_memories_temporal(
4536
4996
  query_embedding vector(1024),
@@ -4554,7 +5014,11 @@ async function initDatabase2() {
4554
5014
  AND m.decay_factor >= min_decay
4555
5015
  AND (filter_types IS NULL OR m.memory_type = ANY(filter_types))
4556
5016
  AND (filter_user IS NULL OR m.related_user = filter_user)
4557
- AND (filter_owner IS NULL OR m.owner_wallet = filter_owner)
5017
+ AND (
5018
+ filter_owner IS NULL
5019
+ OR (filter_owner = '__BOT_OWN__' AND m.owner_wallet IS NULL)
5020
+ OR m.owner_wallet = filter_owner
5021
+ )
4558
5022
  AND (filter_tags IS NULL OR m.tags && filter_tags)
4559
5023
  AND (1 - (m.embedding <=> query_embedding)) > match_threshold
4560
5024
  AND (start_date IS NULL OR COALESCE(m.event_date, m.created_at) >= start_date)
@@ -4564,8 +5028,124 @@ async function initDatabase2() {
4564
5028
  END;
4565
5029
  $$;
4566
5030
 
4567
- -- BM25-ranked full-text search RPC (Exp 8)
4568
- -- Note: ts_summary column requires manual migration (GENERATED ALWAYS AS is not ALTER-able)
5031
+ -- Fragment-level semantic search with deduplication to parent memory. Returns the highest
5032
+ -- similarity fragment per memory (the non-skipExpansion recall lane). MIRROR of
5033
+ -- supabase-schema.sql \u2014 6-arg migration-043 form; the 4-arg (migration 009) call still
5034
+ -- resolves against it via parameter defaults, so a boot-provisioned box is never left on a
5035
+ -- stale fragment signature.
5036
+ CREATE OR REPLACE FUNCTION match_memory_fragments(
5037
+ query_embedding vector(1024),
5038
+ match_threshold float DEFAULT 0.3,
5039
+ match_count int DEFAULT 10,
5040
+ filter_owner text DEFAULT NULL,
5041
+ min_decay float DEFAULT 0.0,
5042
+ filter_types text[] DEFAULT NULL
5043
+ )
5044
+ RETURNS TABLE (memory_id bigint, max_similarity float)
5045
+ LANGUAGE plpgsql AS $$
5046
+ BEGIN
5047
+ RETURN QUERY
5048
+ SELECT f.memory_id, MAX((1 - (f.embedding <=> query_embedding))::float) AS max_similarity
5049
+ FROM memory_fragments f
5050
+ JOIN memories m ON m.id = f.memory_id
5051
+ WHERE f.embedding IS NOT NULL
5052
+ AND (1 - (f.embedding <=> query_embedding)) > match_threshold
5053
+ AND m.decay_factor >= min_decay
5054
+ AND (filter_types IS NULL OR m.memory_type = ANY(filter_types))
5055
+ AND (
5056
+ filter_owner IS NULL
5057
+ OR (filter_owner = '__BOT_OWN__' AND m.owner_wallet IS NULL)
5058
+ OR m.owner_wallet = filter_owner
5059
+ )
5060
+ GROUP BY f.memory_id
5061
+ ORDER BY max_similarity DESC
5062
+ LIMIT match_count;
5063
+ END;
5064
+ $$;
5065
+
5066
+ -- Encryption: owner key registry + per-memory wrapped DEKs (encryption \xA79, Plan 1 sync).
5067
+ CREATE TABLE IF NOT EXISTS encryption_keys (
5068
+ owner_wallet TEXT PRIMARY KEY,
5069
+ x25519_pubkey TEXT NOT NULL,
5070
+ verifier_ct TEXT NOT NULL,
5071
+ created_at TIMESTAMPTZ DEFAULT NOW(),
5072
+ updated_at TIMESTAMPTZ DEFAULT NOW()
5073
+ );
5074
+ CREATE TABLE IF NOT EXISTS memory_dek_wraps (
5075
+ memory_id BIGINT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
5076
+ recipient TEXT NOT NULL,
5077
+ wrapped_dek TEXT NOT NULL,
5078
+ wrap_pubkey TEXT NOT NULL,
5079
+ holder_wallet TEXT, -- (034) names the title_holder; NULL for owner/provider
5080
+ created_at TIMESTAMPTZ DEFAULT NOW(),
5081
+ CONSTRAINT memory_dek_wraps_recipient_chk CHECK (recipient IN ('owner', 'provider', 'title_holder')),
5082
+ CONSTRAINT memory_dek_wraps_holder_chk CHECK ((recipient = 'title_holder') = (holder_wallet IS NOT NULL))
5083
+ );
5084
+ -- (036) holder-aware uniqueness: one owner + one provider per memory (NULLS NOT DISTINCT),
5085
+ -- and one title_holder per (memory, holder) so a sale's seller + buyer wraps coexist (RT7).
5086
+ CREATE UNIQUE INDEX IF NOT EXISTS uq_dek_wraps_identity
5087
+ ON memory_dek_wraps (memory_id, recipient, holder_wallet) NULLS NOT DISTINCT;
5088
+ CREATE INDEX IF NOT EXISTS idx_dek_wraps_memory ON memory_dek_wraps(memory_id);
5089
+
5090
+ -- Populate content_tokens from a transient plaintext arg (PostgREST can't express to_tsvector inline).
5091
+ -- setweight 'B' mirrors the old combined ts_summary weighting (summary 'A' > content 'B').
5092
+ CREATE OR REPLACE FUNCTION set_memory_content_tokens(p_memory_id bigint, p_text text)
5093
+ RETURNS void LANGUAGE sql AS $fn$
5094
+ UPDATE memories
5095
+ SET content_tokens = CASE
5096
+ WHEN p_text IS NULL OR p_text = '' THEN NULL
5097
+ ELSE setweight(to_tsvector('english', p_text), 'B')
5098
+ END
5099
+ WHERE id = p_memory_id;
5100
+ $fn$;
5101
+
5102
+ -- Atomic revoke: clear plaintext + drop the provider wrap in one transaction (encryption \xA77).
5103
+ CREATE OR REPLACE FUNCTION revoke_memory(p_memory_id bigint, p_summary_ct text, p_embedding_ct text)
5104
+ RETURNS void LANGUAGE plpgsql AS $rev$
5105
+ BEGIN
5106
+ UPDATE memories SET
5107
+ summary = '',
5108
+ summary_ciphertext = p_summary_ct,
5109
+ embedding = NULL,
5110
+ embedding_ciphertext = NULLIF(p_embedding_ct, ''),
5111
+ content_tokens = NULL,
5112
+ provider_delegated = false
5113
+ WHERE id = p_memory_id;
5114
+ DELETE FROM memory_dek_wraps WHERE memory_id = p_memory_id AND recipient = 'provider';
5115
+ -- Fragment parity (migration 043): fragments hold plaintext + live embeddings.
5116
+ DELETE FROM memory_fragments WHERE memory_id = p_memory_id;
5117
+ END;
5118
+ $rev$;
5119
+
5120
+ -- Atomic re-delegate (encryption \xA77) \u2014 inverse of revoke_memory. Restores plaintext
5121
+ -- summary/embedding, rebuilds content_tokens via the canonical builder, clears ciphertext
5122
+ -- cols, sets provider_delegated=true, (re-)inserts the provider wrap.
5123
+ CREATE OR REPLACE FUNCTION redelegate_memory(
5124
+ p_memory_id bigint,
5125
+ p_summary text,
5126
+ p_embedding text,
5127
+ p_content text,
5128
+ p_wrapped_dek text,
5129
+ p_wrap_pubkey text
5130
+ )
5131
+ RETURNS void LANGUAGE plpgsql AS $redel$
5132
+ BEGIN
5133
+ UPDATE memories SET
5134
+ summary = p_summary,
5135
+ summary_ciphertext = NULL,
5136
+ embedding = NULLIF(p_embedding, '')::vector,
5137
+ embedding_ciphertext = NULL,
5138
+ provider_delegated = true
5139
+ WHERE id = p_memory_id;
5140
+ PERFORM set_memory_content_tokens(p_memory_id, p_content);
5141
+ INSERT INTO memory_dek_wraps (memory_id, recipient, wrapped_dek, wrap_pubkey)
5142
+ VALUES (p_memory_id, 'provider', p_wrapped_dek, p_wrap_pubkey)
5143
+ ON CONFLICT (memory_id, recipient) DO UPDATE
5144
+ SET wrapped_dek = EXCLUDED.wrapped_dek, wrap_pubkey = EXCLUDED.wrap_pubkey;
5145
+ END;
5146
+ $redel$;
5147
+
5148
+ -- BM25-ranked full-text search RPC (Exp 8) \u2014 dual-column (ts_summary + content_tokens)
4569
5149
  CREATE OR REPLACE FUNCTION bm25_search_memories(
4570
5150
  search_query text,
4571
5151
  match_count int DEFAULT 20,
@@ -4584,11 +5164,17 @@ async function initDatabase2() {
4584
5164
  RETURN;
4585
5165
  END IF;
4586
5166
  RETURN QUERY
4587
- SELECT m.id, ts_rank_cd(m.ts_summary, tsquery_val, 32)::float AS rank
5167
+ SELECT m.id,
5168
+ (ts_rank_cd(COALESCE(m.ts_summary, ''::tsvector), tsquery_val, 32)
5169
+ + ts_rank_cd(COALESCE(m.content_tokens, ''::tsvector), tsquery_val, 32))::float AS rank
4588
5170
  FROM memories m
4589
- WHERE m.ts_summary @@ tsquery_val
5171
+ WHERE (m.ts_summary @@ tsquery_val OR m.content_tokens @@ tsquery_val)
4590
5172
  AND m.decay_factor >= min_decay
4591
- AND (filter_owner IS NULL OR m.owner_wallet = filter_owner)
5173
+ AND (
5174
+ filter_owner IS NULL
5175
+ OR (filter_owner = '__BOT_OWN__' AND m.owner_wallet IS NULL)
5176
+ OR m.owner_wallet = filter_owner
5177
+ )
4592
5178
  AND (filter_types IS NULL OR m.memory_type = ANY(filter_types))
4593
5179
  AND (filter_tags IS NULL OR m.tags && filter_tags)
4594
5180
  ORDER BY rank DESC
@@ -4617,9 +5203,31 @@ async function initDatabase2() {
4617
5203
  tokens_prompt INTEGER,
4618
5204
  tokens_completion INTEGER,
4619
5205
  memory_ids INTEGER[],
5206
+ frontier_tokens INTEGER,
5207
+ memories_used INTEGER,
5208
+ tokens_saved INTEGER,
4620
5209
  created_at TIMESTAMPTZ DEFAULT NOW()
4621
5210
  );
4622
5211
  CREATE INDEX IF NOT EXISTS idx_chat_msg_conv ON chat_messages(conversation_id, created_at);
5212
+ CREATE INDEX IF NOT EXISTS idx_chat_msg_saved ON chat_messages(created_at) WHERE tokens_saved IS NOT NULL;
5213
+ -- Proof counter totals. "today" is UTC-based (resets 00:00 UTC). Full-table aggregate:
5214
+ -- callers MUST use a server-side TTL cache (see proof.routes.ts). Mirror of migration 026.
5215
+ CREATE OR REPLACE FUNCTION proof_tokens_saved_totals()
5216
+ RETURNS TABLE(
5217
+ measured_saved bigint,
5218
+ measured_today bigint,
5219
+ measured_frontier bigint,
5220
+ historical_prompt_sum bigint,
5221
+ n bigint
5222
+ ) LANGUAGE sql STABLE AS $$
5223
+ SELECT
5224
+ COALESCE(SUM(tokens_saved) FILTER (WHERE tokens_saved IS NOT NULL), 0)::bigint,
5225
+ COALESCE(SUM(tokens_saved) FILTER (WHERE tokens_saved IS NOT NULL AND created_at >= date_trunc('day', now())), 0)::bigint,
5226
+ COALESCE(SUM(frontier_tokens) FILTER (WHERE tokens_saved IS NOT NULL), 0)::bigint,
5227
+ COALESCE(SUM(tokens_prompt) FILTER (WHERE tokens_saved IS NULL), 0)::bigint,
5228
+ COUNT(*) FILTER (WHERE tokens_saved IS NOT NULL)::bigint
5229
+ FROM chat_messages;
5230
+ $$;
4623
5231
 
4624
5232
  -- Chat billing: balances, top-ups, and per-message usage
4625
5233
  CREATE TABLE IF NOT EXISTS chat_balances (
@@ -4674,6 +5282,50 @@ async function initDatabase2() {
4674
5282
  );
4675
5283
  CREATE INDEX IF NOT EXISTS idx_wiki_pack_installations_owner
4676
5284
  ON wiki_pack_installations(owner_wallet);
5285
+
5286
+ -- Migration 019: corrected access-boost RPC (no importance mutation on read).
5287
+ -- Re-asserted on every boot so the read->rank->read feedback loop can't return.
5288
+ -- importance_boosts is kept for call-site signature compatibility and ignored.
5289
+ -- Drop the stale single-arg overload (migration 003) so the two-arg version is
5290
+ -- unambiguous and the lockdown below can't be bypassed via an unrevoked overload.
5291
+ DROP FUNCTION IF EXISTS batch_boost_memory_access(bigint[]);
5292
+ CREATE OR REPLACE FUNCTION batch_boost_memory_access(
5293
+ memory_ids BIGINT[],
5294
+ importance_boosts DOUBLE PRECISION[] DEFAULT NULL
5295
+ ) RETURNS void LANGUAGE plpgsql AS $fn$
5296
+ BEGIN
5297
+ UPDATE memories
5298
+ SET access_count = access_count + 1,
5299
+ last_accessed = NOW(),
5300
+ decay_factor = LEAST(1.0, decay_factor + 0.1)
5301
+ WHERE id = ANY(memory_ids);
5302
+ END;
5303
+ $fn$;
5304
+
5305
+ -- Migration 020: lock down dangerous SECURITY DEFINER RPCs to service_role only.
5306
+ -- exec_sql/boost RPCs must never be callable by anon/authenticated (the publishable
5307
+ -- key ships in client apps). Wrapped so a not-yet-created function never aborts boot.
5308
+ DO $do$ BEGIN
5309
+ REVOKE EXECUTE ON FUNCTION exec_sql(text) FROM PUBLIC, anon, authenticated;
5310
+ GRANT EXECUTE ON FUNCTION exec_sql(text) TO service_role;
5311
+ EXCEPTION WHEN undefined_function THEN NULL; END $do$;
5312
+
5313
+ DO $do$ BEGIN
5314
+ REVOKE EXECUTE ON FUNCTION batch_boost_memory_access(bigint[], double precision[]) FROM PUBLIC, anon, authenticated;
5315
+ GRANT EXECUTE ON FUNCTION batch_boost_memory_access(bigint[], double precision[]) TO service_role;
5316
+ EXCEPTION WHEN undefined_function THEN NULL; END $do$;
5317
+
5318
+ DO $do$ BEGIN
5319
+ REVOKE EXECUTE ON FUNCTION boost_memory_importance(bigint, double precision, double precision) FROM PUBLIC, anon, authenticated;
5320
+ GRANT EXECUTE ON FUNCTION boost_memory_importance(bigint, double precision, double precision) TO service_role;
5321
+ EXCEPTION WHEN undefined_function THEN NULL; END $do$;
5322
+
5323
+ -- Migration 022 (guard portion): reject empty content on NEW writes in every env.
5324
+ -- NOT VALID enforces on new INSERT/UPDATE without scanning existing rows; the
5325
+ -- one-time cleanup of legacy blank rows + VALIDATE lives in the manual 022 file.
5326
+ DO $do$ BEGIN
5327
+ ALTER TABLE memories ADD CONSTRAINT memories_content_nonempty CHECK (length(btrim(content)) > 0) NOT VALID;
5328
+ EXCEPTION WHEN duplicate_object THEN NULL; END $do$;
4677
5329
  `
4678
5330
  });
4679
5331
  if (error) {
@@ -4682,7 +5334,75 @@ async function initDatabase2() {
4682
5334
  } catch {
4683
5335
  log2.warn("rpc exec_sql not available. Create tables via Supabase SQL editor.");
4684
5336
  }
4685
- log2.info("Database initialized");
5337
+ }
5338
+ function getSchemaDriftReport() {
5339
+ return lastSchemaReport;
5340
+ }
5341
+ async function verifyCoreSchema(db) {
5342
+ const missingTables = [];
5343
+ const probeErrors = [];
5344
+ for (const table of CORE_TABLES) {
5345
+ try {
5346
+ const { error } = await db.from(table).select("id", { head: true, count: "exact" }).limit(1);
5347
+ if (error) {
5348
+ if (MISSING_RE.test(error.message ?? "")) missingTables.push(table);
5349
+ else probeErrors.push(`${table}: ${error.message}`);
5350
+ }
5351
+ } catch (err) {
5352
+ probeErrors.push(`${table}: ${err instanceof Error ? err.message : String(err)}`);
5353
+ }
5354
+ }
5355
+ const brokenRpcs = [];
5356
+ for (const probe of CORE_RPC_PROBES) {
5357
+ try {
5358
+ const { error } = await db.rpc(probe.name, probe.args);
5359
+ if (error && MISSING_RE.test(error.message ?? "")) brokenRpcs.push(probe.name);
5360
+ } catch (err) {
5361
+ probeErrors.push(`${probe.name}: ${err instanceof Error ? err.message : String(err)}`);
5362
+ }
5363
+ }
5364
+ return { missingTables, brokenRpcs, probeErrors };
5365
+ }
5366
+ async function initDatabase2(dbOverride) {
5367
+ const db = dbOverride ?? getDb();
5368
+ const mode = process.env.INIT_DB_MODE ?? "auto";
5369
+ if (mode === "replay") {
5370
+ await runBootDdl(db);
5371
+ lastSchemaReport = { at: (/* @__PURE__ */ new Date()).toISOString(), status: "replayed", missingTables: [], brokenRpcs: [] };
5372
+ log2.info("Database initialized (legacy replay mode)");
5373
+ return;
5374
+ }
5375
+ let { missingTables, brokenRpcs, probeErrors } = await verifyCoreSchema(db);
5376
+ if (mode === "auto" && missingTables.includes("memories")) {
5377
+ log2.info("Fresh database detected (no memories table) \u2014 running boot DDL");
5378
+ await runBootDdl(db);
5379
+ ({ missingTables, brokenRpcs, probeErrors } = await verifyCoreSchema(db));
5380
+ const healthy2 = missingTables.length === 0 && brokenRpcs.length === 0;
5381
+ lastSchemaReport = {
5382
+ at: (/* @__PURE__ */ new Date()).toISOString(),
5383
+ status: healthy2 ? "fresh-bootstrapped" : "drift",
5384
+ missingTables,
5385
+ brokenRpcs
5386
+ };
5387
+ if (healthy2) log2.info("Database bootstrapped from boot DDL");
5388
+ else log2.error({ missingTables, brokenRpcs }, "SCHEMA DRIFT after fresh bootstrap \u2014 check exec_sql availability");
5389
+ return;
5390
+ }
5391
+ if (probeErrors.length > 0 && missingTables.length === 0 && brokenRpcs.length === 0) {
5392
+ lastSchemaReport = { at: (/* @__PURE__ */ new Date()).toISOString(), status: "unknown", missingTables, brokenRpcs };
5393
+ log2.warn({ probeErrors: probeErrors.slice(0, 3) }, "Schema verification inconclusive (probe errors); skipping");
5394
+ return;
5395
+ }
5396
+ const healthy = missingTables.length === 0 && brokenRpcs.length === 0;
5397
+ lastSchemaReport = { at: (/* @__PURE__ */ new Date()).toISOString(), status: healthy ? "ok" : "drift", missingTables, brokenRpcs };
5398
+ if (healthy) {
5399
+ log2.info("Database schema verified (boot blob frozen; migrations are the single schema writer)");
5400
+ } else {
5401
+ log2.error(
5402
+ { missingTables, brokenRpcs },
5403
+ "SCHEMA DRIFT \u2014 core objects missing on an established database; apply the corresponding migration by hand (the boot blob no longer auto-repairs)"
5404
+ );
5405
+ }
4686
5406
  }
4687
5407
  async function isAlreadyProcessed(tweetId) {
4688
5408
  const db = getDb();
@@ -4715,27 +5435,81 @@ async function markProcessed(tweetId, feature, responseTweetId, extra) {
4715
5435
  processed_at: (/* @__PURE__ */ new Date()).toISOString()
4716
5436
  });
4717
5437
  }
4718
- var import_supabase_js, log2, supabase;
5438
+ var import_supabase_js, log2, supabase, CORE_TABLES, CORE_RPC_PROBES, lastSchemaReport, MISSING_RE;
4719
5439
  var init_database = __esm({
4720
5440
  "packages/shared/src/core/database.ts"() {
4721
5441
  "use strict";
4722
5442
  import_supabase_js = require("@supabase/supabase-js");
4723
5443
  init_config();
4724
5444
  init_logger();
5445
+ init_migration_profile();
4725
5446
  log2 = createChildLogger("database");
5447
+ CORE_TABLES = [
5448
+ "memories",
5449
+ "memory_fragments",
5450
+ "memory_links",
5451
+ "agent_keys",
5452
+ "dream_logs",
5453
+ "rate_limits",
5454
+ "chat_conversations",
5455
+ "chat_messages"
5456
+ ];
5457
+ CORE_RPC_PROBES = [
5458
+ { name: "get_linked_memories", args: { seed_ids: [], min_strength: 0.1, max_results: 1, filter_owner: null } },
5459
+ { name: "bm25_search_memories", args: { search_query: "", match_count: 1, min_decay: 0.1, filter_owner: null } },
5460
+ // Vector recall lanes (memory.ts recall). match_memories is the always-on memory-level lane;
5461
+ // match_memory_fragments is the fragment lane (non-skipExpansion path). Both are silently absent
5462
+ // from a stale boot blob, so probe them so the gap surfaces as drift instead of a dead lane.
5463
+ // Vector-safe args (null embedding + match_count 0) make each a no-op read. filter_tags pins the
5464
+ // migration-028 8-arg match_memories overload recall calls unconditionally (catches a pre-028 box
5465
+ // where recall would break); the fragment probe stays the 4/6-arg common subset (min_decay +
5466
+ // filter_types are opt-in via MEMORY_FRAGMENT_FILTERS) so it never false-drifts a pre-043 box
5467
+ // still serving the migration-009 4-arg form.
5468
+ { name: "match_memories", args: { query_embedding: null, match_count: 0, filter_owner: null, filter_tags: null } },
5469
+ { name: "match_memory_fragments", args: { query_embedding: null, match_count: 0, filter_owner: null } },
5470
+ // Memory 3.0 C2: probe the outbox claim RPC so a table-without-RPC state (the §6 hazard) is
5471
+ // caught at boot as drift rather than silently never draining.
5472
+ { name: "claim_memory_write_jobs", args: { p_limit: 0 } }
5473
+ ];
5474
+ lastSchemaReport = null;
5475
+ MISSING_RE = /does not exist|could not find|schema cache/i;
4726
5476
  }
4727
5477
  });
4728
5478
 
4729
- // packages/shared/src/utils/format.ts
4730
- function timeAgo2(dateStr) {
4731
- const ms = Date.now() - new Date(dateStr).getTime();
4732
- const hours = Math.floor(ms / (1e3 * 60 * 60));
4733
- if (hours < 1) return "just now";
4734
- if (hours < 24) return `${hours}h ago`;
4735
- const days = Math.floor(hours / 24);
4736
- if (days < 7) return `${days}d ago`;
4737
- return `${Math.floor(days / 7)}w ago`;
5479
+ // packages/shared/src/core/memory-grounding.ts
5480
+ function byMemoryDateAsc(a, b) {
5481
+ const ta = a.created_at ? Date.parse(a.created_at) : NaN;
5482
+ const tb = b.created_at ? Date.parse(b.created_at) : NaN;
5483
+ if (Number.isNaN(ta) && Number.isNaN(tb)) return 0;
5484
+ if (Number.isNaN(ta)) return 1;
5485
+ if (Number.isNaN(tb)) return -1;
5486
+ return ta - tb;
5487
+ }
5488
+ function renderGroundedLine(m, suffix = "") {
5489
+ const date = m.created_at ? new Date(m.created_at).toISOString().slice(0, 10) : "";
5490
+ const stamp = date ? `[${date}] ` : "";
5491
+ const summary = (m.summary || "").trim();
5492
+ const content = (m.content || "").trim();
5493
+ let body;
5494
+ if (!content || content === summary) {
5495
+ body = summary || content;
5496
+ } else if (content.length <= GROUNDED_CONTENT_MAX) {
5497
+ body = summary ? `${summary} \u2014 ${content}` : content;
5498
+ } else {
5499
+ const slice = content.slice(0, GROUNDED_CONTENT_MAX);
5500
+ body = summary ? `${summary} \u2014 ${slice}\u2026` : `${slice}\u2026`;
5501
+ }
5502
+ return `- ${stamp}${body}${suffix}`;
4738
5503
  }
5504
+ var GROUNDED_CONTENT_MAX;
5505
+ var init_memory_grounding = __esm({
5506
+ "packages/shared/src/core/memory-grounding.ts"() {
5507
+ "use strict";
5508
+ GROUNDED_CONTENT_MAX = 600;
5509
+ }
5510
+ });
5511
+
5512
+ // packages/shared/src/utils/format.ts
4739
5513
  function clamp(val, min, max) {
4740
5514
  return Math.max(min, Math.min(max, val));
4741
5515
  }
@@ -4753,7 +5527,7 @@ var init_text = __esm({
4753
5527
  });
4754
5528
 
4755
5529
  // packages/shared/src/utils/constants.ts
4756
- var MEMO_PROGRAM_ID, SOLSCAN_TX_BASE_URL, 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;
5530
+ var MEMO_PROGRAM_ID, SOLSCAN_TX_BASE_URL, 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;
4757
5531
  var init_constants = __esm({
4758
5532
  "packages/shared/src/utils/constants.ts"() {
4759
5533
  "use strict";
@@ -4776,10 +5550,11 @@ var init_constants = __esm({
4776
5550
  // Journal entries persist like knowledge
4777
5551
  };
4778
5552
  RECENCY_DECAY_BASE = 0.995;
4779
- RETRIEVAL_WEIGHT_RECENCY = 1;
4780
- RETRIEVAL_WEIGHT_RELEVANCE = 2;
4781
- RETRIEVAL_WEIGHT_IMPORTANCE = 2;
4782
- RETRIEVAL_WEIGHT_VECTOR = 4;
5553
+ RETRIEVAL_WEIGHT_RECENCY = Number(process.env.RETRIEVAL_WEIGHT_RECENCY ?? "1.0");
5554
+ RETRIEVAL_WEIGHT_RELEVANCE = Number(process.env.RETRIEVAL_WEIGHT_RELEVANCE ?? "2.0");
5555
+ RETRIEVAL_WEIGHT_IMPORTANCE = Number(process.env.RETRIEVAL_WEIGHT_IMPORTANCE ?? "2.0");
5556
+ RETRIEVAL_WEIGHT_VECTOR = Number(process.env.RETRIEVAL_WEIGHT_VECTOR ?? "4.0");
5557
+ RETRIEVAL_WEIGHT_BM25 = Number(process.env.RETRIEVAL_WEIGHT_BM25 ?? "0.15");
4783
5558
  KNOWLEDGE_TYPE_BOOST = {
4784
5559
  semantic: 0.15,
4785
5560
  // Distilled knowledge gets meaningful boost
@@ -4796,6 +5571,8 @@ var init_constants = __esm({
4796
5571
  BOND_TYPE_WEIGHTS = {
4797
5572
  causes: 1,
4798
5573
  supports: 0.9,
5574
+ supersedes: 0.85,
5575
+ // decisive replacement: new fact overrides an older one (Memory 3.0 C1)
4799
5576
  concurrent_with: 0.8,
4800
5577
  resolves: 0.8,
4801
5578
  happens_before: 0.7,
@@ -4820,7 +5597,7 @@ var init_constants = __esm({
4820
5597
  "dream-cycle",
4821
5598
  "meditation"
4822
5599
  ]);
4823
- INTERNAL_IMPORTANCE_BOOST = 5e-3;
5600
+ EMBEDDING_FRAGMENT_MAX_LENGTH = 2e3;
4824
5601
  REFLECTION_MIN_INTERVAL_MS = 30 * 60 * 1e3;
4825
5602
  WHALE_SELL_COOLDOWN_MS = 30 * 60 * 1e3;
4826
5603
  MEMO_MAX_LENGTH = 100;
@@ -4857,6 +5634,17 @@ var init_guardrails = __esm({
4857
5634
  }
4858
5635
  });
4859
5636
 
5637
+ // packages/shared/src/core/model-capabilities.ts
5638
+ function modelRejectsSamplingParams(model) {
5639
+ if (!model) return false;
5640
+ return /opus[-_.]?4[-_.]?[789]\b|opus[-_.]?[5-9]\b/i.test(model);
5641
+ }
5642
+ var init_model_capabilities = __esm({
5643
+ "packages/shared/src/core/model-capabilities.ts"() {
5644
+ "use strict";
5645
+ }
5646
+ });
5647
+
4860
5648
  // packages/shared/src/core/openrouter-client.ts
4861
5649
  function getModelForFunction(fn) {
4862
5650
  return COGNITIVE_MODEL_MAP[fn] || OPENROUTER_MODELS["llama-70b"];
@@ -4923,7 +5711,8 @@ async function generateOpenRouterResponse(opts) {
4923
5711
  model,
4924
5712
  messages,
4925
5713
  max_tokens: maxTokens,
4926
- temperature: opts.temperature ?? 0.7
5714
+ // Opus 4.7+ via OpenRouter rejects temperature/top_p strip for those models.
5715
+ ...modelRejectsSamplingParams(model) ? {} : { temperature: opts.temperature ?? 0.7 }
4927
5716
  })
4928
5717
  });
4929
5718
  if (!response.ok) {
@@ -4954,6 +5743,7 @@ var init_openrouter_client = __esm({
4954
5743
  "packages/shared/src/core/openrouter-client.ts"() {
4955
5744
  "use strict";
4956
5745
  init_logger();
5746
+ init_model_capabilities();
4957
5747
  log4 = createChildLogger("openrouter");
4958
5748
  OPENROUTER_API_URL = "https://openrouter.ai/api/v1";
4959
5749
  OPENROUTER_MODELS = {
@@ -5028,10 +5818,11 @@ function getModel() {
5028
5818
  }
5029
5819
  async function generateImportanceScore(description) {
5030
5820
  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.";
5821
+ const scoreModel = getModel();
5031
5822
  const response = await getClient().messages.create({
5032
- model: getModel(),
5823
+ model: scoreModel,
5033
5824
  max_tokens: 10,
5034
- temperature: 0,
5825
+ ...modelRejectsSamplingParams(scoreModel) ? {} : { temperature: 0 },
5035
5826
  system: importancePrompt,
5036
5827
  messages: [{
5037
5828
  role: "user",
@@ -5052,6 +5843,7 @@ var init_claude_client = __esm({
5052
5843
  init_guardrails();
5053
5844
  init_openrouter_client();
5054
5845
  init_constants();
5846
+ init_model_capabilities();
5055
5847
  log5 = createChildLogger("claude-client");
5056
5848
  anthropic = null;
5057
5849
  try {
@@ -5380,7 +6172,7 @@ function stableStringify(value) {
5380
6172
  }
5381
6173
  if (typeof value === "object") {
5382
6174
  const obj = value;
5383
- const keys = Object.keys(obj).sort();
6175
+ const keys = Object.keys(obj).filter((k) => obj[k] !== void 0).sort();
5384
6176
  const pairs = keys.map((k) => JSON.stringify(k) + ":" + stableStringify(obj[k]));
5385
6177
  return "{" + pairs.join(",") + "}";
5386
6178
  }
@@ -5423,9 +6215,12 @@ var init_content_hash = __esm({
5423
6215
  });
5424
6216
 
5425
6217
  // packages/tokenization/src/pack-merkle.ts
6218
+ var LEAF_PREFIX, INNER_PREFIX;
5426
6219
  var init_pack_merkle = __esm({
5427
6220
  "packages/tokenization/src/pack-merkle.ts"() {
5428
6221
  "use strict";
6222
+ LEAF_PREFIX = Buffer.from([0]);
6223
+ INNER_PREFIX = Buffer.from([1]);
5429
6224
  }
5430
6225
  });
5431
6226
 
@@ -5621,7 +6416,73 @@ async function generateEmbeddings(texts) {
5621
6416
  return texts.map(() => null);
5622
6417
  }
5623
6418
  }
5624
- var log7, PROVIDERS, _enabled, _overrideConfig, EMBEDDING_CACHE_MAX, EMBEDDING_CACHE_TTL_MS, _embeddingCache;
6419
+ async function _fetchMetadataToken() {
6420
+ const res = await fetch(METADATA_TOKEN_URL, { headers: { "Metadata-Flavor": "Google" } });
6421
+ if (!res.ok) throw new Error(`Vertex metadata token fetch failed: ${res.status}`);
6422
+ const j = await res.json();
6423
+ return { token: j.access_token, expiresInSec: j.expires_in };
6424
+ }
6425
+ async function getVertexAccessToken() {
6426
+ const override = config.vertex.accessToken;
6427
+ if (override) return override;
6428
+ const now = Date.now();
6429
+ if (_vertexToken && _vertexToken.expiresAtMs > now + 6e4) return _vertexToken.token;
6430
+ const { token, expiresInSec } = await _fetchMetadataToken();
6431
+ _vertexToken = { token, expiresAtMs: now + expiresInSec * 1e3 };
6432
+ return token;
6433
+ }
6434
+ function vertexPredictUrl(model) {
6435
+ const { project, location } = config.vertex;
6436
+ return `https://${location}-aiplatform.googleapis.com/v1/projects/${project}/locations/${location}/publishers/google/models/${model}:predict`;
6437
+ }
6438
+ function isVertexConfigured() {
6439
+ return !!config.vertex.project;
6440
+ }
6441
+ async function generateVertexEmbeddings(texts, dimensions) {
6442
+ if (texts.length === 0) return [];
6443
+ const { project, model, dimensions: defaultDims } = config.vertex;
6444
+ if (!project) {
6445
+ log7.warn("Vertex embeddings requested but VERTEX_PROJECT is unset");
6446
+ return texts.map(() => null);
6447
+ }
6448
+ const outputDimensionality = dimensions ?? defaultDims;
6449
+ const startMs = Date.now();
6450
+ try {
6451
+ const token = await getVertexAccessToken();
6452
+ const res = await fetch(vertexPredictUrl(model), {
6453
+ method: "POST",
6454
+ headers: {
6455
+ "Content-Type": "application/json",
6456
+ "Authorization": `Bearer ${token}`
6457
+ },
6458
+ body: JSON.stringify({
6459
+ instances: texts.map((t) => ({ content: t.slice(0, 8e3) })),
6460
+ parameters: { outputDimensionality }
6461
+ })
6462
+ });
6463
+ if (!res.ok) {
6464
+ const errText = await res.text();
6465
+ log7.error({ status: res.status, body: errText.slice(0, 200), provider: "vertex" }, "Vertex embedding API error");
6466
+ return texts.map(() => null);
6467
+ }
6468
+ const data = await res.json();
6469
+ const preds = data.predictions ?? [];
6470
+ log7.debug({ provider: "vertex", model, count: texts.length, elapsed: Date.now() - startMs }, "Vertex embeddings generated");
6471
+ return texts.map((_, i) => preds[i]?.embeddings?.values ?? null);
6472
+ } catch (err) {
6473
+ log7.error({ err, provider: "vertex" }, "Vertex embedding generation failed");
6474
+ return texts.map(() => null);
6475
+ }
6476
+ }
6477
+ async function generateVertexEmbedding(text, dimensions) {
6478
+ const [vec] = await generateVertexEmbeddings([text], dimensions);
6479
+ return vec ?? null;
6480
+ }
6481
+ async function generateQueryEmbeddingForSpace(space, text) {
6482
+ if (space === "vertex") return generateVertexEmbedding(text);
6483
+ return generateQueryEmbedding(text);
6484
+ }
6485
+ var log7, PROVIDERS, _enabled, _overrideConfig, EMBEDDING_CACHE_MAX, EMBEDDING_CACHE_TTL_MS, _embeddingCache, _vertexToken, METADATA_TOKEN_URL;
5625
6486
  var init_embeddings = __esm({
5626
6487
  "packages/shared/src/core/embeddings.ts"() {
5627
6488
  "use strict";
@@ -5654,6 +6515,8 @@ var init_embeddings = __esm({
5654
6515
  EMBEDDING_CACHE_MAX = 200;
5655
6516
  EMBEDDING_CACHE_TTL_MS = 30 * 60 * 1e3;
5656
6517
  _embeddingCache = /* @__PURE__ */ new Map();
6518
+ _vertexToken = null;
6519
+ METADATA_TOKEN_URL = "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token";
5657
6520
  }
5658
6521
  });
5659
6522
 
@@ -5957,78 +6820,619 @@ var init_bm25_search = __esm({
5957
6820
  }
5958
6821
  });
5959
6822
 
5960
- // packages/shared/src/core/encryption.ts
5961
- function isEncryptionEnabled() {
5962
- return encryptionKey !== null;
5963
- }
5964
- function getEncryptionPubkey() {
5965
- return encryptionPubkey;
5966
- }
5967
- function encryptContent(plaintext) {
5968
- if (!encryptionKey) {
5969
- throw new Error("Encryption not configured. Call configureEncryption() first.");
6823
+ // packages/brain/src/memory/content-tokens.ts
6824
+ async function setContentTokens(db, memoryId, plaintext) {
6825
+ try {
6826
+ const { error } = await db.rpc("set_memory_content_tokens", {
6827
+ p_memory_id: memoryId,
6828
+ p_text: plaintext
6829
+ });
6830
+ if (error) {
6831
+ log9.warn({ memoryId, error: error.message }, "set_memory_content_tokens failed (keyword recall degraded)");
6832
+ }
6833
+ } catch (err) {
6834
+ log9.warn({ memoryId, err }, "set_memory_content_tokens threw (keyword recall degraded)");
5970
6835
  }
5971
- const nonce = import_tweetnacl3.default.randomBytes(NONCE_LENGTH);
5972
- const messageBytes = new TextEncoder().encode(plaintext);
5973
- const ciphertext = import_tweetnacl3.default.secretbox(messageBytes, nonce, encryptionKey);
5974
- const combined = new Uint8Array(NONCE_LENGTH + ciphertext.length);
5975
- combined.set(nonce, 0);
5976
- combined.set(ciphertext, NONCE_LENGTH);
5977
- return Buffer.from(combined).toString("base64");
5978
6836
  }
5979
- function decryptContent(encrypted) {
5980
- if (!encryptionKey) {
5981
- return null;
6837
+ var log9;
6838
+ var init_content_tokens = __esm({
6839
+ "packages/brain/src/memory/content-tokens.ts"() {
6840
+ "use strict";
6841
+ init_logger();
6842
+ log9 = createChildLogger("content-tokens");
5982
6843
  }
6844
+ });
6845
+
6846
+ // packages/brain/src/memory/reconcile-router.ts
6847
+ function shortReason(x) {
6848
+ return typeof x === "string" ? x.slice(0, 200) : "";
6849
+ }
6850
+ function parseRouterReply(raw, candidateIds, model) {
6851
+ const fail = (reason) => ({ op: "add", targetId: null, reason, model });
6852
+ let obj;
5983
6853
  try {
5984
- const combined = Buffer.from(encrypted, "base64");
5985
- if (combined.length < NONCE_LENGTH + 1) {
5986
- return null;
5987
- }
5988
- const nonce = combined.subarray(0, NONCE_LENGTH);
5989
- const ciphertext = combined.subarray(NONCE_LENGTH);
5990
- const plaintext = import_tweetnacl3.default.secretbox.open(ciphertext, nonce, encryptionKey);
5991
- if (!plaintext) {
5992
- return null;
5993
- }
5994
- return new TextDecoder().decode(plaintext);
6854
+ obj = JSON.parse(raw);
5995
6855
  } catch {
5996
- return null;
6856
+ const m = raw.match(/\{[\s\S]*\}/);
6857
+ if (!m) return fail("router_parse_error");
6858
+ try {
6859
+ obj = JSON.parse(m[0]);
6860
+ } catch {
6861
+ return fail("router_parse_error");
6862
+ }
6863
+ }
6864
+ if (obj === null || typeof obj !== "object" || Array.isArray(obj)) return fail("router_invalid_shape");
6865
+ const rec = obj;
6866
+ const op = rec.op;
6867
+ if (op !== "add" && op !== "update" && op !== "noop") return fail("router_invalid_op");
6868
+ if (op === "add") return { op: "add", targetId: null, reason: shortReason(rec.reason), model };
6869
+ const rawTarget = rec.targetId ?? rec.target_id;
6870
+ const targetId = typeof rawTarget === "number" ? rawTarget : Number(rawTarget);
6871
+ if (!Number.isInteger(targetId) || !candidateIds.has(targetId)) return fail("router_invalid_target");
6872
+ return { op, targetId, reason: shortReason(rec.reason), model };
6873
+ }
6874
+ function buildPayload(newFact, candidates) {
6875
+ const lines = candidates.map(
6876
+ (c2) => `[id ${c2.id}] date: ${c2.eventDate ?? "unknown"} \u2014 ${c2.summary}`
6877
+ );
6878
+ return `NEW fact:
6879
+ date: ${newFact.eventDate ?? "unknown"} \u2014 ${newFact.summary}
6880
+
6881
+ Candidate facts:
6882
+ ${lines.join("\n")}`;
6883
+ }
6884
+ function isRouterConfigured() {
6885
+ return config.memory.reconcileRouter && !!config.memory.reconcileModel && isOpenRouterEnabled();
6886
+ }
6887
+ function utcDay() {
6888
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
6889
+ }
6890
+ function rollDay() {
6891
+ const d = utcDay();
6892
+ if (d !== budgetDay) {
6893
+ budgetDay = d;
6894
+ budgetByOwner.clear();
5997
6895
  }
5998
6896
  }
5999
- function decryptMemoryBatch(memories) {
6000
- if (!encryptionKey || !encryptionPubkey) return memories;
6001
- for (const mem of memories) {
6002
- if (!mem.encrypted) continue;
6003
- if (mem.encryption_pubkey && mem.encryption_pubkey !== encryptionPubkey) {
6004
- log9.debug({ memPubkey: mem.encryption_pubkey?.slice(0, 12) }, "Skipping memory encrypted by different key");
6005
- continue;
6006
- }
6007
- const decrypted = decryptContent(mem.content);
6008
- if (decrypted !== null) {
6009
- mem.content = decrypted;
6010
- } else {
6011
- log9.warn("Failed to decrypt memory content \u2014 wrong key or corrupted data");
6897
+ function budgetKey(owner) {
6898
+ return owner ?? "__BOT_OWN__";
6899
+ }
6900
+ function routerBudgetAvailable(owner) {
6901
+ rollDay();
6902
+ return (budgetByOwner.get(budgetKey(owner)) ?? 0) < config.memory.reconcileBudget;
6903
+ }
6904
+ function noteRouterCall(owner) {
6905
+ rollDay();
6906
+ const k = budgetKey(owner);
6907
+ budgetByOwner.set(k, (budgetByOwner.get(k) ?? 0) + 1);
6908
+ }
6909
+ async function routeReconcile(newFact, candidates) {
6910
+ const model = config.memory.reconcileModel;
6911
+ const candidateIds = new Set(candidates.map((c2) => c2.id));
6912
+ try {
6913
+ const raw = await generateOpenRouterResponse({
6914
+ systemPrompt: ROUTER_SYSTEM,
6915
+ messages: [{ role: "user", content: buildPayload(newFact, candidates) }],
6916
+ model,
6917
+ // explicit — never cognitiveFunction (which overrides model with the fast llama slot)
6918
+ maxTokens: 150,
6919
+ temperature: 0
6920
+ });
6921
+ return parseRouterReply(raw, candidateIds, model);
6922
+ } catch (err) {
6923
+ log10.warn({ err: err?.message }, "reconcile router call failed \u2014 defaulting to add");
6924
+ return { op: "add", targetId: null, reason: "router_error", model };
6925
+ }
6926
+ }
6927
+ var log10, ROUTER_SYSTEM, budgetDay, budgetByOwner;
6928
+ var init_reconcile_router = __esm({
6929
+ "packages/brain/src/memory/reconcile-router.ts"() {
6930
+ "use strict";
6931
+ init_config();
6932
+ init_logger();
6933
+ init_openrouter_client();
6934
+ log10 = createChildLogger("reconcile-router");
6935
+ 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.';
6936
+ budgetDay = "";
6937
+ budgetByOwner = /* @__PURE__ */ new Map();
6938
+ }
6939
+ });
6940
+
6941
+ // packages/brain/src/memory/reconcile-shadow.ts
6942
+ function logReconcileDegradeOnce(err, memoryId) {
6943
+ if (reconcileDegradeLogged) return;
6944
+ reconcileDegradeLogged = true;
6945
+ const e = err;
6946
+ log11.warn(
6947
+ { code: e?.code, message: e?.message, memoryId },
6948
+ "Reconcile shadow degraded (migration 046 unapplied?) \u2014 skipping; write unaffected"
6949
+ );
6950
+ }
6951
+ function gateVersion() {
6952
+ const { reconcileFloor: f, reconcileLo: lo, reconcileHi: hi } = config.memory;
6953
+ return `c1-shadow-v1:floor=${f}:lo=${lo}:hi=${hi}`;
6954
+ }
6955
+ async function insertShadowRow(row) {
6956
+ try {
6957
+ const { error } = await getDb().from("memory_reconciliation_log").insert(row);
6958
+ if (error) logReconcileDegradeOnce(error, row.memory_id);
6959
+ } catch (err) {
6960
+ logReconcileDegradeOnce(err, row.memory_id);
6961
+ }
6962
+ }
6963
+ async function runReconcileShadow(memoryId, scope) {
6964
+ if (process.env.BENCH_MODE === "true") return;
6965
+ const ownerWalletForRow = scope === SCOPE_BOT_OWN ? null : scope;
6966
+ const gv = gateVersion();
6967
+ const db = getDb();
6968
+ const skip = (reason) => insertShadowRow({
6969
+ memory_id: memoryId,
6970
+ owner_wallet: ownerWalletForRow,
6971
+ mode: "shadow",
6972
+ proposed_op: "skip",
6973
+ target_memory_id: null,
6974
+ max_cosine: null,
6975
+ band: "none",
6976
+ router_used: false,
6977
+ fact_key: null,
6978
+ reason,
6979
+ gate_version: gv
6980
+ });
6981
+ const add = (reason, maxCosine2, band2) => insertShadowRow({
6982
+ memory_id: memoryId,
6983
+ owner_wallet: ownerWalletForRow,
6984
+ mode: "shadow",
6985
+ proposed_op: "add",
6986
+ target_memory_id: null,
6987
+ max_cosine: maxCosine2,
6988
+ band: band2,
6989
+ router_used: false,
6990
+ fact_key: null,
6991
+ reason,
6992
+ gate_version: gv
6993
+ });
6994
+ let embedding;
6995
+ let newSummary = "";
6996
+ let newEventDate = null;
6997
+ try {
6998
+ const { data, error } = await db.from("memories").select("embedding, summary, event_date, created_at").eq("id", memoryId).maybeSingle();
6999
+ if (error) {
7000
+ logReconcileDegradeOnce(error, memoryId);
7001
+ return;
6012
7002
  }
7003
+ const row = data;
7004
+ embedding = row?.embedding ?? null;
7005
+ newSummary = row?.summary ?? "";
7006
+ newEventDate = row?.event_date ?? row?.created_at ?? null;
7007
+ } catch (err) {
7008
+ logReconcileDegradeOnce(err, memoryId);
7009
+ return;
6013
7010
  }
6014
- return memories;
7011
+ if (!embedding) {
7012
+ await skip("no_embedding");
7013
+ return;
7014
+ }
7015
+ const queryEmbedding = typeof embedding === "string" ? embedding : JSON.stringify(embedding);
7016
+ let candidates;
7017
+ try {
7018
+ const { data, error } = await db.rpc("match_memories_temporal", {
7019
+ query_embedding: queryEmbedding,
7020
+ match_threshold: config.memory.reconcileFloor,
7021
+ match_count: K_CANDIDATES,
7022
+ start_date: null,
7023
+ end_date: null,
7024
+ filter_types: null,
7025
+ filter_user: null,
7026
+ min_decay: 0,
7027
+ filter_owner: scope,
7028
+ filter_tags: null
7029
+ });
7030
+ if (error) {
7031
+ logReconcileDegradeOnce(error, memoryId);
7032
+ return;
7033
+ }
7034
+ candidates = data ?? [];
7035
+ } catch (err) {
7036
+ logReconcileDegradeOnce(err, memoryId);
7037
+ return;
7038
+ }
7039
+ candidates = candidates.filter((c2) => c2.id !== memoryId);
7040
+ if (candidates.length === 0) {
7041
+ await add("no_candidate", null, "none");
7042
+ return;
7043
+ }
7044
+ const ids = candidates.map((c2) => c2.id);
7045
+ let validIds;
7046
+ const hydratedById = /* @__PURE__ */ new Map();
7047
+ try {
7048
+ const base = db.from("memories").select("id, owner_wallet, invalid_at, summary, event_date, created_at").in("id", ids);
7049
+ const scoped = scope === SCOPE_BOT_OWN ? base.is("owner_wallet", null) : base.eq("owner_wallet", scope);
7050
+ const { data, error } = await scoped;
7051
+ if (error) {
7052
+ logReconcileDegradeOnce(error, memoryId);
7053
+ return;
7054
+ }
7055
+ const rows = data ?? [];
7056
+ const valid = rows.filter((r) => r.invalid_at == null).filter((r) => scope === SCOPE_BOT_OWN ? r.owner_wallet == null : r.owner_wallet === scope);
7057
+ validIds = new Set(valid.map((r) => r.id));
7058
+ for (const r of valid) hydratedById.set(r.id, { summary: r.summary ?? "", eventDate: r.event_date ?? r.created_at ?? null });
7059
+ } catch (err) {
7060
+ logReconcileDegradeOnce(err, memoryId);
7061
+ return;
7062
+ }
7063
+ const surviving = candidates.filter((c2) => validIds.has(c2.id));
7064
+ if (surviving.length === 0) {
7065
+ await add("no_valid_candidate", null, "none");
7066
+ return;
7067
+ }
7068
+ const maxCosine = surviving[0].similarity;
7069
+ const { reconcileLo: LO, reconcileHi: HI } = config.memory;
7070
+ if (maxCosine < LO) {
7071
+ await add("below_lo", maxCosine, "lo");
7072
+ return;
7073
+ }
7074
+ const band = maxCosine >= HI ? "hi" : "mid";
7075
+ const top = surviving[0];
7076
+ if (isRouterConfigured() && routerBudgetAvailable(ownerWalletForRow)) {
7077
+ noteRouterCall(ownerWalletForRow);
7078
+ const routerCandidates = surviving.map((c2) => ({
7079
+ id: c2.id,
7080
+ summary: hydratedById.get(c2.id)?.summary ?? "",
7081
+ eventDate: hydratedById.get(c2.id)?.eventDate ?? null
7082
+ }));
7083
+ const decision = await routeReconcile({ summary: newSummary, eventDate: newEventDate }, routerCandidates);
7084
+ await insertShadowRow({
7085
+ memory_id: memoryId,
7086
+ owner_wallet: ownerWalletForRow,
7087
+ mode: "shadow",
7088
+ proposed_op: decision.op,
7089
+ target_memory_id: decision.op === "add" ? null : decision.targetId,
7090
+ max_cosine: maxCosine,
7091
+ band,
7092
+ router_used: true,
7093
+ router_model: decision.model,
7094
+ fact_key: null,
7095
+ reason: decision.reason || "router",
7096
+ gate_version: gv
7097
+ });
7098
+ return;
7099
+ }
7100
+ await insertShadowRow({
7101
+ memory_id: memoryId,
7102
+ owner_wallet: ownerWalletForRow,
7103
+ mode: "shadow",
7104
+ proposed_op: "needs_router",
7105
+ target_memory_id: top.id,
7106
+ max_cosine: maxCosine,
7107
+ band,
7108
+ router_used: false,
7109
+ fact_key: null,
7110
+ reason: "similar_prior_fact",
7111
+ gate_version: gv
7112
+ });
6015
7113
  }
6016
- var import_tweetnacl3, import_crypto2, import_util, log9, hkdfAsync, NONCE_LENGTH, encryptionKey, encryptionPubkey;
6017
- var init_encryption = __esm({
6018
- "packages/shared/src/core/encryption.ts"() {
7114
+ var log11, SCOPE_BOT_OWN, K_CANDIDATES, reconcileDegradeLogged;
7115
+ var init_reconcile_shadow = __esm({
7116
+ "packages/brain/src/memory/reconcile-shadow.ts"() {
7117
+ "use strict";
7118
+ init_database();
7119
+ init_config();
7120
+ init_logger();
7121
+ init_reconcile_router();
7122
+ log11 = createChildLogger("reconcile-shadow");
7123
+ SCOPE_BOT_OWN = "__BOT_OWN__";
7124
+ K_CANDIDATES = 6;
7125
+ reconcileDegradeLogged = false;
7126
+ }
7127
+ });
7128
+
7129
+ // packages/shared/src/core/owner-key-constants.ts
7130
+ var HKDF_SALT, HKDF_INFO;
7131
+ var init_owner_key_constants = __esm({
7132
+ "packages/shared/src/core/owner-key-constants.ts"() {
7133
+ "use strict";
7134
+ HKDF_SALT = "clude-cortex-v1";
7135
+ HKDF_INFO = "memory-encryption-x25519-v2";
7136
+ }
7137
+ });
7138
+
7139
+ // packages/shared/src/core/memory-envelope.ts
7140
+ function generateDek() {
7141
+ return import_tweetnacl3.default.randomBytes(import_tweetnacl3.default.secretbox.keyLength);
7142
+ }
7143
+ function encryptField(plaintext, dek) {
7144
+ const nonce = import_tweetnacl3.default.randomBytes(NONCE_LENGTH);
7145
+ const message = new TextEncoder().encode(plaintext);
7146
+ const ct = import_tweetnacl3.default.secretbox(message, nonce, dek);
7147
+ const combined = new Uint8Array(NONCE_LENGTH + ct.length);
7148
+ combined.set(nonce, 0);
7149
+ combined.set(ct, NONCE_LENGTH);
7150
+ return Buffer.from(combined).toString("base64");
7151
+ }
7152
+ function decryptField(encrypted, dek) {
7153
+ try {
7154
+ const combined = Buffer.from(encrypted, "base64");
7155
+ if (combined.length < NONCE_LENGTH + 1) return null;
7156
+ const nonce = combined.subarray(0, NONCE_LENGTH);
7157
+ const ct = combined.subarray(NONCE_LENGTH);
7158
+ const pt = import_tweetnacl3.default.secretbox.open(ct, nonce, dek);
7159
+ if (!pt) return null;
7160
+ return new TextDecoder().decode(pt);
7161
+ } catch {
7162
+ return null;
7163
+ }
7164
+ }
7165
+ function wrapDek(dek, recipientPubkey) {
7166
+ const ephemeral = import_tweetnacl3.default.box.keyPair();
7167
+ const nonce = import_tweetnacl3.default.randomBytes(BOX_NONCE_LENGTH);
7168
+ const boxed = import_tweetnacl3.default.box(dek, nonce, recipientPubkey, ephemeral.secretKey);
7169
+ const combined = new Uint8Array(BOX_NONCE_LENGTH + boxed.length);
7170
+ combined.set(nonce, 0);
7171
+ combined.set(boxed, BOX_NONCE_LENGTH);
7172
+ return {
7173
+ wrapped: Buffer.from(combined).toString("base64"),
7174
+ wrapPubkey: Buffer.from(ephemeral.publicKey).toString("base64")
7175
+ };
7176
+ }
7177
+ function unwrapDek(wrapped, wrapPubkey, recipientSecretKey) {
7178
+ try {
7179
+ const combined = Buffer.from(wrapped, "base64");
7180
+ if (combined.length < BOX_NONCE_LENGTH + 1) return null;
7181
+ const nonce = combined.subarray(0, BOX_NONCE_LENGTH);
7182
+ const boxed = combined.subarray(BOX_NONCE_LENGTH);
7183
+ const ephemeralPub = Buffer.from(wrapPubkey, "base64");
7184
+ if (ephemeralPub.length !== import_tweetnacl3.default.box.publicKeyLength) return null;
7185
+ const out = import_tweetnacl3.default.box.open(boxed, nonce, ephemeralPub, recipientSecretKey);
7186
+ return out ?? null;
7187
+ } catch {
7188
+ return null;
7189
+ }
7190
+ }
7191
+ async function deriveOwnerEncryptionKeypair(signature) {
7192
+ const seed = await hkdfAsync("sha256", signature, HKDF_SALT, HKDF_INFO, 32);
7193
+ return import_tweetnacl3.default.box.keyPair.fromSecretKey(new Uint8Array(seed));
7194
+ }
7195
+ var import_tweetnacl3, import_crypto2, import_util, hkdfAsync, NONCE_LENGTH, BOX_NONCE_LENGTH;
7196
+ var init_memory_envelope = __esm({
7197
+ "packages/shared/src/core/memory-envelope.ts"() {
6019
7198
  "use strict";
6020
7199
  import_tweetnacl3 = __toESM(require("tweetnacl"));
6021
7200
  import_crypto2 = require("crypto");
6022
7201
  import_util = require("util");
6023
- init_logger();
6024
- log9 = createChildLogger("encryption");
7202
+ init_owner_key_constants();
6025
7203
  hkdfAsync = (0, import_util.promisify)(import_crypto2.hkdf);
6026
7204
  NONCE_LENGTH = import_tweetnacl3.default.secretbox.nonceLength;
7205
+ BOX_NONCE_LENGTH = import_tweetnacl3.default.box.nonceLength;
7206
+ }
7207
+ });
7208
+
7209
+ // packages/shared/src/core/encryption-keys.ts
7210
+ function loadProviderKeypair() {
7211
+ if (providerKeypair) return providerKeypair;
7212
+ const b64 = process.env.PMP_PROVIDER_ENC_SECRET;
7213
+ if (!b64) throw new Error("PMP_PROVIDER_ENC_SECRET is not set");
7214
+ const secret = Buffer.from(b64, "base64");
7215
+ if (secret.length !== import_tweetnacl4.default.box.secretKeyLength) {
7216
+ throw new Error(`PMP_PROVIDER_ENC_SECRET must decode to 32 bytes (got ${secret.length})`);
7217
+ }
7218
+ providerKeypair = import_tweetnacl4.default.box.keyPair.fromSecretKey(new Uint8Array(secret));
7219
+ log12.info("Provider encryption keypair loaded");
7220
+ return providerKeypair;
7221
+ }
7222
+ async function getOwnerEncryptionKey(ownerWallet) {
7223
+ const { data, error } = await getDb().from("encryption_keys").select("x25519_pubkey, verifier_ct").eq("owner_wallet", ownerWallet).maybeSingle();
7224
+ if (error) throw new Error(`getOwnerEncryptionKey failed: ${error.message}`);
7225
+ if (!data) return null;
7226
+ return { x25519_pubkey: data.x25519_pubkey, verifier_ct: data.verifier_ct };
7227
+ }
7228
+ var import_tweetnacl4, log12, providerKeypair;
7229
+ var init_encryption_keys = __esm({
7230
+ "packages/shared/src/core/encryption-keys.ts"() {
7231
+ "use strict";
7232
+ import_tweetnacl4 = __toESM(require("tweetnacl"));
7233
+ init_database();
7234
+ init_logger();
7235
+ log12 = createChildLogger("encryption-keys");
7236
+ providerKeypair = null;
7237
+ }
7238
+ });
7239
+
7240
+ // packages/brain/src/memory/memory-encryption.ts
7241
+ function isMemoryEncryptionEnabled() {
7242
+ if (process.env.PMP_ENCRYPTION_ENABLED !== "true") return false;
7243
+ try {
7244
+ loadProviderKeypair();
7245
+ return true;
7246
+ } catch {
7247
+ return false;
7248
+ }
7249
+ }
7250
+ async function getBotOwnerKeypair() {
7251
+ if (botKeypair) return botKeypair;
7252
+ const wallet = getBotWallet();
7253
+ if (!wallet) return null;
7254
+ const sig = import_tweetnacl5.default.sign.detached(new TextEncoder().encode(DERIVE_MESSAGE), wallet.secretKey);
7255
+ botKeypair = await deriveOwnerEncryptionKeypair(sig);
7256
+ return botKeypair;
7257
+ }
7258
+ async function resolveOwnerPublicKey(ownerWallet) {
7259
+ const botWallet2 = getBotWallet();
7260
+ const botAddr = botWallet2?.publicKey.toBase58();
7261
+ if (!ownerWallet || botAddr && ownerWallet === botAddr) {
7262
+ const kp = await getBotOwnerKeypair();
7263
+ return kp ? Buffer.from(kp.publicKey).toString("base64") : null;
7264
+ }
7265
+ const row = await getOwnerEncryptionKey(ownerWallet);
7266
+ return row?.x25519_pubkey ?? null;
7267
+ }
7268
+ async function encryptForStorage(plaintext, ownerWallet) {
7269
+ if (!isMemoryEncryptionEnabled()) return null;
7270
+ const ownerPubkey = await resolveOwnerPublicKey(ownerWallet);
7271
+ if (!ownerPubkey) {
7272
+ log13.warn("No owner encryption key resolvable \u2014 storing plaintext (encrypted:false)");
7273
+ return null;
7274
+ }
7275
+ const dek = generateDek();
7276
+ const ciphertext = encryptField(plaintext, dek);
7277
+ const ownerWrap = wrapDek(dek, Buffer.from(ownerPubkey, "base64"));
7278
+ const provWrap = wrapDek(dek, loadProviderKeypair().publicKey);
7279
+ return {
7280
+ ciphertext,
7281
+ ownerPubkey,
7282
+ wraps: [
7283
+ { recipient: "owner", wrapped_dek: ownerWrap.wrapped, wrap_pubkey: ownerWrap.wrapPubkey },
7284
+ { recipient: "provider", wrapped_dek: provWrap.wrapped, wrap_pubkey: provWrap.wrapPubkey }
7285
+ ]
7286
+ };
7287
+ }
7288
+ function delegationStateForWrite(hasEnvelope) {
7289
+ return hasEnvelope ? true : null;
7290
+ }
7291
+ var import_tweetnacl5, log13, DERIVE_MESSAGE, botKeypair;
7292
+ var init_memory_encryption = __esm({
7293
+ "packages/brain/src/memory/memory-encryption.ts"() {
7294
+ "use strict";
7295
+ import_tweetnacl5 = __toESM(require("tweetnacl"));
7296
+ init_memory_envelope();
7297
+ init_encryption_keys();
7298
+ init_solana_client();
7299
+ init_logger();
7300
+ log13 = createChildLogger("memory-encryption");
7301
+ DERIVE_MESSAGE = "clude-memory-encryption-v1";
7302
+ botKeypair = null;
7303
+ }
7304
+ });
7305
+
7306
+ // packages/shared/src/core/encryption.ts
7307
+ function isEncryptionEnabled() {
7308
+ return encryptionKey !== null;
7309
+ }
7310
+ function getEncryptionPubkey() {
7311
+ return encryptionPubkey;
7312
+ }
7313
+ function encryptContent(plaintext) {
7314
+ if (!encryptionKey) {
7315
+ throw new Error("Encryption not configured. Call configureEncryption() first.");
7316
+ }
7317
+ const nonce = import_tweetnacl6.default.randomBytes(NONCE_LENGTH2);
7318
+ const messageBytes = new TextEncoder().encode(plaintext);
7319
+ const ciphertext = import_tweetnacl6.default.secretbox(messageBytes, nonce, encryptionKey);
7320
+ const combined = new Uint8Array(NONCE_LENGTH2 + ciphertext.length);
7321
+ combined.set(nonce, 0);
7322
+ combined.set(ciphertext, NONCE_LENGTH2);
7323
+ return Buffer.from(combined).toString("base64");
7324
+ }
7325
+ function decryptContent(encrypted) {
7326
+ if (!encryptionKey) {
7327
+ return null;
7328
+ }
7329
+ try {
7330
+ const combined = Buffer.from(encrypted, "base64");
7331
+ if (combined.length < NONCE_LENGTH2 + 1) {
7332
+ return null;
7333
+ }
7334
+ const nonce = combined.subarray(0, NONCE_LENGTH2);
7335
+ const ciphertext = combined.subarray(NONCE_LENGTH2);
7336
+ const plaintext = import_tweetnacl6.default.secretbox.open(ciphertext, nonce, encryptionKey);
7337
+ if (!plaintext) {
7338
+ return null;
7339
+ }
7340
+ return new TextDecoder().decode(plaintext);
7341
+ } catch {
7342
+ return null;
7343
+ }
7344
+ }
7345
+ function decryptMemoryBatch(memories) {
7346
+ if (!encryptionKey || !encryptionPubkey) return memories;
7347
+ for (const mem of memories) {
7348
+ if (!mem.encrypted) continue;
7349
+ if (mem.encryption_pubkey && mem.encryption_pubkey !== encryptionPubkey) {
7350
+ log14.debug({ memPubkey: mem.encryption_pubkey?.slice(0, 12) }, "Skipping memory encrypted by different key");
7351
+ continue;
7352
+ }
7353
+ const decrypted = decryptContent(mem.content);
7354
+ if (decrypted !== null) {
7355
+ mem.content = decrypted;
7356
+ } else {
7357
+ log14.warn("Failed to decrypt memory content \u2014 wrong key or corrupted data");
7358
+ }
7359
+ }
7360
+ return memories;
7361
+ }
7362
+ var import_tweetnacl6, import_crypto3, import_util2, log14, hkdfAsync2, NONCE_LENGTH2, encryptionKey, encryptionPubkey;
7363
+ var init_encryption = __esm({
7364
+ "packages/shared/src/core/encryption.ts"() {
7365
+ "use strict";
7366
+ import_tweetnacl6 = __toESM(require("tweetnacl"));
7367
+ import_crypto3 = require("crypto");
7368
+ import_util2 = require("util");
7369
+ init_logger();
7370
+ log14 = createChildLogger("encryption");
7371
+ hkdfAsync2 = (0, import_util2.promisify)(import_crypto3.hkdf);
7372
+ NONCE_LENGTH2 = import_tweetnacl6.default.secretbox.nonceLength;
6027
7373
  encryptionKey = null;
6028
7374
  encryptionPubkey = null;
6029
7375
  }
6030
7376
  });
6031
7377
 
7378
+ // packages/brain/src/memory/memory-decryption.ts
7379
+ async function fetchProviderWraps(ids) {
7380
+ const map = /* @__PURE__ */ new Map();
7381
+ if (ids.length === 0) return map;
7382
+ const { data, error } = await getDb().from("memory_dek_wraps").select("memory_id, wrapped_dek, wrap_pubkey").eq("recipient", "provider").in("memory_id", ids);
7383
+ if (error || !data) return map;
7384
+ for (const r of data) {
7385
+ map.set(r.memory_id, { wrapped_dek: r.wrapped_dek, wrap_pubkey: r.wrap_pubkey });
7386
+ }
7387
+ return map;
7388
+ }
7389
+ async function decryptMemories(memories) {
7390
+ if (!memories || memories.length === 0) return memories;
7391
+ const encryptedRows = memories.filter((m) => m.encrypted === true);
7392
+ if (encryptedRows.length === 0) return memories;
7393
+ let providerSecret = null;
7394
+ try {
7395
+ providerSecret = loadProviderKeypair().secretKey;
7396
+ } catch {
7397
+ providerSecret = null;
7398
+ }
7399
+ const wraps = providerSecret ? await fetchProviderWraps(encryptedRows.map((m) => m.id)) : /* @__PURE__ */ new Map();
7400
+ const legacyRows = [];
7401
+ for (const mem of encryptedRows) {
7402
+ const wrap = wraps.get(mem.id);
7403
+ if (providerSecret && wrap) {
7404
+ const dek = unwrapDek(wrap.wrapped_dek, wrap.wrap_pubkey, providerSecret);
7405
+ const plain = dek ? decryptField(mem.content, dek) : null;
7406
+ if (plain !== null) {
7407
+ mem.content = plain;
7408
+ continue;
7409
+ }
7410
+ log15.debug({ id: mem.id }, "Envelope decrypt failed \u2014 leaving ciphertext");
7411
+ continue;
7412
+ }
7413
+ legacyRows.push(mem);
7414
+ }
7415
+ if (legacyRows.length > 0) decryptMemoryBatch(legacyRows);
7416
+ return memories;
7417
+ }
7418
+ async function decryptOneContent(row) {
7419
+ if (row.encrypted !== true) return row.content;
7420
+ const [out] = await decryptMemories([{ ...row }]);
7421
+ return out.content !== row.content ? out.content : null;
7422
+ }
7423
+ var log15;
7424
+ var init_memory_decryption = __esm({
7425
+ "packages/brain/src/memory/memory-decryption.ts"() {
7426
+ "use strict";
7427
+ init_database();
7428
+ init_memory_envelope();
7429
+ init_encryption_keys();
7430
+ init_encryption();
7431
+ init_logger();
7432
+ log15 = createChildLogger("memory-decryption");
7433
+ }
7434
+ });
7435
+
6032
7436
  // packages/brain/src/events/event-bus.ts
6033
7437
  var import_events, TypedEventBus, eventBus;
6034
7438
  var init_event_bus = __esm({
@@ -6077,16 +7481,16 @@ async function findOrCreateEntity(name, entityType, opts) {
6077
7481
  mention_count: 1
6078
7482
  }).select().single();
6079
7483
  if (error) {
6080
- log10.error({ error: error.message, name }, "Failed to create entity");
7484
+ log16.error({ error: error.message, name }, "Failed to create entity");
6081
7485
  return null;
6082
7486
  }
6083
- log10.debug({ id: newEntity.id, name, type: entityType }, "Entity created");
7487
+ log16.debug({ id: newEntity.id, name, type: entityType }, "Entity created");
6084
7488
  embedEntity(newEntity.id, name, opts?.description).catch(
6085
- (err) => log10.debug({ err }, "Entity embedding failed")
7489
+ (err) => log16.debug({ err }, "Entity embedding failed")
6086
7490
  );
6087
7491
  return newEntity;
6088
7492
  } catch (err) {
6089
- log10.error({ err, name }, "Entity findOrCreate failed");
7493
+ log16.error({ err, name }, "Entity findOrCreate failed");
6090
7494
  return null;
6091
7495
  }
6092
7496
  }
@@ -6108,7 +7512,7 @@ async function createEntityMention(entityId, memoryId, context, salience = 0.5)
6108
7512
  salience: Math.max(0, Math.min(1, salience))
6109
7513
  }, { onConflict: "entity_id,memory_id" });
6110
7514
  if (error) {
6111
- log10.debug({ error: error.message, entityId, memoryId }, "Entity mention failed");
7515
+ log16.debug({ error: error.message, entityId, memoryId }, "Entity mention failed");
6112
7516
  }
6113
7517
  }
6114
7518
  async function getMemoriesByEntity(entityId, opts) {
@@ -6120,12 +7524,12 @@ async function getMemoriesByEntity(entityId, opts) {
6120
7524
  `).eq("entity_id", entityId).order("salience", { ascending: false }).limit(opts?.limit || 20);
6121
7525
  const { data, error } = await query;
6122
7526
  if (error) {
6123
- log10.error({ error: error.message, entityId }, "Failed to get memories by entity");
7527
+ log16.error({ error: error.message, entityId }, "Failed to get memories by entity");
6124
7528
  return [];
6125
7529
  }
6126
7530
  const { getOwnerWallet: getOwnerWallet3 } = (init_memory(), __toCommonJS(memory_exports));
6127
7531
  const ownerWallet = getOwnerWallet3();
6128
- 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);
7532
+ 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);
6129
7533
  return memories;
6130
7534
  }
6131
7535
  async function getEntityCooccurrences(entityId, opts) {
@@ -6137,33 +7541,27 @@ async function getEntityCooccurrences(entityId, opts) {
6137
7541
  max_results: opts?.maxResults ?? 10
6138
7542
  });
6139
7543
  if (error || !data) {
6140
- log10.debug({ error: error?.message, entityId }, "Entity co-occurrence lookup failed");
7544
+ log16.debug({ error: error?.message, entityId }, "Entity co-occurrence lookup failed");
6141
7545
  return [];
6142
7546
  }
6143
7547
  return data;
6144
7548
  } catch (err) {
6145
- log10.debug({ err, entityId }, "Entity co-occurrence skipped (RPC unavailable)");
7549
+ log16.debug({ err, entityId }, "Entity co-occurrence skipped (RPC unavailable)");
6146
7550
  return [];
6147
7551
  }
6148
7552
  }
6149
7553
  async function createEntityRelation(sourceEntityId, targetEntityId, relationType, evidenceMemoryId, strength = 0.5) {
6150
7554
  if (sourceEntityId === targetEntityId) return;
6151
7555
  const db = getDb();
6152
- 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();
6153
- if (existing) {
6154
- const newEvidence = evidenceMemoryId && !existing.evidence_memory_ids.includes(evidenceMemoryId) ? [...existing.evidence_memory_ids, evidenceMemoryId] : existing.evidence_memory_ids;
6155
- await db.from("entity_relations").update({
6156
- strength: Math.min(1, existing.strength + 0.1),
6157
- evidence_memory_ids: newEvidence
6158
- }).eq("id", existing.id);
6159
- } else {
6160
- await db.from("entity_relations").insert({
6161
- source_entity_id: sourceEntityId,
6162
- target_entity_id: targetEntityId,
6163
- relation_type: relationType,
6164
- strength,
6165
- evidence_memory_ids: evidenceMemoryId ? [evidenceMemoryId] : []
6166
- });
7556
+ const { error } = await db.rpc("upsert_entity_relation", {
7557
+ p_src: sourceEntityId,
7558
+ p_tgt: targetEntityId,
7559
+ p_type: relationType,
7560
+ p_evidence: evidenceMemoryId ?? null,
7561
+ p_strength: strength
7562
+ });
7563
+ if (error) {
7564
+ log16.warn({ err: error.message, sourceEntityId, targetEntityId, relationType }, "upsert_entity_relation failed");
6167
7565
  }
6168
7566
  }
6169
7567
  function extractEntitiesFromText(text) {
@@ -6243,11 +7641,11 @@ async function extractAndLinkEntities(memoryId, content, summary, relatedUser) {
6243
7641
  // base co-occurrence strength
6244
7642
  );
6245
7643
  } catch (err) {
6246
- log10.debug({ err, source: entityIds[i].name, target: entityIds[j].name }, "Failed to create entity relation");
7644
+ log16.debug({ err, source: entityIds[i].name, target: entityIds[j].name }, "Failed to create entity relation");
6247
7645
  }
6248
7646
  }
6249
7647
  }
6250
- log10.debug({ memoryId, entityCount: extracted.length, relations: Math.max(0, entityIds.length * (entityIds.length - 1) / 2) }, "Entities extracted, linked, and related");
7648
+ log16.debug({ memoryId, entityCount: extracted.length, relations: Math.max(0, entityIds.length * (entityIds.length - 1) / 2) }, "Entities extracted, linked, and related");
6251
7649
  }
6252
7650
  async function findSimilarEntities(query, opts) {
6253
7651
  if (!isEmbeddingEnabled()) return [];
@@ -6261,7 +7659,7 @@ async function findSimilarEntities(query, opts) {
6261
7659
  filter_types: opts?.entityTypes || null
6262
7660
  });
6263
7661
  if (error) {
6264
- log10.debug({ error: error.message }, "Entity vector search failed");
7662
+ log16.debug({ error: error.message }, "Entity vector search failed");
6265
7663
  return [];
6266
7664
  }
6267
7665
  const ids = (data || []).map((d) => d.id);
@@ -6269,14 +7667,14 @@ async function findSimilarEntities(query, opts) {
6269
7667
  const { data: entities } = await db.from("entities").select("*").in("id", ids);
6270
7668
  return entities || [];
6271
7669
  }
6272
- var log10;
7670
+ var log16;
6273
7671
  var init_graph = __esm({
6274
7672
  "packages/brain/src/memory/graph.ts"() {
6275
7673
  "use strict";
6276
7674
  init_database();
6277
7675
  init_logger();
6278
7676
  init_embeddings();
6279
- log10 = createChildLogger("memory-graph");
7677
+ log16 = createChildLogger("memory-graph");
6280
7678
  }
6281
7679
  });
6282
7680
 
@@ -6296,8 +7694,11 @@ var init_owner_context = __esm({
6296
7694
  // packages/brain/src/memory/memory.ts
6297
7695
  var memory_exports = {};
6298
7696
  __export(memory_exports, {
6299
- SCOPE_BOT_OWN: () => SCOPE_BOT_OWN,
7697
+ SCOPE_BOT_OWN: () => SCOPE_BOT_OWN2,
7698
+ _resetOutboxDegradeLog: () => _resetOutboxDegradeLog,
6300
7699
  _setOwnerWallet: () => _setOwnerWallet,
7700
+ applyOwnerPostGuard: () => applyOwnerPostGuard,
7701
+ buildFragmentRpcArgs: () => buildFragmentRpcArgs,
6301
7702
  calculateImportance: () => calculateImportance,
6302
7703
  createMemoryLink: () => createMemoryLink,
6303
7704
  createMemoryLinksBatch: () => createMemoryLinksBatch,
@@ -6308,12 +7709,15 @@ __export(memory_exports, {
6308
7709
  formatMemoryContext: () => formatMemoryContext,
6309
7710
  generateHashId: () => generateHashId,
6310
7711
  getMemoryStats: () => getMemoryStats,
7712
+ getOwnerScope: () => getOwnerScope,
6311
7713
  getOwnerWallet: () => getOwnerWallet,
6312
7714
  getRecentMemories: () => getRecentMemories,
6313
7715
  getSelfModel: () => getSelfModel,
6314
7716
  hydrateMemories: () => hydrateMemories,
6315
7717
  inferConcepts: () => inferConcepts,
6316
7718
  invalidateInstalledPacksCache: () => invalidateInstalledPacksCache,
7719
+ isBenchMode: () => isBenchMode,
7720
+ isOwnerScopeFailClosed: () => isOwnerScopeFailClosed,
6317
7721
  isValidHashId: () => isValidHashId,
6318
7722
  listMemories: () => listMemories,
6319
7723
  markJepaQueried: () => markJepaQueried,
@@ -6321,11 +7725,15 @@ __export(memory_exports, {
6321
7725
  moodToValence: () => moodToValence,
6322
7726
  recallMemories: () => recallMemories,
6323
7727
  recallMemorySummaries: () => recallMemorySummaries,
7728
+ runEnrichPipeline: () => runEnrichPipeline,
6324
7729
  scopeToOwner: () => scopeToOwner,
7730
+ scoreImportanceOnWrite: () => scoreImportanceOnWrite,
6325
7731
  scoreImportanceWithLLM: () => scoreImportanceWithLLM,
6326
7732
  scoreMemory: () => scoreMemory,
7733
+ shouldTrackAccess: () => shouldTrackAccess,
6327
7734
  storeDreamLog: () => storeDreamLog,
6328
7735
  storeMemory: () => storeMemory,
7736
+ storeMemoryWithOutbox: () => storeMemoryWithOutbox,
6329
7737
  updateMemory: () => updateMemory
6330
7738
  });
6331
7739
  function getCachedEmbedding2(query) {
@@ -6354,18 +7762,51 @@ function getOwnerWallet() {
6354
7762
  if (contextWallet !== void 0) return contextWallet;
6355
7763
  return _ownerWallet;
6356
7764
  }
6357
- function scopeToOwner(query) {
7765
+ function isOwnerScopeFailClosed() {
7766
+ return process.env.OWNER_SCOPE_FAILCLOSED === "true";
7767
+ }
7768
+ function getOwnerScope() {
6358
7769
  const wallet = getOwnerWallet();
6359
- if (wallet === SCOPE_BOT_OWN) {
7770
+ if (wallet) return wallet;
7771
+ return isOwnerScopeFailClosed() ? SCOPE_BOT_OWN2 : null;
7772
+ }
7773
+ function scopeToOwner(query) {
7774
+ const scope = getOwnerScope();
7775
+ if (scope === SCOPE_BOT_OWN2) {
6360
7776
  return query.is("owner_wallet", null);
6361
7777
  }
6362
- if (wallet) {
6363
- return query.eq("owner_wallet", wallet);
7778
+ if (scope) {
7779
+ return query.eq("owner_wallet", scope);
6364
7780
  }
6365
7781
  return query;
6366
7782
  }
7783
+ function applyOwnerPostGuard(results, scope) {
7784
+ if (!scope) return { kept: results, stripped: 0 };
7785
+ const kept = scope === SCOPE_BOT_OWN2 ? results.filter((m) => m.owner_wallet == null) : results.filter((m) => m.owner_wallet === scope);
7786
+ return { kept, stripped: results.length - kept.length };
7787
+ }
7788
+ function isBenchMode() {
7789
+ return process.env.BENCH_MODE === "true";
7790
+ }
7791
+ function shouldTrackAccess(opts) {
7792
+ if (isBenchMode()) return false;
7793
+ return opts.trackAccess !== false;
7794
+ }
7795
+ function buildFragmentRpcArgs(opts) {
7796
+ const args = {
7797
+ query_embedding: opts.embeddingJson,
7798
+ match_threshold: opts.matchThreshold,
7799
+ match_count: opts.matchCount,
7800
+ filter_owner: getOwnerScope()
7801
+ };
7802
+ if (process.env.MEMORY_FRAGMENT_FILTERS === "true") {
7803
+ args.min_decay = opts.minDecay;
7804
+ args.filter_types = opts.memoryTypes && opts.memoryTypes.length > 0 ? opts.memoryTypes : null;
7805
+ }
7806
+ return args;
7807
+ }
6367
7808
  function generateHashId() {
6368
- return `${HASH_ID_PREFIX}-${(0, import_crypto3.randomBytes)(4).toString("hex")}`;
7809
+ return `${HASH_ID_PREFIX}-${(0, import_crypto4.randomBytes)(4).toString("hex")}`;
6369
7810
  }
6370
7811
  function isValidHashId(id) {
6371
7812
  return /^clude-[a-f0-9]{8}$/.test(id);
@@ -6400,6 +7841,23 @@ function inferConcepts(summary, source, tags) {
6400
7841
  concepts.push("identity_evolution");
6401
7842
  return [...new Set(concepts)];
6402
7843
  }
7844
+ function scoreImportanceOnWrite(opts) {
7845
+ const base = {
7846
+ self_model: 0.75,
7847
+ semantic: 0.7,
7848
+ procedural: 0.65,
7849
+ introspective: 0.6,
7850
+ episodic: 0.45
7851
+ };
7852
+ let score = base[opts.type] ?? 0.5;
7853
+ const len = (opts.content ?? "").trim().length;
7854
+ score += Math.min(len / 1e3, 0.15);
7855
+ if (opts.tags && opts.tags.length) score += 0.05;
7856
+ const concepts = opts.concepts ?? inferConcepts(opts.summary, opts.source, opts.tags || []);
7857
+ if (concepts.length) score += 0.05;
7858
+ if (INTERNAL_MEMORY_SOURCES.has(opts.source)) score -= 0.05;
7859
+ return Math.max(0, Math.min(1, score));
7860
+ }
6403
7861
  function normalizeSummary(summary) {
6404
7862
  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();
6405
7863
  }
@@ -6431,7 +7889,7 @@ async function getInstalledPackIdsCached(ownerWallet) {
6431
7889
  ids = Array.from(/* @__PURE__ */ new Set([DEFAULT_PACK_ID, ...fromDb]));
6432
7890
  }
6433
7891
  } catch (err) {
6434
- log11.debug({ err }, "Failed to fetch installed packs (using default only)");
7892
+ log17.debug({ err }, "Failed to fetch installed packs (using default only)");
6435
7893
  }
6436
7894
  }
6437
7895
  installedPacksCache.set(key, { ids, expiresAt: now + INSTALLED_PACKS_TTL_MS });
@@ -6468,7 +7926,7 @@ async function ensureTopicEmbeddings(installedPackIds) {
6468
7926
  if (emb) topicEmbeddingCache.set(s.topicId, emb);
6469
7927
  });
6470
7928
  } catch (err) {
6471
- log11.warn({ err }, "Failed to populate topic embeddings cache");
7929
+ log17.warn({ err }, "Failed to populate topic embeddings cache");
6472
7930
  } finally {
6473
7931
  topicEmbeddingPopulationLock = null;
6474
7932
  }
@@ -6479,12 +7937,18 @@ async function ensureTopicEmbeddings(installedPackIds) {
6479
7937
  );
6480
7938
  }
6481
7939
  async function storeMemory(opts) {
7940
+ const trimmedContent = (opts.content ?? "").trim();
7941
+ if (!trimmedContent) {
7942
+ log17.warn({ source: opts.source }, "Rejected empty-content memory");
7943
+ return null;
7944
+ }
6482
7945
  if (opts.source.startsWith("shiro_") && isDuplicateWrite(opts.source, opts.summary)) {
6483
- log11.debug({ source: opts.source, summary: opts.summary.slice(0, 60) }, "Skipping duplicate memory write");
7946
+ log17.debug({ source: opts.source, summary: opts.summary.slice(0, 60) }, "Skipping duplicate memory write");
6484
7947
  return null;
6485
7948
  }
6486
7949
  const db = getDb();
6487
7950
  const ownerWallet = getOwnerWallet();
7951
+ const importance = clamp(opts.importance ?? scoreImportanceOnWrite(opts), 0, 1);
6488
7952
  const concepts = opts.concepts || inferConcepts(opts.summary, opts.source, opts.tags || []);
6489
7953
  const installedPackIds = await getInstalledPackIdsCached(ownerWallet);
6490
7954
  const taggedTags = autoCategorizeTags({
@@ -6495,9 +7959,10 @@ async function storeMemory(opts) {
6495
7959
  });
6496
7960
  const hashId = generateHashId();
6497
7961
  try {
6498
- const plaintextContent = opts.content.slice(0, MEMORY_MAX_CONTENT_LENGTH);
6499
- const shouldEncrypt = isEncryptionEnabled();
6500
- const storedContent = shouldEncrypt ? encryptContent(plaintextContent) : plaintextContent;
7962
+ const plaintextContent = trimmedContent.slice(0, MEMORY_MAX_CONTENT_LENGTH);
7963
+ const envelope = await encryptForStorage(plaintextContent, ownerWallet || null);
7964
+ const legacyEncrypt = !envelope && isEncryptionEnabled();
7965
+ const storedContent = envelope ? envelope.ciphertext : legacyEncrypt ? encryptContent(plaintextContent) : plaintextContent;
6501
7966
  const { data, error } = await db.from("memories").insert({
6502
7967
  hash_id: hashId,
6503
7968
  memory_type: opts.type,
@@ -6506,7 +7971,7 @@ async function storeMemory(opts) {
6506
7971
  tags: taggedTags,
6507
7972
  concepts,
6508
7973
  emotional_valence: clamp(opts.emotionalValence ?? 0, -1, 1),
6509
- importance: clamp(opts.importance ?? 0.5, 0, 1),
7974
+ importance,
6510
7975
  source: opts.source,
6511
7976
  source_id: opts.sourceId || null,
6512
7977
  related_user: opts.relatedUser || null,
@@ -6514,43 +7979,72 @@ async function storeMemory(opts) {
6514
7979
  metadata: opts.metadata || {},
6515
7980
  evidence_ids: opts.evidenceIds || [],
6516
7981
  compacted: false,
6517
- encrypted: shouldEncrypt,
6518
- encryption_pubkey: shouldEncrypt ? getEncryptionPubkey() : null,
7982
+ encrypted: envelope !== null || legacyEncrypt,
7983
+ encryption_pubkey: envelope ? envelope.ownerPubkey : legacyEncrypt ? getEncryptionPubkey() : null,
7984
+ provider_delegated: delegationStateForWrite(envelope !== null),
7985
+ // true|null at write; false is revoke-only (§7)
6519
7986
  owner_wallet: ownerWallet || null
6520
7987
  }).select("id, hash_id, content, memory_type, owner_wallet, created_at, tags, source, related_user, related_wallet").single();
6521
7988
  if (error) {
6522
- log11.error({ error: error.message }, "Failed to store memory");
7989
+ log17.error({ error: error.message }, "Failed to store memory");
6523
7990
  return null;
6524
7991
  }
6525
- log11.debug({
7992
+ log17.debug({
6526
7993
  id: data.id,
6527
7994
  hashId: data.hash_id,
6528
7995
  type: opts.type,
6529
7996
  summary: opts.summary.slice(0, 60),
6530
- importance: opts.importance,
7997
+ importance,
6531
7998
  concepts
6532
7999
  }, "Memory stored");
8000
+ await setContentTokens(db, data.id, plaintextContent);
8001
+ if (envelope) {
8002
+ const { error: wrapErr } = await db.from("memory_dek_wraps").insert(
8003
+ envelope.wraps.map((w) => ({ memory_id: data.id, ...w }))
8004
+ );
8005
+ if (wrapErr) {
8006
+ log17.error({ id: data.id, error: wrapErr.message }, "DEK wrap write failed \u2014 reverting memory to plaintext");
8007
+ await db.from("memories").update({
8008
+ content: plaintextContent,
8009
+ encrypted: false,
8010
+ encryption_pubkey: null,
8011
+ provider_delegated: null
8012
+ }).eq("id", data.id);
8013
+ }
8014
+ }
6533
8015
  eventBus.emit("memory:stored", {
6534
- importance: clamp(opts.importance ?? 0.5, 0, 1),
8016
+ importance,
6535
8017
  memoryType: opts.type,
6536
8018
  source: opts.source
6537
8019
  });
6538
- commitMemoryToChain(data.id, opts, data).catch(
6539
- (err) => log11.warn({ err }, "On-chain memory commit failed")
8020
+ commitMemoryToChain(data.id, opts, data, plaintextContent, envelope !== null || legacyEncrypt).catch(
8021
+ (err) => log17.warn({ err }, "On-chain memory commit failed")
6540
8022
  );
6541
- embedMemory(data.id, opts).catch((err) => log11.warn({ err }, "Embedding generation failed"));
6542
- autoLinkMemory(data.id, opts).catch((err) => log11.warn({ err }, "Auto-linking failed"));
6543
- extractAndLinkEntitiesForMemory(data.id, opts).catch((err) => log11.debug({ err }, "Entity extraction failed"));
8023
+ let enqueued = false;
8024
+ if (config.memory.outboxEnabled) {
8025
+ enqueued = await enqueueEnrichJob(db, data.id);
8026
+ }
8027
+ if (!enqueued) {
8028
+ const embedP = embedMemory(data.id, opts);
8029
+ embedP.catch((err) => log17.warn({ err }, "Embedding generation failed"));
8030
+ autoLinkMemory(data.id, opts).catch((err) => log17.warn({ err }, "Auto-linking failed"));
8031
+ extractAndLinkEntitiesForMemory(data.id, opts).catch((err) => log17.debug({ err }, "Entity extraction failed"));
8032
+ if (config.memory.reconcileEnabled) {
8033
+ const reconcileScope = ownerWallet || SCOPE_BOT_OWN2;
8034
+ embedP.then(() => runReconcileShadow(data.id, reconcileScope)).catch(() => {
8035
+ });
8036
+ }
8037
+ }
6544
8038
  return data.id;
6545
8039
  } catch (err) {
6546
- log11.error({ err }, "Memory store failed");
8040
+ log17.error({ err }, "Memory store failed");
6547
8041
  return null;
6548
8042
  }
6549
8043
  }
6550
- async function commitMemoryToChain(memoryId, opts, row) {
8044
+ async function commitMemoryToChain(memoryId, opts, row, plaintextContent, encrypted) {
6551
8045
  if (TOKENISATION_SKIP_SOURCES.has(opts.source)) return;
6552
8046
  const canonicalHash = memoryContentHash({
6553
- content: row.content,
8047
+ content: plaintextContent,
6554
8048
  memory_type: row.memory_type,
6555
8049
  owner_wallet: row.owner_wallet,
6556
8050
  created_at: row.created_at,
@@ -6562,7 +8056,6 @@ async function commitMemoryToChain(memoryId, opts, row) {
6562
8056
  const contentHashBuf = Buffer.from(canonicalHash, "hex");
6563
8057
  let signature = null;
6564
8058
  if (isRegistryEnabled()) {
6565
- const encrypted = isEncryptionEnabled();
6566
8059
  signature = await registerMemoryOnChain(
6567
8060
  contentHashBuf,
6568
8061
  opts.type,
@@ -6591,18 +8084,101 @@ async function commitMemoryToChain(memoryId, opts, row) {
6591
8084
  tokenization_status: "minted",
6592
8085
  tokenized_at: (/* @__PURE__ */ new Date()).toISOString()
6593
8086
  }).eq("id", memoryId);
6594
- log11.debug({ memoryId, signature: signature.slice(0, 16) }, "Memory committed on-chain (PMP + legacy)");
8087
+ log17.debug({ memoryId, signature: signature.slice(0, 16) }, "Memory committed on-chain (PMP + legacy)");
8088
+ }
8089
+ function logOutboxDegradeOnce(err, memoryId) {
8090
+ if (outboxDegradeLogged) return;
8091
+ outboxDegradeLogged = true;
8092
+ const e = err;
8093
+ log17.warn(
8094
+ { code: e?.code, message: e?.message, memoryId },
8095
+ "Outbox enqueue degraded \u2014 falling back to fire-and-forget (migration 044 unapplied?)"
8096
+ );
6595
8097
  }
6596
- async function embedMemory(memoryId, opts) {
8098
+ async function enqueueEnrichJob(db, memoryId) {
8099
+ try {
8100
+ const { error } = await db.from("memory_write_jobs").insert({ memory_id: memoryId, job_type: "enrich" });
8101
+ if (error) {
8102
+ logOutboxDegradeOnce(error, memoryId);
8103
+ return false;
8104
+ }
8105
+ return true;
8106
+ } catch (err) {
8107
+ logOutboxDegradeOnce(err, memoryId);
8108
+ return false;
8109
+ }
8110
+ }
8111
+ function _resetOutboxDegradeLog() {
8112
+ outboxDegradeLogged = false;
8113
+ }
8114
+ async function runEnrichPipeline(memoryId, opts) {
8115
+ await embedMemory(memoryId, opts, { replaceFragments: true });
8116
+ await autoLinkMemory(memoryId, opts);
8117
+ await extractAndLinkEntitiesForMemory(memoryId, opts);
8118
+ if (config.memory.reconcileEnabled) {
8119
+ await runReconcileShadow(memoryId, getOwnerWallet() || SCOPE_BOT_OWN2);
8120
+ }
8121
+ }
8122
+ async function storeMemoryWithOutbox(opts) {
8123
+ const id = await storeMemory(opts);
8124
+ if (id === null) return null;
8125
+ const db = getDb();
8126
+ const { data: row } = await db.from("memories").select("hash_id").eq("id", id).maybeSingle();
8127
+ const { count } = await db.from("memory_write_jobs").select("id", { count: "exact", head: true }).eq("memory_id", id);
8128
+ return { id, hash_id: row?.hash_id ?? "", jobs_queued: count ?? 0 };
8129
+ }
8130
+ async function embedMemory(memoryId, opts, embedOpts = {}) {
6597
8131
  if (!isEmbeddingEnabled()) return;
6598
8132
  const db = getDb();
6599
- const embeddings = await generateEmbeddings([opts.summary]);
8133
+ const fragments = [];
8134
+ fragments.push({ type: "summary", text: opts.summary });
8135
+ const content = opts.content.slice(0, MEMORY_MAX_CONTENT_LENGTH);
8136
+ if (content.length > EMBEDDING_FRAGMENT_MAX_LENGTH) {
8137
+ const sentences = content.match(/[^.!?\n]+[.!?\n]+/g) || [content];
8138
+ let chunk = "";
8139
+ for (const sentence of sentences) {
8140
+ if (chunk.length + sentence.length > EMBEDDING_FRAGMENT_MAX_LENGTH && chunk.length > 0) {
8141
+ fragments.push({ type: "content_chunk", text: chunk.trim() });
8142
+ chunk = "";
8143
+ }
8144
+ chunk += sentence;
8145
+ }
8146
+ if (chunk.trim()) fragments.push({ type: "content_chunk", text: chunk.trim() });
8147
+ } else {
8148
+ fragments.push({ type: "content_chunk", text: content });
8149
+ }
8150
+ const allLabels = [...opts.tags || [], ...opts.concepts || inferConcepts(opts.summary, opts.source, opts.tags || [])];
8151
+ if (allLabels.length > 0) {
8152
+ fragments.push({ type: "tag_context", text: `Context: ${allLabels.join(", ")}. ${opts.summary}` });
8153
+ }
8154
+ const embeddings = await generateEmbeddings(fragments.map((f) => f.text));
6600
8155
  const summaryEmbedding = embeddings[0];
6601
8156
  if (!summaryEmbedding) {
6602
- log11.debug({ memoryId }, "No embedding generated; skipping semantic tagging");
8157
+ log17.debug({ memoryId }, "No embedding generated; skipping semantic tagging");
6603
8158
  return;
6604
8159
  }
6605
- await db.from("memories").update({ embedding: JSON.stringify(summaryEmbedding) }).eq("id", memoryId);
8160
+ const vertexEmbeddings = isVertexConfigured() ? await generateVertexEmbeddings(fragments.map((f) => f.text)) : [];
8161
+ const vertexSummary = vertexEmbeddings[0];
8162
+ await db.from("memories").update({
8163
+ embedding: JSON.stringify(summaryEmbedding),
8164
+ ...vertexSummary ? { embedding_vertex: JSON.stringify(vertexSummary) } : {}
8165
+ }).eq("id", memoryId);
8166
+ const fragmentRows = fragments.map((f, i) => ({
8167
+ memory_id: memoryId,
8168
+ fragment_type: f.type,
8169
+ content: f.text.slice(0, EMBEDDING_FRAGMENT_MAX_LENGTH),
8170
+ embedding: embeddings[i] ? JSON.stringify(embeddings[i]) : null,
8171
+ ...vertexEmbeddings[i] ? { embedding_vertex: JSON.stringify(vertexEmbeddings[i]) } : {}
8172
+ })).filter((r) => r.embedding !== null);
8173
+ if (fragmentRows.length > 0) {
8174
+ if (embedOpts.replaceFragments) {
8175
+ await db.from("memory_fragments").delete().eq("memory_id", memoryId);
8176
+ }
8177
+ const { error } = await db.from("memory_fragments").insert(fragmentRows);
8178
+ if (error) {
8179
+ log17.warn({ err: error.message, memoryId }, "Failed to store memory fragments (summary embedding intact)");
8180
+ }
8181
+ }
6606
8182
  try {
6607
8183
  const ownerWallet = getOwnerWallet();
6608
8184
  const installedPacks = await getInstalledPackIdsCached(ownerWallet);
@@ -6618,7 +8194,7 @@ async function embedMemory(memoryId, opts) {
6618
8194
  const merged = Array.from(/* @__PURE__ */ new Set([...existing, ...semantic]));
6619
8195
  if (merged.length !== existing.length) {
6620
8196
  await db.from("memories").update({ tags: merged }).eq("id", memoryId);
6621
- log11.debug({
8197
+ log17.debug({
6622
8198
  memoryId,
6623
8199
  added: semantic.filter((t) => !existing.includes(t))
6624
8200
  }, "Semantic pack tags appended");
@@ -6626,9 +8202,9 @@ async function embedMemory(memoryId, opts) {
6626
8202
  }
6627
8203
  }
6628
8204
  } catch (err) {
6629
- log11.debug({ err, memoryId }, "Semantic tagging skipped");
8205
+ log17.debug({ err, memoryId }, "Semantic tagging skipped");
6630
8206
  }
6631
- log11.debug({ memoryId }, "Memory embedded");
8207
+ log17.debug({ memoryId }, "Memory embedded");
6632
8208
  }
6633
8209
  async function expandQuery(query) {
6634
8210
  if (!isOpenRouterEnabled()) return [query];
@@ -6646,10 +8222,10 @@ async function expandQuery(query) {
6646
8222
  new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), 3e3))
6647
8223
  ]);
6648
8224
  const expansions = response.split("\n").map((l) => l.trim()).filter((l) => l.length > 5 && l.length < 200).slice(0, 3);
6649
- log11.debug({ original: query, expansions: expansions.length }, "Query expanded");
8225
+ log17.debug({ original: query, expansions: expansions.length }, "Query expanded");
6650
8226
  return [query, ...expansions];
6651
8227
  } catch (err) {
6652
- log11.debug({ err }, "Query expansion failed, using original");
8228
+ log17.debug({ err }, "Query expansion failed, using original");
6653
8229
  return [query];
6654
8230
  }
6655
8231
  }
@@ -6662,11 +8238,12 @@ async function recallMemories(opts) {
6662
8238
  let vectorScores = opts._vectorScores || /* @__PURE__ */ new Map();
6663
8239
  let primaryQueryEmbedding = null;
6664
8240
  const vectorSearchPromise = queries.length > 0 && isEmbeddingEnabled() && !opts._vectorScores ? (async () => {
8241
+ const space = activeEmbeddingSpace();
6665
8242
  const queryEmbeddings = await Promise.all(
6666
8243
  queries.map(async (q) => {
6667
8244
  const cached = getCachedEmbedding2(q);
6668
8245
  if (cached) return cached;
6669
- const emb = await generateQueryEmbedding(q);
8246
+ const emb = await generateQueryEmbeddingForSpace(space, q);
6670
8247
  if (emb) setCachedEmbedding2(q, emb);
6671
8248
  return emb;
6672
8249
  })
@@ -6674,39 +8251,69 @@ async function recallMemories(opts) {
6674
8251
  const validEmbeddings = queryEmbeddings.filter((e) => e !== null);
6675
8252
  if (validEmbeddings.length > 0) primaryQueryEmbedding = validEmbeddings[0];
6676
8253
  if (validEmbeddings.length === 0) {
6677
- log11.debug("All query embeddings returned null, using keyword-only retrieval");
8254
+ log17.debug("All query embeddings returned null, using keyword-only retrieval");
6678
8255
  return;
6679
8256
  }
6680
8257
  try {
6681
- const allSearches = validEmbeddings.map(
6682
- (emb) => Promise.resolve(db.rpc("match_memories", {
6683
- query_embedding: JSON.stringify(emb),
6684
- match_threshold: VECTOR_MATCH_THRESHOLD,
6685
- match_count: limit * (opts.skipExpansion ? 12 : 4),
6686
- filter_types: opts.memoryTypes || null,
6687
- filter_user: opts.relatedUser || null,
6688
- min_decay: minDecay,
6689
- filter_owner: getOwnerWallet() || null,
6690
- filter_tags: opts.tags && opts.tags.length > 0 ? opts.tags : null
6691
- })).then((r) => r.data || [])
6692
- );
8258
+ const allSearches = validEmbeddings.flatMap((emb) => {
8259
+ const searches = [
8260
+ Promise.resolve(db.rpc(vectorRpcName("match_memories"), {
8261
+ query_embedding: JSON.stringify(emb),
8262
+ match_threshold: VECTOR_MATCH_THRESHOLD,
8263
+ match_count: limit * (opts.skipExpansion ? 12 : 4),
8264
+ filter_types: opts.memoryTypes || null,
8265
+ filter_user: opts.relatedUser || null,
8266
+ min_decay: minDecay,
8267
+ filter_owner: getOwnerScope(),
8268
+ filter_tags: opts.tags && opts.tags.length > 0 ? opts.tags : null
8269
+ })).then((r) => r.data || [])
8270
+ ];
8271
+ if (!opts.skipExpansion) {
8272
+ searches.push(
8273
+ Promise.resolve(db.rpc(vectorRpcName("match_memory_fragments"), buildFragmentRpcArgs({
8274
+ embeddingJson: JSON.stringify(emb),
8275
+ matchThreshold: VECTOR_MATCH_THRESHOLD,
8276
+ matchCount: limit * 2,
8277
+ minDecay,
8278
+ memoryTypes: opts.memoryTypes
8279
+ }))).then((r) => r.data || [])
8280
+ );
8281
+ }
8282
+ return searches;
8283
+ });
6693
8284
  const results2 = await Promise.all(allSearches);
6694
- for (const batch of results2) {
6695
- for (const m of batch) {
6696
- const current = vectorScores.get(m.id) || 0;
6697
- vectorScores.set(m.id, Math.max(current, m.similarity));
8285
+ let fragmentHits = 0;
8286
+ if (opts.skipExpansion) {
8287
+ for (const batch of results2) {
8288
+ for (const m of batch) {
8289
+ const current = vectorScores.get(m.id) || 0;
8290
+ vectorScores.set(m.id, Math.max(current, m.similarity));
8291
+ }
8292
+ }
8293
+ } else {
8294
+ for (let i = 0; i < results2.length; i++) {
8295
+ const isFragmentBatch = i % 2 === 1;
8296
+ for (const r of results2[i]) {
8297
+ const id = isFragmentBatch ? r.memory_id : r.id;
8298
+ const sim = isFragmentBatch ? r.max_similarity : r.similarity;
8299
+ if (!id) continue;
8300
+ const current = vectorScores.get(id) || 0;
8301
+ vectorScores.set(id, Math.max(current, sim));
8302
+ if (isFragmentBatch) fragmentHits++;
8303
+ }
6698
8304
  }
6699
8305
  }
6700
- log11.debug({
8306
+ log17.debug({
6701
8307
  queryVariants: validEmbeddings.length,
6702
8308
  uniqueMemories: vectorScores.size,
8309
+ fragmentHits,
6703
8310
  fastMode: !!opts.skipExpansion
6704
8311
  }, "Vector search completed");
6705
8312
  } catch (err) {
6706
- log11.warn({ err }, "Vector search RPC failed, falling back to keyword retrieval");
8313
+ log17.warn({ err }, "Vector search RPC failed, falling back to keyword retrieval");
6707
8314
  }
6708
8315
  })() : Promise.resolve();
6709
- 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);
8316
+ 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);
6710
8317
  importanceQuery = scopeToOwner(importanceQuery);
6711
8318
  if (opts.memoryTypes && opts.memoryTypes.length > 0) {
6712
8319
  importanceQuery = importanceQuery.in("memory_type", opts.memoryTypes);
@@ -6729,7 +8336,12 @@ async function recallMemories(opts) {
6729
8336
  return await bm25SearchMemories(opts.query, {
6730
8337
  limit: limit * 2,
6731
8338
  minDecay,
6732
- filterOwner: _ownerWallet || void 0,
8339
+ // Use getOwnerWallet() (AsyncLocalStorage-aware), NOT the bare module-level
8340
+ // _ownerWallet. withOwnerWallet() sets the async context, not the module var,
8341
+ // so the bare var is null under hosted/wrapped calls and BM25 would search the
8342
+ // ENTIRE table unscoped — burying the owner's exact-match facts. This is why
8343
+ // BM25 silently never surfaced bench facts even after the RPC was fixed.
8344
+ filterOwner: getOwnerScope() || void 0,
6733
8345
  filterTypes: opts.memoryTypes || void 0,
6734
8346
  filterTags: opts.tags || void 0
6735
8347
  });
@@ -6783,7 +8395,7 @@ async function recallMemories(opts) {
6783
8395
  }
6784
8396
  }
6785
8397
  }
6786
- if (seedsAdded > 0) log11.debug({ seedsAdded, seedsTotal: knowledgeSeeds.length }, "Knowledge seeds added to candidates");
8398
+ if (seedsAdded > 0) log17.debug({ seedsAdded, seedsTotal: knowledgeSeeds.length }, "Knowledge seeds added to candidates");
6787
8399
  }
6788
8400
  }
6789
8401
  const bm25Scores = /* @__PURE__ */ new Map();
@@ -6804,18 +8416,18 @@ async function recallMemories(opts) {
6804
8416
  }
6805
8417
  }
6806
8418
  }
6807
- log11.debug({ bm25Hits: bm25Results.length, bm25New: bm25MissingIds.length }, "BM25 search added candidates");
8419
+ log17.debug({ bm25Hits: bm25Results.length, bm25New: bm25MissingIds.length }, "BM25 search added candidates");
6808
8420
  }
6809
8421
  if (error) {
6810
- log11.error({ error: error.message }, "Memory recall query failed");
8422
+ log17.error({ error: error.message }, "Memory recall query failed");
6811
8423
  return [];
6812
8424
  }
6813
- let candidates = decryptMemoryBatch(data || []);
8425
+ let candidates = await decryptMemories(data || []);
6814
8426
  if (vectorScores.size > 0) {
6815
8427
  const metadataIds = new Set(candidates.map((m) => m.id));
6816
8428
  const missingIds = [...vectorScores.keys()].filter((id) => !metadataIds.has(id));
6817
8429
  if (missingIds.length > 0) {
6818
- let vectorQuery = db.from("memories").select("*").in("id", missingIds);
8430
+ let vectorQuery = db.from("memories").select("*").in("id", missingIds).gte("decay_factor", minDecay).not("embedding", "is", null);
6819
8431
  vectorQuery = scopeToOwner(vectorQuery);
6820
8432
  if (opts.memoryTypes && opts.memoryTypes.length > 0) {
6821
8433
  vectorQuery = vectorQuery.in("memory_type", opts.memoryTypes);
@@ -6824,7 +8436,7 @@ async function recallMemories(opts) {
6824
8436
  vectorQuery = vectorQuery.overlaps("tags", opts.tags);
6825
8437
  }
6826
8438
  const { data: vectorOnly } = await vectorQuery;
6827
- if (vectorOnly) candidates = [...candidates, ...decryptMemoryBatch(vectorOnly)];
8439
+ if (vectorOnly) candidates = [...candidates, ...await decryptMemories(vectorOnly)];
6828
8440
  }
6829
8441
  }
6830
8442
  if (candidates.length === 0) return [];
@@ -6848,10 +8460,10 @@ async function recallMemories(opts) {
6848
8460
  if (entities.length > 0) {
6849
8461
  const resultIdSet = new Set(results.map((m) => m.id));
6850
8462
  for (const entity of entities) {
6851
- const entityMemories = await getMemoriesByEntity(entity.id, {
8463
+ const entityMemories = await decryptMemories(await getMemoriesByEntity(entity.id, {
6852
8464
  limit: Math.ceil(limit / 2),
6853
8465
  memoryTypes: opts.memoryTypes
6854
- });
8466
+ }));
6855
8467
  for (const mem of entityMemories) {
6856
8468
  addLinkPath(mem.id, "entity");
6857
8469
  if (!resultIdSet.has(mem.id)) {
@@ -6863,17 +8475,17 @@ async function recallMemories(opts) {
6863
8475
  }
6864
8476
  }
6865
8477
  }
6866
- log11.debug({ entities: entities.map((e) => e.name) }, "Entity-aware recall applied");
8478
+ log17.debug({ entities: entities.map((e) => e.name) }, "Entity-aware recall applied");
6867
8479
  let cooccurrenceAdded = 0;
6868
8480
  const cooccurrenceNames = [];
6869
8481
  for (const entity of entities) {
6870
8482
  const cooccurrences = await getEntityCooccurrences(entity.id, { minCooccurrence: 2, maxResults: 3 });
6871
8483
  for (const cooc of cooccurrences) {
6872
8484
  if (cooccurrenceAdded >= limit) break;
6873
- const coMems = await getMemoriesByEntity(cooc.related_entity_id, {
8485
+ const coMems = await decryptMemories(await getMemoriesByEntity(cooc.related_entity_id, {
6874
8486
  limit: 3,
6875
8487
  memoryTypes: opts.memoryTypes
6876
- });
8488
+ }));
6877
8489
  for (const mem of coMems) {
6878
8490
  if (cooccurrenceAdded >= limit) break;
6879
8491
  addLinkPath(mem.id, "entity");
@@ -6893,11 +8505,11 @@ async function recallMemories(opts) {
6893
8505
  }
6894
8506
  }
6895
8507
  if (cooccurrenceAdded > 0) {
6896
- log11.debug({ cooccurrenceAdded, cooccurrenceEntities: cooccurrenceNames.length }, "Entity co-occurrence recall applied");
8508
+ log17.debug({ cooccurrenceAdded, cooccurrenceEntities: cooccurrenceNames.length }, "Entity co-occurrence recall applied");
6897
8509
  }
6898
8510
  }
6899
8511
  } catch (err) {
6900
- log11.debug({ err }, "Entity-aware recall skipped");
8512
+ log17.debug({ err }, "Entity-aware recall skipped");
6901
8513
  }
6902
8514
  }
6903
8515
  if (results.length > 0) {
@@ -6907,17 +8519,17 @@ async function recallMemories(opts) {
6907
8519
  seed_ids: seedIds,
6908
8520
  min_strength: 0.2,
6909
8521
  max_results: limit,
6910
- filter_owner: getOwnerWallet() || null
8522
+ filter_owner: getOwnerScope()
6911
8523
  });
6912
8524
  if (linked && linked.length > 0) {
6913
8525
  const resultIdSet = new Set(seedIds);
6914
8526
  const graphCandidateIds = linked.filter((l) => !resultIdSet.has(l.memory_id)).map((l) => l.memory_id);
6915
8527
  if (graphCandidateIds.length > 0) {
6916
- let graphQuery = db.from("memories").select("*").in("id", graphCandidateIds);
8528
+ let graphQuery = db.from("memories").select("*").in("id", graphCandidateIds).not("provider_delegated", "is", false);
6917
8529
  graphQuery = scopeToOwner(graphQuery);
6918
8530
  const { data: graphMemories } = await graphQuery;
6919
8531
  if (graphMemories && graphMemories.length > 0) {
6920
- decryptMemoryBatch(graphMemories);
8532
+ await decryptMemories(graphMemories);
6921
8533
  const linkBoostMap = /* @__PURE__ */ new Map();
6922
8534
  for (const l of linked) {
6923
8535
  const bondWeight = BOND_TYPE_WEIGHTS[l.link_type] ?? 0.4;
@@ -6931,7 +8543,7 @@ async function recallMemories(opts) {
6931
8543
  _score: scoreMemory(mem, scoredOpts) + RETRIEVAL_WEIGHT_GRAPH * (linkBoostMap.get(mem.id) || 0)
6932
8544
  }));
6933
8545
  results = [...results, ...graphScored].sort((a, b) => b._score - a._score).slice(0, limit);
6934
- log11.debug({
8546
+ log17.debug({
6935
8547
  graphExpanded: graphMemories.length,
6936
8548
  linkedTotal: linked.length,
6937
8549
  bondTypes: [...new Set(linked.map((l) => l.link_type))]
@@ -6940,7 +8552,7 @@ async function recallMemories(opts) {
6940
8552
  }
6941
8553
  }
6942
8554
  } catch (err) {
6943
- log11.debug({ err }, "Graph expansion skipped (RPC unavailable)");
8555
+ log17.debug({ err }, "Graph expansion skipped (RPC unavailable)");
6944
8556
  }
6945
8557
  }
6946
8558
  if (results.length >= 3) {
@@ -6956,23 +8568,23 @@ async function recallMemories(opts) {
6956
8568
  ...results.slice(0, results.length - replaceCount),
6957
8569
  ...diverseCandidates.slice(0, replaceCount)
6958
8570
  ].sort((a, b) => b._score - a._score).slice(0, limit);
6959
- log11.debug({ injectedTypes: diverseCandidates.map((m) => m.memory_type) }, "Type diversity applied");
8571
+ log17.debug({ injectedTypes: diverseCandidates.map((m) => m.memory_type) }, "Type diversity applied");
6960
8572
  }
6961
8573
  }
6962
8574
  }
6963
- const finalOwner = getOwnerWallet();
6964
- if (finalOwner) {
6965
- const beforeCount = results.length;
6966
- results = results.filter((m) => m.owner_wallet === finalOwner);
6967
- if (results.length < beforeCount) {
6968
- log11.warn({ stripped: beforeCount - results.length, owner: finalOwner }, "Owner guard stripped foreign memories from recall results");
8575
+ const finalScope = getOwnerScope();
8576
+ {
8577
+ const { kept, stripped } = applyOwnerPostGuard(results, finalScope);
8578
+ if (stripped > 0) {
8579
+ log17.warn({ stripped, botOwn: finalScope === SCOPE_BOT_OWN2 }, "Owner guard stripped foreign memories from recall results");
6969
8580
  }
8581
+ results = kept;
6970
8582
  }
6971
- if (opts.trackAccess !== false) {
8583
+ if (shouldTrackAccess(opts)) {
6972
8584
  const ids = results.map((m) => m.id);
6973
8585
  const sources = results.map((m) => m.source || "");
6974
- updateMemoryAccess(ids, sources).catch((err) => log11.warn({ err }, "Memory access tracking failed"));
6975
- reinforceCoRetrievedLinks(ids).catch((err) => log11.debug({ err }, "Link reinforcement failed"));
8586
+ updateMemoryAccess(ids, sources).catch((err) => log17.warn({ err }, "Memory access tracking failed"));
8587
+ reinforceCoRetrievedLinks(ids).catch((err) => log17.debug({ err }, "Link reinforcement failed"));
6976
8588
  }
6977
8589
  for (const m of results) {
6978
8590
  const paths = linkPathMap.get(m.id);
@@ -6980,7 +8592,7 @@ async function recallMemories(opts) {
6980
8592
  m.link_path = [...paths];
6981
8593
  }
6982
8594
  }
6983
- log11.debug({
8595
+ log17.debug({
6984
8596
  recalled: results.length,
6985
8597
  topScore: results[0]?._score?.toFixed(3),
6986
8598
  query: opts.query?.slice(0, 40),
@@ -6990,7 +8602,7 @@ async function recallMemories(opts) {
6990
8602
  }, "Memories recalled");
6991
8603
  return results;
6992
8604
  } catch (err) {
6993
- log11.error({ err }, "Memory recall failed");
8605
+ log17.error({ err }, "Memory recall failed");
6994
8606
  return [];
6995
8607
  }
6996
8608
  }
@@ -7004,7 +8616,7 @@ function extractQueryTerms(query) {
7004
8616
  }
7005
8617
  function scoreMemory(mem, opts) {
7006
8618
  const now = Date.now();
7007
- const hoursSinceAccess = (now - new Date(mem.last_accessed).getTime()) / (1e3 * 60 * 60);
8619
+ const hoursSinceAccess = Math.max(0, (now - new Date(mem.last_accessed).getTime()) / (1e3 * 60 * 60));
7008
8620
  const recency = Math.pow(RECENCY_DECAY_BASE, hoursSinceAccess);
7009
8621
  let textScore = 0.5;
7010
8622
  if (opts.query) {
@@ -7040,7 +8652,7 @@ function scoreMemory(mem, opts) {
7040
8652
  }
7041
8653
  const bm25Rank = opts._bm25Scores?.get(mem.id) || 0;
7042
8654
  if (bm25Rank > 0) {
7043
- rawScore += 0.15 * Math.min(bm25Rank, 1);
8655
+ rawScore += RETRIEVAL_WEIGHT_BM25 * Math.min(bm25Rank, 1);
7044
8656
  }
7045
8657
  const typeBoost = KNOWLEDGE_TYPE_BOOST[mem.memory_type] || 0;
7046
8658
  rawScore += typeBoost;
@@ -7061,7 +8673,7 @@ async function recallMemorySummaries(opts) {
7061
8673
  const limit = opts.limit || 10;
7062
8674
  const minDecay = opts.minDecay ?? 0.1;
7063
8675
  try {
7064
- 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);
8676
+ 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);
7065
8677
  query = scopeToOwner(query);
7066
8678
  if (opts.memoryTypes && opts.memoryTypes.length > 0) {
7067
8679
  query = query.in("memory_type", opts.memoryTypes);
@@ -7080,12 +8692,12 @@ async function recallMemorySummaries(opts) {
7080
8692
  }
7081
8693
  const { data, error } = await query;
7082
8694
  if (error) {
7083
- log11.error({ error: error.message }, "Memory summary recall failed");
8695
+ log17.error({ error: error.message }, "Memory summary recall failed");
7084
8696
  return [];
7085
8697
  }
7086
8698
  return data || [];
7087
8699
  } catch (err) {
7088
- log11.error({ err }, "Memory summary recall failed");
8700
+ log17.error({ err }, "Memory summary recall failed");
7089
8701
  return [];
7090
8702
  }
7091
8703
  }
@@ -7097,28 +8709,23 @@ async function hydrateMemories(ids) {
7097
8709
  query = scopeToOwner(query);
7098
8710
  const { data, error } = await query;
7099
8711
  if (error) {
7100
- log11.error({ error: error.message }, "Memory hydration failed");
8712
+ log17.error({ error: error.message }, "Memory hydration failed");
7101
8713
  return [];
7102
8714
  }
7103
- return decryptMemoryBatch(data || []);
8715
+ return await decryptMemories(data || []);
7104
8716
  } catch (err) {
7105
- log11.error({ err }, "Memory hydration failed");
8717
+ log17.error({ err }, "Memory hydration failed");
7106
8718
  return [];
7107
8719
  }
7108
8720
  }
7109
- async function updateMemoryAccess(ids, sources = []) {
8721
+ async function updateMemoryAccess(ids, _sources = []) {
7110
8722
  if (ids.length === 0) return;
7111
8723
  const db = getDb();
7112
- const importanceBoosts = ids.map((_, i) => {
7113
- const source = sources[i] || "";
7114
- return INTERNAL_MEMORY_SOURCES.has(source) ? INTERNAL_IMPORTANCE_BOOST : 0.02;
7115
- });
7116
8724
  const { error } = await db.rpc("batch_boost_memory_access", {
7117
- memory_ids: ids,
7118
- importance_boosts: importanceBoosts
8725
+ memory_ids: ids
7119
8726
  });
7120
8727
  if (error) {
7121
- log11.warn({ error: error.message, ids }, "Batch memory access update failed");
8728
+ log17.warn({ error: error.message, ids }, "Batch memory access update failed");
7122
8729
  }
7123
8730
  }
7124
8731
  async function createMemoryLink(sourceId, targetId, linkType, strength = 0.5) {
@@ -7131,7 +8738,7 @@ async function createMemoryLink(sourceId, targetId, linkType, strength = 0.5) {
7131
8738
  strength: clamp(strength, 0, 1)
7132
8739
  }, { onConflict: "source_id,target_id,link_type" });
7133
8740
  if (error) {
7134
- log11.debug({ error: error.message, sourceId, targetId, linkType }, "Link creation failed");
8741
+ log17.debug({ error: error.message, sourceId, targetId, linkType }, "Link creation failed");
7135
8742
  }
7136
8743
  }
7137
8744
  async function createMemoryLinksBatch(links) {
@@ -7153,7 +8760,7 @@ async function createMemoryLinksBatch(links) {
7153
8760
  const db = getDb();
7154
8761
  const { error } = await db.from("memory_links").upsert(rows, { onConflict: "source_id,target_id,link_type" });
7155
8762
  if (error) {
7156
- log11.debug({ error: error.message, count: rows.length }, "Batch link creation failed");
8763
+ log17.debug({ error: error.message, count: rows.length }, "Batch link creation failed");
7157
8764
  }
7158
8765
  }
7159
8766
  async function autoLinkMemory(memoryId, opts) {
@@ -7172,7 +8779,7 @@ async function autoLinkMemory(memoryId, opts) {
7172
8779
  query_embedding: JSON.stringify(embedding),
7173
8780
  match_threshold: LINK_SIMILARITY_THRESHOLD,
7174
8781
  match_count: MAX_AUTO_LINKS * 2,
7175
- filter_owner: getOwnerWallet() || null
8782
+ filter_owner: getOwnerScope()
7176
8783
  });
7177
8784
  if (similar) {
7178
8785
  const similarIds = similar.map((s) => s.id).filter((id) => id !== memoryId);
@@ -7221,7 +8828,7 @@ async function autoLinkMemory(memoryId, opts) {
7221
8828
  }
7222
8829
  if (pendingLinks.length > 0) {
7223
8830
  await createMemoryLinksBatch(pendingLinks);
7224
- log11.debug({ memoryId, linksCreated: pendingLinks.length }, "Auto-linked memory");
8831
+ log17.debug({ memoryId, linksCreated: pendingLinks.length }, "Auto-linked memory");
7225
8832
  }
7226
8833
  }
7227
8834
  function classifyLinkType(newMem, candidate, newConcepts) {
@@ -7243,16 +8850,16 @@ async function reinforceCoRetrievedLinks(ids) {
7243
8850
  boost_amount: LINK_CO_RETRIEVAL_BOOST
7244
8851
  });
7245
8852
  if (error) {
7246
- log11.debug({ error: error.message }, "Link reinforcement RPC failed");
8853
+ log17.debug({ error: error.message }, "Link reinforcement RPC failed");
7247
8854
  } else if (data && data > 0) {
7248
- log11.debug({ boosted: data }, "Co-retrieval link reinforcement applied");
8855
+ log17.debug({ boosted: data }, "Co-retrieval link reinforcement applied");
7249
8856
  }
7250
8857
  }
7251
8858
  async function extractAndLinkEntitiesForMemory(memoryId, opts) {
7252
8859
  try {
7253
8860
  await extractAndLinkEntities(memoryId, opts.content, opts.summary, opts.relatedUser);
7254
8861
  } catch (err) {
7255
- log11.debug({ err, memoryId }, "Entity extraction failed");
8862
+ log17.debug({ err, memoryId }, "Entity extraction failed");
7256
8863
  }
7257
8864
  }
7258
8865
  async function decayMemories() {
@@ -7268,17 +8875,17 @@ async function decayMemories() {
7268
8875
  cutoff
7269
8876
  });
7270
8877
  if (error) {
7271
- log11.warn({ error: error.message, memType }, "Batch decay failed for type");
8878
+ log17.warn({ error: error.message, memType }, "Batch decay failed for type");
7272
8879
  continue;
7273
8880
  }
7274
8881
  totalDecayed += data || 0;
7275
8882
  }
7276
8883
  if (totalDecayed > 0) {
7277
- log11.info({ decayed: totalDecayed }, "Type-specific memory decay applied");
8884
+ log17.info({ decayed: totalDecayed }, "Type-specific memory decay applied");
7278
8885
  }
7279
8886
  return totalDecayed;
7280
8887
  } catch (err) {
7281
- log11.error({ err }, "Memory decay failed");
8888
+ log17.error({ err }, "Memory decay failed");
7282
8889
  return 0;
7283
8890
  }
7284
8891
  }
@@ -7288,7 +8895,7 @@ async function deleteMemory(id) {
7288
8895
  query = scopeToOwner(query);
7289
8896
  const { error } = await query;
7290
8897
  if (error) {
7291
- log11.error({ error: error.message, id }, "Failed to delete memory");
8898
+ log17.error({ error: error.message, id }, "Failed to delete memory");
7292
8899
  return false;
7293
8900
  }
7294
8901
  return true;
@@ -7306,7 +8913,7 @@ async function updateMemory(id, patches) {
7306
8913
  query = scopeToOwner(query);
7307
8914
  const { error } = await query;
7308
8915
  if (error) {
7309
- log11.error({ error: error.message, id }, "Failed to update memory");
8916
+ log17.error({ error: error.message, id }, "Failed to update memory");
7310
8917
  return false;
7311
8918
  }
7312
8919
  return true;
@@ -7327,10 +8934,10 @@ async function listMemories(opts) {
7327
8934
  if (opts.min_importance !== void 0) dataQ = dataQ.gte("importance", opts.min_importance);
7328
8935
  const { data, error } = await dataQ;
7329
8936
  if (error) {
7330
- log11.error({ error: error.message }, "Failed to list memories");
8937
+ log17.error({ error: error.message }, "Failed to list memories");
7331
8938
  return { memories: [], total: 0 };
7332
8939
  }
7333
- return { memories: decryptMemoryBatch(data || []), total: count ?? 0 };
8940
+ return { memories: await decryptMemories(data || []), total: count ?? 0 };
7334
8941
  }
7335
8942
  async function getMemoryStats() {
7336
8943
  const db = getDb();
@@ -7367,7 +8974,7 @@ async function getMemoryStats() {
7367
8974
  const MAX_PAGES = 20;
7368
8975
  let allMemories = [];
7369
8976
  for (let page = 0; page < MAX_PAGES; page++) {
7370
- let pageQuery = db.from("memories").select("importance, decay_factor, created_at, related_user, tags, concepts").order("id", { ascending: true }).range(page * PAGE_SIZE, (page + 1) * PAGE_SIZE - 1);
8977
+ 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);
7371
8978
  pageQuery = scopeToOwner(pageQuery);
7372
8979
  const { data: pageData } = await pageQuery;
7373
8980
  if (!pageData || pageData.length === 0) break;
@@ -7384,6 +8991,7 @@ async function getMemoryStats() {
7384
8991
  impSum += m.importance;
7385
8992
  decaySum += m.decay_factor;
7386
8993
  if (m.related_user) users.add(m.related_user);
8994
+ if (m.owner_wallet) users.add(m.owner_wallet);
7387
8995
  if (m.tags) {
7388
8996
  for (const tag of m.tags) {
7389
8997
  tagCounts[tag] = (tagCounts[tag] || 0) + 1;
@@ -7404,13 +9012,15 @@ async function getMemoryStats() {
7404
9012
  stats.oldestMemory = sorted[0] || null;
7405
9013
  stats.newestMemory = sorted[sorted.length - 1] || null;
7406
9014
  }
7407
- const { count, error: dreamError } = await db.from("dream_logs").select("id", { count: "exact", head: true });
9015
+ let dreamQuery = db.from("dream_logs").select("id", { count: "exact", head: true });
9016
+ dreamQuery = scopeToOwner(dreamQuery);
9017
+ const { count, error: dreamError } = await dreamQuery;
7408
9018
  if (dreamError) {
7409
- log11.warn({ error: dreamError.message }, "Failed to count dream logs");
9019
+ log17.warn({ error: dreamError.message }, "Failed to count dream logs");
7410
9020
  }
7411
9021
  stats.totalDreamSessions = count || 0;
7412
9022
  } catch (err) {
7413
- log11.error({ err }, "Failed to get memory stats");
9023
+ log17.error({ err }, "Failed to get memory stats");
7414
9024
  }
7415
9025
  return stats;
7416
9026
  }
@@ -7424,10 +9034,10 @@ async function getRecentMemories(hours, types, limit) {
7424
9034
  }
7425
9035
  const { data, error } = await query;
7426
9036
  if (error) {
7427
- log11.error({ error: error.message }, "Failed to get recent memories");
9037
+ log17.error({ error: error.message }, "Failed to get recent memories");
7428
9038
  return [];
7429
9039
  }
7430
- return decryptMemoryBatch(data || []);
9040
+ return await decryptMemories(data || []);
7431
9041
  }
7432
9042
  async function getSelfModel() {
7433
9043
  const db = getDb();
@@ -7435,10 +9045,10 @@ async function getSelfModel() {
7435
9045
  query = scopeToOwner(query);
7436
9046
  const { data, error } = await query;
7437
9047
  if (error) {
7438
- log11.error({ error: error.message }, "Failed to get self model");
9048
+ log17.error({ error: error.message }, "Failed to get self model");
7439
9049
  return [];
7440
9050
  }
7441
- return decryptMemoryBatch(data || []);
9051
+ return await decryptMemories(data || []);
7442
9052
  }
7443
9053
  async function storeDreamLog(sessionType, inputMemoryIds, output, newMemoryIds) {
7444
9054
  const db = getDb();
@@ -7446,54 +9056,48 @@ async function storeDreamLog(sessionType, inputMemoryIds, output, newMemoryIds)
7446
9056
  session_type: sessionType,
7447
9057
  input_memory_ids: inputMemoryIds,
7448
9058
  output: output.slice(0, MEMORY_MAX_CONTENT_LENGTH),
7449
- new_memories_created: newMemoryIds
9059
+ new_memories_created: newMemoryIds,
9060
+ // Stamp owner so getMemoryStats can scope the dream count (migration 021).
9061
+ owner_wallet: getOwnerWallet() === SCOPE_BOT_OWN2 ? null : getOwnerWallet()
7450
9062
  });
7451
9063
  if (error) {
7452
- log11.error({ error: error.message }, "Failed to store dream log");
9064
+ log17.error({ error: error.message }, "Failed to store dream log");
7453
9065
  }
7454
9066
  }
7455
9067
  function formatMemoryContext(memories) {
7456
9068
  if (memories.length === 0) return "";
7457
9069
  const lines = ["## Memory Recall"];
7458
- const episodic = memories.filter((m) => m.memory_type === "episodic");
7459
- const semantic = memories.filter((m) => m.memory_type === "semantic");
7460
- const procedural = memories.filter((m) => m.memory_type === "procedural");
7461
- const selfModel = memories.filter((m) => m.memory_type === "self_model");
7462
- const introspective = memories.filter((m) => m.memory_type === "introspective");
9070
+ const episodic = memories.filter((m) => m.memory_type === "episodic").sort(byMemoryDateAsc);
9071
+ const semantic = memories.filter((m) => m.memory_type === "semantic").sort(byMemoryDateAsc);
9072
+ const procedural = memories.filter((m) => m.memory_type === "procedural").sort(byMemoryDateAsc);
9073
+ const selfModel = memories.filter((m) => m.memory_type === "self_model").sort(byMemoryDateAsc);
9074
+ const introspective = memories.filter((m) => m.memory_type === "introspective").sort(byMemoryDateAsc);
7463
9075
  if (episodic.length > 0) {
7464
9076
  lines.push("### Past Interactions");
7465
- for (const m of episodic) {
7466
- lines.push(`- [${timeAgo2(m.created_at)}] ${m.summary}`);
7467
- }
9077
+ for (const m of episodic) lines.push(renderGroundedLine(m));
7468
9078
  }
7469
9079
  if (semantic.length > 0) {
7470
9080
  lines.push("### Things You Know");
7471
- for (const m of semantic) {
7472
- lines.push(`- ${m.summary}`);
7473
- }
9081
+ for (const m of semantic) lines.push(renderGroundedLine(m));
7474
9082
  }
7475
9083
  if (procedural.length > 0) {
7476
9084
  lines.push("### Learned Strategies (from past outcomes)");
7477
9085
  for (const m of procedural) {
7478
9086
  const meta = m.metadata;
7479
9087
  const confidence = meta?.positiveRate != null ? ` [${Math.round(meta.positiveRate * 100)}% success rate, based on ${meta.basedOn || "?"} interactions]` : "";
7480
- lines.push(`- ${m.summary}${confidence}`);
9088
+ lines.push(renderGroundedLine(m, confidence));
7481
9089
  }
7482
9090
  }
7483
9091
  if (introspective.length > 0) {
7484
9092
  lines.push("### Your Own Reflections");
7485
- for (const m of introspective) {
7486
- lines.push(`- [${timeAgo2(m.created_at)}] ${m.summary}`);
7487
- }
9093
+ for (const m of introspective) lines.push(renderGroundedLine(m));
7488
9094
  }
7489
9095
  if (selfModel.length > 0) {
7490
9096
  lines.push("### Self-Observations");
7491
- for (const m of selfModel) {
7492
- lines.push(`- ${m.summary}`);
7493
- }
9097
+ for (const m of selfModel) lines.push(renderGroundedLine(m));
7494
9098
  }
7495
9099
  lines.push("");
7496
- lines.push("You REMEMBER these interactions and facts. Reference them naturally if relevant.");
9100
+ lines.push(CONTEXT_GROUNDING_RULES);
7497
9101
  if (procedural.length > 0) {
7498
9102
  lines.push("");
7499
9103
  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.");
@@ -7518,10 +9122,10 @@ async function scoreImportanceWithLLM(description, fallbackOpts) {
7518
9122
  if (!isNaN(parsed) && parsed >= 1 && parsed <= 10) {
7519
9123
  return parsed / 10;
7520
9124
  }
7521
- log11.warn({ response }, "LLM importance score unparseable, using fallback");
9125
+ log17.warn({ response }, "LLM importance score unparseable, using fallback");
7522
9126
  return calculateImportance(fallbackOpts || {});
7523
9127
  } catch (err) {
7524
- log11.warn({ err }, "LLM importance scoring failed, using fallback");
9128
+ log17.warn({ err }, "LLM importance scoring failed, using fallback");
7525
9129
  return calculateImportance(fallbackOpts || {});
7526
9130
  }
7527
9131
  }
@@ -7545,7 +9149,8 @@ async function matchByEmbedding(opts) {
7545
9149
  query_embedding: JSON.stringify(opts.embedding),
7546
9150
  match_threshold: opts.threshold,
7547
9151
  match_count: opts.limit,
7548
- filter_owner: opts.ownerWallet ?? null
9152
+ // Explicit ownerWallet wins; otherwise the ambient scope (sentinel-aware, C0).
9153
+ filter_owner: opts.ownerWallet ?? getOwnerScope()
7549
9154
  });
7550
9155
  return (data ?? []).map((r) => ({
7551
9156
  id: r.id,
@@ -7568,32 +9173,39 @@ function moodToValence(mood) {
7568
9173
  return 0;
7569
9174
  }
7570
9175
  }
7571
- var import_crypto3, EMBED_CACHE_MAX, embeddingCache, _ownerWallet, SCOPE_BOT_OWN, HASH_ID_PREFIX, log11, DEDUP_TTL_MS, dedupCache, installedPacksCache, INSTALLED_PACKS_TTL_MS, topicEmbeddingCache, topicEmbeddingPopulationLock, TOKENISATION_SKIP_SOURCES, STOPWORDS;
9176
+ var import_crypto4, EMBED_CACHE_MAX, embeddingCache, _ownerWallet, SCOPE_BOT_OWN2, HASH_ID_PREFIX, log17, DEDUP_TTL_MS, dedupCache, installedPacksCache, INSTALLED_PACKS_TTL_MS, topicEmbeddingCache, topicEmbeddingPopulationLock, TOKENISATION_SKIP_SOURCES, outboxDegradeLogged, STOPWORDS, CONTEXT_GROUNDING_RULES;
7572
9177
  var init_memory = __esm({
7573
9178
  "packages/brain/src/memory/memory.ts"() {
7574
9179
  "use strict";
7575
9180
  init_database();
9181
+ init_config();
7576
9182
  init_logger();
9183
+ init_memory_grounding();
7577
9184
  init_utils();
7578
9185
  init_claude_client();
7579
9186
  init_solana_client();
7580
9187
  init_src2();
7581
9188
  init_embeddings();
9189
+ init_migration_profile();
7582
9190
  init_wiki_packs();
7583
9191
  init_config2();
7584
9192
  init_bm25_search();
9193
+ init_content_tokens();
9194
+ init_reconcile_shadow();
9195
+ init_memory_encryption();
7585
9196
  init_openrouter_client();
7586
9197
  init_encryption();
9198
+ init_memory_decryption();
7587
9199
  init_event_bus();
7588
- import_crypto3 = require("crypto");
9200
+ import_crypto4 = require("crypto");
7589
9201
  init_graph();
7590
9202
  init_owner_context();
7591
9203
  EMBED_CACHE_MAX = 200;
7592
9204
  embeddingCache = /* @__PURE__ */ new Map();
7593
9205
  _ownerWallet = null;
7594
- SCOPE_BOT_OWN = "__BOT_OWN__";
9206
+ SCOPE_BOT_OWN2 = "__BOT_OWN__";
7595
9207
  HASH_ID_PREFIX = "clude";
7596
- log11 = createChildLogger("memory");
9208
+ log17 = createChildLogger("memory");
7597
9209
  DEDUP_TTL_MS = 10 * 60 * 1e3;
7598
9210
  dedupCache = /* @__PURE__ */ new Map();
7599
9211
  installedPacksCache = /* @__PURE__ */ new Map();
@@ -7606,6 +9218,7 @@ var init_memory = __esm({
7606
9218
  "locomo-benchmark",
7607
9219
  "longmemeval-benchmark"
7608
9220
  ]);
9221
+ outboxDegradeLogged = false;
7609
9222
  STOPWORDS = /* @__PURE__ */ new Set([
7610
9223
  "the",
7611
9224
  "a",
@@ -7690,18 +9303,92 @@ var init_memory = __esm({
7690
9303
  "our",
7691
9304
  "their"
7692
9305
  ]);
9306
+ 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.`;
9307
+ }
9308
+ });
9309
+
9310
+ // packages/brain/src/memory/memory-revoke.ts
9311
+ async function revokeMemory(db, memoryId) {
9312
+ const { data: mem } = await db.from("memories").select("summary, embedding, encrypted, provider_delegated").eq("id", memoryId).maybeSingle();
9313
+ if (!mem || mem.encrypted !== true || mem.provider_delegated === false) {
9314
+ return { revoked: false, reason: "not_delegated" };
9315
+ }
9316
+ const { data: wrap } = await db.from("memory_dek_wraps").select("wrapped_dek, wrap_pubkey").eq("memory_id", memoryId).eq("recipient", "provider").maybeSingle();
9317
+ if (!wrap) return { revoked: false, reason: "no_provider_wrap" };
9318
+ let providerSecret;
9319
+ try {
9320
+ providerSecret = loadProviderKeypair().secretKey;
9321
+ } catch {
9322
+ return { revoked: false, reason: "no_provider_key" };
9323
+ }
9324
+ const dek = unwrapDek(wrap.wrapped_dek, wrap.wrap_pubkey, providerSecret);
9325
+ if (!dek) return { revoked: false, reason: "unwrap_failed" };
9326
+ const summaryCt = encryptField(String(mem.summary ?? ""), dek);
9327
+ const embeddingCt = mem.embedding != null ? encryptField(String(mem.embedding), dek) : "";
9328
+ const { error } = await db.rpc("revoke_memory", {
9329
+ p_memory_id: memoryId,
9330
+ p_summary_ct: summaryCt,
9331
+ p_embedding_ct: embeddingCt
9332
+ });
9333
+ if (error) {
9334
+ log18.error({ memoryId, error: error.message }, "revoke_memory RPC failed");
9335
+ return { revoked: false, reason: "rpc_failed" };
9336
+ }
9337
+ log18.info({ memoryId }, "Memory revoked \u2014 provider access destroyed");
9338
+ return { revoked: true };
9339
+ }
9340
+ async function redelegateMemory(db, memoryId, providerWrap) {
9341
+ const { data: mem } = await db.from("memories").select("content, summary_ciphertext, embedding_ciphertext, encrypted").eq("id", memoryId).maybeSingle();
9342
+ if (!mem || mem.encrypted !== true) return { redelegated: false, reason: "not_encrypted" };
9343
+ let providerSecret;
9344
+ try {
9345
+ providerSecret = loadProviderKeypair().secretKey;
9346
+ } catch {
9347
+ return { redelegated: false, reason: "no_provider_key" };
9348
+ }
9349
+ const dek = unwrapDek(providerWrap.wrapped_dek, providerWrap.wrap_pubkey, providerSecret);
9350
+ if (!dek) return { redelegated: false, reason: "invalid_wrap" };
9351
+ const content = decryptField(String(mem.content ?? ""), dek);
9352
+ if (content === null) return { redelegated: false, reason: "invalid_wrap" };
9353
+ const summary = mem.summary_ciphertext ? decryptField(mem.summary_ciphertext, dek) : "";
9354
+ const embedding = mem.embedding_ciphertext ? decryptField(mem.embedding_ciphertext, dek) : "";
9355
+ const { error } = await db.rpc("redelegate_memory", {
9356
+ p_memory_id: memoryId,
9357
+ p_summary: summary ?? "",
9358
+ p_embedding: embedding ?? "",
9359
+ p_content: content,
9360
+ p_wrapped_dek: providerWrap.wrapped_dek,
9361
+ p_wrap_pubkey: providerWrap.wrap_pubkey
9362
+ });
9363
+ if (error) {
9364
+ log18.error({ memoryId, error: error.message }, "redelegate_memory RPC failed");
9365
+ return { redelegated: false, reason: "rpc_failed" };
9366
+ }
9367
+ log18.info({ memoryId }, "Memory re-delegated \u2014 provider access restored");
9368
+ return { redelegated: true };
9369
+ }
9370
+ var log18;
9371
+ var init_memory_revoke = __esm({
9372
+ "packages/brain/src/memory/memory-revoke.ts"() {
9373
+ "use strict";
9374
+ init_memory_envelope();
9375
+ init_encryption_keys();
9376
+ init_logger();
9377
+ log18 = createChildLogger("memory-revoke");
7693
9378
  }
7694
9379
  });
7695
9380
 
7696
9381
  // packages/brain/src/memory/index.ts
7697
9382
  var memory_exports2 = {};
7698
9383
  __export(memory_exports2, {
7699
- SCOPE_BOT_OWN: () => SCOPE_BOT_OWN,
9384
+ SCOPE_BOT_OWN: () => SCOPE_BOT_OWN2,
7700
9385
  _setOwnerWallet: () => _setOwnerWallet,
7701
9386
  calculateImportance: () => calculateImportance,
7702
9387
  createMemoryLink: () => createMemoryLink,
7703
9388
  createMemoryLinksBatch: () => createMemoryLinksBatch,
7704
9389
  decayMemories: () => decayMemories,
9390
+ decryptMemories: () => decryptMemories,
9391
+ decryptOneContent: () => decryptOneContent,
7705
9392
  deleteMemory: () => deleteMemory,
7706
9393
  formatMemoryContext: () => formatMemoryContext,
7707
9394
  generateHashId: () => generateHashId,
@@ -7717,6 +9404,8 @@ __export(memory_exports2, {
7717
9404
  moodToValence: () => moodToValence,
7718
9405
  recallMemories: () => recallMemories,
7719
9406
  recallMemorySummaries: () => recallMemorySummaries,
9407
+ redelegateMemory: () => redelegateMemory,
9408
+ revokeMemory: () => revokeMemory,
7720
9409
  scopeToOwner: () => scopeToOwner,
7721
9410
  scoreImportanceWithLLM: () => scoreImportanceWithLLM,
7722
9411
  scoreMemory: () => scoreMemory,
@@ -7728,6 +9417,8 @@ var init_memory2 = __esm({
7728
9417
  "packages/brain/src/memory/index.ts"() {
7729
9418
  "use strict";
7730
9419
  init_memory();
9420
+ init_memory_decryption();
9421
+ init_memory_revoke();
7731
9422
  }
7732
9423
  });
7733
9424
 
@@ -8336,7 +10027,7 @@ function extractFromChatGPTConversations(conversations) {
8336
10027
  }
8337
10028
  async function parseChatGPTZip(filePath) {
8338
10029
  const { execSync: execSync2 } = require("child_process");
8339
- const { mkdtempSync: mkdtempSync3, readdirSync: readdirSync3 } = require("fs");
10030
+ const { mkdtempSync: mkdtempSync3, readdirSync: readdirSync4 } = require("fs");
8340
10031
  const { join: join13 } = require("path");
8341
10032
  const os6 = require("os");
8342
10033
  const tmpDir = mkdtempSync3(join13(os6.tmpdir(), "clude-import-"));
@@ -8346,7 +10037,7 @@ async function parseChatGPTZip(filePath) {
8346
10037
  printError('Failed to unzip file. Make sure "unzip" is installed.');
8347
10038
  process.exit(1);
8348
10039
  }
8349
- const files = readdirSync3(tmpDir);
10040
+ const files = readdirSync4(tmpDir);
8350
10041
  const convFile = files.find((f) => f === "conversations.json") ? join13(tmpDir, "conversations.json") : null;
8351
10042
  if (!convFile) {
8352
10043
  const { execSync: exec2 } = require("child_process");
@@ -9374,7 +11065,7 @@ function timestamp() {
9374
11065
  return `${d.getUTCFullYear()}${pad(d.getUTCMonth() + 1)}${pad(d.getUTCDate())}-${pad(d.getUTCHours())}${pad(d.getUTCMinutes())}${pad(d.getUTCSeconds())}`;
9375
11066
  }
9376
11067
  function defaultOutPath() {
9377
- const dir = (0, import_path7.join)((0, import_os2.homedir)(), ".clude", "snapshots");
11068
+ const dir = (0, import_path7.join)((0, import_os3.homedir)(), ".clude", "snapshots");
9378
11069
  if (!(0, import_fs8.existsSync)(dir)) (0, import_fs8.mkdirSync)(dir, { recursive: true });
9379
11070
  return (0, import_path7.join)(dir, `clude-${timestamp()}.tar.zst`);
9380
11071
  }
@@ -9454,12 +11145,12 @@ async function runSnapshot() {
9454
11145
  }
9455
11146
  console.log(outPath);
9456
11147
  }
9457
- var import_fs8, import_os2, import_path7;
11148
+ var import_fs8, import_os3, import_path7;
9458
11149
  var init_snapshot = __esm({
9459
11150
  "packages/brain/src/cli/snapshot.ts"() {
9460
11151
  "use strict";
9461
11152
  import_fs8 = require("fs");
9462
- import_os2 = require("os");
11153
+ import_os3 = require("os");
9463
11154
  import_path7 = require("path");
9464
11155
  init_src();
9465
11156
  }
@@ -9501,7 +11192,7 @@ function evaluateConfidence(memories, opts = {}) {
9501
11192
  };
9502
11193
  if (!sufficient) {
9503
11194
  result.hedgingInstruction = score < 0.15 ? HEDGING_INSTRUCTIONS.noEvidence : HEDGING_INSTRUCTIONS.weakEvidence;
9504
- log12.info({
11195
+ log19.info({
9505
11196
  score: score.toFixed(3),
9506
11197
  threshold,
9507
11198
  components: {
@@ -9518,7 +11209,7 @@ function evaluateConfidence(memories, opts = {}) {
9518
11209
  function filterLowConfidenceMemories(memories, minScore = 0.15) {
9519
11210
  const filtered = memories.filter((m) => m._score >= minScore);
9520
11211
  if (filtered.length < memories.length) {
9521
- log12.debug({
11212
+ log19.debug({
9522
11213
  before: memories.length,
9523
11214
  after: filtered.length,
9524
11215
  minScore
@@ -9526,12 +11217,12 @@ function filterLowConfidenceMemories(memories, minScore = 0.15) {
9526
11217
  }
9527
11218
  return filtered;
9528
11219
  }
9529
- var log12, HEDGING_INSTRUCTIONS;
11220
+ var log19, HEDGING_INSTRUCTIONS;
9530
11221
  var init_confidence_gate = __esm({
9531
11222
  "packages/brain/src/experimental/confidence-gate.ts"() {
9532
11223
  "use strict";
9533
11224
  init_logger();
9534
- log12 = createChildLogger("exp-confidence");
11225
+ log19 = createChildLogger("exp-confidence");
9535
11226
  HEDGING_INSTRUCTIONS = {
9536
11227
  noEvidence: `IMPORTANT: Your memory retrieval returned no strong matches for this query.
9537
11228
  You MUST NOT fabricate or guess information. Instead:
@@ -9549,6 +11240,87 @@ may not directly answer the query. When responding:
9549
11240
  }
9550
11241
  });
9551
11242
 
11243
+ // packages/brain/package.json
11244
+ var require_package = __commonJS({
11245
+ "packages/brain/package.json"(exports2, module2) {
11246
+ module2.exports = {
11247
+ name: "@clude/brain",
11248
+ version: "3.3.1",
11249
+ private: true,
11250
+ description: "Clude brain \u2014 memory, dreams, agents, personality, SDK",
11251
+ main: "dist/index.js",
11252
+ types: "dist/index.d.ts",
11253
+ exports: {
11254
+ ".": {
11255
+ types: "./dist/index.d.ts",
11256
+ default: "./dist/index.js"
11257
+ },
11258
+ "./*": {
11259
+ types: "./dist/*.d.ts",
11260
+ default: "./dist/*.js"
11261
+ },
11262
+ "./memory": {
11263
+ types: "./dist/memory/index.d.ts",
11264
+ default: "./dist/memory/index.js"
11265
+ },
11266
+ "./agents": {
11267
+ types: "./dist/agents/index.d.ts",
11268
+ default: "./dist/agents/index.js"
11269
+ },
11270
+ "./features/compound": {
11271
+ types: "./dist/features/compound/index.d.ts",
11272
+ default: "./dist/features/compound/index.js"
11273
+ },
11274
+ "./sdk": {
11275
+ types: "./dist/sdk/index.d.ts",
11276
+ default: "./dist/sdk/index.js"
11277
+ }
11278
+ },
11279
+ scripts: {
11280
+ build: "tsc -p tsconfig.build.json",
11281
+ typecheck: "tsc --noEmit",
11282
+ test: "vitest run",
11283
+ "test:watch": "vitest"
11284
+ },
11285
+ dependencies: {
11286
+ "@ai-sdk/anthropic": "^3.0.64",
11287
+ "@ai-sdk/google": "^3.0.55",
11288
+ "@ai-sdk/openai": "^3.0.49",
11289
+ "@ai-sdk/xai": "^3.0.75",
11290
+ "@anthropic-ai/sdk": "^0.39.0",
11291
+ "@clude/memorypack": "workspace:*",
11292
+ "@clude/shared": "workspace:*",
11293
+ "@clude/tokenization": "workspace:*",
11294
+ "@huggingface/transformers": "^4.1.0",
11295
+ "@modelcontextprotocol/sdk": "^1.26.0",
11296
+ "@openrouter/ai-sdk-provider": "^2.3.3",
11297
+ "@privy-io/node": "^0.12.0",
11298
+ "@solana/web3.js": "^1.98.4",
11299
+ "@supabase/supabase-js": "^2.95.3",
11300
+ ai: "^6.0.142",
11301
+ "better-sqlite3": "^12.9.0",
11302
+ bs58: "^6.0.0",
11303
+ express: "^4.21.0",
11304
+ jose: "^6.1.3",
11305
+ "node-cron": "^3.0.3",
11306
+ "sqlite-vec": "^0.1.9",
11307
+ "twitter-api-v2": "^1.29.0",
11308
+ unpdf: "^1.4.0",
11309
+ "vercel-minimax-ai-provider": "^0.0.2",
11310
+ zod: "^4.3.6"
11311
+ },
11312
+ devDependencies: {
11313
+ "@types/better-sqlite3": "^7.6.13",
11314
+ "@types/express": "^4.17.25",
11315
+ "@types/node": "^22.19.11",
11316
+ "@types/node-cron": "^3.0.11",
11317
+ typescript: "^5.6.0",
11318
+ vitest: "^4.1.0"
11319
+ }
11320
+ };
11321
+ }
11322
+ });
11323
+
9552
11324
  // packages/brain/src/memory/clinamen.ts
9553
11325
  var clinamen_exports = {};
9554
11326
  __export(clinamen_exports, {
@@ -9559,7 +11331,7 @@ async function findClinamen(opts) {
9559
11331
  const limit = opts.limit || DEFAULT_LIMIT;
9560
11332
  const minImportance = opts.minImportance ?? MIN_IMPORTANCE;
9561
11333
  const maxRelevance = opts.maxRelevance ?? MAX_RELEVANCE_SIM;
9562
- log13.debug({ context: opts.context.slice(0, 60), limit }, "Searching for clinamen");
11334
+ log20.debug({ context: opts.context.slice(0, 60), limit }, "Searching for clinamen");
9563
11335
  let contextEmbedding = null;
9564
11336
  if (isEmbeddingEnabled()) {
9565
11337
  const cached = getCachedEmbedding(opts.context);
@@ -9571,7 +11343,7 @@ async function findClinamen(opts) {
9571
11343
  }
9572
11344
  }
9573
11345
  if (!contextEmbedding) {
9574
- log13.debug("No embedding available \u2014 falling back to importance-only clinamen");
11346
+ log20.debug("No embedding available \u2014 falling back to importance-only clinamen");
9575
11347
  return importanceOnlyClinamen(db, limit, minImportance, opts.memoryTypes);
9576
11348
  }
9577
11349
  const cutoff = new Date(Date.now() - MIN_AGE_HOURS * 60 * 60 * 1e3).toISOString();
@@ -9582,7 +11354,7 @@ async function findClinamen(opts) {
9582
11354
  }
9583
11355
  const { data: candidates, error } = await query;
9584
11356
  if (error || !candidates || candidates.length === 0) {
9585
- log13.debug({ error: error?.message }, "No clinamen candidates found");
11357
+ log20.debug({ error: error?.message }, "No clinamen candidates found");
9586
11358
  return [];
9587
11359
  }
9588
11360
  const scored = [];
@@ -9608,7 +11380,7 @@ async function findClinamen(opts) {
9608
11380
  }
9609
11381
  scored.sort((a, b) => b._divergence - a._divergence);
9610
11382
  const results = scored.slice(0, limit);
9611
- log13.info({
11383
+ log20.info({
9612
11384
  candidates: candidates.length,
9613
11385
  afterFilter: scored.length,
9614
11386
  returned: results.length,
@@ -9634,7 +11406,7 @@ async function importanceOnlyClinamen(db, limit, minImportance, memoryTypes) {
9634
11406
  _relevanceSim: 0
9635
11407
  }));
9636
11408
  }
9637
- var log13, MIN_IMPORTANCE, MAX_RELEVANCE_SIM, MIN_AGE_HOURS, CANDIDATE_POOL_SIZE, DEFAULT_LIMIT;
11409
+ var log20, MIN_IMPORTANCE, MAX_RELEVANCE_SIM, MIN_AGE_HOURS, CANDIDATE_POOL_SIZE, DEFAULT_LIMIT;
9638
11410
  var init_clinamen = __esm({
9639
11411
  "packages/brain/src/memory/clinamen.ts"() {
9640
11412
  "use strict";
@@ -9642,7 +11414,7 @@ var init_clinamen = __esm({
9642
11414
  init_memory();
9643
11415
  init_embeddings();
9644
11416
  init_logger();
9645
- log13 = createChildLogger("clinamen");
11417
+ log20 = createChildLogger("clinamen");
9646
11418
  MIN_IMPORTANCE = 0.6;
9647
11419
  MAX_RELEVANCE_SIM = 0.35;
9648
11420
  MIN_AGE_HOURS = 24;
@@ -9708,11 +11480,6 @@ function loadSelfHosted() {
9708
11480
  _deleteMemory = memory.deleteMemory;
9709
11481
  _updateMemory = memory.updateMemory;
9710
11482
  _listMemories = memory.listMemories;
9711
- try {
9712
- const skillExtraction = require("@clude/shared/core/skill-extraction");
9713
- _extractSkill = skillExtraction.extractSkill;
9714
- } catch {
9715
- }
9716
11483
  try {
9717
11484
  const confidenceGate = (init_confidence_gate(), __toCommonJS(confidence_gate_exports));
9718
11485
  _evaluateConfidence = confidenceGate.evaluateConfidence;
@@ -9764,7 +11531,7 @@ async function main() {
9764
11531
  await server.connect(transport);
9765
11532
  console.error(`[clude-mcp] Server started on stdio`);
9766
11533
  }
9767
- var import_mcp, import_stdio, import_zod, CORTEX_API_KEY, CORTEX_HOST_URL, isLocalMode, isHostedMode, SUPABASE_URL, isSqliteMode, FETCH_TIMEOUT_MS, _sqliteStore, _recallMemories, _storeMemory, _getMemoryStats, _evaluateConfidence, _deleteMemory, _updateMemory, _listMemories, _extractSkill, server, MEMORY_TYPES;
11534
+ var import_mcp, import_stdio, import_zod, CORTEX_API_KEY, CORTEX_HOST_URL, isLocalMode, isHostedMode, SUPABASE_URL, isSqliteMode, FETCH_TIMEOUT_MS, _sqliteStore, _recallMemories, _storeMemory, _getMemoryStats, _evaluateConfidence, _deleteMemory, _updateMemory, _listMemories, server, MEMORY_TYPES;
9768
11535
  var init_server = __esm({
9769
11536
  "packages/brain/src/mcp/server.ts"() {
9770
11537
  "use strict";
@@ -9781,7 +11548,9 @@ var init_server = __esm({
9781
11548
  _sqliteStore = null;
9782
11549
  server = new import_mcp.McpServer({
9783
11550
  name: "clude-memory",
9784
- version: "2.7.0"
11551
+ // Single-sourced from the package manifest (inlined at bundle time) so the
11552
+ // MCP handshake, `clude --version`, and npm always agree.
11553
+ version: require_package().version
9785
11554
  });
9786
11555
  MEMORY_TYPES = ["episodic", "semantic", "procedural", "self_model", "introspective"];
9787
11556
  server.tool(
@@ -9910,7 +11679,7 @@ var init_server = __esm({
9910
11679
  "Store a new memory. Memories persist across conversations and decay over time if not accessed.",
9911
11680
  {
9912
11681
  type: import_zod.z.enum(MEMORY_TYPES).describe("Memory type: episodic (events), semantic (knowledge), procedural (behaviors), self_model (self-awareness), introspective (journal entries)"),
9913
- content: import_zod.z.string().max(5e3, "Content cannot exceed 5000 characters").describe("Full memory content"),
11682
+ content: import_zod.z.string().trim().min(1, "content cannot be empty").max(5e3, "Content cannot exceed 5000 characters").describe("Full memory content"),
9914
11683
  summary: import_zod.z.string().max(500, "Summary cannot exceed 500 characters").describe("Short summary for recall matching"),
9915
11684
  tags: import_zod.z.array(import_zod.z.string()).optional().describe("Tags for filtering"),
9916
11685
  concepts: import_zod.z.array(import_zod.z.string()).optional().describe("Structured concept labels (auto-inferred if omitted)"),
@@ -10306,7 +12075,7 @@ var init_server = __esm({
10306
12075
  {
10307
12076
  memories: import_zod.z.array(import_zod.z.object({
10308
12077
  type: import_zod.z.enum(MEMORY_TYPES).describe("Memory type"),
10309
- content: import_zod.z.string().max(5e3).describe("Full memory content"),
12078
+ content: import_zod.z.string().trim().min(1, "content cannot be empty").max(5e3).describe("Full memory content"),
10310
12079
  summary: import_zod.z.string().max(500).describe("Short summary"),
10311
12080
  tags: import_zod.z.array(import_zod.z.string()).optional(),
10312
12081
  concepts: import_zod.z.array(import_zod.z.string()).optional(),
@@ -10430,73 +12199,6 @@ var init_server = __esm({
10430
12199
  }]
10431
12200
  })
10432
12201
  );
10433
- server.tool(
10434
- "extract_skill",
10435
- "Extract domain-specific knowledge from memory into a shareable skills document. Performs multi-pass extraction across the memory bank and entity graph, then synthesizes results into a structured markdown document.",
10436
- {
10437
- domain: import_zod.z.string().describe('Domain or topic to extract (e.g., "DeFi", "React", "Solana development")'),
10438
- depth: import_zod.z.enum(["shallow", "deep"]).optional().describe("shallow = seed memories only, deep = graph expansion via entity relations (default: deep)"),
10439
- include_provenance: import_zod.z.boolean().optional().describe("Include source memory IDs for traceability (default: false)"),
10440
- max_memories: import_zod.z.number().min(10).max(500).optional().describe("Max memories to include in extraction (default: 200)")
10441
- },
10442
- async (args) => {
10443
- try {
10444
- if (isSqliteMode || isLocalMode) {
10445
- return {
10446
- content: [{ type: "text", text: JSON.stringify({ error: "extract_skill is not available in local/SQLite mode \u2014 requires Supabase for entity graph and embeddings." }) }],
10447
- isError: true
10448
- };
10449
- }
10450
- if (isHostedMode) {
10451
- const result2 = await cortexFetch("POST", "/api/cortex/extract-skill", {
10452
- domain: args.domain,
10453
- depth: args.depth,
10454
- include_provenance: args.include_provenance,
10455
- max_memories: args.max_memories
10456
- });
10457
- return {
10458
- content: [{
10459
- type: "text",
10460
- text: JSON.stringify({
10461
- markdown: result2.markdown,
10462
- stats: result2.stats,
10463
- warning: result2.warning
10464
- }, null, 2)
10465
- }]
10466
- };
10467
- }
10468
- loadSelfHosted();
10469
- if (!_extractSkill) {
10470
- return {
10471
- content: [{ type: "text", text: JSON.stringify({ error: "Skill extraction module not available. Check that src/core/skill-extraction.ts is built." }) }],
10472
- isError: true
10473
- };
10474
- }
10475
- const result = await _extractSkill({
10476
- domain: args.domain,
10477
- depth: args.depth || "deep",
10478
- includeProvenance: args.include_provenance || false,
10479
- maxMemories: args.max_memories || 200
10480
- });
10481
- return {
10482
- content: [{
10483
- type: "text",
10484
- text: JSON.stringify({
10485
- markdown: result.markdown,
10486
- stats: result.stats,
10487
- warning: result.warning
10488
- }, null, 2)
10489
- }]
10490
- };
10491
- } catch (err) {
10492
- console.error("[clude-mcp] extract_skill error:", err.message);
10493
- return {
10494
- content: [{ type: "text", text: JSON.stringify({ error: err.message }) }],
10495
- isError: true
10496
- };
10497
- }
10498
- }
10499
- );
10500
12202
  main().catch((err) => {
10501
12203
  console.error("[clude-mcp] Fatal error:", err);
10502
12204
  process.exit(1);
@@ -10519,87 +12221,6 @@ var init_index = __esm({
10519
12221
  }
10520
12222
  });
10521
12223
 
10522
- // packages/brain/package.json
10523
- var require_package = __commonJS({
10524
- "packages/brain/package.json"(exports2, module2) {
10525
- module2.exports = {
10526
- name: "@clude/brain",
10527
- version: "3.0.1",
10528
- private: true,
10529
- description: "Clude brain \u2014 memory, dreams, agents, personality, SDK",
10530
- main: "dist/index.js",
10531
- types: "dist/index.d.ts",
10532
- exports: {
10533
- ".": {
10534
- types: "./dist/index.d.ts",
10535
- default: "./dist/index.js"
10536
- },
10537
- "./*": {
10538
- types: "./dist/*.d.ts",
10539
- default: "./dist/*.js"
10540
- },
10541
- "./memory": {
10542
- types: "./dist/memory/index.d.ts",
10543
- default: "./dist/memory/index.js"
10544
- },
10545
- "./agents": {
10546
- types: "./dist/agents/index.d.ts",
10547
- default: "./dist/agents/index.js"
10548
- },
10549
- "./features/compound": {
10550
- types: "./dist/features/compound/index.d.ts",
10551
- default: "./dist/features/compound/index.js"
10552
- },
10553
- "./sdk": {
10554
- types: "./dist/sdk/index.d.ts",
10555
- default: "./dist/sdk/index.js"
10556
- }
10557
- },
10558
- scripts: {
10559
- build: "tsc -p tsconfig.build.json",
10560
- typecheck: "tsc --noEmit",
10561
- test: "vitest run",
10562
- "test:watch": "vitest"
10563
- },
10564
- dependencies: {
10565
- "@ai-sdk/anthropic": "^3.0.64",
10566
- "@ai-sdk/google": "^3.0.55",
10567
- "@ai-sdk/openai": "^3.0.49",
10568
- "@ai-sdk/xai": "^3.0.75",
10569
- "@anthropic-ai/sdk": "^0.39.0",
10570
- "@clude/memorypack": "workspace:*",
10571
- "@clude/shared": "workspace:*",
10572
- "@clude/tokenization": "workspace:*",
10573
- "@huggingface/transformers": "^4.1.0",
10574
- "@modelcontextprotocol/sdk": "^1.26.0",
10575
- "@openrouter/ai-sdk-provider": "^2.3.3",
10576
- "@privy-io/node": "^0.12.0",
10577
- "@solana/web3.js": "^1.98.4",
10578
- "@supabase/supabase-js": "^2.95.3",
10579
- ai: "^6.0.142",
10580
- "better-sqlite3": "^12.9.0",
10581
- bs58: "^6.0.0",
10582
- express: "^4.21.0",
10583
- jose: "^6.1.3",
10584
- "node-cron": "^3.0.3",
10585
- "sqlite-vec": "^0.1.9",
10586
- "twitter-api-v2": "^1.29.0",
10587
- unpdf: "^1.4.0",
10588
- "vercel-minimax-ai-provider": "^0.0.2",
10589
- zod: "^4.3.6"
10590
- },
10591
- devDependencies: {
10592
- "@types/better-sqlite3": "^7.6.13",
10593
- "@types/express": "^4.17.25",
10594
- "@types/node": "^22.19.11",
10595
- "@types/node-cron": "^3.0.11",
10596
- typescript: "^5.6.0",
10597
- vitest: "^4.1.0"
10598
- }
10599
- };
10600
- }
10601
- });
10602
-
10603
12224
  // packages/brain/src/cli/index.ts
10604
12225
  var command = process.argv[2];
10605
12226
  if (command === "setup") {