claude-codex-wechat 0.1.40 → 0.1.44

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.
@@ -639,6 +639,16 @@ async function hasClaudeHistoryDisplay(input) {
639
639
  }
640
640
  return false;
641
641
  }
642
+ async function getClaudeNativeVersion(input) {
643
+ const sessionPath = await findRecoverableClaudeSessionPath(input.sessionId, input.env ?? process.env);
644
+ if (!sessionPath) return null;
645
+ const metadata = await stat2(sessionPath).catch(() => null);
646
+ if (!metadata?.isFile()) return null;
647
+ return {
648
+ fingerprint: `claude:${input.sessionId}:${Math.trunc(metadata.mtimeMs)}:${metadata.size}`,
649
+ observedAt: Date.now()
650
+ };
651
+ }
642
652
  async function readClaudeSessionMetadata(filePath) {
643
653
  const content = await readFile2(filePath, "utf8");
644
654
  let aiTitle;
@@ -865,6 +875,16 @@ async function findRecoverableCodexSessionPath(sessionId, env = process.env) {
865
875
  }
866
876
  return await walk(root, 0);
867
877
  }
878
+ async function getCodexNativeVersion(input) {
879
+ const sessionPath = await findRecoverableCodexSessionPath(input.sessionId, input.env ?? process.env);
880
+ if (!sessionPath) return null;
881
+ const metadata = await stat3(sessionPath).catch(() => null);
882
+ if (!metadata?.isFile()) return null;
883
+ return {
884
+ fingerprint: `codex:${input.sessionId}:${Math.trunc(metadata.mtimeMs)}:${metadata.size}`,
885
+ observedAt: Date.now()
886
+ };
887
+ }
868
888
  async function readCodexSessionIndex(env) {
869
889
  const indexPath = join4(env.CODEX_HOME || join4(env.HOME || homedir3(), ".codex"), "session_index.jsonl");
870
890
  const index = /* @__PURE__ */ new Map();
@@ -2786,7 +2806,7 @@ function isTemporaryShim(candidate) {
2786
2806
  const tempDir = tmpdir();
2787
2807
  return candidate.startsWith(tempDir);
2788
2808
  }
2789
- async function findExecutable(command) {
2809
+ async function findExecutable(command, pathEnv) {
2790
2810
  const extensions = executableExtensions();
2791
2811
  if (command.includes("/") || command.includes("\\")) {
2792
2812
  for (const ext of extensions) {
@@ -2794,7 +2814,7 @@ async function findExecutable(command) {
2794
2814
  }
2795
2815
  return void 0;
2796
2816
  }
2797
- const dirs = (process.env.PATH ?? "").split(delimiter).filter(Boolean);
2817
+ const dirs = (pathEnv ?? "").split(delimiter).filter(Boolean);
2798
2818
  let fallback;
2799
2819
  for (const dir of dirs) {
2800
2820
  for (const ext of extensions) {
@@ -2836,6 +2856,71 @@ function statePath(filename) {
2836
2856
  return join9(tmpdir(), filename);
2837
2857
  }
2838
2858
 
2859
+ // src/shared/loginShellEnv.ts
2860
+ import { spawnSync } from "node:child_process";
2861
+ import { delimiter as delimiter2 } from "node:path";
2862
+ var DELIMITER = "__CCW_LOGIN_ENV_DELIM_5f3a2b7c__";
2863
+ function parseEnvBlock(block) {
2864
+ const env = {};
2865
+ let currentKey = null;
2866
+ const normalized = block.replace(/\n$/, "");
2867
+ for (const line of normalized.split("\n")) {
2868
+ const match = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/.exec(line);
2869
+ if (match) {
2870
+ currentKey = match[1];
2871
+ env[currentKey] = match[2];
2872
+ } else if (currentKey) {
2873
+ env[currentKey] = `${env[currentKey]}
2874
+ ${line}`;
2875
+ }
2876
+ }
2877
+ return env;
2878
+ }
2879
+ function captureLoginShellEnv(deps = {}) {
2880
+ const platform = deps.platform ?? process.platform;
2881
+ if (platform === "win32") return null;
2882
+ const run = deps.run ?? spawnSync;
2883
+ const shell = deps.shell ?? process.env.SHELL ?? "/bin/zsh";
2884
+ const timeoutMs = deps.timeoutMs ?? 5e3;
2885
+ const command = `printf %s '${DELIMITER}'; env; printf %s '${DELIMITER}'`;
2886
+ try {
2887
+ const result = run(shell, ["-l", "-i", "-c", command], {
2888
+ timeout: timeoutMs,
2889
+ encoding: "utf8",
2890
+ maxBuffer: 8 * 1024 * 1024
2891
+ });
2892
+ const stdout = typeof result.stdout === "string" ? result.stdout : "";
2893
+ const start = stdout.indexOf(DELIMITER);
2894
+ const end = stdout.indexOf(DELIMITER, start + DELIMITER.length);
2895
+ if (start === -1 || end === -1 || end <= start) return null;
2896
+ const block = stdout.slice(start + DELIMITER.length, end);
2897
+ const env = parseEnvBlock(block);
2898
+ if (!env.PATH) return null;
2899
+ return env;
2900
+ } catch {
2901
+ return null;
2902
+ }
2903
+ }
2904
+ var cache;
2905
+ function resolveLoginShellEnv() {
2906
+ if (cache !== void 0) return cache;
2907
+ cache = captureLoginShellEnv();
2908
+ if (cache === null && process.platform !== "win32") {
2909
+ console.warn(
2910
+ "[claude-codex-wechat] \u7EC8\u7AEF\u73AF\u5883\u590D\u523B\u5931\u8D25\uFF0C\u5DF2\u9000\u56DE process.env\uFF08PATH/\u51ED\u8BC1\u53EF\u80FD\u4E0E\u7EC8\u7AEF\u4E0D\u4E00\u81F4\uFF09\u3002\u82E5 provider \u62A5\u300C\u627E\u4E0D\u5230\u300D\u6216\u300C\u4EE4\u724C\u5DF2\u8FC7\u671F\u300D\uFF0C\u8BF7\u4ECE\u4EA4\u4E92\u7EC8\u7AEF\u91CD\u542F daemon\u3002"
2911
+ );
2912
+ }
2913
+ return cache;
2914
+ }
2915
+ function resolveTerminalSearchPath() {
2916
+ return mergeSearchPath(resolveLoginShellEnv()?.PATH, process.env.PATH);
2917
+ }
2918
+ function mergeSearchPath(primary, fallback) {
2919
+ if (!primary) return fallback;
2920
+ if (!fallback) return primary;
2921
+ return `${primary}${delimiter2}${fallback}`;
2922
+ }
2923
+
2839
2924
  // src/providers/claude-code/assistantContent.ts
2840
2925
  function extractAssistantBlocks(message) {
2841
2926
  if (!message || typeof message !== "object") return [];
@@ -2950,12 +3035,12 @@ var BRIDGE_APPEND_SYSTEM_PROMPT = [
2950
3035
 
2951
3036
  // src/providers/claude-code/claudeCapabilities.ts
2952
3037
  import { spawn as spawn2 } from "node:child_process";
2953
- var cache = /* @__PURE__ */ new Map();
3038
+ var cache2 = /* @__PURE__ */ new Map();
2954
3039
  var probeAppendSystemPromptSupport = (command) => {
2955
- const cached = cache.get(command);
3040
+ const cached = cache2.get(command);
2956
3041
  if (cached) return cached;
2957
3042
  const result = runHelpProbe(command);
2958
- cache.set(command, result);
3043
+ cache2.set(command, result);
2959
3044
  return result;
2960
3045
  };
2961
3046
  function runHelpProbe(command) {
@@ -3204,7 +3289,7 @@ function parseJsonLine(line) {
3204
3289
  function defaultClaudeStreamSpawner(call, opts = {}) {
3205
3290
  const stderrCapBytes = opts.stderrCapBytes ?? 64 * 1024;
3206
3291
  const maxLineBytes = opts.maxLineBytes ?? 10 * 1024 * 1024;
3207
- const child = spawn3(call.command, call.args, { cwd: expandTilde(call.cwd) ?? call.cwd, stdio: ["pipe", "pipe", "pipe"], shell: useShellForCli() });
3292
+ const child = spawn3(call.command, call.args, { cwd: expandTilde(call.cwd) ?? call.cwd, stdio: ["pipe", "pipe", "pipe"], shell: useShellForCli(), env: resolveLoginShellEnv() ?? process.env });
3208
3293
  let stderr = "";
3209
3294
  let buffer = "";
3210
3295
  const queue = [];
@@ -3299,6 +3384,12 @@ var ClaudeCodeProvider = class {
3299
3384
  async listRecoverableSessions() {
3300
3385
  return await listRecoverableClaudeSessions();
3301
3386
  }
3387
+ async getNativeVersion(input) {
3388
+ return await getClaudeNativeVersion({
3389
+ sessionId: input.providerSessionId,
3390
+ cwd: input.cwd
3391
+ });
3392
+ }
3302
3393
  async attachSession(input) {
3303
3394
  return await this.options.runner.startSession({
3304
3395
  bridgeSessionId: input.bridgeSessionId,
@@ -3323,7 +3414,8 @@ var CodexAppServerClient = class {
3323
3414
  this.child = spawn4(command, ["app-server", "--listen", "stdio://"], {
3324
3415
  cwd: input.cwd,
3325
3416
  stdio: ["pipe", "pipe", "pipe"],
3326
- shell: useShellForCli()
3417
+ shell: useShellForCli(),
3418
+ env: resolveLoginShellEnv() ?? process.env
3327
3419
  });
3328
3420
  this.child.stdout.on("data", (chunk) => this.onStdout(String(chunk)));
3329
3421
  this.child.stderr.on("data", () => {
@@ -3784,6 +3876,12 @@ var CodexProvider = class {
3784
3876
  async listRecoverableSessions() {
3785
3877
  return await listRecoverableCodexSessions();
3786
3878
  }
3879
+ async getNativeVersion(input) {
3880
+ return await getCodexNativeVersion({
3881
+ sessionId: input.providerSessionId,
3882
+ cwd: input.cwd
3883
+ });
3884
+ }
3787
3885
  async attachSession(input) {
3788
3886
  return await this.options.runner.startSession({
3789
3887
  bridgeSessionId: input.bridgeSessionId,
@@ -3954,6 +4052,9 @@ function parseBridgeCommand(input) {
3954
4052
  if (command === "/resume") {
3955
4053
  return { kind: "resume_session", ref: first ?? "" };
3956
4054
  }
4055
+ if (command === "/reload") {
4056
+ return { kind: "reload_session" };
4057
+ }
3957
4058
  if (command === "/new") {
3958
4059
  if (!first) return { kind: "new_session", providerId: null, cwd: null };
3959
4060
  const colonIndex = first.indexOf(":");
@@ -3983,7 +4084,8 @@ var BRIDGE_COMMAND_HELP_GROUPS = [
3983
4084
  { command: "/new claude|codex", desc: "\u65B0\u5EFA\u6307\u5B9A provider \u4F1A\u8BDD" },
3984
4085
  { command: "/new <\u76EE\u5F55>", desc: "\u5728\u6307\u5B9A\u76EE\u5F55\u6309\u5F53\u524D provider \u65B0\u5EFA\u4F1A\u8BDD" },
3985
4086
  { command: "/new claude:<\u76EE\u5F55>", desc: "\u65B0\u5EFA\u6307\u5B9A provider + \u6307\u5B9A\u76EE\u5F55\u4F1A\u8BDD" },
3986
- { command: "/stop", desc: "\u4E2D\u65AD\u5F53\u524D\u6B63\u5728\u751F\u6210\u7684\u56DE\u590D\uFF08\u4F1A\u8BDD\u4FDD\u7559\uFF09" }
4087
+ { command: "/stop", desc: "\u4E2D\u65AD\u5F53\u524D\u6B63\u5728\u751F\u6210\u7684\u56DE\u590D\uFF08\u4F1A\u8BDD\u4FDD\u7559\uFF09" },
4088
+ { command: "/reload", desc: "\u5F53\u524D\u4F1A\u8BDD\u5DF2\u88AB\u53E6\u4E00\u7AEF\u7EE7\u7EED\u540E\uFF0C\u91CD\u8F7D\u539F\u751F\u4F1A\u8BDD\u72B6\u6001\u518D\u7EE7\u7EED" }
3987
4089
  ]
3988
4090
  },
3989
4091
  {
@@ -4276,6 +4378,36 @@ var MessageRouter = class _MessageRouter {
4276
4378
  const provider = this.providers.get(session.providerId);
4277
4379
  void Promise.resolve(provider?.stopSession(session.id)).catch(() => void 0);
4278
4380
  }
4381
+ async ensureSessionFresh(session, provider) {
4382
+ if (!session.providerSessionId || !provider.getNativeVersion) return { ok: true };
4383
+ const version = await provider.getNativeVersion({
4384
+ providerSessionId: session.providerSessionId,
4385
+ cwd: session.cwd
4386
+ }).catch(() => null);
4387
+ if (!version || !session.lastSeenNativeFingerprint) return { ok: true };
4388
+ if (version.fingerprint === session.lastSeenNativeFingerprint) return { ok: true };
4389
+ this.conversation.update({
4390
+ staleReason: "native_advanced_elsewhere",
4391
+ lastActivityAt: Date.now()
4392
+ }, session.id);
4393
+ return {
4394
+ ok: false,
4395
+ message: "\u8BE5\u4F1A\u8BDD\u5DF2\u5728\u53E6\u4E00\u7AEF\u7EE7\u7EED\uFF0C\u5F53\u524D\u6865\u63A5\u72B6\u6001\u5DF2\u8FC7\u671F\u3002\u8BF7\u5148 /reload \u540E\u518D\u7EE7\u7EED\u3002"
4396
+ };
4397
+ }
4398
+ async refreshNativeFingerprint(session, provider) {
4399
+ if (!session.providerSessionId || !provider.getNativeVersion) return;
4400
+ const version = await provider.getNativeVersion({
4401
+ providerSessionId: session.providerSessionId,
4402
+ cwd: session.cwd
4403
+ }).catch(() => null);
4404
+ if (!version) return;
4405
+ this.conversation.update({
4406
+ lastSeenNativeFingerprint: version.fingerprint,
4407
+ staleReason: void 0,
4408
+ lastActivityAt: Date.now()
4409
+ }, session.id);
4410
+ }
4279
4411
  async runChatGeneration(message, user, text, seq) {
4280
4412
  const superseded = () => (this.latestMutatingSeq.get(message.chatId) ?? 0) > seq;
4281
4413
  if (superseded()) return;
@@ -4327,6 +4459,11 @@ var MessageRouter = class _MessageRouter {
4327
4459
  }
4328
4460
  await this.persistBridgeMetadata(updated);
4329
4461
  }
4462
+ const freshness = await this.ensureSessionFresh(this.conversation.getCurrent() ?? session, provider);
4463
+ if (!freshness.ok) {
4464
+ await this.sendToChat({ chatId: message.chatId, kind: "status", text: freshness.message });
4465
+ return;
4466
+ }
4330
4467
  const genId = ++this.generationSeq;
4331
4468
  let abortGeneration;
4332
4469
  const aborted = new Promise((resolve2) => {
@@ -4403,6 +4540,10 @@ var MessageRouter = class _MessageRouter {
4403
4540
  const finalBinding = this.conversation.getCurrent();
4404
4541
  if (finalBinding?.id === session.id && finalBinding.providerSessionId) {
4405
4542
  await this.persistBridgeMetadata(finalBinding).catch(() => void 0);
4543
+ const refreshedBinding = this.conversation.getCurrent();
4544
+ if (refreshedBinding?.id === session.id && refreshedBinding.providerSessionId) {
4545
+ await this.refreshNativeFingerprint(refreshedBinding, provider).catch(() => void 0);
4546
+ }
4406
4547
  }
4407
4548
  }
4408
4549
  } finally {
@@ -4438,6 +4579,44 @@ var MessageRouter = class _MessageRouter {
4438
4579
  await this.sendToChat({ chatId, kind: "status", text: "\u5DF2\u4E2D\u65AD\u5F53\u524D\u751F\u6210\uFF0C\u4F1A\u8BDD\u4FDD\u7559" });
4439
4580
  return;
4440
4581
  }
4582
+ if (command.kind === "reload_session") {
4583
+ this.pendingChoices.delete(chatId);
4584
+ const current = this.conversation.getCurrent();
4585
+ if (!current) {
4586
+ await this.sendToChat({ chatId, kind: "status", text: "No active session" });
4587
+ return;
4588
+ }
4589
+ if (!current.providerSessionId) {
4590
+ await this.sendToChat({ chatId, kind: "status", text: "\u5F53\u524D\u4F1A\u8BDD\u8FD8\u6CA1\u6709\u539F\u751F provider \u4F1A\u8BDD\u53EF\u91CD\u8F7D" });
4591
+ return;
4592
+ }
4593
+ const provider = this.providers.get(current.providerId);
4594
+ if (!provider?.attachSession) {
4595
+ await this.sendToChat({ chatId, kind: "status", text: `\u5F53\u524D provider\uFF08${current.providerId}\uFF09\u4E0D\u652F\u6301\u4F1A\u8BDD\u91CD\u8F7D` });
4596
+ return;
4597
+ }
4598
+ await this.preemptActiveGeneration(chatId);
4599
+ this.recycleProviderProcess(current);
4600
+ const reattached = await provider.attachSession({
4601
+ candidateId: current.providerSessionId,
4602
+ bridgeSessionId: current.id,
4603
+ cwd: current.cwd
4604
+ });
4605
+ const version = await provider.getNativeVersion?.({
4606
+ providerSessionId: reattached.providerSessionId ?? current.providerSessionId,
4607
+ cwd: current.cwd
4608
+ }).catch(() => null);
4609
+ this.conversation.update({
4610
+ providerSessionId: reattached.providerSessionId ?? current.providerSessionId,
4611
+ status: reattached.status,
4612
+ lastSeenNativeFingerprint: version?.fingerprint,
4613
+ lastReloadedAt: Date.now(),
4614
+ staleReason: void 0,
4615
+ lastActivityAt: Date.now()
4616
+ }, current.id);
4617
+ await this.sendToChat({ chatId, kind: "status", text: "\u5DF2\u91CD\u65B0\u52A0\u8F7D\u5F53\u524D\u4F1A\u8BDD\uFF0C\u53EF\u7EE7\u7EED\u5BF9\u8BDD\u3002" });
4618
+ return;
4619
+ }
4441
4620
  if (command.kind === "new_session") {
4442
4621
  this.pendingChoices.delete(chatId);
4443
4622
  const previousSession = this.conversation.getCurrent();
@@ -5494,8 +5673,9 @@ function listLanIpv4Addresses() {
5494
5673
  return Object.values(networkInterfaces()).flat().filter((iface) => Boolean(iface)).filter((iface) => iface.family === "IPv4" && !iface.internal).map((iface) => iface.address);
5495
5674
  }
5496
5675
  async function resolveProviderCommands(providers) {
5497
- const claudeCommand = providers?.claude?.command ?? await findExecutable("claude");
5498
- const codexCommand = providers?.codex?.command ?? await findExecutable("codex");
5676
+ const searchPath = resolveTerminalSearchPath();
5677
+ const claudeCommand = providers?.claude?.command ?? await findExecutable("claude", searchPath);
5678
+ const codexCommand = providers?.codex?.command ?? await findExecutable("codex", searchPath);
5499
5679
  if (!claudeCommand && !codexCommand) return void 0;
5500
5680
  return {
5501
5681
  ...claudeCommand ? { claude: { command: claudeCommand } } : {},
@@ -6063,9 +6243,10 @@ async function cmdDoctor() {
6063
6243
  console.log("claude-codex-wechat doctor\n");
6064
6244
  report("\u914D\u7F6E\u6587\u4EF6", existsSync11(configPath) ? configPath : `\u7F3A\u5931 (${configPath})\uFF0C\u9996\u6B21 start \u65F6\u81EA\u52A8\u521B\u5EFA`);
6065
6245
  report("\u524D\u7AEF\u4EA7\u7269", existsSync11(webRoot) ? webRoot : `\u7F3A\u5931 (${webRoot})`);
6066
- const claude = await findExecutable("claude");
6246
+ const searchPath = resolveTerminalSearchPath();
6247
+ const claude = await findExecutable("claude", searchPath);
6067
6248
  report("claude \u53EF\u6267\u884C", claude ?? "\u672A\u627E\u5230\uFF0C\u9700\u5148\u5B89\u88C5\u5E76\u767B\u5F55 Claude Code");
6068
- const codex = await findExecutable("codex");
6249
+ const codex = await findExecutable("codex", searchPath);
6069
6250
  report("codex \u53EF\u6267\u884C", codex ?? "\u672A\u627E\u5230\uFF08\u4EC5\u4F7F\u7528 Claude \u65F6\u53EF\u5FFD\u7565\uFF09");
6070
6251
  if (existsSync11(configPath)) {
6071
6252
  const config = loadBridgeConfig(configPath);