@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.
@@ -6282,15 +6282,26 @@ interface MemoryVaultSearchOptions {
6282
6282
  /** RRF smoothing constant for lane fusion. Default 60. */
6283
6283
  rrfK?: number;
6284
6284
  /**
6285
- * LLM-based query decomposition for composite/abstract queries. When set,
6286
- * each query is classified + (if composite) decomposed into 3–5 facet
6287
- * sub-queries via gpt-5-mini, then ranked via {@link rankComposite}.
6288
- * Requires `decomposeOptions` (auth) when set to "llm".
6285
+ * Pre-decomposed facet queries for the composite ranker (719/B4). When
6286
+ * ≥2 are supplied (and embeddings are available), runs {@link rankComposite}
6287
+ * over them — no LLM call inside the search path. Callers that want LLM
6288
+ * rewrite (e.g. `createRecallTool`) call `decomposeQuery` first and pass
6289
+ * the facets here.
6290
+ */
6291
+ subQueries?: string[];
6292
+ /**
6293
+ * @deprecated 719/B4 — ignored by {@link searchVaultMemories} /
6294
+ * {@link searchVaultMemoriesWithSize}. Pass {@link MemoryVaultSearchOptions.subQueries}
6295
+ * (or use `createRecallTool`). The legacy {@link createMemoryVaultSearchTool}
6296
+ * executor still honors `decompose: "llm"` + `decomposeOptions` for eval
6297
+ * parity, then forwards facets into the LLM-free search path.
6289
6298
  */
6290
6299
  decompose?: "off" | "llm";
6291
- /** Auth + endpoint for the decomposition LLM call. Required when
6292
- * decompose="llm". Auth is the dual pattern — one of `apiKey` /
6293
- * `getToken`; see {@link PortalLlmAuth}. */
6300
+ /**
6301
+ * @deprecated 719/B4 — see `decompose`. Ignored on the programmatic search
6302
+ * path; tool-layer rewrite still reads this from the search-tool options.
6303
+ * Prefer `RecallToolOptions.decomposeOptions` with `createRecallTool`.
6304
+ */
6294
6305
  decomposeOptions?: PortalLlmAuth & {
6295
6306
  baseUrl?: string;
6296
6307
  model?: string;
@@ -6455,9 +6466,11 @@ type MemoryKind = "fact" | "chunk";
6455
6466
  * Budget controls retrieval depth/cost. Higher budgets enable more
6456
6467
  * candidate sources and the cross-encoder reranker.
6457
6468
  *
6458
- * - `low`: cosine + BM25 fusion only. No reranker. Mobile default.
6459
- * - `mid`: + recency boost. Demo default.
6460
- * - `high`: + cross-encoder rerank stage. Demo `budget: high` path.
6469
+ * - `low`: cosine + BM25 + recency. No reranker. Mobile default.
6470
+ * - `mid`: + cross-encoder rerank.
6471
+ * - `high`: + multi-hop graph traversal. LLM-free — query decomposition
6472
+ * (when wanted) lives in the tool/agent layer ({@link createRecallTool}),
6473
+ * not inside `recall()` itself (719/B4).
6461
6474
  */
6462
6475
  type Budget = "low" | "mid" | "high";
6463
6476
  interface ScoreBreakdown {
@@ -6557,10 +6570,28 @@ interface RecallOptions {
6557
6570
  /** Drop results below this score. Default: 0.1 for facts, 0.5 for chunks (mirrors today's defaults). */
6558
6571
  minScore?: number;
6559
6572
  /**
6560
- * Auth + endpoint for the LLM-based query decomposition pass. Without
6561
- * these, decompose is skipped even at `budget: 'high'`. Mirrors the
6562
- * shape used by `searchVaultMemories`. Auth is the dual pattern — one
6563
- * of `apiKey` / `getToken` is required; see {@link PortalLlmAuth}.
6573
+ * Pre-decomposed facet queries for the composite ranker. When ≥2 are
6574
+ * supplied, the vault lane runs `rankComposite` over them (no LLM call
6575
+ * inside `recall()` — 719/B4). Callers that still want LLM rewrite
6576
+ * (e.g. {@link createRecallTool} at `budget: 'high'`) call
6577
+ * `decomposeQuery` themselves and pass the result here. A single entry
6578
+ * (or omitting this) keeps the single-query path.
6579
+ */
6580
+ subQueries?: string[];
6581
+ /**
6582
+ * Auth + endpoint for optional LLM helpers that reuse portal auth —
6583
+ * currently {@link RecallOptions.graphRefine} neighbor selection.
6584
+ * Query decomposition is **not** driven by this field inside `recall()`
6585
+ * (719/B4); {@link createRecallTool} reads the same shape from
6586
+ * `RecallToolOptions.decomposeOptions` for tool-layer rewrite.
6587
+ *
6588
+ * Callers that still pass `{ budget: 'high', decomposeOptions }` without
6589
+ * {@link RecallOptions.subQueries} keep compiling but no longer rewrite —
6590
+ * `recall()` emits `decompose-moved` on {@link RecallDiagnostics.degraded}
6591
+ * so upgrades without a changelog read still leave a telemetry breadcrumb.
6592
+ *
6593
+ * Auth is the dual pattern — one of `apiKey` / `getToken` is required;
6594
+ * see {@link PortalLlmAuth}.
6564
6595
  */
6565
6596
  decomposeOptions?: PortalLlmAuth & {
6566
6597
  baseUrl?: string;
@@ -6664,9 +6695,22 @@ type RecallDegradation =
6664
6695
  /** Rerank was requested (budget mid/high) but the cross-encoder didn't run
6665
6696
  * this call — unavailable (e.g. React Native) or a transient failure. */
6666
6697
  "rerank-unavailable"
6667
- /** `budget: 'high'` requested but no `decomposeOptions`, so query
6668
- * decomposition was skipped and the budget downgraded to mid. */
6698
+ /**
6699
+ * @deprecated 719/B4 moved query decomposition out of `recall()`. Budget
6700
+ * `high` no longer requires `decomposeOptions` and this signal is never
6701
+ * emitted. Prefer `"decompose-moved"` for the upgrade breadcrumb. Kept in
6702
+ * the union so existing telemetry consumers stay typed.
6703
+ */
6669
6704
  | "decompose-unavailable"
6705
+ /**
6706
+ * `budget: 'high'` with `decomposeOptions` set but no usable
6707
+ * {@link RecallOptions.subQueries}. Pre-B4 this call shape triggered LLM
6708
+ * rewrite inside `recall()`; post-B4 it stays on the single-query high
6709
+ * path (rerank + graph) and callers must pass facets (or use
6710
+ * {@link createRecallTool}). Surfaced so silent quality regressions show
6711
+ * up in diagnostics instead of looking healthier than before.
6712
+ */
6713
+ | "decompose-moved"
6670
6714
  /** There was no usable cosine lane, so results were ranked on BM25 (lexical)
6671
6715
  * alone: either the query embedding failed (or came back empty), or no
6672
6716
  * candidate had a vector to score against it because the row (re)embed failed.
@@ -8706,9 +8750,14 @@ declare function rankProfileCandidates(inputs: readonly ProfileSalienceInput[],
8706
8750
  * Unified Recall API — single retrieval surface above vault + engine.
8707
8751
  *
8708
8752
  * Budget maps to pipeline depth (mirrors Hindsight's tiered defaults):
8709
- * - `low` → V2 cosine + BM25 + recency (no rerank, no decompose)
8753
+ * - `low` → V2 cosine + BM25 + recency (no rerank)
8710
8754
  * - `mid` → V2 + cross-encoder rerank
8711
- * - `high` → V2 + rerank + LLM query decomposition for composite queries
8755
+ * - `high` → V2 + rerank + multi-hop graph traversal
8756
+ *
8757
+ * LLM-free by design (719/B4): query decomposition for composite asks
8758
+ * lives in the tool/agent layer (`createRecallTool`), which may pass
8759
+ * pre-built `subQueries` into this API. `recall()` itself never pays an
8760
+ * LLM RTT.
8712
8761
  *
8713
8762
  * Fact + chunk lanes are fused with RRF (k=60). The previous naive
8714
8763
  * "sort the union by raw score" path is gone — score scales differ
@@ -8763,7 +8812,8 @@ interface RecallToolOptions {
8763
8812
  folderId?: string | null;
8764
8813
  /** Exclude one conversation from chunk results (typically the active one). */
8765
8814
  excludeConversationId?: string;
8766
- /** LLM-decompose options; only used at budget="high". Auth follows the
8815
+ /** LLM-decompose options; only used at budget="high". Runs in THIS tool
8816
+ * executor (719/B4) — `recall()` itself is LLM-free. Auth follows the
8767
8817
  * dual pattern: apiKey (server/CLI) or getToken (browser identity
8768
8818
  * tokens) — at least one required. */
8769
8819
  decomposeOptions?: PortalLlmAuth & {
@@ -10343,8 +10393,12 @@ declare class WatermelonChatStorageAdapter implements ChatStorageAdapter {
10343
10393
  * `topics_updated_at` is a SECOND timestamp because every topic writer pins
10344
10394
  * `updated_at` on purpose (recall's recency multiplier) and both sync paths
10345
10395
  * key on `updated_at`, so a topic-only change would neither upload nor merge
10396
+ * - v43: Added a (is_deleted, created_at) index to conversations. Every
10397
+ * conversation list read filters is_deleted and orders by created_at DESC,
10398
+ * which previously meant a temp B-tree sort of the whole live set on every
10399
+ * read. Structural only — no column added, no data rewritten
10346
10400
  */
10347
- declare const SDK_SCHEMA_VERSION = 42;
10401
+ declare const SDK_SCHEMA_VERSION = 43;
10348
10402
  /**
10349
10403
  * Combined WatermelonDB schema for all SDK storage modules.
10350
10404
  *
@@ -10431,6 +10485,7 @@ declare const sdkSchema: Readonly<{
10431
10485
  * - 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)
10432
10486
  * - 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')
10433
10487
  * - 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)
10488
+ * - v42 → v43: Added a composite `(is_deleted, created_at)` index to conversations so the list reads stop temp-sorting (structural only, no data rewritten)
10434
10489
  */
10435
10490
  declare const sdkMigrations: Readonly<{
10436
10491
  validated: true;
@@ -47,6 +47,7 @@ import {
47
47
  createVaultEmbeddingCache,
48
48
  createVaultMemoriesBatchOp,
49
49
  createVaultMemoryOp,
50
+ decomposeQuery,
50
51
  decryptData,
51
52
  decryptDataBatch,
52
53
  decryptDataBytes,
@@ -164,7 +165,7 @@ import {
164
165
  updateVaultMemoryOp,
165
166
  useEncryption,
166
167
  vaultMemoryToStored
167
- } from "./chunk-CUJDSH55.mjs";
168
+ } from "./chunk-KTBOHRRE.mjs";
168
169
  import {
169
170
  PiiRedactor,
170
171
  createStreamingDeAnonymizer,
@@ -8631,7 +8632,7 @@ function createRecallTool(ctx, toolOptions, callbacks) {
8631
8632
  type: "function",
8632
8633
  function: {
8633
8634
  name: RECALL_TOOL_NAME,
8634
- 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.",
8635
+ 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.`,
8635
8636
  arguments: {
8636
8637
  type: "object",
8637
8638
  properties: {
@@ -8686,6 +8687,13 @@ function createRecallTool(ctx, toolOptions, callbacks) {
8686
8687
  let surfaced = 0;
8687
8688
  try {
8688
8689
  let recallDegraded = [];
8690
+ let subQueries;
8691
+ if (defaultBudget === "high" && toolOptions?.decomposeOptions) {
8692
+ const decomp = await decomposeQuery(query, toolOptions.decomposeOptions);
8693
+ if (decomp.mode === "composite" && decomp.subQueries.length >= 2) {
8694
+ subQueries = decomp.subQueries;
8695
+ }
8696
+ }
8689
8697
  const recallOpts = {
8690
8698
  onDiagnostics: (d) => {
8691
8699
  recallDegraded = d.degraded;
@@ -8699,10 +8707,12 @@ function createRecallTool(ctx, toolOptions, callbacks) {
8699
8707
  ...toolOptions?.excludeConversationId && {
8700
8708
  excludeConversationId: toolOptions.excludeConversationId
8701
8709
  },
8702
- ...toolOptions?.decomposeOptions && {
8703
- decomposeOptions: toolOptions.decomposeOptions
8704
- },
8705
- ...toolOptions?.now !== void 0 && { now: toolOptions.now }
8710
+ // Do NOT forward decomposeOptions into recall() — rewrite already
8711
+ // ran above, and forwarding would falsely trip `decompose-moved`
8712
+ // on every specific-mode high-budget call. graphRefine auth can
8713
+ // be threaded later when the tool opts into that path.
8714
+ ...toolOptions?.now !== void 0 && { now: toolOptions.now },
8715
+ ...subQueries && { subQueries }
8706
8716
  };
8707
8717
  const result = await recall(query, ctx, recallOpts);
8708
8718
  if (callbacks?.onChunksRetrieved) {
@@ -14054,7 +14064,7 @@ __decorateClass([
14054
14064
  ], VaultFolder.prototype, "context", 2);
14055
14065
 
14056
14066
  // src/lib/db/schema.ts
14057
- var SDK_SCHEMA_VERSION = 42;
14067
+ var SDK_SCHEMA_VERSION = 43;
14058
14068
  var sdkSchema = appSchema2({
14059
14069
  version: SDK_SCHEMA_VERSION,
14060
14070
  tables: [
@@ -14102,7 +14112,16 @@ var sdkSchema = appSchema2({
14102
14112
  { name: "conversation_id", type: "string", isIndexed: true },
14103
14113
  { name: "title", type: "string" },
14104
14114
  { name: "project_id", type: "string", isOptional: true, isIndexed: true },
14105
- { name: "created_at", type: "number" },
14115
+ // Indexed to match every other list-sorted created_at in this schema.
14116
+ // Note this single-column index is NOT what makes the conversation list
14117
+ // reads fast on native SQLite — they all filter is_deleted as well, and
14118
+ // SQLite will not combine the two indexes, so the composite created by
14119
+ // the v42 migration is what its planner actually uses. This declaration
14120
+ // still earns its keep on the other two adapters: LokiJS builds its
14121
+ // binary indices from `isIndexed` (and ignores `sql` migration steps
14122
+ // outright), and Postgres gathers statistics via autovacuum, so its
14123
+ // planner can use a single-column index the SQLite planner skips.
14124
+ { name: "created_at", type: "number", isIndexed: true },
14106
14125
  { name: "updated_at", type: "number" },
14107
14126
  { name: "is_deleted", type: "boolean", isIndexed: true },
14108
14127
  { name: "pinned_at", type: "number", isOptional: true }
@@ -14969,6 +14988,49 @@ var sdkMigrations = schemaMigrations2({
14969
14988
  ]
14970
14989
  })
14971
14990
  ]
14991
+ },
14992
+ // v42 -> v43: Give the conversation list reads an index they can actually
14993
+ // sort on. All five of them (getConversationsOp, getConversationsLazyOp,
14994
+ // getConversationsByProjectOp, getConversationsByProjectLazyOp and the
14995
+ // keyset getConversationsPageOp) emit `where is_deleted is 0 ... order by
14996
+ // created_at desc`, so before this they resolved is_deleted through its
14997
+ // index and then built a temp B-tree over every live row just to return
14998
+ // the newest page.
14999
+ //
15000
+ // The index is COMPOSITE, and that is the whole point — a bare index on
15001
+ // created_at does not fix this. SQLite will not combine two single-column
15002
+ // indexes here, so with `is_deleted` indexed and `created_at` indexed
15003
+ // separately it still picks conversations_is_deleted and still temp-sorts.
15004
+ // It only prefers a lone created_at index once ANALYZE has populated
15005
+ // sqlite_stat1, and nothing ever runs ANALYZE: not WatermelonDB, not its
15006
+ // native SQLite bindings, not this SDK. Every device is permanently in the
15007
+ // no-statistics state, so the index has to satisfy the filter and the sort
15008
+ // in one structure. `(is_deleted, created_at)` does: equality on the
15009
+ // leading column, ordered scan on the second, and the keyset boundary
15010
+ // becomes a range seek on the same index.
15011
+ //
15012
+ // unsafeExecuteSql, not addColumns: both columns already exist and
15013
+ // WatermelonDB has no add-index migration step, so raw SQL is the only way
15014
+ // to build one in place (same shape as the v19 -> v20 memory_vault
15015
+ // migration). IF NOT EXISTS keeps the step idempotent.
15016
+ //
15017
+ // Two limits worth knowing before someone re-measures this and finds it
15018
+ // missing. First, the LokiJS (web) adapter discards `sql` steps entirely,
15019
+ // so existing browser databases gain nothing — web's only lever is the
15020
+ // `isIndexed` flag on the column itself. Second, this index reaches
15021
+ // MIGRATED databases only: WatermelonDB builds a fresh database purely from
15022
+ // the encoded schema, and its schema format cannot express a composite
15023
+ // index. The one hook that could (`unsafeSql` on the table) is a function,
15024
+ // and the LokiJS adapter posts the schema to its worker, so attaching it
15025
+ // makes the whole schema fail structuredClone and takes the database down
15026
+ // at setup for anyone on `useWebWorker: true`. Not worth it for an index.
15027
+ {
15028
+ toVersion: 43,
15029
+ steps: [
15030
+ unsafeExecuteSql(
15031
+ "CREATE INDEX IF NOT EXISTS conversations_is_deleted_created_at ON conversations (is_deleted, created_at);"
15032
+ )
15033
+ ]
14972
15034
  }
14973
15035
  ]
14974
15036
  });
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  recall
3
- } from "./chunk-CUJDSH55.mjs";
3
+ } from "./chunk-KTBOHRRE.mjs";
4
4
  import "./chunk-ILAWDQDE.mjs";
5
5
  import "./chunk-GYKJOWEO.mjs";
6
6
  export {