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
|
@@ -12,6 +12,8 @@ import { getProcessRecordByPid, reapOrphanedOpencodeProcesses, reapPeerBridgePro
|
|
|
12
12
|
import { createRuntimeHost } from "../runtime/create-runtime-host.js";
|
|
13
13
|
import { toChannelInboundMessage } from "../channels/wechat/channel-message.js";
|
|
14
14
|
import { routeBridgeMessage } from "../core/bridge-message-router.js";
|
|
15
|
+
import { clearTurn, resolveConversationTarget, rollbackTurn, tryBeginTurn, } from "../core/conversation-routing.js";
|
|
16
|
+
import { handleAdapterControl } from "./adapter-control.js";
|
|
15
17
|
import { forwardBridgeEvent } from "../core/bridge-event-forwarder.js";
|
|
16
18
|
import { isDirectModuleRun } from "../core/direct-run.js";
|
|
17
19
|
import { WechatChannelPort } from "../channels/wechat/wechat-channel-port.js";
|
|
@@ -361,16 +363,17 @@ async function main() {
|
|
|
361
363
|
let textSendChain = Promise.resolve();
|
|
362
364
|
let attachmentSendChain = Promise.resolve();
|
|
363
365
|
const pendingWechatForwardTasks = new Set();
|
|
364
|
-
|
|
365
|
-
let currentWecomConversation = null;
|
|
366
|
-
let activeWecomConversation = null;
|
|
367
|
-
let lastWecomConversation = {
|
|
366
|
+
const defaultWecomConversation = {
|
|
368
367
|
channelId: "wecom",
|
|
369
368
|
accountId: wecomAccount?.botId,
|
|
370
369
|
conversationId: credentials.userId,
|
|
371
370
|
recipientId: credentials.userId,
|
|
372
371
|
metadata: { chatType: "direct" },
|
|
373
372
|
};
|
|
373
|
+
const turnState = {
|
|
374
|
+
activeTask: null,
|
|
375
|
+
lastConversation: defaultWecomConversation,
|
|
376
|
+
};
|
|
374
377
|
const deferredInboundMessages = [];
|
|
375
378
|
let drainingDeferredInboundMessages = false;
|
|
376
379
|
let consecutivePollFailures = 0;
|
|
@@ -389,9 +392,11 @@ async function main() {
|
|
|
389
392
|
if (options.channelId === "wecom") {
|
|
390
393
|
const target = targetOverride ??
|
|
391
394
|
(senderId === credentials.userId
|
|
392
|
-
?
|
|
393
|
-
|
|
394
|
-
|
|
395
|
+
? resolveConversationTarget({
|
|
396
|
+
active: turnState.activeConversation,
|
|
397
|
+
last: turnState.lastConversation,
|
|
398
|
+
fallback: defaultWecomConversation,
|
|
399
|
+
})
|
|
395
400
|
: {
|
|
396
401
|
channelId: "wecom",
|
|
397
402
|
accountId: wecomAccount?.botId,
|
|
@@ -469,8 +474,15 @@ async function main() {
|
|
|
469
474
|
return { status: "failed" };
|
|
470
475
|
};
|
|
471
476
|
const queueWechatMessage = (senderId, text, context = "message", targetOverride) => {
|
|
477
|
+
const queuedTarget = options.channelId === "wecom" && senderId === credentials.userId
|
|
478
|
+
? targetOverride ?? resolveConversationTarget({
|
|
479
|
+
active: turnState.activeConversation,
|
|
480
|
+
last: turnState.lastConversation,
|
|
481
|
+
fallback: defaultWecomConversation,
|
|
482
|
+
})
|
|
483
|
+
: targetOverride;
|
|
472
484
|
return queueWechatTextAction(async () => {
|
|
473
|
-
const result = await sendWechatMessageNow(senderId, text, context,
|
|
485
|
+
const result = await sendWechatMessageNow(senderId, text, context, queuedTarget);
|
|
474
486
|
if (result.status === "context_unavailable") {
|
|
475
487
|
const pending = pendingWechatMessages.enqueue(senderId, text, context, result.target);
|
|
476
488
|
if (pending) {
|
|
@@ -514,7 +526,13 @@ async function main() {
|
|
|
514
526
|
}
|
|
515
527
|
};
|
|
516
528
|
const outputBatcher = new OutputBatcher(async (text) => {
|
|
517
|
-
await queueWechatMessage(stateStore.getState().authorizedUserId, text
|
|
529
|
+
await queueWechatMessage(stateStore.getState().authorizedUserId, text, "message", options.channelId === "wecom"
|
|
530
|
+
? resolveConversationTarget({
|
|
531
|
+
active: turnState.activeConversation,
|
|
532
|
+
last: turnState.lastConversation,
|
|
533
|
+
fallback: defaultWecomConversation,
|
|
534
|
+
})
|
|
535
|
+
: undefined);
|
|
518
536
|
});
|
|
519
537
|
const maybeDrainDeferredInboundMessages = async () => {
|
|
520
538
|
if (drainingDeferredInboundMessages || !ensureRuntimeOwnership()) {
|
|
@@ -529,7 +547,7 @@ async function main() {
|
|
|
529
547
|
hasPendingConfirmation: Boolean(stateStore.getState().pendingConfirmation),
|
|
530
548
|
hasPendingUserInput: Boolean(stateStore.getState().pendingUserInput),
|
|
531
549
|
hasPendingApproval: Boolean(adapterState.pendingApproval),
|
|
532
|
-
hasActiveTask: Boolean(activeTask),
|
|
550
|
+
hasActiveTask: Boolean(turnState.activeTask),
|
|
533
551
|
})) {
|
|
534
552
|
return;
|
|
535
553
|
}
|
|
@@ -540,17 +558,25 @@ async function main() {
|
|
|
540
558
|
drainingDeferredInboundMessages = true;
|
|
541
559
|
try {
|
|
542
560
|
stateStore.appendLog(`draining_deferred_inbound_input: remaining=${deferredInboundMessages.length} text=${truncatePreview(nextDeferred.message.text)}`);
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
561
|
+
const nextTask = createActiveTask(nextDeferred.message);
|
|
562
|
+
const lease = tryBeginTurn(turnState, nextTask, nextDeferred.channelMessage?.conversation);
|
|
563
|
+
if (!lease) {
|
|
564
|
+
deferredInboundMessages.unshift(nextDeferred);
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
try {
|
|
568
|
+
await dispatchInboundWechatText({
|
|
569
|
+
message: nextDeferred.message,
|
|
570
|
+
options,
|
|
571
|
+
stateStore,
|
|
572
|
+
adapter,
|
|
573
|
+
activeTask: nextTask,
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
catch (error) {
|
|
577
|
+
rollbackTurn(turnState, lease);
|
|
578
|
+
throw error;
|
|
546
579
|
}
|
|
547
|
-
const nextTask = await dispatchInboundWechatText({
|
|
548
|
-
message: nextDeferred.message,
|
|
549
|
-
options,
|
|
550
|
-
stateStore,
|
|
551
|
-
adapter,
|
|
552
|
-
});
|
|
553
|
-
activeTask = nextTask;
|
|
554
580
|
}
|
|
555
581
|
catch (err) {
|
|
556
582
|
const errorText = err instanceof Error ? err.message : String(err);
|
|
@@ -565,7 +591,7 @@ async function main() {
|
|
|
565
591
|
adapter: options.adapter,
|
|
566
592
|
cwd: options.cwd,
|
|
567
593
|
errorText,
|
|
568
|
-
}), "inbound_error");
|
|
594
|
+
}), "inbound_error", nextDeferred.channelMessage?.conversation);
|
|
569
595
|
}
|
|
570
596
|
finally {
|
|
571
597
|
drainingDeferredInboundMessages = false;
|
|
@@ -715,9 +741,11 @@ async function main() {
|
|
|
715
741
|
queueWechatMessage,
|
|
716
742
|
trackWechatForwardTask,
|
|
717
743
|
maybeDrainDeferredInboundMessages,
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
744
|
+
getActiveTask: () => turnState.activeTask,
|
|
745
|
+
clearActiveTask: (expectedTask) => {
|
|
746
|
+
if (expectedTask) {
|
|
747
|
+
clearTurn(turnState, expectedTask);
|
|
748
|
+
}
|
|
721
749
|
},
|
|
722
750
|
syncSharedSessionState: () => {
|
|
723
751
|
syncSharedSessionState(stateStore, adapter);
|
|
@@ -727,7 +755,11 @@ async function main() {
|
|
|
727
755
|
},
|
|
728
756
|
requestShutdown,
|
|
729
757
|
wecomTransport,
|
|
730
|
-
getWecomTarget: () =>
|
|
758
|
+
getWecomTarget: () => resolveConversationTarget({
|
|
759
|
+
active: turnState.activeConversation,
|
|
760
|
+
last: turnState.lastConversation,
|
|
761
|
+
fallback: defaultWecomConversation,
|
|
762
|
+
}),
|
|
731
763
|
});
|
|
732
764
|
await adapter.start();
|
|
733
765
|
if (!ensureRuntimeOwnership()) {
|
|
@@ -742,59 +774,73 @@ async function main() {
|
|
|
742
774
|
if (shutdownPromise || !ensureRuntimeOwnership()) {
|
|
743
775
|
return;
|
|
744
776
|
}
|
|
745
|
-
|
|
746
|
-
|
|
777
|
+
turnState.lastConversation = channelMessage.conversation;
|
|
778
|
+
const queueInboundReply = (senderId, text, context) => queueWechatMessage(senderId, text, context, channelMessage.conversation);
|
|
779
|
+
if (pendingWechatMessages.list().length > 0) {
|
|
780
|
+
await flushPendingWechatMessages();
|
|
781
|
+
}
|
|
782
|
+
const message = toLegacyInboundWechatMessage(channelMessage);
|
|
783
|
+
const taskAtMessageStart = turnState.activeTask;
|
|
784
|
+
stateStore.touchActivity(message.createdAt);
|
|
747
785
|
try {
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
786
|
+
await handleInboundMessage({
|
|
787
|
+
message,
|
|
788
|
+
channelMessage,
|
|
789
|
+
options,
|
|
790
|
+
stateStore,
|
|
791
|
+
adapter,
|
|
792
|
+
resumeCoordinator,
|
|
793
|
+
queueWechatMessage: queueInboundReply,
|
|
794
|
+
outputBatcher,
|
|
795
|
+
clearActiveTask: () => {
|
|
796
|
+
if (taskAtMessageStart) {
|
|
797
|
+
clearTurn(turnState, taskAtMessageStart);
|
|
798
|
+
}
|
|
799
|
+
},
|
|
800
|
+
dispatchInboundText: async () => {
|
|
801
|
+
const nextActiveTask = createActiveTask(message);
|
|
802
|
+
const lease = tryBeginTurn(turnState, nextActiveTask, channelMessage.conversation);
|
|
803
|
+
if (!lease) {
|
|
804
|
+
await queueInboundReply(message.senderId, `${options.adapter} is still working. Wait for the current reply or use /stop.`);
|
|
805
|
+
return null;
|
|
806
|
+
}
|
|
807
|
+
try {
|
|
808
|
+
await dispatchInboundWechatText({
|
|
809
|
+
message,
|
|
810
|
+
options,
|
|
811
|
+
stateStore,
|
|
812
|
+
adapter,
|
|
813
|
+
activeTask: nextActiveTask,
|
|
772
814
|
});
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
activeTask = nextTask;
|
|
790
|
-
activeWecomConversation = channelMessage.conversation;
|
|
791
|
-
}
|
|
792
|
-
syncSharedSessionState(stateStore, adapter);
|
|
793
|
-
await maybeDrainDeferredInboundMessages();
|
|
815
|
+
return nextActiveTask;
|
|
816
|
+
}
|
|
817
|
+
catch (error) {
|
|
818
|
+
rollbackTurn(turnState, lease);
|
|
819
|
+
throw error;
|
|
820
|
+
}
|
|
821
|
+
},
|
|
822
|
+
deferInboundMessage: async (nextMessage) => {
|
|
823
|
+
deferredInboundMessages.push({
|
|
824
|
+
message: nextMessage,
|
|
825
|
+
channelMessage,
|
|
826
|
+
});
|
|
827
|
+
stateStore.appendLog(`deferred_inbound_input: position=${deferredInboundMessages.length} text=${truncatePreview(nextMessage.text)}`);
|
|
828
|
+
await queueInboundReply(nextMessage.senderId, formatDeferredCodexInboundQueueMessage(deferredInboundMessages.length));
|
|
829
|
+
},
|
|
830
|
+
});
|
|
794
831
|
}
|
|
795
|
-
|
|
796
|
-
|
|
832
|
+
catch (error) {
|
|
833
|
+
const errorText = error instanceof Error ? error.message : String(error);
|
|
834
|
+
logError(errorText);
|
|
835
|
+
stateStore.appendLog(`inbound_error: ${errorText}`);
|
|
836
|
+
await queueInboundReply(message.senderId, formatUserFacingInboundError({
|
|
837
|
+
adapter: options.adapter,
|
|
838
|
+
cwd: options.cwd,
|
|
839
|
+
errorText,
|
|
840
|
+
}), "inbound_error");
|
|
797
841
|
}
|
|
842
|
+
syncSharedSessionState(stateStore, adapter);
|
|
843
|
+
await maybeDrainDeferredInboundMessages();
|
|
798
844
|
},
|
|
799
845
|
onUnauthorized: async (senderId, chatType) => {
|
|
800
846
|
stateStore.appendLog(`wecom_unauthorized: sender=${senderId} chat_type=${chatType}`);
|
|
@@ -910,10 +956,10 @@ async function main() {
|
|
|
910
956
|
if (!ensureRuntimeOwnership()) {
|
|
911
957
|
break;
|
|
912
958
|
}
|
|
959
|
+
const taskAtMessageStart = turnState.activeTask;
|
|
913
960
|
stateStore.touchActivity(message.createdAt);
|
|
914
|
-
let nextTask = null;
|
|
915
961
|
try {
|
|
916
|
-
|
|
962
|
+
await handleInboundMessage({
|
|
917
963
|
message,
|
|
918
964
|
options,
|
|
919
965
|
stateStore,
|
|
@@ -922,7 +968,31 @@ async function main() {
|
|
|
922
968
|
queueWechatMessage,
|
|
923
969
|
outputBatcher,
|
|
924
970
|
clearActiveTask: () => {
|
|
925
|
-
|
|
971
|
+
if (taskAtMessageStart) {
|
|
972
|
+
clearTurn(turnState, taskAtMessageStart);
|
|
973
|
+
}
|
|
974
|
+
},
|
|
975
|
+
dispatchInboundText: async () => {
|
|
976
|
+
const nextActiveTask = createActiveTask(message);
|
|
977
|
+
const lease = tryBeginTurn(turnState, nextActiveTask);
|
|
978
|
+
if (!lease) {
|
|
979
|
+
await queueWechatMessage(message.senderId, `${options.adapter} is still working. Wait for the current reply or use /stop.`);
|
|
980
|
+
return null;
|
|
981
|
+
}
|
|
982
|
+
try {
|
|
983
|
+
await dispatchInboundWechatText({
|
|
984
|
+
message,
|
|
985
|
+
options,
|
|
986
|
+
stateStore,
|
|
987
|
+
adapter,
|
|
988
|
+
activeTask: nextActiveTask,
|
|
989
|
+
});
|
|
990
|
+
return nextActiveTask;
|
|
991
|
+
}
|
|
992
|
+
catch (error) {
|
|
993
|
+
rollbackTurn(turnState, lease);
|
|
994
|
+
throw error;
|
|
995
|
+
}
|
|
926
996
|
},
|
|
927
997
|
deferInboundMessage: async (nextMessage) => {
|
|
928
998
|
deferredInboundMessages.push({
|
|
@@ -943,9 +1013,6 @@ async function main() {
|
|
|
943
1013
|
errorText,
|
|
944
1014
|
}), "inbound_error");
|
|
945
1015
|
}
|
|
946
|
-
if (nextTask) {
|
|
947
|
-
activeTask = nextTask;
|
|
948
|
-
}
|
|
949
1016
|
syncSharedSessionState(stateStore, adapter);
|
|
950
1017
|
await maybeDrainDeferredInboundMessages();
|
|
951
1018
|
}
|
|
@@ -980,7 +1047,7 @@ function syncSharedSessionState(stateStore, adapter) {
|
|
|
980
1047
|
}
|
|
981
1048
|
}
|
|
982
1049
|
function wireAdapterEvents(params) {
|
|
983
|
-
const { adapter, resumeCoordinator, options, transport, stateStore, outputBatcher, queueWechatAttachmentAction, queueWechatMessage, trackWechatForwardTask, maybeDrainDeferredInboundMessages, clearActiveTask, syncSharedSessionState, syncLocalClientEndpoint, requestShutdown, wecomTransport, getWecomTarget, } = params;
|
|
1050
|
+
const { adapter, resumeCoordinator, options, transport, stateStore, outputBatcher, queueWechatAttachmentAction, queueWechatMessage, trackWechatForwardTask, maybeDrainDeferredInboundMessages, getActiveTask, clearActiveTask, syncSharedSessionState, syncLocalClientEndpoint, requestShutdown, wecomTransport, getWecomTarget, } = params;
|
|
984
1051
|
const channelPort = options.channelId === "wecom"
|
|
985
1052
|
? new WecomChannelPort({
|
|
986
1053
|
transport: wecomTransport,
|
|
@@ -1016,6 +1083,14 @@ function wireAdapterEvents(params) {
|
|
|
1016
1083
|
stateStore.clearPendingUserInput();
|
|
1017
1084
|
}
|
|
1018
1085
|
const authorizedUserId = stateStore.getState().authorizedUserId;
|
|
1086
|
+
const eventTarget = options.channelId === "wecom"
|
|
1087
|
+
? getWecomTarget()
|
|
1088
|
+
: {
|
|
1089
|
+
channelId: "wechat",
|
|
1090
|
+
conversationId: authorizedUserId,
|
|
1091
|
+
recipientId: authorizedUserId,
|
|
1092
|
+
};
|
|
1093
|
+
const eventTask = getActiveTask();
|
|
1019
1094
|
eventForwardChain = eventForwardChain
|
|
1020
1095
|
.then(() => forwardBridgeEvent(event, {
|
|
1021
1096
|
stdout: (next) => {
|
|
@@ -1033,13 +1108,7 @@ function wireAdapterEvents(params) {
|
|
|
1033
1108
|
stateStore.appendLog(`final_reply: ${truncatePreview(next.text)}`);
|
|
1034
1109
|
trackWechatForwardTask(outputBatcher.flushNow().then(async () => {
|
|
1035
1110
|
await channelPort.send({
|
|
1036
|
-
target:
|
|
1037
|
-
? getWecomTarget()
|
|
1038
|
-
: {
|
|
1039
|
-
channelId: "wechat",
|
|
1040
|
-
conversationId: authorizedUserId,
|
|
1041
|
-
recipientId: authorizedUserId,
|
|
1042
|
-
},
|
|
1111
|
+
target: eventTarget,
|
|
1043
1112
|
kind: "final_reply",
|
|
1044
1113
|
text: next.text,
|
|
1045
1114
|
adapter: options.adapter,
|
|
@@ -1057,7 +1126,7 @@ function wireAdapterEvents(params) {
|
|
|
1057
1126
|
stateStore.appendLog(`${next.level}_notice: ${truncatePreview(next.text)}`);
|
|
1058
1127
|
if (shouldForwardBridgeEventToWechat(options.adapter, next.type, { text: next.text })) {
|
|
1059
1128
|
trackWechatForwardTask(outputBatcher.flushNow().then(async () => {
|
|
1060
|
-
await queueWechatMessage(authorizedUserId, next.text, "notice");
|
|
1129
|
+
await queueWechatMessage(authorizedUserId, next.text, "notice", eventTarget);
|
|
1061
1130
|
}));
|
|
1062
1131
|
}
|
|
1063
1132
|
},
|
|
@@ -1069,7 +1138,7 @@ function wireAdapterEvents(params) {
|
|
|
1069
1138
|
trackWechatForwardTask((async () => {
|
|
1070
1139
|
await queueWechatMessage(authorizedUserId, options.channelId === "wecom"
|
|
1071
1140
|
? `Processing: ${thinkingPreview}`
|
|
1072
|
-
: `思考: ${thinkingPreview}`, "thinking");
|
|
1141
|
+
: `思考: ${thinkingPreview}`, "thinking", eventTarget);
|
|
1073
1142
|
})());
|
|
1074
1143
|
}
|
|
1075
1144
|
}
|
|
@@ -1079,7 +1148,7 @@ function wireAdapterEvents(params) {
|
|
|
1079
1148
|
const pending = toPendingApproval(next.request);
|
|
1080
1149
|
stateStore.setPendingConfirmation(pending);
|
|
1081
1150
|
stateStore.appendLog(`Approval requested (${pending.source}): ${pending.commandPreview}`);
|
|
1082
|
-
await queueWechatMessage(authorizedUserId, formatApprovalMessage(pending, adapterState), "approval_required");
|
|
1151
|
+
await queueWechatMessage(authorizedUserId, formatApprovalMessage(pending, adapterState), "approval_required", eventTarget);
|
|
1083
1152
|
}));
|
|
1084
1153
|
},
|
|
1085
1154
|
userInputRequired: (next) => {
|
|
@@ -1087,14 +1156,14 @@ function wireAdapterEvents(params) {
|
|
|
1087
1156
|
const pending = toPendingUserInput(next.request);
|
|
1088
1157
|
stateStore.setPendingUserInput(pending);
|
|
1089
1158
|
stateStore.appendLog(`User input requested: questions=${pending.questions.length}`);
|
|
1090
|
-
await queueWechatMessage(authorizedUserId, formatUserInputRequestMessage(pending, adapterState), "user_input_required");
|
|
1159
|
+
await queueWechatMessage(authorizedUserId, formatUserInputRequestMessage(pending, adapterState), "user_input_required", eventTarget);
|
|
1091
1160
|
}));
|
|
1092
1161
|
},
|
|
1093
1162
|
mirroredUserInput: (next) => {
|
|
1094
1163
|
stateStore.appendLog(`mirrored_local_input: ${truncatePreview(next.text)}`);
|
|
1095
1164
|
if (shouldForwardBridgeEventToWechat(options.adapter, next.type, { text: next.text })) {
|
|
1096
1165
|
trackWechatForwardTask(outputBatcher.flushNow().then(async () => {
|
|
1097
|
-
await queueWechatMessage(authorizedUserId, formatMirroredUserInputMessage(options.adapter, next.text), "mirrored_user_input");
|
|
1166
|
+
await queueWechatMessage(authorizedUserId, formatMirroredUserInputMessage(options.adapter, next.text), "mirrored_user_input", eventTarget);
|
|
1098
1167
|
}));
|
|
1099
1168
|
}
|
|
1100
1169
|
},
|
|
@@ -1104,7 +1173,7 @@ function wireAdapterEvents(params) {
|
|
|
1104
1173
|
stateStore.appendLog(`session_switched: ${next.sessionId} source=${next.source} reason=${next.reason}`);
|
|
1105
1174
|
if (shouldForwardSessionSwitchEvent(next.reason) && shouldForwardBridgeEventToWechat(options.adapter, next.type)) {
|
|
1106
1175
|
trackWechatForwardTask(outputBatcher.flushNow().then(async () => {
|
|
1107
|
-
await queueWechatMessage(authorizedUserId, formatSessionSwitchMessage({ adapter: options.adapter, sessionId: next.sessionId, source: next.source, reason: next.reason }), "session_switched");
|
|
1176
|
+
await queueWechatMessage(authorizedUserId, formatSessionSwitchMessage({ adapter: options.adapter, sessionId: next.sessionId, source: next.source, reason: next.reason }), "session_switched", eventTarget);
|
|
1108
1177
|
}));
|
|
1109
1178
|
}
|
|
1110
1179
|
},
|
|
@@ -1122,7 +1191,7 @@ function wireAdapterEvents(params) {
|
|
|
1122
1191
|
}
|
|
1123
1192
|
if (shouldForwardSessionSwitchEvent(next.reason) && shouldForwardBridgeEventToWechat(options.adapter, next.type)) {
|
|
1124
1193
|
trackWechatForwardTask(outputBatcher.flushNow().then(async () => {
|
|
1125
|
-
await queueWechatMessage(authorizedUserId, formatSessionSwitchMessage({ adapter: options.adapter, sessionId: next.threadId, source: next.source, reason: next.reason }), "thread_switched");
|
|
1194
|
+
await queueWechatMessage(authorizedUserId, formatSessionSwitchMessage({ adapter: options.adapter, sessionId: next.threadId, source: next.source, reason: next.reason }), "thread_switched", eventTarget);
|
|
1126
1195
|
}));
|
|
1127
1196
|
}
|
|
1128
1197
|
void maybeDrainDeferredInboundMessages();
|
|
@@ -1131,7 +1200,7 @@ function wireAdapterEvents(params) {
|
|
|
1131
1200
|
trackWechatForwardTask(outputBatcher.flushNow().then(async () => {
|
|
1132
1201
|
stateStore.clearPendingConfirmation();
|
|
1133
1202
|
stateStore.clearPendingUserInput();
|
|
1134
|
-
clearActiveTask();
|
|
1203
|
+
clearActiveTask(eventTask);
|
|
1135
1204
|
await maybeDrainDeferredInboundMessages();
|
|
1136
1205
|
}));
|
|
1137
1206
|
},
|
|
@@ -1139,8 +1208,8 @@ function wireAdapterEvents(params) {
|
|
|
1139
1208
|
trackWechatForwardTask(outputBatcher.flushNow().then(async () => {
|
|
1140
1209
|
stateStore.clearPendingConfirmation();
|
|
1141
1210
|
stateStore.clearPendingUserInput();
|
|
1142
|
-
clearActiveTask();
|
|
1143
|
-
await queueWechatMessage(authorizedUserId, formatTaskFailedMessage(options.adapter, next.message), "task_failed");
|
|
1211
|
+
clearActiveTask(eventTask);
|
|
1212
|
+
await queueWechatMessage(authorizedUserId, formatTaskFailedMessage(options.adapter, next.message), "task_failed", eventTarget);
|
|
1144
1213
|
await maybeDrainDeferredInboundMessages();
|
|
1145
1214
|
}));
|
|
1146
1215
|
},
|
|
@@ -1149,9 +1218,9 @@ function wireAdapterEvents(params) {
|
|
|
1149
1218
|
stateStore.appendLog(`fatal_error: ${next.message}`);
|
|
1150
1219
|
stateStore.clearPendingConfirmation();
|
|
1151
1220
|
stateStore.clearPendingUserInput();
|
|
1152
|
-
clearActiveTask();
|
|
1221
|
+
clearActiveTask(eventTask);
|
|
1153
1222
|
trackWechatForwardTask(outputBatcher.flushNow().then(async () => {
|
|
1154
|
-
await queueWechatMessage(authorizedUserId, formatUserFacingBridgeFatalError(next.message), "fatal_error");
|
|
1223
|
+
await queueWechatMessage(authorizedUserId, formatUserFacingBridgeFatalError(next.message), "fatal_error", eventTarget);
|
|
1155
1224
|
await maybeDrainDeferredInboundMessages();
|
|
1156
1225
|
}));
|
|
1157
1226
|
},
|
|
@@ -1180,7 +1249,7 @@ function formatInboundMessagePreview(message) {
|
|
|
1180
1249
|
}
|
|
1181
1250
|
async function handleInboundMessage(params) {
|
|
1182
1251
|
const { message, channelMessage, } = params;
|
|
1183
|
-
const { options, stateStore, adapter, resumeCoordinator, queueWechatMessage, outputBatcher, clearActiveTask, deferInboundMessage, } = params;
|
|
1252
|
+
const { options, stateStore, adapter, resumeCoordinator, queueWechatMessage, outputBatcher, clearActiveTask, dispatchInboundText, deferInboundMessage, } = params;
|
|
1184
1253
|
const state = stateStore.getState();
|
|
1185
1254
|
if (message.senderId !== state.authorizedUserId) {
|
|
1186
1255
|
await queueWechatMessage(message.senderId, options.channelId === "wecom"
|
|
@@ -1194,6 +1263,10 @@ async function handleInboundMessage(params) {
|
|
|
1194
1263
|
hasPendingUserInput: Boolean(state.pendingUserInput),
|
|
1195
1264
|
});
|
|
1196
1265
|
switch (systemCommand?.type) {
|
|
1266
|
+
case "model":
|
|
1267
|
+
case "plan":
|
|
1268
|
+
await queueWechatMessage(message.senderId, await handleAdapterControl(adapter, message.senderId, systemCommand));
|
|
1269
|
+
return null;
|
|
1197
1270
|
case "status":
|
|
1198
1271
|
await queueWechatMessage(message.senderId, formatStatusReport(stateStore.getState(), adapter.getState()));
|
|
1199
1272
|
return null;
|
|
@@ -1348,24 +1421,20 @@ async function handleInboundMessage(params) {
|
|
|
1348
1421
|
defer: async () => {
|
|
1349
1422
|
await deferInboundMessage(message);
|
|
1350
1423
|
},
|
|
1351
|
-
dispatch:
|
|
1352
|
-
return await dispatchInboundWechatText({
|
|
1353
|
-
message,
|
|
1354
|
-
options,
|
|
1355
|
-
stateStore,
|
|
1356
|
-
adapter,
|
|
1357
|
-
});
|
|
1358
|
-
},
|
|
1424
|
+
dispatch: dispatchInboundText,
|
|
1359
1425
|
});
|
|
1360
1426
|
return routeResult.kind === "dispatched" ? routeResult.result : null;
|
|
1361
1427
|
}
|
|
1428
|
+
function createActiveTask(message) {
|
|
1429
|
+
return {
|
|
1430
|
+
startedAt: Date.now(),
|
|
1431
|
+
inputPreview: truncatePreview(formatInboundMessagePreview(message), 180),
|
|
1432
|
+
};
|
|
1433
|
+
}
|
|
1362
1434
|
async function dispatchInboundWechatText(params) {
|
|
1363
1435
|
const { message, options, stateStore, adapter } = params;
|
|
1364
1436
|
const preview = formatInboundMessagePreview(message);
|
|
1365
|
-
const activeTask =
|
|
1366
|
-
startedAt: Date.now(),
|
|
1367
|
-
inputPreview: truncatePreview(preview, 180),
|
|
1368
|
-
};
|
|
1437
|
+
const activeTask = params.activeTask ?? createActiveTask(message);
|
|
1369
1438
|
stateStore.appendLog(`Forwarded input to ${options.adapter}: ${truncatePreview(preview)}`);
|
|
1370
1439
|
await adapter.sendInput(options.channelId === "wecom"
|
|
1371
1440
|
? buildWecomInboundPrompt(message.text, message.attachments)
|
|
@@ -5,7 +5,7 @@ import net from "node:net";
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { assertNoReservedExtraCliArgs, buildCliEnvironment, buildCodexCliArgs, resolveSpawnTarget, } from "../bridge/bridge-adapters.shared.js";
|
|
7
7
|
import { killProcessTreeSync } from "../bridge/bridge-process-reaper.js";
|
|
8
|
-
import { CODEX_VISIBLE_CONTROL_HOST,
|
|
8
|
+
import { CODEX_VISIBLE_CONTROL_HOST, parseCodexVisibleControlRequest, sendCodexVisibleShutdownResponse, sendCodexVisibleSwitchResponse, } from "./codex-visible-client-link.js";
|
|
9
9
|
import { clearLocalCompanionOccupancy, readLocalCompanionEndpoint, updateCodexVisibleControl, updateLocalCompanionOccupancy, } from "./local-companion-link.js";
|
|
10
10
|
import { migrateLegacyChannelFiles } from "../wechat/channel-config.js";
|
|
11
11
|
import { CODEX_REMOTE_AUTH_TOKEN_ENV } from "../runtime/runtime-types.js";
|
|
@@ -302,11 +302,20 @@ export class CodexVisibleClientSupervisor {
|
|
|
302
302
|
socket.destroy();
|
|
303
303
|
return;
|
|
304
304
|
}
|
|
305
|
-
const request =
|
|
305
|
+
const request = parseCodexVisibleControlRequest(parsed);
|
|
306
306
|
if (!request || request.token !== this.controlToken) {
|
|
307
307
|
socket.destroy();
|
|
308
308
|
return;
|
|
309
309
|
}
|
|
310
|
+
if (request.type === "shutdown") {
|
|
311
|
+
sendCodexVisibleShutdownResponse(socket, {
|
|
312
|
+
type: "shutdown_result",
|
|
313
|
+
id: request.id,
|
|
314
|
+
ok: true,
|
|
315
|
+
});
|
|
316
|
+
void this.shutdownFromControl();
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
310
319
|
void this.switchThread(request.threadId).then(() => {
|
|
311
320
|
sendCodexVisibleSwitchResponse(socket, {
|
|
312
321
|
type: "switch_thread_result",
|
|
@@ -340,6 +349,17 @@ export class CodexVisibleClientSupervisor {
|
|
|
340
349
|
this.controlServer = server;
|
|
341
350
|
this.controlPort = address.port;
|
|
342
351
|
}
|
|
352
|
+
async shutdownFromControl() {
|
|
353
|
+
await this.switchChain.catch(() => undefined);
|
|
354
|
+
if (this.runSettled) {
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
const child = this.child;
|
|
358
|
+
if (child) {
|
|
359
|
+
await this.stopChild(child);
|
|
360
|
+
}
|
|
361
|
+
this.finishRun(undefined, 0);
|
|
362
|
+
}
|
|
343
363
|
async closeControlServer() {
|
|
344
364
|
const server = this.controlServer;
|
|
345
365
|
this.controlServer = null;
|