cursor-feishu 1.0.5 → 1.1.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/README.md CHANGED
@@ -4,9 +4,18 @@
4
4
 
5
5
  **Cursor 飞书集成** — 通过飞书 WebSocket 长连接将飞书消息接入 Cursor Headless CLI。
6
6
 
7
+ ## 传输模式(`FEISHU_TRANSPORT` / `createFeishuService({ transport })`)
8
+
9
+ | 值 | 说明 |
10
+ |----|------|
11
+ | `http`(默认) | 由宿主(如 Express)提供 Webhook;飞书选「发送到开发者服务器」,需公网/ngrok |
12
+ | `ws` | `Lark.WSClient` 长连接;飞书选「使用长连接接收事件」,**无需**配置请求地址 |
13
+ | `both` | 同时启用 WS + HTTP;请勿在飞书对同一事件重复订阅,否则依赖包内 message_id 去重 |
14
+
7
15
  ## 特性
8
16
 
9
- - 🚀 **WebSocket 长连接** — 实时接收飞书消息,无需 Webhook 配置
17
+ - 🚀 **WebSocket 长连接** — `transport: ws` 时实时收消息,无需公网 Webhook
18
+ - 🌐 **HTTP Webhook** — `transport: http` 时由宿主提供 POST 回调
10
19
  - 🤖 **多媒体支持** — 图片、文件、音频、富文本消息自动处理
11
20
  - 👥 **智能群聊** — 仅 @提及时回复,其他消息静默监听作为上下文
12
21
  - 💬 **流式响应** — 支持实时更新消息(流式输出)
@@ -47,7 +56,9 @@ npm install cursor-feishu
47
56
 
48
57
  1. **添加机器人能力**
49
58
  2. **事件订阅** — 添加 `im.message.receive_v1` 和 `im.chat.member.bot.added_v1`
50
- 3. **订阅方式** 选择「使用长连接接收事件/回调」
59
+ 3. **订阅方式**(与 `FEISHU_TRANSPORT` 一致)
60
+ - 使用 **`ws`**:选「**使用长连接接收事件**」
61
+ - 使用 **`http`**:选「**发送到开发者服务器**」并填写你的 Webhook URL
51
62
  4. **权限** — 开通 `im:message`、`im:message:send_as_bot`、`im:chat`
52
63
  5. **发布应用**
53
64
 
@@ -57,6 +68,7 @@ npm install cursor-feishu
57
68
  import { createFeishuService } from 'cursor-feishu'
58
69
 
