@openclaw/zalouser 2026.5.2 → 2026.5.3-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.
Files changed (95) hide show
  1. package/dist/accounts-C00IMUgd.js +63 -0
  2. package/dist/accounts.runtime-uG7S8cXT.js +2 -0
  3. package/dist/api-BRwdUWuS.js +139 -0
  4. package/dist/api.js +7 -0
  5. package/dist/channel-ou_w_2j-.js +484 -0
  6. package/dist/channel-plugin-api.js +2 -0
  7. package/dist/channel.runtime-C9WxiAiR.js +25 -0
  8. package/dist/channel.setup-CiDeBFrn.js +10 -0
  9. package/dist/contract-api.js +3 -0
  10. package/dist/doctor-contract-DgqHp8E2.js +128 -0
  11. package/dist/doctor-contract-api.js +2 -0
  12. package/dist/index.js +27 -0
  13. package/dist/monitor-Cg7K_s_s.js +705 -0
  14. package/dist/runtime-QNU7vLgI.js +106 -0
  15. package/dist/runtime-api.js +22 -0
  16. package/dist/secret-contract-api.js +5 -0
  17. package/dist/security-audit-BZLhil-V.js +34 -0
  18. package/dist/send-BsmySxe3.js +534 -0
  19. package/dist/session-route-C0-Xr8bt.js +92 -0
  20. package/dist/setup-core-CqipqY98.js +40 -0
  21. package/dist/setup-entry.js +11 -0
  22. package/dist/setup-plugin-api.js +2 -0
  23. package/dist/setup-surface-NCOuKu-l.js +359 -0
  24. package/dist/shared-DSy8aIUx.js +120 -0
  25. package/dist/test-api.js +5 -0
  26. package/dist/zalo-js-CHCUlY3c.js +1279 -0
  27. package/package.json +15 -6
  28. package/api.ts +0 -9
  29. package/channel-plugin-api.ts +0 -3
  30. package/contract-api.ts +0 -2
  31. package/doctor-contract-api.ts +0 -1
  32. package/index.ts +0 -34
  33. package/runtime-api.ts +0 -67
  34. package/secret-contract-api.ts +0 -4
  35. package/setup-entry.ts +0 -9
  36. package/setup-plugin-api.ts +0 -2
  37. package/src/accounts.runtime.ts +0 -1
  38. package/src/accounts.test-mocks.ts +0 -14
  39. package/src/accounts.test.ts +0 -266
  40. package/src/accounts.ts +0 -131
  41. package/src/channel-api.ts +0 -20
  42. package/src/channel.adapters.ts +0 -391
  43. package/src/channel.directory.test.ts +0 -59
  44. package/src/channel.runtime.ts +0 -12
  45. package/src/channel.sendpayload.test.ts +0 -172
  46. package/src/channel.setup.test.ts +0 -33
  47. package/src/channel.setup.ts +0 -12
  48. package/src/channel.test.ts +0 -377
  49. package/src/channel.ts +0 -219
  50. package/src/config-schema.ts +0 -33
  51. package/src/directory.ts +0 -54
  52. package/src/doctor-contract.ts +0 -156
  53. package/src/doctor.test.ts +0 -77
  54. package/src/doctor.ts +0 -37
  55. package/src/group-policy.test.ts +0 -61
  56. package/src/group-policy.ts +0 -83
  57. package/src/message-sid.test.ts +0 -66
  58. package/src/message-sid.ts +0 -80
  59. package/src/monitor.account-scope.test.ts +0 -107
  60. package/src/monitor.group-gating.test.ts +0 -816
  61. package/src/monitor.send-mocks.ts +0 -20
  62. package/src/monitor.ts +0 -1044
  63. package/src/probe.test.ts +0 -60
  64. package/src/probe.ts +0 -35
  65. package/src/qr-temp-file.ts +0 -22
  66. package/src/reaction.test.ts +0 -19
  67. package/src/reaction.ts +0 -32
  68. package/src/runtime.ts +0 -9
  69. package/src/security-audit.test.ts +0 -80
  70. package/src/security-audit.ts +0 -71
  71. package/src/send.test.ts +0 -395
  72. package/src/send.ts +0 -272
  73. package/src/session-route.ts +0 -121
  74. package/src/setup-core.ts +0 -33
  75. package/src/setup-surface.test.ts +0 -363
  76. package/src/setup-surface.ts +0 -470
  77. package/src/setup-test-helpers.ts +0 -42
  78. package/src/shared.ts +0 -92
  79. package/src/status-issues.test.ts +0 -31
  80. package/src/status-issues.ts +0 -58
  81. package/src/test-helpers.ts +0 -26
  82. package/src/text-styles.test.ts +0 -203
  83. package/src/text-styles.ts +0 -540
  84. package/src/tool.test.ts +0 -212
  85. package/src/tool.ts +0 -210
  86. package/src/types.ts +0 -125
  87. package/src/zalo-js.credentials.test.ts +0 -465
  88. package/src/zalo-js.test-mocks.ts +0 -89
  89. package/src/zalo-js.ts +0 -1911
  90. package/src/zca-client.test.ts +0 -24
  91. package/src/zca-client.ts +0 -259
  92. package/src/zca-constants.ts +0 -55
  93. package/src/zca-js-exports.d.ts +0 -22
  94. package/test-api.ts +0 -21
  95. package/tsconfig.json +0 -16
