@rei-standard/amsg-server 2.6.0-next.31 → 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
 
@@ -940,3 +987,30 @@ VERCEL_PROTECTION_BYPASS=YOUR_BYPASS_KEY
940
987
  - [SW 包 README](https://github.com/Tosd0/ReiStandard/blob/main/packages/rei-standard-amsg/sw/README.md)
941
988
  - [API 技术规范](https://github.com/Tosd0/ReiStandard/blob/main/standards/active-messaging-api.md)
942
989
  - [Service Worker 规范](https://github.com/Tosd0/ReiStandard/blob/main/standards/service-worker-specification.md)
990
+
991
+
992
+ ## 取消正在执行的生成与工具
993
+
994
+ 任务被 `DELETE /cancel-message` 取消或被新任务顶替后,租约心跳发现任务已经失效,会中断正在等待的模型请求,阻止新的模型轮次、工具阶段和推送。`leaseHeartbeatMs` 默认仍为 30000;需要及时停止的交互应用可配置为 1000(每个正在执行的任务每秒续租一次)。这是跨请求的数据库检测,延迟取决于心跳和数据库响应,不承诺瞬时中断。自定义适配器需要支持 `claimTask` / `renewTaskLease`;关掉心跳时也关掉了这条主动检测路径。
995
+
996
+ `onBeforeFire`、`onLLMOutput`、`executeToolCalls` 的 context 以及 `onFireSettled` 回执新增:
997
+
998
+ - `signal: AbortSignal`:传给工具内部的 `fetch`,与工具自己的超时信号合并。
999
+ - `isCancelled(): boolean`:当前是否已收到取消信号。
1000
+ - `throwIfCancelled(): void`:已取消时抛出 `code: 'TASK_CANCELLED'` 的错误。每个副作用开始前检查,工具 catch 中也要先调用它,以免取消被转成普通工具失败后继续执行。
1001
+
1002
+ ```js
1003
+ async function executeToolCalls(calls, ctx) {
1004
+ const results = [];
1005
+ for (const call of calls) {
1006
+ ctx.throwIfCancelled();
1007
+ const response = await fetch(toolUrl(call), { signal: ctx.signal });
1008
+ results.push({ tool_call_id: call.id, role: 'tool', content: await response.text() });
1009
+ }
1010
+ return results;
1011
+ }
1012
+ ```
1013
+
1014
+ 取消收尾使用 `onFireSettled({ status: 'cancelled', willRetry: false, error, ... })`,不是生成失败。应用应在这里释放资源、结算已发生的用量,避免发出失败提示或把未送达内容记为已回复。取消时已经开始写入的 outbox 批次会在写完后检查信号并撤掉未投递部分;已经发到客户端的消息仍由客户端处理。应用需要记录停止的任务身份,拦住迟到推送、补收以及尚未显示的本地内容。已完成的外部副作用无法撤销;不支持 AbortSignal 的工具也需要自己在执行步骤之间检查取消。
1015
+
1016
+ 执行阶段的 `ctx.writeState`、`scheduleTask`、`cancelTask`、`renewTask` 和 `emitResult` 会拒绝取消后新发起的操作。`onFireSettled` 的 `info.writeState` 特意仍可用,供宿主释放锁、保存账单等收尾;它不能用于继续生成阶段的业务动作。已经开始的数据库写入不支持回滚,宿主仍需处理并发状态版本。
@@ -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) {
@@ -1707,6 +1719,17 @@ function firstPositiveNumber(values, fallback) {
1707
1719
  }
1708
1720
  async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
1709
1721
  const hooks = ctx.hooks;
1722
+ const signal = ctx.signal || new AbortController().signal;
1723
+ const isCancelled = () => signal.aborted || _optionalChain([ctx, 'access', _5 => _5.isTaskCancelled, 'optionalCall', _6 => _6()]) === true;
1724
+ const throwIfCancelled = () => {
1725
+ if (!isCancelled()) return;
1726
+ if (isTaskCancelledError(signal.reason)) throw signal.reason;
1727
+ const error = new Error("Task was cancelled or superseded");
1728
+ error.code = "TASK_CANCELLED";
1729
+ throw error;
1730
+ };
1731
+ const cancellation = { signal, isCancelled, throwIfCancelled };
1732
+ ctx = { ...ctx, ...cancellation };
1710
1733
  if (typeof hooks.onLLMOutput !== "function") {
1711
1734
  throw new DeploymentConfigError(
1712
1735
  "AGENTIC_CONFIG_ERROR: hooks.onBeforeFire requires hooks.onLLMOutput to classify LLM rounds",
@@ -1715,13 +1738,17 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
1715
1738
  }
1716
1739
  const nowFn = typeof ctx._agenticNow === "function" ? ctx._agenticNow : Date.now;
1717
1740
  const sleep = typeof ctx._agenticSleep === "function" ? ctx._agenticSleep : defaultSleep;
1718
- const { readState, writeState } = createStateAccessors({
1741
+ const { readState, writeState: writeSettledState } = createStateAccessors({
1719
1742
  db: ctx.db,
1720
1743
  userId: task.user_id,
1721
1744
  userKey,
1722
1745
  maxStateValueBytes: ctx.maxStateValueBytes,
1723
1746
  now: nowFn
1724
1747
  });
1748
+ const writeState = async (namespace, entries) => {
1749
+ throwIfCancelled();
1750
+ return writeSettledState(namespace, entries);
1751
+ };
1725
1752
  const sessionId = task.id != null ? `sess_task_${task.id}${occurrenceSuffix(task)}` : `sess_${_amsgshared.randomUUID.call(void 0, )}`;
1726
1753
  const messageIdBase = task.id != null ? `msg_task_${task.id}${occurrenceSuffix(task)}` : `msg_${_amsgshared.randomUUID.call(void 0, )}`;
1727
1754
  const occurrenceMs = occurrenceMsOf(task);
@@ -1737,7 +1764,7 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
1737
1764
  now: nowFn,
1738
1765
  // run-tick 在投递 ctx 上挂的取消信号(与 guardWebpushWithLease 读的是同一
1739
1766
  // 个租约状态),emitResult 不发推送的那条路要靠它拦下已取消任务的落行。
1740
- isCancelled: typeof ctx.isTaskCancelled === "function" ? ctx.isTaskCancelled : null
1767
+ isCancelled
1741
1768
  });
1742
1769
  const maxScheduledTasksPerFire = Number.isInteger(ctx.maxScheduledTasksPerFire) && ctx.maxScheduledTasksPerFire >= 0 ? ctx.maxScheduledTasksPerFire : DEFAULT_MAX_SCHEDULED_TASKS_PER_FIRE;
1743
1770
  let scheduledTaskCount = 0;
@@ -1754,6 +1781,7 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
1754
1781
  }
1755
1782
  }
1756
1783
  const scheduleTask = async (options) => {
1784
+ throwIfCancelled();
1757
1785
  if (!options || typeof options !== "object" || Array.isArray(options)) {
1758
1786
  throw new TypeError("scheduleTask(options) \u9700\u8981\u4E00\u4E2A\u5BF9\u8C61\uFF0C\u81F3\u5C11\u5305\u542B { firstSendTime }");
1759
1787
  }
@@ -1874,6 +1902,7 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
1874
1902
  }
1875
1903
  scheduledTaskCount++;
1876
1904
  const encryptedPayload = await encryptForStorage(serializedTaskData, userKey);
1905
+ throwIfCancelled();
1877
1906
  let created;
1878
1907
  try {
1879
1908
  created = await ctx.db.createTask({
@@ -1908,6 +1937,7 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
1908
1937
  };
1909
1938
  };
1910
1939
  const cancelTask = async (uuid) => {
1940
+ throwIfCancelled();
1911
1941
  if (typeof uuid !== "string" || !uuid.trim()) {
1912
1942
  throw new TypeError("cancelTask(uuid) \u9700\u8981\u975E\u7A7A\u5B57\u7B26\u4E32 uuid");
1913
1943
  }
@@ -1922,6 +1952,7 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
1922
1952
  { code: "AGENTIC_CANCEL_UNSUPPORTED" }
1923
1953
  );
1924
1954
  }
1955
+ throwIfCancelled();
1925
1956
  const cancelled = await ctx.db.deleteTaskByUuid(uuid, task.user_id);
1926
1957
  if (cancelled) {
1927
1958
  await discardUndeliveredPushesForTask({ db: ctx.db, userId: task.user_id, taskUuid: uuid });
@@ -1929,6 +1960,7 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
1929
1960
  return { cancelled: !!cancelled };
1930
1961
  };
1931
1962
  const renewTask = async (uuid, nextSendAt) => {
1963
+ throwIfCancelled();
1932
1964
  if (typeof uuid !== "string" || !uuid.trim()) {
1933
1965
  throw new TypeError("renewTask(uuid, nextSendAt) \u9700\u8981\u975E\u7A7A\u5B57\u7B26\u4E32 uuid");
1934
1966
  }
@@ -1959,6 +1991,7 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
1959
1991
  const payload = JSON.parse(await decryptFromStorage(row.encrypted_payload, userKey));
1960
1992
  const nextSendAtIso = sendAt.toISOString();
1961
1993
  const encrypted = await encryptForStorage(JSON.stringify({ ...payload, firstSendTime: nextSendAtIso }), userKey);
1994
+ throwIfCancelled();
1962
1995
  const updated = await ctx.db.updateTaskByUuid(uuid, task.user_id, encrypted, {
1963
1996
  next_send_at: nextSendAtIso,
1964
1997
  retry_count: 0,
@@ -1979,6 +2012,7 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
1979
2012
  };
1980
2013
  const scratch = {};
1981
2014
  const fireCtx = Object.freeze({
2015
+ ...cancellation,
1982
2016
  task: buildHookTask(task, decryptedPayload),
1983
2017
  userId: task.user_id,
1984
2018
  readState,
@@ -2004,6 +2038,7 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
2004
2038
  };
2005
2039
  let settledStatus = "failed";
2006
2040
  let settledError = null;
2041
+ let settledRetryAfter = null;
2007
2042
  try {
2008
2043
  const outcome = await runFireChain({
2009
2044
  task,
@@ -2027,17 +2062,32 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
2027
2062
  messageIdBase,
2028
2063
  occurrenceMs
2029
2064
  });
2030
- settledStatus = !outcome.handled ? "not-handled" : outcome.result.status === "skipped" ? "skipped" : "sent";
2065
+ throwIfCancelled();
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";
2031
2071
  return outcome;
2032
2072
  } catch (error) {
2073
+ if (isCancelled() && !isTaskCancelledError(error)) {
2074
+ try {
2075
+ throwIfCancelled();
2076
+ } catch (cancelled) {
2077
+ error = cancelled;
2078
+ }
2079
+ }
2080
+ if (isTaskCancelledError(error)) settledStatus = "cancelled";
2033
2081
  settledError = error;
2034
2082
  throw error;
2035
2083
  } finally {
2036
2084
  await notifyFireSettled(ctx, {
2037
2085
  task,
2086
+ ...cancellation,
2038
2087
  status: settledStatus,
2039
- ...settledStatus === "failed" && ctx._deliveryState ? failureRetryDecision(ctx._deliveryState, settledError) : { willRetry: null, failureStage: null },
2088
+ ...settledStatus === "cancelled" ? { willRetry: false, failureStage: null } : settledStatus === "failed" && ctx._deliveryState ? failureRetryDecision(ctx._deliveryState, settledError) : { willRetry: null, failureStage: null },
2040
2089
  skipReason: settledStatus === "skipped" ? progress.skipReason : null,
2090
+ retryAfter: settledStatus === "deferred" ? settledRetryAfter : null,
2041
2091
  sentCount: progress.sentCount,
2042
2092
  pushedCount: progress.pushedCount,
2043
2093
  total: progress.total,
@@ -2060,7 +2110,7 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
2060
2110
  outboxed: progress.outboxed,
2061
2111
  scratch,
2062
2112
  readState,
2063
- writeState,
2113
+ writeState: writeSettledState,
2064
2114
  emitResult
2065
2115
  });
2066
2116
  }
@@ -2087,12 +2137,35 @@ async function runFireChain({
2087
2137
  messageIdBase,
2088
2138
  occurrenceMs
2089
2139
  }) {
2140
+ ctx.throwIfCancelled();
2090
2141
  const before = await hooks.onBeforeFire(fireCtx);
2142
+ ctx.throwIfCancelled();
2091
2143
  if (before == null) return { handled: false };
2092
2144
  if (typeof before === "object" && before.skip === true) {
2093
2145
  progress.skipReason = "before-fire";
2094
2146
  return { handled: true, result: { success: true, messagesSent: 0, status: "skipped", iterations: 0 } };
2095
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
+ }
2096
2169
  const normalized = normalizeBeforeFireResult(before);
2097
2170
  const maxToolIterations = firstPositiveInt(
2098
2171
  [normalized.maxToolIterations, ctx.maxToolIterations],
@@ -2111,6 +2184,7 @@ async function runFireChain({
2111
2184
  decryptedPayload
2112
2185
  });
2113
2186
  for (let iteration = 0; iteration < maxToolIterations; iteration++) {
2187
+ ctx.throwIfCancelled();
2114
2188
  if (nowFn() >= deadline) {
2115
2189
  throw new Error(`AGENTIC_TOTAL_TIMEOUT: fire chain exceeded ${totalTimeoutMs}ms after ${iteration} LLM round(s)`);
2116
2190
  }
@@ -2124,13 +2198,17 @@ async function runFireChain({
2124
2198
  messages,
2125
2199
  ...normalized.tools ? { tools: normalized.tools, toolChoice: normalized.toolChoice } : {}
2126
2200
  },
2127
- { requireContent: false, timeoutMs: roundTimeoutMs }
2201
+ { requireContent: false, timeoutMs: roundTimeoutMs, signal: ctx.signal }
2128
2202
  );
2129
2203
  const assistantMessage = _amsgshared.extractAssistantMessage.call(void 0, llmResponse);
2130
2204
  messages = [...messages, assistantMessage];
2131
2205
  progress.usage = llmResponse && typeof llmResponse === "object" && llmResponse.usage || null;
2132
2206
  progress.usageTotal = accumulateUsage(progress.usageTotal, progress.usage);
2207
+ ctx.throwIfCancelled();
2133
2208
  const sessionCtx = Object.freeze({
2209
+ signal: ctx.signal,
2210
+ isCancelled: ctx.isCancelled,
2211
+ throwIfCancelled: ctx.throwIfCancelled,
2134
2212
  ..._amsgshared.buildSessionContext.call(void 0, {
2135
2213
  sessionId,
2136
2214
  messages,
@@ -2154,6 +2232,7 @@ async function runFireChain({
2154
2232
  resolveLlmCredential: resolveLlmCredential2
2155
2233
  });
2156
2234
  const decision = await hooks.onLLMOutput(sessionCtx);
2235
+ ctx.throwIfCancelled();
2157
2236
  try {
2158
2237
  _amsgshared.assertValidDecision.call(void 0, decision, { inlineToolCalls: true });
2159
2238
  } catch (error) {
@@ -2204,15 +2283,19 @@ async function runFireChain({
2204
2283
  }
2205
2284
  let toolResults;
2206
2285
  try {
2286
+ ctx.throwIfCancelled();
2207
2287
  toolResults = await hooks.executeToolCalls(toolCalls, sessionCtx);
2288
+ ctx.throwIfCancelled();
2208
2289
  if (!Array.isArray(toolResults)) {
2209
2290
  throw new TypeError('executeToolCalls must resolve to an array of { tool_call_id, role: "tool", content }');
2210
2291
  }
2211
2292
  } catch (error) {
2293
+ ctx.throwIfCancelled();
2294
+ if (isTaskCancelledError(error)) throw error;
2212
2295
  toolResults = toolCalls.map((toolCall) => ({
2213
2296
  tool_call_id: toolCall && typeof toolCall === "object" && typeof toolCall.id === "string" ? toolCall.id : "",
2214
2297
  role: "tool",
2215
- content: `Tool execution failed: ${_nullishCoalesce(_optionalChain([error, 'optionalAccess', _5 => _5.message]), () => ( String(error)))}`
2298
+ content: `Tool execution failed: ${_nullishCoalesce(_optionalChain([error, 'optionalAccess', _7 => _7.message]), () => ( String(error)))}`
2216
2299
  }));
2217
2300
  }
2218
2301
  const nativeCalls = Array.isArray(assistantMessage.tool_calls) ? assistantMessage.tool_calls : [];
@@ -2298,9 +2381,11 @@ async function sendHookPushPayloads({
2298
2381
  stampTaskIdentity(push, task, decryptedPayload, occurrenceMs);
2299
2382
  finalized.push(push);
2300
2383
  }
2384
+ ctx.throwIfCancelled();
2301
2385
  outboxed = await appendPushesToOutbox({ db: ctx.db, userId: task.user_id, userKey, pushes: finalized });
2302
2386
  progress.outboxed = outboxed;
2303
2387
  if (ctx._deliveryState) ctx._deliveryState.outboxed = outboxed;
2388
+ ctx.throwIfCancelled();
2304
2389
  if (!ctx.vapid || !ctx.vapid.email || !ctx.vapid.publicKey || !ctx.vapid.privateKey) {
2305
2390
  throw new Error("VAPID configuration missing - push notifications cannot be sent");
2306
2391
  }
@@ -2314,6 +2399,7 @@ async function sendHookPushPayloads({
2314
2399
  const willPush = finalized.map((push) => shouldSendPush(push, pushGate));
2315
2400
  const lastPushIndex = willPush.lastIndexOf(true);
2316
2401
  for (let i = 0; i < total; i++) {
2402
+ ctx.throwIfCancelled();
2317
2403
  if (willPush[i]) {
2318
2404
  await sendTaggedPush(ctx.webpush, pushSubscription, JSON.stringify(finalized[i]));
2319
2405
  pushedCount++;
@@ -2577,7 +2663,7 @@ async function processSingleMessage(task, ctx, providedMasterKey, predecrypted =
2577
2663
  const hasChatSource = hasChatCredRef(decryptedPayload) || !!(decryptedPayload.apiUrl && decryptedPayload.apiKey && decryptedPayload.primaryModel);
2578
2664
  if (hasPrompt && hasChatSource) {
2579
2665
  const chatCred = await resolveFireCredentials({ db: ctx.db, userId: task.user_id, userKey, decryptedPayload });
2580
- const aiResult = await callLlm(chatCred ? { ...decryptedPayload, ...chatCred } : decryptedPayload);
2666
+ const aiResult = await callLlm(chatCred ? { ...decryptedPayload, ...chatCred } : decryptedPayload, { signal: ctx.signal });
2581
2667
  messageContent = aiResult.content;
2582
2668
  llmResponse = aiResult.response;
2583
2669
  } else if (decryptedPayload.userMessage) {
@@ -2587,7 +2673,7 @@ async function processSingleMessage(task, ctx, providedMasterKey, predecrypted =
2587
2673
  }
2588
2674
  } else if (decryptedPayload.messageType === "prompted" || decryptedPayload.messageType === "auto") {
2589
2675
  const chatCred = await resolveFireCredentials({ db: ctx.db, userId: task.user_id, userKey, decryptedPayload });
2590
- const aiResult = await callLlm(chatCred ? { ...decryptedPayload, ...chatCred } : decryptedPayload);
2676
+ const aiResult = await callLlm(chatCred ? { ...decryptedPayload, ...chatCred } : decryptedPayload, { signal: ctx.signal });
2591
2677
  messageContent = aiResult.content;
2592
2678
  llmResponse = aiResult.response;
2593
2679
  } else {
@@ -3193,6 +3279,7 @@ async function deliverTasks(ctx, tasks) {
3193
3279
  deletedOnceOffTasks: 0,
3194
3280
  updatedRecurringTasks: 0,
3195
3281
  staleTasks: [],
3282
+ deferredTasks: [],
3196
3283
  cancelledTasks: [],
3197
3284
  reasoningSkippedTasks: [],
3198
3285
  redeliveredTasks: [],
@@ -3208,7 +3295,8 @@ async function deliverTasks(ctx, tasks) {
3208
3295
  return !!await db.claimTask(task.id, task.next_send_at, leaseUntil, serializeGroup);
3209
3296
  }
3210
3297
  function startLeaseHeartbeat(task) {
3211
- const lease = { lost: false, released: false, stop: () => {
3298
+ const controller = new AbortController();
3299
+ const lease = { lost: false, released: false, signal: controller.signal, stop: () => {
3212
3300
  } };
3213
3301
  if (!heartbeatEnabled) return lease;
3214
3302
  let stopped = false;
@@ -3224,6 +3312,9 @@ async function deliverTasks(ctx, tasks) {
3224
3312
  if (renewed === false) {
3225
3313
  if (stopped || lease.released) return;
3226
3314
  lease.lost = true;
3315
+ const error = new Error("Task was cancelled or superseded");
3316
+ error.code = TASK_CANCELLED_CODE;
3317
+ controller.abort(error);
3227
3318
  console.warn(`[amsg-server] \u4EFB\u52A1 ${task.id} \u7684\u79DF\u7EA6\u5DF2\u5931\u6548\uFF08\u884C\u88AB\u53D6\u6D88\u6216\u9876\u66FF\uFF09\uFF0C\u5269\u4F59\u63A8\u9001\u5C06\u4E2D\u6B62`);
3228
3319
  return;
3229
3320
  }
@@ -3482,7 +3573,7 @@ async function deliverTasks(ctx, tasks) {
3482
3573
  const occurrenceMs = Date.parse(task.next_send_at);
3483
3574
  const retryAfterMs = task.retry_after ? Date.parse(task.retry_after) : NaN;
3484
3575
  const notOnFreshRetryChain = (task.retry_count || 0) === 0 || Number.isFinite(retryAfterMs) && Date.now() - retryAfterMs > staleAfterMs;
3485
- if (Number.isFinite(occurrenceMs) && Date.now() - occurrenceMs > staleAfterMs && notOnFreshRetryChain) {
3576
+ const settleAsStale = async () => {
3486
3577
  try {
3487
3578
  const stateAccessors = createStateAccessors({
3488
3579
  db,
@@ -3542,8 +3633,32 @@ async function deliverTasks(ctx, tasks) {
3542
3633
  results.failedCount++;
3543
3634
  results.failedTasks.push({ taskId: task.id, reason: error.message || "\u8FC7\u671F\u4EFB\u52A1\u5904\u7406\u5931\u8D25", status: "stale_update_failed" });
3544
3635
  }
3636
+ };
3637
+ if (Number.isFinite(occurrenceMs) && Date.now() - occurrenceMs > staleAfterMs && notOnFreshRetryChain) {
3638
+ await settleAsStale();
3545
3639
  return;
3546
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
+ };
3547
3662
  let sendResult;
3548
3663
  try {
3549
3664
  sendResult = await processSingleMessage(
@@ -3553,7 +3668,12 @@ async function deliverTasks(ctx, tasks) {
3553
3668
  db,
3554
3669
  masterKey,
3555
3670
  webpush: guardWebpushWithLease(ctx.webpush, lease),
3556
- isTaskCancelled: () => lease.lost
3671
+ isTaskCancelled: () => lease.lost,
3672
+ signal: lease.signal,
3673
+ // onBeforeFire 的 { defer } 要靠 retry_after 这一列落地,没实现
3674
+ // claimTask 的适配器没有这一列(见 lib/agentic-fire.js 的
3675
+ // AGENTIC_DEFER_UNSUPPORTED)。
3676
+ _deferSupported: supportsClaim
3557
3677
  },
3558
3678
  masterKey,
3559
3679
  { userKey, payload: decryptedPayload }
@@ -3573,6 +3693,14 @@ async function deliverTasks(ctx, tasks) {
3573
3693
  );
3574
3694
  return;
3575
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
+ }
3576
3704
  if (!sendResult.success) {
3577
3705
  if (lease.lost) {
3578
3706
  await recordCancelled(task, "cancelled_mid_delivery");
@@ -3665,6 +3793,9 @@ async function deliverTasks(ctx, tasks) {
3665
3793
  deletedOnceOffTasks: results.deletedOnceOffTasks,
3666
3794
  updatedRecurringTasks: results.updatedRecurringTasks,
3667
3795
  staleTasks: results.staleTasks,
3796
+ // onBeforeFire 返回 { defer } 被推迟的任务({ taskId, retryAfter }):这一
3797
+ // 跳没生成也没发,到 retryAfter 之后再问。不计入 successCount / failedCount。
3798
+ deferredTasks: results.deferredTasks,
3668
3799
  // 投递期间行被取消 / 顶替的任务。`cancelled_mid_delivery` = 推送在发出去
3669
3800
  // 之前被拦下;`cancelled_after_delivery` = 推送已经发完,收尾写库才发现
3670
3801
  // 行没了。两种都不计入 successCount / failedCount。
@@ -5099,7 +5230,7 @@ var D1Adapter = class {
5099
5230
  result.cleanupOutcomes = probeIndexes.map((probeAt) => {
5100
5231
  if (probeAt === null) return null;
5101
5232
  const row = results[probeAt] && Array.isArray(results[probeAt].results) ? results[probeAt].results[0] : null;
5102
- return Number(_nullishCoalesce(_optionalChain([row, 'optionalAccess', _6 => _6.n]), () => ( 0))) === 0;
5233
+ return Number(_nullishCoalesce(_optionalChain([row, 'optionalAccess', _8 => _8.n]), () => ( 0))) === 0;
5103
5234
  });
5104
5235
  }
5105
5236
  return result;
@@ -5960,7 +6091,7 @@ function createClientStateNamespacesHandler(ctx) {
5960
6091
  }
5961
6092
 
5962
6093
  // src/server/lib/version.js
5963
- var SERVER_VERSION = true ? "2.6.0-next.31" : "0.0.0-dev";
6094
+ var SERVER_VERSION = true ? "2.6.0-next.33" : "0.0.0-dev";
5964
6095
 
5965
6096
  // src/server/handlers/capabilities.js
5966
6097
  var SERVER_FEATURES = Object.freeze([
@@ -6073,7 +6204,10 @@ var SERVER_FEATURES = Object.freeze([
6073
6204
  // 工厂配置认 maxDeliveryRetries(投递失败的重试次数上限,默认 3)。
6074
6205
  "max-delivery-retries",
6075
6206
  // Per-task pre-commit retry limit and fire receipt retry decision.
6076
- "max-generation-retries"
6207
+ "max-generation-retries",
6208
+ // onBeforeFire 认 { defer: { afterMs } }:这次不生成,过一会儿再来问(不占重试
6209
+ // 次数);onFireSettled 的 status 多一种 'deferred',带 retryAfter。
6210
+ "before-fire-defer"
6077
6211
  ]);
6078
6212
  function createCapabilitiesHandler(ctx) {
6079
6213
  async function GET(url, headers) {
@@ -6701,4 +6835,5 @@ function createSingleUserCloudflareWorker(buildConfig, options = {}) {
6701
6835
 
6702
6836
 
6703
6837
 
6704
- 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;