@rei-standard/amsg-server 2.6.0-next.30 → 2.6.0-next.31

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
@@ -703,6 +703,26 @@ LLM 这一条只认上游**答复了、并且拒了**的那几种状态码:Key
703
703
 
704
704
  `GET /capabilities` 的 features 里对应 `llm-permanent-errors`、`redeliver-committed-batch`、`hook-usage-total`、`max-delivery-retries`。
705
705
 
706
+ ## 按任务限制生成重试(`maxGenerationRetries`)
707
+
708
+ 用户主动触发的回复可以第一次生成失败就结束,定时任务则保留默认退避。工厂配置和 `runScheduledTick` / `runTask` 支持 `maxGenerationRetries`,值可以是非负整数,也可以是同步函数:
709
+
710
+ ```js
711
+ createSingleUserCloudflareWorker((env) => ({
712
+ // 这里的 interactive 是宿主自己约定的 metadata,库不认识业务类型。
713
+ maxGenerationRetries: (task) => task.metadata?.interactive ? 0 : undefined,
714
+ // ...其余配置
715
+ }));
716
+ ```
717
+
718
+ 回调收到与 `serializeBy` 相同的安全任务视图(含 metadata,不含 API Key 等凭据),每次尝试解析一次。返回 `undefined` 继承 `maxDeliveryRetries`(默认 3),返回 0 禁止本轮失败后重新生成;显式数值单独控制生成重试上限。不配置时行为不变,不需要修改任务 payload 或迁移数据库,已有任务也生效。所有服务端工厂和旧的 UUID 即时入口都透传这个配置。
719
+
720
+ 这里的“生成阶段”以**最终回复整批进入 outbox**为边界:前置 hook、模型、工具循环、组装和落库失败都使用生成上限;没有 outbox 的适配器发生推送失败时仍可能需要重新生成,因此也使用生成上限。完整批次已入 outbox 后的推送失败改用 `maxDeliveryRetries`,补投不会再次调用模型。hook 的独立 `emitResult` 不等于最终回复提交。
721
+
722
+ `onFireSettled` 新增 `willRetry` 与 `failureStage`:失败时前者为本次的重试决策,后者为 `'generation'` 或 `'delivery'`;成功、跳过、未接管时两者均为 `null`。宿主可以用 `info.status === 'failed' && info.willRetry === false && !info.outboxed` 即时显示生成失败,无需修改错误对象或猜测重试次数。此回执发生在调度器写任务状态之前,`willRetry` 描述同源策略决策,不保证后续数据库写入成功;取消导致的失败不会重试。已有永久性错误判定仍然生效。
723
+
724
+ 策略回调抛错或返回非法值时,不调用生成 hook / 模型,以 `GENERATION_RETRY_POLICY_INVALID` 记录配置错误并沿默认投递上限退避,避免一次坏部署立即作废所有任务;此时尚未进入 fire,因此不触发 `onFireSettled`。
725
+
706
726
  ## 同一分组的任务不并发(`serializeBy`)
707
727
 
708
728
  同一个角色可能有好几条定时任务。撞在一起并发跑的话,用户一口气收到两条互不知情的消息;宿主在 hook 里维护的「我刚才说过什么」台账通常是读进内存 → 改 → 整份写回,两条各改各的再写回,后写的必然盖掉前面那条。
@@ -110,6 +110,15 @@ export class D1Adapter {
110
110
  status: string;
111
111
  } | null>;
112
112
  updateTaskById(taskId: any, updates: any): Promise<any>;
