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