@rei-standard/amsg-server 2.6.0-next.32 → 2.6.0-next.33

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
@@ -364,6 +364,16 @@ hook 在 `pushPayloads` 里自己写了这几个字段的话会被库覆盖:
364
364
 
365
365
  配上 `hooks: { onBeforeFire, onLLMOutput, executeToolCalls }` 之后,AI 类任务的 prompt 不再是排程那一刻冻结的文本,而是 cron 触发时现场组装,工具也在服务端就地跑完,全程不需要客户端在线。完整用法见 [`examples/cloudflare-single-user/README.md`](https://github.com/Tosd0/ReiStandard/blob/main/packages/rei-standard-amsg/server/examples/cloudflare-single-user/README.md) 的「Fire 时刻 hooks」。
366
366
 
367
+ `onBeforeFire` 的返回值决定这次触发怎么走:
368
+
369
+ | 返回值 | 这次触发怎么走 |
370
+ |---|---|
371
+ | 消息数组,或 `{ messages, maxToolIterations?, totalTimeoutMs?, tools?, toolChoice? }` | 用这份 prompt 生成 |
372
+ | `{ skip: true }` | 这次不发,按零推送的成功收场:一次性任务删掉、循环任务推进到下一次 |
373
+ | `{ defer: { afterMs } }` | 现在不合适,`afterMs` 毫秒后再来问。见下面[推迟这次触发](#推迟这次触发defer) |
374
+ | `null` | 交还给排程时冻结的 prompt |
375
+ | 抛错 | 按投递失败处理:占一次重试、写 `last_error`、2 / 4 / 6 分钟退避 |
376
+
367
377
  三个 hook 拿到的 ctx 上都有这几个口子:
368
378
 
369
379
  | ctx 上的口子 | 干什么 |
@@ -383,6 +393,41 @@ hook 在 `pushPayloads` 里自己写了这几个字段的话会被库覆盖:
383
393
 
384
394
  `sessionId` 是给日志和去重用的不透明字符串,格式随版本变,别拆它拿上面这些值。
385
395
 
396
+ ### 推迟这次触发(defer)
397
+
398
+ 任务到点了,但宿主这会儿不方便生成——比如同一段对话里正有另一轮回复在生成,想等它结束再说。`onBeforeFire` 返回 `{ defer: { afterMs } }`:
399
+
400
+ ```js
401
+ async onBeforeFire(ctx) {
402
+ if (await isConversationBusy(ctx)) return { defer: { afterMs: 30_000 } };
403
+ return buildMessages(ctx);
404
+ }
405
+ ```
406
+
407
+ 这一次不调 LLM、不推送,也不调 `onLLMOutput` / `onAfterSend`。任务行上只有两处变化:`retry_after` 写成现在 + `afterMs`,租约放掉。到点之后 cron 把它重新捞起来,从 `onBeforeFire` 再走一遍,那时可以继续推迟。
408
+
409
+ 跟抛错重试的区别:
410
+
411
+ | | 抛错 | `{ defer }` |
412
+ |---|---|---|
413
+ | `retry_count` | 加一,用完就终审 | 不变 |
414
+ | `last_error` | 写上这次的原因 | 不写,也不清已有的 |
415
+ | 多久之后再来 | 固定 2 / 4 / 6 分钟 | 宿主给的 `afterMs` |
416
+ | `onFireSettled` 的 `status` | `failed` | `deferred`,带 `retryAfter` |
417
+
418
+ 两边相同的是 `next_send_at` 都不动:它一直是这次触发的名义时刻,`ctx.task.nextSendAt`、`occurrenceMs`、推送的 `messageId` 在推迟前后都是同一套。循环任务被推迟时不会推进到下一次。
419
+
420
+ 几条规矩:
421
+
422
+ - `afterMs` 必须是有限正数,最大 24 小时(导出为 `MAX_DEFER_AFTER_MS`)。不合法的值按 hook 契约违约处理(`AGENTIC_BAD_BEFORE_FIRE`,一跳终审)。没有下限,但 cron 一分钟一跳,实际是「到点之后的第一跳」才会再问;用 `runTask` 自己触发的话,到点之前调它会得到 `retry_pending`。
423
+ - 推迟不会让任务永远挂着。名义时刻过去超过 `staleAfterMs`(默认 60 分钟)的任务照常按过期处理:一次性任务标 `failed`、循环任务快进到下一次,并调 `onStaleSkip`。某次推迟的唤醒时刻已经越过这条线时,库不再等那一轮,当场按过期收场——这时宿主会先收到 `status: 'deferred'` 的 `onFireSettled`,紧接着收到 `onStaleSkip`。
424
+ - 每次被重新问到都是一次新的 fire:`scratch` 是新的,`onFireSettled` 各调各的。
425
+ - 只有定时投递(`runScheduledTick`、单用户 Worker 的 `scheduled()`、`runTask`)支持推迟,并且适配器要实现 `claimTask`(任务表有 `retry_after` 列,内置适配器都有)。`messageType: 'instant'` 在请求里当场投递的任务、以及没实现 `claimTask` 的自定义适配器返回 `{ defer }` 会得到 `AGENTIC_DEFER_UNSUPPORTED`,按[部署配错了算可重试](#部署配错了算可重试)处理。
426
+ - 内容已经落进收件箱、只是推送没发完的重试不调 `onBeforeFire`(见[生成成功之后推送失败:只补推送](#生成成功之后推送失败只补推送)),那一跳也就不会被推迟。
427
+ - 配了 `serializeBy` 时,被推迟的任务不占着分组:同组里排在它后面的任务可以先跑。
428
+
429
+ tick 汇总的 `details.deferredTasks`(`{ taskId, retryAfter }`)列出这一跳被推迟的任务,它们不计入 `successCount` / `failedCount`。`GET /capabilities` 的 features 里有 `before-fire-defer`。
430
+
386
431
  ### config 级 hook
387
432
 
388
433
  这两个挂在 worker 工厂 config 的顶层(不在 `hooks` 里):
@@ -390,7 +435,7 @@ hook 在 `pushPayloads` 里自己写了这几个字段的话会被库覆盖:
390
435
  | hook | 什么时候调 | 载荷 |
391
436
  |---|---|---|
392
437
  | `onAfterSend` | fire 的 pushPayloads 逐段发完,或中途发挂 | `{ task, sentCount, pushedCount, total, error, usage, usageTotal, llmCalls, outboxed, scratch, readState, writeState, emitResult }` |
393
- | `onFireSettled` | 一次 fire 收尾——只要 `onBeforeFire` 被调用过,什么结局都调一次 | `{ task, status, skipReason, sentCount, pushedCount, total, iterations, error, metadata, usage, usageTotal, llmCalls, outboxed, scratch, readState, writeState, emitResult }` |
438
+ | `onFireSettled` | 一次 fire 收尾——只要 `onBeforeFire` 被调用过,什么结局都调一次 | `{ task, status, skipReason, retryAfter, sentCount, pushedCount, total, iterations, error, metadata, usage, usageTotal, llmCalls, outboxed, scratch, readState, writeState, emitResult }` |
394
439
  | `onStaleSkip` | 任务错过触发时刻超过 60 分钟、这一次(或这几次)不再补发 | `{ reason, action, metadata, recurrenceType, occurrenceMs, skippedCount, skippedOccurrences, skippedTruncated, nextSendAt, readState, writeState, emitResult }` |
395
440
 
396
441
  三个 hook 都自带 `readState` / `writeState` / `emitResult`,作用于当前用户,语义与 fire 级那套一致。`onStaleSkip` 尤其需要:服务停摆恢复后的第一跳里可能一次 fire 都没跑过,而那正是它要留痕迹的时候。
@@ -405,7 +450,9 @@ hook 在 `pushPayloads` 里自己写了这几个字段的话会被库覆盖:
405
450
  |---|---|
406
451
  | `sent` | pushPayloads 全部发完(`sentCount === total`) |
407
452
  | `skipped` | 这次不发。`skipReason` 区分是 `onBeforeFire` 直接 `{ skip: true }`(`'before-fire'`)还是模型跑完后判定不发(`'skip-push'`) |
453
+ | `deferred` | `onBeforeFire` 返回了 `{ defer: { afterMs } }`:这次没生成也没发,任务还在。`retryAfter`(ISO 时刻)之后会从 `onBeforeFire` 重新走一遍;这个字段在其余结局都是 `null` |
408
454
  | `failed` | 链路抛错,`error` 带原始错误。发到第 k 段挂了也是这个:`sentCount = k`、`total` 是原本要发的段数 |
455
+ | `cancelled` | 任务在投递期间被取消或顶替,见[取消正在执行的任务](#取消正在执行的生成与工具) |
409
456
  | `not-handled` | `onBeforeFire` 返回 `null`,这条任务交还给排程时冻结的 prompt 老链路。那条链路不归 fire hook 管,它后面发没发出去不体现在这里 |
410
457
 
411
458
  **用量记账**看这三个字段,两个 hook 都带,`onFireSettled` 的每种结局(发完、跳过、失败)都带——失败也花了钱:
@@ -507,7 +554,7 @@ const { messageId, pushed } = await ctx.emitResult({
507
554
 
508
555
  ### hook 契约违约算确定性失败
509
556
 
510
- 宿主 hook 返回了库不认的东西(`onBeforeFire` 的返回形状、`onLLMOutput` 的决策标签),或者建后续任务时 `createTask` 没把行交回来——这些错误带 `permanent: true` 和一个稳定的 `code`(`AGENTIC_BAD_BEFORE_FIRE` / `AGENTIC_BAD_DECISION` / `AGENTIC_SCHEDULE_FAILED` / `TASK_PAYLOAD_TOO_LARGE`),投递侧据此跳过退避阶梯:一次性任务直接标 `failed`,循环任务作废本次 occurrence。重试也是同一个结果,而每重试一轮都要把 `onBeforeFire` 和一整轮 LLM 重跑一遍。
557
+ 宿主 hook 返回了库不认的东西(`onBeforeFire` 的返回形状、`{ defer }` 里不合法的 `afterMs`、`onLLMOutput` 的决策标签),或者建后续任务时 `createTask` 没把行交回来——这些错误带 `permanent: true` 和一个稳定的 `code`(`AGENTIC_BAD_BEFORE_FIRE` / `AGENTIC_BAD_DECISION` / `AGENTIC_SCHEDULE_FAILED` / `TASK_PAYLOAD_TOO_LARGE`),投递侧据此跳过退避阶梯:一次性任务直接标 `failed`,循环任务作废本次 occurrence。重试也是同一个结果,而每重试一轮都要把 `onBeforeFire` 和一整轮 LLM 重跑一遍。
511
558
 
512
559
  分界线是「谁写错了」:契约由宿主代码定死,重掷一次还是同一个形状;而模型这一轮掷出了什么则是每轮都可能不同的。所以「tool-request 决策里没有能解析的 `toolCalls`」(`AGENTIC_EMPTY_TOOL_REQUEST`)和「轮数用尽也没等到 `finish` / `skip-push`」(`AGENTIC_LOOP_EXCEEDED`)带 `code` 但不带 `permanent`,留在退避阶梯上——隔两分钟重掷一次多半就正常收尾了,判终态的话一次性任务第一次掷歪就永久 `failed`,行离开 `pending` 之后连 `PUT /update-message` 都救不回来(回 409)。
513
560
 
@@ -1636,6 +1636,7 @@ var DEFAULT_MAX_TOOL_ITERATIONS = 5;
1636
1636
  var DEFAULT_TOTAL_TIMEOUT_MS = 24e4;
1637
1637
  var DEFAULT_MAX_SCHEDULED_TASKS_PER_FIRE = 2;
1638
1638
  var MIN_SCHEDULE_LEAD_MS = 6e4;
1639
+ var MAX_DEFER_AFTER_MS = 24 * 60 * 60 * 1e3;
1639
1640
  var SCHEDULABLE_MESSAGE_TYPES = /* @__PURE__ */ new Set(["auto", "prompted", "fixed"]);
1640
1641
  var SCHEDULABLE_RECURRENCE_TYPES = /* @__PURE__ */ new Set(["none", "daily", "weekly"]);
1641
1642
  var SLEEP_BETWEEN_MESSAGES_MS = 1500;
@@ -1670,6 +1671,22 @@ function buildHookTask(task, decryptedPayload) {
1670
1671
  retryCount: _nullishCoalesce(task.retry_count, () => ( 0))
1671
1672
  });
1672
1673
  }
1674
+ var BAD_BEFORE_FIRE_MESSAGE = "AGENTIC_BAD_BEFORE_FIRE: onBeforeFire must return ChatMessage[] | { messages, maxToolIterations?, totalTimeoutMs?, tools?, toolChoice? } | { skip: true } | { defer: { afterMs } } | null";
1675
+ function readDeferAfterMs(defer) {
1676
+ const afterMs = defer && typeof defer === "object" ? (
1677
+ /** @type {any} */
1678
+ defer.afterMs
1679
+ ) : void 0;
1680
+ if (typeof afterMs !== "number" || !Number.isFinite(afterMs) || afterMs <= 0 || afterMs > MAX_DEFER_AFTER_MS) {
1681
+ throw markPermanent(
1682
+ new TypeError(
1683
+ `${BAD_BEFORE_FIRE_MESSAGE}\uFF08defer.afterMs \u5FC5\u987B\u662F\u6709\u9650\u6B63\u6570\uFF0C\u4E14\u4E0D\u8D85\u8FC7 ${MAX_DEFER_AFTER_MS} \u6BEB\u79D2\uFF09`
1684
+ ),
1685
+ "AGENTIC_BAD_BEFORE_FIRE"
1686
+ );
1687
+ }
1688
+ return afterMs;
1689
+ }
1673
1690
  function normalizeBeforeFireResult(result) {
1674
1691
  if (Array.isArray(result)) {
1675
1692
  return { messages: result };
@@ -1686,12 +1703,7 @@ function normalizeBeforeFireResult(result) {
1686
1703
  toolChoice: result.toolChoice
1687
1704
  };
1688
1705
  }
1689
- throw markPermanent(
1690
- new TypeError(
1691
- "AGENTIC_BAD_BEFORE_FIRE: onBeforeFire must return ChatMessage[] | { messages, maxToolIterations?, totalTimeoutMs?, tools?, toolChoice? } | { skip: true } | null"
1692
- ),
1693
- "AGENTIC_BAD_BEFORE_FIRE"
1694
- );
1706
+ throw markPermanent(new TypeError(BAD_BEFORE_FIRE_MESSAGE), "AGENTIC_BAD_BEFORE_FIRE");
1695
1707
  }
1696
1708
  function firstPositiveInt(values, fallback) {
1697
1709
  for (const v of values) {
@@ -2026,6 +2038,7 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
2026
2038
  };
2027
2039
  let settledStatus = "failed";
2028
2040
  let settledError = null;
2041
+ let settledRetryAfter = null;
2029
2042
  try {
2030
2043
  const outcome = await runFireChain({
2031
2044
  task,
@@ -2050,7 +2063,11 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
2050
2063
  occurrenceMs
2051
2064
  });
2052
2065
  throwIfCancelled();
2053
- settledStatus = !outcome.handled ? "not-handled" : outcome.result.status === "skipped" ? "skipped" : "sent";
2066
+ if (!outcome.handled) settledStatus = "not-handled";
2067
+ else if (outcome.result.status === "deferred") {
2068
+ settledStatus = "deferred";
2069
+ settledRetryAfter = outcome.result.retryAfter;
2070
+ } else settledStatus = outcome.result.status === "skipped" ? "skipped" : "sent";
2054
2071
  return outcome;
2055
2072
  } catch (error) {
2056
2073
  if (isCancelled() && !isTaskCancelledError(error)) {
@@ -2070,6 +2087,7 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
2070
2087
  status: settledStatus,
2071
2088
  ...settledStatus === "cancelled" ? { willRetry: false, failureStage: null } : settledStatus === "failed" && ctx._deliveryState ? failureRetryDecision(ctx._deliveryState, settledError) : { willRetry: null, failureStage: null },
2072
2089
  skipReason: settledStatus === "skipped" ? progress.skipReason : null,
2090
+ retryAfter: settledStatus === "deferred" ? settledRetryAfter : null,
2073
2091
  sentCount: progress.sentCount,
2074
2092
  pushedCount: progress.pushedCount,
2075
2093
  total: progress.total,
@@ -2127,6 +2145,27 @@ async function runFireChain({
2127
2145
  progress.skipReason = "before-fire";
2128
2146
  return { handled: true, result: { success: true, messagesSent: 0, status: "skipped", iterations: 0 } };
2129
2147
  }
2148
+ if (typeof before === "object" && !Array.isArray(before) && before.defer != null) {
2149
+ const afterMs = readDeferAfterMs(before.defer);
2150
+ if (ctx._deferSupported !== true) {
2151
+ throw new DeploymentConfigError(
2152
+ "AGENTIC_DEFER_UNSUPPORTED: onBeforeFire \u8FD4\u56DE\u4E86 { defer }\uFF0C\u4F46\u8FD9\u6761\u6295\u9012\u8DEF\u5F84\u4E0D\u652F\u6301\u63A8\u8FDF\u2014\u2014\u53EA\u6709 runScheduledTick / runTask \u4E14\u9002\u914D\u5668\u5B9E\u73B0\u4E86 claimTask\uFF08\u4EFB\u52A1\u8868\u6709 retry_after \u5217\uFF09\u65F6\u53EF\u7528\uFF0C\u8BF7\u6C42\u5185\u5F53\u573A\u6295\u9012\u7684 instant \u4EFB\u52A1\u4E0D\u53EF\u7528",
2153
+ { code: "AGENTIC_DEFER_UNSUPPORTED" }
2154
+ );
2155
+ }
2156
+ return {
2157
+ handled: true,
2158
+ result: {
2159
+ success: false,
2160
+ deferred: true,
2161
+ // 用真实时钟:这个时刻要落进 retry_after,跟数据库捞取条件里的「现在」比。
2162
+ retryAfter: new Date(Date.now() + afterMs).toISOString(),
2163
+ messagesSent: 0,
2164
+ status: "deferred",
2165
+ iterations: 0
2166
+ }
2167
+ };
2168
+ }
2130
2169
  const normalized = normalizeBeforeFireResult(before);
2131
2170
  const maxToolIterations = firstPositiveInt(
2132
2171
  [normalized.maxToolIterations, ctx.maxToolIterations],
@@ -3240,6 +3279,7 @@ async function deliverTasks(ctx, tasks) {
3240
3279
  deletedOnceOffTasks: 0,
3241
3280
  updatedRecurringTasks: 0,
3242
3281
  staleTasks: [],
3282
+ deferredTasks: [],
3243
3283
  cancelledTasks: [],
3244
3284
  reasoningSkippedTasks: [],
3245
3285
  redeliveredTasks: [],
@@ -3533,7 +3573,7 @@ async function deliverTasks(ctx, tasks) {
3533
3573
  const occurrenceMs = Date.parse(task.next_send_at);
3534
3574
  const retryAfterMs = task.retry_after ? Date.parse(task.retry_after) : NaN;
3535
3575
  const notOnFreshRetryChain = (task.retry_count || 0) === 0 || Number.isFinite(retryAfterMs) && Date.now() - retryAfterMs > staleAfterMs;
3536
- if (Number.isFinite(occurrenceMs) && Date.now() - occurrenceMs > staleAfterMs && notOnFreshRetryChain) {
3576
+ const settleAsStale = async () => {
3537
3577
  try {
3538
3578
  const stateAccessors = createStateAccessors({
3539
3579
  db,
@@ -3593,8 +3633,32 @@ async function deliverTasks(ctx, tasks) {
3593
3633
  results.failedCount++;
3594
3634
  results.failedTasks.push({ taskId: task.id, reason: error.message || "\u8FC7\u671F\u4EFB\u52A1\u5904\u7406\u5931\u8D25", status: "stale_update_failed" });
3595
3635
  }
3636
+ };
3637
+ if (Number.isFinite(occurrenceMs) && Date.now() - occurrenceMs > staleAfterMs && notOnFreshRetryChain) {
3638
+ await settleAsStale();
3596
3639
  return;
3597
3640
  }
3641
+ const handleDeferred = async (retryAfter) => {
3642
+ const wakeMs = Date.parse(retryAfter);
3643
+ if (Number.isFinite(occurrenceMs) && wakeMs - occurrenceMs > staleAfterMs) {
3644
+ await settleAsStale();
3645
+ return;
3646
+ }
3647
+ try {
3648
+ const updated = await updateTaskWithLastError(task.id, {
3649
+ retry_after: retryAfter,
3650
+ lease_until: null
3651
+ });
3652
+ if (rowVanished(updated)) {
3653
+ await recordCancelled(task, "cancelled_mid_delivery");
3654
+ return;
3655
+ }
3656
+ results.deferredTasks.push({ taskId: task.id, retryAfter });
3657
+ } catch (error) {
3658
+ results.failedCount++;
3659
+ results.failedTasks.push({ taskId: task.id, reason: error.message || "\u63A8\u8FDF\u5199\u5E93\u5931\u8D25", status: "defer_update_failed" });
3660
+ }
3661
+ };
3598
3662
  let sendResult;
3599
3663
  try {
3600
3664
  sendResult = await processSingleMessage(
@@ -3605,7 +3669,11 @@ async function deliverTasks(ctx, tasks) {
3605
3669
  masterKey,
3606
3670
  webpush: guardWebpushWithLease(ctx.webpush, lease),
3607
3671
  isTaskCancelled: () => lease.lost,
3608
- signal: lease.signal
3672
+ signal: lease.signal,
3673
+ // onBeforeFire 的 { defer } 要靠 retry_after 这一列落地,没实现
3674
+ // claimTask 的适配器没有这一列(见 lib/agentic-fire.js 的
3675
+ // AGENTIC_DEFER_UNSUPPORTED)。
3676
+ _deferSupported: supportsClaim
3609
3677
  },
3610
3678
  masterKey,
3611
3679
  { userKey, payload: decryptedPayload }
@@ -3625,6 +3693,14 @@ async function deliverTasks(ctx, tasks) {
3625
3693
  );
3626
3694
  return;
3627
3695
  }
3696
+ if (sendResult.deferred) {
3697
+ if (lease.lost) {
3698
+ await recordCancelled(task, "cancelled_mid_delivery");
3699
+ return;
3700
+ }
3701
+ await handleDeferred(sendResult.retryAfter);
3702
+ return;
3703
+ }
3628
3704
  if (!sendResult.success) {
3629
3705
  if (lease.lost) {
3630
3706
  await recordCancelled(task, "cancelled_mid_delivery");
@@ -3717,6 +3793,9 @@ async function deliverTasks(ctx, tasks) {
3717
3793
  deletedOnceOffTasks: results.deletedOnceOffTasks,
3718
3794
  updatedRecurringTasks: results.updatedRecurringTasks,
3719
3795
  staleTasks: results.staleTasks,
3796
+ // onBeforeFire 返回 { defer } 被推迟的任务({ taskId, retryAfter }):这一
3797
+ // 跳没生成也没发,到 retryAfter 之后再问。不计入 successCount / failedCount。
3798
+ deferredTasks: results.deferredTasks,
3720
3799
  // 投递期间行被取消 / 顶替的任务。`cancelled_mid_delivery` = 推送在发出去
3721
3800
  // 之前被拦下;`cancelled_after_delivery` = 推送已经发完,收尾写库才发现
3722
3801
  // 行没了。两种都不计入 successCount / failedCount。
@@ -6012,7 +6091,7 @@ function createClientStateNamespacesHandler(ctx) {
6012
6091
  }
6013
6092
 
6014
6093
  // src/server/lib/version.js
6015
- var SERVER_VERSION = true ? "2.6.0-next.32" : "0.0.0-dev";
6094
+ var SERVER_VERSION = true ? "2.6.0-next.33" : "0.0.0-dev";
6016
6095
 
6017
6096
  // src/server/handlers/capabilities.js
6018
6097
  var SERVER_FEATURES = Object.freeze([
@@ -6125,7 +6204,10 @@ var SERVER_FEATURES = Object.freeze([
6125
6204
  // 工厂配置认 maxDeliveryRetries(投递失败的重试次数上限,默认 3)。
6126
6205
  "max-delivery-retries",
6127
6206
  // Per-task pre-commit retry limit and fire receipt retry decision.
6128
- "max-generation-retries"
6207
+ "max-generation-retries",
6208
+ // onBeforeFire 认 { defer: { afterMs } }:这次不生成,过一会儿再来问(不占重试
6209
+ // 次数);onFireSettled 的 status 多一种 'deferred',带 retryAfter。
6210
+ "before-fire-defer"
6129
6211
  ]);
6130
6212
  function createCapabilitiesHandler(ctx) {
6131
6213
  async function GET(url, headers) {
@@ -6753,4 +6835,5 @@ function createSingleUserCloudflareWorker(buildConfig, options = {}) {
6753
6835
 
6754
6836
 
6755
6837
 
6756
- 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.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.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;
6838
+
6839
+ 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.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;
@@ -1636,6 +1636,7 @@ var DEFAULT_MAX_TOOL_ITERATIONS = 5;
1636
1636
  var DEFAULT_TOTAL_TIMEOUT_MS = 24e4;
1637
1637
  var DEFAULT_MAX_SCHEDULED_TASKS_PER_FIRE = 2;
1638
1638
  var MIN_SCHEDULE_LEAD_MS = 6e4;
1639
+ var MAX_DEFER_AFTER_MS = 24 * 60 * 60 * 1e3;
1639
1640
  var SCHEDULABLE_MESSAGE_TYPES = /* @__PURE__ */ new Set(["auto", "prompted", "fixed"]);
1640
1641
  var SCHEDULABLE_RECURRENCE_TYPES = /* @__PURE__ */ new Set(["none", "daily", "weekly"]);
1641
1642
  var SLEEP_BETWEEN_MESSAGES_MS = 1500;
@@ -1670,6 +1671,22 @@ function buildHookTask(task, decryptedPayload) {
1670
1671
  retryCount: task.retry_count ?? 0
1671
1672
  });
1672
1673
  }
1674
+ var BAD_BEFORE_FIRE_MESSAGE = "AGENTIC_BAD_BEFORE_FIRE: onBeforeFire must return ChatMessage[] | { messages, maxToolIterations?, totalTimeoutMs?, tools?, toolChoice? } | { skip: true } | { defer: { afterMs } } | null";
1675
+ function readDeferAfterMs(defer) {
1676
+ const afterMs = defer && typeof defer === "object" ? (
1677
+ /** @type {any} */
1678
+ defer.afterMs
1679
+ ) : void 0;
1680
+ if (typeof afterMs !== "number" || !Number.isFinite(afterMs) || afterMs <= 0 || afterMs > MAX_DEFER_AFTER_MS) {
1681
+ throw markPermanent(
1682
+ new TypeError(
1683
+ `${BAD_BEFORE_FIRE_MESSAGE}\uFF08defer.afterMs \u5FC5\u987B\u662F\u6709\u9650\u6B63\u6570\uFF0C\u4E14\u4E0D\u8D85\u8FC7 ${MAX_DEFER_AFTER_MS} \u6BEB\u79D2\uFF09`
1684
+ ),
1685
+ "AGENTIC_BAD_BEFORE_FIRE"
1686
+ );
1687
+ }
1688
+ return afterMs;
1689
+ }
1673
1690
  function normalizeBeforeFireResult(result) {
1674
1691
  if (Array.isArray(result)) {
1675
1692
  return { messages: result };
@@ -1686,12 +1703,7 @@ function normalizeBeforeFireResult(result) {
1686
1703
  toolChoice: result.toolChoice
1687
1704
  };
1688
1705
  }
1689
- throw markPermanent(
1690
- new TypeError(
1691
- "AGENTIC_BAD_BEFORE_FIRE: onBeforeFire must return ChatMessage[] | { messages, maxToolIterations?, totalTimeoutMs?, tools?, toolChoice? } | { skip: true } | null"
1692
- ),
1693
- "AGENTIC_BAD_BEFORE_FIRE"
1694
- );
1706
+ throw markPermanent(new TypeError(BAD_BEFORE_FIRE_MESSAGE), "AGENTIC_BAD_BEFORE_FIRE");
1695
1707
  }
1696
1708
  function firstPositiveInt(values, fallback) {
1697
1709
  for (const v of values) {
@@ -2026,6 +2038,7 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
2026
2038
  };
2027
2039
  let settledStatus = "failed";
2028
2040
  let settledError = null;
2041
+ let settledRetryAfter = null;
2029
2042
  try {
2030
2043
  const outcome = await runFireChain({
2031
2044
  task,
@@ -2050,7 +2063,11 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
2050
2063
  occurrenceMs
2051
2064
  });
2052
2065
  throwIfCancelled();
2053
- settledStatus = !outcome.handled ? "not-handled" : outcome.result.status === "skipped" ? "skipped" : "sent";
2066
+ if (!outcome.handled) settledStatus = "not-handled";
2067
+ else if (outcome.result.status === "deferred") {
2068
+ settledStatus = "deferred";
2069
+ settledRetryAfter = outcome.result.retryAfter;
2070
+ } else settledStatus = outcome.result.status === "skipped" ? "skipped" : "sent";
2054
2071
  return outcome;
2055
2072
  } catch (error) {
2056
2073
  if (isCancelled() && !isTaskCancelledError(error)) {
@@ -2070,6 +2087,7 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
2070
2087
  status: settledStatus,
2071
2088
  ...settledStatus === "cancelled" ? { willRetry: false, failureStage: null } : settledStatus === "failed" && ctx._deliveryState ? failureRetryDecision(ctx._deliveryState, settledError) : { willRetry: null, failureStage: null },
2072
2089
  skipReason: settledStatus === "skipped" ? progress.skipReason : null,
2090
+ retryAfter: settledStatus === "deferred" ? settledRetryAfter : null,
2073
2091
  sentCount: progress.sentCount,
2074
2092
  pushedCount: progress.pushedCount,
2075
2093
  total: progress.total,
@@ -2127,6 +2145,27 @@ async function runFireChain({
2127
2145
  progress.skipReason = "before-fire";
2128
2146
  return { handled: true, result: { success: true, messagesSent: 0, status: "skipped", iterations: 0 } };
2129
2147
  }
2148
+ if (typeof before === "object" && !Array.isArray(before) && before.defer != null) {
2149
+ const afterMs = readDeferAfterMs(before.defer);
2150
+ if (ctx._deferSupported !== true) {
2151
+ throw new DeploymentConfigError(
2152
+ "AGENTIC_DEFER_UNSUPPORTED: onBeforeFire \u8FD4\u56DE\u4E86 { defer }\uFF0C\u4F46\u8FD9\u6761\u6295\u9012\u8DEF\u5F84\u4E0D\u652F\u6301\u63A8\u8FDF\u2014\u2014\u53EA\u6709 runScheduledTick / runTask \u4E14\u9002\u914D\u5668\u5B9E\u73B0\u4E86 claimTask\uFF08\u4EFB\u52A1\u8868\u6709 retry_after \u5217\uFF09\u65F6\u53EF\u7528\uFF0C\u8BF7\u6C42\u5185\u5F53\u573A\u6295\u9012\u7684 instant \u4EFB\u52A1\u4E0D\u53EF\u7528",
2153
+ { code: "AGENTIC_DEFER_UNSUPPORTED" }
2154
+ );
2155
+ }
2156
+ return {
2157
+ handled: true,
2158
+ result: {
2159
+ success: false,
2160
+ deferred: true,
2161
+ // 用真实时钟:这个时刻要落进 retry_after,跟数据库捞取条件里的「现在」比。
2162
+ retryAfter: new Date(Date.now() + afterMs).toISOString(),
2163
+ messagesSent: 0,
2164
+ status: "deferred",
2165
+ iterations: 0
2166
+ }
2167
+ };
2168
+ }
2130
2169
  const normalized = normalizeBeforeFireResult(before);
2131
2170
  const maxToolIterations = firstPositiveInt(
2132
2171
  [normalized.maxToolIterations, ctx.maxToolIterations],
@@ -3240,6 +3279,7 @@ async function deliverTasks(ctx, tasks) {
3240
3279
  deletedOnceOffTasks: 0,
3241
3280
  updatedRecurringTasks: 0,
3242
3281
  staleTasks: [],
3282
+ deferredTasks: [],
3243
3283
  cancelledTasks: [],
3244
3284
  reasoningSkippedTasks: [],
3245
3285
  redeliveredTasks: [],
@@ -3533,7 +3573,7 @@ async function deliverTasks(ctx, tasks) {
3533
3573
  const occurrenceMs = Date.parse(task.next_send_at);
3534
3574
  const retryAfterMs = task.retry_after ? Date.parse(task.retry_after) : NaN;
3535
3575
  const notOnFreshRetryChain = (task.retry_count || 0) === 0 || Number.isFinite(retryAfterMs) && Date.now() - retryAfterMs > staleAfterMs;
3536
- if (Number.isFinite(occurrenceMs) && Date.now() - occurrenceMs > staleAfterMs && notOnFreshRetryChain) {
3576
+ const settleAsStale = async () => {
3537
3577
  try {
3538
3578
  const stateAccessors = createStateAccessors({
3539
3579
  db,
@@ -3593,8 +3633,32 @@ async function deliverTasks(ctx, tasks) {
3593
3633
  results.failedCount++;
3594
3634
  results.failedTasks.push({ taskId: task.id, reason: error.message || "\u8FC7\u671F\u4EFB\u52A1\u5904\u7406\u5931\u8D25", status: "stale_update_failed" });
3595
3635
  }
3636
+ };
3637
+ if (Number.isFinite(occurrenceMs) && Date.now() - occurrenceMs > staleAfterMs && notOnFreshRetryChain) {
3638
+ await settleAsStale();
3596
3639
  return;
3597
3640
  }
3641
+ const handleDeferred = async (retryAfter) => {
3642
+ const wakeMs = Date.parse(retryAfter);
3643
+ if (Number.isFinite(occurrenceMs) && wakeMs - occurrenceMs > staleAfterMs) {
3644
+ await settleAsStale();
3645
+ return;
3646
+ }
3647
+ try {
3648
+ const updated = await updateTaskWithLastError(task.id, {
3649
+ retry_after: retryAfter,
3650
+ lease_until: null
3651
+ });
3652
+ if (rowVanished(updated)) {
3653
+ await recordCancelled(task, "cancelled_mid_delivery");
3654
+ return;
3655
+ }
3656
+ results.deferredTasks.push({ taskId: task.id, retryAfter });
3657
+ } catch (error) {
3658
+ results.failedCount++;
3659
+ results.failedTasks.push({ taskId: task.id, reason: error.message || "\u63A8\u8FDF\u5199\u5E93\u5931\u8D25", status: "defer_update_failed" });
3660
+ }
3661
+ };
3598
3662
  let sendResult;
3599
3663
  try {
3600
3664
  sendResult = await processSingleMessage(
@@ -3605,7 +3669,11 @@ async function deliverTasks(ctx, tasks) {
3605
3669
  masterKey,
3606
3670
  webpush: guardWebpushWithLease(ctx.webpush, lease),
3607
3671
  isTaskCancelled: () => lease.lost,
3608
- signal: lease.signal
3672
+ signal: lease.signal,
3673
+ // onBeforeFire 的 { defer } 要靠 retry_after 这一列落地,没实现
3674
+ // claimTask 的适配器没有这一列(见 lib/agentic-fire.js 的
3675
+ // AGENTIC_DEFER_UNSUPPORTED)。
3676
+ _deferSupported: supportsClaim
3609
3677
  },
3610
3678
  masterKey,
3611
3679
  { userKey, payload: decryptedPayload }
@@ -3625,6 +3693,14 @@ async function deliverTasks(ctx, tasks) {
3625
3693
  );
3626
3694
  return;
3627
3695
  }
3696
+ if (sendResult.deferred) {
3697
+ if (lease.lost) {
3698
+ await recordCancelled(task, "cancelled_mid_delivery");
3699
+ return;
3700
+ }
3701
+ await handleDeferred(sendResult.retryAfter);
3702
+ return;
3703
+ }
3628
3704
  if (!sendResult.success) {
3629
3705
  if (lease.lost) {
3630
3706
  await recordCancelled(task, "cancelled_mid_delivery");
@@ -3717,6 +3793,9 @@ async function deliverTasks(ctx, tasks) {
3717
3793
  deletedOnceOffTasks: results.deletedOnceOffTasks,
3718
3794
  updatedRecurringTasks: results.updatedRecurringTasks,
3719
3795
  staleTasks: results.staleTasks,
3796
+ // onBeforeFire 返回 { defer } 被推迟的任务({ taskId, retryAfter }):这一
3797
+ // 跳没生成也没发,到 retryAfter 之后再问。不计入 successCount / failedCount。
3798
+ deferredTasks: results.deferredTasks,
3720
3799
  // 投递期间行被取消 / 顶替的任务。`cancelled_mid_delivery` = 推送在发出去
3721
3800
  // 之前被拦下;`cancelled_after_delivery` = 推送已经发完,收尾写库才发现
3722
3801
  // 行没了。两种都不计入 successCount / failedCount。
@@ -6012,7 +6091,7 @@ function createClientStateNamespacesHandler(ctx) {
6012
6091
  }
6013
6092
 
6014
6093
  // src/server/lib/version.js
6015
- var SERVER_VERSION = true ? "2.6.0-next.32" : "0.0.0-dev";
6094
+ var SERVER_VERSION = true ? "2.6.0-next.33" : "0.0.0-dev";
6016
6095
 
6017
6096
  // src/server/handlers/capabilities.js
6018
6097
  var SERVER_FEATURES = Object.freeze([
@@ -6125,7 +6204,10 @@ var SERVER_FEATURES = Object.freeze([
6125
6204
  // 工厂配置认 maxDeliveryRetries(投递失败的重试次数上限,默认 3)。
6126
6205
  "max-delivery-retries",
6127
6206
  // Per-task pre-commit retry limit and fire receipt retry decision.
6128
- "max-generation-retries"
6207
+ "max-generation-retries",
6208
+ // onBeforeFire 认 { defer: { afterMs } }:这次不生成,过一会儿再来问(不占重试
6209
+ // 次数);onFireSettled 的 status 多一种 'deferred',带 retryAfter。
6210
+ "before-fire-defer"
6129
6211
  ]);
6130
6212
  function createCapabilitiesHandler(ctx) {
6131
6213
  async function GET(url, headers) {
@@ -6733,6 +6815,7 @@ export {
6733
6815
  DEFAULT_TOTAL_TIMEOUT_MS,
6734
6816
  DEFAULT_MAX_SCHEDULED_TASKS_PER_FIRE,
6735
6817
  MIN_SCHEDULE_LEAD_MS,
6818
+ MAX_DEFER_AFTER_MS,
6736
6819
  createScheduleMessageHandler,
6737
6820
  DEFAULT_CLAIM_LEASE_MS,
6738
6821
  DEFAULT_LEASE_HEARTBEAT_MS,
@@ -27,7 +27,8 @@
27
27
 
28
28
 
29
29
 
30
- var _chunkKZT3YC62cjs = require('./chunk-KZT3YC62.cjs');
30
+
31
+ var _chunkAM2MMY46cjs = require('./chunk-AM2MMY46.cjs');
31
32
  require('./chunk-7NMQFTDJ.cjs');
32
33
 
33
34
 
@@ -58,4 +59,5 @@ require('./chunk-7NMQFTDJ.cjs');
58
59
 
59
60
 
60
61
 
61
- exports.DEFAULT_MAX_DELIVERY_RETRIES = _chunkKZT3YC62cjs.DEFAULT_MAX_DELIVERY_RETRIES; exports.DEFAULT_MAX_REQUEST_BODY_BYTES = _chunkKZT3YC62cjs.DEFAULT_MAX_REQUEST_BODY_BYTES; exports.MAX_PUSH_PAYLOAD_BYTES = _chunkKZT3YC62cjs.MAX_PUSH_PAYLOAD_BYTES; exports.NonRetryableError = _chunkKZT3YC62cjs.NonRetryableError; exports.PUSH_ENVELOPE_RESERVED_BYTES = _chunkKZT3YC62cjs.PUSH_ENVELOPE_RESERVED_BYTES; exports.SCHEMA_VERSION = _chunkKZT3YC62cjs.SCHEMA_VERSION; exports.WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES = _chunkKZT3YC62cjs.WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES; exports.WEB_PUSH_MAX_BODY_BYTES = _chunkKZT3YC62cjs.WEB_PUSH_MAX_BODY_BYTES; exports.advanceOccurrence = _chunkKZT3YC62cjs.advanceOccurrence; exports.createD1Adapter = _chunkKZT3YC62cjs.createD1Adapter; exports.createSingleUserCloudflareWorker = _chunkKZT3YC62cjs.createSingleUserCloudflareWorker; exports.createSingleUserServer = _chunkKZT3YC62cjs.createSingleUserServer; exports.createWebCryptoWebPush = _chunkKZT3YC62cjs.createWebCryptoWebPush; exports.decryptFromStorage = _chunkKZT3YC62cjs.decryptFromStorage; exports.decryptPayload = _chunkKZT3YC62cjs.decryptPayload; exports.deriveUserEncryptionKey = _chunkKZT3YC62cjs.deriveUserEncryptionKey; exports.encryptForStorage = _chunkKZT3YC62cjs.encryptForStorage; exports.ensureSchema = _chunkKZT3YC62cjs.ensureSchema; exports.getSchemaVersion = _chunkKZT3YC62cjs.getSchemaVersion; exports.isNonRetryableError = _chunkKZT3YC62cjs.isNonRetryableError; exports.isValidTimeZoneId = _chunkKZT3YC62cjs.isValidTimeZoneId; exports.measurePushPayload = _chunkKZT3YC62cjs.measurePushPayload; exports.nextFutureOccurrence = _chunkKZT3YC62cjs.nextFutureOccurrence; exports.planNextOccurrence = _chunkKZT3YC62cjs.planNextOccurrence; exports.readRequestBody = _chunkKZT3YC62cjs.readRequestBody; exports.runScheduledTick = _chunkKZT3YC62cjs.runScheduledTick; exports.runTask = _chunkKZT3YC62cjs.runTask; exports.summarizeErrorCause = _chunkKZT3YC62cjs.summarizeErrorCause;
62
+
63
+ exports.DEFAULT_MAX_DELIVERY_RETRIES = _chunkAM2MMY46cjs.DEFAULT_MAX_DELIVERY_RETRIES; exports.DEFAULT_MAX_REQUEST_BODY_BYTES = _chunkAM2MMY46cjs.DEFAULT_MAX_REQUEST_BODY_BYTES; exports.MAX_DEFER_AFTER_MS = _chunkAM2MMY46cjs.MAX_DEFER_AFTER_MS; exports.MAX_PUSH_PAYLOAD_BYTES = _chunkAM2MMY46cjs.MAX_PUSH_PAYLOAD_BYTES; exports.NonRetryableError = _chunkAM2MMY46cjs.NonRetryableError; exports.PUSH_ENVELOPE_RESERVED_BYTES = _chunkAM2MMY46cjs.PUSH_ENVELOPE_RESERVED_BYTES; exports.SCHEMA_VERSION = _chunkAM2MMY46cjs.SCHEMA_VERSION; exports.WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES = _chunkAM2MMY46cjs.WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES; exports.WEB_PUSH_MAX_BODY_BYTES = _chunkAM2MMY46cjs.WEB_PUSH_MAX_BODY_BYTES; exports.advanceOccurrence = _chunkAM2MMY46cjs.advanceOccurrence; exports.createD1Adapter = _chunkAM2MMY46cjs.createD1Adapter; exports.createSingleUserCloudflareWorker = _chunkAM2MMY46cjs.createSingleUserCloudflareWorker; exports.createSingleUserServer = _chunkAM2MMY46cjs.createSingleUserServer; exports.createWebCryptoWebPush = _chunkAM2MMY46cjs.createWebCryptoWebPush; exports.decryptFromStorage = _chunkAM2MMY46cjs.decryptFromStorage; exports.decryptPayload = _chunkAM2MMY46cjs.decryptPayload; exports.deriveUserEncryptionKey = _chunkAM2MMY46cjs.deriveUserEncryptionKey; exports.encryptForStorage = _chunkAM2MMY46cjs.encryptForStorage; exports.ensureSchema = _chunkAM2MMY46cjs.ensureSchema; exports.getSchemaVersion = _chunkAM2MMY46cjs.getSchemaVersion; exports.isNonRetryableError = _chunkAM2MMY46cjs.isNonRetryableError; exports.isValidTimeZoneId = _chunkAM2MMY46cjs.isValidTimeZoneId; exports.measurePushPayload = _chunkAM2MMY46cjs.measurePushPayload; exports.nextFutureOccurrence = _chunkAM2MMY46cjs.nextFutureOccurrence; exports.planNextOccurrence = _chunkAM2MMY46cjs.planNextOccurrence; exports.readRequestBody = _chunkAM2MMY46cjs.readRequestBody; exports.runScheduledTick = _chunkAM2MMY46cjs.runScheduledTick; exports.runTask = _chunkAM2MMY46cjs.runTask; exports.summarizeErrorCause = _chunkAM2MMY46cjs.summarizeErrorCause;
@@ -1,6 +1,7 @@
1
1
  export { createSingleUserCloudflareWorker } from "./cloudflare/single-user-worker.js";
2
2
  export { createSingleUserServer } from "./single-user.js";
3
3
  export { createD1Adapter } from "./adapters/d1.js";
4
+ export { MAX_DEFER_AFTER_MS } from "./lib/agentic-fire.js";
4
5
  export { createWebCryptoWebPush, measurePushPayload, MAX_PUSH_PAYLOAD_BYTES, PUSH_ENVELOPE_RESERVED_BYTES, WEB_PUSH_MAX_BODY_BYTES, WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES } from "./lib/webpush-webcrypto.js";
5
6
  export { runScheduledTick, runTask, DEFAULT_MAX_DELIVERY_RETRIES } from "./lib/run-tick.js";
6
7
  export { getSchemaVersion, ensureSchema, SCHEMA_VERSION } from "./lib/schema-version.js";
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  DEFAULT_MAX_DELIVERY_RETRIES,
3
3
  DEFAULT_MAX_REQUEST_BODY_BYTES,
4
+ MAX_DEFER_AFTER_MS,
4
5
  MAX_PUSH_PAYLOAD_BYTES,
5
6
  NonRetryableError,
6
7
  PUSH_ENVELOPE_RESERVED_BYTES,
@@ -27,11 +28,12 @@ import {
27
28
  runScheduledTick,
28
29
  runTask,
29
30
  summarizeErrorCause
30
- } from "./chunk-INNZZFGJ.mjs";
31
+ } from "./chunk-Q4VQ3NVR.mjs";
31
32
  import "./chunk-GN44PST5.mjs";
32
33
  export {
33
34
  DEFAULT_MAX_DELIVERY_RETRIES,
34
35
  DEFAULT_MAX_REQUEST_BODY_BYTES,
36
+ MAX_DEFER_AFTER_MS,
35
37
  MAX_PUSH_PAYLOAD_BYTES,
36
38
  NonRetryableError,
37
39
  PUSH_ENVELOPE_RESERVED_BYTES,
package/dist/index.cjs CHANGED
@@ -72,7 +72,8 @@
72
72
 
73
73
 
74
74
 
75
- var _chunkKZT3YC62cjs = require('./chunk-KZT3YC62.cjs');
75
+
76
+ var _chunkAM2MMY46cjs = require('./chunk-AM2MMY46.cjs');
76
77
  require('./chunk-7NMQFTDJ.cjs');
77
78
 
78
79
  // src/server/adapters/factory.js
@@ -106,7 +107,7 @@ async function createAdapter(config) {
106
107
  // src/server/handlers/init-tenant.js
107
108
  function createInitTenantHandler(ctx) {
108
109
  async function POST(headers, body) {
109
- const initSecret = _chunkKZT3YC62cjs.getHeader.call(void 0, headers, "x-init-secret");
110
+ const initSecret = _chunkAM2MMY46cjs.getHeader.call(void 0, headers, "x-init-secret");
110
111
  if (ctx.tenant.initSecret && initSecret !== ctx.tenant.initSecret) {
111
112
  return {
112
113
  status: 401,
@@ -119,7 +120,7 @@ function createInitTenantHandler(ctx) {
119
120
  }
120
121
  };
121
122
  }
122
- const parsed = _chunkKZT3YC62cjs.parseJsonBody.call(void 0, body);
123
+ const parsed = _chunkAM2MMY46cjs.parseJsonBody.call(void 0, body);
123
124
  if (!parsed.ok) {
124
125
  return {
125
126
  status: 400,
@@ -133,7 +134,7 @@ function createInitTenantHandler(ctx) {
133
134
  const tenantId = payload.tenantId || void 0;
134
135
  const driver = payload.driver;
135
136
  const databaseUrl = payload.databaseUrl || payload.connectionString;
136
- if (tenantId && !_chunkKZT3YC62cjs.isValidUUIDv4.call(void 0, tenantId)) {
137
+ if (tenantId && !_chunkAM2MMY46cjs.isValidUUIDv4.call(void 0, tenantId)) {
137
138
  return {
138
139
  status: 400,
139
140
  body: {
@@ -253,7 +254,7 @@ function createSendNotificationsHandler(ctx) {
253
254
  }
254
255
  };
255
256
  }
256
- const data = await _chunkKZT3YC62cjs.runScheduledTick.call(void 0, { ...ctx, db, masterKey });
257
+ const data = await _chunkAM2MMY46cjs.runScheduledTick.call(void 0, { ...ctx, db, masterKey });
257
258
  return { status: 200, body: { success: true, data } };
258
259
  }
259
260
  return { POST };
@@ -275,16 +276,16 @@ function encryptConfig(config, kekBuffer) {
275
276
  const plaintext = Buffer.from(JSON.stringify(config), "utf8");
276
277
  const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
277
278
  const authTag = cipher.getAuthTag();
278
- return `v1.${_chunkKZT3YC62cjs.bytesToBase64Url.call(void 0, iv)}.${_chunkKZT3YC62cjs.bytesToBase64Url.call(void 0, authTag)}.${_chunkKZT3YC62cjs.bytesToBase64Url.call(void 0, ciphertext)}`;
279
+ return `v1.${_chunkAM2MMY46cjs.bytesToBase64Url.call(void 0, iv)}.${_chunkAM2MMY46cjs.bytesToBase64Url.call(void 0, authTag)}.${_chunkAM2MMY46cjs.bytesToBase64Url.call(void 0, ciphertext)}`;
279
280
  }
280
281
  function decryptConfig(encrypted, kekBuffer) {
281
282
  const parts = String(encrypted || "").split(".");
282
283
  if (parts.length !== 4 || parts[0] !== "v1") {
283
284
  throw new Error("INVALID_TENANT_CONFIG");
284
285
  }
285
- const iv = _chunkKZT3YC62cjs.base64UrlToBytes.call(void 0, parts[1]);
286
- const authTag = _chunkKZT3YC62cjs.base64UrlToBytes.call(void 0, parts[2]);
287
- const ciphertext = _chunkKZT3YC62cjs.base64UrlToBytes.call(void 0, parts[3]);
286
+ const iv = _chunkAM2MMY46cjs.base64UrlToBytes.call(void 0, parts[1]);
287
+ const authTag = _chunkAM2MMY46cjs.base64UrlToBytes.call(void 0, parts[2]);
288
+ const ciphertext = _chunkAM2MMY46cjs.base64UrlToBytes.call(void 0, parts[3]);
288
289
  const decipher = _crypto.createDecipheriv.call(void 0, "aes-256-gcm", kekBuffer, iv);
289
290
  decipher.setAuthTag(authTag);
290
291
  const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
@@ -352,7 +353,7 @@ function createTenantBlobStore(options) {
352
353
  // src/server/tenant/token.js
353
354
 
354
355
  function sign(input, secret) {
355
- return _chunkKZT3YC62cjs.bytesToBase64Url.call(void 0, _crypto.createHmac.call(void 0, "sha256", secret).update(input).digest());
356
+ return _chunkAM2MMY46cjs.bytesToBase64Url.call(void 0, _crypto.createHmac.call(void 0, "sha256", secret).update(input).digest());
356
357
  }
357
358
  function nowEpochSeconds() {
358
359
  return Math.floor(Date.now() / 1e3);
@@ -368,8 +369,8 @@ function createTenantToken(params, secret) {
368
369
  v: 1
369
370
  };
370
371
  const header = { alg: "HS256", typ: "JWT" };
371
- const encodedHeader = _chunkKZT3YC62cjs.bytesToBase64Url.call(void 0, _chunkKZT3YC62cjs.utf8.call(void 0, JSON.stringify(header)));
372
- const encodedPayload = _chunkKZT3YC62cjs.bytesToBase64Url.call(void 0, _chunkKZT3YC62cjs.utf8.call(void 0, JSON.stringify(payload)));
372
+ const encodedHeader = _chunkAM2MMY46cjs.bytesToBase64Url.call(void 0, _chunkAM2MMY46cjs.utf8.call(void 0, JSON.stringify(header)));
373
+ const encodedPayload = _chunkAM2MMY46cjs.bytesToBase64Url.call(void 0, _chunkAM2MMY46cjs.utf8.call(void 0, JSON.stringify(payload)));
373
374
  const signingInput = `${encodedHeader}.${encodedPayload}`;
374
375
  const signature = sign(signingInput, secret);
375
376
  return `${signingInput}.${signature}`;
@@ -388,17 +389,17 @@ function verifyTenantToken(token, secret, options = {}) {
388
389
  let receivedBytes;
389
390
  let expectedBytes;
390
391
  try {
391
- receivedBytes = _chunkKZT3YC62cjs.base64UrlToBytes.call(void 0, receivedSignature);
392
- expectedBytes = _chunkKZT3YC62cjs.base64UrlToBytes.call(void 0, expectedSignature);
392
+ receivedBytes = _chunkAM2MMY46cjs.base64UrlToBytes.call(void 0, receivedSignature);
393
+ expectedBytes = _chunkAM2MMY46cjs.base64UrlToBytes.call(void 0, expectedSignature);
393
394
  } catch (e) {
394
395
  throw new Error("INVALID_TENANT_AUTH");
395
396
  }
396
- if (!_chunkKZT3YC62cjs.timingSafeEqualBytes.call(void 0, receivedBytes, expectedBytes)) {
397
+ if (!_chunkAM2MMY46cjs.timingSafeEqualBytes.call(void 0, receivedBytes, expectedBytes)) {
397
398
  throw new Error("INVALID_TENANT_AUTH");
398
399
  }
399
400
  let payload;
400
401
  try {
401
- payload = JSON.parse(_chunkKZT3YC62cjs.utf8Decode.call(void 0, _chunkKZT3YC62cjs.base64UrlToBytes.call(void 0, encodedPayload)));
402
+ payload = JSON.parse(_chunkAM2MMY46cjs.utf8Decode.call(void 0, _chunkAM2MMY46cjs.base64UrlToBytes.call(void 0, encodedPayload)));
402
403
  } catch (e2) {
403
404
  throw new Error("INVALID_TENANT_AUTH");
404
405
  }
@@ -626,15 +627,15 @@ async function createReiServer(config) {
626
627
  return {
627
628
  handlers: {
628
629
  initTenant: createInitTenantHandler(ctx),
629
- getUserKey: _chunkKZT3YC62cjs.createGetUserKeyHandler.call(void 0, ctx),
630
- scheduleMessage: _chunkKZT3YC62cjs.createScheduleMessageHandler.call(void 0, ctx),
630
+ getUserKey: _chunkAM2MMY46cjs.createGetUserKeyHandler.call(void 0, ctx),
631
+ scheduleMessage: _chunkAM2MMY46cjs.createScheduleMessageHandler.call(void 0, ctx),
631
632
  sendNotifications: createSendNotificationsHandler(ctx),
632
- updateMessage: _chunkKZT3YC62cjs.createUpdateMessageHandler.call(void 0, ctx),
633
- cancelMessage: _chunkKZT3YC62cjs.createCancelMessageHandler.call(void 0, ctx),
634
- messages: _chunkKZT3YC62cjs.createMessagesHandler.call(void 0, ctx),
635
- getMessage: _chunkKZT3YC62cjs.createGetMessageHandler.call(void 0, ctx),
636
- pushSubscription: _chunkKZT3YC62cjs.createPushSubscriptionHandler.call(void 0, ctx),
637
- llmCredentials: _chunkKZT3YC62cjs.createLlmCredentialsHandler.call(void 0, ctx)
633
+ updateMessage: _chunkAM2MMY46cjs.createUpdateMessageHandler.call(void 0, ctx),
634
+ cancelMessage: _chunkAM2MMY46cjs.createCancelMessageHandler.call(void 0, ctx),
635
+ messages: _chunkAM2MMY46cjs.createMessagesHandler.call(void 0, ctx),
636
+ getMessage: _chunkAM2MMY46cjs.createGetMessageHandler.call(void 0, ctx),
637
+ pushSubscription: _chunkAM2MMY46cjs.createPushSubscriptionHandler.call(void 0, ctx),
638
+ llmCredentials: _chunkAM2MMY46cjs.createLlmCredentialsHandler.call(void 0, ctx)
638
639
  }
639
640
  };
640
641
  }
@@ -701,4 +702,5 @@ async function createReiServer(config) {
701
702
 
702
703
 
703
704
 
704
- exports.CORS_ALLOW_HEADERS = _chunkKZT3YC62cjs.CORS_ALLOW_HEADERS; exports.CORS_ALLOW_METHODS = _chunkKZT3YC62cjs.CORS_ALLOW_METHODS; exports.CRED_ID_MAX_LENGTH = _chunkKZT3YC62cjs.CRED_ID_MAX_LENGTH; exports.CRED_PUT_BATCH_MAX = _chunkKZT3YC62cjs.CRED_PUT_BATCH_MAX; exports.CRED_REFS_MAX_ENTRIES = _chunkKZT3YC62cjs.CRED_REFS_MAX_ENTRIES; exports.CRED_ROWS_PER_USER_MAX = _chunkKZT3YC62cjs.CRED_ROWS_PER_USER_MAX; exports.DEFAULT_CLAIM_LEASE_MS = _chunkKZT3YC62cjs.DEFAULT_CLAIM_LEASE_MS; exports.DEFAULT_HEARTBEAT_LEASE_TTL_MS = _chunkKZT3YC62cjs.DEFAULT_HEARTBEAT_LEASE_TTL_MS; exports.DEFAULT_LEASE_HEARTBEAT_MS = _chunkKZT3YC62cjs.DEFAULT_LEASE_HEARTBEAT_MS; exports.DEFAULT_MAX_DELIVERY_RETRIES = _chunkKZT3YC62cjs.DEFAULT_MAX_DELIVERY_RETRIES; exports.DEFAULT_MAX_REQUEST_BODY_BYTES = _chunkKZT3YC62cjs.DEFAULT_MAX_REQUEST_BODY_BYTES; exports.DEFAULT_MAX_SCHEDULED_TASKS_PER_FIRE = _chunkKZT3YC62cjs.DEFAULT_MAX_SCHEDULED_TASKS_PER_FIRE; exports.DEFAULT_MAX_TOOL_ITERATIONS = _chunkKZT3YC62cjs.DEFAULT_MAX_TOOL_ITERATIONS; exports.DEFAULT_TOTAL_TIMEOUT_MS = _chunkKZT3YC62cjs.DEFAULT_TOTAL_TIMEOUT_MS; exports.DeploymentConfigError = _chunkKZT3YC62cjs.DeploymentConfigError; exports.MAX_PUSH_PAYLOAD_BYTES = _chunkKZT3YC62cjs.MAX_PUSH_PAYLOAD_BYTES; exports.MAX_TASK_PAYLOAD_BYTES = _chunkKZT3YC62cjs.MAX_TASK_PAYLOAD_BYTES; exports.MIN_SCHEDULE_LEAD_MS = _chunkKZT3YC62cjs.MIN_SCHEDULE_LEAD_MS; exports.NonRetryableError = _chunkKZT3YC62cjs.NonRetryableError; exports.PUSH_ENVELOPE_RESERVED_BYTES = _chunkKZT3YC62cjs.PUSH_ENVELOPE_RESERVED_BYTES; exports.SCHEMA_VERSION = _chunkKZT3YC62cjs.SCHEMA_VERSION; exports.WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES = _chunkKZT3YC62cjs.WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES; exports.WEB_PUSH_MAX_BODY_BYTES = _chunkKZT3YC62cjs.WEB_PUSH_MAX_BODY_BYTES; exports.advanceOccurrence = _chunkKZT3YC62cjs.advanceOccurrence; exports.createAdapter = createAdapter; exports.createD1Adapter = _chunkKZT3YC62cjs.createD1Adapter; exports.createReiServer = createReiServer; exports.createSingleUserCloudflareWorker = _chunkKZT3YC62cjs.createSingleUserCloudflareWorker; exports.createSingleUserServer = _chunkKZT3YC62cjs.createSingleUserServer; exports.createTenantToken = createTenantToken; exports.createWebCryptoWebPush = _chunkKZT3YC62cjs.createWebCryptoWebPush; exports.decryptFromStorage = _chunkKZT3YC62cjs.decryptFromStorage; exports.decryptPayload = _chunkKZT3YC62cjs.decryptPayload; exports.deriveUserEncryptionKey = _chunkKZT3YC62cjs.deriveUserEncryptionKey; exports.encryptForStorage = _chunkKZT3YC62cjs.encryptForStorage; exports.ensureSchema = _chunkKZT3YC62cjs.ensureSchema; exports.getSchemaVersion = _chunkKZT3YC62cjs.getSchemaVersion; exports.hasChatCredRef = _chunkKZT3YC62cjs.hasChatCredRef; exports.isNonRetryableError = _chunkKZT3YC62cjs.isNonRetryableError; exports.isValidCredId = _chunkKZT3YC62cjs.isValidCredId; exports.isValidISO8601 = _chunkKZT3YC62cjs.isValidISO8601; exports.isValidTimeZoneId = _chunkKZT3YC62cjs.isValidTimeZoneId; exports.isValidUUID = _chunkKZT3YC62cjs.isValidUUID; exports.isValidUUIDv4 = _chunkKZT3YC62cjs.isValidUUIDv4; exports.isValidUrl = _chunkKZT3YC62cjs.isValidUrl; exports.measurePushPayload = _chunkKZT3YC62cjs.measurePushPayload; exports.nextFutureOccurrence = _chunkKZT3YC62cjs.nextFutureOccurrence; exports.planNextOccurrence = _chunkKZT3YC62cjs.planNextOccurrence; exports.readRequestBody = _chunkKZT3YC62cjs.readRequestBody; exports.resolveLlmCredential = _chunkKZT3YC62cjs.resolveLlmCredential; exports.runScheduledTick = _chunkKZT3YC62cjs.runScheduledTick; exports.runTask = _chunkKZT3YC62cjs.runTask; exports.sanitizeErrorSummary = _chunkKZT3YC62cjs.sanitizeErrorSummary; exports.summarizeErrorCause = _chunkKZT3YC62cjs.summarizeErrorCause; exports.supportsLlmCredentialsStore = _chunkKZT3YC62cjs.supportsLlmCredentialsStore; exports.validateAvatarUrl = _chunkKZT3YC62cjs.validateAvatarUrl; exports.validateCredRefs = _chunkKZT3YC62cjs.validateCredRefs; exports.validateCredValue = _chunkKZT3YC62cjs.validateCredValue; exports.validateLlmMessagesArray = _chunkKZT3YC62cjs.validateLlmMessagesArray; exports.validateScheduleMessagePayload = _chunkKZT3YC62cjs.validateScheduleMessagePayload; exports.validateSplitPattern = _chunkKZT3YC62cjs.validateSplitPattern; exports.verifyTenantToken = verifyTenantToken;
705
+
706
+ exports.CORS_ALLOW_HEADERS = _chunkAM2MMY46cjs.CORS_ALLOW_HEADERS; exports.CORS_ALLOW_METHODS = _chunkAM2MMY46cjs.CORS_ALLOW_METHODS; exports.CRED_ID_MAX_LENGTH = _chunkAM2MMY46cjs.CRED_ID_MAX_LENGTH; exports.CRED_PUT_BATCH_MAX = _chunkAM2MMY46cjs.CRED_PUT_BATCH_MAX; exports.CRED_REFS_MAX_ENTRIES = _chunkAM2MMY46cjs.CRED_REFS_MAX_ENTRIES; exports.CRED_ROWS_PER_USER_MAX = _chunkAM2MMY46cjs.CRED_ROWS_PER_USER_MAX; exports.DEFAULT_CLAIM_LEASE_MS = _chunkAM2MMY46cjs.DEFAULT_CLAIM_LEASE_MS; exports.DEFAULT_HEARTBEAT_LEASE_TTL_MS = _chunkAM2MMY46cjs.DEFAULT_HEARTBEAT_LEASE_TTL_MS; exports.DEFAULT_LEASE_HEARTBEAT_MS = _chunkAM2MMY46cjs.DEFAULT_LEASE_HEARTBEAT_MS; exports.DEFAULT_MAX_DELIVERY_RETRIES = _chunkAM2MMY46cjs.DEFAULT_MAX_DELIVERY_RETRIES; exports.DEFAULT_MAX_REQUEST_BODY_BYTES = _chunkAM2MMY46cjs.DEFAULT_MAX_REQUEST_BODY_BYTES; exports.DEFAULT_MAX_SCHEDULED_TASKS_PER_FIRE = _chunkAM2MMY46cjs.DEFAULT_MAX_SCHEDULED_TASKS_PER_FIRE; exports.DEFAULT_MAX_TOOL_ITERATIONS = _chunkAM2MMY46cjs.DEFAULT_MAX_TOOL_ITERATIONS; exports.DEFAULT_TOTAL_TIMEOUT_MS = _chunkAM2MMY46cjs.DEFAULT_TOTAL_TIMEOUT_MS; exports.DeploymentConfigError = _chunkAM2MMY46cjs.DeploymentConfigError; exports.MAX_DEFER_AFTER_MS = _chunkAM2MMY46cjs.MAX_DEFER_AFTER_MS; exports.MAX_PUSH_PAYLOAD_BYTES = _chunkAM2MMY46cjs.MAX_PUSH_PAYLOAD_BYTES; exports.MAX_TASK_PAYLOAD_BYTES = _chunkAM2MMY46cjs.MAX_TASK_PAYLOAD_BYTES; exports.MIN_SCHEDULE_LEAD_MS = _chunkAM2MMY46cjs.MIN_SCHEDULE_LEAD_MS; exports.NonRetryableError = _chunkAM2MMY46cjs.NonRetryableError; exports.PUSH_ENVELOPE_RESERVED_BYTES = _chunkAM2MMY46cjs.PUSH_ENVELOPE_RESERVED_BYTES; exports.SCHEMA_VERSION = _chunkAM2MMY46cjs.SCHEMA_VERSION; exports.WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES = _chunkAM2MMY46cjs.WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES; exports.WEB_PUSH_MAX_BODY_BYTES = _chunkAM2MMY46cjs.WEB_PUSH_MAX_BODY_BYTES; exports.advanceOccurrence = _chunkAM2MMY46cjs.advanceOccurrence; exports.createAdapter = createAdapter; exports.createD1Adapter = _chunkAM2MMY46cjs.createD1Adapter; exports.createReiServer = createReiServer; exports.createSingleUserCloudflareWorker = _chunkAM2MMY46cjs.createSingleUserCloudflareWorker; exports.createSingleUserServer = _chunkAM2MMY46cjs.createSingleUserServer; exports.createTenantToken = createTenantToken; exports.createWebCryptoWebPush = _chunkAM2MMY46cjs.createWebCryptoWebPush; exports.decryptFromStorage = _chunkAM2MMY46cjs.decryptFromStorage; exports.decryptPayload = _chunkAM2MMY46cjs.decryptPayload; exports.deriveUserEncryptionKey = _chunkAM2MMY46cjs.deriveUserEncryptionKey; exports.encryptForStorage = _chunkAM2MMY46cjs.encryptForStorage; exports.ensureSchema = _chunkAM2MMY46cjs.ensureSchema; exports.getSchemaVersion = _chunkAM2MMY46cjs.getSchemaVersion; exports.hasChatCredRef = _chunkAM2MMY46cjs.hasChatCredRef; exports.isNonRetryableError = _chunkAM2MMY46cjs.isNonRetryableError; exports.isValidCredId = _chunkAM2MMY46cjs.isValidCredId; exports.isValidISO8601 = _chunkAM2MMY46cjs.isValidISO8601; exports.isValidTimeZoneId = _chunkAM2MMY46cjs.isValidTimeZoneId; exports.isValidUUID = _chunkAM2MMY46cjs.isValidUUID; exports.isValidUUIDv4 = _chunkAM2MMY46cjs.isValidUUIDv4; exports.isValidUrl = _chunkAM2MMY46cjs.isValidUrl; exports.measurePushPayload = _chunkAM2MMY46cjs.measurePushPayload; exports.nextFutureOccurrence = _chunkAM2MMY46cjs.nextFutureOccurrence; exports.planNextOccurrence = _chunkAM2MMY46cjs.planNextOccurrence; exports.readRequestBody = _chunkAM2MMY46cjs.readRequestBody; exports.resolveLlmCredential = _chunkAM2MMY46cjs.resolveLlmCredential; exports.runScheduledTick = _chunkAM2MMY46cjs.runScheduledTick; exports.runTask = _chunkAM2MMY46cjs.runTask; exports.sanitizeErrorSummary = _chunkAM2MMY46cjs.sanitizeErrorSummary; exports.summarizeErrorCause = _chunkAM2MMY46cjs.summarizeErrorCause; exports.supportsLlmCredentialsStore = _chunkAM2MMY46cjs.supportsLlmCredentialsStore; exports.validateAvatarUrl = _chunkAM2MMY46cjs.validateAvatarUrl; exports.validateCredRefs = _chunkAM2MMY46cjs.validateCredRefs; exports.validateCredValue = _chunkAM2MMY46cjs.validateCredValue; exports.validateLlmMessagesArray = _chunkAM2MMY46cjs.validateLlmMessagesArray; exports.validateScheduleMessagePayload = _chunkAM2MMY46cjs.validateScheduleMessagePayload; exports.validateSplitPattern = _chunkAM2MMY46cjs.validateSplitPattern; exports.verifyTenantToken = verifyTenantToken;
package/dist/index.d.cts CHANGED
@@ -175,7 +175,7 @@ export type ReiServer = {
175
175
  export { runScheduledTick, runTask, DEFAULT_CLAIM_LEASE_MS, DEFAULT_LEASE_HEARTBEAT_MS, DEFAULT_HEARTBEAT_LEASE_TTL_MS, DEFAULT_MAX_DELIVERY_RETRIES, sanitizeErrorSummary } from "./lib/run-tick.js";
176
176
  export { NonRetryableError, DeploymentConfigError, isNonRetryableError, summarizeErrorCause } from "./lib/errors.js";
177
177
  export { getSchemaVersion, ensureSchema, SCHEMA_VERSION } from "./lib/schema-version.js";
178
- export { DEFAULT_MAX_TOOL_ITERATIONS, DEFAULT_TOTAL_TIMEOUT_MS, DEFAULT_MAX_SCHEDULED_TASKS_PER_FIRE, MIN_SCHEDULE_LEAD_MS } from "./lib/agentic-fire.js";
178
+ export { DEFAULT_MAX_TOOL_ITERATIONS, DEFAULT_TOTAL_TIMEOUT_MS, DEFAULT_MAX_SCHEDULED_TASKS_PER_FIRE, MIN_SCHEDULE_LEAD_MS, MAX_DEFER_AFTER_MS } from "./lib/agentic-fire.js";
179
179
  export { CORS_ALLOW_HEADERS, CORS_ALLOW_METHODS, createSingleUserCloudflareWorker } from "./cloudflare/single-user-worker.js";
180
180
  export { createWebCryptoWebPush, measurePushPayload, MAX_PUSH_PAYLOAD_BYTES, PUSH_ENVELOPE_RESERVED_BYTES, WEB_PUSH_MAX_BODY_BYTES, WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES } from "./lib/webpush-webcrypto.js";
181
181
  export { deriveUserEncryptionKey, decryptPayload, encryptForStorage, decryptFromStorage } from "./lib/encryption.js";
package/dist/index.d.ts CHANGED
@@ -175,7 +175,7 @@ export type ReiServer = {
175
175
  export { runScheduledTick, runTask, DEFAULT_CLAIM_LEASE_MS, DEFAULT_LEASE_HEARTBEAT_MS, DEFAULT_HEARTBEAT_LEASE_TTL_MS, DEFAULT_MAX_DELIVERY_RETRIES, sanitizeErrorSummary } from "./lib/run-tick.js";
176
176
  export { NonRetryableError, DeploymentConfigError, isNonRetryableError, summarizeErrorCause } from "./lib/errors.js";
177
177
  export { getSchemaVersion, ensureSchema, SCHEMA_VERSION } from "./lib/schema-version.js";
178
- export { DEFAULT_MAX_TOOL_ITERATIONS, DEFAULT_TOTAL_TIMEOUT_MS, DEFAULT_MAX_SCHEDULED_TASKS_PER_FIRE, MIN_SCHEDULE_LEAD_MS } from "./lib/agentic-fire.js";
178
+ export { DEFAULT_MAX_TOOL_ITERATIONS, DEFAULT_TOTAL_TIMEOUT_MS, DEFAULT_MAX_SCHEDULED_TASKS_PER_FIRE, MIN_SCHEDULE_LEAD_MS, MAX_DEFER_AFTER_MS } from "./lib/agentic-fire.js";
179
179
  export { CORS_ALLOW_HEADERS, CORS_ALLOW_METHODS, createSingleUserCloudflareWorker } from "./cloudflare/single-user-worker.js";
180
180
  export { createWebCryptoWebPush, measurePushPayload, MAX_PUSH_PAYLOAD_BYTES, PUSH_ENVELOPE_RESERVED_BYTES, WEB_PUSH_MAX_BODY_BYTES, WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES } from "./lib/webpush-webcrypto.js";
181
181
  export { deriveUserEncryptionKey, decryptPayload, encryptForStorage, decryptFromStorage } from "./lib/encryption.js";
package/dist/index.mjs CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  DEFAULT_MAX_TOOL_ITERATIONS,
15
15
  DEFAULT_TOTAL_TIMEOUT_MS,
16
16
  DeploymentConfigError,
17
+ MAX_DEFER_AFTER_MS,
17
18
  MAX_PUSH_PAYLOAD_BYTES,
18
19
  MAX_TASK_PAYLOAD_BYTES,
19
20
  MIN_SCHEDULE_LEAD_MS,
@@ -72,7 +73,7 @@ import {
72
73
  validateLlmMessagesArray,
73
74
  validateScheduleMessagePayload,
74
75
  validateSplitPattern
75
- } from "./chunk-INNZZFGJ.mjs";
76
+ } from "./chunk-Q4VQ3NVR.mjs";
76
77
  import "./chunk-GN44PST5.mjs";
77
78
 
78
79
  // src/server/adapters/factory.js
@@ -654,6 +655,7 @@ export {
654
655
  DEFAULT_MAX_TOOL_ITERATIONS,
655
656
  DEFAULT_TOTAL_TIMEOUT_MS,
656
657
  DeploymentConfigError,
658
+ MAX_DEFER_AFTER_MS,
657
659
  MAX_PUSH_PAYLOAD_BYTES,
658
660
  MAX_TASK_PAYLOAD_BYTES,
659
661
  MIN_SCHEDULE_LEAD_MS,
@@ -57,12 +57,18 @@ export function buildHookTask(task: any, decryptedPayload: any): Readonly<{
57
57
  * @param {Object} args.decryptedPayload - decrypted task payload (has credentials; they stop here)
58
58
  * @param {string} args.userKey - per-user storage key (for readState decryption)
59
59
  * @param {Object} args.ctx - processor ctx ({ db, webpush, vapid, hooks, maxToolIterations, totalTimeoutMs, maxScheduledTasksPerFire })
60
- * @returns {Promise<{ handled: false } | { handled: true, result: { success: true, messagesSent: number, status: 'finished'|'skipped', iterations: number } }>}
60
+ * @returns {Promise<{ handled: false }
61
+ * | { handled: true, result: { success: true, messagesSent: number, status: 'finished'|'skipped', iterations: number } }
62
+ * | { handled: true, result: { success: false, deferred: true, retryAfter: string, messagesSent: 0, status: 'deferred', iterations: 0 } }>}
61
63
  * `handled: false` → caller falls back to the legacy frozen-prompt path
62
64
  * (onBeforeFire returned null).
63
65
  * `onBeforeFire` may also return `{ skip: true }` to complete the fire
64
66
  * before the first LLM call → `status: 'skipped', iterations: 0`, same
65
67
  * success handling as the post-LLM skip-push path.
68
+ * `{ defer: { afterMs } }` → `status: 'deferred'`,`retryAfter` 是「什么时候
69
+ * 再来问」的 ISO 时刻。`success` 给 false 而不是 true:这次什么都没发,调用
70
+ * 方得看 `deferred` 走推迟的收尾;万一有调用方不认识这个字段,也只会把它当
71
+ * 成没发成,不会当成发完了把任务删掉。
66
72
  * Failures (timeout / loop exceeded / config errors) throw — the caller's
67
73
  * existing error handling turns them into task retry/failure.
68
74
  *
@@ -84,6 +90,16 @@ export function runAgenticFire({ task, decryptedPayload, userKey, ctx }: {
84
90
  status: "finished" | "skipped";
85
91
  iterations: number;
86
92
  };
93
+ } | {
94
+ handled: true;
95
+ result: {
96
+ success: false;
97
+ deferred: true;
98
+ retryAfter: string;
99
+ messagesSent: 0;
100
+ status: "deferred";
101
+ iterations: 0;
102
+ };
87
103
  }>;
88
104
  /**
89
105
  * 把这一轮的 usage 累加进整次 fire 的合计,返回新的合计(不改传入的对象)。
@@ -116,6 +132,32 @@ export const DEFAULT_MAX_TOOL_ITERATIONS: 5;
116
132
  export const DEFAULT_TOTAL_TIMEOUT_MS: 240000;
117
133
  export const DEFAULT_MAX_SCHEDULED_TASKS_PER_FIRE: 2;
118
134
  export const MIN_SCHEDULE_LEAD_MS: 60000;
135
+ export const MAX_DEFER_AFTER_MS: number;
136
+ /**
137
+ * - 消息数组 / `{ messages, ... }`:用这份 prompt 生成;
138
+ * - `{ skip: true }`:这次不发,按零推送的成功收场(一次性任务删行、循环任务
139
+ * 推进到下一次);
140
+ * - `{ defer: { afterMs } }`:现在不合适,afterMs 毫秒后再来问。afterMs 必须
141
+ * 是有限正数,且不超过 MAX_DEFER_AFTER_MS(24 小时);
142
+ * - `null` / `undefined`:交还给排程时冻结的 prompt 老链路。
143
+ */
144
+ export type BeforeFireResult = Array<any> | {
145
+ messages: Array<any>;
146
+ maxToolIterations?: number;
147
+ totalTimeoutMs?: number;
148
+ tools?: Array<any>;
149
+ toolChoice?: any;
150
+ } | {
151
+ skip: true;
152
+ } | {
153
+ defer: {
154
+ afterMs: number;
155
+ };
156
+ } | null | undefined;
157
+ /**
158
+ * 收尾回执(onFireSettled)的 status。
159
+ */
160
+ export type FireSettledStatus = "sent" | "skipped" | "deferred" | "failed" | "cancelled" | "not-handled";
119
161
  /**
120
162
  * 把任务的调度身份投影进一条 push。
121
163
  *
@@ -9,8 +9,11 @@
9
9
  * @param {string} [providedMasterKey]
10
10
  * @param {{ userKey: string, payload: Object } | null} [predecrypted] - 调用方
11
11
  * (run-tick 的预扫描)已经解好的 payload;传了就不再解第二遍。
12
- * @returns {Promise<{ success: boolean, messagesSent: number, redelivered?: boolean, pushedCount?: number, error?: string, errorCode?: string|null, pushStatusCode?: number|null, permanent?: boolean, willRetry?: boolean, retryLimit?: number, failureStage?: string }>}
12
+ * @returns {Promise<{ success: boolean, messagesSent: number, redelivered?: boolean, pushedCount?: number, deferred?: boolean, retryAfter?: string, error?: string, errorCode?: string|null, pushStatusCode?: number|null, permanent?: boolean, willRetry?: boolean, retryLimit?: number, failureStage?: string }>}
13
13
  * 失败时 `pushStatusCode` 是推送服务回的 HTTP 状态码(不是推送阶段炸的 → null)。
14
+ * `deferred: true` 表示 onBeforeFire 把这次触发推迟了(`retryAfter` 是唤醒
15
+ * 时刻):`success` 为 false 但不是失败,调用方要先看这个字段。只有 ctx 带
16
+ * `_deferSupported: true`(run-tick 在适配器有 retry_after 列时给)才会出现。
14
17
  */
15
18
  export function processSingleMessage(task: import("../adapters/interface.js").TaskRow, ctx: ProcessorContext, providedMasterKey?: string, predecrypted?: {
16
19
  userKey: string;
@@ -20,6 +23,8 @@ export function processSingleMessage(task: import("../adapters/interface.js").Ta
20
23
  messagesSent: number;
21
24
  redelivered?: boolean;
22
25
  pushedCount?: number;
26
+ deferred?: boolean;
27
+ retryAfter?: string;
23
28
  error?: string;
24
29
  errorCode?: string | null;
25
30
  pushStatusCode?: number | null;
@@ -40,6 +45,9 @@ export function processSingleMessage(task: import("../adapters/interface.js").Ta
40
45
  * `reasoningError` 只在正文都发出去了、思考过程那一条没发成时出现(思考过程是
41
46
  * 附赠内容,它发不出去不算这条消息失败)。调用方拿它提示用户这次没有思考过程,
42
47
  * 不带这个字段就是整轮都送到了。
48
+ *
49
+ * 这条路是请求里当场投递,不支持 onBeforeFire 的 `{ defer }`:返回它会得到
50
+ * AGENTIC_DEFER_UNSUPPORTED 的配置错误,按这里的失败流程处理。
43
51
  */
44
52
  export function processMessagesByUuid(uuid: string, ctx: ProcessorContext, maxRetries?: number, userId?: string, providedMasterKey?: string): Promise<{
45
53
  success: boolean;
@@ -24,6 +24,7 @@ export function runScheduledTick(ctx: any): Promise<{
24
24
  deletedOnceOffTasks: number;
25
25
  updatedRecurringTasks: number;
26
26
  staleTasks: any[];
27
+ deferredTasks: any[];
27
28
  cancelledTasks: any[];
28
29
  reasoningSkippedTasks: any[];
29
30
  redeliveredTasks: any[];
@@ -45,8 +46,11 @@ export function runScheduledTick(ctx: any): Promise<{
45
46
  * - `already_settled`:行还在,但已经是 sent / failed(`status` 带上是哪
46
47
  * 个)。适配器没实现 `getTaskStatusByUuidOnly` 时这种情况并进 `not_found`。
47
48
  * - `not_due`:还没到 `next_send_at`(`nextSendAt` 带上是什么时候)。
48
- * - `retry_pending`:上次投递失败,还在退避窗口里(`retryAfter` 带上什么时
49
- * 候到点)。
49
+ * - `retry_pending`:上次投递失败还在退避窗口里,或者上次 onBeforeFire 把
50
+ * 它推迟了(`{ defer }`)还没到点(`retryAfter` 带上什么时候到点)。
51
+ *
52
+ * 这一次跑的时候 onBeforeFire 返回 `{ defer }` 的话,结果是 `ran: true`,任务
53
+ * 列在 summary.details.deferredTasks 里;到点之前再调都回 `retry_pending`。
50
54
  *
51
55
  * @param {Object} ctx - 与 runScheduledTick 同一份 ctx
52
56
  * @param {string} uuid - 任务 uuid(pending 行)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rei-standard/amsg-server",
3
- "version": "2.6.0-next.32",
3
+ "version": "2.6.0-next.33",
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",