@kodelyth/zalo 2026.5.39 → 2026.5.42

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 (97) hide show
  1. package/README.md +50 -0
  2. package/api.ts +8 -0
  3. package/channel-plugin-api.ts +1 -0
  4. package/contract-api.ts +5 -0
  5. package/dist/actions.runtime-C61oPfyd.js +5 -0
  6. package/dist/api.js +5 -0
  7. package/dist/channel-D8ylaEdN.js +367 -0
  8. package/dist/channel-plugin-api.js +2 -0
  9. package/dist/channel.runtime-sf-rx5n-.js +105 -0
  10. package/dist/contract-api.js +3 -0
  11. package/dist/group-access-DTQVR6Nd.js +15 -0
  12. package/dist/index.js +22 -0
  13. package/dist/monitor-CQ1bjGih.js +825 -0
  14. package/dist/monitor.webhook-CDxUxa9l.js +175 -0
  15. package/dist/runtime-api-CxXTp1Q2.js +23 -0
  16. package/dist/runtime-api.js +2 -0
  17. package/dist/secret-contract-CRFukr2n.js +87 -0
  18. package/dist/secret-contract-api.js +2 -0
  19. package/dist/send-CGAqdfSA.js +270 -0
  20. package/dist/setup-api.js +30 -0
  21. package/dist/setup-core-Dr75wK6l.js +287 -0
  22. package/dist/setup-entry.js +15 -0
  23. package/dist/setup-surface-C8zxrnzG.js +216 -0
  24. package/dist/test-api.js +2 -0
  25. package/index.test.ts +15 -0
  26. package/index.ts +20 -0
  27. package/klaw.plugin.json +2 -509
  28. package/package.json +4 -4
  29. package/runtime-api.test.ts +10 -0
  30. package/runtime-api.ts +71 -0
  31. package/secret-contract-api.ts +5 -0
  32. package/setup-api.ts +34 -0
  33. package/setup-entry.ts +13 -0
  34. package/src/accounts.test.ts +95 -0
  35. package/src/accounts.ts +65 -0
  36. package/src/actions.runtime.ts +5 -0
  37. package/src/actions.test.ts +32 -0
  38. package/src/actions.ts +62 -0
  39. package/src/api.test.ts +166 -0
  40. package/src/api.ts +265 -0
  41. package/src/approval-auth.test.ts +17 -0
  42. package/src/approval-auth.ts +25 -0
  43. package/src/channel.directory.test.ts +56 -0
  44. package/src/channel.runtime.ts +89 -0
  45. package/src/channel.startup.test.ts +121 -0
  46. package/src/channel.ts +309 -0
  47. package/src/config-schema.test.ts +30 -0
  48. package/src/config-schema.ts +29 -0
  49. package/src/group-access.ts +23 -0
  50. package/src/monitor-durable.test.ts +49 -0
  51. package/src/monitor-durable.ts +38 -0
  52. package/src/monitor.group-policy.test.ts +213 -0
  53. package/src/monitor.image.polling.test.ts +113 -0
  54. package/src/monitor.lifecycle.test.ts +194 -0
  55. package/src/monitor.pairing.lifecycle.test.ts +139 -0
  56. package/src/monitor.polling.media-reply.test.ts +433 -0
  57. package/src/monitor.reply-once.lifecycle.test.ts +178 -0
  58. package/src/monitor.ts +1009 -0
  59. package/src/monitor.types.ts +4 -0
  60. package/src/monitor.webhook.test.ts +808 -0
  61. package/src/monitor.webhook.ts +278 -0
  62. package/src/outbound-media.test.ts +186 -0
  63. package/src/outbound-media.ts +236 -0
  64. package/src/outbound-payload.contract.test.ts +143 -0
  65. package/src/probe.ts +45 -0
  66. package/src/proxy.ts +18 -0
  67. package/src/runtime-api.ts +71 -0
  68. package/src/runtime-support.ts +82 -0
  69. package/src/runtime.ts +9 -0
  70. package/src/secret-contract.ts +109 -0
  71. package/src/secret-input.ts +5 -0
  72. package/src/send.test.ts +150 -0
  73. package/src/send.ts +207 -0
  74. package/src/session-route.ts +32 -0
  75. package/src/setup-allow-from.ts +97 -0
  76. package/src/setup-core.ts +152 -0
  77. package/src/setup-status.test.ts +33 -0
  78. package/src/setup-surface.test.ts +193 -0
  79. package/src/setup-surface.ts +294 -0
  80. package/src/status-issues.test.ts +17 -0
  81. package/src/status-issues.ts +34 -0
  82. package/src/test-support/lifecycle-test-support.ts +456 -0
  83. package/src/test-support/monitor-mocks-test-support.ts +209 -0
  84. package/src/token.test.ts +92 -0
  85. package/src/token.ts +79 -0
  86. package/src/types.ts +50 -0
  87. package/test-api.ts +1 -0
  88. package/tsconfig.json +16 -0
  89. package/api.js +0 -7
  90. package/channel-plugin-api.js +0 -7
  91. package/contract-api.js +0 -7
  92. package/index.js +0 -7
  93. package/runtime-api.js +0 -7
  94. package/secret-contract-api.js +0 -7
  95. package/setup-api.js +0 -7
  96. package/setup-entry.js +0 -7
  97. package/test-api.js +0 -7
