@anuma/sdk 6.21.1 → 6.23.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-CIMZZ7GQ.mjs → chunk-6BULYLRY.mjs} +92 -8
- package/dist/expo/index.cjs +761 -162
- package/dist/expo/index.d.mts +151 -9
- package/dist/expo/index.d.ts +151 -9
- package/dist/expo/index.mjs +516 -14
- package/dist/{react/recall-7Q3V337H.mjs → expo/recall-CUTJWQDQ.mjs} +1 -1
- package/dist/memory/context.cjs +183 -0
- package/dist/memory/context.d.mts +936 -0
- package/dist/memory/context.d.ts +936 -0
- package/dist/memory/context.mjs +157 -0
- package/dist/react/{chunk-AVYB5IXC.mjs → chunk-GPLMMMZN.mjs} +1 -1
- package/dist/react/{chunk-JRVAUZHM.mjs → chunk-K4N52PSK.mjs} +93 -9
- package/dist/react/{consolidate-URHGJT7I.mjs → consolidate-XKS6IZFC.mjs} +2 -2
- package/dist/react/index.cjs +876 -270
- package/dist/react/index.d.mts +209 -9
- package/dist/react/index.d.ts +209 -9
- package/dist/react/index.mjs +569 -65
- package/dist/{expo/recall-FQQGE6A2.mjs → react/recall-LFHIWONV.mjs} +2 -2
- package/dist/server/{chunk-4DSDATYG.mjs → chunk-FEDRFVHD.mjs} +182 -98
- package/dist/server/index.cjs +815 -216
- package/dist/server/index.d.mts +150 -5
- package/dist/server/index.d.ts +150 -5
- package/dist/server/index.mjs +509 -7
- package/dist/server/{recall-HA4UX4BB.mjs → recall-Z2WJGXOL.mjs} +1 -1
- package/dist/telemetry/index.d.mts +7 -1
- package/dist/telemetry/index.d.ts +7 -1
- package/dist/tools/index.cjs +347 -1
- package/dist/tools/index.d.mts +167 -1
- package/dist/tools/index.d.ts +167 -1
- package/dist/tools/index.mjs +343 -1
- package/package.json +8 -1
- package/dist/react/{chunk-WUG6GTHO.mjs → chunk-MHZB6KIX.mjs} +8 -8
package/dist/expo/index.mjs
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
ENTITY_FANOUT,
|
|
12
12
|
ENTITY_KINDS,
|
|
13
13
|
EncryptionKeyMissingError,
|
|
14
|
+
ExtractionJob,
|
|
14
15
|
LRUCache,
|
|
15
16
|
MAX_HOPS,
|
|
16
17
|
NODE_BUDGET,
|
|
@@ -52,6 +53,7 @@ import {
|
|
|
52
53
|
decryptJsonField,
|
|
53
54
|
deleteAllVaultMemoriesForUserOp,
|
|
54
55
|
deleteConversationOp,
|
|
56
|
+
deleteMessageOp,
|
|
55
57
|
deleteVaultMemoryOp,
|
|
56
58
|
eagerEmbedContent,
|
|
57
59
|
embedAllMessages,
|
|
@@ -133,7 +135,7 @@ import {
|
|
|
133
135
|
upsertMessageOp,
|
|
134
136
|
useEncryption,
|
|
135
137
|
vaultMemoryToStored
|
|
136
|
-
} from "./chunk-
|
|
138
|
+
} from "./chunk-6BULYLRY.mjs";
|
|
137
139
|
import {
|
|
138
140
|
PII_PATTERNS,
|
|
139
141
|
PiiRedactor,
|
|
@@ -5802,7 +5804,8 @@ function classifyDecay(m, now, policy) {
|
|
|
5802
5804
|
if (m.eventTimeEnd < now - resolved.pastEventGraceMs) return "archive";
|
|
5803
5805
|
if (m.eventTimeEnd >= now) return "keep";
|
|
5804
5806
|
}
|
|
5805
|
-
|
|
5807
|
+
const observedAt = Number.isFinite(m.lastObservedAt) ? m.lastObservedAt : m.updatedAt;
|
|
5808
|
+
if (now - Math.max(m.updatedAt, observedAt) > ttlForType(m.factType, resolved)) return "archive";
|
|
5806
5809
|
return "keep";
|
|
5807
5810
|
}
|
|
5808
5811
|
|
|
@@ -6707,6 +6710,7 @@ async function retain(content, ctx, options = {}) {
|
|
|
6707
6710
|
const updated = await updateVaultMemoryOp(ctx.vaultCtx, targetId, {
|
|
6708
6711
|
content: existing.content,
|
|
6709
6712
|
proofCountIncrement: 1,
|
|
6713
|
+
observationSourceIds: options.sourceChunkIds,
|
|
6710
6714
|
sourceChunkIds: mergedSourceIds,
|
|
6711
6715
|
// resurrect encodes the decay gate: ACTIVE target → { preserveUpdatedAt:
|
|
6712
6716
|
// true } (main's normal re-observation path — bump proof_count without
|
|
@@ -6950,6 +6954,7 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
|
|
|
6950
6954
|
const updated = await updateVaultMemoryOp(ctx.vaultCtx, decision.targetId, {
|
|
6951
6955
|
content: existing.content,
|
|
6952
6956
|
proofCountIncrement: 1,
|
|
6957
|
+
observationSourceIds: options.sourceChunkIds,
|
|
6953
6958
|
sourceChunkIds: mergedSourceIds,
|
|
6954
6959
|
// ACTIVE target → { preserveUpdatedAt: true } (main's normal re-observation
|
|
6955
6960
|
// path); ARCHIVED (non-superseded, non-deleted) → { restore: true } and no
|
|
@@ -6999,6 +7004,7 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
|
|
|
6999
7004
|
const updated = await updateVaultMemoryOp(ctx.vaultCtx, decision.targetId, {
|
|
7000
7005
|
content: decision.content,
|
|
7001
7006
|
proofCountIncrement: 1,
|
|
7007
|
+
observationSourceIds: options.sourceChunkIds,
|
|
7002
7008
|
sourceChunkIds: mergedSourceIds,
|
|
7003
7009
|
embedding: JSON.stringify(newEmbedding),
|
|
7004
7010
|
embeddingModel: consolidatedModel,
|
|
@@ -7635,7 +7641,7 @@ function createAutoExtractor(options) {
|
|
|
7635
7641
|
onQuarantined: (info) => options.onMemoryQuarantined?.({ ...info, conversationId })
|
|
7636
7642
|
}
|
|
7637
7643
|
});
|
|
7638
|
-
if (outcome !== "empty-after-retry") {
|
|
7644
|
+
if (outcome !== "empty-after-retry" && failedCount === 0) {
|
|
7639
7645
|
const advancedTo = window[window.length - 1].id;
|
|
7640
7646
|
stateFor(conversationId).watermark = advancedTo;
|
|
7641
7647
|
persistCursor(conversationId, messages, advancedTo, contiguous);
|
|
@@ -7790,6 +7796,7 @@ function toDecayInput(c) {
|
|
|
7790
7796
|
eventTimeEnd: c.eventTimeEnd,
|
|
7791
7797
|
eventTimeKind: c.eventTimeKind,
|
|
7792
7798
|
updatedAt: c.updatedAt,
|
|
7799
|
+
lastObservedAt: c.lastObservedAt,
|
|
7793
7800
|
archivedAt: c.archivedAt,
|
|
7794
7801
|
source: c.source,
|
|
7795
7802
|
trustTier: c.trustTier
|
|
@@ -7871,7 +7878,8 @@ function createDecaySweeper(options) {
|
|
|
7871
7878
|
for (const c of toArchive) {
|
|
7872
7879
|
const ok = await archiveVaultMemoryOp(vaultCtx, c.uniqueId, {
|
|
7873
7880
|
now,
|
|
7874
|
-
expectedUpdatedAt: c.updatedAt
|
|
7881
|
+
expectedUpdatedAt: c.updatedAt,
|
|
7882
|
+
expectedLastObservedAt: c.lastObservedAt ?? null
|
|
7875
7883
|
});
|
|
7876
7884
|
if (ok) archived++;
|
|
7877
7885
|
}
|
|
@@ -8116,6 +8124,7 @@ function createRecallTool(ctx, toolOptions, callbacks) {
|
|
|
8116
8124
|
budget: defaultBudget,
|
|
8117
8125
|
...toolOptions?.minScore !== void 0 && { minScore: toolOptions.minScore },
|
|
8118
8126
|
...toolOptions?.scopes && { scopes: toolOptions.scopes },
|
|
8127
|
+
...toolOptions?.memoryIds !== void 0 && { memoryIds: toolOptions.memoryIds },
|
|
8119
8128
|
...toolOptions?.folderId !== void 0 && { folderId: toolOptions.folderId },
|
|
8120
8129
|
...toolOptions?.excludeConversationId && {
|
|
8121
8130
|
excludeConversationId: toolOptions.excludeConversationId
|
|
@@ -9305,6 +9314,462 @@ function createMessageSourceResolver(ctx) {
|
|
|
9305
9314
|
};
|
|
9306
9315
|
}
|
|
9307
9316
|
|
|
9317
|
+
// src/lib/memory/context.ts
|
|
9318
|
+
function shouldRecallMemory(query) {
|
|
9319
|
+
const normalized = query.trim().toLowerCase().replace(/[.!?。!?¡¿]+$/u, "").replace(/^[¡¿]+/u, "");
|
|
9320
|
+
return normalized.length > 0 && !/^(hi|hi there|hello|hey|yes|no|yep|nope|sure|k|ok|okay|thanks|thank you|thank you very much|thanks a lot|cool|great|got it|sounds good|hola|buenos días|buenas|sí|si|gracias|muchas gracias|vale|bonjour|salut|oui|non|merci|merci beaucoup|d'accord|hallo|guten tag|ja|nein|danke|danke schön|alles klar|olá|obrigado|obrigada|ciao|grazie|sì|你好|谢谢|好的|こんにちは|ありがとう|안녕하세요|감사합니다)$/u.test(
|
|
9321
|
+
normalized
|
|
9322
|
+
);
|
|
9323
|
+
}
|
|
9324
|
+
async function assembleMemoryContext(options) {
|
|
9325
|
+
const { query, recall: recall2, loadFacts, loadSessionRefs } = options;
|
|
9326
|
+
const memoryIds = options.memoryIds ?? options.recallOptions?.memoryIds;
|
|
9327
|
+
const ranked = shouldRecallMemory(query);
|
|
9328
|
+
const degraded = [];
|
|
9329
|
+
const guard = async (lane, run, fallback) => {
|
|
9330
|
+
try {
|
|
9331
|
+
return await run();
|
|
9332
|
+
} catch (error) {
|
|
9333
|
+
degraded.push(lane);
|
|
9334
|
+
try {
|
|
9335
|
+
options.onDegraded?.(lane, error);
|
|
9336
|
+
} catch {
|
|
9337
|
+
}
|
|
9338
|
+
return fallback;
|
|
9339
|
+
}
|
|
9340
|
+
};
|
|
9341
|
+
const scoped = memoryIds !== void 0 || options.recallOptions?.scopes !== void 0 || options.recallOptions?.folderId !== void 0;
|
|
9342
|
+
const allowed = memoryIds !== void 0 ? new Set(memoryIds) : void 0;
|
|
9343
|
+
const filters = {
|
|
9344
|
+
...options.recallOptions?.scopes && { scopes: options.recallOptions.scopes },
|
|
9345
|
+
...options.recallOptions?.folderId !== void 0 && {
|
|
9346
|
+
folderId: options.recallOptions.folderId
|
|
9347
|
+
}
|
|
9348
|
+
};
|
|
9349
|
+
const active = (m) => !m.isDeleted && !m.archivedAt && !m.supersededBy && m.trustTier !== "quarantined" && (!allowed || allowed.has(m.uniqueId));
|
|
9350
|
+
const asItem = (m, lane, score = 1) => ({
|
|
9351
|
+
id: m.uniqueId,
|
|
9352
|
+
content: m.content,
|
|
9353
|
+
score,
|
|
9354
|
+
lane,
|
|
9355
|
+
sourceIds: m.sourceChunkIds ?? []
|
|
9356
|
+
});
|
|
9357
|
+
const empty = {
|
|
9358
|
+
memories: [],
|
|
9359
|
+
usedBudget: options.recallOptions?.budget ?? "low",
|
|
9360
|
+
reranked: false,
|
|
9361
|
+
candidateCount: 0
|
|
9362
|
+
};
|
|
9363
|
+
const [facts, episodes, profile, session] = await Promise.all([
|
|
9364
|
+
ranked ? guard(
|
|
9365
|
+
"fact",
|
|
9366
|
+
() => recall2(query, { ...options.recallOptions, types: ["fact"], limit: 20, memoryIds }),
|
|
9367
|
+
empty
|
|
9368
|
+
) : empty,
|
|
9369
|
+
ranked && !scoped && options.includeEpisodes !== false ? guard(
|
|
9370
|
+
"episode",
|
|
9371
|
+
() => recall2(query, {
|
|
9372
|
+
...options.recallOptions,
|
|
9373
|
+
types: ["chunk"],
|
|
9374
|
+
limit: 4,
|
|
9375
|
+
minScore: 0.5,
|
|
9376
|
+
onDiagnostics: void 0
|
|
9377
|
+
}),
|
|
9378
|
+
empty
|
|
9379
|
+
) : empty,
|
|
9380
|
+
loadFacts ? guard(
|
|
9381
|
+
"profile",
|
|
9382
|
+
async () => {
|
|
9383
|
+
const stable = await loadFacts({
|
|
9384
|
+
...filters,
|
|
9385
|
+
memoryIds,
|
|
9386
|
+
factTypes: ["identity", "preference", "constraint", "relationship"],
|
|
9387
|
+
limit: 8
|
|
9388
|
+
});
|
|
9389
|
+
const dynamic = await loadFacts({
|
|
9390
|
+
...filters,
|
|
9391
|
+
memoryIds,
|
|
9392
|
+
factTypes: ["ongoing_context", "plan"],
|
|
9393
|
+
limit: 4
|
|
9394
|
+
});
|
|
9395
|
+
return [...stable, ...dynamic].filter(active).map((m) => asItem(m, "profile"));
|
|
9396
|
+
},
|
|
9397
|
+
[]
|
|
9398
|
+
) : [],
|
|
9399
|
+
loadFacts && loadSessionRefs ? guard(
|
|
9400
|
+
"session",
|
|
9401
|
+
async () => {
|
|
9402
|
+
const refs = (await loadSessionRefs()).filter((r) => !allowed || allowed.has(r.memoryId)).sort((a, b) => b.score - a.score).slice(0, 10);
|
|
9403
|
+
if (!refs.length) return [];
|
|
9404
|
+
const rows = await loadFacts({ ...filters, memoryIds: refs.map((r) => r.memoryId) });
|
|
9405
|
+
const byId = new Map(rows.filter(active).map((m) => [m.uniqueId, m]));
|
|
9406
|
+
return refs.flatMap((ref) => {
|
|
9407
|
+
const m = byId.get(ref.memoryId);
|
|
9408
|
+
return m ? [asItem(m, "session", ref.score)] : [];
|
|
9409
|
+
});
|
|
9410
|
+
},
|
|
9411
|
+
[]
|
|
9412
|
+
) : []
|
|
9413
|
+
]);
|
|
9414
|
+
const toItems = (result, lane) => result.memories.filter((m) => !allowed || allowed.has(m.id)).map((m) => ({
|
|
9415
|
+
id: m.id,
|
|
9416
|
+
content: m.content,
|
|
9417
|
+
score: m.score,
|
|
9418
|
+
lane,
|
|
9419
|
+
...lane === "fact" && { recalled: true },
|
|
9420
|
+
sourceIds: m.sourceChunkIds ?? (m.messageId ? [m.messageId] : [])
|
|
9421
|
+
}));
|
|
9422
|
+
const recalledFacts = toItems(facts, "fact");
|
|
9423
|
+
const recalledScores = /* @__PURE__ */ new Map();
|
|
9424
|
+
const recalledContentScores = /* @__PURE__ */ new Map();
|
|
9425
|
+
for (const item of recalledFacts) {
|
|
9426
|
+
recalledScores.set(item.id, Math.max(recalledScores.get(item.id) ?? -Infinity, item.score));
|
|
9427
|
+
const content = item.content.trim();
|
|
9428
|
+
recalledContentScores.set(
|
|
9429
|
+
content,
|
|
9430
|
+
Math.max(recalledContentScores.get(content) ?? -Infinity, item.score)
|
|
9431
|
+
);
|
|
9432
|
+
}
|
|
9433
|
+
const rankedProfile = profile.map((item) => {
|
|
9434
|
+
const score = Math.max(
|
|
9435
|
+
recalledScores.get(item.id) ?? -Infinity,
|
|
9436
|
+
recalledContentScores.get(item.content.trim()) ?? -Infinity
|
|
9437
|
+
);
|
|
9438
|
+
return score === -Infinity ? item : { ...item, score, recalled: true };
|
|
9439
|
+
});
|
|
9440
|
+
const lanes = [
|
|
9441
|
+
[rankedProfile, 2e3],
|
|
9442
|
+
[recalledFacts, 6e3],
|
|
9443
|
+
[toItems(episodes, "episode"), 3e3],
|
|
9444
|
+
[session, 1e3]
|
|
9445
|
+
];
|
|
9446
|
+
const items = [];
|
|
9447
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
9448
|
+
const seenContent = /* @__PURE__ */ new Set();
|
|
9449
|
+
let remaining = Math.max(0, options.maxChars ?? 12e3);
|
|
9450
|
+
let truncated = false;
|
|
9451
|
+
for (const [candidates, laneBudget] of lanes) {
|
|
9452
|
+
let laneRemaining = Math.min(laneBudget, remaining);
|
|
9453
|
+
for (const item of candidates) {
|
|
9454
|
+
const content = item.content?.trim();
|
|
9455
|
+
const key = `${item.lane === "episode" ? "episode" : "fact"}:${item.id}`;
|
|
9456
|
+
if (!content || seenIds.has(key) || seenContent.has(content)) continue;
|
|
9457
|
+
if (content.length > laneRemaining) {
|
|
9458
|
+
truncated = true;
|
|
9459
|
+
continue;
|
|
9460
|
+
}
|
|
9461
|
+
items.push({ ...item, content });
|
|
9462
|
+
seenIds.add(key);
|
|
9463
|
+
seenContent.add(content);
|
|
9464
|
+
remaining -= content.length;
|
|
9465
|
+
laneRemaining -= content.length;
|
|
9466
|
+
}
|
|
9467
|
+
}
|
|
9468
|
+
return { items, ranked, rankedCount: recalledFacts.length, degraded, truncated };
|
|
9469
|
+
}
|
|
9470
|
+
|
|
9471
|
+
// src/lib/memory/durableExtraction.ts
|
|
9472
|
+
import { Q as Q4 } from "@nozbe/watermelondb";
|
|
9473
|
+
var MAX_JOBS_PER_PASS = 3;
|
|
9474
|
+
var MAX_ATTEMPTS = 3;
|
|
9475
|
+
function idsOf(job) {
|
|
9476
|
+
return JSON.parse(String(job._getRaw("message_ids")));
|
|
9477
|
+
}
|
|
9478
|
+
function seqOf(job) {
|
|
9479
|
+
const raw = job._getRaw("watermark_seq");
|
|
9480
|
+
return typeof raw === "number" && raw > 0 ? raw : void 0;
|
|
9481
|
+
}
|
|
9482
|
+
function createDurableAutoExtractor(options) {
|
|
9483
|
+
const vault = options.retainCtx.vaultCtx;
|
|
9484
|
+
const database = vault.database;
|
|
9485
|
+
const owner = vault.userId ?? vault.walletAddress;
|
|
9486
|
+
if (!owner) throw new Error("Durable extraction requires a user or wallet owner");
|
|
9487
|
+
const jobs = database.get(ExtractionJob.table);
|
|
9488
|
+
const storage = {
|
|
9489
|
+
database,
|
|
9490
|
+
messagesCollection: database.get("history"),
|
|
9491
|
+
conversationsCollection: database.get("conversations"),
|
|
9492
|
+
walletAddress: vault.walletAddress,
|
|
9493
|
+
signMessage: vault.signMessage,
|
|
9494
|
+
embeddedWalletSigner: vault.embeddedWalletSigner
|
|
9495
|
+
};
|
|
9496
|
+
const folderId = options.folderId ?? null;
|
|
9497
|
+
let disposed = false;
|
|
9498
|
+
let running = false;
|
|
9499
|
+
let wakeRequested = false;
|
|
9500
|
+
let timer;
|
|
9501
|
+
let writes = Promise.resolve();
|
|
9502
|
+
let storeFailures = 0;
|
|
9503
|
+
const attempts = /* @__PURE__ */ new Map();
|
|
9504
|
+
const reportError = (error, conversationId) => {
|
|
9505
|
+
try {
|
|
9506
|
+
options.onError?.(error instanceof Error ? error : new Error(String(error)), conversationId);
|
|
9507
|
+
} catch {
|
|
9508
|
+
}
|
|
9509
|
+
};
|
|
9510
|
+
const currentScope = () => {
|
|
9511
|
+
try {
|
|
9512
|
+
const value = typeof options.scope === "function" ? options.scope() : options.scope;
|
|
9513
|
+
return value ?? "private";
|
|
9514
|
+
} catch (error) {
|
|
9515
|
+
reportError(error);
|
|
9516
|
+
return "private";
|
|
9517
|
+
}
|
|
9518
|
+
};
|
|
9519
|
+
function schedule(delay) {
|
|
9520
|
+
if (disposed) return;
|
|
9521
|
+
if (running || timer !== void 0) {
|
|
9522
|
+
if (running) wakeRequested = true;
|
|
9523
|
+
return;
|
|
9524
|
+
}
|
|
9525
|
+
timer = setTimeout(() => {
|
|
9526
|
+
timer = void 0;
|
|
9527
|
+
void drain();
|
|
9528
|
+
}, delay);
|
|
9529
|
+
}
|
|
9530
|
+
async function extractBatch(messages, conversationId, ids, jobScope, jobFolderId) {
|
|
9531
|
+
let watchdog;
|
|
9532
|
+
try {
|
|
9533
|
+
return await Promise.race([
|
|
9534
|
+
new Promise((resolve, reject) => {
|
|
9535
|
+
const worker = createAutoExtractor({
|
|
9536
|
+
...options,
|
|
9537
|
+
retainCtx: {
|
|
9538
|
+
...options.retainCtx,
|
|
9539
|
+
vaultCtx: {
|
|
9540
|
+
...vault,
|
|
9541
|
+
canWrite: async () => !disposed && await storage.conversationsCollection.query(Q4.where("conversation_id", conversationId), Q4.where("is_deleted", false)).fetchCount() > 0 && await storage.messagesCollection.query(Q4.where("conversation_id", conversationId), Q4.where("id", Q4.oneOf(ids))).fetchCount() === ids.length && (!vault.canWrite || await vault.canWrite()) && !disposed
|
|
9542
|
+
}
|
|
9543
|
+
},
|
|
9544
|
+
// Never publish a private queued observation after a mode flip.
|
|
9545
|
+
scope: jobScope,
|
|
9546
|
+
folderId: jobFolderId,
|
|
9547
|
+
cursorStore: void 0,
|
|
9548
|
+
windowSize: 20,
|
|
9549
|
+
maxWindowSize: 20,
|
|
9550
|
+
onTurnComplete: resolve,
|
|
9551
|
+
onError: reject
|
|
9552
|
+
});
|
|
9553
|
+
worker.processTurn(messages, conversationId);
|
|
9554
|
+
worker.dispose();
|
|
9555
|
+
}),
|
|
9556
|
+
new Promise((_resolve, reject) => {
|
|
9557
|
+
watchdog = setTimeout(
|
|
9558
|
+
() => reject(new Error("Extraction did not settle in time; retained for retry")),
|
|
9559
|
+
options.batchTimeoutMs ?? 18e4
|
|
9560
|
+
);
|
|
9561
|
+
})
|
|
9562
|
+
]);
|
|
9563
|
+
} finally {
|
|
9564
|
+
if (watchdog !== void 0) clearTimeout(watchdog);
|
|
9565
|
+
}
|
|
9566
|
+
}
|
|
9567
|
+
async function drain() {
|
|
9568
|
+
if (disposed || running) return;
|
|
9569
|
+
running = true;
|
|
9570
|
+
wakeRequested = false;
|
|
9571
|
+
let retry = false;
|
|
9572
|
+
let scheduleAfterSuccess = false;
|
|
9573
|
+
try {
|
|
9574
|
+
await writes;
|
|
9575
|
+
const pending = await jobs.query(Q4.where("owner_key", owner), Q4.where("message_ids", Q4.notEq("[]"))).fetch();
|
|
9576
|
+
storeFailures = 0;
|
|
9577
|
+
let processed = 0;
|
|
9578
|
+
for (const job of pending) {
|
|
9579
|
+
if (disposed) break;
|
|
9580
|
+
const attempted = attempts.get(job.id) ?? 0;
|
|
9581
|
+
if (attempted >= MAX_ATTEMPTS) continue;
|
|
9582
|
+
if (processed >= MAX_JOBS_PER_PASS) {
|
|
9583
|
+
scheduleAfterSuccess = true;
|
|
9584
|
+
break;
|
|
9585
|
+
}
|
|
9586
|
+
processed++;
|
|
9587
|
+
const conversationId = String(job._getRaw("conversation_id"));
|
|
9588
|
+
try {
|
|
9589
|
+
const conversations = await storage.conversationsCollection.query(Q4.where("conversation_id", conversationId), Q4.where("is_deleted", false)).fetchCount();
|
|
9590
|
+
if (!conversations) {
|
|
9591
|
+
await database.write(() => job.destroyPermanently());
|
|
9592
|
+
continue;
|
|
9593
|
+
}
|
|
9594
|
+
const ids = idsOf(job).slice(0, 20);
|
|
9595
|
+
const loaded = await Promise.all(ids.map((id) => getMessageOp(storage, id)));
|
|
9596
|
+
const missing = ids.filter((_id, index) => loaded[index] === null);
|
|
9597
|
+
if (missing.length) {
|
|
9598
|
+
if (attempted < MAX_ATTEMPTS - 1)
|
|
9599
|
+
throw new Error("Source messages not yet available; retained for retry");
|
|
9600
|
+
const dropped = new Set(missing);
|
|
9601
|
+
await database.write(
|
|
9602
|
+
() => job.update(
|
|
9603
|
+
(r) => r._setRaw(
|
|
9604
|
+
"message_ids",
|
|
9605
|
+
JSON.stringify(idsOf(job).filter((id) => !dropped.has(id)))
|
|
9606
|
+
)
|
|
9607
|
+
)
|
|
9608
|
+
);
|
|
9609
|
+
attempts.delete(job.id);
|
|
9610
|
+
scheduleAfterSuccess = true;
|
|
9611
|
+
reportError(
|
|
9612
|
+
new Error(
|
|
9613
|
+
`Dropped ${missing.length} unresolvable source id(s) to unblock extraction`
|
|
9614
|
+
),
|
|
9615
|
+
conversationId
|
|
9616
|
+
);
|
|
9617
|
+
continue;
|
|
9618
|
+
}
|
|
9619
|
+
if (loaded.some(
|
|
9620
|
+
(message) => message && (message.decryptionStatus || isEncrypted(message.content))
|
|
9621
|
+
)) {
|
|
9622
|
+
throw new Error("Source messages are locked; retained for retry");
|
|
9623
|
+
}
|
|
9624
|
+
const messages = loaded.flatMap(
|
|
9625
|
+
(m) => m && m.conversationId === conversationId && (m.role === "user" || m.role === "assistant") ? [{ id: m.uniqueId, role: m.role, content: m.content }] : []
|
|
9626
|
+
);
|
|
9627
|
+
let success = messages.length === 0;
|
|
9628
|
+
if (messages.length) {
|
|
9629
|
+
const result = await extractBatch(
|
|
9630
|
+
messages,
|
|
9631
|
+
conversationId,
|
|
9632
|
+
ids,
|
|
9633
|
+
currentScope() === "private" ? "private" : String(job._getRaw("scope")),
|
|
9634
|
+
job._getRaw("folder_id") ?? null
|
|
9635
|
+
);
|
|
9636
|
+
success = result.failedCount === 0 && result.outcome !== "empty-after-retry";
|
|
9637
|
+
options.onTurnComplete?.(result);
|
|
9638
|
+
}
|
|
9639
|
+
if (!success) throw new Error("Extraction batch incomplete; retained for retry");
|
|
9640
|
+
attempts.delete(job.id);
|
|
9641
|
+
const sequences = /* @__PURE__ */ new Map();
|
|
9642
|
+
for (const message of loaded)
|
|
9643
|
+
if (message && message.messageId > 0)
|
|
9644
|
+
sequences.set(message.uniqueId, message.messageId);
|
|
9645
|
+
await database.write(async () => {
|
|
9646
|
+
const current = idsOf(job);
|
|
9647
|
+
const acknowledged = ids.filter((id) => current.includes(id));
|
|
9648
|
+
if (!acknowledged.length) return;
|
|
9649
|
+
const done = new Set(acknowledged);
|
|
9650
|
+
const remaining = current.filter((id) => !done.has(id));
|
|
9651
|
+
if (remaining.length) {
|
|
9652
|
+
await job.update(
|
|
9653
|
+
(r) => r._setRaw("message_ids", JSON.stringify([...acknowledged.slice(-2), ...remaining]))
|
|
9654
|
+
);
|
|
9655
|
+
} else await job.update((r) => r._setRaw("message_ids", "[]"));
|
|
9656
|
+
const newest = acknowledged[acknowledged.length - 1];
|
|
9657
|
+
await job.update((r) => {
|
|
9658
|
+
r._setRaw("watermark", newest);
|
|
9659
|
+
const sequence = sequences.get(newest);
|
|
9660
|
+
if (sequence !== void 0) {
|
|
9661
|
+
const prior = seqOf(job);
|
|
9662
|
+
r._setRaw(
|
|
9663
|
+
"watermark_seq",
|
|
9664
|
+
prior === void 0 ? sequence : Math.max(prior, sequence)
|
|
9665
|
+
);
|
|
9666
|
+
}
|
|
9667
|
+
});
|
|
9668
|
+
});
|
|
9669
|
+
scheduleAfterSuccess = true;
|
|
9670
|
+
} catch (error) {
|
|
9671
|
+
attempts.set(job.id, attempted + 1);
|
|
9672
|
+
retry = true;
|
|
9673
|
+
reportError(error, conversationId);
|
|
9674
|
+
}
|
|
9675
|
+
}
|
|
9676
|
+
} catch (error) {
|
|
9677
|
+
retry = ++storeFailures < 3;
|
|
9678
|
+
reportError(error);
|
|
9679
|
+
} finally {
|
|
9680
|
+
running = false;
|
|
9681
|
+
const woken = wakeRequested;
|
|
9682
|
+
wakeRequested = false;
|
|
9683
|
+
if (retry || scheduleAfterSuccess || woken)
|
|
9684
|
+
schedule(retry ? options.retryDelayMs ?? 3e4 : 0);
|
|
9685
|
+
}
|
|
9686
|
+
}
|
|
9687
|
+
schedule(0);
|
|
9688
|
+
return {
|
|
9689
|
+
processTurn(messages, conversationId) {
|
|
9690
|
+
if (disposed || !conversationId || !messages.length) return false;
|
|
9691
|
+
const ids = messages.map((m) => m.id);
|
|
9692
|
+
const scope = currentScope();
|
|
9693
|
+
writes = writes.then(
|
|
9694
|
+
() => database.write(async () => {
|
|
9695
|
+
const existing = await jobs.query(Q4.where("owner_key", owner), Q4.where("conversation_id", conversationId)).fetch();
|
|
9696
|
+
const job = existing.find(
|
|
9697
|
+
(candidate) => candidate._getRaw("scope") === scope && (candidate._getRaw("folder_id") ?? null) === folderId
|
|
9698
|
+
);
|
|
9699
|
+
const positions = new Map(ids.map((id, index) => [id, index]));
|
|
9700
|
+
const rows = await storage.messagesCollection.query(Q4.where("id", Q4.oneOf(ids))).fetch();
|
|
9701
|
+
const sequences = /* @__PURE__ */ new Map();
|
|
9702
|
+
for (const row of rows) {
|
|
9703
|
+
const sequence = Number(row._getRaw("message_id"));
|
|
9704
|
+
if (Number.isFinite(sequence) && sequence > 0) sequences.set(row.id, sequence);
|
|
9705
|
+
}
|
|
9706
|
+
let boundary = -1;
|
|
9707
|
+
let observedSeq = 0;
|
|
9708
|
+
const foreignSources = /* @__PURE__ */ new Set();
|
|
9709
|
+
for (const prior of existing) {
|
|
9710
|
+
const sources = [...idsOf(prior), String(prior._getRaw("watermark") ?? "")];
|
|
9711
|
+
for (const id of sources) {
|
|
9712
|
+
boundary = Math.max(boundary, positions.get(id) ?? -1);
|
|
9713
|
+
if (prior !== job) foreignSources.add(id);
|
|
9714
|
+
}
|
|
9715
|
+
observedSeq = Math.max(observedSeq, seqOf(prior) ?? 0);
|
|
9716
|
+
for (const id of idsOf(prior))
|
|
9717
|
+
observedSeq = Math.max(observedSeq, sequences.get(id) ?? 0);
|
|
9718
|
+
}
|
|
9719
|
+
if (observedSeq > 0)
|
|
9720
|
+
ids.forEach((id, index) => {
|
|
9721
|
+
const sequence = sequences.get(id);
|
|
9722
|
+
if (sequence !== void 0 && sequence <= observedSeq)
|
|
9723
|
+
boundary = Math.max(boundary, index);
|
|
9724
|
+
});
|
|
9725
|
+
if (!existing.length && options.cursorStore) {
|
|
9726
|
+
try {
|
|
9727
|
+
const cursor = options.cursorStore.get(conversationId);
|
|
9728
|
+
if (cursor) boundary = Math.max(boundary, positions.get(cursor) ?? -1);
|
|
9729
|
+
} catch (error) {
|
|
9730
|
+
reportError(error, conversationId);
|
|
9731
|
+
}
|
|
9732
|
+
}
|
|
9733
|
+
if (boundary < 0 && existing.some((prior) => idsOf(prior).length > 0 || seqOf(prior) !== void 0)) {
|
|
9734
|
+
reportError(
|
|
9735
|
+
new Error("Extraction boundary unresolved; skipped rather than re-observing"),
|
|
9736
|
+
conversationId
|
|
9737
|
+
);
|
|
9738
|
+
return;
|
|
9739
|
+
}
|
|
9740
|
+
const start = boundary >= 0 ? boundary + 1 : Math.max(0, ids.length - Math.max(1, options.windowSize ?? 6));
|
|
9741
|
+
if (start >= ids.length) return;
|
|
9742
|
+
const ownWatermark = job?._getRaw("watermark");
|
|
9743
|
+
const atOwnWatermark = !!job && boundary >= 0 && (ids[boundary] === ownWatermark || sequences.get(ids[boundary]) !== void 0 && sequences.get(ids[boundary]) === seqOf(job));
|
|
9744
|
+
const overlapStart = atOwnWatermark ? Math.max(0, boundary - 1) : start;
|
|
9745
|
+
const fresh = ids.slice(overlapStart).filter((id) => !foreignSources.has(id));
|
|
9746
|
+
if (!fresh.length) return;
|
|
9747
|
+
if (job) {
|
|
9748
|
+
await job.update(
|
|
9749
|
+
(r) => r._setRaw("message_ids", JSON.stringify([.../* @__PURE__ */ new Set([...idsOf(job), ...fresh])]))
|
|
9750
|
+
);
|
|
9751
|
+
attempts.delete(job.id);
|
|
9752
|
+
} else {
|
|
9753
|
+
await jobs.create((r) => {
|
|
9754
|
+
r._setRaw("owner_key", owner);
|
|
9755
|
+
r._setRaw("conversation_id", conversationId);
|
|
9756
|
+
r._setRaw("scope", scope);
|
|
9757
|
+
r._setRaw("folder_id", folderId);
|
|
9758
|
+
r._setRaw("message_ids", JSON.stringify([...new Set(fresh)]));
|
|
9759
|
+
});
|
|
9760
|
+
}
|
|
9761
|
+
})
|
|
9762
|
+
).then(() => schedule(options.debounceMs ?? 2e4)).catch((error) => reportError(error, conversationId));
|
|
9763
|
+
return true;
|
|
9764
|
+
},
|
|
9765
|
+
isProcessing: () => running,
|
|
9766
|
+
dispose() {
|
|
9767
|
+
disposed = true;
|
|
9768
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
9769
|
+
}
|
|
9770
|
+
};
|
|
9771
|
+
}
|
|
9772
|
+
|
|
9308
9773
|
// src/lib/memoryEngine/quantization.ts
|
|
9309
9774
|
var INT8_MAX = 127;
|
|
9310
9775
|
function quantizeEmbedding(v) {
|
|
@@ -12706,10 +13171,25 @@ __decorateClass([
|
|
|
12706
13171
|
], VaultFolder.prototype, "context", 2);
|
|
12707
13172
|
|
|
12708
13173
|
// src/lib/db/schema.ts
|
|
12709
|
-
var SDK_SCHEMA_VERSION =
|
|
13174
|
+
var SDK_SCHEMA_VERSION = 46;
|
|
12710
13175
|
var sdkSchema = appSchema2({
|
|
12711
13176
|
version: SDK_SCHEMA_VERSION,
|
|
12712
13177
|
tables: [
|
|
13178
|
+
tableSchema2({
|
|
13179
|
+
name: "memory_extraction_jobs",
|
|
13180
|
+
columns: [
|
|
13181
|
+
{ name: "owner_key", type: "string", isIndexed: true },
|
|
13182
|
+
{ name: "conversation_id", type: "string", isIndexed: true },
|
|
13183
|
+
{ name: "scope", type: "string" },
|
|
13184
|
+
{ name: "message_ids", type: "string" },
|
|
13185
|
+
{ name: "watermark", type: "string", isOptional: true },
|
|
13186
|
+
// Deletion-proof anchor: history.message_id of the newest acknowledged
|
|
13187
|
+
// source. `watermark` alone is an id, and deleting that message erased
|
|
13188
|
+
// the anchor — see the column note in extractionJobs/models.ts.
|
|
13189
|
+
{ name: "watermark_seq", type: "number", isOptional: true },
|
|
13190
|
+
{ name: "folder_id", type: "string", isOptional: true }
|
|
13191
|
+
]
|
|
13192
|
+
}),
|
|
12713
13193
|
// Chat storage tables
|
|
12714
13194
|
tableSchema2({
|
|
12715
13195
|
name: "history",
|
|
@@ -13714,10 +14194,28 @@ var sdkMigrations = schemaMigrations2({
|
|
|
13714
14194
|
columns: [{ name: "media", type: "string", isOptional: true }]
|
|
13715
14195
|
})
|
|
13716
14196
|
]
|
|
14197
|
+
},
|
|
14198
|
+
{
|
|
14199
|
+
toVersion: 46,
|
|
14200
|
+
steps: [
|
|
14201
|
+
createTable({
|
|
14202
|
+
name: "memory_extraction_jobs",
|
|
14203
|
+
columns: [
|
|
14204
|
+
{ name: "owner_key", type: "string", isIndexed: true },
|
|
14205
|
+
{ name: "conversation_id", type: "string", isIndexed: true },
|
|
14206
|
+
{ name: "scope", type: "string" },
|
|
14207
|
+
{ name: "message_ids", type: "string" },
|
|
14208
|
+
{ name: "watermark", type: "string", isOptional: true },
|
|
14209
|
+
{ name: "watermark_seq", type: "number", isOptional: true },
|
|
14210
|
+
{ name: "folder_id", type: "string", isOptional: true }
|
|
14211
|
+
]
|
|
14212
|
+
})
|
|
14213
|
+
]
|
|
13717
14214
|
}
|
|
13718
14215
|
]
|
|
13719
14216
|
});
|
|
13720
14217
|
var sdkModelClasses = [
|
|
14218
|
+
ExtractionJob,
|
|
13721
14219
|
Message,
|
|
13722
14220
|
Conversation,
|
|
13723
14221
|
ConversationSummary,
|
|
@@ -13895,7 +14393,7 @@ var DatabaseManager = class {
|
|
|
13895
14393
|
};
|
|
13896
14394
|
|
|
13897
14395
|
// src/lib/db/conversationMemory/operations.ts
|
|
13898
|
-
import { Q as
|
|
14396
|
+
import { Q as Q5 } from "@nozbe/watermelondb";
|
|
13899
14397
|
var MAX_PER_CONVERSATION = 200;
|
|
13900
14398
|
function conversationMemoryToStored(row) {
|
|
13901
14399
|
return {
|
|
@@ -13910,7 +14408,7 @@ async function addConversationMemoriesOp(ctx, conversationId, items) {
|
|
|
13910
14408
|
if (!conversationId || items.length === 0) return;
|
|
13911
14409
|
const now = Date.now();
|
|
13912
14410
|
await ctx.database.write(async () => {
|
|
13913
|
-
const existing = await ctx.conversationMemoryCollection.query(
|
|
14411
|
+
const existing = await ctx.conversationMemoryCollection.query(Q5.where("conversation_id", conversationId), Q5.sortBy("created_at", Q5.asc)).fetch();
|
|
13914
14412
|
const seen = new Set(existing.map((r) => r.memoryId));
|
|
13915
14413
|
const batchSeen = /* @__PURE__ */ new Set();
|
|
13916
14414
|
const deduped = [];
|
|
@@ -13945,20 +14443,20 @@ async function addConversationMemoriesOp(ctx, conversationId, items) {
|
|
|
13945
14443
|
}
|
|
13946
14444
|
async function getConversationMemoriesOp(ctx, conversationId) {
|
|
13947
14445
|
if (!conversationId) return [];
|
|
13948
|
-
const rows = await ctx.conversationMemoryCollection.query(
|
|
14446
|
+
const rows = await ctx.conversationMemoryCollection.query(Q5.where("conversation_id", conversationId), Q5.sortBy("created_at", Q5.asc)).fetch();
|
|
13949
14447
|
return rows.map(conversationMemoryToStored);
|
|
13950
14448
|
}
|
|
13951
14449
|
async function clearConversationMemoriesOp(ctx, conversationId) {
|
|
13952
14450
|
if (!conversationId) return;
|
|
13953
14451
|
await ctx.database.write(async () => {
|
|
13954
|
-
const rows = await ctx.conversationMemoryCollection.query(
|
|
14452
|
+
const rows = await ctx.conversationMemoryCollection.query(Q5.where("conversation_id", conversationId)).fetch();
|
|
13955
14453
|
if (rows.length === 0) return;
|
|
13956
14454
|
await ctx.database.batch(...rows.map((r) => r.prepareDestroyPermanently()));
|
|
13957
14455
|
});
|
|
13958
14456
|
}
|
|
13959
14457
|
|
|
13960
14458
|
// src/lib/db/vaultFolders/operations.ts
|
|
13961
|
-
import { Q as
|
|
14459
|
+
import { Q as Q6 } from "@nozbe/watermelondb";
|
|
13962
14460
|
function folderToStored(folder) {
|
|
13963
14461
|
return {
|
|
13964
14462
|
uniqueId: folder.id,
|
|
@@ -13983,7 +14481,7 @@ async function createVaultFolderOp(ctx, opts) {
|
|
|
13983
14481
|
return folderToStored(created);
|
|
13984
14482
|
}
|
|
13985
14483
|
async function getAllVaultFoldersOp(ctx) {
|
|
13986
|
-
const results = await ctx.vaultFolderCollection.query(
|
|
14484
|
+
const results = await ctx.vaultFolderCollection.query(Q6.where("is_deleted", false), Q6.sortBy("created_at", Q6.desc)).fetch();
|
|
13987
14485
|
return results.map(folderToStored);
|
|
13988
14486
|
}
|
|
13989
14487
|
async function updateVaultFolderOp(ctx, id, opts) {
|
|
@@ -13992,7 +14490,7 @@ async function updateVaultFolderOp(ctx, id, opts) {
|
|
|
13992
14490
|
if (record.isDeleted) return null;
|
|
13993
14491
|
const scopeChanged = opts.scope !== void 0 && opts.scope !== record.scope;
|
|
13994
14492
|
const updated = await ctx.database.write(async () => {
|
|
13995
|
-
const memories = scopeChanged ? await ctx.vaultMemoryCollection.query(
|
|
14493
|
+
const memories = scopeChanged ? await ctx.vaultMemoryCollection.query(Q6.where("folder_id", id), Q6.where("is_deleted", false)).fetch() : [];
|
|
13996
14494
|
const updates = [];
|
|
13997
14495
|
updates.push(
|
|
13998
14496
|
record.prepareUpdate((r) => {
|
|
@@ -14020,7 +14518,7 @@ async function deleteVaultFolderOp(ctx, id) {
|
|
|
14020
14518
|
const record = await ctx.vaultFolderCollection.find(id);
|
|
14021
14519
|
if (record.isDeleted) return false;
|
|
14022
14520
|
await ctx.database.write(async () => {
|
|
14023
|
-
const memories = await ctx.vaultMemoryCollection.query(
|
|
14521
|
+
const memories = await ctx.vaultMemoryCollection.query(Q6.where("folder_id", id), Q6.where("is_deleted", false)).fetch();
|
|
14024
14522
|
const preparedMemories = memories.map(
|
|
14025
14523
|
(memory) => memory.prepareUpdate((r) => {
|
|
14026
14524
|
r._setRaw("folder_id", null);
|
|
@@ -14089,7 +14587,7 @@ async function updateVaultFolderContextOp(ctx, id, context) {
|
|
|
14089
14587
|
}
|
|
14090
14588
|
}
|
|
14091
14589
|
async function getVaultFolderMemoryCountOp(ctx, folderId) {
|
|
14092
|
-
return await ctx.vaultMemoryCollection.query(
|
|
14590
|
+
return await ctx.vaultMemoryCollection.query(Q6.where("folder_id", folderId), Q6.where("is_deleted", false)).fetchCount();
|
|
14093
14591
|
}
|
|
14094
14592
|
|
|
14095
14593
|
// src/lib/db/vaultFolders/defaults.ts
|
|
@@ -15097,6 +15595,7 @@ export {
|
|
|
15097
15595
|
WalletPoller,
|
|
15098
15596
|
addConversationMemoriesOp,
|
|
15099
15597
|
archiveVaultMemoryOp,
|
|
15598
|
+
assembleMemoryContext,
|
|
15100
15599
|
backfillMemoryTopicsOp,
|
|
15101
15600
|
buildNotionAuthUrl,
|
|
15102
15601
|
capHopsForDensity,
|
|
@@ -15129,6 +15628,7 @@ export {
|
|
|
15129
15628
|
createChunkVectorCache,
|
|
15130
15629
|
createCryptoPricePreProcessor,
|
|
15131
15630
|
createDecaySweeper,
|
|
15631
|
+
createDurableAutoExtractor,
|
|
15132
15632
|
createLlmDecayClassifier,
|
|
15133
15633
|
createLlmNeighborRefiner,
|
|
15134
15634
|
createMemoryEngineTool,
|
|
@@ -15149,6 +15649,7 @@ export {
|
|
|
15149
15649
|
decodeChunkVector,
|
|
15150
15650
|
decryptConversationTitle,
|
|
15151
15651
|
deleteAllVaultMemoriesForUserOp,
|
|
15652
|
+
deleteMessageOp,
|
|
15152
15653
|
deleteVaultFolderOp,
|
|
15153
15654
|
deleteVaultMemoryOp,
|
|
15154
15655
|
dequantizeEmbedding,
|
|
@@ -15221,6 +15722,7 @@ export {
|
|
|
15221
15722
|
setLogger,
|
|
15222
15723
|
setMemoryVisibilityOp,
|
|
15223
15724
|
shouldChunkMessage,
|
|
15725
|
+
shouldRecallMemory,
|
|
15224
15726
|
stampTopicsExtractedAtOp,
|
|
15225
15727
|
streamCancelPath,
|
|
15226
15728
|
streamReplayPath,
|