cli-wechat-bridge 1.1.8 → 1.2.1

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 (80) hide show
  1. package/README.md +21 -7
  2. package/bin/_run-entry.mjs +6 -1
  3. package/bin/wechat-check-update.mjs +6 -1
  4. package/bin/wechat-claude.mjs +6 -1
  5. package/bin/wechat-codex.mjs +6 -1
  6. package/bin/wechat-daemon.mjs +6 -1
  7. package/bin/wechat-opencode.mjs +6 -1
  8. package/bin/wechat-pi.mjs +6 -1
  9. package/bin/wechat-setup.mjs +6 -1
  10. package/bin/wecom-check-update.mjs +6 -1
  11. package/bin/wecom-claude.mjs +6 -1
  12. package/bin/wecom-codex.mjs +6 -1
  13. package/bin/wecom-daemon.mjs +6 -1
  14. package/bin/wecom-opencode.mjs +6 -1
  15. package/bin/wecom-pi.mjs +6 -1
  16. package/bin/wecom-setup.mjs +6 -1
  17. package/dist/bridge/bridge-adapters.claude.js +6 -0
  18. package/dist/bridge/bridge-adapters.codex.js +95 -8
  19. package/dist/bridge/bridge-adapters.core.js +6 -0
  20. package/dist/bridge/bridge-adapters.opencode.js +148 -19
  21. package/dist/bridge/bridge-adapters.pi.js +6 -0
  22. package/dist/bridge/bridge-adapters.shared.js +6 -0
  23. package/dist/bridge/bridge-controller.js +6 -0
  24. package/dist/bridge/bridge-final-reply.js +6 -0
  25. package/dist/bridge/bridge-process-reaper.js +6 -0
  26. package/dist/bridge/bridge-state.js +6 -0
  27. package/dist/bridge/bridge-utils.js +6 -31
  28. package/dist/bridge/claude-hook.js +6 -0
  29. package/dist/bridge/claude-native-controls.js +14 -2
  30. package/dist/bridge/codex-visible-thread-proxy.js +140 -0
  31. package/dist/bridge/native-terminal-control.js +18 -0
  32. package/dist/bridge/opencode-v2-compat.js +450 -0
  33. package/dist/bridge/wechat-bridge.js +148 -241
  34. package/dist/bridge/wechat-forwarding.js +6 -0
  35. package/dist/bridge/wechat-outbound-queue.js +6 -0
  36. package/dist/channels/wechat/bridge-final-reply.js +13 -22
  37. package/dist/channels/wechat/index.js +6 -0
  38. package/dist/channels/wechat/wechat-driver.js +191 -0
  39. package/dist/channels/wechat/wechat-forwarding.js +18 -0
  40. package/dist/channels/wechat/wechat-outbound-queue.js +39 -0
  41. package/dist/channels/wecom/bridge-final-reply.js +6 -0
  42. package/dist/channels/wecom/setup-cli.js +6 -0
  43. package/dist/channels/wecom/setup.js +6 -0
  44. package/dist/channels/wecom/wecom-config.js +6 -0
  45. package/dist/channels/wecom/wecom-driver.js +106 -0
  46. package/dist/channels/wecom/wecom-media.js +6 -0
  47. package/dist/channels/wecom/wecom-transport.js +6 -0
  48. package/dist/commands/check-update.js +6 -0
  49. package/dist/companion/codex-remote-client.js +6 -0
  50. package/dist/companion/codex-visible-client-link.js +6 -0
  51. package/dist/companion/local-companion-link.js +6 -0
  52. package/dist/companion/local-companion-start.js +6 -0
  53. package/dist/companion/local-companion.js +6 -0
  54. package/dist/companion/opencode-tui-bridge-plugin.js +6 -0
  55. package/dist/companion/opencode-v2-tui-bridge-plugin.js +93 -0
  56. package/dist/companion/pi-tui-bridge-extension.js +6 -0
  57. package/dist/core/bridge-control.js +2 -0
  58. package/dist/core/bridge-defer.js +25 -0
  59. package/dist/core/channel-driver.js +1 -0
  60. package/dist/core/conversation-routing.js +11 -1
  61. package/dist/core/direct-run.js +6 -0
  62. package/dist/core/inbound-message-claims.js +6 -0
  63. package/dist/core/outbound-target.js +32 -0
  64. package/dist/core/text-utils.js +6 -0
  65. package/dist/core/turn-coordinator.js +85 -0
  66. package/dist/daemon/daemon-link.js +6 -0
  67. package/dist/daemon/emoji-bindings.js +6 -0
  68. package/dist/daemon/wechat-daemon.js +451 -173
  69. package/dist/i18n/index.js +6 -0
  70. package/dist/i18n/messages-en.js +7 -1
  71. package/dist/i18n/messages-zh.js +7 -1
  72. package/dist/runtime/create-runtime-host.js +6 -0
  73. package/dist/utils/atomic-file.js +6 -0
  74. package/dist/utils/doctor.js +6 -0
  75. package/dist/utils/version-checker.js +6 -0
  76. package/dist/wechat/channel-config.js +6 -0
  77. package/dist/wechat/setup.js +6 -0
  78. package/dist/wechat/wechat-channel.js +6 -0
  79. package/dist/wechat/wechat-transport.js +73 -0
  80. package/package.json +5 -2
