@openclaw/zalo 2026.5.2 → 2026.5.3-beta.1

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.
Files changed (87) hide show
  1. package/dist/accounts-9NLDDlZ8.js +118 -0
  2. package/dist/actions.runtime-kJ65ZxW7.js +5 -0
  3. package/dist/api.js +5 -0
  4. package/dist/channel-VPbtV3Oq.js +343 -0
  5. package/dist/channel-plugin-api.js +2 -0
  6. package/dist/channel.runtime-BnTAWQx5.js +106 -0
  7. package/dist/contract-api.js +3 -0
  8. package/dist/group-access-DZR43lOR.js +30 -0
  9. package/dist/index.js +22 -0
  10. package/dist/monitor-DMysJBWa.js +823 -0
  11. package/dist/monitor.webhook-DqnuvgjV.js +175 -0
  12. package/dist/proxy-CY8VuC6H.js +135 -0
  13. package/dist/runtime-BRFxnYQx.js +8 -0
  14. package/dist/runtime-api-MOTmRW4F.js +19 -0
  15. package/dist/runtime-api.js +3 -0
  16. package/dist/secret-contract-Dw93tGo2.js +87 -0
  17. package/dist/secret-contract-api.js +2 -0
  18. package/dist/send-Gv3l5EGI.js +101 -0
  19. package/dist/setup-api.js +30 -0
  20. package/dist/setup-core-DigRD3j1.js +166 -0
  21. package/dist/setup-entry.js +15 -0
  22. package/dist/setup-surface-2Up3yWov.js +216 -0
  23. package/dist/test-api.js +2 -0
  24. package/package.json +15 -6
  25. package/api.ts +0 -9
  26. package/channel-plugin-api.ts +0 -1
  27. package/contract-api.ts +0 -5
  28. package/index.test.ts +0 -15
  29. package/index.ts +0 -20
  30. package/runtime-api.test.ts +0 -17
  31. package/runtime-api.ts +0 -75
  32. package/secret-contract-api.ts +0 -5
  33. package/setup-api.ts +0 -34
  34. package/setup-entry.ts +0 -13
  35. package/src/accounts.test.ts +0 -70
  36. package/src/accounts.ts +0 -60
  37. package/src/actions.runtime.ts +0 -5
  38. package/src/actions.test.ts +0 -32
  39. package/src/actions.ts +0 -62
  40. package/src/api.test.ts +0 -149
  41. package/src/api.ts +0 -265
  42. package/src/approval-auth.test.ts +0 -17
  43. package/src/approval-auth.ts +0 -25
  44. package/src/channel.directory.test.ts +0 -59
  45. package/src/channel.runtime.ts +0 -93
  46. package/src/channel.startup.test.ts +0 -101
  47. package/src/channel.ts +0 -275
  48. package/src/config-schema.test.ts +0 -30
  49. package/src/config-schema.ts +0 -29
  50. package/src/group-access.ts +0 -49
  51. package/src/monitor.group-policy.test.ts +0 -94
  52. package/src/monitor.image.polling.test.ts +0 -110
  53. package/src/monitor.lifecycle.test.ts +0 -198
  54. package/src/monitor.pairing.lifecycle.test.ts +0 -141
  55. package/src/monitor.polling.media-reply.test.ts +0 -425
  56. package/src/monitor.reply-once.lifecycle.test.ts +0 -171
  57. package/src/monitor.ts +0 -1028
  58. package/src/monitor.types.ts +0 -4
  59. package/src/monitor.webhook.test.ts +0 -806
  60. package/src/monitor.webhook.ts +0 -278
  61. package/src/outbound-media.test.ts +0 -182
  62. package/src/outbound-media.ts +0 -241
  63. package/src/outbound-payload.contract.test.ts +0 -45
  64. package/src/probe.ts +0 -45
  65. package/src/proxy.ts +0 -24
  66. package/src/runtime-api.ts +0 -75
  67. package/src/runtime-support.ts +0 -91
  68. package/src/runtime.ts +0 -9
  69. package/src/secret-contract.ts +0 -109
  70. package/src/secret-input.ts +0 -5
  71. package/src/send.test.ts +0 -120
  72. package/src/send.ts +0 -153
  73. package/src/session-route.ts +0 -32
  74. package/src/setup-allow-from.ts +0 -94
  75. package/src/setup-core.ts +0 -149
  76. package/src/setup-status.test.ts +0 -33
  77. package/src/setup-surface.test.ts +0 -175
  78. package/src/setup-surface.ts +0 -291
  79. package/src/status-issues.test.ts +0 -17
  80. package/src/status-issues.ts +0 -37
  81. package/src/test-support/lifecycle-test-support.ts +0 -413
  82. package/src/test-support/monitor-mocks-test-support.ts +0 -209
  83. package/src/token.test.ts +0 -92
  84. package/src/token.ts +0 -79
  85. package/src/types.ts +0 -50
  86. package/test-api.ts +0 -1
  87. package/tsconfig.json +0 -16
