claude-codex-wechat 0.1.53 → 0.1.55
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 +5 -5
- package/dist/server/cli.js +202 -44
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -41,15 +41,15 @@ claude-codex-wechat restart
|
|
|
41
41
|
|
|
42
42
|
安装完,三步就能跑通:
|
|
43
43
|
|
|
44
|
+
**1. 启动**(首次会自动注册成系统后台服务,并自动配好接入公网中转,无需手动配置)
|
|
45
|
+
|
|
44
46
|
```bash
|
|
45
|
-
# 1. 启动(首次会自动注册成系统后台服务,并自动配好接入公网中转,无需手动配置)
|
|
46
47
|
claude-codex-wechat start
|
|
48
|
+
```
|
|
47
49
|
|
|
48
|
-
|
|
49
|
-
# 浏览器访问 http://127.0.0.1:8787
|
|
50
|
+
**2. 绑定微信** — 浏览器打开 <http://127.0.0.1:8787>,用微信扫码登录。
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
```
|
|
52
|
+
**3. 开始用** — 绑定成功后,直接在微信里给机器人发消息即可。
|
|
53
53
|
|
|
54
54
|
搞定。之后不管电脑锁屏还是你人在外面,只要开发机开着,就能用微信指挥它。
|
|
55
55
|
|
package/dist/server/cli.js
CHANGED
|
@@ -5,8 +5,8 @@ import { createRequire as __cjsCreateRequire } from 'node:module'; const require
|
|
|
5
5
|
import { execFile as execFile2 } from "node:child_process";
|
|
6
6
|
import { existsSync as existsSync11, readFileSync as readFileSync10 } from "node:fs";
|
|
7
7
|
import { promisify as promisify2 } from "node:util";
|
|
8
|
-
import { dirname as
|
|
9
|
-
import { fileURLToPath as
|
|
8
|
+
import { dirname as dirname10, join as join15 } from "node:path";
|
|
9
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
10
10
|
|
|
11
11
|
// src/daemon/bootstrap.ts
|
|
12
12
|
import { networkInterfaces } from "node:os";
|
|
@@ -14,8 +14,8 @@ import { networkInterfaces } from "node:os";
|
|
|
14
14
|
// src/daemon/server.ts
|
|
15
15
|
import { mkdtempSync, existsSync as existsSync9, writeFileSync as writeFileSync2 } from "node:fs";
|
|
16
16
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
17
|
-
import { dirname as
|
|
18
|
-
import { fileURLToPath as
|
|
17
|
+
import { dirname as dirname8, join as join12 } from "node:path";
|
|
18
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
19
19
|
import Fastify from "fastify";
|
|
20
20
|
|
|
21
21
|
// src/channels/platforms.ts
|
|
@@ -2760,6 +2760,8 @@ var WeixinOutboundGate = class _WeixinOutboundGate {
|
|
|
2760
2760
|
hintedReplies = /* @__PURE__ */ new Map();
|
|
2761
2761
|
/** The message held back on the final quota slot, pending proof of a follow-up. */
|
|
2762
2762
|
pendingLast = /* @__PURE__ */ new Map();
|
|
2763
|
+
/** 配额归零导致消息入队、且尚未告知用户的会话。 */
|
|
2764
|
+
quotaExhaustedNotified = /* @__PURE__ */ new Set();
|
|
2763
2765
|
static HINT_REPLY_TTL_MS = 2 * 60 * 60 * 1e3;
|
|
2764
2766
|
hasPending(chatId) {
|
|
2765
2767
|
return this.options.store.hasPendingOutbound(chatId);
|
|
@@ -2781,16 +2783,19 @@ var WeixinOutboundGate = class _WeixinOutboundGate {
|
|
|
2781
2783
|
this.pendingLast.delete(chatId);
|
|
2782
2784
|
await this.options.send(chatId, appendHintIfText(buffered));
|
|
2783
2785
|
this.hintedReplies.set(chatId, Date.now());
|
|
2784
|
-
store.enqueueOutbound(chatId, message);
|
|
2786
|
+
store.enqueueOutbound(chatId, stamp(message));
|
|
2785
2787
|
return;
|
|
2786
2788
|
}
|
|
2787
2789
|
if (store.hasPendingOutbound(chatId)) {
|
|
2788
|
-
store.enqueueOutbound(chatId, message);
|
|
2790
|
+
store.enqueueOutbound(chatId, stamp(message));
|
|
2789
2791
|
return;
|
|
2790
2792
|
}
|
|
2791
2793
|
const quota = store.getQuota(chatId);
|
|
2792
2794
|
if (quota.expired || quota.remaining <= 0) {
|
|
2793
|
-
|
|
2795
|
+
if (!this.quotaExhaustedNotified.has(chatId)) {
|
|
2796
|
+
this.quotaExhaustedNotified.add(chatId);
|
|
2797
|
+
}
|
|
2798
|
+
store.enqueueOutbound(chatId, stamp(message));
|
|
2794
2799
|
return;
|
|
2795
2800
|
}
|
|
2796
2801
|
if (quota.remaining === 1) {
|
|
@@ -2810,6 +2815,24 @@ var WeixinOutboundGate = class _WeixinOutboundGate {
|
|
|
2810
2815
|
this.pendingLast.delete(chatId);
|
|
2811
2816
|
await this.options.send(chatId, buffered);
|
|
2812
2817
|
}
|
|
2818
|
+
/**
|
|
2819
|
+
* 取出「配额耗尽导致回复被排队」的一次性提示文案,没有则返回 null。
|
|
2820
|
+
* 取走即清除,保证同一次耗尽只提示一次,不会每条排队消息都刷屏。
|
|
2821
|
+
*/
|
|
2822
|
+
takeQuotaExhaustedNotice(chatId) {
|
|
2823
|
+
if (!this.quotaExhaustedNotified.delete(chatId)) return null;
|
|
2824
|
+
return "\u26A0\uFE0F \u672C\u8F6E\u56DE\u590D\u5DF2\u8D85\u51FA\u5FAE\u4FE1 24 \u5C0F\u65F6\u63A8\u9001\u989D\u5EA6\uFF0C\u5269\u4F59\u5185\u5BB9\u5DF2\u6682\u5B58\u3002\u56DE\u590D\u4EFB\u610F\u6D88\u606F\u5373\u53EF\u7EE7\u7EED\u63A5\u6536\u3002";
|
|
2825
|
+
}
|
|
2826
|
+
/**
|
|
2827
|
+
* 丢弃该会话全部待发消息(含缓冲在最后一格配额上的那条)。
|
|
2828
|
+
* 会话被替换时调用,避免旧会话的回复稍后串进新会话。
|
|
2829
|
+
*/
|
|
2830
|
+
async discardPending(chatId) {
|
|
2831
|
+
this.pendingLast.delete(chatId);
|
|
2832
|
+
this.hintedReplies.delete(chatId);
|
|
2833
|
+
this.quotaExhaustedNotified.delete(chatId);
|
|
2834
|
+
this.options.store.clearOutbound(chatId);
|
|
2835
|
+
}
|
|
2813
2836
|
async drain(chatId) {
|
|
2814
2837
|
const { store } = this.options;
|
|
2815
2838
|
while (store.hasPendingOutbound(chatId)) {
|
|
@@ -2817,6 +2840,10 @@ var WeixinOutboundGate = class _WeixinOutboundGate {
|
|
|
2817
2840
|
if (quota.expired || quota.remaining <= 0) break;
|
|
2818
2841
|
const pending = store.peekOutbound(chatId);
|
|
2819
2842
|
const next = pending[0];
|
|
2843
|
+
if (isStale(next)) {
|
|
2844
|
+
store.shiftOutbound(chatId);
|
|
2845
|
+
continue;
|
|
2846
|
+
}
|
|
2820
2847
|
const isLastSlot = quota.remaining === 1;
|
|
2821
2848
|
const hasMore = pending.length > 1;
|
|
2822
2849
|
if (isLastSlot && hasMore) {
|
|
@@ -2836,6 +2863,13 @@ function appendHintIfText(item) {
|
|
|
2836
2863
|
if (MEDIA_KINDS.has(item.kind)) return item;
|
|
2837
2864
|
return { ...item, text: item.text + CONTINUATION_HINT };
|
|
2838
2865
|
}
|
|
2866
|
+
function isStale(item, now = Date.now()) {
|
|
2867
|
+
if (typeof item.enqueuedAt !== "number") return false;
|
|
2868
|
+
return now - item.enqueuedAt >= PUSH_WINDOW_MS;
|
|
2869
|
+
}
|
|
2870
|
+
function stamp(item) {
|
|
2871
|
+
return item.enqueuedAt === void 0 ? { ...item, enqueuedAt: Date.now() } : item;
|
|
2872
|
+
}
|
|
2839
2873
|
|
|
2840
2874
|
// src/providers/claude-code/claudeStreamingRunner.ts
|
|
2841
2875
|
import { spawn as spawn3 } from "node:child_process";
|
|
@@ -3616,6 +3650,55 @@ var CodexAppServerClient = class {
|
|
|
3616
3650
|
}
|
|
3617
3651
|
};
|
|
3618
3652
|
|
|
3653
|
+
// src/providers/codex/bridgeDeveloperInstructions.ts
|
|
3654
|
+
import { dirname as dirname6, join as join10 } from "node:path";
|
|
3655
|
+
import { fileURLToPath } from "node:url";
|
|
3656
|
+
function buildCodexBridgeInstructions(input = {}) {
|
|
3657
|
+
const apiBaseUrl = input.apiBaseUrl ?? defaultApiBaseUrl();
|
|
3658
|
+
const douyinScript = input.douyinScriptPath ?? defaultDouyinScriptPath();
|
|
3659
|
+
return [
|
|
3660
|
+
"\u4F60\u6B63\u8FD0\u884C\u5728\u4E00\u4E2A\u5FAE\u4FE1\u6865\u63A5\u73AF\u5883\u4E2D\uFF0C\u7528\u6237\u901A\u8FC7\u5FAE\u4FE1\u4E0E\u4F60\u5BF9\u8BDD\u3002",
|
|
3661
|
+
"",
|
|
3662
|
+
"## \u73AF\u5883\u8BF4\u660E",
|
|
3663
|
+
"- \u7528\u6237\u770B\u5230\u7684\u662F\u5FAE\u4FE1\u804A\u5929\u754C\u9762\uFF0C\u6CA1\u6709\u4EA4\u4E92\u5F0F\u5F39\u7A97\u6216\u6309\u94AE\uFF1B\u9700\u8981\u7528\u6237\u505A\u9009\u62E9\u65F6\u7528\u5E8F\u53F7\u5217\u8868\u3002",
|
|
3664
|
+
'- \u7528\u6237\u8BF4\u7684"\u53D1\u7ED9\u6211""\u53D1\u5230\u5FAE\u4FE1""\u53D1\u51FA\u6765""\u4F20\u7ED9\u6211"\uFF0C\u90FD\u662F\u6307\u628A\u6587\u4EF6\u53D1\u9001\u5230\u4ED6\u7684\u5FAE\u4FE1\u3002',
|
|
3665
|
+
"",
|
|
3666
|
+
"## \u53D1\u9001\u6587\u4EF6\u5230\u7528\u6237\u5FAE\u4FE1",
|
|
3667
|
+
"\u7528 shell \u8C03\u7528\u6865\u63A5\u5C42\u7684\u53D1\u9001\u7AEF\u70B9\u5373\u53EF\uFF08kind \u53D6 image / video / audio / file\uFF09\uFF1A",
|
|
3668
|
+
"```bash",
|
|
3669
|
+
`curl -sS -X POST ${apiBaseUrl}/api/channel/send-media \\`,
|
|
3670
|
+
" -H 'Content-Type: application/json' \\",
|
|
3671
|
+
` -d '{"kind":"video","filePath":"/absolute/path/to/file.mp4"}'`,
|
|
3672
|
+
"```",
|
|
3673
|
+
"- filePath \u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84\u3002",
|
|
3674
|
+
'- \u8FD4\u56DE {"ok":true} \u5373\u53D1\u9001\u6210\u529F\uFF1B\u5931\u8D25\u65F6\u628A\u9519\u8BEF\u539F\u6587\u544A\u8BC9\u7528\u6237\uFF0C\u5E76\u7ED9\u51FA\u6587\u4EF6\u672C\u5730\u8DEF\u5F84\u3002',
|
|
3675
|
+
"",
|
|
3676
|
+
"## \u6296\u97F3\u89C6\u9891\u4E0B\u8F7D",
|
|
3677
|
+
"\u5F53\u7528\u6237\u53D1\u6765\u542B\u6296\u97F3\u94FE\u63A5\uFF08v.douyin.com\u3001douyin.com\uFF09\u7684\u6D88\u606F\u6216\u5206\u4EAB\u6587\u6848\u65F6\uFF1A",
|
|
3678
|
+
'- \u7ACB\u5373\u6267\u884C\u4E0B\u8F7D\uFF0C\u4E0D\u8981\u53CD\u95EE\u7528\u6237"\u8981\u4E0B\u8F7D\u8FD8\u662F\u8981\u603B\u7ED3"\u2014\u2014\u4E0B\u8F7D\u5E76\u53D1\u9001\u5C31\u662F\u9ED8\u8BA4\u9884\u671F\u3002',
|
|
3679
|
+
"```bash",
|
|
3680
|
+
`node ${douyinScript} "<\u6296\u97F3\u94FE\u63A5\u6216\u6574\u6BB5\u5206\u4EAB\u6587\u6848>" --output /tmp`,
|
|
3681
|
+
"```",
|
|
3682
|
+
"- \u811A\u672C\u6210\u529F\u65F6\u8F93\u51FA `\u2705 \u5DF2\u4FDD\u5B58: <\u6587\u4EF6\u7EDD\u5BF9\u8DEF\u5F84>`\uFF1B\u53D6\u8BE5\u8DEF\u5F84\uFF0C\u6309\u4E0A\u9762\u7684\u65B9\u5F0F\u4EE5 kind=video \u53D1\u9001\u5230\u5FAE\u4FE1\u3002",
|
|
3683
|
+
"- \u652F\u6301\u77ED\u94FE\u3001\u5B8C\u6574\u94FE\u63A5\u3001\u5206\u4EAB\u6587\u6848\u3001\u7EAF aweme_id\uFF0C\u53EF\u4EE5\u628A\u6574\u6BB5\u6587\u6848\u539F\u6837\u4F20\u8FDB\u53BB\u3002",
|
|
3684
|
+
"- \u5206\u4EAB\u6D88\u606F\u5E38\u9644\u5E26\u5361\u7247\u7F29\u7565\u56FE\uFF0C\u52A0\u8F7D\u5931\u8D25\u65E0\u5173\u7D27\u8981\uFF0C\u76F4\u63A5\u5FFD\u7565\uFF0C\u4E0D\u8981\u56E0\u6B64\u6539\u804A\u56FE\u7247\u3002",
|
|
3685
|
+
'- \u4E0B\u8F7D\u5931\u8D25\u5148\u539F\u6837\u91CD\u8BD5\u4E00\u6B21\uFF1B\u4ECD\u5931\u8D25\u5C31\u4E00\u53E5\u8BDD\u544A\u8BC9\u7528\u6237"\u6296\u97F3\u4E0B\u8F7D\u5931\u8D25\uFF1A<\u539F\u56E0>"\uFF0C\u4E0D\u8981\u5C94\u5F00\u8BDD\u9898\u3002',
|
|
3686
|
+
"",
|
|
3687
|
+
"## \u884C\u4E3A\u51C6\u5219",
|
|
3688
|
+
'- \u7528\u6237\u7684"\u6211"\u5C31\u662F\u4ED6\u7684\u5FAE\u4FE1\uFF0C"\u53D1\u7ED9\u6211"\u5C31\u662F\u53D1\u5230\u5FAE\u4FE1\u3002',
|
|
3689
|
+
'- \u6267\u884C\u5B8C\u6210\u540E\u7B80\u77ED\u786E\u8BA4\uFF08\u5982"\u5DF2\u53D1\u9001"\uFF09\uFF0C\u4E0D\u9700\u8981\u5197\u957F\u89E3\u91CA\u3002',
|
|
3690
|
+
'- \u4F60\u751F\u6210\u4E86\u6587\u4EF6\uFF08\u56FE\u8868\u3001\u4EE3\u7801\u3001\u6587\u6863\u7B49\uFF09\u540E\uFF0C\u7528\u6237\u8BF4"\u53D1\u7ED9\u6211"\u5C31\u76F4\u63A5\u6309\u4E0A\u9762\u7684\u65B9\u5F0F\u53D1\u9001\u3002'
|
|
3691
|
+
].join("\n");
|
|
3692
|
+
}
|
|
3693
|
+
function defaultApiBaseUrl() {
|
|
3694
|
+
const port = process.env.BRIDGE_PORT ?? "8787";
|
|
3695
|
+
return `http://127.0.0.1:${port}`;
|
|
3696
|
+
}
|
|
3697
|
+
function defaultDouyinScriptPath() {
|
|
3698
|
+
const here2 = dirname6(fileURLToPath(import.meta.url));
|
|
3699
|
+
return join10(here2, "..", "mcp", "scripts", "douyin-download.mjs");
|
|
3700
|
+
}
|
|
3701
|
+
|
|
3619
3702
|
// src/providers/codex/codexInteractiveRunner.ts
|
|
3620
3703
|
var IDLE_SENTINEL2 = /* @__PURE__ */ Symbol("idle_timeout");
|
|
3621
3704
|
function withIdleTimeout(promise, ms) {
|
|
@@ -3680,6 +3763,21 @@ function readTurnId(value) {
|
|
|
3680
3763
|
if (typeof record.turnId === "string" && record.turnId) return record.turnId;
|
|
3681
3764
|
return void 0;
|
|
3682
3765
|
}
|
|
3766
|
+
function readTurnAgentMessages(value) {
|
|
3767
|
+
if (!value || typeof value !== "object") return [];
|
|
3768
|
+
const turn = value.turn;
|
|
3769
|
+
if (!turn || typeof turn !== "object") return [];
|
|
3770
|
+
const items = turn.items;
|
|
3771
|
+
if (!Array.isArray(items)) return [];
|
|
3772
|
+
const texts = [];
|
|
3773
|
+
for (const item of items) {
|
|
3774
|
+
if (!item || typeof item !== "object") continue;
|
|
3775
|
+
const record = item;
|
|
3776
|
+
if (record.type !== "agentMessage") continue;
|
|
3777
|
+
if (typeof record.text === "string" && record.text.trim()) texts.push(record.text);
|
|
3778
|
+
}
|
|
3779
|
+
return texts;
|
|
3780
|
+
}
|
|
3683
3781
|
function readTurnError(value) {
|
|
3684
3782
|
if (!value || typeof value !== "object") return void 0;
|
|
3685
3783
|
const record = value;
|
|
@@ -3702,10 +3800,12 @@ var CodexInteractiveRunner = class {
|
|
|
3702
3800
|
command;
|
|
3703
3801
|
syncThreadForResume;
|
|
3704
3802
|
idleTimeoutMs;
|
|
3803
|
+
bridgeInstructions;
|
|
3705
3804
|
constructor(input = {}) {
|
|
3706
3805
|
this.command = input.command;
|
|
3707
3806
|
this.syncThreadForResume = input.syncThreadForResume ?? syncCodexThreadForResume;
|
|
3708
3807
|
this.idleTimeoutMs = input.idleTimeoutMs ?? 18e4;
|
|
3808
|
+
this.bridgeInstructions = input.bridgeInstructions ?? buildCodexBridgeInstructions();
|
|
3709
3809
|
}
|
|
3710
3810
|
async startSession(input) {
|
|
3711
3811
|
const session = {
|
|
@@ -3734,6 +3834,7 @@ var CodexInteractiveRunner = class {
|
|
|
3734
3834
|
session.activeMessage = void 0;
|
|
3735
3835
|
session.activeTurnId = void 0;
|
|
3736
3836
|
session.turnClosed = false;
|
|
3837
|
+
session.emittedTextThisTurn = false;
|
|
3737
3838
|
session.turnCompletedPromise = new Promise((resolve2) => {
|
|
3738
3839
|
session.turnCompletedResolver = resolve2;
|
|
3739
3840
|
});
|
|
@@ -3741,7 +3842,9 @@ var CodexInteractiveRunner = class {
|
|
|
3741
3842
|
const resumed = await withIdleTimeout(client.request("thread/resume", {
|
|
3742
3843
|
threadId: session.threadId,
|
|
3743
3844
|
cwd: session.cwd,
|
|
3744
|
-
persistExtendedHistory: true
|
|
3845
|
+
persistExtendedHistory: true,
|
|
3846
|
+
// resume 也要带:恢复出来的会话同样跑在微信桥接环境里。
|
|
3847
|
+
developerInstructions: this.bridgeInstructions
|
|
3745
3848
|
}).catch(() => void 0), this.idleTimeoutMs);
|
|
3746
3849
|
if (resumed === IDLE_SENTINEL2) {
|
|
3747
3850
|
await this.handleIdleTimeout(session);
|
|
@@ -3755,7 +3858,8 @@ var CodexInteractiveRunner = class {
|
|
|
3755
3858
|
persistExtendedHistory: true,
|
|
3756
3859
|
experimentalRawEvents: true,
|
|
3757
3860
|
sandboxPolicy: { type: "disabled" },
|
|
3758
|
-
approvalMode: "never"
|
|
3861
|
+
approvalMode: "never",
|
|
3862
|
+
developerInstructions: this.bridgeInstructions
|
|
3759
3863
|
}), this.idleTimeoutMs);
|
|
3760
3864
|
if (started === IDLE_SENTINEL2) {
|
|
3761
3865
|
await this.handleIdleTimeout(session);
|
|
@@ -3880,6 +3984,7 @@ var CodexInteractiveRunner = class {
|
|
|
3880
3984
|
});
|
|
3881
3985
|
client.onNotification("turn/completed", (params) => {
|
|
3882
3986
|
this.flushActiveMessage(session);
|
|
3987
|
+
this.recoverTurnMessages(session, params);
|
|
3883
3988
|
const turnError = readTurnError(params);
|
|
3884
3989
|
if (turnError) this.enqueueEvent(session, { type: "error", error: turnError });
|
|
3885
3990
|
session.turnClosed = true;
|
|
@@ -3920,6 +4025,18 @@ var CodexInteractiveRunner = class {
|
|
|
3920
4025
|
this.enqueueEvent(session, { type: "text_delta", text: session.activeMessage.text });
|
|
3921
4026
|
this.enqueueEvent(session, { type: "message_done" });
|
|
3922
4027
|
session.activeMessage = void 0;
|
|
4028
|
+
session.emittedTextThisTurn = true;
|
|
4029
|
+
}
|
|
4030
|
+
// 瞬时(非流式)回合里 app-server 可能完全不发 item/agentMessage/delta,
|
|
4031
|
+
// 助手文本只出现在 turn/completed 的 items 中。此时若不兜底,回复会静默丢失。
|
|
4032
|
+
// 只在本轮一条文本都没发出时才补发——宁可少发,也不要重复刷屏、白耗微信配额。
|
|
4033
|
+
recoverTurnMessages(session, params) {
|
|
4034
|
+
if (session.emittedTextThisTurn) return;
|
|
4035
|
+
for (const text of readTurnAgentMessages(params)) {
|
|
4036
|
+
this.enqueueEvent(session, { type: "text_delta", text });
|
|
4037
|
+
this.enqueueEvent(session, { type: "message_done" });
|
|
4038
|
+
session.emittedTextThisTurn = true;
|
|
4039
|
+
}
|
|
3923
4040
|
}
|
|
3924
4041
|
enqueueEvent(session, event) {
|
|
3925
4042
|
session.pendingEvents.push(event);
|
|
@@ -4363,7 +4480,19 @@ var MessageRouter = class _MessageRouter {
|
|
|
4363
4480
|
}
|
|
4364
4481
|
}
|
|
4365
4482
|
async handleMessage(message) {
|
|
4366
|
-
|
|
4483
|
+
try {
|
|
4484
|
+
return await this.withSessionChangeNotification(() => this.handleMessageInner(message));
|
|
4485
|
+
} catch (error) {
|
|
4486
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
4487
|
+
console.error("[bridge] handleMessage failed:", message.chatId, error);
|
|
4488
|
+
await this.sendToChat({
|
|
4489
|
+
chatId: message.chatId,
|
|
4490
|
+
kind: "status",
|
|
4491
|
+
text: `\u26A0\uFE0F \u6865\u63A5\u5C42\u5185\u90E8\u9519\u8BEF\uFF1A${detail}
|
|
4492
|
+
\u53EF\u91CD\u53D1\u6D88\u606F\u91CD\u8BD5\uFF1B\u82E5\u6301\u7EED\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5 provider \u662F\u5426\u53EF\u7528\uFF08claude-codex-wechat doctor\uFF09\u3002`
|
|
4493
|
+
}).catch(() => void 0);
|
|
4494
|
+
return { status: "accepted" };
|
|
4495
|
+
}
|
|
4367
4496
|
}
|
|
4368
4497
|
async handleMessageInner(message) {
|
|
4369
4498
|
let user = this.options.resolveUser(message);
|
|
@@ -4569,6 +4698,7 @@ var MessageRouter = class _MessageRouter {
|
|
|
4569
4698
|
});
|
|
4570
4699
|
const isLive = () => this.activeGenerations.get(message.chatId)?.genId === genId && (this.latestMutatingSeq.get(message.chatId) ?? 0) <= seq;
|
|
4571
4700
|
let bufferedText = "";
|
|
4701
|
+
let deliveredSomething = false;
|
|
4572
4702
|
const typingMinIntervalMs = this.options.typingKeepaliveMs ?? 5e3;
|
|
4573
4703
|
let lastTypingAt = Date.now();
|
|
4574
4704
|
await this.options.channel.setTyping?.(message.chatId, true);
|
|
@@ -4593,6 +4723,7 @@ var MessageRouter = class _MessageRouter {
|
|
|
4593
4723
|
if (event.type === "message_done" && bufferedText.trim()) {
|
|
4594
4724
|
await this.sendToChat({ chatId: message.chatId, kind: "text", text: bufferedText });
|
|
4595
4725
|
bufferedText = "";
|
|
4726
|
+
deliveredSomething = true;
|
|
4596
4727
|
}
|
|
4597
4728
|
if (event.type === "choice_prompt" && event.labels.length > 0) {
|
|
4598
4729
|
this.pendingChoices.set(message.chatId, { sessionId: session.id, labels: event.labels, multiSelect: event.multiSelect });
|
|
@@ -4622,11 +4753,20 @@ var MessageRouter = class _MessageRouter {
|
|
|
4622
4753
|
kind: "status",
|
|
4623
4754
|
text: errorText
|
|
4624
4755
|
});
|
|
4756
|
+
deliveredSomething = true;
|
|
4625
4757
|
}
|
|
4626
4758
|
}
|
|
4627
4759
|
if (isLive() && bufferedText.trim()) {
|
|
4628
4760
|
await this.sendToChat({ chatId: message.chatId, kind: "text", text: bufferedText });
|
|
4629
4761
|
bufferedText = "";
|
|
4762
|
+
deliveredSomething = true;
|
|
4763
|
+
}
|
|
4764
|
+
if (isLive() && !deliveredSomething) {
|
|
4765
|
+
await this.sendToChat({
|
|
4766
|
+
chatId: message.chatId,
|
|
4767
|
+
kind: "status",
|
|
4768
|
+
text: "\u26A0\uFE0F \u672C\u8F6E\u6CA1\u6709\u8FD4\u56DE\u4EFB\u4F55\u5185\u5BB9\uFF08provider \u5DF2\u7ED3\u675F\u4F46\u672A\u4EA7\u51FA\u56DE\u590D\uFF09\u3002\u53EF\u91CD\u53D1\u6D88\u606F\u91CD\u8BD5\u3002"
|
|
4769
|
+
});
|
|
4630
4770
|
}
|
|
4631
4771
|
if (isLive()) {
|
|
4632
4772
|
const finalBinding = this.conversation.getCurrent();
|
|
@@ -4643,6 +4783,10 @@ var MessageRouter = class _MessageRouter {
|
|
|
4643
4783
|
const stillMine = this.activeGenerations.get(message.chatId)?.genId === genId;
|
|
4644
4784
|
if (stillMine) this.activeGenerations.delete(message.chatId);
|
|
4645
4785
|
await Promise.resolve(this.options.outboundGate?.finalize?.(message.chatId)).catch(() => void 0);
|
|
4786
|
+
const quotaNotice = this.options.outboundGate?.takeQuotaExhaustedNotice?.(message.chatId);
|
|
4787
|
+
if (quotaNotice) {
|
|
4788
|
+
await this.sendToChat({ chatId: message.chatId, kind: "status", text: quotaNotice }).catch(() => void 0);
|
|
4789
|
+
}
|
|
4646
4790
|
if (stillMine) await this.options.channel.setTyping?.(message.chatId, false);
|
|
4647
4791
|
}
|
|
4648
4792
|
}
|
|
@@ -4718,6 +4862,7 @@ var MessageRouter = class _MessageRouter {
|
|
|
4718
4862
|
providerId,
|
|
4719
4863
|
cwd
|
|
4720
4864
|
});
|
|
4865
|
+
await Promise.resolve(this.options.outboundGate?.discardPending?.(chatId)).catch(() => void 0);
|
|
4721
4866
|
await this.sendToChat({
|
|
4722
4867
|
chatId,
|
|
4723
4868
|
kind: "status",
|
|
@@ -5345,12 +5490,12 @@ var RelayTunnelRouter = class {
|
|
|
5345
5490
|
|
|
5346
5491
|
// src/shared/version.ts
|
|
5347
5492
|
import { readFileSync as readFileSync8 } from "node:fs";
|
|
5348
|
-
import { dirname as
|
|
5349
|
-
import { fileURLToPath } from "node:url";
|
|
5493
|
+
import { dirname as dirname7, join as join11 } from "node:path";
|
|
5494
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
5350
5495
|
function readClientVersion() {
|
|
5351
5496
|
try {
|
|
5352
|
-
const here2 =
|
|
5353
|
-
const pkg = JSON.parse(readFileSync8(
|
|
5497
|
+
const here2 = dirname7(fileURLToPath2(import.meta.url));
|
|
5498
|
+
const pkg = JSON.parse(readFileSync8(join11(here2, "..", "..", "package.json"), "utf8"));
|
|
5354
5499
|
return typeof pkg.version === "string" && pkg.version ? pkg.version : "0.0.0";
|
|
5355
5500
|
} catch {
|
|
5356
5501
|
return "0.0.0";
|
|
@@ -5369,7 +5514,7 @@ function readUpdateStatusBestEffort(configPath) {
|
|
|
5369
5514
|
function createDaemonServer(options = {}) {
|
|
5370
5515
|
const app = Fastify({ logger: true });
|
|
5371
5516
|
const events = new BridgeEventHub();
|
|
5372
|
-
const configPath = options.configPath ?? process.env.BRIDGE_CONFIG ??
|
|
5517
|
+
const configPath = options.configPath ?? process.env.BRIDGE_CONFIG ?? join12(mkdtempSync(join12(tmpdir2(), "claude-codex-wechat-")), "config.json");
|
|
5373
5518
|
const persistedConfig = loadBridgeConfig(configPath);
|
|
5374
5519
|
const bridgeDefaults = {
|
|
5375
5520
|
defaultProvider: options.bridgeDefaults?.defaultProvider ?? persistedConfig.bridge?.defaultProvider ?? "claude-code",
|
|
@@ -5406,10 +5551,10 @@ function createDaemonServer(options = {}) {
|
|
|
5406
5551
|
const lastProviderSessions = new LastProviderSessionStore(configPath);
|
|
5407
5552
|
const sessionBindingMatch = /* @__PURE__ */ new Map();
|
|
5408
5553
|
const bridgePort = process.env.BRIDGE_PORT ?? "8787";
|
|
5409
|
-
const mcpConfigPath =
|
|
5410
|
-
const serverDir =
|
|
5411
|
-
const mcpMediaServerTs =
|
|
5412
|
-
const mcpMediaServerJs =
|
|
5554
|
+
const mcpConfigPath = join12(dirname8(configPath), "mcp-media.json");
|
|
5555
|
+
const serverDir = dirname8(fileURLToPath3(import.meta.url));
|
|
5556
|
+
const mcpMediaServerTs = join12(serverDir, "..", "mcp", "mediaServer.ts");
|
|
5557
|
+
const mcpMediaServerJs = join12(serverDir, "..", "mcp", "mediaServer.js");
|
|
5413
5558
|
const useTs = existsSync9(mcpMediaServerTs) && !existsSync9(mcpMediaServerJs);
|
|
5414
5559
|
const mcpCommand = useTs ? "tsx" : "node";
|
|
5415
5560
|
const mcpArgs = [useTs ? mcpMediaServerTs : mcpMediaServerJs];
|
|
@@ -5444,7 +5589,7 @@ function createDaemonServer(options = {}) {
|
|
|
5444
5589
|
});
|
|
5445
5590
|
}
|
|
5446
5591
|
const weixinStateStore = configPath ? new FileWeixinStateStore(configPath) : void 0;
|
|
5447
|
-
const weixinMediaDir = configPath ?
|
|
5592
|
+
const weixinMediaDir = configPath ? join12(dirname8(configPath), "media") : void 0;
|
|
5448
5593
|
const managedWechatChannel = options.channel ? null : new ManagedWeixinDirectAdapter(options.wechat, weixinStateStore, weixinMediaDir);
|
|
5449
5594
|
const channel = options.channel ?? managedWechatChannel;
|
|
5450
5595
|
const weixinOutboundGate = managedWechatChannel && weixinStateStore ? new WeixinOutboundGate({
|
|
@@ -5713,7 +5858,20 @@ function createUpdateChecker(opts) {
|
|
|
5713
5858
|
}
|
|
5714
5859
|
|
|
5715
5860
|
// src/daemon/bootstrap.ts
|
|
5861
|
+
var crashLoggingInstalled = false;
|
|
5862
|
+
function installCrashLogging() {
|
|
5863
|
+
if (crashLoggingInstalled) return;
|
|
5864
|
+
crashLoggingInstalled = true;
|
|
5865
|
+
process.on("unhandledRejection", (reason) => {
|
|
5866
|
+
console.error("[bridge] unhandled rejection:", reason);
|
|
5867
|
+
});
|
|
5868
|
+
process.on("uncaughtException", (error) => {
|
|
5869
|
+
console.error("[bridge] uncaught exception, exiting:", error);
|
|
5870
|
+
process.exit(1);
|
|
5871
|
+
});
|
|
5872
|
+
}
|
|
5716
5873
|
async function startDaemon(options) {
|
|
5874
|
+
installCrashLogging();
|
|
5717
5875
|
const port = options.port ?? Number(process.env.BRIDGE_PORT ?? 8787);
|
|
5718
5876
|
const host = options.host ?? "0.0.0.0";
|
|
5719
5877
|
const configPath = options.configPath ?? process.env.BRIDGE_CONFIG ?? defaultConfigPath();
|
|
@@ -5789,7 +5947,7 @@ async function resolveProviderCommands(providers) {
|
|
|
5789
5947
|
|
|
5790
5948
|
// src/daemon/staticFrontend.ts
|
|
5791
5949
|
import { readFileSync as readFileSync9 } from "node:fs";
|
|
5792
|
-
import { join as
|
|
5950
|
+
import { join as join13 } from "node:path";
|
|
5793
5951
|
import fastifyStatic from "@fastify/static";
|
|
5794
5952
|
function attachStaticFrontend(webRoot2) {
|
|
5795
5953
|
return async (app) => {
|
|
@@ -5797,7 +5955,7 @@ function attachStaticFrontend(webRoot2) {
|
|
|
5797
5955
|
root: webRoot2,
|
|
5798
5956
|
wildcard: false
|
|
5799
5957
|
});
|
|
5800
|
-
const indexHtml = readFileSync9(
|
|
5958
|
+
const indexHtml = readFileSync9(join13(webRoot2, "index.html"), "utf8");
|
|
5801
5959
|
app.setNotFoundHandler((request, reply) => {
|
|
5802
5960
|
if (request.url.startsWith("/api")) {
|
|
5803
5961
|
reply.code(404).send({ error: "not_found" });
|
|
@@ -5812,7 +5970,7 @@ function attachStaticFrontend(webRoot2) {
|
|
|
5812
5970
|
import { existsSync as existsSync10, statSync, openSync as openSync2, closeSync as closeSync2, watchFile } from "node:fs";
|
|
5813
5971
|
import { mkdir as mkdir4, readFile as readFile6, rm as rm2, writeFile as writeFile4 } from "node:fs/promises";
|
|
5814
5972
|
import { homedir as homedir8 } from "node:os";
|
|
5815
|
-
import { dirname as
|
|
5973
|
+
import { dirname as dirname9, join as join14 } from "node:path";
|
|
5816
5974
|
import { createReadStream } from "node:fs";
|
|
5817
5975
|
import { execFile, spawn as spawn7 } from "node:child_process";
|
|
5818
5976
|
import { createInterface } from "node:readline";
|
|
@@ -5989,37 +6147,37 @@ async function readServiceStatus(context) {
|
|
|
5989
6147
|
throw new Error("service_status_not_supported_on_this_platform");
|
|
5990
6148
|
}
|
|
5991
6149
|
function buildLaunchdSpec(context) {
|
|
5992
|
-
const stateDir =
|
|
6150
|
+
const stateDir = join14(homedir8(), ".claude-codex-wechat");
|
|
5993
6151
|
return {
|
|
5994
6152
|
label: "com.claude-codex-wechat",
|
|
5995
|
-
plistPath:
|
|
6153
|
+
plistPath: join14(homedir8(), "Library", "LaunchAgents", "com.claude-codex-wechat.plist"),
|
|
5996
6154
|
programArgs: [context.nodePath ?? process.execPath, context.cliEntrypointPath, "__daemon"],
|
|
5997
6155
|
workingDirectory: homedir8(),
|
|
5998
|
-
stdoutPath:
|
|
5999
|
-
stderrPath:
|
|
6156
|
+
stdoutPath: join14(stateDir, "logs", "service.stdout.log"),
|
|
6157
|
+
stderrPath: join14(stateDir, "logs", "service.stderr.log"),
|
|
6000
6158
|
environment: buildServiceEnvironment(context)
|
|
6001
6159
|
};
|
|
6002
6160
|
}
|
|
6003
6161
|
function buildSystemdUserSpec(context) {
|
|
6004
|
-
const stateDir =
|
|
6162
|
+
const stateDir = join14(homedir8(), ".claude-codex-wechat");
|
|
6005
6163
|
return {
|
|
6006
6164
|
unitName: "claude-codex-wechat.service",
|
|
6007
|
-
unitPath:
|
|
6165
|
+
unitPath: join14(homedir8(), ".config", "systemd", "user", "claude-codex-wechat.service"),
|
|
6008
6166
|
execStart: [context.nodePath ?? process.execPath, context.cliEntrypointPath, "__daemon"],
|
|
6009
6167
|
workingDirectory: homedir8(),
|
|
6010
|
-
stdoutPath:
|
|
6011
|
-
stderrPath:
|
|
6168
|
+
stdoutPath: join14(stateDir, "logs", "service.stdout.log"),
|
|
6169
|
+
stderrPath: join14(stateDir, "logs", "service.stderr.log"),
|
|
6012
6170
|
environment: buildServiceEnvironment(context)
|
|
6013
6171
|
};
|
|
6014
6172
|
}
|
|
6015
6173
|
function buildProcessSpec(context) {
|
|
6016
|
-
const stateDir =
|
|
6174
|
+
const stateDir = join14(homedir8(), ".claude-codex-wechat");
|
|
6017
6175
|
return {
|
|
6018
|
-
pidPath:
|
|
6176
|
+
pidPath: join14(stateDir, "service.pid"),
|
|
6019
6177
|
command: [context.nodePath ?? process.execPath, context.cliEntrypointPath, "__daemon"],
|
|
6020
6178
|
workingDirectory: homedir8(),
|
|
6021
|
-
stdoutPath:
|
|
6022
|
-
stderrPath:
|
|
6179
|
+
stdoutPath: join14(stateDir, "logs", "service.stdout.log"),
|
|
6180
|
+
stderrPath: join14(stateDir, "logs", "service.stderr.log"),
|
|
6023
6181
|
environment: buildServiceEnvironment(context)
|
|
6024
6182
|
};
|
|
6025
6183
|
}
|
|
@@ -6050,8 +6208,8 @@ function buildServiceEnvironment(context) {
|
|
|
6050
6208
|
return env;
|
|
6051
6209
|
}
|
|
6052
6210
|
async function applyLaunchdService(spec) {
|
|
6053
|
-
await mkdir4(
|
|
6054
|
-
await mkdir4(
|
|
6211
|
+
await mkdir4(dirname9(spec.plistPath), { recursive: true });
|
|
6212
|
+
await mkdir4(dirname9(spec.stdoutPath), { recursive: true });
|
|
6055
6213
|
await writeFile4(spec.plistPath, renderLaunchdPlist(spec), "utf8");
|
|
6056
6214
|
const guiDomain = `gui/${process.getuid?.() ?? 0}`;
|
|
6057
6215
|
await runLaunchctl(["bootout", guiDomain, spec.plistPath]).catch(() => void 0);
|
|
@@ -6059,8 +6217,8 @@ async function applyLaunchdService(spec) {
|
|
|
6059
6217
|
await runLaunchctl(["kickstart", "-k", `${guiDomain}/${spec.label}`]);
|
|
6060
6218
|
}
|
|
6061
6219
|
async function writeSystemdUnit(spec) {
|
|
6062
|
-
await mkdir4(
|
|
6063
|
-
await mkdir4(
|
|
6220
|
+
await mkdir4(dirname9(spec.unitPath), { recursive: true });
|
|
6221
|
+
await mkdir4(dirname9(spec.stdoutPath), { recursive: true });
|
|
6064
6222
|
await writeFile4(spec.unitPath, renderSystemdUnit(spec), "utf8");
|
|
6065
6223
|
}
|
|
6066
6224
|
function renderLaunchdPlist(spec) {
|
|
@@ -6154,7 +6312,7 @@ async function readPidFile(pidPath) {
|
|
|
6154
6312
|
}
|
|
6155
6313
|
}
|
|
6156
6314
|
async function writePidFile(pidPath, pid) {
|
|
6157
|
-
await mkdir4(
|
|
6315
|
+
await mkdir4(dirname9(pidPath), { recursive: true });
|
|
6158
6316
|
await writeFile4(pidPath, `${pid}
|
|
6159
6317
|
`, "utf8");
|
|
6160
6318
|
}
|
|
@@ -6169,7 +6327,7 @@ async function killPid(pid) {
|
|
|
6169
6327
|
process.kill(pid, "SIGTERM");
|
|
6170
6328
|
}
|
|
6171
6329
|
async function spawnDetachedDaemon(spec) {
|
|
6172
|
-
await mkdir4(
|
|
6330
|
+
await mkdir4(dirname9(spec.stdoutPath), { recursive: true });
|
|
6173
6331
|
const outFd = openSync2(spec.stdoutPath, "a");
|
|
6174
6332
|
const errFd = openSync2(spec.stderrPath, "a");
|
|
6175
6333
|
try {
|
|
@@ -6294,8 +6452,8 @@ async function performUpgrade(deps) {
|
|
|
6294
6452
|
|
|
6295
6453
|
// src/cli.ts
|
|
6296
6454
|
var execFileAsync2 = promisify2(execFile2);
|
|
6297
|
-
var here =
|
|
6298
|
-
var webRoot =
|
|
6455
|
+
var here = dirname10(fileURLToPath4(import.meta.url));
|
|
6456
|
+
var webRoot = join15(here, "..", "web");
|
|
6299
6457
|
async function main() {
|
|
6300
6458
|
const command = process.argv[2];
|
|
6301
6459
|
switch (command) {
|
|
@@ -6494,7 +6652,7 @@ async function cmdUninstall() {
|
|
|
6494
6652
|
}
|
|
6495
6653
|
function createServiceContext() {
|
|
6496
6654
|
return {
|
|
6497
|
-
cliEntrypointPath:
|
|
6655
|
+
cliEntrypointPath: fileURLToPath4(import.meta.url),
|
|
6498
6656
|
nodePath: process.execPath,
|
|
6499
6657
|
configPath: process.env.BRIDGE_CONFIG ?? defaultConfigPath(),
|
|
6500
6658
|
port: Number(process.env.BRIDGE_PORT ?? 8787)
|