@unlinearity/cli-wechat-bridge 1.2.1 → 1.2.2
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 +23 -9
- package/dist/bridge/adapter-control.js +19 -12
- package/dist/bridge/bridge-adapters.claude.js +219 -115
- package/dist/bridge/bridge-adapters.codex.js +207 -83
- 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-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 +57 -42
- package/dist/channels/channel-driver-factory.js +44 -0
- package/dist/channels/local/local-channel-port.js +40 -0
- package/dist/channels/local/local-driver.js +217 -0
- package/dist/channels/wechat/wechat-driver.js +11 -2
- package/dist/channels/wecom/wecom-driver.js +8 -1
- 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 +5 -1
- package/dist/daemon/wechat-daemon.js +202 -124
- package/dist/i18n/index.js +25 -11
- package/dist/i18n/messages-en.js +244 -1
- package/dist/i18n/messages-zh.js +244 -1
- package/dist/runtime/legacy-adapter-runtime.js +7 -4
- package/dist/wechat/channel-config.js +3 -2
- package/package.json +1 -1
|
@@ -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";
|
|
@@ -38,8 +41,9 @@ import { WechatChannelPort } from "../channels/wechat/wechat-channel-port.js";
|
|
|
38
41
|
import { ensureWecomAccount } from "../channels/wecom/setup.js";
|
|
39
42
|
import { WecomChannelPort } from "../channels/wecom/wecom-channel-port.js";
|
|
40
43
|
import { WecomTransport } from "../channels/wecom/wecom-transport.js";
|
|
41
|
-
import {
|
|
42
|
-
import {
|
|
44
|
+
import { createChannelDriver } from "../channels/channel-driver-factory.js";
|
|
45
|
+
import { LocalChannelPort } from "../channels/local/local-channel-port.js";
|
|
46
|
+
import { getLocalChannelFiles, LOCAL_CHANNEL_OPERATOR_ID, } from "../channels/local/local-driver.js";
|
|
43
47
|
import { clearLocalCompanionEndpoint, clearLocalCompanionOccupancy, readLocalCompanionEndpoint, } from "../companion/local-companion-link.js";
|
|
44
48
|
import { attachDaemonRequestListener, buildDaemonToken, clearDaemonEndpoint, DAEMON_PROTOCOL_VERSION, isPidAlive, readDaemonEndpoint, sendDaemonRequest, sendDaemonResponse, writeDaemonEndpoint, } from "./daemon-link.js";
|
|
45
49
|
const MODULE_FILE = fileURLToPath(import.meta.url);
|
|
@@ -60,7 +64,12 @@ const MAX_DAEMON_DEFERRED_INPUTS = 32;
|
|
|
60
64
|
const DAEMON_ADAPTERS = ["codex", "claude", "opencode", "pi"];
|
|
61
65
|
function getCliChannelId(argv = process.argv.slice(2)) {
|
|
62
66
|
const index = argv.indexOf("--channel");
|
|
63
|
-
|
|
67
|
+
const value = index >= 0 ? argv[index + 1] : undefined;
|
|
68
|
+
if (value === "wecom")
|
|
69
|
+
return "wecom";
|
|
70
|
+
if (value === "local")
|
|
71
|
+
return "local";
|
|
72
|
+
return "wechat";
|
|
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 = channelId === "wecom" ? "WeCom" : channelId === "local" ? "Local" : "WeChat";
|
|
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 (!next || (next !== "wechat" && next !== "wecom")) {
|
|
163
|
+
if (!next || (next !== "wechat" && next !== "wecom" && next !== "local")) {
|
|
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,7 @@ export class WechatDaemon {
|
|
|
555
573
|
channelDriver;
|
|
556
574
|
channelId;
|
|
557
575
|
wecomTransport;
|
|
576
|
+
localFiles;
|
|
558
577
|
inboundConversationContext = new InboundConversationContext();
|
|
559
578
|
fallbackConversation;
|
|
560
579
|
slots = new Map();
|
|
@@ -582,19 +601,19 @@ export class WechatDaemon {
|
|
|
582
601
|
this.transport = params.transport;
|
|
583
602
|
this.channelId = params.channelId ?? "wechat";
|
|
584
603
|
this.wecomTransport = params.wecomTransport ?? null;
|
|
604
|
+
this.localFiles = params.localFiles ?? null;
|
|
585
605
|
this.deps = params.deps ?? {};
|
|
586
|
-
this.channelDriver =
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
:
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
});
|
|
606
|
+
this.channelDriver = createChannelDriver({
|
|
607
|
+
channelId: this.channelId,
|
|
608
|
+
authorizedUserId: params.authorizedUserId,
|
|
609
|
+
accountId: params.accountId,
|
|
610
|
+
wechatTransport: this.transport,
|
|
611
|
+
wecomTransport: this.wecomTransport,
|
|
612
|
+
localFiles: this.localFiles,
|
|
613
|
+
buildWechatInboundPrompt: (text, attachments) => buildWechatInboundPrompt(text, attachments.filter((attachment) => attachment.kind === "image" || attachment.kind === "file")),
|
|
614
|
+
log: (message) => this.daemonLog(message),
|
|
615
|
+
logError,
|
|
616
|
+
});
|
|
598
617
|
this.fallbackConversation = this.channelDriver.defaultConversation()
|
|
599
618
|
?? this.channelDriver.directConversation(params.authorizedUserId);
|
|
600
619
|
this.pendingWechatMessages = this.deps.pendingWechatMessages ??
|
|
@@ -716,7 +735,7 @@ export class WechatDaemon {
|
|
|
716
735
|
if (chatType === "direct") {
|
|
717
736
|
const result = await this.channelDriver.sendText({
|
|
718
737
|
target: this.channelDriver.directConversation(senderId),
|
|
719
|
-
text: "
|
|
738
|
+
text: t("channel.unauthorized"),
|
|
720
739
|
context: "notice",
|
|
721
740
|
log: appendDaemonLog,
|
|
722
741
|
});
|
|
@@ -743,6 +762,7 @@ export class WechatDaemon {
|
|
|
743
762
|
appendDaemonLog(`started: channel=${this.channelId} cwd=${this.cwd}`);
|
|
744
763
|
const activeSlot = this.getActiveSlot();
|
|
745
764
|
const welcomeText = t("daemon.welcome", {
|
|
765
|
+
channel: this.channelDriver.displayName,
|
|
746
766
|
cwd: this.cwd,
|
|
747
767
|
adapter: activeSlot?.adapter ?? "none",
|
|
748
768
|
bindings: formatBindingsListMessage(listBindings()),
|
|
@@ -756,6 +776,9 @@ export class WechatDaemon {
|
|
|
756
776
|
return;
|
|
757
777
|
}
|
|
758
778
|
while (!this.shutdownPromise) {
|
|
779
|
+
if (!this.transport) {
|
|
780
|
+
throw new Error("The wechat poll loop requires a WeChat transport.");
|
|
781
|
+
}
|
|
759
782
|
let pollResult;
|
|
760
783
|
try {
|
|
761
784
|
pollResult = await this.transport.pollMessages({
|
|
@@ -853,7 +876,7 @@ export class WechatDaemon {
|
|
|
853
876
|
// the process alive until the 35-second poll timeout and retain the daemon
|
|
854
877
|
// endpoint for the next startup to clean up.
|
|
855
878
|
try {
|
|
856
|
-
this.transport
|
|
879
|
+
this.transport?.stop();
|
|
857
880
|
}
|
|
858
881
|
catch {
|
|
859
882
|
// Best effort shutdown.
|
|
@@ -1081,12 +1104,12 @@ export class WechatDaemon {
|
|
|
1081
1104
|
remindPendingUserInput: async () => {
|
|
1082
1105
|
routeMessage = prefixDaemonAdapterMessage(slot.adapter, slot.pendingUserInput
|
|
1083
1106
|
? formatPendingUserInputReminder(slot.pendingUserInput)
|
|
1084
|
-
:
|
|
1107
|
+
: t("bridge.input.waiting", { adapter: slot.adapter }));
|
|
1085
1108
|
reason = "pending_user_input";
|
|
1086
1109
|
await sendReminder(routeMessage, "user_input_required");
|
|
1087
1110
|
},
|
|
1088
1111
|
remindBusy: async () => {
|
|
1089
|
-
routeMessage = prefixDaemonAdapterMessage(slot.adapter,
|
|
1112
|
+
routeMessage = prefixDaemonAdapterMessage(slot.adapter, t("bridge.busy.remote", { adapter: slot.adapter }));
|
|
1090
1113
|
reason = "busy";
|
|
1091
1114
|
await sendReminder(routeMessage, "notice");
|
|
1092
1115
|
},
|
|
@@ -1362,7 +1385,7 @@ export class WechatDaemon {
|
|
|
1362
1385
|
}
|
|
1363
1386
|
else if (slot.adapter === "opencode" || slot.adapter === "pi") {
|
|
1364
1387
|
if (!slot.runtime.createSession) {
|
|
1365
|
-
throw new Error(
|
|
1388
|
+
throw new Error(t("bridge.newSession.unavailable", { adapter: slot.adapter }));
|
|
1366
1389
|
}
|
|
1367
1390
|
await slot.runtime.createSession();
|
|
1368
1391
|
}
|
|
@@ -1379,12 +1402,28 @@ export class WechatDaemon {
|
|
|
1379
1402
|
},
|
|
1380
1403
|
});
|
|
1381
1404
|
}
|
|
1405
|
+
if (this.channelId === "local") {
|
|
1406
|
+
return new LocalChannelPort({
|
|
1407
|
+
sendText: (recipientId, text, context) => this.queueWechatMessage(recipientId, text, context),
|
|
1408
|
+
prefixText: (currentAdapter, text) => prefixDaemonAdapterMessage(currentAdapter ?? adapter, text),
|
|
1409
|
+
onEmptyVisibleReply: (currentAdapter, rawText) => {
|
|
1410
|
+
appendDaemonLog(`empty_visible_final_reply: adapter=${currentAdapter ?? adapter} raw=${truncatePreview(rawText)}`);
|
|
1411
|
+
},
|
|
1412
|
+
onTextSent: (currentAdapter, text) => {
|
|
1413
|
+
appendDaemonLog(`final_reply_sent: adapter=${currentAdapter ?? adapter} chars=${Array.from(text).length}`);
|
|
1414
|
+
},
|
|
1415
|
+
});
|
|
1416
|
+
}
|
|
1417
|
+
if (!this.transport) {
|
|
1418
|
+
throw new Error("The wechat channel port requires a WeChat transport.");
|
|
1419
|
+
}
|
|
1420
|
+
const transport = this.transport;
|
|
1382
1421
|
return new WechatChannelPort({
|
|
1383
1422
|
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(() =>
|
|
1423
|
+
sendImage: (recipientId, filePath) => this.queueWechatAttachmentAction(() => transport.sendImage(filePath, { recipientId })),
|
|
1424
|
+
sendFile: (recipientId, filePath) => this.queueWechatAttachmentAction(() => transport.sendFile(filePath, { recipientId })),
|
|
1425
|
+
sendVoice: (recipientId, filePath) => this.queueWechatAttachmentAction(() => transport.sendVoice(filePath, recipientId)),
|
|
1426
|
+
sendVideo: (recipientId, filePath) => this.queueWechatAttachmentAction(() => transport.sendVideo(filePath, { recipientId })),
|
|
1388
1427
|
prefixText: (currentAdapter, text) => prefixDaemonAdapterMessage(currentAdapter ?? adapter, text),
|
|
1389
1428
|
onEmptyVisibleReply: (currentAdapter, rawText) => {
|
|
1390
1429
|
appendDaemonLog(`empty_visible_final_reply: adapter=${currentAdapter ?? adapter} raw=${truncatePreview(rawText)}`);
|
|
@@ -1402,10 +1441,18 @@ export class WechatDaemon {
|
|
|
1402
1441
|
? this.resolveSlotOutputTarget(slot)
|
|
1403
1442
|
: this.fallbackConversation;
|
|
1404
1443
|
const eventTask = slot.turns.activeTask;
|
|
1444
|
+
if (adapterState.pendingApprovals !== undefined) {
|
|
1445
|
+
const previous = new Map(slot.pendingConfirmations.map((pending) => [pending.requestId, pending]));
|
|
1446
|
+
slot.pendingConfirmations = adapterState.pendingApprovals.map((request) => {
|
|
1447
|
+
const existing = previous.get(request.requestId);
|
|
1448
|
+
return existing ? { ...existing, ...request } : toPendingApproval({ type: "approval_required", request, timestamp: nowIso() });
|
|
1449
|
+
});
|
|
1450
|
+
}
|
|
1405
1451
|
if (slot.pendingConfirmations.length > 0 && !adapterState.pendingApproval) {
|
|
1406
1452
|
slot.pendingConfirmations = [];
|
|
1407
1453
|
}
|
|
1408
|
-
if (slot.pendingUserInput && !adapterState.pendingUserInput
|
|
1454
|
+
if (slot.pendingUserInput && (!adapterState.pendingUserInput
|
|
1455
|
+
|| (slot.pendingUserInput.requestId !== undefined && slot.pendingUserInput.requestId !== adapterState.pendingUserInput.requestId))) {
|
|
1409
1456
|
slot.pendingUserInput = null;
|
|
1410
1457
|
}
|
|
1411
1458
|
slot.eventForwardChain = slot.eventForwardChain
|
|
@@ -1452,17 +1499,29 @@ export class WechatDaemon {
|
|
|
1452
1499
|
},
|
|
1453
1500
|
approvalRequired: (next) => {
|
|
1454
1501
|
this.trackWechatForwardTask(slot.outputBatcher.flushNow().then(async () => {
|
|
1502
|
+
const liveState = slot.runtime.getState();
|
|
1503
|
+
if (liveState.pendingApprovals !== undefined && liveState.pendingApproval?.requestId !== next.request.requestId)
|
|
1504
|
+
return;
|
|
1455
1505
|
const pending = toPendingApproval(next);
|
|
1456
|
-
|
|
1506
|
+
if (liveState.pendingApprovals === undefined) {
|
|
1507
|
+
slot.pendingConfirmations = [pending];
|
|
1508
|
+
}
|
|
1457
1509
|
appendDaemonLog(`approval_required: adapter=${slot.adapter} source=${pending.source} command=${truncatePreview(pending.commandPreview)}`);
|
|
1458
|
-
await this.queueWechatMessage(this.authorizedUserId, prefixDaemonAdapterMessage(slot.adapter, formatApprovalMessage(pending,
|
|
1510
|
+
await this.queueWechatMessage(this.authorizedUserId, prefixDaemonAdapterMessage(slot.adapter, formatApprovalMessage(pending, liveState)), "approval_required", eventTarget);
|
|
1511
|
+
if (slot.runtime.getState().pendingApproval?.requestId === pending.requestId) {
|
|
1512
|
+
slot.displayedApprovalRequestId = pending.requestId;
|
|
1513
|
+
}
|
|
1459
1514
|
}));
|
|
1460
1515
|
},
|
|
1461
1516
|
userInputRequired: (next) => {
|
|
1517
|
+
if (next.request.requestId !== undefined && slot.runtime.getState().pendingUserInput?.requestId !== next.request.requestId)
|
|
1518
|
+
return;
|
|
1462
1519
|
const pending = toPendingUserInput(next.request);
|
|
1463
1520
|
slot.pendingUserInput = pending;
|
|
1464
1521
|
appendDaemonLog(`user_input_required: adapter=${slot.adapter} questions=${pending.questions.length}`);
|
|
1465
1522
|
this.trackWechatForwardTask(slot.outputBatcher.flushNow().then(async () => {
|
|
1523
|
+
if (pending.requestId !== undefined && slot.runtime.getState().pendingUserInput?.requestId !== pending.requestId)
|
|
1524
|
+
return;
|
|
1466
1525
|
await this.queueWechatMessage(this.authorizedUserId, prefixDaemonAdapterMessage(slot.adapter, formatUserInputRequestMessage(pending, adapterState)), "user_input_required", eventTarget);
|
|
1467
1526
|
}));
|
|
1468
1527
|
},
|
|
@@ -1561,7 +1620,12 @@ export class WechatDaemon {
|
|
|
1561
1620
|
}
|
|
1562
1621
|
async handleInboundMessage(message) {
|
|
1563
1622
|
if (message.senderId !== this.authorizedUserId) {
|
|
1564
|
-
await this.queueWechatMessage(message.senderId,
|
|
1623
|
+
await this.queueWechatMessage(message.senderId, t("bridge.unauthorized", { mode: t("bridge.mode.daemon"), operator: this.channelDriver.operatorDescription }));
|
|
1624
|
+
return;
|
|
1625
|
+
}
|
|
1626
|
+
const languageCommand = parseBridgeControlCommand(message.text);
|
|
1627
|
+
if (languageCommand?.type === "language") {
|
|
1628
|
+
await this.handleLanguageCommand(message.senderId, languageCommand.target);
|
|
1565
1629
|
return;
|
|
1566
1630
|
}
|
|
1567
1631
|
const emojiMatch = resolveEmojiCommand(message.text);
|
|
@@ -1578,7 +1642,7 @@ export class WechatDaemon {
|
|
|
1578
1642
|
}
|
|
1579
1643
|
else {
|
|
1580
1644
|
const detail = formatDaemonSwitchResultDetail(result);
|
|
1581
|
-
await this.queueWechatMessage(message.senderId,
|
|
1645
|
+
await this.queueWechatMessage(message.senderId, t("daemon.activateFailed", { adapter: switchTarget, detail }));
|
|
1582
1646
|
return;
|
|
1583
1647
|
}
|
|
1584
1648
|
}
|
|
@@ -1597,7 +1661,7 @@ export class WechatDaemon {
|
|
|
1597
1661
|
});
|
|
1598
1662
|
const detail = formatDaemonSwitchResultDetail(result);
|
|
1599
1663
|
if (!result.activated) {
|
|
1600
|
-
await this.queueWechatMessage(message.senderId,
|
|
1664
|
+
await this.queueWechatMessage(message.senderId, t("daemon.activateFailed", { adapter: switchDirective.adapter, detail }));
|
|
1601
1665
|
return;
|
|
1602
1666
|
}
|
|
1603
1667
|
this.bindCurrentWecomConversation(this.getActiveSlot());
|
|
@@ -1605,12 +1669,12 @@ export class WechatDaemon {
|
|
|
1605
1669
|
message = { ...message, text: switchDirective.remainder };
|
|
1606
1670
|
}
|
|
1607
1671
|
else {
|
|
1608
|
-
await this.queueWechatMessage(message.senderId,
|
|
1672
|
+
await this.queueWechatMessage(message.senderId, t("daemon.activeTerminal", { adapter: switchDirective.adapter, detail }));
|
|
1609
1673
|
return;
|
|
1610
1674
|
}
|
|
1611
1675
|
}
|
|
1612
1676
|
if (message.text.trim().toLowerCase() === "/daemon-stop") {
|
|
1613
|
-
await this.queueWechatMessage(message.senderId,
|
|
1677
|
+
await this.queueWechatMessage(message.senderId, t("daemon.stopping", { channel: this.channelId }));
|
|
1614
1678
|
setTimeout(() => {
|
|
1615
1679
|
void this.shutdown().finally(() => process.exit(0));
|
|
1616
1680
|
}, 0);
|
|
@@ -1651,10 +1715,10 @@ export class WechatDaemon {
|
|
|
1651
1715
|
remindPendingUserInput: async () => {
|
|
1652
1716
|
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(currentSlot.adapter, currentSlot.pendingUserInput
|
|
1653
1717
|
? formatPendingUserInputReminder(currentSlot.pendingUserInput)
|
|
1654
|
-
:
|
|
1718
|
+
: t("bridge.input.waiting", { adapter: currentSlot.adapter })));
|
|
1655
1719
|
},
|
|
1656
1720
|
remindBusy: async () => {
|
|
1657
|
-
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(currentSlot.adapter,
|
|
1721
|
+
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(currentSlot.adapter, t("bridge.busy.remote", { adapter: currentSlot.adapter })));
|
|
1658
1722
|
},
|
|
1659
1723
|
defer: async () => undefined,
|
|
1660
1724
|
dispatch: async () => {
|
|
@@ -1702,20 +1766,23 @@ export class WechatDaemon {
|
|
|
1702
1766
|
}
|
|
1703
1767
|
case "bind": {
|
|
1704
1768
|
setBinding(cmd.emoji, cmd.command);
|
|
1705
|
-
await this.queueWechatMessage(senderId,
|
|
1769
|
+
await this.queueWechatMessage(senderId, t("binding.bound", { emoji: cmd.emoji, command: cmd.command }));
|
|
1706
1770
|
return;
|
|
1707
1771
|
}
|
|
1708
1772
|
case "unbind": {
|
|
1709
1773
|
const removed = removeBinding(cmd.emoji);
|
|
1710
1774
|
await this.queueWechatMessage(senderId, removed
|
|
1711
|
-
?
|
|
1712
|
-
:
|
|
1775
|
+
? t("binding.unbound", { emoji: cmd.emoji })
|
|
1776
|
+
: t("binding.notFound", { emoji: cmd.emoji }));
|
|
1713
1777
|
return;
|
|
1714
1778
|
}
|
|
1715
1779
|
}
|
|
1716
1780
|
}
|
|
1717
1781
|
async handleSystemCommand(message, activeSlot, command) {
|
|
1718
1782
|
switch (command.type) {
|
|
1783
|
+
case "language":
|
|
1784
|
+
await this.handleLanguageCommand(message.senderId, command.target);
|
|
1785
|
+
return;
|
|
1719
1786
|
case "model":
|
|
1720
1787
|
case "plan":
|
|
1721
1788
|
await this.queueWechatMessage(message.senderId, await handleAdapterControl(activeSlot.runtime, message.senderId, command));
|
|
@@ -1725,7 +1792,7 @@ export class WechatDaemon {
|
|
|
1725
1792
|
return;
|
|
1726
1793
|
case "resume": {
|
|
1727
1794
|
if (!isWechatResumeEnabled(activeSlot.adapter)) {
|
|
1728
|
-
await this.queueWechatMessage(message.senderId,
|
|
1795
|
+
await this.queueWechatMessage(message.senderId, t("daemon.resume.disabled", { channel: this.channelDriver.displayName, adapter: activeSlot.adapter }));
|
|
1729
1796
|
return;
|
|
1730
1797
|
}
|
|
1731
1798
|
try {
|
|
@@ -1757,7 +1824,7 @@ export class WechatDaemon {
|
|
|
1757
1824
|
}
|
|
1758
1825
|
case "new_session":
|
|
1759
1826
|
if (!activeSlot.runtime.createSession) {
|
|
1760
|
-
await this.queueWechatMessage(message.senderId,
|
|
1827
|
+
await this.queueWechatMessage(message.senderId, t("bridge.newSession.unavailable", { adapter: activeSlot.adapter }));
|
|
1761
1828
|
return;
|
|
1762
1829
|
}
|
|
1763
1830
|
await activeSlot.outputBatcher.flushNow();
|
|
@@ -1774,8 +1841,8 @@ export class WechatDaemon {
|
|
|
1774
1841
|
case "stop": {
|
|
1775
1842
|
const interrupted = await activeSlot.runtime.interrupt();
|
|
1776
1843
|
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(activeSlot.adapter, interrupted
|
|
1777
|
-
? "
|
|
1778
|
-
: "
|
|
1844
|
+
? t("bridge.interrupt.workerSent")
|
|
1845
|
+
: t("bridge.interrupt.workerIdle")));
|
|
1779
1846
|
return;
|
|
1780
1847
|
}
|
|
1781
1848
|
case "reset":
|
|
@@ -1786,7 +1853,7 @@ export class WechatDaemon {
|
|
|
1786
1853
|
activeSlot.resumeCoordinator.clear();
|
|
1787
1854
|
await activeSlot.runtime.reset();
|
|
1788
1855
|
appendDaemonLog(`reset: adapter=${activeSlot.adapter}`);
|
|
1789
|
-
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(activeSlot.adapter, "
|
|
1856
|
+
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(activeSlot.adapter, t("bridge.reset.done")));
|
|
1790
1857
|
return;
|
|
1791
1858
|
case "confirm":
|
|
1792
1859
|
await this.confirmPendingApproval(message, activeSlot);
|
|
@@ -1799,42 +1866,49 @@ export class WechatDaemon {
|
|
|
1799
1866
|
return;
|
|
1800
1867
|
}
|
|
1801
1868
|
}
|
|
1869
|
+
async handleLanguageCommand(senderId, target) {
|
|
1870
|
+
await this.queueWechatMessage(senderId, await handleLanguageControl({
|
|
1871
|
+
target,
|
|
1872
|
+
settingsFile: this.deps.localeSettingsFile ?? LANGUAGE_FILE,
|
|
1873
|
+
adapters: [...this.slots.values()].map((slot) => slot.runtime),
|
|
1874
|
+
}));
|
|
1875
|
+
}
|
|
1802
1876
|
async confirmPendingApproval(message, activeSlot) {
|
|
1803
1877
|
const slot = this.resolvePendingApprovalSlot(activeSlot);
|
|
1804
1878
|
if (!slot || slot.pendingConfirmations.length === 0) {
|
|
1805
|
-
await this.queueWechatMessage(message.senderId, "
|
|
1879
|
+
await this.queueWechatMessage(message.senderId, t("approval.noPending"));
|
|
1806
1880
|
return;
|
|
1807
1881
|
}
|
|
1808
|
-
const
|
|
1882
|
+
const pending = slot.pendingConfirmations[0];
|
|
1883
|
+
const count = await slot.runtime.resolveApproval("confirm", slot.runtime.getState().pendingApprovals !== undefined ? slot.displayedApprovalRequestId ?? "" : pending.requestId) ? 1 : 0;
|
|
1809
1884
|
if (!count) {
|
|
1810
|
-
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(slot.adapter, "
|
|
1885
|
+
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(slot.adapter, t("bridge.approval.applyFailed")));
|
|
1811
1886
|
return;
|
|
1812
1887
|
}
|
|
1813
|
-
const preview =
|
|
1814
|
-
|
|
1815
|
-
slot.
|
|
1816
|
-
|
|
1817
|
-
inputPreview: preview,
|
|
1818
|
-
});
|
|
1888
|
+
const preview = pending.commandPreview;
|
|
1889
|
+
const adapterState = slot.runtime.getState();
|
|
1890
|
+
slot.pendingConfirmations = (adapterState.pendingApprovals ?? (adapterState.pendingApproval ? [adapterState.pendingApproval] : []))
|
|
1891
|
+
.map((request) => toPendingApproval({ type: "approval_required", request, timestamp: nowIso() }));
|
|
1819
1892
|
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..."));
|
|
1893
|
+
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(slot.adapter, t("approval.confirmed")));
|
|
1823
1894
|
}
|
|
1824
1895
|
async denyPendingApproval(message, activeSlot) {
|
|
1825
1896
|
const slot = this.resolvePendingApprovalSlot(activeSlot);
|
|
1826
1897
|
if (!slot || slot.pendingConfirmations.length === 0) {
|
|
1827
|
-
await this.queueWechatMessage(message.senderId, "
|
|
1898
|
+
await this.queueWechatMessage(message.senderId, t("approval.noPending"));
|
|
1828
1899
|
return;
|
|
1829
1900
|
}
|
|
1830
|
-
const
|
|
1901
|
+
const pending = slot.pendingConfirmations[0];
|
|
1902
|
+
const count = await slot.runtime.resolveApproval("deny", slot.runtime.getState().pendingApprovals !== undefined ? slot.displayedApprovalRequestId ?? "" : pending.requestId) ? 1 : 0;
|
|
1831
1903
|
if (!count) {
|
|
1832
|
-
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(slot.adapter, "
|
|
1904
|
+
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(slot.adapter, t("bridge.approval.denyFailed")));
|
|
1833
1905
|
return;
|
|
1834
1906
|
}
|
|
1835
|
-
|
|
1907
|
+
const adapterState = slot.runtime.getState();
|
|
1908
|
+
slot.pendingConfirmations = (adapterState.pendingApprovals ?? (adapterState.pendingApproval ? [adapterState.pendingApproval] : []))
|
|
1909
|
+
.map((request) => toPendingApproval({ type: "approval_required", request, timestamp: nowIso() }));
|
|
1836
1910
|
appendDaemonLog(`approval_denied: adapter=${slot.adapter} count=${count}`);
|
|
1837
|
-
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(slot.adapter,
|
|
1911
|
+
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(slot.adapter, t("approval.denied")));
|
|
1838
1912
|
}
|
|
1839
1913
|
async answerPendingUserInput(message, activeSlot, raw) {
|
|
1840
1914
|
// Mirror the /confirm and /deny behavior: search every slot for a pending
|
|
@@ -1842,12 +1916,12 @@ export class WechatDaemon {
|
|
|
1842
1916
|
// when the user has since switched the active adapter.
|
|
1843
1917
|
const slot = this.resolvePendingUserInputSlot(activeSlot);
|
|
1844
1918
|
if (!slot) {
|
|
1845
|
-
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(activeSlot.adapter, "
|
|
1919
|
+
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(activeSlot.adapter, t("bridge.answer.none")));
|
|
1846
1920
|
return;
|
|
1847
1921
|
}
|
|
1848
1922
|
const pending = slot.pendingUserInput;
|
|
1849
1923
|
if (!pending) {
|
|
1850
|
-
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(activeSlot.adapter, "
|
|
1924
|
+
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(activeSlot.adapter, t("bridge.answer.none")));
|
|
1851
1925
|
return;
|
|
1852
1926
|
}
|
|
1853
1927
|
const parsed = parsePendingUserInputAnswerCommand(raw, pending);
|
|
@@ -1855,18 +1929,15 @@ export class WechatDaemon {
|
|
|
1855
1929
|
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(slot.adapter, parsed.error));
|
|
1856
1930
|
return;
|
|
1857
1931
|
}
|
|
1858
|
-
const submitted = await slot.runtime.submitUserInput(parsed.answers);
|
|
1932
|
+
const submitted = await slot.runtime.submitUserInput(parsed.answers, pending.requestId);
|
|
1859
1933
|
if (!submitted) {
|
|
1860
|
-
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(slot.adapter, "
|
|
1934
|
+
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(slot.adapter, t("bridge.answer.failed")));
|
|
1861
1935
|
return;
|
|
1862
1936
|
}
|
|
1863
|
-
slot.pendingUserInput
|
|
1864
|
-
|
|
1865
|
-
startedAt: Date.now(),
|
|
1866
|
-
inputPreview: parsed.preview,
|
|
1867
|
-
});
|
|
1937
|
+
if (slot.pendingUserInput?.requestId === pending.requestId)
|
|
1938
|
+
slot.pendingUserInput = null;
|
|
1868
1939
|
appendDaemonLog(`user_input_answered: adapter=${slot.adapter} preview=${parsed.preview}`);
|
|
1869
|
-
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(slot.adapter, "
|
|
1940
|
+
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(slot.adapter, t("bridge.answer.sent")));
|
|
1870
1941
|
}
|
|
1871
1942
|
resolvePendingUserInputSlot(activeSlot) {
|
|
1872
1943
|
if (activeSlot.pendingUserInput) {
|
|
@@ -1904,14 +1975,14 @@ export class WechatDaemon {
|
|
|
1904
1975
|
.map((adapter) => this.slots.get(adapter))
|
|
1905
1976
|
.filter((slot) => Boolean(slot));
|
|
1906
1977
|
if (startedSlots.length === 0) {
|
|
1907
|
-
await this.queueWechatMessage(message.senderId, "
|
|
1978
|
+
await this.queueWechatMessage(message.senderId, t("daemon.broadcast.empty"));
|
|
1908
1979
|
return;
|
|
1909
1980
|
}
|
|
1910
1981
|
const busySlots = startedSlots.filter((slot) => this.isSlotBusyForBroadcast(slot));
|
|
1911
1982
|
if (busySlots.length > 0) {
|
|
1912
1983
|
const names = busySlots.map((slot) => slot.adapter).join(", ");
|
|
1913
1984
|
appendDaemonLog(`broadcast_canceled_busy: busy=[${names}]`);
|
|
1914
|
-
await this.queueWechatMessage(message.senderId,
|
|
1985
|
+
await this.queueWechatMessage(message.senderId, t("daemon.broadcast.busy", { workers: t(busySlots.length > 1 ? "daemon.broadcast.multipleBusy" : "daemon.broadcast.singleBusy"), names }));
|
|
1915
1986
|
return;
|
|
1916
1987
|
}
|
|
1917
1988
|
const broadcastMessage = { ...message, text };
|
|
@@ -1928,10 +1999,10 @@ export class WechatDaemon {
|
|
|
1928
1999
|
dispatched.push(adapter);
|
|
1929
2000
|
}
|
|
1930
2001
|
const lines = [
|
|
1931
|
-
|
|
2002
|
+
t("daemon.broadcast.sent", { count: dispatched.length, workers: t(dispatched.length > 1 ? "daemon.broadcast.multipleWorkers" : "daemon.broadcast.singleWorker"), names: dispatched.join(", ") }),
|
|
1932
2003
|
];
|
|
1933
2004
|
if (skipped.length > 0) {
|
|
1934
|
-
lines.push(
|
|
2005
|
+
lines.push(t("daemon.broadcast.skipped", { names: skipped.join(", ") }));
|
|
1935
2006
|
}
|
|
1936
2007
|
await this.queueWechatMessage(message.senderId, lines.join("\n"));
|
|
1937
2008
|
}
|
|
@@ -1948,7 +2019,7 @@ export class WechatDaemon {
|
|
|
1948
2019
|
task: nextTask,
|
|
1949
2020
|
conversation: inboundConversation,
|
|
1950
2021
|
onBusy: async () => {
|
|
1951
|
-
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(slot.adapter,
|
|
2022
|
+
await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(slot.adapter, t("bridge.busy.remote", { adapter: slot.adapter })), "notice", conversationOverride);
|
|
1952
2023
|
},
|
|
1953
2024
|
forward: async () => {
|
|
1954
2025
|
appendDaemonLog(`forwarded_input: adapter=${slot.adapter} text=${truncatePreview(preview)}`);
|
|
@@ -2346,11 +2417,13 @@ export async function runDaemon(options) {
|
|
|
2346
2417
|
: null;
|
|
2347
2418
|
const credentials = wecomAccount
|
|
2348
2419
|
? { userId: wecomAccount.operatorUserId }
|
|
2349
|
-
:
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2420
|
+
: options.channelId === "local"
|
|
2421
|
+
? { userId: LOCAL_CHANNEL_OPERATOR_ID }
|
|
2422
|
+
: await ensureWechatCredentials({
|
|
2423
|
+
requireUserId: true,
|
|
2424
|
+
validateExisting: true,
|
|
2425
|
+
log,
|
|
2426
|
+
});
|
|
2354
2427
|
if (!credentials.userId) {
|
|
2355
2428
|
throw new Error(options.channelId === "wecom"
|
|
2356
2429
|
? "Saved WeCom credentials are missing operatorUserId."
|
|
@@ -2360,7 +2433,9 @@ export async function runDaemon(options) {
|
|
|
2360
2433
|
cwd: options.cwd,
|
|
2361
2434
|
profile: options.profile,
|
|
2362
2435
|
authorizedUserId: credentials.userId,
|
|
2363
|
-
transport:
|
|
2436
|
+
transport: (options.channelId ?? "wechat") === "wechat"
|
|
2437
|
+
? new WeChatTransport({ log, logError })
|
|
2438
|
+
: null,
|
|
2364
2439
|
channelId: options.channelId,
|
|
2365
2440
|
accountId: wecomAccount?.botId,
|
|
2366
2441
|
wecomTransport: wecomAccount
|
|
@@ -2372,6 +2447,9 @@ export async function runDaemon(options) {
|
|
|
2372
2447
|
},
|
|
2373
2448
|
})
|
|
2374
2449
|
: null,
|
|
2450
|
+
localFiles: options.channelId === "local"
|
|
2451
|
+
? getLocalChannelFiles(options.cwd)
|
|
2452
|
+
: null,
|
|
2375
2453
|
});
|
|
2376
2454
|
if (cleanupResult.action === "stopped" && isDaemonAdapterKind(cleanupResult.lock.adapter)) {
|
|
2377
2455
|
daemon.takenOverAdapter = cleanupResult.lock.adapter;
|