cli-wechat-bridge 1.1.7 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/LICENSE.txt +661 -661
  2. package/README.md +461 -434
  3. package/bin/_run-entry.mjs +29 -6
  4. package/bin/wechat-check-update.mjs +6 -1
  5. package/bin/wechat-claude.mjs +6 -1
  6. package/bin/wechat-codex.mjs +6 -1
  7. package/bin/wechat-daemon.mjs +6 -1
  8. package/bin/wechat-opencode.mjs +6 -1
  9. package/bin/wechat-pi.mjs +6 -1
  10. package/bin/wechat-setup.mjs +6 -1
  11. package/bin/wecom-check-update.mjs +6 -1
  12. package/bin/wecom-claude.mjs +6 -1
  13. package/bin/wecom-codex.mjs +6 -1
  14. package/bin/wecom-daemon.mjs +6 -1
  15. package/bin/wecom-opencode.mjs +6 -1
  16. package/bin/wecom-pi.mjs +6 -1
  17. package/bin/wecom-setup.mjs +6 -1
  18. package/dist/bridge/adapter-control.js +58 -0
  19. package/dist/bridge/bridge-adapters.claude.js +35 -1
  20. package/dist/bridge/bridge-adapters.codex.js +118 -7
  21. package/dist/bridge/bridge-adapters.core.js +25 -1
  22. package/dist/bridge/bridge-adapters.opencode.js +156 -1
  23. package/dist/bridge/bridge-adapters.pi.js +45 -0
  24. package/dist/bridge/bridge-adapters.shared.js +6 -0
  25. package/dist/bridge/bridge-controller.js +6 -0
  26. package/dist/bridge/bridge-final-reply.js +6 -0
  27. package/dist/bridge/bridge-process-reaper.js +6 -0
  28. package/dist/bridge/bridge-state.js +6 -0
  29. package/dist/bridge/bridge-utils.js +6 -31
  30. package/dist/bridge/claude-hook.js +6 -0
  31. package/dist/bridge/claude-native-controls.js +133 -0
  32. package/dist/bridge/native-terminal-control.js +259 -0
  33. package/dist/bridge/wechat-bridge.js +250 -274
  34. package/dist/bridge/wechat-forwarding.js +6 -0
  35. package/dist/bridge/wechat-outbound-queue.js +6 -0
  36. package/dist/channels/wechat/bridge-final-reply.js +13 -22
  37. package/dist/channels/wechat/index.js +6 -0
  38. package/dist/channels/wechat/wechat-driver.js +191 -0
  39. package/dist/channels/wechat/wechat-forwarding.js +18 -0
  40. package/dist/channels/wechat/wechat-outbound-queue.js +39 -0
  41. package/dist/channels/wecom/bridge-final-reply.js +6 -0
  42. package/dist/channels/wecom/setup-cli.js +6 -0
  43. package/dist/channels/wecom/setup.js +6 -0
  44. package/dist/channels/wecom/wecom-config.js +6 -0
  45. package/dist/channels/wecom/wecom-driver.js +106 -0
  46. package/dist/channels/wecom/wecom-media.js +6 -0
  47. package/dist/channels/wecom/wecom-transport.js +6 -0
  48. package/dist/commands/check-update.js +6 -0
  49. package/dist/companion/codex-remote-client.js +28 -2
  50. package/dist/companion/codex-visible-client-link.js +107 -0
  51. package/dist/companion/local-companion-link.js +6 -0
  52. package/dist/companion/local-companion-start.js +13 -2
  53. package/dist/companion/local-companion.js +23 -0
  54. package/dist/companion/opencode-tui-bridge-plugin.js +6 -0
  55. package/dist/companion/pi-tui-bridge-extension.js +62 -3
  56. package/dist/core/bridge-control.js +10 -0
  57. package/dist/core/bridge-defer.js +25 -0
  58. package/dist/core/channel-driver.js +1 -0
  59. package/dist/core/conversation-routing.js +58 -0
  60. package/dist/core/direct-run.js +6 -0
  61. package/dist/core/inbound-message-claims.js +6 -0
  62. package/dist/core/outbound-target.js +32 -0
  63. package/dist/core/text-utils.js +6 -0
  64. package/dist/core/turn-coordinator.js +85 -0
  65. package/dist/daemon/daemon-link.js +6 -0
  66. package/dist/daemon/emoji-bindings.js +6 -0
  67. package/dist/daemon/wechat-daemon.js +529 -205
  68. package/dist/i18n/index.js +6 -0
  69. package/dist/i18n/messages-en.js +7 -1
  70. package/dist/i18n/messages-zh.js +7 -1
  71. package/dist/runtime/create-runtime-host.js +6 -0
  72. package/dist/runtime/legacy-adapter-runtime.js +15 -0
  73. package/dist/runtime/runtime-types.js +3 -0
  74. package/dist/utils/atomic-file.js +6 -0
  75. package/dist/utils/doctor.js +6 -0
  76. package/dist/utils/version-checker.js +17 -11
  77. package/dist/wechat/channel-config.js +6 -0
  78. package/dist/wechat/setup.js +6 -0
  79. package/dist/wechat/wechat-channel.js +6 -0
  80. package/dist/wechat/wechat-transport.js +169 -58
  81. package/package.json +108 -106
  82. package/scripts/ensure-node-pty-permissions.mjs +73 -73
@@ -1,26 +1,37 @@
1
1
  #!/usr/bin/env bun
2
+ // SPDX-License-Identifier: AGPL-3.0-or-later
3
+ // Copyright (c) 2026 UNLINEARITY <unlinearity@gmail.com>
4
+ // Source: https://github.com/UNLINEARITY/CLI-WeChat-Bridge
5
+ // This file is part of CLI-WeChat-Bridge. Modifications and derivative works
6
+ // must be released under AGPL-3.0-or-later with full source code; see
7
+ // LICENSE.txt. Network services built on it must offer source to users.
2
8
  import path from "node:path";
3
9
  import { resolveDefaultAdapterCommand, } from "./bridge-adapters.js";
4
10
  import { delay } from "./bridge-adapters.shared.js";
5
11
  import { t } from "../i18n/index.js";
6
12
  import { BridgeController } from "./bridge-controller.js";
7
13
  import { ResumeSessionCoordinator, isWechatResumeEnabled, shouldForwardSessionSwitchEvent, } from "./bridge-session-resume.js";
