@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 +20 -0
- package/dist/adapters/d1.d.ts +9 -0
- package/dist/adapters/interface.d.ts +4 -2
- package/dist/adapters/neon.d.ts +17 -0
- package/dist/adapters/pg-shared.d.ts +33 -0
- package/dist/adapters/pg.d.ts +17 -0
- package/dist/{chunk-E4657Y5X.cjs → chunk-7TM3MH3Q.cjs} +165 -94
- package/dist/{chunk-VBDBORLR.mjs → chunk-PG54W6B5.mjs} +168 -97
- package/dist/{chunk-E7OWP3VL.cjs → chunk-TKDISKCF.cjs} +29 -1
- package/dist/{chunk-ZGGF4GMA.mjs → chunk-XKUWMYVF.mjs} +28 -0
- package/dist/cloudflare.cjs +2 -2
- package/dist/cloudflare.mjs +1 -1
- package/dist/index.cjs +28 -27
- package/dist/index.d.cts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.mjs +4 -3
- package/dist/lib/message-processor.d.ts +9 -11
- package/dist/lib/retry-policy.d.ts +11 -0
- package/dist/lib/run-tick.d.ts +1 -1
- package/dist/{neon-ZIMHALYI.mjs → neon-GKLT4UY3.mjs} +18 -2
- package/dist/{neon-KP2CPA57.cjs → neon-XHPJZESV.cjs} +28 -12
- package/dist/{pg-RZPQGXR3.cjs → pg-FHXQ6BJP.cjs} +27 -12
- package/dist/{pg-QGC2XHUT.mjs → pg-J4YXRQP5.mjs} +17 -2
- package/dist/single-user.d.ts +1 -0
- package/package.json +1 -1
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 里维护的「我刚才说过什么」台账通常是读进内存 → 改 → 整份写回,两条各改各的再写回,后写的必然盖掉前面那条。
|
package/dist/adapters/d1.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
306
|
-
* supersedesUuid
|
|
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;
|
package/dist/adapters/neon.d.ts
CHANGED
|
@@ -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
|
*
|
package/dist/adapters/pg.d.ts
CHANGED
|
@@ -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)
|
|
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',
|
|
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:
|
|
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
|
|
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
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
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 (
|
|
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
|
-
|
|
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',
|
|
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.
|
|
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.
|
|
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;
|