@anuma/sdk 4.2.1 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5009,15 +5009,26 @@ interface MemoryVaultSearchOptions {
5009
5009
  /** RRF smoothing constant for lane fusion. Default 60. */
5010
5010
  rrfK?: number;
5011
5011
  /**
5012
- * LLM-based query decomposition for composite/abstract queries. When set,
5013
- * each query is classified + (if composite) decomposed into 3–5 facet
5014
- * sub-queries via gpt-5-mini, then ranked via {@link rankComposite}.
5015
- * Requires `decomposeOptions` (auth) when set to "llm".
5012
+ * Pre-decomposed facet queries for the composite ranker (719/B4). When
5013
+ * ≥2 are supplied (and embeddings are available), runs {@link rankComposite}
5014
+ * over them — no LLM call inside the search path. Callers that want LLM
5015
+ * rewrite (e.g. `createRecallTool`) call `decomposeQuery` first and pass
5016
+ * the facets here.
5017
+ */
5018
+ subQueries?: string[];
5019
+ /**
5020
+ * @deprecated 719/B4 — ignored by {@link searchVaultMemories} /
5021
+ * {@link searchVaultMemoriesWithSize}. Pass {@link MemoryVaultSearchOptions.subQueries}
5022
+ * (or use `createRecallTool`). The legacy {@link createMemoryVaultSearchTool}
5023
+ * executor still honors `decompose: "llm"` + `decomposeOptions` for eval
5024
+ * parity, then forwards facets into the LLM-free search path.
5016
5025
  */
5017
5026
  decompose?: "off" | "llm";
5018
- /** Auth + endpoint for the decomposition LLM call. Required when
5019
- * decompose="llm". Auth is the dual pattern — one of `apiKey` /
5020
- * `getToken`; see {@link PortalLlmAuth}. */
5027
+ /**
5028
+ * @deprecated 719/B4 — see `decompose`. Ignored on the programmatic search
5029
+ * path; tool-layer rewrite still reads this from the search-tool options.
5030
+ * Prefer `RecallToolOptions.decomposeOptions` with `createRecallTool`.
5031
+ */
5021
5032
  decomposeOptions?: PortalLlmAuth & {
5022
5033
  baseUrl?: string;
5023
5034
  model?: string;
@@ -5182,9 +5193,11 @@ type MemoryKind = "fact" | "chunk";
5182
5193
  * Budget controls retrieval depth/cost. Higher budgets enable more
5183
5194
  * candidate sources and the cross-encoder reranker.
5184
5195
  *
5185
- * - `low`: cosine + BM25 fusion only. No reranker. Mobile default.
5186
- * - `mid`: + recency boost. Demo default.
5187
- * - `high`: + cross-encoder rerank stage. Demo `budget: high` path.
5196
+ * - `low`: cosine + BM25 + recency. No reranker. Mobile default.
5197
+ * - `mid`: + cross-encoder rerank.
5198
+ * - `high`: + multi-hop graph traversal. LLM-free — query decomposition
5199
+ * (when wanted) lives in the tool/agent layer ({@link createRecallTool}),
5200
+ * not inside `recall()` itself (719/B4).
5188
5201
  */
5189
5202
  type Budget = "low" | "mid" | "high";
5190
5203
  interface ScoreBreakdown {
@@ -5284,10 +5297,28 @@ interface RecallOptions {
5284
5297
  /** Drop results below this score. Default: 0.1 for facts, 0.5 for chunks (mirrors today's defaults). */
5285
5298
  minScore?: number;
5286
5299
  /**
5287
- * Auth + endpoint for the LLM-based query decomposition pass. Without
5288
- * these, decompose is skipped even at `budget: 'high'`. Mirrors the
5289
- * shape used by `searchVaultMemories`. Auth is the dual pattern — one
5290
- * of `apiKey` / `getToken` is required; see {@link PortalLlmAuth}.
5300
+ * Pre-decomposed facet queries for the composite ranker. When ≥2 are
5301
+ * supplied, the vault lane runs `rankComposite` over them (no LLM call
5302
+ * inside `recall()` — 719/B4). Callers that still want LLM rewrite
5303
+ * (e.g. {@link createRecallTool} at `budget: 'high'`) call
5304
+ * `decomposeQuery` themselves and pass the result here. A single entry
5305
+ * (or omitting this) keeps the single-query path.
5306
+ */
5307
+ subQueries?: string[];
5308
+ /**
5309
+ * Auth + endpoint for optional LLM helpers that reuse portal auth —
5310
+ * currently {@link RecallOptions.graphRefine} neighbor selection.
5311
+ * Query decomposition is **not** driven by this field inside `recall()`
5312
+ * (719/B4); {@link createRecallTool} reads the same shape from
5313
+ * `RecallToolOptions.decomposeOptions` for tool-layer rewrite.
5314
+ *
5315
+ * Callers that still pass `{ budget: 'high', decomposeOptions }` without
5316
+ * {@link RecallOptions.subQueries} keep compiling but no longer rewrite —
5317
+ * `recall()` emits `decompose-moved` on {@link RecallDiagnostics.degraded}
5318
+ * so upgrades without a changelog read still leave a telemetry breadcrumb.
5319
+ *
5320
+ * Auth is the dual pattern — one of `apiKey` / `getToken` is required;
5321
+ * see {@link PortalLlmAuth}.
5291
5322
  */
5292
5323
  decomposeOptions?: PortalLlmAuth & {
5293
5324
  baseUrl?: string;
@@ -5391,9 +5422,22 @@ type RecallDegradation =
5391
5422
  /** Rerank was requested (budget mid/high) but the cross-encoder didn't run
5392
5423
  * this call — unavailable (e.g. React Native) or a transient failure. */
5393
5424
  "rerank-unavailable"
5394
- /** `budget: 'high'` requested but no `decomposeOptions`, so query
5395
- * decomposition was skipped and the budget downgraded to mid. */
5425
+ /**
5426
+ * @deprecated 719/B4 moved query decomposition out of `recall()`. Budget
5427
+ * `high` no longer requires `decomposeOptions` and this signal is never
5428
+ * emitted. Prefer `"decompose-moved"` for the upgrade breadcrumb. Kept in
5429
+ * the union so existing telemetry consumers stay typed.
5430
+ */
5396
5431
  | "decompose-unavailable"
5432
+ /**
5433
+ * `budget: 'high'` with `decomposeOptions` set but no usable
5434
+ * {@link RecallOptions.subQueries}. Pre-B4 this call shape triggered LLM
5435
+ * rewrite inside `recall()`; post-B4 it stays on the single-query high
5436
+ * path (rerank + graph) and callers must pass facets (or use
5437
+ * {@link createRecallTool}). Surfaced so silent quality regressions show
5438
+ * up in diagnostics instead of looking healthier than before.
5439
+ */
5440
+ | "decompose-moved"
5397
5441
  /** There was no usable cosine lane, so results were ranked on BM25 (lexical)
5398
5442
  * alone: either the query embedding failed (or came back empty), or no
5399
5443
  * candidate had a vector to score against it because the row (re)embed failed.
@@ -7463,9 +7507,14 @@ declare function rankProfileCandidates(inputs: readonly ProfileSalienceInput[],
7463
7507
  * Unified Recall API — single retrieval surface above vault + engine.
7464
7508
  *
7465
7509
  * Budget maps to pipeline depth (mirrors Hindsight's tiered defaults):
7466
- * - `low` → V2 cosine + BM25 + recency (no rerank, no decompose)
7510
+ * - `low` → V2 cosine + BM25 + recency (no rerank)
7467
7511
  * - `mid` → V2 + cross-encoder rerank
7468
- * - `high` → V2 + rerank + LLM query decomposition for composite queries
7512
+ * - `high` → V2 + rerank + multi-hop graph traversal
7513
+ *
7514
+ * LLM-free by design (719/B4): query decomposition for composite asks
7515
+ * lives in the tool/agent layer (`createRecallTool`), which may pass
7516
+ * pre-built `subQueries` into this API. `recall()` itself never pays an
7517
+ * LLM RTT.
7469
7518
  *
7470
7519
  * Fact + chunk lanes are fused with RRF (k=60). The previous naive
7471
7520
  * "sort the union by raw score" path is gone — score scales differ
@@ -7520,7 +7569,8 @@ interface RecallToolOptions {
7520
7569
  folderId?: string | null;
7521
7570
  /** Exclude one conversation from chunk results (typically the active one). */
7522
7571
  excludeConversationId?: string;
7523
- /** LLM-decompose options; only used at budget="high". Auth follows the
7572
+ /** LLM-decompose options; only used at budget="high". Runs in THIS tool
7573
+ * executor (719/B4) — `recall()` itself is LLM-free. Auth follows the
7524
7574
  * dual pattern: apiKey (server/CLI) or getToken (browser identity
7525
7575
  * tokens) — at least one required. */
7526
7576
  decomposeOptions?: PortalLlmAuth & {
@@ -8729,8 +8779,12 @@ declare function useChatStorage(options: UseChatStorageOptions): UseChatStorageR
8729
8779
  * `topics_updated_at` is a SECOND timestamp because every topic writer pins
8730
8780
  * `updated_at` on purpose (recall's recency multiplier) and both sync paths
8731
8781
  * key on `updated_at`, so a topic-only change would neither upload nor merge
8782
+ * - v43: Added a (is_deleted, created_at) index to conversations. Every
8783
+ * conversation list read filters is_deleted and orders by created_at DESC,
8784
+ * which previously meant a temp B-tree sort of the whole live set on every
8785
+ * read. Structural only — no column added, no data rewritten
8732
8786
  */
8733
- declare const SDK_SCHEMA_VERSION = 42;
8787
+ declare const SDK_SCHEMA_VERSION = 43;
8734
8788
  /**
8735
8789
  * Combined WatermelonDB schema for all SDK storage modules.
8736
8790
  *
@@ -8817,6 +8871,7 @@ declare const sdkSchema: Readonly<{
8817
8871
  * - v39 → v40: Added `fact_type`, `archived_at`, `trust_tier` columns to memory_vault for typed memory + decay + Tier-0 security (all nullable + plaintext, NULL backfill)
8818
8872
  * - v40 → v41: Added `visibility`, `twin_opt_in`, `published_at`, `geohash` columns to memory_vault for the People Nearby cross-user visibility axis (two-tier `private | public`; null/unknown grandfathered as 'private')
8819
8873
  * - v41 → v42: Added `topics` + `topics_updated_at` columns to memory_vault, making a memory's topics the durable synced record and `entity`/`memory_entity` a device-local index over it (null `topics` = pre-v42, backfilled from the row's current links by the sweep)
8874
+ * - v42 → v43: Added a composite `(is_deleted, created_at)` index to conversations so the list reads stop temp-sorting (structural only, no data rewritten)
8820
8875
  */
8821
8876
  declare const sdkMigrations: Readonly<{
8822
8877
  validated: true;
@@ -42,6 +42,7 @@ import {
42
42
  createVaultEmbeddingCache,
43
43
  createVaultMemoriesBatchOp,
44
44
  createVaultMemoryOp,
45
+ decomposeQuery,
45
46
  decryptField,
46
47
  decryptJsonField,
47
48
  deleteAllVaultMemoriesForUserOp,
@@ -119,7 +120,7 @@ import {
119
120
  upsertMessageOp,
120
121
  useEncryption,
121
122
  vaultMemoryToStored
122
- } from "./chunk-3CDSIN22.mjs";
123
+ } from "./chunk-SIMGZQ4P.mjs";
123
124
  import {
124
125
  PII_PATTERNS,
125
126
  PiiRedactor,
@@ -7814,7 +7815,7 @@ function createRecallTool(ctx, toolOptions, callbacks) {
7814
7815
  type: "function",
7815
7816
  function: {
7816
7817
  name: RECALL_TOOL_NAME,
7817
- description: "Search the user's memory across stored facts/preferences and past conversation excerpts. Returns a unified ranked list \u2014 facts carry an `id` you can reference; conversation excerpts carry a date and role. Use this whenever the user's question may relate to anything previously discussed or saved (preferences, prior decisions, past topics). Phrase the query naturally.",
7818
+ description: `Search the user's memory across stored facts/preferences and past conversation excerpts. Returns a unified ranked list \u2014 facts carry an \`id\` you can reference; conversation excerpts carry a date and role. Use this whenever the user's question may relate to anything previously discussed or saved (preferences, prior decisions, past topics). Phrase the query naturally. For multi-faceted / overview questions ("tell me about the user", "what's my tech stack"), prefer several targeted searches \u2014 one facet each (e.g. name, work, hobbies) \u2014 over a single broad query; fuse the results yourself.`,
7818
7819
  arguments: {
7819
7820
  type: "object",
7820
7821
  properties: {
@@ -7869,6 +7870,13 @@ function createRecallTool(ctx, toolOptions, callbacks) {
7869
7870
  let surfaced = 0;
7870
7871
  try {
7871
7872
  let recallDegraded = [];
7873
+ let subQueries;
7874
+ if (defaultBudget === "high" && toolOptions?.decomposeOptions) {
7875
+ const decomp = await decomposeQuery(query, toolOptions.decomposeOptions);
7876
+ if (decomp.mode === "composite" && decomp.subQueries.length >= 2) {
7877
+ subQueries = decomp.subQueries;
7878
+ }
7879
+ }
7872
7880
  const recallOpts = {
7873
7881
  onDiagnostics: (d) => {
7874
7882
  recallDegraded = d.degraded;
@@ -7882,10 +7890,12 @@ function createRecallTool(ctx, toolOptions, callbacks) {
7882
7890
  ...toolOptions?.excludeConversationId && {
7883
7891
  excludeConversationId: toolOptions.excludeConversationId
7884
7892
  },
7885
- ...toolOptions?.decomposeOptions && {
7886
- decomposeOptions: toolOptions.decomposeOptions
7887
- },
7888
- ...toolOptions?.now !== void 0 && { now: toolOptions.now }
7893
+ // Do NOT forward decomposeOptions into recall() — rewrite already
7894
+ // ran above, and forwarding would falsely trip `decompose-moved`
7895
+ // on every specific-mode high-budget call. graphRefine auth can
7896
+ // be threaded later when the tool opts into that path.
7897
+ ...toolOptions?.now !== void 0 && { now: toolOptions.now },
7898
+ ...subQueries && { subQueries }
7889
7899
  };
7890
7900
  const result = await recall(query, ctx, recallOpts);
7891
7901
  if (callbacks?.onChunksRetrieved) {
@@ -11970,7 +11980,7 @@ __decorateClass([
11970
11980
  ], VaultFolder.prototype, "context", 2);
11971
11981
 
11972
11982
  // src/lib/db/schema.ts
11973
- var SDK_SCHEMA_VERSION = 42;
11983
+ var SDK_SCHEMA_VERSION = 43;
11974
11984
  var sdkSchema = appSchema2({
11975
11985
  version: SDK_SCHEMA_VERSION,
11976
11986
  tables: [
@@ -12018,7 +12028,16 @@ var sdkSchema = appSchema2({
12018
12028
  { name: "conversation_id", type: "string", isIndexed: true },
12019
12029
  { name: "title", type: "string" },
12020
12030
  { name: "project_id", type: "string", isOptional: true, isIndexed: true },
12021
- { name: "created_at", type: "number" },
12031
+ // Indexed to match every other list-sorted created_at in this schema.
12032
+ // Note this single-column index is NOT what makes the conversation list
12033
+ // reads fast on native SQLite — they all filter is_deleted as well, and
12034
+ // SQLite will not combine the two indexes, so the composite created by
12035
+ // the v42 migration is what its planner actually uses. This declaration
12036
+ // still earns its keep on the other two adapters: LokiJS builds its
12037
+ // binary indices from `isIndexed` (and ignores `sql` migration steps
12038
+ // outright), and Postgres gathers statistics via autovacuum, so its
12039
+ // planner can use a single-column index the SQLite planner skips.
12040
+ { name: "created_at", type: "number", isIndexed: true },
12022
12041
  { name: "updated_at", type: "number" },
12023
12042
  { name: "is_deleted", type: "boolean", isIndexed: true },
12024
12043
  { name: "pinned_at", type: "number", isOptional: true }
@@ -12885,6 +12904,49 @@ var sdkMigrations = schemaMigrations2({
12885
12904
  ]
12886
12905
  })
12887
12906
  ]
12907
+ },
12908
+ // v42 -> v43: Give the conversation list reads an index they can actually
12909
+ // sort on. All five of them (getConversationsOp, getConversationsLazyOp,
12910
+ // getConversationsByProjectOp, getConversationsByProjectLazyOp and the
12911
+ // keyset getConversationsPageOp) emit `where is_deleted is 0 ... order by
12912
+ // created_at desc`, so before this they resolved is_deleted through its
12913
+ // index and then built a temp B-tree over every live row just to return
12914
+ // the newest page.
12915
+ //
12916
+ // The index is COMPOSITE, and that is the whole point — a bare index on
12917
+ // created_at does not fix this. SQLite will not combine two single-column
12918
+ // indexes here, so with `is_deleted` indexed and `created_at` indexed
12919
+ // separately it still picks conversations_is_deleted and still temp-sorts.
12920
+ // It only prefers a lone created_at index once ANALYZE has populated
12921
+ // sqlite_stat1, and nothing ever runs ANALYZE: not WatermelonDB, not its
12922
+ // native SQLite bindings, not this SDK. Every device is permanently in the
12923
+ // no-statistics state, so the index has to satisfy the filter and the sort
12924
+ // in one structure. `(is_deleted, created_at)` does: equality on the
12925
+ // leading column, ordered scan on the second, and the keyset boundary
12926
+ // becomes a range seek on the same index.
12927
+ //
12928
+ // unsafeExecuteSql, not addColumns: both columns already exist and
12929
+ // WatermelonDB has no add-index migration step, so raw SQL is the only way
12930
+ // to build one in place (same shape as the v19 -> v20 memory_vault
12931
+ // migration). IF NOT EXISTS keeps the step idempotent.
12932
+ //
12933
+ // Two limits worth knowing before someone re-measures this and finds it
12934
+ // missing. First, the LokiJS (web) adapter discards `sql` steps entirely,
12935
+ // so existing browser databases gain nothing — web's only lever is the
12936
+ // `isIndexed` flag on the column itself. Second, this index reaches
12937
+ // MIGRATED databases only: WatermelonDB builds a fresh database purely from
12938
+ // the encoded schema, and its schema format cannot express a composite
12939
+ // index. The one hook that could (`unsafeSql` on the table) is a function,
12940
+ // and the LokiJS adapter posts the schema to its worker, so attaching it
12941
+ // makes the whole schema fail structuredClone and takes the database down
12942
+ // at setup for anyone on `useWebWorker: true`. Not worth it for an index.
12943
+ {
12944
+ toVersion: 43,
12945
+ steps: [
12946
+ unsafeExecuteSql(
12947
+ "CREATE INDEX IF NOT EXISTS conversations_is_deleted_created_at ON conversations (is_deleted, created_at);"
12948
+ )
12949
+ ]
12888
12950
  }
12889
12951
  ]
12890
12952
  });
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  recall
3
- } from "./chunk-3CDSIN22.mjs";
3
+ } from "./chunk-SIMGZQ4P.mjs";
4
4
  import "./chunk-ILAWDQDE.mjs";
5
5
  import "./chunk-GYKJOWEO.mjs";
6
6
  export {