claude-codex-wechat 0.1.12 → 0.1.15
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 +29 -0
- package/config.example.json +8 -0
- package/dist/server/cli.js +511 -88
- package/dist/web/assets/index-DBTko96w.js +10 -0
- package/dist/web/index.html +1 -1
- package/package.json +5 -1
- package/dist/web/assets/index-BW-_vdVB.js +0 -10
package/dist/server/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { createRequire as __cjsCreateRequire } from 'node:module'; const require = __cjsCreateRequire(import.meta.url);
|
|
3
3
|
|
|
4
4
|
// src/cli.ts
|
|
5
|
-
import { existsSync as
|
|
5
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "node:fs";
|
|
6
6
|
import { dirname as dirname12, join as join12 } from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
|
|
@@ -80,6 +80,7 @@ function normalizeBridgeConfig(raw, env, path) {
|
|
|
80
80
|
return {
|
|
81
81
|
wechat: normalizeWechatConfig(record.wechat, env),
|
|
82
82
|
bridge: normalizeBridgeDefaultsConfig(record.bridge),
|
|
83
|
+
tunnel: normalizeTunnelConfig(record.tunnel),
|
|
83
84
|
providers: normalizeProvidersConfig(record.providers, env)
|
|
84
85
|
};
|
|
85
86
|
}
|
|
@@ -103,6 +104,20 @@ function normalizeBridgeDefaultsConfig(raw) {
|
|
|
103
104
|
...defaultWorkspace ? { defaultWorkspace } : {}
|
|
104
105
|
};
|
|
105
106
|
}
|
|
107
|
+
function normalizeTunnelConfig(raw) {
|
|
108
|
+
const record = raw && typeof raw === "object" ? raw : {};
|
|
109
|
+
const enabled = record.enabled === true;
|
|
110
|
+
const relayRecord = record.relay && typeof record.relay === "object" ? record.relay : void 0;
|
|
111
|
+
const relay = relayRecord ? {
|
|
112
|
+
...typeof relayRecord.serverUrl === "string" && relayRecord.serverUrl.trim() ? { serverUrl: relayRecord.serverUrl } : {},
|
|
113
|
+
...typeof relayRecord.authToken === "string" && relayRecord.authToken.trim() ? { authToken: relayRecord.authToken } : {}
|
|
114
|
+
} : void 0;
|
|
115
|
+
if (!enabled && !relay) return void 0;
|
|
116
|
+
return {
|
|
117
|
+
enabled,
|
|
118
|
+
...relay ? { relay } : {}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
106
121
|
function normalizeProviderCommand(raw, envFallback) {
|
|
107
122
|
const record = raw && typeof raw === "object" ? raw : {};
|
|
108
123
|
const command = typeof record.command === "string" && record.command ? record.command : typeof envFallback === "string" && envFallback ? envFallback : void 0;
|
|
@@ -125,6 +140,8 @@ function normalizeWechatConfig(raw, env) {
|
|
|
125
140
|
|
|
126
141
|
// src/daemon/configPersistence.ts
|
|
127
142
|
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
143
|
+
import { randomBytes } from "node:crypto";
|
|
144
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
|
|
128
145
|
import { dirname } from "node:path";
|
|
129
146
|
async function persistWechatCredentialsToConfigFile(input) {
|
|
130
147
|
const currentConfig = await readConfigFile(input.configPath);
|
|
@@ -152,18 +169,52 @@ async function deleteConfigFile(configPath) {
|
|
|
152
169
|
}
|
|
153
170
|
async function persistBridgeDefaultsToConfigFile(input) {
|
|
154
171
|
const currentConfig = await readConfigFile(input.configPath);
|
|
172
|
+
const currentBridge = isRecord(currentConfig.bridge) ? currentConfig.bridge : void 0;
|
|
155
173
|
const nextConfig = {
|
|
156
174
|
...currentConfig,
|
|
157
175
|
bridge: {
|
|
158
|
-
...
|
|
159
|
-
defaultProvider: input.defaultProvider,
|
|
160
|
-
defaultWorkspace: input.defaultWorkspace
|
|
161
|
-
}
|
|
176
|
+
...currentBridge ?? {},
|
|
177
|
+
...input.defaultProvider ? { defaultProvider: input.defaultProvider } : {},
|
|
178
|
+
...input.defaultWorkspace ? { defaultWorkspace: input.defaultWorkspace } : {}
|
|
179
|
+
},
|
|
180
|
+
...input.tunnel ? {
|
|
181
|
+
tunnel: {
|
|
182
|
+
enabled: input.tunnel.enabled === true,
|
|
183
|
+
...input.tunnel.relay ? {
|
|
184
|
+
relay: {
|
|
185
|
+
...input.tunnel.relay.serverUrl ? { serverUrl: input.tunnel.relay.serverUrl } : {},
|
|
186
|
+
...input.tunnel.relay.authToken ? { authToken: input.tunnel.relay.authToken } : {}
|
|
187
|
+
}
|
|
188
|
+
} : {}
|
|
189
|
+
}
|
|
190
|
+
} : {}
|
|
162
191
|
};
|
|
163
192
|
await mkdir(dirname(input.configPath), { recursive: true });
|
|
164
193
|
await writeFile(input.configPath, `${JSON.stringify(nextConfig, null, 2)}
|
|
165
194
|
`, "utf8");
|
|
166
195
|
}
|
|
196
|
+
function ensureRelayAuthTokenSync(input) {
|
|
197
|
+
const currentConfig = readConfigFileSync(input.configPath);
|
|
198
|
+
const currentTunnel = isRecord(currentConfig.tunnel) ? currentConfig.tunnel : void 0;
|
|
199
|
+
const currentRelay = isRecord(currentTunnel?.relay) ? currentTunnel.relay : void 0;
|
|
200
|
+
const existing = typeof currentRelay?.authToken === "string" && currentRelay.authToken.trim() ? currentRelay.authToken.trim() : "";
|
|
201
|
+
if (existing) return existing;
|
|
202
|
+
const authToken = `clrt_${randomBytes(12).toString("hex")}`;
|
|
203
|
+
const nextConfig = {
|
|
204
|
+
...currentConfig,
|
|
205
|
+
tunnel: {
|
|
206
|
+
...currentTunnel ?? {},
|
|
207
|
+
relay: {
|
|
208
|
+
...currentRelay ?? {},
|
|
209
|
+
authToken
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
mkdirSync(dirname(input.configPath), { recursive: true });
|
|
214
|
+
writeFileSync(input.configPath, `${JSON.stringify(nextConfig, null, 2)}
|
|
215
|
+
`, "utf8");
|
|
216
|
+
return authToken;
|
|
217
|
+
}
|
|
167
218
|
async function persistProviderCommandsToConfigFile(input) {
|
|
168
219
|
const currentConfig = await readConfigFile(input.configPath);
|
|
169
220
|
const normalizedProviders = normalizeProvidersForPersistence(input.providers);
|
|
@@ -185,6 +236,17 @@ async function readConfigFile(path) {
|
|
|
185
236
|
throw error;
|
|
186
237
|
}
|
|
187
238
|
}
|
|
239
|
+
function readConfigFileSync(path) {
|
|
240
|
+
try {
|
|
241
|
+
if (!existsSync2(path)) return {};
|
|
242
|
+
const raw = readFileSync2(path, "utf8");
|
|
243
|
+
const parsed = JSON.parse(raw);
|
|
244
|
+
return isRecord(parsed) ? parsed : {};
|
|
245
|
+
} catch (error) {
|
|
246
|
+
if (isMissingFileError(error)) return {};
|
|
247
|
+
throw error;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
188
250
|
function isRecord(value) {
|
|
189
251
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
190
252
|
}
|
|
@@ -202,7 +264,7 @@ function normalizeProvidersForPersistence(providers) {
|
|
|
202
264
|
}
|
|
203
265
|
|
|
204
266
|
// src/channels/weixin-direct/weixinStateStore.ts
|
|
205
|
-
import { existsSync as
|
|
267
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
206
268
|
import { dirname as dirname2 } from "node:path";
|
|
207
269
|
var PUSH_QUOTA_LIMIT = 10;
|
|
208
270
|
var PUSH_WINDOW_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -296,9 +358,9 @@ var FileWeixinStateStore = class {
|
|
|
296
358
|
return this.readState().bridge?.weixinChannel ?? {};
|
|
297
359
|
}
|
|
298
360
|
readState() {
|
|
299
|
-
if (!
|
|
361
|
+
if (!existsSync3(this.configPath)) return {};
|
|
300
362
|
try {
|
|
301
|
-
const raw = JSON.parse(
|
|
363
|
+
const raw = JSON.parse(readFileSync3(this.configPath, "utf8"));
|
|
302
364
|
return raw && typeof raw === "object" ? raw : {};
|
|
303
365
|
} catch {
|
|
304
366
|
return {};
|
|
@@ -309,8 +371,8 @@ var FileWeixinStateStore = class {
|
|
|
309
371
|
...state,
|
|
310
372
|
bridge: { ...state.bridge ?? {}, weixinChannel: channel }
|
|
311
373
|
};
|
|
312
|
-
|
|
313
|
-
|
|
374
|
+
mkdirSync2(dirname2(this.configPath), { recursive: true });
|
|
375
|
+
writeFileSync2(this.configPath, `${JSON.stringify(next, null, 2)}
|
|
314
376
|
`, "utf8");
|
|
315
377
|
}
|
|
316
378
|
};
|
|
@@ -608,7 +670,7 @@ async function normalizeClaudeSessionFileForResume(sessionPath) {
|
|
|
608
670
|
// src/providers/codex/nativeSessions.ts
|
|
609
671
|
import Database from "better-sqlite3";
|
|
610
672
|
import { readdir as readdir2, readFile as readFile3, stat as stat2 } from "node:fs/promises";
|
|
611
|
-
import { existsSync as
|
|
673
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
612
674
|
import { homedir as homedir3 } from "node:os";
|
|
613
675
|
import { basename as basename2, join as join3 } from "node:path";
|
|
614
676
|
function resolveCodexSessionsRoot(env = process.env) {
|
|
@@ -710,7 +772,7 @@ async function readCodexSessionIndex(env) {
|
|
|
710
772
|
function readCodexThreadMetadata(env) {
|
|
711
773
|
const dbPath = join3(env.CODEX_HOME || join3(env.HOME || homedir3(), ".codex"), "state_5.sqlite");
|
|
712
774
|
const metadata = /* @__PURE__ */ new Map();
|
|
713
|
-
if (!
|
|
775
|
+
if (!existsSync4(dbPath)) return metadata;
|
|
714
776
|
let db = null;
|
|
715
777
|
try {
|
|
716
778
|
db = new Database(dbPath, { readonly: true });
|
|
@@ -814,7 +876,7 @@ async function upsertCodexSessionIndexEntry(input, env = process.env) {
|
|
|
814
876
|
|
|
815
877
|
// src/providers/codex/nativeThreads.ts
|
|
816
878
|
import Database2 from "better-sqlite3";
|
|
817
|
-
import { existsSync as
|
|
879
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
818
880
|
import { readFile as readFile5, writeFile as writeFile4 } from "node:fs/promises";
|
|
819
881
|
import { homedir as homedir5 } from "node:os";
|
|
820
882
|
import { join as join5 } from "node:path";
|
|
@@ -824,7 +886,7 @@ function resolveCodexStateDbPath(env = process.env) {
|
|
|
824
886
|
async function syncCodexThreadForResume(input) {
|
|
825
887
|
const env = input.env ?? process.env;
|
|
826
888
|
const dbPath = resolveCodexStateDbPath(env);
|
|
827
|
-
if (!
|
|
889
|
+
if (!existsSync5(dbPath)) return false;
|
|
828
890
|
for (let attempt = 0; attempt < 50; attempt += 1) {
|
|
829
891
|
const db = new Database2(dbPath);
|
|
830
892
|
try {
|
|
@@ -863,7 +925,7 @@ async function syncCodexThreadForResume(input) {
|
|
|
863
925
|
return false;
|
|
864
926
|
}
|
|
865
927
|
async function syncCodexRolloutSessionMeta(rolloutPath) {
|
|
866
|
-
if (!
|
|
928
|
+
if (!existsSync5(rolloutPath)) return;
|
|
867
929
|
const content = await readFile5(rolloutPath, "utf8");
|
|
868
930
|
const lines = content.split(/\r?\n/);
|
|
869
931
|
if (!lines[0]?.trim()) return;
|
|
@@ -1405,6 +1467,40 @@ async function resolveRecoverableProviderResumeHistorySynced(session) {
|
|
|
1405
1467
|
});
|
|
1406
1468
|
}
|
|
1407
1469
|
|
|
1470
|
+
// src/admin/tunnelRoutes.ts
|
|
1471
|
+
function registerTunnelRoutes(input) {
|
|
1472
|
+
const getStatus = async () => await input.tunnelManager.getStatus();
|
|
1473
|
+
input.app.get("/api/tunnel/status", getStatus);
|
|
1474
|
+
const start = async () => {
|
|
1475
|
+
input.defaults.tunnel.enabled = true;
|
|
1476
|
+
await persistBridgeDefaultsToConfigFile({
|
|
1477
|
+
configPath: input.configPath,
|
|
1478
|
+
tunnel: input.defaults.tunnel
|
|
1479
|
+
});
|
|
1480
|
+
return await input.tunnelManager.start();
|
|
1481
|
+
};
|
|
1482
|
+
input.app.post("/api/tunnel/start", start);
|
|
1483
|
+
const stop = async () => {
|
|
1484
|
+
input.defaults.tunnel.enabled = false;
|
|
1485
|
+
await persistBridgeDefaultsToConfigFile({
|
|
1486
|
+
configPath: input.configPath,
|
|
1487
|
+
tunnel: input.defaults.tunnel
|
|
1488
|
+
});
|
|
1489
|
+
return await input.tunnelManager.stop();
|
|
1490
|
+
};
|
|
1491
|
+
input.app.post("/api/tunnel/stop", stop);
|
|
1492
|
+
const setEnabled = async (request) => {
|
|
1493
|
+
const enabled = request.body?.enabled === true;
|
|
1494
|
+
input.defaults.tunnel.enabled = enabled;
|
|
1495
|
+
await persistBridgeDefaultsToConfigFile({
|
|
1496
|
+
configPath: input.configPath,
|
|
1497
|
+
tunnel: input.defaults.tunnel
|
|
1498
|
+
});
|
|
1499
|
+
return await input.tunnelManager.setEnabled(enabled);
|
|
1500
|
+
};
|
|
1501
|
+
input.app.post("/api/tunnel/settings", setEnabled);
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1408
1504
|
// src/admin/settingsRoutes.ts
|
|
1409
1505
|
function registerSettingsRoutes(input) {
|
|
1410
1506
|
input.app.get("/api/settings", async () => input.defaults);
|
|
@@ -1412,22 +1508,53 @@ function registerSettingsRoutes(input) {
|
|
|
1412
1508
|
const current = { ...input.defaults };
|
|
1413
1509
|
const next = normalizeSettings({
|
|
1414
1510
|
...current,
|
|
1415
|
-
...request.body
|
|
1511
|
+
...request.body,
|
|
1512
|
+
tunnel: {
|
|
1513
|
+
...current.tunnel,
|
|
1514
|
+
...request.body.tunnel && typeof request.body.tunnel === "object" ? request.body.tunnel : {},
|
|
1515
|
+
relay: {
|
|
1516
|
+
...current.tunnel?.relay ?? {},
|
|
1517
|
+
...request.body.tunnel && typeof request.body.tunnel === "object" && "relay" in request.body.tunnel && request.body.tunnel.relay && typeof request.body.tunnel.relay === "object" ? request.body.tunnel.relay : {}
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1416
1520
|
}, current.defaultWorkspace);
|
|
1521
|
+
if (!next.tunnel.relay?.authToken) {
|
|
1522
|
+
const authToken = ensureRelayAuthTokenSync({ configPath: input.configPath });
|
|
1523
|
+
next.tunnel = {
|
|
1524
|
+
...next.tunnel,
|
|
1525
|
+
relay: {
|
|
1526
|
+
...next.tunnel.relay ?? {},
|
|
1527
|
+
authToken
|
|
1528
|
+
}
|
|
1529
|
+
};
|
|
1530
|
+
}
|
|
1417
1531
|
input.defaults.defaultProvider = next.defaultProvider;
|
|
1418
1532
|
input.defaults.defaultWorkspace = next.defaultWorkspace;
|
|
1533
|
+
input.defaults.tunnel = next.tunnel;
|
|
1419
1534
|
await persistBridgeDefaultsToConfigFile({
|
|
1420
1535
|
configPath: input.configPath,
|
|
1421
1536
|
defaultProvider: next.defaultProvider,
|
|
1422
|
-
defaultWorkspace: next.defaultWorkspace
|
|
1537
|
+
defaultWorkspace: next.defaultWorkspace,
|
|
1538
|
+
tunnel: next.tunnel
|
|
1423
1539
|
});
|
|
1424
1540
|
return { ok: true };
|
|
1425
1541
|
});
|
|
1426
1542
|
}
|
|
1427
1543
|
function normalizeSettings(input, defaultWorkspace) {
|
|
1544
|
+
const tunnel = input.tunnel && typeof input.tunnel === "object" ? input.tunnel : {};
|
|
1545
|
+
const relay = tunnel.relay && typeof tunnel.relay === "object" ? tunnel.relay : {};
|
|
1428
1546
|
return {
|
|
1429
1547
|
defaultProvider: input.defaultProvider === "codex" ? "codex" : "claude-code",
|
|
1430
|
-
defaultWorkspace: typeof input.defaultWorkspace === "string" && input.defaultWorkspace.trim() ? input.defaultWorkspace : defaultWorkspace
|
|
1548
|
+
defaultWorkspace: typeof input.defaultWorkspace === "string" && input.defaultWorkspace.trim() ? input.defaultWorkspace : defaultWorkspace,
|
|
1549
|
+
tunnel: {
|
|
1550
|
+
enabled: tunnel.enabled === true,
|
|
1551
|
+
...typeof relay.serverUrl === "string" && relay.serverUrl.trim() || typeof relay.authToken === "string" && relay.authToken.trim() ? {
|
|
1552
|
+
relay: {
|
|
1553
|
+
...typeof relay.serverUrl === "string" && relay.serverUrl.trim() ? { serverUrl: relay.serverUrl } : {},
|
|
1554
|
+
...typeof relay.authToken === "string" && relay.authToken.trim() ? { authToken: relay.authToken } : {}
|
|
1555
|
+
}
|
|
1556
|
+
} : {}
|
|
1557
|
+
}
|
|
1431
1558
|
};
|
|
1432
1559
|
}
|
|
1433
1560
|
|
|
@@ -1967,7 +2094,7 @@ function chunkText(text, limit) {
|
|
|
1967
2094
|
}
|
|
1968
2095
|
|
|
1969
2096
|
// src/channels/weixin-direct/mediaDownloader.ts
|
|
1970
|
-
import { mkdirSync as
|
|
2097
|
+
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1971
2098
|
import { dirname as dirname6 } from "node:path";
|
|
1972
2099
|
|
|
1973
2100
|
// src/channels/weixin-direct/mediaCrypto.ts
|
|
@@ -2031,8 +2158,8 @@ var WeixinMediaDownloader = class {
|
|
|
2031
2158
|
} catch {
|
|
2032
2159
|
return { ok: false, reason: "decrypt_failed" };
|
|
2033
2160
|
}
|
|
2034
|
-
|
|
2035
|
-
|
|
2161
|
+
mkdirSync3(dirname6(input.destPath), { recursive: true });
|
|
2162
|
+
writeFileSync3(input.destPath, plaintext);
|
|
2036
2163
|
return { ok: true, localPath: input.destPath, bytes: plaintext.length };
|
|
2037
2164
|
}
|
|
2038
2165
|
};
|
|
@@ -2738,7 +2865,8 @@ var CodexInteractiveRunner = class {
|
|
|
2738
2865
|
sessionName: input.options?.sessionName,
|
|
2739
2866
|
cwd: input.cwd,
|
|
2740
2867
|
status: "idle",
|
|
2741
|
-
|
|
2868
|
+
pendingEvents: [],
|
|
2869
|
+
turnClosed: false
|
|
2742
2870
|
};
|
|
2743
2871
|
this.sessions.set(input.bridgeSessionId, session);
|
|
2744
2872
|
return session;
|
|
@@ -2747,8 +2875,10 @@ var CodexInteractiveRunner = class {
|
|
|
2747
2875
|
const session = this.sessions.get(input.bridgeSessionId);
|
|
2748
2876
|
if (!session) throw new Error(`codex_session_not_found:${input.bridgeSessionId}`);
|
|
2749
2877
|
const client = await this.ensureClient(session);
|
|
2750
|
-
session.
|
|
2878
|
+
session.pendingEvents = [];
|
|
2879
|
+
session.activeMessage = void 0;
|
|
2751
2880
|
session.activeTurnId = void 0;
|
|
2881
|
+
session.turnClosed = false;
|
|
2752
2882
|
session.turnCompletedPromise = new Promise((resolve2) => {
|
|
2753
2883
|
session.turnCompletedResolver = resolve2;
|
|
2754
2884
|
});
|
|
@@ -2788,6 +2918,11 @@ var CodexInteractiveRunner = class {
|
|
|
2788
2918
|
});
|
|
2789
2919
|
const maybeTurnId = readTurnId(response);
|
|
2790
2920
|
if (maybeTurnId) session.activeTurnId = maybeTurnId;
|
|
2921
|
+
for (; ; ) {
|
|
2922
|
+
const event = await this.takeNextEvent(session);
|
|
2923
|
+
if (!event) break;
|
|
2924
|
+
yield event;
|
|
2925
|
+
}
|
|
2791
2926
|
await session.turnCompletedPromise;
|
|
2792
2927
|
if (session.threadId) {
|
|
2793
2928
|
await this.syncThreadForResume({
|
|
@@ -2796,11 +2931,6 @@ var CodexInteractiveRunner = class {
|
|
|
2796
2931
|
cwd: session.cwd
|
|
2797
2932
|
});
|
|
2798
2933
|
}
|
|
2799
|
-
for (const message of session.pendingMessages) {
|
|
2800
|
-
if (!message.text) continue;
|
|
2801
|
-
yield { type: "text_delta", text: message.text };
|
|
2802
|
-
yield { type: "message_done" };
|
|
2803
|
-
}
|
|
2804
2934
|
yield {
|
|
2805
2935
|
type: "session_state",
|
|
2806
2936
|
state: {
|
|
@@ -2826,6 +2956,10 @@ var CodexInteractiveRunner = class {
|
|
|
2826
2956
|
turnId: session.activeTurnId
|
|
2827
2957
|
}).catch(() => void 0);
|
|
2828
2958
|
}
|
|
2959
|
+
this.flushActiveMessage(session);
|
|
2960
|
+
session.turnClosed = true;
|
|
2961
|
+
session.eventResolver?.();
|
|
2962
|
+
session.eventResolver = void 0;
|
|
2829
2963
|
session.turnCompletedResolver?.();
|
|
2830
2964
|
session.turnCompletedResolver = void 0;
|
|
2831
2965
|
}
|
|
@@ -2848,19 +2982,23 @@ var CodexInteractiveRunner = class {
|
|
|
2848
2982
|
client.onNotification("item/agentMessage/delta", (params) => {
|
|
2849
2983
|
const record = params;
|
|
2850
2984
|
if (typeof record.delta !== "string" || !record.delta) return;
|
|
2851
|
-
const itemId = readAgentMessageItemId(record) ?? `fallback-${session.
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
last.text += record.delta;
|
|
2985
|
+
const itemId = readAgentMessageItemId(record) ?? `fallback-${session.pendingEvents.length}`;
|
|
2986
|
+
if (session.activeMessage?.itemId === itemId) {
|
|
2987
|
+
session.activeMessage.text += record.delta;
|
|
2855
2988
|
return;
|
|
2856
2989
|
}
|
|
2857
|
-
|
|
2990
|
+
this.flushActiveMessage(session);
|
|
2991
|
+
session.activeMessage = { itemId, text: record.delta };
|
|
2858
2992
|
});
|
|
2859
2993
|
client.onNotification("turn/started", (params) => {
|
|
2860
2994
|
const turnId = readTurnId(params);
|
|
2861
2995
|
if (turnId) session.activeTurnId = turnId;
|
|
2862
2996
|
});
|
|
2863
2997
|
client.onNotification("turn/completed", () => {
|
|
2998
|
+
this.flushActiveMessage(session);
|
|
2999
|
+
session.turnClosed = true;
|
|
3000
|
+
session.eventResolver?.();
|
|
3001
|
+
session.eventResolver = void 0;
|
|
2864
3002
|
session.turnCompletedResolver?.();
|
|
2865
3003
|
session.turnCompletedResolver = void 0;
|
|
2866
3004
|
});
|
|
@@ -2873,6 +3011,27 @@ var CodexInteractiveRunner = class {
|
|
|
2873
3011
|
session.client = client;
|
|
2874
3012
|
return client;
|
|
2875
3013
|
}
|
|
3014
|
+
flushActiveMessage(session) {
|
|
3015
|
+
if (!session.activeMessage?.text) return;
|
|
3016
|
+
this.enqueueEvent(session, { type: "text_delta", text: session.activeMessage.text });
|
|
3017
|
+
this.enqueueEvent(session, { type: "message_done" });
|
|
3018
|
+
session.activeMessage = void 0;
|
|
3019
|
+
}
|
|
3020
|
+
enqueueEvent(session, event) {
|
|
3021
|
+
session.pendingEvents.push(event);
|
|
3022
|
+
session.eventResolver?.();
|
|
3023
|
+
session.eventResolver = void 0;
|
|
3024
|
+
}
|
|
3025
|
+
async takeNextEvent(session) {
|
|
3026
|
+
for (; ; ) {
|
|
3027
|
+
const next = session.pendingEvents.shift();
|
|
3028
|
+
if (next) return next;
|
|
3029
|
+
if (session.turnClosed) return null;
|
|
3030
|
+
await new Promise((resolve2) => {
|
|
3031
|
+
session.eventResolver = resolve2;
|
|
3032
|
+
});
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
2876
3035
|
};
|
|
2877
3036
|
|
|
2878
3037
|
// src/providers/codex/codexProvider.ts
|
|
@@ -3100,8 +3259,11 @@ var BRIDGE_COMMAND_HELP_GROUPS = [
|
|
|
3100
3259
|
]
|
|
3101
3260
|
}
|
|
3102
3261
|
];
|
|
3103
|
-
function buildBridgeCommandHelpMarkdown() {
|
|
3262
|
+
function buildBridgeCommandHelpMarkdown(input) {
|
|
3104
3263
|
const lines = ["**\u53EF\u7528\u547D\u4EE4**", "", BRIDGE_COMMAND_HELP_INTRO];
|
|
3264
|
+
if (input?.publicUrl) {
|
|
3265
|
+
lines.push("", `**\u8BBF\u95EE\u5730\u5740**`, "", `[${input.publicUrl}](${input.publicUrl})`);
|
|
3266
|
+
}
|
|
3105
3267
|
for (const group of BRIDGE_COMMAND_HELP_GROUPS) {
|
|
3106
3268
|
lines.push("", `**${group.title}**${group.note ? `\uFF08${group.note}\uFF09` : ""}`);
|
|
3107
3269
|
for (const entry of group.entries) {
|
|
@@ -3112,7 +3274,7 @@ function buildBridgeCommandHelpMarkdown() {
|
|
|
3112
3274
|
}
|
|
3113
3275
|
|
|
3114
3276
|
// src/session/currentConversationStore.ts
|
|
3115
|
-
import { existsSync as
|
|
3277
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
3116
3278
|
import { dirname as dirname7 } from "node:path";
|
|
3117
3279
|
import { nanoid as nanoid2 } from "nanoid";
|
|
3118
3280
|
var CurrentConversationStore = class {
|
|
@@ -3172,8 +3334,8 @@ var CurrentConversationStore = class {
|
|
|
3172
3334
|
this.writeState(state);
|
|
3173
3335
|
}
|
|
3174
3336
|
readState() {
|
|
3175
|
-
if (!
|
|
3176
|
-
const raw = JSON.parse(
|
|
3337
|
+
if (!existsSync6(this.configPath)) return {};
|
|
3338
|
+
const raw = JSON.parse(readFileSync4(this.configPath, "utf8"));
|
|
3177
3339
|
return raw && typeof raw === "object" ? raw : {};
|
|
3178
3340
|
}
|
|
3179
3341
|
writeCurrent(record) {
|
|
@@ -3195,8 +3357,8 @@ var CurrentConversationStore = class {
|
|
|
3195
3357
|
this.writeState(state);
|
|
3196
3358
|
}
|
|
3197
3359
|
writeState(state) {
|
|
3198
|
-
|
|
3199
|
-
|
|
3360
|
+
mkdirSync4(dirname7(this.configPath), { recursive: true });
|
|
3361
|
+
writeFileSync4(this.configPath, `${JSON.stringify(state, null, 2)}
|
|
3200
3362
|
`, "utf8");
|
|
3201
3363
|
}
|
|
3202
3364
|
};
|
|
@@ -3486,10 +3648,11 @@ var MessageRouter = class _MessageRouter {
|
|
|
3486
3648
|
}
|
|
3487
3649
|
async handleCommand(chatId, user, command) {
|
|
3488
3650
|
if (command.kind === "help") {
|
|
3651
|
+
const helpAddress = await this.options.getHelpAddress?.();
|
|
3489
3652
|
await this.sendToChat({
|
|
3490
3653
|
chatId,
|
|
3491
3654
|
kind: "markdown",
|
|
3492
|
-
text: buildBridgeCommandHelpMarkdown()
|
|
3655
|
+
text: buildBridgeCommandHelpMarkdown({ publicUrl: helpAddress })
|
|
3493
3656
|
});
|
|
3494
3657
|
return;
|
|
3495
3658
|
}
|
|
@@ -3640,6 +3803,13 @@ var MessageRouter = class _MessageRouter {
|
|
|
3640
3803
|
providerSessionId: session.providerSessionId,
|
|
3641
3804
|
cwd: session.cwd
|
|
3642
3805
|
});
|
|
3806
|
+
if (session.providerId === "codex") {
|
|
3807
|
+
await upsertCodexSessionIndexEntry({
|
|
3808
|
+
sessionId: session.providerSessionId,
|
|
3809
|
+
threadName: session.resumeTitle
|
|
3810
|
+
});
|
|
3811
|
+
return;
|
|
3812
|
+
}
|
|
3643
3813
|
if (session.providerId === "claude-code") {
|
|
3644
3814
|
await ensureClaudeSessionBridgeMetadata({
|
|
3645
3815
|
sessionId: session.providerSessionId,
|
|
@@ -3701,7 +3871,7 @@ function composeInboundText(content) {
|
|
|
3701
3871
|
}
|
|
3702
3872
|
|
|
3703
3873
|
// src/storage/lastProviderSessionStore.ts
|
|
3704
|
-
import { existsSync as
|
|
3874
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "node:fs";
|
|
3705
3875
|
import { dirname as dirname8 } from "node:path";
|
|
3706
3876
|
var LastProviderSessionStore = class {
|
|
3707
3877
|
constructor(configPath) {
|
|
@@ -3728,19 +3898,19 @@ var LastProviderSessionStore = class {
|
|
|
3728
3898
|
return this.readState().bridge?.lastProviderSessions ?? {};
|
|
3729
3899
|
}
|
|
3730
3900
|
readState() {
|
|
3731
|
-
if (!
|
|
3732
|
-
const raw = JSON.parse(
|
|
3901
|
+
if (!existsSync7(this.configPath)) return {};
|
|
3902
|
+
const raw = JSON.parse(readFileSync5(this.configPath, "utf8"));
|
|
3733
3903
|
return raw && typeof raw === "object" ? raw : {};
|
|
3734
3904
|
}
|
|
3735
3905
|
writeState(state) {
|
|
3736
|
-
|
|
3737
|
-
|
|
3906
|
+
mkdirSync5(dirname8(this.configPath), { recursive: true });
|
|
3907
|
+
writeFileSync5(this.configPath, `${JSON.stringify(state, null, 2)}
|
|
3738
3908
|
`, "utf8");
|
|
3739
3909
|
}
|
|
3740
3910
|
};
|
|
3741
3911
|
|
|
3742
3912
|
// src/storage/runtimeUserStore.ts
|
|
3743
|
-
import { mkdirSync as
|
|
3913
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync6, existsSync as existsSync8 } from "node:fs";
|
|
3744
3914
|
import { dirname as dirname9 } from "node:path";
|
|
3745
3915
|
import { nanoid as nanoid3 } from "nanoid";
|
|
3746
3916
|
var RuntimeUserStore = class {
|
|
@@ -3801,13 +3971,13 @@ var RuntimeUserStore = class {
|
|
|
3801
3971
|
return { ok: true };
|
|
3802
3972
|
}
|
|
3803
3973
|
readState() {
|
|
3804
|
-
if (!
|
|
3805
|
-
const raw = JSON.parse(
|
|
3974
|
+
if (!existsSync8(this.configPath)) return {};
|
|
3975
|
+
const raw = JSON.parse(readFileSync6(this.configPath, "utf8"));
|
|
3806
3976
|
return raw && typeof raw === "object" ? raw : {};
|
|
3807
3977
|
}
|
|
3808
3978
|
writeState(state) {
|
|
3809
|
-
|
|
3810
|
-
|
|
3979
|
+
mkdirSync6(dirname9(this.configPath), { recursive: true });
|
|
3980
|
+
writeFileSync6(this.configPath, `${JSON.stringify(state, null, 2)}
|
|
3811
3981
|
`, "utf8");
|
|
3812
3982
|
}
|
|
3813
3983
|
};
|
|
@@ -3829,15 +3999,232 @@ var BridgeEventHub = class {
|
|
|
3829
3999
|
}
|
|
3830
4000
|
};
|
|
3831
4001
|
|
|
4002
|
+
// src/runtime/relayTunnelProvider.ts
|
|
4003
|
+
var RelayTunnelProvider = class {
|
|
4004
|
+
constructor(options) {
|
|
4005
|
+
this.options = options;
|
|
4006
|
+
}
|
|
4007
|
+
options;
|
|
4008
|
+
socket = null;
|
|
4009
|
+
status = {
|
|
4010
|
+
installed: true,
|
|
4011
|
+
enabled: false,
|
|
4012
|
+
running: false,
|
|
4013
|
+
status: "stopped"
|
|
4014
|
+
};
|
|
4015
|
+
static defaultCreateSocket(url) {
|
|
4016
|
+
const socket = new WebSocket(url);
|
|
4017
|
+
return {
|
|
4018
|
+
get readyState() {
|
|
4019
|
+
return socket.readyState;
|
|
4020
|
+
},
|
|
4021
|
+
send(payload) {
|
|
4022
|
+
socket.send(payload);
|
|
4023
|
+
},
|
|
4024
|
+
close() {
|
|
4025
|
+
socket.close();
|
|
4026
|
+
},
|
|
4027
|
+
on(event, listener) {
|
|
4028
|
+
socket.addEventListener(event, listener);
|
|
4029
|
+
}
|
|
4030
|
+
};
|
|
4031
|
+
}
|
|
4032
|
+
async getStatus() {
|
|
4033
|
+
return { ...this.status };
|
|
4034
|
+
}
|
|
4035
|
+
async start() {
|
|
4036
|
+
this.status = {
|
|
4037
|
+
...this.status,
|
|
4038
|
+
enabled: true,
|
|
4039
|
+
status: "starting"
|
|
4040
|
+
};
|
|
4041
|
+
const socket = this.options.createSocket(this.options.serverUrl);
|
|
4042
|
+
this.socket = socket;
|
|
4043
|
+
return await new Promise((resolve2, reject) => {
|
|
4044
|
+
let settled = false;
|
|
4045
|
+
const finishResolve = (value) => {
|
|
4046
|
+
if (settled) return;
|
|
4047
|
+
settled = true;
|
|
4048
|
+
resolve2(value);
|
|
4049
|
+
};
|
|
4050
|
+
const finishReject = (error) => {
|
|
4051
|
+
if (settled) return;
|
|
4052
|
+
settled = true;
|
|
4053
|
+
reject(error);
|
|
4054
|
+
};
|
|
4055
|
+
socket.on("open", () => {
|
|
4056
|
+
socket.send(JSON.stringify({
|
|
4057
|
+
type: "register",
|
|
4058
|
+
clientVersion: "0.1.0",
|
|
4059
|
+
targetBaseUrl: this.options.targetBaseUrl,
|
|
4060
|
+
authToken: this.options.authToken
|
|
4061
|
+
}));
|
|
4062
|
+
});
|
|
4063
|
+
socket.on("message", (raw) => {
|
|
4064
|
+
const payload = typeof raw === "string" ? JSON.parse(raw) : typeof raw?.data === "string" ? JSON.parse(raw.data) : raw;
|
|
4065
|
+
if (payload?.type === "error") {
|
|
4066
|
+
const message = typeof payload?.error === "string" && payload.error ? payload.error : "relay_error";
|
|
4067
|
+
this.status = {
|
|
4068
|
+
...this.status,
|
|
4069
|
+
running: false,
|
|
4070
|
+
status: "error",
|
|
4071
|
+
error: message
|
|
4072
|
+
};
|
|
4073
|
+
finishReject(new Error(message));
|
|
4074
|
+
return;
|
|
4075
|
+
}
|
|
4076
|
+
if (payload?.type === "registered") {
|
|
4077
|
+
const publicUrl = typeof payload?.token === "string" ? `${derivePublicBaseUrl(this.options.serverUrl)}/${payload.token}` : void 0;
|
|
4078
|
+
this.status = {
|
|
4079
|
+
installed: true,
|
|
4080
|
+
enabled: true,
|
|
4081
|
+
running: true,
|
|
4082
|
+
status: "running",
|
|
4083
|
+
...publicUrl ? { publicUrl } : {}
|
|
4084
|
+
};
|
|
4085
|
+
finishResolve({ ...this.status });
|
|
4086
|
+
return;
|
|
4087
|
+
}
|
|
4088
|
+
if (payload?.type === "request") {
|
|
4089
|
+
void this.handleRequest(payload);
|
|
4090
|
+
}
|
|
4091
|
+
});
|
|
4092
|
+
socket.on("close", () => {
|
|
4093
|
+
this.status = {
|
|
4094
|
+
...this.status,
|
|
4095
|
+
running: false,
|
|
4096
|
+
status: "error",
|
|
4097
|
+
error: "relay_disconnected"
|
|
4098
|
+
};
|
|
4099
|
+
finishReject(new Error("relay_disconnected"));
|
|
4100
|
+
});
|
|
4101
|
+
socket.on("error", (error) => {
|
|
4102
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4103
|
+
this.status = {
|
|
4104
|
+
...this.status,
|
|
4105
|
+
running: false,
|
|
4106
|
+
status: "error",
|
|
4107
|
+
error: message
|
|
4108
|
+
};
|
|
4109
|
+
finishReject(error);
|
|
4110
|
+
});
|
|
4111
|
+
});
|
|
4112
|
+
}
|
|
4113
|
+
async stop() {
|
|
4114
|
+
this.socket?.close();
|
|
4115
|
+
this.socket = null;
|
|
4116
|
+
this.status = {
|
|
4117
|
+
installed: true,
|
|
4118
|
+
enabled: false,
|
|
4119
|
+
running: false,
|
|
4120
|
+
status: "stopped"
|
|
4121
|
+
};
|
|
4122
|
+
return { ...this.status };
|
|
4123
|
+
}
|
|
4124
|
+
async setEnabled(enabled) {
|
|
4125
|
+
return enabled ? await this.start() : await this.stop();
|
|
4126
|
+
}
|
|
4127
|
+
async handleRequest(payload) {
|
|
4128
|
+
const fetchImpl = this.options.fetchImpl ?? fetch;
|
|
4129
|
+
const response = await fetchImpl(`${this.options.targetBaseUrl}${payload.path}`, {
|
|
4130
|
+
method: payload.method,
|
|
4131
|
+
headers: payload.headers,
|
|
4132
|
+
body: payload.bodyBase64 ? Buffer.from(payload.bodyBase64, "base64") : void 0
|
|
4133
|
+
});
|
|
4134
|
+
const bodyBuffer = Buffer.from(await response.arrayBuffer());
|
|
4135
|
+
this.socket?.send(JSON.stringify({
|
|
4136
|
+
type: "response",
|
|
4137
|
+
requestId: payload.requestId,
|
|
4138
|
+
status: response.status,
|
|
4139
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
4140
|
+
bodyBase64: bodyBuffer.toString("base64")
|
|
4141
|
+
}));
|
|
4142
|
+
}
|
|
4143
|
+
};
|
|
4144
|
+
function derivePublicBaseUrl(serverUrl) {
|
|
4145
|
+
return serverUrl.replace(/^ws:\/\//, "http://").replace(/^wss:\/\//, "https://").replace(/\/agent\/?$/, "").replace(/\/+$/, "");
|
|
4146
|
+
}
|
|
4147
|
+
|
|
4148
|
+
// src/runtime/relayTunnelRouter.ts
|
|
4149
|
+
var RelayTunnelRouter = class {
|
|
4150
|
+
constructor(options) {
|
|
4151
|
+
this.options = options;
|
|
4152
|
+
}
|
|
4153
|
+
options;
|
|
4154
|
+
relayProvider = null;
|
|
4155
|
+
relayKey = "";
|
|
4156
|
+
async getStatus() {
|
|
4157
|
+
const provider = this.getOrCreateRelayProvider();
|
|
4158
|
+
if (!provider) return this.notConfiguredStatus();
|
|
4159
|
+
return await provider.getStatus();
|
|
4160
|
+
}
|
|
4161
|
+
async start() {
|
|
4162
|
+
const provider = this.getOrCreateRelayProvider();
|
|
4163
|
+
if (!provider) return this.notConfiguredStatus();
|
|
4164
|
+
return await provider.start();
|
|
4165
|
+
}
|
|
4166
|
+
async stop() {
|
|
4167
|
+
const provider = this.getOrCreateRelayProvider();
|
|
4168
|
+
if (!provider) return this.notConfiguredStatus();
|
|
4169
|
+
return await provider.stop();
|
|
4170
|
+
}
|
|
4171
|
+
async setEnabled(enabled) {
|
|
4172
|
+
const provider = this.getOrCreateRelayProvider();
|
|
4173
|
+
if (!provider) return this.notConfiguredStatus();
|
|
4174
|
+
return await provider.setEnabled(enabled);
|
|
4175
|
+
}
|
|
4176
|
+
getOrCreateRelayProvider() {
|
|
4177
|
+
const relay = this.options.defaults.tunnel.relay;
|
|
4178
|
+
if (!relay?.serverUrl || !relay.authToken) return null;
|
|
4179
|
+
const key = `${relay.serverUrl}|${relay.authToken}|${this.options.bridgePort}`;
|
|
4180
|
+
if (this.relayProvider && this.relayKey === key) return this.relayProvider;
|
|
4181
|
+
this.relayKey = key;
|
|
4182
|
+
this.relayProvider = new RelayTunnelProvider({
|
|
4183
|
+
serverUrl: relay.serverUrl,
|
|
4184
|
+
authToken: relay.authToken,
|
|
4185
|
+
targetBaseUrl: `http://127.0.0.1:${this.options.bridgePort}`,
|
|
4186
|
+
createSocket: RelayTunnelProvider.defaultCreateSocket
|
|
4187
|
+
});
|
|
4188
|
+
return this.relayProvider;
|
|
4189
|
+
}
|
|
4190
|
+
notConfiguredStatus() {
|
|
4191
|
+
return {
|
|
4192
|
+
installed: false,
|
|
4193
|
+
enabled: false,
|
|
4194
|
+
running: false,
|
|
4195
|
+
status: "error",
|
|
4196
|
+
error: "relay_not_configured"
|
|
4197
|
+
};
|
|
4198
|
+
}
|
|
4199
|
+
};
|
|
4200
|
+
|
|
3832
4201
|
// src/daemon/server.ts
|
|
3833
4202
|
function createDaemonServer(options = {}) {
|
|
3834
4203
|
const app = Fastify({ logger: true });
|
|
3835
4204
|
const events = new BridgeEventHub();
|
|
4205
|
+
const configPath = options.configPath ?? process.env.BRIDGE_CONFIG ?? join9(mkdtempSync(join9(tmpdir2(), "claude-codex-wechat-")), "config.json");
|
|
4206
|
+
const persistedConfig = loadBridgeConfig(configPath);
|
|
3836
4207
|
const bridgeDefaults = {
|
|
3837
|
-
defaultProvider: options.bridgeDefaults?.defaultProvider ?? "claude-code",
|
|
3838
|
-
defaultWorkspace: options.bridgeDefaults?.defaultWorkspace ?? process.cwd()
|
|
4208
|
+
defaultProvider: options.bridgeDefaults?.defaultProvider ?? persistedConfig.bridge?.defaultProvider ?? "claude-code",
|
|
4209
|
+
defaultWorkspace: options.bridgeDefaults?.defaultWorkspace ?? persistedConfig.bridge?.defaultWorkspace ?? process.cwd(),
|
|
4210
|
+
tunnel: options.bridgeDefaults?.tunnel ?? {
|
|
4211
|
+
enabled: persistedConfig.tunnel?.enabled === true,
|
|
4212
|
+
...persistedConfig.tunnel?.relay ? { relay: persistedConfig.tunnel.relay } : {}
|
|
4213
|
+
}
|
|
3839
4214
|
};
|
|
3840
|
-
const
|
|
4215
|
+
const existingRelayAuthToken = bridgeDefaults.tunnel.relay?.authToken?.trim();
|
|
4216
|
+
if (existingRelayAuthToken) {
|
|
4217
|
+
bridgeDefaults.tunnel.relay = {
|
|
4218
|
+
...bridgeDefaults.tunnel.relay ?? {},
|
|
4219
|
+
authToken: existingRelayAuthToken
|
|
4220
|
+
};
|
|
4221
|
+
} else {
|
|
4222
|
+
const authToken = ensureRelayAuthTokenSync({ configPath });
|
|
4223
|
+
bridgeDefaults.tunnel.relay = {
|
|
4224
|
+
...bridgeDefaults.tunnel.relay ?? {},
|
|
4225
|
+
authToken
|
|
4226
|
+
};
|
|
4227
|
+
}
|
|
3841
4228
|
const conversation = new CurrentConversationStore(configPath, {
|
|
3842
4229
|
defaultCwd: bridgeDefaults.defaultWorkspace,
|
|
3843
4230
|
defaultProviderId: bridgeDefaults.defaultProvider
|
|
@@ -3879,6 +4266,10 @@ function createDaemonServer(options = {}) {
|
|
|
3879
4266
|
await managedWechatChannel.sendMessage({ chatId, kind: msg.kind, text: msg.text });
|
|
3880
4267
|
}
|
|
3881
4268
|
}) : void 0;
|
|
4269
|
+
const tunnelProvider = options.tunnelProvider ?? new RelayTunnelRouter({
|
|
4270
|
+
bridgePort: Number(process.env.BRIDGE_PORT ?? 8787),
|
|
4271
|
+
defaults: bridgeDefaults
|
|
4272
|
+
});
|
|
3882
4273
|
const messageRouter = channel ? new MessageRouter({
|
|
3883
4274
|
channel,
|
|
3884
4275
|
providers: providerAdapters,
|
|
@@ -3931,7 +4322,14 @@ function createDaemonServer(options = {}) {
|
|
|
3931
4322
|
},
|
|
3932
4323
|
lastProviderSessions,
|
|
3933
4324
|
events,
|
|
3934
|
-
defaults: bridgeDefaults
|
|
4325
|
+
defaults: bridgeDefaults,
|
|
4326
|
+
getHelpAddress: async () => {
|
|
4327
|
+
const tunnel = await tunnelProvider?.getStatus().catch(() => null);
|
|
4328
|
+
if (tunnel?.running && tunnel.publicUrl) return tunnel.publicUrl;
|
|
4329
|
+
const lan = listLanIpv4Addresses()[0];
|
|
4330
|
+
if (lan) return `http://${lan}:${process.env.BRIDGE_PORT ?? 8787}`;
|
|
4331
|
+
return `http://127.0.0.1:${process.env.BRIDGE_PORT ?? 8787}`;
|
|
4332
|
+
}
|
|
3935
4333
|
}) : void 0;
|
|
3936
4334
|
if (channel && messageRouter) {
|
|
3937
4335
|
channel.onMessage(async (message) => {
|
|
@@ -3960,10 +4358,24 @@ function createDaemonServer(options = {}) {
|
|
|
3960
4358
|
defaults: bridgeDefaults,
|
|
3961
4359
|
configPath
|
|
3962
4360
|
});
|
|
4361
|
+
if (tunnelProvider) {
|
|
4362
|
+
registerTunnelRoutes({
|
|
4363
|
+
app,
|
|
4364
|
+
configPath,
|
|
4365
|
+
defaults: { tunnel: bridgeDefaults.tunnel },
|
|
4366
|
+
tunnelManager: tunnelProvider
|
|
4367
|
+
});
|
|
4368
|
+
}
|
|
3963
4369
|
registerFsBrowseRoutes({ app });
|
|
3964
4370
|
app.get("/api/status", async () => ({
|
|
3965
4371
|
ok: true,
|
|
3966
|
-
sessions: conversation.getCurrent() ? [conversation.getCurrent()] : []
|
|
4372
|
+
sessions: conversation.getCurrent() ? [conversation.getCurrent()] : [],
|
|
4373
|
+
preferredLocalUrl: (() => {
|
|
4374
|
+
const lan = listLanIpv4Addresses()[0];
|
|
4375
|
+
const port = process.env.BRIDGE_PORT ?? 8787;
|
|
4376
|
+
if (lan) return `http://${lan}:${port}`;
|
|
4377
|
+
return `http://127.0.0.1:${port}`;
|
|
4378
|
+
})()
|
|
3967
4379
|
}));
|
|
3968
4380
|
app.get("/api/providers/status", async () => providers.getStatus());
|
|
3969
4381
|
app.post("/api/bridge-events", async (request, reply) => {
|
|
@@ -3997,7 +4409,7 @@ function createDaemonServer(options = {}) {
|
|
|
3997
4409
|
app.addHook("onClose", async () => {
|
|
3998
4410
|
await channel?.stop();
|
|
3999
4411
|
});
|
|
4000
|
-
return { app, conversation, sessions: conversation, events, activeUserStore };
|
|
4412
|
+
return { app, conversation, sessions: conversation, events, activeUserStore, tunnelProvider };
|
|
4001
4413
|
}
|
|
4002
4414
|
|
|
4003
4415
|
// src/daemon/bootstrap.ts
|
|
@@ -4011,24 +4423,35 @@ async function startDaemon(options) {
|
|
|
4011
4423
|
configPath,
|
|
4012
4424
|
providers: providerCommands
|
|
4013
4425
|
});
|
|
4014
|
-
const
|
|
4426
|
+
const bridgeDefaults = {
|
|
4427
|
+
defaultProvider: config.bridge?.defaultProvider ?? "claude-code",
|
|
4428
|
+
defaultWorkspace: config.bridge?.defaultWorkspace ?? process.cwd(),
|
|
4429
|
+
tunnel: {
|
|
4430
|
+
enabled: config.tunnel?.enabled === true,
|
|
4431
|
+
...config.tunnel?.relay ? { relay: config.tunnel.relay } : {}
|
|
4432
|
+
}
|
|
4433
|
+
};
|
|
4434
|
+
const { app, tunnelProvider: resolvedTunnelProvider } = createDaemonServer({
|
|
4015
4435
|
wechat: config.wechat,
|
|
4016
4436
|
providerCommands,
|
|
4017
|
-
bridgeDefaults
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
},
|
|
4021
|
-
configPath
|
|
4437
|
+
bridgeDefaults,
|
|
4438
|
+
configPath,
|
|
4439
|
+
...options.tunnelProvider ? { tunnelProvider: options.tunnelProvider } : {}
|
|
4022
4440
|
});
|
|
4023
4441
|
await options.attachFrontend(app);
|
|
4024
4442
|
await app.listen({ host, port });
|
|
4443
|
+
const address = app.server.address();
|
|
4444
|
+
const actualPort = typeof address === "object" && address ? address.port : port;
|
|
4445
|
+
if (config.tunnel?.enabled === true) {
|
|
4446
|
+
await resolvedTunnelProvider?.start().catch(() => void 0);
|
|
4447
|
+
}
|
|
4025
4448
|
console.log("claude-codex-wechat listening:");
|
|
4026
|
-
console.log(` Local: http://127.0.0.1:${
|
|
4449
|
+
console.log(` Local: http://127.0.0.1:${actualPort}`);
|
|
4027
4450
|
for (const ip of listLanIpv4Addresses()) {
|
|
4028
|
-
console.log(` Network: http://${ip}:${
|
|
4451
|
+
console.log(` Network: http://${ip}:${actualPort}`);
|
|
4029
4452
|
}
|
|
4030
4453
|
console.log(`config path: ${configPath}`);
|
|
4031
|
-
return { app, port, host, configPath };
|
|
4454
|
+
return { app, port: actualPort, host, configPath };
|
|
4032
4455
|
}
|
|
4033
4456
|
function listLanIpv4Addresses() {
|
|
4034
4457
|
return Object.values(networkInterfaces()).flat().filter((iface) => Boolean(iface)).filter((iface) => iface.family === "IPv4" && !iface.internal).map((iface) => iface.address);
|
|
@@ -4044,7 +4467,7 @@ async function resolveProviderCommands(providers) {
|
|
|
4044
4467
|
}
|
|
4045
4468
|
|
|
4046
4469
|
// src/daemon/staticFrontend.ts
|
|
4047
|
-
import { readFileSync as
|
|
4470
|
+
import { readFileSync as readFileSync7 } from "node:fs";
|
|
4048
4471
|
import { join as join10 } from "node:path";
|
|
4049
4472
|
import fastifyStatic from "@fastify/static";
|
|
4050
4473
|
function attachStaticFrontend(webRoot2) {
|
|
@@ -4053,7 +4476,7 @@ function attachStaticFrontend(webRoot2) {
|
|
|
4053
4476
|
root: webRoot2,
|
|
4054
4477
|
wildcard: false
|
|
4055
4478
|
});
|
|
4056
|
-
const indexHtml =
|
|
4479
|
+
const indexHtml = readFileSync7(join10(webRoot2, "index.html"), "utf8");
|
|
4057
4480
|
app.setNotFoundHandler((request, reply) => {
|
|
4058
4481
|
if (request.url.startsWith("/api")) {
|
|
4059
4482
|
reply.code(404).send({ error: "not_found" });
|
|
@@ -4065,7 +4488,7 @@ function attachStaticFrontend(webRoot2) {
|
|
|
4065
4488
|
}
|
|
4066
4489
|
|
|
4067
4490
|
// src/daemon/service.ts
|
|
4068
|
-
import { existsSync as
|
|
4491
|
+
import { existsSync as existsSync9, statSync } from "node:fs";
|
|
4069
4492
|
import { mkdir as mkdir4, readFile as readFile6, rm as rm2, writeFile as writeFile5 } from "node:fs/promises";
|
|
4070
4493
|
import { homedir as homedir8 } from "node:os";
|
|
4071
4494
|
import { dirname as dirname11, join as join11 } from "node:path";
|
|
@@ -4115,13 +4538,13 @@ async function startService(context) {
|
|
|
4115
4538
|
async function stopService(context) {
|
|
4116
4539
|
if (process.platform === "darwin") {
|
|
4117
4540
|
const spec = buildLaunchdSpec(context);
|
|
4118
|
-
if (!
|
|
4541
|
+
if (!existsSync9(spec.plistPath)) return await readServiceStatus(context);
|
|
4119
4542
|
await runLaunchctl(["bootout", `gui/${process.getuid?.() ?? 0}`, spec.plistPath]).catch(() => void 0);
|
|
4120
4543
|
return await readServiceStatus(context);
|
|
4121
4544
|
}
|
|
4122
4545
|
if (process.platform === "linux") {
|
|
4123
4546
|
const spec = buildSystemdUserSpec(context);
|
|
4124
|
-
if (!
|
|
4547
|
+
if (!existsSync9(spec.unitPath)) return await readServiceStatus(context);
|
|
4125
4548
|
await runSystemctl(["--user", "stop", spec.unitName]).catch(() => void 0);
|
|
4126
4549
|
return await readServiceStatus(context);
|
|
4127
4550
|
}
|
|
@@ -4130,7 +4553,7 @@ async function stopService(context) {
|
|
|
4130
4553
|
async function uninstallService(context) {
|
|
4131
4554
|
if (process.platform === "darwin") {
|
|
4132
4555
|
const spec = buildLaunchdSpec(context);
|
|
4133
|
-
if (
|
|
4556
|
+
if (existsSync9(spec.plistPath)) {
|
|
4134
4557
|
await runLaunchctl(["bootout", `gui/${process.getuid?.() ?? 0}`, spec.plistPath]).catch(() => void 0);
|
|
4135
4558
|
await rm2(spec.plistPath, { force: true });
|
|
4136
4559
|
}
|
|
@@ -4138,7 +4561,7 @@ async function uninstallService(context) {
|
|
|
4138
4561
|
}
|
|
4139
4562
|
if (process.platform === "linux") {
|
|
4140
4563
|
const spec = buildSystemdUserSpec(context);
|
|
4141
|
-
if (
|
|
4564
|
+
if (existsSync9(spec.unitPath)) {
|
|
4142
4565
|
await runSystemctl(["--user", "disable", "--now", spec.unitName]).catch(() => void 0);
|
|
4143
4566
|
await rm2(spec.unitPath, { force: true });
|
|
4144
4567
|
await runSystemctl(["--user", "daemon-reload"]).catch(() => void 0);
|
|
@@ -4181,7 +4604,7 @@ async function tailServiceLogs(context) {
|
|
|
4181
4604
|
async function readServiceStatus(context) {
|
|
4182
4605
|
if (process.platform === "darwin") {
|
|
4183
4606
|
const spec = buildLaunchdSpec(context);
|
|
4184
|
-
const installed =
|
|
4607
|
+
const installed = existsSync9(spec.plistPath);
|
|
4185
4608
|
const running = installed ? await runLaunchctl(["print", `gui/${process.getuid?.() ?? 0}/${spec.label}`]).then(() => true, () => false) : false;
|
|
4186
4609
|
return {
|
|
4187
4610
|
manager: "launchd",
|
|
@@ -4195,7 +4618,7 @@ async function readServiceStatus(context) {
|
|
|
4195
4618
|
}
|
|
4196
4619
|
if (process.platform === "linux") {
|
|
4197
4620
|
const spec = buildSystemdUserSpec(context);
|
|
4198
|
-
const installed =
|
|
4621
|
+
const installed = existsSync9(spec.unitPath);
|
|
4199
4622
|
const running = installed ? await runSystemctl(["--user", "is-active", "--quiet", spec.unitName]).then(() => true, () => false) : false;
|
|
4200
4623
|
return {
|
|
4201
4624
|
manager: "systemd-user",
|
|
@@ -4331,16 +4754,16 @@ async function runSystemctl(args) {
|
|
|
4331
4754
|
await execFileAsync("systemctl", args);
|
|
4332
4755
|
}
|
|
4333
4756
|
async function ensureFileExists(path, errorCode) {
|
|
4334
|
-
if (!
|
|
4757
|
+
if (!existsSync9(path)) throw new Error(errorCode);
|
|
4335
4758
|
await readFile6(path, "utf8");
|
|
4336
4759
|
}
|
|
4337
4760
|
async function tailFile(path, lines) {
|
|
4338
|
-
if (!
|
|
4761
|
+
if (!existsSync9(path)) return "";
|
|
4339
4762
|
const content = await readFile6(path, "utf8");
|
|
4340
4763
|
return content.split(/\r?\n/).slice(-lines).join("\n").trim();
|
|
4341
4764
|
}
|
|
4342
4765
|
async function tailFiles(paths) {
|
|
4343
|
-
const readers = paths.filter((path) =>
|
|
4766
|
+
const readers = paths.filter((path) => existsSync9(path)).map((path) => {
|
|
4344
4767
|
const rl = createInterface({
|
|
4345
4768
|
input: createReadStream(path, { encoding: "utf8", start: Math.max(0, fileSizeHint(path) - 8192) }),
|
|
4346
4769
|
crlfDelay: Infinity
|
|
@@ -4354,7 +4777,7 @@ async function tailFiles(paths) {
|
|
|
4354
4777
|
if (process.platform !== "linux" && process.platform !== "darwin") {
|
|
4355
4778
|
return;
|
|
4356
4779
|
}
|
|
4357
|
-
const child = spawn6("tail", ["-f", ...paths.filter((path) =>
|
|
4780
|
+
const child = spawn6("tail", ["-f", ...paths.filter((path) => existsSync9(path))], { stdio: ["ignore", "pipe", "inherit"] });
|
|
4358
4781
|
child.stdout.on("data", (chunk) => {
|
|
4359
4782
|
process.stdout.write(String(chunk));
|
|
4360
4783
|
});
|
|
@@ -4427,7 +4850,7 @@ async function main() {
|
|
|
4427
4850
|
}
|
|
4428
4851
|
}
|
|
4429
4852
|
async function cmdStart() {
|
|
4430
|
-
if (!
|
|
4853
|
+
if (!existsSync10(webRoot)) {
|
|
4431
4854
|
console.error(`\u627E\u4E0D\u5230\u524D\u7AEF\u6784\u5EFA\u4EA7\u7269: ${webRoot}`);
|
|
4432
4855
|
console.error("\u8BF7\u91CD\u65B0\u5B89\u88C5\u5B8C\u6574\u7684 npm \u5305\u3002");
|
|
4433
4856
|
process.exitCode = 1;
|
|
@@ -4457,7 +4880,7 @@ async function cmdTail() {
|
|
|
4457
4880
|
await tailServiceLogs(createServiceContext());
|
|
4458
4881
|
}
|
|
4459
4882
|
async function cmdDaemon() {
|
|
4460
|
-
if (!
|
|
4883
|
+
if (!existsSync10(webRoot)) {
|
|
4461
4884
|
console.error(`\u627E\u4E0D\u5230\u524D\u7AEF\u6784\u5EFA\u4EA7\u7269: ${webRoot}`);
|
|
4462
4885
|
process.exitCode = 1;
|
|
4463
4886
|
return;
|
|
@@ -4469,8 +4892,8 @@ async function cmdDaemon() {
|
|
|
4469
4892
|
function cmdInit() {
|
|
4470
4893
|
const configPath = process.env.BRIDGE_CONFIG ?? defaultConfigPath();
|
|
4471
4894
|
const dir = dirname12(configPath);
|
|
4472
|
-
|
|
4473
|
-
if (
|
|
4895
|
+
mkdirSync7(dir, { recursive: true });
|
|
4896
|
+
if (existsSync10(configPath)) {
|
|
4474
4897
|
console.log(`\u914D\u7F6E\u5DF2\u5B58\u5728\uFF0C\u672A\u8986\u76D6: ${configPath}`);
|
|
4475
4898
|
return;
|
|
4476
4899
|
}
|
|
@@ -4487,7 +4910,7 @@ function cmdInit() {
|
|
|
4487
4910
|
defaultWorkspace: process.cwd()
|
|
4488
4911
|
}
|
|
4489
4912
|
};
|
|
4490
|
-
|
|
4913
|
+
writeFileSync7(configPath, `${JSON.stringify(template, null, 2)}
|
|
4491
4914
|
`, "utf8");
|
|
4492
4915
|
console.log(`\u5DF2\u521B\u5EFA\u914D\u7F6E: ${configPath}`);
|
|
4493
4916
|
console.log("\u8BF7\u586B\u5165\u5FAE\u4FE1 token / accountId \u540E\u8FD0\u884C: claude-codex-wechat start");
|
|
@@ -4495,13 +4918,13 @@ function cmdInit() {
|
|
|
4495
4918
|
async function cmdDoctor() {
|
|
4496
4919
|
const configPath = process.env.BRIDGE_CONFIG ?? defaultConfigPath();
|
|
4497
4920
|
console.log("claude-codex-wechat doctor\n");
|
|
4498
|
-
report("\u914D\u7F6E\u6587\u4EF6",
|
|
4499
|
-
report("\u524D\u7AEF\u4EA7\u7269",
|
|
4921
|
+
report("\u914D\u7F6E\u6587\u4EF6", existsSync10(configPath) ? configPath : `\u7F3A\u5931 (${configPath})\uFF0C\u8FD0\u884C init \u521B\u5EFA`);
|
|
4922
|
+
report("\u524D\u7AEF\u4EA7\u7269", existsSync10(webRoot) ? webRoot : `\u7F3A\u5931 (${webRoot})`);
|
|
4500
4923
|
const claude = await findExecutable("claude");
|
|
4501
4924
|
report("claude \u53EF\u6267\u884C", claude ?? "\u672A\u627E\u5230\uFF0C\u9700\u5148\u5B89\u88C5\u5E76\u767B\u5F55 Claude Code");
|
|
4502
4925
|
const codex = await findExecutable("codex");
|
|
4503
4926
|
report("codex \u53EF\u6267\u884C", codex ?? "\u672A\u627E\u5230\uFF08\u4EC5\u4F7F\u7528 Claude \u65F6\u53EF\u5FFD\u7565\uFF09");
|
|
4504
|
-
if (
|
|
4927
|
+
if (existsSync10(configPath)) {
|
|
4505
4928
|
const config = loadBridgeConfig(configPath);
|
|
4506
4929
|
const wechat = config.wechat;
|
|
4507
4930
|
report("\u5FAE\u4FE1\u542F\u7528", wechat?.enabled ? "\u662F" : "\u5426");
|
|
@@ -4511,12 +4934,12 @@ async function cmdDoctor() {
|
|
|
4511
4934
|
}
|
|
4512
4935
|
function cmdPrintConfig() {
|
|
4513
4936
|
const configPath = process.env.BRIDGE_CONFIG ?? defaultConfigPath();
|
|
4514
|
-
if (!
|
|
4937
|
+
if (!existsSync10(configPath)) {
|
|
4515
4938
|
console.error(`\u914D\u7F6E\u4E0D\u5B58\u5728: ${configPath}\uFF08\u8FD0\u884C init \u521B\u5EFA\uFF09`);
|
|
4516
4939
|
process.exitCode = 1;
|
|
4517
4940
|
return;
|
|
4518
4941
|
}
|
|
4519
|
-
console.log(
|
|
4942
|
+
console.log(readFileSync8(configPath, "utf8"));
|
|
4520
4943
|
}
|
|
4521
4944
|
async function cmdUninstall() {
|
|
4522
4945
|
const status = await uninstallService(createServiceContext());
|