feishu-bridge 1.0.8 → 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,14 +762,20 @@ 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
780
  async start() {
774
781
  if (this.isRunning) {
@@ -776,6 +783,7 @@ var FeishuWebSocketHandler = class {
776
783
  return;
777
784
  }
778
785
  const config = ConfigManager.getInstance().getFeishuConfig();
786
+ this.logger.info(`WebSocket config - appId: ${config.appId ? "\u2705 Set" : "\u274C Empty"}, domain: ${config.domain}`);
779
787
  try {
780
788
  this.logger.info("Creating Feishu WebSocket client...");
781
789
  this.wsClient = new Lark.WSClient({
@@ -794,14 +802,18 @@ var FeishuWebSocketHandler = class {
794
802
  this.isRunning = true;
795
803
  this.logger.info("\u2705 Feishu WebSocket connected successfully!");
796
804
  } catch (error) {
805
+ console.error("\u274C WebSocket ERROR:", error);
797
806
  this.logger.error("\u274C Failed to start WebSocket handler:", error);
798
807
  throw error;
799
808
  }
800
809
  }
801
810
  registerEventHandlers() {
802
811
  if (!this.eventDispatcher) return;
812
+ this.logger.info("Registering event handlers...");
803
813
  this.eventDispatcher.register({
804
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));
805
817
  await this.handleMessageEvent(event);
806
818
  },
807
819
  "im.message.message_read_v1": (event) => {
@@ -816,8 +828,20 @@ var FeishuWebSocketHandler = class {
816
828
  });
817
829
  }
818
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
+ });
819
838
  const { message, sender } = event;
820
- 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") {
821
845
  this.logger.debug("Ignoring non-text message");
822
846
  return;
823
847
  }
@@ -904,20 +928,33 @@ var FeishuClient = class {
904
928
  constructor(config) {
905
929
  this.accessToken = null;
906
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
+ });
907
937
  this.appId = config.appId;
908
938
  this.appSecret = config.appSecret;
909
939
  this.domain = config.domain;
910
940
  this.baseUrl = this.domain === "feishu" ? "https://open.feishu.cn/open-apis" : "https://open.larksuite.com/open-apis";
911
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"}`);
912
944
  }
913
945
  /**
914
946
  * 获取访问令牌
915
947
  */
916
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);
917
952
  const now = Date.now();
918
953
  if (this.accessToken && now < this.tokenExpireTime) {
954
+ console.log("\u{1F50D} DEBUG - Using cached token");
919
955
  return this.accessToken;
920
956
  }