113
+ /**
114
+ * 按 uuid 改一条 pending 任务(PUT /update-message、fire hook 的 renewTask)。
115
+ *
116
+ * 改排期(extraFields 里带 next_send_at)时多一道租约门:这条任务正被一次投递
117
+ * 占着的话不改,返回 null。投递收尾会按自己领取时看到的排期推进下一次(一次性
118
+ * 任务干脆标成已发送),这期间写进去的新时刻随后就被盖掉,接口却已经回了成功
119
+ * ——用户以为改期生效了,实际什么都没留下。正文这类字段不受这道门约束:它们
120
+ * 只影响以后的触发,收尾那边本来就不会覆盖(见 run-tick 的收尾守卫)。
121
+ */
113
122
  updateTaskByUuid(uuid: any, userId: any, encryptedPayload: any, extraFields: any): Promise<{
114
123
  uuid: any;
115
124
  updated_at: string;
@@ -302,8 +302,10 @@ export type DbAdapter = {
302
302
  last_error: string | null;
303
303
  } | null>;
304
304
  /**
305
- * (可选)建新任务的同一事务里取消旧的那条(POST /schedule-message 的
306
- * supersedesUuid)。不实现时 handler 退回「先删再建」两步(失去原子性)。
305
+ * (可选)建新任务的同时取消旧的那条,两件事一起成败(POST /schedule-message
306
+ * 的 supersedesUuid)。内置的 D1 / pg / neon 都实现了。不实现时 handler 退回
307
+ * 两步:先建新、后删旧——删旧失败最坏是两条都留着,客户端重试一次就能收拾,
308
+ * 而反过来(先删后建)建新失败就把旧任务白删了,找不回来。
307
309
  */
308
310
  createTaskSuperseding?: (params: InsertTaskParams, supersedesUuid: string) => Promise<TaskRow & {
309
311
  superseded: boolean;
@@ -39,6 +39,14 @@ export class NeonAdapter {
39
39
  }>;
40
40
  dropSchema(): Promise<void>;
41
41
  createTask(params: any): Promise<Record<string, any>>;
42
+ createTaskSuperseding(params: any, supersedesUuid: any): Promise<{
43
+ id: number;
44
+ uuid: string;
45
+ next_send_at: any;
46
+ status: string;
47
+ created_at: any;
48
+ superseded: boolean;
49
+ }>;
42
50
  getTaskByUuid(uuid: any, userId: any): Promise<Record<string, any>>;
43
51
  getTaskByUuidOnly(uuid: any): Promise<Record<string, any>>;
44
52
  /**
@@ -52,6 +60,15 @@ export class NeonAdapter {
52
60
  status: string;
53
61
  } | null>;
54
62
  updateTaskById(taskId: any, updates: any): Promise<Record<string, any>>;
63
+ /**
64
+ * 按 uuid 改一条 pending 任务(PUT /update-message、fire hook 的 renewTask)。
65
+ *
66
+ * 改排期(extraFields 里带 next_send_at)时多一道租约门:这条任务正被一次投递
67
+ * 占着的话不改,返回 null。投递收尾会按自己领取时看到的排期推进下一次(一次性
68
+ * 任务干脆标成已发送),这期间写进去的新时刻随后就被盖掉,接口却已经回了成功
69
+ * ——用户以为改期生效了,实际什么都没留下。正文这类字段不受这道门约束:它们
70
+ * 只影响以后的触发,收尾那边本来就不会覆盖(见 run-tick 的收尾守卫)。
71
+ */
55
72
  updateTaskByUuid(uuid: any, userId: any, encryptedPayload: any, extraFields: any): Promise<Record<string, any>>;
56
73
  deleteTaskById(taskId: any): Promise<boolean>;
57
74
  deleteTaskByUuid(uuid: any, userId: any): Promise<boolean>;
@@ -59,6 +59,39 @@ export function claimTask(query: PgQuery, taskId: number, expectedNextSendAt: st
59
59
  * @returns {Promise<boolean>} true = 续上了
60
60
  */
61
61
  export function renewTaskLease(query: PgQuery, taskId: number, leaseUntil: string | Date): Promise<boolean>;
62
+ /**
63
+ * 建新任务的同时把被顶替的旧任务删掉(POST /schedule-message 带
64
+ * supersedesUuid 时走这条)。
65
+ *
66
+ * 删旧和建新必须一起成败:分成两次查询的话,建新那步失败(uuid 撞了、连接
67
+ * 一时抖了)时旧任务已经删掉了,接口却回失败——客户端以为旧任务还在,实际
68
+ * 上它已经没了,没有任何办法找回来。
69
+ *
70
+ * 一条语句解决,不用显式事务:数据修改型 CTE 里的 DELETE 一定会执行到底,
71
+ * 而 INSERT 抛错时整条语句一起回滚(pg 与 neon 的 HTTP 驱动都是单语句一个
72
+ * 隐式事务,用法完全一致)。superseded 由 DELETE 到底删没删到行推出来。
73
+ *
74
+ * @param {PgQuery} query
75
+ * @param {{ user_id: string, uuid: string, encrypted_payload: string,
76
+ * next_send_at: string|Date, message_type: string }} params - 新任务
77
+ * @param {string} supersedesUuid - 要顶替掉的旧任务 uuid(同一个用户名下)
78
+ * @returns {Promise<{ id: number, uuid: string, next_send_at: any, status: string,
79
+ * created_at: any, superseded: boolean }|null>}
80
+ */
81
+ export function createTaskSuperseding(query: PgQuery, params: {
82
+ user_id: string;
83
+ uuid: string;
84
+ encrypted_payload: string;
85
+ next_send_at: string | Date;
86
+ message_type: string;
87
+ }, supersedesUuid: string): Promise<{
88
+ id: number;
89
+ uuid: string;
90
+ next_send_at: any;
91
+ status: string;
92
+ created_at: any;
93
+ superseded: boolean;
94
+ } | null>;
62
95
  /**
63
96
  * 状态 + 失败摘要(GET /message 用它把「为什么失败」透给已失败的行)。
64
97
  *
@@ -35,6 +35,14 @@ export class PgAdapter {
35
35
  }>;
36
36
  dropSchema(): Promise<void>;
37
37
  createTask(params: any): Promise<any[]>;
38
+ createTaskSuperseding(params: any, supersedesUuid: any): Promise<{
39
+ id: number;
40
+ uuid: string;
41
+ next_send_at: any;
42
+ status: string;
43
+ created_at: any;
44
+ superseded: boolean;
45
+ }>;
38
46
  getTaskByUuid(uuid: any, userId: any): Promise<any[]>;
39
47
  getTaskByUuidOnly(uuid: any): Promise<any[]>;
40
48
  /**
@@ -48,6 +56,15 @@ export class PgAdapter {
48
56
  status: string;
49
57
  } | null>;
50
58
  updateTaskById(taskId: any, updates: any): Promise<any[]>;
59
+ /**
60
+ * 按 uuid 改一条 pending 任务(PUT /update-message、fire hook 的 renewTask)。
61
+ *
62
+ * 改排期(extraFields 里带 next_send_at)时多一道租约门:这条任务正被一次投递
63
+ * 占着的话不改,返回 null。投递收尾会按自己领取时看到的排期推进下一次(一次性
64
+ * 任务干脆标成已发送),这期间写进去的新时刻随后就被盖掉,接口却已经回了成功
65
+ * ——用户以为改期生效了,实际什么都没留下。正文这类字段不受这道门约束:它们
66
+ * 只影响以后的触发,收尾那边本来就不会覆盖(见 run-tick 的收尾守卫)。
67
+ */
51
68
  updateTaskByUuid(uuid: any, userId: any, encryptedPayload: any, extraFields: any): Promise<any[]>;
52
69
  deleteTaskById(taskId: any): Promise<boolean>;
53
70
  deleteTaskByUuid(uuid: any, userId: any): Promise<boolean>;
@@ -754,62 +754,6 @@ function createGetUserKeyHandler(ctx) {
754
754
  return { GET };
755
755
  }
756
756
 
757
- // src/server/lib/webpush-webcrypto.js
758
-
759
-
760
- var WEB_PUSH_MAX_BODY_BYTES = 4096;
761
- var WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES = 16 + 4 + 1 + 65 + 1 + 16;
762
- var MAX_PUSH_PAYLOAD_BYTES = WEB_PUSH_MAX_BODY_BYTES - WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES;
763
- var PUSH_ENVELOPE_RESERVED_BYTES = 384;
764
- var payloadEncoder = new TextEncoder();
765
- function measurePushPayload(payload, options) {
766
- const reserveEnvelope = !!(options && options.reserveEnvelope);
767
- const bytes = payloadEncoder.encode(typeof payload === "string" ? payload : String(payload)).length;
768
- const envelopeReservedBytes = reserveEnvelope ? PUSH_ENVELOPE_RESERVED_BYTES : 0;
769
- const maxBytes = MAX_PUSH_PAYLOAD_BYTES - envelopeReservedBytes;
770
- return {
771
- bytes,
772
- maxBytes,
773
- remainingBytes: maxBytes - bytes,
774
- withinLimit: bytes <= maxBytes,
775
- envelopeReservedBytes
776
- };
777
- }
778
- async function sendWebPush(args) {
779
- const { payload } = args || {};
780
- if (typeof payload === "string") {
781
- const size = measurePushPayload(payload);
782
- if (!size.withinLimit) {
783
- const err6 = new Error(
784
- `sendWebPush: payload is ${size.bytes} bytes, over the ${MAX_PUSH_PAYLOAD_BYTES}-byte limit (push services cap the encrypted body at ${WEB_PUSH_MAX_BODY_BYTES} bytes; aes128gcm adds ${WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES} bytes)`
785
- );
786
- err6.code = "PUSH_PAYLOAD_TOO_LARGE";
787
- err6.bytes = size.bytes;
788
- err6.maxBytes = MAX_PUSH_PAYLOAD_BYTES;
789
- throw err6;
790
- }
791
- }
792
- return _amsgshared.sendWebPush.call(void 0, args);
793
- }
794
- var SCHEDULED_DEFAULT_TTL = 2419200;
795
- function createWebCryptoWebPush(vapid = {}, { ttl = SCHEDULED_DEFAULT_TTL } = {}) {
796
- return {
797
- async sendNotification(subscription, payload) {
798
- return sendWebPush({
799
- subscription,
800
- payload,
801
- ttl,
802
- vapid: {
803
- email: vapid.email,
804
- publicKey: vapid.publicKey,
805
- privateKey: vapid.privateKey
806
- },
807
- fetch: globalThis.fetch
808
- });
809
- }
810
- };
811
- }
812
-
813
757
  // src/server/lib/errors.js
814
758
 
815
759
  var NonRetryableError = class extends Error {
@@ -949,6 +893,95 @@ function summarizeErrorCause(error, stage) {
949
893
  return cause;
950
894
  }
951
895
 
896
+ // src/server/lib/retry-policy.js
897
+ var DEFAULT_MAX_DELIVERY_RETRIES = 3;
898
+ function resolveMaxDeliveryRetries(ctx) {
899
+ const value = ctx.maxDeliveryRetries;
900
+ return Number.isInteger(value) && value >= 0 ? value : DEFAULT_MAX_DELIVERY_RETRIES;
901
+ }
902
+ function resolveMaxGenerationRetries(ctx, safeTask) {
903
+ let value;
904
+ try {
905
+ value = typeof ctx.maxGenerationRetries === "function" ? ctx.maxGenerationRetries(safeTask) : ctx.maxGenerationRetries;
906
+ } catch (cause) {
907
+ throw new DeploymentConfigError("maxGenerationRetries callback failed", { code: "GENERATION_RETRY_POLICY_INVALID", cause });
908
+ }
909
+ if (value === void 0) return resolveMaxDeliveryRetries(ctx);
910
+ if (!Number.isInteger(value) || value < 0) {
911
+ throw new DeploymentConfigError("maxGenerationRetries must return a non-negative integer or undefined", { code: "GENERATION_RETRY_POLICY_INVALID" });
912
+ }
913
+ return value;
914
+ }
915
+ function failureRetryDecision(state, error) {
916
+ const retryLimit = state.outboxed ? state.deliveryLimit : state.generationLimit;
917
+ const permanent = isPermanentDeliveryFailure({
918
+ permanent: isNonRetryableError(error),
919
+ errorCode: _optionalChain([error, 'optionalAccess', _2 => _2.code]),
920
+ pushStatus: readPushStatusCode(error)
921
+ });
922
+ return {
923
+ failureStage: state.outboxed ? "delivery" : "generation",
924
+ retryLimit,
925
+ willRetry: !_optionalChain([state, 'access', _3 => _3.isCancelled, 'optionalCall', _4 => _4()]) && !isTaskCancelledError(error) && !permanent && state.retryCount < retryLimit
926
+ };
927
+ }
928
+
929
+ // src/server/lib/webpush-webcrypto.js
930
+
931
+
932
+ var WEB_PUSH_MAX_BODY_BYTES = 4096;
933
+ var WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES = 16 + 4 + 1 + 65 + 1 + 16;
934
+ var MAX_PUSH_PAYLOAD_BYTES = WEB_PUSH_MAX_BODY_BYTES - WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES;
935
+ var PUSH_ENVELOPE_RESERVED_BYTES = 384;
936
+ var payloadEncoder = new TextEncoder();
937
+ function measurePushPayload(payload, options) {
938
+ const reserveEnvelope = !!(options && options.reserveEnvelope);
939
+ const bytes = payloadEncoder.encode(typeof payload === "string" ? payload : String(payload)).length;
940
+ const envelopeReservedBytes = reserveEnvelope ? PUSH_ENVELOPE_RESERVED_BYTES : 0;
941
+ const maxBytes = MAX_PUSH_PAYLOAD_BYTES - envelopeReservedBytes;
942
+ return {
943
+ bytes,
944
+ maxBytes,
945
+ remainingBytes: maxBytes - bytes,
946
+ withinLimit: bytes <= maxBytes,
947
+ envelopeReservedBytes
948
+ };
949
+ }
950
+ async function sendWebPush(args) {
951
+ const { payload } = args || {};
952
+ if (typeof payload === "string") {
953
+ const size = measurePushPayload(payload);
954
+ if (!size.withinLimit) {
955
+ const err6 = new Error(
956
+ `sendWebPush: payload is ${size.bytes} bytes, over the ${MAX_PUSH_PAYLOAD_BYTES}-byte limit (push services cap the encrypted body at ${WEB_PUSH_MAX_BODY_BYTES} bytes; aes128gcm adds ${WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES} bytes)`
957
+ );
958
+ err6.code = "PUSH_PAYLOAD_TOO_LARGE";
959
+ err6.bytes = size.bytes;
960
+ err6.maxBytes = MAX_PUSH_PAYLOAD_BYTES;
961
+ throw err6;
962
+ }
963
+ }
964
+ return _amsgshared.sendWebPush.call(void 0, args);
965
+ }
966
+ var SCHEDULED_DEFAULT_TTL = 2419200;
967
+ function createWebCryptoWebPush(vapid = {}, { ttl = SCHEDULED_DEFAULT_TTL } = {}) {
968
+ return {
969
+ async sendNotification(subscription, payload) {
970
+ return sendWebPush({
971
+ subscription,
972
+ payload,
973
+ ttl,
974
+ vapid: {
975
+ email: vapid.email,
976
+ publicKey: vapid.publicKey,
977
+ privateKey: vapid.privateKey
978
+ },
979
+ fetch: globalThis.fetch
980
+ });
981
+ }
982
+ };
983
+ }
984
+
952
985
  // src/server/lib/agentic-fire.js
953
986
 
954
987
 
@@ -1931,7 +1964,10 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
1931
1964
  retry_count: 0,
1932
1965
  ...typeof ctx.db.claimTask === "function" ? { retry_after: null } : {}
1933
1966
  });
1934
- if (!updated) return { renewed: false, reason: "not_found" };
1967
+ if (!updated) {
1968
+ const stillPending = await ctx.db.getTaskByUuid(uuid, task.user_id);
1969
+ return { renewed: false, reason: stillPending ? "in_flight" : "not_found" };
1970
+ }
1935
1971
  return { renewed: true, uuid, nextSendAt: nextSendAtIso };
1936
1972
  };
1937
1973
  const resolveLlmCredential2 = async (credId) => {
@@ -2000,6 +2036,7 @@ async function runAgenticFire({ task, decryptedPayload, userKey, ctx }) {
2000
2036
  await notifyFireSettled(ctx, {
2001
2037
  task,
2002
2038
  status: settledStatus,
2039
+ ...settledStatus === "failed" && ctx._deliveryState ? failureRetryDecision(ctx._deliveryState, settledError) : { willRetry: null, failureStage: null },
2003
2040
  skipReason: settledStatus === "skipped" ? progress.skipReason : null,
2004
2041
  sentCount: progress.sentCount,
2005
2042
  pushedCount: progress.pushedCount,
@@ -2175,7 +2212,7 @@ async function runFireChain({
2175
2212
  toolResults = toolCalls.map((toolCall) => ({
2176
2213
  tool_call_id: toolCall && typeof toolCall === "object" && typeof toolCall.id === "string" ? toolCall.id : "",
2177
2214
  role: "tool",
2178
- content: `Tool execution failed: ${_nullishCoalesce(_optionalChain([error, 'optionalAccess', _2 => _2.message]), () => ( String(error)))}`
2215
+ content: `Tool execution failed: ${_nullishCoalesce(_optionalChain([error, 'optionalAccess', _5 => _5.message]), () => ( String(error)))}`
2179
2216
  }));
2180
2217
  }
2181
2218
  const nativeCalls = Array.isArray(assistantMessage.tool_calls) ? assistantMessage.tool_calls : [];
@@ -2263,6 +2300,7 @@ async function sendHookPushPayloads({
2263
2300
  }
2264
2301
  outboxed = await appendPushesToOutbox({ db: ctx.db, userId: task.user_id, userKey, pushes: finalized });
2265
2302
  progress.outboxed = outboxed;
2303
+ if (ctx._deliveryState) ctx._deliveryState.outboxed = outboxed;
2266
2304
  if (!ctx.vapid || !ctx.vapid.email || !ctx.vapid.publicKey || !ctx.vapid.privateKey) {
2267
2305
  throw new Error("VAPID configuration missing - push notifications cannot be sent");
2268
2306
  }
@@ -2345,6 +2383,7 @@ function addTokenCount(sum, value) {
2345
2383
 
2346
2384
 
2347
2385
 
2386
+
2348
2387
  var DEFAULT_SPLIT_REGEX = /([。!?!?]+)/;
2349
2388
  var SLEEP_BETWEEN_MESSAGES_MS2 = 1500;
2350
2389
  var MULTIPART_WINDOW_USAGE = 0.5;
@@ -2434,7 +2473,7 @@ function resolveMultipartOptions(ctx) {
2434
2473
  function assertChunkBytesFitPushLimit({ maxChunkBytes, maxChunks, ttlMs }) {
2435
2474
  const PROBE_CHUNK_BYTES = 3;
2436
2475
  const [probe] = _amsgshared.buildMultipartPushPayloads.call(void 0,
2437
- { messageKind: "reasoning" },
2476
+ { messageKind: longestMessageKind() },
2438
2477
  { serializedPayload: "x".repeat(PROBE_CHUNK_BYTES), maxChunkBytes: PROBE_CHUNK_BYTES, ttlMs }
2439
2478
  );
2440
2479
  const digitHeadroom = 2 * (String(maxChunks).length - 1);
@@ -2453,6 +2492,9 @@ function assertChunkBytesFitPushLimit({ maxChunkBytes, maxChunks, ttlMs }) {
2453
2492
  function base64UrlLength(n) {
2454
2493
  return Math.ceil(n * 4 / 3);
2455
2494
  }
2495
+ function longestMessageKind() {
2496
+ return Object.values(_amsgshared.MESSAGE_KIND).reduce((a, b) => b.length > a.length ? b : a);
2497
+ }
2456
2498
  function positiveIntegerOr(value, fallback) {
2457
2499
  return Number.isInteger(value) && /** @type {number} */
2458
2500
  value > 0 ? (
@@ -2494,7 +2536,15 @@ async function redeliverCommittedBatch(task, ctx, userKey, decryptedPayload, bat
2494
2536
  }
2495
2537
  return { success: true, messagesSent, redelivered: true, pushedCount: sentIds.length };
2496
2538
  }
2497
- async function processSingleMessage(task, ctx, providedMasterKey, predecrypted = null, options = {}) {
2539
+ async function processSingleMessage(task, ctx, providedMasterKey, predecrypted = null) {
2540
+ const deliveryState = {
2541
+ outboxed: false,
2542
+ deliveryLimit: resolveMaxDeliveryRetries(ctx),
2543
+ generationLimit: resolveMaxDeliveryRetries(ctx),
2544
+ retryCount: _nullishCoalesce(task.retry_count, () => ( 0)),
2545
+ isCancelled: ctx.isTaskCancelled
2546
+ };
2547
+ ctx = { ...ctx, _deliveryState: deliveryState };
2498
2548
  try {
2499
2549
  const masterKey = providedMasterKey || ctx.masterKey;
2500
2550
  if (!masterKey) {
@@ -2502,17 +2552,18 @@ async function processSingleMessage(task, ctx, providedMasterKey, predecrypted =
2502
2552
  }
2503
2553
  const userKey = predecrypted && predecrypted.userKey || await deriveUserEncryptionKey(task.user_id, masterKey);
2504
2554
  const decryptedPayload = predecrypted && predecrypted.payload || JSON.parse(await decryptFromStorage(task.encrypted_payload, userKey));
2505
- const resumeCommittedBatch = options && typeof options.resumeCommittedBatch === "boolean" ? options.resumeCommittedBatch : (task.retry_count || 0) > 0;
2506
- if (resumeCommittedBatch) {
2507
- const committed = await findCommittedBatch({
2508
- db: ctx.db,
2509
- userId: task.user_id,
2510
- userKey,
2511
- taskUuid: task.uuid,
2512
- occurrenceMs: occurrenceMsOf(task)
2513
- });
2514
- if (committed) return await redeliverCommittedBatch(task, ctx, userKey, decryptedPayload, committed);
2555
+ const committed = await findCommittedBatch({
2556
+ db: ctx.db,
2557
+ userId: task.user_id,
2558
+ userKey,
2559
+ taskUuid: task.uuid,
2560
+ occurrenceMs: occurrenceMsOf(task)
2561
+ });
2562
+ if (committed) {
2563
+ deliveryState.outboxed = true;
2564
+ return await redeliverCommittedBatch(task, ctx, userKey, decryptedPayload, committed);
2515
2565
  }
2566
+ deliveryState.generationLimit = resolveMaxGenerationRetries(ctx, buildHookTask(task, decryptedPayload));
2516
2567
  if (ctx.hooks && typeof ctx.hooks.onBeforeFire === "function" && taskNeedsLlm(decryptedPayload)) {
2517
2568
  const agentic = await runAgenticFire({ task, decryptedPayload, userKey, ctx });
2518
2569
  if (agentic.handled) return agentic.result;
@@ -2593,6 +2644,7 @@ async function processSingleMessage(task, ctx, providedMasterKey, predecrypted =
2593
2644
  }
2594
2645
  const pushesToSend = reasoningPush ? [reasoningPush, ...contentPushes] : contentPushes;
2595
2646
  const outboxed = await appendPushesToOutbox({ db: ctx.db, userId: task.user_id, userKey, pushes: pushesToSend });
2647
+ deliveryState.outboxed = outboxed;
2596
2648
  if (!ctx.vapid.email || !ctx.vapid.publicKey || !ctx.vapid.privateKey) {
2597
2649
  throw new Error("VAPID configuration missing - push notifications cannot be sent");
2598
2650
  }
@@ -2650,7 +2702,8 @@ async function processSingleMessage(task, ctx, providedMasterKey, predecrypted =
2650
2702
  error: error.message,
2651
2703
  errorCode: error.code || null,
2652
2704
  pushStatusCode: readPushStatusCode(error),
2653
- permanent: isNonRetryableError(error)
2705
+ permanent: isNonRetryableError(error),
2706
+ ...failureRetryDecision(deliveryState, error)
2654
2707
  };
2655
2708
  }
2656
2709
  }
@@ -2663,7 +2716,7 @@ async function processMessagesByUuid(uuid, ctx, maxRetries = 2, userId, provided
2663
2716
  error: { code: "TENANT_MASTER_KEY_MISSING", message: "\u79DF\u6237\u4E3B\u5BC6\u94A5\u4E0D\u5B58\u5728\u6216\u914D\u7F6E\u5F02\u5E38" }
2664
2717
  };
2665
2718
  }
2666
- while (retryCount <= maxRetries) {
2719
+ while (true) {
2667
2720
  let task;
2668
2721
  try {
2669
2722
  task = userId ? await ctx.db.getTaskByUuid(uuid, userId) : await ctx.db.getTaskByUuidOnly(uuid);
@@ -2681,16 +2734,14 @@ async function processMessagesByUuid(uuid, ctx, maxRetries = 2, userId, provided
2681
2734
  if (!task) {
2682
2735
  return { success: false, error: { code: "TASK_NOT_FOUND", message: "\u4EFB\u52A1\u4E0D\u5B58\u5728\u6216\u5DF2\u5904\u7406" } };
2683
2736
  }
2684
- const result = await processSingleMessage(task, ctx, masterKey, null, {
2685
- resumeCommittedBatch: retryCount > 0 || (task.retry_count || 0) > 0
2686
- });
2737
+ const result = await processSingleMessage({ ...task, retry_count: retryCount }, { ...ctx, maxDeliveryRetries: maxRetries }, masterKey, null);
2687
2738
  if (!result.success) {
2688
2739
  const permanent = isPermanentDeliveryFailure({
2689
2740
  permanent: result.permanent,
2690
2741
  errorCode: result.errorCode,
2691
2742
  pushStatus: result.pushStatusCode
2692
2743
  });
2693
- if (!permanent && retryCount < maxRetries) {
2744
+ if (!permanent && result.willRetry !== false && retryCount < (_nullishCoalesce(result.retryLimit, () => ( maxRetries)))) {
2694
2745
  retryCount++;
2695
2746
  await new Promise((resolve) => setTimeout(resolve, 1e3 * retryCount));
2696
2747
  continue;
@@ -2925,10 +2976,10 @@ function createScheduleMessageHandler(ctx) {
2925
2976
  dbResult = await db.createTaskSuperseding(createParams, supersedesUuid);
2926
2977
  superseded = !!(dbResult && dbResult.superseded);
2927
2978
  } else {
2928
- if (supersedesUuid) {
2979
+ dbResult = await db.createTask(createParams);
2980
+ if (supersedesUuid && dbResult) {
2929
2981
  superseded = await db.deleteTaskByUuid(supersedesUuid, userId);
2930
2982
  }
2931
- dbResult = await db.createTask(createParams);
2932
2983
  }
2933
2984
  } catch (error) {
2934
2985
  if (isUniqueViolation(error)) {
@@ -3010,7 +3061,6 @@ var CLAIM_LEASE_MARGIN_MS = 2 * 60 * 1e3;
3010
3061
  var DEFAULT_LEASE_HEARTBEAT_MS = 30 * 1e3;
3011
3062
  var DEFAULT_HEARTBEAT_LEASE_TTL_MS = 90 * 1e3;
3012
3063
  var STALE_AFTER_MS = 60 * 60 * 1e3;
3013
- var DEFAULT_MAX_DELIVERY_RETRIES = 3;
3014
3064
  var adaptersWithoutLastErrorColumn = /* @__PURE__ */ new WeakSet();
3015
3065
  var lastErrorColumnSuspicions = /* @__PURE__ */ new WeakMap();
3016
3066
  var warnedAboutMissingLastErrorColumn = false;
@@ -3058,10 +3108,6 @@ function guardWebpushWithLease(webpush, lease) {
3058
3108
  function resolveStaleAfterMs(ctx) {
3059
3109
  return positiveNumber(ctx.staleAfterMs) || STALE_AFTER_MS;
3060
3110
  }
3061
- function resolveMaxDeliveryRetries(ctx) {
3062
- const value = ctx.maxDeliveryRetries;
3063
- return Number.isInteger(value) && value >= 0 ? value : DEFAULT_MAX_DELIVERY_RETRIES;
3064
- }
3065
3111
  function isRecurringType(recurrenceType) {
3066
3112
  return recurrenceType === "daily" || recurrenceType === "weekly";
3067
3113
  }
@@ -3324,7 +3370,7 @@ async function deliverTasks(ctx, tasks) {
3324
3370
  const permanent = isPermanentDeliveryFailure({ permanent: failure.permanent, errorCode, pushStatus });
3325
3371
  const errorExtra = buildErrorExtra(errorCode, pushStatus);
3326
3372
  try {
3327
- if (permanent || task.retry_count >= maxDeliveryRetries) {
3373
+ if (permanent || failure.willRetry === false || task.retry_count >= (_nullishCoalesce(failure.retryLimit, () => ( maxDeliveryRetries)))) {
3328
3374
  const encrypted = await encryptPayloadWithLastError(task, decryptedPayload, userKey, reason, errorExtra);
3329
3375
  if (isRecurringType(recurrenceType)) {
3330
3376
  const nextSendAt = nextFutureOccurrence(Date.parse(task.next_send_at), recurrenceType, Date.now(), tzId);
@@ -3510,10 +3556,7 @@ async function deliverTasks(ctx, tasks) {
3510
3556
  isTaskCancelled: () => lease.lost
3511
3557
  },
3512
3558
  masterKey,
3513
- { userKey, payload: decryptedPayload },
3514
- // 重试计数就记在这一列上:大于 0 说明这是同一次触发的重试,内容已经落
3515
- // 进 outbox 的话只补推送、不再生成(见 redeliverCommittedBatch)。
3516
- { resumeCommittedBatch: (task.retry_count || 0) > 0 }
3559
+ { userKey, payload: decryptedPayload }
3517
3560
  );
3518
3561
  } catch (error) {
3519
3562
  if (lease.lost) {
@@ -3541,7 +3584,7 @@ async function deliverTasks(ctx, tasks) {
3541
3584
  recurrenceType,
3542
3585
  decryptedPayload,
3543
3586
  userKey,
3544
- { errorCode: sendResult.errorCode || null, permanent: sendResult.permanent === true, pushStatus: sendResult.pushStatusCode }
3587
+ { errorCode: sendResult.errorCode || null, permanent: sendResult.permanent === true, pushStatus: sendResult.pushStatusCode, willRetry: sendResult.willRetry, retryLimit: sendResult.retryLimit }
3545
3588
  );
3546
3589
  return;
3547
3590
  }
@@ -3822,6 +3865,16 @@ function createUpdateMessageHandler(ctx) {
3822
3865
  };
3823
3866
  const result = await db.updateTaskByUuid(taskUuid, userId, encryptedPayload, extraFields);
3824
3867
  if (!result) {
3868
+ const inFlight = updates.nextSendAt && typeof db.getTaskByUuid === "function" && !!await db.getTaskByUuid(taskUuid, userId);
3869
+ if (inFlight) {
3870
+ return {
3871
+ status: 409,
3872
+ body: {
3873
+ success: false,
3874
+ error: { code: "TASK_IN_FLIGHT", message: "\u8FD9\u6761\u4EFB\u52A1\u6B63\u5728\u6295\u9012\uFF0C\u7B49\u8FD9\u6B21\u53D1\u5B8C\u518D\u6539\u6392\u671F" }
3875
+ }
3876
+ };
3877
+ }
3825
3878
  return { status: 409, body: { success: false, error: { code: "UPDATE_CONFLICT", message: "\u4EFB\u52A1\u66F4\u65B0\u5931\u8D25\uFF0C\u4EFB\u52A1\u53EF\u80FD\u5DF2\u88AB\u4FEE\u6539\u6216\u5220\u9664" } } };
3826
3879
  }
3827
3880
  const applied = /* @__PURE__ */ new Set([
@@ -4758,6 +4811,15 @@ var D1Adapter = class {
4758
4811
  ).bind(...values).run();
4759
4812
  return this._db.prepare("SELECT * FROM scheduled_messages WHERE id = ?").bind(taskId).first();
4760
4813
  }
4814
+ /**
4815
+ * 按 uuid 改一条 pending 任务(PUT /update-message、fire hook 的 renewTask)。
4816
+ *
4817
+ * 改排期(extraFields 里带 next_send_at)时多一道租约门:这条任务正被一次投递
4818
+ * 占着的话不改,返回 null。投递收尾会按自己领取时看到的排期推进下一次(一次性
4819
+ * 任务干脆标成已发送),这期间写进去的新时刻随后就被盖掉,接口却已经回了成功
4820
+ * ——用户以为改期生效了,实际什么都没留下。正文这类字段不受这道门约束:它们
4821
+ * 只影响以后的触发,收尾那边本来就不会覆盖(见 run-tick 的收尾守卫)。
4822
+ */
4761
4823
  async updateTaskByUuid(uuid, userId, encryptedPayload, extraFields) {
4762
4824
  const now = this._now();
4763
4825
  const sets = ["encrypted_payload = ?", "updated_at = ?"];
@@ -4772,9 +4834,14 @@ var D1Adapter = class {
4772
4834
  }
4773
4835
  }
4774
4836
  values.push(uuid, userId);
4837
+ let leaseGate = "";
4838
+ if (extraFields && Object.prototype.hasOwnProperty.call(extraFields, "next_send_at")) {
4839
+ leaseGate = " AND (lease_until IS NULL OR lease_until <= ?)";
4840
+ values.push(now);
4841
+ }
4775
4842
  const res = await this._db.prepare(
4776
4843
  `UPDATE scheduled_messages SET ${sets.join(", ")}
4777
- WHERE uuid = ? AND user_id = ? AND status = 'pending'`
4844
+ WHERE uuid = ? AND user_id = ? AND status = 'pending'${leaseGate}`
4778
4845
  ).bind(...values).run();
4779
4846
  if (!res.meta.changes) return null;
4780
4847
  return { uuid, updated_at: now };
@@ -5032,7 +5099,7 @@ var D1Adapter = class {
5032
5099
  result.cleanupOutcomes = probeIndexes.map((probeAt) => {
5033
5100
  if (probeAt === null) return null;
5034
5101
  const row = results[probeAt] && Array.isArray(results[probeAt].results) ? results[probeAt].results[0] : null;
5035
- return Number(_nullishCoalesce(_optionalChain([row, 'optionalAccess', _3 => _3.n]), () => ( 0))) === 0;
5102
+ return Number(_nullishCoalesce(_optionalChain([row, 'optionalAccess', _6 => _6.n]), () => ( 0))) === 0;
5036
5103
  });
5037
5104
  }
5038
5105
  return result;
@@ -5893,7 +5960,7 @@ function createClientStateNamespacesHandler(ctx) {
5893
5960
  }
5894
5961
 
5895
5962
  // src/server/lib/version.js
5896
- var SERVER_VERSION = true ? "2.6.0-next.30" : "0.0.0-dev";
5963
+ var SERVER_VERSION = true ? "2.6.0-next.31" : "0.0.0-dev";
5897
5964
 
5898
5965
  // src/server/handlers/capabilities.js
5899
5966
  var SERVER_FEATURES = Object.freeze([
@@ -6004,7 +6071,9 @@ var SERVER_FEATURES = Object.freeze([
6004
6071
  // onAfterSend / onFireSettled 的载荷带整次 fire 的 usageTotal 与 llmCalls。
6005
6072
  "hook-usage-total",
6006
6073
  // 工厂配置认 maxDeliveryRetries(投递失败的重试次数上限,默认 3)。
6007
- "max-delivery-retries"
6074
+ "max-delivery-retries",
6075
+ // Per-task pre-commit retry limit and fire receipt retry decision.
6076
+ "max-generation-retries"
6008
6077
  ]);
6009
6078
  function createCapabilitiesHandler(ctx) {
6010
6079
  async function GET(url, headers) {
@@ -6217,6 +6286,7 @@ function createSingleUserServer(config) {
6217
6286
  maxScheduledTasksPerFire: config.maxScheduledTasksPerFire,
6218
6287
  // 定时任务投递失败后的重试次数上限(默认 3)。handlers 用不到,宿主拿这个
6219
6288
  // ctx 去调 runScheduledTick 时它跟着走。
6289
+ maxGenerationRetries: config.maxGenerationRetries,
6220
6290
  maxDeliveryRetries: config.maxDeliveryRetries
6221
6291
  };
6222
6292
  return {
@@ -6400,6 +6470,7 @@ function createSingleUserCloudflareWorker(buildConfig, options = {}) {
6400
6470
  onFireSettled: cfg.onFireSettled,
6401
6471
  // 一次触发投递失败后最多再重试几次(默认 3,0 = 第一次失败就终审;见
6402
6472
  // lib/run-tick.js 的 DEFAULT_MAX_DELIVERY_RETRIES)。
6473
+ maxGenerationRetries: cfg.maxGenerationRetries,
6403
6474
  maxDeliveryRetries: cfg.maxDeliveryRetries,
6404
6475
  // 分组串行:(task) => 分组标识 | null。同一分组的任务同时只跑一条,
6405
6476
  // 跨跳也算(见 lib/run-tick.js)。不配 = 全并发,与以前一致。
@@ -6630,4 +6701,4 @@ function createSingleUserCloudflareWorker(buildConfig, options = {}) {
6630
6701
 
6631
6702
 
6632
6703
 
6633
- 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.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.NonRetryableError = NonRetryableError; exports.isNonRetryableError = isNonRetryableError; exports.DeploymentConfigError = DeploymentConfigError; exports.sanitizeErrorSummary = sanitizeErrorSummary; exports.summarizeErrorCause = summarizeErrorCause; 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.DEFAULT_MAX_DELIVERY_RETRIES = DEFAULT_MAX_DELIVERY_RETRIES; 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;
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;