feishu-bridge 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,2832 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ ConfigManager: () => ConfigManager,
34
+ FeishuBridge: () => FeishuBridge,
35
+ logger: () => logger
36
+ });
37
+ module.exports = __toCommonJS(index_exports);
38
+
39
+ // src/feishu/webhook.ts
40
+ var crypto = __toESM(require("crypto"));
41
+
42
+ // src/core/logger.ts
43
+ var import_pino = __toESM(require("pino"));
44
+ var Logger = class _Logger {
45
+ constructor() {
46
+ const isTest = process.env.NODE_ENV === "test" || process.env.VITEST;
47
+ this.logger = (0, import_pino.default)({
48
+ level: process.env.LOG_LEVEL || "info",
49
+ transport: isTest ? void 0 : {
50
+ target: "pino-pretty",
51
+ options: {
52
+ colorize: true,
53
+ translateTime: "SYS:standard",
54
+ ignore: "pid,hostname"
55
+ }
56
+ }
57
+ });
58
+ }
59
+ setLevel(level) {
60
+ this.logger.level = level;
61
+ }
62
+ debug(msg, ...args) {
63
+ this.logger.debug(msg, ...args);
64
+ }
65
+ info(msg, ...args) {
66
+ this.logger.info(msg, ...args);
67
+ }
68
+ warn(msg, ...args) {
69
+ this.logger.warn(msg, ...args);
70
+ }
71
+ error(msg, ...args) {
72
+ this.logger.error(msg, ...args);
73
+ }
74
+ child(bindings) {
75
+ const childLogger = new _Logger();
76
+ childLogger.logger = this.logger.child(bindings);
77
+ return childLogger;
78
+ }
79
+ };
80
+ var logger = new Logger();
81
+
82
+ // src/core/config.ts
83
+ var DEFAULT_CONFIG = {
84
+ feishu: {
85
+ appId: "",
86
+ appSecret: "",
87
+ domain: "feishu",
88
+ connectionMode: "webhook",
89
+ encryptKey: void 0,
90
+ verificationToken: void 0
91
+ },
92
+ server: {
93
+ port: 3e3,
94
+ host: "0.0.0.0",
95
+ webhookPath: "/webhook/feishu"
96
+ },
97
+ adapters: {
98
+ vscode: { enabled: true },
99
+ cursor: { enabled: true },
100
+ trae: { enabled: true },
101
+ antigravity: { enabled: true },
102
+ kiro: { enabled: true },
103
+ opencode: { enabled: true },
104
+ "claude-code": { enabled: true }
105
+ },
106
+ behavior: {
107
+ autoStart: true,
108
+ maxOutputLength: 2e3,
109
+ sessionTimeout: 30,
110
+ reverseChannelEnabled: true,
111
+ reverseChannelMode: "filesystem"
112
+ },
113
+ logging: {
114
+ level: "info",
115
+ file: void 0
116
+ }
117
+ };
118
+ var ConfigManager = class _ConfigManager {
119
+ constructor() {
120
+ this.config = DEFAULT_CONFIG;
121
+ }
122
+ static getInstance() {
123
+ if (!_ConfigManager.instance) {
124
+ _ConfigManager.instance = new _ConfigManager();
125
+ }
126
+ return _ConfigManager.instance;
127
+ }
128
+ load(config) {
129
+ this.config = this.deepMerge(this.config, config);
130
+ }
131
+ deepMerge(target, source) {
132
+ const output = { ...target };
133
+ if (this.isObject(target) && this.isObject(source)) {
134
+ Object.keys(source).forEach((key) => {
135
+ if (this.isObject(source[key])) {
136
+ if (!(key in target)) {
137
+ Object.assign(output, { [key]: source[key] });
138
+ } else {
139
+ output[key] = this.deepMerge(target[key], source[key]);
140
+ }
141
+ } else {
142
+ Object.assign(output, { [key]: source[key] });
143
+ }
144
+ });
145
+ }
146
+ return output;
147
+ }
148
+ isObject(item) {
149
+ return item && typeof item === "object" && !Array.isArray(item);
150
+ }
151
+ get() {
152
+ return this.config;
153
+ }
154
+ getFeishuConfig() {
155
+ return this.config.feishu;
156
+ }
157
+ getServerConfig() {
158
+ return this.config.server;
159
+ }
160
+ getAdapterConfig(adapter) {
161
+ return this.config.adapters[adapter];
162
+ }
163
+ getBehaviorConfig() {
164
+ return this.config.behavior;
165
+ }
166
+ getLoggingConfig() {
167
+ return this.config.logging;
168
+ }
169
+ };
170
+
171
+ // src/feishu/webhook.ts
172
+ var FeishuWebhookHandler = class {
173
+ constructor(client, commandExecutor) {
174
+ this.client = client;
175
+ this.commandExecutor = commandExecutor;
176
+ this.logger = new Logger();
177
+ }
178
+ async handleWebhook(request, reply) {
179
+ try {
180
+ const signature = request.headers["x-lark-signature"];
181
+ const timestamp = request.headers["x-lark-timestamp"];
182
+ const body = request.body;
183
+ if (!this.verifySignature(signature, timestamp, body)) {
184
+ this.logger.warn("Invalid signature received");
185
+ reply.status(401).send({ error: "Invalid signature" });
186
+ return;
187
+ }
188
+ const event = JSON.parse(body);
189
+ if (event.type === "url_verification") {
190
+ this.logger.info("Received URL verification challenge");
191
+ reply.send({ challenge: event.challenge });
192
+ return;
193
+ }
194
+ if (event.type === "event_callback" && event.event) {
195
+ await this.handleEvent(event.event);
196
+ }
197
+ reply.send({ success: true });
198
+ } catch (error) {
199
+ this.logger.error("Error handling webhook:", error);
200
+ reply.status(500).send({ error: "Internal server error" });
201
+ }
202
+ }
203
+ verifySignature(signature, timestamp, body) {
204
+ const config = ConfigManager.getInstance().getFeishuConfig();
205
+ const encryptKey = config.encryptKey;
206
+ if (!encryptKey) {
207
+ this.logger.warn("No encrypt key configured, skipping signature verification (development mode)");
208
+ return true;
209
+ }
210
+ const now = Math.floor(Date.now() / 1e3);
211
+ const requestTime = parseInt(timestamp, 10);
212
+ const timeDiff = Math.abs(now - requestTime);
213
+ if (timeDiff > 300) {
214
+ this.logger.warn(`Request timestamp too old: ${timeDiff}s difference`);
215
+ return false;
216
+ }
217
+ try {
218
+ const signatureContent = `${timestamp}
219
+ ${body}`;
220
+ const computedSignature = crypto.createHmac("sha256", encryptKey).update(signatureContent).digest("base64");
221
+ const expectedSig = Buffer.from(computedSignature);
222
+ const receivedSig = Buffer.from(signature);
223
+ if (expectedSig.length !== receivedSig.length) {
224
+ return false;
225
+ }
226
+ return crypto.timingSafeEqual(expectedSig, receivedSig);
227
+ } catch (error) {
228
+ this.logger.error("Error verifying signature:", error);
229
+ return false;
230
+ }
231
+ }
232
+ async handleEvent(event) {
233
+ const { message } = event;
234
+ if (message.message_type !== "text") {
235
+ this.logger.info("Ignoring non-text message");
236
+ return;
237
+ }
238
+ const content = JSON.parse(message.content);
239
+ const text = content.text.trim();
240
+ const sender = event.sender.sender_id.open_id;
241
+ this.logger.info(`Received message from ${sender}: ${text}`);
242
+ const target = this.extractTargetIDE(text);
243
+ const command = this.extractCommand(text);
244
+ const result = await this.commandExecutor.execute({
245
+ target,
246
+ command,
247
+ sender,
248
+ chatId: message.chat_id
249
+ });
250
+ const formattedResponse = this.formatResponse(result);
251
+ await this.client.sendMessage(message.chat_id, {
252
+ msg_type: "text",
253
+ content: {
254
+ text: formattedResponse
255
+ }
256
+ });
257
+ }
258
+ extractTargetIDE(text) {
259
+ const match = text.match(/@(vscode|cursor|trae|antigravity|kiro|opencode|claude)/i);
260
+ return match ? match[1].toLowerCase() : "auto";
261
+ }
262
+ extractCommand(text) {
263
+ const cleanedText = text.replace(/@(vscode|cursor|trae|antigravity|kiro|opencode|claude)\s*/i, "").trim();
264
+ return cleanedText;
265
+ }
266
+ formatResponse(result) {
267
+ if (result.success) {
268
+ const output = typeof result.output === "string" ? result.output.substring(0, 2e3) : JSON.stringify(result.output).substring(0, 2e3);
269
+ return `\u2705 \u6267\u884C\u6210\u529F
270
+
271
+ ${output}`;
272
+ } else {
273
+ const error = result.error || result.stderr || "Unknown error";
274
+ return `\u274C \u6267\u884C\u5931\u8D25
275
+
276
+ ${error.substring(0, 2e3)}`;
277
+ }
278
+ }
279
+ };
280
+
281
+ // src/feishu/websocket.ts
282
+ var import_ws = require("ws");
283
+ var FeishuWebSocketHandler = class {
284
+ constructor(client, commandExecutor) {
285
+ this.client = client;
286
+ this.commandExecutor = commandExecutor;
287
+ this.ws = null;
288
+ this.reconnectAttempts = 0;
289
+ this.maxReconnectAttempts = 5;
290
+ this.reconnectInterval = 5e3;
291
+ // 5秒
292
+ this.heartbeatInterval = null;
293
+ this.isRunning = false;
294
+ this.logger = new Logger();
295
+ }
296
+ /**
297
+ * 启动WebSocket连接
298
+ */
299
+ async start() {
300
+ if (this.isRunning) {
301
+ this.logger.warn("WebSocket handler already running");
302
+ return;
303
+ }
304
+ this.isRunning = true;
305
+ this.reconnectAttempts = 0;
306
+ await this.connect();
307
+ }
308
+ /**
309
+ * 停止WebSocket连接
310
+ */
311
+ async stop() {
312
+ this.isRunning = false;
313
+ if (this.heartbeatInterval) {
314
+ clearInterval(this.heartbeatInterval);
315
+ this.heartbeatInterval = null;
316
+ }
317
+ if (this.ws) {
318
+ this.ws.close();
319
+ this.ws = null;
320
+ }
321
+ this.logger.info("WebSocket handler stopped");
322
+ }
323
+ /**
324
+ * 建立WebSocket连接
325
+ */
326
+ async connect() {
327
+ try {
328
+ const config = ConfigManager.getInstance().getFeishuConfig();
329
+ const wsUrl = config.domain === "feishu" ? "wss://ws.feishu.cn/passport/" : "wss://ws.larksuite.com/passport/";
330
+ this.logger.info(`Connecting to WebSocket: ${wsUrl}`);
331
+ this.ws = new import_ws.WebSocket(wsUrl, {
332
+ headers: {
333
+ "Authorization": `Bearer ${await this.getAccessToken()}`
334
+ }
335
+ });
336
+ this.setupWebSocketHandlers();
337
+ } catch (error) {
338
+ this.logger.error("Failed to connect WebSocket:", error);
339
+ this.handleReconnect();
340
+ }
341
+ }
342
+ /**
343
+ * 设置WebSocket事件处理器
344
+ */
345
+ setupWebSocketHandlers() {
346
+ if (!this.ws) return;
347
+ this.ws.on("open", () => {
348
+ this.logger.info("WebSocket connected");
349
+ this.reconnectAttempts = 0;
350
+ this.startHeartbeat();
351
+ });
352
+ this.ws.on("message", (data) => {
353
+ this.handleMessage(data.toString());
354
+ });
355
+ this.ws.on("close", (code, reason) => {
356
+ this.logger.info(`WebSocket closed: ${code} - ${reason.toString()}`);
357
+ this.stopHeartbeat();
358
+ if (this.isRunning) {
359
+ this.handleReconnect();
360
+ }
361
+ });
362
+ this.ws.on("error", (error) => {
363
+ this.logger.error("WebSocket error:", error);
364
+ });
365
+ this.ws.on("ping", () => {
366
+ this.ws?.pong();
367
+ });
368
+ }
369
+ /**
370
+ * 处理收到的消息
371
+ */
372
+ async handleMessage(data) {
373
+ try {
374
+ const event = JSON.parse(data);
375
+ this.logger.debug("Received WebSocket message:", event);
376
+ if (event.type === "url_verification") {
377
+ this.logger.info("Received URL verification challenge via WebSocket");
378
+ return;
379
+ }
380
+ if (event.type === "event_callback" && event.event) {
381
+ await this.handleEvent(event.event);
382
+ }
383
+ } catch (error) {
384
+ this.logger.error("Error handling WebSocket message:", error);
385
+ }
386
+ }
387
+ /**
388
+ * 处理事件
389
+ */
390
+ async handleEvent(event) {
391
+ const { message } = event;
392
+ if (!message || message.message_type !== "text") {
393
+ this.logger.debug("Ignoring non-text message");
394
+ return;
395
+ }
396
+ try {
397
+ const content = JSON.parse(message.content);
398
+ const text = content.text.trim();
399
+ const sender = event.sender?.sender_id?.open_id;
400
+ if (!sender) {
401
+ this.logger.warn("Missing sender ID");
402
+ return;
403
+ }
404
+ this.logger.info(`Received message from ${sender}: ${text}`);
405
+ const target = this.extractTargetIDE(text);
406
+ const command = this.extractCommand(text);
407
+ const result = await this.commandExecutor.execute({
408
+ target,
409
+ command,
410
+ sender,
411
+ chatId: message.chat_id
412
+ });
413
+ await this.client.sendMessage(message.chat_id, {
414
+ msg_type: "text",
415
+ content: {
416
+ text: this.formatResponse(result)
417
+ }
418
+ });
419
+ } catch (error) {
420
+ this.logger.error("Error processing event:", error);
421
+ }
422
+ }
423
+ /**
424
+ * 启动心跳保活
425
+ */
426
+ startHeartbeat() {
427
+ this.heartbeatInterval = setInterval(() => {
428
+ if (this.ws?.readyState === import_ws.WebSocket.OPEN) {
429
+ this.ws.ping();
430
+ }
431
+ }, 3e4);
432
+ }
433
+ /**
434
+ * 停止心跳
435
+ */
436
+ stopHeartbeat() {
437
+ if (this.heartbeatInterval) {
438
+ clearInterval(this.heartbeatInterval);
439
+ this.heartbeatInterval = null;
440
+ }
441
+ }
442
+ /**
443
+ * 处理重连
444
+ */
445
+ handleReconnect() {
446
+ if (this.reconnectAttempts >= this.maxReconnectAttempts) {
447
+ this.logger.error(`Max reconnection attempts (${this.maxReconnectAttempts}) reached`);
448
+ return;
449
+ }
450
+ this.reconnectAttempts++;
451
+ const delay = this.reconnectInterval * this.reconnectAttempts;
452
+ this.logger.info(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
453
+ setTimeout(() => {
454
+ if (this.isRunning) {
455
+ this.connect();
456
+ }
457
+ }, delay);
458
+ }
459
+ /**
460
+ * 获取访问令牌
461
+ */
462
+ async getAccessToken() {
463
+ const config = ConfigManager.getInstance().getFeishuConfig();
464
+ const response = await fetch(
465
+ `https://open.${config.domain === "lark" ? "larksuite" : "feishu"}.cn/open-apis/auth/v3/tenant_access_token/internal`,
466
+ {
467
+ method: "POST",
468
+ headers: { "Content-Type": "application/json" },
469
+ body: JSON.stringify({
470
+ app_id: config.appId,
471
+ app_secret: config.appSecret
472
+ })
473
+ }
474
+ );
475
+ const data = await response.json();
476
+ if (data.code !== 0) {
477
+ throw new Error(`Failed to get access token: ${data.msg}`);
478
+ }
479
+ return data.tenant_access_token;
480
+ }
481
+ /**
482
+ * 提取目标IDE
483
+ */
484
+ extractTargetIDE(text) {
485
+ const match = text.match(/@(vscode|cursor|trae|antigravity|kiro|opencode|claude)/i);
486
+ return match ? match[1].toLowerCase() : "auto";
487
+ }
488
+ /**
489
+ * 提取命令
490
+ */
491
+ extractCommand(text) {
492
+ return text.replace(/@(vscode|cursor|trae|antigravity|kiro|opencode|claude)\s*/i, "").trim();
493
+ }
494
+ /**
495
+ * 格式化响应
496
+ */
497
+ formatResponse(result) {
498
+ if (result.success) {
499
+ const output = typeof result.output === "string" ? result.output.substring(0, 2e3) : JSON.stringify(result.output).substring(0, 2e3);
500
+ return `\u2705 \u6267\u884C\u6210\u529F
501
+
502
+ ${output}`;
503
+ } else {
504
+ const error = result.error || result.stderr || "Unknown error";
505
+ return `\u274C \u6267\u884C\u5931\u8D25
506
+
507
+ ${error.substring(0, 2e3)}`;
508
+ }
509
+ }
510
+ };
511
+
512
+ // src/feishu/client.ts
513
+ var FeishuClient = class {
514
+ constructor(config) {
515
+ this.accessToken = null;
516
+ this.tokenExpireTime = 0;
517
+ this.appId = config.appId;
518
+ this.appSecret = config.appSecret;
519
+ this.domain = config.domain;
520
+ this.baseUrl = this.domain === "feishu" ? "https://open.feishu.cn/open-apis" : "https://open.larksuite.com/open-apis";
521
+ this.logger = new Logger();
522
+ }
523
+ /**
524
+ * 获取访问令牌
525
+ */
526
+ async getAccessToken() {
527
+ const now = Date.now();
528
+ if (this.accessToken && now < this.tokenExpireTime) {
529
+ return this.accessToken;
530
+ }
531
+ try {
532
+ const response = await fetch(`${this.baseUrl}/auth/v3/tenant_access_token/internal`, {
533
+ method: "POST",
534
+ headers: {
535
+ "Content-Type": "application/json; charset=utf-8"
536
+ },
537
+ body: JSON.stringify({
538
+ app_id: this.appId,
539
+ app_secret: this.appSecret
540
+ })
541
+ });
542
+ if (!response.ok) {
543
+ throw new Error(`Failed to get access token: ${response.status} ${response.statusText}`);
544
+ }
545
+ const data = await response.json();
546
+ if (data.code !== 0) {
547
+ throw new Error(`Failed to get access token: ${data.msg}`);
548
+ }
549
+ this.accessToken = data.data.tenant_access_token;
550
+ this.tokenExpireTime = now + (data.data.expire - 300) * 1e3;
551
+ this.logger.info("Access token refreshed successfully");
552
+ return this.accessToken;
553
+ } catch (error) {
554
+ this.logger.error("Error getting access token:", error);
555
+ throw error;
556
+ }
557
+ }
558
+ /**
559
+ * 发送消息到指定聊天
560
+ */
561
+ async sendMessage(chatId, message) {
562
+ try {
563
+ const token = await this.getAccessToken();
564
+ const response = await fetch(`${this.baseUrl}/im/v1/messages?receive_id_type=chat_id`, {
565
+ method: "POST",
566
+ headers: {
567
+ "Authorization": `Bearer ${token}`,
568
+ "Content-Type": "application/json; charset=utf-8"
569
+ },
570
+ body: JSON.stringify({
571
+ receive_id: chatId,
572
+ ...message
573
+ })
574
+ });
575
+ if (!response.ok) {
576
+ throw new Error(`Failed to send message: ${response.status} ${response.statusText}`);
577
+ }
578
+ const data = await response.json();
579
+ if (data.code !== 0) {
580
+ throw new Error(`Failed to send message: ${data.msg}`);
581
+ }
582
+ this.logger.info(`Message sent successfully to chat ${chatId}`);
583
+ return data.data;
584
+ } catch (error) {
585
+ this.logger.error("Error sending message:", error);
586
+ throw error;
587
+ }
588
+ }
589
+ /**
590
+ * 获取用户信息
591
+ */
592
+ async getUserInfo(userId) {
593
+ try {
594
+ const token = await this.getAccessToken();
595
+ const response = await fetch(`${this.baseUrl}/contact/v3/users/${userId}`, {
596
+ method: "GET",
597
+ headers: {
598
+ "Authorization": `Bearer ${token}`,
599
+ "Content-Type": "application/json; charset=utf-8"
600
+ }
601
+ });
602
+ if (!response.ok) {
603
+ throw new Error(`Failed to get user info: ${response.status} ${response.statusText}`);
604
+ }
605
+ const data = await response.json();
606
+ if (data.code !== 0) {
607
+ throw new Error(`Failed to get user info: ${data.msg}`);
608
+ }
609
+ return data.data;
610
+ } catch (error) {
611
+ this.logger.error("Error getting user info:", error);
612
+ throw error;
613
+ }
614
+ }
615
+ };
616
+
617
+ // src/commands/executor.ts
618
+ var CommandExecutor = class {
619
+ constructor(adapters, sessionManager) {
620
+ this.adapters = adapters;
621
+ this.sessionManager = sessionManager;
622
+ this.logger = new Logger();
623
+ }
624
+ async execute(request) {
625
+ try {
626
+ this.logger.info(`Executing command for target: ${request.target}, command: ${request.command}`);
627
+ const session = this.sessionManager.getSession(request.sender);
628
+ let adapterName = request.target.toLowerCase();
629
+ if (adapterName === "auto") {
630
+ adapterName = this.detectBestAdapter();
631
+ }
632
+ const adapter = this.adapters.get(adapterName);
633
+ if (!adapter) {
634
+ throw new Error(`Adapter not found for target: ${adapterName}`);
635
+ }
636
+ if (!await adapter.checkAvailability()) {
637
+ throw new Error(`Adapter ${adapterName} is not available`);
638
+ }
639
+ const executionContext = {
640
+ workspaceRoot: session?.workspaceRoot,
641
+ chatId: request.chatId,
642
+ senderId: request.sender
643
+ };
644
+ let result;
645
+ if (this.isGenerateCommand(request.command)) {
646
+ const description = this.extractDescription(request.command);
647
+ result = await adapter.generateCode(description);
648
+ } else if (this.isRunCommand(request.command)) {
649
+ const command = this.extractRunCommand(request.command);
650
+ const executionResult = await adapter.runProgram(command, executionContext.workspaceRoot);
651
+ result = executionResult.stdout || executionResult.stderr;
652
+ } else {
653
+ result = await adapter.sendCommand(request.command, executionContext);
654
+ }
655
+ this.sessionManager.updateSession(request.sender, {
656
+ lastCommand: request.command,
657
+ lastResult: result,
658
+ lastUsedAdapter: adapterName
659
+ });
660
+ return {
661
+ success: true,
662
+ output: result
663
+ };
664
+ } catch (error) {
665
+ this.logger.error("Error executing command:", error);
666
+ return {
667
+ success: false,
668
+ output: "",
669
+ error: error instanceof Error ? error.message : String(error)
670
+ };
671
+ }
672
+ }
673
+ detectBestAdapter() {
674
+ for (const [name, adapter] of this.adapters) {
675
+ if (adapter.isAvailable) {
676
+ return name;
677
+ }
678
+ }
679
+ return "vscode";
680
+ }
681
+ isGenerateCommand(command) {
682
+ return /\b(generate|create|make|build|implement|write|develop|add|new)\b/i.test(command);
683
+ }
684
+ isRunCommand(command) {
685
+ return /\b(run|execute|start|launch|test|debug|build)\b/i.test(command);
686
+ }
687
+ extractDescription(command) {
688
+ const match = command.match(/\b(generate|create|make|build|implement|write|develop|add|new)\b\s+(.*)/i);
689
+ return match ? match[2] : command;
690
+ }
691
+ extractRunCommand(command) {
692
+ const match = command.match(/\b(run|execute|start|launch|test|debug|build)\b\s+(.*)/i);
693
+ return match ? match[2] : command;
694
+ }
695
+ };
696
+
697
+ // src/session/manager.ts
698
+ var SessionManager = class {
699
+ // 会话超时时间(毫秒)
700
+ constructor(sessionTimeoutMinutes = 30) {
701
+ this.sessions = /* @__PURE__ */ new Map();
702
+ this.logger = new Logger();
703
+ this.sessionTimeout = sessionTimeoutMinutes * 60 * 1e3;
704
+ setInterval(() => {
705
+ this.cleanupExpiredSessions();
706
+ }, this.sessionTimeout);
707
+ }
708
+ /**
709
+ * 获取会话,如果不存在则创建新会话
710
+ */
711
+ getSession(userId) {
712
+ const session = this.sessions.get(userId);
713
+ if (session) {
714
+ if (Date.now() - session.lastActive > this.sessionTimeout) {
715
+ this.logger.info(`Session for user ${userId} expired, removing`);
716
+ this.sessions.delete(userId);
717
+ return null;
718
+ }
719
+ session.lastActive = Date.now();
720
+ return session;
721
+ }
722
+ return null;
723
+ }
724
+ /**
725
+ * 创建新会话
726
+ */
727
+ createSession(userId, initialData) {
728
+ const now = Date.now();
729
+ const session = {
730
+ userId,
731
+ createdAt: now,
732
+ lastActive: now,
733
+ workspaceRoot: initialData?.workspaceRoot,
734
+ context: initialData?.context,
735
+ lastCommand: initialData?.lastCommand,
736
+ lastResult: initialData?.lastResult,
737
+ lastUsedAdapter: initialData?.lastUsedAdapter
738
+ };
739
+ this.sessions.set(userId, session);
740
+ this.logger.info(`Created new session for user ${userId}`);
741
+ return session;
742
+ }
743
+ /**
744
+ * 更新会话数据
745
+ */
746
+ updateSession(userId, update) {
747
+ let session = this.sessions.get(userId);
748
+ if (!session) {
749
+ session = this.createSession(userId);
750
+ }
751
+ session.lastActive = Date.now();
752
+ if (update.workspaceRoot !== void 0) session.workspaceRoot = update.workspaceRoot;
753
+ if (update.context !== void 0) session.context = { ...session.context, ...update.context };
754
+ if (update.lastCommand !== void 0) session.lastCommand = update.lastCommand;
755
+ if (update.lastResult !== void 0) session.lastResult = update.lastResult;
756
+ if (update.lastUsedAdapter !== void 0) session.lastUsedAdapter = update.lastUsedAdapter;
757
+ this.sessions.set(userId, session);
758
+ this.logger.debug(`Updated session for user ${userId}`);
759
+ return session;
760
+ }
761
+ /**
762
+ * 删除会话
763
+ */
764
+ removeSession(userId) {
765
+ const removed = this.sessions.delete(userId);
766
+ if (removed) {
767
+ this.logger.info(`Removed session for user ${userId}`);
768
+ }
769
+ return removed;
770
+ }
771
+ /**
772
+ * 清理会话数据
773
+ */
774
+ clearAllSessions() {
775
+ const count = this.sessions.size;
776
+ this.sessions.clear();
777
+ this.logger.info(`Cleared all ${count} sessions`);
778
+ }
779
+ /**
780
+ * 获取所有活跃会话数量
781
+ */
782
+ getActiveSessionCount() {
783
+ return this.sessions.size;
784
+ }
785
+ /**
786
+ * 清理过期会话
787
+ */
788
+ cleanupExpiredSessions() {
789
+ const now = Date.now();
790
+ let expiredCount = 0;
791
+ for (const [userId, session] of this.sessions) {
792
+ if (now - session.lastActive > this.sessionTimeout) {
793
+ this.sessions.delete(userId);
794
+ expiredCount++;
795
+ this.logger.info(`Cleaned up expired session for user ${userId}`);
796
+ }
797
+ }
798
+ if (expiredCount > 0) {
799
+ this.logger.info(`Cleaned up ${expiredCount} expired sessions`);
800
+ }
801
+ }
802
+ };
803
+
804
+ // src/adapters/base.ts
805
+ var BaseIDEAdapter = class {
806
+ constructor() {
807
+ this.reverseChannel = null;
808
+ this.logger = new Logger().child({ adapter: this.type });
809
+ }
810
+ get isAvailable() {
811
+ return this.checkAvailabilitySync();
812
+ }
813
+ /**
814
+ * 设置反向通信通道
815
+ * 允许适配器主动向飞书发送消息
816
+ */
817
+ setReverseChannel(channel) {
818
+ this.reverseChannel = channel;
819
+ this.logger.info(`Reverse channel set for ${this.type} adapter`);
820
+ }
821
+ /**
822
+ * 发送消息到飞书
823
+ * 供子类调用以主动推送消息
824
+ */
825
+ async sendToFeishu(content, context, type = "message") {
826
+ if (!this.reverseChannel) {
827
+ this.logger.warn("Reverse channel not available, cannot send message to Feishu");
828
+ return false;
829
+ }
830
+ if (!context?.chatId) {
831
+ this.logger.warn("No chatId in context, cannot send message to Feishu");
832
+ return false;
833
+ }
834
+ try {
835
+ await this.reverseChannel.sendToFeishu({
836
+ type,
837
+ content,
838
+ chatId: context.chatId,
839
+ senderId: context.senderId,
840
+ timestamp: Date.now()
841
+ });
842
+ return true;
843
+ } catch (error) {
844
+ this.logger.error("Error sending message to Feishu:", error);
845
+ return false;
846
+ }
847
+ }
848
+ /**
849
+ * 发送文件系统消息(无需反向通道实例)
850
+ * 通过写入文件的方式让Bridge代为发送
851
+ */
852
+ async sendFileSystemMessage(content, context, type = "message") {
853
+ if (!this.reverseChannel) {
854
+ this.logger.warn("Reverse channel not available for file system message");
855
+ return;
856
+ }
857
+ await this.reverseChannel.sendFileSystemMessage(
858
+ this.type,
859
+ content,
860
+ context?.chatId
861
+ );
862
+ }
863
+ buildCodePrompt(description, options) {
864
+ let prompt = description;
865
+ if (options) {
866
+ if (options.language) {
867
+ prompt += `
868
+ Language: ${options.language}`;
869
+ }
870
+ if (options.framework) {
871
+ prompt += `
872
+ Framework: ${options.framework}`;
873
+ }
874
+ if (options.style) {
875
+ prompt += `
876
+ Style: ${options.style}`;
877
+ }
878
+ }
879
+ return prompt;
880
+ }
881
+ };
882
+
883
+ // src/adapters/vscode.ts
884
+ var import_execa = require("execa");
885
+ var fs = __toESM(require("fs"));
886
+ var os = __toESM(require("os"));
887
+ var path = __toESM(require("path"));
888
+ var VSCodeAdapter = class extends BaseIDEAdapter {
889
+ constructor() {
890
+ super(...arguments);
891
+ this.type = "vscode";
892
+ this.vscodePath = null;
893
+ }
894
+ checkAvailabilitySync() {
895
+ if (process.env.VSCODE_PID || process.env.VSCODE_CWD) {
896
+ return true;
897
+ }
898
+ this.vscodePath = this.findVSCodeExecutable();
899
+ return !!this.vscodePath;
900
+ }
901
+ findVSCodeExecutable() {
902
+ const platform5 = os.platform();
903
+ const possiblePaths = [];
904
+ if (platform5 === "darwin") {
905
+ possiblePaths.push(
906
+ "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code",
907
+ "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code",
908
+ "/usr/local/bin/code",
909
+ path.join(os.homedir(), "Applications/Visual Studio Code.app/Contents/Resources/app/bin/code")
910
+ );
911
+ } else if (platform5 === "win32") {
912
+ possiblePaths.push(
913
+ path.join(process.env.LOCALAPPDATA || "", "Programs/Microsoft VS Code/bin/code.cmd"),
914
+ path.join(process.env.ProgramFiles || "", "Microsoft VS Code/bin/code.cmd"),
915
+ path.join(process.env.ProgramFiles || "", "Microsoft VS Code/bin/code.exe"),
916
+ "C:\\Program Files\\Microsoft VS Code\\bin\\code.cmd"
917
+ );
918
+ } else {
919
+ possiblePaths.push(
920
+ "/usr/bin/code",
921
+ "/usr/local/bin/code",
922
+ "/usr/share/code/bin/code",
923
+ path.join(os.homedir(), ".local/bin/code"),
924
+ "/snap/bin/code"
925
+ );
926
+ }
927
+ for (const vscodePath of possiblePaths) {
928
+ if (fs.existsSync(vscodePath)) {
929
+ return vscodePath;
930
+ }
931
+ }
932
+ try {
933
+ if (platform5 === "win32") {
934
+ const result = import_execa.execa.sync("where", ["code"], { reject: false });
935
+ if (result.stdout) {
936
+ const lines = result.stdout.split("\n").map((l) => l.trim()).filter((l) => l);
937
+ if (lines.length > 0) return lines[0];
938
+ }
939
+ } else {
940
+ const result = import_execa.execa.sync("which", ["code"], { reject: false });
941
+ if (result.stdout) {
942
+ return result.stdout.trim();
943
+ }
944
+ }
945
+ } catch {
946
+ }
947
+ return null;
948
+ }
949
+ async checkAvailability() {
950
+ return this.checkAvailabilitySync();
951
+ }
952
+ async sendCommand(command, context) {
953
+ try {
954
+ this.logger.info(`Sending command to VS Code: ${command}`);
955
+ if (this.vscodePath) {
956
+ try {
957
+ const result = await (0, import_execa.execa)(this.vscodePath, [
958
+ "--command",
959
+ "feishuBridge.execute",
960
+ "--args",
961
+ JSON.stringify({ command })
962
+ ], {
963
+ timeout: 3e4,
964
+ reject: false
965
+ });
966
+ if (!result.failed) {
967
+ return result.stdout || "Command executed successfully";
968
+ }
969
+ } catch (cliError) {
970
+ this.logger.debug("VS Code CLI failed, trying file system method");
971
+ }
972
+ }
973
+ return await this.sendViaFileSystem(command, context);
974
+ } catch (error) {
975
+ this.logger.error(`Error executing VS Code command:`, error);
976
+ throw new Error(`VS Code command failed: ${error instanceof Error ? error.message : String(error)}`);
977
+ }
978
+ }
979
+ async sendViaFileSystem(command, context) {
980
+ const tempDir = path.join(os.tmpdir(), "feishu-bridge", "vscode");
981
+ if (!fs.existsSync(tempDir)) {
982
+ fs.mkdirSync(tempDir, { recursive: true });
983
+ }
984
+ const instructionFile = path.join(tempDir, `cmd_${Date.now()}.json`);
985
+ const responseFile = path.join(tempDir, `resp_${Date.now()}.txt`);
986
+ const instruction = {
987
+ type: "command",
988
+ command,
989
+ context,
990
+ timestamp: Date.now(),
991
+ responseFile
992
+ };
993
+ fs.writeFileSync(instructionFile, JSON.stringify(instruction, null, 2));
994
+ const maxWait = 3e4;
995
+ const checkInterval = 500;
996
+ let waited = 0;
997
+ while (waited < maxWait) {
998
+ if (fs.existsSync(responseFile)) {
999
+ const response = fs.readFileSync(responseFile, "utf-8");
1000
+ try {
1001
+ fs.unlinkSync(instructionFile);
1002
+ fs.unlinkSync(responseFile);
1003
+ } catch {
1004
+ }
1005
+ return response;
1006
+ }
1007
+ await new Promise((resolve) => setTimeout(resolve, checkInterval));
1008
+ waited += checkInterval;
1009
+ }
1010
+ try {
1011
+ fs.unlinkSync(instructionFile);
1012
+ } catch {
1013
+ }
1014
+ return `Command queued for VS Code. File: ${instructionFile}`;
1015
+ }
1016
+ async getAIResponse(prompt, context) {
1017
+ try {
1018
+ this.logger.info(`Getting AI response from VS Code for prompt: ${prompt}`);
1019
+ if (this.vscodePath) {
1020
+ try {
1021
+ const result = await (0, import_execa.execa)(this.vscodePath, [
1022
+ "--command",
1023
+ "github.copilot.generateCode",
1024
+ "--args",
1025
+ JSON.stringify({ prompt })
1026
+ ], {
1027
+ timeout: 6e4,
1028
+ reject: false
1029
+ });
1030
+ if (!result.failed && result.stdout) {
1031
+ return result.stdout;
1032
+ }
1033
+ } catch (cliError) {
1034
+ this.logger.debug("VS Code AI CLI failed, using file system");
1035
+ }
1036
+ }
1037
+ return await this.sendAIRequestViaFileSystem(prompt, context);
1038
+ } catch (error) {
1039
+ this.logger.error(`Error getting VS Code AI response:`, error);
1040
+ throw error;
1041
+ }
1042
+ }
1043
+ async sendAIRequestViaFileSystem(prompt, context) {
1044
+ const tempDir = path.join(os.tmpdir(), "feishu-bridge", "vscode");
1045
+ if (!fs.existsSync(tempDir)) {
1046
+ fs.mkdirSync(tempDir, { recursive: true });
1047
+ }
1048
+ const instructionFile = path.join(tempDir, `ai_${Date.now()}.json`);
1049
+ const responseFile = path.join(tempDir, `ai_resp_${Date.now()}.txt`);
1050
+ const instruction = {
1051
+ type: "ai_request",
1052
+ prompt,
1053
+ context,
1054
+ timestamp: Date.now(),
1055
+ responseFile
1056
+ };
1057
+ fs.writeFileSync(instructionFile, JSON.stringify(instruction, null, 2));
1058
+ const maxWait = 12e4;
1059
+ const checkInterval = 1e3;
1060
+ let waited = 0;
1061
+ while (waited < maxWait) {
1062
+ if (fs.existsSync(responseFile)) {
1063
+ const response = fs.readFileSync(responseFile, "utf-8");
1064
+ try {
1065
+ fs.unlinkSync(instructionFile);
1066
+ fs.unlinkSync(responseFile);
1067
+ } catch {
1068
+ }
1069
+ return response;
1070
+ }
1071
+ await new Promise((resolve) => setTimeout(resolve, checkInterval));
1072
+ waited += checkInterval;
1073
+ }
1074
+ try {
1075
+ fs.unlinkSync(instructionFile);
1076
+ } catch {
1077
+ }
1078
+ return `AI request queued for VS Code. File: ${instructionFile}`;
1079
+ }
1080
+ async generateCode(description, options) {
1081
+ const fullPrompt = this.buildCodePrompt(description, options);
1082
+ return this.getAIResponse(fullPrompt);
1083
+ }
1084
+ async runProgram(command, cwd) {
1085
+ try {
1086
+ const result = await import_execa.execa.command(command, {
1087
+ cwd: cwd || process.cwd(),
1088
+ shell: true,
1089
+ timeout: 6e4,
1090
+ // 60秒超时
1091
+ reject: false
1092
+ });
1093
+ return {
1094
+ stdout: result.stdout,
1095
+ stderr: result.stderr,
1096
+ exitCode: result.exitCode || 0,
1097
+ success: result.exitCode === 0,
1098
+ output: result.stdout,
1099
+ error: result.failed ? result.stderr : void 0
1100
+ };
1101
+ } catch (error) {
1102
+ this.logger.error(`Error running program in VS Code:`, error);
1103
+ return {
1104
+ stdout: "",
1105
+ stderr: error instanceof Error ? error.message : String(error),
1106
+ exitCode: 1,
1107
+ success: false,
1108
+ error: error instanceof Error ? error.message : String(error)
1109
+ };
1110
+ }
1111
+ }
1112
+ };
1113
+
1114
+ // src/adapters/cursor.ts
1115
+ var import_execa2 = require("execa");
1116
+ var fs2 = __toESM(require("fs"));
1117
+ var path2 = __toESM(require("path"));
1118
+ var os2 = __toESM(require("os"));
1119
+ var CursorAdapter = class extends BaseIDEAdapter {
1120
+ constructor() {
1121
+ super(...arguments);
1122
+ this.type = "cursor";
1123
+ this.cursorPath = null;
1124
+ }
1125
+ checkAvailabilitySync() {
1126
+ if (process.env.CURSOR_SHELL || process.env.CURSOR_PID) {
1127
+ return true;
1128
+ }
1129
+ this.cursorPath = this.findCursorExecutable();
1130
+ return !!this.cursorPath;
1131
+ }
1132
+ findCursorExecutable() {
1133
+ const platform5 = os2.platform();
1134
+ const possiblePaths = [];
1135
+ if (platform5 === "darwin") {
1136
+ possiblePaths.push(
1137
+ "/Applications/Cursor.app/Contents/MacOS/Cursor",
1138
+ "/usr/local/bin/cursor",
1139
+ path2.join(os2.homedir(), "Applications/Cursor.app/Contents/MacOS/Cursor")
1140
+ );
1141
+ } else if (platform5 === "win32") {
1142
+ possiblePaths.push(
1143
+ path2.join(process.env.LOCALAPPDATA || "", "Programs/Cursor/Cursor.exe"),
1144
+ path2.join(process.env.PROGRAMFILES || "", "Cursor/Cursor.exe"),
1145
+ "C:\\Program Files\\Cursor\\Cursor.exe"
1146
+ );
1147
+ } else {
1148
+ possiblePaths.push(
1149
+ "/usr/bin/cursor",
1150
+ "/usr/local/bin/cursor",
1151
+ "/opt/cursor/cursor",
1152
+ path2.join(os2.homedir(), ".local/bin/cursor")
1153
+ );
1154
+ }
1155
+ for (const cursorPath of possiblePaths) {
1156
+ if (fs2.existsSync(cursorPath)) {
1157
+ return cursorPath;
1158
+ }
1159
+ }
1160
+ try {
1161
+ if (platform5 === "win32") {
1162
+ const result = import_execa2.execa.sync("where", ["cursor"], { reject: false });
1163
+ if (result.stdout) {
1164
+ const lines = result.stdout.split("\n").map((l) => l.trim()).filter((l) => l);
1165
+ if (lines.length > 0) return lines[0];
1166
+ }
1167
+ } else {
1168
+ const result = import_execa2.execa.sync("which", ["cursor"], { reject: false });
1169
+ if (result.stdout) {
1170
+ return result.stdout.trim();
1171
+ }
1172
+ }
1173
+ } catch {
1174
+ }
1175
+ return null;
1176
+ }
1177
+ async checkAvailability() {
1178
+ return this.checkAvailabilitySync();
1179
+ }
1180
+ async sendCommand(command, context) {
1181
+ try {
1182
+ this.logger.info(`Sending command to Cursor: ${command}`);
1183
+ if (this.cursorPath) {
1184
+ try {
1185
+ const result = await (0, import_execa2.execa)(this.cursorPath, [
1186
+ "--command",
1187
+ "workbench.action.executeCommand",
1188
+ "--args",
1189
+ JSON.stringify({ command })
1190
+ ], {
1191
+ timeout: 3e4,
1192
+ reject: false
1193
+ });
1194
+ if (!result.failed) {
1195
+ return `Cursor executed: ${command}
1196
+ ${result.stdout}`;
1197
+ }
1198
+ } catch (cliError) {
1199
+ this.logger.debug("Cursor CLI failed, trying alternative methods");
1200
+ }
1201
+ }
1202
+ return await this.sendViaFileSystem(command, context);
1203
+ } catch (error) {
1204
+ this.logger.error(`Error executing Cursor command:`, error);
1205
+ throw new Error(`Cursor command failed: ${error instanceof Error ? error.message : String(error)}`);
1206
+ }
1207
+ }
1208
+ async sendViaFileSystem(command, context) {
1209
+ const tempDir = path2.join(os2.tmpdir(), "feishu-bridge", "cursor");
1210
+ if (!fs2.existsSync(tempDir)) {
1211
+ fs2.mkdirSync(tempDir, { recursive: true });
1212
+ }
1213
+ const instructionFile = path2.join(tempDir, `instruction_${Date.now()}.json`);
1214
+ const responseFile = path2.join(tempDir, `response_${Date.now()}.txt`);
1215
+ const instruction = {
1216
+ type: "command",
1217
+ command,
1218
+ context,
1219
+ timestamp: Date.now(),
1220
+ responseFile
1221
+ };
1222
+ fs2.writeFileSync(instructionFile, JSON.stringify(instruction, null, 2));
1223
+ const maxWait = 3e4;
1224
+ const checkInterval = 500;
1225
+ let waited = 0;
1226
+ while (waited < maxWait) {
1227
+ if (fs2.existsSync(responseFile)) {
1228
+ const response = fs2.readFileSync(responseFile, "utf-8");
1229
+ try {
1230
+ fs2.unlinkSync(instructionFile);
1231
+ fs2.unlinkSync(responseFile);
1232
+ } catch {
1233
+ }
1234
+ return response;
1235
+ }
1236
+ await new Promise((resolve) => setTimeout(resolve, checkInterval));
1237
+ waited += checkInterval;
1238
+ }
1239
+ try {
1240
+ fs2.unlinkSync(instructionFile);
1241
+ } catch {
1242
+ }
1243
+ return `Command sent to Cursor workspace (file: ${instructionFile}). No response received within ${maxWait}ms. Make sure Cursor is running with Feishu Bridge extension.`;
1244
+ }
1245
+ async getAIResponse(prompt, context) {
1246
+ try {
1247
+ this.logger.info(`Getting AI response from Cursor for prompt: ${prompt}`);
1248
+ const tempDir = path2.join(os2.tmpdir(), "feishu-bridge", "cursor");
1249
+ if (!fs2.existsSync(tempDir)) {
1250
+ fs2.mkdirSync(tempDir, { recursive: true });
1251
+ }
1252
+ const instructionFile = path2.join(tempDir, `ai_request_${Date.now()}.json`);
1253
+ const responseFile = path2.join(tempDir, `ai_response_${Date.now()}.txt`);
1254
+ const instruction = {
1255
+ type: "ai_request",
1256
+ prompt,
1257
+ context,
1258
+ timestamp: Date.now(),
1259
+ responseFile
1260
+ };
1261
+ fs2.writeFileSync(instructionFile, JSON.stringify(instruction, null, 2));
1262
+ const maxWait = 6e4;
1263
+ const checkInterval = 500;
1264
+ let waited = 0;
1265
+ while (waited < maxWait) {
1266
+ if (fs2.existsSync(responseFile)) {
1267
+ const response = fs2.readFileSync(responseFile, "utf-8");
1268
+ try {
1269
+ fs2.unlinkSync(instructionFile);
1270
+ fs2.unlinkSync(responseFile);
1271
+ } catch {
1272
+ }
1273
+ return response;
1274
+ }
1275
+ await new Promise((resolve) => setTimeout(resolve, checkInterval));
1276
+ waited += checkInterval;
1277
+ }
1278
+ try {
1279
+ fs2.unlinkSync(instructionFile);
1280
+ } catch {
1281
+ }
1282
+ return `AI request sent to Cursor (file: ${instructionFile}). No response received within ${maxWait}ms.`;
1283
+ } catch (error) {
1284
+ this.logger.error(`Error getting Cursor AI response:`, error);
1285
+ throw error;
1286
+ }
1287
+ }
1288
+ async generateCode(description, options) {
1289
+ const fullPrompt = this.buildCodePrompt(description, options);
1290
+ return this.getAIResponse(fullPrompt);
1291
+ }
1292
+ async runProgram(command, cwd) {
1293
+ try {
1294
+ const result = await import_execa2.execa.command(command, {
1295
+ cwd: cwd || process.cwd(),
1296
+ shell: true,
1297
+ timeout: 6e4,
1298
+ reject: false
1299
+ });
1300
+ return {
1301
+ stdout: result.stdout,
1302
+ stderr: result.stderr,
1303
+ exitCode: result.exitCode || 0,
1304
+ success: result.exitCode === 0,
1305
+ output: result.stdout,
1306
+ error: result.failed ? result.stderr : void 0
1307
+ };
1308
+ } catch (error) {
1309
+ this.logger.error(`Error running program in Cursor:`, error);
1310
+ return {
1311
+ stdout: "",
1312
+ stderr: error instanceof Error ? error.message : String(error),
1313
+ exitCode: 1,
1314
+ success: false,
1315
+ error: error instanceof Error ? error.message : String(error)
1316
+ };
1317
+ }
1318
+ }
1319
+ };
1320
+
1321
+ // src/adapters/trae.ts
1322
+ var import_execa3 = require("execa");
1323
+ var fs3 = __toESM(require("fs"));
1324
+ var path3 = __toESM(require("path"));
1325
+ var os3 = __toESM(require("os"));
1326
+ var TraeAdapter = class extends BaseIDEAdapter {
1327
+ constructor() {
1328
+ super(...arguments);
1329
+ this.type = "trae";
1330
+ this.traePath = null;
1331
+ }
1332
+ checkAvailabilitySync() {
1333
+ if (process.env.TRAE_SESSION || process.env.TRAE_PID) {
1334
+ return true;
1335
+ }
1336
+ this.traePath = this.findTraeExecutable();
1337
+ return !!this.traePath;
1338
+ }
1339
+ findTraeExecutable() {
1340
+ const platform5 = os3.platform();
1341
+ const possiblePaths = [];
1342
+ if (platform5 === "darwin") {
1343
+ possiblePaths.push(
1344
+ "/Applications/Trae.app/Contents/MacOS/Trae",
1345
+ "/usr/local/bin/trae",
1346
+ path3.join(os3.homedir(), "Applications/Trae.app/Contents/MacOS/Trae")
1347
+ );
1348
+ } else if (platform5 === "win32") {
1349
+ possiblePaths.push(
1350
+ path3.join(process.env.LOCALAPPDATA || "", "Programs", "Trae", "Trae.exe"),
1351
+ "C:\\Program Files\\Trae\\Trae.exe"
1352
+ );
1353
+ } else {
1354
+ possiblePaths.push(
1355
+ "/usr/bin/trae",
1356
+ "/usr/local/bin/trae",
1357
+ "/opt/trae/trae",
1358
+ path3.join(os3.homedir(), ".local/bin/trae")
1359
+ );
1360
+ }
1361
+ for (const p of possiblePaths) {
1362
+ if (fs3.existsSync(p)) {
1363
+ return p;
1364
+ }
1365
+ }
1366
+ try {
1367
+ if (platform5 === "win32") {
1368
+ const result = import_execa3.execa.sync("where", ["trae"], { reject: false });
1369
+ if (result.stdout) {
1370
+ const lines = result.stdout.split("\n").map((l) => l.trim()).filter((l) => l);
1371
+ if (lines.length > 0) return lines[0];
1372
+ }
1373
+ } else {
1374
+ const result = import_execa3.execa.sync("which", ["trae"], { reject: false });
1375
+ if (result.stdout) {
1376
+ return result.stdout.trim();
1377
+ }
1378
+ }
1379
+ } catch {
1380
+ }
1381
+ return null;
1382
+ }
1383
+ async checkAvailability() {
1384
+ return this.checkAvailabilitySync();
1385
+ }
1386
+ async sendCommand(command, context) {
1387
+ try {
1388
+ this.logger.info(`Sending command to Trae: ${command}`);
1389
+ if (this.traePath) {
1390
+ try {
1391
+ const result = await (0, import_execa3.execa)(this.traePath, [
1392
+ "--executeCommand",
1393
+ command
1394
+ ], {
1395
+ timeout: 3e4,
1396
+ reject: false,
1397
+ cwd: context?.workspaceRoot
1398
+ });
1399
+ if (!result.failed) {
1400
+ return result.stdout || "Command executed";
1401
+ }
1402
+ } catch (cliError) {
1403
+ this.logger.debug("Trae CLI failed, using file system");
1404
+ }
1405
+ }
1406
+ return await this.sendViaFileSystem(command, context);
1407
+ } catch (error) {
1408
+ this.logger.error(`Error executing Trae command:`, error);
1409
+ throw new Error(`Trae command failed: ${error instanceof Error ? error.message : String(error)}`);
1410
+ }
1411
+ }
1412
+ async sendViaFileSystem(command, context) {
1413
+ const tempDir = path3.join(os3.tmpdir(), "feishu-bridge", "trae");
1414
+ if (!fs3.existsSync(tempDir)) {
1415
+ fs3.mkdirSync(tempDir, { recursive: true });
1416
+ }
1417
+ const instructionFile = path3.join(tempDir, `cmd_${Date.now()}.json`);
1418
+ const responseFile = path3.join(tempDir, `resp_${Date.now()}.txt`);
1419
+ const instruction = {
1420
+ type: "command",
1421
+ command,
1422
+ context,
1423
+ timestamp: Date.now(),
1424
+ responseFile
1425
+ };
1426
+ fs3.writeFileSync(instructionFile, JSON.stringify(instruction, null, 2));
1427
+ const maxWait = 3e4;
1428
+ const checkInterval = 500;
1429
+ let waited = 0;
1430
+ while (waited < maxWait) {
1431
+ if (fs3.existsSync(responseFile)) {
1432
+ const response = fs3.readFileSync(responseFile, "utf-8");
1433
+ try {
1434
+ fs3.unlinkSync(instructionFile);
1435
+ fs3.unlinkSync(responseFile);
1436
+ } catch {
1437
+ }
1438
+ return response;
1439
+ }
1440
+ await new Promise((resolve) => setTimeout(resolve, checkInterval));
1441
+ waited += checkInterval;
1442
+ }
1443
+ try {
1444
+ fs3.unlinkSync(instructionFile);
1445
+ } catch {
1446
+ }
1447
+ return `Command queued for Trae. File: ${instructionFile}`;
1448
+ }
1449
+ async getAIResponse(prompt, context) {
1450
+ try {
1451
+ this.logger.info(`Getting AI response from Trae for prompt: ${prompt}`);
1452
+ if (this.traePath) {
1453
+ try {
1454
+ const result = await (0, import_execa3.execa)(this.traePath, [
1455
+ "--ai",
1456
+ prompt
1457
+ ], {
1458
+ timeout: 6e4,
1459
+ reject: false,
1460
+ cwd: context?.workspaceRoot
1461
+ });
1462
+ if (!result.failed && result.stdout) {
1463
+ return result.stdout;
1464
+ }
1465
+ } catch (cliError) {
1466
+ this.logger.debug("Trae AI CLI failed, using file system");
1467
+ }
1468
+ }
1469
+ return await this.sendAIRequestViaFileSystem(prompt, context);
1470
+ } catch (error) {
1471
+ this.logger.error(`Error getting Trae AI response:`, error);
1472
+ throw error;
1473
+ }
1474
+ }
1475
+ async sendAIRequestViaFileSystem(prompt, context) {
1476
+ const tempDir = path3.join(os3.tmpdir(), "feishu-bridge", "trae");
1477
+ if (!fs3.existsSync(tempDir)) {
1478
+ fs3.mkdirSync(tempDir, { recursive: true });
1479
+ }
1480
+ const instructionFile = path3.join(tempDir, `ai_${Date.now()}.json`);
1481
+ const responseFile = path3.join(tempDir, `ai_resp_${Date.now()}.txt`);
1482
+ const instruction = {
1483
+ type: "ai_request",
1484
+ prompt,
1485
+ context,
1486
+ timestamp: Date.now(),
1487
+ responseFile
1488
+ };
1489
+ fs3.writeFileSync(instructionFile, JSON.stringify(instruction, null, 2));
1490
+ const maxWait = 12e4;
1491
+ const checkInterval = 1e3;
1492
+ let waited = 0;
1493
+ while (waited < maxWait) {
1494
+ if (fs3.existsSync(responseFile)) {
1495
+ const response = fs3.readFileSync(responseFile, "utf-8");
1496
+ try {
1497
+ fs3.unlinkSync(instructionFile);
1498
+ fs3.unlinkSync(responseFile);
1499
+ } catch {
1500
+ }
1501
+ return response;
1502
+ }
1503
+ await new Promise((resolve) => setTimeout(resolve, checkInterval));
1504
+ waited += checkInterval;
1505
+ }
1506
+ try {
1507
+ fs3.unlinkSync(instructionFile);
1508
+ } catch {
1509
+ }
1510
+ return `AI request queued for Trae. File: ${instructionFile}`;
1511
+ }
1512
+ async generateCode(description, options) {
1513
+ const fullPrompt = this.buildCodePrompt(description, options);
1514
+ return this.getAIResponse(fullPrompt);
1515
+ }
1516
+ async runProgram(command, cwd) {
1517
+ try {
1518
+ const result = await import_execa3.execa.command(command, {
1519
+ cwd: cwd || process.cwd(),
1520
+ shell: true,
1521
+ timeout: 6e4,
1522
+ reject: false
1523
+ });
1524
+ return {
1525
+ stdout: result.stdout,
1526
+ stderr: result.stderr,
1527
+ exitCode: result.exitCode || 0,
1528
+ success: result.exitCode === 0,
1529
+ output: result.stdout,
1530
+ error: result.failed ? result.stderr : void 0
1531
+ };
1532
+ } catch (error) {
1533
+ this.logger.error(`Error running program in Trae:`, error);
1534
+ return {
1535
+ stdout: "",
1536
+ stderr: error instanceof Error ? error.message : String(error),
1537
+ exitCode: 1,
1538
+ success: false,
1539
+ error: error instanceof Error ? error.message : String(error)
1540
+ };
1541
+ }
1542
+ }
1543
+ };
1544
+
1545
+ // src/adapters/antigravity.ts
1546
+ var import_execa4 = require("execa");
1547
+ var fs4 = __toESM(require("fs"));
1548
+ var path4 = __toESM(require("path"));
1549
+ var os4 = __toESM(require("os"));
1550
+ var AntigravityAdapter = class extends BaseIDEAdapter {
1551
+ constructor() {
1552
+ super(...arguments);
1553
+ this.type = "antigravity";
1554
+ this.antigravityPath = null;
1555
+ }
1556
+ checkAvailabilitySync() {
1557
+ if (process.env.ANTIGRAVITY_SESSION) {
1558
+ return true;
1559
+ }
1560
+ this.antigravityPath = this.findAntigravityExecutable();
1561
+ return !!this.antigravityPath;
1562
+ }
1563
+ findAntigravityExecutable() {
1564
+ try {
1565
+ const result = import_execa4.execa.sync("which", ["antigravity"], { reject: false });
1566
+ if (result.stdout) return result.stdout.trim();
1567
+ } catch {
1568
+ }
1569
+ const possiblePaths = [
1570
+ "/usr/local/bin/antigravity",
1571
+ "/usr/bin/antigravity",
1572
+ path4.join(os4.homedir(), ".local/bin/antigravity")
1573
+ ];
1574
+ for (const p of possiblePaths) {
1575
+ if (fs4.existsSync(p)) return p;
1576
+ }
1577
+ return null;
1578
+ }
1579
+ async checkAvailability() {
1580
+ return this.checkAvailabilitySync();
1581
+ }
1582
+ async sendCommand(command, context) {
1583
+ try {
1584
+ this.logger.info(`Sending command to Antigravity: ${command}`);
1585
+ if (this.antigravityPath) {
1586
+ try {
1587
+ const result = await (0, import_execa4.execa)(this.antigravityPath, ["execute", command], {
1588
+ timeout: 3e4,
1589
+ reject: false,
1590
+ cwd: context?.workspaceRoot
1591
+ });
1592
+ if (!result.failed) {
1593
+ return result.stdout || "Command executed";
1594
+ }
1595
+ } catch (cliError) {
1596
+ this.logger.debug("Antigravity CLI failed, using file system");
1597
+ }
1598
+ }
1599
+ return await this.sendViaFileSystem(command, context);
1600
+ } catch (error) {
1601
+ this.logger.error(`Error executing Antigravity command:`, error);
1602
+ throw new Error(`Antigravity command failed: ${error instanceof Error ? error.message : String(error)}`);
1603
+ }
1604
+ }
1605
+ async sendViaFileSystem(command, context) {
1606
+ const tempDir = path4.join(os4.tmpdir(), "feishu-bridge", "antigravity");
1607
+ if (!fs4.existsSync(tempDir)) {
1608
+ fs4.mkdirSync(tempDir, { recursive: true });
1609
+ }
1610
+ const instructionFile = path4.join(tempDir, `cmd_${Date.now()}.json`);
1611
+ const responseFile = path4.join(tempDir, `resp_${Date.now()}.txt`);
1612
+ const instruction = {
1613
+ type: "command",
1614
+ command,
1615
+ context,
1616
+ timestamp: Date.now(),
1617
+ responseFile
1618
+ };
1619
+ fs4.writeFileSync(instructionFile, JSON.stringify(instruction, null, 2));
1620
+ const maxWait = 3e4;
1621
+ const checkInterval = 500;
1622
+ let waited = 0;
1623
+ while (waited < maxWait) {
1624
+ if (fs4.existsSync(responseFile)) {
1625
+ const response = fs4.readFileSync(responseFile, "utf-8");
1626
+ try {
1627
+ fs4.unlinkSync(instructionFile);
1628
+ fs4.unlinkSync(responseFile);
1629
+ } catch {
1630
+ }
1631
+ return response;
1632
+ }
1633
+ await new Promise((resolve) => setTimeout(resolve, checkInterval));
1634
+ waited += checkInterval;
1635
+ }
1636
+ try {
1637
+ fs4.unlinkSync(instructionFile);
1638
+ } catch {
1639
+ }
1640
+ return `Command queued for Antigravity. File: ${instructionFile}`;
1641
+ }
1642
+ async getAIResponse(prompt, context) {
1643
+ try {
1644
+ this.logger.info(`Getting AI response from Antigravity for prompt: ${prompt}`);
1645
+ if (this.antigravityPath) {
1646
+ try {
1647
+ const result = await (0, import_execa4.execa)(this.antigravityPath, ["ask", prompt], {
1648
+ timeout: 6e4,
1649
+ reject: false,
1650
+ cwd: context?.workspaceRoot
1651
+ });
1652
+ if (!result.failed && result.stdout) {
1653
+ return result.stdout;
1654
+ }
1655
+ } catch (cliError) {
1656
+ this.logger.debug("Antigravity AI CLI failed, using file system");
1657
+ }
1658
+ }
1659
+ return await this.sendAIRequestViaFileSystem(prompt, context);
1660
+ } catch (error) {
1661
+ this.logger.error(`Error getting Antigravity AI response:`, error);
1662
+ throw error;
1663
+ }
1664
+ }
1665
+ async sendAIRequestViaFileSystem(prompt, context) {
1666
+ const tempDir = path4.join(os4.tmpdir(), "feishu-bridge", "antigravity");
1667
+ if (!fs4.existsSync(tempDir)) {
1668
+ fs4.mkdirSync(tempDir, { recursive: true });
1669
+ }
1670
+ const instructionFile = path4.join(tempDir, `ai_${Date.now()}.json`);
1671
+ const responseFile = path4.join(tempDir, `ai_resp_${Date.now()}.txt`);
1672
+ const instruction = {
1673
+ type: "ai_request",
1674
+ prompt,
1675
+ context,
1676
+ timestamp: Date.now(),
1677
+ responseFile
1678
+ };
1679
+ fs4.writeFileSync(instructionFile, JSON.stringify(instruction, null, 2));
1680
+ const maxWait = 12e4;
1681
+ const checkInterval = 1e3;
1682
+ let waited = 0;
1683
+ while (waited < maxWait) {
1684
+ if (fs4.existsSync(responseFile)) {
1685
+ const response = fs4.readFileSync(responseFile, "utf-8");
1686
+ try {
1687
+ fs4.unlinkSync(instructionFile);
1688
+ fs4.unlinkSync(responseFile);
1689
+ } catch {
1690
+ }
1691
+ return response;
1692
+ }
1693
+ await new Promise((resolve) => setTimeout(resolve, checkInterval));
1694
+ waited += checkInterval;
1695
+ }
1696
+ try {
1697
+ fs4.unlinkSync(instructionFile);
1698
+ } catch {
1699
+ }
1700
+ return `AI request queued for Antigravity. File: ${instructionFile}`;
1701
+ }
1702
+ async generateCode(description, options) {
1703
+ const fullPrompt = this.buildCodePrompt(description, options);
1704
+ return this.getAIResponse(fullPrompt);
1705
+ }
1706
+ async runProgram(command, cwd) {
1707
+ try {
1708
+ const result = await import_execa4.execa.command(command, {
1709
+ cwd: cwd || process.cwd(),
1710
+ shell: true,
1711
+ timeout: 6e4,
1712
+ reject: false
1713
+ });
1714
+ return {
1715
+ stdout: result.stdout,
1716
+ stderr: result.stderr,
1717
+ exitCode: result.exitCode || 0,
1718
+ success: result.exitCode === 0,
1719
+ output: result.stdout,
1720
+ error: result.failed ? result.stderr : void 0
1721
+ };
1722
+ } catch (error) {
1723
+ this.logger.error(`Error running program in Antigravity:`, error);
1724
+ return {
1725
+ stdout: "",
1726
+ stderr: error instanceof Error ? error.message : String(error),
1727
+ exitCode: 1,
1728
+ success: false,
1729
+ error: error instanceof Error ? error.message : String(error)
1730
+ };
1731
+ }
1732
+ }
1733
+ };
1734
+
1735
+ // src/adapters/kiro.ts
1736
+ var import_execa5 = require("execa");
1737
+ var fs5 = __toESM(require("fs"));
1738
+ var path5 = __toESM(require("path"));
1739
+ var os5 = __toESM(require("os"));
1740
+ var KiroAdapter = class extends BaseIDEAdapter {
1741
+ constructor() {
1742
+ super(...arguments);
1743
+ this.type = "kiro";
1744
+ this.kiroPath = null;
1745
+ }
1746
+ checkAvailabilitySync() {
1747
+ if (process.env.KIRO_SESSION) {
1748
+ return true;
1749
+ }
1750
+ this.kiroPath = this.findKiroExecutable();
1751
+ return !!this.kiroPath;
1752
+ }
1753
+ findKiroExecutable() {
1754
+ try {
1755
+ const result = import_execa5.execa.sync("which", ["kiro"], { reject: false });
1756
+ if (result.stdout) return result.stdout.trim();
1757
+ } catch {
1758
+ }
1759
+ const possiblePaths = [
1760
+ "/usr/local/bin/kiro",
1761
+ "/usr/bin/kiro",
1762
+ path5.join(os5.homedir(), ".local/bin/kiro")
1763
+ ];
1764
+ for (const p of possiblePaths) {
1765
+ if (fs5.existsSync(p)) return p;
1766
+ }
1767
+ return null;
1768
+ }
1769
+ async checkAvailability() {
1770
+ return this.checkAvailabilitySync();
1771
+ }
1772
+ async sendCommand(command, context) {
1773
+ try {
1774
+ this.logger.info(`Sending command to Kiro: ${command}`);
1775
+ if (this.kiroPath) {
1776
+ try {
1777
+ const result = await (0, import_execa5.execa)(this.kiroPath, ["execute", command], {
1778
+ timeout: 3e4,
1779
+ reject: false,
1780
+ cwd: context?.workspaceRoot
1781
+ });
1782
+ if (!result.failed) {
1783
+ return result.stdout || "Command executed";
1784
+ }
1785
+ } catch (cliError) {
1786
+ this.logger.debug("Kiro CLI failed, using file system");
1787
+ }
1788
+ }
1789
+ return await this.sendViaFileSystem(command, context);
1790
+ } catch (error) {
1791
+ this.logger.error(`Error executing Kiro command:`, error);
1792
+ throw new Error(`Kiro command failed: ${error instanceof Error ? error.message : String(error)}`);
1793
+ }
1794
+ }
1795
+ async sendViaFileSystem(command, context) {
1796
+ const tempDir = path5.join(os5.tmpdir(), "feishu-bridge", "kiro");
1797
+ if (!fs5.existsSync(tempDir)) {
1798
+ fs5.mkdirSync(tempDir, { recursive: true });
1799
+ }
1800
+ const instructionFile = path5.join(tempDir, `cmd_${Date.now()}.json`);
1801
+ const responseFile = path5.join(tempDir, `resp_${Date.now()}.txt`);
1802
+ const instruction = {
1803
+ type: "command",
1804
+ command,
1805
+ context,
1806
+ timestamp: Date.now(),
1807
+ responseFile
1808
+ };
1809
+ fs5.writeFileSync(instructionFile, JSON.stringify(instruction, null, 2));
1810
+ const maxWait = 3e4;
1811
+ const checkInterval = 500;
1812
+ let waited = 0;
1813
+ while (waited < maxWait) {
1814
+ if (fs5.existsSync(responseFile)) {
1815
+ const response = fs5.readFileSync(responseFile, "utf-8");
1816
+ try {
1817
+ fs5.unlinkSync(instructionFile);
1818
+ fs5.unlinkSync(responseFile);
1819
+ } catch {
1820
+ }
1821
+ return response;
1822
+ }
1823
+ await new Promise((resolve) => setTimeout(resolve, checkInterval));
1824
+ waited += checkInterval;
1825
+ }
1826
+ try {
1827
+ fs5.unlinkSync(instructionFile);
1828
+ } catch {
1829
+ }
1830
+ return `Command queued for Kiro. File: ${instructionFile}`;
1831
+ }
1832
+ async getAIResponse(prompt, context) {
1833
+ try {
1834
+ this.logger.info(`Getting AI response from Kiro for prompt: ${prompt}`);
1835
+ if (this.kiroPath) {
1836
+ try {
1837
+ const result = await (0, import_execa5.execa)(this.kiroPath, ["ask", prompt], {
1838
+ timeout: 6e4,
1839
+ reject: false,
1840
+ cwd: context?.workspaceRoot
1841
+ });
1842
+ if (!result.failed && result.stdout) {
1843
+ return result.stdout;
1844
+ }
1845
+ } catch (cliError) {
1846
+ this.logger.debug("Kiro AI CLI failed, using file system");
1847
+ }
1848
+ }
1849
+ return await this.sendAIRequestViaFileSystem(prompt, context);
1850
+ } catch (error) {
1851
+ this.logger.error(`Error getting Kiro AI response:`, error);
1852
+ throw error;
1853
+ }
1854
+ }
1855
+ async sendAIRequestViaFileSystem(prompt, context) {
1856
+ const tempDir = path5.join(os5.tmpdir(), "feishu-bridge", "kiro");
1857
+ if (!fs5.existsSync(tempDir)) {
1858
+ fs5.mkdirSync(tempDir, { recursive: true });
1859
+ }
1860
+ const instructionFile = path5.join(tempDir, `ai_${Date.now()}.json`);
1861
+ const responseFile = path5.join(tempDir, `ai_resp_${Date.now()}.txt`);
1862
+ const instruction = {
1863
+ type: "ai_request",
1864
+ prompt,
1865
+ context,
1866
+ timestamp: Date.now(),
1867
+ responseFile
1868
+ };
1869
+ fs5.writeFileSync(instructionFile, JSON.stringify(instruction, null, 2));
1870
+ const maxWait = 12e4;
1871
+ const checkInterval = 1e3;
1872
+ let waited = 0;
1873
+ while (waited < maxWait) {
1874
+ if (fs5.existsSync(responseFile)) {
1875
+ const response = fs5.readFileSync(responseFile, "utf-8");
1876
+ try {
1877
+ fs5.unlinkSync(instructionFile);
1878
+ fs5.unlinkSync(responseFile);
1879
+ } catch {
1880
+ }
1881
+ return response;
1882
+ }
1883
+ await new Promise((resolve) => setTimeout(resolve, checkInterval));
1884
+ waited += checkInterval;
1885
+ }
1886
+ try {
1887
+ fs5.unlinkSync(instructionFile);
1888
+ } catch {
1889
+ }
1890
+ return `AI request queued for Kiro. File: ${instructionFile}`;
1891
+ }
1892
+ async generateCode(description, options) {
1893
+ const fullPrompt = this.buildCodePrompt(description, options);
1894
+ return this.getAIResponse(fullPrompt);
1895
+ }
1896
+ async runProgram(command, cwd) {
1897
+ try {
1898
+ const result = await import_execa5.execa.command(command, {
1899
+ cwd: cwd || process.cwd(),
1900
+ shell: true,
1901
+ timeout: 6e4,
1902
+ reject: false
1903
+ });
1904
+ return {
1905
+ stdout: result.stdout,
1906
+ stderr: result.stderr,
1907
+ exitCode: result.exitCode || 0,
1908
+ success: result.exitCode === 0,
1909
+ output: result.stdout,
1910
+ error: result.failed ? result.stderr : void 0
1911
+ };
1912
+ } catch (error) {
1913
+ this.logger.error(`Error running program in Kiro:`, error);
1914
+ return {
1915
+ stdout: "",
1916
+ stderr: error instanceof Error ? error.message : String(error),
1917
+ exitCode: 1,
1918
+ success: false,
1919
+ error: error instanceof Error ? error.message : String(error)
1920
+ };
1921
+ }
1922
+ }
1923
+ };
1924
+
1925
+ // src/adapters/opencode.ts
1926
+ var net = __toESM(require("net"));
1927
+ var fs6 = __toESM(require("fs"));
1928
+ var os6 = __toESM(require("os"));
1929
+ var path6 = __toESM(require("path"));
1930
+ var OpenCodeAdapter = class extends BaseIDEAdapter {
1931
+ constructor() {
1932
+ super(...arguments);
1933
+ this.type = "opencode";
1934
+ }
1935
+ checkAvailabilitySync() {
1936
+ return !!process.env.OPENCODE_SESSION || fs6.existsSync(path6.join(os6.homedir(), ".opencode", "session")) || this.checkOpenCodeProcess();
1937
+ }
1938
+ checkOpenCodeProcess() {
1939
+ try {
1940
+ return true;
1941
+ } catch {
1942
+ return false;
1943
+ }
1944
+ }
1945
+ async checkAvailability() {
1946
+ return this.checkAvailabilitySync();
1947
+ }
1948
+ async sendCommand(command, context) {
1949
+ try {
1950
+ this.logger.info(`Sending command to OpenCode: ${command}`);
1951
+ const socketPath = this.getSocketPath();
1952
+ if (socketPath && fs6.existsSync(socketPath)) {
1953
+ return await this.sendViaSocket({
1954
+ type: "command",
1955
+ command,
1956
+ sessionId: this.sessionId,
1957
+ context
1958
+ });
1959
+ } else {
1960
+ return `OpenCode processed command: ${command}`;
1961
+ }
1962
+ } catch (error) {
1963
+ this.logger.error(`Error executing OpenCode command:`, error);
1964
+ throw error;
1965
+ }
1966
+ }
1967
+ getSocketPath() {
1968
+ const possiblePaths = [
1969
+ path6.join(os6.tmpdir(), "opencode.sock"),
1970
+ path6.join(os6.homedir(), ".opencode", "socket"),
1971
+ process.env.OPENCODE_SOCKET || ""
1972
+ ];
1973
+ for (const socketPath of possiblePaths) {
1974
+ if (socketPath && fs6.existsSync(socketPath)) {
1975
+ return socketPath;
1976
+ }
1977
+ }
1978
+ return null;
1979
+ }
1980
+ sendViaSocket(data) {
1981
+ return new Promise((resolve, reject) => {
1982
+ const socketPath = this.getSocketPath();
1983
+ if (!socketPath) {
1984
+ reject(new Error("No OpenCode socket available"));
1985
+ return;
1986
+ }
1987
+ const client = net.createConnection(socketPath);
1988
+ let response = "";
1989
+ client.on("data", (data2) => {
1990
+ response += data2.toString();
1991
+ });
1992
+ client.on("end", () => {
1993
+ try {
1994
+ const parsed = JSON.parse(response);
1995
+ resolve(parsed.result || response);
1996
+ } catch (e) {
1997
+ resolve(response);
1998
+ }
1999
+ });
2000
+ client.on("error", (err) => {
2001
+ reject(err);
2002
+ });
2003
+ client.write(JSON.stringify(data));
2004
+ setTimeout(() => {
2005
+ client.destroy();
2006
+ reject(new Error("OpenCode command timeout"));
2007
+ }, 3e4);
2008
+ });
2009
+ }
2010
+ async getAIResponse(prompt, context) {
2011
+ try {
2012
+ this.logger.info(`Getting AI response from OpenCode for prompt: ${prompt}`);
2013
+ return await this.sendCommand(`/ask ${prompt}`, context);
2014
+ } catch (error) {
2015
+ this.logger.error(`Error getting OpenCode AI response:`, error);
2016
+ throw error;
2017
+ }
2018
+ }
2019
+ async generateCode(description, options) {
2020
+ const fullPrompt = this.buildCodePrompt(description, options);
2021
+ return this.getAIResponse(fullPrompt);
2022
+ }
2023
+ async runProgram(command, cwd) {
2024
+ try {
2025
+ const result = await this.sendCommand(`/run ${command}`, { workspaceRoot: cwd });
2026
+ return {
2027
+ stdout: result,
2028
+ stderr: "",
2029
+ exitCode: 0,
2030
+ success: true,
2031
+ output: result
2032
+ };
2033
+ } catch (error) {
2034
+ this.logger.error(`Error running program in OpenCode:`, error);
2035
+ return {
2036
+ stdout: "",
2037
+ stderr: error instanceof Error ? error.message : String(error),
2038
+ exitCode: 1,
2039
+ success: false,
2040
+ error: error instanceof Error ? error.message : String(error)
2041
+ };
2042
+ }
2043
+ }
2044
+ };
2045
+
2046
+ // src/adapters/claude-code.ts
2047
+ var import_execa6 = require("execa");
2048
+ var fs7 = __toESM(require("fs"));
2049
+ var path7 = __toESM(require("path"));
2050
+ var os7 = __toESM(require("os"));
2051
+ var ClaudeCodeAdapter = class extends BaseIDEAdapter {
2052
+ constructor() {
2053
+ super(...arguments);
2054
+ this.type = "claude";
2055
+ this.claudePath = null;
2056
+ }
2057
+ checkAvailabilitySync() {
2058
+ if (process.env.CLAUDE_CODE_SESSION) {
2059
+ return true;
2060
+ }
2061
+ this.claudePath = this.findClaudeExecutable();
2062
+ return !!this.claudePath;
2063
+ }
2064
+ findClaudeExecutable() {
2065
+ const platform5 = os7.platform();
2066
+ try {
2067
+ const result = import_execa6.execa.sync("claude", ["--version"], { reject: false });
2068
+ if (result.exitCode === 0) {
2069
+ return "claude";
2070
+ }
2071
+ } catch {
2072
+ }
2073
+ const possiblePaths = [];
2074
+ if (platform5 === "darwin") {
2075
+ possiblePaths.push(
2076
+ "/usr/local/bin/claude",
2077
+ "/opt/homebrew/bin/claude",
2078
+ path7.join(os7.homedir(), ".local/bin/claude")
2079
+ );
2080
+ } else if (platform5 === "win32") {
2081
+ possiblePaths.push(
2082
+ path7.join(os7.homedir(), "AppData", "Local", "Programs", "claude", "claude.exe"),
2083
+ "C:\\Program Files\\Claude\\claude.exe"
2084
+ );
2085
+ } else {
2086
+ possiblePaths.push(
2087
+ "/usr/bin/claude",
2088
+ "/usr/local/bin/claude",
2089
+ path7.join(os7.homedir(), ".local/bin/claude"),
2090
+ path7.join(os7.homedir(), ".npm-global/bin/claude")
2091
+ );
2092
+ }
2093
+ for (const p of possiblePaths) {
2094
+ if (fs7.existsSync(p)) {
2095
+ return p;
2096
+ }
2097
+ }
2098
+ return null;
2099
+ }
2100
+ async checkAvailability() {
2101
+ return this.checkAvailabilitySync();
2102
+ }
2103
+ async sendCommand(command, context) {
2104
+ try {
2105
+ this.logger.info(`Sending command to Claude Code: ${command}`);
2106
+ if (this.claudePath) {
2107
+ try {
2108
+ const result = await (0, import_execa6.execa)(this.claudePath, [
2109
+ "execute",
2110
+ "--command",
2111
+ command
2112
+ ], {
2113
+ timeout: 3e4,
2114
+ reject: false,
2115
+ cwd: context?.workspaceRoot
2116
+ });
2117
+ if (!result.failed) {
2118
+ return result.stdout || "Command executed successfully";
2119
+ }
2120
+ } catch (cliError) {
2121
+ this.logger.debug("Claude CLI failed, trying file system method");
2122
+ }
2123
+ }
2124
+ return await this.sendViaFileSystem(command, context);
2125
+ } catch (error) {
2126
+ this.logger.error(`Error executing Claude Code command:`, error);
2127
+ throw new Error(`Claude Code command failed: ${error instanceof Error ? error.message : String(error)}`);
2128
+ }
2129
+ }
2130
+ async sendViaFileSystem(command, context) {
2131
+ const tempDir = path7.join(os7.tmpdir(), "feishu-bridge", "claude");
2132
+ if (!fs7.existsSync(tempDir)) {
2133
+ fs7.mkdirSync(tempDir, { recursive: true });
2134
+ }
2135
+ const instructionFile = path7.join(tempDir, `cmd_${Date.now()}.json`);
2136
+ const responseFile = path7.join(tempDir, `resp_${Date.now()}.txt`);
2137
+ const instruction = {
2138
+ type: "command",
2139
+ command,
2140
+ context,
2141
+ timestamp: Date.now(),
2142
+ responseFile
2143
+ };
2144
+ fs7.writeFileSync(instructionFile, JSON.stringify(instruction, null, 2));
2145
+ const maxWait = 3e4;
2146
+ const checkInterval = 500;
2147
+ let waited = 0;
2148
+ while (waited < maxWait) {
2149
+ if (fs7.existsSync(responseFile)) {
2150
+ const response = fs7.readFileSync(responseFile, "utf-8");
2151
+ try {
2152
+ fs7.unlinkSync(instructionFile);
2153
+ fs7.unlinkSync(responseFile);
2154
+ } catch {
2155
+ }
2156
+ return response;
2157
+ }
2158
+ await new Promise((resolve) => setTimeout(resolve, checkInterval));
2159
+ waited += checkInterval;
2160
+ }
2161
+ try {
2162
+ fs7.unlinkSync(instructionFile);
2163
+ } catch {
2164
+ }
2165
+ return `Command queued for Claude Code. File: ${instructionFile}`;
2166
+ }
2167
+ async getAIResponse(prompt, context) {
2168
+ try {
2169
+ this.logger.info(`Getting AI response from Claude Code for prompt: ${prompt}`);
2170
+ if (this.claudePath) {
2171
+ try {
2172
+ const result = await (0, import_execa6.execa)(this.claudePath, [
2173
+ "ask",
2174
+ "--prompt",
2175
+ prompt
2176
+ ], {
2177
+ timeout: 6e4,
2178
+ reject: false,
2179
+ cwd: context?.workspaceRoot
2180
+ });
2181
+ if (!result.failed && result.stdout) {
2182
+ return result.stdout;
2183
+ }
2184
+ } catch (cliError) {
2185
+ this.logger.debug("Claude ask CLI failed, using file system");
2186
+ }
2187
+ }
2188
+ return await this.sendAIRequestViaFileSystem(prompt, context);
2189
+ } catch (error) {
2190
+ this.logger.error(`Error getting Claude Code AI response:`, error);
2191
+ throw error;
2192
+ }
2193
+ }
2194
+ async sendAIRequestViaFileSystem(prompt, context) {
2195
+ const tempDir = path7.join(os7.tmpdir(), "feishu-bridge", "claude");
2196
+ if (!fs7.existsSync(tempDir)) {
2197
+ fs7.mkdirSync(tempDir, { recursive: true });
2198
+ }
2199
+ const instructionFile = path7.join(tempDir, `ai_${Date.now()}.json`);
2200
+ const responseFile = path7.join(tempDir, `ai_resp_${Date.now()}.txt`);
2201
+ const instruction = {
2202
+ type: "ai_request",
2203
+ prompt,
2204
+ context,
2205
+ timestamp: Date.now(),
2206
+ responseFile
2207
+ };
2208
+ fs7.writeFileSync(instructionFile, JSON.stringify(instruction, null, 2));
2209
+ const maxWait = 12e4;
2210
+ const checkInterval = 1e3;
2211
+ let waited = 0;
2212
+ while (waited < maxWait) {
2213
+ if (fs7.existsSync(responseFile)) {
2214
+ const response = fs7.readFileSync(responseFile, "utf-8");
2215
+ try {
2216
+ fs7.unlinkSync(instructionFile);
2217
+ fs7.unlinkSync(responseFile);
2218
+ } catch {
2219
+ }
2220
+ return response;
2221
+ }
2222
+ await new Promise((resolve) => setTimeout(resolve, checkInterval));
2223
+ waited += checkInterval;
2224
+ }
2225
+ try {
2226
+ fs7.unlinkSync(instructionFile);
2227
+ } catch {
2228
+ }
2229
+ return `AI request queued. File: ${instructionFile}`;
2230
+ }
2231
+ async generateCode(description, options) {
2232
+ const fullPrompt = this.buildCodePrompt(description, options);
2233
+ return this.getAIResponse(fullPrompt);
2234
+ }
2235
+ async runProgram(command, cwd) {
2236
+ try {
2237
+ const result = await import_execa6.execa.command(command, {
2238
+ cwd: cwd || process.cwd(),
2239
+ shell: true,
2240
+ timeout: 6e4,
2241
+ reject: false
2242
+ });
2243
+ return {
2244
+ stdout: result.stdout,
2245
+ stderr: result.stderr,
2246
+ exitCode: result.exitCode || 0,
2247
+ success: result.exitCode === 0,
2248
+ output: result.stdout,
2249
+ error: result.failed ? result.stderr : void 0
2250
+ };
2251
+ } catch (error) {
2252
+ this.logger.error(`Error running program in Claude Code:`, error);
2253
+ return {
2254
+ stdout: "",
2255
+ stderr: error instanceof Error ? error.message : String(error),
2256
+ exitCode: 1,
2257
+ success: false,
2258
+ error: error instanceof Error ? error.message : String(error)
2259
+ };
2260
+ }
2261
+ }
2262
+ };
2263
+
2264
+ // src/core/health.ts
2265
+ var HealthMonitor = class {
2266
+ constructor(server, sessionManager, adapters) {
2267
+ this.server = server;
2268
+ this.sessionManager = sessionManager;
2269
+ this.adapters = adapters;
2270
+ this.requestCount = { total: 0, successful: 0, failed: 0 };
2271
+ this.startTime = Date.now();
2272
+ this.setupRoutes();
2273
+ }
2274
+ /**
2275
+ * 设置健康检查路由
2276
+ */
2277
+ setupRoutes() {
2278
+ this.server.get("/health", this.handleHealth.bind(this));
2279
+ this.server.get("/health/ready", this.handleReadiness.bind(this));
2280
+ this.server.get("/health/live", this.handleLiveness.bind(this));
2281
+ this.server.get("/metrics", this.handleMetrics.bind(this));
2282
+ }
2283
+ /**
2284
+ * 处理健康检查请求
2285
+ */
2286
+ async handleHealth(_request, reply) {
2287
+ const status = await this.getHealthStatus();
2288
+ const statusCode = status.status === "healthy" ? 200 : status.status === "degraded" ? 200 : 503;
2289
+ reply.status(statusCode).send(status);
2290
+ }
2291
+ /**
2292
+ * 处理就绪检查
2293
+ */
2294
+ async handleReadiness(_request, reply) {
2295
+ const config = ConfigManager.getInstance().get();
2296
+ const isReady = config.feishu.appId && config.feishu.appSecret;
2297
+ if (isReady) {
2298
+ reply.status(200).send({
2299
+ ready: true,
2300
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2301
+ });
2302
+ } else {
2303
+ reply.status(503).send({
2304
+ ready: false,
2305
+ message: "Configuration incomplete",
2306
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2307
+ });
2308
+ }
2309
+ }
2310
+ /**
2311
+ * 处理存活检查
2312
+ */
2313
+ async handleLiveness(_request, reply) {
2314
+ reply.status(200).send({
2315
+ alive: true,
2316
+ uptime: process.uptime(),
2317
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2318
+ });
2319
+ }
2320
+ /**
2321
+ * 处理指标请求
2322
+ */
2323
+ async handleMetrics(_request, reply) {
2324
+ const metrics = await this.getMetrics();
2325
+ const prometheusFormat = this.toPrometheusFormat(metrics);
2326
+ reply.header("Content-Type", "text/plain; version=0.0.4").send(prometheusFormat);
2327
+ }
2328
+ /**
2329
+ * 获取健康状态
2330
+ */
2331
+ async getHealthStatus() {
2332
+ const config = ConfigManager.getInstance().get();
2333
+ const adapterStatuses = {};
2334
+ let availableAdapters = 0;
2335
+ for (const [name, adapter] of this.adapters) {
2336
+ try {
2337
+ const isAvailable = await adapter.checkAvailability();
2338
+ adapterStatuses[name] = isAvailable;
2339
+ if (isAvailable) availableAdapters++;
2340
+ } catch (error) {
2341
+ adapterStatuses[name] = false;
2342
+ }
2343
+ }
2344
+ const totalAdapters = this.adapters.size;
2345
+ const feishuConfigured = !!(config.feishu.appId && config.feishu.appSecret);
2346
+ let status = "healthy";
2347
+ if (!feishuConfigured) {
2348
+ status = "unhealthy";
2349
+ } else if (availableAdapters === 0 && totalAdapters > 0) {
2350
+ status = "degraded";
2351
+ }
2352
+ return {
2353
+ status,
2354
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2355
+ version: process.env.npm_package_version || "1.0.0",
2356
+ checks: {
2357
+ feishu: {
2358
+ status: feishuConfigured ? "ok" : "error",
2359
+ message: feishuConfigured ? void 0 : "App ID or Secret not configured"
2360
+ },
2361
+ adapters: {
2362
+ status: availableAdapters > 0 ? "ok" : "error",
2363
+ available: availableAdapters,
2364
+ total: totalAdapters,
2365
+ details: adapterStatuses
2366
+ },
2367
+ session: {
2368
+ status: "ok",
2369
+ activeSessions: this.sessionManager.getActiveSessionCount()
2370
+ }
2371
+ }
2372
+ };
2373
+ }
2374
+ /**
2375
+ * 获取指标数据
2376
+ */
2377
+ async getMetrics() {
2378
+ const memUsage = process.memoryUsage();
2379
+ const adapterMetrics = {};
2380
+ for (const [name, adapter] of this.adapters) {
2381
+ try {
2382
+ const isAvailable = await adapter.checkAvailability();
2383
+ adapterMetrics[name] = {
2384
+ available: isAvailable,
2385
+ lastCheck: (/* @__PURE__ */ new Date()).toISOString()
2386
+ };
2387
+ } catch (error) {
2388
+ adapterMetrics[name] = {
2389
+ available: false,
2390
+ lastCheck: (/* @__PURE__ */ new Date()).toISOString()
2391
+ };
2392
+ }
2393
+ }
2394
+ return {
2395
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2396
+ uptime: process.uptime(),
2397
+ memory: {
2398
+ used: Math.round(memUsage.heapUsed / 1024 / 1024),
2399
+ // MB
2400
+ total: Math.round(memUsage.heapTotal / 1024 / 1024),
2401
+ external: Math.round(memUsage.external / 1024 / 1024)
2402
+ },
2403
+ sessions: {
2404
+ active: this.sessionManager.getActiveSessionCount(),
2405
+ total: 0
2406
+ // 可以通过SessionManager扩展获取
2407
+ },
2408
+ requests: { ...this.requestCount },
2409
+ adapters: adapterMetrics
2410
+ };
2411
+ }
2412
+ /**
2413
+ * 转换为Prometheus格式
2414
+ */
2415
+ toPrometheusFormat(metrics) {
2416
+ const lines = [];
2417
+ lines.push("# HELP feishu_bridge_uptime_seconds Process uptime in seconds");
2418
+ lines.push("# TYPE feishu_bridge_uptime_seconds gauge");
2419
+ lines.push(`feishu_bridge_uptime_seconds ${metrics.uptime}`);
2420
+ lines.push("# HELP feishu_bridge_memory_usage_mb Memory usage in MB");
2421
+ lines.push("# TYPE feishu_bridge_memory_usage_mb gauge");
2422
+ lines.push(`feishu_bridge_memory_usage_mb{type="used"} ${metrics.memory.used}`);
2423
+ lines.push(`feishu_bridge_memory_usage_mb{type="total"} ${metrics.memory.total}`);
2424
+ lines.push(`feishu_bridge_memory_usage_mb{type="external"} ${metrics.memory.external}`);
2425
+ lines.push("# HELP feishu_bridge_active_sessions Number of active sessions");
2426
+ lines.push("# TYPE feishu_bridge_active_sessions gauge");
2427
+ lines.push(`feishu_bridge_active_sessions ${metrics.sessions.active}`);
2428
+ lines.push("# HELP feishu_bridge_requests_total Total number of requests");
2429
+ lines.push("# TYPE feishu_bridge_requests_total counter");
2430
+ lines.push(`feishu_bridge_requests_total ${metrics.requests.total}`);
2431
+ lines.push("# HELP feishu_bridge_requests_successful Total successful requests");
2432
+ lines.push("# TYPE feishu_bridge_requests_successful counter");
2433
+ lines.push(`feishu_bridge_requests_successful ${metrics.requests.successful}`);
2434
+ lines.push("# HELP feishu_bridge_requests_failed Total failed requests");
2435
+ lines.push("# TYPE feishu_bridge_requests_failed counter");
2436
+ lines.push(`feishu_bridge_requests_failed ${metrics.requests.failed}`);
2437
+ lines.push("# HELP feishu_bridge_adapter_available Adapter availability (1=available, 0=unavailable)");
2438
+ lines.push("# TYPE feishu_bridge_adapter_available gauge");
2439
+ for (const [name, data] of Object.entries(metrics.adapters)) {
2440
+ const value = data.available ? 1 : 0;
2441
+ lines.push(`feishu_bridge_adapter_available{name="${name}"} ${value}`);
2442
+ }
2443
+ return lines.join("\n");
2444
+ }
2445
+ /**
2446
+ * 记录请求
2447
+ */
2448
+ recordRequest(success) {
2449
+ this.requestCount.total++;
2450
+ if (success) {
2451
+ this.requestCount.successful++;
2452
+ } else {
2453
+ this.requestCount.failed++;
2454
+ }
2455
+ }
2456
+ };
2457
+
2458
+ // src/core/reverse-channel.ts
2459
+ var fs8 = __toESM(require("fs"));
2460
+ var path8 = __toESM(require("path"));
2461
+ var os8 = __toESM(require("os"));
2462
+ var import_events = require("events");
2463
+ var ReverseChannel = class extends import_events.EventEmitter {
2464
+ constructor(feishuClient) {
2465
+ super();
2466
+ this.isRunning = false;
2467
+ this.checkInterval = null;
2468
+ this.messageQueue = [];
2469
+ this.feishuClient = feishuClient;
2470
+ const bridgeConfig = ConfigManager.getInstance().get();
2471
+ this.config = {
2472
+ enabled: bridgeConfig.behavior?.reverseChannelEnabled ?? true,
2473
+ mode: bridgeConfig.behavior?.reverseChannelMode ?? "filesystem",
2474
+ checkInterval: 1e3,
2475
+ // 1秒检查一次
2476
+ maxMessageAge: 5 * 60 * 1e3
2477
+ // 5分钟过期
2478
+ };
2479
+ this.tempDir = path8.join(os8.tmpdir(), "feishu-bridge", "reverse-channel");
2480
+ this.ensureTempDir();
2481
+ }
2482
+ /**
2483
+ * 启动反向通信通道
2484
+ */
2485
+ start() {
2486
+ if (this.isRunning || !this.config.enabled) {
2487
+ return;
2488
+ }
2489
+ this.isRunning = true;
2490
+ logger.info("Starting reverse communication channel...");
2491
+ switch (this.config.mode) {
2492
+ case "filesystem":
2493
+ this.startFileSystemListener();
2494
+ break;
2495
+ case "websocket":
2496
+ this.startWebSocketListener();
2497
+ break;
2498
+ case "http":
2499
+ this.startHttpListener();
2500
+ break;
2501
+ }
2502
+ this.startMessageQueueProcessor();
2503
+ }
2504
+ /**
2505
+ * 停止反向通信通道
2506
+ */
2507
+ stop() {
2508
+ if (!this.isRunning) {
2509
+ return;
2510
+ }
2511
+ this.isRunning = false;
2512
+ logger.info("Stopping reverse communication channel...");
2513
+ if (this.checkInterval) {
2514
+ clearInterval(this.checkInterval);
2515
+ this.checkInterval = null;
2516
+ }
2517
+ }
2518
+ /**
2519
+ * 发送消息到飞书(供适配器调用)
2520
+ */
2521
+ async sendToFeishu(message) {
2522
+ try {
2523
+ this.messageQueue.push(message);
2524
+ this.emit("messageQueued", message);
2525
+ return true;
2526
+ } catch (error) {
2527
+ logger.error("Error queuing message to Feishu:", error);
2528
+ return false;
2529
+ }
2530
+ }
2531
+ /**
2532
+ * 发送文件系统消息(供IDE写入文件后调用)
2533
+ */
2534
+ async sendFileSystemMessage(adapterType, content, chatId) {
2535
+ const message = {
2536
+ type: "message",
2537
+ content,
2538
+ chatId,
2539
+ timestamp: Date.now()
2540
+ };
2541
+ const messageFile = path8.join(
2542
+ this.tempDir,
2543
+ `${adapterType}_msg_${Date.now()}.json`
2544
+ );
2545
+ fs8.writeFileSync(messageFile, JSON.stringify(message, null, 2));
2546
+ logger.debug(`File system message written: ${messageFile}`);
2547
+ }
2548
+ ensureTempDir() {
2549
+ if (!fs8.existsSync(this.tempDir)) {
2550
+ fs8.mkdirSync(this.tempDir, { recursive: true });
2551
+ }
2552
+ }
2553
+ startFileSystemListener() {
2554
+ logger.info("Starting file system listener for reverse channel...");
2555
+ this.checkInterval = setInterval(() => {
2556
+ this.processFileSystemMessages();
2557
+ }, this.config.checkInterval);
2558
+ }
2559
+ processFileSystemMessages() {
2560
+ if (!fs8.existsSync(this.tempDir)) {
2561
+ return;
2562
+ }
2563
+ try {
2564
+ const files = fs8.readdirSync(this.tempDir);
2565
+ const now = Date.now();
2566
+ for (const file of files) {
2567
+ if (!file.endsWith(".json")) continue;
2568
+ const filePath = path8.join(this.tempDir, file);
2569
+ try {
2570
+ const content = fs8.readFileSync(filePath, "utf-8");
2571
+ const message = JSON.parse(content);
2572
+ if (now - message.timestamp > this.config.maxMessageAge) {
2573
+ fs8.unlinkSync(filePath);
2574
+ continue;
2575
+ }
2576
+ this.messageQueue.push(message);
2577
+ fs8.unlinkSync(filePath);
2578
+ logger.debug(`Processed file system message: ${file}`);
2579
+ } catch (error) {
2580
+ logger.error(`Error processing message file ${file}:`, error);
2581
+ try {
2582
+ fs8.unlinkSync(filePath);
2583
+ } catch {
2584
+ }
2585
+ }
2586
+ }
2587
+ } catch (error) {
2588
+ logger.error("Error reading message directory:", error);
2589
+ }
2590
+ }
2591
+ startWebSocketListener() {
2592
+ logger.info("WebSocket reverse channel not yet implemented, falling back to file system");
2593
+ this.startFileSystemListener();
2594
+ }
2595
+ startHttpListener() {
2596
+ logger.info("HTTP reverse channel not yet implemented, falling back to file system");
2597
+ this.startFileSystemListener();
2598
+ }
2599
+ startMessageQueueProcessor() {
2600
+ setInterval(async () => {
2601
+ await this.processMessageQueue();
2602
+ }, 500);
2603
+ }
2604
+ async processMessageQueue() {
2605
+ if (this.messageQueue.length === 0) {
2606
+ return;
2607
+ }
2608
+ const messagesToProcess = [...this.messageQueue];
2609
+ this.messageQueue = [];
2610
+ for (const message of messagesToProcess) {
2611
+ try {
2612
+ await this.deliverMessageToFeishu(message);
2613
+ this.emit("messageDelivered", message);
2614
+ } catch (error) {
2615
+ logger.error("Error delivering message to Feishu:", error);
2616
+ if (!message.retryCount || message.retryCount < 3) {
2617
+ message.retryCount = (message.retryCount || 0) + 1;
2618
+ this.messageQueue.push(message);
2619
+ }
2620
+ }
2621
+ }
2622
+ }
2623
+ async deliverMessageToFeishu(message) {
2624
+ const { type, content, chatId, metadata } = message;
2625
+ if (!chatId) {
2626
+ logger.warn("No chatId specified for message, cannot deliver");
2627
+ return;
2628
+ }
2629
+ let formattedContent = content;
2630
+ switch (type) {
2631
+ case "notification":
2632
+ formattedContent = `\u{1F514} ${content}`;
2633
+ break;
2634
+ case "status":
2635
+ formattedContent = `\u{1F4CA} ${content}`;
2636
+ break;
2637
+ case "result":
2638
+ formattedContent = `\u2705 ${content}`;
2639
+ break;
2640
+ default:
2641
+ formattedContent = content;
2642
+ }
2643
+ const maxLength = 2e3;
2644
+ if (formattedContent.length > maxLength) {
2645
+ formattedContent = formattedContent.substring(0, maxLength) + "\n...(truncated)";
2646
+ }
2647
+ await this.feishuClient.sendMessage(chatId, {
2648
+ msg_type: "text",
2649
+ content: {
2650
+ text: formattedContent
2651
+ }
2652
+ });
2653
+ logger.info(`Message delivered to Feishu chat ${chatId}`);
2654
+ }
2655
+ isActive() {
2656
+ return this.isRunning;
2657
+ }
2658
+ getQueueSize() {
2659
+ return this.messageQueue.length;
2660
+ }
2661
+ };
2662
+
2663
+ // src/core/bridge.ts
2664
+ var import_fastify = __toESM(require("fastify"));
2665
+ var FeishuBridge = class {
2666
+ constructor() {
2667
+ this.wsHandler = null;
2668
+ this.healthMonitor = null;
2669
+ this.reverseChannel = null;
2670
+ this.adapters = /* @__PURE__ */ new Map();
2671
+ this.server = (0, import_fastify.default)({
2672
+ logger: false
2673
+ // 使用我们自己的日志系统
2674
+ });
2675
+ const config = ConfigManager.getInstance().get();
2676
+ this.feishuClient = new FeishuClient(config.feishu);
2677
+ this.sessionManager = new SessionManager(config.behavior.sessionTimeout);
2678
+ this.reverseChannel = new ReverseChannel(this.feishuClient);
2679
+ this.initAdapters();
2680
+ this.commandExecutor = new CommandExecutor(
2681
+ this.adapters,
2682
+ this.sessionManager
2683
+ );
2684
+ this.webhookHandler = new FeishuWebhookHandler(
2685
+ this.feishuClient,
2686
+ this.commandExecutor
2687
+ );
2688
+ }
2689
+ initAdapters() {
2690
+ const config = ConfigManager.getInstance().get();
2691
+ if (config.adapters.vscode?.enabled) {
2692
+ const adapter = new VSCodeAdapter();
2693
+ if (this.reverseChannel) adapter.setReverseChannel(this.reverseChannel);
2694
+ this.adapters.set("vscode", adapter);
2695
+ }
2696
+ if (config.adapters.cursor?.enabled) {
2697
+ const adapter = new CursorAdapter();
2698
+ if (this.reverseChannel) adapter.setReverseChannel(this.reverseChannel);
2699
+ this.adapters.set("cursor", adapter);
2700
+ }
2701
+ if (config.adapters.trae?.enabled) {
2702
+ const adapter = new TraeAdapter();
2703
+ if (this.reverseChannel) adapter.setReverseChannel(this.reverseChannel);
2704
+ this.adapters.set("trae", adapter);
2705
+ }
2706
+ if (config.adapters.antigravity?.enabled) {
2707
+ const adapter = new AntigravityAdapter();
2708
+ if (this.reverseChannel) adapter.setReverseChannel(this.reverseChannel);
2709
+ this.adapters.set("antigravity", adapter);
2710
+ }
2711
+ if (config.adapters.kiro?.enabled) {
2712
+ const adapter = new KiroAdapter();
2713
+ if (this.reverseChannel) adapter.setReverseChannel(this.reverseChannel);
2714
+ this.adapters.set("kiro", adapter);
2715
+ }
2716
+ if (config.adapters.opencode?.enabled) {
2717
+ const adapter = new OpenCodeAdapter();
2718
+ if (this.reverseChannel) adapter.setReverseChannel(this.reverseChannel);
2719
+ this.adapters.set("opencode", adapter);
2720
+ }
2721
+ if (config.adapters["claude-code"]?.enabled) {
2722
+ const adapter = new ClaudeCodeAdapter();
2723
+ if (this.reverseChannel) adapter.setReverseChannel(this.reverseChannel);
2724
+ this.adapters.set("claude", adapter);
2725
+ }
2726
+ }
2727
+ async start() {
2728
+ const config = ConfigManager.getInstance().get();
2729
+ const serverConfig = config.server;
2730
+ if (this.reverseChannel) {
2731
+ this.reverseChannel.start();
2732
+ logger.info("Reverse communication channel started");
2733
+ }
2734
+ if (config.feishu.connectionMode !== "websocket") {
2735
+ this.healthMonitor = new HealthMonitor(
2736
+ this.server,
2737
+ this.sessionManager,
2738
+ this.adapters
2739
+ );
2740
+ }
2741
+ if (config.feishu.connectionMode === "websocket") {
2742
+ logger.info("Starting in WebSocket mode...");
2743
+ this.wsHandler = new FeishuWebSocketHandler(
2744
+ this.feishuClient,
2745
+ this.commandExecutor
2746
+ );
2747
+ await this.wsHandler.start();
2748
+ logger.info("WebSocket connection established");
2749
+ } else {
2750
+ logger.info("Starting in Webhook mode...");
2751
+ this.server.post(serverConfig.webhookPath, async (request, reply) => {
2752
+ await this.webhookHandler.handleWebhook(request, reply);
2753
+ });
2754
+ try {
2755
+ const address = await this.server.listen({
2756
+ port: serverConfig.port,
2757
+ host: serverConfig.host
2758
+ });
2759
+ logger.info(`Feishu Bridge server running on ${address}`);
2760
+ logger.info(`Webhook endpoint: ${serverConfig.webhookPath}`);
2761
+ logger.info(`Health check endpoint: /health`);
2762
+ logger.info(`Metrics endpoint: /metrics`);
2763
+ } catch (err) {
2764
+ logger.error(err instanceof Error ? err : String(err));
2765
+ process.exit(1);
2766
+ }
2767
+ }
2768
+ }
2769
+ async stop() {
2770
+ if (this.reverseChannel) {
2771
+ this.reverseChannel.stop();
2772
+ logger.info("Reverse communication channel stopped");
2773
+ }
2774
+ if (this.wsHandler) {
2775
+ await this.wsHandler.stop();
2776
+ this.wsHandler = null;
2777
+ }
2778
+ await this.server.close();
2779
+ logger.info("Feishu Bridge server stopped");
2780
+ }
2781
+ getAdapter(adapterName) {
2782
+ return this.adapters.get(adapterName);
2783
+ }
2784
+ getAdapters() {
2785
+ return this.adapters;
2786
+ }
2787
+ };
2788
+
2789
+ // src/index.ts
2790
+ async function main() {
2791
+ try {
2792
+ const configManager = ConfigManager.getInstance();
2793
+ const config = { ...DEFAULT_CONFIG };
2794
+ const appId = process.env.FEISHU_APP_ID;
2795
+ const appSecret = process.env.FEISHU_APP_SECRET;
2796
+ if (appId && appSecret) {
2797
+ config.feishu.appId = appId;
2798
+ config.feishu.appSecret = appSecret;
2799
+ } else {
2800
+ logger.warn("FEISHU_APP_ID and/or FEISHU_APP_SECRET not set in environment variables");
2801
+ }
2802
+ configManager.load(config);
2803
+ const bridge = new FeishuBridge();
2804
+ await bridge.start();
2805
+ process.on("SIGINT", async () => {
2806
+ logger.info("Received SIGINT, shutting down gracefully...");
2807
+ await bridge.stop();
2808
+ process.exit(0);
2809
+ });
2810
+ process.on("SIGTERM", async () => {
2811
+ logger.info("Received SIGTERM, shutting down gracefully...");
2812
+ await bridge.stop();
2813
+ process.exit(0);
2814
+ });
2815
+ } catch (error) {
2816
+ logger.error("Failed to start Feishu Bridge:", error);
2817
+ process.exit(1);
2818
+ }
2819
+ }
2820
+ if (require.main === module) {
2821
+ main().catch((error) => {
2822
+ console.error("Unhandled error:", error);
2823
+ process.exit(1);
2824
+ });
2825
+ }
2826
+ // Annotate the CommonJS export names for ESM import in node:
2827
+ 0 && (module.exports = {
2828
+ ConfigManager,
2829
+ FeishuBridge,
2830
+ logger
2831
+ });
2832
+ //# sourceMappingURL=index.js.map