@anuma/sdk 6.32.0 → 6.33.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.
@@ -11576,6 +11576,51 @@ async function updateVaultMemoryOp(ctx, id, opts) {
11576
11576
  return null;
11577
11577
  }
11578
11578
  }
11579
+ async function setMemoryEntitiesOp(ctx, memoryId, entities) {
11580
+ const entityCtx = ctx.entityCtx;
11581
+ if (!entityCtx) {
11582
+ throw new Error("setMemoryEntitiesOp requires ctx.entityCtx (entity collections)");
11583
+ }
11584
+ let record;
11585
+ try {
11586
+ record = await ctx.vaultMemoryCollection.find(memoryId);
11587
+ } catch {
11588
+ return null;
11589
+ }
11590
+ if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) return null;
11591
+ let stale = false;
11592
+ const originalUpdatedAt = record.updatedAt.getTime();
11593
+ await ctx.database.write(async (writer) => {
11594
+ if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) {
11595
+ stale = true;
11596
+ return;
11597
+ }
11598
+ await record.update((r) => {
11599
+ r._setRaw("topics_user_managed", true);
11600
+ r._setRaw("updated_at", originalUpdatedAt);
11601
+ });
11602
+ const linked = entities.length > 0 ? await writer.callWriter(
11603
+ () => linkMemoryEntitiesOp(entityCtx, memoryId, entities, { topicsSource: "user" })
11604
+ ) : [];
11605
+ const keep = new Set(linked.map((e) => e.uniqueId));
11606
+ const existing = await entityCtx.memoryEntityCollection.query(Q5.where("memory_id", memoryId)).fetch();
11607
+ const staleLinks = existing.filter((l) => !keep.has(String(l.entityId)));
11608
+ if (staleLinks.length === 0 && entities.length > 0) return;
11609
+ const topicsWrite = await resolveMemoryTopicsWrite(
11610
+ entityCtx,
11611
+ memoryId,
11612
+ linked,
11613
+ entities,
11614
+ "user"
11615
+ );
11616
+ await ctx.database.batch(
11617
+ ...staleLinks.map((l) => l.prepareDestroyPermanently()),
11618
+ ...topicsWrite ? [prepareMemoryTopicsUpdate(topicsWrite)] : []
11619
+ );
11620
+ });
11621
+ if (stale) return null;
11622
+ return vaultMemoryToStored(record, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner);
11623
+ }
11579
11624
  async function clearMemoryTopicsOverrideOp(ctx, memoryId, options) {
11580
11625
  let record;
11581
11626
  try {
@@ -14129,7 +14174,10 @@ async function eagerEmbedContent(content, embeddingOptions, cache, vaultCtx, mem
14129
14174
  if (memoryId) cacheRowVector(cache, memoryId, Float32Array.from(embedding), updatedAt, content);
14130
14175
  if (vaultCtx && memoryId) {
14131
14176
  const currentModel = embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
14132
- updateVaultMemoryEmbeddingOp(vaultCtx, memoryId, JSON.stringify(embedding), currentModel).catch(
14177
+ updateVaultMemoryEmbeddingOp(vaultCtx, memoryId, JSON.stringify(embedding), currentModel, {
14178
+ content,
14179
+ ...updatedAt !== void 0 && { updatedAt: updatedAt.getTime() }
14180
+ }).catch(
14133
14181
  // Silently swallow – SDK must not use console.*; embedding will be retried on next search
14134
14182
  () => {
14135
14183
  }
@@ -17650,6 +17698,349 @@ function parseAnswer(body, base, parseSchema) {
17650
17698
  };
17651
17699
  }
17652
17700
 
17701
+ // src/lib/memory/store/local.ts
17702
+ import { Q as Q7 } from "@nozbe/watermelondb";
17703
+
17704
+ // src/lib/memory/topicExtract.ts
17705
+ var TOPIC_EXTRACTION_BATCH_SIZE = 10;
17706
+ var MAX_CHARS_PER_MEMORY = 300;
17707
+ var MAX_VOCABULARY_NAMES = 100;
17708
+ var MAX_COMPLETION_TOKENS = 8192;
17709
+ var SYSTEM_PROMPT5 = `You assign topics to saved memories for a personal memory system.
17710
+
17711
+ 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.
17712
+
17713
+ Include only NAMED entities, skip generic/common nouns. A topic must identify a specific thing the user could recognize by name; if it would be equally true of thousands of unrelated memories, leave it out:
17714
+ - SKIP bare generic nouns even when the memory capitalizes them or a calendar entry is titled that way \u2014 "Home", "Work", "Meeting", "Lunch", "Birthday", "Appointment", "Trip", "Today", "Friday", "March" are NOT topics.
17715
+ - KEEP the specific thing instead, when the memory names one: "Blue Bottle on Valencia" over "cafe", "Chicago Marathon" over "event", "Hollowpoint Labs" over "work".
17716
+ - Prefer 0 entities over a weak one. A memory whose only candidate is a generic noun has NO entities.
17717
+ ${ENTITY_KIND_GUIDELINES}
17718
+
17719
+ Each memory below is written as "<id>: <text>". Output strict JSON: {"memories": [{"id": string, "entities": [{"name": string, "kind": string}]}]}
17720
+ - exactly one element per input memory; copy its id EXACTLY as written (the token before the first ": "), with no brackets, quotes, or extra characters
17721
+ - "entities" may be empty when a memory mentions no named entities \u2014 most short memories have 0-3
17722
+ No prose.`;
17723
+ async function extractEntitiesForMemories(memories, options) {
17724
+ const out = /* @__PURE__ */ new Map();
17725
+ if (memories.length === 0) return out;
17726
+ const redactor = resolvePiiRedactor(options.piiRedaction);
17727
+ const vocabulary = [];
17728
+ for (const name of (options.existingEntityNames ?? []).filter((n) => n.trim().length > 0).slice(0, MAX_VOCABULARY_NAMES)) {
17729
+ vocabulary.push(redactor ? (await redactor.redactTextAsync(name)).text : name);
17730
+ }
17731
+ const vocabularyNote = vocabulary.length > 0 ? `The user's existing topics: ${vocabulary.join(", ")}.
17732
+ When a memory refers to one of these, reuse the EXACT existing name (same spelling, casing, spacing and punctuation) instead of a variant \u2014 if "Foo.ai" is listed, never emit "Foo ai" or "Foo AI", and if "Jane Roe" is listed, emit that rather than a bare "Jane".
17733
+
17734
+ ` : "";
17735
+ for (let i = 0; i < memories.length; i += TOPIC_EXTRACTION_BATCH_SIZE) {
17736
+ const batch = memories.slice(i, i + TOPIC_EXTRACTION_BATCH_SIZE);
17737
+ const rows = [];
17738
+ for (const m of batch) {
17739
+ const content = m.content.slice(0, MAX_CHARS_PER_MEMORY).replace(/\s+/g, " ").trim();
17740
+ rows.push(`${m.id}: ${redactor ? (await redactor.redactTextAsync(content)).text : content}`);
17741
+ }
17742
+ const listing = rows.join("\n");
17743
+ const parsed = await callPortalJsonCompletion({
17744
+ ...options.apiKey !== void 0 && { apiKey: options.apiKey },
17745
+ ...options.getToken !== void 0 && { getToken: options.getToken },
17746
+ ...options.baseUrl !== void 0 && { baseUrl: options.baseUrl },
17747
+ taskType: "memory_topic",
17748
+ ...options.endpointOverride !== void 0 && {
17749
+ endpointOverride: options.endpointOverride
17750
+ },
17751
+ model: options.model ?? DEFAULT_EXTRACTION_MODEL,
17752
+ systemPrompt: SYSTEM_PROMPT5,
17753
+ userMessage: `${vocabularyNote}Memories:
17754
+ ${listing}
17755
+
17756
+ List each memory's named entities.`,
17757
+ tag: "memory/topics",
17758
+ extra: { max_completion_tokens: MAX_COMPLETION_TOKENS },
17759
+ ...options.fetchFn && { fetchFn: options.fetchFn },
17760
+ ...options.maxAttempts !== void 0 && { maxAttempts: options.maxAttempts },
17761
+ ...options.timeoutMs !== void 0 && { timeoutMs: options.timeoutMs },
17762
+ ...options.totalTimeoutMs !== void 0 && { totalTimeoutMs: options.totalTimeoutMs },
17763
+ ...options.backoffMs && { backoffMs: options.backoffMs }
17764
+ });
17765
+ if (parsed === null) {
17766
+ getLogger().warn(
17767
+ `[memory/topics] batch of ${batch.length} failed after retries \u2014 will retry next sweep`
17768
+ );
17769
+ continue;
17770
+ }
17771
+ const validIds = new Set(batch.map((m) => m.id));
17772
+ const byId = parseTopicResponse(parsed, validIds);
17773
+ if (byId === null) {
17774
+ getLogger().warn(
17775
+ `[memory/topics] batch of ${batch.length} returned an unrecognized shape \u2014 will retry next sweep`
17776
+ );
17777
+ continue;
17778
+ }
17779
+ for (const m of batch) {
17780
+ const entities = byId.get(m.id);
17781
+ if (entities === void 0) {
17782
+ continue;
17783
+ }
17784
+ out.set(m.id, redactor ? restoreEntities(entities, redactor) : entities);
17785
+ }
17786
+ }
17787
+ return out;
17788
+ }
17789
+ function restoreEntities(entities, redactor) {
17790
+ return entities.map((e) => ({ kind: e.kind, restored: redactor.restoreForStorage(e.name) })).filter((e) => !e.restored.unresolved).map(
17791
+ (e) => e.kind !== void 0 ? { name: e.restored.text, kind: e.kind } : { name: e.restored.text }
17792
+ );
17793
+ }
17794
+ function parseTopicResponse(parsed, validIds) {
17795
+ if (typeof parsed !== "object" || parsed === null) return null;
17796
+ const list = parsed.memories;
17797
+ if (!Array.isArray(list)) return null;
17798
+ const out = /* @__PURE__ */ new Map();
17799
+ for (const raw of list) {
17800
+ if (typeof raw !== "object" || raw === null) continue;
17801
+ const obj = raw;
17802
+ if (typeof obj.id !== "string") continue;
17803
+ const id = obj.id.trim().replace(/:$/, "").replace(/^\[([\s\S]*)\]$/, "$1").trim();
17804
+ if (!validIds.has(id) || out.has(id)) continue;
17805
+ const entities = Array.isArray(obj.entities) ? parseEntities(obj.entities) : [];
17806
+ out.set(id, entities);
17807
+ }
17808
+ return out;
17809
+ }
17810
+ var DEGRADED_TOPIC_SKIP = {
17811
+ excluded: false,
17812
+ "link-declined": false,
17813
+ "stamp-declined": false,
17814
+ "llm-unanswered": true,
17815
+ unreadable: true,
17816
+ "not-found": true,
17817
+ "link-failed": true
17818
+ };
17819
+ function isDegradedTopicSkip(reason) {
17820
+ return DEGRADED_TOPIC_SKIP[reason] === true;
17821
+ }
17822
+ async function extractAndLinkEntitiesForMemoriesOp(ctx, memoryIds, options) {
17823
+ const entityCtx = ctx.entityCtx;
17824
+ if (!entityCtx) {
17825
+ throw new Error("extractAndLinkEntitiesForMemoriesOp requires ctx.entityCtx");
17826
+ }
17827
+ const log = getLogger();
17828
+ const extractedAt = options.now ?? Date.now();
17829
+ const skippedIds = [];
17830
+ const skippedReasons = /* @__PURE__ */ new Map();
17831
+ const skip = (id, reason) => {
17832
+ if (skippedReasons.has(id)) return;
17833
+ skippedIds.push(id);
17834
+ skippedReasons.set(id, reason);
17835
+ };
17836
+ const inputs = [];
17837
+ for (const id of new Set(memoryIds)) {
17838
+ let record;
17839
+ try {
17840
+ record = await ctx.vaultMemoryCollection.find(id);
17841
+ } catch (err) {
17842
+ log.warn("[memory/topics] vault lookup failed for extraction", err);
17843
+ skip(id, "not-found");
17844
+ continue;
17845
+ }
17846
+ if (record.isDeleted || ctx.userId !== void 0 && record.userId !== ctx.userId || record.topicsUserManaged) {
17847
+ skip(id, "excluded");
17848
+ continue;
17849
+ }
17850
+ try {
17851
+ const stored = await vaultMemoryToStored(
17852
+ record,
17853
+ ctx.walletAddress,
17854
+ ctx.signMessage,
17855
+ ctx.embeddedWalletSigner
17856
+ );
17857
+ inputs.push({ id, content: stored.content });
17858
+ } catch (err) {
17859
+ log.warn("[memory/topics] failed to load memory for extraction", err);
17860
+ skip(id, "unreadable");
17861
+ }
17862
+ }
17863
+ const entitiesByMemory = await extractEntitiesForMemories(inputs, options);
17864
+ const toStamp = [];
17865
+ for (const input of inputs) {
17866
+ const entities = entitiesByMemory.get(input.id);
17867
+ if (entities === void 0) {
17868
+ skip(input.id, "llm-unanswered");
17869
+ continue;
17870
+ }
17871
+ try {
17872
+ const linked = await replaceMemoryEntitiesGuardedOp(entityCtx, input.id, entities);
17873
+ if (linked === null) {
17874
+ skip(input.id, "link-declined");
17875
+ entitiesByMemory.delete(input.id);
17876
+ continue;
17877
+ }
17878
+ } catch (err) {
17879
+ log.warn("[memory/topics] replaceMemoryEntitiesGuardedOp failed", err);
17880
+ skip(input.id, "link-failed");
17881
+ entitiesByMemory.delete(input.id);
17882
+ continue;
17883
+ }
17884
+ toStamp.push(input.id);
17885
+ }
17886
+ const stampedIds = await stampTopicsExtractedAtOp(ctx, toStamp, extractedAt);
17887
+ const stampedSet = new Set(stampedIds);
17888
+ for (const id of toStamp) {
17889
+ if (!stampedSet.has(id)) {
17890
+ skip(id, "stamp-declined");
17891
+ entitiesByMemory.delete(id);
17892
+ }
17893
+ }
17894
+ return { entitiesByMemory, stampedIds, skippedIds, skippedReasons };
17895
+ }
17896
+
17897
+ // src/lib/memory/store/local.ts
17898
+ function createLocalMemoryStore(options) {
17899
+ const { database, userId } = options;
17900
+ const entityCtx = {
17901
+ database,
17902
+ entityCollection: database.get("entity"),
17903
+ memoryEntityCollection: database.get("memory_entity"),
17904
+ ...userId !== void 0 && { userId },
17905
+ ...options.allowUnscopedRows !== void 0 && {
17906
+ allowUnscopedRows: options.allowUnscopedRows
17907
+ }
17908
+ };
17909
+ const vaultCtx = {
17910
+ database,
17911
+ vaultMemoryCollection: database.get("memory_vault"),
17912
+ walletAddress: options.walletAddress,
17913
+ signMessage: options.signMessage,
17914
+ embeddedWalletSigner: options.embeddedWalletSigner,
17915
+ ...userId !== void 0 && { userId },
17916
+ ...options.singleTenant !== void 0 && { singleTenant: options.singleTenant },
17917
+ // Deletes cascade to memory_entity, so the graph lane never serves a
17918
+ // deleted memory's id.
17919
+ entityCtx
17920
+ };
17921
+ const vaultCache = options.vaultCache ?? createVaultEmbeddingCache();
17922
+ const ownedBy = userId !== void 0 ? [Q7.where("user_id", userId)] : [];
17923
+ const watchedColumns = [
17924
+ "content",
17925
+ "scope",
17926
+ "updated_at",
17927
+ "fact_type",
17928
+ "event_time_start",
17929
+ "event_time_end",
17930
+ "event_time_kind",
17931
+ "archived_at",
17932
+ "trust_tier",
17933
+ "superseded_by",
17934
+ "visibility",
17935
+ "published_at",
17936
+ "geohash",
17937
+ "topics",
17938
+ "topics_user_managed",
17939
+ "media",
17940
+ "source"
17941
+ ];
17942
+ const embedInBackground = (memory) => {
17943
+ eagerEmbedContent(
17944
+ memory.content,
17945
+ options.embeddingOptions,
17946
+ vaultCache,
17947
+ vaultCtx,
17948
+ memory.uniqueId,
17949
+ memory.updatedAt
17950
+ ).catch((err) => {
17951
+ getLogger().warn("[memory/store] Failed to embed memory:", err);
17952
+ });
17953
+ };
17954
+ return {
17955
+ list: (listOptions) => getAllVaultMemoriesOp(vaultCtx, listOptions),
17956
+ get: (id) => getVaultMemoryOp(vaultCtx, id),
17957
+ listArchived: async () => {
17958
+ const ids = await vaultCtx.vaultMemoryCollection.query(Q7.where("is_deleted", false), Q7.where("archived_at", Q7.notEq(null)), ...ownedBy).fetchIds();
17959
+ if (ids.length === 0) return [];
17960
+ const rows = await getAllVaultMemoriesOp(vaultCtx, { memoryIds: ids, includeArchived: true });
17961
+ return rows.sort((a, b) => (b.archivedAt ?? 0) - (a.archivedAt ?? 0));
17962
+ },
17963
+ memoriesByTopics: (names) => getMemoriesByEntityNamesOp(entityCtx, names),
17964
+ topicsByMemories: (memoryIds) => getEntitiesByMemoryIdsOp(entityCtx, memoryIds),
17965
+ create: async (input) => {
17966
+ const created = await createVaultMemoryOp(vaultCtx, input);
17967
+ if (input.embedding === void 0) embedInBackground(created);
17968
+ return created;
17969
+ },
17970
+ createMany: async (inputs) => {
17971
+ const created = await createVaultMemoriesBatchOp(vaultCtx, inputs);
17972
+ created.forEach((memory, i) => {
17973
+ if (inputs[i].embedding === void 0) embedInBackground(memory);
17974
+ });
17975
+ return created;
17976
+ },
17977
+ update: async (id, patch) => {
17978
+ const reembed = patch.embedding === void 0;
17979
+ const updated = await updateVaultMemoryOp(
17980
+ vaultCtx,
17981
+ id,
17982
+ reembed ? { ...patch, embedding: null } : patch
17983
+ );
17984
+ if (!updated) return null;
17985
+ vaultCache.delete(id);
17986
+ if (reembed) embedInBackground(updated);
17987
+ return updated;
17988
+ },
17989
+ delete: async (id) => {
17990
+ const deleted = await deleteVaultMemoryOp(vaultCtx, id);
17991
+ if (deleted) vaultCache.delete(id);
17992
+ return deleted;
17993
+ },
17994
+ supersede: (id, supersededById) => supersedeVaultMemoryOp(vaultCtx, id, supersededById),
17995
+ archive: (id) => archiveVaultMemoryOp(vaultCtx, id),
17996
+ restore: (id) => restoreVaultMemoryOp(vaultCtx, id),
17997
+ setTopics: (memoryId, topics) => setMemoryEntitiesOp(vaultCtx, memoryId, topics),
17998
+ addTopics: (memoryId, topics) => database.write(async (writer) => {
17999
+ const owned = await vaultCtx.vaultMemoryCollection.query(Q7.where("id", memoryId), Q7.where("is_deleted", false), ...ownedBy).fetchCount();
18000
+ return owned > 0 ? writer.callWriter(() => linkMemoryEntitiesOp(entityCtx, memoryId, topics)) : [];
18001
+ }),
18002
+ setVisibility: (id, visibility, visibilityOptions) => setMemoryVisibilityOp(vaultCtx, id, { visibility, ...visibilityOptions }),
18003
+ recall: (query, recallOptions) => recall(
18004
+ query,
18005
+ { vaultCtx, entityCtx, embeddingOptions: options.embeddingOptions, vaultCache },
18006
+ { ...recallOptions, types: ["fact"] }
18007
+ ),
18008
+ retain: (content, retainOptions) => retain(
18009
+ content,
18010
+ { vaultCtx, embeddingOptions: options.embeddingOptions, vaultCache },
18011
+ retainOptions
18012
+ ),
18013
+ subscribe: (onChange, subscribeOptions) => {
18014
+ const afterFirst = () => {
18015
+ let first = true;
18016
+ return () => {
18017
+ if (first) first = false;
18018
+ else onChange();
18019
+ };
18020
+ };
18021
+ const memories = subscribeOptions?.includeDeleted ? vaultCtx.vaultMemoryCollection.query(...ownedBy).observe() : vaultCtx.vaultMemoryCollection.query(Q7.where("is_deleted", false), ...ownedBy).observeWithColumns(
18022
+ subscribeOptions?.embeddings ? [...watchedColumns, "embedding", "embedding_model"] : watchedColumns
18023
+ );
18024
+ const subscriptions = [memories.subscribe(afterFirst())];
18025
+ if (subscribeOptions?.topics) {
18026
+ const linksOwnedBy = userId === void 0 ? [] : options.allowUnscopedRows ? [Q7.or(Q7.where("user_id", userId), Q7.where("user_id", null))] : [Q7.where("user_id", userId)];
18027
+ subscriptions.push(
18028
+ entityCtx.memoryEntityCollection.query(...linksOwnedBy).observe().subscribe(afterFirst())
18029
+ );
18030
+ }
18031
+ return () => subscriptions.forEach((s) => s.unsubscribe());
18032
+ },
18033
+ maintenance: {
18034
+ createDecaySweeper: (sweeperOptions) => createDecaySweeper({ ...sweeperOptions, vaultCtx }),
18035
+ getTopicBacklog: (backlogOptions) => getMemoriesNeedingTopicExtractionOp(vaultCtx, backlogOptions),
18036
+ extractTopics: (memoryIds, extractOptions) => extractAndLinkEntitiesForMemoriesOp(vaultCtx, memoryIds, extractOptions),
18037
+ stampTopicsExtracted: (memoryIds, extractedAt, version) => stampTopicsExtractedAtOp(vaultCtx, memoryIds, extractedAt, version),
18038
+ relinkTopics: (memoryIds) => relinkMemoryTopicsOp(vaultCtx, memoryIds),
18039
+ backfillTopics: (memoryIds) => backfillMemoryTopicsOp(vaultCtx, memoryIds)
18040
+ }
18041
+ };
18042
+ }
18043
+
17653
18044
  // src/lib/memory/synthesizeProfile.ts
17654
18045
  var DEFAULT_SYNTHESIS_MODEL = "inclusionai/ling-2.6-flash";
17655
18046
  var DEFAULT_FACET_RECALL_LIMIT = 20;
@@ -18177,199 +18568,6 @@ function normalizeInterests(values) {
18177
18568
  return out.length > 0 ? out : void 0;
18178
18569
  }
18179
18570
 
18180
- // src/lib/memory/topicExtract.ts
18181
- var TOPIC_EXTRACTION_BATCH_SIZE = 10;
18182
- var MAX_CHARS_PER_MEMORY = 300;
18183
- var MAX_VOCABULARY_NAMES = 100;
18184
- var MAX_COMPLETION_TOKENS = 8192;
18185
- var SYSTEM_PROMPT5 = `You assign topics to saved memories for a personal memory system.
18186
-
18187
- 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.
18188
-
18189
- Include only NAMED entities, skip generic/common nouns. A topic must identify a specific thing the user could recognize by name; if it would be equally true of thousands of unrelated memories, leave it out:
18190
- - SKIP bare generic nouns even when the memory capitalizes them or a calendar entry is titled that way \u2014 "Home", "Work", "Meeting", "Lunch", "Birthday", "Appointment", "Trip", "Today", "Friday", "March" are NOT topics.
18191
- - KEEP the specific thing instead, when the memory names one: "Blue Bottle on Valencia" over "cafe", "Chicago Marathon" over "event", "Hollowpoint Labs" over "work".
18192
- - Prefer 0 entities over a weak one. A memory whose only candidate is a generic noun has NO entities.
18193
- ${ENTITY_KIND_GUIDELINES}
18194
-
18195
- Each memory below is written as "<id>: <text>". Output strict JSON: {"memories": [{"id": string, "entities": [{"name": string, "kind": string}]}]}
18196
- - exactly one element per input memory; copy its id EXACTLY as written (the token before the first ": "), with no brackets, quotes, or extra characters
18197
- - "entities" may be empty when a memory mentions no named entities \u2014 most short memories have 0-3
18198
- No prose.`;
18199
- async function extractEntitiesForMemories(memories, options) {
18200
- const out = /* @__PURE__ */ new Map();
18201
- if (memories.length === 0) return out;
18202
- const redactor = resolvePiiRedactor(options.piiRedaction);
18203
- const vocabulary = [];
18204
- for (const name of (options.existingEntityNames ?? []).filter((n) => n.trim().length > 0).slice(0, MAX_VOCABULARY_NAMES)) {
18205
- vocabulary.push(redactor ? (await redactor.redactTextAsync(name)).text : name);
18206
- }
18207
- const vocabularyNote = vocabulary.length > 0 ? `The user's existing topics: ${vocabulary.join(", ")}.
18208
- When a memory refers to one of these, reuse the EXACT existing name (same spelling, casing, spacing and punctuation) instead of a variant \u2014 if "Foo.ai" is listed, never emit "Foo ai" or "Foo AI", and if "Jane Roe" is listed, emit that rather than a bare "Jane".
18209
-
18210
- ` : "";
18211
- for (let i = 0; i < memories.length; i += TOPIC_EXTRACTION_BATCH_SIZE) {
18212
- const batch = memories.slice(i, i + TOPIC_EXTRACTION_BATCH_SIZE);
18213
- const rows = [];
18214
- for (const m of batch) {
18215
- const content = m.content.slice(0, MAX_CHARS_PER_MEMORY).replace(/\s+/g, " ").trim();
18216
- rows.push(`${m.id}: ${redactor ? (await redactor.redactTextAsync(content)).text : content}`);
18217
- }
18218
- const listing = rows.join("\n");
18219
- const parsed = await callPortalJsonCompletion({
18220
- ...options.apiKey !== void 0 && { apiKey: options.apiKey },
18221
- ...options.getToken !== void 0 && { getToken: options.getToken },
18222
- ...options.baseUrl !== void 0 && { baseUrl: options.baseUrl },
18223
- taskType: "memory_topic",
18224
- ...options.endpointOverride !== void 0 && {
18225
- endpointOverride: options.endpointOverride
18226
- },
18227
- model: options.model ?? DEFAULT_EXTRACTION_MODEL,
18228
- systemPrompt: SYSTEM_PROMPT5,
18229
- userMessage: `${vocabularyNote}Memories:
18230
- ${listing}
18231
-
18232
- List each memory's named entities.`,
18233
- tag: "memory/topics",
18234
- extra: { max_completion_tokens: MAX_COMPLETION_TOKENS },
18235
- ...options.fetchFn && { fetchFn: options.fetchFn },
18236
- ...options.maxAttempts !== void 0 && { maxAttempts: options.maxAttempts },
18237
- ...options.timeoutMs !== void 0 && { timeoutMs: options.timeoutMs },
18238
- ...options.totalTimeoutMs !== void 0 && { totalTimeoutMs: options.totalTimeoutMs },
18239
- ...options.backoffMs && { backoffMs: options.backoffMs }
18240
- });
18241
- if (parsed === null) {
18242
- getLogger().warn(
18243
- `[memory/topics] batch of ${batch.length} failed after retries \u2014 will retry next sweep`
18244
- );
18245
- continue;
18246
- }
18247
- const validIds = new Set(batch.map((m) => m.id));
18248
- const byId = parseTopicResponse(parsed, validIds);
18249
- if (byId === null) {
18250
- getLogger().warn(
18251
- `[memory/topics] batch of ${batch.length} returned an unrecognized shape \u2014 will retry next sweep`
18252
- );
18253
- continue;
18254
- }
18255
- for (const m of batch) {
18256
- const entities = byId.get(m.id);
18257
- if (entities === void 0) {
18258
- continue;
18259
- }
18260
- out.set(m.id, redactor ? restoreEntities(entities, redactor) : entities);
18261
- }
18262
- }
18263
- return out;
18264
- }
18265
- function restoreEntities(entities, redactor) {
18266
- return entities.map((e) => ({ kind: e.kind, restored: redactor.restoreForStorage(e.name) })).filter((e) => !e.restored.unresolved).map(
18267
- (e) => e.kind !== void 0 ? { name: e.restored.text, kind: e.kind } : { name: e.restored.text }
18268
- );
18269
- }
18270
- function parseTopicResponse(parsed, validIds) {
18271
- if (typeof parsed !== "object" || parsed === null) return null;
18272
- const list = parsed.memories;
18273
- if (!Array.isArray(list)) return null;
18274
- const out = /* @__PURE__ */ new Map();
18275
- for (const raw of list) {
18276
- if (typeof raw !== "object" || raw === null) continue;
18277
- const obj = raw;
18278
- if (typeof obj.id !== "string") continue;
18279
- const id = obj.id.trim().replace(/:$/, "").replace(/^\[([\s\S]*)\]$/, "$1").trim();
18280
- if (!validIds.has(id) || out.has(id)) continue;
18281
- const entities = Array.isArray(obj.entities) ? parseEntities(obj.entities) : [];
18282
- out.set(id, entities);
18283
- }
18284
- return out;
18285
- }
18286
- var DEGRADED_TOPIC_SKIP = {
18287
- excluded: false,
18288
- "link-declined": false,
18289
- "stamp-declined": false,
18290
- "llm-unanswered": true,
18291
- unreadable: true,
18292
- "not-found": true,
18293
- "link-failed": true
18294
- };
18295
- function isDegradedTopicSkip(reason) {
18296
- return DEGRADED_TOPIC_SKIP[reason] === true;
18297
- }
18298
- async function extractAndLinkEntitiesForMemoriesOp(ctx, memoryIds, options) {
18299
- const entityCtx = ctx.entityCtx;
18300
- if (!entityCtx) {
18301
- throw new Error("extractAndLinkEntitiesForMemoriesOp requires ctx.entityCtx");
18302
- }
18303
- const log = getLogger();
18304
- const extractedAt = options.now ?? Date.now();
18305
- const skippedIds = [];
18306
- const skippedReasons = /* @__PURE__ */ new Map();
18307
- const skip = (id, reason) => {
18308
- if (skippedReasons.has(id)) return;
18309
- skippedIds.push(id);
18310
- skippedReasons.set(id, reason);
18311
- };
18312
- const inputs = [];
18313
- for (const id of new Set(memoryIds)) {
18314
- let record;
18315
- try {
18316
- record = await ctx.vaultMemoryCollection.find(id);
18317
- } catch (err) {
18318
- log.warn("[memory/topics] vault lookup failed for extraction", err);
18319
- skip(id, "not-found");
18320
- continue;
18321
- }
18322
- if (record.isDeleted || ctx.userId !== void 0 && record.userId !== ctx.userId || record.topicsUserManaged) {
18323
- skip(id, "excluded");
18324
- continue;
18325
- }
18326
- try {
18327
- const stored = await vaultMemoryToStored(
18328
- record,
18329
- ctx.walletAddress,
18330
- ctx.signMessage,
18331
- ctx.embeddedWalletSigner
18332
- );
18333
- inputs.push({ id, content: stored.content });
18334
- } catch (err) {
18335
- log.warn("[memory/topics] failed to load memory for extraction", err);
18336
- skip(id, "unreadable");
18337
- }
18338
- }
18339
- const entitiesByMemory = await extractEntitiesForMemories(inputs, options);
18340
- const toStamp = [];
18341
- for (const input of inputs) {
18342
- const entities = entitiesByMemory.get(input.id);
18343
- if (entities === void 0) {
18344
- skip(input.id, "llm-unanswered");
18345
- continue;
18346
- }
18347
- try {
18348
- const linked = await replaceMemoryEntitiesGuardedOp(entityCtx, input.id, entities);
18349
- if (linked === null) {
18350
- skip(input.id, "link-declined");
18351
- entitiesByMemory.delete(input.id);
18352
- continue;
18353
- }
18354
- } catch (err) {
18355
- log.warn("[memory/topics] replaceMemoryEntitiesGuardedOp failed", err);
18356
- skip(input.id, "link-failed");
18357
- entitiesByMemory.delete(input.id);
18358
- continue;
18359
- }
18360
- toStamp.push(input.id);
18361
- }
18362
- const stampedIds = await stampTopicsExtractedAtOp(ctx, toStamp, extractedAt);
18363
- const stampedSet = new Set(stampedIds);
18364
- for (const id of toStamp) {
18365
- if (!stampedSet.has(id)) {
18366
- skip(id, "stamp-declined");
18367
- entitiesByMemory.delete(id);
18368
- }
18369
- }
18370
- return { entitiesByMemory, stampedIds, skippedIds, skippedReasons };
18371
- }
18372
-
18373
18571
  // src/lib/memory/verifySupport.ts
18374
18572
  var DEFAULT_MODEL5 = "inclusionai/ling-2.6-flash";
18375
18573
  var DEFAULT_ATTEMPTS3 = 2;
@@ -18760,7 +18958,7 @@ async function assembleMemoryContext(options) {
18760
18958
  }
18761
18959
 
18762
18960
  // src/lib/memory/durableExtraction.ts
18763
- import { Q as Q7 } from "@nozbe/watermelondb";
18961
+ import { Q as Q8 } from "@nozbe/watermelondb";
18764
18962
  var MAX_JOBS_PER_PASS = 3;
18765
18963
  var MAX_ATTEMPTS = 3;
18766
18964
  var MAX_FAILED_SESSIONS = 3;
@@ -18907,7 +19105,7 @@ function createDurableAutoExtractor(options) {
18907
19105
  ...options.retainCtx,
18908
19106
  vaultCtx: {
18909
19107
  ...vault,
18910
- canWrite: async () => !disposed && !cancelled && await storage.conversationsCollection.query(Q7.where("conversation_id", conversationId), Q7.where("is_deleted", false)).fetchCount() > 0 && await storage.messagesCollection.query(Q7.where("conversation_id", conversationId), Q7.where("id", Q7.oneOf(ids))).fetchCount() === ids.length && (!vault.canWrite || await vault.canWrite()) && !disposed && !cancelled
19108
+ canWrite: async () => !disposed && !cancelled && await storage.conversationsCollection.query(Q8.where("conversation_id", conversationId), Q8.where("is_deleted", false)).fetchCount() > 0 && await storage.messagesCollection.query(Q8.where("conversation_id", conversationId), Q8.where("id", Q8.oneOf(ids))).fetchCount() === ids.length && (!vault.canWrite || await vault.canWrite()) && !disposed && !cancelled
18911
19109
  }
18912
19110
  },
18913
19111
  // The model follows the job's scope, not the mode alive at drain time.
@@ -19049,7 +19247,7 @@ function createDurableAutoExtractor(options) {
19049
19247
  let scheduleAfterSuccess = false;
19050
19248
  try {
19051
19249
  await writes;
19052
- const pending = await jobs.query(Q7.where("owner_key", owner), Q7.where("message_ids", Q7.notEq("[]"))).fetch();
19250
+ const pending = await jobs.query(Q8.where("owner_key", owner), Q8.where("message_ids", Q8.notEq("[]"))).fetch();
19053
19251
  storeFailures = 0;
19054
19252
  let processed = 0;
19055
19253
  for (const job of pending) {
@@ -19065,7 +19263,7 @@ function createDurableAutoExtractor(options) {
19065
19263
  let ids = [];
19066
19264
  let loaded = [];
19067
19265
  try {
19068
- const conversations = await storage.conversationsCollection.query(Q7.where("conversation_id", conversationId), Q7.where("is_deleted", false)).fetchCount();
19266
+ const conversations = await storage.conversationsCollection.query(Q8.where("conversation_id", conversationId), Q8.where("is_deleted", false)).fetchCount();
19069
19267
  if (!conversations) {
19070
19268
  await database.write(() => job.destroyPermanently());
19071
19269
  continue;
@@ -19182,12 +19380,12 @@ function createDurableAutoExtractor(options) {
19182
19380
  const scope = currentScope();
19183
19381
  writes = writes.then(
19184
19382
  () => database.write(async () => {
19185
- const existing = await jobs.query(Q7.where("owner_key", owner), Q7.where("conversation_id", conversationId)).fetch();
19383
+ const existing = await jobs.query(Q8.where("owner_key", owner), Q8.where("conversation_id", conversationId)).fetch();
19186
19384
  const job = existing.find(
19187
19385
  (candidate) => candidate._getRaw("scope") === scope && (candidate._getRaw("folder_id") ?? null) === folderId
19188
19386
  );
19189
19387
  const positions = new Map(ids.map((id, index) => [id, index]));
19190
- const rows = await storage.messagesCollection.query(Q7.where("id", Q7.oneOf(ids))).fetch();
19388
+ const rows = await storage.messagesCollection.query(Q8.where("id", Q8.oneOf(ids))).fetch();
19191
19389
  const sequences = /* @__PURE__ */ new Map();
19192
19390
  for (const row of rows) {
19193
19391
  const sequence = Number(row._getRaw("message_id"));
@@ -23340,7 +23538,7 @@ var DatabaseManager = class {
23340
23538
  };
23341
23539
 
23342
23540
  // src/lib/db/conversationMemory/operations.ts
23343
- import { Q as Q8 } from "@nozbe/watermelondb";
23541
+ import { Q as Q9 } from "@nozbe/watermelondb";
23344
23542
  var MAX_PER_CONVERSATION = 200;
23345
23543
  function conversationMemoryToStored(row) {
23346
23544
  return {
@@ -23355,7 +23553,7 @@ async function addConversationMemoriesOp(ctx, conversationId, items) {
23355
23553
  if (!conversationId || items.length === 0) return;
23356
23554
  const now = Date.now();
23357
23555
  await ctx.database.write(async () => {
23358
- const existing = await ctx.conversationMemoryCollection.query(Q8.where("conversation_id", conversationId), Q8.sortBy("created_at", Q8.asc)).fetch();
23556
+ const existing = await ctx.conversationMemoryCollection.query(Q9.where("conversation_id", conversationId), Q9.sortBy("created_at", Q9.asc)).fetch();
23359
23557
  const seen = new Set(existing.map((r) => r.memoryId));
23360
23558
  const batchSeen = /* @__PURE__ */ new Set();
23361
23559
  const deduped = [];
@@ -23390,20 +23588,20 @@ async function addConversationMemoriesOp(ctx, conversationId, items) {
23390
23588
  }
23391
23589
  async function getConversationMemoriesOp(ctx, conversationId) {
23392
23590
  if (!conversationId) return [];
23393
- const rows = await ctx.conversationMemoryCollection.query(Q8.where("conversation_id", conversationId), Q8.sortBy("created_at", Q8.asc)).fetch();
23591
+ const rows = await ctx.conversationMemoryCollection.query(Q9.where("conversation_id", conversationId), Q9.sortBy("created_at", Q9.asc)).fetch();
23394
23592
  return rows.map(conversationMemoryToStored);
23395
23593
  }
23396
23594
  async function clearConversationMemoriesOp(ctx, conversationId) {
23397
23595
  if (!conversationId) return;
23398
23596
  await ctx.database.write(async () => {
23399
- const rows = await ctx.conversationMemoryCollection.query(Q8.where("conversation_id", conversationId)).fetch();
23597
+ const rows = await ctx.conversationMemoryCollection.query(Q9.where("conversation_id", conversationId)).fetch();
23400
23598
  if (rows.length === 0) return;
23401
23599
  await ctx.database.batch(...rows.map((r) => r.prepareDestroyPermanently()));
23402
23600
  });
23403
23601
  }
23404
23602
 
23405
23603
  // src/lib/db/vaultFolders/operations.ts
23406
- import { Q as Q9 } from "@nozbe/watermelondb";
23604
+ import { Q as Q10 } from "@nozbe/watermelondb";
23407
23605
  function folderToStored(folder) {
23408
23606
  return {
23409
23607
  uniqueId: folder.id,
@@ -23428,7 +23626,7 @@ async function createVaultFolderOp(ctx, opts) {
23428
23626
  return folderToStored(created);
23429
23627
  }
23430
23628
  async function getAllVaultFoldersOp(ctx) {
23431
- const results = await ctx.vaultFolderCollection.query(Q9.where("is_deleted", false), Q9.sortBy("created_at", Q9.desc)).fetch();
23629
+ const results = await ctx.vaultFolderCollection.query(Q10.where("is_deleted", false), Q10.sortBy("created_at", Q10.desc)).fetch();
23432
23630
  return results.map(folderToStored);
23433
23631
  }
23434
23632
  async function updateVaultFolderOp(ctx, id, opts) {
@@ -23437,7 +23635,7 @@ async function updateVaultFolderOp(ctx, id, opts) {
23437
23635
  if (record.isDeleted) return null;
23438
23636
  const scopeChanged = opts.scope !== void 0 && opts.scope !== record.scope;
23439
23637
  const updated = await ctx.database.write(async () => {
23440
- const memories = scopeChanged ? await ctx.vaultMemoryCollection.query(Q9.where("folder_id", id), Q9.where("is_deleted", false)).fetch() : [];
23638
+ const memories = scopeChanged ? await ctx.vaultMemoryCollection.query(Q10.where("folder_id", id), Q10.where("is_deleted", false)).fetch() : [];
23441
23639
  const updates = [];
23442
23640
  updates.push(
23443
23641
  record.prepareUpdate((r) => {
@@ -23465,7 +23663,7 @@ async function deleteVaultFolderOp(ctx, id) {
23465
23663
  const record = await ctx.vaultFolderCollection.find(id);
23466
23664
  if (record.isDeleted) return false;
23467
23665
  await ctx.database.write(async () => {
23468
- const memories = await ctx.vaultMemoryCollection.query(Q9.where("folder_id", id), Q9.where("is_deleted", false)).fetch();
23666
+ const memories = await ctx.vaultMemoryCollection.query(Q10.where("folder_id", id), Q10.where("is_deleted", false)).fetch();
23469
23667
  const preparedMemories = memories.map(
23470
23668
  (memory) => memory.prepareUpdate((r) => {
23471
23669
  r._setRaw("folder_id", null);
@@ -23534,7 +23732,7 @@ async function updateVaultFolderContextOp(ctx, id, context) {
23534
23732
  }
23535
23733
  }
23536
23734
  async function getVaultFolderMemoryCountOp(ctx, folderId) {
23537
- return await ctx.vaultMemoryCollection.query(Q9.where("folder_id", folderId), Q9.where("is_deleted", false)).fetchCount();
23735
+ return await ctx.vaultMemoryCollection.query(Q10.where("folder_id", folderId), Q10.where("is_deleted", false)).fetchCount();
23538
23736
  }
23539
23737
 
23540
23738
  // src/lib/db/vaultFolders/defaults.ts
@@ -24567,6 +24765,7 @@ export {
24567
24765
  createDurableAutoExtractor,
24568
24766
  createLlmDecayClassifier,
24569
24767
  createLlmNeighborRefiner,
24768
+ createLocalMemoryStore,
24570
24769
  createMemoryEngineTool,
24571
24770
  createMemoryVaultSearchTool,
24572
24771
  createMemoryVaultTool,