@openclaw/zalo 2026.7.2-beta.5 → 2026.8.1-beta.2
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/dist/{actions.runtime-Cqmv6cPB.js → actions.runtime-BRP15DNe.js} +1 -1
- package/dist/api.js +2 -2
- package/dist/{channel-BqA9TbSR.js → channel-Dcp-XLf8.js} +26 -11
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel.runtime-D4wDYzqp.js → channel.runtime-5CivhE5e.js} +17 -34
- package/dist/{monitor-DvrLTTtn.js → monitor-Ckz5Mo22.js} +137 -80
- package/dist/{monitor.webhook-BfWVHoMH.js → monitor.webhook-S7nThy3R.js} +4 -5
- package/dist/{send-BeG5dH7I.js → send-BGGtv8x_.js} +6 -2
- package/dist/setup-api.js +2 -2
- package/dist/{setup-core-qcMoaIyL.js → setup-core-T6qc2eAG.js} +61 -69
- package/dist/{setup-surface-Cqevy46r.js → setup-surface-edfRk3eu.js} +7 -8
- package/dist/setup-surface.js +3 -0
- package/dist/{webhook-spool-D9q-q0oh.js → webhook-spool-Bf17xwqi.js} +17 -72
- package/openclaw.plugin.json +99 -0
- package/package.json +8 -5
package/dist/api.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { n as zaloDmPolicy, r as zaloSetupAdapter, t as createZaloSetupWizardProxy } from "./setup-core-
|
|
2
|
-
import { t as zaloPlugin } from "./channel-
|
|
1
|
+
import { n as zaloDmPolicy, r as zaloSetupAdapter, t as createZaloSetupWizardProxy } from "./setup-core-T6qc2eAG.js";
|
|
2
|
+
import { t as zaloPlugin } from "./channel-Dcp-XLf8.js";
|
|
3
3
|
import { n as resolveZaloRuntimeGroupPolicy } from "./group-access-8qHRzDHx.js";
|
|
4
4
|
import { zaloSetupWizard } from "./setup-api.js";
|
|
5
5
|
export { createZaloSetupWizardProxy, resolveZaloRuntimeGroupPolicy, zaloDmPolicy, zaloPlugin, zaloSetupAdapter, zaloSetupWizard };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as
|
|
1
|
+
import { c as inspectZaloAccount, d as resolveZaloAccount, i as zaloSetupContract, l as listZaloAccountIds, p as buildSecretInputSchema, t as createZaloSetupWizardProxy, u as resolveDefaultZaloAccountId } from "./setup-core-T6qc2eAG.js";
|
|
2
2
|
import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-Bxvjh0Hq.js";
|
|
3
3
|
import { describeWebhookAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
|
|
4
4
|
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
|
|
@@ -24,14 +24,17 @@ import { AllowFromListSchema, DmPolicySchema, GroupPolicySchema, MarkdownConfigS
|
|
|
24
24
|
import { z } from "zod";
|
|
25
25
|
import { coerceStatusIssueAccountId, readStatusIssueFields } from "openclaw/plugin-sdk/extension-shared";
|
|
26
26
|
//#region extensions/zalo/src/actions.ts
|
|
27
|
-
const loadZaloActionsRuntime = createLazyRuntimeNamedExport(() => import("./actions.runtime-
|
|
27
|
+
const loadZaloActionsRuntime = createLazyRuntimeNamedExport(() => import("./actions.runtime-BRP15DNe.js"), "zaloActionsRuntime");
|
|
28
28
|
const providerId = "zalo";
|
|
29
29
|
const ZALO_ACTIONS = /* @__PURE__ */ new Set(["send"]);
|
|
30
30
|
function listEnabledAccounts(cfg, accountId) {
|
|
31
|
-
return (accountId ? [
|
|
31
|
+
return (accountId ? [inspectZaloAccount({
|
|
32
32
|
cfg,
|
|
33
33
|
accountId
|
|
34
|
-
})] :
|
|
34
|
+
})] : listZaloAccountIds(cfg).map((listedAccountId) => inspectZaloAccount({
|
|
35
|
+
cfg,
|
|
36
|
+
accountId: listedAccountId
|
|
37
|
+
}))).filter((account) => account.enabled && account.tokenStatus === "available");
|
|
35
38
|
}
|
|
36
39
|
const zaloMessageActions = {
|
|
37
40
|
describeMessageTool: ({ cfg, accountId }) => {
|
|
@@ -89,6 +92,7 @@ const zaloApprovalAuth = createChannelApprovalAuth({
|
|
|
89
92
|
const ZaloConfigSchema = buildMultiAccountChannelSchema(z.object({
|
|
90
93
|
name: z.string().optional(),
|
|
91
94
|
enabled: z.boolean().optional(),
|
|
95
|
+
configWrites: z.boolean().optional(),
|
|
92
96
|
markdown: MarkdownConfigSchema,
|
|
93
97
|
botToken: buildSecretInputSchema().optional(),
|
|
94
98
|
tokenFile: z.string().optional(),
|
|
@@ -176,8 +180,8 @@ function looksLikeZaloChatId(raw, normalized) {
|
|
|
176
180
|
const target = normalizeZaloMessagingTarget(normalized ?? raw);
|
|
177
181
|
return Boolean(target);
|
|
178
182
|
}
|
|
179
|
-
const loadZaloChannelRuntime = createLazyRuntimeModule(() => import("./channel.runtime-
|
|
180
|
-
const zaloSetupWizard = createZaloSetupWizardProxy(async () => (await import("./setup-surface-
|
|
183
|
+
const loadZaloChannelRuntime = createLazyRuntimeModule(() => import("./channel.runtime-5CivhE5e.js"));
|
|
184
|
+
const zaloSetupWizard = createZaloSetupWizardProxy(async () => (await import("./setup-surface-edfRk3eu.js").then((n) => n.t)).zaloSetupWizard);
|
|
181
185
|
const zaloTextChunkLimit = 2e3;
|
|
182
186
|
async function sendZaloDelivery(ctx) {
|
|
183
187
|
const result = await (await loadZaloChannelRuntime()).sendZaloText({
|
|
@@ -210,6 +214,9 @@ const zaloMessageAdapter = defineChannelMessageAdapter({
|
|
|
210
214
|
media: sendZaloDelivery
|
|
211
215
|
}
|
|
212
216
|
});
|
|
217
|
+
function isZaloAccountConfigured(account) {
|
|
218
|
+
return account.tokenStatus ? account.tokenStatus !== "missing" : Boolean(account.token?.trim());
|
|
219
|
+
}
|
|
213
220
|
const zaloConfigAdapter = createScopedChannelConfigAdapter({
|
|
214
221
|
sectionKey: "zalo",
|
|
215
222
|
listAccountIds: listZaloAccountIds,
|
|
@@ -257,7 +264,6 @@ const zaloPlugin = createChatChannelPlugin({
|
|
|
257
264
|
base: {
|
|
258
265
|
id: "zalo",
|
|
259
266
|
meta,
|
|
260
|
-
setup: zaloSetupAdapter,
|
|
261
267
|
setupContract: zaloSetupContract,
|
|
262
268
|
setupWizard: zaloSetupWizard,
|
|
263
269
|
capabilities: {
|
|
@@ -273,12 +279,16 @@ const zaloPlugin = createChatChannelPlugin({
|
|
|
273
279
|
configSchema: buildChannelConfigSchema(ZaloConfigSchema),
|
|
274
280
|
config: {
|
|
275
281
|
...zaloConfigAdapter,
|
|
276
|
-
|
|
282
|
+
inspectAccount: adaptScopedAccountAccessor(inspectZaloAccount),
|
|
283
|
+
isConfigured: isZaloAccountConfigured,
|
|
277
284
|
describeAccount: (account) => describeWebhookAccountSnapshot({
|
|
278
285
|
account,
|
|
279
|
-
configured:
|
|
286
|
+
configured: isZaloAccountConfigured(account),
|
|
280
287
|
mode: account.config.webhookUrl ? "webhook" : "polling",
|
|
281
|
-
extra: {
|
|
288
|
+
extra: {
|
|
289
|
+
tokenSource: account.tokenSource,
|
|
290
|
+
tokenStatus: account.tokenStatus
|
|
291
|
+
}
|
|
282
292
|
})
|
|
283
293
|
},
|
|
284
294
|
approvalCapability: zaloApprovalAuth,
|
|
@@ -291,6 +301,10 @@ const zaloPlugin = createChatChannelPlugin({
|
|
|
291
301
|
messaging: {
|
|
292
302
|
targetPrefixes: ["zalo", "zl"],
|
|
293
303
|
normalizeTarget: normalizeZaloMessagingTarget,
|
|
304
|
+
inferTargetChatType: ({ to }) => {
|
|
305
|
+
const target = normalizeZaloMessagingTarget(to);
|
|
306
|
+
return target ? /^group:/i.test(target) ? "group" : "direct" : void 0;
|
|
307
|
+
},
|
|
294
308
|
resolveOutboundSessionRoute: (params) => resolveZaloOutboundSessionRoute(params),
|
|
295
309
|
targetResolver: {
|
|
296
310
|
looksLikeId: looksLikeZaloChatId,
|
|
@@ -315,7 +329,7 @@ const zaloPlugin = createChatChannelPlugin({
|
|
|
315
329
|
timeoutMs
|
|
316
330
|
}),
|
|
317
331
|
resolveAccountSnapshot: ({ account }) => {
|
|
318
|
-
const configured =
|
|
332
|
+
const configured = isZaloAccountConfigured(account);
|
|
319
333
|
return {
|
|
320
334
|
accountId: account.accountId,
|
|
321
335
|
name: account.name,
|
|
@@ -323,6 +337,7 @@ const zaloPlugin = createChatChannelPlugin({
|
|
|
323
337
|
configured,
|
|
324
338
|
extra: {
|
|
325
339
|
tokenSource: account.tokenSource,
|
|
340
|
+
tokenStatus: account.tokenStatus,
|
|
326
341
|
mode: account.config.webhookUrl ? "webhook" : "polling",
|
|
327
342
|
dmPolicy: account.config.dmPolicy ?? "pairing"
|
|
328
343
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as zaloPlugin } from "./channel-
|
|
1
|
+
import { t as zaloPlugin } from "./channel-Dcp-XLf8.js";
|
|
2
2
|
export { zaloPlugin };
|
|
@@ -1,58 +1,41 @@
|
|
|
1
|
-
import { m as normalizeSecretInputString } from "./setup-core-
|
|
1
|
+
import { m as normalizeSecretInputString } from "./setup-core-T6qc2eAG.js";
|
|
2
2
|
import { i as PAIRING_APPROVED_MESSAGE } from "./runtime-api-qxdOyk3M.js";
|
|
3
|
-
import { a as getMe, n as resolveZaloProxyFetch, r as ZaloApiError, t as sendMessageZalo } from "./send-
|
|
3
|
+
import { a as getMe, n as resolveZaloProxyFetch, r as ZaloApiError, t as sendMessageZalo } from "./send-BGGtv8x_.js";
|
|
4
4
|
import { createAccountStatusSink } from "openclaw/plugin-sdk/channel-outbound";
|
|
5
|
+
import { runChannelProbe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
5
6
|
//#region extensions/zalo/src/probe.ts
|
|
7
|
+
function formatZaloProbeError(error, timeoutMs) {
|
|
8
|
+
if (error instanceof ZaloApiError) return error.description ?? error.message;
|
|
9
|
+
if (error instanceof Error) return error.name === "AbortError" ? `Request timed out after ${timeoutMs}ms` : error.message;
|
|
10
|
+
return String(error);
|
|
11
|
+
}
|
|
6
12
|
async function probeZalo(token, timeoutMs = 5e3, fetcher) {
|
|
7
13
|
if (!token?.trim()) return {
|
|
8
14
|
ok: false,
|
|
9
15
|
error: "No token provided",
|
|
10
16
|
elapsedMs: 0
|
|
11
17
|
};
|
|
12
|
-
|
|
13
|
-
try {
|
|
18
|
+
return await runChannelProbe(void 0, async ({ elapsedMs }) => {
|
|
14
19
|
const response = await getMe(token.trim(), timeoutMs, fetcher);
|
|
15
|
-
const elapsedMs = Date.now() - startTime;
|
|
16
20
|
if (response.ok && response.result) return {
|
|
17
21
|
ok: true,
|
|
18
22
|
bot: response.result,
|
|
19
|
-
elapsedMs
|
|
23
|
+
elapsedMs: elapsedMs()
|
|
20
24
|
};
|
|
21
25
|
return {
|
|
22
26
|
ok: false,
|
|
23
27
|
error: "Invalid response from Zalo API",
|
|
24
|
-
elapsedMs
|
|
25
|
-
};
|
|
26
|
-
} catch (err) {
|
|
27
|
-
const elapsedMs = Date.now() - startTime;
|
|
28
|
-
if (err instanceof ZaloApiError) return {
|
|
29
|
-
ok: false,
|
|
30
|
-
error: err.description ?? err.message,
|
|
31
|
-
elapsedMs
|
|
28
|
+
elapsedMs: elapsedMs()
|
|
32
29
|
};
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
elapsedMs
|
|
38
|
-
};
|
|
39
|
-
return {
|
|
40
|
-
ok: false,
|
|
41
|
-
error: err.message,
|
|
42
|
-
elapsedMs
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
return {
|
|
46
|
-
ok: false,
|
|
47
|
-
error: String(err),
|
|
48
|
-
elapsedMs
|
|
49
|
-
};
|
|
50
|
-
}
|
|
30
|
+
}, (error) => ({
|
|
31
|
+
ok: false,
|
|
32
|
+
error: formatZaloProbeError(error, timeoutMs)
|
|
33
|
+
}));
|
|
51
34
|
}
|
|
52
35
|
//#endregion
|
|
53
36
|
//#region extensions/zalo/src/channel.runtime.ts
|
|
54
37
|
async function notifyZaloPairingApproval(params) {
|
|
55
|
-
const { resolveZaloAccount } = await import("./setup-core-
|
|
38
|
+
const { resolveZaloAccount } = await import("./setup-core-T6qc2eAG.js").then((n) => n.s);
|
|
56
39
|
const account = resolveZaloAccount({ cfg: params.cfg });
|
|
57
40
|
if (!account.token) throw new Error("Zalo token not configured");
|
|
58
41
|
await sendMessageZalo(params.id, PAIRING_APPROVED_MESSAGE, { token: account.token });
|
|
@@ -86,7 +69,7 @@ async function startZaloGatewayAccount(ctx) {
|
|
|
86
69
|
setStatus: ctx.setStatus
|
|
87
70
|
});
|
|
88
71
|
ctx.log?.info(`[${account.accountId}] starting provider${zaloBotLabel} mode=${mode}`);
|
|
89
|
-
const { monitorZaloProvider } = await import("./monitor-
|
|
72
|
+
const { monitorZaloProvider } = await import("./monitor-Ckz5Mo22.js");
|
|
90
73
|
return monitorZaloProvider({
|
|
91
74
|
token,
|
|
92
75
|
account,
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { n as resolveZaloRuntimeGroupPolicy, t as normalizeZaloAllowEntry } from "./group-access-8qHRzDHx.js";
|
|
2
2
|
import { t as getZaloRuntime } from "./runtime-api-qxdOyk3M.js";
|
|
3
|
-
import { c as sendChatAction, d as setWebhook, f as ZALO_OUTBOUND_MEDIA_TTL_MS, i as deleteWebhook, l as sendMessage, n as resolveZaloProxyFetch, o as getUpdates, r as ZaloApiError, s as getWebhookInfo, u as sendPhoto } from "./send-
|
|
3
|
+
import { c as sendChatAction, d as setWebhook, f as ZALO_OUTBOUND_MEDIA_TTL_MS, i as deleteWebhook, l as sendMessage, n as resolveZaloProxyFetch, o as getUpdates, r as ZaloApiError, s as getWebhookInfo, u as sendPhoto } from "./send-BGGtv8x_.js";
|
|
4
|
+
import { createMessageReceiptFromOutboundResults } from "openclaw/plugin-sdk/channel-outbound";
|
|
4
5
|
import { createLazyRuntimeModule, createLazyRuntimeNamedExport } from "openclaw/plugin-sdk/lazy-runtime";
|
|
5
6
|
import { deliverTextOrMediaReply, resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
|
|
6
7
|
import { normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
7
8
|
import { resolveDefaultGroupPolicy, warnMissingProviderGroupPolicyFallbackOnce } from "openclaw/plugin-sdk/runtime-group-policy";
|
|
8
9
|
import { createChannelPairingController } from "openclaw/plugin-sdk/channel-pairing";
|
|
9
10
|
import { logTypingFailure } from "openclaw/plugin-sdk/channel-feedback";
|
|
10
|
-
import { registerPluginHttpRoute, resolveWebhookPath } from "openclaw/plugin-sdk/webhook-ingress";
|
|
11
|
+
import { canonicalizeWebhookRouteKey, registerPluginHttpRoute, resolveWebhookPath } from "openclaw/plugin-sdk/webhook-ingress";
|
|
11
12
|
import { asDateTimestampMs, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
|
|
12
|
-
import { formatInboundMediaUnavailableText, resolveChannelInboundRouteEnvelope } from "openclaw/plugin-sdk/channel-inbound";
|
|
13
|
+
import { createChannelPartialDeliveryError, formatInboundMediaUnavailableText, resolveChannelInboundRouteEnvelope } from "openclaw/plugin-sdk/channel-inbound";
|
|
13
14
|
import { resolveStableChannelMessageIngress } from "openclaw/plugin-sdk/channel-ingress-runtime";
|
|
15
|
+
import { channelReadyPatch } from "openclaw/plugin-sdk/gateway-runtime";
|
|
14
16
|
import { sleepWithAbort, waitForAbortSignal } from "openclaw/plugin-sdk/runtime-env";
|
|
15
17
|
import { createHostedOutboundMediaStore } from "openclaw/plugin-sdk/outbound-media";
|
|
16
18
|
import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
|
|
@@ -115,35 +117,44 @@ async function tryHandleHostedZaloMediaRequest(req, res) {
|
|
|
115
117
|
res.end("Expired");
|
|
116
118
|
return true;
|
|
117
119
|
}
|
|
118
|
-
const
|
|
119
|
-
if (!
|
|
120
|
+
const metadata = await store.readMetadata(id, now);
|
|
121
|
+
if (!metadata || metadata.routePath !== routePath) {
|
|
120
122
|
res.statusCode = 404;
|
|
121
123
|
res.end("Not Found");
|
|
122
124
|
return true;
|
|
123
125
|
}
|
|
124
|
-
const expiresAt = asDateTimestampMs(
|
|
126
|
+
const expiresAt = asDateTimestampMs(metadata.expiresAt);
|
|
125
127
|
if (expiresAt === void 0 || expiresAt <= now) {
|
|
126
128
|
await store.delete(id);
|
|
127
129
|
res.statusCode = 410;
|
|
128
130
|
res.end("Expired");
|
|
129
131
|
return true;
|
|
130
132
|
}
|
|
131
|
-
|
|
133
|
+
const token = url.searchParams.get("token");
|
|
134
|
+
if (!safeEqualSecret(token, metadata.token)) {
|
|
132
135
|
res.statusCode = 401;
|
|
133
136
|
res.end("Unauthorized");
|
|
134
137
|
return true;
|
|
135
138
|
}
|
|
136
|
-
|
|
139
|
+
let servedMetadata = metadata;
|
|
140
|
+
let body;
|
|
141
|
+
if (method === "GET") {
|
|
142
|
+
const entry = await store.read(id, now);
|
|
143
|
+
if (!entry || entry.metadata.routePath !== routePath || !safeEqualSecret(token, entry.metadata.token)) {
|
|
144
|
+
res.statusCode = 404;
|
|
145
|
+
res.end("Not Found");
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
servedMetadata = entry.metadata;
|
|
149
|
+
body = entry.buffer;
|
|
150
|
+
}
|
|
151
|
+
if (servedMetadata.contentType) res.setHeader("Content-Type", servedMetadata.contentType);
|
|
137
152
|
res.setHeader("Cache-Control", "no-store");
|
|
138
153
|
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
139
|
-
res.setHeader("Content-Length", String(
|
|
140
|
-
if (method === "HEAD") {
|
|
141
|
-
res.statusCode = 200;
|
|
142
|
-
res.end();
|
|
143
|
-
return true;
|
|
144
|
-
}
|
|
154
|
+
res.setHeader("Content-Length", String(servedMetadata.byteLength));
|
|
145
155
|
res.statusCode = 200;
|
|
146
|
-
res.end(
|
|
156
|
+
res.end(body);
|
|
157
|
+
if (method === "HEAD") return true;
|
|
147
158
|
await store.delete(id);
|
|
148
159
|
return true;
|
|
149
160
|
}
|
|
@@ -163,31 +174,23 @@ function resolveZaloTimestampMs(date) {
|
|
|
163
174
|
if (!date) return;
|
|
164
175
|
return date >= UNIX_MILLISECONDS_THRESHOLD ? date : date * 1e3;
|
|
165
176
|
}
|
|
166
|
-
const loadZaloWebhookRuntime = createLazyRuntimeNamedExport(() => import("./monitor.webhook-
|
|
167
|
-
const loadZaloWebhookIngressRuntime = createLazyRuntimeNamedExport(() => import("./webhook-spool-
|
|
177
|
+
const loadZaloWebhookRuntime = createLazyRuntimeNamedExport(() => import("./monitor.webhook-S7nThy3R.js"), "zaloWebhookRuntime");
|
|
178
|
+
const loadZaloWebhookIngressRuntime = createLazyRuntimeNamedExport(() => import("./webhook-spool-Bf17xwqi.js"), "zaloWebhookIngressRuntime");
|
|
168
179
|
const loadZaloWebhookModule = createLazyRuntimeModule(async () => ({
|
|
169
180
|
...await loadZaloWebhookRuntime(),
|
|
170
181
|
...await loadZaloWebhookIngressRuntime()
|
|
171
182
|
}));
|
|
172
|
-
function releaseSharedHostedMediaRouteRef(routePath) {
|
|
173
|
-
const current = hostedMediaRouteRefs.get(routePath);
|
|
174
|
-
if (!current) return;
|
|
175
|
-
if (current.count > 1) {
|
|
176
|
-
current.count -= 1;
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
hostedMediaRouteRefs.delete(routePath);
|
|
180
|
-
for (const unregisterHandle of current.unregisters) unregisterHandle();
|
|
181
|
-
}
|
|
182
183
|
function registerSharedHostedMediaRoute(params) {
|
|
184
|
+
const routeKey = canonicalizeWebhookRouteKey(params.path);
|
|
183
185
|
const unregister = registerPluginHttpRoute({
|
|
184
186
|
auth: "plugin",
|
|
185
187
|
match: "prefix",
|
|
186
188
|
path: params.path,
|
|
187
189
|
pluginId: "zalo",
|
|
188
190
|
source: "zalo-hosted-media",
|
|
189
|
-
accountId: params.accountId,
|
|
190
191
|
log: params.log,
|
|
192
|
+
reuseExistingSameOwner: true,
|
|
193
|
+
throwOnFailure: true,
|
|
191
194
|
handler: async (req, res) => {
|
|
192
195
|
if (!await tryHandleHostedZaloMediaRequest(req, res) && !res.headersSent) {
|
|
193
196
|
res.statusCode = 404;
|
|
@@ -196,17 +199,23 @@ function registerSharedHostedMediaRoute(params) {
|
|
|
196
199
|
}
|
|
197
200
|
}
|
|
198
201
|
});
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
202
|
+
const acquired = hostedMediaRouteRefs.get(routeKey) ?? {
|
|
203
|
+
count: 0,
|
|
204
|
+
unregisters: []
|
|
205
|
+
};
|
|
206
|
+
if (acquired.count === 0) hostedMediaRouteRefs.set(routeKey, acquired);
|
|
207
|
+
acquired.count += 1;
|
|
208
|
+
acquired.unregisters.push(unregister);
|
|
209
|
+
let released = false;
|
|
210
|
+
return () => {
|
|
211
|
+
if (released) return;
|
|
212
|
+
released = true;
|
|
213
|
+
if (hostedMediaRouteRefs.get(routeKey) !== acquired) return;
|
|
214
|
+
acquired.count -= 1;
|
|
215
|
+
if (acquired.count > 0) return;
|
|
216
|
+
hostedMediaRouteRefs.delete(routeKey);
|
|
217
|
+
for (const unregisterHandle of acquired.unregisters) unregisterHandle();
|
|
218
|
+
};
|
|
210
219
|
}
|
|
211
220
|
function formatZaloError(error) {
|
|
212
221
|
if (error instanceof Error) return error.stack ?? `${error.name}: ${error.message}`;
|
|
@@ -253,6 +262,7 @@ function startPollingLoop(params) {
|
|
|
253
262
|
try {
|
|
254
263
|
const response = await getUpdates(token, { timeout: pollTimeout }, fetcher);
|
|
255
264
|
if (isStopped() || abortSignal.aborted) return;
|
|
265
|
+
if (response.ok) statusSink?.(channelReadyPatch());
|
|
256
266
|
if (response.ok && response.result) {
|
|
257
267
|
statusSink?.({ lastInboundAt: Date.now() });
|
|
258
268
|
await processUpdate({
|
|
@@ -262,7 +272,13 @@ function startPollingLoop(params) {
|
|
|
262
272
|
}
|
|
263
273
|
} catch (err) {
|
|
264
274
|
if (err instanceof ZaloApiError && err.isPollingTimeout) {} else if (!isStopped() && !abortSignal.aborted) {
|
|
265
|
-
|
|
275
|
+
const error = formatZaloError(err);
|
|
276
|
+
runtime.error?.(`[${account.accountId}] Zalo polling error: ${error}`);
|
|
277
|
+
statusSink?.({
|
|
278
|
+
connected: false,
|
|
279
|
+
lifecycle: "recovering",
|
|
280
|
+
lastError: error
|
|
281
|
+
});
|
|
266
282
|
await sleepWithAbort(5e3, abortSignal).catch(() => void 0);
|
|
267
283
|
}
|
|
268
284
|
}
|
|
@@ -385,7 +401,7 @@ async function authorizeZaloMessage(params) {
|
|
|
385
401
|
defaultGroupPolicy
|
|
386
402
|
});
|
|
387
403
|
const shouldComputeAuth = core.channel.commands.shouldComputeCommandAuthorized(rawBody, config);
|
|
388
|
-
const
|
|
404
|
+
const resolveChannelIngress = async (contextBinding) => await resolveStableChannelMessageIngress({
|
|
389
405
|
channelId: "zalo",
|
|
390
406
|
accountId: account.accountId,
|
|
391
407
|
identity: {
|
|
@@ -401,6 +417,7 @@ async function authorizeZaloMessage(params) {
|
|
|
401
417
|
kind: isGroup ? "group" : "direct",
|
|
402
418
|
id: chatId
|
|
403
419
|
},
|
|
420
|
+
contextBinding,
|
|
404
421
|
providerMissingFallbackApplied,
|
|
405
422
|
dmPolicy,
|
|
406
423
|
groupPolicy,
|
|
@@ -409,6 +426,7 @@ async function authorizeZaloMessage(params) {
|
|
|
409
426
|
groupAllowFrom: normalizeStringEntries(account.config.groupAllowFrom),
|
|
410
427
|
command: shouldComputeAuth ? {} : void 0
|
|
411
428
|
});
|
|
429
|
+
const access = await resolveChannelIngress();
|
|
412
430
|
const senderAccess = access.senderAccess;
|
|
413
431
|
if (isGroup) {
|
|
414
432
|
warnMissingProviderGroupPolicyFallbackOnce({
|
|
@@ -451,6 +469,8 @@ async function authorizeZaloMessage(params) {
|
|
|
451
469
|
return;
|
|
452
470
|
}
|
|
453
471
|
return {
|
|
472
|
+
channelIngress: access,
|
|
473
|
+
resolveChannelIngress,
|
|
454
474
|
chatId,
|
|
455
475
|
commandAuthorized: access.commandAccess.requested ? access.commandAccess.authorized : void 0,
|
|
456
476
|
isGroup,
|
|
@@ -468,7 +488,7 @@ async function processMessageWithPipeline(params) {
|
|
|
468
488
|
mediaType
|
|
469
489
|
});
|
|
470
490
|
if (!authorization) return;
|
|
471
|
-
const { isGroup, chatId, senderId, senderName, rawBody
|
|
491
|
+
const { isGroup, chatId, senderId, senderName, rawBody } = authorization;
|
|
472
492
|
const agentBody = agentBodyOverride ?? rawBody;
|
|
473
493
|
const { route, buildEnvelope } = resolveChannelInboundRouteEnvelope({
|
|
474
494
|
cfg: config,
|
|
@@ -479,6 +499,17 @@ async function processMessageWithPipeline(params) {
|
|
|
479
499
|
id: chatId
|
|
480
500
|
}
|
|
481
501
|
});
|
|
502
|
+
const channelIngress = await authorization.resolveChannelIngress({
|
|
503
|
+
agentId: route.agentId,
|
|
504
|
+
sessionKey: route.sessionKey,
|
|
505
|
+
messageId: message_id,
|
|
506
|
+
inboundEventKind: "user_request"
|
|
507
|
+
});
|
|
508
|
+
if (!channelIngress.senderAccess.allowed) {
|
|
509
|
+
logVerbose(core, runtime, `zalo: authorization changed before dispatch for ${senderId}`);
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
const commandAuthorized = channelIngress.commandAccess.requested ? channelIngress.commandAccess.authorized : void 0;
|
|
482
513
|
if (isGroup && core.channel.commands.isControlCommandMessage(rawBody, config) && commandAuthorized !== true) {
|
|
483
514
|
logVerbose(core, runtime, `zalo: drop control command from unauthorized sender ${senderId}`);
|
|
484
515
|
return;
|
|
@@ -492,6 +523,7 @@ async function processMessageWithPipeline(params) {
|
|
|
492
523
|
body: agentBody
|
|
493
524
|
});
|
|
494
525
|
const ctxPayload = core.channel.inbound.buildContext({
|
|
526
|
+
channelIngress,
|
|
495
527
|
channel: "zalo",
|
|
496
528
|
accountId: route.accountId,
|
|
497
529
|
messageId: message_id,
|
|
@@ -561,7 +593,6 @@ async function processMessageWithPipeline(params) {
|
|
|
561
593
|
payload,
|
|
562
594
|
token,
|
|
563
595
|
chatId,
|
|
564
|
-
runtime,
|
|
565
596
|
core,
|
|
566
597
|
config,
|
|
567
598
|
webhookUrl: params.webhookUrl,
|
|
@@ -575,8 +606,8 @@ async function processMessageWithPipeline(params) {
|
|
|
575
606
|
tableMode: "off"
|
|
576
607
|
});
|
|
577
608
|
},
|
|
578
|
-
onDelivered: () => {
|
|
579
|
-
statusSink?.({ lastOutboundAt: Date.now() });
|
|
609
|
+
onDelivered: (_payload, _info, result) => {
|
|
610
|
+
if (result?.visibleReplySent !== false) statusSink?.({ lastOutboundAt: Date.now() });
|
|
580
611
|
},
|
|
581
612
|
onError: (err, info) => {
|
|
582
613
|
runtime.error?.(`[${account.accountId}] Zalo ${info.kind} reply failed: ${String(err)}`);
|
|
@@ -606,49 +637,64 @@ async function processMessageWithPipeline(params) {
|
|
|
606
637
|
});
|
|
607
638
|
}
|
|
608
639
|
async function deliverZaloReply(params) {
|
|
609
|
-
const { payload, token, chatId,
|
|
640
|
+
const { payload, token, chatId, core, config, webhookUrl, webhookPath, proxyUrl, mediaMaxBytes, canHostMedia, accountId, statusSink, fetcher } = params;
|
|
610
641
|
const tableMode = params.tableMode ?? "code";
|
|
611
642
|
const reply = resolveSendableOutboundReplyParts(payload, { text: core.channel.text.convertMarkdownTables(payload.text ?? "", tableMode) });
|
|
612
643
|
const chunkMode = core.channel.text.resolveChunkMode(config, "zalo", accountId);
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
644
|
+
const acceptedMessageIds = [];
|
|
645
|
+
let visibleReplySent = false;
|
|
646
|
+
const recordAcceptedSend = (result) => {
|
|
647
|
+
const messageId = result.result?.message_id;
|
|
648
|
+
if (messageId) acceptedMessageIds.push(messageId);
|
|
649
|
+
visibleReplySent = true;
|
|
650
|
+
statusSink?.({ lastOutboundAt: Date.now() });
|
|
651
|
+
};
|
|
652
|
+
try {
|
|
653
|
+
await deliverTextOrMediaReply({
|
|
654
|
+
payload,
|
|
655
|
+
text: reply.text,
|
|
656
|
+
chunkText: (value) => core.channel.text.chunkMarkdownTextWithMode(value, ZALO_TEXT_LIMIT, chunkMode),
|
|
657
|
+
sendText: async (chunk) => {
|
|
658
|
+
recordAcceptedSend(await sendMessage(token, {
|
|
620
659
|
chat_id: chatId,
|
|
621
660
|
text: chunk
|
|
622
|
-
}, fetcher);
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
661
|
+
}, fetcher));
|
|
662
|
+
},
|
|
663
|
+
sendMedia: async ({ mediaUrl, caption }) => {
|
|
664
|
+
const sendableMediaUrl = canHostMedia && webhookUrl && webhookPath ? await prepareHostedZaloMediaUrl({
|
|
665
|
+
mediaUrl,
|
|
666
|
+
webhookUrl,
|
|
667
|
+
webhookPath,
|
|
668
|
+
maxBytes: mediaMaxBytes,
|
|
669
|
+
proxyUrl
|
|
670
|
+
}) : mediaUrl;
|
|
671
|
+
recordAcceptedSend(await sendPhoto(token, {
|
|
672
|
+
chat_id: chatId,
|
|
673
|
+
photo: sendableMediaUrl,
|
|
674
|
+
caption
|
|
675
|
+
}, fetcher));
|
|
626
676
|
}
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
},
|
|
643
|
-
onMediaError: (error) => {
|
|
644
|
-
runtime.error?.(`Zalo photo send failed: ${error instanceof Error ? error.message : JSON.stringify(error)}`);
|
|
645
|
-
}
|
|
646
|
-
});
|
|
677
|
+
});
|
|
678
|
+
} catch (error) {
|
|
679
|
+
if (!visibleReplySent) throw error;
|
|
680
|
+
throw createChannelPartialDeliveryError(error, {
|
|
681
|
+
messageIds: acceptedMessageIds,
|
|
682
|
+
receipt: createMessageReceiptFromOutboundResults({
|
|
683
|
+
results: acceptedMessageIds.map((messageId) => ({
|
|
684
|
+
channel: "zalo",
|
|
685
|
+
messageId
|
|
686
|
+
})),
|
|
687
|
+
kind: reply.hasMedia ? "media" : "text"
|
|
688
|
+
}),
|
|
689
|
+
visibleReplySent: true
|
|
690
|
+
});
|
|
691
|
+
}
|
|
647
692
|
}
|
|
648
693
|
async function monitorZaloProvider(options) {
|
|
649
694
|
const { token, account, config, runtime, abortSignal, useWebhook, webhookUrl, webhookSecret, webhookPath, statusSink, fetcher: fetcherOverride } = options;
|
|
650
695
|
const core = getZaloRuntime();
|
|
651
|
-
const
|
|
696
|
+
const configuredMediaMaxMb = account.config.mediaMaxMb;
|
|
697
|
+
const effectiveMediaMaxMb = typeof configuredMediaMaxMb === "number" && configuredMediaMaxMb > 0 ? configuredMediaMaxMb : DEFAULT_MEDIA_MAX_MB;
|
|
652
698
|
const fetcher = fetcherOverride ?? resolveZaloProxyFetch(account.config.proxy);
|
|
653
699
|
const mode = useWebhook ? "webhook" : "polling";
|
|
654
700
|
const effectiveWebhookUrl = normalizeWebhookUrl(webhookUrl ?? account.config.webhookUrl);
|
|
@@ -674,13 +720,16 @@ async function monitorZaloProvider(options) {
|
|
|
674
720
|
const stopOnAbort = () => {
|
|
675
721
|
if (!useWebhook) stop();
|
|
676
722
|
};
|
|
723
|
+
if (abortSignal.aborted) {
|
|
724
|
+
stopOnAbort();
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
677
727
|
abortSignal.addEventListener("abort", stopOnAbort, { once: true });
|
|
678
728
|
runtime.log?.(`[${account.accountId}] Zalo provider init mode=${mode} mediaMaxMb=${String(effectiveMediaMaxMb)}`);
|
|
679
729
|
try {
|
|
680
730
|
if (hostedMediaRoutePath) {
|
|
681
731
|
const unregisterHostedMediaRoute = registerSharedHostedMediaRoute({
|
|
682
732
|
path: hostedMediaRoutePath,
|
|
683
|
-
accountId: account.accountId,
|
|
684
733
|
log: runtime.log
|
|
685
734
|
});
|
|
686
735
|
stopHandlers.push(unregisterHostedMediaRoute);
|
|
@@ -731,6 +780,7 @@ async function monitorZaloProvider(options) {
|
|
|
731
780
|
source: "zalo-webhook",
|
|
732
781
|
accountId: account.accountId,
|
|
733
782
|
log: runtime.log,
|
|
783
|
+
throwOnFailure: true,
|
|
734
784
|
handler: async (req, res) => {
|
|
735
785
|
if (!await handleZaloWebhookRequest(req, res) && !res.headersSent) {
|
|
736
786
|
res.statusCode = 404;
|
|
@@ -744,6 +794,7 @@ async function monitorZaloProvider(options) {
|
|
|
744
794
|
url: effectiveWebhookUrl,
|
|
745
795
|
secret_token: webhookSecret
|
|
746
796
|
}, fetcher);
|
|
797
|
+
statusSink?.(channelReadyPatch());
|
|
747
798
|
let webhookCleanupPromise;
|
|
748
799
|
cleanupWebhook = async () => {
|
|
749
800
|
if (!webhookCleanupPromise) webhookCleanupPromise = (async () => {
|
|
@@ -796,7 +847,13 @@ async function monitorZaloProvider(options) {
|
|
|
796
847
|
});
|
|
797
848
|
await waitForAbortSignal(abortSignal);
|
|
798
849
|
} catch (err) {
|
|
799
|
-
|
|
850
|
+
const error = formatZaloError(err);
|
|
851
|
+
runtime.error?.(`[${account.accountId}] Zalo provider startup failed mode=${mode}: ${error}`);
|
|
852
|
+
statusSink?.({
|
|
853
|
+
connected: false,
|
|
854
|
+
lifecycle: "recovering",
|
|
855
|
+
lastError: error
|
|
856
|
+
});
|
|
800
857
|
throw err;
|
|
801
858
|
} finally {
|
|
802
859
|
abortSignal.removeEventListener("abort", stopOnAbort);
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { B as registerWebhookTargetWithPluginRoute, G as resolveWebhookTargetWithAuthOrRejectSync, V as resolveClientIp, Z as withResolvedWebhookRequestPipeline, a as WEBHOOK_ANOMALY_COUNTER_DEFAULTS, b as createFixedWindowRateLimiter, l as applyBasicWebhookRequestGuards, o as WEBHOOK_RATE_LIMIT_DEFAULTS, x as createWebhookAnomalyTracker, z as registerWebhookTarget } from "./runtime-api-qxdOyk3M.js";
|
|
2
|
-
import { ZaloWebhookPayloadError } from "./webhook-spool-
|
|
2
|
+
import { ZaloWebhookPayloadError } from "./webhook-spool-Bf17xwqi.js";
|
|
3
3
|
import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
|
|
4
4
|
import { readWebhookBodyOrReject } from "openclaw/plugin-sdk/webhook-request-guards";
|
|
5
5
|
//#region extensions/zalo/src/monitor.webhook.ts
|
|
6
|
+
const ZALO_WEBHOOK_ACCEPTED_HEADER = "x-openclaw-delivery-accepted";
|
|
7
|
+
const ZALO_WEBHOOK_ACCEPTED_VALUE = "durable";
|
|
6
8
|
const webhookTargets = /* @__PURE__ */ new Map();
|
|
7
9
|
const webhookRateLimiter = createFixedWindowRateLimiter({
|
|
8
10
|
windowMs: WEBHOOK_RATE_LIMIT_DEFAULTS.windowMs,
|
|
@@ -18,9 +20,6 @@ function clearZaloWebhookSecurityStateForTest() {
|
|
|
18
20
|
webhookRateLimiter.clear();
|
|
19
21
|
webhookAnomalyTracker.clear();
|
|
20
22
|
}
|
|
21
|
-
function getZaloWebhookRateLimitStateSizeForTest() {
|
|
22
|
-
return webhookRateLimiter.size();
|
|
23
|
-
}
|
|
24
23
|
function getZaloWebhookStatusCounterSizeForTest() {
|
|
25
24
|
return webhookAnomalyTracker.size();
|
|
26
25
|
}
|
|
@@ -108,6 +107,7 @@ async function handleZaloWebhookRequest(req, res) {
|
|
|
108
107
|
target.runtime.error?.(`[${target.account.accountId}] Zalo webhook admission failed: ${String(error)}`);
|
|
109
108
|
return true;
|
|
110
109
|
}
|
|
110
|
+
res.setHeader(ZALO_WEBHOOK_ACCEPTED_HEADER, ZALO_WEBHOOK_ACCEPTED_VALUE);
|
|
111
111
|
res.statusCode = 200;
|
|
112
112
|
res.end("ok");
|
|
113
113
|
return true;
|
|
@@ -116,7 +116,6 @@ async function handleZaloWebhookRequest(req, res) {
|
|
|
116
116
|
}
|
|
117
117
|
const zaloWebhookRuntime = {
|
|
118
118
|
clearZaloWebhookSecurityStateForTest,
|
|
119
|
-
getZaloWebhookRateLimitStateSizeForTest,
|
|
120
119
|
getZaloWebhookStatusCounterSizeForTest,
|
|
121
120
|
handleZaloWebhookRequest,
|
|
122
121
|
registerZaloWebhookTarget
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { d as resolveZaloAccount, f as resolveZaloToken } from "./setup-core-
|
|
1
|
+
import { d as resolveZaloAccount, f as resolveZaloToken } from "./setup-core-T6qc2eAG.js";
|
|
2
2
|
import { createMessageReceiptFromOutboundResults } from "openclaw/plugin-sdk/channel-outbound";
|
|
3
|
+
import { stripChannelTargetPrefix, stripTargetKindPrefix } from "openclaw/plugin-sdk/core";
|
|
3
4
|
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
4
5
|
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
5
6
|
import { resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
|
|
@@ -231,7 +232,7 @@ function resolveValidatedSendContext(chatId, options) {
|
|
|
231
232
|
ok: false,
|
|
232
233
|
error: "No Zalo bot token configured"
|
|
233
234
|
};
|
|
234
|
-
const trimmedChatId = chatId
|
|
235
|
+
const trimmedChatId = normalizeZaloSendChatId(chatId);
|
|
235
236
|
if (!trimmedChatId) return {
|
|
236
237
|
ok: false,
|
|
237
238
|
error: "No chat_id provided"
|
|
@@ -243,6 +244,9 @@ function resolveValidatedSendContext(chatId, options) {
|
|
|
243
244
|
fetcher
|
|
244
245
|
};
|
|
245
246
|
}
|
|
247
|
+
function normalizeZaloSendChatId(chatId) {
|
|
248
|
+
return stripTargetKindPrefix(stripChannelTargetPrefix(chatId, "zalo", "zl"));
|
|
249
|
+
}
|
|
246
250
|
function resolveSendContextOrFailure(chatId, options) {
|
|
247
251
|
const context = resolveValidatedSendContext(chatId, options);
|
|
248
252
|
return context.ok ? { context } : { failure: {
|
package/dist/setup-api.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as zaloDmPolicy, r as zaloSetupAdapter, t as createZaloSetupWizardProxy } from "./setup-core-
|
|
1
|
+
import { n as zaloDmPolicy, r as zaloSetupAdapter, t as createZaloSetupWizardProxy } from "./setup-core-T6qc2eAG.js";
|
|
2
2
|
import { n as resolveZaloRuntimeGroupPolicy } from "./group-access-8qHRzDHx.js";
|
|
3
3
|
import { loadBundledEntryExportSync } from "openclaw/plugin-sdk/channel-entry-contract";
|
|
4
4
|
//#region extensions/zalo/setup-api.ts
|
|
@@ -23,7 +23,7 @@ function createLazyObjectValue(load) {
|
|
|
23
23
|
});
|
|
24
24
|
}
|
|
25
25
|
function loadSetupSurfaceModule() {
|
|
26
|
-
return loadBundledEntryExportSync(import.meta.url, { specifier: "./
|
|
26
|
+
return loadBundledEntryExportSync(import.meta.url, { specifier: "./setup-surface.js" });
|
|
27
27
|
}
|
|
28
28
|
const zaloSetupWizard = createLazyObjectValue(() => loadSetupSurfaceModule().zaloSetupWizard);
|
|
29
29
|
//#endregion
|
|
@@ -3,10 +3,10 @@ import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/acco
|
|
|
3
3
|
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
4
4
|
import { tryReadSecretFileSync } from "openclaw/plugin-sdk/core";
|
|
5
5
|
import { resolveAccountEntry } from "openclaw/plugin-sdk/routing";
|
|
6
|
-
import { buildSecretInputSchema,
|
|
6
|
+
import { buildSecretInputSchema, normalizeSecretInputString, resolveSecretInputString } from "openclaw/plugin-sdk/secret-input";
|
|
7
7
|
import { createChannelDmPolicy } from "openclaw/plugin-sdk/channel-dm-policy";
|
|
8
8
|
import { defineChannelSetupContract } from "openclaw/plugin-sdk/channel-setup";
|
|
9
|
-
import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, createDelegatedSetupWizardProxy, createPatchedAccountSetupAdapter, createSetupInputPresenceValidator, createSetupTranslator, formatDocsLink, mergeAllowFromEntries, normalizeAccountId as normalizeAccountId$1 } from "openclaw/plugin-sdk/setup";
|
|
9
|
+
import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, createDelegatedSetupWizardProxy, createPatchedAccountSetupAdapter, createSetupInputPresenceValidator, createSetupTranslator, formatDocsLink, mergeAllowFromEntries, normalizeAccountId as normalizeAccountId$1, patchScopedAccountConfig, setSetupChannelEnabled } from "openclaw/plugin-sdk/setup";
|
|
10
10
|
//#region \0rolldown/runtime.js
|
|
11
11
|
var __defProp = Object.defineProperty;
|
|
12
12
|
var __exportAll = (all, no_symbols) => {
|
|
@@ -30,58 +30,77 @@ function resolveZaloToken(config, accountId, options) {
|
|
|
30
30
|
const accountConfig = resolveAccountEntry(baseConfig?.accounts, normalizeAccountId(resolvedAccountId));
|
|
31
31
|
const accountHasBotToken = Boolean(accountConfig && Object.hasOwn(accountConfig, "botToken"));
|
|
32
32
|
if (accountConfig && accountHasBotToken) {
|
|
33
|
-
const token =
|
|
33
|
+
const token = resolveSecretInputString({
|
|
34
34
|
value: accountConfig.botToken,
|
|
35
|
-
path: `channels.zalo.accounts.${resolvedAccountId}.botToken
|
|
35
|
+
path: `channels.zalo.accounts.${resolvedAccountId}.botToken`,
|
|
36
|
+
mode: options?.mode
|
|
36
37
|
});
|
|
37
|
-
if (token) return {
|
|
38
|
-
token,
|
|
39
|
-
source: "config"
|
|
38
|
+
if (token.status === "available") return {
|
|
39
|
+
token: token.value,
|
|
40
|
+
source: "config",
|
|
41
|
+
status: "available"
|
|
42
|
+
};
|
|
43
|
+
if (token.status === "configured_unavailable") return {
|
|
44
|
+
token: "",
|
|
45
|
+
source: "config",
|
|
46
|
+
status: "configured_unavailable"
|
|
40
47
|
};
|
|
41
48
|
const fileToken = readTokenFromFile(accountConfig.tokenFile);
|
|
42
49
|
if (fileToken) return {
|
|
43
50
|
token: fileToken,
|
|
44
|
-
source: "configFile"
|
|
51
|
+
source: "configFile",
|
|
52
|
+
status: "available"
|
|
45
53
|
};
|
|
46
54
|
}
|
|
47
55
|
if (!accountHasBotToken) {
|
|
48
56
|
const fileToken = readTokenFromFile(accountConfig?.tokenFile);
|
|
49
57
|
if (fileToken) return {
|
|
50
58
|
token: fileToken,
|
|
51
|
-
source: "configFile"
|
|
59
|
+
source: "configFile",
|
|
60
|
+
status: "available"
|
|
52
61
|
};
|
|
53
62
|
}
|
|
54
63
|
if (!accountHasBotToken) {
|
|
55
|
-
const token =
|
|
64
|
+
const token = resolveSecretInputString({
|
|
56
65
|
value: baseConfig?.botToken,
|
|
57
|
-
path: "channels.zalo.botToken"
|
|
66
|
+
path: "channels.zalo.botToken",
|
|
67
|
+
mode: options?.mode
|
|
58
68
|
});
|
|
59
|
-
if (token) return {
|
|
60
|
-
token,
|
|
61
|
-
source: "config"
|
|
69
|
+
if (token.status === "available") return {
|
|
70
|
+
token: token.value,
|
|
71
|
+
source: "config",
|
|
72
|
+
status: "available"
|
|
73
|
+
};
|
|
74
|
+
if (token.status === "configured_unavailable") return {
|
|
75
|
+
token: "",
|
|
76
|
+
source: "config",
|
|
77
|
+
status: "configured_unavailable"
|
|
62
78
|
};
|
|
63
79
|
const fileToken = readTokenFromFile(baseConfig?.tokenFile);
|
|
64
80
|
if (fileToken) return {
|
|
65
81
|
token: fileToken,
|
|
66
|
-
source: "configFile"
|
|
82
|
+
source: "configFile",
|
|
83
|
+
status: "available"
|
|
67
84
|
};
|
|
68
85
|
}
|
|
69
86
|
if (isDefaultAccount) {
|
|
70
87
|
const envToken = process.env.ZALO_BOT_TOKEN?.trim();
|
|
71
88
|
if (envToken) return {
|
|
72
89
|
token: envToken,
|
|
73
|
-
source: "env"
|
|
90
|
+
source: "env",
|
|
91
|
+
status: "available"
|
|
74
92
|
};
|
|
75
93
|
}
|
|
76
94
|
return {
|
|
77
95
|
token: "",
|
|
78
|
-
source: "none"
|
|
96
|
+
source: "none",
|
|
97
|
+
status: "missing"
|
|
79
98
|
};
|
|
80
99
|
}
|
|
81
100
|
//#endregion
|
|
82
101
|
//#region extensions/zalo/src/accounts.ts
|
|
83
102
|
var accounts_exports = /* @__PURE__ */ __exportAll({
|
|
84
|
-
|
|
103
|
+
inspectZaloAccount: () => inspectZaloAccount,
|
|
85
104
|
listZaloAccountIds: () => listZaloAccountIds,
|
|
86
105
|
resolveDefaultZaloAccountId: () => resolveDefaultZaloAccountId,
|
|
87
106
|
resolveZaloAccount: () => resolveZaloAccount
|
|
@@ -93,27 +112,34 @@ const { listAccountIds: listZaloAccountIds, resolveDefaultAccountId: resolveDefa
|
|
|
93
112
|
envVars: ["ZALO_BOT_TOKEN"]
|
|
94
113
|
}
|
|
95
114
|
});
|
|
96
|
-
function
|
|
115
|
+
function resolveZaloAccountWithMode(params) {
|
|
97
116
|
const accountId = normalizeAccountId(params.accountId ?? (params.cfg.channels?.zalo)?.defaultAccount);
|
|
98
117
|
const baseEnabled = (params.cfg.channels?.zalo)?.enabled !== false;
|
|
99
118
|
const merged = mergeZaloAccountConfig(params.cfg, accountId);
|
|
100
119
|
const accountEnabled = merged.enabled !== false;
|
|
101
120
|
const enabled = baseEnabled && accountEnabled;
|
|
102
|
-
const tokenResolution = resolveZaloToken(params.cfg.channels?.zalo, accountId, {
|
|
121
|
+
const tokenResolution = resolveZaloToken(params.cfg.channels?.zalo, accountId, { mode: params.mode });
|
|
103
122
|
return {
|
|
104
123
|
accountId,
|
|
105
124
|
name: normalizeOptionalString(merged.name),
|
|
106
125
|
enabled,
|
|
107
126
|
token: tokenResolution.token,
|
|
108
127
|
tokenSource: tokenResolution.source,
|
|
128
|
+
tokenStatus: tokenResolution.status,
|
|
109
129
|
config: merged
|
|
110
130
|
};
|
|
111
131
|
}
|
|
112
|
-
function
|
|
113
|
-
return
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
})
|
|
132
|
+
function resolveZaloAccount(params) {
|
|
133
|
+
return resolveZaloAccountWithMode({
|
|
134
|
+
...params,
|
|
135
|
+
mode: "strict"
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
function inspectZaloAccount(params) {
|
|
139
|
+
return resolveZaloAccountWithMode({
|
|
140
|
+
...params,
|
|
141
|
+
mode: "inspect"
|
|
142
|
+
});
|
|
117
143
|
}
|
|
118
144
|
//#endregion
|
|
119
145
|
//#region extensions/zalo/src/setup-allow-from.ts
|
|
@@ -218,7 +244,8 @@ const zaloSetupContract = defineChannelSetupContract({
|
|
|
218
244
|
cli: {
|
|
219
245
|
flags: "--use-env",
|
|
220
246
|
description: "Use ZALO_BOT_TOKEN"
|
|
221
|
-
}
|
|
247
|
+
},
|
|
248
|
+
envVars: ["ZALO_BOT_TOKEN"]
|
|
222
249
|
}
|
|
223
250
|
},
|
|
224
251
|
legacyAdapter: zaloSetupAdapter
|
|
@@ -232,38 +259,12 @@ const zaloDmPolicy = createChannelDmPolicy({
|
|
|
232
259
|
accountId: accountId && normalizeAccountId$1(accountId) ? normalizeAccountId$1(accountId) ?? DEFAULT_ACCOUNT_ID$1 : resolveDefaultZaloAccountId(cfg)
|
|
233
260
|
});
|
|
234
261
|
},
|
|
235
|
-
applyPatch: ({ cfg, account, patch }) => {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
...cfg.channels?.zalo,
|
|
242
|
-
enabled: true,
|
|
243
|
-
...patch
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
};
|
|
247
|
-
const currentAccount = cfg.channels?.zalo?.accounts?.[account.accountId];
|
|
248
|
-
return {
|
|
249
|
-
...cfg,
|
|
250
|
-
channels: {
|
|
251
|
-
...cfg.channels,
|
|
252
|
-
zalo: {
|
|
253
|
-
...cfg.channels?.zalo,
|
|
254
|
-
enabled: true,
|
|
255
|
-
accounts: {
|
|
256
|
-
...cfg.channels?.zalo?.accounts,
|
|
257
|
-
[account.accountId]: {
|
|
258
|
-
...currentAccount,
|
|
259
|
-
enabled: currentAccount?.enabled ?? true,
|
|
260
|
-
...patch
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
};
|
|
266
|
-
},
|
|
262
|
+
applyPatch: ({ cfg, account, patch }) => patchScopedAccountConfig({
|
|
263
|
+
cfg,
|
|
264
|
+
channelKey: channel,
|
|
265
|
+
accountId: account.accountId,
|
|
266
|
+
patch
|
|
267
|
+
}),
|
|
267
268
|
promptAllowFrom: async ({ cfg, prompter, accountId }) => promptZaloAllowFrom({
|
|
268
269
|
cfg,
|
|
269
270
|
prompter,
|
|
@@ -285,17 +286,8 @@ function createZaloSetupWizardProxy(loadWizard) {
|
|
|
285
286
|
credentials: [],
|
|
286
287
|
delegateFinalize: true,
|
|
287
288
|
dmPolicy: zaloDmPolicy,
|
|
288
|
-
disable: (cfg) => (
|
|
289
|
-
...cfg,
|
|
290
|
-
channels: {
|
|
291
|
-
...cfg.channels,
|
|
292
|
-
zalo: {
|
|
293
|
-
...cfg.channels?.zalo,
|
|
294
|
-
enabled: false
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
})
|
|
289
|
+
disable: (cfg) => setSetupChannelEnabled(cfg, channel, false)
|
|
298
290
|
});
|
|
299
291
|
}
|
|
300
292
|
//#endregion
|
|
301
|
-
export { noteZaloTokenHelp as a,
|
|
293
|
+
export { noteZaloTokenHelp as a, inspectZaloAccount as c, resolveZaloAccount as d, resolveZaloToken as f, __exportAll as h, zaloSetupContract as i, listZaloAccountIds as l, normalizeSecretInputString as m, zaloDmPolicy as n, promptZaloAllowFrom as o, buildSecretInputSchema as p, zaloSetupAdapter as r, accounts_exports as s, createZaloSetupWizardProxy as t, resolveDefaultZaloAccountId as u };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { a as noteZaloTokenHelp,
|
|
1
|
+
import { a as noteZaloTokenHelp, c as inspectZaloAccount, h as __exportAll, n as zaloDmPolicy, o as promptZaloAllowFrom } from "./setup-core-T6qc2eAG.js";
|
|
2
2
|
import { DEFAULT_ACCOUNT_ID, buildSingleChannelSecretPromptState, createSetupTranslator, createStandardChannelSetupStatus, hasConfiguredSecretInput, promptSingleChannelSecretInput, runSingleChannelSecretStep } from "openclaw/plugin-sdk/setup";
|
|
3
3
|
//#region extensions/zalo/src/setup-surface.ts
|
|
4
|
+
var setup_surface_exports = /* @__PURE__ */ __exportAll({ zaloSetupWizard: () => zaloSetupWizard });
|
|
4
5
|
const t = createSetupTranslator();
|
|
5
6
|
const channel = "zalo";
|
|
6
7
|
function setZaloUpdateMode(cfg, accountId, mode, webhookUrl, webhookSecret, webhookPath) {
|
|
@@ -72,10 +73,9 @@ const zaloSetupWizard = {
|
|
|
72
73
|
unconfiguredScore: 10,
|
|
73
74
|
includeStatusLine: true,
|
|
74
75
|
resolveConfigured: ({ cfg, accountId }) => {
|
|
75
|
-
const account =
|
|
76
|
+
const account = inspectZaloAccount({
|
|
76
77
|
cfg,
|
|
77
|
-
accountId
|
|
78
|
-
allowUnresolvedSecretRef: true
|
|
78
|
+
accountId
|
|
79
79
|
});
|
|
80
80
|
return Boolean(account.token) || hasConfiguredSecretInput(account.config.botToken) || Boolean(account.config.tokenFile?.trim());
|
|
81
81
|
}
|
|
@@ -83,10 +83,9 @@ const zaloSetupWizard = {
|
|
|
83
83
|
credentials: [],
|
|
84
84
|
finalize: async ({ cfg, accountId, forceAllowFrom, options, prompter }) => {
|
|
85
85
|
let next = cfg;
|
|
86
|
-
const resolvedAccount =
|
|
86
|
+
const resolvedAccount = inspectZaloAccount({
|
|
87
87
|
cfg: next,
|
|
88
|
-
accountId
|
|
89
|
-
allowUnresolvedSecretRef: true
|
|
88
|
+
accountId
|
|
90
89
|
});
|
|
91
90
|
const accountConfigured = Boolean(resolvedAccount.token);
|
|
92
91
|
const allowEnv = accountId === DEFAULT_ACCOUNT_ID;
|
|
@@ -213,4 +212,4 @@ const zaloSetupWizard = {
|
|
|
213
212
|
dmPolicy: zaloDmPolicy
|
|
214
213
|
};
|
|
215
214
|
//#endregion
|
|
216
|
-
export { zaloSetupWizard };
|
|
215
|
+
export { zaloSetupWizard as n, setup_surface_exports as t };
|
|
@@ -1,28 +1,15 @@
|
|
|
1
1
|
import { t as getZaloRuntime } from "./runtime-api-qxdOyk3M.js";
|
|
2
|
-
import { r as ZaloApiError } from "./send-
|
|
3
|
-
import { DEFAULT_INGRESS_ADOPTION_STALL_MS, DEFAULT_INGRESS_RETRY_MAX_ATTEMPTS, bindIngressLifecycleToReplyOptions, createChannelIngressMonitor } from "openclaw/plugin-sdk/channel-outbound";
|
|
2
|
+
import { r as ZaloApiError } from "./send-BGGtv8x_.js";
|
|
3
|
+
import { DEFAULT_INGRESS_ADOPTION_STALL_MS, DEFAULT_INGRESS_RETRY_MAX_ATTEMPTS, bindIngressLifecycleToReplyOptions, createChannelIngressError, createChannelIngressMonitor } from "openclaw/plugin-sdk/channel-outbound";
|
|
4
|
+
import { normalizeNullableString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
5
|
+
import { isRecord } from "openclaw/plugin-sdk/channel-secret-basic-runtime";
|
|
6
|
+
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
4
7
|
import { runDetachedWebhookWork } from "openclaw/plugin-sdk/webhook-request-guards";
|
|
5
8
|
//#region extensions/zalo/src/webhook-spool.ts
|
|
6
9
|
const ZALO_WEBHOOK_SPOOL_VERSION = 1;
|
|
7
10
|
const ZALO_WEBHOOK_DRAIN_INTERVAL_MS = 500;
|
|
8
11
|
const ZALO_WEBHOOK_MAX_CONCURRENT_DELIVERIES = 8;
|
|
9
|
-
const
|
|
10
|
-
const ZALO_WEBHOOK_COMPLETED_TTL_MS = 720 * 60 * 6e4;
|
|
11
|
-
const ZALO_WEBHOOK_COMPLETED_MAX_ENTRIES = 2e4;
|
|
12
|
-
const ZALO_WEBHOOK_FAILED_TTL_MS = 720 * 60 * 6e4;
|
|
13
|
-
const ZALO_WEBHOOK_FAILED_MAX_ENTRIES = 5e3;
|
|
14
|
-
var ZaloWebhookPayloadError = class extends Error {
|
|
15
|
-
constructor(message, options) {
|
|
16
|
-
super(message, options);
|
|
17
|
-
this.name = "ZaloWebhookPayloadError";
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
function isRecord(value) {
|
|
21
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
22
|
-
}
|
|
23
|
-
function nonEmptyString(value) {
|
|
24
|
-
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
25
|
-
}
|
|
12
|
+
const ZaloWebhookPayloadError = createChannelIngressError("ZaloWebhookPayloadError");
|
|
26
13
|
function parseRawRecord(rawEvent) {
|
|
27
14
|
let parsed;
|
|
28
15
|
try {
|
|
@@ -40,9 +27,9 @@ function resolveUpdateRecord(envelope) {
|
|
|
40
27
|
function inspectZaloWebhookEvent(rawEvent) {
|
|
41
28
|
const update = resolveUpdateRecord(parseRawRecord(rawEvent));
|
|
42
29
|
const message = isRecord(update.message) ? update.message : null;
|
|
43
|
-
const eventId =
|
|
30
|
+
const eventId = normalizeNullableString(message?.message_id);
|
|
44
31
|
if (!eventId) throw new ZaloWebhookPayloadError("Zalo webhook message is missing message.message_id.");
|
|
45
|
-
const chatId =
|
|
32
|
+
const chatId = normalizeNullableString((isRecord(message?.chat) ? message.chat : null)?.id);
|
|
46
33
|
if (!chatId) throw new ZaloWebhookPayloadError("Zalo webhook message is missing message.chat.id.");
|
|
47
34
|
return {
|
|
48
35
|
eventId,
|
|
@@ -54,20 +41,17 @@ function parseClaimedUpdate(payload, claimedId) {
|
|
|
54
41
|
if (payload.version !== ZALO_WEBHOOK_SPOOL_VERSION || typeof payload.rawEvent !== "string") throw new ZaloWebhookPayloadError("Zalo webhook spool payload is invalid.");
|
|
55
42
|
const facts = inspectZaloWebhookEvent(payload.rawEvent);
|
|
56
43
|
if (facts.eventId !== claimedId) throw new ZaloWebhookPayloadError("Zalo webhook message id changed after durable admission.");
|
|
57
|
-
const eventName =
|
|
44
|
+
const eventName = normalizeNullableString(facts.update.event_name);
|
|
58
45
|
if (eventName !== "message.text.received" && eventName !== "message.image.received" && eventName !== "message.sticker.received" && eventName !== "message.unsupported.received") throw new ZaloWebhookPayloadError("Zalo webhook event_name is unsupported.");
|
|
59
46
|
const message = facts.update.message;
|
|
60
47
|
const from = isRecord(message.from) ? message.from : null;
|
|
61
48
|
const chat = isRecord(message.chat) ? message.chat : null;
|
|
62
|
-
if (!
|
|
49
|
+
if (!normalizeNullableString(from?.id)) throw new ZaloWebhookPayloadError("Zalo webhook message is missing message.from.id.");
|
|
63
50
|
if (chat?.chat_type !== "PRIVATE" && chat?.chat_type !== "GROUP") throw new ZaloWebhookPayloadError("Zalo webhook message has an invalid chat type.");
|
|
64
51
|
if (typeof message.date !== "number" || !Number.isFinite(message.date)) throw new ZaloWebhookPayloadError("Zalo webhook message has an invalid date.");
|
|
65
52
|
if (eventName === "message.text.received" && typeof message.text !== "string") throw new ZaloWebhookPayloadError("Zalo text event is missing message.text.");
|
|
66
53
|
return facts.update;
|
|
67
54
|
}
|
|
68
|
-
function errorText(error) {
|
|
69
|
-
return error instanceof Error ? error.message : String(error);
|
|
70
|
-
}
|
|
71
55
|
function isZaloAuthenticationFailure(error) {
|
|
72
56
|
let current = error;
|
|
73
57
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -80,10 +64,8 @@ function isZaloAuthenticationFailure(error) {
|
|
|
80
64
|
return false;
|
|
81
65
|
}
|
|
82
66
|
function createZaloWebhookIngress(options) {
|
|
83
|
-
const queue = options.queue ?? getZaloRuntime().state.openChannelIngressQueue({ accountId: options.accountId });
|
|
84
|
-
const deferredClaims = /* @__PURE__ */ new Map();
|
|
85
67
|
const monitor = createChannelIngressMonitor({
|
|
86
|
-
queue,
|
|
68
|
+
queue: options.queue ?? getZaloRuntime().state.openChannelIngressQueue({ accountId: options.accountId }),
|
|
87
69
|
inspect: (rawEvent) => inspectZaloWebhookEvent(rawEvent),
|
|
88
70
|
payload: {
|
|
89
71
|
storage: "raw-event",
|
|
@@ -94,47 +76,13 @@ function createZaloWebhookIngress(options) {
|
|
|
94
76
|
},
|
|
95
77
|
deliver: async (_rawEvent, lifecycle, claim) => {
|
|
96
78
|
const update = parseClaimedUpdate(claim.payload, claim.id);
|
|
97
|
-
|
|
98
|
-
let resolveDeferredClaim;
|
|
99
|
-
const deferredClaim = new Promise((resolve) => {
|
|
100
|
-
resolveDeferredClaim = resolve;
|
|
101
|
-
});
|
|
102
|
-
let deferredClaimSettled = false;
|
|
103
|
-
const settleDeferredClaim = () => {
|
|
104
|
-
if (deferredClaimSettled) return;
|
|
105
|
-
deferredClaimSettled = true;
|
|
106
|
-
if (deferredClaims.get(claim.id) === deferredClaim) deferredClaims.delete(claim.id);
|
|
107
|
-
resolveDeferredClaim();
|
|
108
|
-
};
|
|
109
|
-
await options.deliver(update, {
|
|
110
|
-
...boundLifecycle,
|
|
111
|
-
onAdopted: async () => {
|
|
112
|
-
try {
|
|
113
|
-
await boundLifecycle.onAdopted();
|
|
114
|
-
} finally {
|
|
115
|
-
settleDeferredClaim();
|
|
116
|
-
}
|
|
117
|
-
},
|
|
118
|
-
onDeferred: () => {
|
|
119
|
-
if (!deferredClaimSettled) deferredClaims.set(claim.id, deferredClaim);
|
|
120
|
-
boundLifecycle.onDeferred();
|
|
121
|
-
},
|
|
122
|
-
onAbandoned: () => {
|
|
123
|
-
Promise.resolve(boundLifecycle.onAbandoned()).finally(settleDeferredClaim);
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
return deferredClaims.has(claim.id) ? { kind: "deferred" } : { kind: "completed" };
|
|
79
|
+
await options.deliver(update, bindIngressLifecycleToReplyOptions(lifecycle).turnAdoptionLifecycle);
|
|
127
80
|
},
|
|
128
81
|
pollIntervalMs: ZALO_WEBHOOK_DRAIN_INTERVAL_MS,
|
|
129
|
-
retention: {
|
|
130
|
-
pruneIntervalMs: ZALO_WEBHOOK_PRUNE_INTERVAL_MS,
|
|
131
|
-
completedTtlMs: ZALO_WEBHOOK_COMPLETED_TTL_MS,
|
|
132
|
-
completedMaxEntries: ZALO_WEBHOOK_COMPLETED_MAX_ENTRIES,
|
|
133
|
-
failedTtlMs: ZALO_WEBHOOK_FAILED_TTL_MS,
|
|
134
|
-
failedMaxEntries: ZALO_WEBHOOK_FAILED_MAX_ENTRIES
|
|
135
|
-
},
|
|
82
|
+
retention: { failedMaxEntries: 5e3 },
|
|
136
83
|
waitForDeliveryIdleBeforeRepump: false,
|
|
137
84
|
runPumpTask: runDetachedWebhookWork,
|
|
85
|
+
deferredClaims: "wait-on-stop",
|
|
138
86
|
drain: {
|
|
139
87
|
adoptionStallTimeoutMs: DEFAULT_INGRESS_ADOPTION_STALL_MS,
|
|
140
88
|
startLimit: ZALO_WEBHOOK_MAX_CONCURRENT_DELIVERIES,
|
|
@@ -149,24 +97,21 @@ function createZaloWebhookIngress(options) {
|
|
|
149
97
|
};
|
|
150
98
|
if (isZaloAuthenticationFailure(error)) return {
|
|
151
99
|
reason: "authentication-failed",
|
|
152
|
-
message:
|
|
100
|
+
message: formatErrorMessage(error)
|
|
153
101
|
};
|
|
154
102
|
return null;
|
|
155
103
|
},
|
|
156
104
|
onLog: (message) => options.runtime.error?.(`zalo ingress: ${message}`)
|
|
157
105
|
},
|
|
158
106
|
createStoppedError: () => /* @__PURE__ */ new Error("Zalo ingress stopped."),
|
|
159
|
-
onError: (error) => options.runtime.error?.(`zalo ingress drain failed: ${
|
|
107
|
+
onError: (error) => options.runtime.error?.(`zalo ingress drain failed: ${formatErrorMessage(error)}`)
|
|
160
108
|
});
|
|
161
109
|
return {
|
|
162
110
|
accept: async (rawEvent) => {
|
|
163
111
|
await monitor.admit(rawEvent);
|
|
164
112
|
},
|
|
165
113
|
start: monitor.start,
|
|
166
|
-
stop:
|
|
167
|
-
await monitor.stop();
|
|
168
|
-
await Promise.allSettled(deferredClaims.values());
|
|
169
|
-
}
|
|
114
|
+
stop: monitor.stop
|
|
170
115
|
};
|
|
171
116
|
}
|
|
172
117
|
const zaloWebhookIngressRuntime = { createZaloWebhookIngress };
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "zalo",
|
|
3
|
+
"doctorContract": {},
|
|
3
4
|
"name": "Zalo",
|
|
4
5
|
"description": "OpenClaw Zalo channel plugin for bot and webhook chats.",
|
|
5
6
|
"icon": "https://cdn.simpleicons.org/zalo",
|
|
@@ -26,6 +27,9 @@
|
|
|
26
27
|
"enabled": {
|
|
27
28
|
"type": "boolean"
|
|
28
29
|
},
|
|
30
|
+
"configWrites": {
|
|
31
|
+
"type": "boolean"
|
|
32
|
+
},
|
|
29
33
|
"markdown": {
|
|
30
34
|
"type": "object",
|
|
31
35
|
"properties": {
|
|
@@ -71,6 +75,29 @@
|
|
|
71
75
|
],
|
|
72
76
|
"additionalProperties": false
|
|
73
77
|
},
|
|
78
|
+
{
|
|
79
|
+
"type": "object",
|
|
80
|
+
"properties": {
|
|
81
|
+
"source": {
|
|
82
|
+
"type": "string",
|
|
83
|
+
"const": "store"
|
|
84
|
+
},
|
|
85
|
+
"provider": {
|
|
86
|
+
"type": "string",
|
|
87
|
+
"pattern": "^[a-z][a-z0-9_-]{0,63}$"
|
|
88
|
+
},
|
|
89
|
+
"id": {
|
|
90
|
+
"type": "string",
|
|
91
|
+
"pattern": "^[A-Z][A-Z0-9_]{0,127}$"
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
"required": [
|
|
95
|
+
"source",
|
|
96
|
+
"provider",
|
|
97
|
+
"id"
|
|
98
|
+
],
|
|
99
|
+
"additionalProperties": false
|
|
100
|
+
},
|
|
74
101
|
{
|
|
75
102
|
"type": "object",
|
|
76
103
|
"properties": {
|
|
@@ -155,6 +182,29 @@
|
|
|
155
182
|
],
|
|
156
183
|
"additionalProperties": false
|
|
157
184
|
},
|
|
185
|
+
{
|
|
186
|
+
"type": "object",
|
|
187
|
+
"properties": {
|
|
188
|
+
"source": {
|
|
189
|
+
"type": "string",
|
|
190
|
+
"const": "store"
|
|
191
|
+
},
|
|
192
|
+
"provider": {
|
|
193
|
+
"type": "string",
|
|
194
|
+
"pattern": "^[a-z][a-z0-9_-]{0,63}$"
|
|
195
|
+
},
|
|
196
|
+
"id": {
|
|
197
|
+
"type": "string",
|
|
198
|
+
"pattern": "^[A-Z][A-Z0-9_]{0,127}$"
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
"required": [
|
|
202
|
+
"source",
|
|
203
|
+
"provider",
|
|
204
|
+
"id"
|
|
205
|
+
],
|
|
206
|
+
"additionalProperties": false
|
|
207
|
+
},
|
|
158
208
|
{
|
|
159
209
|
"type": "object",
|
|
160
210
|
"properties": {
|
|
@@ -270,6 +320,9 @@
|
|
|
270
320
|
"enabled": {
|
|
271
321
|
"type": "boolean"
|
|
272
322
|
},
|
|
323
|
+
"configWrites": {
|
|
324
|
+
"type": "boolean"
|
|
325
|
+
},
|
|
273
326
|
"markdown": {
|
|
274
327
|
"type": "object",
|
|
275
328
|
"properties": {
|
|
@@ -315,6 +368,29 @@
|
|
|
315
368
|
],
|
|
316
369
|
"additionalProperties": false
|
|
317
370
|
},
|
|
371
|
+
{
|
|
372
|
+
"type": "object",
|
|
373
|
+
"properties": {
|
|
374
|
+
"source": {
|
|
375
|
+
"type": "string",
|
|
376
|
+
"const": "store"
|
|
377
|
+
},
|
|
378
|
+
"provider": {
|
|
379
|
+
"type": "string",
|
|
380
|
+
"pattern": "^[a-z][a-z0-9_-]{0,63}$"
|
|
381
|
+
},
|
|
382
|
+
"id": {
|
|
383
|
+
"type": "string",
|
|
384
|
+
"pattern": "^[A-Z][A-Z0-9_]{0,127}$"
|
|
385
|
+
}
|
|
386
|
+
},
|
|
387
|
+
"required": [
|
|
388
|
+
"source",
|
|
389
|
+
"provider",
|
|
390
|
+
"id"
|
|
391
|
+
],
|
|
392
|
+
"additionalProperties": false
|
|
393
|
+
},
|
|
318
394
|
{
|
|
319
395
|
"type": "object",
|
|
320
396
|
"properties": {
|
|
@@ -399,6 +475,29 @@
|
|
|
399
475
|
],
|
|
400
476
|
"additionalProperties": false
|
|
401
477
|
},
|
|
478
|
+
{
|
|
479
|
+
"type": "object",
|
|
480
|
+
"properties": {
|
|
481
|
+
"source": {
|
|
482
|
+
"type": "string",
|
|
483
|
+
"const": "store"
|
|
484
|
+
},
|
|
485
|
+
"provider": {
|
|
486
|
+
"type": "string",
|
|
487
|
+
"pattern": "^[a-z][a-z0-9_-]{0,63}$"
|
|
488
|
+
},
|
|
489
|
+
"id": {
|
|
490
|
+
"type": "string",
|
|
491
|
+
"pattern": "^[A-Z][A-Z0-9_]{0,127}$"
|
|
492
|
+
}
|
|
493
|
+
},
|
|
494
|
+
"required": [
|
|
495
|
+
"source",
|
|
496
|
+
"provider",
|
|
497
|
+
"id"
|
|
498
|
+
],
|
|
499
|
+
"additionalProperties": false
|
|
500
|
+
},
|
|
402
501
|
{
|
|
403
502
|
"type": "object",
|
|
404
503
|
"properties": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/zalo",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.8.1-beta.2",
|
|
4
4
|
"description": "OpenClaw Zalo channel plugin for bot and webhook chats.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"type": "module",
|
|
10
10
|
"peerDependencies": {
|
|
11
|
-
"openclaw": ">=2026.
|
|
11
|
+
"openclaw": ">=2026.8.1-beta.2"
|
|
12
12
|
},
|
|
13
13
|
"peerDependenciesMeta": {
|
|
14
14
|
"openclaw": {
|
|
@@ -69,7 +69,10 @@
|
|
|
69
69
|
"cli": {
|
|
70
70
|
"flags": "--use-env",
|
|
71
71
|
"description": "Use ZALO_BOT_TOKEN"
|
|
72
|
-
}
|
|
72
|
+
},
|
|
73
|
+
"envVars": [
|
|
74
|
+
"ZALO_BOT_TOKEN"
|
|
75
|
+
]
|
|
73
76
|
}
|
|
74
77
|
]
|
|
75
78
|
}
|
|
@@ -80,10 +83,10 @@
|
|
|
80
83
|
"minHostVersion": ">=2026.4.10"
|
|
81
84
|
},
|
|
82
85
|
"compat": {
|
|
83
|
-
"pluginApi": ">=2026.
|
|
86
|
+
"pluginApi": ">=2026.8.1-beta.2"
|
|
84
87
|
},
|
|
85
88
|
"build": {
|
|
86
|
-
"openclawVersion": "2026.
|
|
89
|
+
"openclawVersion": "2026.8.1-beta.2"
|
|
87
90
|
},
|
|
88
91
|
"release": {
|
|
89
92
|
"publishToClawHub": true,
|