@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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/zalouser",
3
- "version": "2026.3.11",
3
+ "version": "2026.3.13",
4
4
  "description": "OpenClaw Zalo Personal Account plugin via native zca-js integration",
5
5
  "type": "module",
6
6
  "dependencies": {
@@ -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
- export async function resolveZalouserAccount(params: {
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
- const accountEnabled = merged.enabled !== false;
55
- const enabled = baseEnabled && accountEnabled;
56
- const profile = resolveProfile(merged, accountId);
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 = normalizeAccountId(params.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: RuntimeEnv = {
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: "split", longTextLength: 3000, maxChunkLength: 2000 },
116
+ chunking: { mode: "passthrough", longTextLength: 3000 },
98
117
  createHarness: ({ payload, sendResults }) => {
99
118
  primeSendMock(mockedSend, { ok: true, messageId: "zlu-1" }, sendResults);
100
119
  return {
@@ -1,30 +1,92 @@
1
1
  import { beforeEach, describe, expect, it, vi } from "vitest";
2
2
  import { zalouserPlugin } from "./channel.js";
3
- import { sendReactionZalouser } from "./send.js";
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
- describe("zalouser outbound chunker", () => {
16
- it("chunks without empty strings and respects limit", () => {
17
- const chunker = zalouserPlugin.outbound?.chunker;
18
- expect(chunker).toBeTypeOf("function");
19
- if (!chunker) {
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 limit = 10;
24
- const chunks = chunker("hello world\nthis is a test", limit);
25
- expect(chunks.length).toBeGreaterThan(1);
26
- expect(chunks.every((c) => c.length > 0)).toBe(true);
27
- expect(chunks.every((c) => c.length <= limit)).toBe(true);
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 resolveToolPolicy = zalouserPlugin.groups?.resolveToolPolicy;
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 resolveToolPolicy = zalouserPlugin.groups?.resolveToolPolicy;
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: chunkTextForOutbound,
599
- chunkerMode: "text",
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);
@@ -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(),
@@ -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 },
@@ -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
- push(groupChannel);
47
- push(groupName);
48
- if (groupName) {
49
- push(normalizeZalouserGroupSlug(groupName));
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(