@openclaw/zalo 2026.7.1-beta.1 → 2026.7.1-beta.4

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.
@@ -1,4 +1,4 @@
1
- import { t as sendMessageZalo } from "./send-DzchAfon.js";
1
+ import { t as sendMessageZalo } from "./send-9sVVxyap.js";
2
2
  //#region extensions/zalo/src/actions.runtime.ts
3
3
  const zaloActionsRuntime = { sendMessageZalo };
4
4
  //#endregion
package/dist/api.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { n as zaloDmPolicy, r as zaloSetupAdapter, t as createZaloSetupWizardProxy } from "./setup-core-fwCh0QUi.js";
2
- import { t as zaloPlugin } from "./channel-Zju2s6y_.js";
2
+ import { t as zaloPlugin } from "./channel-BOWhJVyl.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 };
@@ -12,7 +12,7 @@ import { buildTokenChannelStatusSummary } from "openclaw/plugin-sdk/channel-stat
12
12
  import { createStaticReplyToModeResolver } from "openclaw/plugin-sdk/conversation-runtime";
13
13
  import { createChannelDirectoryAdapter, listResolvedDirectoryUserEntriesFromAllowFrom } from "openclaw/plugin-sdk/directory-runtime";
14
14
  import { createLazyRuntimeModule, createLazyRuntimeNamedExport } from "openclaw/plugin-sdk/lazy-runtime";
15
- import { isNumericTargetId, sendPayloadWithChunkedTextAndMedia } from "openclaw/plugin-sdk/reply-payload";
15
+ import { sendPayloadWithChunkedTextAndMedia } from "openclaw/plugin-sdk/reply-payload";
16
16
  import { createComputedAccountStatusAdapter, createDefaultChannelRuntimeState } from "openclaw/plugin-sdk/status-helpers";
17
17
  import { chunkTextForOutbound } from "openclaw/plugin-sdk/text-chunking";
18
18
  import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
@@ -24,7 +24,7 @@ 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-BvO8xrg_.js"), "zaloActionsRuntime");
27
+ const loadZaloActionsRuntime = createLazyRuntimeNamedExport(() => import("./actions.runtime-OmfJFYZn.js"), "zaloActionsRuntime");
28
28
  const providerId = "zalo";
