feishu-bridge 1.0.7 → 1.0.9

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/cli/index.js CHANGED
@@ -111,7 +111,8 @@ var init_config = __esm({
111
111
  maxOutputLength: 2e3,
112
112
  sessionTimeout: 30,
113
113
  reverseChannelEnabled: true,
114
- reverseChannelMode: "filesystem"
114
+ reverseChannelMode: "websocket"
115
+ // 默认使用WebSocket模式
115
116
  },
116
117
  logging: {
117
118
  level: "info",
@@ -761,24 +762,28 @@ init_config();
761
762
  var Lark = __toESM(require("@larksuiteoapi/node-sdk"));
762
763
  var FeishuWebSocketHandler = class {
763
764
  constructor(client, commandExecutor, adapters) {
764
- this.client = client;
765
765
  this.commandExecutor = commandExecutor;
766
766
  this.adapters = adapters;
767
767
  this.wsClient = null;
768
768
  this.eventDispatcher = null;
769
769
  this.isRunning = false;
770
+ this.client = client;
770
771
  this.logger = new Logger();
771
772
  this.messageRouter = new MessageRouter(adapters);
773
+ this.logger.info(`WebSocket handler created with client`);
774
+ console.log("\u{1F50D} DEBUG - WebSocket handler constructor - client:", {
775
+ hasAppId: !!client,
776
+ appId: client.appId,
777
+ baseUrl: client.baseUrl
778
+ });
772
779
  }
773
- /**
774
- * 启动WebSocket连接
775
- */
776
780
  async start() {
777
781
  if (this.isRunning) {
778
782
  this.logger.warn("WebSocket handler already running");
779
783
  return;
780
784
  }
781
785
  const config = ConfigManager.getInstance().getFeishuConfig();
786
+ this.logger.info(`WebSocket config - appId: ${config.appId ? "\u2705 Set" : "\u274C Empty"}, domain: ${config.domain}`);
782
787
  try {
783
788
  this.logger.info("Creating Feishu WebSocket client...");
784
789
  this.wsClient = new Lark.WSClient({
@@ -788,74 +793,55 @@ var FeishuWebSocketHandler = class {
788
793
  loggerLevel: Lark.LoggerLevel.info
789
794
  });
790
795
  this.eventDispatcher = new Lark.EventDispatcher({
791
- encryptKey: config.encryptKey,
792
- verificationToken: config.verificationToken
796
+ encryptKey: config.encryptKey || "",
797
+ verificationToken: config.verificationToken || ""
793
798
  });
794
799
  this.registerEventHandlers();
795
- this.wsClient.on("message", async (event) => {
796
- await this.handleEvent(event);
797
- });
798
- this.wsClient.on("error", (error) => {
799
- this.logger.error("WebSocket error:", error);
800
- });
801
- this.wsClient.on("close", () => {
802
- this.logger.info("WebSocket connection closed");
803
- if (this.isRunning) {
804
- this.logger.info("Attempting to reconnect...");
805
- setTimeout(() => this.start(), 5e3);
806
- }
807
- });
800
+ this.logger.info("Starting WebSocket connection...");
801
+ await this.wsClient.start({ eventDispatcher: this.eventDispatcher });
808
802
  this.isRunning = true;
809
- this.logger.info("Feishu WebSocket handler started successfully");
803
+ this.logger.info("\u2705 Feishu WebSocket connected successfully!");
810
804
  } catch (error) {
811
- this.logger.error("Failed to start WebSocket handler:", error);
805
+ console.error("\u274C WebSocket ERROR:", error);
806
+ this.logger.error("\u274C Failed to start WebSocket handler:", error);
812
807
  throw error;
813
808
  }
814
809
  }
815
- /**
816
- * 停止WebSocket连接
817
- */
818
- async stop() {
819
- this.isRunning = false;
820
- if (this.wsClient) {
821
- this.wsClient.close();
822
- this.wsClient = null;
823
- }
824
- this.logger.info("WebSocket handler stopped");
825
- }
826
- /**
827
- * 注册事件处理器
828
- */
829
810
  registerEventHandlers() {
830
811
  if (!this.eventDispatcher) return;
831
- this.eventDispatcher.on("im.message.receive_v1", async (event) => {
832
- await this.handleMessageEvent(event);
833
- });
834
- this.eventDispatcher.on("im.chat.member.bot.added_v1", (event) => {
835
- this.logger.info("Bot added to chat:", event);
836
- });
837
- this.eventDispatcher.on("im.chat.member.bot.deleted_v1", (event) => {
838
- this.logger.info("Bot removed from chat:", event);
839
- });
840
- }
841
- /**
842
- * 处理事件
843
- */
844
- async handleEvent(event) {
845
- try {
846
- if (this.eventDispatcher) {
847
- await this.eventDispatcher.dispatch(event);
812
+ this.logger.info("Registering event handlers...");
813
+ this.eventDispatcher.register({
814
+ "im.message.receive_v1": async (event) => {
815
+ this.logger.info("\u{1F4E8} Event received: im.message.receive_v1");
816
+ this.logger.info("Event data:", JSON.stringify(event, null, 2));
817
+ await this.handleMessageEvent(event);
818
+ },
819
+ "im.message.message_read_v1": (event) => {
820
+ this.logger.debug("Message read:", event);
821
+ },
822
+ "im.chat.member.bot.added_v1": (event) => {
823
+ this.logger.info("Bot added to chat:", event);
824
+ },
825
+ "im.chat.member.bot.deleted_v1": (event) => {
826
+ this.logger.info("Bot removed from chat:", event);
848
827
  }
849
- } catch (error) {
850
- this.logger.error("Error dispatching event:", error);
851
- }
828
+ });
852
829
  }
853
- /**
854
- * 处理消息事件
855
- */
856
830
  async handleMessageEvent(event) {
831
+ this.logger.info("\u{1F504} handleMessageEvent called");
832
+ this.logger.info("Event:", JSON.stringify(event, null, 2));
833
+ console.log("\u{1F50D} DEBUG - handleMessageEvent - this.client:", {
834
+ exists: !!this.client,
835
+ appId: this.client?.appId,
836
+ baseUrl: this.client?.baseUrl
837
+ });
857
838
  const { message, sender } = event;
858
- if (!message || message.message_type !== "text") {
839
+ if (!message) {
840
+ this.logger.warn("No message in event");
841
+ return;
842
+ }
843
+ this.logger.info(`Message type: ${message.message_type}`);
844
+ if (message.message_type !== "text") {
859
845
  this.logger.debug("Ignoring non-text message");
860
846
  return;
861
847
  }
@@ -867,7 +853,7 @@ var FeishuWebSocketHandler = class {
867
853
  this.logger.warn("Missing sender ID");
868
854
  return;
869
855
  }
870
- this.logger.info(`Received message from ${senderId}: ${text}`);
856
+ this.logger.info(`\u{1F4E8} Received message from ${senderId}: ${text}`);
871
857
  const route = await this.messageRouter.route(text);
872
858
  if (route.type === "error") {
873
859
  await this.client.sendMessage(message.chat_id, {
@@ -885,7 +871,7 @@ var FeishuWebSocketHandler = class {
885
871
  }
886
872
  if (route.type === "direct" && route.target) {
887
873
  const command = this.messageRouter.extractCommand(text);
888
- this.logger.info(`Executing command for ${route.target}: ${command}`);
874
+ this.logger.info(`\u{1F3AF} Routing to ${route.target}: ${command}`);
889
875
  await this.client.sendMessage(message.chat_id, {
890
876
  msg_type: "text",
891
877
  content: { text: `\u23F3 \u6B63\u5728\u6267\u884C: ${command}` }
@@ -902,20 +888,17 @@ var FeishuWebSocketHandler = class {
902
888
  });
903
889
  }
904
890
  } catch (error) {
905
- this.logger.error("Error processing message event:", error);
891
+ this.logger.error("Error processing event:", error);
906
892
  try {
907
893
  await this.client.sendMessage(message.chat_id, {
908
894
  msg_type: "text",
909
- content: { text: `\u274C \u5904\u7406\u6D88\u606F\u65F6\u51FA\u9519: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}` }
895
+ content: { text: `\u274C \u5904\u7406\u6D88\u606F\u65F6\u51FA\u9519` }
910
896
  });
911
897
  } catch (sendError) {
912
898
  this.logger.error("Failed to send error message:", sendError);
913
899
  }
914
900
  }
915
901
  }
916
- /**
917
- * 格式化响应
918
- */
919
902
  formatResponse(result) {
920
903
  if (result.success) {
921
904
  const output = typeof result.output === "string" ? result.output.substring(0, 2e3) : JSON.stringify(result.output).substring(0, 2e3);
@@ -929,6 +912,14 @@ ${output}`;
929
912
  ${error.substring(0, 2e3)}`;
930
913
  }
931
914
  }
915
+ async stop() {
916
+ this.isRunning = false;
917
+ if (this.wsClient) {
918
+ this.wsClient.close();
919
+ this.wsClient = null;
920
+ }
921
+ this.logger.info("WebSocket handler stopped");
922
+ }
932
923
  };
933
924
 
934
925
  // src/feishu/client.ts
@@ -937,20 +928,33 @@ var FeishuClient = class {
937
928
  constructor(config) {
938
929
  this.accessToken = null;
939
930
  this.tokenExpireTime = 0;
931
+ console.log("\u{1F50D} DEBUG - FeishuClient constructor called with:", {
932
+ hasAppId: !!config.appId,
933
+ appIdLength: config.appId?.length,
934
+ hasSecret: !!config.appSecret,
935
+ domain: config.domain
936
+ });
940
937
  this.appId = config.appId;
941
938
  this.appSecret = config.appSecret;
942
939
  this.domain = config.domain;
943
940
  this.baseUrl = this.domain === "feishu" ? "https://open.feishu.cn/open-apis" : "https://open.larksuite.com/open-apis";
944
941
  this.logger = new Logger();
942
+ console.log("\u{1F50D} DEBUG - FeishuClient this.appId after assignment:", this.appId);
943
+ this.logger.info(`FeishuClient initialized with appId: ${this.appId ? "\u2705" : "\u274C Empty"}`);
945
944
  }
946
945
  /**
947
946
  * 获取访问令牌
948
947
  */
949
948
  async getAccessToken() {
949
+ console.log("\u{1F50D} DEBUG - getAccessToken called");
950
+ console.log("\u{1F50D} DEBUG - this.appId:", this.appId);
951
+ console.log("\u{1F50D} DEBUG - this.baseUrl:", this.baseUrl);
950
952
  const now = Date.now();
951
953
  if (this.accessToken && now < this.tokenExpireTime) {
954
+ console.log("\u{1F50D} DEBUG - Using cached token");
952
955
  return this.accessToken;
953
956
  }
957
+ console.log("\u{1F50D} DEBUG - Fetching new token from:", `${this.baseUrl}/auth/v3/tenant_access_token/internal`);
954
958
  try {
955
959
  const response = await fetch(`${this.baseUrl}/auth/v3/tenant_access_token/internal`, {
956
960
  method: "POST",
@@ -974,6 +978,9 @@ var FeishuClient = class {
974
978
  this.logger.info("Access token refreshed successfully");
975
979
  return this.accessToken;
976
980
  } catch (error) {
981
+ console.error("\u274C getAccessToken ERROR:", error);
982
+ console.error("\u274C this.appId:", this.appId);
983
+ console.error("\u274C this.baseUrl:", this.baseUrl);
977
984
  this.logger.error("Error getting access token:", error);
978
985
  throw error;
979
986
  }
@@ -982,6 +989,16 @@ var FeishuClient = class {
982
989
  * 发送消息到指定聊天
983
990
  */
984
991
  async sendMessage(chatId, message) {
992
+ console.log("\u{1F50D}\u{1F50D}\u{1F50D} CRITICAL DEBUG - sendMessage ENTER");
993
+ console.log("\u{1F50D}\u{1F50D}\u{1F50D} this.appId:", this.appId);
994
+ console.log("\u{1F50D}\u{1F50D}\u{1F50D} this.appSecret:", this.appSecret ? "***" + this.appSecret.slice(-4) : "EMPTY");
995
+ console.log("\u{1F50D}\u{1F50D}\u{1F50D} this.baseUrl:", this.baseUrl);
996
+ console.log("\u{1F50D}\u{1F50D}\u{1F50D} chatId:", chatId);
997
+ console.log("\u{1F50D}\u{1F50D}\u{1F50D} message:", JSON.stringify(message));
998
+ if (!this.appId || !this.baseUrl) {
999
+ console.error("\u274C\u274C\u274C FATAL: this.appId or this.baseUrl is empty in sendMessage!");
1000
+ throw new Error(`Invalid client state: appId=${this.appId}, baseUrl=${this.baseUrl}`);
1001
+ }
985
1002
  try {
986
1003
  const token = await this.getAccessToken();
987
1004
  const response = await fetch(`${this.baseUrl}/im/v1/messages?receive_id_type=chat_id`, {
@@ -1047,11 +1064,28 @@ var CommandExecutor = class {
1047
1064
  }
1048
1065
  async execute(request) {
1049
1066
  try {
1050
- this.logger.info(`Executing command for target: ${request.target}, command: ${request.command}`);
1067
+ this.logger.info(
1068
+ `Executing command for target: ${request.target}, command: ${request.command}`
1069
+ );
1051
1070
  const session = this.sessionManager.getSession(request.sender);
1052
1071
  let adapterName = request.target.toLowerCase();
1053
- if (adapterName === "auto") {
1054
- adapterName = this.detectBestAdapter();
1072
+ if (adapterName === "auto" || adapterName === "") {
1073
+ const available = this.getAvailableAdapters();
1074
+ if (available.length === 0) {
1075
+ throw new Error("\u672A\u627E\u5230\u4EFB\u4F55\u53EF\u7528\u7684\u5DE5\u5177\uFF0C\u8BF7\u786E\u4FDD\u5DF2\u6253\u5F00IDE\u6216\u542F\u52A8CLI");
1076
+ }
1077
+ if (available.length === 1) {
1078
+ adapterName = available[0];
1079
+ } else {
1080
+ const availableList = available.map((a) => `@${a}`).join(", ");
1081
+ throw new Error(
1082
+ `\u8BF7\u6307\u5B9A\u8981\u4F7F\u7528\u7684\u5DE5\u5177
1083
+
1084
+ \u53EF\u7528\u5DE5\u5177: ${availableList}
1085
+
1086
+ \u4F8B\u5982\uFF1A@opencode /help`
1087
+ );
1088
+ }
1055
1089
  }
1056
1090
  const adapter = this.adapters.get(adapterName);
1057
1091
  if (!adapter) {
@@ -1071,7 +1105,10 @@ var CommandExecutor = class {
1071
1105
  result = await adapter.generateCode(description);
1072
1106
  } else if (this.isRunCommand(request.command)) {
1073
1107
  const command = this.extractRunCommand(request.command);
1074
- const executionResult = await adapter.runProgram(command, executionContext.workspaceRoot);
1108
+ const executionResult = await adapter.runProgram(
1109
+ command,
1110
+ executionContext.workspaceRoot
1111
+ );
1075
1112
  result = executionResult.stdout || executionResult.stderr;
1076
1113
  } else {
1077
1114
  result = await adapter.sendCommand(request.command, executionContext);
@@ -1095,25 +1132,64 @@ var CommandExecutor = class {
1095
1132
  }
1096
1133
  }
1097
1134
  detectBestAdapter() {
1098
- for (const [name, adapter] of this.adapters) {
1099
- if (adapter.isAvailable) {
1135
+ const cliAdapters = ["opencode", "claude", "gemini"];
1136
+ const ideAdapters = ["vscode", "cursor", "trae", "antigravity", "kiro"];
1137
+ for (const name of cliAdapters) {
1138
+ const adapter = this.adapters.get(name);
1139
+ if (adapter && adapter.isAvailable) {
1140
+ return name;
1141
+ }
1142
+ }
1143
+ for (const name of ideAdapters) {
1144
+ const adapter = this.adapters.get(name);
1145
+ if (adapter && adapter.isAvailable) {
1100
1146
  return name;
1101
1147
  }
1102
1148
  }
1103
1149
  return "vscode";
1104
1150
  }
1151
+ /**
1152
+ * 获取所有可用的适配器列表
1153
+ */
1154
+ getAvailableAdapters() {
1155
+ const available = [];
1156
+ for (const [name, adapter] of this.adapters) {
1157
+ if (adapter.isAvailable) {
1158
+ available.push(name);
1159
+ }
1160
+ }
1161
+ return available;
1162
+ }
1163
+ /**
1164
+ * 获取适配器使用提示
1165
+ */
1166
+ getAdapterHint(currentAdapter) {
1167
+ const available = this.getAvailableAdapters();
1168
+ if (available.length <= 1) {
1169
+ return "";
1170
+ }
1171
+ return `
1172
+
1173
+ \u{1F4A1} \u5F53\u524D\u53EF\u7528\u5DE5\u5177: ${available.join(", ")}`;
1174
+ }
1105
1175
  isGenerateCommand(command) {
1106
- return /\b(generate|create|make|build|implement|write|develop|add|new)\b/i.test(command);
1176
+ return /\b(generate|create|make|build|implement|write|develop|add|new)\b/i.test(
1177
+ command
1178
+ );
1107
1179
  }
1108
1180
  isRunCommand(command) {
1109
1181
  return /\b(run|execute|start|launch|test|debug|build)\b/i.test(command);
1110
1182
  }
1111
1183
  extractDescription(command) {
1112
- const match = command.match(/\b(generate|create|make|build|implement|write|develop|add|new)\b\s+(.*)/i);
1184
+ const match = command.match(
1185
+ /\b(generate|create|make|build|implement|write|develop|add|new)\b\s+(.*)/i
1186
+ );
1113
1187
  return match ? match[2] : command;
1114
1188
  }
1115
1189
  extractRunCommand(command) {
1116
- const match = command.match(/\b(run|execute|start|launch|test|debug|build)\b\s+(.*)/i);
1190
+ const match = command.match(
1191
+ /\b(run|execute|start|launch|test|debug|build)\b\s+(.*)/i
1192
+ );
1117
1193
  return match ? match[2] : command;
1118
1194
  }
1119
1195
  };
@@ -2362,6 +2438,7 @@ var OpenCodeAdapter = class extends BaseIDEAdapter {
2362
2438
  this.ideType = "opencode";
2363
2439
  this.ideName = "OpenCode";
2364
2440
  this.useSocket = false;
2441
+ this.useHttp = false;
2365
2442
  this.messageId = 0;
2366
2443
  this.pendingMessages = /* @__PURE__ */ new Map();
2367
2444
  }
@@ -2395,19 +2472,40 @@ var OpenCodeAdapter = class extends BaseIDEAdapter {
2395
2472
  }
2396
2473
  getSocketPath() {
2397
2474
  const possiblePaths = [
2475
+ // Windows: temp directory
2398
2476
  path7.join(os7.tmpdir(), "opencode.sock"),
2399
2477
  path7.join(os7.tmpdir(), "opencode-bridge.sock"),
2478
+ // Unix socket locations
2400
2479
  path7.join(os7.homedir(), ".opencode", "socket"),
2401
- process.env.OPENCODE_SOCKET
2480
+ // Environment variable override
2481
+ process.env.OPENCODE_SOCKET,
2482
+ // Windows named pipe format
2483
+ "\\\\.\\pipe\\opencode"
2402
2484
  ].filter(Boolean);
2403
2485
  for (const socketPath of possiblePaths) {
2404
- if (fs7.existsSync(socketPath)) {
2405
- return socketPath;
2486
+ try {
2487
+ if (fs7.existsSync(socketPath)) {
2488
+ return socketPath;
2489
+ }
2490
+ } catch {
2491
+ if (socketPath.startsWith("\\\\.\\pipe\\")) {
2492
+ return socketPath;
2493
+ }
2406
2494
  }
2407
2495
  }
2408
2496
  return null;
2409
2497
  }
2410
2498
  async activate() {
2499
+ try {
2500
+ const httpConnected = await this.connectHttp();
2501
+ if (httpConnected) {
2502
+ this.useHttp = true;
2503
+ this.logger.info("OpenCode HTTP\u6A21\u5F0F\u5DF2\u8FDE\u63A5");
2504
+ return;
2505
+ }
2506
+ } catch (error) {
2507
+ this.logger.warn(`HTTP\u8FDE\u63A5\u5931\u8D25: ${error}`);
2508
+ }
2411
2509
  const socketPath = this.getSocketPath();
2412
2510
  if (socketPath) {
2413
2511
  try {
@@ -2416,13 +2514,16 @@ var OpenCodeAdapter = class extends BaseIDEAdapter {
2416
2514
  this.logger.info(`OpenCode Socket\u6A21\u5F0F\u5DF2\u8FDE\u63A5: ${socketPath}`);
2417
2515
  return;
2418
2516
  } catch (error) {
2419
- this.logger.warn(`Socket\u8FDE\u63A5\u5931\u8D25\uFF0C\u5C06\u4F7F\u7528CLI\u6A21\u5F0F: ${error}`);
2517
+ this.logger.warn(`Socket\u8FDE\u63A5\u5931\u8D25: ${error}`);
2420
2518
  }
2421
2519
  }
2422
- if (!this.checkCLIExists()) {
2423
- throw new Error("OpenCode\u672A\u5B89\u88C5\uFF0C\u8BF7\u5148\u8FD0\u884C: npm install -g opencode");
2520
+ if (this.checkCLIExists()) {
2521
+ this.logger.info("OpenCode CLI\u6A21\u5F0F\u5C31\u7EEA");
2522
+ return;
2424
2523
  }
2425
- this.logger.info("OpenCode CLI\u6A21\u5F0F\u5C31\u7EEA");
2524
+ throw new Error(
2525
+ "OpenCode\u672A\u5B89\u88C5\u6216\u4E0D\u53EF\u7528\u3002\u8BF7\u786E\u4FDDOpenCode\u6B63\u5728\u8FD0\u884C\u6216\u8FD0\u884C: npm install -g opencode"
2526
+ );
2426
2527
  }
2427
2528
  async deactivate() {
2428
2529
  if (this.socketClient) {
@@ -2442,6 +2543,30 @@ var OpenCodeAdapter = class extends BaseIDEAdapter {
2442
2543
  this.setupSocketListener();
2443
2544
  });
2444
2545
  }
2546
+ /**
2547
+ * HTTP方式连接(OpenCode 1.1.53 使用HTTP协议)
2548
+ */
2549
+ async connectHttp() {
2550
+ const port = process.env.OPENCODE_PORT || "26178";
2551
+ const host = process.env.OPENCODE_HOST || "127.0.0.1";
2552
+ try {
2553
+ this.logger.info(`Trying HTTP connection to OpenCode at ${host}:${port}`);
2554
+ const response = await fetch(`http://${host}:${port}/api/ping`, {
2555
+ method: "GET",
2556
+ headers: {
2557
+ "Content-Type": "application/json"
2558
+ }
2559
+ });
2560
+ if (response.ok) {
2561
+ this.logger.info("HTTP connection to OpenCode successful");
2562
+ return true;
2563
+ }
2564
+ return false;
2565
+ } catch (error) {
2566
+ this.logger.warn(`HTTP connection failed: ${error}`);
2567
+ return false;
2568
+ }
2569
+ }
2445
2570
  setupSocketListener() {
2446
2571
  if (!this.socketClient) return;
2447
2572
  let buffer = "";
@@ -2542,7 +2667,9 @@ var OpenCodeAdapter = class extends BaseIDEAdapter {
2542
2667
  }
2543
2668
  async getAIResponse(prompt, context) {
2544
2669
  try {
2545
- this.logger.info(`Getting AI response from OpenCode for prompt: ${prompt.substring(0, 100)}...`);
2670
+ this.logger.info(
2671
+ `Getting AI response from OpenCode for prompt: ${prompt.substring(0, 100)}...`
2672
+ );
2546
2673
  return await this.sendCommand(prompt, context);
2547
2674
  } catch (error) {
2548
2675
  this.logger.error(`Error getting OpenCode AI response:`, error);
@@ -2585,6 +2712,31 @@ var OpenCodeAdapter = class extends BaseIDEAdapter {
2585
2712
  const content = await this.getAIResponse(prompt);
2586
2713
  return { content, model: "claude-3-5-sonnet" };
2587
2714
  }
2715
+ /**
2716
+ * 发送进度更新到飞书
2717
+ * 允许长时间任务在执行过程中推送进度信息
2718
+ */
2719
+ async sendProgressUpdate(progress, context) {
2720
+ return this.sendToFeishu(progress, context, "progress");
2721
+ }
2722
+ /**
2723
+ * 发送状态更新到飞书
2724
+ */
2725
+ async sendStatusUpdate(status, context) {
2726
+ return this.sendToFeishu(status, context, "status");
2727
+ }
2728
+ /**
2729
+ * 发送通知到飞书
2730
+ */
2731
+ async sendNotification(notification, context) {
2732
+ return this.sendToFeishu(notification, context, "notification");
2733
+ }
2734
+ /**
2735
+ * 发送最终结果到飞书
2736
+ */
2737
+ async sendResult(result, context) {
2738
+ return this.sendToFeishu(result, context, "result");
2739
+ }
2588
2740
  };
2589
2741
 
2590
2742
  // src/adapters/claude-code.ts
@@ -3151,22 +3303,255 @@ init_logger();
3151
3303
  var fs9 = __toESM(require("fs"));
3152
3304
  var path9 = __toESM(require("path"));
3153
3305
  var os9 = __toESM(require("os"));
3306
+ var http = __toESM(require("http"));
3154
3307
  var import_events = require("events");
3308
+ var import_ws = __toESM(require("ws"));
3309
+ var WebSocketManager = class extends import_events.EventEmitter {
3310
+ constructor(config) {
3311
+ super();
3312
+ this.wss = null;
3313
+ this.clients = /* @__PURE__ */ new Map();
3314
+ this.isRunning = false;
3315
+ this.config = config;
3316
+ }
3317
+ start() {
3318
+ return new Promise((resolve, reject) => {
3319
+ if (this.isRunning) {
3320
+ resolve();
3321
+ return;
3322
+ }
3323
+ try {
3324
+ this.wss = new import_ws.WebSocketServer({
3325
+ port: this.config.port,
3326
+ host: this.config.host
3327
+ });
3328
+ this.wss.on("listening", () => {
3329
+ this.isRunning = true;
3330
+ logger.info(
3331
+ `WebSocket server listening on ${this.config.host}:${this.config.port}`
3332
+ );
3333
+ this.emit("listening");
3334
+ resolve();
3335
+ });
3336
+ this.wss.on(
3337
+ "connection",
3338
+ (ws, req) => {
3339
+ const clientId = `${req.socket.remoteAddress}:${req.socket.remotePort}`;
3340
+ logger.info(`WebSocket client connected: ${clientId}`);
3341
+ this.clients.set(clientId, ws);
3342
+ this.emit("connection", clientId, ws);
3343
+ ws.on("message", (data) => {
3344
+ try {
3345
+ const message = JSON.parse(data.toString());
3346
+ this.emit("message", clientId, message);
3347
+ } catch (error) {
3348
+ logger.error("Invalid WebSocket message format:", error);
3349
+ ws.send(JSON.stringify({ error: "Invalid message format" }));
3350
+ }
3351
+ });
3352
+ ws.on("close", () => {
3353
+ logger.info(`WebSocket client disconnected: ${clientId}`);
3354
+ this.clients.delete(clientId);
3355
+ this.emit("disconnect", clientId);
3356
+ });
3357
+ ws.on("error", (error) => {
3358
+ logger.error(`WebSocket error for client ${clientId}:`, error);
3359
+ this.emit("error", clientId, error);
3360
+ });
3361
+ ws.send(
3362
+ JSON.stringify({
3363
+ type: "connected",
3364
+ message: "Connected to Feishu Bridge reverse channel",
3365
+ timestamp: Date.now()
3366
+ })
3367
+ );
3368
+ }
3369
+ );
3370
+ this.wss.on("error", (error) => {
3371
+ logger.error("WebSocket server error:", error);
3372
+ this.emit("error", error);
3373
+ reject(error);
3374
+ });
3375
+ } catch (error) {
3376
+ reject(error);
3377
+ }
3378
+ });
3379
+ }
3380
+ stop() {
3381
+ return new Promise((resolve) => {
3382
+ if (!this.isRunning || !this.wss) {
3383
+ resolve();
3384
+ return;
3385
+ }
3386
+ this.clients.forEach((ws, clientId) => {
3387
+ ws.close();
3388
+ logger.debug(`Closed WebSocket connection: ${clientId}`);
3389
+ });
3390
+ this.clients.clear();
3391
+ this.wss.close(() => {
3392
+ this.isRunning = false;
3393
+ logger.info("WebSocket server stopped");
3394
+ this.emit("closed");
3395
+ resolve();
3396
+ });
3397
+ });
3398
+ }
3399
+ broadcast(message) {
3400
+ const messageStr = JSON.stringify(message);
3401
+ this.clients.forEach((ws, clientId) => {
3402
+ if (ws.readyState === import_ws.default.OPEN) {
3403
+ ws.send(messageStr);
3404
+ }
3405
+ });
3406
+ }
3407
+ sendToClient(clientId, message) {
3408
+ const ws = this.clients.get(clientId);
3409
+ if (ws && ws.readyState === import_ws.default.OPEN) {
3410
+ ws.send(JSON.stringify(message));
3411
+ return true;
3412
+ }
3413
+ return false;
3414
+ }
3415
+ getClientCount() {
3416
+ return this.clients.size;
3417
+ }
3418
+ isActive() {
3419
+ return this.isRunning;
3420
+ }
3421
+ };
3422
+ var HttpManager = class extends import_events.EventEmitter {
3423
+ constructor(config) {
3424
+ super();
3425
+ this.server = null;
3426
+ this.isRunning = false;
3427
+ this.config = config;
3428
+ }
3429
+ start() {
3430
+ return new Promise((resolve, reject) => {
3431
+ if (this.isRunning) {
3432
+ resolve();
3433
+ return;
3434
+ }
3435
+ this.server = http.createServer((req, res) => {
3436
+ res.setHeader("Access-Control-Allow-Origin", "*");
3437
+ res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
3438
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
3439
+ if (req.method === "OPTIONS") {
3440
+ res.writeHead(200);
3441
+ res.end();
3442
+ return;
3443
+ }
3444
+ if (req.url === "/health" && req.method === "GET") {
3445
+ res.writeHead(200, { "Content-Type": "application/json" });
3446
+ res.end(
3447
+ JSON.stringify({
3448
+ status: "healthy",
3449
+ mode: "http-reverse-channel",
3450
+ timestamp: Date.now()
3451
+ })
3452
+ );
3453
+ return;
3454
+ }
3455
+ if (req.url === "/message" && req.method === "POST") {
3456
+ let body = "";
3457
+ req.on("data", (chunk) => {
3458
+ body += chunk.toString();
3459
+ });
3460
+ req.on("end", () => {
3461
+ try {
3462
+ const message = JSON.parse(body);
3463
+ this.emit("message", message);
3464
+ res.writeHead(200, { "Content-Type": "application/json" });
3465
+ res.end(
3466
+ JSON.stringify({
3467
+ success: true,
3468
+ message: "Message received",
3469
+ timestamp: Date.now()
3470
+ })
3471
+ );
3472
+ } catch (error) {
3473
+ logger.error("Invalid HTTP message format:", error);
3474
+ res.writeHead(400, { "Content-Type": "application/json" });
3475
+ res.end(
3476
+ JSON.stringify({
3477
+ success: false,
3478
+ error: "Invalid message format"
3479
+ })
3480
+ );
3481
+ }
3482
+ });
3483
+ return;
3484
+ }
3485
+ res.writeHead(404, { "Content-Type": "application/json" });
3486
+ res.end(JSON.stringify({ error: "Not found" }));
3487
+ });
3488
+ this.server.listen(this.config.port, this.config.host, () => {
3489
+ this.isRunning = true;
3490
+ logger.info(
3491
+ `HTTP reverse channel server listening on ${this.config.host}:${this.config.port}`
3492
+ );
3493
+ this.emit("listening");
3494
+ resolve();
3495
+ });
3496
+ this.server.on("error", (error) => {
3497
+ logger.error("HTTP server error:", error);
3498
+ this.emit("error", error);
3499
+ reject(error);
3500
+ });
3501
+ });
3502
+ }
3503
+ stop() {
3504
+ return new Promise((resolve) => {
3505
+ if (!this.isRunning || !this.server) {
3506
+ resolve();
3507
+ return;
3508
+ }
3509
+ this.server.close(() => {
3510
+ this.isRunning = false;
3511
+ logger.info("HTTP server stopped");
3512
+ this.emit("closed");
3513
+ resolve();
3514
+ });
3515
+ });
3516
+ }
3517
+ isActive() {
3518
+ return this.isRunning;
3519
+ }
3520
+ };
3155
3521
  var ReverseChannel = class extends import_events.EventEmitter {
3156
3522
  constructor(feishuClient) {
3157
3523
  super();
3158
3524
  this.isRunning = false;
3159
3525
  this.checkInterval = null;
3160
3526
  this.messageQueue = [];
3527
+ // 模式特定的管理器
3528
+ this.wsManager = null;
3529
+ this.httpManager = null;
3530
+ // 健康检查
3531
+ this.healthCheckTimer = null;
3532
+ this.reconnectAttempts = 0;
3533
+ this.isReconnecting = false;
3161
3534
  this.feishuClient = feishuClient;
3162
3535
  const bridgeConfig = ConfigManager.getInstance().get();
3163
3536
  this.config = {
3164
3537
  enabled: bridgeConfig.behavior?.reverseChannelEnabled ?? true,
3165
- mode: bridgeConfig.behavior?.reverseChannelMode ?? "filesystem",
3538
+ mode: bridgeConfig.behavior?.reverseChannelMode ?? "websocket",
3539
+ // 默认改为WebSocket
3166
3540
  checkInterval: 1e3,
3167
- // 1秒检查一次
3168
- maxMessageAge: 5 * 60 * 1e3
3169
- // 5分钟过期
3541
+ maxMessageAge: 5 * 60 * 1e3,
3542
+ // WebSocket默认配置
3543
+ websocketPort: bridgeConfig.server?.port ? bridgeConfig.server.port + 1 : 3001,
3544
+ websocketHost: bridgeConfig.server?.host || "0.0.0.0",
3545
+ // HTTP默认配置
3546
+ httpPort: bridgeConfig.server?.port ? bridgeConfig.server.port + 2 : 3002,
3547
+ httpHost: bridgeConfig.server?.host || "0.0.0.0",
3548
+ // 健康检查配置
3549
+ healthCheckInterval: 3e4,
3550
+ // 30秒
3551
+ autoReconnect: true,
3552
+ reconnectDelay: 5e3,
3553
+ // 5秒
3554
+ maxReconnectAttempts: 10
3170
3555
  };
3171
3556
  this.tempDir = path9.join(os9.tmpdir(), "feishu-bridge", "reverse-channel");
3172
3557
  this.ensureTempDir();
@@ -3174,46 +3559,223 @@ var ReverseChannel = class extends import_events.EventEmitter {
3174
3559
  /**
3175
3560
  * 启动反向通信通道
3176
3561
  */
3177
- start() {
3562
+ async start() {
3178
3563
  if (this.isRunning || !this.config.enabled) {
3179
3564
  return;
3180
3565
  }
3181
3566
  this.isRunning = true;
3182
- logger.info("Starting reverse communication channel...");
3567
+ this.reconnectAttempts = 0;
3568
+ logger.info(
3569
+ `Starting reverse communication channel in ${this.config.mode} mode...`
3570
+ );
3571
+ try {
3572
+ await this.startByMode();
3573
+ this.startHealthCheck();
3574
+ this.startMessageQueueProcessor();
3575
+ this.emit("started");
3576
+ } catch (error) {
3577
+ logger.error("Failed to start reverse channel:", error);
3578
+ this.isRunning = false;
3579
+ throw error;
3580
+ }
3581
+ }
3582
+ /**
3583
+ * 根据模式启动对应服务
3584
+ */
3585
+ async startByMode() {
3183
3586
  switch (this.config.mode) {
3587
+ case "websocket":
3588
+ await this.startWebSocketServer();
3589
+ break;
3590
+ case "http":
3591
+ await this.startHttpServer();
3592
+ break;
3184
3593
  case "filesystem":
3185
3594
  this.startFileSystemListener();
3186
3595
  break;
3596
+ default:
3597
+ throw new Error(`Unknown reverse channel mode: ${this.config.mode}`);
3598
+ }
3599
+ }
3600
+ /**
3601
+ * 启动WebSocket服务器
3602
+ */
3603
+ async startWebSocketServer() {
3604
+ this.wsManager = new WebSocketManager({
3605
+ port: this.config.websocketPort,
3606
+ host: this.config.websocketHost
3607
+ });
3608
+ this.wsManager.on("message", (clientId, message) => {
3609
+ logger.debug(`Received WebSocket message from ${clientId}:`, message);
3610
+ if (this.isValidMessage(message)) {
3611
+ const messageId = message.id || `${clientId}_${Date.now()}`;
3612
+ this.messageQueue.push({
3613
+ ...message,
3614
+ timestamp: message.timestamp || Date.now(),
3615
+ id: messageId
3616
+ });
3617
+ this.emit("messageReceived", message);
3618
+ this.wsManager?.sendToClient(clientId, {
3619
+ type: "ack",
3620
+ originalId: messageId,
3621
+ timestamp: Date.now()
3622
+ });
3623
+ } else {
3624
+ this.wsManager?.sendToClient(clientId, {
3625
+ type: "error",
3626
+ error: "Invalid message format",
3627
+ timestamp: Date.now()
3628
+ });
3629
+ }
3630
+ });
3631
+ this.wsManager.on("error", (error) => {
3632
+ logger.error("WebSocket manager error:", error);
3633
+ this.handleConnectionError();
3634
+ });
3635
+ await this.wsManager.start();
3636
+ }
3637
+ /**
3638
+ * 启动HTTP服务器
3639
+ */
3640
+ async startHttpServer() {
3641
+ this.httpManager = new HttpManager({
3642
+ port: this.config.httpPort,
3643
+ host: this.config.httpHost
3644
+ });
3645
+ this.httpManager.on("message", (message) => {
3646
+ logger.debug("Received HTTP message:", message);
3647
+ if (this.isValidMessage(message)) {
3648
+ this.messageQueue.push({
3649
+ ...message,
3650
+ timestamp: message.timestamp || Date.now()
3651
+ });
3652
+ this.emit("messageReceived", message);
3653
+ }
3654
+ });
3655
+ this.httpManager.on("error", (error) => {
3656
+ logger.error("HTTP manager error:", error);
3657
+ this.handleConnectionError();
3658
+ });
3659
+ await this.httpManager.start();
3660
+ }
3661
+ /**
3662
+ * 验证消息格式
3663
+ */
3664
+ isValidMessage(message) {
3665
+ return message && typeof message === "object" && typeof message.content === "string" && (message.chatId || message.chatId === void 0);
3666
+ }
3667
+ /**
3668
+ * 处理连接错误
3669
+ */
3670
+ handleConnectionError() {
3671
+ if (!this.config.autoReconnect || this.isReconnecting) {
3672
+ return;
3673
+ }
3674
+ this.isReconnecting = true;
3675
+ this.reconnectAttempts++;
3676
+ if (this.reconnectAttempts > this.config.maxReconnectAttempts) {
3677
+ logger.error(
3678
+ `Max reconnect attempts (${this.config.maxReconnectAttempts}) reached. Giving up.`
3679
+ );
3680
+ this.emit("reconnectFailed");
3681
+ this.isReconnecting = false;
3682
+ return;
3683
+ }
3684
+ logger.info(
3685
+ `Attempting to reconnect (${this.reconnectAttempts}/${this.config.maxReconnectAttempts})...`
3686
+ );
3687
+ setTimeout(async () => {
3688
+ try {
3689
+ await this.restart();
3690
+ this.reconnectAttempts = 0;
3691
+ logger.info("Reconnection successful");
3692
+ this.emit("reconnected");
3693
+ } catch (error) {
3694
+ logger.error("Reconnection failed:", error);
3695
+ } finally {
3696
+ this.isReconnecting = false;
3697
+ }
3698
+ }, this.config.reconnectDelay);
3699
+ }
3700
+ /**
3701
+ * 重启服务
3702
+ */
3703
+ async restart() {
3704
+ await this.stopInternal();
3705
+ await this.startByMode();
3706
+ }
3707
+ /**
3708
+ * 启动健康检查
3709
+ */
3710
+ startHealthCheck() {
3711
+ this.healthCheckTimer = setInterval(() => {
3712
+ this.performHealthCheck();
3713
+ }, this.config.healthCheckInterval);
3714
+ }
3715
+ /**
3716
+ * 执行健康检查
3717
+ */
3718
+ performHealthCheck() {
3719
+ let isHealthy = false;
3720
+ switch (this.config.mode) {
3187
3721
  case "websocket":
3188
- this.startWebSocketListener();
3722
+ isHealthy = this.wsManager?.isActive() ?? false;
3189
3723
  break;
3190
3724
  case "http":
3191
- this.startHttpListener();
3725
+ isHealthy = this.httpManager?.isActive() ?? false;
3192
3726
  break;
3727
+ case "filesystem":
3728
+ isHealthy = this.isRunning;
3729
+ break;
3730
+ }
3731
+ if (!isHealthy && this.config.autoReconnect && !this.isReconnecting) {
3732
+ logger.warn("Health check failed, triggering reconnection...");
3733
+ this.handleConnectionError();
3193
3734
  }
3194
- this.startMessageQueueProcessor();
3735
+ this.emit("healthCheck", { healthy: isHealthy, mode: this.config.mode });
3195
3736
  }
3196
3737
  /**
3197
3738
  * 停止反向通信通道
3198
3739
  */
3199
- stop() {
3200
- if (!this.isRunning) {
3201
- return;
3202
- }
3740
+ async stop() {
3741
+ await this.stopInternal();
3742
+ this.emit("stopped");
3743
+ }
3744
+ /**
3745
+ * 内部停止方法
3746
+ */
3747
+ async stopInternal() {
3203
3748
  this.isRunning = false;
3204
3749
  logger.info("Stopping reverse communication channel...");
3750
+ if (this.healthCheckTimer) {
3751
+ clearInterval(this.healthCheckTimer);
3752
+ this.healthCheckTimer = null;
3753
+ }
3205
3754
  if (this.checkInterval) {
3206
3755
  clearInterval(this.checkInterval);
3207
3756
  this.checkInterval = null;
3208
3757
  }
3758
+ if (this.wsManager) {
3759
+ await this.wsManager.stop();
3760
+ this.wsManager = null;
3761
+ }
3762
+ if (this.httpManager) {
3763
+ await this.httpManager.stop();
3764
+ this.httpManager = null;
3765
+ }
3209
3766
  }
3210
3767
  /**
3211
3768
  * 发送消息到飞书(供适配器调用)
3212
3769
  */
3213
3770
  async sendToFeishu(message) {
3214
3771
  try {
3215
- this.messageQueue.push(message);
3216
- this.emit("messageQueued", message);
3772
+ const queuedMessage = {
3773
+ ...message,
3774
+ id: `local_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
3775
+ timestamp: message.timestamp || Date.now()
3776
+ };
3777
+ this.messageQueue.push(queuedMessage);
3778
+ this.emit("messageQueued", queuedMessage);
3217
3779
  return true;
3218
3780
  } catch (error) {
3219
3781
  logger.error("Error queuing message to Feishu:", error);
@@ -3223,9 +3785,9 @@ var ReverseChannel = class extends import_events.EventEmitter {
3223
3785
  /**
3224
3786
  * 发送文件系统消息(供IDE写入文件后调用)
3225
3787
  */
3226
- async sendFileSystemMessage(adapterType, content, chatId) {
3788
+ async sendFileSystemMessage(adapterType, content, chatId, type = "message") {
3227
3789
  const message = {
3228
- type: "message",
3790
+ type,
3229
3791
  content,
3230
3792
  chatId,
3231
3793
  timestamp: Date.now()
@@ -3265,7 +3827,10 @@ var ReverseChannel = class extends import_events.EventEmitter {
3265
3827
  fs9.unlinkSync(filePath);
3266
3828
  continue;
3267
3829
  }
3268
- this.messageQueue.push(message);
3830
+ this.messageQueue.push({
3831
+ ...message,
3832
+ id: `fs_${file}`
3833
+ });
3269
3834
  fs9.unlinkSync(filePath);
3270
3835
  logger.debug(`Processed file system message: ${file}`);
3271
3836
  } catch (error) {
@@ -3280,14 +3845,6 @@ var ReverseChannel = class extends import_events.EventEmitter {
3280
3845
  logger.error("Error reading message directory:", error);
3281
3846
  }
3282
3847
  }
3283
- startWebSocketListener() {
3284
- logger.info("WebSocket reverse channel not yet implemented, falling back to file system");
3285
- this.startFileSystemListener();
3286
- }
3287
- startHttpListener() {
3288
- logger.info("HTTP reverse channel not yet implemented, falling back to file system");
3289
- this.startFileSystemListener();
3290
- }
3291
3848
  startMessageQueueProcessor() {
3292
3849
  setInterval(async () => {
3293
3850
  await this.processMessageQueue();
@@ -3305,19 +3862,25 @@ var ReverseChannel = class extends import_events.EventEmitter {
3305
3862
  this.emit("messageDelivered", message);
3306
3863
  } catch (error) {
3307
3864
  logger.error("Error delivering message to Feishu:", error);
3308
- if (!message.retryCount || message.retryCount < 3) {
3865
+ if ((message.retryCount || 0) < 3) {
3309
3866
  message.retryCount = (message.retryCount || 0) + 1;
3310
3867
  this.messageQueue.push(message);
3868
+ } else {
3869
+ this.emit("messageFailed", message, error);
3311
3870
  }
3312
3871
  }
3313
3872
  }
3314
3873
  }
3315
3874
  async deliverMessageToFeishu(message) {
3316
- const { type, content, chatId, metadata } = message;
3875
+ const { type, content, chatId, metadata, media } = message;
3317
3876
  if (!chatId) {
3318
3877
  logger.warn("No chatId specified for message, cannot deliver");
3319
3878
  return;
3320
3879
  }
3880
+ if (media) {
3881
+ await this.deliverMediaMessage(chatId, media, content);
3882
+ return;
3883
+ }
3321
3884
  let formattedContent = content;
3322
3885
  switch (type) {
3323
3886
  case "notification":
@@ -3329,6 +3892,9 @@ var ReverseChannel = class extends import_events.EventEmitter {
3329
3892
  case "result":
3330
3893
  formattedContent = `\u2705 ${content}`;
3331
3894
  break;
3895
+ case "progress":
3896
+ formattedContent = `\u23F3 ${content}`;
3897
+ break;
3332
3898
  default:
3333
3899
  formattedContent = content;
3334
3900
  }
@@ -3344,12 +3910,41 @@ var ReverseChannel = class extends import_events.EventEmitter {
3344
3910
  });
3345
3911
  logger.info(`Message delivered to Feishu chat ${chatId}`);
3346
3912
  }
3913
+ /**
3914
+ * 发送富媒体消息
3915
+ */
3916
+ async deliverMediaMessage(chatId, media, caption) {
3917
+ logger.info(`Delivering media message to ${chatId}: ${media.type}`);
3918
+ await this.feishuClient.sendMessage(chatId, {
3919
+ msg_type: "text",
3920
+ content: {
3921
+ text: `${caption || ""}
3922
+ [${media.type}: ${media.filename || "unnamed"}]`
3923
+ }
3924
+ });
3925
+ }
3926
+ // 公共API
3347
3927
  isActive() {
3348
3928
  return this.isRunning;
3349
3929
  }
3350
3930
  getQueueSize() {
3351
3931
  return this.messageQueue.length;
3352
3932
  }
3933
+ getMode() {
3934
+ return this.config.mode;
3935
+ }
3936
+ getWebSocketPort() {
3937
+ return this.config.websocketPort;
3938
+ }
3939
+ getHttpPort() {
3940
+ return this.config.httpPort;
3941
+ }
3942
+ getConnectedClients() {
3943
+ if (this.config.mode === "websocket" && this.wsManager) {
3944
+ return this.wsManager.getClientCount();
3945
+ }
3946
+ return 0;
3947
+ }
3353
3948
  };
3354
3949
 
3355
3950
  // src/skills/builtin/feishu.ts
@@ -3368,14 +3963,17 @@ var FeishuSkills = class {
3368
3963
  return this.accessToken;
3369
3964
  }
3370
3965
  const config = ConfigManager.getInstance().getFeishuConfig();
3371
- const response = await fetch(`${this.baseUrl}/auth/v3/tenant_access_token/internal`, {
3372
- method: "POST",
3373
- headers: { "Content-Type": "application/json" },
3374
- body: JSON.stringify({
3375
- app_id: config.appId,
3376
- app_secret: config.appSecret
3377
- })
3378
- });
3966
+ const response = await fetch(
3967
+ `${this.baseUrl}/auth/v3/tenant_access_token/internal`,
3968
+ {
3969
+ method: "POST",
3970
+ headers: { "Content-Type": "application/json" },
3971
+ body: JSON.stringify({
3972
+ app_id: config.appId,
3973
+ app_secret: config.appSecret
3974
+ })
3975
+ }
3976
+ );
3379
3977
  const data = await response.json();
3380
3978
  if (data.code !== 0) {
3381
3979
  throw new Error(`Failed to get access token: ${data.msg}`);
@@ -3390,10 +3988,7 @@ var FeishuSkills = class {
3390
3988
  description: "\u53D1\u9001\u6D88\u606F\u5230\u6307\u5B9A\u4F1A\u8BDD",
3391
3989
  trigger: /发送消息|发信息|发消息/,
3392
3990
  riskLevel: "low",
3393
- examples: [
3394
- "\u53D1\u9001\u6D88\u606F\u7ED9\u5F20\u4E09\uFF1A\u4E0B\u5348\u5F00\u4F1A",
3395
- "\u53D1\u4FE1\u606F\u5230\u6D4B\u8BD5\u7FA4\uFF1A\u9879\u76EE\u5DF2\u90E8\u7F72"
3396
- ],
3991
+ examples: ["\u53D1\u9001\u6D88\u606F\u7ED9\u5F20\u4E09\uFF1A\u4E0B\u5348\u5F00\u4F1A", "\u53D1\u4FE1\u606F\u5230\u6D4B\u8BD5\u7FA4\uFF1A\u9879\u76EE\u5DF2\u90E8\u7F72"],
3397
3992
  execute: async (context) => {
3398
3993
  try {
3399
3994
  const { content } = this.parseMessageArgs(context.message);
@@ -3403,7 +3998,10 @@ var FeishuSkills = class {
3403
3998
  });
3404
3999
  return { success: true, content: "\u2705 \u6D88\u606F\u5DF2\u53D1\u9001" };
3405
4000
  } catch (error) {
3406
- return { success: false, content: `\u274C \u53D1\u9001\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}` };
4001
+ return {
4002
+ success: false,
4003
+ content: `\u274C \u53D1\u9001\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`
4004
+ };
3407
4005
  }
3408
4006
  }
3409
4007
  };
@@ -3422,46 +4020,57 @@ var FeishuSkills = class {
3422
4020
  try {
3423
4021
  const { title, startTime, endTime, description } = this.parseEventArgs(context.message);
3424
4022
  const token = await this.getAccessToken();
3425
- const response = await fetch(`${this.baseUrl}/calendar/v4/calendars/primary/events`, {
3426
- method: "POST",
3427
- headers: {
3428
- "Authorization": `Bearer ${token}`,
3429
- "Content-Type": "application/json"
3430
- },
3431
- body: JSON.stringify({
3432
- summary: title,
3433
- description,
3434
- start_time: {
3435
- timestamp: Math.floor(startTime / 1e3).toString(),
3436
- timezone: "Asia/Shanghai"
3437
- },
3438
- end_time: {
3439
- timestamp: Math.floor(endTime / 1e3).toString(),
3440
- timezone: "Asia/Shanghai"
4023
+ const response = await fetch(
4024
+ `${this.baseUrl}/calendar/v4/calendars/primary/events`,
4025
+ {
4026
+ method: "POST",
4027
+ headers: {
4028
+ Authorization: `Bearer ${token}`,
4029
+ "Content-Type": "application/json"
3441
4030
  },
3442
- attendee_ability: "can_see_others",
3443
- free_busy_status: "busy"
3444
- })
3445
- });
4031
+ body: JSON.stringify({
4032
+ summary: title,
4033
+ description,
4034
+ start_time: {
4035
+ timestamp: Math.floor(startTime / 1e3).toString(),
4036
+ timezone: "Asia/Shanghai"
4037
+ },
4038
+ end_time: {
4039
+ timestamp: Math.floor(endTime / 1e3).toString(),
4040
+ timezone: "Asia/Shanghai"
4041
+ },
4042
+ attendee_ability: "can_see_others",
4043
+ free_busy_status: "busy"
4044
+ })
4045
+ }
4046
+ );
3446
4047
  const data = await response.json();
3447
4048
  if (data.code !== 0) {
3448
4049
  throw new Error(data.msg || "\u521B\u5EFA\u65E5\u7A0B\u5931\u8D25");
3449
4050
  }
3450
4051
  const eventLink = data.data?.event_link || "";
4052
+ const linkText = eventLink ? `
4053
+
4054
+ \u{1F517} ${eventLink}` : "";
3451
4055
  return {
3452
4056
  success: true,
3453
4057
  content: `\u2705 \u65E5\u7A0B\u5DF2\u521B\u5EFA
3454
4058
 
3455
4059
  \u{1F4C5} ${title}
3456
- \u{1F550} ${this.formatDateTime(startTime)} - ${this.formatDateTime(endTime)}
3457
-
3458
- \u{1F517} ${eventLink}`,
3459
- actions: [
3460
- { label: "\u67E5\u770B\u65E5\u7A0B", action: "view_calendar", url: eventLink }
3461
- ]
4060
+ \u{1F550} ${this.formatDateTime(startTime)} - ${this.formatDateTime(endTime)}${linkText}`,
4061
+ actions: eventLink ? [
4062
+ {
4063
+ label: "\u67E5\u770B\u65E5\u7A0B",
4064
+ action: "view_calendar",
4065
+ data: { url: eventLink }
4066
+ }
4067
+ ] : []
3462
4068
  };
3463
4069
  } catch (error) {
3464
- return { success: false, content: `\u274C \u521B\u5EFA\u65E5\u7A0B\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}` };
4070
+ return {
4071
+ success: false,
4072
+ content: `\u274C \u521B\u5EFA\u65E5\u7A0B\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`
4073
+ };
3465
4074
  }
3466
4075
  }
3467
4076
  };
@@ -3472,10 +4081,7 @@ var FeishuSkills = class {
3472
4081
  description: "\u521B\u5EFA\u98DE\u4E66\u4E91\u6587\u6863",
3473
4082
  trigger: /创建文档|新建文档|生成文档/,
3474
4083
  riskLevel: "low",
3475
- examples: [
3476
- "\u521B\u5EFA\u6587\u6863\uFF1A\u9879\u76EE\u9700\u6C42\u6587\u6863",
3477
- "\u65B0\u5EFA\u6587\u6863\uFF1A\u4F1A\u8BAE\u7EAA\u8981"
3478
- ],
4084
+ examples: ["\u521B\u5EFA\u6587\u6863\uFF1A\u9879\u76EE\u9700\u6C42\u6587\u6863", "\u65B0\u5EFA\u6587\u6863\uFF1A\u4F1A\u8BAE\u7EAA\u8981"],
3479
4085
  execute: async (context) => {
3480
4086
  try {
3481
4087
  const { title } = this.parseDocArgs(context.message);
@@ -3483,7 +4089,7 @@ var FeishuSkills = class {
3483
4089
  const response = await fetch(`${this.baseUrl}/docx/v1/documents`, {
3484
4090
  method: "POST",
3485
4091
  headers: {
3486
- "Authorization": `Bearer ${token}`,
4092
+ Authorization: `Bearer ${token}`,
3487
4093
  "Content-Type": "application/json"
3488
4094
  },
3489
4095
  body: JSON.stringify({ title })
@@ -3492,22 +4098,30 @@ var FeishuSkills = class {
3492
4098
  if (data.code !== 0) {
3493
4099
  throw new Error(data.msg || "\u521B\u5EFA\u6587\u6863\u5931\u8D25");
3494
4100
  }
3495
- const documentId = data.data?.document?.document_id;
4101
+ const documentId = data.data?.document?.document_id || "";
3496
4102
  const domain = ConfigManager.getInstance().getFeishuConfig().domain === "lark" ? "larksuite.com" : "feishu.cn";
3497
- const documentUrl = `https://${domain}/docx/${documentId}`;
4103
+ const documentUrl = documentId ? `https://${domain}/docx/${documentId}` : "";
4104
+ const urlText = documentUrl ? `
4105
+
4106
+ \u{1F517} ${documentUrl}` : "";
3498
4107
  return {
3499
4108
  success: true,
3500
4109
  content: `\u2705 \u6587\u6863\u5DF2\u521B\u5EFA
3501
4110
 
3502
- \u{1F4C4} ${title}
3503
-
3504
- \u{1F517} ${documentUrl}`,
3505
- actions: [
3506
- { label: "\u6253\u5F00\u6587\u6863", action: "open_doc", url: documentUrl }
3507
- ]
4111
+ \u{1F4C4} ${title}${urlText}`,
4112
+ actions: documentUrl ? [
4113
+ {
4114
+ label: "\u6253\u5F00\u6587\u6863",
4115
+ action: "open_doc",
4116
+ data: { url: documentUrl }
4117
+ }
4118
+ ] : []
3508
4119
  };
3509
4120
  } catch (error) {
3510
- return { success: false, content: `\u274C \u521B\u5EFA\u6587\u6863\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}` };
4121
+ return {
4122
+ success: false,
4123
+ content: `\u274C \u521B\u5EFA\u6587\u6863\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`
4124
+ };
3511
4125
  }
3512
4126
  }
3513
4127
  };
@@ -3519,7 +4133,10 @@ var FeishuSkills = class {
3519
4133
  trigger: /上传文件|发送文件|传文件/,
3520
4134
  riskLevel: "low",
3521
4135
  execute: async (context) => {
3522
- return { success: true, content: "\u{1F4CE} \u6587\u4EF6\u4E0A\u4F20\u529F\u80FD\u9700\u8981\u6587\u4EF6\u8DEF\u5F84\uFF0C\u8BF7\u4F7F\u7528\uFF1A\u4E0A\u4F20\u6587\u4EF6 /path/to/file" };
4136
+ return {
4137
+ success: true,
4138
+ content: "\u{1F4CE} \u6587\u4EF6\u4E0A\u4F20\u529F\u80FD\u9700\u8981\u6587\u4EF6\u8DEF\u5F84\uFF0C\u8BF7\u4F7F\u7528\uFF1A\u4E0A\u4F20\u6587\u4EF6 /path/to/file"
4139
+ };
3523
4140
  }
3524
4141
  };
3525
4142
  }
@@ -3532,7 +4149,9 @@ var FeishuSkills = class {
3532
4149
  requireConfirm: true,
3533
4150
  execute: async (context) => {
3534
4151
  try {
3535
- const { title, content } = this.parseAnnouncementArgs(context.message);
4152
+ const { title, content } = this.parseAnnouncementArgs(
4153
+ context.message
4154
+ );
3536
4155
  await this.client.sendMessage(context.chatId, {
3537
4156
  msg_type: "text",
3538
4157
  content: { text: `\u{1F4E2} **${title}**
@@ -3541,7 +4160,10 @@ ${content}` }
3541
4160
  });
3542
4161
  return { success: true, content: "\u2705 \u516C\u544A\u5DF2\u53D1\u9001" };
3543
4162
  } catch (error) {
3544
- return { success: false, content: `\u274C \u53D1\u5E03\u516C\u544A\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}` };
4163
+ return {
4164
+ success: false,
4165
+ content: `\u274C \u53D1\u5E03\u516C\u544A\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`
4166
+ };
3545
4167
  }
3546
4168
  }
3547
4169
  };
@@ -3553,9 +4175,13 @@ ${content}` }
3553
4175
  }
3554
4176
  parseEventArgs(message) {
3555
4177
  const now = /* @__PURE__ */ new Date();
3556
- const titleMatch = message.match(/日程[::]\s*(.+?)(?=(明天|今天|后天|时间|地点|$))/i);
4178
+ const titleMatch = message.match(
4179
+ /日程[::]\s*(.+?)(?=(明天|今天|后天|时间|地点|$))/i
4180
+ );
3557
4181
  const title = titleMatch ? titleMatch[1].trim() : "\u65B0\u65E5\u7A0B";
3558
- const timeMatch = message.match(/(明天|今天|后天)?\s*(上午|下午|晚上)?\s*(\d{1,2})[:点时]?(\d{0,2})?/);
4182
+ const timeMatch = message.match(
4183
+ /(明天|今天|后天)?\s*(上午|下午|晚上)?\s*(\d{1,2})[:点时]?(\d{0,2})?/
4184
+ );
3559
4185
  let startTime = new Date(now.getTime() + 60 * 60 * 1e3);
3560
4186
  if (timeMatch) {
3561
4187
  const day = timeMatch[1];
@@ -3569,7 +4195,12 @@ ${content}` }
3569
4195
  startTime.setHours(hour, minute, 0, 0);
3570
4196
  }
3571
4197
  const endTime = new Date(startTime.getTime() + 60 * 60 * 1e3);
3572
- return { title, startTime: startTime.getTime(), endTime: endTime.getTime(), description: "" };
4198
+ return {
4199
+ title,
4200
+ startTime: startTime.getTime(),
4201
+ endTime: endTime.getTime(),
4202
+ description: ""
4203
+ };
3573
4204
  }
3574
4205
  parseDocArgs(message) {
3575
4206
  const match = message.match(/文档[::]\s*(.+?)(?=(内容|文件夹|$))/i);
@@ -3986,16 +4617,28 @@ function initializeSkills(registry, feishuClient, getAdapter) {
3986
4617
  var import_fastify = __toESM(require("fastify"));
3987
4618
  var FeishuBridge = class {
3988
4619
  constructor() {
4620
+ this.feishuClient = null;
4621
+ this.webhookHandler = null;
3989
4622
  this.wsHandler = null;
4623
+ this.commandExecutor = null;
4624
+ this.sessionManager = null;
3990
4625
  this.healthMonitor = null;
3991
4626
  this.reverseChannel = null;
3992
4627
  this.adapters = /* @__PURE__ */ new Map();
4628
+ this.skillRegistry = null;
3993
4629
  this.server = (0, import_fastify.default)({
3994
4630
  logger: false
3995
4631
  // 使用我们自己的日志系统
3996
4632
  });
4633
+ }
4634
+ /**
4635
+ * 初始化桥接器(在配置加载完成后调用)
4636
+ */
4637
+ initialize() {
3997
4638
  const config = ConfigManager.getInstance().get();
4639
+ logger.info(`Initializing FeishuBridge with appId: ${config.feishu.appId}`);
3998
4640
  this.feishuClient = new FeishuClient(config.feishu);
4641
+ logger.info("FeishuClient created successfully");
3999
4642
  this.sessionManager = new SessionManager(config.behavior.sessionTimeout);
4000
4643
  this.reverseChannel = new ReverseChannel(this.feishuClient);
4001
4644
  this.initAdapters();
@@ -4058,11 +4701,23 @@ var FeishuBridge = class {
4058
4701
  }
4059
4702
  }
4060
4703
  async start() {
4704
+ this.initialize();
4061
4705
  const config = ConfigManager.getInstance().get();
4062
4706
  const serverConfig = config.server;
4063
4707
  if (this.reverseChannel) {
4064
- this.reverseChannel.start();
4065
- logger.info("Reverse communication channel started");
4708
+ await this.reverseChannel.start();
4709
+ logger.info(
4710
+ `Reverse communication channel started in ${this.reverseChannel.getMode()} mode`
4711
+ );
4712
+ if (this.reverseChannel.getMode() === "websocket") {
4713
+ logger.info(
4714
+ `WebSocket reverse channel port: ${this.reverseChannel.getWebSocketPort()}`
4715
+ );
4716
+ } else if (this.reverseChannel.getMode() === "http") {
4717
+ logger.info(
4718
+ `HTTP reverse channel port: ${this.reverseChannel.getHttpPort()}`
4719
+ );
4720
+ }
4066
4721
  }
4067
4722
  if (config.feishu.connectionMode !== "websocket") {
4068
4723
  this.healthMonitor = new HealthMonitor(
@@ -4102,7 +4757,7 @@ var FeishuBridge = class {
4102
4757
  }
4103
4758
  async stop() {
4104
4759
  if (this.reverseChannel) {
4105
- this.reverseChannel.stop();
4760
+ await this.reverseChannel.stop();
4106
4761
  logger.info("Reverse communication channel stopped");
4107
4762
  }
4108
4763
  if (this.wsHandler) {
@@ -4124,7 +4779,9 @@ var FeishuBridge = class {
4124
4779
  init_config();
4125
4780
  init_logger();
4126
4781
  var program = new import_commander.Command();
4127
- var pkg = JSON.parse(fs10.readFileSync(path10.join(__dirname, "../../package.json"), "utf-8"));
4782
+ var pkg = JSON.parse(
4783
+ fs10.readFileSync(path10.join(__dirname, "../../package.json"), "utf-8")
4784
+ );
4128
4785
  program.name("feishu-bridge").description("Feishu Bridge - AI IDE/CLI integration for Feishu").version(pkg.version);
4129
4786
  program.command("start").description("Start the Feishu Bridge server").option("-p, --port <port>", "Server port", "3000").option("-h, --host <host>", "Server host", "0.0.0.0").option("-d, --daemon", "Run in daemon mode").action(async (options) => {
4130
4787
  try {
@@ -4133,22 +4790,33 @@ program.command("start").description("Start the Feishu Bridge server").option("-
4133
4790
  const config = { ...DEFAULT_CONFIG };
4134
4791
  if (fileConfig.appId) config.feishu.appId = fileConfig.appId;
4135
4792
  if (fileConfig.appSecret) config.feishu.appSecret = fileConfig.appSecret;
4136
- if (fileConfig.encryptKey) config.feishu.encryptKey = fileConfig.encryptKey;
4137
- if (fileConfig.verificationToken) config.feishu.verificationToken = fileConfig.verificationToken;
4793
+ if (fileConfig.encryptKey)
4794
+ config.feishu.encryptKey = fileConfig.encryptKey;
4795
+ if (fileConfig.verificationToken)
4796
+ config.feishu.verificationToken = fileConfig.verificationToken;
4138
4797
  if (fileConfig.domain) config.feishu.domain = fileConfig.domain;
4139
4798
  config.server.port = parseInt(options.port);
4140
4799
  config.server.host = options.host;
4141
- if (process.env.FEISHU_APP_ID) config.feishu.appId = process.env.FEISHU_APP_ID;
4142
- if (process.env.FEISHU_APP_SECRET) config.feishu.appSecret = process.env.FEISHU_APP_SECRET;
4143
- if (process.env.FEISHU_ENCRYPT_KEY) config.feishu.encryptKey = process.env.FEISHU_ENCRYPT_KEY;
4144
- if (process.env.FEISHU_VERIFICATION_TOKEN) config.feishu.verificationToken = process.env.FEISHU_VERIFICATION_TOKEN;
4145
- if (process.env.SERVER_PORT) config.server.port = parseInt(process.env.SERVER_PORT);
4800
+ if (process.env.FEISHU_APP_ID)
4801
+ config.feishu.appId = process.env.FEISHU_APP_ID;
4802
+ if (process.env.FEISHU_APP_SECRET)
4803
+ config.feishu.appSecret = process.env.FEISHU_APP_SECRET;
4804
+ if (process.env.FEISHU_ENCRYPT_KEY)
4805
+ config.feishu.encryptKey = process.env.FEISHU_ENCRYPT_KEY;
4806
+ if (process.env.FEISHU_VERIFICATION_TOKEN)
4807
+ config.feishu.verificationToken = process.env.FEISHU_VERIFICATION_TOKEN;
4808
+ if (process.env.SERVER_PORT)
4809
+ config.server.port = parseInt(process.env.SERVER_PORT);
4146
4810
  if (process.env.SERVER_HOST) config.server.host = process.env.SERVER_HOST;
4147
4811
  if (!config.feishu.appId || !config.feishu.appSecret) {
4148
- console.error("Error: FEISHU_APP_ID and FEISHU_APP_SECRET are required");
4812
+ console.error(
4813
+ "Error: FEISHU_APP_ID and FEISHU_APP_SECRET are required"
4814
+ );
4149
4815
  console.error("Set them via:");
4150
4816
  console.error(" 1. Environment variables: export FEISHU_APP_ID=xxx");
4151
- console.error(" 2. Config file: feishu-bridge config set feishu.appId xxx");
4817
+ console.error(
4818
+ " 2. Config file: feishu-bridge config set feishu.appId xxx"
4819
+ );
4152
4820
  process.exit(1);
4153
4821
  }
4154
4822
  configManager.load(config);
@@ -4268,38 +4936,61 @@ program.command("status").description("Show service status").action(async () =>
4268
4936
  const config = listConfig();
4269
4937
  console.log("\n\u{1F4C1} Configuration:");
4270
4938
  console.log(` Config file: ${getConfigFilePath()}`);
4271
- console.log(` Feishu App ID: ${config.feishu?.appId ? "\u2705 Set" : "\u274C Not set"}`);
4272
- console.log(` Feishu App Secret: ${config.feishu?.appSecret ? "\u2705 Set" : "\u274C Not set"}`);
4939
+ console.log(
4940
+ ` Feishu App ID: ${config.feishu?.appId ? "\u2705 Set" : "\u274C Not set"}`
4941
+ );
4942
+ console.log(
4943
+ ` Feishu App Secret: ${config.feishu?.appSecret ? "\u2705 Set" : "\u274C Not set"}`
4944
+ );
4273
4945
  } catch (error) {
4274
4946
  console.error("\u274C Error checking status:", error);
4275
4947
  }
4276
4948
  });
4277
4949
  program.command("shell-init").description("Output shell initialization script").action(() => {
4278
- console.log(`
4279
- # Feishu Bridge Shell Integration
4280
- # Add this to your ~/.bashrc, ~/.zshrc, or equivalent
4950
+ const hookPath = path10.join(__dirname, "../../scripts/hook.sh");
4951
+ if (fs10.existsSync(hookPath)) {
4952
+ console.log(fs10.readFileSync(hookPath, "utf-8"));
4953
+ } else {
4954
+ console.log(`# Feishu Bridge Shell Hook
4955
+ # Auto-starts Feishu Bridge when IDE/CLI is detected
4956
+ # Add this to ~/.bashrc or ~/.zshrc
4281
4957
 
4282
- feishu-bridge-auto() {
4283
- # \u68C0\u6D4B\u5F53\u524D\u662F\u5426\u5728IDE/CLI\u73AF\u5883\u4E2D
4284
- if [ -n "$VSCODE_PID" ] || [ -n "$CURSOR_SHELL" ] || [ -n "$OPENCODE_SESSION" ]; then
4285
- # \u68C0\u67E5bridge\u662F\u5426\u5DF2\u8FD0\u884C
4286
- if ! pgrep -f "feishu-bridge" > /dev/null; then
4287
- echo "Auto-starting Feishu Bridge..."
4958
+ feishu-bridge-auto-start() {
4959
+ local ide=""
4960
+
4961
+ # \u68C0\u6D4BIDE\u73AF\u5883
4962
+ if [[ -n "$OPENCODE_SESSION" ]]; then
4963
+ ide="opencode"
4964
+ elif [[ -n "$CLAUDE_CODE_SESSION" ]]; then
4965
+ ide="claude-code"
4966
+ elif [[ -n "$VSCODE_PID" ]]; then
4967
+ ide="vscode"
4968
+ elif [[ -n "$CURSOR_PID" ]]; then
4969
+ ide="cursor"
4970
+ elif [[ -n "$TRAE_PID" ]]; then
4971
+ ide="trae"
4972
+ fi
4973
+
4974
+ # \u5982\u679C\u68C0\u6D4B\u5230IDE\uFF0C\u542F\u52A8\u670D\u52A1
4975
+ if [[ -n "$ide" ]]; then
4976
+ if ! pgrep -f "feishu-bridge" > /dev/null 2>&1; then
4288
4977
  feishu-bridge start --daemon 2>/dev/null &
4978
+ echo "Feishu Bridge started for $ide"
4289
4979
  fi
4290
4980
  fi
4291
4981
  }
4292
4982
 
4293
- # Bash
4294
- if [ -n "$BASH_VERSION" ]; then
4295
- PROMPT_COMMAND="feishu-bridge-auto; $PROMPT_COMMAND"
4983
+ # Bash: Run before each prompt
4984
+ if [[ -n "$BASH_VERSION" ]]; then
4985
+ PROMPT_COMMAND="feishu-bridge-auto-start; $PROMPT_COMMAND"
4296
4986
  fi
4297
4987
 
4298
- # Zsh
4299
- if [ -n "$ZSH_VERSION" ]; then
4300
- precmd_functions+=(feishu-bridge-auto)
4988
+ # Zsh: Run before each prompt
4989
+ if [[ -n "$ZSH_VERSION" ]]; then
4990
+ precmd_functions+=(feishu-bridge-auto-start)
4301
4991
  fi
4302
4992
  `);
4993
+ }
4303
4994
  });
4304
4995
  program.parse();
4305
4996
  if (!process.argv.slice(2).length) {