@unlinearity/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 +14 -4
- 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
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
// LICENSE.txt. Network services built on it must offer source to users.
|
|
7
7
|
import crypto from "node:crypto";
|
|
8
8
|
import fs from "node:fs";
|
|
9
|
+
import { t } from "../i18n/index.js";
|
|
9
10
|
import net from "node:net";
|
|
10
11
|
import os from "node:os";
|
|
11
12
|
import path from "node:path";
|
|
@@ -301,7 +302,7 @@ export class PiTuiAdapter {
|
|
|
301
302
|
return;
|
|
302
303
|
}
|
|
303
304
|
if (this.state.status === "busy") {
|
|
304
|
-
throw new Error("
|
|
305
|
+
throw new Error(t("adapter.pi.busy"));
|
|
305
306
|
}
|
|
306
307
|
this.beginTurn("wechat");
|
|
307
308
|
try {
|
|
@@ -319,7 +320,7 @@ export class PiTuiAdapter {
|
|
|
319
320
|
async resumeSession(sessionId) {
|
|
320
321
|
this.assertConnected();
|
|
321
322
|
if (this.state.status === "busy") {
|
|
322
|
-
throw new Error("
|
|
323
|
+
throw new Error(t("adapter.pi.switchBusy"));
|
|
323
324
|
}
|
|
324
325
|
const sessionPath = findPiSessionFile(this.options.cwd, sessionId);
|
|
325
326
|
if (!sessionPath) {
|
|
@@ -338,7 +339,7 @@ export class PiTuiAdapter {
|
|
|
338
339
|
});
|
|
339
340
|
this.applySessionState(response.data, "wechat", "wechat_resume", true);
|
|
340
341
|
if (isRecord(response.data) && response.data.cancelled === true) {
|
|
341
|
-
throw new Error("
|
|
342
|
+
throw new Error(t("adapter.pi.switchCancelled"));
|
|
342
343
|
}
|
|
343
344
|
}
|
|
344
345
|
finally {
|
|
@@ -348,14 +349,14 @@ export class PiTuiAdapter {
|
|
|
348
349
|
async createSession() {
|
|
349
350
|
this.assertConnected();
|
|
350
351
|
if (this.state.status === "busy") {
|
|
351
|
-
throw new Error("
|
|
352
|
+
throw new Error(t("adapter.pi.createBusy"));
|
|
352
353
|
}
|
|
353
354
|
this.pendingSessionSwitch = { source: "wechat", reason: "wechat_resume" };
|
|
354
355
|
try {
|
|
355
356
|
const response = await this.sendCommand("new_session");
|
|
356
357
|
this.applySessionState(response.data, "wechat", "wechat_resume", true);
|
|
357
358
|
if (isRecord(response.data) && response.data.cancelled === true) {
|
|
358
|
-
throw new Error("
|
|
359
|
+
throw new Error(t("adapter.pi.createCancelled"));
|
|
359
360
|
}
|
|
360
361
|
}
|
|
361
362
|
finally {
|
|
@@ -364,11 +365,11 @@ export class PiTuiAdapter {
|
|
|
364
365
|
}
|
|
365
366
|
async listModels() {
|
|
366
367
|
if (this.state.status === "busy") {
|
|
367
|
-
throw new Error("
|
|
368
|
+
throw new Error(t("adapter.pi.listModelsBusy"));
|
|
368
369
|
}
|
|
369
370
|
const response = await this.sendCommand("list_models");
|
|
370
371
|
if (!isRecord(response.data) || !Array.isArray(response.data.models)) {
|
|
371
|
-
throw new Error("
|
|
372
|
+
throw new Error(t("adapter.pi.invalidModels"));
|
|
372
373
|
}
|
|
373
374
|
return response.data.models
|
|
374
375
|
.filter((model) => isRecord(model))
|
|
@@ -381,15 +382,15 @@ export class PiTuiAdapter {
|
|
|
381
382
|
}
|
|
382
383
|
async selectModel(modelId) {
|
|
383
384
|
if (this.state.status === "busy") {
|
|
384
|
-
throw new Error("
|
|
385
|
+
throw new Error(t("adapter.pi.selectModelBusy"));
|
|
385
386
|
}
|
|
386
387
|
const response = await this.sendCommand("select_model", { modelId });
|
|
387
388
|
if (!isRecord(response.data) || !isRecord(response.data.model)) {
|
|
388
|
-
throw new Error("
|
|
389
|
+
throw new Error(t("adapter.pi.invalidModel"));
|
|
389
390
|
}
|
|
390
391
|
const model = response.data.model;
|
|
391
392
|
if (typeof model.id !== "string" || typeof model.displayName !== "string") {
|
|
392
|
-
throw new Error("
|
|
393
|
+
throw new Error(t("adapter.pi.invalidModel"));
|
|
393
394
|
}
|
|
394
395
|
return {
|
|
395
396
|
id: model.id,
|
|
@@ -565,7 +566,7 @@ export class PiTuiAdapter {
|
|
|
565
566
|
}
|
|
566
567
|
break;
|
|
567
568
|
case "agent_settled":
|
|
568
|
-
void this.finalizeTurn();
|
|
569
|
+
void this.finalizeTurn(frame.aborted === true);
|
|
569
570
|
break;
|
|
570
571
|
}
|
|
571
572
|
}
|
|
@@ -581,7 +582,7 @@ export class PiTuiAdapter {
|
|
|
581
582
|
if (this.state.activeTurnOrigin === "wechat") {
|
|
582
583
|
this.emit({
|
|
583
584
|
type: "task_failed",
|
|
584
|
-
message: "
|
|
585
|
+
message: t("adapter.pi.localSwitchInterrupted"),
|
|
585
586
|
timestamp: nowIso(),
|
|
586
587
|
});
|
|
587
588
|
}
|
|
@@ -598,7 +599,7 @@ export class PiTuiAdapter {
|
|
|
598
599
|
this.state.lastInputAt = nowIso();
|
|
599
600
|
this.setStatus("busy", origin === "wechat" ? "Pi TUI is handling a WeChat request." : "Pi TUI is working.");
|
|
600
601
|
}
|
|
601
|
-
async finalizeTurn() {
|
|
602
|
+
async finalizeTurn(aborted = false) {
|
|
602
603
|
if (this.finalizingTurn || !this.state.activeTurnOrigin) {
|
|
603
604
|
return;
|
|
604
605
|
}
|
|
@@ -613,10 +614,14 @@ export class PiTuiAdapter {
|
|
|
613
614
|
// Mirror the other adapters: a settled turn always reports completion so
|
|
614
615
|
// bridge/daemon ownership state is released even when the turn produced
|
|
615
616
|
// no visible reply.
|
|
616
|
-
this.emit({
|
|
617
|
+
this.emit({
|
|
618
|
+
type: "task_complete",
|
|
619
|
+
...(aborted ? { summary: t("task.interrupted") } : {}),
|
|
620
|
+
timestamp: nowIso(),
|
|
621
|
+
});
|
|
617
622
|
this.state.lastOutputAt = nowIso();
|
|
618
623
|
this.clearActiveTurn();
|
|
619
|
-
this.setStatus("idle");
|
|
624
|
+
this.setStatus("idle", aborted ? "Pi task interrupted." : undefined);
|
|
620
625
|
}
|
|
621
626
|
finally {
|
|
622
627
|
this.finalizingTurn = false;
|
|
@@ -645,7 +650,7 @@ export class PiTuiAdapter {
|
|
|
645
650
|
this.assertConnected();
|
|
646
651
|
const socket = this.socket;
|
|
647
652
|
if (!socket || socket.destroyed) {
|
|
648
|
-
return Promise.reject(new Error("
|
|
653
|
+
return Promise.reject(new Error(t("adapter.pi.disconnected")));
|
|
649
654
|
}
|
|
650
655
|
const id = `pi-tui-${++this.requestCounter}`;
|
|
651
656
|
return new Promise((resolve, reject) => {
|
|
@@ -685,7 +690,7 @@ export class PiTuiAdapter {
|
|
|
685
690
|
}
|
|
686
691
|
assertConnected() {
|
|
687
692
|
if (!this.child || !this.socket || this.socket.destroyed) {
|
|
688
|
-
throw new Error("
|
|
693
|
+
throw new Error(t("adapter.pi.disconnected"));
|
|
689
694
|
}
|
|
690
695
|
}
|
|
691
696
|
clearActiveTurn() {
|
|
@@ -715,7 +720,7 @@ export class PiTuiAdapter {
|
|
|
715
720
|
this.setStatus("idle", "The Pi TUI extension disconnected; the bridge is waiting for a reconnect.");
|
|
716
721
|
this.emit({
|
|
717
722
|
type: "fatal_error",
|
|
718
|
-
message: "
|
|
723
|
+
message: t("adapter.pi.extensionDisconnected"),
|
|
719
724
|
timestamp: nowIso(),
|
|
720
725
|
});
|
|
721
726
|
}
|
|
@@ -141,24 +141,24 @@ export function buildCodexApprovalRequest(method, params) {
|
|
|
141
141
|
const reason = typeof params.reason === "string" ? params.reason : "";
|
|
142
142
|
const preview = command && cwd
|
|
143
143
|
? `${command} (${cwd})`
|
|
144
|
-
: command || reason || "
|
|
144
|
+
: command || reason || t("approval.codex.commandPreview");
|
|
145
145
|
return {
|
|
146
146
|
source: "cli",
|
|
147
147
|
summary: reason
|
|
148
|
-
?
|
|
149
|
-
: "
|
|
148
|
+
? t("approval.codex.commandReason", { reason: truncatePreview(reason, 160) })
|
|
149
|
+
: t("approval.codex.command"),
|
|
150
150
|
commandPreview: truncatePreview(preview, 180),
|
|
151
151
|
};
|
|
152
152
|
}
|
|
153
153
|
if (method === "item/fileChange/requestApproval") {
|
|
154
154
|
const grantRoot = typeof params.grantRoot === "string" ? params.grantRoot : "";
|
|
155
155
|
const reason = typeof params.reason === "string" ? params.reason : "";
|
|
156
|
-
const preview = grantRoot || reason || "
|
|
156
|
+
const preview = grantRoot || reason || t("approval.codex.filePreview");
|
|
157
157
|
return {
|
|
158
158
|
source: "cli",
|
|
159
159
|
summary: reason
|
|
160
|
-
?
|
|
161
|
-
: "
|
|
160
|
+
? t("approval.codex.fileReason", { reason: truncatePreview(reason, 160) })
|
|
161
|
+
: t("approval.codex.file"),
|
|
162
162
|
commandPreview: truncatePreview(preview, 180),
|
|
163
163
|
};
|
|
164
164
|
}
|
|
@@ -168,8 +168,8 @@ export function buildCodexApprovalRequest(method, params) {
|
|
|
168
168
|
return {
|
|
169
169
|
source: "cli",
|
|
170
170
|
summary: reason
|
|
171
|
-
?
|
|
172
|
-
: "
|
|
171
|
+
? t("approval.codex.permissionsReason", { reason: truncatePreview(reason, 160) })
|
|
172
|
+
: t("approval.codex.permissions"),
|
|
173
173
|
commandPreview: truncatePreview(preview, 180),
|
|
174
174
|
};
|
|
175
175
|
}
|
|
@@ -428,7 +428,7 @@ export function buildCodexUserInputRequest(params) {
|
|
|
428
428
|
return null;
|
|
429
429
|
}
|
|
430
430
|
return {
|
|
431
|
-
summary: "
|
|
431
|
+
summary: t("input.request.codex"),
|
|
432
432
|
questions,
|
|
433
433
|
};
|
|
434
434
|
}
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Copyright (c) 2026 UNLINEARITY <unlinearity@gmail.com>
|
|
3
|
+
// Source: https://github.com/UNLINEARITY/CLI-WeChat-Bridge
|
|
4
|
+
// This file is part of CLI-WeChat-Bridge. Modifications and derivative works
|
|
5
|
+
// must be released under AGPL-3.0-or-later with full source code; see
|
|
6
|
+
// LICENSE.txt. Network services built on it must offer source to users.
|
|
7
|
+
import { t } from "../i18n/index.js";
|
|
1
8
|
import { formatResumeSessionList, formatSessionSwitchMessage, } from "./bridge-utils.js";
|
|
2
9
|
export const RESUME_SESSION_LIST_LIMIT = 8;
|
|
3
10
|
export const RESUME_SESSION_SNAPSHOT_TTL_MS = 5 * 60 * 1_000;
|
|
@@ -18,7 +25,7 @@ function adapterDisplayName(adapter) {
|
|
|
18
25
|
}
|
|
19
26
|
}
|
|
20
27
|
function adapterSessionNoun(adapter) {
|
|
21
|
-
return adapter === "codex" ? "thread" : "session";
|
|
28
|
+
return t(adapter === "codex" ? "session.thread" : "session.noun");
|
|
22
29
|
}
|
|
23
30
|
export function isWechatResumeEnabled(adapter) {
|
|
24
31
|
return (adapter === "codex" ||
|
|
@@ -67,7 +74,7 @@ export class ResumeSessionCoordinator {
|
|
|
67
74
|
return {
|
|
68
75
|
kind: "already_active",
|
|
69
76
|
sessionId: candidate.sessionId,
|
|
70
|
-
message:
|
|
77
|
+
message: t("session.alreadyActive", { adapter: adapterDisplayName(this.adapter), noun: adapterSessionNoun(this.adapter), id: candidate.sessionId.slice(0, 12) }),
|
|
71
78
|
};
|
|
72
79
|
}
|
|
73
80
|
await this.runtime.resumeSession(candidate.sessionId);
|
|
@@ -86,12 +93,12 @@ export class ResumeSessionCoordinator {
|
|
|
86
93
|
if (/^\d+$/.test(target)) {
|
|
87
94
|
const snapshot = this.getCurrentSnapshot();
|
|
88
95
|
if (!snapshot) {
|
|
89
|
-
throw new Error("
|
|
96
|
+
throw new Error(t("session.list.expired"));
|
|
90
97
|
}
|
|
91
98
|
const index = Number(target) - 1;
|
|
92
99
|
const candidate = snapshot.candidates[index];
|
|
93
100
|
if (!candidate) {
|
|
94
|
-
throw new Error(
|
|
101
|
+
throw new Error(t("session.list.range", { target }));
|
|
95
102
|
}
|
|
96
103
|
return candidate;
|
|
97
104
|
}
|
|
@@ -122,7 +129,7 @@ export class ResumeSessionCoordinator {
|
|
|
122
129
|
}
|
|
123
130
|
const prefixMatches = candidates.filter((candidate) => candidate.sessionId.toLowerCase().startsWith(normalizedTarget));
|
|
124
131
|
if (prefixMatches.length > 1) {
|
|
125
|
-
throw new Error(
|
|
132
|
+
throw new Error(t("session.list.ambiguous", { target }));
|
|
126
133
|
}
|
|
127
134
|
return prefixMatches[0] ?? null;
|
|
128
135
|
}
|
|
@@ -145,7 +152,7 @@ export class ResumeSessionCoordinator {
|
|
|
145
152
|
if (/^Failed to list\s/i.test(message)) {
|
|
146
153
|
throw error;
|
|
147
154
|
}
|
|
148
|
-
throw new Error(
|
|
155
|
+
throw new Error(t("session.list.failed", { adapter: adapterDisplayName(this.adapter), noun: t(this.adapter === "codex" ? "session.threads" : "session.sessions"), message }), { cause: error });
|
|
149
156
|
}
|
|
150
157
|
}
|
|
151
158
|
}
|
|
@@ -85,7 +85,9 @@ export function normalizeBridgeLockPayload(value) {
|
|
|
85
85
|
startedAt: record.startedAt,
|
|
86
86
|
lifecycle: record.lifecycle === "companion_bound" ? "companion_bound" : "persistent",
|
|
87
87
|
legacyLifecycleFallback: hasExplicitLifecycle ? undefined : true,
|
|
88
|
-
...(record.channelId === "wechat" ||
|
|
88
|
+
...(record.channelId === "wechat" ||
|
|
89
|
+
record.channelId === "wecom" ||
|
|
90
|
+
record.channelId === "telegram"
|
|
89
91
|
? { channelId: record.channelId }
|
|
90
92
|
: {}),
|
|
91
93
|
};
|