@anuma/sdk 6.32.0 → 6.33.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.
@@ -7500,7 +7500,13 @@ async function runToolLoop(options) {
7500
7500
  );
7501
7501
  const extra = results.flatMap((r) => Array.isArray(r) ? r : []);
7502
7502
  if (extra.length > 0) {
7503
- messages = [...messages, ...await redactBatch(extra)];
7503
+ let userIndex = messages.length - 1;
7504
+ while (userIndex > 0 && messages[userIndex].role !== "user") userIndex--;
7505
+ messages = [
7506
+ ...messages.slice(0, userIndex),
7507
+ ...await redactBatch(extra),
7508
+ ...messages.slice(userIndex)
7509
+ ];
7504
7510
  }
7505
7511
  }
7506
7512
  } catch (err) {
@@ -11576,6 +11582,51 @@ async function updateVaultMemoryOp(ctx, id, opts) {
11576
11582
  return null;
11577
11583
  }
11578
11584
  }
11585
+ async function setMemoryEntitiesOp(ctx, memoryId, entities) {
11586
+ const entityCtx = ctx.entityCtx;
11587
+ if (!entityCtx) {
11588
+ throw new Error("setMemoryEntitiesOp requires ctx.entityCtx (entity collections)");
11589
+ }
11590
+ let record;
11591
+ try {
11592
+ record = await ctx.vaultMemoryCollection.find(memoryId);
11593
+ } catch {
11594
+ return null;
11595
+ }
11596
+ if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) return null;
11597
+ let stale = false;
11598
+ const originalUpdatedAt = record.updatedAt.getTime();
11599
+ await ctx.database.write(async (writer) => {
11600
+ if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) {
11601
+ stale = true;
11602
+ return;
11603
+ }
11604
+ await record.update((r) => {
11605
+ r._setRaw("topics_user_managed", true);
11606
+ r._setRaw("updated_at", originalUpdatedAt);
11607
+ });
11608
+ const linked = entities.length > 0 ? await writer.callWriter(
11609
+ () => linkMemoryEntitiesOp(entityCtx, memoryId, entities, { topicsSource: "user" })
11610
+ ) : [];
11611
+ const keep = new Set(linked.map((e) => e.uniqueId));
11612
+ const existing = await entityCtx.memoryEntityCollection.query(Q5.where("memory_id", memoryId)).fetch();
11613
+ const staleLinks = existing.filter((l) => !keep.has(String(l.entityId)));
11614
+ if (staleLinks.length === 0 && entities.length > 0) return;
11615
+ const topicsWrite = await resolveMemoryTopicsWrite(
11616
+ entityCtx,
11617
+ memoryId,
11618
+ linked,
11619
+ entities,
11620
+ "user"
11621
+ );
11622
+ await ctx.database.batch(
11623
+ ...staleLinks.map((l) => l.prepareDestroyPermanently()),
11624
+ ...topicsWrite ? [prepareMemoryTopicsUpdate(topicsWrite)] : []
11625
+ );
11626
+ });
11627
+ if (stale) return null;
11628
+ return vaultMemoryToStored(record, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner);
11629
+ }
11579
11630
  async function clearMemoryTopicsOverrideOp(ctx, memoryId, options) {
11580
11631
  let record;
11581
11632
  try {
@@ -14129,7 +14180,10 @@ async function eagerEmbedContent(content, embeddingOptions, cache, vaultCtx, mem
14129
14180
  if (memoryId) cacheRowVector(cache, memoryId, Float32Array.from(embedding), updatedAt, content);
14130
14181
  if (vaultCtx && memoryId) {
14131
14182
  const currentModel = embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
14132
- updateVaultMemoryEmbeddingOp(vaultCtx, memoryId, JSON.stringify(embedding), currentModel).catch(
14183
+ updateVaultMemoryEmbeddingOp(vaultCtx, memoryId, JSON.stringify(embedding), currentModel, {
14184
+ content,
14185
+ ...updatedAt !== void 0 && { updatedAt: updatedAt.getTime() }
14186
+ }).catch(
14133
14187
  // Silently swallow – SDK must not use console.*; embedding will be retried on next search
14134
14188
  () => {
14135
14189
  }
@@ -17650,6 +17704,349 @@ function parseAnswer(body, base, parseSchema) {
17650
17704
  };
17651
17705
  }
17652
17706
 
17707
+ // src/lib/memory/store/local.ts
17708
+ import { Q as Q7 } from "@nozbe/watermelondb";
17709
+
17710
+ // src/lib/memory/topicExtract.ts
17711
+ var TOPIC_EXTRACTION_BATCH_SIZE = 10;
17712
+ var MAX_CHARS_PER_MEMORY = 300;
17713
+ var MAX_VOCABULARY_NAMES = 100;
17714
+ var MAX_COMPLETION_TOKENS = 8192;
17715
+ var SYSTEM_PROMPT5 = `You assign topics to saved memories for a personal memory system.
17716
+
17717
+ 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.
17718
+
17719
+ 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:
17720
+ - 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.
17721
+ - KEEP the specific thing instead, when the memory names one: "Blue Bottle on Valencia" over "cafe", "Chicago Marathon" over "event", "Hollowpoint Labs" over "work".
17722
+ - Prefer 0 entities over a weak one. A memory whose only candidate is a generic noun has NO entities.
17723
+ ${ENTITY_KIND_GUIDELINES}
17724
+
17725
+ Each memory below is written as "<id>: <text>". Output strict JSON: {"memories": [{"id": string, "entities": [{"name": string, "kind": string}]}]}
17726
+ - exactly one element per input memory; copy its id EXACTLY as written (the token before the first ": "), with no brackets, quotes, or extra characters
17727
+ - "entities" may be empty when a memory mentions no named entities \u2014 most short memories have 0-3
17728
+ No prose.`;
17729
+ async function extractEntitiesForMemories(memories, options) {
17730
+ const out = /* @__PURE__ */ new Map();
17731
+ if (memories.length === 0) return out;
17732
+ const redactor = resolvePiiRedactor(options.piiRedaction);
17733
+ const vocabulary = [];
17734
+ for (const name of (options.existingEntityNames ?? []).filter((n) => n.trim().length > 0).slice(0, MAX_VOCABULARY_NAMES)) {
17735
+ vocabulary.push(redactor ? (await redactor.redactTextAsync(name)).text : name);
17736
+ }
17737
+ const vocabularyNote = vocabulary.length > 0 ? `The user's existing topics: ${vocabulary.join(", ")}.
17738
+ 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".
17739
+
17740
+ ` : "";
17741
+ for (let i = 0; i < memories.length; i += TOPIC_EXTRACTION_BATCH_SIZE) {
17742
+ const batch = memories.slice(i, i + TOPIC_EXTRACTION_BATCH_SIZE);
17743
+ const rows = [];
17744
+ for (const m of batch) {
17745
+ const content = m.content.slice(0, MAX_CHARS_PER_MEMORY).replace(/\s+/g, " ").trim();
17746
+ rows.push(`${m.id}: ${redactor ? (await redactor.redactTextAsync(content)).text : content}`);
17747
+ }
17748
+ const listing = rows.join("\n");
17749
+ const parsed = await callPortalJsonCompletion({
17750
+ ...options.apiKey !== void 0 && { apiKey: options.apiKey },
17751
+ ...options.getToken !== void 0 && { getToken: options.getToken },
17752
+ ...options.baseUrl !== void 0 && { baseUrl: options.baseUrl },
17753
+ taskType: "memory_topic",
17754
+ ...options.endpointOverride !== void 0 && {
17755
+ endpointOverride: options.endpointOverride
17756
+ },
17757
+ model: options.model ?? DEFAULT_EXTRACTION_MODEL,
17758
+ systemPrompt: SYSTEM_PROMPT5,
17759
+ userMessage: `${vocabularyNote}Memories:
17760
+ ${listing}
17761
+
17762
+ List each memory's named entities.`,
17763
+ tag: "memory/topics",
17764
+ extra: { max_completion_tokens: MAX_COMPLETION_TOKENS },
17765
+ ...options.fetchFn && { fetchFn: options.fetchFn },
17766
+ ...options.maxAttempts !== void 0 && { maxAttempts: options.maxAttempts },
17767
+ ...options.timeoutMs !== void 0 && { timeoutMs: options.timeoutMs },
17768
+ ...options.totalTimeoutMs !== void 0 && { totalTimeoutMs: options.totalTimeoutMs },
17769
+ ...options.backoffMs && { backoffMs: options.backoffMs }
17770
+ });
17771
+ if (parsed === null) {
17772
+ getLogger().warn(
17773
+ `[memory/topics] batch of ${batch.length} failed after retries \u2014 will retry next sweep`
17774
+ );
17775
+ continue;
17776
+ }
17777
+ const validIds = new Set(batch.map((m) => m.id));
17778
+ const byId = parseTopicResponse(parsed, validIds);
17779
+ if (byId === null) {
17780
+ getLogger().warn(
17781
+ `[memory/topics] batch of ${batch.length} returned an unrecognized shape \u2014 will retry next sweep`
17782
+ );
17783
+ continue;
17784
+ }
17785
+ for (const m of batch) {
17786
+ const entities = byId.get(m.id);
17787
+ if (entities === void 0) {
17788
+ continue;
17789
+ }
17790
+ out.set(m.id, redactor ? restoreEntities(entities, redactor) : entities);
17791
+ }
17792
+ }
17793
+ return out;
17794
+ }
17795
+ function restoreEntities(entities, redactor) {
17796
+ return entities.map((e) => ({ kind: e.kind, restored: redactor.restoreForStorage(e.name) })).filter((e) => !e.restored.unresolved).map(
17797
+ (e) => e.kind !== void 0 ? { name: e.restored.text, kind: e.kind } : { name: e.restored.text }
17798
+ );
17799
+ }
17800
+ function parseTopicResponse(parsed, validIds) {
17801
+ if (typeof parsed !== "object" || parsed === null) return null;
17802
+ const list = parsed.memories;
17803
+ if (!Array.isArray(list)) return null;
17804
+ const out = /* @__PURE__ */ new Map();
17805
+ for (const raw of list) {
17806
+ if (typeof raw !== "object" || raw === null) continue;
17807
+ const obj = raw;
17808
+ if (typeof obj.id !== "string") continue;
17809
+ const id = obj.id.trim().replace(/:$/, "").replace(/^\[([\s\S]*)\]$/, "$1").trim();
17810
+ if (!validIds.has(id) || out.has(id)) continue;
17811
+ const entities = Array.isArray(obj.entities) ? parseEntities(obj.entities) : [];
17812
+ out.set(id, entities);
17813
+ }
17814
+ return out;
17815
+ }
17816
+ var DEGRADED_TOPIC_SKIP = {
17817
+ excluded: false,
17818
+ "link-declined": false,
17819
+ "stamp-declined": false,
17820
+ "llm-unanswered": true,
17821
+ unreadable: true,
17822
+ "not-found": true,
17823
+ "link-failed": true
17824
+ };
17825
+ function isDegradedTopicSkip(reason) {
17826
+ return DEGRADED_TOPIC_SKIP[reason] === true;
17827
+ }
17828
+ async function extractAndLinkEntitiesForMemoriesOp(ctx, memoryIds, options) {
17829
+ const entityCtx = ctx.entityCtx;
17830
+ if (!entityCtx) {
17831
+ throw new Error("extractAndLinkEntitiesForMemoriesOp requires ctx.entityCtx");
17832
+ }
17833
+ const log = getLogger();
17834
+ const extractedAt = options.now ?? Date.now();
17835
+ const skippedIds = [];
17836
+ const skippedReasons = /* @__PURE__ */ new Map();
17837
+ const skip = (id, reason) => {
17838
+ if (skippedReasons.has(id)) return;
17839
+ skippedIds.push(id);
17840
+ skippedReasons.set(id, reason);
17841
+ };
17842
+ const inputs = [];
17843
+ for (const id of new Set(memoryIds)) {
17844
+ let record;
17845
+ try {
17846
+ record = await ctx.vaultMemoryCollection.find(id);
17847
+ } catch (err) {
17848
+ log.warn("[memory/topics] vault lookup failed for extraction", err);
17849
+ skip(id, "not-found");
17850
+ continue;
17851
+ }
17852
+ if (record.isDeleted || ctx.userId !== void 0 && record.userId !== ctx.userId || record.topicsUserManaged) {
17853
+ skip(id, "excluded");
17854
+ continue;
17855
+ }
17856
+ try {
17857
+ const stored = await vaultMemoryToStored(
17858
+ record,
17859
+ ctx.walletAddress,
17860
+ ctx.signMessage,
17861
+ ctx.embeddedWalletSigner
17862
+ );
17863
+ inputs.push({ id, content: stored.content });
17864
+ } catch (err) {
17865
+ log.warn("[memory/topics] failed to load memory for extraction", err);
17866
+ skip(id, "unreadable");
17867
+ }
17868
+ }
17869
+ const entitiesByMemory = await extractEntitiesForMemories(inputs, options);
17870
+ const toStamp = [];
17871
+ for (const input of inputs) {
17872
+ const entities = entitiesByMemory.get(input.id);
17873
+ if (entities === void 0) {
17874
+ skip(input.id, "llm-unanswered");
17875
+ continue;
17876
+ }
17877
+ try {
17878
+ const linked = await replaceMemoryEntitiesGuardedOp(entityCtx, input.id, entities);
17879
+ if (linked === null) {
17880
+ skip(input.id, "link-declined");
17881
+ entitiesByMemory.delete(input.id);
17882
+ continue;
17883
+ }
17884
+ } catch (err) {
17885
+ log.warn("[memory/topics] replaceMemoryEntitiesGuardedOp failed", err);
17886
+ skip(input.id, "link-failed");
17887
+ entitiesByMemory.delete(input.id);
17888
+ continue;
17889
+ }
17890
+ toStamp.push(input.id);
17891
+ }
17892
+ const stampedIds = await stampTopicsExtractedAtOp(ctx, toStamp, extractedAt);
17893
+ const stampedSet = new Set(stampedIds);
17894
+ for (const id of toStamp) {
17895
+ if (!stampedSet.has(id)) {
17896
+ skip(id, "stamp-declined");
17897
+ entitiesByMemory.delete(id);
17898
+ }
17899
+ }
17900
+ return { entitiesByMemory, stampedIds, skippedIds, skippedReasons };
17901
+ }
17902
+
17903
+ // src/lib/memory/store/local.ts
17904
+ function createLocalMemoryStore(options) {
17905
+ const { database, userId } = options;
17906
+ const entityCtx = {
17907
+ database,
17908
+ entityCollection: database.get("entity"),
17909
+ memoryEntityCollection: database.get("memory_entity"),
17910
+ ...userId !== void 0 && { userId },
17911
+ ...options.allowUnscopedRows !== void 0 && {
17912
+ allowUnscopedRows: options.allowUnscopedRows
17913
+ }
17914
+ };
17915
+ const vaultCtx = {
17916
+ database,
17917
+ vaultMemoryCollection: database.get("memory_vault"),
17918
+ walletAddress: options.walletAddress,
17919
+ signMessage: options.signMessage,
17920
+ embeddedWalletSigner: options.embeddedWalletSigner,
17921
+ ...userId !== void 0 && { userId },
17922
+ ...options.singleTenant !== void 0 && { singleTenant: options.singleTenant },
17923
+ // Deletes cascade to memory_entity, so the graph lane never serves a
17924
+ // deleted memory's id.
17925
+ entityCtx
17926
+ };
17927
+ const vaultCache = options.vaultCache ?? createVaultEmbeddingCache();
17928
+ const ownedBy = userId !== void 0 ? [Q7.where("user_id", userId)] : [];
17929
+ const watchedColumns = [
17930
+ "content",
17931
+ "scope",
17932
+ "updated_at",
17933
+ "fact_type",
17934
+ "event_time_start",
17935
+ "event_time_end",
17936
+ "event_time_kind",
17937
+ "archived_at",
17938
+ "trust_tier",
17939
+ "superseded_by",
17940
+ "visibility",
17941
+ "published_at",
17942
+ "geohash",
17943
+ "topics",
17944
+ "topics_user_managed",
17945
+ "media",
17946
+ "source"
17947
+ ];
17948
+ const embedInBackground = (memory) => {
17949
+ eagerEmbedContent(
17950
+ memory.content,
17951
+ options.embeddingOptions,
17952
+ vaultCache,
17953
+ vaultCtx,
17954
+ memory.uniqueId,
17955
+ memory.updatedAt
17956
+ ).catch((err) => {
17957
+ getLogger().warn("[memory/store] Failed to embed memory:", err);
17958
+ });
17959
+ };
17960
+ return {
17961
+ list: (listOptions) => getAllVaultMemoriesOp(vaultCtx, listOptions),
17962
+ get: (id) => getVaultMemoryOp(vaultCtx, id),
17963
+ listArchived: async () => {
17964
+ const ids = await vaultCtx.vaultMemoryCollection.query(Q7.where("is_deleted", false), Q7.where("archived_at", Q7.notEq(null)), ...ownedBy).fetchIds();
17965
+ if (ids.length === 0) return [];
17966
+ const rows = await getAllVaultMemoriesOp(vaultCtx, { memoryIds: ids, includeArchived: true });
17967
+ return rows.sort((a, b) => (b.archivedAt ?? 0) - (a.archivedAt ?? 0));
17968
+ },
17969
+ memoriesByTopics: (names) => getMemoriesByEntityNamesOp(entityCtx, names),
17970
+ topicsByMemories: (memoryIds) => getEntitiesByMemoryIdsOp(entityCtx, memoryIds),
17971
+ create: async (input) => {
17972
+ const created = await createVaultMemoryOp(vaultCtx, input);
17973
+ if (input.embedding === void 0) embedInBackground(created);
17974
+ return created;
17975
+ },
17976
+ createMany: async (inputs) => {
17977
+ const created = await createVaultMemoriesBatchOp(vaultCtx, inputs);
17978
+ created.forEach((memory, i) => {
17979
+ if (inputs[i].embedding === void 0) embedInBackground(memory);
17980
+ });
17981
+ return created;
17982
+ },
17983
+ update: async (id, patch) => {
17984
+ const reembed = patch.embedding === void 0;
17985
+ const updated = await updateVaultMemoryOp(
17986
+ vaultCtx,
17987
+ id,
17988
+ reembed ? { ...patch, embedding: null } : patch
17989
+ );
17990
+ if (!updated) return null;
17991
+ vaultCache.delete(id);
17992
+ if (reembed) embedInBackground(updated);
17993
+ return updated;
17994
+ },
17995
+ delete: async (id) => {
17996
+ const deleted = await deleteVaultMemoryOp(vaultCtx, id);
17997
+ if (deleted) vaultCache.delete(id);
17998
+ return deleted;
17999
+ },
18000
+ supersede: (id, supersededById) => supersedeVaultMemoryOp(vaultCtx, id, supersededById),
18001
+ archive: (id) => archiveVaultMemoryOp(vaultCtx, id),
18002
+ restore: (id) => restoreVaultMemoryOp(vaultCtx, id),
18003
+ setTopics: (memoryId, topics) => setMemoryEntitiesOp(vaultCtx, memoryId, topics),
18004
+ addTopics: (memoryId, topics) => database.write(async (writer) => {
18005
+ const owned = await vaultCtx.vaultMemoryCollection.query(Q7.where("id", memoryId), Q7.where("is_deleted", false), ...ownedBy).fetchCount();
18006
+ return owned > 0 ? writer.callWriter(() => linkMemoryEntitiesOp(entityCtx, memoryId, topics)) : [];
18007
+ }),
18008
+ setVisibility: (id, visibility, visibilityOptions) => setMemoryVisibilityOp(vaultCtx, id, { visibility, ...visibilityOptions }),
18009
+ recall: (query, recallOptions) => recall(
18010
+ query,
18011
+ { vaultCtx, entityCtx, embeddingOptions: options.embeddingOptions, vaultCache },
18012
+ { ...recallOptions, types: ["fact"] }
18013
+ ),
18014
+ retain: (content, retainOptions) => retain(
18015
+ content,
18016
+ { vaultCtx, embeddingOptions: options.embeddingOptions, vaultCache },
18017
+ retainOptions
18018
+ ),
18019
+ subscribe: (onChange, subscribeOptions) => {
18020
+ const afterFirst = () => {
18021
+ let first = true;
18022
+ return () => {
18023
+ if (first) first = false;
18024
+ else onChange();
18025
+ };
18026
+ };
18027
+ const memories = subscribeOptions?.includeDeleted ? vaultCtx.vaultMemoryCollection.query(...ownedBy).observe() : vaultCtx.vaultMemoryCollection.query(Q7.where("is_deleted", false), ...ownedBy).observeWithColumns(
18028
+ subscribeOptions?.embeddings ? [...watchedColumns, "embedding", "embedding_model"] : watchedColumns
18029
+ );
18030
+ const subscriptions = [memories.subscribe(afterFirst())];
18031
+ if (subscribeOptions?.topics) {
18032
+ const linksOwnedBy = userId === void 0 ? [] : options.allowUnscopedRows ? [Q7.or(Q7.where("user_id", userId), Q7.where("user_id", null))] : [Q7.where("user_id", userId)];
18033
+ subscriptions.push(
18034
+ entityCtx.memoryEntityCollection.query(...linksOwnedBy).observe().subscribe(afterFirst())
18035
+ );
18036
+ }
18037
+ return () => subscriptions.forEach((s) => s.unsubscribe());
18038
+ },
18039
+ maintenance: {
18040
+ createDecaySweeper: (sweeperOptions) => createDecaySweeper({ ...sweeperOptions, vaultCtx }),
18041
+ getTopicBacklog: (backlogOptions) => getMemoriesNeedingTopicExtractionOp(vaultCtx, backlogOptions),
18042
+ extractTopics: (memoryIds, extractOptions) => extractAndLinkEntitiesForMemoriesOp(vaultCtx, memoryIds, extractOptions),
18043
+ stampTopicsExtracted: (memoryIds, extractedAt, version) => stampTopicsExtractedAtOp(vaultCtx, memoryIds, extractedAt, version),
18044
+ relinkTopics: (memoryIds) => relinkMemoryTopicsOp(vaultCtx, memoryIds),
18045
+ backfillTopics: (memoryIds) => backfillMemoryTopicsOp(vaultCtx, memoryIds)
18046
+ }
18047
+ };
18048
+ }
18049
+
17653
18050
  // src/lib/memory/synthesizeProfile.ts
17654
18051
  var DEFAULT_SYNTHESIS_MODEL = "inclusionai/ling-2.6-flash";
17655
18052
  var DEFAULT_FACET_RECALL_LIMIT = 20;
@@ -18177,199 +18574,6 @@ function normalizeInterests(values) {
18177
18574
  return out.length > 0 ? out : void 0;
18178
18575
  }
18179
18576
 
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
18577
  // src/lib/memory/verifySupport.ts
18374
18578
  var DEFAULT_MODEL5 = "inclusionai/ling-2.6-flash";
18375
18579
  var DEFAULT_ATTEMPTS3 = 2;
@@ -18760,7 +18964,7 @@ async function assembleMemoryContext(options) {
18760
18964
  }
18761
18965
 
18762
18966
  // src/lib/memory/durableExtraction.ts
18763
- import { Q as Q7 } from "@nozbe/watermelondb";
18967
+ import { Q as Q8 } from "@nozbe/watermelondb";
18764
18968
  var MAX_JOBS_PER_PASS = 3;
18765
18969
  var MAX_ATTEMPTS = 3;
18766
18970
  var MAX_FAILED_SESSIONS = 3;
@@ -18907,7 +19111,7 @@ function createDurableAutoExtractor(options) {
18907
19111
  ...options.retainCtx,
18908
19112
  vaultCtx: {
18909
19113
  ...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
19114
+ 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
19115
  }
18912
19116
  },
18913
19117
  // The model follows the job's scope, not the mode alive at drain time.
@@ -19049,7 +19253,7 @@ function createDurableAutoExtractor(options) {
19049
19253
  let scheduleAfterSuccess = false;
19050
19254
  try {
19051
19255
  await writes;
19052
- const pending = await jobs.query(Q7.where("owner_key", owner), Q7.where("message_ids", Q7.notEq("[]"))).fetch();
19256
+ const pending = await jobs.query(Q8.where("owner_key", owner), Q8.where("message_ids", Q8.notEq("[]"))).fetch();
19053
19257
  storeFailures = 0;
19054
19258
  let processed = 0;
19055
19259
  for (const job of pending) {
@@ -19065,7 +19269,7 @@ function createDurableAutoExtractor(options) {
19065
19269
  let ids = [];
19066
19270
  let loaded = [];
19067
19271
  try {
19068
- const conversations = await storage.conversationsCollection.query(Q7.where("conversation_id", conversationId), Q7.where("is_deleted", false)).fetchCount();
19272
+ const conversations = await storage.conversationsCollection.query(Q8.where("conversation_id", conversationId), Q8.where("is_deleted", false)).fetchCount();
19069
19273
  if (!conversations) {
19070
19274
  await database.write(() => job.destroyPermanently());
19071
19275
  continue;
@@ -19182,12 +19386,12 @@ function createDurableAutoExtractor(options) {
19182
19386
  const scope = currentScope();
19183
19387
  writes = writes.then(
19184
19388
  () => database.write(async () => {
19185
- const existing = await jobs.query(Q7.where("owner_key", owner), Q7.where("conversation_id", conversationId)).fetch();
19389
+ const existing = await jobs.query(Q8.where("owner_key", owner), Q8.where("conversation_id", conversationId)).fetch();
19186
19390
  const job = existing.find(
19187
19391
  (candidate) => candidate._getRaw("scope") === scope && (candidate._getRaw("folder_id") ?? null) === folderId
19188
19392
  );
19189
19393
  const positions = new Map(ids.map((id, index) => [id, index]));
19190
- const rows = await storage.messagesCollection.query(Q7.where("id", Q7.oneOf(ids))).fetch();
19394
+ const rows = await storage.messagesCollection.query(Q8.where("id", Q8.oneOf(ids))).fetch();
19191
19395
  const sequences = /* @__PURE__ */ new Map();
19192
19396
  for (const row of rows) {
19193
19397
  const sequence = Number(row._getRaw("message_id"));
@@ -23340,7 +23544,7 @@ var DatabaseManager = class {
23340
23544
  };
23341
23545
 
23342
23546
  // src/lib/db/conversationMemory/operations.ts
23343
- import { Q as Q8 } from "@nozbe/watermelondb";
23547
+ import { Q as Q9 } from "@nozbe/watermelondb";
23344
23548
  var MAX_PER_CONVERSATION = 200;
23345
23549
  function conversationMemoryToStored(row) {
23346
23550
  return {
@@ -23355,7 +23559,7 @@ async function addConversationMemoriesOp(ctx, conversationId, items) {
23355
23559
  if (!conversationId || items.length === 0) return;
23356
23560
  const now = Date.now();
23357
23561
  await ctx.database.write(async () => {
23358
- const existing = await ctx.conversationMemoryCollection.query(Q8.where("conversation_id", conversationId), Q8.sortBy("created_at", Q8.asc)).fetch();
23562
+ const existing = await ctx.conversationMemoryCollection.query(Q9.where("conversation_id", conversationId), Q9.sortBy("created_at", Q9.asc)).fetch();
23359
23563
  const seen = new Set(existing.map((r) => r.memoryId));
23360
23564
  const batchSeen = /* @__PURE__ */ new Set();
23361
23565
  const deduped = [];
@@ -23390,20 +23594,20 @@ async function addConversationMemoriesOp(ctx, conversationId, items) {
23390
23594
  }
23391
23595
  async function getConversationMemoriesOp(ctx, conversationId) {
23392
23596
  if (!conversationId) return [];
23393
- const rows = await ctx.conversationMemoryCollection.query(Q8.where("conversation_id", conversationId), Q8.sortBy("created_at", Q8.asc)).fetch();
23597
+ const rows = await ctx.conversationMemoryCollection.query(Q9.where("conversation_id", conversationId), Q9.sortBy("created_at", Q9.asc)).fetch();
23394
23598
  return rows.map(conversationMemoryToStored);
23395
23599
  }
23396
23600
  async function clearConversationMemoriesOp(ctx, conversationId) {
23397
23601
  if (!conversationId) return;
23398
23602
  await ctx.database.write(async () => {
23399
- const rows = await ctx.conversationMemoryCollection.query(Q8.where("conversation_id", conversationId)).fetch();
23603
+ const rows = await ctx.conversationMemoryCollection.query(Q9.where("conversation_id", conversationId)).fetch();
23400
23604
  if (rows.length === 0) return;
23401
23605
  await ctx.database.batch(...rows.map((r) => r.prepareDestroyPermanently()));
23402
23606
  });
23403
23607
  }
23404
23608
 
23405
23609
  // src/lib/db/vaultFolders/operations.ts
23406
- import { Q as Q9 } from "@nozbe/watermelondb";
23610
+ import { Q as Q10 } from "@nozbe/watermelondb";
23407
23611
  function folderToStored(folder) {
23408
23612
  return {
23409
23613
  uniqueId: folder.id,
@@ -23428,7 +23632,7 @@ async function createVaultFolderOp(ctx, opts) {
23428
23632
  return folderToStored(created);
23429
23633
  }
23430
23634
  async function getAllVaultFoldersOp(ctx) {
23431
- const results = await ctx.vaultFolderCollection.query(Q9.where("is_deleted", false), Q9.sortBy("created_at", Q9.desc)).fetch();
23635
+ const results = await ctx.vaultFolderCollection.query(Q10.where("is_deleted", false), Q10.sortBy("created_at", Q10.desc)).fetch();
23432
23636
  return results.map(folderToStored);
23433
23637
  }
23434
23638
  async function updateVaultFolderOp(ctx, id, opts) {
@@ -23437,7 +23641,7 @@ async function updateVaultFolderOp(ctx, id, opts) {
23437
23641
  if (record.isDeleted) return null;
23438
23642
  const scopeChanged = opts.scope !== void 0 && opts.scope !== record.scope;
23439
23643
  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() : [];
23644
+ const memories = scopeChanged ? await ctx.vaultMemoryCollection.query(Q10.where("folder_id", id), Q10.where("is_deleted", false)).fetch() : [];
23441
23645
  const updates = [];
23442
23646
  updates.push(
23443
23647
  record.prepareUpdate((r) => {
@@ -23465,7 +23669,7 @@ async function deleteVaultFolderOp(ctx, id) {
23465
23669
  const record = await ctx.vaultFolderCollection.find(id);
23466
23670
  if (record.isDeleted) return false;
23467
23671
  await ctx.database.write(async () => {
23468
- const memories = await ctx.vaultMemoryCollection.query(Q9.where("folder_id", id), Q9.where("is_deleted", false)).fetch();
23672
+ const memories = await ctx.vaultMemoryCollection.query(Q10.where("folder_id", id), Q10.where("is_deleted", false)).fetch();
23469
23673
  const preparedMemories = memories.map(
23470
23674
  (memory) => memory.prepareUpdate((r) => {
23471
23675
  r._setRaw("folder_id", null);
@@ -23534,7 +23738,7 @@ async function updateVaultFolderContextOp(ctx, id, context) {
23534
23738
  }
23535
23739
  }
23536
23740
  async function getVaultFolderMemoryCountOp(ctx, folderId) {
23537
- return await ctx.vaultMemoryCollection.query(Q9.where("folder_id", folderId), Q9.where("is_deleted", false)).fetchCount();
23741
+ return await ctx.vaultMemoryCollection.query(Q10.where("folder_id", folderId), Q10.where("is_deleted", false)).fetchCount();
23538
23742
  }
23539
23743
 
23540
23744
  // src/lib/db/vaultFolders/defaults.ts
@@ -24567,6 +24771,7 @@ export {
24567
24771
  createDurableAutoExtractor,
24568
24772
  createLlmDecayClassifier,
24569
24773
  createLlmNeighborRefiner,
24774
+ createLocalMemoryStore,
24570
24775
  createMemoryEngineTool,
24571
24776
  createMemoryVaultSearchTool,
24572
24777
  createMemoryVaultTool,