@unlinearity/cli-wechat-bridge 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +21 -0
- package/README.md +531 -0
- package/bin/_run-entry.mjs +41 -0
- package/bin/wechat-bridge-claude.mjs +5 -0
- package/bin/wechat-bridge-codex.mjs +5 -0
- package/bin/wechat-bridge-opencode.mjs +5 -0
- package/bin/wechat-bridge-shell.mjs +5 -0
- package/bin/wechat-bridge.mjs +5 -0
- package/bin/wechat-check-update.mjs +5 -0
- package/bin/wechat-claude-start.mjs +5 -0
- package/bin/wechat-claude.mjs +5 -0
- package/bin/wechat-codex-start.mjs +5 -0
- package/bin/wechat-codex.mjs +5 -0
- package/bin/wechat-opencode-start.mjs +5 -0
- package/bin/wechat-opencode.mjs +5 -0
- package/package.json +86 -0
- package/src/bridge/bridge-adapters.claude.ts +1047 -0
- package/src/bridge/bridge-adapters.codex.ts +2733 -0
- package/src/bridge/bridge-adapters.core.ts +877 -0
- package/src/bridge/bridge-adapters.opencode.ts +2390 -0
- package/src/bridge/bridge-adapters.shared.ts +1647 -0
- package/src/bridge/bridge-adapters.shell.ts +456 -0
- package/src/bridge/bridge-adapters.ts +30 -0
- package/src/bridge/bridge-controller.ts +58 -0
- package/src/bridge/bridge-final-reply.ts +56 -0
- package/src/bridge/bridge-process-reaper.ts +417 -0
- package/src/bridge/bridge-state.ts +520 -0
- package/src/bridge/bridge-types.ts +218 -0
- package/src/bridge/bridge-utils.ts +1524 -0
- package/src/bridge/claude-hook.ts +92 -0
- package/src/bridge/claude-hooks.ts +376 -0
- package/src/bridge/wechat-bridge.ts +1305 -0
- package/src/commands/check-update.ts +46 -0
- package/src/companion/codex-panel-link.ts +151 -0
- package/src/companion/codex-panel.ts +225 -0
- package/src/companion/codex-remote-client.ts +178 -0
- package/src/companion/local-companion-link.ts +338 -0
- package/src/companion/local-companion-start.ts +520 -0
- package/src/companion/local-companion.ts +532 -0
- package/src/media/media-types.ts +11 -0
- package/src/runtime/create-runtime-host.ts +16 -0
- package/src/runtime/legacy-adapter-runtime.ts +55 -0
- package/src/runtime/runtime-types.ts +51 -0
- package/src/utils/version-checker.ts +197 -0
- package/src/wechat/channel-config.ts +141 -0
- package/src/wechat/setup.ts +232 -0
- package/src/wechat/standalone-bot.ts +483 -0
- package/src/wechat/wechat-channel.ts +608 -0
- package/src/wechat/wechat-transport.ts +1290 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import type net from "node:net";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
ensureWorkspaceChannelDir,
|
|
7
|
+
getWorkspaceChannelPaths,
|
|
8
|
+
} from "../wechat/channel-config.ts";
|
|
9
|
+
import type {
|
|
10
|
+
BridgeAdapterKind,
|
|
11
|
+
BridgeAdapterState,
|
|
12
|
+
BridgeEvent,
|
|
13
|
+
BridgeWorkerStatus,
|
|
14
|
+
} from "../bridge/bridge-types.ts";
|
|
15
|
+
import {
|
|
16
|
+
LOCAL_CLIENT_PROTOCOL_VERSION,
|
|
17
|
+
type LocalClientEndpoint,
|
|
18
|
+
} from "../runtime/runtime-types.ts";
|
|
19
|
+
|
|
20
|
+
export type LocalCompanionCommand =
|
|
21
|
+
| { command: "send_input"; text: string }
|
|
22
|
+
| { command: "list_resume_sessions"; limit?: number }
|
|
23
|
+
| { command: "list_resume_threads"; limit?: number }
|
|
24
|
+
| { command: "resume_session"; sessionId: string }
|
|
25
|
+
| { command: "resume_thread"; threadId: string }
|
|
26
|
+
| { command: "interrupt" }
|
|
27
|
+
| { command: "reset" }
|
|
28
|
+
| { command: "dispose" }
|
|
29
|
+
| { command: "resolve_approval"; action: "confirm" | "deny" };
|
|
30
|
+
|
|
31
|
+
export type LocalCompanionCloseReason =
|
|
32
|
+
| "bridge_dispose"
|
|
33
|
+
| "companion_shutdown"
|
|
34
|
+
| "fatal_error"
|
|
35
|
+
| "signal"
|
|
36
|
+
| "worker_exit";
|
|
37
|
+
|
|
38
|
+
export type LocalCompanionEndpoint = LocalClientEndpoint;
|
|
39
|
+
|
|
40
|
+
export type LocalCompanionMessage =
|
|
41
|
+
| {
|
|
42
|
+
type: "hello";
|
|
43
|
+
token: string;
|
|
44
|
+
companionPid?: number;
|
|
45
|
+
panelPid?: number;
|
|
46
|
+
}
|
|
47
|
+
| {
|
|
48
|
+
type: "hello_ack";
|
|
49
|
+
}
|
|
50
|
+
| {
|
|
51
|
+
type: "closing";
|
|
52
|
+
reason: LocalCompanionCloseReason;
|
|
53
|
+
exitCode?: number;
|
|
54
|
+
}
|
|
55
|
+
| {
|
|
56
|
+
type: "request";
|
|
57
|
+
id: string;
|
|
58
|
+
payload: LocalCompanionCommand;
|
|
59
|
+
}
|
|
60
|
+
| {
|
|
61
|
+
type: "response";
|
|
62
|
+
id: string;
|
|
63
|
+
ok: boolean;
|
|
64
|
+
result?: unknown;
|
|
65
|
+
error?: string;
|
|
66
|
+
}
|
|
67
|
+
| {
|
|
68
|
+
type: "event";
|
|
69
|
+
event: BridgeEvent;
|
|
70
|
+
}
|
|
71
|
+
| {
|
|
72
|
+
type: "state";
|
|
73
|
+
state: BridgeAdapterState;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
function normalizeEndpoint(value: unknown): LocalCompanionEndpoint | null {
|
|
77
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const record = value as Record<string, unknown>;
|
|
82
|
+
const kind =
|
|
83
|
+
record.kind === "codex" || record.kind === "claude" || record.kind === "opencode" || record.kind === "shell"
|
|
84
|
+
? record.kind
|
|
85
|
+
: "codex";
|
|
86
|
+
const sharedSessionId =
|
|
87
|
+
typeof record.sharedSessionId === "string"
|
|
88
|
+
? record.sharedSessionId
|
|
89
|
+
: typeof record.sharedThreadId === "string"
|
|
90
|
+
? record.sharedThreadId
|
|
91
|
+
: undefined;
|
|
92
|
+
|
|
93
|
+
if (
|
|
94
|
+
typeof record.instanceId !== "string" ||
|
|
95
|
+
typeof record.port !== "number" ||
|
|
96
|
+
typeof record.token !== "string" ||
|
|
97
|
+
typeof record.cwd !== "string" ||
|
|
98
|
+
typeof record.command !== "string" ||
|
|
99
|
+
typeof record.startedAt !== "string"
|
|
100
|
+
) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
protocolVersion:
|
|
106
|
+
typeof record.protocolVersion === "number"
|
|
107
|
+
? record.protocolVersion
|
|
108
|
+
: LOCAL_CLIENT_PROTOCOL_VERSION,
|
|
109
|
+
runtimeKind:
|
|
110
|
+
record.runtimeKind === "codex_runtime_host" ? "codex_runtime_host" : "legacy_adapter",
|
|
111
|
+
instanceId: record.instanceId,
|
|
112
|
+
kind,
|
|
113
|
+
port: record.port,
|
|
114
|
+
token: record.token,
|
|
115
|
+
renderMode:
|
|
116
|
+
record.renderMode === "embedded" ||
|
|
117
|
+
record.renderMode === "panel" ||
|
|
118
|
+
record.renderMode === "companion" ||
|
|
119
|
+
record.renderMode === "headless"
|
|
120
|
+
? record.renderMode
|
|
121
|
+
: undefined,
|
|
122
|
+
bridgeOwnerPid:
|
|
123
|
+
typeof record.bridgeOwnerPid === "number" ? record.bridgeOwnerPid : undefined,
|
|
124
|
+
serverPort: typeof record.serverPort === "number" ? record.serverPort : undefined,
|
|
125
|
+
serverUrl: typeof record.serverUrl === "string" ? record.serverUrl : undefined,
|
|
126
|
+
remoteAuthTokenEnv:
|
|
127
|
+
typeof record.remoteAuthTokenEnv === "string" ? record.remoteAuthTokenEnv : undefined,
|
|
128
|
+
cwd: record.cwd,
|
|
129
|
+
command: record.command,
|
|
130
|
+
profile: typeof record.profile === "string" ? record.profile : undefined,
|
|
131
|
+
sharedSessionId,
|
|
132
|
+
sharedThreadId:
|
|
133
|
+
kind === "codex" || kind === "opencode"
|
|
134
|
+
? sharedSessionId
|
|
135
|
+
: typeof record.sharedThreadId === "string"
|
|
136
|
+
? record.sharedThreadId
|
|
137
|
+
: undefined,
|
|
138
|
+
resumeConversationId:
|
|
139
|
+
typeof record.resumeConversationId === "string" ? record.resumeConversationId : undefined,
|
|
140
|
+
transcriptPath:
|
|
141
|
+
typeof record.transcriptPath === "string" ? record.transcriptPath : undefined,
|
|
142
|
+
companionPid: typeof record.companionPid === "number" ? record.companionPid : undefined,
|
|
143
|
+
companionConnectedAt:
|
|
144
|
+
typeof record.companionConnectedAt === "string" ? record.companionConnectedAt : undefined,
|
|
145
|
+
companionStatus:
|
|
146
|
+
typeof record.companionStatus === "string"
|
|
147
|
+
? (record.companionStatus as BridgeWorkerStatus)
|
|
148
|
+
: undefined,
|
|
149
|
+
companionLastStateAt:
|
|
150
|
+
typeof record.companionLastStateAt === "string" ? record.companionLastStateAt : undefined,
|
|
151
|
+
companionWorkerPid:
|
|
152
|
+
typeof record.companionWorkerPid === "number" ? record.companionWorkerPid : undefined,
|
|
153
|
+
startedAt: record.startedAt,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function buildLocalCompanionToken(): string {
|
|
158
|
+
return crypto.randomBytes(18).toString("hex");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function writeLocalCompanionEndpoint(endpoint: LocalCompanionEndpoint): void {
|
|
162
|
+
const { endpointFile } = ensureWorkspaceChannelDir(endpoint.cwd);
|
|
163
|
+
const payload: LocalCompanionEndpoint = {
|
|
164
|
+
...endpoint,
|
|
165
|
+
protocolVersion: endpoint.protocolVersion ?? LOCAL_CLIENT_PROTOCOL_VERSION,
|
|
166
|
+
sharedThreadId:
|
|
167
|
+
endpoint.kind === "codex" || endpoint.kind === "opencode"
|
|
168
|
+
? endpoint.sharedSessionId ?? endpoint.sharedThreadId
|
|
169
|
+
: undefined,
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
fs.writeFileSync(endpointFile, JSON.stringify(payload, null, 2), "utf8");
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function readLocalCompanionEndpoint(cwd: string): LocalCompanionEndpoint | null {
|
|
176
|
+
try {
|
|
177
|
+
const { endpointFile } = getWorkspaceChannelPaths(cwd);
|
|
178
|
+
if (!fs.existsSync(endpointFile)) {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
return normalizeEndpoint(JSON.parse(fs.readFileSync(endpointFile, "utf8")));
|
|
182
|
+
} catch {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function clearLocalCompanionEndpoint(cwd: string, instanceId?: string): void {
|
|
188
|
+
try {
|
|
189
|
+
const { endpointFile } = getWorkspaceChannelPaths(cwd);
|
|
190
|
+
if (!fs.existsSync(endpointFile)) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (!instanceId) {
|
|
195
|
+
fs.rmSync(endpointFile, { force: true });
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const endpoint = readLocalCompanionEndpoint(cwd);
|
|
200
|
+
if (!endpoint || endpoint.instanceId === instanceId) {
|
|
201
|
+
fs.rmSync(endpointFile, { force: true });
|
|
202
|
+
}
|
|
203
|
+
} catch {
|
|
204
|
+
// Best effort cleanup.
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function clearLocalCompanionOccupancy(cwd: string, instanceId?: string): void {
|
|
209
|
+
try {
|
|
210
|
+
const endpoint = readLocalCompanionEndpoint(cwd);
|
|
211
|
+
if (!endpoint) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (instanceId && endpoint.instanceId !== instanceId) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
writeLocalCompanionEndpoint({
|
|
220
|
+
...endpoint,
|
|
221
|
+
companionPid: undefined,
|
|
222
|
+
companionConnectedAt: undefined,
|
|
223
|
+
companionStatus: undefined,
|
|
224
|
+
companionLastStateAt: undefined,
|
|
225
|
+
companionWorkerPid: undefined,
|
|
226
|
+
});
|
|
227
|
+
} catch {
|
|
228
|
+
// Best effort cleanup.
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function updateLocalCompanionHealth(
|
|
233
|
+
cwd: string,
|
|
234
|
+
patch: {
|
|
235
|
+
companionStatus?: BridgeWorkerStatus;
|
|
236
|
+
companionLastStateAt?: string;
|
|
237
|
+
companionWorkerPid?: number;
|
|
238
|
+
},
|
|
239
|
+
instanceId?: string,
|
|
240
|
+
): void {
|
|
241
|
+
try {
|
|
242
|
+
const endpoint = readLocalCompanionEndpoint(cwd);
|
|
243
|
+
if (!endpoint) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (instanceId && endpoint.instanceId !== instanceId) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
writeLocalCompanionEndpoint({
|
|
252
|
+
...endpoint,
|
|
253
|
+
companionStatus: patch.companionStatus,
|
|
254
|
+
companionLastStateAt: patch.companionLastStateAt,
|
|
255
|
+
companionWorkerPid: patch.companionWorkerPid,
|
|
256
|
+
});
|
|
257
|
+
} catch {
|
|
258
|
+
// Best effort cleanup.
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function updateLocalCompanionOccupancy(
|
|
263
|
+
cwd: string,
|
|
264
|
+
patch: {
|
|
265
|
+
companionPid?: number;
|
|
266
|
+
companionConnectedAt?: string;
|
|
267
|
+
},
|
|
268
|
+
instanceId?: string,
|
|
269
|
+
): void {
|
|
270
|
+
try {
|
|
271
|
+
const endpoint = readLocalCompanionEndpoint(cwd);
|
|
272
|
+
if (!endpoint) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (instanceId && endpoint.instanceId !== instanceId) {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
writeLocalCompanionEndpoint({
|
|
281
|
+
...endpoint,
|
|
282
|
+
companionPid: patch.companionPid,
|
|
283
|
+
companionConnectedAt: patch.companionConnectedAt,
|
|
284
|
+
});
|
|
285
|
+
} catch {
|
|
286
|
+
// Best effort cleanup.
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export function sendLocalCompanionMessage(
|
|
291
|
+
socket: net.Socket,
|
|
292
|
+
message: LocalCompanionMessage,
|
|
293
|
+
): boolean {
|
|
294
|
+
if (socket.destroyed || socket.writableEnded) {
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
try {
|
|
299
|
+
return socket.write(`${JSON.stringify(message)}\n`);
|
|
300
|
+
} catch {
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export function attachLocalCompanionMessageListener(
|
|
306
|
+
socket: net.Socket,
|
|
307
|
+
onMessage: (message: LocalCompanionMessage) => void,
|
|
308
|
+
): () => void {
|
|
309
|
+
let buffer = "";
|
|
310
|
+
const onData = (chunk: string | Buffer) => {
|
|
311
|
+
buffer += typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
312
|
+
|
|
313
|
+
while (true) {
|
|
314
|
+
const newlineIndex = buffer.indexOf("\n");
|
|
315
|
+
if (newlineIndex < 0) {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const line = buffer.slice(0, newlineIndex).trim();
|
|
320
|
+
buffer = buffer.slice(newlineIndex + 1);
|
|
321
|
+
if (!line) {
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
try {
|
|
326
|
+
onMessage(JSON.parse(line) as LocalCompanionMessage);
|
|
327
|
+
} catch {
|
|
328
|
+
// Ignore malformed local IPC frames.
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
socket.setEncoding("utf8");
|
|
334
|
+
socket.on("data", onData);
|
|
335
|
+
return () => {
|
|
336
|
+
socket.off("data", onData);
|
|
337
|
+
};
|
|
338
|
+
}
|