@openclaw/zalouser 2026.3.13 → 2026.5.2-beta.1
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 +293 -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 +391 -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/tool.test.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
2
|
import { sendImageZalouser, sendLinkZalouser, sendMessageZalouser } from "./send.js";
|
|
3
|
-
import { executeZalouserTool } from "./tool.js";
|
|
3
|
+
import { createZalouserTool, executeZalouserTool } from "./tool.js";
|
|
4
4
|
import {
|
|
5
5
|
checkZaloAuthenticated,
|
|
6
6
|
getZaloUserInfo,
|
|
@@ -30,8 +30,8 @@ const mockGetUserInfo = vi.mocked(getZaloUserInfo);
|
|
|
30
30
|
const mockListFriends = vi.mocked(listZaloFriendsMatching);
|
|
31
31
|
const mockListGroups = vi.mocked(listZaloGroupsMatching);
|
|
32
32
|
|
|
33
|
-
function extractDetails(result:
|
|
34
|
-
const text = result.content[0]?.text ?? "{}";
|
|
33
|
+
function extractDetails(result: { content?: Array<{ type: string; text?: string }> }): unknown {
|
|
34
|
+
const text = result.content?.[0]?.text ?? "{}";
|
|
35
35
|
return JSON.parse(text) as unknown;
|
|
36
36
|
}
|
|
37
37
|
|
|
@@ -69,6 +69,69 @@ describe("executeZalouserTool", () => {
|
|
|
69
69
|
expect(extractDetails(result)).toEqual({ success: true, messageId: "m-1" });
|
|
70
70
|
});
|
|
71
71
|
|
|
72
|
+
it("defaults send routing from ambient deliveryContext target", async () => {
|
|
73
|
+
mockSendMessage.mockResolvedValueOnce({ ok: true, messageId: "m-ambient" });
|
|
74
|
+
const tool = createZalouserTool({
|
|
75
|
+
deliveryContext: {
|
|
76
|
+
channel: "zalouser",
|
|
77
|
+
to: "zalouser:g-ambient",
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const result = await tool.execute("tool-1", {
|
|
82
|
+
action: "send",
|
|
83
|
+
message: "hello",
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
expect(mockSendMessage).toHaveBeenCalledWith("g-ambient", "hello", {
|
|
87
|
+
profile: undefined,
|
|
88
|
+
isGroup: true,
|
|
89
|
+
});
|
|
90
|
+
expect(extractDetails(result)).toEqual({ success: true, messageId: "m-ambient" });
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("keeps explicit threadId over ambient delivery defaults", async () => {
|
|
94
|
+
mockSendMessage.mockResolvedValueOnce({ ok: true, messageId: "m-explicit" });
|
|
95
|
+
const tool = createZalouserTool({
|
|
96
|
+
deliveryContext: {
|
|
97
|
+
channel: "zalouser",
|
|
98
|
+
to: "zalouser:g-ambient",
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
await tool.execute("tool-1", {
|
|
103
|
+
action: "send",
|
|
104
|
+
threadId: "u-explicit",
|
|
105
|
+
message: "hello",
|
|
106
|
+
isGroup: false,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
expect(mockSendMessage).toHaveBeenCalledWith("u-explicit", "hello", {
|
|
110
|
+
profile: undefined,
|
|
111
|
+
isGroup: false,
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("does not route send actions from foreign ambient thread defaults", async () => {
|
|
116
|
+
const tool = createZalouserTool({
|
|
117
|
+
deliveryContext: {
|
|
118
|
+
channel: "slack",
|
|
119
|
+
to: "channel:C123",
|
|
120
|
+
threadId: "1710000000.000100",
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const result = await tool.execute("tool-1", {
|
|
125
|
+
action: "send",
|
|
126
|
+
message: "hello",
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
expect(mockSendMessage).not.toHaveBeenCalled();
|
|
130
|
+
expect(extractDetails(result)).toEqual({
|
|
131
|
+
error: "threadId and message required for send action",
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
72
135
|
it("returns tool error when send action fails", async () => {
|
|
73
136
|
mockSendMessage.mockResolvedValueOnce({ ok: false, error: "blocked" });
|
|
74
137
|
const result = await executeZalouserTool("tool-1", {
|
package/src/tool.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { AnyAgentTool, OpenClawPluginToolContext } from "openclaw/plugin-sdk/core";
|
|
2
|
+
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
3
|
+
import { Type } from "typebox";
|
|
2
4
|
import { sendImageZalouser, sendLinkZalouser, sendMessageZalouser } from "./send.js";
|
|
5
|
+
import { parseZalouserOutboundTarget } from "./session-route.js";
|
|
3
6
|
import {
|
|
4
7
|
checkZaloAuthenticated,
|
|
5
8
|
getZaloUserInfo,
|
|
@@ -10,8 +13,8 @@ import {
|
|
|
10
13
|
const ACTIONS = ["send", "image", "link", "friends", "groups", "me", "status"] as const;
|
|
11
14
|
|
|
12
15
|
type AgentToolResult = {
|
|
13
|
-
content: Array<{ type:
|
|
14
|
-
details
|
|
16
|
+
content: Array<{ type: "text"; text: string }>;
|
|
17
|
+
details: unknown;
|
|
15
18
|
};
|
|
16
19
|
|
|
17
20
|
function stringEnum<T extends readonly string[]>(
|
|
@@ -25,7 +28,7 @@ function stringEnum<T extends readonly string[]>(
|
|
|
25
28
|
});
|
|
26
29
|
}
|
|
27
30
|
|
|
28
|
-
|
|
31
|
+
const ZalouserToolSchema = Type.Object(
|
|
29
32
|
{
|
|
30
33
|
action: stringEnum(ACTIONS, { description: `Action to perform: ${ACTIONS.join(", ")}` }),
|
|
31
34
|
threadId: Type.Optional(Type.String({ description: "Thread ID for messaging" })),
|
|
@@ -48,6 +51,8 @@ type ToolParams = {
|
|
|
48
51
|
url?: string;
|
|
49
52
|
};
|
|
50
53
|
|
|
54
|
+
type ZalouserToolContext = Pick<OpenClawPluginToolContext, "deliveryContext">;
|
|
55
|
+
|
|
51
56
|
function json(payload: unknown): AgentToolResult {
|
|
52
57
|
return {
|
|
53
58
|
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
@@ -55,21 +60,62 @@ function json(payload: unknown): AgentToolResult {
|
|
|
55
60
|
};
|
|
56
61
|
}
|
|
57
62
|
|
|
63
|
+
function resolveAmbientZalouserTarget(context?: ZalouserToolContext): {
|
|
64
|
+
threadId?: string;
|
|
65
|
+
isGroup?: boolean;
|
|
66
|
+
} {
|
|
67
|
+
const deliveryContext = context?.deliveryContext;
|
|
68
|
+
const rawTarget = deliveryContext?.to;
|
|
69
|
+
if (
|
|
70
|
+
(deliveryContext?.channel === undefined || deliveryContext.channel === "zalouser") &&
|
|
71
|
+
typeof rawTarget === "string" &&
|
|
72
|
+
rawTarget.trim()
|
|
73
|
+
) {
|
|
74
|
+
try {
|
|
75
|
+
return parseZalouserOutboundTarget(rawTarget);
|
|
76
|
+
} catch {
|
|
77
|
+
// Ignore unrelated delivery targets; explicit tool params still win.
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (deliveryContext?.channel && deliveryContext.channel !== "zalouser") {
|
|
81
|
+
return {};
|
|
82
|
+
}
|
|
83
|
+
const ambientThreadId = deliveryContext?.threadId;
|
|
84
|
+
if (typeof ambientThreadId === "string" && ambientThreadId.trim()) {
|
|
85
|
+
return { threadId: ambientThreadId.trim() };
|
|
86
|
+
}
|
|
87
|
+
if (typeof ambientThreadId === "number" && Number.isFinite(ambientThreadId)) {
|
|
88
|
+
return { threadId: String(ambientThreadId) };
|
|
89
|
+
}
|
|
90
|
+
return {};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function resolveZalouserSendTarget(params: ToolParams, context?: ZalouserToolContext) {
|
|
94
|
+
const explicitThreadId = typeof params.threadId === "string" ? params.threadId.trim() : "";
|
|
95
|
+
const ambientTarget = resolveAmbientZalouserTarget(context);
|
|
96
|
+
return {
|
|
97
|
+
threadId: explicitThreadId || ambientTarget.threadId,
|
|
98
|
+
isGroup: typeof params.isGroup === "boolean" ? params.isGroup : ambientTarget.isGroup,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
58
102
|
export async function executeZalouserTool(
|
|
59
103
|
_toolCallId: string,
|
|
60
104
|
params: ToolParams,
|
|
61
105
|
_signal?: AbortSignal,
|
|
62
106
|
_onUpdate?: unknown,
|
|
107
|
+
context?: ZalouserToolContext,
|
|
63
108
|
): Promise<AgentToolResult> {
|
|
64
109
|
try {
|
|
65
110
|
switch (params.action) {
|
|
66
111
|
case "send": {
|
|
67
|
-
|
|
112
|
+
const target = resolveZalouserSendTarget(params, context);
|
|
113
|
+
if (!target.threadId || !params.message) {
|
|
68
114
|
throw new Error("threadId and message required for send action");
|
|
69
115
|
}
|
|
70
|
-
const result = await sendMessageZalouser(
|
|
116
|
+
const result = await sendMessageZalouser(target.threadId, params.message, {
|
|
71
117
|
profile: params.profile,
|
|
72
|
-
isGroup:
|
|
118
|
+
isGroup: target.isGroup,
|
|
73
119
|
});
|
|
74
120
|
if (!result.ok) {
|
|
75
121
|
throw new Error(result.error || "Failed to send message");
|
|
@@ -78,16 +124,17 @@ export async function executeZalouserTool(
|
|
|
78
124
|
}
|
|
79
125
|
|
|
80
126
|
case "image": {
|
|
81
|
-
|
|
127
|
+
const target = resolveZalouserSendTarget(params, context);
|
|
128
|
+
if (!target.threadId) {
|
|
82
129
|
throw new Error("threadId required for image action");
|
|
83
130
|
}
|
|
84
131
|
if (!params.url) {
|
|
85
132
|
throw new Error("url required for image action");
|
|
86
133
|
}
|
|
87
|
-
const result = await sendImageZalouser(
|
|
134
|
+
const result = await sendImageZalouser(target.threadId, params.url, {
|
|
88
135
|
profile: params.profile,
|
|
89
136
|
caption: params.message,
|
|
90
|
-
isGroup:
|
|
137
|
+
isGroup: target.isGroup,
|
|
91
138
|
});
|
|
92
139
|
if (!result.ok) {
|
|
93
140
|
throw new Error(result.error || "Failed to send image");
|
|
@@ -96,13 +143,14 @@ export async function executeZalouserTool(
|
|
|
96
143
|
}
|
|
97
144
|
|
|
98
145
|
case "link": {
|
|
99
|
-
|
|
146
|
+
const target = resolveZalouserSendTarget(params, context);
|
|
147
|
+
if (!target.threadId || !params.url) {
|
|
100
148
|
throw new Error("threadId and url required for link action");
|
|
101
149
|
}
|
|
102
|
-
const result = await sendLinkZalouser(
|
|
150
|
+
const result = await sendLinkZalouser(target.threadId, params.url, {
|
|
103
151
|
profile: params.profile,
|
|
104
152
|
caption: params.message,
|
|
105
|
-
isGroup:
|
|
153
|
+
isGroup: target.isGroup,
|
|
106
154
|
});
|
|
107
155
|
if (!result.ok) {
|
|
108
156
|
throw new Error(result.error || "Failed to send link");
|
|
@@ -142,7 +190,21 @@ export async function executeZalouserTool(
|
|
|
142
190
|
}
|
|
143
191
|
} catch (err) {
|
|
144
192
|
return json({
|
|
145
|
-
error:
|
|
193
|
+
error: formatErrorMessage(err),
|
|
146
194
|
});
|
|
147
195
|
}
|
|
148
196
|
}
|
|
197
|
+
|
|
198
|
+
export function createZalouserTool(context?: ZalouserToolContext): AnyAgentTool {
|
|
199
|
+
return {
|
|
200
|
+
name: "zalouser",
|
|
201
|
+
label: "Zalo Personal",
|
|
202
|
+
description:
|
|
203
|
+
"Send messages and access data via Zalo personal account. " +
|
|
204
|
+
"Actions: send (text message), image (send image URL), link (send link), " +
|
|
205
|
+
"friends (list/search friends), groups (list groups), me (profile info), status (auth check).",
|
|
206
|
+
parameters: ZalouserToolSchema,
|
|
207
|
+
execute: async (toolCallId, params, signal, onUpdate) =>
|
|
208
|
+
await executeZalouserTool(toolCallId, params as ToolParams, signal, onUpdate, context),
|
|
209
|
+
} satisfies AnyAgentTool;
|
|
210
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Style } from "./zca-
|
|
1
|
+
import type { Style } from "./zca-constants.js";
|
|
2
2
|
|
|
3
3
|
export type ZcaFriend = {
|
|
4
4
|
userId: string;
|
|
@@ -61,6 +61,7 @@ export type ZaloSendOptions = {
|
|
|
61
61
|
caption?: string;
|
|
62
62
|
isGroup?: boolean;
|
|
63
63
|
mediaLocalRoots?: readonly string[];
|
|
64
|
+
mediaReadFile?: (filePath: string) => Promise<Buffer>;
|
|
64
65
|
textMode?: "markdown" | "plain";
|
|
65
66
|
textChunkMode?: "length" | "newline";
|
|
66
67
|
textChunkLimit?: number;
|
|
@@ -84,10 +85,9 @@ export type ZaloAuthStatus = {
|
|
|
84
85
|
message: string;
|
|
85
86
|
};
|
|
86
87
|
|
|
87
|
-
|
|
88
|
+
type ZalouserToolConfig = { allow?: string[]; deny?: string[] };
|
|
88
89
|
|
|
89
90
|
export type ZalouserGroupConfig = {
|
|
90
|
-
allow?: boolean;
|
|
91
91
|
enabled?: boolean;
|
|
92
92
|
requireMention?: boolean;
|
|
93
93
|
tools?: ZalouserToolConfig;
|