@henryxiaoyang/wechat-access-unqclawed 1.1.0-beta.3 → 1.1.0-beta.30

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/index.js CHANGED
@@ -7890,6 +7890,7 @@ var CentrifugeGatewayClient = class _CentrifugeGatewayClient {
7890
7890
  callbacks;
7891
7891
  client = null;
7892
7892
  sub = null;
7893
+ extraSubs = [];
7893
7894
  state = "disconnected";
7894
7895
  processedMsgIds = /* @__PURE__ */ new Set();
7895
7896
  static MAX_MSG_ID_CACHE = 1e3;
@@ -7949,6 +7950,10 @@ var CentrifugeGatewayClient = class _CentrifugeGatewayClient {
7949
7950
  console.log(`${this.logPrefix} \u6B63\u5728\u505C\u6B62...`);
7950
7951
  this.state = "disconnected";
7951
7952
  this.processedMsgIds.clear();
7953
+ for (const sub of this.extraSubs) {
7954
+ sub.unsubscribe();
7955
+ }
7956
+ this.extraSubs = [];
7952
7957
  if (this.sub) {
7953
7958
  this.sub.unsubscribe();
7954
7959
  this.sub = null;
@@ -7963,6 +7968,26 @@ var CentrifugeGatewayClient = class _CentrifugeGatewayClient {
7963
7968
  setCallbacks = (callbacks) => {
7964
7969
  this.callbacks = { ...this.callbacks, ...callbacks };
7965
7970
  };
7971
+ /** 订阅额外的 channel(用于 Claw workspace 等,复用同一个 Centrifuge 连接) */
7972
+ subscribeChannel = (channel, subscriptionToken) => {
7973
+ if (!this.client) {
7974
+ console.warn(`${this.logPrefix} \u65E0\u6CD5\u8BA2\u9605: \u5BA2\u6237\u7AEF\u672A\u521D\u59CB\u5316`);
7975
+ return;
7976
+ }
7977
+ console.log(`${this.logPrefix} \u8BA2\u9605\u989D\u5916 channel: ${channel}`);
7978
+ const sub = this.client.newSubscription(channel, { token: subscriptionToken });
7979
+ sub.on("publication", (ctx) => {
7980
+ this.handlePublication(ctx.data);
7981
+ });
7982
+ sub.on("error", (ctx) => {
7983
+ console.error(`${this.logPrefix} \u989D\u5916\u8BA2\u9605\u9519\u8BEF (${channel}): ${ctx.error.message}`);
7984
+ });
7985
+ sub.on("subscribed", () => {
7986
+ console.log(`${this.logPrefix} \u989D\u5916 channel \u5DF2\u8BA2\u9605: ${channel}`);
7987
+ });
7988
+ this.extraSubs.push(sub);
7989
+ sub.subscribe();
7990
+ };
7966
7991
  // ============================================
7967
7992
  // 上行消息发送(与 WechatAccessWebSocketClient 同签名)
7968
7993
  // ============================================
@@ -7994,6 +8019,41 @@ var CentrifugeGatewayClient = class _CentrifugeGatewayClient {
7994
8019
  this.sendEnvelope("session.update", payload, guid, userId);
7995
8020
  };
7996
8021
  sendPromptResponse = (payload, guid, userId) => {
8022
+ if (this.config.httpBaseUrl && this.config.httpAccessToken) {
8023
+ const message = payload.content?.map((c) => c.text).join("") || payload.error || "";
8024
+ const httpPayload = {
8025
+ type: "COPILOT_RESPONSE",
8026
+ msgId: payload.prompt_id,
8027
+ chatId: payload.session_id,
8028
+ // 原始 WeChat KF chatId(从 incoming message 透传)
8029
+ success: payload.stop_reason === "end_turn",
8030
+ message,
8031
+ metadata: {
8032
+ sessionId: this.config.workspaceSessionId || payload.session_id,
8033
+ // workspace sessionId
8034
+ requestId: payload.prompt_id,
8035
+ state: payload.stop_reason === "end_turn" ? "completed" : payload.stop_reason
8036
+ }
8037
+ };
8038
+ const url = `${this.config.httpBaseUrl}/v2/backgroundagent/wecom/local-proxy/receive`;
8039
+ fetch(url, {
8040
+ method: "POST",
8041
+ headers: {
8042
+ "Content-Type": "application/json",
8043
+ "Authorization": `Bearer ${this.config.httpAccessToken}`
8044
+ },
8045
+ body: JSON.stringify(httpPayload),
8046
+ signal: AbortSignal.timeout(3e4)
8047
+ }).then(async (res) => {
8048
+ if (!res.ok) {
8049
+ const body = await res.text().catch(() => "");
8050
+ console.error(`${this.logPrefix} HTTP COPILOT_RESPONSE \u5931\u8D25: ${res.status} ${body.substring(0, 200)}`);
8051
+ }
8052
+ }).catch((err) => {
8053
+ console.error(`${this.logPrefix} HTTP COPILOT_RESPONSE \u5F02\u5E38:`, err);
8054
+ });
8055
+ return;
8056
+ }
7997
8057
  this.sendEnvelope("session.promptResponse", payload, guid, userId);
7998
8058
  };
7999
8059
  // ============================================
@@ -8001,28 +8061,57 @@ var CentrifugeGatewayClient = class _CentrifugeGatewayClient {
8001
8061
  // ============================================
8002
8062
  handlePublication = (data) => {
8003
8063
  try {
8004
- const envelope = data;
8005
- if (!envelope?.method || !envelope?.msg_id) {
8006
- console.warn(`${this.logPrefix} \u6536\u5230\u975E AGP \u683C\u5F0F\u6D88\u606F\uFF0C\u8DF3\u8FC7`);
8064
+ const raw = data;
8065
+ if (raw?.method && raw?.msg_id) {
8066
+ const envelope = raw;
8067
+ if (this.processedMsgIds.has(envelope.msg_id)) {
8068
+ console.log(`${this.logPrefix} \u91CD\u590D\u6D88\u606F\uFF0C\u8DF3\u8FC7: ${envelope.msg_id}`);
8069
+ return;
8070
+ }
8071
+ this.processedMsgIds.add(envelope.msg_id);
8072
+ this.cleanMsgIdCache();
8073
+ console.log(`${this.logPrefix} \u6536\u5230 AGP \u6D88\u606F: method=${envelope.method}, msg_id=${envelope.msg_id}`);
8074
+ switch (envelope.method) {
8075
+ case "session.prompt":
8076
+ this.callbacks.onPrompt?.(envelope);
8077
+ break;
8078
+ case "session.cancel":
8079
+ this.callbacks.onCancel?.(envelope);
8080
+ break;
8081
+ default:
8082
+ console.warn(`${this.logPrefix} \u672A\u77E5 AGP \u6D88\u606F\u7C7B\u578B: ${envelope.method}`);
8083
+ }
8007
8084
  return;
8008
8085
  }
8009
- if (this.processedMsgIds.has(envelope.msg_id)) {
8010
- console.log(`${this.logPrefix} \u91CD\u590D\u6D88\u606F\uFF0C\u8DF3\u8FC7: ${envelope.msg_id}`);
8086
+ if (raw?.chatId && raw?.msgId) {
8087
+ const msgId = String(raw.msgId);
8088
+ if (this.processedMsgIds.has(msgId)) {
8089
+ console.log(`${this.logPrefix} \u91CD\u590D\u6D88\u606F\uFF0C\u8DF3\u8FC7: ${msgId}`);
8090
+ return;
8091
+ }
8092
+ this.processedMsgIds.add(msgId);
8093
+ this.cleanMsgIdCache();
8094
+ const content = String(raw.content ?? "");
8095
+ const chatId = String(raw.chatId);
8096
+ console.log(`${this.logPrefix} \u6536\u5230 WeChat KF \u6D88\u606F: msgId=${msgId}, chatId=${chatId}, content=${content.substring(0, 50)}`);
8097
+ const envelope = {
8098
+ msg_id: msgId,
8099
+ guid: this.config.guid,
8100
+ user_id: this.config.userId,
8101
+ method: "session.prompt",
8102
+ payload: {
8103
+ session_id: chatId,
8104
+ prompt_id: msgId,
8105
+ agent_app: "default",
8106
+ content: [{ type: "text", text: content }]
8107
+ }
8108
+ };
8109
+ envelope._wechatKf = { chatId, msgId, raw };
8110
+ this.callbacks.onPrompt?.(envelope);
8011
8111
  return;
8012
8112
  }
8013
- this.processedMsgIds.add(envelope.msg_id);
8014
- this.cleanMsgIdCache();
8015
- console.log(`${this.logPrefix} \u6536\u5230\u6D88\u606F: method=${envelope.method}, msg_id=${envelope.msg_id}`);
8016
- switch (envelope.method) {
8017
- case "session.prompt":
8018
- this.callbacks.onPrompt?.(envelope);
8019
- break;
8020
- case "session.cancel":
8021
- this.callbacks.onCancel?.(envelope);
8022
- break;
8023
- default:
8024
- console.warn(`${this.logPrefix} \u672A\u77E5\u6D88\u606F\u7C7B\u578B: ${envelope.method}`);
8025
- }
8113
+ const preview = JSON.stringify(data).substring(0, 500);
8114
+ console.warn(`${this.logPrefix} \u6536\u5230\u672A\u77E5\u683C\u5F0F\u6D88\u606F: ${preview}`);
8026
8115
  } catch (error) {
8027
8116
  console.error(`${this.logPrefix} \u6D88\u606F\u5904\u7406\u5931\u8D25:`, error);
8028
8117
  this.callbacks.onError?.(
@@ -8170,6 +8259,8 @@ var buildMessageContext = (message) => {
8170
8259
  // 原始消息内容
8171
8260
  CommandBody: content,
8172
8261
  // 命令体(用于解析命令)
8262
+ CommandAuthorized: true,
8263
+ // 自建通道,所有命令均授权
8173
8264
  From: `wechat-access:${userId}`,
8174
8265
  // 发送者标识(带频道前缀)
8175
8266
  To: `wechat-access:${toUser}`,
@@ -8335,7 +8426,7 @@ var handlePrompt = async (message, client) => {
8335
8426
  route.agentId
8336
8427
  );
8337
8428
  let finalText = null;
8338
- const { queuedFinal } = await runtime2.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
8429
+ await runtime2.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
8339
8430
  ctx,
8340
8431
  cfg,
8341
8432
  dispatcherOptions: {
@@ -8615,7 +8706,8 @@ var QClawAPI = class {
8615
8706
  };
8616
8707
 
8617
8708
  // auth/codebuddy-api.ts
8618
- import { hostname } from "os";
8709
+ import { hostname, homedir as homedir2 } from "os";
8710
+ import { join as join2 } from "path";
8619
8711
  var DEFAULT_BASE_URL = "https://copilot.tencent.com";
8620
8712
  var PREFIX_PATH = "/plugin";
8621
8713
  var PLATFORM = "ide";
@@ -8759,9 +8851,10 @@ var CodeBuddyAPI = class {
8759
8851
  throw new Error("refreshToken: missing accessToken in response");
8760
8852
  }
8761
8853
  // ==================== WeChat KF 绑定 ====================
8762
- /** 构造 sessionId */
8763
- buildSessionId(workspacePath = "") {
8764
- return `${this.userId}_${this.hostId}_${workspacePath}`;
8854
+ /** 构造 sessionId(与 WorkBuddy 保持一致) */
8855
+ buildSessionId(workspacePath) {
8856
+ const wp = workspacePath ?? join2(homedir2(), "WorkBuddy", "Claw");
8857
+ return `${this.userId}_${this.hostId}_${wp}`;
8765
8858
  }
8766
8859
  /** 获取微信客服绑定链接 (QR 码) */
8767
8860
  async wechatkfGetLink(sessionId, userId) {
@@ -8867,153 +8960,30 @@ var CodeBuddyAPI = class {
8867
8960
  }
8868
8961
  };
8869
8962
 
8870
- // auth/state-store.ts
8871
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync, mkdirSync as mkdirSync2, chmodSync } from "fs";
8872
- import { join as join2, dirname as dirname2 } from "path";
8873
- import { homedir as homedir2 } from "os";
8874
- var DEFAULT_STATE_PATH = join2(homedir2(), ".openclaw", "wechat-access-auth.json");
8875
- var loadState = () => {
8876
- try {
8877
- const raw = readFileSync2(DEFAULT_STATE_PATH, "utf-8");
8878
- return JSON.parse(raw);
8879
- } catch {
8880
- return null;
8881
- }
8882
- };
8883
- var saveState = (state) => {
8884
- mkdirSync2(dirname2(DEFAULT_STATE_PATH), { recursive: true });
8885
- writeFileSync2(DEFAULT_STATE_PATH, JSON.stringify(state, null, 2), { encoding: "utf-8", mode: 384 });
8886
- try {
8887
- chmodSync(DEFAULT_STATE_PATH, 384);
8888
- } catch {
8889
- }
8890
- };
8891
- var clearState = () => {
8892
- try {
8893
- unlinkSync(DEFAULT_STATE_PATH);
8894
- } catch {
8895
- }
8896
- };
8897
-
8898
8963
  // auth/wechat-login.ts
8899
- import { readFileSync as readFileSync3, unlinkSync as unlinkSync2, existsSync } from "fs";
8900
- import { join as join3 } from "path";
8901
- import { homedir as homedir3 } from "os";
8964
+ import { createInterface } from "readline";
8902
8965
 
8903
- // auth/device-bind.ts
8904
- var DEFAULT_OPEN_KFID = "wkzLlJLAAAfbxEV3ZcS-lHZxkaKmpejQ";
8905
- var POLL_INTERVAL_MS = 2e3;
8906
- var DEFAULT_TIMEOUT_MS = 3e5;
8907
- async function defaultShowQr(url) {
8908
- try {
8909
- const qrterm = await import("qrcode-terminal");
8910
- const generate = qrterm.default?.generate ?? qrterm.generate;
8911
- generate(url, { small: true }, (qrcode) => {
8912
- console.log(qrcode);
8913
- });
8914
- } catch {
8915
- }
8916
- }
8917
- async function performDeviceBinding(options) {
8918
- const {
8919
- api,
8920
- openKfId = DEFAULT_OPEN_KFID,
8921
- timeoutMs = DEFAULT_TIMEOUT_MS,
8922
- log: log2 = { info: console.log, warn: console.warn, error: console.error },
8923
- showQr = defaultShowQr
8924
- } = options;
8925
- log2.info("[device-bind] \u751F\u6210\u4F01\u5FAE\u5BA2\u670D\u94FE\u63A5...");
8926
- let linkResult;
8927
- try {
8928
- linkResult = await api.generateContactLink(openKfId);
8929
- } catch (e) {
8930
- const msg = `\u751F\u6210\u5BA2\u670D\u94FE\u63A5\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`;
8931
- log2.warn(`[device-bind] ${msg}`);
8932
- return { success: false, message: msg };
8933
- }
8934
- if (!linkResult.success) {
8935
- const msg = `\u751F\u6210\u5BA2\u670D\u94FE\u63A5\u5931\u8D25: ${linkResult.message ?? "\u672A\u77E5\u9519\u8BEF"}`;
8936
- log2.warn(`[device-bind] ${msg}`);
8937
- return { success: false, message: msg };
8938
- }
8939
- const linkData = linkResult.data;
8940
- const contactUrl = nested(linkData, "url") || nested(linkData, "data", "url") || nested(linkData, "resp", "url") || "";
8941
- if (!contactUrl) {
8942
- const msg = "\u670D\u52A1\u7AEF\u672A\u8FD4\u56DE\u5BA2\u670D\u94FE\u63A5 URL";
8943
- log2.warn(`[device-bind] ${msg}`);
8944
- return { success: false, message: msg };
8945
- }
8946
- console.log("");
8947
- console.log("=".repeat(64));
8948
- console.log("\u8BF7\u7528\u300C\u63A7\u5236\u7AEF\u5FAE\u4FE1\u300D\u6253\u5F00\u4E0B\u65B9\u94FE\u63A5\uFF0C\u5B8C\u6210\u8BBE\u5907\u7ED1\u5B9A");
8949
- console.log("\u7ED1\u5B9A\u540E\u5FAE\u4FE1\u4E2D\u4F1A\u51FA\u73B0\u5BF9\u8BDD\u5165\u53E3");
8950
- console.log("=".repeat(64));
8951
- await showQr(contactUrl);
8952
- console.log(`
8953
- \u94FE\u63A5: ${contactUrl}
8954
- `);
8955
- log2.info("[device-bind] \u7B49\u5F85\u8BBE\u5907\u7ED1\u5B9A...");
8956
- const deadline = Date.now() + timeoutMs;
8957
- while (Date.now() < deadline) {
8958
- await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
8959
- try {
8960
- const status = await api.queryDeviceByGuid();
8961
- if (status.success) {
8962
- const sd = status.data;
8963
- const nickname = nested(sd, "nickname") || nested(sd, "data", "nickname");
8964
- const externalUserId = nested(sd, "external_user_id") || nested(sd, "data", "external_user_id");
8965
- if (nickname || externalUserId) {
8966
- const msg = `\u8BBE\u5907\u7ED1\u5B9A\u6210\u529F!${nickname ? ` \u5FAE\u4FE1\u6635\u79F0: ${nickname}` : ""}`;
8967
- log2.info(`[device-bind] ${msg}`);
8968
- return { success: true, contactUrl, nickname: nickname || void 0, message: msg };
8969
- }
8970
- }
8971
- } catch {
8972
- }
8973
- }
8974
- return {
8975
- success: false,
8976
- contactUrl,
8977
- message: "\u8BBE\u5907\u7ED1\u5B9A\u8D85\u65F6\u3002\u8BF7\u786E\u8BA4\u5DF2\u5728\u5FAE\u4FE1\u4E2D\u6253\u5F00\u4E0A\u65B9\u94FE\u63A5\uFF0C\u7136\u540E\u91CD\u542F Gateway \u91CD\u8BD5\u3002"
8978
- };
8979
- }
8966
+ // auth/wechat-qr-poll.ts
8967
+ var buildAuthUrl = (state, env) => {
8968
+ const params = new URLSearchParams({
8969
+ appid: env.wxAppId,
8970
+ redirect_uri: env.wxLoginRedirectUri,
8971
+ response_type: "code",
8972
+ scope: "snsapi_login",
8973
+ state
8974
+ });
8975
+ return `https://open.weixin.qq.com/connect/qrconnect?${params.toString()}#wechat_redirect`;
8976
+ };
8980
8977
 
8981
8978
  // auth/wechat-login.ts
8982
- var waitForInviteCodeFile = async (log2) => {
8983
- const inviteCodeTmpFile = join3(homedir3(), ".openclaw", "wechat-invite-code.tmp");
8984
- try {
8985
- unlinkSync2(inviteCodeTmpFile);
8986
- } catch {
8987
- }
8988
- log2("");
8989
- log2("=".repeat(64));
8990
- log2("\u9700\u8981\u9080\u8BF7\u7801\u9A8C\u8BC1\u3002\u8BF7\u5728\u53E6\u4E00\u4E2A\u7EC8\u7AEF\u6267\u884C\uFF1A");
8991
- log2("");
8992
- log2(`echo "\u4F60\u7684\u9080\u8BF7\u7801" > ${inviteCodeTmpFile}`);
8993
- log2("");
8994
- log2("\u6CA1\u6709\u9080\u8BF7\u7801\uFF1F\u76F4\u63A5\u6267\u884C\uFF1A");
8995
- log2(`echo "" > ${inviteCodeTmpFile}`);
8996
- log2("");
8997
- log2("\u672C\u7A97\u53E3\u4F1A\u81EA\u52A8\u68C0\u6D4B\u5E76\u7EE7\u7EED\u3002");
8998
- log2("=".repeat(64));
8999
- const deadline = Date.now() + 3e5;
9000
- while (Date.now() < deadline) {
9001
- await new Promise((r) => setTimeout(r, 1500));
9002
- if (!existsSync(inviteCodeTmpFile)) continue;
9003
- let raw = "";
9004
- try {
9005
- raw = readFileSync3(inviteCodeTmpFile, "utf-8").trim();
9006
- unlinkSync2(inviteCodeTmpFile);
9007
- } catch {
9008
- continue;
9009
- }
9010
- return raw;
9011
- }
9012
- try {
9013
- unlinkSync2(inviteCodeTmpFile);
9014
- } catch {
9015
- }
9016
- return "";
8979
+ var askInput = (prompt) => {
8980
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
8981
+ return new Promise((resolve) => {
8982
+ rl.question(prompt, (answer) => {
8983
+ rl.close();
8984
+ resolve(answer.trim());
8985
+ });
8986
+ });
9017
8987
  };
9018
8988
  var performLogin = async (options) => {
9019
8989
  const { guid, env, inviteCode, log: log2 } = options;
@@ -9028,66 +8998,44 @@ var performLogin = async (options) => {
9028
8998
  if (s) state = s;
9029
8999
  }
9030
9000
  info(`[Login] state=${state}`);
9031
- info("[Login] \u6B65\u9AA4 2/5: \u8BF7\u901A\u8FC7\u8C03\u7528\u65B9\u5C55\u793A\u4E8C\u7EF4\u7801\u5E76\u83B7\u53D6 code...");
9032
- info("[Login] \u6B65\u9AA4 3/5: \u7B49\u5F85\u5FAE\u4FE1\u626B\u7801\u6388\u6743...");
9033
- const codeTmpFile = join3(homedir3(), ".openclaw", "wechat-auth-code.tmp");
9034
- try {
9035
- unlinkSync2(codeTmpFile);
9036
- } catch {
9037
- }
9001
+ info("[Login] \u6B65\u9AA4 2/5: \u8BF7\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00\u4EE5\u4E0B\u94FE\u63A5\u626B\u7801\u767B\u5F55...");
9002
+ const authUrl = buildAuthUrl(state, env);
9038
9003
  info("");
9039
9004
  info("=".repeat(64));
9040
- info("\u626B\u7801\u5E76\u5728\u624B\u673A\u4E0A\u786E\u8BA4\u540E\uFF0C\u6D4F\u89C8\u5668\u4F1A\u8DF3\u8F6C\u5230\u65B0\u9875\u9762\u3002");
9041
- info("\u5730\u5740\u680F URL \u5F62\u5982\uFF1A");
9005
+ info("\u8BF7\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00\u4EE5\u4E0B\u94FE\u63A5\uFF0C\u7528\u5FAE\u4FE1\u626B\u7801\u767B\u5F55\uFF1A");
9042
9006
  info("");
9043
- info("https://security.guanjia.qq.com/login?code=0a1B2c...&state=xxx");
9007
+ info(authUrl);
9008
+ info("=".repeat(64));
9009
+ info("[Login] \u6B65\u9AA4 3/5: \u7B49\u5F85\u5FAE\u4FE1\u626B\u7801\u6388\u6743...");
9044
9010
  info("");
9045
- info("\u8BF7\u590D\u5236 code= \u540E\u9762\u7684\u503C\uFF08\u5230 & \u4E4B\u524D\uFF09\uFF0C\u5728\u53E6\u4E00\u4E2A\u7EC8\u7AEF\u6267\u884C\uFF1A");
9011
+ info("\u626B\u7801\u5E76\u5728\u624B\u673A\u4E0A\u786E\u8BA4\u540E\uFF0C\u6D4F\u89C8\u5668\u4F1A\u8DF3\u8F6C\u5230\u65B0\u9875\u9762\u3002");
9012
+ info("\u5730\u5740\u680F URL \u5F62\u5982\uFF1A");
9046
9013
  info("");
9047
- info(`echo "0a1B2c3D4e" > ${codeTmpFile}`);
9014
+ info(" https://security.guanjia.qq.com/login?code=0a1B2c...&state=xxx");
9048
9015
  info("");
9049
- info("\u4E5F\u53EF\u4EE5\u76F4\u63A5\u7C98\u8D34\u5B8C\u6574 URL\uFF0C\u4F1A\u81EA\u52A8\u63D0\u53D6 code\u3002");
9050
- info("\u672C\u7A97\u53E3\u4F1A\u81EA\u52A8\u68C0\u6D4B\u5E76\u5B8C\u6210\u767B\u5F55\u3002");
9051
- info("=".repeat(64));
9016
+ info("\u8BF7\u590D\u5236 code= \u540E\u9762\u7684\u503C\uFF08\u5230 & \u4E4B\u524D\uFF09\uFF0C\u6216\u76F4\u63A5\u7C98\u8D34\u5B8C\u6574 URL\u3002");
9052
9017
  let code = "";
9053
- const deadline = Date.now() + 3e5;
9054
- while (Date.now() < deadline) {
9055
- await new Promise((r) => setTimeout(r, 1500));
9056
- if (!existsSync(codeTmpFile)) continue;
9057
- let raw = "";
9018
+ const raw = await askInput("\n\u8BF7\u7C98\u8D34 code \u6216\u5B8C\u6574 URL: ");
9019
+ if (!raw) {
9020
+ throw new Error("\u672A\u83B7\u53D6\u5230\u6388\u6743 code");
9021
+ }
9022
+ const cleaned = raw.replace(/\\([?=&#])/g, "$1");
9023
+ if (cleaned.includes("open.weixin.qq.com/connect/qrconnect")) {
9024
+ throw new Error("\u8FD9\u662F\u626B\u7801\u9875\u9762\u7684 URL\uFF0C\u4E0D\u662F\u56DE\u8C03 URL\u3002\u8BF7\u5148\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00\u6B64\u94FE\u63A5\u626B\u7801\uFF0C\u626B\u7801\u786E\u8BA4\u540E\u6D4F\u89C8\u5668\u4F1A\u8DF3\u8F6C\u5230\u65B0\u9875\u9762\uFF0C\u518D\u590D\u5236\u65B0\u9875\u9762\u5730\u5740\u680F\u4E2D\u7684 URL\u3002");
9025
+ }
9026
+ if (cleaned.includes("code=")) {
9058
9027
  try {
9059
- raw = readFileSync3(codeTmpFile, "utf-8").trim();
9060
- unlinkSync2(codeTmpFile);
9028
+ const url = new URL(cleaned);
9029
+ const c = url.searchParams.get("code");
9030
+ if (c) code = c;
9061
9031
  } catch {
9062
- continue;
9063
- }
9064
- if (!raw) continue;
9065
- raw = raw.replace(/\\([?=&#])/g, "$1");
9066
- if (raw.includes("code=")) {
9067
- try {
9068
- const url = new URL(raw);
9069
- const c = url.searchParams.get("code");
9070
- if (c) {
9071
- code = c;
9072
- break;
9073
- }
9074
- } catch {
9075
- const match = raw.match(/[?&#]code=([^&#]+)/);
9076
- if (match?.[1]) {
9077
- code = match[1];
9078
- break;
9079
- }
9080
- }
9032
+ const match = cleaned.match(/[?&#]code=([^&#]+)/);
9033
+ if (match?.[1]) code = match[1];
9081
9034
  }
9082
- code = raw;
9083
- break;
9084
- }
9085
- try {
9086
- unlinkSync2(codeTmpFile);
9087
- } catch {
9088
9035
  }
9089
- if (!code) {
9090
- throw new Error("\u672A\u83B7\u53D6\u5230\u6388\u6743 code\uFF08\u8D85\u65F6\uFF09");
9036
+ if (!code) code = cleaned;
9037
+ if (code.startsWith("http")) {
9038
+ throw new Error("\u65E0\u6CD5\u4ECE URL \u4E2D\u63D0\u53D6 code\u3002\u8BF7\u786E\u8BA4\u7C98\u8D34\u7684\u662F\u626B\u7801\u786E\u8BA4\u540E\u8DF3\u8F6C\u9875\u9762\u7684 URL\u3002");
9091
9039
  }
9092
9040
  info(`[Login] \u6B65\u9AA4 4/5: \u7528\u6388\u6743\u7801\u767B\u5F55 (code=${code.substring(0, 10)}...)`);
9093
9041
  const loginResult = await api.wxLogin(code, state);
@@ -9125,7 +9073,9 @@ var performLogin = async (options) => {
9125
9073
  if (!verified) {
9126
9074
  let codeToSubmit = inviteCode;
9127
9075
  if (codeToSubmit === void 0) {
9128
- codeToSubmit = await waitForInviteCodeFile(info);
9076
+ info("");
9077
+ info("\u9700\u8981\u9080\u8BF7\u7801\u9A8C\u8BC1\u3002\u6CA1\u6709\u9080\u8BF7\u7801\u53EF\u76F4\u63A5\u6309\u56DE\u8F66\u8DF3\u8FC7\u3002");
9078
+ codeToSubmit = await askInput("\u8BF7\u8F93\u5165\u9080\u8BF7\u7801: ");
9129
9079
  }
9130
9080
  const submitResult = await api.submitInviteCode(userId, codeToSubmit ?? "");
9131
9081
  if (!submitResult.success) {
@@ -9146,109 +9096,123 @@ var performLogin = async (options) => {
9146
9096
  apiKey,
9147
9097
  guid
9148
9098
  };
9149
- const persistedState = {
9150
- jwtToken,
9151
- channelToken,
9152
- apiKey,
9153
- guid,
9154
- userInfo,
9155
- savedAt: Date.now()
9156
- };
9157
- saveState(persistedState);
9158
- info("[Login] \u767B\u5F55\u6001\u5DF2\u4FDD\u5B58");
9159
- info("[Login] \u5F00\u59CB\u8BBE\u5907\u7ED1\u5B9A...");
9160
- const bindResult = await performDeviceBinding({
9161
- api,
9162
- log: log2 ?? { info: console.log, warn: console.warn, error: console.error }
9163
- });
9164
- if (bindResult.success) {
9165
- info(`[Login] ${bindResult.message}`);
9166
- } else {
9167
- warn(`[Login] ${bindResult.message}`);
9168
- warn("[Login] \u53EF\u7A0D\u540E\u91CD\u65B0\u6267\u884C\u767B\u5F55\u547D\u4EE4\u5B8C\u6210\u7ED1\u5B9A\u3002");
9169
- }
9099
+ info("[Login] \u767B\u5F55\u5B8C\u6210");
9170
9100
  return credentials;
9171
9101
  };
9172
9102
 
9173
- // auth/wechat-qr-poll.ts
9174
- var buildAuthUrl = (state, env) => {
9175
- const params = new URLSearchParams({
9176
- appid: env.wxAppId,
9177
- redirect_uri: env.wxLoginRedirectUri,
9178
- response_type: "code",
9179
- scope: "snsapi_login",
9180
- state
9181
- });
9182
- return `https://open.weixin.qq.com/connect/qrconnect?${params.toString()}#wechat_redirect`;
9183
- };
9184
- var fetchQrUuid = async (authUrl) => {
9185
- const res = await fetch(authUrl, {
9186
- headers: {
9187
- "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
9188
- },
9189
- signal: AbortSignal.timeout(15e3)
9190
- });
9191
- const html = await res.text();
9192
- const match = html.match(/\/connect\/qrcode\/([a-zA-Z0-9_=-]+)/);
9193
- if (!match?.[1]) {
9194
- throw new Error("\u65E0\u6CD5\u4ECE\u5FAE\u4FE1\u767B\u5F55\u9875\u9762\u63D0\u53D6 QR UUID");
9195
- }
9196
- return match[1];
9197
- };
9198
- var fetchQrImageDataUrl = async (uuid) => {
9199
- const url = `https://open.weixin.qq.com/connect/qrcode/${uuid}`;
9200
- const res = await fetch(url, { signal: AbortSignal.timeout(15e3) });
9201
- const buf = Buffer.from(await res.arrayBuffer());
9202
- const contentType = res.headers.get("content-type") || "image/png";
9203
- return `data:${contentType};base64,${buf.toString("base64")}`;
9204
- };
9205
- var pollQrStatus = async (uuid) => {
9103
+ // auth/device-bind.ts
9104
+ var DEFAULT_OPEN_KFID = "wkzLlJLAAAfbxEV3ZcS-lHZxkaKmpejQ";
9105
+ var POLL_INTERVAL_MS = 2e3;
9106
+ var DEFAULT_TIMEOUT_MS = 3e5;
9107
+ async function defaultShowQr(url) {
9206
9108
  try {
9207
- const url = `https://lp.open.weixin.qq.com/connect/l/qrconnect?uuid=${uuid}&_=${Date.now()}`;
9208
- const res = await fetch(url, {
9209
- headers: {
9210
- "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
9211
- },
9212
- signal: AbortSignal.timeout(3e4)
9109
+ const qrterm = await import("qrcode-terminal");
9110
+ const generate = qrterm.default?.generate ?? qrterm.generate;
9111
+ generate(url, { small: true }, (qrcode) => {
9112
+ console.log(qrcode);
9213
9113
  });
9214
- const text = await res.text();
9215
- const errMatch = text.match(/wx_errcode=(\d+)/);
9216
- const codeMatch = text.match(/wx_code='([^']*)'/);
9217
- const errCode = errMatch ? parseInt(errMatch[1], 10) : 0;
9218
- const wxCode = codeMatch?.[1] || "";
9219
- if (errCode === 408) return { status: "waiting" };
9220
- if (errCode === 404) return { status: "scanned" };
9221
- if (errCode === 405 && wxCode) return { status: "confirmed", code: wxCode };
9222
- if (errCode === 403 || errCode === 402) return { status: "expired" };
9223
- return { status: "error" };
9224
9114
  } catch {
9225
- return { status: "error" };
9226
9115
  }
9227
- };
9116
+ }
9117
+ async function performDeviceBinding(options) {
9118
+ const {
9119
+ api,
9120
+ openKfId = DEFAULT_OPEN_KFID,
9121
+ timeoutMs = DEFAULT_TIMEOUT_MS,
9122
+ log: log2 = { info: console.log, warn: console.warn, error: console.error },
9123
+ showQr = defaultShowQr
9124
+ } = options;
9125
+ log2.info("[device-bind] \u751F\u6210\u4F01\u5FAE\u5BA2\u670D\u94FE\u63A5...");
9126
+ let linkResult;
9127
+ try {
9128
+ linkResult = await api.generateContactLink(openKfId);
9129
+ } catch (e) {
9130
+ const msg = `\u751F\u6210\u5BA2\u670D\u94FE\u63A5\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`;
9131
+ log2.warn(`[device-bind] ${msg}`);
9132
+ return { success: false, message: msg };
9133
+ }
9134
+ if (!linkResult.success) {
9135
+ const msg = `\u751F\u6210\u5BA2\u670D\u94FE\u63A5\u5931\u8D25: ${linkResult.message ?? "\u672A\u77E5\u9519\u8BEF"}`;
9136
+ log2.warn(`[device-bind] ${msg}`);
9137
+ return { success: false, message: msg };
9138
+ }
9139
+ const linkData = linkResult.data;
9140
+ const contactUrl = nested(linkData, "url") || nested(linkData, "data", "url") || nested(linkData, "resp", "url") || "";
9141
+ if (!contactUrl) {
9142
+ const msg = "\u670D\u52A1\u7AEF\u672A\u8FD4\u56DE\u5BA2\u670D\u94FE\u63A5 URL";
9143
+ log2.warn(`[device-bind] ${msg}`);
9144
+ return { success: false, message: msg };
9145
+ }
9146
+ console.log("");
9147
+ console.log("=".repeat(64));
9148
+ console.log("\u8BF7\u7528\u300C\u63A7\u5236\u7AEF\u5FAE\u4FE1\u300D\u6253\u5F00\u4E0B\u65B9\u94FE\u63A5\uFF0C\u5B8C\u6210\u8BBE\u5907\u7ED1\u5B9A");
9149
+ console.log("\u7ED1\u5B9A\u540E\u5FAE\u4FE1\u4E2D\u4F1A\u51FA\u73B0\u5BF9\u8BDD\u5165\u53E3");
9150
+ console.log("=".repeat(64));
9151
+ await showQr(contactUrl);
9152
+ console.log(`
9153
+ \u94FE\u63A5: ${contactUrl}
9154
+ `);
9155
+ log2.info("[device-bind] \u7B49\u5F85\u8BBE\u5907\u7ED1\u5B9A...");
9156
+ const deadline = Date.now() + timeoutMs;
9157
+ while (Date.now() < deadline) {
9158
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
9159
+ try {
9160
+ const status = await api.queryDeviceByGuid();
9161
+ if (status.success) {
9162
+ const sd = status.data;
9163
+ const nickname = nested(sd, "nickname") || nested(sd, "data", "nickname");
9164
+ const externalUserId = nested(sd, "external_user_id") || nested(sd, "data", "external_user_id");
9165
+ if (nickname || externalUserId) {
9166
+ const msg = `\u8BBE\u5907\u7ED1\u5B9A\u6210\u529F!${nickname ? ` \u5FAE\u4FE1\u6635\u79F0: ${nickname}` : ""}`;
9167
+ log2.info(`[device-bind] ${msg}`);
9168
+ return { success: true, contactUrl, nickname: nickname || void 0, message: msg };
9169
+ }
9170
+ }
9171
+ } catch {
9172
+ }
9173
+ }
9174
+ return {
9175
+ success: false,
9176
+ contactUrl,
9177
+ message: "\u8BBE\u5907\u7ED1\u5B9A\u8D85\u65F6\u3002\u8BF7\u786E\u8BA4\u5DF2\u5728\u5FAE\u4FE1\u4E2D\u6253\u5F00\u4E0A\u65B9\u94FE\u63A5\uFF0C\u7136\u540E\u91CD\u542F Gateway \u91CD\u8BD5\u3002"
9178
+ };
9179
+ }
9228
9180
 
9229
9181
  // index.ts
9182
+ import { join as join3 } from "path";
9183
+ import { homedir as homedir3 } from "os";
9230
9184
  var wsClients = /* @__PURE__ */ new Map();
9231
- var pendingQrLogin = null;
9185
+ var wbHttpContext = null;
9186
+ var lastChatIdByTo = /* @__PURE__ */ new Map();
9187
+ function readChannelConfig() {
9188
+ const runtime2 = getWecomRuntime();
9189
+ const cfg = runtime2.config.loadConfig();
9190
+ return cfg?.channels?.["wechat-access-unqclawed"] ?? {};
9191
+ }
9192
+ async function writeChannelConfig(update) {
9193
+ const runtime2 = getWecomRuntime();
9194
+ const cfg = runtime2.config.loadConfig();
9195
+ const channels = { ...cfg.channels ?? {} };
9196
+ channels["wechat-access-unqclawed"] = {
9197
+ ...channels["wechat-access-unqclawed"] ?? {},
9198
+ ...update
9199
+ };
9200
+ await runtime2.config.writeConfigFile({ ...cfg, channels });
9201
+ }
9232
9202
  var meta = {
9233
9203
  id: "wechat-access-unqclawed",
9234
9204
  label: "\u817E\u8BAF\u901A\u8DEF",
9235
- /** 选择时的显示文本 */
9236
9205
  selectionLabel: "\u817E\u8BAF\u901A\u8DEF",
9237
9206
  detailLabel: "\u817E\u8BAF\u901A\u8DEF",
9238
- /** 文档路径 */
9239
9207
  docsPath: "/channels/wechat-access",
9240
9208
  docsLabel: "wechat-access-unqclawed",
9241
- /** 简介 */
9242
9209
  blurb: "\u901A\u7528\u901A\u8DEF",
9243
- /** 图标 */
9244
9210
  systemImage: "message.fill",
9245
- /** 排序权重 */
9246
9211
  order: 85
9247
9212
  };
9248
9213
  var tencentAccessPlugin = {
9249
9214
  id: "wechat-access-unqclawed",
9250
9215
  meta,
9251
- // 能力声明
9252
9216
  capabilities: {
9253
9217
  chatTypes: ["direct"],
9254
9218
  reactions: false,
@@ -9257,13 +9221,9 @@ var tencentAccessPlugin = {
9257
9221
  nativeCommands: false,
9258
9222
  blockStreaming: false
9259
9223
  },
9260
- // 热重载:token 或 wsUrl 变更时触发 gateway 重启
9261
9224
  reload: {
9262
9225
  configPrefixes: ["channels.wechat-access-unqclawed.token", "channels.wechat-access-unqclawed.wsUrl"]
9263
9226
  },
9264
- // 声明支持的 gateway 方法(框架通过此字段找到 login provider)
9265
- gatewayMethods: ["web.login.start", "web.login.wait"],
9266
- // 配置适配器(必需)
9267
9227
  config: {
9268
9228
  listAccountIds: (cfg) => {
9269
9229
  const accounts = cfg.channels?.["wechat-access-unqclawed"]?.accounts;
@@ -9276,174 +9236,62 @@ var tencentAccessPlugin = {
9276
9236
  const accounts = cfg.channels?.["wechat-access-unqclawed"]?.accounts;
9277
9237
  const account = accounts?.[accountId ?? "default"];
9278
9238
  return account ?? { accountId: accountId ?? "default" };
9279
- }
9239
+ },
9240
+ resolveAllowFrom: () => ["*"]
9280
9241
  },
9281
- // 认证适配器:openclaw channels login --channel wechat-access-unqclawed
9242
+ // 认证适配器:提示用户使用 CLI
9282
9243
  auth: {
9283
- login: async ({ cfg, accountId, runtime: runtime2 }) => {
9284
- const channelCfg = cfg?.channels?.["wechat-access-unqclawed"];
9285
- const envName = channelCfg?.environment ? String(channelCfg.environment) : "production";
9286
- const authStatePath = channelCfg?.authStatePath ? String(channelCfg.authStatePath) : void 0;
9287
- const env = getEnvironment(envName);
9288
- const guid = getDeviceGuid();
9289
- runtime2.log("[wechat-access] \u83B7\u53D6\u767B\u5F55 state...");
9290
- const api = new QClawAPI(env, guid);
9291
- const stateResult = await api.getWxLoginState();
9292
- let state = String(Math.floor(Math.random() * 1e4));
9293
- if (stateResult.success) {
9294
- const s = nested(stateResult.data, "state");
9295
- if (s) state = s;
9296
- }
9297
- runtime2.log("[wechat-access] \u751F\u6210\u5FAE\u4FE1\u767B\u5F55\u4E8C\u7EF4\u7801...");
9298
- const authUrl = buildAuthUrl(state, env);
9299
- try {
9300
- const qrterm = await import("qrcode-terminal");
9301
- const generate = qrterm.default?.generate ?? qrterm.generate;
9302
- generate(authUrl, { small: true }, (qrcode) => {
9303
- runtime2.log("\n" + qrcode);
9304
- });
9305
- } catch {
9306
- runtime2.log("(qrcode-terminal \u4E0D\u53EF\u7528)");
9307
- }
9308
- runtime2.log(`
9309
- \u6216\u5728\u6D4F\u89C8\u5668\u6253\u5F00: ${authUrl}
9310
- `);
9311
- const { join: join4 } = await import("path");
9312
- const { homedir: homedir4 } = await import("os");
9313
- const { readFileSync: readFileSync4, unlinkSync: unlinkSync3, existsSync: existsSync2 } = await import("fs");
9314
- const codeTmpFile = join4(homedir4(), ".openclaw", "wechat-auth-code.tmp");
9315
- try {
9316
- unlinkSync3(codeTmpFile);
9317
- } catch {
9318
- }
9319
- runtime2.log("");
9320
- runtime2.log("=".repeat(64));
9321
- runtime2.log("\u626B\u7801\u5E76\u5728\u624B\u673A\u4E0A\u786E\u8BA4\u540E\uFF0C\u6D4F\u89C8\u5668\u4F1A\u8DF3\u8F6C\u5230\u65B0\u9875\u9762\u3002");
9322
- runtime2.log("\u5730\u5740\u680F URL \u5F62\u5982\uFF1A");
9323
- runtime2.log("");
9324
- runtime2.log("https://security.guanjia.qq.com/login?code=0a1B2c...&state=xxx");
9325
- runtime2.log("");
9326
- runtime2.log("\u8BF7\u590D\u5236 code= \u540E\u9762\u7684\u503C\uFF08\u5230 & \u4E4B\u524D\uFF09\uFF0C\u5728\u53E6\u4E00\u4E2A\u7EC8\u7AEF\u6267\u884C\uFF1A");
9327
- runtime2.log("");
9328
- runtime2.log(`echo "0a1B2c3D4e" > ${codeTmpFile}`);
9329
- runtime2.log("");
9330
- runtime2.log("\u4E5F\u53EF\u4EE5\u76F4\u63A5\u7C98\u8D34\u5B8C\u6574 URL\uFF0C\u4F1A\u81EA\u52A8\u63D0\u53D6 code\u3002");
9331
- runtime2.log("\u672C\u7A97\u53E3\u4F1A\u81EA\u52A8\u68C0\u6D4B\u5E76\u5B8C\u6210\u767B\u5F55\u3002");
9332
- runtime2.log("=".repeat(64));
9333
- const deadline = Date.now() + 3e5;
9334
- while (Date.now() < deadline) {
9335
- await new Promise((r) => setTimeout(r, 1500));
9336
- if (!existsSync2(codeTmpFile)) continue;
9337
- let raw = "";
9338
- try {
9339
- raw = readFileSync4(codeTmpFile, "utf-8").trim();
9340
- unlinkSync3(codeTmpFile);
9341
- } catch {
9342
- continue;
9343
- }
9344
- if (!raw) continue;
9345
- raw = raw.replace(/\\([?=&#])/g, "$1");
9346
- let code = raw;
9347
- if (raw.includes("code=")) {
9348
- try {
9349
- const url = new URL(raw);
9350
- const c = url.searchParams.get("code");
9351
- if (c) code = c;
9352
- } catch {
9353
- const match = raw.match(/[?&#]code=([^&#]+)/);
9354
- if (match?.[1]) code = match[1];
9355
- }
9356
- }
9357
- if (!code) {
9358
- runtime2.log("[wechat-access] \u672A\u80FD\u4ECE\u8F93\u5165\u4E2D\u63D0\u53D6 code\u3002\u8BF7\u53EA\u590D\u5236 code= \u540E\u9762\u7684\u503C\uFF08\u5230 & \u4E4B\u524D\uFF09\uFF0C\u91CD\u65B0\u5199\u5165\u6587\u4EF6\u3002");
9359
- continue;
9360
- }
9361
- runtime2.log(`[wechat-access] \u6536\u5230 code: ${code.substring(0, 10)}...\uFF0C\u6B63\u5728\u83B7\u53D6 token...`);
9362
- const loginResult = await api.wxLogin(code, state);
9363
- if (!loginResult.success) {
9364
- throw new Error(`\u767B\u5F55\u5931\u8D25: ${loginResult.message ?? "\u672A\u77E5\u9519\u8BEF"}`);
9365
- }
9366
- const loginData = loginResult.data;
9367
- const jwtToken = nested(loginData, "token") || nested(loginData, "data", "token") || "";
9368
- const channelToken = nested(loginData, "openclaw_channel_token") || nested(loginData, "data", "openclaw_channel_token") || "";
9369
- const userInfo = nested(loginData, "user_info") || nested(loginData, "data", "user_info") || {};
9370
- runtime2.log(`[wechat-access] wxLogin \u54CD\u5E94: jwtToken=${jwtToken ? jwtToken.substring(0, 8) + "..." : "(\u7A7A)"}, channelToken=${channelToken ? channelToken.substring(0, 8) + "..." : "(\u7A7A)"}, userId=${nested(userInfo, "user_id") ?? "(\u65E0)"}`);
9371
- const loginKey = userInfo.loginKey;
9372
- if (loginKey) api.loginKey = loginKey;
9373
- api.jwtToken = jwtToken;
9374
- api.userId = String(userInfo.user_id ?? "");
9375
- let apiKey = "";
9376
- try {
9377
- const keyResult = await api.createApiKey();
9378
- if (keyResult.success) {
9379
- apiKey = nested(keyResult.data, "key") ?? nested(keyResult.data, "resp", "data", "key") ?? "";
9244
+ login: async ({ runtime: runtime2 }) => {
9245
+ runtime2.log("[wechat-access] \u8BF7\u4F7F\u7528 'openclaw wechat login' \u547D\u4EE4\u767B\u5F55");
9246
+ }
9247
+ },
9248
+ // 出站适配器
9249
+ outbound: {
9250
+ deliveryMode: "direct",
9251
+ sendText: async (ctx) => {
9252
+ if (wbHttpContext) {
9253
+ const chatId = lastChatIdByTo.get(ctx.to) || lastChatIdByTo.get(ctx.to.split(":").pop() || "");
9254
+ if (!chatId) {
9255
+ console.warn(`[wechat-access] sendText: \u672A\u627E\u5230 chatId for to=${ctx.to}, \u8DF3\u8FC7`);
9256
+ return { ok: true };
9257
+ }
9258
+ const httpPayload = {
9259
+ type: "COPILOT_RESPONSE",
9260
+ msgId: ctx.replyToId || `outbound-${Date.now()}`,
9261
+ chatId,
9262
+ success: true,
9263
+ message: ctx.text,
9264
+ metadata: {
9265
+ sessionId: wbHttpContext.sessionId,
9266
+ state: "completed"
9380
9267
  }
9381
- } catch {
9382
- }
9383
- const runtimeLog = {
9384
- info: (...args) => runtime2.log(...args),
9385
- warn: (...args) => runtime2.log(...args),
9386
- error: (...args) => runtime2.log(...args)
9387
9268
  };
9388
- const bindResult = await performDeviceBinding({
9389
- api,
9390
- log: runtimeLog,
9391
- showQr: async (url) => {
9392
- try {
9393
- const qrterm = await import("qrcode-terminal");
9394
- const generate = qrterm.default?.generate ?? qrterm.generate;
9395
- generate(url, { small: true }, (qrcode) => {
9396
- runtime2.log("\n" + qrcode);
9397
- });
9398
- } catch {
9399
- runtime2.log("(qrcode-terminal \u4E0D\u53EF\u7528)");
9400
- }
9401
- }
9402
- });
9403
- if (!bindResult.success) {
9404
- throw new Error(`\u8BBE\u5907\u7ED1\u5B9A\u5931\u8D25: ${bindResult.message}\u3002\u8BF7\u91CD\u65B0\u767B\u5F55\u91CD\u8BD5\u3002`);
9405
- }
9406
- runtime2.log(`[wechat-access] ${bindResult.message}`);
9269
+ const url = `${wbHttpContext.baseUrl}/v2/backgroundagent/wecom/local-proxy/receive`;
9407
9270
  try {
9408
- const wRuntime = getWecomRuntime();
9409
- const fullCfg = wRuntime.config.loadConfig();
9410
- const channels = { ...fullCfg.channels ?? {} };
9411
- channels["wechat-access-unqclawed"] = {
9412
- ...channels["wechat-access-unqclawed"] ?? {},
9413
- token: channelToken,
9414
- wsUrl: env.wechatWsUrl,
9415
- guid,
9416
- userId: String(userInfo.user_id ?? "")
9417
- };
9418
- const nextCfg = { ...fullCfg, channels };
9419
- if (apiKey) {
9420
- const models = { ...fullCfg.models ?? {} };
9421
- const providers = { ...models.providers ?? {} };
9422
- providers.qclaw = { ...providers.qclaw ?? {}, apiKey };
9423
- models.providers = providers;
9424
- nextCfg.models = models;
9271
+ const res = await fetch(url, {
9272
+ method: "POST",
9273
+ headers: {
9274
+ "Content-Type": "application/json",
9275
+ "Authorization": `Bearer ${wbHttpContext.accessToken}`
9276
+ },
9277
+ body: JSON.stringify(httpPayload),
9278
+ signal: AbortSignal.timeout(3e4)
9279
+ });
9280
+ if (!res.ok) {
9281
+ const body = await res.text().catch(() => "");
9282
+ console.error(`[wechat-access] sendText HTTP \u5931\u8D25: ${res.status} ${body.substring(0, 200)}`);
9283
+ } else {
9284
+ console.log(`[wechat-access] sendText HTTP \u53D1\u9001\u6210\u529F: to=${ctx.to}`);
9425
9285
  }
9426
- await wRuntime.config.writeConfigFile(nextCfg);
9427
- } catch {
9286
+ } catch (err) {
9287
+ console.error(`[wechat-access] sendText HTTP \u5F02\u5E38:`, err);
9428
9288
  }
9429
- saveState({ jwtToken, channelToken, apiKey, guid, userInfo, savedAt: Date.now() }, authStatePath);
9430
- const nickname = userInfo.nickname ?? "\u7528\u6237";
9431
- runtime2.log(`[wechat-access] \u767B\u5F55\u6210\u529F! \u6B22\u8FCE ${nickname}\uFF0Ctoken \u5DF2\u4FDD\u5B58\u3002\u8BF7\u91CD\u542F Gateway \u751F\u6548\u3002`);
9432
- return;
9433
- }
9434
- try {
9435
- unlinkSync3(codeTmpFile);
9436
- } catch {
9289
+ return { ok: true };
9437
9290
  }
9438
- throw new Error("\u767B\u5F55\u8D85\u65F6\uFF085 \u5206\u949F\uFF09\uFF0C\u8BF7\u91CD\u8BD5");
9291
+ return { ok: true };
9439
9292
  }
9440
9293
  },
9441
- // 出站适配器(必需)
9442
- outbound: {
9443
- deliveryMode: "direct",
9444
- sendText: async () => ({ ok: true })
9445
- },
9446
- // 状态适配器:上报 WebSocket 连接状态
9294
+ // 状态适配器
9447
9295
  status: {
9448
9296
  buildAccountSnapshot: ({ accountId }) => {
9449
9297
  const client = wsClients.get(accountId ?? "default");
@@ -9451,68 +9299,62 @@ var tencentAccessPlugin = {
9451
9299
  return { running };
9452
9300
  }
9453
9301
  },
9454
- // Gateway 适配器:按账号启动/停止 WebSocket 连接
9302
+ // Gateway 适配器
9455
9303
  gateway: {
9456
9304
  startAccount: async (ctx) => {
9457
9305
  const { cfg, accountId, abortSignal, log: log2 } = ctx;
9458
9306
  log2?.info(`[wechat-access] >>> startAccount \u88AB\u8C03\u7528, accountId=${accountId}`);
9459
9307
  try {
9460
- const tencentAccessConfig = cfg?.channels?.["wechat-access-unqclawed"];
9461
- log2?.info(`[wechat-access] tencentAccessConfig=${JSON.stringify(tencentAccessConfig ?? null)}`);
9462
- const authStatePath = tencentAccessConfig?.authStatePath ? String(tencentAccessConfig.authStatePath) : void 0;
9308
+ const channelCfg = readChannelConfig();
9463
9309
  const gatewayPort = cfg?.gateway?.port ? String(cfg.gateway.port) : "unknown";
9464
9310
  const guid = getDeviceGuid();
9465
- const savedState = loadState(authStatePath);
9466
- const loginMode = tencentAccessConfig?.loginMode || savedState?.loginMode || "qclaw";
9311
+ const loginMode = channelCfg.loginMode || "qclaw";
9467
9312
  log2?.info(`[wechat-access] \u542F\u52A8\u8D26\u53F7 ${accountId}, loginMode=${loginMode}`);
9468
- if (loginMode === "codebuddy") {
9469
- log2?.info(`[wechat-access] CodeBuddy \u6A21\u5F0F, savedState\u5B58\u5728=${!!savedState}, accessToken\u5B58\u5728=${!!savedState?.accessToken}, loginMode=${savedState?.loginMode}`);
9470
- if (!savedState?.accessToken) {
9471
- log2?.warn(`[wechat-access] CodeBuddy \u6A21\u5F0F\u672A\u627E\u5230 accessToken\uFF0C\u8BF7\u8FD0\u884C "openclaw wechat login --mode codebuddy" \u5B8C\u6210\u767B\u5F55`);
9313
+ if (loginMode === "workbuddy") {
9314
+ const creds = channelCfg.workbuddy;
9315
+ log2?.info(`[wechat-access] WorkBuddy \u6A21\u5F0F, hasCredentials=${!!creds}, hasAccessToken=${!!creds?.accessToken}`);
9316
+ if (!creds?.accessToken) {
9317
+ log2?.warn(`[wechat-access] WorkBuddy \u6A21\u5F0F\u672A\u627E\u5230 accessToken\uFF0C\u8BF7\u8FD0\u884C "openclaw wechat login" \u5B8C\u6210\u767B\u5F55`);
9318
+ return;
9319
+ }
9320
+ const cbApi = new CodeBuddyAPI(creds.baseUrl);
9321
+ cbApi.accessToken = creds.accessToken;
9322
+ cbApi.refreshToken = creds.refreshToken || "";
9323
+ cbApi.userId = creds.userId || String(creds.userInfo?.userId ?? "") || String(creds.userInfo?.user_id ?? "") || String(creds.userInfo?.uid ?? "");
9324
+ cbApi.hostId = creds.hostId || cbApi.hostId;
9325
+ log2?.info(`[wechat-access] CodeBuddy API \u521D\u59CB\u5316\u5B8C\u6210, userId=${cbApi.userId}, hostId=${cbApi.hostId}`);
9326
+ if (!cbApi.userId) {
9327
+ log2?.warn(`[wechat-access] userId \u4E3A\u7A7A\uFF0C\u8BF7\u91CD\u65B0\u8FD0\u884C "openclaw wechat login" \u767B\u5F55`);
9472
9328
  return;
9473
9329
  }
9474
- const cbBaseUrl = tencentAccessConfig?.codebuddyBaseUrl ? String(tencentAccessConfig.codebuddyBaseUrl) : void 0;
9475
- const cbApi = new CodeBuddyAPI(cbBaseUrl);
9476
- cbApi.accessToken = savedState.accessToken;
9477
- cbApi.refreshToken = savedState.refreshToken || "";
9478
- cbApi.userId = String(savedState.userInfo?.userId ?? "");
9479
- cbApi.hostId = savedState.hostId || cbApi.hostId;
9480
- log2?.info(`[wechat-access] CodeBuddy API \u521D\u59CB\u5316\u5B8C\u6210, userId=${cbApi.userId}, hostId=${cbApi.hostId}, hasRefreshToken=${!!cbApi.refreshToken}`);
9481
9330
  try {
9482
- log2?.info(`[wechat-access] \u6B63\u5728\u5237\u65B0 CodeBuddy accessToken...`);
9483
- const refreshed = await cbApi.doRefreshToken();
9484
- log2?.info(`[wechat-access] CodeBuddy accessToken \u5DF2\u5237\u65B0`);
9485
- if (savedState) {
9486
- savedState.accessToken = refreshed.accessToken;
9487
- savedState.refreshToken = refreshed.refreshToken;
9488
- savedState.savedAt = Date.now();
9489
- saveState(savedState, authStatePath);
9490
- }
9331
+ log2?.info(`[wechat-access] \u6B63\u5728\u5237\u65B0 accessToken...`);
9332
+ await cbApi.doRefreshToken();
9333
+ log2?.info(`[wechat-access] accessToken \u5DF2\u5237\u65B0`);
9491
9334
  } catch (e) {
9492
9335
  const msg = e instanceof Error ? e.message : String(e);
9493
- log2?.warn(`[wechat-access] CodeBuddy token \u5237\u65B0\u5F02\u5E38: ${msg}`);
9336
+ log2?.warn(`[wechat-access] token \u5237\u65B0\u5F02\u5E38: ${msg}`);
9494
9337
  if (msg.includes("\u8FC7\u671F") || msg.includes("401") || msg.includes("403")) {
9495
- clearState(authStatePath);
9496
- log2?.warn(`[wechat-access] CodeBuddy token \u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u8FD0\u884C "openclaw wechat login --mode codebuddy"`);
9338
+ log2?.warn(`[wechat-access] token \u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u8FD0\u884C "openclaw wechat login"`);
9497
9339
  return;
9498
9340
  }
9499
- log2?.warn(`[wechat-access] CodeBuddy token \u5237\u65B0\u5931\u8D25\uFF0C\u4F7F\u7528\u65E7 token \u7EE7\u7EED`);
9341
+ log2?.warn(`[wechat-access] token \u5237\u65B0\u5931\u8D25\uFF0C\u4F7F\u7528\u65E7 token \u7EE7\u7EED`);
9500
9342
  }
9501
- log2?.info(`[wechat-access] \u6B63\u5728\u6CE8\u518C Workspace...`);
9343
+ log2?.info(`[wechat-access] \u6B63\u5728\u6CE8\u518C Host Channel...`);
9502
9344
  let centrifugeParams;
9503
9345
  try {
9504
9346
  centrifugeParams = await cbApi.registerWorkspace({
9505
9347
  userId: cbApi.userId,
9506
9348
  hostId: cbApi.hostId,
9507
- workspaceId: `openclaw-${accountId}`,
9508
- workspaceName: `openclaw-${accountId}`
9349
+ workspaceId: "",
9350
+ workspaceName: `Host Channel (${cbApi.hostId})`
9509
9351
  });
9510
- log2?.info(`[wechat-access] Workspace \u6CE8\u518C\u6210\u529F, channel=${centrifugeParams.channel}, url=${centrifugeParams.url}`);
9352
+ log2?.info(`[wechat-access] Host Channel \u6CE8\u518C\u6210\u529F, channel=${centrifugeParams.channel}, url=${centrifugeParams.url}`);
9511
9353
  } catch (e) {
9512
9354
  log2?.error(`[wechat-access] Workspace \u6CE8\u518C\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`);
9513
9355
  return;
9514
9356
  }
9515
- const client2 = new CentrifugeGatewayClient(
9357
+ const client = new CentrifugeGatewayClient(
9516
9358
  {
9517
9359
  url: centrifugeParams.url,
9518
9360
  connectionToken: centrifugeParams.connectionToken,
@@ -9520,37 +9362,62 @@ var tencentAccessPlugin = {
9520
9362
  subscriptionToken: centrifugeParams.subscriptionToken,
9521
9363
  guid,
9522
9364
  userId: cbApi.userId,
9523
- gatewayPort
9365
+ gatewayPort,
9366
+ httpBaseUrl: creds.baseUrl || "https://copilot.tencent.com",
9367
+ httpAccessToken: cbApi.accessToken,
9368
+ workspaceSessionId: cbApi.buildSessionId()
9524
9369
  },
9525
9370
  {
9526
9371
  onConnected: () => {
9527
9372
  log2?.info(`[wechat-access] Centrifuge \u8FDE\u63A5\u6210\u529F`);
9528
9373
  ctx.setStatus({ running: true });
9374
+ const clawPath = join3(homedir3(), "WorkBuddy", "Claw");
9375
+ cbApi.registerWorkspace({
9376
+ userId: cbApi.userId,
9377
+ hostId: cbApi.hostId,
9378
+ workspaceId: clawPath,
9379
+ workspaceName: "Claw"
9380
+ }).then((clawParams) => {
9381
+ log2?.info(`[wechat-access] Claw workspace \u6CE8\u518C\u6210\u529F, channel=${clawParams.channel}`);
9382
+ client.subscribeChannel(clawParams.channel, clawParams.subscriptionToken);
9383
+ }).catch((e) => {
9384
+ log2?.warn(`[wechat-access] Claw workspace \u6CE8\u518C\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`);
9385
+ });
9529
9386
  },
9530
9387
  onDisconnected: (reason) => {
9531
9388
  log2?.warn(`[wechat-access] Centrifuge \u8FDE\u63A5\u65AD\u5F00: ${reason}`);
9532
9389
  ctx.setStatus({ running: false });
9533
9390
  },
9534
9391
  onPrompt: (message) => {
9535
- void handlePrompt(message, client2).catch((err) => {
9392
+ if (message._wechatKf?.chatId) {
9393
+ const sessionKey = `agent:main:wechat-access:direct:${cbApi.userId}`;
9394
+ lastChatIdByTo.set(sessionKey, message._wechatKf.chatId);
9395
+ lastChatIdByTo.set(cbApi.userId, message._wechatKf.chatId);
9396
+ }
9397
+ void handlePrompt(message, client).catch((err) => {
9536
9398
  log2?.error(`[wechat-access] \u5904\u7406 prompt \u5931\u8D25: ${err.message}`);
9537
9399
  });
9538
9400
  },
9539
9401
  onCancel: (message) => {
9540
- handleCancel(message, client2);
9402
+ handleCancel(message, client);
9541
9403
  },
9542
9404
  onError: (error) => {
9543
9405
  log2?.error(`[wechat-access] Centrifuge \u9519\u8BEF: ${error.message}`);
9544
9406
  }
9545
9407
  }
9546
9408
  );
9547
- wsClients.set(accountId, client2);
9548
- client2.start();
9409
+ wsClients.set(accountId, client);
9410
+ client.start();
9411
+ wbHttpContext = {
9412
+ baseUrl: creds.baseUrl || "https://copilot.tencent.com",
9413
+ accessToken: cbApi.accessToken,
9414
+ sessionId: cbApi.buildSessionId()
9415
+ };
9549
9416
  await new Promise((resolve) => {
9550
9417
  abortSignal.addEventListener("abort", () => {
9551
9418
  log2?.info(`[wechat-access] \u505C\u6B62\u8D26\u53F7 ${accountId}`);
9552
- client2.stop();
9553
- if (wsClients.get(accountId) === client2) {
9419
+ client.stop();
9420
+ if (wsClients.get(accountId) === client) {
9554
9421
  wsClients.delete(accountId);
9555
9422
  ctx.setStatus({ running: false });
9556
9423
  }
@@ -9559,11 +9426,11 @@ var tencentAccessPlugin = {
9559
9426
  });
9560
9427
  return;
9561
9428
  }
9562
- let token = tencentAccessConfig?.token ? String(tencentAccessConfig.token) : "";
9563
- const configWsUrl = tencentAccessConfig?.wsUrl ? String(tencentAccessConfig.wsUrl) : "";
9564
- const envName = tencentAccessConfig?.environment ? String(tencentAccessConfig.environment) : "production";
9429
+ const qclawCreds = channelCfg.qclaw;
9430
+ const envName = channelCfg.environment ? String(channelCfg.environment) : "production";
9565
9431
  const env = getEnvironment(envName);
9566
- const wsUrl = configWsUrl || env.wechatWsUrl;
9432
+ const wsUrl = qclawCreds?.wsUrl || env.wechatWsUrl;
9433
+ let token = qclawCreds?.channelToken || "";
9567
9434
  log2?.info(`[wechat-access] QClaw \u6A21\u5F0F\u542F\u52A8`, {
9568
9435
  platform: process.platform,
9569
9436
  nodeVersion: process.version,
@@ -9572,19 +9439,15 @@ var tencentAccessPlugin = {
9572
9439
  url: wsUrl || "(\u672A\u914D\u7F6E)",
9573
9440
  tokenPrefix: token ? token.substring(0, 6) + "..." : "(\u672A\u914D\u7F6E)"
9574
9441
  });
9575
- if (!token && savedState?.channelToken) {
9576
- token = savedState.channelToken;
9577
- log2?.info(`[wechat-access] \u4F7F\u7528\u5DF2\u4FDD\u5B58\u7684 token: ${token.substring(0, 6)}...`);
9578
- }
9579
9442
  if (!token) {
9580
- log2?.warn(`[wechat-access] \u672A\u627E\u5230 token\uFF0C\u8BF7\u8FD0\u884C "openclaw channels login --channel wechat-access-unqclawed" \u5B8C\u6210\u626B\u7801\u767B\u5F55\uFF0C\u7136\u540E\u91CD\u542F Gateway`);
9443
+ log2?.warn(`[wechat-access] \u672A\u627E\u5230 token\uFF0C\u8BF7\u8FD0\u884C "openclaw wechat login" \u5B8C\u6210\u626B\u7801\u767B\u5F55\uFF0C\u7136\u540E\u91CD\u542F Gateway`);
9581
9444
  return;
9582
9445
  }
9583
- const jwtToken = savedState?.jwtToken || "";
9446
+ const jwtToken = qclawCreds?.jwtToken || "";
9584
9447
  if (jwtToken) {
9585
9448
  const api = new QClawAPI(env, guid, jwtToken);
9586
- api.userId = String(savedState?.userInfo?.user_id ?? "");
9587
- const savedLoginKey = savedState?.userInfo?.loginKey;
9449
+ api.userId = qclawCreds?.userId || "";
9450
+ const savedLoginKey = qclawCreds?.userInfo?.loginKey;
9588
9451
  if (savedLoginKey) api.loginKey = savedLoginKey;
9589
9452
  let refreshed = false;
9590
9453
  for (let attempt = 0; attempt < 3; attempt++) {
@@ -9593,29 +9456,12 @@ var tencentAccessPlugin = {
9593
9456
  if (newToken) {
9594
9457
  token = newToken;
9595
9458
  log2?.info(`[wechat-access] channel_token \u5DF2\u5237\u65B0: ${token.substring(0, 6)}...`);
9596
- if (savedState) {
9597
- savedState.channelToken = newToken;
9598
- savedState.savedAt = Date.now();
9599
- saveState(savedState, authStatePath);
9600
- }
9601
- try {
9602
- const wRuntime = getWecomRuntime();
9603
- const fullCfg = wRuntime.config.loadConfig();
9604
- const channels = { ...fullCfg.channels ?? {} };
9605
- channels["wechat-access-unqclawed"] = {
9606
- ...channels["wechat-access-unqclawed"] ?? {},
9607
- token: newToken
9608
- };
9609
- await wRuntime.config.writeConfigFile({ ...fullCfg, channels });
9610
- } catch {
9611
- }
9612
9459
  refreshed = true;
9613
9460
  break;
9614
9461
  }
9615
9462
  } catch (e) {
9616
9463
  if (e instanceof TokenExpiredError) {
9617
- clearState(authStatePath);
9618
- log2?.warn(`[wechat-access] jwt_token \u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u8FD0\u884C "openclaw channels login --channel wechat-access-unqclawed"`);
9464
+ log2?.warn(`[wechat-access] jwt_token \u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u8FD0\u884C "openclaw wechat login"`);
9619
9465
  return;
9620
9466
  }
9621
9467
  log2?.warn(`[wechat-access] token \u5237\u65B0\u5931\u8D25 (${attempt + 1}/3): ${e instanceof Error ? e.message : String(e)}`);
@@ -9626,7 +9472,7 @@ var tencentAccessPlugin = {
9626
9472
  log2?.info(`[wechat-access] token \u5237\u65B0\u5931\u8D25\uFF0C\u4F7F\u7528\u65E7 token \u5C1D\u8BD5\u8FDE\u63A5`);
9627
9473
  }
9628
9474
  }
9629
- const userId = tencentAccessConfig?.userId ? String(tencentAccessConfig.userId) : String(savedState?.userInfo?.user_id ?? "");
9475
+ const userId = qclawCreds?.userId || "";
9630
9476
  const wsConfig = {
9631
9477
  url: wsUrl,
9632
9478
  token,
@@ -9637,7 +9483,7 @@ var tencentAccessPlugin = {
9637
9483
  maxReconnectAttempts: 10,
9638
9484
  heartbeatInterval: 2e4
9639
9485
  };
9640
- const client = new WechatAccessWebSocketClient(wsConfig, {
9486
+ const qclawClient = new WechatAccessWebSocketClient(wsConfig, {
9641
9487
  onConnected: () => {
9642
9488
  log2?.info(`[wechat-access] WebSocket \u8FDE\u63A5\u6210\u529F`);
9643
9489
  ctx.setStatus({ running: true });
@@ -9647,24 +9493,24 @@ var tencentAccessPlugin = {
9647
9493
  ctx.setStatus({ running: false });
9648
9494
  },
9649
9495
  onPrompt: (message) => {
9650
- void handlePrompt(message, client).catch((err) => {
9496
+ void handlePrompt(message, qclawClient).catch((err) => {
9651
9497
  log2?.error(`[wechat-access] \u5904\u7406 prompt \u5931\u8D25: ${err.message}`);
9652
9498
  });
9653
9499
  },
9654
9500
  onCancel: (message) => {
9655
- handleCancel(message, client);
9501
+ handleCancel(message, qclawClient);
9656
9502
  },
9657
9503
  onError: (error) => {
9658
9504
  log2?.error(`[wechat-access] WebSocket \u9519\u8BEF: ${error.message}`);
9659
9505
  }
9660
9506
  });
9661
- wsClients.set(accountId, client);
9662
- client.start();
9507
+ wsClients.set(accountId, qclawClient);
9508
+ qclawClient.start();
9663
9509
  await new Promise((resolve) => {
9664
9510
  abortSignal.addEventListener("abort", () => {
9665
9511
  log2?.info(`[wechat-access] \u505C\u6B62\u8D26\u53F7 ${accountId}`);
9666
- client.stop();
9667
- if (wsClients.get(accountId) === client) {
9512
+ qclawClient.stop();
9513
+ if (wsClients.get(accountId) === qclawClient) {
9668
9514
  wsClients.delete(accountId);
9669
9515
  ctx.setStatus({ running: false });
9670
9516
  }
@@ -9688,253 +9534,6 @@ ${e?.stack ?? ""}`);
9688
9534
  } else {
9689
9535
  log2?.warn(`[wechat-access] stopAccount: \u672A\u627E\u5230\u8D26\u53F7 ${accountId} \u7684\u5BA2\u6237\u7AEF`);
9690
9536
  }
9691
- },
9692
- // QR 扫码登录:生成二维码(openclaw channels login 调用)
9693
- loginWithQrStart: async (_params) => {
9694
- try {
9695
- const runtime2 = getWecomRuntime();
9696
- const cfg = runtime2.config.loadConfig();
9697
- const channelCfg = cfg?.channels?.["wechat-access-unqclawed"];
9698
- const loginMode = channelCfg?.loginMode || "qclaw";
9699
- if (loginMode === "codebuddy") {
9700
- const cbBaseUrl = channelCfg?.codebuddyBaseUrl ? String(channelCfg.codebuddyBaseUrl) : void 0;
9701
- const cbApi = new CodeBuddyAPI(cbBaseUrl);
9702
- const { authUrl: authUrl2, state: cbState } = await cbApi.fetchAuthState();
9703
- pendingQrLogin = { loginMode: "codebuddy", cbApi, cbState, cbPhase: "oauth" };
9704
- return {
9705
- // 没有 qrDataUrl,前端应该展示 authUrl 链接让用户在浏览器中打开
9706
- authUrl: authUrl2,
9707
- message: `\u8BF7\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00\u4EE5\u4E0B\u94FE\u63A5\u5B8C\u6210\u767B\u5F55\uFF1A
9708
- ${authUrl2}`
9709
- };
9710
- }
9711
- const envName = channelCfg?.environment ? String(channelCfg.environment) : "production";
9712
- const bypassInvite = channelCfg?.bypassInvite === true;
9713
- const authStatePath = channelCfg?.authStatePath ? String(channelCfg.authStatePath) : void 0;
9714
- const env = getEnvironment(envName);
9715
- const guid = getDeviceGuid();
9716
- const api = new QClawAPI(env, guid);
9717
- const stateResult = await api.getWxLoginState();
9718
- let state = String(Math.floor(Math.random() * 1e4));
9719
- if (stateResult.success) {
9720
- const s = nested(stateResult.data, "state");
9721
- if (s) state = s;
9722
- }
9723
- const authUrl = buildAuthUrl(state, env);
9724
- const uuid = await fetchQrUuid(authUrl);
9725
- const qrDataUrl = await fetchQrImageDataUrl(uuid);
9726
- pendingQrLogin = { loginMode: "qclaw", state, uuid, env, guid, bypassInvite, authStatePath };
9727
- return { qrDataUrl, message: "\u8BF7\u7528\u5FAE\u4FE1\u626B\u63CF\u4E8C\u7EF4\u7801\u767B\u5F55" };
9728
- } catch (err) {
9729
- return { message: `\u767B\u5F55\u521D\u59CB\u5316\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}` };
9730
- }
9731
- },
9732
- // QR 扫码登录:轮询扫码状态(openclaw channels login 循环调用)
9733
- loginWithQrWait: async (_params) => {
9734
- if (!pendingQrLogin) {
9735
- return { connected: false, message: "\u8BF7\u5148\u6267\u884C loginWithQrStart" };
9736
- }
9737
- if (pendingQrLogin.loginMode === "codebuddy") {
9738
- try {
9739
- const { cbApi, cbState, cbPhase } = pendingQrLogin;
9740
- if (!cbApi || !cbState) {
9741
- pendingQrLogin = null;
9742
- return { connected: false, message: "CodeBuddy \u767B\u5F55\u72B6\u6001\u5F02\u5E38" };
9743
- }
9744
- if (cbPhase === "oauth") {
9745
- try {
9746
- const url = `${cbApi.baseUrl}/v2/plugin/auth/token?state=${cbState}`;
9747
- const res = await fetch(url, {
9748
- method: "GET",
9749
- headers: { "Content-Type": "application/json", "X-No-Authorization": "true" },
9750
- signal: AbortSignal.timeout(5e3)
9751
- });
9752
- if (!res.ok) {
9753
- const body = await res.text().catch(() => "");
9754
- if (body.includes("11217")) {
9755
- return { connected: false, message: "\u7B49\u5F85\u6D4F\u89C8\u5668\u767B\u5F55..." };
9756
- }
9757
- return { connected: false, message: "\u7B49\u5F85\u6D4F\u89C8\u5668\u767B\u5F55..." };
9758
- }
9759
- const data = await res.json();
9760
- const token = data?.data;
9761
- if (!token?.accessToken) {
9762
- return { connected: false, message: "\u7B49\u5F85\u6D4F\u89C8\u5668\u767B\u5F55..." };
9763
- }
9764
- cbApi.accessToken = token.accessToken;
9765
- cbApi.refreshToken = token.refreshToken || "";
9766
- let userInfo = {};
9767
- try {
9768
- userInfo = await cbApi.getAccount(cbState);
9769
- } catch {
9770
- }
9771
- cbApi.userId = String(userInfo.userId ?? userInfo.user_id ?? "");
9772
- const sessionId = cbApi.buildSessionId(`openclaw-${_params.accountId ?? "default"}`);
9773
- pendingQrLogin.cbSessionId = sessionId;
9774
- let bindUrl = "";
9775
- try {
9776
- const linkResult = await cbApi.wechatkfGetLink(sessionId);
9777
- if (linkResult.success && linkResult.url) {
9778
- bindUrl = linkResult.url;
9779
- }
9780
- } catch {
9781
- }
9782
- const guid = getDeviceGuid();
9783
- const authStatePath = (() => {
9784
- try {
9785
- const rt = getWecomRuntime();
9786
- const c = rt.config.loadConfig();
9787
- const cc = c?.channels?.["wechat-access-unqclawed"];
9788
- return cc?.authStatePath ? String(cc.authStatePath) : void 0;
9789
- } catch {
9790
- return void 0;
9791
- }
9792
- })();
9793
- const persistedState = {
9794
- loginMode: "codebuddy",
9795
- jwtToken: "",
9796
- channelToken: "",
9797
- apiKey: "",
9798
- guid,
9799
- userInfo,
9800
- savedAt: Date.now(),
9801
- accessToken: cbApi.accessToken,
9802
- refreshToken: cbApi.refreshToken,
9803
- hostId: cbApi.hostId
9804
- };
9805
- saveState(persistedState, authStatePath);
9806
- try {
9807
- const wRuntime = getWecomRuntime();
9808
- const fullCfg = wRuntime.config.loadConfig();
9809
- const channels = { ...fullCfg.channels ?? {} };
9810
- channels["wechat-access-unqclawed"] = {
9811
- ...channels["wechat-access-unqclawed"] ?? {},
9812
- loginMode: "codebuddy"
9813
- };
9814
- await wRuntime.config.writeConfigFile({ ...fullCfg, channels });
9815
- } catch {
9816
- }
9817
- pendingQrLogin = null;
9818
- const nickname = String(userInfo.nickName ?? userInfo.nickname ?? "\u7528\u6237");
9819
- if (bindUrl) {
9820
- return {
9821
- connected: true,
9822
- bindUrl,
9823
- message: `CodeBuddy \u767B\u5F55\u6210\u529F! \u6B22\u8FCE ${nickname}\u3002
9824
-
9825
- \u8BF7\u5728\u5FAE\u4FE1\u4E2D\u6253\u5F00\u4EE5\u4E0B\u94FE\u63A5\u7ED1\u5B9A\u5FAE\u4FE1\u5BA2\u670D\u53F7\uFF08\u7ED1\u5B9A\u540E\u624D\u6709\u5BF9\u8BDD\u5165\u53E3\uFF09\uFF1A
9826
- ${bindUrl}
9827
-
9828
- \u7ED1\u5B9A\u5B8C\u6210\u540E\u8BF7\u91CD\u542F Gateway \u751F\u6548\u3002`
9829
- };
9830
- }
9831
- return {
9832
- connected: true,
9833
- message: `CodeBuddy \u767B\u5F55\u6210\u529F! \u6B22\u8FCE ${nickname}\u3002Token \u5DF2\u4FDD\u5B58\uFF0C\u8BF7\u91CD\u542F Gateway \u751F\u6548\u3002`
9834
- };
9835
- } catch (err) {
9836
- return { connected: false, message: "\u7B49\u5F85\u6D4F\u89C8\u5668\u767B\u5F55..." };
9837
- }
9838
- }
9839
- return { connected: false, message: "\u7B49\u5F85\u4E2D..." };
9840
- } catch (err) {
9841
- return { connected: false, message: `CodeBuddy \u8F6E\u8BE2\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}` };
9842
- }
9843
- }
9844
- try {
9845
- const result = await pollQrStatus(pendingQrLogin.uuid);
9846
- if (result.status === "waiting") {
9847
- return { connected: false, message: "\u7B49\u5F85\u626B\u7801..." };
9848
- }
9849
- if (result.status === "scanned") {
9850
- return { connected: false, message: "\u5DF2\u626B\u7801\uFF0C\u8BF7\u5728\u624B\u673A\u4E0A\u786E\u8BA4..." };
9851
- }
9852
- if (result.status === "expired") {
9853
- pendingQrLogin = null;
9854
- return { connected: false, message: "\u4E8C\u7EF4\u7801\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u6267\u884C openclaw channels login" };
9855
- }
9856
- if (result.status === "confirmed" && result.code) {
9857
- const { state, env, guid, authStatePath } = pendingQrLogin;
9858
- const api = new QClawAPI(env, guid);
9859
- const loginResult = await api.wxLogin(result.code, state);
9860
- if (!loginResult.success) {
9861
- pendingQrLogin = null;
9862
- return { connected: false, message: `\u767B\u5F55\u5931\u8D25: ${loginResult.message ?? "\u672A\u77E5\u9519\u8BEF"}` };
9863
- }
9864
- const loginData = loginResult.data;
9865
- const jwtToken = nested(loginData, "token") || nested(loginData, "data", "token") || "";
9866
- const channelToken = nested(loginData, "openclaw_channel_token") || nested(loginData, "data", "openclaw_channel_token") || "";
9867
- const userInfo = nested(loginData, "user_info") || nested(loginData, "data", "user_info") || {};
9868
- const loginKey = userInfo.loginKey;
9869
- if (loginKey) api.loginKey = loginKey;
9870
- api.jwtToken = jwtToken;
9871
- api.userId = String(userInfo.user_id ?? "");
9872
- let apiKey = "";
9873
- try {
9874
- const keyResult = await api.createApiKey();
9875
- if (keyResult.success) {
9876
- apiKey = nested(keyResult.data, "key") ?? nested(keyResult.data, "resp", "data", "key") ?? "";
9877
- }
9878
- } catch {
9879
- }
9880
- try {
9881
- const wRuntime = getWecomRuntime();
9882
- const fullCfg = wRuntime.config.loadConfig();
9883
- const channels = { ...fullCfg.channels ?? {} };
9884
- channels["wechat-access-unqclawed"] = {
9885
- ...channels["wechat-access-unqclawed"] ?? {},
9886
- token: channelToken,
9887
- guid,
9888
- userId: String(userInfo.user_id ?? "")
9889
- };
9890
- const nextCfg = { ...fullCfg, channels };
9891
- if (apiKey) {
9892
- const models = { ...fullCfg.models ?? {} };
9893
- const providers = { ...models.providers ?? {} };
9894
- providers.qclaw = { ...providers.qclaw ?? {}, apiKey };
9895
- models.providers = providers;
9896
- nextCfg.models = models;
9897
- }
9898
- await wRuntime.config.writeConfigFile(nextCfg);
9899
- } catch {
9900
- }
9901
- saveState({ loginMode: "qclaw", jwtToken, channelToken, apiKey, guid, userInfo, savedAt: Date.now() }, authStatePath);
9902
- let bindUrl = "";
9903
- let bindError = "";
9904
- try {
9905
- const OPEN_KFID = "wkzLlJLAAAfbxEV3ZcS-lHZxkaKmpejQ";
9906
- const linkResult = await api.generateContactLink(OPEN_KFID);
9907
- if (linkResult.success) {
9908
- const linkData = linkResult.data;
9909
- bindUrl = nested(linkData, "url") || nested(linkData, "data", "url") || "";
9910
- }
9911
- if (!bindUrl) {
9912
- bindError = "\u751F\u6210\u8BBE\u5907\u7ED1\u5B9A\u94FE\u63A5\u5931\u8D25\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55\u91CD\u8BD5";
9913
- }
9914
- } catch (e) {
9915
- bindError = `\u751F\u6210\u8BBE\u5907\u7ED1\u5B9A\u94FE\u63A5\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`;
9916
- }
9917
- pendingQrLogin = null;
9918
- const nickname = userInfo.nickname ?? "\u7528\u6237";
9919
- if (!bindUrl) {
9920
- return {
9921
- connected: false,
9922
- message: `\u767B\u5F55\u6210\u529F\u4F46\u8BBE\u5907\u7ED1\u5B9A\u5931\u8D25: ${bindError}\u3002\u8BF7\u91CD\u65B0\u767B\u5F55\u91CD\u8BD5\u3002`
9923
- };
9924
- }
9925
- return {
9926
- connected: true,
9927
- bindUrl,
9928
- message: `\u767B\u5F55\u6210\u529F! \u6B22\u8FCE ${nickname}\uFF0C\u8BF7\u5728\u5FAE\u4FE1\u4E2D\u6253\u5F00\u4EE5\u4E0B\u94FE\u63A5\u5B8C\u6210\u8BBE\u5907\u7ED1\u5B9A\uFF08\u7ED1\u5B9A\u540E\u624D\u6709\u5BF9\u8BDD\u5165\u53E3\uFF09\uFF1A
9929
- ${bindUrl}
9930
-
9931
- \u7ED1\u5B9A\u5B8C\u6210\u540E\u8BF7\u91CD\u542F Gateway \u751F\u6548\u3002`
9932
- };
9933
- }
9934
- return { connected: false, message: "\u7B49\u5F85\u626B\u7801..." };
9935
- } catch (err) {
9936
- return { connected: false, message: `\u8F6E\u8BE2\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}` };
9937
- }
9938
9537
  }
9939
9538
  }
9940
9539
  };
@@ -9943,21 +9542,26 @@ var index = {
9943
9542
  name: "\u901A\u7528\u901A\u8DEF\u63D2\u4EF6",
9944
9543
  description: "\u817E\u8BAF\u901A\u7528\u901A\u8DEF\u63D2\u4EF6",
9945
9544
  configSchema: emptyPluginConfigSchema(),
9946
- /**
9947
- * 插件注册入口点
9948
- */
9949
9545
  register(api) {
9950
9546
  setWecomRuntime(api.runtime);
9951
9547
  api.registerChannel({ plugin: tencentAccessPlugin });
9952
9548
  api.registerCli(
9953
9549
  ({ program, config }) => {
9954
9550
  const wechat = program.command("wechat").description("\u5FAE\u4FE1\u901A\u8DEF\u767B\u5F55\u7BA1\u7406");
9955
- wechat.command("login").description("\u767B\u5F55\uFF08\u652F\u6301 QClaw \u5FAE\u4FE1\u626B\u7801 \u6216 CodeBuddy OAuth\uFF09").option("--mode <mode>", "\u767B\u5F55\u6A21\u5F0F: qclaw \u6216 codebuddy").action(async (opts) => {
9956
- const channelCfg = config?.channels?.["wechat-access-unqclawed"];
9957
- const authStatePath = channelCfg?.authStatePath ? String(channelCfg.authStatePath) : void 0;
9958
- const loginMode = opts.mode || channelCfg?.loginMode || "qclaw";
9959
- if (loginMode === "codebuddy") {
9960
- console.log("[wechat login] \u4F7F\u7528 CodeBuddy \u6A21\u5F0F\u767B\u5F55...");
9551
+ wechat.command("login").description("\u767B\u5F55\uFF08\u4EA4\u4E92\u5F0F\u9009\u62E9 QClaw \u6216 WorkBuddy\uFF09").action(async () => {
9552
+ const { createInterface: createInterface2 } = await import("readline");
9553
+ const rl = createInterface2({ input: process.stdin, output: process.stdout });
9554
+ const ask = (q) => new Promise((r) => rl.question(q, (a) => {
9555
+ r(a.trim());
9556
+ }));
9557
+ console.log("\n\u8BF7\u9009\u62E9\u767B\u5F55\u65B9\u5F0F\uFF1A");
9558
+ console.log(" 1. QClaw\uFF08\u5FAE\u4FE1\u626B\u7801 \u2192 JPRX \u7F51\u5173\uFF09");
9559
+ console.log(" 2. WorkBuddy\uFF08CodeBuddy OAuth \u2192 Centrifuge\uFF09");
9560
+ const choice = await ask("\n\u8BF7\u8F93\u5165 1 \u6216 2: ");
9561
+ rl.close();
9562
+ if (choice === "2") {
9563
+ console.log("[wechat login] \u4F7F\u7528 WorkBuddy \u6A21\u5F0F\u767B\u5F55...");
9564
+ const channelCfg = config?.channels?.["wechat-access-unqclawed"];
9961
9565
  const cbBaseUrl = channelCfg?.codebuddyBaseUrl ? String(channelCfg.codebuddyBaseUrl) : void 0;
9962
9566
  const cbApi = new CodeBuddyAPI(cbBaseUrl);
9963
9567
  try {
@@ -9974,82 +9578,135 @@ var index = {
9974
9578
  let userInfo = {};
9975
9579
  try {
9976
9580
  userInfo = await cbApi.getAccount(state);
9977
- cbApi.userId = String(userInfo.userId ?? userInfo.user_id ?? "");
9978
- } catch {
9581
+ cbApi.userId = String(userInfo.uid ?? userInfo.userId ?? userInfo.user_id ?? "");
9582
+ } catch (e) {
9583
+ console.warn(`[wechat login] \u83B7\u53D6\u8D26\u53F7\u4FE1\u606F\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`);
9979
9584
  }
9980
- const guid2 = getDeviceGuid();
9981
- const persistedState = {
9982
- loginMode: "codebuddy",
9983
- jwtToken: "",
9984
- channelToken: "",
9985
- apiKey: "",
9986
- guid: guid2,
9987
- userInfo,
9988
- savedAt: Date.now(),
9585
+ const wbCreds = {
9989
9586
  accessToken: tokenResult.accessToken,
9990
9587
  refreshToken: tokenResult.refreshToken,
9991
- hostId: cbApi.hostId
9588
+ userId: cbApi.userId,
9589
+ hostId: cbApi.hostId,
9590
+ baseUrl: cbBaseUrl,
9591
+ userInfo
9992
9592
  };
9993
- saveState(persistedState, authStatePath);
9994
- try {
9995
- const wRuntime = getWecomRuntime();
9996
- const fullCfg = wRuntime.config.loadConfig();
9997
- const channels = { ...fullCfg.channels ?? {} };
9998
- channels["wechat-access-unqclawed"] = {
9999
- ...channels["wechat-access-unqclawed"] ?? {},
10000
- loginMode: "codebuddy"
10001
- };
10002
- await wRuntime.config.writeConfigFile({ ...fullCfg, channels });
10003
- } catch {
10004
- }
10005
- const sessionId = cbApi.buildSessionId(`openclaw-default`);
10006
- try {
10007
- const linkResult = await cbApi.wechatkfGetLink(sessionId);
10008
- if (linkResult.success && linkResult.url) {
10009
- console.log("\n" + "=".repeat(64));
10010
- console.log("\u8BF7\u5728\u5FAE\u4FE1\u4E2D\u6253\u5F00\u4EE5\u4E0B\u94FE\u63A5\u7ED1\u5B9A\u5FAE\u4FE1\u5BA2\u670D\u53F7\uFF08\u7ED1\u5B9A\u540E\u624D\u6709\u5BF9\u8BDD\u5165\u53E3\uFF09\uFF1A");
10011
- console.log("");
10012
- console.log(linkResult.url);
10013
- console.log("=".repeat(64));
10014
- }
10015
- } catch {
10016
- }
9593
+ await writeChannelConfig({ loginMode: "workbuddy", workbuddy: wbCreds });
10017
9594
  const nickname = String(userInfo.nickName ?? userInfo.nickname ?? "\u7528\u6237");
10018
9595
  console.log(`
10019
- CodeBuddy \u767B\u5F55\u6210\u529F! \u6B22\u8FCE ${nickname}`);
10020
- console.log("token \u5DF2\u4FDD\u5B58\uFF0C\u8BF7\u8FD0\u884C openclaw gateway restart \u751F\u6548\u3002");
9596
+ WorkBuddy \u767B\u5F55\u6210\u529F! \u6B22\u8FCE ${nickname}`);
9597
+ console.log("\u8BF7\u8FD0\u884C openclaw gateway restart \u542F\u52A8\u901A\u8DEF\u3002");
9598
+ console.log("\u9996\u6B21\u4F7F\u7528\u8BF7\u5728 gateway \u542F\u52A8\u540E\u8FD0\u884C openclaw wechat bind \u83B7\u53D6\u5FAE\u4FE1\u5BA2\u670D\u7ED1\u5B9A\u94FE\u63A5\u3002");
10021
9599
  } catch (err) {
10022
9600
  console.error(`
10023
- CodeBuddy \u767B\u5F55\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
9601
+ WorkBuddy \u767B\u5F55\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
10024
9602
  process.exit(1);
10025
9603
  }
10026
- return;
10027
- }
10028
- const bypassInvite = channelCfg?.bypassInvite === true;
10029
- const envName = channelCfg?.environment ? String(channelCfg.environment) : "production";
10030
- const env = getEnvironment(envName);
10031
- const guid = getDeviceGuid();
10032
- try {
10033
- const credentials = await performLogin({
10034
- guid,
10035
- env,
10036
- bypassInvite,
10037
- authStatePath
10038
- });
10039
- console.log(`
9604
+ } else {
9605
+ const channelCfg = config?.channels?.["wechat-access-unqclawed"];
9606
+ const envName = channelCfg?.environment ? String(channelCfg.environment) : "production";
9607
+ const env = getEnvironment(envName);
9608
+ const guid = getDeviceGuid();
9609
+ try {
9610
+ const credentials = await performLogin({ guid, env });
9611
+ const qclawCreds = {
9612
+ jwtToken: credentials.jwtToken,
9613
+ channelToken: credentials.channelToken,
9614
+ apiKey: credentials.apiKey,
9615
+ guid: credentials.guid,
9616
+ userId: String(credentials.userInfo?.user_id ?? ""),
9617
+ wsUrl: env.wechatWsUrl,
9618
+ userInfo: credentials.userInfo
9619
+ };
9620
+ await writeChannelConfig({ loginMode: "qclaw", qclaw: qclawCreds });
9621
+ if (credentials.apiKey) {
9622
+ try {
9623
+ const wRuntime = getWecomRuntime();
9624
+ const fullCfg = wRuntime.config.loadConfig();
9625
+ const models = { ...fullCfg.models ?? {} };
9626
+ const providers = { ...models.providers ?? {} };
9627
+ providers.qclaw = { ...providers.qclaw ?? {}, apiKey: credentials.apiKey };
9628
+ models.providers = providers;
9629
+ await wRuntime.config.writeConfigFile({ ...fullCfg, models });
9630
+ } catch {
9631
+ }
9632
+ }
9633
+ console.log(`
10040
9634
  \u767B\u5F55\u6210\u529F! token: ${credentials.channelToken.substring(0, 6)}...`);
10041
- console.log("token \u5DF2\u4FDD\u5B58\uFF0C\u8BF7\u8FD0\u884C openclaw gateway restart \u751F\u6548\u3002");
10042
- } catch (err) {
10043
- console.error(`
9635
+ console.log("\u8BF7\u8FD0\u884C openclaw gateway restart \u542F\u52A8\u901A\u8DEF\u3002");
9636
+ console.log("\u9996\u6B21\u4F7F\u7528\u8BF7\u5728 gateway \u542F\u52A8\u540E\u8FD0\u884C openclaw wechat bind \u5B8C\u6210\u8BBE\u5907\u7ED1\u5B9A\u3002");
9637
+ } catch (err) {
9638
+ console.error(`
10044
9639
  \u767B\u5F55\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
10045
- process.exit(1);
9640
+ process.exit(1);
9641
+ }
10046
9642
  }
10047
9643
  });
10048
- wechat.command("logout").description("\u6E05\u9664\u5DF2\u4FDD\u5B58\u7684\u5FAE\u4FE1\u767B\u5F55\u6001").action(() => {
10049
- const channelCfg = config?.channels?.["wechat-access-unqclawed"];
10050
- const authStatePath = channelCfg?.authStatePath ? String(channelCfg.authStatePath) : void 0;
10051
- clearState(authStatePath);
10052
- console.log("\u5DF2\u6E05\u9664\u767B\u5F55\u6001\uFF0C\u4E0B\u6B21\u542F\u52A8\u5C06\u9700\u8981\u91CD\u65B0\u626B\u7801\u767B\u5F55\u3002");
9644
+ wechat.command("logout").description("\u6E05\u9664\u5DF2\u4FDD\u5B58\u7684\u5FAE\u4FE1\u767B\u5F55\u6001").action(async () => {
9645
+ await writeChannelConfig({ loginMode: void 0, qclaw: void 0, workbuddy: void 0 });
9646
+ console.log("\u5DF2\u6E05\u9664\u767B\u5F55\u6001\u3002");
9647
+ });
9648
+ wechat.command("bind").description("\u83B7\u53D6\u8BBE\u5907\u7ED1\u5B9A\u94FE\u63A5\uFF08\u9700\u5148\u767B\u5F55\uFF09").action(async () => {
9649
+ const channelCfg = readChannelConfig();
9650
+ if (channelCfg.loginMode === "workbuddy") {
9651
+ const creds = channelCfg.workbuddy;
9652
+ if (!creds?.accessToken) {
9653
+ console.error("\u8BF7\u5148\u767B\u5F55: openclaw wechat login\uFF08\u9009\u62E9 2\uFF09");
9654
+ process.exit(1);
9655
+ }
9656
+ const cbApi = new CodeBuddyAPI(creds.baseUrl);
9657
+ cbApi.accessToken = creds.accessToken;
9658
+ cbApi.refreshToken = creds.refreshToken || "";
9659
+ cbApi.userId = creds.userId || "";
9660
+ cbApi.hostId = creds.hostId || cbApi.hostId;
9661
+ try {
9662
+ const refreshed = await cbApi.doRefreshToken();
9663
+ await writeChannelConfig({
9664
+ workbuddy: { ...creds, accessToken: refreshed.accessToken, refreshToken: refreshed.refreshToken }
9665
+ });
9666
+ } catch {
9667
+ console.warn("token \u5237\u65B0\u5931\u8D25\uFF0C\u4F7F\u7528\u65E7 token \u7EE7\u7EED...");
9668
+ }
9669
+ const sessionId = cbApi.buildSessionId();
9670
+ try {
9671
+ const linkResult = await cbApi.wechatkfGetLink(sessionId);
9672
+ if (linkResult.success && linkResult.url) {
9673
+ console.log("\n" + "=".repeat(64));
9674
+ console.log("\u8BF7\u5728\u5FAE\u4FE1\u4E2D\u6253\u5F00\u4EE5\u4E0B\u94FE\u63A5\u7ED1\u5B9A\u5FAE\u4FE1\u5BA2\u670D\u53F7\uFF1A");
9675
+ console.log("");
9676
+ console.log(linkResult.url);
9677
+ console.log("");
9678
+ console.log("\u7ED1\u5B9A\u5B8C\u6210\u540E\u5373\u53EF\u901A\u8FC7\u5FAE\u4FE1\u5BA2\u670D\u53F7\u5BF9\u8BDD\u3002");
9679
+ console.log("=".repeat(64));
9680
+ } else {
9681
+ console.error(`\u83B7\u53D6\u7ED1\u5B9A\u94FE\u63A5\u5931\u8D25: ${linkResult.message ?? "\u672A\u77E5\u9519\u8BEF"}`);
9682
+ }
9683
+ } catch (err) {
9684
+ console.error(`\u83B7\u53D6\u7ED1\u5B9A\u94FE\u63A5\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
9685
+ process.exit(1);
9686
+ }
9687
+ return;
9688
+ }
9689
+ if (channelCfg.loginMode === "qclaw") {
9690
+ const creds = channelCfg.qclaw;
9691
+ if (!creds?.channelToken) {
9692
+ console.error("\u8BF7\u5148\u767B\u5F55: openclaw wechat login\uFF08\u9009\u62E9 1\uFF09");
9693
+ process.exit(1);
9694
+ }
9695
+ const envName = channelCfg.environment ? String(channelCfg.environment) : "production";
9696
+ const env = getEnvironment(envName);
9697
+ const api2 = new QClawAPI(env, creds.guid, creds.jwtToken);
9698
+ api2.userId = creds.userId || "";
9699
+ const loginKey = creds.userInfo?.loginKey;
9700
+ if (loginKey) api2.loginKey = loginKey;
9701
+ const bindResult = await performDeviceBinding({ api: api2 });
9702
+ if (!bindResult.success) {
9703
+ console.error(bindResult.message);
9704
+ process.exit(1);
9705
+ }
9706
+ return;
9707
+ }
9708
+ console.error("\u8BF7\u5148\u767B\u5F55: openclaw wechat login");
9709
+ process.exit(1);
10053
9710
  });
10054
9711
  },
10055
9712
  { commands: ["wechat"] }
@@ -10059,18 +9716,21 @@ CodeBuddy \u767B\u5F55\u5931\u8D25: ${err instanceof Error ? err.message : Strin
10059
9716
  description: "\u624B\u52A8\u6267\u884C\u5FAE\u4FE1\u626B\u7801\u767B\u5F55\uFF0C\u83B7\u53D6 channel token",
10060
9717
  handler: async ({ config }) => {
10061
9718
  const channelCfg = config?.channels?.["wechat-access-unqclawed"];
10062
- const bypassInvite = channelCfg?.bypassInvite === true;
10063
- const authStatePath = channelCfg?.authStatePath ? String(channelCfg.authStatePath) : void 0;
10064
9719
  const envName = channelCfg?.environment ? String(channelCfg.environment) : "production";
10065
9720
  const env = getEnvironment(envName);
10066
9721
  const guid = getDeviceGuid();
10067
9722
  try {
10068
- const credentials = await performLogin({
10069
- guid,
10070
- env,
10071
- bypassInvite,
10072
- authStatePath
10073
- });
9723
+ const credentials = await performLogin({ guid, env });
9724
+ const qclawCreds = {
9725
+ jwtToken: credentials.jwtToken,
9726
+ channelToken: credentials.channelToken,
9727
+ apiKey: credentials.apiKey,
9728
+ guid: credentials.guid,
9729
+ userId: String(credentials.userInfo?.user_id ?? ""),
9730
+ wsUrl: env.wechatWsUrl,
9731
+ userInfo: credentials.userInfo
9732
+ };
9733
+ await writeChannelConfig({ loginMode: "qclaw", qclaw: qclawCreds });
10074
9734
  return { text: `\u767B\u5F55\u6210\u529F! token: ${credentials.channelToken.substring(0, 6)}... (\u5DF2\u4FDD\u5B58\uFF0C\u91CD\u542F Gateway \u751F\u6548)` };
10075
9735
  } catch (err) {
10076
9736
  return { text: `\u767B\u5F55\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`, isError: true };
@@ -10080,11 +9740,9 @@ CodeBuddy \u767B\u5F55\u5931\u8D25: ${err instanceof Error ? err.message : Strin
10080
9740
  api.registerCommand?.({
10081
9741
  name: "wechat-logout",
10082
9742
  description: "\u6E05\u9664\u5DF2\u4FDD\u5B58\u7684\u5FAE\u4FE1\u767B\u5F55\u6001",
10083
- handler: async ({ config }) => {
10084
- const channelCfg = config?.channels?.["wechat-access-unqclawed"];
10085
- const authStatePath = channelCfg?.authStatePath ? String(channelCfg.authStatePath) : void 0;
10086
- clearState(authStatePath);
10087
- return { text: "\u5DF2\u6E05\u9664\u767B\u5F55\u6001\uFF0C\u4E0B\u6B21\u542F\u52A8\u5C06\u91CD\u65B0\u626B\u7801\u767B\u5F55\u3002" };
9743
+ handler: async () => {
9744
+ await writeChannelConfig({ loginMode: void 0, qclaw: void 0, workbuddy: void 0 });
9745
+ return { text: "\u5DF2\u6E05\u9664\u767B\u5F55\u6001\uFF0C\u4E0B\u6B21\u542F\u52A8\u5C06\u91CD\u65B0\u767B\u5F55\u3002" };
10088
9746
  }
10089
9747
  });
10090
9748
  console.log("[wechat-access] \u817E\u8BAF\u901A\u8DEF\u63D2\u4EF6\u5DF2\u6CE8\u518C");