@anuma/sdk 2.0.1 → 2.2.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/expo/{chunk-N4FEY34G.mjs → chunk-7PAISHUN.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-62ZPSDHR.mjs} +1 -1
- package/dist/react/{chunk-63IW5BS5.mjs → chunk-KRNJVLI6.mjs} +135 -3
- package/dist/react/index.cjs +361 -26
- package/dist/react/index.d.mts +216 -6
- package/dist/react/index.d.ts +216 -6
- package/dist/react/index.mjs +226 -26
- package/dist/react/{recall-PUXEQHAP.mjs → recall-ASTJPM5Z.mjs} +1 -1
- package/dist/server/{chunk-45GOZ3AB.mjs → chunk-KEMPJE6C.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-K2YHLBBV.mjs} +1 -1
- package/package.json +1 -1
package/dist/expo/index.mjs
CHANGED
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
getConversationsLazyOp,
|
|
49
49
|
getConversationsOp,
|
|
50
50
|
getImageModel,
|
|
51
|
+
getMemoriesNeedingTopicExtractionOp,
|
|
51
52
|
getMessageCountOp,
|
|
52
53
|
getMessageSkeletonsOp,
|
|
53
54
|
getMessagesOp,
|
|
@@ -65,11 +66,13 @@ import {
|
|
|
65
66
|
postApiV1CreditsPurchase,
|
|
66
67
|
preEmbedVaultMemories,
|
|
67
68
|
recall,
|
|
69
|
+
replaceMemoryEntitiesGuardedOp,
|
|
68
70
|
requestEncryptionKey,
|
|
69
71
|
resolveStoredUserContent,
|
|
70
72
|
searchChunksOp,
|
|
71
73
|
searchVaultMemories,
|
|
72
74
|
shouldChunkMessage,
|
|
75
|
+
stampTopicsExtractedAtOp,
|
|
73
76
|
stripToolCalls,
|
|
74
77
|
updateConversationPinnedOp,
|
|
75
78
|
updateConversationTitleOp,
|
|
@@ -78,8 +81,9 @@ import {
|
|
|
78
81
|
updateVaultMemoryEmbeddingOp,
|
|
79
82
|
updateVaultMemoryOp,
|
|
80
83
|
upsertMessageOp,
|
|
81
|
-
useEncryption
|
|
82
|
-
|
|
84
|
+
useEncryption,
|
|
85
|
+
vaultMemoryToStored
|
|
86
|
+
} from "./chunk-7PAISHUN.mjs";
|
|
83
87
|
import {
|
|
84
88
|
PII_PATTERNS,
|
|
85
89
|
PiiRedactor,
|
|
@@ -5498,6 +5502,9 @@ __decorateClass([
|
|
|
5498
5502
|
__decorateClass([
|
|
5499
5503
|
field3("topics_user_managed")
|
|
5500
5504
|
], VaultMemory.prototype, "topicsUserManaged", 2);
|
|
5505
|
+
__decorateClass([
|
|
5506
|
+
field3("topics_extracted_at")
|
|
5507
|
+
], VaultMemory.prototype, "topicsExtractedAt", 2);
|
|
5501
5508
|
__decorateClass([
|
|
5502
5509
|
readonly2,
|
|
5503
5510
|
date3("created_at")
|
|
@@ -6053,7 +6060,7 @@ async function isMemoryTopicsUserManaged(ctx, memoryId) {
|
|
|
6053
6060
|
return true;
|
|
6054
6061
|
}
|
|
6055
6062
|
}
|
|
6056
|
-
var
|
|
6063
|
+
var DEFAULT_EXTRACTION_MODEL = "gpt-oss/gpt-oss-120b";
|
|
6057
6064
|
var DEFAULT_MIN_CONFIDENCE = 0.7;
|
|
6058
6065
|
var MAX_CONTENT_LENGTH = 200;
|
|
6059
6066
|
var MIN_CONTENT_LENGTH = 3;
|
|
@@ -6066,6 +6073,16 @@ var FACT_TYPES = [
|
|
|
6066
6073
|
"constraint",
|
|
6067
6074
|
"other"
|
|
6068
6075
|
];
|
|
6076
|
+
var ENTITY_KIND_GUIDELINES = `Choose the MOST SPECIFIC kind:
|
|
6077
|
+
- person: a named individual ("Sara", "Dr. Lee")
|
|
6078
|
+
- organization: a company, team, school, or group ("Google", "the Warriors", "Stanford")
|
|
6079
|
+
- place: a geographic location or venue ("San Francisco", "Blue Bottle on Valencia")
|
|
6080
|
+
- event: a dated or nameable occurrence ("Chicago Marathon", "the Japan trip", "their wedding")
|
|
6081
|
+
- product: a named app, tool, brand, device, book, film, or other media title ("Linear", "iPhone", "Dune", "Ableton")
|
|
6082
|
+
- thing: a concrete physical object that isn't a branded product ("road bike", "sourdough starter")
|
|
6083
|
+
- concept: a topic, skill, field, language, or idea ("machine learning", "Spanish", "async communication")
|
|
6084
|
+
- other: a named entity that genuinely fits none of the above \u2014 use sparingly
|
|
6085
|
+
Prefer organization / product / event / place over the generic "thing"; fall back to "thing" or "other" only when no specific kind fits.`;
|
|
6069
6086
|
var SYSTEM_PROMPT = `You extract durable user facts from conversations for a personal memory system.
|
|
6070
6087
|
|
|
6071
6088
|
A "durable fact" is something the user shared about themselves that should still be true in future conversations. Examples that ARE durable:
|
|
@@ -6094,16 +6111,7 @@ For each durable fact, output:
|
|
|
6094
6111
|
- type: one of identity | preference | relationship | plan | ongoing_context | constraint | other
|
|
6095
6112
|
- confidence: 0.0-1.0; how sure you are this is durable AND true. Only include facts >= 0.7.
|
|
6096
6113
|
- sourceMessageIds: which message IDs from the conversation contained the evidence
|
|
6097
|
-
- 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.
|
|
6098
|
-
- person: a named individual ("Sara", "Dr. Lee")
|
|
6099
|
-
- organization: a company, team, school, or group ("Google", "the Warriors", "Stanford")
|
|
6100
|
-
- place: a geographic location or venue ("San Francisco", "Blue Bottle on Valencia")
|
|
6101
|
-
- event: a dated or nameable occurrence ("Chicago Marathon", "the Japan trip", "their wedding")
|
|
6102
|
-
- product: a named app, tool, brand, device, book, film, or other media title ("Linear", "iPhone", "Dune", "Ableton")
|
|
6103
|
-
- thing: a concrete physical object that isn't a branded product ("road bike", "sourdough starter")
|
|
6104
|
-
- concept: a topic, skill, field, language, or idea ("machine learning", "Spanish", "async communication")
|
|
6105
|
-
- other: a named entity that genuinely fits none of the above \u2014 use sparingly
|
|
6106
|
-
Prefer organization / product / event / place over the generic "thing"; fall back to "thing" or "other" only when no specific kind fits. Examples:
|
|
6114
|
+
- 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:
|
|
6107
6115
|
- "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" }]
|
|
6108
6116
|
- "running the Berlin Half in May and I track workouts in Whoop" \u2192 entities: [{ "name": "Berlin Half", "kind": "event" }, { "name": "Whoop", "kind": "product" }]
|
|
6109
6117
|
- 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:
|
|
@@ -6129,7 +6137,7 @@ async function extractFacts(messages, options) {
|
|
|
6129
6137
|
...options.apiKey !== void 0 && { apiKey: options.apiKey },
|
|
6130
6138
|
...options.getToken !== void 0 && { getToken: options.getToken },
|
|
6131
6139
|
...options.baseUrl !== void 0 && { baseUrl: options.baseUrl },
|
|
6132
|
-
model: options.model ??
|
|
6140
|
+
model: options.model ?? DEFAULT_EXTRACTION_MODEL,
|
|
6133
6141
|
systemPrompt: SYSTEM_PROMPT,
|
|
6134
6142
|
userMessage: `Today's date is ${today}. Resolve any relative dates ("yesterday", "next week") against it.
|
|
6135
6143
|
|
|
@@ -6223,7 +6231,9 @@ async function extractAndRetain(messages, retainCtx, options) {
|
|
|
6223
6231
|
if (options.entityCtx && candidate.entities.length > 0) {
|
|
6224
6232
|
try {
|
|
6225
6233
|
if (!await isMemoryTopicsUserManaged(options.entityCtx, result.memoryId)) {
|
|
6226
|
-
await linkMemoryEntitiesOp(options.entityCtx, result.memoryId, candidate.entities
|
|
6234
|
+
await linkMemoryEntitiesOp(options.entityCtx, result.memoryId, candidate.entities, {
|
|
6235
|
+
unlessTopicsUserManaged: true
|
|
6236
|
+
});
|
|
6227
6237
|
}
|
|
6228
6238
|
} catch (err) {
|
|
6229
6239
|
log.warn("[memory/extract] linkMemoryEntitiesOp failed", err);
|
|
@@ -6618,7 +6628,7 @@ function createRecallTool(ctx, toolOptions, callbacks) {
|
|
|
6618
6628
|
|
|
6619
6629
|
// src/lib/memory/reflect.ts
|
|
6620
6630
|
var DEFAULT_BASE_URL = BASE_URL;
|
|
6621
|
-
var
|
|
6631
|
+
var DEFAULT_MODEL = "anthropic/claude-sonnet-4-6";
|
|
6622
6632
|
var DEFAULT_MAX_TOKENS = 4096;
|
|
6623
6633
|
var REQUEST_TIMEOUT_MS = 6e4;
|
|
6624
6634
|
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.
|
|
@@ -6650,7 +6660,7 @@ async function reflect(query, ctx, options) {
|
|
|
6650
6660
|
const evidence = recalled.memories.map((m, i) => `[${i + 1}] (id: ${m.id}, kind: ${m.kind})
|
|
6651
6661
|
${m.content}`).join("\n\n");
|
|
6652
6662
|
const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
|
|
6653
|
-
const model = options.llmModel ??
|
|
6663
|
+
const model = options.llmModel ?? DEFAULT_MODEL;
|
|
6654
6664
|
const wantsStructured = !!options.responseSchema;
|
|
6655
6665
|
const sendResponseFormat = wantsStructured && supportsResponseFormat(model, "json_schema");
|
|
6656
6666
|
const basePrompt = options.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
@@ -6760,6 +6770,163 @@ function parseAnswer(body, base, parseSchema) {
|
|
|
6760
6770
|
};
|
|
6761
6771
|
}
|
|
6762
6772
|
|
|
6773
|
+
// src/lib/memory/topicExtract.ts
|
|
6774
|
+
var TOPIC_EXTRACTION_BATCH_SIZE = 10;
|
|
6775
|
+
var MAX_CHARS_PER_MEMORY = 300;
|
|
6776
|
+
var MAX_VOCABULARY_NAMES = 100;
|
|
6777
|
+
var SYSTEM_PROMPT2 = `You assign topics to saved memories for a personal memory system.
|
|
6778
|
+
|
|
6779
|
+
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.
|
|
6780
|
+
|
|
6781
|
+
Include only NAMED entities, skip generic/common nouns. ${ENTITY_KIND_GUIDELINES}
|
|
6782
|
+
|
|
6783
|
+
Output strict JSON: {"memories": [{"id": string, "entities": [{"name": string, "kind": string}]}]}
|
|
6784
|
+
- exactly one element per input memory, echoing its id verbatim
|
|
6785
|
+
- "entities" may be empty when a memory mentions no named entities \u2014 most short memories have 0-3
|
|
6786
|
+
No prose.`;
|
|
6787
|
+
async function extractEntitiesForMemories(memories, options) {
|
|
6788
|
+
const out = /* @__PURE__ */ new Map();
|
|
6789
|
+
if (memories.length === 0) return out;
|
|
6790
|
+
const redactor = resolvePiiRedactor(options.piiRedaction);
|
|
6791
|
+
const vocabulary = (options.existingEntityNames ?? []).filter((n) => n.trim().length > 0).slice(0, MAX_VOCABULARY_NAMES).map((n) => redactor ? redactor.redactText(n).text : n);
|
|
6792
|
+
const vocabularyNote = vocabulary.length > 0 ? `The user's existing topics: ${vocabulary.join(", ")}.
|
|
6793
|
+
When a memory refers to one of these, reuse the EXACT existing name (same spelling and casing) instead of a variant.
|
|
6794
|
+
|
|
6795
|
+
` : "";
|
|
6796
|
+
for (let i = 0; i < memories.length; i += TOPIC_EXTRACTION_BATCH_SIZE) {
|
|
6797
|
+
const batch = memories.slice(i, i + TOPIC_EXTRACTION_BATCH_SIZE);
|
|
6798
|
+
const listing = batch.map((m) => {
|
|
6799
|
+
const content = m.content.slice(0, MAX_CHARS_PER_MEMORY);
|
|
6800
|
+
return `[${m.id}] ${redactor ? redactor.redactText(content).text : content}`;
|
|
6801
|
+
}).join("\n");
|
|
6802
|
+
const parsed = await callPortalJsonCompletion({
|
|
6803
|
+
...options.apiKey !== void 0 && { apiKey: options.apiKey },
|
|
6804
|
+
...options.getToken !== void 0 && { getToken: options.getToken },
|
|
6805
|
+
...options.baseUrl !== void 0 && { baseUrl: options.baseUrl },
|
|
6806
|
+
model: options.model ?? DEFAULT_EXTRACTION_MODEL,
|
|
6807
|
+
systemPrompt: SYSTEM_PROMPT2,
|
|
6808
|
+
userMessage: `${vocabularyNote}Memories:
|
|
6809
|
+
${listing}
|
|
6810
|
+
|
|
6811
|
+
List each memory's named entities.`,
|
|
6812
|
+
tag: "memory/topics",
|
|
6813
|
+
...options.fetchFn && { fetchFn: options.fetchFn },
|
|
6814
|
+
...options.maxAttempts !== void 0 && { maxAttempts: options.maxAttempts },
|
|
6815
|
+
...options.timeoutMs !== void 0 && { timeoutMs: options.timeoutMs },
|
|
6816
|
+
...options.totalTimeoutMs !== void 0 && { totalTimeoutMs: options.totalTimeoutMs },
|
|
6817
|
+
...options.backoffMs && { backoffMs: options.backoffMs }
|
|
6818
|
+
});
|
|
6819
|
+
if (parsed === null) {
|
|
6820
|
+
getLogger().warn(
|
|
6821
|
+
`[memory/topics] batch of ${batch.length} failed after retries \u2014 will retry next sweep`
|
|
6822
|
+
);
|
|
6823
|
+
continue;
|
|
6824
|
+
}
|
|
6825
|
+
const validIds = new Set(batch.map((m) => m.id));
|
|
6826
|
+
const byId = parseTopicResponse(parsed, validIds);
|
|
6827
|
+
if (byId === null) {
|
|
6828
|
+
getLogger().warn(
|
|
6829
|
+
`[memory/topics] batch of ${batch.length} returned an unrecognized shape \u2014 will retry next sweep`
|
|
6830
|
+
);
|
|
6831
|
+
continue;
|
|
6832
|
+
}
|
|
6833
|
+
for (const m of batch) {
|
|
6834
|
+
const entities = byId.get(m.id);
|
|
6835
|
+
if (entities === void 0) {
|
|
6836
|
+
continue;
|
|
6837
|
+
}
|
|
6838
|
+
out.set(m.id, redactor ? restoreEntities(entities, redactor) : entities);
|
|
6839
|
+
}
|
|
6840
|
+
}
|
|
6841
|
+
return out;
|
|
6842
|
+
}
|
|
6843
|
+
function restoreEntities(entities, redactor) {
|
|
6844
|
+
return entities.map((e) => ({ kind: e.kind, restored: redactor.restoreForStorage(e.name) })).filter((e) => !e.restored.unresolved).map(
|
|
6845
|
+
(e) => e.kind !== void 0 ? { name: e.restored.text, kind: e.kind } : { name: e.restored.text }
|
|
6846
|
+
);
|
|
6847
|
+
}
|
|
6848
|
+
function parseTopicResponse(parsed, validIds) {
|
|
6849
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
6850
|
+
const list = parsed.memories;
|
|
6851
|
+
if (!Array.isArray(list)) return null;
|
|
6852
|
+
const out = /* @__PURE__ */ new Map();
|
|
6853
|
+
for (const raw of list) {
|
|
6854
|
+
if (typeof raw !== "object" || raw === null) continue;
|
|
6855
|
+
const obj = raw;
|
|
6856
|
+
if (typeof obj.id !== "string" || !validIds.has(obj.id) || out.has(obj.id)) continue;
|
|
6857
|
+
const entities = Array.isArray(obj.entities) ? parseEntities(obj.entities) : [];
|
|
6858
|
+
out.set(obj.id, entities);
|
|
6859
|
+
}
|
|
6860
|
+
return out;
|
|
6861
|
+
}
|
|
6862
|
+
async function extractAndLinkEntitiesForMemoriesOp(ctx, memoryIds, options) {
|
|
6863
|
+
const entityCtx = ctx.entityCtx;
|
|
6864
|
+
if (!entityCtx) {
|
|
6865
|
+
throw new Error("extractAndLinkEntitiesForMemoriesOp requires ctx.entityCtx");
|
|
6866
|
+
}
|
|
6867
|
+
const log = getLogger();
|
|
6868
|
+
const extractedAt = options.now ?? Date.now();
|
|
6869
|
+
const skippedIds = [];
|
|
6870
|
+
const inputs = [];
|
|
6871
|
+
for (const id of memoryIds) {
|
|
6872
|
+
let record;
|
|
6873
|
+
try {
|
|
6874
|
+
record = await ctx.vaultMemoryCollection.find(id);
|
|
6875
|
+
} catch {
|
|
6876
|
+
skippedIds.push(id);
|
|
6877
|
+
continue;
|
|
6878
|
+
}
|
|
6879
|
+
if (record.isDeleted || ctx.userId !== void 0 && record.userId !== ctx.userId || record.topicsUserManaged) {
|
|
6880
|
+
skippedIds.push(id);
|
|
6881
|
+
continue;
|
|
6882
|
+
}
|
|
6883
|
+
try {
|
|
6884
|
+
const stored = await vaultMemoryToStored(
|
|
6885
|
+
record,
|
|
6886
|
+
ctx.walletAddress,
|
|
6887
|
+
ctx.signMessage,
|
|
6888
|
+
ctx.embeddedWalletSigner
|
|
6889
|
+
);
|
|
6890
|
+
inputs.push({ id, content: stored.content });
|
|
6891
|
+
} catch (err) {
|
|
6892
|
+
log.warn("[memory/topics] failed to load memory for extraction", err);
|
|
6893
|
+
skippedIds.push(id);
|
|
6894
|
+
}
|
|
6895
|
+
}
|
|
6896
|
+
const entitiesByMemory = await extractEntitiesForMemories(inputs, options);
|
|
6897
|
+
const toStamp = [];
|
|
6898
|
+
for (const input of inputs) {
|
|
6899
|
+
const entities = entitiesByMemory.get(input.id);
|
|
6900
|
+
if (entities === void 0) {
|
|
6901
|
+
skippedIds.push(input.id);
|
|
6902
|
+
continue;
|
|
6903
|
+
}
|
|
6904
|
+
try {
|
|
6905
|
+
const linked = await replaceMemoryEntitiesGuardedOp(entityCtx, input.id, entities);
|
|
6906
|
+
if (linked === null) {
|
|
6907
|
+
skippedIds.push(input.id);
|
|
6908
|
+
entitiesByMemory.delete(input.id);
|
|
6909
|
+
continue;
|
|
6910
|
+
}
|
|
6911
|
+
} catch (err) {
|
|
6912
|
+
log.warn("[memory/topics] replaceMemoryEntitiesGuardedOp failed", err);
|
|
6913
|
+
skippedIds.push(input.id);
|
|
6914
|
+
entitiesByMemory.delete(input.id);
|
|
6915
|
+
continue;
|
|
6916
|
+
}
|
|
6917
|
+
toStamp.push(input.id);
|
|
6918
|
+
}
|
|
6919
|
+
const stampedIds = await stampTopicsExtractedAtOp(ctx, toStamp, extractedAt);
|
|
6920
|
+
const stampedSet = new Set(stampedIds);
|
|
6921
|
+
for (const id of toStamp) {
|
|
6922
|
+
if (!stampedSet.has(id)) {
|
|
6923
|
+
skippedIds.push(id);
|
|
6924
|
+
entitiesByMemory.delete(id);
|
|
6925
|
+
}
|
|
6926
|
+
}
|
|
6927
|
+
return { entitiesByMemory, stampedIds, skippedIds };
|
|
6928
|
+
}
|
|
6929
|
+
|
|
6763
6930
|
// src/lib/memoryEngine/quantization.ts
|
|
6764
6931
|
var INT8_MAX = 127;
|
|
6765
6932
|
function quantizeEmbedding(v) {
|
|
@@ -9261,7 +9428,7 @@ __decorateClass([
|
|
|
9261
9428
|
], VaultFolder.prototype, "context", 2);
|
|
9262
9429
|
|
|
9263
9430
|
// src/lib/db/schema.ts
|
|
9264
|
-
var SDK_SCHEMA_VERSION =
|
|
9431
|
+
var SDK_SCHEMA_VERSION = 36;
|
|
9265
9432
|
var sdkSchema = appSchema2({
|
|
9266
9433
|
version: SDK_SCHEMA_VERSION,
|
|
9267
9434
|
tables: [
|
|
@@ -9382,7 +9549,11 @@ var sdkSchema = appSchema2({
|
|
|
9382
9549
|
// When true, the user has manually set this memory's topics (entity
|
|
9383
9550
|
// links). Auto-extraction then leaves its links alone — the user owns
|
|
9384
9551
|
// them. Null/false = topics are auto-derived (default).
|
|
9385
|
-
{ name: "topics_user_managed", type: "boolean", isOptional: true }
|
|
9552
|
+
{ name: "topics_user_managed", type: "boolean", isOptional: true },
|
|
9553
|
+
// Unix ms of the last LLM topic-extraction pass over this memory's
|
|
9554
|
+
// content. Null = never extracted standalone (legacy rows with entity
|
|
9555
|
+
// links are grandfathered — see getMemoriesNeedingTopicExtractionOp).
|
|
9556
|
+
{ name: "topics_extracted_at", type: "number", isOptional: true }
|
|
9386
9557
|
]
|
|
9387
9558
|
}),
|
|
9388
9559
|
// Entity table — canonical names extracted from auto-extraction (W5).
|
|
@@ -9980,6 +10151,19 @@ var sdkMigrations = schemaMigrations2({
|
|
|
9980
10151
|
]
|
|
9981
10152
|
})
|
|
9982
10153
|
]
|
|
10154
|
+
},
|
|
10155
|
+
// v35 -> v36: topics_extracted_at watermark on memory_vault. Existing rows
|
|
10156
|
+
// keep it NULL — rows that already have entity links are grandfathered as
|
|
10157
|
+
// extracted (no mass re-extraction on upgrade), rows without links are the
|
|
10158
|
+
// backfill target. See getMemoriesNeedingTopicExtractionOp.
|
|
10159
|
+
{
|
|
10160
|
+
toVersion: 36,
|
|
10161
|
+
steps: [
|
|
10162
|
+
addColumns2({
|
|
10163
|
+
table: "memory_vault",
|
|
10164
|
+
columns: [{ name: "topics_extracted_at", type: "number", isOptional: true }]
|
|
10165
|
+
})
|
|
10166
|
+
]
|
|
9983
10167
|
}
|
|
9984
10168
|
]
|
|
9985
10169
|
});
|
|
@@ -11337,6 +11521,7 @@ export {
|
|
|
11337
11521
|
VaultFolder as StoredVaultFolderModel,
|
|
11338
11522
|
VaultMemory as StoredVaultMemoryModel,
|
|
11339
11523
|
StreamExpiredError,
|
|
11524
|
+
TOPIC_EXTRACTION_BATCH_SIZE,
|
|
11340
11525
|
WalletPoller,
|
|
11341
11526
|
addConversationMemoriesOp,
|
|
11342
11527
|
buildNotionAuthUrl,
|
|
@@ -11388,7 +11573,9 @@ export {
|
|
|
11388
11573
|
embedMessage,
|
|
11389
11574
|
ensureDefaultFoldersOp,
|
|
11390
11575
|
exchangeNotionCode,
|
|
11576
|
+
extractAndLinkEntitiesForMemoriesOp,
|
|
11391
11577
|
extractAndRetain,
|
|
11578
|
+
extractEntitiesForMemories,
|
|
11392
11579
|
extractFacts,
|
|
11393
11580
|
generateConversationId,
|
|
11394
11581
|
generateEmbedding,
|
|
@@ -11402,6 +11589,7 @@ export {
|
|
|
11402
11589
|
getConversationsByProjectLazyOp,
|
|
11403
11590
|
getConversationsLazyOp,
|
|
11404
11591
|
getLogger,
|
|
11592
|
+
getMemoriesNeedingTopicExtractionOp,
|
|
11405
11593
|
getServerTools,
|
|
11406
11594
|
getUnfiledVaultMemoriesOp,
|
|
11407
11595
|
getVaultFolderMemoryCountOp,
|
|
@@ -11428,6 +11616,7 @@ export {
|
|
|
11428
11616
|
searchVaultMemories,
|
|
11429
11617
|
setLogger,
|
|
11430
11618
|
shouldChunkMessage,
|
|
11619
|
+
stampTopicsExtractedAtOp,
|
|
11431
11620
|
streamCancelPath,
|
|
11432
11621
|
streamReplayPath,
|
|
11433
11622
|
updateVaultFolderContextOp,
|
|
@@ -1797,14 +1797,19 @@ async function upsertEntitiesOp(ctx, entities) {
|
|
|
1797
1797
|
return out;
|
|
1798
1798
|
});
|
|
1799
1799
|
}
|
|
1800
|
-
async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs) {
|
|
1800
|
+
async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
|
|
1801
1801
|
if (entityInputs.length === 0) return [];
|
|
1802
1802
|
const normalized = entityInputs.map((e) => typeof e === "string" ? { name: e } : e);
|
|
1803
1803
|
const byName = await upsertEntitiesOp(ctx, normalized);
|
|
1804
1804
|
const entities = Array.from(byName.values());
|
|
1805
1805
|
if (entities.length === 0) return [];
|
|
1806
1806
|
const userId = ctx.userId;
|
|
1807
|
+
let skipped = false;
|
|
1807
1808
|
await ctx.database.write(async () => {
|
|
1809
|
+
if (options?.unlessTopicsUserManaged && await autoLinkBlocked(ctx, memoryId)) {
|
|
1810
|
+
skipped = true;
|
|
1811
|
+
return;
|
|
1812
|
+
}
|
|
1808
1813
|
const existingLinks = await ctx.memoryEntityCollection.query(Q2.where("memory_id", memoryId)).fetch();
|
|
1809
1814
|
const existingEntityIds = new Set(existingLinks.map((l) => String(l.entityId)));
|
|
1810
1815
|
const toCreate = entities.filter((e) => !existingEntityIds.has(e.uniqueId));
|
|
@@ -1818,7 +1823,46 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs) {
|
|
|
1818
1823
|
);
|
|
1819
1824
|
await ctx.database.batch(...prepared);
|
|
1820
1825
|
});
|
|
1821
|
-
return entities;
|
|
1826
|
+
return skipped ? [] : entities;
|
|
1827
|
+
}
|
|
1828
|
+
async function autoLinkBlocked(ctx, memoryId) {
|
|
1829
|
+
try {
|
|
1830
|
+
const rows = await ctx.database.get("memory_vault").query(Q2.where("id", memoryId)).fetch();
|
|
1831
|
+
const row = rows[0];
|
|
1832
|
+
return !row || !!row.isDeleted || !!row.topicsUserManaged;
|
|
1833
|
+
} catch {
|
|
1834
|
+
return true;
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
async function replaceMemoryEntitiesGuardedOp(ctx, memoryId, entityInputs) {
|
|
1838
|
+
const normalized = entityInputs.map((e) => typeof e === "string" ? { name: e } : e);
|
|
1839
|
+
const byName = await upsertEntitiesOp(ctx, normalized);
|
|
1840
|
+
const entities = Array.from(byName.values());
|
|
1841
|
+
const userId = ctx.userId;
|
|
1842
|
+
let skipped = false;
|
|
1843
|
+
await ctx.database.write(async () => {
|
|
1844
|
+
if (await autoLinkBlocked(ctx, memoryId)) {
|
|
1845
|
+
skipped = true;
|
|
1846
|
+
return;
|
|
1847
|
+
}
|
|
1848
|
+
const existingLinks = await ctx.memoryEntityCollection.query(Q2.where("memory_id", memoryId)).fetch();
|
|
1849
|
+
const keep = new Set(entities.map((e) => e.uniqueId));
|
|
1850
|
+
const existingEntityIds = new Set(existingLinks.map((l) => String(l.entityId)));
|
|
1851
|
+
const toCreate = entities.filter((e) => !existingEntityIds.has(e.uniqueId));
|
|
1852
|
+
const toDestroy = existingLinks.filter((l) => !keep.has(String(l.entityId)));
|
|
1853
|
+
if (toCreate.length === 0 && toDestroy.length === 0) return;
|
|
1854
|
+
await ctx.database.batch(
|
|
1855
|
+
...toCreate.map(
|
|
1856
|
+
(e) => ctx.memoryEntityCollection.prepareCreate((record) => {
|
|
1857
|
+
record._setRaw("memory_id", memoryId);
|
|
1858
|
+
record._setRaw("entity_id", e.uniqueId);
|
|
1859
|
+
if (userId !== void 0) record._setRaw("user_id", userId);
|
|
1860
|
+
})
|
|
1861
|
+
),
|
|
1862
|
+
...toDestroy.map((l) => l.prepareDestroyPermanently())
|
|
1863
|
+
);
|
|
1864
|
+
});
|
|
1865
|
+
return skipped ? null : entities;
|
|
1822
1866
|
}
|
|
1823
1867
|
async function unlinkMemoryEntitiesOp(ctx, memoryIds) {
|
|
1824
1868
|
if (memoryIds.length === 0) return;
|
|
@@ -1963,6 +2007,7 @@ function vaultMemoryToStoredRaw(memory) {
|
|
|
1963
2007
|
eventTimeEnd: memory.eventTimeEnd ?? null,
|
|
1964
2008
|
eventTimeKind: memory.eventTimeKind ?? null,
|
|
1965
2009
|
topicsUserManaged: memory.topicsUserManaged ?? false,
|
|
2010
|
+
topicsExtractedAt: memory.topicsExtractedAt ?? null,
|
|
1966
2011
|
createdAt: memory.createdAt,
|
|
1967
2012
|
updatedAt: memory.updatedAt,
|
|
1968
2013
|
isDeleted: memory.isDeleted
|
|
@@ -2146,6 +2191,7 @@ function vaultMemoryRawToStoredRaw(raw) {
|
|
|
2146
2191
|
eventTimeKind: raw.event_time_kind ?? null,
|
|
2147
2192
|
// SQLite stores booleans as 0/1, LokiJS as true/false — coerce both.
|
|
2148
2193
|
topicsUserManaged: raw.topics_user_managed === true || raw.topics_user_managed === 1,
|
|
2194
|
+
topicsExtractedAt: raw.topics_extracted_at ?? null,
|
|
2149
2195
|
createdAt: new Date(raw.created_at),
|
|
2150
2196
|
updatedAt: new Date(raw.updated_at),
|
|
2151
2197
|
isDeleted: raw.is_deleted === true || raw.is_deleted === 1
|
|
@@ -2298,6 +2344,7 @@ async function clearMemoryTopicsOverrideOp(ctx, memoryId) {
|
|
|
2298
2344
|
await ctx.database.write(async () => {
|
|
2299
2345
|
await record.update((r) => {
|
|
2300
2346
|
r._setRaw("topics_user_managed", false);
|
|
2347
|
+
r._setRaw("topics_extracted_at", null);
|
|
2301
2348
|
r._setRaw("updated_at", originalUpdatedAt);
|
|
2302
2349
|
});
|
|
2303
2350
|
});
|
|
@@ -2341,6 +2388,87 @@ async function getUnfiledVaultMemoriesOp(ctx) {
|
|
|
2341
2388
|
(record) => vaultMemoryToStored(record, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner)
|
|
2342
2389
|
);
|
|
2343
2390
|
}
|
|
2391
|
+
async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
|
|
2392
|
+
const entityCtx = ctx.entityCtx;
|
|
2393
|
+
if (!entityCtx) {
|
|
2394
|
+
throw new Error("getMemoriesNeedingTopicExtractionOp requires ctx.entityCtx");
|
|
2395
|
+
}
|
|
2396
|
+
const conditions = [
|
|
2397
|
+
// Boolean optional column: null (legacy) and false both mean auto-managed.
|
|
2398
|
+
// Q.notEq(true) is a NULL trap on SQLite (NULL <> TRUE is NULL) — enumerate.
|
|
2399
|
+
Q3.or(Q3.where("topics_user_managed", null), Q3.where("topics_user_managed", false)),
|
|
2400
|
+
...baseVaultConditions(ctx),
|
|
2401
|
+
Q3.sortBy("created_at", Q3.desc)
|
|
2402
|
+
];
|
|
2403
|
+
const rows = await ctx.vaultMemoryCollection.query(...conditions).unsafeFetchRaw();
|
|
2404
|
+
const stampedPending = [];
|
|
2405
|
+
const unstamped = [];
|
|
2406
|
+
for (const raw of rows) {
|
|
2407
|
+
const stamp = raw.topics_extracted_at ?? null;
|
|
2408
|
+
if (stamp !== null) {
|
|
2409
|
+
if (raw.updated_at > stamp) stampedPending.push(raw);
|
|
2410
|
+
} else {
|
|
2411
|
+
unstamped.push(raw);
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
const linkedIds = /* @__PURE__ */ new Set();
|
|
2415
|
+
const CHUNK = 500;
|
|
2416
|
+
for (let i = 0; i < unstamped.length; i += CHUNK) {
|
|
2417
|
+
const ids = unstamped.slice(i, i + CHUNK).map((r) => r.id);
|
|
2418
|
+
const links = await entityCtx.memoryEntityCollection.query(Q3.where("memory_id", Q3.oneOf(ids))).unsafeFetchRaw();
|
|
2419
|
+
for (const link of links) linkedIds.add(String(link.memory_id));
|
|
2420
|
+
}
|
|
2421
|
+
const linkedUnstampedAll = [];
|
|
2422
|
+
const pendingRaw = [...stampedPending];
|
|
2423
|
+
for (const raw of unstamped) {
|
|
2424
|
+
if (linkedIds.has(raw.id)) {
|
|
2425
|
+
linkedUnstampedAll.push(raw.id);
|
|
2426
|
+
} else {
|
|
2427
|
+
pendingRaw.push(raw);
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
const cap = options?.limit !== void 0 && options.limit > 0 ? options.limit : void 0;
|
|
2431
|
+
const limitedPendingRaw = cap !== void 0 ? pendingRaw.slice(0, cap) : pendingRaw;
|
|
2432
|
+
const linkedUnstamped = cap !== void 0 ? linkedUnstampedAll.slice(0, cap) : linkedUnstampedAll;
|
|
2433
|
+
const pending = await mapInBatches(
|
|
2434
|
+
limitedPendingRaw,
|
|
2435
|
+
(raw) => vaultMemoryRawToStored(raw, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner)
|
|
2436
|
+
);
|
|
2437
|
+
return { pending, linkedUnstamped };
|
|
2438
|
+
}
|
|
2439
|
+
async function stampTopicsExtractedAtOp(ctx, memoryIds, extractedAt) {
|
|
2440
|
+
if (memoryIds.length === 0) return [];
|
|
2441
|
+
const uniqueIds = Array.from(new Set(memoryIds));
|
|
2442
|
+
const CHUNK = 500;
|
|
2443
|
+
const stamped = [];
|
|
2444
|
+
for (let i = 0; i < uniqueIds.length; i += CHUNK) {
|
|
2445
|
+
const chunkIds = uniqueIds.slice(i, i + CHUNK);
|
|
2446
|
+
await ctx.database.write(async () => {
|
|
2447
|
+
const prepared = [];
|
|
2448
|
+
for (const id of chunkIds) {
|
|
2449
|
+
let record;
|
|
2450
|
+
try {
|
|
2451
|
+
record = await ctx.vaultMemoryCollection.find(id);
|
|
2452
|
+
} catch {
|
|
2453
|
+
continue;
|
|
2454
|
+
}
|
|
2455
|
+
if (record.isDeleted || !isOwnedByCtxUser(ctx, record) || record.topicsUserManaged) {
|
|
2456
|
+
continue;
|
|
2457
|
+
}
|
|
2458
|
+
const originalUpdatedAt = record.updatedAt.getTime();
|
|
2459
|
+
prepared.push(
|
|
2460
|
+
record.prepareUpdate((r) => {
|
|
2461
|
+
r._setRaw("topics_extracted_at", extractedAt);
|
|
2462
|
+
r._setRaw("updated_at", originalUpdatedAt);
|
|
2463
|
+
})
|
|
2464
|
+
);
|
|
2465
|
+
stamped.push(id);
|
|
2466
|
+
}
|
|
2467
|
+
if (prepared.length > 0) await ctx.database.batch(...prepared);
|
|
2468
|
+
});
|
|
2469
|
+
}
|
|
2470
|
+
return stamped;
|
|
2471
|
+
}
|
|
2344
2472
|
async function deleteAllVaultMemoriesForUserOp(ctx, userId) {
|
|
2345
2473
|
if (ctx.userId !== void 0 && ctx.userId !== userId) return 0;
|
|
2346
2474
|
const records = await ctx.vaultMemoryCollection.query(Q3.where("user_id", userId), Q3.where("is_deleted", false)).fetch();
|
|
@@ -5021,7 +5149,7 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
|
|
|
5021
5149
|
legacy.map((r) => ({ id: r.uniqueId, content: r.content, score: r.similarity }))
|
|
5022
5150
|
);
|
|
5023
5151
|
}
|
|
5024
|
-
const { recall: recall2 } = await import("./recall-
|
|
5152
|
+
const { recall: recall2 } = await import("./recall-ASTJPM5Z.mjs");
|
|
5025
5153
|
const result = await recall2(
|
|
5026
5154
|
query,
|
|
5027
5155
|
{ vaultCtx, embeddingOptions, vaultCache: cache },
|
|
@@ -5684,8 +5812,10 @@ export {
|
|
|
5684
5812
|
chunkAndEmbedAllMessages,
|
|
5685
5813
|
ENTITY_KINDS,
|
|
5686
5814
|
linkMemoryEntitiesOp,
|
|
5815
|
+
replaceMemoryEntitiesGuardedOp,
|
|
5687
5816
|
backfillMemoryEntityUserIdsOp,
|
|
5688
5817
|
getMemoriesByEntityNamesOp,
|
|
5818
|
+
vaultMemoryToStored,
|
|
5689
5819
|
createVaultMemoryOp,
|
|
5690
5820
|
createVaultMemoriesBatchOp,
|
|
5691
5821
|
getVaultMemoryOp,
|
|
@@ -5696,6 +5826,8 @@ export {
|
|
|
5696
5826
|
clearMemoryTopicsOverrideOp,
|
|
5697
5827
|
deleteVaultMemoryOp,
|
|
5698
5828
|
getUnfiledVaultMemoriesOp,
|
|
5829
|
+
getMemoriesNeedingTopicExtractionOp,
|
|
5830
|
+
stampTopicsExtractedAtOp,
|
|
5699
5831
|
deleteAllVaultMemoriesForUserOp,
|
|
5700
5832
|
updateVaultMemoryEmbeddingOp,
|
|
5701
5833
|
DEFAULT_VAULT_CACHE_SIZE,
|