@rei-standard/amsg-server 2.6.0-next.23 → 2.6.0-next.24

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 CHANGED
@@ -224,6 +224,8 @@ createSingleUserCloudflareWorker((env) => ({ …, multipart })); // CF Worker
224
224
 
225
225
  不配 = 两边都用默认值。发送节奏也按 `ttlMs` 排:片数多时自动收紧每片之间的间隔,保证整批分片在这个窗口内发完;收紧到下限还装不下就一片都不发。
226
226
 
227
+ `maxChunkBytes` 只用来**收窄**,有上限:每片原文经 base64url 膨胀 4/3、再套上分片信封后,必须仍装得进单条 push 的明文上限(约 3993 字节),换算下来原文一片最多约 2800 字节。配超了服务端在投递时抛 `MULTIPART_CHUNK_BYTES_TOO_LARGE` 的配置错误(错误信息带当前配置下允许的最大值),不会切出一批推送服务收不了的分片。
228
+
227
229
  两边对不上的下场值得记一下:页面把 `maxChunks` 收窄到 32、服务端还按 128 切的话,分片到了接收端会被逐片拒收;节奏排得比窗口长的话,迟到的分片会被当过期丢掉。两种都是「页面上这条思考过程直接没有」,而服务端那边每一片都发成功、看不出任何异常。
228
230
 
229
231
  ### 思考过程没送到时的可见性
@@ -328,6 +330,7 @@ ctx / metadata / push 上)。
328
330
  - `metadata` 是整体替换,不深合并——只改一个子字段的读-改-写流程见上一节。
329
331
  - `avatarUrl` 显式传 `null` 是「不改」而不是「清空」(§6.2 的软清空策略要求非法头像被摘掉时保留旧头像,「摘掉」和「传了个 null」在这一层是同一件事)。
330
332
  - 凭据三件套传 `null` 同样只是忽略:清掉任何一个,任务到点就发不出去。
333
+ - 凭据三件套只对**没存 `credRefs.chat`** 的任务是「凭据刷新」入口。任务已经通过 `credRefs.chat` 引用凭据的话,触发时以凭据表那行为准、内联只是表行缺失的兜底,改内联不会生效——这种组合返回 `409 TASK_USES_CRED_REFS`,换 Key 走 `PUT /llm-credentials` 覆盖对应凭据,或在请求里改用 `credRefs` 指向新凭据。
331
334
  - `credRefs` 是整体替换(语义同 `metadata`),同样做存在性检查;与内联三件套在同一个请求里混着传返回 `400`。给存量内联任务补 `credRefs` 时不动已存的三件套——那份留作 fire 时表行缺失的兜底。
332
335
  - `pushSubscription` 不收(`400 PUSH_SUBSCRIPTION_NOT_ACCEPTED`),它是用户级的一份,走 `PUT /push-subscription`。
333
336
  - `userMessage` 给了就必须是字符串(口径与排程时一致):它到点要过正则切分,别的类型收进来只会在投递时炸。