29
29
  function listEnabledAccounts(cfg, accountId) {
30
30
  return (accountId ? [resolveZaloAccount({
@@ -35,7 +35,7 @@ function listEnabledAccounts(cfg, accountId) {
35
35
  const zaloMessageActions = {
36
36
  describeMessageTool: ({ cfg, accountId }) => {
37
37
  if (listEnabledAccounts(cfg, accountId).length === 0) return null;
38
- const actions = new Set(["send"]);
38
+ const actions = /* @__PURE__ */ new Set(["send"]);
39
39
  return {
40
40
  actions: Array.from(actions),
41
41
  capabilities: []
@@ -111,7 +111,9 @@ const ZaloConfigSchema = buildCatchallMultiAccountChannelSchema(z.object({
111
111
  function resolveZaloOutboundSessionRoute(params) {
112
112
  const trimmed = stripChannelTargetPrefix(params.target, "zalo", "zl");
113
113
  if (!trimmed) return null;
114
- const isGroup = normalizeLowercaseStringOrEmpty(trimmed).startsWith("group:");
114
+ const normalizedTarget = normalizeLowercaseStringOrEmpty(trimmed);
115
+ const isGroup = normalizedTarget.startsWith("group:");
116
+ const recipientSessionExact = /^(?:group|user|dm):/.test(normalizedTarget);
115
117
  const peerId = stripTargetKindPrefix(trimmed);
116
118
  if (!peerId) return null;
117
119
  return buildChannelOutboundSessionRoute({
@@ -119,6 +121,7 @@ function resolveZaloOutboundSessionRoute(params) {
119
121
  agentId: params.agentId,
120
122
  channel: "zalo",
121
123
  accountId: params.accountId,
124
+ recipientSessionExact,
122
125
  peer: {
123
126
  kind: isGroup ? "group" : "direct",
124
127
  id: peerId
@@ -173,7 +176,11 @@ function normalizeZaloMessagingTarget(raw) {
173
176
  if (!trimmed) return;
174
177
  return trimmed.replace(/^(zalo|zl):/i, "").trim();
175
178
  }
176
- const loadZaloChannelRuntime = createLazyRuntimeModule(() => import("./channel.runtime-T7qX1CZo.js"));
179
+ function looksLikeZaloChatId(raw, normalized) {
180
+ const target = normalizeZaloMessagingTarget(normalized ?? raw);
181
+ return Boolean(target);
182
+ }
183
+ const loadZaloChannelRuntime = createLazyRuntimeModule(() => import("./channel.runtime-CEPgF8Zo.js"));
177
184
  const zaloSetupWizard = createZaloSetupWizardProxy(async () => (await import("./setup-surface-8eRimod9.js")).zaloSetupWizard);
178
185
  const zaloTextChunkLimit = 2e3;
179
186
  const zaloRawSendResultAdapter = createRawChannelSendResultAdapter({
@@ -297,7 +304,7 @@ const zaloPlugin = createChatChannelPlugin({
297
304
  normalizeTarget: normalizeZaloMessagingTarget,
298
305
  resolveOutboundSessionRoute: (params) => resolveZaloOutboundSessionRoute(params),
299
306
  targetResolver: {
300
- looksLikeId: isNumericTargetId,
307
+ looksLikeId: looksLikeZaloChatId,
301
308
  hint: "<chatId>"
302
309
  }
303
310
  },
@@ -358,7 +365,8 @@ const zaloPlugin = createChatChannelPlugin({
358
365
  chunker: chunkTextForOutbound,
359
366
  sendText: (nextCtx) => zaloRawSendResultAdapter.sendText(nextCtx),
360
367
  sendMedia: (nextCtx) => zaloRawSendResultAdapter.sendMedia(nextCtx),
361
- emptyResult: createEmptyChannelResult("zalo")
368
+ emptyResult: createEmptyChannelResult("zalo"),
369
+ onResult: ctx.onDeliveryResult
362
370
  }),
363
371
  ...zaloRawSendResultAdapter
364
372
  }
@@ -1,2 +1,2 @@
1
- import { t as zaloPlugin } from "./channel-Zju2s6y_.js";
1
+ import { t as zaloPlugin } from "./channel-BOWhJVyl.js";
2
2
  export { zaloPlugin };
@@ -1,6 +1,6 @@
1
1
  import { p as normalizeSecretInputString } from "./setup-core-fwCh0QUi.js";
2
- import { i as PAIRING_APPROVED_MESSAGE } from "./runtime-api-CDwUY_-_.js";
3
- import { a as getMe, n as resolveZaloProxyFetch, r as ZaloApiError, t as sendMessageZalo } from "./send-DzchAfon.js";
2
+ import { i as PAIRING_APPROVED_MESSAGE } from "./runtime-api-CAA4ZtqF.js";
3
+ import { a as getMe, n as resolveZaloProxyFetch, r as ZaloApiError, t as sendMessageZalo } from "./send-9sVVxyap.js";
4
4
  import { createAccountStatusSink } from "openclaw/plugin-sdk/channel-outbound";
5
5
  //#region extensions/zalo/src/probe.ts
6
6
  async function probeZalo(token, timeoutMs = 5e3, fetcher) {
@@ -71,7 +71,7 @@ async function startZaloGatewayAccount(ctx) {
71
71
  const fetcher = resolveZaloProxyFetch(account.config.proxy);
72
72
  try {
73
73
  const probe = await probeZalo(token, 2500, fetcher);
74
- const name = probe.ok ? probe.bot?.name?.trim() : null;
74
+ const name = probe.ok ? probe.bot?.account_name?.trim() : null;
75
75
  if (name) zaloBotLabel = ` (${name})`;
76
76
  if (!probe.ok) ctx.log?.warn?.(`[${account.accountId}] Zalo probe failed before provider start (${String(probe.elapsedMs)}ms): ${probe.error}`);
77
77
  ctx.setStatus({
@@ -86,7 +86,7 @@ async function startZaloGatewayAccount(ctx) {
86
86
  setStatus: ctx.setStatus
87
87
  });
88
88
  ctx.log?.info(`[${account.accountId}] starting provider${zaloBotLabel} mode=${mode}`);
89
- const { monitorZaloProvider } = await import("./monitor-C1dGXDyQ.js");
89
+ const { monitorZaloProvider } = await import("./monitor-fgcJhAZL.js");
90
90
  return monitorZaloProvider({
91
91
  token,
92
92
  account,
@@ -1,6 +1,7 @@
1
1
  import { n as resolveZaloRuntimeGroupPolicy, t as normalizeZaloAllowEntry } from "./group-access-8qHRzDHx.js";
2
- import { t as getZaloRuntime } from "./runtime-api-CDwUY_-_.js";
3
- import { c as sendChatAction, d as setWebhook, i as deleteWebhook, l as sendMessage, n as resolveZaloProxyFetch, o as getUpdates, r as ZaloApiError, s as getWebhookInfo, u as sendPhoto } from "./send-DzchAfon.js";
2
+ import { t as getZaloRuntime } from "./runtime-api-CAA4ZtqF.js";
3
+ import { c as sendChatAction, d as setWebhook, i as deleteWebhook, l as sendMessage, n as resolveZaloProxyFetch, o as getUpdates, r as ZaloApiError, s as getWebhookInfo, u as sendPhoto } from "./send-9sVVxyap.js";
4
+ import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
4
5
  import { deliverTextOrMediaReply, resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
5
6
  import { normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
6
7
  import { resolveDefaultGroupPolicy, warnMissingProviderGroupPolicyFallbackOnce } from "openclaw/plugin-sdk/runtime-group-policy";
@@ -9,6 +10,7 @@ import { logTypingFailure } from "openclaw/plugin-sdk/channel-feedback";
9
10
  import { resolveInboundRouteEnvelopeBuilderWithRuntime } from "openclaw/plugin-sdk/inbound-envelope";
10
11
  import { registerPluginHttpRoute, resolveWebhookPath } from "openclaw/plugin-sdk/webhook-ingress";
11
12
  import { asDateTimestampMs, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
13
+ import { formatInboundMediaUnavailableText } from "openclaw/plugin-sdk/channel-inbound";
12
14
  import { resolveStableChannelMessageIngress } from "openclaw/plugin-sdk/channel-ingress-runtime";
13
15
  import { waitForAbortSignal } from "openclaw/plugin-sdk/runtime-env";
14
16
  import { createHostedOutboundMediaStore } from "openclaw/plugin-sdk/outbound-media";
@@ -152,12 +154,13 @@ const ZALO_TEXT_LIMIT = 2e3;
152
154
  const DEFAULT_MEDIA_MAX_MB = 5;
153
155
  const WEBHOOK_CLEANUP_TIMEOUT_MS = 5e3;
154
156
  const ZALO_TYPING_TIMEOUT_MS = 5e3;
155
- let zaloWebhookModulePromise;
157
+ const UNIX_MILLISECONDS_THRESHOLD = 0xe8d4a51000;
156
158
  const hostedMediaRouteRefs = /* @__PURE__ */ new Map();
157
- function loadZaloWebhookModule() {
158
- zaloWebhookModulePromise ??= import("./monitor.webhook-B23mUx-y.js");
159
- return zaloWebhookModulePromise;
159
+ function resolveZaloTimestampMs(date) {
160
+ if (!date) return;
161
+ return date >= UNIX_MILLISECONDS_THRESHOLD ? date : date * 1e3;
160
162
  }
163
+ const loadZaloWebhookModule = createLazyRuntimeModule(() => import("./monitor.webhook-AYcIUBBP.js"));
161
164
  function releaseSharedHostedMediaRouteRef(routePath) {
162
165
  const current = hostedMediaRouteRefs.get(routePath);
163
166
  if (!current) return;
@@ -334,7 +337,7 @@ async function handleImageMessage(params) {
334
337
  const authorization = await authorizeZaloMessage({
335
338
  ...params,
336
339
  text: caption,
337
- mediaPath: photo_url ? "__pending_media__" : void 0,
340
+ mediaPath: "__pending_media__",
338
341
  mediaType: void 0
339
342
  });
340
343
  if (!authorization) return;
@@ -351,9 +354,15 @@ async function handleImageMessage(params) {
351
354
  } catch (err) {
352
355
  runtime.error?.(`[${account.accountId}] Failed to download Zalo image: ${String(err)}`);
353
356
  }
357
+ const agentBody = mediaPath ? authorization.rawBody : formatInboundMediaUnavailableText({
358
+ body: caption,
359
+ mediaPlaceholder: "<media:image>",
360
+ notice: "[zalo image attachment unavailable]"
361
+ });
354
362
  await processMessageWithPipeline({
355
363
  ...params,
356
364
  authorization,
365
+ agentBody,
357
366
  text: caption,
358
367
  mediaPath,
359
368
  mediaType
@@ -455,7 +464,7 @@ async function authorizeZaloMessage(params) {
455
464
  };
456
465
  }
457
466
  async function processMessageWithPipeline(params) {
458
- const { message, token, account, config, runtime, core, mediaPath, mediaType, statusSink, fetcher, authorization: authorizationOverride } = params;
467
+ const { message, token, account, config, runtime, core, mediaPath, mediaType, statusSink, fetcher, agentBody: agentBodyOverride, authorization: authorizationOverride } = params;
459
468
  const { message_id, date } = message;
460
469
  const authorization = authorizationOverride ?? await authorizeZaloMessage({
461
470
  ...params,
@@ -464,6 +473,7 @@ async function processMessageWithPipeline(params) {
464
473
  });
465
474
  if (!authorization) return;
466
475
  const { isGroup, chatId, senderId, senderName, rawBody, commandAuthorized } = authorization;
476
+ const agentBody = agentBodyOverride ?? rawBody;
467
477
  const { route, buildEnvelope } = resolveInboundRouteEnvelopeBuilderWithRuntime({
468
478
  cfg: config,
469
479
  channel: "zalo",
@@ -480,17 +490,18 @@ async function processMessageWithPipeline(params) {
480
490
  return;
481
491
  }
482
492
  const fromLabel = isGroup ? `group:${chatId}` : senderName || `user:${senderId}`;
493
+ const timestamp = resolveZaloTimestampMs(date);
483
494
  const { storePath, body } = buildEnvelope({
484
495
  channel: "Zalo",
485
496
  from: fromLabel,
486
- timestamp: date ? date * 1e3 : void 0,
487
- body: rawBody
497
+ timestamp,
498
+ body: agentBody
488
499
  });
489
500
  const ctxPayload = core.channel.inbound.buildContext({
490
501
  channel: "zalo",
491
502
  accountId: route.accountId,
492
503
  messageId: message_id,
493
- timestamp: date ? date * 1e3 : void 0,
504
+ timestamp,
494
505
  from: isGroup ? `zalo:group:${chatId}` : `zalo:${senderId}`,
495
506
  sender: {
496
507
  id: senderId,
@@ -509,7 +520,7 @@ async function processMessageWithPipeline(params) {
509
520
  reply: { to: `zalo:${chatId}` },
510
521
  message: {
511
522
  body,
512
- bodyForAgent: rawBody,
523
+ bodyForAgent: agentBody,
513
524
  rawBody,
514
525
  commandBody: rawBody
515
526
  },
@@ -618,15 +629,16 @@ async function deliverZaloReply(params) {
618
629
  }
619
630
  },
620
631
  sendMedia: async ({ mediaUrl, caption }) => {
632
+ const sendableMediaUrl = canHostMedia && webhookUrl && webhookPath ? await prepareHostedZaloMediaUrl({
633
+ mediaUrl,
634
+ webhookUrl,
635
+ webhookPath,
636
+ maxBytes: mediaMaxBytes,
637
+ proxyUrl
638
+ }) : mediaUrl;
621
639
  await sendPhoto(token, {
622
640
  chat_id: chatId,
623
- photo: canHostMedia && webhookUrl && webhookPath ? await prepareHostedZaloMediaUrl({
624
- mediaUrl,
625
- webhookUrl,
626
- webhookPath,
627
- maxBytes: mediaMaxBytes,
628
- proxyUrl
629
- }) : mediaUrl,
641
+ photo: sendableMediaUrl,
630
642
  caption
631
643
  }, fetcher);
632
644
  statusSink?.({ lastOutboundAt: Date.now() });
@@ -1,4 +1,4 @@
1
- import { B as registerWebhookTargetWithPluginRoute, I as readJsonWebhookBodyOrReject, K as resolveWebhookTargetWithAuthOrRejectSync, Q as withResolvedWebhookRequestPipeline, V as resolveClientIp, 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-CDwUY_-_.js";
1
+ import { B as registerWebhookTargetWithPluginRoute, I as readJsonWebhookBodyOrReject, K as resolveWebhookTargetWithAuthOrRejectSync, Q as withResolvedWebhookRequestPipeline, V as resolveClientIp, 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-CAA4ZtqF.js";
2
2
  import { createClaimableDedupe } from "openclaw/plugin-sdk/persistent-dedupe";
3
3
  import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
4
4
  //#region extensions/zalo/src/monitor.webhook.ts
@@ -29,9 +29,6 @@ function getZaloWebhookRateLimitStateSizeForTest() {
29
29
  function getZaloWebhookStatusCounterSizeForTest() {
30
30
  return webhookAnomalyTracker.size();
31
31
  }
32
- function timingSafeEquals(left, right) {
33
- return safeEqualSecret(left, right);
34
- }
35
32
  function buildReplayEventCacheKey(target, update) {
36
33
  const messageId = update.message?.message_id;
37
34
  if (!messageId) return null;
@@ -122,7 +119,7 @@ async function handleZaloWebhookRequest(req, res, processUpdate) {
122
119
  const target = resolveWebhookTargetWithAuthOrRejectSync({
123
120
  targets,
124
121
  res,
125
- isMatch: (entry) => timingSafeEquals(entry.secret, headerToken)
122
+ isMatch: (entry) => safeEqualSecret(entry.secret, headerToken)
126
123
  });
127
124
  if (!target) {
128
125
  recordWebhookStatus(targets[0]?.runtime, path, res.statusCode);
@@ -172,4 +169,4 @@ async function handleZaloWebhookRequest(req, res, processUpdate) {
172
169
  });
173
170
  }
174
171
  //#endregion
175
- export { ZaloRetryableWebhookError, clearZaloWebhookSecurityStateForTest, getZaloWebhookRateLimitStateSizeForTest, getZaloWebhookStatusCounterSizeForTest, handleZaloWebhookRequest, processZaloReplayGuardedUpdate, registerZaloWebhookTarget };
172
+ export { ZaloRetryableWebhookError, clearZaloWebhookSecurityStateForTest, getZaloWebhookRateLimitStateSizeForTest, getZaloWebhookStatusCounterSizeForTest, handleZaloWebhookRequest, registerZaloWebhookTarget };
@@ -1,7 +1,7 @@
1
1
  import { formatAllowFromLowercase as formatAllowFromLowercase$1, isNormalizedSenderAllowed } from "openclaw/plugin-sdk/allow-from";
2
2
  import { createChannelMessageReplyPipeline } from "openclaw/plugin-sdk/channel-outbound";
3
3
  import { PAIRING_APPROVED_MESSAGE, buildTokenChannelStatusSummary as buildTokenChannelStatusSummary$1 } from "openclaw/plugin-sdk/channel-status";
4
- import { deliverTextOrMediaReply as deliverTextOrMediaReply$1, isNumericTargetId as isNumericTargetId$1, sendPayloadWithChunkedTextAndMedia as sendPayloadWithChunkedTextAndMedia$1 } from "openclaw/plugin-sdk/reply-payload";
4
+ import { deliverTextOrMediaReply as deliverTextOrMediaReply$1, isNumericTargetId, sendPayloadWithChunkedTextAndMedia as sendPayloadWithChunkedTextAndMedia$1 } from "openclaw/plugin-sdk/reply-payload";
5
5
  import { buildBaseAccountStatusSnapshot } from "openclaw/plugin-sdk/status-helpers";
6
6
  import { chunkTextForOutbound as chunkTextForOutbound$1 } from "openclaw/plugin-sdk/text-chunking";
7
7
  import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, buildChannelConfigSchema, createDedupeCache, formatPairingApproveHint, jsonResult, normalizeAccountId as normalizeAccountId$1, readStringParam, resolveClientIp } from "openclaw/plugin-sdk/core";
@@ -20,4 +20,4 @@ const { setRuntime: setZaloRuntime, getRuntime: getZaloRuntime } = createPluginR
20
20
  errorMessage: "Zalo runtime not initialized"
21
21
  });
22
22
  //#endregion
23
- export { mergeAllowFromEntries$1 as A, registerWebhookTargetWithPluginRoute as B, formatAllowFromLowercase$1 as C, isNumericTargetId$1 as D, isNormalizedSenderAllowed as E, promptSingleChannelSecretInput$1 as F, resolveWebhookPath$1 as G, resolveDefaultGroupPolicy$1 as H, readJsonWebhookBodyOrReject as I, sendPayloadWithChunkedTextAndMedia$1 as J, resolveWebhookTargetWithAuthOrRejectSync as K, readStringParam as L, normalizeAccountId$1 as M, normalizeResolvedSecretInputString as N, jsonResult as O, normalizeSecretInputString as P, withResolvedWebhookRequestPipeline as Q, registerPluginHttpRoute$1 as R, deliverTextOrMediaReply$1 as S, hasConfiguredSecretInput$1 as T, resolveInboundRouteEnvelopeBuilderWithRuntime$1 as U, resolveClientIp as V, resolveOpenProviderRuntimeGroupPolicy$1 as W, waitForAbortSignal as X, setTopLevelChannelDmPolicyWithAllowFrom as Y, warnMissingProviderGroupPolicyFallbackOnce$1 as Z, createChannelMessageReplyPipeline as _, WEBHOOK_ANOMALY_COUNTER_DEFAULTS as a, createFixedWindowRateLimiter as b, applyAccountNameToChannelSection as c, buildBaseAccountStatusSnapshot as d, buildChannelConfigSchema as f, chunkTextForOutbound$1 as g, buildTokenChannelStatusSummary$1 as h, PAIRING_APPROVED_MESSAGE as i, migrateBaseNameToDefaultAccount as j, logTypingFailure$1 as k, applyBasicWebhookRequestGuards as l, buildSingleChannelSecretPromptState$1 as m, setZaloRuntime as n, WEBHOOK_RATE_LIMIT_DEFAULTS as o, buildSecretInputSchema as p, runSingleChannelSecretStep$1 as q, DEFAULT_ACCOUNT_ID$1 as r, addWildcardAllowFrom$1 as s, getZaloRuntime as t, applySetupAccountConfigPatch as u, createChannelPairingController$1 as v, formatPairingApproveHint as w, createWebhookAnomalyTracker as x, createDedupeCache as y, registerWebhookTarget as z };
23
+ export { mergeAllowFromEntries$1 as A, registerWebhookTargetWithPluginRoute as B, formatAllowFromLowercase$1 as C, isNumericTargetId as D, isNormalizedSenderAllowed as E, promptSingleChannelSecretInput$1 as F, resolveWebhookPath$1 as G, resolveDefaultGroupPolicy$1 as H, readJsonWebhookBodyOrReject as I, sendPayloadWithChunkedTextAndMedia$1 as J, resolveWebhookTargetWithAuthOrRejectSync as K, readStringParam as L, normalizeAccountId$1 as M, normalizeResolvedSecretInputString as N, jsonResult as O, normalizeSecretInputString as P, withResolvedWebhookRequestPipeline as Q, registerPluginHttpRoute$1 as R, deliverTextOrMediaReply$1 as S, hasConfiguredSecretInput$1 as T, resolveInboundRouteEnvelopeBuilderWithRuntime$1 as U, resolveClientIp as V, resolveOpenProviderRuntimeGroupPolicy$1 as W, waitForAbortSignal as X, setTopLevelChannelDmPolicyWithAllowFrom as Y, warnMissingProviderGroupPolicyFallbackOnce$1 as Z, createChannelMessageReplyPipeline as _, WEBHOOK_ANOMALY_COUNTER_DEFAULTS as a, createFixedWindowRateLimiter as b, applyAccountNameToChannelSection as c, buildBaseAccountStatusSnapshot as d, buildChannelConfigSchema as f, chunkTextForOutbound$1 as g, buildTokenChannelStatusSummary$1 as h, PAIRING_APPROVED_MESSAGE as i, migrateBaseNameToDefaultAccount as j, logTypingFailure$1 as k, applyBasicWebhookRequestGuards as l, buildSingleChannelSecretPromptState$1 as m, setZaloRuntime as n, WEBHOOK_RATE_LIMIT_DEFAULTS as o, buildSecretInputSchema as p, runSingleChannelSecretStep$1 as q, DEFAULT_ACCOUNT_ID$1 as r, addWildcardAllowFrom$1 as s, getZaloRuntime as t, applySetupAccountConfigPatch as u, createChannelPairingController$1 as v, formatPairingApproveHint as w, createWebhookAnomalyTracker as x, createDedupeCache as y, registerWebhookTarget as z };
@@ -1,2 +1,2 @@
1
- import { A as mergeAllowFromEntries, B as registerWebhookTargetWithPluginRoute, C as formatAllowFromLowercase, D as isNumericTargetId, E as isNormalizedSenderAllowed, F as promptSingleChannelSecretInput, G as resolveWebhookPath, H as resolveDefaultGroupPolicy, I as readJsonWebhookBodyOrReject, J as sendPayloadWithChunkedTextAndMedia, K as resolveWebhookTargetWithAuthOrRejectSync, L as readStringParam, M as normalizeAccountId, N as normalizeResolvedSecretInputString, O as jsonResult, P as normalizeSecretInputString, Q as withResolvedWebhookRequestPipeline, R as registerPluginHttpRoute, S as deliverTextOrMediaReply, T as hasConfiguredSecretInput, U as resolveInboundRouteEnvelopeBuilderWithRuntime, V as resolveClientIp, W as resolveOpenProviderRuntimeGroupPolicy, X as waitForAbortSignal, Y as setTopLevelChannelDmPolicyWithAllowFrom, Z as warnMissingProviderGroupPolicyFallbackOnce, _ as createChannelMessageReplyPipeline, a as WEBHOOK_ANOMALY_COUNTER_DEFAULTS, b as createFixedWindowRateLimiter, c as applyAccountNameToChannelSection, d as buildBaseAccountStatusSnapshot, f as buildChannelConfigSchema, g as chunkTextForOutbound, h as buildTokenChannelStatusSummary, i as PAIRING_APPROVED_MESSAGE, j as migrateBaseNameToDefaultAccount, k as logTypingFailure, l as applyBasicWebhookRequestGuards, m as buildSingleChannelSecretPromptState, n as setZaloRuntime, o as WEBHOOK_RATE_LIMIT_DEFAULTS, p as buildSecretInputSchema, q as runSingleChannelSecretStep, r as DEFAULT_ACCOUNT_ID, s as addWildcardAllowFrom, u as applySetupAccountConfigPatch, v as createChannelPairingController, w as formatPairingApproveHint, x as createWebhookAnomalyTracker, y as createDedupeCache, z as registerWebhookTarget } from "./runtime-api-CDwUY_-_.js";
1
+ import { A as mergeAllowFromEntries, B as registerWebhookTargetWithPluginRoute, C as formatAllowFromLowercase, D as isNumericTargetId, E as isNormalizedSenderAllowed, F as promptSingleChannelSecretInput, G as resolveWebhookPath, H as resolveDefaultGroupPolicy, I as readJsonWebhookBodyOrReject, J as sendPayloadWithChunkedTextAndMedia, K as resolveWebhookTargetWithAuthOrRejectSync, L as readStringParam, M as normalizeAccountId, N as normalizeResolvedSecretInputString, O as jsonResult, P as normalizeSecretInputString, Q as withResolvedWebhookRequestPipeline, R as registerPluginHttpRoute, S as deliverTextOrMediaReply, T as hasConfiguredSecretInput, U as resolveInboundRouteEnvelopeBuilderWithRuntime, V as resolveClientIp, W as resolveOpenProviderRuntimeGroupPolicy, X as waitForAbortSignal, Y as setTopLevelChannelDmPolicyWithAllowFrom, Z as warnMissingProviderGroupPolicyFallbackOnce, _ as createChannelMessageReplyPipeline, a as WEBHOOK_ANOMALY_COUNTER_DEFAULTS, b as createFixedWindowRateLimiter, c as applyAccountNameToChannelSection, d as buildBaseAccountStatusSnapshot, f as buildChannelConfigSchema, g as chunkTextForOutbound, h as buildTokenChannelStatusSummary, i as PAIRING_APPROVED_MESSAGE, j as migrateBaseNameToDefaultAccount, k as logTypingFailure, l as applyBasicWebhookRequestGuards, m as buildSingleChannelSecretPromptState, n as setZaloRuntime, o as WEBHOOK_RATE_LIMIT_DEFAULTS, p as buildSecretInputSchema, q as runSingleChannelSecretStep, r as DEFAULT_ACCOUNT_ID, s as addWildcardAllowFrom, u as applySetupAccountConfigPatch, v as createChannelPairingController, w as formatPairingApproveHint, x as createWebhookAnomalyTracker, y as createDedupeCache, z as registerWebhookTarget } from "./runtime-api-CAA4ZtqF.js";
2
2
  export { DEFAULT_ACCOUNT_ID, PAIRING_APPROVED_MESSAGE, WEBHOOK_ANOMALY_COUNTER_DEFAULTS, WEBHOOK_RATE_LIMIT_DEFAULTS, addWildcardAllowFrom, applyAccountNameToChannelSection, applyBasicWebhookRequestGuards, applySetupAccountConfigPatch, buildBaseAccountStatusSnapshot, buildChannelConfigSchema, buildSecretInputSchema, buildSingleChannelSecretPromptState, buildTokenChannelStatusSummary, chunkTextForOutbound, createChannelMessageReplyPipeline, createChannelPairingController, createDedupeCache, createFixedWindowRateLimiter, createWebhookAnomalyTracker, deliverTextOrMediaReply, formatAllowFromLowercase, formatPairingApproveHint, hasConfiguredSecretInput, isNormalizedSenderAllowed, isNumericTargetId, jsonResult, logTypingFailure, mergeAllowFromEntries, migrateBaseNameToDefaultAccount, normalizeAccountId, normalizeResolvedSecretInputString, normalizeSecretInputString, promptSingleChannelSecretInput, readJsonWebhookBodyOrReject, readStringParam, registerPluginHttpRoute, registerWebhookTarget, registerWebhookTargetWithPluginRoute, resolveClientIp, resolveDefaultGroupPolicy, resolveInboundRouteEnvelopeBuilderWithRuntime, resolveOpenProviderRuntimeGroupPolicy, resolveWebhookPath, resolveWebhookTargetWithAuthOrRejectSync, runSingleChannelSecretStep, sendPayloadWithChunkedTextAndMedia, setTopLevelChannelDmPolicyWithAllowFrom, setZaloRuntime, waitForAbortSignal, warnMissingProviderGroupPolicyFallbackOnce, withResolvedWebhookRequestPipeline };
@@ -1,6 +1,7 @@
1
1
  import { d as resolveZaloToken, u as resolveZaloAccount } from "./setup-core-fwCh0QUi.js";
2
2
  import { createMessageReceiptFromOutboundResults } from "openclaw/plugin-sdk/channel-outbound";
3
3
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
4
+ import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
4
5
  import { resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
5
6
  import { readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http";
6
7
  import { resolvePinnedHostnameWithPolicy } from "openclaw/plugin-sdk/ssrf-runtime";
@@ -11,6 +12,7 @@ import { makeProxyFetch } from "openclaw/plugin-sdk/fetch-runtime";
11
12
  * @see https://bot.zaloplatforms.com/docs
12
13
  */
13
14
  const ZALO_API_BASE = "https://bot-api.zaloplatforms.com";
15
+ const ZALO_API_URL_ENV = "ZALO_API_URL";
14
16
  const ZALO_MEDIA_SSRF_POLICY = {};
15
17
  var ZaloApiError = class extends Error {
16
18
  constructor(message, errorCode, description) {
@@ -24,11 +26,24 @@ var ZaloApiError = class extends Error {
24
26
  return this.errorCode === 408;
25
27
  }
26
28
  };
29
+ function resolveZaloApiUrl(apiUrl) {
30
+ const value = apiUrl === void 0 ? process.env[ZALO_API_URL_ENV]?.trim() ?? ZALO_API_BASE : apiUrl.trim();
31
+ if (!value) throw new Error(`${ZALO_API_URL_ENV} must not be empty.`);
32
+ let parsed;
33
+ try {
34
+ parsed = new URL(value);
35
+ } catch {
36
+ throw new Error(`${ZALO_API_URL_ENV} must be a valid URL.`);
37
+ }
38
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new Error(`${ZALO_API_URL_ENV} must use http:// or https://.`);
39
+ if (parsed.search || parsed.hash) throw new Error(`${ZALO_API_URL_ENV} must not include a query string or fragment.`);
40
+ return parsed.href.replace(/\/+$/u, "");
41
+ }
27
42
  /**
28
43
  * Call the Zalo Bot API
29
44
  */
30
45
  async function callZaloApi(method, token, body, options) {
31
- const url = `${ZALO_API_BASE}/bot${token}/${method}`;
46
+ const url = `${resolveZaloApiUrl(options?.apiUrl)}/bot${token}/${method}`;
32
47
  const controller = new AbortController();
33
48
  const requestTimeoutMs = options?.timeoutMs === void 0 ? void 0 : resolveTimerTimeoutMs(options.timeoutMs, 1);
34
49
  const timeoutId = requestTimeoutMs === void 0 ? void 0 : setTimeout(() => controller.abort(), requestTimeoutMs);
@@ -245,7 +260,7 @@ async function sendMessageZalo(chatId, text, options = {}) {
245
260
  kind: "text"
246
261
  }, () => sendMessage(context.token, {
247
262
  chat_id: context.chatId,
248
- text: text.slice(0, 2e3)
263
+ text: truncateUtf16Safe(text, 2e3)
249
264
  }, context.fetcher));
250
265
  }
251
266
  async function sendPhotoZalo(chatId, photoUrl, options = {}) {
@@ -266,7 +281,7 @@ async function sendPhotoZalo(chatId, photoUrl, options = {}) {
266
281
  }, () => (async () => sendPhoto(context.token, {
267
282
  chat_id: context.chatId,
268
283
  photo: photoUrl.trim(),
269
- caption: options.caption?.slice(0, 2e3)
284
+ caption: options.caption !== void 0 ? truncateUtf16Safe(options.caption, 2e3) : void 0
270
285
  }, context.fetcher))());
271
286
  }
272
287
  //#endregion
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@openclaw/zalo",
3
- "version": "2026.7.1-beta.1",
3
+ "version": "2026.7.1-beta.4",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@openclaw/zalo",
9
- "version": "2026.7.1-beta.1",
9
+ "version": "2026.7.1-beta.4",
10
10
  "dependencies": {
11
11
  "zod": "4.4.3"
12
12
  },
13
13
  "peerDependencies": {
14
- "openclaw": ">=2026.7.1-beta.1"
14
+ "openclaw": ">=2026.7.1-beta.4"
15
15
  },
16
16
  "peerDependenciesMeta": {
17
17
  "openclaw": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/zalo",
3
- "version": "2026.7.1-beta.1",
3
+ "version": "2026.7.1-beta.4",
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.7.1-beta.1"
11
+ "openclaw": ">=2026.7.1-beta.4"
12
12
  },
13
13
  "peerDependenciesMeta": {
14
14
  "openclaw": {
@@ -39,10 +39,10 @@
39
39
  "minHostVersion": ">=2026.4.10"
40
40
  },
41
41
  "compat": {
42
- "pluginApi": ">=2026.7.1-beta.1"
42
+ "pluginApi": ">=2026.7.1-beta.4"
43
43
  },
44
44
  "build": {
45
- "openclawVersion": "2026.7.1-beta.1"
45
+ "openclawVersion": "2026.7.1-beta.4"
46
46
  },
47
47
  "release": {
48
48
  "publishToClawHub": true,