package/src/send.ts ADDED
@@ -0,0 +1,207 @@
1
+ import {
2
+ createMessageReceiptFromOutboundResults,
3
+ type MessageReceipt,
4
+ type MessageReceiptPartKind,
5
+ } from "klaw/plugin-sdk/channel-message";
6
+ import type { KlawConfig } from "klaw/plugin-sdk/config-contracts";
7
+ import { formatErrorMessage } from "klaw/plugin-sdk/error-runtime";
8
+ import { resolveZaloAccount } from "./accounts.js";
9
+ import type { ZaloFetch } from "./api.js";
10
+ import { sendMessage, sendPhoto } from "./api.js";
11
+ import { resolveZaloProxyFetch } from "./proxy.js";
12
+ import { resolveZaloToken } from "./token.js";
13
+
14
+ type ZaloSendOptions = {
15
+ token?: string;
16
+ accountId?: string;
17
+ cfg?: KlawConfig;
18
+ mediaUrl?: string;
19
+ caption?: string;
20
+ verbose?: boolean;
21
+ proxy?: string;
22
+ };
23
+
24
+ type ZaloSendResult = {
25
+ ok: boolean;
26
+ messageId?: string;
27
+ receipt: MessageReceipt;
28
+ error?: string;
29
+ };
30
+
31
+ function createZaloSendReceipt(params: {
32
+ messageId?: string;
33
+ chatId: string;
34
+ kind: MessageReceiptPartKind;
35
+ }): MessageReceipt {
36
+ const messageId = params.messageId?.trim();
37
+ return createMessageReceiptFromOutboundResults({
38
+ results: messageId
39
+ ? [
40
+ {
41
+ channel: "zalo",
42
+ messageId,
43
+ chatId: params.chatId,
44
+ },
45
+ ]
46
+ : [],
47
+ kind: params.kind,
48
+ });
49
+ }
50
+
51
+ function toZaloSendResult(
52
+ response: {
53
+ ok?: boolean;
54
+ result?: { message_id?: string };
55
+ },
56
+ params: { chatId: string; kind: MessageReceiptPartKind },
57
+ ): ZaloSendResult {
58
+ if (response.ok && response.result) {
59
+ return {
60
+ ok: true,
61
+ messageId: response.result.message_id,
62
+ receipt: createZaloSendReceipt({
63
+ messageId: response.result.message_id,
64
+ chatId: params.chatId,
65
+ kind: params.kind,
66
+ }),
67
+ };
68
+ }
69
+ return {
70
+ ok: false,
71
+ error: "Failed to send message",
72
+ receipt: createZaloSendReceipt({ chatId: params.chatId, kind: params.kind }),
73
+ };
74
+ }
75
+
76
+ async function runZaloSend(
77
+ failureMessage: string,
78
+ params: { chatId: string; kind: MessageReceiptPartKind },
79
+ send: () => Promise<{ ok?: boolean; result?: { message_id?: string } }>,
80
+ ): Promise<ZaloSendResult> {
81
+ try {
82
+ const result = toZaloSendResult(await send(), params);
83
+ return result.ok ? result : { ok: false, error: failureMessage, receipt: result.receipt };
84
+ } catch (err) {
85
+ return {
86
+ ok: false,
87
+ error: formatErrorMessage(err),
88
+ receipt: createZaloSendReceipt({ chatId: params.chatId, kind: params.kind }),
89
+ };
90
+ }
91
+ }
92
+
93
+ function resolveSendContext(options: ZaloSendOptions): {
94
+ token: string;
95
+ fetcher?: ZaloFetch;
96
+ } {
97
+ if (options.cfg) {
98
+ const account = resolveZaloAccount({
99
+ cfg: options.cfg,
100
+ accountId: options.accountId,
101
+ });
102
+ const token = options.token || account.token;
103
+ const proxy = options.proxy ?? account.config.proxy;
104
+ return { token, fetcher: resolveZaloProxyFetch(proxy) };
105
+ }
106
+
107
+ const token = options.token ?? resolveZaloToken(undefined, options.accountId).token;
108
+ const proxy = options.proxy;
109
+ return { token, fetcher: resolveZaloProxyFetch(proxy) };
110
+ }
111
+
112
+ function resolveValidatedSendContext(
113
+ chatId: string,
114
+ options: ZaloSendOptions,
115
+ ): { ok: true; chatId: string; token: string; fetcher?: ZaloFetch } | { ok: false; error: string } {
116
+ const { token, fetcher } = resolveSendContext(options);
117
+ if (!token) {
118
+ return { ok: false, error: "No Zalo bot token configured" };
119
+ }
120
+ const trimmedChatId = chatId?.trim();
121
+ if (!trimmedChatId) {
122
+ return { ok: false, error: "No chat_id provided" };
123
+ }
124
+ return { ok: true, chatId: trimmedChatId, token, fetcher };
125
+ }
126
+
127
+ function resolveSendContextOrFailure(
128
+ chatId: string,
129
+ options: ZaloSendOptions,
130
+ ):
131
+ | { context: { chatId: string; token: string; fetcher?: ZaloFetch } }
132
+ | { failure: ZaloSendResult } {
133
+ const context = resolveValidatedSendContext(chatId, options);
134
+ return context.ok
135
+ ? { context }
136
+ : {
137
+ failure: {
138
+ ok: false,
139
+ error: context.error,
140
+ receipt: createZaloSendReceipt({ chatId, kind: "unknown" }),
141
+ },
142
+ };
143
+ }
144
+
145
+ export async function sendMessageZalo(
146
+ chatId: string,
147
+ text: string,
148
+ options: ZaloSendOptions = {},
149
+ ): Promise<ZaloSendResult> {
150
+ const resolved = resolveSendContextOrFailure(chatId, options);
151
+ if ("failure" in resolved) {
152
+ return resolved.failure;
153
+ }
154
+ const { context } = resolved;
155
+
156
+ if (options.mediaUrl) {
157
+ return sendPhotoZalo(context.chatId, options.mediaUrl, {
158
+ ...options,
159
+ token: context.token,
160
+ caption: text || options.caption,
161
+ });
162
+ }
163
+
164
+ return await runZaloSend("Failed to send message", { chatId: context.chatId, kind: "text" }, () =>
165
+ sendMessage(
166
+ context.token,
167
+ {
168
+ chat_id: context.chatId,
169
+ text: text.slice(0, 2000),
170
+ },
171
+ context.fetcher,
172
+ ),
173
+ );
174
+ }
175
+
176
+ export async function sendPhotoZalo(
177
+ chatId: string,
178
+ photoUrl: string,
179
+ options: ZaloSendOptions = {},
180
+ ): Promise<ZaloSendResult> {
181
+ const resolved = resolveSendContextOrFailure(chatId, options);
182
+ if ("failure" in resolved) {
183
+ return resolved.failure;
184
+ }
185
+ const { context } = resolved;
186
+
187
+ if (!photoUrl?.trim()) {
188
+ return {
189
+ ok: false,
190
+ error: "No photo URL provided",
191
+ receipt: createZaloSendReceipt({ chatId: context.chatId, kind: "media" }),
192
+ };
193
+ }
194
+
195
+ return await runZaloSend("Failed to send photo", { chatId: context.chatId, kind: "media" }, () =>
196
+ (async () =>
197
+ sendPhoto(
198
+ context.token,
199
+ {
200
+ chat_id: context.chatId,
201
+ photo: photoUrl.trim(),
202
+ caption: options.caption?.slice(0, 2000),
203
+ },
204
+ context.fetcher,
205
+ ))(),
206
+ );
207
+ }
@@ -0,0 +1,32 @@
1
+ import {
2
+ buildChannelOutboundSessionRoute,
3
+ stripChannelTargetPrefix,
4
+ stripTargetKindPrefix,
5
+ type ChannelOutboundSessionRouteParams,
6
+ } from "klaw/plugin-sdk/core";
7
+ import { normalizeLowercaseStringOrEmpty } from "klaw/plugin-sdk/string-coerce-runtime";
8
+
9
+ export function resolveZaloOutboundSessionRoute(params: ChannelOutboundSessionRouteParams) {
10
+ const trimmed = stripChannelTargetPrefix(params.target, "zalo", "zl");
11
+ if (!trimmed) {
12
+ return null;
13
+ }
14
+ const isGroup = normalizeLowercaseStringOrEmpty(trimmed).startsWith("group:");
15
+ const peerId = stripTargetKindPrefix(trimmed);
16
+ if (!peerId) {
17
+ return null;
18
+ }
19
+ return buildChannelOutboundSessionRoute({
20
+ cfg: params.cfg,
21
+ agentId: params.agentId,
22
+ channel: "zalo",
23
+ accountId: params.accountId,
24
+ peer: {
25
+ kind: isGroup ? "group" : "direct",
26
+ id: peerId,
27
+ },
28
+ chatType: isGroup ? "group" : "direct",
29
+ from: isGroup ? `zalo:group:${peerId}` : `zalo:${peerId}`,
30
+ to: `zalo:${peerId}`,
31
+ });
32
+ }
@@ -0,0 +1,97 @@
1
+ import {
2
+ DEFAULT_ACCOUNT_ID,
3
+ createSetupTranslator,
4
+ formatDocsLink,
5
+ mergeAllowFromEntries,
6
+ type ChannelSetupDmPolicy,
7
+ type ChannelSetupWizard,
8
+ type KlawConfig,
9
+ } from "klaw/plugin-sdk/setup";
10
+ import { resolveDefaultZaloAccountId, resolveZaloAccount } from "./accounts.js";
11
+
12
+ const t = createSetupTranslator();
13
+
14
+ type ZaloAccountSetupConfig = {
15
+ enabled?: boolean;
16
+ };
17
+
18
+ export async function noteZaloTokenHelp(
19
+ prompter: Parameters<NonNullable<ChannelSetupWizard["finalize"]>>[0]["prompter"],
20
+ ): Promise<void> {
21
+ await prompter.note(
22
+ [
23
+ t("wizard.zalo.helpOpenPlatform"),
24
+ t("wizard.zalo.helpCreateBot"),
25
+ t("wizard.zalo.helpTokenFormat"),
26
+ t("wizard.zalo.helpEnvTip"),
27
+ `Docs: ${formatDocsLink("/channels/zalo", "zalo")}`,
28
+ ].join("\n"),
29
+ t("wizard.zalo.botTokenTitle"),
30
+ );
31
+ }
32
+
33
+ export async function promptZaloAllowFrom(params: {
34
+ cfg: KlawConfig;
35
+ prompter: Parameters<NonNullable<ChannelSetupDmPolicy["promptAllowFrom"]>>[0]["prompter"];
36
+ accountId?: string;
37
+ }): Promise<KlawConfig> {
38
+ const { cfg, prompter } = params;
39
+ const accountId = params.accountId ?? resolveDefaultZaloAccountId(cfg);
40
+ const resolved = resolveZaloAccount({ cfg, accountId });
41
+ const existingAllowFrom = resolved.config.allowFrom ?? [];
42
+ const entry = await prompter.text({
43
+ message: t("wizard.zalo.allowFromPrompt"),
44
+ placeholder: "123456789",
45
+ initialValue: existingAllowFrom[0] ? String(existingAllowFrom[0]) : undefined,
46
+ validate: (value) => {
47
+ const raw = (value ?? "").trim();
48
+ if (!raw) {
49
+ return t("common.required");
50
+ }
51
+ if (!/^\d+$/.test(raw)) {
52
+ return t("wizard.zalo.allowFromNumeric");
53
+ }
54
+ return undefined;
55
+ },
56
+ });
57
+ const normalized = entry.trim();
58
+ const unique = mergeAllowFromEntries(existingAllowFrom, [normalized]);
59
+
60
+ if (accountId === DEFAULT_ACCOUNT_ID) {
61
+ return {
62
+ ...cfg,
63
+ channels: {
64
+ ...cfg.channels,
65
+ zalo: {
66
+ ...cfg.channels?.zalo,
67
+ enabled: true,
68
+ dmPolicy: "allowlist",
69
+ allowFrom: unique,
70
+ },
71
+ },
72
+ } as KlawConfig;
73
+ }
74
+
75
+ const currentAccount = cfg.channels?.zalo?.accounts?.[accountId] as
76
+ | ZaloAccountSetupConfig
77
+ | undefined;
78
+ return {
79
+ ...cfg,
80
+ channels: {
81
+ ...cfg.channels,
82
+ zalo: {
83
+ ...cfg.channels?.zalo,
84
+ enabled: true,
85
+ accounts: {
86
+ ...cfg.channels?.zalo?.accounts,
87
+ [accountId]: {
88
+ ...currentAccount,
89
+ enabled: currentAccount?.enabled ?? true,
90
+ dmPolicy: "allowlist",
91
+ allowFrom: unique,
92
+ },
93
+ },
94
+ },
95
+ },
96
+ } as KlawConfig;
97
+ }
@@ -0,0 +1,152 @@
1
+ import {
2
+ addWildcardAllowFrom,
3
+ createDelegatedSetupWizardProxy,
4
+ createPatchedAccountSetupAdapter,
5
+ createSetupInputPresenceValidator,
6
+ DEFAULT_ACCOUNT_ID,
7
+ normalizeAccountId,
8
+ createSetupTranslator,
9
+ type ChannelSetupDmPolicy,
10
+ type ChannelSetupWizard,
11
+ } from "klaw/plugin-sdk/setup";
12
+ import { resolveDefaultZaloAccountId, resolveZaloAccount } from "./accounts.js";
13
+ import { promptZaloAllowFrom } from "./setup-allow-from.js";
14
+
15
+ const t = createSetupTranslator();
16
+
17
+ const channel = "zalo" as const;
18
+
19
+ type ZaloAccountSetupConfig = {
20
+ enabled?: boolean;
21
+ dmPolicy?: string;
22
+ allowFrom?: Array<string | number> | ReadonlyArray<string | number>;
23
+ };
24
+
25
+ export const zaloSetupAdapter = createPatchedAccountSetupAdapter({
26
+ channelKey: channel,
27
+ validateInput: createSetupInputPresenceValidator({
28
+ defaultAccountOnlyEnvError: "ZALO_BOT_TOKEN can only be used for the default account.",
29
+ whenNotUseEnv: [
30
+ {
31
+ someOf: ["token", "tokenFile"],
32
+ message: "Zalo requires token or --token-file (or --use-env).",
33
+ },
34
+ ],
35
+ }),
36
+ buildPatch: (input) =>
37
+ input.useEnv
38
+ ? {}
39
+ : input.tokenFile
40
+ ? { tokenFile: input.tokenFile }
41
+ : input.token
42
+ ? { botToken: input.token }
43
+ : {},
44
+ });
45
+
46
+ export const zaloDmPolicy: ChannelSetupDmPolicy = {
47
+ label: "Zalo",
48
+ channel,
49
+ policyKey: "channels.zalo.dmPolicy",
50
+ allowFromKey: "channels.zalo.allowFrom",
51
+ resolveConfigKeys: (cfg, accountId) =>
52
+ (accountId ?? resolveDefaultZaloAccountId(cfg)) !== DEFAULT_ACCOUNT_ID
53
+ ? {
54
+ policyKey: `channels.zalo.accounts.${accountId ?? resolveDefaultZaloAccountId(cfg)}.dmPolicy`,
55
+ allowFromKey: `channels.zalo.accounts.${accountId ?? resolveDefaultZaloAccountId(cfg)}.allowFrom`,
56
+ }
57
+ : {
58
+ policyKey: "channels.zalo.dmPolicy",
59
+ allowFromKey: "channels.zalo.allowFrom",
60
+ },
61
+ getCurrent: (cfg, accountId) =>
62
+ resolveZaloAccount({
63
+ cfg: cfg,
64
+ accountId: accountId ?? resolveDefaultZaloAccountId(cfg),
65
+ }).config.dmPolicy ?? "pairing",
66
+ setPolicy: (cfg, policy, accountId) => {
67
+ const resolvedAccountId =
68
+ accountId && normalizeAccountId(accountId)
69
+ ? (normalizeAccountId(accountId) ?? DEFAULT_ACCOUNT_ID)
70
+ : resolveDefaultZaloAccountId(cfg);
71
+ const resolved = resolveZaloAccount({
72
+ cfg: cfg,
73
+ accountId: resolvedAccountId,
74
+ });
75
+ if (resolvedAccountId === DEFAULT_ACCOUNT_ID) {
76
+ return {
77
+ ...cfg,
78
+ channels: {
79
+ ...cfg.channels,
80
+ zalo: {
81
+ ...cfg.channels?.zalo,
82
+ enabled: true,
83
+ dmPolicy: policy,
84
+ ...(policy === "open"
85
+ ? { allowFrom: addWildcardAllowFrom(resolved.config.allowFrom) }
86
+ : {}),
87
+ },
88
+ },
89
+ };
90
+ }
91
+ const currentAccount = cfg.channels?.zalo?.accounts?.[resolvedAccountId] as
92
+ | ZaloAccountSetupConfig
93
+ | undefined;
94
+ return {
95
+ ...cfg,
96
+ channels: {
97
+ ...cfg.channels,
98
+ zalo: {
99
+ ...cfg.channels?.zalo,
100
+ enabled: true,
101
+ accounts: {
102
+ ...cfg.channels?.zalo?.accounts,
103
+ [resolvedAccountId]: {
104
+ ...currentAccount,
105
+ enabled: currentAccount?.enabled ?? true,
106
+ dmPolicy: policy,
107
+ ...(policy === "open"
108
+ ? { allowFrom: addWildcardAllowFrom(resolved.config.allowFrom) }
109
+ : {}),
110
+ },
111
+ },
112
+ },
113
+ },
114
+ };
115
+ },
116
+ promptAllowFrom: async ({ cfg, prompter, accountId }) =>
117
+ promptZaloAllowFrom({
118
+ cfg,
119
+ prompter,
120
+ accountId: accountId ?? resolveDefaultZaloAccountId(cfg),
121
+ }),
122
+ };
123
+
124
+ export function createZaloSetupWizardProxy(
125
+ loadWizard: () => Promise<ChannelSetupWizard>,
126
+ ): ChannelSetupWizard {
127
+ return createDelegatedSetupWizardProxy({
128
+ channel,
129
+ loadWizard,
130
+ status: {
131
+ configuredLabel: t("wizard.channels.statusConfigured"),
132
+ unconfiguredLabel: t("wizard.channels.statusNeedsToken"),
133
+ configuredHint: t("wizard.channels.statusRecommendedConfigured"),
134
+ unconfiguredHint: t("wizard.channels.statusRecommendedNewcomerFriendly"),
135
+ configuredScore: 1,
136
+ unconfiguredScore: 10,
137
+ },
138
+ credentials: [],
139
+ delegateFinalize: true,
140
+ dmPolicy: zaloDmPolicy,
141
+ disable: (cfg) => ({
142
+ ...cfg,
143
+ channels: {
144
+ ...cfg.channels,
145
+ zalo: {
146
+ ...cfg.channels?.zalo,
147
+ enabled: false,
148
+ },
149
+ },
150
+ }),
151
+ });
152
+ }
@@ -0,0 +1,33 @@
1
+ import { createPluginSetupWizardStatus } from "klaw/plugin-sdk/plugin-test-runtime";
2
+ import { describe, expect, it } from "vitest";
3
+ import type { KlawConfig } from "../runtime-api.js";
4
+ import { zaloSetupWizard } from "./setup-surface.js";
5
+
6
+ const zaloGetStatus = createPluginSetupWizardStatus({
7
+ id: "zalo",
8
+ meta: {
9
+ label: "Zalo",
10
+ },
11
+ setupWizard: zaloSetupWizard,
12
+ } as never);
13
+
14
+ describe("zalo setup wizard status", () => {
15
+ it("treats SecretRef botToken as configured", async () => {
16
+ const status = await zaloGetStatus({
17
+ cfg: {
18
+ channels: {
19
+ zalo: {
20
+ botToken: {
21
+ source: "env",
22
+ provider: "default",
23
+ id: "ZALO_BOT_TOKEN",
24
+ },
25
+ },
26
+ },
27
+ } as KlawConfig,
28
+ accountOverrides: {},
29
+ });
30
+
31
+ expect(status.configured).toBe(true);
32
+ });
33
+ });