codex-to-im 1.0.22 → 1.0.23
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.
- package/README.md +1 -1
- package/README_EN.md +105 -172
- package/SECURITY.md +3 -7
- package/dist/cli.mjs +1 -7
- package/dist/daemon.mjs +30 -90
- package/dist/ui-server.mjs +201 -591
- package/docs/codex-to-im-prd.md +1 -1
- package/docs/codex-to-im-shared-thread-design.md +8 -8
- package/docs/install-windows.md +5 -9
- package/package.json +1 -1
- package/references/troubleshooting.md +1 -1
- package/scripts/daemon.sh +6 -6
- package/scripts/doctor.sh +2 -2
- package/scripts/supervisor-macos.sh +1 -1
- package/scripts/supervisor-windows.ps1 +8 -8
package/README.md
CHANGED
|
@@ -159,7 +159,7 @@ codex-to-im stop
|
|
|
159
159
|
- `/t 0`:切换到当前聊天的临时线程。
|
|
160
160
|
- `/new`:在当前正式会话目录下新建线程。
|
|
161
161
|
- `/new <路径或项目名>`:按指定目录新建线程。
|
|
162
|
-
- `/mode <ask|code>`:切换运行模式。
|
|
162
|
+
- `/mode <ask|code|plan>`:切换运行模式。
|
|
163
163
|
- `/reasoning <1-5>`:切换思考级别。
|
|
164
164
|
- `/model`:查看当前模型和可选模型。
|
|
165
165
|
- `/model <模型名>`:切换当前 IM 会话模型。
|
package/README_EN.md
CHANGED
|
@@ -2,89 +2,62 @@
|
|
|
2
2
|
|
|
3
3
|
[中文版](README.md)
|
|
4
4
|
|
|
5
|
-
`codex-to-im` is a local bridge app that connects Codex
|
|
5
|
+
`codex-to-im` is a local bridge app that connects Codex to IM channels such as Feishu/Lark and Weixin.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Its main path is not to modify Codex itself, but to:
|
|
8
8
|
|
|
9
|
-
1.
|
|
10
|
-
2.
|
|
11
|
-
3.
|
|
12
|
-
4.
|
|
13
|
-
5. Bind real desktop Codex threads to Feishu or Weixin chats
|
|
9
|
+
1. start a local web workbench and bridge on your machine
|
|
10
|
+
2. create and configure one or more channel instances in the workbench
|
|
11
|
+
3. bind desktop Codex sessions to IM chats
|
|
12
|
+
4. continue the same conversation, switch threads, and inspect status from IM
|
|
14
13
|
|
|
15
|
-
|
|
14
|
+
## Core Capabilities
|
|
16
15
|
|
|
17
|
-
|
|
16
|
+
- Shared desktop threads: bind a thread currently used in Codex Desktop to IM and continue the same conversation there.
|
|
17
|
+
- IM remote control: inspect current status, switch threads, create threads, change mode, change reasoning effort, switch model, stop the current task, and inspect history from IM.
|
|
18
|
+
- Local web workbench: central place for configuration, channel login, logs, session management, and binding management.
|
|
19
|
+
- Feishu streaming cards: Feishu can show streaming shared-thread responses and tool progress updates.
|
|
20
|
+
- Attachment send-back: send local images or files back to Feishu; if you want Codex to actively use that capability, install the bundled `codex-to-im` skill.
|
|
21
|
+
- Local-first: services, config, logs, and the bridge all run on the local machine; LAN access to the web console is optional.
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
## Supported Channels
|
|
20
24
|
|
|
21
|
-
-
|
|
22
|
-
-
|
|
25
|
+
- Feishu: supports multiple bot instances, connectivity testing, shared threads, streaming cards, image sending, and file sending.
|
|
26
|
+
- Weixin: supports multiple instances, QR login, shared threads, and text feedback.
|
|
23
27
|
|
|
24
|
-
|
|
28
|
+
Each channel instance can have its own alias, for example:
|
|
25
29
|
|
|
26
|
-
|
|
30
|
+
- `Feishu Main`
|
|
31
|
+
- `Feishu Backup`
|
|
32
|
+
- `Weixin Work`
|
|
27
33
|
|
|
28
|
-
|
|
34
|
+
These aliases only distinguish different chat entry points. They do not change Codex session semantics.
|
|
29
35
|
|
|
30
|
-
|
|
31
|
-
- Local web workbench for configuration, testing, logs, and bindings
|
|
32
|
-
- Multi-instance Feishu bot setup and connectivity testing
|
|
33
|
-
- Multi-instance Weixin login flow
|
|
34
|
-
- Desktop session discovery from `~/.codex/sessions`
|
|
35
|
-
- Web-side binding updates for IM chats
|
|
36
|
-
|
|
37
|
-
## Install
|
|
36
|
+
## Quick Start
|
|
38
37
|
|
|
39
38
|
### Prerequisites
|
|
40
39
|
|
|
41
40
|
- Node.js 20+
|
|
42
|
-
-
|
|
43
|
-
|
|
44
|
-
`codex-to-im` now ships with the required `@openai/codex-sdk` / Codex CLI platform dependency, so you do not need to install a separate global Codex CLI just to run the bridge.
|
|
41
|
+
- Codex login state or API credentials available under the current OS user
|
|
45
42
|
|
|
46
|
-
|
|
43
|
+
Any of the following is sufficient:
|
|
47
44
|
|
|
48
45
|
- a logged-in Codex Desktop App
|
|
49
|
-
-
|
|
46
|
+
- a logged-in Codex CLI
|
|
50
47
|
- `CTI_CODEX_API_KEY`, `CODEX_API_KEY`, or `OPENAI_API_KEY`
|
|
51
48
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
```bash
|
|
55
|
-
npm install -g @openai/codex
|
|
56
|
-
codex auth login
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
### Global install
|
|
49
|
+
### Install
|
|
60
50
|
|
|
61
51
|
```bash
|
|
62
52
|
npm install -g codex-to-im
|
|
63
53
|
```
|
|
64
54
|
|
|
65
|
-
###
|
|
66
|
-
|
|
67
|
-
```bash
|
|
68
|
-
npm install
|
|
69
|
-
npm run build
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
Windows maintenance note:
|
|
73
|
-
|
|
74
|
-
- The repo includes [patch-codex-sdk-windows-hide.js](scripts/patch-codex-sdk-windows-hide.js), which applies a conservative postinstall patch to `@openai/codex-sdk`.
|
|
75
|
-
- This exists because on Windows the SDK may spawn the bundled Codex CLI without `windowsHide`, causing a black console window to flash for each IM-triggered run.
|
|
76
|
-
- When upgrading `@openai/codex-sdk`, verify that the spawn block still matches; if upstream fixes this natively, remove the patch instead of carrying it forward.
|
|
77
|
-
|
|
78
|
-
## Run
|
|
79
|
-
|
|
80
|
-
Start the local app:
|
|
55
|
+
### Start
|
|
81
56
|
|
|
82
57
|
```bash
|
|
83
58
|
codex-to-im
|
|
84
59
|
```
|
|
85
60
|
|
|
86
|
-
This launches the local workbench and opens it in your browser.
|
|
87
|
-
|
|
88
61
|
If you only want the background bridge without opening the UI:
|
|
89
62
|
|
|
90
63
|
```bash
|
|
@@ -93,7 +66,7 @@ codex-to-im start
|
|
|
93
66
|
|
|
94
67
|
### Boot Autostart on Windows
|
|
95
68
|
|
|
96
|
-
The
|
|
69
|
+
The current implementation can register the **bridge** as a Windows boot task. The UI is still opened on demand with `codex-to-im`.
|
|
97
70
|
|
|
98
71
|
```powershell
|
|
99
72
|
codex-to-im autostart status
|
|
@@ -104,161 +77,121 @@ codex-to-im autostart uninstall
|
|
|
104
77
|
Notes:
|
|
105
78
|
|
|
106
79
|
- `codex-to-im autostart install` and `codex-to-im autostart uninstall` must be run from an **elevated Administrator PowerShell / terminal**.
|
|
107
|
-
- Installation prompts for the current Windows account password so the
|
|
80
|
+
- Installation prompts for the current Windows account password so the boot task can be created.
|
|
108
81
|
- Autostart only launches the bridge; it does not open the Web UI.
|
|
109
|
-
- Running `codex-to-im` manually later only starts the UI if needed and
|
|
110
|
-
- The current implementation uses Windows Task Scheduler and does not
|
|
111
|
-
- The
|
|
82
|
+
- Running `codex-to-im` manually later only starts the UI if needed and does not duplicate the bridge.
|
|
83
|
+
- The current implementation uses the built-in Windows Task Scheduler and does not depend on WinSW or NSSM.
|
|
84
|
+
- The web workbench only shows autostart status; enable or disable it from the administrator commands above.
|
|
112
85
|
|
|
113
|
-
By default the workbench
|
|
86
|
+
By default the local workbench opens at:
|
|
114
87
|
|
|
115
88
|
```text
|
|
116
89
|
http://127.0.0.1:4781
|
|
117
90
|
```
|
|
118
91
|
|
|
119
|
-
If
|
|
120
|
-
|
|
121
|
-
By default, the web workbench only accepts local access.
|
|
122
|
-
|
|
123
|
-
If you want to open it from your phone or another device on the same LAN, enable `允许局域网访问 Web 控制台` in the `配置` page. When enabled:
|
|
124
|
-
|
|
125
|
-
- the workbench shows detected LAN URLs
|
|
126
|
-
- the workbench displays an access token
|
|
127
|
-
- LAN devices see a login page before they can view or modify settings
|
|
128
|
-
- you can also copy a ready-to-use login link that includes `?token=...`
|
|
129
|
-
|
|
130
|
-
If you forget the current address, run:
|
|
92
|
+
If you want to inspect the current address or service state:
|
|
131
93
|
|
|
132
94
|
```bash
|
|
133
95
|
codex-to-im url
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
Check the current local service state:
|
|
137
|
-
|
|
138
|
-
```bash
|
|
139
96
|
codex-to-im status
|
|
140
97
|
```
|
|
141
98
|
|
|
142
|
-
|
|
99
|
+
If you want to stop the local UI and bridge:
|
|
143
100
|
|
|
144
101
|
```bash
|
|
145
102
|
codex-to-im stop
|
|
146
103
|
```
|
|
147
104
|
|
|
148
|
-
##
|
|
149
|
-
|
|
150
|
-
1. Open the workbench
|
|
151
|
-
2. Create a Feishu or Weixin channel instance in the workbench
|
|
152
|
-
3. Give the instance an alias such as `Feishu Main` or `Weixin Work`
|
|
153
|
-
4. Save config and test connectivity
|
|
154
|
-
5. Start the bridge
|
|
155
|
-
6. Open the desktop sessions section
|
|
156
|
-
7. Bind a Feishu or Weixin chat to the target thread
|
|
157
|
-
8. Continue the same Codex thread from IM
|
|
158
|
-
|
|
159
|
-
If LAN access is enabled, the easiest path is to copy the LAN login link from the local workbench and open it on your phone or another device on the same network.
|
|
160
|
-
|
|
161
|
-
Useful commands:
|
|
105
|
+
## Typical Workflows
|
|
162
106
|
|
|
163
|
-
|
|
164
|
-
- `/h` / `/help` shows help
|
|
165
|
-
- `/t` / `/threads` lists the most recent 10 desktop threads, `/t all` / `/threads all` lists up to 200 of them, `/t n 100` / `/threads n 100` lists the most recent 100 desktop threads (also capped at 200), and `/t 1` / `/thread 1` binds the first one
|
|
166
|
-
- `/n` / `/new` creates a new thread in the current formal session directory; these IM-created threads are only guaranteed to continue inside IM and will not automatically appear in the Codex Desktop thread list
|
|
167
|
-
- `/n proj1` / `/new proj1` creates a new project session under the default workspace root
|
|
168
|
-
- `/m` / `/mode` shows or changes the current mode; options: `code` / `plan` / `ask`
|
|
169
|
-
- `/r` / `/reasoning` shows or changes the current reasoning effort; options: `1|2|3|4|5`
|
|
170
|
-
- `/his` / `/history` shows the summarized history, and `/his raw` / `/history raw` shows raw history
|
|
171
|
-
- `/t 0` / `/thread 0` enters a temporary draft thread that does not pollute the main work thread
|
|
172
|
-
- `1 / 2 / 3` or `/perm ...` handles permission prompts
|
|
173
|
-
- N is configurable in the web workbench under the basic settings panel
|
|
174
|
-
- The workbench command guide shows both short commands and compatible original commands
|
|
107
|
+
### 1. Take over a desktop thread
|
|
175
108
|
|
|
176
|
-
|
|
109
|
+
After creating a Feishu or Weixin channel instance in the web workbench, start the bridge.
|
|
110
|
+
Then send:
|
|
177
111
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
If those permissions are missing, the bridge log will usually show `99991672` with `cardkit:card:write`, and the bridge falls back to a final-result message.
|
|
183
|
-
|
|
184
|
-
Also note that under the current `codex` runtime, the `Codex CLI / SDK` typically emits the assistant text only when the `agent_message` item is completed, not as token-level deltas. In practice that means Feishu "streaming cards" currently behave more like:
|
|
185
|
-
|
|
186
|
-
- early `Thinking / Tool Progress` updates
|
|
187
|
-
- final response text written into the card at completion
|
|
188
|
-
|
|
189
|
-
So character-by-character text streaming is not guaranteed in the current implementation.
|
|
190
|
-
|
|
191
|
-
If creating a new session fails with `Not inside a trusted directory`, either:
|
|
192
|
-
|
|
193
|
-
- switch to a trusted project with `/new /absolute/path` or `/new proj1`, or
|
|
194
|
-
- enable `Allow Codex outside trusted Git repos` in the basic settings and restart the bridge
|
|
195
|
-
|
|
196
|
-
The configuration page also includes Codex runtime controls:
|
|
197
|
-
|
|
198
|
-
- `Default workspace root`
|
|
199
|
-
- parent directory used for `/new proj1`
|
|
200
|
-
- falls back to `~/cx2im` when left empty, expanded for the current OS
|
|
201
|
-
- `Codex filesystem permission`
|
|
202
|
-
- `read-only`, `workspace-write`, or `danger-full-access`
|
|
203
|
-
- default: `workspace-write`
|
|
204
|
-
- `Codex reasoning effort`
|
|
205
|
-
- global default reasoning level
|
|
206
|
-
- can be overridden per IM session with `/reasoning`
|
|
207
|
-
- official runtime levels are `minimal`, `low`, `medium`, `high`, `xhigh`
|
|
208
|
-
- IM numeric aliases are `1=minimal`, `2=low`, `3=medium`, `4=high`, `5=xhigh`
|
|
112
|
+
```text
|
|
113
|
+
/t
|
|
114
|
+
```
|
|
209
115
|
|
|
210
|
-
|
|
116
|
+
to list the latest 10 desktop threads. Send:
|
|
211
117
|
|
|
212
|
-
|
|
118
|
+
```text
|
|
119
|
+
/t all
|
|
120
|
+
```
|
|
213
121
|
|
|
214
|
-
|
|
122
|
+
to list up to 200 desktop threads. Then use:
|
|
215
123
|
|
|
216
|
-
|
|
124
|
+
```text
|
|
125
|
+
/t 1
|
|
126
|
+
```
|
|
217
127
|
|
|
218
|
-
|
|
219
|
-
- set `Codex reasoning effort` to `xhigh`
|
|
128
|
+
to switch to the selected thread.
|
|
220
129
|
|
|
221
|
-
|
|
130
|
+
### 2. Continue from IM
|
|
222
131
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
- disabled by default for WeChat
|
|
226
|
-
- affects text sent through the bridge, including normal replies, shared-thread mirror messages, and system feedback such as `/h`, `/status`, and `/threads`
|
|
132
|
+
Once the binding is established, send normal messages to continue the current thread.
|
|
133
|
+
If the same shared thread is also used on desktop, its output is mirrored back to IM.
|
|
227
134
|
|
|
228
|
-
|
|
135
|
+
### 3. Create a new IM thread
|
|
229
136
|
|
|
230
|
-
|
|
137
|
+
```text
|
|
138
|
+
/new
|
|
139
|
+
```
|
|
231
140
|
|
|
232
|
-
|
|
141
|
+
This creates a new thread under the working directory of the current formal session.
|
|
142
|
+
If there is no formal session yet, or the current session is temporary, the command fails.
|
|
233
143
|
|
|
234
|
-
|
|
144
|
+
You can also specify a directory explicitly:
|
|
235
145
|
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
codex-to-im
|
|
146
|
+
```text
|
|
147
|
+
/new my-project
|
|
148
|
+
/new D:\work\my-project
|
|
240
149
|
```
|
|
241
150
|
|
|
242
|
-
##
|
|
151
|
+
## Common Commands
|
|
152
|
+
|
|
153
|
+
- `/` or `/status`: inspect the current session, thread, model, mode, reasoning effort, and shared-mirror status.
|
|
154
|
+
- `/t`: list the latest 10 desktop threads.
|
|
155
|
+
- `/t all`: list up to 200 desktop threads.
|
|
156
|
+
- `/t n 100`: list the latest 100 desktop threads, capped at 200.
|
|
157
|
+
- `/t 1`: switch to desktop thread 1.
|
|
158
|
+
- `/t 0`: switch to the temporary thread for the current chat.
|
|
159
|
+
- `/new`: create a new thread under the current formal session directory.
|
|
160
|
+
- `/new <path or project name>`: create a new thread under the specified directory.
|
|
161
|
+
- `/mode <ask|code|plan>`: change the runtime mode.
|
|
162
|
+
- `/reasoning <1-5>`: change the reasoning effort.
|
|
163
|
+
- `/model`: inspect the current model and available models.
|
|
164
|
+
- `/model <model name>`: change the model for the current IM session.
|
|
165
|
+
- `/history`: inspect the current thread history summary.
|
|
166
|
+
- `/stop`: stop the current task.
|
|
167
|
+
- `/unbind`: remove the binding between the current chat and the session.
|
|
168
|
+
|
|
169
|
+
## Key Settings
|
|
170
|
+
|
|
171
|
+
Common settings in the workbench include:
|
|
172
|
+
|
|
173
|
+
- Default workspace root: used for relative paths such as `/new my-project`.
|
|
174
|
+
- Codex filesystem permission: for example `workspace-write` or `danger-full-access`.
|
|
175
|
+
- Codex reasoning effort: `1-5`.
|
|
176
|
+
- Default model: chosen from the models available on the local machine.
|
|
177
|
+
- Use Markdown for feedback: controls whether bridge text feedback is sent through markdown rendering.
|
|
178
|
+
- Allow LAN access to the Web console: useful when opening the workbench from a phone or another device on the same LAN.
|
|
179
|
+
- Channel instances: you can create multiple Feishu or Weixin bot/account entry points and assign an alias to each instance.
|
|
180
|
+
|
|
181
|
+
The primary config file is:
|
|
243
182
|
|
|
244
|
-
-
|
|
245
|
-
- `src/service-manager.ts` — bridge and UI lifecycle management
|
|
246
|
-
- `src/desktop-sessions.ts` — desktop thread discovery from Codex session files
|
|
247
|
-
- `src/session-bindings.ts` — binding summaries and web-side binding updates
|
|
248
|
-
- `src/lib/bridge/` — bridge runtime and IM channel routing
|
|
249
|
-
- `docs/` — PRD and shared-thread design docs
|
|
183
|
+
- `~/.codex-to-im/config.v2.json`
|
|
250
184
|
|
|
251
|
-
|
|
185
|
+
The compatibility `config.env` file is still kept as a snapshot and fallback for older tooling, but it no longer fully represents multi-instance channel configuration.
|
|
252
186
|
|
|
253
|
-
|
|
254
|
-
npm run typecheck
|
|
255
|
-
npm run build
|
|
256
|
-
```
|
|
187
|
+
## Current Boundaries
|
|
257
188
|
|
|
258
|
-
|
|
189
|
+
- Threads created with `/new` are only guaranteed to continue inside IM; they are not guaranteed to automatically appear in the Codex Desktop thread list.
|
|
190
|
+
- One session can only be bound to one chat at a time, and that exclusivity also applies across Feishu and Weixin.
|
|
191
|
+
- Feishu attachments currently support images and files; videos are currently sent as files and are not guaranteed to render with native preview.
|
|
192
|
+
- `/t` shows only the latest 10 desktop threads by default; `/t all` is capped at 200, and `/t n 100` is also capped at 200.
|
|
259
193
|
|
|
260
|
-
|
|
194
|
+
## More Docs
|
|
261
195
|
|
|
262
|
-
-
|
|
263
|
-
-
|
|
264
|
-
- Shared Codex thread model first
|
|
196
|
+
- Windows installation guide: [docs/install-windows.md](docs/install-windows.md)
|
|
197
|
+
- Chinese version: [README.md](README.md)
|
package/SECURITY.md
CHANGED
|
@@ -4,8 +4,6 @@
|
|
|
4
4
|
|
|
5
5
|
Credentials are stored in `~/.codex-to-im/config.env` and the local runtime data under `~/.codex-to-im/`.
|
|
6
6
|
|
|
7
|
-
The app still falls back to `~/.claude-to-im/` on machines that already have legacy data, but new installs should treat `~/.codex-to-im/` as the primary home.
|
|
8
|
-
|
|
9
7
|
This repository never stores secrets in source control.
|
|
10
8
|
|
|
11
9
|
## Log Redaction
|
|
@@ -30,9 +28,7 @@ If a token is rotated or suspected to be exposed:
|
|
|
30
28
|
3. Restart the bridge from the workbench
|
|
31
29
|
4. Review recent logs under `~/.codex-to-im/logs/`
|
|
32
30
|
|
|
33
|
-
##
|
|
34
|
-
|
|
35
|
-
If you upgraded from an older `claude-to-im` install, check both of these locations during diagnosis:
|
|
31
|
+
## Home Directory
|
|
36
32
|
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
Current builds read and write only `~/.codex-to-im/`.
|
|
34
|
+
If a legacy home directory from older releases still exists on a machine, treat it as historical leftover data rather than an active runtime home.
|
package/dist/cli.mjs
CHANGED
|
@@ -15,15 +15,9 @@ import { fileURLToPath } from "node:url";
|
|
|
15
15
|
import fs from "node:fs";
|
|
16
16
|
import os from "node:os";
|
|
17
17
|
import path from "node:path";
|
|
18
|
-
var LEGACY_CTI_HOME = path.join(os.homedir(), ".claude-to-im");
|
|
19
18
|
var DEFAULT_CTI_HOME = path.join(os.homedir(), ".codex-to-im");
|
|
20
19
|
var DEFAULT_WORKSPACE_ROOT = path.join(os.homedir(), "cx2im");
|
|
21
|
-
|
|
22
|
-
if (fs.existsSync(DEFAULT_CTI_HOME)) return DEFAULT_CTI_HOME;
|
|
23
|
-
if (fs.existsSync(LEGACY_CTI_HOME)) return LEGACY_CTI_HOME;
|
|
24
|
-
return DEFAULT_CTI_HOME;
|
|
25
|
-
}
|
|
26
|
-
var CTI_HOME = process.env.CTI_HOME || resolveDefaultCtiHome();
|
|
20
|
+
var CTI_HOME = process.env.CTI_HOME || DEFAULT_CTI_HOME;
|
|
27
21
|
var CONFIG_PATH = path.join(CTI_HOME, "config.env");
|
|
28
22
|
var CONFIG_V2_PATH = path.join(CTI_HOME, "config.v2.json");
|
|
29
23
|
function parseEnvFile(content) {
|
package/dist/daemon.mjs
CHANGED
|
@@ -5096,15 +5096,9 @@ function toFeishuConfig(channel) {
|
|
|
5096
5096
|
function toWeixinConfig(channel) {
|
|
5097
5097
|
return channel?.provider === "weixin" ? channel.config : void 0;
|
|
5098
5098
|
}
|
|
5099
|
-
var LEGACY_CTI_HOME = path.join(os.homedir(), ".claude-to-im");
|
|
5100
5099
|
var DEFAULT_CTI_HOME = path.join(os.homedir(), ".codex-to-im");
|
|
5101
5100
|
var DEFAULT_WORKSPACE_ROOT = path.join(os.homedir(), "cx2im");
|
|
5102
|
-
|
|
5103
|
-
if (fs.existsSync(DEFAULT_CTI_HOME)) return DEFAULT_CTI_HOME;
|
|
5104
|
-
if (fs.existsSync(LEGACY_CTI_HOME)) return LEGACY_CTI_HOME;
|
|
5105
|
-
return DEFAULT_CTI_HOME;
|
|
5106
|
-
}
|
|
5107
|
-
var CTI_HOME = process.env.CTI_HOME || resolveDefaultCtiHome();
|
|
5101
|
+
var CTI_HOME = process.env.CTI_HOME || DEFAULT_CTI_HOME;
|
|
5108
5102
|
var CONFIG_PATH = path.join(CTI_HOME, "config.env");
|
|
5109
5103
|
var CONFIG_V2_PATH = path.join(CTI_HOME, "config.v2.json");
|
|
5110
5104
|
function expandHomePath(value) {
|
|
@@ -15305,7 +15299,7 @@ function assertBindingTargetAvailable(store, current, opts) {
|
|
|
15305
15299
|
function getSessionMode(store, session) {
|
|
15306
15300
|
return session.preferred_mode || store.getSetting("bridge_default_mode") || "code";
|
|
15307
15301
|
}
|
|
15308
|
-
function bindStoreToSession(store, channelType, chatId, sessionId) {
|
|
15302
|
+
function bindStoreToSession(store, channelType, chatId, sessionId, chatMeta) {
|
|
15309
15303
|
const session = store.getSession(sessionId);
|
|
15310
15304
|
if (!session) return null;
|
|
15311
15305
|
assertBindingTargetAvailable(
|
|
@@ -15319,6 +15313,8 @@ function bindStoreToSession(store, channelType, chatId, sessionId) {
|
|
|
15319
15313
|
channelProvider: meta.provider,
|
|
15320
15314
|
channelAlias: meta.alias,
|
|
15321
15315
|
chatId,
|
|
15316
|
+
chatUserId: chatMeta?.chatUserId,
|
|
15317
|
+
chatDisplayName: chatMeta?.chatDisplayName,
|
|
15322
15318
|
codepilotSessionId: session.id,
|
|
15323
15319
|
sdkSessionId: session.sdk_session_id || "",
|
|
15324
15320
|
workingDirectory: session.working_directory,
|
|
@@ -15340,6 +15336,8 @@ function bindStoreToSdkSession(store, channelType, chatId, sdkSessionId, opts) {
|
|
|
15340
15336
|
channelProvider: meta.provider,
|
|
15341
15337
|
channelAlias: meta.alias,
|
|
15342
15338
|
chatId,
|
|
15339
|
+
chatUserId: opts?.chatUserId,
|
|
15340
|
+
chatDisplayName: opts?.chatDisplayName,
|
|
15343
15341
|
codepilotSessionId: existing.id,
|
|
15344
15342
|
sdkSessionId,
|
|
15345
15343
|
workingDirectory: opts?.workingDirectory || existing.working_directory,
|
|
@@ -15363,6 +15361,8 @@ function bindStoreToSdkSession(store, channelType, chatId, sdkSessionId, opts) {
|
|
|
15363
15361
|
channelProvider: meta.provider,
|
|
15364
15362
|
channelAlias: meta.alias,
|
|
15365
15363
|
chatId,
|
|
15364
|
+
chatUserId: opts?.chatUserId,
|
|
15365
|
+
chatDisplayName: opts?.chatDisplayName,
|
|
15366
15366
|
codepilotSessionId: session.id,
|
|
15367
15367
|
sdkSessionId,
|
|
15368
15368
|
workingDirectory: workingDirectory || session.working_directory,
|
|
@@ -15504,10 +15504,23 @@ function createBinding(address, workingDirectory) {
|
|
|
15504
15504
|
});
|
|
15505
15505
|
}
|
|
15506
15506
|
function bindToSession(address, codepilotSessionId) {
|
|
15507
|
-
return bindStoreToSession(
|
|
15507
|
+
return bindStoreToSession(
|
|
15508
|
+
getBridgeContext().store,
|
|
15509
|
+
address.channelType,
|
|
15510
|
+
address.chatId,
|
|
15511
|
+
codepilotSessionId,
|
|
15512
|
+
{
|
|
15513
|
+
chatUserId: address.userId,
|
|
15514
|
+
chatDisplayName: address.displayName
|
|
15515
|
+
}
|
|
15516
|
+
);
|
|
15508
15517
|
}
|
|
15509
15518
|
function bindToSdkSession(address, sdkSessionId, opts) {
|
|
15510
|
-
return bindStoreToSdkSession(getBridgeContext().store, address.channelType, address.chatId, sdkSessionId,
|
|
15519
|
+
return bindStoreToSdkSession(getBridgeContext().store, address.channelType, address.chatId, sdkSessionId, {
|
|
15520
|
+
...opts,
|
|
15521
|
+
chatUserId: address.userId,
|
|
15522
|
+
chatDisplayName: address.displayName
|
|
15523
|
+
});
|
|
15511
15524
|
}
|
|
15512
15525
|
function updateBinding(id, updates) {
|
|
15513
15526
|
getBridgeContext().store.updateChannelBinding(id, updates);
|
|
@@ -17114,18 +17127,6 @@ function parseDesktopThreadListArgs(args) {
|
|
|
17114
17127
|
if (!Number.isInteger(limit) || limit < 1) return null;
|
|
17115
17128
|
return { showAll: false, limit };
|
|
17116
17129
|
}
|
|
17117
|
-
function getDisplayedBridgeSessions(currentSessionId) {
|
|
17118
|
-
const { store } = getBridgeContext();
|
|
17119
|
-
const sessions = store.listSessions().filter((session) => session.hidden !== true).toReversed();
|
|
17120
|
-
return sessions.sort((a, b) => {
|
|
17121
|
-
if (a.id === currentSessionId && b.id !== currentSessionId) return -1;
|
|
17122
|
-
if (b.id === currentSessionId && a.id !== currentSessionId) return 1;
|
|
17123
|
-
const aShared = a.sdk_session_id ? 1 : 0;
|
|
17124
|
-
const bShared = b.sdk_session_id ? 1 : 0;
|
|
17125
|
-
if (aShared !== bShared) return bShared - aShared;
|
|
17126
|
-
return a.name?.localeCompare(b.name || "") || 0;
|
|
17127
|
-
});
|
|
17128
|
-
}
|
|
17129
17130
|
function getSessionDisplayName(session, fallbackDirectory) {
|
|
17130
17131
|
if (session?.name?.trim()) return session.name.trim();
|
|
17131
17132
|
const cwd = session?.working_directory || fallbackDirectory || "";
|
|
@@ -19471,43 +19472,6 @@ async function handleCommand(adapter, msg, text2) {
|
|
|
19471
19472
|
);
|
|
19472
19473
|
break;
|
|
19473
19474
|
}
|
|
19474
|
-
case "/use": {
|
|
19475
|
-
if (!args) {
|
|
19476
|
-
response = "\u7528\u6CD5\uFF1A/use <session-id | \u5E8F\u53F7>";
|
|
19477
|
-
break;
|
|
19478
|
-
}
|
|
19479
|
-
const displayedSessions = getDisplayedBridgeSessions(currentBinding?.codepilotSessionId);
|
|
19480
|
-
const sessionPick = resolveByIndexOrPrefix(args, displayedSessions, (session) => session.id);
|
|
19481
|
-
if (sessionPick.ambiguous) {
|
|
19482
|
-
response = "\u5339\u914D\u5230\u591A\u4E2A\u5185\u90E8\u4F1A\u8BDD\uFF0C\u8BF7\u4F7F\u7528\u66F4\u957F\u7684\u7F16\u53F7\u3002";
|
|
19483
|
-
break;
|
|
19484
|
-
}
|
|
19485
|
-
if (!sessionPick.match) {
|
|
19486
|
-
response = "\u6CA1\u6709\u627E\u5230\u5BF9\u5E94\u7684\u5185\u90E8\u4F1A\u8BDD\u3002\u5148\u53D1\u9001 /sessions \u67E5\u770B\u53EF\u9009\u9879\u3002";
|
|
19487
|
-
break;
|
|
19488
|
-
}
|
|
19489
|
-
let binding;
|
|
19490
|
-
try {
|
|
19491
|
-
binding = bindToSession(msg.address, sessionPick.match.id);
|
|
19492
|
-
} catch (error) {
|
|
19493
|
-
response = toUserVisibleBindingError(error, "\u5207\u6362\u4F1A\u8BDD\u5931\u8D25\u3002");
|
|
19494
|
-
break;
|
|
19495
|
-
}
|
|
19496
|
-
if (!binding) {
|
|
19497
|
-
response = "\u5207\u6362\u5931\u8D25\uFF0C\u8BE5\u4F1A\u8BDD\u4E0D\u5B58\u5728\u3002";
|
|
19498
|
-
break;
|
|
19499
|
-
}
|
|
19500
|
-
response = buildCommandFields(
|
|
19501
|
-
"\u5DF2\u5207\u6362\u4F1A\u8BDD\uFF08\u517C\u5BB9\u547D\u4EE4\uFF09",
|
|
19502
|
-
[
|
|
19503
|
-
["\u6807\u9898", getSessionDisplayName(sessionPick.match, binding.workingDirectory)],
|
|
19504
|
-
["\u76EE\u5F55", formatCommandPath(binding.workingDirectory)]
|
|
19505
|
-
],
|
|
19506
|
-
["\u666E\u901A\u4F7F\u7528\u5EFA\u8BAE\u76F4\u63A5\u901A\u8FC7 `/t` \u5207\u6362\u684C\u9762\u4F1A\u8BDD\u3002"],
|
|
19507
|
-
responseParseMode === "Markdown"
|
|
19508
|
-
);
|
|
19509
|
-
break;
|
|
19510
|
-
}
|
|
19511
19475
|
case "/reasoning": {
|
|
19512
19476
|
if (!currentBinding) {
|
|
19513
19477
|
response = "\u5F53\u524D\u804A\u5929\u8FD8\u6CA1\u6709\u7ED1\u5B9A\u4F1A\u8BDD\u3002\u5148\u53D1\u9001\u6D88\u606F\u521B\u5EFA\u4F1A\u8BDD\uFF0C\u6216\u5148\u7528 `/t 1` \u63A5\u7BA1\u684C\u9762\u4F1A\u8BDD\u3002";
|
|
@@ -19549,7 +19513,7 @@ async function handleCommand(adapter, msg, text2) {
|
|
|
19549
19513
|
break;
|
|
19550
19514
|
}
|
|
19551
19515
|
case "/cwd": {
|
|
19552
|
-
response = "\u5F53\u524D\u7248\u672C\u5DF2\u4E0D\u652F\u6301 /cwd\u3002\u8BF7\u4F7F\u7528 /new \u65B0\u5EFA\u4F1A\u8BDD\uFF0C\u6216\u4F7F\u7528 /
|
|
19516
|
+
response = "\u5F53\u524D\u7248\u672C\u5DF2\u4E0D\u652F\u6301 /cwd\u3002\u8BF7\u4F7F\u7528 /new \u65B0\u5EFA\u4F1A\u8BDD\uFF0C\u6216\u4F7F\u7528 /t \u5207\u6362\u5230\u5DF2\u6709\u684C\u9762\u4F1A\u8BDD\u3002";
|
|
19553
19517
|
break;
|
|
19554
19518
|
}
|
|
19555
19519
|
case "/mode": {
|
|
@@ -19746,32 +19710,6 @@ ${truncateHistoryContent(formatStoredMessageContent(message.content))}`;
|
|
|
19746
19710
|
response = [header, summary].join("\n\n").trim();
|
|
19747
19711
|
break;
|
|
19748
19712
|
}
|
|
19749
|
-
case "/sessions": {
|
|
19750
|
-
const sessions = getDisplayedBridgeSessions(currentBinding?.codepilotSessionId);
|
|
19751
|
-
if (sessions.length === 0) {
|
|
19752
|
-
response = "\u5F53\u524D\u6CA1\u6709\u5185\u90E8\u4F1A\u8BDD\u3002\u666E\u901A\u4F7F\u7528\u5EFA\u8BAE\u76F4\u63A5\u53D1\u9001\u6D88\u606F\u521B\u5EFA\u4F1A\u8BDD\uFF0C\u6216\u5148\u7528 `/t 1` \u63A5\u7BA1\u684C\u9762\u4F1A\u8BDD\u3002";
|
|
19753
|
-
} else {
|
|
19754
|
-
response = buildIndexedCommandList(
|
|
19755
|
-
"\u53EF\u5207\u6362\u7684\u5185\u90E8\u4F1A\u8BDD\uFF08\u517C\u5BB9\u547D\u4EE4\uFF09",
|
|
19756
|
-
sessions.slice(0, 10).map((session) => {
|
|
19757
|
-
const threadTitle = session.sdk_session_id ? getDesktopThreadTitle(session.sdk_session_id) : null;
|
|
19758
|
-
return {
|
|
19759
|
-
heading: `${getSessionDisplayName(session, session.working_directory)}${session.id === currentBinding?.codepilotSessionId ? " [\u5F53\u524D]" : ""}`,
|
|
19760
|
-
details: [
|
|
19761
|
-
`\u72B6\u6001\uFF1A${formatRuntimeStatus(session)}`,
|
|
19762
|
-
`\u76EE\u5F55\uFF1A${formatCommandPath(session.working_directory)}`
|
|
19763
|
-
]
|
|
19764
|
-
};
|
|
19765
|
-
}),
|
|
19766
|
-
[
|
|
19767
|
-
"\u666E\u901A\u4F7F\u7528\u5EFA\u8BAE\u76F4\u63A5\u901A\u8FC7 `/t` \u5207\u6362\u684C\u9762\u4F1A\u8BDD\u3002",
|
|
19768
|
-
"\u517C\u5BB9\u547D\u4EE4\u4ECD\u53EF\u7528\uFF0C\u4F8B\u5982 `/use 2`\u3002"
|
|
19769
|
-
],
|
|
19770
|
-
responseParseMode === "Markdown"
|
|
19771
|
-
);
|
|
19772
|
-
}
|
|
19773
|
-
break;
|
|
19774
|
-
}
|
|
19775
19713
|
case "/stop": {
|
|
19776
19714
|
const binding = resolve(msg.address);
|
|
19777
19715
|
const st = getState();
|
|
@@ -19926,10 +19864,12 @@ function defaultAliasForProvider2(provider) {
|
|
|
19926
19864
|
if (provider === "weixin") return "\u5FAE\u4FE1";
|
|
19927
19865
|
return void 0;
|
|
19928
19866
|
}
|
|
19929
|
-
function
|
|
19867
|
+
function upgradeLegacyBinding(binding) {
|
|
19930
19868
|
const config2 = loadConfig();
|
|
19931
|
-
const
|
|
19932
|
-
const
|
|
19869
|
+
const exactInstance = findChannelInstance(binding.channelType, config2);
|
|
19870
|
+
const hasInstanceMetadata = Boolean(binding.channelProvider || binding.channelAlias);
|
|
19871
|
+
const legacyProvider = !exactInstance && !hasInstanceMetadata && (binding.channelType === "feishu" || binding.channelType === "weixin") ? binding.channelType : void 0;
|
|
19872
|
+
const resolvedInstance = exactInstance || (legacyProvider ? (config2.channels || []).find((channel) => channel.provider === legacyProvider) : void 0);
|
|
19933
19873
|
if (!resolvedInstance && !legacyProvider) {
|
|
19934
19874
|
return {
|
|
19935
19875
|
...binding,
|
|
@@ -20010,7 +19950,7 @@ var JsonFileStore = class {
|
|
|
20010
19950
|
const normalized = /* @__PURE__ */ new Map();
|
|
20011
19951
|
let changed = false;
|
|
20012
19952
|
for (const binding of Object.values(bindings)) {
|
|
20013
|
-
const normalizedBinding =
|
|
19953
|
+
const normalizedBinding = upgradeLegacyBinding(binding);
|
|
20014
19954
|
if (didBindingChange(binding, normalizedBinding)) {
|
|
20015
19955
|
changed = true;
|
|
20016
19956
|
}
|