@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/react/index.mjs
CHANGED
|
@@ -11159,8 +11159,42 @@ async function vaultMemoryToStored(memory, walletAddress, signMessage, embeddedW
|
|
|
11159
11159
|
}
|
|
11160
11160
|
return raw;
|
|
11161
11161
|
}
|
|
11162
|
+
function populateNewVaultMemory(record, ctx, opts, encryptedContent) {
|
|
11163
|
+
record._setRaw("content", encryptedContent);
|
|
11164
|
+
record._setRaw("scope", opts.scope ?? "private");
|
|
11165
|
+
record._setRaw("folder_id", opts.folderId ?? null);
|
|
11166
|
+
record._setRaw("user_id", ctx.userId ?? null);
|
|
11167
|
+
record._setRaw("is_deleted", false);
|
|
11168
|
+
if (opts.embedding !== void 0) {
|
|
11169
|
+
record._setRaw("embedding", opts.embedding);
|
|
11170
|
+
record._setRaw("embedding_model", opts.embeddingModel ?? null);
|
|
11171
|
+
}
|
|
11172
|
+
if (opts.sourceChunkIds !== void 0) {
|
|
11173
|
+
record._setRaw("source_chunk_ids", JSON.stringify(opts.sourceChunkIds));
|
|
11174
|
+
}
|
|
11175
|
+
record._setRaw("proof_count", opts.proofCount ?? 1);
|
|
11176
|
+
record._setRaw("source", opts.source ?? "manual");
|
|
11177
|
+
if (opts.eventTime) {
|
|
11178
|
+
record._setRaw("event_time_start", opts.eventTime.start ?? null);
|
|
11179
|
+
record._setRaw("event_time_end", opts.eventTime.end ?? null);
|
|
11180
|
+
record._setRaw("event_time_kind", opts.eventTime.kind ?? null);
|
|
11181
|
+
}
|
|
11182
|
+
if (opts.factType !== void 0) {
|
|
11183
|
+
record._setRaw("fact_type", opts.factType);
|
|
11184
|
+
}
|
|
11185
|
+
if (opts.trustTier !== void 0) {
|
|
11186
|
+
record._setRaw("trust_tier", normalizeTrustTier(opts.trustTier));
|
|
11187
|
+
}
|
|
11188
|
+
record._setRaw("visibility", opts.visibility ?? "private");
|
|
11189
|
+
record._setRaw(
|
|
11190
|
+
"published_at",
|
|
11191
|
+
opts.visibility && opts.visibility !== "private" ? opts.publishedAt ?? Date.now() : null
|
|
11192
|
+
);
|
|
11193
|
+
if (opts.geohash !== void 0) {
|
|
11194
|
+
record._setRaw("geohash", opts.geohash);
|
|
11195
|
+
}
|
|
11196
|
+
}
|
|
11162
11197
|
async function createVaultMemoryOp(ctx, opts) {
|
|
11163
|
-
const scope = opts.scope ?? "private";
|
|
11164
11198
|
const encryptedContent = ctx.walletAddress && ctx.signMessage ? await encryptVaultMemoryContent(
|
|
11165
11199
|
opts.content,
|
|
11166
11200
|
ctx.walletAddress,
|
|
@@ -11170,47 +11204,14 @@ async function createVaultMemoryOp(ctx, opts) {
|
|
|
11170
11204
|
const created = await ctx.database.write(async () => {
|
|
11171
11205
|
if (ctx.canWrite && !await ctx.canWrite())
|
|
11172
11206
|
throw new Error("Memory source is no longer eligible");
|
|
11173
|
-
return ctx.vaultMemoryCollection.create(
|
|
11174
|
-
record
|
|
11175
|
-
|
|
11176
|
-
record._setRaw("folder_id", opts.folderId ?? null);
|
|
11177
|
-
record._setRaw("user_id", ctx.userId ?? null);
|
|
11178
|
-
record._setRaw("is_deleted", false);
|
|
11179
|
-
if (opts.embedding !== void 0) {
|
|
11180
|
-
record._setRaw("embedding", opts.embedding);
|
|
11181
|
-
record._setRaw("embedding_model", opts.embeddingModel ?? null);
|
|
11182
|
-
}
|
|
11183
|
-
if (opts.sourceChunkIds !== void 0) {
|
|
11184
|
-
record._setRaw("source_chunk_ids", JSON.stringify(opts.sourceChunkIds));
|
|
11185
|
-
}
|
|
11186
|
-
record._setRaw("proof_count", opts.proofCount ?? 1);
|
|
11187
|
-
record._setRaw("source", opts.source ?? "manual");
|
|
11188
|
-
if (opts.eventTime) {
|
|
11189
|
-
record._setRaw("event_time_start", opts.eventTime.start ?? null);
|
|
11190
|
-
record._setRaw("event_time_end", opts.eventTime.end ?? null);
|
|
11191
|
-
record._setRaw("event_time_kind", opts.eventTime.kind ?? null);
|
|
11192
|
-
}
|
|
11193
|
-
if (opts.factType !== void 0) {
|
|
11194
|
-
record._setRaw("fact_type", opts.factType);
|
|
11195
|
-
}
|
|
11196
|
-
if (opts.trustTier !== void 0) {
|
|
11197
|
-
record._setRaw("trust_tier", normalizeTrustTier(opts.trustTier));
|
|
11198
|
-
}
|
|
11199
|
-
record._setRaw("visibility", opts.visibility ?? "private");
|
|
11200
|
-
record._setRaw(
|
|
11201
|
-
"published_at",
|
|
11202
|
-
opts.visibility && opts.visibility !== "private" ? opts.publishedAt ?? Date.now() : null
|
|
11203
|
-
);
|
|
11204
|
-
if (opts.geohash !== void 0) {
|
|
11205
|
-
record._setRaw("geohash", opts.geohash);
|
|
11206
|
-
}
|
|
11207
|
-
});
|
|
11207
|
+
return ctx.vaultMemoryCollection.create(
|
|
11208
|
+
(record) => populateNewVaultMemory(record, ctx, opts, encryptedContent)
|
|
11209
|
+
);
|
|
11208
11210
|
});
|
|
11209
11211
|
return vaultMemoryToStored(created, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner);
|
|
11210
11212
|
}
|
|
11211
11213
|
async function createSupersedingMemoryOp(ctx, opts, targetId) {
|
|
11212
11214
|
if (!targetId) return { created: null, retired: false };
|
|
11213
|
-
const scope = opts.scope ?? "private";
|
|
11214
11215
|
const encryptedContent = ctx.walletAddress && ctx.signMessage ? await encryptVaultMemoryContent(
|
|
11215
11216
|
opts.content,
|
|
11216
11217
|
ctx.walletAddress,
|
|
@@ -11228,27 +11229,9 @@ async function createSupersedingMemoryOp(ctx, opts, targetId) {
|
|
|
11228
11229
|
return;
|
|
11229
11230
|
}
|
|
11230
11231
|
if (target.isDeleted || target.supersededBy || !isOwnedByCtxUser(ctx, target)) return;
|
|
11231
|
-
createdRecord = await ctx.vaultMemoryCollection.create(
|
|
11232
|
-
record
|
|
11233
|
-
|
|
11234
|
-
record._setRaw("folder_id", opts.folderId ?? null);
|
|
11235
|
-
record._setRaw("user_id", ctx.userId ?? null);
|
|
11236
|
-
record._setRaw("is_deleted", false);
|
|
11237
|
-
if (opts.embedding !== void 0) {
|
|
11238
|
-
record._setRaw("embedding", opts.embedding);
|
|
11239
|
-
record._setRaw("embedding_model", opts.embeddingModel ?? null);
|
|
11240
|
-
}
|
|
11241
|
-
if (opts.sourceChunkIds !== void 0) {
|
|
11242
|
-
record._setRaw("source_chunk_ids", JSON.stringify(opts.sourceChunkIds));
|
|
11243
|
-
}
|
|
11244
|
-
record._setRaw("proof_count", opts.proofCount ?? 1);
|
|
11245
|
-
record._setRaw("source", opts.source ?? "manual");
|
|
11246
|
-
if (opts.eventTime) {
|
|
11247
|
-
record._setRaw("event_time_start", opts.eventTime.start ?? null);
|
|
11248
|
-
record._setRaw("event_time_end", opts.eventTime.end ?? null);
|
|
11249
|
-
record._setRaw("event_time_kind", opts.eventTime.kind ?? null);
|
|
11250
|
-
}
|
|
11251
|
-
});
|
|
11232
|
+
createdRecord = await ctx.vaultMemoryCollection.create(
|
|
11233
|
+
(record) => populateNewVaultMemory(record, ctx, opts, encryptedContent)
|
|
11234
|
+
);
|
|
11252
11235
|
await target.update((r) => {
|
|
11253
11236
|
r._setRaw("superseded_by", createdRecord.id);
|
|
11254
11237
|
r._setRaw("superseded_at", Date.now());
|
|
@@ -11328,41 +11311,9 @@ async function createVaultMemoriesBatchOp(ctx, optionsArray) {
|
|
|
11328
11311
|
if (ctx.canWrite && !await ctx.canWrite())
|
|
11329
11312
|
throw new Error("Memory source is no longer eligible");
|
|
11330
11313
|
const prepared = optionsArray.map(
|
|
11331
|
-
(opts, i) => ctx.vaultMemoryCollection.prepareCreate(
|
|
11332
|
-
record
|
|
11333
|
-
|
|
11334
|
-
record._setRaw("folder_id", opts.folderId ?? null);
|
|
11335
|
-
record._setRaw("user_id", ctx.userId ?? null);
|
|
11336
|
-
record._setRaw("is_deleted", false);
|
|
11337
|
-
if (optionsArray[i].embedding !== void 0) {
|
|
11338
|
-
record._setRaw("embedding", optionsArray[i].embedding);
|
|
11339
|
-
record._setRaw("embedding_model", optionsArray[i].embeddingModel ?? null);
|
|
11340
|
-
}
|
|
11341
|
-
if (opts.sourceChunkIds !== void 0) {
|
|
11342
|
-
record._setRaw("source_chunk_ids", JSON.stringify(opts.sourceChunkIds));
|
|
11343
|
-
}
|
|
11344
|
-
record._setRaw("proof_count", opts.proofCount ?? 1);
|
|
11345
|
-
record._setRaw("source", opts.source ?? "manual");
|
|
11346
|
-
if (opts.eventTime) {
|
|
11347
|
-
record._setRaw("event_time_start", opts.eventTime.start ?? null);
|
|
11348
|
-
record._setRaw("event_time_end", opts.eventTime.end ?? null);
|
|
11349
|
-
record._setRaw("event_time_kind", opts.eventTime.kind ?? null);
|
|
11350
|
-
}
|
|
11351
|
-
if (opts.factType !== void 0) {
|
|
11352
|
-
record._setRaw("fact_type", opts.factType);
|
|
11353
|
-
}
|
|
11354
|
-
if (opts.trustTier !== void 0) {
|
|
11355
|
-
record._setRaw("trust_tier", normalizeTrustTier(opts.trustTier));
|
|
11356
|
-
}
|
|
11357
|
-
record._setRaw("visibility", opts.visibility ?? "private");
|
|
11358
|
-
record._setRaw(
|
|
11359
|
-
"published_at",
|
|
11360
|
-
opts.visibility && opts.visibility !== "private" ? opts.publishedAt ?? Date.now() : null
|
|
11361
|
-
);
|
|
11362
|
-
if (opts.geohash !== void 0) {
|
|
11363
|
-
record._setRaw("geohash", opts.geohash);
|
|
11364
|
-
}
|
|
11365
|
-
})
|
|
11314
|
+
(opts, i) => ctx.vaultMemoryCollection.prepareCreate(
|
|
11315
|
+
(record) => populateNewVaultMemory(record, ctx, opts, encryptedContents[i])
|
|
11316
|
+
)
|
|
11366
11317
|
);
|
|
11367
11318
|
await ctx.database.batch(...prepared);
|
|
11368
11319
|
return prepared;
|
|
@@ -12228,21 +12179,73 @@ async function hardDeleteDecayedOp(ctx, id, opts) {
|
|
|
12228
12179
|
return false;
|
|
12229
12180
|
}
|
|
12230
12181
|
}
|
|
12231
|
-
async function updateVaultMemoryEmbeddingOp(ctx, id, embedding, embeddingModel) {
|
|
12182
|
+
async function updateVaultMemoryEmbeddingOp(ctx, id, embedding, embeddingModel, expected) {
|
|
12232
12183
|
try {
|
|
12233
12184
|
const record = await ctx.vaultMemoryCollection.find(id);
|
|
12234
12185
|
if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) return false;
|
|
12186
|
+
const readUpdatedAt = record.updatedAt.getTime();
|
|
12187
|
+
const readStoredContent = record._getRaw("content");
|
|
12188
|
+
if (expected?.updatedAt !== void 0 && expected.updatedAt !== readUpdatedAt) return false;
|
|
12189
|
+
if (expected?.content !== void 0) {
|
|
12190
|
+
const current = await vaultMemoryToStored(
|
|
12191
|
+
record,
|
|
12192
|
+
ctx.walletAddress,
|
|
12193
|
+
ctx.signMessage,
|
|
12194
|
+
ctx.embeddedWalletSigner
|
|
12195
|
+
);
|
|
12196
|
+
if (current.content !== expected.content) return false;
|
|
12197
|
+
}
|
|
12198
|
+
let written = false;
|
|
12235
12199
|
await ctx.database.write(async () => {
|
|
12200
|
+
if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) return;
|
|
12201
|
+
const originalUpdatedAt = record.updatedAt.getTime();
|
|
12202
|
+
if (originalUpdatedAt !== readUpdatedAt || record._getRaw("content") !== readStoredContent)
|
|
12203
|
+
return;
|
|
12236
12204
|
await record.update((r) => {
|
|
12237
12205
|
r._setRaw("embedding", embedding);
|
|
12238
12206
|
r._setRaw("embedding_model", embeddingModel);
|
|
12207
|
+
r._setRaw("updated_at", originalUpdatedAt);
|
|
12239
12208
|
});
|
|
12209
|
+
written = true;
|
|
12240
12210
|
});
|
|
12241
|
-
return
|
|
12211
|
+
return written;
|
|
12242
12212
|
} catch {
|
|
12243
12213
|
return false;
|
|
12244
12214
|
}
|
|
12245
12215
|
}
|
|
12216
|
+
function normalizeForDedupe(content) {
|
|
12217
|
+
return content.normalize("NFKC").trim().replace(/\s+/g, " ").toLowerCase();
|
|
12218
|
+
}
|
|
12219
|
+
async function findQuarantinedDuplicateOp(ctx, content, sourceIds, where) {
|
|
12220
|
+
const wanted = [...new Set(sourceIds)].sort().join("\0");
|
|
12221
|
+
const target = normalizeForDedupe(content);
|
|
12222
|
+
const rows = await ctx.vaultMemoryCollection.query(
|
|
12223
|
+
Q6.where("trust_tier", "quarantined"),
|
|
12224
|
+
Q6.where("is_deleted", false),
|
|
12225
|
+
Q6.where("scope", where.scope),
|
|
12226
|
+
Q6.where("folder_id", where.folderId),
|
|
12227
|
+
...ctx.userId !== void 0 ? [Q6.where("user_id", ctx.userId)] : []
|
|
12228
|
+
).unsafeFetchRaw();
|
|
12229
|
+
for (const raw of rows) {
|
|
12230
|
+
let ids;
|
|
12231
|
+
try {
|
|
12232
|
+
ids = JSON.parse(raw.source_chunk_ids ?? "[]");
|
|
12233
|
+
} catch {
|
|
12234
|
+
continue;
|
|
12235
|
+
}
|
|
12236
|
+
if (!Array.isArray(ids)) continue;
|
|
12237
|
+
const key = [...new Set(ids.filter((id) => typeof id === "string"))].sort().join("\0");
|
|
12238
|
+
if (key !== wanted) continue;
|
|
12239
|
+
const stored = await vaultMemoryRawToStored(
|
|
12240
|
+
raw,
|
|
12241
|
+
ctx.walletAddress,
|
|
12242
|
+
ctx.signMessage,
|
|
12243
|
+
ctx.embeddedWalletSigner
|
|
12244
|
+
);
|
|
12245
|
+
if (normalizeForDedupe(stored.content) === target) return stored.uniqueId;
|
|
12246
|
+
}
|
|
12247
|
+
return null;
|
|
12248
|
+
}
|
|
12246
12249
|
|
|
12247
12250
|
// src/lib/memory/decay.ts
|
|
12248
12251
|
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -12266,6 +12269,10 @@ var DEFAULT_DECAY_POLICY = {
|
|
|
12266
12269
|
hardDeleteWindowMs: HARD_DELETE_WINDOW_MS,
|
|
12267
12270
|
pastEventGraceMs: PAST_EVENT_GRACE_MS
|
|
12268
12271
|
};
|
|
12272
|
+
function lastActivityAt(m) {
|
|
12273
|
+
const observedAt = Number.isFinite(m.lastObservedAt) ? m.lastObservedAt : m.updatedAt;
|
|
12274
|
+
return Math.max(m.updatedAt, observedAt);
|
|
12275
|
+
}
|
|
12269
12276
|
function resolvePolicy(policy) {
|
|
12270
12277
|
if (!policy) return DEFAULT_DECAY_POLICY;
|
|
12271
12278
|
return {
|
|
@@ -12305,8 +12312,7 @@ function classifyDecay(m, now, policy) {
|
|
|
12305
12312
|
if (m.eventTimeEnd < now - resolved.pastEventGraceMs) return "archive";
|
|
12306
12313
|
if (m.eventTimeEnd >= now) return "keep";
|
|
12307
12314
|
}
|
|
12308
|
-
|
|
12309
|
-
if (now - Math.max(m.updatedAt, observedAt) > ttlForType(m.factType, resolved)) return "archive";
|
|
12315
|
+
if (now - lastActivityAt(m) > ttlForType(m.factType, resolved)) return "archive";
|
|
12310
12316
|
return "keep";
|
|
12311
12317
|
}
|
|
12312
12318
|
|
|
@@ -15307,6 +15313,24 @@ async function extractAndRetain(messages, retainCtx, options) {
|
|
|
15307
15313
|
const tRetain = Date.now();
|
|
15308
15314
|
for (const { candidate, isQuarantined, reason, signature } of toRetain) {
|
|
15309
15315
|
try {
|
|
15316
|
+
if (isQuarantined) {
|
|
15317
|
+
const existingId = await findQuarantinedDuplicateOp(
|
|
15318
|
+
retainCtx.vaultCtx,
|
|
15319
|
+
candidate.content,
|
|
15320
|
+
candidate.sourceMessageIds,
|
|
15321
|
+
// The same defaults retain() writes the row with.
|
|
15322
|
+
{ scope: options.scope ?? "private", folderId: options.folderId ?? null }
|
|
15323
|
+
).catch(() => null);
|
|
15324
|
+
if (existingId) {
|
|
15325
|
+
quarantinedInfo.push({
|
|
15326
|
+
candidate,
|
|
15327
|
+
memoryId: existingId,
|
|
15328
|
+
reason,
|
|
15329
|
+
signature
|
|
15330
|
+
});
|
|
15331
|
+
continue;
|
|
15332
|
+
}
|
|
15333
|
+
}
|
|
15310
15334
|
const result = await retain(candidate.content, retainCtx, {
|
|
15311
15335
|
source: "auto-extracted",
|
|
15312
15336
|
sourceChunkIds: candidate.sourceMessageIds,
|
|
@@ -15752,7 +15776,7 @@ function createLlmDecayClassifier(options) {
|
|
|
15752
15776
|
if (!content || content.trim().length === 0) return ruleVerdict;
|
|
15753
15777
|
const trimmed = content.trim().slice(0, MAX_CONTENT_CHARS);
|
|
15754
15778
|
const safe = redactor ? (await redactor.redactTextAsync(trimmed)).text : trimmed;
|
|
15755
|
-
const meta = `factType: ${input.factType ?? "none"}; ageDays: ${ageDays(input
|
|
15779
|
+
const meta = `factType: ${input.factType ?? "none"}; ageDays: ${ageDays(lastActivityAt(input), now)}`;
|
|
15756
15780
|
let parsed;
|
|
15757
15781
|
try {
|
|
15758
15782
|
parsed = await callPortalJsonCompletion({
|
|
@@ -15785,9 +15809,9 @@ Should this be kept or archived?`,
|
|
|
15785
15809
|
}
|
|
15786
15810
|
};
|
|
15787
15811
|
}
|
|
15788
|
-
function ageDays(
|
|
15789
|
-
if (!Number.isFinite(
|
|
15790
|
-
return Math.max(0, Math.floor((now -
|
|
15812
|
+
function ageDays(activityAt, now) {
|
|
15813
|
+
if (!Number.isFinite(activityAt) || !Number.isFinite(now)) return 0;
|
|
15814
|
+
return Math.max(0, Math.floor((now - activityAt) / (24 * 60 * 60 * 1e3)));
|
|
15791
15815
|
}
|
|
15792
15816
|
function parseVerdict(parsed) {
|
|
15793
15817
|
if (typeof parsed !== "object" || parsed === null) return null;
|
|
@@ -15840,7 +15864,7 @@ function createDecaySweeper(options) {
|
|
|
15840
15864
|
if (!classifier || !isBorderline(input)) return ruleVerdict;
|
|
15841
15865
|
if (input.id) {
|
|
15842
15866
|
const cached = classifierCache.get(input.id);
|
|
15843
|
-
if (cached && cached.
|
|
15867
|
+
if (cached && cached.activityAt === lastActivityAt(input)) return cached.verdict;
|
|
15844
15868
|
}
|
|
15845
15869
|
if (sweep.classifierCalls >= maxClassifierCalls) {
|
|
15846
15870
|
if (!sweep.ceilingLogged) {
|
|
@@ -15854,7 +15878,7 @@ function createDecaySweeper(options) {
|
|
|
15854
15878
|
sweep.classifierCalls++;
|
|
15855
15879
|
try {
|
|
15856
15880
|
const verdict = await classifier.classify(input, ruleVerdict, now);
|
|
15857
|
-
if (input.id) classifierCache.set(input.id, {
|
|
15881
|
+
if (input.id) classifierCache.set(input.id, { activityAt: lastActivityAt(input), verdict });
|
|
15858
15882
|
return verdict;
|
|
15859
15883
|
} catch (err) {
|
|
15860
15884
|
getLogger().warn(
|
|
@@ -18472,6 +18496,37 @@ async function assembleMemoryContext(options) {
|
|
|
18472
18496
|
import { Q as Q8 } from "@nozbe/watermelondb";
|
|
18473
18497
|
var MAX_JOBS_PER_PASS = 3;
|
|
18474
18498
|
var MAX_ATTEMPTS = 3;
|
|
18499
|
+
var MAX_FAILED_SESSIONS = 3;
|
|
18500
|
+
var MIN_FAILED_SESSION_GAP_MS = 60 * 60 * 1e3;
|
|
18501
|
+
var REQUEST_REJECTED_STATUSES = /* @__PURE__ */ new Set([400, 404, 413, 422]);
|
|
18502
|
+
var CONTENT_FAILURE_REASONS = /* @__PURE__ */ new Set([
|
|
18503
|
+
"empty-content",
|
|
18504
|
+
"invalid-json",
|
|
18505
|
+
"null-completion",
|
|
18506
|
+
"body-parse-failed"
|
|
18507
|
+
]);
|
|
18508
|
+
var BACKOFF_ALLOWANCE_MS = 2100;
|
|
18509
|
+
var RETAIN_BUDGET_MS = 12e4;
|
|
18510
|
+
function defaultBatchTimeoutMs(extract) {
|
|
18511
|
+
const attempts = Math.max(1, extract.maxAttempts ?? 3);
|
|
18512
|
+
const calls = (extract.timeoutMs ?? 6e4) * attempts + BACKOFF_ALLOWANCE_MS * (attempts - 1);
|
|
18513
|
+
const extraction = extract.totalTimeoutMs !== void 0 ? Math.min(extract.totalTimeoutMs, calls) : calls;
|
|
18514
|
+
return extraction + RETAIN_BUDGET_MS;
|
|
18515
|
+
}
|
|
18516
|
+
var BatchFailureError = class extends Error {
|
|
18517
|
+
constructor(message, terminal) {
|
|
18518
|
+
super(message);
|
|
18519
|
+
this.terminal = terminal;
|
|
18520
|
+
}
|
|
18521
|
+
};
|
|
18522
|
+
var AccountFailureError = class extends Error {
|
|
18523
|
+
};
|
|
18524
|
+
var LockedSourcesError = class extends Error {
|
|
18525
|
+
constructor(persistentIds) {
|
|
18526
|
+
super("Source messages are locked; retained for retry");
|
|
18527
|
+
this.persistentIds = persistentIds;
|
|
18528
|
+
}
|
|
18529
|
+
};
|
|
18475
18530
|
function idsOf(job) {
|
|
18476
18531
|
return JSON.parse(String(job._getRaw("message_ids")));
|
|
18477
18532
|
}
|
|
@@ -18479,6 +18534,16 @@ function seqOf(job) {
|
|
|
18479
18534
|
const raw = job._getRaw("watermark_seq");
|
|
18480
18535
|
return typeof raw === "number" && raw > 0 ? raw : void 0;
|
|
18481
18536
|
}
|
|
18537
|
+
function failedSessionsOf(job, head) {
|
|
18538
|
+
if (job._getRaw("failed_head") !== head) return 0;
|
|
18539
|
+
const raw = job._getRaw("failed_sessions");
|
|
18540
|
+
return typeof raw === "number" && raw > 0 ? raw : 0;
|
|
18541
|
+
}
|
|
18542
|
+
function failedAtOf(job, head) {
|
|
18543
|
+
if (job._getRaw("failed_head") !== head) return void 0;
|
|
18544
|
+
const raw = job._getRaw("failed_at");
|
|
18545
|
+
return typeof raw === "number" ? raw : void 0;
|
|
18546
|
+
}
|
|
18482
18547
|
function createDurableAutoExtractor(options) {
|
|
18483
18548
|
const vault = options.retainCtx.vaultCtx;
|
|
18484
18549
|
const database = vault.database;
|
|
@@ -18501,6 +18566,9 @@ function createDurableAutoExtractor(options) {
|
|
|
18501
18566
|
let writes = Promise.resolve();
|
|
18502
18567
|
let storeFailures = 0;
|
|
18503
18568
|
const attempts = /* @__PURE__ */ new Map();
|
|
18569
|
+
const sessionFailures = /* @__PURE__ */ new Map();
|
|
18570
|
+
const skippedThisSession = /* @__PURE__ */ new Set();
|
|
18571
|
+
const clock = options.now ?? Date.now;
|
|
18504
18572
|
const reportError = (error, conversationId) => {
|
|
18505
18573
|
try {
|
|
18506
18574
|
options.onError?.(error instanceof Error ? error : new Error(String(error)), conversationId);
|
|
@@ -18529,6 +18597,7 @@ function createDurableAutoExtractor(options) {
|
|
|
18529
18597
|
}
|
|
18530
18598
|
async function extractBatch(messages, conversationId, ids, jobScope, jobFolderId) {
|
|
18531
18599
|
let watchdog;
|
|
18600
|
+
let cancelled = false;
|
|
18532
18601
|
const model = options.modelForScope?.(jobScope) ?? options.extract.model;
|
|
18533
18602
|
try {
|
|
18534
18603
|
return await Promise.race([
|
|
@@ -18539,7 +18608,7 @@ function createDurableAutoExtractor(options) {
|
|
|
18539
18608
|
...options.retainCtx,
|
|
18540
18609
|
vaultCtx: {
|
|
18541
18610
|
...vault,
|
|
18542
|
-
canWrite: async () => !disposed && await storage.conversationsCollection.query(Q8.where("conversation_id", conversationId), Q8.where("is_deleted", false)).fetchCount() > 0 && await storage.messagesCollection.query(Q8.where("conversation_id", conversationId), Q8.where("id", Q8.oneOf(ids))).fetchCount() === ids.length && (!vault.canWrite || await vault.canWrite()) && !disposed
|
|
18611
|
+
canWrite: async () => !disposed && !cancelled && await storage.conversationsCollection.query(Q8.where("conversation_id", conversationId), Q8.where("is_deleted", false)).fetchCount() > 0 && await storage.messagesCollection.query(Q8.where("conversation_id", conversationId), Q8.where("id", Q8.oneOf(ids))).fetchCount() === ids.length && (!vault.canWrite || await vault.canWrite()) && !disposed && !cancelled
|
|
18543
18612
|
}
|
|
18544
18613
|
},
|
|
18545
18614
|
// The model follows the job's scope, not the mode alive at drain time.
|
|
@@ -18559,14 +18628,116 @@ function createDurableAutoExtractor(options) {
|
|
|
18559
18628
|
new Promise((_resolve, reject) => {
|
|
18560
18629
|
watchdog = setTimeout(
|
|
18561
18630
|
() => reject(new Error("Extraction did not settle in time; retained for retry")),
|
|
18562
|
-
options.batchTimeoutMs ??
|
|
18631
|
+
options.batchTimeoutMs ?? defaultBatchTimeoutMs(options.extract)
|
|
18563
18632
|
);
|
|
18564
18633
|
})
|
|
18565
18634
|
]);
|
|
18566
18635
|
} finally {
|
|
18636
|
+
cancelled = true;
|
|
18567
18637
|
if (watchdog !== void 0) clearTimeout(watchdog);
|
|
18568
18638
|
}
|
|
18569
18639
|
}
|
|
18640
|
+
async function acknowledge(job, ids, loaded, abandoned) {
|
|
18641
|
+
attempts.delete(job.id);
|
|
18642
|
+
sessionFailures.delete(job.id);
|
|
18643
|
+
skippedThisSession.delete(job.id);
|
|
18644
|
+
const sequences = /* @__PURE__ */ new Map();
|
|
18645
|
+
for (const message of loaded)
|
|
18646
|
+
if (message && message.messageId > 0) sequences.set(message.uniqueId, message.messageId);
|
|
18647
|
+
await database.write(async () => {
|
|
18648
|
+
const current = idsOf(job);
|
|
18649
|
+
const acknowledged = ids.filter((id) => current.includes(id));
|
|
18650
|
+
if (!acknowledged.length) return;
|
|
18651
|
+
const done = new Set(acknowledged);
|
|
18652
|
+
const remaining = current.filter((id) => !done.has(id));
|
|
18653
|
+
await job.update((r) => {
|
|
18654
|
+
r._setRaw(
|
|
18655
|
+
"message_ids",
|
|
18656
|
+
JSON.stringify(
|
|
18657
|
+
remaining.length ? [...abandoned ? [] : acknowledged.slice(-2), ...remaining] : []
|
|
18658
|
+
)
|
|
18659
|
+
);
|
|
18660
|
+
const newest = acknowledged[acknowledged.length - 1];
|
|
18661
|
+
r._setRaw("watermark", newest);
|
|
18662
|
+
const sequence = sequences.get(newest);
|
|
18663
|
+
if (sequence !== void 0) {
|
|
18664
|
+
const prior = seqOf(job);
|
|
18665
|
+
r._setRaw("watermark_seq", prior === void 0 ? sequence : Math.max(prior, sequence));
|
|
18666
|
+
}
|
|
18667
|
+
r._setRaw("failed_sessions", abandoned ? MAX_FAILED_SESSIONS : null);
|
|
18668
|
+
r._setRaw("failed_head", abandoned ? newest : null);
|
|
18669
|
+
r._setRaw("failed_at", null);
|
|
18670
|
+
});
|
|
18671
|
+
});
|
|
18672
|
+
}
|
|
18673
|
+
async function recordFailure(job, ids, loaded, error, conversationId) {
|
|
18674
|
+
const head = ids[0];
|
|
18675
|
+
if (!head || !loaded.some(Boolean)) return false;
|
|
18676
|
+
if (error instanceof AccountFailureError) {
|
|
18677
|
+
skippedThisSession.add(job.id);
|
|
18678
|
+
return false;
|
|
18679
|
+
}
|
|
18680
|
+
const counted = error instanceof BatchFailureError || error instanceof LockedSourcesError && error.persistentIds.length > 0;
|
|
18681
|
+
if (!counted) return false;
|
|
18682
|
+
const prior = sessionFailures.get(job.id);
|
|
18683
|
+
const count = prior?.head === head ? prior.count + 1 : 1;
|
|
18684
|
+
sessionFailures.set(job.id, { head, count });
|
|
18685
|
+
const terminal = error instanceof BatchFailureError && error.terminal;
|
|
18686
|
+
if (!terminal && count < MAX_ATTEMPTS) return false;
|
|
18687
|
+
skippedThisSession.add(job.id);
|
|
18688
|
+
const now = clock();
|
|
18689
|
+
const lastCounted = failedAtOf(job, head);
|
|
18690
|
+
if (lastCounted !== void 0 && now - lastCounted < MIN_FAILED_SESSION_GAP_MS) return false;
|
|
18691
|
+
const failures = failedSessionsOf(job, head) + 1;
|
|
18692
|
+
if (failures < MAX_FAILED_SESSIONS) {
|
|
18693
|
+
await database.write(
|
|
18694
|
+
() => job.update((r) => {
|
|
18695
|
+
r._setRaw("failed_sessions", failures);
|
|
18696
|
+
r._setRaw("failed_head", head);
|
|
18697
|
+
r._setRaw("failed_at", now);
|
|
18698
|
+
})
|
|
18699
|
+
);
|
|
18700
|
+
return false;
|
|
18701
|
+
}
|
|
18702
|
+
if (error instanceof LockedSourcesError) {
|
|
18703
|
+
const dropped = new Set(error.persistentIds);
|
|
18704
|
+
let newest;
|
|
18705
|
+
for (const message of loaded)
|
|
18706
|
+
if (message && dropped.has(message.uniqueId) && message.messageId > (newest?.seq ?? 0))
|
|
18707
|
+
newest = { id: message.uniqueId, seq: message.messageId };
|
|
18708
|
+
const advance = newest !== void 0 && newest.seq > (seqOf(job) ?? 0) ? newest : void 0;
|
|
18709
|
+
await database.write(
|
|
18710
|
+
() => job.update((r) => {
|
|
18711
|
+
r._setRaw("message_ids", JSON.stringify(idsOf(job).filter((id) => !dropped.has(id))));
|
|
18712
|
+
if (advance) {
|
|
18713
|
+
r._setRaw("watermark", advance.id);
|
|
18714
|
+
r._setRaw("watermark_seq", advance.seq);
|
|
18715
|
+
}
|
|
18716
|
+
r._setRaw("failed_sessions", advance ? MAX_FAILED_SESSIONS : null);
|
|
18717
|
+
r._setRaw("failed_head", advance ? advance.id : null);
|
|
18718
|
+
r._setRaw("failed_at", null);
|
|
18719
|
+
})
|
|
18720
|
+
);
|
|
18721
|
+
attempts.delete(job.id);
|
|
18722
|
+
sessionFailures.delete(job.id);
|
|
18723
|
+
skippedThisSession.delete(job.id);
|
|
18724
|
+
reportError(
|
|
18725
|
+
new Error(
|
|
18726
|
+
`Dropped ${dropped.size} source id(s) that stayed locked for ${failures} sessions to unblock extraction`
|
|
18727
|
+
),
|
|
18728
|
+
conversationId
|
|
18729
|
+
);
|
|
18730
|
+
return true;
|
|
18731
|
+
}
|
|
18732
|
+
await acknowledge(job, ids, loaded, true);
|
|
18733
|
+
reportError(
|
|
18734
|
+
new Error(
|
|
18735
|
+
`Abandoned an extraction batch of ${ids.length} source id(s) after it failed in ${failures} sessions`
|
|
18736
|
+
),
|
|
18737
|
+
conversationId
|
|
18738
|
+
);
|
|
18739
|
+
return true;
|
|
18740
|
+
}
|
|
18570
18741
|
async function drain() {
|
|
18571
18742
|
if (disposed || running) return;
|
|
18572
18743
|
running = true;
|
|
@@ -18581,21 +18752,23 @@ function createDurableAutoExtractor(options) {
|
|
|
18581
18752
|
for (const job of pending) {
|
|
18582
18753
|
if (disposed) break;
|
|
18583
18754
|
const attempted = attempts.get(job.id) ?? 0;
|
|
18584
|
-
if (attempted >= MAX_ATTEMPTS) continue;
|
|
18755
|
+
if (attempted >= MAX_ATTEMPTS || skippedThisSession.has(job.id)) continue;
|
|
18585
18756
|
if (processed >= MAX_JOBS_PER_PASS) {
|
|
18586
18757
|
scheduleAfterSuccess = true;
|
|
18587
18758
|
break;
|
|
18588
18759
|
}
|
|
18589
18760
|
processed++;
|
|
18590
18761
|
const conversationId = String(job._getRaw("conversation_id"));
|
|
18762
|
+
let ids = [];
|
|
18763
|
+
let loaded = [];
|
|
18591
18764
|
try {
|
|
18592
18765
|
const conversations = await storage.conversationsCollection.query(Q8.where("conversation_id", conversationId), Q8.where("is_deleted", false)).fetchCount();
|
|
18593
18766
|
if (!conversations) {
|
|
18594
18767
|
await database.write(() => job.destroyPermanently());
|
|
18595
18768
|
continue;
|
|
18596
18769
|
}
|
|
18597
|
-
|
|
18598
|
-
|
|
18770
|
+
ids = idsOf(job).slice(0, 20);
|
|
18771
|
+
loaded = await Promise.all(ids.map((id) => getMessageOp(storage, id)));
|
|
18599
18772
|
const missing = ids.filter((_id, index) => loaded[index] === null);
|
|
18600
18773
|
if (missing.length) {
|
|
18601
18774
|
if (attempted < MAX_ATTEMPTS - 1)
|
|
@@ -18619,17 +18792,21 @@ function createDurableAutoExtractor(options) {
|
|
|
18619
18792
|
);
|
|
18620
18793
|
continue;
|
|
18621
18794
|
}
|
|
18622
|
-
|
|
18623
|
-
|
|
18624
|
-
|
|
18625
|
-
|
|
18795
|
+
const locked = loaded.filter((message) => message && isEncrypted(message.content));
|
|
18796
|
+
if (locked.length) {
|
|
18797
|
+
throw new LockedSourcesError(
|
|
18798
|
+
locked.flatMap(
|
|
18799
|
+
(message) => message.decryptionStatus === "auth_mismatch" || message.decryptionStatus === "invalid_payload" ? [message.uniqueId] : []
|
|
18800
|
+
)
|
|
18801
|
+
);
|
|
18626
18802
|
}
|
|
18627
18803
|
const messages = loaded.flatMap(
|
|
18628
18804
|
(m) => m && m.conversationId === conversationId && (m.role === "user" || m.role === "assistant") ? [{ id: m.uniqueId, role: m.role, content: m.content }] : []
|
|
18629
18805
|
);
|
|
18630
18806
|
let success = messages.length === 0;
|
|
18807
|
+
let result;
|
|
18631
18808
|
if (messages.length) {
|
|
18632
|
-
|
|
18809
|
+
result = await extractBatch(
|
|
18633
18810
|
messages,
|
|
18634
18811
|
conversationId,
|
|
18635
18812
|
ids,
|
|
@@ -18639,41 +18816,35 @@ function createDurableAutoExtractor(options) {
|
|
|
18639
18816
|
success = result.failedCount === 0 && result.outcome !== "empty-after-retry";
|
|
18640
18817
|
options.onTurnComplete?.(result);
|
|
18641
18818
|
}
|
|
18642
|
-
if (!success)
|
|
18643
|
-
|
|
18644
|
-
|
|
18645
|
-
|
|
18646
|
-
|
|
18647
|
-
|
|
18648
|
-
|
|
18649
|
-
|
|
18650
|
-
const acknowledged = ids.filter((id) => current.includes(id));
|
|
18651
|
-
if (!acknowledged.length) return;
|
|
18652
|
-
const done = new Set(acknowledged);
|
|
18653
|
-
const remaining = current.filter((id) => !done.has(id));
|
|
18654
|
-
if (remaining.length) {
|
|
18655
|
-
await job.update(
|
|
18656
|
-
(r) => r._setRaw("message_ids", JSON.stringify([...acknowledged.slice(-2), ...remaining]))
|
|
18657
|
-
);
|
|
18658
|
-
} else await job.update((r) => r._setRaw("message_ids", "[]"));
|
|
18659
|
-
const newest = acknowledged[acknowledged.length - 1];
|
|
18660
|
-
await job.update((r) => {
|
|
18661
|
-
r._setRaw("watermark", newest);
|
|
18662
|
-
const sequence = sequences.get(newest);
|
|
18663
|
-
if (sequence !== void 0) {
|
|
18664
|
-
const prior = seqOf(job);
|
|
18665
|
-
r._setRaw(
|
|
18666
|
-
"watermark_seq",
|
|
18667
|
-
prior === void 0 ? sequence : Math.max(prior, sequence)
|
|
18819
|
+
if (!success) {
|
|
18820
|
+
const failure = result?.failure;
|
|
18821
|
+
if (failure?.reason === "http-terminal") {
|
|
18822
|
+
const status = failure.httpStatus;
|
|
18823
|
+
if (status !== void 0 && REQUEST_REJECTED_STATUSES.has(status))
|
|
18824
|
+
throw new BatchFailureError(
|
|
18825
|
+
`Extraction rejected the batch (HTTP ${status}); not retried this session`,
|
|
18826
|
+
true
|
|
18668
18827
|
);
|
|
18669
|
-
|
|
18670
|
-
|
|
18671
|
-
|
|
18828
|
+
throw new AccountFailureError(
|
|
18829
|
+
`Extraction refused for the account (HTTP ${status ?? "unknown"}); retried next session`
|
|
18830
|
+
);
|
|
18831
|
+
}
|
|
18832
|
+
if ((result?.failedCount ?? 0) > 0 || failure !== void 0 && CONTENT_FAILURE_REASONS.has(failure.reason))
|
|
18833
|
+
throw new BatchFailureError("Extraction batch incomplete; retained for retry", false);
|
|
18834
|
+
throw new Error("Extraction batch incomplete; retained for retry");
|
|
18835
|
+
}
|
|
18836
|
+
await acknowledge(job, ids, loaded, false);
|
|
18672
18837
|
scheduleAfterSuccess = true;
|
|
18673
18838
|
} catch (error) {
|
|
18674
18839
|
attempts.set(job.id, attempted + 1);
|
|
18675
18840
|
retry = true;
|
|
18676
18841
|
reportError(error, conversationId);
|
|
18842
|
+
try {
|
|
18843
|
+
if (await recordFailure(job, ids, loaded, error, conversationId))
|
|
18844
|
+
scheduleAfterSuccess = true;
|
|
18845
|
+
} catch (recordError) {
|
|
18846
|
+
reportError(recordError, conversationId);
|
|
18847
|
+
}
|
|
18677
18848
|
}
|
|
18678
18849
|
}
|
|
18679
18850
|
} catch (error) {
|
|
@@ -18743,7 +18914,7 @@ function createDurableAutoExtractor(options) {
|
|
|
18743
18914
|
const start = boundary >= 0 ? boundary + 1 : Math.max(0, ids.length - Math.max(1, options.windowSize ?? 6));
|
|
18744
18915
|
if (start >= ids.length) return;
|
|
18745
18916
|
const ownWatermark = job?._getRaw("watermark");
|
|
18746
|
-
const atOwnWatermark = !!job && boundary >= 0 && (ids[boundary] === ownWatermark || sequences.get(ids[boundary]) !== void 0 && sequences.get(ids[boundary]) === seqOf(job));
|
|
18917
|
+
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));
|
|
18747
18918
|
const overlapStart = atOwnWatermark ? Math.max(0, boundary - 1) : start;
|
|
18748
18919
|
const fresh = ids.slice(overlapStart).filter((id) => !foreignSources.has(id));
|
|
18749
18920
|
if (!fresh.length) return;
|
|
@@ -19190,6 +19361,9 @@ var MANUAL_FACT_TYPES = [
|
|
|
19190
19361
|
function normalizeManualFactType(value) {
|
|
19191
19362
|
return typeof value === "string" && MANUAL_FACT_TYPES.includes(value) ? value : void 0;
|
|
19192
19363
|
}
|
|
19364
|
+
function isSharedScope(scope) {
|
|
19365
|
+
return scope === "shared";
|
|
19366
|
+
}
|
|
19193
19367
|
async function notifyWritten(options, input, outcome) {
|
|
19194
19368
|
try {
|
|
19195
19369
|
await options?.onWritten?.({ input, outcome });
|
|
@@ -19258,14 +19432,17 @@ function createMemoryVaultTool(vaultCtx, options, embeddingOptions, cache) {
|
|
|
19258
19432
|
try {
|
|
19259
19433
|
const isUpdate = !!id;
|
|
19260
19434
|
let previousContent;
|
|
19435
|
+
const scope = options?.scope ?? "private";
|
|
19261
19436
|
if (isUpdate) {
|
|
19262
19437
|
const existing = await getVaultMemoryOp(vaultCtx, id);
|
|
19263
19438
|
if (!existing) {
|
|
19264
19439
|
return `Error: Memory with ID "${id}" not found. Creating a new memory instead would require a separate call without an ID.`;
|
|
19265
19440
|
}
|
|
19441
|
+
if (isSharedScope(existing.scope) !== isSharedScope(scope)) {
|
|
19442
|
+
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.`;
|
|
19443
|
+
}
|
|
19266
19444
|
previousContent = existing.content;
|
|
19267
19445
|
}
|
|
19268
|
-
const scope = options?.scope ?? "private";
|
|
19269
19446
|
const operation = {
|
|
19270
19447
|
action: isUpdate ? "update" : "add",
|
|
19271
19448
|
content,
|
|
@@ -23504,7 +23681,7 @@ __decorateClass([
|
|
|
23504
23681
|
], VaultFolder.prototype, "context", 2);
|
|
23505
23682
|
|
|
23506
23683
|
// src/lib/db/schema.ts
|
|
23507
|
-
var SDK_SCHEMA_VERSION =
|
|
23684
|
+
var SDK_SCHEMA_VERSION = 47;
|
|
23508
23685
|
var sdkSchema = appSchema2({
|
|
23509
23686
|
version: SDK_SCHEMA_VERSION,
|
|
23510
23687
|
tables: [
|
|
@@ -23520,7 +23697,11 @@ var sdkSchema = appSchema2({
|
|
|
23520
23697
|
// source. `watermark` alone is an id, and deleting that message erased
|
|
23521
23698
|
// the anchor — see the column note in extractionJobs/models.ts.
|
|
23522
23699
|
{ name: "watermark_seq", type: "number", isOptional: true },
|
|
23523
|
-
{ name: "folder_id", type: "string", isOptional: true }
|
|
23700
|
+
{ name: "folder_id", type: "string", isOptional: true },
|
|
23701
|
+
// Poison accounting for the head batch; see extractionJobs/models.ts.
|
|
23702
|
+
{ name: "failed_sessions", type: "number", isOptional: true },
|
|
23703
|
+
{ name: "failed_head", type: "string", isOptional: true },
|
|
23704
|
+
{ name: "failed_at", type: "number", isOptional: true }
|
|
23524
23705
|
]
|
|
23525
23706
|
}),
|
|
23526
23707
|
// Chat storage tables
|
|
@@ -24544,6 +24725,22 @@ var sdkMigrations = schemaMigrations2({
|
|
|
24544
24725
|
]
|
|
24545
24726
|
})
|
|
24546
24727
|
]
|
|
24728
|
+
},
|
|
24729
|
+
// v46 -> v47: persisted poison count (and when it was last counted) for a
|
|
24730
|
+
// durable-extraction job's head batch. NULL = never failed, which is right
|
|
24731
|
+
// for every existing row.
|
|
24732
|
+
{
|
|
24733
|
+
toVersion: 47,
|
|
24734
|
+
steps: [
|
|
24735
|
+
addColumns2({
|
|
24736
|
+
table: "memory_extraction_jobs",
|
|
24737
|
+
columns: [
|
|
24738
|
+
{ name: "failed_sessions", type: "number", isOptional: true },
|
|
24739
|
+
{ name: "failed_head", type: "string", isOptional: true },
|
|
24740
|
+
{ name: "failed_at", type: "number", isOptional: true }
|
|
24741
|
+
]
|
|
24742
|
+
})
|
|
24743
|
+
]
|
|
24547
24744
|
}
|
|
24548
24745
|
]
|
|
24549
24746
|
});
|