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 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
- # 2. 打开管理页,用微信扫码登录完成绑定
49
- # 浏览器访问 http://127.0.0.1:8787
50
+ **2. 绑定微信** — 浏览器打开 <http://127.0.0.1:8787>,用微信扫码登录。
50
51
 
51
- # 3. 绑定成功后,直接在微信里给机器人发消息即可
52
- ```
52
+ **3. 开始用** — 绑定成功后,直接在微信里给机器人发消息即可。
53
53
 
54
54
  搞定。之后不管电脑锁屏还是你人在外面,只要开发机开着,就能用微信指挥它。
55
55
 
@@ -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 dirname9, join as join14 } from "node:path";
9
- import { fileURLToPath as fileURLToPath3 } from "node:url";
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 dirname7, join as join11 } from "node:path";
18
- import { fileURLToPath as fileURLToPath2 } from "node:url";
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
- store.enqueueOutbound(chatId, message);
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
- return this.withSessionChangeNotification(() => this.handleMessageInner(message));
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 dirname6, join as join10 } from "node:path";
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 = dirname6(fileURLToPath(import.meta.url));
5353
- const pkg = JSON.parse(readFileSync8(join10(here2, "..", "..", "package.json"), "utf8"));
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 ?? join11(mkdtempSync(join11(tmpdir2(), "claude-codex-wechat-")), "config.json");
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 = join11(dirname7(configPath), "mcp-media.json");
5410
- const serverDir = dirname7(fileURLToPath2(import.meta.url));
5411
- const mcpMediaServerTs = join11(serverDir, "..", "mcp", "mediaServer.ts");
5412
- const mcpMediaServerJs = join11(serverDir, "..", "mcp", "mediaServer.js");
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 ? join11(dirname7(configPath), "media") : void 0;
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 join12 } from "node:path";
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(join12(webRoot2, "index.html"), "utf8");
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 dirname8, join as join13 } from "node:path";
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 = join13(homedir8(), ".claude-codex-wechat");
6150
+ const stateDir = join14(homedir8(), ".claude-codex-wechat");
5993
6151
  return {
5994
6152
  label: "com.claude-codex-wechat",
5995
- plistPath: join13(homedir8(), "Library", "LaunchAgents", "com.claude-codex-wechat.plist"),
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: join13(stateDir, "logs", "service.stdout.log"),
5999
- stderrPath: join13(stateDir, "logs", "service.stderr.log"),
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 = join13(homedir8(), ".claude-codex-wechat");
6162
+ const stateDir = join14(homedir8(), ".claude-codex-wechat");
6005
6163
  return {
6006
6164
  unitName: "claude-codex-wechat.service",
6007
- unitPath: join13(homedir8(), ".config", "systemd", "user", "claude-codex-wechat.service"),
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: join13(stateDir, "logs", "service.stdout.log"),
6011
- stderrPath: join13(stateDir, "logs", "service.stderr.log"),
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 = join13(homedir8(), ".claude-codex-wechat");
6174
+ const stateDir = join14(homedir8(), ".claude-codex-wechat");
6017
6175
  return {
6018
- pidPath: join13(stateDir, "service.pid"),
6176
+ pidPath: join14(stateDir, "service.pid"),
6019
6177
  command: [context.nodePath ?? process.execPath, context.cliEntrypointPath, "__daemon"],
6020
6178
  workingDirectory: homedir8(),
6021
- stdoutPath: join13(stateDir, "logs", "service.stdout.log"),
6022
- stderrPath: join13(stateDir, "logs", "service.stderr.log"),
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(dirname8(spec.plistPath), { recursive: true });
6054
- await mkdir4(dirname8(spec.stdoutPath), { recursive: true });
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(dirname8(spec.unitPath), { recursive: true });
6063
- await mkdir4(dirname8(spec.stdoutPath), { recursive: true });
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(dirname8(pidPath), { recursive: true });
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(dirname8(spec.stdoutPath), { recursive: true });
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 = dirname9(fileURLToPath3(import.meta.url));
6298
- var webRoot = join14(here, "..", "web");
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: fileURLToPath3(import.meta.url),
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)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-codex-wechat",
3
- "version": "0.1.53",
3
+ "version": "0.1.55",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@11.5.0",