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 +862 -138
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +862 -138
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.d.mts +11 -10
- package/dist/index.d.ts +11 -10
- package/dist/index.js +808 -112
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +808 -112
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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: "
|
|
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
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
1066
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
2372
|
-
|
|
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
|
|
2517
|
+
this.logger.warn(`Socket\u8FDE\u63A5\u5931\u8D25: ${error}`);
|
|
2387
2518
|
}
|
|
2388
2519
|
}
|
|
2389
|
-
if (
|
|
2390
|
-
|
|
2520
|
+
if (this.checkCLIExists()) {
|
|
2521
|
+
this.logger.info("OpenCode CLI\u6A21\u5F0F\u5C31\u7EEA");
|
|
2522
|
+
return;
|
|
2391
2523
|
}
|
|
2392
|
-
|
|
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(
|
|
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 ?? "
|
|
3538
|
+
mode: bridgeConfig.behavior?.reverseChannelMode ?? "websocket",
|
|
3539
|
+
// 默认改为WebSocket
|
|
3133
3540
|
checkInterval: 1e3,
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
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
|
-
|
|
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.
|
|
3722
|
+
isHealthy = this.wsManager?.isActive() ?? false;
|
|
3156
3723
|
break;
|
|
3157
3724
|
case "http":
|
|
3158
|
-
this.
|
|
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.
|
|
3735
|
+
this.emit("healthCheck", { healthy: isHealthy, mode: this.config.mode });
|
|
3162
3736
|
}
|
|
3163
3737
|
/**
|
|
3164
3738
|
* 停止反向通信通道
|
|
3165
3739
|
*/
|
|
3166
|
-
stop() {
|
|
3167
|
-
|
|
3168
|
-
|
|
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
|
-
|
|
3183
|
-
|
|
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
|
|
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(
|
|
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 (
|
|
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(
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
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 {
|
|
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(
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
"
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
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
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
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
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
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 {
|
|
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 {
|
|
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(
|
|
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 {
|
|
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(
|
|
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(
|
|
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 {
|
|
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(
|
|
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(
|
|
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)
|
|
4104
|
-
|
|
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)
|
|
4109
|
-
|
|
4110
|
-
if (process.env.
|
|
4111
|
-
|
|
4112
|
-
if (process.env.
|
|
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(
|
|
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(
|
|
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(
|
|
4239
|
-
|
|
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
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
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
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
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) {
|