@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
|
@@ -1891,9 +1891,10 @@ async function updateMessageChunksOp(ctx, uniqueId, chunks, embeddingModel) {
|
|
|
1891
1891
|
} catch {
|
|
1892
1892
|
return null;
|
|
1893
1893
|
}
|
|
1894
|
+
const storedChunks = chunks.map(({ text: _text, ...rest }) => rest);
|
|
1894
1895
|
await ctx.database.write(async () => {
|
|
1895
1896
|
await message.update((msg) => {
|
|
1896
|
-
msg._setRaw("chunks", JSON.stringify(
|
|
1897
|
+
msg._setRaw("chunks", JSON.stringify(storedChunks));
|
|
1897
1898
|
msg._setRaw("embedding_model", embeddingModel);
|
|
1898
1899
|
});
|
|
1899
1900
|
});
|
|
@@ -1991,6 +1992,16 @@ async function _updateMessageOp(ctx, uniqueId, opts) {
|
|
|
1991
1992
|
});
|
|
1992
1993
|
return messageToStored(message, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner);
|
|
1993
1994
|
}
|
|
1995
|
+
function resolveChunkText(content, chunk, chunksInRow) {
|
|
1996
|
+
if (!chunk) return content;
|
|
1997
|
+
if (typeof chunk.text === "string" && chunk.text.length > 0) return chunk.text;
|
|
1998
|
+
const { startOffset: start, endOffset: end } = chunk;
|
|
1999
|
+
if (!Number.isInteger(start) || !Number.isInteger(end)) return content;
|
|
2000
|
+
if (start < 0 || end <= start || end > content.length) return content;
|
|
2001
|
+
const covered = chunksInRow?.reduce((max, c) => Math.max(max, c.endOffset ?? 0), 0) ?? 0;
|
|
2002
|
+
if (covered !== content.length) return content;
|
|
2003
|
+
return content.slice(start, end);
|
|
2004
|
+
}
|
|
1994
2005
|
async function readJsonField(model, column, walletAddress) {
|
|
1995
2006
|
const raw = model._getRaw(column);
|
|
1996
2007
|
if (!raw) return void 0;
|
|
@@ -2104,7 +2115,7 @@ async function searchChunksOp(ctx, queryVector, options) {
|
|
|
2104
2115
|
candidates.push({
|
|
2105
2116
|
message,
|
|
2106
2117
|
similarity,
|
|
2107
|
-
chunkTextSource: { kind: "chunk",
|
|
2118
|
+
chunkTextSource: { kind: "chunk", chunk: chunks[ci], siblings: chunks }
|
|
2108
2119
|
});
|
|
2109
2120
|
}
|
|
2110
2121
|
}
|
|
@@ -2162,11 +2173,11 @@ async function searchChunksOp(ctx, queryVector, options) {
|
|
|
2162
2173
|
const stored = { ...storedById.get(message.id) };
|
|
2163
2174
|
let chunkText2;
|
|
2164
2175
|
if (chunkTextSource.kind === "chunk") {
|
|
2165
|
-
chunkText2 = chunkTextSource.
|
|
2176
|
+
chunkText2 = resolveChunkText(stored.content, chunkTextSource.chunk, chunkTextSource.siblings);
|
|
2166
2177
|
} else if (chunkTextSource.kind === "chunk-cached") {
|
|
2167
2178
|
const state = chunkStateByMsgId.get(message.id);
|
|
2168
2179
|
const chunk = state && state.version === chunkTextSource.version ? state.chunks[chunkTextSource.chunkIndex] : void 0;
|
|
2169
|
-
chunkText2 =
|
|
2180
|
+
chunkText2 = resolveChunkText(stored.content, chunk, state?.chunks);
|
|
2170
2181
|
} else {
|
|
2171
2182
|
chunkText2 = stored.content;
|
|
2172
2183
|
}
|
|
@@ -2310,13 +2321,13 @@ async function upsertEntitiesInWrite(ctx, entities) {
|
|
|
2310
2321
|
}
|
|
2311
2322
|
const unique = Array.from(kindByName.keys());
|
|
2312
2323
|
const out = /* @__PURE__ */ new Map();
|
|
2313
|
-
if (unique.length === 0) return { entities: out, operations: [] };
|
|
2324
|
+
if (unique.length === 0) return { entities: out, operations: [], kindBackfills: [] };
|
|
2314
2325
|
const existing = await ctx.entityCollection.query(Q2.where("canonical_name", Q2.oneOf(unique))).fetch();
|
|
2315
2326
|
const existingNames = new Set(existing.map((e) => e.canonicalName));
|
|
2316
|
-
const
|
|
2327
|
+
const kindBackfills = existing.filter((e) => {
|
|
2317
2328
|
const incoming = kindByName.get(e.canonicalName);
|
|
2318
2329
|
return incoming !== void 0 && (e.kind === null || e.kind === void 0 || e.kind === "");
|
|
2319
|
-
});
|
|
2330
|
+
}).map((record) => ({ record, kind: kindByName.get(record.canonicalName) }));
|
|
2320
2331
|
const missing = unique.filter((n) => !existingNames.has(n));
|
|
2321
2332
|
const created = missing.map(
|
|
2322
2333
|
(name) => ctx.entityCollection.prepareCreate((record) => {
|
|
@@ -2325,14 +2336,19 @@ async function upsertEntitiesInWrite(ctx, entities) {
|
|
|
2325
2336
|
if (kind !== void 0) record._setRaw("kind", kind);
|
|
2326
2337
|
})
|
|
2327
2338
|
);
|
|
2328
|
-
const updated = updates.map(
|
|
2329
|
-
(e) => e.prepareUpdate((record) => {
|
|
2330
|
-
record._setRaw("kind", kindByName.get(e.canonicalName));
|
|
2331
|
-
})
|
|
2332
|
-
);
|
|
2333
2339
|
for (const e of existing) out.set(e.canonicalName, entityToStored(e));
|
|
2334
2340
|
for (const record of created) out.set(record.canonicalName, entityToStored(record));
|
|
2335
|
-
|
|
2341
|
+
for (const { record, kind } of kindBackfills) {
|
|
2342
|
+
out.set(record.canonicalName, { ...entityToStored(record), kind });
|
|
2343
|
+
}
|
|
2344
|
+
return { entities: out, operations: created, kindBackfills };
|
|
2345
|
+
}
|
|
2346
|
+
function prepareKindBackfills(backfills) {
|
|
2347
|
+
return backfills.map(
|
|
2348
|
+
({ record, kind }) => record.prepareUpdate((r) => {
|
|
2349
|
+
r._setRaw("kind", kind);
|
|
2350
|
+
})
|
|
2351
|
+
);
|
|
2336
2352
|
}
|
|
2337
2353
|
async function findVaultRowInWrite(ctx, memoryId) {
|
|
2338
2354
|
try {
|
|
@@ -2382,10 +2398,12 @@ function topicsForEntities(entities, displayNames, source) {
|
|
|
2382
2398
|
function toEntityObjects(entityInputs) {
|
|
2383
2399
|
return entityInputs.map((e) => typeof e === "string" ? { name: e } : e);
|
|
2384
2400
|
}
|
|
2385
|
-
async function
|
|
2401
|
+
async function resolveMemoryTopicsWrite(ctx, memoryId, linked, inputs, source) {
|
|
2386
2402
|
const row = await findVaultRowInWrite(ctx, memoryId);
|
|
2387
|
-
|
|
2388
|
-
|
|
2403
|
+
return resolveTopicsWrite(row, linked, displayNamesOf(toEntityObjects(inputs)), source);
|
|
2404
|
+
}
|
|
2405
|
+
function prepareMemoryTopicsUpdate(write) {
|
|
2406
|
+
return prepareTopicsUpdate(write.row, write.topics, Date.now());
|
|
2389
2407
|
}
|
|
2390
2408
|
async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
|
|
2391
2409
|
if (entityInputs.length === 0) return [];
|
|
@@ -2394,15 +2412,16 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
|
|
|
2394
2412
|
let skipped = false;
|
|
2395
2413
|
let entities = [];
|
|
2396
2414
|
await ctx.database.write(async () => {
|
|
2397
|
-
const {
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2415
|
+
const {
|
|
2416
|
+
entities: byName,
|
|
2417
|
+
operations: entityOps,
|
|
2418
|
+
kindBackfills
|
|
2419
|
+
} = await upsertEntitiesInWrite(ctx, normalized);
|
|
2401
2420
|
entities = Array.from(byName.values());
|
|
2402
2421
|
const vaultRow = await findVaultRowInWrite(ctx, memoryId);
|
|
2403
2422
|
if (options?.unlessTopicsUserManaged && autoLinkBlocked(vaultRow)) {
|
|
2404
|
-
if (entityOps.length > 0) {
|
|
2405
|
-
await ctx.database.batch(...entityOps);
|
|
2423
|
+
if (entityOps.length > 0 || kindBackfills.length > 0) {
|
|
2424
|
+
await ctx.database.batch(...entityOps, ...prepareKindBackfills(kindBackfills));
|
|
2406
2425
|
}
|
|
2407
2426
|
skipped = true;
|
|
2408
2427
|
return;
|
|
@@ -2422,7 +2441,9 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
|
|
|
2422
2441
|
displayNamesOf(normalized),
|
|
2423
2442
|
options?.topicsSource ?? "auto"
|
|
2424
2443
|
);
|
|
2425
|
-
if (entityOps.length === 0 && toCreate.length === 0 && topicsWrite === null)
|
|
2444
|
+
if (entityOps.length === 0 && kindBackfills.length === 0 && toCreate.length === 0 && topicsWrite === null) {
|
|
2445
|
+
return;
|
|
2446
|
+
}
|
|
2426
2447
|
const linkOps = toCreate.map(
|
|
2427
2448
|
(e) => ctx.memoryEntityCollection.prepareCreate((record) => {
|
|
2428
2449
|
record._setRaw("memory_id", memoryId);
|
|
@@ -2432,6 +2453,7 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
|
|
|
2432
2453
|
);
|
|
2433
2454
|
await ctx.database.batch(
|
|
2434
2455
|
...entityOps,
|
|
2456
|
+
...prepareKindBackfills(kindBackfills),
|
|
2435
2457
|
...linkOps,
|
|
2436
2458
|
...topicsWrite ? [prepareTopicsUpdate(topicsWrite.row, topicsWrite.topics, Date.now())] : []
|
|
2437
2459
|
);
|
|
@@ -2469,10 +2491,11 @@ async function replaceMemoryEntities(ctx, memoryId, entityInputs, options) {
|
|
|
2469
2491
|
skipped = true;
|
|
2470
2492
|
return;
|
|
2471
2493
|
}
|
|
2472
|
-
const {
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2494
|
+
const {
|
|
2495
|
+
entities: byName,
|
|
2496
|
+
operations: entityOps,
|
|
2497
|
+
kindBackfills
|
|
2498
|
+
} = await upsertEntitiesInWrite(ctx, normalized);
|
|
2476
2499
|
entities = Array.from(byName.values());
|
|
2477
2500
|
const existingLinks = await ctx.memoryEntityCollection.query(Q2.where("memory_id", memoryId)).fetch();
|
|
2478
2501
|
const keep = new Set(entities.map((e) => e.uniqueId));
|
|
@@ -2485,12 +2508,13 @@ async function replaceMemoryEntities(ctx, memoryId, entityInputs, options) {
|
|
|
2485
2508
|
displayNamesOf(normalized),
|
|
2486
2509
|
options.topicsSource
|
|
2487
2510
|
);
|
|
2488
|
-
if (entityOps.length === 0 && toCreate.length === 0 && toDestroy.length === 0 && topicsWrite === null) {
|
|
2511
|
+
if (entityOps.length === 0 && kindBackfills.length === 0 && toCreate.length === 0 && toDestroy.length === 0 && topicsWrite === null) {
|
|
2489
2512
|
return;
|
|
2490
2513
|
}
|
|
2491
2514
|
const orphans = await findOrphanedEntities(ctx, memoryId, toDestroy);
|
|
2492
2515
|
await ctx.database.batch(
|
|
2493
2516
|
...entityOps,
|
|
2517
|
+
...prepareKindBackfills(kindBackfills),
|
|
2494
2518
|
...toCreate.map(
|
|
2495
2519
|
(e) => ctx.memoryEntityCollection.prepareCreate((record) => {
|
|
2496
2520
|
record._setRaw("memory_id", memoryId);
|
|
@@ -3303,10 +3327,16 @@ async function setMemoryEntitiesOp(ctx, memoryId, entities) {
|
|
|
3303
3327
|
const existing = await entityCtx.memoryEntityCollection.query(Q3.where("memory_id", memoryId)).fetch();
|
|
3304
3328
|
const staleLinks = existing.filter((l) => !keep.has(String(l.entityId)));
|
|
3305
3329
|
if (staleLinks.length === 0 && entities.length > 0) return;
|
|
3306
|
-
const
|
|
3330
|
+
const topicsWrite = await resolveMemoryTopicsWrite(
|
|
3331
|
+
entityCtx,
|
|
3332
|
+
memoryId,
|
|
3333
|
+
linked,
|
|
3334
|
+
entities,
|
|
3335
|
+
"user"
|
|
3336
|
+
);
|
|
3307
3337
|
await ctx.database.batch(
|
|
3308
3338
|
...staleLinks.map((l) => l.prepareDestroyPermanently()),
|
|
3309
|
-
...
|
|
3339
|
+
...topicsWrite ? [prepareMemoryTopicsUpdate(topicsWrite)] : []
|
|
3310
3340
|
);
|
|
3311
3341
|
});
|
|
3312
3342
|
if (stale) return null;
|
|
@@ -3625,27 +3655,36 @@ async function backfillMemoryTopicsOp(ctx, memoryIds) {
|
|
|
3625
3655
|
if (!entityCtx) {
|
|
3626
3656
|
throw new Error("backfillMemoryTopicsOp requires ctx.entityCtx");
|
|
3627
3657
|
}
|
|
3658
|
+
const uniqueIds = Array.from(new Set(memoryIds));
|
|
3659
|
+
if (uniqueIds.length === 0) return [];
|
|
3628
3660
|
const filled = [];
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
record = await ctx.vaultMemoryCollection.find(id);
|
|
3633
|
-
} catch {
|
|
3634
|
-
continue;
|
|
3635
|
-
}
|
|
3636
|
-
if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) continue;
|
|
3637
|
-
if (parseTopics(record.topics) !== null) continue;
|
|
3638
|
-
const links = await entityCtx.memoryEntityCollection.query(Q3.where("memory_id", id)).unsafeFetchRaw();
|
|
3639
|
-
const entityIds = links.map((l) => String(l.entity_id));
|
|
3640
|
-
if (entityIds.length === 0) continue;
|
|
3641
|
-
const entities = await entityCtx.entityCollection.query(Q3.where("id", Q3.oneOf(entityIds))).fetch();
|
|
3642
|
-
if (entities.length === 0) continue;
|
|
3643
|
-
const source = record.topicsUserManaged ? "user" : "auto";
|
|
3661
|
+
const CHUNK = 500;
|
|
3662
|
+
for (let i = 0; i < uniqueIds.length; i += CHUNK) {
|
|
3663
|
+
const chunkIds = uniqueIds.slice(i, i + CHUNK);
|
|
3644
3664
|
await ctx.database.write(async () => {
|
|
3645
|
-
const
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3665
|
+
const writes = [];
|
|
3666
|
+
for (const id of chunkIds) {
|
|
3667
|
+
let record;
|
|
3668
|
+
try {
|
|
3669
|
+
record = await ctx.vaultMemoryCollection.find(id);
|
|
3670
|
+
} catch {
|
|
3671
|
+
continue;
|
|
3672
|
+
}
|
|
3673
|
+
if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) continue;
|
|
3674
|
+
if (parseTopics(record.topics) !== null) continue;
|
|
3675
|
+
const links = await entityCtx.memoryEntityCollection.query(Q3.where("memory_id", id)).unsafeFetchRaw();
|
|
3676
|
+
const entityIds = links.map((l) => String(l.entity_id));
|
|
3677
|
+
if (entityIds.length === 0) continue;
|
|
3678
|
+
const entities = await entityCtx.entityCollection.query(Q3.where("id", Q3.oneOf(entityIds))).fetch();
|
|
3679
|
+
if (entities.length === 0) continue;
|
|
3680
|
+
const source = record.topicsUserManaged ? "user" : "auto";
|
|
3681
|
+
const write = await resolveMemoryTopicsWrite(entityCtx, id, entities, [], source);
|
|
3682
|
+
if (write) writes.push(write);
|
|
3683
|
+
}
|
|
3684
|
+
if (writes.length === 0) return;
|
|
3685
|
+
const prepared = writes.map(prepareMemoryTopicsUpdate);
|
|
3686
|
+
await ctx.database.batch(...prepared);
|
|
3687
|
+
for (const write of writes) filled.push(write.row.id);
|
|
3649
3688
|
});
|
|
3650
3689
|
}
|
|
3651
3690
|
return filled;
|
|
@@ -7085,7 +7124,7 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
|
|
|
7085
7124
|
subQueries = decomp.subQueries;
|
|
7086
7125
|
}
|
|
7087
7126
|
}
|
|
7088
|
-
const { recall: recall2 } = await import("./recall-
|
|
7127
|
+
const { recall: recall2 } = await import("./recall-FWILZSFH.mjs");
|
|
7089
7128
|
let recallDegraded = [];
|
|
7090
7129
|
const result = await recall2(
|
|
7091
7130
|
query,
|
package/dist/react/index.cjs
CHANGED
|
@@ -2338,9 +2338,10 @@ async function updateMessageChunksOp(ctx, uniqueId, chunks, embeddingModel) {
|
|
|
2338
2338
|
} catch {
|
|
2339
2339
|
return null;
|
|
2340
2340
|
}
|
|
2341
|
+
const storedChunks = chunks.map(({ text: _text, ...rest }) => rest);
|
|
2341
2342
|
await ctx.database.write(async () => {
|
|
2342
2343
|
await message.update((msg) => {
|
|
2343
|
-
msg._setRaw("chunks", JSON.stringify(
|
|
2344
|
+
msg._setRaw("chunks", JSON.stringify(storedChunks));
|
|
2344
2345
|
msg._setRaw("embedding_model", embeddingModel);
|
|
2345
2346
|
});
|
|
2346
2347
|
});
|
|
@@ -2438,6 +2439,16 @@ async function _updateMessageOp(ctx, uniqueId, opts) {
|
|
|
2438
2439
|
});
|
|
2439
2440
|
return messageToStored(message, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner);
|
|
2440
2441
|
}
|
|
2442
|
+
function resolveChunkText(content, chunk, chunksInRow) {
|
|
2443
|
+
if (!chunk) return content;
|
|
2444
|
+
if (typeof chunk.text === "string" && chunk.text.length > 0) return chunk.text;
|
|
2445
|
+
const { startOffset: start, endOffset: end } = chunk;
|
|
2446
|
+
if (!Number.isInteger(start) || !Number.isInteger(end)) return content;
|
|
2447
|
+
if (start < 0 || end <= start || end > content.length) return content;
|
|
2448
|
+
const covered = chunksInRow?.reduce((max, c) => Math.max(max, c.endOffset ?? 0), 0) ?? 0;
|
|
2449
|
+
if (covered !== content.length) return content;
|
|
2450
|
+
return content.slice(start, end);
|
|
2451
|
+
}
|
|
2441
2452
|
async function readJsonField(model, column, walletAddress) {
|
|
2442
2453
|
const raw = model._getRaw(column);
|
|
2443
2454
|
if (!raw) return void 0;
|
|
@@ -2551,7 +2562,7 @@ async function searchChunksOp(ctx, queryVector, options) {
|
|
|
2551
2562
|
candidates.push({
|
|
2552
2563
|
message,
|
|
2553
2564
|
similarity,
|
|
2554
|
-
chunkTextSource: { kind: "chunk",
|
|
2565
|
+
chunkTextSource: { kind: "chunk", chunk: chunks[ci], siblings: chunks }
|
|
2555
2566
|
});
|
|
2556
2567
|
}
|
|
2557
2568
|
}
|
|
@@ -2609,11 +2620,11 @@ async function searchChunksOp(ctx, queryVector, options) {
|
|
|
2609
2620
|
const stored = { ...storedById.get(message.id) };
|
|
2610
2621
|
let chunkText2;
|
|
2611
2622
|
if (chunkTextSource.kind === "chunk") {
|
|
2612
|
-
chunkText2 = chunkTextSource.
|
|
2623
|
+
chunkText2 = resolveChunkText(stored.content, chunkTextSource.chunk, chunkTextSource.siblings);
|
|
2613
2624
|
} else if (chunkTextSource.kind === "chunk-cached") {
|
|
2614
2625
|
const state = chunkStateByMsgId.get(message.id);
|
|
2615
2626
|
const chunk = state && state.version === chunkTextSource.version ? state.chunks[chunkTextSource.chunkIndex] : void 0;
|
|
2616
|
-
chunkText2 =
|
|
2627
|
+
chunkText2 = resolveChunkText(stored.content, chunk, state?.chunks);
|
|
2617
2628
|
} else {
|
|
2618
2629
|
chunkText2 = stored.content;
|
|
2619
2630
|
}
|
|
@@ -4877,13 +4888,13 @@ async function upsertEntitiesInWrite(ctx, entities) {
|
|
|
4877
4888
|
}
|
|
4878
4889
|
const unique = Array.from(kindByName.keys());
|
|
4879
4890
|
const out = /* @__PURE__ */ new Map();
|
|
4880
|
-
if (unique.length === 0) return { entities: out, operations: [] };
|
|
4891
|
+
if (unique.length === 0) return { entities: out, operations: [], kindBackfills: [] };
|
|
4881
4892
|
const existing = await ctx.entityCollection.query(import_watermelondb9.Q.where("canonical_name", import_watermelondb9.Q.oneOf(unique))).fetch();
|
|
4882
4893
|
const existingNames = new Set(existing.map((e) => e.canonicalName));
|
|
4883
|
-
const
|
|
4894
|
+
const kindBackfills = existing.filter((e) => {
|
|
4884
4895
|
const incoming = kindByName.get(e.canonicalName);
|
|
4885
4896
|
return incoming !== void 0 && (e.kind === null || e.kind === void 0 || e.kind === "");
|
|
4886
|
-
});
|
|
4897
|
+
}).map((record) => ({ record, kind: kindByName.get(record.canonicalName) }));
|
|
4887
4898
|
const missing = unique.filter((n) => !existingNames.has(n));
|
|
4888
4899
|
const created = missing.map(
|
|
4889
4900
|
(name) => ctx.entityCollection.prepareCreate((record) => {
|
|
@@ -4892,14 +4903,19 @@ async function upsertEntitiesInWrite(ctx, entities) {
|
|
|
4892
4903
|
if (kind !== void 0) record._setRaw("kind", kind);
|
|
4893
4904
|
})
|
|
4894
4905
|
);
|
|
4895
|
-
const updated = updates.map(
|
|
4896
|
-
(e) => e.prepareUpdate((record) => {
|
|
4897
|
-
record._setRaw("kind", kindByName.get(e.canonicalName));
|
|
4898
|
-
})
|
|
4899
|
-
);
|
|
4900
4906
|
for (const e of existing) out.set(e.canonicalName, entityToStored(e));
|
|
4901
4907
|
for (const record of created) out.set(record.canonicalName, entityToStored(record));
|
|
4902
|
-
|
|
4908
|
+
for (const { record, kind } of kindBackfills) {
|
|
4909
|
+
out.set(record.canonicalName, { ...entityToStored(record), kind });
|
|
4910
|
+
}
|
|
4911
|
+
return { entities: out, operations: created, kindBackfills };
|
|
4912
|
+
}
|
|
4913
|
+
function prepareKindBackfills(backfills) {
|
|
4914
|
+
return backfills.map(
|
|
4915
|
+
({ record, kind }) => record.prepareUpdate((r) => {
|
|
4916
|
+
r._setRaw("kind", kind);
|
|
4917
|
+
})
|
|
4918
|
+
);
|
|
4903
4919
|
}
|
|
4904
4920
|
async function findVaultRowInWrite(ctx, memoryId) {
|
|
4905
4921
|
try {
|
|
@@ -4949,10 +4965,12 @@ function topicsForEntities(entities, displayNames, source) {
|
|
|
4949
4965
|
function toEntityObjects(entityInputs) {
|
|
4950
4966
|
return entityInputs.map((e) => typeof e === "string" ? { name: e } : e);
|
|
4951
4967
|
}
|
|
4952
|
-
async function
|
|
4968
|
+
async function resolveMemoryTopicsWrite(ctx, memoryId, linked, inputs, source) {
|
|
4953
4969
|
const row = await findVaultRowInWrite(ctx, memoryId);
|
|
4954
|
-
|
|
4955
|
-
|
|
4970
|
+
return resolveTopicsWrite(row, linked, displayNamesOf(toEntityObjects(inputs)), source);
|
|
4971
|
+
}
|
|
4972
|
+
function prepareMemoryTopicsUpdate(write) {
|
|
4973
|
+
return prepareTopicsUpdate(write.row, write.topics, Date.now());
|
|
4956
4974
|
}
|
|
4957
4975
|
async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
|
|
4958
4976
|
if (entityInputs.length === 0) return [];
|
|
@@ -4961,15 +4979,16 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
|
|
|
4961
4979
|
let skipped = false;
|
|
4962
4980
|
let entities = [];
|
|
4963
4981
|
await ctx.database.write(async () => {
|
|
4964
|
-
const {
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4982
|
+
const {
|
|
4983
|
+
entities: byName,
|
|
4984
|
+
operations: entityOps,
|
|
4985
|
+
kindBackfills
|
|
4986
|
+
} = await upsertEntitiesInWrite(ctx, normalized);
|
|
4968
4987
|
entities = Array.from(byName.values());
|
|
4969
4988
|
const vaultRow = await findVaultRowInWrite(ctx, memoryId);
|
|
4970
4989
|
if (options?.unlessTopicsUserManaged && autoLinkBlocked(vaultRow)) {
|
|
4971
|
-
if (entityOps.length > 0) {
|
|
4972
|
-
await ctx.database.batch(...entityOps);
|
|
4990
|
+
if (entityOps.length > 0 || kindBackfills.length > 0) {
|
|
4991
|
+
await ctx.database.batch(...entityOps, ...prepareKindBackfills(kindBackfills));
|
|
4973
4992
|
}
|
|
4974
4993
|
skipped = true;
|
|
4975
4994
|
return;
|
|
@@ -4989,7 +5008,9 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
|
|
|
4989
5008
|
displayNamesOf(normalized),
|
|
4990
5009
|
options?.topicsSource ?? "auto"
|
|
4991
5010
|
);
|
|
4992
|
-
if (entityOps.length === 0 && toCreate.length === 0 && topicsWrite === null)
|
|
5011
|
+
if (entityOps.length === 0 && kindBackfills.length === 0 && toCreate.length === 0 && topicsWrite === null) {
|
|
5012
|
+
return;
|
|
5013
|
+
}
|
|
4993
5014
|
const linkOps = toCreate.map(
|
|
4994
5015
|
(e) => ctx.memoryEntityCollection.prepareCreate((record) => {
|
|
4995
5016
|
record._setRaw("memory_id", memoryId);
|
|
@@ -4999,6 +5020,7 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
|
|
|
4999
5020
|
);
|
|
5000
5021
|
await ctx.database.batch(
|
|
5001
5022
|
...entityOps,
|
|
5023
|
+
...prepareKindBackfills(kindBackfills),
|
|
5002
5024
|
...linkOps,
|
|
5003
5025
|
...topicsWrite ? [prepareTopicsUpdate(topicsWrite.row, topicsWrite.topics, Date.now())] : []
|
|
5004
5026
|
);
|
|
@@ -5036,10 +5058,11 @@ async function replaceMemoryEntities(ctx, memoryId, entityInputs, options) {
|
|
|
5036
5058
|
skipped = true;
|
|
5037
5059
|
return;
|
|
5038
5060
|
}
|
|
5039
|
-
const {
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5061
|
+
const {
|
|
5062
|
+
entities: byName,
|
|
5063
|
+
operations: entityOps,
|
|
5064
|
+
kindBackfills
|
|
5065
|
+
} = await upsertEntitiesInWrite(ctx, normalized);
|
|
5043
5066
|
entities = Array.from(byName.values());
|
|
5044
5067
|
const existingLinks = await ctx.memoryEntityCollection.query(import_watermelondb9.Q.where("memory_id", memoryId)).fetch();
|
|
5045
5068
|
const keep = new Set(entities.map((e) => e.uniqueId));
|
|
@@ -5052,12 +5075,13 @@ async function replaceMemoryEntities(ctx, memoryId, entityInputs, options) {
|
|
|
5052
5075
|
displayNamesOf(normalized),
|
|
5053
5076
|
options.topicsSource
|
|
5054
5077
|
);
|
|
5055
|
-
if (entityOps.length === 0 && toCreate.length === 0 && toDestroy.length === 0 && topicsWrite === null) {
|
|
5078
|
+
if (entityOps.length === 0 && kindBackfills.length === 0 && toCreate.length === 0 && toDestroy.length === 0 && topicsWrite === null) {
|
|
5056
5079
|
return;
|
|
5057
5080
|
}
|
|
5058
5081
|
const orphans = await findOrphanedEntities(ctx, memoryId, toDestroy);
|
|
5059
5082
|
await ctx.database.batch(
|
|
5060
5083
|
...entityOps,
|
|
5084
|
+
...prepareKindBackfills(kindBackfills),
|
|
5061
5085
|
...toCreate.map(
|
|
5062
5086
|
(e) => ctx.memoryEntityCollection.prepareCreate((record) => {
|
|
5063
5087
|
record._setRaw("memory_id", memoryId);
|
|
@@ -5886,10 +5910,16 @@ async function setMemoryEntitiesOp(ctx, memoryId, entities) {
|
|
|
5886
5910
|
const existing = await entityCtx.memoryEntityCollection.query(import_watermelondb10.Q.where("memory_id", memoryId)).fetch();
|
|
5887
5911
|
const staleLinks = existing.filter((l) => !keep.has(String(l.entityId)));
|
|
5888
5912
|
if (staleLinks.length === 0 && entities.length > 0) return;
|
|
5889
|
-
const
|
|
5913
|
+
const topicsWrite = await resolveMemoryTopicsWrite(
|
|
5914
|
+
entityCtx,
|
|
5915
|
+
memoryId,
|
|
5916
|
+
linked,
|
|
5917
|
+
entities,
|
|
5918
|
+
"user"
|
|
5919
|
+
);
|
|
5890
5920
|
await ctx.database.batch(
|
|
5891
5921
|
...staleLinks.map((l) => l.prepareDestroyPermanently()),
|
|
5892
|
-
...
|
|
5922
|
+
...topicsWrite ? [prepareMemoryTopicsUpdate(topicsWrite)] : []
|
|
5893
5923
|
);
|
|
5894
5924
|
});
|
|
5895
5925
|
if (stale) return null;
|
|
@@ -6207,27 +6237,36 @@ async function backfillMemoryTopicsOp(ctx, memoryIds) {
|
|
|
6207
6237
|
if (!entityCtx) {
|
|
6208
6238
|
throw new Error("backfillMemoryTopicsOp requires ctx.entityCtx");
|
|
6209
6239
|
}
|
|
6240
|
+
const uniqueIds = Array.from(new Set(memoryIds));
|
|
6241
|
+
if (uniqueIds.length === 0) return [];
|
|
6210
6242
|
const filled = [];
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
|
|
6214
|
-
record = await ctx.vaultMemoryCollection.find(id);
|
|
6215
|
-
} catch {
|
|
6216
|
-
continue;
|
|
6217
|
-
}
|
|
6218
|
-
if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) continue;
|
|
6219
|
-
if (parseTopics(record.topics) !== null) continue;
|
|
6220
|
-
const links = await entityCtx.memoryEntityCollection.query(import_watermelondb10.Q.where("memory_id", id)).unsafeFetchRaw();
|
|
6221
|
-
const entityIds = links.map((l) => String(l.entity_id));
|
|
6222
|
-
if (entityIds.length === 0) continue;
|
|
6223
|
-
const entities = await entityCtx.entityCollection.query(import_watermelondb10.Q.where("id", import_watermelondb10.Q.oneOf(entityIds))).fetch();
|
|
6224
|
-
if (entities.length === 0) continue;
|
|
6225
|
-
const source = record.topicsUserManaged ? "user" : "auto";
|
|
6243
|
+
const CHUNK = 500;
|
|
6244
|
+
for (let i = 0; i < uniqueIds.length; i += CHUNK) {
|
|
6245
|
+
const chunkIds = uniqueIds.slice(i, i + CHUNK);
|
|
6226
6246
|
await ctx.database.write(async () => {
|
|
6227
|
-
const
|
|
6228
|
-
|
|
6229
|
-
|
|
6230
|
-
|
|
6247
|
+
const writes = [];
|
|
6248
|
+
for (const id of chunkIds) {
|
|
6249
|
+
let record;
|
|
6250
|
+
try {
|
|
6251
|
+
record = await ctx.vaultMemoryCollection.find(id);
|
|
6252
|
+
} catch {
|
|
6253
|
+
continue;
|
|
6254
|
+
}
|
|
6255
|
+
if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) continue;
|
|
6256
|
+
if (parseTopics(record.topics) !== null) continue;
|
|
6257
|
+
const links = await entityCtx.memoryEntityCollection.query(import_watermelondb10.Q.where("memory_id", id)).unsafeFetchRaw();
|
|
6258
|
+
const entityIds = links.map((l) => String(l.entity_id));
|
|
6259
|
+
if (entityIds.length === 0) continue;
|
|
6260
|
+
const entities = await entityCtx.entityCollection.query(import_watermelondb10.Q.where("id", import_watermelondb10.Q.oneOf(entityIds))).fetch();
|
|
6261
|
+
if (entities.length === 0) continue;
|
|
6262
|
+
const source = record.topicsUserManaged ? "user" : "auto";
|
|
6263
|
+
const write = await resolveMemoryTopicsWrite(entityCtx, id, entities, [], source);
|
|
6264
|
+
if (write) writes.push(write);
|
|
6265
|
+
}
|
|
6266
|
+
if (writes.length === 0) return;
|
|
6267
|
+
const prepared = writes.map(prepareMemoryTopicsUpdate);
|
|
6268
|
+
await ctx.database.batch(...prepared);
|
|
6269
|
+
for (const write of writes) filled.push(write.row.id);
|
|
6231
6270
|
});
|
|
6232
6271
|
}
|
|
6233
6272
|
return filled;
|
|
@@ -18376,7 +18415,7 @@ function createAutoExtractor(options) {
|
|
|
18376
18415
|
const t0 = Date.now();
|
|
18377
18416
|
void (async () => {
|
|
18378
18417
|
try {
|
|
18379
|
-
const { candidates, results, failedCount, outcome } = await extractAndRetain(
|
|
18418
|
+
const { candidates, results, failedCount, outcome, failure } = await extractAndRetain(
|
|
18380
18419
|
window2,
|
|
18381
18420
|
retainCtx,
|
|
18382
18421
|
{
|
|
@@ -18416,7 +18455,11 @@ function createAutoExtractor(options) {
|
|
|
18416
18455
|
failedCount,
|
|
18417
18456
|
durationMs: Date.now() - t0,
|
|
18418
18457
|
conversationId,
|
|
18419
|
-
outcome
|
|
18458
|
+
outcome,
|
|
18459
|
+
// Only set alongside `outcome: "empty-after-retry"`. Spread so the key
|
|
18460
|
+
// is absent rather than explicitly undefined on a healthy turn —
|
|
18461
|
+
// analytics backends store an explicit undefined as a real value.
|
|
18462
|
+
...failure !== void 0 && { failure }
|
|
18420
18463
|
});
|
|
18421
18464
|
} catch (err) {
|
|
18422
18465
|
options.onError?.(err instanceof Error ? err : new Error(String(err)), conversationId);
|
package/dist/react/index.d.mts
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
|
/**
|