@rei-standard/amsg-server 3.0.0 → 3.1.0-next.1
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/README.md +110 -11
- package/dist/adapters/d1.d.ts +8 -3
- package/dist/adapters/interface.d.ts +6 -3
- package/dist/{chunk-YBDQJVFQ.cjs → chunk-D4SKSCIY.cjs} +461 -140
- package/dist/{chunk-RL7V7VSS.mjs → chunk-EIM3PKFY.mjs} +413 -92
- package/dist/cloudflare.cjs +4 -2
- package/dist/cloudflare.d.ts +1 -1
- package/dist/cloudflare.mjs +3 -1
- package/dist/index.cjs +27 -25
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +3 -1
- package/dist/lib/agentic-fire.d.ts +1 -0
- package/dist/lib/attachments.d.ts +39 -0
- package/dist/lib/client-state-store.d.ts +6 -4
- package/dist/lib/state-accessors.d.ts +12 -8
- package/dist/lib/task-projection.d.ts +2 -2
- package/package.json +1 -1
|
@@ -147,14 +147,33 @@ var _amsgshared = require('@rei-standard/amsg-shared');
|
|
|
147
147
|
// src/server/lib/encryption.js
|
|
148
148
|
var base64ToBytes = _amsgshared.base64UrlToBytes;
|
|
149
149
|
var TAG_LENGTH_BYTES = 16;
|
|
150
|
+
var AES_KEY_CACHE_MAX = 128;
|
|
151
|
+
var aesKeyCache = /* @__PURE__ */ new Map();
|
|
150
152
|
function importAesKey(hexKey, usage) {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
153
|
+
const cacheKey = `${usage}:${hexKey}`;
|
|
154
|
+
const cached = aesKeyCache.get(cacheKey);
|
|
155
|
+
if (cached) return cached;
|
|
156
|
+
let promise;
|
|
157
|
+
try {
|
|
158
|
+
promise = globalThis.crypto.subtle.importKey(
|
|
159
|
+
"raw",
|
|
160
|
+
_amsgshared.hexToBytes.call(void 0, hexKey),
|
|
161
|
+
{ name: "AES-GCM" },
|
|
162
|
+
false,
|
|
163
|
+
[usage]
|
|
164
|
+
).catch((err6) => {
|
|
165
|
+
aesKeyCache.delete(cacheKey);
|
|
166
|
+
throw err6;
|
|
167
|
+
});
|
|
168
|
+
} catch (err6) {
|
|
169
|
+
return Promise.reject(err6);
|
|
170
|
+
}
|
|
171
|
+
if (aesKeyCache.size >= AES_KEY_CACHE_MAX) {
|
|
172
|
+
const oldest = aesKeyCache.keys().next().value;
|
|
173
|
+
if (oldest !== void 0) aesKeyCache.delete(oldest);
|
|
174
|
+
}
|
|
175
|
+
aesKeyCache.set(cacheKey, promise);
|
|
176
|
+
return promise;
|
|
158
177
|
}
|
|
159
178
|
async function aesGcmSeal(hexKey, iv, plaintextBytes) {
|
|
160
179
|
const key = await importAesKey(hexKey, "encrypt");
|
|
@@ -428,6 +447,9 @@ function taskPayloadByteLength(serializedPayload) {
|
|
|
428
447
|
return taskPayloadEncoder.encode(serializedPayload).length;
|
|
429
448
|
}
|
|
430
449
|
function validateScheduleMessagePayload(payload) {
|
|
450
|
+
if (payload.taskInput != null && (typeof payload.taskInput !== "object" || Array.isArray(payload.taskInput))) {
|
|
451
|
+
return { valid: false, errorCode: "INVALID_TASK_INPUT", errorMessage: "taskInput must be an object or null" };
|
|
452
|
+
}
|
|
431
453
|
if (!payload.contactName || typeof payload.contactName !== "string") {
|
|
432
454
|
return { valid: false, errorCode: "INVALID_PARAMETERS", errorMessage: "\u7F3A\u5C11\u5FC5\u9700\u53C2\u6570\u6216\u53C2\u6570\u683C\u5F0F\u9519\u8BEF", details: { missingFields: ["contactName"] } };
|
|
433
455
|
}
|
|
@@ -982,6 +1004,40 @@ function createWebCryptoWebPush(vapid = {}, { ttl = SCHEDULED_DEFAULT_TTL } = {}
|
|
|
982
1004
|
};
|
|
983
1005
|
}
|
|
984
1006
|
|
|
1007
|
+
// src/server/lib/attachments.js
|
|
1008
|
+
var ATTACHMENT_NAMESPACE = "amsg:attachments";
|
|
1009
|
+
var ATTACHMENT_RETENTION_DAYS = 30;
|
|
1010
|
+
var MAX_ATTACHMENT_TTL_MS = ATTACHMENT_RETENTION_DAYS * 24 * 60 * 60 * 1e3;
|
|
1011
|
+
function createAttachmentStore({ task, occurrenceMs, writeState, now }) {
|
|
1012
|
+
return async function storeAttachment(value, { ttlMs = MAX_ATTACHMENT_TTL_MS, owner, kind = "attachment", ownerGeneration } = {}) {
|
|
1013
|
+
if (typeof value !== "string") throw new TypeError("storeAttachment value must be a serialized string");
|
|
1014
|
+
if (!Number.isSafeInteger(ttlMs) || ttlMs <= 0 || ttlMs > MAX_ATTACHMENT_TTL_MS) {
|
|
1015
|
+
throw new RangeError("storeAttachment ttlMs must be between 1 millisecond and 30 days");
|
|
1016
|
+
}
|
|
1017
|
+
if (typeof task.uuid !== "string" || !task.uuid || !Number.isFinite(occurrenceMs)) {
|
|
1018
|
+
throw new TypeError("storeAttachment requires a task UUID and a valid occurrence");
|
|
1019
|
+
}
|
|
1020
|
+
const at = now();
|
|
1021
|
+
const ref = { version: (
|
|
1022
|
+
/** @type {1} */
|
|
1023
|
+
1
|
|
1024
|
+
), namespace: ATTACHMENT_NAMESPACE, key: _amsgshared.randomUUID.call(void 0, ), taskUuid: task.uuid, occurrenceMs, expiresAt: at + ttlMs };
|
|
1025
|
+
let result;
|
|
1026
|
+
try {
|
|
1027
|
+
result = await writeState(ATTACHMENT_NAMESPACE, [{ key: ref.key, value: JSON.stringify({ ...ref, owner, kind, ownerGeneration, value }), updatedAt: at, owner, kind, ownerGeneration }]);
|
|
1028
|
+
} catch (error) {
|
|
1029
|
+
if (_optionalChain([error, 'optionalAccess', _5 => _5.code]) === "AGENTIC_STATE_WRITE_UNSUPPORTED") {
|
|
1030
|
+
throw new DeploymentConfigError("ATTACHMENT_STORAGE_UNSUPPORTED: attachments require a client_state adapter (built-in support: D1)", { code: "ATTACHMENT_STORAGE_UNSUPPORTED" });
|
|
1031
|
+
}
|
|
1032
|
+
throw error;
|
|
1033
|
+
}
|
|
1034
|
+
if (result.upserted !== 1 || result.skipped !== 0) {
|
|
1035
|
+
throw new Error("ATTACHMENT_WRITE_INCOMPLETE: attachment was not stored");
|
|
1036
|
+
}
|
|
1037
|
+
return ref;
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
|
|
985
1041
|
// src/server/lib/agentic-fire.js
|
|
986
1042
|
|
|
987
1043
|
|
|
@@ -1192,6 +1248,7 @@ async function writeClientStateEntries({ db, userId, userKey, entries, now }) {
|
|
|
1192
1248
|
let upserted = 0;
|
|
1193
1249
|
let skipped = 0;
|
|
1194
1250
|
let deleted = 0;
|
|
1251
|
+
let unknown = 0;
|
|
1195
1252
|
const skippedEntries = [];
|
|
1196
1253
|
if (Array.isArray(result.outcomes) && result.outcomes.length === physicalRows.length) {
|
|
1197
1254
|
for (let i = 0; i < rootRowIndexes.length; i++) {
|
|
@@ -1203,9 +1260,10 @@ async function writeClientStateEntries({ db, userId, userKey, entries, now }) {
|
|
|
1203
1260
|
skippedEntries.push({ namespace: entry.namespace, key: entry.key });
|
|
1204
1261
|
}
|
|
1205
1262
|
}
|
|
1263
|
+
} else if (result.upserted === physicalRows.length && result.skipped === 0) {
|
|
1264
|
+
upserted = rootRowIndexes.length;
|
|
1206
1265
|
} else {
|
|
1207
|
-
|
|
1208
|
-
skipped = result.skipped;
|
|
1266
|
+
unknown = rootRowIndexes.length;
|
|
1209
1267
|
}
|
|
1210
1268
|
const cleanupOutcomes = Array.isArray(result.cleanupOutcomes) && result.cleanupOutcomes.length === cleanups.length ? result.cleanupOutcomes : null;
|
|
1211
1269
|
for (const { cleanupIndex, entry } of deletions) {
|
|
@@ -1216,22 +1274,26 @@ async function writeClientStateEntries({ db, userId, userKey, entries, now }) {
|
|
|
1216
1274
|
deleted++;
|
|
1217
1275
|
}
|
|
1218
1276
|
}
|
|
1219
|
-
return { upserted, skipped, deleted, skippedEntries };
|
|
1277
|
+
return { upserted, skipped, deleted, skippedEntries, ...unknown > 0 ? { unknown } : {} };
|
|
1220
1278
|
}
|
|
1221
1279
|
|
|
1222
1280
|
// src/server/lib/state-accessors.js
|
|
1223
1281
|
function createStateAccessors({ db, userId, userKey, maxStateValueBytes, now }) {
|
|
1224
1282
|
const nowFn = typeof now === "function" ? now : Date.now;
|
|
1225
1283
|
const valueCeiling = Number.isInteger(maxStateValueBytes) && maxStateValueBytes > 0 ? maxStateValueBytes : DEFAULT_MAX_STATE_VALUE_BYTES;
|
|
1226
|
-
const readState = async (namespace) => {
|
|
1284
|
+
const readState = async (namespace, { key } = {}) => {
|
|
1227
1285
|
if (typeof namespace !== "string" || !namespace.trim()) {
|
|
1228
1286
|
throw new TypeError("readState(namespace) requires a non-empty string");
|
|
1229
1287
|
}
|
|
1288
|
+
if (key !== void 0 && (typeof key !== "string" || !key.trim() || key.length > MAX_KEY_CHARS || INTERNAL_STATE_CHAR_RE.test(key))) {
|
|
1289
|
+
throw new TypeError(`readState key must be a non-empty string of at most ${MAX_KEY_CHARS} characters without control characters`);
|
|
1290
|
+
}
|
|
1230
1291
|
if (!db || typeof db.getClientState !== "function") return [];
|
|
1231
|
-
const rows = await db.getClientState(userId, namespace);
|
|
1292
|
+
const rows = await db.getClientState(userId, namespace, key === void 0 ? void 0 : { key });
|
|
1232
1293
|
return resolveClientStateEntries(
|
|
1233
|
-
|
|
1234
|
-
() =>
|
|
1294
|
+
// 老适配器可能忽略第三个参数,仍在解密前过滤根行,保持返回契约。
|
|
1295
|
+
key === void 0 ? rows : rows.filter((row) => row.key === key),
|
|
1296
|
+
() => db.getClientState(userId, chunkNamespaceFor(namespace), key === void 0 ? void 0 : { keyPrefix: chunkKeyPrefixFor(key) }),
|
|
1235
1297
|
(value) => decryptFromStorage(value, userKey)
|
|
1236
1298
|
);
|
|
1237
1299
|
};
|
|
@@ -1287,6 +1349,9 @@ function createStateAccessors({ db, userId, userKey, maxStateValueBytes, now })
|
|
|
1287
1349
|
namespace,
|
|
1288
1350
|
key: entry.key,
|
|
1289
1351
|
value: entry.value,
|
|
1352
|
+
owner: entry.owner,
|
|
1353
|
+
kind: entry.kind,
|
|
1354
|
+
ownerGeneration: entry.ownerGeneration,
|
|
1290
1355
|
updatedAt: _nullishCoalesce(entry.updatedAt, () => ( at)),
|
|
1291
1356
|
...entry.version !== void 0 ? { version: entry.version } : {}
|
|
1292
1357
|
};
|
|
@@ -1309,7 +1374,7 @@ async function appendPushesToOutbox({ db, userId, userKey, pushes }) {
|
|
|
1309
1374
|
await db.appendOutboxMessages(userId, await toOutboxRows(pushes, userKey, Date.now()));
|
|
1310
1375
|
return true;
|
|
1311
1376
|
} catch (error) {
|
|
1312
|
-
if (_optionalChain([error, 'optionalAccess',
|
|
1377
|
+
if (_optionalChain([error, 'optionalAccess', _6 => _6.code]) === "CLOUD_OWNER_RETIRED") throw error;
|
|
1313
1378
|
console.warn("[amsg-server] outbox \u843D\u884C\u5931\u8D25\uFF08\u4E0D\u5F71\u54CD\u6295\u9012\uFF09:", error && error.message);
|
|
1314
1379
|
return false;
|
|
1315
1380
|
}
|
|
@@ -1583,7 +1648,7 @@ async function sendResultPush({ db, task, userKey, decryptedPayload, webpush, pu
|
|
|
1583
1648
|
await sendTaggedPush(webpush, subscription, JSON.stringify(push));
|
|
1584
1649
|
return true;
|
|
1585
1650
|
} catch (error) {
|
|
1586
|
-
if (isTaskCancelledError(error) || _optionalChain([error, 'optionalAccess',
|
|
1651
|
+
if (isTaskCancelledError(error) || _optionalChain([error, 'optionalAccess', _7 => _7.code]) === "CLOUD_OWNER_RETIRED") throw error;
|
|
1587
1652
|
console.warn(
|
|
1588
1653
|
`[amsg-server] \u7ED3\u679C ${push.messageId} \u7684\u63A8\u9001\u6CA1\u53D1\u51FA\u53BB\uFF08\u5DF2\u843D\u8FDB\u6536\u4EF6\u7BB1\uFF0C\u7B49\u5BA2\u6237\u7AEF\u8865\u6536\uFF09:`,
|
|
1589
1654
|
error && error.message
|
|
@@ -1618,7 +1683,7 @@ function projectTask(row, decryptedPayload, options = {}) {
|
|
|
1618
1683
|
// 凭据引用只是名字(cred_id),本体在 llm_credentials 表里,凭据字段本身
|
|
1619
1684
|
// 照旧被白名单挡在外面。
|
|
1620
1685
|
credRefs: _nullishCoalesce(payload.credRefs, () => ( null)),
|
|
1621
|
-
// 整份 metadata
|
|
1686
|
+
// 整份 metadata 由 includeMetadata 控制,单条查询与 hook 回执会带。
|
|
1622
1687
|
...options.includeMetadata ? { metadata: _nullishCoalesce(payload.metadata, () => ( null)) } : {},
|
|
1623
1688
|
// 上一次没发出去的原因。reason 'stale' 表示错过触发时刻太久被判定不再补
|
|
1624
1689
|
// 发;其余是投递失败的错误信息。没有记录 → null。
|
|
@@ -1709,6 +1774,9 @@ function normalizeBeforeFireResult(result) {
|
|
|
1709
1774
|
return { messages: result };
|
|
1710
1775
|
}
|
|
1711
1776
|
if (result && typeof result === "object" && Array.isArray(result.messages)) {
|
|
1777
|
+
if (result.allowedToolNames !== void 0 && (!Array.isArray(result.allowedToolNames) || result.allowedToolNames.some((name) => typeof name !== "string" || !name.trim()))) {
|
|
1778
|
+
throw markPermanent(new TypeError("allowedToolNames must be an array of non-empty strings"), "AGENTIC_BAD_BEFORE_FIRE");
|
|
1779
|
+
}
|
|
1712
1780
|
return {
|
|
1713
1781
|
messages: result.messages,
|
|
1714
1782
|
maxToolIterations: result.maxToolIterations,
|
|
@@ -1717,7 +1785,8 @@ function normalizeBeforeFireResult(result) {
|
|
|
1717
1785
|
// 用真值。真正决定请求体带不带 tools 的规则住在 llm.js 的
|
|
1718
1786
|
// buildAiRequestBody(那边有测试钉着),这层只是不把空数组往下传。
|
|
1719
1787
|
tools: Array.isArray(result.tools) && result.tools.length > 0 ? result.tools : void 0,
|
|
1720
|
-
toolChoice: result.toolChoice
|
|
1788
|
+
toolChoice: result.toolChoice,
|
|
1789
|
+
allowedToolNames: result.allowedToolNames === void 0 ? void 0 : new Set(result.allowedToolNames)
|
|
1721
1790
|
};
|
|
1722
1791
|
}
|
|
1723
1792
|
throw markPermanent(new TypeError(BAD_BEFORE_FIRE_MESSAGE), "AGENTIC_BAD_BEFORE_FIRE");
|
|
@@ -1737,7 +1806,7 @@ function firstPositiveNumber(values, fallback) {
|
|
|
1737
1806
|
async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
|
|
1738
1807
|
const hooks = ctx.hooks;
|
|
1739
1808
|
const signal = ctx.signal || new AbortController().signal;
|
|
1740
|
-
const isCancelled = () => signal.aborted || _optionalChain([ctx, 'access',
|
|
1809
|
+
const isCancelled = () => signal.aborted || _optionalChain([ctx, 'access', _8 => _8.isTaskCancelled, 'optionalCall', _9 => _9()]) === true;
|
|
1741
1810
|
const throwIfCancelled = () => {
|
|
1742
1811
|
if (!isCancelled()) return;
|
|
1743
1812
|
if (isTaskCancelledError(signal.reason)) throw signal.reason;
|
|
@@ -1769,6 +1838,8 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
|
|
|
1769
1838
|
const sessionId = task.id != null ? `sess_task_${task.id}${occurrenceSuffix(task)}` : `sess_${_amsgshared.randomUUID.call(void 0, )}`;
|
|
1770
1839
|
const messageIdBase = task.id != null ? `msg_task_${task.id}${occurrenceSuffix(task)}` : `msg_${_amsgshared.randomUUID.call(void 0, )}`;
|
|
1771
1840
|
const occurrenceMs = occurrenceMsOf(task);
|
|
1841
|
+
const storeAttachment = createAttachmentStore({ task, occurrenceMs, writeState, now: nowFn });
|
|
1842
|
+
const storeSettledAttachment = createAttachmentStore({ task, occurrenceMs, writeState: writeSettledState, now: nowFn });
|
|
1772
1843
|
const { emitResult } = createResultEmitter({
|
|
1773
1844
|
db: ctx.db,
|
|
1774
1845
|
task,
|
|
@@ -1791,17 +1862,37 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
|
|
|
1791
1862
|
const row = await ctx.db.getTaskByUuid(uuid, task.user_id);
|
|
1792
1863
|
if (!row) return null;
|
|
1793
1864
|
const payload = JSON.parse(await decryptFromStorage(row.encrypted_payload, userKey));
|
|
1794
|
-
return projectTask(row, payload);
|
|
1865
|
+
return projectTask(row, payload, { includeMetadata: true });
|
|
1795
1866
|
} catch (error) {
|
|
1796
1867
|
console.warn("[amsg-server] scheduleTask: \u8BFB\u53D6\u649E\u8F66\u4EFB\u52A1\u5931\u8D25\uFF08\u5DF2\u5FFD\u7565\uFF09:", error && error.message);
|
|
1797
1868
|
return null;
|
|
1798
1869
|
}
|
|
1799
1870
|
}
|
|
1871
|
+
const listTasks = async (options = {}) => {
|
|
1872
|
+
throwIfCancelled();
|
|
1873
|
+
const { status = "pending", limit = 100, offset = 0, includeMetadata = true } = options;
|
|
1874
|
+
if (!["pending", "sent", "failed", "all"].includes(status) || !Number.isInteger(limit) || limit < 1 || limit > 100 || !Number.isInteger(offset) || offset < 0) {
|
|
1875
|
+
throw new TypeError("listTasks requires a valid status, limit 1-100, and non-negative offset");
|
|
1876
|
+
}
|
|
1877
|
+
if (typeof _optionalChain([ctx, 'access', _10 => _10.db, 'optionalAccess', _11 => _11.listTasks]) !== "function") {
|
|
1878
|
+
throw new DeploymentConfigError("AGENTIC_TASK_VIEW_UNSUPPORTED: adapter has no listTasks", { code: "AGENTIC_TASK_VIEW_UNSUPPORTED" });
|
|
1879
|
+
}
|
|
1880
|
+
const { tasks, total } = await ctx.db.listTasks(task.user_id, { status, limit, offset });
|
|
1881
|
+
const projected = await Promise.all(tasks.map(async (row) => projectTask(
|
|
1882
|
+
row,
|
|
1883
|
+
JSON.parse(await decryptFromStorage(row.encrypted_payload, userKey)),
|
|
1884
|
+
{ includeMetadata }
|
|
1885
|
+
)));
|
|
1886
|
+
return { tasks: projected, pagination: { total, limit, offset, hasMore: offset + tasks.length < total } };
|
|
1887
|
+
};
|
|
1800
1888
|
const scheduleTask = async (options) => {
|
|
1801
1889
|
throwIfCancelled();
|
|
1802
1890
|
if (!options || typeof options !== "object" || Array.isArray(options)) {
|
|
1803
1891
|
throw new TypeError("scheduleTask(options) \u9700\u8981\u4E00\u4E2A\u5BF9\u8C61\uFF0C\u81F3\u5C11\u5305\u542B { firstSendTime }");
|
|
1804
1892
|
}
|
|
1893
|
+
if (options.taskInput != null && (typeof options.taskInput !== "object" || Array.isArray(options.taskInput))) {
|
|
1894
|
+
throw new TypeError("scheduleTask: taskInput must be an object or null");
|
|
1895
|
+
}
|
|
1805
1896
|
if (typeof options.firstSendTime !== "string" || !options.firstSendTime.trim()) {
|
|
1806
1897
|
throw new RangeError("scheduleTask: firstSendTime \u5FC5\u586B\uFF0C\u4E14\u5FC5\u987B\u662F ISO 8601 \u5B57\u7B26\u4E32");
|
|
1807
1898
|
}
|
|
@@ -1896,7 +1987,8 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
|
|
|
1896
1987
|
// 与凭据同列的投递配置:中转的非标准参数(thinking 之类)跟着任务走,
|
|
1897
1988
|
// 自排的后续任务不该突然掉档。
|
|
1898
1989
|
llmExtraBody: _nullishCoalesce(decryptedPayload.llmExtraBody, () => ( null)),
|
|
1899
|
-
metadata
|
|
1990
|
+
metadata,
|
|
1991
|
+
taskInput: _nullishCoalesce(options.taskInput, () => ( null))
|
|
1900
1992
|
};
|
|
1901
1993
|
const serializedTaskData = JSON.stringify(fullTaskData);
|
|
1902
1994
|
const sizeError = validateTaskPayloadSize(serializedTaskData);
|
|
@@ -1950,7 +2042,8 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
|
|
|
1950
2042
|
created: true,
|
|
1951
2043
|
id: _nullishCoalesce(created.id, () => ( null)),
|
|
1952
2044
|
uuid: _nullishCoalesce(created.uuid, () => ( uuid)),
|
|
1953
|
-
nextSendAt: _nullishCoalesce(created.next_send_at, () => ( nextSendAt))
|
|
2045
|
+
nextSendAt: _nullishCoalesce(created.next_send_at, () => ( nextSendAt)),
|
|
2046
|
+
task: projectTask({ status: "pending", next_send_at: nextSendAt, ...created }, fullTaskData, { includeMetadata: true })
|
|
1954
2047
|
};
|
|
1955
2048
|
};
|
|
1956
2049
|
const cancelTask = async (uuid) => {
|
|
@@ -2018,7 +2111,11 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
|
|
|
2018
2111
|
const stillPending = await ctx.db.getTaskByUuid(uuid, task.user_id);
|
|
2019
2112
|
return { renewed: false, reason: stillPending ? "in_flight" : "not_found" };
|
|
2020
2113
|
}
|
|
2021
|
-
return { renewed: true, uuid, nextSendAt: nextSendAtIso
|
|
2114
|
+
return { renewed: true, uuid, nextSendAt: nextSendAtIso, task: projectTask(
|
|
2115
|
+
{ ...row, next_send_at: nextSendAtIso, retry_count: 0 },
|
|
2116
|
+
payload,
|
|
2117
|
+
{ includeMetadata: true }
|
|
2118
|
+
) };
|
|
2022
2119
|
};
|
|
2023
2120
|
const resolveLlmCredential2 = async (credId) => {
|
|
2024
2121
|
if (typeof credId !== "string" || !credId.trim()) {
|
|
@@ -2035,7 +2132,9 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
|
|
|
2035
2132
|
readState,
|
|
2036
2133
|
writeState,
|
|
2037
2134
|
emitResult,
|
|
2135
|
+
storeAttachment,
|
|
2038
2136
|
scheduleTask,
|
|
2137
|
+
listTasks,
|
|
2039
2138
|
cancelTask,
|
|
2040
2139
|
renewTask,
|
|
2041
2140
|
resolveLlmCredential: resolveLlmCredential2,
|
|
@@ -2068,8 +2167,10 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
|
|
|
2068
2167
|
readState,
|
|
2069
2168
|
writeState,
|
|
2070
2169
|
emitResult,
|
|
2170
|
+
storeAttachment,
|
|
2071
2171
|
scratch,
|
|
2072
2172
|
scheduleTask,
|
|
2173
|
+
listTasks,
|
|
2073
2174
|
cancelTask,
|
|
2074
2175
|
renewTask,
|
|
2075
2176
|
resolveLlmCredential: resolveLlmCredential2,
|
|
@@ -2128,7 +2229,8 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
|
|
|
2128
2229
|
scratch,
|
|
2129
2230
|
readState,
|
|
2130
2231
|
writeState: writeSettledState,
|
|
2131
|
-
emitResult
|
|
2232
|
+
emitResult,
|
|
2233
|
+
storeAttachment: storeSettledAttachment
|
|
2132
2234
|
});
|
|
2133
2235
|
}
|
|
2134
2236
|
}
|
|
@@ -2143,8 +2245,10 @@ async function runFireChain({
|
|
|
2143
2245
|
readState,
|
|
2144
2246
|
writeState,
|
|
2145
2247
|
emitResult,
|
|
2248
|
+
storeAttachment,
|
|
2146
2249
|
scratch,
|
|
2147
2250
|
scheduleTask,
|
|
2251
|
+
listTasks,
|
|
2148
2252
|
cancelTask,
|
|
2149
2253
|
renewTask,
|
|
2150
2254
|
resolveLlmCredential: resolveLlmCredential2,
|
|
@@ -2243,15 +2347,35 @@ async function runFireChain({
|
|
|
2243
2347
|
readState,
|
|
2244
2348
|
writeState,
|
|
2245
2349
|
emitResult,
|
|
2350
|
+
storeAttachment,
|
|
2246
2351
|
scheduleTask,
|
|
2352
|
+
listTasks,
|
|
2247
2353
|
cancelTask,
|
|
2248
2354
|
renewTask,
|
|
2249
2355
|
resolveLlmCredential: resolveLlmCredential2
|
|
2250
2356
|
});
|
|
2251
2357
|
const decision = await hooks.onLLMOutput(sessionCtx);
|
|
2252
2358
|
ctx.throwIfCancelled();
|
|
2359
|
+
const rawCalls = _optionalChain([decision, 'optionalAccess', _12 => _12.decision]) === "tool-request" ? _amsgshared.extractToolCallsFromDecision.call(void 0, decision) : [];
|
|
2360
|
+
const validId = (call) => typeof _optionalChain([call, 'optionalAccess', _13 => _13.id]) === "string" && call.id.trim().length > 0;
|
|
2361
|
+
const nativeCalls = Array.isArray(assistantMessage.tool_calls) ? assistantMessage.tool_calls : [];
|
|
2362
|
+
const usedIds = new Set([...rawCalls, ...nativeCalls].filter(validId).map((call) => call.id));
|
|
2363
|
+
const malformedIds = /* @__PURE__ */ new Set();
|
|
2364
|
+
const toolCalls = rawCalls.map((call, index) => {
|
|
2365
|
+
let id = validId(call) ? call.id : `call_${iteration + 1}_${index + 1}`;
|
|
2366
|
+
if (!validId(call)) {
|
|
2367
|
+
while (usedIds.has(id)) id += "_";
|
|
2368
|
+
usedIds.add(id);
|
|
2369
|
+
}
|
|
2370
|
+
if (typeof _optionalChain([call, 'optionalAccess', _14 => _14.function, 'optionalAccess', _15 => _15.name]) !== "string" || !call.function.name.trim()) {
|
|
2371
|
+
malformedIds.add(id);
|
|
2372
|
+
return { id, type: "function", function: { name: "invalid_tool_call", arguments: "{}" } };
|
|
2373
|
+
}
|
|
2374
|
+
return { ...call, id };
|
|
2375
|
+
});
|
|
2376
|
+
const validationDecision = _optionalChain([decision, 'optionalAccess', _16 => _16.decision]) === "tool-request" && Array.isArray(decision.toolCalls) && decision.toolCalls.length > 0 ? { ...decision, toolCalls } : decision;
|
|
2253
2377
|
try {
|
|
2254
|
-
_amsgshared.assertValidDecision.call(void 0,
|
|
2378
|
+
_amsgshared.assertValidDecision.call(void 0, validationDecision, { inlineToolCalls: true });
|
|
2255
2379
|
} catch (error) {
|
|
2256
2380
|
throw markPermanent(error, "AGENTIC_BAD_DECISION");
|
|
2257
2381
|
}
|
|
@@ -2282,14 +2406,19 @@ async function runFireChain({
|
|
|
2282
2406
|
});
|
|
2283
2407
|
return { handled: true, result: { success: true, messagesSent, status: "finished", iterations: iteration + 1 } };
|
|
2284
2408
|
}
|
|
2285
|
-
const toolCalls = _amsgshared.extractToolCallsFromDecision.call(void 0, decision);
|
|
2286
2409
|
if (toolCalls.length === 0) {
|
|
2287
2410
|
throw taggedRetryableError(
|
|
2288
2411
|
"AGENTIC_EMPTY_TOOL_REQUEST: tool-request decision carried no toolCalls (neither decision.toolCalls nor pushPayloads[].toolCalls)",
|
|
2289
2412
|
"AGENTIC_EMPTY_TOOL_REQUEST"
|
|
2290
2413
|
);
|
|
2291
2414
|
}
|
|
2292
|
-
|
|
2415
|
+
const permittedCalls = toolCalls.filter((call) => !malformedIds.has(call.id) && (normalized.allowedToolNames === void 0 || normalized.allowedToolNames.has(call.function.name)));
|
|
2416
|
+
const deniedResults = toolCalls.filter((call) => !permittedCalls.includes(call)).map((call) => ({
|
|
2417
|
+
role: "tool",
|
|
2418
|
+
tool_call_id: call.id,
|
|
2419
|
+
content: JSON.stringify({ error: malformedIds.has(call.id) ? { code: "INVALID_TOOL_CALL", message: "Tool call requires a function name" } : { code: "TOOL_NOT_ALLOWED", message: "Tool execution is not allowed for this fire" } })
|
|
2420
|
+
}));
|
|
2421
|
+
if (permittedCalls.length > 0 && typeof hooks.executeToolCalls !== "function") {
|
|
2293
2422
|
throw new DeploymentConfigError(
|
|
2294
2423
|
"AGENTIC_CONFIG_ERROR: onLLMOutput returned tool-request but hooks.executeToolCalls is not configured",
|
|
2295
2424
|
{ code: "AGENTIC_CONFIG_ERROR" }
|
|
@@ -2301,7 +2430,7 @@ async function runFireChain({
|
|
|
2301
2430
|
let toolResults;
|
|
2302
2431
|
try {
|
|
2303
2432
|
ctx.throwIfCancelled();
|
|
2304
|
-
toolResults = await hooks.executeToolCalls(
|
|
2433
|
+
toolResults = permittedCalls.length > 0 ? await hooks.executeToolCalls(permittedCalls, sessionCtx) : [];
|
|
2305
2434
|
ctx.throwIfCancelled();
|
|
2306
2435
|
if (!Array.isArray(toolResults)) {
|
|
2307
2436
|
throw new TypeError('executeToolCalls must resolve to an array of { tool_call_id, role: "tool", content }');
|
|
@@ -2309,16 +2438,17 @@ async function runFireChain({
|
|
|
2309
2438
|
} catch (error) {
|
|
2310
2439
|
ctx.throwIfCancelled();
|
|
2311
2440
|
if (isTaskCancelledError(error)) throw error;
|
|
2312
|
-
toolResults =
|
|
2441
|
+
toolResults = permittedCalls.map((toolCall) => ({
|
|
2313
2442
|
tool_call_id: toolCall && typeof toolCall === "object" && typeof toolCall.id === "string" ? toolCall.id : "",
|
|
2314
2443
|
role: "tool",
|
|
2315
|
-
content: `Tool execution failed: ${_nullishCoalesce(_optionalChain([error, 'optionalAccess',
|
|
2444
|
+
content: `Tool execution failed: ${_nullishCoalesce(_optionalChain([error, 'optionalAccess', _17 => _17.message]), () => ( String(error)))}`
|
|
2316
2445
|
}));
|
|
2317
2446
|
}
|
|
2318
|
-
|
|
2319
|
-
const
|
|
2320
|
-
const
|
|
2321
|
-
const
|
|
2447
|
+
toolResults = [...toolResults, ...deniedResults];
|
|
2448
|
+
const declared = nativeCalls.filter(validId).map((call) => toolCalls.find((item) => item.id === call.id) || call);
|
|
2449
|
+
const nativeIds = new Set(declared.map((call) => call.id));
|
|
2450
|
+
const synthesized = toolCalls.filter((call) => !nativeIds.has(call.id));
|
|
2451
|
+
const assistantWithTools = { ...assistantMessage, tool_calls: [...declared, ...synthesized] };
|
|
2322
2452
|
messages = [...messages.slice(0, -1), assistantWithTools, ...toolResults];
|
|
2323
2453
|
}
|
|
2324
2454
|
throw taggedRetryableError(
|
|
@@ -2497,7 +2627,7 @@ function validOwner(owner) {
|
|
|
2497
2627
|
return owner && typeof owner.type === "string" && owner.type.trim().length > 0 && owner.type.length <= 100 && typeof owner.id === "string" && owner.id.trim().length > 0 && owner.id.length <= 300;
|
|
2498
2628
|
}
|
|
2499
2629
|
function validateGuard(guard) {
|
|
2500
|
-
if (!validOwner(_optionalChain([guard, 'optionalAccess',
|
|
2630
|
+
if (!validOwner(_optionalChain([guard, 'optionalAccess', _18 => _18.owner])))
|
|
2501
2631
|
throw guardError("INVALID_CLOUD_OWNER", "Cloud owner is invalid");
|
|
2502
2632
|
if (!Number.isSafeInteger(guard.generation) || guard.generation < 0) {
|
|
2503
2633
|
throw guardError(
|
|
@@ -2525,14 +2655,14 @@ async function assertCloudGuard(db, userId, guard) {
|
|
|
2525
2655
|
}
|
|
2526
2656
|
async function resolveMetadata(resolveOwner, input, explicit) {
|
|
2527
2657
|
const resolved = typeof resolveOwner === "function" ? await resolveOwner(input) : null;
|
|
2528
|
-
const owner = _nullishCoalesce(_nullishCoalesce(_nullishCoalesce(_optionalChain([explicit, 'optionalAccess',
|
|
2658
|
+
const owner = _nullishCoalesce(_nullishCoalesce(_nullishCoalesce(_optionalChain([explicit, 'optionalAccess', _19 => _19.owner]), () => ( _optionalChain([explicit, 'optionalAccess', _20 => _20.metadata, 'optionalAccess', _21 => _21.cloudOwner]))), () => ( _optionalChain([resolved, 'optionalAccess', _22 => _22.owner]))), () => ( (validOwner(resolved) ? resolved : null)));
|
|
2529
2659
|
if (owner !== null && !validOwner(owner))
|
|
2530
2660
|
throw guardError("INVALID_CLOUD_OWNER", "Cloud owner is invalid");
|
|
2531
|
-
return { owner, kind: _nullishCoalesce(_nullishCoalesce(_optionalChain([explicit, 'optionalAccess',
|
|
2661
|
+
return { owner, kind: _nullishCoalesce(_nullishCoalesce(_optionalChain([explicit, 'optionalAccess', _23 => _23.kind]), () => ( _optionalChain([resolved, 'optionalAccess', _24 => _24.kind]))), () => ( null)) };
|
|
2532
2662
|
}
|
|
2533
2663
|
async function captureCloudTaskGuard(db, userId, payload, resolveOwner, task) {
|
|
2534
2664
|
if (!db.cloudDataManagement) return null;
|
|
2535
|
-
if (_optionalChain([payload, 'optionalAccess',
|
|
2665
|
+
if (_optionalChain([payload, 'optionalAccess', _25 => _25.__cloudOwnerGuard])) {
|
|
2536
2666
|
const guard2 = validateGuard(payload.__cloudOwnerGuard);
|
|
2537
2667
|
await assertCloudGuard(db, userId, guard2);
|
|
2538
2668
|
return guard2;
|
|
@@ -2545,7 +2675,7 @@ async function captureCloudTaskGuard(db, userId, payload, resolveOwner, task) {
|
|
|
2545
2675
|
if (!owner) return null;
|
|
2546
2676
|
const guard = validateGuard({
|
|
2547
2677
|
owner,
|
|
2548
|
-
generation: _nullishCoalesce(_optionalChain([payload, 'optionalAccess',
|
|
2678
|
+
generation: _nullishCoalesce(_optionalChain([payload, 'optionalAccess', _26 => _26.ownerGeneration]), () => ( 0))
|
|
2549
2679
|
});
|
|
2550
2680
|
await assertCloudGuard(db, userId, guard);
|
|
2551
2681
|
return guard;
|
|
@@ -2597,7 +2727,7 @@ function createCloudGuardedAdapter(db, {
|
|
|
2597
2727
|
}
|
|
2598
2728
|
guard = inheritedGuard || validateGuard({
|
|
2599
2729
|
owner: resolved.owner,
|
|
2600
|
-
generation: _nullishCoalesce(_optionalChain([explicit, 'optionalAccess',
|
|
2730
|
+
generation: _nullishCoalesce(_optionalChain([explicit, 'optionalAccess', _27 => _27.ownerGeneration]), () => ( 0))
|
|
2601
2731
|
});
|
|
2602
2732
|
} else {
|
|
2603
2733
|
guard = null;
|
|
@@ -2605,9 +2735,9 @@ function createCloudGuardedAdapter(db, {
|
|
|
2605
2735
|
await assertCloudGuard(db, userId, inheritedGuard);
|
|
2606
2736
|
await assertCloudGuard(db, userId, guard);
|
|
2607
2737
|
const metadata = {
|
|
2608
|
-
owner: _nullishCoalesce(_optionalChain([guard, 'optionalAccess',
|
|
2738
|
+
owner: _nullishCoalesce(_optionalChain([guard, 'optionalAccess', _28 => _28.owner]), () => ( resolved.owner)),
|
|
2609
2739
|
kind: resolved.kind,
|
|
2610
|
-
generation: _nullishCoalesce(_optionalChain([guard, 'optionalAccess',
|
|
2740
|
+
generation: _nullishCoalesce(_optionalChain([guard, 'optionalAccess', _29 => _29.generation]), () => ( 0))
|
|
2611
2741
|
};
|
|
2612
2742
|
return {
|
|
2613
2743
|
cloudGuard: guard,
|
|
@@ -2787,7 +2917,7 @@ function createCloudGuardedAdapter(db, {
|
|
|
2787
2917
|
else delete payload[field];
|
|
2788
2918
|
}
|
|
2789
2919
|
if (payload.metadata && typeof payload.metadata === "object") {
|
|
2790
|
-
if (_optionalChain([originalPayload, 'access',
|
|
2920
|
+
if (_optionalChain([originalPayload, 'access', _30 => _30.metadata, 'optionalAccess', _31 => _31.cloudOwner]) !== void 0)
|
|
2791
2921
|
payload.metadata.cloudOwner = originalPayload.metadata.cloudOwner;
|
|
2792
2922
|
else delete payload.metadata.cloudOwner;
|
|
2793
2923
|
}
|
|
@@ -2979,11 +3109,11 @@ async function processSingleMessage(task, ctx, providedMasterKey, predecrypted =
|
|
|
2979
3109
|
}
|
|
2980
3110
|
const userKey = predecrypted && predecrypted.userKey || await deriveUserEncryptionKey(task.user_id, masterKey);
|
|
2981
3111
|
const decryptedPayload = predecrypted && predecrypted.payload || JSON.parse(await decryptFromStorage(task.encrypted_payload, userKey));
|
|
2982
|
-
const cloudGuard = await captureCloudTaskGuard(ctx.db, task.user_id, decryptedPayload, _optionalChain([ctx, 'access',
|
|
3112
|
+
const cloudGuard = await captureCloudTaskGuard(ctx.db, task.user_id, decryptedPayload, _optionalChain([ctx, 'access', _32 => _32.cloudData, 'optionalAccess', _33 => _33.resolveOwner]), task);
|
|
2983
3113
|
if (cloudGuard) {
|
|
2984
3114
|
await assertCloudGuard(ctx.db, task.user_id, cloudGuard);
|
|
2985
3115
|
const originalWebpush = ctx.webpush;
|
|
2986
|
-
const guardedDb = createCloudGuardedAdapter(ctx.db, { masterKey, resolveOwner: _optionalChain([ctx, 'access',
|
|
3116
|
+
const guardedDb = createCloudGuardedAdapter(ctx.db, { masterKey, resolveOwner: _optionalChain([ctx, 'access', _34 => _34.cloudData, 'optionalAccess', _35 => _35.resolveOwner]), guard: cloudGuard, userId: task.user_id });
|
|
2987
3117
|
ctx = { ...ctx, db: guardedDb, webpush: originalWebpush && {
|
|
2988
3118
|
...originalWebpush,
|
|
2989
3119
|
async sendNotification(...args) {
|
|
@@ -3179,8 +3309,8 @@ async function processMessagesByUuid(uuid, ctx, maxRetries = 2, userId, provided
|
|
|
3179
3309
|
try {
|
|
3180
3310
|
const userKey = await deriveUserEncryptionKey(task.user_id, masterKey);
|
|
3181
3311
|
const payload = JSON.parse(await decryptFromStorage(task.encrypted_payload, userKey));
|
|
3182
|
-
const guard = await captureCloudTaskGuard(ctx.db, task.user_id, payload, _optionalChain([ctx, 'access',
|
|
3183
|
-
if (guard) attemptDb = createCloudGuardedAdapter(ctx.db, { masterKey, resolveOwner: _optionalChain([ctx, 'access',
|
|
3312
|
+
const guard = await captureCloudTaskGuard(ctx.db, task.user_id, payload, _optionalChain([ctx, 'access', _36 => _36.cloudData, 'optionalAccess', _37 => _37.resolveOwner]), task);
|
|
3313
|
+
if (guard) attemptDb = createCloudGuardedAdapter(ctx.db, { masterKey, resolveOwner: _optionalChain([ctx, 'access', _38 => _38.cloudData, 'optionalAccess', _39 => _39.resolveOwner]), guard, userId: task.user_id });
|
|
3184
3314
|
} catch (error) {
|
|
3185
3315
|
return { success: false, error: { code: error.code || "PROCESSING_ERROR", message: error.message, retriesAttempted: retryCount } };
|
|
3186
3316
|
}
|
|
@@ -3216,7 +3346,7 @@ async function processMessagesByUuid(uuid, ctx, maxRetries = 2, userId, provided
|
|
|
3216
3346
|
})
|
|
3217
3347
|
});
|
|
3218
3348
|
} catch (_updateError) {
|
|
3219
|
-
if (_optionalChain([_updateError, 'optionalAccess',
|
|
3349
|
+
if (_optionalChain([_updateError, 'optionalAccess', _40 => _40.code]) === "CLOUD_OWNER_RETIRED") {
|
|
3220
3350
|
return { success: false, error: { code: _updateError.code, message: _updateError.message, retriesAttempted: retryCount } };
|
|
3221
3351
|
}
|
|
3222
3352
|
try {
|
|
@@ -3392,6 +3522,9 @@ function createScheduleMessageHandler(ctx) {
|
|
|
3392
3522
|
owner: payload.owner,
|
|
3393
3523
|
kind: payload.kind,
|
|
3394
3524
|
ownerGeneration: payload.ownerGeneration,
|
|
3525
|
+
// Input is persisted with the task in the same create/supersede transaction.
|
|
3526
|
+
// Shared credentials remain references and continue to resolve at fire time.
|
|
3527
|
+
taskInput: _nullishCoalesce(payload.taskInput, () => ( null)),
|
|
3395
3528
|
metadata: payload.metadata || {}
|
|
3396
3529
|
};
|
|
3397
3530
|
const serializedTaskData = JSON.stringify(fullTaskData);
|
|
@@ -3599,7 +3732,7 @@ async function runScheduledTick(ctx) {
|
|
|
3599
3732
|
}
|
|
3600
3733
|
async function cleanupExpiredClientState(ctx) {
|
|
3601
3734
|
if (typeof ctx.db.cleanupClientState !== "function") return;
|
|
3602
|
-
const targets = planClientStateCleanup(ctx.clientStateTtl, Date.now());
|
|
3735
|
+
const targets = planClientStateCleanup({ [ATTACHMENT_NAMESPACE]: ATTACHMENT_RETENTION_DAYS, ...ctx.clientStateTtl }, Date.now());
|
|
3603
3736
|
if (targets.length === 0) return;
|
|
3604
3737
|
try {
|
|
3605
3738
|
await ctx.db.cleanupClientState(targets);
|
|
@@ -3689,7 +3822,7 @@ async function deliverTasks(ctx, tasks) {
|
|
|
3689
3822
|
const until = new Date(Date.now() + heartbeatLeaseTtlMs).toISOString();
|
|
3690
3823
|
renewed = guard ? await db.renewTaskLease(task.id, until, guard) : await db.renewTaskLease(task.id, until);
|
|
3691
3824
|
} catch (error) {
|
|
3692
|
-
if (_optionalChain([error, 'optionalAccess',
|
|
3825
|
+
if (_optionalChain([error, 'optionalAccess', _41 => _41.code]) === "CLOUD_OWNER_RETIRED") renewed = false;
|
|
3693
3826
|
else console.warn("[amsg-server] \u79DF\u7EA6\u7EED\u79DF\u5931\u8D25\uFF08\u4E0B\u4E2A\u5FC3\u8DF3\u518D\u8BD5\uFF09:", error && error.message);
|
|
3694
3827
|
}
|
|
3695
3828
|
if (renewed === false) {
|
|
@@ -3732,7 +3865,7 @@ async function deliverTasks(ctx, tasks) {
|
|
|
3732
3865
|
lastErrorColumnSuspicions.delete(db);
|
|
3733
3866
|
return result2;
|
|
3734
3867
|
} catch (error) {
|
|
3735
|
-
if (_optionalChain([error, 'optionalAccess',
|
|
3868
|
+
if (_optionalChain([error, 'optionalAccess', _42 => _42.code]) === "CLOUD_OWNER_RETIRED") throw error;
|
|
3736
3869
|
combinedError = error;
|
|
3737
3870
|
}
|
|
3738
3871
|
const result = await updateLifecycleTask(taskId, stateFields);
|
|
@@ -3927,12 +4060,12 @@ async function deliverTasks(ctx, tasks) {
|
|
|
3927
4060
|
let claimed;
|
|
3928
4061
|
try {
|
|
3929
4062
|
if (decrypted.ok) {
|
|
3930
|
-
const guard = await captureCloudTaskGuard(db, task.user_id, decryptedPayload, _optionalChain([ctx, 'access',
|
|
4063
|
+
const guard = await captureCloudTaskGuard(db, task.user_id, decryptedPayload, _optionalChain([ctx, 'access', _43 => _43.cloudData, 'optionalAccess', _44 => _44.resolveOwner]), task);
|
|
3931
4064
|
if (guard) taskCloudGuards.set(task.id, guard);
|
|
3932
4065
|
}
|
|
3933
4066
|
claimed = await claimForThisTick(task, serializeGroup);
|
|
3934
4067
|
} catch (error) {
|
|
3935
|
-
if (_optionalChain([error, 'optionalAccess',
|
|
4068
|
+
if (_optionalChain([error, 'optionalAccess', _45 => _45.code]) === "CLOUD_OWNER_RETIRED") {
|
|
3936
4069
|
await recordCancelled(task, "owner_retired");
|
|
3937
4070
|
return;
|
|
3938
4071
|
}
|
|
@@ -3967,8 +4100,8 @@ async function deliverTasks(ctx, tasks) {
|
|
|
3967
4100
|
const notOnFreshRetryChain = (task.retry_count || 0) === 0 || Number.isFinite(retryAfterMs) && Date.now() - retryAfterMs > staleAfterMs;
|
|
3968
4101
|
const settleAsStale = async () => {
|
|
3969
4102
|
try {
|
|
3970
|
-
const cloudGuard = await captureCloudTaskGuard(db, task.user_id, decryptedPayload, _optionalChain([ctx, 'access',
|
|
3971
|
-
const guardedDb = createCloudGuardedAdapter(db, { masterKey, resolveOwner: _optionalChain([ctx, 'access',
|
|
4103
|
+
const cloudGuard = await captureCloudTaskGuard(db, task.user_id, decryptedPayload, _optionalChain([ctx, 'access', _46 => _46.cloudData, 'optionalAccess', _47 => _47.resolveOwner]), task);
|
|
4104
|
+
const guardedDb = createCloudGuardedAdapter(db, { masterKey, resolveOwner: _optionalChain([ctx, 'access', _48 => _48.cloudData, 'optionalAccess', _49 => _49.resolveOwner]), guard: cloudGuard, userId: task.user_id });
|
|
3972
4105
|
const guardedWebpush = cloudGuard && ctx.webpush ? {
|
|
3973
4106
|
...ctx.webpush,
|
|
3974
4107
|
async sendNotification(...args) {
|
|
@@ -4259,6 +4392,9 @@ function createUpdateMessageHandler(ctx) {
|
|
|
4259
4392
|
if (!isPlainObject(updates)) {
|
|
4260
4393
|
return { status: 400, body: { success: false, error: { code: "INVALID_UPDATE_DATA", message: "\u66F4\u65B0\u6570\u636E\u683C\u5F0F\u9519\u8BEF" } } };
|
|
4261
4394
|
}
|
|
4395
|
+
if (Object.prototype.hasOwnProperty.call(updates, "taskInput")) {
|
|
4396
|
+
return { status: 400, body: { success: false, error: { code: "INVALID_UPDATE_DATA", message: "taskInput \u53EA\u80FD\u5728\u521B\u5EFA\u6216\u66FF\u6362\u4EFB\u52A1\u65F6\u8BBE\u7F6E", details: { invalidFields: ["taskInput"] } } } };
|
|
4397
|
+
}
|
|
4262
4398
|
if (updates.nextSendAt && !isValidISO8601(updates.nextSendAt)) {
|
|
4263
4399
|
return { status: 400, body: { success: false, error: { code: "INVALID_UPDATE_DATA", message: "\u66F4\u65B0\u6570\u636E\u683C\u5F0F\u9519\u8BEF", details: { invalidFields: ["nextSendAt"] } } } };
|
|
4264
4400
|
}
|
|
@@ -5270,7 +5406,7 @@ var D1Adapter = class {
|
|
|
5270
5406
|
return typeof this._db.batch === "function";
|
|
5271
5407
|
}
|
|
5272
5408
|
_cloudGuardPredicate(userId, guard) {
|
|
5273
|
-
if (!_optionalChain([guard, 'optionalAccess',
|
|
5409
|
+
if (!_optionalChain([guard, 'optionalAccess', _50 => _50.owner]) || !Number.isSafeInteger(guard.generation) || guard.generation < 0) {
|
|
5274
5410
|
throw new Error("Invalid cloud owner guard");
|
|
5275
5411
|
}
|
|
5276
5412
|
return {
|
|
@@ -5297,7 +5433,7 @@ var D1Adapter = class {
|
|
|
5297
5433
|
indexes.push(expanded.length);
|
|
5298
5434
|
expanded.push(statements[i]);
|
|
5299
5435
|
const item = metadata.get(i);
|
|
5300
|
-
if (_optionalChain([item, 'optionalAccess',
|
|
5436
|
+
if (_optionalChain([item, 'optionalAccess', _51 => _51.value])) {
|
|
5301
5437
|
expanded.push(this._db.prepare(
|
|
5302
5438
|
`INSERT INTO cloud_resource_metadata
|
|
5303
5439
|
(user_id, resource_key, encrypted_value) SELECT ?, ?, ? WHERE changes() > 0
|
|
@@ -5332,14 +5468,39 @@ var D1Adapter = class {
|
|
|
5332
5468
|
}
|
|
5333
5469
|
async getCloudResourceMetadata(userId, resourceKey) {
|
|
5334
5470
|
const row = await this._db.prepare("SELECT encrypted_value FROM cloud_resource_metadata WHERE user_id=? AND resource_key=?").bind(userId, resourceKey).first();
|
|
5335
|
-
return _nullishCoalesce(_optionalChain([row, 'optionalAccess',
|
|
5471
|
+
return _nullishCoalesce(_optionalChain([row, 'optionalAccess', _52 => _52.encrypted_value]), () => ( null));
|
|
5472
|
+
}
|
|
5473
|
+
async getOutboxPayload(userId, messageId) {
|
|
5474
|
+
const row = await this._db.prepare(
|
|
5475
|
+
"SELECT payload FROM message_outbox WHERE user_id = ? AND message_id = ? LIMIT 1"
|
|
5476
|
+
).bind(userId, messageId).first();
|
|
5477
|
+
return _nullishCoalesce(_optionalChain([row, 'optionalAccess', _53 => _53.payload]), () => ( null));
|
|
5336
5478
|
}
|
|
5337
5479
|
async listCloudResourceRows(userId) {
|
|
5338
|
-
const
|
|
5480
|
+
const queries = {
|
|
5481
|
+
task: "SELECT * FROM scheduled_messages WHERE user_id = ?",
|
|
5482
|
+
state: `SELECT user_id, namespace, key,
|
|
5483
|
+
CASE WHEN LENGTH(value) <= 1024 THEN value ELSE NULL END AS value,
|
|
5484
|
+
LENGTH(CAST(value AS BLOB)) AS byte_size,
|
|
5485
|
+
substr(value, 1, 128) AS value_prefix,
|
|
5486
|
+
CASE WHEN LENGTH(value) > 128 THEN substr(value, -64) ELSE '' END AS value_suffix,
|
|
5487
|
+
updated_at
|
|
5488
|
+
FROM client_state WHERE user_id = ?`,
|
|
5489
|
+
credential: "SELECT * FROM llm_credentials WHERE user_id = ?",
|
|
5490
|
+
outbox: `SELECT id, user_id, message_id, task_uuid, session_id, message_index, total_messages,
|
|
5491
|
+
CASE WHEN LENGTH(payload) <= 1024 THEN payload ELSE NULL END AS payload,
|
|
5492
|
+
LENGTH(CAST(payload AS BLOB)) AS byte_size,
|
|
5493
|
+
substr(payload, 1, 128) AS payload_prefix,
|
|
5494
|
+
CASE WHEN LENGTH(payload) > 128 THEN substr(payload, -64) ELSE '' END AS payload_suffix,
|
|
5495
|
+
created_at, delivered_at, acked_at
|
|
5496
|
+
FROM message_outbox WHERE user_id = ?`,
|
|
5497
|
+
subscription: "SELECT * FROM push_subscriptions WHERE user_id = ?",
|
|
5498
|
+
metadata: "SELECT * FROM cloud_resource_metadata WHERE user_id = ?"
|
|
5499
|
+
};
|
|
5339
5500
|
const result = { gaps: [] };
|
|
5340
|
-
await Promise.all(Object.entries(
|
|
5501
|
+
await Promise.all(Object.entries(queries).map(async ([type, sql]) => {
|
|
5341
5502
|
try {
|
|
5342
|
-
const response = await this._db.prepare(
|
|
5503
|
+
const response = await this._db.prepare(sql).bind(userId).all();
|
|
5343
5504
|
result[type] = response.results || [];
|
|
5344
5505
|
} catch (e4) {
|
|
5345
5506
|
result[type] = [];
|
|
@@ -5404,7 +5565,10 @@ var D1Adapter = class {
|
|
|
5404
5565
|
if (idempotencyKey) {
|
|
5405
5566
|
return this._cloudRecord(await this._db.prepare(`SELECT * FROM cloud_data_records WHERE user_id=? AND kind=? AND idempotency_key=?`).bind(userId, kind, idempotencyKey).first());
|
|
5406
5567
|
}
|
|
5407
|
-
|
|
5568
|
+
if (createOnly) {
|
|
5569
|
+
return this.getCloudDataRecord(userId, kind, id);
|
|
5570
|
+
}
|
|
5571
|
+
return { id, userId, kind, data, updatedAt: now };
|
|
5408
5572
|
}
|
|
5409
5573
|
async getCloudDataRecord(userId, kind, id) {
|
|
5410
5574
|
return this._cloudRecord(await this._db.prepare("SELECT * FROM cloud_data_records WHERE user_id=? AND kind=? AND id=?").bind(userId, kind, id).first());
|
|
@@ -5537,7 +5701,7 @@ var D1Adapter = class {
|
|
|
5537
5701
|
const name = "metadata-sidecars-v1";
|
|
5538
5702
|
const batch = await this.getCloudMaintenanceBatch(name, "cloud_resource_metadata", limit);
|
|
5539
5703
|
if (batch.completed) return;
|
|
5540
|
-
const cursor = _nullishCoalesce(_optionalChain([batch, 'access',
|
|
5704
|
+
const cursor = _nullishCoalesce(_optionalChain([batch, 'access', _54 => _54.rows, 'access', _55 => _55.at, 'call', _56 => _56(-1), 'optionalAccess', _57 => _57.maintenance_rowid]), () => ( batch.upperBound));
|
|
5541
5705
|
if (batch.rows.length) await this.cleanupCloudResourceMetadata(null, { after: batch.cursor, through: cursor });
|
|
5542
5706
|
await this.finishCloudMaintenanceBatch(name, cursor, batch.rows.length < limit || cursor >= batch.upperBound);
|
|
5543
5707
|
}
|
|
@@ -5611,7 +5775,30 @@ var D1Adapter = class {
|
|
|
5611
5775
|
if (!guard) return true;
|
|
5612
5776
|
const condition = this._cloudGuardPredicate(userId, guard);
|
|
5613
5777
|
const row = await this._db.prepare(`SELECT ${condition.sql} AS valid`).bind(...condition.args).first();
|
|
5614
|
-
return !!_optionalChain([row, 'optionalAccess',
|
|
5778
|
+
return !!_optionalChain([row, 'optionalAccess', _58 => _58.valid]);
|
|
5779
|
+
}
|
|
5780
|
+
_rowSnapshotMatch(spec, row) {
|
|
5781
|
+
const clauses = [];
|
|
5782
|
+
const args = [];
|
|
5783
|
+
for (const column of spec.columns) {
|
|
5784
|
+
if (!Object.hasOwn(row, column)) continue;
|
|
5785
|
+
if ((column === "value" || column === "payload") && row[column] === null && typeof row.byte_size === "number") {
|
|
5786
|
+
clauses.push(
|
|
5787
|
+
`LENGTH(CAST(${column} AS BLOB)) IS ?`,
|
|
5788
|
+
`substr(${column}, 1, 128) IS ?`,
|
|
5789
|
+
`(CASE WHEN LENGTH(${column}) > 128 THEN substr(${column}, -64) ELSE '' END) IS ?`
|
|
5790
|
+
);
|
|
5791
|
+
args.push(
|
|
5792
|
+
row.byte_size,
|
|
5793
|
+
_nullishCoalesce(row[`${column}_prefix`], () => ( null)),
|
|
5794
|
+
_nullishCoalesce(row[`${column}_suffix`], () => ( ""))
|
|
5795
|
+
);
|
|
5796
|
+
} else {
|
|
5797
|
+
clauses.push(`${column} IS ?`);
|
|
5798
|
+
args.push(row[column]);
|
|
5799
|
+
}
|
|
5800
|
+
}
|
|
5801
|
+
return { predicate: clauses.join(" AND "), values: args };
|
|
5615
5802
|
}
|
|
5616
5803
|
/** Delete only the exact physical snapshot; no untrusted SQL identifiers. */
|
|
5617
5804
|
async deleteCloudResourceRows(userId, locators, { operationId, leaseToken } = {}) {
|
|
@@ -5660,9 +5847,7 @@ var D1Adapter = class {
|
|
|
5660
5847
|
).bind(userId, namespace, key, "amsg-chunks" + namespace, key + "", key + " ", identities));
|
|
5661
5848
|
}
|
|
5662
5849
|
for (const row of locator.rows) {
|
|
5663
|
-
const
|
|
5664
|
-
const predicate = columns.map((column) => `${column} IS ?`).join(" AND ");
|
|
5665
|
-
const values = columns.map((column) => row[column]);
|
|
5850
|
+
const { predicate, values } = this._rowSnapshotMatch(spec, row);
|
|
5666
5851
|
const identity = spec.keys.map((column) => `${column} IS ?`).join(" AND ");
|
|
5667
5852
|
statements.push(this._db.prepare(
|
|
5668
5853
|
`INSERT INTO cloud_guard_assertions (allowed)
|
|
@@ -5671,8 +5856,8 @@ var D1Adapter = class {
|
|
|
5671
5856
|
).bind(...spec.keys.map((column) => row[column]), ...values));
|
|
5672
5857
|
}
|
|
5673
5858
|
for (const row of locator.rows) {
|
|
5674
|
-
const
|
|
5675
|
-
statements.push(this._db.prepare(`DELETE FROM ${spec.table} WHERE ${
|
|
5859
|
+
const { predicate, values } = this._rowSnapshotMatch(spec, row);
|
|
5860
|
+
statements.push(this._db.prepare(`DELETE FROM ${spec.table} WHERE ${predicate}`).bind(...values));
|
|
5676
5861
|
}
|
|
5677
5862
|
if (locator.metadataKey) statements.push(this._db.prepare("DELETE FROM cloud_resource_metadata WHERE user_id=? AND resource_key=?").bind(userId, locator.metadataKey));
|
|
5678
5863
|
try {
|
|
@@ -5864,7 +6049,7 @@ var D1Adapter = class {
|
|
|
5864
6049
|
).bind(...values);
|
|
5865
6050
|
const ownerUser = cloudGuard ? await this._db.prepare("SELECT user_id FROM scheduled_messages WHERE id=?").bind(taskId).first() : null;
|
|
5866
6051
|
if (cloudGuard && !ownerUser) return null;
|
|
5867
|
-
await this._cloudBatch([statement], _optionalChain([ownerUser, 'optionalAccess',
|
|
6052
|
+
await this._cloudBatch([statement], _optionalChain([ownerUser, 'optionalAccess', _59 => _59.user_id]), [cloudGuard]);
|
|
5868
6053
|
return this._db.prepare("SELECT * FROM scheduled_messages WHERE id = ?").bind(taskId).first();
|
|
5869
6054
|
}
|
|
5870
6055
|
/**
|
|
@@ -5989,7 +6174,7 @@ var D1Adapter = class {
|
|
|
5989
6174
|
).bind(...values);
|
|
5990
6175
|
const ownerUser = cloudGuard ? await this._db.prepare("SELECT user_id FROM scheduled_messages WHERE id=?").bind(taskId).first() : null;
|
|
5991
6176
|
if (cloudGuard && !ownerUser) return false;
|
|
5992
|
-
const [res] = await this._cloudBatch([statement], _optionalChain([ownerUser, 'optionalAccess',
|
|
6177
|
+
const [res] = await this._cloudBatch([statement], _optionalChain([ownerUser, 'optionalAccess', _60 => _60.user_id]), [cloudGuard]);
|
|
5993
6178
|
return (res.meta.changes || 0) > 0;
|
|
5994
6179
|
}
|
|
5995
6180
|
/**
|
|
@@ -6008,7 +6193,7 @@ var D1Adapter = class {
|
|
|
6008
6193
|
).bind(this._iso(leaseUntil), taskId);
|
|
6009
6194
|
const ownerUser = cloudGuard ? await this._db.prepare("SELECT user_id FROM scheduled_messages WHERE id=?").bind(taskId).first() : null;
|
|
6010
6195
|
if (cloudGuard && !ownerUser) return false;
|
|
6011
|
-
const [res] = await this._cloudBatch([statement], _optionalChain([ownerUser, 'optionalAccess',
|
|
6196
|
+
const [res] = await this._cloudBatch([statement], _optionalChain([ownerUser, 'optionalAccess', _61 => _61.user_id]), [cloudGuard]);
|
|
6012
6197
|
return (res.meta.changes || 0) > 0;
|
|
6013
6198
|
}
|
|
6014
6199
|
async listTasks(userId, opts = {}) {
|
|
@@ -6169,30 +6354,39 @@ var D1Adapter = class {
|
|
|
6169
6354
|
result.cleanupOutcomes = probeIndexes.map((probeAt) => {
|
|
6170
6355
|
if (probeAt === null) return null;
|
|
6171
6356
|
const row = results[probeAt] && Array.isArray(results[probeAt].results) ? results[probeAt].results[0] : null;
|
|
6172
|
-
return Number(_nullishCoalesce(_optionalChain([row, 'optionalAccess',
|
|
6357
|
+
return Number(_nullishCoalesce(_optionalChain([row, 'optionalAccess', _62 => _62.n]), () => ( 0))) === 0;
|
|
6173
6358
|
});
|
|
6174
6359
|
}
|
|
6175
6360
|
return result;
|
|
6176
6361
|
}
|
|
6177
6362
|
/**
|
|
6178
|
-
*
|
|
6363
|
+
* Entries of one namespace (values still encrypted); optional exact/prefix selection.
|
|
6179
6364
|
*
|
|
6180
6365
|
* @param {string} userId
|
|
6181
6366
|
* @param {string} namespace
|
|
6182
6367
|
* @returns {Promise<Array<{ namespace: string, key: string, value: string, updated_at: number }>>}
|
|
6183
6368
|
*/
|
|
6184
|
-
async getClientState(userId, namespace) {
|
|
6369
|
+
async getClientState(userId, namespace, options = {}) {
|
|
6370
|
+
const params = [userId, namespace];
|
|
6371
|
+
let selector = "";
|
|
6372
|
+
if (options.key !== void 0) {
|
|
6373
|
+
selector = " AND key = ?";
|
|
6374
|
+
params.push(options.key);
|
|
6375
|
+
} else if (options.keyPrefix !== void 0) {
|
|
6376
|
+
selector = " AND key >= ? AND key < ?";
|
|
6377
|
+
params.push(options.keyPrefix, prefixRangeEnd(options.keyPrefix));
|
|
6378
|
+
}
|
|
6185
6379
|
const res = await this._db.prepare(
|
|
6186
6380
|
`SELECT namespace, key, value, updated_at
|
|
6187
6381
|
FROM client_state
|
|
6188
|
-
WHERE user_id = ? AND namespace =
|
|
6382
|
+
WHERE user_id = ? AND namespace = ?${selector}
|
|
6189
6383
|
ORDER BY key ASC`
|
|
6190
|
-
).bind(
|
|
6384
|
+
).bind(...params).all();
|
|
6191
6385
|
return res.results || [];
|
|
6192
6386
|
}
|
|
6193
6387
|
/**
|
|
6194
6388
|
* 例行清理:把指定命名空间下太久没更新的条目删掉(run-tick 每跳顺手调,
|
|
6195
|
-
*
|
|
6389
|
+
* 附件默认保留 30 天,宿主 `clientStateTtl` 可覆盖或配置其他命名空间)。
|
|
6196
6390
|
*
|
|
6197
6391
|
* 不限用户——「这个命名空间只留最近 N 天」是命名空间级的约定,单用户部署
|
|
6198
6392
|
* 下也就是这一个用户的行。指令由 lib/client-state-store.js 的
|
|
@@ -6924,8 +7118,9 @@ function createClientStateHandler(ctx) {
|
|
|
6924
7118
|
if (typeof db.upsertClientState !== "function") {
|
|
6925
7119
|
return err3(501, "CLIENT_STATE_NOT_SUPPORTED", "\u5F53\u524D\u6570\u636E\u5E93\u9002\u914D\u5668\u4E0D\u652F\u6301 client_state");
|
|
6926
7120
|
}
|
|
6927
|
-
const { upserted, skipped, deleted, skippedEntries } = await writeClientStateEntries({ db, userId, userKey, entries: accepted });
|
|
7121
|
+
const { upserted, skipped, deleted, skippedEntries, unknown } = await writeClientStateEntries({ db, userId, userKey, entries: accepted });
|
|
6928
7122
|
const data = { upserted, skipped };
|
|
7123
|
+
if (unknown > 0) data.unknown = unknown;
|
|
6929
7124
|
if (deleted > 0) data.deleted = deleted;
|
|
6930
7125
|
if (skippedEntries.length > 0) data.skippedEntries = skippedEntries;
|
|
6931
7126
|
if (rejected.length > 0) data.rejected = rejected;
|
|
@@ -6938,7 +7133,12 @@ function createClientStateHandler(ctx) {
|
|
|
6938
7133
|
const gate = requireUserId(headers);
|
|
6939
7134
|
if (gate.error) return gate.error;
|
|
6940
7135
|
const { userId } = gate;
|
|
6941
|
-
const
|
|
7136
|
+
const query = new URL(url, "https://dummy").searchParams;
|
|
7137
|
+
const namespace = query.get("namespace") || "";
|
|
7138
|
+
const key = query.get("key");
|
|
7139
|
+
if (key !== null && (!key.trim() || key.length > MAX_KEY_CHARS || INTERNAL_STATE_CHAR_RE.test(key))) {
|
|
7140
|
+
return err3(400, "INVALID_STATE_KEY", "key must be non-empty, at most 256 characters, without control characters");
|
|
7141
|
+
}
|
|
6942
7142
|
if (!namespace.trim()) return err3(400, "NAMESPACE_REQUIRED", "\u5FC5\u987B\u63D0\u4F9B namespace \u67E5\u8BE2\u53C2\u6570");
|
|
6943
7143
|
if (INTERNAL_STATE_CHAR_RE.test(namespace)) {
|
|
6944
7144
|
return err3(400, "INVALID_STATE_NAMESPACE", "namespace \u4E0D\u80FD\u5305\u542B\u63A7\u5236\u5B57\u7B26\uFF08\\u0000-\\u001f \u4E3A\u5E93\u5185\u90E8\u4FDD\u7559\uFF09");
|
|
@@ -6947,10 +7147,10 @@ function createClientStateHandler(ctx) {
|
|
|
6947
7147
|
return err3(501, "CLIENT_STATE_NOT_SUPPORTED", "\u5F53\u524D\u6570\u636E\u5E93\u9002\u914D\u5668\u4E0D\u652F\u6301 client_state");
|
|
6948
7148
|
}
|
|
6949
7149
|
const userKey = await deriveUserEncryptionKey(userId, masterKey);
|
|
6950
|
-
const rows = await db.getClientState(userId, namespace);
|
|
7150
|
+
const rows = await db.getClientState(userId, namespace, key === null ? void 0 : { key });
|
|
6951
7151
|
const decrypted = await resolveClientStateEntries(
|
|
6952
|
-
rows,
|
|
6953
|
-
() => db.getClientState(userId, chunkNamespaceFor(namespace)),
|
|
7152
|
+
key === null ? rows : rows.filter((row) => row.key === key),
|
|
7153
|
+
() => db.getClientState(userId, chunkNamespaceFor(namespace), key === null ? void 0 : { keyPrefix: chunkKeyPrefixFor(key) }),
|
|
6954
7154
|
(value) => decryptFromStorage(value, userKey)
|
|
6955
7155
|
);
|
|
6956
7156
|
const encryptedResponse = await encryptPayload({ namespace, entries: decrypted }, userKey);
|
|
@@ -7028,7 +7228,7 @@ function createClientStateNamespacesHandler(ctx) {
|
|
|
7028
7228
|
}
|
|
7029
7229
|
|
|
7030
7230
|
// src/server/lib/version.js
|
|
7031
|
-
var SERVER_VERSION = true ? "3.0.
|
|
7231
|
+
var SERVER_VERSION = true ? "3.1.0-next.1" : "0.0.0-dev";
|
|
7032
7232
|
|
|
7033
7233
|
// src/server/handlers/capabilities.js
|
|
7034
7234
|
var SERVER_FEATURES = Object.freeze([
|
|
@@ -7144,7 +7344,12 @@ var SERVER_FEATURES = Object.freeze([
|
|
|
7144
7344
|
"max-generation-retries",
|
|
7145
7345
|
// onBeforeFire 认 { defer: { afterMs } }:这次不生成,过一会儿再来问(不占重试
|
|
7146
7346
|
// 次数);onFireSettled 的 status 多一种 'deferred',带 retryAfter。
|
|
7147
|
-
"before-fire-defer"
|
|
7347
|
+
"before-fire-defer",
|
|
7348
|
+
"task-input",
|
|
7349
|
+
"client-state-key",
|
|
7350
|
+
"agentic-list-tasks",
|
|
7351
|
+
"agentic-tool-allowlist",
|
|
7352
|
+
"attachments"
|
|
7148
7353
|
]);
|
|
7149
7354
|
function createCapabilitiesHandler(ctx) {
|
|
7150
7355
|
async function GET(url, headers) {
|
|
@@ -7191,6 +7396,39 @@ function objectOrText(value) {
|
|
|
7191
7396
|
return value;
|
|
7192
7397
|
}
|
|
7193
7398
|
}
|
|
7399
|
+
function utf8ByteLength(text) {
|
|
7400
|
+
if (typeof text !== "string" || text.length === 0) return 0;
|
|
7401
|
+
return /[^\x00-\x7f]/.test(text) ? encoder.encode(text).length : text.length;
|
|
7402
|
+
}
|
|
7403
|
+
function rowByteSize(row) {
|
|
7404
|
+
if (typeof row.byte_size === "number") return row.byte_size;
|
|
7405
|
+
return utf8ByteLength(
|
|
7406
|
+
String(
|
|
7407
|
+
_nullishCoalesce(_nullishCoalesce(_nullishCoalesce(_nullishCoalesce(_nullishCoalesce(row.value, () => ( row.encrypted_payload)), () => ( row.encrypted_value)), () => ( row.payload)), () => ( row.subscription)), () => ( ""))
|
|
7408
|
+
)
|
|
7409
|
+
);
|
|
7410
|
+
}
|
|
7411
|
+
var STORAGE_PREFIX_RE = /^[0-9a-f]{32}:[0-9a-f]{24,32}:[0-9a-f]*$/i;
|
|
7412
|
+
var HEX_RE = /^[0-9a-f]*$/i;
|
|
7413
|
+
function isValidStorageCiphertext(fullText, prefix, suffix, byteSize) {
|
|
7414
|
+
if (typeof fullText === "string") {
|
|
7415
|
+
if (!STORAGE_PREFIX_RE.test(fullText)) return false;
|
|
7416
|
+
const secondColon = fullText.indexOf(":", 33);
|
|
7417
|
+
return secondColon > 32 && (fullText.length - secondColon - 1) % 2 === 0;
|
|
7418
|
+
}
|
|
7419
|
+
if (typeof prefix === "string" && typeof byteSize === "number") {
|
|
7420
|
+
if (!STORAGE_PREFIX_RE.test(prefix)) return false;
|
|
7421
|
+
if (typeof suffix === "string" && !HEX_RE.test(suffix)) return false;
|
|
7422
|
+
const secondColon = prefix.indexOf(":", 33);
|
|
7423
|
+
return secondColon > 32 && (byteSize - secondColon - 1) % 2 === 0;
|
|
7424
|
+
}
|
|
7425
|
+
return false;
|
|
7426
|
+
}
|
|
7427
|
+
function hasCompleteStoredMetadata(stored) {
|
|
7428
|
+
return Boolean(
|
|
7429
|
+
stored && safeOwner(stored.owner) !== null && typeof stored.kind === "string" && stored.kind.length > 0
|
|
7430
|
+
);
|
|
7431
|
+
}
|
|
7194
7432
|
async function buildCloudDataInventory({
|
|
7195
7433
|
db,
|
|
7196
7434
|
userId,
|
|
@@ -7203,6 +7441,20 @@ async function buildCloudDataInventory({
|
|
|
7203
7441
|
const metadata = new Map(
|
|
7204
7442
|
(raw.metadata || []).map((row) => [row.resource_key, row.encrypted_value])
|
|
7205
7443
|
);
|
|
7444
|
+
const readStoredMetadata = async (metadataKey) => {
|
|
7445
|
+
const encryptedMetadata = metadata.get(metadataKey) || null;
|
|
7446
|
+
if (!encryptedMetadata) {
|
|
7447
|
+
return { encryptedMetadata: null, stored: null, unreadable: false };
|
|
7448
|
+
}
|
|
7449
|
+
try {
|
|
7450
|
+
const stored = JSON.parse(
|
|
7451
|
+
await decryptFromStorage(encryptedMetadata, userKey)
|
|
7452
|
+
);
|
|
7453
|
+
return { encryptedMetadata, stored, unreadable: false };
|
|
7454
|
+
} catch (e6) {
|
|
7455
|
+
return { encryptedMetadata, stored: null, unreadable: true };
|
|
7456
|
+
}
|
|
7457
|
+
};
|
|
7206
7458
|
const taskPayloads = /* @__PURE__ */ new Map();
|
|
7207
7459
|
const taskByUuid = /* @__PURE__ */ new Map();
|
|
7208
7460
|
for (const row of raw.task || []) {
|
|
@@ -7212,7 +7464,7 @@ async function buildCloudDataInventory({
|
|
|
7212
7464
|
);
|
|
7213
7465
|
taskPayloads.set(row.id, payload);
|
|
7214
7466
|
if (row.uuid) taskByUuid.set(row.uuid, payload);
|
|
7215
|
-
} catch (
|
|
7467
|
+
} catch (e7) {
|
|
7216
7468
|
}
|
|
7217
7469
|
}
|
|
7218
7470
|
const tasks = (raw.task || []).filter((row) => taskPayloads.has(row.id)).map((row) => ({ ...taskPayloads.get(row.id), uuid: row.uuid }));
|
|
@@ -7227,23 +7479,19 @@ async function buildCloudDataInventory({
|
|
|
7227
7479
|
status = null,
|
|
7228
7480
|
namespace,
|
|
7229
7481
|
key,
|
|
7230
|
-
credId
|
|
7482
|
+
credId,
|
|
7483
|
+
metadataInfo
|
|
7231
7484
|
}) => {
|
|
7232
7485
|
const metadataKey = JSON.stringify([type, ...identity]);
|
|
7233
7486
|
const id = await cloudDataHash(JSON.stringify([userId, metadataKey]));
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
|
|
7240
|
-
);
|
|
7241
|
-
} catch (e7) {
|
|
7242
|
-
unreadable = true;
|
|
7243
|
-
}
|
|
7244
|
-
}
|
|
7487
|
+
const {
|
|
7488
|
+
encryptedMetadata,
|
|
7489
|
+
stored,
|
|
7490
|
+
unreadable: metadataUnreadable
|
|
7491
|
+
} = await _asyncNullishCoalesce(metadataInfo, async () => ( await readStoredMetadata(metadataKey)));
|
|
7492
|
+
if (metadataUnreadable) unreadable = true;
|
|
7245
7493
|
let resolved = null;
|
|
7246
|
-
if (resolveOwner) {
|
|
7494
|
+
if (resolveOwner && !hasCompleteStoredMetadata(stored)) {
|
|
7247
7495
|
try {
|
|
7248
7496
|
resolved = await resolveOwner({
|
|
7249
7497
|
type,
|
|
@@ -7263,17 +7511,10 @@ async function buildCloudDataInventory({
|
|
|
7263
7511
|
});
|
|
7264
7512
|
}
|
|
7265
7513
|
}
|
|
7266
|
-
const owner = safeOwner(_optionalChain([stored, 'optionalAccess',
|
|
7267
|
-
const kind = _nullishCoalesce(_nullishCoalesce(_nullishCoalesce(_nullishCoalesce(_optionalChain([stored, 'optionalAccess',
|
|
7514
|
+
const owner = safeOwner(_optionalChain([stored, 'optionalAccess', _63 => _63.owner])) || safeOwner(_optionalChain([payload, 'optionalAccess', _64 => _64.__cloudOwnerGuard, 'optionalAccess', _65 => _65.owner])) || safeOwner(_optionalChain([payload, 'optionalAccess', _66 => _66.metadata, 'optionalAccess', _67 => _67.cloudOwner])) || safeOwner(_optionalChain([payload, 'optionalAccess', _68 => _68.metadata, 'optionalAccess', _69 => _69.owner])) || safeOwner(_optionalChain([payload, 'optionalAccess', _70 => _70.owner])) || safeOwner(_optionalChain([task, 'optionalAccess', _71 => _71.__cloudOwnerGuard, 'optionalAccess', _72 => _72.owner])) || safeOwner(_optionalChain([task, 'optionalAccess', _73 => _73.metadata, 'optionalAccess', _74 => _74.cloudOwner])) || safeOwner(_optionalChain([task, 'optionalAccess', _75 => _75.metadata, 'optionalAccess', _76 => _76.owner])) || safeOwner(_optionalChain([task, 'optionalAccess', _77 => _77.owner])) || safeOwner(_optionalChain([resolved, 'optionalAccess', _78 => _78.owner]));
|
|
7515
|
+
const kind = _nullishCoalesce(_nullishCoalesce(_nullishCoalesce(_nullishCoalesce(_optionalChain([stored, 'optionalAccess', _79 => _79.kind]), () => ( _optionalChain([payload, 'optionalAccess', _80 => _80.metadata, 'optionalAccess', _81 => _81.kind]))), () => ( _optionalChain([payload, 'optionalAccess', _82 => _82.kind]))), () => ( _optionalChain([resolved, 'optionalAccess', _83 => _83.kind]))), () => ( null));
|
|
7268
7516
|
const times = rows.map((row) => timestamp(_nullishCoalesce(row.updated_at, () => ( row.created_at)))).filter((time) => time !== null);
|
|
7269
|
-
const byteSize = rows.reduce(
|
|
7270
|
-
(sum, row) => sum + encoder.encode(
|
|
7271
|
-
String(
|
|
7272
|
-
_nullishCoalesce(_nullishCoalesce(_nullishCoalesce(_nullishCoalesce(_nullishCoalesce(row.value, () => ( row.encrypted_payload)), () => ( row.encrypted_value)), () => ( row.payload)), () => ( row.subscription)), () => ( ""))
|
|
7273
|
-
)
|
|
7274
|
-
).length,
|
|
7275
|
-
0
|
|
7276
|
-
);
|
|
7517
|
+
const byteSize = rows.reduce((sum, row) => sum + rowByteSize(row), 0);
|
|
7277
7518
|
const label = type === "state" ? `${namespace}/${key}` : type === "credential" ? credId : type === "task" ? rows[0].uuid || `task row ${rows[0].id}` : type === "outbox" ? rows[0].message_id : "Web Push subscription";
|
|
7278
7519
|
entries.push({
|
|
7279
7520
|
resource: {
|
|
@@ -7328,22 +7569,75 @@ async function buildCloudDataInventory({
|
|
|
7328
7569
|
const slices = chunks.get(group) || [];
|
|
7329
7570
|
chunks.delete(group);
|
|
7330
7571
|
const rows = [row, ...slices.sort((a, b) => a.key.localeCompare(b.key))];
|
|
7331
|
-
|
|
7572
|
+
const metadataKey = JSON.stringify(["state", row.namespace, row.key]);
|
|
7573
|
+
const metadataInfo = await readStoredMetadata(metadataKey);
|
|
7574
|
+
const skipFullDecrypt = !metadataInfo.unreadable && hasCompleteStoredMetadata(metadataInfo.stored);
|
|
7575
|
+
let value, unreadable = metadataInfo.unreadable;
|
|
7332
7576
|
try {
|
|
7333
|
-
const
|
|
7334
|
-
|
|
7335
|
-
|
|
7577
|
+
const rootMarker = _nullishCoalesce(_nullishCoalesce(row.value, () => ( row.value_prefix)), () => ( ""));
|
|
7578
|
+
const count = parseChunkedRootCount(rootMarker);
|
|
7579
|
+
if (count === null) {
|
|
7580
|
+
if (typeof row.value === "string" && (!skipFullDecrypt || row.value.length <= 1024)) {
|
|
7581
|
+
value = await decryptFromStorage(row.value, userKey);
|
|
7582
|
+
} else if (skipFullDecrypt) {
|
|
7583
|
+
if (!isValidStorageCiphertext(
|
|
7584
|
+
row.value,
|
|
7585
|
+
row.value_prefix,
|
|
7586
|
+
row.value_suffix,
|
|
7587
|
+
row.byte_size
|
|
7588
|
+
)) {
|
|
7589
|
+
throw new Error("Invalid state ciphertext");
|
|
7590
|
+
}
|
|
7591
|
+
} else {
|
|
7592
|
+
const fetched = typeof db.getClientState === "function" ? await db.getClientState(userId, row.namespace, { key: row.key }) : [];
|
|
7593
|
+
const rawValue = _optionalChain([fetched, 'optionalAccess', _84 => _84[0], 'optionalAccess', _85 => _85.value]);
|
|
7594
|
+
if (typeof rawValue !== "string") {
|
|
7595
|
+
throw new Error("Missing state value");
|
|
7596
|
+
}
|
|
7597
|
+
value = await decryptFromStorage(rawValue, userKey);
|
|
7598
|
+
}
|
|
7599
|
+
} else {
|
|
7336
7600
|
if (count > slices.length) throw new Error("Missing state chunks");
|
|
7337
|
-
const
|
|
7601
|
+
const sliceByKey = new Map(
|
|
7602
|
+
slices.map((candidate) => [candidate.key, candidate])
|
|
7603
|
+
);
|
|
7604
|
+
const orderedSlices = [];
|
|
7338
7605
|
for (let i = 0; i < count; i++) {
|
|
7339
|
-
const slice =
|
|
7340
|
-
(candidate) => candidate.key === chunkKeyFor(row.key, i)
|
|
7341
|
-
);
|
|
7606
|
+
const slice = sliceByKey.get(chunkKeyFor(row.key, i));
|
|
7342
7607
|
if (!slice || slice.updated_at !== row.updated_at)
|
|
7343
7608
|
throw new Error("Mismatched state chunks");
|
|
7344
|
-
|
|
7609
|
+
if (!isValidStorageCiphertext(
|
|
7610
|
+
slice.value,
|
|
7611
|
+
slice.value_prefix,
|
|
7612
|
+
slice.value_suffix,
|
|
7613
|
+
slice.byte_size
|
|
7614
|
+
)) {
|
|
7615
|
+
throw new Error("Corrupt state chunk");
|
|
7616
|
+
}
|
|
7617
|
+
orderedSlices.push(slice);
|
|
7618
|
+
}
|
|
7619
|
+
if (!skipFullDecrypt) {
|
|
7620
|
+
let fetchedByKey = null;
|
|
7621
|
+
if (orderedSlices.some((slice) => typeof slice.value !== "string")) {
|
|
7622
|
+
const fetchedSlices = typeof db.getClientState === "function" ? await db.getClientState(
|
|
7623
|
+
userId,
|
|
7624
|
+
CHUNK_NAMESPACE_PREFIX + row.namespace,
|
|
7625
|
+
{ keyPrefix: `${row.key}` }
|
|
7626
|
+
) : [];
|
|
7627
|
+
fetchedByKey = new Map(
|
|
7628
|
+
fetchedSlices.map((candidate) => [candidate.key, candidate.value])
|
|
7629
|
+
);
|
|
7630
|
+
}
|
|
7631
|
+
const parts = [];
|
|
7632
|
+
for (const slice of orderedSlices) {
|
|
7633
|
+
const rawSliceValue = typeof slice.value === "string" ? slice.value : _optionalChain([fetchedByKey, 'optionalAccess', _86 => _86.get, 'call', _87 => _87(slice.key)]);
|
|
7634
|
+
if (typeof rawSliceValue !== "string") {
|
|
7635
|
+
throw new Error("Missing state chunk value");
|
|
7636
|
+
}
|
|
7637
|
+
parts.push(await decryptFromStorage(rawSliceValue, userKey));
|
|
7638
|
+
}
|
|
7639
|
+
value = parts.join("");
|
|
7345
7640
|
}
|
|
7346
|
-
value = parts.join("");
|
|
7347
7641
|
}
|
|
7348
7642
|
} catch (e9) {
|
|
7349
7643
|
unreadable = true;
|
|
@@ -7356,7 +7650,8 @@ async function buildCloudDataInventory({
|
|
|
7356
7650
|
rows,
|
|
7357
7651
|
value,
|
|
7358
7652
|
unreadable,
|
|
7359
|
-
status: null
|
|
7653
|
+
status: null,
|
|
7654
|
+
metadataInfo
|
|
7360
7655
|
});
|
|
7361
7656
|
}
|
|
7362
7657
|
for (const [group, rows] of chunks) {
|
|
@@ -7380,9 +7675,29 @@ async function buildCloudDataInventory({
|
|
|
7380
7675
|
});
|
|
7381
7676
|
}
|
|
7382
7677
|
for (const row of raw.outbox || []) {
|
|
7383
|
-
|
|
7678
|
+
const metadataKey = JSON.stringify(["outbox", row.message_id]);
|
|
7679
|
+
const metadataInfo = await readStoredMetadata(metadataKey);
|
|
7680
|
+
const skipFullDecrypt = !metadataInfo.unreadable && hasCompleteStoredMetadata(metadataInfo.stored);
|
|
7681
|
+
let payload, unreadable = metadataInfo.unreadable;
|
|
7384
7682
|
try {
|
|
7385
|
-
payload
|
|
7683
|
+
if (typeof row.payload === "string" && (!skipFullDecrypt || row.payload.length <= 1024)) {
|
|
7684
|
+
payload = objectOrText(await decryptFromStorage(row.payload, userKey));
|
|
7685
|
+
} else if (skipFullDecrypt) {
|
|
7686
|
+
if (!isValidStorageCiphertext(
|
|
7687
|
+
row.payload,
|
|
7688
|
+
row.payload_prefix,
|
|
7689
|
+
row.payload_suffix,
|
|
7690
|
+
row.byte_size
|
|
7691
|
+
)) {
|
|
7692
|
+
throw new Error("Invalid outbox ciphertext");
|
|
7693
|
+
}
|
|
7694
|
+
} else {
|
|
7695
|
+
const rawPayload = typeof db.getOutboxPayload === "function" ? await db.getOutboxPayload(userId, row.message_id) : null;
|
|
7696
|
+
if (typeof rawPayload !== "string") {
|
|
7697
|
+
throw new Error("Missing outbox payload");
|
|
7698
|
+
}
|
|
7699
|
+
payload = objectOrText(await decryptFromStorage(rawPayload, userKey));
|
|
7700
|
+
}
|
|
7386
7701
|
} catch (e10) {
|
|
7387
7702
|
unreadable = true;
|
|
7388
7703
|
}
|
|
@@ -7393,7 +7708,8 @@ async function buildCloudDataInventory({
|
|
|
7393
7708
|
payload,
|
|
7394
7709
|
task: taskByUuid.get(row.task_uuid),
|
|
7395
7710
|
unreadable,
|
|
7396
|
-
status: row.acked_at != null ? "acked" : row.delivered_at != null ? "delivered" : "pending"
|
|
7711
|
+
status: row.acked_at != null ? "acked" : row.delivered_at != null ? "delivered" : "pending",
|
|
7712
|
+
metadataInfo
|
|
7397
7713
|
});
|
|
7398
7714
|
}
|
|
7399
7715
|
for (const row of raw.subscription || []) {
|
|
@@ -7407,7 +7723,7 @@ async function buildCloudDataInventory({
|
|
|
7407
7723
|
const ownerLabels = /* @__PURE__ */ new Map();
|
|
7408
7724
|
for (const { resource } of entries) {
|
|
7409
7725
|
const owner = resource.owner;
|
|
7410
|
-
if (!_optionalChain([owner, 'optionalAccess',
|
|
7726
|
+
if (!_optionalChain([owner, 'optionalAccess', _88 => _88.label, 'optionalAccess', _89 => _89.trim, 'call', _90 => _90()])) continue;
|
|
7411
7727
|
const identity = JSON.stringify([owner.type, owner.id]);
|
|
7412
7728
|
const current = ownerLabels.get(identity);
|
|
7413
7729
|
if (!current || (_nullishCoalesce(resource.updatedAt, () => ( 0))) > current.updatedAt) {
|
|
@@ -7419,7 +7735,7 @@ async function buildCloudDataInventory({
|
|
|
7419
7735
|
}
|
|
7420
7736
|
for (const { resource } of entries) {
|
|
7421
7737
|
const owner = resource.owner;
|
|
7422
|
-
if (!owner || _optionalChain([owner, 'access',
|
|
7738
|
+
if (!owner || _optionalChain([owner, 'access', _91 => _91.label, 'optionalAccess', _92 => _92.trim, 'call', _93 => _93()])) continue;
|
|
7423
7739
|
const known = ownerLabels.get(JSON.stringify([owner.type, owner.id]));
|
|
7424
7740
|
if (known) resource.owner = { ...owner, label: known.label };
|
|
7425
7741
|
}
|
|
@@ -7447,7 +7763,7 @@ async function cloudInventory(ctx, db, userId, userKey) {
|
|
|
7447
7763
|
db,
|
|
7448
7764
|
userId,
|
|
7449
7765
|
userKey,
|
|
7450
|
-
resolveOwner: _optionalChain([ctx, 'access',
|
|
7766
|
+
resolveOwner: _optionalChain([ctx, 'access', _94 => _94.cloudData, 'optionalAccess', _95 => _95.resolveOwner])
|
|
7451
7767
|
});
|
|
7452
7768
|
}
|
|
7453
7769
|
function operationWork(operation) {
|
|
@@ -7504,7 +7820,7 @@ async function makeCleanupPlan(ctx, db, userId, key, input) {
|
|
|
7504
7820
|
throw cloudError("INVALID_CLEANUP_SELECTION", "\u8BF7\u9009\u62E9\u81F3\u5C11\u4E00\u9879\u8D44\u6E90");
|
|
7505
7821
|
if (types !== void 0 && (!Array.isArray(types) || !types.length || types.some((type) => !CLOUD_RESOURCE_TYPES.includes(type))))
|
|
7506
7822
|
throw cloudError("INVALID_CLEANUP_SELECTION", "\u8D44\u6E90\u7C7B\u522B\u65E0\u6548");
|
|
7507
|
-
if (mode === "purge" && !owner && !_optionalChain([resourceIds, 'optionalAccess',
|
|
7823
|
+
if (mode === "purge" && !owner && !_optionalChain([resourceIds, 'optionalAccess', _96 => _96.length]))
|
|
7508
7824
|
throw cloudError("INVALID_CLEANUP_SELECTION", "\u6E05\u7406\u9700\u8981\u660E\u786E\u8D44\u6E90\u6216\u5F52\u5C5E");
|
|
7509
7825
|
if (mode === "retire-owner" && (resourceIds || types))
|
|
7510
7826
|
throw cloudError("INVALID_CLEANUP_SELECTION", "\u5F7B\u5E95\u79FB\u9664\u4E0D\u80FD\u9650\u5236\u8D44\u6E90\u7C7B\u522B");
|
|
@@ -7516,10 +7832,12 @@ async function makeCleanupPlan(ctx, db, userId, key, input) {
|
|
|
7516
7832
|
"\u4E91\u7AEF\u6E05\u5355\u4E0D\u5B8C\u6574\uFF0C\u6682\u4E0D\u80FD\u751F\u6210\u6E05\u7406\u8BA1\u5212",
|
|
7517
7833
|
409
|
|
7518
7834
|
);
|
|
7835
|
+
const resourceIdSet = resourceIds ? new Set(resourceIds) : null;
|
|
7836
|
+
const typeSet = types ? new Set(types) : null;
|
|
7519
7837
|
const entries = inventory.entries.filter(
|
|
7520
|
-
(e) => (!owner || ownerEquals(e.resource.owner, owner)) && (!
|
|
7838
|
+
(e) => (!owner || ownerEquals(e.resource.owner, owner)) && (!resourceIdSet || resourceIdSet.has(e.resource.id)) && (!typeSet || typeSet.has(e.resource.type))
|
|
7521
7839
|
);
|
|
7522
|
-
if (
|
|
7840
|
+
if (resourceIdSet && resourceIdSet.size !== entries.length)
|
|
7523
7841
|
throw cloudError(
|
|
7524
7842
|
"CLOUD_RESOURCE_NOT_FOUND",
|
|
7525
7843
|
"\u90E8\u5206\u9009\u4E2D\u8D44\u6E90\u4E0D\u5B58\u5728\u6216\u4E0D\u5C5E\u4E8E\u5F53\u524D\u8303\u56F4",
|
|
@@ -7677,6 +7995,7 @@ async function advanceCleanup(ctx, db, userId, key, operation) {
|
|
|
7677
7995
|
if (operation.status === "completed" || operation.status === "failed")
|
|
7678
7996
|
return operation;
|
|
7679
7997
|
operation.status = "running";
|
|
7998
|
+
let retirementInventory = null;
|
|
7680
7999
|
if (operation.mode === "retire-owner") {
|
|
7681
8000
|
if (operation.fencedGeneration == null) {
|
|
7682
8001
|
const state = await db.setCloudOwnerActive(
|
|
@@ -7704,6 +8023,7 @@ async function advanceCleanup(ctx, db, userId, key, operation) {
|
|
|
7704
8023
|
"\u65E0\u6CD5\u786E\u8BA4\u5F85\u6E05\u7406\u8D44\u6E90",
|
|
7705
8024
|
503
|
|
7706
8025
|
);
|
|
8026
|
+
retirementInventory = inventory;
|
|
7707
8027
|
const known = new Set(operation.knownResourceIds || []);
|
|
7708
8028
|
operation.knownResourceVersions ||= {};
|
|
7709
8029
|
operation.entries = inventory.entries.filter((entry) => {
|
|
@@ -7730,7 +8050,7 @@ async function advanceCleanup(ctx, db, userId, key, operation) {
|
|
|
7730
8050
|
).length;
|
|
7731
8051
|
await save(db, userId, key, "operation", operation, { leaseToken });
|
|
7732
8052
|
}
|
|
7733
|
-
const beforeBatch = await cloudInventory(ctx, db, userId, key);
|
|
8053
|
+
const beforeBatch = await _asyncNullishCoalesce(retirementInventory, async () => ( await cloudInventory(ctx, db, userId, key)));
|
|
7734
8054
|
if (!beforeBatch.complete)
|
|
7735
8055
|
throw cloudError("CLOUD_INVENTORY_INCOMPLETE", "\u65E0\u6CD5\u786E\u8BA4\u5F85\u6E05\u7406\u8D44\u6E90", 503);
|
|
7736
8056
|
const currentById = new Map(
|
|
@@ -7768,7 +8088,6 @@ async function advanceCleanup(ctx, db, userId, key, operation) {
|
|
|
7768
8088
|
delete entry.locator;
|
|
7769
8089
|
operation.cursor++;
|
|
7770
8090
|
operation.updatedAt = Date.now();
|
|
7771
|
-
await save(db, userId, key, "operation", operation, { leaseToken });
|
|
7772
8091
|
}
|
|
7773
8092
|
await renewLease();
|
|
7774
8093
|
const current = await cloudInventory(ctx, db, userId, key);
|
|
@@ -7779,8 +8098,9 @@ async function advanceCleanup(ctx, db, userId, key, operation) {
|
|
|
7779
8098
|
503
|
|
7780
8099
|
);
|
|
7781
8100
|
const selected = new Set(operation.entries.map((e) => e.resource.id));
|
|
8101
|
+
const knownIds = new Set(operation.knownResourceIds || []);
|
|
7782
8102
|
const remaining = current.entries.filter(
|
|
7783
|
-
(e) => operation.mode === "retire-owner" ? ownerEquals(e.resource.owner, operation.owner) ||
|
|
8103
|
+
(e) => operation.mode === "retire-owner" ? ownerEquals(e.resource.owner, operation.owner) || knownIds.has(e.resource.id) : selected.has(e.resource.id)
|
|
7784
8104
|
);
|
|
7785
8105
|
for (const count of operation.counts)
|
|
7786
8106
|
count.remaining = remaining.filter(
|
|
@@ -7869,12 +8189,12 @@ async function repairLegacyWorkIndex(ctx) {
|
|
|
7869
8189
|
entries.push({ userId: row.user_id, kind: row.kind, id: row.id, work, writeToken: row.write_token });
|
|
7870
8190
|
}
|
|
7871
8191
|
await db.indexCloudDataRecordWorkBatch(entries);
|
|
7872
|
-
const cursor = _nullishCoalesce(_optionalChain([batch, 'access',
|
|
8192
|
+
const cursor = _nullishCoalesce(_optionalChain([batch, 'access', _97 => _97.rows, 'access', _98 => _98.at, 'call', _99 => _99(-1), 'optionalAccess', _100 => _100.maintenance_rowid]), () => ( batch.upperBound));
|
|
7873
8193
|
await db.finishCloudMaintenanceBatch(name, cursor, batch.rows.length < 100 || cursor >= batch.upperBound);
|
|
7874
8194
|
}
|
|
7875
8195
|
async function resumeCloudDataCleanups(ctx) {
|
|
7876
8196
|
const db = ctx.db;
|
|
7877
|
-
if (!_optionalChain([db, 'optionalAccess',
|
|
8197
|
+
if (!_optionalChain([db, 'optionalAccess', _101 => _101.cloudDataManagement])) return;
|
|
7878
8198
|
const indexed = WORK_INDEX_METHODS.every((method) => typeof db[method] === "function");
|
|
7879
8199
|
let rows;
|
|
7880
8200
|
if (indexed) {
|
|
@@ -7906,7 +8226,7 @@ async function resumeCloudDataCleanups(ctx) {
|
|
|
7906
8226
|
}
|
|
7907
8227
|
} catch (error) {
|
|
7908
8228
|
console.warn("[amsg] A cloud cleanup operation could not be resumed");
|
|
7909
|
-
if (!indexed || _optionalChain([error, 'optionalAccess',
|
|
8229
|
+
if (!indexed || _optionalChain([error, 'optionalAccess', _102 => _102.code]) === "CLOUD_LEASE_LOST") continue;
|
|
7910
8230
|
await db.rescheduleCloudDataRecordWork(
|
|
7911
8231
|
row.userId,
|
|
7912
8232
|
"operation",
|
|
@@ -8014,7 +8334,7 @@ function createCloudDataHandler(ctx) {
|
|
|
8014
8334
|
summary: inventorySummary(inventory),
|
|
8015
8335
|
expiresAt: Date.now() + 15 * 60 * 1e3,
|
|
8016
8336
|
resources: inventory.entries.map((e) => e.resource).filter(
|
|
8017
|
-
(r) => (!type || r.type === type) && (!ownerId || _optionalChain([r, 'access',
|
|
8337
|
+
(r) => (!type || r.type === type) && (!ownerId || _optionalChain([r, 'access', _103 => _103.owner, 'optionalAccess', _104 => _104.type]) === ownerType && r.owner.id === ownerId)
|
|
8018
8338
|
),
|
|
8019
8339
|
complete: inventory.complete,
|
|
8020
8340
|
gaps: inventory.gaps
|
|
@@ -8097,7 +8417,7 @@ function createCloudDataHandler(ctx) {
|
|
|
8097
8417
|
const rows = await db.listCloudDataRecords(userId, "operation");
|
|
8098
8418
|
for (const row of rows) {
|
|
8099
8419
|
const op = JSON.parse(await decryptFromStorage(row.data, key));
|
|
8100
|
-
if (op.mode === "retire-owner" && _optionalChain([op, 'access',
|
|
8420
|
+
if (op.mode === "retire-owner" && _optionalChain([op, 'access', _105 => _105.owner, 'optionalAccess', _106 => _106.type]) === owner.type && _optionalChain([op, 'access', _107 => _107.owner, 'optionalAccess', _108 => _108.id]) === owner.id && ["pending", "running"].includes(op.status))
|
|
8101
8421
|
throw cloudError(
|
|
8102
8422
|
"CLOUD_CLEANUP_IN_PROGRESS",
|
|
8103
8423
|
"\u6E05\u7406\u5B8C\u6210\u540E\u624D\u80FD\u91CD\u65B0\u542F\u7528",
|
|
@@ -8315,7 +8635,7 @@ function createSingleUserServer(config) {
|
|
|
8315
8635
|
if (!config.masterKey) throw new Error("[amsg-server single-user] config.masterKey is required");
|
|
8316
8636
|
const vapid = config.vapid || {};
|
|
8317
8637
|
const tenantManager = createSingleUserContextManager({
|
|
8318
|
-
db: createCloudGuardedAdapter(config.db, { masterKey: config.masterKey, resolveOwner: _optionalChain([config, 'access',
|
|
8638
|
+
db: createCloudGuardedAdapter(config.db, { masterKey: config.masterKey, resolveOwner: _optionalChain([config, 'access', _109 => _109.cloudData, 'optionalAccess', _110 => _110.resolveOwner]) }),
|
|
8319
8639
|
masterKey: config.masterKey,
|
|
8320
8640
|
serverToken: config.serverToken
|
|
8321
8641
|
});
|
|
@@ -8401,7 +8721,7 @@ async function getSchemaVersion(db) {
|
|
|
8401
8721
|
for (const index of SQLITE_REQUIRED_SCHEMA.indexes) {
|
|
8402
8722
|
if (!liveIndexes.has(index)) missing.push(`index:${index}`);
|
|
8403
8723
|
}
|
|
8404
|
-
if (Array.isArray(_optionalChain([live, 'optionalAccess',
|
|
8724
|
+
if (Array.isArray(_optionalChain([live, 'optionalAccess', _111 => _111.triggers]))) {
|
|
8405
8725
|
const liveTriggers = new Set(live.triggers);
|
|
8406
8726
|
for (const name of SQLITE_REQUIRED_SCHEMA.triggers) {
|
|
8407
8727
|
if (!liveTriggers.has(name)) missing.push(`trigger:${name}`);
|
|
@@ -8781,4 +9101,5 @@ function createSingleUserCloudflareWorker(buildConfig, options = {}) {
|
|
|
8781
9101
|
|
|
8782
9102
|
|
|
8783
9103
|
|
|
8784
|
-
|
|
9104
|
+
|
|
9105
|
+
exports.isValidTimeZoneId = isValidTimeZoneId; exports.advanceOccurrence = advanceOccurrence; exports.planNextOccurrence = planNextOccurrence; exports.nextFutureOccurrence = nextFutureOccurrence; exports.base64UrlToBytes = _amsgshared.base64UrlToBytes; exports.utf8 = _amsgshared.utf8; exports.utf8Decode = _amsgshared.utf8Decode; exports.bytesToBase64Url = _amsgshared.bytesToBase64Url; exports.timingSafeEqualBytes = _amsgshared.timingSafeEqualBytes; exports.deriveUserEncryptionKey = deriveUserEncryptionKey; exports.decryptPayload = decryptPayload; exports.encryptPayload = encryptPayload; exports.encryptForStorage = encryptForStorage; exports.decryptFromStorage = decryptFromStorage; exports.CRED_ID_MAX_LENGTH = CRED_ID_MAX_LENGTH; exports.CRED_PUT_BATCH_MAX = CRED_PUT_BATCH_MAX; exports.CRED_ROWS_PER_USER_MAX = CRED_ROWS_PER_USER_MAX; exports.CRED_REFS_MAX_ENTRIES = CRED_REFS_MAX_ENTRIES; exports.supportsLlmCredentialsStore = supportsLlmCredentialsStore; exports.isValidCredId = isValidCredId; exports.validateCredValue = validateCredValue; exports.validateCredRefs = validateCredRefs; exports.hasChatCredRef = hasChatCredRef; exports.resolveLlmCredential = resolveLlmCredential; exports.isValidUrl = _amsgshared.isValidUrl; exports.validateAvatarUrl = _amsgshared.validateAvatarUrl; exports.isValidISO8601 = isValidISO8601; exports.isValidUUID = isValidUUID; exports.isValidUUIDv4 = isValidUUIDv4; exports.validateSplitPattern = validateSplitPattern; exports.validateLlmMessagesArray = validateLlmMessagesArray; exports.MAX_TASK_PAYLOAD_BYTES = MAX_TASK_PAYLOAD_BYTES; exports.validateScheduleMessagePayload = validateScheduleMessagePayload; exports.DEFAULT_MAX_REQUEST_BODY_BYTES = DEFAULT_MAX_REQUEST_BODY_BYTES; exports.readRequestBody = readRequestBody; exports.parseJsonBody = parseJsonBody; exports.getHeader = getHeader; exports.createGetUserKeyHandler = createGetUserKeyHandler; exports.NonRetryableError = NonRetryableError; exports.isNonRetryableError = isNonRetryableError; exports.DeploymentConfigError = DeploymentConfigError; exports.sanitizeErrorSummary = sanitizeErrorSummary; exports.summarizeErrorCause = summarizeErrorCause; exports.DEFAULT_MAX_DELIVERY_RETRIES = DEFAULT_MAX_DELIVERY_RETRIES; exports.WEB_PUSH_MAX_BODY_BYTES = WEB_PUSH_MAX_BODY_BYTES; exports.WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES = WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES; exports.MAX_PUSH_PAYLOAD_BYTES = MAX_PUSH_PAYLOAD_BYTES; exports.PUSH_ENVELOPE_RESERVED_BYTES = PUSH_ENVELOPE_RESERVED_BYTES; exports.measurePushPayload = measurePushPayload; exports.createWebCryptoWebPush = createWebCryptoWebPush; exports.DEFAULT_MAX_TOOL_ITERATIONS = DEFAULT_MAX_TOOL_ITERATIONS; exports.DEFAULT_TOTAL_TIMEOUT_MS = DEFAULT_TOTAL_TIMEOUT_MS; exports.DEFAULT_MAX_SCHEDULED_TASKS_PER_FIRE = DEFAULT_MAX_SCHEDULED_TASKS_PER_FIRE; exports.MIN_SCHEDULE_LEAD_MS = MIN_SCHEDULE_LEAD_MS; exports.MAX_DEFER_AFTER_MS = MAX_DEFER_AFTER_MS; exports.createScheduleMessageHandler = createScheduleMessageHandler; exports.DEFAULT_CLAIM_LEASE_MS = DEFAULT_CLAIM_LEASE_MS; exports.DEFAULT_LEASE_HEARTBEAT_MS = DEFAULT_LEASE_HEARTBEAT_MS; exports.DEFAULT_HEARTBEAT_LEASE_TTL_MS = DEFAULT_HEARTBEAT_LEASE_TTL_MS; exports.runScheduledTick = runScheduledTick; exports.runTask = runTask; exports.createUpdateMessageHandler = createUpdateMessageHandler; exports.createCancelMessageHandler = createCancelMessageHandler; exports.createMessagesHandler = createMessagesHandler; exports.createGetMessageHandler = createGetMessageHandler; exports.createPushSubscriptionHandler = createPushSubscriptionHandler; exports.createLlmCredentialsHandler = createLlmCredentialsHandler; exports.createD1Adapter = createD1Adapter; exports.createSingleUserServer = createSingleUserServer; exports.SCHEMA_VERSION = SCHEMA_VERSION; exports.getSchemaVersion = getSchemaVersion; exports.ensureSchema = ensureSchema; exports.CORS_ALLOW_HEADERS = CORS_ALLOW_HEADERS; exports.CORS_ALLOW_METHODS = CORS_ALLOW_METHODS; exports.createSingleUserCloudflareWorker = createSingleUserCloudflareWorker;
|