@@ -372,6 +372,19 @@ export class D1Adapter {
372
372
  * @returns {Promise<number>} 删掉的行数
373
373
  */
374
374
  discardOutboxMessages(userId: string, messageIds: string[]): Promise<number>;
375
+ /**
376
+ * 把某条任务名下还没发出去的行一次删掉(取消 / 顶替时的 outbox 清理)。
377
+ *
378
+ * 判据与 discardOutboxMessages 相同:只删 delivered_at 与 acked_at 均为
379
+ * NULL 的行——已经推给设备 / 已 ack 的照旧留着。按 task_uuid 直删是为了不
380
+ * 受未 ack 积压量的影响:靠翻页扫描挑行的话,积压一大这条任务的行就落在
381
+ * 扫描上限之外(见 lib/outbox-store.js 的 discardUndeliveredPushesForTask)。
382
+ *
383
+ * @param {string} userId
384
+ * @param {string} taskUuid
385
+ * @returns {Promise<number>} 删掉的行数
386
+ */
387
+ discardUndeliveredOutboxForTask(userId: string, taskUuid: string): Promise<number>;
375
388
  /**
376
389
  * 未 ack 的行(id 升序,游标翻页)。payload 仍是密文,解密在 handler。
377
390
  *
@@ -280,11 +280,20 @@ export type DbAdapter = {
280
280
  * 把它们补收回去。不实现 → 取消只挡住 Web Push 这一路。
281
281
  */
282
282
  discardOutboxMessages?: (userId: string, messageIds: string[]) => Promise<number>;
283
+ /**
284
+ * (可选;单用户/D1)按 task_uuid 一次删掉该任务名下还没发出去的行
285
+ * (delivered_at 与 acked_at 均为 NULL)。取消 / 顶替的 outbox 清理优先走
286
+ * 它:一个来回,且不受未 ack 积压量的影响。不实现 → 退回
287
+ * listUnackedOutbox + discardOutboxMessages 的翻页扫描,扫描有行数上限
288
+ * (见 lib/outbox-store.js),积压超过上限时会漏撤并打日志。
289
+ */
290
+ discardUndeliveredOutboxForTask?: (userId: string, taskUuid: string) => Promise<number>;
283
291
  /**
284
292
  * (可选;单用户/D1)未 ack 的行,id 升序游标翻页(GET /outbox)。行上要带
285
- * `task_uuid` 和 `delivered_at`:`DELETE /message` 和 supersedesUuid 顶替这两
286
- * 条路靠翻这份名单找出该任务名下还没发出去的行(没有按 task_uuid 查的读法),
287
- * 缺任一字段就挑不出来,那两条路上的 outbox 清理会静默跳过。
293
+ * `task_uuid` 和 `delivered_at`:适配器没实现 discardUndeliveredOutboxForTask
294
+ * 时,`DELETE /message` 和 supersedesUuid 顶替这两条路靠翻这份名单找出该任务
295
+ * 名下还没发出去的行,缺任一字段就挑不出来,那两条路上的 outbox 清理会静默
296
+ * 跳过。
288
297
  */
289
298
  listUnackedOutbox?: (userId: string, sinceId: number, limit: number) => Promise<Array<any>>;
290
299
  /**
@@ -1265,15 +1265,27 @@ async function discardUndeliveredPushes({ db, userId, pushes, sentIds }) {
1265
1265
  var OUTBOX_SCAN_PAGE_SIZE = 100;
1266
1266
  var OUTBOX_SCAN_MAX_ROWS = 5e3;
1267
1267
  async function discardUndeliveredPushesForTask({ db, userId, taskUuid }) {
1268
- if (!db || typeof db.listUnackedOutbox !== "function" || typeof db.discardOutboxMessages !== "function") return;
1269
- if (!taskUuid) return;
1268
+ if (!db || !taskUuid) return;
1269
+ if (typeof db.discardUndeliveredOutboxForTask === "function") {
1270
+ try {
1271
+ await db.discardUndeliveredOutboxForTask(userId, taskUuid);
1272
+ } catch (error) {
1273
+ console.warn("[amsg-server] outbox \u6309\u4EFB\u52A1\u64A4\u56DE\u672A\u6295\u9012\u7684\u884C\u5931\u8D25\uFF08\u5DF2\u5FFD\u7565\uFF09:", error && error.message);
1274
+ }
1275
+ return;
1276
+ }
1277
+ if (typeof db.listUnackedOutbox !== "function" || typeof db.discardOutboxMessages !== "function") return;
1270
1278
  const messageIds = [];
1279
+ let exhausted = false;
1271
1280
  try {
1272
1281
  let cursor = 0;
1273
1282
  let scanned = 0;
1274
1283
  while (scanned < OUTBOX_SCAN_MAX_ROWS) {
1275
1284
  const rows = await db.listUnackedOutbox(userId, cursor, OUTBOX_SCAN_PAGE_SIZE);
1276
- if (!rows || rows.length === 0) break;
1285
+ if (!rows || rows.length === 0) {
1286
+ exhausted = true;
1287
+ break;
1288
+ }
1277
1289
  scanned += rows.length;
1278
1290
  let nextCursor = cursor;
1279
1291
  for (const row of rows) {
@@ -1284,12 +1296,20 @@ async function discardUndeliveredPushesForTask({ db, userId, taskUuid }) {
1284
1296
  }
1285
1297
  if (nextCursor <= cursor) break;
1286
1298
  cursor = nextCursor;
1287
- if (rows.length < OUTBOX_SCAN_PAGE_SIZE) break;
1299
+ if (rows.length < OUTBOX_SCAN_PAGE_SIZE) {
1300
+ exhausted = true;
1301
+ break;
1302
+ }
1288
1303
  }
1289
1304
  } catch (error) {
1290
1305
  console.warn("[amsg-server] outbox \u67E5\u672A\u6295\u9012\u7684\u884C\u5931\u8D25\uFF08\u5DF2\u5FFD\u7565\uFF09:", error && error.message);
1291
1306
  return;
1292
1307
  }
1308
+ if (!exhausted) {
1309
+ console.warn(
1310
+ `[amsg-server] outbox \u626B\u63CF\u5230 ${OUTBOX_SCAN_MAX_ROWS} \u884C\u4E0A\u9650\u4ECD\u672A\u626B\u5B8C\uFF0C\u4EFB\u52A1 ${taskUuid} \u53EF\u80FD\u8FD8\u6709\u672A\u6295\u9012\u7684\u884C\u6CA1\u64A4\u6389\uFF08\u88AB\u53D6\u6D88\u4EFB\u52A1\u7684\u884C\u901A\u5E38\u662F\u6700\u65B0\u7684\uFF0C\u6B63\u597D\u5728\u4E0A\u9650\u4E4B\u5916\uFF09\u3002\u7ED9\u9002\u914D\u5668\u5B9E\u73B0 discardUndeliveredOutboxForTask \u53EF\u7ED5\u5F00\u8FD9\u4E2A\u4E0A\u9650\u3002`
1311
+ );
1312
+ }
1293
1313
  await discardPushesFromOutbox({ db, userId, messageIds });
1294
1314
  }
1295
1315
 
@@ -1352,10 +1372,17 @@ function createResultEmitter({
1352
1372
  sessionId,
1353
1373
  occurrenceMs,
1354
1374
  webpush,
1355
- now
1375
+ now,
1376
+ isCancelled
1356
1377
  }) {
1357
1378
  const nowFn = typeof now === "function" ? now : Date.now;
1358
1379
  let emitted = 0;
1380
+ const assertNotCancelled = () => {
1381
+ if (typeof isCancelled !== "function" || !isCancelled()) return;
1382
+ const error = new Error("\u4EFB\u52A1\u5728\u6295\u9012\u671F\u95F4\u88AB\u53D6\u6D88\u6216\u9876\u66FF\uFF0C\u7ED3\u679C\u5DF2\u4E2D\u6B62");
1383
+ error.code = TASK_CANCELLED_CODE;
1384
+ throw error;
1385
+ };
1359
1386
  const emitResult = async (payload) => {
1360
1387
  if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
1361
1388
  throw new TypeError("emitResult(payload) \u9700\u8981\u4E00\u4E2A\u666E\u901A\u5BF9\u8C61");
@@ -1367,9 +1394,10 @@ function createResultEmitter({
1367
1394
  );
1368
1395
  }
1369
1396
  const seq = emitted++;
1397
+ const messageType = decryptedPayload.messageType || "auto";
1370
1398
  const push = buildResultPush({
1371
- messageType: decryptedPayload.messageType || "auto",
1372
- source: "scheduled",
1399
+ messageType,
1400
+ source: messageType === "instant" ? "instant" : "scheduled",
1373
1401
  messageId: `${messageIdBase}_result_${seq}`,
1374
1402
  sessionId,
1375
1403
  ...payload,
@@ -1383,9 +1411,11 @@ function createResultEmitter({
1383
1411
  recurrenceType: decryptedPayload.recurrenceType || "none",
1384
1412
  occurrenceMs
1385
1413
  });
1414
+ assertNotCancelled();
1386
1415
  await db.appendOutboxMessages(task.user_id, await toOutboxRows([push], userKey, nowFn()));
1387
1416
  let pushed;
1388
1417
  try {
1418
+ assertNotCancelled();
1389
1419
  pushed = shouldSendPush(push, { outboxed: true }) ? await sendResultPush({ db, task, userKey, decryptedPayload, webpush, push }) : false;
1390
1420
  } catch (error) {
1391
1421
  await discardUndeliveredPushes({ db, userId: task.user_id, pushes: [push], sentIds: [] });
@@ -1585,7 +1615,10 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
1585
1615
  sessionId,
1586
1616
  occurrenceMs,
1587
1617
  webpush: ctx.webpush,
1588
- now: nowFn
1618
+ now: nowFn,
1619
+ // run-tick 在投递 ctx 上挂的取消信号(与 guardWebpushWithLease 读的是同一
1620
+ // 个租约状态),emitResult 不发推送的那条路要靠它拦下已取消任务的落行。
1621
+ isCancelled: typeof ctx.isTaskCancelled === "function" ? ctx.isTaskCancelled : null
1589
1622
  });
1590
1623
  const maxScheduledTasksPerFire = Number.isInteger(ctx.maxScheduledTasksPerFire) && ctx.maxScheduledTasksPerFire >= 0 ? ctx.maxScheduledTasksPerFire : DEFAULT_MAX_SCHEDULED_TASKS_PER_FIRE;
1591
1624
  let scheduledTaskCount = 0;
@@ -1771,6 +1804,9 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
1771
1804
  );
1772
1805
  }
1773
1806
  const cancelled = await ctx.db.deleteTaskByUuid(uuid, task.user_id);
1807
+ if (cancelled) {
1808
+ await discardUndeliveredPushesForTask({ db: ctx.db, userId: task.user_id, taskUuid: uuid });
1809
+ }
1774
1810
  return { cancelled: !!cancelled };
1775
1811
  };
1776
1812
  const renewTask = async (uuid, nextSendAt) => {
@@ -2240,12 +2276,36 @@ function sleepFor(ctx, ms) {
2240
2276
  }
2241
2277
  function resolveMultipartOptions(ctx) {
2242
2278
  const configured = ctx && ctx.multipart && typeof ctx.multipart === "object" ? ctx.multipart : {};
2243
- return {
2279
+ const resolved = {
2244
2280
  maxChunkBytes: positiveIntegerOr(configured.maxChunkBytes, DEFAULT_MULTIPART_CHUNK_BYTES),
2245
2281
  maxChunks: positiveIntegerOr(configured.maxChunks, DEFAULT_MULTIPART_MAX_CHUNKS),
2246
2282
  maxTotalBytes: positiveIntegerOr(configured.maxTotalBytes, DEFAULT_MULTIPART_MAX_TOTAL_BYTES),
2247
2283
  ttlMs: positiveIntegerOr(configured.ttlMs, DEFAULT_MULTIPART_TTL_MS)
2248
2284
  };
2285
+ assertChunkBytesFitPushLimit(resolved);
2286
+ return resolved;
2287
+ }
2288
+ function assertChunkBytesFitPushLimit({ maxChunkBytes, maxChunks, ttlMs }) {
2289
+ const PROBE_CHUNK_BYTES = 3;
2290
+ const [probe] = buildMultipartPushPayloads(
2291
+ { messageKind: "reasoning" },
2292
+ { serializedPayload: "x".repeat(PROBE_CHUNK_BYTES), maxChunkBytes: PROBE_CHUNK_BYTES, ttlMs }
2293
+ );
2294
+ const digitHeadroom = 2 * (String(maxChunks).length - 1);
2295
+ const envelopeOverhead = measurePushPayload(JSON.stringify(probe)).bytes - base64UrlLength(PROBE_CHUNK_BYTES) + digitHeadroom;
2296
+ const worstEnvelopeBytes = envelopeOverhead + base64UrlLength(maxChunkBytes);
2297
+ if (worstEnvelopeBytes <= MAX_PUSH_PAYLOAD_BYTES) return;
2298
+ let maxAllowed = Math.floor((MAX_PUSH_PAYLOAD_BYTES - envelopeOverhead) * 3 / 4);
2299
+ while (maxAllowed > 0 && envelopeOverhead + base64UrlLength(maxAllowed) > MAX_PUSH_PAYLOAD_BYTES) {
2300
+ maxAllowed--;
2301
+ }
2302
+ throw new DeploymentConfigError(
2303
+ `MULTIPART_CHUNK_BYTES_TOO_LARGE: multipart.maxChunkBytes = ${maxChunkBytes} \u5207\u51FA\u7684\u5206\u7247\u4FE1\u5C01\u6700\u574F ${worstEnvelopeBytes} \u5B57\u8282\uFF0C\u8D85\u8FC7\u5355\u6761 push \u660E\u6587\u4E0A\u9650 ${MAX_PUSH_PAYLOAD_BYTES} \u5B57\u8282\uFF0C\u6BCF\u4E00\u7247\u90FD\u4F1A\u88AB\u63A8\u9001\u670D\u52A1\u62D2\u6536\u3002\u8FD9\u4E2A\u65CB\u94AE\u53EA\u7528\u4E8E\u6536\u7A84\uFF0C\u5F53\u524D\u914D\u7F6E\u4E0B\u6700\u5927 ${maxAllowed}`,
2304
+ { code: "MULTIPART_CHUNK_BYTES_TOO_LARGE" }
2305
+ );
2306
+ }
2307
+ function base64UrlLength(n) {
2308
+ return Math.ceil(n * 4 / 3);
2249
2309
  }
2250
2310
  function positiveIntegerOr(value, fallback) {
2251
2311
  return Number.isInteger(value) && /** @type {number} */
@@ -3003,13 +3063,14 @@ async function deliverTasks(ctx, tasks) {
3003
3063
  groupsTakenThisTick.add(scopedKey);
3004
3064
  return { taken: false, rawKey };
3005
3065
  }
3006
- function recordCancelled(task, status) {
3066
+ async function recordCancelled(task, status) {
3007
3067
  results.cancelledTasks.push({
3008
3068
  taskId: task.id,
3009
3069
  reason: "\u4EFB\u52A1\u5728\u6295\u9012\u671F\u95F4\u88AB\u53D6\u6D88\u6216\u9876\u66FF",
3010
3070
  status
3011
3071
  });
3012
3072
  console.warn(`[amsg-server] \u4EFB\u52A1 ${task.id} \u5728\u6295\u9012\u671F\u95F4\u88AB\u53D6\u6D88\u6216\u9876\u66FF\uFF08${status}\uFF09`);
3073
+ await discardUndeliveredPushesForTask({ db, userId: task.user_id, taskUuid: task.uuid });
3013
3074
  }
3014
3075
  async function payloadStillFresh(task) {
3015
3076
  if (typeof db.getTaskByUuid !== "function" || !task.uuid) return true;
@@ -3191,7 +3252,8 @@ async function deliverTasks(ctx, tasks) {
3191
3252
  messageIdBase: task.id != null ? `msg_task_${task.id}${occurrenceSuffix(task)}` : `msg_stale_${task.uuid || ""}`,
3192
3253
  sessionId: task.id != null ? `sess_task_${task.id}${occurrenceSuffix(task)}` : `sess_stale_${task.uuid || ""}`,
3193
3254
  occurrenceMs,
3194
- webpush: ctx.webpush
3255
+ webpush: ctx.webpush,
3256
+ isCancelled: () => lease.lost
3195
3257
  });
3196
3258
  const recurring = isRecurringType(recurrenceType);
3197
3259
  const plan = recurring ? planNextOccurrence(occurrenceMs, recurrenceType, Date.now(), tzId) : null;
@@ -3240,13 +3302,19 @@ async function deliverTasks(ctx, tasks) {
3240
3302
  try {
3241
3303
  sendResult = await processSingleMessage(
3242
3304
  task,
3243
- { ...ctx, db, masterKey, webpush: guardWebpushWithLease(ctx.webpush, lease) },
3305
+ {
3306
+ ...ctx,
3307
+ db,
3308
+ masterKey,
3309
+ webpush: guardWebpushWithLease(ctx.webpush, lease),
3310
+ isTaskCancelled: () => lease.lost
3311
+ },
3244
3312
  masterKey,
3245
3313
  { userKey, payload: decryptedPayload }
3246
3314
  );
3247
3315
  } catch (error) {
3248
3316
  if (lease.lost) {
3249
- recordCancelled(task, "cancelled_mid_delivery");
3317
+ await recordCancelled(task, "cancelled_mid_delivery");
3250
3318
  return;
3251
3319
  }
3252
3320
  await handleDeliveryFailure(
@@ -3261,7 +3329,7 @@ async function deliverTasks(ctx, tasks) {
3261
3329
  }
3262
3330
  if (!sendResult.success) {
3263
3331
  if (lease.lost) {
3264
- recordCancelled(task, "cancelled_mid_delivery");
3332
+ await recordCancelled(task, "cancelled_mid_delivery");
3265
3333
  return;
3266
3334
  }
3267
3335
  await handleDeliveryFailure(
@@ -3284,7 +3352,7 @@ async function deliverTasks(ctx, tasks) {
3284
3352
  if (recurrenceType === "none") {
3285
3353
  markLeaseReleased(task.id);
3286
3354
  if (rowVanished(await db.deleteTaskById(task.id))) {
3287
- recordCancelled(task, "cancelled_after_delivery");
3355
+ await recordCancelled(task, "cancelled_after_delivery");
3288
3356
  return;
3289
3357
  }
3290
3358
  results.deletedOnceOffTasks++;
@@ -3298,7 +3366,7 @@ async function deliverTasks(ctx, tasks) {
3298
3366
  ...clearedPayload ? { encrypted_payload: clearedPayload } : {}
3299
3367
  });
3300
3368
  if (rowVanished(updated)) {
3301
- recordCancelled(task, "cancelled_after_delivery");
3369
+ await recordCancelled(task, "cancelled_after_delivery");
3302
3370
  return;
3303
3371
  }
3304
3372
  results.updatedRecurringTasks++;
@@ -3484,6 +3552,13 @@ function createUpdateMessageHandler(ctx) {
3484
3552
  return { status: 409, body: { success: false, error: { code: "TASK_ALREADY_COMPLETED", message: "\u4EFB\u52A1\u5DF2\u5B8C\u6210\u6216\u5DF2\u5931\u8D25\uFF0C\u65E0\u6CD5\u66F4\u65B0" } } };
3485
3553
  }
3486
3554
  const existingData = JSON.parse(await decryptFromStorage(existingTask.encrypted_payload, userKey));
3555
+ if ((updates.apiUrl || updates.apiKey || updates.primaryModel) && hasChatCredRef(existingData)) {
3556
+ return { status: 409, body: { success: false, error: {
3557
+ code: "TASK_USES_CRED_REFS",
3558
+ message: "\u4EFB\u52A1\u5DF2\u901A\u8FC7 credRefs.chat \u5F15\u7528\u51ED\u636E\uFF0C\u89E6\u53D1\u65F6\u4EE5\u51ED\u636E\u8868\u4E3A\u51C6\uFF0C\u5185\u8054 apiUrl / apiKey / primaryModel \u7684\u66F4\u65B0\u4E0D\u4F1A\u751F\u6548\u3002\u6362 Key \u8BF7\u7528 PUT /llm-credentials \u8986\u76D6\u5BF9\u5E94\u51ED\u636E\uFF0C\u6216\u5728\u672C\u6B21\u8BF7\u6C42\u91CC\u6539\u7528 credRefs \u6307\u5411\u65B0\u51ED\u636E",
3559
+ details: { invalidFields: ["apiUrl", "apiKey", "primaryModel"].filter((name) => updates[name]) }
3560
+ } } };
3561
+ }
3487
3562
  const promptUpdates = {};
3488
3563
  if (updates.completePrompt) {
3489
3564
  promptUpdates.completePrompt = updates.completePrompt;
@@ -4896,6 +4971,25 @@ var D1Adapter = class {
4896
4971
  messageIds
4897
4972
  );
4898
4973
  }
4974
+ /**
4975
+ * 把某条任务名下还没发出去的行一次删掉(取消 / 顶替时的 outbox 清理)。
4976
+ *
4977
+ * 判据与 discardOutboxMessages 相同:只删 delivered_at 与 acked_at 均为
4978
+ * NULL 的行——已经推给设备 / 已 ack 的照旧留着。按 task_uuid 直删是为了不
4979
+ * 受未 ack 积压量的影响:靠翻页扫描挑行的话,积压一大这条任务的行就落在
4980
+ * 扫描上限之外(见 lib/outbox-store.js 的 discardUndeliveredPushesForTask)。
4981
+ *
4982
+ * @param {string} userId
4983
+ * @param {string} taskUuid
4984
+ * @returns {Promise<number>} 删掉的行数
4985
+ */
4986
+ async discardUndeliveredOutboxForTask(userId, taskUuid) {
4987
+ const res = await this._db.prepare(
4988
+ `DELETE FROM message_outbox
4989
+ WHERE user_id = ? AND task_uuid = ? AND delivered_at IS NULL AND acked_at IS NULL`
4990
+ ).bind(userId, taskUuid).run();
4991
+ return res.meta.changes || 0;
4992
+ }
4899
4993
  /**
4900
4994
  * 未 ack 的行(id 升序,游标翻页)。payload 仍是密文,解密在 handler。
4901
4995
  *
@@ -5213,7 +5307,7 @@ function createClientStateHandler(ctx) {
5213
5307
  }
5214
5308
 
5215
5309
  // src/server/lib/version.js
5216
- var SERVER_VERSION = true ? "2.6.0-next.23" : "0.0.0-dev";
5310
+ var SERVER_VERSION = true ? "2.6.0-next.24" : "0.0.0-dev";
5217
5311
 
5218
5312
  // src/server/handlers/capabilities.js
5219
5313
  var SERVER_FEATURES = Object.freeze([
@@ -1265,15 +1265,27 @@ async function discardUndeliveredPushes({ db, userId, pushes, sentIds }) {
1265
1265
  var OUTBOX_SCAN_PAGE_SIZE = 100;
1266
1266
  var OUTBOX_SCAN_MAX_ROWS = 5e3;
1267
1267
  async function discardUndeliveredPushesForTask({ db, userId, taskUuid }) {
1268
- if (!db || typeof db.listUnackedOutbox !== "function" || typeof db.discardOutboxMessages !== "function") return;
1269
- if (!taskUuid) return;
1268
+ if (!db || !taskUuid) return;
1269
+ if (typeof db.discardUndeliveredOutboxForTask === "function") {
1270
+ try {
1271
+ await db.discardUndeliveredOutboxForTask(userId, taskUuid);
1272
+ } catch (error) {
1273
+ console.warn("[amsg-server] outbox \u6309\u4EFB\u52A1\u64A4\u56DE\u672A\u6295\u9012\u7684\u884C\u5931\u8D25\uFF08\u5DF2\u5FFD\u7565\uFF09:", error && error.message);
1274
+ }
1275
+ return;
1276
+ }
1277
+ if (typeof db.listUnackedOutbox !== "function" || typeof db.discardOutboxMessages !== "function") return;
1270
1278
  const messageIds = [];
1279
+ let exhausted = false;
1271
1280
  try {
1272
1281
  let cursor = 0;
1273
1282
  let scanned = 0;
1274
1283
  while (scanned < OUTBOX_SCAN_MAX_ROWS) {
1275
1284
  const rows = await db.listUnackedOutbox(userId, cursor, OUTBOX_SCAN_PAGE_SIZE);
1276
- if (!rows || rows.length === 0) break;
1285
+ if (!rows || rows.length === 0) {
1286
+ exhausted = true;
1287
+ break;
1288
+ }
1277
1289
  scanned += rows.length;
1278
1290
  let nextCursor = cursor;
1279
1291
  for (const row of rows) {
@@ -1284,12 +1296,20 @@ async function discardUndeliveredPushesForTask({ db, userId, taskUuid }) {
1284
1296
  }
1285
1297
  if (nextCursor <= cursor) break;
1286
1298
  cursor = nextCursor;
1287
- if (rows.length < OUTBOX_SCAN_PAGE_SIZE) break;
1299
+ if (rows.length < OUTBOX_SCAN_PAGE_SIZE) {
1300
+ exhausted = true;
1301
+ break;
1302
+ }
1288
1303
  }
1289
1304
  } catch (error) {
1290
1305
  console.warn("[amsg-server] outbox \u67E5\u672A\u6295\u9012\u7684\u884C\u5931\u8D25\uFF08\u5DF2\u5FFD\u7565\uFF09:", error && error.message);
1291
1306
  return;
1292
1307
  }
1308
+ if (!exhausted) {
1309
+ console.warn(
1310
+ `[amsg-server] outbox \u626B\u63CF\u5230 ${OUTBOX_SCAN_MAX_ROWS} \u884C\u4E0A\u9650\u4ECD\u672A\u626B\u5B8C\uFF0C\u4EFB\u52A1 ${taskUuid} \u53EF\u80FD\u8FD8\u6709\u672A\u6295\u9012\u7684\u884C\u6CA1\u64A4\u6389\uFF08\u88AB\u53D6\u6D88\u4EFB\u52A1\u7684\u884C\u901A\u5E38\u662F\u6700\u65B0\u7684\uFF0C\u6B63\u597D\u5728\u4E0A\u9650\u4E4B\u5916\uFF09\u3002\u7ED9\u9002\u914D\u5668\u5B9E\u73B0 discardUndeliveredOutboxForTask \u53EF\u7ED5\u5F00\u8FD9\u4E2A\u4E0A\u9650\u3002`
1311
+ );
1312
+ }
1293
1313
  await discardPushesFromOutbox({ db, userId, messageIds });
1294
1314
  }
1295
1315
 
@@ -1352,10 +1372,17 @@ function createResultEmitter({
1352
1372
  sessionId,
1353
1373
  occurrenceMs,
1354
1374
  webpush,
1355
- now
1375
+ now,
1376
+ isCancelled
1356
1377
  }) {
1357
1378
  const nowFn = typeof now === "function" ? now : Date.now;
1358
1379
  let emitted = 0;
1380
+ const assertNotCancelled = () => {
1381
+ if (typeof isCancelled !== "function" || !isCancelled()) return;
1382
+ const error = new Error("\u4EFB\u52A1\u5728\u6295\u9012\u671F\u95F4\u88AB\u53D6\u6D88\u6216\u9876\u66FF\uFF0C\u7ED3\u679C\u5DF2\u4E2D\u6B62");
1383
+ error.code = TASK_CANCELLED_CODE;
1384
+ throw error;
1385
+ };
1359
1386
  const emitResult = async (payload) => {
1360
1387
  if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
1361
1388
  throw new TypeError("emitResult(payload) \u9700\u8981\u4E00\u4E2A\u666E\u901A\u5BF9\u8C61");
@@ -1367,9 +1394,10 @@ function createResultEmitter({
1367
1394
  );
1368
1395
  }
1369
1396
  const seq = emitted++;
1397
+ const messageType = decryptedPayload.messageType || "auto";
1370
1398
  const push = _amsgshared.buildResultPush.call(void 0, {
1371
- messageType: decryptedPayload.messageType || "auto",
1372
- source: "scheduled",
1399
+ messageType,
1400
+ source: messageType === "instant" ? "instant" : "scheduled",
1373
1401
  messageId: `${messageIdBase}_result_${seq}`,
1374
1402
  sessionId,
1375
1403
  ...payload,
@@ -1383,9 +1411,11 @@ function createResultEmitter({
1383
1411
  recurrenceType: decryptedPayload.recurrenceType || "none",
1384
1412
  occurrenceMs
1385
1413
  });
1414
+ assertNotCancelled();
1386
1415
  await db.appendOutboxMessages(task.user_id, await toOutboxRows([push], userKey, nowFn()));
1387
1416
  let pushed;
1388
1417
  try {
1418
+ assertNotCancelled();
1389
1419
  pushed = shouldSendPush(push, { outboxed: true }) ? await sendResultPush({ db, task, userKey, decryptedPayload, webpush, push }) : false;
1390
1420
  } catch (error) {
1391
1421
  await discardUndeliveredPushes({ db, userId: task.user_id, pushes: [push], sentIds: [] });
@@ -1585,7 +1615,10 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
1585
1615
  sessionId,
1586
1616
  occurrenceMs,
1587
1617
  webpush: ctx.webpush,
1588
- now: nowFn
1618
+ now: nowFn,
1619
+ // run-tick 在投递 ctx 上挂的取消信号(与 guardWebpushWithLease 读的是同一
1620
+ // 个租约状态),emitResult 不发推送的那条路要靠它拦下已取消任务的落行。
1621
+ isCancelled: typeof ctx.isTaskCancelled === "function" ? ctx.isTaskCancelled : null
1589
1622
  });
1590
1623
  const maxScheduledTasksPerFire = Number.isInteger(ctx.maxScheduledTasksPerFire) && ctx.maxScheduledTasksPerFire >= 0 ? ctx.maxScheduledTasksPerFire : DEFAULT_MAX_SCHEDULED_TASKS_PER_FIRE;
1591
1624
  let scheduledTaskCount = 0;
@@ -1771,6 +1804,9 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
1771
1804
  );
1772
1805
  }
1773
1806
  const cancelled = await ctx.db.deleteTaskByUuid(uuid, task.user_id);
1807
+ if (cancelled) {
1808
+ await discardUndeliveredPushesForTask({ db: ctx.db, userId: task.user_id, taskUuid: uuid });
1809
+ }
1774
1810
  return { cancelled: !!cancelled };
1775
1811
  };
1776
1812
  const renewTask = async (uuid, nextSendAt) => {
@@ -2240,12 +2276,36 @@ function sleepFor(ctx, ms) {
2240
2276
  }
2241
2277
  function resolveMultipartOptions(ctx) {
2242
2278
  const configured = ctx && ctx.multipart && typeof ctx.multipart === "object" ? ctx.multipart : {};
2243
- return {
2279
+ const resolved = {
2244
2280
  maxChunkBytes: positiveIntegerOr(configured.maxChunkBytes, _amsgshared.DEFAULT_MULTIPART_CHUNK_BYTES),
2245
2281
  maxChunks: positiveIntegerOr(configured.maxChunks, _amsgshared.DEFAULT_MULTIPART_MAX_CHUNKS),
2246
2282
  maxTotalBytes: positiveIntegerOr(configured.maxTotalBytes, _amsgshared.DEFAULT_MULTIPART_MAX_TOTAL_BYTES),
2247
2283
  ttlMs: positiveIntegerOr(configured.ttlMs, _amsgshared.DEFAULT_MULTIPART_TTL_MS)
2248
2284
  };
2285
+ assertChunkBytesFitPushLimit(resolved);
2286
+ return resolved;
2287
+ }
2288
+ function assertChunkBytesFitPushLimit({ maxChunkBytes, maxChunks, ttlMs }) {
2289
+ const PROBE_CHUNK_BYTES = 3;
2290
+ const [probe] = _amsgshared.buildMultipartPushPayloads.call(void 0,
2291
+ { messageKind: "reasoning" },
2292
+ { serializedPayload: "x".repeat(PROBE_CHUNK_BYTES), maxChunkBytes: PROBE_CHUNK_BYTES, ttlMs }
2293
+ );
2294
+ const digitHeadroom = 2 * (String(maxChunks).length - 1);
2295
+ const envelopeOverhead = measurePushPayload(JSON.stringify(probe)).bytes - base64UrlLength(PROBE_CHUNK_BYTES) + digitHeadroom;
2296
+ const worstEnvelopeBytes = envelopeOverhead + base64UrlLength(maxChunkBytes);
2297
+ if (worstEnvelopeBytes <= MAX_PUSH_PAYLOAD_BYTES) return;
2298
+ let maxAllowed = Math.floor((MAX_PUSH_PAYLOAD_BYTES - envelopeOverhead) * 3 / 4);
2299
+ while (maxAllowed > 0 && envelopeOverhead + base64UrlLength(maxAllowed) > MAX_PUSH_PAYLOAD_BYTES) {
2300
+ maxAllowed--;
2301
+ }
2302
+ throw new DeploymentConfigError(
2303
+ `MULTIPART_CHUNK_BYTES_TOO_LARGE: multipart.maxChunkBytes = ${maxChunkBytes} \u5207\u51FA\u7684\u5206\u7247\u4FE1\u5C01\u6700\u574F ${worstEnvelopeBytes} \u5B57\u8282\uFF0C\u8D85\u8FC7\u5355\u6761 push \u660E\u6587\u4E0A\u9650 ${MAX_PUSH_PAYLOAD_BYTES} \u5B57\u8282\uFF0C\u6BCF\u4E00\u7247\u90FD\u4F1A\u88AB\u63A8\u9001\u670D\u52A1\u62D2\u6536\u3002\u8FD9\u4E2A\u65CB\u94AE\u53EA\u7528\u4E8E\u6536\u7A84\uFF0C\u5F53\u524D\u914D\u7F6E\u4E0B\u6700\u5927 ${maxAllowed}`,
2304
+ { code: "MULTIPART_CHUNK_BYTES_TOO_LARGE" }
2305
+ );
2306
+ }
2307
+ function base64UrlLength(n) {
2308
+ return Math.ceil(n * 4 / 3);
2249
2309
  }
2250
2310
  function positiveIntegerOr(value, fallback) {
2251
2311
  return Number.isInteger(value) && /** @type {number} */
@@ -3003,13 +3063,14 @@ async function deliverTasks(ctx, tasks) {
3003
3063
  groupsTakenThisTick.add(scopedKey);
3004
3064
  return { taken: false, rawKey };
3005
3065
  }
3006
- function recordCancelled(task, status) {
3066
+ async function recordCancelled(task, status) {
3007
3067
  results.cancelledTasks.push({
3008
3068
  taskId: task.id,
3009
3069
  reason: "\u4EFB\u52A1\u5728\u6295\u9012\u671F\u95F4\u88AB\u53D6\u6D88\u6216\u9876\u66FF",
3010
3070
  status
3011
3071
  });
3012
3072
  console.warn(`[amsg-server] \u4EFB\u52A1 ${task.id} \u5728\u6295\u9012\u671F\u95F4\u88AB\u53D6\u6D88\u6216\u9876\u66FF\uFF08${status}\uFF09`);
3073
+ await discardUndeliveredPushesForTask({ db, userId: task.user_id, taskUuid: task.uuid });
3013
3074
  }
3014
3075
  async function payloadStillFresh(task) {
3015
3076
  if (typeof db.getTaskByUuid !== "function" || !task.uuid) return true;
@@ -3191,7 +3252,8 @@ async function deliverTasks(ctx, tasks) {
3191
3252
  messageIdBase: task.id != null ? `msg_task_${task.id}${occurrenceSuffix(task)}` : `msg_stale_${task.uuid || ""}`,
3192
3253
  sessionId: task.id != null ? `sess_task_${task.id}${occurrenceSuffix(task)}` : `sess_stale_${task.uuid || ""}`,
3193
3254
  occurrenceMs,
3194
- webpush: ctx.webpush
3255
+ webpush: ctx.webpush,
3256
+ isCancelled: () => lease.lost
3195
3257
  });
3196
3258
  const recurring = isRecurringType(recurrenceType);
3197
3259
  const plan = recurring ? planNextOccurrence(occurrenceMs, recurrenceType, Date.now(), tzId) : null;
@@ -3240,13 +3302,19 @@ async function deliverTasks(ctx, tasks) {
3240
3302
  try {
3241
3303
  sendResult = await processSingleMessage(
3242
3304
  task,
3243
- { ...ctx, db, masterKey, webpush: guardWebpushWithLease(ctx.webpush, lease) },
3305
+ {
3306
+ ...ctx,
3307
+ db,
3308
+ masterKey,
3309
+ webpush: guardWebpushWithLease(ctx.webpush, lease),
3310
+ isTaskCancelled: () => lease.lost
3311
+ },
3244
3312
  masterKey,
3245
3313
  { userKey, payload: decryptedPayload }
3246
3314
  );
3247
3315
  } catch (error) {
3248
3316
  if (lease.lost) {
3249
- recordCancelled(task, "cancelled_mid_delivery");
3317
+ await recordCancelled(task, "cancelled_mid_delivery");
3250
3318
  return;
3251
3319
  }
3252
3320
  await handleDeliveryFailure(
@@ -3261,7 +3329,7 @@ async function deliverTasks(ctx, tasks) {
3261
3329
  }
3262
3330
  if (!sendResult.success) {
3263
3331
  if (lease.lost) {
3264
- recordCancelled(task, "cancelled_mid_delivery");
3332
+ await recordCancelled(task, "cancelled_mid_delivery");
3265
3333
  return;
3266
3334
  }
3267
3335
  await handleDeliveryFailure(
@@ -3284,7 +3352,7 @@ async function deliverTasks(ctx, tasks) {
3284
3352
  if (recurrenceType === "none") {
3285
3353
  markLeaseReleased(task.id);
3286
3354
  if (rowVanished(await db.deleteTaskById(task.id))) {
3287
- recordCancelled(task, "cancelled_after_delivery");
3355
+ await recordCancelled(task, "cancelled_after_delivery");
3288
3356
  return;
3289
3357
  }
3290
3358
  results.deletedOnceOffTasks++;
@@ -3298,7 +3366,7 @@ async function deliverTasks(ctx, tasks) {
3298
3366
  ...clearedPayload ? { encrypted_payload: clearedPayload } : {}
3299
3367
  });
3300
3368
  if (rowVanished(updated)) {
3301
- recordCancelled(task, "cancelled_after_delivery");
3369
+ await recordCancelled(task, "cancelled_after_delivery");
3302
3370
  return;
3303
3371
  }
3304
3372
  results.updatedRecurringTasks++;
@@ -3484,6 +3552,13 @@ function createUpdateMessageHandler(ctx) {
3484
3552
  return { status: 409, body: { success: false, error: { code: "TASK_ALREADY_COMPLETED", message: "\u4EFB\u52A1\u5DF2\u5B8C\u6210\u6216\u5DF2\u5931\u8D25\uFF0C\u65E0\u6CD5\u66F4\u65B0" } } };
3485
3553
  }
3486
3554
  const existingData = JSON.parse(await decryptFromStorage(existingTask.encrypted_payload, userKey));
3555
+ if ((updates.apiUrl || updates.apiKey || updates.primaryModel) && hasChatCredRef(existingData)) {
3556
+ return { status: 409, body: { success: false, error: {
3557
+ code: "TASK_USES_CRED_REFS",
3558
+ message: "\u4EFB\u52A1\u5DF2\u901A\u8FC7 credRefs.chat \u5F15\u7528\u51ED\u636E\uFF0C\u89E6\u53D1\u65F6\u4EE5\u51ED\u636E\u8868\u4E3A\u51C6\uFF0C\u5185\u8054 apiUrl / apiKey / primaryModel \u7684\u66F4\u65B0\u4E0D\u4F1A\u751F\u6548\u3002\u6362 Key \u8BF7\u7528 PUT /llm-credentials \u8986\u76D6\u5BF9\u5E94\u51ED\u636E\uFF0C\u6216\u5728\u672C\u6B21\u8BF7\u6C42\u91CC\u6539\u7528 credRefs \u6307\u5411\u65B0\u51ED\u636E",
3559
+ details: { invalidFields: ["apiUrl", "apiKey", "primaryModel"].filter((name) => updates[name]) }
3560
+ } } };
3561
+ }
3487
3562
  const promptUpdates = {};
3488
3563
  if (updates.completePrompt) {
3489
3564
  promptUpdates.completePrompt = updates.completePrompt;
@@ -4896,6 +4971,25 @@ var D1Adapter = class {
4896
4971
  messageIds
4897
4972
  );
4898
4973
  }
4974
+ /**
4975
+ * 把某条任务名下还没发出去的行一次删掉(取消 / 顶替时的 outbox 清理)。
4976
+ *
4977
+ * 判据与 discardOutboxMessages 相同:只删 delivered_at 与 acked_at 均为
4978
+ * NULL 的行——已经推给设备 / 已 ack 的照旧留着。按 task_uuid 直删是为了不
4979
+ * 受未 ack 积压量的影响:靠翻页扫描挑行的话,积压一大这条任务的行就落在
4980
+ * 扫描上限之外(见 lib/outbox-store.js 的 discardUndeliveredPushesForTask)。
4981
+ *
4982
+ * @param {string} userId
4983
+ * @param {string} taskUuid
4984
+ * @returns {Promise<number>} 删掉的行数
4985
+ */
4986
+ async discardUndeliveredOutboxForTask(userId, taskUuid) {
4987
+ const res = await this._db.prepare(
4988
+ `DELETE FROM message_outbox
4989
+ WHERE user_id = ? AND task_uuid = ? AND delivered_at IS NULL AND acked_at IS NULL`
4990
+ ).bind(userId, taskUuid).run();
4991
+ return res.meta.changes || 0;
4992
+ }
4899
4993
  /**
4900
4994
  * 未 ack 的行(id 升序,游标翻页)。payload 仍是密文,解密在 handler。
4901
4995
  *
@@ -5213,7 +5307,7 @@ function createClientStateHandler(ctx) {
5213
5307
  }
5214
5308
 
5215
5309
  // src/server/lib/version.js
5216
- var SERVER_VERSION = true ? "2.6.0-next.23" : "0.0.0-dev";
5310
+ var SERVER_VERSION = true ? "2.6.0-next.24" : "0.0.0-dev";
5217
5311
 
5218
5312
  // src/server/handlers/capabilities.js
5219
5313
  var SERVER_FEATURES = Object.freeze([
@@ -26,7 +26,7 @@
26
26
 
27
27
 
28
28
 
29
- var _chunkBNZLICCIcjs = require('./chunk-BNZLICCI.cjs');
29
+ var _chunkXLCBVU37cjs = require('./chunk-XLCBVU37.cjs');
30
30
  require('./chunk-7NMQFTDJ.cjs');
31
31
 
32
32
 
@@ -56,4 +56,4 @@ require('./chunk-7NMQFTDJ.cjs');
56
56
 
57
57
 
58
58
 
59
- exports.DEFAULT_MAX_REQUEST_BODY_BYTES = _chunkBNZLICCIcjs.DEFAULT_MAX_REQUEST_BODY_BYTES; exports.MAX_PUSH_PAYLOAD_BYTES = _chunkBNZLICCIcjs.MAX_PUSH_PAYLOAD_BYTES; exports.NonRetryableError = _chunkBNZLICCIcjs.NonRetryableError; exports.PUSH_ENVELOPE_RESERVED_BYTES = _chunkBNZLICCIcjs.PUSH_ENVELOPE_RESERVED_BYTES; exports.SCHEMA_VERSION = _chunkBNZLICCIcjs.SCHEMA_VERSION; exports.WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES = _chunkBNZLICCIcjs.WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES; exports.WEB_PUSH_MAX_BODY_BYTES = _chunkBNZLICCIcjs.WEB_PUSH_MAX_BODY_BYTES; exports.advanceOccurrence = _chunkBNZLICCIcjs.advanceOccurrence; exports.createD1Adapter = _chunkBNZLICCIcjs.createD1Adapter; exports.createSingleUserCloudflareWorker = _chunkBNZLICCIcjs.createSingleUserCloudflareWorker; exports.createSingleUserServer = _chunkBNZLICCIcjs.createSingleUserServer; exports.createWebCryptoWebPush = _chunkBNZLICCIcjs.createWebCryptoWebPush; exports.decryptFromStorage = _chunkBNZLICCIcjs.decryptFromStorage; exports.decryptPayload = _chunkBNZLICCIcjs.decryptPayload; exports.deriveUserEncryptionKey = _chunkBNZLICCIcjs.deriveUserEncryptionKey; exports.encryptForStorage = _chunkBNZLICCIcjs.encryptForStorage; exports.ensureSchema = _chunkBNZLICCIcjs.ensureSchema; exports.getSchemaVersion = _chunkBNZLICCIcjs.getSchemaVersion; exports.isNonRetryableError = _chunkBNZLICCIcjs.isNonRetryableError; exports.isValidTimeZoneId = _chunkBNZLICCIcjs.isValidTimeZoneId; exports.measurePushPayload = _chunkBNZLICCIcjs.measurePushPayload; exports.nextFutureOccurrence = _chunkBNZLICCIcjs.nextFutureOccurrence; exports.planNextOccurrence = _chunkBNZLICCIcjs.planNextOccurrence; exports.readRequestBody = _chunkBNZLICCIcjs.readRequestBody; exports.runScheduledTick = _chunkBNZLICCIcjs.runScheduledTick; exports.runTask = _chunkBNZLICCIcjs.runTask; exports.summarizeErrorCause = _chunkBNZLICCIcjs.summarizeErrorCause;
59
+ exports.DEFAULT_MAX_REQUEST_BODY_BYTES = _chunkXLCBVU37cjs.DEFAULT_MAX_REQUEST_BODY_BYTES; exports.MAX_PUSH_PAYLOAD_BYTES = _chunkXLCBVU37cjs.MAX_PUSH_PAYLOAD_BYTES; exports.NonRetryableError = _chunkXLCBVU37cjs.NonRetryableError; exports.PUSH_ENVELOPE_RESERVED_BYTES = _chunkXLCBVU37cjs.PUSH_ENVELOPE_RESERVED_BYTES; exports.SCHEMA_VERSION = _chunkXLCBVU37cjs.SCHEMA_VERSION; exports.WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES = _chunkXLCBVU37cjs.WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES; exports.WEB_PUSH_MAX_BODY_BYTES = _chunkXLCBVU37cjs.WEB_PUSH_MAX_BODY_BYTES; exports.advanceOccurrence = _chunkXLCBVU37cjs.advanceOccurrence; exports.createD1Adapter = _chunkXLCBVU37cjs.createD1Adapter; exports.createSingleUserCloudflareWorker = _chunkXLCBVU37cjs.createSingleUserCloudflareWorker; exports.createSingleUserServer = _chunkXLCBVU37cjs.createSingleUserServer; exports.createWebCryptoWebPush = _chunkXLCBVU37cjs.createWebCryptoWebPush; exports.decryptFromStorage = _chunkXLCBVU37cjs.decryptFromStorage; exports.decryptPayload = _chunkXLCBVU37cjs.decryptPayload; exports.deriveUserEncryptionKey = _chunkXLCBVU37cjs.deriveUserEncryptionKey; exports.encryptForStorage = _chunkXLCBVU37cjs.encryptForStorage; exports.ensureSchema = _chunkXLCBVU37cjs.ensureSchema; exports.getSchemaVersion = _chunkXLCBVU37cjs.getSchemaVersion; exports.isNonRetryableError = _chunkXLCBVU37cjs.isNonRetryableError; exports.isValidTimeZoneId = _chunkXLCBVU37cjs.isValidTimeZoneId; exports.measurePushPayload = _chunkXLCBVU37cjs.measurePushPayload; exports.nextFutureOccurrence = _chunkXLCBVU37cjs.nextFutureOccurrence; exports.planNextOccurrence = _chunkXLCBVU37cjs.planNextOccurrence; exports.readRequestBody = _chunkXLCBVU37cjs.readRequestBody; exports.runScheduledTick = _chunkXLCBVU37cjs.runScheduledTick; exports.runTask = _chunkXLCBVU37cjs.runTask; exports.summarizeErrorCause = _chunkXLCBVU37cjs.summarizeErrorCause;
@@ -26,7 +26,7 @@ import {
26
26
  runScheduledTick,
27
27
  runTask,
28
28
  summarizeErrorCause
29
- } from "./chunk-3JEWYDM4.mjs";
29
+ } from "./chunk-N7CZ3KXB.mjs";
30
30
  import "./chunk-GN44PST5.mjs";
31
31
  export {
32
32
  DEFAULT_MAX_REQUEST_BODY_BYTES,
package/dist/index.cjs CHANGED
@@ -71,7 +71,7 @@
71
71
 
72
72
 
73
73
 
74
- var _chunkBNZLICCIcjs = require('./chunk-BNZLICCI.cjs');
74
+ var _chunkXLCBVU37cjs = require('./chunk-XLCBVU37.cjs');
75
75
  require('./chunk-7NMQFTDJ.cjs');
76
76
 
77
77
  // src/server/adapters/factory.js
@@ -105,7 +105,7 @@ async function createAdapter(config) {
105
105
  // src/server/handlers/init-tenant.js
106
106
  function createInitTenantHandler(ctx) {
107
107
  async function POST(headers, body) {
108
- const initSecret = _chunkBNZLICCIcjs.getHeader.call(void 0, headers, "x-init-secret");
108
+ const initSecret = _chunkXLCBVU37cjs.getHeader.call(void 0, headers, "x-init-secret");
109
109
  if (ctx.tenant.initSecret && initSecret !== ctx.tenant.initSecret) {
110
110
  return {
111
111
  status: 401,
@@ -118,7 +118,7 @@ function createInitTenantHandler(ctx) {
118
118
  }
119
119
  };
120
120
  }
121
- const parsed = _chunkBNZLICCIcjs.parseJsonBody.call(void 0, body);
121
+ const parsed = _chunkXLCBVU37cjs.parseJsonBody.call(void 0, body);
122
122
  if (!parsed.ok) {
123
123
  return {
124
124
  status: 400,
@@ -132,7 +132,7 @@ function createInitTenantHandler(ctx) {
132
132
  const tenantId = payload.tenantId || void 0;
133
133
  const driver = payload.driver;
134
134
  const databaseUrl = payload.databaseUrl || payload.connectionString;
135
- if (tenantId && !_chunkBNZLICCIcjs.isValidUUIDv4.call(void 0, tenantId)) {
135
+ if (tenantId && !_chunkXLCBVU37cjs.isValidUUIDv4.call(void 0, tenantId)) {
136
136
  return {
137
137
  status: 400,
138
138
  body: {
@@ -252,7 +252,7 @@ function createSendNotificationsHandler(ctx) {
252
252
  }
253
253
  };
254
254
  }
255
- const data = await _chunkBNZLICCIcjs.runScheduledTick.call(void 0, { ...ctx, db, masterKey });
255
+ const data = await _chunkXLCBVU37cjs.runScheduledTick.call(void 0, { ...ctx, db, masterKey });
256
256
  return { status: 200, body: { success: true, data } };
257
257
  }
258
258
  return { POST };
@@ -274,16 +274,16 @@ function encryptConfig(config, kekBuffer) {
274
274
  const plaintext = Buffer.from(JSON.stringify(config), "utf8");
275
275
  const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
276
276
  const authTag = cipher.getAuthTag();
277
- return `v1.${_chunkBNZLICCIcjs.bytesToBase64Url.call(void 0, iv)}.${_chunkBNZLICCIcjs.bytesToBase64Url.call(void 0, authTag)}.${_chunkBNZLICCIcjs.bytesToBase64Url.call(void 0, ciphertext)}`;
277
+ return `v1.${_chunkXLCBVU37cjs.bytesToBase64Url.call(void 0, iv)}.${_chunkXLCBVU37cjs.bytesToBase64Url.call(void 0, authTag)}.${_chunkXLCBVU37cjs.bytesToBase64Url.call(void 0, ciphertext)}`;
278
278
  }
279
279
  function decryptConfig(encrypted, kekBuffer) {
280
280
  const parts = String(encrypted || "").split(".");
281
281
  if (parts.length !== 4 || parts[0] !== "v1") {
282
282
  throw new Error("INVALID_TENANT_CONFIG");
283
283
  }
284
- const iv = _chunkBNZLICCIcjs.base64UrlToBytes.call(void 0, parts[1]);
285
- const authTag = _chunkBNZLICCIcjs.base64UrlToBytes.call(void 0, parts[2]);
286
- const ciphertext = _chunkBNZLICCIcjs.base64UrlToBytes.call(void 0, parts[3]);
284
+ const iv = _chunkXLCBVU37cjs.base64UrlToBytes.call(void 0, parts[1]);
285
+ const authTag = _chunkXLCBVU37cjs.base64UrlToBytes.call(void 0, parts[2]);
286
+ const ciphertext = _chunkXLCBVU37cjs.base64UrlToBytes.call(void 0, parts[3]);
287
287
  const decipher = _crypto.createDecipheriv.call(void 0, "aes-256-gcm", kekBuffer, iv);
288
288
  decipher.setAuthTag(authTag);
289
289
  const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
@@ -351,7 +351,7 @@ function createTenantBlobStore(options) {
351
351
  // src/server/tenant/token.js
352
352
 
353
353
  function sign(input, secret) {
354
- return _chunkBNZLICCIcjs.bytesToBase64Url.call(void 0, _crypto.createHmac.call(void 0, "sha256", secret).update(input).digest());
354
+ return _chunkXLCBVU37cjs.bytesToBase64Url.call(void 0, _crypto.createHmac.call(void 0, "sha256", secret).update(input).digest());
355
355
  }
356
356
  function nowEpochSeconds() {
357
357
  return Math.floor(Date.now() / 1e3);
@@ -367,8 +367,8 @@ function createTenantToken(params, secret) {
367
367
  v: 1
368
368
  };
369
369
  const header = { alg: "HS256", typ: "JWT" };
370
- const encodedHeader = _chunkBNZLICCIcjs.bytesToBase64Url.call(void 0, _chunkBNZLICCIcjs.utf8.call(void 0, JSON.stringify(header)));
371
- const encodedPayload = _chunkBNZLICCIcjs.bytesToBase64Url.call(void 0, _chunkBNZLICCIcjs.utf8.call(void 0, JSON.stringify(payload)));
370
+ const encodedHeader = _chunkXLCBVU37cjs.bytesToBase64Url.call(void 0, _chunkXLCBVU37cjs.utf8.call(void 0, JSON.stringify(header)));
371
+ const encodedPayload = _chunkXLCBVU37cjs.bytesToBase64Url.call(void 0, _chunkXLCBVU37cjs.utf8.call(void 0, JSON.stringify(payload)));
372
372
  const signingInput = `${encodedHeader}.${encodedPayload}`;
373
373
  const signature = sign(signingInput, secret);
374
374
  return `${signingInput}.${signature}`;
@@ -387,17 +387,17 @@ function verifyTenantToken(token, secret, options = {}) {
387
387
  let receivedBytes;
388
388
  let expectedBytes;
389
389
  try {
390
- receivedBytes = _chunkBNZLICCIcjs.base64UrlToBytes.call(void 0, receivedSignature);
391
- expectedBytes = _chunkBNZLICCIcjs.base64UrlToBytes.call(void 0, expectedSignature);
390
+ receivedBytes = _chunkXLCBVU37cjs.base64UrlToBytes.call(void 0, receivedSignature);
391
+ expectedBytes = _chunkXLCBVU37cjs.base64UrlToBytes.call(void 0, expectedSignature);
392
392
  } catch (e) {
393
393
  throw new Error("INVALID_TENANT_AUTH");
394
394
  }
395
- if (!_chunkBNZLICCIcjs.timingSafeEqualBytes.call(void 0, receivedBytes, expectedBytes)) {
395
+ if (!_chunkXLCBVU37cjs.timingSafeEqualBytes.call(void 0, receivedBytes, expectedBytes)) {
396
396
  throw new Error("INVALID_TENANT_AUTH");
397
397
  }
398
398
  let payload;
399
399
  try {
400
- payload = JSON.parse(_chunkBNZLICCIcjs.utf8Decode.call(void 0, _chunkBNZLICCIcjs.base64UrlToBytes.call(void 0, encodedPayload)));
400
+ payload = JSON.parse(_chunkXLCBVU37cjs.utf8Decode.call(void 0, _chunkXLCBVU37cjs.base64UrlToBytes.call(void 0, encodedPayload)));
401
401
  } catch (e2) {
402
402
  throw new Error("INVALID_TENANT_AUTH");
403
403
  }
@@ -621,15 +621,15 @@ async function createReiServer(config) {
621
621
  return {
622
622
  handlers: {
623
623
  initTenant: createInitTenantHandler(ctx),
624
- getUserKey: _chunkBNZLICCIcjs.createGetUserKeyHandler.call(void 0, ctx),
625
- scheduleMessage: _chunkBNZLICCIcjs.createScheduleMessageHandler.call(void 0, ctx),
624
+ getUserKey: _chunkXLCBVU37cjs.createGetUserKeyHandler.call(void 0, ctx),
625
+ scheduleMessage: _chunkXLCBVU37cjs.createScheduleMessageHandler.call(void 0, ctx),
626
626
  sendNotifications: createSendNotificationsHandler(ctx),
627
- updateMessage: _chunkBNZLICCIcjs.createUpdateMessageHandler.call(void 0, ctx),
628
- cancelMessage: _chunkBNZLICCIcjs.createCancelMessageHandler.call(void 0, ctx),
629
- messages: _chunkBNZLICCIcjs.createMessagesHandler.call(void 0, ctx),
630
- getMessage: _chunkBNZLICCIcjs.createGetMessageHandler.call(void 0, ctx),
631
- pushSubscription: _chunkBNZLICCIcjs.createPushSubscriptionHandler.call(void 0, ctx),
632
- llmCredentials: _chunkBNZLICCIcjs.createLlmCredentialsHandler.call(void 0, ctx)
627
+ updateMessage: _chunkXLCBVU37cjs.createUpdateMessageHandler.call(void 0, ctx),
628
+ cancelMessage: _chunkXLCBVU37cjs.createCancelMessageHandler.call(void 0, ctx),
629
+ messages: _chunkXLCBVU37cjs.createMessagesHandler.call(void 0, ctx),
630
+ getMessage: _chunkXLCBVU37cjs.createGetMessageHandler.call(void 0, ctx),
631
+ pushSubscription: _chunkXLCBVU37cjs.createPushSubscriptionHandler.call(void 0, ctx),
632
+ llmCredentials: _chunkXLCBVU37cjs.createLlmCredentialsHandler.call(void 0, ctx)
633
633
  }
634
634
  };
635
635
  }
@@ -695,4 +695,4 @@ async function createReiServer(config) {
695
695
 
696
696
 
697
697
 
698
- exports.CORS_ALLOW_HEADERS = _chunkBNZLICCIcjs.CORS_ALLOW_HEADERS; exports.CORS_ALLOW_METHODS = _chunkBNZLICCIcjs.CORS_ALLOW_METHODS; exports.CRED_ID_MAX_LENGTH = _chunkBNZLICCIcjs.CRED_ID_MAX_LENGTH; exports.CRED_PUT_BATCH_MAX = _chunkBNZLICCIcjs.CRED_PUT_BATCH_MAX; exports.CRED_REFS_MAX_ENTRIES = _chunkBNZLICCIcjs.CRED_REFS_MAX_ENTRIES; exports.CRED_ROWS_PER_USER_MAX = _chunkBNZLICCIcjs.CRED_ROWS_PER_USER_MAX; exports.DEFAULT_CLAIM_LEASE_MS = _chunkBNZLICCIcjs.DEFAULT_CLAIM_LEASE_MS; exports.DEFAULT_HEARTBEAT_LEASE_TTL_MS = _chunkBNZLICCIcjs.DEFAULT_HEARTBEAT_LEASE_TTL_MS; exports.DEFAULT_LEASE_HEARTBEAT_MS = _chunkBNZLICCIcjs.DEFAULT_LEASE_HEARTBEAT_MS; exports.DEFAULT_MAX_REQUEST_BODY_BYTES = _chunkBNZLICCIcjs.DEFAULT_MAX_REQUEST_BODY_BYTES; exports.DEFAULT_MAX_SCHEDULED_TASKS_PER_FIRE = _chunkBNZLICCIcjs.DEFAULT_MAX_SCHEDULED_TASKS_PER_FIRE; exports.DEFAULT_MAX_TOOL_ITERATIONS = _chunkBNZLICCIcjs.DEFAULT_MAX_TOOL_ITERATIONS; exports.DEFAULT_TOTAL_TIMEOUT_MS = _chunkBNZLICCIcjs.DEFAULT_TOTAL_TIMEOUT_MS; exports.DeploymentConfigError = _chunkBNZLICCIcjs.DeploymentConfigError; exports.MAX_PUSH_PAYLOAD_BYTES = _chunkBNZLICCIcjs.MAX_PUSH_PAYLOAD_BYTES; exports.MAX_TASK_PAYLOAD_BYTES = _chunkBNZLICCIcjs.MAX_TASK_PAYLOAD_BYTES; exports.MIN_SCHEDULE_LEAD_MS = _chunkBNZLICCIcjs.MIN_SCHEDULE_LEAD_MS; exports.NonRetryableError = _chunkBNZLICCIcjs.NonRetryableError; exports.PUSH_ENVELOPE_RESERVED_BYTES = _chunkBNZLICCIcjs.PUSH_ENVELOPE_RESERVED_BYTES; exports.SCHEMA_VERSION = _chunkBNZLICCIcjs.SCHEMA_VERSION; exports.WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES = _chunkBNZLICCIcjs.WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES; exports.WEB_PUSH_MAX_BODY_BYTES = _chunkBNZLICCIcjs.WEB_PUSH_MAX_BODY_BYTES; exports.advanceOccurrence = _chunkBNZLICCIcjs.advanceOccurrence; exports.createAdapter = createAdapter; exports.createD1Adapter = _chunkBNZLICCIcjs.createD1Adapter; exports.createReiServer = createReiServer; exports.createSingleUserCloudflareWorker = _chunkBNZLICCIcjs.createSingleUserCloudflareWorker; exports.createSingleUserServer = _chunkBNZLICCIcjs.createSingleUserServer; exports.createTenantToken = createTenantToken; exports.createWebCryptoWebPush = _chunkBNZLICCIcjs.createWebCryptoWebPush; exports.decryptFromStorage = _chunkBNZLICCIcjs.decryptFromStorage; exports.decryptPayload = _chunkBNZLICCIcjs.decryptPayload; exports.deriveUserEncryptionKey = _chunkBNZLICCIcjs.deriveUserEncryptionKey; exports.encryptForStorage = _chunkBNZLICCIcjs.encryptForStorage; exports.ensureSchema = _chunkBNZLICCIcjs.ensureSchema; exports.getSchemaVersion = _chunkBNZLICCIcjs.getSchemaVersion; exports.hasChatCredRef = _chunkBNZLICCIcjs.hasChatCredRef; exports.isNonRetryableError = _chunkBNZLICCIcjs.isNonRetryableError; exports.isValidCredId = _chunkBNZLICCIcjs.isValidCredId; exports.isValidISO8601 = _chunkBNZLICCIcjs.isValidISO8601; exports.isValidTimeZoneId = _chunkBNZLICCIcjs.isValidTimeZoneId; exports.isValidUUID = _chunkBNZLICCIcjs.isValidUUID; exports.isValidUUIDv4 = _chunkBNZLICCIcjs.isValidUUIDv4; exports.isValidUrl = _chunkBNZLICCIcjs.isValidUrl; exports.measurePushPayload = _chunkBNZLICCIcjs.measurePushPayload; exports.nextFutureOccurrence = _chunkBNZLICCIcjs.nextFutureOccurrence; exports.planNextOccurrence = _chunkBNZLICCIcjs.planNextOccurrence; exports.readRequestBody = _chunkBNZLICCIcjs.readRequestBody; exports.resolveLlmCredential = _chunkBNZLICCIcjs.resolveLlmCredential; exports.runScheduledTick = _chunkBNZLICCIcjs.runScheduledTick; exports.runTask = _chunkBNZLICCIcjs.runTask; exports.sanitizeErrorSummary = _chunkBNZLICCIcjs.sanitizeErrorSummary; exports.summarizeErrorCause = _chunkBNZLICCIcjs.summarizeErrorCause; exports.supportsLlmCredentialsStore = _chunkBNZLICCIcjs.supportsLlmCredentialsStore; exports.validateAvatarUrl = _chunkBNZLICCIcjs.validateAvatarUrl; exports.validateCredRefs = _chunkBNZLICCIcjs.validateCredRefs; exports.validateCredValue = _chunkBNZLICCIcjs.validateCredValue; exports.validateLlmMessagesArray = _chunkBNZLICCIcjs.validateLlmMessagesArray; exports.validateScheduleMessagePayload = _chunkBNZLICCIcjs.validateScheduleMessagePayload; exports.validateSplitPattern = _chunkBNZLICCIcjs.validateSplitPattern; exports.verifyTenantToken = verifyTenantToken;
698
+ exports.CORS_ALLOW_HEADERS = _chunkXLCBVU37cjs.CORS_ALLOW_HEADERS; exports.CORS_ALLOW_METHODS = _chunkXLCBVU37cjs.CORS_ALLOW_METHODS; exports.CRED_ID_MAX_LENGTH = _chunkXLCBVU37cjs.CRED_ID_MAX_LENGTH; exports.CRED_PUT_BATCH_MAX = _chunkXLCBVU37cjs.CRED_PUT_BATCH_MAX; exports.CRED_REFS_MAX_ENTRIES = _chunkXLCBVU37cjs.CRED_REFS_MAX_ENTRIES; exports.CRED_ROWS_PER_USER_MAX = _chunkXLCBVU37cjs.CRED_ROWS_PER_USER_MAX; exports.DEFAULT_CLAIM_LEASE_MS = _chunkXLCBVU37cjs.DEFAULT_CLAIM_LEASE_MS; exports.DEFAULT_HEARTBEAT_LEASE_TTL_MS = _chunkXLCBVU37cjs.DEFAULT_HEARTBEAT_LEASE_TTL_MS; exports.DEFAULT_LEASE_HEARTBEAT_MS = _chunkXLCBVU37cjs.DEFAULT_LEASE_HEARTBEAT_MS; exports.DEFAULT_MAX_REQUEST_BODY_BYTES = _chunkXLCBVU37cjs.DEFAULT_MAX_REQUEST_BODY_BYTES; exports.DEFAULT_MAX_SCHEDULED_TASKS_PER_FIRE = _chunkXLCBVU37cjs.DEFAULT_MAX_SCHEDULED_TASKS_PER_FIRE; exports.DEFAULT_MAX_TOOL_ITERATIONS = _chunkXLCBVU37cjs.DEFAULT_MAX_TOOL_ITERATIONS; exports.DEFAULT_TOTAL_TIMEOUT_MS = _chunkXLCBVU37cjs.DEFAULT_TOTAL_TIMEOUT_MS; exports.DeploymentConfigError = _chunkXLCBVU37cjs.DeploymentConfigError; exports.MAX_PUSH_PAYLOAD_BYTES = _chunkXLCBVU37cjs.MAX_PUSH_PAYLOAD_BYTES; exports.MAX_TASK_PAYLOAD_BYTES = _chunkXLCBVU37cjs.MAX_TASK_PAYLOAD_BYTES; exports.MIN_SCHEDULE_LEAD_MS = _chunkXLCBVU37cjs.MIN_SCHEDULE_LEAD_MS; exports.NonRetryableError = _chunkXLCBVU37cjs.NonRetryableError; exports.PUSH_ENVELOPE_RESERVED_BYTES = _chunkXLCBVU37cjs.PUSH_ENVELOPE_RESERVED_BYTES; exports.SCHEMA_VERSION = _chunkXLCBVU37cjs.SCHEMA_VERSION; exports.WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES = _chunkXLCBVU37cjs.WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES; exports.WEB_PUSH_MAX_BODY_BYTES = _chunkXLCBVU37cjs.WEB_PUSH_MAX_BODY_BYTES; exports.advanceOccurrence = _chunkXLCBVU37cjs.advanceOccurrence; exports.createAdapter = createAdapter; exports.createD1Adapter = _chunkXLCBVU37cjs.createD1Adapter; exports.createReiServer = createReiServer; exports.createSingleUserCloudflareWorker = _chunkXLCBVU37cjs.createSingleUserCloudflareWorker; exports.createSingleUserServer = _chunkXLCBVU37cjs.createSingleUserServer; exports.createTenantToken = createTenantToken; exports.createWebCryptoWebPush = _chunkXLCBVU37cjs.createWebCryptoWebPush; exports.decryptFromStorage = _chunkXLCBVU37cjs.decryptFromStorage; exports.decryptPayload = _chunkXLCBVU37cjs.decryptPayload; exports.deriveUserEncryptionKey = _chunkXLCBVU37cjs.deriveUserEncryptionKey; exports.encryptForStorage = _chunkXLCBVU37cjs.encryptForStorage; exports.ensureSchema = _chunkXLCBVU37cjs.ensureSchema; exports.getSchemaVersion = _chunkXLCBVU37cjs.getSchemaVersion; exports.hasChatCredRef = _chunkXLCBVU37cjs.hasChatCredRef; exports.isNonRetryableError = _chunkXLCBVU37cjs.isNonRetryableError; exports.isValidCredId = _chunkXLCBVU37cjs.isValidCredId; exports.isValidISO8601 = _chunkXLCBVU37cjs.isValidISO8601; exports.isValidTimeZoneId = _chunkXLCBVU37cjs.isValidTimeZoneId; exports.isValidUUID = _chunkXLCBVU37cjs.isValidUUID; exports.isValidUUIDv4 = _chunkXLCBVU37cjs.isValidUUIDv4; exports.isValidUrl = _chunkXLCBVU37cjs.isValidUrl; exports.measurePushPayload = _chunkXLCBVU37cjs.measurePushPayload; exports.nextFutureOccurrence = _chunkXLCBVU37cjs.nextFutureOccurrence; exports.planNextOccurrence = _chunkXLCBVU37cjs.planNextOccurrence; exports.readRequestBody = _chunkXLCBVU37cjs.readRequestBody; exports.resolveLlmCredential = _chunkXLCBVU37cjs.resolveLlmCredential; exports.runScheduledTick = _chunkXLCBVU37cjs.runScheduledTick; exports.runTask = _chunkXLCBVU37cjs.runTask; exports.sanitizeErrorSummary = _chunkXLCBVU37cjs.sanitizeErrorSummary; exports.summarizeErrorCause = _chunkXLCBVU37cjs.summarizeErrorCause; exports.supportsLlmCredentialsStore = _chunkXLCBVU37cjs.supportsLlmCredentialsStore; exports.validateAvatarUrl = _chunkXLCBVU37cjs.validateAvatarUrl; exports.validateCredRefs = _chunkXLCBVU37cjs.validateCredRefs; exports.validateCredValue = _chunkXLCBVU37cjs.validateCredValue; exports.validateLlmMessagesArray = _chunkXLCBVU37cjs.validateLlmMessagesArray; exports.validateScheduleMessagePayload = _chunkXLCBVU37cjs.validateScheduleMessagePayload; exports.validateSplitPattern = _chunkXLCBVU37cjs.validateSplitPattern; exports.verifyTenantToken = verifyTenantToken;
package/dist/index.mjs CHANGED
@@ -71,7 +71,7 @@ import {
71
71
  validateLlmMessagesArray,
72
72
  validateScheduleMessagePayload,
73
73
  validateSplitPattern
74
- } from "./chunk-3JEWYDM4.mjs";
74
+ } from "./chunk-N7CZ3KXB.mjs";
75
75
  import "./chunk-GN44PST5.mjs";
76
76
 
77
77
  // src/server/adapters/factory.js
@@ -70,16 +70,21 @@ export function discardUndeliveredPushes({ db, userId, pushes, sentIds }: {
70
70
  /**
71
71
  * 把某条任务名下「还没发出去的」行从 outbox 撤掉。
72
72
  *
73
- * 用在取消 / 顶替只碰了任务行的那两条路上(`DELETE /message` 与
74
- * `supersedesUuid`):任务此前投递到一半失败过的话,没发出去的那几段还躺在
75
- * outbox 里等重试,任务行删掉它们也不会跟着走。不撤的话客户端下一次
76
- * `GET /outbox` 照样把它们补收回去——用户看到的就是「取消接口回了成功,消息还
77
- * 是来了」。
73
+ * 用在取消 / 顶替之后的每条收尾路上(`DELETE /message`、`supersedesUuid`、
74
+ * fire 内的 cancelTask、投递侧发现行已被取消):任务此前投递到一半失败过的话,
75
+ * 没发出去的那几段还躺在 outbox 里等重试,任务行删掉它们也不会跟着走。不撤的
76
+ * 话客户端下一次 `GET /outbox` 照样把它们补收回去——用户看到的就是「取消接口回
77
+ * 了成功,消息还是来了」。
78
78
  *
79
79
  * 判据与 discardUndeliveredPushes 一致:只撤 delivered_at 为 null 的行。已经推
80
80
  * 给设备的那几条撤不回来,行留着让客户端照常 ack——取消的意思是「别再发后面
81
81
  * 的」,不是「把已经收到的从收件箱里抹掉」。
82
82
  *
83
+ * 优先走适配器的按任务删除(discardUndeliveredOutboxForTask):一个来回,且不
84
+ * 受未 ack 积压量的影响。没有这个方法的适配器(宿主自带的旧实现)退回翻页扫
85
+ * 描——被取消任务的行通常是最新的,积压超过扫描上限时正好扫不到它们,所以扫到
86
+ * 上限还没扫完必须吵出来,不能装作清干净了。
87
+ *
83
88
  * 同样是 best-effort:适配器缺读/删任一侧就静默跳过,出错只记日志。取消 / 顶
84
89
  * 替本身已经生效了,不该因为账本没清干净被翻成失败。
85
90
  *
@@ -25,9 +25,14 @@
25
25
  * @param {number|null} args.occurrenceMs
26
26
  * @param {{ sendNotification: Function }|null} args.webpush
27
27
  * @param {() => number} [args.now] - 取当前时刻(测试可注入假时钟)
28
+ * @param {(() => boolean)|null} [args.isCancelled] - 「这条任务是否已在投递期间
29
+ * 被取消 / 顶替」(run-tick 的租约心跳信号)。推送那条路的取消检查在
30
+ * webpush 那层(guardWebpushWithLease),但 `notification.show: false` 的结
31
+ * 果不发推送,只有这个显式读法能拦得住它——不给的话那条路上没有任何取消检
32
+ * 查点。
28
33
  * @returns {ResultEmitter}
29
34
  */
30
- export function createResultEmitter({ db, task, userKey, decryptedPayload, messageIdBase, sessionId, occurrenceMs, webpush, now, }: {
35
+ export function createResultEmitter({ db, task, userKey, decryptedPayload, messageIdBase, sessionId, occurrenceMs, webpush, now, isCancelled, }: {
31
36
  db: import("../adapters/interface.js").DbAdapter;
32
37
  task: any;
33
38
  userKey: string;
@@ -39,6 +44,7 @@ export function createResultEmitter({ db, task, userKey, decryptedPayload, messa
39
44
  sendNotification: Function;
40
45
  } | null;
41
46
  now?: () => number;
47
+ isCancelled?: (() => boolean) | null;
42
48
  }): ResultEmitter;
43
49
  export type ResultEmitter = {
44
50
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rei-standard/amsg-server",
3
- "version": "2.6.0-next.23",
3
+ "version": "2.6.0-next.24",
4
4
  "description": "ReiStandard Active Messaging server SDK with pluggable database adapters. Three-axis push schema (messageKind / messageType / messageSubtype) from @rei-standard/amsg-shared. Auto-emits ReasoningPush when the LLM response carries reasoning_content.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -39,7 +39,7 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "@netlify/blobs": "^8.1.0",
42
- "@rei-standard/amsg-shared": "^0.4.0-next.8",
42
+ "@rei-standard/amsg-shared": "^0.4.0-next.9",
43
43
  "web-push": "^3.6.7"
44
44
  },
45
45
  "peerDependencies": {