cli-wechat-bridge 1.2.1 → 1.2.3
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/README.md +146 -73
- package/README_EN.md +544 -0
- package/bin/telegram-check-update.mjs +10 -0
- package/bin/telegram-claude.mjs +10 -0
- package/bin/telegram-codex.mjs +10 -0
- package/bin/telegram-daemon.mjs +10 -0
- package/bin/telegram-opencode.mjs +10 -0
- package/bin/telegram-pi.mjs +10 -0
- package/bin/telegram-setup.mjs +10 -0
- package/dist/bridge/adapter-control.js +19 -12
- package/dist/bridge/bridge-adapters.claude.js +219 -115
- package/dist/bridge/bridge-adapters.codex.js +532 -99
- package/dist/bridge/bridge-adapters.core.js +84 -7
- package/dist/bridge/bridge-adapters.opencode.js +202 -55
- package/dist/bridge/bridge-adapters.pi.js +23 -18
- package/dist/bridge/bridge-adapters.shared.js +9 -9
- package/dist/bridge/bridge-session-resume.js +13 -6
- package/dist/bridge/bridge-state.js +3 -1
- package/dist/bridge/bridge-utils.js +94 -144
- package/dist/bridge/claude-hooks.js +60 -3
- package/dist/bridge/claude-native-controls.js +19 -12
- package/dist/bridge/language-control.js +33 -0
- package/dist/bridge/native-terminal-control.js +26 -10
- package/dist/bridge/opencode-v2-compat.js +11 -3
- package/dist/bridge/wechat-bridge.js +154 -72
- package/dist/channels/channel-driver-factory.js +56 -0
- package/dist/channels/local/local-channel-port.js +40 -0
- package/dist/channels/local/local-driver.js +217 -0
- package/dist/channels/telegram/setup-cli.js +12 -0
- package/dist/channels/telegram/setup.js +163 -0
- package/dist/channels/telegram/telegram-channel-port.js +47 -0
- package/dist/channels/telegram/telegram-config.js +66 -0
- package/dist/channels/telegram/telegram-driver.js +106 -0
- package/dist/channels/telegram/telegram-message.js +41 -0
- package/dist/channels/telegram/telegram-transport.js +378 -0
- package/dist/channels/wechat/wechat-driver.js +11 -2
- package/dist/channels/wecom/wecom-driver.js +8 -1
- package/dist/companion/local-companion-start.js +180 -61
- package/dist/companion/local-companion.js +27 -7
- package/dist/companion/pi-tui-bridge-extension.js +10 -4
- package/dist/core/bridge-control.js +3 -0
- package/dist/core/bridge-defer.js +3 -2
- package/dist/core/bridge-event-forwarder.js +3 -0
- package/dist/core/channel-types.js +13 -1
- package/dist/daemon/wechat-daemon.js +239 -126
- package/dist/i18n/index.js +25 -11
- package/dist/i18n/messages-en.js +245 -1
- package/dist/i18n/messages-zh.js +245 -1
- package/dist/runtime/legacy-adapter-runtime.js +7 -4
- package/dist/wechat/channel-config.js +3 -2
- package/package.json +17 -5
- package/skills/codex-bridge/SKILL.md +35 -0
- package/skills/codex-bridge/agents/openai.yaml +4 -0
- package/skills/codex-bridge/scripts/install.mjs +28 -0
|
@@ -12,6 +12,9 @@ import { execFileSync, spawn } from "node:child_process";
|
|
|
12
12
|
import { fileURLToPath } from "node:url";
|
|
13
13
|
import { resolveDefaultAdapterCommand, } from "../bridge/bridge-adapters.js";
|
|
14
14
|
import { t } from "../i18n/index.js";
|
|
15
|
+
import { handleLanguageControl } from "../bridge/language-control.js";
|
|
16
|
+
import { parseBridgeControlCommand } from "../core/bridge-control.js";
|
|
17
|
+
import { LANGUAGE_FILE } from "../wechat/channel-config.js";
|
|
15
18
|
import { delay, getSharedSessionIdFromAdapterState, quoteWindowsCommandArg, } from "../bridge/bridge-adapters.shared.js";
|
|
16
19
|
import { BridgeController } from "../bridge/bridge-controller.js";
|
|
17
20
|
import { readBridgeLockFile, } from "../bridge/bridge-state.js";
|
|
@@ -36,10 +39,15 @@ import { forwardBridgeEvent } from "../core/bridge-event-forwarder.js";
|
|
|
36
39
|
import { isDirectModuleRun } from "../core/direct-run.js";
|
|
37
40
|
import { WechatChannelPort } from "../channels/wechat/wechat-channel-port.js";
|
|
38
41
|
import { ensureWecomAccount } from "../channels/wecom/setup.js";
|
|
42
|
+
import { ensureTelegramAccount } from "../channels/telegram/setup.js";
|
|
43
|
+
import { TelegramTransport } from "../channels/telegram/telegram-transport.js";
|
|
44
|
+
import { TelegramChannelPort } from "../channels/telegram/telegram-channel-port.js";
|
|
39
45
|
import { WecomChannelPort } from "../channels/wecom/wecom-channel-port.js";
|
|
40
46
|
import { WecomTransport } from "../channels/wecom/wecom-transport.js";
|
|
41
|
-
import {
|
|
42
|
-
import {
|
|
47
|
+
import { createChannelDriver } from "../channels/channel-driver-factory.js";
|
|
48
|
+
import { LocalChannelPort } from "../channels/local/local-channel-port.js";
|
|
49
|
+
import { getLocalChannelFiles, LOCAL_CHANNEL_OPERATOR_ID, } from "../channels/local/local-driver.js";
|
|
50
|
+
import { getBridgeChannelDisplayName, isBridgeChannelId, normalizeBridgeChannelId, } from "../core/channel-types.js";
|
|
43
51
|
import { clearLocalCompanionEndpoint, clearLocalCompanionOccupancy, readLocalCompanionEndpoint, } from "../companion/local-companion-link.js";
|
|
44
52
|
import { attachDaemonRequestListener, buildDaemonToken, clearDaemonEndpoint, DAEMON_PROTOCOL_VERSION, isPidAlive, readDaemonEndpoint, sendDaemonRequest, sendDaemonResponse, writeDaemonEndpoint, } from "./daemon-link.js";
|
|
45
53
|
const MODULE_FILE = fileURLToPath(import.meta.url);
|
|
@@ -60,7 +68,8 @@ const MAX_DAEMON_DEFERRED_INPUTS = 32;
|
|
|
60
68
|
const DAEMON_ADAPTERS = ["codex", "claude", "opencode", "pi"];
|
|
61
69
|
function getCliChannelId(argv = process.argv.slice(2)) {
|
|
62
70
|
const index = argv.indexOf("--channel");
|
|
63
|
-
|
|
71
|
+
const value = index >= 0 ? argv[index + 1] : undefined;
|
|
72
|
+
return normalizeBridgeChannelId(value);
|
|
64
73
|
}
|
|
65
74
|
function log(message) {
|
|
66
75
|
process.stderr.write(`[${getCliChannelId()}-daemon] ${message}\n`);
|
|
@@ -132,11 +141,12 @@ export function parseDaemonCliArgs(argv) {
|
|
|
132
141
|
}
|
|
133
142
|
const next = argv[i + 1];
|
|
134
143
|
if (arg === "--help" || arg === "-h") {
|
|
144
|
+
const channelDisplayName = getBridgeChannelDisplayName(channelId);
|
|
135
145
|
process.stdout.write([
|
|
136
146
|
`Usage: ${channelId}-daemon [--cwd <path>] [--adapter <codex|claude|opencode|pi>] [--profile <name-or-path>] [--no-open]`,
|
|
137
147
|
"",
|
|
138
|
-
`Keeps one ${
|
|
139
|
-
`Send /codex, /claude, /opencode, or /pi in ${
|
|
148
|
+
`Keeps one ${channelDisplayName} connection alive and switches between Codex, Claude Code, OpenCode, and Pi.`,
|
|
149
|
+
`Send /codex, /claude, /opencode, or /pi in ${channelDisplayName} to switch the active terminal.`,
|
|
140
150
|
"",
|
|
141
151
|
].join("\n"));
|
|
142
152
|
process.exit(0);
|
|
@@ -150,7 +160,7 @@ export function parseDaemonCliArgs(argv) {
|
|
|
150
160
|
continue;
|
|
151
161
|
}
|
|
152
162
|
if (arg === "--channel") {
|
|
153
|
-
if (!
|
|
163
|
+
if (!isBridgeChannelId(next)) {
|
|
154
164
|
throw new Error(`Invalid channel: ${next ?? "(missing)"}`);
|
|
155
165
|
}
|
|
156
166
|
channelId = next;
|
|
@@ -184,7 +194,7 @@ export function parseDaemonCliArgs(argv) {
|
|
|
184
194
|
profile,
|
|
185
195
|
initialAdapter,
|
|
186
196
|
openVisible,
|
|
187
|
-
...(channelId === "
|
|
197
|
+
...(channelId === "wechat" ? {} : { channelId }),
|
|
188
198
|
};
|
|
189
199
|
}
|
|
190
200
|
export function parseDaemonSwitchCommand(text) {
|
|
@@ -323,8 +333,29 @@ function shellQuotePosix(s) {
|
|
|
323
333
|
function formatLaunchPreview(launch) {
|
|
324
334
|
return [launch.command, ...launch.args].join(" ");
|
|
325
335
|
}
|
|
326
|
-
function openVisibleClient(params) {
|
|
336
|
+
export function openVisibleClient(params) {
|
|
327
337
|
const args = buildVisibleClientLaunchArgs(params);
|
|
338
|
+
// E2E harness override: host the visible client in a PTY owned by an
|
|
339
|
+
// external launcher script instead of a terminal window, so automated
|
|
340
|
+
// runs can type and read the TUI while every other step stays identical.
|
|
341
|
+
const launcherOverride = process.env.CLI_BRIDGE_VISIBLE_LAUNCHER;
|
|
342
|
+
if (launcherOverride) {
|
|
343
|
+
const child = spawn(process.execPath, [launcherOverride, ...args], {
|
|
344
|
+
cwd: params.cwd,
|
|
345
|
+
env: process.env,
|
|
346
|
+
detached: true,
|
|
347
|
+
stdio: "ignore",
|
|
348
|
+
});
|
|
349
|
+
child.once("error", (error) => {
|
|
350
|
+
params.onError?.(error instanceof Error ? error : new Error(String(error)));
|
|
351
|
+
});
|
|
352
|
+
child.unref();
|
|
353
|
+
return {
|
|
354
|
+
command: process.execPath,
|
|
355
|
+
args: [launcherOverride, ...args],
|
|
356
|
+
pid: child.pid,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
328
359
|
if (process.platform === "win32") {
|
|
329
360
|
const command = process.env.ComSpec || "cmd.exe";
|
|
330
361
|
const launchArgs = [
|
|
@@ -490,57 +521,44 @@ function formatInboundMessagePreview(message) {
|
|
|
490
521
|
return "(empty)";
|
|
491
522
|
}
|
|
492
523
|
function formatNoActiveAdapterMessage() {
|
|
493
|
-
return
|
|
494
|
-
"No active terminal is selected.",
|
|
495
|
-
"Send /codex, /claude, /opencode, or /pi to choose one.",
|
|
496
|
-
].join("\n");
|
|
524
|
+
return t("daemon.noActiveAdapter");
|
|
497
525
|
}
|
|
498
526
|
export function formatDaemonSwitchResultDetail(result) {
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
?
|
|
502
|
-
: "
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
return
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
}
|
|
518
|
-
if (result.openedVisible) {
|
|
519
|
-
return result.created
|
|
520
|
-
? `Started the bridge slot and tried to open the visible CLI, but it has not connected yet. Check ${BRIDGE_LOG_FILE}.`
|
|
521
|
-
: `Tried to open a visible CLI for the existing slot, but it has not connected yet. Check ${BRIDGE_LOG_FILE}.`;
|
|
522
|
-
}
|
|
523
|
-
if (result.visibleConnected) {
|
|
524
|
-
return "Reused the existing visible CLI.";
|
|
525
|
-
}
|
|
526
|
-
return result.created ? "Started the bridge slot." : "Reused the bridge slot.";
|
|
527
|
+
const previous = result.activated === false
|
|
528
|
+
? result.previousActiveAdapter
|
|
529
|
+
? t("daemon.switch.previous", { adapter: result.previousActiveAdapter })
|
|
530
|
+
: t("daemon.switch.none")
|
|
531
|
+
: "";
|
|
532
|
+
if (result.activated === false && result.visibleConnected && result.visibleReady === false) {
|
|
533
|
+
return t("daemon.switch.threadNotReady", { previous, log: BRIDGE_LOG_FILE });
|
|
534
|
+
}
|
|
535
|
+
if (result.openedVisible && (result.activated === false || !result.visibleConnected)) {
|
|
536
|
+
return t(result.created ? "daemon.switch.newNotConnected" : "daemon.switch.existingNotConnected", { previous, log: BRIDGE_LOG_FILE });
|
|
537
|
+
}
|
|
538
|
+
if (result.activated === false)
|
|
539
|
+
return t("daemon.switch.notConnected", { previous, log: BRIDGE_LOG_FILE });
|
|
540
|
+
if (result.openedVisible && result.visibleConnected)
|
|
541
|
+
return t(result.created ? "daemon.switchResult.new" : "daemon.switch.opened");
|
|
542
|
+
if (result.visibleConnected)
|
|
543
|
+
return t("daemon.switchResult.reused");
|
|
544
|
+
return t(result.created ? "daemon.switch.slotStarted" : "daemon.switch.slotReused");
|
|
527
545
|
}
|
|
528
546
|
export function formatDaemonStatus(status) {
|
|
529
547
|
const lines = [
|
|
530
|
-
"
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
548
|
+
t("status.daemon", { channel: status.channelId ?? "wechat" }),
|
|
549
|
+
`${t("status.label.cwd")}: ${status.cwd}`,
|
|
550
|
+
`${t("status.label.active")}: ${status.activeAdapter ?? t("status.none")}`,
|
|
551
|
+
`${t("status.label.started_at")}: ${status.startedAt}`,
|
|
534
552
|
];
|
|
535
553
|
for (const adapter of DAEMON_ADAPTERS) {
|
|
536
554
|
const slot = status.slots.find((entry) => entry.adapter === adapter);
|
|
537
555
|
if (!slot) {
|
|
538
|
-
lines.push(`${adapter}:
|
|
556
|
+
lines.push(`${adapter}: ${t("status.notStarted")}`);
|
|
539
557
|
continue;
|
|
540
558
|
}
|
|
541
559
|
const flags = [
|
|
542
|
-
slot.pendingApproval ? "
|
|
543
|
-
slot.pendingUserInput ? "
|
|
560
|
+
slot.pendingApproval ? t("status.pendingApproval") : "",
|
|
561
|
+
slot.pendingUserInput ? t("status.pendingInput") : "",
|
|
544
562
|
slot.companionPid ? `companion_pid=${slot.companionPid}` : "",
|
|
545
563
|
].filter(Boolean);
|
|
546
564
|
lines.push(`${adapter}: ${slot.status}${flags.length ? ` (${flags.join(", ")})` : ""}`);
|
|
@@ -555,6 +573,8 @@ export class WechatDaemon {
|
|
|
555
573
|
channelDriver;
|
|
556
574
|
channelId;
|
|
557
575
|
wecomTransport;
|
|
576
|
+
telegramTransport;
|
|
577
|
+
localFiles;
|
|
558
578
|
inboundConversationContext = new InboundConversationContext();
|
|
559
579
|
fallbackConversation;
|
|
560
580
|
slots = new Map();
|
|
@@ -582,19 +602,21 @@ export class WechatDaemon {
|
|
|
582
602
|
this.transport = params.transport;
|
|
583
603
|
this.channelId = params.channelId ?? "wechat";
|
|
584
604
|
this.wecomTransport = params.wecomTransport ?? null;
|
|
605
|
+
this.telegramTransport = params.telegramTransport ?? null;
|
|
606
|
+
this.localFiles = params.localFiles ?? null;
|
|
585
607
|
this.deps = params.deps ?? {};
|
|
586
|
-
this.channelDriver =
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
:
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
608
|
+
this.channelDriver = createChannelDriver({
|
|
609
|
+
channelId: this.channelId,
|
|
610
|
+
authorizedUserId: params.authorizedUserId,
|
|
611
|
+
accountId: params.accountId,
|
|
612
|
+
wechatTransport: this.transport,
|
|
613
|
+
wecomTransport: this.wecomTransport,
|
|
614
|
+
telegramTransport: this.telegramTransport,
|
|
615
|
+
localFiles: this.localFiles,
|
|
616
|
+
buildWechatInboundPrompt: (text, attachments) => buildWechatInboundPrompt(text, attachments.filter((attachment) => attachment.kind === "image" || attachment.kind === "file")),
|
|
617
|
+
log: (message) => this.daemonLog(message),
|
|
618
|
+
logError,
|
|
619
|
+
});
|
|
598
620
|
this.fallbackConversation = this.channelDriver.defaultConversation()
|
|
599
621
|
?? this.channelDriver.directConversation(params.authorizedUserId);
|
|
600
622
|
this.pendingWechatMessages = this.deps.pendingWechatMessages ??
|
|
@@ -716,7 +738,7 @@ export class WechatDaemon {
|
|
|
716
738
|
if (chatType === "direct") {
|
|
717
739
|
const result = await this.channelDriver.sendText({
|
|
718
740
|
target: this.channelDriver.directConversation(senderId),
|
|
719
|
-
text: "
|
|
741
|
+
text: t("channel.unauthorized"),
|
|
720
742
|
context: "notice",
|
|
721
743
|
log: appendDaemonLog,
|
|
722
744
|
});
|
|
@@ -743,6 +765,7 @@ export class WechatDaemon {
|
|
|
743
765
|
appendDaemonLog(`started: channel=${this.channelId} cwd=${this.cwd}`);
|
|
744
766
|
const activeSlot = this.getActiveSlot();
|
|
745
767
|
const welcomeText = t("daemon.welcome", {
|
|
768
|
+
channel: this.channelDriver.displayName,
|
|
746
769
|
cwd: this.cwd,
|
|
747
770
|
adapter: activeSlot?.adapter ?? "none",
|
|
748
771
|
bindings: formatBindingsListMessage(listBindings()),
|
|
@@ -756,6 +779,9 @@ export class WechatDaemon {
|
|
|
756
779
|
return;
|
|
757
780
|
}
|
|
758
781
|
while (!this.shutdownPromise) {
|
|
782
|
+
if (!this.transport) {
|
|
783
|
+
throw new Error("The wechat poll loop requires a WeChat transport.");
|
|
784
|
+
}
|
|
759
785
|
let pollResult;
|
|
760
786
|
try {
|
|
761
787
|
pollResult = await this.transport.pollMessages({
|
|
@@ -853,7 +879,7 @@ export class WechatDaemon {
|
|
|
853
879
|
// the process alive until the 35-second poll timeout and retain the daemon
|
|
854
880
|
// endpoint for the next startup to clean up.
|
|
855
881
|
try {
|
|
856
|
-
this.transport
|
|
882
|
+
this.transport?.stop();
|
|
857
883
|
}
|
|
858
884
|
catch {
|
|
859
885
|
// Best effort shutdown.
|
|
@@ -870,6 +896,12 @@ export class WechatDaemon {
|
|
|
870
896
|
catch {
|
|
871
897
|
// Best effort shutdown.
|
|
872
898
|
}
|
|
899
|
+
try {
|
|
900
|
+
await this.telegramTransport?.stop();
|
|
901
|
+
}
|
|
902
|
+
catch {
|
|
903
|
+
// Best effort shutdown.
|
|
904
|
+
}
|
|
873
905
|
clearDaemonEndpoint();
|
|
874
906
|
for (const slot of this.slots.values()) {
|
|
875
907
|
try {
|
|
@@ -1081,12 +1113,12 @@ export class WechatDaemon {
|
|
|
1081
1113
|
remindPendingUserInput: async () => {
|
|
1082
1114
|
routeMessage = prefixDaemonAdapterMessage(slot.adapter, slot.pendingUserInput
|
|
1083
1115
|
? formatPendingUserInputReminder(slot.pendingUserInput)
|
|
1084
|
-
:
|
|
1116
|
+
: t("bridge.input.waiting", { adapter: slot.adapter }));
|
|
1085
1117
|
reason = "pending_user_input";
|
|
1086
1118
|
await sendReminder(routeMessage, "user_input_required");
|
|
1087
1119
|
},
|
|
1088
1120
|
remindBusy: async () => {
|
|
1089
|
-
routeMessage = prefixDaemonAdapterMessage(slot.adapter,
|
|
1121
|
+
routeMessage = prefixDaemonAdapterMessage(slot.adapter, t("bridge.busy.remote", { adapter: slot.adapter }));
|
|
1090
1122
|
reason = "busy";
|
|
1091
1123
|
await sendReminder(routeMessage, "notice");
|
|
1092
1124
|
},
|
|
@@ -1362,7 +1394,7 @@ export class WechatDaemon {
|
|
|
1362
1394
|
}
|
|
1363
1395
|
else if (slot.adapter === "opencode" || slot.adapter === "pi") {
|
|
1364
1396
|
if (!slot.runtime.createSession) {
|
|
1365
|
-
throw new Error(
|
|
1397
|
+
throw new Error(t("bridge.newSession.unavailable", { adapter: slot.adapter }));
|
|
1366
1398
|
}
|
|
1367
1399
|
await slot.runtime.createSession();
|
|
1368
1400
|
}
|
|
@@ -1379,12 +1411,38 @@ export class WechatDaemon {
|
|
|
1379
1411
|
},
|
|
1380
1412
|
});
|
|
1381
1413
|
}
|
|
1414
|
+
if (this.channelId === "telegram") {
|
|
1415
|
+
return new TelegramChannelPort({
|
|
1416
|
+
transport: this.telegramTransport,
|
|
1417
|
+
sendText: (target, text, kind) => this.queueWechatMessage(this.authorizedUserId, text, toWechatSendContext(kind), target),
|
|
1418
|
+
prefixText: (currentAdapter, text) => prefixDaemonAdapterMessage(currentAdapter ?? adapter, text),
|
|
1419
|
+
onEmptyVisibleReply: (currentAdapter, rawText) => {
|
|
1420
|
+
appendDaemonLog(`empty_visible_final_reply: adapter=${currentAdapter ?? adapter} raw=${truncatePreview(rawText)}`);
|
|
1421
|
+
},
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
if (this.channelId === "local") {
|
|
1425
|
+
return new LocalChannelPort({
|
|
1426
|
+
sendText: (recipientId, text, context) => this.queueWechatMessage(recipientId, text, context),
|
|
1427
|
+
prefixText: (currentAdapter, text) => prefixDaemonAdapterMessage(currentAdapter ?? adapter, text),
|
|
1428
|
+
onEmptyVisibleReply: (currentAdapter, rawText) => {
|
|
1429
|
+
appendDaemonLog(`empty_visible_final_reply: adapter=${currentAdapter ?? adapter} raw=${truncatePreview(rawText)}`);
|
|
1430
|
+
},
|
|
1431
|
+
onTextSent: (currentAdapter, text) => {
|
|
1432
|
+
appendDaemonLog(`final_reply_sent: adapter=${currentAdapter ?? adapter} chars=${Array.from(text).length}`);
|
|
1433
|
+
},
|
|
1434
|
+
});
|
|
1435
|
+
}
|
|
1436
|
+
if (!this.transport) {
|
|
1437
|
+
throw new Error("The wechat channel port requires a WeChat transport.");
|
|
1438
|
+
}
|
|
1439
|
+
const transport = this.transport;
|
|
1382
1440
|
return new WechatChannelPort({
|
|
1383
1441
|
sendText: (recipientId, text, context) => this.queueWechatMessage(recipientId, text, context),
|
|
1384
|
-
sendImage: (recipientId, filePath) => this.queueWechatAttachmentAction(() =>
|
|
1385
|
-
sendFile: (recipientId, filePath) => this.queueWechatAttachmentAction(() =>
|
|
1386
|
-
sendVoice: (recipientId, filePath) => this.queueWechatAttachmentAction(() =>
|
|
1387
|
-
sendVideo: (recipientId, filePath) => this.queueWechatAttachmentAction(() =>
|
|
1442
|
+
sendImage: (recipientId, filePath) => this.queueWechatAttachmentAction(() => transport.sendImage(filePath, { recipientId })),
|
|
1443
|
+
sendFile: (recipientId, filePath) => this.queueWechatAttachmentAction(() => transport.sendFile(filePath, { recipientId })),
|
|
1444
|
+
sendVoice: (recipientId, filePath) => this.queueWechatAttachmentAction(() => transport.sendVoice(filePath, recipientId)),
|
|
1445
|
+
sendVideo: (recipientId, filePath) => this.queueWechatAttachmentAction(() => transport.sendVideo(filePath, { recipientId })),
|
|
1388
1446
|
prefixText: (currentAdapter, text) => prefixDaemonAdapterMessage(currentAdapter ?? adapter, text),
|
|
1389
1447
|
onEmptyVisibleReply: (currentAdapter, rawText) => {
|
|
1390
1448
|
appendDaemonLog(`empty_visible_final_reply: adapter=${currentAdapter ?? adapter} raw=${truncatePreview(rawText)}`);
|
|
@@ -1402,10 +1460,18 @@ export class WechatDaemon {
|
|
|
1402
1460
|
? this.resolveSlotOutputTarget(slot)
|
|
1403
1461
|
: this.fallbackConversation;
|
|
1404
1462
|
const eventTask = slot.turns.activeTask;
|
|
1463
|
+
if (adapterState.pendingApprovals !== undefined) {
|
|
1464
|
+
const previous = new Map(slot.pendingConfirmations.map((pending) => [pending.requestId, pending]));
|
|
1465
|
+
slot.pendingConfirmations = adapterState.pendingApprovals.map((request) => {
|
|
1466
|
+
const existing = previous.get(request.requestId);
|
|
1467
|
+
return existing ? { ...existing, ...request } : toPendingApproval({ type: "approval_required", request, timestamp: nowIso() });
|
|
1468
|
+
});
|
|
1469
|
+
}
|
|
1405
1470
|
if (slot.pendingConfirmations.length > 0 && !adapterState.pendingApproval) {
|
|
1406
1471
|
slot.pendingConfirmations = [];
|
|
1407
1472
|
}
|
|
1408
|
-
if (slot.pendingUserInput && !adapterState.pendingUserInput
|
|
1473
|
+
if (slot.pendingUserInput && (!adapterState.pendingUserInput
|
|
1474
|
+
|| (slot.pendingUserInput.requestId !== undefined && slot.pendingUserInput.requestId !== adapterState.pendingUserInput.requestId))) {
|
|
1409
1475
|
slot.pendingUserInput = null;
|
|
1410
1476
|
}
|
|
1411
1477
|
slot.eventForwardChain = slot.eventForwardChain
|
|
@@ -1452,17 +1518,29 @@ export class WechatDaemon {
|
|
|
1452
1518
|
},
|
|
1453
1519
|
approvalRequired: (next) => {
|
|
1454
1520
|
this.trackWechatForwardTask(slot.outputBatcher.flushNow().then(async () => {
|
|
1521
|
+
const liveState = slot.runtime.getState();
|
|
1522
|
+
if (liveState.pendingApprovals !== undefined && liveState.pendingApproval?.requestId !== next.request.requestId)
|
|
1523
|
+
return;
|
|
1455
1524
|
const pending = toPendingApproval(next);
|
|
1456
|
-
|
|
1525
|
+
if (liveState.pendingApprovals === undefined) {
|
|
1526
|
+
slot.pendingConfirmations = [pending];
|
|
1527
|
+
}
|
|
1457
1528
|
appendDaemonLog(`approval_required: adapter=${slot.adapter} source=${pending.source} command=${truncatePreview(pending.commandPreview)}`);
|
|
1458
|
-
await this.queueWechatMessage(this.authorizedUserId, prefixDaemonAdapterMessage(slot.adapter, formatApprovalMessage(pending,
|
|
1529
|
+
await this.queueWechatMessage(this.authorizedUserId, prefixDaemonAdapterMessage(slot.adapter, formatApprovalMessage(pending, liveState)), "approval_required", eventTarget);
|
|
1530
|
+
if (slot.runtime.getState().pendingApproval?.requestId === pending.requestId) {
|
|
1531
|
+
slot.displayedApprovalRequestId = pending.requestId;
|
|
1532
|
+
}
|
|
1459
1533
|
}));
|
|
1460
1534
|
},
|
|
1461
1535
|
userInputRequired: (next) => {
|
|
1536
|
+
if (next.request.requestId !== undefined && slot.runtime.getState().pendingUserInput?.requestId !== next.request.requestId)
|
|
1537
|
+
return;
|
|
1462
1538
|
const pending = toPendingUserInput(next.request);
|
|
1463
1539
|
slot.pendingUserInput = pending;
|
|
1464
1540
|
appendDaemonLog(`user_input_required: adapter=${slot.adapter} questions=${pending.questions.length}`);
|
|
1465
1541
|
this.trackWechatForwardTask(slot.outputBatcher.flushNow().then(async () => {
|
|
1542
|
+
if (pending.requestId !== undefined && slot.runtime.getState().pendingUserInput?.requestId !== pending.requestId)
|
|
1543
|
+
return;
|
|
1466
1544
|
await this.queueWechatMessage(this.authorizedUserId, prefixDaemonAdapterMessage(slot.adapter, formatUserInputRequestMessage(pending, adapterState)), "user_input_required", eventTarget);
|
|
1467
1545
|
}));
|
|
1468
1546
|
},
|
|
@@ -1561,7 +1639,12 @@ export class WechatDaemon {
|
|
|
1561
1639
|
}
|
|
1562
1640
|
async handleInboundMessage(message) {
|
|
1563
1641
|
if (message.senderId !== this.authorizedUserId) {
|
|
1564
|
-
await this.queueWechatMessage(message.senderId,
|
|
1642
|
+
await this.queueWechatMessage(message.senderId, t("bridge.unauthorized", { mode: t("bridge.mode.daemon"), operator: this.channelDriver.operatorDescription }));
|
|
1643
|
+
return;
|
|
1644
|
+
}
|
|
1645
|
+
const languageCommand = parseBridgeControlCommand(message.text);
|
|
1646
|
+
if (languageCommand?.type === "language") {
|
|
1647
|
+
await this.handleLanguageCommand(message.senderId, languageCommand.target);
|
|
1565
1648
|
return;
|
|
1566
1649
|
}
|
|
1567
1650
|
const emojiMatch = resolveEmojiCommand(message.text);
|
|
@@ -1578,7 +1661,7 @@ export class WechatDaemon {
|
|
|
1578
1661
|
}
|
|
1579
1662
|
else {
|
|
1580
1663
|
const detail = formatDaemonSwitchResultDetail(result);
|
|
1581
|
-
await this.queueWechatMessage(message.senderId,
|
|
1664
|
+
await this.queueWechatMessage(message.senderId, t("daemon.activateFailed", { adapter: switchTarget, detail }));
|
|
1582
1665
|
return;
|
|
1583
1666
|
}
|
|
1584
1667
|
}
|
|
@@ -1597,7 +1680,7 @@ export class WechatDaemon {
|
|
|
1597
1680
|
});
|
|
1598
1681
|
const detail = formatDaemonSwitchResultDetail(result);
|
|
1599
1682
|
if (!result.activated) {
|
|
1600
|
-
await this.queueWechatMessage(message.senderId,
|
|
1683
|
+
await this.queueWechatMessage(message.senderId, t("daemon.activateFailed", { adapter: switchDirective.adapter, detail }));
|
|
1601
1684
|
return;
|
|
1602
1685
|
}
|
|
1603
1686
|
this.bindCurrentWecomConversation(this.getActiveSlot());
|
|
@@ -1605,12 +1688,12 @@ export class WechatDaemon {
|
|
|
1605
1688
|
message = { ...message, text: switchDirective.remainder };
|
|
1606
1689
|
}
|
|
1607
1690
|
else {
|
|
1608
|
-
await this.queueWechatMessage(message.senderId,
|
|
1691
|
+
await this.queueWechatMessage(message.senderId, t("daemon.activeTerminal", { adapter: switchDirective.adapter, detail }));
|
|
1609
1692
|
return;
|
|
1610
1693
|
}
|
|
1611
1694
|
}
|
|
1612
1695
|
if (message.text.trim().toLowerCase() === "/daemon-stop") {
|
|
1613
|
-
await this.queueWechatMessage(message.senderId,
|
|
1696
|
+
await this.queueWechatMessage(message.senderId, t("daemon.stopping", { channel: this.channelId }));
|
|
1614
1697
|
setTimeout(() => {
|
|
1615
1698
|
void this.shutdown().finally(() => process.exit(0));
|
|
1616
1699
|
}, 0);
|
|
@@ -1651,10 +1734,10 @@ export class WechatDaemon {
|
|
|
1651
1734
|
remindPendingUserInput: async () => {
|
|
1652
1735
|
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(currentSlot.adapter, currentSlot.pendingUserInput
|
|
1653
1736
|
? formatPendingUserInputReminder(currentSlot.pendingUserInput)
|
|
1654
|
-
:
|
|
1737
|
+
: t("bridge.input.waiting", { adapter: currentSlot.adapter })));
|
|
1655
1738
|
},
|
|
1656
1739
|
remindBusy: async () => {
|
|
1657
|
-
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(currentSlot.adapter,
|
|
1740
|
+
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(currentSlot.adapter, t("bridge.busy.remote", { adapter: currentSlot.adapter })));
|
|
1658
1741
|
},
|
|
1659
1742
|
defer: async () => undefined,
|
|
1660
1743
|
dispatch: async () => {
|
|
@@ -1702,20 +1785,23 @@ export class WechatDaemon {
|
|
|
1702
1785
|
}
|
|
1703
1786
|
case "bind": {
|
|
1704
1787
|
setBinding(cmd.emoji, cmd.command);
|
|
1705
|
-
await this.queueWechatMessage(senderId,
|
|
1788
|
+
await this.queueWechatMessage(senderId, t("binding.bound", { emoji: cmd.emoji, command: cmd.command }));
|
|
1706
1789
|
return;
|
|
1707
1790
|
}
|
|
1708
1791
|
case "unbind": {
|
|
1709
1792
|
const removed = removeBinding(cmd.emoji);
|
|
1710
1793
|
await this.queueWechatMessage(senderId, removed
|
|
1711
|
-
?
|
|
1712
|
-
:
|
|
1794
|
+
? t("binding.unbound", { emoji: cmd.emoji })
|
|
1795
|
+
: t("binding.notFound", { emoji: cmd.emoji }));
|
|
1713
1796
|
return;
|
|
1714
1797
|
}
|
|
1715
1798
|
}
|
|
1716
1799
|
}
|
|
1717
1800
|
async handleSystemCommand(message, activeSlot, command) {
|
|
1718
1801
|
switch (command.type) {
|
|
1802
|
+
case "language":
|
|
1803
|
+
await this.handleLanguageCommand(message.senderId, command.target);
|
|
1804
|
+
return;
|
|
1719
1805
|
case "model":
|
|
1720
1806
|
case "plan":
|
|
1721
1807
|
await this.queueWechatMessage(message.senderId, await handleAdapterControl(activeSlot.runtime, message.senderId, command));
|
|
@@ -1725,7 +1811,7 @@ export class WechatDaemon {
|
|
|
1725
1811
|
return;
|
|
1726
1812
|
case "resume": {
|
|
1727
1813
|
if (!isWechatResumeEnabled(activeSlot.adapter)) {
|
|
1728
|
-
await this.queueWechatMessage(message.senderId,
|
|
1814
|
+
await this.queueWechatMessage(message.senderId, t("daemon.resume.disabled", { channel: this.channelDriver.displayName, adapter: activeSlot.adapter }));
|
|
1729
1815
|
return;
|
|
1730
1816
|
}
|
|
1731
1817
|
try {
|
|
@@ -1757,7 +1843,7 @@ export class WechatDaemon {
|
|
|
1757
1843
|
}
|
|
1758
1844
|
case "new_session":
|
|
1759
1845
|
if (!activeSlot.runtime.createSession) {
|
|
1760
|
-
await this.queueWechatMessage(message.senderId,
|
|
1846
|
+
await this.queueWechatMessage(message.senderId, t("bridge.newSession.unavailable", { adapter: activeSlot.adapter }));
|
|
1761
1847
|
return;
|
|
1762
1848
|
}
|
|
1763
1849
|
await activeSlot.outputBatcher.flushNow();
|
|
@@ -1774,8 +1860,8 @@ export class WechatDaemon {
|
|
|
1774
1860
|
case "stop": {
|
|
1775
1861
|
const interrupted = await activeSlot.runtime.interrupt();
|
|
1776
1862
|
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(activeSlot.adapter, interrupted
|
|
1777
|
-
? "
|
|
1778
|
-
: "
|
|
1863
|
+
? t("bridge.interrupt.workerSent")
|
|
1864
|
+
: t("bridge.interrupt.workerIdle")));
|
|
1779
1865
|
return;
|
|
1780
1866
|
}
|
|
1781
1867
|
case "reset":
|
|
@@ -1786,7 +1872,7 @@ export class WechatDaemon {
|
|
|
1786
1872
|
activeSlot.resumeCoordinator.clear();
|
|
1787
1873
|
await activeSlot.runtime.reset();
|
|
1788
1874
|
appendDaemonLog(`reset: adapter=${activeSlot.adapter}`);
|
|
1789
|
-
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(activeSlot.adapter, "
|
|
1875
|
+
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(activeSlot.adapter, t("bridge.reset.done")));
|
|
1790
1876
|
return;
|
|
1791
1877
|
case "confirm":
|
|
1792
1878
|
await this.confirmPendingApproval(message, activeSlot);
|
|
@@ -1799,42 +1885,49 @@ export class WechatDaemon {
|
|
|
1799
1885
|
return;
|
|
1800
1886
|
}
|
|
1801
1887
|
}
|
|
1888
|
+
async handleLanguageCommand(senderId, target) {
|
|
1889
|
+
await this.queueWechatMessage(senderId, await handleLanguageControl({
|
|
1890
|
+
target,
|
|
1891
|
+
settingsFile: this.deps.localeSettingsFile ?? LANGUAGE_FILE,
|
|
1892
|
+
adapters: [...this.slots.values()].map((slot) => slot.runtime),
|
|
1893
|
+
}));
|
|
1894
|
+
}
|
|
1802
1895
|
async confirmPendingApproval(message, activeSlot) {
|
|
1803
1896
|
const slot = this.resolvePendingApprovalSlot(activeSlot);
|
|
1804
1897
|
if (!slot || slot.pendingConfirmations.length === 0) {
|
|
1805
|
-
await this.queueWechatMessage(message.senderId, "
|
|
1898
|
+
await this.queueWechatMessage(message.senderId, t("approval.noPending"));
|
|
1806
1899
|
return;
|
|
1807
1900
|
}
|
|
1808
|
-
const
|
|
1901
|
+
const pending = slot.pendingConfirmations[0];
|
|
1902
|
+
const count = await slot.runtime.resolveApproval("confirm", slot.runtime.getState().pendingApprovals !== undefined ? slot.displayedApprovalRequestId ?? "" : pending.requestId) ? 1 : 0;
|
|
1809
1903
|
if (!count) {
|
|
1810
|
-
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(slot.adapter, "
|
|
1904
|
+
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(slot.adapter, t("bridge.approval.applyFailed")));
|
|
1811
1905
|
return;
|
|
1812
1906
|
}
|
|
1813
|
-
const preview =
|
|
1814
|
-
|
|
1815
|
-
slot.
|
|
1816
|
-
|
|
1817
|
-
inputPreview: preview,
|
|
1818
|
-
});
|
|
1907
|
+
const preview = pending.commandPreview;
|
|
1908
|
+
const adapterState = slot.runtime.getState();
|
|
1909
|
+
slot.pendingConfirmations = (adapterState.pendingApprovals ?? (adapterState.pendingApproval ? [adapterState.pendingApproval] : []))
|
|
1910
|
+
.map((request) => toPendingApproval({ type: "approval_required", request, timestamp: nowIso() }));
|
|
1819
1911
|
appendDaemonLog(`approval_confirmed: adapter=${slot.adapter} count=${count} command=${truncatePreview(preview)}`);
|
|
1820
|
-
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(slot.adapter,
|
|
1821
|
-
? `${count} approvals confirmed. Continuing...`
|
|
1822
|
-
: "Approval confirmed. Continuing..."));
|
|
1912
|
+
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(slot.adapter, t("approval.confirmed")));
|
|
1823
1913
|
}
|
|
1824
1914
|
async denyPendingApproval(message, activeSlot) {
|
|
1825
1915
|
const slot = this.resolvePendingApprovalSlot(activeSlot);
|
|
1826
1916
|
if (!slot || slot.pendingConfirmations.length === 0) {
|
|
1827
|
-
await this.queueWechatMessage(message.senderId, "
|
|
1917
|
+
await this.queueWechatMessage(message.senderId, t("approval.noPending"));
|
|
1828
1918
|
return;
|
|
1829
1919
|
}
|
|
1830
|
-
const
|
|
1920
|
+
const pending = slot.pendingConfirmations[0];
|
|
1921
|
+
const count = await slot.runtime.resolveApproval("deny", slot.runtime.getState().pendingApprovals !== undefined ? slot.displayedApprovalRequestId ?? "" : pending.requestId) ? 1 : 0;
|
|
1831
1922
|
if (!count) {
|
|
1832
|
-
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(slot.adapter, "
|
|
1923
|
+
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(slot.adapter, t("bridge.approval.denyFailed")));
|
|
1833
1924
|
return;
|
|
1834
1925
|
}
|
|
1835
|
-
|
|
1926
|
+
const adapterState = slot.runtime.getState();
|
|
1927
|
+
slot.pendingConfirmations = (adapterState.pendingApprovals ?? (adapterState.pendingApproval ? [adapterState.pendingApproval] : []))
|
|
1928
|
+
.map((request) => toPendingApproval({ type: "approval_required", request, timestamp: nowIso() }));
|
|
1836
1929
|
appendDaemonLog(`approval_denied: adapter=${slot.adapter} count=${count}`);
|
|
1837
|
-
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(slot.adapter,
|
|
1930
|
+
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(slot.adapter, t("approval.denied")));
|
|
1838
1931
|
}
|
|
1839
1932
|
async answerPendingUserInput(message, activeSlot, raw) {
|
|
1840
1933
|
// Mirror the /confirm and /deny behavior: search every slot for a pending
|
|
@@ -1842,12 +1935,12 @@ export class WechatDaemon {
|
|
|
1842
1935
|
// when the user has since switched the active adapter.
|
|
1843
1936
|
const slot = this.resolvePendingUserInputSlot(activeSlot);
|
|
1844
1937
|
if (!slot) {
|
|
1845
|
-
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(activeSlot.adapter, "
|
|
1938
|
+
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(activeSlot.adapter, t("bridge.answer.none")));
|
|
1846
1939
|
return;
|
|
1847
1940
|
}
|
|
1848
1941
|
const pending = slot.pendingUserInput;
|
|
1849
1942
|
if (!pending) {
|
|
1850
|
-
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(activeSlot.adapter, "
|
|
1943
|
+
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(activeSlot.adapter, t("bridge.answer.none")));
|
|
1851
1944
|
return;
|
|
1852
1945
|
}
|
|
1853
1946
|
const parsed = parsePendingUserInputAnswerCommand(raw, pending);
|
|
@@ -1855,18 +1948,15 @@ export class WechatDaemon {
|
|
|
1855
1948
|
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(slot.adapter, parsed.error));
|
|
1856
1949
|
return;
|
|
1857
1950
|
}
|
|
1858
|
-
const submitted = await slot.runtime.submitUserInput(parsed.answers);
|
|
1951
|
+
const submitted = await slot.runtime.submitUserInput(parsed.answers, pending.requestId);
|
|
1859
1952
|
if (!submitted) {
|
|
1860
|
-
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(slot.adapter, "
|
|
1953
|
+
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(slot.adapter, t("bridge.answer.failed")));
|
|
1861
1954
|
return;
|
|
1862
1955
|
}
|
|
1863
|
-
slot.pendingUserInput
|
|
1864
|
-
|
|
1865
|
-
startedAt: Date.now(),
|
|
1866
|
-
inputPreview: parsed.preview,
|
|
1867
|
-
});
|
|
1956
|
+
if (slot.pendingUserInput?.requestId === pending.requestId)
|
|
1957
|
+
slot.pendingUserInput = null;
|
|
1868
1958
|
appendDaemonLog(`user_input_answered: adapter=${slot.adapter} preview=${parsed.preview}`);
|
|
1869
|
-
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(slot.adapter, "
|
|
1959
|
+
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(slot.adapter, t("bridge.answer.sent")));
|
|
1870
1960
|
}
|
|
1871
1961
|
resolvePendingUserInputSlot(activeSlot) {
|
|
1872
1962
|
if (activeSlot.pendingUserInput) {
|
|
@@ -1904,14 +1994,14 @@ export class WechatDaemon {
|
|
|
1904
1994
|
.map((adapter) => this.slots.get(adapter))
|
|
1905
1995
|
.filter((slot) => Boolean(slot));
|
|
1906
1996
|
if (startedSlots.length === 0) {
|
|
1907
|
-
await this.queueWechatMessage(message.senderId, "
|
|
1997
|
+
await this.queueWechatMessage(message.senderId, t("daemon.broadcast.empty"));
|
|
1908
1998
|
return;
|
|
1909
1999
|
}
|
|
1910
2000
|
const busySlots = startedSlots.filter((slot) => this.isSlotBusyForBroadcast(slot));
|
|
1911
2001
|
if (busySlots.length > 0) {
|
|
1912
2002
|
const names = busySlots.map((slot) => slot.adapter).join(", ");
|
|
1913
2003
|
appendDaemonLog(`broadcast_canceled_busy: busy=[${names}]`);
|
|
1914
|
-
await this.queueWechatMessage(message.senderId,
|
|
2004
|
+
await this.queueWechatMessage(message.senderId, t("daemon.broadcast.busy", { workers: t(busySlots.length > 1 ? "daemon.broadcast.multipleBusy" : "daemon.broadcast.singleBusy"), names }));
|
|
1915
2005
|
return;
|
|
1916
2006
|
}
|
|
1917
2007
|
const broadcastMessage = { ...message, text };
|
|
@@ -1928,10 +2018,10 @@ export class WechatDaemon {
|
|
|
1928
2018
|
dispatched.push(adapter);
|
|
1929
2019
|
}
|
|
1930
2020
|
const lines = [
|
|
1931
|
-
|
|
2021
|
+
t("daemon.broadcast.sent", { count: dispatched.length, workers: t(dispatched.length > 1 ? "daemon.broadcast.multipleWorkers" : "daemon.broadcast.singleWorker"), names: dispatched.join(", ") }),
|
|
1932
2022
|
];
|
|
1933
2023
|
if (skipped.length > 0) {
|
|
1934
|
-
lines.push(
|
|
2024
|
+
lines.push(t("daemon.broadcast.skipped", { names: skipped.join(", ") }));
|
|
1935
2025
|
}
|
|
1936
2026
|
await this.queueWechatMessage(message.senderId, lines.join("\n"));
|
|
1937
2027
|
}
|
|
@@ -1948,7 +2038,7 @@ export class WechatDaemon {
|
|
|
1948
2038
|
task: nextTask,
|
|
1949
2039
|
conversation: inboundConversation,
|
|
1950
2040
|
onBusy: async () => {
|
|
1951
|
-
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(slot.adapter,
|
|
2041
|
+
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(slot.adapter, t("bridge.busy.remote", { adapter: slot.adapter })), "notice", conversationOverride);
|
|
1952
2042
|
},
|
|
1953
2043
|
forward: async () => {
|
|
1954
2044
|
appendDaemonLog(`forwarded_input: adapter=${slot.adapter} text=${truncatePreview(preview)}`);
|
|
@@ -2344,25 +2434,36 @@ export async function runDaemon(options) {
|
|
|
2344
2434
|
const wecomAccount = options.channelId === "wecom"
|
|
2345
2435
|
? await ensureWecomAccount({ log })
|
|
2346
2436
|
: null;
|
|
2437
|
+
const telegramAccount = options.channelId === "telegram"
|
|
2438
|
+
? await ensureTelegramAccount({ log })
|
|
2439
|
+
: null;
|
|
2347
2440
|
const credentials = wecomAccount
|
|
2348
2441
|
? { userId: wecomAccount.operatorUserId }
|
|
2349
|
-
:
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2442
|
+
: telegramAccount
|
|
2443
|
+
? { userId: telegramAccount.operatorUserId }
|
|
2444
|
+
: options.channelId === "local"
|
|
2445
|
+
? { userId: LOCAL_CHANNEL_OPERATOR_ID }
|
|
2446
|
+
: await ensureWechatCredentials({
|
|
2447
|
+
requireUserId: true,
|
|
2448
|
+
validateExisting: true,
|
|
2449
|
+
log,
|
|
2450
|
+
});
|
|
2354
2451
|
if (!credentials.userId) {
|
|
2355
2452
|
throw new Error(options.channelId === "wecom"
|
|
2356
2453
|
? "Saved WeCom credentials are missing operatorUserId."
|
|
2357
|
-
:
|
|
2454
|
+
: options.channelId === "telegram"
|
|
2455
|
+
? "Saved Telegram credentials are missing operatorUserId."
|
|
2456
|
+
: "Saved WeChat credentials are missing userId.");
|
|
2358
2457
|
}
|
|
2359
2458
|
const daemon = new WechatDaemon({
|
|
2360
2459
|
cwd: options.cwd,
|
|
2361
2460
|
profile: options.profile,
|
|
2362
2461
|
authorizedUserId: credentials.userId,
|
|
2363
|
-
transport:
|
|
2462
|
+
transport: (options.channelId ?? "wechat") === "wechat"
|
|
2463
|
+
? new WeChatTransport({ log, logError })
|
|
2464
|
+
: null,
|
|
2364
2465
|
channelId: options.channelId,
|
|
2365
|
-
accountId: wecomAccount?.botId,
|
|
2466
|
+
accountId: wecomAccount?.botId ?? telegramAccount?.botUsername,
|
|
2366
2467
|
wecomTransport: wecomAccount
|
|
2367
2468
|
? new WecomTransport({
|
|
2368
2469
|
account: wecomAccount,
|
|
@@ -2372,6 +2473,18 @@ export async function runDaemon(options) {
|
|
|
2372
2473
|
},
|
|
2373
2474
|
})
|
|
2374
2475
|
: null,
|
|
2476
|
+
telegramTransport: telegramAccount
|
|
2477
|
+
? new TelegramTransport({
|
|
2478
|
+
account: telegramAccount,
|
|
2479
|
+
logger: {
|
|
2480
|
+
log: (message) => log(message),
|
|
2481
|
+
error: (message) => logError(message),
|
|
2482
|
+
},
|
|
2483
|
+
})
|
|
2484
|
+
: null,
|
|
2485
|
+
localFiles: options.channelId === "local"
|
|
2486
|
+
? getLocalChannelFiles(options.cwd)
|
|
2487
|
+
: null,
|
|
2375
2488
|
});
|
|
2376
2489
|
if (cleanupResult.action === "stopped" && isDaemonAdapterKind(cleanupResult.lock.adapter)) {
|
|
2377
2490
|
daemon.takenOverAdapter = cleanupResult.lock.adapter;
|