我把 ZCode 的 Hook 机制扒了一遍
📌 项目地址:https://github.com/Aealen/zcode-hooks-poc
欢迎Star、提Issue、交流ZCode Hook的玩法。如果这篇文章帮到了你,仓库点个Star是对我最大的鼓励。
项目摘要
ZCode 的 Hook 机制能拦截工具调用,但官方文档语焉不详,社区信息真假参半。我反编译了客户端源码(11.7MB 的混淆 zcode.cjs),配合端到端实测,把 Hook 的配置、事件、Schema、调用链全部摸清——并验证了最不被看好的能力:接管 AskUserQuestion 弹窗。
结论:完全可行。AI 调用 AskUserQuestion 时,原生弹窗可以完全不出现,由外部 UI 接管作答。
为什么要做这件事
如果你用过 ZCode,大概率遇到过这种体验断裂:AI 正在干活,突然弹出一个权限询问窗口,你得切回 ZCode 主窗口、点击允许、再切回原本的工作。一天下来,这种切换可能发生几十次。
ZCode 的 Hook 机制理论上允许外部进程介入工具调用的权限决策——这意味着你可以做一个集中的外部 UI(桌面浮窗、手机推送、甚至另一台电脑的网页)来管理所有工具权限,不用反复切窗口。
但这条路线到底走不走得通?具体有三个问题我想搞清楚:
- Hook 真的能拦截所有工具吗?还是有例外?
- AskUserQuestion 这种特殊的交互式工具能不能接管?(社区曾有"不能"的结论)
- 接管后用户输入的中文,能不能正确传回 ZCode?
核心发现
1. ZCode 的权限决策链远比想象中复杂
反编译源码后,我发现 ZCode 的 checkPermission 有一条 13 步的决策优先级链,从 PlanMode 特殊工具、到 requiresUserInteraction、到 yolo 模式、到项目规则、到内置白名单……每一步都可能让 hook 被触发或被绕过。
关键洞察:AskUserQuestion 走的是 requiresUserInteraction → ask 分支,不是被默认放行——它会触发 PermissionRequest hook。这推翻了旧版"AskUserQuestion 绕过 hook"的结论。
2. PermissionRequest 有个隐藏能力:updatedInput
ZCode 的 PermissionRequest hook 不只能 allow/deny,还能改写工具输入:
{
"hookSpecificOutput": {
"hookEventName": "PermissionRequest",
"decision": {
"behavior": "allow",
"updatedInput": { "answers": { "问题": "用户的答案" } }
}
}
}
AskUserQuestion 的 handler 原样返回输入里的 answers 字段——所以只要 hook 注入答案,原生 UI 不会出现,AI 拿到的就是 hook 填进去的值。这是完全接管的钥匙。
3. PowerShell 5.1 的"4 层编码地狱"
真正的拦路虎不是 ZCode 源码,而是 Windows 中文环境下的 PowerShell 5.1。
实测中发现,一个 PowerShell hook 脚本要正确处理中文,4 层独立的编码点全部默认 GBK,每一层都要单独强制 UTF-8,缺一不可:
| 层 | 问题 | 修复 |
|---|---|---|
| ps1 文件本身 | 无 BOM 时按 GBK 读,中文注释乱码破坏语法 | 保存为 UTF-8 with BOM |
| stdin 读取 | [Console]::In 默认 GBK,破坏中文 JSON | InputEncoding = UTF8 |
| HTTP 响应解码 | Invoke-RestMethod 默认 GBK | 改用 WebClient.UploadData + 手动 UTF8.GetString |
| stdout 输出 | OutputEncoding 仍是 GBK | 显式设置为 UTF8 |
这也是旧版"AskUserQuestion 不触发 hook"误判的真正根因——hook 一直被调用,只是中文乱码让 JSON 解析失败,hook 静默退出,bus 收不到事件,看起来像"没触发"。
成果
所有核心能力端到端跑通:
| 能力 | 状态 |
|---|---|
| PermissionRequest 接管(Write/Edit/Bash) | ✅ |
| PermissionRequest deny / allow | ✅ |
| PreToolUse 拦截 + 通知 | ✅ |
| PostToolUse 结果展示 | ✅ |
| AskUserQuestion 完全接管 | ✅ 核心突破 |
| 中文 Other 自定义输入端到端字节级正确 | ✅ |
| ZCode 原生 UI 不弹出 | ✅ |
技术架构
整个 PoC 零依赖、单文件 UI、纯 Node.js 总线:
ZCode 调用工具
↓ stdin JSON
PowerShell Hook 探针(UTF-8 全链路)
↓ HTTP
Node.js 总线(HTTP + SSE)
↓ SSE
浏览器 UI(询问卡片 / 事件流)
↓ 用户作答
总线透传 → hook 包装 → ZCode 收到决策
绑定 127.0.0.1 only,安全红线——外部网络无法访问。
踩过的坑
完整的 9 个坑都在仓库里:
- PowerShell 反引号续行 + 中文注释的解析 bug
- PowerShell
$inputvs[Console]::In.ReadToEnd() - Windows cmd.exe 吞双引号
- WindowsApps 的 PowerShell 7 symlink 跨平台 spawn 失败
- PreToolUse vs PermissionRequest 返回 Schema 不同
hookSpecificOutput严格 schema(多余字段直接失败)- UTF-8 无 BOM 的 ps1 在 PowerShell 5.1 下乱码
- PowerShell 5.1 在中文 Windows 上的 4 层编码地狱(最深的坑)
每个坑都有现象、根因、解法、验证方法。
你能拿这套东西做什么
- 做一个桌面权限管理工具(Tauri 壳包现有 UI 即可)
- 研究 ZCode 内部机制(4 篇文档 + 源码 offset 索引)
- 扩展到其他工具(Cursor、CodeX 等支持类似 hook 的工具)
- 学习 PowerShell 在 Windows 下的编码处理(4 层编码地狱那部分)
来玩
👉 https://github.com/Aealen/zcode-hooks-poc
git clone https://github.com/Aealen/zcode-hooks-poc.git
cd zcode-hooks-poc
.\install.ps1 # 安装 hook(会自动备份 ZCode config)
# 完全重启 ZCode
.\start.ps1 # 启动总线 + 自动开浏览器
三步就能在浏览器里看到所有 ZCode 工具调用的事件流,并接管权限询问。
卸载也一键:
.\uninstall.ps1 # 恢复 ZCode 原状
这是一个 PoC,验证完毕即告一段落,仓库主要作为参考留存,不计划继续迭代新功能。
如果文章对你有帮助,点个 ⭐ Star 就是对我最大的鼓励。也可以开 Issue 交流 ZCode Hook 的玩法——但接受 PR 之前请先开 Issue 讨论,避免做无用功。
