@clawdbot/zalouser 2026.1.16 → 2026.1.21
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/clawdbot.plugin.json +11 -0
- package/index.ts +7 -3
- package/package.json +21 -2
- package/src/accounts.ts +17 -21
- package/src/channel.ts +196 -103
- package/src/config-schema.ts +24 -0
- package/src/monitor.ts +229 -46
- package/src/onboarding.ts +259 -83
- package/src/probe.ts +28 -0
- package/src/runtime.ts +14 -0
- package/src/status-issues.test.ts +58 -0
- package/src/status-issues.ts +81 -0
- package/src/types.ts +4 -11
- package/src/zca.ts +26 -1
- package/src/core-bridge.ts +0 -171
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 2026.1.21
|
|
4
|
+
|
|
5
|
+
### Changes
|
|
6
|
+
- Version alignment with core Clawdbot release numbers.
|
|
7
|
+
|
|
8
|
+
## 2026.1.20
|
|
9
|
+
|
|
10
|
+
### Changes
|
|
11
|
+
- Version alignment with core Clawdbot release numbers.
|
|
12
|
+
|
|
13
|
+
## 2026.1.17-1
|
|
14
|
+
|
|
15
|
+
- Initial version with full channel plugin support
|
|
16
|
+
- QR code login via zca-cli
|
|
17
|
+
- Multi-account support
|
|
18
|
+
- Agent tool for sending messages
|
|
19
|
+
- Group and DM policy support
|
|
20
|
+
- ChannelDock for lightweight shared metadata
|
|
21
|
+
- Zod-based config schema validation
|
|
22
|
+
- Setup adapter for programmatic configuration
|
|
23
|
+
- Dedicated probe and status issues modules
|
package/index.ts
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
|
-
import type { ClawdbotPluginApi } from "
|
|
1
|
+
import type { ClawdbotPluginApi } from "clawdbot/plugin-sdk";
|
|
2
|
+
import { emptyPluginConfigSchema } from "clawdbot/plugin-sdk";
|
|
2
3
|
|
|
3
|
-
import { zalouserPlugin } from "./src/channel.js";
|
|
4
|
+
import { zalouserDock, zalouserPlugin } from "./src/channel.js";
|
|
4
5
|
import { ZalouserToolSchema, executeZalouserTool } from "./src/tool.js";
|
|
6
|
+
import { setZalouserRuntime } from "./src/runtime.js";
|
|
5
7
|
|
|
6
8
|
const plugin = {
|
|
7
9
|
id: "zalouser",
|
|
8
10
|
name: "Zalo Personal",
|
|
9
11
|
description: "Zalo personal account messaging via zca-cli",
|
|
12
|
+
configSchema: emptyPluginConfigSchema(),
|
|
10
13
|
register(api: ClawdbotPluginApi) {
|
|
14
|
+
setZalouserRuntime(api.runtime);
|
|
11
15
|
// Register channel plugin (for onboarding & gateway)
|
|
12
|
-
api.registerChannel(zalouserPlugin);
|
|
16
|
+
api.registerChannel({ plugin: zalouserPlugin, dock: zalouserDock });
|
|
13
17
|
|
|
14
18
|
// Register agent tool
|
|
15
19
|
api.registerTool({
|
package/package.json
CHANGED
|
@@ -1,14 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clawdbot/zalouser",
|
|
3
|
-
"version": "2026.1.
|
|
3
|
+
"version": "2026.1.21",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Clawdbot Zalo Personal Account plugin via zca-cli",
|
|
6
6
|
"dependencies": {
|
|
7
|
+
"clawdbot": "workspace:*",
|
|
7
8
|
"@sinclair/typebox": "0.34.47"
|
|
8
9
|
},
|
|
9
10
|
"clawdbot": {
|
|
10
11
|
"extensions": [
|
|
11
12
|
"./index.ts"
|
|
12
|
-
]
|
|
13
|
+
],
|
|
14
|
+
"channel": {
|
|
15
|
+
"id": "zalouser",
|
|
16
|
+
"label": "Zalo Personal",
|
|
17
|
+
"selectionLabel": "Zalo (Personal Account)",
|
|
18
|
+
"docsPath": "/channels/zalouser",
|
|
19
|
+
"docsLabel": "zalouser",
|
|
20
|
+
"blurb": "Zalo personal account via QR code login.",
|
|
21
|
+
"aliases": [
|
|
22
|
+
"zlu"
|
|
23
|
+
],
|
|
24
|
+
"order": 85,
|
|
25
|
+
"quickstartAllowFrom": true
|
|
26
|
+
},
|
|
27
|
+
"install": {
|
|
28
|
+
"npmSpec": "@clawdbot/zalouser",
|
|
29
|
+
"localPath": "extensions/zalouser",
|
|
30
|
+
"defaultChoice": "npm"
|
|
31
|
+
}
|
|
13
32
|
}
|
|
14
33
|
}
|
package/src/accounts.ts
CHANGED
|
@@ -1,25 +1,22 @@
|
|
|
1
|
+
import type { ClawdbotConfig } from "clawdbot/plugin-sdk";
|
|
2
|
+
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "clawdbot/plugin-sdk";
|
|
3
|
+
|
|
1
4
|
import { runZca, parseJsonOutput } from "./zca.js";
|
|
2
|
-
import {
|
|
3
|
-
DEFAULT_ACCOUNT_ID,
|
|
4
|
-
type CoreConfig,
|
|
5
|
-
type ResolvedZalouserAccount,
|
|
6
|
-
type ZalouserAccountConfig,
|
|
7
|
-
type ZalouserConfig,
|
|
8
|
-
} from "./types.js";
|
|
5
|
+
import type { ResolvedZalouserAccount, ZalouserAccountConfig, ZalouserConfig } from "./types.js";
|
|
9
6
|
|
|
10
|
-
function listConfiguredAccountIds(cfg:
|
|
7
|
+
function listConfiguredAccountIds(cfg: ClawdbotConfig): string[] {
|
|
11
8
|
const accounts = (cfg.channels?.zalouser as ZalouserConfig | undefined)?.accounts;
|
|
12
9
|
if (!accounts || typeof accounts !== "object") return [];
|
|
13
10
|
return Object.keys(accounts).filter(Boolean);
|
|
14
11
|
}
|
|
15
12
|
|
|
16
|
-
export function listZalouserAccountIds(cfg:
|
|
13
|
+
export function listZalouserAccountIds(cfg: ClawdbotConfig): string[] {
|
|
17
14
|
const ids = listConfiguredAccountIds(cfg);
|
|
18
15
|
if (ids.length === 0) return [DEFAULT_ACCOUNT_ID];
|
|
19
16
|
return ids.sort((a, b) => a.localeCompare(b));
|
|
20
17
|
}
|
|
21
18
|
|
|
22
|
-
export function resolveDefaultZalouserAccountId(cfg:
|
|
19
|
+
export function resolveDefaultZalouserAccountId(cfg: ClawdbotConfig): string {
|
|
23
20
|
const zalouserConfig = cfg.channels?.zalouser as ZalouserConfig | undefined;
|
|
24
21
|
if (zalouserConfig?.defaultAccount?.trim()) return zalouserConfig.defaultAccount.trim();
|
|
25
22
|
const ids = listZalouserAccountIds(cfg);
|
|
@@ -27,14 +24,8 @@ export function resolveDefaultZalouserAccountId(cfg: CoreConfig): string {
|
|
|
27
24
|
return ids[0] ?? DEFAULT_ACCOUNT_ID;
|
|
28
25
|
}
|
|
29
26
|
|
|
30
|
-
export function normalizeAccountId(accountId?: string | null): string {
|
|
31
|
-
const trimmed = accountId?.trim();
|
|
32
|
-
if (!trimmed) return DEFAULT_ACCOUNT_ID;
|
|
33
|
-
return trimmed.toLowerCase();
|
|
34
|
-
}
|
|
35
|
-
|
|
36
27
|
function resolveAccountConfig(
|
|
37
|
-
cfg:
|
|
28
|
+
cfg: ClawdbotConfig,
|
|
38
29
|
accountId: string,
|
|
39
30
|
): ZalouserAccountConfig | undefined {
|
|
40
31
|
const accounts = (cfg.channels?.zalouser as ZalouserConfig | undefined)?.accounts;
|
|
@@ -42,7 +33,10 @@ function resolveAccountConfig(
|
|
|
42
33
|
return accounts[accountId] as ZalouserAccountConfig | undefined;
|
|
43
34
|
}
|
|
44
35
|
|
|
45
|
-
function mergeZalouserAccountConfig(
|
|
36
|
+
function mergeZalouserAccountConfig(
|
|
37
|
+
cfg: ClawdbotConfig,
|
|
38
|
+
accountId: string,
|
|
39
|
+
): ZalouserAccountConfig {
|
|
46
40
|
const raw = (cfg.channels?.zalouser ?? {}) as ZalouserConfig;
|
|
47
41
|
const { accounts: _ignored, defaultAccount: _ignored2, ...base } = raw;
|
|
48
42
|
const account = resolveAccountConfig(cfg, accountId) ?? {};
|
|
@@ -62,7 +56,7 @@ export async function checkZcaAuthenticated(profile: string): Promise<boolean> {
|
|
|
62
56
|
}
|
|
63
57
|
|
|
64
58
|
export async function resolveZalouserAccount(params: {
|
|
65
|
-
cfg:
|
|
59
|
+
cfg: ClawdbotConfig;
|
|
66
60
|
accountId?: string | null;
|
|
67
61
|
}): Promise<ResolvedZalouserAccount> {
|
|
68
62
|
const accountId = normalizeAccountId(params.accountId);
|
|
@@ -84,7 +78,7 @@ export async function resolveZalouserAccount(params: {
|
|
|
84
78
|
}
|
|
85
79
|
|
|
86
80
|
export function resolveZalouserAccountSync(params: {
|
|
87
|
-
cfg:
|
|
81
|
+
cfg: ClawdbotConfig;
|
|
88
82
|
accountId?: string | null;
|
|
89
83
|
}): ResolvedZalouserAccount {
|
|
90
84
|
const accountId = normalizeAccountId(params.accountId);
|
|
@@ -104,7 +98,9 @@ export function resolveZalouserAccountSync(params: {
|
|
|
104
98
|
};
|
|
105
99
|
}
|
|
106
100
|
|
|
107
|
-
export async function listEnabledZalouserAccounts(
|
|
101
|
+
export async function listEnabledZalouserAccounts(
|
|
102
|
+
cfg: ClawdbotConfig,
|
|
103
|
+
): Promise<ResolvedZalouserAccount[]> {
|
|
108
104
|
const ids = listZalouserAccountIds(cfg);
|
|
109
105
|
const accounts = await Promise.all(
|
|
110
106
|
ids.map((accountId) => resolveZalouserAccount({ cfg, accountId }))
|
package/src/channel.ts
CHANGED
|
@@ -1,10 +1,20 @@
|
|
|
1
|
-
import type { ChannelPlugin } from "../../../src/channels/plugins/types.plugin.js";
|
|
2
1
|
import type {
|
|
3
2
|
ChannelAccountSnapshot,
|
|
4
3
|
ChannelDirectoryEntry,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
ChannelDock,
|
|
5
|
+
ChannelPlugin,
|
|
6
|
+
ClawdbotConfig,
|
|
7
|
+
} from "clawdbot/plugin-sdk";
|
|
8
|
+
import {
|
|
9
|
+
applyAccountNameToChannelSection,
|
|
10
|
+
buildChannelConfigSchema,
|
|
11
|
+
DEFAULT_ACCOUNT_ID,
|
|
12
|
+
deleteAccountFromConfigSection,
|
|
13
|
+
formatPairingApproveHint,
|
|
14
|
+
migrateBaseNameToDefaultAccount,
|
|
15
|
+
normalizeAccountId,
|
|
16
|
+
setAccountEnabledInConfigSection,
|
|
17
|
+
} from "clawdbot/plugin-sdk";
|
|
8
18
|
import {
|
|
9
19
|
listZalouserAccountIds,
|
|
10
20
|
resolveDefaultZalouserAccountId,
|
|
@@ -16,14 +26,10 @@ import {
|
|
|
16
26
|
import { zalouserOnboardingAdapter } from "./onboarding.js";
|
|
17
27
|
import { sendMessageZalouser } from "./send.js";
|
|
18
28
|
import { checkZcaInstalled, parseJsonOutput, runZca, runZcaInteractive } from "./zca.js";
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
type ZcaFriend,
|
|
24
|
-
type ZcaGroup,
|
|
25
|
-
type ZcaUserInfo,
|
|
26
|
-
} from "./types.js";
|
|
29
|
+
import type { ZcaFriend, ZcaGroup, ZcaUserInfo } from "./types.js";
|
|
30
|
+
import { ZalouserConfigSchema } from "./config-schema.js";
|
|
31
|
+
import { collectZalouserStatusIssues } from "./status-issues.js";
|
|
32
|
+
import { probeZalouser } from "./probe.js";
|
|
27
33
|
|
|
28
34
|
const meta = {
|
|
29
35
|
id: "zalouser",
|
|
@@ -38,7 +44,7 @@ const meta = {
|
|
|
38
44
|
};
|
|
39
45
|
|
|
40
46
|
function resolveZalouserQrProfile(accountId?: string | null): string {
|
|
41
|
-
const normalized =
|
|
47
|
+
const normalized = normalizeAccountId(accountId);
|
|
42
48
|
if (!normalized || normalized === DEFAULT_ACCOUNT_ID) {
|
|
43
49
|
return process.env.ZCA_PROFILE?.trim() || "default";
|
|
44
50
|
}
|
|
@@ -73,64 +79,33 @@ function mapGroup(params: {
|
|
|
73
79
|
};
|
|
74
80
|
}
|
|
75
81
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}): CoreConfig {
|
|
104
|
-
const { cfg, accountId, enabled } = params;
|
|
105
|
-
if (accountId === DEFAULT_ACCOUNT_ID) {
|
|
106
|
-
return {
|
|
107
|
-
...cfg,
|
|
108
|
-
channels: {
|
|
109
|
-
...cfg.channels,
|
|
110
|
-
zalouser: {
|
|
111
|
-
...cfg.channels?.zalouser,
|
|
112
|
-
enabled,
|
|
113
|
-
},
|
|
114
|
-
},
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
return {
|
|
118
|
-
...cfg,
|
|
119
|
-
channels: {
|
|
120
|
-
...cfg.channels,
|
|
121
|
-
zalouser: {
|
|
122
|
-
...cfg.channels?.zalouser,
|
|
123
|
-
accounts: {
|
|
124
|
-
...(cfg.channels?.zalouser?.accounts ?? {}),
|
|
125
|
-
[accountId]: {
|
|
126
|
-
...(cfg.channels?.zalouser?.accounts?.[accountId] ?? {}),
|
|
127
|
-
enabled,
|
|
128
|
-
},
|
|
129
|
-
},
|
|
130
|
-
},
|
|
131
|
-
},
|
|
132
|
-
};
|
|
133
|
-
}
|
|
82
|
+
export const zalouserDock: ChannelDock = {
|
|
83
|
+
id: "zalouser",
|
|
84
|
+
capabilities: {
|
|
85
|
+
chatTypes: ["direct", "group"],
|
|
86
|
+
media: true,
|
|
87
|
+
blockStreaming: true,
|
|
88
|
+
},
|
|
89
|
+
outbound: { textChunkLimit: 2000 },
|
|
90
|
+
config: {
|
|
91
|
+
resolveAllowFrom: ({ cfg, accountId }) =>
|
|
92
|
+
(resolveZalouserAccountSync({ cfg: cfg as ClawdbotConfig, accountId }).config.allowFrom ?? []).map(
|
|
93
|
+
(entry) => String(entry),
|
|
94
|
+
),
|
|
95
|
+
formatAllowFrom: ({ allowFrom }) =>
|
|
96
|
+
allowFrom
|
|
97
|
+
.map((entry) => String(entry).trim())
|
|
98
|
+
.filter(Boolean)
|
|
99
|
+
.map((entry) => entry.replace(/^(zalouser|zlu):/i, ""))
|
|
100
|
+
.map((entry) => entry.toLowerCase()),
|
|
101
|
+
},
|
|
102
|
+
groups: {
|
|
103
|
+
resolveRequireMention: () => true,
|
|
104
|
+
},
|
|
105
|
+
threading: {
|
|
106
|
+
resolveReplyToMode: () => "off",
|
|
107
|
+
},
|
|
108
|
+
};
|
|
134
109
|
|
|
135
110
|
export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
136
111
|
id: "zalouser",
|
|
@@ -146,21 +121,26 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
146
121
|
blockStreaming: true,
|
|
147
122
|
},
|
|
148
123
|
reload: { configPrefixes: ["channels.zalouser"] },
|
|
124
|
+
configSchema: buildChannelConfigSchema(ZalouserConfigSchema),
|
|
149
125
|
config: {
|
|
150
|
-
listAccountIds: (cfg) => listZalouserAccountIds(cfg as
|
|
126
|
+
listAccountIds: (cfg) => listZalouserAccountIds(cfg as ClawdbotConfig),
|
|
151
127
|
resolveAccount: (cfg, accountId) =>
|
|
152
|
-
resolveZalouserAccountSync({ cfg: cfg as
|
|
153
|
-
defaultAccountId: (cfg) => resolveDefaultZalouserAccountId(cfg as
|
|
128
|
+
resolveZalouserAccountSync({ cfg: cfg as ClawdbotConfig, accountId }),
|
|
129
|
+
defaultAccountId: (cfg) => resolveDefaultZalouserAccountId(cfg as ClawdbotConfig),
|
|
154
130
|
setAccountEnabled: ({ cfg, accountId, enabled }) =>
|
|
155
131
|
setAccountEnabledInConfigSection({
|
|
156
|
-
cfg: cfg as
|
|
132
|
+
cfg: cfg as ClawdbotConfig,
|
|
133
|
+
sectionKey: "zalouser",
|
|
157
134
|
accountId,
|
|
158
135
|
enabled,
|
|
136
|
+
allowTopLevel: true,
|
|
159
137
|
}),
|
|
160
138
|
deleteAccount: ({ cfg, accountId }) =>
|
|
161
139
|
deleteAccountFromConfigSection({
|
|
162
|
-
cfg: cfg as
|
|
140
|
+
cfg: cfg as ClawdbotConfig,
|
|
141
|
+
sectionKey: "zalouser",
|
|
163
142
|
accountId,
|
|
143
|
+
clearBaseFields: ["profile", "name", "dmPolicy", "allowFrom", "groupPolicy", "groups", "messagePrefix"],
|
|
164
144
|
}),
|
|
165
145
|
isConfigured: async (account) => {
|
|
166
146
|
// Check if zca auth status is OK for this profile
|
|
@@ -177,7 +157,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
177
157
|
configured: undefined,
|
|
178
158
|
}),
|
|
179
159
|
resolveAllowFrom: ({ cfg, accountId }) =>
|
|
180
|
-
(resolveZalouserAccountSync({ cfg: cfg as
|
|
160
|
+
(resolveZalouserAccountSync({ cfg: cfg as ClawdbotConfig, accountId }).config.allowFrom ?? []).map(
|
|
181
161
|
(entry) => String(entry),
|
|
182
162
|
),
|
|
183
163
|
formatAllowFrom: ({ allowFrom }) =>
|
|
@@ -191,7 +171,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
191
171
|
resolveDmPolicy: ({ cfg, accountId, account }) => {
|
|
192
172
|
const resolvedAccountId = accountId ?? account.accountId ?? DEFAULT_ACCOUNT_ID;
|
|
193
173
|
const useAccountPath = Boolean(
|
|
194
|
-
(cfg as
|
|
174
|
+
(cfg as ClawdbotConfig).channels?.zalouser?.accounts?.[resolvedAccountId],
|
|
195
175
|
);
|
|
196
176
|
const basePath = useAccountPath
|
|
197
177
|
? `channels.zalouser.accounts.${resolvedAccountId}.`
|
|
@@ -212,6 +192,61 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
212
192
|
threading: {
|
|
213
193
|
resolveReplyToMode: () => "off",
|
|
214
194
|
},
|
|
195
|
+
setup: {
|
|
196
|
+
resolveAccountId: ({ accountId }) => normalizeAccountId(accountId),
|
|
197
|
+
applyAccountName: ({ cfg, accountId, name }) =>
|
|
198
|
+
applyAccountNameToChannelSection({
|
|
199
|
+
cfg: cfg as ClawdbotConfig,
|
|
200
|
+
channelKey: "zalouser",
|
|
201
|
+
accountId,
|
|
202
|
+
name,
|
|
203
|
+
}),
|
|
204
|
+
validateInput: () => null,
|
|
205
|
+
applyAccountConfig: ({ cfg, accountId, input }) => {
|
|
206
|
+
const namedConfig = applyAccountNameToChannelSection({
|
|
207
|
+
cfg: cfg as ClawdbotConfig,
|
|
208
|
+
channelKey: "zalouser",
|
|
209
|
+
accountId,
|
|
210
|
+
name: input.name,
|
|
211
|
+
});
|
|
212
|
+
const next =
|
|
213
|
+
accountId !== DEFAULT_ACCOUNT_ID
|
|
214
|
+
? migrateBaseNameToDefaultAccount({
|
|
215
|
+
cfg: namedConfig,
|
|
216
|
+
channelKey: "zalouser",
|
|
217
|
+
})
|
|
218
|
+
: namedConfig;
|
|
219
|
+
if (accountId === DEFAULT_ACCOUNT_ID) {
|
|
220
|
+
return {
|
|
221
|
+
...next,
|
|
222
|
+
channels: {
|
|
223
|
+
...next.channels,
|
|
224
|
+
zalouser: {
|
|
225
|
+
...next.channels?.zalouser,
|
|
226
|
+
enabled: true,
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
} as ClawdbotConfig;
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
...next,
|
|
233
|
+
channels: {
|
|
234
|
+
...next.channels,
|
|
235
|
+
zalouser: {
|
|
236
|
+
...next.channels?.zalouser,
|
|
237
|
+
enabled: true,
|
|
238
|
+
accounts: {
|
|
239
|
+
...(next.channels?.zalouser?.accounts ?? {}),
|
|
240
|
+
[accountId]: {
|
|
241
|
+
...(next.channels?.zalouser?.accounts?.[accountId] ?? {}),
|
|
242
|
+
enabled: true,
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
} as ClawdbotConfig;
|
|
248
|
+
},
|
|
249
|
+
},
|
|
215
250
|
messaging: {
|
|
216
251
|
normalizeTarget: (raw) => {
|
|
217
252
|
const trimmed = raw?.trim();
|
|
@@ -231,7 +266,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
231
266
|
self: async ({ cfg, accountId, runtime }) => {
|
|
232
267
|
const ok = await checkZcaInstalled();
|
|
233
268
|
if (!ok) throw new Error("Missing dependency: `zca` not found in PATH");
|
|
234
|
-
const account = resolveZalouserAccountSync({ cfg: cfg as
|
|
269
|
+
const account = resolveZalouserAccountSync({ cfg: cfg as ClawdbotConfig, accountId });
|
|
235
270
|
const result = await runZca(["me", "info", "-j"], { profile: account.profile, timeout: 10000 });
|
|
236
271
|
if (!result.ok) {
|
|
237
272
|
runtime.error(result.stderr || "Failed to fetch profile");
|
|
@@ -249,7 +284,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
249
284
|
listPeers: async ({ cfg, accountId, query, limit }) => {
|
|
250
285
|
const ok = await checkZcaInstalled();
|
|
251
286
|
if (!ok) throw new Error("Missing dependency: `zca` not found in PATH");
|
|
252
|
-
const account = resolveZalouserAccountSync({ cfg: cfg as
|
|
287
|
+
const account = resolveZalouserAccountSync({ cfg: cfg as ClawdbotConfig, accountId });
|
|
253
288
|
const args = query?.trim()
|
|
254
289
|
? ["friend", "find", query.trim()]
|
|
255
290
|
: ["friend", "list", "-j"];
|
|
@@ -273,7 +308,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
273
308
|
listGroups: async ({ cfg, accountId, query, limit }) => {
|
|
274
309
|
const ok = await checkZcaInstalled();
|
|
275
310
|
if (!ok) throw new Error("Missing dependency: `zca` not found in PATH");
|
|
276
|
-
const account = resolveZalouserAccountSync({ cfg: cfg as
|
|
311
|
+
const account = resolveZalouserAccountSync({ cfg: cfg as ClawdbotConfig, accountId });
|
|
277
312
|
const result = await runZca(["group", "list", "-j"], { profile: account.profile, timeout: 15000 });
|
|
278
313
|
if (!result.ok) {
|
|
279
314
|
throw new Error(result.stderr || "Failed to list groups");
|
|
@@ -297,7 +332,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
297
332
|
listGroupMembers: async ({ cfg, accountId, groupId, limit }) => {
|
|
298
333
|
const ok = await checkZcaInstalled();
|
|
299
334
|
if (!ok) throw new Error("Missing dependency: `zca` not found in PATH");
|
|
300
|
-
const account = resolveZalouserAccountSync({ cfg: cfg as
|
|
335
|
+
const account = resolveZalouserAccountSync({ cfg: cfg as ClawdbotConfig, accountId });
|
|
301
336
|
const result = await runZca(["group", "members", groupId, "-j"], {
|
|
302
337
|
profile: account.profile,
|
|
303
338
|
timeout: 20000,
|
|
@@ -324,11 +359,78 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
324
359
|
return sliced as ChannelDirectoryEntry[];
|
|
325
360
|
},
|
|
326
361
|
},
|
|
362
|
+
resolver: {
|
|
363
|
+
resolveTargets: async ({ cfg, accountId, inputs, kind, runtime }) => {
|
|
364
|
+
const results = [];
|
|
365
|
+
for (const input of inputs) {
|
|
366
|
+
const trimmed = input.trim();
|
|
367
|
+
if (!trimmed) {
|
|
368
|
+
results.push({ input, resolved: false, note: "empty input" });
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
if (/^\d+$/.test(trimmed)) {
|
|
372
|
+
results.push({ input, resolved: true, id: trimmed });
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
try {
|
|
376
|
+
const account = resolveZalouserAccountSync({
|
|
377
|
+
cfg: cfg as ClawdbotConfig,
|
|
378
|
+
accountId: accountId ?? DEFAULT_ACCOUNT_ID,
|
|
379
|
+
});
|
|
380
|
+
const args =
|
|
381
|
+
kind === "user"
|
|
382
|
+
? trimmed
|
|
383
|
+
? ["friend", "find", trimmed]
|
|
384
|
+
: ["friend", "list", "-j"]
|
|
385
|
+
: ["group", "list", "-j"];
|
|
386
|
+
const result = await runZca(args, { profile: account.profile, timeout: 15000 });
|
|
387
|
+
if (!result.ok) throw new Error(result.stderr || "zca lookup failed");
|
|
388
|
+
if (kind === "user") {
|
|
389
|
+
const parsed = parseJsonOutput<ZcaFriend[]>(result.stdout) ?? [];
|
|
390
|
+
const matches = Array.isArray(parsed)
|
|
391
|
+
? parsed.map((f) => ({
|
|
392
|
+
id: String(f.userId),
|
|
393
|
+
name: f.displayName ?? undefined,
|
|
394
|
+
}))
|
|
395
|
+
: [];
|
|
396
|
+
const best = matches[0];
|
|
397
|
+
results.push({
|
|
398
|
+
input,
|
|
399
|
+
resolved: Boolean(best?.id),
|
|
400
|
+
id: best?.id,
|
|
401
|
+
name: best?.name,
|
|
402
|
+
note: matches.length > 1 ? "multiple matches; chose first" : undefined,
|
|
403
|
+
});
|
|
404
|
+
} else {
|
|
405
|
+
const parsed = parseJsonOutput<ZcaGroup[]>(result.stdout) ?? [];
|
|
406
|
+
const matches = Array.isArray(parsed)
|
|
407
|
+
? parsed.map((g) => ({
|
|
408
|
+
id: String(g.groupId),
|
|
409
|
+
name: g.name ?? undefined,
|
|
410
|
+
}))
|
|
411
|
+
: [];
|
|
412
|
+
const best = matches.find((g) => g.name?.toLowerCase() === trimmed.toLowerCase()) ?? matches[0];
|
|
413
|
+
results.push({
|
|
414
|
+
input,
|
|
415
|
+
resolved: Boolean(best?.id),
|
|
416
|
+
id: best?.id,
|
|
417
|
+
name: best?.name,
|
|
418
|
+
note: matches.length > 1 ? "multiple matches; chose first" : undefined,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
} catch (err) {
|
|
422
|
+
runtime.error?.(`zalouser resolve failed: ${String(err)}`);
|
|
423
|
+
results.push({ input, resolved: false, note: "lookup failed" });
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
return results;
|
|
427
|
+
},
|
|
428
|
+
},
|
|
327
429
|
pairing: {
|
|
328
430
|
idLabel: "zalouserUserId",
|
|
329
431
|
normalizeAllowEntry: (entry) => entry.replace(/^(zalouser|zlu):/i, ""),
|
|
330
432
|
notifyApproval: async ({ cfg, id }) => {
|
|
331
|
-
const account = resolveZalouserAccountSync({ cfg: cfg as
|
|
433
|
+
const account = resolveZalouserAccountSync({ cfg: cfg as ClawdbotConfig });
|
|
332
434
|
const authenticated = await checkZcaAuthenticated(account.profile);
|
|
333
435
|
if (!authenticated) throw new Error("Zalouser not authenticated");
|
|
334
436
|
await sendMessageZalouser(id, "Your pairing request has been approved.", {
|
|
@@ -339,7 +441,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
339
441
|
auth: {
|
|
340
442
|
login: async ({ cfg, accountId, runtime }) => {
|
|
341
443
|
const account = resolveZalouserAccountSync({
|
|
342
|
-
cfg: cfg as
|
|
444
|
+
cfg: cfg as ClawdbotConfig,
|
|
343
445
|
accountId: accountId ?? DEFAULT_ACCOUNT_ID,
|
|
344
446
|
});
|
|
345
447
|
const ok = await checkZcaInstalled();
|
|
@@ -382,7 +484,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
382
484
|
},
|
|
383
485
|
textChunkLimit: 2000,
|
|
384
486
|
sendText: async ({ to, text, accountId, cfg }) => {
|
|
385
|
-
const account = resolveZalouserAccountSync({ cfg: cfg as
|
|
487
|
+
const account = resolveZalouserAccountSync({ cfg: cfg as ClawdbotConfig, accountId });
|
|
386
488
|
const result = await sendMessageZalouser(to, text, { profile: account.profile });
|
|
387
489
|
return {
|
|
388
490
|
channel: "zalouser",
|
|
@@ -392,7 +494,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
392
494
|
};
|
|
393
495
|
},
|
|
394
496
|
sendMedia: async ({ to, text, mediaUrl, accountId, cfg }) => {
|
|
395
|
-
const account = resolveZalouserAccountSync({ cfg: cfg as
|
|
497
|
+
const account = resolveZalouserAccountSync({ cfg: cfg as ClawdbotConfig, accountId });
|
|
396
498
|
const result = await sendMessageZalouser(to, text, {
|
|
397
499
|
profile: account.profile,
|
|
398
500
|
mediaUrl,
|
|
@@ -413,6 +515,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
413
515
|
lastStopAt: null,
|
|
414
516
|
lastError: null,
|
|
415
517
|
},
|
|
518
|
+
collectStatusIssues: collectZalouserStatusIssues,
|
|
416
519
|
buildChannelSummary: ({ snapshot }) => ({
|
|
417
520
|
configured: snapshot.configured ?? false,
|
|
418
521
|
running: snapshot.running ?? false,
|
|
@@ -422,22 +525,12 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
422
525
|
probe: snapshot.probe,
|
|
423
526
|
lastProbeAt: snapshot.lastProbeAt ?? null,
|
|
424
527
|
}),
|
|
425
|
-
probeAccount: async ({ account, timeoutMs }) =>
|
|
426
|
-
|
|
427
|
-
profile: account.profile,
|
|
428
|
-
timeout: timeoutMs,
|
|
429
|
-
});
|
|
430
|
-
if (!result.ok) {
|
|
431
|
-
return { ok: false, error: result.stderr };
|
|
432
|
-
}
|
|
433
|
-
try {
|
|
434
|
-
return { ok: true, user: JSON.parse(result.stdout) };
|
|
435
|
-
} catch {
|
|
436
|
-
return { ok: false, error: "Failed to parse user info" };
|
|
437
|
-
}
|
|
438
|
-
},
|
|
528
|
+
probeAccount: async ({ account, timeoutMs }) =>
|
|
529
|
+
probeZalouser(account.profile, timeoutMs),
|
|
439
530
|
buildAccountSnapshot: async ({ account, runtime }) => {
|
|
440
|
-
const
|
|
531
|
+
const zcaInstalled = await checkZcaInstalled();
|
|
532
|
+
const configured = zcaInstalled ? await checkZcaAuthenticated(account.profile) : false;
|
|
533
|
+
const configError = zcaInstalled ? "not authenticated" : "zca CLI not found in PATH";
|
|
441
534
|
return {
|
|
442
535
|
accountId: account.accountId,
|
|
443
536
|
name: account.name,
|
|
@@ -446,7 +539,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
446
539
|
running: runtime?.running ?? false,
|
|
447
540
|
lastStartAt: runtime?.lastStartAt ?? null,
|
|
448
541
|
lastStopAt: runtime?.lastStopAt ?? null,
|
|
449
|
-
lastError: configured ? (runtime?.lastError ?? null) :
|
|
542
|
+
lastError: configured ? (runtime?.lastError ?? null) : runtime?.lastError ?? configError,
|
|
450
543
|
lastInboundAt: runtime?.lastInboundAt ?? null,
|
|
451
544
|
lastOutboundAt: runtime?.lastOutboundAt ?? null,
|
|
452
545
|
dmPolicy: account.config.dmPolicy ?? "pairing",
|
|
@@ -471,7 +564,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
471
564
|
const { monitorZalouserProvider } = await import("./monitor.js");
|
|
472
565
|
return monitorZalouserProvider({
|
|
473
566
|
account,
|
|
474
|
-
config: ctx.cfg as
|
|
567
|
+
config: ctx.cfg as ClawdbotConfig,
|
|
475
568
|
runtime: ctx.runtime,
|
|
476
569
|
abortSignal: ctx.abortSignal,
|
|
477
570
|
statusSink: (patch) => ctx.setStatus({ accountId: ctx.accountId, ...patch }),
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
const allowFromEntry = z.union([z.string(), z.number()]);
|
|
4
|
+
|
|
5
|
+
const groupConfigSchema = z.object({
|
|
6
|
+
allow: z.boolean().optional(),
|
|
7
|
+
enabled: z.boolean().optional(),
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const zalouserAccountSchema = z.object({
|
|
11
|
+
name: z.string().optional(),
|
|
12
|
+
enabled: z.boolean().optional(),
|
|
13
|
+
profile: z.string().optional(),
|
|
14
|
+
dmPolicy: z.enum(["pairing", "allowlist", "open", "disabled"]).optional(),
|
|
15
|
+
allowFrom: z.array(allowFromEntry).optional(),
|
|
16
|
+
groupPolicy: z.enum(["disabled", "allowlist", "open"]).optional(),
|
|
17
|
+
groups: z.object({}).catchall(groupConfigSchema).optional(),
|
|
18
|
+
messagePrefix: z.string().optional(),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export const ZalouserConfigSchema = zalouserAccountSchema.extend({
|
|
22
|
+
accounts: z.object({}).catchall(zalouserAccountSchema).optional(),
|
|
23
|
+
defaultAccount: z.string().optional(),
|
|
24
|
+
});
|