package/README.md CHANGED
@@ -53,10 +53,11 @@
53
53
 
54
54
  - [Node.js](https://nodejs.org/en/download) `>= 22.13.0`(建议直接安装官网 LTS 版本)
55
55
  - 已安装以下任意一种本地 CLI,并尽量保持最新版本:
56
- - [Codex](https://github.com/openai/codex)
56
+ - [Codex](https://github.com/openai/codex)(已验证 `0.149.x`–`0.157.x`)
57
57
  - [Claude Code](https://code.claude.com/docs/en/overview)
58
- - [OpenCode](https://github.com/anomalyco/opencode) `>= 1.18.0 < 2.0.0`
59
- - [Pi](https://github.com/earendil-works/pi)(已验证 `0.84.2`;需要本机可执行 `pi` 命令)
58
+ - [OpenCode](https://github.com/anomalyco/opencode)(同时支持 `1.18.x` 与 `2.0.x`)
59
+ - [Pi](https://github.com/earendil-works/pi)(已验证 `0.85.1`;需要本机可执行 `pi` 命令)
60
+ - Bridge 仅调用安装在 `PATH` 中、可直接执行的独立 CLI;不会发现或调用 ChatGPT.app 等桌面应用内部捆绑的私有可执行文件。
60
61
  - 已准备一个远程通道:个人微信使用 `wechat-setup`,企业微信使用 `wecom-setup`
61
62
 
62
63
  ### 2. 安装与更新
@@ -344,6 +345,7 @@ OpenCode 模式下,微信和企业微信侧都支持 `/new` 或 `/new-session`
344
345
  | `/claude [prompt]` | daemon | 切换到 Claude Code;可选 prompt 在切换成功后转发 |
345
346
  | `/opencode [prompt]` | daemon | 切换到 OpenCode;可选 prompt 在切换成功后转发 |
346
347
  | `/pi [prompt]` | daemon | 切换到 Pi;可选 prompt 在切换成功后转发 |
348
+ | `/all <prompt>` | daemon | 将同一段提示词同时下发给全部已启动的 CLI;未启动的列为跳过,任一 CLI 忙碌时整条取消 |
347
349
  | `/status` | 直接启动、daemon | 查看 bridge、daemon、适配器和工作区状态 |
348
350
  | `/model`、`/model <编号>` | Codex、Claude Code、OpenCode、Pi | 列出模型并切换当前会话的模型;编号列表 5 分钟内有效 |
349
351
  | `/plan`、`/plan on`、`/plan off` | Codex、Claude Code、OpenCode | 开启计划模式,或恢复进入前的模式 / primary agent |
@@ -365,7 +367,17 @@ OpenCode 的控制不依赖可见 TUI 的输入框、菜单或当前面板。桥
365
367
 
366
368
  Pi 的 `/model` 通过已连接原生 TUI extension 读取当前 scoped model 列表;没有 scoped models 时使用 model registry 中可用的模型,并通过 `pi.setModel()` 切换。Pi 的 `/plan` 仍由可选扩展定义,不作为 bridge 的通用控制命令。
367
369
 
368
- 模型列表绑定当前会话、CLI 进程和操作者;切换会话或 daemon 活动 CLI 后请重新发送 `/model`。Claude 原生菜单名称被终端宽度截断或无法唯一识别时,桥接会提示刷新或扩大终端;OpenCode 只有在 server 回读确认后才报告切换成功。
370
+ 模型列表绑定当前会话、CLI 进程和操作者;切换会话或 daemon 活动 CLI 后请重新发送 `/model`。
371
+
372
+ ### 「正在输入」状态与回复投递
373
+
374
+ 从微信派发任务后,聊天窗口会显示原生的「对方正在输入…」状态,并以 5 秒心跳保持,直到回复送达、任务失败或 daemon 关闭。typing 凭证按联系人从微信官方配置端点获取并自动刷新,获取失败时整体静默降级,不影响消息收发。
375
+
376
+ 较长的回复现在作为一条完整消息发出,不再按固定长度拆分,与微信官方机器人客户端的行为一致。若服务器拒收超长消息,既有的重试与待发队列机制仍会记录失败,回复不会无声丢失。
377
+
378
+ ### daemon IPC(外部程序接入)
379
+
380
+ `wechat-daemon` 与 `wecom-daemon` 在本地开放带 token 认证的 IPC 接口,外部程序(网页面板、脚本、自建机器人前端等)可以将文本按与聊天消息完全一致的处理规则下发给任意 CLI,或借用通道发送消息:忙碌时拒绝、有待审批或结构化输入时等待、Codex 本地任务期间自动排队,回复始终发回所属会话,输入被拒绝或失败时自动回滚状态。接口语义见 [docs/development.md](docs/development.md)。Claude 原生菜单名称被终端宽度截断或无法唯一识别时,桥接会提示刷新或扩大终端;OpenCode 只有在 server 回读确认后才报告切换成功。
369
381
 
370
382
  ### 5.2 会话恢复与本地同步
371
383
 
@@ -438,11 +450,13 @@ Daemon 模式支持将消息开头的表情映射为命令。表情绑定对微
438
450
 
439
451
  本项目采用**双协议**授权:
440
452
 
441
- **开源协议:[AGPL-3.0](LICENSE.txt)**
453
+ **开源协议:[AGPL-3.0-or-later](LICENSE.txt)**
442
454
 
443
455
  - 个人使用、学习、研究:完全免费
444
- - 修改和衍生作品必须以相同协议(AGPL-3.0)开源
445
- - 通过网络提供基于本项目的服务,也必须公开完整源代码
456
+ - 修改和衍生作品必须以相同协议(AGPL-3.0-or-later)开源,并保留版权声明
457
+ - 通过网络提供基于本项目的服务(包括仅供内部或私有部署使用的服务),也必须向服务使用者提供完整对应源代码(AGPL 第 13 条网络条款)
458
+ - **无论修改由人类还是 AI 工具完成**,只要修改后的版本被发布或对外提供服务,发布方都有义务以 AGPL-3.0-or-later 公开完整源码;仅自用不分发则无此义务
459
+ - 每个源文件头部的 SPDX 声明构成协议通知的一部分,不得删除或篡改
446
460
 
447
461
  **商业授权**
448
462
 
@@ -1,5 +1,10 @@
1
1
  #!/usr/bin/env node
2
-
2
+ // SPDX-License-Identifier: AGPL-3.0-or-later
3
+ // Copyright (c) 2026 UNLINEARITY <unlinearity@gmail.com>
4
+ // Source: https://github.com/UNLINEARITY/CLI-WeChat-Bridge
5
+ // This file is part of CLI-WeChat-Bridge. Modifications and derivative works
6
+ // must be released under AGPL-3.0-or-later with full source code; see
7
+ // LICENSE.txt. Network services built on it must offer source to users.
3
8
  import { spawn } from "node:child_process";
4
9
  import path from "node:path";
5
10
  import { fileURLToPath } from "node:url";
@@ -1,5 +1,10 @@
1
1
  #!/usr/bin/env node
2
-
2
+ // SPDX-License-Identifier: AGPL-3.0-or-later
3
+ // Copyright (c) 2026 UNLINEARITY <unlinearity@gmail.com>
4
+ // Source: https://github.com/UNLINEARITY/CLI-WeChat-Bridge
5
+ // This file is part of CLI-WeChat-Bridge. Modifications and derivative works
6
+ // must be released under AGPL-3.0-or-later with full source code; see
7
+ // LICENSE.txt. Network services built on it must offer source to users.
3
8
  import { runJsEntry } from "./_run-entry.mjs";
4
9
 
5
10
  runJsEntry("dist/commands/check-update.js", []);
@@ -1,5 +1,10 @@
1
1
  #!/usr/bin/env node
2
-
2
+ // SPDX-License-Identifier: AGPL-3.0-or-later
3
+ // Copyright (c) 2026 UNLINEARITY <unlinearity@gmail.com>
4
+ // Source: https://github.com/UNLINEARITY/CLI-WeChat-Bridge
5
+ // This file is part of CLI-WeChat-Bridge. Modifications and derivative works
6
+ // must be released under AGPL-3.0-or-later with full source code; see
7
+ // LICENSE.txt. Network services built on it must offer source to users.
3
8
  import { runJsEntry } from "./_run-entry.mjs";
4
9
 
5
10
  runJsEntry("dist/companion/local-companion-start.js", ["--adapter", "claude"]);
@@ -1,5 +1,10 @@
1
1
  #!/usr/bin/env node
2
-
2
+ // SPDX-License-Identifier: AGPL-3.0-or-later
3
+ // Copyright (c) 2026 UNLINEARITY <unlinearity@gmail.com>
4
+ // Source: https://github.com/UNLINEARITY/CLI-WeChat-Bridge
5
+ // This file is part of CLI-WeChat-Bridge. Modifications and derivative works
6
+ // must be released under AGPL-3.0-or-later with full source code; see
7
+ // LICENSE.txt. Network services built on it must offer source to users.
3
8
  import { runJsEntry } from "./_run-entry.mjs";
4
9
 
5
10
  runJsEntry("dist/companion/local-companion-start.js", ["--adapter", "codex"]);
@@ -1,5 +1,10 @@
1
1
  #!/usr/bin/env node
2
-
2
+ // SPDX-License-Identifier: AGPL-3.0-or-later
3
+ // Copyright (c) 2026 UNLINEARITY <unlinearity@gmail.com>
4
+ // Source: https://github.com/UNLINEARITY/CLI-WeChat-Bridge
5
+ // This file is part of CLI-WeChat-Bridge. Modifications and derivative works
6
+ // must be released under AGPL-3.0-or-later with full source code; see
7
+ // LICENSE.txt. Network services built on it must offer source to users.
3
8
  import { runJsEntry } from "./_run-entry.mjs";
4
9
 
5
10
  runJsEntry("dist/daemon/wechat-daemon.js");
@@ -1,5 +1,10 @@
1
1
  #!/usr/bin/env node
2
-
2
+ // SPDX-License-Identifier: AGPL-3.0-or-later
3
+ // Copyright (c) 2026 UNLINEARITY <unlinearity@gmail.com>
4
+ // Source: https://github.com/UNLINEARITY/CLI-WeChat-Bridge
5
+ // This file is part of CLI-WeChat-Bridge. Modifications and derivative works
6
+ // must be released under AGPL-3.0-or-later with full source code; see
7
+ // LICENSE.txt. Network services built on it must offer source to users.
3
8
  import { runJsEntry } from "./_run-entry.mjs";
4
9
 
5
10
  runJsEntry("dist/companion/local-companion-start.js", ["--adapter", "opencode"]);
package/bin/wechat-pi.mjs CHANGED
@@ -1,5 +1,10 @@
1
1
  #!/usr/bin/env node
2
-
2
+ // SPDX-License-Identifier: AGPL-3.0-or-later
3
+ // Copyright (c) 2026 UNLINEARITY <unlinearity@gmail.com>
4
+ // Source: https://github.com/UNLINEARITY/CLI-WeChat-Bridge
5
+ // This file is part of CLI-WeChat-Bridge. Modifications and derivative works
6
+ // must be released under AGPL-3.0-or-later with full source code; see
7
+ // LICENSE.txt. Network services built on it must offer source to users.
3
8
  import { runJsEntry } from "./_run-entry.mjs";
4
9
 
5
10
  runJsEntry("dist/companion/local-companion-start.js", ["--adapter", "pi"]);
@@ -1,5 +1,10 @@
1
1
  #!/usr/bin/env node
2
-
2
+ // SPDX-License-Identifier: AGPL-3.0-or-later
3
+ // Copyright (c) 2026 UNLINEARITY <unlinearity@gmail.com>
4
+ // Source: https://github.com/UNLINEARITY/CLI-WeChat-Bridge
5
+ // This file is part of CLI-WeChat-Bridge. Modifications and derivative works
6
+ // must be released under AGPL-3.0-or-later with full source code; see
7
+ // LICENSE.txt. Network services built on it must offer source to users.
3
8
  import { runJsEntry } from "./_run-entry.mjs";
4
9
 
5
10
  runJsEntry("dist/wechat/setup.js");
@@ -1,5 +1,10 @@
1
1
  #!/usr/bin/env node
2
-
2
+ // SPDX-License-Identifier: AGPL-3.0-or-later
3
+ // Copyright (c) 2026 UNLINEARITY <unlinearity@gmail.com>
4
+ // Source: https://github.com/UNLINEARITY/CLI-WeChat-Bridge
5
+ // This file is part of CLI-WeChat-Bridge. Modifications and derivative works
6
+ // must be released under AGPL-3.0-or-later with full source code; see
7
+ // LICENSE.txt. Network services built on it must offer source to users.
3
8
  import { runJsEntry } from "./_run-entry.mjs";
4
9
 
5
10
  runJsEntry("dist/commands/check-update.js");
@@ -1,5 +1,10 @@
1
1
  #!/usr/bin/env node
2
-
2
+ // SPDX-License-Identifier: AGPL-3.0-or-later
3
+ // Copyright (c) 2026 UNLINEARITY <unlinearity@gmail.com>
4
+ // Source: https://github.com/UNLINEARITY/CLI-WeChat-Bridge
5
+ // This file is part of CLI-WeChat-Bridge. Modifications and derivative works
6
+ // must be released under AGPL-3.0-or-later with full source code; see
7
+ // LICENSE.txt. Network services built on it must offer source to users.
3
8
  import { runJsEntry } from "./_run-entry.mjs";
4
9
 
5
10
  runJsEntry("dist/companion/local-companion-start.js", ["--adapter", "claude", "--channel", "wecom"]);
@@ -1,5 +1,10 @@
1
1
  #!/usr/bin/env node
2
-
2
+ // SPDX-License-Identifier: AGPL-3.0-or-later
3
+ // Copyright (c) 2026 UNLINEARITY <unlinearity@gmail.com>
4
+ // Source: https://github.com/UNLINEARITY/CLI-WeChat-Bridge
5
+ // This file is part of CLI-WeChat-Bridge. Modifications and derivative works
6
+ // must be released under AGPL-3.0-or-later with full source code; see
7
+ // LICENSE.txt. Network services built on it must offer source to users.
3
8
  import { runJsEntry } from "./_run-entry.mjs";
4
9
 
5
10
  runJsEntry("dist/companion/local-companion-start.js", ["--adapter", "codex", "--channel", "wecom"]);
@@ -1,5 +1,10 @@
1
1
  #!/usr/bin/env node
2
-
2
+ // SPDX-License-Identifier: AGPL-3.0-or-later
3
+ // Copyright (c) 2026 UNLINEARITY <unlinearity@gmail.com>
4
+ // Source: https://github.com/UNLINEARITY/CLI-WeChat-Bridge
5
+ // This file is part of CLI-WeChat-Bridge. Modifications and derivative works
6
+ // must be released under AGPL-3.0-or-later with full source code; see
7
+ // LICENSE.txt. Network services built on it must offer source to users.
3
8
  import { runJsEntry } from "./_run-entry.mjs";
4
9
 
5
10
  runJsEntry("dist/daemon/wechat-daemon.js", ["--channel", "wecom"]);
@@ -1,5 +1,10 @@
1
1
  #!/usr/bin/env node
2
-
2
+ // SPDX-License-Identifier: AGPL-3.0-or-later
3
+ // Copyright (c) 2026 UNLINEARITY <unlinearity@gmail.com>
4
+ // Source: https://github.com/UNLINEARITY/CLI-WeChat-Bridge
5
+ // This file is part of CLI-WeChat-Bridge. Modifications and derivative works
6
+ // must be released under AGPL-3.0-or-later with full source code; see
7
+ // LICENSE.txt. Network services built on it must offer source to users.
3
8
  import { runJsEntry } from "./_run-entry.mjs";
4
9
 
5
10
  runJsEntry("dist/companion/local-companion-start.js", ["--adapter", "opencode", "--channel", "wecom"]);
package/bin/wecom-pi.mjs CHANGED
@@ -1,5 +1,10 @@
1
1
  #!/usr/bin/env node
2
-
2
+ // SPDX-License-Identifier: AGPL-3.0-or-later
3
+ // Copyright (c) 2026 UNLINEARITY <unlinearity@gmail.com>
4
+ // Source: https://github.com/UNLINEARITY/CLI-WeChat-Bridge
5
+ // This file is part of CLI-WeChat-Bridge. Modifications and derivative works
6
+ // must be released under AGPL-3.0-or-later with full source code; see
7
+ // LICENSE.txt. Network services built on it must offer source to users.
3
8
  import { runJsEntry } from "./_run-entry.mjs";
4
9
 
5
10
  runJsEntry("dist/companion/local-companion-start.js", ["--adapter", "pi", "--channel", "wecom"]);
@@ -1,5 +1,10 @@
1
1
  #!/usr/bin/env node
2
-
2
+ // SPDX-License-Identifier: AGPL-3.0-or-later
3
+ // Copyright (c) 2026 UNLINEARITY <unlinearity@gmail.com>
4
+ // Source: https://github.com/UNLINEARITY/CLI-WeChat-Bridge
5
+ // This file is part of CLI-WeChat-Bridge. Modifications and derivative works
6
+ // must be released under AGPL-3.0-or-later with full source code; see
7
+ // LICENSE.txt. Network services built on it must offer source to users.
3
8
  import { runJsEntry } from "./_run-entry.mjs";
4
9
 
5
10
  runJsEntry("dist/channels/wecom/setup-cli.js");
@@ -1,3 +1,9 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Copyright (c) 2026 UNLINEARITY <unlinearity@gmail.com>
3
+ // Source: https://github.com/UNLINEARITY/CLI-WeChat-Bridge
4
+ // This file is part of CLI-WeChat-Bridge. Modifications and derivative works
5
+ // must be released under AGPL-3.0-or-later with full source code; see
6
+ // LICENSE.txt. Network services built on it must offer source to users.
1
7
  import { NativeTerminalControl, readClaudePermissionMode } from "./native-terminal-control.js";
2
8
  import { ClaudeNativeControls } from "./claude-native-controls.js";
3
9
  import fs from "node:fs";
@@ -1,3 +1,9 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Copyright (c) 2026 UNLINEARITY <unlinearity@gmail.com>
3
+ // Source: https://github.com/UNLINEARITY/CLI-WeChat-Bridge
4
+ // This file is part of CLI-WeChat-Bridge. Modifications and derivative works
5
+ // must be released under AGPL-3.0-or-later with full source code; see
6
+ // LICENSE.txt. Network services built on it must offer source to users.
1
7
  import fs from "node:fs";
2
8
  import path from "node:path";
3
9
  import crypto from "node:crypto";
@@ -6,10 +12,11 @@ import { detectCliApproval, } from "./bridge-utils.js";
6
12
  import { normalizeOutput, nowIso, summarizeOutput, truncatePreview, } from "../core/text-utils.js";
7
13
  import { AbstractPtyAdapter } from "./bridge-adapters.core.js";
8
14
  import { killProcessTreeSync } from "./bridge-process-reaper.js";
15
+ import { CodexVisibleThreadProxy } from "./codex-visible-thread-proxy.js";
9
16
  import * as shared from "./bridge-adapters.shared.js";
10
17
  import { requestCodexVisibleClientShutdown, requestCodexVisibleThreadSwitch, } from "../companion/codex-visible-client-link.js";
11
18
  import { readLocalCompanionEndpoint } from "../companion/local-companion-link.js";
12
- import { ensureWorkspaceChannelDir } from "../wechat/channel-config.js";
19
+ import { appendBoundedLog as appendBridgeLog, BRIDGE_LOG_FILE, ensureWorkspaceChannelDir } from "../wechat/channel-config.js";
13
20
  import { CODEX_REMOTE_AUTH_TOKEN_ENV, LOCAL_CLIENT_PROTOCOL_VERSION, } from "../runtime/runtime-types.js";
14
21
  export function parseCodexCliVersion(output) {
15
22
  const match = output.match(/\b(\d+\.\d+\.\d+)\b/);
@@ -22,7 +29,7 @@ export function isCodexVersionInCompatibilityRange(version) {
22
29
  }
23
30
  const major = parts[0] ?? -1;
24
31
  const minor = parts[1] ?? -1;
25
- return major === 0 && minor >= 149 && minor <= 155;
32
+ return major === 0 && minor >= 149 && minor <= 157;
26
33
  }
27
34
  const { CODEX_APP_SERVER_HOST, CODEX_APP_SERVER_READY_TIMEOUT_MS, CODEX_FINAL_REPLY_SETTLE_DELAY_MS, CODEX_RECENT_SESSION_KEY_LIMIT, CODEX_RPC_CONNECT_RETRY_MS, CODEX_RPC_RECONNECT_TIMEOUT_MS, CODEX_SESSION_FALLBACK_SCAN_INTERVAL_MS, CODEX_SESSION_LOCAL_MIRROR_FALLBACK_WINDOW_MS, CODEX_SESSION_POLL_INTERVAL_MS, CODEX_STARTUP_WARMUP_MS, CODEX_THREAD_SIGNAL_TTL_MS, INTERRUPT_SETTLE_DELAY_MS, appendBoundedLog, buildCodexApprovalRequest, buildCodexCliArgs, buildCodexDynamicToolCallFailureResponse, buildCodexMcpServerElicitationDeclineResponse, buildCodexPermissionsRequestApprovalResponse, buildCodexUserInputRequest, coerceWebSocketMessageData, delay, describeUnknownError, extractCodexFinalTextFromItem, extractCodexThreadFollowIdFromStatusChanged, extractCodexThreadStartedThreadId, extractCodexUserMessageText, findCodexSessionFile, findRecentCodexSessionFileForCwd, getCodexRpcRequestId, getCodexApprovalAutoResponse, getCodexWechatOutboundAttachmentDenyMessage, getNotificationThreadId, getNotificationTurnId, isRecord, isRecentIsoTimestamp, listCodexResumeSessions, normalizeComparablePath, normalizeCodexRpcError, reserveLocalPort, resolveSpawnTarget, shouldAutoCompleteCodexWechatTurnAfterFinalReply, shouldIgnoreCodexSessionReplayEntry, shouldRecoverCodexStaleBusyState, waitForTcpPort, } = shared;
28
35
  const CODEX_LOCAL_THREAD_ANNOUNCE_SETTLE_MS = 150;
@@ -74,6 +81,8 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
74
81
  appServer = null;
75
82
  nativeProcess = null;
76
83
  appServerPort = null;
84
+ visibleThreadProxy = null;
85
+ visibleOutOfWorkspaceThreadId = null;
77
86
  appServerShuttingDown = false;
78
87
  appServerLog = "";
79
88
  appServerAuthToken = null;
@@ -160,8 +169,18 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
160
169
  this.setStatus("starting", `Codex protocol compatibility: ${compatibility}.`);
161
170
  await this.startAppServer();
162
171
  await this.connectRpcClient();
163
- await this.restoreInitialSharedThreadIfNeeded();
164
172
  try {
173
+ if (this.isHeadlessRuntimeMode() && this.appServerPort && this.appServerAuthToken) {
174
+ const trace = (message) => appendBridgeLog(BRIDGE_LOG_FILE, `[${nowIso()}] codex_visible_proxy: ${message}\n`);
175
+ this.visibleThreadProxy = await CodexVisibleThreadProxy.start({
176
+ upstreamUrl: `ws://${CODEX_APP_SERVER_HOST}:${this.appServerPort}`,
177
+ token: this.appServerAuthToken,
178
+ onTrace: trace,
179
+ onThreadOpened: (threadId, cwd) => this.handleVisibleCodexThreadOpened(threadId, cwd, trace),
180
+ onVisibleTurn: (threadId) => this.handleVisibleCodexTurn(threadId, trace),
181
+ });
182
+ }
183
+ await this.restoreInitialSharedThreadIfNeeded();
165
184
  if (this.isNativePanelMode()) {
166
185
  await this.startNativeClient();
167
186
  }
@@ -280,6 +299,7 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
280
299
  return models;
281
300
  }
282
301
  async selectModel(modelId) {
302
+ this.assertVisibleCodexWorkspace();
283
303
  const models = await this.listModels();
284
304
  const selected = models.find((model) => model.id === modelId);
285
305
  if (!selected)
@@ -288,6 +308,7 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
288
308
  return selected;
289
309
  }
290
310
  async setPlanMode(enabled) {
311
+ this.assertVisibleCodexWorkspace();
291
312
  if (!this.usesRpcTurnTransport() || !this.sharedThreadId) {
292
313
  throw new Error("Codex plan mode requires an active app-server thread.");
293
314
  }
@@ -458,7 +479,7 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
458
479
  renderMode: "headless",
459
480
  bridgeOwnerPid: process.pid,
460
481
  serverPort: this.appServerPort,
461
- serverUrl: `ws://${CODEX_APP_SERVER_HOST}:${this.appServerPort}`,
482
+ serverUrl: this.visibleThreadProxy?.url ?? `ws://${CODEX_APP_SERVER_HOST}:${this.appServerPort}`,
462
483
  remoteAuthTokenEnv: CODEX_REMOTE_AUTH_TOKEN_ENV,
463
484
  cwd: this.options.cwd,
464
485
  command: this.options.command,
@@ -1014,6 +1035,7 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
1014
1035
  }
1015
1036
  }
1016
1037
  async sendPanelTurn(text) {
1038
+ this.assertVisibleCodexWorkspace();
1017
1039
  if (this.isNativePanelMode() && !this.nativeProcess) {
1018
1040
  throw new Error("codex panel is not running.");
1019
1041
  }
@@ -1162,6 +1184,8 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
1162
1184
  });
1163
1185
  this.appServer = null;
1164
1186
  this.appServerPort = null;
1187
+ void this.visibleThreadProxy?.close();
1188
+ this.visibleThreadProxy = null;
1165
1189
  this.appServerShuttingDown = false;
1166
1190
  this.deleteAppServerAuthTokenFile();
1167
1191
  this.appServerAuthToken = null;
@@ -1184,6 +1208,8 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
1184
1208
  });
1185
1209
  this.appServer = null;
1186
1210
  this.appServerPort = null;
1211
+ void this.visibleThreadProxy?.close();
1212
+ this.visibleThreadProxy = null;
1187
1213
  this.appServerShuttingDown = false;
1188
1214
  this.deleteAppServerAuthTokenFile();
1189
1215
  this.appServerAuthToken = null;
@@ -1654,6 +1680,7 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
1654
1680
  reason: "wechat_resume",
1655
1681
  notify: true,
1656
1682
  });
1683
+ this.clearVisibleCodexWorkspaceBlock();
1657
1684
  this.bridgeResumeReplayThreadId = targetThreadId;
1658
1685
  this.bridgeResumeReplayUntilMs = Date.now() + CODEX_RESUME_REPLAY_SETTLE_MS;
1659
1686
  this.setStatus("starting", `Codex resume committed thread ${targetThreadId.slice(0, 12)}.`);
@@ -1875,6 +1902,7 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
1875
1902
  if (!options.preserveThread) {
1876
1903
  this.clearPendingThreadAnnouncement();
1877
1904
  this.announcedThreadId = null;
1905
+ this.visibleOutOfWorkspaceThreadId = null;
1878
1906
  }
1879
1907
  if (!options.preserveThread) {
1880
1908
  this.updateSharedThread(null);
@@ -1994,6 +2022,62 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
1994
2022
  }, CODEX_LOCAL_THREAD_ANNOUNCE_SETTLE_MS);
1995
2023
  pending.timer.unref?.();
1996
2024
  }
