@openclaw/zalouser 2026.3.11 → 2026.3.13
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 +13 -0
- package/package.json +1 -1
- package/src/accounts.test-mocks.ts +10 -0
- package/src/accounts.ts +15 -14
- package/src/channel.directory.test.ts +3 -23
- package/src/channel.sendpayload.test.ts +37 -18
- package/src/channel.test.ts +75 -49
- package/src/channel.ts +23 -15
- package/src/config-schema.ts +1 -0
- package/src/group-policy.test.ts +12 -0
- package/src/group-policy.ts +7 -4
- package/src/monitor.account-scope.test.ts +2 -9
- package/src/monitor.group-gating.test.ts +230 -147
- package/src/monitor.ts +25 -24
- package/src/send.test.ts +247 -9
- package/src/send.ts +187 -2
- package/src/status-issues.test.ts +5 -5
- package/src/status-issues.ts +10 -28
- package/src/test-helpers.ts +26 -0
- package/src/text-styles.test.ts +203 -0
- package/src/text-styles.ts +537 -0
- package/src/types.ts +7 -0
- package/src/zalo-js.ts +48 -2
- package/src/zca-client.ts +33 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2026.3.13
|
|
4
|
+
|
|
5
|
+
### Changes
|
|
6
|
+
|
|
7
|
+
- Version alignment with core OpenClaw release numbers.
|
|
8
|
+
|
|
9
|
+
## 2026.3.12
|
|
10
|
+
|
|
11
|
+
### Changes
|
|
12
|
+
|
|
13
|
+
- Version alignment with core OpenClaw release numbers.
|
|
14
|
+
|
|
3
15
|
## 2026.3.11
|
|
4
16
|
|
|
5
17
|
### Changes
|
|
18
|
+
|
|
6
19
|
- Version alignment with core OpenClaw release numbers.
|
|
7
20
|
|
|
8
21
|
## 2026.3.10
|
package/package.json
CHANGED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { vi } from "vitest";
|
|
2
|
+
import { createDefaultResolvedZalouserAccount } from "./test-helpers.js";
|
|
3
|
+
|
|
4
|
+
vi.mock("./accounts.js", async (importOriginal) => {
|
|
5
|
+
const actual = (await importOriginal()) as Record<string, unknown>;
|
|
6
|
+
return {
|
|
7
|
+
...actual,
|
|
8
|
+
resolveZalouserAccountSync: () => createDefaultResolvedZalouserAccount(),
|
|
9
|
+
};
|
|
10
|
+
});
|
package/src/accounts.ts
CHANGED
|
@@ -43,17 +43,24 @@ function resolveProfile(config: ZalouserAccountConfig, accountId: string): strin
|
|
|
43
43
|
return "default";
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
cfg: OpenClawConfig;
|
|
48
|
-
accountId?: string | null;
|
|
49
|
-
}): Promise<ResolvedZalouserAccount> {
|
|
46
|
+
function resolveZalouserAccountBase(params: { cfg: OpenClawConfig; accountId?: string | null }) {
|
|
50
47
|
const accountId = normalizeAccountId(params.accountId);
|
|
51
48
|
const baseEnabled =
|
|
52
49
|
(params.cfg.channels?.zalouser as ZalouserConfig | undefined)?.enabled !== false;
|
|
53
50
|
const merged = mergeZalouserAccountConfig(params.cfg, accountId);
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
return {
|
|
52
|
+
accountId,
|
|
53
|
+
enabled: baseEnabled && merged.enabled !== false,
|
|
54
|
+
merged,
|
|
55
|
+
profile: resolveProfile(merged, accountId),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function resolveZalouserAccount(params: {
|
|
60
|
+
cfg: OpenClawConfig;
|
|
61
|
+
accountId?: string | null;
|
|
62
|
+
}): Promise<ResolvedZalouserAccount> {
|
|
63
|
+
const { accountId, enabled, merged, profile } = resolveZalouserAccountBase(params);
|
|
57
64
|
const authenticated = await checkZaloAuthenticated(profile);
|
|
58
65
|
|
|
59
66
|
return {
|
|
@@ -70,13 +77,7 @@ export function resolveZalouserAccountSync(params: {
|
|
|
70
77
|
cfg: OpenClawConfig;
|
|
71
78
|
accountId?: string | null;
|
|
72
79
|
}): ResolvedZalouserAccount {
|
|
73
|
-
const accountId =
|
|
74
|
-
const baseEnabled =
|
|
75
|
-
(params.cfg.channels?.zalouser as ZalouserConfig | undefined)?.enabled !== false;
|
|
76
|
-
const merged = mergeZalouserAccountConfig(params.cfg, accountId);
|
|
77
|
-
const accountEnabled = merged.enabled !== false;
|
|
78
|
-
const enabled = baseEnabled && accountEnabled;
|
|
79
|
-
const profile = resolveProfile(merged, accountId);
|
|
80
|
+
const { accountId, enabled, merged, profile } = resolveZalouserAccountBase(params);
|
|
80
81
|
|
|
81
82
|
return {
|
|
82
83
|
accountId,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { RuntimeEnv } from "openclaw/plugin-sdk/zalouser";
|
|
2
1
|
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import "./accounts.test-mocks.js";
|
|
3
|
+
import { createZalouserRuntimeEnv } from "./test-helpers.js";
|
|
3
4
|
|
|
4
5
|
const listZaloGroupMembersMock = vi.hoisted(() => vi.fn(async () => []));
|
|
5
6
|
|
|
@@ -11,30 +12,9 @@ vi.mock("./zalo-js.js", async (importOriginal) => {
|
|
|
11
12
|
};
|
|
12
13
|
});
|
|
13
14
|
|
|
14
|
-
vi.mock("./accounts.js", async (importOriginal) => {
|
|
15
|
-
const actual = (await importOriginal()) as Record<string, unknown>;
|
|
16
|
-
return {
|
|
17
|
-
...actual,
|
|
18
|
-
resolveZalouserAccountSync: () => ({
|
|
19
|
-
accountId: "default",
|
|
20
|
-
profile: "default",
|
|
21
|
-
name: "test",
|
|
22
|
-
enabled: true,
|
|
23
|
-
authenticated: true,
|
|
24
|
-
config: {},
|
|
25
|
-
}),
|
|
26
|
-
};
|
|
27
|
-
});
|
|
28
|
-
|
|
29
15
|
import { zalouserPlugin } from "./channel.js";
|
|
30
16
|
|
|
31
|
-
const runtimeStub
|
|
32
|
-
log: vi.fn(),
|
|
33
|
-
error: vi.fn(),
|
|
34
|
-
exit: ((code: number): never => {
|
|
35
|
-
throw new Error(`exit ${code}`);
|
|
36
|
-
}) as RuntimeEnv["exit"],
|
|
37
|
-
};
|
|
17
|
+
const runtimeStub = createZalouserRuntimeEnv();
|
|
38
18
|
|
|
39
19
|
describe("zalouser directory group members", () => {
|
|
40
20
|
it("accepts prefixed group ids from directory groups list output", async () => {
|
|
@@ -1,30 +1,18 @@
|
|
|
1
1
|
import type { ReplyPayload } from "openclaw/plugin-sdk/zalouser";
|
|
2
2
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import "./accounts.test-mocks.js";
|
|
3
4
|
import {
|
|
4
5
|
installSendPayloadContractSuite,
|
|
5
6
|
primeSendMock,
|
|
6
7
|
} from "../../../src/test-utils/send-payload-contract.js";
|
|
7
8
|
import { zalouserPlugin } from "./channel.js";
|
|
9
|
+
import { setZalouserRuntime } from "./runtime.js";
|
|
8
10
|
|
|
9
11
|
vi.mock("./send.js", () => ({
|
|
10
12
|
sendMessageZalouser: vi.fn().mockResolvedValue({ ok: true, messageId: "zlu-1" }),
|
|
11
13
|
sendReactionZalouser: vi.fn().mockResolvedValue({ ok: true }),
|
|
12
14
|
}));
|
|
13
15
|
|
|
14
|
-
vi.mock("./accounts.js", async (importOriginal) => {
|
|
15
|
-
const actual = (await importOriginal()) as Record<string, unknown>;
|
|
16
|
-
return {
|
|
17
|
-
...actual,
|
|
18
|
-
resolveZalouserAccountSync: () => ({
|
|
19
|
-
accountId: "default",
|
|
20
|
-
profile: "default",
|
|
21
|
-
name: "test",
|
|
22
|
-
enabled: true,
|
|
23
|
-
config: {},
|
|
24
|
-
}),
|
|
25
|
-
};
|
|
26
|
-
});
|
|
27
|
-
|
|
28
16
|
function baseCtx(payload: ReplyPayload) {
|
|
29
17
|
return {
|
|
30
18
|
cfg: {},
|
|
@@ -38,6 +26,14 @@ describe("zalouserPlugin outbound sendPayload", () => {
|
|
|
38
26
|
let mockedSend: ReturnType<typeof vi.mocked<(typeof import("./send.js"))["sendMessageZalouser"]>>;
|
|
39
27
|
|
|
40
28
|
beforeEach(async () => {
|
|
29
|
+
setZalouserRuntime({
|
|
30
|
+
channel: {
|
|
31
|
+
text: {
|
|
32
|
+
resolveChunkMode: vi.fn(() => "length"),
|
|
33
|
+
resolveTextChunkLimit: vi.fn(() => 1200),
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
} as never);
|
|
41
37
|
const mod = await import("./send.js");
|
|
42
38
|
mockedSend = vi.mocked(mod.sendMessageZalouser);
|
|
43
39
|
mockedSend.mockClear();
|
|
@@ -55,7 +51,7 @@ describe("zalouserPlugin outbound sendPayload", () => {
|
|
|
55
51
|
expect(mockedSend).toHaveBeenCalledWith(
|
|
56
52
|
"1471383327500481391",
|
|
57
53
|
"hello group",
|
|
58
|
-
expect.objectContaining({ isGroup: true }),
|
|
54
|
+
expect.objectContaining({ isGroup: true, textMode: "markdown" }),
|
|
59
55
|
);
|
|
60
56
|
expect(result).toMatchObject({ channel: "zalouser", messageId: "zlu-g1" });
|
|
61
57
|
});
|
|
@@ -71,7 +67,7 @@ describe("zalouserPlugin outbound sendPayload", () => {
|
|
|
71
67
|
expect(mockedSend).toHaveBeenCalledWith(
|
|
72
68
|
"987654321",
|
|
73
69
|
"hello",
|
|
74
|
-
expect.objectContaining({ isGroup: false }),
|
|
70
|
+
expect.objectContaining({ isGroup: false, textMode: "markdown" }),
|
|
75
71
|
);
|
|
76
72
|
expect(result).toMatchObject({ channel: "zalouser", messageId: "zlu-d1" });
|
|
77
73
|
});
|
|
@@ -87,14 +83,37 @@ describe("zalouserPlugin outbound sendPayload", () => {
|
|
|
87
83
|
expect(mockedSend).toHaveBeenCalledWith(
|
|
88
84
|
"g-1471383327500481391",
|
|
89
85
|
"hello native group",
|
|
90
|
-
expect.objectContaining({ isGroup: true }),
|
|
86
|
+
expect.objectContaining({ isGroup: true, textMode: "markdown" }),
|
|
91
87
|
);
|
|
92
88
|
expect(result).toMatchObject({ channel: "zalouser", messageId: "zlu-g-native" });
|
|
93
89
|
});
|
|
94
90
|
|
|
91
|
+
it("passes long markdown through once so formatting happens before chunking", async () => {
|
|
92
|
+
const text = `**${"a".repeat(2501)}**`;
|
|
93
|
+
mockedSend.mockResolvedValue({ ok: true, messageId: "zlu-code" });
|
|
94
|
+
|
|
95
|
+
const result = await zalouserPlugin.outbound!.sendPayload!({
|
|
96
|
+
...baseCtx({ text }),
|
|
97
|
+
to: "987654321",
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
expect(mockedSend).toHaveBeenCalledTimes(1);
|
|
101
|
+
expect(mockedSend).toHaveBeenCalledWith(
|
|
102
|
+
"987654321",
|
|
103
|
+
text,
|
|
104
|
+
expect.objectContaining({
|
|
105
|
+
isGroup: false,
|
|
106
|
+
textMode: "markdown",
|
|
107
|
+
textChunkMode: "length",
|
|
108
|
+
textChunkLimit: 1200,
|
|
109
|
+
}),
|
|
110
|
+
);
|
|
111
|
+
expect(result).toMatchObject({ channel: "zalouser", messageId: "zlu-code" });
|
|
112
|
+
});
|
|
113
|
+
|
|
95
114
|
installSendPayloadContractSuite({
|
|
96
115
|
channel: "zalouser",
|
|
97
|
-
chunking: { mode: "
|
|
116
|
+
chunking: { mode: "passthrough", longTextLength: 3000 },
|
|
98
117
|
createHarness: ({ payload, sendResults }) => {
|
|
99
118
|
primeSendMock(mockedSend, { ok: true, messageId: "zlu-1" }, sendResults);
|
|
100
119
|
return {
|
package/src/channel.test.ts
CHANGED
|
@@ -1,30 +1,92 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
2
|
import { zalouserPlugin } from "./channel.js";
|
|
3
|
-
import {
|
|
3
|
+
import { setZalouserRuntime } from "./runtime.js";
|
|
4
|
+
import { sendMessageZalouser, sendReactionZalouser } from "./send.js";
|
|
4
5
|
|
|
5
6
|
vi.mock("./send.js", async (importOriginal) => {
|
|
6
7
|
const actual = (await importOriginal()) as Record<string, unknown>;
|
|
7
8
|
return {
|
|
8
9
|
...actual,
|
|
10
|
+
sendMessageZalouser: vi.fn(async () => ({ ok: true, messageId: "mid-1" })),
|
|
9
11
|
sendReactionZalouser: vi.fn(async () => ({ ok: true })),
|
|
10
12
|
};
|
|
11
13
|
});
|
|
12
14
|
|
|
15
|
+
const mockSendMessage = vi.mocked(sendMessageZalouser);
|
|
13
16
|
const mockSendReaction = vi.mocked(sendReactionZalouser);
|
|
14
17
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
function getResolveToolPolicy() {
|
|
19
|
+
const resolveToolPolicy = zalouserPlugin.groups?.resolveToolPolicy;
|
|
20
|
+
expect(resolveToolPolicy).toBeTypeOf("function");
|
|
21
|
+
if (!resolveToolPolicy) {
|
|
22
|
+
throw new Error("resolveToolPolicy unavailable");
|
|
23
|
+
}
|
|
24
|
+
return resolveToolPolicy;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function resolveGroupToolPolicy(
|
|
28
|
+
groups: Record<string, { tools: { allow?: string[]; deny?: string[] } }>,
|
|
29
|
+
groupId: string,
|
|
30
|
+
) {
|
|
31
|
+
return getResolveToolPolicy()({
|
|
32
|
+
cfg: {
|
|
33
|
+
channels: {
|
|
34
|
+
zalouser: {
|
|
35
|
+
groups,
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
accountId: "default",
|
|
40
|
+
groupId,
|
|
41
|
+
groupChannel: groupId,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
describe("zalouser outbound", () => {
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
mockSendMessage.mockClear();
|
|
48
|
+
setZalouserRuntime({
|
|
49
|
+
channel: {
|
|
50
|
+
text: {
|
|
51
|
+
resolveChunkMode: vi.fn(() => "newline"),
|
|
52
|
+
resolveTextChunkLimit: vi.fn(() => 10),
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
} as never);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("passes markdown chunk settings through sendText", async () => {
|
|
59
|
+
const sendText = zalouserPlugin.outbound?.sendText;
|
|
60
|
+
expect(sendText).toBeTypeOf("function");
|
|
61
|
+
if (!sendText) {
|
|
20
62
|
return;
|
|
21
63
|
}
|
|
22
64
|
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
65
|
+
const result = await sendText({
|
|
66
|
+
cfg: { channels: { zalouser: { enabled: true } } } as never,
|
|
67
|
+
to: "group:123456",
|
|
68
|
+
text: "hello world\nthis is a test",
|
|
69
|
+
accountId: "default",
|
|
70
|
+
} as never);
|
|
71
|
+
|
|
72
|
+
expect(mockSendMessage).toHaveBeenCalledWith(
|
|
73
|
+
"123456",
|
|
74
|
+
"hello world\nthis is a test",
|
|
75
|
+
expect.objectContaining({
|
|
76
|
+
profile: "default",
|
|
77
|
+
isGroup: true,
|
|
78
|
+
textMode: "markdown",
|
|
79
|
+
textChunkMode: "newline",
|
|
80
|
+
textChunkLimit: 10,
|
|
81
|
+
}),
|
|
82
|
+
);
|
|
83
|
+
expect(result).toEqual(
|
|
84
|
+
expect.objectContaining({
|
|
85
|
+
channel: "zalouser",
|
|
86
|
+
messageId: "mid-1",
|
|
87
|
+
ok: true,
|
|
88
|
+
}),
|
|
89
|
+
);
|
|
28
90
|
});
|
|
29
91
|
});
|
|
30
92
|
|
|
@@ -58,48 +120,12 @@ describe("zalouser channel policies", () => {
|
|
|
58
120
|
});
|
|
59
121
|
|
|
60
122
|
it("resolves group tool policy by explicit group id", () => {
|
|
61
|
-
const
|
|
62
|
-
expect(resolveToolPolicy).toBeTypeOf("function");
|
|
63
|
-
if (!resolveToolPolicy) {
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
const policy = resolveToolPolicy({
|
|
67
|
-
cfg: {
|
|
68
|
-
channels: {
|
|
69
|
-
zalouser: {
|
|
70
|
-
groups: {
|
|
71
|
-
"123": { tools: { allow: ["search"] } },
|
|
72
|
-
},
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
accountId: "default",
|
|
77
|
-
groupId: "123",
|
|
78
|
-
groupChannel: "123",
|
|
79
|
-
});
|
|
123
|
+
const policy = resolveGroupToolPolicy({ "123": { tools: { allow: ["search"] } } }, "123");
|
|
80
124
|
expect(policy).toEqual({ allow: ["search"] });
|
|
81
125
|
});
|
|
82
126
|
|
|
83
127
|
it("falls back to wildcard group policy", () => {
|
|
84
|
-
const
|
|
85
|
-
expect(resolveToolPolicy).toBeTypeOf("function");
|
|
86
|
-
if (!resolveToolPolicy) {
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
const policy = resolveToolPolicy({
|
|
90
|
-
cfg: {
|
|
91
|
-
channels: {
|
|
92
|
-
zalouser: {
|
|
93
|
-
groups: {
|
|
94
|
-
"*": { tools: { deny: ["system.run"] } },
|
|
95
|
-
},
|
|
96
|
-
},
|
|
97
|
-
},
|
|
98
|
-
},
|
|
99
|
-
accountId: "default",
|
|
100
|
-
groupId: "missing",
|
|
101
|
-
groupChannel: "missing",
|
|
102
|
-
});
|
|
128
|
+
const policy = resolveGroupToolPolicy({ "*": { tools: { deny: ["system.run"] } } }, "missing");
|
|
103
129
|
expect(policy).toEqual({ deny: ["system.run"] });
|
|
104
130
|
});
|
|
105
131
|
|
package/src/channel.ts
CHANGED
|
@@ -20,15 +20,16 @@ import {
|
|
|
20
20
|
buildBaseAccountStatusSnapshot,
|
|
21
21
|
buildChannelConfigSchema,
|
|
22
22
|
DEFAULT_ACCOUNT_ID,
|
|
23
|
-
chunkTextForOutbound,
|
|
24
23
|
deleteAccountFromConfigSection,
|
|
25
24
|
formatAllowFromLowercase,
|
|
25
|
+
isDangerousNameMatchingEnabled,
|
|
26
26
|
isNumericTargetId,
|
|
27
27
|
migrateBaseNameToDefaultAccount,
|
|
28
28
|
normalizeAccountId,
|
|
29
29
|
sendPayloadWithChunkedTextAndMedia,
|
|
30
30
|
setAccountEnabledInConfigSection,
|
|
31
31
|
} from "openclaw/plugin-sdk/zalouser";
|
|
32
|
+
import { buildPassiveProbedChannelStatusSummary } from "../../shared/channel-status-summary.js";
|
|
32
33
|
import {
|
|
33
34
|
listZalouserAccountIds,
|
|
34
35
|
resolveDefaultZalouserAccountId,
|
|
@@ -43,6 +44,7 @@ import { resolveZalouserReactionMessageIds } from "./message-sid.js";
|
|
|
43
44
|
import { zalouserOnboardingAdapter } from "./onboarding.js";
|
|
44
45
|
import { probeZalouser } from "./probe.js";
|
|
45
46
|
import { writeQrDataUrlToTempFile } from "./qr-temp-file.js";
|
|
47
|
+
import { getZalouserRuntime } from "./runtime.js";
|
|
46
48
|
import { sendMessageZalouser, sendReactionZalouser } from "./send.js";
|
|
47
49
|
import { collectZalouserStatusIssues } from "./status-issues.js";
|
|
48
50
|
import {
|
|
@@ -166,6 +168,16 @@ function resolveZalouserQrProfile(accountId?: string | null): string {
|
|
|
166
168
|
return normalized;
|
|
167
169
|
}
|
|
168
170
|
|
|
171
|
+
function resolveZalouserOutboundChunkMode(cfg: OpenClawConfig, accountId?: string) {
|
|
172
|
+
return getZalouserRuntime().channel.text.resolveChunkMode(cfg, "zalouser", accountId);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function resolveZalouserOutboundTextChunkLimit(cfg: OpenClawConfig, accountId?: string) {
|
|
176
|
+
return getZalouserRuntime().channel.text.resolveTextChunkLimit(cfg, "zalouser", accountId, {
|
|
177
|
+
fallbackLimit: zalouserDock.outbound?.textChunkLimit ?? 2000,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
169
181
|
function mapUser(params: {
|
|
170
182
|
id: string;
|
|
171
183
|
name?: string | null;
|
|
@@ -206,6 +218,7 @@ function resolveZalouserGroupPolicyEntry(params: ChannelGroupContext) {
|
|
|
206
218
|
groupId: params.groupId,
|
|
207
219
|
groupChannel: params.groupChannel,
|
|
208
220
|
includeWildcard: true,
|
|
221
|
+
allowNameMatching: isDangerousNameMatchingEnabled(account.config),
|
|
209
222
|
}),
|
|
210
223
|
);
|
|
211
224
|
}
|
|
@@ -595,14 +608,11 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
595
608
|
},
|
|
596
609
|
outbound: {
|
|
597
610
|
deliveryMode: "direct",
|
|
598
|
-
chunker:
|
|
599
|
-
chunkerMode: "
|
|
600
|
-
textChunkLimit: 2000,
|
|
611
|
+
chunker: (text, limit) => getZalouserRuntime().channel.text.chunkMarkdownText(text, limit),
|
|
612
|
+
chunkerMode: "markdown",
|
|
601
613
|
sendPayload: async (ctx) =>
|
|
602
614
|
await sendPayloadWithChunkedTextAndMedia({
|
|
603
615
|
ctx,
|
|
604
|
-
textChunkLimit: zalouserPlugin.outbound!.textChunkLimit,
|
|
605
|
-
chunker: zalouserPlugin.outbound!.chunker,
|
|
606
616
|
sendText: (nextCtx) => zalouserPlugin.outbound!.sendText!(nextCtx),
|
|
607
617
|
sendMedia: (nextCtx) => zalouserPlugin.outbound!.sendMedia!(nextCtx),
|
|
608
618
|
emptyResult: { channel: "zalouser", messageId: "" },
|
|
@@ -613,6 +623,9 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
613
623
|
const result = await sendMessageZalouser(target.threadId, text, {
|
|
614
624
|
profile: account.profile,
|
|
615
625
|
isGroup: target.isGroup,
|
|
626
|
+
textMode: "markdown",
|
|
627
|
+
textChunkMode: resolveZalouserOutboundChunkMode(cfg, account.accountId),
|
|
628
|
+
textChunkLimit: resolveZalouserOutboundTextChunkLimit(cfg, account.accountId),
|
|
616
629
|
});
|
|
617
630
|
return buildChannelSendResult("zalouser", result);
|
|
618
631
|
},
|
|
@@ -624,6 +637,9 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
624
637
|
isGroup: target.isGroup,
|
|
625
638
|
mediaUrl,
|
|
626
639
|
mediaLocalRoots,
|
|
640
|
+
textMode: "markdown",
|
|
641
|
+
textChunkMode: resolveZalouserOutboundChunkMode(cfg, account.accountId),
|
|
642
|
+
textChunkLimit: resolveZalouserOutboundTextChunkLimit(cfg, account.accountId),
|
|
627
643
|
});
|
|
628
644
|
return buildChannelSendResult("zalouser", result);
|
|
629
645
|
},
|
|
@@ -637,15 +653,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
637
653
|
lastError: null,
|
|
638
654
|
},
|
|
639
655
|
collectStatusIssues: collectZalouserStatusIssues,
|
|
640
|
-
buildChannelSummary: ({ snapshot }) => (
|
|
641
|
-
configured: snapshot.configured ?? false,
|
|
642
|
-
running: snapshot.running ?? false,
|
|
643
|
-
lastStartAt: snapshot.lastStartAt ?? null,
|
|
644
|
-
lastStopAt: snapshot.lastStopAt ?? null,
|
|
645
|
-
lastError: snapshot.lastError ?? null,
|
|
646
|
-
probe: snapshot.probe,
|
|
647
|
-
lastProbeAt: snapshot.lastProbeAt ?? null,
|
|
648
|
-
}),
|
|
656
|
+
buildChannelSummary: ({ snapshot }) => buildPassiveProbedChannelStatusSummary(snapshot),
|
|
649
657
|
probeAccount: async ({ account, timeoutMs }) => probeZalouser(account.profile, timeoutMs),
|
|
650
658
|
buildAccountSnapshot: async ({ account, runtime }) => {
|
|
651
659
|
const configured = await checkZcaAuthenticated(account.profile);
|
package/src/config-schema.ts
CHANGED
|
@@ -19,6 +19,7 @@ const zalouserAccountSchema = z.object({
|
|
|
19
19
|
enabled: z.boolean().optional(),
|
|
20
20
|
markdown: MarkdownConfigSchema,
|
|
21
21
|
profile: z.string().optional(),
|
|
22
|
+
dangerouslyAllowNameMatching: z.boolean().optional(),
|
|
22
23
|
dmPolicy: DmPolicySchema.optional(),
|
|
23
24
|
allowFrom: AllowFromListSchema,
|
|
24
25
|
historyLimit: z.number().int().min(0).optional(),
|
package/src/group-policy.test.ts
CHANGED
|
@@ -23,6 +23,18 @@ describe("zalouser group policy helpers", () => {
|
|
|
23
23
|
).toEqual(["123", "group:123", "chan-1", "Team Alpha", "team-alpha", "*"]);
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
+
it("builds id-only candidates when name matching is disabled", () => {
|
|
27
|
+
expect(
|
|
28
|
+
buildZalouserGroupCandidates({
|
|
29
|
+
groupId: "123",
|
|
30
|
+
groupChannel: "chan-1",
|
|
31
|
+
groupName: "Team Alpha",
|
|
32
|
+
includeGroupIdAlias: true,
|
|
33
|
+
allowNameMatching: false,
|
|
34
|
+
}),
|
|
35
|
+
).toEqual(["123", "group:123", "*"]);
|
|
36
|
+
});
|
|
37
|
+
|
|
26
38
|
it("finds the first matching group entry", () => {
|
|
27
39
|
const groups = {
|
|
28
40
|
"group:123": { allow: true },
|
package/src/group-policy.ts
CHANGED
|
@@ -23,6 +23,7 @@ export function buildZalouserGroupCandidates(params: {
|
|
|
23
23
|
groupName?: string | null;
|
|
24
24
|
includeGroupIdAlias?: boolean;
|
|
25
25
|
includeWildcard?: boolean;
|
|
26
|
+
allowNameMatching?: boolean;
|
|
26
27
|
}): string[] {
|
|
27
28
|
const seen = new Set<string>();
|
|
28
29
|
const out: string[] = [];
|
|
@@ -43,10 +44,12 @@ export function buildZalouserGroupCandidates(params: {
|
|
|
43
44
|
if (params.includeGroupIdAlias === true && groupId) {
|
|
44
45
|
push(`group:${groupId}`);
|
|
45
46
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
if (params.allowNameMatching !== false) {
|
|
48
|
+
push(groupChannel);
|
|
49
|
+
push(groupName);
|
|
50
|
+
if (groupName) {
|
|
51
|
+
push(normalizeZalouserGroupSlug(groupName));
|
|
52
|
+
}
|
|
50
53
|
}
|
|
51
54
|
if (params.includeWildcard !== false) {
|
|
52
55
|
push("*");
|
|
@@ -4,6 +4,7 @@ import "./monitor.send-mocks.js";
|
|
|
4
4
|
import { __testing } from "./monitor.js";
|
|
5
5
|
import { sendMessageZalouserMock } from "./monitor.send-mocks.js";
|
|
6
6
|
import { setZalouserRuntime } from "./runtime.js";
|
|
7
|
+
import { createZalouserRuntimeEnv } from "./test-helpers.js";
|
|
7
8
|
import type { ResolvedZalouserAccount, ZaloInboundMessage } from "./types.js";
|
|
8
9
|
|
|
9
10
|
describe("zalouser monitor pairing account scoping", () => {
|
|
@@ -80,19 +81,11 @@ describe("zalouser monitor pairing account scoping", () => {
|
|
|
80
81
|
raw: { source: "test" },
|
|
81
82
|
};
|
|
82
83
|
|
|
83
|
-
const runtime: RuntimeEnv = {
|
|
84
|
-
log: vi.fn(),
|
|
85
|
-
error: vi.fn(),
|
|
86
|
-
exit: ((code: number): never => {
|
|
87
|
-
throw new Error(`exit ${code}`);
|
|
88
|
-
}) as RuntimeEnv["exit"],
|
|
89
|
-
};
|
|
90
|
-
|
|
91
84
|
await __testing.processMessage({
|
|
92
85
|
message,
|
|
93
86
|
account,
|
|
94
87
|
config,
|
|
95
|
-
runtime,
|
|
88
|
+
runtime: createZalouserRuntimeEnv(),
|
|
96
89
|
});
|
|
97
90
|
|
|
98
91
|
expect(readAllowFromStore).toHaveBeenCalledWith(
|