@openclaw/zalo 2026.3.13 → 2026.5.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/README.md +1 -1
- package/api.ts +9 -0
- package/channel-plugin-api.ts +1 -0
- package/contract-api.ts +5 -0
- package/index.test.ts +15 -0
- package/index.ts +16 -13
- package/openclaw.plugin.json +514 -1
- package/package.json +31 -5
- package/runtime-api.test.ts +17 -0
- package/runtime-api.ts +75 -0
- package/secret-contract-api.ts +5 -0
- package/setup-api.ts +34 -0
- package/setup-entry.ts +13 -0
- package/src/accounts.test.ts +70 -0
- package/src/accounts.ts +19 -19
- package/src/actions.runtime.ts +5 -0
- package/src/actions.test.ts +32 -0
- package/src/actions.ts +20 -14
- package/src/api.test.ts +93 -2
- package/src/api.ts +29 -2
- package/src/approval-auth.test.ts +17 -0
- package/src/approval-auth.ts +25 -0
- package/src/channel.directory.test.ts +19 -6
- package/src/channel.runtime.ts +93 -0
- package/src/channel.startup.test.ts +26 -19
- package/src/channel.ts +228 -336
- package/src/config-schema.ts +3 -3
- package/src/group-access.ts +4 -3
- package/src/monitor.group-policy.test.ts +0 -12
- package/src/monitor.image.polling.test.ts +110 -0
- package/src/monitor.lifecycle.test.ts +41 -22
- package/src/monitor.pairing.lifecycle.test.ts +141 -0
- package/src/monitor.polling.media-reply.test.ts +425 -0
- package/src/monitor.reply-once.lifecycle.test.ts +171 -0
- package/src/monitor.ts +460 -206
- package/src/monitor.types.ts +4 -0
- package/src/monitor.webhook.test.ts +392 -62
- package/src/monitor.webhook.ts +73 -36
- package/src/outbound-media.test.ts +182 -0
- package/src/outbound-media.ts +241 -0
- package/src/outbound-payload.contract.test.ts +45 -0
- package/src/probe.ts +1 -1
- package/src/proxy.ts +1 -1
- package/src/runtime-api.ts +75 -0
- package/src/runtime-support.ts +91 -0
- package/src/runtime.ts +6 -3
- package/src/secret-contract.ts +109 -0
- package/src/secret-input.ts +1 -9
- package/src/send.test.ts +120 -0
- package/src/send.ts +15 -13
- package/src/session-route.ts +32 -0
- package/src/setup-allow-from.ts +94 -0
- package/src/setup-core.ts +149 -0
- package/src/{onboarding.status.test.ts → setup-status.test.ts} +13 -4
- package/src/setup-surface.test.ts +175 -0
- package/src/{onboarding.ts → setup-surface.ts} +59 -177
- package/src/status-issues.test.ts +2 -14
- package/src/status-issues.ts +8 -2
- package/src/test-support/lifecycle-test-support.ts +413 -0
- package/src/test-support/monitor-mocks-test-support.ts +209 -0
- package/src/token.test.ts +15 -0
- package/src/token.ts +8 -17
- package/src/types.ts +2 -2
- package/test-api.ts +1 -0
- package/tsconfig.json +16 -0
- package/CHANGELOG.md +0 -101
- package/src/channel.sendpayload.test.ts +0 -44
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { adaptScopedAccountAccessor } from "openclaw/plugin-sdk/channel-config-helpers";
|
|
2
|
+
import {
|
|
3
|
+
createPluginSetupWizardConfigure,
|
|
4
|
+
createTestWizardPrompter,
|
|
5
|
+
runSetupWizardConfigure,
|
|
6
|
+
} from "openclaw/plugin-sdk/plugin-test-runtime";
|
|
7
|
+
import type { WizardPrompter } from "openclaw/plugin-sdk/plugin-test-runtime";
|
|
8
|
+
import { describe, expect, it, vi } from "vitest";
|
|
9
|
+
import type { OpenClawConfig } from "../runtime-api.js";
|
|
10
|
+
import { listZaloAccountIds, resolveDefaultZaloAccountId, resolveZaloAccount } from "./accounts.js";
|
|
11
|
+
import { zaloDmPolicy } from "./setup-core.js";
|
|
12
|
+
import { zaloSetupAdapter, zaloSetupWizard } from "./setup-surface.js";
|
|
13
|
+
|
|
14
|
+
const zaloSetupPlugin = {
|
|
15
|
+
id: "zalo",
|
|
16
|
+
meta: {
|
|
17
|
+
id: "zalo",
|
|
18
|
+
label: "Zalo",
|
|
19
|
+
selectionLabel: "Zalo (Bot API)",
|
|
20
|
+
docsPath: "/channels/zalo",
|
|
21
|
+
blurb: "Vietnam-focused messaging platform with Bot API.",
|
|
22
|
+
},
|
|
23
|
+
capabilities: {
|
|
24
|
+
chatTypes: ["direct", "group"] as Array<"direct" | "group">,
|
|
25
|
+
},
|
|
26
|
+
config: {
|
|
27
|
+
listAccountIds: (cfg: unknown) => listZaloAccountIds(cfg as never),
|
|
28
|
+
defaultAccountId: (cfg: unknown) => resolveDefaultZaloAccountId(cfg as never),
|
|
29
|
+
resolveAccount: adaptScopedAccountAccessor(resolveZaloAccount),
|
|
30
|
+
},
|
|
31
|
+
setup: zaloSetupAdapter,
|
|
32
|
+
setupWizard: zaloSetupWizard,
|
|
33
|
+
} as const;
|
|
34
|
+
|
|
35
|
+
const zaloConfigure = createPluginSetupWizardConfigure(zaloSetupPlugin);
|
|
36
|
+
|
|
37
|
+
describe("zalo setup wizard", () => {
|
|
38
|
+
it("configures a polling token flow", async () => {
|
|
39
|
+
const prompter = createTestWizardPrompter({
|
|
40
|
+
select: vi.fn(async () => "plaintext") as WizardPrompter["select"],
|
|
41
|
+
text: vi.fn(async ({ message }: { message: string }) => {
|
|
42
|
+
if (message === "Enter Zalo bot token") {
|
|
43
|
+
return "12345689:abc-xyz";
|
|
44
|
+
}
|
|
45
|
+
throw new Error(`Unexpected prompt: ${message}`);
|
|
46
|
+
}) as WizardPrompter["text"],
|
|
47
|
+
confirm: vi.fn(async ({ message }: { message: string }) => {
|
|
48
|
+
if (message === "Use webhook mode for Zalo?") {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
}),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const result = await runSetupWizardConfigure({
|
|
56
|
+
configure: zaloConfigure,
|
|
57
|
+
cfg: {} as OpenClawConfig,
|
|
58
|
+
prompter,
|
|
59
|
+
options: { secretInputMode: "plaintext" as const },
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
expect(result.accountId).toBe("default");
|
|
63
|
+
expect(result.cfg.channels?.zalo?.enabled).toBe(true);
|
|
64
|
+
expect(result.cfg.channels?.zalo?.botToken).toBe("12345689:abc-xyz");
|
|
65
|
+
expect(result.cfg.channels?.zalo?.webhookUrl).toBeUndefined();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("reads the named-account DM policy instead of the channel root", () => {
|
|
69
|
+
expect(
|
|
70
|
+
zaloDmPolicy.getCurrent(
|
|
71
|
+
{
|
|
72
|
+
channels: {
|
|
73
|
+
zalo: {
|
|
74
|
+
dmPolicy: "disabled",
|
|
75
|
+
accounts: {
|
|
76
|
+
work: {
|
|
77
|
+
botToken: "12345689:abc-xyz",
|
|
78
|
+
dmPolicy: "allowlist",
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
} as OpenClawConfig,
|
|
84
|
+
"work",
|
|
85
|
+
),
|
|
86
|
+
).toBe("allowlist");
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("reports account-scoped config keys for named accounts", () => {
|
|
90
|
+
expect(zaloDmPolicy.resolveConfigKeys?.({} as OpenClawConfig, "work")).toEqual({
|
|
91
|
+
policyKey: "channels.zalo.accounts.work.dmPolicy",
|
|
92
|
+
allowFromKey: "channels.zalo.accounts.work.allowFrom",
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("uses configured defaultAccount for omitted DM policy account context", () => {
|
|
97
|
+
const cfg = {
|
|
98
|
+
channels: {
|
|
99
|
+
zalo: {
|
|
100
|
+
defaultAccount: "work",
|
|
101
|
+
dmPolicy: "disabled",
|
|
102
|
+
allowFrom: ["123456789"],
|
|
103
|
+
accounts: {
|
|
104
|
+
work: {
|
|
105
|
+
botToken: "12345689:abc-xyz",
|
|
106
|
+
dmPolicy: "allowlist",
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
} as OpenClawConfig;
|
|
112
|
+
|
|
113
|
+
expect(zaloDmPolicy.getCurrent(cfg)).toBe("allowlist");
|
|
114
|
+
expect(zaloDmPolicy.resolveConfigKeys?.(cfg)).toEqual({
|
|
115
|
+
policyKey: "channels.zalo.accounts.work.dmPolicy",
|
|
116
|
+
allowFromKey: "channels.zalo.accounts.work.allowFrom",
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const next = zaloDmPolicy.setPolicy(cfg, "open");
|
|
120
|
+
expect(next.channels?.zalo?.dmPolicy).toBe("disabled");
|
|
121
|
+
const workAccount = next.channels?.zalo?.accounts?.work as
|
|
122
|
+
| { dmPolicy?: string; allowFrom?: Array<string | number> }
|
|
123
|
+
| undefined;
|
|
124
|
+
expect(workAccount?.dmPolicy).toBe("open");
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('writes open policy state to the named account and preserves inherited allowFrom with "*"', () => {
|
|
128
|
+
const next = zaloDmPolicy.setPolicy(
|
|
129
|
+
{
|
|
130
|
+
channels: {
|
|
131
|
+
zalo: {
|
|
132
|
+
allowFrom: ["123456789"],
|
|
133
|
+
accounts: {
|
|
134
|
+
work: {
|
|
135
|
+
botToken: "12345689:abc-xyz",
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
} as OpenClawConfig,
|
|
141
|
+
"open",
|
|
142
|
+
"work",
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
expect(next.channels?.zalo?.dmPolicy).toBeUndefined();
|
|
146
|
+
const workAccount = next.channels?.zalo?.accounts?.work as
|
|
147
|
+
| { dmPolicy?: string; allowFrom?: Array<string | number> }
|
|
148
|
+
| undefined;
|
|
149
|
+
expect(workAccount?.dmPolicy).toBe("open");
|
|
150
|
+
expect(workAccount?.allowFrom).toEqual(["123456789", "*"]);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("uses configured defaultAccount for omitted setup configured state", async () => {
|
|
154
|
+
const configured = await zaloSetupWizard.status.resolveConfigured({
|
|
155
|
+
cfg: {
|
|
156
|
+
channels: {
|
|
157
|
+
zalo: {
|
|
158
|
+
defaultAccount: "work",
|
|
159
|
+
botToken: "root-token",
|
|
160
|
+
accounts: {
|
|
161
|
+
alerts: {
|
|
162
|
+
botToken: "alerts-token",
|
|
163
|
+
},
|
|
164
|
+
work: {
|
|
165
|
+
botToken: "",
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
} as OpenClawConfig,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
expect(configured).toBe(false);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
@@ -1,38 +1,22 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ChannelOnboardingAdapter,
|
|
3
|
-
ChannelOnboardingDmPolicy,
|
|
4
|
-
OpenClawConfig,
|
|
5
|
-
SecretInput,
|
|
6
|
-
WizardPrompter,
|
|
7
|
-
} from "openclaw/plugin-sdk/zalo";
|
|
8
1
|
import {
|
|
9
2
|
buildSingleChannelSecretPromptState,
|
|
3
|
+
createStandardChannelSetupStatus,
|
|
10
4
|
DEFAULT_ACCOUNT_ID,
|
|
11
5
|
hasConfiguredSecretInput,
|
|
12
|
-
mergeAllowFromEntries,
|
|
13
|
-
normalizeAccountId,
|
|
14
6
|
promptSingleChannelSecretInput,
|
|
15
7
|
runSingleChannelSecretStep,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
8
|
+
type ChannelSetupWizard,
|
|
9
|
+
type OpenClawConfig,
|
|
10
|
+
type SecretInput,
|
|
11
|
+
} from "openclaw/plugin-sdk/setup";
|
|
12
|
+
import { resolveZaloAccount } from "./accounts.js";
|
|
13
|
+
import { noteZaloTokenHelp, promptZaloAllowFrom } from "./setup-allow-from.js";
|
|
14
|
+
import { zaloDmPolicy } from "./setup-core.js";
|
|
20
15
|
|
|
21
16
|
const channel = "zalo" as const;
|
|
22
17
|
|
|
23
18
|
type UpdateMode = "polling" | "webhook";
|
|
24
19
|
|
|
25
|
-
function setZaloDmPolicy(
|
|
26
|
-
cfg: OpenClawConfig,
|
|
27
|
-
dmPolicy: "pairing" | "allowlist" | "open" | "disabled",
|
|
28
|
-
) {
|
|
29
|
-
return setTopLevelChannelDmPolicyWithAllowFrom({
|
|
30
|
-
cfg,
|
|
31
|
-
channel: "zalo",
|
|
32
|
-
dmPolicy,
|
|
33
|
-
}) as OpenClawConfig;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
20
|
function setZaloUpdateMode(
|
|
37
21
|
cfg: OpenClawConfig,
|
|
38
22
|
accountId: string,
|
|
@@ -108,108 +92,22 @@ function setZaloUpdateMode(
|
|
|
108
92
|
} as OpenClawConfig;
|
|
109
93
|
}
|
|
110
94
|
|
|
111
|
-
|
|
112
|
-
await prompter.note(
|
|
113
|
-
[
|
|
114
|
-
"1) Open Zalo Bot Platform: https://bot.zaloplatforms.com",
|
|
115
|
-
"2) Create a bot and get the token",
|
|
116
|
-
"3) Token looks like 12345689:abc-xyz",
|
|
117
|
-
"Tip: you can also set ZALO_BOT_TOKEN in your env.",
|
|
118
|
-
"Docs: https://docs.openclaw.ai/channels/zalo",
|
|
119
|
-
].join("\n"),
|
|
120
|
-
"Zalo bot token",
|
|
121
|
-
);
|
|
122
|
-
}
|
|
95
|
+
export { zaloSetupAdapter } from "./setup-core.js";
|
|
123
96
|
|
|
124
|
-
|
|
125
|
-
cfg: OpenClawConfig;
|
|
126
|
-
prompter: WizardPrompter;
|
|
127
|
-
accountId: string;
|
|
128
|
-
}): Promise<OpenClawConfig> {
|
|
129
|
-
const { cfg, prompter, accountId } = params;
|
|
130
|
-
const resolved = resolveZaloAccount({ cfg, accountId });
|
|
131
|
-
const existingAllowFrom = resolved.config.allowFrom ?? [];
|
|
132
|
-
const entry = await prompter.text({
|
|
133
|
-
message: "Zalo allowFrom (user id)",
|
|
134
|
-
placeholder: "123456789",
|
|
135
|
-
initialValue: existingAllowFrom[0] ? String(existingAllowFrom[0]) : undefined,
|
|
136
|
-
validate: (value) => {
|
|
137
|
-
const raw = String(value ?? "").trim();
|
|
138
|
-
if (!raw) {
|
|
139
|
-
return "Required";
|
|
140
|
-
}
|
|
141
|
-
if (!/^\d+$/.test(raw)) {
|
|
142
|
-
return "Use a numeric Zalo user id";
|
|
143
|
-
}
|
|
144
|
-
return undefined;
|
|
145
|
-
},
|
|
146
|
-
});
|
|
147
|
-
const normalized = String(entry).trim();
|
|
148
|
-
const unique = mergeAllowFromEntries(existingAllowFrom, [normalized]);
|
|
149
|
-
|
|
150
|
-
if (accountId === DEFAULT_ACCOUNT_ID) {
|
|
151
|
-
return {
|
|
152
|
-
...cfg,
|
|
153
|
-
channels: {
|
|
154
|
-
...cfg.channels,
|
|
155
|
-
zalo: {
|
|
156
|
-
...cfg.channels?.zalo,
|
|
157
|
-
enabled: true,
|
|
158
|
-
dmPolicy: "allowlist",
|
|
159
|
-
allowFrom: unique,
|
|
160
|
-
},
|
|
161
|
-
},
|
|
162
|
-
} as OpenClawConfig;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return {
|
|
166
|
-
...cfg,
|
|
167
|
-
channels: {
|
|
168
|
-
...cfg.channels,
|
|
169
|
-
zalo: {
|
|
170
|
-
...cfg.channels?.zalo,
|
|
171
|
-
enabled: true,
|
|
172
|
-
accounts: {
|
|
173
|
-
...cfg.channels?.zalo?.accounts,
|
|
174
|
-
[accountId]: {
|
|
175
|
-
...cfg.channels?.zalo?.accounts?.[accountId],
|
|
176
|
-
enabled: cfg.channels?.zalo?.accounts?.[accountId]?.enabled ?? true,
|
|
177
|
-
dmPolicy: "allowlist",
|
|
178
|
-
allowFrom: unique,
|
|
179
|
-
},
|
|
180
|
-
},
|
|
181
|
-
},
|
|
182
|
-
},
|
|
183
|
-
} as OpenClawConfig;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const dmPolicy: ChannelOnboardingDmPolicy = {
|
|
187
|
-
label: "Zalo",
|
|
188
|
-
channel,
|
|
189
|
-
policyKey: "channels.zalo.dmPolicy",
|
|
190
|
-
allowFromKey: "channels.zalo.allowFrom",
|
|
191
|
-
getCurrent: (cfg) => (cfg.channels?.zalo?.dmPolicy ?? "pairing") as "pairing",
|
|
192
|
-
setPolicy: (cfg, policy) => setZaloDmPolicy(cfg, policy),
|
|
193
|
-
promptAllowFrom: async ({ cfg, prompter, accountId }) => {
|
|
194
|
-
const id =
|
|
195
|
-
accountId && normalizeAccountId(accountId)
|
|
196
|
-
? (normalizeAccountId(accountId) ?? DEFAULT_ACCOUNT_ID)
|
|
197
|
-
: resolveDefaultZaloAccountId(cfg);
|
|
198
|
-
return promptZaloAllowFrom({
|
|
199
|
-
cfg: cfg,
|
|
200
|
-
prompter,
|
|
201
|
-
accountId: id,
|
|
202
|
-
});
|
|
203
|
-
},
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
export const zaloOnboardingAdapter: ChannelOnboardingAdapter = {
|
|
97
|
+
export const zaloSetupWizard: ChannelSetupWizard = {
|
|
207
98
|
channel,
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
99
|
+
status: createStandardChannelSetupStatus({
|
|
100
|
+
channelLabel: "Zalo",
|
|
101
|
+
configuredLabel: "configured",
|
|
102
|
+
unconfiguredLabel: "needs token",
|
|
103
|
+
configuredHint: "recommended · configured",
|
|
104
|
+
unconfiguredHint: "recommended · newcomer-friendly",
|
|
105
|
+
configuredScore: 1,
|
|
106
|
+
unconfiguredScore: 10,
|
|
107
|
+
includeStatusLine: true,
|
|
108
|
+
resolveConfigured: ({ cfg, accountId }) => {
|
|
211
109
|
const account = resolveZaloAccount({
|
|
212
|
-
cfg
|
|
110
|
+
cfg,
|
|
213
111
|
accountId,
|
|
214
112
|
allowUnresolvedSecretRef: true,
|
|
215
113
|
});
|
|
@@ -218,41 +116,18 @@ export const zaloOnboardingAdapter: ChannelOnboardingAdapter = {
|
|
|
218
116
|
hasConfiguredSecretInput(account.config.botToken) ||
|
|
219
117
|
Boolean(account.config.tokenFile?.trim())
|
|
220
118
|
);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
statusLines: [`Zalo: ${configured ? "configured" : "needs token"}`],
|
|
226
|
-
selectionHint: configured ? "recommended · configured" : "recommended · newcomer-friendly",
|
|
227
|
-
quickstartScore: configured ? 1 : 10,
|
|
228
|
-
};
|
|
229
|
-
},
|
|
230
|
-
configure: async ({
|
|
231
|
-
cfg,
|
|
232
|
-
prompter,
|
|
233
|
-
accountOverrides,
|
|
234
|
-
shouldPromptAccountIds,
|
|
235
|
-
forceAllowFrom,
|
|
236
|
-
}) => {
|
|
237
|
-
const defaultZaloAccountId = resolveDefaultZaloAccountId(cfg);
|
|
238
|
-
const zaloAccountId = await resolveAccountIdForConfigure({
|
|
239
|
-
cfg,
|
|
240
|
-
prompter,
|
|
241
|
-
label: "Zalo",
|
|
242
|
-
accountOverride: accountOverrides.zalo,
|
|
243
|
-
shouldPromptAccountIds,
|
|
244
|
-
listAccountIds: listZaloAccountIds,
|
|
245
|
-
defaultAccountId: defaultZaloAccountId,
|
|
246
|
-
});
|
|
247
|
-
|
|
119
|
+
},
|
|
120
|
+
}),
|
|
121
|
+
credentials: [],
|
|
122
|
+
finalize: async ({ cfg, accountId, forceAllowFrom, options, prompter }) => {
|
|
248
123
|
let next = cfg;
|
|
249
124
|
const resolvedAccount = resolveZaloAccount({
|
|
250
125
|
cfg: next,
|
|
251
|
-
accountId
|
|
126
|
+
accountId,
|
|
252
127
|
allowUnresolvedSecretRef: true,
|
|
253
128
|
});
|
|
254
129
|
const accountConfigured = Boolean(resolvedAccount.token);
|
|
255
|
-
const allowEnv =
|
|
130
|
+
const allowEnv = accountId === DEFAULT_ACCOUNT_ID;
|
|
256
131
|
const hasConfigToken = Boolean(
|
|
257
132
|
hasConfiguredSecretInput(resolvedAccount.config.botToken) || resolvedAccount.config.tokenFile,
|
|
258
133
|
);
|
|
@@ -261,6 +136,7 @@ export const zaloOnboardingAdapter: ChannelOnboardingAdapter = {
|
|
|
261
136
|
prompter,
|
|
262
137
|
providerHint: "zalo",
|
|
263
138
|
credentialLabel: "bot token",
|
|
139
|
+
secretInputMode: options?.secretInputMode,
|
|
264
140
|
accountConfigured,
|
|
265
141
|
hasConfigToken,
|
|
266
142
|
allowEnv,
|
|
@@ -270,43 +146,45 @@ export const zaloOnboardingAdapter: ChannelOnboardingAdapter = {
|
|
|
270
146
|
inputPrompt: "Enter Zalo bot token",
|
|
271
147
|
preferredEnvVar: "ZALO_BOT_TOKEN",
|
|
272
148
|
onMissingConfigured: async () => await noteZaloTokenHelp(prompter),
|
|
273
|
-
applyUseEnv: async (
|
|
274
|
-
|
|
149
|
+
applyUseEnv: async (currentCfg) =>
|
|
150
|
+
accountId === DEFAULT_ACCOUNT_ID
|
|
275
151
|
? ({
|
|
276
|
-
...
|
|
152
|
+
...currentCfg,
|
|
277
153
|
channels: {
|
|
278
|
-
...
|
|
154
|
+
...currentCfg.channels,
|
|
279
155
|
zalo: {
|
|
280
|
-
...
|
|
156
|
+
...currentCfg.channels?.zalo,
|
|
281
157
|
enabled: true,
|
|
282
158
|
},
|
|
283
159
|
},
|
|
284
160
|
} as OpenClawConfig)
|
|
285
|
-
:
|
|
286
|
-
applySet: async (
|
|
287
|
-
|
|
161
|
+
: currentCfg,
|
|
162
|
+
applySet: async (currentCfg, value) =>
|
|
163
|
+
accountId === DEFAULT_ACCOUNT_ID
|
|
288
164
|
? ({
|
|
289
|
-
...
|
|
165
|
+
...currentCfg,
|
|
290
166
|
channels: {
|
|
291
|
-
...
|
|
167
|
+
...currentCfg.channels,
|
|
292
168
|
zalo: {
|
|
293
|
-
...
|
|
169
|
+
...currentCfg.channels?.zalo,
|
|
294
170
|
enabled: true,
|
|
295
171
|
botToken: value,
|
|
296
172
|
},
|
|
297
173
|
},
|
|
298
174
|
} as OpenClawConfig)
|
|
299
175
|
: ({
|
|
300
|
-
...
|
|
176
|
+
...currentCfg,
|
|
301
177
|
channels: {
|
|
302
|
-
...
|
|
178
|
+
...currentCfg.channels,
|
|
303
179
|
zalo: {
|
|
304
|
-
...
|
|
180
|
+
...currentCfg.channels?.zalo,
|
|
305
181
|
enabled: true,
|
|
306
182
|
accounts: {
|
|
307
|
-
...
|
|
308
|
-
[
|
|
309
|
-
...
|
|
183
|
+
...currentCfg.channels?.zalo?.accounts,
|
|
184
|
+
[accountId]: {
|
|
185
|
+
...(currentCfg.channels?.zalo?.accounts?.[accountId] as
|
|
186
|
+
| Record<string, unknown>
|
|
187
|
+
| undefined),
|
|
310
188
|
enabled: true,
|
|
311
189
|
botToken: value,
|
|
312
190
|
},
|
|
@@ -322,13 +200,13 @@ export const zaloOnboardingAdapter: ChannelOnboardingAdapter = {
|
|
|
322
200
|
initialValue: Boolean(resolvedAccount.config.webhookUrl),
|
|
323
201
|
});
|
|
324
202
|
if (wantsWebhook) {
|
|
325
|
-
const webhookUrl =
|
|
203
|
+
const webhookUrl = (
|
|
326
204
|
await prompter.text({
|
|
327
205
|
message: "Webhook URL (https://...) ",
|
|
328
206
|
initialValue: resolvedAccount.config.webhookUrl,
|
|
329
207
|
validate: (value) =>
|
|
330
208
|
value?.trim()?.startsWith("https://") ? undefined : "HTTPS URL required",
|
|
331
|
-
})
|
|
209
|
+
})
|
|
332
210
|
).trim();
|
|
333
211
|
const defaultPath = (() => {
|
|
334
212
|
try {
|
|
@@ -337,11 +215,13 @@ export const zaloOnboardingAdapter: ChannelOnboardingAdapter = {
|
|
|
337
215
|
return "/zalo-webhook";
|
|
338
216
|
}
|
|
339
217
|
})();
|
|
218
|
+
|
|
340
219
|
let webhookSecretResult = await promptSingleChannelSecretInput({
|
|
341
220
|
cfg: next,
|
|
342
221
|
prompter,
|
|
343
222
|
providerHint: "zalo-webhook",
|
|
344
223
|
credentialLabel: "webhook secret",
|
|
224
|
+
secretInputMode: options?.secretInputMode,
|
|
345
225
|
...buildSingleChannelSecretPromptState({
|
|
346
226
|
accountConfigured: hasConfiguredSecretInput(resolvedAccount.config.webhookSecret),
|
|
347
227
|
hasConfigToken: hasConfiguredSecretInput(resolvedAccount.config.webhookSecret),
|
|
@@ -363,6 +243,7 @@ export const zaloOnboardingAdapter: ChannelOnboardingAdapter = {
|
|
|
363
243
|
prompter,
|
|
364
244
|
providerHint: "zalo-webhook",
|
|
365
245
|
credentialLabel: "webhook secret",
|
|
246
|
+
secretInputMode: options?.secretInputMode,
|
|
366
247
|
...buildSingleChannelSecretPromptState({
|
|
367
248
|
accountConfigured: false,
|
|
368
249
|
hasConfigToken: false,
|
|
@@ -378,32 +259,33 @@ export const zaloOnboardingAdapter: ChannelOnboardingAdapter = {
|
|
|
378
259
|
webhookSecretResult.action === "set"
|
|
379
260
|
? webhookSecretResult.value
|
|
380
261
|
: resolvedAccount.config.webhookSecret;
|
|
381
|
-
const webhookPath =
|
|
262
|
+
const webhookPath = (
|
|
382
263
|
await prompter.text({
|
|
383
264
|
message: "Webhook path (optional)",
|
|
384
265
|
initialValue: resolvedAccount.config.webhookPath ?? defaultPath,
|
|
385
|
-
})
|
|
266
|
+
})
|
|
386
267
|
).trim();
|
|
387
268
|
next = setZaloUpdateMode(
|
|
388
269
|
next,
|
|
389
|
-
|
|
270
|
+
accountId,
|
|
390
271
|
"webhook",
|
|
391
272
|
webhookUrl,
|
|
392
273
|
webhookSecret,
|
|
393
274
|
webhookPath || undefined,
|
|
394
275
|
);
|
|
395
276
|
} else {
|
|
396
|
-
next = setZaloUpdateMode(next,
|
|
277
|
+
next = setZaloUpdateMode(next, accountId, "polling");
|
|
397
278
|
}
|
|
398
279
|
|
|
399
280
|
if (forceAllowFrom) {
|
|
400
281
|
next = await promptZaloAllowFrom({
|
|
401
282
|
cfg: next,
|
|
402
283
|
prompter,
|
|
403
|
-
accountId
|
|
284
|
+
accountId,
|
|
404
285
|
});
|
|
405
286
|
}
|
|
406
287
|
|
|
407
|
-
return { cfg: next
|
|
288
|
+
return { cfg: next };
|
|
408
289
|
},
|
|
290
|
+
dmPolicy: zaloDmPolicy,
|
|
409
291
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { expectOpenDmPolicyConfigIssue } from "openclaw/plugin-sdk/channel-test-helpers";
|
|
2
|
+
import { describe, it } from "vitest";
|
|
3
3
|
import { collectZaloStatusIssues } from "./status-issues.js";
|
|
4
4
|
|
|
5
5
|
describe("collectZaloStatusIssues", () => {
|
|
@@ -14,16 +14,4 @@ describe("collectZaloStatusIssues", () => {
|
|
|
14
14
|
},
|
|
15
15
|
});
|
|
16
16
|
});
|
|
17
|
-
|
|
18
|
-
it("skips unconfigured accounts", () => {
|
|
19
|
-
const issues = collectZaloStatusIssues([
|
|
20
|
-
{
|
|
21
|
-
accountId: "default",
|
|
22
|
-
enabled: true,
|
|
23
|
-
configured: false,
|
|
24
|
-
dmPolicy: "open",
|
|
25
|
-
},
|
|
26
|
-
]);
|
|
27
|
-
expect(issues).toHaveLength(0);
|
|
28
|
-
});
|
|
29
17
|
});
|
package/src/status-issues.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
1
|
+
import type {
|
|
2
|
+
ChannelAccountSnapshot,
|
|
3
|
+
ChannelStatusIssue,
|
|
4
|
+
} from "openclaw/plugin-sdk/channel-contract";
|
|
5
|
+
import {
|
|
6
|
+
coerceStatusIssueAccountId,
|
|
7
|
+
readStatusIssueFields,
|
|
8
|
+
} from "openclaw/plugin-sdk/extension-shared";
|
|
3
9
|
|
|
4
10
|
const ZALO_STATUS_FIELDS = ["accountId", "enabled", "configured", "dmPolicy"] as const;
|
|
5
11
|
|