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