@wu529778790/open-im 1.8.3-beta.5 → 1.8.3-beta.7

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/dist/config.d.ts CHANGED
@@ -6,14 +6,8 @@ export interface Config {
6
6
  telegramBotToken?: string;
7
7
  feishuAppId?: string;
8
8
  feishuAppSecret?: string;
9
- wechatAppId?: string;
10
- wechatAppSecret?: string;
11
- wechatToken?: string;
12
- wechatJwtToken?: string;
13
- wechatLoginKey?: string;
14
- wechatGuid?: string;
9
+ /** WorkBuddy 用户 ID(可选,与 platforms.wechat.userId 二选一,环境变量 WECHAT_USER_ID) */
15
10
  wechatUserId?: string;
16
- wechatWsUrl?: string;
17
11
  weworkCorpId?: string;
18
12
  weworkSecret?: string;
19
13
  weworkWsUrl?: string;
@@ -64,12 +58,6 @@ export interface Config {
64
58
  wechat?: {
65
59
  enabled: boolean;
66
60
  aiCommand?: AiCommand;
67
- loginMode?: 'qclaw' | 'workbuddy';
68
- wsUrl?: string;
69
- token?: string;
70
- jwtToken?: string;
71
- loginKey?: string;
72
- guid?: string;
73
61
  userId?: string;
74
62
  allowedUserIds: string[];
75
63
  workbuddyAccessToken?: string;
@@ -124,17 +112,8 @@ interface FilePlatformQQ {
124
112
  }
125
113
  interface FilePlatformWechat {
126
114
  enabled?: boolean;
127
- appId?: string;
128
- appSecret?: string;
129
115
  aiCommand?: AiCommand;
130
- /** 连接模式:qclaw(QClaw JPRX 网关)或 workbuddy(Centrifuge) */
131
- loginMode?: 'qclaw' | 'workbuddy';
132
- token?: string;
133
- jwtToken?: string;
134
- loginKey?: string;
135
- guid?: string;
136
116
  userId?: string;
137
- wsUrl?: string;
138
117
  allowedUserIds?: string[];
139
118
  workbuddyAccessToken?: string;
140
119
  workbuddyRefreshToken?: string;
package/dist/config.js CHANGED
@@ -179,10 +179,10 @@ export function needsSetup() {
179
179
  return false;
180
180
  if (process.env.QQ_BOT_APPID && process.env.QQ_BOT_SECRET)
181
181
  return false;
182
- if (process.env.WECHAT_APP_ID && process.env.WECHAT_APP_SECRET)
183
- return false;
184
- if (process.env.WECHAT_TOKEN && process.env.WECHAT_GUID && process.env.WECHAT_USER_ID)
182
+ if (process.env.WECHAT_WORKBUDDY_ACCESS_TOKEN &&
183
+ process.env.WECHAT_WORKBUDDY_REFRESH_TOKEN) {
185
184
  return false;
185
+ }
186
186
  if (process.env.WEWORK_CORP_ID && process.env.WEWORK_SECRET)
187
187
  return false;
188
188
  if (process.env.DINGTALK_CLIENT_ID && process.env.DINGTALK_CLIENT_SECRET)
@@ -197,8 +197,7 @@ export function needsSetup() {
197
197
  const hasTelegram = !!tg?.botToken;
198
198
  const hasFeishu = !!(fs?.appId && fs?.appSecret);
199
199
  const hasQQ = !!(qq?.appId && qq?.secret);
200
- // 微信支持 AGP 协议(token + guid + userId)或标准协议(appId + appSecret)
201
- const hasWechat = !!(wc?.token && wc?.guid && wc?.userId) || !!(wc?.appId && wc?.appSecret);
200
+ const hasWechat = !!(wc?.workbuddyAccessToken && wc?.workbuddyRefreshToken);
202
201
  // 企业微信只需要 corpId 和 secret
203
202
  const hasWework = !!(ww?.corpId && ww?.secret);
204
203
  const hasDingtalk = !!(dt?.clientId && dt?.clientSecret);
@@ -248,25 +247,8 @@ export function loadConfig() {
248
247
  fileQQ?.appId;
249
248
  const qqSecret = process.env.QQ_BOT_SECRET ??
250
249
  fileQQ?.secret;
251
- // 微信支持两种协议:
252
- // 1. AGP 协议:token + guid + userId(推荐)
253
- // 2. 标准协议:appId + appSecret
254
- const wechatLoginMode = fileWechat?.loginMode ?? 'workbuddy';
255
- const wechatToken = process.env.WECHAT_TOKEN ??
256
- fileWechat?.token;
257
- const wechatJwtToken = fileWechat?.jwtToken;
258
- const wechatLoginKey = fileWechat?.loginKey;
259
- const wechatGuid = process.env.WECHAT_GUID ??
260
- fileWechat?.guid;
261
250
  const wechatUserId = process.env.WECHAT_USER_ID ??
262
251
  fileWechat?.userId;
263
- const wechatAppId = process.env.WECHAT_APP_ID ??
264
- fileWechat?.appId;
265
- const wechatAppSecret = process.env.WECHAT_APP_SECRET ??
266
- fileWechat?.appSecret;
267
- const wechatWsUrl = process.env.WECHAT_WS_URL ??
268
- fileWechat?.wsUrl;
269
- // 微信 WorkBuddy 模式凭证(loginMode === 'workbuddy' 时使用)
270
252
  const wechatWorkbuddyAccessToken = process.env.WECHAT_WORKBUDDY_ACCESS_TOKEN ??
271
253
  fileWechat?.workbuddyAccessToken;
272
254
  const wechatWorkbuddyRefreshToken = process.env.WECHAT_WORKBUDDY_REFRESH_TOKEN ??
@@ -312,15 +294,8 @@ export function loadConfig() {
312
294
  const telegramEnabled = !!telegramBotToken && (telegramEnabledFlag !== false);
313
295
  const feishuEnabled = !!(feishuAppId && feishuAppSecret) && (feishuEnabledFlag !== false);
314
296
  const qqEnabled = !!(qqAppId && qqSecret) && (qqEnabledFlag !== false);
315
- // 微信启用条件:
316
- // - qclaw 模式:AGP 协议凭证(token + guid + userId)或 标准协议凭证(appId + appSecret)
317
- // - workbuddy 模式:workbuddy OAuth 凭证
318
- const hasWechatAGPCreds = !!(wechatToken && wechatGuid && wechatUserId);
319
- const hasWechatStandardCreds = !!(wechatAppId && wechatAppSecret);
320
297
  const hasWechatWorkbuddyCreds = !!(wechatWorkbuddyAccessToken && wechatWorkbuddyRefreshToken);
321
- const wechatEnabled = (wechatLoginMode === 'workbuddy'
322
- ? hasWechatWorkbuddyCreds
323
- : (hasWechatAGPCreds || hasWechatStandardCreds)) && (wechatEnabledFlag !== false);
298
+ const wechatEnabled = hasWechatWorkbuddyCreds && (wechatEnabledFlag !== false);
324
299
  // 企业微信只需要 corpId (botId) 和 secret
325
300
  const weworkEnabled = !!(weworkCorpId && weworkSecret) && (weworkEnabledFlag !== false);
326
301
  const dingtalkEnabled = !!(dingtalkClientId && dingtalkClientSecret) && (dingtalkEnabledFlag !== false);
@@ -573,12 +548,6 @@ export function loadConfig() {
573
548
  ? {
574
549
  enabled: true,
575
550
  aiCommand: normalizeAiCommand(file.platforms?.wechat?.aiCommand, aiCommand),
576
- loginMode: wechatLoginMode,
577
- wsUrl: wechatWsUrl,
578
- token: wechatToken,
579
- jwtToken: wechatJwtToken,
580
- loginKey: wechatLoginKey,
581
- guid: wechatGuid,
582
551
  userId: wechatUserId,
583
552
  allowedUserIds: wechatAllowedUserIds,
584
553
  workbuddyAccessToken: wechatWorkbuddyAccessToken,
@@ -589,12 +558,6 @@ export function loadConfig() {
589
558
  : {
590
559
  enabled: false,
591
560
  aiCommand: normalizeAiCommand(file.platforms?.wechat?.aiCommand, aiCommand),
592
- loginMode: wechatLoginMode,
593
- wsUrl: wechatWsUrl,
594
- token: wechatToken,
595
- jwtToken: wechatJwtToken,
596
- loginKey: wechatLoginKey,
597
- guid: wechatGuid,
598
561
  userId: wechatUserId,
599
562
  allowedUserIds: wechatAllowedUserIds,
600
563
  workbuddyAccessToken: wechatWorkbuddyAccessToken,
@@ -657,14 +620,7 @@ export function loadConfig() {
657
620
  feishuAppSecret: feishuAppSecret ?? '',
658
621
  qqAppId: qqAppId ?? '',
659
622
  qqSecret: qqSecret ?? '',
660
- wechatAppId: wechatAppId ?? '',
661
- wechatAppSecret: wechatAppSecret ?? '',
662
- wechatToken: wechatToken,
663
- wechatJwtToken: wechatJwtToken,
664
- wechatLoginKey: wechatLoginKey,
665
- wechatGuid: wechatGuid,
666
623
  wechatUserId: wechatUserId,
667
- wechatWsUrl: wechatWsUrl,
668
624
  weworkCorpId: weworkCorpId ?? '',
669
625
  weworkSecret: weworkSecret ?? '',
670
626
  weworkWsUrl: weworkWsUrl,
@@ -707,9 +663,8 @@ export function getPlatformsWithCredentials(config) {
707
663
  r.push('wework');
708
664
  if (config.dingtalkClientId && config.dingtalkClientSecret)
709
665
  r.push('dingtalk');
710
- const hasWechat = (config.wechatToken && config.wechatGuid && config.wechatUserId) ||
711
- (config.wechatAppId && config.wechatAppSecret);
712
- if (hasWechat)
666
+ const wc = config.platforms.wechat;
667
+ if (wc?.workbuddyAccessToken && wc?.workbuddyRefreshToken)
713
668
  r.push('wechat');
714
669
  return r;
715
670
  }
package/dist/setup.js CHANGED
@@ -43,9 +43,8 @@ function getConfiguredPlatforms(existing) {
43
43
  return !!(p.appId && p.appSecret);
44
44
  if (k === "qq")
45
45
  return !!(p.appId && p.secret);
46
- // 微信支持 AGP 协议(token + guid + userId)或标准协议(appId + appSecret)
47
46
  if (k === "wechat")
48
- return !!(p.token && p.guid && p.userId) || !!(p.appId && p.appSecret);
47
+ return !!(p.workbuddyAccessToken && p.workbuddyRefreshToken);
49
48
  if (k === "wework")
50
49
  return !!(p.corpId && p.secret);
51
50
  if (k === "dingtalk")
@@ -116,16 +115,16 @@ function printManualInstructions(configPath) {
116
115
  },
117
116
  "wechat": {
118
117
  "enabled": false,
119
- "appId": "你的微信 App ID(可选,测试中)",
120
- "appSecret": "你的微信 App Secret(可选)",
121
- "wsUrl": "AGP WebSocket URL(可选)",
118
+ "workbuddyAccessToken": "(由 open-im init 在浏览器完成 WorkBuddy 登录后自动写入)",
119
+ "workbuddyRefreshToken": "",
120
+ "userId": "",
122
121
  "allowedUserIds": ["允许访问的微信用户 ID(可选)"]
123
122
  }
124
123
  }
125
124
  }`);
126
125
  console.log("");
127
126
  console.log("提示:至少需要配置 Telegram、Feishu、QQ、WeChat、WeWork 或 DingTalk 其中一个平台");
128
- console.log("或设置环境变量: TELEGRAM_BOT_TOKEN=xxx、FEISHU_APP_ID=xxx、QQ_BOT_APPID=xxx、WECHAT_APP_ID=xxx、WEWORK_CORP_ID=xxx 或 DINGTALK_CLIENT_ID=xxx 后再运行");
127
+ console.log("或设置环境变量: TELEGRAM_BOT_TOKEN=xxx、FEISHU_APP_ID=xxx、QQ_BOT_APPID=xxx、WECHAT_WORKBUDDY_ACCESS_TOKEN=xxx、WEWORK_CORP_ID=xxx 或 DINGTALK_CLIENT_ID=xxx 后再运行");
129
128
  console.log("");
130
129
  }
131
130
  const CLAUDE_SETTINGS_PATH = join(homedir(), ".claude", "settings.json");
@@ -274,7 +273,7 @@ export async function runInteractiveSetup() {
274
273
  const hasFs = !!(existing?.platforms?.feishu?.appId && existing?.platforms?.feishu?.appSecret);
275
274
  const hasQq = !!(existing?.platforms?.qq?.appId && existing?.platforms?.qq?.secret);
276
275
  const wc = existing?.platforms?.wechat;
277
- const hasWc = !!(wc?.token && wc?.guid && wc?.userId) || !!(wc?.appId && wc?.appSecret);
276
+ const hasWc = !!(wc?.workbuddyAccessToken && wc?.workbuddyRefreshToken);
278
277
  const hasWw = !!(existing?.platforms?.wework?.corpId && existing?.platforms?.wework?.secret);
279
278
  const hasDt = !!(existing?.platforms?.dingtalk?.clientId && existing?.platforms?.dingtalk?.clientSecret);
280
279
  // 第一步:选择平台(在选项和提示中显示已配置项)
@@ -308,7 +307,7 @@ export async function runInteractiveSetup() {
308
307
  value: "dingtalk",
309
308
  },
310
309
  {
311
- title: "微信 (WeChat) - 扫码登录获取 token(QClaw/AGP 协议,测试中)" +
310
+ title: "微信 (WeChat)" +
312
311
  (hasWc ? " ✓已配置" : ""),
313
312
  value: "wechat",
314
313
  },
@@ -423,30 +422,25 @@ export async function runInteractiveSetup() {
423
422
  }
424
423
  if (selectedPlatforms.includes("wechat")) {
425
424
  const wc = existing?.platforms?.wechat;
426
- const hasToken = !!(wc?.token && wc?.guid && wc?.userId);
427
425
  const hasWbCreds = !!(wc?.workbuddyAccessToken && wc?.workbuddyRefreshToken);
428
426
  const wechatModeResp = await prompts({
429
427
  type: "select",
430
428
  name: "mode",
431
- message: "微信登录方式",
429
+ message: "微信(WorkBuddy / CodeBuddy OAuth)",
432
430
  choices: [
433
431
  {
434
- title: "WorkBuddy OAuth - 在浏览器中完成 CodeBuddy 登录(推荐)",
435
- value: "workbuddy",
432
+ title: "在浏览器中完成 CodeBuddy 登录并绑定微信客服(推荐)",
433
+ value: "oauth",
436
434
  },
437
435
  {
438
- title: "扫码登录 - 用微信扫描二维码获取 token(QClaw/AGP 协议)",
439
- value: "qr",
440
- },
441
- {
442
- title: "使用已有配置" + (hasToken || hasWbCreds ? " ✓" : "(需已配置)"),
436
+ title: "使用已有 WorkBuddy 凭证" + (hasWbCreds ? " ✓" : ""),
443
437
  value: "keep",
444
- disabled: !hasToken && !hasWbCreds,
438
+ disabled: !hasWbCreds,
445
439
  },
446
440
  ],
447
- initial: hasWbCreds ? 0 : (hasToken ? 1 : 0),
441
+ initial: hasWbCreds ? 1 : 0,
448
442
  }, { onCancel });
449
- if (wechatModeResp.mode === "workbuddy") {
443
+ if (wechatModeResp.mode === "oauth") {
450
444
  console.log("\n正在启动 WorkBuddy OAuth 登录...\n");
451
445
  try {
452
446
  const { WorkBuddyOAuth } = await import("./workbuddy/oauth.js");
@@ -467,12 +461,30 @@ export async function runInteractiveSetup() {
467
461
  }
468
462
  const userId = accountInfo?.uid?.toString() ?? "";
469
463
  config.platforms.wechat = {
470
- loginMode: 'workbuddy',
471
464
  enabled: true,
472
465
  workbuddyAccessToken: tokenResult.accessToken,
473
466
  workbuddyRefreshToken: tokenResult.refreshToken,
474
467
  userId,
475
468
  };
469
+ console.log("\n正在获取微信客服绑定链接...");
470
+ const sessionId = oauth.buildSessionId();
471
+ const linkResult = await oauth.getWeChatKfLink(sessionId);
472
+ if (linkResult.success && linkResult.url) {
473
+ console.log("\n━━━ 微信客服绑定 ━━━");
474
+ console.log("请复制以下链接,在微信中发给「文件传输助手」并点击打开:");
475
+ console.log(linkResult.url);
476
+ console.log("\n等待绑定完成(最长 5 分钟)...\n");
477
+ const bindResult = await oauth.pollBindStatus(sessionId);
478
+ if (bindResult.bound) {
479
+ console.log(`✅ 微信客服绑定成功!${bindResult.nickname ? ` 用户: ${bindResult.nickname}` : ""}`);
480
+ }
481
+ else {
482
+ console.log("⚠️ 绑定超时,你可以稍后重新运行 open-im init 完成绑定");
483
+ }
484
+ }
485
+ else {
486
+ console.log("⚠️ 获取微信客服链接失败,你可以稍后重新运行 open-im init 完成绑定");
487
+ }
476
488
  console.log("\n✅ WorkBuddy 登录成功,配置已保存");
477
489
  }
478
490
  catch (err) {
@@ -481,34 +493,7 @@ export async function runInteractiveSetup() {
481
493
  return false;
482
494
  }
483
495
  }
484
- else if (wechatModeResp.mode === "qr") {
485
- console.log("\n正在启动微信扫码登录...\n");
486
- try {
487
- const { performWeChatLogin } = await import("./wechat/auth/index.js");
488
- const credentials = await performWeChatLogin({
489
- envName: "production",
490
- appId: wc?.appId || undefined,
491
- });
492
- config.platforms.wechat = {
493
- loginMode: 'qclaw',
494
- appId: wc?.appId,
495
- enabled: true,
496
- token: credentials.channelToken,
497
- jwtToken: credentials.jwtToken,
498
- loginKey: credentials.loginKey,
499
- guid: credentials.guid,
500
- userId: credentials.userId,
501
- wsUrl: "wss://mmgrcalltoken.3g.qq.com/agentwss",
502
- };
503
- console.log("\n✅ 微信登录成功,配置已获取");
504
- }
505
- catch (err) {
506
- console.error("\n❌ 微信登录失败:", err instanceof Error ? err.message : String(err));
507
- if (platform === "wechat")
508
- return false;
509
- }
510
- }
511
- else if (hasToken || hasWbCreds) {
496
+ else if (hasWbCreds) {
512
497
  config.platforms.wechat = {
513
498
  ...wc,
514
499
  enabled: true,
@@ -898,19 +883,21 @@ export async function runInteractiveSetup() {
898
883
  }
899
884
  if (selectedPlatforms.includes("wechat")) {
900
885
  const wcConfig = config.platforms?.wechat;
901
- out.platforms.wechat = {
902
- ...base?.platforms?.wechat,
886
+ const baseWc = base?.platforms?.wechat;
887
+ const wbOut = {
903
888
  enabled: true,
904
- appId: wcConfig?.appId ?? base?.platforms?.wechat?.appId,
905
- appSecret: wcConfig?.appSecret ?? base?.platforms?.wechat?.appSecret,
906
- token: wcConfig?.token ?? base?.platforms?.wechat?.token,
907
- jwtToken: wcConfig?.jwtToken ?? base?.platforms?.wechat?.jwtToken,
908
- loginKey: wcConfig?.loginKey ?? base?.platforms?.wechat?.loginKey,
909
- guid: wcConfig?.guid ?? base?.platforms?.wechat?.guid,
910
- userId: wcConfig?.userId ?? base?.platforms?.wechat?.userId,
911
- wsUrl: wcConfig?.wsUrl ?? base?.platforms?.wechat?.wsUrl,
889
+ workbuddyAccessToken: wcConfig?.workbuddyAccessToken ?? baseWc?.workbuddyAccessToken,
890
+ workbuddyRefreshToken: wcConfig?.workbuddyRefreshToken ?? baseWc?.workbuddyRefreshToken,
891
+ userId: wcConfig?.userId ?? baseWc?.userId ?? "",
912
892
  allowedUserIds: wechatIds,
913
893
  };
894
+ const wbBaseUrl = wcConfig?.workbuddyBaseUrl ?? baseWc?.workbuddyBaseUrl;
895
+ const wbHostId = wcConfig?.workbuddyHostId ?? baseWc?.workbuddyHostId;
896
+ if (wbBaseUrl)
897
+ wbOut.workbuddyBaseUrl = wbBaseUrl;
898
+ if (wbHostId)
899
+ wbOut.workbuddyHostId = wbHostId;
900
+ out.platforms.wechat = wbOut;
914
901
  }
915
902
  else if (basePlatforms?.wechat) {
916
903
  outPlatforms.wechat = {
@@ -40,11 +40,7 @@ describe("runAITask", () => {
40
40
  codebuddyTimeoutMs: 600000,
41
41
  claudeModel: "",
42
42
  codexProxy: "",
43
- wechatToken: "",
44
- wechatGuid: "",
45
43
  wechatUserId: "",
46
- wechatAppId: "",
47
- wechatAppSecret: "",
48
44
  dingtalkClientId: "",
49
45
  dingtalkClientSecret: "",
50
46
  qqAppId: "",
@@ -1,9 +1,5 @@
1
1
  /**
2
- * WeChat Client - 薄封装层,根据 loginMode 委托给对应 transport
3
- *
4
- * 支持两种通道:
5
- * - workbuddy: 通过 Centrifuge WebSocket 连接(默认)
6
- * - qclaw: 直连腾讯 JPRX 网关
2
+ * WeChat Client 通过 WorkBuddy(CodeBuddy OAuth + Centrifuge)连接微信
7
3
  */
8
4
  import type { Config } from '../config.js';
9
5
  import type { AGPEnvelope, WeChatChannelState, WeChatToken } from './types.js';
@@ -1,14 +1,9 @@
1
1
  /**
2
- * WeChat Client - 薄封装层,根据 loginMode 委托给对应 transport
3
- *
4
- * 支持两种通道:
5
- * - workbuddy: 通过 Centrifuge WebSocket 连接(默认)
6
- * - qclaw: 直连腾讯 JPRX 网关
2
+ * WeChat Client 通过 WorkBuddy(CodeBuddy OAuth + Centrifuge)连接微信
7
3
  */
8
- import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
4
+ import { readFileSync, existsSync, mkdirSync } from 'node:fs';
9
5
  import { join } from 'node:path';
10
6
  import { createLogger } from '../logger.js';
11
- import { QClawTransport } from './qclaw-transport.js';
12
7
  import { WorkBuddyTransport } from './workbuddy-transport.js';
13
8
  const log = createLogger('WeChat');
14
9
  const TOKEN_FILE = 'wechat-token.json';
@@ -17,7 +12,6 @@ let transport = null;
17
12
  let channelState = 'disconnected';
18
13
  let currentToken = null;
19
14
  let tokenStoragePath = null;
20
- let isStopping = false;
21
15
  // Event handlers
22
16
  let messageHandler = null;
23
17
  let stateChangeHandler = null;
@@ -27,7 +21,6 @@ let stateChangeHandler = null;
27
21
  export async function dispatchIncomingAGPEnvelope(envelope, handler) {
28
22
  switch (envelope.method) {
29
23
  case 'ping':
30
- // Respond to ping with pong via transport
31
24
  if (transport) {
32
25
  transport.send('ping', { timestamp: Date.now() }, envelope.msg_id);
33
26
  }
@@ -64,25 +57,14 @@ export function getCurrentToken() {
64
57
  export async function initWeChat(config, eventHandler, onStateChange) {
65
58
  messageHandler = eventHandler;
66
59
  stateChangeHandler = onStateChange ?? null;
67
- isStopping = false;
68
- // Set up token storage path
69
60
  const baseDir = config.logDir ?? join(process.env.HOME ?? '', '.open-im');
70
61
  tokenStoragePath = join(baseDir, 'data');
71
62
  if (!existsSync(tokenStoragePath)) {
72
63
  mkdirSync(tokenStoragePath, { recursive: true });
73
64
  }
74
- // Load existing token if available
75
65
  await loadToken();
76
- // Determine login mode from config
77
- const loginMode = config.platforms.wechat?.loginMode ?? 'workbuddy';
78
- log.info(`Initializing WeChat with loginMode: ${loginMode}`);
79
- if (loginMode === 'workbuddy') {
80
- transport = createWorkBuddyTransport(config);
81
- }
82
- else {
83
- transport = createQClawTransport(config);
84
- }
85
- // Wire up transport callbacks
66
+ log.info('Initializing WeChat (WorkBuddy)');
67
+ transport = createWorkBuddyTransport(config);
86
68
  transport.onMessage(async (envelope) => {
87
69
  await dispatchIncomingAGPEnvelope(envelope, messageHandler);
88
70
  });
@@ -93,25 +75,8 @@ export async function initWeChat(config, eventHandler, onStateChange) {
93
75
  }
94
76
  });
95
77
  await transport.start();
96
- log.info(`WeChat client initialized (${loginMode} mode)`);
78
+ log.info('WeChat client initialized (WorkBuddy)');
97
79
  }
98
- /**
99
- * Create QClaw transport from config
100
- */
101
- function createQClawTransport(config) {
102
- const qclawConfig = {
103
- channelToken: config.wechatToken,
104
- jwtToken: config.wechatJwtToken ?? config.platforms.wechat?.jwtToken,
105
- loginKey: config.wechatLoginKey ?? config.platforms.wechat?.loginKey,
106
- guid: config.wechatGuid,
107
- userId: config.wechatUserId,
108
- wsUrl: config.wechatWsUrl,
109
- };
110
- return new QClawTransport(qclawConfig);
111
- }
112
- /**
113
- * Create WorkBuddy transport from config
114
- */
115
80
  function createWorkBuddyTransport(config) {
116
81
  const wp = config.platforms.wechat;
117
82
  const workbuddyConfig = {
@@ -137,7 +102,6 @@ export function sendAGPMessage(method, payload, replyTo) {
137
102
  * Stop WeChat client
138
103
  */
139
104
  export function stopWeChat() {
140
- isStopping = true;
141
105
  if (transport) {
142
106
  transport.stop();
143
107
  transport = null;
@@ -170,18 +134,3 @@ async function loadToken() {
170
134
  }
171
135
  }
172
136
  }
173
- /**
174
- * Save token to storage
175
- */
176
- function saveToken() {
177
- if (!currentToken || !tokenStoragePath)
178
- return;
179
- try {
180
- const tokenPath = join(tokenStoragePath, TOKEN_FILE);
181
- writeFileSync(tokenPath, JSON.stringify(currentToken, null, 2), 'utf-8');
182
- log.info('Token saved to storage');
183
- }
184
- catch (err) {
185
- log.error('Error saving token:', err);
186
- }
187
- }
@@ -1,6 +1,5 @@
1
1
  /**
2
- * WeChat Transport Interface
3
- * 抽象 QClaw 和 WorkBuddy 两种 WebSocket 传输方式
2
+ * WeChat Transport Interface — WorkBuddy Centrifuge 实现
4
3
  */
5
4
  import type { AGPEnvelope } from './types.js';
6
5
  import type { WeChatChannelState } from './types.js';
@@ -11,7 +10,6 @@ export type StateChangeHandler = (state: WeChatChannelState) => void;
11
10
  /**
12
11
  * WeChat 传输接口
13
12
  *
14
- * 所有传输方式(QClaw WebSocket、WorkBuddy Centrifuge)都实现此接口。
15
13
  * client.ts 通过此接口与传输层交互,无需关心底层协议。
16
14
  */
17
15
  export interface WeChatTransport {
@@ -1,5 +1,4 @@
1
1
  /**
2
- * WeChat Transport Interface
3
- * 抽象 QClaw 和 WorkBuddy 两种 WebSocket 传输方式
2
+ * WeChat Transport Interface — WorkBuddy Centrifuge 实现
4
3
  */
5
4
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wu529778790/open-im",
3
- "version": "1.8.3-beta.5",
3
+ "version": "1.8.3-beta.7",
4
4
  "description": "Multi-platform IM bridge for AI CLI tools (Claude, Codex, CodeBuddy)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,13 +0,0 @@
1
- /**
2
- * 设备绑定:生成企微客服链接,用户在微信中打开后才有对话入口
3
- */
4
- import type { QClawAPI } from './qclaw-api.js';
5
- export interface DeviceBindResult {
6
- success: boolean;
7
- contactUrl?: string;
8
- message: string;
9
- }
10
- export declare function performDeviceBinding(api: QClawAPI, options?: {
11
- timeoutMs?: number;
12
- showQr?: (url: string) => void | Promise<void>;
13
- }): Promise<DeviceBindResult>;
@@ -1,75 +0,0 @@
1
- /**
2
- * 设备绑定:生成企微客服链接,用户在微信中打开后才有对话入口
3
- */
4
- import { createLogger } from '../../logger.js';
5
- const log = createLogger('WeChatDeviceBind');
6
- function nested(obj, ...keys) {
7
- let cur = obj;
8
- for (const k of keys) {
9
- if (cur == null || typeof cur !== 'object')
10
- return undefined;
11
- cur = cur[k];
12
- }
13
- return cur;
14
- }
15
- const DEFAULT_OPEN_KFID = 'wkzLlJLAAAfbxEV3ZcS-lHZxkaKmpejQ';
16
- const POLL_INTERVAL_MS = 2000;
17
- const DEFAULT_TIMEOUT_MS = 180_000; // 3 分钟
18
- export async function performDeviceBinding(api, options) {
19
- const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
20
- const showQr = options?.showQr;
21
- log.info('正在调用 4018 接口生成绑定链接...');
22
- let linkResult;
23
- try {
24
- linkResult = await Promise.race([
25
- api.generateContactLink(DEFAULT_OPEN_KFID),
26
- new Promise((_, reject) => setTimeout(() => reject(new Error('4018 接口超时(15秒)')), 15_000)),
27
- ]);
28
- }
29
- catch (err) {
30
- const msg = err instanceof Error ? err.message : String(err);
31
- return { success: false, message: `生成绑定链接失败: ${msg}` };
32
- }
33
- if (!linkResult.success) {
34
- return { success: false, message: `生成绑定链接失败: ${linkResult.message}` };
35
- }
36
- const linkData = linkResult.data;
37
- const bindUrl = nested(linkData, 'url') ||
38
- nested(linkData, 'data', 'url') ||
39
- nested(linkData, 'resp', 'url') ||
40
- nested(linkData, 'resp', 'data', 'url') ||
41
- '';
42
- if (!bindUrl) {
43
- log.warn('4018 响应结构:', JSON.stringify(linkData, null, 2).slice(0, 500));
44
- return { success: false, message: '生成绑定链接失败,未返回 URL。服务端响应结构可能已变更' };
45
- }
46
- if (showQr) {
47
- await showQr(bindUrl);
48
- }
49
- else {
50
- log.info('【设备绑定】请复制下方链接,在企微/微信中打开:');
51
- log.info('打开后会进入客服会话,后续发消息必须在此会话中进行');
52
- log.info(bindUrl);
53
- }
54
- const deadline = Date.now() + timeoutMs;
55
- while (Date.now() < deadline) {
56
- await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
57
- const queryResult = await api.queryDeviceByGuid();
58
- if (!queryResult.success)
59
- continue;
60
- const data = queryResult.data;
61
- const inner = nested(data, 'data');
62
- const isBind = nested(data, 'is_bind') ?? inner?.is_bind;
63
- const nickname = (nested(data, 'nickname') ?? inner?.nickname);
64
- const externalUserId = (nested(data, 'external_user_id') ?? inner?.external_user_id);
65
- // 与 wechat-access 一致:nickname 或 external_user_id 表示已绑定
66
- if (isBind === true || isBind === 1 || !!nickname || !!externalUserId) {
67
- return { success: true, contactUrl: bindUrl, message: '设备绑定成功' };
68
- }
69
- }
70
- return {
71
- success: false,
72
- contactUrl: bindUrl,
73
- message: '绑定超时,请稍后重新登录并完成绑定',
74
- };
75
- }
@@ -1,5 +0,0 @@
1
- /**
2
- * 设备唯一标识生成
3
- * 首次运行时随机生成并持久化到 ~/.open-im/wechat-guid
4
- */
5
- export declare function getDeviceGuid(): string;
@@ -1,28 +0,0 @@
1
- /**
2
- * 设备唯一标识生成
3
- * 首次运行时随机生成并持久化到 ~/.open-im/wechat-guid
4
- */
5
- import { randomUUID, createHash } from 'node:crypto';
6
- import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
7
- import { join, dirname } from 'node:path';
8
- import { APP_HOME } from '../../constants.js';
9
- const GUID_FILE = join(APP_HOME, 'wechat-guid');
10
- export function getDeviceGuid() {
11
- try {
12
- const existing = readFileSync(GUID_FILE, 'utf-8').trim();
13
- if (existing)
14
- return existing;
15
- }
16
- catch {
17
- /* 文件不存在 */
18
- }
19
- const guid = createHash('md5').update(randomUUID()).digest('hex');
20
- try {
21
- mkdirSync(dirname(GUID_FILE), { recursive: true });
22
- writeFileSync(GUID_FILE, guid, 'utf-8');
23
- }
24
- catch {
25
- /* 写入失败不致命 */
26
- }
27
- return guid;
28
- }
@@ -1,5 +0,0 @@
1
- /**
2
- * QClaw 环境配置(生产/测试)
3
- */
4
- import type { QClawEnvironment } from './types.js';
5
- export declare function getEnvironment(name: string, appId: string): QClawEnvironment;