59
70
  const service = await createFeishuService({
71
+ transport: process.env.FEISHU_TRANSPORT === 'ws' ? 'ws' : 'http', // 或 'both'
60
72
  onMessage: async (msgCtx) => {
61
73
  console.log(`收到消息: ${msgCtx.content}`)
62
74
  // 调用 cursor-agent 处理消息
@@ -84,6 +96,10 @@ await service.run()
84
96
  interface FeishuServiceOptions {
85
97
  /** 飞书配置,可从 ~/.config/cursor/plugins/feishu.json 自动加载 */
86
98
  config?: Partial<ResolvedConfig>
99
+ appId?: string
100
+ appSecret?: string
101
+ /** `http` | `ws` | `both`,默认读 `FEISHU_TRANSPORT` */
102
+ transport?: 'http' | 'ws' | 'both'
87
103
  /** 消息处理回调 */
88
104
  onMessage?: (msgCtx: FeishuMessageContext) => Promise<void>
89
105
  /** Bot 入群回调 */
@@ -99,6 +115,7 @@ interface FeishuServiceOptions {
99
115
 
100
116
  ```typescript
101
117
  interface FeishuService {
118
+ readonly transport: 'http' | 'ws' | 'both'
102
119
  run: () => Promise<void> // 启动并运行服务
103
120
  shutdown: () => Promise<void> // 关闭服务
104
121
  getSender: () => FeishuSender // 获取消息发送器
package/dist/index.d.ts CHANGED
@@ -91,10 +91,10 @@ declare class FeishuSender {
91
91
  declare function createSender(larkClient: InstanceType<typeof Lark.Client>, log: LogFn): FeishuSender;
92
92
 
93
93
  /**
94
- * Feishu 事件网关 使用 HTTP Webhook 接收飞书事件
94
+ * Feishu HTTP 模式占位说明(实际收事件由宿主如 Express 解析后调 onMessage)
95
95
  *
96
- * 注意: cursor-feishu 1.0.x 已弃用 WebSocket 方式,改用 HTTP Webhook。
97
- * WebSocket 长连接需要 Lark SDK 更高版本支持。
96
+ * WebSocket 长连接见 `gateway-ws.ts`(`startFeishuWebSocketGateway`)。
97
+ * 双模式由环境变量 `FEISHU_TRANSPORT` `createFeishuService({ transport })` 控制。
98
98
  */
99
99
 
100
100
  interface FeishuGatewayResult {
@@ -108,13 +108,32 @@ interface GatewayOptions {
108
108
  log: LogFn;
109
109
  }
110
110
  /**
111
- * 启动飞书事件网关(Webhook 模式)
112
- *
113
- * 使用 HTTP Webhook 方式接收飞书事件,而不是 WebSocket。
114
- * 这是 Lark SDK 原生支持的方式。
111
+ * HTTP/Webhook 占位:事件由宿主(如 cursor-agent 的 Express)解析后调用 onMessage。
112
+ * 与 `startFeishuWebSocketGateway` 二选一或并存(双模式时飞书勿对同一事件重复订阅)。
115
113
  */
116
114
  declare function startFeishuGateway(options: GatewayOptions): Promise<FeishuGatewayResult>;
117
115
 
116
+ /**
117
+ * 飞书 WebSocket 长连接:EventDispatcher + WSClient(@larksuiteoapi/node-sdk)
118
+ * 飞书开放平台需选择「使用长连接接收事件」,无需公网 Webhook URL。
119
+ */
120
+
121
+ interface FeishuWebSocketGatewayOptions {
122
+ config: ResolvedConfig;
123
+ /** 复用 token 的 Client(WS 仍用 appId/appSecret 建连,此参数预留与 sender 一致) */
124
+ larkClient: InstanceType<typeof Lark.Client>;
125
+ botOpenId: string;
126
+ handlers: GatewayHandlers;
127
+ log: LogFn;
128
+ }
129
+ interface FeishuWebSocketGatewayResult {
130
+ shutdown: () => Promise<void>;
131
+ }
132
+ /**
133
+ * 启动飞书 WebSocket 长连接网关
134
+ */
135
+ declare function startFeishuWebSocketGateway(options: FeishuWebSocketGatewayOptions): FeishuWebSocketGatewayResult;
136
+
118
137
  /**
119
138
  * Cursor 飞书集成主模块
120
139
  *
@@ -134,9 +153,21 @@ declare function startFeishuGateway(options: GatewayOptions): Promise<FeishuGate
134
153
  * ```
135
154
  */
136
155
 
156
+ /** 飞书事件传输方式 */
157
+ type FeishuTransport = "ws" | "http" | "both";
137
158
  interface FeishuServiceOptions {
138
159
  /** 飞书配置,可从 ~/.config/cursor/plugins/feishu.json 自动加载 */
139
160
  config?: Partial<ResolvedConfig>;
161
+ /** 与 config 二选一:直接传 appId / appSecret */
162
+ appId?: string;
163
+ appSecret?: string;
164
+ /**
165
+ * - `http`:仅 HTTP Webhook(宿主提供 Express 等)
166
+ * - `ws`:仅 WebSocket 长连接(飞书选「长连接」,无需公网 URL)
167
+ * - `both`:同时启用(勿在飞书重复订阅同一事件,否则需依赖去重)
168
+ * 默认读环境变量 `FEISHU_TRANSPORT`,未设置时为 `http`
169
+ */
170
+ transport?: FeishuTransport;
140
171
  /** 消息处理回调 */
141
172
  onMessage?: (msgCtx: FeishuMessageContext) => Promise<void>;
142
173
  /** Bot 入群回调 */
@@ -147,7 +178,9 @@ interface FeishuServiceOptions {
147
178
  log?: LogFn;
148
179
  }
149
180
  interface FeishuService {
150
- /** 启动并运行服务 */
181
+ /** 当前传输模式 */
182
+ readonly transport: FeishuTransport;
183
+ /** 启动并运行服务(阻塞;纯 ws 时 WS 已在 create 阶段启动,此处仅挂起) */
151
184
  run: () => Promise<void>;
152
185
  /** 关闭服务 */
153
186
  shutdown: () => Promise<void>;
@@ -161,4 +194,4 @@ interface FeishuService {
161
194
  */
162
195
  declare function createFeishuService(options: FeishuServiceOptions): Promise<FeishuService>;
163
196
 
164
- export { type FeishuMessageContext, type FeishuService, type FeishuServiceOptions, type LogFn, type ResolvedConfig, createFeishuService, createSender, startFeishuGateway };
197
+ export { type FeishuMessageContext, type FeishuService, type FeishuServiceOptions, type FeishuTransport, type LogFn, type ResolvedConfig, createFeishuService, createSender, startFeishuGateway, startFeishuWebSocketGateway };
package/dist/index.js CHANGED
@@ -26,91 +26,168 @@ var FeishuConfigSchema = z.object({
26
26
  });
27
27
 
28
28
  // src/feishu/gateway.ts
29
- import * as Lark from "@larksuiteoapi/node-sdk";
30
29
  async function startFeishuGateway(options) {
31
- const { config, larkClient, botOpenId, handlers, log } = options;
32
- log("info", "\u542F\u52A8\u98DE\u4E66\u4E8B\u4EF6\u7F51\u5173\uFF08Webhook \u6A21\u5F0F\uFF09");
33
- const eventDispatcher = new Lark.EventDispatcher({
34
- verificationToken: process.env.FEISHU_VERIFICATION_TOKEN,
35
- encryptKey: process.env.FEISHU_ENCRYPT_KEY
36
- });
37
- eventDispatcher.register({
38
- // 处理消息事件
39
- "im.message.message_create_v1": async (data) => {
30
+ const { log } = options;
31
+ log("info", "\u98DE\u4E66 HTTP \u6A21\u5F0F\uFF1A\u7531\u5BBF\u4E3B\u63D0\u4F9B Webhook\uFF08\u5982 POST /webhook/feishu\uFF09");
32
+ return {
33
+ shutdown: async () => {
34
+ log("info", "\u5173\u95ED\u98DE\u4E66 HTTP \u5360\u4F4D\u7F51\u5173");
35
+ }
36
+ };
37
+ }
38
+
39
+ // src/feishu/gateway-ws.ts
40
+ import * as Lark from "@larksuiteoapi/node-sdk";
41
+ import { HttpsProxyAgent } from "https-proxy-agent";
42
+
43
+ // src/feishu/dedup.ts
44
+ var seen = /* @__PURE__ */ new Map();
45
+ var DEFAULT_TTL_MS = 2 * 60 * 1e3;
46
+ function prune(now, ttl) {
47
+ for (const [id, t] of seen) {
48
+ if (now - t > ttl) seen.delete(id);
49
+ }
50
+ }
51
+ function isDuplicateMessageId(messageId, ttlMs = DEFAULT_TTL_MS) {
52
+ if (!messageId) return false;
53
+ const now = Date.now();
54
+ prune(now, ttlMs);
55
+ if (seen.has(messageId)) return true;
56
+ seen.set(messageId, now);
57
+ return false;
58
+ }
59
+
60
+ // src/feishu/gateway-ws.ts
61
+ function extractTextContent(message) {
62
+ try {
63
+ if (message.message_type === "text") {
64
+ const content = JSON.parse(message.content || "{}");
65
+ return content.text || "";
66
+ }
67
+ } catch {
68
+ }
69
+ return "";
70
+ }
71
+ function computeShouldReply(chatType, message, botOpenId) {
72
+ if (chatType === "p2p") return true;
73
+ const mentions = message.mentions;
74
+ if (Array.isArray(mentions) && mentions.length > 0) {
75
+ return mentions.some((m) => m?.id?.open_id === botOpenId);
76
+ }
77
+ if (message.message_type === "text") {
78
+ try {
79
+ const content = JSON.parse(message.content || "{}");
80
+ const text = content.text || "";
81
+ return text.includes(`<at user_id="${botOpenId}">`) || text.includes(`<at open_id="${botOpenId}">`);
82
+ } catch {
83
+ return false;
84
+ }
85
+ }
86
+ return false;
87
+ }
88
+ function startFeishuWebSocketGateway(options) {
89
+ const { config, botOpenId, handlers, log } = options;
90
+ const { appId, appSecret } = config;
91
+ const proxyUrl = process.env.HTTPS_PROXY || process.env.HTTP_PROXY || process.env.ALL_PROXY || "";
92
+ let wsAgent;
93
+ if (proxyUrl) {
94
+ wsAgent = new HttpsProxyAgent(proxyUrl);
95
+ log("info", "\u98DE\u4E66 WS \u5DF2\u542F\u7528\u4EE3\u7406", { proxy: proxyUrl });
96
+ }
97
+ const dispatcher = new Lark.EventDispatcher({}).register({
98
+ "im.message.receive_v1": async (data) => {
40
99
  try {
41
- const event = data.header?.event_id ? data : { detail: { event: data } };
42
- const message = event.detail?.event?.message || event.message;
100
+ const message = data.message;
43
101
  if (!message) return;
44
- const msgCtx = {
45
- chatId: event.detail?.event?.chat_id || event.chat_id,
46
- messageId: message.message_id,
47
- messageType: message.message_type,
48
- content: extractTextContent(message),
102
+ const chatId = message.chat_id;
103
+ if (!chatId) return;
104
+ const messageId = message.message_id;
105
+ if (isDuplicateMessageId(messageId)) return;
106
+ const messageType = message.message_type || "text";
107
+ let text = extractTextContent(message);
108
+ if (messageType === "text") {
109
+ text = text.replace(/@_user_\d+\s*/g, "").trim();
110
+ }
111
+ if (!text) return;
112
+ const chatType = message.chat_type === "group" ? "group" : "p2p";
113
+ const shouldReply = computeShouldReply(chatType, message, botOpenId);
114
+ const sender = data.sender;
115
+ const senderId = sender?.sender_id?.open_id || sender?.sender_id?.user_id || "";
116
+ const ctx = {
117
+ chatId,
118
+ messageId: messageId || "",
119
+ messageType,
120
+ content: text,
49
121
  rawContent: message.content || "{}",
50
- chatType: event.detail?.event?.chat_type || event.chat_type || "p2p",
51
- senderId: event.detail?.event?.sender?.sender_id?.user_id || event.sender?.sender_id?.user_id || "",
122
+ chatType,
123
+ senderId,
52
124
  rootId: message.root_id,
53
125
  createTime: message.create_time,
54
- shouldReply: shouldReply(event.detail?.event || event, botOpenId)
126
+ shouldReply
55
127
  };
128
+ log("info", "WS \u6536\u5230\u98DE\u4E66\u6D88\u606F", {
129
+ chatId,
130
+ messageId: messageId || "",
131
+ chatType,
132
+ shouldReply,
133
+ preview: text.slice(0, 80)
134
+ });
56
135
  if (handlers.onMessage) {
57
- await handlers.onMessage(msgCtx);
136
+ await handlers.onMessage(ctx);
58
137
  }
59
138
  } catch (err) {
60
- log("error", "\u5904\u7406\u6D88\u606F\u4E8B\u4EF6\u5931\u8D25", {
139
+ log("error", "WS \u5904\u7406\u6D88\u606F\u5931\u8D25", {
61
140
  error: err instanceof Error ? err.message : String(err)
62
141
  });
63
142
  }
64
143
  },
65
- // 处理 bot 入群事件
66
- "im.chat.member_bot_added_v1": async (data) => {
144
+ "im.chat.member.bot.added_v1": async (data) => {
67
145
  try {
68
- const event = data.detail?.event || data;
69
- const chatId = event.chat_id;
146
+ const chatId = data.chat_id || data.detail?.event?.chat_id;
70
147
  if (chatId && handlers.onBotAdded) {
148
+ log("info", "WS: Bot \u5165\u7FA4", { chatId });
71
149
  await handlers.onBotAdded(chatId);
72
150
  }
73
151
  } catch (err) {
74
- log("error", "\u5904\u7406 bot \u5165\u7FA4\u4E8B\u4EF6\u5931\u8D25", {
152
+ log("error", "WS \u5904\u7406\u5165\u7FA4\u5931\u8D25", {
75
153
  error: err instanceof Error ? err.message : String(err)
76
154
  });
77
155
  }
78
156
  }
79
157
  });
80
- log("info", "\u98DE\u4E66\u4E8B\u4EF6\u7F51\u5173\u5DF2\u542F\u52A8\uFF08Webhook \u6A21\u5F0F\uFF09");
81
- log("warn", "\u6CE8\u610F: Webhook \u6A21\u5F0F\u9700\u8981\u914D\u7F6E\u98DE\u4E66\u5E94\u7528\u7684\u56DE\u8C03 URL");
82
- log("info", "\u8BF7\u5728\u98DE\u4E66\u5F00\u653E\u5E73\u53F0\u914D\u7F6E: POST http://your-domain/webhook/feishu");
158
+ const logLevelMap = {
159
+ fatal: Lark.LoggerLevel.fatal,
160
+ error: Lark.LoggerLevel.error,
161
+ warn: Lark.LoggerLevel.warn,
162
+ info: Lark.LoggerLevel.info,
163
+ debug: Lark.LoggerLevel.debug,
164
+ trace: Lark.LoggerLevel.trace
165
+ };
166
+ const wsClient = new Lark.WSClient({
167
+ appId,
168
+ appSecret,
169
+ domain: Lark.Domain.Feishu,
170
+ ...wsAgent ? { agent: wsAgent } : {},
171
+ loggerLevel: logLevelMap[config.logLevel] ?? Lark.LoggerLevel.info,
172
+ logger: {
173
+ error: (...msg) => log("error", "[lark.ws]", { msg }),
174
+ warn: (...msg) => log("warn", "[lark.ws]", { msg }),
175
+ info: (...msg) => log("info", "[lark.ws]", { msg }),
176
+ debug: (...msg) => log("info", "[lark.ws]", { msg }),
177
+ trace: (...msg) => log("info", "[lark.ws]", { msg })
178
+ }
179
+ });
180
+ wsClient.start({ eventDispatcher: dispatcher });
181
+ log("info", "\u98DE\u4E66 WebSocket \u957F\u8FDE\u63A5\u5DF2\u542F\u52A8", { appIdPrefix: appId.slice(0, 8) + "..." });
182
+ log("info", "\u98DE\u4E66\u4FA7\u8BF7\u9009\u62E9\u300C\u4F7F\u7528\u957F\u8FDE\u63A5\u63A5\u6536\u4E8B\u4EF6\u300D\uFF0C\u65E0\u9700\u914D\u7F6E\u8BF7\u6C42\u5730\u5740");
83
183
  return {
84
184
  shutdown: async () => {
85
- log("info", "\u5173\u95ED\u98DE\u4E66\u4E8B\u4EF6\u7F51\u5173");
185
+ log("info", "\u98DE\u4E66 WebSocket \u6B63\u5728\u5173\u95ED");
186
+ wsClient.close();
187
+ log("info", "\u98DE\u4E66 WebSocket \u5DF2\u5173\u95ED");
86
188
  }
87
189
  };
88
190
  }
89
- function extractTextContent(message) {
90
- try {
91
- if (message.message_type === "text") {
92
- const content = JSON.parse(message.content || "{}");
93
- return content.text || "";
94
- }
95
- } catch {
96
- }
97
- return "";
98
- }
99
- function shouldReply(event, botOpenId) {
100
- if (event.chat_type === "p2p") {
101
- return true;
102
- }
103
- const message = event.message;
104
- if (message?.message_type === "text") {
105
- try {
106
- const content = JSON.parse(message.content || "{}");
107
- const text = content.text || "";
108
- return text.includes(`<at user_id="${botOpenId}">`) || text.includes(`<at open_id="${botOpenId}">`);
109
- } catch {
110
- }
111
- }
112
- return false;
113
- }
114
191
 
115
192
  // src/feishu/sender.ts
116
193
  var FeishuSender = class {
@@ -212,8 +289,24 @@ function createSender(larkClient, log) {
212
289
  var SERVICE_NAME = "cursor-feishu";
213
290
  var LOG_PREFIX = "[feishu]";
214
291
  var isDebug = !!process.env.FEISHU_DEBUG;
292
+ function parseTransport(raw) {
293
+ const v = (raw || "http").toLowerCase().trim();
294
+ if (v === "ws" || v === "websocket" || v === "long") return "ws";
295
+ if (v === "both" || v === "dual") return "both";
296
+ return "http";
297
+ }
215
298
  async function createFeishuService(options) {
216
- const { config: partialConfig, onMessage, onBotAdded, onCardAction, log: customLog } = options;
299
+ const {
300
+ config: partialConfig,
301
+ appId: optAppId,
302
+ appSecret: optAppSecret,
303
+ transport: transportOpt,
304
+ onMessage,
305
+ onBotAdded,
306
+ onCardAction,
307
+ log: customLog
308
+ } = options;
309
+ const effectivePartial = partialConfig ?? (optAppId && optAppSecret ? { appId: optAppId, appSecret: optAppSecret } : void 0);
217
310
  const log = customLog || ((level, message, extra) => {
218
311
  const prefixed = `${LOG_PREFIX} ${message}`;
219
312
  if (isDebug) {
@@ -230,7 +323,8 @@ async function createFeishuService(options) {
230
323
  console.log(prefixed, extra || "");
231
324
  }
232
325
  });
233
- const config = await loadConfig(partialConfig, log);
326
+ const config = await loadConfig(effectivePartial, log);
327
+ const transport = transportOpt ?? parseTransport(process.env.FEISHU_TRANSPORT);
234
328
  const larkClient = new Lark2.Client({
235
329
  appId: config.appId,
236
330
  appSecret: config.appSecret,
@@ -239,27 +333,41 @@ async function createFeishuService(options) {
239
333
  });
240
334
  const botOpenId = await fetchBotOpenId(larkClient, log);
241
335
  const sender = createSender(larkClient, log);
242
- let gateway = null;
243
336
  const handlers = {
244
337
  onMessage,
245
338
  onBotAdded,
246
339
  onCardAction
247
340
  };
341
+ let wsGateway = null;
342
+ let httpGateway = null;
343
+ if (transport === "ws" || transport === "both") {
344
+ wsGateway = startFeishuWebSocketGateway({
345
+ config,
346
+ larkClient,
347
+ botOpenId,
348
+ handlers,
349
+ log
350
+ });
351
+ }
248
352
  log("info", "\u98DE\u4E66\u670D\u52A1\u5DF2\u521D\u59CB\u5316", {
249
353
  appId: config.appId.slice(0, 8) + "...",
250
- botOpenId
354
+ botOpenId,
355
+ transport
251
356
  });
252
357
  return {
358
+ transport,
253
359
  async run() {
254
360
  try {
255
- gateway = await startFeishuGateway({
256
- config,
257
- larkClient,
258
- botOpenId,
259
- handlers,
260
- log
261
- });
262
- log("info", "\u98DE\u4E66\u670D\u52A1\u5DF2\u542F\u52A8");
361
+ if (transport === "http" || transport === "both") {
362
+ httpGateway = await startFeishuGateway({
363
+ config,
364
+ larkClient,
365
+ botOpenId,
366
+ handlers,
367
+ log
368
+ });
369
+ }
370
+ log("info", "\u98DE\u4E66\u670D\u52A1\u5DF2\u8FDB\u5165\u8FD0\u884C\u6001", { transport });
263
371
  await new Promise(() => {
264
372
  });
265
373
  } catch (err) {
@@ -270,8 +378,13 @@ async function createFeishuService(options) {
270
378
  }
271
379
  },
272
380
  async shutdown() {
273
- if (gateway) {
274
- await gateway.shutdown();
381
+ if (wsGateway) {
382
+ await wsGateway.shutdown();
383
+ wsGateway = null;
384
+ }
385
+ if (httpGateway) {
386
+ await httpGateway.shutdown();
387
+ httpGateway = null;
275
388
  }
276
389
  log("info", "\u98DE\u4E66\u670D\u52A1\u5DF2\u5173\u95ED");
277
390
  },
@@ -372,6 +485,7 @@ async function fetchBotOpenId(larkClient, log) {
372
485
  export {
373
486
  createFeishuService,
374
487
  createSender,
375
- startFeishuGateway
488
+ startFeishuGateway,
489
+ startFeishuWebSocketGateway
376
490
  };
377
491
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/types.ts","../src/feishu/gateway.ts","../src/feishu/sender.ts"],"sourcesContent":["/**\n * Cursor 飞书集成主模块\n *\n * 使用方式:\n * ```typescript\n * import { createFeishuService } from 'cursor-feishu'\n *\n * const service = await createFeishuService({\n * config: { appId: '...', appSecret: '...' },\n * onMessage: async (msg) => {\n * // 处理消息\n * }\n * })\n *\n * // 等待服务运行\n * await service.run()\n * ```\n */\n\nimport { readFileSync, existsSync } from \"node:fs\"\nimport { join } from \"node:path\"\nimport { homedir } from \"node:os\"\nimport * as Lark from \"@larksuiteoapi/node-sdk\"\nimport { z } from \"zod\"\nimport type { ResolvedConfig, LogFn, FeishuMessageContext, GatewayHandlers } from \"./types.js\"\nimport { FeishuConfigSchema } from \"./types.js\"\nimport { startFeishuGateway, type FeishuGatewayResult } from \"./feishu/gateway.js\"\nimport { createSender, type FeishuSender } from \"./feishu/sender.js\"\n\nconst SERVICE_NAME = \"cursor-feishu\"\nconst LOG_PREFIX = \"[feishu]\"\nconst isDebug = !!process.env.FEISHU_DEBUG\n\nexport interface FeishuServiceOptions {\n /** 飞书配置,可从 ~/.config/cursor/plugins/feishu.json 自动加载 */\n config?: Partial<ResolvedConfig>\n /** 消息处理回调 */\n onMessage?: (msgCtx: FeishuMessageContext) => Promise<void>\n /** Bot 入群回调 */\n onBotAdded?: (chatId: string) => Promise<void>\n /** 卡片交互回调 */\n onCardAction?: (action: any) => Promise<void>\n /** 自定义日志函数 */\n log?: LogFn\n}\n\nexport interface FeishuService {\n /** 启动并运行服务 */\n run: () => Promise<void>\n /** 关闭服务 */\n shutdown: () => Promise<void>\n /** 获取 Feishu 消息发送器 */\n getSender: () => FeishuSender\n /** 获取 Lark SDK 客户端 */\n getClient: () => InstanceType<typeof Lark.Client>\n}\n\n/**\n * 创建 Cursor 飞书集成服务\n */\nexport async function createFeishuService(options: FeishuServiceOptions): Promise<FeishuService> {\n const { config: partialConfig, onMessage, onBotAdded, onCardAction, log: customLog } = options\n\n // 日志函数\n const log: LogFn = customLog || ((level, message, extra) => {\n const prefixed = `${LOG_PREFIX} ${message}`\n if (isDebug) {\n console.error(JSON.stringify({\n ts: new Date().toISOString(),\n service: SERVICE_NAME,\n level,\n message: prefixed,\n ...extra,\n }))\n } else if (level === \"error\" || level === \"warn\") {\n console.error(prefixed, extra || \"\")\n } else {\n console.log(prefixed, extra || \"\")\n }\n })\n\n // 加载配置\n const config = await loadConfig(partialConfig, log)\n\n // 创建 Lark 客户端\n const larkClient = new Lark.Client({\n appId: config.appId,\n appSecret: config.appSecret,\n domain: Lark.Domain.Feishu,\n appType: Lark.AppType.SelfBuild,\n })\n\n // 获取 bot open_id\n const botOpenId = await fetchBotOpenId(larkClient, log)\n\n // 创建消息发送器\n const sender = createSender(larkClient, log)\n\n // 网关\n let gateway: FeishuGatewayResult | null = null\n\n // 创建处理器\n const handlers: GatewayHandlers = {\n onMessage,\n onBotAdded,\n onCardAction,\n }\n\n log(\"info\", \"飞书服务已初始化\", {\n appId: config.appId.slice(0, 8) + \"...\",\n botOpenId,\n })\n\n return {\n async run() {\n try {\n gateway = await startFeishuGateway({\n config,\n larkClient,\n botOpenId,\n handlers,\n log,\n })\n log(\"info\", \"飞书服务已启动\")\n // 保持运行\n await new Promise(() => {})\n } catch (err) {\n log(\"error\", \"启动飞书服务失败\", {\n error: err instanceof Error ? err.message : String(err),\n })\n throw err\n }\n },\n\n async shutdown() {\n if (gateway) {\n await gateway.shutdown()\n }\n log(\"info\", \"飞书服务已关闭\")\n },\n\n getSender() {\n return sender\n },\n\n getClient() {\n return larkClient\n },\n }\n}\n\n/**\n * 加载配置\n */\nasync function loadConfig(\n partialConfig: Partial<ResolvedConfig> | undefined,\n log: LogFn,\n): Promise<ResolvedConfig> {\n if (partialConfig && partialConfig.appId && partialConfig.appSecret) {\n // 使用传入的配置\n const config = FeishuConfigSchema.parse({\n appId: partialConfig.appId,\n appSecret: partialConfig.appSecret,\n ...partialConfig,\n })\n return {\n ...config,\n directory: expandDirectoryPath(partialConfig.directory || \"\"),\n }\n }\n\n // 从配置文件加载\n const configPath = join(homedir(), \".config\", \"cursor\", \"plugins\", \"feishu.json\")\n if (!existsSync(configPath)) {\n throw new Error(\n `缺少飞书配置文件:请创建 ${configPath},内容为 {\"appId\":\"cli_xxx\",\"appSecret\":\"xxx\"}`,\n )\n }\n\n try {\n const raw = resolveEnvPlaceholders(JSON.parse(readFileSync(configPath, \"utf-8\")))\n const config = FeishuConfigSchema.parse(raw)\n return {\n ...config,\n directory: expandDirectoryPath(config.directory || \"\"),\n }\n } catch (err) {\n if (err instanceof z.ZodError) {\n const details = err.issues.map((i) => ` - ${i.path.join(\".\")}: ${i.message}`).join(\"\\n\")\n throw new Error(`${LOG_PREFIX} 配置验证失败:\\n${details}`)\n }\n if (err instanceof SyntaxError) {\n throw new Error(`飞书配置文件格式错误:${configPath} 必须是合法的 JSON (${err.message})`)\n }\n throw err\n }\n}\n\n/**\n * 展开目录路径中的环境变量和 ~ 前缀\n */\nfunction expandDirectoryPath(dir: string): string {\n if (!dir) return dir\n if (dir.startsWith(\"~\")) {\n dir = join(homedir(), dir.slice(1))\n }\n dir = dir.replace(/\\$\\{(\\w+)\\}/g, (_match, name: string) => {\n const val = process.env[name]\n if (val === undefined) {\n throw new Error(`环境变量 ${name} 未设置(directory 引用了 \\${${name}})`)\n }\n return val\n })\n return dir\n}\n\n/**\n * 递归替换对象中字符串值里的 ${ENV_VAR} 占位符\n */\nfunction resolveEnvPlaceholders(obj: unknown): unknown {\n if (typeof obj === \"string\") {\n if (!obj.includes(\"${\")) return obj\n return obj.replace(/\\$\\{(\\w+)\\}/g, (_match, name: string) => {\n const val = process.env[name]\n if (val === undefined) {\n throw new Error(`环境变量 ${name} 未设置(配置值引用了 \\${${name}})`)\n }\n return val\n })\n }\n if (Array.isArray(obj)) {\n return obj.map(resolveEnvPlaceholders)\n }\n if (obj !== null && typeof obj === \"object\") {\n const result: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {\n result[key] = resolveEnvPlaceholders(value)\n }\n return result\n }\n return obj\n}\n\n/**\n * 获取 bot 自身的 open_id\n */\nasync function fetchBotOpenId(\n larkClient: InstanceType<typeof Lark.Client>,\n log: LogFn,\n): Promise<string> {\n const res = await larkClient.request<{ bot?: { open_id?: string } }>({\n url: \"https://open.feishu.cn/open-apis/bot/v3/info\",\n method: \"GET\",\n })\n const openId = res?.bot?.open_id\n if (!openId) {\n throw new Error(\"Bot open_id 为空,无法启动飞书服务\")\n }\n log(\"info\", \"Bot open_id 获取成功\", { openId })\n return openId\n}\n\nexport type { ResolvedConfig, LogFn, FeishuMessageContext } from \"./types.js\"\nexport { startFeishuGateway } from \"./feishu/gateway.js\"\nexport { createSender } from \"./feishu/sender.js\"\n","import { z } from \"zod\"\n\n/**\n * 飞书消息上下文\n */\nexport interface FeishuMessageContext {\n chatId: string\n messageId: string\n messageType: string\n /** 提取后的文本内容 */\n content: string\n /** 原始 JSON content 字符串 */\n rawContent: string\n chatType: \"p2p\" | \"group\"\n senderId: string\n rootId?: string\n /** 消息创建时间 */\n createTime?: string\n /** 是否需要回复 */\n shouldReply: boolean\n}\n\n/**\n * 插件配置(从 ~/.config/cursor/plugins/feishu.json 读取)\n */\nexport interface FeishuPluginConfig {\n appId: string\n appSecret: string\n timeout?: number\n logLevel?: \"fatal\" | \"error\" | \"warn\" | \"info\" | \"debug\" | \"trace\"\n /** 入群时拉取历史消息的最大条数 */\n maxHistoryMessages?: number\n /** 轮询响应的间隔毫秒数 */\n pollInterval?: number\n /** 连续几次轮询内容不变视为回复完成 */\n stablePolls?: number\n /** 消息去重缓存过期毫秒数 */\n dedupTtl?: number\n /** 默认工作目录 */\n directory?: string\n}\n\nconst AutoPromptSchema = z.object({\n enabled: z.boolean().default(false),\n intervalSeconds: z.number().int().positive().max(300).default(30),\n maxIterations: z.number().int().positive().max(100).default(10),\n message: z.string().min(1).default(\"请同步当前进度,如需帮助请说明\"),\n})\n\nexport const FeishuConfigSchema = z.object({\n appId: z.string().min(1, \"appId 不能为空\"),\n appSecret: z.string().min(1, \"appSecret 不能为空\"),\n timeout: z.number().int().positive().max(600_000).default(120_000),\n logLevel: z.enum([\"fatal\", \"error\", \"warn\", \"info\", \"debug\", \"trace\"]).default(\"info\"),\n maxHistoryMessages: z.number().int().positive().max(500).default(200),\n pollInterval: z.number().int().positive().default(1_000),\n stablePolls: z.number().int().positive().default(3),\n dedupTtl: z.number().int().positive().default(10 * 60 * 1_000),\n directory: z.string().optional(),\n})\n\n/**\n * 合并默认值后的完整配置\n */\nexport type ResolvedConfig = z.infer<typeof FeishuConfigSchema> & { directory: string }\n\n/**\n * 日志函数签名\n */\nexport type LogFn = (\n level: \"info\" | \"warn\" | \"error\",\n message: string,\n extra?: Record<string, unknown>,\n) => void\n\n/**\n * 网关回调处理器\n */\nexport interface GatewayHandlers {\n onMessage?: (msgCtx: FeishuMessageContext) => Promise<void>\n onBotAdded?: (chatId: string) => Promise<void>\n onCardAction?: (action: CardAction) => Promise<void>\n}\n\n/**\n * 卡片交互动作\n */\nexport interface CardAction {\n actionId: string\n messageId: string\n chatId: string\n senderId: string\n actionValue: Record<string, unknown>\n}\n","/**\n * Feishu 事件网关 — 使用 HTTP Webhook 接收飞书事件\n *\n * 注意: cursor-feishu 1.0.x 已弃用 WebSocket 方式,改用 HTTP Webhook。\n * WebSocket 长连接需要 Lark SDK 更高版本支持。\n */\n\nimport * as Lark from \"@larksuiteoapi/node-sdk\"\nimport type { ResolvedConfig, FeishuMessageContext, LogFn, CardAction, GatewayHandlers } from \"../types.js\"\n\nexport interface FeishuGatewayResult {\n shutdown: () => Promise<void>\n}\n\nexport interface GatewayOptions {\n config: ResolvedConfig\n larkClient: InstanceType<typeof Lark.Client>\n botOpenId: string\n handlers: GatewayHandlers\n log: LogFn\n}\n\n/**\n * 启动飞书事件网关(Webhook 模式)\n *\n * 使用 HTTP Webhook 方式接收飞书事件,而不是 WebSocket。\n * 这是 Lark SDK 原生支持的方式。\n */\nexport async function startFeishuGateway(options: GatewayOptions): Promise<FeishuGatewayResult> {\n const { config, larkClient, botOpenId, handlers, log } = options\n\n log(\"info\", \"启动飞书事件网关(Webhook 模式)\")\n\n // 创建事件分发器用于处理飞书 Webhook 回调\n const eventDispatcher = new Lark.EventDispatcher({\n verificationToken: process.env.FEISHU_VERIFICATION_TOKEN,\n encryptKey: process.env.FEISHU_ENCRYPT_KEY,\n })\n\n // 注册事件处理器\n eventDispatcher.register({\n // 处理消息事件\n \"im.message.message_create_v1\": async (data: any) => {\n try {\n const event = data.header?.event_id ? data : { detail: { event: data } }\n const message = event.detail?.event?.message || event.message\n if (!message) return\n\n const msgCtx: FeishuMessageContext = {\n chatId: event.detail?.event?.chat_id || event.chat_id,\n messageId: message.message_id,\n messageType: message.message_type,\n content: extractTextContent(message),\n rawContent: message.content || \"{}\",\n chatType: (event.detail?.event?.chat_type || event.chat_type || \"p2p\") as \"p2p\" | \"group\",\n senderId: event.detail?.event?.sender?.sender_id?.user_id || event.sender?.sender_id?.user_id || \"\",\n rootId: message.root_id,\n createTime: message.create_time,\n shouldReply: shouldReply(event.detail?.event || event, botOpenId),\n }\n\n if (handlers.onMessage) {\n await handlers.onMessage(msgCtx)\n }\n } catch (err) {\n log(\"error\", \"处理消息事件失败\", {\n error: err instanceof Error ? err.message : String(err),\n })\n }\n },\n\n // 处理 bot 入群事件\n \"im.chat.member_bot_added_v1\": async (data: any) => {\n try {\n const event = data.detail?.event || data\n const chatId = event.chat_id\n if (chatId && handlers.onBotAdded) {\n await handlers.onBotAdded(chatId)\n }\n } catch (err) {\n log(\"error\", \"处理 bot 入群事件失败\", {\n error: err instanceof Error ? err.message : String(err),\n })\n }\n },\n })\n\n log(\"info\", \"飞书事件网关已启动(Webhook 模式)\")\n log(\"warn\", \"注意: Webhook 模式需要配置飞书应用的回调 URL\")\n log(\"info\", \"请在飞书开放平台配置: POST http://your-domain/webhook/feishu\")\n\n return {\n shutdown: async () => {\n log(\"info\", \"关闭飞书事件网关\")\n // HTTP Webhook 无需主动关闭\n },\n }\n}\n\n/**\n * 从飞书消息中提取文本内容\n */\nfunction extractTextContent(message: any): string {\n try {\n if (message.message_type === \"text\") {\n const content = JSON.parse(message.content || \"{}\")\n return content.text || \"\"\n }\n } catch {\n // ignore\n }\n return \"\"\n}\n\n/**\n * 判断是否应该回复\n */\nfunction shouldReply(event: any, botOpenId: string): boolean {\n // 单聊总是回复\n if (event.chat_type === \"p2p\") {\n return true\n }\n\n // 群聊只有被 @提及 才回复\n const message = event.message\n if (message?.message_type === \"text\") {\n try {\n const content = JSON.parse(message.content || \"{}\")\n const text = content.text || \"\"\n // 检查是否包含 @bot\n return text.includes(`<at user_id=\"${botOpenId}\">`) || text.includes(`<at open_id=\"${botOpenId}\">`)\n } catch {\n // ignore\n }\n }\n\n return false\n}\n","/**\n * 飞书消息发送 — 向飞书用户/群组发送消息\n */\n\nimport * as Lark from \"@larksuiteoapi/node-sdk\"\nimport type { LogFn } from \"../types.js\"\n\nexport interface SenderOptions {\n larkClient: InstanceType<typeof Lark.Client>\n log: LogFn\n}\n\nexport class FeishuSender {\n constructor(private larkClient: InstanceType<typeof Lark.Client>, private log: LogFn) {}\n\n /**\n * 发送文本消息\n */\n async sendText(chatId: string, text: string): Promise<boolean> {\n try {\n const res = await this.larkClient.im.message.create({\n params: {\n receive_id_type: \"chat_id\",\n },\n data: {\n receive_id: chatId,\n msg_type: \"text\",\n content: JSON.stringify({ text }),\n },\n })\n\n const messageId = (res as any)?.data?.message_id\n if (messageId) {\n this.log(\"info\", \"消息已发送\", { chatId, messageId })\n return true\n } else {\n this.log(\"error\", \"发送消息失败\", { chatId, response: res })\n return false\n }\n } catch (err) {\n this.log(\"error\", \"发送消息异常\", {\n chatId,\n error: err instanceof Error ? err.message : String(err),\n })\n return false\n }\n }\n\n /**\n * 发送富文本卡片\n */\n async sendCard(chatId: string, card: any): Promise<boolean> {\n try {\n const res = await (this.larkClient.im.message.create as any)({\n params: {\n receive_id_type: \"chat_id\",\n },\n data: {\n receive_id: chatId,\n msg_type: \"interactive\",\n card,\n },\n })\n\n const messageId = (res as any)?.data?.message_id\n if (messageId) {\n this.log(\"info\", \"卡片已发送\", { chatId, messageId })\n return true\n } else {\n this.log(\"error\", \"发送卡片失败\", { chatId, response: res })\n return false\n }\n } catch (err) {\n this.log(\"error\", \"发送卡片异常\", {\n chatId,\n error: err instanceof Error ? err.message : String(err),\n })\n return false\n }\n }\n\n /**\n * 更新消息(用于流式响应)\n */\n async updateMessage(messageId: string, text: string): Promise<boolean> {\n try {\n await this.larkClient.im.message.patch({\n path: {\n message_id: messageId,\n },\n data: {\n content: JSON.stringify({ text }),\n },\n })\n\n this.log(\"info\", \"消息已更新\", { messageId })\n return true\n } catch (err) {\n this.log(\"error\", \"更新消息异常\", {\n messageId,\n error: err instanceof Error ? err.message : String(err),\n })\n return false\n }\n }\n}\n\nexport function createSender(\n larkClient: InstanceType<typeof Lark.Client>,\n log: LogFn,\n): FeishuSender {\n return new FeishuSender(larkClient, log)\n}\n"],"mappings":"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","names":["Lark","z","z"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/types.ts","../src/feishu/gateway.ts","../src/feishu/gateway-ws.ts","../src/feishu/dedup.ts","../src/feishu/sender.ts"],"sourcesContent":["/**\n * Cursor 飞书集成主模块\n *\n * 使用方式:\n * ```typescript\n * import { createFeishuService } from 'cursor-feishu'\n *\n * const service = await createFeishuService({\n * config: { appId: '...', appSecret: '...' },\n * onMessage: async (msg) => {\n * // 处理消息\n * }\n * })\n *\n * // 等待服务运行\n * await service.run()\n * ```\n */\n\nimport { readFileSync, existsSync } from \"node:fs\"\nimport { join } from \"node:path\"\nimport { homedir } from \"node:os\"\nimport * as Lark from \"@larksuiteoapi/node-sdk\"\nimport { z } from \"zod\"\nimport type { ResolvedConfig, LogFn, FeishuMessageContext, GatewayHandlers } from \"./types.js\"\nimport { FeishuConfigSchema } from \"./types.js\"\nimport { startFeishuGateway, type FeishuGatewayResult } from \"./feishu/gateway.js\"\nimport {\n startFeishuWebSocketGateway,\n type FeishuWebSocketGatewayResult,\n} from \"./feishu/gateway-ws.js\"\nimport { createSender, type FeishuSender } from \"./feishu/sender.js\"\n\nconst SERVICE_NAME = \"cursor-feishu\"\nconst LOG_PREFIX = \"[feishu]\"\nconst isDebug = !!process.env.FEISHU_DEBUG\n\n/** 飞书事件传输方式 */\nexport type FeishuTransport = \"ws\" | \"http\" | \"both\"\n\nfunction parseTransport(raw: string | undefined): FeishuTransport {\n const v = (raw || \"http\").toLowerCase().trim()\n if (v === \"ws\" || v === \"websocket\" || v === \"long\") return \"ws\"\n if (v === \"both\" || v === \"dual\") return \"both\"\n return \"http\"\n}\n\nexport interface FeishuServiceOptions {\n /** 飞书配置,可从 ~/.config/cursor/plugins/feishu.json 自动加载 */\n config?: Partial<ResolvedConfig>\n /** 与 config 二选一:直接传 appId / appSecret */\n appId?: string\n appSecret?: string\n /**\n * - `http`:仅 HTTP Webhook(宿主提供 Express 等)\n * - `ws`:仅 WebSocket 长连接(飞书选「长连接」,无需公网 URL)\n * - `both`:同时启用(勿在飞书重复订阅同一事件,否则需依赖去重)\n * 默认读环境变量 `FEISHU_TRANSPORT`,未设置时为 `http`\n */\n transport?: FeishuTransport\n /** 消息处理回调 */\n onMessage?: (msgCtx: FeishuMessageContext) => Promise<void>\n /** Bot 入群回调 */\n onBotAdded?: (chatId: string) => Promise<void>\n /** 卡片交互回调 */\n onCardAction?: (action: any) => Promise<void>\n /** 自定义日志函数 */\n log?: LogFn\n}\n\nexport interface FeishuService {\n /** 当前传输模式 */\n readonly transport: FeishuTransport\n /** 启动并运行服务(阻塞;纯 ws 时 WS 已在 create 阶段启动,此处仅挂起) */\n run: () => Promise<void>\n /** 关闭服务 */\n shutdown: () => Promise<void>\n /** 获取 Feishu 消息发送器 */\n getSender: () => FeishuSender\n /** 获取 Lark SDK 客户端 */\n getClient: () => InstanceType<typeof Lark.Client>\n}\n\n/**\n * 创建 Cursor 飞书集成服务\n */\nexport async function createFeishuService(options: FeishuServiceOptions): Promise<FeishuService> {\n const {\n config: partialConfig,\n appId: optAppId,\n appSecret: optAppSecret,\n transport: transportOpt,\n onMessage,\n onBotAdded,\n onCardAction,\n log: customLog,\n } = options\n\n const effectivePartial: Partial<ResolvedConfig> | undefined =\n partialConfig ??\n (optAppId && optAppSecret ? { appId: optAppId, appSecret: optAppSecret } : undefined)\n\n // 日志函数\n const log: LogFn = customLog || ((level, message, extra) => {\n const prefixed = `${LOG_PREFIX} ${message}`\n if (isDebug) {\n console.error(JSON.stringify({\n ts: new Date().toISOString(),\n service: SERVICE_NAME,\n level,\n message: prefixed,\n ...extra,\n }))\n } else if (level === \"error\" || level === \"warn\") {\n console.error(prefixed, extra || \"\")\n } else {\n console.log(prefixed, extra || \"\")\n }\n })\n\n // 加载配置\n const config = await loadConfig(effectivePartial, log)\n\n const transport: FeishuTransport = transportOpt ?? parseTransport(process.env.FEISHU_TRANSPORT)\n\n // 创建 Lark 客户端\n const larkClient = new Lark.Client({\n appId: config.appId,\n appSecret: config.appSecret,\n domain: Lark.Domain.Feishu,\n appType: Lark.AppType.SelfBuild,\n })\n\n // 获取 bot open_id\n const botOpenId = await fetchBotOpenId(larkClient, log)\n\n // 创建消息发送器\n const sender = createSender(larkClient, log)\n\n // 创建处理器\n const handlers: GatewayHandlers = {\n onMessage,\n onBotAdded,\n onCardAction,\n }\n\n let wsGateway: FeishuWebSocketGatewayResult | null = null\n let httpGateway: FeishuGatewayResult | null = null\n\n if (transport === \"ws\" || transport === \"both\") {\n wsGateway = startFeishuWebSocketGateway({\n config,\n larkClient,\n botOpenId,\n handlers,\n log,\n })\n }\n\n log(\"info\", \"飞书服务已初始化\", {\n appId: config.appId.slice(0, 8) + \"...\",\n botOpenId,\n transport,\n })\n\n return {\n transport,\n\n async run() {\n try {\n if (transport === \"http\" || transport === \"both\") {\n httpGateway = await startFeishuGateway({\n config,\n larkClient,\n botOpenId,\n handlers,\n log,\n })\n }\n log(\"info\", \"飞书服务已进入运行态\", { transport })\n await new Promise(() => {})\n } catch (err) {\n log(\"error\", \"启动飞书服务失败\", {\n error: err instanceof Error ? err.message : String(err),\n })\n throw err\n }\n },\n\n async shutdown() {\n if (wsGateway) {\n await wsGateway.shutdown()\n wsGateway = null\n }\n if (httpGateway) {\n await httpGateway.shutdown()\n httpGateway = null\n }\n log(\"info\", \"飞书服务已关闭\")\n },\n\n getSender() {\n return sender\n },\n\n getClient() {\n return larkClient\n },\n }\n}\n\n/**\n * 加载配置\n */\nasync function loadConfig(\n partialConfig: Partial<ResolvedConfig> | undefined,\n log: LogFn,\n): Promise<ResolvedConfig> {\n if (partialConfig && partialConfig.appId && partialConfig.appSecret) {\n // 使用传入的配置\n const config = FeishuConfigSchema.parse({\n appId: partialConfig.appId,\n appSecret: partialConfig.appSecret,\n ...partialConfig,\n })\n return {\n ...config,\n directory: expandDirectoryPath(partialConfig.directory || \"\"),\n }\n }\n\n // 从配置文件加载\n const configPath = join(homedir(), \".config\", \"cursor\", \"plugins\", \"feishu.json\")\n if (!existsSync(configPath)) {\n throw new Error(\n `缺少飞书配置文件:请创建 ${configPath},内容为 {\"appId\":\"cli_xxx\",\"appSecret\":\"xxx\"}`,\n )\n }\n\n try {\n const raw = resolveEnvPlaceholders(JSON.parse(readFileSync(configPath, \"utf-8\")))\n const config = FeishuConfigSchema.parse(raw)\n return {\n ...config,\n directory: expandDirectoryPath(config.directory || \"\"),\n }\n } catch (err) {\n if (err instanceof z.ZodError) {\n const details = err.issues.map((i) => ` - ${i.path.join(\".\")}: ${i.message}`).join(\"\\n\")\n throw new Error(`${LOG_PREFIX} 配置验证失败:\\n${details}`)\n }\n if (err instanceof SyntaxError) {\n throw new Error(`飞书配置文件格式错误:${configPath} 必须是合法的 JSON (${err.message})`)\n }\n throw err\n }\n}\n\n/**\n * 展开目录路径中的环境变量和 ~ 前缀\n */\nfunction expandDirectoryPath(dir: string): string {\n if (!dir) return dir\n if (dir.startsWith(\"~\")) {\n dir = join(homedir(), dir.slice(1))\n }\n dir = dir.replace(/\\$\\{(\\w+)\\}/g, (_match, name: string) => {\n const val = process.env[name]\n if (val === undefined) {\n throw new Error(`环境变量 ${name} 未设置(directory 引用了 \\${${name}})`)\n }\n return val\n })\n return dir\n}\n\n/**\n * 递归替换对象中字符串值里的 ${ENV_VAR} 占位符\n */\nfunction resolveEnvPlaceholders(obj: unknown): unknown {\n if (typeof obj === \"string\") {\n if (!obj.includes(\"${\")) return obj\n return obj.replace(/\\$\\{(\\w+)\\}/g, (_match, name: string) => {\n const val = process.env[name]\n if (val === undefined) {\n throw new Error(`环境变量 ${name} 未设置(配置值引用了 \\${${name}})`)\n }\n return val\n })\n }\n if (Array.isArray(obj)) {\n return obj.map(resolveEnvPlaceholders)\n }\n if (obj !== null && typeof obj === \"object\") {\n const result: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {\n result[key] = resolveEnvPlaceholders(value)\n }\n return result\n }\n return obj\n}\n\n/**\n * 获取 bot 自身的 open_id\n */\nasync function fetchBotOpenId(\n larkClient: InstanceType<typeof Lark.Client>,\n log: LogFn,\n): Promise<string> {\n const res = await larkClient.request<{ bot?: { open_id?: string } }>({\n url: \"https://open.feishu.cn/open-apis/bot/v3/info\",\n method: \"GET\",\n })\n const openId = res?.bot?.open_id\n if (!openId) {\n throw new Error(\"Bot open_id 为空,无法启动飞书服务\")\n }\n log(\"info\", \"Bot open_id 获取成功\", { openId })\n return openId\n}\n\nexport type { ResolvedConfig, LogFn, FeishuMessageContext } from \"./types.js\"\nexport { startFeishuGateway } from \"./feishu/gateway.js\"\nexport { startFeishuWebSocketGateway } from \"./feishu/gateway-ws.js\"\nexport { createSender } from \"./feishu/sender.js\"\n","import { z } from \"zod\"\n\n/**\n * 飞书消息上下文\n */\nexport interface FeishuMessageContext {\n chatId: string\n messageId: string\n messageType: string\n /** 提取后的文本内容 */\n content: string\n /** 原始 JSON content 字符串 */\n rawContent: string\n chatType: \"p2p\" | \"group\"\n senderId: string\n rootId?: string\n /** 消息创建时间 */\n createTime?: string\n /** 是否需要回复 */\n shouldReply: boolean\n}\n\n/**\n * 插件配置(从 ~/.config/cursor/plugins/feishu.json 读取)\n */\nexport interface FeishuPluginConfig {\n appId: string\n appSecret: string\n timeout?: number\n logLevel?: \"fatal\" | \"error\" | \"warn\" | \"info\" | \"debug\" | \"trace\"\n /** 入群时拉取历史消息的最大条数 */\n maxHistoryMessages?: number\n /** 轮询响应的间隔毫秒数 */\n pollInterval?: number\n /** 连续几次轮询内容不变视为回复完成 */\n stablePolls?: number\n /** 消息去重缓存过期毫秒数 */\n dedupTtl?: number\n /** 默认工作目录 */\n directory?: string\n}\n\nconst AutoPromptSchema = z.object({\n enabled: z.boolean().default(false),\n intervalSeconds: z.number().int().positive().max(300).default(30),\n maxIterations: z.number().int().positive().max(100).default(10),\n message: z.string().min(1).default(\"请同步当前进度,如需帮助请说明\"),\n})\n\nexport const FeishuConfigSchema = z.object({\n appId: z.string().min(1, \"appId 不能为空\"),\n appSecret: z.string().min(1, \"appSecret 不能为空\"),\n timeout: z.number().int().positive().max(600_000).default(120_000),\n logLevel: z.enum([\"fatal\", \"error\", \"warn\", \"info\", \"debug\", \"trace\"]).default(\"info\"),\n maxHistoryMessages: z.number().int().positive().max(500).default(200),\n pollInterval: z.number().int().positive().default(1_000),\n stablePolls: z.number().int().positive().default(3),\n dedupTtl: z.number().int().positive().default(10 * 60 * 1_000),\n directory: z.string().optional(),\n})\n\n/**\n * 合并默认值后的完整配置\n */\nexport type ResolvedConfig = z.infer<typeof FeishuConfigSchema> & { directory: string }\n\n/**\n * 日志函数签名\n */\nexport type LogFn = (\n level: \"info\" | \"warn\" | \"error\",\n message: string,\n extra?: Record<string, unknown>,\n) => void\n\n/**\n * 网关回调处理器\n */\nexport interface GatewayHandlers {\n onMessage?: (msgCtx: FeishuMessageContext) => Promise<void>\n onBotAdded?: (chatId: string) => Promise<void>\n onCardAction?: (action: CardAction) => Promise<void>\n}\n\n/**\n * 卡片交互动作\n */\nexport interface CardAction {\n actionId: string\n messageId: string\n chatId: string\n senderId: string\n actionValue: Record<string, unknown>\n}\n","/**\n * Feishu HTTP 模式占位说明(实际收事件由宿主如 Express 解析后调 onMessage)\n *\n * WebSocket 长连接见 `gateway-ws.ts`(`startFeishuWebSocketGateway`)。\n * 双模式由环境变量 `FEISHU_TRANSPORT` 或 `createFeishuService({ transport })` 控制。\n */\n\nimport * as Lark from \"@larksuiteoapi/node-sdk\"\nimport type { ResolvedConfig, LogFn, GatewayHandlers } from \"../types.js\"\n\nexport interface FeishuGatewayResult {\n shutdown: () => Promise<void>\n}\n\nexport interface GatewayOptions {\n config: ResolvedConfig\n larkClient: InstanceType<typeof Lark.Client>\n botOpenId: string\n handlers: GatewayHandlers\n log: LogFn\n}\n\n/**\n * HTTP/Webhook 占位:事件由宿主(如 cursor-agent 的 Express)解析后调用 onMessage。\n * 与 `startFeishuWebSocketGateway` 二选一或并存(双模式时飞书勿对同一事件重复订阅)。\n */\nexport async function startFeishuGateway(options: GatewayOptions): Promise<FeishuGatewayResult> {\n const { log } = options\n\n log(\"info\", \"飞书 HTTP 模式:由宿主提供 Webhook(如 POST /webhook/feishu)\")\n\n return {\n shutdown: async () => {\n log(\"info\", \"关闭飞书 HTTP 占位网关\")\n },\n }\n}\n","/**\n * 飞书 WebSocket 长连接:EventDispatcher + WSClient(@larksuiteoapi/node-sdk)\n * 飞书开放平台需选择「使用长连接接收事件」,无需公网 Webhook URL。\n */\nimport * as Lark from \"@larksuiteoapi/node-sdk\"\nimport { HttpsProxyAgent } from \"https-proxy-agent\"\nimport type { Agent } from \"node:https\"\nimport type { ResolvedConfig, FeishuMessageContext, LogFn, GatewayHandlers } from \"../types.js\"\nimport { isDuplicateMessageId } from \"./dedup.js\"\n\nexport interface FeishuWebSocketGatewayOptions {\n config: ResolvedConfig\n /** 复用 token 的 Client(WS 仍用 appId/appSecret 建连,此参数预留与 sender 一致) */\n larkClient: InstanceType<typeof Lark.Client>\n botOpenId: string\n handlers: GatewayHandlers\n log: LogFn\n}\n\nexport interface FeishuWebSocketGatewayResult {\n shutdown: () => Promise<void>\n}\n\nfunction extractTextContent(message: Record<string, unknown>): string {\n try {\n if (message.message_type === \"text\") {\n const content = JSON.parse((message.content as string) || \"{}\")\n return (content.text as string) || \"\"\n }\n } catch {\n // ignore\n }\n return \"\"\n}\n\nfunction computeShouldReply(\n chatType: \"p2p\" | \"group\",\n message: Record<string, unknown>,\n botOpenId: string,\n): boolean {\n if (chatType === \"p2p\") return true\n\n const mentions = message.mentions as Array<{ id?: { open_id?: string } }> | undefined\n if (Array.isArray(mentions) && mentions.length > 0) {\n return mentions.some((m) => m?.id?.open_id === botOpenId)\n }\n\n if (message.message_type === \"text\") {\n try {\n const content = JSON.parse((message.content as string) || \"{}\")\n const text = (content.text as string) || \"\"\n return (\n text.includes(`<at user_id=\"${botOpenId}\">`) ||\n text.includes(`<at open_id=\"${botOpenId}\">`)\n )\n } catch {\n return false\n }\n }\n return false\n}\n\n/**\n * 启动飞书 WebSocket 长连接网关\n */\nexport function startFeishuWebSocketGateway(\n options: FeishuWebSocketGatewayOptions,\n): FeishuWebSocketGatewayResult {\n const { config, botOpenId, handlers, log } = options\n const { appId, appSecret } = config\n\n const proxyUrl =\n process.env.HTTPS_PROXY || process.env.HTTP_PROXY || process.env.ALL_PROXY || \"\"\n\n let wsAgent: Agent | undefined\n if (proxyUrl) {\n wsAgent = new HttpsProxyAgent(proxyUrl)\n log(\"info\", \"飞书 WS 已启用代理\", { proxy: proxyUrl })\n }\n\n const dispatcher = new Lark.EventDispatcher({}).register({\n \"im.message.receive_v1\": async (data: Record<string, unknown>) => {\n try {\n const message = data.message as Record<string, unknown> | undefined\n if (!message) return\n\n const chatId = message.chat_id as string | undefined\n if (!chatId) return\n\n const messageId = message.message_id as string | undefined\n if (isDuplicateMessageId(messageId)) return\n\n const messageType = (message.message_type as string) || \"text\"\n let text = extractTextContent(message)\n if (messageType === \"text\") {\n text = text.replace(/@_user_\\d+\\s*/g, \"\").trim()\n }\n if (!text) return\n\n const chatType: \"p2p\" | \"group\" =\n (message.chat_type as string) === \"group\" ? \"group\" : \"p2p\"\n\n const shouldReply = computeShouldReply(chatType, message, botOpenId)\n\n const sender = data.sender as { sender_id?: { open_id?: string; user_id?: string } } | undefined\n const senderId =\n sender?.sender_id?.open_id || sender?.sender_id?.user_id || \"\"\n\n const ctx: FeishuMessageContext = {\n chatId,\n messageId: messageId || \"\",\n messageType,\n content: text,\n rawContent: (message.content as string) || \"{}\",\n chatType,\n senderId,\n rootId: message.root_id as string | undefined,\n createTime: message.create_time as string | undefined,\n shouldReply,\n }\n\n log(\"info\", \"WS 收到飞书消息\", {\n chatId,\n messageId: messageId || \"\",\n chatType,\n shouldReply,\n preview: text.slice(0, 80),\n })\n\n if (handlers.onMessage) {\n await handlers.onMessage(ctx)\n }\n } catch (err) {\n log(\"error\", \"WS 处理消息失败\", {\n error: err instanceof Error ? err.message : String(err),\n })\n }\n },\n\n \"im.chat.member.bot.added_v1\": async (data: Record<string, unknown>) => {\n try {\n const chatId = (data.chat_id as string) || (data.detail as { event?: { chat_id?: string } })?.event?.chat_id\n if (chatId && handlers.onBotAdded) {\n log(\"info\", \"WS: Bot 入群\", { chatId })\n await handlers.onBotAdded(chatId)\n }\n } catch (err) {\n log(\"error\", \"WS 处理入群失败\", {\n error: err instanceof Error ? err.message : String(err),\n })\n }\n },\n })\n\n const logLevelMap: Record<string, Lark.LoggerLevel> = {\n fatal: Lark.LoggerLevel.fatal,\n error: Lark.LoggerLevel.error,\n warn: Lark.LoggerLevel.warn,\n info: Lark.LoggerLevel.info,\n debug: Lark.LoggerLevel.debug,\n trace: Lark.LoggerLevel.trace,\n }\n\n const wsClient = new Lark.WSClient({\n appId,\n appSecret,\n domain: Lark.Domain.Feishu,\n ...(wsAgent ? { agent: wsAgent } : {}),\n loggerLevel: logLevelMap[config.logLevel] ?? Lark.LoggerLevel.info,\n logger: {\n error: (...msg: unknown[]) => log(\"error\", \"[lark.ws]\", { msg }),\n warn: (...msg: unknown[]) => log(\"warn\", \"[lark.ws]\", { msg }),\n info: (...msg: unknown[]) => log(\"info\", \"[lark.ws]\", { msg }),\n debug: (...msg: unknown[]) => log(\"info\", \"[lark.ws]\", { msg }),\n trace: (...msg: unknown[]) => log(\"info\", \"[lark.ws]\", { msg }),\n },\n })\n\n wsClient.start({ eventDispatcher: dispatcher })\n log(\"info\", \"飞书 WebSocket 长连接已启动\", { appIdPrefix: appId.slice(0, 8) + \"...\" })\n log(\"info\", \"飞书侧请选择「使用长连接接收事件」,无需配置请求地址\")\n\n return {\n shutdown: async () => {\n log(\"info\", \"飞书 WebSocket 正在关闭\")\n wsClient.close()\n log(\"info\", \"飞书 WebSocket 已关闭\")\n },\n }\n}\n","/**\n * WebSocket 可能重复投递同一 message_id;双模式时与 HTTP 同时到达也去重\n */\nconst seen = new Map<string, number>()\nconst DEFAULT_TTL_MS = 2 * 60 * 1000\n\nfunction prune(now: number, ttl: number) {\n for (const [id, t] of seen) {\n if (now - t > ttl) seen.delete(id)\n }\n}\n\nexport function isDuplicateMessageId(messageId: string | undefined, ttlMs = DEFAULT_TTL_MS): boolean {\n if (!messageId) return false\n const now = Date.now()\n prune(now, ttlMs)\n if (seen.has(messageId)) return true\n seen.set(messageId, now)\n return false\n}\n","/**\n * 飞书消息发送 — 向飞书用户/群组发送消息\n */\n\nimport * as Lark from \"@larksuiteoapi/node-sdk\"\nimport type { LogFn } from \"../types.js\"\n\nexport interface SenderOptions {\n larkClient: InstanceType<typeof Lark.Client>\n log: LogFn\n}\n\nexport class FeishuSender {\n constructor(private larkClient: InstanceType<typeof Lark.Client>, private log: LogFn) {}\n\n /**\n * 发送文本消息\n */\n async sendText(chatId: string, text: string): Promise<boolean> {\n try {\n const res = await this.larkClient.im.message.create({\n params: {\n receive_id_type: \"chat_id\",\n },\n data: {\n receive_id: chatId,\n msg_type: \"text\",\n content: JSON.stringify({ text }),\n },\n })\n\n const messageId = (res as any)?.data?.message_id\n if (messageId) {\n this.log(\"info\", \"消息已发送\", { chatId, messageId })\n return true\n } else {\n this.log(\"error\", \"发送消息失败\", { chatId, response: res })\n return false\n }\n } catch (err) {\n this.log(\"error\", \"发送消息异常\", {\n chatId,\n error: err instanceof Error ? err.message : String(err),\n })\n return false\n }\n }\n\n /**\n * 发送富文本卡片\n */\n async sendCard(chatId: string, card: any): Promise<boolean> {\n try {\n const res = await (this.larkClient.im.message.create as any)({\n params: {\n receive_id_type: \"chat_id\",\n },\n data: {\n receive_id: chatId,\n msg_type: \"interactive\",\n card,\n },\n })\n\n const messageId = (res as any)?.data?.message_id\n if (messageId) {\n this.log(\"info\", \"卡片已发送\", { chatId, messageId })\n return true\n } else {\n this.log(\"error\", \"发送卡片失败\", { chatId, response: res })\n return false\n }\n } catch (err) {\n this.log(\"error\", \"发送卡片异常\", {\n chatId,\n error: err instanceof Error ? err.message : String(err),\n })\n return false\n }\n }\n\n /**\n * 更新消息(用于流式响应)\n */\n async updateMessage(messageId: string, text: string): Promise<boolean> {\n try {\n await this.larkClient.im.message.patch({\n path: {\n message_id: messageId,\n },\n data: {\n content: JSON.stringify({ text }),\n },\n })\n\n this.log(\"info\", \"消息已更新\", { messageId })\n return true\n } catch (err) {\n this.log(\"error\", \"更新消息异常\", {\n messageId,\n error: err instanceof Error ? err.message : String(err),\n })\n return false\n }\n }\n}\n\nexport function createSender(\n larkClient: InstanceType<typeof Lark.Client>,\n log: LogFn,\n): FeishuSender {\n return new FeishuSender(larkClient, log)\n}\n"],"mappings":"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","names":["Lark","z","z"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cursor-feishu",
3
- "version": "1.0.5",
3
+ "version": "1.1.0",
4
4
  "description": "Cursor 飞书集成 — 通过飞书 WebSocket 长连接与 Cursor Headless CLI 交互",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",