@anuma/sdk 3.12.2 → 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 +330 -46
- package/dist/expo/index.d.mts +133 -13
- package/dist/expo/index.d.ts +133 -13
- package/dist/expo/index.mjs +56 -2
- 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 +343 -49
- package/dist/react/index.d.mts +146 -14
- package/dist/react/index.d.ts +146 -14
- package/dist/react/index.mjs +56 -2
- 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 +330 -46
- package/dist/server/index.d.mts +133 -13
- package/dist/server/index.d.ts +133 -13
- package/dist/server/index.mjs +56 -2
- package/dist/server/{recall-SLMMIFHM.mjs → recall-RMCSM6L6.mjs} +1 -1
- package/package.json +1 -1
package/dist/expo/index.cjs
CHANGED
|
@@ -4003,6 +4003,31 @@ var init_endpointOverride = __esm({
|
|
|
4003
4003
|
function normalizeEntityName(name) {
|
|
4004
4004
|
return name.trim().toLowerCase();
|
|
4005
4005
|
}
|
|
4006
|
+
function serializeTopics(topics) {
|
|
4007
|
+
return JSON.stringify(topics);
|
|
4008
|
+
}
|
|
4009
|
+
function parseTopics(value) {
|
|
4010
|
+
if (typeof value !== "string" || value.length === 0) return null;
|
|
4011
|
+
let parsed;
|
|
4012
|
+
try {
|
|
4013
|
+
parsed = JSON.parse(value);
|
|
4014
|
+
} catch {
|
|
4015
|
+
return null;
|
|
4016
|
+
}
|
|
4017
|
+
if (!Array.isArray(parsed)) return null;
|
|
4018
|
+
const out = [];
|
|
4019
|
+
for (const raw of parsed) {
|
|
4020
|
+
if (typeof raw !== "object" || raw === null) continue;
|
|
4021
|
+
const { name, kind, source } = raw;
|
|
4022
|
+
if (typeof name !== "string" || name.trim().length === 0) continue;
|
|
4023
|
+
out.push({
|
|
4024
|
+
name,
|
|
4025
|
+
...typeof kind === "string" && kind.length > 0 ? { kind } : {},
|
|
4026
|
+
source: source === "user" ? "user" : "auto"
|
|
4027
|
+
});
|
|
4028
|
+
}
|
|
4029
|
+
return out;
|
|
4030
|
+
}
|
|
4006
4031
|
var ENTITY_KINDS;
|
|
4007
4032
|
var init_types3 = __esm({
|
|
4008
4033
|
"src/lib/db/entities/types.ts"() {
|
|
@@ -4068,9 +4093,62 @@ async function upsertEntitiesInWrite(ctx, entities) {
|
|
|
4068
4093
|
for (const record of created) out.set(record.canonicalName, entityToStored(record));
|
|
4069
4094
|
return { entities: out, operations: [...created, ...updated] };
|
|
4070
4095
|
}
|
|
4096
|
+
async function findVaultRowInWrite(ctx, memoryId) {
|
|
4097
|
+
try {
|
|
4098
|
+
const rows = await ctx.database.get("memory_vault").query(import_watermelondb8.Q.where("id", memoryId)).fetch();
|
|
4099
|
+
return rows[0] ?? null;
|
|
4100
|
+
} catch {
|
|
4101
|
+
return null;
|
|
4102
|
+
}
|
|
4103
|
+
}
|
|
4104
|
+
function prepareTopicsUpdate(row, topics, now) {
|
|
4105
|
+
const originalUpdatedAt = row.updatedAt.getTime();
|
|
4106
|
+
return row.prepareUpdate((r) => {
|
|
4107
|
+
r._setRaw("topics", serializeTopics(topics));
|
|
4108
|
+
r._setRaw("topics_updated_at", now);
|
|
4109
|
+
r._setRaw("updated_at", originalUpdatedAt);
|
|
4110
|
+
});
|
|
4111
|
+
}
|
|
4112
|
+
function displayNamesOf(entities) {
|
|
4113
|
+
const out = /* @__PURE__ */ new Map();
|
|
4114
|
+
for (const e of entities) {
|
|
4115
|
+
const name = e.name.trim();
|
|
4116
|
+
const normalized = normalizeEntityName(name);
|
|
4117
|
+
if (normalized.length === 0 || out.has(normalized)) continue;
|
|
4118
|
+
out.set(normalized, name);
|
|
4119
|
+
}
|
|
4120
|
+
return out;
|
|
4121
|
+
}
|
|
4122
|
+
function topicsEqual(stored, computed) {
|
|
4123
|
+
if (stored === null || stored.length !== computed.length) return false;
|
|
4124
|
+
const key = (t) => JSON.stringify([t.name, t.kind ?? null, t.source]);
|
|
4125
|
+
const storedKeys = stored.map(key).sort();
|
|
4126
|
+
const computedKeys = computed.map(key).sort();
|
|
4127
|
+
return storedKeys.every((k, i) => k === computedKeys[i]);
|
|
4128
|
+
}
|
|
4129
|
+
function resolveTopicsWrite(row, linked, displayNames, source) {
|
|
4130
|
+
if (row === null || source === null) return null;
|
|
4131
|
+
const topics = topicsForEntities(linked, displayNames, source);
|
|
4132
|
+
if (topicsEqual(parseTopics(row.topics), topics)) return null;
|
|
4133
|
+
return { row, topics };
|
|
4134
|
+
}
|
|
4135
|
+
function topicsForEntities(entities, displayNames, source) {
|
|
4136
|
+
return entities.map((e) => {
|
|
4137
|
+
const name = displayNames.get(e.canonicalName) ?? e.canonicalName;
|
|
4138
|
+
return { name, ...e.kind ? { kind: e.kind } : {}, source };
|
|
4139
|
+
});
|
|
4140
|
+
}
|
|
4141
|
+
function toEntityObjects(entityInputs) {
|
|
4142
|
+
return entityInputs.map((e) => typeof e === "string" ? { name: e } : e);
|
|
4143
|
+
}
|
|
4144
|
+
async function prepareMemoryTopicsUpdate(ctx, memoryId, linked, inputs, source) {
|
|
4145
|
+
const row = await findVaultRowInWrite(ctx, memoryId);
|
|
4146
|
+
const write = resolveTopicsWrite(row, linked, displayNamesOf(toEntityObjects(inputs)), source);
|
|
4147
|
+
return write === null ? null : prepareTopicsUpdate(write.row, write.topics, Date.now());
|
|
4148
|
+
}
|
|
4071
4149
|
async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
|
|
4072
4150
|
if (entityInputs.length === 0) return [];
|
|
4073
|
-
const normalized = entityInputs
|
|
4151
|
+
const normalized = toEntityObjects(entityInputs);
|
|
4074
4152
|
const userId = ctx.userId;
|
|
4075
4153
|
let skipped = false;
|
|
4076
4154
|
let entities = [];
|
|
@@ -4080,7 +4158,8 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
|
|
|
4080
4158
|
normalized
|
|
4081
4159
|
);
|
|
4082
4160
|
entities = Array.from(byName.values());
|
|
4083
|
-
|
|
4161
|
+
const vaultRow = await findVaultRowInWrite(ctx, memoryId);
|
|
4162
|
+
if (options?.unlessTopicsUserManaged && autoLinkBlocked(vaultRow)) {
|
|
4084
4163
|
if (entityOps.length > 0) {
|
|
4085
4164
|
await ctx.database.batch(...entityOps);
|
|
4086
4165
|
}
|
|
@@ -4091,7 +4170,18 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
|
|
|
4091
4170
|
const existingLinks = await ctx.memoryEntityCollection.query(import_watermelondb8.Q.where("memory_id", memoryId)).fetch();
|
|
4092
4171
|
const existingEntityIds = new Set(existingLinks.map((l) => String(l.entityId)));
|
|
4093
4172
|
const toCreate = entities.filter((e) => !existingEntityIds.has(e.uniqueId));
|
|
4094
|
-
|
|
4173
|
+
const keptIds = entities.map((e) => e.uniqueId);
|
|
4174
|
+
const carriedOver = await resolveEntitiesByIds(
|
|
4175
|
+
ctx,
|
|
4176
|
+
[...existingEntityIds].filter((id) => !keptIds.includes(id))
|
|
4177
|
+
);
|
|
4178
|
+
const topicsWrite = resolveTopicsWrite(
|
|
4179
|
+
vaultRow,
|
|
4180
|
+
[...carriedOver, ...entities],
|
|
4181
|
+
displayNamesOf(normalized),
|
|
4182
|
+
options?.topicsSource ?? "auto"
|
|
4183
|
+
);
|
|
4184
|
+
if (entityOps.length === 0 && toCreate.length === 0 && topicsWrite === null) return;
|
|
4095
4185
|
const linkOps = toCreate.map(
|
|
4096
4186
|
(e) => ctx.memoryEntityCollection.prepareCreate((record) => {
|
|
4097
4187
|
record._setRaw("memory_id", memoryId);
|
|
@@ -4099,26 +4189,42 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
|
|
|
4099
4189
|
if (userId !== void 0) record._setRaw("user_id", userId);
|
|
4100
4190
|
})
|
|
4101
4191
|
);
|
|
4102
|
-
await ctx.database.batch(
|
|
4192
|
+
await ctx.database.batch(
|
|
4193
|
+
...entityOps,
|
|
4194
|
+
...linkOps,
|
|
4195
|
+
...topicsWrite ? [prepareTopicsUpdate(topicsWrite.row, topicsWrite.topics, Date.now())] : []
|
|
4196
|
+
);
|
|
4103
4197
|
});
|
|
4104
4198
|
return skipped ? [] : entities;
|
|
4105
4199
|
}
|
|
4106
|
-
async function
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
}
|
|
4200
|
+
async function resolveEntitiesByIds(ctx, entityIds) {
|
|
4201
|
+
if (entityIds.length === 0) return [];
|
|
4202
|
+
const rows = await ctx.entityCollection.query(import_watermelondb8.Q.where("id", import_watermelondb8.Q.oneOf(Array.from(entityIds)))).fetch();
|
|
4203
|
+
return rows.map(entityToStored);
|
|
4204
|
+
}
|
|
4205
|
+
function autoLinkBlocked(row) {
|
|
4206
|
+
return !row || !!row.isDeleted || !!row.topicsUserManaged;
|
|
4114
4207
|
}
|
|
4115
4208
|
async function replaceMemoryEntitiesGuardedOp(ctx, memoryId, entityInputs) {
|
|
4116
|
-
|
|
4209
|
+
return replaceMemoryEntities(ctx, memoryId, entityInputs, {
|
|
4210
|
+
guarded: true,
|
|
4211
|
+
topicsSource: "auto"
|
|
4212
|
+
});
|
|
4213
|
+
}
|
|
4214
|
+
async function relinkMemoryEntitiesFromTopicsOp(ctx, memoryId, topics) {
|
|
4215
|
+
return replaceMemoryEntities(ctx, memoryId, topics, {
|
|
4216
|
+
guarded: false,
|
|
4217
|
+
topicsSource: null
|
|
4218
|
+
});
|
|
4219
|
+
}
|
|
4220
|
+
async function replaceMemoryEntities(ctx, memoryId, entityInputs, options) {
|
|
4221
|
+
const normalized = toEntityObjects(entityInputs);
|
|
4117
4222
|
const userId = ctx.userId;
|
|
4118
4223
|
let skipped = false;
|
|
4119
4224
|
let entities = [];
|
|
4120
4225
|
await ctx.database.write(async () => {
|
|
4121
|
-
|
|
4226
|
+
const vaultRow = await findVaultRowInWrite(ctx, memoryId);
|
|
4227
|
+
if (options.guarded && autoLinkBlocked(vaultRow)) {
|
|
4122
4228
|
skipped = true;
|
|
4123
4229
|
return;
|
|
4124
4230
|
}
|
|
@@ -4132,7 +4238,15 @@ async function replaceMemoryEntitiesGuardedOp(ctx, memoryId, entityInputs) {
|
|
|
4132
4238
|
const existingEntityIds = new Set(existingLinks.map((l) => String(l.entityId)));
|
|
4133
4239
|
const toCreate = entities.filter((e) => !existingEntityIds.has(e.uniqueId));
|
|
4134
4240
|
const toDestroy = existingLinks.filter((l) => !keep.has(String(l.entityId)));
|
|
4135
|
-
|
|
4241
|
+
const topicsWrite = resolveTopicsWrite(
|
|
4242
|
+
vaultRow,
|
|
4243
|
+
entities,
|
|
4244
|
+
displayNamesOf(normalized),
|
|
4245
|
+
options.topicsSource
|
|
4246
|
+
);
|
|
4247
|
+
if (entityOps.length === 0 && toCreate.length === 0 && toDestroy.length === 0 && topicsWrite === null) {
|
|
4248
|
+
return;
|
|
4249
|
+
}
|
|
4136
4250
|
const orphans = await findOrphanedEntities(ctx, memoryId, toDestroy);
|
|
4137
4251
|
await ctx.database.batch(
|
|
4138
4252
|
...entityOps,
|
|
@@ -4144,7 +4258,8 @@ async function replaceMemoryEntitiesGuardedOp(ctx, memoryId, entityInputs) {
|
|
|
4144
4258
|
})
|
|
4145
4259
|
),
|
|
4146
4260
|
...toDestroy.map((l) => l.prepareDestroyPermanently()),
|
|
4147
|
-
...orphans.map((e) => e.prepareDestroyPermanently())
|
|
4261
|
+
...orphans.map((e) => e.prepareDestroyPermanently()),
|
|
4262
|
+
...topicsWrite ? [prepareTopicsUpdate(topicsWrite.row, topicsWrite.topics, Date.now())] : []
|
|
4148
4263
|
);
|
|
4149
4264
|
});
|
|
4150
4265
|
return skipped ? null : entities;
|
|
@@ -4343,6 +4458,8 @@ function vaultMemoryToStoredRaw(memory) {
|
|
|
4343
4458
|
eventTimeEnd: memory.eventTimeEnd ?? null,
|
|
4344
4459
|
eventTimeKind: memory.eventTimeKind ?? null,
|
|
4345
4460
|
topicsUserManaged: memory.topicsUserManaged ?? false,
|
|
4461
|
+
topics: parseTopics(memory.topics),
|
|
4462
|
+
topicsUpdatedAt: memory.topicsUpdatedAt ?? null,
|
|
4346
4463
|
topicsExtractedAt: memory.topicsExtractedAt ?? null,
|
|
4347
4464
|
topicsExtractedVersion: memory.topicsExtractedVersion ?? null,
|
|
4348
4465
|
supersededBy: memory.supersededBy ?? null,
|
|
@@ -4619,6 +4736,8 @@ function vaultMemoryRawToStoredRaw(raw) {
|
|
|
4619
4736
|
eventTimeKind: raw.event_time_kind ?? null,
|
|
4620
4737
|
// SQLite stores booleans as 0/1, LokiJS as true/false — coerce both.
|
|
4621
4738
|
topicsUserManaged: raw.topics_user_managed === true || raw.topics_user_managed === 1,
|
|
4739
|
+
topics: parseTopics(raw.topics),
|
|
4740
|
+
topicsUpdatedAt: raw.topics_updated_at ?? null,
|
|
4622
4741
|
topicsExtractedAt: raw.topics_extracted_at ?? null,
|
|
4623
4742
|
topicsExtractedVersion: raw.topics_extracted_version ?? null,
|
|
4624
4743
|
supersededBy: raw.superseded_by ?? null,
|
|
@@ -4965,56 +5084,111 @@ async function getUnfiledVaultMemoriesOp(ctx) {
|
|
|
4965
5084
|
(record) => vaultMemoryToStored(record, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner)
|
|
4966
5085
|
);
|
|
4967
5086
|
}
|
|
5087
|
+
async function linkedEntityNamesByMemory(entityCtx, memoryIds) {
|
|
5088
|
+
const CHUNK = 500;
|
|
5089
|
+
const entityIdByMemory = /* @__PURE__ */ new Map();
|
|
5090
|
+
const allEntityIds = /* @__PURE__ */ new Set();
|
|
5091
|
+
for (let i = 0; i < memoryIds.length; i += CHUNK) {
|
|
5092
|
+
const links = await entityCtx.memoryEntityCollection.query(import_watermelondb9.Q.where("memory_id", import_watermelondb9.Q.oneOf(memoryIds.slice(i, i + CHUNK)))).unsafeFetchRaw();
|
|
5093
|
+
for (const link of links) {
|
|
5094
|
+
const memoryId = String(link.memory_id);
|
|
5095
|
+
const entityId = String(link.entity_id);
|
|
5096
|
+
allEntityIds.add(entityId);
|
|
5097
|
+
let bucket = entityIdByMemory.get(memoryId);
|
|
5098
|
+
if (!bucket) {
|
|
5099
|
+
bucket = /* @__PURE__ */ new Set();
|
|
5100
|
+
entityIdByMemory.set(memoryId, bucket);
|
|
5101
|
+
}
|
|
5102
|
+
bucket.add(entityId);
|
|
5103
|
+
}
|
|
5104
|
+
}
|
|
5105
|
+
const entityIds = Array.from(allEntityIds);
|
|
5106
|
+
const nameById = /* @__PURE__ */ new Map();
|
|
5107
|
+
for (let i = 0; i < entityIds.length; i += CHUNK) {
|
|
5108
|
+
const entities = await entityCtx.entityCollection.query(import_watermelondb9.Q.where("id", import_watermelondb9.Q.oneOf(entityIds.slice(i, i + CHUNK)))).unsafeFetchRaw();
|
|
5109
|
+
for (const e of entities) {
|
|
5110
|
+
if (typeof e.canonical_name === "string") nameById.set(String(e.id), e.canonical_name);
|
|
5111
|
+
}
|
|
5112
|
+
}
|
|
5113
|
+
const out = /* @__PURE__ */ new Map();
|
|
5114
|
+
for (const [memoryId, ids] of entityIdByMemory) {
|
|
5115
|
+
const names = /* @__PURE__ */ new Set();
|
|
5116
|
+
for (const id of ids) {
|
|
5117
|
+
const name = nameById.get(id);
|
|
5118
|
+
if (name !== void 0) names.add(name);
|
|
5119
|
+
}
|
|
5120
|
+
if (names.size > 0) out.set(memoryId, names);
|
|
5121
|
+
}
|
|
5122
|
+
return out;
|
|
5123
|
+
}
|
|
5124
|
+
function linksDivergeFromTopics(topics, linked) {
|
|
5125
|
+
const wanted = new Set(
|
|
5126
|
+
topics.map((t) => normalizeEntityName(t.name)).filter((n) => n.length > 0)
|
|
5127
|
+
);
|
|
5128
|
+
if (wanted.size !== linked.size) return true;
|
|
5129
|
+
for (const name of wanted) if (!linked.has(name)) return true;
|
|
5130
|
+
return false;
|
|
5131
|
+
}
|
|
4968
5132
|
async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
|
|
4969
5133
|
const entityCtx = ctx.entityCtx;
|
|
4970
5134
|
if (!entityCtx) {
|
|
4971
5135
|
throw new Error("getMemoriesNeedingTopicExtractionOp requires ctx.entityCtx");
|
|
4972
5136
|
}
|
|
4973
|
-
const conditions = [
|
|
4974
|
-
// Boolean optional column: null (legacy) and false both mean auto-managed.
|
|
4975
|
-
// Q.notEq(true) is a NULL trap on SQLite (NULL <> TRUE is NULL) — enumerate.
|
|
4976
|
-
import_watermelondb9.Q.or(import_watermelondb9.Q.where("topics_user_managed", null), import_watermelondb9.Q.where("topics_user_managed", false)),
|
|
4977
|
-
...baseVaultConditions(ctx),
|
|
4978
|
-
import_watermelondb9.Q.sortBy("created_at", import_watermelondb9.Q.desc)
|
|
4979
|
-
];
|
|
5137
|
+
const conditions = [...baseVaultConditions(ctx), import_watermelondb9.Q.sortBy("created_at", import_watermelondb9.Q.desc)];
|
|
4980
5138
|
const rows = await ctx.vaultMemoryCollection.query(...conditions).unsafeFetchRaw();
|
|
4981
|
-
const
|
|
4982
|
-
|
|
5139
|
+
const linkedNames = await linkedEntityNamesByMemory(
|
|
5140
|
+
entityCtx,
|
|
5141
|
+
rows.map((r) => r.id)
|
|
5142
|
+
);
|
|
5143
|
+
const pendingRaw = [];
|
|
5144
|
+
const stampedPendingRaw = [];
|
|
5145
|
+
const linkedUnstampedAll = [];
|
|
5146
|
+
const topicsToRelinkAll = [];
|
|
5147
|
+
const topicsBackfillAll = [];
|
|
4983
5148
|
for (const raw of rows) {
|
|
5149
|
+
const id = raw.id;
|
|
5150
|
+
const topics = parseTopics(raw.topics);
|
|
5151
|
+
const linked = linkedNames.get(id);
|
|
5152
|
+
if (topics !== null && topics.length > 0 && linksDivergeFromTopics(topics, linked ?? /* @__PURE__ */ new Set())) {
|
|
5153
|
+
topicsToRelinkAll.push(id);
|
|
5154
|
+
continue;
|
|
5155
|
+
}
|
|
5156
|
+
if (raw.topics_user_managed) {
|
|
5157
|
+
if (topics === null && linked !== void 0) topicsBackfillAll.push(id);
|
|
5158
|
+
continue;
|
|
5159
|
+
}
|
|
4984
5160
|
const stamp = raw.topics_extracted_at ?? null;
|
|
5161
|
+
let isPending = false;
|
|
4985
5162
|
if (stamp !== null) {
|
|
4986
5163
|
const version = raw.topics_extracted_version ?? 0;
|
|
4987
5164
|
if (raw.updated_at > stamp || version < TOPICS_EXTRACTION_VERSION) {
|
|
4988
|
-
|
|
5165
|
+
stampedPendingRaw.push(raw);
|
|
5166
|
+
isPending = true;
|
|
4989
5167
|
}
|
|
4990
|
-
} else {
|
|
4991
|
-
|
|
4992
|
-
}
|
|
4993
|
-
}
|
|
4994
|
-
const linkedIds = /* @__PURE__ */ new Set();
|
|
4995
|
-
const CHUNK = 500;
|
|
4996
|
-
for (let i = 0; i < unstamped.length; i += CHUNK) {
|
|
4997
|
-
const ids = unstamped.slice(i, i + CHUNK).map((r) => r.id);
|
|
4998
|
-
const links = await entityCtx.memoryEntityCollection.query(import_watermelondb9.Q.where("memory_id", import_watermelondb9.Q.oneOf(ids))).unsafeFetchRaw();
|
|
4999
|
-
for (const link of links) linkedIds.add(String(link.memory_id));
|
|
5000
|
-
}
|
|
5001
|
-
const linkedUnstampedAll = [];
|
|
5002
|
-
const pendingRaw = [...stampedPending];
|
|
5003
|
-
for (const raw of unstamped) {
|
|
5004
|
-
if (linkedIds.has(raw.id)) {
|
|
5005
|
-
linkedUnstampedAll.push(raw.id);
|
|
5168
|
+
} else if (linked !== void 0) {
|
|
5169
|
+
linkedUnstampedAll.push(id);
|
|
5006
5170
|
} else {
|
|
5007
5171
|
pendingRaw.push(raw);
|
|
5172
|
+
isPending = true;
|
|
5173
|
+
}
|
|
5174
|
+
if (!isPending && topics === null && linked !== void 0) {
|
|
5175
|
+
topicsBackfillAll.push(id);
|
|
5008
5176
|
}
|
|
5009
5177
|
}
|
|
5010
5178
|
const cap = options?.limit !== void 0 && options.limit > 0 ? options.limit : void 0;
|
|
5011
|
-
const
|
|
5012
|
-
const
|
|
5179
|
+
const orderedPendingRaw = [...stampedPendingRaw, ...pendingRaw];
|
|
5180
|
+
const limitedPendingRaw = cap !== void 0 ? orderedPendingRaw.slice(0, cap) : orderedPendingRaw;
|
|
5013
5181
|
const pending = await mapInBatches(
|
|
5014
5182
|
limitedPendingRaw,
|
|
5015
5183
|
(raw) => vaultMemoryRawToStored(raw, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner)
|
|
5016
5184
|
);
|
|
5017
|
-
|
|
5185
|
+
const applyCap = (ids) => cap !== void 0 ? ids.slice(0, cap) : ids;
|
|
5186
|
+
return {
|
|
5187
|
+
pending,
|
|
5188
|
+
linkedUnstamped: applyCap(linkedUnstampedAll),
|
|
5189
|
+
topicsToRelink: applyCap(topicsToRelinkAll),
|
|
5190
|
+
topicsBackfill: applyCap(topicsBackfillAll)
|
|
5191
|
+
};
|
|
5018
5192
|
}
|
|
5019
5193
|
async function stampTopicsExtractedAtOp(ctx, memoryIds, extractedAt, version = TOPICS_EXTRACTION_VERSION) {
|
|
5020
5194
|
if (memoryIds.length === 0) return [];
|
|
@@ -5048,6 +5222,61 @@ async function stampTopicsExtractedAtOp(ctx, memoryIds, extractedAt, version = T
|
|
|
5048
5222
|
}
|
|
5049
5223
|
return stamped;
|
|
5050
5224
|
}
|
|
5225
|
+
async function relinkMemoryTopicsOp(ctx, memoryIds) {
|
|
5226
|
+
const entityCtx = ctx.entityCtx;
|
|
5227
|
+
if (!entityCtx) {
|
|
5228
|
+
throw new Error("relinkMemoryTopicsOp requires ctx.entityCtx");
|
|
5229
|
+
}
|
|
5230
|
+
const relinked = [];
|
|
5231
|
+
for (const id of Array.from(new Set(memoryIds))) {
|
|
5232
|
+
let record;
|
|
5233
|
+
try {
|
|
5234
|
+
record = await ctx.vaultMemoryCollection.find(id);
|
|
5235
|
+
} catch {
|
|
5236
|
+
continue;
|
|
5237
|
+
}
|
|
5238
|
+
if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) continue;
|
|
5239
|
+
const topics = parseTopics(record.topics);
|
|
5240
|
+
if (topics === null || topics.length === 0) continue;
|
|
5241
|
+
try {
|
|
5242
|
+
await relinkMemoryEntitiesFromTopicsOp(entityCtx, id, topics);
|
|
5243
|
+
relinked.push(id);
|
|
5244
|
+
} catch (err) {
|
|
5245
|
+
getLogger().warn("[memory/topics] relink failed", err);
|
|
5246
|
+
}
|
|
5247
|
+
}
|
|
5248
|
+
return relinked;
|
|
5249
|
+
}
|
|
5250
|
+
async function backfillMemoryTopicsOp(ctx, memoryIds) {
|
|
5251
|
+
const entityCtx = ctx.entityCtx;
|
|
5252
|
+
if (!entityCtx) {
|
|
5253
|
+
throw new Error("backfillMemoryTopicsOp requires ctx.entityCtx");
|
|
5254
|
+
}
|
|
5255
|
+
const filled = [];
|
|
5256
|
+
for (const id of Array.from(new Set(memoryIds))) {
|
|
5257
|
+
let record;
|
|
5258
|
+
try {
|
|
5259
|
+
record = await ctx.vaultMemoryCollection.find(id);
|
|
5260
|
+
} catch {
|
|
5261
|
+
continue;
|
|
5262
|
+
}
|
|
5263
|
+
if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) continue;
|
|
5264
|
+
if (parseTopics(record.topics) !== null) continue;
|
|
5265
|
+
const links = await entityCtx.memoryEntityCollection.query(import_watermelondb9.Q.where("memory_id", id)).unsafeFetchRaw();
|
|
5266
|
+
const entityIds = links.map((l) => String(l.entity_id));
|
|
5267
|
+
if (entityIds.length === 0) continue;
|
|
5268
|
+
const entities = await entityCtx.entityCollection.query(import_watermelondb9.Q.where("id", import_watermelondb9.Q.oneOf(entityIds))).fetch();
|
|
5269
|
+
if (entities.length === 0) continue;
|
|
5270
|
+
const source = record.topicsUserManaged ? "user" : "auto";
|
|
5271
|
+
await ctx.database.write(async () => {
|
|
5272
|
+
const topicsOp = await prepareMemoryTopicsUpdate(entityCtx, id, entities, [], source);
|
|
5273
|
+
if (!topicsOp) return;
|
|
5274
|
+
await ctx.database.batch(topicsOp);
|
|
5275
|
+
filled.push(id);
|
|
5276
|
+
});
|
|
5277
|
+
}
|
|
5278
|
+
return filled;
|
|
5279
|
+
}
|
|
5051
5280
|
async function deleteAllVaultMemoriesForUserOp(ctx, userId) {
|
|
5052
5281
|
if (ctx.userId !== void 0 && ctx.userId !== userId) return 0;
|
|
5053
5282
|
const records = await ctx.vaultMemoryCollection.query(import_watermelondb9.Q.where("user_id", userId), import_watermelondb9.Q.where("is_deleted", false)).fetch();
|
|
@@ -5189,6 +5418,7 @@ var init_operations3 = __esm({
|
|
|
5189
5418
|
import_watermelondb9 = require("@nozbe/watermelondb");
|
|
5190
5419
|
init_logger();
|
|
5191
5420
|
init_operations2();
|
|
5421
|
+
init_types3();
|
|
5192
5422
|
init_encryption2();
|
|
5193
5423
|
NON_PRIVATE_VISIBILITIES = ["public"];
|
|
5194
5424
|
KNOWN_TRUST_TIERS = /* @__PURE__ */ new Set(["quarantined", "trusted"]);
|
|
@@ -8385,6 +8615,7 @@ __export(index_exports, {
|
|
|
8385
8615
|
WalletPoller: () => WalletPoller,
|
|
8386
8616
|
addConversationMemoriesOp: () => addConversationMemoriesOp,
|
|
8387
8617
|
archiveVaultMemoryOp: () => archiveVaultMemoryOp,
|
|
8618
|
+
backfillMemoryTopicsOp: () => backfillMemoryTopicsOp,
|
|
8388
8619
|
buildNotionAuthUrl: () => buildNotionAuthUrl,
|
|
8389
8620
|
capHopsForDensity: () => capHopsForDensity,
|
|
8390
8621
|
chatStorageMigrations: () => chatStorageMigrations,
|
|
@@ -8482,6 +8713,7 @@ __export(index_exports, {
|
|
|
8482
8713
|
reflect: () => reflect,
|
|
8483
8714
|
refreshNotionAccessToken: () => refreshNotionAccessToken,
|
|
8484
8715
|
registerNotionClient: () => registerNotionClient,
|
|
8716
|
+
relinkMemoryTopicsOp: () => relinkMemoryTopicsOp,
|
|
8485
8717
|
requestEncryptionKey: () => requestEncryptionKey,
|
|
8486
8718
|
resolvePiiRedactor: () => resolvePiiRedactor,
|
|
8487
8719
|
restoreVaultMemoryOp: () => restoreVaultMemoryOp,
|
|
@@ -14027,6 +14259,12 @@ __decorateClass([
|
|
|
14027
14259
|
__decorateClass([
|
|
14028
14260
|
(0, import_decorators3.field)("topics_user_managed")
|
|
14029
14261
|
], VaultMemory.prototype, "topicsUserManaged", 2);
|
|
14262
|
+
__decorateClass([
|
|
14263
|
+
(0, import_decorators3.field)("topics")
|
|
14264
|
+
], VaultMemory.prototype, "topics", 2);
|
|
14265
|
+
__decorateClass([
|
|
14266
|
+
(0, import_decorators3.field)("topics_updated_at")
|
|
14267
|
+
], VaultMemory.prototype, "topicsUpdatedAt", 2);
|
|
14030
14268
|
__decorateClass([
|
|
14031
14269
|
(0, import_decorators3.field)("topics_extracted_at")
|
|
14032
14270
|
], VaultMemory.prototype, "topicsExtractedAt", 2);
|
|
@@ -20294,7 +20532,7 @@ __decorateClass([
|
|
|
20294
20532
|
], VaultFolder.prototype, "context", 2);
|
|
20295
20533
|
|
|
20296
20534
|
// src/lib/db/schema.ts
|
|
20297
|
-
var SDK_SCHEMA_VERSION =
|
|
20535
|
+
var SDK_SCHEMA_VERSION = 42;
|
|
20298
20536
|
var sdkSchema = (0, import_watermelondb19.appSchema)({
|
|
20299
20537
|
version: SDK_SCHEMA_VERSION,
|
|
20300
20538
|
tables: [
|
|
@@ -20416,9 +20654,33 @@ var sdkSchema = (0, import_watermelondb19.appSchema)({
|
|
|
20416
20654
|
// links). Auto-extraction then leaves its links alone — the user owns
|
|
20417
20655
|
// them. Null/false = topics are auto-derived (default).
|
|
20418
20656
|
{ name: "topics_user_managed", type: "boolean", isOptional: true },
|
|
20657
|
+
// The memory's topics as a DURABLE, SYNCED record: a JSON array of
|
|
20658
|
+
// `{name, kind?, source}`, `name` in the caller's display casing (unlike
|
|
20659
|
+
// `entity.canonical_name`, which is lowercased and has no display
|
|
20660
|
+
// column). `entity` / `memory_entity` are a device-local INDEX over this
|
|
20661
|
+
// — their ids are locally generated, so they can never sync — and a
|
|
20662
|
+
// restored device rebuilds them from these names with no LLM call.
|
|
20663
|
+
// Null = predates v42 (backfilled from the row's current links by the
|
|
20664
|
+
// sweep's topicsBackfill bucket). See getMemoriesNeedingTopicExtractionOp.
|
|
20665
|
+
{ name: "topics", type: "string", isOptional: true },
|
|
20666
|
+
// Unix ms of the last write to `topics`. A SECOND timestamp is required:
|
|
20667
|
+
// every topic writer deliberately pins `updated_at` so a topic change
|
|
20668
|
+
// doesn't inflate recall's recency multiplier, and both client sync
|
|
20669
|
+
// paths key on `updated_at` — so without this a topic-only change would
|
|
20670
|
+
// neither upload nor merge. Null = `topics` never written.
|
|
20671
|
+
{ name: "topics_updated_at", type: "number", isOptional: true },
|
|
20419
20672
|
// Unix ms of the last LLM topic-extraction pass over this memory's
|
|
20420
20673
|
// content. Null = never extracted standalone (legacy rows with entity
|
|
20421
20674
|
// links are grandfathered — see getMemoriesNeedingTopicExtractionOp).
|
|
20675
|
+
//
|
|
20676
|
+
// DEPRECATED (v42) — `topics_updated_at` subsumes this and
|
|
20677
|
+
// `topics_extracted_version` both: null there means never processed, and
|
|
20678
|
+
// non-null with an empty `topics` means processed and found nothing
|
|
20679
|
+
// (today's "answered empty" case), while a release-time
|
|
20680
|
+
// EXTRACTOR_CHANGED_AT constant compared against `topics_updated_at`
|
|
20681
|
+
// replaces the version check. Both columns are kept only to avoid a
|
|
20682
|
+
// column-drop migration in an otherwise additive list; removing them is
|
|
20683
|
+
// a follow-up once `topics` is proven in production.
|
|
20422
20684
|
{ name: "topics_extracted_at", type: "number", isOptional: true },
|
|
20423
20685
|
// Write-time supersession (A2). When set, this fact was replaced by a
|
|
20424
20686
|
// newer, incompatible-value fact (e.g. "Lives in Portland" superseded by
|
|
@@ -20432,6 +20694,9 @@ var sdkSchema = (0, import_watermelondb19.appSchema)({
|
|
|
20432
20694
|
// The extraction-logic version this memory was last stamped under. Null
|
|
20433
20695
|
// (pre-v38) is treated as version 0, so a bump of TOPICS_EXTRACTION_VERSION
|
|
20434
20696
|
// re-extracts stale rows. See getMemoriesNeedingTopicExtractionOp.
|
|
20697
|
+
//
|
|
20698
|
+
// DEPRECATED (v42) alongside `topics_extracted_at` — same rationale and
|
|
20699
|
+
// same removal follow-up; see that column's note above.
|
|
20435
20700
|
{ name: "topics_extracted_version", type: "number", isOptional: true },
|
|
20436
20701
|
// Re-observation watermark (C3). Unix ms of the last time retain() merged
|
|
20437
20702
|
// a duplicate observation into this fact (proof_count++). Distinct from
|
|
@@ -21165,6 +21430,23 @@ var sdkMigrations = (0, import_migrations2.schemaMigrations)({
|
|
|
21165
21430
|
]
|
|
21166
21431
|
})
|
|
21167
21432
|
]
|
|
21433
|
+
},
|
|
21434
|
+
// v41 -> v42: topics + topics_updated_at on memory_vault — the durable,
|
|
21435
|
+
// synced record of a memory's topics. Existing rows keep both NULL: the
|
|
21436
|
+
// sweep's topicsBackfill bucket fills them from each row's current entity
|
|
21437
|
+
// links (no LLM), capped under the worker's `limit` so the one-time
|
|
21438
|
+
// re-upload it triggers drains across sweeps instead of spiking.
|
|
21439
|
+
{
|
|
21440
|
+
toVersion: 42,
|
|
21441
|
+
steps: [
|
|
21442
|
+
(0, import_migrations2.addColumns)({
|
|
21443
|
+
table: "memory_vault",
|
|
21444
|
+
columns: [
|
|
21445
|
+
{ name: "topics", type: "string", isOptional: true },
|
|
21446
|
+
{ name: "topics_updated_at", type: "number", isOptional: true }
|
|
21447
|
+
]
|
|
21448
|
+
})
|
|
21449
|
+
]
|
|
21168
21450
|
}
|
|
21169
21451
|
]
|
|
21170
21452
|
});
|
|
@@ -22550,6 +22832,7 @@ function createNotionTools(getAccessToken, requestNotionAccess) {
|
|
|
22550
22832
|
WalletPoller,
|
|
22551
22833
|
addConversationMemoriesOp,
|
|
22552
22834
|
archiveVaultMemoryOp,
|
|
22835
|
+
backfillMemoryTopicsOp,
|
|
22553
22836
|
buildNotionAuthUrl,
|
|
22554
22837
|
capHopsForDensity,
|
|
22555
22838
|
chatStorageMigrations,
|
|
@@ -22647,6 +22930,7 @@ function createNotionTools(getAccessToken, requestNotionAccess) {
|
|
|
22647
22930
|
reflect,
|
|
22648
22931
|
refreshNotionAccessToken,
|
|
22649
22932
|
registerNotionClient,
|
|
22933
|
+
relinkMemoryTopicsOp,
|
|
22650
22934
|
requestEncryptionKey,
|
|
22651
22935
|
resolvePiiRedactor,
|
|
22652
22936
|
restoreVaultMemoryOp,
|