@kodelyth/zalouser 2026.5.42 → 2026.6.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.
Files changed (71) hide show
  1. package/klaw.plugin.json +286 -3
  2. package/package.json +17 -4
  3. package/api.ts +0 -9
  4. package/channel-plugin-api.ts +0 -3
  5. package/contract-api.ts +0 -2
  6. package/doctor-contract-api.ts +0 -1
  7. package/index.ts +0 -34
  8. package/runtime-api.ts +0 -62
  9. package/secret-contract-api.ts +0 -4
  10. package/setup-entry.ts +0 -9
  11. package/setup-plugin-api.ts +0 -2
  12. package/src/accounts.runtime.ts +0 -1
  13. package/src/accounts.test-mocks.ts +0 -14
  14. package/src/accounts.test.ts +0 -298
  15. package/src/accounts.ts +0 -136
  16. package/src/channel-api.ts +0 -16
  17. package/src/channel.adapters.ts +0 -432
  18. package/src/channel.directory.test.ts +0 -59
  19. package/src/channel.runtime.ts +0 -12
  20. package/src/channel.sendpayload.test.ts +0 -311
  21. package/src/channel.setup.test.ts +0 -30
  22. package/src/channel.setup.ts +0 -12
  23. package/src/channel.test.ts +0 -424
  24. package/src/channel.ts +0 -221
  25. package/src/config-schema.ts +0 -33
  26. package/src/directory.ts +0 -54
  27. package/src/doctor-contract.ts +0 -156
  28. package/src/doctor.test.ts +0 -87
  29. package/src/doctor.ts +0 -37
  30. package/src/group-policy.test.ts +0 -61
  31. package/src/group-policy.ts +0 -83
  32. package/src/message-sid.test.ts +0 -66
  33. package/src/message-sid.ts +0 -80
  34. package/src/monitor.account-scope.test.ts +0 -122
  35. package/src/monitor.group-gating.test.ts +0 -967
  36. package/src/monitor.send-mocks.ts +0 -20
  37. package/src/monitor.ts +0 -1057
  38. package/src/probe.test.ts +0 -60
  39. package/src/probe.ts +0 -35
  40. package/src/qr-temp-file.ts +0 -19
  41. package/src/reaction.test.ts +0 -19
  42. package/src/reaction.ts +0 -32
  43. package/src/runtime.ts +0 -9
  44. package/src/security-audit.test.ts +0 -83
  45. package/src/security-audit.ts +0 -71
  46. package/src/send-receipt.ts +0 -31
  47. package/src/send.test.ts +0 -424
  48. package/src/send.ts +0 -280
  49. package/src/session-route.ts +0 -121
  50. package/src/setup-core.ts +0 -36
  51. package/src/setup-surface.test.ts +0 -367
  52. package/src/setup-surface.ts +0 -481
  53. package/src/setup-test-helpers.ts +0 -42
  54. package/src/shared.ts +0 -92
  55. package/src/status-issues.test.ts +0 -31
  56. package/src/status-issues.ts +0 -55
  57. package/src/test-helpers.ts +0 -26
  58. package/src/text-styles.test.ts +0 -203
  59. package/src/text-styles.ts +0 -540
  60. package/src/tool.test.ts +0 -212
  61. package/src/tool.ts +0 -200
  62. package/src/types.ts +0 -127
  63. package/src/zalo-js.credentials.test.ts +0 -465
  64. package/src/zalo-js.test-mocks.ts +0 -89
  65. package/src/zalo-js.ts +0 -1889
  66. package/src/zca-client.test.ts +0 -27
  67. package/src/zca-client.ts +0 -259
  68. package/src/zca-constants.ts +0 -55
  69. package/src/zca-js-exports.d.ts +0 -22
  70. package/test-api.ts +0 -21
  71. package/tsconfig.json +0 -16
