@anuma/sdk 6.29.2 → 6.30.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.
@@ -10200,6 +10200,9 @@ interface MemoryContextItem {
10200
10200
  sourceIds: string[];
10201
10201
  /** Ranked fact evidence, retained when profile deduplication wins placement. */
10202
10202
  recalled?: boolean;
10203
+ /** When the memory was written — lets the prompt show a saved date so the
10204
+ * model can answer "what did I save recently". */
10205
+ createdAt?: Date;
10203
10206
  }
10204
10207
  interface MemoryContextOptions {
10205
10208
  query: string;
@@ -20701,6 +20701,33 @@ async function getAllVaultMemoriesOp(ctx, options) {
20701
20701
  (raw) => vaultMemoryRawToStored(raw, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner)
20702
20702
  );
20703
20703
  }
20704
+ function vaultMemoryRawToRankable(raw) {
20705
+ return {
20706
+ uniqueId: raw.id,
20707
+ // @text coerces NULL→"" on the Model path; unsafeFetchRaw returns raw NULL, so guard.
20708
+ scope: raw.scope ?? "",
20709
+ folderId: raw.folder_id ?? null,
20710
+ embedding: raw.embedding ?? null,
20711
+ embeddingModel: raw.embedding_model ?? null,
20712
+ createdAt: new Date(raw.created_at),
20713
+ updatedAt: new Date(raw.updated_at),
20714
+ // Same mapping as vaultMemoryRawToStoredRaw. A consolidation rewrite moves
20715
+ // ONLY this column (preserveUpdatedAt pins updated_at), so a change-since
20716
+ // check that reads this projection needs it — see the type doc.
20717
+ lastObservedAt: raw.last_observed_at ?? null
20718
+ };
20719
+ }
20720
+ async function getVaultRankingProjectionsOp(ctx, options) {
20721
+ const conditions = [
20722
+ ...baseVaultConditions(ctx, options),
20723
+ ...options?.scopes?.length ? [import_watermelondb18.Q.where("scope", import_watermelondb18.Q.oneOf(options.scopes))] : [],
20724
+ ...options?.folderId !== void 0 ? [import_watermelondb18.Q.where("folder_id", options.folderId)] : [],
20725
+ import_watermelondb18.Q.sortBy(options?.since ? "updated_at" : "created_at", import_watermelondb18.Q.desc),
20726
+ ...options?.limit !== null && options?.limit !== void 0 && options.limit > 0 ? [import_watermelondb18.Q.take(options.limit)] : []
20727
+ ];
20728
+ const results = await ctx.vaultMemoryCollection.query(...conditions).unsafeFetchRaw();
20729
+ return results.map(vaultMemoryRawToRankable);
20730
+ }
20704
20731
  function baseVaultSql(ctx, options) {
20705
20732
  const clauses = [
20706
20733
  '"is_deleted" = 0',
@@ -29072,6 +29099,11 @@ function formatEventTime(start, end, kind) {
29072
29099
  if (kind === "ongoing") return `, event: since ${startDate}`;
29073
29100
  return `, event: ${startDate}`;
29074
29101
  }
29102
+ function formatSavedTime(createdAt) {
29103
+ const ms = createdAt?.getTime();
29104
+ if (ms === void 0 || !Number.isFinite(ms) || ms <= 0) return "";
29105
+ return `, saved: ${new Date(ms).toISOString().slice(0, 10)}`;
29106
+ }
29075
29107
  function generateFenceNonce() {
29076
29108
  const bytes = new Uint8Array(9);
29077
29109
  crypto.getRandomValues(bytes);
@@ -29090,8 +29122,9 @@ function formatRecallResult(memories) {
29090
29122
  const lines = memories.map((m, i) => {
29091
29123
  let body;
29092
29124
  if (m.kind === "fact") {
29125
+ const savedSuffix = formatSavedTime(m.createdAt);
29093
29126
  const eventSuffix = formatEventTime(m.eventTimeStart, m.eventTimeEnd, m.eventTimeKind);
29094
- body = `[${i + 1}] fact (id: ${m.id}${eventSuffix})
29127
+ body = `[${i + 1}] fact (id: ${m.id}${savedSuffix}${eventSuffix})
29095
29128
  ${m.content}`;
29096
29129
  } else {
29097
29130
  const date10 = m.createdAt.toISOString().slice(0, 10);
@@ -29141,6 +29174,40 @@ function isDumpQuery(query) {
29141
29174
  if (EVERYTHING_YOU_KNOW_RE.test(q)) return true;
29142
29175
  return false;
29143
29176
  }
29177
+ async function listRecentFacts(ctx, limit, toolOptions) {
29178
+ const vaultCtx = ctx.vaultCtx;
29179
+ if (!vaultCtx) return [];
29180
+ const ordered = await getVaultRankingProjectionsOp(vaultCtx, {
29181
+ ...toolOptions?.scopes && { scopes: toolOptions.scopes },
29182
+ ...toolOptions?.memoryIds !== void 0 && { memoryIds: toolOptions.memoryIds },
29183
+ ...toolOptions?.folderId !== void 0 && { folderId: toolOptions.folderId }
29184
+ });
29185
+ const readable = [];
29186
+ for (let i = 0; i < ordered.length && readable.length < limit; i += limit) {
29187
+ const batch = ordered.slice(i, i + limit).map((p) => p.uniqueId);
29188
+ const byId = new Map(
29189
+ (await getVaultMemoriesByIdsOp(vaultCtx, batch)).map((m) => [m.uniqueId, m])
29190
+ );
29191
+ for (const id of batch) {
29192
+ const m = byId.get(id);
29193
+ if (m && !isEncrypted(m.content)) readable.push(m);
29194
+ if (readable.length >= limit) break;
29195
+ }
29196
+ }
29197
+ return readable.map((m) => ({
29198
+ id: m.uniqueId,
29199
+ kind: "fact",
29200
+ content: m.content,
29201
+ score: 0,
29202
+ folderId: m.folderId,
29203
+ eventTimeStart: m.eventTimeStart,
29204
+ eventTimeEnd: m.eventTimeEnd,
29205
+ eventTimeKind: m.eventTimeKind,
29206
+ factType: m.factType,
29207
+ createdAt: m.createdAt,
29208
+ updatedAt: m.updatedAt
29209
+ }));
29210
+ }
29144
29211
  function createRecallTool(ctx, toolOptions, callbacks) {
29145
29212
  const defaultTypes = toolOptions?.types ?? ["fact", "chunk"];
29146
29213
  const defaultLimit = toolOptions?.limit ?? DEFAULT_LIMIT2;
@@ -29153,7 +29220,7 @@ function createRecallTool(ctx, toolOptions, callbacks) {
29153
29220
  type: "function",
29154
29221
  function: {
29155
29222
  name: RECALL_TOOL_NAME,
29156
- 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.`,
29223
+ 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 and the date they were saved; 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. For "what did I save recently / latest memories" questions, set `sort` to "recent" to get the newest saved facts, newest first, instead of a relevance search.',
29157
29224
  arguments: {
29158
29225
  type: "object",
29159
29226
  properties: {
@@ -29164,6 +29231,11 @@ function createRecallTool(ctx, toolOptions, callbacks) {
29164
29231
  limit: {
29165
29232
  type: "integer",
29166
29233
  description: `Max number of results. Default: ${defaultLimit}.`
29234
+ },
29235
+ sort: {
29236
+ type: "string",
29237
+ enum: ["relevance", "recent"],
29238
+ description: `"relevance" (default) ranks by match to the query. "recent" returns the newest saved facts, newest first, and ignores the query's topic.`
29167
29239
  }
29168
29240
  },
29169
29241
  required: ["query"]
@@ -29208,35 +29280,41 @@ function createRecallTool(ctx, toolOptions, callbacks) {
29208
29280
  let surfaced = 0;
29209
29281
  try {
29210
29282
  let recallDegraded = [];
29211
- let subQueries;
29212
- if (defaultBudget === "high" && toolOptions?.decomposeOptions) {
29213
- const decomp = await decomposeQuery(query, toolOptions.decomposeOptions);
29214
- if (decomp.mode === "composite" && decomp.subQueries.length >= 2) {
29215
- subQueries = decomp.subQueries;
29283
+ const wantsRecent = args.sort === "recent" && defaultTypes.includes("fact") && ctx.vaultCtx !== void 0;
29284
+ let result;
29285
+ if (wantsRecent) {
29286
+ result = { memories: await listRecentFacts(ctx, effectiveLimit, toolOptions) };
29287
+ } else {
29288
+ let subQueries;
29289
+ if (defaultBudget === "high" && toolOptions?.decomposeOptions) {
29290
+ const decomp = await decomposeQuery(query, toolOptions.decomposeOptions);
29291
+ if (decomp.mode === "composite" && decomp.subQueries.length >= 2) {
29292
+ subQueries = decomp.subQueries;
29293
+ }
29216
29294
  }
29295
+ const recallOpts = {
29296
+ onDiagnostics: (d) => {
29297
+ recallDegraded = d.degraded;
29298
+ },
29299
+ types: defaultTypes,
29300
+ limit: effectiveLimit,
29301
+ budget: defaultBudget,
29302
+ ...toolOptions?.minScore !== void 0 && { minScore: toolOptions.minScore },
29303
+ ...toolOptions?.scopes && { scopes: toolOptions.scopes },
29304
+ ...toolOptions?.memoryIds !== void 0 && { memoryIds: toolOptions.memoryIds },
29305
+ ...toolOptions?.folderId !== void 0 && { folderId: toolOptions.folderId },
29306
+ ...toolOptions?.excludeConversationId && {
29307
+ excludeConversationId: toolOptions.excludeConversationId
29308
+ },
29309
+ // Do NOT forward decomposeOptions into recall() — rewrite already
29310
+ // ran above, and forwarding would falsely trip `decompose-moved`
29311
+ // on every specific-mode high-budget call. graphRefine auth can
29312
+ // be threaded later when the tool opts into that path.
29313
+ ...toolOptions?.now !== void 0 && { now: toolOptions.now },
29314
+ ...subQueries && { subQueries }
29315
+ };
29316
+ result = await recall(query, ctx, recallOpts);
29217
29317
  }
29218
- const recallOpts = {
29219
- onDiagnostics: (d) => {
29220
- recallDegraded = d.degraded;
29221
- },
29222
- types: defaultTypes,
29223
- limit: effectiveLimit,
29224
- budget: defaultBudget,
29225
- ...toolOptions?.minScore !== void 0 && { minScore: toolOptions.minScore },
29226
- ...toolOptions?.scopes && { scopes: toolOptions.scopes },
29227
- ...toolOptions?.memoryIds !== void 0 && { memoryIds: toolOptions.memoryIds },
29228
- ...toolOptions?.folderId !== void 0 && { folderId: toolOptions.folderId },
29229
- ...toolOptions?.excludeConversationId && {
29230
- excludeConversationId: toolOptions.excludeConversationId
29231
- },
29232
- // Do NOT forward decomposeOptions into recall() — rewrite already
29233
- // ran above, and forwarding would falsely trip `decompose-moved`
29234
- // on every specific-mode high-budget call. graphRefine auth can
29235
- // be threaded later when the tool opts into that path.
29236
- ...toolOptions?.now !== void 0 && { now: toolOptions.now },
29237
- ...subQueries && { subQueries }
29238
- };
29239
- const result = await recall(query, ctx, recallOpts);
29240
29318
  if (callbacks?.onChunksRetrieved) {
29241
29319
  const convIds = Array.from(
29242
29320
  new Set(
@@ -29249,7 +29327,7 @@ function createRecallTool(ctx, toolOptions, callbacks) {
29249
29327
  const facts = result.memories.filter((m) => m.kind === "fact").map((m) => ({ id: m.id, score: m.score }));
29250
29328
  if (facts.length > 0) {
29251
29329
  callbacks.onFactsRetrieved?.(facts.map((f) => f.id));
29252
- callbacks.onFactsRanked?.(facts);
29330
+ if (!wantsRecent) callbacks.onFactsRanked?.(facts);
29253
29331
  }
29254
29332
  }
29255
29333
  surfaced = result.memories.length;
@@ -30418,6 +30496,10 @@ function createMessageSourceResolver(ctx) {
30418
30496
  }
30419
30497
 
30420
30498
  // src/lib/memory/context.ts
30499
+ function savedAt(createdAt) {
30500
+ const ms = createdAt?.getTime();
30501
+ return ms !== void 0 && Number.isFinite(ms) && ms > 0 ? { createdAt } : {};
30502
+ }
30421
30503
  function shouldRecallMemory(query) {
30422
30504
  const normalized = query.trim().toLowerCase().replace(/[.!?。!?¡¿]+$/u, "").replace(/^[¡¿]+/u, "");
30423
30505
  return normalized.length > 0 && !/^(hi|hi there|hello|hey|yes|no|yep|nope|sure|k|ok|okay|thanks|thank you|thank you very much|thanks a lot|cool|great|got it|sounds good|hola|buenos días|buenas|sí|si|gracias|muchas gracias|vale|bonjour|salut|oui|non|merci|merci beaucoup|d'accord|hallo|guten tag|ja|nein|danke|danke schön|alles klar|olá|obrigado|obrigada|ciao|grazie|sì|你好|谢谢|好的|こんにちは|ありがとう|안녕하세요|감사합니다)$/u.test(
@@ -30455,7 +30537,8 @@ async function assembleMemoryContext(options) {
30455
30537
  content: m.content,
30456
30538
  score,
30457
30539
  lane,
30458
- sourceIds: m.sourceChunkIds ?? []
30540
+ sourceIds: m.sourceChunkIds ?? [],
30541
+ ...savedAt(m.createdAt)
30459
30542
  });
30460
30543
  const empty = {
30461
30544
  memories: [],
@@ -30520,7 +30603,8 @@ async function assembleMemoryContext(options) {
30520
30603
  score: m.score,
30521
30604
  lane,
30522
30605
  ...lane === "fact" && { recalled: true },
30523
- sourceIds: m.sourceChunkIds ?? (m.messageId ? [m.messageId] : [])
30606
+ sourceIds: m.sourceChunkIds ?? (m.messageId ? [m.messageId] : []),
30607
+ ...savedAt(m.createdAt)
30524
30608
  }));
30525
30609
  const recalledFacts = toItems(facts, "fact");
30526
30610
  const recalledScores = /* @__PURE__ */ new Map();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anuma/sdk",
3
- "version": "6.29.2",
3
+ "version": "6.30.0",
4
4
  "description": "",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",