@wu529778790/open-im 1.8.3-beta.10 → 1.8.3-beta.12

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/setup.js CHANGED
@@ -461,9 +461,18 @@ export async function runInteractiveSetup() {
461
461
  catch {
462
462
  // account info is optional
463
463
  }
464
- const userId = accountInfo?.uid?.toString() ?? "";
464
+ const ai = accountInfo;
465
+ // Try multiple field names; fall back to tokenResult.userId, then existing config
466
+ const existingUserId = wb?.userId;
467
+ const userId = String(ai?.uid ?? ai?.userId ?? ai?.user_id ??
468
+ tokenResult.userId ??
469
+ existingUserId ?? "");
470
+ if (!userId) {
471
+ console.warn("⚠️ 未能获取 WorkBuddy 用户 ID,sessionId 可能不正确,建议稍后重试");
472
+ }
465
473
  // Set oauth.userId so buildSessionId() produces the correct sessionId.
466
474
  oauth.userId = userId;
475
+ console.log(` userId: ${userId || "(空)"}, sessionId 将为: ${oauth.buildSessionId()}`);
467
476
  // Save credentials immediately — even if binding fails below
468
477
  config.platforms.workbuddy = {
469
478
  enabled: true,
@@ -473,22 +482,18 @@ export async function runInteractiveSetup() {
473
482
  };
474
483
  oauthOk = true;
475
484
  console.log("\n✅ WorkBuddy 凭证已获取,正在生成微信客服绑定链接...");
476
- // Phase 2: WeChat KF binding (non-fatal)
485
+ // Phase 2: WeChat KF binding link (non-fatal, no polling needed)
477
486
  try {
478
- const sessionId = oauth.buildSessionId();
487
+ const { join: pathJoin } = await import("node:path");
488
+ const { homedir } = await import("node:os");
489
+ const clawPath = pathJoin(homedir(), "WorkBuddy", "Claw");
490
+ const sessionId = oauth.buildSessionId(clawPath);
479
491
  const linkResult = await oauth.getWeChatKfLink(sessionId);
480
492
  if (linkResult.success && linkResult.url) {
481
493
  console.log("\n━━━ 微信客服绑定 ━━━");
482
- console.log("请复制以下链接,在微信中发给「文件传输助手」并点击打开:");
494
+ console.log("请将以下链接发给微信「文件传输助手」并点击打开,即完成绑定:");
483
495
  console.log(linkResult.url);
484
- console.log("\n等待绑定完成(最长 5 分钟)...\n");
485
- const bindResult = await oauth.pollBindStatus(sessionId);
486
- if (bindResult.bound) {
487
- console.log(`✅ 微信客服绑定成功!${bindResult.nickname ? ` 用户: ${bindResult.nickname}` : ""}`);
488
- }
489
- else {
490
- console.log("⚠️ 绑定超时,你可以稍后重新运行 open-im init 完成绑定");
491
- }
496
+ console.log("\n绑定完成后运行 open-im start 启动服务即可收发消息。");
492
497
  }
493
498
  else {
494
499
  console.log(`⚠️ 获取微信客服链接失败: ${linkResult.message ?? "未知错误"}`);
@@ -526,21 +531,17 @@ export async function runInteractiveSetup() {
526
531
  userId: wb?.userId ?? '',
527
532
  });
528
533
  oauthKeep.userId = wb?.userId ?? '';
529
- const sessionId = oauthKeep.buildSessionId();
534
+ const { join: pathJoin2 } = await import("node:path");
535
+ const { homedir: homedir2 } = await import("node:os");
536
+ const clawPath2 = pathJoin2(homedir2(), "WorkBuddy", "Claw");
537
+ const sessionId = oauthKeep.buildSessionId(clawPath2);
530
538
  console.log(`\n正在获取微信客服绑定链接... (sessionId: ${sessionId})`);
531
539
  const linkResult = await oauthKeep.getWeChatKfLink(sessionId);
532
540
  if (linkResult.success && linkResult.url) {
533
541
  console.log("\n━━━ 微信客服绑定 ━━━");
534
- console.log("请复制以下链接,在微信中发给「文件传输助手」并点击打开:");
542
+ console.log("请将以下链接发给微信「文件传输助手」并点击打开,即完成绑定:");
535
543
  console.log(linkResult.url);
536
- console.log("\n等待绑定完成(最长 5 分钟)...\n");
537
- const bindResult = await oauthKeep.pollBindStatus(sessionId);
538
- if (bindResult.bound) {
539
- console.log(`✅ 微信客服绑定成功!${bindResult.nickname ? ` 用户: ${bindResult.nickname}` : ""}`);
540
- }
541
- else {
542
- console.log("⚠️ 绑定超时,你可以稍后重新运行 open-im init 完成绑定");
543
- }
544
+ console.log("\n绑定完成后运行 open-im start 启动服务即可收发消息。");
544
545
  }
545
546
  else {
546
547
  console.log(`⚠️ 获取绑定链接失败: ${linkResult.message ?? "未知"}`);
@@ -145,20 +145,20 @@ export class WorkBuddyCentrifugeClient {
145
145
  // WeChat KF messages: send via HTTP COPILOT_RESPONSE
146
146
  if (this.config.httpBaseUrl && this.config.httpAccessToken) {
147
147
  const message = payload.content?.map((c) => c.text).join('') || payload.error || '';
148
- // chatId format: "<wechatUserId>::origin::wechatkfProxy"
149
- // Extract user ID for toUser field; keep full chatId for routing.
148
+ // Incoming chatId format: "<wechatExternalUserId>::origin::wechatkfProxy"
149
+ // Server requires chatId = just the WeChat external user ID (the part before "::").
150
150
  const chatIdFull = payload.session_id;
151
- const toUser = chatIdFull.includes('::') ? chatIdFull.split('::')[0] : chatIdFull;
151
+ const chatId = chatIdFull.includes('::') ? chatIdFull.split('::')[0] : chatIdFull;
152
152
  const httpPayload = {
153
153
  type: 'COPILOT_RESPONSE',
154
154
  msgId: payload.prompt_id,
155
- chatId: chatIdFull,
156
- toUser,
155
+ chatId, // WeChat external user ID only — server uses this as touser
157
156
  success: payload.stop_reason === 'end_turn',
158
157
  message,
159
158
  metadata: {
160
159
  sessionId: this.config.workspaceSessionId || payload.session_id,
161
- toUser,
160
+ requestId: payload.prompt_id,
161
+ state: payload.stop_reason === 'end_turn' ? 'completed' : payload.stop_reason,
162
162
  },
163
163
  };
164
164
  const url = `${this.config.httpBaseUrl}/v2/backgroundagent/wecom/local-proxy/receive`;
@@ -7,7 +7,7 @@
7
7
  import { randomUUID } from 'node:crypto';
8
8
  import { existsSync, mkdirSync } from 'node:fs';
9
9
  import { join } from 'node:path';
10
- import { hostname } from 'node:os';
10
+ import { hostname, homedir } from 'node:os';
11
11
  import { createLogger } from '../logger.js';
12
12
  import { WorkBuddyOAuth } from './oauth.js';
13
13
  import { WorkBuddyCentrifugeClient } from './centrifuge-client.js';
@@ -62,27 +62,30 @@ async function connect() {
62
62
  const pc = platformConfig;
63
63
  const baseUrl = pc.baseUrl ?? 'https://copilot.tencent.com';
64
64
  const hostId = hostname();
65
- const stableWorkspaceId = `${pc.userId}-open-im-workbuddy`;
66
- log.info('Registering WorkBuddy workspace...');
65
+ // Claw workspace path — matches the plugin's WorkBuddy Claw installation path.
66
+ // The directory does NOT need to exist; the server uses it as a string identifier.
67
+ const clawPath = join(homedir(), 'WorkBuddy', 'Claw');
68
+ log.info('Registering WorkBuddy host workspace...');
67
69
  let tokens;
68
70
  try {
71
+ // Step 1: Register host workspace (workspaceId="") — gets the Centrifuge connection
69
72
  tokens = await oauth.registerWorkspace({
70
73
  userId: pc.userId ?? '',
71
74
  hostId,
72
- workspaceId: stableWorkspaceId,
73
- workspaceName: 'open-im-workbuddy',
75
+ workspaceId: '',
76
+ workspaceName: 'Host Channel',
74
77
  });
75
78
  }
76
79
  catch (err) {
77
- log.error('Workspace registration failed:', err);
80
+ log.error('Host workspace registration failed:', err);
78
81
  scheduleReconnect();
79
82
  return;
80
83
  }
81
- // sessionId ≤64 chars (WeChat KF uses it as `touser`)
82
- const workspaceSessionId = oauth.buildSessionId();
84
+ // sessionId = userId_hostId_clawPath (matches plugin's buildSessionId format)
85
+ const workspaceSessionId = oauth.buildSessionId(clawPath);
83
86
  const channel = tokens.channel;
84
87
  const guid = pc.guid ?? randomUUID();
85
- log.info(`Workspace registered: channel=${channel}, sessionId=${workspaceSessionId}`);
88
+ log.info(`Host workspace registered: channel=${channel}, clawSessionId=${workspaceSessionId}`);
86
89
  if (centrifugeClient) {
87
90
  centrifugeClient.stop();
88
91
  centrifugeClient = null;
@@ -100,25 +103,56 @@ async function connect() {
100
103
  }, {
101
104
  onConnected: () => {
102
105
  log.info('WorkBuddy Centrifuge connected');
103
- log.info(`sessionId (must match WeChat KF binding): ${workspaceSessionId}`);
106
+ log.info(`WeChat KF sessionId: ${workspaceSessionId}`);
104
107
  reconnectAttempt = 0;
105
108
  updateState('connected');
106
- const doRegister = () => {
107
- if (stopped || channelState !== 'connected')
108
- return;
109
- oauth.registerChannel({
110
- type: 'wechatkf',
111
- sessionId: workspaceSessionId,
112
- channelId: channel,
113
- userId: pc.userId ?? '',
114
- })
115
- .then((res) => log.info(`WeChat KF channel registered (online): ${JSON.stringify(res)}`))
116
- .catch((err) => log.warn(`registerChannel failed: ${String(err)}`));
117
- };
118
- doRegister();
119
- if (heartbeatTimer)
120
- clearInterval(heartbeatTimer);
121
- heartbeatTimer = setInterval(doRegister, CHANNEL_HEARTBEAT_MS);
109
+ // Step 2: Register Claw workspace to get WeChat KF routing channel + sessionId
110
+ oauth.registerWorkspace({
111
+ userId: pc.userId ?? '',
112
+ hostId,
113
+ workspaceId: clawPath,
114
+ workspaceName: 'Claw',
115
+ }).then((clawParams) => {
116
+ const clawSessionId = clawParams.sessionId ?? workspaceSessionId;
117
+ log.info(`Claw workspace registered: channel=${clawParams.channel}, sessionId=${clawSessionId}`);
118
+ // Subscribe to Claw channel — WeChat KF messages are published here
119
+ centrifugeClient?.subscribeChannel(clawParams.channel, clawParams.subscriptionToken);
120
+ const doRegister = () => {
121
+ if (stopped || channelState !== 'connected')
122
+ return;
123
+ oauth.registerChannel({
124
+ type: 'wechatkf',
125
+ sessionId: clawSessionId,
126
+ channelId: pc.userId ?? '', // plugin uses userId, not full channel name
127
+ userId: pc.userId ?? '',
128
+ })
129
+ .then((res) => log.info(`WeChat KF channel registered (online): ${JSON.stringify(res)}`))
130
+ .catch((err) => log.warn(`registerChannel failed: ${String(err)}`));
131
+ };
132
+ doRegister();
133
+ if (heartbeatTimer)
134
+ clearInterval(heartbeatTimer);
135
+ heartbeatTimer = setInterval(doRegister, CHANNEL_HEARTBEAT_MS);
136
+ }).catch((err) => {
137
+ log.error('Claw workspace registration failed:', err);
138
+ // Fallback: register with host sessionId
139
+ const doRegister = () => {
140
+ if (stopped || channelState !== 'connected')
141
+ return;
142
+ oauth.registerChannel({
143
+ type: 'wechatkf',
144
+ sessionId: workspaceSessionId,
145
+ channelId: pc.userId ?? '',
146
+ userId: pc.userId ?? '',
147
+ })
148
+ .then((res) => log.info(`WeChat KF channel registered (fallback): ${JSON.stringify(res)}`))
149
+ .catch((e) => log.warn(`registerChannel failed: ${String(e)}`));
150
+ };
151
+ doRegister();
152
+ if (heartbeatTimer)
153
+ clearInterval(heartbeatTimer);
154
+ heartbeatTimer = setInterval(doRegister, CHANNEL_HEARTBEAT_MS);
155
+ });
122
156
  },
123
157
  onDisconnected: (reason) => {
124
158
  log.info(`WorkBuddy Centrifuge disconnected: ${reason}`);
@@ -39,7 +39,7 @@ export declare class WorkBuddyOAuth {
39
39
  /**
40
40
  * Build sessionId for WorkBuddy workspace
41
41
  */
42
- buildSessionId(_workspacePath?: string): string;
42
+ buildSessionId(workspacePath?: string): string;
43
43
  /**
44
44
  * Get WeChat KF binding link
45
45
  */
@@ -174,10 +174,10 @@ export class WorkBuddyOAuth {
174
174
  /**
175
175
  * Build sessionId for WorkBuddy workspace
176
176
  */
177
- buildSessionId(_workspacePath) {
178
- // Keep session ID ≤64 chars: WeChat KF uses it as `touser` in send_msg.
179
- // userId (36) + "_" + hostId (≤26) is always well under the 64-char limit.
180
- return `${this.userId}_${this.hostId}`;
177
+ buildSessionId(workspacePath) {
178
+ // Must match the plugin's format: ${userId}_${hostId}_${workspacePath}
179
+ // The server uses this as the chatId/routing key for WeChat KF messages.
180
+ return `${this.userId}_${this.hostId}_${workspacePath ?? ''}`;
181
181
  }
182
182
  /**
183
183
  * Get WeChat KF binding link
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wu529778790/open-im",
3
- "version": "1.8.3-beta.10",
3
+ "version": "1.8.3-beta.12",
4
4
  "description": "Multi-platform IM bridge for AI CLI tools (Claude, Codex, CodeBuddy)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",