957
+ console.log("\u{1F50D} DEBUG - Fetching new token from:", `${this.baseUrl}/auth/v3/tenant_access_token/internal`);
921
958
  try {
922
959
  const response = await fetch(`${this.baseUrl}/auth/v3/tenant_access_token/internal`, {
923
960
  method: "POST",
@@ -941,6 +978,9 @@ var FeishuClient = class {
941
978
  this.logger.info("Access token refreshed successfully");
942
979
  return this.accessToken;
943
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);
944
984
  this.logger.error("Error getting access token:", error);
945
985
  throw error;
946
986
  }
@@ -949,6 +989,16 @@ var FeishuClient = class {
949
989
  * 发送消息到指定聊天
950
990
  */
951
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
+ }
952
1002
  try {
953
1003
  const token = await this.getAccessToken();
954
1004
  const response = await fetch(`${this.baseUrl}/im/v1/messages?receive_id_type=chat_id`, {
@@ -1014,11 +1064,28 @@ var CommandExecutor = class {
1014
1064
  }
1015
1065
  async execute(request) {
1016
1066
  try {
1017
- 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
+ );
1018
1070
  const session = this.sessionManager.getSession(request.sender);
1019
1071
  let adapterName = request.target.toLowerCase();
1020
- if (adapterName === "auto") {
1021
- 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
+ }
1022
1089
  }
1023
1090
  const adapter = this.adapters.get(adapterName);
1024
1091
  if (!adapter) {
@@ -1038,7 +1105,10 @@ var CommandExecutor = class {
1038
1105
  result = await adapter.generateCode(description);
1039
1106
  } else if (this.isRunCommand(request.command)) {
1040
1107
  const command = this.extractRunCommand(request.command);
1041
- const executionResult = await adapter.runProgram(command, executionContext.workspaceRoot);
1108
+ const executionResult = await adapter.runProgram(
1109
+ command,
1110
+ executionContext.workspaceRoot
1111
+ );
1042
1112
  result = executionResult.stdout || executionResult.stderr;
1043
1113
  } else {
1044
1114
  result = await adapter.sendCommand(request.command, executionContext);
@@ -1062,25 +1132,64 @@ var CommandExecutor = class {
1062
1132
  }
1063
1133
  }
1064
1134
  detectBestAdapter() {
1065
- for (const [name, adapter] of this.adapters) {
1066
- 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) {
1067
1146
  return name;
1068
1147
  }
1069
1148
  }
1070
1149
  return "vscode";
1071
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
+ }
1072
1175
  isGenerateCommand(command) {
1073
- 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
+ );
1074
1179
  }
1075
1180
  isRunCommand(command) {
1076
1181
  return /\b(run|execute|start|launch|test|debug|build)\b/i.test(command);
1077
1182
  }
1078
1183
  extractDescription(command) {
1079
- 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
+ );
1080
1187
  return match ? match[2] : command;
1081
1188
  }
1082
1189
  extractRunCommand(command) {
1083
- 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
+ );
1084
1193
  return match ? match[2] : command;
1085
1194
  }
1086
1195
  };
@@ -2329,6 +2438,7 @@ var OpenCodeAdapter = class extends BaseIDEAdapter {
2329
2438
  this.ideType = "opencode";
2330
2439
  this.ideName = "OpenCode";
2331
2440
  this.useSocket = false;
2441
+ this.useHttp = false;
2332
2442
  this.messageId = 0;
2333
2443
  this.pendingMessages = /* @__PURE__ */ new Map();
2334
2444
  }
@@ -2362,19 +2472,40 @@ var OpenCodeAdapter = class extends BaseIDEAdapter {
2362
2472
  }
2363
2473
  getSocketPath() {
2364
2474
  const possiblePaths = [
2475
+ // Windows: temp directory
2365
2476
  path7.join(os7.tmpdir(), "opencode.sock"),
2366
2477
  path7.join(os7.tmpdir(), "opencode-bridge.sock"),
2478
+ // Unix socket locations
2367
2479
  path7.join(os7.homedir(), ".opencode", "socket"),
2368
- process.env.OPENCODE_SOCKET
2480
+ // Environment variable override
2481
+ process.env.OPENCODE_SOCKET,
2482
+ // Windows named pipe format
2483
+ "\\\\.\\pipe\\opencode"
2369
2484
  ].filter(Boolean);
2370
2485
  for (const socketPath of possiblePaths) {
2371
- if (fs7.existsSync(socketPath)) {
2372
- return socketPath;
2486
+ try {
2487
+ if (fs7.existsSync(socketPath)) {
2488
+ return socketPath;
2489
+ }
2490
+ } catch {
2491
+ if (socketPath.startsWith("\\\\.\\pipe\\")) {
2492
+ return socketPath;
2493
+ }
2373
2494
  }
2374
2495
  }
2375
2496
  return null;
2376
2497
  }
2377
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
+ }
2378
2509
  const socketPath = this.getSocketPath();
2379
2510
  if (socketPath) {
2380
2511
  try {
@@ -2383,13 +2514,16 @@ var OpenCodeAdapter = class extends BaseIDEAdapter {
2383
2514
  this.logger.info(`OpenCode Socket\u6A21\u5F0F\u5DF2\u8FDE\u63A5: ${socketPath}`);
2384
2515
  return;
2385
2516
  } catch (error) {
2386
- 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}`);
2387
2518
  }
2388
2519
  }
2389
- if (!this.checkCLIExists()) {
2390
- 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;
2391
2523
  }
2392
- 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
+ );
2393
2527
  }
2394
2528
  async deactivate() {
2395
2529
  if (this.socketClient) {
@@ -2409,6 +2543,30 @@ var OpenCodeAdapter = class extends BaseIDEAdapter {
2409
2543
  this.setupSocketListener();
2410
2544
  });
2411
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
+ }
2412
2570
  setupSocketListener() {
2413
2571
  if (!this.socketClient) return;
2414
2572
  let buffer = "";
@@ -2509,7 +2667,9 @@ var OpenCodeAdapter = class extends BaseIDEAdapter {
2509
2667
  }
2510
2668
  async getAIResponse(prompt, context) {
2511
2669
  try {
2512
- 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
+ );
2513
2673
  return await this.sendCommand(prompt, context);
2514
2674
  } catch (error) {
2515
2675
  this.logger.error(`Error getting OpenCode AI response:`, error);
@@ -2552,6 +2712,31 @@ var OpenCodeAdapter = class extends BaseIDEAdapter {
2552
2712
  const content = await this.getAIResponse(prompt);
2553
2713
  return { content, model: "claude-3-5-sonnet" };
2554
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
+ }
2555
2740
  };
2556
2741
 
2557
2742
  // src/adapters/claude-code.ts
@@ -3118,22 +3303,255 @@ init_logger();
3118
3303
  var fs9 = __toESM(require("fs"));
3119
3304
  var path9 = __toESM(require("path"));
3120
3305
  var os9 = __toESM(require("os"));
3306
+ var http = __toESM(require("http"));
3121
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
+ };
3122
3521
  var ReverseChannel = class extends import_events.EventEmitter {
3123
3522
  constructor(feishuClient) {
3124
3523
  super();
3125
3524
  this.isRunning = false;
3126
3525
  this.checkInterval = null;
3127
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;
3128
3534
  this.feishuClient = feishuClient;
3129
3535
  const bridgeConfig = ConfigManager.getInstance().get();
3130
3536
  this.config = {
3131
3537
  enabled: bridgeConfig.behavior?.reverseChannelEnabled ?? true,
3132
- mode: bridgeConfig.behavior?.reverseChannelMode ?? "filesystem",
3538
+ mode: bridgeConfig.behavior?.reverseChannelMode ?? "websocket",
3539
+ // 默认改为WebSocket
3133
3540
  checkInterval: 1e3,
3134
- // 1秒检查一次
3135
- maxMessageAge: 5 * 60 * 1e3
3136
- // 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
3137
3555
  };
3138
3556
  this.tempDir = path9.join(os9.tmpdir(), "feishu-bridge", "reverse-channel");
3139
3557
  this.ensureTempDir();
@@ -3141,46 +3559,223 @@ var ReverseChannel = class extends import_events.EventEmitter {
3141
3559
  /**
3142
3560
  * 启动反向通信通道
3143
3561
  */
3144
- start() {
3562
+ async start() {
3145
3563
  if (this.isRunning || !this.config.enabled) {
3146
3564
  return;
3147
3565
  }
3148
3566
  this.isRunning = true;
3149
- 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() {
3150
3586
  switch (this.config.mode) {
3587
+ case "websocket":
3588
+ await this.startWebSocketServer();
3589
+ break;
3590
+ case "http":
3591
+ await this.startHttpServer();
3592
+ break;
3151
3593
  case "filesystem":
3152
3594
  this.startFileSystemListener();
3153
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) {
3154
3721
  case "websocket":
3155
- this.startWebSocketListener();
3722
+ isHealthy = this.wsManager?.isActive() ?? false;
3156
3723
  break;
3157
3724
  case "http":
3158
- this.startHttpListener();
3725
+ isHealthy = this.httpManager?.isActive() ?? false;
3159
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();
3160
3734
  }
3161
- this.startMessageQueueProcessor();
3735
+ this.emit("healthCheck", { healthy: isHealthy, mode: this.config.mode });
3162
3736
  }
3163
3737
  /**
3164
3738
  * 停止反向通信通道
3165
3739
  */
3166
- stop() {
3167
- if (!this.isRunning) {
3168
- return;
3169
- }
3740
+ async stop() {
3741
+ await this.stopInternal();
3742
+ this.emit("stopped");
3743
+ }
3744
+ /**
3745
+ * 内部停止方法
3746
+ */
3747
+ async stopInternal() {
3170
3748
  this.isRunning = false;
3171
3749
  logger.info("Stopping reverse communication channel...");
3750
+ if (this.healthCheckTimer) {
3751
+ clearInterval(this.healthCheckTimer);
3752
+ this.healthCheckTimer = null;
3753
+ }
3172
3754
  if (this.checkInterval) {
3173
3755
  clearInterval(this.checkInterval);
3174
3756
  this.checkInterval = null;
3175
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
+ }
3176
3766
  }
3177
3767
  /**
3178
3768
  * 发送消息到飞书(供适配器调用)
3179
3769
  */
3180
3770
  async sendToFeishu(message) {
3181
3771
  try {
3182
- this.messageQueue.push(message);
3183
- 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);
3184
3779
  return true;
3185
3780
  } catch (error) {
3186
3781
  logger.error("Error queuing message to Feishu:", error);
@@ -3190,9 +3785,9 @@ var ReverseChannel = class extends import_events.EventEmitter {
3190
3785
  /**
3191
3786
  * 发送文件系统消息(供IDE写入文件后调用)
3192
3787
  */
3193
- async sendFileSystemMessage(adapterType, content, chatId) {
3788
+ async sendFileSystemMessage(adapterType, content, chatId, type = "message") {
3194
3789
  const message = {
3195
- type: "message",
3790
+ type,
3196
3791
  content,
3197
3792
  chatId,
3198
3793
  timestamp: Date.now()
@@ -3232,7 +3827,10 @@ var ReverseChannel = class extends import_events.EventEmitter {
3232
3827
  fs9.unlinkSync(filePath);
3233
3828
  continue;
3234
3829
  }
3235
- this.messageQueue.push(message);
3830
+ this.messageQueue.push({
3831
+ ...message,
3832
+ id: `fs_${file}`
3833
+ });
3236
3834
  fs9.unlinkSync(filePath);
3237
3835
  logger.debug(`Processed file system message: ${file}`);
3238
3836
  } catch (error) {
@@ -3247,14 +3845,6 @@ var ReverseChannel = class extends import_events.EventEmitter {
3247
3845
  logger.error("Error reading message directory:", error);
3248
3846
  }
3249
3847
  }
3250
- startWebSocketListener() {
3251
- logger.info("WebSocket reverse channel not yet implemented, falling back to file system");
3252
- this.startFileSystemListener();
3253
- }
3254
- startHttpListener() {
3255
- logger.info("HTTP reverse channel not yet implemented, falling back to file system");
3256
- this.startFileSystemListener();
3257
- }
3258
3848
  startMessageQueueProcessor() {
3259
3849
  setInterval(async () => {
3260
3850
  await this.processMessageQueue();
@@ -3272,19 +3862,25 @@ var ReverseChannel = class extends import_events.EventEmitter {
3272
3862
  this.emit("messageDelivered", message);
3273
3863
  } catch (error) {
3274
3864
  logger.error("Error delivering message to Feishu:", error);
3275
- if (!message.retryCount || message.retryCount < 3) {
3865
+ if ((message.retryCount || 0) < 3) {
3276
3866
  message.retryCount = (message.retryCount || 0) + 1;
3277
3867
  this.messageQueue.push(message);
3868
+ } else {
3869
+ this.emit("messageFailed", message, error);
3278
3870
  }
3279
3871
  }
3280
3872
  }
3281
3873
  }
3282
3874
  async deliverMessageToFeishu(message) {
3283
- const { type, content, chatId, metadata } = message;
3875
+ const { type, content, chatId, metadata, media } = message;
3284
3876
  if (!chatId) {
3285
3877
  logger.warn("No chatId specified for message, cannot deliver");
3286
3878
  return;
3287
3879
  }
3880
+ if (media) {
3881
+ await this.deliverMediaMessage(chatId, media, content);
3882
+ return;
3883
+ }
3288
3884
  let formattedContent = content;
3289
3885
  switch (type) {
3290
3886
  case "notification":
@@ -3296,6 +3892,9 @@ var ReverseChannel = class extends import_events.EventEmitter {
3296
3892
  case "result":
3297
3893
  formattedContent = `\u2705 ${content}`;
3298
3894
  break;
3895
+ case "progress":
3896
+ formattedContent = `\u23F3 ${content}`;
3897
+ break;
3299
3898
  default:
3300
3899
  formattedContent = content;
3301
3900
  }
@@ -3311,12 +3910,41 @@ var ReverseChannel = class extends import_events.EventEmitter {
3311
3910
  });
3312
3911
  logger.info(`Message delivered to Feishu chat ${chatId}`);
3313
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
3314
3927
  isActive() {
3315
3928
  return this.isRunning;
3316
3929
  }
3317
3930
  getQueueSize() {
3318
3931
  return this.messageQueue.length;
3319
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
+ }
3320
3948
  };
3321
3949
 
3322
3950
  // src/skills/builtin/feishu.ts
@@ -3335,14 +3963,17 @@ var FeishuSkills = class {
3335
3963
  return this.accessToken;
3336
3964
  }
3337
3965
  const config = ConfigManager.getInstance().getFeishuConfig();
3338
- const response = await fetch(`${this.baseUrl}/auth/v3/tenant_access_token/internal`, {
3339
- method: "POST",
3340
- headers: { "Content-Type": "application/json" },
3341
- body: JSON.stringify({
3342
- app_id: config.appId,
3343
- app_secret: config.appSecret
3344
- })
3345
- });
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
+ );
3346
3977
  const data = await response.json();
3347
3978
  if (data.code !== 0) {
3348
3979
  throw new Error(`Failed to get access token: ${data.msg}`);
@@ -3357,10 +3988,7 @@ var FeishuSkills = class {
3357
3988
  description: "\u53D1\u9001\u6D88\u606F\u5230\u6307\u5B9A\u4F1A\u8BDD",
3358
3989
  trigger: /发送消息|发信息|发消息/,
3359
3990
  riskLevel: "low",
3360
- examples: [
3361
- "\u53D1\u9001\u6D88\u606F\u7ED9\u5F20\u4E09\uFF1A\u4E0B\u5348\u5F00\u4F1A",
3362
- "\u53D1\u4FE1\u606F\u5230\u6D4B\u8BD5\u7FA4\uFF1A\u9879\u76EE\u5DF2\u90E8\u7F72"
3363
- ],
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"],
3364
3992
  execute: async (context) => {
3365
3993
  try {
3366
3994
  const { content } = this.parseMessageArgs(context.message);
@@ -3370,7 +3998,10 @@ var FeishuSkills = class {
3370
3998
  });
3371
3999
  return { success: true, content: "\u2705 \u6D88\u606F\u5DF2\u53D1\u9001" };
3372
4000
  } catch (error) {
3373
- 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
+ };
3374
4005
  }
3375
4006
  }
3376
4007
  };
@@ -3389,46 +4020,57 @@ var FeishuSkills = class {
3389
4020
  try {
3390
4021
  const { title, startTime, endTime, description } = this.parseEventArgs(context.message);
3391
4022
  const token = await this.getAccessToken();
3392
- const response = await fetch(`${this.baseUrl}/calendar/v4/calendars/primary/events`, {
3393
- method: "POST",
3394
- headers: {
3395
- "Authorization": `Bearer ${token}`,
3396
- "Content-Type": "application/json"
3397
- },
3398
- body: JSON.stringify({
3399
- summary: title,
3400
- description,
3401
- start_time: {
3402
- timestamp: Math.floor(startTime / 1e3).toString(),
3403
- 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"
3404
4030
  },
3405
- end_time: {
3406
- timestamp: Math.floor(endTime / 1e3).toString(),
3407
- timezone: "Asia/Shanghai"
3408
- },
3409
- attendee_ability: "can_see_others",
3410
- free_busy_status: "busy"
3411
- })
3412
- });
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
+ );
3413
4047
  const data = await response.json();
3414
4048
  if (data.code !== 0) {
3415
4049
  throw new Error(data.msg || "\u521B\u5EFA\u65E5\u7A0B\u5931\u8D25");
3416
4050
  }
3417
4051
  const eventLink = data.data?.event_link || "";
4052
+ const linkText = eventLink ? `
4053
+
4054
+ \u{1F517} ${eventLink}` : "";
3418
4055
  return {
3419
4056
  success: true,
3420
4057
  content: `\u2705 \u65E5\u7A0B\u5DF2\u521B\u5EFA
3421
4058
 
3422
4059
  \u{1F4C5} ${title}
3423
- \u{1F550} ${this.formatDateTime(startTime)} - ${this.formatDateTime(endTime)}
3424
-
3425
- \u{1F517} ${eventLink}`,
3426
- actions: [
3427
- { label: "\u67E5\u770B\u65E5\u7A0B", action: "view_calendar", url: eventLink }
3428
- ]
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
+ ] : []
3429
4068
  };
3430
4069
  } catch (error) {
3431
- 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
+ };
3432
4074
  }
3433
4075
  }
3434
4076
  };
@@ -3439,10 +4081,7 @@ var FeishuSkills = class {
3439
4081
  description: "\u521B\u5EFA\u98DE\u4E66\u4E91\u6587\u6863",
3440
4082
  trigger: /创建文档|新建文档|生成文档/,
3441
4083
  riskLevel: "low",
3442
- examples: [
3443
- "\u521B\u5EFA\u6587\u6863\uFF1A\u9879\u76EE\u9700\u6C42\u6587\u6863",
3444
- "\u65B0\u5EFA\u6587\u6863\uFF1A\u4F1A\u8BAE\u7EAA\u8981"
3445
- ],
4084
+ examples: ["\u521B\u5EFA\u6587\u6863\uFF1A\u9879\u76EE\u9700\u6C42\u6587\u6863", "\u65B0\u5EFA\u6587\u6863\uFF1A\u4F1A\u8BAE\u7EAA\u8981"],
3446
4085
  execute: async (context) => {
3447
4086
  try {
3448
4087
  const { title } = this.parseDocArgs(context.message);
@@ -3450,7 +4089,7 @@ var FeishuSkills = class {
3450
4089
  const response = await fetch(`${this.baseUrl}/docx/v1/documents`, {
3451
4090
  method: "POST",
3452
4091
  headers: {
3453
- "Authorization": `Bearer ${token}`,
4092
+ Authorization: `Bearer ${token}`,
3454
4093
  "Content-Type": "application/json"
3455
4094
  },
3456
4095
  body: JSON.stringify({ title })
@@ -3459,22 +4098,30 @@ var FeishuSkills = class {
3459
4098
  if (data.code !== 0) {
3460
4099
  throw new Error(data.msg || "\u521B\u5EFA\u6587\u6863\u5931\u8D25");
3461
4100
  }
3462
- const documentId = data.data?.document?.document_id;
4101
+ const documentId = data.data?.document?.document_id || "";
3463
4102
  const domain = ConfigManager.getInstance().getFeishuConfig().domain === "lark" ? "larksuite.com" : "feishu.cn";
3464
- const documentUrl = `https://${domain}/docx/${documentId}`;
4103
+ const documentUrl = documentId ? `https://${domain}/docx/${documentId}` : "";
4104
+ const urlText = documentUrl ? `
4105
+
4106
+ \u{1F517} ${documentUrl}` : "";
3465
4107
  return {
3466
4108
  success: true,
3467
4109
  content: `\u2705 \u6587\u6863\u5DF2\u521B\u5EFA
3468
4110
 
3469
- \u{1F4C4} ${title}
3470
-
3471
- \u{1F517} ${documentUrl}`,
3472
- actions: [
3473
- { label: "\u6253\u5F00\u6587\u6863", action: "open_doc", url: documentUrl }
3474
- ]
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
+ ] : []
3475
4119
  };
3476
4120
  } catch (error) {
3477
- 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
+ };
3478
4125
  }
