@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/sdk/index.js
CHANGED
|
@@ -67,7 +67,20 @@ var init_config = __esm({
|
|
|
67
67
|
accessToken: requiredUnlessSiteOnly("X_ACCESS_TOKEN"),
|
|
68
68
|
accessSecret: requiredUnlessSiteOnly("X_ACCESS_SECRET"),
|
|
69
69
|
botUserId: requiredUnlessSiteOnly("X_BOT_USER_ID"),
|
|
70
|
-
creatorUserId: optional("CREATOR_USER_ID", "")
|
|
70
|
+
creatorUserId: optional("CREATOR_USER_ID", ""),
|
|
71
|
+
/**
|
|
72
|
+
* Bearer token for the public "$ANSEM LIVE" feed (GET /api/ansem/feed).
|
|
73
|
+
* Reads-only X app bearer — used server-side for GET /2/tweets/search/recent.
|
|
74
|
+
* Empty → the feed endpoint returns { enabled:false } and the frontend panel hides.
|
|
75
|
+
* NEVER sent to the browser.
|
|
76
|
+
*/
|
|
77
|
+
searchBearer: optional("X_SEARCH_BEARER", ""),
|
|
78
|
+
/** Min poll gap for the on-demand $ANSEM feed (ms). Idle → no polling → $0. */
|
|
79
|
+
ansemFeedIntervalMs: parseInt(optional("ANSEM_FEED_INTERVAL_MS", "90000"), 10),
|
|
80
|
+
/** Posts older than ~15min must clear this like floor (fresh posts are exempt). */
|
|
81
|
+
ansemFeedMinLikes: parseInt(optional("ANSEM_FEED_MIN_LIKES", "3"), 10),
|
|
82
|
+
/** Hard daily read-cap: once exceeded, serve the stale buffer (cost stop-loss). */
|
|
83
|
+
ansemFeedDailyReadCap: parseInt(optional("ANSEM_FEED_DAILY_READ_CAP", "40000"), 10)
|
|
71
84
|
},
|
|
72
85
|
supabase: {
|
|
73
86
|
url: requiredUnlessSiteOnly("SUPABASE_URL"),
|
|
@@ -85,7 +98,9 @@ var init_config = __esm({
|
|
|
85
98
|
optional("SOLANA_NETWORK", "mainnet-beta") === "devnet" ? "https://api.devnet.solana.com" : "https://api.mainnet-beta.solana.com"
|
|
86
99
|
),
|
|
87
100
|
botWalletPrivateKey: optional("BOT_WALLET_PRIVATE_KEY", ""),
|
|
88
|
-
cludeTokenMint: optional("CLUUDE_TOKEN_MINT", "")
|
|
101
|
+
cludeTokenMint: optional("CLUUDE_TOKEN_MINT", ""),
|
|
102
|
+
/** Deployed memory-registry Anchor program ID. Empty → on-chain registration disabled. */
|
|
103
|
+
memoryRegistryProgramId: optional("CLUDE_PROGRAM_ID", "")
|
|
89
104
|
},
|
|
90
105
|
server: {
|
|
91
106
|
port: parseInt(optional("PORT", "3000"), 10),
|
|
@@ -123,6 +138,69 @@ var init_config = __esm({
|
|
|
123
138
|
freePromoCreditUsdc: parseFloat(optional("FREE_PROMO_CREDIT_USDC", "1")),
|
|
124
139
|
freePromoExpiry: optional("FREE_PROMO_EXPIRY", "")
|
|
125
140
|
},
|
|
141
|
+
memory: {
|
|
142
|
+
/**
|
|
143
|
+
* Memory 3.0 C2: route storeMemory enrichment (embed/link/extract) through the durable
|
|
144
|
+
* memory_write_jobs outbox instead of fire-and-forget. Default OFF — fire-and-forget stays
|
|
145
|
+
* the default until the worker is proven. Requires migrations 044 + 045; degrades gracefully
|
|
146
|
+
* (falls back to fire-and-forget) if unapplied.
|
|
147
|
+
*/
|
|
148
|
+
outboxEnabled: optional("MEMORY_OUTBOX", "false") === "true",
|
|
149
|
+
/**
|
|
150
|
+
* Memory 3.0 C1: write-time reconciliation, SHADOW slice (LLM-free). When on, each write records
|
|
151
|
+
* a PROPOSED reconcile op (add / needs_router / skip) into memory_reconciliation_log WITHOUT
|
|
152
|
+
* applying it — the labeled sample the enforce path must earn its turn-on from. Default OFF;
|
|
153
|
+
* runs fully detached after embedMemory (zero write latency); degrades gracefully if migration
|
|
154
|
+
* 046 is unapplied. Disabled under BENCH_MODE. The router is a later slice; only the cosine gate
|
|
155
|
+
* runs here.
|
|
156
|
+
*/
|
|
157
|
+
reconcileEnabled: optional("MEMORY_RECONCILE", "false") === "true",
|
|
158
|
+
/** Screen floor passed to match_memories_temporal — LOW so max_cosine is captured for below-LO
|
|
159
|
+
* writes (LO is tuned from the shadow data, not fixed up-front). */
|
|
160
|
+
reconcileFloor: Number(optional("MEMORY_RECONCILE_FLOOR", "0.5")),
|
|
161
|
+
/** "similar enough to reconcile" boundary → needs_router (vs add). A starting probe, not gospel. */
|
|
162
|
+
reconcileLo: Number(optional("MEMORY_RECONCILE_LO", "0.85")),
|
|
163
|
+
/** hi/mid band LABEL boundary for analysis (not a decision boundary). */
|
|
164
|
+
reconcileHi: Number(optional("MEMORY_RECONCILE_HI", "0.95")),
|
|
165
|
+
/**
|
|
166
|
+
* C1 slice 1.5: the LLM router. Its own sub-flag (default OFF, independent of reconcileEnabled)
|
|
167
|
+
* so gate-only instrumentation runs first and LO is calibrated from that data before any LLM
|
|
168
|
+
* spend. When on, a >= LO write is classified add/update/noop by reconcileModel (still SHADOW —
|
|
169
|
+
* the op is logged, never applied). Requires OpenRouter configured.
|
|
170
|
+
*/
|
|
171
|
+
reconcileRouter: optional("MEMORY_RECONCILE_ROUTER", "false") === "true",
|
|
172
|
+
/** Router model — an explicit id (NEVER a cognitiveFunction, which would silently override it
|
|
173
|
+
* with the fast llama slot). Haiku-class default: capable enough for dup-vs-update, cheap. */
|
|
174
|
+
reconcileModel: optional("MEMORY_RECONCILE_MODEL", "anthropic/claude-haiku-4.5"),
|
|
175
|
+
/** Per-owner soft daily cap on router calls (approximate, per-process) — bounds LLM spend. */
|
|
176
|
+
reconcileBudget: Number(optional("MEMORY_RECONCILE_BUDGET", "200"))
|
|
177
|
+
},
|
|
178
|
+
oauth: {
|
|
179
|
+
/** HMAC secret for signing OAuth access-token JWTs. Empty disables the OAuth AS — bearer API-key auth still works. */
|
|
180
|
+
signingSecret: optional("OAUTH_SIGNING_SECRET", ""),
|
|
181
|
+
/** Issuer/audience identifier baked into tokens. Falls back to the request origin when empty. */
|
|
182
|
+
issuer: optional("OAUTH_ISSUER", ""),
|
|
183
|
+
/** Access-token lifetime in seconds (default 1h). */
|
|
184
|
+
accessTtlSec: parseInt(optional("OAUTH_ACCESS_TTL_SEC", "3600"), 10),
|
|
185
|
+
/** Refresh-token lifetime in seconds (default 30d). */
|
|
186
|
+
refreshTtlSec: parseInt(optional("OAUTH_REFRESH_TTL_SEC", "2592000"), 10),
|
|
187
|
+
/** Authorization-code lifetime in seconds (default 60s). */
|
|
188
|
+
codeTtlSec: parseInt(optional("OAUTH_CODE_TTL_SEC", "60"), 10)
|
|
189
|
+
},
|
|
190
|
+
stripe: {
|
|
191
|
+
/**
|
|
192
|
+
* Stripe secret API key (sk_live_… / sk_test_…). Empty disables the
|
|
193
|
+
* marketplace Stripe rail — the orchestrator/webhook fail closed without it.
|
|
194
|
+
* SDK/MCP consumers never need this, so it stays optional() (not required()).
|
|
195
|
+
*/
|
|
196
|
+
secretKey: optional("STRIPE_SECRET_KEY", ""),
|
|
197
|
+
/**
|
|
198
|
+
* Stripe webhook signing secret (whsec_…) used by stripe.webhooks.constructEvent
|
|
199
|
+
* to verify the raw body BEFORE any DB write (Risk R6). Empty ⇒ every webhook is
|
|
200
|
+
* rejected, so a misconfigured deploy can never process an unverified event.
|
|
201
|
+
*/
|
|
202
|
+
webhookSecret: optional("STRIPE_WEBHOOK_SECRET", "")
|
|
203
|
+
},
|
|
126
204
|
campaign: {
|
|
127
205
|
startDate: optional("CAMPAIGN_START", "")
|
|
128
206
|
},
|
|
@@ -136,6 +214,51 @@ var init_config = __esm({
|
|
|
136
214
|
queryApiKey: optional("EMBEDDING_QUERY_API_KEY", ""),
|
|
137
215
|
queryModel: optional("EMBEDDING_QUERY_MODEL", "")
|
|
138
216
|
},
|
|
217
|
+
migration: {
|
|
218
|
+
/**
|
|
219
|
+
* Database backend that getDb() targets during the GCP parallel-run.
|
|
220
|
+
* 'supabase' (default, current live infra) | 'cloudsql' (PostgREST /
|
|
221
|
+
* self-hosted Supabase in front of Cloud SQL). Flip per-layer for the
|
|
222
|
+
* shadow-stack cutover; Supabase stays the source of truth until sunset.
|
|
223
|
+
*/
|
|
224
|
+
dbTarget: optional("DB_TARGET", "supabase"),
|
|
225
|
+
/** PostgREST endpoint for the Cloud SQL backend (used only when DB_TARGET=cloudsql). */
|
|
226
|
+
cloudsqlUrl: optional("CLOUDSQL_PGREST_URL", ""),
|
|
227
|
+
/** Service key for the Cloud SQL PostgREST backend (used only when DB_TARGET=cloudsql). */
|
|
228
|
+
cloudsqlServiceKey: optional("CLOUDSQL_SERVICE_KEY", ""),
|
|
229
|
+
/**
|
|
230
|
+
* Active embedding vector space for ingest + recall. 'voyage' (default,
|
|
231
|
+
* current corpus) | 'vertex' (shadow column, only after the LongMemEval gate
|
|
232
|
+
* passes). Separate from EMBEDDING_PROVIDER so the swap is A/B, not one-way.
|
|
233
|
+
*/
|
|
234
|
+
embeddingActive: optional("EMBEDDING_ACTIVE", "voyage"),
|
|
235
|
+
/**
|
|
236
|
+
* Whether THIS process runs the in-server singleton timers (recall canary,
|
|
237
|
+
* marketplace delivery poller, title-mint reconciliation). Default true =
|
|
238
|
+
* current Railway behavior. Set false on the Cloud Run server so exactly one
|
|
239
|
+
* owner (the worker) runs them and autoscaling never duplicates them.
|
|
240
|
+
*/
|
|
241
|
+
runInProcessTimers: optional("RUN_INPROCESS_TIMERS", "true") === "true"
|
|
242
|
+
},
|
|
243
|
+
vertex: {
|
|
244
|
+
/**
|
|
245
|
+
* Vertex AI embedding backend (the GCP replacement for Voyage), used only when
|
|
246
|
+
* ingest/backfill/recall target the 'vertex' space (EMBEDDING_ACTIVE=vertex or an
|
|
247
|
+
* explicit generateEmbeddingForSpace('vertex') call). Separate from config.embedding
|
|
248
|
+
* so the Vertex space can be backfilled + A/B-gated while Voyage stays live.
|
|
249
|
+
*/
|
|
250
|
+
project: optional("VERTEX_PROJECT", optional("GCP_PROJECT", "")),
|
|
251
|
+
location: optional("VERTEX_LOCATION", "us-central1"),
|
|
252
|
+
model: optional("VERTEX_EMBEDDING_MODEL", "gemini-embedding-001"),
|
|
253
|
+
/** Output dims — MRL-truncated to 1024 so vector(1024) columns + HNSW + match_* RPCs are unchanged. */
|
|
254
|
+
dimensions: parseInt(optional("VERTEX_EMBEDDING_DIMENSIONS", "1024"), 10),
|
|
255
|
+
/**
|
|
256
|
+
* Optional static OAuth token override (from `gcloud auth print-access-token`) for
|
|
257
|
+
* local dev / smoke tests. Empty in prod: Cloud Run mints a token from the attached
|
|
258
|
+
* service account via the metadata server (SDK-free), no static key stored.
|
|
259
|
+
*/
|
|
260
|
+
accessToken: optional("VERTEX_ACCESS_TOKEN", "")
|
|
261
|
+
},
|
|
139
262
|
openrouter: {
|
|
140
263
|
apiKey: optional("OPENROUTER_API_KEY", ""),
|
|
141
264
|
model: optional("OPENROUTER_MODEL", "meta-llama/llama-3.3-70b-instruct")
|
|
@@ -149,6 +272,35 @@ var init_config = __esm({
|
|
|
149
272
|
tavily: {
|
|
150
273
|
apiKey: optional("TAVILY_API_KEY", "")
|
|
151
274
|
},
|
|
275
|
+
higgsfield: {
|
|
276
|
+
/** Higgsfield API key ID — first half of the V2 `Authorization: Key <id>:<secret>` pair (server-only). Empty → /api/ansem/speak returns 501. */
|
|
277
|
+
apiKey: optional("HIGGSFIELD_API_KEY", ""),
|
|
278
|
+
/** Higgsfield API key SECRET — second half of the V2 `Key <id>:<secret>` pair. */
|
|
279
|
+
apiSecret: optional("HIGGSFIELD_API_SECRET", ""),
|
|
280
|
+
/** REST base URL (Higgsfield V2 API). */
|
|
281
|
+
apiBase: optional("HIGGSFIELD_API_BASE", "https://platform.higgsfield.ai"),
|
|
282
|
+
/**
|
|
283
|
+
* V2 model path for the seed_audio TTS model: create is POST {apiBase}/{ttsEndpoint}.
|
|
284
|
+
* Verified live against the Higgsfield V2 API — bytedance/seed-audio-1.0.
|
|
285
|
+
*/
|
|
286
|
+
ttsEndpoint: optional("HIGGSFIELD_TTS_ENDPOINT", "bytedance/seed-audio-1.0"),
|
|
287
|
+
/** Ansem voice — Higgsfield "Sterling" preset (founder's choice). */
|
|
288
|
+
voiceId: optional("ANSEM_VOICE_ID", "dc382508-c8bd-443c-8cb2-46e57b8d2e6f"),
|
|
289
|
+
voiceType: optional("ANSEM_VOICE_TYPE", "preset"),
|
|
290
|
+
/** Deep-voice tuning: seed_audio pitch_rate / speech_rate (integers, default 0). */
|
|
291
|
+
voicePitch: parseInt(optional("ANSEM_VOICE_PITCH", "-9"), 10),
|
|
292
|
+
voiceSpeechRate: parseInt(optional("ANSEM_VOICE_SPEECH_RATE", "-12"), 10),
|
|
293
|
+
/** Output audio format (seed_audio supports wav|mp3|pcm|ogg_opus). */
|
|
294
|
+
audioFormat: optional("ANSEM_VOICE_FORMAT", "mp3")
|
|
295
|
+
},
|
|
296
|
+
elevenlabs: {
|
|
297
|
+
/** ElevenLabs API key (server-only). Set → PRIMARY TTS (~1-3s, no lag); empty → falls back to Higgsfield. */
|
|
298
|
+
apiKey: optional("ELEVENLABS_API_KEY", ""),
|
|
299
|
+
/** Voice id — default "Brian" (deep, resonant, american). Swap via ELEVENLABS_VOICE_ID. */
|
|
300
|
+
voiceId: optional("ELEVENLABS_VOICE_ID", "nPczCjzI2devNBz1zQrb"),
|
|
301
|
+
/** Model — turbo for lowest latency. */
|
|
302
|
+
model: optional("ELEVENLABS_MODEL", "eleven_turbo_v2_5")
|
|
303
|
+
},
|
|
152
304
|
privy: {
|
|
153
305
|
appId: optional("PRIVY_APP_ID", ""),
|
|
154
306
|
appSecret: optional("PRIVY_APP_SECRET", ""),
|
|
@@ -230,28 +382,70 @@ var init_logger = __esm({
|
|
|
230
382
|
}
|
|
231
383
|
});
|
|
232
384
|
|
|
385
|
+
// packages/shared/src/core/migration-profile.ts
|
|
386
|
+
function defaultSupabaseConnection() {
|
|
387
|
+
return { url: config.supabase.url, serviceKey: config.supabase.serviceKey };
|
|
388
|
+
}
|
|
389
|
+
function resolveDbConnection(profile = config.migration, supabase2 = defaultSupabaseConnection()) {
|
|
390
|
+
if (!DB_TARGETS.includes(profile.dbTarget)) {
|
|
391
|
+
throw new Error(
|
|
392
|
+
`Invalid DB_TARGET '${profile.dbTarget}' (expected 'supabase' | 'cloudsql')`
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
if (profile.dbTarget === "cloudsql") {
|
|
396
|
+
if (!profile.cloudsqlUrl || !profile.cloudsqlServiceKey) {
|
|
397
|
+
throw new Error(
|
|
398
|
+
"DB_TARGET=cloudsql requires CLOUDSQL_PGREST_URL and CLOUDSQL_SERVICE_KEY"
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
return { url: profile.cloudsqlUrl, serviceKey: profile.cloudsqlServiceKey };
|
|
402
|
+
}
|
|
403
|
+
return { url: supabase2.url, serviceKey: supabase2.serviceKey };
|
|
404
|
+
}
|
|
405
|
+
function activeEmbeddingSpace(profile = config.migration) {
|
|
406
|
+
if (!EMBEDDING_SPACES.includes(profile.embeddingActive)) {
|
|
407
|
+
throw new Error(
|
|
408
|
+
`Invalid EMBEDDING_ACTIVE '${profile.embeddingActive}' (expected 'voyage' | 'vertex')`
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
return profile.embeddingActive;
|
|
412
|
+
}
|
|
413
|
+
function vectorRpcName(baseRpc, profile = config.migration) {
|
|
414
|
+
return activeEmbeddingSpace(profile) === "vertex" ? `${baseRpc}_vertex` : baseRpc;
|
|
415
|
+
}
|
|
416
|
+
var DB_TARGETS, EMBEDDING_SPACES;
|
|
417
|
+
var init_migration_profile = __esm({
|
|
418
|
+
"packages/shared/src/core/migration-profile.ts"() {
|
|
419
|
+
"use strict";
|
|
420
|
+
init_config();
|
|
421
|
+
DB_TARGETS = ["supabase", "cloudsql"];
|
|
422
|
+
EMBEDDING_SPACES = ["voyage", "vertex"];
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
|
|
233
426
|
// packages/shared/src/core/database.ts
|
|
234
427
|
var database_exports = {};
|
|
235
428
|
__export(database_exports, {
|
|
236
429
|
_setDb: () => _setDb,
|
|
237
430
|
claimForProcessing: () => claimForProcessing,
|
|
238
431
|
getDb: () => getDb,
|
|
432
|
+
getSchemaDriftReport: () => getSchemaDriftReport,
|
|
239
433
|
initDatabase: () => initDatabase,
|
|
240
434
|
isAlreadyProcessed: () => isAlreadyProcessed,
|
|
241
435
|
markProcessed: () => markProcessed
|
|
242
436
|
});
|
|
243
437
|
function getDb() {
|
|
244
438
|
if (!supabase) {
|
|
245
|
-
|
|
246
|
-
|
|
439
|
+
const conn = resolveDbConnection();
|
|
440
|
+
supabase = (0, import_supabase_js.createClient)(conn.url, conn.serviceKey);
|
|
441
|
+
log.info({ dbTarget: config.migration.dbTarget }, "Database client initialized");
|
|
247
442
|
}
|
|
248
443
|
return supabase;
|
|
249
444
|
}
|
|
250
445
|
function _setDb(client2) {
|
|
251
446
|
supabase = client2;
|
|
252
447
|
}
|
|
253
|
-
async function
|
|
254
|
-
const db = getDb();
|
|
448
|
+
async function runBootDdl(db) {
|
|
255
449
|
try {
|
|
256
450
|
const { error } = await db.rpc("exec_sql", {
|
|
257
451
|
query: `
|
|
@@ -323,6 +517,7 @@ async function initDatabase() {
|
|
|
323
517
|
input_memory_ids BIGINT[] DEFAULT '{}',
|
|
324
518
|
output TEXT NOT NULL,
|
|
325
519
|
new_memories_created BIGINT[] DEFAULT '{}',
|
|
520
|
+
owner_wallet TEXT,
|
|
326
521
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
327
522
|
);
|
|
328
523
|
|
|
@@ -348,13 +543,18 @@ async function initDatabase() {
|
|
|
348
543
|
is_active BOOLEAN DEFAULT TRUE,
|
|
349
544
|
metadata JSONB DEFAULT '{}',
|
|
350
545
|
owner_wallet TEXT,
|
|
351
|
-
privy_did TEXT
|
|
546
|
+
privy_did TEXT,
|
|
547
|
+
email TEXT
|
|
352
548
|
);
|
|
353
549
|
|
|
550
|
+
-- Backfill: older deployments may have agent_keys without the email column.
|
|
551
|
+
ALTER TABLE agent_keys ADD COLUMN IF NOT EXISTS email TEXT;
|
|
552
|
+
|
|
354
553
|
CREATE INDEX IF NOT EXISTS idx_agent_keys_api_key ON agent_keys(api_key);
|
|
355
554
|
CREATE INDEX IF NOT EXISTS idx_agent_keys_owner ON agent_keys(owner_wallet);
|
|
356
555
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_keys_owner_unique ON agent_keys(owner_wallet) WHERE owner_wallet IS NOT NULL;
|
|
357
556
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_keys_privy_did ON agent_keys(privy_did) WHERE privy_did IS NOT NULL;
|
|
557
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_keys_email ON agent_keys(email) WHERE email IS NOT NULL AND is_active = true;
|
|
358
558
|
|
|
359
559
|
-- Cortex recall performance: owner_wallet scoped queries
|
|
360
560
|
CREATE INDEX IF NOT EXISTS idx_cortex_owner_recall ON memories(owner_wallet, decay_factor DESC, created_at DESC);
|
|
@@ -402,7 +602,7 @@ async function initDatabase() {
|
|
|
402
602
|
target_id BIGINT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
|
|
403
603
|
link_type TEXT NOT NULL CHECK (link_type IN (
|
|
404
604
|
'supports', 'contradicts', 'elaborates', 'causes', 'follows', 'relates', 'resolves',
|
|
405
|
-
'happens_before', 'happens_after', 'concurrent_with'
|
|
605
|
+
'supersedes', 'happens_before', 'happens_after', 'concurrent_with'
|
|
406
606
|
)),
|
|
407
607
|
strength REAL DEFAULT 0.5,
|
|
408
608
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
@@ -437,7 +637,11 @@ async function initDatabase() {
|
|
|
437
637
|
WHERE ml.source_id = ANY(seed_ids)
|
|
438
638
|
AND ml.target_id != ALL(seed_ids)
|
|
439
639
|
AND ml.strength >= min_strength
|
|
440
|
-
AND (
|
|
640
|
+
AND (
|
|
641
|
+
filter_owner IS NULL
|
|
642
|
+
OR (filter_owner = '__BOT_OWN__' AND m.owner_wallet IS NULL)
|
|
643
|
+
OR m.owner_wallet = filter_owner
|
|
644
|
+
)
|
|
441
645
|
UNION
|
|
442
646
|
SELECT DISTINCT ON (ml.source_id, ml.link_type)
|
|
443
647
|
ml.source_id AS memory_id,
|
|
@@ -449,7 +653,11 @@ async function initDatabase() {
|
|
|
449
653
|
WHERE ml.target_id = ANY(seed_ids)
|
|
450
654
|
AND ml.source_id != ALL(seed_ids)
|
|
451
655
|
AND ml.strength >= min_strength
|
|
452
|
-
AND (
|
|
656
|
+
AND (
|
|
657
|
+
filter_owner IS NULL
|
|
658
|
+
OR (filter_owner = '__BOT_OWN__' AND m.owner_wallet IS NULL)
|
|
659
|
+
OR m.owner_wallet = filter_owner
|
|
660
|
+
)
|
|
453
661
|
ORDER BY strength DESC
|
|
454
662
|
LIMIT max_results;
|
|
455
663
|
$$;
|
|
@@ -477,6 +685,10 @@ async function initDatabase() {
|
|
|
477
685
|
ALTER TABLE dream_logs ADD CONSTRAINT dream_logs_session_type_check
|
|
478
686
|
CHECK (session_type IN ('consolidation', 'reflection', 'emergence', 'compaction', 'decay', 'contradiction_resolution'));
|
|
479
687
|
|
|
688
|
+
-- Migration 021: per-owner attribution for dream_logs (fixes totalDreamSessions leak)
|
|
689
|
+
ALTER TABLE dream_logs ADD COLUMN IF NOT EXISTS owner_wallet TEXT;
|
|
690
|
+
CREATE INDEX IF NOT EXISTS idx_dream_logs_owner ON dream_logs(owner_wallet);
|
|
691
|
+
|
|
480
692
|
-- Migration: add 'resolves' + temporal link types
|
|
481
693
|
ALTER TABLE memory_links DROP CONSTRAINT IF EXISTS memory_links_link_type_check;
|
|
482
694
|
ALTER TABLE memory_links ADD CONSTRAINT memory_links_link_type_check
|
|
@@ -658,6 +870,170 @@ async function initDatabase() {
|
|
|
658
870
|
CREATE INDEX IF NOT EXISTS idx_memories_event_date ON memories(event_date)
|
|
659
871
|
WHERE event_date IS NOT NULL;
|
|
660
872
|
|
|
873
|
+
-- Lexical index for keyword/BM25 search (encryption \xA79). content_tokens is
|
|
874
|
+
-- app-maintained; ts_summary (summary-only) is added on fresh deploys that lack it.
|
|
875
|
+
ALTER TABLE memories ADD COLUMN IF NOT EXISTS content_tokens tsvector;
|
|
876
|
+
CREATE INDEX IF NOT EXISTS idx_memories_content_tokens ON memories USING GIN(content_tokens);
|
|
877
|
+
ALTER TABLE memories ADD COLUMN IF NOT EXISTS provider_delegated BOOLEAN DEFAULT TRUE;
|
|
878
|
+
CREATE INDEX IF NOT EXISTS idx_memories_delegated ON memories(provider_delegated) WHERE encrypted = TRUE;
|
|
879
|
+
ALTER TABLE memories ADD COLUMN IF NOT EXISTS summary_ciphertext TEXT;
|
|
880
|
+
ALTER TABLE memories ADD COLUMN IF NOT EXISTS embedding_ciphertext TEXT;
|
|
881
|
+
|
|
882
|
+
-- Memory 3.0 Phase 1 (migration 044): bi-temporal validity + provenance (additive/nullable, inert until MEMORY_RECONCILE)
|
|
883
|
+
ALTER TABLE memories ADD COLUMN IF NOT EXISTS valid_from TIMESTAMPTZ;
|
|
884
|
+
ALTER TABLE memories ADD COLUMN IF NOT EXISTS invalid_at TIMESTAMPTZ;
|
|
885
|
+
ALTER TABLE memories ADD COLUMN IF NOT EXISTS superseded_by TEXT;
|
|
886
|
+
ALTER TABLE memories ADD COLUMN IF NOT EXISTS fact_key TEXT;
|
|
887
|
+
ALTER TABLE memories ADD COLUMN IF NOT EXISTS extractor_version TEXT;
|
|
888
|
+
ALTER TABLE memories ADD COLUMN IF NOT EXISTS extraction_confidence REAL;
|
|
889
|
+
ALTER TABLE memories ADD COLUMN IF NOT EXISTS source_turn_ref JSONB;
|
|
890
|
+
ALTER TABLE memories ADD COLUMN IF NOT EXISTS hash_id_v2 TEXT;
|
|
891
|
+
CREATE INDEX IF NOT EXISTS idx_memories_valid ON memories(id) WHERE invalid_at IS NULL;
|
|
892
|
+
CREATE INDEX IF NOT EXISTS idx_memories_fact_key ON memories(fact_key) WHERE fact_key IS NOT NULL;
|
|
893
|
+
CREATE INDEX IF NOT EXISTS idx_memories_hash_id_v2 ON memories(hash_id_v2) WHERE hash_id_v2 IS NOT NULL;
|
|
894
|
+
|
|
895
|
+
-- Memory 3.0 Phase 1 (migration 044): the C2 durable write outbox.
|
|
896
|
+
CREATE TABLE IF NOT EXISTS memory_write_jobs (
|
|
897
|
+
id BIGSERIAL PRIMARY KEY,
|
|
898
|
+
memory_id BIGINT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
|
|
899
|
+
job_type TEXT NOT NULL CHECK (job_type IN ('enrich', 'embed', 'link', 'extract', 'reconcile', 'backfill_v2')),
|
|
900
|
+
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'running', 'done', 'failed')),
|
|
901
|
+
attempts INT NOT NULL DEFAULT 0,
|
|
902
|
+
next_retry_at TIMESTAMPTZ DEFAULT NOW(),
|
|
903
|
+
last_error TEXT,
|
|
904
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
905
|
+
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
906
|
+
);
|
|
907
|
+
CREATE INDEX IF NOT EXISTS idx_write_jobs_claim ON memory_write_jobs(status, next_retry_at) WHERE status IN ('pending', 'failed');
|
|
908
|
+
CREATE INDEX IF NOT EXISTS idx_write_jobs_memory ON memory_write_jobs(memory_id);
|
|
909
|
+
CREATE INDEX IF NOT EXISTS idx_write_jobs_running ON memory_write_jobs(updated_at) WHERE status = 'running';
|
|
910
|
+
|
|
911
|
+
-- Memory 3.0 C2 outbox \u2014 MIRROR of migration 045 (byte-equivalent). The table above is in
|
|
912
|
+
-- the boot blob, so the claim RPC + idempotency objects MUST be too: otherwise a
|
|
913
|
+
-- boot-provisioned box gets the table but not the RPC and the worker silently never drains
|
|
914
|
+
-- (the migration-028 class). Keep in sync with 045.
|
|
915
|
+
DO $do$
|
|
916
|
+
BEGIN
|
|
917
|
+
ALTER TABLE memory_write_jobs DROP CONSTRAINT IF EXISTS memory_write_jobs_job_type_check;
|
|
918
|
+
ALTER TABLE memory_write_jobs ADD CONSTRAINT memory_write_jobs_job_type_check
|
|
919
|
+
CHECK (job_type IN ('enrich', 'embed', 'link', 'extract', 'reconcile', 'backfill_v2'));
|
|
920
|
+
EXCEPTION WHEN undefined_table THEN NULL; END $do$;
|
|
921
|
+
|
|
922
|
+
CREATE OR REPLACE FUNCTION claim_memory_write_jobs(
|
|
923
|
+
p_limit INT DEFAULT 20, p_stale_running INTERVAL DEFAULT INTERVAL '15 minutes', p_owner TEXT DEFAULT NULL
|
|
924
|
+
)
|
|
925
|
+
RETURNS TABLE (id BIGINT, memory_id BIGINT, job_type TEXT, attempts INT, owner_wallet TEXT)
|
|
926
|
+
LANGUAGE plpgsql AS $claimfn$
|
|
927
|
+
BEGIN
|
|
928
|
+
RETURN QUERY
|
|
929
|
+
UPDATE memory_write_jobs j
|
|
930
|
+
SET status = 'running', attempts = j.attempts + 1, updated_at = NOW()
|
|
931
|
+
FROM (
|
|
932
|
+
SELECT jj.id FROM memory_write_jobs jj JOIN memories m ON m.id = jj.memory_id
|
|
933
|
+
WHERE ((jj.status IN ('pending','failed') AND jj.next_retry_at IS NOT NULL AND jj.next_retry_at <= NOW())
|
|
934
|
+
OR (jj.status = 'running' AND jj.updated_at < NOW() - p_stale_running))
|
|
935
|
+
AND (p_owner IS NULL OR m.owner_wallet = p_owner OR (p_owner = '__BOT_OWN__' AND m.owner_wallet IS NULL))
|
|
936
|
+
ORDER BY jj.next_retry_at ASC NULLS LAST LIMIT p_limit FOR UPDATE SKIP LOCKED
|
|
937
|
+
) claimed
|
|
938
|
+
WHERE j.id = claimed.id
|
|
939
|
+
RETURNING j.id, j.memory_id, j.job_type, j.attempts,
|
|
940
|
+
(SELECT mm.owner_wallet FROM memories mm WHERE mm.id = j.memory_id);
|
|
941
|
+
END; $claimfn$;
|
|
942
|
+
|
|
943
|
+
DO $do$
|
|
944
|
+
BEGIN
|
|
945
|
+
DELETE FROM memory_links a USING memory_links b
|
|
946
|
+
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;
|
|
947
|
+
ALTER TABLE memory_links DROP CONSTRAINT IF EXISTS memory_links_unique_edge;
|
|
948
|
+
ALTER TABLE memory_links ADD CONSTRAINT memory_links_unique_edge UNIQUE (source_id, target_id, link_type);
|
|
949
|
+
EXCEPTION WHEN undefined_table THEN NULL; END $do$;
|
|
950
|
+
|
|
951
|
+
CREATE OR REPLACE FUNCTION upsert_entity_relation(
|
|
952
|
+
p_src BIGINT, p_tgt BIGINT, p_type TEXT, p_evidence BIGINT DEFAULT NULL, p_strength REAL DEFAULT 0.5
|
|
953
|
+
)
|
|
954
|
+
RETURNS VOID LANGUAGE sql AS $uerfn$
|
|
955
|
+
INSERT INTO entity_relations (source_entity_id, target_entity_id, relation_type, strength, evidence_memory_ids)
|
|
956
|
+
VALUES (p_src, p_tgt, p_type, LEAST(1.0, p_strength),
|
|
957
|
+
CASE WHEN p_evidence IS NULL THEN '{}'::bigint[] ELSE ARRAY[p_evidence] END)
|
|
958
|
+
ON CONFLICT (source_entity_id, target_entity_id, relation_type) DO UPDATE
|
|
959
|
+
SET strength = LEAST(1.0, entity_relations.strength +
|
|
960
|
+
CASE WHEN p_evidence IS NULL OR p_evidence = ANY(entity_relations.evidence_memory_ids) THEN 0.0 ELSE 0.1 END),
|
|
961
|
+
evidence_memory_ids = (SELECT ARRAY(SELECT DISTINCT e FROM unnest(entity_relations.evidence_memory_ids || EXCLUDED.evidence_memory_ids) AS e));
|
|
962
|
+
$uerfn$;
|
|
963
|
+
|
|
964
|
+
-- Memory 3.0 C1 (migration 046): reconciliation SHADOW decision log. MIRROR \u2014 byte-equivalent
|
|
965
|
+
-- to migration 046. Records a PROPOSED reconcile op per write without applying it, so enforce
|
|
966
|
+
-- can be greenlit from a labeled sample. Deliberately NOT added to CORE_TABLES (dormant,
|
|
967
|
+
-- default-off; must not trip SCHEMA DRIFT at boot on an un-migrated prod box \u2014 C2 precedent).
|
|
968
|
+
CREATE TABLE IF NOT EXISTS memory_reconciliation_log (
|
|
969
|
+
id BIGSERIAL PRIMARY KEY,
|
|
970
|
+
memory_id BIGINT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
|
|
971
|
+
owner_wallet TEXT,
|
|
972
|
+
mode TEXT NOT NULL DEFAULT 'shadow' CHECK (mode IN ('shadow','enforce')),
|
|
973
|
+
proposed_op TEXT NOT NULL CHECK (proposed_op IN ('add','update','noop','needs_router','skip')),
|
|
974
|
+
target_memory_id BIGINT,
|
|
975
|
+
max_cosine REAL,
|
|
976
|
+
band TEXT CHECK (band IN ('hi','mid','lo','none')),
|
|
977
|
+
router_used BOOLEAN NOT NULL DEFAULT false,
|
|
978
|
+
router_model TEXT,
|
|
979
|
+
fact_key TEXT,
|
|
980
|
+
reason TEXT,
|
|
981
|
+
label TEXT,
|
|
982
|
+
labeled_at TIMESTAMPTZ,
|
|
983
|
+
gate_version TEXT,
|
|
984
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
985
|
+
);
|
|
986
|
+
CREATE INDEX IF NOT EXISTS idx_reconcile_log_owner_created ON memory_reconciliation_log(owner_wallet, created_at);
|
|
987
|
+
CREATE INDEX IF NOT EXISTS idx_reconcile_log_op ON memory_reconciliation_log(proposed_op);
|
|
988
|
+
CREATE INDEX IF NOT EXISTS idx_reconcile_log_router ON memory_reconciliation_log(mode, router_used);
|
|
989
|
+
|
|
990
|
+
DO $do$
|
|
991
|
+
BEGIN
|
|
992
|
+
IF NOT EXISTS (
|
|
993
|
+
SELECT 1 FROM information_schema.columns
|
|
994
|
+
WHERE table_name = 'memories' AND column_name = 'ts_summary'
|
|
995
|
+
) THEN
|
|
996
|
+
ALTER TABLE memories ADD COLUMN ts_summary tsvector GENERATED ALWAYS AS (
|
|
997
|
+
setweight(to_tsvector('english', COALESCE(summary, '')), 'A')
|
|
998
|
+
) STORED;
|
|
999
|
+
CREATE INDEX IF NOT EXISTS idx_memories_ts_summary ON memories USING GIN(ts_summary);
|
|
1000
|
+
END IF;
|
|
1001
|
+
END $do$;
|
|
1002
|
+
|
|
1003
|
+
-- Semantic search across memory-level embeddings with metadata filtering (the always-on
|
|
1004
|
+
-- recall vector lane). MIRROR of migration 028 / supabase-schema.sql \u2014 8-arg filter_tags form.
|
|
1005
|
+
CREATE OR REPLACE FUNCTION match_memories(
|
|
1006
|
+
query_embedding vector(1024),
|
|
1007
|
+
match_threshold float DEFAULT 0.3,
|
|
1008
|
+
match_count int DEFAULT 10,
|
|
1009
|
+
filter_types text[] DEFAULT NULL,
|
|
1010
|
+
filter_user text DEFAULT NULL,
|
|
1011
|
+
min_decay float DEFAULT 0.1,
|
|
1012
|
+
filter_owner text DEFAULT NULL,
|
|
1013
|
+
filter_tags text[] DEFAULT NULL
|
|
1014
|
+
)
|
|
1015
|
+
RETURNS TABLE (id bigint, similarity float)
|
|
1016
|
+
LANGUAGE plpgsql AS $$
|
|
1017
|
+
BEGIN
|
|
1018
|
+
RETURN QUERY
|
|
1019
|
+
SELECT m.id, (1 - (m.embedding <=> query_embedding))::float AS similarity
|
|
1020
|
+
FROM memories m
|
|
1021
|
+
WHERE m.embedding IS NOT NULL
|
|
1022
|
+
AND m.decay_factor >= min_decay
|
|
1023
|
+
AND (filter_types IS NULL OR m.memory_type = ANY(filter_types))
|
|
1024
|
+
AND (filter_user IS NULL OR m.related_user = filter_user)
|
|
1025
|
+
AND (
|
|
1026
|
+
filter_owner IS NULL
|
|
1027
|
+
OR (filter_owner = '__BOT_OWN__' AND m.owner_wallet IS NULL)
|
|
1028
|
+
OR m.owner_wallet = filter_owner
|
|
1029
|
+
)
|
|
1030
|
+
AND (filter_tags IS NULL OR m.tags && filter_tags)
|
|
1031
|
+
AND (1 - (m.embedding <=> query_embedding)) > match_threshold
|
|
1032
|
+
ORDER BY m.embedding <=> query_embedding
|
|
1033
|
+
LIMIT match_count;
|
|
1034
|
+
END;
|
|
1035
|
+
$$;
|
|
1036
|
+
|
|
661
1037
|
-- Temporal-aware semantic search RPC (Exp 9)
|
|
662
1038
|
CREATE OR REPLACE FUNCTION match_memories_temporal(
|
|
663
1039
|
query_embedding vector(1024),
|
|
@@ -681,7 +1057,11 @@ async function initDatabase() {
|
|
|
681
1057
|
AND m.decay_factor >= min_decay
|
|
682
1058
|
AND (filter_types IS NULL OR m.memory_type = ANY(filter_types))
|
|
683
1059
|
AND (filter_user IS NULL OR m.related_user = filter_user)
|
|
684
|
-
AND (
|
|
1060
|
+
AND (
|
|
1061
|
+
filter_owner IS NULL
|
|
1062
|
+
OR (filter_owner = '__BOT_OWN__' AND m.owner_wallet IS NULL)
|
|
1063
|
+
OR m.owner_wallet = filter_owner
|
|
1064
|
+
)
|
|
685
1065
|
AND (filter_tags IS NULL OR m.tags && filter_tags)
|
|
686
1066
|
AND (1 - (m.embedding <=> query_embedding)) > match_threshold
|
|
687
1067
|
AND (start_date IS NULL OR COALESCE(m.event_date, m.created_at) >= start_date)
|
|
@@ -691,8 +1071,124 @@ async function initDatabase() {
|
|
|
691
1071
|
END;
|
|
692
1072
|
$$;
|
|
693
1073
|
|
|
694
|
-
--
|
|
695
|
-
--
|
|
1074
|
+
-- Fragment-level semantic search with deduplication to parent memory. Returns the highest
|
|
1075
|
+
-- similarity fragment per memory (the non-skipExpansion recall lane). MIRROR of
|
|
1076
|
+
-- supabase-schema.sql \u2014 6-arg migration-043 form; the 4-arg (migration 009) call still
|
|
1077
|
+
-- resolves against it via parameter defaults, so a boot-provisioned box is never left on a
|
|
1078
|
+
-- stale fragment signature.
|
|
1079
|
+
CREATE OR REPLACE FUNCTION match_memory_fragments(
|
|
1080
|
+
query_embedding vector(1024),
|
|
1081
|
+
match_threshold float DEFAULT 0.3,
|
|
1082
|
+
match_count int DEFAULT 10,
|
|
1083
|
+
filter_owner text DEFAULT NULL,
|
|
1084
|
+
min_decay float DEFAULT 0.0,
|
|
1085
|
+
filter_types text[] DEFAULT NULL
|
|
1086
|
+
)
|
|
1087
|
+
RETURNS TABLE (memory_id bigint, max_similarity float)
|
|
1088
|
+
LANGUAGE plpgsql AS $$
|
|
1089
|
+
BEGIN
|
|
1090
|
+
RETURN QUERY
|
|
1091
|
+
SELECT f.memory_id, MAX((1 - (f.embedding <=> query_embedding))::float) AS max_similarity
|
|
1092
|
+
FROM memory_fragments f
|
|
1093
|
+
JOIN memories m ON m.id = f.memory_id
|
|
1094
|
+
WHERE f.embedding IS NOT NULL
|
|
1095
|
+
AND (1 - (f.embedding <=> query_embedding)) > match_threshold
|
|
1096
|
+
AND m.decay_factor >= min_decay
|
|
1097
|
+
AND (filter_types IS NULL OR m.memory_type = ANY(filter_types))
|
|
1098
|
+
AND (
|
|
1099
|
+
filter_owner IS NULL
|
|
1100
|
+
OR (filter_owner = '__BOT_OWN__' AND m.owner_wallet IS NULL)
|
|
1101
|
+
OR m.owner_wallet = filter_owner
|
|
1102
|
+
)
|
|
1103
|
+
GROUP BY f.memory_id
|
|
1104
|
+
ORDER BY max_similarity DESC
|
|
1105
|
+
LIMIT match_count;
|
|
1106
|
+
END;
|
|
1107
|
+
$$;
|
|
1108
|
+
|
|
1109
|
+
-- Encryption: owner key registry + per-memory wrapped DEKs (encryption \xA79, Plan 1 sync).
|
|
1110
|
+
CREATE TABLE IF NOT EXISTS encryption_keys (
|
|
1111
|
+
owner_wallet TEXT PRIMARY KEY,
|
|
1112
|
+
x25519_pubkey TEXT NOT NULL,
|
|
1113
|
+
verifier_ct TEXT NOT NULL,
|
|
1114
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
1115
|
+
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
1116
|
+
);
|
|
1117
|
+
CREATE TABLE IF NOT EXISTS memory_dek_wraps (
|
|
1118
|
+
memory_id BIGINT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
|
|
1119
|
+
recipient TEXT NOT NULL,
|
|
1120
|
+
wrapped_dek TEXT NOT NULL,
|
|
1121
|
+
wrap_pubkey TEXT NOT NULL,
|
|
1122
|
+
holder_wallet TEXT, -- (034) names the title_holder; NULL for owner/provider
|
|
1123
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
1124
|
+
CONSTRAINT memory_dek_wraps_recipient_chk CHECK (recipient IN ('owner', 'provider', 'title_holder')),
|
|
1125
|
+
CONSTRAINT memory_dek_wraps_holder_chk CHECK ((recipient = 'title_holder') = (holder_wallet IS NOT NULL))
|
|
1126
|
+
);
|
|
1127
|
+
-- (036) holder-aware uniqueness: one owner + one provider per memory (NULLS NOT DISTINCT),
|
|
1128
|
+
-- and one title_holder per (memory, holder) so a sale's seller + buyer wraps coexist (RT7).
|
|
1129
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_dek_wraps_identity
|
|
1130
|
+
ON memory_dek_wraps (memory_id, recipient, holder_wallet) NULLS NOT DISTINCT;
|
|
1131
|
+
CREATE INDEX IF NOT EXISTS idx_dek_wraps_memory ON memory_dek_wraps(memory_id);
|
|
1132
|
+
|
|
1133
|
+
-- Populate content_tokens from a transient plaintext arg (PostgREST can't express to_tsvector inline).
|
|
1134
|
+
-- setweight 'B' mirrors the old combined ts_summary weighting (summary 'A' > content 'B').
|
|
1135
|
+
CREATE OR REPLACE FUNCTION set_memory_content_tokens(p_memory_id bigint, p_text text)
|
|
1136
|
+
RETURNS void LANGUAGE sql AS $fn$
|
|
1137
|
+
UPDATE memories
|
|
1138
|
+
SET content_tokens = CASE
|
|
1139
|
+
WHEN p_text IS NULL OR p_text = '' THEN NULL
|
|
1140
|
+
ELSE setweight(to_tsvector('english', p_text), 'B')
|
|
1141
|
+
END
|
|
1142
|
+
WHERE id = p_memory_id;
|
|
1143
|
+
$fn$;
|
|
1144
|
+
|
|
1145
|
+
-- Atomic revoke: clear plaintext + drop the provider wrap in one transaction (encryption \xA77).
|
|
1146
|
+
CREATE OR REPLACE FUNCTION revoke_memory(p_memory_id bigint, p_summary_ct text, p_embedding_ct text)
|
|
1147
|
+
RETURNS void LANGUAGE plpgsql AS $rev$
|
|
1148
|
+
BEGIN
|
|
1149
|
+
UPDATE memories SET
|
|
1150
|
+
summary = '',
|
|
1151
|
+
summary_ciphertext = p_summary_ct,
|
|
1152
|
+
embedding = NULL,
|
|
1153
|
+
embedding_ciphertext = NULLIF(p_embedding_ct, ''),
|
|
1154
|
+
content_tokens = NULL,
|
|
1155
|
+
provider_delegated = false
|
|
1156
|
+
WHERE id = p_memory_id;
|
|
1157
|
+
DELETE FROM memory_dek_wraps WHERE memory_id = p_memory_id AND recipient = 'provider';
|
|
1158
|
+
-- Fragment parity (migration 043): fragments hold plaintext + live embeddings.
|
|
1159
|
+
DELETE FROM memory_fragments WHERE memory_id = p_memory_id;
|
|
1160
|
+
END;
|
|
1161
|
+
$rev$;
|
|
1162
|
+
|
|
1163
|
+
-- Atomic re-delegate (encryption \xA77) \u2014 inverse of revoke_memory. Restores plaintext
|
|
1164
|
+
-- summary/embedding, rebuilds content_tokens via the canonical builder, clears ciphertext
|
|
1165
|
+
-- cols, sets provider_delegated=true, (re-)inserts the provider wrap.
|
|
1166
|
+
CREATE OR REPLACE FUNCTION redelegate_memory(
|
|
1167
|
+
p_memory_id bigint,
|
|
1168
|
+
p_summary text,
|
|
1169
|
+
p_embedding text,
|
|
1170
|
+
p_content text,
|
|
1171
|
+
p_wrapped_dek text,
|
|
1172
|
+
p_wrap_pubkey text
|
|
1173
|
+
)
|
|
1174
|
+
RETURNS void LANGUAGE plpgsql AS $redel$
|
|
1175
|
+
BEGIN
|
|
1176
|
+
UPDATE memories SET
|
|
1177
|
+
summary = p_summary,
|
|
1178
|
+
summary_ciphertext = NULL,
|
|
1179
|
+
embedding = NULLIF(p_embedding, '')::vector,
|
|
1180
|
+
embedding_ciphertext = NULL,
|
|
1181
|
+
provider_delegated = true
|
|
1182
|
+
WHERE id = p_memory_id;
|
|
1183
|
+
PERFORM set_memory_content_tokens(p_memory_id, p_content);
|
|
1184
|
+
INSERT INTO memory_dek_wraps (memory_id, recipient, wrapped_dek, wrap_pubkey)
|
|
1185
|
+
VALUES (p_memory_id, 'provider', p_wrapped_dek, p_wrap_pubkey)
|
|
1186
|
+
ON CONFLICT (memory_id, recipient) DO UPDATE
|
|
1187
|
+
SET wrapped_dek = EXCLUDED.wrapped_dek, wrap_pubkey = EXCLUDED.wrap_pubkey;
|
|
1188
|
+
END;
|
|
1189
|
+
$redel$;
|
|
1190
|
+
|
|
1191
|
+
-- BM25-ranked full-text search RPC (Exp 8) \u2014 dual-column (ts_summary + content_tokens)
|
|
696
1192
|
CREATE OR REPLACE FUNCTION bm25_search_memories(
|
|
697
1193
|
search_query text,
|
|
698
1194
|
match_count int DEFAULT 20,
|
|
@@ -711,11 +1207,17 @@ async function initDatabase() {
|
|
|
711
1207
|
RETURN;
|
|
712
1208
|
END IF;
|
|
713
1209
|
RETURN QUERY
|
|
714
|
-
SELECT m.id,
|
|
1210
|
+
SELECT m.id,
|
|
1211
|
+
(ts_rank_cd(COALESCE(m.ts_summary, ''::tsvector), tsquery_val, 32)
|
|
1212
|
+
+ ts_rank_cd(COALESCE(m.content_tokens, ''::tsvector), tsquery_val, 32))::float AS rank
|
|
715
1213
|
FROM memories m
|
|
716
|
-
WHERE m.ts_summary @@ tsquery_val
|
|
1214
|
+
WHERE (m.ts_summary @@ tsquery_val OR m.content_tokens @@ tsquery_val)
|
|
717
1215
|
AND m.decay_factor >= min_decay
|
|
718
|
-
AND (
|
|
1216
|
+
AND (
|
|
1217
|
+
filter_owner IS NULL
|
|
1218
|
+
OR (filter_owner = '__BOT_OWN__' AND m.owner_wallet IS NULL)
|
|
1219
|
+
OR m.owner_wallet = filter_owner
|
|
1220
|
+
)
|
|
719
1221
|
AND (filter_types IS NULL OR m.memory_type = ANY(filter_types))
|
|
720
1222
|
AND (filter_tags IS NULL OR m.tags && filter_tags)
|
|
721
1223
|
ORDER BY rank DESC
|
|
@@ -744,9 +1246,31 @@ async function initDatabase() {
|
|
|
744
1246
|
tokens_prompt INTEGER,
|
|
745
1247
|
tokens_completion INTEGER,
|
|
746
1248
|
memory_ids INTEGER[],
|
|
1249
|
+
frontier_tokens INTEGER,
|
|
1250
|
+
memories_used INTEGER,
|
|
1251
|
+
tokens_saved INTEGER,
|
|
747
1252
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
748
1253
|
);
|
|
749
1254
|
CREATE INDEX IF NOT EXISTS idx_chat_msg_conv ON chat_messages(conversation_id, created_at);
|
|
1255
|
+
CREATE INDEX IF NOT EXISTS idx_chat_msg_saved ON chat_messages(created_at) WHERE tokens_saved IS NOT NULL;
|
|
1256
|
+
-- Proof counter totals. "today" is UTC-based (resets 00:00 UTC). Full-table aggregate:
|
|
1257
|
+
-- callers MUST use a server-side TTL cache (see proof.routes.ts). Mirror of migration 026.
|
|
1258
|
+
CREATE OR REPLACE FUNCTION proof_tokens_saved_totals()
|
|
1259
|
+
RETURNS TABLE(
|
|
1260
|
+
measured_saved bigint,
|
|
1261
|
+
measured_today bigint,
|
|
1262
|
+
measured_frontier bigint,
|
|
1263
|
+
historical_prompt_sum bigint,
|
|
1264
|
+
n bigint
|
|
1265
|
+
) LANGUAGE sql STABLE AS $$
|
|
1266
|
+
SELECT
|
|
1267
|
+
COALESCE(SUM(tokens_saved) FILTER (WHERE tokens_saved IS NOT NULL), 0)::bigint,
|
|
1268
|
+
COALESCE(SUM(tokens_saved) FILTER (WHERE tokens_saved IS NOT NULL AND created_at >= date_trunc('day', now())), 0)::bigint,
|
|
1269
|
+
COALESCE(SUM(frontier_tokens) FILTER (WHERE tokens_saved IS NOT NULL), 0)::bigint,
|
|
1270
|
+
COALESCE(SUM(tokens_prompt) FILTER (WHERE tokens_saved IS NULL), 0)::bigint,
|
|
1271
|
+
COUNT(*) FILTER (WHERE tokens_saved IS NOT NULL)::bigint
|
|
1272
|
+
FROM chat_messages;
|
|
1273
|
+
$$;
|
|
750
1274
|
|
|
751
1275
|
-- Chat billing: balances, top-ups, and per-message usage
|
|
752
1276
|
CREATE TABLE IF NOT EXISTS chat_balances (
|
|
@@ -788,6 +1312,63 @@ async function initDatabase() {
|
|
|
788
1312
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
789
1313
|
);
|
|
790
1314
|
CREATE INDEX IF NOT EXISTS idx_chat_usage_wallet ON chat_usage(wallet_address, created_at DESC);
|
|
1315
|
+
|
|
1316
|
+
-- Wiki pack installations: which packs (Workspace, Compliance, Sales)
|
|
1317
|
+
-- each wallet has installed. Drives the topic rail in /wiki and
|
|
1318
|
+
-- the auto-categorisation rules applied to incoming memories.
|
|
1319
|
+
CREATE TABLE IF NOT EXISTS wiki_pack_installations (
|
|
1320
|
+
id BIGSERIAL PRIMARY KEY,
|
|
1321
|
+
owner_wallet TEXT NOT NULL,
|
|
1322
|
+
pack_id TEXT NOT NULL,
|
|
1323
|
+
installed_at TIMESTAMPTZ DEFAULT NOW(),
|
|
1324
|
+
UNIQUE (owner_wallet, pack_id)
|
|
1325
|
+
);
|
|
1326
|
+
CREATE INDEX IF NOT EXISTS idx_wiki_pack_installations_owner
|
|
1327
|
+
ON wiki_pack_installations(owner_wallet);
|
|
1328
|
+
|
|
1329
|
+
-- Migration 019: corrected access-boost RPC (no importance mutation on read).
|
|
1330
|
+
-- Re-asserted on every boot so the read->rank->read feedback loop can't return.
|
|
1331
|
+
-- importance_boosts is kept for call-site signature compatibility and ignored.
|
|
1332
|
+
-- Drop the stale single-arg overload (migration 003) so the two-arg version is
|
|
1333
|
+
-- unambiguous and the lockdown below can't be bypassed via an unrevoked overload.
|
|
1334
|
+
DROP FUNCTION IF EXISTS batch_boost_memory_access(bigint[]);
|
|
1335
|
+
CREATE OR REPLACE FUNCTION batch_boost_memory_access(
|
|
1336
|
+
memory_ids BIGINT[],
|
|
1337
|
+
importance_boosts DOUBLE PRECISION[] DEFAULT NULL
|
|
1338
|
+
) RETURNS void LANGUAGE plpgsql AS $fn$
|
|
1339
|
+
BEGIN
|
|
1340
|
+
UPDATE memories
|
|
1341
|
+
SET access_count = access_count + 1,
|
|
1342
|
+
last_accessed = NOW(),
|
|
1343
|
+
decay_factor = LEAST(1.0, decay_factor + 0.1)
|
|
1344
|
+
WHERE id = ANY(memory_ids);
|
|
1345
|
+
END;
|
|
1346
|
+
$fn$;
|
|
1347
|
+
|
|
1348
|
+
-- Migration 020: lock down dangerous SECURITY DEFINER RPCs to service_role only.
|
|
1349
|
+
-- exec_sql/boost RPCs must never be callable by anon/authenticated (the publishable
|
|
1350
|
+
-- key ships in client apps). Wrapped so a not-yet-created function never aborts boot.
|
|
1351
|
+
DO $do$ BEGIN
|
|
1352
|
+
REVOKE EXECUTE ON FUNCTION exec_sql(text) FROM PUBLIC, anon, authenticated;
|
|
1353
|
+
GRANT EXECUTE ON FUNCTION exec_sql(text) TO service_role;
|
|
1354
|
+
EXCEPTION WHEN undefined_function THEN NULL; END $do$;
|
|
1355
|
+
|
|
1356
|
+
DO $do$ BEGIN
|
|
1357
|
+
REVOKE EXECUTE ON FUNCTION batch_boost_memory_access(bigint[], double precision[]) FROM PUBLIC, anon, authenticated;
|
|
1358
|
+
GRANT EXECUTE ON FUNCTION batch_boost_memory_access(bigint[], double precision[]) TO service_role;
|
|
1359
|
+
EXCEPTION WHEN undefined_function THEN NULL; END $do$;
|
|
1360
|
+
|
|
1361
|
+
DO $do$ BEGIN
|
|
1362
|
+
REVOKE EXECUTE ON FUNCTION boost_memory_importance(bigint, double precision, double precision) FROM PUBLIC, anon, authenticated;
|
|
1363
|
+
GRANT EXECUTE ON FUNCTION boost_memory_importance(bigint, double precision, double precision) TO service_role;
|
|
1364
|
+
EXCEPTION WHEN undefined_function THEN NULL; END $do$;
|
|
1365
|
+
|
|
1366
|
+
-- Migration 022 (guard portion): reject empty content on NEW writes in every env.
|
|
1367
|
+
-- NOT VALID enforces on new INSERT/UPDATE without scanning existing rows; the
|
|
1368
|
+
-- one-time cleanup of legacy blank rows + VALIDATE lives in the manual 022 file.
|
|
1369
|
+
DO $do$ BEGIN
|
|
1370
|
+
ALTER TABLE memories ADD CONSTRAINT memories_content_nonempty CHECK (length(btrim(content)) > 0) NOT VALID;
|
|
1371
|
+
EXCEPTION WHEN duplicate_object THEN NULL; END $do$;
|
|
791
1372
|
`
|
|
792
1373
|
});
|
|
793
1374
|
if (error) {
|
|
@@ -796,7 +1377,75 @@ async function initDatabase() {
|
|
|
796
1377
|
} catch {
|
|
797
1378
|
log.warn("rpc exec_sql not available. Create tables via Supabase SQL editor.");
|
|
798
1379
|
}
|
|
799
|
-
|
|
1380
|
+
}
|
|
1381
|
+
function getSchemaDriftReport() {
|
|
1382
|
+
return lastSchemaReport;
|
|
1383
|
+
}
|
|
1384
|
+
async function verifyCoreSchema(db) {
|
|
1385
|
+
const missingTables = [];
|
|
1386
|
+
const probeErrors = [];
|
|
1387
|
+
for (const table of CORE_TABLES) {
|
|
1388
|
+
try {
|
|
1389
|
+
const { error } = await db.from(table).select("id", { head: true, count: "exact" }).limit(1);
|
|
1390
|
+
if (error) {
|
|
1391
|
+
if (MISSING_RE.test(error.message ?? "")) missingTables.push(table);
|
|
1392
|
+
else probeErrors.push(`${table}: ${error.message}`);
|
|
1393
|
+
}
|
|
1394
|
+
} catch (err) {
|
|
1395
|
+
probeErrors.push(`${table}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
const brokenRpcs = [];
|
|
1399
|
+
for (const probe of CORE_RPC_PROBES) {
|
|
1400
|
+
try {
|
|
1401
|
+
const { error } = await db.rpc(probe.name, probe.args);
|
|
1402
|
+
if (error && MISSING_RE.test(error.message ?? "")) brokenRpcs.push(probe.name);
|
|
1403
|
+
} catch (err) {
|
|
1404
|
+
probeErrors.push(`${probe.name}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
return { missingTables, brokenRpcs, probeErrors };
|
|
1408
|
+
}
|
|
1409
|
+
async function initDatabase(dbOverride) {
|
|
1410
|
+
const db = dbOverride ?? getDb();
|
|
1411
|
+
const mode = process.env.INIT_DB_MODE ?? "auto";
|
|
1412
|
+
if (mode === "replay") {
|
|
1413
|
+
await runBootDdl(db);
|
|
1414
|
+
lastSchemaReport = { at: (/* @__PURE__ */ new Date()).toISOString(), status: "replayed", missingTables: [], brokenRpcs: [] };
|
|
1415
|
+
log.info("Database initialized (legacy replay mode)");
|
|
1416
|
+
return;
|
|
1417
|
+
}
|
|
1418
|
+
let { missingTables, brokenRpcs, probeErrors } = await verifyCoreSchema(db);
|
|
1419
|
+
if (mode === "auto" && missingTables.includes("memories")) {
|
|
1420
|
+
log.info("Fresh database detected (no memories table) \u2014 running boot DDL");
|
|
1421
|
+
await runBootDdl(db);
|
|
1422
|
+
({ missingTables, brokenRpcs, probeErrors } = await verifyCoreSchema(db));
|
|
1423
|
+
const healthy2 = missingTables.length === 0 && brokenRpcs.length === 0;
|
|
1424
|
+
lastSchemaReport = {
|
|
1425
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1426
|
+
status: healthy2 ? "fresh-bootstrapped" : "drift",
|
|
1427
|
+
missingTables,
|
|
1428
|
+
brokenRpcs
|
|
1429
|
+
};
|
|
1430
|
+
if (healthy2) log.info("Database bootstrapped from boot DDL");
|
|
1431
|
+
else log.error({ missingTables, brokenRpcs }, "SCHEMA DRIFT after fresh bootstrap \u2014 check exec_sql availability");
|
|
1432
|
+
return;
|
|
1433
|
+
}
|
|
1434
|
+
if (probeErrors.length > 0 && missingTables.length === 0 && brokenRpcs.length === 0) {
|
|
1435
|
+
lastSchemaReport = { at: (/* @__PURE__ */ new Date()).toISOString(), status: "unknown", missingTables, brokenRpcs };
|
|
1436
|
+
log.warn({ probeErrors: probeErrors.slice(0, 3) }, "Schema verification inconclusive (probe errors); skipping");
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1439
|
+
const healthy = missingTables.length === 0 && brokenRpcs.length === 0;
|
|
1440
|
+
lastSchemaReport = { at: (/* @__PURE__ */ new Date()).toISOString(), status: healthy ? "ok" : "drift", missingTables, brokenRpcs };
|
|
1441
|
+
if (healthy) {
|
|
1442
|
+
log.info("Database schema verified (boot blob frozen; migrations are the single schema writer)");
|
|
1443
|
+
} else {
|
|
1444
|
+
log.error(
|
|
1445
|
+
{ missingTables, brokenRpcs },
|
|
1446
|
+
"SCHEMA DRIFT \u2014 core objects missing on an established database; apply the corresponding migration by hand (the boot blob no longer auto-repairs)"
|
|
1447
|
+
);
|
|
1448
|
+
}
|
|
800
1449
|
}
|
|
801
1450
|
async function isAlreadyProcessed(tweetId) {
|
|
802
1451
|
const db = getDb();
|
|
@@ -829,14 +1478,44 @@ async function markProcessed(tweetId, feature, responseTweetId, extra) {
|
|
|
829
1478
|
processed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
830
1479
|
});
|
|
831
1480
|
}
|
|
832
|
-
var import_supabase_js, log, supabase;
|
|
1481
|
+
var import_supabase_js, log, supabase, CORE_TABLES, CORE_RPC_PROBES, lastSchemaReport, MISSING_RE;
|
|
833
1482
|
var init_database = __esm({
|
|
834
1483
|
"packages/shared/src/core/database.ts"() {
|
|
835
1484
|
"use strict";
|
|
836
1485
|
import_supabase_js = require("@supabase/supabase-js");
|
|
837
1486
|
init_config();
|
|
838
1487
|
init_logger();
|
|
1488
|
+
init_migration_profile();
|
|
839
1489
|
log = createChildLogger("database");
|
|
1490
|
+
CORE_TABLES = [
|
|
1491
|
+
"memories",
|
|
1492
|
+
"memory_fragments",
|
|
1493
|
+
"memory_links",
|
|
1494
|
+
"agent_keys",
|
|
1495
|
+
"dream_logs",
|
|
1496
|
+
"rate_limits",
|
|
1497
|
+
"chat_conversations",
|
|
1498
|
+
"chat_messages"
|
|
1499
|
+
];
|
|
1500
|
+
CORE_RPC_PROBES = [
|
|
1501
|
+
{ name: "get_linked_memories", args: { seed_ids: [], min_strength: 0.1, max_results: 1, filter_owner: null } },
|
|
1502
|
+
{ name: "bm25_search_memories", args: { search_query: "", match_count: 1, min_decay: 0.1, filter_owner: null } },
|
|
1503
|
+
// Vector recall lanes (memory.ts recall). match_memories is the always-on memory-level lane;
|
|
1504
|
+
// match_memory_fragments is the fragment lane (non-skipExpansion path). Both are silently absent
|
|
1505
|
+
// from a stale boot blob, so probe them so the gap surfaces as drift instead of a dead lane.
|
|
1506
|
+
// Vector-safe args (null embedding + match_count 0) make each a no-op read. filter_tags pins the
|
|
1507
|
+
// migration-028 8-arg match_memories overload recall calls unconditionally (catches a pre-028 box
|
|
1508
|
+
// where recall would break); the fragment probe stays the 4/6-arg common subset (min_decay +
|
|
1509
|
+
// filter_types are opt-in via MEMORY_FRAGMENT_FILTERS) so it never false-drifts a pre-043 box
|
|
1510
|
+
// still serving the migration-009 4-arg form.
|
|
1511
|
+
{ name: "match_memories", args: { query_embedding: null, match_count: 0, filter_owner: null, filter_tags: null } },
|
|
1512
|
+
{ name: "match_memory_fragments", args: { query_embedding: null, match_count: 0, filter_owner: null } },
|
|
1513
|
+
// Memory 3.0 C2: probe the outbox claim RPC so a table-without-RPC state (the §6 hazard) is
|
|
1514
|
+
// caught at boot as drift rather than silently never draining.
|
|
1515
|
+
{ name: "claim_memory_write_jobs", args: { p_limit: 0 } }
|
|
1516
|
+
];
|
|
1517
|
+
lastSchemaReport = null;
|
|
1518
|
+
MISSING_RE = /does not exist|could not find|schema cache/i;
|
|
840
1519
|
}
|
|
841
1520
|
});
|
|
842
1521
|
|
|
@@ -949,6 +1628,17 @@ var init_guardrails = __esm({
|
|
|
949
1628
|
}
|
|
950
1629
|
});
|
|
951
1630
|
|
|
1631
|
+
// packages/shared/src/core/model-capabilities.ts
|
|
1632
|
+
function modelRejectsSamplingParams(model) {
|
|
1633
|
+
if (!model) return false;
|
|
1634
|
+
return /opus[-_.]?4[-_.]?[789]\b|opus[-_.]?[5-9]\b/i.test(model);
|
|
1635
|
+
}
|
|
1636
|
+
var init_model_capabilities = __esm({
|
|
1637
|
+
"packages/shared/src/core/model-capabilities.ts"() {
|
|
1638
|
+
"use strict";
|
|
1639
|
+
}
|
|
1640
|
+
});
|
|
1641
|
+
|
|
952
1642
|
// packages/shared/src/core/openrouter-client.ts
|
|
953
1643
|
function getModelForFunction(fn) {
|
|
954
1644
|
return COGNITIVE_MODEL_MAP[fn] || OPENROUTER_MODELS["llama-70b"];
|
|
@@ -1015,7 +1705,8 @@ async function generateOpenRouterResponse(opts) {
|
|
|
1015
1705
|
model,
|
|
1016
1706
|
messages,
|
|
1017
1707
|
max_tokens: maxTokens,
|
|
1018
|
-
|
|
1708
|
+
// Opus 4.7+ via OpenRouter rejects temperature/top_p — strip for those models.
|
|
1709
|
+
...modelRejectsSamplingParams(model) ? {} : { temperature: opts.temperature ?? 0.7 }
|
|
1019
1710
|
})
|
|
1020
1711
|
});
|
|
1021
1712
|
if (!response.ok) {
|
|
@@ -1046,15 +1737,18 @@ var init_openrouter_client = __esm({
|
|
|
1046
1737
|
"packages/shared/src/core/openrouter-client.ts"() {
|
|
1047
1738
|
"use strict";
|
|
1048
1739
|
init_logger();
|
|
1740
|
+
init_model_capabilities();
|
|
1049
1741
|
log3 = createChildLogger("openrouter");
|
|
1050
1742
|
OPENROUTER_API_URL = "https://openrouter.ai/api/v1";
|
|
1051
1743
|
OPENROUTER_MODELS = {
|
|
1052
1744
|
// Frontier (Anthropic)
|
|
1745
|
+
"claude-opus-4.7": "anthropic/claude-opus-4.7",
|
|
1053
1746
|
"claude-opus-4.6": "anthropic/claude-opus-4.6",
|
|
1054
1747
|
"claude-sonnet-4.6": "anthropic/claude-sonnet-4.6",
|
|
1055
1748
|
"claude-opus-4.5": "anthropic/claude-opus-4.5",
|
|
1056
1749
|
"claude-sonnet-4.5": "anthropic/claude-sonnet-4.5",
|
|
1057
1750
|
// Frontier (Other providers)
|
|
1751
|
+
"gpt-5.5": "openai/gpt-5.5",
|
|
1058
1752
|
"gpt-5.4": "openai/gpt-5.4",
|
|
1059
1753
|
"grok-4.1": "x-ai/grok-4.1-fast",
|
|
1060
1754
|
"gemini-3-pro": "google/gemini-3-pro-preview",
|
|
@@ -1106,7 +1800,7 @@ var init_openrouter_client = __esm({
|
|
|
1106
1800
|
});
|
|
1107
1801
|
|
|
1108
1802
|
// packages/shared/src/utils/constants.ts
|
|
1109
|
-
var MEMO_PROGRAM_ID, SOLSCAN_TX_BASE_URL, TWEET_MAX_LENGTH, 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,
|
|
1803
|
+
var MEMO_PROGRAM_ID, SOLSCAN_TX_BASE_URL, TWEET_MAX_LENGTH, 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_IMPORTANCE_THRESHOLD, REFLECTION_MIN_INTERVAL_MS, WHALE_SELL_COOLDOWN_MS, MEMO_MAX_LENGTH;
|
|
1110
1804
|
var init_constants = __esm({
|
|
1111
1805
|
"packages/shared/src/utils/constants.ts"() {
|
|
1112
1806
|
"use strict";
|
|
@@ -1130,10 +1824,11 @@ var init_constants = __esm({
|
|
|
1130
1824
|
// Journal entries persist like knowledge
|
|
1131
1825
|
};
|
|
1132
1826
|
RECENCY_DECAY_BASE = 0.995;
|
|
1133
|
-
RETRIEVAL_WEIGHT_RECENCY = 1;
|
|
1134
|
-
RETRIEVAL_WEIGHT_RELEVANCE = 2;
|
|
1135
|
-
RETRIEVAL_WEIGHT_IMPORTANCE = 2;
|
|
1136
|
-
RETRIEVAL_WEIGHT_VECTOR = 4;
|
|
1827
|
+
RETRIEVAL_WEIGHT_RECENCY = Number(process.env.RETRIEVAL_WEIGHT_RECENCY ?? "1.0");
|
|
1828
|
+
RETRIEVAL_WEIGHT_RELEVANCE = Number(process.env.RETRIEVAL_WEIGHT_RELEVANCE ?? "2.0");
|
|
1829
|
+
RETRIEVAL_WEIGHT_IMPORTANCE = Number(process.env.RETRIEVAL_WEIGHT_IMPORTANCE ?? "2.0");
|
|
1830
|
+
RETRIEVAL_WEIGHT_VECTOR = Number(process.env.RETRIEVAL_WEIGHT_VECTOR ?? "4.0");
|
|
1831
|
+
RETRIEVAL_WEIGHT_BM25 = Number(process.env.RETRIEVAL_WEIGHT_BM25 ?? "0.15");
|
|
1137
1832
|
KNOWLEDGE_TYPE_BOOST = {
|
|
1138
1833
|
semantic: 0.15,
|
|
1139
1834
|
// Distilled knowledge gets meaningful boost
|
|
@@ -1150,6 +1845,8 @@ var init_constants = __esm({
|
|
|
1150
1845
|
BOND_TYPE_WEIGHTS = {
|
|
1151
1846
|
causes: 1,
|
|
1152
1847
|
supports: 0.9,
|
|
1848
|
+
supersedes: 0.85,
|
|
1849
|
+
// decisive replacement: new fact overrides an older one (Memory 3.0 C1)
|
|
1153
1850
|
concurrent_with: 0.8,
|
|
1154
1851
|
resolves: 0.8,
|
|
1155
1852
|
happens_before: 0.7,
|
|
@@ -1174,7 +1871,7 @@ var init_constants = __esm({
|
|
|
1174
1871
|
"dream-cycle",
|
|
1175
1872
|
"meditation"
|
|
1176
1873
|
]);
|
|
1177
|
-
|
|
1874
|
+
EMBEDDING_FRAGMENT_MAX_LENGTH = 2e3;
|
|
1178
1875
|
REFLECTION_IMPORTANCE_THRESHOLD = 2;
|
|
1179
1876
|
REFLECTION_MIN_INTERVAL_MS = 30 * 60 * 1e3;
|
|
1180
1877
|
WHALE_SELL_COOLDOWN_MS = 30 * 60 * 1e3;
|
|
@@ -1271,10 +1968,12 @@ ${userContent || "(no message, just a mention)"}`;
|
|
|
1271
1968
|
cognitiveFunction: options.cognitiveFunction
|
|
1272
1969
|
});
|
|
1273
1970
|
} else {
|
|
1971
|
+
const directModel = getModel();
|
|
1274
1972
|
const response = await getClient().messages.create({
|
|
1275
|
-
model:
|
|
1973
|
+
model: directModel,
|
|
1276
1974
|
max_tokens: options.maxTokens || 1024,
|
|
1277
|
-
temperature
|
|
1975
|
+
// Opus 4.7+ rejects temperature/top_p (HTTP 400) — strip for those models.
|
|
1976
|
+
...modelRejectsSamplingParams(directModel) ? {} : { temperature: 0.9 },
|
|
1278
1977
|
system: systemPrompt,
|
|
1279
1978
|
messages: [{ role: "user", content: userContent }]
|
|
1280
1979
|
});
|
|
@@ -1301,10 +2000,11 @@ ${userContent || "(no message, just a mention)"}`;
|
|
|
1301
2000
|
}
|
|
1302
2001
|
async function generateImportanceScore(description) {
|
|
1303
2002
|
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.";
|
|
2003
|
+
const scoreModel = getModel();
|
|
1304
2004
|
const response = await getClient().messages.create({
|
|
1305
|
-
model:
|
|
2005
|
+
model: scoreModel,
|
|
1306
2006
|
max_tokens: 10,
|
|
1307
|
-
temperature: 0,
|
|
2007
|
+
...modelRejectsSamplingParams(scoreModel) ? {} : { temperature: 0 },
|
|
1308
2008
|
system: importancePrompt,
|
|
1309
2009
|
messages: [{
|
|
1310
2010
|
role: "user",
|
|
@@ -1333,6 +2033,7 @@ var init_claude_client = __esm({
|
|
|
1333
2033
|
init_guardrails();
|
|
1334
2034
|
init_openrouter_client();
|
|
1335
2035
|
init_constants();
|
|
2036
|
+
init_model_capabilities();
|
|
1336
2037
|
log4 = createChildLogger("claude-client");
|
|
1337
2038
|
_systemPromptProvider = () => "You are an AI assistant with persistent memory.";
|
|
1338
2039
|
_responsePostProcessor = (t) => t;
|
|
@@ -1353,11 +2054,18 @@ var init_claude_client = __esm({
|
|
|
1353
2054
|
var embeddings_exports = {};
|
|
1354
2055
|
__export(embeddings_exports, {
|
|
1355
2056
|
_configureEmbeddings: () => _configureEmbeddings,
|
|
2057
|
+
_fetchMetadataToken: () => _fetchMetadataToken,
|
|
2058
|
+
_resetVertexAuthCache: () => _resetVertexAuthCache,
|
|
1356
2059
|
generateEmbedding: () => generateEmbedding,
|
|
2060
|
+
generateEmbeddingForSpace: () => generateEmbeddingForSpace,
|
|
1357
2061
|
generateEmbeddings: () => generateEmbeddings,
|
|
1358
2062
|
generateQueryEmbedding: () => generateQueryEmbedding,
|
|
2063
|
+
generateQueryEmbeddingForSpace: () => generateQueryEmbeddingForSpace,
|
|
2064
|
+
generateVertexEmbedding: () => generateVertexEmbedding,
|
|
2065
|
+
generateVertexEmbeddings: () => generateVertexEmbeddings,
|
|
1359
2066
|
getCachedEmbedding: () => getCachedEmbedding,
|
|
1360
2067
|
isEmbeddingEnabled: () => isEmbeddingEnabled,
|
|
2068
|
+
isVertexConfigured: () => isVertexConfigured,
|
|
1361
2069
|
setCachedEmbedding: () => setCachedEmbedding
|
|
1362
2070
|
});
|
|
1363
2071
|
function getCachedEmbedding(text) {
|
|
@@ -1501,7 +2209,80 @@ async function generateEmbeddings(texts) {
|
|
|
1501
2209
|
return texts.map(() => null);
|
|
1502
2210
|
}
|
|
1503
2211
|
}
|
|
1504
|
-
|
|
2212
|
+
function _resetVertexAuthCache() {
|
|
2213
|
+
_vertexToken = null;
|
|
2214
|
+
}
|
|
2215
|
+
async function _fetchMetadataToken() {
|
|
2216
|
+
const res = await fetch(METADATA_TOKEN_URL, { headers: { "Metadata-Flavor": "Google" } });
|
|
2217
|
+
if (!res.ok) throw new Error(`Vertex metadata token fetch failed: ${res.status}`);
|
|
2218
|
+
const j = await res.json();
|
|
2219
|
+
return { token: j.access_token, expiresInSec: j.expires_in };
|
|
2220
|
+
}
|
|
2221
|
+
async function getVertexAccessToken() {
|
|
2222
|
+
const override = config.vertex.accessToken;
|
|
2223
|
+
if (override) return override;
|
|
2224
|
+
const now = Date.now();
|
|
2225
|
+
if (_vertexToken && _vertexToken.expiresAtMs > now + 6e4) return _vertexToken.token;
|
|
2226
|
+
const { token, expiresInSec } = await _fetchMetadataToken();
|
|
2227
|
+
_vertexToken = { token, expiresAtMs: now + expiresInSec * 1e3 };
|
|
2228
|
+
return token;
|
|
2229
|
+
}
|
|
2230
|
+
function vertexPredictUrl(model) {
|
|
2231
|
+
const { project, location } = config.vertex;
|
|
2232
|
+
return `https://${location}-aiplatform.googleapis.com/v1/projects/${project}/locations/${location}/publishers/google/models/${model}:predict`;
|
|
2233
|
+
}
|
|
2234
|
+
function isVertexConfigured() {
|
|
2235
|
+
return !!config.vertex.project;
|
|
2236
|
+
}
|
|
2237
|
+
async function generateVertexEmbeddings(texts, dimensions) {
|
|
2238
|
+
if (texts.length === 0) return [];
|
|
2239
|
+
const { project, model, dimensions: defaultDims } = config.vertex;
|
|
2240
|
+
if (!project) {
|
|
2241
|
+
log5.warn("Vertex embeddings requested but VERTEX_PROJECT is unset");
|
|
2242
|
+
return texts.map(() => null);
|
|
2243
|
+
}
|
|
2244
|
+
const outputDimensionality = dimensions ?? defaultDims;
|
|
2245
|
+
const startMs = Date.now();
|
|
2246
|
+
try {
|
|
2247
|
+
const token = await getVertexAccessToken();
|
|
2248
|
+
const res = await fetch(vertexPredictUrl(model), {
|
|
2249
|
+
method: "POST",
|
|
2250
|
+
headers: {
|
|
2251
|
+
"Content-Type": "application/json",
|
|
2252
|
+
"Authorization": `Bearer ${token}`
|
|
2253
|
+
},
|
|
2254
|
+
body: JSON.stringify({
|
|
2255
|
+
instances: texts.map((t) => ({ content: t.slice(0, 8e3) })),
|
|
2256
|
+
parameters: { outputDimensionality }
|
|
2257
|
+
})
|
|
2258
|
+
});
|
|
2259
|
+
if (!res.ok) {
|
|
2260
|
+
const errText = await res.text();
|
|
2261
|
+
log5.error({ status: res.status, body: errText.slice(0, 200), provider: "vertex" }, "Vertex embedding API error");
|
|
2262
|
+
return texts.map(() => null);
|
|
2263
|
+
}
|
|
2264
|
+
const data = await res.json();
|
|
2265
|
+
const preds = data.predictions ?? [];
|
|
2266
|
+
log5.debug({ provider: "vertex", model, count: texts.length, elapsed: Date.now() - startMs }, "Vertex embeddings generated");
|
|
2267
|
+
return texts.map((_, i) => preds[i]?.embeddings?.values ?? null);
|
|
2268
|
+
} catch (err) {
|
|
2269
|
+
log5.error({ err, provider: "vertex" }, "Vertex embedding generation failed");
|
|
2270
|
+
return texts.map(() => null);
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
async function generateVertexEmbedding(text, dimensions) {
|
|
2274
|
+
const [vec] = await generateVertexEmbeddings([text], dimensions);
|
|
2275
|
+
return vec ?? null;
|
|
2276
|
+
}
|
|
2277
|
+
async function generateEmbeddingForSpace(space, text) {
|
|
2278
|
+
if (space === "vertex") return generateVertexEmbedding(text);
|
|
2279
|
+
return generateEmbedding(text);
|
|
2280
|
+
}
|
|
2281
|
+
async function generateQueryEmbeddingForSpace(space, text) {
|
|
2282
|
+
if (space === "vertex") return generateVertexEmbedding(text);
|
|
2283
|
+
return generateQueryEmbedding(text);
|
|
2284
|
+
}
|
|
2285
|
+
var log5, PROVIDERS, _enabled, _overrideConfig, EMBEDDING_CACHE_MAX, EMBEDDING_CACHE_TTL_MS, _embeddingCache, _vertexToken, METADATA_TOKEN_URL;
|
|
1505
2286
|
var init_embeddings = __esm({
|
|
1506
2287
|
"packages/shared/src/core/embeddings.ts"() {
|
|
1507
2288
|
"use strict";
|
|
@@ -1534,6 +2315,8 @@ var init_embeddings = __esm({
|
|
|
1534
2315
|
EMBEDDING_CACHE_MAX = 200;
|
|
1535
2316
|
EMBEDDING_CACHE_TTL_MS = 30 * 60 * 1e3;
|
|
1536
2317
|
_embeddingCache = /* @__PURE__ */ new Map();
|
|
2318
|
+
_vertexToken = null;
|
|
2319
|
+
METADATA_TOKEN_URL = "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token";
|
|
1537
2320
|
}
|
|
1538
2321
|
});
|
|
1539
2322
|
|
|
@@ -1674,8 +2457,8 @@ function deriveRegistryPDA(authority) {
|
|
|
1674
2457
|
);
|
|
1675
2458
|
}
|
|
1676
2459
|
function anchorDiscriminator(name) {
|
|
1677
|
-
const { createHash:
|
|
1678
|
-
const hash =
|
|
2460
|
+
const { createHash: createHash3 } = require("crypto");
|
|
2461
|
+
const hash = createHash3("sha256").update(`global:${name}`).digest();
|
|
1679
2462
|
return hash.subarray(0, 8);
|
|
1680
2463
|
}
|
|
1681
2464
|
async function initializeRegistry() {
|
|
@@ -1840,6 +2623,39 @@ var init_solana_client = __esm({
|
|
|
1840
2623
|
}
|
|
1841
2624
|
});
|
|
1842
2625
|
|
|
2626
|
+
// packages/shared/src/core/memory-grounding.ts
|
|
2627
|
+
function byMemoryDateAsc(a, b) {
|
|
2628
|
+
const ta = a.created_at ? Date.parse(a.created_at) : NaN;
|
|
2629
|
+
const tb = b.created_at ? Date.parse(b.created_at) : NaN;
|
|
2630
|
+
if (Number.isNaN(ta) && Number.isNaN(tb)) return 0;
|
|
2631
|
+
if (Number.isNaN(ta)) return 1;
|
|
2632
|
+
if (Number.isNaN(tb)) return -1;
|
|
2633
|
+
return ta - tb;
|
|
2634
|
+
}
|
|
2635
|
+
function renderGroundedLine(m, suffix = "") {
|
|
2636
|
+
const date = m.created_at ? new Date(m.created_at).toISOString().slice(0, 10) : "";
|
|
2637
|
+
const stamp = date ? `[${date}] ` : "";
|
|
2638
|
+
const summary = (m.summary || "").trim();
|
|
2639
|
+
const content = (m.content || "").trim();
|
|
2640
|
+
let body;
|
|
2641
|
+
if (!content || content === summary) {
|
|
2642
|
+
body = summary || content;
|
|
2643
|
+
} else if (content.length <= GROUNDED_CONTENT_MAX) {
|
|
2644
|
+
body = summary ? `${summary} \u2014 ${content}` : content;
|
|
2645
|
+
} else {
|
|
2646
|
+
const slice = content.slice(0, GROUNDED_CONTENT_MAX);
|
|
2647
|
+
body = summary ? `${summary} \u2014 ${slice}\u2026` : `${slice}\u2026`;
|
|
2648
|
+
}
|
|
2649
|
+
return `- ${stamp}${body}${suffix}`;
|
|
2650
|
+
}
|
|
2651
|
+
var GROUNDED_CONTENT_MAX;
|
|
2652
|
+
var init_memory_grounding = __esm({
|
|
2653
|
+
"packages/shared/src/core/memory-grounding.ts"() {
|
|
2654
|
+
"use strict";
|
|
2655
|
+
GROUNDED_CONTENT_MAX = 600;
|
|
2656
|
+
}
|
|
2657
|
+
});
|
|
2658
|
+
|
|
1843
2659
|
// packages/shared/src/utils/format.ts
|
|
1844
2660
|
function timeAgo(dateStr) {
|
|
1845
2661
|
const ms = Date.now() - new Date(dateStr).getTime();
|
|
@@ -1890,6 +2706,338 @@ var init_utils = __esm({
|
|
|
1890
2706
|
}
|
|
1891
2707
|
});
|
|
1892
2708
|
|
|
2709
|
+
// packages/tokenization/src/content-hash.ts
|
|
2710
|
+
function stableStringify(value) {
|
|
2711
|
+
if (value === null) return "null";
|
|
2712
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
2713
|
+
return JSON.stringify(value);
|
|
2714
|
+
}
|
|
2715
|
+
if (Array.isArray(value)) {
|
|
2716
|
+
return "[" + value.map(stableStringify).join(",") + "]";
|
|
2717
|
+
}
|
|
2718
|
+
if (typeof value === "object") {
|
|
2719
|
+
const obj = value;
|
|
2720
|
+
const keys = Object.keys(obj).filter((k) => obj[k] !== void 0).sort();
|
|
2721
|
+
const pairs = keys.map((k) => JSON.stringify(k) + ":" + stableStringify(obj[k]));
|
|
2722
|
+
return "{" + pairs.join(",") + "}";
|
|
2723
|
+
}
|
|
2724
|
+
throw new Error(`stableStringify: unsupported value of type ${typeof value}`);
|
|
2725
|
+
}
|
|
2726
|
+
function normaliseString(s) {
|
|
2727
|
+
return s.normalize("NFC").trim();
|
|
2728
|
+
}
|
|
2729
|
+
function normaliseNullable(s) {
|
|
2730
|
+
return s === null ? null : normaliseString(s);
|
|
2731
|
+
}
|
|
2732
|
+
function normaliseTags(tags) {
|
|
2733
|
+
const trimmed = tags.map(normaliseString).filter((t) => t.length > 0);
|
|
2734
|
+
return Array.from(new Set(trimmed)).sort();
|
|
2735
|
+
}
|
|
2736
|
+
function canonicaliseMemory(input) {
|
|
2737
|
+
const normalised = {
|
|
2738
|
+
algorithm: HASH_ALGORITHM,
|
|
2739
|
+
content: normaliseString(input.content),
|
|
2740
|
+
created_at: input.created_at,
|
|
2741
|
+
memory_type: input.memory_type,
|
|
2742
|
+
owner_wallet: normaliseNullable(input.owner_wallet),
|
|
2743
|
+
related_user: normaliseNullable(input.related_user),
|
|
2744
|
+
related_wallet: normaliseNullable(input.related_wallet),
|
|
2745
|
+
source: normaliseNullable(input.source),
|
|
2746
|
+
tags: normaliseTags(input.tags)
|
|
2747
|
+
};
|
|
2748
|
+
return stableStringify(normalised);
|
|
2749
|
+
}
|
|
2750
|
+
function memoryContentHash(input) {
|
|
2751
|
+
return (0, import_node_crypto.createHash)("sha256").update(canonicaliseMemory(input), "utf8").digest("hex");
|
|
2752
|
+
}
|
|
2753
|
+
var import_node_crypto, HASH_ALGORITHM;
|
|
2754
|
+
var init_content_hash = __esm({
|
|
2755
|
+
"packages/tokenization/src/content-hash.ts"() {
|
|
2756
|
+
"use strict";
|
|
2757
|
+
import_node_crypto = require("node:crypto");
|
|
2758
|
+
HASH_ALGORITHM = "memory-hash-v1";
|
|
2759
|
+
}
|
|
2760
|
+
});
|
|
2761
|
+
|
|
2762
|
+
// packages/tokenization/src/pack-merkle.ts
|
|
2763
|
+
var LEAF_PREFIX, INNER_PREFIX;
|
|
2764
|
+
var init_pack_merkle = __esm({
|
|
2765
|
+
"packages/tokenization/src/pack-merkle.ts"() {
|
|
2766
|
+
"use strict";
|
|
2767
|
+
LEAF_PREFIX = Buffer.from([0]);
|
|
2768
|
+
INNER_PREFIX = Buffer.from([1]);
|
|
2769
|
+
}
|
|
2770
|
+
});
|
|
2771
|
+
|
|
2772
|
+
// packages/tokenization/src/mint-client.ts
|
|
2773
|
+
var init_mint_client = __esm({
|
|
2774
|
+
"packages/tokenization/src/mint-client.ts"() {
|
|
2775
|
+
"use strict";
|
|
2776
|
+
}
|
|
2777
|
+
});
|
|
2778
|
+
|
|
2779
|
+
// packages/tokenization/src/tokenize-memory.ts
|
|
2780
|
+
var init_tokenize_memory = __esm({
|
|
2781
|
+
"packages/tokenization/src/tokenize-memory.ts"() {
|
|
2782
|
+
"use strict";
|
|
2783
|
+
init_content_hash();
|
|
2784
|
+
}
|
|
2785
|
+
});
|
|
2786
|
+
|
|
2787
|
+
// packages/tokenization/src/tokenize-batch.ts
|
|
2788
|
+
var init_tokenize_batch = __esm({
|
|
2789
|
+
"packages/tokenization/src/tokenize-batch.ts"() {
|
|
2790
|
+
"use strict";
|
|
2791
|
+
init_content_hash();
|
|
2792
|
+
init_pack_merkle();
|
|
2793
|
+
}
|
|
2794
|
+
});
|
|
2795
|
+
|
|
2796
|
+
// packages/tokenization/src/tokenize-pack.ts
|
|
2797
|
+
var init_tokenize_pack = __esm({
|
|
2798
|
+
"packages/tokenization/src/tokenize-pack.ts"() {
|
|
2799
|
+
"use strict";
|
|
2800
|
+
init_pack_merkle();
|
|
2801
|
+
}
|
|
2802
|
+
});
|
|
2803
|
+
|
|
2804
|
+
// packages/tokenization/src/verify.ts
|
|
2805
|
+
var init_verify = __esm({
|
|
2806
|
+
"packages/tokenization/src/verify.ts"() {
|
|
2807
|
+
"use strict";
|
|
2808
|
+
init_pack_merkle();
|
|
2809
|
+
}
|
|
2810
|
+
});
|
|
2811
|
+
|
|
2812
|
+
// packages/tokenization/src/index.ts
|
|
2813
|
+
var init_src = __esm({
|
|
2814
|
+
"packages/tokenization/src/index.ts"() {
|
|
2815
|
+
"use strict";
|
|
2816
|
+
init_content_hash();
|
|
2817
|
+
init_pack_merkle();
|
|
2818
|
+
init_mint_client();
|
|
2819
|
+
init_tokenize_memory();
|
|
2820
|
+
init_tokenize_batch();
|
|
2821
|
+
init_tokenize_pack();
|
|
2822
|
+
init_verify();
|
|
2823
|
+
}
|
|
2824
|
+
});
|
|
2825
|
+
|
|
2826
|
+
// packages/shared/src/wiki-packs.ts
|
|
2827
|
+
function getPackManifest(id) {
|
|
2828
|
+
return ALL_PACK_MANIFESTS.find((p) => p.id === id);
|
|
2829
|
+
}
|
|
2830
|
+
function matchesKeyword(text, phrase) {
|
|
2831
|
+
if (!phrase) return false;
|
|
2832
|
+
const escaped = phrase.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2833
|
+
const re = new RegExp(`(?<![\\w-])${escaped}(?![\\w-])`, "i");
|
|
2834
|
+
return re.test(text);
|
|
2835
|
+
}
|
|
2836
|
+
function autoCategorizeTags(opts) {
|
|
2837
|
+
const haystack = [
|
|
2838
|
+
opts.content || "",
|
|
2839
|
+
opts.summary || "",
|
|
2840
|
+
...opts.existingTags || []
|
|
2841
|
+
].join(" ");
|
|
2842
|
+
const matched = new Set(opts.existingTags || []);
|
|
2843
|
+
const ids = /* @__PURE__ */ new Set([DEFAULT_PACK_ID, ...opts.installedPackIds]);
|
|
2844
|
+
for (const packId of ids) {
|
|
2845
|
+
const pack = getPackManifest(packId);
|
|
2846
|
+
if (!pack) continue;
|
|
2847
|
+
for (const rule of pack.rules) {
|
|
2848
|
+
if (matched.has(rule.topicId)) continue;
|
|
2849
|
+
const hasMatch = rule.keywords.some((kw) => matchesKeyword(haystack, kw));
|
|
2850
|
+
if (!hasMatch) continue;
|
|
2851
|
+
const isVetoed = (rule.excludeKeywords || []).some((kw) => matchesKeyword(haystack, kw));
|
|
2852
|
+
if (isVetoed) continue;
|
|
2853
|
+
matched.add(rule.topicId);
|
|
2854
|
+
}
|
|
2855
|
+
}
|
|
2856
|
+
return Array.from(matched);
|
|
2857
|
+
}
|
|
2858
|
+
function topicEmbedSources(installedPackIds) {
|
|
2859
|
+
const out = [];
|
|
2860
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2861
|
+
const ids = /* @__PURE__ */ new Set([DEFAULT_PACK_ID, ...installedPackIds]);
|
|
2862
|
+
for (const packId of ids) {
|
|
2863
|
+
const pack = getPackManifest(packId);
|
|
2864
|
+
if (!pack) continue;
|
|
2865
|
+
for (const t of pack.topics) {
|
|
2866
|
+
if (seen.has(t.id)) continue;
|
|
2867
|
+
seen.add(t.id);
|
|
2868
|
+
const sectionTitles = (t.sectionTemplates || []).map((s) => s.title).join(". ");
|
|
2869
|
+
const text = [t.name, t.summary, sectionTitles].filter(Boolean).join(" \u2014 ");
|
|
2870
|
+
out.push({ topicId: t.id, packId, text });
|
|
2871
|
+
}
|
|
2872
|
+
}
|
|
2873
|
+
return out;
|
|
2874
|
+
}
|
|
2875
|
+
function cosineSimilarity(a, b) {
|
|
2876
|
+
if (a.length !== b.length || a.length === 0) return 0;
|
|
2877
|
+
let dot = 0;
|
|
2878
|
+
let normA = 0;
|
|
2879
|
+
let normB = 0;
|
|
2880
|
+
for (let i = 0; i < a.length; i++) {
|
|
2881
|
+
dot += a[i] * b[i];
|
|
2882
|
+
normA += a[i] * a[i];
|
|
2883
|
+
normB += b[i] * b[i];
|
|
2884
|
+
}
|
|
2885
|
+
const denom = Math.sqrt(normA) * Math.sqrt(normB);
|
|
2886
|
+
return denom === 0 ? 0 : dot / denom;
|
|
2887
|
+
}
|
|
2888
|
+
function semanticTagMatches(memoryEmbedding, topicEmbeddings, threshold = EMBEDDING_TAG_THRESHOLD) {
|
|
2889
|
+
const matched = [];
|
|
2890
|
+
for (const { topicId, embedding } of topicEmbeddings) {
|
|
2891
|
+
const sim = cosineSimilarity(memoryEmbedding, embedding);
|
|
2892
|
+
if (sim >= threshold) matched.push(topicId);
|
|
2893
|
+
}
|
|
2894
|
+
return matched;
|
|
2895
|
+
}
|
|
2896
|
+
var WORKSPACE_PACK, COMPLIANCE_PACK, SALES_PACK, ALL_PACK_MANIFESTS, DEFAULT_PACK_ID, EMBEDDING_TAG_THRESHOLD;
|
|
2897
|
+
var init_wiki_packs = __esm({
|
|
2898
|
+
"packages/shared/src/wiki-packs.ts"() {
|
|
2899
|
+
"use strict";
|
|
2900
|
+
WORKSPACE_PACK = {
|
|
2901
|
+
id: "workspace",
|
|
2902
|
+
name: "Workspace Essentials",
|
|
2903
|
+
vendor: "Clude",
|
|
2904
|
+
vertical: "General",
|
|
2905
|
+
version: "1.0.0",
|
|
2906
|
+
installedByDefault: true,
|
|
2907
|
+
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.",
|
|
2908
|
+
topics: [
|
|
2909
|
+
{ id: "q3-roadmap", name: "Q3 Roadmap", cluster: "architecture", color: "#2244FF", summary: "What ships this quarter, what got cut, and the calls behind those decisions." },
|
|
2910
|
+
{ id: "auth-migration", name: "Auth Migration", cluster: "architecture", color: "#2244FF", summary: "Moving from session cookies to JWT \u2014 incidents, fixes, and the rollback plan." },
|
|
2911
|
+
{ id: "customer-research", name: "Customer Research", cluster: "research", color: "#F59E0B", summary: "Patterns surfaced across customer calls." },
|
|
2912
|
+
{ id: "pricing-model", name: "Pricing Model", cluster: "product", color: "#10B981", summary: "Per-token vs per-seat \u2014 the active disagreement." },
|
|
2913
|
+
{ id: "demo-day-prep", name: "Demo Day Prep", cluster: "product", color: "#10B981", summary: "What works in the live demo, what breaks." },
|
|
2914
|
+
{ id: "hiring", name: "Hiring Pipeline", cluster: "product", color: "#10B981", summary: "Candidates in flight and interview-signal patterns." },
|
|
2915
|
+
{ id: "team-process", name: "Team Process", cluster: "self", color: "#8B5CF6", summary: "What's working in how the team operates and what isn't." },
|
|
2916
|
+
{ id: "design-decisions", name: "Design Decisions", cluster: "research", color: "#F59E0B", summary: "API shapes, naming choices, and the reasons behind them." }
|
|
2917
|
+
],
|
|
2918
|
+
rules: [
|
|
2919
|
+
{ topicId: "q3-roadmap", keywords: ["roadmap", "q3", "quarter", "sprint plan", "priority"] },
|
|
2920
|
+
{ topicId: "auth-migration", keywords: ["auth", "jwt", "session cookie", "oauth", "sso", "token migration"] },
|
|
2921
|
+
{ topicId: "customer-research", keywords: ["customer call", "interview", "feedback", "user research", "persona"] },
|
|
2922
|
+
{ topicId: "pricing-model", keywords: ["pricing", "per-token", "per-seat", "subscription", "billing tier"] },
|
|
2923
|
+
{ topicId: "demo-day-prep", keywords: ["demo", "rehearsal", "investor", "pitch"] },
|
|
2924
|
+
{
|
|
2925
|
+
topicId: "hiring",
|
|
2926
|
+
keywords: ["candidate", "interview", "hire", "phone screen", "onsite", "offer"],
|
|
2927
|
+
// Suppress when "hire" is in the wrong context — got fired, rejected an offer,
|
|
2928
|
+
// hire-purchase, etc. The classic substring-match false positive.
|
|
2929
|
+
excludeKeywords: ["fired me", "i was fired", "hire purchase", "rejected my offer", "rejected the offer"]
|
|
2930
|
+
},
|
|
2931
|
+
{ topicId: "team-process", keywords: ["standup", "retro", "sprint", "1:1", "process", "team"] },
|
|
2932
|
+
{ topicId: "design-decisions", keywords: ["api design", "schema", "naming", "rfc", "design doc"] }
|
|
2933
|
+
]
|
|
2934
|
+
};
|
|
2935
|
+
COMPLIANCE_PACK = {
|
|
2936
|
+
id: "compliance",
|
|
2937
|
+
name: "Clude Compliance",
|
|
2938
|
+
vendor: "Clude",
|
|
2939
|
+
vertical: "Compliance",
|
|
2940
|
+
version: "1.0.0",
|
|
2941
|
+
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.",
|
|
2942
|
+
topics: [
|
|
2943
|
+
{
|
|
2944
|
+
id: "audit-logs",
|
|
2945
|
+
name: "Audit Logs",
|
|
2946
|
+
cluster: "architecture",
|
|
2947
|
+
color: "#0EA5E9",
|
|
2948
|
+
summary: "Every flagged agent action with attribution, timestamp, and a Solana-anchored hash a regulator can verify without trusting us.",
|
|
2949
|
+
sectionTemplates: [
|
|
2950
|
+
{ id: "recent-events", title: "Recent flagged events", kind: "overview" },
|
|
2951
|
+
{ id: "anomalies", title: "Anomalies & escalations", kind: "concern" },
|
|
2952
|
+
{ id: "retention", title: "Retention policy", kind: "decision" }
|
|
2953
|
+
]
|
|
2954
|
+
},
|
|
2955
|
+
{
|
|
2956
|
+
id: "evidence",
|
|
2957
|
+
name: "Evidence Collection",
|
|
2958
|
+
cluster: "product",
|
|
2959
|
+
color: "#06B6D4",
|
|
2960
|
+
summary: "Documentation gathered for SOC2, HIPAA, and ISO 27001 \u2014 what we have, what we still need, and what auditors have already accepted.",
|
|
2961
|
+
sectionTemplates: [
|
|
2962
|
+
{ id: "have", title: "What we have", kind: "highlight" },
|
|
2963
|
+
{ id: "gaps", title: "Gaps to fill", kind: "action" },
|
|
2964
|
+
{ id: "accepted", title: "What auditors accepted", kind: "decision" }
|
|
2965
|
+
]
|
|
2966
|
+
},
|
|
2967
|
+
{
|
|
2968
|
+
id: "regulator-asks",
|
|
2969
|
+
name: "Regulator Asks",
|
|
2970
|
+
cluster: "research",
|
|
2971
|
+
color: "#F59E0B",
|
|
2972
|
+
summary: "Specific requests from regulators \u2014 owner, deadline, response status. Nothing falls through the cracks.",
|
|
2973
|
+
sectionTemplates: [
|
|
2974
|
+
{ id: "open", title: "Open requests", kind: "action" },
|
|
2975
|
+
{ id: "responded", title: "Responded", kind: "decision" },
|
|
2976
|
+
{ id: "patterns", title: "Recurring patterns", kind: "highlight" }
|
|
2977
|
+
]
|
|
2978
|
+
},
|
|
2979
|
+
{
|
|
2980
|
+
id: "policy-decisions",
|
|
2981
|
+
name: "Policy Decisions",
|
|
2982
|
+
cluster: "self",
|
|
2983
|
+
color: "#8B5CF6",
|
|
2984
|
+
summary: "Calls made about what the agents are allowed to do \u2014 data handling, escalation triggers, redaction rules \u2014 each anchored on-chain.",
|
|
2985
|
+
sectionTemplates: [
|
|
2986
|
+
{ id: "standing", title: "Standing policy", kind: "decision" },
|
|
2987
|
+
{ id: "changes", title: "Recent changes", kind: "overview" },
|
|
2988
|
+
{ id: "open", title: "Open questions", kind: "question" }
|
|
2989
|
+
]
|
|
2990
|
+
},
|
|
2991
|
+
{
|
|
2992
|
+
id: "soc2-status",
|
|
2993
|
+
name: "SOC2 Status",
|
|
2994
|
+
cluster: "product",
|
|
2995
|
+
color: "#10B981",
|
|
2996
|
+
summary: "Where we are in the SOC2 audit cycle, what controls are passing, what still needs evidence.",
|
|
2997
|
+
sectionTemplates: [
|
|
2998
|
+
{ id: "controls", title: "Control status", kind: "overview" },
|
|
2999
|
+
{ id: "next", title: "Up next", kind: "action" }
|
|
3000
|
+
]
|
|
3001
|
+
}
|
|
3002
|
+
],
|
|
3003
|
+
rules: [
|
|
3004
|
+
{ topicId: "audit-logs", keywords: ["audit", "audit log", "trail", "attribution", "flagged"] },
|
|
3005
|
+
{ topicId: "evidence", keywords: ["evidence", "soc2", "hipaa", "iso 27001", "compliance evidence", "control"] },
|
|
3006
|
+
{ topicId: "regulator-asks", keywords: ["regulator", "subpoena", "compliance request", "data request"] },
|
|
3007
|
+
{ topicId: "policy-decisions", keywords: ["policy", "redaction", "pii", "escalation rule", "data handling"] },
|
|
3008
|
+
{ topicId: "soc2-status", keywords: ["soc2", "audit cycle", "control test"] }
|
|
3009
|
+
]
|
|
3010
|
+
};
|
|
3011
|
+
SALES_PACK = {
|
|
3012
|
+
id: "sales",
|
|
3013
|
+
name: "Sales Intelligence",
|
|
3014
|
+
vendor: "Clude",
|
|
3015
|
+
vertical: "Sales",
|
|
3016
|
+
version: "1.0.0",
|
|
3017
|
+
description: "Auto-organises pipeline conversations, deal blockers, objection patterns, and post-call follow-ups. Built for AEs who hate CRM data entry.",
|
|
3018
|
+
topics: [
|
|
3019
|
+
{ id: "pipeline", name: "Pipeline", cluster: "product", color: "#10B981", summary: "Active deals, stages, blockers." },
|
|
3020
|
+
{ id: "objections", name: "Objections", cluster: "research", color: "#F59E0B", summary: "Patterns across discovery calls." },
|
|
3021
|
+
{ id: "follow-ups", name: "Follow-ups", cluster: "product", color: "#10B981", summary: "What you committed to send and to whom." },
|
|
3022
|
+
{ id: "champions", name: "Champions", cluster: "self", color: "#8B5CF6", summary: "Who is championing your product internally at each account." }
|
|
3023
|
+
],
|
|
3024
|
+
rules: [
|
|
3025
|
+
{ topicId: "pipeline", keywords: ["deal", "opportunity", "stage", "close date", "mql", "sql"] },
|
|
3026
|
+
{ topicId: "objections", keywords: ["objection", "concern raised", "pushback", "competitor mentioned"] },
|
|
3027
|
+
{ topicId: "follow-ups", keywords: ["follow up", "send", "next step", "committed to"] },
|
|
3028
|
+
{ topicId: "champions", keywords: ["champion", "sponsor", "advocate", "introduced me to"] }
|
|
3029
|
+
]
|
|
3030
|
+
};
|
|
3031
|
+
ALL_PACK_MANIFESTS = [
|
|
3032
|
+
WORKSPACE_PACK,
|
|
3033
|
+
COMPLIANCE_PACK,
|
|
3034
|
+
SALES_PACK
|
|
3035
|
+
];
|
|
3036
|
+
DEFAULT_PACK_ID = "workspace";
|
|
3037
|
+
EMBEDDING_TAG_THRESHOLD = 0.78;
|
|
3038
|
+
}
|
|
3039
|
+
});
|
|
3040
|
+
|
|
1893
3041
|
// packages/brain/src/experimental/config.ts
|
|
1894
3042
|
function envBool(key, fallback) {
|
|
1895
3043
|
const val = process.env[key];
|
|
@@ -1975,26 +3123,509 @@ var init_bm25_search = __esm({
|
|
|
1975
3123
|
}
|
|
1976
3124
|
});
|
|
1977
3125
|
|
|
1978
|
-
// packages/
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
}
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
throw new Error("Encryption requires a 64-byte Ed25519 secret key");
|
|
3126
|
+
// packages/brain/src/memory/content-tokens.ts
|
|
3127
|
+
async function setContentTokens(db, memoryId, plaintext) {
|
|
3128
|
+
try {
|
|
3129
|
+
const { error } = await db.rpc("set_memory_content_tokens", {
|
|
3130
|
+
p_memory_id: memoryId,
|
|
3131
|
+
p_text: plaintext
|
|
3132
|
+
});
|
|
3133
|
+
if (error) {
|
|
3134
|
+
log8.warn({ memoryId, error: error.message }, "set_memory_content_tokens failed (keyword recall degraded)");
|
|
3135
|
+
}
|
|
3136
|
+
} catch (err) {
|
|
3137
|
+
log8.warn({ memoryId, err }, "set_memory_content_tokens threw (keyword recall degraded)");
|
|
1991
3138
|
}
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
3139
|
+
}
|
|
3140
|
+
var log8;
|
|
3141
|
+
var init_content_tokens = __esm({
|
|
3142
|
+
"packages/brain/src/memory/content-tokens.ts"() {
|
|
3143
|
+
"use strict";
|
|
3144
|
+
init_logger();
|
|
3145
|
+
log8 = createChildLogger("content-tokens");
|
|
3146
|
+
}
|
|
3147
|
+
});
|
|
3148
|
+
|
|
3149
|
+
// packages/brain/src/memory/reconcile-router.ts
|
|
3150
|
+
function shortReason(x) {
|
|
3151
|
+
return typeof x === "string" ? x.slice(0, 200) : "";
|
|
3152
|
+
}
|
|
3153
|
+
function parseRouterReply(raw, candidateIds, model) {
|
|
3154
|
+
const fail = (reason) => ({ op: "add", targetId: null, reason, model });
|
|
3155
|
+
let obj;
|
|
3156
|
+
try {
|
|
3157
|
+
obj = JSON.parse(raw);
|
|
3158
|
+
} catch {
|
|
3159
|
+
const m = raw.match(/\{[\s\S]*\}/);
|
|
3160
|
+
if (!m) return fail("router_parse_error");
|
|
3161
|
+
try {
|
|
3162
|
+
obj = JSON.parse(m[0]);
|
|
3163
|
+
} catch {
|
|
3164
|
+
return fail("router_parse_error");
|
|
3165
|
+
}
|
|
3166
|
+
}
|
|
3167
|
+
if (obj === null || typeof obj !== "object" || Array.isArray(obj)) return fail("router_invalid_shape");
|
|
3168
|
+
const rec = obj;
|
|
3169
|
+
const op = rec.op;
|
|
3170
|
+
if (op !== "add" && op !== "update" && op !== "noop") return fail("router_invalid_op");
|
|
3171
|
+
if (op === "add") return { op: "add", targetId: null, reason: shortReason(rec.reason), model };
|
|
3172
|
+
const rawTarget = rec.targetId ?? rec.target_id;
|
|
3173
|
+
const targetId = typeof rawTarget === "number" ? rawTarget : Number(rawTarget);
|
|
3174
|
+
if (!Number.isInteger(targetId) || !candidateIds.has(targetId)) return fail("router_invalid_target");
|
|
3175
|
+
return { op, targetId, reason: shortReason(rec.reason), model };
|
|
3176
|
+
}
|
|
3177
|
+
function buildPayload(newFact, candidates) {
|
|
3178
|
+
const lines = candidates.map(
|
|
3179
|
+
(c) => `[id ${c.id}] date: ${c.eventDate ?? "unknown"} \u2014 ${c.summary}`
|
|
3180
|
+
);
|
|
3181
|
+
return `NEW fact:
|
|
3182
|
+
date: ${newFact.eventDate ?? "unknown"} \u2014 ${newFact.summary}
|
|
3183
|
+
|
|
3184
|
+
Candidate facts:
|
|
3185
|
+
${lines.join("\n")}`;
|
|
3186
|
+
}
|
|
3187
|
+
function isRouterConfigured() {
|
|
3188
|
+
return config.memory.reconcileRouter && !!config.memory.reconcileModel && isOpenRouterEnabled();
|
|
3189
|
+
}
|
|
3190
|
+
function utcDay() {
|
|
3191
|
+
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
3192
|
+
}
|
|
3193
|
+
function rollDay() {
|
|
3194
|
+
const d = utcDay();
|
|
3195
|
+
if (d !== budgetDay) {
|
|
3196
|
+
budgetDay = d;
|
|
3197
|
+
budgetByOwner.clear();
|
|
3198
|
+
}
|
|
3199
|
+
}
|
|
3200
|
+
function budgetKey(owner) {
|
|
3201
|
+
return owner ?? "__BOT_OWN__";
|
|
3202
|
+
}
|
|
3203
|
+
function routerBudgetAvailable(owner) {
|
|
3204
|
+
rollDay();
|
|
3205
|
+
return (budgetByOwner.get(budgetKey(owner)) ?? 0) < config.memory.reconcileBudget;
|
|
3206
|
+
}
|
|
3207
|
+
function noteRouterCall(owner) {
|
|
3208
|
+
rollDay();
|
|
3209
|
+
const k = budgetKey(owner);
|
|
3210
|
+
budgetByOwner.set(k, (budgetByOwner.get(k) ?? 0) + 1);
|
|
3211
|
+
}
|
|
3212
|
+
async function routeReconcile(newFact, candidates) {
|
|
3213
|
+
const model = config.memory.reconcileModel;
|
|
3214
|
+
const candidateIds = new Set(candidates.map((c) => c.id));
|
|
3215
|
+
try {
|
|
3216
|
+
const raw = await generateOpenRouterResponse({
|
|
3217
|
+
systemPrompt: ROUTER_SYSTEM,
|
|
3218
|
+
messages: [{ role: "user", content: buildPayload(newFact, candidates) }],
|
|
3219
|
+
model,
|
|
3220
|
+
// explicit — never cognitiveFunction (which overrides model with the fast llama slot)
|
|
3221
|
+
maxTokens: 150,
|
|
3222
|
+
temperature: 0
|
|
3223
|
+
});
|
|
3224
|
+
return parseRouterReply(raw, candidateIds, model);
|
|
3225
|
+
} catch (err) {
|
|
3226
|
+
log9.warn({ err: err?.message }, "reconcile router call failed \u2014 defaulting to add");
|
|
3227
|
+
return { op: "add", targetId: null, reason: "router_error", model };
|
|
3228
|
+
}
|
|
3229
|
+
}
|
|
3230
|
+
var log9, ROUTER_SYSTEM, budgetDay, budgetByOwner;
|
|
3231
|
+
var init_reconcile_router = __esm({
|
|
3232
|
+
"packages/brain/src/memory/reconcile-router.ts"() {
|
|
3233
|
+
"use strict";
|
|
3234
|
+
init_config();
|
|
3235
|
+
init_logger();
|
|
3236
|
+
init_openrouter_client();
|
|
3237
|
+
log9 = createChildLogger("reconcile-router");
|
|
3238
|
+
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.';
|
|
3239
|
+
budgetDay = "";
|
|
3240
|
+
budgetByOwner = /* @__PURE__ */ new Map();
|
|
3241
|
+
}
|
|
3242
|
+
});
|
|
3243
|
+
|
|
3244
|
+
// packages/brain/src/memory/reconcile-shadow.ts
|
|
3245
|
+
function logReconcileDegradeOnce(err, memoryId) {
|
|
3246
|
+
if (reconcileDegradeLogged) return;
|
|
3247
|
+
reconcileDegradeLogged = true;
|
|
3248
|
+
const e = err;
|
|
3249
|
+
log10.warn(
|
|
3250
|
+
{ code: e?.code, message: e?.message, memoryId },
|
|
3251
|
+
"Reconcile shadow degraded (migration 046 unapplied?) \u2014 skipping; write unaffected"
|
|
3252
|
+
);
|
|
3253
|
+
}
|
|
3254
|
+
function gateVersion() {
|
|
3255
|
+
const { reconcileFloor: f, reconcileLo: lo, reconcileHi: hi } = config.memory;
|
|
3256
|
+
return `c1-shadow-v1:floor=${f}:lo=${lo}:hi=${hi}`;
|
|
3257
|
+
}
|
|
3258
|
+
async function insertShadowRow(row) {
|
|
3259
|
+
try {
|
|
3260
|
+
const { error } = await getDb().from("memory_reconciliation_log").insert(row);
|
|
3261
|
+
if (error) logReconcileDegradeOnce(error, row.memory_id);
|
|
3262
|
+
} catch (err) {
|
|
3263
|
+
logReconcileDegradeOnce(err, row.memory_id);
|
|
3264
|
+
}
|
|
3265
|
+
}
|
|
3266
|
+
async function runReconcileShadow(memoryId, scope) {
|
|
3267
|
+
if (process.env.BENCH_MODE === "true") return;
|
|
3268
|
+
const ownerWalletForRow = scope === SCOPE_BOT_OWN ? null : scope;
|
|
3269
|
+
const gv = gateVersion();
|
|
3270
|
+
const db = getDb();
|
|
3271
|
+
const skip = (reason) => insertShadowRow({
|
|
3272
|
+
memory_id: memoryId,
|
|
3273
|
+
owner_wallet: ownerWalletForRow,
|
|
3274
|
+
mode: "shadow",
|
|
3275
|
+
proposed_op: "skip",
|
|
3276
|
+
target_memory_id: null,
|
|
3277
|
+
max_cosine: null,
|
|
3278
|
+
band: "none",
|
|
3279
|
+
router_used: false,
|
|
3280
|
+
fact_key: null,
|
|
3281
|
+
reason,
|
|
3282
|
+
gate_version: gv
|
|
3283
|
+
});
|
|
3284
|
+
const add = (reason, maxCosine2, band2) => insertShadowRow({
|
|
3285
|
+
memory_id: memoryId,
|
|
3286
|
+
owner_wallet: ownerWalletForRow,
|
|
3287
|
+
mode: "shadow",
|
|
3288
|
+
proposed_op: "add",
|
|
3289
|
+
target_memory_id: null,
|
|
3290
|
+
max_cosine: maxCosine2,
|
|
3291
|
+
band: band2,
|
|
3292
|
+
router_used: false,
|
|
3293
|
+
fact_key: null,
|
|
3294
|
+
reason,
|
|
3295
|
+
gate_version: gv
|
|
3296
|
+
});
|
|
3297
|
+
let embedding;
|
|
3298
|
+
let newSummary = "";
|
|
3299
|
+
let newEventDate = null;
|
|
3300
|
+
try {
|
|
3301
|
+
const { data, error } = await db.from("memories").select("embedding, summary, event_date, created_at").eq("id", memoryId).maybeSingle();
|
|
3302
|
+
if (error) {
|
|
3303
|
+
logReconcileDegradeOnce(error, memoryId);
|
|
3304
|
+
return;
|
|
3305
|
+
}
|
|
3306
|
+
const row = data;
|
|
3307
|
+
embedding = row?.embedding ?? null;
|
|
3308
|
+
newSummary = row?.summary ?? "";
|
|
3309
|
+
newEventDate = row?.event_date ?? row?.created_at ?? null;
|
|
3310
|
+
} catch (err) {
|
|
3311
|
+
logReconcileDegradeOnce(err, memoryId);
|
|
3312
|
+
return;
|
|
3313
|
+
}
|
|
3314
|
+
if (!embedding) {
|
|
3315
|
+
await skip("no_embedding");
|
|
3316
|
+
return;
|
|
3317
|
+
}
|
|
3318
|
+
const queryEmbedding = typeof embedding === "string" ? embedding : JSON.stringify(embedding);
|
|
3319
|
+
let candidates;
|
|
3320
|
+
try {
|
|
3321
|
+
const { data, error } = await db.rpc("match_memories_temporal", {
|
|
3322
|
+
query_embedding: queryEmbedding,
|
|
3323
|
+
match_threshold: config.memory.reconcileFloor,
|
|
3324
|
+
match_count: K_CANDIDATES,
|
|
3325
|
+
start_date: null,
|
|
3326
|
+
end_date: null,
|
|
3327
|
+
filter_types: null,
|
|
3328
|
+
filter_user: null,
|
|
3329
|
+
min_decay: 0,
|
|
3330
|
+
filter_owner: scope,
|
|
3331
|
+
filter_tags: null
|
|
3332
|
+
});
|
|
3333
|
+
if (error) {
|
|
3334
|
+
logReconcileDegradeOnce(error, memoryId);
|
|
3335
|
+
return;
|
|
3336
|
+
}
|
|
3337
|
+
candidates = data ?? [];
|
|
3338
|
+
} catch (err) {
|
|
3339
|
+
logReconcileDegradeOnce(err, memoryId);
|
|
3340
|
+
return;
|
|
3341
|
+
}
|
|
3342
|
+
candidates = candidates.filter((c) => c.id !== memoryId);
|
|
3343
|
+
if (candidates.length === 0) {
|
|
3344
|
+
await add("no_candidate", null, "none");
|
|
3345
|
+
return;
|
|
3346
|
+
}
|
|
3347
|
+
const ids = candidates.map((c) => c.id);
|
|
3348
|
+
let validIds;
|
|
3349
|
+
const hydratedById = /* @__PURE__ */ new Map();
|
|
3350
|
+
try {
|
|
3351
|
+
const base = db.from("memories").select("id, owner_wallet, invalid_at, summary, event_date, created_at").in("id", ids);
|
|
3352
|
+
const scoped = scope === SCOPE_BOT_OWN ? base.is("owner_wallet", null) : base.eq("owner_wallet", scope);
|
|
3353
|
+
const { data, error } = await scoped;
|
|
3354
|
+
if (error) {
|
|
3355
|
+
logReconcileDegradeOnce(error, memoryId);
|
|
3356
|
+
return;
|
|
3357
|
+
}
|
|
3358
|
+
const rows = data ?? [];
|
|
3359
|
+
const valid = rows.filter((r) => r.invalid_at == null).filter((r) => scope === SCOPE_BOT_OWN ? r.owner_wallet == null : r.owner_wallet === scope);
|
|
3360
|
+
validIds = new Set(valid.map((r) => r.id));
|
|
3361
|
+
for (const r of valid) hydratedById.set(r.id, { summary: r.summary ?? "", eventDate: r.event_date ?? r.created_at ?? null });
|
|
3362
|
+
} catch (err) {
|
|
3363
|
+
logReconcileDegradeOnce(err, memoryId);
|
|
3364
|
+
return;
|
|
3365
|
+
}
|
|
3366
|
+
const surviving = candidates.filter((c) => validIds.has(c.id));
|
|
3367
|
+
if (surviving.length === 0) {
|
|
3368
|
+
await add("no_valid_candidate", null, "none");
|
|
3369
|
+
return;
|
|
3370
|
+
}
|
|
3371
|
+
const maxCosine = surviving[0].similarity;
|
|
3372
|
+
const { reconcileLo: LO, reconcileHi: HI } = config.memory;
|
|
3373
|
+
if (maxCosine < LO) {
|
|
3374
|
+
await add("below_lo", maxCosine, "lo");
|
|
3375
|
+
return;
|
|
3376
|
+
}
|
|
3377
|
+
const band = maxCosine >= HI ? "hi" : "mid";
|
|
3378
|
+
const top = surviving[0];
|
|
3379
|
+
if (isRouterConfigured() && routerBudgetAvailable(ownerWalletForRow)) {
|
|
3380
|
+
noteRouterCall(ownerWalletForRow);
|
|
3381
|
+
const routerCandidates = surviving.map((c) => ({
|
|
3382
|
+
id: c.id,
|
|
3383
|
+
summary: hydratedById.get(c.id)?.summary ?? "",
|
|
3384
|
+
eventDate: hydratedById.get(c.id)?.eventDate ?? null
|
|
3385
|
+
}));
|
|
3386
|
+
const decision = await routeReconcile({ summary: newSummary, eventDate: newEventDate }, routerCandidates);
|
|
3387
|
+
await insertShadowRow({
|
|
3388
|
+
memory_id: memoryId,
|
|
3389
|
+
owner_wallet: ownerWalletForRow,
|
|
3390
|
+
mode: "shadow",
|
|
3391
|
+
proposed_op: decision.op,
|
|
3392
|
+
target_memory_id: decision.op === "add" ? null : decision.targetId,
|
|
3393
|
+
max_cosine: maxCosine,
|
|
3394
|
+
band,
|
|
3395
|
+
router_used: true,
|
|
3396
|
+
router_model: decision.model,
|
|
3397
|
+
fact_key: null,
|
|
3398
|
+
reason: decision.reason || "router",
|
|
3399
|
+
gate_version: gv
|
|
3400
|
+
});
|
|
3401
|
+
return;
|
|
3402
|
+
}
|
|
3403
|
+
await insertShadowRow({
|
|
3404
|
+
memory_id: memoryId,
|
|
3405
|
+
owner_wallet: ownerWalletForRow,
|
|
3406
|
+
mode: "shadow",
|
|
3407
|
+
proposed_op: "needs_router",
|
|
3408
|
+
target_memory_id: top.id,
|
|
3409
|
+
max_cosine: maxCosine,
|
|
3410
|
+
band,
|
|
3411
|
+
router_used: false,
|
|
3412
|
+
fact_key: null,
|
|
3413
|
+
reason: "similar_prior_fact",
|
|
3414
|
+
gate_version: gv
|
|
3415
|
+
});
|
|
3416
|
+
}
|
|
3417
|
+
var log10, SCOPE_BOT_OWN, K_CANDIDATES, reconcileDegradeLogged;
|
|
3418
|
+
var init_reconcile_shadow = __esm({
|
|
3419
|
+
"packages/brain/src/memory/reconcile-shadow.ts"() {
|
|
3420
|
+
"use strict";
|
|
3421
|
+
init_database();
|
|
3422
|
+
init_config();
|
|
3423
|
+
init_logger();
|
|
3424
|
+
init_reconcile_router();
|
|
3425
|
+
log10 = createChildLogger("reconcile-shadow");
|
|
3426
|
+
SCOPE_BOT_OWN = "__BOT_OWN__";
|
|
3427
|
+
K_CANDIDATES = 6;
|
|
3428
|
+
reconcileDegradeLogged = false;
|
|
3429
|
+
}
|
|
3430
|
+
});
|
|
3431
|
+
|
|
3432
|
+
// packages/shared/src/core/owner-key-constants.ts
|
|
3433
|
+
var HKDF_SALT, HKDF_INFO;
|
|
3434
|
+
var init_owner_key_constants = __esm({
|
|
3435
|
+
"packages/shared/src/core/owner-key-constants.ts"() {
|
|
3436
|
+
"use strict";
|
|
3437
|
+
HKDF_SALT = "clude-cortex-v1";
|
|
3438
|
+
HKDF_INFO = "memory-encryption-x25519-v2";
|
|
3439
|
+
}
|
|
3440
|
+
});
|
|
3441
|
+
|
|
3442
|
+
// packages/shared/src/core/memory-envelope.ts
|
|
3443
|
+
function generateDek() {
|
|
3444
|
+
return import_tweetnacl2.default.randomBytes(import_tweetnacl2.default.secretbox.keyLength);
|
|
3445
|
+
}
|
|
3446
|
+
function encryptField(plaintext, dek) {
|
|
3447
|
+
const nonce = import_tweetnacl2.default.randomBytes(NONCE_LENGTH);
|
|
3448
|
+
const message = new TextEncoder().encode(plaintext);
|
|
3449
|
+
const ct = import_tweetnacl2.default.secretbox(message, nonce, dek);
|
|
3450
|
+
const combined = new Uint8Array(NONCE_LENGTH + ct.length);
|
|
3451
|
+
combined.set(nonce, 0);
|
|
3452
|
+
combined.set(ct, NONCE_LENGTH);
|
|
3453
|
+
return Buffer.from(combined).toString("base64");
|
|
3454
|
+
}
|
|
3455
|
+
function decryptField(encrypted, dek) {
|
|
3456
|
+
try {
|
|
3457
|
+
const combined = Buffer.from(encrypted, "base64");
|
|
3458
|
+
if (combined.length < NONCE_LENGTH + 1) return null;
|
|
3459
|
+
const nonce = combined.subarray(0, NONCE_LENGTH);
|
|
3460
|
+
const ct = combined.subarray(NONCE_LENGTH);
|
|
3461
|
+
const pt = import_tweetnacl2.default.secretbox.open(ct, nonce, dek);
|
|
3462
|
+
if (!pt) return null;
|
|
3463
|
+
return new TextDecoder().decode(pt);
|
|
3464
|
+
} catch {
|
|
3465
|
+
return null;
|
|
3466
|
+
}
|
|
3467
|
+
}
|
|
3468
|
+
function wrapDek(dek, recipientPubkey) {
|
|
3469
|
+
const ephemeral = import_tweetnacl2.default.box.keyPair();
|
|
3470
|
+
const nonce = import_tweetnacl2.default.randomBytes(BOX_NONCE_LENGTH);
|
|
3471
|
+
const boxed = import_tweetnacl2.default.box(dek, nonce, recipientPubkey, ephemeral.secretKey);
|
|
3472
|
+
const combined = new Uint8Array(BOX_NONCE_LENGTH + boxed.length);
|
|
3473
|
+
combined.set(nonce, 0);
|
|
3474
|
+
combined.set(boxed, BOX_NONCE_LENGTH);
|
|
3475
|
+
return {
|
|
3476
|
+
wrapped: Buffer.from(combined).toString("base64"),
|
|
3477
|
+
wrapPubkey: Buffer.from(ephemeral.publicKey).toString("base64")
|
|
3478
|
+
};
|
|
3479
|
+
}
|
|
3480
|
+
function unwrapDek(wrapped, wrapPubkey, recipientSecretKey) {
|
|
3481
|
+
try {
|
|
3482
|
+
const combined = Buffer.from(wrapped, "base64");
|
|
3483
|
+
if (combined.length < BOX_NONCE_LENGTH + 1) return null;
|
|
3484
|
+
const nonce = combined.subarray(0, BOX_NONCE_LENGTH);
|
|
3485
|
+
const boxed = combined.subarray(BOX_NONCE_LENGTH);
|
|
3486
|
+
const ephemeralPub = Buffer.from(wrapPubkey, "base64");
|
|
3487
|
+
if (ephemeralPub.length !== import_tweetnacl2.default.box.publicKeyLength) return null;
|
|
3488
|
+
const out = import_tweetnacl2.default.box.open(boxed, nonce, ephemeralPub, recipientSecretKey);
|
|
3489
|
+
return out ?? null;
|
|
3490
|
+
} catch {
|
|
3491
|
+
return null;
|
|
3492
|
+
}
|
|
3493
|
+
}
|
|
3494
|
+
async function deriveOwnerEncryptionKeypair(signature) {
|
|
3495
|
+
const seed = await hkdfAsync("sha256", signature, HKDF_SALT, HKDF_INFO, 32);
|
|
3496
|
+
return import_tweetnacl2.default.box.keyPair.fromSecretKey(new Uint8Array(seed));
|
|
3497
|
+
}
|
|
3498
|
+
var import_tweetnacl2, import_crypto, import_util, hkdfAsync, NONCE_LENGTH, BOX_NONCE_LENGTH;
|
|
3499
|
+
var init_memory_envelope = __esm({
|
|
3500
|
+
"packages/shared/src/core/memory-envelope.ts"() {
|
|
3501
|
+
"use strict";
|
|
3502
|
+
import_tweetnacl2 = __toESM(require("tweetnacl"));
|
|
3503
|
+
import_crypto = require("crypto");
|
|
3504
|
+
import_util = require("util");
|
|
3505
|
+
init_owner_key_constants();
|
|
3506
|
+
hkdfAsync = (0, import_util.promisify)(import_crypto.hkdf);
|
|
3507
|
+
NONCE_LENGTH = import_tweetnacl2.default.secretbox.nonceLength;
|
|
3508
|
+
BOX_NONCE_LENGTH = import_tweetnacl2.default.box.nonceLength;
|
|
3509
|
+
}
|
|
3510
|
+
});
|
|
3511
|
+
|
|
3512
|
+
// packages/shared/src/core/encryption-keys.ts
|
|
3513
|
+
function loadProviderKeypair() {
|
|
3514
|
+
if (providerKeypair) return providerKeypair;
|
|
3515
|
+
const b64 = process.env.PMP_PROVIDER_ENC_SECRET;
|
|
3516
|
+
if (!b64) throw new Error("PMP_PROVIDER_ENC_SECRET is not set");
|
|
3517
|
+
const secret = Buffer.from(b64, "base64");
|
|
3518
|
+
if (secret.length !== import_tweetnacl3.default.box.secretKeyLength) {
|
|
3519
|
+
throw new Error(`PMP_PROVIDER_ENC_SECRET must decode to 32 bytes (got ${secret.length})`);
|
|
3520
|
+
}
|
|
3521
|
+
providerKeypair = import_tweetnacl3.default.box.keyPair.fromSecretKey(new Uint8Array(secret));
|
|
3522
|
+
log11.info("Provider encryption keypair loaded");
|
|
3523
|
+
return providerKeypair;
|
|
3524
|
+
}
|
|
3525
|
+
async function getOwnerEncryptionKey(ownerWallet) {
|
|
3526
|
+
const { data, error } = await getDb().from("encryption_keys").select("x25519_pubkey, verifier_ct").eq("owner_wallet", ownerWallet).maybeSingle();
|
|
3527
|
+
if (error) throw new Error(`getOwnerEncryptionKey failed: ${error.message}`);
|
|
3528
|
+
if (!data) return null;
|
|
3529
|
+
return { x25519_pubkey: data.x25519_pubkey, verifier_ct: data.verifier_ct };
|
|
3530
|
+
}
|
|
3531
|
+
var import_tweetnacl3, log11, providerKeypair;
|
|
3532
|
+
var init_encryption_keys = __esm({
|
|
3533
|
+
"packages/shared/src/core/encryption-keys.ts"() {
|
|
3534
|
+
"use strict";
|
|
3535
|
+
import_tweetnacl3 = __toESM(require("tweetnacl"));
|
|
3536
|
+
init_database();
|
|
3537
|
+
init_logger();
|
|
3538
|
+
log11 = createChildLogger("encryption-keys");
|
|
3539
|
+
providerKeypair = null;
|
|
3540
|
+
}
|
|
3541
|
+
});
|
|
3542
|
+
|
|
3543
|
+
// packages/brain/src/memory/memory-encryption.ts
|
|
3544
|
+
function isMemoryEncryptionEnabled() {
|
|
3545
|
+
if (process.env.PMP_ENCRYPTION_ENABLED !== "true") return false;
|
|
3546
|
+
try {
|
|
3547
|
+
loadProviderKeypair();
|
|
3548
|
+
return true;
|
|
3549
|
+
} catch {
|
|
3550
|
+
return false;
|
|
3551
|
+
}
|
|
3552
|
+
}
|
|
3553
|
+
async function getBotOwnerKeypair() {
|
|
3554
|
+
if (botKeypair) return botKeypair;
|
|
3555
|
+
const wallet = getBotWallet();
|
|
3556
|
+
if (!wallet) return null;
|
|
3557
|
+
const sig = import_tweetnacl4.default.sign.detached(new TextEncoder().encode(DERIVE_MESSAGE), wallet.secretKey);
|
|
3558
|
+
botKeypair = await deriveOwnerEncryptionKeypair(sig);
|
|
3559
|
+
return botKeypair;
|
|
3560
|
+
}
|
|
3561
|
+
async function resolveOwnerPublicKey(ownerWallet) {
|
|
3562
|
+
const botWallet2 = getBotWallet();
|
|
3563
|
+
const botAddr = botWallet2?.publicKey.toBase58();
|
|
3564
|
+
if (!ownerWallet || botAddr && ownerWallet === botAddr) {
|
|
3565
|
+
const kp = await getBotOwnerKeypair();
|
|
3566
|
+
return kp ? Buffer.from(kp.publicKey).toString("base64") : null;
|
|
3567
|
+
}
|
|
3568
|
+
const row = await getOwnerEncryptionKey(ownerWallet);
|
|
3569
|
+
return row?.x25519_pubkey ?? null;
|
|
3570
|
+
}
|
|
3571
|
+
async function encryptForStorage(plaintext, ownerWallet) {
|
|
3572
|
+
if (!isMemoryEncryptionEnabled()) return null;
|
|
3573
|
+
const ownerPubkey = await resolveOwnerPublicKey(ownerWallet);
|
|
3574
|
+
if (!ownerPubkey) {
|
|
3575
|
+
log12.warn("No owner encryption key resolvable \u2014 storing plaintext (encrypted:false)");
|
|
3576
|
+
return null;
|
|
3577
|
+
}
|
|
3578
|
+
const dek = generateDek();
|
|
3579
|
+
const ciphertext = encryptField(plaintext, dek);
|
|
3580
|
+
const ownerWrap = wrapDek(dek, Buffer.from(ownerPubkey, "base64"));
|
|
3581
|
+
const provWrap = wrapDek(dek, loadProviderKeypair().publicKey);
|
|
3582
|
+
return {
|
|
3583
|
+
ciphertext,
|
|
3584
|
+
ownerPubkey,
|
|
3585
|
+
wraps: [
|
|
3586
|
+
{ recipient: "owner", wrapped_dek: ownerWrap.wrapped, wrap_pubkey: ownerWrap.wrapPubkey },
|
|
3587
|
+
{ recipient: "provider", wrapped_dek: provWrap.wrapped, wrap_pubkey: provWrap.wrapPubkey }
|
|
3588
|
+
]
|
|
3589
|
+
};
|
|
3590
|
+
}
|
|
3591
|
+
function delegationStateForWrite(hasEnvelope) {
|
|
3592
|
+
return hasEnvelope ? true : null;
|
|
3593
|
+
}
|
|
3594
|
+
var import_tweetnacl4, log12, DERIVE_MESSAGE, botKeypair;
|
|
3595
|
+
var init_memory_encryption = __esm({
|
|
3596
|
+
"packages/brain/src/memory/memory-encryption.ts"() {
|
|
3597
|
+
"use strict";
|
|
3598
|
+
import_tweetnacl4 = __toESM(require("tweetnacl"));
|
|
3599
|
+
init_memory_envelope();
|
|
3600
|
+
init_encryption_keys();
|
|
3601
|
+
init_solana_client();
|
|
3602
|
+
init_logger();
|
|
3603
|
+
log12 = createChildLogger("memory-encryption");
|
|
3604
|
+
DERIVE_MESSAGE = "clude-memory-encryption-v1";
|
|
3605
|
+
botKeypair = null;
|
|
3606
|
+
}
|
|
3607
|
+
});
|
|
3608
|
+
|
|
3609
|
+
// packages/shared/src/core/encryption.ts
|
|
3610
|
+
var encryption_exports = {};
|
|
3611
|
+
__export(encryption_exports, {
|
|
3612
|
+
configureEncryption: () => configureEncryption,
|
|
3613
|
+
decryptContent: () => decryptContent,
|
|
3614
|
+
decryptMemoryBatch: () => decryptMemoryBatch,
|
|
3615
|
+
encryptContent: () => encryptContent,
|
|
3616
|
+
getEncryptionPubkey: () => getEncryptionPubkey,
|
|
3617
|
+
isEncryptionEnabled: () => isEncryptionEnabled
|
|
3618
|
+
});
|
|
3619
|
+
async function configureEncryption(solanaSecretKey) {
|
|
3620
|
+
if (solanaSecretKey.length !== 64) {
|
|
3621
|
+
throw new Error("Encryption requires a 64-byte Ed25519 secret key");
|
|
3622
|
+
}
|
|
3623
|
+
const seed = solanaSecretKey.slice(0, 32);
|
|
3624
|
+
const derived = await hkdfAsync2("sha256", seed, HKDF_SALT2, HKDF_INFO2, 32);
|
|
3625
|
+
encryptionKey = new Uint8Array(derived);
|
|
3626
|
+
const keypair = import_tweetnacl5.default.sign.keyPair.fromSecretKey(solanaSecretKey);
|
|
1996
3627
|
encryptionPubkey = Buffer.from(keypair.publicKey).toString("base64");
|
|
1997
|
-
|
|
3628
|
+
log13.info({ pubkey: encryptionPubkey.slice(0, 12) + "..." }, "Encryption configured");
|
|
1998
3629
|
}
|
|
1999
3630
|
function isEncryptionEnabled() {
|
|
2000
3631
|
return encryptionKey !== null;
|
|
@@ -2006,12 +3637,12 @@ function encryptContent(plaintext) {
|
|
|
2006
3637
|
if (!encryptionKey) {
|
|
2007
3638
|
throw new Error("Encryption not configured. Call configureEncryption() first.");
|
|
2008
3639
|
}
|
|
2009
|
-
const nonce =
|
|
3640
|
+
const nonce = import_tweetnacl5.default.randomBytes(NONCE_LENGTH2);
|
|
2010
3641
|
const messageBytes = new TextEncoder().encode(plaintext);
|
|
2011
|
-
const ciphertext =
|
|
2012
|
-
const combined = new Uint8Array(
|
|
3642
|
+
const ciphertext = import_tweetnacl5.default.secretbox(messageBytes, nonce, encryptionKey);
|
|
3643
|
+
const combined = new Uint8Array(NONCE_LENGTH2 + ciphertext.length);
|
|
2013
3644
|
combined.set(nonce, 0);
|
|
2014
|
-
combined.set(ciphertext,
|
|
3645
|
+
combined.set(ciphertext, NONCE_LENGTH2);
|
|
2015
3646
|
return Buffer.from(combined).toString("base64");
|
|
2016
3647
|
}
|
|
2017
3648
|
function decryptContent(encrypted) {
|
|
@@ -2020,12 +3651,12 @@ function decryptContent(encrypted) {
|
|
|
2020
3651
|
}
|
|
2021
3652
|
try {
|
|
2022
3653
|
const combined = Buffer.from(encrypted, "base64");
|
|
2023
|
-
if (combined.length <
|
|
3654
|
+
if (combined.length < NONCE_LENGTH2 + 1) {
|
|
2024
3655
|
return null;
|
|
2025
3656
|
}
|
|
2026
|
-
const nonce = combined.subarray(0,
|
|
2027
|
-
const ciphertext = combined.subarray(
|
|
2028
|
-
const plaintext =
|
|
3657
|
+
const nonce = combined.subarray(0, NONCE_LENGTH2);
|
|
3658
|
+
const ciphertext = combined.subarray(NONCE_LENGTH2);
|
|
3659
|
+
const plaintext = import_tweetnacl5.default.secretbox.open(ciphertext, nonce, encryptionKey);
|
|
2029
3660
|
if (!plaintext) {
|
|
2030
3661
|
return null;
|
|
2031
3662
|
}
|
|
@@ -2039,36 +3670,94 @@ function decryptMemoryBatch(memories) {
|
|
|
2039
3670
|
for (const mem of memories) {
|
|
2040
3671
|
if (!mem.encrypted) continue;
|
|
2041
3672
|
if (mem.encryption_pubkey && mem.encryption_pubkey !== encryptionPubkey) {
|
|
2042
|
-
|
|
3673
|
+
log13.debug({ memPubkey: mem.encryption_pubkey?.slice(0, 12) }, "Skipping memory encrypted by different key");
|
|
2043
3674
|
continue;
|
|
2044
3675
|
}
|
|
2045
3676
|
const decrypted = decryptContent(mem.content);
|
|
2046
3677
|
if (decrypted !== null) {
|
|
2047
3678
|
mem.content = decrypted;
|
|
2048
3679
|
} else {
|
|
2049
|
-
|
|
3680
|
+
log13.warn("Failed to decrypt memory content \u2014 wrong key or corrupted data");
|
|
2050
3681
|
}
|
|
2051
3682
|
}
|
|
2052
3683
|
return memories;
|
|
2053
3684
|
}
|
|
2054
|
-
var
|
|
3685
|
+
var import_tweetnacl5, import_crypto2, import_util2, log13, hkdfAsync2, HKDF_SALT2, HKDF_INFO2, NONCE_LENGTH2, encryptionKey, encryptionPubkey;
|
|
2055
3686
|
var init_encryption = __esm({
|
|
2056
3687
|
"packages/shared/src/core/encryption.ts"() {
|
|
2057
3688
|
"use strict";
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
3689
|
+
import_tweetnacl5 = __toESM(require("tweetnacl"));
|
|
3690
|
+
import_crypto2 = require("crypto");
|
|
3691
|
+
import_util2 = require("util");
|
|
2061
3692
|
init_logger();
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
3693
|
+
log13 = createChildLogger("encryption");
|
|
3694
|
+
hkdfAsync2 = (0, import_util2.promisify)(import_crypto2.hkdf);
|
|
3695
|
+
HKDF_SALT2 = "clude-cortex-v1";
|
|
3696
|
+
HKDF_INFO2 = "memory-encryption";
|
|
3697
|
+
NONCE_LENGTH2 = import_tweetnacl5.default.secretbox.nonceLength;
|
|
2067
3698
|
encryptionKey = null;
|
|
2068
3699
|
encryptionPubkey = null;
|
|
2069
3700
|
}
|
|
2070
3701
|
});
|
|
2071
3702
|
|
|
3703
|
+
// packages/brain/src/memory/memory-decryption.ts
|
|
3704
|
+
async function fetchProviderWraps(ids) {
|
|
3705
|
+
const map = /* @__PURE__ */ new Map();
|
|
3706
|
+
if (ids.length === 0) return map;
|
|
3707
|
+
const { data, error } = await getDb().from("memory_dek_wraps").select("memory_id, wrapped_dek, wrap_pubkey").eq("recipient", "provider").in("memory_id", ids);
|
|
3708
|
+
if (error || !data) return map;
|
|
3709
|
+
for (const r of data) {
|
|
3710
|
+
map.set(r.memory_id, { wrapped_dek: r.wrapped_dek, wrap_pubkey: r.wrap_pubkey });
|
|
3711
|
+
}
|
|
3712
|
+
return map;
|
|
3713
|
+
}
|
|
3714
|
+
async function decryptMemories(memories) {
|
|
3715
|
+
if (!memories || memories.length === 0) return memories;
|
|
3716
|
+
const encryptedRows = memories.filter((m) => m.encrypted === true);
|
|
3717
|
+
if (encryptedRows.length === 0) return memories;
|
|
3718
|
+
let providerSecret = null;
|
|
3719
|
+
try {
|
|
3720
|
+
providerSecret = loadProviderKeypair().secretKey;
|
|
3721
|
+
} catch {
|
|
3722
|
+
providerSecret = null;
|
|
3723
|
+
}
|
|
3724
|
+
const wraps = providerSecret ? await fetchProviderWraps(encryptedRows.map((m) => m.id)) : /* @__PURE__ */ new Map();
|
|
3725
|
+
const legacyRows = [];
|
|
3726
|
+
for (const mem of encryptedRows) {
|
|
3727
|
+
const wrap = wraps.get(mem.id);
|
|
3728
|
+
if (providerSecret && wrap) {
|
|
3729
|
+
const dek = unwrapDek(wrap.wrapped_dek, wrap.wrap_pubkey, providerSecret);
|
|
3730
|
+
const plain = dek ? decryptField(mem.content, dek) : null;
|
|
3731
|
+
if (plain !== null) {
|
|
3732
|
+
mem.content = plain;
|
|
3733
|
+
continue;
|
|
3734
|
+
}
|
|
3735
|
+
log14.debug({ id: mem.id }, "Envelope decrypt failed \u2014 leaving ciphertext");
|
|
3736
|
+
continue;
|
|
3737
|
+
}
|
|
3738
|
+
legacyRows.push(mem);
|
|
3739
|
+
}
|
|
3740
|
+
if (legacyRows.length > 0) decryptMemoryBatch(legacyRows);
|
|
3741
|
+
return memories;
|
|
3742
|
+
}
|
|
3743
|
+
async function decryptOneContent(row) {
|
|
3744
|
+
if (row.encrypted !== true) return row.content;
|
|
3745
|
+
const [out] = await decryptMemories([{ ...row }]);
|
|
3746
|
+
return out.content !== row.content ? out.content : null;
|
|
3747
|
+
}
|
|
3748
|
+
var log14;
|
|
3749
|
+
var init_memory_decryption = __esm({
|
|
3750
|
+
"packages/brain/src/memory/memory-decryption.ts"() {
|
|
3751
|
+
"use strict";
|
|
3752
|
+
init_database();
|
|
3753
|
+
init_memory_envelope();
|
|
3754
|
+
init_encryption_keys();
|
|
3755
|
+
init_encryption();
|
|
3756
|
+
init_logger();
|
|
3757
|
+
log14 = createChildLogger("memory-decryption");
|
|
3758
|
+
}
|
|
3759
|
+
});
|
|
3760
|
+
|
|
2072
3761
|
// packages/brain/src/events/event-bus.ts
|
|
2073
3762
|
var event_bus_exports = {};
|
|
2074
3763
|
__export(event_bus_exports, {
|
|
@@ -2121,16 +3810,16 @@ async function findOrCreateEntity(name, entityType, opts) {
|
|
|
2121
3810
|
mention_count: 1
|
|
2122
3811
|
}).select().single();
|
|
2123
3812
|
if (error) {
|
|
2124
|
-
|
|
3813
|
+
log15.error({ error: error.message, name }, "Failed to create entity");
|
|
2125
3814
|
return null;
|
|
2126
3815
|
}
|
|
2127
|
-
|
|
3816
|
+
log15.debug({ id: newEntity.id, name, type: entityType }, "Entity created");
|
|
2128
3817
|
embedEntity(newEntity.id, name, opts?.description).catch(
|
|
2129
|
-
(err) =>
|
|
3818
|
+
(err) => log15.debug({ err }, "Entity embedding failed")
|
|
2130
3819
|
);
|
|
2131
3820
|
return newEntity;
|
|
2132
3821
|
} catch (err) {
|
|
2133
|
-
|
|
3822
|
+
log15.error({ err, name }, "Entity findOrCreate failed");
|
|
2134
3823
|
return null;
|
|
2135
3824
|
}
|
|
2136
3825
|
}
|
|
@@ -2152,7 +3841,7 @@ async function createEntityMention(entityId, memoryId, context, salience = 0.5)
|
|
|
2152
3841
|
salience: Math.max(0, Math.min(1, salience))
|
|
2153
3842
|
}, { onConflict: "entity_id,memory_id" });
|
|
2154
3843
|
if (error) {
|
|
2155
|
-
|
|
3844
|
+
log15.debug({ error: error.message, entityId, memoryId }, "Entity mention failed");
|
|
2156
3845
|
}
|
|
2157
3846
|
}
|
|
2158
3847
|
async function getMemoriesByEntity(entityId, opts) {
|
|
@@ -2164,12 +3853,12 @@ async function getMemoriesByEntity(entityId, opts) {
|
|
|
2164
3853
|
`).eq("entity_id", entityId).order("salience", { ascending: false }).limit(opts?.limit || 20);
|
|
2165
3854
|
const { data, error } = await query;
|
|
2166
3855
|
if (error) {
|
|
2167
|
-
|
|
3856
|
+
log15.error({ error: error.message, entityId }, "Failed to get memories by entity");
|
|
2168
3857
|
return [];
|
|
2169
3858
|
}
|
|
2170
3859
|
const { getOwnerWallet: getOwnerWallet4 } = (init_memory(), __toCommonJS(memory_exports));
|
|
2171
3860
|
const ownerWallet = getOwnerWallet4();
|
|
2172
|
-
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);
|
|
3861
|
+
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);
|
|
2173
3862
|
return memories;
|
|
2174
3863
|
}
|
|
2175
3864
|
async function getEntityCooccurrences(entityId, opts) {
|
|
@@ -2181,33 +3870,27 @@ async function getEntityCooccurrences(entityId, opts) {
|
|
|
2181
3870
|
max_results: opts?.maxResults ?? 10
|
|
2182
3871
|
});
|
|
2183
3872
|
if (error || !data) {
|
|
2184
|
-
|
|
3873
|
+
log15.debug({ error: error?.message, entityId }, "Entity co-occurrence lookup failed");
|
|
2185
3874
|
return [];
|
|
2186
3875
|
}
|
|
2187
3876
|
return data;
|
|
2188
3877
|
} catch (err) {
|
|
2189
|
-
|
|
3878
|
+
log15.debug({ err, entityId }, "Entity co-occurrence skipped (RPC unavailable)");
|
|
2190
3879
|
return [];
|
|
2191
3880
|
}
|
|
2192
3881
|
}
|
|
2193
3882
|
async function createEntityRelation(sourceEntityId, targetEntityId, relationType, evidenceMemoryId, strength = 0.5) {
|
|
2194
3883
|
if (sourceEntityId === targetEntityId) return;
|
|
2195
3884
|
const db = getDb();
|
|
2196
|
-
const {
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
source_entity_id: sourceEntityId,
|
|
2206
|
-
target_entity_id: targetEntityId,
|
|
2207
|
-
relation_type: relationType,
|
|
2208
|
-
strength,
|
|
2209
|
-
evidence_memory_ids: evidenceMemoryId ? [evidenceMemoryId] : []
|
|
2210
|
-
});
|
|
3885
|
+
const { error } = await db.rpc("upsert_entity_relation", {
|
|
3886
|
+
p_src: sourceEntityId,
|
|
3887
|
+
p_tgt: targetEntityId,
|
|
3888
|
+
p_type: relationType,
|
|
3889
|
+
p_evidence: evidenceMemoryId ?? null,
|
|
3890
|
+
p_strength: strength
|
|
3891
|
+
});
|
|
3892
|
+
if (error) {
|
|
3893
|
+
log15.warn({ err: error.message, sourceEntityId, targetEntityId, relationType }, "upsert_entity_relation failed");
|
|
2211
3894
|
}
|
|
2212
3895
|
}
|
|
2213
3896
|
function extractEntitiesFromText(text) {
|
|
@@ -2287,11 +3970,11 @@ async function extractAndLinkEntities(memoryId, content, summary, relatedUser) {
|
|
|
2287
3970
|
// base co-occurrence strength
|
|
2288
3971
|
);
|
|
2289
3972
|
} catch (err) {
|
|
2290
|
-
|
|
3973
|
+
log15.debug({ err, source: entityIds[i].name, target: entityIds[j].name }, "Failed to create entity relation");
|
|
2291
3974
|
}
|
|
2292
3975
|
}
|
|
2293
3976
|
}
|
|
2294
|
-
|
|
3977
|
+
log15.debug({ memoryId, entityCount: extracted.length, relations: Math.max(0, entityIds.length * (entityIds.length - 1) / 2) }, "Entities extracted, linked, and related");
|
|
2295
3978
|
}
|
|
2296
3979
|
async function findSimilarEntities(query, opts) {
|
|
2297
3980
|
if (!isEmbeddingEnabled()) return [];
|
|
@@ -2305,7 +3988,7 @@ async function findSimilarEntities(query, opts) {
|
|
|
2305
3988
|
filter_types: opts?.entityTypes || null
|
|
2306
3989
|
});
|
|
2307
3990
|
if (error) {
|
|
2308
|
-
|
|
3991
|
+
log15.debug({ error: error.message }, "Entity vector search failed");
|
|
2309
3992
|
return [];
|
|
2310
3993
|
}
|
|
2311
3994
|
const ids = (data || []).map((d) => d.id);
|
|
@@ -2313,14 +3996,14 @@ async function findSimilarEntities(query, opts) {
|
|
|
2313
3996
|
const { data: entities } = await db.from("entities").select("*").in("id", ids);
|
|
2314
3997
|
return entities || [];
|
|
2315
3998
|
}
|
|
2316
|
-
var
|
|
3999
|
+
var log15;
|
|
2317
4000
|
var init_graph = __esm({
|
|
2318
4001
|
"packages/brain/src/memory/graph.ts"() {
|
|
2319
4002
|
"use strict";
|
|
2320
4003
|
init_database();
|
|
2321
4004
|
init_logger();
|
|
2322
4005
|
init_embeddings();
|
|
2323
|
-
|
|
4006
|
+
log15 = createChildLogger("memory-graph");
|
|
2324
4007
|
}
|
|
2325
4008
|
});
|
|
2326
4009
|
|
|
@@ -2348,8 +4031,11 @@ var init_owner_context = __esm({
|
|
|
2348
4031
|
// packages/brain/src/memory/memory.ts
|
|
2349
4032
|
var memory_exports = {};
|
|
2350
4033
|
__export(memory_exports, {
|
|
2351
|
-
SCOPE_BOT_OWN: () =>
|
|
4034
|
+
SCOPE_BOT_OWN: () => SCOPE_BOT_OWN2,
|
|
4035
|
+
_resetOutboxDegradeLog: () => _resetOutboxDegradeLog,
|
|
2352
4036
|
_setOwnerWallet: () => _setOwnerWallet,
|
|
4037
|
+
applyOwnerPostGuard: () => applyOwnerPostGuard,
|
|
4038
|
+
buildFragmentRpcArgs: () => buildFragmentRpcArgs,
|
|
2353
4039
|
calculateImportance: () => calculateImportance,
|
|
2354
4040
|
createMemoryLink: () => createMemoryLink,
|
|
2355
4041
|
createMemoryLinksBatch: () => createMemoryLinksBatch,
|
|
@@ -2360,11 +4046,15 @@ __export(memory_exports, {
|
|
|
2360
4046
|
formatMemoryContext: () => formatMemoryContext,
|
|
2361
4047
|
generateHashId: () => generateHashId,
|
|
2362
4048
|
getMemoryStats: () => getMemoryStats,
|
|
4049
|
+
getOwnerScope: () => getOwnerScope,
|
|
2363
4050
|
getOwnerWallet: () => getOwnerWallet,
|
|
2364
4051
|
getRecentMemories: () => getRecentMemories,
|
|
2365
4052
|
getSelfModel: () => getSelfModel,
|
|
2366
4053
|
hydrateMemories: () => hydrateMemories,
|
|
2367
4054
|
inferConcepts: () => inferConcepts,
|
|
4055
|
+
invalidateInstalledPacksCache: () => invalidateInstalledPacksCache,
|
|
4056
|
+
isBenchMode: () => isBenchMode,
|
|
4057
|
+
isOwnerScopeFailClosed: () => isOwnerScopeFailClosed,
|
|
2368
4058
|
isValidHashId: () => isValidHashId,
|
|
2369
4059
|
listMemories: () => listMemories,
|
|
2370
4060
|
markJepaQueried: () => markJepaQueried,
|
|
@@ -2372,11 +4062,15 @@ __export(memory_exports, {
|
|
|
2372
4062
|
moodToValence: () => moodToValence,
|
|
2373
4063
|
recallMemories: () => recallMemories,
|
|
2374
4064
|
recallMemorySummaries: () => recallMemorySummaries,
|
|
4065
|
+
runEnrichPipeline: () => runEnrichPipeline,
|
|
2375
4066
|
scopeToOwner: () => scopeToOwner,
|
|
4067
|
+
scoreImportanceOnWrite: () => scoreImportanceOnWrite,
|
|
2376
4068
|
scoreImportanceWithLLM: () => scoreImportanceWithLLM,
|
|
2377
4069
|
scoreMemory: () => scoreMemory,
|
|
4070
|
+
shouldTrackAccess: () => shouldTrackAccess,
|
|
2378
4071
|
storeDreamLog: () => storeDreamLog,
|
|
2379
4072
|
storeMemory: () => storeMemory,
|
|
4073
|
+
storeMemoryWithOutbox: () => storeMemoryWithOutbox,
|
|
2380
4074
|
updateMemory: () => updateMemory
|
|
2381
4075
|
});
|
|
2382
4076
|
function getCachedEmbedding2(query) {
|
|
@@ -2405,18 +4099,51 @@ function getOwnerWallet() {
|
|
|
2405
4099
|
if (contextWallet !== void 0) return contextWallet;
|
|
2406
4100
|
return _ownerWallet;
|
|
2407
4101
|
}
|
|
2408
|
-
function
|
|
4102
|
+
function isOwnerScopeFailClosed() {
|
|
4103
|
+
return process.env.OWNER_SCOPE_FAILCLOSED === "true";
|
|
4104
|
+
}
|
|
4105
|
+
function getOwnerScope() {
|
|
2409
4106
|
const wallet = getOwnerWallet();
|
|
2410
|
-
if (wallet
|
|
4107
|
+
if (wallet) return wallet;
|
|
4108
|
+
return isOwnerScopeFailClosed() ? SCOPE_BOT_OWN2 : null;
|
|
4109
|
+
}
|
|
4110
|
+
function scopeToOwner(query) {
|
|
4111
|
+
const scope = getOwnerScope();
|
|
4112
|
+
if (scope === SCOPE_BOT_OWN2) {
|
|
2411
4113
|
return query.is("owner_wallet", null);
|
|
2412
4114
|
}
|
|
2413
|
-
if (
|
|
2414
|
-
return query.eq("owner_wallet",
|
|
4115
|
+
if (scope) {
|
|
4116
|
+
return query.eq("owner_wallet", scope);
|
|
2415
4117
|
}
|
|
2416
4118
|
return query;
|
|
2417
4119
|
}
|
|
4120
|
+
function applyOwnerPostGuard(results, scope) {
|
|
4121
|
+
if (!scope) return { kept: results, stripped: 0 };
|
|
4122
|
+
const kept = scope === SCOPE_BOT_OWN2 ? results.filter((m) => m.owner_wallet == null) : results.filter((m) => m.owner_wallet === scope);
|
|
4123
|
+
return { kept, stripped: results.length - kept.length };
|
|
4124
|
+
}
|
|
4125
|
+
function isBenchMode() {
|
|
4126
|
+
return process.env.BENCH_MODE === "true";
|
|
4127
|
+
}
|
|
4128
|
+
function shouldTrackAccess(opts) {
|
|
4129
|
+
if (isBenchMode()) return false;
|
|
4130
|
+
return opts.trackAccess !== false;
|
|
4131
|
+
}
|
|
4132
|
+
function buildFragmentRpcArgs(opts) {
|
|
4133
|
+
const args = {
|
|
4134
|
+
query_embedding: opts.embeddingJson,
|
|
4135
|
+
match_threshold: opts.matchThreshold,
|
|
4136
|
+
match_count: opts.matchCount,
|
|
4137
|
+
filter_owner: getOwnerScope()
|
|
4138
|
+
};
|
|
4139
|
+
if (process.env.MEMORY_FRAGMENT_FILTERS === "true") {
|
|
4140
|
+
args.min_decay = opts.minDecay;
|
|
4141
|
+
args.filter_types = opts.memoryTypes && opts.memoryTypes.length > 0 ? opts.memoryTypes : null;
|
|
4142
|
+
}
|
|
4143
|
+
return args;
|
|
4144
|
+
}
|
|
2418
4145
|
function generateHashId() {
|
|
2419
|
-
return `${HASH_ID_PREFIX}-${(0,
|
|
4146
|
+
return `${HASH_ID_PREFIX}-${(0, import_crypto3.randomBytes)(4).toString("hex")}`;
|
|
2420
4147
|
}
|
|
2421
4148
|
function isValidHashId(id) {
|
|
2422
4149
|
return /^clude-[a-f0-9]{8}$/.test(id);
|
|
@@ -2451,6 +4178,23 @@ function inferConcepts(summary, source, tags) {
|
|
|
2451
4178
|
concepts.push("identity_evolution");
|
|
2452
4179
|
return [...new Set(concepts)];
|
|
2453
4180
|
}
|
|
4181
|
+
function scoreImportanceOnWrite(opts) {
|
|
4182
|
+
const base = {
|
|
4183
|
+
self_model: 0.75,
|
|
4184
|
+
semantic: 0.7,
|
|
4185
|
+
procedural: 0.65,
|
|
4186
|
+
introspective: 0.6,
|
|
4187
|
+
episodic: 0.45
|
|
4188
|
+
};
|
|
4189
|
+
let score = base[opts.type] ?? 0.5;
|
|
4190
|
+
const len = (opts.content ?? "").trim().length;
|
|
4191
|
+
score += Math.min(len / 1e3, 0.15);
|
|
4192
|
+
if (opts.tags && opts.tags.length) score += 0.05;
|
|
4193
|
+
const concepts = opts.concepts ?? inferConcepts(opts.summary, opts.source, opts.tags || []);
|
|
4194
|
+
if (concepts.length) score += 0.05;
|
|
4195
|
+
if (INTERNAL_MEMORY_SOURCES.has(opts.source)) score -= 0.05;
|
|
4196
|
+
return Math.max(0, Math.min(1, score));
|
|
4197
|
+
}
|
|
2454
4198
|
function normalizeSummary(summary) {
|
|
2455
4199
|
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();
|
|
2456
4200
|
}
|
|
@@ -2467,27 +4211,104 @@ function isDuplicateWrite(source, summary) {
|
|
|
2467
4211
|
}
|
|
2468
4212
|
return false;
|
|
2469
4213
|
}
|
|
4214
|
+
async function getInstalledPackIdsCached(ownerWallet) {
|
|
4215
|
+
const key = ownerWallet ?? "__no_owner__";
|
|
4216
|
+
const now = Date.now();
|
|
4217
|
+
const cached = installedPacksCache.get(key);
|
|
4218
|
+
if (cached && cached.expiresAt > now) return cached.ids;
|
|
4219
|
+
let ids = [DEFAULT_PACK_ID];
|
|
4220
|
+
if (ownerWallet) {
|
|
4221
|
+
try {
|
|
4222
|
+
const db = getDb();
|
|
4223
|
+
const { data, error } = await db.from("wiki_pack_installations").select("pack_id").eq("owner_wallet", ownerWallet);
|
|
4224
|
+
if (!error && data) {
|
|
4225
|
+
const fromDb = data.map((r) => r.pack_id);
|
|
4226
|
+
ids = Array.from(/* @__PURE__ */ new Set([DEFAULT_PACK_ID, ...fromDb]));
|
|
4227
|
+
}
|
|
4228
|
+
} catch (err) {
|
|
4229
|
+
log16.debug({ err }, "Failed to fetch installed packs (using default only)");
|
|
4230
|
+
}
|
|
4231
|
+
}
|
|
4232
|
+
installedPacksCache.set(key, { ids, expiresAt: now + INSTALLED_PACKS_TTL_MS });
|
|
4233
|
+
if (installedPacksCache.size > 200) {
|
|
4234
|
+
for (const [k, v] of installedPacksCache) {
|
|
4235
|
+
if (v.expiresAt <= now) installedPacksCache.delete(k);
|
|
4236
|
+
}
|
|
4237
|
+
}
|
|
4238
|
+
return ids;
|
|
4239
|
+
}
|
|
4240
|
+
function invalidateInstalledPacksCache(ownerWallet) {
|
|
4241
|
+
if (ownerWallet) installedPacksCache.delete(ownerWallet);
|
|
4242
|
+
else installedPacksCache.clear();
|
|
4243
|
+
}
|
|
4244
|
+
async function ensureTopicEmbeddings(installedPackIds) {
|
|
4245
|
+
const sources = topicEmbedSources(installedPackIds);
|
|
4246
|
+
const missing = sources.filter((s) => !topicEmbeddingCache.has(s.topicId));
|
|
4247
|
+
if (missing.length === 0) {
|
|
4248
|
+
return new Map(sources.map((s) => [s.topicId, topicEmbeddingCache.get(s.topicId)]));
|
|
4249
|
+
}
|
|
4250
|
+
if (topicEmbeddingPopulationLock) {
|
|
4251
|
+
await topicEmbeddingPopulationLock;
|
|
4252
|
+
}
|
|
4253
|
+
const stillMissing = sources.filter((s) => !topicEmbeddingCache.has(s.topicId));
|
|
4254
|
+
if (stillMissing.length === 0) {
|
|
4255
|
+
return new Map(sources.map((s) => [s.topicId, topicEmbeddingCache.get(s.topicId)]));
|
|
4256
|
+
}
|
|
4257
|
+
topicEmbeddingPopulationLock = (async () => {
|
|
4258
|
+
try {
|
|
4259
|
+
const texts = stillMissing.map((s) => s.text);
|
|
4260
|
+
const embeddings = await generateEmbeddings(texts);
|
|
4261
|
+
stillMissing.forEach((s, i) => {
|
|
4262
|
+
const emb = embeddings[i];
|
|
4263
|
+
if (emb) topicEmbeddingCache.set(s.topicId, emb);
|
|
4264
|
+
});
|
|
4265
|
+
} catch (err) {
|
|
4266
|
+
log16.warn({ err }, "Failed to populate topic embeddings cache");
|
|
4267
|
+
} finally {
|
|
4268
|
+
topicEmbeddingPopulationLock = null;
|
|
4269
|
+
}
|
|
4270
|
+
})();
|
|
4271
|
+
await topicEmbeddingPopulationLock;
|
|
4272
|
+
return new Map(
|
|
4273
|
+
sources.filter((s) => topicEmbeddingCache.has(s.topicId)).map((s) => [s.topicId, topicEmbeddingCache.get(s.topicId)])
|
|
4274
|
+
);
|
|
4275
|
+
}
|
|
2470
4276
|
async function storeMemory(opts) {
|
|
4277
|
+
const trimmedContent = (opts.content ?? "").trim();
|
|
4278
|
+
if (!trimmedContent) {
|
|
4279
|
+
log16.warn({ source: opts.source }, "Rejected empty-content memory");
|
|
4280
|
+
return null;
|
|
4281
|
+
}
|
|
2471
4282
|
if (opts.source.startsWith("shiro_") && isDuplicateWrite(opts.source, opts.summary)) {
|
|
2472
|
-
|
|
4283
|
+
log16.debug({ source: opts.source, summary: opts.summary.slice(0, 60) }, "Skipping duplicate memory write");
|
|
2473
4284
|
return null;
|
|
2474
4285
|
}
|
|
2475
4286
|
const db = getDb();
|
|
4287
|
+
const ownerWallet = getOwnerWallet();
|
|
4288
|
+
const importance = clamp(opts.importance ?? scoreImportanceOnWrite(opts), 0, 1);
|
|
2476
4289
|
const concepts = opts.concepts || inferConcepts(opts.summary, opts.source, opts.tags || []);
|
|
4290
|
+
const installedPackIds = await getInstalledPackIdsCached(ownerWallet);
|
|
4291
|
+
const taggedTags = autoCategorizeTags({
|
|
4292
|
+
content: opts.content,
|
|
4293
|
+
summary: opts.summary,
|
|
4294
|
+
existingTags: opts.tags || [],
|
|
4295
|
+
installedPackIds
|
|
4296
|
+
});
|
|
2477
4297
|
const hashId = generateHashId();
|
|
2478
4298
|
try {
|
|
2479
|
-
const plaintextContent =
|
|
2480
|
-
const
|
|
2481
|
-
const
|
|
4299
|
+
const plaintextContent = trimmedContent.slice(0, MEMORY_MAX_CONTENT_LENGTH);
|
|
4300
|
+
const envelope = await encryptForStorage(plaintextContent, ownerWallet || null);
|
|
4301
|
+
const legacyEncrypt = !envelope && isEncryptionEnabled();
|
|
4302
|
+
const storedContent = envelope ? envelope.ciphertext : legacyEncrypt ? encryptContent(plaintextContent) : plaintextContent;
|
|
2482
4303
|
const { data, error } = await db.from("memories").insert({
|
|
2483
4304
|
hash_id: hashId,
|
|
2484
4305
|
memory_type: opts.type,
|
|
2485
4306
|
content: storedContent,
|
|
2486
4307
|
summary: opts.summary.slice(0, MEMORY_MAX_SUMMARY_LENGTH),
|
|
2487
|
-
tags:
|
|
4308
|
+
tags: taggedTags,
|
|
2488
4309
|
concepts,
|
|
2489
4310
|
emotional_valence: clamp(opts.emotionalValence ?? 0, -1, 1),
|
|
2490
|
-
importance
|
|
4311
|
+
importance,
|
|
2491
4312
|
source: opts.source,
|
|
2492
4313
|
source_id: opts.sourceId || null,
|
|
2493
4314
|
related_user: opts.relatedUser || null,
|
|
@@ -2495,43 +4316,83 @@ async function storeMemory(opts) {
|
|
|
2495
4316
|
metadata: opts.metadata || {},
|
|
2496
4317
|
evidence_ids: opts.evidenceIds || [],
|
|
2497
4318
|
compacted: false,
|
|
2498
|
-
encrypted:
|
|
2499
|
-
encryption_pubkey:
|
|
2500
|
-
|
|
2501
|
-
|
|
4319
|
+
encrypted: envelope !== null || legacyEncrypt,
|
|
4320
|
+
encryption_pubkey: envelope ? envelope.ownerPubkey : legacyEncrypt ? getEncryptionPubkey() : null,
|
|
4321
|
+
provider_delegated: delegationStateForWrite(envelope !== null),
|
|
4322
|
+
// true|null at write; false is revoke-only (§7)
|
|
4323
|
+
owner_wallet: ownerWallet || null
|
|
4324
|
+
}).select("id, hash_id, content, memory_type, owner_wallet, created_at, tags, source, related_user, related_wallet").single();
|
|
2502
4325
|
if (error) {
|
|
2503
|
-
|
|
4326
|
+
log16.error({ error: error.message }, "Failed to store memory");
|
|
2504
4327
|
return null;
|
|
2505
4328
|
}
|
|
2506
|
-
|
|
4329
|
+
log16.debug({
|
|
2507
4330
|
id: data.id,
|
|
2508
4331
|
hashId: data.hash_id,
|
|
2509
4332
|
type: opts.type,
|
|
2510
4333
|
summary: opts.summary.slice(0, 60),
|
|
2511
|
-
importance
|
|
4334
|
+
importance,
|
|
2512
4335
|
concepts
|
|
2513
4336
|
}, "Memory stored");
|
|
4337
|
+
await setContentTokens(db, data.id, plaintextContent);
|
|
4338
|
+
if (envelope) {
|
|
4339
|
+
const { error: wrapErr } = await db.from("memory_dek_wraps").insert(
|
|
4340
|
+
envelope.wraps.map((w) => ({ memory_id: data.id, ...w }))
|
|
4341
|
+
);
|
|
4342
|
+
if (wrapErr) {
|
|
4343
|
+
log16.error({ id: data.id, error: wrapErr.message }, "DEK wrap write failed \u2014 reverting memory to plaintext");
|
|
4344
|
+
await db.from("memories").update({
|
|
4345
|
+
content: plaintextContent,
|
|
4346
|
+
encrypted: false,
|
|
4347
|
+
encryption_pubkey: null,
|
|
4348
|
+
provider_delegated: null
|
|
4349
|
+
}).eq("id", data.id);
|
|
4350
|
+
}
|
|
4351
|
+
}
|
|
2514
4352
|
eventBus.emit("memory:stored", {
|
|
2515
|
-
importance
|
|
4353
|
+
importance,
|
|
2516
4354
|
memoryType: opts.type,
|
|
2517
4355
|
source: opts.source
|
|
2518
4356
|
});
|
|
2519
|
-
commitMemoryToChain(data.id, opts
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
4357
|
+
commitMemoryToChain(data.id, opts, data, plaintextContent, envelope !== null || legacyEncrypt).catch(
|
|
4358
|
+
(err) => log16.warn({ err }, "On-chain memory commit failed")
|
|
4359
|
+
);
|
|
4360
|
+
let enqueued = false;
|
|
4361
|
+
if (config.memory.outboxEnabled) {
|
|
4362
|
+
enqueued = await enqueueEnrichJob(db, data.id);
|
|
4363
|
+
}
|
|
4364
|
+
if (!enqueued) {
|
|
4365
|
+
const embedP = embedMemory(data.id, opts);
|
|
4366
|
+
embedP.catch((err) => log16.warn({ err }, "Embedding generation failed"));
|
|
4367
|
+
autoLinkMemory(data.id, opts).catch((err) => log16.warn({ err }, "Auto-linking failed"));
|
|
4368
|
+
extractAndLinkEntitiesForMemory(data.id, opts).catch((err) => log16.debug({ err }, "Entity extraction failed"));
|
|
4369
|
+
if (config.memory.reconcileEnabled) {
|
|
4370
|
+
const reconcileScope = ownerWallet || SCOPE_BOT_OWN2;
|
|
4371
|
+
embedP.then(() => runReconcileShadow(data.id, reconcileScope)).catch(() => {
|
|
4372
|
+
});
|
|
4373
|
+
}
|
|
4374
|
+
}
|
|
2523
4375
|
return data.id;
|
|
2524
4376
|
} catch (err) {
|
|
2525
|
-
|
|
4377
|
+
log16.error({ err }, "Memory store failed");
|
|
2526
4378
|
return null;
|
|
2527
4379
|
}
|
|
2528
4380
|
}
|
|
2529
|
-
async function commitMemoryToChain(memoryId, opts) {
|
|
2530
|
-
if (
|
|
2531
|
-
const
|
|
4381
|
+
async function commitMemoryToChain(memoryId, opts, row, plaintextContent, encrypted) {
|
|
4382
|
+
if (TOKENISATION_SKIP_SOURCES.has(opts.source)) return;
|
|
4383
|
+
const canonicalHash = memoryContentHash({
|
|
4384
|
+
content: plaintextContent,
|
|
4385
|
+
memory_type: row.memory_type,
|
|
4386
|
+
owner_wallet: row.owner_wallet,
|
|
4387
|
+
created_at: row.created_at,
|
|
4388
|
+
tags: row.tags ?? [],
|
|
4389
|
+
source: row.source,
|
|
4390
|
+
related_user: row.related_user,
|
|
4391
|
+
related_wallet: row.related_wallet
|
|
4392
|
+
});
|
|
4393
|
+
const contentHashBuf = Buffer.from(canonicalHash, "hex");
|
|
2532
4394
|
let signature = null;
|
|
2533
4395
|
if (isRegistryEnabled()) {
|
|
2534
|
-
const encrypted = isEncryptionEnabled();
|
|
2535
4396
|
signature = await registerMemoryOnChain(
|
|
2536
4397
|
contentHashBuf,
|
|
2537
4398
|
opts.type,
|
|
@@ -2541,24 +4402,146 @@ async function commitMemoryToChain(memoryId, opts) {
|
|
|
2541
4402
|
);
|
|
2542
4403
|
}
|
|
2543
4404
|
if (!signature) {
|
|
2544
|
-
const
|
|
2545
|
-
const memo = `clude:v1:sha256:${contentHashHex}`;
|
|
4405
|
+
const memo = `clude:v1:sha256:${canonicalHash}`;
|
|
2546
4406
|
signature = await writeMemo(memo);
|
|
2547
4407
|
}
|
|
2548
|
-
if (!signature) return;
|
|
2549
4408
|
const db = getDb();
|
|
2550
|
-
|
|
2551
|
-
|
|
4409
|
+
if (!signature) {
|
|
4410
|
+
await db.from("memories").update({ content_hash: canonicalHash, tokenization_status: "failed" }).eq("id", memoryId);
|
|
4411
|
+
return;
|
|
4412
|
+
}
|
|
4413
|
+
await db.from("memories").update({
|
|
4414
|
+
solana_signature: signature,
|
|
4415
|
+
content_hash: canonicalHash,
|
|
4416
|
+
cnft_address: signature,
|
|
4417
|
+
// PDA-based v0.1 — assetId = tx sig; LightMintClient (v0.2) will use real cNFT mints
|
|
4418
|
+
cnft_tx_sig: signature,
|
|
4419
|
+
cnft_tree: null,
|
|
4420
|
+
cnft_leaf_index: null,
|
|
4421
|
+
tokenization_status: "minted",
|
|
4422
|
+
tokenized_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
4423
|
+
}).eq("id", memoryId);
|
|
4424
|
+
log16.debug({ memoryId, signature: signature.slice(0, 16) }, "Memory committed on-chain (PMP + legacy)");
|
|
4425
|
+
}
|
|
4426
|
+
function logOutboxDegradeOnce(err, memoryId) {
|
|
4427
|
+
if (outboxDegradeLogged) return;
|
|
4428
|
+
outboxDegradeLogged = true;
|
|
4429
|
+
const e = err;
|
|
4430
|
+
log16.warn(
|
|
4431
|
+
{ code: e?.code, message: e?.message, memoryId },
|
|
4432
|
+
"Outbox enqueue degraded \u2014 falling back to fire-and-forget (migration 044 unapplied?)"
|
|
4433
|
+
);
|
|
2552
4434
|
}
|
|
2553
|
-
async function
|
|
4435
|
+
async function enqueueEnrichJob(db, memoryId) {
|
|
4436
|
+
try {
|
|
4437
|
+
const { error } = await db.from("memory_write_jobs").insert({ memory_id: memoryId, job_type: "enrich" });
|
|
4438
|
+
if (error) {
|
|
4439
|
+
logOutboxDegradeOnce(error, memoryId);
|
|
4440
|
+
return false;
|
|
4441
|
+
}
|
|
4442
|
+
return true;
|
|
4443
|
+
} catch (err) {
|
|
4444
|
+
logOutboxDegradeOnce(err, memoryId);
|
|
4445
|
+
return false;
|
|
4446
|
+
}
|
|
4447
|
+
}
|
|
4448
|
+
function _resetOutboxDegradeLog() {
|
|
4449
|
+
outboxDegradeLogged = false;
|
|
4450
|
+
}
|
|
4451
|
+
async function runEnrichPipeline(memoryId, opts) {
|
|
4452
|
+
await embedMemory(memoryId, opts, { replaceFragments: true });
|
|
4453
|
+
await autoLinkMemory(memoryId, opts);
|
|
4454
|
+
await extractAndLinkEntitiesForMemory(memoryId, opts);
|
|
4455
|
+
if (config.memory.reconcileEnabled) {
|
|
4456
|
+
await runReconcileShadow(memoryId, getOwnerWallet() || SCOPE_BOT_OWN2);
|
|
4457
|
+
}
|
|
4458
|
+
}
|
|
4459
|
+
async function storeMemoryWithOutbox(opts) {
|
|
4460
|
+
const id = await storeMemory(opts);
|
|
4461
|
+
if (id === null) return null;
|
|
4462
|
+
const db = getDb();
|
|
4463
|
+
const { data: row } = await db.from("memories").select("hash_id").eq("id", id).maybeSingle();
|
|
4464
|
+
const { count } = await db.from("memory_write_jobs").select("id", { count: "exact", head: true }).eq("memory_id", id);
|
|
4465
|
+
return { id, hash_id: row?.hash_id ?? "", jobs_queued: count ?? 0 };
|
|
4466
|
+
}
|
|
4467
|
+
async function embedMemory(memoryId, opts, embedOpts = {}) {
|
|
2554
4468
|
if (!isEmbeddingEnabled()) return;
|
|
2555
4469
|
const db = getDb();
|
|
2556
|
-
const
|
|
4470
|
+
const fragments = [];
|
|
4471
|
+
fragments.push({ type: "summary", text: opts.summary });
|
|
4472
|
+
const content = opts.content.slice(0, MEMORY_MAX_CONTENT_LENGTH);
|
|
4473
|
+
if (content.length > EMBEDDING_FRAGMENT_MAX_LENGTH) {
|
|
4474
|
+
const sentences = content.match(/[^.!?\n]+[.!?\n]+/g) || [content];
|
|
4475
|
+
let chunk = "";
|
|
4476
|
+
for (const sentence of sentences) {
|
|
4477
|
+
if (chunk.length + sentence.length > EMBEDDING_FRAGMENT_MAX_LENGTH && chunk.length > 0) {
|
|
4478
|
+
fragments.push({ type: "content_chunk", text: chunk.trim() });
|
|
4479
|
+
chunk = "";
|
|
4480
|
+
}
|
|
4481
|
+
chunk += sentence;
|
|
4482
|
+
}
|
|
4483
|
+
if (chunk.trim()) fragments.push({ type: "content_chunk", text: chunk.trim() });
|
|
4484
|
+
} else {
|
|
4485
|
+
fragments.push({ type: "content_chunk", text: content });
|
|
4486
|
+
}
|
|
4487
|
+
const allLabels = [...opts.tags || [], ...opts.concepts || inferConcepts(opts.summary, opts.source, opts.tags || [])];
|
|
4488
|
+
if (allLabels.length > 0) {
|
|
4489
|
+
fragments.push({ type: "tag_context", text: `Context: ${allLabels.join(", ")}. ${opts.summary}` });
|
|
4490
|
+
}
|
|
4491
|
+
const embeddings = await generateEmbeddings(fragments.map((f) => f.text));
|
|
2557
4492
|
const summaryEmbedding = embeddings[0];
|
|
2558
|
-
if (summaryEmbedding) {
|
|
2559
|
-
|
|
4493
|
+
if (!summaryEmbedding) {
|
|
4494
|
+
log16.debug({ memoryId }, "No embedding generated; skipping semantic tagging");
|
|
4495
|
+
return;
|
|
4496
|
+
}
|
|
4497
|
+
const vertexEmbeddings = isVertexConfigured() ? await generateVertexEmbeddings(fragments.map((f) => f.text)) : [];
|
|
4498
|
+
const vertexSummary = vertexEmbeddings[0];
|
|
4499
|
+
await db.from("memories").update({
|
|
4500
|
+
embedding: JSON.stringify(summaryEmbedding),
|
|
4501
|
+
...vertexSummary ? { embedding_vertex: JSON.stringify(vertexSummary) } : {}
|
|
4502
|
+
}).eq("id", memoryId);
|
|
4503
|
+
const fragmentRows = fragments.map((f, i) => ({
|
|
4504
|
+
memory_id: memoryId,
|
|
4505
|
+
fragment_type: f.type,
|
|
4506
|
+
content: f.text.slice(0, EMBEDDING_FRAGMENT_MAX_LENGTH),
|
|
4507
|
+
embedding: embeddings[i] ? JSON.stringify(embeddings[i]) : null,
|
|
4508
|
+
...vertexEmbeddings[i] ? { embedding_vertex: JSON.stringify(vertexEmbeddings[i]) } : {}
|
|
4509
|
+
})).filter((r) => r.embedding !== null);
|
|
4510
|
+
if (fragmentRows.length > 0) {
|
|
4511
|
+
if (embedOpts.replaceFragments) {
|
|
4512
|
+
await db.from("memory_fragments").delete().eq("memory_id", memoryId);
|
|
4513
|
+
}
|
|
4514
|
+
const { error } = await db.from("memory_fragments").insert(fragmentRows);
|
|
4515
|
+
if (error) {
|
|
4516
|
+
log16.warn({ err: error.message, memoryId }, "Failed to store memory fragments (summary embedding intact)");
|
|
4517
|
+
}
|
|
4518
|
+
}
|
|
4519
|
+
try {
|
|
4520
|
+
const ownerWallet = getOwnerWallet();
|
|
4521
|
+
const installedPacks = await getInstalledPackIdsCached(ownerWallet);
|
|
4522
|
+
const topicEmbeddings = await ensureTopicEmbeddings(installedPacks);
|
|
4523
|
+
if (topicEmbeddings.size > 0) {
|
|
4524
|
+
const semantic = semanticTagMatches(
|
|
4525
|
+
summaryEmbedding,
|
|
4526
|
+
Array.from(topicEmbeddings, ([topicId, embedding]) => ({ topicId, embedding }))
|
|
4527
|
+
);
|
|
4528
|
+
if (semantic.length > 0) {
|
|
4529
|
+
const { data: row } = await db.from("memories").select("tags").eq("id", memoryId).maybeSingle();
|
|
4530
|
+
const existing = row?.tags ?? [];
|
|
4531
|
+
const merged = Array.from(/* @__PURE__ */ new Set([...existing, ...semantic]));
|
|
4532
|
+
if (merged.length !== existing.length) {
|
|
4533
|
+
await db.from("memories").update({ tags: merged }).eq("id", memoryId);
|
|
4534
|
+
log16.debug({
|
|
4535
|
+
memoryId,
|
|
4536
|
+
added: semantic.filter((t) => !existing.includes(t))
|
|
4537
|
+
}, "Semantic pack tags appended");
|
|
4538
|
+
}
|
|
4539
|
+
}
|
|
4540
|
+
}
|
|
4541
|
+
} catch (err) {
|
|
4542
|
+
log16.debug({ err, memoryId }, "Semantic tagging skipped");
|
|
2560
4543
|
}
|
|
2561
|
-
|
|
4544
|
+
log16.debug({ memoryId }, "Memory embedded");
|
|
2562
4545
|
}
|
|
2563
4546
|
async function expandQuery(query) {
|
|
2564
4547
|
if (!isOpenRouterEnabled()) return [query];
|
|
@@ -2576,10 +4559,10 @@ async function expandQuery(query) {
|
|
|
2576
4559
|
new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), 3e3))
|
|
2577
4560
|
]);
|
|
2578
4561
|
const expansions = response.split("\n").map((l) => l.trim()).filter((l) => l.length > 5 && l.length < 200).slice(0, 3);
|
|
2579
|
-
|
|
4562
|
+
log16.debug({ original: query, expansions: expansions.length }, "Query expanded");
|
|
2580
4563
|
return [query, ...expansions];
|
|
2581
4564
|
} catch (err) {
|
|
2582
|
-
|
|
4565
|
+
log16.debug({ err }, "Query expansion failed, using original");
|
|
2583
4566
|
return [query];
|
|
2584
4567
|
}
|
|
2585
4568
|
}
|
|
@@ -2592,11 +4575,12 @@ async function recallMemories(opts) {
|
|
|
2592
4575
|
let vectorScores = opts._vectorScores || /* @__PURE__ */ new Map();
|
|
2593
4576
|
let primaryQueryEmbedding = null;
|
|
2594
4577
|
const vectorSearchPromise = queries.length > 0 && isEmbeddingEnabled() && !opts._vectorScores ? (async () => {
|
|
4578
|
+
const space = activeEmbeddingSpace();
|
|
2595
4579
|
const queryEmbeddings = await Promise.all(
|
|
2596
4580
|
queries.map(async (q) => {
|
|
2597
4581
|
const cached = getCachedEmbedding2(q);
|
|
2598
4582
|
if (cached) return cached;
|
|
2599
|
-
const emb = await
|
|
4583
|
+
const emb = await generateQueryEmbeddingForSpace(space, q);
|
|
2600
4584
|
if (emb) setCachedEmbedding2(q, emb);
|
|
2601
4585
|
return emb;
|
|
2602
4586
|
})
|
|
@@ -2604,39 +4588,69 @@ async function recallMemories(opts) {
|
|
|
2604
4588
|
const validEmbeddings = queryEmbeddings.filter((e) => e !== null);
|
|
2605
4589
|
if (validEmbeddings.length > 0) primaryQueryEmbedding = validEmbeddings[0];
|
|
2606
4590
|
if (validEmbeddings.length === 0) {
|
|
2607
|
-
|
|
4591
|
+
log16.debug("All query embeddings returned null, using keyword-only retrieval");
|
|
2608
4592
|
return;
|
|
2609
4593
|
}
|
|
2610
4594
|
try {
|
|
2611
|
-
const allSearches = validEmbeddings.
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
4595
|
+
const allSearches = validEmbeddings.flatMap((emb) => {
|
|
4596
|
+
const searches = [
|
|
4597
|
+
Promise.resolve(db.rpc(vectorRpcName("match_memories"), {
|
|
4598
|
+
query_embedding: JSON.stringify(emb),
|
|
4599
|
+
match_threshold: VECTOR_MATCH_THRESHOLD,
|
|
4600
|
+
match_count: limit * (opts.skipExpansion ? 12 : 4),
|
|
4601
|
+
filter_types: opts.memoryTypes || null,
|
|
4602
|
+
filter_user: opts.relatedUser || null,
|
|
4603
|
+
min_decay: minDecay,
|
|
4604
|
+
filter_owner: getOwnerScope(),
|
|
4605
|
+
filter_tags: opts.tags && opts.tags.length > 0 ? opts.tags : null
|
|
4606
|
+
})).then((r) => r.data || [])
|
|
4607
|
+
];
|
|
4608
|
+
if (!opts.skipExpansion) {
|
|
4609
|
+
searches.push(
|
|
4610
|
+
Promise.resolve(db.rpc(vectorRpcName("match_memory_fragments"), buildFragmentRpcArgs({
|
|
4611
|
+
embeddingJson: JSON.stringify(emb),
|
|
4612
|
+
matchThreshold: VECTOR_MATCH_THRESHOLD,
|
|
4613
|
+
matchCount: limit * 2,
|
|
4614
|
+
minDecay,
|
|
4615
|
+
memoryTypes: opts.memoryTypes
|
|
4616
|
+
}))).then((r) => r.data || [])
|
|
4617
|
+
);
|
|
4618
|
+
}
|
|
4619
|
+
return searches;
|
|
4620
|
+
});
|
|
2623
4621
|
const results2 = await Promise.all(allSearches);
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
4622
|
+
let fragmentHits = 0;
|
|
4623
|
+
if (opts.skipExpansion) {
|
|
4624
|
+
for (const batch of results2) {
|
|
4625
|
+
for (const m of batch) {
|
|
4626
|
+
const current = vectorScores.get(m.id) || 0;
|
|
4627
|
+
vectorScores.set(m.id, Math.max(current, m.similarity));
|
|
4628
|
+
}
|
|
4629
|
+
}
|
|
4630
|
+
} else {
|
|
4631
|
+
for (let i = 0; i < results2.length; i++) {
|
|
4632
|
+
const isFragmentBatch = i % 2 === 1;
|
|
4633
|
+
for (const r of results2[i]) {
|
|
4634
|
+
const id = isFragmentBatch ? r.memory_id : r.id;
|
|
4635
|
+
const sim = isFragmentBatch ? r.max_similarity : r.similarity;
|
|
4636
|
+
if (!id) continue;
|
|
4637
|
+
const current = vectorScores.get(id) || 0;
|
|
4638
|
+
vectorScores.set(id, Math.max(current, sim));
|
|
4639
|
+
if (isFragmentBatch) fragmentHits++;
|
|
4640
|
+
}
|
|
2628
4641
|
}
|
|
2629
4642
|
}
|
|
2630
|
-
|
|
4643
|
+
log16.debug({
|
|
2631
4644
|
queryVariants: validEmbeddings.length,
|
|
2632
4645
|
uniqueMemories: vectorScores.size,
|
|
4646
|
+
fragmentHits,
|
|
2633
4647
|
fastMode: !!opts.skipExpansion
|
|
2634
4648
|
}, "Vector search completed");
|
|
2635
4649
|
} catch (err) {
|
|
2636
|
-
|
|
4650
|
+
log16.warn({ err }, "Vector search RPC failed, falling back to keyword retrieval");
|
|
2637
4651
|
}
|
|
2638
4652
|
})() : Promise.resolve();
|
|
2639
|
-
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);
|
|
4653
|
+
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);
|
|
2640
4654
|
importanceQuery = scopeToOwner(importanceQuery);
|
|
2641
4655
|
if (opts.memoryTypes && opts.memoryTypes.length > 0) {
|
|
2642
4656
|
importanceQuery = importanceQuery.in("memory_type", opts.memoryTypes);
|
|
@@ -2659,7 +4673,12 @@ async function recallMemories(opts) {
|
|
|
2659
4673
|
return await bm25SearchMemories(opts.query, {
|
|
2660
4674
|
limit: limit * 2,
|
|
2661
4675
|
minDecay,
|
|
2662
|
-
|
|
4676
|
+
// Use getOwnerWallet() (AsyncLocalStorage-aware), NOT the bare module-level
|
|
4677
|
+
// _ownerWallet. withOwnerWallet() sets the async context, not the module var,
|
|
4678
|
+
// so the bare var is null under hosted/wrapped calls and BM25 would search the
|
|
4679
|
+
// ENTIRE table unscoped — burying the owner's exact-match facts. This is why
|
|
4680
|
+
// BM25 silently never surfaced bench facts even after the RPC was fixed.
|
|
4681
|
+
filterOwner: getOwnerScope() || void 0,
|
|
2663
4682
|
filterTypes: opts.memoryTypes || void 0,
|
|
2664
4683
|
filterTags: opts.tags || void 0
|
|
2665
4684
|
});
|
|
@@ -2713,7 +4732,7 @@ async function recallMemories(opts) {
|
|
|
2713
4732
|
}
|
|
2714
4733
|
}
|
|
2715
4734
|
}
|
|
2716
|
-
if (seedsAdded > 0)
|
|
4735
|
+
if (seedsAdded > 0) log16.debug({ seedsAdded, seedsTotal: knowledgeSeeds.length }, "Knowledge seeds added to candidates");
|
|
2717
4736
|
}
|
|
2718
4737
|
}
|
|
2719
4738
|
const bm25Scores = /* @__PURE__ */ new Map();
|
|
@@ -2734,18 +4753,18 @@ async function recallMemories(opts) {
|
|
|
2734
4753
|
}
|
|
2735
4754
|
}
|
|
2736
4755
|
}
|
|
2737
|
-
|
|
4756
|
+
log16.debug({ bm25Hits: bm25Results.length, bm25New: bm25MissingIds.length }, "BM25 search added candidates");
|
|
2738
4757
|
}
|
|
2739
4758
|
if (error) {
|
|
2740
|
-
|
|
4759
|
+
log16.error({ error: error.message }, "Memory recall query failed");
|
|
2741
4760
|
return [];
|
|
2742
4761
|
}
|
|
2743
|
-
let candidates =
|
|
4762
|
+
let candidates = await decryptMemories(data || []);
|
|
2744
4763
|
if (vectorScores.size > 0) {
|
|
2745
4764
|
const metadataIds = new Set(candidates.map((m) => m.id));
|
|
2746
4765
|
const missingIds = [...vectorScores.keys()].filter((id) => !metadataIds.has(id));
|
|
2747
4766
|
if (missingIds.length > 0) {
|
|
2748
|
-
let vectorQuery = db.from("memories").select("*").in("id", missingIds);
|
|
4767
|
+
let vectorQuery = db.from("memories").select("*").in("id", missingIds).gte("decay_factor", minDecay).not("embedding", "is", null);
|
|
2749
4768
|
vectorQuery = scopeToOwner(vectorQuery);
|
|
2750
4769
|
if (opts.memoryTypes && opts.memoryTypes.length > 0) {
|
|
2751
4770
|
vectorQuery = vectorQuery.in("memory_type", opts.memoryTypes);
|
|
@@ -2754,7 +4773,7 @@ async function recallMemories(opts) {
|
|
|
2754
4773
|
vectorQuery = vectorQuery.overlaps("tags", opts.tags);
|
|
2755
4774
|
}
|
|
2756
4775
|
const { data: vectorOnly } = await vectorQuery;
|
|
2757
|
-
if (vectorOnly) candidates = [...candidates, ...
|
|
4776
|
+
if (vectorOnly) candidates = [...candidates, ...await decryptMemories(vectorOnly)];
|
|
2758
4777
|
}
|
|
2759
4778
|
}
|
|
2760
4779
|
if (candidates.length === 0) return [];
|
|
@@ -2778,10 +4797,10 @@ async function recallMemories(opts) {
|
|
|
2778
4797
|
if (entities.length > 0) {
|
|
2779
4798
|
const resultIdSet = new Set(results.map((m) => m.id));
|
|
2780
4799
|
for (const entity of entities) {
|
|
2781
|
-
const entityMemories = await getMemoriesByEntity(entity.id, {
|
|
4800
|
+
const entityMemories = await decryptMemories(await getMemoriesByEntity(entity.id, {
|
|
2782
4801
|
limit: Math.ceil(limit / 2),
|
|
2783
4802
|
memoryTypes: opts.memoryTypes
|
|
2784
|
-
});
|
|
4803
|
+
}));
|
|
2785
4804
|
for (const mem of entityMemories) {
|
|
2786
4805
|
addLinkPath(mem.id, "entity");
|
|
2787
4806
|
if (!resultIdSet.has(mem.id)) {
|
|
@@ -2793,17 +4812,17 @@ async function recallMemories(opts) {
|
|
|
2793
4812
|
}
|
|
2794
4813
|
}
|
|
2795
4814
|
}
|
|
2796
|
-
|
|
4815
|
+
log16.debug({ entities: entities.map((e) => e.name) }, "Entity-aware recall applied");
|
|
2797
4816
|
let cooccurrenceAdded = 0;
|
|
2798
4817
|
const cooccurrenceNames = [];
|
|
2799
4818
|
for (const entity of entities) {
|
|
2800
4819
|
const cooccurrences = await getEntityCooccurrences(entity.id, { minCooccurrence: 2, maxResults: 3 });
|
|
2801
4820
|
for (const cooc of cooccurrences) {
|
|
2802
4821
|
if (cooccurrenceAdded >= limit) break;
|
|
2803
|
-
const coMems = await getMemoriesByEntity(cooc.related_entity_id, {
|
|
4822
|
+
const coMems = await decryptMemories(await getMemoriesByEntity(cooc.related_entity_id, {
|
|
2804
4823
|
limit: 3,
|
|
2805
4824
|
memoryTypes: opts.memoryTypes
|
|
2806
|
-
});
|
|
4825
|
+
}));
|
|
2807
4826
|
for (const mem of coMems) {
|
|
2808
4827
|
if (cooccurrenceAdded >= limit) break;
|
|
2809
4828
|
addLinkPath(mem.id, "entity");
|
|
@@ -2823,11 +4842,11 @@ async function recallMemories(opts) {
|
|
|
2823
4842
|
}
|
|
2824
4843
|
}
|
|
2825
4844
|
if (cooccurrenceAdded > 0) {
|
|
2826
|
-
|
|
4845
|
+
log16.debug({ cooccurrenceAdded, cooccurrenceEntities: cooccurrenceNames.length }, "Entity co-occurrence recall applied");
|
|
2827
4846
|
}
|
|
2828
4847
|
}
|
|
2829
4848
|
} catch (err) {
|
|
2830
|
-
|
|
4849
|
+
log16.debug({ err }, "Entity-aware recall skipped");
|
|
2831
4850
|
}
|
|
2832
4851
|
}
|
|
2833
4852
|
if (results.length > 0) {
|
|
@@ -2837,17 +4856,17 @@ async function recallMemories(opts) {
|
|
|
2837
4856
|
seed_ids: seedIds,
|
|
2838
4857
|
min_strength: 0.2,
|
|
2839
4858
|
max_results: limit,
|
|
2840
|
-
filter_owner:
|
|
4859
|
+
filter_owner: getOwnerScope()
|
|
2841
4860
|
});
|
|
2842
4861
|
if (linked && linked.length > 0) {
|
|
2843
4862
|
const resultIdSet = new Set(seedIds);
|
|
2844
4863
|
const graphCandidateIds = linked.filter((l) => !resultIdSet.has(l.memory_id)).map((l) => l.memory_id);
|
|
2845
4864
|
if (graphCandidateIds.length > 0) {
|
|
2846
|
-
let graphQuery = db.from("memories").select("*").in("id", graphCandidateIds);
|
|
4865
|
+
let graphQuery = db.from("memories").select("*").in("id", graphCandidateIds).not("provider_delegated", "is", false);
|
|
2847
4866
|
graphQuery = scopeToOwner(graphQuery);
|
|
2848
4867
|
const { data: graphMemories } = await graphQuery;
|
|
2849
4868
|
if (graphMemories && graphMemories.length > 0) {
|
|
2850
|
-
|
|
4869
|
+
await decryptMemories(graphMemories);
|
|
2851
4870
|
const linkBoostMap = /* @__PURE__ */ new Map();
|
|
2852
4871
|
for (const l of linked) {
|
|
2853
4872
|
const bondWeight = BOND_TYPE_WEIGHTS[l.link_type] ?? 0.4;
|
|
@@ -2861,7 +4880,7 @@ async function recallMemories(opts) {
|
|
|
2861
4880
|
_score: scoreMemory(mem, scoredOpts) + RETRIEVAL_WEIGHT_GRAPH * (linkBoostMap.get(mem.id) || 0)
|
|
2862
4881
|
}));
|
|
2863
4882
|
results = [...results, ...graphScored].sort((a, b) => b._score - a._score).slice(0, limit);
|
|
2864
|
-
|
|
4883
|
+
log16.debug({
|
|
2865
4884
|
graphExpanded: graphMemories.length,
|
|
2866
4885
|
linkedTotal: linked.length,
|
|
2867
4886
|
bondTypes: [...new Set(linked.map((l) => l.link_type))]
|
|
@@ -2870,7 +4889,7 @@ async function recallMemories(opts) {
|
|
|
2870
4889
|
}
|
|
2871
4890
|
}
|
|
2872
4891
|
} catch (err) {
|
|
2873
|
-
|
|
4892
|
+
log16.debug({ err }, "Graph expansion skipped (RPC unavailable)");
|
|
2874
4893
|
}
|
|
2875
4894
|
}
|
|
2876
4895
|
if (results.length >= 3) {
|
|
@@ -2886,23 +4905,23 @@ async function recallMemories(opts) {
|
|
|
2886
4905
|
...results.slice(0, results.length - replaceCount),
|
|
2887
4906
|
...diverseCandidates.slice(0, replaceCount)
|
|
2888
4907
|
].sort((a, b) => b._score - a._score).slice(0, limit);
|
|
2889
|
-
|
|
4908
|
+
log16.debug({ injectedTypes: diverseCandidates.map((m) => m.memory_type) }, "Type diversity applied");
|
|
2890
4909
|
}
|
|
2891
4910
|
}
|
|
2892
4911
|
}
|
|
2893
|
-
const
|
|
2894
|
-
|
|
2895
|
-
const
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
log10.warn({ stripped: beforeCount - results.length, owner: finalOwner }, "Owner guard stripped foreign memories from recall results");
|
|
4912
|
+
const finalScope = getOwnerScope();
|
|
4913
|
+
{
|
|
4914
|
+
const { kept, stripped } = applyOwnerPostGuard(results, finalScope);
|
|
4915
|
+
if (stripped > 0) {
|
|
4916
|
+
log16.warn({ stripped, botOwn: finalScope === SCOPE_BOT_OWN2 }, "Owner guard stripped foreign memories from recall results");
|
|
2899
4917
|
}
|
|
4918
|
+
results = kept;
|
|
2900
4919
|
}
|
|
2901
|
-
if (opts
|
|
4920
|
+
if (shouldTrackAccess(opts)) {
|
|
2902
4921
|
const ids = results.map((m) => m.id);
|
|
2903
4922
|
const sources = results.map((m) => m.source || "");
|
|
2904
|
-
updateMemoryAccess(ids, sources).catch((err) =>
|
|
2905
|
-
reinforceCoRetrievedLinks(ids).catch((err) =>
|
|
4923
|
+
updateMemoryAccess(ids, sources).catch((err) => log16.warn({ err }, "Memory access tracking failed"));
|
|
4924
|
+
reinforceCoRetrievedLinks(ids).catch((err) => log16.debug({ err }, "Link reinforcement failed"));
|
|
2906
4925
|
}
|
|
2907
4926
|
for (const m of results) {
|
|
2908
4927
|
const paths = linkPathMap.get(m.id);
|
|
@@ -2910,7 +4929,7 @@ async function recallMemories(opts) {
|
|
|
2910
4929
|
m.link_path = [...paths];
|
|
2911
4930
|
}
|
|
2912
4931
|
}
|
|
2913
|
-
|
|
4932
|
+
log16.debug({
|
|
2914
4933
|
recalled: results.length,
|
|
2915
4934
|
topScore: results[0]?._score?.toFixed(3),
|
|
2916
4935
|
query: opts.query?.slice(0, 40),
|
|
@@ -2920,7 +4939,7 @@ async function recallMemories(opts) {
|
|
|
2920
4939
|
}, "Memories recalled");
|
|
2921
4940
|
return results;
|
|
2922
4941
|
} catch (err) {
|
|
2923
|
-
|
|
4942
|
+
log16.error({ err }, "Memory recall failed");
|
|
2924
4943
|
return [];
|
|
2925
4944
|
}
|
|
2926
4945
|
}
|
|
@@ -2934,7 +4953,7 @@ function extractQueryTerms(query) {
|
|
|
2934
4953
|
}
|
|
2935
4954
|
function scoreMemory(mem, opts) {
|
|
2936
4955
|
const now = Date.now();
|
|
2937
|
-
const hoursSinceAccess = (now - new Date(mem.last_accessed).getTime()) / (1e3 * 60 * 60);
|
|
4956
|
+
const hoursSinceAccess = Math.max(0, (now - new Date(mem.last_accessed).getTime()) / (1e3 * 60 * 60));
|
|
2938
4957
|
const recency = Math.pow(RECENCY_DECAY_BASE, hoursSinceAccess);
|
|
2939
4958
|
let textScore = 0.5;
|
|
2940
4959
|
if (opts.query) {
|
|
@@ -2970,7 +4989,7 @@ function scoreMemory(mem, opts) {
|
|
|
2970
4989
|
}
|
|
2971
4990
|
const bm25Rank = opts._bm25Scores?.get(mem.id) || 0;
|
|
2972
4991
|
if (bm25Rank > 0) {
|
|
2973
|
-
rawScore +=
|
|
4992
|
+
rawScore += RETRIEVAL_WEIGHT_BM25 * Math.min(bm25Rank, 1);
|
|
2974
4993
|
}
|
|
2975
4994
|
const typeBoost = KNOWLEDGE_TYPE_BOOST[mem.memory_type] || 0;
|
|
2976
4995
|
rawScore += typeBoost;
|
|
@@ -2991,7 +5010,7 @@ async function recallMemorySummaries(opts) {
|
|
|
2991
5010
|
const limit = opts.limit || 10;
|
|
2992
5011
|
const minDecay = opts.minDecay ?? 0.1;
|
|
2993
5012
|
try {
|
|
2994
|
-
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);
|
|
5013
|
+
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);
|
|
2995
5014
|
query = scopeToOwner(query);
|
|
2996
5015
|
if (opts.memoryTypes && opts.memoryTypes.length > 0) {
|
|
2997
5016
|
query = query.in("memory_type", opts.memoryTypes);
|
|
@@ -3010,12 +5029,12 @@ async function recallMemorySummaries(opts) {
|
|
|
3010
5029
|
}
|
|
3011
5030
|
const { data, error } = await query;
|
|
3012
5031
|
if (error) {
|
|
3013
|
-
|
|
5032
|
+
log16.error({ error: error.message }, "Memory summary recall failed");
|
|
3014
5033
|
return [];
|
|
3015
5034
|
}
|
|
3016
5035
|
return data || [];
|
|
3017
5036
|
} catch (err) {
|
|
3018
|
-
|
|
5037
|
+
log16.error({ err }, "Memory summary recall failed");
|
|
3019
5038
|
return [];
|
|
3020
5039
|
}
|
|
3021
5040
|
}
|
|
@@ -3027,28 +5046,23 @@ async function hydrateMemories(ids) {
|
|
|
3027
5046
|
query = scopeToOwner(query);
|
|
3028
5047
|
const { data, error } = await query;
|
|
3029
5048
|
if (error) {
|
|
3030
|
-
|
|
5049
|
+
log16.error({ error: error.message }, "Memory hydration failed");
|
|
3031
5050
|
return [];
|
|
3032
5051
|
}
|
|
3033
|
-
return
|
|
5052
|
+
return await decryptMemories(data || []);
|
|
3034
5053
|
} catch (err) {
|
|
3035
|
-
|
|
5054
|
+
log16.error({ err }, "Memory hydration failed");
|
|
3036
5055
|
return [];
|
|
3037
5056
|
}
|
|
3038
5057
|
}
|
|
3039
|
-
async function updateMemoryAccess(ids,
|
|
5058
|
+
async function updateMemoryAccess(ids, _sources = []) {
|
|
3040
5059
|
if (ids.length === 0) return;
|
|
3041
5060
|
const db = getDb();
|
|
3042
|
-
const importanceBoosts = ids.map((_, i) => {
|
|
3043
|
-
const source = sources[i] || "";
|
|
3044
|
-
return INTERNAL_MEMORY_SOURCES.has(source) ? INTERNAL_IMPORTANCE_BOOST : 0.02;
|
|
3045
|
-
});
|
|
3046
5061
|
const { error } = await db.rpc("batch_boost_memory_access", {
|
|
3047
|
-
memory_ids: ids
|
|
3048
|
-
importance_boosts: importanceBoosts
|
|
5062
|
+
memory_ids: ids
|
|
3049
5063
|
});
|
|
3050
5064
|
if (error) {
|
|
3051
|
-
|
|
5065
|
+
log16.warn({ error: error.message, ids }, "Batch memory access update failed");
|
|
3052
5066
|
}
|
|
3053
5067
|
}
|
|
3054
5068
|
async function createMemoryLink(sourceId, targetId, linkType, strength = 0.5) {
|
|
@@ -3061,7 +5075,7 @@ async function createMemoryLink(sourceId, targetId, linkType, strength = 0.5) {
|
|
|
3061
5075
|
strength: clamp(strength, 0, 1)
|
|
3062
5076
|
}, { onConflict: "source_id,target_id,link_type" });
|
|
3063
5077
|
if (error) {
|
|
3064
|
-
|
|
5078
|
+
log16.debug({ error: error.message, sourceId, targetId, linkType }, "Link creation failed");
|
|
3065
5079
|
}
|
|
3066
5080
|
}
|
|
3067
5081
|
async function createMemoryLinksBatch(links) {
|
|
@@ -3083,7 +5097,7 @@ async function createMemoryLinksBatch(links) {
|
|
|
3083
5097
|
const db = getDb();
|
|
3084
5098
|
const { error } = await db.from("memory_links").upsert(rows, { onConflict: "source_id,target_id,link_type" });
|
|
3085
5099
|
if (error) {
|
|
3086
|
-
|
|
5100
|
+
log16.debug({ error: error.message, count: rows.length }, "Batch link creation failed");
|
|
3087
5101
|
}
|
|
3088
5102
|
}
|
|
3089
5103
|
async function autoLinkMemory(memoryId, opts) {
|
|
@@ -3102,7 +5116,7 @@ async function autoLinkMemory(memoryId, opts) {
|
|
|
3102
5116
|
query_embedding: JSON.stringify(embedding),
|
|
3103
5117
|
match_threshold: LINK_SIMILARITY_THRESHOLD,
|
|
3104
5118
|
match_count: MAX_AUTO_LINKS * 2,
|
|
3105
|
-
filter_owner:
|
|
5119
|
+
filter_owner: getOwnerScope()
|
|
3106
5120
|
});
|
|
3107
5121
|
if (similar) {
|
|
3108
5122
|
const similarIds = similar.map((s) => s.id).filter((id) => id !== memoryId);
|
|
@@ -3151,7 +5165,7 @@ async function autoLinkMemory(memoryId, opts) {
|
|
|
3151
5165
|
}
|
|
3152
5166
|
if (pendingLinks.length > 0) {
|
|
3153
5167
|
await createMemoryLinksBatch(pendingLinks);
|
|
3154
|
-
|
|
5168
|
+
log16.debug({ memoryId, linksCreated: pendingLinks.length }, "Auto-linked memory");
|
|
3155
5169
|
}
|
|
3156
5170
|
}
|
|
3157
5171
|
function classifyLinkType(newMem, candidate, newConcepts) {
|
|
@@ -3173,16 +5187,16 @@ async function reinforceCoRetrievedLinks(ids) {
|
|
|
3173
5187
|
boost_amount: LINK_CO_RETRIEVAL_BOOST
|
|
3174
5188
|
});
|
|
3175
5189
|
if (error) {
|
|
3176
|
-
|
|
5190
|
+
log16.debug({ error: error.message }, "Link reinforcement RPC failed");
|
|
3177
5191
|
} else if (data && data > 0) {
|
|
3178
|
-
|
|
5192
|
+
log16.debug({ boosted: data }, "Co-retrieval link reinforcement applied");
|
|
3179
5193
|
}
|
|
3180
5194
|
}
|
|
3181
5195
|
async function extractAndLinkEntitiesForMemory(memoryId, opts) {
|
|
3182
5196
|
try {
|
|
3183
5197
|
await extractAndLinkEntities(memoryId, opts.content, opts.summary, opts.relatedUser);
|
|
3184
5198
|
} catch (err) {
|
|
3185
|
-
|
|
5199
|
+
log16.debug({ err, memoryId }, "Entity extraction failed");
|
|
3186
5200
|
}
|
|
3187
5201
|
}
|
|
3188
5202
|
async function decayMemories() {
|
|
@@ -3198,17 +5212,17 @@ async function decayMemories() {
|
|
|
3198
5212
|
cutoff
|
|
3199
5213
|
});
|
|
3200
5214
|
if (error) {
|
|
3201
|
-
|
|
5215
|
+
log16.warn({ error: error.message, memType }, "Batch decay failed for type");
|
|
3202
5216
|
continue;
|
|
3203
5217
|
}
|
|
3204
5218
|
totalDecayed += data || 0;
|
|
3205
5219
|
}
|
|
3206
5220
|
if (totalDecayed > 0) {
|
|
3207
|
-
|
|
5221
|
+
log16.info({ decayed: totalDecayed }, "Type-specific memory decay applied");
|
|
3208
5222
|
}
|
|
3209
5223
|
return totalDecayed;
|
|
3210
5224
|
} catch (err) {
|
|
3211
|
-
|
|
5225
|
+
log16.error({ err }, "Memory decay failed");
|
|
3212
5226
|
return 0;
|
|
3213
5227
|
}
|
|
3214
5228
|
}
|
|
@@ -3218,7 +5232,7 @@ async function deleteMemory(id) {
|
|
|
3218
5232
|
query = scopeToOwner(query);
|
|
3219
5233
|
const { error } = await query;
|
|
3220
5234
|
if (error) {
|
|
3221
|
-
|
|
5235
|
+
log16.error({ error: error.message, id }, "Failed to delete memory");
|
|
3222
5236
|
return false;
|
|
3223
5237
|
}
|
|
3224
5238
|
return true;
|
|
@@ -3236,7 +5250,7 @@ async function updateMemory(id, patches) {
|
|
|
3236
5250
|
query = scopeToOwner(query);
|
|
3237
5251
|
const { error } = await query;
|
|
3238
5252
|
if (error) {
|
|
3239
|
-
|
|
5253
|
+
log16.error({ error: error.message, id }, "Failed to update memory");
|
|
3240
5254
|
return false;
|
|
3241
5255
|
}
|
|
3242
5256
|
return true;
|
|
@@ -3257,10 +5271,10 @@ async function listMemories(opts) {
|
|
|
3257
5271
|
if (opts.min_importance !== void 0) dataQ = dataQ.gte("importance", opts.min_importance);
|
|
3258
5272
|
const { data, error } = await dataQ;
|
|
3259
5273
|
if (error) {
|
|
3260
|
-
|
|
5274
|
+
log16.error({ error: error.message }, "Failed to list memories");
|
|
3261
5275
|
return { memories: [], total: 0 };
|
|
3262
5276
|
}
|
|
3263
|
-
return { memories:
|
|
5277
|
+
return { memories: await decryptMemories(data || []), total: count ?? 0 };
|
|
3264
5278
|
}
|
|
3265
5279
|
async function getMemoryStats() {
|
|
3266
5280
|
const db = getDb();
|
|
@@ -3278,25 +5292,31 @@ async function getMemoryStats() {
|
|
|
3278
5292
|
embeddedCount: 0
|
|
3279
5293
|
};
|
|
3280
5294
|
try {
|
|
3281
|
-
let countQuery = db.from("memories").select("id", { count: "exact", head: true })
|
|
5295
|
+
let countQuery = db.from("memories").select("id", { count: "exact", head: true });
|
|
3282
5296
|
countQuery = scopeToOwner(countQuery);
|
|
3283
5297
|
const { count: totalCount } = await countQuery;
|
|
3284
5298
|
stats.total = totalCount || 0;
|
|
3285
|
-
let embeddedQuery = db.from("memories").select("id", { count: "exact", head: true }).
|
|
5299
|
+
let embeddedQuery = db.from("memories").select("id", { count: "exact", head: true }).not("embedding", "is", null);
|
|
3286
5300
|
embeddedQuery = scopeToOwner(embeddedQuery);
|
|
3287
5301
|
const { count: embCount } = await embeddedQuery;
|
|
3288
5302
|
stats.embeddedCount = embCount || 0;
|
|
3289
|
-
const
|
|
5303
|
+
const TYPES = ["episodic", "semantic", "procedural", "self_model", "introspective"];
|
|
5304
|
+
await Promise.all(TYPES.map(async (type) => {
|
|
5305
|
+
let q = db.from("memories").select("id", { count: "exact", head: true }).eq("memory_type", type);
|
|
5306
|
+
q = scopeToOwner(q);
|
|
5307
|
+
const { count: count2 } = await q;
|
|
5308
|
+
stats.byType[type] = count2 || 0;
|
|
5309
|
+
}));
|
|
5310
|
+
const PAGE_SIZE = 1e3;
|
|
5311
|
+
const MAX_PAGES = 20;
|
|
3290
5312
|
let allMemories = [];
|
|
3291
|
-
let page = 0;
|
|
3292
|
-
|
|
3293
|
-
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);
|
|
5313
|
+
for (let page = 0; page < MAX_PAGES; page++) {
|
|
5314
|
+
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);
|
|
3294
5315
|
pageQuery = scopeToOwner(pageQuery);
|
|
3295
5316
|
const { data: pageData } = await pageQuery;
|
|
3296
5317
|
if (!pageData || pageData.length === 0) break;
|
|
3297
5318
|
allMemories = allMemories.concat(pageData);
|
|
3298
5319
|
if (pageData.length < PAGE_SIZE) break;
|
|
3299
|
-
page++;
|
|
3300
5320
|
}
|
|
3301
5321
|
if (allMemories.length > 0) {
|
|
3302
5322
|
let impSum = 0;
|
|
@@ -3305,11 +5325,10 @@ async function getMemoryStats() {
|
|
|
3305
5325
|
const conceptCounts = {};
|
|
3306
5326
|
const users = /* @__PURE__ */ new Set();
|
|
3307
5327
|
for (const m of allMemories) {
|
|
3308
|
-
const type = m.memory_type;
|
|
3309
|
-
if (type in stats.byType) stats.byType[type]++;
|
|
3310
5328
|
impSum += m.importance;
|
|
3311
5329
|
decaySum += m.decay_factor;
|
|
3312
5330
|
if (m.related_user) users.add(m.related_user);
|
|
5331
|
+
if (m.owner_wallet) users.add(m.owner_wallet);
|
|
3313
5332
|
if (m.tags) {
|
|
3314
5333
|
for (const tag of m.tags) {
|
|
3315
5334
|
tagCounts[tag] = (tagCounts[tag] || 0) + 1;
|
|
@@ -3330,13 +5349,15 @@ async function getMemoryStats() {
|
|
|
3330
5349
|
stats.oldestMemory = sorted[0] || null;
|
|
3331
5350
|
stats.newestMemory = sorted[sorted.length - 1] || null;
|
|
3332
5351
|
}
|
|
3333
|
-
|
|
5352
|
+
let dreamQuery = db.from("dream_logs").select("id", { count: "exact", head: true });
|
|
5353
|
+
dreamQuery = scopeToOwner(dreamQuery);
|
|
5354
|
+
const { count, error: dreamError } = await dreamQuery;
|
|
3334
5355
|
if (dreamError) {
|
|
3335
|
-
|
|
5356
|
+
log16.warn({ error: dreamError.message }, "Failed to count dream logs");
|
|
3336
5357
|
}
|
|
3337
5358
|
stats.totalDreamSessions = count || 0;
|
|
3338
5359
|
} catch (err) {
|
|
3339
|
-
|
|
5360
|
+
log16.error({ err }, "Failed to get memory stats");
|
|
3340
5361
|
}
|
|
3341
5362
|
return stats;
|
|
3342
5363
|
}
|
|
@@ -3350,10 +5371,10 @@ async function getRecentMemories(hours, types, limit) {
|
|
|
3350
5371
|
}
|
|
3351
5372
|
const { data, error } = await query;
|
|
3352
5373
|
if (error) {
|
|
3353
|
-
|
|
5374
|
+
log16.error({ error: error.message }, "Failed to get recent memories");
|
|
3354
5375
|
return [];
|
|
3355
5376
|
}
|
|
3356
|
-
return
|
|
5377
|
+
return await decryptMemories(data || []);
|
|
3357
5378
|
}
|
|
3358
5379
|
async function getSelfModel() {
|
|
3359
5380
|
const db = getDb();
|
|
@@ -3361,10 +5382,10 @@ async function getSelfModel() {
|
|
|
3361
5382
|
query = scopeToOwner(query);
|
|
3362
5383
|
const { data, error } = await query;
|
|
3363
5384
|
if (error) {
|
|
3364
|
-
|
|
5385
|
+
log16.error({ error: error.message }, "Failed to get self model");
|
|
3365
5386
|
return [];
|
|
3366
5387
|
}
|
|
3367
|
-
return
|
|
5388
|
+
return await decryptMemories(data || []);
|
|
3368
5389
|
}
|
|
3369
5390
|
async function storeDreamLog(sessionType, inputMemoryIds, output, newMemoryIds) {
|
|
3370
5391
|
const db = getDb();
|
|
@@ -3372,54 +5393,48 @@ async function storeDreamLog(sessionType, inputMemoryIds, output, newMemoryIds)
|
|
|
3372
5393
|
session_type: sessionType,
|
|
3373
5394
|
input_memory_ids: inputMemoryIds,
|
|
3374
5395
|
output: output.slice(0, MEMORY_MAX_CONTENT_LENGTH),
|
|
3375
|
-
new_memories_created: newMemoryIds
|
|
5396
|
+
new_memories_created: newMemoryIds,
|
|
5397
|
+
// Stamp owner so getMemoryStats can scope the dream count (migration 021).
|
|
5398
|
+
owner_wallet: getOwnerWallet() === SCOPE_BOT_OWN2 ? null : getOwnerWallet()
|
|
3376
5399
|
});
|
|
3377
5400
|
if (error) {
|
|
3378
|
-
|
|
5401
|
+
log16.error({ error: error.message }, "Failed to store dream log");
|
|
3379
5402
|
}
|
|
3380
5403
|
}
|
|
3381
5404
|
function formatMemoryContext(memories) {
|
|
3382
5405
|
if (memories.length === 0) return "";
|
|
3383
5406
|
const lines = ["## Memory Recall"];
|
|
3384
|
-
const episodic = memories.filter((m) => m.memory_type === "episodic");
|
|
3385
|
-
const semantic = memories.filter((m) => m.memory_type === "semantic");
|
|
3386
|
-
const procedural = memories.filter((m) => m.memory_type === "procedural");
|
|
3387
|
-
const selfModel = memories.filter((m) => m.memory_type === "self_model");
|
|
3388
|
-
const introspective = memories.filter((m) => m.memory_type === "introspective");
|
|
5407
|
+
const episodic = memories.filter((m) => m.memory_type === "episodic").sort(byMemoryDateAsc);
|
|
5408
|
+
const semantic = memories.filter((m) => m.memory_type === "semantic").sort(byMemoryDateAsc);
|
|
5409
|
+
const procedural = memories.filter((m) => m.memory_type === "procedural").sort(byMemoryDateAsc);
|
|
5410
|
+
const selfModel = memories.filter((m) => m.memory_type === "self_model").sort(byMemoryDateAsc);
|
|
5411
|
+
const introspective = memories.filter((m) => m.memory_type === "introspective").sort(byMemoryDateAsc);
|
|
3389
5412
|
if (episodic.length > 0) {
|
|
3390
5413
|
lines.push("### Past Interactions");
|
|
3391
|
-
for (const m of episodic)
|
|
3392
|
-
lines.push(`- [${timeAgo(m.created_at)}] ${m.summary}`);
|
|
3393
|
-
}
|
|
5414
|
+
for (const m of episodic) lines.push(renderGroundedLine(m));
|
|
3394
5415
|
}
|
|
3395
5416
|
if (semantic.length > 0) {
|
|
3396
5417
|
lines.push("### Things You Know");
|
|
3397
|
-
for (const m of semantic)
|
|
3398
|
-
lines.push(`- ${m.summary}`);
|
|
3399
|
-
}
|
|
5418
|
+
for (const m of semantic) lines.push(renderGroundedLine(m));
|
|
3400
5419
|
}
|
|
3401
5420
|
if (procedural.length > 0) {
|
|
3402
5421
|
lines.push("### Learned Strategies (from past outcomes)");
|
|
3403
5422
|
for (const m of procedural) {
|
|
3404
5423
|
const meta = m.metadata;
|
|
3405
5424
|
const confidence = meta?.positiveRate != null ? ` [${Math.round(meta.positiveRate * 100)}% success rate, based on ${meta.basedOn || "?"} interactions]` : "";
|
|
3406
|
-
lines.push(
|
|
5425
|
+
lines.push(renderGroundedLine(m, confidence));
|
|
3407
5426
|
}
|
|
3408
5427
|
}
|
|
3409
5428
|
if (introspective.length > 0) {
|
|
3410
5429
|
lines.push("### Your Own Reflections");
|
|
3411
|
-
for (const m of introspective)
|
|
3412
|
-
lines.push(`- [${timeAgo(m.created_at)}] ${m.summary}`);
|
|
3413
|
-
}
|
|
5430
|
+
for (const m of introspective) lines.push(renderGroundedLine(m));
|
|
3414
5431
|
}
|
|
3415
5432
|
if (selfModel.length > 0) {
|
|
3416
5433
|
lines.push("### Self-Observations");
|
|
3417
|
-
for (const m of selfModel)
|
|
3418
|
-
lines.push(`- ${m.summary}`);
|
|
3419
|
-
}
|
|
5434
|
+
for (const m of selfModel) lines.push(renderGroundedLine(m));
|
|
3420
5435
|
}
|
|
3421
5436
|
lines.push("");
|
|
3422
|
-
lines.push(
|
|
5437
|
+
lines.push(CONTEXT_GROUNDING_RULES);
|
|
3423
5438
|
if (procedural.length > 0) {
|
|
3424
5439
|
lines.push("");
|
|
3425
5440
|
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.");
|
|
@@ -3444,10 +5459,10 @@ async function scoreImportanceWithLLM(description, fallbackOpts) {
|
|
|
3444
5459
|
if (!isNaN(parsed) && parsed >= 1 && parsed <= 10) {
|
|
3445
5460
|
return parsed / 10;
|
|
3446
5461
|
}
|
|
3447
|
-
|
|
5462
|
+
log16.warn({ response }, "LLM importance score unparseable, using fallback");
|
|
3448
5463
|
return calculateImportance(fallbackOpts || {});
|
|
3449
5464
|
} catch (err) {
|
|
3450
|
-
|
|
5465
|
+
log16.warn({ err }, "LLM importance scoring failed, using fallback");
|
|
3451
5466
|
return calculateImportance(fallbackOpts || {});
|
|
3452
5467
|
}
|
|
3453
5468
|
}
|
|
@@ -3471,7 +5486,8 @@ async function matchByEmbedding(opts) {
|
|
|
3471
5486
|
query_embedding: JSON.stringify(opts.embedding),
|
|
3472
5487
|
match_threshold: opts.threshold,
|
|
3473
5488
|
match_count: opts.limit,
|
|
3474
|
-
|
|
5489
|
+
// Explicit ownerWallet wins; otherwise the ambient scope (sentinel-aware, C0).
|
|
5490
|
+
filter_owner: opts.ownerWallet ?? getOwnerScope()
|
|
3475
5491
|
});
|
|
3476
5492
|
return (data ?? []).map((r) => ({
|
|
3477
5493
|
id: r.id,
|
|
@@ -3494,32 +5510,52 @@ function moodToValence(mood) {
|
|
|
3494
5510
|
return 0;
|
|
3495
5511
|
}
|
|
3496
5512
|
}
|
|
3497
|
-
var
|
|
5513
|
+
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;
|
|
3498
5514
|
var init_memory = __esm({
|
|
3499
5515
|
"packages/brain/src/memory/memory.ts"() {
|
|
3500
5516
|
"use strict";
|
|
3501
5517
|
init_database();
|
|
5518
|
+
init_config();
|
|
3502
5519
|
init_logger();
|
|
5520
|
+
init_memory_grounding();
|
|
3503
5521
|
init_utils();
|
|
3504
5522
|
init_claude_client();
|
|
3505
5523
|
init_solana_client();
|
|
5524
|
+
init_src();
|
|
3506
5525
|
init_embeddings();
|
|
5526
|
+
init_migration_profile();
|
|
5527
|
+
init_wiki_packs();
|
|
3507
5528
|
init_config2();
|
|
3508
5529
|
init_bm25_search();
|
|
5530
|
+
init_content_tokens();
|
|
5531
|
+
init_reconcile_shadow();
|
|
5532
|
+
init_memory_encryption();
|
|
3509
5533
|
init_openrouter_client();
|
|
3510
5534
|
init_encryption();
|
|
5535
|
+
init_memory_decryption();
|
|
3511
5536
|
init_event_bus();
|
|
3512
|
-
|
|
5537
|
+
import_crypto3 = require("crypto");
|
|
3513
5538
|
init_graph();
|
|
3514
5539
|
init_owner_context();
|
|
3515
5540
|
EMBED_CACHE_MAX = 200;
|
|
3516
5541
|
embeddingCache = /* @__PURE__ */ new Map();
|
|
3517
5542
|
_ownerWallet = null;
|
|
3518
|
-
|
|
5543
|
+
SCOPE_BOT_OWN2 = "__BOT_OWN__";
|
|
3519
5544
|
HASH_ID_PREFIX = "clude";
|
|
3520
|
-
|
|
5545
|
+
log16 = createChildLogger("memory");
|
|
3521
5546
|
DEDUP_TTL_MS = 10 * 60 * 1e3;
|
|
3522
5547
|
dedupCache = /* @__PURE__ */ new Map();
|
|
5548
|
+
installedPacksCache = /* @__PURE__ */ new Map();
|
|
5549
|
+
INSTALLED_PACKS_TTL_MS = 6e4;
|
|
5550
|
+
topicEmbeddingCache = /* @__PURE__ */ new Map();
|
|
5551
|
+
topicEmbeddingPopulationLock = null;
|
|
5552
|
+
TOKENISATION_SKIP_SOURCES = /* @__PURE__ */ new Set([
|
|
5553
|
+
"demo",
|
|
5554
|
+
"demo-maas",
|
|
5555
|
+
"locomo-benchmark",
|
|
5556
|
+
"longmemeval-benchmark"
|
|
5557
|
+
]);
|
|
5558
|
+
outboxDegradeLogged = false;
|
|
3523
5559
|
STOPWORDS = /* @__PURE__ */ new Set([
|
|
3524
5560
|
"the",
|
|
3525
5561
|
"a",
|
|
@@ -3604,18 +5640,92 @@ var init_memory = __esm({
|
|
|
3604
5640
|
"our",
|
|
3605
5641
|
"their"
|
|
3606
5642
|
]);
|
|
5643
|
+
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.`;
|
|
5644
|
+
}
|
|
5645
|
+
});
|
|
5646
|
+
|
|
5647
|
+
// packages/brain/src/memory/memory-revoke.ts
|
|
5648
|
+
async function revokeMemory(db, memoryId) {
|
|
5649
|
+
const { data: mem } = await db.from("memories").select("summary, embedding, encrypted, provider_delegated").eq("id", memoryId).maybeSingle();
|
|
5650
|
+
if (!mem || mem.encrypted !== true || mem.provider_delegated === false) {
|
|
5651
|
+
return { revoked: false, reason: "not_delegated" };
|
|
5652
|
+
}
|
|
5653
|
+
const { data: wrap } = await db.from("memory_dek_wraps").select("wrapped_dek, wrap_pubkey").eq("memory_id", memoryId).eq("recipient", "provider").maybeSingle();
|
|
5654
|
+
if (!wrap) return { revoked: false, reason: "no_provider_wrap" };
|
|
5655
|
+
let providerSecret;
|
|
5656
|
+
try {
|
|
5657
|
+
providerSecret = loadProviderKeypair().secretKey;
|
|
5658
|
+
} catch {
|
|
5659
|
+
return { revoked: false, reason: "no_provider_key" };
|
|
5660
|
+
}
|
|
5661
|
+
const dek = unwrapDek(wrap.wrapped_dek, wrap.wrap_pubkey, providerSecret);
|
|
5662
|
+
if (!dek) return { revoked: false, reason: "unwrap_failed" };
|
|
5663
|
+
const summaryCt = encryptField(String(mem.summary ?? ""), dek);
|
|
5664
|
+
const embeddingCt = mem.embedding != null ? encryptField(String(mem.embedding), dek) : "";
|
|
5665
|
+
const { error } = await db.rpc("revoke_memory", {
|
|
5666
|
+
p_memory_id: memoryId,
|
|
5667
|
+
p_summary_ct: summaryCt,
|
|
5668
|
+
p_embedding_ct: embeddingCt
|
|
5669
|
+
});
|
|
5670
|
+
if (error) {
|
|
5671
|
+
log17.error({ memoryId, error: error.message }, "revoke_memory RPC failed");
|
|
5672
|
+
return { revoked: false, reason: "rpc_failed" };
|
|
5673
|
+
}
|
|
5674
|
+
log17.info({ memoryId }, "Memory revoked \u2014 provider access destroyed");
|
|
5675
|
+
return { revoked: true };
|
|
5676
|
+
}
|
|
5677
|
+
async function redelegateMemory(db, memoryId, providerWrap) {
|
|
5678
|
+
const { data: mem } = await db.from("memories").select("content, summary_ciphertext, embedding_ciphertext, encrypted").eq("id", memoryId).maybeSingle();
|
|
5679
|
+
if (!mem || mem.encrypted !== true) return { redelegated: false, reason: "not_encrypted" };
|
|
5680
|
+
let providerSecret;
|
|
5681
|
+
try {
|
|
5682
|
+
providerSecret = loadProviderKeypair().secretKey;
|
|
5683
|
+
} catch {
|
|
5684
|
+
return { redelegated: false, reason: "no_provider_key" };
|
|
5685
|
+
}
|
|
5686
|
+
const dek = unwrapDek(providerWrap.wrapped_dek, providerWrap.wrap_pubkey, providerSecret);
|
|
5687
|
+
if (!dek) return { redelegated: false, reason: "invalid_wrap" };
|
|
5688
|
+
const content = decryptField(String(mem.content ?? ""), dek);
|
|
5689
|
+
if (content === null) return { redelegated: false, reason: "invalid_wrap" };
|
|
5690
|
+
const summary = mem.summary_ciphertext ? decryptField(mem.summary_ciphertext, dek) : "";
|
|
5691
|
+
const embedding = mem.embedding_ciphertext ? decryptField(mem.embedding_ciphertext, dek) : "";
|
|
5692
|
+
const { error } = await db.rpc("redelegate_memory", {
|
|
5693
|
+
p_memory_id: memoryId,
|
|
5694
|
+
p_summary: summary ?? "",
|
|
5695
|
+
p_embedding: embedding ?? "",
|
|
5696
|
+
p_content: content,
|
|
5697
|
+
p_wrapped_dek: providerWrap.wrapped_dek,
|
|
5698
|
+
p_wrap_pubkey: providerWrap.wrap_pubkey
|
|
5699
|
+
});
|
|
5700
|
+
if (error) {
|
|
5701
|
+
log17.error({ memoryId, error: error.message }, "redelegate_memory RPC failed");
|
|
5702
|
+
return { redelegated: false, reason: "rpc_failed" };
|
|
5703
|
+
}
|
|
5704
|
+
log17.info({ memoryId }, "Memory re-delegated \u2014 provider access restored");
|
|
5705
|
+
return { redelegated: true };
|
|
5706
|
+
}
|
|
5707
|
+
var log17;
|
|
5708
|
+
var init_memory_revoke = __esm({
|
|
5709
|
+
"packages/brain/src/memory/memory-revoke.ts"() {
|
|
5710
|
+
"use strict";
|
|
5711
|
+
init_memory_envelope();
|
|
5712
|
+
init_encryption_keys();
|
|
5713
|
+
init_logger();
|
|
5714
|
+
log17 = createChildLogger("memory-revoke");
|
|
3607
5715
|
}
|
|
3608
5716
|
});
|
|
3609
5717
|
|
|
3610
5718
|
// packages/brain/src/memory/index.ts
|
|
3611
5719
|
var memory_exports2 = {};
|
|
3612
5720
|
__export(memory_exports2, {
|
|
3613
|
-
SCOPE_BOT_OWN: () =>
|
|
5721
|
+
SCOPE_BOT_OWN: () => SCOPE_BOT_OWN2,
|
|
3614
5722
|
_setOwnerWallet: () => _setOwnerWallet,
|
|
3615
5723
|
calculateImportance: () => calculateImportance,
|
|
3616
5724
|
createMemoryLink: () => createMemoryLink,
|
|
3617
5725
|
createMemoryLinksBatch: () => createMemoryLinksBatch,
|
|
3618
5726
|
decayMemories: () => decayMemories,
|
|
5727
|
+
decryptMemories: () => decryptMemories,
|
|
5728
|
+
decryptOneContent: () => decryptOneContent,
|
|
3619
5729
|
deleteMemory: () => deleteMemory,
|
|
3620
5730
|
formatMemoryContext: () => formatMemoryContext,
|
|
3621
5731
|
generateHashId: () => generateHashId,
|
|
@@ -3625,11 +5735,14 @@ __export(memory_exports2, {
|
|
|
3625
5735
|
getSelfModel: () => getSelfModel,
|
|
3626
5736
|
hydrateMemories: () => hydrateMemories,
|
|
3627
5737
|
inferConcepts: () => inferConcepts,
|
|
5738
|
+
invalidateInstalledPacksCache: () => invalidateInstalledPacksCache,
|
|
3628
5739
|
isValidHashId: () => isValidHashId,
|
|
3629
5740
|
listMemories: () => listMemories,
|
|
3630
5741
|
moodToValence: () => moodToValence,
|
|
3631
5742
|
recallMemories: () => recallMemories,
|
|
3632
5743
|
recallMemorySummaries: () => recallMemorySummaries,
|
|
5744
|
+
redelegateMemory: () => redelegateMemory,
|
|
5745
|
+
revokeMemory: () => revokeMemory,
|
|
3633
5746
|
scopeToOwner: () => scopeToOwner,
|
|
3634
5747
|
scoreImportanceWithLLM: () => scoreImportanceWithLLM,
|
|
3635
5748
|
scoreMemory: () => scoreMemory,
|
|
@@ -3641,6 +5754,8 @@ var init_memory2 = __esm({
|
|
|
3641
5754
|
"packages/brain/src/memory/index.ts"() {
|
|
3642
5755
|
"use strict";
|
|
3643
5756
|
init_memory();
|
|
5757
|
+
init_memory_decryption();
|
|
5758
|
+
init_memory_revoke();
|
|
3644
5759
|
}
|
|
3645
5760
|
});
|
|
3646
5761
|
|
|
@@ -3770,7 +5885,7 @@ function shuffle(arr) {
|
|
|
3770
5885
|
async function runDeepConnectionPhase(ownerWallet) {
|
|
3771
5886
|
const SKIPPED = { linksCreated: 0, skipped: true, topLinks: [] };
|
|
3772
5887
|
if (process.env.JEPA_ENABLED !== "true") {
|
|
3773
|
-
|
|
5888
|
+
log18.debug("JEPA disabled \u2014 skipping deep connection phase");
|
|
3774
5889
|
return SKIPPED;
|
|
3775
5890
|
}
|
|
3776
5891
|
const jepaClient = getJepaClient();
|
|
@@ -3793,16 +5908,16 @@ async function runDeepConnectionPhase(ownerWallet) {
|
|
|
3793
5908
|
}
|
|
3794
5909
|
}
|
|
3795
5910
|
if (candidates.length === 0) {
|
|
3796
|
-
|
|
5911
|
+
log18.debug("No candidate memories \u2014 skipping deep connection phase");
|
|
3797
5912
|
return SKIPPED;
|
|
3798
5913
|
}
|
|
3799
5914
|
const queriedRecently = await fetchJepaQueriedSince(Date.now() - TWENTY_FOUR_HOURS_MS);
|
|
3800
5915
|
const toProcess = candidates.filter((m) => !queriedRecently.has(m.id));
|
|
3801
5916
|
if (toProcess.length === 0) {
|
|
3802
|
-
|
|
5917
|
+
log18.debug("All candidates JEPA-queried in last 24 h \u2014 skipping");
|
|
3803
5918
|
return SKIPPED;
|
|
3804
5919
|
}
|
|
3805
|
-
|
|
5920
|
+
log18.info({ total: candidates.length, toProcess: toProcess.length }, "Deep connection phase: processing memories");
|
|
3806
5921
|
const allLinks = [];
|
|
3807
5922
|
const topLinks = [];
|
|
3808
5923
|
for (const mem of toProcess) {
|
|
@@ -3888,16 +6003,16 @@ async function runDeepConnectionPhase(ownerWallet) {
|
|
|
3888
6003
|
new_memories_created: []
|
|
3889
6004
|
});
|
|
3890
6005
|
} catch (err) {
|
|
3891
|
-
|
|
6006
|
+
log18.warn({ err }, "Failed to write deep_connection dream log");
|
|
3892
6007
|
}
|
|
3893
|
-
|
|
6008
|
+
log18.info({ linksCreated: allLinks.length, topLinks: topLinks.length }, "Deep connection phase complete");
|
|
3894
6009
|
return {
|
|
3895
6010
|
linksCreated: allLinks.length,
|
|
3896
6011
|
skipped: false,
|
|
3897
6012
|
topLinks
|
|
3898
6013
|
};
|
|
3899
6014
|
}
|
|
3900
|
-
var
|
|
6015
|
+
var log18, TWENTY_FOUR_HOURS_MS, RECENT_SAMPLE, HIGH_IMPORTANCE_SAMPLE, HIGH_IMPORTANCE_THRESHOLD, EMBEDDING_SIMILARITY_THRESHOLD, MATCH_LIMIT, TOP_LINKS_PER_MEMORY, _jepaClient;
|
|
3901
6016
|
var init_deep_connection = __esm({
|
|
3902
6017
|
"packages/brain/src/memory/dream/deep-connection.ts"() {
|
|
3903
6018
|
"use strict";
|
|
@@ -3905,7 +6020,7 @@ var init_deep_connection = __esm({
|
|
|
3905
6020
|
init_logger();
|
|
3906
6021
|
init_memory();
|
|
3907
6022
|
init_jepa();
|
|
3908
|
-
|
|
6023
|
+
log18 = createChildLogger("dream-deep-connection");
|
|
3909
6024
|
TWENTY_FOUR_HOURS_MS = 24 * 60 * 60 * 1e3;
|
|
3910
6025
|
RECENT_SAMPLE = 15;
|
|
3911
6026
|
HIGH_IMPORTANCE_SAMPLE = 5;
|
|
@@ -3940,7 +6055,7 @@ async function throttlePost() {
|
|
|
3940
6055
|
const elapsed = now - lastPostTime;
|
|
3941
6056
|
if (elapsed < MIN_POST_GAP_MS) {
|
|
3942
6057
|
const wait = MIN_POST_GAP_MS - elapsed;
|
|
3943
|
-
|
|
6058
|
+
log19.debug({ waitMs: wait }, "Throttling post");
|
|
3944
6059
|
await new Promise((r) => setTimeout(r, wait));
|
|
3945
6060
|
}
|
|
3946
6061
|
lastPostTime = Date.now();
|
|
@@ -3952,7 +6067,7 @@ async function withRetry(fn) {
|
|
|
3952
6067
|
if (err?.code === 429 || err?.data?.status === 429) {
|
|
3953
6068
|
const retryAfter = Number(err?.rateLimit?.reset || 0) * 1e3 - Date.now();
|
|
3954
6069
|
const wait = Math.max(retryAfter, 6e4);
|
|
3955
|
-
|
|
6070
|
+
log19.warn({ waitMs: wait }, "X API 429 \u2014 backing off");
|
|
3956
6071
|
await new Promise((r) => setTimeout(r, wait));
|
|
3957
6072
|
return fn();
|
|
3958
6073
|
}
|
|
@@ -3973,20 +6088,20 @@ function smartTruncate(text, maxLength = MAX_TWEET_LENGTH) {
|
|
|
3973
6088
|
}
|
|
3974
6089
|
async function postReply(tweetId, text) {
|
|
3975
6090
|
const truncated = smartTruncate(stripEmDashes(text));
|
|
3976
|
-
|
|
6091
|
+
log19.info({ tweetId, originalLength: text.length, length: truncated.length }, "Posting reply");
|
|
3977
6092
|
await throttlePost();
|
|
3978
6093
|
const result = await withRetry(() => rwClient.v2.reply(truncated, tweetId));
|
|
3979
6094
|
return result.data.id;
|
|
3980
6095
|
}
|
|
3981
6096
|
async function postTweet(text) {
|
|
3982
6097
|
const truncated = smartTruncate(stripEmDashes(text));
|
|
3983
|
-
|
|
6098
|
+
log19.info({ originalLength: text.length, length: truncated.length }, "Posting tweet");
|
|
3984
6099
|
await throttlePost();
|
|
3985
6100
|
const result = await withRetry(() => rwClient.v2.tweet(truncated));
|
|
3986
6101
|
return result.data.id;
|
|
3987
6102
|
}
|
|
3988
6103
|
async function postThread(texts) {
|
|
3989
|
-
|
|
6104
|
+
log19.info({ count: texts.length }, "Posting thread");
|
|
3990
6105
|
const tweetIds = [];
|
|
3991
6106
|
let previousId;
|
|
3992
6107
|
for (const text of texts) {
|
|
@@ -4040,7 +6155,7 @@ function getUsernameOrId(userId) {
|
|
|
4040
6155
|
return _usernameCache.get(userId) || userId;
|
|
4041
6156
|
}
|
|
4042
6157
|
async function getMentions(sinceId) {
|
|
4043
|
-
|
|
6158
|
+
log19.debug({ sinceId }, "Fetching mentions");
|
|
4044
6159
|
const params = {
|
|
4045
6160
|
"tweet.fields": "created_at,author_id,conversation_id,text",
|
|
4046
6161
|
"expansions": "author_id",
|
|
@@ -4079,12 +6194,12 @@ async function getTweetWithContext(tweetId, depth = 3) {
|
|
|
4079
6194
|
}
|
|
4080
6195
|
return { tweet, parents };
|
|
4081
6196
|
} catch (err) {
|
|
4082
|
-
|
|
6197
|
+
log19.warn({ tweetId, err }, "Failed to fetch tweet context");
|
|
4083
6198
|
return { tweet: { id: tweetId, text: "" }, parents: [] };
|
|
4084
6199
|
}
|
|
4085
6200
|
}
|
|
4086
6201
|
async function searchHashtagTweets(sinceId, maxResults = 100) {
|
|
4087
|
-
|
|
6202
|
+
log19.info({ sinceId, maxResults }, "Searching campaign tweets");
|
|
4088
6203
|
try {
|
|
4089
6204
|
const query = `(@${BOT_X_HANDLE} OR #CludeHackathon OR ($CLUDE (hackathon OR "10 days" OR "growing a brain" OR "blockchain brain"))) -is:retweet`;
|
|
4090
6205
|
const params = {
|
|
@@ -4117,15 +6232,15 @@ async function searchHashtagTweets(sinceId, maxResults = 100) {
|
|
|
4117
6232
|
} : void 0
|
|
4118
6233
|
});
|
|
4119
6234
|
}
|
|
4120
|
-
|
|
6235
|
+
log19.info({ found: tweets.length }, "Campaign tweet search complete");
|
|
4121
6236
|
return tweets;
|
|
4122
6237
|
} catch (err) {
|
|
4123
|
-
|
|
6238
|
+
log19.error({ err }, "Failed to search campaign tweets");
|
|
4124
6239
|
return [];
|
|
4125
6240
|
}
|
|
4126
6241
|
}
|
|
4127
6242
|
async function searchTokenMentions(sinceId, maxResults = 50) {
|
|
4128
|
-
|
|
6243
|
+
log19.info({ sinceId, maxResults }, "Searching $CLUDE mentions");
|
|
4129
6244
|
try {
|
|
4130
6245
|
const query = `($CLUDE OR @${BOT_X_HANDLE}) -is:retweet`;
|
|
4131
6246
|
const params = {
|
|
@@ -4158,15 +6273,15 @@ async function searchTokenMentions(sinceId, maxResults = 50) {
|
|
|
4158
6273
|
} : void 0
|
|
4159
6274
|
});
|
|
4160
6275
|
}
|
|
4161
|
-
|
|
6276
|
+
log19.info({ found: tweets.length }, "Token mention search complete");
|
|
4162
6277
|
return tweets;
|
|
4163
6278
|
} catch (err) {
|
|
4164
|
-
|
|
6279
|
+
log19.error({ err }, "Failed to search token mentions");
|
|
4165
6280
|
return [];
|
|
4166
6281
|
}
|
|
4167
6282
|
}
|
|
4168
6283
|
async function getUserTweets(userId, sinceId, maxResults = 10) {
|
|
4169
|
-
|
|
6284
|
+
log19.debug({ userId, sinceId }, "Fetching user tweets");
|
|
4170
6285
|
try {
|
|
4171
6286
|
const params = {
|
|
4172
6287
|
"tweet.fields": "created_at,author_id,public_metrics",
|
|
@@ -4192,7 +6307,7 @@ async function getUserTweets(userId, sinceId, maxResults = 10) {
|
|
|
4192
6307
|
}
|
|
4193
6308
|
return tweets;
|
|
4194
6309
|
} catch (err) {
|
|
4195
|
-
|
|
6310
|
+
log19.warn({ userId, err }, "Failed to fetch user tweets");
|
|
4196
6311
|
return [];
|
|
4197
6312
|
}
|
|
4198
6313
|
}
|
|
@@ -4219,7 +6334,7 @@ async function refreshTweetMetrics(tweetIds) {
|
|
|
4219
6334
|
}
|
|
4220
6335
|
}
|
|
4221
6336
|
} catch (err) {
|
|
4222
|
-
|
|
6337
|
+
log19.warn({ err, batchSize: batch.length }, "Failed to refresh tweet metrics batch");
|
|
4223
6338
|
}
|
|
4224
6339
|
}
|
|
4225
6340
|
return metricsMap;
|
|
@@ -4235,11 +6350,11 @@ async function getUserById(userId) {
|
|
|
4235
6350
|
});
|
|
4236
6351
|
return result.data;
|
|
4237
6352
|
} catch (err) {
|
|
4238
|
-
|
|
6353
|
+
log19.warn({ userId, err }, "Failed to fetch user");
|
|
4239
6354
|
return null;
|
|
4240
6355
|
}
|
|
4241
6356
|
}
|
|
4242
|
-
var import_twitter_api_v2,
|
|
6357
|
+
var import_twitter_api_v2, log19, client, rwClient, MIN_POST_GAP_MS, lastPostTime, MAX_TWEET_LENGTH, _usernameCache;
|
|
4243
6358
|
var init_x_client = __esm({
|
|
4244
6359
|
"packages/shared/src/core/x-client.ts"() {
|
|
4245
6360
|
"use strict";
|
|
@@ -4247,7 +6362,7 @@ var init_x_client = __esm({
|
|
|
4247
6362
|
init_config();
|
|
4248
6363
|
init_logger();
|
|
4249
6364
|
init_constants();
|
|
4250
|
-
|
|
6365
|
+
log19 = createChildLogger("x-client");
|
|
4251
6366
|
client = new import_twitter_api_v2.TwitterApi({
|
|
4252
6367
|
appKey: config.x.apiKey,
|
|
4253
6368
|
appSecret: config.x.apiSecret,
|
|
@@ -4293,10 +6408,10 @@ Trigger: "${record.trigger.slice(0, 200)}"` : "");
|
|
|
4293
6408
|
...record.metadata
|
|
4294
6409
|
}
|
|
4295
6410
|
});
|
|
4296
|
-
|
|
6411
|
+
log20.info({ actionId: record.actionId, feature: record.feature, memoryId }, "Action logged");
|
|
4297
6412
|
return memoryId;
|
|
4298
6413
|
} catch (err) {
|
|
4299
|
-
|
|
6414
|
+
log20.error({ err, actionId: record.actionId }, "Failed to log action");
|
|
4300
6415
|
return null;
|
|
4301
6416
|
}
|
|
4302
6417
|
}
|
|
@@ -4326,10 +6441,10 @@ Original action: ${record.actionId}`;
|
|
|
4326
6441
|
}
|
|
4327
6442
|
});
|
|
4328
6443
|
await updateActionImportance(record.actionId, record.sentiment);
|
|
4329
|
-
|
|
6444
|
+
log20.info({ actionId: record.actionId, sentiment: record.sentiment, memoryId }, "Outcome logged");
|
|
4330
6445
|
return memoryId;
|
|
4331
6446
|
} catch (err) {
|
|
4332
|
-
|
|
6447
|
+
log20.error({ err, actionId: record.actionId }, "Failed to log outcome");
|
|
4333
6448
|
return null;
|
|
4334
6449
|
}
|
|
4335
6450
|
}
|
|
@@ -4347,9 +6462,9 @@ async function updateActionImportance(actionId, sentiment) {
|
|
|
4347
6462
|
tags.push(`outcome:${sentiment}`);
|
|
4348
6463
|
const newImportance = sentiment === "positive" ? Math.min(1, currentImportance + 0.15) : sentiment === "negative" ? Math.min(1, currentImportance + 0.2) : currentImportance;
|
|
4349
6464
|
await db.from("memories").update({ importance: newImportance, tags }).eq("id", mem.id);
|
|
4350
|
-
|
|
6465
|
+
log20.debug({ actionId, oldImportance: currentImportance, newImportance }, "Action importance updated");
|
|
4351
6466
|
} catch (err) {
|
|
4352
|
-
|
|
6467
|
+
log20.error({ err, actionId }, "Failed to update action importance");
|
|
4353
6468
|
}
|
|
4354
6469
|
}
|
|
4355
6470
|
async function refineStrategies() {
|
|
@@ -4362,7 +6477,7 @@ async function refineStrategies() {
|
|
|
4362
6477
|
if (ownerWallet) recentQuery = recentQuery.eq("owner_wallet", ownerWallet);
|
|
4363
6478
|
const { data: recentActions } = await recentQuery;
|
|
4364
6479
|
if (!recentActions || recentActions.length === 0) {
|
|
4365
|
-
|
|
6480
|
+
log20.debug("No recent actions to analyze");
|
|
4366
6481
|
return lessons;
|
|
4367
6482
|
}
|
|
4368
6483
|
const withOutcome = recentActions.filter(
|
|
@@ -4375,7 +6490,7 @@ async function refineStrategies() {
|
|
|
4375
6490
|
(m) => (m.tags || []).includes("outcome:negative")
|
|
4376
6491
|
);
|
|
4377
6492
|
if (withOutcome.length < 3) {
|
|
4378
|
-
|
|
6493
|
+
log20.debug({ total: recentActions.length, withOutcome: withOutcome.length }, "Not enough outcome data for strategy refinement");
|
|
4379
6494
|
return lessons;
|
|
4380
6495
|
}
|
|
4381
6496
|
const byFeature = /* @__PURE__ */ new Map();
|
|
@@ -4420,7 +6535,7 @@ async function refineStrategies() {
|
|
|
4420
6535
|
}
|
|
4421
6536
|
});
|
|
4422
6537
|
lessons.push(lesson);
|
|
4423
|
-
|
|
6538
|
+
log20.info({ feature, lesson: lesson.slice(0, 100) }, "New strategy learned");
|
|
4424
6539
|
}
|
|
4425
6540
|
}
|
|
4426
6541
|
if (positive.length > 0) {
|
|
@@ -4428,7 +6543,7 @@ async function refineStrategies() {
|
|
|
4428
6543
|
}
|
|
4429
6544
|
return lessons;
|
|
4430
6545
|
} catch (err) {
|
|
4431
|
-
|
|
6546
|
+
log20.error({ err }, "Strategy refinement failed");
|
|
4432
6547
|
return lessons;
|
|
4433
6548
|
}
|
|
4434
6549
|
}
|
|
@@ -4452,7 +6567,7 @@ Write a concise procedural rule (1-2 sentences) that captures this successful st
|
|
|
4452
6567
|
});
|
|
4453
6568
|
return response.trim();
|
|
4454
6569
|
} catch (err) {
|
|
4455
|
-
|
|
6570
|
+
log20.error({ err, feature }, "Failed to generate lesson");
|
|
4456
6571
|
return null;
|
|
4457
6572
|
}
|
|
4458
6573
|
}
|
|
@@ -4474,11 +6589,11 @@ async function reinforceSuccessfulStrategies(positiveActions) {
|
|
|
4474
6589
|
if (relevantPositive.length > 0) {
|
|
4475
6590
|
const newImportance = Math.min(1, (strategy.importance || 0.5) + 0.05 * relevantPositive.length);
|
|
4476
6591
|
await db.from("memories").update({ importance: newImportance, access_count: (strategy.access_count || 0) + 1 }).eq("id", strategy.id);
|
|
4477
|
-
|
|
6592
|
+
log20.debug({ strategyId: strategy.id, feature, boost: relevantPositive.length }, "Strategy reinforced");
|
|
4478
6593
|
}
|
|
4479
6594
|
}
|
|
4480
6595
|
} catch (err) {
|
|
4481
|
-
|
|
6596
|
+
log20.error({ err }, "Failed to reinforce strategies");
|
|
4482
6597
|
}
|
|
4483
6598
|
}
|
|
4484
6599
|
async function trackSocialOutcomes() {
|
|
@@ -4532,16 +6647,16 @@ async function trackSocialOutcomes() {
|
|
|
4532
6647
|
tracked++;
|
|
4533
6648
|
}
|
|
4534
6649
|
} catch {
|
|
4535
|
-
|
|
6650
|
+
log20.debug({ replyId }, "Could not fetch tweet metrics");
|
|
4536
6651
|
}
|
|
4537
6652
|
}
|
|
4538
|
-
|
|
6653
|
+
log20.info({ tracked }, "Social outcomes tracked");
|
|
4539
6654
|
} catch (err) {
|
|
4540
|
-
|
|
6655
|
+
log20.error({ err }, "Social outcome tracking failed");
|
|
4541
6656
|
}
|
|
4542
6657
|
return tracked;
|
|
4543
6658
|
}
|
|
4544
|
-
var
|
|
6659
|
+
var log20;
|
|
4545
6660
|
var init_action_learning = __esm({
|
|
4546
6661
|
"packages/brain/src/memory/action-learning.ts"() {
|
|
4547
6662
|
"use strict";
|
|
@@ -4549,7 +6664,7 @@ var init_action_learning = __esm({
|
|
|
4549
6664
|
init_logger();
|
|
4550
6665
|
init_database();
|
|
4551
6666
|
init_claude_client();
|
|
4552
|
-
|
|
6667
|
+
log20 = createChildLogger("action-learning");
|
|
4553
6668
|
}
|
|
4554
6669
|
});
|
|
4555
6670
|
|
|
@@ -4576,7 +6691,7 @@ function accumulateImportance(importance) {
|
|
|
4576
6691
|
const timeSinceLastReflection = Date.now() - lastReflectionTime;
|
|
4577
6692
|
const pastMinInterval = timeSinceLastReflection >= REFLECTION_MIN_INTERVAL_MS;
|
|
4578
6693
|
if (importanceAccumulator >= REFLECTION_IMPORTANCE_THRESHOLD && pastMinInterval && !reflectionInProgress) {
|
|
4579
|
-
|
|
6694
|
+
log21.info({
|
|
4580
6695
|
accumulator: importanceAccumulator.toFixed(2),
|
|
4581
6696
|
threshold: REFLECTION_IMPORTANCE_THRESHOLD,
|
|
4582
6697
|
minutesSinceLast: Math.round(timeSinceLastReflection / 6e4)
|
|
@@ -4586,7 +6701,7 @@ function accumulateImportance(importance) {
|
|
|
4586
6701
|
try {
|
|
4587
6702
|
await triggerReflection();
|
|
4588
6703
|
} catch (err) {
|
|
4589
|
-
|
|
6704
|
+
log21.error({ err }, "Event-driven reflection failed");
|
|
4590
6705
|
}
|
|
4591
6706
|
});
|
|
4592
6707
|
}
|
|
@@ -4594,7 +6709,7 @@ function accumulateImportance(importance) {
|
|
|
4594
6709
|
async function triggerReflection() {
|
|
4595
6710
|
reflectionInProgress = true;
|
|
4596
6711
|
try {
|
|
4597
|
-
|
|
6712
|
+
log21.info({ accumulator: importanceAccumulator.toFixed(2) }, "=== DREAM CYCLE TRIGGERED ===");
|
|
4598
6713
|
await Promise.race([
|
|
4599
6714
|
(async () => {
|
|
4600
6715
|
await runConsolidation();
|
|
@@ -4609,9 +6724,9 @@ async function triggerReflection() {
|
|
|
4609
6724
|
await sleep(3e3);
|
|
4610
6725
|
try {
|
|
4611
6726
|
const deepResult = await runDeepConnectionPhase();
|
|
4612
|
-
|
|
6727
|
+
log21.info({ linksCreated: deepResult.linksCreated }, "JEPA deep connection complete");
|
|
4613
6728
|
} catch (err) {
|
|
4614
|
-
|
|
6729
|
+
log21.warn({ err }, "JEPA deep connection phase failed, continuing");
|
|
4615
6730
|
}
|
|
4616
6731
|
await sleep(3e3);
|
|
4617
6732
|
await runEmergence();
|
|
@@ -4623,9 +6738,9 @@ async function triggerReflection() {
|
|
|
4623
6738
|
importanceAccumulator = 0;
|
|
4624
6739
|
lastReflectionTime = Date.now();
|
|
4625
6740
|
await saveAccumulator();
|
|
4626
|
-
|
|
6741
|
+
log21.info("=== DREAM CYCLE COMPLETE ===");
|
|
4627
6742
|
} catch (err) {
|
|
4628
|
-
|
|
6743
|
+
log21.error({ err }, "Dream cycle failed or timed out");
|
|
4629
6744
|
} finally {
|
|
4630
6745
|
reflectionInProgress = false;
|
|
4631
6746
|
}
|
|
@@ -4637,7 +6752,7 @@ async function loadAccumulator() {
|
|
|
4637
6752
|
if (data) {
|
|
4638
6753
|
importanceAccumulator = (data.count || 0) / 100;
|
|
4639
6754
|
lastReflectionTime = new Date(data.window_start).getTime();
|
|
4640
|
-
|
|
6755
|
+
log21.debug({
|
|
4641
6756
|
accumulator: importanceAccumulator.toFixed(2),
|
|
4642
6757
|
lastReflection: new Date(lastReflectionTime).toISOString()
|
|
4643
6758
|
}, "Loaded reflection accumulator");
|
|
@@ -4654,7 +6769,7 @@ async function saveAccumulator() {
|
|
|
4654
6769
|
window_start: new Date(lastReflectionTime).toISOString()
|
|
4655
6770
|
});
|
|
4656
6771
|
} catch (err) {
|
|
4657
|
-
|
|
6772
|
+
log21.warn({ err }, "Failed to save reflection accumulator");
|
|
4658
6773
|
}
|
|
4659
6774
|
}
|
|
4660
6775
|
function parseEvidenceCitations(text, sourceMemories) {
|
|
@@ -4698,14 +6813,14 @@ ${memoryDump}`,
|
|
|
4698
6813
|
return response.split("\n").map((l) => l.trim()).filter((l) => l.length > 10 && l.includes("?")).slice(0, 3);
|
|
4699
6814
|
}
|
|
4700
6815
|
async function runConsolidation() {
|
|
4701
|
-
|
|
6816
|
+
log21.info("Dream phase 1: CONSOLIDATION starting");
|
|
4702
6817
|
const recentSummaries = await recallMemorySummaries({
|
|
4703
6818
|
memoryTypes: ["episodic"],
|
|
4704
6819
|
limit: 20,
|
|
4705
6820
|
trackAccess: false
|
|
4706
6821
|
});
|
|
4707
6822
|
if (recentSummaries.length < 3) {
|
|
4708
|
-
|
|
6823
|
+
log21.info({ count: recentSummaries.length }, "Too few recent memories for consolidation");
|
|
4709
6824
|
return;
|
|
4710
6825
|
}
|
|
4711
6826
|
const recentEpisodic = await getRecentMemories(6, ["episodic"], 20);
|
|
@@ -4713,14 +6828,14 @@ async function runConsolidation() {
|
|
|
4713
6828
|
try {
|
|
4714
6829
|
focalPoints = await generateFocalPoints(recentEpisodic);
|
|
4715
6830
|
} catch (err) {
|
|
4716
|
-
|
|
6831
|
+
log21.warn({ err }, "Focal point generation failed, using direct consolidation");
|
|
4717
6832
|
focalPoints = [];
|
|
4718
6833
|
}
|
|
4719
6834
|
if (focalPoints.length === 0) {
|
|
4720
6835
|
await runDirectConsolidation(recentEpisodic);
|
|
4721
6836
|
return;
|
|
4722
6837
|
}
|
|
4723
|
-
|
|
6838
|
+
log21.info({ focalPoints }, "Focal points generated");
|
|
4724
6839
|
const allNewIds = [];
|
|
4725
6840
|
const allInputIds = new Set(recentEpisodic.map((m) => m.id));
|
|
4726
6841
|
for (const question of focalPoints) {
|
|
@@ -4745,7 +6860,7 @@ ${numberedMemories}`,
|
|
|
4745
6860
|
});
|
|
4746
6861
|
const { text, evidenceIds } = parseEvidenceCitations(response, relevant);
|
|
4747
6862
|
if (isCopoutResponse(text)) {
|
|
4748
|
-
|
|
6863
|
+
log21.warn({ question }, "Consolidation produced cop-out response \u2014 discarding");
|
|
4749
6864
|
continue;
|
|
4750
6865
|
}
|
|
4751
6866
|
const id = await storeMemory({
|
|
@@ -4776,7 +6891,7 @@ Semantic insights: ${allNewIds.length - proceduralIds.length}
|
|
|
4776
6891
|
Procedural patterns: ${proceduralIds.length}`,
|
|
4777
6892
|
allNewIds
|
|
4778
6893
|
);
|
|
4779
|
-
|
|
6894
|
+
log21.info({ focalPoints: focalPoints.length, insights: allNewIds.length, procedural: proceduralIds.length }, "Focal-point consolidation complete");
|
|
4780
6895
|
}
|
|
4781
6896
|
async function runDirectConsolidation(recentEpisodic) {
|
|
4782
6897
|
const memoryDump = recentEpisodic.map(
|
|
@@ -4797,7 +6912,7 @@ Total interactions: ${recentEpisodic.length}`,
|
|
|
4797
6912
|
for (const obs of observations.slice(0, 3)) {
|
|
4798
6913
|
const { text, evidenceIds } = parseEvidenceCitations(obs, recentEpisodic);
|
|
4799
6914
|
if (isCopoutResponse(text)) {
|
|
4800
|
-
|
|
6915
|
+
log21.warn("Direct consolidation produced cop-out response \u2014 discarding");
|
|
4801
6916
|
continue;
|
|
4802
6917
|
}
|
|
4803
6918
|
const id = await storeMemory({
|
|
@@ -4820,24 +6935,24 @@ Total interactions: ${recentEpisodic.length}`,
|
|
|
4820
6935
|
response,
|
|
4821
6936
|
newIds
|
|
4822
6937
|
);
|
|
4823
|
-
|
|
6938
|
+
log21.info({ observations: newIds.length, procedural: proceduralIds.length }, "Direct consolidation complete");
|
|
4824
6939
|
}
|
|
4825
6940
|
async function runCompaction() {
|
|
4826
|
-
|
|
6941
|
+
log21.info("Dream phase 1.5: COMPACTION starting");
|
|
4827
6942
|
const db = getDb();
|
|
4828
6943
|
let compactionQuery = db.from("memories").select("id, hash_id, memory_type, summary, tags, concepts, importance, decay_factor, created_at").eq("memory_type", "episodic").eq("compacted", false).lt("decay_factor", 0.3).lt("importance", 0.5).lt("created_at", new Date(Date.now() - 7 * 24 * 60 * 60 * 1e3).toISOString()).order("created_at", { ascending: true }).limit(30);
|
|
4829
6944
|
const ownerWallet = getOwnerWallet();
|
|
4830
6945
|
if (ownerWallet) compactionQuery = compactionQuery.eq("owner_wallet", ownerWallet);
|
|
4831
6946
|
const { data: candidates, error } = await compactionQuery;
|
|
4832
6947
|
if (error) {
|
|
4833
|
-
|
|
6948
|
+
log21.error({ error: error.message }, "Failed to fetch compaction candidates");
|
|
4834
6949
|
return;
|
|
4835
6950
|
}
|
|
4836
6951
|
if (!candidates || candidates.length < 5) {
|
|
4837
|
-
|
|
6952
|
+
log21.debug({ count: candidates?.length || 0 }, "Too few candidates for compaction");
|
|
4838
6953
|
return;
|
|
4839
6954
|
}
|
|
4840
|
-
|
|
6955
|
+
log21.info({ count: candidates.length }, "Found compaction candidates");
|
|
4841
6956
|
const groups = /* @__PURE__ */ new Map();
|
|
4842
6957
|
for (const mem of candidates) {
|
|
4843
6958
|
const concept = mem.concepts?.[0] || "general";
|
|
@@ -4881,10 +6996,10 @@ ${memoryDump}`,
|
|
|
4881
6996
|
});
|
|
4882
6997
|
}
|
|
4883
6998
|
compactedCount += memories.length;
|
|
4884
|
-
|
|
6999
|
+
log21.debug({ concept, count: memories.length }, "Memory group compacted");
|
|
4885
7000
|
}
|
|
4886
7001
|
} catch (err) {
|
|
4887
|
-
|
|
7002
|
+
log21.warn({ err, concept }, "Failed to compact memory group");
|
|
4888
7003
|
}
|
|
4889
7004
|
}
|
|
4890
7005
|
if (compactedCount > 0) {
|
|
@@ -4895,10 +7010,10 @@ ${memoryDump}`,
|
|
|
4895
7010
|
summaryIds
|
|
4896
7011
|
);
|
|
4897
7012
|
}
|
|
4898
|
-
|
|
7013
|
+
log21.info({ compacted: compactedCount, summaries: summaryIds.length }, "Compaction complete");
|
|
4899
7014
|
}
|
|
4900
7015
|
async function runReflection() {
|
|
4901
|
-
|
|
7016
|
+
log21.info("Dream phase 2: REFLECTION starting");
|
|
4902
7017
|
const selfModel = await getSelfModel();
|
|
4903
7018
|
const recentSemantic = await getRecentMemories(48, ["semantic"], 10);
|
|
4904
7019
|
const stats = await getMemoryStats();
|
|
@@ -4921,7 +7036,7 @@ async function runReflection() {
|
|
|
4921
7036
|
});
|
|
4922
7037
|
const { text, evidenceIds } = parseEvidenceCitations(response, allInputMemories);
|
|
4923
7038
|
if (isCopoutResponse(text)) {
|
|
4924
|
-
|
|
7039
|
+
log21.warn("Reflection produced cop-out response \u2014 discarding");
|
|
4925
7040
|
await storeDreamLog("reflection", allInputMemories.map((m) => m.id), "(discarded \u2014 cop-out)", []);
|
|
4926
7041
|
return;
|
|
4927
7042
|
}
|
|
@@ -4953,10 +7068,10 @@ async function runReflection() {
|
|
|
4953
7068
|
response,
|
|
4954
7069
|
id ? [id] : []
|
|
4955
7070
|
);
|
|
4956
|
-
|
|
7071
|
+
log21.info({ evidenceCount: evidenceIds.length }, "Reflection complete (progressive disclosure enabled)");
|
|
4957
7072
|
}
|
|
4958
7073
|
async function runContradictionResolution() {
|
|
4959
|
-
|
|
7074
|
+
log21.info("Dream phase 2.5: CONTRADICTION RESOLUTION starting");
|
|
4960
7075
|
const db = getDb();
|
|
4961
7076
|
let pairs;
|
|
4962
7077
|
try {
|
|
@@ -4965,22 +7080,22 @@ async function runContradictionResolution() {
|
|
|
4965
7080
|
filter_owner: getOwnerWallet() || null
|
|
4966
7081
|
});
|
|
4967
7082
|
if (error || !data || data.length === 0) {
|
|
4968
|
-
|
|
7083
|
+
log21.debug({ error: error?.message }, "No unresolved contradictions found \u2014 skipping phase");
|
|
4969
7084
|
return;
|
|
4970
7085
|
}
|
|
4971
7086
|
pairs = data;
|
|
4972
7087
|
} catch (err) {
|
|
4973
|
-
|
|
7088
|
+
log21.debug({ err }, "Contradiction resolution skipped (RPC unavailable)");
|
|
4974
7089
|
return;
|
|
4975
7090
|
}
|
|
4976
|
-
|
|
7091
|
+
log21.info({ pairCount: pairs.length }, "Found unresolved contradictions");
|
|
4977
7092
|
const allIds = [...new Set(pairs.flatMap((p) => [p.source_id, p.target_id]))];
|
|
4978
7093
|
let memFetchQuery = db.from("memories").select("*").in("id", allIds);
|
|
4979
7094
|
const ownerWallet = getOwnerWallet();
|
|
4980
7095
|
if (ownerWallet) memFetchQuery = memFetchQuery.eq("owner_wallet", ownerWallet);
|
|
4981
7096
|
const { data: memories } = await memFetchQuery;
|
|
4982
7097
|
if (!memories || memories.length === 0) {
|
|
4983
|
-
|
|
7098
|
+
log21.warn("Could not fetch contradiction memories \u2014 skipping phase");
|
|
4984
7099
|
return;
|
|
4985
7100
|
}
|
|
4986
7101
|
const memoryMap = new Map(memories.map((m) => [m.id, m]));
|
|
@@ -5008,7 +7123,7 @@ ${numberedContext}`,
|
|
|
5008
7123
|
});
|
|
5009
7124
|
const { text, evidenceIds } = parseEvidenceCitations(response, sourceMemories);
|
|
5010
7125
|
if (isCopoutResponse(text)) {
|
|
5011
|
-
|
|
7126
|
+
log21.warn({ memA: memA.id, memB: memB.id }, "Contradiction resolution produced cop-out \u2014 skipping pair");
|
|
5012
7127
|
continue;
|
|
5013
7128
|
}
|
|
5014
7129
|
outputs.push(text);
|
|
@@ -5032,9 +7147,9 @@ ${numberedContext}`,
|
|
|
5032
7147
|
const newDecay = Math.max(0.05, weaker.decay_factor * 0.8);
|
|
5033
7148
|
const { error: decayErr } = await db.from("memories").update({ decay_factor: newDecay }).eq("id", weaker.id);
|
|
5034
7149
|
if (!decayErr) {
|
|
5035
|
-
|
|
7150
|
+
log21.debug({ memoryId: weaker.id, newDecay: newDecay.toFixed(3) }, "Accelerated decay on weaker contradicting memory");
|
|
5036
7151
|
} else {
|
|
5037
|
-
|
|
7152
|
+
log21.warn({ error: decayErr.message, memoryId: weaker.id }, "Failed to accelerate decay on contradicting memory");
|
|
5038
7153
|
}
|
|
5039
7154
|
}
|
|
5040
7155
|
}
|
|
@@ -5046,29 +7161,29 @@ ${numberedContext}`,
|
|
|
5046
7161
|
newMemoryIds
|
|
5047
7162
|
);
|
|
5048
7163
|
}
|
|
5049
|
-
|
|
7164
|
+
log21.info({ resolved: outputs.length, skipped: pairs.length - outputs.length }, "Contradiction resolution complete");
|
|
5050
7165
|
}
|
|
5051
7166
|
async function runLearning() {
|
|
5052
|
-
|
|
7167
|
+
log21.info("--- Learning phase: tracking outcomes & refining strategies ---");
|
|
5053
7168
|
try {
|
|
5054
7169
|
const { trackSocialOutcomes: trackSocialOutcomes2, refineStrategies: refineStrategies2 } = await Promise.resolve().then(() => (init_action_learning(), action_learning_exports));
|
|
5055
7170
|
const tracked = await trackSocialOutcomes2();
|
|
5056
7171
|
if (tracked > 0) {
|
|
5057
|
-
|
|
7172
|
+
log21.info({ tracked }, "Social outcomes tracked");
|
|
5058
7173
|
}
|
|
5059
7174
|
const lessons = await refineStrategies2();
|
|
5060
7175
|
if (lessons.length > 0) {
|
|
5061
|
-
|
|
7176
|
+
log21.info({ lessons: lessons.length }, "New strategies learned");
|
|
5062
7177
|
for (const lesson of lessons) {
|
|
5063
|
-
|
|
7178
|
+
log21.info({ lesson: lesson.slice(0, 150) }, "Learned strategy");
|
|
5064
7179
|
}
|
|
5065
7180
|
}
|
|
5066
7181
|
} catch (err) {
|
|
5067
|
-
|
|
7182
|
+
log21.error({ err }, "Learning phase failed");
|
|
5068
7183
|
}
|
|
5069
7184
|
}
|
|
5070
7185
|
async function runEmergence() {
|
|
5071
|
-
|
|
7186
|
+
log21.info("Dream phase 3: EMERGENCE starting");
|
|
5072
7187
|
const selfModel = await getSelfModel();
|
|
5073
7188
|
const stats = await getMemoryStats();
|
|
5074
7189
|
const randomMemories = await getRecentMemories(168, ["episodic"], 30);
|
|
@@ -5116,9 +7231,9 @@ async function runEmergence() {
|
|
|
5116
7231
|
if (_emergenceHandler) {
|
|
5117
7232
|
try {
|
|
5118
7233
|
await _emergenceHandler(response);
|
|
5119
|
-
|
|
7234
|
+
log21.info("Emergence thought sent to SDK handler");
|
|
5120
7235
|
} catch (err) {
|
|
5121
|
-
|
|
7236
|
+
log21.error({ err }, "SDK emergence handler failed");
|
|
5122
7237
|
}
|
|
5123
7238
|
} else {
|
|
5124
7239
|
const canPost = await checkRateLimit("global:emergence-tweet", 1, 720);
|
|
@@ -5126,15 +7241,15 @@ async function runEmergence() {
|
|
|
5126
7241
|
try {
|
|
5127
7242
|
const { postTweet: postTweet2 } = (init_x_client(), __toCommonJS(x_client_exports));
|
|
5128
7243
|
await postTweet2(response);
|
|
5129
|
-
|
|
7244
|
+
log21.info("Emergence thought posted to X");
|
|
5130
7245
|
} catch (err) {
|
|
5131
|
-
|
|
7246
|
+
log21.error({ err }, "Failed to post emergence thought");
|
|
5132
7247
|
}
|
|
5133
7248
|
} else if (!hasDepth) {
|
|
5134
|
-
|
|
7249
|
+
log21.debug({ selfModelCount: selfModel.length }, "Emergence not deep enough to post \u2014 skipping tweet");
|
|
5135
7250
|
}
|
|
5136
7251
|
}
|
|
5137
|
-
|
|
7252
|
+
log21.info("Emergence complete");
|
|
5138
7253
|
}
|
|
5139
7254
|
async function extractProceduralInsights(recentEpisodic) {
|
|
5140
7255
|
if (recentEpisodic.length < 3) return [];
|
|
@@ -5155,7 +7270,7 @@ ${numberedMemories}`,
|
|
|
5155
7270
|
for (const pattern of patterns.slice(0, 2)) {
|
|
5156
7271
|
const { text, evidenceIds } = parseEvidenceCitations(pattern, recentEpisodic);
|
|
5157
7272
|
if (isCopoutResponse(text)) {
|
|
5158
|
-
|
|
7273
|
+
log21.warn("Procedural extraction produced cop-out response \u2014 discarding");
|
|
5159
7274
|
continue;
|
|
5160
7275
|
}
|
|
5161
7276
|
const id = await storeMemory({
|
|
@@ -5170,10 +7285,10 @@ ${numberedMemories}`,
|
|
|
5170
7285
|
});
|
|
5171
7286
|
if (id) newIds.push(id);
|
|
5172
7287
|
}
|
|
5173
|
-
|
|
7288
|
+
log21.info({ count: newIds.length }, "Procedural insights extracted");
|
|
5174
7289
|
return newIds;
|
|
5175
7290
|
} catch (err) {
|
|
5176
|
-
|
|
7291
|
+
log21.warn({ err }, "Procedural extraction failed");
|
|
5177
7292
|
return [];
|
|
5178
7293
|
}
|
|
5179
7294
|
}
|
|
@@ -5200,33 +7315,33 @@ function buildReflectionStats(stats) {
|
|
|
5200
7315
|
return lines.join("\n");
|
|
5201
7316
|
}
|
|
5202
7317
|
async function startDreamCycle() {
|
|
5203
|
-
|
|
7318
|
+
log21.info("Starting dream cycle scheduler");
|
|
5204
7319
|
await loadAccumulator();
|
|
5205
7320
|
const cron = require("node-cron");
|
|
5206
7321
|
dreamCron = cron.schedule("0 */6 * * *", async () => {
|
|
5207
7322
|
if (reflectionInProgress) {
|
|
5208
|
-
|
|
7323
|
+
log21.info("Scheduled dream cycle skipped \u2014 reflection already in progress");
|
|
5209
7324
|
return;
|
|
5210
7325
|
}
|
|
5211
|
-
|
|
7326
|
+
log21.info({
|
|
5212
7327
|
accumulator: importanceAccumulator.toFixed(2)
|
|
5213
7328
|
}, "=== SCHEDULED DREAM CYCLE (6h fallback) ===");
|
|
5214
7329
|
await triggerReflection();
|
|
5215
7330
|
});
|
|
5216
7331
|
decayCron = cron.schedule("0 3 * * *", async () => {
|
|
5217
|
-
|
|
7332
|
+
log21.info("Running memory decay");
|
|
5218
7333
|
try {
|
|
5219
7334
|
await decayMemories();
|
|
5220
7335
|
} catch (err) {
|
|
5221
|
-
|
|
7336
|
+
log21.error({ err }, "Memory decay failed");
|
|
5222
7337
|
}
|
|
5223
7338
|
});
|
|
5224
7339
|
setTimeout(async () => {
|
|
5225
|
-
|
|
7340
|
+
log21.info("Running initial dream cycle");
|
|
5226
7341
|
try {
|
|
5227
7342
|
await triggerReflection();
|
|
5228
7343
|
} catch (err) {
|
|
5229
|
-
|
|
7344
|
+
log21.error({ err }, "Initial dream cycle failed");
|
|
5230
7345
|
}
|
|
5231
7346
|
}, 12e4);
|
|
5232
7347
|
}
|
|
@@ -5239,12 +7354,12 @@ function stopDreamCycle() {
|
|
|
5239
7354
|
decayCron.stop();
|
|
5240
7355
|
decayCron = null;
|
|
5241
7356
|
}
|
|
5242
|
-
|
|
7357
|
+
log21.info("Dream cycle stopped");
|
|
5243
7358
|
}
|
|
5244
7359
|
function sleep(ms) {
|
|
5245
7360
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
5246
7361
|
}
|
|
5247
|
-
var
|
|
7362
|
+
var log21, COPOUT_PATTERNS, importanceAccumulator, lastReflectionTime, reflectionInProgress, REFLECTION_TIMEOUT_MS, _emergenceHandler, dreamCron, decayCron;
|
|
5248
7363
|
var init_cycle = __esm({
|
|
5249
7364
|
"packages/brain/src/memory/dream/cycle.ts"() {
|
|
5250
7365
|
"use strict";
|
|
@@ -5256,7 +7371,7 @@ var init_cycle = __esm({
|
|
|
5256
7371
|
init_constants();
|
|
5257
7372
|
init_format();
|
|
5258
7373
|
init_deep_connection();
|
|
5259
|
-
|
|
7374
|
+
log21 = createChildLogger("dream-cycle");
|
|
5260
7375
|
COPOUT_PATTERNS = [
|
|
5261
7376
|
/^good question/i,
|
|
5262
7377
|
/^let me think about that/i,
|
|
@@ -5281,7 +7396,7 @@ async function findClinamen(opts) {
|
|
|
5281
7396
|
const limit = opts.limit || DEFAULT_LIMIT;
|
|
5282
7397
|
const minImportance = opts.minImportance ?? MIN_IMPORTANCE;
|
|
5283
7398
|
const maxRelevance = opts.maxRelevance ?? MAX_RELEVANCE_SIM;
|
|
5284
|
-
|
|
7399
|
+
log22.debug({ context: opts.context.slice(0, 60), limit }, "Searching for clinamen");
|
|
5285
7400
|
let contextEmbedding = null;
|
|
5286
7401
|
if (isEmbeddingEnabled()) {
|
|
5287
7402
|
const cached = getCachedEmbedding(opts.context);
|
|
@@ -5293,7 +7408,7 @@ async function findClinamen(opts) {
|
|
|
5293
7408
|
}
|
|
5294
7409
|
}
|
|
5295
7410
|
if (!contextEmbedding) {
|
|
5296
|
-
|
|
7411
|
+
log22.debug("No embedding available \u2014 falling back to importance-only clinamen");
|
|
5297
7412
|
return importanceOnlyClinamen(db, limit, minImportance, opts.memoryTypes);
|
|
5298
7413
|
}
|
|
5299
7414
|
const cutoff = new Date(Date.now() - MIN_AGE_HOURS * 60 * 60 * 1e3).toISOString();
|
|
@@ -5304,7 +7419,7 @@ async function findClinamen(opts) {
|
|
|
5304
7419
|
}
|
|
5305
7420
|
const { data: candidates, error } = await query;
|
|
5306
7421
|
if (error || !candidates || candidates.length === 0) {
|
|
5307
|
-
|
|
7422
|
+
log22.debug({ error: error?.message }, "No clinamen candidates found");
|
|
5308
7423
|
return [];
|
|
5309
7424
|
}
|
|
5310
7425
|
const scored = [];
|
|
@@ -5330,7 +7445,7 @@ async function findClinamen(opts) {
|
|
|
5330
7445
|
}
|
|
5331
7446
|
scored.sort((a, b) => b._divergence - a._divergence);
|
|
5332
7447
|
const results = scored.slice(0, limit);
|
|
5333
|
-
|
|
7448
|
+
log22.info({
|
|
5334
7449
|
candidates: candidates.length,
|
|
5335
7450
|
afterFilter: scored.length,
|
|
5336
7451
|
returned: results.length,
|
|
@@ -5356,7 +7471,7 @@ async function importanceOnlyClinamen(db, limit, minImportance, memoryTypes) {
|
|
|
5356
7471
|
_relevanceSim: 0
|
|
5357
7472
|
}));
|
|
5358
7473
|
}
|
|
5359
|
-
var
|
|
7474
|
+
var log22, MIN_IMPORTANCE, MAX_RELEVANCE_SIM, MIN_AGE_HOURS, CANDIDATE_POOL_SIZE, DEFAULT_LIMIT;
|
|
5360
7475
|
var init_clinamen = __esm({
|
|
5361
7476
|
"packages/brain/src/memory/clinamen.ts"() {
|
|
5362
7477
|
"use strict";
|
|
@@ -5364,7 +7479,7 @@ var init_clinamen = __esm({
|
|
|
5364
7479
|
init_memory();
|
|
5365
7480
|
init_embeddings();
|
|
5366
7481
|
init_logger();
|
|
5367
|
-
|
|
7482
|
+
log22 = createChildLogger("clinamen");
|
|
5368
7483
|
MIN_IMPORTANCE = 0.6;
|
|
5369
7484
|
MAX_RELEVANCE_SIM = 0.35;
|
|
5370
7485
|
MIN_AGE_HOURS = 24;
|
|
@@ -5400,13 +7515,13 @@ async function selectSeeds() {
|
|
|
5400
7515
|
memoryTypes: ["episodic", "semantic", "procedural"]
|
|
5401
7516
|
});
|
|
5402
7517
|
if (clinamenMemories.length > 0) {
|
|
5403
|
-
|
|
7518
|
+
log23.info({
|
|
5404
7519
|
count: clinamenMemories.length,
|
|
5405
7520
|
summaries: clinamenMemories.map((m) => m.summary.slice(0, 40))
|
|
5406
7521
|
}, "Clinamen seeds selected (anomaly retrieval)");
|
|
5407
7522
|
}
|
|
5408
7523
|
} catch (err) {
|
|
5409
|
-
|
|
7524
|
+
log23.debug({ err }, "Clinamen retrieval failed \u2014 falling back to random");
|
|
5410
7525
|
const older = await getRecentMemories(720, ["episodic", "semantic", "procedural"], 50);
|
|
5411
7526
|
if (older.length > 0) {
|
|
5412
7527
|
clinamenMemories = [older[Math.floor(Math.random() * older.length)]];
|
|
@@ -5439,18 +7554,18 @@ ${summaries}`,
|
|
|
5439
7554
|
}
|
|
5440
7555
|
async function runActiveReflection() {
|
|
5441
7556
|
if (reflectionInProgress2) {
|
|
5442
|
-
|
|
7557
|
+
log23.info("Active reflection already in progress \u2014 skipping");
|
|
5443
7558
|
return null;
|
|
5444
7559
|
}
|
|
5445
7560
|
reflectionInProgress2 = true;
|
|
5446
7561
|
try {
|
|
5447
|
-
|
|
7562
|
+
log23.info("=== ACTIVE REFLECTION STARTING ===");
|
|
5448
7563
|
const { seeds, theme } = await selectSeeds();
|
|
5449
7564
|
if (seeds.length < MIN_MEMORIES_FOR_REFLECTION) {
|
|
5450
|
-
|
|
7565
|
+
log23.info({ count: seeds.length }, "Too few memories for reflection \u2014 skipping");
|
|
5451
7566
|
return null;
|
|
5452
7567
|
}
|
|
5453
|
-
|
|
7568
|
+
log23.info({ seedCount: seeds.length, theme }, "Reflection seeds selected");
|
|
5454
7569
|
const selfModel = await getSelfModel();
|
|
5455
7570
|
const stats = await getMemoryStats();
|
|
5456
7571
|
const previousReflections = await getRecentMemories(72, ["introspective"], 3);
|
|
@@ -5481,10 +7596,10 @@ async function runActiveReflection() {
|
|
|
5481
7596
|
cognitiveFunction: "reflect"
|
|
5482
7597
|
});
|
|
5483
7598
|
if (!journal || journal.trim().length < 50) {
|
|
5484
|
-
|
|
7599
|
+
log23.warn("Reflection produced empty or trivial output \u2014 discarding");
|
|
5485
7600
|
return null;
|
|
5486
7601
|
}
|
|
5487
|
-
|
|
7602
|
+
log23.info({ length: journal.length, theme }, "Journal entry generated");
|
|
5488
7603
|
const title = await generateTitle(journal);
|
|
5489
7604
|
const memoryId = await storeMemory({
|
|
5490
7605
|
type: "introspective",
|
|
@@ -5518,17 +7633,17 @@ ${journal}`,
|
|
|
5518
7633
|
if (_reflectionHandler) {
|
|
5519
7634
|
try {
|
|
5520
7635
|
await _reflectionHandler(result);
|
|
5521
|
-
|
|
7636
|
+
log23.info("Reflection sent to SDK handler");
|
|
5522
7637
|
} catch (err) {
|
|
5523
|
-
|
|
7638
|
+
log23.error({ err }, "SDK reflection handler failed");
|
|
5524
7639
|
}
|
|
5525
7640
|
} else {
|
|
5526
7641
|
await maybePostThread(result);
|
|
5527
7642
|
}
|
|
5528
|
-
|
|
7643
|
+
log23.info({ memoryId, title }, "=== ACTIVE REFLECTION COMPLETE ===");
|
|
5529
7644
|
return result;
|
|
5530
7645
|
} catch (err) {
|
|
5531
|
-
|
|
7646
|
+
log23.error({ err }, "Active reflection failed");
|
|
5532
7647
|
return null;
|
|
5533
7648
|
} finally {
|
|
5534
7649
|
reflectionInProgress2 = false;
|
|
@@ -5552,25 +7667,25 @@ async function maybePostThread(journal) {
|
|
|
5552
7667
|
const hour = (/* @__PURE__ */ new Date()).getUTCHours();
|
|
5553
7668
|
const isQuietHours = hour >= 23 || hour < 8;
|
|
5554
7669
|
if (isQuietHours) {
|
|
5555
|
-
|
|
7670
|
+
log23.debug("Quiet hours \u2014 skipping thread post");
|
|
5556
7671
|
return;
|
|
5557
7672
|
}
|
|
5558
7673
|
if (journal.text.length < 300) {
|
|
5559
|
-
|
|
7674
|
+
log23.debug("Journal too short for thread \u2014 skipping post");
|
|
5560
7675
|
return;
|
|
5561
7676
|
}
|
|
5562
7677
|
const canPost = await checkRateLimit(THREAD_RATE_KEY, THREAD_RATE_LIMIT, THREAD_RATE_WINDOW);
|
|
5563
7678
|
if (!canPost) {
|
|
5564
|
-
|
|
7679
|
+
log23.debug("Thread rate limit hit \u2014 skipping post");
|
|
5565
7680
|
return;
|
|
5566
7681
|
}
|
|
5567
7682
|
try {
|
|
5568
7683
|
const tweets = formatAsThread(journal);
|
|
5569
7684
|
const { postThread: postThread2 } = (init_x_client(), __toCommonJS(x_client_exports));
|
|
5570
7685
|
const ids = await postThread2(tweets);
|
|
5571
|
-
|
|
7686
|
+
log23.info({ tweetCount: ids.length, title: journal.title }, "Reflection thread posted to X");
|
|
5572
7687
|
} catch (err) {
|
|
5573
|
-
|
|
7688
|
+
log23.error({ err }, "Failed to post reflection thread");
|
|
5574
7689
|
}
|
|
5575
7690
|
}
|
|
5576
7691
|
function formatAsThread(journal) {
|
|
@@ -5593,29 +7708,29 @@ A reflection \u{1F9F5}`;
|
|
|
5593
7708
|
return tweets.slice(0, MAX_THREAD_TWEETS);
|
|
5594
7709
|
}
|
|
5595
7710
|
async function startActiveReflection() {
|
|
5596
|
-
|
|
7711
|
+
log23.info("Starting active reflection scheduler");
|
|
5597
7712
|
const cron = require("node-cron");
|
|
5598
7713
|
reflectionCron = cron.schedule("30 1,4,7,10,13,16,19,22 * * *", async () => {
|
|
5599
7714
|
if (reflectionInProgress2) {
|
|
5600
|
-
|
|
7715
|
+
log23.info("Scheduled reflection skipped \u2014 already in progress");
|
|
5601
7716
|
return;
|
|
5602
7717
|
}
|
|
5603
|
-
|
|
7718
|
+
log23.info("=== SCHEDULED ACTIVE REFLECTION ===");
|
|
5604
7719
|
await Promise.race([
|
|
5605
7720
|
runActiveReflection(),
|
|
5606
7721
|
new Promise(
|
|
5607
7722
|
(_, reject) => setTimeout(() => reject(new Error("Active reflection timed out")), REFLECTION_TIMEOUT_MS2)
|
|
5608
7723
|
)
|
|
5609
7724
|
]).catch((err) => {
|
|
5610
|
-
|
|
7725
|
+
log23.error({ err }, "Scheduled active reflection failed or timed out");
|
|
5611
7726
|
});
|
|
5612
7727
|
});
|
|
5613
7728
|
setTimeout(async () => {
|
|
5614
|
-
|
|
7729
|
+
log23.info("Running initial active reflection");
|
|
5615
7730
|
try {
|
|
5616
7731
|
await runActiveReflection();
|
|
5617
7732
|
} catch (err) {
|
|
5618
|
-
|
|
7733
|
+
log23.error({ err }, "Initial active reflection failed");
|
|
5619
7734
|
}
|
|
5620
7735
|
}, 30 * 60 * 1e3);
|
|
5621
7736
|
}
|
|
@@ -5624,9 +7739,9 @@ function stopActiveReflection() {
|
|
|
5624
7739
|
reflectionCron.stop();
|
|
5625
7740
|
reflectionCron = null;
|
|
5626
7741
|
}
|
|
5627
|
-
|
|
7742
|
+
log23.info("Active reflection stopped");
|
|
5628
7743
|
}
|
|
5629
|
-
var
|
|
7744
|
+
var log23, REFLECTION_INTERVAL_MS, MIN_MEMORIES_FOR_REFLECTION, MAX_JOURNAL_TOKENS, MAX_THREAD_TWEETS, THREAD_RATE_KEY, THREAD_RATE_LIMIT, THREAD_RATE_WINDOW, reflectionInProgress2, reflectionCron, REFLECTION_TIMEOUT_MS2, _reflectionHandler;
|
|
5630
7745
|
var init_active_reflection = __esm({
|
|
5631
7746
|
"packages/brain/src/memory/active-reflection.ts"() {
|
|
5632
7747
|
"use strict";
|
|
@@ -5636,7 +7751,7 @@ var init_active_reflection = __esm({
|
|
|
5636
7751
|
init_logger();
|
|
5637
7752
|
init_constants();
|
|
5638
7753
|
init_clinamen();
|
|
5639
|
-
|
|
7754
|
+
log23 = createChildLogger("active-reflection");
|
|
5640
7755
|
REFLECTION_INTERVAL_MS = 3 * 60 * 60 * 1e3;
|
|
5641
7756
|
MIN_MEMORIES_FOR_REFLECTION = 5;
|
|
5642
7757
|
MAX_JOURNAL_TOKENS = 1500;
|
|
@@ -6037,10 +8152,10 @@ var Cortex = class {
|
|
|
6037
8152
|
this.requireSelfHosted("verifyOnChain");
|
|
6038
8153
|
const { hydrateMemories: hydrateMemories2 } = (init_memory2(), __toCommonJS(memory_exports2));
|
|
6039
8154
|
const { verifyMemoryOnChain: verifyMemoryOnChain2 } = (init_solana_client(), __toCommonJS(solana_client_exports));
|
|
6040
|
-
const { createHash:
|
|
8155
|
+
const { createHash: createHash3 } = require("crypto");
|
|
6041
8156
|
const memories = await hydrateMemories2([memoryId]);
|
|
6042
8157
|
if (memories.length === 0) return false;
|
|
6043
|
-
const contentHash =
|
|
8158
|
+
const contentHash = createHash3("sha256").update(memories[0].content).digest();
|
|
6044
8159
|
return verifyMemoryOnChain2(contentHash);
|
|
6045
8160
|
}
|
|
6046
8161
|
/** Clean up resources and stop schedules. */
|
|
@@ -6066,7 +8181,7 @@ var Cortex = class {
|
|
|
6066
8181
|
};
|
|
6067
8182
|
|
|
6068
8183
|
// packages/brain/src/sdk/cortex-v2.ts
|
|
6069
|
-
var
|
|
8184
|
+
var import_crypto4 = __toESM(require("crypto"));
|
|
6070
8185
|
var DEFAULT_ROUTES = {
|
|
6071
8186
|
routes: {
|
|
6072
8187
|
embed: { provider: "voyage", model: "voyage-4-large" },
|
|
@@ -6127,7 +8242,7 @@ var CortexV2 = class extends Cortex {
|
|
|
6127
8242
|
});
|
|
6128
8243
|
}
|
|
6129
8244
|
const pack = {
|
|
6130
|
-
id:
|
|
8245
|
+
id: import_crypto4.default.randomUUID(),
|
|
6131
8246
|
name: opts.name,
|
|
6132
8247
|
description: opts.description,
|
|
6133
8248
|
memories: memories.map((m) => ({
|