cli-wechat-bridge 1.1.2 → 1.1.4

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.
Files changed (46) hide show
  1. package/README.md +129 -150
  2. package/bin/_run-entry.mjs +12 -0
  3. package/bin/wechat-claude-start.mjs +7 -2
  4. package/bin/wechat-claude.mjs +1 -1
  5. package/bin/wechat-codex-start.mjs +7 -2
  6. package/bin/wechat-codex.mjs +1 -1
  7. package/bin/wechat-opencode-start.mjs +7 -2
  8. package/bin/wechat-opencode.mjs +1 -1
  9. package/bin/wechat-pi-start.mjs +10 -0
  10. package/bin/wechat-pi.mjs +5 -0
  11. package/dist/bridge/bridge-adapter-common.js +2 -0
  12. package/dist/bridge/bridge-adapters.claude.js +21 -1
  13. package/dist/bridge/bridge-adapters.codex.js +85 -6
  14. package/dist/bridge/bridge-adapters.core.js +112 -6
  15. package/dist/bridge/bridge-adapters.js +5 -3
  16. package/dist/bridge/bridge-adapters.opencode.js +390 -52
  17. package/dist/bridge/bridge-adapters.pi.js +707 -0
  18. package/dist/bridge/bridge-adapters.shared.js +22 -102
  19. package/dist/bridge/bridge-final-reply.js +17 -2
  20. package/dist/bridge/bridge-process-reaper.js +14 -4
  21. package/dist/bridge/bridge-state.js +42 -6
  22. package/dist/bridge/bridge-utils.js +94 -237
  23. package/dist/bridge/wechat-bridge.js +77 -131
  24. package/dist/bridge/wechat-forwarding.js +58 -0
  25. package/dist/companion/codex-remote-client.js +20 -6
  26. package/dist/companion/local-companion-link.js +13 -3
  27. package/dist/companion/local-companion-start.js +64 -16
  28. package/dist/companion/local-companion.js +8 -5
  29. package/dist/companion/pi-tui-bridge-extension.js +256 -0
  30. package/dist/daemon/daemon-link.js +25 -3
  31. package/dist/daemon/wechat-daemon.js +264 -77
  32. package/dist/i18n/index.js +0 -3
  33. package/dist/i18n/messages-en.js +4 -5
  34. package/dist/i18n/messages-zh.js +4 -5
  35. package/dist/utils/atomic-file.js +15 -0
  36. package/dist/utils/doctor.js +27 -11
  37. package/dist/wechat/channel-config.js +0 -2
  38. package/dist/wechat/setup.js +5 -9
  39. package/dist/wechat/wechat-transport.js +14 -8
  40. package/package.json +113 -102
  41. package/bin/wechat-bridge-claude.mjs +0 -5
  42. package/bin/wechat-bridge-codex.mjs +0 -5
  43. package/bin/wechat-bridge-opencode.mjs +0 -5
  44. package/bin/wechat-bridge-shell.mjs +0 -5
  45. package/bin/wechat-bridge.mjs +0 -5
  46. package/dist/bridge/bridge-adapters.shell.js +0 -363
package/README.md CHANGED
@@ -15,7 +15,7 @@
15
15
  <a href="https://github.com/UNLINEARITY/CLI-WeChat-Bridge/actions/workflows/ci.yml"><img alt="CI" src="https://img.shields.io/github/actions/workflow/status/UNLINEARITY/CLI-WeChat-Bridge/ci.yml?branch=main&label=CI&style=for-the-badge&logo=githubactions&logoColor=white&labelColor=1c1917"></a>
16
16
  </p>
17
17
 