2025
+ handleVisibleCodexThreadOpened(threadId, cwd, trace) {
2026
+ if (this.pendingVisibleResume || this.shuttingDown) {
2027
+ trace?.(`ignored thread=${threadId.slice(0, 12)} reason=bridge_switch`);
2028
+ return;
2029
+ }
2030
+ if (cwd && normalizeComparablePath(cwd) !== normalizeComparablePath(this.options.cwd)) {
2031
+ trace?.(`paused thread=${threadId.slice(0, 12)} reason=workspace`);
2032
+ this.interruptWechatTurnForLocalThreadSwitch(threadId);
2033
+ this.clearPendingThreadAnnouncement();
2034
+ this.pendingThreadFollowId = null;
2035
+ if (this.visibleOutOfWorkspaceThreadId !== threadId) {
2036
+ this.visibleOutOfWorkspaceThreadId = threadId;
2037
+ this.emit({
2038
+ type: "notice",
2039
+ level: "warning",
2040
+ text: `The visible Codex terminal switched to ${cwd}, outside the bridge workspace ${this.options.cwd}. WeChat input is paused instead of being sent to the previous session. Resume a session from the bridge workspace, or restart the bridge in ${cwd}.`,
2041
+ timestamp: nowIso(),
2042
+ });
2043
+ }
2044
+ return;
2045
+ }
2046
+ this.clearVisibleCodexWorkspaceBlock();
2047
+ trace?.(`follow thread=${threadId.slice(0, 12)} source=visible_open`);
2048
+ this.trackLocalSharedThread(threadId, { reason: "local_follow", signal: "visible_request" });
2049
+ }
2050
+ handleVisibleCodexTurn(threadId, trace) {
2051
+ if (this.pendingVisibleResume || this.shuttingDown)
2052
+ return;
2053
+ if (threadId === this.sharedThreadId) {
2054
+ this.clearVisibleCodexWorkspaceBlock();
2055
+ return;
2056
+ }
2057
+ if (!findCodexSessionFile(this.options.cwd, Date.now(), { threadId })) {
2058
+ trace?.(`ignored thread=${threadId.slice(0, 12)} reason=workspace_or_unpersisted`);
2059
+ return;
2060
+ }
2061
+ this.clearVisibleCodexWorkspaceBlock();
2062
+ trace?.(`follow thread=${threadId.slice(0, 12)} source=visible_turn`);
2063
+ this.trackLocalSharedThread(threadId, { reason: "local_follow", signal: "visible_request" });
2064
+ }
2065
+ clearVisibleCodexWorkspaceBlock() {
2066
+ if (!this.visibleOutOfWorkspaceThreadId)
2067
+ return;
2068
+ this.visibleOutOfWorkspaceThreadId = null;
2069
+ this.emit({
2070
+ type: "notice",
2071
+ level: "info",
2072
+ text: `The visible Codex terminal is back in the bridge workspace ${this.options.cwd}. WeChat input has resumed.`,
2073
+ timestamp: nowIso(),
2074
+ });
2075
+ }
2076
+ assertVisibleCodexWorkspace() {
2077
+ if (this.visibleOutOfWorkspaceThreadId) {
2078
+ throw new Error(`The visible Codex terminal is outside the bridge workspace ${this.options.cwd}. WeChat input is paused; resume a session from this workspace or restart the bridge in the terminal's workspace.`);
2079
+ }
2080
+ }
1997
2081
  trackLocalSharedThread(threadId, options) {
1998
2082
  const weakSignal = options.signal === "status_changed" ||
1999
2083
  options.signal === "thread_started" ||
@@ -2001,7 +2085,7 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
2001
2085
  if (weakSignal && Date.now() < this.localThreadFollowBlockedUntilMs) {
2002
2086
  return;
2003
2087
  }
2004
- if (options.signal === "user_message" || options.signal === "turn_started") {
2088
+ if (options.signal === "user_message" || options.signal === "turn_started" || options.signal === "visible_request") {
2005
2089
  this.localThreadFollowBlockedUntilMs = 0;
2006
2090
  }
2007
2091
  if (this.pendingVisibleResume) {
@@ -2337,7 +2421,7 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
2337
2421
  origin: "local",
2338
2422
  };
2339
2423
  }
2340
- if (method === "turn/started" && !this.activeTurn) {
2424
+ if (method === "turn/started" && !this.activeTurn && !this.visibleThreadProxy) {
2341
2425
  return {
2342
2426
  threadId,
2343
2427
  turnId,
@@ -2611,7 +2695,7 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
2611
2695
  if (!threadId) {
2612
2696
  return;
2613
2697
  }
2614
- if (this.pendingVisibleResume) {
2698
+ if (this.pendingVisibleResume || this.visibleThreadProxy) {
2615
2699
  return;
2616
2700
  }
2617
2701
  if (this.isBridgeResumeReplay(threadId)) {
@@ -2690,7 +2774,7 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
2690
2774
  if (!threadId) {
2691
2775
  return;
2692
2776
  }
2693
- if (this.isBridgeResumeReplay(threadId)) {
2777
+ if (this.visibleThreadProxy || this.isBridgeResumeReplay(threadId)) {
2694
2778
  return;
2695
2779
  }
2696
2780
  if (this.isRecentlyBridgeOwnedThread(threadId)) {
@@ -2968,6 +3052,9 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
2968
3052
  this.rememberCompletedTurn(turnId);
2969
3053
  }
2970
3054
  async stopAppServer() {
3055
+ const visibleThreadProxy = this.visibleThreadProxy;
3056
+ this.visibleThreadProxy = null;
3057
+ await visibleThreadProxy?.close();
2971
3058
  if (!this.appServer) {
2972
3059
  this.appServerPort = null;
2973
3060
  this.appServerShuttingDown = false;
@@ -1,3 +1,9 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Copyright (c) 2026 UNLINEARITY <unlinearity@gmail.com>
3
+ // Source: https://github.com/UNLINEARITY/CLI-WeChat-Bridge
4
+ // This file is part of CLI-WeChat-Bridge. Modifications and derivative works
5
+ // must be released under AGPL-3.0-or-later with full source code; see
6
+ // LICENSE.txt. Network services built on it must offer source to users.
1
7
  import net from "node:net";
2
8
  import path from "node:path";
3
9
  import { spawn as spawnPty } from "node-pty";