@clude/sdk 3.2.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +2107 -414
- package/dist/mcp/local-store.d.ts +74 -0
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +1941 -253
- package/dist/sdk/cortex-v2.d.ts +120 -0
- package/dist/sdk/cortex.d.ts +63 -0
- package/dist/sdk/http-transport.d.ts +14 -0
- package/dist/sdk/index.d.ts +4 -0
- package/dist/sdk/index.js +2005 -378
- package/dist/sdk/memory-types.d.ts +321 -0
- package/dist/sdk/sdk-mode.d.ts +1 -0
- package/dist/sdk/shared-constants.d.ts +53 -0
- package/dist/sdk/types.d.ts +47 -0
- package/package.json +16 -23
- package/supabase-schema.sql +169 -12
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 (
|
|
940
|
-
console.
|
|
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}
|
|
950
|
-
console.log(`
|
|
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
|
-
|
|
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
|
|
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 = {
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
2856
|
-
|
|
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)(
|
|
2977
|
-
{ name: "Cursor", path: (0, import_path2.join)(
|
|
2978
|
-
{ name: "Claude Code", path: (0, import_path2.join)(
|
|
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
|
|
2985
|
-
|
|
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:
|
|
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
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
4114
|
-
|
|
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
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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
|
-
--
|
|
4568
|
-
--
|
|
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,
|
|
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 (
|
|
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
|
-
|
|
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/
|
|
4730
|
-
function
|
|
4731
|
-
const
|
|
4732
|
-
const
|
|
4733
|
-
if (
|
|
4734
|
-
if (
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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/
|
|
5961
|
-
function
|
|
5962
|
-
|
|
5963
|
-
}
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
}
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
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
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
|
|
6004
|
-
|
|
6005
|
-
|
|
6006
|
-
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
|
|
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
|
-
|
|
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
|
|
6017
|
-
var
|
|
6018
|
-
"packages/
|
|
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
|
-
|
|
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
|
-
|
|
7484
|
+
log16.error({ error: error.message, name }, "Failed to create entity");
|
|
6081
7485
|
return null;
|
|
6082
7486
|
}
|
|
6083
|
-
|
|
7487
|
+
log16.debug({ id: newEntity.id, name, type: entityType }, "Entity created");
|
|
6084
7488
|
embedEntity(newEntity.id, name, opts?.description).catch(
|
|
6085
|
-
(err) =>
|
|
7489
|
+
(err) => log16.debug({ err }, "Entity embedding failed")
|
|
6086
7490
|
);
|
|
6087
7491
|
return newEntity;
|
|
6088
7492
|
} catch (err) {
|
|
6089
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
6153
|
-
|
|
6154
|
-
|
|
6155
|
-
|
|
6156
|
-
|
|
6157
|
-
|
|
6158
|
-
|
|
6159
|
-
|
|
6160
|
-
|
|
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
|
-
|
|
7644
|
+
log16.debug({ err, source: entityIds[i].name, target: entityIds[j].name }, "Failed to create entity relation");
|
|
6247
7645
|
}
|
|
6248
7646
|
}
|
|
6249
7647
|
}
|
|
6250
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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: () =>
|
|
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
|
|
7765
|
+
function isOwnerScopeFailClosed() {
|
|
7766
|
+
return process.env.OWNER_SCOPE_FAILCLOSED === "true";
|
|
7767
|
+
}
|
|
7768
|
+
function getOwnerScope() {
|
|
6358
7769
|
const wallet = getOwnerWallet();
|
|
6359
|
-
if (wallet
|
|
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 (
|
|
6363
|
-
return query.eq("owner_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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
6499
|
-
const
|
|
6500
|
-
const
|
|
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
|
|
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:
|
|
6518
|
-
encryption_pubkey:
|
|
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
|
-
|
|
7989
|
+
log17.error({ error: error.message }, "Failed to store memory");
|
|
6523
7990
|
return null;
|
|
6524
7991
|
}
|
|
6525
|
-
|
|
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
|
|
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
|
|
8016
|
+
importance,
|
|
6535
8017
|
memoryType: opts.type,
|
|
6536
8018
|
source: opts.source
|
|
6537
8019
|
});
|
|
6538
|
-
commitMemoryToChain(data.id, opts, data).catch(
|
|
6539
|
-
(err) =>
|
|
8020
|
+
commitMemoryToChain(data.id, opts, data, plaintextContent, envelope !== null || legacyEncrypt).catch(
|
|
8021
|
+
(err) => log17.warn({ err }, "On-chain memory commit failed")
|
|
6540
8022
|
);
|
|
6541
|
-
|
|
6542
|
-
|
|
6543
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
8157
|
+
log17.debug({ memoryId }, "No embedding generated; skipping semantic tagging");
|
|
6603
8158
|
return;
|
|
6604
8159
|
}
|
|
6605
|
-
|
|
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
|
-
|
|
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
|
-
|
|
8205
|
+
log17.debug({ err, memoryId }, "Semantic tagging skipped");
|
|
6630
8206
|
}
|
|
6631
|
-
|
|
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
|
-
|
|
8225
|
+
log17.debug({ original: query, expansions: expansions.length }, "Query expanded");
|
|
6650
8226
|
return [query, ...expansions];
|
|
6651
8227
|
} catch (err) {
|
|
6652
|
-
|
|
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
|
|
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
|
-
|
|
8254
|
+
log17.debug("All query embeddings returned null, using keyword-only retrieval");
|
|
6678
8255
|
return;
|
|
6679
8256
|
}
|
|
6680
8257
|
try {
|
|
6681
|
-
const allSearches = validEmbeddings.
|
|
6682
|
-
|
|
6683
|
-
|
|
6684
|
-
|
|
6685
|
-
|
|
6686
|
-
|
|
6687
|
-
|
|
6688
|
-
|
|
6689
|
-
|
|
6690
|
-
|
|
6691
|
-
|
|
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
|
-
|
|
6695
|
-
|
|
6696
|
-
|
|
6697
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
8419
|
+
log17.debug({ bm25Hits: bm25Results.length, bm25New: bm25MissingIds.length }, "BM25 search added candidates");
|
|
6808
8420
|
}
|
|
6809
8421
|
if (error) {
|
|
6810
|
-
|
|
8422
|
+
log17.error({ error: error.message }, "Memory recall query failed");
|
|
6811
8423
|
return [];
|
|
6812
8424
|
}
|
|
6813
|
-
let candidates =
|
|
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, ...
|
|
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
|
-
|
|
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
|
-
|
|
8508
|
+
log17.debug({ cooccurrenceAdded, cooccurrenceEntities: cooccurrenceNames.length }, "Entity co-occurrence recall applied");
|
|
6897
8509
|
}
|
|
6898
8510
|
}
|
|
6899
8511
|
} catch (err) {
|
|
6900
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
8571
|
+
log17.debug({ injectedTypes: diverseCandidates.map((m) => m.memory_type) }, "Type diversity applied");
|
|
6960
8572
|
}
|
|
6961
8573
|
}
|
|
6962
8574
|
}
|
|
6963
|
-
const
|
|
6964
|
-
|
|
6965
|
-
const
|
|
6966
|
-
|
|
6967
|
-
|
|
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
|
|
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) =>
|
|
6975
|
-
reinforceCoRetrievedLinks(ids).catch((err) =>
|
|
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
|
-
|
|
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
|
-
|
|
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 +=
|
|
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
|
-
|
|
8695
|
+
log17.error({ error: error.message }, "Memory summary recall failed");
|
|
7084
8696
|
return [];
|
|
7085
8697
|
}
|
|
7086
8698
|
return data || [];
|
|
7087
8699
|
} catch (err) {
|
|
7088
|
-
|
|
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
|
-
|
|
8712
|
+
log17.error({ error: error.message }, "Memory hydration failed");
|
|
7101
8713
|
return [];
|
|
7102
8714
|
}
|
|
7103
|
-
return
|
|
8715
|
+
return await decryptMemories(data || []);
|
|
7104
8716
|
} catch (err) {
|
|
7105
|
-
|
|
8717
|
+
log17.error({ err }, "Memory hydration failed");
|
|
7106
8718
|
return [];
|
|
7107
8719
|
}
|
|
7108
8720
|
}
|
|
7109
|
-
async function updateMemoryAccess(ids,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
8853
|
+
log17.debug({ error: error.message }, "Link reinforcement RPC failed");
|
|
7247
8854
|
} else if (data && data > 0) {
|
|
7248
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
8884
|
+
log17.info({ decayed: totalDecayed }, "Type-specific memory decay applied");
|
|
7278
8885
|
}
|
|
7279
8886
|
return totalDecayed;
|
|
7280
8887
|
} catch (err) {
|
|
7281
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
8937
|
+
log17.error({ error: error.message }, "Failed to list memories");
|
|
7331
8938
|
return { memories: [], total: 0 };
|
|
7332
8939
|
}
|
|
7333
|
-
return { memories:
|
|
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
|
-
|
|
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
|
-
|
|
9019
|
+
log17.warn({ error: dreamError.message }, "Failed to count dream logs");
|
|
7410
9020
|
}
|
|
7411
9021
|
stats.totalDreamSessions = count || 0;
|
|
7412
9022
|
} catch (err) {
|
|
7413
|
-
|
|
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
|
-
|
|
9037
|
+
log17.error({ error: error.message }, "Failed to get recent memories");
|
|
7428
9038
|
return [];
|
|
7429
9039
|
}
|
|
7430
|
-
return
|
|
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
|
-
|
|
9048
|
+
log17.error({ error: error.message }, "Failed to get self model");
|
|
7439
9049
|
return [];
|
|
7440
9050
|
}
|
|
7441
|
-
return
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
9125
|
+
log17.warn({ response }, "LLM importance score unparseable, using fallback");
|
|
7522
9126
|
return calculateImportance(fallbackOpts || {});
|
|
7523
9127
|
} catch (err) {
|
|
7524
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
9206
|
+
SCOPE_BOT_OWN2 = "__BOT_OWN__";
|
|
7595
9207
|
HASH_ID_PREFIX = "clude";
|
|
7596
|
-
|
|
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: () =>
|
|
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:
|
|
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 =
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.0",
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
11417
|
+
log20 = createChildLogger("clinamen");
|
|
9646
11418
|
MIN_IMPORTANCE = 0.6;
|
|
9647
11419
|
MAX_RELEVANCE_SIM = 0.35;
|
|
9648
11420
|
MIN_AGE_HOURS = 24;
|
|
@@ -9781,7 +11553,9 @@ var init_server = __esm({
|
|
|
9781
11553
|
_sqliteStore = null;
|
|
9782
11554
|
server = new import_mcp.McpServer({
|
|
9783
11555
|
name: "clude-memory",
|
|
9784
|
-
|
|
11556
|
+
// Single-sourced from the package manifest (inlined at bundle time) so the
|
|
11557
|
+
// MCP handshake, `clude --version`, and npm always agree.
|
|
11558
|
+
version: require_package().version
|
|
9785
11559
|
});
|
|
9786
11560
|
MEMORY_TYPES = ["episodic", "semantic", "procedural", "self_model", "introspective"];
|
|
9787
11561
|
server.tool(
|
|
@@ -9910,7 +11684,7 @@ var init_server = __esm({
|
|
|
9910
11684
|
"Store a new memory. Memories persist across conversations and decay over time if not accessed.",
|
|
9911
11685
|
{
|
|
9912
11686
|
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"),
|
|
11687
|
+
content: import_zod.z.string().trim().min(1, "content cannot be empty").max(5e3, "Content cannot exceed 5000 characters").describe("Full memory content"),
|
|
9914
11688
|
summary: import_zod.z.string().max(500, "Summary cannot exceed 500 characters").describe("Short summary for recall matching"),
|
|
9915
11689
|
tags: import_zod.z.array(import_zod.z.string()).optional().describe("Tags for filtering"),
|
|
9916
11690
|
concepts: import_zod.z.array(import_zod.z.string()).optional().describe("Structured concept labels (auto-inferred if omitted)"),
|
|
@@ -10306,7 +12080,7 @@ var init_server = __esm({
|
|
|
10306
12080
|
{
|
|
10307
12081
|
memories: import_zod.z.array(import_zod.z.object({
|
|
10308
12082
|
type: import_zod.z.enum(MEMORY_TYPES).describe("Memory type"),
|
|
10309
|
-
content: import_zod.z.string().max(5e3).describe("Full memory content"),
|
|
12083
|
+
content: import_zod.z.string().trim().min(1, "content cannot be empty").max(5e3).describe("Full memory content"),
|
|
10310
12084
|
summary: import_zod.z.string().max(500).describe("Short summary"),
|
|
10311
12085
|
tags: import_zod.z.array(import_zod.z.string()).optional(),
|
|
10312
12086
|
concepts: import_zod.z.array(import_zod.z.string()).optional(),
|
|
@@ -10519,87 +12293,6 @@ var init_index = __esm({
|
|
|
10519
12293
|
}
|
|
10520
12294
|
});
|
|
10521
12295
|
|
|
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
12296
|
// packages/brain/src/cli/index.ts
|
|
10604
12297
|
var command = process.argv[2];
|
|
10605
12298
|
if (command === "setup") {
|