8
- import { WECHAT_SEND_MAX_ATTEMPTS, computeWechatSendRetryDelayMs, formatUserFacingBridgeFatalError, formatUserFacingInboundError, formatWechatContextTokenStaleLogEntry, formatWechatSendFailureLogEntry, formatWechatSendRetryLogEntry, isWechatContextUnavailableError, isRetryableWechatSendError, shouldForwardBridgeEventToWechat, shouldSuppressCodexLocalThreadNotice, } from "../channels/wechat/wechat-forwarding.js";
14
+ import { formatUserFacingBridgeFatalError, formatUserFacingInboundError, shouldForwardBridgeEventToWechat, shouldSuppressCodexLocalThreadNotice, } from "../channels/wechat/wechat-forwarding.js";
9
15
  import { ensureWechatCredentials } from "../wechat/setup.js";
10
16
  import { BridgeStateStore } from "./bridge-state.js";
11
17
  import { getProcessRecordByPid, reapOrphanedOpencodeProcesses, reapPeerBridgeProcesses, } from "./bridge-process-reaper.js";
12
18
  import { createRuntimeHost } from "../runtime/create-runtime-host.js";
13
19
  import { toChannelInboundMessage } from "../channels/wechat/channel-message.js";
14
20
  import { routeBridgeMessage } from "../core/bridge-message-router.js";
21
+ import { canDrainDeferredCodexInboundQueue, formatDeferredCodexInboundQueueMessage, isRetryableDeferredCodexDrainError, shouldDeferCodexInboundMessage, } from "../core/bridge-defer.js";
22
+ import { TurnCoordinator } from "../core/turn-coordinator.js";
23
+ import { resolveOutboundConversationTarget } from "../core/outbound-target.js";
24
+ import { handleAdapterControl } from "./adapter-control.js";
15
25
  import { forwardBridgeEvent } from "../core/bridge-event-forwarder.js";
16
26
  import { isDirectModuleRun } from "../core/direct-run.js";
17
27
  import { WechatChannelPort } from "../channels/wechat/wechat-channel-port.js";
18
28
  import { ensureWecomAccount } from "../channels/wecom/setup.js";
19
29
  import { WecomChannelPort } from "../channels/wecom/wecom-channel-port.js";
20
30
  import { WecomTransport } from "../channels/wecom/wecom-transport.js";
21
- import { buildWecomInboundPrompt, formatWecomVisibleText, } from "../channels/wecom/wecom-message.js";
31
+ import { WecomChannelDriver } from "../channels/wecom/wecom-driver.js";
32
+ import { WechatChannelDriver } from "../channels/wechat/wechat-driver.js";
22
33
  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";
23
- import { classifyWechatTransportError, DEFAULT_LONG_POLL_TIMEOUT_MS, WeChatTransport, describeWechatTransportError, isWechatContextTokenStaleError, } from "../wechat/wechat-transport.js";
34
+ import { classifyWechatTransportError, DEFAULT_LONG_POLL_TIMEOUT_MS, WeChatTransport, describeWechatTransportError, } from "../wechat/wechat-transport.js";
24
35
  import { getPendingChannelMessagesFile, getPendingWechatMessagesFile, PendingWechatMessageStore, } from "../channels/wechat/wechat-outbound-queue.js";
25
36
  import { checkForUpdate, formatUpdateMessage, } from "../utils/version-checker.js";
26
37
  import { clearDaemonEndpoint, isDaemonEndpointAlive, readDaemonEndpoint, } from "../daemon/daemon-link.js";
@@ -78,31 +89,7 @@ function toPendingUserInput(request) {
78
89
  createdAt: nowIso(),
79
90
  };
80
91
  }
81
- export function shouldDeferCodexInboundMessage(params) {
82
- return (params.adapter === "codex" &&
83
- !params.hasPendingConfirmation &&
84
- !params.hasSystemCommand &&
85
- params.activeTurnOrigin === "local" &&
86
- (params.status === "busy" || params.status === "awaiting_approval"));
87
- }
88
- export function canDrainDeferredCodexInboundQueue(params) {
89
- return (params.adapter === "codex" &&
90
- params.deferredCount > 0 &&
91
- !params.hasPendingConfirmation &&
92
- !params.hasPendingUserInput &&
93
- !params.hasPendingApproval &&
94
- !params.hasActiveTask &&
95
- !params.activeTurnId &&
96
- params.status !== "busy" &&
97
- params.status !== "awaiting_approval" &&
98
- params.status !== "awaiting_input");
99
- }
100
- export function formatDeferredCodexInboundQueueMessage(queuePosition) {
101
- return `Queued for delivery after the current local Codex turn finishes. Queue position: ${queuePosition}.`;
102
- }
103
- export function isRetryableDeferredCodexDrainError(errorText) {
104
- return /still working|approval request is pending|waiting for local terminal input/i.test(errorText);
105
- }
92
+ export { canDrainDeferredCodexInboundQueue, formatDeferredCodexInboundQueueMessage, isRetryableDeferredCodexDrainError, shouldDeferCodexInboundMessage, } from "../core/bridge-defer.js";
106
93
  export function parseCliArgs(argv) {
107
94
  let adapter = null;
108
95
  let commandOverride;
@@ -189,24 +176,6 @@ export function parseCliArgs(argv) {
189
176
  channelId,
190
177
  };
191
178
  }
192
- function toWecomOutputKind(context) {
193
- const known = new Set([
194
- "status",
195
- "notice",
196
- "thinking",
197
- "final_reply",
198
- "approval_required",
199
- "user_input_required",
200
- "mirrored_input",
201
- "task_failed",
202
- "fatal_error",
203
- ]);
204
- return known.has(context)
205
- ? context
206
- : context === "mirrored_user_input"
207
- ? "mirrored_input"
208
- : "notice";
209
- }
210
179
  function toWechatSendContext(kind) {
211
180
  if (kind === "mirrored_input") {
212
181
  return "mirrored_user_input";
@@ -283,6 +252,18 @@ async function main() {
283
252
  },
284
253
  })
285
254
  : null;
