@anuma/sdk 3.8.2 → 3.8.4

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.
@@ -1801,7 +1801,7 @@ function entityToStored(e) {
1801
1801
  updatedAt: e.updatedAt
1802
1802
  };
1803
1803
  }
1804
- async function upsertEntitiesOp(ctx, entities) {
1804
+ async function upsertEntitiesInWrite(ctx, entities) {
1805
1805
  const kindByName = /* @__PURE__ */ new Map();
1806
1806
  for (const e of entities) {
1807
1807
  const name = normalizeEntityName(e.name);
@@ -1815,60 +1815,62 @@ async function upsertEntitiesOp(ctx, entities) {
1815
1815
  }
1816
1816
  const unique = Array.from(kindByName.keys());
1817
1817
  const out = /* @__PURE__ */ new Map();
1818
- if (unique.length === 0) return out;
1819
- return await ctx.database.write(async () => {
1820
- const existing = await ctx.entityCollection.query(Q2.where("canonical_name", Q2.oneOf(unique))).fetch();
1821
- const existingNames = new Set(existing.map((e) => e.canonicalName));
1822
- const updates = existing.filter((e) => {
1823
- const incoming = kindByName.get(e.canonicalName);
1824
- return incoming !== void 0 && (e.kind === null || e.kind === void 0 || e.kind === "");
1825
- });
1826
- const missing = unique.filter((n) => !existingNames.has(n));
1827
- const created = missing.map(
1828
- (name) => ctx.entityCollection.prepareCreate((record) => {
1829
- record._setRaw("canonical_name", name);
1830
- const kind = kindByName.get(name);
1831
- if (kind !== void 0) record._setRaw("kind", kind);
1832
- })
1833
- );
1834
- const updated = updates.map(
1835
- (e) => e.prepareUpdate((record) => {
1836
- record._setRaw("kind", kindByName.get(e.canonicalName));
1837
- })
1838
- );
1839
- if (created.length > 0 || updated.length > 0) {
1840
- await ctx.database.batch(...created, ...updated);
1841
- }
1842
- for (const e of existing) out.set(e.canonicalName, entityToStored(e));
1843
- for (const record of created) out.set(record.canonicalName, entityToStored(record));
1844
- return out;
1818
+ if (unique.length === 0) return { entities: out, operations: [] };
1819
+ const existing = await ctx.entityCollection.query(Q2.where("canonical_name", Q2.oneOf(unique))).fetch();
1820
+ const existingNames = new Set(existing.map((e) => e.canonicalName));
1821
+ const updates = existing.filter((e) => {
1822
+ const incoming = kindByName.get(e.canonicalName);
1823
+ return incoming !== void 0 && (e.kind === null || e.kind === void 0 || e.kind === "");
1845
1824
  });
1825
+ const missing = unique.filter((n) => !existingNames.has(n));
1826
+ const created = missing.map(
1827
+ (name) => ctx.entityCollection.prepareCreate((record) => {
1828
+ record._setRaw("canonical_name", name);
1829
+ const kind = kindByName.get(name);
1830
+ if (kind !== void 0) record._setRaw("kind", kind);
1831
+ })
1832
+ );
1833
+ const updated = updates.map(
1834
+ (e) => e.prepareUpdate((record) => {
1835
+ record._setRaw("kind", kindByName.get(e.canonicalName));
1836
+ })
1837
+ );
1838
+ for (const e of existing) out.set(e.canonicalName, entityToStored(e));
1839
+ for (const record of created) out.set(record.canonicalName, entityToStored(record));
1840
+ return { entities: out, operations: [...created, ...updated] };
1846
1841
  }
1847
1842
  async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
1848
1843
  if (entityInputs.length === 0) return [];
1849
1844
  const normalized = entityInputs.map((e) => typeof e === "string" ? { name: e } : e);
1850
- const byName = await upsertEntitiesOp(ctx, normalized);
1851
- const entities = Array.from(byName.values());
1852
- if (entities.length === 0) return [];
1853
1845
  const userId = ctx.userId;
1854
1846
  let skipped = false;
1847
+ let entities = [];
1855
1848
  await ctx.database.write(async () => {
1849
+ const { entities: byName, operations: entityOps } = await upsertEntitiesInWrite(
1850
+ ctx,
1851
+ normalized
1852
+ );
1853
+ entities = Array.from(byName.values());
1856
1854
  if (options?.unlessTopicsUserManaged && await autoLinkBlocked(ctx, memoryId)) {
1855
+ if (entityOps.length > 0) {
1856
+ await ctx.database.batch(...entityOps);
1857
+ }
1857
1858
  skipped = true;
1858
1859
  return;
1859
1860
  }
1861
+ if (entities.length === 0) return;
1860
1862
  const existingLinks = await ctx.memoryEntityCollection.query(Q2.where("memory_id", memoryId)).fetch();
1861
1863
  const existingEntityIds = new Set(existingLinks.map((l) => String(l.entityId)));
1862
1864
  const toCreate = entities.filter((e) => !existingEntityIds.has(e.uniqueId));
1863
- if (toCreate.length === 0) return;
1864
- const prepared = toCreate.map(
1865
+ if (entityOps.length === 0 && toCreate.length === 0) return;
1866
+ const linkOps = toCreate.map(
1865
1867
  (e) => ctx.memoryEntityCollection.prepareCreate((record) => {
1866
1868
  record._setRaw("memory_id", memoryId);
1867
1869
  record._setRaw("entity_id", e.uniqueId);
1868
1870
  if (userId !== void 0) record._setRaw("user_id", userId);
1869
1871
  })
1870
1872
  );
1871
- await ctx.database.batch(...prepared);
1873
+ await ctx.database.batch(...entityOps, ...linkOps);
1872
1874
  });
1873
1875
  return skipped ? [] : entities;
1874
1876
  }
@@ -1883,22 +1885,28 @@ async function autoLinkBlocked(ctx, memoryId) {
1883
1885
  }
1884
1886
  async function replaceMemoryEntitiesGuardedOp(ctx, memoryId, entityInputs) {
1885
1887
  const normalized = entityInputs.map((e) => typeof e === "string" ? { name: e } : e);
1886
- const byName = await upsertEntitiesOp(ctx, normalized);
1887
- const entities = Array.from(byName.values());
1888
1888
  const userId = ctx.userId;
1889
1889
  let skipped = false;
1890
+ let entities = [];
1890
1891
  await ctx.database.write(async () => {
1891
1892
  if (await autoLinkBlocked(ctx, memoryId)) {
1892
1893
  skipped = true;
1893
1894
  return;
1894
1895
  }
1896
+ const { entities: byName, operations: entityOps } = await upsertEntitiesInWrite(
1897
+ ctx,
1898
+ normalized
1899
+ );
1900
+ entities = Array.from(byName.values());
1895
1901
  const existingLinks = await ctx.memoryEntityCollection.query(Q2.where("memory_id", memoryId)).fetch();
1896
1902
  const keep = new Set(entities.map((e) => e.uniqueId));
1897
1903
  const existingEntityIds = new Set(existingLinks.map((l) => String(l.entityId)));
1898
1904
  const toCreate = entities.filter((e) => !existingEntityIds.has(e.uniqueId));
1899
1905
  const toDestroy = existingLinks.filter((l) => !keep.has(String(l.entityId)));
1900
- if (toCreate.length === 0 && toDestroy.length === 0) return;
1906
+ if (entityOps.length === 0 && toCreate.length === 0 && toDestroy.length === 0) return;
1907
+ const orphans = await findOrphanedEntities(ctx, memoryId, toDestroy);
1901
1908
  await ctx.database.batch(
1909
+ ...entityOps,
1902
1910
  ...toCreate.map(
1903
1911
  (e) => ctx.memoryEntityCollection.prepareCreate((record) => {
1904
1912
  record._setRaw("memory_id", memoryId);
@@ -1906,11 +1914,23 @@ async function replaceMemoryEntitiesGuardedOp(ctx, memoryId, entityInputs) {
1906
1914
  if (userId !== void 0) record._setRaw("user_id", userId);
1907
1915
  })
1908
1916
  ),
1909
- ...toDestroy.map((l) => l.prepareDestroyPermanently())
1917
+ ...toDestroy.map((l) => l.prepareDestroyPermanently()),
1918
+ ...orphans.map((e) => e.prepareDestroyPermanently())
1910
1919
  );
1911
1920
  });
1912
1921
  return skipped ? null : entities;
1913
1922
  }
1923
+ async function findOrphanedEntities(ctx, memoryId, toDestroy) {
1924
+ const candidateIds = [...new Set(toDestroy.map((l) => String(l.entityId)))];
1925
+ if (candidateIds.length === 0) return [];
1926
+ const links = await ctx.memoryEntityCollection.query(Q2.where("entity_id", Q2.oneOf(candidateIds))).fetch();
1927
+ const stillLinked = new Set(
1928
+ links.filter((l) => String(l.memoryId) !== memoryId).map((l) => String(l.entityId))
1929
+ );
1930
+ const orphanIds = candidateIds.filter((id) => !stillLinked.has(id));
1931
+ if (orphanIds.length === 0) return [];
1932
+ return await ctx.entityCollection.query(Q2.where("id", Q2.oneOf(orphanIds))).fetch();
1933
+ }
1914
1934
  async function unlinkMemoryEntitiesOp(ctx, memoryIds) {
1915
1935
  if (memoryIds.length === 0) return;
1916
1936
  const links = await ctx.memoryEntityCollection.query(Q2.where("memory_id", Q2.oneOf(Array.from(memoryIds)))).fetch();
@@ -2531,7 +2551,7 @@ async function getUnfiledVaultMemoriesOp(ctx) {
2531
2551
  (record) => vaultMemoryToStored(record, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner)
2532
2552
  );
2533
2553
  }
2534
- var TOPICS_EXTRACTION_VERSION = 2;
2554
+ var TOPICS_EXTRACTION_VERSION = 3;
2535
2555
  async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
2536
2556
  const entityCtx = ctx.entityCtx;
2537
2557
  if (!entityCtx) {
@@ -5363,7 +5383,7 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
5363
5383
  legacy.map((r) => ({ id: r.uniqueId, content: r.content, score: r.similarity }))
5364
5384
  );
5365
5385
  }
5366
- const { recall: recall2 } = await import("./recall-MUFOZB6P.mjs");
5386
+ const { recall: recall2 } = await import("./recall-JFZ3Z6EX.mjs");
5367
5387
  const result = await recall2(
5368
5388
  query,
5369
5389
  { vaultCtx, embeddingOptions, vaultCache: cache },
@@ -4030,7 +4030,7 @@ function entityToStored(e) {
4030
4030
  updatedAt: e.updatedAt
4031
4031
  };
4032
4032
  }
4033
- async function upsertEntitiesOp(ctx, entities) {
4033
+ async function upsertEntitiesInWrite(ctx, entities) {
4034
4034
  const kindByName = /* @__PURE__ */ new Map();
4035
4035
  for (const e of entities) {
4036
4036
  const name = normalizeEntityName(e.name);
@@ -4044,60 +4044,62 @@ async function upsertEntitiesOp(ctx, entities) {
4044
4044
  }
4045
4045
  const unique = Array.from(kindByName.keys());
4046
4046
  const out = /* @__PURE__ */ new Map();
4047
- if (unique.length === 0) return out;
4048
- return await ctx.database.write(async () => {
4049
- const existing = await ctx.entityCollection.query(import_watermelondb8.Q.where("canonical_name", import_watermelondb8.Q.oneOf(unique))).fetch();
4050
- const existingNames = new Set(existing.map((e) => e.canonicalName));
4051
- const updates = existing.filter((e) => {
4052
- const incoming = kindByName.get(e.canonicalName);
4053
- return incoming !== void 0 && (e.kind === null || e.kind === void 0 || e.kind === "");
4054
- });
4055
- const missing = unique.filter((n) => !existingNames.has(n));
4056
- const created = missing.map(
4057
- (name) => ctx.entityCollection.prepareCreate((record) => {
4058
- record._setRaw("canonical_name", name);
4059
- const kind = kindByName.get(name);
4060
- if (kind !== void 0) record._setRaw("kind", kind);
4061
- })
4062
- );
4063
- const updated = updates.map(
4064
- (e) => e.prepareUpdate((record) => {
4065
- record._setRaw("kind", kindByName.get(e.canonicalName));
4066
- })
4067
- );
4068
- if (created.length > 0 || updated.length > 0) {
4069
- await ctx.database.batch(...created, ...updated);
4070
- }
4071
- for (const e of existing) out.set(e.canonicalName, entityToStored(e));
4072
- for (const record of created) out.set(record.canonicalName, entityToStored(record));
4073
- return out;
4047
+ if (unique.length === 0) return { entities: out, operations: [] };
4048
+ const existing = await ctx.entityCollection.query(import_watermelondb8.Q.where("canonical_name", import_watermelondb8.Q.oneOf(unique))).fetch();
4049
+ const existingNames = new Set(existing.map((e) => e.canonicalName));
4050
+ const updates = existing.filter((e) => {
4051
+ const incoming = kindByName.get(e.canonicalName);
4052
+ return incoming !== void 0 && (e.kind === null || e.kind === void 0 || e.kind === "");
4074
4053
  });
4054
+ const missing = unique.filter((n) => !existingNames.has(n));
4055
+ const created = missing.map(
4056
+ (name) => ctx.entityCollection.prepareCreate((record) => {
4057
+ record._setRaw("canonical_name", name);
4058
+ const kind = kindByName.get(name);
4059
+ if (kind !== void 0) record._setRaw("kind", kind);
4060
+ })
4061
+ );
4062
+ const updated = updates.map(
4063
+ (e) => e.prepareUpdate((record) => {
4064
+ record._setRaw("kind", kindByName.get(e.canonicalName));
4065
+ })
4066
+ );
4067
+ for (const e of existing) out.set(e.canonicalName, entityToStored(e));
4068
+ for (const record of created) out.set(record.canonicalName, entityToStored(record));
4069
+ return { entities: out, operations: [...created, ...updated] };
4075
4070
  }
4076
4071
  async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
4077
4072
  if (entityInputs.length === 0) return [];
4078
4073
  const normalized = entityInputs.map((e) => typeof e === "string" ? { name: e } : e);
4079
- const byName = await upsertEntitiesOp(ctx, normalized);
4080
- const entities = Array.from(byName.values());
4081
- if (entities.length === 0) return [];
4082
4074
  const userId = ctx.userId;
4083
4075
  let skipped = false;
4076
+ let entities = [];
4084
4077
  await ctx.database.write(async () => {
4078
+ const { entities: byName, operations: entityOps } = await upsertEntitiesInWrite(
4079
+ ctx,
4080
+ normalized
4081
+ );
4082
+ entities = Array.from(byName.values());
4085
4083
  if (options?.unlessTopicsUserManaged && await autoLinkBlocked(ctx, memoryId)) {
4084
+ if (entityOps.length > 0) {
4085
+ await ctx.database.batch(...entityOps);
4086
+ }
4086
4087
  skipped = true;
4087
4088
  return;
4088
4089
  }
4090
+ if (entities.length === 0) return;
4089
4091
  const existingLinks = await ctx.memoryEntityCollection.query(import_watermelondb8.Q.where("memory_id", memoryId)).fetch();
4090
4092
  const existingEntityIds = new Set(existingLinks.map((l) => String(l.entityId)));
4091
4093
  const toCreate = entities.filter((e) => !existingEntityIds.has(e.uniqueId));
4092
- if (toCreate.length === 0) return;
4093
- const prepared = toCreate.map(
4094
+ if (entityOps.length === 0 && toCreate.length === 0) return;
4095
+ const linkOps = toCreate.map(
4094
4096
  (e) => ctx.memoryEntityCollection.prepareCreate((record) => {
4095
4097
  record._setRaw("memory_id", memoryId);
4096
4098
  record._setRaw("entity_id", e.uniqueId);
4097
4099
  if (userId !== void 0) record._setRaw("user_id", userId);
4098
4100
  })
4099
4101
  );
4100
- await ctx.database.batch(...prepared);
4102
+ await ctx.database.batch(...entityOps, ...linkOps);
4101
4103
  });
4102
4104
  return skipped ? [] : entities;
4103
4105
  }
@@ -4112,22 +4114,28 @@ async function autoLinkBlocked(ctx, memoryId) {
4112
4114
  }
4113
4115
  async function replaceMemoryEntitiesGuardedOp(ctx, memoryId, entityInputs) {
4114
4116
  const normalized = entityInputs.map((e) => typeof e === "string" ? { name: e } : e);
4115
- const byName = await upsertEntitiesOp(ctx, normalized);
4116
- const entities = Array.from(byName.values());
4117
4117
  const userId = ctx.userId;
4118
4118
  let skipped = false;
4119
+ let entities = [];
4119
4120
  await ctx.database.write(async () => {
4120
4121
  if (await autoLinkBlocked(ctx, memoryId)) {
4121
4122
  skipped = true;
4122
4123
  return;
4123
4124
  }
4125
+ const { entities: byName, operations: entityOps } = await upsertEntitiesInWrite(
4126
+ ctx,
4127
+ normalized
4128
+ );
4129
+ entities = Array.from(byName.values());
4124
4130
  const existingLinks = await ctx.memoryEntityCollection.query(import_watermelondb8.Q.where("memory_id", memoryId)).fetch();
4125
4131
  const keep = new Set(entities.map((e) => e.uniqueId));
4126
4132
  const existingEntityIds = new Set(existingLinks.map((l) => String(l.entityId)));
4127
4133
  const toCreate = entities.filter((e) => !existingEntityIds.has(e.uniqueId));
4128
4134
  const toDestroy = existingLinks.filter((l) => !keep.has(String(l.entityId)));
4129
- if (toCreate.length === 0 && toDestroy.length === 0) return;
4135
+ if (entityOps.length === 0 && toCreate.length === 0 && toDestroy.length === 0) return;
4136
+ const orphans = await findOrphanedEntities(ctx, memoryId, toDestroy);
4130
4137
  await ctx.database.batch(
4138
+ ...entityOps,
4131
4139
  ...toCreate.map(
4132
4140
  (e) => ctx.memoryEntityCollection.prepareCreate((record) => {
4133
4141
  record._setRaw("memory_id", memoryId);
@@ -4135,11 +4143,23 @@ async function replaceMemoryEntitiesGuardedOp(ctx, memoryId, entityInputs) {
4135
4143
  if (userId !== void 0) record._setRaw("user_id", userId);
4136
4144
  })
4137
4145
  ),
4138
- ...toDestroy.map((l) => l.prepareDestroyPermanently())
4146
+ ...toDestroy.map((l) => l.prepareDestroyPermanently()),
4147
+ ...orphans.map((e) => e.prepareDestroyPermanently())
4139
4148
  );
4140
4149
  });
4141
4150
  return skipped ? null : entities;
4142
4151
  }
4152
+ async function findOrphanedEntities(ctx, memoryId, toDestroy) {
4153
+ const candidateIds = [...new Set(toDestroy.map((l) => String(l.entityId)))];
4154
+ if (candidateIds.length === 0) return [];
4155
+ const links = await ctx.memoryEntityCollection.query(import_watermelondb8.Q.where("entity_id", import_watermelondb8.Q.oneOf(candidateIds))).fetch();
4156
+ const stillLinked = new Set(
4157
+ links.filter((l) => String(l.memoryId) !== memoryId).map((l) => String(l.entityId))
4158
+ );
4159
+ const orphanIds = candidateIds.filter((id) => !stillLinked.has(id));
4160
+ if (orphanIds.length === 0) return [];
4161
+ return await ctx.entityCollection.query(import_watermelondb8.Q.where("id", import_watermelondb8.Q.oneOf(orphanIds))).fetch();
4162
+ }
4143
4163
  async function unlinkMemoryEntitiesOp(ctx, memoryIds) {
4144
4164
  if (memoryIds.length === 0) return;
4145
4165
  const links = await ctx.memoryEntityCollection.query(import_watermelondb8.Q.where("memory_id", import_watermelondb8.Q.oneOf(Array.from(memoryIds)))).fetch();
@@ -4997,7 +5017,7 @@ var init_operations3 = __esm({
4997
5017
  init_operations2();
4998
5018
  init_encryption2();
4999
5019
  KNOWN_TRUST_TIERS = /* @__PURE__ */ new Set(["quarantined", "trusted"]);
5000
- TOPICS_EXTRACTION_VERSION = 2;
5020
+ TOPICS_EXTRACTION_VERSION = 3;
5001
5021
  }
5002
5022
  });
5003
5023
 
@@ -13744,6 +13764,162 @@ init_types3();
13744
13764
  init_logger();
13745
13765
  init_redactor();
13746
13766
 
13767
+ // src/lib/memory/entitySalience.ts
13768
+ var GENERIC_ENTITY_NAMES = /* @__PURE__ */ new Set([
13769
+ // Relative and absolute time
13770
+ "today",
13771
+ "tomorrow",
13772
+ "yesterday",
13773
+ "tonight",
13774
+ "morning",
13775
+ "afternoon",
13776
+ "evening",
13777
+ "night",
13778
+ "day",
13779
+ "date",
13780
+ "time",
13781
+ "week",
13782
+ "weekend",
13783
+ "weekday",
13784
+ "month",
13785
+ "year",
13786
+ "monday",
13787
+ "tuesday",
13788
+ "wednesday",
13789
+ "thursday",
13790
+ "friday",
13791
+ "saturday",
13792
+ "sunday",
13793
+ "january",
13794
+ "february",
13795
+ "march",
13796
+ "april",
13797
+ "may",
13798
+ "june",
13799
+ "july",
13800
+ "august",
13801
+ "september",
13802
+ "october",
13803
+ "november",
13804
+ "december",
13805
+ // Generic containers and venues — the named ones ("Blue Bottle on Valencia",
13806
+ // "Stanford") are unaffected because they are not bare tokens.
13807
+ "home",
13808
+ "house",
13809
+ "apartment",
13810
+ "room",
13811
+ "office",
13812
+ "work",
13813
+ "workplace",
13814
+ "school",
13815
+ "gym",
13816
+ "store",
13817
+ "shop",
13818
+ "hospital",
13819
+ "airport",
13820
+ "hotel",
13821
+ "city",
13822
+ "town",
13823
+ "country",
13824
+ "place",
13825
+ // Generic calendar entries — "Chicago Marathon" survives, "meeting" doesn't.
13826
+ "meeting",
13827
+ "call",
13828
+ "appointment",
13829
+ "reminder",
13830
+ "event",
13831
+ "birthday",
13832
+ "breakfast",
13833
+ "lunch",
13834
+ "dinner",
13835
+ "brunch",
13836
+ "vacation",
13837
+ "holiday",
13838
+ "trip",
13839
+ // Plurals are listed explicitly rather than stemmed: naive `s`-stripping
13840
+ // would fold real titles into generics ("The Times" → "time"). Only plurals
13841
+ // that are junk as topics in their own right belong here — note the absence
13842
+ // of "times", "places", and anything else that reads as a name.
13843
+ "days",
13844
+ "weeks",
13845
+ "weekends",
13846
+ "months",
13847
+ "years",
13848
+ "rooms",
13849
+ "offices",
13850
+ "houses",
13851
+ "apartments",
13852
+ "stores",
13853
+ "shops",
13854
+ "hotels",
13855
+ "meetings",
13856
+ "calls",
13857
+ "appointments",
13858
+ "reminders",
13859
+ "events",
13860
+ "birthdays",
13861
+ "trips",
13862
+ "vacations",
13863
+ "holidays",
13864
+ // Meta / filler the models occasionally echo back as an entity
13865
+ "user",
13866
+ "assistant",
13867
+ "memory",
13868
+ "memories",
13869
+ "note",
13870
+ "notes",
13871
+ "thing",
13872
+ "things",
13873
+ "stuff",
13874
+ "something",
13875
+ "someone",
13876
+ "anything",
13877
+ "everything",
13878
+ "none",
13879
+ "other",
13880
+ "misc",
13881
+ "general",
13882
+ "unknown"
13883
+ ]);
13884
+ function isGenericEntityName(name) {
13885
+ const tokens = name.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((token) => token.length > 0);
13886
+ if (tokens.length === 0) return true;
13887
+ const distinctive = tokens.filter(
13888
+ (token) => !GENERIC_ENTITY_NAMES.has(token) && !ARTICLES.has(token) && !WEAK_MODIFIERS.has(token)
13889
+ );
13890
+ if (distinctive.length > 0) return false;
13891
+ const generic = tokens.filter((token) => GENERIC_ENTITY_NAMES.has(token));
13892
+ if (generic.length === 0) return true;
13893
+ const titleShaped = generic.length === 1 && tokens.some((token) => ARTICLES.has(token)) && !tokens.some((token) => WEAK_MODIFIERS.has(token));
13894
+ return !titleShaped;
13895
+ }
13896
+ var ARTICLES = /* @__PURE__ */ new Set(["the", "a", "an"]);
13897
+ var WEAK_MODIFIERS = /* @__PURE__ */ new Set([
13898
+ "my",
13899
+ "our",
13900
+ "their",
13901
+ "his",
13902
+ "her",
13903
+ "its",
13904
+ "this",
13905
+ "that",
13906
+ "next",
13907
+ "last",
13908
+ "upcoming",
13909
+ "every",
13910
+ "at",
13911
+ "in",
13912
+ "on",
13913
+ "of",
13914
+ "for",
13915
+ "with",
13916
+ "from",
13917
+ "to",
13918
+ "by",
13919
+ "and",
13920
+ "or"
13921
+ ]);
13922
+
13747
13923
  // src/lib/memory/injectionClassifier.ts
13748
13924
  init_logger();
13749
13925
  init_redactor();
@@ -14684,13 +14860,13 @@ function parseEntities(raw) {
14684
14860
  for (const e of raw) {
14685
14861
  if (typeof e === "string") {
14686
14862
  const name2 = e.trim();
14687
- if (name2.length > 0) out.push({ name: name2 });
14863
+ if (name2.length > 0 && !isGenericEntityName(name2)) out.push({ name: name2 });
14688
14864
  continue;
14689
14865
  }
14690
14866
  if (typeof e !== "object" || e === null) continue;
14691
14867
  const obj = e;
14692
14868
  const name = typeof obj.name === "string" ? obj.name.trim() : "";
14693
- if (name.length === 0) continue;
14869
+ if (name.length === 0 || isGenericEntityName(name)) continue;
14694
14870
  const kind = typeof obj.kind === "string" && ENTITY_KINDS.includes(obj.kind) ? obj.kind : void 0;
14695
14871
  out.push(kind !== void 0 ? { name, kind } : { name });
14696
14872
  }
@@ -16032,7 +16208,11 @@ var SYSTEM_PROMPT6 = `You assign topics to saved memories for a personal memory
16032
16208
 
16033
16209
  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.
16034
16210
 
16035
- Include only NAMED entities, skip generic/common nouns. ${ENTITY_KIND_GUIDELINES}
16211
+ 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:
16212
+ - 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.
16213
+ - KEEP the specific thing instead, when the memory names one: "Blue Bottle on Valencia" over "cafe", "Chicago Marathon" over "event", "Hollowpoint Labs" over "work".
16214
+ - Prefer 0 entities over a weak one. A memory whose only candidate is a generic noun has NO entities.
16215
+ ${ENTITY_KIND_GUIDELINES}
16036
16216
 
16037
16217
  Each memory below is written as "<id>: <text>". Output strict JSON: {"memories": [{"id": string, "entities": [{"name": string, "kind": string}]}]}
16038
16218
  - exactly one element per input memory; copy its id EXACTLY as written (the token before the first ": "), with no brackets, quotes, or extra characters
@@ -16044,7 +16224,7 @@ async function extractEntitiesForMemories(memories, options) {
16044
16224
  const redactor = resolvePiiRedactor(options.piiRedaction);
16045
16225
  const vocabulary = (options.existingEntityNames ?? []).filter((n) => n.trim().length > 0).slice(0, MAX_VOCABULARY_NAMES).map((n) => redactor ? redactor.redactText(n).text : n);
16046
16226
  const vocabularyNote = vocabulary.length > 0 ? `The user's existing topics: ${vocabulary.join(", ")}.
16047
- When a memory refers to one of these, reuse the EXACT existing name (same spelling and casing) instead of a variant.
16227
+ 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".
16048
16228
 
16049
16229
  ` : "";
16050
16230
  for (let i = 0; i < memories.length; i += TOPIC_EXTRACTION_BATCH_SIZE) {
@@ -5879,7 +5879,7 @@ declare function getUnfiledVaultMemoriesOp(ctx: VaultMemoryOperationsContext): P
5879
5879
  * by the next sweep, so topic-quality improvements propagate across the existing
5880
5880
  * vault. The worker's `limit` drains that re-extraction across sweeps.
5881
5881
  */
5882
- declare const TOPICS_EXTRACTION_VERSION = 2;
5882
+ declare const TOPICS_EXTRACTION_VERSION = 3;
5883
5883
  /**
5884
5884
  * Result of {@link getMemoriesNeedingTopicExtractionOp}: which memories the
5885
5885
  * background topic worker should run LLM entity extraction on, and which it
@@ -5879,7 +5879,7 @@ declare function getUnfiledVaultMemoriesOp(ctx: VaultMemoryOperationsContext): P
5879
5879
  * by the next sweep, so topic-quality improvements propagate across the existing
5880
5880
  * vault. The worker's `limit` drains that re-extraction across sweeps.
5881
5881
  */
5882
- declare const TOPICS_EXTRACTION_VERSION = 2;
5882
+ declare const TOPICS_EXTRACTION_VERSION = 3;
5883
5883
  /**
5884
5884
  * Result of {@link getMemoriesNeedingTopicExtractionOp}: which memories the
5885
5885
  * background topic worker should run LLM entity extraction on, and which it