这篇文章用来记录一下最近快折腾完的一个项目:weixin-household-gateway。
项目地址:
github.com/thekfjie/weixin-household-gateway
简单说,它是一个把 Codex / AI 后端接进微信里的家庭网关。
它不是一个网页端 agent,也不是单纯的聊天机器人,而是一个部署在自己服务器上的微信入口:我可以在微信里让它处理服务器、代码、文件、附件,也可以让家人用相对安全的方式进行普通 AI 对话。
好复杂…
1. 为什么要做这个?
一开始的想法其实很朴素:我平时很多东西都在服务器上跑,博客、API、音乐解析、各种乱七八糟的服务都有。如果我人在外面,想让 AI 帮我看一下日志、改个配置、分析个文件,最自然的入口其实不是网页,而是微信。
此外,我还有一个想法是想分享我的模型给家里人用,以最便捷的方式让家里人用上某些大模型,不用考虑网络环境,操作学习成本等等…很自然的,微信成了最好的解决入口。
因为微信永远在线。
与此同时,微信在今年年初提供了一个官方接口:微信openclaw(🙏)
但是我不想用上openclaw这种token大户,我的周限额肯定喂不饱他(🙏),然后直接把一个 agent 丢进微信肯定不行。微信的聊天体验和网页控制台完全不是一回事:
- 微信不能原地刷新一条消息。
- 微信没有流式输出。
- 消息太碎会刷屏。
- 家人使用时不能和 admin 拥有同样权限。
- 服务器操作、文件读取、sudo、docker 这些东西必须分清边界。
所以这个项目最后的目标不是“把网页 agent 搬进微信”,而是做一个更适合微信环境的 AI 网关。
2. 现在它大概长什么样?
整体是一个 TypeScript / Node.js 单服务,部署在 Linux 服务器上。核心链路大概是这样:
flowchart TB
USER["微信用户"] --> ILINK["iLink 轮询"]
ILINK --> WORKER["WechatWorker"]
WORKER --> ROUTE{"角色与任务路由"}
ROUTE -->|admin| ADMIN["Admin ACP 持久会话"]
ROUTE -->|family 普通聊天| API["Family API 直连"]
ROUTE -->|family 复杂任务| FACP["Family ACP 非持久任务"]
ADMIN --> CODEX["Codex / ACP / CLI"]
API --> MODEL["OpenAI-compatible API"]
FACP --> CODEX
CODEX --> FILTER["输出过滤与分段"]
MODEL --> FILTER
FILTER --> SEND["iLink 回复微信"]
WORKER --> DB["SQLite 会话与消息"]
WORKER --> FILES["inbox / office / outbox 工作区"]
它大概做这些事情:
- 轮询 iLink 微信消息。
- 识别账号角色:
admin/family。 - 根据任务类型选择 API 或 ACP。
- 下载微信附件,放入受控工作区。
- 调用 Codex 处理任务。
- 把结果按微信更能接受的方式发回去。
- 用 SQLite 保存账号、会话、消息、附件记录。
部署目录现在也比较固定:
/opt/weixin-household-gateway
/var/lib/weixin-household-gateway
inbox/
office/
outbox/
~/.codex
其中 inbox 是入站附件,office 是中间处理区,outbox 是可以发回微信的成品文件。
3. admin 和 family 为什么要分开?
这是这个项目里我觉得最重要的一点。
admin 是给我自己用的。它默认走 ACP 持久会话,适合做服务器运维、代码修改、长任务、多步骤工具调用。它可以知道更多上下文,也可以在我明确配置以后执行更强的操作。
family 是给家人用的。它默认优先走直连 API,普通聊天、图片理解、轻量问答都走这一条。只有遇到压缩包、Office 文件、复杂附件、需要工具处理的任务时,才升级到 ACP,而且是非持久 ACP。
这样做的原因很现实:
- 家人不需要碰服务器运维权限。
- 普通聊天不应该为了“像 agent”就绕一圈工具链。
- API 路径更快,也更容易保持上下文缓存。
- ACP 适合工具任务,但不应该变成 family 的默认会话模式。
最后变成了一条比较舒服的规则:
| 场景 | 默认路径 | 说明 |
|---|---|---|
| admin 运维/代码/长任务 | ACP 持久会话 | 给我自己用 |
| family 普通聊天 | API 直连 | 更快、更安全 |
| family 文件/附件复杂任务 | ACP 非持久 | 用完回到 API 主轨道 |
这个设计中间改了很多次。尤其是 family 的上下文,一开始只取最近几条消息,体验非常糟糕,缓存也打不到。后来改成了独立的 API 上下文轨道,预算大约 100k 字符,尽量稳定 prompt cache key,不要每次都像新开一局。
4. 微信里没有真正的流式输出
这是另一个很折磨的点。
网页里可以 token 级刷新,终端里可以一行一行滚动。但微信不是这样的。微信里你只能发消息,不能优雅地修改同一条消息。硬要逐 token 发,只会变成刷屏。
所以最后做的是更适合微信的“准流式”:
- admin ACP 默认开启过程输出。
- family ACP 默认不开过程输出。
- family API 默认不开提前分段。
- 最终回答还是会按长度兜底分段。
现在的大概策略是:
| 模式 | 默认过程输出 | 说明 |
|---|---|---|
| admin-acp | 开 | 可以看到它正在做什么,适合长任务 |
| family-acp | 关 | 默认只给最终结果 |
| family-api | 关 | 普通聊天优先等完整答案 |
这个地方最开始我也想过“能不能真流式”,后来发现微信聊天里最重要的不是技术上像不像流,而是用户体感是否自然。
比如 admin 长任务里,我可以接受它隔一段发一句:
我看完主链路了,现在项目是一个 TypeScript/Node 单服务。
这比傻等 30 秒“思考中”要舒服很多。
但 family 那边如果也这么发,就会显得很奇怪,像一个机器人在把内部施工日志全部倒出来。
所以最后按角色区分。
5. ACP 这条线最难受
这个项目真正痛苦的地方基本都在 ACP。
ACP 不是单纯“调用一次模型,拿一个字符串”。它会不断吐事件:
- status
- thinking
- responding
- tool progress
- visible message run
- usage update
- prompt result
其中最坑的是:visible_message_run 不一定就是最终答案。它可能是过程说明,也可能是最后对用户有用的文本。
所以项目里专门做了 collector,把 ACP 事件分出来,再判断哪些能发给微信,哪些只做内部状态,哪些应该作为最终答案。
后来还遇到过一个很典型的问题:ACP 最后一步不一定及时输出。如果项目一到超时就直接判异常,前台这轮就不等了,后续结果也接不回来。最后只能把超时、取消、收尾结果这些行为重新梳理,让它至少能给出明确状态,而不是让用户看起来像“突然没了”。
这种问题很烦,但也很真实。
因为你一旦把 AI 放进微信,它就不再只是一个函数调用,而是一个正在和人聊天的东西。它卡住、沉默、重复、乱分段,用户都会感受到。
6. 权限和沙箱也踩了不少坑
这个项目真的会接触服务器!!!
所以权限分成了几层:
- 项目自己的角色策略。
- family 的小模型权限审核。
- Codex ACP 的权限请求。
- Linux 用户和 sudoers。
- Codex 自己的 sandbox。
这几层叠在一起后,就会出现很经典的情况:
明明项目已经允许 admin sudo 了,但 Codex 自己的 sandbox 还在拦。
后来才发现,Linux 下 Codex 默认 sandbox 会走 bubblewrap/bwrap,而且可能带 NoNewPrivs=1。这时即使服务用户 sudoers 放开了,sudo 也可能失败。
所以现在安装器会处理几件事:
- 检测
bubblewrap/bwrap。 - 必要时建立兼容软链。
- admin 运维模式下配置
danger-full-access。 - family 不放开这个参数。
- Codex trusted project 只信任项目目录和 admin runtime,不信任
/。
这里的原则也很简单:
admin 是我的运维入口,可以强;family 是家用入口,要稳和收敛。
7. 文件任务:能做,但不能乱做
微信里另一个很常见的场景是发文件。
比如发一个压缩包、Word、Excel、图片,想让 AI 看一下里面是什么。这个项目会把附件下载到 inbox,然后根据类型决定是否升级到 ACP。
但是这里也有坑。
比如有一次看起来像 zip 的文件,实际内容更接近 RAR5,本机又没有合适的 RAR 读取工具。模型绕了一圈后想 apt-get update 安装工具,结果 family 策略当然不能放行。
所以现在 family 的设计更偏向:
- 能用现有工具处理就处理。
- 需要系统级安装、sudo、docker、systemctl 之类操作时,不要乱绕。
- 输出给用户的内容要过滤路径、命令片段和疑似内部推理。
不然家人只是想看个文件,结果收到一堆服务器施工现场照片,那也太抽象了。
8. 这个项目最后解决了什么?
如果用一句话总结:
它把 Codex 变成了一个可以通过微信使用的、分角色、可部署、能处理文件和长任务的家庭 AI 网关。
它现在比较重要的能力有:
- 微信多账号接入。
- admin / family 分权。
- family 普通聊天优先 API。
- family 复杂任务自动升级 ACP。
- admin ACP 持久会话。
- 微信友好的分段输出。
- 可配置过程输出。
- 文件下载和文件回传。
- SQLite 会话存储。
- Codex 配置生成。
- Linux 安装脚本、systemd 服务、doctor 自检。
- prompt 模板外置,方便以后改。
还有一些看起来不起眼,但实际很影响体验的东西:
/new新会话。/output控制过程输出。- API 上下文预算和缓存命中。
- ACP 最终答案提取。
- 超时后的取消和收尾。
- family 输出过滤。
这些东西单独看都不华丽,但拼起来以后,微信里的体验才会从“能用”慢慢变成“我能忍受,甚至愿意继续用”。
9. 还不完美的地方
当然,它还不是一个完美产品。
我现在比较清楚的遗留问题有:
- 微信消息没有真正的送达确认。 (acp工作流还是有点奇怪)
- 撤回/编辑消息能力还没有接进去。(微信何时放权?!)
- ACP 事件未来可能继续变,需要跟着适配。
- family 的安全策略还需要继续观察真实使用场景。 (总有奇怪的问题)
- prompt 还需要慢慢调,不可能一次写完。(我不是promote糕手q)
但至少它已经从“一个很想当然的想法”,变成了一套能部署、能更新、能解释、能维护的东西。
这一路确实像爬山。
中间有很多很小但很烦的坑:分段怪、上下文丢、ACP 不收尾、sudo 被沙箱拦、bubblewrap 名字不一致、API 和 ACP 来回切、family 缓存打不到……
好消息是,它们大部分都已经被一个个按下去了。
10. 最后
总之,weixin-household-gateway 目前算是快到一个可以收口的阶段了。
以后如果继续折腾,大概会围绕消息撤回、文件处理能力、ACP 事件适配和更舒服的输出体验继续慢慢改。大概地基已经可以了吧?希望是…
处理了好多边界:
权限边界、上下文边界、输出边界、家庭成员之间的使用边界,还有我自己能不能继续维护下去的边界。
先写到这里。
终于可以给这个坑立一块小牌子了。