@@ -0,0 +1,118 @@
1
+ import { createAccountListHelpers, resolveMergedAccountConfig } from "openclaw/plugin-sdk/account-helpers";
2
+ import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
3
+ import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
4
+ import { tryReadSecretFileSync } from "openclaw/plugin-sdk/core";
5
+ import { resolveAccountEntry } from "openclaw/plugin-sdk/routing";
6
+ import { buildSecretInputSchema, normalizeResolvedSecretInputString, normalizeSecretInputString } from "openclaw/plugin-sdk/secret-input";
7
+ //#region \0rolldown/runtime.js
8
+ var __defProp = Object.defineProperty;
9
+ var __exportAll = (all, no_symbols) => {
10
+ let target = {};
11
+ for (var name in all) __defProp(target, name, {
12
+ get: all[name],
13
+ enumerable: true
14
+ });
15
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
16
+ return target;
17
+ };
18
+ //#endregion
19
+ //#region extensions/zalo/src/token.ts
20
+ function readTokenFromFile(tokenFile) {
21
+ return tryReadSecretFileSync(tokenFile, "Zalo token file", { rejectSymlink: true }) ?? "";
22
+ }
23
+ function resolveZaloToken(config, accountId, options) {
24
+ const resolvedAccountId = normalizeAccountId(accountId ?? config?.defaultAccount);
25
+ const isDefaultAccount = resolvedAccountId === DEFAULT_ACCOUNT_ID;
26
+ const baseConfig = config;
27
+ const accountConfig = resolveAccountEntry(baseConfig?.accounts, normalizeAccountId(resolvedAccountId));
28
+ const accountHasBotToken = Boolean(accountConfig && Object.prototype.hasOwnProperty.call(accountConfig, "botToken"));
29
+ if (accountConfig && accountHasBotToken) {
30
+ const token = options?.allowUnresolvedSecretRef ? normalizeSecretInputString(accountConfig.botToken) : normalizeResolvedSecretInputString({
31
+ value: accountConfig.botToken,
32
+ path: `channels.zalo.accounts.${resolvedAccountId}.botToken`
33
+ });
34
+ if (token) return {
35
+ token,
36
+ source: "config"
37
+ };
38
+ const fileToken = readTokenFromFile(accountConfig.tokenFile);
39
+ if (fileToken) return {
40
+ token: fileToken,
41
+ source: "configFile"
42
+ };
43
+ }
44
+ if (!accountHasBotToken) {
45
+ const fileToken = readTokenFromFile(accountConfig?.tokenFile);
46
+ if (fileToken) return {
47
+ token: fileToken,
48
+ source: "configFile"
49
+ };
50
+ }
51
+ if (!accountHasBotToken) {
52
+ const token = options?.allowUnresolvedSecretRef ? normalizeSecretInputString(baseConfig?.botToken) : normalizeResolvedSecretInputString({
53
+ value: baseConfig?.botToken,
54
+ path: "channels.zalo.botToken"
55
+ });
56
+ if (token) return {
57
+ token,
58
+ source: "config"
59
+ };
60
+ const fileToken = readTokenFromFile(baseConfig?.tokenFile);
61
+ if (fileToken) return {
62
+ token: fileToken,
63
+ source: "configFile"
64
+ };
65
+ }
66
+ if (isDefaultAccount) {
67
+ const envToken = process.env.ZALO_BOT_TOKEN?.trim();
68
+ if (envToken) return {
69
+ token: envToken,
70
+ source: "env"
71
+ };
72
+ }
73
+ return {
74
+ token: "",
75
+ source: "none"
76
+ };
77
+ }
78
+ //#endregion
79
+ //#region extensions/zalo/src/accounts.ts
80
+ var accounts_exports = /* @__PURE__ */ __exportAll({
81
+ listEnabledZaloAccounts: () => listEnabledZaloAccounts,
82
+ listZaloAccountIds: () => listZaloAccountIds,
83
+ resolveDefaultZaloAccountId: () => resolveDefaultZaloAccountId,
84
+ resolveZaloAccount: () => resolveZaloAccount
85
+ });
86
+ const { listAccountIds: listZaloAccountIds, resolveDefaultAccountId: resolveDefaultZaloAccountId } = createAccountListHelpers("zalo");
87
+ function mergeZaloAccountConfig(cfg, accountId) {
88
+ return resolveMergedAccountConfig({
89
+ channelConfig: cfg.channels?.zalo,
90
+ accounts: (cfg.channels?.zalo)?.accounts,
91
+ accountId,
92
+ omitKeys: ["defaultAccount"]
93
+ });
94
+ }
95
+ function resolveZaloAccount(params) {
96
+ const accountId = normalizeAccountId(params.accountId ?? (params.cfg.channels?.zalo)?.defaultAccount);
97
+ const baseEnabled = (params.cfg.channels?.zalo)?.enabled !== false;
98
+ const merged = mergeZaloAccountConfig(params.cfg, accountId);
99
+ const accountEnabled = merged.enabled !== false;
100
+ const enabled = baseEnabled && accountEnabled;
101
+ const tokenResolution = resolveZaloToken(params.cfg.channels?.zalo, accountId, { allowUnresolvedSecretRef: params.allowUnresolvedSecretRef });
102
+ return {
103
+ accountId,
104
+ name: normalizeOptionalString(merged.name),
105
+ enabled,
106
+ token: tokenResolution.token,
107
+ tokenSource: tokenResolution.source,
108
+ config: merged
109
+ };
110
+ }
111
+ function listEnabledZaloAccounts(cfg) {
112
+ return listZaloAccountIds(cfg).map((accountId) => resolveZaloAccount({
113
+ cfg,
114
+ accountId
115
+ })).filter((account) => account.enabled);
116
+ }
117
+ //#endregion
118
+ export { resolveZaloAccount as a, normalizeSecretInputString as c, resolveDefaultZaloAccountId as i, listEnabledZaloAccounts as n, resolveZaloToken as o, listZaloAccountIds as r, buildSecretInputSchema as s, accounts_exports as t };
@@ -0,0 +1,5 @@
1
+ import { t as sendMessageZalo } from "./send-Gv3l5EGI.js";
2
+ //#region extensions/zalo/src/actions.runtime.ts
3
+ const zaloActionsRuntime = { sendMessageZalo };
4
+ //#endregion
5
+ export { zaloActionsRuntime };
package/dist/api.js ADDED
@@ -0,0 +1,5 @@
1
+ import { t as zaloPlugin } from "./channel-VPbtV3Oq.js";
2
+ import { n as zaloDmPolicy, r as zaloSetupAdapter, t as createZaloSetupWizardProxy } from "./setup-core-DigRD3j1.js";
3
+ import { r as resolveZaloRuntimeGroupPolicy, t as evaluateZaloGroupAccess } from "./group-access-DZR43lOR.js";
4
+ import { zaloSetupWizard } from "./setup-api.js";
5
+ export { createZaloSetupWizardProxy, evaluateZaloGroupAccess, resolveZaloRuntimeGroupPolicy, zaloDmPolicy, zaloPlugin, zaloSetupAdapter, zaloSetupWizard };
@@ -0,0 +1,343 @@
1
+ import { a as resolveZaloAccount, i as resolveDefaultZaloAccountId, n as listEnabledZaloAccounts, r as listZaloAccountIds, s as buildSecretInputSchema } from "./accounts-9NLDDlZ8.js";
2
+ import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-Dw93tGo2.js";
3
+ import { r as zaloSetupAdapter, t as createZaloSetupWizardProxy } from "./setup-core-DigRD3j1.js";
4
+ import { describeWebhookAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
5
+ import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
6
+ import { formatAllowFromLowercase } from "openclaw/plugin-sdk/allow-from";
7
+ import { adaptScopedAccountAccessor, createScopedChannelConfigAdapter, createScopedDmSecurityResolver, mapAllowFromEntries } from "openclaw/plugin-sdk/channel-config-helpers";
8
+ import { buildChannelConfigSchema, createChatChannelPlugin } from "openclaw/plugin-sdk/channel-core";
9
+ import { buildOpenGroupPolicyRestrictSendersWarning, buildOpenGroupPolicyWarning, createOpenProviderGroupPolicyWarningCollector } from "openclaw/plugin-sdk/channel-policy";
10
+ import { createEmptyChannelResult, createRawChannelSendResultAdapter } from "openclaw/plugin-sdk/channel-send-result";
11
+ import { buildTokenChannelStatusSummary } from "openclaw/plugin-sdk/channel-status";
12
+ import { createStaticReplyToModeResolver } from "openclaw/plugin-sdk/conversation-runtime";
13
+ import { createChannelDirectoryAdapter, listResolvedDirectoryUserEntriesFromAllowFrom } from "openclaw/plugin-sdk/directory-runtime";
14
+ import { createLazyRuntimeModule, createLazyRuntimeNamedExport } from "openclaw/plugin-sdk/lazy-runtime";
15
+ import { isNumericTargetId, sendPayloadWithChunkedTextAndMedia } from "openclaw/plugin-sdk/reply-payload";
16
+ import { createComputedAccountStatusAdapter, createDefaultChannelRuntimeState } from "openclaw/plugin-sdk/status-helpers";
17
+ import { chunkTextForOutbound } from "openclaw/plugin-sdk/text-chunking";
18
+ import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
19
+ import { buildChannelOutboundSessionRoute, stripChannelTargetPrefix, stripTargetKindPrefix } from "openclaw/plugin-sdk/core";
20
+ import { jsonResult, readStringParam } from "openclaw/plugin-sdk/channel-actions";
21
+ import { extractToolSend } from "openclaw/plugin-sdk/tool-send";
22
+ import { createResolvedApproverActionAuthAdapter, resolveApprovalApprovers } from "openclaw/plugin-sdk/approval-auth-runtime";
23
+ import { AllowFromListSchema, DmPolicySchema, GroupPolicySchema, MarkdownConfigSchema, buildCatchallMultiAccountChannelSchema } from "openclaw/plugin-sdk/channel-config-schema";
24
+ import { z } from "openclaw/plugin-sdk/zod";
25
+ import { coerceStatusIssueAccountId, readStatusIssueFields } from "openclaw/plugin-sdk/extension-shared";
26
+ //#region extensions/zalo/src/actions.ts
27
+ const loadZaloActionsRuntime = createLazyRuntimeNamedExport(() => import("./actions.runtime-kJ65ZxW7.js"), "zaloActionsRuntime");
28
+ const providerId = "zalo";
29
+ function listEnabledAccounts(cfg, accountId) {
30
+ return (accountId ? [resolveZaloAccount({
31
+ cfg,
32
+ accountId
33
+ })] : listEnabledZaloAccounts(cfg)).filter((account) => account.enabled && account.tokenSource !== "none");
34
+ }
35
+ const zaloMessageActions = {
36
+ describeMessageTool: ({ cfg, accountId }) => {
37
+ if (listEnabledAccounts(cfg, accountId).length === 0) return null;
38
+ const actions = new Set(["send"]);
39
+ return {
40
+ actions: Array.from(actions),
41
+ capabilities: []
42
+ };
43
+ },
44
+ extractToolSend: ({ args }) => extractToolSend(args, "sendMessage"),
45
+ handleAction: async ({ action, params, cfg, accountId }) => {
46
+ if (action === "send") {
47
+ const to = readStringParam(params, "to", { required: true });
48
+ const content = readStringParam(params, "message", {
49
+ required: true,
50
+ allowEmpty: true
51
+ });
52
+ const mediaUrl = readStringParam(params, "media", { trim: false });
53
+ const { sendMessageZalo } = await loadZaloActionsRuntime();
54
+ const result = await sendMessageZalo(to ?? "", content ?? "", {
55
+ accountId: accountId ?? void 0,
56
+ mediaUrl: mediaUrl ?? void 0,
57
+ cfg
58
+ });
59
+ if (!result.ok) return jsonResult({
60
+ ok: false,
61
+ error: result.error ?? "Failed to send Zalo message"
62
+ });
63
+ return jsonResult({
64
+ ok: true,
65
+ to,
66
+ messageId: result.messageId
67
+ });
68
+ }
69
+ throw new Error(`Action ${action} is not supported for provider ${providerId}.`);
70
+ }
71
+ };
72
+ //#endregion
73
+ //#region extensions/zalo/src/approval-auth.ts
74
+ function normalizeZaloApproverId(value) {
75
+ const normalized = String(value).trim().replace(/^(zalo|zl):/i, "").trim();
76
+ return /^\d+$/.test(normalized) ? normalized : void 0;
77
+ }
78
+ const zaloApprovalAuth = createResolvedApproverActionAuthAdapter({
79
+ channelLabel: "Zalo",
80
+ resolveApprovers: ({ cfg, accountId }) => {
81
+ const account = resolveZaloAccount({
82
+ cfg,
83
+ accountId
84
+ }).config;
85
+ return resolveApprovalApprovers({
86
+ allowFrom: account.allowFrom,
87
+ normalizeApprover: normalizeZaloApproverId
88
+ });
89
+ },
90
+ normalizeSenderId: (value) => normalizeZaloApproverId(value)
91
+ });
92
+ const ZaloConfigSchema = buildCatchallMultiAccountChannelSchema(z.object({
93
+ name: z.string().optional(),
94
+ enabled: z.boolean().optional(),
95
+ markdown: MarkdownConfigSchema,
96
+ botToken: buildSecretInputSchema().optional(),
97
+ tokenFile: z.string().optional(),
98
+ webhookUrl: z.string().optional(),
99
+ webhookSecret: buildSecretInputSchema().optional(),
100
+ webhookPath: z.string().optional(),
101
+ dmPolicy: DmPolicySchema.optional(),
102
+ allowFrom: AllowFromListSchema,
103
+ groupPolicy: GroupPolicySchema.optional(),
104
+ groupAllowFrom: AllowFromListSchema,
105
+ mediaMaxMb: z.number().optional(),
106
+ proxy: z.string().optional(),
107
+ responsePrefix: z.string().optional()
108
+ }));
109
+ //#endregion
110
+ //#region extensions/zalo/src/session-route.ts
111
+ function resolveZaloOutboundSessionRoute(params) {
112
+ const trimmed = stripChannelTargetPrefix(params.target, "zalo", "zl");
113
+ if (!trimmed) return null;
114
+ const isGroup = normalizeLowercaseStringOrEmpty(trimmed).startsWith("group:");
115
+ const peerId = stripTargetKindPrefix(trimmed);
116
+ if (!peerId) return null;
117
+ return buildChannelOutboundSessionRoute({
118
+ cfg: params.cfg,
119
+ agentId: params.agentId,
120
+ channel: "zalo",
121
+ accountId: params.accountId,
122
+ peer: {
123
+ kind: isGroup ? "group" : "direct",
124
+ id: peerId
125
+ },
126
+ chatType: isGroup ? "group" : "direct",
127
+ from: isGroup ? `zalo:group:${peerId}` : `zalo:${peerId}`,
128
+ to: `zalo:${peerId}`
129
+ });
130
+ }
131
+ //#endregion
132
+ //#region extensions/zalo/src/status-issues.ts
133
+ const ZALO_STATUS_FIELDS = [
134
+ "accountId",
135
+ "enabled",
136
+ "configured",
137
+ "dmPolicy"
138
+ ];
139
+ function collectZaloStatusIssues(accounts) {
140
+ const issues = [];
141
+ for (const entry of accounts) {
142
+ const account = readStatusIssueFields(entry, ZALO_STATUS_FIELDS);
143
+ if (!account) continue;
144
+ const accountId = coerceStatusIssueAccountId(account.accountId) ?? "default";
145
+ const enabled = account.enabled !== false;
146
+ const configured = account.configured === true;
147
+ if (!enabled || !configured) continue;
148
+ if (account.dmPolicy === "open") issues.push({
149
+ channel: "zalo",
150
+ accountId,
151
+ kind: "config",
152
+ message: "Zalo dmPolicy is \"open\", allowing any user to message the bot without pairing.",
153
+ fix: "Set channels.zalo.dmPolicy to \"pairing\" or \"allowlist\" to restrict access."
154
+ });
155
+ }
156
+ return issues;
157
+ }
158
+ //#endregion
159
+ //#region extensions/zalo/src/channel.ts
160
+ const meta = {
161
+ id: "zalo",
162
+ label: "Zalo",
163
+ selectionLabel: "Zalo (Bot API)",
164
+ docsPath: "/channels/zalo",
165
+ docsLabel: "zalo",
166
+ blurb: "Vietnam-focused messaging platform with Bot API.",
167
+ aliases: ["zl"],
168
+ order: 80,
169
+ quickstartAllowFrom: true
170
+ };
171
+ function normalizeZaloMessagingTarget(raw) {
172
+ const trimmed = raw?.trim();
173
+ if (!trimmed) return;
174
+ return trimmed.replace(/^(zalo|zl):/i, "").trim();
175
+ }
176
+ const loadZaloChannelRuntime = createLazyRuntimeModule(() => import("./channel.runtime-BnTAWQx5.js"));
177
+ const zaloSetupWizard = createZaloSetupWizardProxy(async () => (await import("./setup-surface-2Up3yWov.js")).zaloSetupWizard);
178
+ const zaloTextChunkLimit = 2e3;
179
+ const zaloRawSendResultAdapter = createRawChannelSendResultAdapter({
180
+ channel: "zalo",
181
+ sendText: async ({ to, text, accountId, cfg }) => await (await loadZaloChannelRuntime()).sendZaloText({
182
+ to,
183
+ text,
184
+ accountId: accountId ?? void 0,
185
+ cfg
186
+ }),
187
+ sendMedia: async ({ to, text, mediaUrl, accountId, cfg }) => await (await loadZaloChannelRuntime()).sendZaloText({
188
+ to,
189
+ text,
190
+ accountId: accountId ?? void 0,
191
+ mediaUrl,
192
+ cfg
193
+ })
194
+ });
195
+ const zaloConfigAdapter = createScopedChannelConfigAdapter({
196
+ sectionKey: "zalo",
197
+ listAccountIds: listZaloAccountIds,
198
+ resolveAccount: adaptScopedAccountAccessor(resolveZaloAccount),
199
+ defaultAccountId: resolveDefaultZaloAccountId,
200
+ clearBaseFields: [
201
+ "botToken",
202
+ "tokenFile",
203
+ "name"
204
+ ],
205
+ resolveAllowFrom: (account) => account.config.allowFrom,
206
+ formatAllowFrom: (allowFrom) => formatAllowFromLowercase({
207
+ allowFrom,
208
+ stripPrefixRe: /^(zalo|zl):/i
209
+ })
210
+ });
211
+ const resolveZaloDmPolicy = createScopedDmSecurityResolver({
212
+ channelKey: "zalo",
213
+ resolvePolicy: (account) => account.config.dmPolicy,
214
+ resolveAllowFrom: (account) => account.config.allowFrom,
215
+ policyPathSuffix: "dmPolicy",
216
+ normalizeEntry: (raw) => raw.trim().replace(/^(zalo|zl):/i, "")
217
+ });
218
+ const collectZaloSecurityWarnings = createOpenProviderGroupPolicyWarningCollector({
219
+ providerConfigPresent: (cfg) => cfg.channels?.zalo !== void 0,
220
+ resolveGroupPolicy: ({ account }) => account.config.groupPolicy,
221
+ collect: ({ account, groupPolicy }) => {
222
+ if (groupPolicy !== "open") return [];
223
+ const explicitGroupAllowFrom = mapAllowFromEntries(account.config.groupAllowFrom);
224
+ const dmAllowFrom = mapAllowFromEntries(account.config.allowFrom);
225
+ if ((explicitGroupAllowFrom.length > 0 ? explicitGroupAllowFrom : dmAllowFrom).length > 0) return [buildOpenGroupPolicyRestrictSendersWarning({
226
+ surface: "Zalo groups",
227
+ openScope: "any member",
228
+ groupPolicyPath: "channels.zalo.groupPolicy",
229
+ groupAllowFromPath: "channels.zalo.groupAllowFrom"
230
+ })];
231
+ return [buildOpenGroupPolicyWarning({
232
+ surface: "Zalo groups",
233
+ openBehavior: "with no groupAllowFrom/allowFrom allowlist; any member can trigger (mention-gated)",
234
+ remediation: "Set channels.zalo.groupPolicy=\"allowlist\" + channels.zalo.groupAllowFrom"
235
+ })];
236
+ }
237
+ });
238
+ const zaloPlugin = createChatChannelPlugin({
239
+ base: {
240
+ id: "zalo",
241
+ meta,
242
+ setup: zaloSetupAdapter,
243
+ setupWizard: zaloSetupWizard,
244
+ capabilities: {
245
+ chatTypes: ["direct", "group"],
246
+ media: true,
247
+ reactions: false,
248
+ threads: false,
249
+ polls: false,
250
+ nativeCommands: false,
251
+ blockStreaming: true
252
+ },
253
+ reload: { configPrefixes: ["channels.zalo"] },
254
+ configSchema: buildChannelConfigSchema(ZaloConfigSchema),
255
+ config: {
256
+ ...zaloConfigAdapter,
257
+ isConfigured: (account) => Boolean(account.token?.trim()),
258
+ describeAccount: (account) => describeWebhookAccountSnapshot({
259
+ account,
260
+ configured: Boolean(account.token?.trim()),
261
+ mode: account.config.webhookUrl ? "webhook" : "polling",
262
+ extra: { tokenSource: account.tokenSource }
263
+ })
264
+ },
265
+ approvalCapability: zaloApprovalAuth,
266
+ secrets: {
267
+ secretTargetRegistryEntries,
268
+ collectRuntimeConfigAssignments
269
+ },
270
+ groups: { resolveRequireMention: () => true },
271
+ actions: zaloMessageActions,
272
+ messaging: {
273
+ targetPrefixes: ["zalo", "zl"],
274
+ normalizeTarget: normalizeZaloMessagingTarget,
275
+ resolveOutboundSessionRoute: (params) => resolveZaloOutboundSessionRoute(params),
276
+ targetResolver: {
277
+ looksLikeId: isNumericTargetId,
278
+ hint: "<chatId>"
279
+ }
280
+ },
281
+ directory: createChannelDirectoryAdapter({
282
+ listPeers: async (params) => listResolvedDirectoryUserEntriesFromAllowFrom({
283
+ ...params,
284
+ resolveAccount: adaptScopedAccountAccessor(resolveZaloAccount),
285
+ resolveAllowFrom: (account) => account.config.allowFrom,
286
+ normalizeId: (entry) => entry.trim().replace(/^(zalo|zl):/i, "")
287
+ }),
288
+ listGroups: async () => []
289
+ }),
290
+ status: createComputedAccountStatusAdapter({
291
+ defaultRuntime: createDefaultChannelRuntimeState(DEFAULT_ACCOUNT_ID),
292
+ collectStatusIssues: collectZaloStatusIssues,
293
+ buildChannelSummary: ({ snapshot }) => buildTokenChannelStatusSummary(snapshot),
294
+ probeAccount: async ({ account, timeoutMs }) => await (await loadZaloChannelRuntime()).probeZaloAccount({
295
+ account,
296
+ timeoutMs
297
+ }),
298
+ resolveAccountSnapshot: ({ account }) => {
299
+ const configured = Boolean(account.token?.trim());
300
+ return {
301
+ accountId: account.accountId,
302
+ name: account.name,
303
+ enabled: account.enabled,
304
+ configured,
305
+ extra: {
306
+ tokenSource: account.tokenSource,
307
+ mode: account.config.webhookUrl ? "webhook" : "polling",
308
+ dmPolicy: account.config.dmPolicy ?? "pairing"
309
+ }
310
+ };
311
+ }
312
+ }),
313
+ gateway: { startAccount: async (ctx) => await (await loadZaloChannelRuntime()).startZaloGatewayAccount(ctx) }
314
+ },
315
+ security: {
316
+ resolveDmPolicy: resolveZaloDmPolicy,
317
+ collectWarnings: collectZaloSecurityWarnings
318
+ },
319
+ pairing: { text: {
320
+ idLabel: "zaloUserId",
321
+ message: "Your pairing request has been approved.",
322
+ normalizeAllowEntry: (entry) => entry.trim().replace(/^(zalo|zl):/i, ""),
323
+ notify: async (params) => await (await loadZaloChannelRuntime()).notifyZaloPairingApproval(params)
324
+ } },
325
+ threading: { resolveReplyToMode: createStaticReplyToModeResolver("off") },
326
+ outbound: {
327
+ deliveryMode: "direct",
328
+ chunker: chunkTextForOutbound,
329
+ chunkerMode: "text",
330
+ textChunkLimit: zaloTextChunkLimit,
331
+ sendPayload: async (ctx) => await sendPayloadWithChunkedTextAndMedia({
332
+ ctx,
333
+ textChunkLimit: zaloTextChunkLimit,
334
+ chunker: chunkTextForOutbound,
335
+ sendText: (nextCtx) => zaloRawSendResultAdapter.sendText(nextCtx),
336
+ sendMedia: (nextCtx) => zaloRawSendResultAdapter.sendMedia(nextCtx),
337
+ emptyResult: createEmptyChannelResult("zalo")
338
+ }),
339
+ ...zaloRawSendResultAdapter
340
+ }
341
+ });
342
+ //#endregion
343
+ export { zaloPlugin as t };
@@ -0,0 +1,2 @@
1
+ import { t as zaloPlugin } from "./channel-VPbtV3Oq.js";
2
+ export { zaloPlugin };
@@ -0,0 +1,106 @@
1
+ import { c as normalizeSecretInputString } from "./accounts-9NLDDlZ8.js";
2
+ import { n as PAIRING_APPROVED_MESSAGE } from "./runtime-api-MOTmRW4F.js";
3
+ import { i as getMe, n as ZaloApiError, t as resolveZaloProxyFetch } from "./proxy-CY8VuC6H.js";
4
+ import { t as sendMessageZalo } from "./send-Gv3l5EGI.js";
5
+ import { createAccountStatusSink } from "openclaw/plugin-sdk/channel-lifecycle";
6
+ //#region extensions/zalo/src/probe.ts
7
+ async function probeZalo(token, timeoutMs = 5e3, fetcher) {
8
+ if (!token?.trim()) return {
9
+ ok: false,
10
+ error: "No token provided",
11
+ elapsedMs: 0
12
+ };
13
+ const startTime = Date.now();
14
+ try {
15
+ const response = await getMe(token.trim(), timeoutMs, fetcher);
16
+ const elapsedMs = Date.now() - startTime;
17
+ if (response.ok && response.result) return {
18
+ ok: true,
19
+ bot: response.result,
20
+ elapsedMs
21
+ };
22
+ return {
23
+ ok: false,
24
+ error: "Invalid response from Zalo API",
25
+ elapsedMs
26
+ };
27
+ } catch (err) {
28
+ const elapsedMs = Date.now() - startTime;
29
+ if (err instanceof ZaloApiError) return {
30
+ ok: false,
31
+ error: err.description ?? err.message,
32
+ elapsedMs
33
+ };
34
+ if (err instanceof Error) {
35
+ if (err.name === "AbortError") return {
36
+ ok: false,
37
+ error: `Request timed out after ${timeoutMs}ms`,
38
+ elapsedMs
39
+ };
40
+ return {
41
+ ok: false,
42
+ error: err.message,
43
+ elapsedMs
44
+ };
45
+ }
46
+ return {
47
+ ok: false,
48
+ error: String(err),
49
+ elapsedMs
50
+ };
51
+ }
52
+ }
53
+ //#endregion
54
+ //#region extensions/zalo/src/channel.runtime.ts
55
+ async function notifyZaloPairingApproval(params) {
56
+ const { resolveZaloAccount } = await import("./accounts-9NLDDlZ8.js").then((n) => n.t);
57
+ const account = resolveZaloAccount({ cfg: params.cfg });
58
+ if (!account.token) throw new Error("Zalo token not configured");
59
+ await sendMessageZalo(params.id, PAIRING_APPROVED_MESSAGE, { token: account.token });
60
+ }
61
+ async function sendZaloText(params) {
62
+ return await sendMessageZalo(params.to, params.text, params);
63
+ }
64
+ async function probeZaloAccount(params) {
65
+ return await probeZalo(params.account.token, params.timeoutMs, resolveZaloProxyFetch(params.account.config.proxy));
66
+ }
67
+ async function startZaloGatewayAccount(ctx) {
68
+ const account = ctx.account;
69
+ const token = account.token.trim();
70
+ const mode = account.config.webhookUrl ? "webhook" : "polling";
71
+ let zaloBotLabel = "";
72
+ const fetcher = resolveZaloProxyFetch(account.config.proxy);
73
+ try {
74
+ const probe = await probeZalo(token, 2500, fetcher);
75
+ const name = probe.ok ? probe.bot?.name?.trim() : null;
76
+ if (name) zaloBotLabel = ` (${name})`;
77
+ if (!probe.ok) ctx.log?.warn?.(`[${account.accountId}] Zalo probe failed before provider start (${String(probe.elapsedMs)}ms): ${probe.error}`);
78
+ ctx.setStatus({
79
+ accountId: account.accountId,
80
+ bot: probe.bot
81
+ });
82
+ } catch (err) {
83
+ ctx.log?.warn?.(`[${account.accountId}] Zalo probe threw before provider start: ${err instanceof Error ? err.stack ?? err.message : String(err)}`);
84
+ }
85
+ const statusSink = createAccountStatusSink({
86
+ accountId: ctx.accountId,
87
+ setStatus: ctx.setStatus
88
+ });
89
+ ctx.log?.info(`[${account.accountId}] starting provider${zaloBotLabel} mode=${mode}`);
90
+ const { monitorZaloProvider } = await import("./monitor-DMysJBWa.js");
91
+ return monitorZaloProvider({
92
+ token,
93
+ account,
94
+ config: ctx.cfg,
95
+ runtime: ctx.runtime,
96
+ abortSignal: ctx.abortSignal,
97
+ useWebhook: Boolean(account.config.webhookUrl),
98
+ webhookUrl: account.config.webhookUrl,
99
+ webhookSecret: normalizeSecretInputString(account.config.webhookSecret),
100
+ webhookPath: account.config.webhookPath,
101
+ fetcher,
102
+ statusSink
103
+ });
104
+ }
105
+ //#endregion
106
+ export { notifyZaloPairingApproval, probeZaloAccount, sendZaloText, startZaloGatewayAccount };
@@ -0,0 +1,3 @@
1
+ import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-Dw93tGo2.js";
2
+ import { r as resolveZaloRuntimeGroupPolicy, t as evaluateZaloGroupAccess } from "./group-access-DZR43lOR.js";
3
+ export { collectRuntimeConfigAssignments, evaluateZaloGroupAccess, resolveZaloRuntimeGroupPolicy, secretTargetRegistryEntries };
@@ -0,0 +1,30 @@
1
+ import { isNormalizedSenderAllowed } from "openclaw/plugin-sdk/allow-from";
2
+ import { evaluateSenderGroupAccess, resolveOpenProviderRuntimeGroupPolicy } from "openclaw/plugin-sdk/group-access";
3
+ //#region extensions/zalo/src/group-access.ts
4
+ const ZALO_ALLOW_FROM_PREFIX_RE = /^(zalo|zl):/i;
5
+ function isZaloSenderAllowed(senderId, allowFrom) {
6
+ return isNormalizedSenderAllowed({
7
+ senderId,
8
+ allowFrom,
9
+ stripPrefixRe: ZALO_ALLOW_FROM_PREFIX_RE
10
+ });
11
+ }
12
+ function resolveZaloRuntimeGroupPolicy(params) {
13
+ return resolveOpenProviderRuntimeGroupPolicy({
14
+ providerConfigPresent: params.providerConfigPresent,
15
+ groupPolicy: params.groupPolicy,
16
+ defaultGroupPolicy: params.defaultGroupPolicy
17
+ });
18
+ }
19
+ function evaluateZaloGroupAccess(params) {
20
+ return evaluateSenderGroupAccess({
21
+ providerConfigPresent: params.providerConfigPresent,
22
+ configuredGroupPolicy: params.configuredGroupPolicy,
23
+ defaultGroupPolicy: params.defaultGroupPolicy,
24
+ groupAllowFrom: params.groupAllowFrom,
25
+ senderId: params.senderId,
26
+ isSenderAllowed: isZaloSenderAllowed
27
+ });
28
+ }
29
+ //#endregion
30
+ export { isZaloSenderAllowed as n, resolveZaloRuntimeGroupPolicy as r, evaluateZaloGroupAccess as t };
package/dist/index.js ADDED
@@ -0,0 +1,22 @@
1
+ import { defineBundledChannelEntry } from "openclaw/plugin-sdk/channel-entry-contract";
2
+ //#region extensions/zalo/index.ts
3
+ var zalo_default = defineBundledChannelEntry({
4
+ id: "zalo",
5
+ name: "Zalo",
6
+ description: "Zalo channel plugin",
7
+ importMetaUrl: import.meta.url,
8
+ plugin: {
9
+ specifier: "./channel-plugin-api.js",
10
+ exportName: "zaloPlugin"
11
+ },
12
+ secrets: {
13
+ specifier: "./secret-contract-api.js",
14
+ exportName: "channelSecrets"
15
+ },
16
+ runtime: {
17
+ specifier: "./runtime-api.js",
18
+ exportName: "setZaloRuntime"
19
+ }
20
+ });
21
+ //#endregion
22
+ export { zalo_default as default };