@clisbot/channels-zalouser

中文文档
2.2.3发布于 2026/11/05TypeScriptES Module近 30 天下载 3389

In-repo Zalo Personal (zalouser) channel vertical (goal ledger slice 17). Ported from extensions/zalouser/src@5d8067a4483: the zca-js client boundary, the QR login state machine, the credential/session closure, the markdown text-style compiler, the send

原始使用文档
文档目录5 节
  1. Layout
  2. What works
  3. Not supported
  4. Live testing
  5. Tests

@clisbot/channels-zalouser

The in-repo Zalo Personal channel vertical (goal ledger slice 17), ported from extensions/zalouser/src at 5d8067a4483. Hub wiring landed in a follow-up slice; HUB-WIRING.md is the handoff document it was built from and stays the record of what the Hub side owns.

Zalo Personal is not a bot. It drives a real personal Zalo account through zca-js, the community client, and the account is linked by scanning a QR code with a phone. That single fact shapes the whole vertical:

  • there is no token to paste — onboarding is a live, multi-step QR flow;
  • the credential is a session (imei + cookie jar + user agent) that must be stored, refreshed, and revoked, not a static secret;
  • inbound is a push WebSocket with no ack and no cursor, so a message the server pushed is gone if the process drops it;
  • everything the account can do, a human on that account can do — this is the highest-trust channel in the catalog.

Layout

PathWhat
src/zca-client.tsThe zca-js boundary; the SDK loads lazily so metadata can be read without its socket tree
src/zalo-js.tsThe ported client closure: QR login, session credentials, listener, sends, directory
src/session-state.tsThe credential records and their store keys
src/fusion/session-store.tsThe ZalouserSessionStore port + the HostRuntime keyed-store implementation
src/fusion/qr-setup.tsThe five QR verbs the Hub drives: start, poll, cancel, relink, logout
src/fusion/listener-session.tsThe L2 session: admission before the message is let go
src/fusion/admission.tsDurable admission onto the Hub ingress queue
src/fusion/inbound-adapter.tsNative message → shared ChannelInboundEvent (kind + facts)
src/text-styles*.tsThe markdown → Zalo style-range compiler (Zalo has no markdown; it takes styled ranges)
src/lifecycle/start-account.tsL4: install the session store, probe, register inbound, run the listener
src/tool.ts, src/fusion/tools.tsThe zalouser agent tool and its Hub registrar

Deviations, omissions and their reasons are in upstream-sync.json — not in prose here. Verify with:

node scripts/channel-upstream-sync.mjs check --pkg zalouser

What works

  • QR login — start → QR PNG (data URL + a private temp file) → poll → linked, with relink (force a fresh QR, discarding the stored session), cancel and logout. Expiry and decline are reported, not swallowed.
  • Session persistence — the QR credentials go to the injected session store, are restored on the next account start, refresh in place as zca-js rotates the cookie, and leave a durable revocation marker on logout so a stale copy cannot resurrect them.
  • Inbound — direct and group messages, sender labels, group names, quotes (the quoted body and owner travel as facts), explicit and implicit mentions, and a leading /verb as a command event. Every message is durably admitted to the Hub queue before the listener moves on.
  • Outbound — text with Zalo's native style ranges compiled from markdown, 2000-character chunking with the styles sliced per chunk, native file upload (audio becomes a voice message), links, and typing/delivered/seen primitives.
  • Message tool — one action, react, over msgId + cliMsgId.
  • Agent tool — zalouser with upstream's seven actions (send, image, link, friends, groups, me, status).
  • Directory — friend and group lookup by name or id, and group members.

Not supported

  • Threads. Zalo Personal has none; upstream pins replyToMode: "off". An outbound reply is a plain message on the conversation.
  • Inbound media download. Attachments arrive as message content, and the upstream monitor that turned them into agent attachments is Hub-owned and not carried (upstream-sync.json omitted: monitor.ts).
  • Typing / delivered / seen. Wired on the client (send.ts) but not driven — the Hub's streaming producer does not know this channel yet.
  • Polls, buttons, edits, deletes, pins. The platform has no API for them.
  • DM/group access policy, pairing, allowlist name resolution. Hub-owned (goal ledger slice 23). The group scope helpers are carried, the policy is not.

Live testing

Live E2E needs a human with a phone: the QR code must be scanned by the Zalo account under test, and there is no way around that. Plan for a person, not a credential.

Environment variable names (values live in the repo-root .env, which this package never reads directly and which must not be committed):

VariableRole
ZALO_PERSONAL_TEST_CLISBOT_USER_IDThe Zalo user id of the personal account under test
ZALO_PERSONAL_TEST_LONG_USER_IDThe Zalo user id of the human driver who sends the marker
ZALOUSER_PROFILE / ZCA_PROFILEThe credential profile the session is stored under (upstream's own env names)
ZALOUSER_TMP_DIROptional override for where the QR PNG is written

The loop:

  1. Start the account; it fails fast with "not linked" until a session exists.
  2. Call the QR start verb, open the returned PNG, scan it from the phone signed in as ZALO_PERSONAL_TEST_CLISBOT_USER_ID.
  3. Poll until linked; confirm the reported user.userId matches that id.
  4. From the driver account (ZALO_PERSONAL_TEST_LONG_USER_ID), DM the linked account a marker.
  5. Read the agent's reply back in the same conversation and match the returned message id — a send without read-back is not a verified send.

Two live risks worth stating before anyone runs this:

  • A personal-account client is not a sanctioned API. Use a test account.
  • The stored session is a full credential for that account. It must be encrypted at rest by whatever backs the session store (HUB-WIRING.md §6).

Tests

npx vitest run packages/channels/zalouser/src/<file>.test.ts --maxWorkers=1 --no-file-parallelism

Ported upstream tests keep their names; Fusion-owned tests sit next to the seam they cover (src/fusion/*.test.ts).

版本与发布记录

@clisbot/channels-zalouser 共发布 18 个版本,首次发布于 2026/10/10;过去 12 个月发布了 18 个版本。

版本发布日期距今
2.2.3latest2026/11/055 天前
2.2.22026/11/037 天前
2.2.12026/11/019 天前
2.2.02026/10/2714 天前
2.1.82026/10/2218 天前
2.1.72026/10/2219 天前

关于 @clisbot/channels-zalouser 的常见问题

@clisbot/channels-zalouser 是什么?

In-repo Zalo Personal (zalouser) channel vertical (goal ledger slice 17). Ported from extensions/zalouser/src@5d8067a4483: the zca-js client boundary, the QR login state machine, the credential/session closure, the markdown text-style compiler, the send

如何安装 @clisbot/channels-zalouser?

在项目目录运行 npm install @clisbot/channels-zalouser,或使用 pnpm add / yarn add / bun add。

@clisbot/channels-zalouser 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@clisbot/channels-zalouser 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@clisbot/channels-zalouser 可以商用吗?

@clisbot/channels-zalouser 使用 未声明 许可。package.json 没有声明许可证。没有许可证的代码默认保留全部权利,商用前请联系作者或查看仓库中的 LICENSE 文件。以上为概括,不构成法律意见,请以许可原文为准。

@clisbot/channels-zalouser 的下载量有多少?

根据 npm 官方下载统计,@clisbot/channels-zalouser 在 2026/10/10 – 2026/11/08 这 30 天内被下载 3,389 次,日均约 113 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@clisbot/channels-zalouser 还在维护吗?

最新版本 2.2.3 发布于 2026/11/05(5 天前);至今共发布 18 个版本,其中过去 12 个月发布了 18 个;首次发布于 2026/10/10。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。