@@ -0,0 +1,484 @@
1
+ import { a as resolveZalouserAccountSync, i as resolveDefaultZalouserAccountId, r as listZalouserAccountIds, t as checkZcaAuthenticated } from "./accounts-C00IMUgd.js";
2
+ import { a as isNumericTargetId, i as isDangerousNameMatchingEnabled, n as DEFAULT_ACCOUNT_ID, o as normalizeAccountId, r as chunkTextForOutbound, s as sendPayloadWithChunkedTextAndMedia, t as createZalouserPluginBase } from "./shared-DSy8aIUx.js";
3
+ import { a as resolveZalouserReactionMessageIds, o as buildZalouserGroupCandidates, s as findZalouserGroupEntry, t as getZalouserRuntime } from "./runtime-QNU7vLgI.js";
4
+ import { n as zalouserSetupAdapter, r as writeQrDataUrlToTempFile, t as createZalouserSetupWizardProxy } from "./setup-core-CqipqY98.js";
5
+ import { i as resolveZalouserOutboundSessionRoute, n as parseZalouserDirectoryGroupId, r as parseZalouserOutboundTarget, t as normalizeZalouserTarget } from "./session-route-C0-Xr8bt.js";
6
+ import { createChatChannelPlugin } from "openclaw/plugin-sdk/channel-core";
7
+ import { createAccountStatusSink } from "openclaw/plugin-sdk/channel-lifecycle";
8
+ import { buildPassiveProbedChannelStatusSummary, coerceStatusIssueAccountId, readStatusIssueFields } from "openclaw/plugin-sdk/extension-shared";
9
+ import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
10
+ import { createAsyncComputedAccountStatusAdapter, createDefaultChannelRuntimeState } from "openclaw/plugin-sdk/status-helpers";
11
+ import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
12
+ import { createScopedDmSecurityResolver } from "openclaw/plugin-sdk/channel-config-helpers";
13
+ import { createPairingPrefixStripper } from "openclaw/plugin-sdk/channel-pairing";
14
+ import { createEmptyChannelResult, createRawChannelSendResultAdapter } from "openclaw/plugin-sdk/channel-send-result";
15
+ import { createStaticReplyToModeResolver } from "openclaw/plugin-sdk/conversation-runtime";
16
+ //#region extensions/zalouser/src/channel.adapters.ts
17
+ const loadZalouserChannelRuntime$1 = createLazyRuntimeModule(() => import("./channel.runtime-C9WxiAiR.js"));
18
+ const ZALOUSER_TEXT_CHUNK_LIMIT = 2e3;
19
+ function resolveZalouserQrProfile(accountId) {
20
+ const normalized = normalizeAccountId(accountId);
21
+ if (!normalized || normalized === DEFAULT_ACCOUNT_ID) return process.env.ZALOUSER_PROFILE?.trim() || process.env.ZCA_PROFILE?.trim() || "default";
22
+ return normalized;
23
+ }
24
+ function resolveZalouserOutboundChunkMode(cfg, accountId) {
25
+ return getZalouserRuntime().channel.text.resolveChunkMode(cfg, "zalouser", accountId);
26
+ }
27
+ function resolveZalouserOutboundTextChunkLimit(cfg, accountId) {
28
+ return getZalouserRuntime().channel.text.resolveTextChunkLimit(cfg, "zalouser", accountId, { fallbackLimit: ZALOUSER_TEXT_CHUNK_LIMIT });
29
+ }
30
+ function resolveZalouserGroupPolicyEntry(params) {
31
+ const account = resolveZalouserAccountSync({
32
+ cfg: params.cfg,
33
+ accountId: params.accountId ?? void 0
34
+ });
35
+ return findZalouserGroupEntry(account.config.groups ?? {}, buildZalouserGroupCandidates({
36
+ groupId: params.groupId,
37
+ groupChannel: params.groupChannel,
38
+ includeWildcard: true,
39
+ allowNameMatching: isDangerousNameMatchingEnabled(account.config)
40
+ }));
41
+ }
42
+ function resolveZalouserGroupToolPolicy(params) {
43
+ return resolveZalouserGroupPolicyEntry(params)?.tools;
44
+ }
45
+ function resolveZalouserRequireMention(params) {
46
+ const entry = resolveZalouserGroupPolicyEntry(params);
47
+ if (typeof entry?.requireMention === "boolean") return entry.requireMention;
48
+ return true;
49
+ }
50
+ const zalouserRawSendResultAdapter = createRawChannelSendResultAdapter({
51
+ channel: "zalouser",
52
+ sendText: async ({ to, text, accountId, cfg }) => {
53
+ const { sendMessageZalouser } = await loadZalouserChannelRuntime$1();
54
+ const account = resolveZalouserAccountSync({
55
+ cfg,
56
+ accountId
57
+ });
58
+ const target = parseZalouserOutboundTarget(to);
59
+ return await sendMessageZalouser(target.threadId, text, {
60
+ profile: account.profile,
61
+ isGroup: target.isGroup,
62
+ textMode: "markdown",
63
+ textChunkMode: resolveZalouserOutboundChunkMode(cfg, account.accountId),
64
+ textChunkLimit: resolveZalouserOutboundTextChunkLimit(cfg, account.accountId)
65
+ });
66
+ },
67
+ sendMedia: async ({ to, text, mediaUrl, accountId, cfg, mediaLocalRoots, mediaReadFile }) => {
68
+ const { sendMessageZalouser } = await loadZalouserChannelRuntime$1();
69
+ const account = resolveZalouserAccountSync({
70
+ cfg,
71
+ accountId
72
+ });
73
+ const target = parseZalouserOutboundTarget(to);
74
+ return await sendMessageZalouser(target.threadId, text, {
75
+ profile: account.profile,
76
+ isGroup: target.isGroup,
77
+ mediaUrl,
78
+ mediaLocalRoots,
79
+ mediaReadFile,
80
+ textMode: "markdown",
81
+ textChunkMode: resolveZalouserOutboundChunkMode(cfg, account.accountId),
82
+ textChunkLimit: resolveZalouserOutboundTextChunkLimit(cfg, account.accountId)
83
+ });
84
+ }
85
+ });
86
+ const resolveZalouserDmPolicy = createScopedDmSecurityResolver({
87
+ channelKey: "zalouser",
88
+ resolvePolicy: (account) => account.config.dmPolicy,
89
+ resolveAllowFrom: (account) => account.config.allowFrom,
90
+ policyPathSuffix: "dmPolicy",
91
+ normalizeEntry: (raw) => raw.trim().replace(/^(zalouser|zlu):/i, "")
92
+ });
93
+ const zalouserGroupsAdapter = {
94
+ resolveRequireMention: resolveZalouserRequireMention,
95
+ resolveToolPolicy: resolveZalouserGroupToolPolicy
96
+ };
97
+ const zalouserMessageActions = {
98
+ describeMessageTool: ({ cfg, accountId }) => {
99
+ if ((accountId ? [resolveZalouserAccountSync({
100
+ cfg,
101
+ accountId
102
+ })].filter((account) => account.enabled) : listZalouserAccountIds(cfg).map((resolvedAccountId) => resolveZalouserAccountSync({
103
+ cfg,
104
+ accountId: resolvedAccountId
105
+ })).filter((account) => account.enabled)).length === 0) return null;
106
+ return { actions: ["react"] };
107
+ },
108
+ supportsAction: ({ action }) => action === "react",
109
+ handleAction: async ({ action, params, cfg, accountId, toolContext }) => {
110
+ if (action !== "react") throw new Error(`Zalouser action ${action} not supported`);
111
+ const { sendReactionZalouser } = await loadZalouserChannelRuntime$1();
112
+ const account = resolveZalouserAccountSync({
113
+ cfg,
114
+ accountId
115
+ });
116
+ const threadId = (typeof params.threadId === "string" ? params.threadId.trim() : "") || (typeof params.to === "string" ? params.to.trim() : "") || (typeof params.chatId === "string" ? params.chatId.trim() : "") || (toolContext?.currentChannelId?.trim() ?? "");
117
+ if (!threadId) throw new Error("Zalouser react requires threadId (or to/chatId).");
118
+ const emoji = typeof params.emoji === "string" ? params.emoji.trim() : "";
119
+ if (!emoji) throw new Error("Zalouser react requires emoji.");
120
+ const ids = resolveZalouserReactionMessageIds({
121
+ messageId: typeof params.messageId === "string" ? params.messageId : void 0,
122
+ cliMsgId: typeof params.cliMsgId === "string" ? params.cliMsgId : void 0,
123
+ currentMessageId: toolContext?.currentMessageId
124
+ });
125
+ if (!ids) throw new Error("Zalouser react requires messageId + cliMsgId (or a current message context id).");
126
+ const result = await sendReactionZalouser({
127
+ profile: account.profile,
128
+ threadId,
129
+ isGroup: params.isGroup === true,
130
+ msgId: ids.msgId,
131
+ cliMsgId: ids.cliMsgId,
132
+ emoji,
133
+ remove: params.remove === true
134
+ });
135
+ if (!result.ok) throw new Error(result.error || "Failed to react on Zalo message");
136
+ return {
137
+ content: [{
138
+ type: "text",
139
+ text: params.remove === true ? `Removed reaction ${emoji} from ${ids.msgId}` : `Reacted ${emoji} on ${ids.msgId}`
140
+ }],
141
+ details: {
142
+ messageId: ids.msgId,
143
+ cliMsgId: ids.cliMsgId,
144
+ threadId
145
+ }
146
+ };
147
+ }
148
+ };
149
+ const zalouserResolverAdapter = { resolveTargets: async ({ cfg, accountId, inputs, kind, runtime }) => {
150
+ const results = [];
151
+ for (const input of inputs) {
152
+ const trimmed = input.trim();
153
+ if (!trimmed) {
154
+ results.push({
155
+ input,
156
+ resolved: false,
157
+ note: "empty input"
158
+ });
159
+ continue;
160
+ }
161
+ if (/^\d+$/.test(trimmed)) {
162
+ results.push({
163
+ input,
164
+ resolved: true,
165
+ id: trimmed
166
+ });
167
+ continue;
168
+ }
169
+ try {
170
+ const runtimeModule = await loadZalouserChannelRuntime$1();
171
+ const account = resolveZalouserAccountSync({
172
+ cfg,
173
+ accountId: accountId ?? resolveDefaultZalouserAccountId(cfg)
174
+ });
175
+ if (kind === "user") {
176
+ const friends = await runtimeModule.listZaloFriendsMatching(account.profile, trimmed);
177
+ const best = friends[0];
178
+ results.push({
179
+ input,
180
+ resolved: Boolean(best?.userId),
181
+ id: best?.userId,
182
+ name: best?.displayName,
183
+ note: friends.length > 1 ? "multiple matches; chose first" : void 0
184
+ });
185
+ } else {
186
+ const groups = await runtimeModule.listZaloGroupsMatching(account.profile, trimmed);
187
+ const best = groups.find((group) => normalizeLowercaseStringOrEmpty(group.name) === normalizeLowercaseStringOrEmpty(trimmed)) ?? groups[0];
188
+ results.push({
189
+ input,
190
+ resolved: Boolean(best?.groupId),
191
+ id: best?.groupId,
192
+ name: best?.name,
193
+ note: groups.length > 1 ? "multiple matches; chose first" : void 0
194
+ });
195
+ }
196
+ } catch (err) {
197
+ runtime.error?.(`zalouser resolve failed: ${String(err)}`);
198
+ results.push({
199
+ input,
200
+ resolved: false,
201
+ note: "lookup failed"
202
+ });
203
+ }
204
+ }
205
+ return results;
206
+ } };
207
+ const zalouserAuthAdapter = { login: async ({ cfg, accountId, runtime }) => {
208
+ const { startZaloQrLogin, waitForZaloQrLogin } = await loadZalouserChannelRuntime$1();
209
+ const account = resolveZalouserAccountSync({
210
+ cfg,
211
+ accountId: accountId ?? resolveDefaultZalouserAccountId(cfg)
212
+ });
213
+ runtime.log(`Generating QR login for Zalo Personal (account: ${account.accountId}, profile: ${account.profile})...`);
214
+ const started = await startZaloQrLogin({
215
+ profile: account.profile,
216
+ timeoutMs: 35e3
217
+ });
218
+ if (!started.qrDataUrl) throw new Error(started.message || "Failed to start QR login");
219
+ const qrPath = await writeQrDataUrlToTempFile(started.qrDataUrl, account.profile);
220
+ if (qrPath) runtime.log(`Scan QR image: ${qrPath}`);
221
+ else runtime.log("QR generated but could not be written to a temp file.");
222
+ const waited = await waitForZaloQrLogin({
223
+ profile: account.profile,
224
+ timeoutMs: 18e4
225
+ });
226
+ if (!waited.connected) throw new Error(waited.message || "Zalouser login failed");
227
+ runtime.log(waited.message);
228
+ } };
229
+ const zalouserSecurityAdapter = {
230
+ resolveDmPolicy: resolveZalouserDmPolicy,
231
+ collectAuditFindings: async (params) => (await loadZalouserChannelRuntime$1()).collectZalouserSecurityAuditFindings(params)
232
+ };
233
+ const zalouserThreadingAdapter = { resolveReplyToMode: createStaticReplyToModeResolver("off") };
234
+ const zalouserPairingTextAdapter = {
235
+ idLabel: "zalouserUserId",
236
+ message: "Your pairing request has been approved.",
237
+ normalizeAllowEntry: createPairingPrefixStripper(/^(zalouser|zlu):/i),
238
+ notify: async ({ cfg, id, message }) => {
239
+ const { sendMessageZalouser } = await loadZalouserChannelRuntime$1();
240
+ const account = resolveZalouserAccountSync({ cfg });
241
+ if (!await checkZcaAuthenticated(account.profile)) throw new Error("Zalouser not authenticated");
242
+ await sendMessageZalouser(id, message, { profile: account.profile });
243
+ }
244
+ };
245
+ const zalouserOutboundAdapter = {
246
+ deliveryMode: "direct",
247
+ chunker: chunkTextForOutbound,
248
+ chunkerMode: "markdown",
249
+ sendPayload: async (ctx) => await sendPayloadWithChunkedTextAndMedia({
250
+ ctx,
251
+ sendText: (nextCtx) => zalouserRawSendResultAdapter.sendText(nextCtx),
252
+ sendMedia: (nextCtx) => zalouserRawSendResultAdapter.sendMedia(nextCtx),
253
+ emptyResult: createEmptyChannelResult("zalouser")
254
+ }),
255
+ ...zalouserRawSendResultAdapter
256
+ };
257
+ const zalouserMessagingAdapter = {
258
+ targetPrefixes: ["zalouser", "zlu"],
259
+ normalizeTarget: (raw) => normalizeZalouserTarget(raw),
260
+ resolveOutboundSessionRoute: (params) => resolveZalouserOutboundSessionRoute(params),
261
+ targetResolver: {
262
+ looksLikeId: (raw) => {
263
+ const normalized = normalizeZalouserTarget(raw);
264
+ if (!normalized) return false;
265
+ if (/^group:[^\s]+$/i.test(normalized) || /^user:[^\s]+$/i.test(normalized)) return true;
266
+ return isNumericTargetId(normalized);
267
+ },
268
+ hint: "<user:id|group:id>"
269
+ }
270
+ };
271
+ //#endregion
272
+ //#region extensions/zalouser/src/directory.ts
273
+ function mapUser$1(params) {
274
+ return {
275
+ kind: "user",
276
+ id: params.id,
277
+ name: params.name ?? void 0,
278
+ avatarUrl: params.avatarUrl ?? void 0,
279
+ raw: params.raw
280
+ };
281
+ }
282
+ async function listZalouserDirectoryGroupMembers(params, deps) {
283
+ const account = resolveZalouserAccountSync({
284
+ cfg: params.cfg,
285
+ accountId: params.accountId
286
+ });
287
+ const normalizedGroupId = parseZalouserDirectoryGroupId(params.groupId);
288
+ const rows = (await deps.listZaloGroupMembers(account.profile, normalizedGroupId)).map((member) => mapUser$1({
289
+ id: member.userId,
290
+ name: member.displayName,
291
+ avatarUrl: member.avatar ?? null,
292
+ raw: member
293
+ }));
294
+ return typeof params.limit === "number" && params.limit > 0 ? rows.slice(0, params.limit) : rows;
295
+ }
296
+ //#endregion
297
+ //#region extensions/zalouser/src/status-issues.ts
298
+ const ZALOUSER_STATUS_FIELDS = [
299
+ "accountId",
300
+ "enabled",
301
+ "configured",
302
+ "dmPolicy",
303
+ "lastError"
304
+ ];
305
+ function collectZalouserStatusIssues(accounts) {
306
+ const issues = [];
307
+ for (const entry of accounts) {
308
+ const account = readStatusIssueFields(entry, ZALOUSER_STATUS_FIELDS);
309
+ if (!account) continue;
310
+ const accountId = coerceStatusIssueAccountId(account.accountId) ?? "default";
311
+ if (!(account.enabled !== false)) continue;
312
+ if (!(account.configured === true)) {
313
+ issues.push({
314
+ channel: "zalouser",
315
+ accountId,
316
+ kind: "auth",
317
+ message: "Not authenticated (no saved Zalo session).",
318
+ fix: "Run: openclaw channels login --channel zalouser"
319
+ });
320
+ continue;
321
+ }
322
+ if (account.dmPolicy === "open") issues.push({
323
+ channel: "zalouser",
324
+ accountId,
325
+ kind: "config",
326
+ message: "Zalo Personal dmPolicy is \"open\", allowing any user to message the bot without pairing.",
327
+ fix: "Set channels.zalouser.dmPolicy to \"pairing\" or \"allowlist\" to restrict access."
328
+ });
329
+ }
330
+ return issues;
331
+ }
332
+ //#endregion
333
+ //#region extensions/zalouser/src/channel.ts
334
+ const loadZalouserChannelRuntime = createLazyRuntimeModule(() => import("./channel.runtime-C9WxiAiR.js"));
335
+ const zalouserSetupWizardProxy = createZalouserSetupWizardProxy(async () => (await import("./setup-surface-NCOuKu-l.js").then((n) => n.t)).zalouserSetupWizard);
336
+ function mapUser(params) {
337
+ return {
338
+ kind: "user",
339
+ id: params.id,
340
+ name: params.name ?? void 0,
341
+ avatarUrl: params.avatarUrl ?? void 0,
342
+ raw: params.raw
343
+ };
344
+ }
345
+ function mapGroup(params) {
346
+ return {
347
+ kind: "group",
348
+ id: params.id,
349
+ name: params.name ?? void 0,
350
+ raw: params.raw
351
+ };
352
+ }
353
+ const zalouserPlugin = createChatChannelPlugin({
354
+ base: {
355
+ ...createZalouserPluginBase({
356
+ setupWizard: zalouserSetupWizardProxy,
357
+ setup: zalouserSetupAdapter
358
+ }),
359
+ groups: zalouserGroupsAdapter,
360
+ actions: zalouserMessageActions,
361
+ messaging: zalouserMessagingAdapter,
362
+ directory: {
363
+ self: async ({ cfg, accountId }) => {
364
+ const { getZaloUserInfo } = await loadZalouserChannelRuntime();
365
+ const parsed = await getZaloUserInfo(resolveZalouserAccountSync({
366
+ cfg,
367
+ accountId
368
+ }).profile);
369
+ if (!parsed?.userId) return null;
370
+ return mapUser({
371
+ id: parsed.userId,
372
+ name: parsed.displayName ?? null,
373
+ avatarUrl: parsed.avatar ?? null,
374
+ raw: parsed
375
+ });
376
+ },
377
+ listPeers: async ({ cfg, accountId, query, limit }) => {
378
+ const { listZaloFriendsMatching } = await loadZalouserChannelRuntime();
379
+ const rows = (await listZaloFriendsMatching(resolveZalouserAccountSync({
380
+ cfg,
381
+ accountId
382
+ }).profile, query)).map((friend) => mapUser({
383
+ id: friend.userId,
384
+ name: friend.displayName ?? null,
385
+ avatarUrl: friend.avatar ?? null,
386
+ raw: friend
387
+ }));
388
+ return typeof limit === "number" && limit > 0 ? rows.slice(0, limit) : rows;
389
+ },
390
+ listGroups: async ({ cfg, accountId, query, limit }) => {
391
+ const { listZaloGroupsMatching } = await loadZalouserChannelRuntime();
392
+ const rows = (await listZaloGroupsMatching(resolveZalouserAccountSync({
393
+ cfg,
394
+ accountId
395
+ }).profile, query)).map((group) => mapGroup({
396
+ id: `group:${group.groupId}`,
397
+ name: group.name ?? null,
398
+ raw: group
399
+ }));
400
+ return typeof limit === "number" && limit > 0 ? rows.slice(0, limit) : rows;
401
+ },
402
+ listGroupMembers: async ({ cfg, accountId, groupId, limit }) => {
403
+ const { listZaloGroupMembers } = await loadZalouserChannelRuntime();
404
+ return await listZalouserDirectoryGroupMembers({
405
+ cfg,
406
+ accountId: accountId ?? void 0,
407
+ groupId,
408
+ limit: limit ?? void 0
409
+ }, { listZaloGroupMembers });
410
+ }
411
+ },
412
+ resolver: zalouserResolverAdapter,
413
+ auth: zalouserAuthAdapter,
414
+ status: createAsyncComputedAccountStatusAdapter({
415
+ defaultRuntime: createDefaultChannelRuntimeState(DEFAULT_ACCOUNT_ID),
416
+ collectStatusIssues: collectZalouserStatusIssues,
417
+ buildChannelSummary: ({ snapshot }) => buildPassiveProbedChannelStatusSummary(snapshot),
418
+ probeAccount: async ({ account, timeoutMs }) => (await loadZalouserChannelRuntime()).probeZalouser(account.profile, timeoutMs),
419
+ resolveAccountSnapshot: async ({ account, runtime }) => {
420
+ const configured = await checkZcaAuthenticated(account.profile);
421
+ return {
422
+ accountId: account.accountId,
423
+ name: account.name,
424
+ enabled: account.enabled,
425
+ configured,
426
+ extra: {
427
+ dmPolicy: account.config.dmPolicy ?? "pairing",
428
+ lastError: configured ? runtime?.lastError ?? null : runtime?.lastError ?? "not authenticated"
429
+ }
430
+ };
431
+ }
432
+ }),
433
+ gateway: {
434
+ startAccount: async (ctx) => {
435
+ const { getZaloUserInfo } = await loadZalouserChannelRuntime();
436
+ const account = ctx.account;
437
+ let userLabel = "";
438
+ try {
439
+ const userInfo = await getZaloUserInfo(account.profile);
440
+ if (userInfo?.displayName) userLabel = ` (${userInfo.displayName})`;
441
+ ctx.setStatus({
442
+ accountId: account.accountId,
443
+ profile: userInfo
444
+ });
445
+ } catch {}
446
+ const statusSink = createAccountStatusSink({
447
+ accountId: ctx.accountId,
448
+ setStatus: ctx.setStatus
449
+ });
450
+ ctx.log?.info(`[${account.accountId}] starting zalouser provider${userLabel}`);
451
+ const { monitorZalouserProvider } = await import("./monitor-Cg7K_s_s.js");
452
+ return monitorZalouserProvider({
453
+ account,
454
+ config: ctx.cfg,
455
+ runtime: ctx.runtime,
456
+ abortSignal: ctx.abortSignal,
457
+ statusSink
458
+ });
459
+ },
460
+ loginWithQrStart: async (params) => {
461
+ const { startZaloQrLogin } = await loadZalouserChannelRuntime();
462
+ return await startZaloQrLogin({
463
+ profile: resolveZalouserQrProfile(params.accountId),
464
+ force: params.force,
465
+ timeoutMs: params.timeoutMs
466
+ });
467
+ },
468
+ loginWithQrWait: async (params) => {
469
+ const { waitForZaloQrLogin } = await loadZalouserChannelRuntime();
470
+ return await waitForZaloQrLogin({
471
+ profile: resolveZalouserQrProfile(params.accountId),
472
+ timeoutMs: params.timeoutMs
473
+ });
474
+ },
475
+ logoutAccount: async (ctx) => await (await loadZalouserChannelRuntime()).logoutZaloProfile(ctx.account.profile || resolveZalouserQrProfile(ctx.accountId))
476
+ }
477
+ },
478
+ security: zalouserSecurityAdapter,
479
+ threading: zalouserThreadingAdapter,
480
+ pairing: { text: zalouserPairingTextAdapter },
481
+ outbound: zalouserOutboundAdapter
482
+ });
483
+ //#endregion
484
+ export { zalouserPlugin as t };
@@ -0,0 +1,2 @@
1
+ import { t as zalouserPlugin } from "./channel-ou_w_2j-.js";
2
+ export { zalouserPlugin };
@@ -0,0 +1,25 @@
1
+ import { t as collectZalouserSecurityAuditFindings } from "./security-audit-BZLhil-V.js";
2
+ import { a as listZaloGroupMembers, b as waitForZaloQrLogin, c as logoutZaloProfile, i as listZaloFriendsMatching, n as getZaloUserInfo, s as listZaloGroupsMatching, y as startZaloQrLogin } from "./zalo-js-CHCUlY3c.js";
3
+ import { a as sendReactionZalouser, i as sendMessageZalouser } from "./send-BsmySxe3.js";
4
+ import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
5
+ //#region extensions/zalouser/src/probe.ts
6
+ async function probeZalouser(profile, timeoutMs) {
7
+ try {
8
+ const user = timeoutMs ? await Promise.race([getZaloUserInfo(profile), new Promise((resolve) => setTimeout(() => resolve(null), Math.max(timeoutMs, 1e3)))]) : await getZaloUserInfo(profile);
9
+ if (!user) return {
10
+ ok: false,
11
+ error: "Not authenticated"
12
+ };
13
+ return {
14
+ ok: true,
15
+ user
16
+ };
17
+ } catch (error) {
18
+ return {
19
+ ok: false,
20
+ error: formatErrorMessage(error)
21
+ };
22
+ }
23
+ }
24
+ //#endregion
25
+ export { collectZalouserSecurityAuditFindings, getZaloUserInfo, listZaloFriendsMatching, listZaloGroupMembers, listZaloGroupsMatching, logoutZaloProfile, probeZalouser, sendMessageZalouser, sendReactionZalouser, startZaloQrLogin, waitForZaloQrLogin };
@@ -0,0 +1,10 @@
1
+ import { n as zalouserSetupWizard } from "./setup-surface-NCOuKu-l.js";
2
+ import { t as createZalouserPluginBase } from "./shared-DSy8aIUx.js";
3
+ import { n as zalouserSetupAdapter } from "./setup-core-CqipqY98.js";
4
+ //#region extensions/zalouser/src/channel.setup.ts
5
+ const zalouserSetupPlugin = { ...createZalouserPluginBase({
6
+ setupWizard: zalouserSetupWizard,
7
+ setup: zalouserSetupAdapter
8
+ }) };
9
+ //#endregion
10
+ export { zalouserSetupPlugin as t };
@@ -0,0 +1,3 @@
1
+ import { n as normalizeCompatibilityConfig, t as legacyConfigRules } from "./doctor-contract-DgqHp8E2.js";
2
+ import { t as collectZalouserSecurityAuditFindings } from "./security-audit-BZLhil-V.js";
3
+ export { collectZalouserSecurityAuditFindings, legacyConfigRules, normalizeCompatibilityConfig };
@@ -0,0 +1,128 @@
1
+ //#region extensions/zalouser/src/doctor-contract.ts
2
+ function asObjectRecord(value) {
3
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
4
+ }
5
+ function hasLegacyZalouserGroupAllowAlias(value) {
6
+ const group = asObjectRecord(value);
7
+ return Boolean(group && typeof group.allow === "boolean");
8
+ }
9
+ function hasLegacyZalouserGroupAllowAliases(value) {
10
+ const groups = asObjectRecord(value);
11
+ return Boolean(groups && Object.values(groups).some((group) => hasLegacyZalouserGroupAllowAlias(group)));
12
+ }
13
+ function hasLegacyZalouserAccountGroupAllowAliases(value) {
14
+ const accounts = asObjectRecord(value);
15
+ if (!accounts) return false;
16
+ return Object.values(accounts).some((account) => {
17
+ const accountRecord = asObjectRecord(account);
18
+ return Boolean(accountRecord && hasLegacyZalouserGroupAllowAliases(accountRecord.groups));
19
+ });
20
+ }
21
+ function normalizeZalouserGroupAllowAliases(params) {
22
+ let changed = false;
23
+ const nextGroups = { ...params.groups };
24
+ for (const [groupId, groupValue] of Object.entries(params.groups)) {
25
+ const group = asObjectRecord(groupValue);
26
+ if (!group || typeof group.allow !== "boolean") continue;
27
+ const nextGroup = { ...group };
28
+ if (typeof nextGroup.enabled !== "boolean") nextGroup.enabled = group.allow;
29
+ delete nextGroup.allow;
30
+ nextGroups[groupId] = nextGroup;
31
+ changed = true;
32
+ params.changes.push(`Moved ${params.pathPrefix}.${groupId}.allow → ${params.pathPrefix}.${groupId}.enabled (${String(nextGroup.enabled)}).`);
33
+ }
34
+ return {
35
+ groups: nextGroups,
36
+ changed
37
+ };
38
+ }
39
+ function normalizeZalouserCompatibilityConfig(cfg) {
40
+ const zalouser = asObjectRecord(asObjectRecord(cfg.channels)?.zalouser);
41
+ if (!zalouser) return {
42
+ config: cfg,
43
+ changes: []
44
+ };
45
+ const changes = [];
46
+ let updatedZalouser = zalouser;
47
+ let changed = false;
48
+ const groups = asObjectRecord(updatedZalouser.groups);
49
+ if (groups) {
50
+ const normalized = normalizeZalouserGroupAllowAliases({
51
+ groups,
52
+ pathPrefix: "channels.zalouser.groups",
53
+ changes
54
+ });
55
+ if (normalized.changed) {
56
+ updatedZalouser = {
57
+ ...updatedZalouser,
58
+ groups: normalized.groups
59
+ };
60
+ changed = true;
61
+ }
62
+ }
63
+ const accounts = asObjectRecord(updatedZalouser.accounts);
64
+ if (accounts) {
65
+ let accountsChanged = false;
66
+ const nextAccounts = { ...accounts };
67
+ for (const [accountId, accountValue] of Object.entries(accounts)) {
68
+ const account = asObjectRecord(accountValue);
69
+ if (!account) continue;
70
+ const accountGroups = asObjectRecord(account.groups);
71
+ if (!accountGroups) continue;
72
+ const normalized = normalizeZalouserGroupAllowAliases({
73
+ groups: accountGroups,
74
+ pathPrefix: `channels.zalouser.accounts.${accountId}.groups`,
75
+ changes
76
+ });
77
+ if (!normalized.changed) continue;
78
+ nextAccounts[accountId] = {
79
+ ...account,
80
+ groups: normalized.groups
81
+ };
82
+ accountsChanged = true;
83
+ }
84
+ if (accountsChanged) {
85
+ updatedZalouser = {
86
+ ...updatedZalouser,
87
+ accounts: nextAccounts
88
+ };
89
+ changed = true;
90
+ }
91
+ }
92
+ if (!changed) return {
93
+ config: cfg,
94
+ changes: []
95
+ };
96
+ return {
97
+ config: {
98
+ ...cfg,
99
+ channels: {
100
+ ...cfg.channels,
101
+ zalouser: updatedZalouser
102
+ }
103
+ },
104
+ changes
105
+ };
106
+ }
107
+ const legacyConfigRules = [{
108
+ path: [
109
+ "channels",
110
+ "zalouser",
111
+ "groups"
112
+ ],
113
+ message: "channels.zalouser.groups.<id>.allow is legacy; use channels.zalouser.groups.<id>.enabled instead. Run \"openclaw doctor --fix\".",
114
+ match: hasLegacyZalouserGroupAllowAliases
115
+ }, {
116
+ path: [
117
+ "channels",
118
+ "zalouser",
119
+ "accounts"
120
+ ],
121
+ message: "channels.zalouser.accounts.<id>.groups.<id>.allow is legacy; use channels.zalouser.accounts.<id>.groups.<id>.enabled instead. Run \"openclaw doctor --fix\".",
122
+ match: hasLegacyZalouserAccountGroupAllowAliases
123
+ }];
124
+ function normalizeCompatibilityConfig(params) {
125
+ return normalizeZalouserCompatibilityConfig(params.cfg);
126
+ }
127
+ //#endregion
128
+ export { normalizeCompatibilityConfig as n, legacyConfigRules as t };
@@ -0,0 +1,2 @@
1
+ import { n as normalizeCompatibilityConfig, t as legacyConfigRules } from "./doctor-contract-DgqHp8E2.js";
2
+ export { legacyConfigRules, normalizeCompatibilityConfig };
package/dist/index.js ADDED
@@ -0,0 +1,27 @@
1
+ import { defineBundledChannelEntry, loadBundledEntryExportSync } from "openclaw/plugin-sdk/channel-entry-contract";
2
+ //#region extensions/zalouser/index.ts
3
+ function createZalouserTool(context) {
4
+ return loadBundledEntryExportSync(import.meta.url, {
5
+ specifier: "./api.js",
6
+ exportName: "createZalouserTool"
7
+ })(context);
8
+ }
9
+ var zalouser_default = defineBundledChannelEntry({
10
+ id: "zalouser",
11
+ name: "Zalo Personal",
12
+ description: "Zalo personal account messaging via native zca-js integration",
13
+ importMetaUrl: import.meta.url,
14
+ plugin: {
15
+ specifier: "./channel-plugin-api.js",
16
+ exportName: "zalouserPlugin"
17
+ },
18
+ runtime: {
19
+ specifier: "./runtime-api.js",
20
+ exportName: "setZalouserRuntime"
21
+ },
22
+ registerFull(api) {
23
+ api.registerTool((ctx) => createZalouserTool(ctx), { name: "zalouser" });
24
+ }
25
+ });
26
+ //#endregion
27
+ export { zalouser_default as default };