@anuma/sdk 3.12.1 → 3.13.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/expo/{chunk-ORVYPS3R.mjs → chunk-BOAKGY4R.mjs} +277 -46
- package/dist/expo/index.cjs +331 -48
- package/dist/expo/index.d.mts +138 -14
- package/dist/expo/index.d.ts +138 -14
- package/dist/expo/index.mjs +57 -4
- package/dist/expo/{recall-A4WZELH5.mjs → recall-O65PGPIQ.mjs} +1 -1
- package/dist/react/{chunk-QURYPMCE.mjs → chunk-XXCZVG4Z.mjs} +290 -49
- package/dist/react/index.cjs +344 -51
- package/dist/react/index.d.mts +151 -15
- package/dist/react/index.d.ts +151 -15
- package/dist/react/index.mjs +57 -4
- package/dist/react/{recall-NQNZDPNU.mjs → recall-KTJCUAXN.mjs} +1 -1
- package/dist/server/{chunk-FXGG7JA5.mjs → chunk-ME6ZQN52.mjs} +277 -46
- package/dist/server/index.cjs +331 -48
- package/dist/server/index.d.mts +138 -14
- package/dist/server/index.d.ts +138 -14
- package/dist/server/index.mjs +57 -4
- package/dist/server/{recall-SLMMIFHM.mjs → recall-RMCSM6L6.mjs} +1 -1
- package/package.json +2 -1
package/dist/react/index.cjs
CHANGED
|
@@ -4202,6 +4202,31 @@ var init_endpointOverride = __esm({
|
|
|
4202
4202
|
function normalizeEntityName(name) {
|
|
4203
4203
|
return name.trim().toLowerCase();
|
|
4204
4204
|
}
|
|
4205
|
+
function serializeTopics(topics) {
|
|
4206
|
+
return JSON.stringify(topics);
|
|
4207
|
+
}
|
|
4208
|
+
function parseTopics(value) {
|
|
4209
|
+
if (typeof value !== "string" || value.length === 0) return null;
|
|
4210
|
+
let parsed;
|
|
4211
|
+
try {
|
|
4212
|
+
parsed = JSON.parse(value);
|
|
4213
|
+
} catch {
|
|
4214
|
+
return null;
|
|
4215
|
+
}
|
|
4216
|
+
if (!Array.isArray(parsed)) return null;
|
|
4217
|
+
const out = [];
|
|
4218
|
+
for (const raw of parsed) {
|
|
4219
|
+
if (typeof raw !== "object" || raw === null) continue;
|
|
4220
|
+
const { name, kind, source } = raw;
|
|
4221
|
+
if (typeof name !== "string" || name.trim().length === 0) continue;
|
|
4222
|
+
out.push({
|
|
4223
|
+
name,
|
|
4224
|
+
...typeof kind === "string" && kind.length > 0 ? { kind } : {},
|
|
4225
|
+
source: source === "user" ? "user" : "auto"
|
|
4226
|
+
});
|
|
4227
|
+
}
|
|
4228
|
+
return out;
|
|
4229
|
+
}
|
|
4205
4230
|
var ENTITY_KINDS;
|
|
4206
4231
|
var init_types3 = __esm({
|
|
4207
4232
|
"src/lib/db/entities/types.ts"() {
|
|
@@ -4267,9 +4292,62 @@ async function upsertEntitiesInWrite(ctx, entities) {
|
|
|
4267
4292
|
for (const record of created) out.set(record.canonicalName, entityToStored(record));
|
|
4268
4293
|
return { entities: out, operations: [...created, ...updated] };
|
|
4269
4294
|
}
|
|
4295
|
+
async function findVaultRowInWrite(ctx, memoryId) {
|
|
4296
|
+
try {
|
|
4297
|
+
const rows = await ctx.database.get("memory_vault").query(import_watermelondb9.Q.where("id", memoryId)).fetch();
|
|
4298
|
+
return rows[0] ?? null;
|
|
4299
|
+
} catch {
|
|
4300
|
+
return null;
|
|
4301
|
+
}
|
|
4302
|
+
}
|
|
4303
|
+
function prepareTopicsUpdate(row, topics, now) {
|
|
4304
|
+
const originalUpdatedAt = row.updatedAt.getTime();
|
|
4305
|
+
return row.prepareUpdate((r) => {
|
|
4306
|
+
r._setRaw("topics", serializeTopics(topics));
|
|
4307
|
+
r._setRaw("topics_updated_at", now);
|
|
4308
|
+
r._setRaw("updated_at", originalUpdatedAt);
|
|
4309
|
+
});
|
|
4310
|
+
}
|
|
4311
|
+
function displayNamesOf(entities) {
|
|
4312
|
+
const out = /* @__PURE__ */ new Map();
|
|
4313
|
+
for (const e of entities) {
|
|
4314
|
+
const name = e.name.trim();
|
|
4315
|
+
const normalized = normalizeEntityName(name);
|
|
4316
|
+
if (normalized.length === 0 || out.has(normalized)) continue;
|
|
4317
|
+
out.set(normalized, name);
|
|
4318
|
+
}
|
|
4319
|
+
return out;
|
|
4320
|
+
}
|
|
4321
|
+
function topicsEqual(stored, computed) {
|
|
4322
|
+
if (stored === null || stored.length !== computed.length) return false;
|
|
4323
|
+
const key = (t) => JSON.stringify([t.name, t.kind ?? null, t.source]);
|
|
4324
|
+
const storedKeys = stored.map(key).sort();
|
|
4325
|
+
const computedKeys = computed.map(key).sort();
|
|
4326
|
+
return storedKeys.every((k, i) => k === computedKeys[i]);
|
|
4327
|
+
}
|
|
4328
|
+
function resolveTopicsWrite(row, linked, displayNames, source) {
|
|
4329
|
+
if (row === null || source === null) return null;
|
|
4330
|
+
const topics = topicsForEntities(linked, displayNames, source);
|
|
4331
|
+
if (topicsEqual(parseTopics(row.topics), topics)) return null;
|
|
4332
|
+
return { row, topics };
|
|
4333
|
+
}
|
|
4334
|
+
function topicsForEntities(entities, displayNames, source) {
|
|
4335
|
+
return entities.map((e) => {
|
|
4336
|
+
const name = displayNames.get(e.canonicalName) ?? e.canonicalName;
|
|
4337
|
+
return { name, ...e.kind ? { kind: e.kind } : {}, source };
|
|
4338
|
+
});
|
|
4339
|
+
}
|
|
4340
|
+
function toEntityObjects(entityInputs) {
|
|
4341
|
+
return entityInputs.map((e) => typeof e === "string" ? { name: e } : e);
|
|
4342
|
+
}
|
|
4343
|
+
async function prepareMemoryTopicsUpdate(ctx, memoryId, linked, inputs, source) {
|
|
4344
|
+
const row = await findVaultRowInWrite(ctx, memoryId);
|
|
4345
|
+
const write = resolveTopicsWrite(row, linked, displayNamesOf(toEntityObjects(inputs)), source);
|
|
4346
|
+
return write === null ? null : prepareTopicsUpdate(write.row, write.topics, Date.now());
|
|
4347
|
+
}
|
|
4270
4348
|
async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
|
|
4271
4349
|
if (entityInputs.length === 0) return [];
|
|
4272
|
-
const normalized = entityInputs
|
|
4350
|
+
const normalized = toEntityObjects(entityInputs);
|
|
4273
4351
|
const userId = ctx.userId;
|
|
4274
4352
|
let skipped = false;
|
|
4275
4353
|
let entities = [];
|
|
@@ -4279,7 +4357,8 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
|
|
|
4279
4357
|
normalized
|
|
4280
4358
|
);
|
|
4281
4359
|
entities = Array.from(byName.values());
|
|
4282
|
-
|
|
4360
|
+
const vaultRow = await findVaultRowInWrite(ctx, memoryId);
|
|
4361
|
+
if (options?.unlessTopicsUserManaged && autoLinkBlocked(vaultRow)) {
|
|
4283
4362
|
if (entityOps.length > 0) {
|
|
4284
4363
|
await ctx.database.batch(...entityOps);
|
|
4285
4364
|
}
|
|
@@ -4290,7 +4369,18 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
|
|
|
4290
4369
|
const existingLinks = await ctx.memoryEntityCollection.query(import_watermelondb9.Q.where("memory_id", memoryId)).fetch();
|
|
4291
4370
|
const existingEntityIds = new Set(existingLinks.map((l) => String(l.entityId)));
|
|
4292
4371
|
const toCreate = entities.filter((e) => !existingEntityIds.has(e.uniqueId));
|
|
4293
|
-
|
|
4372
|
+
const keptIds = entities.map((e) => e.uniqueId);
|
|
4373
|
+
const carriedOver = await resolveEntitiesByIds(
|
|
4374
|
+
ctx,
|
|
4375
|
+
[...existingEntityIds].filter((id) => !keptIds.includes(id))
|
|
4376
|
+
);
|
|
4377
|
+
const topicsWrite = resolveTopicsWrite(
|
|
4378
|
+
vaultRow,
|
|
4379
|
+
[...carriedOver, ...entities],
|
|
4380
|
+
displayNamesOf(normalized),
|
|
4381
|
+
options?.topicsSource ?? "auto"
|
|
4382
|
+
);
|
|
4383
|
+
if (entityOps.length === 0 && toCreate.length === 0 && topicsWrite === null) return;
|
|
4294
4384
|
const linkOps = toCreate.map(
|
|
4295
4385
|
(e) => ctx.memoryEntityCollection.prepareCreate((record) => {
|
|
4296
4386
|
record._setRaw("memory_id", memoryId);
|
|
@@ -4298,26 +4388,42 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
|
|
|
4298
4388
|
if (userId !== void 0) record._setRaw("user_id", userId);
|
|
4299
4389
|
})
|
|
4300
4390
|
);
|
|
4301
|
-
await ctx.database.batch(
|
|
4391
|
+
await ctx.database.batch(
|
|
4392
|
+
...entityOps,
|
|
4393
|
+
...linkOps,
|
|
4394
|
+
...topicsWrite ? [prepareTopicsUpdate(topicsWrite.row, topicsWrite.topics, Date.now())] : []
|
|
4395
|
+
);
|
|
4302
4396
|
});
|
|
4303
4397
|
return skipped ? [] : entities;
|
|
4304
4398
|
}
|
|
4305
|
-
async function
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
}
|
|
4399
|
+
async function resolveEntitiesByIds(ctx, entityIds) {
|
|
4400
|
+
if (entityIds.length === 0) return [];
|
|
4401
|
+
const rows = await ctx.entityCollection.query(import_watermelondb9.Q.where("id", import_watermelondb9.Q.oneOf(Array.from(entityIds)))).fetch();
|
|
4402
|
+
return rows.map(entityToStored);
|
|
4403
|
+
}
|
|
4404
|
+
function autoLinkBlocked(row) {
|
|
4405
|
+
return !row || !!row.isDeleted || !!row.topicsUserManaged;
|
|
4313
4406
|
}
|
|
4314
4407
|
async function replaceMemoryEntitiesGuardedOp(ctx, memoryId, entityInputs) {
|
|
4315
|
-
|
|
4408
|
+
return replaceMemoryEntities(ctx, memoryId, entityInputs, {
|
|
4409
|
+
guarded: true,
|
|
4410
|
+
topicsSource: "auto"
|
|
4411
|
+
});
|
|
4412
|
+
}
|
|
4413
|
+
async function relinkMemoryEntitiesFromTopicsOp(ctx, memoryId, topics) {
|
|
4414
|
+
return replaceMemoryEntities(ctx, memoryId, topics, {
|
|
4415
|
+
guarded: false,
|
|
4416
|
+
topicsSource: null
|
|
4417
|
+
});
|
|
4418
|
+
}
|
|
4419
|
+
async function replaceMemoryEntities(ctx, memoryId, entityInputs, options) {
|
|
4420
|
+
const normalized = toEntityObjects(entityInputs);
|
|
4316
4421
|
const userId = ctx.userId;
|
|
4317
4422
|
let skipped = false;
|
|
4318
4423
|
let entities = [];
|
|
4319
4424
|
await ctx.database.write(async () => {
|
|
4320
|
-
|
|
4425
|
+
const vaultRow = await findVaultRowInWrite(ctx, memoryId);
|
|
4426
|
+
if (options.guarded && autoLinkBlocked(vaultRow)) {
|
|
4321
4427
|
skipped = true;
|
|
4322
4428
|
return;
|
|
4323
4429
|
}
|
|
@@ -4331,7 +4437,15 @@ async function replaceMemoryEntitiesGuardedOp(ctx, memoryId, entityInputs) {
|
|
|
4331
4437
|
const existingEntityIds = new Set(existingLinks.map((l) => String(l.entityId)));
|
|
4332
4438
|
const toCreate = entities.filter((e) => !existingEntityIds.has(e.uniqueId));
|
|
4333
4439
|
const toDestroy = existingLinks.filter((l) => !keep.has(String(l.entityId)));
|
|
4334
|
-
|
|
4440
|
+
const topicsWrite = resolveTopicsWrite(
|
|
4441
|
+
vaultRow,
|
|
4442
|
+
entities,
|
|
4443
|
+
displayNamesOf(normalized),
|
|
4444
|
+
options.topicsSource
|
|
4445
|
+
);
|
|
4446
|
+
if (entityOps.length === 0 && toCreate.length === 0 && toDestroy.length === 0 && topicsWrite === null) {
|
|
4447
|
+
return;
|
|
4448
|
+
}
|
|
4335
4449
|
const orphans = await findOrphanedEntities(ctx, memoryId, toDestroy);
|
|
4336
4450
|
await ctx.database.batch(
|
|
4337
4451
|
...entityOps,
|
|
@@ -4343,7 +4457,8 @@ async function replaceMemoryEntitiesGuardedOp(ctx, memoryId, entityInputs) {
|
|
|
4343
4457
|
})
|
|
4344
4458
|
),
|
|
4345
4459
|
...toDestroy.map((l) => l.prepareDestroyPermanently()),
|
|
4346
|
-
...orphans.map((e) => e.prepareDestroyPermanently())
|
|
4460
|
+
...orphans.map((e) => e.prepareDestroyPermanently()),
|
|
4461
|
+
...topicsWrite ? [prepareTopicsUpdate(topicsWrite.row, topicsWrite.topics, Date.now())] : []
|
|
4347
4462
|
);
|
|
4348
4463
|
});
|
|
4349
4464
|
return skipped ? null : entities;
|
|
@@ -4564,6 +4679,8 @@ function vaultMemoryToStoredRaw(memory) {
|
|
|
4564
4679
|
eventTimeEnd: memory.eventTimeEnd ?? null,
|
|
4565
4680
|
eventTimeKind: memory.eventTimeKind ?? null,
|
|
4566
4681
|
topicsUserManaged: memory.topicsUserManaged ?? false,
|
|
4682
|
+
topics: parseTopics(memory.topics),
|
|
4683
|
+
topicsUpdatedAt: memory.topicsUpdatedAt ?? null,
|
|
4567
4684
|
topicsExtractedAt: memory.topicsExtractedAt ?? null,
|
|
4568
4685
|
topicsExtractedVersion: memory.topicsExtractedVersion ?? null,
|
|
4569
4686
|
supersededBy: memory.supersededBy ?? null,
|
|
@@ -4840,6 +4957,8 @@ function vaultMemoryRawToStoredRaw(raw) {
|
|
|
4840
4957
|
eventTimeKind: raw.event_time_kind ?? null,
|
|
4841
4958
|
// SQLite stores booleans as 0/1, LokiJS as true/false — coerce both.
|
|
4842
4959
|
topicsUserManaged: raw.topics_user_managed === true || raw.topics_user_managed === 1,
|
|
4960
|
+
topics: parseTopics(raw.topics),
|
|
4961
|
+
topicsUpdatedAt: raw.topics_updated_at ?? null,
|
|
4843
4962
|
topicsExtractedAt: raw.topics_extracted_at ?? null,
|
|
4844
4963
|
topicsExtractedVersion: raw.topics_extracted_version ?? null,
|
|
4845
4964
|
supersededBy: raw.superseded_by ?? null,
|
|
@@ -5130,13 +5249,23 @@ async function setMemoryEntitiesOp(ctx, memoryId, entities) {
|
|
|
5130
5249
|
});
|
|
5131
5250
|
});
|
|
5132
5251
|
if (stale) return null;
|
|
5133
|
-
const linked = entities.length > 0 ? await linkMemoryEntitiesOp(entityCtx, memoryId, entities) : [];
|
|
5252
|
+
const linked = entities.length > 0 ? await linkMemoryEntitiesOp(entityCtx, memoryId, entities, { topicsSource: "user" }) : [];
|
|
5134
5253
|
const keep = new Set(linked.map((e) => e.uniqueId));
|
|
5135
5254
|
const existing = await entityCtx.memoryEntityCollection.query(import_watermelondb10.Q.where("memory_id", memoryId)).fetch();
|
|
5136
5255
|
const staleLinks = existing.filter((l) => !keep.has(String(l.entityId)));
|
|
5137
|
-
if (staleLinks.length > 0) {
|
|
5256
|
+
if (staleLinks.length > 0 || entities.length === 0) {
|
|
5138
5257
|
await ctx.database.write(async () => {
|
|
5139
|
-
|
|
5258
|
+
const topicsOp = await prepareMemoryTopicsUpdate(
|
|
5259
|
+
entityCtx,
|
|
5260
|
+
memoryId,
|
|
5261
|
+
linked,
|
|
5262
|
+
entities,
|
|
5263
|
+
"user"
|
|
5264
|
+
);
|
|
5265
|
+
await ctx.database.batch(
|
|
5266
|
+
...staleLinks.map((l) => l.prepareDestroyPermanently()),
|
|
5267
|
+
...topicsOp ? [topicsOp] : []
|
|
5268
|
+
);
|
|
5140
5269
|
});
|
|
5141
5270
|
}
|
|
5142
5271
|
return vaultMemoryToStored(record, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner);
|
|
@@ -5266,56 +5395,111 @@ async function getUnfiledVaultMemoriesOp(ctx) {
|
|
|
5266
5395
|
(record) => vaultMemoryToStored(record, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner)
|
|
5267
5396
|
);
|
|
5268
5397
|
}
|
|
5398
|
+
async function linkedEntityNamesByMemory(entityCtx, memoryIds) {
|
|
5399
|
+
const CHUNK = 500;
|
|
5400
|
+
const entityIdByMemory = /* @__PURE__ */ new Map();
|
|
5401
|
+
const allEntityIds = /* @__PURE__ */ new Set();
|
|
5402
|
+
for (let i = 0; i < memoryIds.length; i += CHUNK) {
|
|
5403
|
+
const links = await entityCtx.memoryEntityCollection.query(import_watermelondb10.Q.where("memory_id", import_watermelondb10.Q.oneOf(memoryIds.slice(i, i + CHUNK)))).unsafeFetchRaw();
|
|
5404
|
+
for (const link of links) {
|
|
5405
|
+
const memoryId = String(link.memory_id);
|
|
5406
|
+
const entityId = String(link.entity_id);
|
|
5407
|
+
allEntityIds.add(entityId);
|
|
5408
|
+
let bucket = entityIdByMemory.get(memoryId);
|
|
5409
|
+
if (!bucket) {
|
|
5410
|
+
bucket = /* @__PURE__ */ new Set();
|
|
5411
|
+
entityIdByMemory.set(memoryId, bucket);
|
|
5412
|
+
}
|
|
5413
|
+
bucket.add(entityId);
|
|
5414
|
+
}
|
|
5415
|
+
}
|
|
5416
|
+
const entityIds = Array.from(allEntityIds);
|
|
5417
|
+
const nameById = /* @__PURE__ */ new Map();
|
|
5418
|
+
for (let i = 0; i < entityIds.length; i += CHUNK) {
|
|
5419
|
+
const entities = await entityCtx.entityCollection.query(import_watermelondb10.Q.where("id", import_watermelondb10.Q.oneOf(entityIds.slice(i, i + CHUNK)))).unsafeFetchRaw();
|
|
5420
|
+
for (const e of entities) {
|
|
5421
|
+
if (typeof e.canonical_name === "string") nameById.set(String(e.id), e.canonical_name);
|
|
5422
|
+
}
|
|
5423
|
+
}
|
|
5424
|
+
const out = /* @__PURE__ */ new Map();
|
|
5425
|
+
for (const [memoryId, ids] of entityIdByMemory) {
|
|
5426
|
+
const names = /* @__PURE__ */ new Set();
|
|
5427
|
+
for (const id of ids) {
|
|
5428
|
+
const name = nameById.get(id);
|
|
5429
|
+
if (name !== void 0) names.add(name);
|
|
5430
|
+
}
|
|
5431
|
+
if (names.size > 0) out.set(memoryId, names);
|
|
5432
|
+
}
|
|
5433
|
+
return out;
|
|
5434
|
+
}
|
|
5435
|
+
function linksDivergeFromTopics(topics, linked) {
|
|
5436
|
+
const wanted = new Set(
|
|
5437
|
+
topics.map((t) => normalizeEntityName(t.name)).filter((n) => n.length > 0)
|
|
5438
|
+
);
|
|
5439
|
+
if (wanted.size !== linked.size) return true;
|
|
5440
|
+
for (const name of wanted) if (!linked.has(name)) return true;
|
|
5441
|
+
return false;
|
|
5442
|
+
}
|
|
5269
5443
|
async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
|
|
5270
5444
|
const entityCtx = ctx.entityCtx;
|
|
5271
5445
|
if (!entityCtx) {
|
|
5272
5446
|
throw new Error("getMemoriesNeedingTopicExtractionOp requires ctx.entityCtx");
|
|
5273
5447
|
}
|
|
5274
|
-
const conditions = [
|
|
5275
|
-
// Boolean optional column: null (legacy) and false both mean auto-managed.
|
|
5276
|
-
// Q.notEq(true) is a NULL trap on SQLite (NULL <> TRUE is NULL) — enumerate.
|
|
5277
|
-
import_watermelondb10.Q.or(import_watermelondb10.Q.where("topics_user_managed", null), import_watermelondb10.Q.where("topics_user_managed", false)),
|
|
5278
|
-
...baseVaultConditions(ctx),
|
|
5279
|
-
import_watermelondb10.Q.sortBy("created_at", import_watermelondb10.Q.desc)
|
|
5280
|
-
];
|
|
5448
|
+
const conditions = [...baseVaultConditions(ctx), import_watermelondb10.Q.sortBy("created_at", import_watermelondb10.Q.desc)];
|
|
5281
5449
|
const rows = await ctx.vaultMemoryCollection.query(...conditions).unsafeFetchRaw();
|
|
5282
|
-
const
|
|
5283
|
-
|
|
5450
|
+
const linkedNames = await linkedEntityNamesByMemory(
|
|
5451
|
+
entityCtx,
|
|
5452
|
+
rows.map((r) => r.id)
|
|
5453
|
+
);
|
|
5454
|
+
const pendingRaw = [];
|
|
5455
|
+
const stampedPendingRaw = [];
|
|
5456
|
+
const linkedUnstampedAll = [];
|
|
5457
|
+
const topicsToRelinkAll = [];
|
|
5458
|
+
const topicsBackfillAll = [];
|
|
5284
5459
|
for (const raw of rows) {
|
|
5460
|
+
const id = raw.id;
|
|
5461
|
+
const topics = parseTopics(raw.topics);
|
|
5462
|
+
const linked = linkedNames.get(id);
|
|
5463
|
+
if (topics !== null && topics.length > 0 && linksDivergeFromTopics(topics, linked ?? /* @__PURE__ */ new Set())) {
|
|
5464
|
+
topicsToRelinkAll.push(id);
|
|
5465
|
+
continue;
|
|
5466
|
+
}
|
|
5467
|
+
if (raw.topics_user_managed) {
|
|
5468
|
+
if (topics === null && linked !== void 0) topicsBackfillAll.push(id);
|
|
5469
|
+
continue;
|
|
5470
|
+
}
|
|
5285
5471
|
const stamp = raw.topics_extracted_at ?? null;
|
|
5472
|
+
let isPending = false;
|
|
5286
5473
|
if (stamp !== null) {
|
|
5287
5474
|
const version = raw.topics_extracted_version ?? 0;
|
|
5288
5475
|
if (raw.updated_at > stamp || version < TOPICS_EXTRACTION_VERSION) {
|
|
5289
|
-
|
|
5476
|
+
stampedPendingRaw.push(raw);
|
|
5477
|
+
isPending = true;
|
|
5290
5478
|
}
|
|
5291
|
-
} else {
|
|
5292
|
-
|
|
5293
|
-
}
|
|
5294
|
-
}
|
|
5295
|
-
const linkedIds = /* @__PURE__ */ new Set();
|
|
5296
|
-
const CHUNK = 500;
|
|
5297
|
-
for (let i = 0; i < unstamped.length; i += CHUNK) {
|
|
5298
|
-
const ids = unstamped.slice(i, i + CHUNK).map((r) => r.id);
|
|
5299
|
-
const links = await entityCtx.memoryEntityCollection.query(import_watermelondb10.Q.where("memory_id", import_watermelondb10.Q.oneOf(ids))).unsafeFetchRaw();
|
|
5300
|
-
for (const link of links) linkedIds.add(String(link.memory_id));
|
|
5301
|
-
}
|
|
5302
|
-
const linkedUnstampedAll = [];
|
|
5303
|
-
const pendingRaw = [...stampedPending];
|
|
5304
|
-
for (const raw of unstamped) {
|
|
5305
|
-
if (linkedIds.has(raw.id)) {
|
|
5306
|
-
linkedUnstampedAll.push(raw.id);
|
|
5479
|
+
} else if (linked !== void 0) {
|
|
5480
|
+
linkedUnstampedAll.push(id);
|
|
5307
5481
|
} else {
|
|
5308
5482
|
pendingRaw.push(raw);
|
|
5483
|
+
isPending = true;
|
|
5484
|
+
}
|
|
5485
|
+
if (!isPending && topics === null && linked !== void 0) {
|
|
5486
|
+
topicsBackfillAll.push(id);
|
|
5309
5487
|
}
|
|
5310
5488
|
}
|
|
5311
5489
|
const cap = options?.limit !== void 0 && options.limit > 0 ? options.limit : void 0;
|
|
5312
|
-
const
|
|
5313
|
-
const
|
|
5490
|
+
const orderedPendingRaw = [...stampedPendingRaw, ...pendingRaw];
|
|
5491
|
+
const limitedPendingRaw = cap !== void 0 ? orderedPendingRaw.slice(0, cap) : orderedPendingRaw;
|
|
5314
5492
|
const pending = await mapInBatches(
|
|
5315
5493
|
limitedPendingRaw,
|
|
5316
5494
|
(raw) => vaultMemoryRawToStored(raw, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner)
|
|
5317
5495
|
);
|
|
5318
|
-
|
|
5496
|
+
const applyCap = (ids) => cap !== void 0 ? ids.slice(0, cap) : ids;
|
|
5497
|
+
return {
|
|
5498
|
+
pending,
|
|
5499
|
+
linkedUnstamped: applyCap(linkedUnstampedAll),
|
|
5500
|
+
topicsToRelink: applyCap(topicsToRelinkAll),
|
|
5501
|
+
topicsBackfill: applyCap(topicsBackfillAll)
|
|
5502
|
+
};
|
|
5319
5503
|
}
|
|
5320
5504
|
async function stampTopicsExtractedAtOp(ctx, memoryIds, extractedAt, version = TOPICS_EXTRACTION_VERSION) {
|
|
5321
5505
|
if (memoryIds.length === 0) return [];
|
|
@@ -5349,6 +5533,61 @@ async function stampTopicsExtractedAtOp(ctx, memoryIds, extractedAt, version = T
|
|
|
5349
5533
|
}
|
|
5350
5534
|
return stamped;
|
|
5351
5535
|
}
|
|
5536
|
+
async function relinkMemoryTopicsOp(ctx, memoryIds) {
|
|
5537
|
+
const entityCtx = ctx.entityCtx;
|
|
5538
|
+
if (!entityCtx) {
|
|
5539
|
+
throw new Error("relinkMemoryTopicsOp requires ctx.entityCtx");
|
|
5540
|
+
}
|
|
5541
|
+
const relinked = [];
|
|
5542
|
+
for (const id of Array.from(new Set(memoryIds))) {
|
|
5543
|
+
let record;
|
|
5544
|
+
try {
|
|
5545
|
+
record = await ctx.vaultMemoryCollection.find(id);
|
|
5546
|
+
} catch {
|
|
5547
|
+
continue;
|
|
5548
|
+
}
|
|
5549
|
+
if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) continue;
|
|
5550
|
+
const topics = parseTopics(record.topics);
|
|
5551
|
+
if (topics === null || topics.length === 0) continue;
|
|
5552
|
+
try {
|
|
5553
|
+
await relinkMemoryEntitiesFromTopicsOp(entityCtx, id, topics);
|
|
5554
|
+
relinked.push(id);
|
|
5555
|
+
} catch (err) {
|
|
5556
|
+
getLogger().warn("[memory/topics] relink failed", err);
|
|
5557
|
+
}
|
|
5558
|
+
}
|
|
5559
|
+
return relinked;
|
|
5560
|
+
}
|
|
5561
|
+
async function backfillMemoryTopicsOp(ctx, memoryIds) {
|
|
5562
|
+
const entityCtx = ctx.entityCtx;
|
|
5563
|
+
if (!entityCtx) {
|
|
5564
|
+
throw new Error("backfillMemoryTopicsOp requires ctx.entityCtx");
|
|
5565
|
+
}
|
|
5566
|
+
const filled = [];
|
|
5567
|
+
for (const id of Array.from(new Set(memoryIds))) {
|
|
5568
|
+
let record;
|
|
5569
|
+
try {
|
|
5570
|
+
record = await ctx.vaultMemoryCollection.find(id);
|
|
5571
|
+
} catch {
|
|
5572
|
+
continue;
|
|
5573
|
+
}
|
|
5574
|
+
if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) continue;
|
|
5575
|
+
if (parseTopics(record.topics) !== null) continue;
|
|
5576
|
+
const links = await entityCtx.memoryEntityCollection.query(import_watermelondb10.Q.where("memory_id", id)).unsafeFetchRaw();
|
|
5577
|
+
const entityIds = links.map((l) => String(l.entity_id));
|
|
5578
|
+
if (entityIds.length === 0) continue;
|
|
5579
|
+
const entities = await entityCtx.entityCollection.query(import_watermelondb10.Q.where("id", import_watermelondb10.Q.oneOf(entityIds))).fetch();
|
|
5580
|
+
if (entities.length === 0) continue;
|
|
5581
|
+
const source = record.topicsUserManaged ? "user" : "auto";
|
|
5582
|
+
await ctx.database.write(async () => {
|
|
5583
|
+
const topicsOp = await prepareMemoryTopicsUpdate(entityCtx, id, entities, [], source);
|
|
5584
|
+
if (!topicsOp) return;
|
|
5585
|
+
await ctx.database.batch(topicsOp);
|
|
5586
|
+
filled.push(id);
|
|
5587
|
+
});
|
|
5588
|
+
}
|
|
5589
|
+
return filled;
|
|
5590
|
+
}
|
|
5352
5591
|
async function deleteAllVaultMemoriesForUserOp(ctx, userId) {
|
|
5353
5592
|
if (ctx.userId !== void 0 && ctx.userId !== userId) return 0;
|
|
5354
5593
|
const records = await ctx.vaultMemoryCollection.query(import_watermelondb10.Q.where("user_id", userId), import_watermelondb10.Q.where("is_deleted", false)).fetch();
|
|
@@ -5490,6 +5729,7 @@ var init_operations3 = __esm({
|
|
|
5490
5729
|
import_watermelondb10 = require("@nozbe/watermelondb");
|
|
5491
5730
|
init_logger();
|
|
5492
5731
|
init_operations2();
|
|
5732
|
+
init_types3();
|
|
5493
5733
|
init_encryption2();
|
|
5494
5734
|
NON_PRIVATE_VISIBILITIES = ["public"];
|
|
5495
5735
|
KNOWN_TRUST_TIERS = /* @__PURE__ */ new Set(["quarantined", "trusted"]);
|
|
@@ -8740,6 +8980,7 @@ __export(index_exports, {
|
|
|
8740
8980
|
appFileToStored: () => appFileToStored,
|
|
8741
8981
|
archiveVaultMemoryOp: () => archiveVaultMemoryOp,
|
|
8742
8982
|
backfillMemoryEntityUserIdsOp: () => backfillMemoryEntityUserIdsOp,
|
|
8983
|
+
backfillMemoryTopicsOp: () => backfillMemoryTopicsOp,
|
|
8743
8984
|
capHopsForDensity: () => capHopsForDensity,
|
|
8744
8985
|
chatStorageMigrations: () => chatStorageMigrations,
|
|
8745
8986
|
chatStorageSchema: () => chatStorageSchema,
|
|
@@ -8975,6 +9216,7 @@ __export(index_exports, {
|
|
|
8975
9216
|
refreshDriveToken: () => refreshDriveToken,
|
|
8976
9217
|
refreshGithubToken: () => refreshGithubToken,
|
|
8977
9218
|
refreshNotionToken: () => refreshNotionToken,
|
|
9219
|
+
relinkMemoryTopicsOp: () => relinkMemoryTopicsOp,
|
|
8978
9220
|
relinkMisclassifiedVideosOp: () => relinkMisclassifiedVideosOp,
|
|
8979
9221
|
removeById: () => removeById,
|
|
8980
9222
|
renderAnumaJsx: () => renderAnumaJsx,
|
|
@@ -15684,6 +15926,12 @@ __decorateClass([
|
|
|
15684
15926
|
__decorateClass([
|
|
15685
15927
|
(0, import_decorators3.field)("topics_user_managed")
|
|
15686
15928
|
], VaultMemory.prototype, "topicsUserManaged", 2);
|
|
15929
|
+
__decorateClass([
|
|
15930
|
+
(0, import_decorators3.field)("topics")
|
|
15931
|
+
], VaultMemory.prototype, "topics", 2);
|
|
15932
|
+
__decorateClass([
|
|
15933
|
+
(0, import_decorators3.field)("topics_updated_at")
|
|
15934
|
+
], VaultMemory.prototype, "topicsUpdatedAt", 2);
|
|
15687
15935
|
__decorateClass([
|
|
15688
15936
|
(0, import_decorators3.field)("topics_extracted_at")
|
|
15689
15937
|
], VaultMemory.prototype, "topicsExtractedAt", 2);
|
|
@@ -17142,7 +17390,6 @@ function createAutoExtractor(options) {
|
|
|
17142
17390
|
return true;
|
|
17143
17391
|
}
|
|
17144
17392
|
function drainPending() {
|
|
17145
|
-
if (disposed) return;
|
|
17146
17393
|
for (const [conversationId, state] of conversations) {
|
|
17147
17394
|
if (state.pending) {
|
|
17148
17395
|
const messages = state.pending;
|
|
@@ -17157,7 +17404,7 @@ function createAutoExtractor(options) {
|
|
|
17157
17404
|
isProcessing: () => inflight > 0,
|
|
17158
17405
|
dispose: () => {
|
|
17159
17406
|
disposed = true;
|
|
17160
|
-
|
|
17407
|
+
if (inflight === 0) drainPending();
|
|
17161
17408
|
}
|
|
17162
17409
|
};
|
|
17163
17410
|
}
|
|
@@ -22877,7 +23124,7 @@ __decorateClass([
|
|
|
22877
23124
|
], VaultFolder.prototype, "context", 2);
|
|
22878
23125
|
|
|
22879
23126
|
// src/lib/db/schema.ts
|
|
22880
|
-
var SDK_SCHEMA_VERSION =
|
|
23127
|
+
var SDK_SCHEMA_VERSION = 42;
|
|
22881
23128
|
var sdkSchema = (0, import_watermelondb20.appSchema)({
|
|
22882
23129
|
version: SDK_SCHEMA_VERSION,
|
|
22883
23130
|
tables: [
|
|
@@ -22999,9 +23246,33 @@ var sdkSchema = (0, import_watermelondb20.appSchema)({
|
|
|
22999
23246
|
// links). Auto-extraction then leaves its links alone — the user owns
|
|
23000
23247
|
// them. Null/false = topics are auto-derived (default).
|
|
23001
23248
|
{ name: "topics_user_managed", type: "boolean", isOptional: true },
|
|
23249
|
+
// The memory's topics as a DURABLE, SYNCED record: a JSON array of
|
|
23250
|
+
// `{name, kind?, source}`, `name` in the caller's display casing (unlike
|
|
23251
|
+
// `entity.canonical_name`, which is lowercased and has no display
|
|
23252
|
+
// column). `entity` / `memory_entity` are a device-local INDEX over this
|
|
23253
|
+
// — their ids are locally generated, so they can never sync — and a
|
|
23254
|
+
// restored device rebuilds them from these names with no LLM call.
|
|
23255
|
+
// Null = predates v42 (backfilled from the row's current links by the
|
|
23256
|
+
// sweep's topicsBackfill bucket). See getMemoriesNeedingTopicExtractionOp.
|
|
23257
|
+
{ name: "topics", type: "string", isOptional: true },
|
|
23258
|
+
// Unix ms of the last write to `topics`. A SECOND timestamp is required:
|
|
23259
|
+
// every topic writer deliberately pins `updated_at` so a topic change
|
|
23260
|
+
// doesn't inflate recall's recency multiplier, and both client sync
|
|
23261
|
+
// paths key on `updated_at` — so without this a topic-only change would
|
|
23262
|
+
// neither upload nor merge. Null = `topics` never written.
|
|
23263
|
+
{ name: "topics_updated_at", type: "number", isOptional: true },
|
|
23002
23264
|
// Unix ms of the last LLM topic-extraction pass over this memory's
|
|
23003
23265
|
// content. Null = never extracted standalone (legacy rows with entity
|
|
23004
23266
|
// links are grandfathered — see getMemoriesNeedingTopicExtractionOp).
|
|
23267
|
+
//
|
|
23268
|
+
// DEPRECATED (v42) — `topics_updated_at` subsumes this and
|
|
23269
|
+
// `topics_extracted_version` both: null there means never processed, and
|
|
23270
|
+
// non-null with an empty `topics` means processed and found nothing
|
|
23271
|
+
// (today's "answered empty" case), while a release-time
|
|
23272
|
+
// EXTRACTOR_CHANGED_AT constant compared against `topics_updated_at`
|
|
23273
|
+
// replaces the version check. Both columns are kept only to avoid a
|
|
23274
|
+
// column-drop migration in an otherwise additive list; removing them is
|
|
23275
|
+
// a follow-up once `topics` is proven in production.
|
|
23005
23276
|
{ name: "topics_extracted_at", type: "number", isOptional: true },
|
|
23006
23277
|
// Write-time supersession (A2). When set, this fact was replaced by a
|
|
23007
23278
|
// newer, incompatible-value fact (e.g. "Lives in Portland" superseded by
|
|
@@ -23015,6 +23286,9 @@ var sdkSchema = (0, import_watermelondb20.appSchema)({
|
|
|
23015
23286
|
// The extraction-logic version this memory was last stamped under. Null
|
|
23016
23287
|
// (pre-v38) is treated as version 0, so a bump of TOPICS_EXTRACTION_VERSION
|
|
23017
23288
|
// re-extracts stale rows. See getMemoriesNeedingTopicExtractionOp.
|
|
23289
|
+
//
|
|
23290
|
+
// DEPRECATED (v42) alongside `topics_extracted_at` — same rationale and
|
|
23291
|
+
// same removal follow-up; see that column's note above.
|
|
23018
23292
|
{ name: "topics_extracted_version", type: "number", isOptional: true },
|
|
23019
23293
|
// Re-observation watermark (C3). Unix ms of the last time retain() merged
|
|
23020
23294
|
// a duplicate observation into this fact (proof_count++). Distinct from
|
|
@@ -23748,6 +24022,23 @@ var sdkMigrations = (0, import_migrations2.schemaMigrations)({
|
|
|
23748
24022
|
]
|
|
23749
24023
|
})
|
|
23750
24024
|
]
|
|
24025
|
+
},
|
|
24026
|
+
// v41 -> v42: topics + topics_updated_at on memory_vault — the durable,
|
|
24027
|
+
// synced record of a memory's topics. Existing rows keep both NULL: the
|
|
24028
|
+
// sweep's topicsBackfill bucket fills them from each row's current entity
|
|
24029
|
+
// links (no LLM), capped under the worker's `limit` so the one-time
|
|
24030
|
+
// re-upload it triggers drains across sweeps instead of spiking.
|
|
24031
|
+
{
|
|
24032
|
+
toVersion: 42,
|
|
24033
|
+
steps: [
|
|
24034
|
+
(0, import_migrations2.addColumns)({
|
|
24035
|
+
table: "memory_vault",
|
|
24036
|
+
columns: [
|
|
24037
|
+
{ name: "topics", type: "string", isOptional: true },
|
|
24038
|
+
{ name: "topics_updated_at", type: "number", isOptional: true }
|
|
24039
|
+
]
|
|
24040
|
+
})
|
|
24041
|
+
]
|
|
23751
24042
|
}
|
|
23752
24043
|
]
|
|
23753
24044
|
});
|
|
@@ -33452,6 +33743,7 @@ function renderAnumaJsx(jsx3) {
|
|
|
33452
33743
|
appFileToStored,
|
|
33453
33744
|
archiveVaultMemoryOp,
|
|
33454
33745
|
backfillMemoryEntityUserIdsOp,
|
|
33746
|
+
backfillMemoryTopicsOp,
|
|
33455
33747
|
capHopsForDensity,
|
|
33456
33748
|
chatStorageMigrations,
|
|
33457
33749
|
chatStorageSchema,
|
|
@@ -33687,6 +33979,7 @@ function renderAnumaJsx(jsx3) {
|
|
|
33687
33979
|
refreshDriveToken,
|
|
33688
33980
|
refreshGithubToken,
|
|
33689
33981
|
refreshNotionToken,
|
|
33982
|
+
relinkMemoryTopicsOp,
|
|
33690
33983
|
relinkMisclassifiedVideosOp,
|
|
33691
33984
|
removeById,
|
|
33692
33985
|
renderAnumaJsx,
|