255
+ const channelDriver = wecomTransport
256
+ ? new WecomChannelDriver({
257
+ transport: wecomTransport,
258
+ accountId: wecomAccount?.botId,
259
+ operatorId: credentials.userId,
260
+ logError,
261
+ })
262
+ : new WechatChannelDriver({
263
+ transport,
264
+ logError,
265
+ buildInboundPrompt: (text, attachments) => buildWechatInboundPrompt(text, attachments.filter((attachment) => attachment.kind === "image" || attachment.kind === "file")),
266
+ });
286
267
  // 非阻塞地检查更新(不影响启动速度,也避免首次登录时打断二维码输出)
287
268
  // unref:不能让这个延迟检查把 event loop 挂活(如 --doctor 或快速退出场景),
288
269
  // 否则会与强制退出 teardown 竞态。
@@ -361,16 +342,13 @@ async function main() {
361
342
  let textSendChain = Promise.resolve();
362
343
  let attachmentSendChain = Promise.resolve();
363
344
  const pendingWechatForwardTasks = new Set();
364
- let activeTask = null;
365
- let currentWecomConversation = null;
366
- let activeWecomConversation = null;
367
- let lastWecomConversation = {
368
- channelId: "wecom",
369
- accountId: wecomAccount?.botId,
370
- conversationId: credentials.userId,
371
- recipientId: credentials.userId,
372
- metadata: { chatType: "direct" },
373
- };
345
+ const defaultConversation = channelDriver.defaultConversation();
346
+ const operatorId = credentials.userId;
347
+ const operatorConversation = defaultConversation
348
+ ?? channelDriver.directConversation(operatorId);
349
+ const turns = new TurnCoordinator({
350
+ initialLastConversation: defaultConversation,
351
+ });
374
352
  const deferredInboundMessages = [];
375
353
  let drainingDeferredInboundMessages = false;
376
354
  let consecutivePollFailures = 0;
@@ -386,92 +364,28 @@ async function main() {
386
364
  return run;
387
365
  };
388
366
  const sendWechatMessageNow = async (senderId, text, context = "message", targetOverride) => {
389
- if (options.channelId === "wecom") {
390
- const target = targetOverride ??
391
- (senderId === credentials.userId
392
- ? currentWecomConversation ??
393
- activeWecomConversation ??
394
- lastWecomConversation
395
- : {
396
- channelId: "wecom",
397
- accountId: wecomAccount?.botId,
398
- conversationId: senderId,
399
- recipientId: senderId,
400
- metadata: { chatType: "direct" },
401
- });
402
- const startedAtMs = Date.now();
403
- stateStore.appendLog(`wecom_send_started: context=${context} recipient=${target.recipientId} chars=${Array.from(text).length}`);
404
- try {
405
- await wecomTransport.sendText(target, formatWecomVisibleText(text), toWecomOutputKind(context));
406
- stateStore.appendLog(`wecom_send_completed: context=${context} recipient=${target.recipientId} elapsed_ms=${Date.now() - startedAtMs}`);
407
- return { status: "sent" };
408
- }
409
- catch (error) {
410
- const message = error instanceof Error ? error.message : String(error);
411
- stateStore.appendLog(`wecom_send_failed: context=${context} recipient=${target.recipientId} error=${truncatePreview(message, 400)}`);
412
- logError(`Failed to send WeCom ${context}: ${message}`);
413
- return { status: "context_unavailable", error, target };
414
- }
415
- }
416
- const startedAtMs = Date.now();
417
- stateStore.appendLog(`wechat_send_started: context=${context} recipient=${senderId} chars=${Array.from(text).length}`);
418
- for (let attempt = 1; attempt <= WECHAT_SEND_MAX_ATTEMPTS; attempt += 1) {
419
- try {
420
- await transport.sendText(senderId, text);
421
- stateStore.appendLog(`wechat_send_completed: context=${context} recipient=${senderId} attempt=${attempt} elapsed_ms=${Date.now() - startedAtMs}`);
422
- return { status: "sent" };
423
- }
424
- catch (err) {
425
- if (err instanceof Error && err.name === "AbortError") {
426
- stateStore.appendLog(`wechat_send_timeout: context=${context} recipient=${senderId} attempt=${attempt} elapsed_ms=${Date.now() - startedAtMs}`);
427
- }
428
- if (isWechatContextUnavailableError(err)) {
429
- if (isWechatContextTokenStaleError(err)) {
430
- transport.clearCachedContextToken(senderId);
431
- }
432
- const hint = "WeChat conversation context is stale or unavailable. Ask the WeChat owner to send any message first, then local terminal replies can sync back to WeChat.";
433
- logError(`Failed to send WeChat ${context}: ${hint}`);
434
- stateStore.appendLog(isWechatContextTokenStaleError(err)
435
- ? formatWechatContextTokenStaleLogEntry({
436
- context,
437
- recipientId: senderId,
438
- error: err,
439
- })
440
- : formatWechatSendFailureLogEntry({
441
- context,
442
- recipientId: senderId,
443
- error: err,
444
- }));
445
- return { status: "context_unavailable", error: err };
446
- }
447
- if (attempt < WECHAT_SEND_MAX_ATTEMPTS && isRetryableWechatSendError(err)) {
448
- const delayMs = computeWechatSendRetryDelayMs(attempt);
449
- logError(`Failed to send WeChat ${context} (attempt ${attempt}). Retrying in ${formatDuration(delayMs)}. ${describeWechatTransportError(err)}`);
450
- stateStore.appendLog(formatWechatSendRetryLogEntry({
451
- context,
452
- recipientId: senderId,
453
- attempt,
454
- delayMs,
455
- error: err,
456
- }));
457
- await delay(delayMs);
458
- continue;
459
- }
460
- logError(`Failed to send WeChat ${context}: ${describeWechatTransportError(err)}`);
461
- stateStore.appendLog(formatWechatSendFailureLogEntry({
462
- context,
463
- recipientId: senderId,
464
- error: err,
465
- }));
466
- return { status: "failed" };
467
- }
468
- }
469
- return { status: "failed" };
367
+ const target = resolveOutboundConversationTarget({
368
+ senderId,
369
+ operatorId,
370
+ override: targetOverride,
371
+ multiConversation: channelDriver.capabilities.multiConversation,
372
+ operatorTarget: turns.resolveTarget(operatorConversation),
373
+ directConversation: (id) => channelDriver.directConversation(id),
374
+ });
375
+ return channelDriver.sendText({
376
+ target,
377
+ text,
378
+ context,
379
+ log: (entry) => stateStore.appendLog(entry),
380
+ });
470
381
  };
471
382
  const queueWechatMessage = (senderId, text, context = "message", targetOverride) => {
383
+ const queuedTarget = senderId === operatorId && channelDriver.capabilities.multiConversation
384
+ ? targetOverride ?? turns.resolveTarget(operatorConversation)
385
+ : targetOverride;
472
386
  return queueWechatTextAction(async () => {
473
- const result = await sendWechatMessageNow(senderId, text, context, targetOverride);
474
- if (result.status === "context_unavailable") {
387
+ const result = await sendWechatMessageNow(senderId, text, context, queuedTarget);
388
+ if (result.status === "target_stale") {
475
389
  const pending = pendingWechatMessages.enqueue(senderId, text, context, result.target);
476
390
  if (pending) {
477
391
  stateStore.appendLog(`wechat_send_queued: id=${pending.id} context=${context} recipient=${senderId} pending=${pendingWechatMessages.list().length}`);
@@ -489,7 +403,7 @@ async function main() {
489
403
  stateStore.appendLog(`wechat_pending_sent: id=${pending.id} context=${pending.context} recipient=${pending.recipientId}`);
490
404
  continue;
491
405
  }
492
- if (result.status === "context_unavailable") {
406
+ if (result.status === "target_stale") {
493
407
  break;
494
408
  }
495
409
  stateStore.appendLog(`wechat_pending_retryable_failure: id=${pending.id} context=${pending.context} recipient=${pending.recipientId}`);
@@ -514,7 +428,9 @@ async function main() {
514
428
  }
515
429
  };
516
430
  const outputBatcher = new OutputBatcher(async (text) => {
517
- await queueWechatMessage(stateStore.getState().authorizedUserId, text);
431
+ await queueWechatMessage(stateStore.getState().authorizedUserId, text, "message", channelDriver.capabilities.multiConversation
432
+ ? turns.resolveTarget(operatorConversation)
433
+ : undefined);
518
434
  });
519
435
  const maybeDrainDeferredInboundMessages = async () => {
520
436
  if (drainingDeferredInboundMessages || !ensureRuntimeOwnership()) {
@@ -529,7 +445,7 @@ async function main() {
529
445
  hasPendingConfirmation: Boolean(stateStore.getState().pendingConfirmation),
530
446
  hasPendingUserInput: Boolean(stateStore.getState().pendingUserInput),
531
447
  hasPendingApproval: Boolean(adapterState.pendingApproval),
532
- hasActiveTask: Boolean(activeTask),
448
+ hasActiveTask: turns.hasActiveTask,
533
449
  })) {
534
450
  return;
535
451
  }
@@ -540,17 +456,26 @@ async function main() {
540
456
  drainingDeferredInboundMessages = true;
541
457
  try {
542
458
  stateStore.appendLog(`draining_deferred_inbound_input: remaining=${deferredInboundMessages.length} text=${truncatePreview(nextDeferred.message.text)}`);
543
- if (nextDeferred.channelMessage) {
544
- activeWecomConversation = nextDeferred.channelMessage.conversation;
545
- lastWecomConversation = nextDeferred.channelMessage.conversation;
459
+ const nextTask = createActiveTask(nextDeferred.message);
460
+ const lease = turns.beginTurn(nextTask, nextDeferred.channelMessage?.conversation);
461
+ if (!lease) {
462
+ deferredInboundMessages.unshift(nextDeferred);
463
+ return;
464
+ }
465
+ try {
466
+ await dispatchInboundWechatText({
467
+ message: nextDeferred.message,
468
+ channelDriver,
469
+ options,
470
+ stateStore,
471
+ adapter,
472
+ activeTask: nextTask,
473
+ });
474
+ }
475
+ catch (error) {
476
+ turns.rollback(lease);
477
+ throw error;
546
478
  }
547
- const nextTask = await dispatchInboundWechatText({
548
- message: nextDeferred.message,
549
- options,
550
- stateStore,
551
- adapter,
552
- });
553
- activeTask = nextTask;
554
479
  }
555
480
  catch (err) {
556
481
  const errorText = err instanceof Error ? err.message : String(err);
@@ -565,7 +490,7 @@ async function main() {
565
490
  adapter: options.adapter,
566
491
  cwd: options.cwd,
567
492
  errorText,
568
- }), "inbound_error");
493
+ }), "inbound_error", nextDeferred.channelMessage?.conversation);
569
494
  }
570
495
  finally {
571
496
  drainingDeferredInboundMessages = false;
@@ -638,6 +563,12 @@ async function main() {
638
563
  catch {
639
564
  // Best effort shutdown.
640
565
  }
566
+ try {
567
+ await channelDriver.endAllTyping?.();
568
+ }
569
+ catch {
570
+ // Best effort shutdown.
571
+ }
641
572
  controller.clearLocalClientEndpoint();
642
573
  stateStore.releaseLock();
643
574
  };
@@ -706,6 +637,7 @@ async function main() {
706
637
  try {
707
638
  wireAdapterEvents({
708
639
  adapter,
640
+ channelDriver,
709
641
  resumeCoordinator,
710
642
  options,
711
643
  transport,
@@ -715,9 +647,11 @@ async function main() {
715
647
  queueWechatMessage,
716
648
  trackWechatForwardTask,
717
649
  maybeDrainDeferredInboundMessages,
718
- clearActiveTask: () => {
719
- activeTask = null;
720
- activeWecomConversation = null;
650
+ getActiveTask: () => turns.activeTask,
651
+ clearActiveTask: (expectedTask) => {
652
+ if (expectedTask) {
653
+ turns.complete(expectedTask);
654
+ }
721
655
  },
722
656
  syncSharedSessionState: () => {
723
657
  syncSharedSessionState(stateStore, adapter);
@@ -727,7 +661,7 @@ async function main() {
727
661
  },
728
662
  requestShutdown,
729
663
  wecomTransport,
730
- getWecomTarget: () => activeWecomConversation ?? lastWecomConversation,
664
+ getWecomTarget: () => turns.resolveTarget(operatorConversation),
731
665
  });
732
666
  await adapter.start();
733
667
  if (!ensureRuntimeOwnership()) {
@@ -736,97 +670,115 @@ async function main() {
736
670
  syncSharedSessionState(stateStore, adapter);
737
671
  controller.syncLocalClientEndpoint();
738
672
  stateStore.appendLog(`Bridge started with channel=${options.channelId} adapter=${options.adapter} command=${options.command} cwd=${options.cwd}`);
739
- if (wecomTransport) {
740
- wecomTransport.setHandlers({
741
- onMessage: async (channelMessage) => {
673
+ if (channelDriver.start) {
674
+ await channelDriver.start({
675
+ onInboundMessage: async (channelMessage) => {
742
676
  if (shutdownPromise || !ensureRuntimeOwnership()) {
743
677
  return;
744
678
  }
745
- currentWecomConversation = channelMessage.conversation;
746
- lastWecomConversation = channelMessage.conversation;
679
+ turns.observeConversation(channelMessage.conversation);
680
+ const queueInboundReply = (senderId, text, context) => queueWechatMessage(senderId, text, context, channelMessage.conversation);
681
+ if (pendingWechatMessages.list().length > 0) {
682
+ await flushPendingWechatMessages();
683
+ }
684
+ const message = toLegacyInboundWechatMessage(channelMessage);
685
+ const taskAtMessageStart = turns.activeTask;
686
+ stateStore.touchActivity(message.createdAt);
747
687
  try {
748
- if (pendingWechatMessages.list().length > 0) {
749
- await flushPendingWechatMessages();
750
- }
751
- const message = toLegacyInboundWechatMessage(channelMessage);
752
- stateStore.touchActivity(message.createdAt);
753
- let nextTask = null;
754
- try {
755
- nextTask = await handleInboundMessage({
756
- message,
757
- channelMessage,
758
- options,
759
- stateStore,
760
- adapter,
761
- resumeCoordinator,
762
- queueWechatMessage,
763
- outputBatcher,
764
- clearActiveTask: () => {
765
- activeTask = null;
766
- activeWecomConversation = null;
767
- },
768
- deferInboundMessage: async (nextMessage) => {
769
- deferredInboundMessages.push({
770
- message: nextMessage,
771
- channelMessage,
772
- });
773
- stateStore.appendLog(`deferred_inbound_input: position=${deferredInboundMessages.length} text=${truncatePreview(nextMessage.text)}`);
774
- await queueWechatMessage(nextMessage.senderId, formatDeferredCodexInboundQueueMessage(deferredInboundMessages.length));
775
- },
776
- });
777
- }
778
- catch (error) {
779
- const errorText = error instanceof Error ? error.message : String(error);
780
- logError(errorText);
781
- stateStore.appendLog(`inbound_error: ${errorText}`);
782
- await queueWechatMessage(message.senderId, formatUserFacingInboundError({
783
- adapter: options.adapter,
784
- cwd: options.cwd,
785
- errorText,
786
- }), "inbound_error");
787
- }
788
- if (nextTask) {
789
- activeTask = nextTask;
790
- activeWecomConversation = channelMessage.conversation;
791
- }
792
- syncSharedSessionState(stateStore, adapter);
793
- await maybeDrainDeferredInboundMessages();
688
+ await handleInboundMessage({
689
+ message,
690
+ channelMessage,
691
+ channelDriver,
692
+ options,
693
+ stateStore,
694
+ adapter,
695
+ resumeCoordinator,
696
+ queueWechatMessage: queueInboundReply,
697
+ outputBatcher,
698
+ clearActiveTask: () => {
699
+ if (taskAtMessageStart) {
700
+ turns.complete(taskAtMessageStart);
701
+ }
702
+ },
703
+ dispatchInboundText: async () => {
704
+ const nextActiveTask = createActiveTask(message);
705
+ const result = await turns.dispatch({
706
+ task: nextActiveTask,
707
+ conversation: channelMessage.conversation,
708
+ onBusy: async () => {
709
+ await queueInboundReply(message.senderId, `${options.adapter} is still working. Wait for the current reply or use /stop.`);
710
+ },
711
+ forward: async () => {
712
+ await dispatchInboundWechatText({
713
+ message,
714
+ channelDriver,
715
+ options,
716
+ stateStore,
717
+ adapter,
718
+ activeTask: nextActiveTask,
719
+ });
720
+ },
721
+ });
722
+ if (result.status === "dispatched") {
723
+ void channelDriver.beginTyping?.(message.senderId);
724
+ }
725
+ return result.status === "dispatched" ? nextActiveTask : null;
726
+ },
727
+ deferInboundMessage: async (nextMessage) => {
728
+ deferredInboundMessages.push({
729
+ message: nextMessage,
730
+ channelMessage,
731
+ });
732
+ stateStore.appendLog(`deferred_inbound_input: position=${deferredInboundMessages.length} text=${truncatePreview(nextMessage.text)}`);
733
+ await queueInboundReply(nextMessage.senderId, formatDeferredCodexInboundQueueMessage(deferredInboundMessages.length));
734
+ },
735
+ });
794
736
  }
795
- finally {
796
- currentWecomConversation = null;
737
+ catch (error) {
738
+ const errorText = error instanceof Error ? error.message : String(error);
739
+ logError(errorText);
740
+ stateStore.appendLog(`inbound_error: ${errorText}`);
741
+ await queueInboundReply(message.senderId, formatUserFacingInboundError({
742
+ adapter: options.adapter,
743
+ cwd: options.cwd,
744
+ errorText,
745
+ }), "inbound_error");
797
746
  }
747
+ syncSharedSessionState(stateStore, adapter);
748
+ await maybeDrainDeferredInboundMessages();
798
749
  },
799
- onUnauthorized: async (senderId, chatType) => {
750
+ onUnauthorizedSender: async (senderId, chatType) => {
800
751
  stateStore.appendLog(`wecom_unauthorized: sender=${senderId} chat_type=${chatType}`);
801
752
  if (chatType === "direct") {
802
- await wecomTransport.sendText({
803
- channelId: "wecom",
804
- accountId: wecomAccount.botId,
805
- conversationId: senderId,
806
- recipientId: senderId,
807
- metadata: { chatType: "direct" },
808
- }, "Unauthorized.", "notice");
753
+ const result = await channelDriver.sendText({
754
+ target: channelDriver.directConversation(senderId),
755
+ text: "Unauthorized.",
756
+ context: "notice",
757
+ log: (entry) => stateStore.appendLog(entry),
758
+ });
759
+ if (result.status !== "sent") {
760
+ logError(`Failed to reply unauthorized notice to ${senderId}`);
761
+ }
809
762
  }
810
763
  },
811
- onFatal: async (error) => {
764
+ onChannelFatal: async (error) => {
812
765
  stateStore.appendLog(`wecom_fatal_error: ${error.message}`);
813
766
  requestShutdown(error.message, 1);
814
767
  },
815
- onConnected: async () => {
768
+ onChannelConnected: async () => {
816
769
  if (pendingWechatMessages.list().length > 0) {
817
770
  await flushPendingWechatMessages();
818
771
  }
819
772
  },
820
773
  });
821
- wecomTransport.start();
822
- await wecomTransport.waitUntilConnected();
774
+ await channelDriver.waitUntilReady?.();
823
775
  }
824
- log(`${options.channelId === "wecom" ? "WeCom" : "WeChat"} bridge is ready for adapter "${options.adapter}".`);
776
+ log(`${channelDriver.displayName} bridge is ready for adapter "${options.adapter}".`);
825
777
  log(`Working directory: ${options.cwd}`);
826
778
  if (options.profile) {
827
779
  log(`Profile: ${options.profile}`);
828
780
  }
829
- log(`Authorized ${options.channelId === "wecom" ? "WeCom" : "WeChat"} user: ${credentials.userId}`);
781
+ log(`Authorized ${channelDriver.displayName} user: ${credentials.userId}`);
830
782
  if (options.adapter === "codex") {
831
783
  log("For source-mode debugging, open the visible Codex client with: npm run codex:panel");
832
784
  }
@@ -844,7 +796,7 @@ async function main() {
844
796
  cwd: options.cwd,
845
797
  });
846
798
  await queueWechatMessage(credentials.userId, welcomeText);
847
- if (options.channelId === "wecom") {
799
+ if (channelDriver.capabilities.pushInbound) {
848
800
  while (!shutdownPromise && ensureRuntimeOwnership()) {
849
801
  await delay(1_000);
850
802
  }
@@ -910,11 +862,12 @@ async function main() {
910
862
  if (!ensureRuntimeOwnership()) {
911
863
  break;
912
864
  }
865
+ const taskAtMessageStart = turns.activeTask;
913
866
  stateStore.touchActivity(message.createdAt);
914
- let nextTask = null;
915
867
  try {
916
- nextTask = await handleInboundMessage({
868
+ await handleInboundMessage({
917
869
  message,
870
+ channelDriver,
918
871
  options,
919
872
  stateStore,
920
873
  adapter,
@@ -922,7 +875,32 @@ async function main() {
922
875
  queueWechatMessage,
923
876
  outputBatcher,
924
877
  clearActiveTask: () => {
925
- activeTask = null;
878
+ if (taskAtMessageStart) {
879
+ turns.complete(taskAtMessageStart);
880
+ }
881
+ },
882
+ dispatchInboundText: async () => {
883
+ const nextActiveTask = createActiveTask(message);
884
+ const result = await turns.dispatch({
885
+ task: nextActiveTask,
886
+ onBusy: async () => {
887
+ await queueWechatMessage(message.senderId, `${options.adapter} is still working. Wait for the current reply or use /stop.`);
888
+ },
889
+ forward: async () => {
890
+ await dispatchInboundWechatText({
891
+ message,
892
+ channelDriver,
893
+ options,
894
+ stateStore,
895
+ adapter,
896
+ activeTask: nextActiveTask,
897
+ });
898
+ },
899
+ });
900
+ if (result.status === "dispatched") {
901
+ void channelDriver.beginTyping?.(message.senderId);
902
+ }
903
+ return result.status === "dispatched" ? nextActiveTask : null;
926
904
  },
927
905
  deferInboundMessage: async (nextMessage) => {
928
906
  deferredInboundMessages.push({
@@ -943,9 +921,6 @@ async function main() {
943
921
  errorText,
944
922
  }), "inbound_error");
945
923
  }
946
- if (nextTask) {
947
- activeTask = nextTask;
948
- }
949
924
  syncSharedSessionState(stateStore, adapter);
950
925
  await maybeDrainDeferredInboundMessages();
951
926
  }
@@ -980,7 +955,7 @@ function syncSharedSessionState(stateStore, adapter) {
980
955
  }
981
956
  }
982
957
  function wireAdapterEvents(params) {
983
- const { adapter, resumeCoordinator, options, transport, stateStore, outputBatcher, queueWechatAttachmentAction, queueWechatMessage, trackWechatForwardTask, maybeDrainDeferredInboundMessages, clearActiveTask, syncSharedSessionState, syncLocalClientEndpoint, requestShutdown, wecomTransport, getWecomTarget, } = params;
958
+ const { adapter, channelDriver, resumeCoordinator, options, transport, stateStore, outputBatcher, queueWechatAttachmentAction, queueWechatMessage, trackWechatForwardTask, maybeDrainDeferredInboundMessages, getActiveTask, clearActiveTask, syncSharedSessionState, syncLocalClientEndpoint, requestShutdown, wecomTransport, getWecomTarget, } = params;
984
959
  const channelPort = options.channelId === "wecom"
985
960
  ? new WecomChannelPort({
986
961
  transport: wecomTransport,
@@ -1016,6 +991,10 @@ function wireAdapterEvents(params) {
1016
991
  stateStore.clearPendingUserInput();
1017
992
  }
1018
993
  const authorizedUserId = stateStore.getState().authorizedUserId;
994
+ const eventTarget = channelDriver.capabilities.multiConversation
995
+ ? getWecomTarget()
996
+ : channelDriver.directConversation(authorizedUserId);
997
+ const eventTask = getActiveTask();
1019
998
  eventForwardChain = eventForwardChain
1020
999
  .then(() => forwardBridgeEvent(event, {
1021
1000
  stdout: (next) => {
@@ -1029,17 +1008,12 @@ function wireAdapterEvents(params) {
1029
1008
  }
1030
1009
  },
1031
1010
  finalReply: (next) => {
1011
+ void channelDriver.endTyping?.(authorizedUserId);
1032
1012
  lastFinalReplyAtMs = Date.now();
1033
1013
  stateStore.appendLog(`final_reply: ${truncatePreview(next.text)}`);
1034
1014
  trackWechatForwardTask(outputBatcher.flushNow().then(async () => {
1035
1015
  await channelPort.send({
1036
- target: options.channelId === "wecom"
1037
- ? getWecomTarget()
1038
- : {
1039
- channelId: "wechat",
1040
- conversationId: authorizedUserId,
1041
- recipientId: authorizedUserId,
1042
- },
1016
+ target: eventTarget,
1043
1017
  kind: "final_reply",
1044
1018
  text: next.text,
1045
1019
  adapter: options.adapter,
@@ -1057,7 +1031,7 @@ function wireAdapterEvents(params) {
1057
1031
  stateStore.appendLog(`${next.level}_notice: ${truncatePreview(next.text)}`);
1058
1032
  if (shouldForwardBridgeEventToWechat(options.adapter, next.type, { text: next.text })) {
1059
1033
  trackWechatForwardTask(outputBatcher.flushNow().then(async () => {
1060
- await queueWechatMessage(authorizedUserId, next.text, "notice");
1034
+ await queueWechatMessage(authorizedUserId, next.text, "notice", eventTarget);
1061
1035
  }));
1062
1036
  }
1063
1037
  },
@@ -1067,9 +1041,9 @@ function wireAdapterEvents(params) {
1067
1041
  if (thinkingPreview) {
1068
1042
  stateStore.appendLog(`thinking: ${thinkingPreview}`);
1069
1043
  trackWechatForwardTask((async () => {
1070
- await queueWechatMessage(authorizedUserId, options.channelId === "wecom"
1044
+ await queueWechatMessage(authorizedUserId, channelDriver.id === "wecom"
1071
1045
  ? `Processing: ${thinkingPreview}`
1072
- : `思考: ${thinkingPreview}`, "thinking");
1046
+ : `思考: ${thinkingPreview}`, "thinking", eventTarget);
1073
1047
  })());
1074
1048
  }
1075
1049
  }
@@ -1079,7 +1053,7 @@ function wireAdapterEvents(params) {
1079
1053
  const pending = toPendingApproval(next.request);
1080
1054
  stateStore.setPendingConfirmation(pending);
1081
1055
  stateStore.appendLog(`Approval requested (${pending.source}): ${pending.commandPreview}`);
1082
- await queueWechatMessage(authorizedUserId, formatApprovalMessage(pending, adapterState), "approval_required");
1056
+ await queueWechatMessage(authorizedUserId, formatApprovalMessage(pending, adapterState), "approval_required", eventTarget);
1083
1057
  }));
1084
1058
  },
1085
1059
  userInputRequired: (next) => {
@@ -1087,14 +1061,14 @@ function wireAdapterEvents(params) {
1087
1061
  const pending = toPendingUserInput(next.request);
1088
1062
  stateStore.setPendingUserInput(pending);
1089
1063
  stateStore.appendLog(`User input requested: questions=${pending.questions.length}`);
1090
- await queueWechatMessage(authorizedUserId, formatUserInputRequestMessage(pending, adapterState), "user_input_required");
1064
+ await queueWechatMessage(authorizedUserId, formatUserInputRequestMessage(pending, adapterState), "user_input_required", eventTarget);
1091
1065
  }));
1092
1066
  },
1093
1067
  mirroredUserInput: (next) => {
1094
1068
  stateStore.appendLog(`mirrored_local_input: ${truncatePreview(next.text)}`);
1095
1069
  if (shouldForwardBridgeEventToWechat(options.adapter, next.type, { text: next.text })) {
1096
1070
  trackWechatForwardTask(outputBatcher.flushNow().then(async () => {
1097
- await queueWechatMessage(authorizedUserId, formatMirroredUserInputMessage(options.adapter, next.text), "mirrored_user_input");
1071
+ await queueWechatMessage(authorizedUserId, formatMirroredUserInputMessage(options.adapter, next.text), "mirrored_user_input", eventTarget);
1098
1072
  }));
1099
1073
  }
1100
1074
  },
@@ -1104,7 +1078,7 @@ function wireAdapterEvents(params) {
1104
1078
  stateStore.appendLog(`session_switched: ${next.sessionId} source=${next.source} reason=${next.reason}`);
1105
1079
  if (shouldForwardSessionSwitchEvent(next.reason) && shouldForwardBridgeEventToWechat(options.adapter, next.type)) {
1106
1080
  trackWechatForwardTask(outputBatcher.flushNow().then(async () => {
1107
- await queueWechatMessage(authorizedUserId, formatSessionSwitchMessage({ adapter: options.adapter, sessionId: next.sessionId, source: next.source, reason: next.reason }), "session_switched");
1081
+ await queueWechatMessage(authorizedUserId, formatSessionSwitchMessage({ adapter: options.adapter, sessionId: next.sessionId, source: next.source, reason: next.reason }), "session_switched", eventTarget);
1108
1082
  }));
1109
1083
  }
1110
1084
  },
@@ -1122,36 +1096,39 @@ function wireAdapterEvents(params) {
1122
1096
  }
1123
1097
  if (shouldForwardSessionSwitchEvent(next.reason) && shouldForwardBridgeEventToWechat(options.adapter, next.type)) {
1124
1098
  trackWechatForwardTask(outputBatcher.flushNow().then(async () => {
1125
- await queueWechatMessage(authorizedUserId, formatSessionSwitchMessage({ adapter: options.adapter, sessionId: next.threadId, source: next.source, reason: next.reason }), "thread_switched");
1099
+ await queueWechatMessage(authorizedUserId, formatSessionSwitchMessage({ adapter: options.adapter, sessionId: next.threadId, source: next.source, reason: next.reason }), "thread_switched", eventTarget);
1126
1100
  }));
1127
1101
  }
1128
1102
  void maybeDrainDeferredInboundMessages();
1129
1103
  },
1130
1104
  taskComplete: () => {
1105
+ void channelDriver.endTyping?.(authorizedUserId);
1131
1106
  trackWechatForwardTask(outputBatcher.flushNow().then(async () => {
1132
1107
  stateStore.clearPendingConfirmation();
1133
1108
  stateStore.clearPendingUserInput();
1134
- clearActiveTask();
1109
+ clearActiveTask(eventTask);
1135
1110
  await maybeDrainDeferredInboundMessages();
1136
1111
  }));
1137
1112
  },
1138
1113
  taskFailed: (next) => {
1114
+ void channelDriver.endTyping?.(authorizedUserId);
1139
1115
  trackWechatForwardTask(outputBatcher.flushNow().then(async () => {
1140
1116
  stateStore.clearPendingConfirmation();
1141
1117
  stateStore.clearPendingUserInput();
1142
- clearActiveTask();
1143
- await queueWechatMessage(authorizedUserId, formatTaskFailedMessage(options.adapter, next.message), "task_failed");
1118
+ clearActiveTask(eventTask);
1119
+ await queueWechatMessage(authorizedUserId, formatTaskFailedMessage(options.adapter, next.message), "task_failed", eventTarget);
1144
1120
  await maybeDrainDeferredInboundMessages();
1145
1121
  }));
1146
1122
  },
1147
1123
  fatalError: (next) => {
1124
+ void channelDriver.endTyping?.(authorizedUserId);
1148
1125
  logError(next.message);
1149
1126
  stateStore.appendLog(`fatal_error: ${next.message}`);
1150
1127
  stateStore.clearPendingConfirmation();
1151
1128
  stateStore.clearPendingUserInput();
1152
- clearActiveTask();
1129
+ clearActiveTask(eventTask);
1153
1130
  trackWechatForwardTask(outputBatcher.flushNow().then(async () => {
1154
- await queueWechatMessage(authorizedUserId, formatUserFacingBridgeFatalError(next.message), "fatal_error");
1131
+ await queueWechatMessage(authorizedUserId, formatUserFacingBridgeFatalError(next.message), "fatal_error", eventTarget);
1155
1132
  await maybeDrainDeferredInboundMessages();
1156
1133
  }));
1157
1134
  },
@@ -1179,13 +1156,11 @@ function formatInboundMessagePreview(message) {
1179
1156
  return "(empty)";
1180
1157
  }
1181
1158
  async function handleInboundMessage(params) {
1182
- const { message, channelMessage, } = params;
1183
- const { options, stateStore, adapter, resumeCoordinator, queueWechatMessage, outputBatcher, clearActiveTask, deferInboundMessage, } = params;
1159
+ const { message, channelMessage, channelDriver, } = params;
1160
+ const { options, stateStore, adapter, resumeCoordinator, queueWechatMessage, outputBatcher, clearActiveTask, dispatchInboundText, deferInboundMessage, } = params;
1184
1161
  const state = stateStore.getState();
1185
1162
  if (message.senderId !== state.authorizedUserId) {
1186
- await queueWechatMessage(message.senderId, options.channelId === "wecom"
1187
- ? "Unauthorized. This bridge only accepts messages from the paired WeCom operator."
1188
- : "Unauthorized. This bridge only accepts messages from the configured WeChat owner.");
1163
+ await queueWechatMessage(message.senderId, `Unauthorized. This bridge only accepts messages from ${channelDriver.operatorDescription}.`);
1189
1164
  return null;
1190
1165
  }
1191
1166
  const systemCommand = parseWechatControlCommand(message.text, {
@@ -1194,12 +1169,19 @@ async function handleInboundMessage(params) {
1194
1169
  hasPendingUserInput: Boolean(state.pendingUserInput),
1195
1170
  });
1196
1171
  switch (systemCommand?.type) {
1172
+ case "model":
1173
+ case "plan":
1174
+ await queueWechatMessage(message.senderId, await handleAdapterControl(adapter, message.senderId, systemCommand));
1175
+ return null;
1176
+ case "broadcast":
1177
+ await queueWechatMessage(message.senderId, "/all is only available in daemon mode. Start the daemon with wechat-daemon or wecom-daemon to broadcast to all workers.");
1178
+ return null;
1197
1179
  case "status":
1198
1180
  await queueWechatMessage(message.senderId, formatStatusReport(stateStore.getState(), adapter.getState()));
1199
1181
  return null;
1200
1182
  case "resume": {
1201
1183
  if (!isWechatResumeEnabled(options.adapter)) {
1202
- 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.`);
1184
+ await queueWechatMessage(message.senderId, `${channelDriver.displayName} /resume is disabled in ${options.adapter} mode. Use /resume directly inside "${channelDriver.id}-${options.adapter}"; the remote channel will follow the active local session.`);
1203
1185
  return null;
1204
1186
  }
1205
1187
  try {
@@ -1348,28 +1330,22 @@ async function handleInboundMessage(params) {
1348
1330
  defer: async () => {
1349
1331
  await deferInboundMessage(message);
1350
1332
  },
1351
- dispatch: async () => {
1352
- return await dispatchInboundWechatText({
1353
- message,
1354
- options,
1355
- stateStore,
1356
- adapter,
1357
- });
1358
- },
1333
+ dispatch: dispatchInboundText,
1359
1334
  });
1360
1335
  return routeResult.kind === "dispatched" ? routeResult.result : null;
1361
1336
  }
1362
- async function dispatchInboundWechatText(params) {
1363
- const { message, options, stateStore, adapter } = params;
1364
- const preview = formatInboundMessagePreview(message);
1365
- const activeTask = {
1337
+ function createActiveTask(message) {
1338
+ return {
1366
1339
  startedAt: Date.now(),
1367
- inputPreview: truncatePreview(preview, 180),
1340
+ inputPreview: truncatePreview(formatInboundMessagePreview(message), 180),
1368
1341
  };
1342
+ }
1343
+ async function dispatchInboundWechatText(params) {
1344
+ const { message, options, channelDriver, stateStore, adapter } = params;
1345
+ const preview = formatInboundMessagePreview(message);
1346
+ const activeTask = params.activeTask ?? createActiveTask(message);
1369
1347
  stateStore.appendLog(`Forwarded input to ${options.adapter}: ${truncatePreview(preview)}`);
1370
- await adapter.sendInput(options.channelId === "wecom"
1371
- ? buildWecomInboundPrompt(message.text, message.attachments)
1372
- : buildWechatInboundPrompt(message.text, message.attachments));
1348
+ await adapter.sendInput(channelDriver.buildInboundPrompt(message.text, message.attachments));
1373
1349
  return activeTask;
1374
1350
  }
1375
1351
  const isDirectRun = isDirectModuleRun(import.meta.url, process.argv, import.meta.main);