@anuma/sdk 6.8.1 → 6.9.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.
- package/dist/client/index.cjs +10 -0
- package/dist/client/index.d.mts +79 -1
- package/dist/client/index.d.ts +79 -1
- package/dist/client/index.mjs +9 -0
- package/dist/expo/{chunk-YO5UXOC4.mjs → chunk-4M56MKBV.mjs} +81 -48
- package/dist/expo/index.cjs +86 -49
- package/dist/expo/index.d.mts +44 -2
- package/dist/expo/index.d.ts +44 -2
- package/dist/expo/index.mjs +7 -3
- package/dist/expo/{recall-IEKVX4JL.mjs → recall-WIB7RQQZ.mjs} +1 -1
- package/dist/index.d.mts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/react/{chunk-POCLKALT.mjs → chunk-EJ65X4GI.mjs} +89 -50
- package/dist/react/index.cjs +94 -51
- package/dist/react/index.d.mts +44 -2
- package/dist/react/index.d.ts +44 -2
- package/dist/react/index.mjs +7 -3
- package/dist/react/{recall-B2AIAPCM.mjs → recall-FWILZSFH.mjs} +1 -1
- package/dist/server/{chunk-FS6DNUNT.mjs → chunk-I56324FR.mjs} +81 -48
- package/dist/server/index.cjs +86 -49
- package/dist/server/index.d.mts +44 -2
- package/dist/server/index.d.ts +44 -2
- package/dist/server/index.mjs +7 -3
- package/dist/server/{recall-2GAPE2XH.mjs → recall-T7CNXTQ3.mjs} +1 -1
- package/package.json +2 -2
package/dist/react/index.d.ts
CHANGED
|
@@ -5055,8 +5055,26 @@ interface StoredMessageWithSimilarity extends StoredMessage {
|
|
|
5055
5055
|
* A chunk of a message with its own embedding for fine-grained search
|
|
5056
5056
|
*/
|
|
5057
5057
|
interface MessageChunk {
|
|
5058
|
-
/**
|
|
5059
|
-
|
|
5058
|
+
/**
|
|
5059
|
+
* The chunk text — IN MEMORY ONLY. Not persisted (sdk#880).
|
|
5060
|
+
*
|
|
5061
|
+
* `chunkText` covers a message end to end with a 50-character overlap, so the
|
|
5062
|
+
* chunk set of any message over 400 characters reconstructs the whole thing.
|
|
5063
|
+
* Storing it put a fully readable copy of the message next to its own
|
|
5064
|
+
* ciphertext `content` on devices with at-rest encryption enabled.
|
|
5065
|
+
*
|
|
5066
|
+
* `updateMessageChunksOp` now strips this before writing and readers rebuild
|
|
5067
|
+
* the snippet from {@link startOffset}/{@link endOffset} against the message's
|
|
5068
|
+
* own (decrypted) `content` — so the text exists exactly once, under the
|
|
5069
|
+
* protection `content` already has. Encrypting the column instead was
|
|
5070
|
+
* rejected: the client reads this column raw and `JSON.parse`s it in four
|
|
5071
|
+
* places, each swallowing the throw and silently scoring 0.
|
|
5072
|
+
*
|
|
5073
|
+
* Optional because rows written before that change still carry it, and
|
|
5074
|
+
* readers prefer a stored value when present. It is populated in memory by
|
|
5075
|
+
* `chunkText()` and on the way out of a search.
|
|
5076
|
+
*/
|
|
5077
|
+
text?: string;
|
|
5060
5078
|
/**
|
|
5061
5079
|
* Embedding vector for this chunk.
|
|
5062
5080
|
*
|
|
@@ -8096,6 +8114,14 @@ declare function relinkMemoryTopicsOp(ctx: VaultMemoryOperationsContext, memoryI
|
|
|
8096
8114
|
* row has: a curated memory's topics are recorded as `user`, everything else as
|
|
8097
8115
|
* `auto`. Skips deleted, foreign-user, unlinked rows, and rows that already have
|
|
8098
8116
|
* a record. Returns the ids filled.
|
|
8117
|
+
*
|
|
8118
|
+
* Runs as ONE writer over the whole (caller-bounded) list, in two phases:
|
|
8119
|
+
* resolve every row's write — all the awaits — and only then prepare and batch,
|
|
8120
|
+
* synchronously. Resolving and preparing per row inside its own writer is what
|
|
8121
|
+
* fired WatermelonDB's "wasn't sent to batch() synchronously" diagnostic once
|
|
8122
|
+
* per memory (~107 on a single dev launch, sdk#891); it also cost N writes.
|
|
8123
|
+
* Same treatment as {@link stampTopicsExtractedAtOp}, including its
|
|
8124
|
+
* transpilation hazard — the prepare pass MUST stay a `.map()`.
|
|
8099
8125
|
*/
|
|
8100
8126
|
declare function backfillMemoryTopicsOp(ctx: VaultMemoryOperationsContext, memoryIds: readonly string[]): Promise<string[]>;
|
|
8101
8127
|
declare function deleteAllVaultMemoriesForUserOp(ctx: VaultMemoryOperationsContext, userId: string): Promise<number>;
|
|
@@ -8380,6 +8406,22 @@ interface TurnCompleteEvent {
|
|
|
8380
8406
|
* previously indistinguishable (both surfaced as zero candidates).
|
|
8381
8407
|
*/
|
|
8382
8408
|
outcome: ExtractOutcome;
|
|
8409
|
+
/**
|
|
8410
|
+
* Present only alongside `outcome: "empty-after-retry"` — WHICH failure ended
|
|
8411
|
+
* the turn (#888).
|
|
8412
|
+
*
|
|
8413
|
+
* `outcome` says extraction gave up; this says why, from a stable enum. The
|
|
8414
|
+
* distinction is the whole point: a 2026-08-11 audit measured ~63% of
|
|
8415
|
+
* production extraction turns ending in `empty-after-retry` and could not tell
|
|
8416
|
+
* from telemetry whether the cause was the freeloader 403 everyone assumed or
|
|
8417
|
+
* something else. It took a Prometheus cross-check to find the real one — the
|
|
8418
|
+
* portal returning HTTP 200 with an empty body, which it counts as a success.
|
|
8419
|
+
*
|
|
8420
|
+
* Forward `failure.reason` into your extraction analytics event; all three
|
|
8421
|
+
* fields are bounded (an enum, an HTTP status, a small attempt count) and none
|
|
8422
|
+
* carries content.
|
|
8423
|
+
*/
|
|
8424
|
+
failure?: PortalLlmFailure;
|
|
8383
8425
|
}
|
|
8384
8426
|
/** @public */
|
|
8385
8427
|
/**
|
package/dist/react/index.mjs
CHANGED
|
@@ -178,7 +178,7 @@ import {
|
|
|
178
178
|
updateVaultMemoryOp,
|
|
179
179
|
useEncryption,
|
|
180
180
|
vaultMemoryToStored
|
|
181
|
-
} from "./chunk-
|
|
181
|
+
} from "./chunk-EJ65X4GI.mjs";
|
|
182
182
|
import {
|
|
183
183
|
PiiRedactor,
|
|
184
184
|
createStreamingDeAnonymizer,
|
|
@@ -8357,7 +8357,7 @@ function createAutoExtractor(options) {
|
|
|
8357
8357
|
const t0 = Date.now();
|
|
8358
8358
|
void (async () => {
|
|
8359
8359
|
try {
|
|
8360
|
-
const { candidates, results, failedCount, outcome } = await extractAndRetain(
|
|
8360
|
+
const { candidates, results, failedCount, outcome, failure } = await extractAndRetain(
|
|
8361
8361
|
window2,
|
|
8362
8362
|
retainCtx,
|
|
8363
8363
|
{
|
|
@@ -8397,7 +8397,11 @@ function createAutoExtractor(options) {
|
|
|
8397
8397
|
failedCount,
|
|
8398
8398
|
durationMs: Date.now() - t0,
|
|
8399
8399
|
conversationId,
|
|
8400
|
-
outcome
|
|
8400
|
+
outcome,
|
|
8401
|
+
// Only set alongside `outcome: "empty-after-retry"`. Spread so the key
|
|
8402
|
+
// is absent rather than explicitly undefined on a healthy turn —
|
|
8403
|
+
// analytics backends store an explicit undefined as a real value.
|
|
8404
|
+
...failure !== void 0 && { failure }
|
|
8401
8405
|
});
|
|
8402
8406
|
} catch (err) {
|
|
8403
8407
|
options.onError?.(err instanceof Error ? err : new Error(String(err)), conversationId);
|
|
@@ -15964,9 +15964,10 @@ async function updateMessageChunksOp(ctx, uniqueId, chunks, embeddingModel) {
|
|
|
15964
15964
|
} catch {
|
|
15965
15965
|
return null;
|
|
15966
15966
|
}
|
|
15967
|
+
const storedChunks = chunks.map(({ text: _text, ...rest }) => rest);
|
|
15967
15968
|
await ctx.database.write(async () => {
|
|
15968
15969
|
await message.update((msg) => {
|
|
15969
|
-
msg._setRaw("chunks", JSON.stringify(
|
|
15970
|
+
msg._setRaw("chunks", JSON.stringify(storedChunks));
|
|
15970
15971
|
msg._setRaw("embedding_model", embeddingModel);
|
|
15971
15972
|
});
|
|
15972
15973
|
});
|
|
@@ -16061,6 +16062,16 @@ async function _updateMessageOp(ctx, uniqueId, opts) {
|
|
|
16061
16062
|
});
|
|
16062
16063
|
return messageToStored(message, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner);
|
|
16063
16064
|
}
|
|
16065
|
+
function resolveChunkText(content, chunk, chunksInRow) {
|
|
16066
|
+
if (!chunk) return content;
|
|
16067
|
+
if (typeof chunk.text === "string" && chunk.text.length > 0) return chunk.text;
|
|
16068
|
+
const { startOffset: start, endOffset: end } = chunk;
|
|
16069
|
+
if (!Number.isInteger(start) || !Number.isInteger(end)) return content;
|
|
16070
|
+
if (start < 0 || end <= start || end > content.length) return content;
|
|
16071
|
+
const covered = chunksInRow?.reduce((max, c) => Math.max(max, c.endOffset ?? 0), 0) ?? 0;
|
|
16072
|
+
if (covered !== content.length) return content;
|
|
16073
|
+
return content.slice(start, end);
|
|
16074
|
+
}
|
|
16064
16075
|
async function readJsonField(model, column, walletAddress) {
|
|
16065
16076
|
const raw = model._getRaw(column);
|
|
16066
16077
|
if (!raw) return void 0;
|
|
@@ -16174,7 +16185,7 @@ async function searchChunksOp(ctx, queryVector, options) {
|
|
|
16174
16185
|
candidates.push({
|
|
16175
16186
|
message,
|
|
16176
16187
|
similarity,
|
|
16177
|
-
chunkTextSource: { kind: "chunk",
|
|
16188
|
+
chunkTextSource: { kind: "chunk", chunk: chunks[ci], siblings: chunks }
|
|
16178
16189
|
});
|
|
16179
16190
|
}
|
|
16180
16191
|
}
|
|
@@ -16232,11 +16243,11 @@ async function searchChunksOp(ctx, queryVector, options) {
|
|
|
16232
16243
|
const stored = { ...storedById.get(message.id) };
|
|
16233
16244
|
let chunkText2;
|
|
16234
16245
|
if (chunkTextSource.kind === "chunk") {
|
|
16235
|
-
chunkText2 = chunkTextSource.
|
|
16246
|
+
chunkText2 = resolveChunkText(stored.content, chunkTextSource.chunk, chunkTextSource.siblings);
|
|
16236
16247
|
} else if (chunkTextSource.kind === "chunk-cached") {
|
|
16237
16248
|
const state = chunkStateByMsgId.get(message.id);
|
|
16238
16249
|
const chunk = state && state.version === chunkTextSource.version ? state.chunks[chunkTextSource.chunkIndex] : void 0;
|
|
16239
|
-
chunkText2 =
|
|
16250
|
+
chunkText2 = resolveChunkText(stored.content, chunk, state?.chunks);
|
|
16240
16251
|
} else {
|
|
16241
16252
|
chunkText2 = stored.content;
|
|
16242
16253
|
}
|
|
@@ -16380,13 +16391,13 @@ async function upsertEntitiesInWrite(ctx, entities) {
|
|
|
16380
16391
|
}
|
|
16381
16392
|
const unique = Array.from(kindByName.keys());
|
|
16382
16393
|
const out = /* @__PURE__ */ new Map();
|
|
16383
|
-
if (unique.length === 0) return { entities: out, operations: [] };
|
|
16394
|
+
if (unique.length === 0) return { entities: out, operations: [], kindBackfills: [] };
|
|
16384
16395
|
const existing = await ctx.entityCollection.query(import_watermelondb2.Q.where("canonical_name", import_watermelondb2.Q.oneOf(unique))).fetch();
|
|
16385
16396
|
const existingNames = new Set(existing.map((e) => e.canonicalName));
|
|
16386
|
-
const
|
|
16397
|
+
const kindBackfills = existing.filter((e) => {
|
|
16387
16398
|
const incoming = kindByName.get(e.canonicalName);
|
|
16388
16399
|
return incoming !== void 0 && (e.kind === null || e.kind === void 0 || e.kind === "");
|
|
16389
|
-
});
|
|
16400
|
+
}).map((record) => ({ record, kind: kindByName.get(record.canonicalName) }));
|
|
16390
16401
|
const missing = unique.filter((n) => !existingNames.has(n));
|
|
16391
16402
|
const created = missing.map(
|
|
16392
16403
|
(name) => ctx.entityCollection.prepareCreate((record) => {
|
|
@@ -16395,14 +16406,19 @@ async function upsertEntitiesInWrite(ctx, entities) {
|
|
|
16395
16406
|
if (kind !== void 0) record._setRaw("kind", kind);
|
|
16396
16407
|
})
|
|
16397
16408
|
);
|
|
16398
|
-
const updated = updates.map(
|
|
16399
|
-
(e) => e.prepareUpdate((record) => {
|
|
16400
|
-
record._setRaw("kind", kindByName.get(e.canonicalName));
|
|
16401
|
-
})
|
|
16402
|
-
);
|
|
16403
16409
|
for (const e of existing) out.set(e.canonicalName, entityToStored(e));
|
|
16404
16410
|
for (const record of created) out.set(record.canonicalName, entityToStored(record));
|
|
16405
|
-
|
|
16411
|
+
for (const { record, kind } of kindBackfills) {
|
|
16412
|
+
out.set(record.canonicalName, { ...entityToStored(record), kind });
|
|
16413
|
+
}
|
|
16414
|
+
return { entities: out, operations: created, kindBackfills };
|
|
16415
|
+
}
|
|
16416
|
+
function prepareKindBackfills(backfills) {
|
|
16417
|
+
return backfills.map(
|
|
16418
|
+
({ record, kind }) => record.prepareUpdate((r) => {
|
|
16419
|
+
r._setRaw("kind", kind);
|
|
16420
|
+
})
|
|
16421
|
+
);
|
|
16406
16422
|
}
|
|
16407
16423
|
async function findVaultRowInWrite(ctx, memoryId) {
|
|
16408
16424
|
try {
|
|
@@ -16452,10 +16468,12 @@ function topicsForEntities(entities, displayNames, source) {
|
|
|
16452
16468
|
function toEntityObjects(entityInputs) {
|
|
16453
16469
|
return entityInputs.map((e) => typeof e === "string" ? { name: e } : e);
|
|
16454
16470
|
}
|
|
16455
|
-
async function
|
|
16471
|
+
async function resolveMemoryTopicsWrite(ctx, memoryId, linked, inputs, source) {
|
|
16456
16472
|
const row = await findVaultRowInWrite(ctx, memoryId);
|
|
16457
|
-
|
|
16458
|
-
|
|
16473
|
+
return resolveTopicsWrite(row, linked, displayNamesOf(toEntityObjects(inputs)), source);
|
|
16474
|
+
}
|
|
16475
|
+
function prepareMemoryTopicsUpdate(write) {
|
|
16476
|
+
return prepareTopicsUpdate(write.row, write.topics, Date.now());
|
|
16459
16477
|
}
|
|
16460
16478
|
async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
|
|
16461
16479
|
if (entityInputs.length === 0) return [];
|
|
@@ -16464,15 +16482,16 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
|
|
|
16464
16482
|
let skipped = false;
|
|
16465
16483
|
let entities = [];
|
|
16466
16484
|
await ctx.database.write(async () => {
|
|
16467
|
-
const {
|
|
16468
|
-
|
|
16469
|
-
|
|
16470
|
-
|
|
16485
|
+
const {
|
|
16486
|
+
entities: byName,
|
|
16487
|
+
operations: entityOps,
|
|
16488
|
+
kindBackfills
|
|
16489
|
+
} = await upsertEntitiesInWrite(ctx, normalized);
|
|
16471
16490
|
entities = Array.from(byName.values());
|
|
16472
16491
|
const vaultRow = await findVaultRowInWrite(ctx, memoryId);
|
|
16473
16492
|
if (options?.unlessTopicsUserManaged && autoLinkBlocked(vaultRow)) {
|
|
16474
|
-
if (entityOps.length > 0) {
|
|
16475
|
-
await ctx.database.batch(...entityOps);
|
|
16493
|
+
if (entityOps.length > 0 || kindBackfills.length > 0) {
|
|
16494
|
+
await ctx.database.batch(...entityOps, ...prepareKindBackfills(kindBackfills));
|
|
16476
16495
|
}
|
|
16477
16496
|
skipped = true;
|
|
16478
16497
|
return;
|
|
@@ -16492,7 +16511,9 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
|
|
|
16492
16511
|
displayNamesOf(normalized),
|
|
16493
16512
|
options?.topicsSource ?? "auto"
|
|
16494
16513
|
);
|
|
16495
|
-
if (entityOps.length === 0 && toCreate.length === 0 && topicsWrite === null)
|
|
16514
|
+
if (entityOps.length === 0 && kindBackfills.length === 0 && toCreate.length === 0 && topicsWrite === null) {
|
|
16515
|
+
return;
|
|
16516
|
+
}
|
|
16496
16517
|
const linkOps = toCreate.map(
|
|
16497
16518
|
(e) => ctx.memoryEntityCollection.prepareCreate((record) => {
|
|
16498
16519
|
record._setRaw("memory_id", memoryId);
|
|
@@ -16502,6 +16523,7 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
|
|
|
16502
16523
|
);
|
|
16503
16524
|
await ctx.database.batch(
|
|
16504
16525
|
...entityOps,
|
|
16526
|
+
...prepareKindBackfills(kindBackfills),
|
|
16505
16527
|
...linkOps,
|
|
16506
16528
|
...topicsWrite ? [prepareTopicsUpdate(topicsWrite.row, topicsWrite.topics, Date.now())] : []
|
|
16507
16529
|
);
|
|
@@ -16539,10 +16561,11 @@ async function replaceMemoryEntities(ctx, memoryId, entityInputs, options) {
|
|
|
16539
16561
|
skipped = true;
|
|
16540
16562
|
return;
|
|
16541
16563
|
}
|
|
16542
|
-
const {
|
|
16543
|
-
|
|
16544
|
-
|
|
16545
|
-
|
|
16564
|
+
const {
|
|
16565
|
+
entities: byName,
|
|
16566
|
+
operations: entityOps,
|
|
16567
|
+
kindBackfills
|
|
16568
|
+
} = await upsertEntitiesInWrite(ctx, normalized);
|
|
16546
16569
|
entities = Array.from(byName.values());
|
|
16547
16570
|
const existingLinks = await ctx.memoryEntityCollection.query(import_watermelondb2.Q.where("memory_id", memoryId)).fetch();
|
|
16548
16571
|
const keep = new Set(entities.map((e) => e.uniqueId));
|
|
@@ -16555,12 +16578,13 @@ async function replaceMemoryEntities(ctx, memoryId, entityInputs, options) {
|
|
|
16555
16578
|
displayNamesOf(normalized),
|
|
16556
16579
|
options.topicsSource
|
|
16557
16580
|
);
|
|
16558
|
-
if (entityOps.length === 0 && toCreate.length === 0 && toDestroy.length === 0 && topicsWrite === null) {
|
|
16581
|
+
if (entityOps.length === 0 && kindBackfills.length === 0 && toCreate.length === 0 && toDestroy.length === 0 && topicsWrite === null) {
|
|
16559
16582
|
return;
|
|
16560
16583
|
}
|
|
16561
16584
|
const orphans = await findOrphanedEntities(ctx, memoryId, toDestroy);
|
|
16562
16585
|
await ctx.database.batch(
|
|
16563
16586
|
...entityOps,
|
|
16587
|
+
...prepareKindBackfills(kindBackfills),
|
|
16564
16588
|
...toCreate.map(
|
|
16565
16589
|
(e) => ctx.memoryEntityCollection.prepareCreate((record) => {
|
|
16566
16590
|
record._setRaw("memory_id", memoryId);
|
|
@@ -17611,27 +17635,36 @@ async function backfillMemoryTopicsOp(ctx, memoryIds) {
|
|
|
17611
17635
|
if (!entityCtx) {
|
|
17612
17636
|
throw new Error("backfillMemoryTopicsOp requires ctx.entityCtx");
|
|
17613
17637
|
}
|
|
17638
|
+
const uniqueIds = Array.from(new Set(memoryIds));
|
|
17639
|
+
if (uniqueIds.length === 0) return [];
|
|
17614
17640
|
const filled = [];
|
|
17615
|
-
|
|
17616
|
-
|
|
17617
|
-
|
|
17618
|
-
record = await ctx.vaultMemoryCollection.find(id);
|
|
17619
|
-
} catch {
|
|
17620
|
-
continue;
|
|
17621
|
-
}
|
|
17622
|
-
if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) continue;
|
|
17623
|
-
if (parseTopics(record.topics) !== null) continue;
|
|
17624
|
-
const links = await entityCtx.memoryEntityCollection.query(import_watermelondb3.Q.where("memory_id", id)).unsafeFetchRaw();
|
|
17625
|
-
const entityIds = links.map((l) => String(l.entity_id));
|
|
17626
|
-
if (entityIds.length === 0) continue;
|
|
17627
|
-
const entities = await entityCtx.entityCollection.query(import_watermelondb3.Q.where("id", import_watermelondb3.Q.oneOf(entityIds))).fetch();
|
|
17628
|
-
if (entities.length === 0) continue;
|
|
17629
|
-
const source = record.topicsUserManaged ? "user" : "auto";
|
|
17641
|
+
const CHUNK = 500;
|
|
17642
|
+
for (let i = 0; i < uniqueIds.length; i += CHUNK) {
|
|
17643
|
+
const chunkIds = uniqueIds.slice(i, i + CHUNK);
|
|
17630
17644
|
await ctx.database.write(async () => {
|
|
17631
|
-
const
|
|
17632
|
-
|
|
17633
|
-
|
|
17634
|
-
|
|
17645
|
+
const writes = [];
|
|
17646
|
+
for (const id of chunkIds) {
|
|
17647
|
+
let record;
|
|
17648
|
+
try {
|
|
17649
|
+
record = await ctx.vaultMemoryCollection.find(id);
|
|
17650
|
+
} catch {
|
|
17651
|
+
continue;
|
|
17652
|
+
}
|
|
17653
|
+
if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) continue;
|
|
17654
|
+
if (parseTopics(record.topics) !== null) continue;
|
|
17655
|
+
const links = await entityCtx.memoryEntityCollection.query(import_watermelondb3.Q.where("memory_id", id)).unsafeFetchRaw();
|
|
17656
|
+
const entityIds = links.map((l) => String(l.entity_id));
|
|
17657
|
+
if (entityIds.length === 0) continue;
|
|
17658
|
+
const entities = await entityCtx.entityCollection.query(import_watermelondb3.Q.where("id", import_watermelondb3.Q.oneOf(entityIds))).fetch();
|
|
17659
|
+
if (entities.length === 0) continue;
|
|
17660
|
+
const source = record.topicsUserManaged ? "user" : "auto";
|
|
17661
|
+
const write = await resolveMemoryTopicsWrite(entityCtx, id, entities, [], source);
|
|
17662
|
+
if (write) writes.push(write);
|
|
17663
|
+
}
|
|
17664
|
+
if (writes.length === 0) return;
|
|
17665
|
+
const prepared = writes.map(prepareMemoryTopicsUpdate);
|
|
17666
|
+
await ctx.database.batch(...prepared);
|
|
17667
|
+
for (const write of writes) filled.push(write.row.id);
|
|
17635
17668
|
});
|
|
17636
17669
|
}
|
|
17637
17670
|
return filled;
|
|
@@ -20845,7 +20878,7 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
|
|
|
20845
20878
|
subQueries = decomp.subQueries;
|
|
20846
20879
|
}
|
|
20847
20880
|
}
|
|
20848
|
-
const { recall: recall2 } = await import("./recall-
|
|
20881
|
+
const { recall: recall2 } = await import("./recall-T7CNXTQ3.mjs");
|
|
20849
20882
|
let recallDegraded = [];
|
|
20850
20883
|
const result = await recall2(
|
|
20851
20884
|
query,
|
package/dist/server/index.cjs
CHANGED
|
@@ -16722,9 +16722,10 @@ async function updateMessageChunksOp(ctx, uniqueId, chunks, embeddingModel) {
|
|
|
16722
16722
|
} catch {
|
|
16723
16723
|
return null;
|
|
16724
16724
|
}
|
|
16725
|
+
const storedChunks = chunks.map(({ text: _text, ...rest }) => rest);
|
|
16725
16726
|
await ctx.database.write(async () => {
|
|
16726
16727
|
await message.update((msg) => {
|
|
16727
|
-
msg._setRaw("chunks", JSON.stringify(
|
|
16728
|
+
msg._setRaw("chunks", JSON.stringify(storedChunks));
|
|
16728
16729
|
msg._setRaw("embedding_model", embeddingModel);
|
|
16729
16730
|
});
|
|
16730
16731
|
});
|
|
@@ -16819,6 +16820,16 @@ async function _updateMessageOp(ctx, uniqueId, opts) {
|
|
|
16819
16820
|
});
|
|
16820
16821
|
return messageToStored(message, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner);
|
|
16821
16822
|
}
|
|
16823
|
+
function resolveChunkText(content, chunk, chunksInRow) {
|
|
16824
|
+
if (!chunk) return content;
|
|
16825
|
+
if (typeof chunk.text === "string" && chunk.text.length > 0) return chunk.text;
|
|
16826
|
+
const { startOffset: start, endOffset: end } = chunk;
|
|
16827
|
+
if (!Number.isInteger(start) || !Number.isInteger(end)) return content;
|
|
16828
|
+
if (start < 0 || end <= start || end > content.length) return content;
|
|
16829
|
+
const covered = chunksInRow?.reduce((max, c) => Math.max(max, c.endOffset ?? 0), 0) ?? 0;
|
|
16830
|
+
if (covered !== content.length) return content;
|
|
16831
|
+
return content.slice(start, end);
|
|
16832
|
+
}
|
|
16822
16833
|
async function readJsonField(model, column, walletAddress) {
|
|
16823
16834
|
const raw = model._getRaw(column);
|
|
16824
16835
|
if (!raw) return void 0;
|
|
@@ -16932,7 +16943,7 @@ async function searchChunksOp(ctx, queryVector, options) {
|
|
|
16932
16943
|
candidates.push({
|
|
16933
16944
|
message,
|
|
16934
16945
|
similarity,
|
|
16935
|
-
chunkTextSource: { kind: "chunk",
|
|
16946
|
+
chunkTextSource: { kind: "chunk", chunk: chunks[ci], siblings: chunks }
|
|
16936
16947
|
});
|
|
16937
16948
|
}
|
|
16938
16949
|
}
|
|
@@ -16990,11 +17001,11 @@ async function searchChunksOp(ctx, queryVector, options) {
|
|
|
16990
17001
|
const stored = { ...storedById.get(message.id) };
|
|
16991
17002
|
let chunkText2;
|
|
16992
17003
|
if (chunkTextSource.kind === "chunk") {
|
|
16993
|
-
chunkText2 = chunkTextSource.
|
|
17004
|
+
chunkText2 = resolveChunkText(stored.content, chunkTextSource.chunk, chunkTextSource.siblings);
|
|
16994
17005
|
} else if (chunkTextSource.kind === "chunk-cached") {
|
|
16995
17006
|
const state = chunkStateByMsgId.get(message.id);
|
|
16996
17007
|
const chunk = state && state.version === chunkTextSource.version ? state.chunks[chunkTextSource.chunkIndex] : void 0;
|
|
16997
|
-
chunkText2 =
|
|
17008
|
+
chunkText2 = resolveChunkText(stored.content, chunk, state?.chunks);
|
|
16998
17009
|
} else {
|
|
16999
17010
|
chunkText2 = stored.content;
|
|
17000
17011
|
}
|
|
@@ -17214,13 +17225,13 @@ async function upsertEntitiesInWrite(ctx, entities) {
|
|
|
17214
17225
|
}
|
|
17215
17226
|
const unique = Array.from(kindByName.keys());
|
|
17216
17227
|
const out = /* @__PURE__ */ new Map();
|
|
17217
|
-
if (unique.length === 0) return { entities: out, operations: [] };
|
|
17228
|
+
if (unique.length === 0) return { entities: out, operations: [], kindBackfills: [] };
|
|
17218
17229
|
const existing = await ctx.entityCollection.query(import_watermelondb16.Q.where("canonical_name", import_watermelondb16.Q.oneOf(unique))).fetch();
|
|
17219
17230
|
const existingNames = new Set(existing.map((e) => e.canonicalName));
|
|
17220
|
-
const
|
|
17231
|
+
const kindBackfills = existing.filter((e) => {
|
|
17221
17232
|
const incoming = kindByName.get(e.canonicalName);
|
|
17222
17233
|
return incoming !== void 0 && (e.kind === null || e.kind === void 0 || e.kind === "");
|
|
17223
|
-
});
|
|
17234
|
+
}).map((record) => ({ record, kind: kindByName.get(record.canonicalName) }));
|
|
17224
17235
|
const missing = unique.filter((n) => !existingNames.has(n));
|
|
17225
17236
|
const created = missing.map(
|
|
17226
17237
|
(name) => ctx.entityCollection.prepareCreate((record) => {
|
|
@@ -17229,14 +17240,19 @@ async function upsertEntitiesInWrite(ctx, entities) {
|
|
|
17229
17240
|
if (kind !== void 0) record._setRaw("kind", kind);
|
|
17230
17241
|
})
|
|
17231
17242
|
);
|
|
17232
|
-
const updated = updates.map(
|
|
17233
|
-
(e) => e.prepareUpdate((record) => {
|
|
17234
|
-
record._setRaw("kind", kindByName.get(e.canonicalName));
|
|
17235
|
-
})
|
|
17236
|
-
);
|
|
17237
17243
|
for (const e of existing) out.set(e.canonicalName, entityToStored(e));
|
|
17238
17244
|
for (const record of created) out.set(record.canonicalName, entityToStored(record));
|
|
17239
|
-
|
|
17245
|
+
for (const { record, kind } of kindBackfills) {
|
|
17246
|
+
out.set(record.canonicalName, { ...entityToStored(record), kind });
|
|
17247
|
+
}
|
|
17248
|
+
return { entities: out, operations: created, kindBackfills };
|
|
17249
|
+
}
|
|
17250
|
+
function prepareKindBackfills(backfills) {
|
|
17251
|
+
return backfills.map(
|
|
17252
|
+
({ record, kind }) => record.prepareUpdate((r) => {
|
|
17253
|
+
r._setRaw("kind", kind);
|
|
17254
|
+
})
|
|
17255
|
+
);
|
|
17240
17256
|
}
|
|
17241
17257
|
async function findVaultRowInWrite(ctx, memoryId) {
|
|
17242
17258
|
try {
|
|
@@ -17286,10 +17302,12 @@ function topicsForEntities(entities, displayNames, source) {
|
|
|
17286
17302
|
function toEntityObjects(entityInputs) {
|
|
17287
17303
|
return entityInputs.map((e) => typeof e === "string" ? { name: e } : e);
|
|
17288
17304
|
}
|
|
17289
|
-
async function
|
|
17305
|
+
async function resolveMemoryTopicsWrite(ctx, memoryId, linked, inputs, source) {
|
|
17290
17306
|
const row = await findVaultRowInWrite(ctx, memoryId);
|
|
17291
|
-
|
|
17292
|
-
|
|
17307
|
+
return resolveTopicsWrite(row, linked, displayNamesOf(toEntityObjects(inputs)), source);
|
|
17308
|
+
}
|
|
17309
|
+
function prepareMemoryTopicsUpdate(write) {
|
|
17310
|
+
return prepareTopicsUpdate(write.row, write.topics, Date.now());
|
|
17293
17311
|
}
|
|
17294
17312
|
async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
|
|
17295
17313
|
if (entityInputs.length === 0) return [];
|
|
@@ -17298,15 +17316,16 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
|
|
|
17298
17316
|
let skipped = false;
|
|
17299
17317
|
let entities = [];
|
|
17300
17318
|
await ctx.database.write(async () => {
|
|
17301
|
-
const {
|
|
17302
|
-
|
|
17303
|
-
|
|
17304
|
-
|
|
17319
|
+
const {
|
|
17320
|
+
entities: byName,
|
|
17321
|
+
operations: entityOps,
|
|
17322
|
+
kindBackfills
|
|
17323
|
+
} = await upsertEntitiesInWrite(ctx, normalized);
|
|
17305
17324
|
entities = Array.from(byName.values());
|
|
17306
17325
|
const vaultRow = await findVaultRowInWrite(ctx, memoryId);
|
|
17307
17326
|
if (options?.unlessTopicsUserManaged && autoLinkBlocked(vaultRow)) {
|
|
17308
|
-
if (entityOps.length > 0) {
|
|
17309
|
-
await ctx.database.batch(...entityOps);
|
|
17327
|
+
if (entityOps.length > 0 || kindBackfills.length > 0) {
|
|
17328
|
+
await ctx.database.batch(...entityOps, ...prepareKindBackfills(kindBackfills));
|
|
17310
17329
|
}
|
|
17311
17330
|
skipped = true;
|
|
17312
17331
|
return;
|
|
@@ -17326,7 +17345,9 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
|
|
|
17326
17345
|
displayNamesOf(normalized),
|
|
17327
17346
|
options?.topicsSource ?? "auto"
|
|
17328
17347
|
);
|
|
17329
|
-
if (entityOps.length === 0 && toCreate.length === 0 && topicsWrite === null)
|
|
17348
|
+
if (entityOps.length === 0 && kindBackfills.length === 0 && toCreate.length === 0 && topicsWrite === null) {
|
|
17349
|
+
return;
|
|
17350
|
+
}
|
|
17330
17351
|
const linkOps = toCreate.map(
|
|
17331
17352
|
(e) => ctx.memoryEntityCollection.prepareCreate((record) => {
|
|
17332
17353
|
record._setRaw("memory_id", memoryId);
|
|
@@ -17336,6 +17357,7 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
|
|
|
17336
17357
|
);
|
|
17337
17358
|
await ctx.database.batch(
|
|
17338
17359
|
...entityOps,
|
|
17360
|
+
...prepareKindBackfills(kindBackfills),
|
|
17339
17361
|
...linkOps,
|
|
17340
17362
|
...topicsWrite ? [prepareTopicsUpdate(topicsWrite.row, topicsWrite.topics, Date.now())] : []
|
|
17341
17363
|
);
|
|
@@ -17373,10 +17395,11 @@ async function replaceMemoryEntities(ctx, memoryId, entityInputs, options) {
|
|
|
17373
17395
|
skipped = true;
|
|
17374
17396
|
return;
|
|
17375
17397
|
}
|
|
17376
|
-
const {
|
|
17377
|
-
|
|
17378
|
-
|
|
17379
|
-
|
|
17398
|
+
const {
|
|
17399
|
+
entities: byName,
|
|
17400
|
+
operations: entityOps,
|
|
17401
|
+
kindBackfills
|
|
17402
|
+
} = await upsertEntitiesInWrite(ctx, normalized);
|
|
17380
17403
|
entities = Array.from(byName.values());
|
|
17381
17404
|
const existingLinks = await ctx.memoryEntityCollection.query(import_watermelondb16.Q.where("memory_id", memoryId)).fetch();
|
|
17382
17405
|
const keep = new Set(entities.map((e) => e.uniqueId));
|
|
@@ -17389,12 +17412,13 @@ async function replaceMemoryEntities(ctx, memoryId, entityInputs, options) {
|
|
|
17389
17412
|
displayNamesOf(normalized),
|
|
17390
17413
|
options.topicsSource
|
|
17391
17414
|
);
|
|
17392
|
-
if (entityOps.length === 0 && toCreate.length === 0 && toDestroy.length === 0 && topicsWrite === null) {
|
|
17415
|
+
if (entityOps.length === 0 && kindBackfills.length === 0 && toCreate.length === 0 && toDestroy.length === 0 && topicsWrite === null) {
|
|
17393
17416
|
return;
|
|
17394
17417
|
}
|
|
17395
17418
|
const orphans = await findOrphanedEntities(ctx, memoryId, toDestroy);
|
|
17396
17419
|
await ctx.database.batch(
|
|
17397
17420
|
...entityOps,
|
|
17421
|
+
...prepareKindBackfills(kindBackfills),
|
|
17398
17422
|
...toCreate.map(
|
|
17399
17423
|
(e) => ctx.memoryEntityCollection.prepareCreate((record) => {
|
|
17400
17424
|
record._setRaw("memory_id", memoryId);
|
|
@@ -18460,27 +18484,36 @@ async function backfillMemoryTopicsOp(ctx, memoryIds) {
|
|
|
18460
18484
|
if (!entityCtx) {
|
|
18461
18485
|
throw new Error("backfillMemoryTopicsOp requires ctx.entityCtx");
|
|
18462
18486
|
}
|
|
18487
|
+
const uniqueIds = Array.from(new Set(memoryIds));
|
|
18488
|
+
if (uniqueIds.length === 0) return [];
|
|
18463
18489
|
const filled = [];
|
|
18464
|
-
|
|
18465
|
-
|
|
18466
|
-
|
|
18467
|
-
record = await ctx.vaultMemoryCollection.find(id);
|
|
18468
|
-
} catch {
|
|
18469
|
-
continue;
|
|
18470
|
-
}
|
|
18471
|
-
if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) continue;
|
|
18472
|
-
if (parseTopics(record.topics) !== null) continue;
|
|
18473
|
-
const links = await entityCtx.memoryEntityCollection.query(import_watermelondb17.Q.where("memory_id", id)).unsafeFetchRaw();
|
|
18474
|
-
const entityIds = links.map((l) => String(l.entity_id));
|
|
18475
|
-
if (entityIds.length === 0) continue;
|
|
18476
|
-
const entities = await entityCtx.entityCollection.query(import_watermelondb17.Q.where("id", import_watermelondb17.Q.oneOf(entityIds))).fetch();
|
|
18477
|
-
if (entities.length === 0) continue;
|
|
18478
|
-
const source = record.topicsUserManaged ? "user" : "auto";
|
|
18490
|
+
const CHUNK = 500;
|
|
18491
|
+
for (let i = 0; i < uniqueIds.length; i += CHUNK) {
|
|
18492
|
+
const chunkIds = uniqueIds.slice(i, i + CHUNK);
|
|
18479
18493
|
await ctx.database.write(async () => {
|
|
18480
|
-
const
|
|
18481
|
-
|
|
18482
|
-
|
|
18483
|
-
|
|
18494
|
+
const writes = [];
|
|
18495
|
+
for (const id of chunkIds) {
|
|
18496
|
+
let record;
|
|
18497
|
+
try {
|
|
18498
|
+
record = await ctx.vaultMemoryCollection.find(id);
|
|
18499
|
+
} catch {
|
|
18500
|
+
continue;
|
|
18501
|
+
}
|
|
18502
|
+
if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) continue;
|
|
18503
|
+
if (parseTopics(record.topics) !== null) continue;
|
|
18504
|
+
const links = await entityCtx.memoryEntityCollection.query(import_watermelondb17.Q.where("memory_id", id)).unsafeFetchRaw();
|
|
18505
|
+
const entityIds = links.map((l) => String(l.entity_id));
|
|
18506
|
+
if (entityIds.length === 0) continue;
|
|
18507
|
+
const entities = await entityCtx.entityCollection.query(import_watermelondb17.Q.where("id", import_watermelondb17.Q.oneOf(entityIds))).fetch();
|
|
18508
|
+
if (entities.length === 0) continue;
|
|
18509
|
+
const source = record.topicsUserManaged ? "user" : "auto";
|
|
18510
|
+
const write = await resolveMemoryTopicsWrite(entityCtx, id, entities, [], source);
|
|
18511
|
+
if (write) writes.push(write);
|
|
18512
|
+
}
|
|
18513
|
+
if (writes.length === 0) return;
|
|
18514
|
+
const prepared = writes.map(prepareMemoryTopicsUpdate);
|
|
18515
|
+
await ctx.database.batch(...prepared);
|
|
18516
|
+
for (const write of writes) filled.push(write.row.id);
|
|
18484
18517
|
});
|
|
18485
18518
|
}
|
|
18486
18519
|
return filled;
|
|
@@ -30000,7 +30033,7 @@ function createAutoExtractor(options) {
|
|
|
30000
30033
|
const t0 = Date.now();
|
|
30001
30034
|
void (async () => {
|
|
30002
30035
|
try {
|
|
30003
|
-
const { candidates, results, failedCount, outcome } = await extractAndRetain(
|
|
30036
|
+
const { candidates, results, failedCount, outcome, failure } = await extractAndRetain(
|
|
30004
30037
|
window2,
|
|
30005
30038
|
retainCtx,
|
|
30006
30039
|
{
|
|
@@ -30040,7 +30073,11 @@ function createAutoExtractor(options) {
|
|
|
30040
30073
|
failedCount,
|
|
30041
30074
|
durationMs: Date.now() - t0,
|
|
30042
30075
|
conversationId,
|
|
30043
|
-
outcome
|
|
30076
|
+
outcome,
|
|
30077
|
+
// Only set alongside `outcome: "empty-after-retry"`. Spread so the key
|
|
30078
|
+
// is absent rather than explicitly undefined on a healthy turn —
|
|
30079
|
+
// analytics backends store an explicit undefined as a real value.
|
|
30080
|
+
...failure !== void 0 && { failure }
|
|
30044
30081
|
});
|
|
30045
30082
|
} catch (err) {
|
|
30046
30083
|
options.onError?.(err instanceof Error ? err : new Error(String(err)), conversationId);
|