@anuma/sdk 2.1.0 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/index.cjs +8 -0
- package/dist/client/index.d.mts +59 -1
- package/dist/client/index.d.ts +59 -1
- package/dist/client/index.mjs +7 -0
- package/dist/expo/{chunk-N4FEY34G.mjs → chunk-SF5AYTXH.mjs} +134 -3
- package/dist/expo/index.cjs +341 -19
- package/dist/expo/index.d.mts +176 -2
- package/dist/expo/index.d.ts +176 -2
- package/dist/expo/index.mjs +208 -19
- package/dist/expo/{recall-DHOR5WWW.mjs → recall-PCKDVF3U.mjs} +1 -1
- package/dist/react/{chunk-63IW5BS5.mjs → chunk-JUDJVX66.mjs} +135 -3
- package/dist/react/index.cjs +344 -19
- package/dist/react/index.d.mts +208 -5
- package/dist/react/index.d.ts +208 -5
- package/dist/react/index.mjs +209 -19
- package/dist/react/{recall-PUXEQHAP.mjs → recall-DSJSBDR4.mjs} +1 -1
- package/dist/server/{chunk-45GOZ3AB.mjs → chunk-3AWYZRTS.mjs} +134 -3
- package/dist/server/index.cjs +341 -19
- package/dist/server/index.d.mts +176 -2
- package/dist/server/index.d.ts +176 -2
- package/dist/server/index.mjs +208 -19
- package/dist/server/{recall-RMBNCEG6.mjs → recall-EWMI64SA.mjs} +1 -1
- package/dist/tools/index.d.mts +5 -0
- package/dist/tools/index.d.ts +5 -0
- package/package.json +2 -2
package/dist/react/index.mjs
CHANGED
|
@@ -72,6 +72,7 @@ import {
|
|
|
72
72
|
getEncryptionKey,
|
|
73
73
|
getImageModel,
|
|
74
74
|
getMemoriesByEntityNamesOp,
|
|
75
|
+
getMemoriesNeedingTopicExtractionOp,
|
|
75
76
|
getMessageCountOp,
|
|
76
77
|
getMessageSkeletonsOp,
|
|
77
78
|
getMessagesOp,
|
|
@@ -101,6 +102,7 @@ import {
|
|
|
101
102
|
postAuthOauthByProviderRevoke,
|
|
102
103
|
preEmbedVaultMemories,
|
|
103
104
|
recall,
|
|
105
|
+
replaceMemoryEntitiesGuardedOp,
|
|
104
106
|
requestEncryptionKey,
|
|
105
107
|
requestKeyPair,
|
|
106
108
|
resolveStoredUserContent,
|
|
@@ -109,6 +111,7 @@ import {
|
|
|
109
111
|
searchVaultMemories,
|
|
110
112
|
setMemoryEntitiesOp,
|
|
111
113
|
shouldChunkMessage,
|
|
114
|
+
stampTopicsExtractedAtOp,
|
|
112
115
|
stripToolCalls,
|
|
113
116
|
updateConversationPinnedOp,
|
|
114
117
|
updateConversationProjectOp,
|
|
@@ -120,8 +123,9 @@ import {
|
|
|
120
123
|
updateMessageFileIdsOp,
|
|
121
124
|
updateVaultMemoryEmbeddingOp,
|
|
122
125
|
updateVaultMemoryOp,
|
|
123
|
-
useEncryption
|
|
124
|
-
|
|
126
|
+
useEncryption,
|
|
127
|
+
vaultMemoryToStored
|
|
128
|
+
} from "./chunk-JUDJVX66.mjs";
|
|
125
129
|
import {
|
|
126
130
|
PiiRedactor,
|
|
127
131
|
createStreamingDeAnonymizer,
|
|
@@ -6659,6 +6663,9 @@ __decorateClass([
|
|
|
6659
6663
|
__decorateClass([
|
|
6660
6664
|
field3("topics_user_managed")
|
|
6661
6665
|
], VaultMemory.prototype, "topicsUserManaged", 2);
|
|
6666
|
+
__decorateClass([
|
|
6667
|
+
field3("topics_extracted_at")
|
|
6668
|
+
], VaultMemory.prototype, "topicsExtractedAt", 2);
|
|
6662
6669
|
__decorateClass([
|
|
6663
6670
|
readonly2,
|
|
6664
6671
|
date3("created_at")
|
|
@@ -6875,7 +6882,7 @@ async function isMemoryTopicsUserManaged(ctx, memoryId) {
|
|
|
6875
6882
|
return true;
|
|
6876
6883
|
}
|
|
6877
6884
|
}
|
|
6878
|
-
var
|
|
6885
|
+
var DEFAULT_EXTRACTION_MODEL = "gpt-oss/gpt-oss-120b";
|
|
6879
6886
|
var DEFAULT_MIN_CONFIDENCE = 0.7;
|
|
6880
6887
|
var MAX_CONTENT_LENGTH = 200;
|
|
6881
6888
|
var MIN_CONTENT_LENGTH = 3;
|
|
@@ -6888,6 +6895,16 @@ var FACT_TYPES = [
|
|
|
6888
6895
|
"constraint",
|
|
6889
6896
|
"other"
|
|
6890
6897
|
];
|
|
6898
|
+
var ENTITY_KIND_GUIDELINES = `Choose the MOST SPECIFIC kind:
|
|
6899
|
+
- person: a named individual ("Sara", "Dr. Lee")
|
|
6900
|
+
- organization: a company, team, school, or group ("Google", "the Warriors", "Stanford")
|
|
6901
|
+
- place: a geographic location or venue ("San Francisco", "Blue Bottle on Valencia")
|
|
6902
|
+
- event: a dated or nameable occurrence ("Chicago Marathon", "the Japan trip", "their wedding")
|
|
6903
|
+
- product: a named app, tool, brand, device, book, film, or other media title ("Linear", "iPhone", "Dune", "Ableton")
|
|
6904
|
+
- thing: a concrete physical object that isn't a branded product ("road bike", "sourdough starter")
|
|
6905
|
+
- concept: a topic, skill, field, language, or idea ("machine learning", "Spanish", "async communication")
|
|
6906
|
+
- other: a named entity that genuinely fits none of the above \u2014 use sparingly
|
|
6907
|
+
Prefer organization / product / event / place over the generic "thing"; fall back to "thing" or "other" only when no specific kind fits.`;
|
|
6891
6908
|
var SYSTEM_PROMPT = `You extract durable user facts from conversations for a personal memory system.
|
|
6892
6909
|
|
|
6893
6910
|
A "durable fact" is something the user shared about themselves that should still be true in future conversations. Examples that ARE durable:
|
|
@@ -6916,16 +6933,7 @@ For each durable fact, output:
|
|
|
6916
6933
|
- type: one of identity | preference | relationship | plan | ongoing_context | constraint | other
|
|
6917
6934
|
- confidence: 0.0-1.0; how sure you are this is durable AND true. Only include facts >= 0.7.
|
|
6918
6935
|
- sourceMessageIds: which message IDs from the conversation contained the evidence
|
|
6919
|
-
- entities: named entities mentioned, each as an object { "name": string, "kind": one of "person" | "organization" | "place" | "event" | "product" | "thing" | "concept" | "other" }. Optional \u2014 include only NAMED entities, skip generic/common nouns.
|
|
6920
|
-
- person: a named individual ("Sara", "Dr. Lee")
|
|
6921
|
-
- organization: a company, team, school, or group ("Google", "the Warriors", "Stanford")
|
|
6922
|
-
- place: a geographic location or venue ("San Francisco", "Blue Bottle on Valencia")
|
|
6923
|
-
- event: a dated or nameable occurrence ("Chicago Marathon", "the Japan trip", "their wedding")
|
|
6924
|
-
- product: a named app, tool, brand, device, book, film, or other media title ("Linear", "iPhone", "Dune", "Ableton")
|
|
6925
|
-
- thing: a concrete physical object that isn't a branded product ("road bike", "sourdough starter")
|
|
6926
|
-
- concept: a topic, skill, field, language, or idea ("machine learning", "Spanish", "async communication")
|
|
6927
|
-
- other: a named entity that genuinely fits none of the above \u2014 use sparingly
|
|
6928
|
-
Prefer organization / product / event / place over the generic "thing"; fall back to "thing" or "other" only when no specific kind fits. Examples:
|
|
6936
|
+
- entities: named entities mentioned, each as an object { "name": string, "kind": one of "person" | "organization" | "place" | "event" | "product" | "thing" | "concept" | "other" }. Optional \u2014 include only NAMED entities, skip generic/common nouns. ${ENTITY_KIND_GUIDELINES} Examples:
|
|
6929
6937
|
- "started a new job at Hollowpoint Labs and I'm learning Haskell for it" \u2192 entities: [{ "name": "Hollowpoint Labs", "kind": "organization" }, { "name": "Haskell", "kind": "concept" }]
|
|
6930
6938
|
- "running the Berlin Half in May and I track workouts in Whoop" \u2192 entities: [{ "name": "Berlin Half", "kind": "event" }, { "name": "Whoop", "kind": "product" }]
|
|
6931
6939
|
- eventTime: when the event in this fact occurs/occurred. Resolve relative phrases ("yesterday", "next week") against the current date given in the user message. Output one of:
|
|
@@ -6951,7 +6959,7 @@ async function extractFacts(messages, options) {
|
|
|
6951
6959
|
...options.apiKey !== void 0 && { apiKey: options.apiKey },
|
|
6952
6960
|
...options.getToken !== void 0 && { getToken: options.getToken },
|
|
6953
6961
|
...options.baseUrl !== void 0 && { baseUrl: options.baseUrl },
|
|
6954
|
-
model: options.model ??
|
|
6962
|
+
model: options.model ?? DEFAULT_EXTRACTION_MODEL,
|
|
6955
6963
|
systemPrompt: SYSTEM_PROMPT,
|
|
6956
6964
|
userMessage: `Today's date is ${today}. Resolve any relative dates ("yesterday", "next week") against it.
|
|
6957
6965
|
|
|
@@ -7045,7 +7053,9 @@ async function extractAndRetain(messages, retainCtx, options) {
|
|
|
7045
7053
|
if (options.entityCtx && candidate.entities.length > 0) {
|
|
7046
7054
|
try {
|
|
7047
7055
|
if (!await isMemoryTopicsUserManaged(options.entityCtx, result.memoryId)) {
|
|
7048
|
-
await linkMemoryEntitiesOp(options.entityCtx, result.memoryId, candidate.entities
|
|
7056
|
+
await linkMemoryEntitiesOp(options.entityCtx, result.memoryId, candidate.entities, {
|
|
7057
|
+
unlessTopicsUserManaged: true
|
|
7058
|
+
});
|
|
7049
7059
|
}
|
|
7050
7060
|
} catch (err) {
|
|
7051
7061
|
log.warn("[memory/extract] linkMemoryEntitiesOp failed", err);
|
|
@@ -7440,7 +7450,7 @@ function createRecallTool(ctx, toolOptions, callbacks) {
|
|
|
7440
7450
|
|
|
7441
7451
|
// src/lib/memory/reflect.ts
|
|
7442
7452
|
var DEFAULT_BASE_URL = BASE_URL;
|
|
7443
|
-
var
|
|
7453
|
+
var DEFAULT_MODEL = "anthropic/claude-sonnet-4-6";
|
|
7444
7454
|
var DEFAULT_MAX_TOKENS = 4096;
|
|
7445
7455
|
var REQUEST_TIMEOUT_MS = 6e4;
|
|
7446
7456
|
var DEFAULT_SYSTEM_PROMPT = `You are a personal assistant with access to the user's memory. Answer the user's question using the supplied memories as evidence.
|
|
@@ -7472,7 +7482,7 @@ async function reflect(query, ctx, options) {
|
|
|
7472
7482
|
const evidence = recalled.memories.map((m, i) => `[${i + 1}] (id: ${m.id}, kind: ${m.kind})
|
|
7473
7483
|
${m.content}`).join("\n\n");
|
|
7474
7484
|
const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
|
|
7475
|
-
const model = options.llmModel ??
|
|
7485
|
+
const model = options.llmModel ?? DEFAULT_MODEL;
|
|
7476
7486
|
const wantsStructured = !!options.responseSchema;
|
|
7477
7487
|
const sendResponseFormat = wantsStructured && supportsResponseFormat(model, "json_schema");
|
|
7478
7488
|
const basePrompt = options.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
@@ -7582,6 +7592,163 @@ function parseAnswer(body, base, parseSchema) {
|
|
|
7582
7592
|
};
|
|
7583
7593
|
}
|
|
7584
7594
|
|
|
7595
|
+
// src/lib/memory/topicExtract.ts
|
|
7596
|
+
var TOPIC_EXTRACTION_BATCH_SIZE = 10;
|
|
7597
|
+
var MAX_CHARS_PER_MEMORY = 300;
|
|
7598
|
+
var MAX_VOCABULARY_NAMES = 100;
|
|
7599
|
+
var SYSTEM_PROMPT2 = `You assign topics to saved memories for a personal memory system.
|
|
7600
|
+
|
|
7601
|
+
Each memory is a short statement about the user. For each memory, list the NAMED entities it mentions \u2014 these become the memory's topics, used to connect related memories in a knowledge graph.
|
|
7602
|
+
|
|
7603
|
+
Include only NAMED entities, skip generic/common nouns. ${ENTITY_KIND_GUIDELINES}
|
|
7604
|
+
|
|
7605
|
+
Output strict JSON: {"memories": [{"id": string, "entities": [{"name": string, "kind": string}]}]}
|
|
7606
|
+
- exactly one element per input memory, echoing its id verbatim
|
|
7607
|
+
- "entities" may be empty when a memory mentions no named entities \u2014 most short memories have 0-3
|
|
7608
|
+
No prose.`;
|
|
7609
|
+
async function extractEntitiesForMemories(memories, options) {
|
|
7610
|
+
const out = /* @__PURE__ */ new Map();
|
|
7611
|
+
if (memories.length === 0) return out;
|
|
7612
|
+
const redactor = resolvePiiRedactor(options.piiRedaction);
|
|
7613
|
+
const vocabulary = (options.existingEntityNames ?? []).filter((n) => n.trim().length > 0).slice(0, MAX_VOCABULARY_NAMES).map((n) => redactor ? redactor.redactText(n).text : n);
|
|
7614
|
+
const vocabularyNote = vocabulary.length > 0 ? `The user's existing topics: ${vocabulary.join(", ")}.
|
|
7615
|
+
When a memory refers to one of these, reuse the EXACT existing name (same spelling and casing) instead of a variant.
|
|
7616
|
+
|
|
7617
|
+
` : "";
|
|
7618
|
+
for (let i = 0; i < memories.length; i += TOPIC_EXTRACTION_BATCH_SIZE) {
|
|
7619
|
+
const batch = memories.slice(i, i + TOPIC_EXTRACTION_BATCH_SIZE);
|
|
7620
|
+
const listing = batch.map((m) => {
|
|
7621
|
+
const content = m.content.slice(0, MAX_CHARS_PER_MEMORY);
|
|
7622
|
+
return `[${m.id}] ${redactor ? redactor.redactText(content).text : content}`;
|
|
7623
|
+
}).join("\n");
|
|
7624
|
+
const parsed = await callPortalJsonCompletion({
|
|
7625
|
+
...options.apiKey !== void 0 && { apiKey: options.apiKey },
|
|
7626
|
+
...options.getToken !== void 0 && { getToken: options.getToken },
|
|
7627
|
+
...options.baseUrl !== void 0 && { baseUrl: options.baseUrl },
|
|
7628
|
+
model: options.model ?? DEFAULT_EXTRACTION_MODEL,
|
|
7629
|
+
systemPrompt: SYSTEM_PROMPT2,
|
|
7630
|
+
userMessage: `${vocabularyNote}Memories:
|
|
7631
|
+
${listing}
|
|
7632
|
+
|
|
7633
|
+
List each memory's named entities.`,
|
|
7634
|
+
tag: "memory/topics",
|
|
7635
|
+
...options.fetchFn && { fetchFn: options.fetchFn },
|
|
7636
|
+
...options.maxAttempts !== void 0 && { maxAttempts: options.maxAttempts },
|
|
7637
|
+
...options.timeoutMs !== void 0 && { timeoutMs: options.timeoutMs },
|
|
7638
|
+
...options.totalTimeoutMs !== void 0 && { totalTimeoutMs: options.totalTimeoutMs },
|
|
7639
|
+
...options.backoffMs && { backoffMs: options.backoffMs }
|
|
7640
|
+
});
|
|
7641
|
+
if (parsed === null) {
|
|
7642
|
+
getLogger().warn(
|
|
7643
|
+
`[memory/topics] batch of ${batch.length} failed after retries \u2014 will retry next sweep`
|
|
7644
|
+
);
|
|
7645
|
+
continue;
|
|
7646
|
+
}
|
|
7647
|
+
const validIds = new Set(batch.map((m) => m.id));
|
|
7648
|
+
const byId = parseTopicResponse(parsed, validIds);
|
|
7649
|
+
if (byId === null) {
|
|
7650
|
+
getLogger().warn(
|
|
7651
|
+
`[memory/topics] batch of ${batch.length} returned an unrecognized shape \u2014 will retry next sweep`
|
|
7652
|
+
);
|
|
7653
|
+
continue;
|
|
7654
|
+
}
|
|
7655
|
+
for (const m of batch) {
|
|
7656
|
+
const entities = byId.get(m.id);
|
|
7657
|
+
if (entities === void 0) {
|
|
7658
|
+
continue;
|
|
7659
|
+
}
|
|
7660
|
+
out.set(m.id, redactor ? restoreEntities(entities, redactor) : entities);
|
|
7661
|
+
}
|
|
7662
|
+
}
|
|
7663
|
+
return out;
|
|
7664
|
+
}
|
|
7665
|
+
function restoreEntities(entities, redactor) {
|
|
7666
|
+
return entities.map((e) => ({ kind: e.kind, restored: redactor.restoreForStorage(e.name) })).filter((e) => !e.restored.unresolved).map(
|
|
7667
|
+
(e) => e.kind !== void 0 ? { name: e.restored.text, kind: e.kind } : { name: e.restored.text }
|
|
7668
|
+
);
|
|
7669
|
+
}
|
|
7670
|
+
function parseTopicResponse(parsed, validIds) {
|
|
7671
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
7672
|
+
const list = parsed.memories;
|
|
7673
|
+
if (!Array.isArray(list)) return null;
|
|
7674
|
+
const out = /* @__PURE__ */ new Map();
|
|
7675
|
+
for (const raw of list) {
|
|
7676
|
+
if (typeof raw !== "object" || raw === null) continue;
|
|
7677
|
+
const obj = raw;
|
|
7678
|
+
if (typeof obj.id !== "string" || !validIds.has(obj.id) || out.has(obj.id)) continue;
|
|
7679
|
+
const entities = Array.isArray(obj.entities) ? parseEntities(obj.entities) : [];
|
|
7680
|
+
out.set(obj.id, entities);
|
|
7681
|
+
}
|
|
7682
|
+
return out;
|
|
7683
|
+
}
|
|
7684
|
+
async function extractAndLinkEntitiesForMemoriesOp(ctx, memoryIds, options) {
|
|
7685
|
+
const entityCtx = ctx.entityCtx;
|
|
7686
|
+
if (!entityCtx) {
|
|
7687
|
+
throw new Error("extractAndLinkEntitiesForMemoriesOp requires ctx.entityCtx");
|
|
7688
|
+
}
|
|
7689
|
+
const log = getLogger();
|
|
7690
|
+
const extractedAt = options.now ?? Date.now();
|
|
7691
|
+
const skippedIds = [];
|
|
7692
|
+
const inputs = [];
|
|
7693
|
+
for (const id of memoryIds) {
|
|
7694
|
+
let record;
|
|
7695
|
+
try {
|
|
7696
|
+
record = await ctx.vaultMemoryCollection.find(id);
|
|
7697
|
+
} catch {
|
|
7698
|
+
skippedIds.push(id);
|
|
7699
|
+
continue;
|
|
7700
|
+
}
|
|
7701
|
+
if (record.isDeleted || ctx.userId !== void 0 && record.userId !== ctx.userId || record.topicsUserManaged) {
|
|
7702
|
+
skippedIds.push(id);
|
|
7703
|
+
continue;
|
|
7704
|
+
}
|
|
7705
|
+
try {
|
|
7706
|
+
const stored = await vaultMemoryToStored(
|
|
7707
|
+
record,
|
|
7708
|
+
ctx.walletAddress,
|
|
7709
|
+
ctx.signMessage,
|
|
7710
|
+
ctx.embeddedWalletSigner
|
|
7711
|
+
);
|
|
7712
|
+
inputs.push({ id, content: stored.content });
|
|
7713
|
+
} catch (err) {
|
|
7714
|
+
log.warn("[memory/topics] failed to load memory for extraction", err);
|
|
7715
|
+
skippedIds.push(id);
|
|
7716
|
+
}
|
|
7717
|
+
}
|
|
7718
|
+
const entitiesByMemory = await extractEntitiesForMemories(inputs, options);
|
|
7719
|
+
const toStamp = [];
|
|
7720
|
+
for (const input of inputs) {
|
|
7721
|
+
const entities = entitiesByMemory.get(input.id);
|
|
7722
|
+
if (entities === void 0) {
|
|
7723
|
+
skippedIds.push(input.id);
|
|
7724
|
+
continue;
|
|
7725
|
+
}
|
|
7726
|
+
try {
|
|
7727
|
+
const linked = await replaceMemoryEntitiesGuardedOp(entityCtx, input.id, entities);
|
|
7728
|
+
if (linked === null) {
|
|
7729
|
+
skippedIds.push(input.id);
|
|
7730
|
+
entitiesByMemory.delete(input.id);
|
|
7731
|
+
continue;
|
|
7732
|
+
}
|
|
7733
|
+
} catch (err) {
|
|
7734
|
+
log.warn("[memory/topics] replaceMemoryEntitiesGuardedOp failed", err);
|
|
7735
|
+
skippedIds.push(input.id);
|
|
7736
|
+
entitiesByMemory.delete(input.id);
|
|
7737
|
+
continue;
|
|
7738
|
+
}
|
|
7739
|
+
toStamp.push(input.id);
|
|
7740
|
+
}
|
|
7741
|
+
const stampedIds = await stampTopicsExtractedAtOp(ctx, toStamp, extractedAt);
|
|
7742
|
+
const stampedSet = new Set(stampedIds);
|
|
7743
|
+
for (const id of toStamp) {
|
|
7744
|
+
if (!stampedSet.has(id)) {
|
|
7745
|
+
skippedIds.push(id);
|
|
7746
|
+
entitiesByMemory.delete(id);
|
|
7747
|
+
}
|
|
7748
|
+
}
|
|
7749
|
+
return { entitiesByMemory, stampedIds, skippedIds };
|
|
7750
|
+
}
|
|
7751
|
+
|
|
7585
7752
|
// src/lib/memoryEngine/quantization.ts
|
|
7586
7753
|
var INT8_MAX = 127;
|
|
7587
7754
|
function quantizeEmbedding(v) {
|
|
@@ -11921,7 +12088,7 @@ __decorateClass([
|
|
|
11921
12088
|
], VaultFolder.prototype, "context", 2);
|
|
11922
12089
|
|
|
11923
12090
|
// src/lib/db/schema.ts
|
|
11924
|
-
var SDK_SCHEMA_VERSION =
|
|
12091
|
+
var SDK_SCHEMA_VERSION = 36;
|
|
11925
12092
|
var sdkSchema = appSchema2({
|
|
11926
12093
|
version: SDK_SCHEMA_VERSION,
|
|
11927
12094
|
tables: [
|
|
@@ -12042,7 +12209,11 @@ var sdkSchema = appSchema2({
|
|
|
12042
12209
|
// When true, the user has manually set this memory's topics (entity
|
|
12043
12210
|
// links). Auto-extraction then leaves its links alone — the user owns
|
|
12044
12211
|
// them. Null/false = topics are auto-derived (default).
|
|
12045
|
-
{ name: "topics_user_managed", type: "boolean", isOptional: true }
|
|
12212
|
+
{ name: "topics_user_managed", type: "boolean", isOptional: true },
|
|
12213
|
+
// Unix ms of the last LLM topic-extraction pass over this memory's
|
|
12214
|
+
// content. Null = never extracted standalone (legacy rows with entity
|
|
12215
|
+
// links are grandfathered — see getMemoriesNeedingTopicExtractionOp).
|
|
12216
|
+
{ name: "topics_extracted_at", type: "number", isOptional: true }
|
|
12046
12217
|
]
|
|
12047
12218
|
}),
|
|
12048
12219
|
// Entity table — canonical names extracted from auto-extraction (W5).
|
|
@@ -12640,6 +12811,19 @@ var sdkMigrations = schemaMigrations2({
|
|
|
12640
12811
|
]
|
|
12641
12812
|
})
|
|
12642
12813
|
]
|
|
12814
|
+
},
|
|
12815
|
+
// v35 -> v36: topics_extracted_at watermark on memory_vault. Existing rows
|
|
12816
|
+
// keep it NULL — rows that already have entity links are grandfathered as
|
|
12817
|
+
// extracted (no mass re-extraction on upgrade), rows without links are the
|
|
12818
|
+
// backfill target. See getMemoriesNeedingTopicExtractionOp.
|
|
12819
|
+
{
|
|
12820
|
+
toVersion: 36,
|
|
12821
|
+
steps: [
|
|
12822
|
+
addColumns2({
|
|
12823
|
+
table: "memory_vault",
|
|
12824
|
+
columns: [{ name: "topics_extracted_at", type: "number", isOptional: true }]
|
|
12825
|
+
})
|
|
12826
|
+
]
|
|
12643
12827
|
}
|
|
12644
12828
|
]
|
|
12645
12829
|
});
|
|
@@ -22293,6 +22477,7 @@ export {
|
|
|
22293
22477
|
VaultMemory as StoredVaultMemoryModel,
|
|
22294
22478
|
StreamExpiredError,
|
|
22295
22479
|
THEME_ATTRS,
|
|
22480
|
+
TOPIC_EXTRACTION_BATCH_SIZE,
|
|
22296
22481
|
TextProcessor,
|
|
22297
22482
|
UIInteractionProvider,
|
|
22298
22483
|
WalletPoller,
|
|
@@ -22384,7 +22569,9 @@ export {
|
|
|
22384
22569
|
exportElementToPdf,
|
|
22385
22570
|
exportMarkdownToPdf,
|
|
22386
22571
|
exportPublicKey,
|
|
22572
|
+
extractAndLinkEntitiesForMemoriesOp,
|
|
22387
22573
|
extractAndRetain,
|
|
22574
|
+
extractEntitiesForMemories,
|
|
22388
22575
|
extractFacts,
|
|
22389
22576
|
extractFileIds,
|
|
22390
22577
|
fileExists,
|
|
@@ -22440,6 +22627,7 @@ export {
|
|
|
22440
22627
|
getMediaOp,
|
|
22441
22628
|
getMediaTypeFromMime,
|
|
22442
22629
|
getMemoriesByEntityNamesOp,
|
|
22630
|
+
getMemoriesNeedingTopicExtractionOp,
|
|
22443
22631
|
getNotionAccessToken,
|
|
22444
22632
|
getNotionMCPUrl,
|
|
22445
22633
|
getNumberAttr,
|
|
@@ -22520,6 +22708,7 @@ export {
|
|
|
22520
22708
|
renderAnumaTree,
|
|
22521
22709
|
renderElementToCanvas,
|
|
22522
22710
|
replaceById,
|
|
22711
|
+
replaceMemoryEntitiesGuardedOp,
|
|
22523
22712
|
requestEncryptionKey,
|
|
22524
22713
|
requestKeyPair,
|
|
22525
22714
|
resolveFilePlaceholders,
|
|
@@ -22545,6 +22734,7 @@ export {
|
|
|
22545
22734
|
settingsStorageSchema,
|
|
22546
22735
|
shouldChunkMessage,
|
|
22547
22736
|
shouldRefreshTools,
|
|
22737
|
+
stampTopicsExtractedAtOp,
|
|
22548
22738
|
startGithubAuth,
|
|
22549
22739
|
startNotionAuth,
|
|
22550
22740
|
storeCalendarPendingMessage,
|
|
@@ -15849,14 +15849,19 @@ async function upsertEntitiesOp(ctx, entities) {
|
|
|
15849
15849
|
return out;
|
|
15850
15850
|
});
|
|
15851
15851
|
}
|
|
15852
|
-
async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs) {
|
|
15852
|
+
async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
|
|
15853
15853
|
if (entityInputs.length === 0) return [];
|
|
15854
15854
|
const normalized = entityInputs.map((e) => typeof e === "string" ? { name: e } : e);
|
|
15855
15855
|
const byName = await upsertEntitiesOp(ctx, normalized);
|
|
15856
15856
|
const entities = Array.from(byName.values());
|
|
15857
15857
|
if (entities.length === 0) return [];
|
|
15858
15858
|
const userId = ctx.userId;
|
|
15859
|
+
let skipped = false;
|
|
15859
15860
|
await ctx.database.write(async () => {
|
|
15861
|
+
if (options?.unlessTopicsUserManaged && await autoLinkBlocked(ctx, memoryId)) {
|
|
15862
|
+
skipped = true;
|
|
15863
|
+
return;
|
|
15864
|
+
}
|
|
15860
15865
|
const existingLinks = await ctx.memoryEntityCollection.query(import_watermelondb2.Q.where("memory_id", memoryId)).fetch();
|
|
15861
15866
|
const existingEntityIds = new Set(existingLinks.map((l) => String(l.entityId)));
|
|
15862
15867
|
const toCreate = entities.filter((e) => !existingEntityIds.has(e.uniqueId));
|
|
@@ -15870,7 +15875,46 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs) {
|
|
|
15870
15875
|
);
|
|
15871
15876
|
await ctx.database.batch(...prepared);
|
|
15872
15877
|
});
|
|
15873
|
-
return entities;
|
|
15878
|
+
return skipped ? [] : entities;
|
|
15879
|
+
}
|
|
15880
|
+
async function autoLinkBlocked(ctx, memoryId) {
|
|
15881
|
+
try {
|
|
15882
|
+
const rows = await ctx.database.get("memory_vault").query(import_watermelondb2.Q.where("id", memoryId)).fetch();
|
|
15883
|
+
const row = rows[0];
|
|
15884
|
+
return !row || !!row.isDeleted || !!row.topicsUserManaged;
|
|
15885
|
+
} catch {
|
|
15886
|
+
return true;
|
|
15887
|
+
}
|
|
15888
|
+
}
|
|
15889
|
+
async function replaceMemoryEntitiesGuardedOp(ctx, memoryId, entityInputs) {
|
|
15890
|
+
const normalized = entityInputs.map((e) => typeof e === "string" ? { name: e } : e);
|
|
15891
|
+
const byName = await upsertEntitiesOp(ctx, normalized);
|
|
15892
|
+
const entities = Array.from(byName.values());
|
|
15893
|
+
const userId = ctx.userId;
|
|
15894
|
+
let skipped = false;
|
|
15895
|
+
await ctx.database.write(async () => {
|
|
15896
|
+
if (await autoLinkBlocked(ctx, memoryId)) {
|
|
15897
|
+
skipped = true;
|
|
15898
|
+
return;
|
|
15899
|
+
}
|
|
15900
|
+
const existingLinks = await ctx.memoryEntityCollection.query(import_watermelondb2.Q.where("memory_id", memoryId)).fetch();
|
|
15901
|
+
const keep = new Set(entities.map((e) => e.uniqueId));
|
|
15902
|
+
const existingEntityIds = new Set(existingLinks.map((l) => String(l.entityId)));
|
|
15903
|
+
const toCreate = entities.filter((e) => !existingEntityIds.has(e.uniqueId));
|
|
15904
|
+
const toDestroy = existingLinks.filter((l) => !keep.has(String(l.entityId)));
|
|
15905
|
+
if (toCreate.length === 0 && toDestroy.length === 0) return;
|
|
15906
|
+
await ctx.database.batch(
|
|
15907
|
+
...toCreate.map(
|
|
15908
|
+
(e) => ctx.memoryEntityCollection.prepareCreate((record) => {
|
|
15909
|
+
record._setRaw("memory_id", memoryId);
|
|
15910
|
+
record._setRaw("entity_id", e.uniqueId);
|
|
15911
|
+
if (userId !== void 0) record._setRaw("user_id", userId);
|
|
15912
|
+
})
|
|
15913
|
+
),
|
|
15914
|
+
...toDestroy.map((l) => l.prepareDestroyPermanently())
|
|
15915
|
+
);
|
|
15916
|
+
});
|
|
15917
|
+
return skipped ? null : entities;
|
|
15874
15918
|
}
|
|
15875
15919
|
async function unlinkMemoryEntitiesOp(ctx, memoryIds) {
|
|
15876
15920
|
if (memoryIds.length === 0) return;
|
|
@@ -15993,6 +16037,7 @@ function vaultMemoryToStoredRaw(memory) {
|
|
|
15993
16037
|
eventTimeEnd: memory.eventTimeEnd ?? null,
|
|
15994
16038
|
eventTimeKind: memory.eventTimeKind ?? null,
|
|
15995
16039
|
topicsUserManaged: memory.topicsUserManaged ?? false,
|
|
16040
|
+
topicsExtractedAt: memory.topicsExtractedAt ?? null,
|
|
15996
16041
|
createdAt: memory.createdAt,
|
|
15997
16042
|
updatedAt: memory.updatedAt,
|
|
15998
16043
|
isDeleted: memory.isDeleted
|
|
@@ -16176,6 +16221,7 @@ function vaultMemoryRawToStoredRaw(raw) {
|
|
|
16176
16221
|
eventTimeKind: raw.event_time_kind ?? null,
|
|
16177
16222
|
// SQLite stores booleans as 0/1, LokiJS as true/false — coerce both.
|
|
16178
16223
|
topicsUserManaged: raw.topics_user_managed === true || raw.topics_user_managed === 1,
|
|
16224
|
+
topicsExtractedAt: raw.topics_extracted_at ?? null,
|
|
16179
16225
|
createdAt: new Date(raw.created_at),
|
|
16180
16226
|
updatedAt: new Date(raw.updated_at),
|
|
16181
16227
|
isDeleted: raw.is_deleted === true || raw.is_deleted === 1
|
|
@@ -16318,6 +16364,87 @@ async function getUnfiledVaultMemoriesOp(ctx) {
|
|
|
16318
16364
|
(record) => vaultMemoryToStored(record, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner)
|
|
16319
16365
|
);
|
|
16320
16366
|
}
|
|
16367
|
+
async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
|
|
16368
|
+
const entityCtx = ctx.entityCtx;
|
|
16369
|
+
if (!entityCtx) {
|
|
16370
|
+
throw new Error("getMemoriesNeedingTopicExtractionOp requires ctx.entityCtx");
|
|
16371
|
+
}
|
|
16372
|
+
const conditions = [
|
|
16373
|
+
// Boolean optional column: null (legacy) and false both mean auto-managed.
|
|
16374
|
+
// Q.notEq(true) is a NULL trap on SQLite (NULL <> TRUE is NULL) — enumerate.
|
|
16375
|
+
import_watermelondb3.Q.or(import_watermelondb3.Q.where("topics_user_managed", null), import_watermelondb3.Q.where("topics_user_managed", false)),
|
|
16376
|
+
...baseVaultConditions(ctx),
|
|
16377
|
+
import_watermelondb3.Q.sortBy("created_at", import_watermelondb3.Q.desc)
|
|
16378
|
+
];
|
|
16379
|
+
const rows = await ctx.vaultMemoryCollection.query(...conditions).unsafeFetchRaw();
|
|
16380
|
+
const stampedPending = [];
|
|
16381
|
+
const unstamped = [];
|
|
16382
|
+
for (const raw of rows) {
|
|
16383
|
+
const stamp = raw.topics_extracted_at ?? null;
|
|
16384
|
+
if (stamp !== null) {
|
|
16385
|
+
if (raw.updated_at > stamp) stampedPending.push(raw);
|
|
16386
|
+
} else {
|
|
16387
|
+
unstamped.push(raw);
|
|
16388
|
+
}
|
|
16389
|
+
}
|
|
16390
|
+
const linkedIds = /* @__PURE__ */ new Set();
|
|
16391
|
+
const CHUNK = 500;
|
|
16392
|
+
for (let i = 0; i < unstamped.length; i += CHUNK) {
|
|
16393
|
+
const ids = unstamped.slice(i, i + CHUNK).map((r) => r.id);
|
|
16394
|
+
const links = await entityCtx.memoryEntityCollection.query(import_watermelondb3.Q.where("memory_id", import_watermelondb3.Q.oneOf(ids))).unsafeFetchRaw();
|
|
16395
|
+
for (const link of links) linkedIds.add(String(link.memory_id));
|
|
16396
|
+
}
|
|
16397
|
+
const linkedUnstampedAll = [];
|
|
16398
|
+
const pendingRaw = [...stampedPending];
|
|
16399
|
+
for (const raw of unstamped) {
|
|
16400
|
+
if (linkedIds.has(raw.id)) {
|
|
16401
|
+
linkedUnstampedAll.push(raw.id);
|
|
16402
|
+
} else {
|
|
16403
|
+
pendingRaw.push(raw);
|
|
16404
|
+
}
|
|
16405
|
+
}
|
|
16406
|
+
const cap = options?.limit !== void 0 && options.limit > 0 ? options.limit : void 0;
|
|
16407
|
+
const limitedPendingRaw = cap !== void 0 ? pendingRaw.slice(0, cap) : pendingRaw;
|
|
16408
|
+
const linkedUnstamped = cap !== void 0 ? linkedUnstampedAll.slice(0, cap) : linkedUnstampedAll;
|
|
16409
|
+
const pending = await mapInBatches(
|
|
16410
|
+
limitedPendingRaw,
|
|
16411
|
+
(raw) => vaultMemoryRawToStored(raw, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner)
|
|
16412
|
+
);
|
|
16413
|
+
return { pending, linkedUnstamped };
|
|
16414
|
+
}
|
|
16415
|
+
async function stampTopicsExtractedAtOp(ctx, memoryIds, extractedAt) {
|
|
16416
|
+
if (memoryIds.length === 0) return [];
|
|
16417
|
+
const uniqueIds = Array.from(new Set(memoryIds));
|
|
16418
|
+
const CHUNK = 500;
|
|
16419
|
+
const stamped = [];
|
|
16420
|
+
for (let i = 0; i < uniqueIds.length; i += CHUNK) {
|
|
16421
|
+
const chunkIds = uniqueIds.slice(i, i + CHUNK);
|
|
16422
|
+
await ctx.database.write(async () => {
|
|
16423
|
+
const prepared = [];
|
|
16424
|
+
for (const id of chunkIds) {
|
|
16425
|
+
let record;
|
|
16426
|
+
try {
|
|
16427
|
+
record = await ctx.vaultMemoryCollection.find(id);
|
|
16428
|
+
} catch {
|
|
16429
|
+
continue;
|
|
16430
|
+
}
|
|
16431
|
+
if (record.isDeleted || !isOwnedByCtxUser(ctx, record) || record.topicsUserManaged) {
|
|
16432
|
+
continue;
|
|
16433
|
+
}
|
|
16434
|
+
const originalUpdatedAt = record.updatedAt.getTime();
|
|
16435
|
+
prepared.push(
|
|
16436
|
+
record.prepareUpdate((r) => {
|
|
16437
|
+
r._setRaw("topics_extracted_at", extractedAt);
|
|
16438
|
+
r._setRaw("updated_at", originalUpdatedAt);
|
|
16439
|
+
})
|
|
16440
|
+
);
|
|
16441
|
+
stamped.push(id);
|
|
16442
|
+
}
|
|
16443
|
+
if (prepared.length > 0) await ctx.database.batch(...prepared);
|
|
16444
|
+
});
|
|
16445
|
+
}
|
|
16446
|
+
return stamped;
|
|
16447
|
+
}
|
|
16321
16448
|
async function deleteAllVaultMemoriesForUserOp(ctx, userId) {
|
|
16322
16449
|
if (ctx.userId !== void 0 && ctx.userId !== userId) return 0;
|
|
16323
16450
|
const records = await ctx.vaultMemoryCollection.query(import_watermelondb3.Q.where("user_id", userId), import_watermelondb3.Q.where("is_deleted", false)).fetch();
|
|
@@ -18848,7 +18975,7 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
|
|
|
18848
18975
|
legacy.map((r) => ({ id: r.uniqueId, content: r.content, score: r.similarity }))
|
|
18849
18976
|
);
|
|
18850
18977
|
}
|
|
18851
|
-
const { recall: recall2 } = await import("./recall-
|
|
18978
|
+
const { recall: recall2 } = await import("./recall-EWMI64SA.mjs");
|
|
18852
18979
|
const result = await recall2(
|
|
18853
18980
|
query,
|
|
18854
18981
|
{ vaultCtx, embeddingOptions, vaultCache: cache },
|
|
@@ -19476,6 +19603,8 @@ export {
|
|
|
19476
19603
|
makeSyntheticStoredConversation,
|
|
19477
19604
|
ENTITY_KINDS,
|
|
19478
19605
|
linkMemoryEntitiesOp,
|
|
19606
|
+
replaceMemoryEntitiesGuardedOp,
|
|
19607
|
+
vaultMemoryToStored,
|
|
19479
19608
|
createVaultMemoryOp,
|
|
19480
19609
|
createVaultMemoriesBatchOp,
|
|
19481
19610
|
getVaultMemoryOp,
|
|
@@ -19484,6 +19613,8 @@ export {
|
|
|
19484
19613
|
updateVaultMemoryOp,
|
|
19485
19614
|
deleteVaultMemoryOp,
|
|
19486
19615
|
getUnfiledVaultMemoriesOp,
|
|
19616
|
+
getMemoriesNeedingTopicExtractionOp,
|
|
19617
|
+
stampTopicsExtractedAtOp,
|
|
19487
19618
|
deleteAllVaultMemoriesForUserOp,
|
|
19488
19619
|
updateVaultMemoryEmbeddingOp,
|
|
19489
19620
|
DEFAULT_CHUNK_SIZE,
|