3479
4126
  }
3480
4127
  };
@@ -3486,7 +4133,10 @@ var FeishuSkills = class {
3486
4133
  trigger: /上传文件|发送文件|传文件/,
3487
4134
  riskLevel: "low",
3488
4135
  execute: async (context) => {
3489
- 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
+ };
3490
4140
  }
3491
4141
  };
3492
4142
  }
@@ -3499,7 +4149,9 @@ var FeishuSkills = class {
3499
4149
  requireConfirm: true,
3500
4150
  execute: async (context) => {
3501
4151
  try {
3502
- const { title, content } = this.parseAnnouncementArgs(context.message);
4152
+ const { title, content } = this.parseAnnouncementArgs(
4153
+ context.message
4154
+ );
3503
4155
  await this.client.sendMessage(context.chatId, {
3504
4156
  msg_type: "text",
3505
4157
  content: { text: `\u{1F4E2} **${title}**
@@ -3508,7 +4160,10 @@ ${content}` }
3508
4160
  });
3509
4161
  return { success: true, content: "\u2705 \u516C\u544A\u5DF2\u53D1\u9001" };
3510
4162
  } catch (error) {
3511
- 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
+ };
3512
4167
  }
3513
4168
  }
3514
4169
  };
@@ -3520,9 +4175,13 @@ ${content}` }
3520
4175
  }
3521
4176
  parseEventArgs(message) {
3522
4177
  const now = /* @__PURE__ */ new Date();
3523
- const titleMatch = message.match(/日程[::]\s*(.+?)(?=(明天|今天|后天|时间|地点|$))/i);
4178
+ const titleMatch = message.match(
4179
+ /日程[::]\s*(.+?)(?=(明天|今天|后天|时间|地点|$))/i
4180
+ );
3524
4181
  const title = titleMatch ? titleMatch[1].trim() : "\u65B0\u65E5\u7A0B";
3525
- 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
+ );
3526
4185
  let startTime = new Date(now.getTime() + 60 * 60 * 1e3);
3527
4186
  if (timeMatch) {
3528
4187
  const day = timeMatch[1];
@@ -3536,7 +4195,12 @@ ${content}` }
3536
4195
  startTime.setHours(hour, minute, 0, 0);
