cli-wechat-bridge 1.1.7 → 1.1.8
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 +448 -434
- package/bin/_run-entry.mjs +23 -5
- package/dist/bridge/adapter-control.js +58 -0
- package/dist/bridge/bridge-adapters.claude.js +29 -1
- package/dist/bridge/bridge-adapters.codex.js +112 -7
- package/dist/bridge/bridge-adapters.core.js +19 -1
- package/dist/bridge/bridge-adapters.opencode.js +150 -1
- package/dist/bridge/bridge-adapters.pi.js +39 -0
- package/dist/bridge/claude-native-controls.js +133 -0
- package/dist/bridge/native-terminal-control.js +253 -0
- package/dist/bridge/wechat-bridge.js +182 -113
- package/dist/companion/codex-remote-client.js +22 -2
- package/dist/companion/codex-visible-client-link.js +101 -0
- package/dist/companion/local-companion-start.js +7 -2
- package/dist/companion/local-companion.js +17 -0
- package/dist/companion/pi-tui-bridge-extension.js +56 -3
- package/dist/core/bridge-control.js +8 -0
- package/dist/core/conversation-routing.js +48 -0
- package/dist/daemon/wechat-daemon.js +120 -74
- package/dist/i18n/messages-en.js +1 -1
- package/dist/i18n/messages-zh.js +1 -1
- package/dist/runtime/legacy-adapter-runtime.js +15 -0
- package/dist/runtime/runtime-types.js +3 -0
- package/dist/utils/version-checker.js +11 -11
- package/dist/wechat/wechat-transport.js +96 -58
- package/package.json +108 -106
- package/scripts/ensure-node-pty-permissions.mjs +73 -73
|
@@ -21,6 +21,8 @@ import { getPendingChannelMessagesFile, getPendingWechatMessagesFile, PendingWec
|
|
|
21
21
|
import { createRuntimeHost, } from "../runtime/create-runtime-host.js";
|
|
22
22
|
import { toChannelInboundMessage } from "../channels/wechat/channel-message.js";
|
|
23
23
|
import { routeBridgeMessage } from "../core/bridge-message-router.js";
|
|
24
|
+
import { clearTurn, InboundConversationContext, resolveConversationTarget, rollbackTurn, tryBeginTurn, } from "../core/conversation-routing.js";
|
|
25
|
+
import { handleAdapterControl, invalidateModelSnapshot } from "../bridge/adapter-control.js";
|
|
24
26
|
import { forwardBridgeEvent } from "../core/bridge-event-forwarder.js";
|
|
25
27
|
import { isDirectModuleRun } from "../core/direct-run.js";
|
|
26
28
|
import { WechatChannelPort } from "../channels/wechat/wechat-channel-port.js";
|
|
@@ -217,8 +219,13 @@ export function parseDaemonSwitchDirective(text) {
|
|
|
217
219
|
remainder: match[2]?.trim() ?? "",
|
|
218
220
|
};
|
|
219
221
|
}
|
|
220
|
-
export function defaultDaemonSessionStartMode(
|
|
221
|
-
|
|
222
|
+
export function defaultDaemonSessionStartMode(_adapter) {
|
|
223
|
+
// Codex 0.155+ persists app-server threads lazily, so resuming a freshly
|
|
224
|
+
// prepared (not yet persisted) thread crashes the visible TUI bootstrap
|
|
225
|
+
// with "no rollout found". Every adapter therefore opens a fresh session
|
|
226
|
+
// by default; explicit --session-start-mode restore and WeChat /resume
|
|
227
|
+
// remain available for deliberate restores of persisted threads.
|
|
228
|
+
return "new";
|
|
222
229
|
}
|
|
223
230
|
export function resolveDaemonSessionStartMode(params) {
|
|
224
231
|
// An explicit --session-start-mode wins over visible-client reuse: the user
|
|
@@ -231,9 +238,6 @@ export function resolveDaemonSessionStartMode(params) {
|
|
|
231
238
|
if (params.reuseExistingVisible && params.visibleConnected) {
|
|
232
239
|
return "restore";
|
|
233
240
|
}
|
|
234
|
-
if (params.adapter === "codex") {
|
|
235
|
-
return "restore";
|
|
236
|
-
}
|
|
237
241
|
if (params.slotCreated) {
|
|
238
242
|
return "new";
|
|
239
243
|
}
|
|
@@ -360,9 +364,9 @@ function openVisibleClient(params) {
|
|
|
360
364
|
const fullArgs = [process.execPath, ...args];
|
|
361
365
|
if (process.platform === "darwin") {
|
|
362
366
|
const cmdLine = fullArgs.map(shellQuotePosix).join(" ");
|
|
363
|
-
const script = `tell application "Terminal"
|
|
364
|
-
activate
|
|
365
|
-
do script "cd ${shellQuotePosix(params.cwd)} && exec ${cmdLine}"
|
|
367
|
+
const script = `tell application "Terminal"
|
|
368
|
+
activate
|
|
369
|
+
do script "cd ${shellQuotePosix(params.cwd)} && exec ${cmdLine}"
|
|
366
370
|
end tell`;
|
|
367
371
|
const child = spawn("osascript", ["-e", script], {
|
|
368
372
|
cwd: params.cwd,
|
|
@@ -555,8 +559,8 @@ class WechatDaemon {
|
|
|
555
559
|
transport;
|
|
556
560
|
channelId;
|
|
557
561
|
wecomTransport;
|
|
558
|
-
|
|
559
|
-
|
|
562
|
+
inboundConversationContext = new InboundConversationContext();
|
|
563
|
+
fallbackConversation;
|
|
560
564
|
slots = new Map();
|
|
561
565
|
// Per-adapter serialization chains for ensureSlot (see ensureSlot comment).
|
|
562
566
|
slotEnsureChains = new Map();
|
|
@@ -579,14 +583,12 @@ class WechatDaemon {
|
|
|
579
583
|
this.transport = params.transport;
|
|
580
584
|
this.channelId = params.channelId ?? "wechat";
|
|
581
585
|
this.wecomTransport = params.wecomTransport ?? null;
|
|
582
|
-
|
|
583
|
-
this.
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
};
|
|
589
|
-
}
|
|
586
|
+
this.fallbackConversation = {
|
|
587
|
+
channelId: this.channelId,
|
|
588
|
+
conversationId: params.authorizedUserId,
|
|
589
|
+
recipientId: params.authorizedUserId,
|
|
590
|
+
metadata: this.channelId === "wecom" ? { chatType: "direct" } : undefined,
|
|
591
|
+
};
|
|
590
592
|
this.pendingWechatMessages = new PendingWechatMessageStore(this.channelId === "wechat"
|
|
591
593
|
? getPendingWechatMessagesFile(this.cwd)
|
|
592
594
|
: getPendingChannelMessagesFile(this.cwd, this.channelId));
|
|
@@ -681,17 +683,12 @@ class WechatDaemon {
|
|
|
681
683
|
if (this.shutdownPromise) {
|
|
682
684
|
return;
|
|
683
685
|
}
|
|
684
|
-
this.
|
|
685
|
-
this.handlingInboundMessage = true;
|
|
686
|
-
try {
|
|
686
|
+
await this.inboundConversationContext.run(channelMessage.conversation, async () => {
|
|
687
687
|
if (this.pendingWechatMessages.list().length > 0) {
|
|
688
688
|
await this.flushPendingWechatMessages();
|
|
689
689
|
}
|
|
690
690
|
await this.handleInboundMessage(toLegacyInboundWechatMessage(channelMessage));
|
|
691
|
-
}
|
|
692
|
-
finally {
|
|
693
|
-
this.handlingInboundMessage = false;
|
|
694
|
-
}
|
|
691
|
+
});
|
|
695
692
|
},
|
|
696
693
|
onUnauthorized: async (senderId, chatType) => {
|
|
697
694
|
appendDaemonLog(`wecom_unauthorized: sender=${senderId} chat_type=${chatType}`);
|
|
@@ -828,6 +825,23 @@ class WechatDaemon {
|
|
|
828
825
|
}
|
|
829
826
|
async cleanup() {
|
|
830
827
|
appendDaemonLog("shutdown_started");
|
|
828
|
+
// Stop accepting new channel work and abort an in-flight long poll before
|
|
829
|
+
// waiting for queued sends or adapter disposal. Otherwise Ctrl+C can leave
|
|
830
|
+
// the process alive until the 35-second poll timeout and retain the daemon
|
|
831
|
+
// endpoint for the next startup to clean up.
|
|
832
|
+
try {
|
|
833
|
+
this.transport.stop();
|
|
834
|
+
}
|
|
835
|
+
catch {
|
|
836
|
+
// Best effort shutdown.
|
|
837
|
+
}
|
|
838
|
+
try {
|
|
839
|
+
this.wecomTransport?.stop();
|
|
840
|
+
}
|
|
841
|
+
catch {
|
|
842
|
+
// Best effort shutdown.
|
|
843
|
+
}
|
|
844
|
+
clearDaemonEndpoint();
|
|
831
845
|
for (const slot of this.slots.values()) {
|
|
832
846
|
try {
|
|
833
847
|
await slot.outputBatcher.flushNow();
|
|
@@ -839,12 +853,6 @@ class WechatDaemon {
|
|
|
839
853
|
await this.waitForPendingWechatForwardTasks();
|
|
840
854
|
await this.textSendChain.catch(() => undefined);
|
|
841
855
|
await this.attachmentSendChain.catch(() => undefined);
|
|
842
|
-
try {
|
|
843
|
-
this.wecomTransport?.stop();
|
|
844
|
-
}
|
|
845
|
-
catch {
|
|
846
|
-
// Best effort shutdown.
|
|
847
|
-
}
|
|
848
856
|
for (const slot of this.slots.values()) {
|
|
849
857
|
try {
|
|
850
858
|
await slot.runtime.dispose();
|
|
@@ -966,6 +974,9 @@ class WechatDaemon {
|
|
|
966
974
|
const launch = openVisibleClient({
|
|
967
975
|
adapter,
|
|
968
976
|
cwd: this.cwd,
|
|
977
|
+
// A freshly prepared blank thread is semantically a new session, but
|
|
978
|
+
// the visible client must resume its id from the endpoint instead of
|
|
979
|
+
// creating a second thread of its own.
|
|
969
980
|
sessionStartMode,
|
|
970
981
|
cliArgs: options.cliArgs,
|
|
971
982
|
channelId: this.channelId,
|
|
@@ -1009,6 +1020,9 @@ class WechatDaemon {
|
|
|
1009
1020
|
}
|
|
1010
1021
|
const activated = options.openVisible === false || (visibleConnected && visibleReady);
|
|
1011
1022
|
if (activated) {
|
|
1023
|
+
const previousSlot = this.getActiveSlot();
|
|
1024
|
+
if (previousSlot && this.activeAdapter !== adapter)
|
|
1025
|
+
invalidateModelSnapshot(previousSlot.runtime);
|
|
1012
1026
|
this.activeAdapter = adapter;
|
|
1013
1027
|
}
|
|
1014
1028
|
appendDaemonLog(`switch_adapter: adapter=${adapter} created=${created} opened_visible=${openedVisible} visible_connected=${visibleConnected} visible_ready=${visibleReady} activated=${activated} previous_active=${previousActiveAdapter ?? "(none)"} session_start_mode=${sessionStartMode}`);
|
|
@@ -1039,7 +1053,7 @@ class WechatDaemon {
|
|
|
1039
1053
|
runtime,
|
|
1040
1054
|
controller,
|
|
1041
1055
|
outputBatcher: new OutputBatcher(async (text) => {
|
|
1042
|
-
await this.queueWechatMessage(this.authorizedUserId, prefixDaemonAdapterMessage(adapter, text));
|
|
1056
|
+
await this.queueWechatMessage(this.authorizedUserId, prefixDaemonAdapterMessage(adapter, text), "message", this.resolveSlotOutputTarget(slot));
|
|
1043
1057
|
}),
|
|
1044
1058
|
pendingConfirmations: [],
|
|
1045
1059
|
pendingUserInput: null,
|
|
@@ -1065,7 +1079,7 @@ class WechatDaemon {
|
|
|
1065
1079
|
slot.outputBatcher.clear();
|
|
1066
1080
|
slot.pendingConfirmations = [];
|
|
1067
1081
|
slot.pendingUserInput = null;
|
|
1068
|
-
slot
|
|
1082
|
+
clearTurn(slot);
|
|
1069
1083
|
if (slot.adapter === "codex" || slot.adapter === "claude") {
|
|
1070
1084
|
await slot.runtime.reset();
|
|
1071
1085
|
}
|
|
@@ -1107,6 +1121,10 @@ class WechatDaemon {
|
|
|
1107
1121
|
slot.controller.syncLocalClientEndpoint();
|
|
1108
1122
|
const adapterState = slot.runtime.getState();
|
|
1109
1123
|
const channelPort = this.createWechatChannelPort(slot.adapter);
|
|
1124
|
+
const eventTarget = this.channelId === "wecom"
|
|
1125
|
+
? this.resolveSlotOutputTarget(slot)
|
|
1126
|
+
: this.fallbackConversation;
|
|
1127
|
+
const eventTask = slot.activeTask;
|
|
1110
1128
|
if (slot.pendingConfirmations.length > 0 && !adapterState.pendingApproval) {
|
|
1111
1129
|
slot.pendingConfirmations = [];
|
|
1112
1130
|
}
|
|
@@ -1132,15 +1150,7 @@ class WechatDaemon {
|
|
|
1132
1150
|
appendDaemonLog(`final_reply: adapter=${slot.adapter} text=${truncatePreview(next.text)}`);
|
|
1133
1151
|
this.trackWechatForwardTask(slot.outputBatcher.flushNow().then(async () => {
|
|
1134
1152
|
await channelPort.send({
|
|
1135
|
-
target:
|
|
1136
|
-
? slot.activeConversation ??
|
|
1137
|
-
slot.lastConversation ??
|
|
1138
|
-
this.currentInboundConversation
|
|
1139
|
-
: {
|
|
1140
|
-
channelId: "wechat",
|
|
1141
|
-
conversationId: this.authorizedUserId,
|
|
1142
|
-
recipientId: this.authorizedUserId,
|
|
1143
|
-
},
|
|
1153
|
+
target: eventTarget,
|
|
1144
1154
|
kind: "final_reply",
|
|
1145
1155
|
text: next.text,
|
|
1146
1156
|
adapter: slot.adapter,
|
|
@@ -1158,7 +1168,7 @@ class WechatDaemon {
|
|
|
1158
1168
|
appendDaemonLog(`${slot.adapter}_${next.level}_notice: ${truncatePreview(next.text)}`);
|
|
1159
1169
|
if (shouldForwardBridgeEventToWechat(slot.adapter, next.type, { text: next.text })) {
|
|
1160
1170
|
this.trackWechatForwardTask(slot.outputBatcher.flushNow().then(async () => {
|
|
1161
|
-
await this.queueWechatMessage(this.authorizedUserId, prefixDaemonAdapterMessage(slot.adapter, next.text), "notice");
|
|
1171
|
+
await this.queueWechatMessage(this.authorizedUserId, prefixDaemonAdapterMessage(slot.adapter, next.text), "notice", eventTarget);
|
|
1162
1172
|
}));
|
|
1163
1173
|
}
|
|
1164
1174
|
},
|
|
@@ -1167,7 +1177,7 @@ class WechatDaemon {
|
|
|
1167
1177
|
const pending = toPendingApproval(next);
|
|
1168
1178
|
slot.pendingConfirmations.push(pending);
|
|
1169
1179
|
appendDaemonLog(`approval_required: adapter=${slot.adapter} source=${pending.source} command=${truncatePreview(pending.commandPreview)}`);
|
|
1170
|
-
await this.queueWechatMessage(this.authorizedUserId, prefixDaemonAdapterMessage(slot.adapter, formatApprovalMessage(pending, adapterState)), "approval_required");
|
|
1180
|
+
await this.queueWechatMessage(this.authorizedUserId, prefixDaemonAdapterMessage(slot.adapter, formatApprovalMessage(pending, adapterState)), "approval_required", eventTarget);
|
|
1171
1181
|
}));
|
|
1172
1182
|
},
|
|
1173
1183
|
userInputRequired: (next) => {
|
|
@@ -1175,14 +1185,14 @@ class WechatDaemon {
|
|
|
1175
1185
|
slot.pendingUserInput = pending;
|
|
1176
1186
|
appendDaemonLog(`user_input_required: adapter=${slot.adapter} questions=${pending.questions.length}`);
|
|
1177
1187
|
this.trackWechatForwardTask(slot.outputBatcher.flushNow().then(async () => {
|
|
1178
|
-
await this.queueWechatMessage(this.authorizedUserId, prefixDaemonAdapterMessage(slot.adapter, formatUserInputRequestMessage(pending, adapterState)), "user_input_required");
|
|
1188
|
+
await this.queueWechatMessage(this.authorizedUserId, prefixDaemonAdapterMessage(slot.adapter, formatUserInputRequestMessage(pending, adapterState)), "user_input_required", eventTarget);
|
|
1179
1189
|
}));
|
|
1180
1190
|
},
|
|
1181
1191
|
mirroredUserInput: (next) => {
|
|
1182
1192
|
appendDaemonLog(`mirrored_local_input: adapter=${slot.adapter} text=${truncatePreview(next.text)}`);
|
|
1183
1193
|
if (shouldForwardBridgeEventToWechat(slot.adapter, next.type, { text: next.text })) {
|
|
1184
1194
|
this.trackWechatForwardTask(slot.outputBatcher.flushNow().then(async () => {
|
|
1185
|
-
await this.queueWechatMessage(this.authorizedUserId, prefixDaemonAdapterMessage(slot.adapter, formatMirroredUserInputMessage(slot.adapter, next.text)), "mirrored_user_input");
|
|
1195
|
+
await this.queueWechatMessage(this.authorizedUserId, prefixDaemonAdapterMessage(slot.adapter, formatMirroredUserInputMessage(slot.adapter, next.text)), "mirrored_user_input", eventTarget);
|
|
1186
1196
|
}));
|
|
1187
1197
|
}
|
|
1188
1198
|
},
|
|
@@ -1192,7 +1202,7 @@ class WechatDaemon {
|
|
|
1192
1202
|
appendDaemonLog(`session_switched: adapter=${slot.adapter} session=${next.sessionId} source=${next.source} reason=${next.reason}`);
|
|
1193
1203
|
if (shouldForwardSessionSwitchEvent(next.reason) && shouldForwardBridgeEventToWechat(slot.adapter, next.type)) {
|
|
1194
1204
|
this.trackWechatForwardTask(slot.outputBatcher.flushNow().then(async () => {
|
|
1195
|
-
await this.queueWechatMessage(this.authorizedUserId, prefixDaemonAdapterMessage(slot.adapter, formatSessionSwitchMessage({ adapter: slot.adapter, sessionId: next.sessionId, source: next.source, reason: next.reason })), "session_switched");
|
|
1205
|
+
await this.queueWechatMessage(this.authorizedUserId, prefixDaemonAdapterMessage(slot.adapter, formatSessionSwitchMessage({ adapter: slot.adapter, sessionId: next.sessionId, source: next.source, reason: next.reason })), "session_switched", eventTarget);
|
|
1196
1206
|
}));
|
|
1197
1207
|
}
|
|
1198
1208
|
},
|
|
@@ -1210,7 +1220,7 @@ class WechatDaemon {
|
|
|
1210
1220
|
}
|
|
1211
1221
|
if (shouldForwardSessionSwitchEvent(next.reason) && shouldForwardBridgeEventToWechat(slot.adapter, next.type)) {
|
|
1212
1222
|
this.trackWechatForwardTask(slot.outputBatcher.flushNow().then(async () => {
|
|
1213
|
-
await this.queueWechatMessage(this.authorizedUserId, prefixDaemonAdapterMessage(slot.adapter, formatSessionSwitchMessage({ adapter: slot.adapter, sessionId: next.threadId, source: next.source, reason: next.reason })), "thread_switched");
|
|
1223
|
+
await this.queueWechatMessage(this.authorizedUserId, prefixDaemonAdapterMessage(slot.adapter, formatSessionSwitchMessage({ adapter: slot.adapter, sessionId: next.threadId, source: next.source, reason: next.reason })), "thread_switched", eventTarget);
|
|
1214
1224
|
}));
|
|
1215
1225
|
}
|
|
1216
1226
|
},
|
|
@@ -1218,17 +1228,19 @@ class WechatDaemon {
|
|
|
1218
1228
|
this.trackWechatForwardTask(slot.outputBatcher.flushNow().then(() => {
|
|
1219
1229
|
slot.pendingConfirmations = [];
|
|
1220
1230
|
slot.pendingUserInput = null;
|
|
1221
|
-
|
|
1222
|
-
|
|
1231
|
+
if (eventTask) {
|
|
1232
|
+
clearTurn(slot, eventTask);
|
|
1233
|
+
}
|
|
1223
1234
|
}));
|
|
1224
1235
|
},
|
|
1225
1236
|
taskFailed: (next) => {
|
|
1226
1237
|
this.trackWechatForwardTask(slot.outputBatcher.flushNow().then(async () => {
|
|
1227
1238
|
slot.pendingConfirmations = [];
|
|
1228
1239
|
slot.pendingUserInput = null;
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1240
|
+
if (eventTask) {
|
|
1241
|
+
clearTurn(slot, eventTask);
|
|
1242
|
+
}
|
|
1243
|
+
await this.queueWechatMessage(this.authorizedUserId, prefixDaemonAdapterMessage(slot.adapter, formatTaskFailedMessage(slot.adapter, next.message)), "task_failed", eventTarget);
|
|
1232
1244
|
}));
|
|
1233
1245
|
},
|
|
1234
1246
|
fatalError: (next) => {
|
|
@@ -1236,11 +1248,12 @@ class WechatDaemon {
|
|
|
1236
1248
|
appendDaemonLog(`fatal_error: adapter=${slot.adapter} message=${next.message}`);
|
|
1237
1249
|
slot.pendingConfirmations = [];
|
|
1238
1250
|
slot.pendingUserInput = null;
|
|
1239
|
-
|
|
1240
|
-
|
|
1251
|
+
if (eventTask) {
|
|
1252
|
+
clearTurn(slot, eventTask);
|
|
1253
|
+
}
|
|
1241
1254
|
this.disposeDeadSlot(slot);
|
|
1242
1255
|
this.trackWechatForwardTask(slot.outputBatcher.flushNow().then(async () => {
|
|
1243
|
-
await this.queueWechatMessage(this.authorizedUserId, prefixDaemonAdapterMessage(slot.adapter, formatUserFacingBridgeFatalError(next.message)), "fatal_error");
|
|
1256
|
+
await this.queueWechatMessage(this.authorizedUserId, prefixDaemonAdapterMessage(slot.adapter, formatUserFacingBridgeFatalError(next.message)), "fatal_error", eventTarget);
|
|
1244
1257
|
}));
|
|
1245
1258
|
},
|
|
1246
1259
|
shutdownRequested: (next) => {
|
|
@@ -1252,11 +1265,19 @@ class WechatDaemon {
|
|
|
1252
1265
|
appendDaemonLog(`event_forward_failed: adapter=${slot.adapter} message=${message}`);
|
|
1253
1266
|
});
|
|
1254
1267
|
}
|
|
1268
|
+
resolveSlotOutputTarget(slot) {
|
|
1269
|
+
return resolveConversationTarget({
|
|
1270
|
+
active: slot.activeConversation,
|
|
1271
|
+
last: slot.lastConversation,
|
|
1272
|
+
fallback: this.fallbackConversation,
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1255
1275
|
bindCurrentWecomConversation(slot) {
|
|
1256
|
-
|
|
1276
|
+
const conversation = this.inboundConversationContext.get();
|
|
1277
|
+
if (this.channelId !== "wecom" || !slot || !conversation) {
|
|
1257
1278
|
return;
|
|
1258
1279
|
}
|
|
1259
|
-
slot.lastConversation =
|
|
1280
|
+
slot.lastConversation = conversation;
|
|
1260
1281
|
}
|
|
1261
1282
|
async handleInboundMessage(message) {
|
|
1262
1283
|
if (message.senderId !== this.authorizedUserId) {
|
|
@@ -1363,11 +1384,12 @@ class WechatDaemon {
|
|
|
1363
1384
|
},
|
|
1364
1385
|
});
|
|
1365
1386
|
if (this.channelId === "wecom" &&
|
|
1366
|
-
this.
|
|
1387
|
+
this.inboundConversationContext.get() &&
|
|
1367
1388
|
currentSlot.activeTask &&
|
|
1368
1389
|
currentSlot.activeTask !== previousTask) {
|
|
1369
|
-
|
|
1370
|
-
currentSlot.
|
|
1390
|
+
const conversation = this.inboundConversationContext.get();
|
|
1391
|
+
currentSlot.activeConversation = conversation;
|
|
1392
|
+
currentSlot.lastConversation = conversation;
|
|
1371
1393
|
}
|
|
1372
1394
|
};
|
|
1373
1395
|
const command = parseWechatControlCommand(message.text, {
|
|
@@ -1417,6 +1439,10 @@ class WechatDaemon {
|
|
|
1417
1439
|
}
|
|
1418
1440
|
async handleSystemCommand(message, activeSlot, command) {
|
|
1419
1441
|
switch (command.type) {
|
|
1442
|
+
case "model":
|
|
1443
|
+
case "plan":
|
|
1444
|
+
await this.queueWechatMessage(message.senderId, await handleAdapterControl(activeSlot.runtime, message.senderId, command));
|
|
1445
|
+
return;
|
|
1420
1446
|
case "status":
|
|
1421
1447
|
await this.queueWechatMessage(message.senderId, formatDaemonStatus(this.getStatus()));
|
|
1422
1448
|
return;
|
|
@@ -1440,9 +1466,10 @@ class WechatDaemon {
|
|
|
1440
1466
|
if (command.target) {
|
|
1441
1467
|
await resumeSlot.outputBatcher.flushNow();
|
|
1442
1468
|
}
|
|
1469
|
+
const taskBeforeResume = resumeSlot.activeTask;
|
|
1443
1470
|
const result = await resumeSlot.resumeCoordinator.execute(command.target);
|
|
1444
|
-
if (result.kind === "resumed") {
|
|
1445
|
-
resumeSlot
|
|
1471
|
+
if (result.kind === "resumed" && taskBeforeResume) {
|
|
1472
|
+
clearTurn(resumeSlot, taskBeforeResume);
|
|
1446
1473
|
}
|
|
1447
1474
|
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(resumeSlot.adapter, result.message));
|
|
1448
1475
|
}
|
|
@@ -1581,14 +1608,28 @@ class WechatDaemon {
|
|
|
1581
1608
|
}
|
|
1582
1609
|
async dispatchInboundWechatText(message, slot) {
|
|
1583
1610
|
const preview = formatInboundMessagePreview(message);
|
|
1584
|
-
|
|
1611
|
+
const nextTask = {
|
|
1585
1612
|
startedAt: Date.now(),
|
|
1586
1613
|
inputPreview: truncatePreview(preview, 180),
|
|
1587
1614
|
};
|
|
1615
|
+
const inboundConversation = this.channelId === "wecom"
|
|
1616
|
+
? this.inboundConversationContext.get()
|
|
1617
|
+
: undefined;
|
|
1618
|
+
const lease = tryBeginTurn(slot, nextTask, inboundConversation);
|
|
1619
|
+
if (!lease) {
|
|
1620
|
+
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(slot.adapter, `${slot.adapter} is still working. Wait for the current reply or use /stop.`));
|
|
1621
|
+
return;
|
|
1622
|
+
}
|
|
1588
1623
|
appendDaemonLog(`forwarded_input: adapter=${slot.adapter} text=${truncatePreview(preview)}`);
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1624
|
+
try {
|
|
1625
|
+
await slot.runtime.sendInput(this.channelId === "wecom"
|
|
1626
|
+
? buildWecomInboundPrompt(message.text, message.attachments)
|
|
1627
|
+
: buildWechatInboundPrompt(message.text, message.attachments));
|
|
1628
|
+
}
|
|
1629
|
+
catch (error) {
|
|
1630
|
+
rollbackTurn(slot, lease);
|
|
1631
|
+
throw error;
|
|
1632
|
+
}
|
|
1592
1633
|
}
|
|
1593
1634
|
queueWechatTextAction(action) {
|
|
1594
1635
|
const run = this.textSendChain.then(action);
|
|
@@ -1605,12 +1646,8 @@ class WechatDaemon {
|
|
|
1605
1646
|
const activeSlot = this.getActiveSlot();
|
|
1606
1647
|
const target = targetOverride ??
|
|
1607
1648
|
(senderId === this.authorizedUserId
|
|
1608
|
-
?
|
|
1609
|
-
? this.
|
|
1610
|
-
: null) ??
|
|
1611
|
-
activeSlot?.activeConversation ??
|
|
1612
|
-
activeSlot?.lastConversation ??
|
|
1613
|
-
this.currentInboundConversation
|
|
1649
|
+
? this.inboundConversationContext.get() ??
|
|
1650
|
+
(activeSlot ? this.resolveSlotOutputTarget(activeSlot) : this.fallbackConversation)
|
|
1614
1651
|
: {
|
|
1615
1652
|
channelId: "wecom",
|
|
1616
1653
|
conversationId: senderId,
|
|
@@ -1678,8 +1715,14 @@ class WechatDaemon {
|
|
|
1678
1715
|
return { status: "failed" };
|
|
1679
1716
|
}
|
|
1680
1717
|
queueWechatMessage(senderId, text, context = "message", targetOverride) {
|
|
1718
|
+
const activeSlot = this.getActiveSlot();
|
|
1719
|
+
const queuedTarget = this.channelId === "wecom" && senderId === this.authorizedUserId
|
|
1720
|
+
? targetOverride ??
|
|
1721
|
+
this.inboundConversationContext.get() ??
|
|
1722
|
+
(activeSlot ? this.resolveSlotOutputTarget(activeSlot) : this.fallbackConversation)
|
|
1723
|
+
: targetOverride;
|
|
1681
1724
|
return this.queueWechatTextAction(async () => {
|
|
1682
|
-
const result = await this.sendWechatMessageNow(senderId, text, context,
|
|
1725
|
+
const result = await this.sendWechatMessageNow(senderId, text, context, queuedTarget);
|
|
1683
1726
|
if (result.status === "context_unavailable") {
|
|
1684
1727
|
const pending = this.pendingWechatMessages.enqueue(senderId, text, context, result.target);
|
|
1685
1728
|
if (pending) {
|
|
@@ -2066,6 +2109,9 @@ export async function runDaemon(options) {
|
|
|
2066
2109
|
}
|
|
2067
2110
|
shutdownInProgress = true;
|
|
2068
2111
|
log(`Received ${signal}. Stopping daemon.`);
|
|
2112
|
+
// Remove this process's endpoint synchronously so a new terminal can be
|
|
2113
|
+
// started immediately even if asynchronous adapter cleanup takes time.
|
|
2114
|
+
clearDaemonEndpoint();
|
|
2069
2115
|
void daemon.shutdown().finally(() => process.exit(0));
|
|
2070
2116
|
};
|
|
2071
2117
|
process.on("SIGINT", () => handleSignal("SIGINT"));
|
package/dist/i18n/messages-en.js
CHANGED
|
@@ -11,7 +11,7 @@ export const messages = {
|
|
|
11
11
|
"bridge.fatalError": "Bridge error: {message}",
|
|
12
12
|
"bridge.backlogIgnored": "Ignored {count} backlog message(s) sent before the bridge started (they will not be processed).\nIf you actually sent them after the bridge started, your system clock is likely skewed by more than {graceSeconds}s — sync the clock and restart the bridge.",
|
|
13
13
|
// === Daemon welcome & control ===
|
|
14
|
-
"daemon.welcome": "WeChat Daemon ready.\nCWD: {cwd}\nActive: {adapter}\n\nCommands: /claude, /codex, /opencode, /pi, /stop, /confirm, /deny, /status\n{bindings}\n\nManage: /bind [emoji] cmd, /unbind [emoji], /bindings",
|
|
14
|
+
"daemon.welcome": "WeChat Daemon ready.\nCWD: {cwd}\nActive: {adapter}\n\nCommands: /claude, /codex, /opencode, /pi, /stop, /confirm, /deny, /status, /model, /plan\n{bindings}\n\nManage: /bind [emoji] cmd, /unbind [emoji], /bindings",
|
|
15
15
|
"daemon.noActiveAdapter": "No active terminal is selected. Send /codex, /claude, /opencode, or /pi to start one.",
|
|
16
16
|
"daemon.switchResult.new": "Started a new visible CLI.",
|
|
17
17
|
"daemon.switchResult.reused": "Reused the existing visible CLI.",
|
package/dist/i18n/messages-zh.js
CHANGED
|
@@ -11,7 +11,7 @@ export const messages = {
|
|
|
11
11
|
"bridge.fatalError": "桥接错误: {message}",
|
|
12
12
|
"bridge.backlogIgnored": "已忽略 {count} 条桥接启动前的积压消息(不会自动处理)。\n如果你是在桥接启动后才发送的消息却看到此提示,说明本机系统时钟可能不准(偏差超过 {graceSeconds} 秒),请校准时间后重启桥接。",
|
|
13
13
|
// === Daemon 欢迎 & 控制 ===
|
|
14
|
-
"daemon.welcome": "微信 Daemon 就绪。\n工作目录: {cwd}\n当前: {adapter}\n\n命令: /claude, /codex, /opencode, /pi, /stop, /confirm, /deny, /status\n{bindings}\n\n管理: /bind [表情] 命令, /unbind [表情], /bindings",
|
|
14
|
+
"daemon.welcome": "微信 Daemon 就绪。\n工作目录: {cwd}\n当前: {adapter}\n\n命令: /claude, /codex, /opencode, /pi, /stop, /confirm, /deny, /status, /model, /plan\n{bindings}\n\n管理: /bind [表情] 命令, /unbind [表情], /bindings",
|
|
15
15
|
"daemon.noActiveAdapter": "未选择活跃终端。发送 /codex、/claude、/opencode 或 /pi 启动一个。",
|
|
16
16
|
"daemon.switchResult.new": "已启动新的可见 CLI。",
|
|
17
17
|
"daemon.switchResult.reused": "已复用现有的可见 CLI。",
|
|
@@ -25,6 +25,21 @@ export class LegacyAdapterRuntime {
|
|
|
25
25
|
}
|
|
26
26
|
await this.adapter.createSession();
|
|
27
27
|
}
|
|
28
|
+
async listModels() {
|
|
29
|
+
if (!this.adapter.listModels)
|
|
30
|
+
throw new Error("Model listing is not supported by this companion.");
|
|
31
|
+
return this.adapter.listModels();
|
|
32
|
+
}
|
|
33
|
+
async selectModel(modelId) {
|
|
34
|
+
if (!this.adapter.selectModel)
|
|
35
|
+
throw new Error("Model selection is not supported by this companion.");
|
|
36
|
+
return this.adapter.selectModel(modelId);
|
|
37
|
+
}
|
|
38
|
+
async setPlanMode(enabled) {
|
|
39
|
+
if (!this.adapter.setPlanMode)
|
|
40
|
+
throw new Error("Plan mode is not supported by this companion.");
|
|
41
|
+
return this.adapter.setPlanMode(enabled);
|
|
42
|
+
}
|
|
28
43
|
async interrupt() {
|
|
29
44
|
return await this.adapter.interrupt();
|
|
30
45
|
}
|
|
@@ -3,3 +3,6 @@ export const CODEX_REMOTE_AUTH_TOKEN_ENV = "WECHAT_BRIDGE_CODEX_REMOTE_AUTH_TOKE
|
|
|
3
3
|
export function hasLocalClientEndpointProvider(runtime) {
|
|
4
4
|
return typeof runtime.getLocalClientEndpoint === "function";
|
|
5
5
|
}
|
|
6
|
+
export function hasVisibleClientSessionPreparer(runtime) {
|
|
7
|
+
return typeof runtime.prepareVisibleClientSession === "function";
|
|
8
|
+
}
|
|
@@ -179,16 +179,16 @@ export async function checkForUpdate(forceCheck = false) {
|
|
|
179
179
|
*/
|
|
180
180
|
export function formatUpdateMessage(versionInfo) {
|
|
181
181
|
const { current, latest } = versionInfo;
|
|
182
|
-
return `
|
|
183
|
-
[Update Available] Version ${latest} is available (current: ${current})
|
|
184
|
-
|
|
185
|
-
Update instructions:
|
|
186
|
-
cd CLI-WeChat-Bridge
|
|
187
|
-
git pull
|
|
188
|
-
bun install
|
|
189
|
-
npm install -g .
|
|
190
|
-
|
|
191
|
-
For more information:
|
|
192
|
-
https://github.com/UNLINEARITY/CLI-WeChat-Bridge/releases
|
|
182
|
+
return `
|
|
183
|
+
[Update Available] Version ${latest} is available (current: ${current})
|
|
184
|
+
|
|
185
|
+
Update instructions:
|
|
186
|
+
cd CLI-WeChat-Bridge
|
|
187
|
+
git pull
|
|
188
|
+
bun install
|
|
189
|
+
npm install -g .
|
|
190
|
+
|
|
191
|
+
For more information:
|
|
192
|
+
https://github.com/UNLINEARITY/CLI-WeChat-Bridge/releases
|
|
193
193
|
`;
|
|
194
194
|
}
|