@openclaw/zalouser 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 +4 -3
- package/api.ts +9 -0
- package/channel-plugin-api.ts +3 -0
- package/contract-api.ts +2 -0
- package/doctor-contract-api.ts +1 -0
- package/index.ts +29 -24
- package/openclaw.plugin.json +288 -1
- package/package.json +38 -11
- package/runtime-api.ts +67 -0
- package/secret-contract-api.ts +4 -0
- package/setup-entry.ts +9 -0
- package/setup-plugin-api.ts +2 -0
- package/src/accounts.runtime.ts +1 -0
- package/src/accounts.test-mocks.ts +7 -3
- package/src/accounts.test.ts +53 -1
- package/src/accounts.ts +38 -24
- package/src/channel-api.ts +20 -0
- package/src/channel.adapters.ts +390 -0
- package/src/channel.directory.test.ts +47 -40
- package/src/channel.runtime.ts +12 -0
- package/src/channel.sendpayload.test.ts +41 -23
- package/src/channel.setup.test.ts +33 -0
- package/src/channel.setup.ts +12 -0
- package/src/channel.test.ts +231 -20
- package/src/channel.ts +176 -685
- package/src/config-schema.ts +5 -5
- package/src/directory.ts +54 -0
- package/src/doctor-contract.ts +156 -0
- package/src/doctor.test.ts +77 -0
- package/src/doctor.ts +37 -0
- package/src/group-policy.test.ts +4 -4
- package/src/group-policy.ts +4 -2
- package/src/monitor.account-scope.test.ts +2 -1
- package/src/monitor.group-gating.test.ts +162 -8
- package/src/monitor.ts +233 -173
- package/src/probe.ts +3 -2
- package/src/qr-temp-file.ts +1 -1
- package/src/reaction.ts +5 -2
- package/src/runtime.ts +6 -3
- package/src/security-audit.test.ts +80 -0
- package/src/security-audit.ts +71 -0
- package/src/send.test.ts +2 -2
- package/src/send.ts +3 -3
- package/src/session-route.ts +121 -0
- package/src/setup-core.ts +33 -0
- package/src/setup-surface.test.ts +363 -0
- package/src/setup-surface.ts +470 -0
- package/src/setup-test-helpers.ts +42 -0
- package/src/shared.ts +92 -0
- package/src/status-issues.test.ts +1 -13
- package/src/status-issues.ts +8 -2
- package/src/test-helpers.ts +1 -1
- package/src/text-styles.test.ts +1 -1
- package/src/text-styles.ts +5 -2
- package/src/tool.test.ts +66 -3
- package/src/tool.ts +76 -14
- package/src/types.ts +3 -3
- package/src/zalo-js.credentials.test.ts +465 -0
- package/src/zalo-js.test-mocks.ts +89 -0
- package/src/zalo-js.ts +491 -274
- package/src/zca-client.test.ts +24 -0
- package/src/zca-client.ts +24 -58
- package/src/zca-constants.ts +55 -0
- package/test-api.ts +21 -0
- package/tsconfig.json +16 -0
- package/CHANGELOG.md +0 -107
- package/src/onboarding.ts +0 -340
package/src/monitor.ts
CHANGED
|
@@ -1,37 +1,45 @@
|
|
|
1
|
+
import { mergeAllowlist, summarizeMapping } from "openclaw/plugin-sdk/allow-from";
|
|
2
|
+
import {
|
|
3
|
+
implicitMentionKindWhen,
|
|
4
|
+
resolveInboundMentionDecision,
|
|
5
|
+
} from "openclaw/plugin-sdk/channel-inbound";
|
|
6
|
+
import { createChannelPairingController } from "openclaw/plugin-sdk/channel-pairing";
|
|
1
7
|
import {
|
|
2
8
|
DM_GROUP_ACCESS_REASON,
|
|
9
|
+
resolveDmGroupAccessWithLists,
|
|
10
|
+
} from "openclaw/plugin-sdk/channel-policy";
|
|
11
|
+
import { createChannelReplyPipeline } from "openclaw/plugin-sdk/channel-reply-pipeline";
|
|
12
|
+
import { resolveSenderCommandAuthorization } from "openclaw/plugin-sdk/command-auth";
|
|
13
|
+
import type { MarkdownTableMode, OpenClawConfig } from "openclaw/plugin-sdk/config-types";
|
|
14
|
+
import { KeyedAsyncQueue } from "openclaw/plugin-sdk/core";
|
|
15
|
+
import { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/dangerous-name-runtime";
|
|
16
|
+
import { createDeferred } from "openclaw/plugin-sdk/extension-shared";
|
|
17
|
+
import {
|
|
18
|
+
evaluateGroupRouteAccessForPolicy,
|
|
19
|
+
resolveSenderScopedGroupPolicy,
|
|
20
|
+
} from "openclaw/plugin-sdk/group-access";
|
|
21
|
+
import {
|
|
3
22
|
DEFAULT_GROUP_HISTORY_LIMIT,
|
|
4
23
|
type HistoryEntry,
|
|
5
|
-
KeyedAsyncQueue,
|
|
6
24
|
buildPendingHistoryContextFromMap,
|
|
7
25
|
clearHistoryEntriesIfEnabled,
|
|
8
26
|
recordPendingHistoryEntryIfEnabled,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
} from "openclaw/plugin-sdk/zalouser";
|
|
27
|
+
} from "openclaw/plugin-sdk/reply-history";
|
|
28
|
+
import {
|
|
29
|
+
deliverTextOrMediaReply,
|
|
30
|
+
resolveSendableOutboundReplyParts,
|
|
31
|
+
type OutboundReplyPayload,
|
|
32
|
+
} from "openclaw/plugin-sdk/reply-payload";
|
|
33
|
+
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime";
|
|
17
34
|
import {
|
|
18
|
-
createTypingCallbacks,
|
|
19
|
-
createScopedPairingAccess,
|
|
20
|
-
createReplyPrefixOptions,
|
|
21
|
-
evaluateGroupRouteAccessForPolicy,
|
|
22
|
-
isDangerousNameMatchingEnabled,
|
|
23
|
-
issuePairingChallenge,
|
|
24
|
-
resolveOutboundMediaUrls,
|
|
25
|
-
mergeAllowlist,
|
|
26
|
-
resolveMentionGatingWithBypass,
|
|
27
|
-
resolveOpenProviderRuntimeGroupPolicy,
|
|
28
35
|
resolveDefaultGroupPolicy,
|
|
29
|
-
|
|
30
|
-
sendMediaWithLeadingCaption,
|
|
31
|
-
summarizeMapping,
|
|
36
|
+
resolveOpenProviderRuntimeGroupPolicy,
|
|
32
37
|
warnMissingProviderGroupPolicyFallbackOnce,
|
|
33
|
-
} from "openclaw/plugin-sdk/
|
|
34
|
-
import {
|
|
38
|
+
} from "openclaw/plugin-sdk/runtime-group-policy";
|
|
39
|
+
import {
|
|
40
|
+
normalizeLowercaseStringOrEmpty,
|
|
41
|
+
normalizeOptionalLowercaseString,
|
|
42
|
+
} from "openclaw/plugin-sdk/text-runtime";
|
|
35
43
|
import {
|
|
36
44
|
buildZalouserGroupCandidates,
|
|
37
45
|
findZalouserGroupEntry,
|
|
@@ -74,7 +82,7 @@ function normalizeZalouserEntry(entry: string): string {
|
|
|
74
82
|
function buildNameIndex<T>(items: T[], nameFn: (item: T) => string | undefined): Map<string, T[]> {
|
|
75
83
|
const index = new Map<string, T[]>();
|
|
76
84
|
for (const item of items) {
|
|
77
|
-
const name = nameFn(item)
|
|
85
|
+
const name = normalizeOptionalLowercaseString(nameFn(item));
|
|
78
86
|
if (!name) {
|
|
79
87
|
continue;
|
|
80
88
|
}
|
|
@@ -101,9 +109,9 @@ function resolveUserAllowlistEntries(
|
|
|
101
109
|
additions.push(entry);
|
|
102
110
|
continue;
|
|
103
111
|
}
|
|
104
|
-
const matches = byName.get(entry
|
|
112
|
+
const matches = byName.get(normalizeLowercaseStringOrEmpty(entry)) ?? [];
|
|
105
113
|
const match = matches[0];
|
|
106
|
-
const id = match?.userId
|
|
114
|
+
const id = match?.userId;
|
|
107
115
|
if (id) {
|
|
108
116
|
additions.push(id);
|
|
109
117
|
mapping.push(`${entry}->${id}`);
|
|
@@ -147,24 +155,24 @@ function resolveZalouserInboundSessionKey(params: {
|
|
|
147
155
|
return params.route.sessionKey;
|
|
148
156
|
}
|
|
149
157
|
|
|
150
|
-
const directSessionKey =
|
|
151
|
-
.buildAgentSessionKey({
|
|
158
|
+
const directSessionKey = normalizeLowercaseStringOrEmpty(
|
|
159
|
+
params.core.channel.routing.buildAgentSessionKey({
|
|
152
160
|
agentId: params.route.agentId,
|
|
153
161
|
channel: "zalouser",
|
|
154
162
|
accountId: params.route.accountId,
|
|
155
163
|
peer: { kind: "direct", id: params.senderId },
|
|
156
164
|
dmScope: resolveZalouserDmSessionScope(params.config),
|
|
157
165
|
identityLinks: params.config.session?.identityLinks,
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
const legacySessionKey =
|
|
161
|
-
.buildAgentSessionKey({
|
|
166
|
+
}),
|
|
167
|
+
);
|
|
168
|
+
const legacySessionKey = normalizeLowercaseStringOrEmpty(
|
|
169
|
+
params.core.channel.routing.buildAgentSessionKey({
|
|
162
170
|
agentId: params.route.agentId,
|
|
163
171
|
channel: "zalouser",
|
|
164
172
|
accountId: params.route.accountId,
|
|
165
173
|
peer: { kind: "group", id: params.senderId },
|
|
166
|
-
})
|
|
167
|
-
|
|
174
|
+
}),
|
|
175
|
+
);
|
|
168
176
|
const hasDirectSession =
|
|
169
177
|
params.core.channel.session.readSessionUpdatedAt({
|
|
170
178
|
storePath: params.storePath,
|
|
@@ -190,12 +198,12 @@ function isSenderAllowed(senderId: string | undefined, allowFrom: string[]): boo
|
|
|
190
198
|
if (allowFrom.includes("*")) {
|
|
191
199
|
return true;
|
|
192
200
|
}
|
|
193
|
-
const normalizedSenderId = senderId
|
|
201
|
+
const normalizedSenderId = normalizeOptionalLowercaseString(senderId);
|
|
194
202
|
if (!normalizedSenderId) {
|
|
195
203
|
return false;
|
|
196
204
|
}
|
|
197
205
|
return allowFrom.some((entry) => {
|
|
198
|
-
const normalized = entry
|
|
206
|
+
const normalized = normalizeLowercaseStringOrEmpty(entry).replace(/^(zalouser|zlu):/i, "");
|
|
199
207
|
return normalized === normalizedSenderId;
|
|
200
208
|
});
|
|
201
209
|
}
|
|
@@ -203,7 +211,7 @@ function isSenderAllowed(senderId: string | undefined, allowFrom: string[]): boo
|
|
|
203
211
|
function resolveGroupRequireMention(params: {
|
|
204
212
|
groupId: string;
|
|
205
213
|
groupName?: string | null;
|
|
206
|
-
groups: Record<string, {
|
|
214
|
+
groups: Record<string, { enabled?: boolean; requireMention?: boolean }>;
|
|
207
215
|
allowNameMatching?: boolean;
|
|
208
216
|
}): boolean {
|
|
209
217
|
const entry = findZalouserGroupEntry(
|
|
@@ -249,7 +257,7 @@ async function processMessage(
|
|
|
249
257
|
historyState: ZalouserGroupHistoryState,
|
|
250
258
|
statusSink?: (patch: { lastInboundAt?: number; lastOutboundAt?: number }) => void,
|
|
251
259
|
): Promise<void> {
|
|
252
|
-
const pairing =
|
|
260
|
+
const pairing = createChannelPairingController({
|
|
253
261
|
core,
|
|
254
262
|
channel: "zalouser",
|
|
255
263
|
accountId: account.accountId,
|
|
@@ -310,6 +318,7 @@ async function processMessage(
|
|
|
310
318
|
});
|
|
311
319
|
|
|
312
320
|
const groups = account.config.groups ?? {};
|
|
321
|
+
const routeAllowlistConfigured = Object.keys(groups).length > 0;
|
|
313
322
|
const allowNameMatching = isDangerousNameMatchingEnabled(account.config);
|
|
314
323
|
if (isGroup) {
|
|
315
324
|
const groupEntry = findZalouserGroupEntry(
|
|
@@ -324,7 +333,7 @@ async function processMessage(
|
|
|
324
333
|
);
|
|
325
334
|
const routeAccess = evaluateGroupRouteAccessForPolicy({
|
|
326
335
|
groupPolicy,
|
|
327
|
-
routeAllowlistConfigured
|
|
336
|
+
routeAllowlistConfigured,
|
|
328
337
|
routeMatched: Boolean(groupEntry),
|
|
329
338
|
routeEnabled: isZalouserGroupEntryAllowed(groupEntry),
|
|
330
339
|
});
|
|
@@ -349,21 +358,25 @@ async function processMessage(
|
|
|
349
358
|
const dmPolicy = account.config.dmPolicy ?? "pairing";
|
|
350
359
|
const configAllowFrom = (account.config.allowFrom ?? []).map((v) => String(v));
|
|
351
360
|
const configGroupAllowFrom = (account.config.groupAllowFrom ?? []).map((v) => String(v));
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
361
|
+
const senderGroupPolicy =
|
|
362
|
+
routeAllowlistConfigured && configGroupAllowFrom.length === 0
|
|
363
|
+
? groupPolicy
|
|
364
|
+
: resolveSenderScopedGroupPolicy({
|
|
365
|
+
groupPolicy,
|
|
366
|
+
groupAllowFrom: configGroupAllowFrom,
|
|
367
|
+
});
|
|
356
368
|
const storeAllowFrom =
|
|
357
|
-
!isGroup && dmPolicy !== "allowlist" &&
|
|
369
|
+
!isGroup && dmPolicy !== "allowlist" && dmPolicy !== "open"
|
|
358
370
|
? await pairing.readAllowFromStore().catch(() => [])
|
|
359
371
|
: [];
|
|
360
372
|
const accessDecision = resolveDmGroupAccessWithLists({
|
|
361
373
|
isGroup,
|
|
362
374
|
dmPolicy,
|
|
363
|
-
groupPolicy,
|
|
375
|
+
groupPolicy: senderGroupPolicy,
|
|
364
376
|
allowFrom: configAllowFrom,
|
|
365
377
|
groupAllowFrom: configGroupAllowFrom,
|
|
366
378
|
storeAllowFrom,
|
|
379
|
+
groupAllowFromFallbackToAllowFrom: false,
|
|
367
380
|
isSenderAllowed: (allowFrom) => isSenderAllowed(senderId, allowFrom),
|
|
368
381
|
});
|
|
369
382
|
if (isGroup && accessDecision.decision !== "allow") {
|
|
@@ -381,12 +394,10 @@ async function processMessage(
|
|
|
381
394
|
|
|
382
395
|
if (!isGroup && accessDecision.decision !== "allow") {
|
|
383
396
|
if (accessDecision.decision === "pairing") {
|
|
384
|
-
await
|
|
385
|
-
channel: "zalouser",
|
|
397
|
+
await pairing.issueChallenge({
|
|
386
398
|
senderId,
|
|
387
399
|
senderIdLine: `Your Zalo user id: ${senderId}`,
|
|
388
400
|
meta: { name: senderName || undefined },
|
|
389
|
-
upsertPairingRequest: pairing.upsertPairingRequest,
|
|
390
401
|
onCreated: () => {
|
|
391
402
|
logVerbose(core, runtime, `zalouser pairing request sender=${senderId}`);
|
|
392
403
|
},
|
|
@@ -425,6 +436,8 @@ async function processMessage(
|
|
|
425
436
|
configuredGroupAllowFrom: configGroupAllowFrom,
|
|
426
437
|
senderId,
|
|
427
438
|
isSenderAllowed,
|
|
439
|
+
channel: "zalouser",
|
|
440
|
+
accountId: account.accountId,
|
|
428
441
|
readAllowFromStore: async () => storeAllowFrom,
|
|
429
442
|
shouldComputeCommandAuthorized: (body, cfg) =>
|
|
430
443
|
core.channel.commands.shouldComputeCommandAuthorized(body, cfg),
|
|
@@ -479,28 +492,32 @@ async function processMessage(
|
|
|
479
492
|
})
|
|
480
493
|
: true;
|
|
481
494
|
const canDetectMention = mentionRegexes.length > 0 || explicitMention.canResolveExplicit;
|
|
482
|
-
const
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
+
const mentionDecision = resolveInboundMentionDecision({
|
|
496
|
+
facts: {
|
|
497
|
+
canDetectMention,
|
|
498
|
+
wasMentioned,
|
|
499
|
+
hasAnyMention: explicitMention.hasAnyMention,
|
|
500
|
+
implicitMentionKinds: implicitMentionKindWhen("quoted_bot", message.implicitMention === true),
|
|
501
|
+
},
|
|
502
|
+
policy: {
|
|
503
|
+
isGroup,
|
|
504
|
+
requireMention,
|
|
505
|
+
allowTextCommands: core.channel.commands.shouldHandleTextCommands({
|
|
506
|
+
cfg: config,
|
|
507
|
+
surface: "zalouser",
|
|
508
|
+
}),
|
|
509
|
+
hasControlCommand,
|
|
510
|
+
commandAuthorized: commandAuthorized === true,
|
|
511
|
+
},
|
|
495
512
|
});
|
|
496
|
-
if (isGroup && requireMention && !canDetectMention && !
|
|
513
|
+
if (isGroup && requireMention && !canDetectMention && !mentionDecision.effectiveWasMentioned) {
|
|
497
514
|
runtime.error?.(
|
|
498
515
|
`[${account.accountId}] zalouser mention required but detection unavailable ` +
|
|
499
516
|
`(missing mention regexes and bot self id); dropping group ${chatId}`,
|
|
500
517
|
);
|
|
501
518
|
return;
|
|
502
519
|
}
|
|
503
|
-
if (isGroup &&
|
|
520
|
+
if (isGroup && mentionDecision.shouldSkip) {
|
|
504
521
|
recordPendingHistoryEntryIfEnabled({
|
|
505
522
|
historyMap: historyState.groupHistories,
|
|
506
523
|
historyKey: historyKey ?? "",
|
|
@@ -577,102 +594,144 @@ async function processMessage(
|
|
|
577
594
|
: undefined;
|
|
578
595
|
|
|
579
596
|
const normalizedTo = isGroup ? `zalouser:group:${chatId}` : `zalouser:${chatId}`;
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
InboundHistory: inboundHistory,
|
|
585
|
-
RawBody: rawBody,
|
|
586
|
-
CommandBody: commandBody,
|
|
587
|
-
BodyForCommands: commandBody,
|
|
588
|
-
From: isGroup ? `zalouser:group:${chatId}` : `zalouser:${senderId}`,
|
|
589
|
-
To: normalizedTo,
|
|
590
|
-
SessionKey: inboundSessionKey,
|
|
591
|
-
AccountId: route.accountId,
|
|
592
|
-
ChatType: isGroup ? "group" : "direct",
|
|
593
|
-
ConversationLabel: fromLabel,
|
|
594
|
-
GroupSubject: isGroup ? groupName || undefined : undefined,
|
|
595
|
-
GroupChannel: isGroup ? groupName || undefined : undefined,
|
|
596
|
-
GroupMembers: isGroup ? groupMembers : undefined,
|
|
597
|
-
SenderName: senderName || undefined,
|
|
598
|
-
SenderId: senderId,
|
|
599
|
-
WasMentioned: isGroup ? mentionGate.effectiveWasMentioned : undefined,
|
|
600
|
-
CommandAuthorized: commandAuthorized,
|
|
601
|
-
Provider: "zalouser",
|
|
602
|
-
Surface: "zalouser",
|
|
603
|
-
MessageSid: resolveZalouserMessageSid({
|
|
604
|
-
msgId: message.msgId,
|
|
605
|
-
cliMsgId: message.cliMsgId,
|
|
606
|
-
fallback: `${message.timestampMs}`,
|
|
607
|
-
}),
|
|
608
|
-
MessageSidFull: formatZalouserMessageSidFull({
|
|
609
|
-
msgId: message.msgId,
|
|
610
|
-
cliMsgId: message.cliMsgId,
|
|
611
|
-
}),
|
|
612
|
-
OriginatingChannel: "zalouser",
|
|
613
|
-
OriginatingTo: normalizedTo,
|
|
597
|
+
const messageSid = resolveZalouserMessageSid({
|
|
598
|
+
msgId: message.msgId,
|
|
599
|
+
cliMsgId: message.cliMsgId,
|
|
600
|
+
fallback: `${message.timestampMs}`,
|
|
614
601
|
});
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
sessionKey: ctxPayload.SessionKey ?? route.sessionKey,
|
|
619
|
-
ctx: ctxPayload,
|
|
620
|
-
onRecordError: (err) => {
|
|
621
|
-
runtime.error?.(`zalouser: failed updating session meta: ${String(err)}`);
|
|
622
|
-
},
|
|
602
|
+
const messageSidFull = formatZalouserMessageSidFull({
|
|
603
|
+
msgId: message.msgId,
|
|
604
|
+
cliMsgId: message.cliMsgId,
|
|
623
605
|
});
|
|
624
606
|
|
|
625
|
-
const
|
|
626
|
-
cfg: config,
|
|
627
|
-
agentId: route.agentId,
|
|
607
|
+
const ctxPayload = core.channel.turn.buildContext({
|
|
628
608
|
channel: "zalouser",
|
|
629
|
-
accountId:
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
609
|
+
accountId: route.accountId,
|
|
610
|
+
messageId: messageSid,
|
|
611
|
+
messageIdFull: messageSidFull,
|
|
612
|
+
timestamp: message.timestampMs,
|
|
613
|
+
from: isGroup ? `zalouser:group:${chatId}` : `zalouser:${senderId}`,
|
|
614
|
+
sender: {
|
|
615
|
+
id: senderId,
|
|
616
|
+
name: senderName || undefined,
|
|
637
617
|
},
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
618
|
+
conversation: {
|
|
619
|
+
kind: isGroup ? "group" : "direct",
|
|
620
|
+
id: chatId,
|
|
621
|
+
label: fromLabel,
|
|
622
|
+
routePeer: {
|
|
623
|
+
kind: isGroup ? "group" : "direct",
|
|
624
|
+
id: chatId,
|
|
625
|
+
},
|
|
626
|
+
},
|
|
627
|
+
route: {
|
|
628
|
+
agentId: route.agentId,
|
|
629
|
+
accountId: route.accountId,
|
|
630
|
+
routeSessionKey: route.sessionKey,
|
|
631
|
+
dispatchSessionKey: inboundSessionKey,
|
|
632
|
+
},
|
|
633
|
+
reply: {
|
|
634
|
+
to: normalizedTo,
|
|
635
|
+
originatingTo: normalizedTo,
|
|
636
|
+
},
|
|
637
|
+
message: {
|
|
638
|
+
body: combinedBody,
|
|
639
|
+
bodyForAgent: rawBody,
|
|
640
|
+
rawBody,
|
|
641
|
+
commandBody,
|
|
642
|
+
inboundHistory,
|
|
643
|
+
envelopeFrom: fromLabel,
|
|
644
|
+
},
|
|
645
|
+
extra: {
|
|
646
|
+
BodyForCommands: commandBody,
|
|
647
|
+
GroupSubject: isGroup ? groupName || undefined : undefined,
|
|
648
|
+
GroupChannel: isGroup ? groupName || undefined : undefined,
|
|
649
|
+
GroupMembers: isGroup ? groupMembers : undefined,
|
|
650
|
+
WasMentioned: isGroup ? mentionDecision.effectiveWasMentioned : undefined,
|
|
651
|
+
CommandAuthorized: commandAuthorized,
|
|
643
652
|
},
|
|
644
653
|
});
|
|
645
654
|
|
|
646
|
-
|
|
647
|
-
ctx: ctxPayload,
|
|
655
|
+
const { onModelSelected, ...replyPipeline } = createChannelReplyPipeline({
|
|
648
656
|
cfg: config,
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
657
|
+
agentId: route.agentId,
|
|
658
|
+
channel: "zalouser",
|
|
659
|
+
accountId: account.accountId,
|
|
660
|
+
typing: {
|
|
661
|
+
start: async () => {
|
|
662
|
+
await sendTypingZalouser(chatId, {
|
|
655
663
|
profile: account.profile,
|
|
656
|
-
chatId,
|
|
657
664
|
isGroup,
|
|
658
|
-
runtime,
|
|
659
|
-
core,
|
|
660
|
-
config,
|
|
661
|
-
accountId: account.accountId,
|
|
662
|
-
statusSink,
|
|
663
|
-
tableMode: core.channel.text.resolveMarkdownTableMode({
|
|
664
|
-
cfg: config,
|
|
665
|
-
channel: "zalouser",
|
|
666
|
-
accountId: account.accountId,
|
|
667
|
-
}),
|
|
668
665
|
});
|
|
669
666
|
},
|
|
670
|
-
|
|
671
|
-
runtime.error(
|
|
667
|
+
onStartError: (err) => {
|
|
668
|
+
runtime.error?.(
|
|
669
|
+
`[${account.accountId}] zalouser typing start failed for ${chatId}: ${String(err)}`,
|
|
670
|
+
);
|
|
671
|
+
logVerbose(core, runtime, `zalouser typing failed for ${chatId}: ${String(err)}`);
|
|
672
672
|
},
|
|
673
673
|
},
|
|
674
|
-
|
|
675
|
-
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
await core.channel.turn.run({
|
|
677
|
+
channel: "zalouser",
|
|
678
|
+
accountId: account.accountId,
|
|
679
|
+
raw: message,
|
|
680
|
+
adapter: {
|
|
681
|
+
ingest: () => ({
|
|
682
|
+
id: messageSid ?? `${message.timestampMs}`,
|
|
683
|
+
timestamp: message.timestampMs,
|
|
684
|
+
rawText: rawBody,
|
|
685
|
+
textForAgent: rawBody,
|
|
686
|
+
textForCommands: commandBody,
|
|
687
|
+
raw: message,
|
|
688
|
+
}),
|
|
689
|
+
resolveTurn: () => ({
|
|
690
|
+
cfg: config,
|
|
691
|
+
channel: "zalouser",
|
|
692
|
+
accountId: account.accountId,
|
|
693
|
+
agentId: route.agentId,
|
|
694
|
+
routeSessionKey: route.sessionKey,
|
|
695
|
+
storePath,
|
|
696
|
+
ctxPayload,
|
|
697
|
+
recordInboundSession: core.channel.session.recordInboundSession,
|
|
698
|
+
dispatchReplyWithBufferedBlockDispatcher:
|
|
699
|
+
core.channel.reply.dispatchReplyWithBufferedBlockDispatcher,
|
|
700
|
+
delivery: {
|
|
701
|
+
deliver: async (payload) => {
|
|
702
|
+
await deliverZalouserReply({
|
|
703
|
+
payload: payload as { text?: string; mediaUrls?: string[]; mediaUrl?: string },
|
|
704
|
+
profile: account.profile,
|
|
705
|
+
chatId,
|
|
706
|
+
isGroup,
|
|
707
|
+
runtime,
|
|
708
|
+
core,
|
|
709
|
+
config,
|
|
710
|
+
accountId: account.accountId,
|
|
711
|
+
statusSink,
|
|
712
|
+
tableMode: core.channel.text.resolveMarkdownTableMode({
|
|
713
|
+
cfg: config,
|
|
714
|
+
channel: "zalouser",
|
|
715
|
+
accountId: account.accountId,
|
|
716
|
+
}),
|
|
717
|
+
});
|
|
718
|
+
},
|
|
719
|
+
onError: (err, info) => {
|
|
720
|
+
runtime.error(
|
|
721
|
+
`[${account.accountId}] Zalouser ${info.kind} reply failed: ${String(err)}`,
|
|
722
|
+
);
|
|
723
|
+
},
|
|
724
|
+
},
|
|
725
|
+
dispatcherOptions: replyPipeline,
|
|
726
|
+
replyOptions: {
|
|
727
|
+
onModelSelected,
|
|
728
|
+
},
|
|
729
|
+
record: {
|
|
730
|
+
onRecordError: (err) => {
|
|
731
|
+
runtime.error?.(`zalouser: failed updating session meta: ${String(err)}`);
|
|
732
|
+
},
|
|
733
|
+
},
|
|
734
|
+
}),
|
|
676
735
|
},
|
|
677
736
|
});
|
|
678
737
|
if (isGroup && historyKey) {
|
|
@@ -699,16 +758,31 @@ async function deliverZalouserReply(params: {
|
|
|
699
758
|
const { payload, profile, chatId, isGroup, runtime, core, config, accountId, statusSink } =
|
|
700
759
|
params;
|
|
701
760
|
const tableMode = params.tableMode ?? "code";
|
|
702
|
-
const
|
|
761
|
+
const reply = resolveSendableOutboundReplyParts(payload, {
|
|
762
|
+
text: core.channel.text.convertMarkdownTables(payload.text ?? "", tableMode),
|
|
763
|
+
});
|
|
703
764
|
const chunkMode = core.channel.text.resolveChunkMode(config, "zalouser", accountId);
|
|
704
765
|
const textChunkLimit = core.channel.text.resolveTextChunkLimit(config, "zalouser", accountId, {
|
|
705
766
|
fallbackLimit: ZALOUSER_TEXT_LIMIT,
|
|
706
767
|
});
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
768
|
+
await deliverTextOrMediaReply({
|
|
769
|
+
payload,
|
|
770
|
+
text: reply.text,
|
|
771
|
+
sendText: async (chunk) => {
|
|
772
|
+
try {
|
|
773
|
+
await sendMessageZalouser(chatId, chunk, {
|
|
774
|
+
profile,
|
|
775
|
+
isGroup,
|
|
776
|
+
textMode: "markdown",
|
|
777
|
+
textChunkMode: chunkMode,
|
|
778
|
+
textChunkLimit,
|
|
779
|
+
});
|
|
780
|
+
statusSink?.({ lastOutboundAt: Date.now() });
|
|
781
|
+
} catch (err) {
|
|
782
|
+
runtime.error(`Zalouser message send failed: ${String(err)}`);
|
|
783
|
+
}
|
|
784
|
+
},
|
|
785
|
+
sendMedia: async ({ mediaUrl, caption }) => {
|
|
712
786
|
logVerbose(core, runtime, `Sending media to ${chatId}`);
|
|
713
787
|
await sendMessageZalouser(chatId, caption ?? "", {
|
|
714
788
|
profile,
|
|
@@ -720,28 +794,14 @@ async function deliverZalouserReply(params: {
|
|
|
720
794
|
});
|
|
721
795
|
statusSink?.({ lastOutboundAt: Date.now() });
|
|
722
796
|
},
|
|
723
|
-
|
|
724
|
-
runtime.error(
|
|
797
|
+
onMediaError: (error) => {
|
|
798
|
+
runtime.error(
|
|
799
|
+
`Zalouser media send failed: ${
|
|
800
|
+
error instanceof Error ? error.message : JSON.stringify(error)
|
|
801
|
+
}`,
|
|
802
|
+
);
|
|
725
803
|
},
|
|
726
804
|
});
|
|
727
|
-
if (sentMedia) {
|
|
728
|
-
return;
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
if (text) {
|
|
732
|
-
try {
|
|
733
|
-
await sendMessageZalouser(chatId, text, {
|
|
734
|
-
profile,
|
|
735
|
-
isGroup,
|
|
736
|
-
textMode: "markdown",
|
|
737
|
-
textChunkMode: chunkMode,
|
|
738
|
-
textChunkLimit,
|
|
739
|
-
});
|
|
740
|
-
statusSink?.({ lastOutboundAt: Date.now() });
|
|
741
|
-
} catch (err) {
|
|
742
|
-
runtime.error(`Zalouser message send failed: ${String(err)}`);
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
805
|
}
|
|
746
806
|
|
|
747
807
|
export async function monitorZalouserProvider(
|
|
@@ -824,9 +884,9 @@ export async function monitorZalouserProvider(
|
|
|
824
884
|
mapping.push(`${entry}→${cleaned}`);
|
|
825
885
|
continue;
|
|
826
886
|
}
|
|
827
|
-
const matches = byName.get(cleaned
|
|
887
|
+
const matches = byName.get(normalizeLowercaseStringOrEmpty(cleaned)) ?? [];
|
|
828
888
|
const match = matches[0];
|
|
829
|
-
const id = match?.groupId
|
|
889
|
+
const id = match?.groupId;
|
|
830
890
|
if (id) {
|
|
831
891
|
if (!nextGroups[id]) {
|
|
832
892
|
nextGroups[id] = groupsConfig[entry];
|
package/src/probe.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { BaseProbeResult } from "openclaw/plugin-sdk/
|
|
1
|
+
import type { BaseProbeResult } from "openclaw/plugin-sdk/channel-contract";
|
|
2
|
+
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
2
3
|
import type { ZcaUserInfo } from "./types.js";
|
|
3
4
|
import { getZaloUserInfo } from "./zalo-js.js";
|
|
4
5
|
|
|
@@ -28,7 +29,7 @@ export async function probeZalouser(
|
|
|
28
29
|
} catch (error) {
|
|
29
30
|
return {
|
|
30
31
|
ok: false,
|
|
31
|
-
error:
|
|
32
|
+
error: formatErrorMessage(error),
|
|
32
33
|
};
|
|
33
34
|
}
|
|
34
35
|
}
|
package/src/qr-temp-file.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fsp from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/
|
|
3
|
+
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
|
|
4
4
|
|
|
5
5
|
export async function writeQrDataUrlToTempFile(
|
|
6
6
|
qrDataUrl: string,
|
package/src/reaction.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
|
2
|
+
import { Reactions } from "./zca-constants.js";
|
|
2
3
|
|
|
3
4
|
const REACTION_ALIAS_MAP = new Map<string, string>([
|
|
4
5
|
["like", Reactions.LIKE],
|
|
@@ -24,6 +25,8 @@ export function normalizeZaloReactionIcon(raw: string): string {
|
|
|
24
25
|
return Reactions.LIKE;
|
|
25
26
|
}
|
|
26
27
|
return (
|
|
27
|
-
REACTION_ALIAS_MAP.get(trimmed
|
|
28
|
+
REACTION_ALIAS_MAP.get(normalizeLowercaseStringOrEmpty(trimmed)) ??
|
|
29
|
+
REACTION_ALIAS_MAP.get(trimmed) ??
|
|
30
|
+
trimmed
|
|
28
31
|
);
|
|
29
32
|
}
|
package/src/runtime.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
1
|
+
import type { PluginRuntime } from "openclaw/plugin-sdk/core";
|
|
2
|
+
import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
|
|
3
3
|
|
|
4
4
|
const { setRuntime: setZalouserRuntime, getRuntime: getZalouserRuntime } =
|
|
5
|
-
createPluginRuntimeStore<PluginRuntime>(
|
|
5
|
+
createPluginRuntimeStore<PluginRuntime>({
|
|
6
|
+
pluginId: "zalouser",
|
|
7
|
+
errorMessage: "Zalouser runtime not initialized",
|
|
8
|
+
});
|
|
6
9
|
export { getZalouserRuntime, setZalouserRuntime };
|