3537
4196
  }
3538
4197
  const endTime = new Date(startTime.getTime() + 60 * 60 * 1e3);
3539
- return { title, startTime: startTime.getTime(), endTime: endTime.getTime(), description: "" };
4198
+ return {
4199
+ title,
4200
+ startTime: startTime.getTime(),
4201
+ endTime: endTime.getTime(),
4202
+ description: ""
4203
+ };
3540
4204
  }
3541
4205
  parseDocArgs(message) {
3542
4206
  const match = message.match(/文档[::]\s*(.+?)(?=(内容|文件夹|$))/i);
@@ -3953,16 +4617,28 @@ function initializeSkills(registry, feishuClient, getAdapter) {
3953
4617
  var import_fastify = __toESM(require("fastify"));
3954
4618
  var FeishuBridge = class {
3955
4619
  constructor() {
4620
+ this.feishuClient = null;
4621
+ this.webhookHandler = null;
3956
4622
  this.wsHandler = null;
4623
+ this.commandExecutor = null;
4624
+ this.sessionManager = null;
3957
4625
  this.healthMonitor = null;
3958
4626
  this.reverseChannel = null;
3959
4627
  this.adapters = /* @__PURE__ */ new Map();
4628
+ this.skillRegistry = null;
3960
4629
  this.server = (0, import_fastify.default)({
3961
4630
  logger: false
3962
4631
  // 使用我们自己的日志系统
3963
4632
  });
4633
+ }
4634
+ /**
4635
+ * 初始化桥接器(在配置加载完成后调用)
4636
+ */
4637
+ initialize() {
3964
4638
  const config = ConfigManager.getInstance().get();
4639
+ logger.info(`Initializing FeishuBridge with appId: ${config.feishu.appId}`);
3965
4640
  this.feishuClient = new FeishuClient(config.feishu);
4641
+ logger.info("FeishuClient created successfully");
3966
4642
  this.sessionManager = new SessionManager(config.behavior.sessionTimeout);
3967
4643
  this.reverseChannel = new ReverseChannel(this.feishuClient);
3968
4644
  this.initAdapters();
@@ -4025,11 +4701,23 @@ var FeishuBridge = class {
4025
4701
  }
4026
4702
  }
4027
4703
  async start() {
4704
+ this.initialize();
4028
4705
  const config = ConfigManager.getInstance().get();
4029
4706
  const serverConfig = config.server;
4030
4707
  if (this.reverseChannel) {
4031
- this.reverseChannel.start();
4032
- 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
+ }
4033
4721
  }
4034
4722
  if (config.feishu.connectionMode !== "websocket") {
4035
4723
  this.healthMonitor = new HealthMonitor(
@@ -4069,7 +4757,7 @@ var FeishuBridge = class {
4069
4757
  }
4070
4758
  async stop() {
4071
4759
  if (this.reverseChannel) {
4072
- this.reverseChannel.stop();
4760
+ await this.reverseChannel.stop();
4073
4761
  logger.info("Reverse communication channel stopped");
4074
4762
  }
4075
4763
  if (this.wsHandler) {
@@ -4091,7 +4779,9 @@ var FeishuBridge = class {
4091
4779
  init_config();
4092
4780
  init_logger();
4093
4781
  var program = new import_commander.Command();
4094
- 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
+ );
4095
4785
  program.name("feishu-bridge").description("Feishu Bridge - AI IDE/CLI integration for Feishu").version(pkg.version);
4096
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) => {
4097
4787
  try {
@@ -4100,22 +4790,33 @@ program.command("start").description("Start the Feishu Bridge server").option("-
4100
4790
  const config = { ...DEFAULT_CONFIG };
4101
4791
  if (fileConfig.appId) config.feishu.appId = fileConfig.appId;
4102
4792
  if (fileConfig.appSecret) config.feishu.appSecret = fileConfig.appSecret;
4103
- if (fileConfig.encryptKey) config.feishu.encryptKey = fileConfig.encryptKey;
4104
- 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;
4105
4797
  if (fileConfig.domain) config.feishu.domain = fileConfig.domain;
4106
4798
  config.server.port = parseInt(options.port);
4107
4799
  config.server.host = options.host;
4108
- if (process.env.FEISHU_APP_ID) config.feishu.appId = process.env.FEISHU_APP_ID;
4109
- if (process.env.FEISHU_APP_SECRET) config.feishu.appSecret = process.env.FEISHU_APP_SECRET;
4110
- if (process.env.FEISHU_ENCRYPT_KEY) config.feishu.encryptKey = process.env.FEISHU_ENCRYPT_KEY;
4111
- if (process.env.FEISHU_VERIFICATION_TOKEN) config.feishu.verificationToken = process.env.FEISHU_VERIFICATION_TOKEN;
4112
- 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);
4113
4810
  if (process.env.SERVER_HOST) config.server.host = process.env.SERVER_HOST;
4114
4811
  if (!config.feishu.appId || !config.feishu.appSecret) {
4115
- 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
+ );
4116
4815
  console.error("Set them via:");
4117
4816
  console.error(" 1. Environment variables: export FEISHU_APP_ID=xxx");
4118
- 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
+ );
4119
4820
  process.exit(1);
4120
4821
  }
4121
4822
  configManager.load(config);
@@ -4235,38 +4936,61 @@ program.command("status").description("Show service status").action(async () =>
4235
4936
  const config = listConfig();
4236
4937
  console.log("\n\u{1F4C1} Configuration:");
4237
4938
  console.log(` Config file: ${getConfigFilePath()}`);
4238
- console.log(` Feishu App ID: ${config.feishu?.appId ? "\u2705 Set" : "\u274C Not set"}`);
4239
- 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
+ );
4240
4945
  } catch (error) {
4241
4946
  console.error("\u274C Error checking status:", error);
4242
4947
  }
4243
4948
  });
4244
4949
  program.command("shell-init").description("Output shell initialization script").action(() => {
4245
- console.log(`
4246
- # Feishu Bridge Shell Integration
4247
- # 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
4248
4957
 
4249
- feishu-bridge-auto() {
4250
- # \u68C0\u6D4B\u5F53\u524D\u662F\u5426\u5728IDE/CLI\u73AF\u5883\u4E2D
4251
- if [ -n "$VSCODE_PID" ] || [ -n "$CURSOR_SHELL" ] || [ -n "$OPENCODE_SESSION" ]; then
4252
- # \u68C0\u67E5bridge\u662F\u5426\u5DF2\u8FD0\u884C
4253
- if ! pgrep -f "feishu-bridge" > /dev/null; then
4254
- 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
4255
4977
  feishu-bridge start --daemon 2>/dev/null &
4978
+ echo "Feishu Bridge started for $ide"
4256
4979
  fi
4257
4980
  fi
4258
4981
  }
4259
4982
 
4260
- # Bash
4261
- if [ -n "$BASH_VERSION" ]; then
4262
- 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"
4263
4986
  fi
4264
4987
 
4265
- # Zsh
4266
- if [ -n "$ZSH_VERSION" ]; then
4267
- precmd_functions+=(feishu-bridge-auto)
4988
+ # Zsh: Run before each prompt
4989
+ if [[ -n "$ZSH_VERSION" ]]; then
4990
+ precmd_functions+=(feishu-bridge-auto-start)
4268
4991
  fi
4269
4992
  `);
4993
+ }
4270
4994
  });
4271
4995
  program.parse();
4272
4996
  if (!process.argv.slice(2).length) {