@anuma/sdk 3.1.3 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2036,6 +2036,7 @@ function vaultMemoryToStoredRaw(memory) {
2036
2036
  topicsExtractedVersion: memory.topicsExtractedVersion ?? null,
2037
2037
  supersededBy: memory.supersededBy ?? null,
2038
2038
  supersededAt: memory.supersededAt ?? null,
2039
+ lastObservedAt: memory.lastObservedAt ?? null,
2039
2040
  createdAt: memory.createdAt,
2040
2041
  updatedAt: memory.updatedAt,
2041
2042
  isDeleted: memory.isDeleted
@@ -2276,6 +2277,7 @@ function vaultMemoryRawToStoredRaw(raw) {
2276
2277
  topicsExtractedVersion: raw.topics_extracted_version ?? null,
2277
2278
  supersededBy: raw.superseded_by ?? null,
2278
2279
  supersededAt: raw.superseded_at ?? null,
2280
+ lastObservedAt: raw.last_observed_at ?? null,
2279
2281
  createdAt: new Date(raw.created_at),
2280
2282
  updatedAt: new Date(raw.updated_at),
2281
2283
  isDeleted: raw.is_deleted === true || raw.is_deleted === 1
@@ -2364,6 +2366,9 @@ async function updateVaultMemoryOp(ctx, id, opts) {
2364
2366
  if (opts.topicsUserManaged !== void 0) {
2365
2367
  r._setRaw("topics_user_managed", opts.topicsUserManaged);
2366
2368
  }
2369
+ if (opts.lastObservedAt !== void 0) {
2370
+ r._setRaw("last_observed_at", opts.lastObservedAt);
2371
+ }
2367
2372
  if (opts.preserveUpdatedAt) {
2368
2373
  r._setRaw("updated_at", originalUpdatedAt);
2369
2374
  }
@@ -4322,7 +4327,7 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
4322
4327
  legacy.map((r) => ({ id: r.uniqueId, content: r.content, score: r.similarity }))
4323
4328
  );
4324
4329
  }
4325
- const { recall: recall2 } = await import("./recall-WPKWZM5N.mjs");
4330
+ const { recall: recall2 } = await import("./recall-3YPU4PS2.mjs");
4326
4331
  const result = await recall2(
4327
4332
  query,
4328
4333
  { vaultCtx, embeddingOptions, vaultCache: cache },
@@ -3374,6 +3374,7 @@ function vaultMemoryToStoredRaw(memory) {
3374
3374
  topicsExtractedVersion: memory.topicsExtractedVersion ?? null,
3375
3375
  supersededBy: memory.supersededBy ?? null,
3376
3376
  supersededAt: memory.supersededAt ?? null,
3377
+ lastObservedAt: memory.lastObservedAt ?? null,
3377
3378
  createdAt: memory.createdAt,
3378
3379
  updatedAt: memory.updatedAt,
3379
3380
  isDeleted: memory.isDeleted
@@ -3614,6 +3615,7 @@ function vaultMemoryRawToStoredRaw(raw) {
3614
3615
  topicsExtractedVersion: raw.topics_extracted_version ?? null,
3615
3616
  supersededBy: raw.superseded_by ?? null,
3616
3617
  supersededAt: raw.superseded_at ?? null,
3618
+ lastObservedAt: raw.last_observed_at ?? null,
3617
3619
  createdAt: new Date(raw.created_at),
3618
3620
  updatedAt: new Date(raw.updated_at),
3619
3621
  isDeleted: raw.is_deleted === true || raw.is_deleted === 1
@@ -3702,6 +3704,9 @@ async function updateVaultMemoryOp(ctx, id, opts) {
3702
3704
  if (opts.topicsUserManaged !== void 0) {
3703
3705
  r._setRaw("topics_user_managed", opts.topicsUserManaged);
3704
3706
  }
3707
+ if (opts.lastObservedAt !== void 0) {
3708
+ r._setRaw("last_observed_at", opts.lastObservedAt);
3709
+ }
3705
3710
  if (opts.preserveUpdatedAt) {
3706
3711
  r._setRaw("updated_at", originalUpdatedAt);
3707
3712
  }
@@ -6270,12 +6275,14 @@ __export(index_exports, {
6270
6275
  DEFAULT_CHUNK_OVERLAP: () => DEFAULT_CHUNK_OVERLAP,
6271
6276
  DEFAULT_CHUNK_SIZE: () => DEFAULT_CHUNK_SIZE,
6272
6277
  DEFAULT_MIN_CHUNK_SIZE: () => DEFAULT_MIN_CHUNK_SIZE,
6278
+ DEFAULT_PROFILE_FACETS: () => DEFAULT_PROFILE_FACETS,
6273
6279
  DEFAULT_VAULT_CACHE_SIZE: () => DEFAULT_VAULT_CACHE_SIZE,
6274
6280
  DatabaseManager: () => DatabaseManager,
6275
6281
  INFERENCE_ID_HEADER: () => INFERENCE_ID_HEADER,
6276
6282
  LoggerProvider: () => LoggerProvider,
6277
6283
  NOTION_OAUTH_CONFIG: () => NOTION_OAUTH_CONFIG,
6278
6284
  PII_PATTERNS: () => PII_PATTERNS,
6285
+ PROFILE_DOC_VERSION: () => PROFILE_DOC_VERSION,
6279
6286
  PiiRedactor: () => PiiRedactor,
6280
6287
  QueueManager: () => QueueManager,
6281
6288
  RECALL_MAX_LIMIT: () => RECALL_MAX_LIMIT,
@@ -6390,6 +6397,7 @@ __export(index_exports, {
6390
6397
  streamCancelPath: () => streamCancelPath,
6391
6398
  streamReplayPath: () => streamReplayPath,
6392
6399
  supersedeVaultMemoryOp: () => supersedeVaultMemoryOp,
6400
+ synthesizeProfile: () => synthesizeProfile,
6393
6401
  updateVaultFolderContextOp: () => updateVaultFolderContextOp,
6394
6402
  updateVaultFolderOp: () => updateVaultFolderOp,
6395
6403
  updateVaultMemoryEmbeddingOp: () => updateVaultMemoryEmbeddingOp,
@@ -12733,6 +12741,9 @@ __decorateClass([
12733
12741
  __decorateClass([
12734
12742
  (0, import_decorators3.field)("topics_extracted_version")
12735
12743
  ], VaultMemory.prototype, "topicsExtractedVersion", 2);
12744
+ __decorateClass([
12745
+ (0, import_decorators3.field)("last_observed_at")
12746
+ ], VaultMemory.prototype, "lastObservedAt", 2);
12736
12747
  __decorateClass([
12737
12748
  import_decorators3.readonly,
12738
12749
  (0, import_decorators3.date)("created_at")
@@ -13155,6 +13166,8 @@ async function retain(content, ctx, options = {}) {
13155
13166
  proofCountIncrement: 1,
13156
13167
  sourceChunkIds: mergedSourceIds,
13157
13168
  preserveUpdatedAt: true,
13169
+ // C3: record the re-observation without touching updated_at.
13170
+ lastObservedAt: Date.now(),
13158
13171
  ...eventTimeUpdate && { eventTime: eventTimeUpdate }
13159
13172
  });
13160
13173
  if (updated) {
@@ -13305,6 +13318,8 @@ async function tryConsolidate(trimmed, ctx, options) {
13305
13318
  proofCountIncrement: 1,
13306
13319
  sourceChunkIds: mergedSourceIds,
13307
13320
  preserveUpdatedAt: true,
13321
+ // C3: record the re-observation without touching updated_at.
13322
+ lastObservedAt: Date.now(),
13308
13323
  ...eventTimeUpdate && { eventTime: eventTimeUpdate }
13309
13324
  });
13310
13325
  if (!updated) {
@@ -13341,6 +13356,8 @@ async function tryConsolidate(trimmed, ctx, options) {
13341
13356
  // one. Preserving updated_at keeps the recency multiplier honest
13342
13357
  // and matches the merge/noop paths above.
13343
13358
  preserveUpdatedAt: true,
13359
+ // C3: record the re-observation without touching updated_at.
13360
+ lastObservedAt: Date.now(),
13344
13361
  ...eventTimeUpdate && { eventTime: eventTimeUpdate }
13345
13362
  });
13346
13363
  if (!updated) {
@@ -14160,6 +14177,316 @@ function parseAnswer(body, base, parseSchema) {
14160
14177
  // src/lib/memory/index.ts
14161
14178
  init_reranker();
14162
14179
 
14180
+ // src/lib/memory/synthesizeProfile.ts
14181
+ init_operations3();
14182
+ init_logger();
14183
+ init_constants();
14184
+ init_embeddings();
14185
+ init_vector();
14186
+ var DEFAULT_SYNTHESIS_MODEL = "inclusionai/ling-2.6-flash";
14187
+ var DEFAULT_FACET_RECALL_LIMIT = 20;
14188
+ var DEFAULT_FACET_MAX_TOKENS = 512;
14189
+ var DEFAULT_SCOPES = ["private"];
14190
+ var NEW_FACT_ATTRIBUTION_MIN_SCORE = 0.1;
14191
+ var PROFILE_DOC_VERSION = 1;
14192
+ var DEFAULT_PROFILE_FACETS = [
14193
+ {
14194
+ key: "bio",
14195
+ label: "Bio",
14196
+ query: "Who is this person? Their background, personality, values, and what defines them.",
14197
+ guidance: "Write a 1-2 sentence bio (max ~40 words) that captures what makes this person distinctive \u2014 their character, values, or a defining thread across the memories. Be specific and grounded; ban generic dating clich\xE9s ('loves to laugh', 'foodie', 'work hard play hard', 'living life to the fullest')."
14198
+ },
14199
+ {
14200
+ key: "interests",
14201
+ label: "Interests",
14202
+ query: "What are this person's hobbies, passions, pastimes, and interests?",
14203
+ guidance: "Return 3-6 specific interests as a comma-separated list (e.g. 'trail running, film photography, Thai cooking'). Prefer concrete activities the memories actually show over broad categories ('music', 'travel'). No sentence, just the list."
14204
+ },
14205
+ {
14206
+ key: "work_role",
14207
+ label: "Work & Role",
14208
+ query: "What does this person do for work \u2014 their profession, role, industry, or studies?",
14209
+ guidance: "State the person's current role/profession and field in one short line (e.g. 'Backend engineer at a fintech startup'). Use only what the memories support; if unclear or absent, return hasEvidence=false rather than guessing a title."
14210
+ },
14211
+ {
14212
+ key: "location_context",
14213
+ label: "Location",
14214
+ query: "Where does this person live, spend time, or come from?",
14215
+ guidance: "Summarize where the person is based or spends time at neighborhood-or-city granularity in one short line (e.g. 'Based in the Mission, SF'). PRIVACY: never emit a precise address, building, or workplace location \u2014 coarse-grain to the city/neighborhood."
14216
+ },
14217
+ {
14218
+ key: "communication_style",
14219
+ label: "Communication Style",
14220
+ query: "How does this person communicate, express themselves, and interact with others?",
14221
+ guidance: "Describe the person's communication and social style in one line using 2-4 concrete adjectives grounded in the memories (e.g. 'Direct, dry-humored, and a careful listener'). Avoid empty praise."
14222
+ },
14223
+ {
14224
+ key: "recent_activity",
14225
+ label: "Recently",
14226
+ query: "What has this person been doing, focused on, or working on recently?",
14227
+ guidance: "Summarize what the person has been up to lately in one line, favoring the most recently reinforced facts. Frame it as current/ongoing (e.g. 'Lately: training for a first half-marathon and learning Portuguese'). Omit if nothing recent stands out."
14228
+ }
14229
+ ];
14230
+ var FACET_RESPONSE_SCHEMA = {
14231
+ type: "object",
14232
+ properties: {
14233
+ summary: {
14234
+ type: "string",
14235
+ description: "The synthesized section prose. Empty string if the memories don't cover it."
14236
+ },
14237
+ hasEvidence: {
14238
+ type: "boolean",
14239
+ description: "False when the supplied memories don't support any claim for this facet."
14240
+ }
14241
+ },
14242
+ required: ["summary", "hasEvidence"],
14243
+ additionalProperties: false
14244
+ };
14245
+ async function synthesizeProfile(ctx, options = {}) {
14246
+ if (!ctx.vaultCtx) {
14247
+ throw new Error("synthesizeProfile requires ctx.vaultCtx (vault-backed facts).");
14248
+ }
14249
+ if (!ctx.vaultCache) {
14250
+ throw new Error("synthesizeProfile requires ctx.vaultCache (semantic fact recall).");
14251
+ }
14252
+ const facets = options.facets ?? DEFAULT_PROFILE_FACETS;
14253
+ const scopes = options.scopes ?? DEFAULT_SCOPES;
14254
+ const config = {
14255
+ facetKeys: facets.map((f) => f.key).sort(),
14256
+ facetsSignature: facetsSignature(facets),
14257
+ scopes: [...scopes].sort(),
14258
+ redacted: options.redactor !== void 0
14259
+ };
14260
+ const previous = options.previous && options.previous.version === PROFILE_DOC_VERSION && configMatches(options.previous.config, config) ? options.previous : void 0;
14261
+ const memories = await getAllVaultMemoriesOp(ctx.vaultCtx, {
14262
+ // Scope the snapshot to the same scopes synthesis recalls from, so the
14263
+ // watermark, delta, and new-fact attribution all track only facts that can
14264
+ // actually appear in the profile — an out-of-scope change/new-fact must not
14265
+ // trigger a regeneration that scoped recall would never reflect.
14266
+ scopes,
14267
+ includeDeleted: true,
14268
+ includeSuperseded: true
14269
+ });
14270
+ const watermark = computeVaultWatermark(memories);
14271
+ const presentIds = new Set(memories.map((m) => m.uniqueId));
14272
+ const hasStaleSections = previous?.sections.some((s) => s.stale);
14273
+ const citesMissingFact = previous?.sections.some(
14274
+ (s) => s.sourceMemoryIds.some((id) => !presentIds.has(id))
14275
+ );
14276
+ if (previous && previous.vaultWatermark === watermark && !hasStaleSections && !citesMissingFact) {
14277
+ const sameOrder = previous.sections.length === facets.length && facets.every((f, i) => previous.sections[i]?.key === f.key);
14278
+ if (sameOrder) return previous;
14279
+ return {
14280
+ ...previous,
14281
+ sections: facets.map((f) => previous.sections.find((s) => s.key === f.key))
14282
+ };
14283
+ }
14284
+ const staleKeys = await computeStaleFacetKeys(ctx, memories, facets, previous, watermark);
14285
+ const settled = await Promise.allSettled(
14286
+ facets.map(async (facet) => {
14287
+ const prior = previous?.sections.find((s) => s.key === facet.key);
14288
+ if (prior && !staleKeys.has(facet.key)) {
14289
+ return prior;
14290
+ }
14291
+ return synthesizeFacet(facet, ctx, options, prior);
14292
+ })
14293
+ );
14294
+ const sections = settled.map((r, i) => {
14295
+ if (r.status === "fulfilled") return r.value;
14296
+ const facet = facets[i];
14297
+ getLogger().warn(
14298
+ "[memory/synthesizeProfile] facet synthesis rejected; using fallback section",
14299
+ {
14300
+ facet: facet.key,
14301
+ error: r.reason instanceof Error ? r.reason.message : String(r.reason)
14302
+ }
14303
+ );
14304
+ return fallbackSection(
14305
+ facet,
14306
+ previous?.sections.find((s) => s.key === facet.key)
14307
+ );
14308
+ });
14309
+ return {
14310
+ version: PROFILE_DOC_VERSION,
14311
+ sections,
14312
+ vaultWatermark: watermark,
14313
+ config,
14314
+ generatedAt: Date.now()
14315
+ };
14316
+ }
14317
+ function configMatches(a, b) {
14318
+ if (!a) return false;
14319
+ return a.redacted === b.redacted && a.facetsSignature === b.facetsSignature && a.scopes.length === b.scopes.length && a.scopes.every((s, i) => s === b.scopes[i]);
14320
+ }
14321
+ function facetsSignature(facets) {
14322
+ return facets.map((f) => JSON.stringify([f.key, f.label, f.query, f.guidance])).sort().join("\n");
14323
+ }
14324
+ function changeTime(m) {
14325
+ return Math.max(m.updatedAt.getTime(), m.supersededAt ?? 0, m.lastObservedAt ?? 0);
14326
+ }
14327
+ function computeVaultWatermark(memories) {
14328
+ let max = 0;
14329
+ for (const m of memories) {
14330
+ const t = changeTime(m);
14331
+ if (t > max) max = t;
14332
+ }
14333
+ return max;
14334
+ }
14335
+ async function computeStaleFacetKeys(ctx, memories, facets, previous, watermark) {
14336
+ const allKeys = new Set(facets.map((f) => f.key));
14337
+ if (previous && watermark < previous.vaultWatermark) return allKeys;
14338
+ if (!previous) return allKeys;
14339
+ const changed = memories.filter((m) => changeTime(m) > previous.vaultWatermark);
14340
+ const changedIds = new Set(changed.map((m) => m.uniqueId));
14341
+ const presentIds = new Set(memories.map((m) => m.uniqueId));
14342
+ const stale = /* @__PURE__ */ new Set();
14343
+ for (const section of previous.sections) {
14344
+ if (section.sourceMemoryIds.some((id) => changedIds.has(id))) {
14345
+ stale.add(section.key);
14346
+ }
14347
+ if (section.sourceMemoryIds.some((id) => !presentIds.has(id))) {
14348
+ stale.add(section.key);
14349
+ }
14350
+ if (section.stale) {
14351
+ stale.add(section.key);
14352
+ }
14353
+ }
14354
+ for (const facet of facets) {
14355
+ if (!previous.sections.some((s) => s.key === facet.key)) stale.add(facet.key);
14356
+ }
14357
+ const citedIds = new Set(previous.sections.flatMap((s) => s.sourceMemoryIds));
14358
+ const toAttribute = changed.filter(
14359
+ (m) => !m.isDeleted && !m.supersededBy && !citedIds.has(m.uniqueId)
14360
+ );
14361
+ if (toAttribute.length > 0) {
14362
+ const attributed = await attributeFacts(ctx, toAttribute, facets);
14363
+ if (attributed === null) return allKeys;
14364
+ for (const k of attributed) stale.add(k);
14365
+ }
14366
+ return stale;
14367
+ }
14368
+ async function attributeFacts(ctx, candidates, facets) {
14369
+ const model = ctx.embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
14370
+ const factVectors = [];
14371
+ for (const f of candidates) {
14372
+ if (!f.embedding || f.embeddingModel && f.embeddingModel !== model) {
14373
+ getLogger().warn(
14374
+ "[memory/synthesizeProfile] fact lacks a usable current-model embedding; regenerating all facets",
14375
+ { memoryId: f.uniqueId, hasEmbedding: !!f.embedding, embeddingModel: f.embeddingModel }
14376
+ );
14377
+ return null;
14378
+ }
14379
+ let vec;
14380
+ try {
14381
+ vec = JSON.parse(f.embedding);
14382
+ } catch {
14383
+ getLogger().warn(
14384
+ "[memory/synthesizeProfile] fact embedding failed to parse; regenerating all facets",
14385
+ { memoryId: f.uniqueId }
14386
+ );
14387
+ return null;
14388
+ }
14389
+ if (!Array.isArray(vec) || vec.length === 0) return null;
14390
+ factVectors.push(vec);
14391
+ }
14392
+ let queryVectors;
14393
+ try {
14394
+ queryVectors = await generateEmbeddings(
14395
+ facets.map((f) => f.query),
14396
+ ctx.embeddingOptions
14397
+ );
14398
+ } catch (err) {
14399
+ getLogger().warn(
14400
+ "[memory/synthesizeProfile] facet-query embedding failed; regenerating all facets",
14401
+ { error: err instanceof Error ? err.message : String(err) }
14402
+ );
14403
+ return null;
14404
+ }
14405
+ const keys = /* @__PURE__ */ new Set();
14406
+ for (const fv of factVectors) {
14407
+ let matchedAny = false;
14408
+ for (let i = 0; i < facets.length; i++) {
14409
+ if (cosineSimilarity(fv, queryVectors[i]) >= NEW_FACT_ATTRIBUTION_MIN_SCORE) {
14410
+ keys.add(facets[i].key);
14411
+ matchedAny = true;
14412
+ }
14413
+ }
14414
+ if (!matchedAny) return null;
14415
+ }
14416
+ return keys;
14417
+ }
14418
+ async function synthesizeFacet(facet, ctx, options, prior) {
14419
+ const result = await reflect(facet.query, ctx, {
14420
+ apiKey: options.apiKey,
14421
+ getToken: options.getToken,
14422
+ llmModel: options.llmModel ?? DEFAULT_SYNTHESIS_MODEL,
14423
+ baseUrl: options.baseUrl,
14424
+ fetchFn: options.fetchFn,
14425
+ scopes: options.scopes ?? DEFAULT_SCOPES,
14426
+ limit: options.limit ?? DEFAULT_FACET_RECALL_LIMIT,
14427
+ types: ["fact"],
14428
+ maxTokens: DEFAULT_FACET_MAX_TOKENS,
14429
+ systemPrompt: buildFacetSystemPrompt(facet),
14430
+ responseSchema: FACET_RESPONSE_SCHEMA
14431
+ });
14432
+ const noEvidence = result.basedOn.memoryIds.length === 0;
14433
+ const { text: text12, legitimateEmpty } = extractFacetText(result.structuredOutput);
14434
+ if (!text12 && !legitimateEmpty && !noEvidence) {
14435
+ getLogger().warn(
14436
+ "[memory/synthesizeProfile] facet synthesis returned degraded-empty; keeping prior section",
14437
+ { facet: facet.key, recalledCount: result.basedOn.memoryIds.length }
14438
+ );
14439
+ return fallbackSection(facet, prior);
14440
+ }
14441
+ const section = {
14442
+ key: facet.key,
14443
+ label: facet.label,
14444
+ text: text12,
14445
+ sourceMemoryIds: result.basedOn.memoryIds,
14446
+ generatedAt: Date.now()
14447
+ };
14448
+ if (options.redactor && text12) {
14449
+ const redacted = await options.redactor.redactTextAsync(text12);
14450
+ section.text = redacted.text;
14451
+ }
14452
+ return section;
14453
+ }
14454
+ function fallbackSection(facet, prior) {
14455
+ if (prior && prior.text) {
14456
+ return { ...prior, stale: true };
14457
+ }
14458
+ return {
14459
+ key: facet.key,
14460
+ label: facet.label,
14461
+ text: "",
14462
+ sourceMemoryIds: [],
14463
+ generatedAt: Date.now(),
14464
+ stale: true
14465
+ };
14466
+ }
14467
+ function buildFacetSystemPrompt(facet) {
14468
+ return `You are writing the "${facet.label}" section of a person's shareable profile, using their private memories (supplied as evidence) as the only source of truth.
14469
+
14470
+ Task for this section:
14471
+ ${facet.guidance}
14472
+
14473
+ Rules:
14474
+ - Ground every claim in the supplied memories \u2014 never invent, infer beyond, or embellish what they support.
14475
+ - If the memories don't cover this section, return an empty summary with hasEvidence=false. Do not pad or guess.
14476
+ - Write in third person about the person, in a natural voice suitable for a public profile (no "I"/"you", no name repetition).
14477
+ - Be concise and specific; no preamble, hedging, or meta-commentary.
14478
+ - Respond as JSON: { "summary": <the section text, or "">, "hasEvidence": <true|false> }.`;
14479
+ }
14480
+ function extractFacetText(structured) {
14481
+ if (structured && typeof structured === "object") {
14482
+ const obj = structured;
14483
+ if (obj.hasEvidence === false) return { text: "", legitimateEmpty: true };
14484
+ if (typeof obj.summary === "string")
14485
+ return { text: obj.summary.trim(), legitimateEmpty: false };
14486
+ }
14487
+ return { text: "", legitimateEmpty: false };
14488
+ }
14489
+
14163
14490
  // src/lib/memory/topicExtract.ts
14164
14491
  init_operations2();
14165
14492
  init_operations3();
@@ -17507,7 +17834,7 @@ __decorateClass([
17507
17834
  ], VaultFolder.prototype, "context", 2);
17508
17835
 
17509
17836
  // src/lib/db/schema.ts
17510
- var SDK_SCHEMA_VERSION = 38;
17837
+ var SDK_SCHEMA_VERSION = 39;
17511
17838
  var sdkSchema = (0, import_watermelondb19.appSchema)({
17512
17839
  version: SDK_SCHEMA_VERSION,
17513
17840
  tables: [
@@ -17645,7 +17972,14 @@ var sdkSchema = (0, import_watermelondb19.appSchema)({
17645
17972
  // The extraction-logic version this memory was last stamped under. Null
17646
17973
  // (pre-v38) is treated as version 0, so a bump of TOPICS_EXTRACTION_VERSION
17647
17974
  // re-extracts stale rows. See getMemoriesNeedingTopicExtractionOp.
17648
- { name: "topics_extracted_version", type: "number", isOptional: true }
17975
+ { name: "topics_extracted_version", type: "number", isOptional: true },
17976
+ // Re-observation watermark (C3). Unix ms of the last time retain() merged
17977
+ // a duplicate observation into this fact (proof_count++). Distinct from
17978
+ // updated_at, which merges deliberately preserve (preserveUpdatedAt) so a
17979
+ // re-observation doesn't reorder the vault by edit time. Null = never
17980
+ // re-observed since the column was added. Indexed so recency-weighted
17981
+ // synthesis can filter/sort on it cheaply.
17982
+ { name: "last_observed_at", type: "number", isOptional: true, isIndexed: true }
17649
17983
  ]
17650
17984
  }),
17651
17985
  // Entity table — canonical names extracted from auto-extraction (W5).
@@ -18284,6 +18618,20 @@ var sdkMigrations = (0, import_migrations2.schemaMigrations)({
18284
18618
  columns: [{ name: "topics_extracted_version", type: "number", isOptional: true }]
18285
18619
  })
18286
18620
  ]
18621
+ },
18622
+ // v38 -> v39: re-observation watermark on memory_vault (C3). Existing rows
18623
+ // keep NULL (= never re-observed since the column was added); synthesis
18624
+ // treats NULL as "fall back to updated_at" so no backfill is needed.
18625
+ {
18626
+ toVersion: 39,
18627
+ steps: [
18628
+ (0, import_migrations2.addColumns)({
18629
+ table: "memory_vault",
18630
+ columns: [
18631
+ { name: "last_observed_at", type: "number", isOptional: true, isIndexed: true }
18632
+ ]
18633
+ })
18634
+ ]
18287
18635
  }
18288
18636
  ]
18289
18637
  });
@@ -19630,12 +19978,14 @@ function createNotionTools(getAccessToken, requestNotionAccess) {
19630
19978
  DEFAULT_CHUNK_OVERLAP,
19631
19979
  DEFAULT_CHUNK_SIZE,
19632
19980
  DEFAULT_MIN_CHUNK_SIZE,
19981
+ DEFAULT_PROFILE_FACETS,
19633
19982
  DEFAULT_VAULT_CACHE_SIZE,
19634
19983
  DatabaseManager,
19635
19984
  INFERENCE_ID_HEADER,
19636
19985
  LoggerProvider,
19637
19986
  NOTION_OAUTH_CONFIG,
19638
19987
  PII_PATTERNS,
19988
+ PROFILE_DOC_VERSION,
19639
19989
  PiiRedactor,
19640
19990
  QueueManager,
19641
19991
  RECALL_MAX_LIMIT,
@@ -19750,6 +20100,7 @@ function createNotionTools(getAccessToken, requestNotionAccess) {
19750
20100
  streamCancelPath,
19751
20101
  streamReplayPath,
19752
20102
  supersedeVaultMemoryOp,
20103
+ synthesizeProfile,
19753
20104
  updateVaultFolderContextOp,
19754
20105
  updateVaultFolderOp,
19755
20106
  updateVaultMemoryEmbeddingOp,