@unlinearity/cli-wechat-bridge 1.0.0 → 1.0.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/bin/_run-entry.mjs +2 -8
- package/bin/wechat-bridge-claude.mjs +2 -2
- package/bin/wechat-bridge-codex.mjs +2 -2
- package/bin/wechat-bridge-opencode.mjs +2 -2
- package/bin/wechat-bridge-shell.mjs +2 -2
- package/bin/wechat-bridge.mjs +2 -2
- package/bin/wechat-check-update.mjs +2 -2
- package/bin/wechat-claude-start.mjs +2 -2
- package/bin/wechat-claude.mjs +2 -2
- package/bin/wechat-codex-start.mjs +2 -2
- package/bin/wechat-codex.mjs +2 -2
- package/bin/wechat-opencode-start.mjs +2 -2
- package/bin/wechat-opencode.mjs +2 -2
- package/bin/wechat-setup.mjs +5 -0
- package/dist/bridge/bridge-adapters.claude.js +829 -0
- package/dist/bridge/bridge-adapters.codex.js +2206 -0
- package/dist/bridge/bridge-adapters.core.js +668 -0
- package/dist/bridge/bridge-adapters.js +26 -0
- package/dist/bridge/bridge-adapters.opencode.js +1777 -0
- package/dist/bridge/bridge-adapters.shared.js +1098 -0
- package/dist/bridge/bridge-adapters.shell.js +363 -0
- package/dist/bridge/bridge-controller.js +44 -0
- package/dist/bridge/bridge-final-reply.js +31 -0
- package/dist/bridge/bridge-process-reaper.js +348 -0
- package/dist/bridge/bridge-state.js +356 -0
- package/dist/bridge/bridge-types.js +1 -0
- package/dist/bridge/bridge-utils.js +1150 -0
- package/dist/bridge/claude-hook.js +82 -0
- package/dist/bridge/claude-hooks.js +267 -0
- package/dist/bridge/wechat-bridge.js +904 -0
- package/dist/commands/check-update.js +30 -0
- package/dist/companion/codex-panel-link.js +72 -0
- package/dist/companion/codex-panel.js +179 -0
- package/dist/companion/codex-remote-client.js +117 -0
- package/dist/companion/local-companion-link.js +210 -0
- package/dist/companion/local-companion-start.js +372 -0
- package/dist/companion/local-companion.js +411 -0
- package/dist/media/media-types.js +1 -0
- package/dist/runtime/create-runtime-host.js +12 -0
- package/dist/runtime/legacy-adapter-runtime.js +40 -0
- package/dist/runtime/runtime-types.js +5 -0
- package/dist/utils/version-checker.js +161 -0
- package/dist/wechat/channel-config.js +103 -0
- package/dist/wechat/setup.js +283 -0
- package/dist/wechat/standalone-bot.js +356 -0
- package/dist/wechat/wechat-channel.js +481 -0
- package/dist/wechat/wechat-transport.js +887 -0
- package/package.json +6 -2
- package/src/bridge/bridge-adapters.claude.ts +0 -1047
- package/src/bridge/bridge-adapters.codex.ts +0 -2733
- package/src/bridge/bridge-adapters.core.ts +0 -877
- package/src/bridge/bridge-adapters.opencode.ts +0 -2390
- package/src/bridge/bridge-adapters.shared.ts +0 -1647
- package/src/bridge/bridge-adapters.shell.ts +0 -456
- package/src/bridge/bridge-adapters.ts +0 -30
- package/src/bridge/bridge-controller.ts +0 -58
- package/src/bridge/bridge-final-reply.ts +0 -56
- package/src/bridge/bridge-process-reaper.ts +0 -417
- package/src/bridge/bridge-state.ts +0 -520
- package/src/bridge/bridge-types.ts +0 -218
- package/src/bridge/bridge-utils.ts +0 -1524
- package/src/bridge/claude-hook.ts +0 -92
- package/src/bridge/claude-hooks.ts +0 -376
- package/src/bridge/wechat-bridge.ts +0 -1305
- package/src/commands/check-update.ts +0 -46
- package/src/companion/codex-panel-link.ts +0 -151
- package/src/companion/codex-panel.ts +0 -225
- package/src/companion/codex-remote-client.ts +0 -178
- package/src/companion/local-companion-link.ts +0 -338
- package/src/companion/local-companion-start.ts +0 -520
- package/src/companion/local-companion.ts +0 -532
- package/src/media/media-types.ts +0 -11
- package/src/runtime/create-runtime-host.ts +0 -16
- package/src/runtime/legacy-adapter-runtime.ts +0 -55
- package/src/runtime/runtime-types.ts +0 -51
- package/src/utils/version-checker.ts +0 -197
- package/src/wechat/channel-config.ts +0 -141
- package/src/wechat/setup.ts +0 -232
- package/src/wechat/standalone-bot.ts +0 -483
- package/src/wechat/wechat-channel.ts +0 -608
- package/src/wechat/wechat-transport.ts +0 -1290
|
@@ -1,532 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
|
|
3
|
-
import net from "node:net";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
|
|
6
|
-
import { createBridgeAdapter } from "../bridge/bridge-adapters.ts";
|
|
7
|
-
import {
|
|
8
|
-
LOCAL_COMPANION_RECONNECT_GRACE_MS,
|
|
9
|
-
} from "../bridge/bridge-adapters.shared.ts";
|
|
10
|
-
import { runCodexRemoteClientFromEndpoint } from "./codex-remote-client.ts";
|
|
11
|
-
import {
|
|
12
|
-
attachLocalCompanionMessageListener,
|
|
13
|
-
readLocalCompanionEndpoint,
|
|
14
|
-
sendLocalCompanionMessage,
|
|
15
|
-
type LocalCompanionCloseReason,
|
|
16
|
-
type LocalCompanionEndpoint,
|
|
17
|
-
type LocalCompanionMessage,
|
|
18
|
-
} from "./local-companion-link.ts";
|
|
19
|
-
import { migrateLegacyChannelFiles } from "../wechat/channel-config.ts";
|
|
20
|
-
|
|
21
|
-
export const LOCAL_COMPANION_RECONNECT_RETRY_MS = 250;
|
|
22
|
-
|
|
23
|
-
function log(adapter: string, message: string): void {
|
|
24
|
-
process.stderr.write(`[${adapter}-companion] ${message}\n`);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export type LocalCompanionCliOptions = {
|
|
28
|
-
adapter: "codex" | "claude" | "opencode";
|
|
29
|
-
cwd: string;
|
|
30
|
-
cliArgs: string[];
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
function parseCliArgs(argv: string[]): LocalCompanionCliOptions {
|
|
34
|
-
let adapter: "codex" | "claude" | "opencode" | null = null;
|
|
35
|
-
let cwd = process.cwd();
|
|
36
|
-
const cliArgs: string[] = [];
|
|
37
|
-
|
|
38
|
-
for (let i = 0; i < argv.length; i += 1) {
|
|
39
|
-
const arg = argv[i];
|
|
40
|
-
const next = argv[i + 1];
|
|
41
|
-
|
|
42
|
-
if (arg === "--help" || arg === "-h") {
|
|
43
|
-
process.stdout.write(
|
|
44
|
-
[
|
|
45
|
-
"Usage: local-companion --adapter <codex|claude|opencode> [--cwd <path>] [...cli args]",
|
|
46
|
-
"",
|
|
47
|
-
'Starts the visible local companion and connects it to the matching running bridge for the current directory.',
|
|
48
|
-
"Unknown arguments are forwarded to the visible CLI client.",
|
|
49
|
-
"",
|
|
50
|
-
].join("\n"),
|
|
51
|
-
);
|
|
52
|
-
process.exit(0);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (arg === "--adapter") {
|
|
56
|
-
if (!next || !["codex", "claude", "opencode"].includes(next)) {
|
|
57
|
-
throw new Error(`Invalid adapter: ${next ?? "(missing)"}`);
|
|
58
|
-
}
|
|
59
|
-
adapter = next as "codex" | "claude" | "opencode";
|
|
60
|
-
i += 1;
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (arg === "--cwd") {
|
|
65
|
-
if (!next) {
|
|
66
|
-
throw new Error("--cwd requires a value");
|
|
67
|
-
}
|
|
68
|
-
cwd = path.resolve(next);
|
|
69
|
-
i += 1;
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
cliArgs.push(arg);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (!adapter) {
|
|
77
|
-
throw new Error("Missing required --adapter <codex|claude|opencode>");
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return { adapter, cwd, cliArgs };
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function delay(ms: number): Promise<void> {
|
|
84
|
-
if (ms <= 0) {
|
|
85
|
-
return Promise.resolve();
|
|
86
|
-
}
|
|
87
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function readMatchingEndpoint(
|
|
91
|
-
options: LocalCompanionCliOptions,
|
|
92
|
-
): LocalCompanionEndpoint {
|
|
93
|
-
const endpoint = readLocalCompanionEndpoint(options.cwd);
|
|
94
|
-
if (!endpoint || endpoint.kind !== options.adapter) {
|
|
95
|
-
throw new Error(
|
|
96
|
-
`No active ${options.adapter} bridge endpoint was found for ${options.cwd}. Start "wechat-bridge-${options.adapter}" in that directory first.`,
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return endpoint;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export function shouldReconnectLocalCompanion(params: {
|
|
104
|
-
shuttingDown: boolean;
|
|
105
|
-
closeReason: LocalCompanionCloseReason | null | undefined;
|
|
106
|
-
}): boolean {
|
|
107
|
-
return !params.shuttingDown && !params.closeReason;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export async function runLocalCompanion(options: LocalCompanionCliOptions): Promise<number> {
|
|
111
|
-
const initialEndpoint = readMatchingEndpoint(options);
|
|
112
|
-
|
|
113
|
-
if (
|
|
114
|
-
initialEndpoint.kind === "codex" &&
|
|
115
|
-
initialEndpoint.runtimeKind === "codex_runtime_host"
|
|
116
|
-
) {
|
|
117
|
-
return await runCodexRemoteClientFromEndpoint(initialEndpoint, {
|
|
118
|
-
extraCliArgs: options.cliArgs,
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const adapter = createBridgeAdapter({
|
|
123
|
-
kind: initialEndpoint.kind,
|
|
124
|
-
command: initialEndpoint.command,
|
|
125
|
-
cwd: initialEndpoint.cwd,
|
|
126
|
-
profile: initialEndpoint.profile,
|
|
127
|
-
initialSharedSessionId:
|
|
128
|
-
initialEndpoint.sharedSessionId ?? initialEndpoint.sharedThreadId,
|
|
129
|
-
initialResumeConversationId: initialEndpoint.resumeConversationId,
|
|
130
|
-
initialTranscriptPath: initialEndpoint.transcriptPath,
|
|
131
|
-
renderMode: initialEndpoint.kind === "codex" ? "panel" : "companion",
|
|
132
|
-
extraCliArgs: options.cliArgs,
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
let shuttingDown = false;
|
|
136
|
-
let closeReason: LocalCompanionCloseReason | null = null;
|
|
137
|
-
let activeSocket: net.Socket | null = null;
|
|
138
|
-
let detachListener: (() => void) | null = null;
|
|
139
|
-
let reconnectPromise: Promise<boolean> | null = null;
|
|
140
|
-
let resolveExitCode: ((code: number) => void) | null = null;
|
|
141
|
-
const exitCodePromise = new Promise<number>((resolve) => {
|
|
142
|
-
resolveExitCode = resolve;
|
|
143
|
-
});
|
|
144
|
-
let signalHandlersRegistered = false;
|
|
145
|
-
|
|
146
|
-
const detachActiveSocket = (destroy = false) => {
|
|
147
|
-
const socket = activeSocket;
|
|
148
|
-
activeSocket = null;
|
|
149
|
-
detachListener?.();
|
|
150
|
-
detachListener = null;
|
|
151
|
-
if (!socket) {
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
socket.removeAllListeners("close");
|
|
156
|
-
socket.removeAllListeners("error");
|
|
157
|
-
if (destroy) {
|
|
158
|
-
try {
|
|
159
|
-
socket.destroy();
|
|
160
|
-
} catch {
|
|
161
|
-
// Best effort cleanup.
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
const resolveExit = (code: number) => {
|
|
167
|
-
const resolve = resolveExitCode;
|
|
168
|
-
if (!resolve) {
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
resolveExitCode = null;
|
|
172
|
-
resolve(code);
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
const unregisterSignalHandlers = () => {
|
|
176
|
-
if (!signalHandlersRegistered) {
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
signalHandlersRegistered = false;
|
|
180
|
-
process.removeListener("SIGINT", handleSigint);
|
|
181
|
-
process.removeListener("SIGTERM", handleSigterm);
|
|
182
|
-
process.removeListener("SIGHUP", handleSighup);
|
|
183
|
-
if (process.platform === "win32") {
|
|
184
|
-
process.removeListener("SIGBREAK", handleSigbreak);
|
|
185
|
-
}
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
const publishState = () => {
|
|
189
|
-
if (!activeSocket) {
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
sendLocalCompanionMessage(activeSocket, {
|
|
194
|
-
type: "state",
|
|
195
|
-
state: adapter.getState(),
|
|
196
|
-
});
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
const sendResponse = (
|
|
200
|
-
socket: net.Socket,
|
|
201
|
-
id: string,
|
|
202
|
-
ok: boolean,
|
|
203
|
-
result?: unknown,
|
|
204
|
-
error?: string,
|
|
205
|
-
) => {
|
|
206
|
-
sendLocalCompanionMessage(socket, {
|
|
207
|
-
type: "response",
|
|
208
|
-
id,
|
|
209
|
-
ok,
|
|
210
|
-
result,
|
|
211
|
-
error,
|
|
212
|
-
});
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
const announceClosing = (
|
|
216
|
-
reason: LocalCompanionCloseReason,
|
|
217
|
-
exitCode?: number,
|
|
218
|
-
) => {
|
|
219
|
-
closeReason = reason;
|
|
220
|
-
if (!activeSocket) {
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
sendLocalCompanionMessage(activeSocket, {
|
|
224
|
-
type: "closing",
|
|
225
|
-
reason,
|
|
226
|
-
exitCode,
|
|
227
|
-
});
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
const closeCompanion = async (
|
|
231
|
-
exitCode = 0,
|
|
232
|
-
reason: LocalCompanionCloseReason = "companion_shutdown",
|
|
233
|
-
) => {
|
|
234
|
-
if (shuttingDown) {
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
shuttingDown = true;
|
|
238
|
-
|
|
239
|
-
announceClosing(reason, exitCode);
|
|
240
|
-
detachActiveSocket(false);
|
|
241
|
-
try {
|
|
242
|
-
await adapter.dispose();
|
|
243
|
-
} catch {
|
|
244
|
-
// Best effort cleanup.
|
|
245
|
-
}
|
|
246
|
-
resolveExit(exitCode);
|
|
247
|
-
};
|
|
248
|
-
|
|
249
|
-
const handleBridgeRequest = async (
|
|
250
|
-
socket: net.Socket,
|
|
251
|
-
message: Extract<LocalCompanionMessage, { type: "request" }>,
|
|
252
|
-
) => {
|
|
253
|
-
try {
|
|
254
|
-
switch (message.payload.command) {
|
|
255
|
-
case "send_input":
|
|
256
|
-
await adapter.sendInput(message.payload.text);
|
|
257
|
-
sendResponse(socket, message.id, true);
|
|
258
|
-
break;
|
|
259
|
-
case "list_resume_sessions":
|
|
260
|
-
case "list_resume_threads":
|
|
261
|
-
sendResponse(
|
|
262
|
-
socket,
|
|
263
|
-
message.id,
|
|
264
|
-
true,
|
|
265
|
-
await adapter.listResumeSessions(message.payload.limit),
|
|
266
|
-
);
|
|
267
|
-
break;
|
|
268
|
-
case "resume_session":
|
|
269
|
-
await adapter.resumeSession(message.payload.sessionId);
|
|
270
|
-
publishState();
|
|
271
|
-
sendResponse(socket, message.id, true);
|
|
272
|
-
break;
|
|
273
|
-
case "resume_thread":
|
|
274
|
-
await adapter.resumeSession(message.payload.threadId);
|
|
275
|
-
publishState();
|
|
276
|
-
sendResponse(socket, message.id, true);
|
|
277
|
-
break;
|
|
278
|
-
case "interrupt":
|
|
279
|
-
sendResponse(socket, message.id, true, await adapter.interrupt());
|
|
280
|
-
break;
|
|
281
|
-
case "reset":
|
|
282
|
-
await adapter.reset();
|
|
283
|
-
publishState();
|
|
284
|
-
sendResponse(socket, message.id, true);
|
|
285
|
-
break;
|
|
286
|
-
case "resolve_approval":
|
|
287
|
-
sendResponse(
|
|
288
|
-
socket,
|
|
289
|
-
message.id,
|
|
290
|
-
true,
|
|
291
|
-
await adapter.resolveApproval(message.payload.action),
|
|
292
|
-
);
|
|
293
|
-
break;
|
|
294
|
-
case "dispose":
|
|
295
|
-
sendResponse(socket, message.id, true);
|
|
296
|
-
await closeCompanion(0, "bridge_dispose");
|
|
297
|
-
break;
|
|
298
|
-
}
|
|
299
|
-
} catch (error) {
|
|
300
|
-
const text = error instanceof Error ? error.message : String(error);
|
|
301
|
-
sendResponse(socket, message.id, false, undefined, text);
|
|
302
|
-
}
|
|
303
|
-
};
|
|
304
|
-
|
|
305
|
-
const connectToBridge = async (
|
|
306
|
-
endpoint: LocalCompanionEndpoint,
|
|
307
|
-
): Promise<void> => {
|
|
308
|
-
await new Promise<void>((resolve, reject) => {
|
|
309
|
-
const socket = net.connect({
|
|
310
|
-
host: "127.0.0.1",
|
|
311
|
-
port: endpoint.port,
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
let settled = false;
|
|
315
|
-
let helloAcknowledged = false;
|
|
316
|
-
let localDetach: (() => void) | null = null;
|
|
317
|
-
|
|
318
|
-
const fail = (error: Error) => {
|
|
319
|
-
if (settled) {
|
|
320
|
-
return;
|
|
321
|
-
}
|
|
322
|
-
settled = true;
|
|
323
|
-
localDetach?.();
|
|
324
|
-
localDetach = null;
|
|
325
|
-
try {
|
|
326
|
-
socket.destroy();
|
|
327
|
-
} catch {
|
|
328
|
-
// Best effort cleanup.
|
|
329
|
-
}
|
|
330
|
-
reject(error);
|
|
331
|
-
};
|
|
332
|
-
|
|
333
|
-
socket.once("connect", () => {
|
|
334
|
-
socket.setNoDelay(true);
|
|
335
|
-
localDetach = attachLocalCompanionMessageListener(
|
|
336
|
-
socket,
|
|
337
|
-
(message: LocalCompanionMessage) => {
|
|
338
|
-
if (!helloAcknowledged) {
|
|
339
|
-
if (message.type === "hello_ack") {
|
|
340
|
-
helloAcknowledged = true;
|
|
341
|
-
closeReason = null;
|
|
342
|
-
activeSocket = socket;
|
|
343
|
-
detachListener = localDetach;
|
|
344
|
-
if (!settled) {
|
|
345
|
-
settled = true;
|
|
346
|
-
resolve();
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
if (message.type !== "request") {
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
void handleBridgeRequest(socket, message);
|
|
357
|
-
},
|
|
358
|
-
);
|
|
359
|
-
|
|
360
|
-
socket.once("close", () => {
|
|
361
|
-
if (!settled) {
|
|
362
|
-
fail(
|
|
363
|
-
new Error(
|
|
364
|
-
`The ${options.adapter} bridge closed the local companion socket before authentication.`,
|
|
365
|
-
),
|
|
366
|
-
);
|
|
367
|
-
return;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
if (activeSocket === socket) {
|
|
371
|
-
detachActiveSocket(false);
|
|
372
|
-
void (async () => {
|
|
373
|
-
if (
|
|
374
|
-
!shouldReconnectLocalCompanion({
|
|
375
|
-
shuttingDown,
|
|
376
|
-
closeReason,
|
|
377
|
-
})
|
|
378
|
-
) {
|
|
379
|
-
return;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
const reconnected = await reconnectToBridge();
|
|
383
|
-
if (!reconnected && !shuttingDown) {
|
|
384
|
-
await closeCompanion(1, "fatal_error");
|
|
385
|
-
}
|
|
386
|
-
})();
|
|
387
|
-
}
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
socket.once("error", (error) => {
|
|
391
|
-
if (!settled) {
|
|
392
|
-
fail(
|
|
393
|
-
error instanceof Error
|
|
394
|
-
? error
|
|
395
|
-
: new Error(String(error)),
|
|
396
|
-
);
|
|
397
|
-
}
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
sendLocalCompanionMessage(socket, {
|
|
401
|
-
type: "hello",
|
|
402
|
-
token: endpoint.token,
|
|
403
|
-
companionPid: process.pid,
|
|
404
|
-
});
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
socket.once("error", (error) => {
|
|
408
|
-
if (!settled) {
|
|
409
|
-
fail(error instanceof Error ? error : new Error(String(error)));
|
|
410
|
-
}
|
|
411
|
-
});
|
|
412
|
-
});
|
|
413
|
-
};
|
|
414
|
-
|
|
415
|
-
const reconnectToBridge = async (): Promise<boolean> => {
|
|
416
|
-
if (reconnectPromise) {
|
|
417
|
-
return await reconnectPromise;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
reconnectPromise = (async () => {
|
|
421
|
-
const deadline = Date.now() + LOCAL_COMPANION_RECONNECT_GRACE_MS;
|
|
422
|
-
let lastError = "";
|
|
423
|
-
log(
|
|
424
|
-
options.adapter,
|
|
425
|
-
`Bridge connection dropped unexpectedly. Waiting up to ${Math.ceil(LOCAL_COMPANION_RECONNECT_GRACE_MS / 1000)}s to reconnect...`,
|
|
426
|
-
);
|
|
427
|
-
|
|
428
|
-
while (!shuttingDown && Date.now() < deadline) {
|
|
429
|
-
try {
|
|
430
|
-
const nextEndpoint = readMatchingEndpoint(options);
|
|
431
|
-
await connectToBridge(nextEndpoint);
|
|
432
|
-
publishState();
|
|
433
|
-
log(options.adapter, `Reconnected to bridge ${nextEndpoint.instanceId}.`);
|
|
434
|
-
return true;
|
|
435
|
-
} catch (error) {
|
|
436
|
-
lastError = error instanceof Error ? error.message : String(error);
|
|
437
|
-
await delay(LOCAL_COMPANION_RECONNECT_RETRY_MS);
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
if (lastError) {
|
|
442
|
-
log(
|
|
443
|
-
options.adapter,
|
|
444
|
-
`Bridge reconnection timed out after ${Math.ceil(LOCAL_COMPANION_RECONNECT_GRACE_MS / 1000)}s: ${lastError}`,
|
|
445
|
-
);
|
|
446
|
-
} else {
|
|
447
|
-
log(
|
|
448
|
-
options.adapter,
|
|
449
|
-
`Bridge reconnection timed out after ${Math.ceil(LOCAL_COMPANION_RECONNECT_GRACE_MS / 1000)}s.`,
|
|
450
|
-
);
|
|
451
|
-
}
|
|
452
|
-
return false;
|
|
453
|
-
})();
|
|
454
|
-
|
|
455
|
-
try {
|
|
456
|
-
return await reconnectPromise;
|
|
457
|
-
} finally {
|
|
458
|
-
reconnectPromise = null;
|
|
459
|
-
}
|
|
460
|
-
};
|
|
461
|
-
|
|
462
|
-
adapter.setEventSink((event) => {
|
|
463
|
-
if (activeSocket) {
|
|
464
|
-
sendLocalCompanionMessage(activeSocket, {
|
|
465
|
-
type: "event",
|
|
466
|
-
event,
|
|
467
|
-
});
|
|
468
|
-
}
|
|
469
|
-
publishState();
|
|
470
|
-
|
|
471
|
-
if (event.type === "fatal_error") {
|
|
472
|
-
void closeCompanion(1, "fatal_error");
|
|
473
|
-
return;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
if (event.type === "status" && event.status === "stopped") {
|
|
477
|
-
void closeCompanion(0, "worker_exit");
|
|
478
|
-
}
|
|
479
|
-
});
|
|
480
|
-
|
|
481
|
-
const requestSignalShutdown = (signal: string) => {
|
|
482
|
-
log(options.adapter, `Received ${signal}. Closing local companion.`);
|
|
483
|
-
void closeCompanion(0, "signal");
|
|
484
|
-
};
|
|
485
|
-
|
|
486
|
-
const handleSigint = () => requestSignalShutdown("SIGINT");
|
|
487
|
-
const handleSigterm = () => requestSignalShutdown("SIGTERM");
|
|
488
|
-
const handleSighup = () => requestSignalShutdown("SIGHUP");
|
|
489
|
-
const handleSigbreak = () => requestSignalShutdown("SIGBREAK");
|
|
490
|
-
|
|
491
|
-
process.once("SIGINT", handleSigint);
|
|
492
|
-
process.once("SIGTERM", handleSigterm);
|
|
493
|
-
process.once("SIGHUP", handleSighup);
|
|
494
|
-
if (process.platform === "win32") {
|
|
495
|
-
process.once("SIGBREAK", handleSigbreak);
|
|
496
|
-
}
|
|
497
|
-
signalHandlersRegistered = true;
|
|
498
|
-
|
|
499
|
-
try {
|
|
500
|
-
await connectToBridge(initialEndpoint);
|
|
501
|
-
await adapter.start();
|
|
502
|
-
publishState();
|
|
503
|
-
log(options.adapter, `Connected to bridge ${initialEndpoint.instanceId}.`);
|
|
504
|
-
return await exitCodePromise;
|
|
505
|
-
} finally {
|
|
506
|
-
unregisterSignalHandlers();
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
export async function main(argv: string[] = process.argv.slice(2)): Promise<void> {
|
|
511
|
-
migrateLegacyChannelFiles((message) => log("local", message));
|
|
512
|
-
try {
|
|
513
|
-
const options = parseCliArgs(argv);
|
|
514
|
-
const exitCode = await runLocalCompanion(options);
|
|
515
|
-
process.exit(exitCode);
|
|
516
|
-
} catch (error) {
|
|
517
|
-
const adapter = (() => {
|
|
518
|
-
try {
|
|
519
|
-
return parseCliArgs(argv).adapter;
|
|
520
|
-
} catch {
|
|
521
|
-
return "local";
|
|
522
|
-
}
|
|
523
|
-
})();
|
|
524
|
-
log(adapter, error instanceof Error ? error.message : String(error));
|
|
525
|
-
process.exit(1);
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
const isDirectRun = Boolean((import.meta as ImportMeta & { main?: boolean }).main);
|
|
530
|
-
if (isDirectRun) {
|
|
531
|
-
void main();
|
|
532
|
-
}
|
package/src/media/media-types.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export interface MediaTransport<TInboundMessage> {
|
|
2
|
-
loadCredentials(): Promise<{ userId?: string } | null>;
|
|
3
|
-
pollMessages(options: {
|
|
4
|
-
afterMessageId?: string;
|
|
5
|
-
timeoutMs?: number;
|
|
6
|
-
minCreatedAtMs?: number;
|
|
7
|
-
}): Promise<{
|
|
8
|
-
messages: TInboundMessage[];
|
|
9
|
-
}>;
|
|
10
|
-
sendText(recipientId: string, text: string): Promise<unknown>;
|
|
11
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { createBridgeAdapter } from "../bridge/bridge-adapters.ts";
|
|
2
|
-
import { CodexPtyAdapter } from "../bridge/bridge-adapters.codex.ts";
|
|
3
|
-
import type { AdapterOptions } from "../bridge/bridge-adapters.shared.ts";
|
|
4
|
-
import { LegacyAdapterRuntime } from "./legacy-adapter-runtime.ts";
|
|
5
|
-
import type { RuntimeHost } from "./runtime-types.ts";
|
|
6
|
-
|
|
7
|
-
export function createRuntimeHost(options: AdapterOptions): RuntimeHost {
|
|
8
|
-
if (options.kind === "codex") {
|
|
9
|
-
return new CodexPtyAdapter({
|
|
10
|
-
...options,
|
|
11
|
-
renderMode: options.renderMode ?? "headless",
|
|
12
|
-
});
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
return new LegacyAdapterRuntime(createBridgeAdapter(options));
|
|
16
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import type { BridgeAdapter, BridgeResumeSessionCandidate } from "../bridge/bridge-types.ts";
|
|
2
|
-
import type { RuntimeHost } from "./runtime-types.ts";
|
|
3
|
-
|
|
4
|
-
export class LegacyAdapterRuntime implements RuntimeHost {
|
|
5
|
-
readonly runtimeKind = "legacy_adapter" as const;
|
|
6
|
-
private readonly adapter: BridgeAdapter;
|
|
7
|
-
|
|
8
|
-
constructor(adapter: BridgeAdapter) {
|
|
9
|
-
this.adapter = adapter;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
setEventSink(sink: Parameters<BridgeAdapter["setEventSink"]>[0]): void {
|
|
13
|
-
this.adapter.setEventSink(sink);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
async start(): Promise<void> {
|
|
17
|
-
await this.adapter.start();
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async sendInput(text: string): Promise<void> {
|
|
21
|
-
await this.adapter.sendInput(text);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
async listResumeSessions(limit?: number): Promise<BridgeResumeSessionCandidate[]> {
|
|
25
|
-
return await this.adapter.listResumeSessions(limit);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async resumeSession(sessionId: string): Promise<void> {
|
|
29
|
-
await this.adapter.resumeSession(sessionId);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async interrupt(): Promise<boolean> {
|
|
33
|
-
return await this.adapter.interrupt();
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async reset(): Promise<void> {
|
|
37
|
-
await this.adapter.reset();
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async resolveApproval(action: "confirm" | "deny"): Promise<boolean> {
|
|
41
|
-
return await this.adapter.resolveApproval(action);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async submitUserInput(answers: Record<string, string[]>): Promise<boolean> {
|
|
45
|
-
return await this.adapter.submitUserInput(answers);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async dispose(): Promise<void> {
|
|
49
|
-
await this.adapter.dispose();
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
getState() {
|
|
53
|
-
return this.adapter.getState();
|
|
54
|
-
}
|
|
55
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
BridgeAdapter,
|
|
3
|
-
BridgeWorkerStatus,
|
|
4
|
-
} from "../bridge/bridge-types.ts";
|
|
5
|
-
|
|
6
|
-
export const LOCAL_CLIENT_PROTOCOL_VERSION = 2;
|
|
7
|
-
export const CODEX_REMOTE_AUTH_TOKEN_ENV = "WECHAT_BRIDGE_CODEX_REMOTE_AUTH_TOKEN";
|
|
8
|
-
|
|
9
|
-
export type RuntimeKind = "legacy_adapter" | "codex_runtime_host";
|
|
10
|
-
export type RuntimeRenderMode = "embedded" | "panel" | "companion" | "headless";
|
|
11
|
-
|
|
12
|
-
export type LocalClientEndpoint = {
|
|
13
|
-
protocolVersion: number;
|
|
14
|
-
runtimeKind: RuntimeKind;
|
|
15
|
-
instanceId: string;
|
|
16
|
-
kind: "codex" | "claude" | "opencode" | "shell";
|
|
17
|
-
port: number;
|
|
18
|
-
token: string;
|
|
19
|
-
renderMode?: RuntimeRenderMode;
|
|
20
|
-
bridgeOwnerPid?: number;
|
|
21
|
-
serverPort?: number;
|
|
22
|
-
serverUrl?: string;
|
|
23
|
-
remoteAuthTokenEnv?: string;
|
|
24
|
-
cwd: string;
|
|
25
|
-
command: string;
|
|
26
|
-
profile?: string;
|
|
27
|
-
sharedSessionId?: string;
|
|
28
|
-
sharedThreadId?: string;
|
|
29
|
-
resumeConversationId?: string;
|
|
30
|
-
transcriptPath?: string;
|
|
31
|
-
companionPid?: number;
|
|
32
|
-
companionConnectedAt?: string;
|
|
33
|
-
companionStatus?: BridgeWorkerStatus;
|
|
34
|
-
companionLastStateAt?: string;
|
|
35
|
-
companionWorkerPid?: number;
|
|
36
|
-
startedAt: string;
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
export interface LocalClientEndpointProvider {
|
|
40
|
-
getLocalClientEndpoint(): LocalClientEndpoint | null;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export interface RuntimeHost extends BridgeAdapter {
|
|
44
|
-
readonly runtimeKind: RuntimeKind;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function hasLocalClientEndpointProvider(
|
|
48
|
-
runtime: BridgeAdapter,
|
|
49
|
-
): runtime is BridgeAdapter & LocalClientEndpointProvider {
|
|
50
|
-
return typeof (runtime as Partial<LocalClientEndpointProvider>).getLocalClientEndpoint === "function";
|
|
51
|
-
}
|