@anuma/sdk 2.3.1 → 2.4.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.
@@ -6817,6 +6817,7 @@ __export(index_exports, {
6817
6817
  createMemoryVaultSearchTool: () => createMemoryVaultSearchTool,
6818
6818
  createMemoryVaultTool: () => createMemoryVaultTool,
6819
6819
  createNotionTools: () => createNotionTools,
6820
+ createPlatformCursorStore: () => createPlatformCursorStore,
6820
6821
  createRecallTool: () => createRecallTool,
6821
6822
  createStockPricePreProcessor: () => createStockPricePreProcessor,
6822
6823
  createStreamingDeAnonymizer: () => createStreamingDeAnonymizer,
@@ -13245,6 +13246,23 @@ function parseLocalCalendarDay2(year, month, day) {
13245
13246
  // src/lib/memory/autoExtractWorker.ts
13246
13247
  init_logger();
13247
13248
  init_redactor();
13249
+ function createPlatformCursorStore(storage, keyPrefix = "anuma:mem:xcursor:") {
13250
+ return {
13251
+ get(conversationId) {
13252
+ try {
13253
+ return storage.getItem(keyPrefix + conversationId) ?? void 0;
13254
+ } catch {
13255
+ return void 0;
13256
+ }
13257
+ },
13258
+ set(conversationId, messageId) {
13259
+ try {
13260
+ storage.setItem(keyPrefix + conversationId, messageId);
13261
+ } catch {
13262
+ }
13263
+ }
13264
+ };
13265
+ }
13248
13266
  var DEFAULT_WINDOW_SIZE = 6;
13249
13267
  var DEFAULT_MAX_WINDOW_SIZE = 20;
13250
13268
  var CONTEXT_OVERLAP = 2;
@@ -13264,12 +13282,24 @@ function createAutoExtractor(options) {
13264
13282
  let inflight = 0;
13265
13283
  let disposed = false;
13266
13284
  const conversations = /* @__PURE__ */ new Map();
13285
+ const cursorStore = options.cursorStore;
13286
+ const hydrate = (state, conversationId) => {
13287
+ if (state.hydrated) return;
13288
+ state.hydrated = true;
13289
+ if (!cursorStore || conversationId === void 0 || state.watermark !== void 0) return;
13290
+ try {
13291
+ const persisted = cursorStore.get(conversationId);
13292
+ if (persisted) state.watermark = persisted;
13293
+ } catch {
13294
+ }
13295
+ };
13267
13296
  const stateFor = (conversationId) => {
13268
13297
  let state = conversations.get(conversationId);
13269
13298
  if (!state) {
13270
13299
  if (conversations.size >= maxTrackedConversations) evictOldestIdle(conversationId);
13271
13300
  state = {};
13272
13301
  conversations.set(conversationId, state);
13302
+ hydrate(state, conversationId);
13273
13303
  }
13274
13304
  return state;
13275
13305
  };
@@ -13282,10 +13312,12 @@ function createAutoExtractor(options) {
13282
13312
  }
13283
13313
  };
13284
13314
  function computeWindow(messages, conversationId) {
13285
- const lastId = conversations.get(conversationId)?.watermark;
13315
+ const lastId = stateFor(conversationId).watermark;
13286
13316
  const idx = lastId === void 0 ? -1 : messages.findIndex((m) => m.id === lastId);
13287
- if (idx === -1) return messages.slice(-windowSize);
13288
- if (idx >= messages.length - 1) return [];
13317
+ if (idx === -1) {
13318
+ return { window: messages.slice(-windowSize), contiguous: messages.length <= windowSize };
13319
+ }
13320
+ if (idx >= messages.length - 1) return { window: [], contiguous: true };
13289
13321
  const start = Math.max(0, idx + 1 - CONTEXT_OVERLAP);
13290
13322
  let window2 = messages.slice(start);
13291
13323
  if (window2.length > maxWindowSize) {
@@ -13294,8 +13326,21 @@ function createAutoExtractor(options) {
13294
13326
  );
13295
13327
  window2 = window2.slice(0, maxWindowSize);
13296
13328
  }
13297
- return window2;
13329
+ return { window: window2, contiguous: true };
13298
13330
  }
13331
+ const persistCursor = (conversationId, messages, advancedToId, contiguous) => {
13332
+ if (!cursorStore || conversationId === void 0 || !contiguous) return;
13333
+ try {
13334
+ const stored = cursorStore.get(conversationId);
13335
+ if (stored && stored !== advancedToId) {
13336
+ const storedIdx = messages.findIndex((m) => m.id === stored);
13337
+ const newIdx = messages.findIndex((m) => m.id === advancedToId);
13338
+ if (storedIdx !== -1 && newIdx !== -1 && storedIdx >= newIdx) return;
13339
+ }
13340
+ cursorStore.set(conversationId, advancedToId);
13341
+ } catch {
13342
+ }
13343
+ };
13299
13344
  const consolidateBaseUrl = options.consolidate?.baseUrl ?? options.extract.baseUrl;
13300
13345
  const consolidateOptions = options.consolidate ? {
13301
13346
  ...options.extract.apiKey !== void 0 && { apiKey: options.extract.apiKey },
@@ -13341,7 +13386,7 @@ function createAutoExtractor(options) {
13341
13386
  return dispatch(messages, conversationId);
13342
13387
  }
13343
13388
  function dispatch(messages, conversationId) {
13344
- const window2 = computeWindow(messages, conversationId);
13389
+ const { window: window2, contiguous } = computeWindow(messages, conversationId);
13345
13390
  if (window2.length === 0) {
13346
13391
  options.onSkipped?.({ reason: "no-new-content", conversationId });
13347
13392
  drainPending();
@@ -13366,7 +13411,9 @@ function createAutoExtractor(options) {
13366
13411
  }
13367
13412
  }
13368
13413
  );
13369
- stateFor(conversationId).watermark = window2[window2.length - 1].id;
13414
+ const advancedTo = window2[window2.length - 1].id;
13415
+ stateFor(conversationId).watermark = advancedTo;
13416
+ persistCursor(conversationId, messages, advancedTo, contiguous);
13370
13417
  for (let i = 0; i < results.length; i++) {
13371
13418
  if (results[i].action === "suppressed") continue;
13372
13419
  options.onMemoryExtracted?.({
@@ -18493,6 +18540,7 @@ function createNotionTools(getAccessToken, requestNotionAccess) {
18493
18540
  createMemoryVaultSearchTool,
18494
18541
  createMemoryVaultTool,
18495
18542
  createNotionTools,
18543
+ createPlatformCursorStore,
18496
18544
  createRecallTool,
18497
18545
  createStockPricePreProcessor,
18498
18546
  createStreamingDeAnonymizer,
@@ -5446,15 +5446,32 @@ declare function extractAndRetain(messages: AutoExtractMessage[], retainCtx: Ret
5446
5446
  * newest (superset) message list re-covers any superseded turn. Turns for
5447
5447
  * different conversations queue independently and never displace each other.
5448
5448
  *
5449
- * Watermark + window: the worker keeps an in-memory, per-conversation
5450
- * watermark of the last message it extracted through. Each run sends every
5451
- * message *after* that watermark (plus a small overlap for coreference,
5452
- * capped at `maxWindowSize`) rather than a fixed trailing slice — so facts
5453
- * stated in turns that arrived during a busy window are still examined instead
5454
- * of scrolling out of a last-N window. The watermark is session-scoped (in
5455
- * memory); it does NOT survive process death, so an extraction killed mid-flight
5456
- * by app backgrounding is not replayed in a later session. Durable cross-process
5457
- * replay would require a persisted watermark (out of scope here).
5449
+ * Watermark + window: the worker keeps a per-conversation watermark of the
5450
+ * last message it extracted through. Each run sends every message *after* that
5451
+ * watermark (plus a small overlap for coreference, capped at `maxWindowSize`)
5452
+ * rather than a fixed trailing slice — so facts stated in turns that arrived
5453
+ * during a busy window are still examined instead of scrolling out of a last-N
5454
+ * window.
5455
+ *
5456
+ * Durability: the in-memory watermark alone does NOT survive process death, so
5457
+ * a session killed after messages accumulate but before extraction would, on
5458
+ * the next launch, fall back to a trailing-window guess and could skip the
5459
+ * un-extracted tail. Pass a {@link ExtractionCursorStore} (`cursorStore`) to
5460
+ * persist the watermark per conversation — it is hydrated synchronously the
5461
+ * first time a conversation is touched, and written through on every *contiguous*
5462
+ * advance (never on a trailing-slice guess, which could jump the durable cursor
5463
+ * past an un-extracted gap), so a later session resumes exactly after the last
5464
+ * extracted message. The primary guarantee is single-session resume across
5465
+ * process death.
5466
+ *
5467
+ * Concurrent sessions sharing one store (web `localStorage`, mobile MMKV) are
5468
+ * best-effort, NOT exclusive: two sessions can each hydrate the same cursor
5469
+ * before either advances it and briefly re-extract the same turn (retain()'s
5470
+ * dedup/consolidation absorbs the duplicate — wasteful, not corrupting). The
5471
+ * durable write is last-writer-wins with a regression guard (it won't move the
5472
+ * cursor backwards when the currently-stored id is ahead within the same turn's
5473
+ * messages), but it does not serialize writers. Omit `cursorStore` for the
5474
+ * legacy in-memory-only behavior.
5458
5475
  *
5459
5476
  * Memory Studio panel subscribes by passing `onMemoryExtracted` and/or
5460
5477
  * `onTurnComplete` callbacks. The Studio uses `onMemoryExtracted` to
@@ -5502,6 +5519,37 @@ interface TurnCompleteEvent {
5502
5519
  outcome: ExtractOutcome;
5503
5520
  }
5504
5521
  /** @public */
5522
+ /**
5523
+ * Durable per-conversation extraction cursor. Synchronous by contract (both
5524
+ * SDK platform stores — web `localStorage`, mobile MMKV — are sync), so the
5525
+ * worker can hydrate the watermark inline without changing its fire-and-forget
5526
+ * control flow. Implementations should be best-effort; the worker guards every
5527
+ * call, so a throwing store degrades to in-memory-only rather than breaking
5528
+ * extraction.
5529
+ *
5530
+ * @public
5531
+ */
5532
+ interface ExtractionCursorStore {
5533
+ /** Last message id extracted through for `conversationId`, or undefined. */
5534
+ get(conversationId: string): string | undefined;
5535
+ /** Persist the last-extracted message id for `conversationId`. */
5536
+ set(conversationId: string, messageId: string): void;
5537
+ }
5538
+ /** Minimal synchronous key/value surface — satisfied by the SDK's
5539
+ * `PlatformStorage` (web `localStorage`, mobile MMKV). */
5540
+ interface SyncKeyValueStore {
5541
+ getItem(key: string): string | null;
5542
+ setItem(key: string, value: string): void;
5543
+ }
5544
+ /**
5545
+ * Build an {@link ExtractionCursorStore} over a synchronous key/value store
5546
+ * (e.g. the SDK's `PlatformStorage`). Keys are namespaced by `keyPrefix`.
5547
+ * Reads/writes are guarded so a store that throws (quota, private mode) can't
5548
+ * break extraction — the watermark just falls back to in-memory.
5549
+ *
5550
+ * @public
5551
+ */
5552
+ declare function createPlatformCursorStore(storage: SyncKeyValueStore, keyPrefix?: string): ExtractionCursorStore;
5505
5553
  interface CreateAutoExtractorOptions {
5506
5554
  retainCtx: RetainContext;
5507
5555
  extract: ExtractFactsOptions;
@@ -5532,6 +5580,15 @@ interface CreateAutoExtractorOptions {
5532
5580
  * sessions in RAM-constrained hosts. Default 200.
5533
5581
  */
5534
5582
  maxTrackedConversations?: number;
5583
+ /**
5584
+ * Durable per-conversation watermark store. When provided, the watermark is
5585
+ * hydrated from it the first time each conversation is touched and written
5586
+ * through on every advance, so extraction resumes exactly after the last
5587
+ * extracted message across process restarts (and, with a process-shared
5588
+ * store, across concurrent sessions). Build via {@link createPlatformCursorStore}.
5589
+ * Omit for in-memory-only (legacy) behavior.
5590
+ */
5591
+ cursorStore?: ExtractionCursorStore;
5535
5592
  /**
5536
5593
  * Entity / memory_entity write context — when provided, each retained
5537
5594
  * candidate's `entities[]` is persisted via `linkMemoryEntitiesOp`,
@@ -7133,4 +7190,4 @@ declare function refreshNotionAccessToken(params: NotionRefreshTokenParams): Pro
7133
7190
  */
7134
7191
  declare function createNotionTools(getAccessToken: () => string | null, requestNotionAccess: () => Promise<string>): ToolConfig[];
7135
7192
 
7136
- export { type AutoExtractMessage, type AutoExtractor, type Budget, type CachedServerTools, Conversation as ChatConversation, Message as ChatMessage, type ChatRole, type ChunkingOptions, type ConsolidationFallbackReason, type ConversationMemoryInput, ConversationMemory as ConversationMemoryModel, type ConversationMemoryOperationsContext, type CreateAutoExtractorOptions, type CreateConversationOptions, type CreateMessageOptions, type CreateVaultFolderOptions, type CreateVaultMemoryOptions, type CryptoPriceClassification, type CryptoPricePreProcessorOptions, DEFAULT_CACHE_EXPIRATION_MS, DEFAULT_CHUNK_OVERLAP, DEFAULT_CHUNK_SIZE, DEFAULT_MIN_CHUNK_SIZE, DEFAULT_VAULT_CACHE_SIZE, DatabaseManager, type DatabaseManagerLogger, type DatabaseManagerOptions, type EmbeddedWalletSignerFn, type ExtractFactsOptions, type ExtractOutcome, type ExtractedCandidate, type ExtractedEntity, type FactType, type FileMetadata, type FlushResult, type GetMessagesPageOptions, INFERENCE_ID_HEADER, type LazyStoredConversation, type Logger, LoggerProvider, type LoggerProviderProps, type MemoriesNeedingTopicExtraction, type EmbeddingOptions as MemoryEngineEmbeddingOptions, type MemoryEngineResult, type MemoryEngineSearchOptions, type MemoryExtractedEvent, type MemoryKind, type MemoryVaultSearchOptions, type MemoryVaultToolOptions, type MessageRedactionResult, type MessageSkeleton, NOTION_OAUTH_CONFIG, type NotionAuthUrlParams, type NotionClientRegistration, type NotionExchangeCodeParams, type NotionOAuthEndpoints, type NotionPKCEChallenge, type NotionRefreshTokenParams, type NotionTokenResponse, PII_PATTERNS, type PiiCategory, type PiiMatch, type PiiPattern, PiiRedactor, type PiiRedactorOptions, type PlatformStorage, type PortalLlmAuth, type PromptPreProcessor, type PromptPreProcessorContext, type QuantizedEmbedding, QueueManager, type QueueStatus, RECALL_MAX_LIMIT, RECALL_TOOL_NAME, type RankedMemory, type RecallContext, type RecallOptions, type RecallResult, type RecallToolCallbacks, type RecallToolOptions, type RecencyOptions, type RedactionResult, type ReflectOptions, type ReflectResult, type ResumeStreamOptions, type ResumeStreamResult, type ResumeStreamWithStorageResult, type RetainAction, type RetainContext, type RetainOptions, type RetainResult, type RetainSource, SDK_SCHEMA_VERSION, STREAM_RESUMABLE_HEADER, type ScoreBreakdown, type SearchSource, type SendMessageWithStorageArgs, type SendMessageWithStorageDetachedResult, type SendMessageWithStorageResult, type ServerToolsOptions, type ServerToolsResponse, type SignMessageFn, type StockPriceClassification, type StockPricePreProcessorOptions, type ChatCompletionUsage as StoredChatCompletionUsage, type StoredConversation, type StoredConversationMemory, type StoredMessage, type StoredMessageWithSimilarity, type StoredVaultFolder, VaultFolder as StoredVaultFolderModel, type StoredVaultMemory, VaultMemory as StoredVaultMemoryModel, StreamExpiredError, type StreamMetaEvent, type StreamResumeHandle, TOPIC_EXTRACTION_BATCH_SIZE, type TextChunk, type TopicExtractOptions, type TopicExtractionInput, type TopicExtractionRunResult, type TurnCompleteEvent, type TurnSkippedEvent, type UpdateVaultFolderOptions, type UpdateVaultMemoryOptions, type UseChatStorageOptions, type UseChatStorageResult, type UseCreditsOptions, type UseCreditsResult, type UseModelsOptions, type UseModelsResult, type VaultEmbeddingCache, type VaultFolderOperationsContext, type VaultMemoryOperationsContext, type VaultSaveOperation, type VaultSearchResult, WalletPoller, type WeatherClassification, type WeatherPreProcessorOptions, type WebSearchClassification, type WebSearchPreProcessorOptions, addConversationMemoriesOp, buildNotionAuthUrl, chatStorageMigrations, chatStorageSchema, chunkAndEmbedAllMessages, chunkAndEmbedMessage, chunkText, classifyCryptoPrice, classifyCryptoPriceBatch, classifyStockPrice, classifyStockPriceBatch, classifyWeather, classifyWeatherBatch, classifyWebSearch, classifyWebSearchBatch, clearAllEncryptionKeys, clearAllEncryptionState, clearConversationMemoriesOp, clearEncryptionKey, clearLazyTitleCache, clearServerToolsCache, consoleLogger, conversationMemoryToStored, cosineInt8, createAutoExtractor, createCryptoPricePreProcessor, createMemoryEngineTool, createMemoryVaultSearchTool, createMemoryVaultTool, createNotionTools, createRecallTool, createStockPricePreProcessor, createStreamingDeAnonymizer, createVaultEmbeddingCache, createVaultFolderOp, createVaultMemoriesBatchOp, createVaultMemoryOp, createWeatherPreProcessor, createWebSearchPreProcessor, decryptConversationTitle, deleteAllVaultMemoriesForUserOp, deleteVaultFolderOp, deleteVaultMemoryOp, dequantizeEmbedding, discoverNotionOAuthEndpoints, eagerEmbedContent, embedAllMessages, embedMessage, ensureDefaultFoldersOp, exchangeNotionCode, extractAndLinkEntitiesForMemoriesOp, extractAndRetain, extractEntitiesForMemories, extractFacts, generateConversationId, generateEmbedding, generateEmbeddings, generateNotionPKCE, getAllVaultFoldersOp, getAllVaultMemoriesOp, getAllVaultMemoryContentsOp, getCachedServerTools, getConversationMemoriesOp, getConversationsByProjectLazyOp, getConversationsLazyOp, getLogger, getMemoriesNeedingTopicExtractionOp, getServerTools, getUnfiledVaultMemoriesOp, getVaultFolderMemoryCountOp, getVaultMemoryOp, hasEncryptionKey, isPiiRedactor, moveMemoriesToFolderOp, noopLogger, onKeyAvailable, preEmbedVaultMemories, quantizeEmbedding, queueManager, recall, reflect, refreshNotionAccessToken, registerNotionClient, requestEncryptionKey, resolvePiiRedactor, resumeStream, retain, sdkMigrations, sdkModelClasses, sdkSchema, searchVaultMemories, setLogger, shouldChunkMessage, stampTopicsExtractedAtOp, streamCancelPath, streamReplayPath, updateVaultFolderContextOp, updateVaultFolderOp, updateVaultMemoryEmbeddingOp, updateVaultMemoryOp, upsertMessageOp, useChat, useChatStorage, useCredits, useEncryption, useModels, xhrTransport };
7193
+ export { type AutoExtractMessage, type AutoExtractor, type Budget, type CachedServerTools, Conversation as ChatConversation, Message as ChatMessage, type ChatRole, type ChunkingOptions, type ConsolidationFallbackReason, type ConversationMemoryInput, ConversationMemory as ConversationMemoryModel, type ConversationMemoryOperationsContext, type CreateAutoExtractorOptions, type CreateConversationOptions, type CreateMessageOptions, type CreateVaultFolderOptions, type CreateVaultMemoryOptions, type CryptoPriceClassification, type CryptoPricePreProcessorOptions, DEFAULT_CACHE_EXPIRATION_MS, DEFAULT_CHUNK_OVERLAP, DEFAULT_CHUNK_SIZE, DEFAULT_MIN_CHUNK_SIZE, DEFAULT_VAULT_CACHE_SIZE, DatabaseManager, type DatabaseManagerLogger, type DatabaseManagerOptions, type EmbeddedWalletSignerFn, type ExtractFactsOptions, type ExtractOutcome, type ExtractedCandidate, type ExtractedEntity, type ExtractionCursorStore, type FactType, type FileMetadata, type FlushResult, type GetMessagesPageOptions, INFERENCE_ID_HEADER, type LazyStoredConversation, type Logger, LoggerProvider, type LoggerProviderProps, type MemoriesNeedingTopicExtraction, type EmbeddingOptions as MemoryEngineEmbeddingOptions, type MemoryEngineResult, type MemoryEngineSearchOptions, type MemoryExtractedEvent, type MemoryKind, type MemoryVaultSearchOptions, type MemoryVaultToolOptions, type MessageRedactionResult, type MessageSkeleton, NOTION_OAUTH_CONFIG, type NotionAuthUrlParams, type NotionClientRegistration, type NotionExchangeCodeParams, type NotionOAuthEndpoints, type NotionPKCEChallenge, type NotionRefreshTokenParams, type NotionTokenResponse, PII_PATTERNS, type PiiCategory, type PiiMatch, type PiiPattern, PiiRedactor, type PiiRedactorOptions, type PlatformStorage, type PortalLlmAuth, type PromptPreProcessor, type PromptPreProcessorContext, type QuantizedEmbedding, QueueManager, type QueueStatus, RECALL_MAX_LIMIT, RECALL_TOOL_NAME, type RankedMemory, type RecallContext, type RecallOptions, type RecallResult, type RecallToolCallbacks, type RecallToolOptions, type RecencyOptions, type RedactionResult, type ReflectOptions, type ReflectResult, type ResumeStreamOptions, type ResumeStreamResult, type ResumeStreamWithStorageResult, type RetainAction, type RetainContext, type RetainOptions, type RetainResult, type RetainSource, SDK_SCHEMA_VERSION, STREAM_RESUMABLE_HEADER, type ScoreBreakdown, type SearchSource, type SendMessageWithStorageArgs, type SendMessageWithStorageDetachedResult, type SendMessageWithStorageResult, type ServerToolsOptions, type ServerToolsResponse, type SignMessageFn, type StockPriceClassification, type StockPricePreProcessorOptions, type ChatCompletionUsage as StoredChatCompletionUsage, type StoredConversation, type StoredConversationMemory, type StoredMessage, type StoredMessageWithSimilarity, type StoredVaultFolder, VaultFolder as StoredVaultFolderModel, type StoredVaultMemory, VaultMemory as StoredVaultMemoryModel, StreamExpiredError, type StreamMetaEvent, type StreamResumeHandle, TOPIC_EXTRACTION_BATCH_SIZE, type TextChunk, type TopicExtractOptions, type TopicExtractionInput, type TopicExtractionRunResult, type TurnCompleteEvent, type TurnSkippedEvent, type UpdateVaultFolderOptions, type UpdateVaultMemoryOptions, type UseChatStorageOptions, type UseChatStorageResult, type UseCreditsOptions, type UseCreditsResult, type UseModelsOptions, type UseModelsResult, type VaultEmbeddingCache, type VaultFolderOperationsContext, type VaultMemoryOperationsContext, type VaultSaveOperation, type VaultSearchResult, WalletPoller, type WeatherClassification, type WeatherPreProcessorOptions, type WebSearchClassification, type WebSearchPreProcessorOptions, addConversationMemoriesOp, buildNotionAuthUrl, chatStorageMigrations, chatStorageSchema, chunkAndEmbedAllMessages, chunkAndEmbedMessage, chunkText, classifyCryptoPrice, classifyCryptoPriceBatch, classifyStockPrice, classifyStockPriceBatch, classifyWeather, classifyWeatherBatch, classifyWebSearch, classifyWebSearchBatch, clearAllEncryptionKeys, clearAllEncryptionState, clearConversationMemoriesOp, clearEncryptionKey, clearLazyTitleCache, clearServerToolsCache, consoleLogger, conversationMemoryToStored, cosineInt8, createAutoExtractor, createCryptoPricePreProcessor, createMemoryEngineTool, createMemoryVaultSearchTool, createMemoryVaultTool, createNotionTools, createPlatformCursorStore, createRecallTool, createStockPricePreProcessor, createStreamingDeAnonymizer, createVaultEmbeddingCache, createVaultFolderOp, createVaultMemoriesBatchOp, createVaultMemoryOp, createWeatherPreProcessor, createWebSearchPreProcessor, decryptConversationTitle, deleteAllVaultMemoriesForUserOp, deleteVaultFolderOp, deleteVaultMemoryOp, dequantizeEmbedding, discoverNotionOAuthEndpoints, eagerEmbedContent, embedAllMessages, embedMessage, ensureDefaultFoldersOp, exchangeNotionCode, extractAndLinkEntitiesForMemoriesOp, extractAndRetain, extractEntitiesForMemories, extractFacts, generateConversationId, generateEmbedding, generateEmbeddings, generateNotionPKCE, getAllVaultFoldersOp, getAllVaultMemoriesOp, getAllVaultMemoryContentsOp, getCachedServerTools, getConversationMemoriesOp, getConversationsByProjectLazyOp, getConversationsLazyOp, getLogger, getMemoriesNeedingTopicExtractionOp, getServerTools, getUnfiledVaultMemoriesOp, getVaultFolderMemoryCountOp, getVaultMemoryOp, hasEncryptionKey, isPiiRedactor, moveMemoriesToFolderOp, noopLogger, onKeyAvailable, preEmbedVaultMemories, quantizeEmbedding, queueManager, recall, reflect, refreshNotionAccessToken, registerNotionClient, requestEncryptionKey, resolvePiiRedactor, resumeStream, retain, sdkMigrations, sdkModelClasses, sdkSchema, searchVaultMemories, setLogger, shouldChunkMessage, stampTopicsExtractedAtOp, streamCancelPath, streamReplayPath, updateVaultFolderContextOp, updateVaultFolderOp, updateVaultMemoryEmbeddingOp, updateVaultMemoryOp, upsertMessageOp, useChat, useChatStorage, useCredits, useEncryption, useModels, xhrTransport };
@@ -5446,15 +5446,32 @@ declare function extractAndRetain(messages: AutoExtractMessage[], retainCtx: Ret
5446
5446
  * newest (superset) message list re-covers any superseded turn. Turns for
5447
5447
  * different conversations queue independently and never displace each other.
5448
5448
  *
5449
- * Watermark + window: the worker keeps an in-memory, per-conversation
5450
- * watermark of the last message it extracted through. Each run sends every
5451
- * message *after* that watermark (plus a small overlap for coreference,
5452
- * capped at `maxWindowSize`) rather than a fixed trailing slice — so facts
5453
- * stated in turns that arrived during a busy window are still examined instead
5454
- * of scrolling out of a last-N window. The watermark is session-scoped (in
5455
- * memory); it does NOT survive process death, so an extraction killed mid-flight
5456
- * by app backgrounding is not replayed in a later session. Durable cross-process
5457
- * replay would require a persisted watermark (out of scope here).
5449
+ * Watermark + window: the worker keeps a per-conversation watermark of the
5450
+ * last message it extracted through. Each run sends every message *after* that
5451
+ * watermark (plus a small overlap for coreference, capped at `maxWindowSize`)
5452
+ * rather than a fixed trailing slice — so facts stated in turns that arrived
5453
+ * during a busy window are still examined instead of scrolling out of a last-N
5454
+ * window.
5455
+ *
5456
+ * Durability: the in-memory watermark alone does NOT survive process death, so
5457
+ * a session killed after messages accumulate but before extraction would, on
5458
+ * the next launch, fall back to a trailing-window guess and could skip the
5459
+ * un-extracted tail. Pass a {@link ExtractionCursorStore} (`cursorStore`) to
5460
+ * persist the watermark per conversation — it is hydrated synchronously the
5461
+ * first time a conversation is touched, and written through on every *contiguous*
5462
+ * advance (never on a trailing-slice guess, which could jump the durable cursor
5463
+ * past an un-extracted gap), so a later session resumes exactly after the last
5464
+ * extracted message. The primary guarantee is single-session resume across
5465
+ * process death.
5466
+ *
5467
+ * Concurrent sessions sharing one store (web `localStorage`, mobile MMKV) are
5468
+ * best-effort, NOT exclusive: two sessions can each hydrate the same cursor
5469
+ * before either advances it and briefly re-extract the same turn (retain()'s
5470
+ * dedup/consolidation absorbs the duplicate — wasteful, not corrupting). The
5471
+ * durable write is last-writer-wins with a regression guard (it won't move the
5472
+ * cursor backwards when the currently-stored id is ahead within the same turn's
5473
+ * messages), but it does not serialize writers. Omit `cursorStore` for the
5474
+ * legacy in-memory-only behavior.
5458
5475
  *
5459
5476
  * Memory Studio panel subscribes by passing `onMemoryExtracted` and/or
5460
5477
  * `onTurnComplete` callbacks. The Studio uses `onMemoryExtracted` to
@@ -5502,6 +5519,37 @@ interface TurnCompleteEvent {
5502
5519
  outcome: ExtractOutcome;
5503
5520
  }
5504
5521
  /** @public */
5522
+ /**
5523
+ * Durable per-conversation extraction cursor. Synchronous by contract (both
5524
+ * SDK platform stores — web `localStorage`, mobile MMKV — are sync), so the
5525
+ * worker can hydrate the watermark inline without changing its fire-and-forget
5526
+ * control flow. Implementations should be best-effort; the worker guards every
5527
+ * call, so a throwing store degrades to in-memory-only rather than breaking
5528
+ * extraction.
5529
+ *
5530
+ * @public
5531
+ */
5532
+ interface ExtractionCursorStore {
5533
+ /** Last message id extracted through for `conversationId`, or undefined. */
5534
+ get(conversationId: string): string | undefined;
5535
+ /** Persist the last-extracted message id for `conversationId`. */
5536
+ set(conversationId: string, messageId: string): void;
5537
+ }
5538
+ /** Minimal synchronous key/value surface — satisfied by the SDK's
5539
+ * `PlatformStorage` (web `localStorage`, mobile MMKV). */
5540
+ interface SyncKeyValueStore {
5541
+ getItem(key: string): string | null;
5542
+ setItem(key: string, value: string): void;
5543
+ }
5544
+ /**
5545
+ * Build an {@link ExtractionCursorStore} over a synchronous key/value store
5546
+ * (e.g. the SDK's `PlatformStorage`). Keys are namespaced by `keyPrefix`.
5547
+ * Reads/writes are guarded so a store that throws (quota, private mode) can't
5548
+ * break extraction — the watermark just falls back to in-memory.
5549
+ *
5550
+ * @public
5551
+ */
5552
+ declare function createPlatformCursorStore(storage: SyncKeyValueStore, keyPrefix?: string): ExtractionCursorStore;
5505
5553
  interface CreateAutoExtractorOptions {
5506
5554
  retainCtx: RetainContext;
5507
5555
  extract: ExtractFactsOptions;
@@ -5532,6 +5580,15 @@ interface CreateAutoExtractorOptions {
5532
5580
  * sessions in RAM-constrained hosts. Default 200.
5533
5581
  */
5534
5582
  maxTrackedConversations?: number;
5583
+ /**
5584
+ * Durable per-conversation watermark store. When provided, the watermark is
5585
+ * hydrated from it the first time each conversation is touched and written
5586
+ * through on every advance, so extraction resumes exactly after the last
5587
+ * extracted message across process restarts (and, with a process-shared
5588
+ * store, across concurrent sessions). Build via {@link createPlatformCursorStore}.
5589
+ * Omit for in-memory-only (legacy) behavior.
5590
+ */
5591
+ cursorStore?: ExtractionCursorStore;
5535
5592
  /**
5536
5593
  * Entity / memory_entity write context — when provided, each retained
5537
5594
  * candidate's `entities[]` is persisted via `linkMemoryEntitiesOp`,
@@ -7133,4 +7190,4 @@ declare function refreshNotionAccessToken(params: NotionRefreshTokenParams): Pro
7133
7190
  */
7134
7191
  declare function createNotionTools(getAccessToken: () => string | null, requestNotionAccess: () => Promise<string>): ToolConfig[];
7135
7192
 
7136
- export { type AutoExtractMessage, type AutoExtractor, type Budget, type CachedServerTools, Conversation as ChatConversation, Message as ChatMessage, type ChatRole, type ChunkingOptions, type ConsolidationFallbackReason, type ConversationMemoryInput, ConversationMemory as ConversationMemoryModel, type ConversationMemoryOperationsContext, type CreateAutoExtractorOptions, type CreateConversationOptions, type CreateMessageOptions, type CreateVaultFolderOptions, type CreateVaultMemoryOptions, type CryptoPriceClassification, type CryptoPricePreProcessorOptions, DEFAULT_CACHE_EXPIRATION_MS, DEFAULT_CHUNK_OVERLAP, DEFAULT_CHUNK_SIZE, DEFAULT_MIN_CHUNK_SIZE, DEFAULT_VAULT_CACHE_SIZE, DatabaseManager, type DatabaseManagerLogger, type DatabaseManagerOptions, type EmbeddedWalletSignerFn, type ExtractFactsOptions, type ExtractOutcome, type ExtractedCandidate, type ExtractedEntity, type FactType, type FileMetadata, type FlushResult, type GetMessagesPageOptions, INFERENCE_ID_HEADER, type LazyStoredConversation, type Logger, LoggerProvider, type LoggerProviderProps, type MemoriesNeedingTopicExtraction, type EmbeddingOptions as MemoryEngineEmbeddingOptions, type MemoryEngineResult, type MemoryEngineSearchOptions, type MemoryExtractedEvent, type MemoryKind, type MemoryVaultSearchOptions, type MemoryVaultToolOptions, type MessageRedactionResult, type MessageSkeleton, NOTION_OAUTH_CONFIG, type NotionAuthUrlParams, type NotionClientRegistration, type NotionExchangeCodeParams, type NotionOAuthEndpoints, type NotionPKCEChallenge, type NotionRefreshTokenParams, type NotionTokenResponse, PII_PATTERNS, type PiiCategory, type PiiMatch, type PiiPattern, PiiRedactor, type PiiRedactorOptions, type PlatformStorage, type PortalLlmAuth, type PromptPreProcessor, type PromptPreProcessorContext, type QuantizedEmbedding, QueueManager, type QueueStatus, RECALL_MAX_LIMIT, RECALL_TOOL_NAME, type RankedMemory, type RecallContext, type RecallOptions, type RecallResult, type RecallToolCallbacks, type RecallToolOptions, type RecencyOptions, type RedactionResult, type ReflectOptions, type ReflectResult, type ResumeStreamOptions, type ResumeStreamResult, type ResumeStreamWithStorageResult, type RetainAction, type RetainContext, type RetainOptions, type RetainResult, type RetainSource, SDK_SCHEMA_VERSION, STREAM_RESUMABLE_HEADER, type ScoreBreakdown, type SearchSource, type SendMessageWithStorageArgs, type SendMessageWithStorageDetachedResult, type SendMessageWithStorageResult, type ServerToolsOptions, type ServerToolsResponse, type SignMessageFn, type StockPriceClassification, type StockPricePreProcessorOptions, type ChatCompletionUsage as StoredChatCompletionUsage, type StoredConversation, type StoredConversationMemory, type StoredMessage, type StoredMessageWithSimilarity, type StoredVaultFolder, VaultFolder as StoredVaultFolderModel, type StoredVaultMemory, VaultMemory as StoredVaultMemoryModel, StreamExpiredError, type StreamMetaEvent, type StreamResumeHandle, TOPIC_EXTRACTION_BATCH_SIZE, type TextChunk, type TopicExtractOptions, type TopicExtractionInput, type TopicExtractionRunResult, type TurnCompleteEvent, type TurnSkippedEvent, type UpdateVaultFolderOptions, type UpdateVaultMemoryOptions, type UseChatStorageOptions, type UseChatStorageResult, type UseCreditsOptions, type UseCreditsResult, type UseModelsOptions, type UseModelsResult, type VaultEmbeddingCache, type VaultFolderOperationsContext, type VaultMemoryOperationsContext, type VaultSaveOperation, type VaultSearchResult, WalletPoller, type WeatherClassification, type WeatherPreProcessorOptions, type WebSearchClassification, type WebSearchPreProcessorOptions, addConversationMemoriesOp, buildNotionAuthUrl, chatStorageMigrations, chatStorageSchema, chunkAndEmbedAllMessages, chunkAndEmbedMessage, chunkText, classifyCryptoPrice, classifyCryptoPriceBatch, classifyStockPrice, classifyStockPriceBatch, classifyWeather, classifyWeatherBatch, classifyWebSearch, classifyWebSearchBatch, clearAllEncryptionKeys, clearAllEncryptionState, clearConversationMemoriesOp, clearEncryptionKey, clearLazyTitleCache, clearServerToolsCache, consoleLogger, conversationMemoryToStored, cosineInt8, createAutoExtractor, createCryptoPricePreProcessor, createMemoryEngineTool, createMemoryVaultSearchTool, createMemoryVaultTool, createNotionTools, createRecallTool, createStockPricePreProcessor, createStreamingDeAnonymizer, createVaultEmbeddingCache, createVaultFolderOp, createVaultMemoriesBatchOp, createVaultMemoryOp, createWeatherPreProcessor, createWebSearchPreProcessor, decryptConversationTitle, deleteAllVaultMemoriesForUserOp, deleteVaultFolderOp, deleteVaultMemoryOp, dequantizeEmbedding, discoverNotionOAuthEndpoints, eagerEmbedContent, embedAllMessages, embedMessage, ensureDefaultFoldersOp, exchangeNotionCode, extractAndLinkEntitiesForMemoriesOp, extractAndRetain, extractEntitiesForMemories, extractFacts, generateConversationId, generateEmbedding, generateEmbeddings, generateNotionPKCE, getAllVaultFoldersOp, getAllVaultMemoriesOp, getAllVaultMemoryContentsOp, getCachedServerTools, getConversationMemoriesOp, getConversationsByProjectLazyOp, getConversationsLazyOp, getLogger, getMemoriesNeedingTopicExtractionOp, getServerTools, getUnfiledVaultMemoriesOp, getVaultFolderMemoryCountOp, getVaultMemoryOp, hasEncryptionKey, isPiiRedactor, moveMemoriesToFolderOp, noopLogger, onKeyAvailable, preEmbedVaultMemories, quantizeEmbedding, queueManager, recall, reflect, refreshNotionAccessToken, registerNotionClient, requestEncryptionKey, resolvePiiRedactor, resumeStream, retain, sdkMigrations, sdkModelClasses, sdkSchema, searchVaultMemories, setLogger, shouldChunkMessage, stampTopicsExtractedAtOp, streamCancelPath, streamReplayPath, updateVaultFolderContextOp, updateVaultFolderOp, updateVaultMemoryEmbeddingOp, updateVaultMemoryOp, upsertMessageOp, useChat, useChatStorage, useCredits, useEncryption, useModels, xhrTransport };
7193
+ export { type AutoExtractMessage, type AutoExtractor, type Budget, type CachedServerTools, Conversation as ChatConversation, Message as ChatMessage, type ChatRole, type ChunkingOptions, type ConsolidationFallbackReason, type ConversationMemoryInput, ConversationMemory as ConversationMemoryModel, type ConversationMemoryOperationsContext, type CreateAutoExtractorOptions, type CreateConversationOptions, type CreateMessageOptions, type CreateVaultFolderOptions, type CreateVaultMemoryOptions, type CryptoPriceClassification, type CryptoPricePreProcessorOptions, DEFAULT_CACHE_EXPIRATION_MS, DEFAULT_CHUNK_OVERLAP, DEFAULT_CHUNK_SIZE, DEFAULT_MIN_CHUNK_SIZE, DEFAULT_VAULT_CACHE_SIZE, DatabaseManager, type DatabaseManagerLogger, type DatabaseManagerOptions, type EmbeddedWalletSignerFn, type ExtractFactsOptions, type ExtractOutcome, type ExtractedCandidate, type ExtractedEntity, type ExtractionCursorStore, type FactType, type FileMetadata, type FlushResult, type GetMessagesPageOptions, INFERENCE_ID_HEADER, type LazyStoredConversation, type Logger, LoggerProvider, type LoggerProviderProps, type MemoriesNeedingTopicExtraction, type EmbeddingOptions as MemoryEngineEmbeddingOptions, type MemoryEngineResult, type MemoryEngineSearchOptions, type MemoryExtractedEvent, type MemoryKind, type MemoryVaultSearchOptions, type MemoryVaultToolOptions, type MessageRedactionResult, type MessageSkeleton, NOTION_OAUTH_CONFIG, type NotionAuthUrlParams, type NotionClientRegistration, type NotionExchangeCodeParams, type NotionOAuthEndpoints, type NotionPKCEChallenge, type NotionRefreshTokenParams, type NotionTokenResponse, PII_PATTERNS, type PiiCategory, type PiiMatch, type PiiPattern, PiiRedactor, type PiiRedactorOptions, type PlatformStorage, type PortalLlmAuth, type PromptPreProcessor, type PromptPreProcessorContext, type QuantizedEmbedding, QueueManager, type QueueStatus, RECALL_MAX_LIMIT, RECALL_TOOL_NAME, type RankedMemory, type RecallContext, type RecallOptions, type RecallResult, type RecallToolCallbacks, type RecallToolOptions, type RecencyOptions, type RedactionResult, type ReflectOptions, type ReflectResult, type ResumeStreamOptions, type ResumeStreamResult, type ResumeStreamWithStorageResult, type RetainAction, type RetainContext, type RetainOptions, type RetainResult, type RetainSource, SDK_SCHEMA_VERSION, STREAM_RESUMABLE_HEADER, type ScoreBreakdown, type SearchSource, type SendMessageWithStorageArgs, type SendMessageWithStorageDetachedResult, type SendMessageWithStorageResult, type ServerToolsOptions, type ServerToolsResponse, type SignMessageFn, type StockPriceClassification, type StockPricePreProcessorOptions, type ChatCompletionUsage as StoredChatCompletionUsage, type StoredConversation, type StoredConversationMemory, type StoredMessage, type StoredMessageWithSimilarity, type StoredVaultFolder, VaultFolder as StoredVaultFolderModel, type StoredVaultMemory, VaultMemory as StoredVaultMemoryModel, StreamExpiredError, type StreamMetaEvent, type StreamResumeHandle, TOPIC_EXTRACTION_BATCH_SIZE, type TextChunk, type TopicExtractOptions, type TopicExtractionInput, type TopicExtractionRunResult, type TurnCompleteEvent, type TurnSkippedEvent, type UpdateVaultFolderOptions, type UpdateVaultMemoryOptions, type UseChatStorageOptions, type UseChatStorageResult, type UseCreditsOptions, type UseCreditsResult, type UseModelsOptions, type UseModelsResult, type VaultEmbeddingCache, type VaultFolderOperationsContext, type VaultMemoryOperationsContext, type VaultSaveOperation, type VaultSearchResult, WalletPoller, type WeatherClassification, type WeatherPreProcessorOptions, type WebSearchClassification, type WebSearchPreProcessorOptions, addConversationMemoriesOp, buildNotionAuthUrl, chatStorageMigrations, chatStorageSchema, chunkAndEmbedAllMessages, chunkAndEmbedMessage, chunkText, classifyCryptoPrice, classifyCryptoPriceBatch, classifyStockPrice, classifyStockPriceBatch, classifyWeather, classifyWeatherBatch, classifyWebSearch, classifyWebSearchBatch, clearAllEncryptionKeys, clearAllEncryptionState, clearConversationMemoriesOp, clearEncryptionKey, clearLazyTitleCache, clearServerToolsCache, consoleLogger, conversationMemoryToStored, cosineInt8, createAutoExtractor, createCryptoPricePreProcessor, createMemoryEngineTool, createMemoryVaultSearchTool, createMemoryVaultTool, createNotionTools, createPlatformCursorStore, createRecallTool, createStockPricePreProcessor, createStreamingDeAnonymizer, createVaultEmbeddingCache, createVaultFolderOp, createVaultMemoriesBatchOp, createVaultMemoryOp, createWeatherPreProcessor, createWebSearchPreProcessor, decryptConversationTitle, deleteAllVaultMemoriesForUserOp, deleteVaultFolderOp, deleteVaultMemoryOp, dequantizeEmbedding, discoverNotionOAuthEndpoints, eagerEmbedContent, embedAllMessages, embedMessage, ensureDefaultFoldersOp, exchangeNotionCode, extractAndLinkEntitiesForMemoriesOp, extractAndRetain, extractEntitiesForMemories, extractFacts, generateConversationId, generateEmbedding, generateEmbeddings, generateNotionPKCE, getAllVaultFoldersOp, getAllVaultMemoriesOp, getAllVaultMemoryContentsOp, getCachedServerTools, getConversationMemoriesOp, getConversationsByProjectLazyOp, getConversationsLazyOp, getLogger, getMemoriesNeedingTopicExtractionOp, getServerTools, getUnfiledVaultMemoriesOp, getVaultFolderMemoryCountOp, getVaultMemoryOp, hasEncryptionKey, isPiiRedactor, moveMemoriesToFolderOp, noopLogger, onKeyAvailable, preEmbedVaultMemories, quantizeEmbedding, queueManager, recall, reflect, refreshNotionAccessToken, registerNotionClient, requestEncryptionKey, resolvePiiRedactor, resumeStream, retain, sdkMigrations, sdkModelClasses, sdkSchema, searchVaultMemories, setLogger, shouldChunkMessage, stampTopicsExtractedAtOp, streamCancelPath, streamReplayPath, updateVaultFolderContextOp, updateVaultFolderOp, updateVaultMemoryEmbeddingOp, updateVaultMemoryOp, upsertMessageOp, useChat, useChatStorage, useCredits, useEncryption, useModels, xhrTransport };
@@ -6384,6 +6384,23 @@ function parseLocalCalendarDay(year, month, day) {
6384
6384
  }
6385
6385
 
6386
6386
  // src/lib/memory/autoExtractWorker.ts
6387
+ function createPlatformCursorStore(storage, keyPrefix = "anuma:mem:xcursor:") {
6388
+ return {
6389
+ get(conversationId) {
6390
+ try {
6391
+ return storage.getItem(keyPrefix + conversationId) ?? void 0;
6392
+ } catch {
6393
+ return void 0;
6394
+ }
6395
+ },
6396
+ set(conversationId, messageId) {
6397
+ try {
6398
+ storage.setItem(keyPrefix + conversationId, messageId);
6399
+ } catch {
6400
+ }
6401
+ }
6402
+ };
6403
+ }
6387
6404
  var DEFAULT_WINDOW_SIZE = 6;
6388
6405
  var DEFAULT_MAX_WINDOW_SIZE = 20;
6389
6406
  var CONTEXT_OVERLAP = 2;
@@ -6403,12 +6420,24 @@ function createAutoExtractor(options) {
6403
6420
  let inflight = 0;
6404
6421
  let disposed = false;
6405
6422
  const conversations = /* @__PURE__ */ new Map();
6423
+ const cursorStore = options.cursorStore;
6424
+ const hydrate = (state, conversationId) => {
6425
+ if (state.hydrated) return;
6426
+ state.hydrated = true;
6427
+ if (!cursorStore || conversationId === void 0 || state.watermark !== void 0) return;
6428
+ try {
6429
+ const persisted = cursorStore.get(conversationId);
6430
+ if (persisted) state.watermark = persisted;
6431
+ } catch {
6432
+ }
6433
+ };
6406
6434
  const stateFor = (conversationId) => {
6407
6435
  let state = conversations.get(conversationId);
6408
6436
  if (!state) {
6409
6437
  if (conversations.size >= maxTrackedConversations) evictOldestIdle(conversationId);
6410
6438
  state = {};
6411
6439
  conversations.set(conversationId, state);
6440
+ hydrate(state, conversationId);
6412
6441
  }
6413
6442
  return state;
6414
6443
  };
@@ -6421,10 +6450,12 @@ function createAutoExtractor(options) {
6421
6450
  }
6422
6451
  };
6423
6452
  function computeWindow(messages, conversationId) {
6424
- const lastId = conversations.get(conversationId)?.watermark;
6453
+ const lastId = stateFor(conversationId).watermark;
6425
6454
  const idx = lastId === void 0 ? -1 : messages.findIndex((m) => m.id === lastId);
6426
- if (idx === -1) return messages.slice(-windowSize);
6427
- if (idx >= messages.length - 1) return [];
6455
+ if (idx === -1) {
6456
+ return { window: messages.slice(-windowSize), contiguous: messages.length <= windowSize };
6457
+ }
6458
+ if (idx >= messages.length - 1) return { window: [], contiguous: true };
6428
6459
  const start = Math.max(0, idx + 1 - CONTEXT_OVERLAP);
6429
6460
  let window = messages.slice(start);
6430
6461
  if (window.length > maxWindowSize) {
@@ -6433,8 +6464,21 @@ function createAutoExtractor(options) {
6433
6464
  );
6434
6465
  window = window.slice(0, maxWindowSize);
6435
6466
  }
6436
- return window;
6467
+ return { window, contiguous: true };
6437
6468
  }
6469
+ const persistCursor = (conversationId, messages, advancedToId, contiguous) => {
6470
+ if (!cursorStore || conversationId === void 0 || !contiguous) return;
6471
+ try {
6472
+ const stored = cursorStore.get(conversationId);
6473
+ if (stored && stored !== advancedToId) {
6474
+ const storedIdx = messages.findIndex((m) => m.id === stored);
6475
+ const newIdx = messages.findIndex((m) => m.id === advancedToId);
6476
+ if (storedIdx !== -1 && newIdx !== -1 && storedIdx >= newIdx) return;
6477
+ }
6478
+ cursorStore.set(conversationId, advancedToId);
6479
+ } catch {
6480
+ }
6481
+ };
6438
6482
  const consolidateBaseUrl = options.consolidate?.baseUrl ?? options.extract.baseUrl;
6439
6483
  const consolidateOptions = options.consolidate ? {
6440
6484
  ...options.extract.apiKey !== void 0 && { apiKey: options.extract.apiKey },
@@ -6480,7 +6524,7 @@ function createAutoExtractor(options) {
6480
6524
  return dispatch(messages, conversationId);
6481
6525
  }
6482
6526
  function dispatch(messages, conversationId) {
6483
- const window = computeWindow(messages, conversationId);
6527
+ const { window, contiguous } = computeWindow(messages, conversationId);
6484
6528
  if (window.length === 0) {
6485
6529
  options.onSkipped?.({ reason: "no-new-content", conversationId });
6486
6530
  drainPending();
@@ -6505,7 +6549,9 @@ function createAutoExtractor(options) {
6505
6549
  }
6506
6550
  }
6507
6551
  );
6508
- stateFor(conversationId).watermark = window[window.length - 1].id;
6552
+ const advancedTo = window[window.length - 1].id;
6553
+ stateFor(conversationId).watermark = advancedTo;
6554
+ persistCursor(conversationId, messages, advancedTo, contiguous);
6509
6555
  for (let i = 0; i < results.length; i++) {
6510
6556
  if (results[i].action === "suppressed") continue;
6511
6557
  options.onMemoryExtracted?.({
@@ -11591,6 +11637,7 @@ export {
11591
11637
  createMemoryVaultSearchTool,
11592
11638
  createMemoryVaultTool,
11593
11639
  createNotionTools,
11640
+ createPlatformCursorStore,
11594
11641
  createRecallTool,
11595
11642
  createStockPricePreProcessor,
11596
11643
  createStreamingDeAnonymizer,
@@ -7214,6 +7214,7 @@ __export(index_exports, {
7214
7214
  createMemoryEngineTool: () => createMemoryEngineTool,
7215
7215
  createMemoryVaultSearchTool: () => createMemoryVaultSearchTool,
7216
7216
  createMemoryVaultTool: () => createMemoryVaultTool,
7217
+ createPlatformCursorStore: () => createPlatformCursorStore,
7217
7218
  createProjectOp: () => createProjectOp,
7218
7219
  createRecallTool: () => createRecallTool,
7219
7220
  createSavedToolOp: () => createSavedToolOp,
@@ -14596,6 +14597,23 @@ function parseLocalCalendarDay2(year, month, day) {
14596
14597
  // src/lib/memory/autoExtractWorker.ts
14597
14598
  init_logger();
14598
14599
  init_redactor();
14600
+ function createPlatformCursorStore(storage, keyPrefix = "anuma:mem:xcursor:") {
14601
+ return {
14602
+ get(conversationId) {
14603
+ try {
14604
+ return storage.getItem(keyPrefix + conversationId) ?? void 0;
14605
+ } catch {
14606
+ return void 0;
14607
+ }
14608
+ },
14609
+ set(conversationId, messageId) {
14610
+ try {
14611
+ storage.setItem(keyPrefix + conversationId, messageId);
14612
+ } catch {
14613
+ }
14614
+ }
14615
+ };
14616
+ }
14599
14617
  var DEFAULT_WINDOW_SIZE = 6;
14600
14618
  var DEFAULT_MAX_WINDOW_SIZE = 20;
14601
14619
  var CONTEXT_OVERLAP = 2;
@@ -14615,12 +14633,24 @@ function createAutoExtractor(options) {
14615
14633
  let inflight = 0;
14616
14634
  let disposed = false;
14617
14635
  const conversations = /* @__PURE__ */ new Map();
14636
+ const cursorStore = options.cursorStore;
14637
+ const hydrate = (state, conversationId) => {
14638
+ if (state.hydrated) return;
14639
+ state.hydrated = true;
14640
+ if (!cursorStore || conversationId === void 0 || state.watermark !== void 0) return;
14641
+ try {
14642
+ const persisted = cursorStore.get(conversationId);
14643
+ if (persisted) state.watermark = persisted;
14644
+ } catch {
14645
+ }
14646
+ };
14618
14647
  const stateFor = (conversationId) => {
14619
14648
  let state = conversations.get(conversationId);
14620
14649
  if (!state) {
14621
14650
  if (conversations.size >= maxTrackedConversations) evictOldestIdle(conversationId);
14622
14651
  state = {};
14623
14652
  conversations.set(conversationId, state);
14653
+ hydrate(state, conversationId);
14624
14654
  }
14625
14655
  return state;
14626
14656
  };
@@ -14633,10 +14663,12 @@ function createAutoExtractor(options) {
14633
14663
  }
14634
14664
  };
14635
14665
  function computeWindow(messages, conversationId) {
14636
- const lastId = conversations.get(conversationId)?.watermark;
14666
+ const lastId = stateFor(conversationId).watermark;
14637
14667
  const idx = lastId === void 0 ? -1 : messages.findIndex((m) => m.id === lastId);
14638
- if (idx === -1) return messages.slice(-windowSize);
14639
- if (idx >= messages.length - 1) return [];
14668
+ if (idx === -1) {
14669
+ return { window: messages.slice(-windowSize), contiguous: messages.length <= windowSize };
14670
+ }
14671
+ if (idx >= messages.length - 1) return { window: [], contiguous: true };
14640
14672
  const start = Math.max(0, idx + 1 - CONTEXT_OVERLAP);
14641
14673
  let window2 = messages.slice(start);
14642
14674
  if (window2.length > maxWindowSize) {
@@ -14645,8 +14677,21 @@ function createAutoExtractor(options) {
14645
14677
  );
14646
14678
  window2 = window2.slice(0, maxWindowSize);
14647
14679
  }
14648
- return window2;
14680
+ return { window: window2, contiguous: true };
14649
14681
  }
14682
+ const persistCursor = (conversationId, messages, advancedToId, contiguous) => {
14683
+ if (!cursorStore || conversationId === void 0 || !contiguous) return;
14684
+ try {
14685
+ const stored = cursorStore.get(conversationId);
14686
+ if (stored && stored !== advancedToId) {
14687
+ const storedIdx = messages.findIndex((m) => m.id === stored);
14688
+ const newIdx = messages.findIndex((m) => m.id === advancedToId);
14689
+ if (storedIdx !== -1 && newIdx !== -1 && storedIdx >= newIdx) return;
14690
+ }
14691
+ cursorStore.set(conversationId, advancedToId);
14692
+ } catch {
14693
+ }
14694
+ };
14650
14695
  const consolidateBaseUrl = options.consolidate?.baseUrl ?? options.extract.baseUrl;
14651
14696
  const consolidateOptions = options.consolidate ? {
14652
14697
  ...options.extract.apiKey !== void 0 && { apiKey: options.extract.apiKey },
@@ -14692,7 +14737,7 @@ function createAutoExtractor(options) {
14692
14737
  return dispatch(messages, conversationId);
14693
14738
  }
14694
14739
  function dispatch(messages, conversationId) {
14695
- const window2 = computeWindow(messages, conversationId);
14740
+ const { window: window2, contiguous } = computeWindow(messages, conversationId);
14696
14741
  if (window2.length === 0) {
14697
14742
  options.onSkipped?.({ reason: "no-new-content", conversationId });
14698
14743
  drainPending();
@@ -14717,7 +14762,9 @@ function createAutoExtractor(options) {
14717
14762
  }
14718
14763
  }
14719
14764
  );
14720
- stateFor(conversationId).watermark = window2[window2.length - 1].id;
14765
+ const advancedTo = window2[window2.length - 1].id;
14766
+ stateFor(conversationId).watermark = advancedTo;
14767
+ persistCursor(conversationId, messages, advancedTo, contiguous);
14721
14768
  for (let i = 0; i < results.length; i++) {
14722
14769
  if (results[i].action === "suppressed") continue;
14723
14770
  options.onMemoryExtracted?.({
@@ -30023,6 +30070,7 @@ function renderAnumaJsx(jsx3) {
30023
30070
  createMemoryEngineTool,
30024
30071
  createMemoryVaultSearchTool,
30025
30072
  createMemoryVaultTool,
30073
+ createPlatformCursorStore,
30026
30074
  createProjectOp,
30027
30075
  createRecallTool,
30028
30076
  createSavedToolOp,