cli-wechat-bridge 1.1.6 → 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 +454 -475
- package/bin/_run-entry.mjs +23 -5
- package/bin/wecom-check-update.mjs +5 -0
- package/bin/wecom-claude.mjs +5 -0
- package/bin/wecom-codex.mjs +5 -0
- package/bin/wecom-daemon.mjs +5 -0
- package/bin/wecom-opencode.mjs +5 -0
- package/bin/wecom-pi.mjs +5 -0
- package/bin/wecom-setup.mjs +5 -0
- package/dist/bridge/adapter-control.js +58 -0
- package/dist/bridge/bridge-adapters.claude.js +155 -4
- 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/bridge-process-reaper.js +35 -2
- package/dist/bridge/bridge-state.js +10 -3
- package/dist/bridge/claude-native-controls.js +133 -0
- package/dist/bridge/native-terminal-control.js +253 -0
- package/dist/bridge/wechat-bridge.js +389 -87
- package/dist/channels/wechat/wechat-outbound-queue.js +10 -3
- package/dist/channels/wecom/bridge-final-reply.js +26 -0
- package/dist/channels/wecom/setup-cli.js +6 -0
- package/dist/channels/wecom/setup.js +176 -0
- package/dist/channels/wecom/wecom-channel-port.js +45 -0
- package/dist/channels/wecom/wecom-config.js +66 -0
- package/dist/channels/wecom/wecom-media.js +141 -0
- package/dist/channels/wecom/wecom-message.js +149 -0
- package/dist/channels/wecom/wecom-transport.js +308 -0
- package/dist/companion/codex-remote-client.js +24 -3
- package/dist/companion/codex-visible-client-link.js +101 -0
- package/dist/companion/local-companion-start.js +56 -11
- package/dist/companion/local-companion.js +19 -1
- package/dist/companion/pi-tui-bridge-extension.js +56 -3
- package/dist/core/bridge-control.js +8 -0
- package/dist/core/channel-types.js +3 -1
- package/dist/core/conversation-routing.js +48 -0
- package/dist/core/direct-run.js +17 -0
- package/dist/core/inbound-message-claims.js +61 -0
- package/dist/daemon/daemon-link.js +3 -0
- package/dist/daemon/wechat-daemon.js +335 -60
- package/dist/i18n/messages-en.js +3 -1
- package/dist/i18n/messages-zh.js +3 -1
- package/dist/runtime/legacy-adapter-runtime.js +15 -0
- package/dist/runtime/runtime-types.js +3 -0
- package/dist/utils/doctor.js +30 -10
- package/dist/utils/version-checker.js +11 -11
- package/dist/wechat/setup.js +113 -13
- package/dist/wechat/wechat-transport.js +96 -58
- package/package.json +116 -104
- package/scripts/ensure-node-pty-permissions.mjs +73 -73
|
@@ -12,11 +12,18 @@ 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";
|
|
18
|
+
import { isDirectModuleRun } from "../core/direct-run.js";
|
|
16
19
|
import { WechatChannelPort } from "../channels/wechat/wechat-channel-port.js";
|
|
20
|
+
import { ensureWecomAccount } from "../channels/wecom/setup.js";
|
|
21
|
+
import { WecomChannelPort } from "../channels/wecom/wecom-channel-port.js";
|
|
22
|
+
import { WecomTransport } from "../channels/wecom/wecom-transport.js";
|
|
23
|
+
import { buildWecomInboundPrompt, formatWecomVisibleText, } from "../channels/wecom/wecom-message.js";
|
|
17
24
|
import { buildWechatInboundPrompt, buildOneTimeCode, formatApprovalMessage, formatPendingApprovalReminder, formatPendingUserInputReminder, formatDuration, formatMirroredUserInputMessage, formatSessionSwitchMessage, formatStatusReport, formatTaskFailedMessage, formatThinkingForWechat, formatUserInputRequestMessage, MESSAGE_START_GRACE_MS, nowIso, OutputBatcher, parsePendingUserInputAnswerCommand, parseWechatControlCommand, truncatePreview, } from "./bridge-utils.js";
|
|
18
25
|
import { classifyWechatTransportError, DEFAULT_LONG_POLL_TIMEOUT_MS, WeChatTransport, describeWechatTransportError, isWechatContextTokenStaleError, } from "../wechat/wechat-transport.js";
|
|
19
|
-
import { getPendingWechatMessagesFile, PendingWechatMessageStore, } from "../channels/wechat/wechat-outbound-queue.js";
|
|
26
|
+
import { getPendingChannelMessagesFile, getPendingWechatMessagesFile, PendingWechatMessageStore, } from "../channels/wechat/wechat-outbound-queue.js";
|
|
20
27
|
import { checkForUpdate, formatUpdateMessage, } from "../utils/version-checker.js";
|
|
21
28
|
import { clearDaemonEndpoint, isDaemonEndpointAlive, readDaemonEndpoint, } from "../daemon/daemon-link.js";
|
|
22
29
|
const POLL_RETRY_BASE_MS = 1_000;
|
|
@@ -105,6 +112,7 @@ export function parseCliArgs(argv) {
|
|
|
105
112
|
let profile;
|
|
106
113
|
let lifecycle = "persistent";
|
|
107
114
|
let sessionStartMode = "restore";
|
|
115
|
+
let channelId = "wechat";
|
|
108
116
|
for (let i = 0; i < argv.length; i += 1) {
|
|
109
117
|
const arg = argv[i];
|
|
110
118
|
const next = argv[i + 1];
|
|
@@ -151,6 +159,13 @@ export function parseCliArgs(argv) {
|
|
|
151
159
|
sessionStartMode = next;
|
|
152
160
|
i += 1;
|
|
153
161
|
break;
|
|
162
|
+
case "--channel":
|
|
163
|
+
if (!next || (next !== "wechat" && next !== "wecom")) {
|
|
164
|
+
throw new Error(`Invalid channel: ${next ?? "(missing)"}`);
|
|
165
|
+
}
|
|
166
|
+
channelId = next;
|
|
167
|
+
i += 1;
|
|
168
|
+
break;
|
|
154
169
|
case "--shutdown-on-parent-exit":
|
|
155
170
|
lifecycle = "companion_bound";
|
|
156
171
|
break;
|
|
@@ -173,6 +188,51 @@ export function parseCliArgs(argv) {
|
|
|
173
188
|
profile,
|
|
174
189
|
lifecycle,
|
|
175
190
|
sessionStartMode,
|
|
191
|
+
channelId,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function toWecomOutputKind(context) {
|
|
195
|
+
const known = new Set([
|
|
196
|
+
"status",
|
|
197
|
+
"notice",
|
|
198
|
+
"thinking",
|
|
199
|
+
"final_reply",
|
|
200
|
+
"approval_required",
|
|
201
|
+
"user_input_required",
|
|
202
|
+
"mirrored_input",
|
|
203
|
+
"task_failed",
|
|
204
|
+
"fatal_error",
|
|
205
|
+
]);
|
|
206
|
+
return known.has(context)
|
|
207
|
+
? context
|
|
208
|
+
: context === "mirrored_user_input"
|
|
209
|
+
? "mirrored_input"
|
|
210
|
+
: "notice";
|
|
211
|
+
}
|
|
212
|
+
function toWechatSendContext(kind) {
|
|
213
|
+
if (kind === "mirrored_input") {
|
|
214
|
+
return "mirrored_user_input";
|
|
215
|
+
}
|
|
216
|
+
if (kind === "status") {
|
|
217
|
+
return "message";
|
|
218
|
+
}
|
|
219
|
+
return kind;
|
|
220
|
+
}
|
|
221
|
+
function toLegacyInboundWechatMessage(message) {
|
|
222
|
+
return {
|
|
223
|
+
senderId: message.senderId,
|
|
224
|
+
sender: message.senderId,
|
|
225
|
+
sessionId: message.conversation.conversationId,
|
|
226
|
+
text: message.text,
|
|
227
|
+
attachments: message.attachments.map((attachment) => ({
|
|
228
|
+
kind: attachment.kind === "image" ? "image" : "file",
|
|
229
|
+
path: attachment.path,
|
|
230
|
+
fileName: attachment.fileName || path.basename(attachment.path),
|
|
231
|
+
sizeBytes: attachment.sizeBytes ?? 0,
|
|
232
|
+
})),
|
|
233
|
+
contextToken: message.conversation.opaqueRef,
|
|
234
|
+
createdAt: message.createdAt,
|
|
235
|
+
createdAtMs: Date.parse(message.createdAt),
|
|
176
236
|
};
|
|
177
237
|
}
|
|
178
238
|
function printUsageAndExit() {
|
|
@@ -180,7 +240,7 @@ function printUsageAndExit() {
|
|
|
180
240
|
"Internal bridge runtime usage:",
|
|
181
241
|
" npm run bridge -- --adapter <codex|claude|opencode|pi> [--cmd <executable>] [--cwd <path>] [--profile <name-or-path>] [--lifecycle <persistent|companion_bound>] [--session-start-mode <restore|new>]",
|
|
182
242
|
"",
|
|
183
|
-
"This entry is internal. Users should run
|
|
243
|
+
"This entry is internal. Users should run a wechat-* or wecom-* launcher.",
|
|
184
244
|
"",
|
|
185
245
|
].join("\n"));
|
|
186
246
|
process.exit(0);
|
|
@@ -194,21 +254,37 @@ async function main() {
|
|
|
194
254
|
const options = parseCliArgs(process.argv.slice(2));
|
|
195
255
|
const daemonEndpoint = readDaemonEndpoint();
|
|
196
256
|
if (daemonEndpoint && await isDaemonEndpointAlive(daemonEndpoint, { timeoutMs: 500 })) {
|
|
197
|
-
throw new Error(
|
|
257
|
+
throw new Error(`${daemonEndpoint.channelId ?? "wechat"}-daemon is already running (pid=${daemonEndpoint.pid}, cwd=${daemonEndpoint.cwd}). Stop it before starting a standalone bridge.`);
|
|
198
258
|
}
|
|
199
259
|
if (daemonEndpoint) {
|
|
200
260
|
clearDaemonEndpoint(daemonEndpoint.pid);
|
|
201
261
|
log(`Cleared stale wechat-daemon endpoint for pid=${daemonEndpoint.pid}.`);
|
|
202
262
|
}
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
263
|
+
const wecomAccount = options.channelId === "wecom"
|
|
264
|
+
? await ensureWecomAccount({ log })
|
|
265
|
+
: null;
|
|
266
|
+
const credentials = wecomAccount
|
|
267
|
+
? { userId: wecomAccount.operatorUserId }
|
|
268
|
+
: await ensureWechatCredentials({
|
|
269
|
+
requireUserId: true,
|
|
270
|
+
validateExisting: true,
|
|
271
|
+
log,
|
|
272
|
+
});
|
|
208
273
|
if (!credentials.userId) {
|
|
209
|
-
throw new Error(
|
|
274
|
+
throw new Error(options.channelId === "wecom"
|
|
275
|
+
? "Saved WeCom credentials are missing operatorUserId."
|
|
276
|
+
: "Saved WeChat credentials are missing userId.");
|
|
210
277
|
}
|
|
211
278
|
const transport = new WeChatTransport({ log, logError });
|
|
279
|
+
const wecomTransport = wecomAccount
|
|
280
|
+
? new WecomTransport({
|
|
281
|
+
account: wecomAccount,
|
|
282
|
+
logger: {
|
|
283
|
+
log: (message) => log(message),
|
|
284
|
+
error: (message) => logError(message),
|
|
285
|
+
},
|
|
286
|
+
})
|
|
287
|
+
: null;
|
|
212
288
|
// 非阻塞地检查更新(不影响启动速度,也避免首次登录时打断二维码输出)
|
|
213
289
|
// unref:不能让这个延迟检查把 event loop 挂活(如 --doctor 或快速退出场景),
|
|
214
290
|
// 否则会与强制退出 teardown 竞态。
|
|
@@ -227,6 +303,7 @@ async function main() {
|
|
|
227
303
|
const stateStore = new BridgeStateStore({
|
|
228
304
|
...options,
|
|
229
305
|
authorizedUserId: credentials.userId,
|
|
306
|
+
channelId: options.channelId,
|
|
230
307
|
});
|
|
231
308
|
const reapedPeerPids = await reapPeerBridgeProcesses({
|
|
232
309
|
logger: (message) => stateStore.appendLog(message),
|
|
@@ -273,7 +350,9 @@ async function main() {
|
|
|
273
350
|
initialResumeConversationId: stateStore.getState().resumeConversationId,
|
|
274
351
|
initialTranscriptPath: stateStore.getState().transcriptPath,
|
|
275
352
|
});
|
|
276
|
-
const pendingWechatMessages = new PendingWechatMessageStore(
|
|
353
|
+
const pendingWechatMessages = new PendingWechatMessageStore(options.channelId === "wechat"
|
|
354
|
+
? getPendingWechatMessagesFile(options.cwd)
|
|
355
|
+
: getPendingChannelMessagesFile(options.cwd, options.channelId));
|
|
277
356
|
const resumeCoordinator = new ResumeSessionCoordinator({
|
|
278
357
|
adapter: options.adapter,
|
|
279
358
|
runtime: adapter,
|
|
@@ -284,7 +363,17 @@ async function main() {
|
|
|
284
363
|
let textSendChain = Promise.resolve();
|
|
285
364
|
let attachmentSendChain = Promise.resolve();
|
|
286
365
|
const pendingWechatForwardTasks = new Set();
|
|
287
|
-
|
|
366
|
+
const defaultWecomConversation = {
|
|
367
|
+
channelId: "wecom",
|
|
368
|
+
accountId: wecomAccount?.botId,
|
|
369
|
+
conversationId: credentials.userId,
|
|
370
|
+
recipientId: credentials.userId,
|
|
371
|
+
metadata: { chatType: "direct" },
|
|
372
|
+
};
|
|
373
|
+
const turnState = {
|
|
374
|
+
activeTask: null,
|
|
375
|
+
lastConversation: defaultWecomConversation,
|
|
376
|
+
};
|
|
288
377
|
const deferredInboundMessages = [];
|
|
289
378
|
let drainingDeferredInboundMessages = false;
|
|
290
379
|
let consecutivePollFailures = 0;
|
|
@@ -299,7 +388,36 @@ async function main() {
|
|
|
299
388
|
attachmentSendChain = run.then(() => undefined, () => undefined);
|
|
300
389
|
return run;
|
|
301
390
|
};
|
|
302
|
-
const sendWechatMessageNow = async (senderId, text, context = "message") => {
|
|
391
|
+
const sendWechatMessageNow = async (senderId, text, context = "message", targetOverride) => {
|
|
392
|
+
if (options.channelId === "wecom") {
|
|
393
|
+
const target = targetOverride ??
|
|
394
|
+
(senderId === credentials.userId
|
|
395
|
+
? resolveConversationTarget({
|
|
396
|
+
active: turnState.activeConversation,
|
|
397
|
+
last: turnState.lastConversation,
|
|
398
|
+
fallback: defaultWecomConversation,
|
|
399
|
+
})
|
|
400
|
+
: {
|
|
401
|
+
channelId: "wecom",
|
|
402
|
+
accountId: wecomAccount?.botId,
|
|
403
|
+
conversationId: senderId,
|
|
404
|
+
recipientId: senderId,
|
|
405
|
+
metadata: { chatType: "direct" },
|
|
406
|
+
});
|
|
407
|
+
const startedAtMs = Date.now();
|
|
408
|
+
stateStore.appendLog(`wecom_send_started: context=${context} recipient=${target.recipientId} chars=${Array.from(text).length}`);
|
|
409
|
+
try {
|
|
410
|
+
await wecomTransport.sendText(target, formatWecomVisibleText(text), toWecomOutputKind(context));
|
|
411
|
+
stateStore.appendLog(`wecom_send_completed: context=${context} recipient=${target.recipientId} elapsed_ms=${Date.now() - startedAtMs}`);
|
|
412
|
+
return { status: "sent" };
|
|
413
|
+
}
|
|
414
|
+
catch (error) {
|
|
415
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
416
|
+
stateStore.appendLog(`wecom_send_failed: context=${context} recipient=${target.recipientId} error=${truncatePreview(message, 400)}`);
|
|
417
|
+
logError(`Failed to send WeCom ${context}: ${message}`);
|
|
418
|
+
return { status: "context_unavailable", error, target };
|
|
419
|
+
}
|
|
420
|
+
}
|
|
303
421
|
const startedAtMs = Date.now();
|
|
304
422
|
stateStore.appendLog(`wechat_send_started: context=${context} recipient=${senderId} chars=${Array.from(text).length}`);
|
|
305
423
|
for (let attempt = 1; attempt <= WECHAT_SEND_MAX_ATTEMPTS; attempt += 1) {
|
|
@@ -355,11 +473,18 @@ async function main() {
|
|
|
355
473
|
}
|
|
356
474
|
return { status: "failed" };
|
|
357
475
|
};
|
|
358
|
-
const queueWechatMessage = (senderId, text, context = "message") => {
|
|
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;
|
|
359
484
|
return queueWechatTextAction(async () => {
|
|
360
|
-
const result = await sendWechatMessageNow(senderId, text, context);
|
|
485
|
+
const result = await sendWechatMessageNow(senderId, text, context, queuedTarget);
|
|
361
486
|
if (result.status === "context_unavailable") {
|
|
362
|
-
const pending = pendingWechatMessages.enqueue(senderId, text, context);
|
|
487
|
+
const pending = pendingWechatMessages.enqueue(senderId, text, context, result.target);
|
|
363
488
|
if (pending) {
|
|
364
489
|
stateStore.appendLog(`wechat_send_queued: id=${pending.id} context=${context} recipient=${senderId} pending=${pendingWechatMessages.list().length}`);
|
|
365
490
|
}
|
|
@@ -370,7 +495,7 @@ async function main() {
|
|
|
370
495
|
const flushPendingWechatMessages = () => {
|
|
371
496
|
return queueWechatTextAction(async () => {
|
|
372
497
|
for (const pending of pendingWechatMessages.list()) {
|
|
373
|
-
const result = await sendWechatMessageNow(pending.recipientId, pending.text, pending.context);
|
|
498
|
+
const result = await sendWechatMessageNow(pending.recipientId, pending.text, pending.context, pending.target);
|
|
374
499
|
if (result.status === "sent") {
|
|
375
500
|
pendingWechatMessages.remove(pending.id);
|
|
376
501
|
stateStore.appendLog(`wechat_pending_sent: id=${pending.id} context=${pending.context} recipient=${pending.recipientId}`);
|
|
@@ -401,7 +526,13 @@ async function main() {
|
|
|
401
526
|
}
|
|
402
527
|
};
|
|
403
528
|
const outputBatcher = new OutputBatcher(async (text) => {
|
|
404
|
-
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);
|
|
405
536
|
});
|
|
406
537
|
const maybeDrainDeferredInboundMessages = async () => {
|
|
407
538
|
if (drainingDeferredInboundMessages || !ensureRuntimeOwnership()) {
|
|
@@ -416,7 +547,7 @@ async function main() {
|
|
|
416
547
|
hasPendingConfirmation: Boolean(stateStore.getState().pendingConfirmation),
|
|
417
548
|
hasPendingUserInput: Boolean(stateStore.getState().pendingUserInput),
|
|
418
549
|
hasPendingApproval: Boolean(adapterState.pendingApproval),
|
|
419
|
-
hasActiveTask: Boolean(activeTask),
|
|
550
|
+
hasActiveTask: Boolean(turnState.activeTask),
|
|
420
551
|
})) {
|
|
421
552
|
return;
|
|
422
553
|
}
|
|
@@ -427,13 +558,25 @@ async function main() {
|
|
|
427
558
|
drainingDeferredInboundMessages = true;
|
|
428
559
|
try {
|
|
429
560
|
stateStore.appendLog(`draining_deferred_inbound_input: remaining=${deferredInboundMessages.length} text=${truncatePreview(nextDeferred.message.text)}`);
|
|
430
|
-
const nextTask =
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
}
|
|
436
|
-
|
|
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;
|
|
579
|
+
}
|
|
437
580
|
}
|
|
438
581
|
catch (err) {
|
|
439
582
|
const errorText = err instanceof Error ? err.message : String(err);
|
|
@@ -448,7 +591,7 @@ async function main() {
|
|
|
448
591
|
adapter: options.adapter,
|
|
449
592
|
cwd: options.cwd,
|
|
450
593
|
errorText,
|
|
451
|
-
}), "inbound_error");
|
|
594
|
+
}), "inbound_error", nextDeferred.channelMessage?.conversation);
|
|
452
595
|
}
|
|
453
596
|
finally {
|
|
454
597
|
drainingDeferredInboundMessages = false;
|
|
@@ -515,6 +658,12 @@ async function main() {
|
|
|
515
658
|
catch {
|
|
516
659
|
// Best effort shutdown.
|
|
517
660
|
}
|
|
661
|
+
try {
|
|
662
|
+
wecomTransport?.stop();
|
|
663
|
+
}
|
|
664
|
+
catch {
|
|
665
|
+
// Best effort shutdown.
|
|
666
|
+
}
|
|
518
667
|
controller.clearLocalClientEndpoint();
|
|
519
668
|
stateStore.releaseLock();
|
|
520
669
|
};
|
|
@@ -592,8 +741,11 @@ async function main() {
|
|
|
592
741
|
queueWechatMessage,
|
|
593
742
|
trackWechatForwardTask,
|
|
594
743
|
maybeDrainDeferredInboundMessages,
|
|
595
|
-
|
|
596
|
-
|
|
744
|
+
getActiveTask: () => turnState.activeTask,
|
|
745
|
+
clearActiveTask: (expectedTask) => {
|
|
746
|
+
if (expectedTask) {
|
|
747
|
+
clearTurn(turnState, expectedTask);
|
|
748
|
+
}
|
|
597
749
|
},
|
|
598
750
|
syncSharedSessionState: () => {
|
|
599
751
|
syncSharedSessionState(stateStore, adapter);
|
|
@@ -602,6 +754,12 @@ async function main() {
|
|
|
602
754
|
controller.syncLocalClientEndpoint();
|
|
603
755
|
},
|
|
604
756
|
requestShutdown,
|
|
757
|
+
wecomTransport,
|
|
758
|
+
getWecomTarget: () => resolveConversationTarget({
|
|
759
|
+
active: turnState.activeConversation,
|
|
760
|
+
last: turnState.lastConversation,
|
|
761
|
+
fallback: defaultWecomConversation,
|
|
762
|
+
}),
|
|
605
763
|
});
|
|
606
764
|
await adapter.start();
|
|
607
765
|
if (!ensureRuntimeOwnership()) {
|
|
@@ -609,13 +767,112 @@ async function main() {
|
|
|
609
767
|
}
|
|
610
768
|
syncSharedSessionState(stateStore, adapter);
|
|
611
769
|
controller.syncLocalClientEndpoint();
|
|
612
|
-
stateStore.appendLog(`Bridge started with adapter=${options.adapter} command=${options.command} cwd=${options.cwd}`);
|
|
613
|
-
|
|
770
|
+
stateStore.appendLog(`Bridge started with channel=${options.channelId} adapter=${options.adapter} command=${options.command} cwd=${options.cwd}`);
|
|
771
|
+
if (wecomTransport) {
|
|
772
|
+
wecomTransport.setHandlers({
|
|
773
|
+
onMessage: async (channelMessage) => {
|
|
774
|
+
if (shutdownPromise || !ensureRuntimeOwnership()) {
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
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);
|
|
785
|
+
try {
|
|
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,
|
|
814
|
+
});
|
|
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
|
+
});
|
|
831
|
+
}
|
|
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");
|
|
841
|
+
}
|
|
842
|
+
syncSharedSessionState(stateStore, adapter);
|
|
843
|
+
await maybeDrainDeferredInboundMessages();
|
|
844
|
+
},
|
|
845
|
+
onUnauthorized: async (senderId, chatType) => {
|
|
846
|
+
stateStore.appendLog(`wecom_unauthorized: sender=${senderId} chat_type=${chatType}`);
|
|
847
|
+
if (chatType === "direct") {
|
|
848
|
+
await wecomTransport.sendText({
|
|
849
|
+
channelId: "wecom",
|
|
850
|
+
accountId: wecomAccount.botId,
|
|
851
|
+
conversationId: senderId,
|
|
852
|
+
recipientId: senderId,
|
|
853
|
+
metadata: { chatType: "direct" },
|
|
854
|
+
}, "Unauthorized.", "notice");
|
|
855
|
+
}
|
|
856
|
+
},
|
|
857
|
+
onFatal: async (error) => {
|
|
858
|
+
stateStore.appendLog(`wecom_fatal_error: ${error.message}`);
|
|
859
|
+
requestShutdown(error.message, 1);
|
|
860
|
+
},
|
|
861
|
+
onConnected: async () => {
|
|
862
|
+
if (pendingWechatMessages.list().length > 0) {
|
|
863
|
+
await flushPendingWechatMessages();
|
|
864
|
+
}
|
|
865
|
+
},
|
|
866
|
+
});
|
|
867
|
+
wecomTransport.start();
|
|
868
|
+
await wecomTransport.waitUntilConnected();
|
|
869
|
+
}
|
|
870
|
+
log(`${options.channelId === "wecom" ? "WeCom" : "WeChat"} bridge is ready for adapter "${options.adapter}".`);
|
|
614
871
|
log(`Working directory: ${options.cwd}`);
|
|
615
872
|
if (options.profile) {
|
|
616
873
|
log(`Profile: ${options.profile}`);
|
|
617
874
|
}
|
|
618
|
-
log(`Authorized WeChat user: ${credentials.userId}`);
|
|
875
|
+
log(`Authorized ${options.channelId === "wecom" ? "WeCom" : "WeChat"} user: ${credentials.userId}`);
|
|
619
876
|
if (options.adapter === "codex") {
|
|
620
877
|
log("For source-mode debugging, open the visible Codex client with: npm run codex:panel");
|
|
621
878
|
}
|
|
@@ -633,6 +890,12 @@ async function main() {
|
|
|
633
890
|
cwd: options.cwd,
|
|
634
891
|
});
|
|
635
892
|
await queueWechatMessage(credentials.userId, welcomeText);
|
|
893
|
+
if (options.channelId === "wecom") {
|
|
894
|
+
while (!shutdownPromise && ensureRuntimeOwnership()) {
|
|
895
|
+
await delay(1_000);
|
|
896
|
+
}
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
636
899
|
while (true) {
|
|
637
900
|
if (shutdownPromise) {
|
|
638
901
|
// Shutdown (e.g. SIGINT) was requested while we were awaiting the
|
|
@@ -693,10 +956,10 @@ async function main() {
|
|
|
693
956
|
if (!ensureRuntimeOwnership()) {
|
|
694
957
|
break;
|
|
695
958
|
}
|
|
959
|
+
const taskAtMessageStart = turnState.activeTask;
|
|
696
960
|
stateStore.touchActivity(message.createdAt);
|
|
697
|
-
let nextTask = null;
|
|
698
961
|
try {
|
|
699
|
-
|
|
962
|
+
await handleInboundMessage({
|
|
700
963
|
message,
|
|
701
964
|
options,
|
|
702
965
|
stateStore,
|
|
@@ -705,7 +968,31 @@ async function main() {
|
|
|
705
968
|
queueWechatMessage,
|
|
706
969
|
outputBatcher,
|
|
707
970
|
clearActiveTask: () => {
|
|
708
|
-
|
|
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
|
+
}
|
|
709
996
|
},
|
|
710
997
|
deferInboundMessage: async (nextMessage) => {
|
|
711
998
|
deferredInboundMessages.push({
|
|
@@ -726,9 +1013,6 @@ async function main() {
|
|
|
726
1013
|
errorText,
|
|
727
1014
|
}), "inbound_error");
|
|
728
1015
|
}
|
|
729
|
-
if (nextTask) {
|
|
730
|
-
activeTask = nextTask;
|
|
731
|
-
}
|
|
732
1016
|
syncSharedSessionState(stateStore, adapter);
|
|
733
1017
|
await maybeDrainDeferredInboundMessages();
|
|
734
1018
|
}
|
|
@@ -763,20 +1047,28 @@ function syncSharedSessionState(stateStore, adapter) {
|
|
|
763
1047
|
}
|
|
764
1048
|
}
|
|
765
1049
|
function wireAdapterEvents(params) {
|
|
766
|
-
const { adapter, resumeCoordinator, options, transport, stateStore, outputBatcher, queueWechatAttachmentAction, queueWechatMessage, trackWechatForwardTask, maybeDrainDeferredInboundMessages, clearActiveTask, syncSharedSessionState, syncLocalClientEndpoint, requestShutdown, } = params;
|
|
767
|
-
const channelPort =
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
1050
|
+
const { adapter, resumeCoordinator, options, transport, stateStore, outputBatcher, queueWechatAttachmentAction, queueWechatMessage, trackWechatForwardTask, maybeDrainDeferredInboundMessages, getActiveTask, clearActiveTask, syncSharedSessionState, syncLocalClientEndpoint, requestShutdown, wecomTransport, getWecomTarget, } = params;
|
|
1051
|
+
const channelPort = options.channelId === "wecom"
|
|
1052
|
+
? new WecomChannelPort({
|
|
1053
|
+
transport: wecomTransport,
|
|
1054
|
+
sendText: (target, text, kind) => queueWechatMessage(stateStore.getState().authorizedUserId, text, toWechatSendContext(kind), target),
|
|
1055
|
+
onEmptyVisibleReply: (adapter, rawText) => {
|
|
1056
|
+
stateStore.appendLog(`empty_visible_final_reply: adapter=${adapter ?? options.adapter} raw=${truncatePreview(rawText)}`);
|
|
1057
|
+
},
|
|
1058
|
+
})
|
|
1059
|
+
: new WechatChannelPort({
|
|
1060
|
+
sendText: (recipientId, text, context) => queueWechatMessage(recipientId, text, context),
|
|
1061
|
+
sendImage: (recipientId, filePath) => queueWechatAttachmentAction(() => transport.sendImage(filePath, { recipientId })),
|
|
1062
|
+
sendFile: (recipientId, filePath) => queueWechatAttachmentAction(() => transport.sendFile(filePath, { recipientId })),
|
|
1063
|
+
sendVoice: (recipientId, filePath) => queueWechatAttachmentAction(() => transport.sendVoice(filePath, recipientId)),
|
|
1064
|
+
sendVideo: (recipientId, filePath) => queueWechatAttachmentAction(() => transport.sendVideo(filePath, { recipientId })),
|
|
1065
|
+
onEmptyVisibleReply: (adapter, rawText) => {
|
|
1066
|
+
stateStore.appendLog(`empty_visible_final_reply: adapter=${adapter ?? options.adapter} raw=${truncatePreview(rawText)}`);
|
|
1067
|
+
},
|
|
1068
|
+
onTextSent: (_adapter, text) => {
|
|
1069
|
+
stateStore.appendLog(`final_reply_sent: chars=${Array.from(text).length}`);
|
|
1070
|
+
},
|
|
1071
|
+
});
|
|
780
1072
|
let lastFinalReplyAtMs = 0;
|
|
781
1073
|
let eventForwardChain = Promise.resolve();
|
|
782
1074
|
adapter.setEventSink((event) => {
|
|
@@ -791,6 +1083,14 @@ function wireAdapterEvents(params) {
|
|
|
791
1083
|
stateStore.clearPendingUserInput();
|
|
792
1084
|
}
|
|
793
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();
|
|
794
1094
|
eventForwardChain = eventForwardChain
|
|
795
1095
|
.then(() => forwardBridgeEvent(event, {
|
|
796
1096
|
stdout: (next) => {
|
|
@@ -808,11 +1108,7 @@ function wireAdapterEvents(params) {
|
|
|
808
1108
|
stateStore.appendLog(`final_reply: ${truncatePreview(next.text)}`);
|
|
809
1109
|
trackWechatForwardTask(outputBatcher.flushNow().then(async () => {
|
|
810
1110
|
await channelPort.send({
|
|
811
|
-
target:
|
|
812
|
-
channelId: "wechat",
|
|
813
|
-
conversationId: authorizedUserId,
|
|
814
|
-
recipientId: authorizedUserId,
|
|
815
|
-
},
|
|
1111
|
+
target: eventTarget,
|
|
816
1112
|
kind: "final_reply",
|
|
817
1113
|
text: next.text,
|
|
818
1114
|
adapter: options.adapter,
|
|
@@ -830,7 +1126,7 @@ function wireAdapterEvents(params) {
|
|
|
830
1126
|
stateStore.appendLog(`${next.level}_notice: ${truncatePreview(next.text)}`);
|
|
831
1127
|
if (shouldForwardBridgeEventToWechat(options.adapter, next.type, { text: next.text })) {
|
|
832
1128
|
trackWechatForwardTask(outputBatcher.flushNow().then(async () => {
|
|
833
|
-
await queueWechatMessage(authorizedUserId, next.text, "notice");
|
|
1129
|
+
await queueWechatMessage(authorizedUserId, next.text, "notice", eventTarget);
|
|
834
1130
|
}));
|
|
835
1131
|
}
|
|
836
1132
|
},
|
|
@@ -840,7 +1136,9 @@ function wireAdapterEvents(params) {
|
|
|
840
1136
|
if (thinkingPreview) {
|
|
841
1137
|
stateStore.appendLog(`thinking: ${thinkingPreview}`);
|
|
842
1138
|
trackWechatForwardTask((async () => {
|
|
843
|
-
await queueWechatMessage(authorizedUserId,
|
|
1139
|
+
await queueWechatMessage(authorizedUserId, options.channelId === "wecom"
|
|
1140
|
+
? `Processing: ${thinkingPreview}`
|
|
1141
|
+
: `思考: ${thinkingPreview}`, "thinking", eventTarget);
|
|
844
1142
|
})());
|
|
845
1143
|
}
|
|
846
1144
|
}
|
|
@@ -850,7 +1148,7 @@ function wireAdapterEvents(params) {
|
|
|
850
1148
|
const pending = toPendingApproval(next.request);
|
|
851
1149
|
stateStore.setPendingConfirmation(pending);
|
|
852
1150
|
stateStore.appendLog(`Approval requested (${pending.source}): ${pending.commandPreview}`);
|
|
853
|
-
await queueWechatMessage(authorizedUserId, formatApprovalMessage(pending, adapterState), "approval_required");
|
|
1151
|
+
await queueWechatMessage(authorizedUserId, formatApprovalMessage(pending, adapterState), "approval_required", eventTarget);
|
|
854
1152
|
}));
|
|
855
1153
|
},
|
|
856
1154
|
userInputRequired: (next) => {
|
|
@@ -858,14 +1156,14 @@ function wireAdapterEvents(params) {
|
|
|
858
1156
|
const pending = toPendingUserInput(next.request);
|
|
859
1157
|
stateStore.setPendingUserInput(pending);
|
|
860
1158
|
stateStore.appendLog(`User input requested: questions=${pending.questions.length}`);
|
|
861
|
-
await queueWechatMessage(authorizedUserId, formatUserInputRequestMessage(pending, adapterState), "user_input_required");
|
|
1159
|
+
await queueWechatMessage(authorizedUserId, formatUserInputRequestMessage(pending, adapterState), "user_input_required", eventTarget);
|
|
862
1160
|
}));
|
|
863
1161
|
},
|
|
864
1162
|
mirroredUserInput: (next) => {
|
|
865
1163
|
stateStore.appendLog(`mirrored_local_input: ${truncatePreview(next.text)}`);
|
|
866
1164
|
if (shouldForwardBridgeEventToWechat(options.adapter, next.type, { text: next.text })) {
|
|
867
1165
|
trackWechatForwardTask(outputBatcher.flushNow().then(async () => {
|
|
868
|
-
await queueWechatMessage(authorizedUserId, formatMirroredUserInputMessage(options.adapter, next.text), "mirrored_user_input");
|
|
1166
|
+
await queueWechatMessage(authorizedUserId, formatMirroredUserInputMessage(options.adapter, next.text), "mirrored_user_input", eventTarget);
|
|
869
1167
|
}));
|
|
870
1168
|
}
|
|
871
1169
|
},
|
|
@@ -875,7 +1173,7 @@ function wireAdapterEvents(params) {
|
|
|
875
1173
|
stateStore.appendLog(`session_switched: ${next.sessionId} source=${next.source} reason=${next.reason}`);
|
|
876
1174
|
if (shouldForwardSessionSwitchEvent(next.reason) && shouldForwardBridgeEventToWechat(options.adapter, next.type)) {
|
|
877
1175
|
trackWechatForwardTask(outputBatcher.flushNow().then(async () => {
|
|
878
|
-
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);
|
|
879
1177
|
}));
|
|
880
1178
|
}
|
|
881
1179
|
},
|
|
@@ -893,7 +1191,7 @@ function wireAdapterEvents(params) {
|
|
|
893
1191
|
}
|
|
894
1192
|
if (shouldForwardSessionSwitchEvent(next.reason) && shouldForwardBridgeEventToWechat(options.adapter, next.type)) {
|
|
895
1193
|
trackWechatForwardTask(outputBatcher.flushNow().then(async () => {
|
|
896
|
-
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);
|
|
897
1195
|
}));
|
|
898
1196
|
}
|
|
899
1197
|
void maybeDrainDeferredInboundMessages();
|
|
@@ -902,7 +1200,7 @@ function wireAdapterEvents(params) {
|
|
|
902
1200
|
trackWechatForwardTask(outputBatcher.flushNow().then(async () => {
|
|
903
1201
|
stateStore.clearPendingConfirmation();
|
|
904
1202
|
stateStore.clearPendingUserInput();
|
|
905
|
-
clearActiveTask();
|
|
1203
|
+
clearActiveTask(eventTask);
|
|
906
1204
|
await maybeDrainDeferredInboundMessages();
|
|
907
1205
|
}));
|
|
908
1206
|
},
|
|
@@ -910,8 +1208,8 @@ function wireAdapterEvents(params) {
|
|
|
910
1208
|
trackWechatForwardTask(outputBatcher.flushNow().then(async () => {
|
|
911
1209
|
stateStore.clearPendingConfirmation();
|
|
912
1210
|
stateStore.clearPendingUserInput();
|
|
913
|
-
clearActiveTask();
|
|
914
|
-
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);
|
|
915
1213
|
await maybeDrainDeferredInboundMessages();
|
|
916
1214
|
}));
|
|
917
1215
|
},
|
|
@@ -920,9 +1218,9 @@ function wireAdapterEvents(params) {
|
|
|
920
1218
|
stateStore.appendLog(`fatal_error: ${next.message}`);
|
|
921
1219
|
stateStore.clearPendingConfirmation();
|
|
922
1220
|
stateStore.clearPendingUserInput();
|
|
923
|
-
clearActiveTask();
|
|
1221
|
+
clearActiveTask(eventTask);
|
|
924
1222
|
trackWechatForwardTask(outputBatcher.flushNow().then(async () => {
|
|
925
|
-
await queueWechatMessage(authorizedUserId, formatUserFacingBridgeFatalError(next.message), "fatal_error");
|
|
1223
|
+
await queueWechatMessage(authorizedUserId, formatUserFacingBridgeFatalError(next.message), "fatal_error", eventTarget);
|
|
926
1224
|
await maybeDrainDeferredInboundMessages();
|
|
927
1225
|
}));
|
|
928
1226
|
},
|
|
@@ -950,11 +1248,13 @@ function formatInboundMessagePreview(message) {
|
|
|
950
1248
|
return "(empty)";
|
|
951
1249
|
}
|
|
952
1250
|
async function handleInboundMessage(params) {
|
|
953
|
-
const { message, } = params;
|
|
954
|
-
const { options, stateStore, adapter, resumeCoordinator, queueWechatMessage, outputBatcher, clearActiveTask, deferInboundMessage, } = params;
|
|
1251
|
+
const { message, channelMessage, } = params;
|
|
1252
|
+
const { options, stateStore, adapter, resumeCoordinator, queueWechatMessage, outputBatcher, clearActiveTask, dispatchInboundText, deferInboundMessage, } = params;
|
|
955
1253
|
const state = stateStore.getState();
|
|
956
1254
|
if (message.senderId !== state.authorizedUserId) {
|
|
957
|
-
await queueWechatMessage(message.senderId,
|
|
1255
|
+
await queueWechatMessage(message.senderId, options.channelId === "wecom"
|
|
1256
|
+
? "Unauthorized. This bridge only accepts messages from the paired WeCom operator."
|
|
1257
|
+
: "Unauthorized. This bridge only accepts messages from the configured WeChat owner.");
|
|
958
1258
|
return null;
|
|
959
1259
|
}
|
|
960
1260
|
const systemCommand = parseWechatControlCommand(message.text, {
|
|
@@ -963,12 +1263,16 @@ async function handleInboundMessage(params) {
|
|
|
963
1263
|
hasPendingUserInput: Boolean(state.pendingUserInput),
|
|
964
1264
|
});
|
|
965
1265
|
switch (systemCommand?.type) {
|
|
1266
|
+
case "model":
|
|
1267
|
+
case "plan":
|
|
1268
|
+
await queueWechatMessage(message.senderId, await handleAdapterControl(adapter, message.senderId, systemCommand));
|
|
1269
|
+
return null;
|
|
966
1270
|
case "status":
|
|
967
1271
|
await queueWechatMessage(message.senderId, formatStatusReport(stateStore.getState(), adapter.getState()));
|
|
968
1272
|
return null;
|
|
969
1273
|
case "resume": {
|
|
970
1274
|
if (!isWechatResumeEnabled(options.adapter)) {
|
|
971
|
-
await queueWechatMessage(message.senderId,
|
|
1275
|
+
await queueWechatMessage(message.senderId, `${options.channelId === "wecom" ? "WeCom" : "WeChat"} /resume is disabled in ${options.adapter} mode. Use /resume directly inside "${options.channelId}-${options.adapter}"; the remote channel will follow the active local session.`);
|
|
972
1276
|
return null;
|
|
973
1277
|
}
|
|
974
1278
|
try {
|
|
@@ -1081,7 +1385,7 @@ async function handleInboundMessage(params) {
|
|
|
1081
1385
|
}
|
|
1082
1386
|
const adapterState = adapter.getState();
|
|
1083
1387
|
const routeResult = await routeBridgeMessage({
|
|
1084
|
-
message: toChannelInboundMessage(message),
|
|
1388
|
+
message: channelMessage ?? toChannelInboundMessage(message),
|
|
1085
1389
|
authorized: true,
|
|
1086
1390
|
command: null,
|
|
1087
1391
|
adapterState,
|
|
@@ -1117,29 +1421,27 @@ async function handleInboundMessage(params) {
|
|
|
1117
1421
|
defer: async () => {
|
|
1118
1422
|
await deferInboundMessage(message);
|
|
1119
1423
|
},
|
|
1120
|
-
dispatch:
|
|
1121
|
-
return await dispatchInboundWechatText({
|
|
1122
|
-
message,
|
|
1123
|
-
options,
|
|
1124
|
-
stateStore,
|
|
1125
|
-
adapter,
|
|
1126
|
-
});
|
|
1127
|
-
},
|
|
1424
|
+
dispatch: dispatchInboundText,
|
|
1128
1425
|
});
|
|
1129
1426
|
return routeResult.kind === "dispatched" ? routeResult.result : null;
|
|
1130
1427
|
}
|
|
1428
|
+
function createActiveTask(message) {
|
|
1429
|
+
return {
|
|
1430
|
+
startedAt: Date.now(),
|
|
1431
|
+
inputPreview: truncatePreview(formatInboundMessagePreview(message), 180),
|
|
1432
|
+
};
|
|
1433
|
+
}
|
|
1131
1434
|
async function dispatchInboundWechatText(params) {
|
|
1132
1435
|
const { message, options, stateStore, adapter } = params;
|
|
1133
1436
|
const preview = formatInboundMessagePreview(message);
|
|
1134
|
-
const activeTask =
|
|
1135
|
-
startedAt: Date.now(),
|
|
1136
|
-
inputPreview: truncatePreview(preview, 180),
|
|
1137
|
-
};
|
|
1437
|
+
const activeTask = params.activeTask ?? createActiveTask(message);
|
|
1138
1438
|
stateStore.appendLog(`Forwarded input to ${options.adapter}: ${truncatePreview(preview)}`);
|
|
1139
|
-
await adapter.sendInput(
|
|
1439
|
+
await adapter.sendInput(options.channelId === "wecom"
|
|
1440
|
+
? buildWecomInboundPrompt(message.text, message.attachments)
|
|
1441
|
+
: buildWechatInboundPrompt(message.text, message.attachments));
|
|
1140
1442
|
return activeTask;
|
|
1141
1443
|
}
|
|
1142
|
-
const isDirectRun =
|
|
1444
|
+
const isDirectRun = isDirectModuleRun(import.meta.url, process.argv, import.meta.main);
|
|
1143
1445
|
if (isDirectRun) {
|
|
1144
1446
|
main().catch((err) => {
|
|
1145
1447
|
logError(describeWechatTransportError(err));
|