@openclaw/zalouser 2026.1.29 → 2026.2.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/CHANGELOG.md +23 -0
- package/README.md +6 -2
- package/index.ts +1 -2
- package/openclaw.plugin.json +1 -3
- package/package.json +7 -4
- package/src/accounts.ts +38 -20
- package/src/channel.test.ts +3 -2
- package/src/channel.ts +107 -62
- package/src/monitor.ts +49 -33
- package/src/onboarding.ts +63 -47
- package/src/probe.ts +1 -1
- package/src/send.ts +15 -5
- package/src/status-issues.test.ts +0 -1
- package/src/status-issues.ts +12 -4
- package/src/tool.ts +29 -21
- package/src/types.ts +8 -2
- package/src/zca.ts +3 -9
package/src/channel.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
normalizeAccountId,
|
|
18
18
|
setAccountEnabledInConfigSection,
|
|
19
19
|
} from "openclaw/plugin-sdk";
|
|
20
|
+
import type { ZcaFriend, ZcaGroup, ZcaUserInfo } from "./types.js";
|
|
20
21
|
import {
|
|
21
22
|
listZalouserAccountIds,
|
|
22
23
|
resolveDefaultZalouserAccountId,
|
|
@@ -25,13 +26,12 @@ import {
|
|
|
25
26
|
checkZcaAuthenticated,
|
|
26
27
|
type ResolvedZalouserAccount,
|
|
27
28
|
} from "./accounts.js";
|
|
29
|
+
import { ZalouserConfigSchema } from "./config-schema.js";
|
|
28
30
|
import { zalouserOnboardingAdapter } from "./onboarding.js";
|
|
31
|
+
import { probeZalouser } from "./probe.js";
|
|
29
32
|
import { sendMessageZalouser } from "./send.js";
|
|
30
|
-
import { checkZcaInstalled, parseJsonOutput, runZca, runZcaInteractive } from "./zca.js";
|
|
31
|
-
import type { ZcaFriend, ZcaGroup, ZcaUserInfo } from "./types.js";
|
|
32
|
-
import { ZalouserConfigSchema } from "./config-schema.js";
|
|
33
33
|
import { collectZalouserStatusIssues } from "./status-issues.js";
|
|
34
|
-
import {
|
|
34
|
+
import { checkZcaInstalled, parseJsonOutput, runZca, runZcaInteractive } from "./zca.js";
|
|
35
35
|
|
|
36
36
|
const meta = {
|
|
37
37
|
id: "zalouser",
|
|
@@ -85,18 +85,20 @@ function resolveZalouserGroupToolPolicy(
|
|
|
85
85
|
params: ChannelGroupContext,
|
|
86
86
|
): GroupToolPolicyConfig | undefined {
|
|
87
87
|
const account = resolveZalouserAccountSync({
|
|
88
|
-
cfg: params.cfg
|
|
88
|
+
cfg: params.cfg,
|
|
89
89
|
accountId: params.accountId ?? undefined,
|
|
90
90
|
});
|
|
91
91
|
const groups = account.config.groups ?? {};
|
|
92
92
|
const groupId = params.groupId?.trim();
|
|
93
93
|
const groupChannel = params.groupChannel?.trim();
|
|
94
|
-
const candidates = [groupId, groupChannel, "*"].filter(
|
|
95
|
-
|
|
94
|
+
const candidates = [groupId, groupChannel, "*"].filter((value): value is string =>
|
|
95
|
+
Boolean(value),
|
|
96
96
|
);
|
|
97
97
|
for (const key of candidates) {
|
|
98
98
|
const entry = groups[key];
|
|
99
|
-
if (entry?.tools)
|
|
99
|
+
if (entry?.tools) {
|
|
100
|
+
return entry.tools;
|
|
101
|
+
}
|
|
100
102
|
}
|
|
101
103
|
return undefined;
|
|
102
104
|
}
|
|
@@ -111,8 +113,8 @@ export const zalouserDock: ChannelDock = {
|
|
|
111
113
|
outbound: { textChunkLimit: 2000 },
|
|
112
114
|
config: {
|
|
113
115
|
resolveAllowFrom: ({ cfg, accountId }) =>
|
|
114
|
-
(resolveZalouserAccountSync({ cfg: cfg
|
|
115
|
-
|
|
116
|
+
(resolveZalouserAccountSync({ cfg: cfg, accountId }).config.allowFrom ?? []).map((entry) =>
|
|
117
|
+
String(entry),
|
|
116
118
|
),
|
|
117
119
|
formatAllowFrom: ({ allowFrom }) =>
|
|
118
120
|
allowFrom
|
|
@@ -146,13 +148,12 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
146
148
|
reload: { configPrefixes: ["channels.zalouser"] },
|
|
147
149
|
configSchema: buildChannelConfigSchema(ZalouserConfigSchema),
|
|
148
150
|
config: {
|
|
149
|
-
listAccountIds: (cfg) => listZalouserAccountIds(cfg
|
|
150
|
-
resolveAccount: (cfg, accountId) =>
|
|
151
|
-
|
|
152
|
-
defaultAccountId: (cfg) => resolveDefaultZalouserAccountId(cfg as OpenClawConfig),
|
|
151
|
+
listAccountIds: (cfg) => listZalouserAccountIds(cfg),
|
|
152
|
+
resolveAccount: (cfg, accountId) => resolveZalouserAccountSync({ cfg: cfg, accountId }),
|
|
153
|
+
defaultAccountId: (cfg) => resolveDefaultZalouserAccountId(cfg),
|
|
153
154
|
setAccountEnabled: ({ cfg, accountId, enabled }) =>
|
|
154
155
|
setAccountEnabledInConfigSection({
|
|
155
|
-
cfg: cfg
|
|
156
|
+
cfg: cfg,
|
|
156
157
|
sectionKey: "zalouser",
|
|
157
158
|
accountId,
|
|
158
159
|
enabled,
|
|
@@ -160,10 +161,18 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
160
161
|
}),
|
|
161
162
|
deleteAccount: ({ cfg, accountId }) =>
|
|
162
163
|
deleteAccountFromConfigSection({
|
|
163
|
-
cfg: cfg
|
|
164
|
+
cfg: cfg,
|
|
164
165
|
sectionKey: "zalouser",
|
|
165
166
|
accountId,
|
|
166
|
-
clearBaseFields: [
|
|
167
|
+
clearBaseFields: [
|
|
168
|
+
"profile",
|
|
169
|
+
"name",
|
|
170
|
+
"dmPolicy",
|
|
171
|
+
"allowFrom",
|
|
172
|
+
"groupPolicy",
|
|
173
|
+
"groups",
|
|
174
|
+
"messagePrefix",
|
|
175
|
+
],
|
|
167
176
|
}),
|
|
168
177
|
isConfigured: async (account) => {
|
|
169
178
|
// Check if zca auth status is OK for this profile
|
|
@@ -180,8 +189,8 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
180
189
|
configured: undefined,
|
|
181
190
|
}),
|
|
182
191
|
resolveAllowFrom: ({ cfg, accountId }) =>
|
|
183
|
-
(resolveZalouserAccountSync({ cfg: cfg
|
|
184
|
-
|
|
192
|
+
(resolveZalouserAccountSync({ cfg: cfg, accountId }).config.allowFrom ?? []).map((entry) =>
|
|
193
|
+
String(entry),
|
|
185
194
|
),
|
|
186
195
|
formatAllowFrom: ({ allowFrom }) =>
|
|
187
196
|
allowFrom
|
|
@@ -193,9 +202,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
193
202
|
security: {
|
|
194
203
|
resolveDmPolicy: ({ cfg, accountId, account }) => {
|
|
195
204
|
const resolvedAccountId = accountId ?? account.accountId ?? DEFAULT_ACCOUNT_ID;
|
|
196
|
-
const useAccountPath = Boolean(
|
|
197
|
-
(cfg as OpenClawConfig).channels?.zalouser?.accounts?.[resolvedAccountId],
|
|
198
|
-
);
|
|
205
|
+
const useAccountPath = Boolean(cfg.channels?.zalouser?.accounts?.[resolvedAccountId]);
|
|
199
206
|
const basePath = useAccountPath
|
|
200
207
|
? `channels.zalouser.accounts.${resolvedAccountId}.`
|
|
201
208
|
: "channels.zalouser.";
|
|
@@ -220,7 +227,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
220
227
|
resolveAccountId: ({ accountId }) => normalizeAccountId(accountId),
|
|
221
228
|
applyAccountName: ({ cfg, accountId, name }) =>
|
|
222
229
|
applyAccountNameToChannelSection({
|
|
223
|
-
cfg: cfg
|
|
230
|
+
cfg: cfg,
|
|
224
231
|
channelKey: "zalouser",
|
|
225
232
|
accountId,
|
|
226
233
|
name,
|
|
@@ -228,7 +235,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
228
235
|
validateInput: () => null,
|
|
229
236
|
applyAccountConfig: ({ cfg, accountId, input }) => {
|
|
230
237
|
const namedConfig = applyAccountNameToChannelSection({
|
|
231
|
-
cfg: cfg
|
|
238
|
+
cfg: cfg,
|
|
232
239
|
channelKey: "zalouser",
|
|
233
240
|
accountId,
|
|
234
241
|
name: input.name,
|
|
@@ -260,9 +267,9 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
260
267
|
...next.channels?.zalouser,
|
|
261
268
|
enabled: true,
|
|
262
269
|
accounts: {
|
|
263
|
-
...
|
|
270
|
+
...next.channels?.zalouser?.accounts,
|
|
264
271
|
[accountId]: {
|
|
265
|
-
...
|
|
272
|
+
...next.channels?.zalouser?.accounts?.[accountId],
|
|
266
273
|
enabled: true,
|
|
267
274
|
},
|
|
268
275
|
},
|
|
@@ -274,13 +281,17 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
274
281
|
messaging: {
|
|
275
282
|
normalizeTarget: (raw) => {
|
|
276
283
|
const trimmed = raw?.trim();
|
|
277
|
-
if (!trimmed)
|
|
284
|
+
if (!trimmed) {
|
|
285
|
+
return undefined;
|
|
286
|
+
}
|
|
278
287
|
return trimmed.replace(/^(zalouser|zlu):/i, "");
|
|
279
288
|
},
|
|
280
289
|
targetResolver: {
|
|
281
290
|
looksLikeId: (raw) => {
|
|
282
291
|
const trimmed = raw.trim();
|
|
283
|
-
if (!trimmed)
|
|
292
|
+
if (!trimmed) {
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
284
295
|
return /^\d{3,}$/.test(trimmed);
|
|
285
296
|
},
|
|
286
297
|
hint: "<threadId>",
|
|
@@ -289,15 +300,22 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
289
300
|
directory: {
|
|
290
301
|
self: async ({ cfg, accountId, runtime }) => {
|
|
291
302
|
const ok = await checkZcaInstalled();
|
|
292
|
-
if (!ok)
|
|
293
|
-
|
|
294
|
-
|
|
303
|
+
if (!ok) {
|
|
304
|
+
throw new Error("Missing dependency: `zca` not found in PATH");
|
|
305
|
+
}
|
|
306
|
+
const account = resolveZalouserAccountSync({ cfg: cfg, accountId });
|
|
307
|
+
const result = await runZca(["me", "info", "-j"], {
|
|
308
|
+
profile: account.profile,
|
|
309
|
+
timeout: 10000,
|
|
310
|
+
});
|
|
295
311
|
if (!result.ok) {
|
|
296
312
|
runtime.error(result.stderr || "Failed to fetch profile");
|
|
297
313
|
return null;
|
|
298
314
|
}
|
|
299
315
|
const parsed = parseJsonOutput<ZcaUserInfo>(result.stdout);
|
|
300
|
-
if (!parsed?.userId)
|
|
316
|
+
if (!parsed?.userId) {
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
301
319
|
return mapUser({
|
|
302
320
|
id: String(parsed.userId),
|
|
303
321
|
name: parsed.displayName ?? null,
|
|
@@ -307,11 +325,11 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
307
325
|
},
|
|
308
326
|
listPeers: async ({ cfg, accountId, query, limit }) => {
|
|
309
327
|
const ok = await checkZcaInstalled();
|
|
310
|
-
if (!ok)
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
328
|
+
if (!ok) {
|
|
329
|
+
throw new Error("Missing dependency: `zca` not found in PATH");
|
|
330
|
+
}
|
|
331
|
+
const account = resolveZalouserAccountSync({ cfg: cfg, accountId });
|
|
332
|
+
const args = query?.trim() ? ["friend", "find", query.trim()] : ["friend", "list", "-j"];
|
|
315
333
|
const result = await runZca(args, { profile: account.profile, timeout: 15000 });
|
|
316
334
|
if (!result.ok) {
|
|
317
335
|
throw new Error(result.stderr || "Failed to list peers");
|
|
@@ -331,9 +349,14 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
331
349
|
},
|
|
332
350
|
listGroups: async ({ cfg, accountId, query, limit }) => {
|
|
333
351
|
const ok = await checkZcaInstalled();
|
|
334
|
-
if (!ok)
|
|
335
|
-
|
|
336
|
-
|
|
352
|
+
if (!ok) {
|
|
353
|
+
throw new Error("Missing dependency: `zca` not found in PATH");
|
|
354
|
+
}
|
|
355
|
+
const account = resolveZalouserAccountSync({ cfg: cfg, accountId });
|
|
356
|
+
const result = await runZca(["group", "list", "-j"], {
|
|
357
|
+
profile: account.profile,
|
|
358
|
+
timeout: 15000,
|
|
359
|
+
});
|
|
337
360
|
if (!result.ok) {
|
|
338
361
|
throw new Error(result.stderr || "Failed to list groups");
|
|
339
362
|
}
|
|
@@ -355,8 +378,10 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
355
378
|
},
|
|
356
379
|
listGroupMembers: async ({ cfg, accountId, groupId, limit }) => {
|
|
357
380
|
const ok = await checkZcaInstalled();
|
|
358
|
-
if (!ok)
|
|
359
|
-
|
|
381
|
+
if (!ok) {
|
|
382
|
+
throw new Error("Missing dependency: `zca` not found in PATH");
|
|
383
|
+
}
|
|
384
|
+
const account = resolveZalouserAccountSync({ cfg: cfg, accountId });
|
|
360
385
|
const result = await runZca(["group", "members", groupId, "-j"], {
|
|
361
386
|
profile: account.profile,
|
|
362
387
|
timeout: 20000,
|
|
@@ -364,12 +389,16 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
364
389
|
if (!result.ok) {
|
|
365
390
|
throw new Error(result.stderr || "Failed to list group members");
|
|
366
391
|
}
|
|
367
|
-
const parsed = parseJsonOutput<Array<Partial<ZcaFriend> & { userId?: string | number }>>(
|
|
392
|
+
const parsed = parseJsonOutput<Array<Partial<ZcaFriend> & { userId?: string | number }>>(
|
|
393
|
+
result.stdout,
|
|
394
|
+
);
|
|
368
395
|
const rows = Array.isArray(parsed)
|
|
369
396
|
? parsed
|
|
370
397
|
.map((m) => {
|
|
371
398
|
const id = m.userId ?? (m as { id?: string | number }).id;
|
|
372
|
-
if (!id)
|
|
399
|
+
if (!id) {
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
373
402
|
return mapUser({
|
|
374
403
|
id: String(id),
|
|
375
404
|
name: (m as { displayName?: string }).displayName ?? null,
|
|
@@ -398,7 +427,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
398
427
|
}
|
|
399
428
|
try {
|
|
400
429
|
const account = resolveZalouserAccountSync({
|
|
401
|
-
cfg: cfg
|
|
430
|
+
cfg: cfg,
|
|
402
431
|
accountId: accountId ?? DEFAULT_ACCOUNT_ID,
|
|
403
432
|
});
|
|
404
433
|
const args =
|
|
@@ -408,7 +437,9 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
408
437
|
: ["friend", "list", "-j"]
|
|
409
438
|
: ["group", "list", "-j"];
|
|
410
439
|
const result = await runZca(args, { profile: account.profile, timeout: 15000 });
|
|
411
|
-
if (!result.ok)
|
|
440
|
+
if (!result.ok) {
|
|
441
|
+
throw new Error(result.stderr || "zca lookup failed");
|
|
442
|
+
}
|
|
412
443
|
if (kind === "user") {
|
|
413
444
|
const parsed = parseJsonOutput<ZcaFriend[]>(result.stdout) ?? [];
|
|
414
445
|
const matches = Array.isArray(parsed)
|
|
@@ -433,7 +464,8 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
433
464
|
name: g.name ?? undefined,
|
|
434
465
|
}))
|
|
435
466
|
: [];
|
|
436
|
-
const best =
|
|
467
|
+
const best =
|
|
468
|
+
matches.find((g) => g.name?.toLowerCase() === trimmed.toLowerCase()) ?? matches[0];
|
|
437
469
|
results.push({
|
|
438
470
|
input,
|
|
439
471
|
resolved: Boolean(best?.id),
|
|
@@ -454,9 +486,11 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
454
486
|
idLabel: "zalouserUserId",
|
|
455
487
|
normalizeAllowEntry: (entry) => entry.replace(/^(zalouser|zlu):/i, ""),
|
|
456
488
|
notifyApproval: async ({ cfg, id }) => {
|
|
457
|
-
const account = resolveZalouserAccountSync({ cfg: cfg
|
|
489
|
+
const account = resolveZalouserAccountSync({ cfg: cfg });
|
|
458
490
|
const authenticated = await checkZcaAuthenticated(account.profile);
|
|
459
|
-
if (!authenticated)
|
|
491
|
+
if (!authenticated) {
|
|
492
|
+
throw new Error("Zalouser not authenticated");
|
|
493
|
+
}
|
|
460
494
|
await sendMessageZalouser(id, "Your pairing request has been approved.", {
|
|
461
495
|
profile: account.profile,
|
|
462
496
|
});
|
|
@@ -465,7 +499,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
465
499
|
auth: {
|
|
466
500
|
login: async ({ cfg, accountId, runtime }) => {
|
|
467
501
|
const account = resolveZalouserAccountSync({
|
|
468
|
-
cfg: cfg
|
|
502
|
+
cfg: cfg,
|
|
469
503
|
accountId: accountId ?? DEFAULT_ACCOUNT_ID,
|
|
470
504
|
});
|
|
471
505
|
const ok = await checkZcaInstalled();
|
|
@@ -486,8 +520,12 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
486
520
|
outbound: {
|
|
487
521
|
deliveryMode: "direct",
|
|
488
522
|
chunker: (text, limit) => {
|
|
489
|
-
if (!text)
|
|
490
|
-
|
|
523
|
+
if (!text) {
|
|
524
|
+
return [];
|
|
525
|
+
}
|
|
526
|
+
if (limit <= 0 || text.length <= limit) {
|
|
527
|
+
return [text];
|
|
528
|
+
}
|
|
491
529
|
const chunks: string[] = [];
|
|
492
530
|
let remaining = text;
|
|
493
531
|
while (remaining.length > limit) {
|
|
@@ -495,21 +533,27 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
495
533
|
const lastNewline = window.lastIndexOf("\n");
|
|
496
534
|
const lastSpace = window.lastIndexOf(" ");
|
|
497
535
|
let breakIdx = lastNewline > 0 ? lastNewline : lastSpace;
|
|
498
|
-
if (breakIdx <= 0)
|
|
536
|
+
if (breakIdx <= 0) {
|
|
537
|
+
breakIdx = limit;
|
|
538
|
+
}
|
|
499
539
|
const rawChunk = remaining.slice(0, breakIdx);
|
|
500
540
|
const chunk = rawChunk.trimEnd();
|
|
501
|
-
if (chunk.length > 0)
|
|
541
|
+
if (chunk.length > 0) {
|
|
542
|
+
chunks.push(chunk);
|
|
543
|
+
}
|
|
502
544
|
const brokeOnSeparator = breakIdx < remaining.length && /\s/.test(remaining[breakIdx]);
|
|
503
545
|
const nextStart = Math.min(remaining.length, breakIdx + (brokeOnSeparator ? 1 : 0));
|
|
504
546
|
remaining = remaining.slice(nextStart).trimStart();
|
|
505
547
|
}
|
|
506
|
-
if (remaining.length)
|
|
548
|
+
if (remaining.length) {
|
|
549
|
+
chunks.push(remaining);
|
|
550
|
+
}
|
|
507
551
|
return chunks;
|
|
508
552
|
},
|
|
509
553
|
chunkerMode: "text",
|
|
510
554
|
textChunkLimit: 2000,
|
|
511
555
|
sendText: async ({ to, text, accountId, cfg }) => {
|
|
512
|
-
const account = resolveZalouserAccountSync({ cfg: cfg
|
|
556
|
+
const account = resolveZalouserAccountSync({ cfg: cfg, accountId });
|
|
513
557
|
const result = await sendMessageZalouser(to, text, { profile: account.profile });
|
|
514
558
|
return {
|
|
515
559
|
channel: "zalouser",
|
|
@@ -519,7 +563,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
519
563
|
};
|
|
520
564
|
},
|
|
521
565
|
sendMedia: async ({ to, text, mediaUrl, accountId, cfg }) => {
|
|
522
|
-
const account = resolveZalouserAccountSync({ cfg: cfg
|
|
566
|
+
const account = resolveZalouserAccountSync({ cfg: cfg, accountId });
|
|
523
567
|
const result = await sendMessageZalouser(to, text, {
|
|
524
568
|
profile: account.profile,
|
|
525
569
|
mediaUrl,
|
|
@@ -550,8 +594,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
550
594
|
probe: snapshot.probe,
|
|
551
595
|
lastProbeAt: snapshot.lastProbeAt ?? null,
|
|
552
596
|
}),
|
|
553
|
-
probeAccount: async ({ account, timeoutMs }) =>
|
|
554
|
-
probeZalouser(account.profile, timeoutMs),
|
|
597
|
+
probeAccount: async ({ account, timeoutMs }) => probeZalouser(account.profile, timeoutMs),
|
|
555
598
|
buildAccountSnapshot: async ({ account, runtime }) => {
|
|
556
599
|
const zcaInstalled = await checkZcaInstalled();
|
|
557
600
|
const configured = zcaInstalled ? await checkZcaAuthenticated(account.profile) : false;
|
|
@@ -564,7 +607,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
564
607
|
running: runtime?.running ?? false,
|
|
565
608
|
lastStartAt: runtime?.lastStartAt ?? null,
|
|
566
609
|
lastStopAt: runtime?.lastStopAt ?? null,
|
|
567
|
-
lastError: configured ? (runtime?.lastError ?? null) : runtime?.lastError ?? configError,
|
|
610
|
+
lastError: configured ? (runtime?.lastError ?? null) : (runtime?.lastError ?? configError),
|
|
568
611
|
lastInboundAt: runtime?.lastInboundAt ?? null,
|
|
569
612
|
lastOutboundAt: runtime?.lastOutboundAt ?? null,
|
|
570
613
|
dmPolicy: account.config.dmPolicy ?? "pairing",
|
|
@@ -577,7 +620,9 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
577
620
|
let userLabel = "";
|
|
578
621
|
try {
|
|
579
622
|
const userInfo = await getZcaUserInfo(account.profile);
|
|
580
|
-
if (userInfo?.displayName)
|
|
623
|
+
if (userInfo?.displayName) {
|
|
624
|
+
userLabel = ` (${userInfo.displayName})`;
|
|
625
|
+
}
|
|
581
626
|
ctx.setStatus({
|
|
582
627
|
accountId: account.accountId,
|
|
583
628
|
user: userInfo,
|
|
@@ -589,7 +634,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
589
634
|
const { monitorZalouserProvider } = await import("./monitor.js");
|
|
590
635
|
return monitorZalouserProvider({
|
|
591
636
|
account,
|
|
592
|
-
config: ctx.cfg
|
|
637
|
+
config: ctx.cfg,
|
|
593
638
|
runtime: ctx.runtime,
|
|
594
639
|
abortSignal: ctx.abortSignal,
|
|
595
640
|
statusSink: (patch) => ctx.setStatus({ accountId: ctx.accountId, ...patch }),
|
package/src/monitor.ts
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
import type { ChildProcess } from "node:child_process";
|
|
2
|
-
|
|
3
2
|
import type { OpenClawConfig, MarkdownTableMode, RuntimeEnv } from "openclaw/plugin-sdk";
|
|
4
3
|
import { mergeAllowlist, summarizeMapping } from "openclaw/plugin-sdk";
|
|
5
|
-
import {
|
|
6
|
-
import type {
|
|
7
|
-
ResolvedZalouserAccount,
|
|
8
|
-
ZcaFriend,
|
|
9
|
-
ZcaGroup,
|
|
10
|
-
ZcaMessage,
|
|
11
|
-
} from "./types.js";
|
|
4
|
+
import type { ResolvedZalouserAccount, ZcaFriend, ZcaGroup, ZcaMessage } from "./types.js";
|
|
12
5
|
import { getZalouserRuntime } from "./runtime.js";
|
|
6
|
+
import { sendMessageZalouser } from "./send.js";
|
|
13
7
|
import { parseJsonOutput, runZca, runZcaStreaming } from "./zca.js";
|
|
14
8
|
|
|
15
9
|
export type ZalouserMonitorOptions = {
|
|
@@ -30,14 +24,13 @@ function normalizeZalouserEntry(entry: string): string {
|
|
|
30
24
|
return entry.replace(/^(zalouser|zlu):/i, "").trim();
|
|
31
25
|
}
|
|
32
26
|
|
|
33
|
-
function buildNameIndex<T>(
|
|
34
|
-
items: T[],
|
|
35
|
-
nameFn: (item: T) => string | undefined,
|
|
36
|
-
): Map<string, T[]> {
|
|
27
|
+
function buildNameIndex<T>(items: T[], nameFn: (item: T) => string | undefined): Map<string, T[]> {
|
|
37
28
|
const index = new Map<string, T[]>();
|
|
38
29
|
for (const item of items) {
|
|
39
30
|
const name = nameFn(item)?.trim().toLowerCase();
|
|
40
|
-
if (!name)
|
|
31
|
+
if (!name) {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
41
34
|
const list = index.get(name) ?? [];
|
|
42
35
|
list.push(item);
|
|
43
36
|
index.set(name, list);
|
|
@@ -54,7 +47,9 @@ function logVerbose(core: ZalouserCoreRuntime, runtime: RuntimeEnv, message: str
|
|
|
54
47
|
}
|
|
55
48
|
|
|
56
49
|
function isSenderAllowed(senderId: string, allowFrom: string[]): boolean {
|
|
57
|
-
if (allowFrom.includes("*"))
|
|
50
|
+
if (allowFrom.includes("*")) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
58
53
|
const normalizedSenderId = senderId.toLowerCase();
|
|
59
54
|
return allowFrom.some((entry) => {
|
|
60
55
|
const normalized = entry.toLowerCase().replace(/^(zalouser|zlu):/i, "");
|
|
@@ -64,7 +59,9 @@ function isSenderAllowed(senderId: string, allowFrom: string[]): boolean {
|
|
|
64
59
|
|
|
65
60
|
function normalizeGroupSlug(raw?: string | null): string {
|
|
66
61
|
const trimmed = raw?.trim().toLowerCase() ?? "";
|
|
67
|
-
if (!trimmed)
|
|
62
|
+
if (!trimmed) {
|
|
63
|
+
return "";
|
|
64
|
+
}
|
|
68
65
|
return trimmed
|
|
69
66
|
.replace(/^#/, "")
|
|
70
67
|
.replace(/[^a-z0-9]+/g, "-")
|
|
@@ -78,7 +75,9 @@ function isGroupAllowed(params: {
|
|
|
78
75
|
}): boolean {
|
|
79
76
|
const groups = params.groups ?? {};
|
|
80
77
|
const keys = Object.keys(groups);
|
|
81
|
-
if (keys.length === 0)
|
|
78
|
+
if (keys.length === 0) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
82
81
|
const candidates = [
|
|
83
82
|
params.groupId,
|
|
84
83
|
`group:${params.groupId}`,
|
|
@@ -87,11 +86,15 @@ function isGroupAllowed(params: {
|
|
|
87
86
|
].filter(Boolean);
|
|
88
87
|
for (const candidate of candidates) {
|
|
89
88
|
const entry = groups[candidate];
|
|
90
|
-
if (!entry)
|
|
89
|
+
if (!entry) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
91
92
|
return entry.allow !== false && entry.enabled !== false;
|
|
92
93
|
}
|
|
93
94
|
const wildcard = groups["*"];
|
|
94
|
-
if (wildcard)
|
|
95
|
+
if (wildcard) {
|
|
96
|
+
return wildcard.allow !== false && wildcard.enabled !== false;
|
|
97
|
+
}
|
|
95
98
|
return false;
|
|
96
99
|
}
|
|
97
100
|
|
|
@@ -112,7 +115,9 @@ function startZcaListener(
|
|
|
112
115
|
buffer = lines.pop() ?? "";
|
|
113
116
|
for (const line of lines) {
|
|
114
117
|
const trimmed = line.trim();
|
|
115
|
-
if (!trimmed)
|
|
118
|
+
if (!trimmed) {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
116
121
|
try {
|
|
117
122
|
const parsed = JSON.parse(trimmed) as ZcaMessage;
|
|
118
123
|
onMessage(parsed);
|
|
@@ -126,7 +131,9 @@ function startZcaListener(
|
|
|
126
131
|
|
|
127
132
|
proc.stderr?.on("data", (data: Buffer) => {
|
|
128
133
|
const text = data.toString().trim();
|
|
129
|
-
if (text)
|
|
134
|
+
if (text) {
|
|
135
|
+
runtime.error(`[zalouser] zca stderr: ${text}`);
|
|
136
|
+
}
|
|
130
137
|
});
|
|
131
138
|
|
|
132
139
|
void promise.then((result) => {
|
|
@@ -155,7 +162,9 @@ async function processMessage(
|
|
|
155
162
|
statusSink?: (patch: { lastInboundAt?: number; lastOutboundAt?: number }) => void,
|
|
156
163
|
): Promise<void> {
|
|
157
164
|
const { threadId, content, timestamp, metadata } = message;
|
|
158
|
-
if (!content?.trim())
|
|
165
|
+
if (!content?.trim()) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
159
168
|
|
|
160
169
|
const isGroup = metadata?.isGroup ?? false;
|
|
161
170
|
const senderId = metadata?.fromId ?? threadId;
|
|
@@ -183,10 +192,7 @@ async function processMessage(
|
|
|
183
192
|
const dmPolicy = account.config.dmPolicy ?? "pairing";
|
|
184
193
|
const configAllowFrom = (account.config.allowFrom ?? []).map((v) => String(v));
|
|
185
194
|
const rawBody = content.trim();
|
|
186
|
-
const shouldComputeAuth = core.channel.commands.shouldComputeCommandAuthorized(
|
|
187
|
-
rawBody,
|
|
188
|
-
config,
|
|
189
|
-
);
|
|
195
|
+
const shouldComputeAuth = core.channel.commands.shouldComputeCommandAuthorized(rawBody, config);
|
|
190
196
|
const storeAllowFrom =
|
|
191
197
|
!isGroup && (dmPolicy !== "open" || shouldComputeAuth)
|
|
192
198
|
? await core.channel.pairing.readAllowFromStore("zalouser").catch(() => [])
|
|
@@ -197,7 +203,9 @@ async function processMessage(
|
|
|
197
203
|
const commandAuthorized = shouldComputeAuth
|
|
198
204
|
? core.channel.commands.resolveCommandAuthorizedFromAuthorizers({
|
|
199
205
|
useAccessGroups,
|
|
200
|
-
authorizers: [
|
|
206
|
+
authorizers: [
|
|
207
|
+
{ configured: effectiveAllowFrom.length > 0, allowed: senderAllowedForCommands },
|
|
208
|
+
],
|
|
201
209
|
})
|
|
202
210
|
: undefined;
|
|
203
211
|
|
|
@@ -256,11 +264,17 @@ async function processMessage(
|
|
|
256
264
|
core.channel.commands.isControlCommandMessage(rawBody, config) &&
|
|
257
265
|
commandAuthorized !== true
|
|
258
266
|
) {
|
|
259
|
-
logVerbose(
|
|
267
|
+
logVerbose(
|
|
268
|
+
core,
|
|
269
|
+
runtime,
|
|
270
|
+
`zalouser: drop control command from unauthorized sender ${senderId}`,
|
|
271
|
+
);
|
|
260
272
|
return;
|
|
261
273
|
}
|
|
262
274
|
|
|
263
|
-
const peer = isGroup
|
|
275
|
+
const peer = isGroup
|
|
276
|
+
? { kind: "group" as const, id: chatId }
|
|
277
|
+
: { kind: "group" as const, id: senderId };
|
|
264
278
|
|
|
265
279
|
const route = core.channel.routing.resolveAgentRoute({
|
|
266
280
|
cfg: config,
|
|
@@ -343,9 +357,7 @@ async function processMessage(
|
|
|
343
357
|
});
|
|
344
358
|
},
|
|
345
359
|
onError: (err, info) => {
|
|
346
|
-
runtime.error(
|
|
347
|
-
`[${account.accountId}] Zalouser ${info.kind} reply failed: ${String(err)}`,
|
|
348
|
-
);
|
|
360
|
+
runtime.error(`[${account.accountId}] Zalouser ${info.kind} reply failed: ${String(err)}`);
|
|
349
361
|
},
|
|
350
362
|
},
|
|
351
363
|
});
|
|
@@ -481,7 +493,9 @@ export async function monitorZalouserProvider(
|
|
|
481
493
|
for (const entry of groupKeys) {
|
|
482
494
|
const cleaned = normalizeZalouserEntry(entry);
|
|
483
495
|
if (/^\d+$/.test(cleaned)) {
|
|
484
|
-
if (!nextGroups[cleaned])
|
|
496
|
+
if (!nextGroups[cleaned]) {
|
|
497
|
+
nextGroups[cleaned] = groupsConfig[entry];
|
|
498
|
+
}
|
|
485
499
|
mapping.push(`${entry}→${cleaned}`);
|
|
486
500
|
continue;
|
|
487
501
|
}
|
|
@@ -489,7 +503,9 @@ export async function monitorZalouserProvider(
|
|
|
489
503
|
const match = matches[0];
|
|
490
504
|
const id = match?.groupId ? String(match.groupId) : undefined;
|
|
491
505
|
if (id) {
|
|
492
|
-
if (!nextGroups[id])
|
|
506
|
+
if (!nextGroups[id]) {
|
|
507
|
+
nextGroups[id] = groupsConfig[entry];
|
|
508
|
+
}
|
|
493
509
|
mapping.push(`${entry}→${id}`);
|
|
494
510
|
} else {
|
|
495
511
|
unresolved.push(entry);
|