18
- **命令行工具的微信桥接**:本项目将微信消息桥接到本地运行的 [`Codex`](https://github.com/openai/codex)、[`Claude Code`](https://code.claude.com/docs/en/overview) 和 [`OpenCode`](https://github.com/anomalyco/opencode),同时把本地输出、审批请求与运行状态同步回微信。
18
+ **命令行工具的微信桥接**:本项目将微信消息桥接到本地运行的 [`Codex`](https://github.com/openai/codex)、[`Claude Code`](https://code.claude.com/docs/en/overview)、[`OpenCode`](https://github.com/anomalyco/opencode) 和 [`Pi`](https://github.com/earendil-works/pi),同时把本地输出、审批请求与运行状态同步回微信。
19
19
 
20
20
  项目围绕本地工作流设计,重点是保留**本地原生终端体验**:你仍然在本地使用原生 CLI 和高级启动参数,微信负责远程输入、结果回流与状态同步。
21
21
 
@@ -27,14 +27,14 @@
27
27
  - [运行配置](docs/configuration.md):数据目录、上传大小限制、调试开关等环境变量。
28
28
  - [开发说明](docs/development.md):源码运行、测试、构建、打包和全局 smoke 验证。
29
29
  - [发布说明](docs/releases/README.md):各版本变更与升级说明。
30
- - [通信架构](docs/architecture.md):三种 CLI 适配器的通信机制、PTY 依赖分析和技术决策。
30
+ - [通信架构](docs/architecture.md):各 CLI 适配器的通信机制、PTY / RPC 依赖分析和技术决策。
31
31
 
32
32
  ## 这个项目解决什么问题?
33
33
 
34
34
  本项目适合这样的使用场景:
35
35
 
36
36
  - 你的主工作流仍在本地终端中进行;
37
- - 你希望继续使用 Codex、Claude Code、OpenCode 等原生 CLI,而不是迁移到网页或托管机器人;
37
+ - 你希望继续使用 Codex、Claude Code、OpenCode、Pi 等原生 CLI,而不是迁移到网页或托管机器人;
38
38
  - 你希望离开电脑后,仍能通过微信向本地会话发送请求,并接收必要输出和状态更新。
39
39
 
40
40
  本项目不试图把微信变成新的主工作界面。它的定位是:
@@ -51,7 +51,8 @@
51
51
  - 已安装以下任意一种本地 CLI,并尽量保持最新版本:
52
52
  - [Codex](https://github.com/openai/codex)
53
53
  - [Claude Code](https://code.claude.com/docs/en/overview)
54
- - [OpenCode](https://github.com/anomalyco/opencode)
54
+ - [OpenCode](https://github.com/anomalyco/opencode) `>= 1.18.0 < 2.0.0`
55
+ - [Pi](https://github.com/earendil-works/pi)(已验证 `0.84.2`;需要本机可执行 `pi` 命令)
55
56
 
56
57
  ### 2. 安装
57
58
 
@@ -61,14 +62,14 @@
61
62
  npm install -g cli-wechat-bridge@latest
62
63
  ```
63
64
 
64
- 安装后,可以在任意项目目录中使用 `wechat-daemon`、`wechat-codex-start`、`wechat-claude-start`、`wechat-opencode-start` 等命令。
65
+ 安装后,可以在任意项目目录中直接运行 `wechat-codex`、`wechat-claude`、`wechat-opencode`、`wechat-pi`,或使用 `wechat-daemon` 保持长期连接。
65
66
 
66
67
  兼容性说明:旧包名 `@unlinearity/cli-wechat-bridge` 会继续同步发布,已经安装旧包名的用户可以正常升级;新用户优先使用更短的 `cli-wechat-bridge`。
67
68
 
68
69
  <details>
69
70
  <summary><b>安装遇到问题?(node-pty 原生模块)</b></summary>
70
71
 
71
- 本项目使用 `node-pty` 为 CLI 适配器提供完整终端模拟。**Claude Code 适配器当前通过 PTY 交互模式工作**,node-pty 不可用时会回退到兼容模式,但 Claude Code 在此模式下可能无法正常桥接;Codex 适配器主要通过 WebSocket RPC 通信,通常不受影响;OpenCode 适配器完全不依赖 node-pty。
72
+ 本项目使用 `node-pty` 为 CLI 适配器提供完整终端模拟。**Claude Code 适配器当前通过 PTY 交互模式工作**,node-pty 不可用时会回退到兼容模式,但 Claude Code 在此模式下可能无法正常桥接;Codex 适配器主要通过 WebSocket RPC 通信,通常不受影响;OpenCode 适配器不依赖 node-pty;Pi 直接继承可见 companion 的真实终端,也不需要 node-pty 模拟。
72
73
 
73
74
  **Linux 用户**(最常见):需要原生模块编译工具:
74
75
 
@@ -113,15 +114,15 @@ wechat-setup
113
114
 
114
115
  登录成功后,程序会清理旧的同步游标和上下文 token,避免旧会话状态污染新的登录状态。数据目录、状态文件和旧版本迁移说明见 [问题排查](docs/troubleshooting.md#数据目录与状态文件)。
115
116
 
116
- 首次安装或微信登录过期时,`wechat-codex-start`、`wechat-claude-start`、`wechat-opencode-start` 也会在前台提示扫码登录。
117
+ 首次安装或微信登录过期时,四个直接启动命令也会在前台提示扫码登录。
117
118
 
118
119
  ### 4. 先从微信发一条同步消息(重要)
119
120
 
120
121
  启动 bridge 后,建议先在微信里向 Bot 发送一条消息,例如 `hello`、你要执行的任务,或任意一句话。这样 bridge 能拿到最新的微信会话 `context_token`,之后本地终端中的输入、最终回复和审批提示才能稳定同步回微信。
121
122
 
122
- 如果冷启动或长时间闲置后直接从本地终端先发消息,bridge 通常仍会捕获这条本地输入并交给 Codex / Claude Code / OpenCode 处理,但回发到微信时可能因为旧的 `context_token` 失效而失败。表现是:本地已经有回复,微信暂时收不到;等你先从微信发来一条消息后,后续双向同步就能恢复正常。
123
+ 如果冷启动或长时间闲置后直接从本地终端先发消息,bridge 通常仍会捕获这条本地输入并交给 Codex / Claude Code / OpenCode / Pi 处理,但回发到微信时可能因为旧的 `context_token` 失效而失败。表现是:本地已经有回复,微信暂时收不到;等你先从微信发来一条消息后,后续双向同步就能恢复正常。
123
124
 
124
- ### 5. 单命令启动桥接(兼容模式)
125
+ ### 5. 直接启动本地 CLI
125
126
 
126
127
  先进入需要操作的项目目录:
127
128
 
@@ -133,30 +134,34 @@ cd D:\work\your-project
133
134
 
134
135
  | 使用的本地 CLI | 启动命令 |
135
136
  | --- | --- |
136
- | Codex | `wechat-codex-start` |
137
- | Claude Code | `wechat-claude-start` |
138
- | OpenCode | `wechat-opencode-start` |
137
+ | Codex | `wechat-codex` |
138
+ | Claude Code | `wechat-claude` |
139
+ | OpenCode | `wechat-opencode` |
140
+ | Pi | `wechat-pi` |
139
141
 
140
- 这些启动器会自动完成以下动作:
142
+ 这些命令会自动完成以下动作:
141
143
 
142
144
  1. 校验或刷新微信登录凭据;
143
- 2. 如果当前目录已有 `wechat-daemon`,则委托 daemon 切换到对应 CLI;
144
- 3. 如果没有 daemon,则复用当前目录中已运行的 bridge,或在当前目录启动新的 bridge;
145
- 4. 如果已有独立 bridge 正在服务其他目录,则停止旧 bridge 并切换到当前目录;
146
- 5. 等待当前目录对应的本地 companion endpoint 就绪;
147
- 6. 打开可见的本地 CLI 会话。
148
-
149
- 没有 daemon 时,`wechat-codex-start` / `wechat-claude-start` / `wechat-opencode-start` 仍按**单活工作区切换器**工作:
150
-
151
- - 同一时间只有一个项目与微信对话;
152
- - 在当前目录重复执行是幂等的;
153
- - 如果当前目录已经有可见 companion / panel 在运行,则不会重复打开第二个窗口;
145
+ 2. 如果当前目录已有 `wechat-daemon`,则委托 daemon 切换到对应 CLI;
146
+ 3. 如果没有 daemon,则创建或复用内部 transient bridge runtime;
147
+ 4. 如果旧 runtime 正在服务其他目录,则安全停止旧 runtime 并切换到当前目录;
148
+ 5. 等待当前目录对应的本地 endpoint 就绪;
149
+ 6. 打开可见的本地 CLI 会话。
150
+
151
+ 没有 daemon 时,四个直接命令按**单活工作区切换器**工作:
152
+
153
+ - 同一时间只有一个项目与微信对话;
154
+ - Codex 在当前目录重复执行时会复用已有会话;Claude Code、OpenCode 和 Pi 启动器默认新建会话;
155
+ - Pi 如需显式恢复上一次会话,可使用 `wechat-pi --session-start-mode restore`;
156
+ - 如果当前目录已经有可见 companion / panel 在运行,则不会重复打开第二个窗口;
154
157
  - 如果检测到可见端仍在运行但 worker 状态异常(如 `stopped` / `error`),会自动重启 bridge 再重新打开可见端;
155
- - 在其他目录执行会显式切换活动工作区。
158
+ - 在其他目录执行会显式切换活动工作区。
159
+
160
+ `wechat-codex-start`、`wechat-claude-start`、`wechat-opencode-start`、`wechat-pi-start` 暂时保留为过渡别名,功能与直接命令一致,但会输出弃用提示,并将在下一版本移除。
156
161
 
157
162
  ### 6. 常驻 daemon 模式(支持多 CLI 切换)
158
163
 
159
- 如果你希望微信连接长期保持在线,并在 Codex / Claude Code / OpenCode 之间来回切换,可以在项目目录启动统一 daemon:
164
+ 如果你希望微信连接长期保持在线,并在 Codex / Claude Code / OpenCode / Pi 之间来回切换,可以在项目目录启动统一 daemon:
160
165
 
161
166
  ```bash
162
167
  cd D:\work\your-project
@@ -167,11 +172,12 @@ wechat-daemon
167
172
 
168
173
  | 指令 | 行为 |
169
174
  | --- | --- |
170
- | `/codex` | 切换到 Codex |
171
- | `/claude` | 切换到 Claude Code |
172
- | `/opencode` | 切换到 OpenCode |
175
+ | `/codex [prompt]` | 切换到 Codex;携带 prompt 时切换后立即转发剩余文本 |
176
+ | `/claude [prompt]` | 切换到 Claude Code;携带 prompt 时切换后立即转发剩余文本 |
177
+ | `/opencode [prompt]` | 切换到 OpenCode;携带 prompt 时切换后立即转发剩余文本 |
178
+ | `/pi [prompt]` | 切换到 Pi;携带 prompt 时切换后立即转发剩余文本 |
173
179
 
174
- daemon 启动后,后续切换都可以直接从微信发起;如果对应 CLI 还没有可见窗口,daemon 会自动打开或复用它,不需要再手动运行 `wechat-codex`、`wechat-claude` 或 `wechat-opencode`。
180
+ daemon 启动后,后续切换都可以直接从微信发起;如果对应 CLI 还没有可见窗口,daemon 会自动打开或复用它,不需要再手动运行 `wechat-codex`、`wechat-claude`、`wechat-opencode` 或 `wechat-pi`。
175
181
 
176
182
  ![多CLI 示例](docs/images/image-9.png)
177
183
 
@@ -179,10 +185,11 @@ daemon 启动后,后续切换都可以直接从微信发起;如果对应 CLI
179
185
 
180
186
  - daemon 绑定启动时的工作目录;暂不支持在微信里切换工作目录;
181
187
  - 启动时会自动接管并清理旧的单 bridge 进程、失效 lock 和旧 endpoint;
182
- - 切换适配器不会关闭之前的 CLI;
183
- - 如果对应适配器已经有可见 CLI 在运行,则直接复用;
184
- - 如果还没有对应 CLI,daemon 会自动打开一个新的可见终端;
185
- - Codex / Claude / OpenCode 的重要输出都会带上 `[codex]`、`[claude]`、`[opencode]` 标签再发回微信;
188
+ - 切换适配器不会关闭之前的 CLI;
189
+ - 如果对应适配器已经有可见 CLI 在运行,则直接复用;
190
+ - 如果还没有对应 CLI,daemon 会自动打开一个新的可见终端;
191
+ - 如果当前活动 CLI 的本地窗口被关闭,下一条普通微信输入会自动重开可见终端;Codex 会重置旧 runtime 的残留 busy/turn 状态并建立新的可见 thread;
192
+ - Codex / Claude / OpenCode / Pi 的重要输出都会带上 `[codex]`、`[claude]`、`[opencode]`、`[pi]` 标签再发回微信;
186
193
  - 可以在微信里发送 `/daemon-stop` 停止 daemon。
187
194
 
188
195
  也可以在启动时指定初始 CLI:
@@ -192,17 +199,9 @@ wechat-daemon --adapter codex
192
199
  wechat-daemon --adapter claude --profile work
193
200
  ```
194
201
 
195
- 当同一工作目录已有 `wechat-daemon` 在运行时,`wechat-codex-start` / `wechat-claude-start` / `wechat-opencode-start` 会自动委托给 daemon:请求 daemon 切到对应 CLI,并在需要时打开可见终端,不会停止 daemon 或关闭其他 CLI。
196
-
197
- ### 7. 手动双终端模式(高级调试)
198
-
199
- 如果你希望明确观察 bridge 与本地 CLI companion,也可以用两个终端分别启动。
200
-
201
- | 适配器 | 终端 A:bridge | 终端 B:本地 companion |
202
- | --- | --- | --- |
203
- | Codex | `wechat-bridge-codex` | `wechat-codex` |
204
- | Claude Code | `wechat-bridge-claude` | `wechat-claude` |
205
- | OpenCode | `wechat-bridge-opencode` | `wechat-opencode` |
202
+ 当同一工作目录已有 `wechat-daemon` 在运行时,四个直接启动命令会自动委托给 daemon:请求 daemon 切到对应 CLI,并在需要时打开可见终端,不会停止 daemon 或关闭其他 CLI。
203
+
204
+ 其中 `wechat-pi` 默认表示启动一个新的 Pi session;如果 Pi TUI 已经可见,daemon 会在现有窗口中创建新 session。微信中的 `/pi` 仅用于切换适配器,仍会复用已经连接的 Pi TUI。
206
205
 
207
206
  ## 适配器支持情况
208
207
 
@@ -212,15 +211,18 @@ wechat-daemon --adapter claude --profile work
212
211
 
213
212
  微信发来的图片和普通文件也会被接收并保存到本地:
214
213
 
215
- - bridge 会将本地路径追加到转发给 Codex / Claude Code / OpenCode 的 prompt 中,模型可按需读取或解析这些文件;
214
+ - bridge 会将本地路径追加到转发给 Codex / Claude Code / OpenCode / Pi 的 prompt 中,模型可按需读取或解析这些文件;
216
215
  - 当前不会自动 OCR 图片,也不会自动抽取 PDF / DOCX 正文;如需解析,由本地 CLI 根据路径完成。
217
216
  - 具体保存位置见 [问题排查](docs/troubleshooting.md#数据目录与状态文件)。
218
217
 
219
- | 适配器 | 当前状态 | 说明 |
220
- | --- | --- | --- |
221
- | `codex` | 已接入 | 双终端模式;本地 companion 作为线程权威;微信跟随本地线程 |
222
- | `claude` | 已接入 | `wechat-bridge-claude` + `wechat-claude` 的双终端 companion 模式;会话切换、最终回复与审批元数据按 Claude session 语义同步 |
223
- | `opencode` | 已接入 | [`OpenCode`](https://github.com/anomalyco/opencode) 适配器;`wechat-bridge-opencode` + `wechat-opencode` 的双终端 companion 模式;支持本地 session 切换跟随,微信侧支持 `/new` / `/new-session` |
218
+ | 适配器 | 当前状态 | 说明 |
219
+ | --- | --- | --- |
220
+ | `codex` | 已接入 | `wechat-codex` 自动确保内部 runtime 并打开可见 Codex;微信跟随本地 thread |
221
+ | `claude` | 已接入 | `wechat-claude` 自动启动或复用 Claude Code;会话、最终回复与审批按 Claude session 语义同步 |
222
+ | `opencode` | 已接入 | `wechat-opencode` 自动启动或复用 OpenCode;支持本地 session 跟随及微信 `/new` / `/new-session` |
223
+ | `pi` | 已接入 | `wechat-pi` 启动用户原生 Pi TUI,并通过本地 extension 让微信接管同一 session,支持最终回复、停止、新建和恢复 session |
224
+
225
+ Pi 按全权限本地代理运行:bridge 不增加工具审批层,并传入 `--approve` 信任当前项目;读写文件和执行命令均使用启动 `wechat-pi` 的本地用户权限。原生 TUI 的主题、快捷键、模型选择和 extension UI 都会保留。`wechat-pi` 本身就是被微信接管的 Pi TUI;不要再启动第二个 Pi 进程同时写入同一个 session 文件。
224
226
 
225
227
  ### Codex 示例
226
228
 
@@ -238,109 +240,86 @@ wechat-daemon --adapter claude --profile work
238
240
 
239
241
  OpenCode 模式下,微信侧支持 `/new` 或 `/new-session` 创建新 session;如果在本地 OpenCode CLI 中创建新 session,微信消息也会跟随新的 session。
240
242
 
241
- ## 命令说明
242
-
243
- ### 常用全局命令
244
-
245
- | 类型 | 命令 |
246
- | --- | --- |
247
- | 登录与更新 | `wechat-setup`、`wechat-check-update` |
248
- | 常驻 daemon | `wechat-daemon` |
249
- | Codex | `wechat-bridge-codex`、`wechat-codex`、`wechat-codex-start` |
250
- | Claude Code | `wechat-bridge-claude`、`wechat-claude`、`wechat-claude-start` |
251
- | OpenCode | `wechat-bridge-opencode`、`wechat-opencode`、`wechat-opencode-start` |
252
-
253
- ### Daemon CLI 参数
254
-
255
- 适用于:
256
-
257
- - `wechat-daemon`
258
-
259
- 示例:
260
-
261
- ```bash
262
- wechat-daemon --cwd D:\work\my-project
263
- wechat-daemon --adapter codex
264
- wechat-daemon --adapter claude --profile work
265
- ```
266
-
267
- 支持参数:
268
-
269
- - `--cwd <path>`:指定 daemon 绑定的工作目录;
270
- - `--adapter <codex|claude|opencode>`:启动 daemon 后立即切换到指定 CLI;
271
- - `--profile <name-or-path>`:传给 daemon 创建的对应适配器;
272
- - `--no-open`:只创建 bridge slot,不自动打开可见 CLI。
273
-
274
- ### Bridge CLI 参数
275
-
276
- 适用于:
277
-
278
- - `wechat-bridge`
279
- - `wechat-bridge-codex`
280
- - `wechat-bridge-claude`
281
- - `wechat-bridge-opencode`
282
-
283
- 示例:
284
-
285
- ```bash
286
- wechat-bridge --adapter codex --cwd D:\work\my-project
287
- wechat-bridge-codex --cwd D:\work\my-project
288
- wechat-bridge-claude --profile work
289
- wechat-bridge-opencode --cwd D:\work\my-project
290
- wechat-bridge-codex --lifecycle companion_bound
291
- ```
292
-
293
- 支持参数:
294
-
295
- - `--adapter <codex|claude|opencode>`:通用入口 `wechat-bridge` 需要显式指定适配器;
296
- - `--cwd <path>`:指定工作目录;
297
- - `--profile <name-or-path>`:向适配器传入 profile;
298
- - `--lifecycle <persistent|companion_bound>`:指定 bridge 生命周期;`wechat-*-start` 会使用 `companion_bound`。
299
-
300
- ### `wechat-*-start` 参数
301
-
302
- 示例:
303
-
304
- ```bash
305
- wechat-codex-start --cwd D:\work\my-project
306
- wechat-claude-start --profile work
307
- wechat-opencode-start --cwd D:\work\my-project
308
- ```
309
-
310
- 支持参数:
311
-
312
- - `--cwd <path>`:显式指定 bridge / companion 对应的工作目录;
313
- - `--profile <name-or-path>`:转发给后台启动的 `wechat-bridge-codex` / `wechat-bridge-claude` / `wechat-bridge-opencode`;
314
- - `--timeout-ms <ms>`:等待当前目录 endpoint 的最长时间,默认 `15000`。
315
-
316
- 高级用法:除上述启动器参数外,未知参数会继续透传给可见的底层 CLI。这样既能保留微信登录、工作区切换与 bridge 生命周期管理,也能启用 Codex / Claude Code 自己的高级启动模式。
317
-
318
- ```bash
319
- wechat-codex-start --yolo
320
- wechat-codex-start --model gpt-5.2 --yolo
321
- wechat-claude-start --dangerously-skip-permissions
322
- wechat-claude-start --model sonnet --dangerously-skip-permissions
323
- ```
324
-
325
- 其中,`--yolo` 会传给 Codex;`--dangerously-skip-permissions` 会传给 Claude Code。它们只影响本地可见 CLI,不会覆盖 bridge 托管的 Codex remote 连接参数、Claude settings、微信登录状态或当前工作区锁。由于这两类参数会降低审批保护,建议只在可信工作区中使用。
326
-
243
+ ## 命令说明
244
+
245
+ ### 常用全局命令
246
+
247
+ | 类型 | 命令 |
248
+ | --- | --- |
249
+ | 登录与更新 | `wechat-setup`、`wechat-check-update` |
250
+ | 常驻 daemon | `wechat-daemon` |
251
+ | Codex | `wechat-codex` |
252
+ | Claude Code | `wechat-claude` |
253
+ | OpenCode | `wechat-opencode` |
254
+ | Pi | `wechat-pi` |
255
+
256
+ `wechat-*-start` 暂时作为弃用别名保留一个版本。公开的 `wechat-bridge*` 命令和 Shell adapter 已移除;内部 bridge runtime 仍由直接命令和 daemon 自动管理。
257
+
258
+ ### Daemon CLI 参数
259
+
260
+ 示例:
261
+
262
+ ```bash
263
+ wechat-daemon --cwd D:\work\my-project
264
+ wechat-daemon --adapter codex
265
+ wechat-daemon --adapter claude --profile work
266
+ ```
267
+
268
+ 支持参数:
269
+
270
+ - `--cwd <path>`:指定 daemon 绑定的工作目录;
271
+ - `--adapter <codex|claude|opencode|pi>`:启动 daemon 后立即切换到指定 CLI;
272
+ - `--profile <name-or-path>`:传给 daemon 创建的对应适配器;
273
+ - `--no-open`:只创建 runtime slot,不自动打开可见 CLI;
274
+ - `--doctor`:检查环境、CLI、锁、endpoint 和 daemon 状态。
275
+
276
+ ### 直接启动命令参数
277
+
278
+ 适用于 `wechat-codex`、`wechat-claude`、`wechat-opencode`、`wechat-pi`。
279
+
280
+ ```bash
281
+ wechat-codex --cwd D:\work\my-project
282
+ wechat-claude --profile work
283
+ wechat-opencode --cwd D:\work\my-project
284
+ wechat-pi --cwd D:\work\my-project --model openai/gpt-5.6-sol
285
+ ```
286
+
287
+ 支持参数:
288
+
289
+ - `--cwd <path>`:指定 runtime 与可见 CLI 对应的工作目录;
290
+ - `--profile <name-or-path>`:传给内部 runtime;
291
+ - `--timeout-ms <ms>`:等待当前目录 endpoint 的最长时间,默认 `15000`;
292
+ - `--session-start-mode <restore|new>`:显式选择恢复或新建会话;
293
+ - 其他未知参数会继续透传给可见的底层 CLI。
294
+
295
+ 默认会话策略:Codex 恢复当前会话;Claude Code、OpenCode 和 Pi 新建会话。
296
+
297
+ ```bash
298
+ wechat-codex --yolo
299
+ wechat-codex --model gpt-5.2 --yolo
300
+ wechat-claude --dangerously-skip-permissions
301
+ wechat-claude --model sonnet --dangerously-skip-permissions
302
+ ```
303
+
304
+ 其中,`--yolo` 会传给 Codex;`--dangerously-skip-permissions` 会传给 Claude Code。它们只影响本地可见 CLI,不会覆盖内部 runtime 的连接参数、微信登录状态或工作区锁。由于这两类参数会降低审批保护,建议只在可信工作区中使用。
305
+
327
306
  ## 微信侧支持的指令
328
307
 
329
308
  | 指令 | 说明 |
330
309
  | --- | --- |
331
310
  | 普通文本 | 发送到当前活动会话 |
332
- | `/codex` / `/claude` / `/opencode` | daemon 模式下切换活动 CLI;已有 CLI 会复用,没有则自动打开 |
311
+ | `/codex [prompt]` / `/claude [prompt]` / `/opencode [prompt]` / `/pi [prompt]` | daemon 模式下切换活动 CLI;已有 CLI 会复用,没有则自动打开;可选 prompt 会在切换成功后立即转发 |
333
312
  | `/status` | 查看 bridge 当前状态 |
334
313
  | `/stop` | 中断当前任务 |
335
314
  | `/reset` | 重建当前本地会话 |
336
- | `/new` 或 `/new-session` | OpenCode 模式下新建 session |
315
+ | `/new` 或 `/new-session` | OpenCode 或 Pi 模式下新建 session |
337
316
  | `/confirm` / `/deny` | 处理 CLI 权限请求;需要一次性 code 的请求会在消息中提示具体确认格式 |
338
317
  | `/daemon-stop` | daemon 模式下停止常驻进程 |
339
318
  | `/bindings` | 查看当前所有表情绑定 |
340
319
  | `/bind [表情] /命令` | 绑定表情到指定命令 |
341
320
  | `/unbind [表情]` | 解除指定表情的绑定 |
342
321
 
343
- 说明:微信侧 `/resume` 目前暂时保持禁用;需要切换 Codex / Claude / OpenCode 会话时,优先在本地 companion 中使用原生 `/resume`、`/new` 或对应 CLI 命令,微信会跟随本地活动会话。
322
+ 说明:微信侧 `/resume` 目前暂时保持禁用;需要切换 Codex / Claude / OpenCode / Pi 会话时,优先在本地 companion 中使用 `/new` 或对应 CLI 的会话命令,微信会跟随本地活动会话。
344
323
 
345
324
  ### 表情绑定
346
325
 
@@ -376,18 +355,18 @@ Daemon 模式支持将微信表情映射为命令,在微信中发送表情即
376
355
 
377
356
  ## 工作区模型
378
357
 
379
- 本项目采用“当前目录即当前工作区”的模型:
380
-
381
- - 从哪个目录启动 `wechat-bridge-codex` / `wechat-bridge-claude` / `wechat-bridge-opencode`,哪个目录就是当前工作区;
382
- - 对应的本地 companion 必须连接同一工作区;
383
- - 不同工作区的运行状态相互隔离。
358
+ 本项目采用“当前目录即当前工作区”的模型:
359
+
360
+ - 从哪个目录启动 `wechat-codex` / `wechat-claude` / `wechat-opencode` / `wechat-pi`,哪个目录就是当前工作区;
361
+ - 直接命令会自动管理同一工作区的内部 runtime 与可见 CLI;
362
+ - 不同工作区的运行状态相互隔离。
384
363
 
385
364
  目前支持两种运行模型:
386
365
 
387
- - 独立 bridge:单 owner、单 bridge、单活动工作区;
388
- - `wechat-daemon`:单 owner、单 daemon、绑定一个启动工作区,但可在这个工作区内同时保留 Codex / Claude Code / OpenCode 三个 CLI slot。
389
-
390
- 对 `wechat-codex-start` / `wechat-claude-start` / `wechat-opencode-start` 来说,这意味着:
366
+ - 直接启动模式:单 owner、内部 transient runtime、单活动工作区;
367
+ - `wechat-daemon`:单 owner、单 daemon、绑定一个启动工作区,但可在这个工作区内同时保留 Codex / Claude Code / OpenCode / Pi 四个 CLI slot。
368
+
369
+ 对四个直接启动命令来说,这意味着:
391
370
 
392
371
  - 如果同一工作区已有 daemon,它们会委托 daemon 切换 CLI;
393
372
  - 如果没有 daemon,它们会作为**单活工作区切换器**工作;
@@ -410,7 +389,7 @@ wechat-check-update
410
389
  - 远程仓库的最新版本;
411
390
  - 如果有更新,会提供详细的更新指引。
412
391
 
413
- 当启动 `wechat-bridge` 相关命令时(如 `wechat-bridge-codex`、`wechat-bridge-claude`),程序也会自动检查更新(每 24 小时一次)。自动检查在后台异步执行,不影响启动速度。
392
+ 没有 daemon 时,四个直接命令启动的内部 runtime 会按现有缓存策略检查更新。自动检查在后台异步执行,不影响启动速度。
414
393
 
415
394
  ### 获取最新版本
416
395
 
@@ -57,3 +57,15 @@ export function runJsEntry(relativeEntryPath, extraArgs = []) {
57
57
  process.exit(code ?? 0);
58
58
  });
59
59
  }
60
+
61
+ export function runDeprecatedJsEntry(
62
+ commandName,
63
+ replacementCommand,
64
+ relativeEntryPath,
65
+ extraArgs = [],
66
+ ) {
67
+ process.stderr.write(
68
+ `[cli-wechat-bridge] ${commandName} is deprecated; use ${replacementCommand}. This alias will be removed in the next release.\n`,
69
+ );
70
+ runJsEntry(relativeEntryPath, extraArgs);
71
+ }
@@ -1,5 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { runJsEntry } from "./_run-entry.mjs";
3
+ import { runDeprecatedJsEntry } from "./_run-entry.mjs";
4
4
 
5
- runJsEntry("dist/companion/local-companion-start.js", ["--adapter", "claude"]);
5
+ runDeprecatedJsEntry(
6
+ "wechat-claude-start",
7
+ "wechat-claude",
8
+ "dist/companion/local-companion-start.js",
9
+ ["--adapter", "claude"],
10
+ );
@@ -2,4 +2,4 @@
2
2
 
3
3
  import { runJsEntry } from "./_run-entry.mjs";
4
4
 
5
- runJsEntry("dist/companion/local-companion.js", ["--adapter", "claude"]);
5
+ runJsEntry("dist/companion/local-companion-start.js", ["--adapter", "claude"]);
@@ -1,5 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { runJsEntry } from "./_run-entry.mjs";
3
+ import { runDeprecatedJsEntry } from "./_run-entry.mjs";
4
4
 
5
- runJsEntry("dist/companion/local-companion-start.js");
5
+ runDeprecatedJsEntry(
6
+ "wechat-codex-start",
7
+ "wechat-codex",
8
+ "dist/companion/local-companion-start.js",
9
+ ["--adapter", "codex"],
10
+ );
@@ -2,4 +2,4 @@
2
2
 
3
3
  import { runJsEntry } from "./_run-entry.mjs";
4
4
 
5
- runJsEntry("dist/companion/codex-remote-client.js");
5
+ runJsEntry("dist/companion/local-companion-start.js", ["--adapter", "codex"]);
@@ -1,5 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { runJsEntry } from "./_run-entry.mjs";
3
+ import { runDeprecatedJsEntry } from "./_run-entry.mjs";
4
4
 
5
- runJsEntry("dist/companion/local-companion-start.js", ["--adapter", "opencode"]);
5
+ runDeprecatedJsEntry(
6
+ "wechat-opencode-start",
7
+ "wechat-opencode",
8
+ "dist/companion/local-companion-start.js",
9
+ ["--adapter", "opencode"],
10
+ );
@@ -2,4 +2,4 @@
2
2
 
3
3
  import { runJsEntry } from "./_run-entry.mjs";
4
4
 
5
- runJsEntry("dist/companion/local-companion.js", ["--adapter", "opencode"]);
5
+ runJsEntry("dist/companion/local-companion-start.js", ["--adapter", "opencode"]);
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { runDeprecatedJsEntry } from "./_run-entry.mjs";
4
+
5
+ runDeprecatedJsEntry(
6
+ "wechat-pi-start",
7
+ "wechat-pi",
8
+ "dist/companion/local-companion-start.js",
9
+ ["--adapter", "pi"],
10
+ );
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { runJsEntry } from "./_run-entry.mjs";
4
+
5
+ runJsEntry("dist/companion/local-companion-start.js", ["--adapter", "pi"]);
@@ -61,6 +61,8 @@ export function getLocalCompanionCommandName(kind) {
61
61
  return "wechat-claude";
62
62
  case "opencode":
63
63
  return "wechat-opencode";
64
+ case "pi":
65
+ return "wechat-pi";
64
66
  default:
65
67
  return "local companion";
66
68
  }
@@ -208,6 +208,21 @@ export class ClaudeCompanionAdapter extends AbstractPtyAdapter {
208
208
  this.clearWechatWorkingNotice(true);
209
209
  this.pendingCliApprovalHints = null;
210
210
  this.flushPendingClaudeHookApprovals();
211
+ if (this.pendingApproval) {
212
+ // The flush above answered the pending hook approval with an empty
213
+ // response, so the mirrored request is dead. Drop it — otherwise a
214
+ // later /confirm targets a deleted requestId and every new message is
215
+ // rejected by a stale "approval pending" state. Mirrors what the
216
+ // socket-close path does via handleClosedClaudeHookApproval.
217
+ this.pendingApproval = null;
218
+ this.state.pendingApproval = null;
219
+ this.state.pendingApprovalOrigin = undefined;
220
+ }
221
+ if (this.state.status === "awaiting_approval") {
222
+ // Return to busy so the interrupt settle path (guarded on busy) can
223
+ // complete the turn normally.
224
+ this.setStatus("busy");
225
+ }
211
226
  this.writeToPty("\u0003");
212
227
  this.scheduleTaskComplete(shared.INTERRUPT_SETTLE_DELAY_MS);
213
228
  return true;
@@ -480,7 +495,12 @@ export class ClaudeCompanionAdapter extends AbstractPtyAdapter {
480
495
  throw new Error("Claude hook server is not ready.");
481
496
  }
482
497
  const { workspaceDir } = ensureWorkspaceChannelDir(this.options.cwd);
483
- const runtimeDir = path.join(workspaceDir, "claude-runtime");
498
+ // Scope the runtime directory to this adapter instance: two bridges in the
499
+ // same cwd would otherwise overwrite each other's hook script, settings,
500
+ // and error log, routing approvals and final replies to the wrong chat.
501
+ // The hook token is unique per hook server (per instance), and the same
502
+ // token regenerates the same directory across hook restarts.
503
+ const runtimeDir = path.join(workspaceDir, `claude-runtime-${this.hookToken.replace(/[^a-zA-Z0-9]/g, "").slice(0, 12)}`);
484
504
  fs.mkdirSync(runtimeDir, { recursive: true });
485
505
  const hookScriptPath = path.join(runtimeDir, process.platform === "win32" ? "hook.cmd" : "hook.sh");
486
506
  const settingsFilePath = path.join(runtimeDir, "settings.json");