package/src/tool.test.ts DELETED
@@ -1,212 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from "vitest";
2
- import { sendImageZalouser, sendLinkZalouser, sendMessageZalouser } from "./send.js";
3
- import { createZalouserTool, executeZalouserTool } from "./tool.js";
4
- import {
5
- checkZaloAuthenticated,
6
- getZaloUserInfo,
7
- listZaloFriendsMatching,
8
- listZaloGroupsMatching,
9
- } from "./zalo-js.js";
10
-
11
- vi.mock("./send.js", () => ({
12
- sendMessageZalouser: vi.fn(),
13
- sendImageZalouser: vi.fn(),
14
- sendLinkZalouser: vi.fn(),
15
- sendReactionZalouser: vi.fn(),
16
- }));
17
-
18
- vi.mock("./zalo-js.js", () => ({
19
- checkZaloAuthenticated: vi.fn(),
20
- getZaloUserInfo: vi.fn(),
21
- listZaloFriendsMatching: vi.fn(),
22
- listZaloGroupsMatching: vi.fn(),
23
- }));
24
-
25
- const mockSendMessage = vi.mocked(sendMessageZalouser);
26
- const mockSendImage = vi.mocked(sendImageZalouser);
27
- const mockSendLink = vi.mocked(sendLinkZalouser);
28
- const mockCheckAuth = vi.mocked(checkZaloAuthenticated);
29
- const mockGetUserInfo = vi.mocked(getZaloUserInfo);
30
- const mockListFriends = vi.mocked(listZaloFriendsMatching);
31
- const mockListGroups = vi.mocked(listZaloGroupsMatching);
32
-
33
- function extractDetails(result: { content?: Array<{ type: string; text?: string }> }): unknown {
34
- const text = result.content?.[0]?.text ?? "{}";
35
- return JSON.parse(text) as unknown;
36
- }
37
-
38
- describe("executeZalouserTool", () => {
39
- beforeEach(() => {
40
- mockSendMessage.mockReset();
41
- mockSendImage.mockReset();
42
- mockSendLink.mockReset();
43
- mockCheckAuth.mockReset();
44
- mockGetUserInfo.mockReset();
45
- mockListFriends.mockReset();
46
- mockListGroups.mockReset();
47
- });
48
-
49
- it("returns error when send action is missing required fields", async () => {
50
- const result = await executeZalouserTool("tool-1", { action: "send" });
51
- expect(extractDetails(result)).toEqual({
52
- error: "threadId and message required for send action",
53
- });
54
- });
55
-
56
- it("sends text message for send action", async () => {
57
- mockSendMessage.mockResolvedValueOnce({ ok: true, messageId: "m-1" } as never);
58
- const result = await executeZalouserTool("tool-1", {
59
- action: "send",
60
- threadId: "t-1",
61
- message: "hello",
62
- profile: "work",
63
- isGroup: true,
64
- });
65
- expect(mockSendMessage).toHaveBeenCalledWith("t-1", "hello", {
66
- profile: "work",
67
- isGroup: true,
68
- });
69
- expect(extractDetails(result)).toEqual({ success: true, messageId: "m-1" });
70
- });
71
-
72
- it("defaults send routing from ambient deliveryContext target", async () => {
73
- mockSendMessage.mockResolvedValueOnce({ ok: true, messageId: "m-ambient" } as never);
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" } as never);
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
-
135
- it("returns tool error when send action fails", async () => {
136
- mockSendMessage.mockResolvedValueOnce({ ok: false, error: "blocked" } as never);
137
- const result = await executeZalouserTool("tool-1", {
138
- action: "send",
139
- threadId: "t-1",
140
- message: "hello",
141
- });
142
- expect(extractDetails(result)).toEqual({ error: "blocked" });
143
- });
144
-
145
- it("routes image and link actions to correct helpers", async () => {
146
- mockSendImage.mockResolvedValueOnce({ ok: true, messageId: "img-1" } as never);
147
- const imageResult = await executeZalouserTool("tool-1", {
148
- action: "image",
149
- threadId: "g-1",
150
- url: "https://example.com/image.jpg",
151
- message: "caption",
152
- isGroup: true,
153
- });
154
- expect(mockSendImage).toHaveBeenCalledWith("g-1", "https://example.com/image.jpg", {
155
- profile: undefined,
156
- caption: "caption",
157
- isGroup: true,
158
- });
159
- expect(extractDetails(imageResult)).toEqual({ success: true, messageId: "img-1" });
160
-
161
- mockSendLink.mockResolvedValueOnce({ ok: true, messageId: "lnk-1" } as never);
162
- const linkResult = await executeZalouserTool("tool-1", {
163
- action: "link",
164
- threadId: "t-2",
165
- url: "https://klaw.kodelyth.com",
166
- message: "read this",
167
- });
168
- expect(mockSendLink).toHaveBeenCalledWith("t-2", "https://klaw.kodelyth.com", {
169
- profile: undefined,
170
- caption: "read this",
171
- isGroup: undefined,
172
- });
173
- expect(extractDetails(linkResult)).toEqual({ success: true, messageId: "lnk-1" });
174
- });
175
-
176
- it("returns friends/groups lists", async () => {
177
- mockListFriends.mockResolvedValueOnce([{ userId: "1", displayName: "Alice" }]);
178
- mockListGroups.mockResolvedValueOnce([{ groupId: "2", name: "Work" }]);
179
-
180
- const friends = await executeZalouserTool("tool-1", {
181
- action: "friends",
182
- profile: "work",
183
- query: "ali",
184
- });
185
- expect(mockListFriends).toHaveBeenCalledWith("work", "ali");
186
- expect(extractDetails(friends)).toEqual([{ userId: "1", displayName: "Alice" }]);
187
-
188
- const groups = await executeZalouserTool("tool-1", {
189
- action: "groups",
190
- profile: "work",
191
- query: "wrk",
192
- });
193
- expect(mockListGroups).toHaveBeenCalledWith("work", "wrk");
194
- expect(extractDetails(groups)).toEqual([{ groupId: "2", name: "Work" }]);
195
- });
196
-
197
- it("reports me + status actions", async () => {
198
- mockGetUserInfo.mockResolvedValueOnce({ userId: "7", displayName: "Me" });
199
- mockCheckAuth.mockResolvedValueOnce(true);
200
-
201
- const me = await executeZalouserTool("tool-1", { action: "me", profile: "work" });
202
- expect(mockGetUserInfo).toHaveBeenCalledWith("work");
203
- expect(extractDetails(me)).toEqual({ userId: "7", displayName: "Me" });
204
-
205
- const status = await executeZalouserTool("tool-1", { action: "status", profile: "work" });
206
- expect(mockCheckAuth).toHaveBeenCalledWith("work");
207
- expect(extractDetails(status)).toEqual({
208
- authenticated: true,
209
- output: "authenticated",
210
- });
211
- });
212
- });
package/src/tool.ts DELETED
@@ -1,200 +0,0 @@
1
- import { stringEnum } from "klaw/plugin-sdk/channel-actions";
2
- import type { AnyAgentTool, KlawPluginToolContext } from "klaw/plugin-sdk/core";
3
- import { formatErrorMessage } from "klaw/plugin-sdk/error-runtime";
4
- import { Type } from "typebox";
5
- import { sendImageZalouser, sendLinkZalouser, sendMessageZalouser } from "./send.js";
6
- import { parseZalouserOutboundTarget } from "./session-route.js";
7
- import {
8
- checkZaloAuthenticated,
9
- getZaloUserInfo,
10
- listZaloFriendsMatching,
11
- listZaloGroupsMatching,
12
- } from "./zalo-js.js";
13
-
14
- const ACTIONS = ["send", "image", "link", "friends", "groups", "me", "status"] as const;
15
-
16
- type AgentToolResult = {
17
- content: Array<{ type: "text"; text: string }>;
18
- details: unknown;
19
- };
20
-
21
- const ZalouserToolSchema = Type.Object(
22
- {
23
- action: stringEnum(ACTIONS, { description: `Action to perform: ${ACTIONS.join(", ")}` }),
24
- threadId: Type.Optional(Type.String({ description: "Thread ID for messaging" })),
25
- message: Type.Optional(Type.String({ description: "Message text" })),
26
- isGroup: Type.Optional(Type.Boolean({ description: "Is group chat" })),
27
- profile: Type.Optional(Type.String({ description: "Profile name" })),
28
- query: Type.Optional(Type.String({ description: "Search query" })),
29
- url: Type.Optional(Type.String({ description: "URL for media/link" })),
30
- },
31
- { additionalProperties: false },
32
- );
33
-
34
- type ToolParams = {
35
- action: (typeof ACTIONS)[number];
36
- threadId?: string;
37
- message?: string;
38
- isGroup?: boolean;
39
- profile?: string;
40
- query?: string;
41
- url?: string;
42
- };
43
-
44
- type ZalouserToolContext = Pick<KlawPluginToolContext, "deliveryContext">;
45
-
46
- function json(payload: unknown): AgentToolResult {
47
- return {
48
- content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
49
- details: payload,
50
- };
51
- }
52
-
53
- function resolveAmbientZalouserTarget(context?: ZalouserToolContext): {
54
- threadId?: string;
55
- isGroup?: boolean;
56
- } {
57
- const deliveryContext = context?.deliveryContext;
58
- const rawTarget = deliveryContext?.to;
59
- if (
60
- (deliveryContext?.channel === undefined || deliveryContext.channel === "zalouser") &&
61
- typeof rawTarget === "string" &&
62
- rawTarget.trim()
63
- ) {
64
- try {
65
- return parseZalouserOutboundTarget(rawTarget);
66
- } catch {
67
- // Ignore unrelated delivery targets; explicit tool params still win.
68
- }
69
- }
70
- if (deliveryContext?.channel && deliveryContext.channel !== "zalouser") {
71
- return {};
72
- }
73
- const ambientThreadId = deliveryContext?.threadId;
74
- if (typeof ambientThreadId === "string" && ambientThreadId.trim()) {
75
- return { threadId: ambientThreadId.trim() };
76
- }
77
- if (typeof ambientThreadId === "number" && Number.isFinite(ambientThreadId)) {
78
- return { threadId: String(ambientThreadId) };
79
- }
80
- return {};
81
- }
82
-
83
- function resolveZalouserSendTarget(params: ToolParams, context?: ZalouserToolContext) {
84
- const explicitThreadId = typeof params.threadId === "string" ? params.threadId.trim() : "";
85
- const ambientTarget = resolveAmbientZalouserTarget(context);
86
- return {
87
- threadId: explicitThreadId || ambientTarget.threadId,
88
- isGroup: typeof params.isGroup === "boolean" ? params.isGroup : ambientTarget.isGroup,
89
- };
90
- }
91
-
92
- export async function executeZalouserTool(
93
- _toolCallId: string,
94
- params: ToolParams,
95
- _signal?: AbortSignal,
96
- _onUpdate?: unknown,
97
- context?: ZalouserToolContext,
98
- ): Promise<AgentToolResult> {
99
- try {
100
- switch (params.action) {
101
- case "send": {
102
- const target = resolveZalouserSendTarget(params, context);
103
- if (!target.threadId || !params.message) {
104
- throw new Error("threadId and message required for send action");
105
- }
106
- const result = await sendMessageZalouser(target.threadId, params.message, {
107
- profile: params.profile,
108
- isGroup: target.isGroup,
109
- });
110
- if (!result.ok) {
111
- throw new Error(result.error || "Failed to send message");
112
- }
113
- return json({ success: true, messageId: result.messageId });
114
- }
115
-
116
- case "image": {
117
- const target = resolveZalouserSendTarget(params, context);
118
- if (!target.threadId) {
119
- throw new Error("threadId required for image action");
120
- }
121
- if (!params.url) {
122
- throw new Error("url required for image action");
123
- }
124
- const result = await sendImageZalouser(target.threadId, params.url, {
125
- profile: params.profile,
126
- caption: params.message,
127
- isGroup: target.isGroup,
128
- });
129
- if (!result.ok) {
130
- throw new Error(result.error || "Failed to send image");
131
- }
132
- return json({ success: true, messageId: result.messageId });
133
- }
134
-
135
- case "link": {
136
- const target = resolveZalouserSendTarget(params, context);
137
- if (!target.threadId || !params.url) {
138
- throw new Error("threadId and url required for link action");
139
- }
140
- const result = await sendLinkZalouser(target.threadId, params.url, {
141
- profile: params.profile,
142
- caption: params.message,
143
- isGroup: target.isGroup,
144
- });
145
- if (!result.ok) {
146
- throw new Error(result.error || "Failed to send link");
147
- }
148
- return json({ success: true, messageId: result.messageId });
149
- }
150
-
151
- case "friends": {
152
- const rows = await listZaloFriendsMatching(params.profile, params.query);
153
- return json(rows);
154
- }
155
-
156
- case "groups": {
157
- const rows = await listZaloGroupsMatching(params.profile, params.query);
158
- return json(rows);
159
- }
160
-
161
- case "me": {
162
- const info = await getZaloUserInfo(params.profile);
163
- return json(info ?? { error: "Not authenticated" });
164
- }
165
-
166
- case "status": {
167
- const authenticated = await checkZaloAuthenticated(params.profile);
168
- return json({
169
- authenticated,
170
- output: authenticated ? "authenticated" : "not authenticated",
171
- });
172
- }
173
-
174
- default: {
175
- params.action satisfies never;
176
- throw new Error(
177
- `Unknown action: ${String(params.action)}. Valid actions: send, image, link, friends, groups, me, status`,
178
- );
179
- }
180
- }
181
- } catch (err) {
182
- return json({
183
- error: formatErrorMessage(err),
184
- });
185
- }
186
- }
187
-
188
- export function createZalouserTool(context?: ZalouserToolContext): AnyAgentTool {
189
- return {
190
- name: "zalouser",
191
- label: "Zalo Personal",
192
- description:
193
- "Send messages and access data via Zalo personal account. " +
194
- "Actions: send (text message), image (send image URL), link (send link), " +
195
- "friends (list/search friends), groups (list groups), me (profile info), status (auth check).",
196
- parameters: ZalouserToolSchema,
197
- execute: async (toolCallId, params, signal, onUpdate) =>
198
- await executeZalouserTool(toolCallId, params as ToolParams, signal, onUpdate, context),
199
- } satisfies AnyAgentTool;
200
- }
package/src/types.ts DELETED
@@ -1,127 +0,0 @@
1
- import type { MessageReceipt } from "klaw/plugin-sdk/channel-message";
2
- import type { Style } from "./zca-constants.js";
3
-
4
- export type ZcaFriend = {
5
- userId: string;
6
- displayName: string;
7
- avatar?: string;
8
- };
9
-
10
- export type ZaloGroup = {
11
- groupId: string;
12
- name: string;
13
- memberCount?: number;
14
- };
15
-
16
- export type ZaloGroupMember = {
17
- userId: string;
18
- displayName: string;
19
- avatar?: string;
20
- };
21
-
22
- export type ZaloEventMessage = {
23
- msgId: string;
24
- cliMsgId: string;
25
- uidFrom: string;
26
- idTo: string;
27
- msgType: string;
28
- st: number;
29
- at: number;
30
- cmd: number;
31
- ts: string | number;
32
- };
33
-
34
- export type ZaloInboundMessage = {
35
- threadId: string;
36
- isGroup: boolean;
37
- senderId: string;
38
- senderName?: string;
39
- groupName?: string;
40
- content: string;
41
- commandContent?: string;
42
- timestampMs: number;
43
- msgId?: string;
44
- cliMsgId?: string;
45
- hasAnyMention?: boolean;
46
- wasExplicitlyMentioned?: boolean;
47
- canResolveExplicitMention?: boolean;
48
- implicitMention?: boolean;
49
- eventMessage?: ZaloEventMessage;
50
- raw: unknown;
51
- };
52
-
53
- export type ZcaUserInfo = {
54
- userId: string;
55
- displayName: string;
56
- avatar?: string;
57
- };
58
-
59
- export type ZaloSendOptions = {
60
- profile?: string;
61
- mediaUrl?: string;
62
- caption?: string;
63
- isGroup?: boolean;
64
- mediaLocalRoots?: readonly string[];
65
- mediaReadFile?: (filePath: string) => Promise<Buffer>;
66
- textMode?: "markdown" | "plain";
67
- textChunkMode?: "length" | "newline";
68
- textChunkLimit?: number;
69
- textStyles?: Style[];
70
- };
71
-
72
- export type ZaloSendResult = {
73
- ok: boolean;
74
- messageId?: string;
75
- receipt: MessageReceipt;
76
- error?: string;
77
- };
78
-
79
- export type ZaloGroupContext = {
80
- groupId: string;
81
- name?: string;
82
- members?: string[];
83
- };
84
-
85
- export type ZaloAuthStatus = {
86
- connected: boolean;
87
- message: string;
88
- };
89
-
90
- type ZalouserToolConfig = { allow?: string[]; deny?: string[] };
91
-
92
- export type ZalouserGroupConfig = {
93
- enabled?: boolean;
94
- requireMention?: boolean;
95
- tools?: ZalouserToolConfig;
96
- };
97
-
98
- type ZalouserSharedConfig = {
99
- enabled?: boolean;
100
- name?: string;
101
- profile?: string;
102
- dangerouslyAllowNameMatching?: boolean;
103
- dmPolicy?: "pairing" | "allowlist" | "open" | "disabled";
104
- allowFrom?: Array<string | number>;
105
- historyLimit?: number;
106
- groupAllowFrom?: Array<string | number>;
107
- groupPolicy?: "open" | "allowlist" | "disabled";
108
- groups?: Record<string, ZalouserGroupConfig>;
109
- messagePrefix?: string;
110
- responsePrefix?: string;
111
- };
112
-
113
- export type ZalouserAccountConfig = ZalouserSharedConfig;
114
-
115
- export type ZalouserConfig = ZalouserSharedConfig & {
116
- defaultAccount?: string;
117
- accounts?: Record<string, ZalouserAccountConfig>;
118
- };
119
-
120
- export type ResolvedZalouserAccount = {
121
- accountId: string;
122
- name?: string;
123
- enabled: boolean;
124
- profile: string;
125
- authenticated: boolean;
126
- config: ZalouserAccountConfig;
127
- };