@anuma/sdk 6.25.1 → 6.25.2
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/index.cjs +343 -146
- package/dist/expo/index.d.mts +34 -9
- package/dist/expo/index.d.ts +34 -9
- package/dist/expo/index.mjs +343 -146
- package/dist/react/index.cjs +343 -146
- package/dist/react/index.d.mts +34 -9
- package/dist/react/index.d.ts +34 -9
- package/dist/react/index.mjs +343 -146
- package/dist/server/index.cjs +343 -146
- package/dist/server/index.d.mts +34 -9
- package/dist/server/index.d.ts +34 -9
- package/dist/server/index.mjs +343 -146
- package/package.json +1 -1
package/dist/expo/index.mjs
CHANGED
|
@@ -9809,8 +9809,42 @@ async function vaultMemoryToStored(memory, walletAddress, signMessage, embeddedW
|
|
|
9809
9809
|
}
|
|
9810
9810
|
return raw;
|
|
9811
9811
|
}
|
|
9812
|
+
function populateNewVaultMemory(record, ctx, opts, encryptedContent) {
|
|
9813
|
+
record._setRaw("content", encryptedContent);
|
|
9814
|
+
record._setRaw("scope", opts.scope ?? "private");
|
|
9815
|
+
record._setRaw("folder_id", opts.folderId ?? null);
|
|
9816
|
+
record._setRaw("user_id", ctx.userId ?? null);
|
|
9817
|
+
record._setRaw("is_deleted", false);
|
|
9818
|
+
if (opts.embedding !== void 0) {
|
|
9819
|
+
record._setRaw("embedding", opts.embedding);
|
|
9820
|
+
record._setRaw("embedding_model", opts.embeddingModel ?? null);
|
|
9821
|
+
}
|
|
9822
|
+
if (opts.sourceChunkIds !== void 0) {
|
|
9823
|
+
record._setRaw("source_chunk_ids", JSON.stringify(opts.sourceChunkIds));
|
|
9824
|
+
}
|
|
9825
|
+
record._setRaw("proof_count", opts.proofCount ?? 1);
|
|
9826
|
+
record._setRaw("source", opts.source ?? "manual");
|
|
9827
|
+
if (opts.eventTime) {
|
|
9828
|
+
record._setRaw("event_time_start", opts.eventTime.start ?? null);
|
|
9829
|
+
record._setRaw("event_time_end", opts.eventTime.end ?? null);
|
|
9830
|
+
record._setRaw("event_time_kind", opts.eventTime.kind ?? null);
|
|
9831
|
+
}
|
|
9832
|
+
if (opts.factType !== void 0) {
|
|
9833
|
+
record._setRaw("fact_type", opts.factType);
|
|
9834
|
+
}
|
|
9835
|
+
if (opts.trustTier !== void 0) {
|
|
9836
|
+
record._setRaw("trust_tier", normalizeTrustTier(opts.trustTier));
|
|
9837
|
+
}
|
|
9838
|
+
record._setRaw("visibility", opts.visibility ?? "private");
|
|
9839
|
+
record._setRaw(
|
|
9840
|
+
"published_at",
|
|
9841
|
+
opts.visibility && opts.visibility !== "private" ? opts.publishedAt ?? Date.now() : null
|
|
9842
|
+
);
|
|
9843
|
+
if (opts.geohash !== void 0) {
|
|
9844
|
+
record._setRaw("geohash", opts.geohash);
|
|
9845
|
+
}
|
|
9846
|
+
}
|
|
9812
9847
|
async function createVaultMemoryOp(ctx, opts) {
|
|
9813
|
-
const scope = opts.scope ?? "private";
|
|
9814
9848
|
const encryptedContent = ctx.walletAddress && ctx.signMessage ? await encryptVaultMemoryContent(
|
|
9815
9849
|
opts.content,
|
|
9816
9850
|
ctx.walletAddress,
|
|
@@ -9820,47 +9854,14 @@ async function createVaultMemoryOp(ctx, opts) {
|
|
|
9820
9854
|
const created = await ctx.database.write(async () => {
|
|
9821
9855
|
if (ctx.canWrite && !await ctx.canWrite())
|
|
9822
9856
|
throw new Error("Memory source is no longer eligible");
|
|
9823
|
-
return ctx.vaultMemoryCollection.create(
|
|
9824
|
-
record
|
|
9825
|
-
|
|
9826
|
-
record._setRaw("folder_id", opts.folderId ?? null);
|
|
9827
|
-
record._setRaw("user_id", ctx.userId ?? null);
|
|
9828
|
-
record._setRaw("is_deleted", false);
|
|
9829
|
-
if (opts.embedding !== void 0) {
|
|
9830
|
-
record._setRaw("embedding", opts.embedding);
|
|
9831
|
-
record._setRaw("embedding_model", opts.embeddingModel ?? null);
|
|
9832
|
-
}
|
|
9833
|
-
if (opts.sourceChunkIds !== void 0) {
|
|
9834
|
-
record._setRaw("source_chunk_ids", JSON.stringify(opts.sourceChunkIds));
|
|
9835
|
-
}
|
|
9836
|
-
record._setRaw("proof_count", opts.proofCount ?? 1);
|
|
9837
|
-
record._setRaw("source", opts.source ?? "manual");
|
|
9838
|
-
if (opts.eventTime) {
|
|
9839
|
-
record._setRaw("event_time_start", opts.eventTime.start ?? null);
|
|
9840
|
-
record._setRaw("event_time_end", opts.eventTime.end ?? null);
|
|
9841
|
-
record._setRaw("event_time_kind", opts.eventTime.kind ?? null);
|
|
9842
|
-
}
|
|
9843
|
-
if (opts.factType !== void 0) {
|
|
9844
|
-
record._setRaw("fact_type", opts.factType);
|
|
9845
|
-
}
|
|
9846
|
-
if (opts.trustTier !== void 0) {
|
|
9847
|
-
record._setRaw("trust_tier", normalizeTrustTier(opts.trustTier));
|
|
9848
|
-
}
|
|
9849
|
-
record._setRaw("visibility", opts.visibility ?? "private");
|
|
9850
|
-
record._setRaw(
|
|
9851
|
-
"published_at",
|
|
9852
|
-
opts.visibility && opts.visibility !== "private" ? opts.publishedAt ?? Date.now() : null
|
|
9853
|
-
);
|
|
9854
|
-
if (opts.geohash !== void 0) {
|
|
9855
|
-
record._setRaw("geohash", opts.geohash);
|
|
9856
|
-
}
|
|
9857
|
-
});
|
|
9857
|
+
return ctx.vaultMemoryCollection.create(
|
|
9858
|
+
(record) => populateNewVaultMemory(record, ctx, opts, encryptedContent)
|
|
9859
|
+
);
|
|
9858
9860
|
});
|
|
9859
9861
|
return vaultMemoryToStored(created, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner);
|
|
9860
9862
|
}
|
|
9861
9863
|
async function createSupersedingMemoryOp(ctx, opts, targetId) {
|
|
9862
9864
|
if (!targetId) return { created: null, retired: false };
|
|
9863
|
-
const scope = opts.scope ?? "private";
|
|
9864
9865
|
const encryptedContent = ctx.walletAddress && ctx.signMessage ? await encryptVaultMemoryContent(
|
|
9865
9866
|
opts.content,
|
|
9866
9867
|
ctx.walletAddress,
|
|
@@ -9878,27 +9879,9 @@ async function createSupersedingMemoryOp(ctx, opts, targetId) {
|
|
|
9878
9879
|
return;
|
|
9879
9880
|
}
|
|
9880
9881
|
if (target.isDeleted || target.supersededBy || !isOwnedByCtxUser(ctx, target)) return;
|
|
9881
|
-
createdRecord = await ctx.vaultMemoryCollection.create(
|
|
9882
|
-
record
|
|
9883
|
-
|
|
9884
|
-
record._setRaw("folder_id", opts.folderId ?? null);
|
|
9885
|
-
record._setRaw("user_id", ctx.userId ?? null);
|
|
9886
|
-
record._setRaw("is_deleted", false);
|
|
9887
|
-
if (opts.embedding !== void 0) {
|
|
9888
|
-
record._setRaw("embedding", opts.embedding);
|
|
9889
|
-
record._setRaw("embedding_model", opts.embeddingModel ?? null);
|
|
9890
|
-
}
|
|
9891
|
-
if (opts.sourceChunkIds !== void 0) {
|
|
9892
|
-
record._setRaw("source_chunk_ids", JSON.stringify(opts.sourceChunkIds));
|
|
9893
|
-
}
|
|
9894
|
-
record._setRaw("proof_count", opts.proofCount ?? 1);
|
|
9895
|
-
record._setRaw("source", opts.source ?? "manual");
|
|
9896
|
-
if (opts.eventTime) {
|
|
9897
|
-
record._setRaw("event_time_start", opts.eventTime.start ?? null);
|
|
9898
|
-
record._setRaw("event_time_end", opts.eventTime.end ?? null);
|
|
9899
|
-
record._setRaw("event_time_kind", opts.eventTime.kind ?? null);
|
|
9900
|
-
}
|
|
9901
|
-
});
|
|
9882
|
+
createdRecord = await ctx.vaultMemoryCollection.create(
|
|
9883
|
+
(record) => populateNewVaultMemory(record, ctx, opts, encryptedContent)
|
|
9884
|
+
);
|
|
9902
9885
|
await target.update((r) => {
|
|
9903
9886
|
r._setRaw("superseded_by", createdRecord.id);
|
|
9904
9887
|
r._setRaw("superseded_at", Date.now());
|
|
@@ -9978,41 +9961,9 @@ async function createVaultMemoriesBatchOp(ctx, optionsArray) {
|
|
|
9978
9961
|
if (ctx.canWrite && !await ctx.canWrite())
|
|
9979
9962
|
throw new Error("Memory source is no longer eligible");
|
|
9980
9963
|
const prepared = optionsArray.map(
|
|
9981
|
-
(opts, i) => ctx.vaultMemoryCollection.prepareCreate(
|
|
9982
|
-
record
|
|
9983
|
-
|
|
9984
|
-
record._setRaw("folder_id", opts.folderId ?? null);
|
|
9985
|
-
record._setRaw("user_id", ctx.userId ?? null);
|
|
9986
|
-
record._setRaw("is_deleted", false);
|
|
9987
|
-
if (optionsArray[i].embedding !== void 0) {
|
|
9988
|
-
record._setRaw("embedding", optionsArray[i].embedding);
|
|
9989
|
-
record._setRaw("embedding_model", optionsArray[i].embeddingModel ?? null);
|
|
9990
|
-
}
|
|
9991
|
-
if (opts.sourceChunkIds !== void 0) {
|
|
9992
|
-
record._setRaw("source_chunk_ids", JSON.stringify(opts.sourceChunkIds));
|
|
9993
|
-
}
|
|
9994
|
-
record._setRaw("proof_count", opts.proofCount ?? 1);
|
|
9995
|
-
record._setRaw("source", opts.source ?? "manual");
|
|
9996
|
-
if (opts.eventTime) {
|
|
9997
|
-
record._setRaw("event_time_start", opts.eventTime.start ?? null);
|
|
9998
|
-
record._setRaw("event_time_end", opts.eventTime.end ?? null);
|
|
9999
|
-
record._setRaw("event_time_kind", opts.eventTime.kind ?? null);
|
|
10000
|
-
}
|
|
10001
|
-
if (opts.factType !== void 0) {
|
|
10002
|
-
record._setRaw("fact_type", opts.factType);
|
|
10003
|
-
}
|
|
10004
|
-
if (opts.trustTier !== void 0) {
|
|
10005
|
-
record._setRaw("trust_tier", normalizeTrustTier(opts.trustTier));
|
|
10006
|
-
}
|
|
10007
|
-
record._setRaw("visibility", opts.visibility ?? "private");
|
|
10008
|
-
record._setRaw(
|
|
10009
|
-
"published_at",
|
|
10010
|
-
opts.visibility && opts.visibility !== "private" ? opts.publishedAt ?? Date.now() : null
|
|
10011
|
-
);
|
|
10012
|
-
if (opts.geohash !== void 0) {
|
|
10013
|
-
record._setRaw("geohash", opts.geohash);
|
|
10014
|
-
}
|
|
10015
|
-
})
|
|
9964
|
+
(opts, i) => ctx.vaultMemoryCollection.prepareCreate(
|
|
9965
|
+
(record) => populateNewVaultMemory(record, ctx, opts, encryptedContents[i])
|
|
9966
|
+
)
|
|
10016
9967
|
);
|
|
10017
9968
|
await ctx.database.batch(...prepared);
|
|
10018
9969
|
return prepared;
|
|
@@ -10833,21 +10784,73 @@ async function hardDeleteDecayedOp(ctx, id, opts) {
|
|
|
10833
10784
|
return false;
|
|
10834
10785
|
}
|
|
10835
10786
|
}
|
|
10836
|
-
async function updateVaultMemoryEmbeddingOp(ctx, id, embedding, embeddingModel) {
|
|
10787
|
+
async function updateVaultMemoryEmbeddingOp(ctx, id, embedding, embeddingModel, expected) {
|
|
10837
10788
|
try {
|
|
10838
10789
|
const record = await ctx.vaultMemoryCollection.find(id);
|
|
10839
10790
|
if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) return false;
|
|
10791
|
+
const readUpdatedAt = record.updatedAt.getTime();
|
|
10792
|
+
const readStoredContent = record._getRaw("content");
|
|
10793
|
+
if (expected?.updatedAt !== void 0 && expected.updatedAt !== readUpdatedAt) return false;
|
|
10794
|
+
if (expected?.content !== void 0) {
|
|
10795
|
+
const current = await vaultMemoryToStored(
|
|
10796
|
+
record,
|
|
10797
|
+
ctx.walletAddress,
|
|
10798
|
+
ctx.signMessage,
|
|
10799
|
+
ctx.embeddedWalletSigner
|
|
10800
|
+
);
|
|
10801
|
+
if (current.content !== expected.content) return false;
|
|
10802
|
+
}
|
|
10803
|
+
let written = false;
|
|
10840
10804
|
await ctx.database.write(async () => {
|
|
10805
|
+
if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) return;
|
|
10806
|
+
const originalUpdatedAt = record.updatedAt.getTime();
|
|
10807
|
+
if (originalUpdatedAt !== readUpdatedAt || record._getRaw("content") !== readStoredContent)
|
|
10808
|
+
return;
|
|
10841
10809
|
await record.update((r) => {
|
|
10842
10810
|
r._setRaw("embedding", embedding);
|
|
10843
10811
|
r._setRaw("embedding_model", embeddingModel);
|
|
10812
|
+
r._setRaw("updated_at", originalUpdatedAt);
|
|
10844
10813
|
});
|
|
10814
|
+
written = true;
|
|
10845
10815
|
});
|
|
10846
|
-
return
|
|
10816
|
+
return written;
|
|
10847
10817
|
} catch {
|
|
10848
10818
|
return false;
|
|
10849
10819
|
}
|
|
10850
10820
|
}
|
|
10821
|
+
function normalizeForDedupe(content) {
|
|
10822
|
+
return content.normalize("NFKC").trim().replace(/\s+/g, " ").toLowerCase();
|
|
10823
|
+
}
|
|
10824
|
+
async function findQuarantinedDuplicateOp(ctx, content, sourceIds, where) {
|
|
10825
|
+
const wanted = [...new Set(sourceIds)].sort().join("\0");
|
|
10826
|
+
const target = normalizeForDedupe(content);
|
|
10827
|
+
const rows = await ctx.vaultMemoryCollection.query(
|
|
10828
|
+
Q5.where("trust_tier", "quarantined"),
|
|
10829
|
+
Q5.where("is_deleted", false),
|
|
10830
|
+
Q5.where("scope", where.scope),
|
|
10831
|
+
Q5.where("folder_id", where.folderId),
|
|
10832
|
+
...ctx.userId !== void 0 ? [Q5.where("user_id", ctx.userId)] : []
|
|
10833
|
+
).unsafeFetchRaw();
|
|
10834
|
+
for (const raw of rows) {
|
|
10835
|
+
let ids;
|
|
10836
|
+
try {
|
|
10837
|
+
ids = JSON.parse(raw.source_chunk_ids ?? "[]");
|
|
10838
|
+
} catch {
|
|
10839
|
+
continue;
|
|
10840
|
+
}
|
|
10841
|
+
if (!Array.isArray(ids)) continue;
|
|
10842
|
+
const key = [...new Set(ids.filter((id) => typeof id === "string"))].sort().join("\0");
|
|
10843
|
+
if (key !== wanted) continue;
|
|
10844
|
+
const stored = await vaultMemoryRawToStored(
|
|
10845
|
+
raw,
|
|
10846
|
+
ctx.walletAddress,
|
|
10847
|
+
ctx.signMessage,
|
|
10848
|
+
ctx.embeddedWalletSigner
|
|
10849
|
+
);
|
|
10850
|
+
if (normalizeForDedupe(stored.content) === target) return stored.uniqueId;
|
|
10851
|
+
}
|
|
10852
|
+
return null;
|
|
10853
|
+
}
|
|
10851
10854
|
|
|
10852
10855
|
// src/lib/memory/decay.ts
|
|
10853
10856
|
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -10871,6 +10874,10 @@ var DEFAULT_DECAY_POLICY = {
|
|
|
10871
10874
|
hardDeleteWindowMs: HARD_DELETE_WINDOW_MS,
|
|
10872
10875
|
pastEventGraceMs: PAST_EVENT_GRACE_MS
|
|
10873
10876
|
};
|
|
10877
|
+
function lastActivityAt(m) {
|
|
10878
|
+
const observedAt = Number.isFinite(m.lastObservedAt) ? m.lastObservedAt : m.updatedAt;
|
|
10879
|
+
return Math.max(m.updatedAt, observedAt);
|
|
10880
|
+
}
|
|
10874
10881
|
function resolvePolicy(policy) {
|
|
10875
10882
|
if (!policy) return DEFAULT_DECAY_POLICY;
|
|
10876
10883
|
return {
|
|
@@ -10910,8 +10917,7 @@ function classifyDecay(m, now, policy) {
|
|
|
10910
10917
|
if (m.eventTimeEnd < now - resolved.pastEventGraceMs) return "archive";
|
|
10911
10918
|
if (m.eventTimeEnd >= now) return "keep";
|
|
10912
10919
|
}
|
|
10913
|
-
|
|
10914
|
-
if (now - Math.max(m.updatedAt, observedAt) > ttlForType(m.factType, resolved)) return "archive";
|
|
10920
|
+
if (now - lastActivityAt(m) > ttlForType(m.factType, resolved)) return "archive";
|
|
10915
10921
|
return "keep";
|
|
10916
10922
|
}
|
|
10917
10923
|
|
|
@@ -14251,6 +14257,24 @@ async function extractAndRetain(messages, retainCtx, options) {
|
|
|
14251
14257
|
const tRetain = Date.now();
|
|
14252
14258
|
for (const { candidate, isQuarantined, reason, signature } of toRetain) {
|
|
14253
14259
|
try {
|
|
14260
|
+
if (isQuarantined) {
|
|
14261
|
+
const existingId = await findQuarantinedDuplicateOp(
|
|
14262
|
+
retainCtx.vaultCtx,
|
|
14263
|
+
candidate.content,
|
|
14264
|
+
candidate.sourceMessageIds,
|
|
14265
|
+
// The same defaults retain() writes the row with.
|
|
14266
|
+
{ scope: options.scope ?? "private", folderId: options.folderId ?? null }
|
|
14267
|
+
).catch(() => null);
|
|
14268
|
+
if (existingId) {
|
|
14269
|
+
quarantinedInfo.push({
|
|
14270
|
+
candidate,
|
|
14271
|
+
memoryId: existingId,
|
|
14272
|
+
reason,
|
|
14273
|
+
signature
|
|
14274
|
+
});
|
|
14275
|
+
continue;
|
|
14276
|
+
}
|
|
14277
|
+
}
|
|
14254
14278
|
const result = await retain(candidate.content, retainCtx, {
|
|
14255
14279
|
source: "auto-extracted",
|
|
14256
14280
|
sourceChunkIds: candidate.sourceMessageIds,
|
|
@@ -14696,7 +14720,7 @@ function createLlmDecayClassifier(options) {
|
|
|
14696
14720
|
if (!content || content.trim().length === 0) return ruleVerdict;
|
|
14697
14721
|
const trimmed = content.trim().slice(0, MAX_CONTENT_CHARS);
|
|
14698
14722
|
const safe = redactor ? (await redactor.redactTextAsync(trimmed)).text : trimmed;
|
|
14699
|
-
const meta = `factType: ${input.factType ?? "none"}; ageDays: ${ageDays(input
|
|
14723
|
+
const meta = `factType: ${input.factType ?? "none"}; ageDays: ${ageDays(lastActivityAt(input), now)}`;
|
|
14700
14724
|
let parsed;
|
|
14701
14725
|
try {
|
|
14702
14726
|
parsed = await callPortalJsonCompletion({
|
|
@@ -14729,9 +14753,9 @@ Should this be kept or archived?`,
|
|
|
14729
14753
|
}
|
|
14730
14754
|
};
|
|
14731
14755
|
}
|
|
14732
|
-
function ageDays(
|
|
14733
|
-
if (!Number.isFinite(
|
|
14734
|
-
return Math.max(0, Math.floor((now -
|
|
14756
|
+
function ageDays(activityAt, now) {
|
|
14757
|
+
if (!Number.isFinite(activityAt) || !Number.isFinite(now)) return 0;
|
|
14758
|
+
return Math.max(0, Math.floor((now - activityAt) / (24 * 60 * 60 * 1e3)));
|
|
14735
14759
|
}
|
|
14736
14760
|
function parseVerdict(parsed) {
|
|
14737
14761
|
if (typeof parsed !== "object" || parsed === null) return null;
|
|
@@ -14784,7 +14808,7 @@ function createDecaySweeper(options) {
|
|
|
14784
14808
|
if (!classifier || !isBorderline(input)) return ruleVerdict;
|
|
14785
14809
|
if (input.id) {
|
|
14786
14810
|
const cached = classifierCache.get(input.id);
|
|
14787
|
-
if (cached && cached.
|
|
14811
|
+
if (cached && cached.activityAt === lastActivityAt(input)) return cached.verdict;
|
|
14788
14812
|
}
|
|
14789
14813
|
if (sweep.classifierCalls >= maxClassifierCalls) {
|
|
14790
14814
|
if (!sweep.ceilingLogged) {
|
|
@@ -14798,7 +14822,7 @@ function createDecaySweeper(options) {
|
|
|
14798
14822
|
sweep.classifierCalls++;
|
|
14799
14823
|
try {
|
|
14800
14824
|
const verdict = await classifier.classify(input, ruleVerdict, now);
|
|
14801
|
-
if (input.id) classifierCache.set(input.id, {
|
|
14825
|
+
if (input.id) classifierCache.set(input.id, { activityAt: lastActivityAt(input), verdict });
|
|
14802
14826
|
return verdict;
|
|
14803
14827
|
} catch (err) {
|
|
14804
14828
|
getLogger().warn(
|
|
@@ -17416,6 +17440,37 @@ async function assembleMemoryContext(options) {
|
|
|
17416
17440
|
import { Q as Q7 } from "@nozbe/watermelondb";
|
|
17417
17441
|
var MAX_JOBS_PER_PASS = 3;
|
|
17418
17442
|
var MAX_ATTEMPTS = 3;
|
|
17443
|
+
var MAX_FAILED_SESSIONS = 3;
|
|
17444
|
+
var MIN_FAILED_SESSION_GAP_MS = 60 * 60 * 1e3;
|
|
17445
|
+
var REQUEST_REJECTED_STATUSES = /* @__PURE__ */ new Set([400, 404, 413, 422]);
|
|
17446
|
+
var CONTENT_FAILURE_REASONS = /* @__PURE__ */ new Set([
|
|
17447
|
+
"empty-content",
|
|
17448
|
+
"invalid-json",
|
|
17449
|
+
"null-completion",
|
|
17450
|
+
"body-parse-failed"
|
|
17451
|
+
]);
|
|
17452
|
+
var BACKOFF_ALLOWANCE_MS = 2100;
|
|
17453
|
+
var RETAIN_BUDGET_MS = 12e4;
|
|
17454
|
+
function defaultBatchTimeoutMs(extract) {
|
|
17455
|
+
const attempts = Math.max(1, extract.maxAttempts ?? 3);
|
|
17456
|
+
const calls = (extract.timeoutMs ?? 6e4) * attempts + BACKOFF_ALLOWANCE_MS * (attempts - 1);
|
|
17457
|
+
const extraction = extract.totalTimeoutMs !== void 0 ? Math.min(extract.totalTimeoutMs, calls) : calls;
|
|
17458
|
+
return extraction + RETAIN_BUDGET_MS;
|
|
17459
|
+
}
|
|
17460
|
+
var BatchFailureError = class extends Error {
|
|
17461
|
+
constructor(message, terminal) {
|
|
17462
|
+
super(message);
|
|
17463
|
+
this.terminal = terminal;
|
|
17464
|
+
}
|
|
17465
|
+
};
|
|
17466
|
+
var AccountFailureError = class extends Error {
|
|
17467
|
+
};
|
|
17468
|
+
var LockedSourcesError = class extends Error {
|
|
17469
|
+
constructor(persistentIds) {
|
|
17470
|
+
super("Source messages are locked; retained for retry");
|
|
17471
|
+
this.persistentIds = persistentIds;
|
|
17472
|
+
}
|
|
17473
|
+
};
|
|
17419
17474
|
function idsOf(job) {
|
|
17420
17475
|
return JSON.parse(String(job._getRaw("message_ids")));
|
|
17421
17476
|
}
|
|
@@ -17423,6 +17478,16 @@ function seqOf(job) {
|
|
|
17423
17478
|
const raw = job._getRaw("watermark_seq");
|
|
17424
17479
|
return typeof raw === "number" && raw > 0 ? raw : void 0;
|
|
17425
17480
|
}
|
|
17481
|
+
function failedSessionsOf(job, head) {
|
|
17482
|
+
if (job._getRaw("failed_head") !== head) return 0;
|
|
17483
|
+
const raw = job._getRaw("failed_sessions");
|
|
17484
|
+
return typeof raw === "number" && raw > 0 ? raw : 0;
|
|
17485
|
+
}
|
|
17486
|
+
function failedAtOf(job, head) {
|
|
17487
|
+
if (job._getRaw("failed_head") !== head) return void 0;
|
|
17488
|
+
const raw = job._getRaw("failed_at");
|
|
17489
|
+
return typeof raw === "number" ? raw : void 0;
|
|
17490
|
+
}
|
|
17426
17491
|
function createDurableAutoExtractor(options) {
|
|
17427
17492
|
const vault = options.retainCtx.vaultCtx;
|
|
17428
17493
|
const database = vault.database;
|
|
@@ -17445,6 +17510,9 @@ function createDurableAutoExtractor(options) {
|
|
|
17445
17510
|
let writes = Promise.resolve();
|
|
17446
17511
|
let storeFailures = 0;
|
|
17447
17512
|
const attempts = /* @__PURE__ */ new Map();
|
|
17513
|
+
const sessionFailures = /* @__PURE__ */ new Map();
|
|
17514
|
+
const skippedThisSession = /* @__PURE__ */ new Set();
|
|
17515
|
+
const clock = options.now ?? Date.now;
|
|
17448
17516
|
const reportError = (error, conversationId) => {
|
|
17449
17517
|
try {
|
|
17450
17518
|
options.onError?.(error instanceof Error ? error : new Error(String(error)), conversationId);
|
|
@@ -17473,6 +17541,7 @@ function createDurableAutoExtractor(options) {
|
|
|
17473
17541
|
}
|
|
17474
17542
|
async function extractBatch(messages, conversationId, ids, jobScope, jobFolderId) {
|
|
17475
17543
|
let watchdog;
|
|
17544
|
+
let cancelled = false;
|
|
17476
17545
|
const model = options.modelForScope?.(jobScope) ?? options.extract.model;
|
|
17477
17546
|
try {
|
|
17478
17547
|
return await Promise.race([
|
|
@@ -17483,7 +17552,7 @@ function createDurableAutoExtractor(options) {
|
|
|
17483
17552
|
...options.retainCtx,
|
|
17484
17553
|
vaultCtx: {
|
|
17485
17554
|
...vault,
|
|
17486
|
-
canWrite: async () => !disposed && await storage.conversationsCollection.query(Q7.where("conversation_id", conversationId), Q7.where("is_deleted", false)).fetchCount() > 0 && await storage.messagesCollection.query(Q7.where("conversation_id", conversationId), Q7.where("id", Q7.oneOf(ids))).fetchCount() === ids.length && (!vault.canWrite || await vault.canWrite()) && !disposed
|
|
17555
|
+
canWrite: async () => !disposed && !cancelled && await storage.conversationsCollection.query(Q7.where("conversation_id", conversationId), Q7.where("is_deleted", false)).fetchCount() > 0 && await storage.messagesCollection.query(Q7.where("conversation_id", conversationId), Q7.where("id", Q7.oneOf(ids))).fetchCount() === ids.length && (!vault.canWrite || await vault.canWrite()) && !disposed && !cancelled
|
|
17487
17556
|
}
|
|
17488
17557
|
},
|
|
17489
17558
|
// The model follows the job's scope, not the mode alive at drain time.
|
|
@@ -17503,14 +17572,116 @@ function createDurableAutoExtractor(options) {
|
|
|
17503
17572
|
new Promise((_resolve, reject) => {
|
|
17504
17573
|
watchdog = setTimeout(
|
|
17505
17574
|
() => reject(new Error("Extraction did not settle in time; retained for retry")),
|
|
17506
|
-
options.batchTimeoutMs ??
|
|
17575
|
+
options.batchTimeoutMs ?? defaultBatchTimeoutMs(options.extract)
|
|
17507
17576
|
);
|
|
17508
17577
|
})
|
|
17509
17578
|
]);
|
|
17510
17579
|
} finally {
|
|
17580
|
+
cancelled = true;
|
|
17511
17581
|
if (watchdog !== void 0) clearTimeout(watchdog);
|
|
17512
17582
|
}
|
|
17513
17583
|
}
|
|
17584
|
+
async function acknowledge(job, ids, loaded, abandoned) {
|
|
17585
|
+
attempts.delete(job.id);
|
|
17586
|
+
sessionFailures.delete(job.id);
|
|
17587
|
+
skippedThisSession.delete(job.id);
|
|
17588
|
+
const sequences = /* @__PURE__ */ new Map();
|
|
17589
|
+
for (const message of loaded)
|
|
17590
|
+
if (message && message.messageId > 0) sequences.set(message.uniqueId, message.messageId);
|
|
17591
|
+
await database.write(async () => {
|
|
17592
|
+
const current = idsOf(job);
|
|
17593
|
+
const acknowledged = ids.filter((id) => current.includes(id));
|
|
17594
|
+
if (!acknowledged.length) return;
|
|
17595
|
+
const done = new Set(acknowledged);
|
|
17596
|
+
const remaining = current.filter((id) => !done.has(id));
|
|
17597
|
+
await job.update((r) => {
|
|
17598
|
+
r._setRaw(
|
|
17599
|
+
"message_ids",
|
|
17600
|
+
JSON.stringify(
|
|
17601
|
+
remaining.length ? [...abandoned ? [] : acknowledged.slice(-2), ...remaining] : []
|
|
17602
|
+
)
|
|
17603
|
+
);
|
|
17604
|
+
const newest = acknowledged[acknowledged.length - 1];
|
|
17605
|
+
r._setRaw("watermark", newest);
|
|
17606
|
+
const sequence = sequences.get(newest);
|
|
17607
|
+
if (sequence !== void 0) {
|
|
17608
|
+
const prior = seqOf(job);
|
|
17609
|
+
r._setRaw("watermark_seq", prior === void 0 ? sequence : Math.max(prior, sequence));
|
|
17610
|
+
}
|
|
17611
|
+
r._setRaw("failed_sessions", abandoned ? MAX_FAILED_SESSIONS : null);
|
|
17612
|
+
r._setRaw("failed_head", abandoned ? newest : null);
|
|
17613
|
+
r._setRaw("failed_at", null);
|
|
17614
|
+
});
|
|
17615
|
+
});
|
|
17616
|
+
}
|
|
17617
|
+
async function recordFailure(job, ids, loaded, error, conversationId) {
|
|
17618
|
+
const head = ids[0];
|
|
17619
|
+
if (!head || !loaded.some(Boolean)) return false;
|
|
17620
|
+
if (error instanceof AccountFailureError) {
|
|
17621
|
+
skippedThisSession.add(job.id);
|
|
17622
|
+
return false;
|
|
17623
|
+
}
|
|
17624
|
+
const counted = error instanceof BatchFailureError || error instanceof LockedSourcesError && error.persistentIds.length > 0;
|
|
17625
|
+
if (!counted) return false;
|
|
17626
|
+
const prior = sessionFailures.get(job.id);
|
|
17627
|
+
const count = prior?.head === head ? prior.count + 1 : 1;
|
|
17628
|
+
sessionFailures.set(job.id, { head, count });
|
|
17629
|
+
const terminal = error instanceof BatchFailureError && error.terminal;
|
|
17630
|
+
if (!terminal && count < MAX_ATTEMPTS) return false;
|
|
17631
|
+
skippedThisSession.add(job.id);
|
|
17632
|
+
const now = clock();
|
|
17633
|
+
const lastCounted = failedAtOf(job, head);
|
|
17634
|
+
if (lastCounted !== void 0 && now - lastCounted < MIN_FAILED_SESSION_GAP_MS) return false;
|
|
17635
|
+
const failures = failedSessionsOf(job, head) + 1;
|
|
17636
|
+
if (failures < MAX_FAILED_SESSIONS) {
|
|
17637
|
+
await database.write(
|
|
17638
|
+
() => job.update((r) => {
|
|
17639
|
+
r._setRaw("failed_sessions", failures);
|
|
17640
|
+
r._setRaw("failed_head", head);
|
|
17641
|
+
r._setRaw("failed_at", now);
|
|
17642
|
+
})
|
|
17643
|
+
);
|
|
17644
|
+
return false;
|
|
17645
|
+
}
|
|
17646
|
+
if (error instanceof LockedSourcesError) {
|
|
17647
|
+
const dropped = new Set(error.persistentIds);
|
|
17648
|
+
let newest;
|
|
17649
|
+
for (const message of loaded)
|
|
17650
|
+
if (message && dropped.has(message.uniqueId) && message.messageId > (newest?.seq ?? 0))
|
|
17651
|
+
newest = { id: message.uniqueId, seq: message.messageId };
|
|
17652
|
+
const advance = newest !== void 0 && newest.seq > (seqOf(job) ?? 0) ? newest : void 0;
|
|
17653
|
+
await database.write(
|
|
17654
|
+
() => job.update((r) => {
|
|
17655
|
+
r._setRaw("message_ids", JSON.stringify(idsOf(job).filter((id) => !dropped.has(id))));
|
|
17656
|
+
if (advance) {
|
|
17657
|
+
r._setRaw("watermark", advance.id);
|
|
17658
|
+
r._setRaw("watermark_seq", advance.seq);
|
|
17659
|
+
}
|
|
17660
|
+
r._setRaw("failed_sessions", advance ? MAX_FAILED_SESSIONS : null);
|
|
17661
|
+
r._setRaw("failed_head", advance ? advance.id : null);
|
|
17662
|
+
r._setRaw("failed_at", null);
|
|
17663
|
+
})
|
|
17664
|
+
);
|
|
17665
|
+
attempts.delete(job.id);
|
|
17666
|
+
sessionFailures.delete(job.id);
|
|
17667
|
+
skippedThisSession.delete(job.id);
|
|
17668
|
+
reportError(
|
|
17669
|
+
new Error(
|
|
17670
|
+
`Dropped ${dropped.size} source id(s) that stayed locked for ${failures} sessions to unblock extraction`
|
|
17671
|
+
),
|
|
17672
|
+
conversationId
|
|
17673
|
+
);
|
|
17674
|
+
return true;
|
|
17675
|
+
}
|
|
17676
|
+
await acknowledge(job, ids, loaded, true);
|
|
17677
|
+
reportError(
|
|
17678
|
+
new Error(
|
|
17679
|
+
`Abandoned an extraction batch of ${ids.length} source id(s) after it failed in ${failures} sessions`
|
|
17680
|
+
),
|
|
17681
|
+
conversationId
|
|
17682
|
+
);
|
|
17683
|
+
return true;
|
|
17684
|
+
}
|
|
17514
17685
|
async function drain() {
|
|
17515
17686
|
if (disposed || running) return;
|
|
17516
17687
|
running = true;
|
|
@@ -17525,21 +17696,23 @@ function createDurableAutoExtractor(options) {
|
|
|
17525
17696
|
for (const job of pending) {
|
|
17526
17697
|
if (disposed) break;
|
|
17527
17698
|
const attempted = attempts.get(job.id) ?? 0;
|
|
17528
|
-
if (attempted >= MAX_ATTEMPTS) continue;
|
|
17699
|
+
if (attempted >= MAX_ATTEMPTS || skippedThisSession.has(job.id)) continue;
|
|
17529
17700
|
if (processed >= MAX_JOBS_PER_PASS) {
|
|
17530
17701
|
scheduleAfterSuccess = true;
|
|
17531
17702
|
break;
|
|
17532
17703
|
}
|
|
17533
17704
|
processed++;
|
|
17534
17705
|
const conversationId = String(job._getRaw("conversation_id"));
|
|
17706
|
+
let ids = [];
|
|
17707
|
+
let loaded = [];
|
|
17535
17708
|
try {
|
|
17536
17709
|
const conversations = await storage.conversationsCollection.query(Q7.where("conversation_id", conversationId), Q7.where("is_deleted", false)).fetchCount();
|
|
17537
17710
|
if (!conversations) {
|
|
17538
17711
|
await database.write(() => job.destroyPermanently());
|
|
17539
17712
|
continue;
|
|
17540
17713
|
}
|
|
17541
|
-
|
|
17542
|
-
|
|
17714
|
+
ids = idsOf(job).slice(0, 20);
|
|
17715
|
+
loaded = await Promise.all(ids.map((id) => getMessageOp(storage, id)));
|
|
17543
17716
|
const missing = ids.filter((_id, index) => loaded[index] === null);
|
|
17544
17717
|
if (missing.length) {
|
|
17545
17718
|
if (attempted < MAX_ATTEMPTS - 1)
|
|
@@ -17563,17 +17736,21 @@ function createDurableAutoExtractor(options) {
|
|
|
17563
17736
|
);
|
|
17564
17737
|
continue;
|
|
17565
17738
|
}
|
|
17566
|
-
|
|
17567
|
-
|
|
17568
|
-
|
|
17569
|
-
|
|
17739
|
+
const locked = loaded.filter((message) => message && isEncrypted(message.content));
|
|
17740
|
+
if (locked.length) {
|
|
17741
|
+
throw new LockedSourcesError(
|
|
17742
|
+
locked.flatMap(
|
|
17743
|
+
(message) => message.decryptionStatus === "auth_mismatch" || message.decryptionStatus === "invalid_payload" ? [message.uniqueId] : []
|
|
17744
|
+
)
|
|
17745
|
+
);
|
|
17570
17746
|
}
|
|
17571
17747
|
const messages = loaded.flatMap(
|
|
17572
17748
|
(m) => m && m.conversationId === conversationId && (m.role === "user" || m.role === "assistant") ? [{ id: m.uniqueId, role: m.role, content: m.content }] : []
|
|
17573
17749
|
);
|
|
17574
17750
|
let success = messages.length === 0;
|
|
17751
|
+
let result;
|
|
17575
17752
|
if (messages.length) {
|
|
17576
|
-
|
|
17753
|
+
result = await extractBatch(
|
|
17577
17754
|
messages,
|
|
17578
17755
|
conversationId,
|
|
17579
17756
|
ids,
|
|
@@ -17583,41 +17760,35 @@ function createDurableAutoExtractor(options) {
|
|
|
17583
17760
|
success = result.failedCount === 0 && result.outcome !== "empty-after-retry";
|
|
17584
17761
|
options.onTurnComplete?.(result);
|
|
17585
17762
|
}
|
|
17586
|
-
if (!success)
|
|
17587
|
-
|
|
17588
|
-
|
|
17589
|
-
|
|
17590
|
-
|
|
17591
|
-
|
|
17592
|
-
|
|
17593
|
-
|
|
17594
|
-
const acknowledged = ids.filter((id) => current.includes(id));
|
|
17595
|
-
if (!acknowledged.length) return;
|
|
17596
|
-
const done = new Set(acknowledged);
|
|
17597
|
-
const remaining = current.filter((id) => !done.has(id));
|
|
17598
|
-
if (remaining.length) {
|
|
17599
|
-
await job.update(
|
|
17600
|
-
(r) => r._setRaw("message_ids", JSON.stringify([...acknowledged.slice(-2), ...remaining]))
|
|
17601
|
-
);
|
|
17602
|
-
} else await job.update((r) => r._setRaw("message_ids", "[]"));
|
|
17603
|
-
const newest = acknowledged[acknowledged.length - 1];
|
|
17604
|
-
await job.update((r) => {
|
|
17605
|
-
r._setRaw("watermark", newest);
|
|
17606
|
-
const sequence = sequences.get(newest);
|
|
17607
|
-
if (sequence !== void 0) {
|
|
17608
|
-
const prior = seqOf(job);
|
|
17609
|
-
r._setRaw(
|
|
17610
|
-
"watermark_seq",
|
|
17611
|
-
prior === void 0 ? sequence : Math.max(prior, sequence)
|
|
17763
|
+
if (!success) {
|
|
17764
|
+
const failure = result?.failure;
|
|
17765
|
+
if (failure?.reason === "http-terminal") {
|
|
17766
|
+
const status = failure.httpStatus;
|
|
17767
|
+
if (status !== void 0 && REQUEST_REJECTED_STATUSES.has(status))
|
|
17768
|
+
throw new BatchFailureError(
|
|
17769
|
+
`Extraction rejected the batch (HTTP ${status}); not retried this session`,
|
|
17770
|
+
true
|
|
17612
17771
|
);
|
|
17613
|
-
|
|
17614
|
-
|
|
17615
|
-
|
|
17772
|
+
throw new AccountFailureError(
|
|
17773
|
+
`Extraction refused for the account (HTTP ${status ?? "unknown"}); retried next session`
|
|
17774
|
+
);
|
|
17775
|
+
}
|
|
17776
|
+
if ((result?.failedCount ?? 0) > 0 || failure !== void 0 && CONTENT_FAILURE_REASONS.has(failure.reason))
|
|
17777
|
+
throw new BatchFailureError("Extraction batch incomplete; retained for retry", false);
|
|
17778
|
+
throw new Error("Extraction batch incomplete; retained for retry");
|
|
17779
|
+
}
|
|
17780
|
+
await acknowledge(job, ids, loaded, false);
|
|
17616
17781
|
scheduleAfterSuccess = true;
|
|
17617
17782
|
} catch (error) {
|
|
17618
17783
|
attempts.set(job.id, attempted + 1);
|
|
17619
17784
|
retry = true;
|
|
17620
17785
|
reportError(error, conversationId);
|
|
17786
|
+
try {
|
|
17787
|
+
if (await recordFailure(job, ids, loaded, error, conversationId))
|
|
17788
|
+
scheduleAfterSuccess = true;
|
|
17789
|
+
} catch (recordError) {
|
|
17790
|
+
reportError(recordError, conversationId);
|
|
17791
|
+
}
|
|
17621
17792
|
}
|
|
17622
17793
|
}
|
|
17623
17794
|
} catch (error) {
|
|
@@ -17687,7 +17858,7 @@ function createDurableAutoExtractor(options) {
|
|
|
17687
17858
|
const start = boundary >= 0 ? boundary + 1 : Math.max(0, ids.length - Math.max(1, options.windowSize ?? 6));
|
|
17688
17859
|
if (start >= ids.length) return;
|
|
17689
17860
|
const ownWatermark = job?._getRaw("watermark");
|
|
17690
|
-
const atOwnWatermark = !!job && boundary >= 0 && (ids[boundary] === ownWatermark || sequences.get(ids[boundary]) !== void 0 && sequences.get(ids[boundary]) === seqOf(job));
|
|
17861
|
+
const atOwnWatermark = !!job && boundary >= 0 && failedSessionsOf(job, String(ownWatermark)) < MAX_FAILED_SESSIONS && (ids[boundary] === ownWatermark || sequences.get(ids[boundary]) !== void 0 && sequences.get(ids[boundary]) === seqOf(job));
|
|
17691
17862
|
const overlapStart = atOwnWatermark ? Math.max(0, boundary - 1) : start;
|
|
17692
17863
|
const fresh = ids.slice(overlapStart).filter((id) => !foreignSources.has(id));
|
|
17693
17864
|
if (!fresh.length) return;
|
|
@@ -18134,6 +18305,9 @@ var MANUAL_FACT_TYPES = [
|
|
|
18134
18305
|
function normalizeManualFactType(value) {
|
|
18135
18306
|
return typeof value === "string" && MANUAL_FACT_TYPES.includes(value) ? value : void 0;
|
|
18136
18307
|
}
|
|
18308
|
+
function isSharedScope(scope) {
|
|
18309
|
+
return scope === "shared";
|
|
18310
|
+
}
|
|
18137
18311
|
async function notifyWritten(options, input, outcome) {
|
|
18138
18312
|
try {
|
|
18139
18313
|
await options?.onWritten?.({ input, outcome });
|
|
@@ -18202,14 +18376,17 @@ function createMemoryVaultTool(vaultCtx, options, embeddingOptions, cache) {
|
|
|
18202
18376
|
try {
|
|
18203
18377
|
const isUpdate = !!id;
|
|
18204
18378
|
let previousContent;
|
|
18379
|
+
const scope = options?.scope ?? "private";
|
|
18205
18380
|
if (isUpdate) {
|
|
18206
18381
|
const existing = await getVaultMemoryOp(vaultCtx, id);
|
|
18207
18382
|
if (!existing) {
|
|
18208
18383
|
return `Error: Memory with ID "${id}" not found. Creating a new memory instead would require a separate call without an ID.`;
|
|
18209
18384
|
}
|
|
18385
|
+
if (isSharedScope(existing.scope) !== isSharedScope(scope)) {
|
|
18386
|
+
return `Error: Memory "${id}" belongs to a different scope than this conversation and cannot be updated from here. It was not modified. To record this fact, save it as a new memory without an ID.`;
|
|
18387
|
+
}
|
|
18210
18388
|
previousContent = existing.content;
|
|
18211
18389
|
}
|
|
18212
|
-
const scope = options?.scope ?? "private";
|
|
18213
18390
|
const operation = {
|
|
18214
18391
|
action: isUpdate ? "update" : "add",
|
|
18215
18392
|
content,
|
|
@@ -21269,7 +21446,7 @@ __decorateClass([
|
|
|
21269
21446
|
], VaultFolder.prototype, "context", 2);
|
|
21270
21447
|
|
|
21271
21448
|
// src/lib/db/schema.ts
|
|
21272
|
-
var SDK_SCHEMA_VERSION =
|
|
21449
|
+
var SDK_SCHEMA_VERSION = 47;
|
|
21273
21450
|
var sdkSchema = appSchema2({
|
|
21274
21451
|
version: SDK_SCHEMA_VERSION,
|
|
21275
21452
|
tables: [
|
|
@@ -21285,7 +21462,11 @@ var sdkSchema = appSchema2({
|
|
|
21285
21462
|
// source. `watermark` alone is an id, and deleting that message erased
|
|
21286
21463
|
// the anchor — see the column note in extractionJobs/models.ts.
|
|
21287
21464
|
{ name: "watermark_seq", type: "number", isOptional: true },
|
|
21288
|
-
{ name: "folder_id", type: "string", isOptional: true }
|
|
21465
|
+
{ name: "folder_id", type: "string", isOptional: true },
|
|
21466
|
+
// Poison accounting for the head batch; see extractionJobs/models.ts.
|
|
21467
|
+
{ name: "failed_sessions", type: "number", isOptional: true },
|
|
21468
|
+
{ name: "failed_head", type: "string", isOptional: true },
|
|
21469
|
+
{ name: "failed_at", type: "number", isOptional: true }
|
|
21289
21470
|
]
|
|
21290
21471
|
}),
|
|
21291
21472
|
// Chat storage tables
|
|
@@ -22309,6 +22490,22 @@ var sdkMigrations = schemaMigrations2({
|
|
|
22309
22490
|
]
|
|
22310
22491
|
})
|
|
22311
22492
|
]
|
|
22493
|
+
},
|
|
22494
|
+
// v46 -> v47: persisted poison count (and when it was last counted) for a
|
|
22495
|
+
// durable-extraction job's head batch. NULL = never failed, which is right
|
|
22496
|
+
// for every existing row.
|
|
22497
|
+
{
|
|
22498
|
+
toVersion: 47,
|
|
22499
|
+
steps: [
|
|
22500
|
+
addColumns2({
|
|
22501
|
+
table: "memory_extraction_jobs",
|
|
22502
|
+
columns: [
|
|
22503
|
+
{ name: "failed_sessions", type: "number", isOptional: true },
|
|
22504
|
+
{ name: "failed_head", type: "string", isOptional: true },
|
|
22505
|
+
{ name: "failed_at", type: "number", isOptional: true }
|
|
22506
|
+
]
|
|
22507
|
+
})
|
|
22508
|
+
]
|
|
22312
22509
|
}
|
|
22313
22510
|
]
|
|
22314
22511
|
});
|