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