cli-wechat-bridge 1.1.7 → 1.2.0
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/LICENSE.txt +661 -661
- package/README.md +461 -434
- package/bin/_run-entry.mjs +29 -6
- package/bin/wechat-check-update.mjs +6 -1
- package/bin/wechat-claude.mjs +6 -1
- package/bin/wechat-codex.mjs +6 -1
- package/bin/wechat-daemon.mjs +6 -1
- package/bin/wechat-opencode.mjs +6 -1
- package/bin/wechat-pi.mjs +6 -1
- package/bin/wechat-setup.mjs +6 -1
- package/bin/wecom-check-update.mjs +6 -1
- package/bin/wecom-claude.mjs +6 -1
- package/bin/wecom-codex.mjs +6 -1
- package/bin/wecom-daemon.mjs +6 -1
- package/bin/wecom-opencode.mjs +6 -1
- package/bin/wecom-pi.mjs +6 -1
- package/bin/wecom-setup.mjs +6 -1
- package/dist/bridge/adapter-control.js +58 -0
- package/dist/bridge/bridge-adapters.claude.js +35 -1
- package/dist/bridge/bridge-adapters.codex.js +118 -7
- package/dist/bridge/bridge-adapters.core.js +25 -1
- package/dist/bridge/bridge-adapters.opencode.js +156 -1
- package/dist/bridge/bridge-adapters.pi.js +45 -0
- package/dist/bridge/bridge-adapters.shared.js +6 -0
- package/dist/bridge/bridge-controller.js +6 -0
- package/dist/bridge/bridge-final-reply.js +6 -0
- package/dist/bridge/bridge-process-reaper.js +6 -0
- package/dist/bridge/bridge-state.js +6 -0
- package/dist/bridge/bridge-utils.js +6 -31
- package/dist/bridge/claude-hook.js +6 -0
- package/dist/bridge/claude-native-controls.js +133 -0
- package/dist/bridge/native-terminal-control.js +259 -0
- package/dist/bridge/wechat-bridge.js +250 -274
- package/dist/bridge/wechat-forwarding.js +6 -0
- package/dist/bridge/wechat-outbound-queue.js +6 -0
- package/dist/channels/wechat/bridge-final-reply.js +13 -22
- package/dist/channels/wechat/index.js +6 -0
- package/dist/channels/wechat/wechat-driver.js +191 -0
- package/dist/channels/wechat/wechat-forwarding.js +18 -0
- package/dist/channels/wechat/wechat-outbound-queue.js +39 -0
- package/dist/channels/wecom/bridge-final-reply.js +6 -0
- package/dist/channels/wecom/setup-cli.js +6 -0
- package/dist/channels/wecom/setup.js +6 -0
- package/dist/channels/wecom/wecom-config.js +6 -0
- package/dist/channels/wecom/wecom-driver.js +106 -0
- package/dist/channels/wecom/wecom-media.js +6 -0
- package/dist/channels/wecom/wecom-transport.js +6 -0
- package/dist/commands/check-update.js +6 -0
- package/dist/companion/codex-remote-client.js +28 -2
- package/dist/companion/codex-visible-client-link.js +107 -0
- package/dist/companion/local-companion-link.js +6 -0
- package/dist/companion/local-companion-start.js +13 -2
- package/dist/companion/local-companion.js +23 -0
- package/dist/companion/opencode-tui-bridge-plugin.js +6 -0
- package/dist/companion/pi-tui-bridge-extension.js +62 -3
- package/dist/core/bridge-control.js +10 -0
- package/dist/core/bridge-defer.js +25 -0
- package/dist/core/channel-driver.js +1 -0
- package/dist/core/conversation-routing.js +58 -0
- package/dist/core/direct-run.js +6 -0
- package/dist/core/inbound-message-claims.js +6 -0
- package/dist/core/outbound-target.js +32 -0
- package/dist/core/text-utils.js +6 -0
- package/dist/core/turn-coordinator.js +85 -0
- package/dist/daemon/daemon-link.js +6 -0
- package/dist/daemon/emoji-bindings.js +6 -0
- package/dist/daemon/wechat-daemon.js +529 -205
- package/dist/i18n/index.js +6 -0
- package/dist/i18n/messages-en.js +7 -1
- package/dist/i18n/messages-zh.js +7 -1
- package/dist/runtime/create-runtime-host.js +6 -0
- package/dist/runtime/legacy-adapter-runtime.js +15 -0
- package/dist/runtime/runtime-types.js +3 -0
- package/dist/utils/atomic-file.js +6 -0
- package/dist/utils/doctor.js +6 -0
- package/dist/utils/version-checker.js +17 -11
- package/dist/wechat/channel-config.js +6 -0
- package/dist/wechat/setup.js +6 -0
- package/dist/wechat/wechat-channel.js +6 -0
- package/dist/wechat/wechat-transport.js +169 -58
- package/package.json +108 -106
- package/scripts/ensure-node-pty-permissions.mjs +73 -73
package/bin/_run-entry.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 { spawn } from "node:child_process";
|
|
4
9
|
import path from "node:path";
|
|
5
10
|
import { fileURLToPath } from "node:url";
|
|
@@ -8,6 +13,8 @@ const BIN_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
|
8
13
|
const PROJECT_DIR = path.resolve(BIN_DIR, "..");
|
|
9
14
|
export const MIN_NODE_VERSION = Object.freeze([22, 13, 0]);
|
|
10
15
|
export const MIN_NODE_VERSION_TEXT = MIN_NODE_VERSION.join(".");
|
|
16
|
+
export const MIN_PI_NODE_VERSION = Object.freeze([22, 19, 0]);
|
|
17
|
+
export const MIN_PI_NODE_VERSION_TEXT = MIN_PI_NODE_VERSION.join(".");
|
|
11
18
|
|
|
12
19
|
export function isSupportedNodeVersion(version) {
|
|
13
20
|
const match = /^v?(\d+)(?:\.(\d+))?(?:\.(\d+))?/.exec(version);
|
|
@@ -24,19 +31,21 @@ export function isSupportedNodeVersion(version) {
|
|
|
24
31
|
return true;
|
|
25
32
|
}
|
|
26
33
|
|
|
27
|
-
function ensureSupportedNodeVersion() {
|
|
34
|
+
function ensureSupportedNodeVersion(extraArgs = []) {
|
|
28
35
|
if (process.env.CLI_BRIDGE_SKIP_NODE_CHECK === "1") {
|
|
29
36
|
return;
|
|
30
37
|
}
|
|
31
38
|
|
|
39
|
+
const piRequested = extraArgs.some((arg, index) => arg === "pi" && extraArgs[index - 1] === "--adapter");
|
|
32
40
|
if (isSupportedNodeVersion(process.versions.node)) {
|
|
33
|
-
return;
|
|
41
|
+
if (!piRequested || compareNodeVersion(process.versions.node, MIN_PI_NODE_VERSION) >= 0) return;
|
|
34
42
|
}
|
|
35
43
|
|
|
44
|
+
const minimumText = piRequested ? MIN_PI_NODE_VERSION_TEXT : MIN_NODE_VERSION_TEXT;
|
|
36
45
|
process.stderr.write(
|
|
37
46
|
[
|
|
38
|
-
`[cli-wechat-bridge] Node.js >= ${
|
|
39
|
-
`[cli-wechat-bridge] 需要 Node.js >= ${
|
|
47
|
+
`[cli-wechat-bridge] Node.js >= ${minimumText} is required, but you are running ${process.version}.`,
|
|
48
|
+
`[cli-wechat-bridge] 需要 Node.js >= ${minimumText},当前版本为 ${process.version}。`,
|
|
40
49
|
"Install the latest LTS from https://nodejs.org/ (or via nvm), then retry.",
|
|
41
50
|
"Set CLI_BRIDGE_SKIP_NODE_CHECK=1 to bypass this check at your own risk.",
|
|
42
51
|
"",
|
|
@@ -45,8 +54,22 @@ function ensureSupportedNodeVersion() {
|
|
|
45
54
|
process.exit(1);
|
|
46
55
|
}
|
|
47
56
|
|
|
57
|
+
function compareNodeVersion(version, minimum) {
|
|
58
|
+
const match = /^v?(\d+)(?:\.(\d+))?(?:\.(\d+))?/.exec(version);
|
|
59
|
+
if (!match) return -1;
|
|
60
|
+
const current = [Number(match[1]), Number(match[2] ?? 0), Number(match[3] ?? 0)];
|
|
61
|
+
for (let index = 0; index < minimum.length; index += 1) {
|
|
62
|
+
if (current[index] !== minimum[index]) return current[index] > minimum[index] ? 1 : -1;
|
|
63
|
+
}
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function isSupportedPiNodeVersion(version) {
|
|
68
|
+
return compareNodeVersion(version, MIN_PI_NODE_VERSION) >= 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
48
71
|
export function runJsEntry(relativeEntryPath, extraArgs = []) {
|
|
49
|
-
ensureSupportedNodeVersion();
|
|
72
|
+
ensureSupportedNodeVersion([...extraArgs, ...process.argv.slice(2)]);
|
|
50
73
|
const entryPath = path.join(PROJECT_DIR, relativeEntryPath);
|
|
51
74
|
const child = spawn(
|
|
52
75
|
process.execPath,
|
|
@@ -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", []);
|
package/bin/wechat-claude.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", "claude"]);
|
package/bin/wechat-codex.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", "codex"]);
|
package/bin/wechat-daemon.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/daemon/wechat-daemon.js");
|
package/bin/wechat-opencode.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", "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"]);
|
package/bin/wechat-setup.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/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");
|
package/bin/wecom-claude.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", "claude", "--channel", "wecom"]);
|
package/bin/wecom-codex.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", "codex", "--channel", "wecom"]);
|
package/bin/wecom-daemon.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/daemon/wechat-daemon.js", ["--channel", "wecom"]);
|
package/bin/wecom-opencode.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", "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"]);
|
package/bin/wecom-setup.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/channels/wecom/setup-cli.js");
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const snapshots = new WeakMap();
|
|
2
|
+
export const MODEL_SNAPSHOT_TTL_MS = 5 * 60_000;
|
|
3
|
+
export function invalidateModelSnapshot(adapter) {
|
|
4
|
+
snapshots.delete(adapter);
|
|
5
|
+
}
|
|
6
|
+
function snapshotOwner(adapter, senderId) {
|
|
7
|
+
const state = adapter.getState();
|
|
8
|
+
return JSON.stringify([
|
|
9
|
+
senderId, state.kind, state.cwd, state.pid, state.startedAt,
|
|
10
|
+
state.resumeConversationId ?? state.sharedSessionId ?? state.sharedThreadId,
|
|
11
|
+
state.lastSessionSwitchAt ?? state.lastThreadSwitchAt,
|
|
12
|
+
]);
|
|
13
|
+
}
|
|
14
|
+
export async function handleAdapterControl(adapter, senderId, command) {
|
|
15
|
+
const kind = adapter.getState().kind;
|
|
16
|
+
const name = kind === "claude" ? "Claude Code" : kind === "opencode" ? "OpenCode" : kind === "codex" ? "Codex" : "Pi";
|
|
17
|
+
try {
|
|
18
|
+
if (command.type === "plan") {
|
|
19
|
+
if (!adapter.setPlanMode)
|
|
20
|
+
throw new Error(`Plan mode is not available for ${name}.`);
|
|
21
|
+
const enabled = await adapter.setPlanMode(command.enabled);
|
|
22
|
+
return enabled
|
|
23
|
+
? `${name} plan mode enabled. Send /plan off to restore the previous mode.`
|
|
24
|
+
: `${name} plan mode disabled.`;
|
|
25
|
+
}
|
|
26
|
+
if (!adapter.listModels || !adapter.selectModel)
|
|
27
|
+
throw new Error(`Model selection is not available for ${name}.`);
|
|
28
|
+
const owner = snapshotOwner(adapter, senderId);
|
|
29
|
+
if (!command.target) {
|
|
30
|
+
snapshots.delete(adapter);
|
|
31
|
+
const models = await adapter.listModels();
|
|
32
|
+
if (snapshotOwner(adapter, senderId) !== owner)
|
|
33
|
+
throw new Error("The active session changed. Send /model again.");
|
|
34
|
+
if (!models.length)
|
|
35
|
+
return `${name} did not return any available models.`;
|
|
36
|
+
snapshots.set(adapter, { owner, createdAtMs: Date.now(), models });
|
|
37
|
+
return [
|
|
38
|
+
`Available ${name} models:`, "",
|
|
39
|
+
...models.map((model, index) => `${index + 1}. ${model.displayName}${model.isCurrent ? " (current)" : ""}`),
|
|
40
|
+
"", "Reply with /model <number> to switch. The list is valid for 5 minutes.",
|
|
41
|
+
].join("\n");
|
|
42
|
+
}
|
|
43
|
+
const snapshot = snapshots.get(adapter);
|
|
44
|
+
if (!snapshot || snapshot.owner !== owner || Date.now() - snapshot.createdAtMs > MODEL_SNAPSHOT_TTL_MS) {
|
|
45
|
+
throw new Error("The model list has expired or belongs to another session. Send /model again.");
|
|
46
|
+
}
|
|
47
|
+
const index = Number(command.target);
|
|
48
|
+
if (!/^\d+$/.test(command.target) || !Number.isSafeInteger(index) || index < 1 || index > snapshot.models.length) {
|
|
49
|
+
throw new Error("Model selection is outside the displayed range. Send /model again to refresh the list.");
|
|
50
|
+
}
|
|
51
|
+
const model = await adapter.selectModel(snapshot.models[index - 1].id);
|
|
52
|
+
snapshots.delete(adapter);
|
|
53
|
+
return `${name} model switched to ${model.displayName}.`;
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
return error instanceof Error ? error.message : String(error);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -1,3 +1,11 @@
|
|
|
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.
|
|
7
|
+
import { NativeTerminalControl, readClaudePermissionMode } from "./native-terminal-control.js";
|
|
8
|
+
import { ClaudeNativeControls } from "./claude-native-controls.js";
|
|
1
9
|
import fs from "node:fs";
|
|
2
10
|
import net from "node:net";
|
|
3
11
|
import os from "node:os";
|
|
@@ -345,6 +353,20 @@ export class ClaudeCompanionAdapter extends AbstractPtyAdapter {
|
|
|
345
353
|
remoteSubmitRetryTimer = null;
|
|
346
354
|
remoteSubmitRetryIndex = 0;
|
|
347
355
|
remoteSubmitText = null;
|
|
356
|
+
terminalControl = new NativeTerminalControl({
|
|
357
|
+
write: (text) => this.writeToPty(text),
|
|
358
|
+
identity: () => JSON.stringify([this.state.pid, this.runtimeSessionId, this.resumeConversationId]),
|
|
359
|
+
isPromptEmpty: () => readClaudePermissionMode(this.terminalControl.text) !== null,
|
|
360
|
+
assertReady: () => {
|
|
361
|
+
if (!this.pty || this.state.status !== "idle" || this.pendingApproval || this.pendingResume) {
|
|
362
|
+
throw new Error("Claude must be idle with no pending approval before using remote controls.");
|
|
363
|
+
}
|
|
364
|
+
},
|
|
365
|
+
});
|
|
366
|
+
nativeControls = new ClaudeNativeControls(this.terminalControl, () => this.resumeConversationId ?? this.runtimeSessionId ?? "");
|
|
367
|
+
async listModels() { return this.nativeControls.listModels(); }
|
|
368
|
+
async selectModel(modelId) { return this.nativeControls.selectModel(modelId); }
|
|
369
|
+
async setPlanMode(enabled) { return this.nativeControls.setPlanMode(enabled); }
|
|
348
370
|
constructor(options) {
|
|
349
371
|
super(options);
|
|
350
372
|
const shouldRestoreInitialSession = options.sessionStartMode !== "new";
|
|
@@ -408,6 +430,8 @@ export class ClaudeCompanionAdapter extends AbstractPtyAdapter {
|
|
|
408
430
|
}
|
|
409
431
|
}
|
|
410
432
|
async sendInput(text) {
|
|
433
|
+
if (this.terminalControl.active)
|
|
434
|
+
throw new Error("A Claude control operation is in progress.");
|
|
411
435
|
if (!this.pty) {
|
|
412
436
|
throw new Error("claude adapter is not running.");
|
|
413
437
|
}
|
|
@@ -452,6 +476,8 @@ export class ClaudeCompanionAdapter extends AbstractPtyAdapter {
|
|
|
452
476
|
return listClaudeResumeSessions(this.options.cwd, limit);
|
|
453
477
|
}
|
|
454
478
|
async resumeSession(sessionId) {
|
|
479
|
+
if (this.terminalControl.active)
|
|
480
|
+
throw new Error("A CLI control operation is in progress. Wait for it to finish before switching sessions.");
|
|
455
481
|
if (!this.pty) {
|
|
456
482
|
throw new Error("Claude is not running yet.");
|
|
457
483
|
}
|
|
@@ -548,6 +574,7 @@ export class ClaudeCompanionAdapter extends AbstractPtyAdapter {
|
|
|
548
574
|
return findClaudeResumeSessionFile(this.options.cwd, sessionId);
|
|
549
575
|
}
|
|
550
576
|
async interrupt() {
|
|
577
|
+
this.terminalControl.cancel("The remote control operation was interrupted.");
|
|
551
578
|
if (!this.pty) {
|
|
552
579
|
return false;
|
|
553
580
|
}
|
|
@@ -578,6 +605,8 @@ export class ClaudeCompanionAdapter extends AbstractPtyAdapter {
|
|
|
578
605
|
return true;
|
|
579
606
|
}
|
|
580
607
|
async reset() {
|
|
608
|
+
if (this.terminalControl.active)
|
|
609
|
+
throw new Error("A CLI control operation is in progress. Wait for it to finish before switching sessions.");
|
|
581
610
|
this.rejectPendingResume(new Error("Claude was reset during session switching."));
|
|
582
611
|
this.clearWechatWorkingNotice(true);
|
|
583
612
|
this.pendingCliApprovalHints = null;
|
|
@@ -647,6 +676,7 @@ export class ClaudeCompanionAdapter extends AbstractPtyAdapter {
|
|
|
647
676
|
return ok ? 1 : 0;
|
|
648
677
|
}
|
|
649
678
|
async dispose() {
|
|
679
|
+
this.terminalControl.cancel("Claude is shutting down.");
|
|
650
680
|
this.rejectPendingResume(new Error("Claude is shutting down during session switching."));
|
|
651
681
|
this.detachLocalTerminal();
|
|
652
682
|
this.clearWechatWorkingNotice(true);
|
|
@@ -676,6 +706,7 @@ export class ClaudeCompanionAdapter extends AbstractPtyAdapter {
|
|
|
676
706
|
this.startHookHealthCheck();
|
|
677
707
|
}
|
|
678
708
|
handleData(rawText) {
|
|
709
|
+
this.terminalControl.feed(rawText);
|
|
679
710
|
this.renderLocalOutput(rawText);
|
|
680
711
|
const text = normalizeOutput(rawText);
|
|
681
712
|
if (!text) {
|
|
@@ -747,6 +778,7 @@ export class ClaudeCompanionAdapter extends AbstractPtyAdapter {
|
|
|
747
778
|
return true;
|
|
748
779
|
}
|
|
749
780
|
handleExit(exitCode) {
|
|
781
|
+
this.terminalControl.cancel("Claude exited during the control operation.");
|
|
750
782
|
this.detachLocalTerminal();
|
|
751
783
|
this.clearWechatWorkingNotice(true);
|
|
752
784
|
this.clearRemoteSubmitRetry();
|
|
@@ -903,6 +935,7 @@ export class ClaudeCompanionAdapter extends AbstractPtyAdapter {
|
|
|
903
935
|
}
|
|
904
936
|
this.localTerminalInputListener = (chunk) => {
|
|
905
937
|
const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
938
|
+
this.terminalControl.localInput(text);
|
|
906
939
|
this.writeToPty(text);
|
|
907
940
|
};
|
|
908
941
|
process.stdin.on("data", this.localTerminalInputListener);
|
|
@@ -931,10 +964,11 @@ export class ClaudeCompanionAdapter extends AbstractPtyAdapter {
|
|
|
931
964
|
}
|
|
932
965
|
}
|
|
933
966
|
resizePtyToTerminal() {
|
|
934
|
-
if (!this.pty
|
|
967
|
+
if (!this.pty) {
|
|
935
968
|
return;
|
|
936
969
|
}
|
|
937
970
|
try {
|
|
971
|
+
this.terminalControl.resize(process.stdout.columns || DEFAULT_COLS, process.stdout.rows || DEFAULT_ROWS);
|
|
938
972
|
this.pty.resize?.(process.stdout.columns || DEFAULT_COLS, process.stdout.rows || DEFAULT_ROWS);
|
|
939
973
|
}
|
|
940
974
|
catch {
|
|
@@ -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";
|
|
@@ -7,7 +13,8 @@ import { normalizeOutput, nowIso, summarizeOutput, truncatePreview, } from "../c
|
|
|
7
13
|
import { AbstractPtyAdapter } from "./bridge-adapters.core.js";
|
|
8
14
|
import { killProcessTreeSync } from "./bridge-process-reaper.js";
|
|
9
15
|
import * as shared from "./bridge-adapters.shared.js";
|
|
10
|
-
import { requestCodexVisibleThreadSwitch } from "../companion/codex-visible-client-link.js";
|
|
16
|
+
import { requestCodexVisibleClientShutdown, requestCodexVisibleThreadSwitch, } from "../companion/codex-visible-client-link.js";
|
|
17
|
+
import { readLocalCompanionEndpoint } from "../companion/local-companion-link.js";
|
|
11
18
|
import { ensureWorkspaceChannelDir } from "../wechat/channel-config.js";
|
|
12
19
|
import { CODEX_REMOTE_AUTH_TOKEN_ENV, LOCAL_CLIENT_PROTOCOL_VERSION, } from "../runtime/runtime-types.js";
|
|
13
20
|
export function parseCodexCliVersion(output) {
|
|
@@ -21,7 +28,7 @@ export function isCodexVersionInCompatibilityRange(version) {
|
|
|
21
28
|
}
|
|
22
29
|
const major = parts[0] ?? -1;
|
|
23
30
|
const minor = parts[1] ?? -1;
|
|
24
|
-
return major === 0 && minor >= 149 && minor <=
|
|
31
|
+
return major === 0 && minor >= 149 && minor <= 155;
|
|
25
32
|
}
|
|
26
33
|
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;
|
|
27
34
|
const CODEX_LOCAL_THREAD_ANNOUNCE_SETTLE_MS = 150;
|
|
@@ -78,6 +85,7 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
|
|
|
78
85
|
appServerAuthToken = null;
|
|
79
86
|
appServerAuthTokenFilePath = null;
|
|
80
87
|
rpcSocket = null;
|
|
88
|
+
lastRpcCloseDetail = "";
|
|
81
89
|
rpcShuttingDown = false;
|
|
82
90
|
rpcReconnectPromise = null;
|
|
83
91
|
cleanPanelExitInProgress = false;
|
|
@@ -86,6 +94,7 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
|
|
|
86
94
|
subscribedThreadIds = new Set();
|
|
87
95
|
pendingThreadSubscriptions = new Map();
|
|
88
96
|
sharedThreadId = null;
|
|
97
|
+
previousCollaborationMode = null;
|
|
89
98
|
announcedThreadId = null;
|
|
90
99
|
localThreadFollowBlockedUntilMs = 0;
|
|
91
100
|
pendingThreadStatusChecks = new Map();
|
|
@@ -182,6 +191,13 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
|
|
|
182
191
|
throw err;
|
|
183
192
|
}
|
|
184
193
|
}
|
|
194
|
+
async prepareVisibleClientSession() {
|
|
195
|
+
if (this.isNativePanelMode() || this.sharedThreadId) {
|
|
196
|
+
return Boolean(this.sharedThreadId);
|
|
197
|
+
}
|
|
198
|
+
const threadId = await this.ensureThreadStarted();
|
|
199
|
+
return Boolean(threadId);
|
|
200
|
+
}
|
|
185
201
|
buildSpawnArgs() {
|
|
186
202
|
if (!this.appServerPort) {
|
|
187
203
|
throw new Error("Codex app-server is not ready.");
|
|
@@ -248,6 +264,74 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
|
|
|
248
264
|
}
|
|
249
265
|
await this.resumeVisibleSharedThread(threadId);
|
|
250
266
|
}
|
|
267
|
+
async listModels() {
|
|
268
|
+
if (!this.usesRpcTurnTransport() || !this.sharedThreadId) {
|
|
269
|
+
throw new Error("Codex model selection requires an active app-server thread.");
|
|
270
|
+
}
|
|
271
|
+
const response = await this.sendRpcRequest("model/list", { includeHidden: false });
|
|
272
|
+
const data = isRecord(response) && Array.isArray(response.data) ? response.data : [];
|
|
273
|
+
const current = await this.readCodexThreadSettings();
|
|
274
|
+
const models = data.flatMap((item) => {
|
|
275
|
+
if (!isRecord(item))
|
|
276
|
+
return [];
|
|
277
|
+
const id = typeof item.id === "string" ? item.id : typeof item.model === "string" ? item.model : "";
|
|
278
|
+
if (!id)
|
|
279
|
+
return [];
|
|
280
|
+
const displayName = typeof item.displayName === "string" ? item.displayName : id;
|
|
281
|
+
const efforts = Array.isArray(item.supportedReasoningEfforts)
|
|
282
|
+
? item.supportedReasoningEfforts.filter((value) => typeof value === "string")
|
|
283
|
+
: undefined;
|
|
284
|
+
return [{ id, displayName, isCurrent: id === current.model, supportedReasoningEfforts: efforts }];
|
|
285
|
+
});
|
|
286
|
+
return models;
|
|
287
|
+
}
|
|
288
|
+
async selectModel(modelId) {
|
|
289
|
+
const models = await this.listModels();
|
|
290
|
+
const selected = models.find((model) => model.id === modelId);
|
|
291
|
+
if (!selected)
|
|
292
|
+
throw new Error(`Codex model ${modelId} is not available.`);
|
|
293
|
+
await this.sendRpcRequest("thread/settings/update", { threadId: this.sharedThreadId, model: selected.id });
|
|
294
|
+
return selected;
|
|
295
|
+
}
|
|
296
|
+
async setPlanMode(enabled) {
|
|
297
|
+
if (!this.usesRpcTurnTransport() || !this.sharedThreadId) {
|
|
298
|
+
throw new Error("Codex plan mode requires an active app-server thread.");
|
|
299
|
+
}
|
|
300
|
+
const settings = await this.readCodexThreadSettings();
|
|
301
|
+
if (enabled) {
|
|
302
|
+
const response = await this.sendRpcRequest("collaborationMode/list", {});
|
|
303
|
+
const modes = isRecord(response) && Array.isArray(response.data) ? response.data : [];
|
|
304
|
+
const plan = modes.find((item) => isRecord(item) && (item.mode === "plan" || item.name?.toString().toLowerCase() === "plan"));
|
|
305
|
+
if (!isRecord(plan))
|
|
306
|
+
throw new Error("Codex plan mode is not available in this app-server.");
|
|
307
|
+
this.previousCollaborationMode = isRecord(settings.collaborationMode) ? settings.collaborationMode : null;
|
|
308
|
+
const mode = typeof plan.mode === "string" ? plan.mode : "plan";
|
|
309
|
+
const model = typeof plan.model === "string" ? plan.model : settings.model;
|
|
310
|
+
const effort = plan.reasoning_effort ?? plan.reasoningEffort ?? null;
|
|
311
|
+
await this.sendRpcRequest("thread/settings/update", {
|
|
312
|
+
threadId: this.sharedThreadId,
|
|
313
|
+
collaborationMode: { mode, settings: { model, reasoning_effort: effort, developer_instructions: null } },
|
|
314
|
+
});
|
|
315
|
+
return true;
|
|
316
|
+
}
|
|
317
|
+
const previous = this.previousCollaborationMode ?? {
|
|
318
|
+
mode: "default",
|
|
319
|
+
settings: { model: settings.model, reasoning_effort: settings.effort ?? null, developer_instructions: null },
|
|
320
|
+
};
|
|
321
|
+
await this.sendRpcRequest("thread/settings/update", {
|
|
322
|
+
threadId: this.sharedThreadId,
|
|
323
|
+
collaborationMode: previous,
|
|
324
|
+
});
|
|
325
|
+
this.previousCollaborationMode = null;
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
async readCodexThreadSettings() {
|
|
329
|
+
const response = await this.sendRpcRequest("thread/read", { threadId: this.sharedThreadId, includeTurns: false });
|
|
330
|
+
const thread = isRecord(response) && isRecord(response.thread) ? response.thread : null;
|
|
331
|
+
if (!thread || typeof thread.model !== "string")
|
|
332
|
+
throw new Error("Codex did not return the current thread model.");
|
|
333
|
+
return { model: thread.model, effort: thread.reasoningEffort, collaborationMode: thread.collaborationMode };
|
|
334
|
+
}
|
|
251
335
|
async interrupt() {
|
|
252
336
|
if (this.usesRpcTurnTransport()) {
|
|
253
337
|
return await this.interruptPanelTurn();
|
|
@@ -315,6 +399,7 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
|
|
|
315
399
|
return true;
|
|
316
400
|
}
|
|
317
401
|
async dispose() {
|
|
402
|
+
await this.shutdownVisibleClient();
|
|
318
403
|
this.resetTurnTracking({ preserveThread: false });
|
|
319
404
|
if (this.isEmbeddedCliMode()) {
|
|
320
405
|
this.detachLocalInputForwarding();
|
|
@@ -346,6 +431,25 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
|
|
|
346
431
|
}
|
|
347
432
|
await this.stopAppServer();
|
|
348
433
|
}
|
|
434
|
+
async shutdownVisibleClient() {
|
|
435
|
+
const endpoint = readLocalCompanionEndpoint(this.options.cwd, { adapter: "codex" });
|
|
436
|
+
if (!endpoint?.companionPid ||
|
|
437
|
+
endpoint.instanceId !== this.localClientInstanceId ||
|
|
438
|
+
!endpoint.codexControlPort ||
|
|
439
|
+
!endpoint.codexControlToken) {
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
try {
|
|
443
|
+
await requestCodexVisibleClientShutdown({
|
|
444
|
+
cwd: this.options.cwd,
|
|
445
|
+
instanceId: endpoint.instanceId,
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
catch {
|
|
449
|
+
// The visible client may already be exiting. Its endpoint is cleared
|
|
450
|
+
// by the supervisor when the child process finishes.
|
|
451
|
+
}
|
|
452
|
+
}
|
|
349
453
|
getLocalClientEndpoint() {
|
|
350
454
|
if (!this.isHeadlessRuntimeMode() || !this.appServerPort || !this.appServerAuthToken) {
|
|
351
455
|
return null;
|
|
@@ -1192,8 +1296,8 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
|
|
|
1192
1296
|
socket.addEventListener("message", (event) => {
|
|
1193
1297
|
this.handleRpcMessageData(event.data);
|
|
1194
1298
|
});
|
|
1195
|
-
socket.addEventListener("close", () => {
|
|
1196
|
-
this.handleRpcSocketClosed();
|
|
1299
|
+
socket.addEventListener("close", (event) => {
|
|
1300
|
+
this.handleRpcSocketClosed(event.code, event.reason);
|
|
1197
1301
|
});
|
|
1198
1302
|
}
|
|
1199
1303
|
async disconnectRpcClient() {
|
|
@@ -1231,13 +1335,14 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
|
|
|
1231
1335
|
});
|
|
1232
1336
|
this.rpcShuttingDown = false;
|
|
1233
1337
|
}
|
|
1234
|
-
handleRpcSocketClosed() {
|
|
1338
|
+
handleRpcSocketClosed(code, reason) {
|
|
1235
1339
|
const expectedShutdown = shouldSuppressCodexTransportFatalError({
|
|
1236
1340
|
transportShuttingDown: this.rpcShuttingDown,
|
|
1237
1341
|
shuttingDown: this.shuttingDown,
|
|
1238
1342
|
cleanPanelExitInProgress: this.cleanPanelExitInProgress,
|
|
1239
1343
|
});
|
|
1240
1344
|
this.rpcSocket = null;
|
|
1345
|
+
this.lastRpcCloseDetail = `close_code=${code ?? "unknown"}${reason ? ` reason=${truncatePreview(reason, 240)}` : ""}`;
|
|
1241
1346
|
this.subscribedThreadIds.clear();
|
|
1242
1347
|
this.rejectPendingRpcRequests("Codex websocket connection closed.");
|
|
1243
1348
|
this.rpcShuttingDown = false;
|
|
@@ -1269,7 +1374,7 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
|
|
|
1269
1374
|
const details = this.describeAppServerLog();
|
|
1270
1375
|
this.emit({
|
|
1271
1376
|
type: "fatal_error",
|
|
1272
|
-
message: `codex app-server websocket closed unexpectedly.${details}`,
|
|
1377
|
+
message: `codex app-server websocket closed unexpectedly (${this.lastRpcCloseDetail || "no close details"}).${details}`,
|
|
1273
1378
|
timestamp: nowIso(),
|
|
1274
1379
|
});
|
|
1275
1380
|
this.terminateCodexClient();
|
|
@@ -1307,7 +1412,7 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
|
|
|
1307
1412
|
}
|
|
1308
1413
|
this.emit({
|
|
1309
1414
|
type: "fatal_error",
|
|
1310
|
-
message: `codex app-server websocket closed unexpectedly and could not reconnect: ${lastError}.${details}`,
|
|
1415
|
+
message: `codex app-server websocket closed unexpectedly and could not reconnect (${this.lastRpcCloseDetail || "no close details"}): ${lastError}.${details}`,
|
|
1311
1416
|
timestamp: nowIso(),
|
|
1312
1417
|
});
|
|
1313
1418
|
this.terminateCodexClient();
|
|
@@ -1460,6 +1565,9 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
|
|
|
1460
1565
|
if (typeof thread.parentThreadId === "string") {
|
|
1461
1566
|
throw new Error(`Codex thread ${targetThreadId} is a subagent thread and cannot be resumed.`);
|
|
1462
1567
|
}
|
|
1568
|
+
if (thread.canAcceptDirectInput === false) {
|
|
1569
|
+
throw new Error(`Codex thread ${targetThreadId} is owned by a parent agent and cannot accept direct input.`);
|
|
1570
|
+
}
|
|
1463
1571
|
if (thread.ephemeral === true) {
|
|
1464
1572
|
throw new Error(`Codex thread ${targetThreadId} is ephemeral and cannot be resumed.`);
|
|
1465
1573
|
}
|
|
@@ -1483,6 +1591,9 @@ export class CodexPtyAdapter extends AbstractPtyAdapter {
|
|
|
1483
1591
|
if (typeof thread.parentThreadId === "string") {
|
|
1484
1592
|
throw new Error(`Codex thread ${targetThreadId} is a subagent thread.`);
|
|
1485
1593
|
}
|
|
1594
|
+
if (thread.canAcceptDirectInput === false) {
|
|
1595
|
+
throw new Error(`Codex thread ${targetThreadId} is owned by a parent agent.`);
|
|
1596
|
+
}
|
|
1486
1597
|
if (thread.ephemeral === true) {
|
|
1487
1598
|
throw new Error(`Codex thread ${targetThreadId} is ephemeral.`);
|
|
1488
1599
|
}
|