claude-codex-wechat 0.1.47 → 0.1.49

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.
@@ -198,7 +198,7 @@ var server = new McpServer(
198
198
  '\u7528\u6237\u8BF4\u7684"\u53D1\u7ED9\u6211""\u53D1\u5230\u5FAE\u4FE1""\u53D1\u51FA\u6765""\u4F20\u7ED9\u6211"\u90FD\u662F\u6307\u901A\u8FC7\u672C\u670D\u52A1\u5668\u7684\u5DE5\u5177\u53D1\u9001\u6587\u4EF6\u5230\u7528\u6237\u5FAE\u4FE1\u3002',
199
199
  "",
200
200
  "\u5173\u952E\u884C\u4E3A\uFF1A",
201
- "- \u5F53\u7528\u6237\u53D1\u6765\u6296\u97F3\u94FE\u63A5\u6216\u5206\u4EAB\u6587\u6848\u65F6\uFF0C\u7ACB\u5373\u8C03\u7528 download_douyin \u4E0B\u8F7D\u5E76\u53D1\u9001\uFF0C\u65E0\u9700\u8BE2\u95EE\u3002",
201
+ '- \u5F53\u7528\u6237\u53D1\u6765\u6296\u97F3\u94FE\u63A5\u6216\u5206\u4EAB\u6587\u6848\u65F6\uFF0C\u7ACB\u5373\u8C03\u7528 download_douyin \u4E0B\u8F7D\u5E76\u53D1\u9001\uFF0C\u65E0\u9700\u8BE2\u95EE\uFF1B\u5206\u4EAB\u81EA\u5E26\u7684\u7F29\u7565\u56FE\u9644\u4EF6\u5373\u4F7F\u5931\u8D25\u4E5F\u5FFD\u7565\uFF0C\u4E0D\u8981\u5C94\u5F00\u53BB\u804A\u56FE\u7247\u3002download_douyin \u5931\u8D25\u65F6\u5148\u91CD\u8BD5\u4E00\u6B21\uFF0C\u4ECD\u5931\u8D25\u5C31\u4E00\u53E5\u8BDD\u544A\u77E5\u7528\u6237"\u6296\u97F3\u4E0B\u8F7D\u5931\u8D25\uFF1A<\u539F\u56E0>"\u3002',
202
202
  '- \u5F53\u7528\u6237\u8BF4"\u628AXX\u53D1\u7ED9\u6211"\u65F6\uFF0C\u6839\u636E\u6587\u4EF6\u7C7B\u578B\u8C03\u7528 send_image/send_video/send_file\u3002',
203
203
  '- \u751F\u6210\u4E86\u6587\u4EF6\u540E\u7528\u6237\u8BF4"\u53D1\u7ED9\u6211"\uFF0C\u76F4\u63A5\u8C03\u7528\u5BF9\u5E94\u5DE5\u5177\u53D1\u9001\u3002',
204
204
  "- \u5B8C\u6210\u540E\u7B80\u77ED\u786E\u8BA4\u5373\u53EF\uFF0C\u4E0D\u9700\u8981\u5197\u957F\u89E3\u91CA\u3002"
@@ -21,6 +21,43 @@ const MOBILE_UA =
21
21
  const DESKTOP_UA =
22
22
  "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
23
23
 
24
+ // ─── 带重试/超时的 fetch ─────────────────────────────────────────────────────
25
+
26
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
27
+
28
+ /**
29
+ * fetch 包一层重试与超时,抵御瞬时网络抖动("fetch failed" 之类的底层网络错误)。
30
+ * 仅对网络层错误和 5xx/429 重试;4xx(除 429)视为确定性失败,不重试。
31
+ */
32
+ async function fetchWithRetry(url, options = {}, { retries = 3, timeoutMs = 20_000 } = {}) {
33
+ let lastErr;
34
+ for (let attempt = 1; attempt <= retries; attempt += 1) {
35
+ const controller = new AbortController();
36
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
37
+ try {
38
+ const resp = await fetch(url, { ...options, signal: controller.signal });
39
+ clearTimeout(timer);
40
+ // 5xx / 429 属于可恢复的服务端抖动,重试;其余(含 4xx)直接返回交由调用方判断
41
+ if ((resp.status >= 500 || resp.status === 429) && attempt < retries) {
42
+ lastErr = new Error(`HTTP ${resp.status}`);
43
+ await sleep(attempt * 800);
44
+ continue;
45
+ }
46
+ return resp;
47
+ } catch (err) {
48
+ clearTimeout(timer);
49
+ lastErr = err;
50
+ const reason = err?.name === "AbortError" ? `请求超时(>${timeoutMs}ms)` : (err?.message || String(err));
51
+ if (attempt < retries) {
52
+ await sleep(attempt * 800);
53
+ continue;
54
+ }
55
+ throw new Error(`网络请求失败(已重试${retries}次):${reason}`);
56
+ }
57
+ }
58
+ throw lastErr ?? new Error("网络请求失败");
59
+ }
60
+
24
61
  // ─── 解析逻辑 ────────────────────────────────────────────────────────────────
25
62
 
26
63
  /** 从任意文本里提取第一个抖音 URL */
@@ -45,7 +82,7 @@ async function resolveAwemeId(input) {
45
82
  throw new Error(`无法识别的输入(既不是抖音链接,也不是 aweme_id):${trimmed}`);
46
83
  }
47
84
 
48
- const resp = await fetch(url, {
85
+ const resp = await fetchWithRetry(url, {
49
86
  method: "GET",
50
87
  headers: { "User-Agent": MOBILE_UA },
51
88
  redirect: "follow",
@@ -58,7 +95,7 @@ async function resolveAwemeId(input) {
58
95
  /** 从抖音分享页提取 video_id */
59
96
  async function resolveVideoId(awemeId) {
60
97
  const shareUrl = `https://www.iesdouyin.com/share/video/${awemeId}/`;
61
- const resp = await fetch(shareUrl, { headers: { "User-Agent": MOBILE_UA } });
98
+ const resp = await fetchWithRetry(shareUrl, { headers: { "User-Agent": MOBILE_UA } });
62
99
  if (!resp.ok) throw new Error(`分享页请求失败:HTTP ${resp.status}(aweme_id=${awemeId})`);
63
100
  const html = await resp.text();
64
101
  const m =
@@ -95,10 +132,10 @@ async function download(input, outputDir) {
95
132
  const playUrl = buildPlayUrlNoWatermark(videoId);
96
133
  console.log("正在下载无水印视频...");
97
134
 
98
- const resp = await fetch(playUrl, {
135
+ const resp = await fetchWithRetry(playUrl, {
99
136
  headers: { "User-Agent": DESKTOP_UA, Referer: "https://www.douyin.com/" },
100
137
  redirect: "follow",
101
- });
138
+ }, { timeoutMs: 60_000 });
102
139
 
103
140
  if (!resp.ok) {
104
141
  throw new Error(`下载失败: HTTP ${resp.status}`);
@@ -497,7 +497,7 @@ var FileWeixinStateStore = class {
497
497
  };
498
498
 
499
499
  // src/providers/claude-code/nativeSessions.ts
500
- import { appendFile, mkdir as mkdir2, readdir as readdir2, readFile as readFile2, stat as stat2, writeFile } from "node:fs/promises";
500
+ import { appendFile, mkdir as mkdir2, readdir as readdir2, readFile as readFile2, stat as stat2, unlink as unlink2, writeFile } from "node:fs/promises";
501
501
  import { homedir as homedir2 } from "node:os";
502
502
  import { basename as basename2, dirname as dirname2, join as join3 } from "node:path";
503
503
 
@@ -528,6 +528,19 @@ function parseSessionBridgeName(value) {
528
528
  function resolveClaudeConfigDir(env = process.env) {
529
529
  return join3(env.HOME || homedir2(), ".claude");
530
530
  }
531
+ async function prepareClaudeSessionForResume(input) {
532
+ const env = input.env ?? process.env;
533
+ const index = await readClaudeRuntimeSessionIndex(env);
534
+ const record = index.get(input.sessionId.trim());
535
+ if (!record || record.kind !== "bg") return { ok: true, adoptedBackground: false };
536
+ if (record.status && record.status !== "idle") {
537
+ return { ok: false, reason: "background_session_running" };
538
+ }
539
+ if (record.path) {
540
+ await unlink2(record.path).catch(() => void 0);
541
+ }
542
+ return { ok: true, adoptedBackground: true };
543
+ }
531
544
  async function listRecoverableClaudeSessions(env = process.env) {
532
545
  const projectsDir = join3(resolveClaudeConfigDir(env), "projects");
533
546
  const historyIndex = await readClaudeHistoryIndex(env);
@@ -685,6 +698,30 @@ async function readClaudeSessionMetadata(filePath) {
685
698
  }
686
699
  return { aiTitle, lastPrompt, sessionName, cwd };
687
700
  }
701
+ async function readClaudeRuntimeSessionIndex(env) {
702
+ const sessionsDir = join3(resolveClaudeConfigDir(env), "sessions");
703
+ const index = /* @__PURE__ */ new Map();
704
+ try {
705
+ const entries = await readdir2(sessionsDir, { withFileTypes: true });
706
+ for (const entry of entries) {
707
+ if (!entry.isFile() || entry.isSymbolicLink() || !String(entry.name).endsWith(".json")) continue;
708
+ const path = join3(sessionsDir, String(entry.name));
709
+ try {
710
+ const raw = await readFile2(path, "utf8");
711
+ const record = JSON.parse(raw);
712
+ const sessionId = typeof record.sessionId === "string" ? record.sessionId.trim() : "";
713
+ if (!sessionId) continue;
714
+ const kind = typeof record.kind === "string" ? record.kind.trim() : void 0;
715
+ const status = typeof record.status === "string" ? record.status.trim() : void 0;
716
+ index.set(sessionId, { kind, status, path });
717
+ } catch {
718
+ }
719
+ }
720
+ } catch {
721
+ return index;
722
+ }
723
+ return index;
724
+ }
688
725
  async function readClaudeHistoryIndex(env) {
689
726
  const historyPath = join3(resolveClaudeConfigDir(env), "history.jsonl");
690
727
  const index = /* @__PURE__ */ new Map();
@@ -1373,6 +1410,12 @@ function registerChannelAdminRoutes(input) {
1373
1410
  if (!input.conversation) {
1374
1411
  return reply.code(500).send({ ok: false, error: "current_conversation_store_unavailable" });
1375
1412
  }
1413
+ if (request.body.providerId === "claude-code") {
1414
+ const prepared = await prepareClaudeSessionForResume({ sessionId: request.body.providerSessionId });
1415
+ if (!prepared.ok) {
1416
+ return reply.code(409).send({ ok: false, error: "claude_background_session_still_running" });
1417
+ }
1418
+ }
1376
1419
  const recoverableCandidate = provider.listRecoverableSessions ? (await provider.listRecoverableSessions()).find((candidate) => candidate.id === request.body.providerSessionId) : void 0;
1377
1420
  const attached = await attachProviderSessionToBridge({
1378
1421
  conversationStore: input.conversation,
@@ -1387,6 +1430,19 @@ function registerChannelAdminRoutes(input) {
1387
1430
  resumeTitle: recoverableCandidate?.resumeTitle
1388
1431
  });
1389
1432
  input.events?.emit({ type: "channel.current-session-changed" });
1433
+ try {
1434
+ const providerLabel = attached.providerId === "codex" ? "Codex" : "Claude";
1435
+ const lines = [`### \u5DF2\u63A5\u5165 ${providerLabel} \u4F1A\u8BDD`];
1436
+ if (attached.providerSessionId) lines.push(`- \`sessionId\`: \`${attached.providerSessionId}\``);
1437
+ lines.push(`- \`cwd\`: \`${attached.cwd}\``);
1438
+ await input.channel?.sendMessage({
1439
+ chatId: request.body.chatId ?? user.platformUserId,
1440
+ kind: "status",
1441
+ text: lines.join("\n")
1442
+ });
1443
+ } catch (err) {
1444
+ input.app.log.warn({ err }, "sessions_attach_notify_failed");
1445
+ }
1390
1446
  return {
1391
1447
  ok: true,
1392
1448
  session: {
@@ -3024,6 +3080,8 @@ var BRIDGE_APPEND_SYSTEM_PROMPT = [
3024
3080
  "- \u7B2C\u4E00\u65F6\u95F4\u8C03\u7528 download_douyin \u4E0B\u8F7D\u65E0\u6C34\u5370\u89C6\u9891\u5E76\u81EA\u52A8\u53D1\u9001\u5230\u7528\u6237\u5FAE\u4FE1",
3025
3081
  "- \u65E0\u9700\u8BE2\u95EE\u7528\u6237\u662F\u5426\u8981\u4E0B\u8F7D\uFF0C\u76F4\u63A5\u6267\u884C",
3026
3082
  "- \u652F\u6301\u77ED\u94FE\u3001\u5B8C\u6574\u94FE\u63A5\u3001\u5206\u4EAB\u6587\u6848\u3001\u7EAF aweme_id",
3083
+ "- \u6296\u97F3\u5206\u4EAB\u6D88\u606F\u5E38\u5E38\u9644\u5E26\u5361\u7247\u7F29\u7565\u56FE\uFF0C\u8FD9\u4E9B\u56FE\u7247\u9644\u4EF6\u5373\u4F7F\u52A0\u8F7D\u5931\u8D25\u4E5F\u65E0\u5173\u7D27\u8981\uFF0C\u76F4\u63A5\u5FFD\u7565\uFF0C\u4E0D\u8981\u56E0\u6B64\u8FFD\u95EE\u7528\u6237\u6216\u6539\u804A\u56FE\u7247\u2014\u2014\u4F60\u7684\u4EFB\u52A1\u53EA\u6709\u4E00\u4E2A\uFF1A\u4E0B\u8F7D\u5E76\u53D1\u9001\u89C6\u9891\u3002",
3084
+ '- \u5982\u679C download_douyin \u8FD4\u56DE\u5931\u8D25\uFF0C\u5148\u539F\u6837\u91CD\u8BD5\u4E00\u6B21\uFF1B\u4ECD\u5931\u8D25\u5C31\u4E00\u53E5\u8BDD\u660E\u786E\u544A\u8BC9\u7528\u6237"\u6296\u97F3\u4E0B\u8F7D\u5931\u8D25\uFF1A<\u539F\u56E0>"\uFF0C\u4E0D\u8981\u8F6C\u800C\u8BA8\u8BBA\u56FE\u7247\u6216\u5C94\u5F00\u8BDD\u9898\u3002',
3027
3085
  "",
3028
3086
  "## \u884C\u4E3A\u51C6\u5219",
3029
3087
  '- \u7528\u6237\u7684"\u6211"\u5C31\u662F\u4ED6\u7684\u5FAE\u4FE1\uFF0C"\u53D1\u7ED9\u6211"\u5C31\u662F\u53D1\u5230\u5FAE\u4FE1\u3002',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-codex-wechat",
3
- "version": "0.1.47",
3
+ "version": "0.1.49",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@11.5.0",