@lih-x-x/kmr 1.0.15 → 1.0.17

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.
Files changed (3) hide show
  1. package/README.md +18 -10
  2. package/dist/index.js +29 -10
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -5,8 +5,8 @@
5
5
  ## 功能
6
6
 
7
7
  - **会议纪要提取**:发送飞书云文档链接给机器人,自动提取会议摘要、待办事项、风险项、关键共识、可复用知识
8
- - **飞书任务创建**:提取待办后自动发送确认消息,回复 `/confirm` 创建飞书任务,`/reject` 取消,1 分钟超时自动取消
9
- - **AI 对话**:发送非指令文本即可与 AI 自由对话,基于 acpx claude 持久化 session,按用户隔离
8
+ - **飞书任务创建**:提取待办后自动发送确认消息,回复 `/confirm` 创建飞书任务,`/reject` 取消,3 分钟超时自动取消。自动通过群成员列表解析负责人并指派任务
9
+ - **AI 对话**:发送非指令文本即可与 AI 自由对话,基于 acpx 持久化 session,按用户隔离,支持 claude codex 两种 agent
10
10
  - **历史会议查询**:通过 `/find` 指令用自然语言搜索历史会议记录
11
11
  - **记录管理**:`/listall` 列出所有记录,`/show <id>` 查看详情,`/del <id>` 删除记录
12
12
  - **群聊智能识别**:群聊中非 @消息自动识别会议纪要文档链接(通过文档标题判断),@机器人可使用所有指令和 AI 对话
@@ -51,10 +51,11 @@ KMR 不做流水账式的会议记录搬运。每条提取结果都围绕两个
51
51
 
52
52
  - Node.js >= 18
53
53
  - [acpx](https://www.npmjs.com/package/acpx) 已全局安装(`npm install -g acpx@latest`)
54
- - Claude Code 已安装并可通过 acpx 调用
54
+ - Claude Code 或 Codex 已安装并可通过 acpx 调用
55
55
  - 飞书自建应用,开启以下权限:
56
56
  - `im:message:receive_v1`(接收消息事件)
57
57
  - `im:message`(发送消息)
58
+ - `im:chat:readonly` 或 `im:chat.members:read`(获取群成员列表,用于解析任务负责人)
58
59
  - `docs:document.content:read`(读取文档内容)
59
60
  - `docs:document:readonly`(读取文档元信息,用于标题识别)
60
61
  - `task:task:write`(创建飞书任务)
@@ -64,7 +65,7 @@ KMR 不做流水账式的会议记录搬运。每条提取结果都围绕两个
64
65
  ### 方式一:全局安装(推荐)
65
66
 
66
67
  ```bash
67
- npm install -g kmr
68
+ npm install -g @lih-x-x/kmr
68
69
  kmr init # 初始化配置
69
70
  # 编辑 ~/.kmr/config.json 填入飞书凭证
70
71
  kmr # 启动服务
@@ -90,7 +91,7 @@ npm run dev # 启动服务
90
91
  "appSecret": "你的飞书应用 App Secret"
91
92
  },
92
93
  "agent": {
93
- "provider": "claude-code",
94
+ "provider": "claude",
94
95
  "timeout": 120000
95
96
  },
96
97
  "storage": {
@@ -99,6 +100,8 @@ npm run dev # 启动服务
99
100
  }
100
101
  ```
101
102
 
103
+ `provider` 支持 `claude` 和 `codex` 两种 agent,可在 Web 设置页面实时切换,无需重启服务。
104
+
102
105
  配置完成后运行 `kmr` 或 `npm run dev` 启动服务。
103
106
 
104
107
  ## 使用方式
@@ -151,10 +154,10 @@ https://xxx.feishu.cn/docx/abc123
151
154
  回复 /confirm 1,2 创建选中的任务
152
155
  回复 /reject 取消创建
153
156
 
154
- 1 分钟内未回复将自动取消
157
+ 3 分钟内未回复将自动取消
155
158
  ```
156
159
 
157
- 创建的任务会自动设置截止时间,并将确认者设为任务负责人。任务信息同步回写到会议记录中。
160
+ 创建的任务会自动设置截止时间,并通过群成员列表自动解析负责人姓名为飞书 open_id 进行指派(解析失败时 fallback 到确认者)。任务信息同步回写到会议记录中。
158
161
 
159
162
  ### AI 自由对话
160
163
 
@@ -197,16 +200,18 @@ kmr/
197
200
  ├── src/
198
201
  │ ├── index.ts # 服务入口
199
202
  │ ├── config.ts # 配置管理(~/.kmr/config.json)
203
+ │ ├── cli.ts # CLI 入口(kmr 命令、版本更新检查)
200
204
  │ ├── cli-init.ts # kmr init 命令
201
205
  │ ├── lark/
202
206
  │ │ ├── client.ts # 飞书长连接客户端(消息去重、群聊过滤)
203
207
  │ │ ├── docReader.ts # 飞书文档内容读取(含标题获取)
204
208
  │ │ ├── messenger.ts # 飞书消息回复(摘要、确认、任务结果等)
205
209
  │ │ ├── router.ts # 消息路由(指令解析、自然语言匹配)
206
- │ │ └── taskCreator.ts # 飞书任务创建(Task API v2)
210
+ │ │ ├── taskCreator.ts # 飞书任务创建(Task API v2)
211
+ │ │ └── userResolver.ts # 群成员姓名→open_id 解析
207
212
  │ ├── agent/
208
213
  │ │ ├── types.ts # AgentProvider 接口
209
- │ │ ├── claudeCode.ts # Claude Code 实现(acpx)
214
+ │ │ ├── claudeCode.ts # Claude/Codex 实现(acpx)
210
215
  │ │ └── prompt.ts # Prompt 模板
211
216
  │ ├── session/
212
217
  │ │ ├── manager.ts # AI 会话管理(per-user acpx session)
@@ -221,10 +226,13 @@ kmr/
221
226
  │ │ ├── index.html
222
227
  │ │ ├── style.css
223
228
  │ │ └── app.js
229
+ │ ├── utils/
230
+ │ │ └── claudeEnv.ts # ~/.claude/settings.json 环境变量加载
224
231
  │ └── storage/
225
232
  │ ├── types.ts # 数据类型定义
226
233
  │ └── jsonStore.ts # JSON 文件存储
227
234
  ├── tests/ # 测试文件
235
+ ├── tsup.config.ts # tsup 构建配置
228
236
  ├── package.json
229
237
  └── tsconfig.json
230
238
  ```
@@ -266,7 +274,7 @@ npm run build
266
274
 
267
275
  ## 架构扩展
268
276
 
269
- Agent 模块通过 `AgentProvider` 接口抽象,当前仅实现 Claude Code。后续可扩展其他 Agent(如 Kiro、Gemini 等),只需实现该接口:
277
+ Agent 模块通过 `AgentProvider` 接口抽象,当前支持 Claude Code 和 Codex 两种 agent(均通过 acpx 调用)。可在 Web 设置页面实时切换,无需重启服务。后续可扩展其他 Agent(如 Kiro、Gemini 等),只需实现该接口:
270
278
 
271
279
  ```typescript
272
280
  interface AgentProvider {
package/dist/index.js CHANGED
@@ -13,7 +13,25 @@ function createLarkClient(config) {
13
13
  appType: lark.AppType.SelfBuild
14
14
  });
15
15
  }
16
- function createEventDispatcher(onMessage) {
16
+ async function getBotOpenId(client) {
17
+ try {
18
+ const res = await client.request({
19
+ method: "GET",
20
+ url: "/open-apis/bot/v3/info"
21
+ });
22
+ const openId = res.data?.bot?.open_id || "";
23
+ if (openId) {
24
+ console.log(`[bot] \u673A\u5668\u4EBA open_id: ${openId}`);
25
+ } else {
26
+ console.warn(`[bot] \u672A\u80FD\u83B7\u53D6\u673A\u5668\u4EBA open_id\uFF0C\u7FA4\u804A @\u5224\u65AD\u53EF\u80FD\u4E0D\u51C6\u786E`);
27
+ }
28
+ return openId;
29
+ } catch (err) {
30
+ console.error(`[bot] \u83B7\u53D6\u673A\u5668\u4EBA\u4FE1\u606F\u5931\u8D25:`, err.message);
31
+ return "";
32
+ }
33
+ }
34
+ function createEventDispatcher(botOpenId, onMessage) {
17
35
  const dispatcher = new lark.EventDispatcher({});
18
36
  const processedMessages = /* @__PURE__ */ new Set();
19
37
  dispatcher.register({
@@ -47,10 +65,11 @@ function createEventDispatcher(onMessage) {
47
65
  const content = JSON.parse(message.content);
48
66
  let text = content.text || "";
49
67
  const chatId = message.chat_id;
50
- const isMentioned = message.mentions && message.mentions.length > 0;
68
+ const mentions = message.mentions || [];
69
+ const isMentioned = botOpenId ? mentions.some((m) => m.id?.open_id === botOpenId) : mentions.some((m) => m.id?.open_id && data.sender?.sender_type !== "user");
51
70
  const isGroup = message.chat_type === "group";
52
- if (isMentioned) {
53
- for (const mention of message.mentions) {
71
+ if (mentions.length > 0) {
72
+ for (const mention of mentions) {
54
73
  if (mention.key) {
55
74
  text = text.replace(mention.key, "").trim();
56
75
  }
@@ -58,9 +77,8 @@ function createEventDispatcher(onMessage) {
58
77
  }
59
78
  if (isGroup && !isMentioned) {
60
79
  const hasDocLink = /(https?:\/\/[^\s]*feishu\.cn\/[^\s]+)/.test(text);
61
- const isConfirmOrReject = /^(?:\/confirm|\/reject|确认创建|取消创建|不创建|不用创建|全部创建|跳过待办|跳过|算了)\b/.test(text.trim());
62
- if (!hasDocLink && !isConfirmOrReject) {
63
- console.log(`[recv] \u7FA4\u804A\u975E@\u6D88\u606F\u4E14\u975E\u6587\u6863\u94FE\u63A5/\u5F85\u529E\u786E\u8BA4, \u5FFD\u7565`);
80
+ if (!hasDocLink) {
81
+ console.log(`[recv] \u7FA4\u804A\u975E@\u6D88\u606F\u4E14\u975E\u6587\u6863\u94FE\u63A5, \u5FFD\u7565`);
64
82
  return;
65
83
  }
66
84
  }
@@ -295,7 +313,7 @@ var Messenger = class {
295
313
  lines.push("", "\u56DE\u590D /confirm all \u521B\u5EFA\u5168\u90E8");
296
314
  lines.push("\u56DE\u590D /confirm 1,2 \u521B\u5EFA\u9009\u4E2D\u7684\u4EFB\u52A1");
297
315
  lines.push("\u56DE\u590D /reject \u53D6\u6D88\u521B\u5EFA");
298
- lines.push("", "\u23F1 1 \u5206\u949F\u5185\u672A\u56DE\u590D\u5C06\u81EA\u52A8\u53D6\u6D88");
316
+ lines.push("", "\u23F1 3 \u5206\u949F\u5185\u672A\u56DE\u590D\u5C06\u81EA\u52A8\u53D6\u6D88");
299
317
  await this.replyText(messageId, lines.join("\n"));
300
318
  }
301
319
  async replyTaskResults(messageId, results) {
@@ -1102,7 +1120,8 @@ async function main() {
1102
1120
  const taskCreator = new TaskCreator(client);
1103
1121
  const userResolver = new UserResolver(client);
1104
1122
  const pendingConfirmations = /* @__PURE__ */ new Map();
1105
- const dispatcher = createEventDispatcher(async (messageId, text, chatId, senderId, meta) => {
1123
+ const botOpenId = await getBotOpenId(client);
1124
+ const dispatcher = createEventDispatcher(botOpenId, async (messageId, text, chatId, senderId, meta) => {
1106
1125
  const parsed = parseMessage(text);
1107
1126
  console.log(`[route] \u6D88\u606F\u8DEF\u7531: type=${parsed.type}, messageId=${messageId}, isGroup=${meta.isGroup}, isMentioned=${meta.isMentioned}`);
1108
1127
  if (meta.isGroup && !meta.isMentioned && parsed.type === "document_link" /* DOCUMENT_LINK */) {
@@ -1142,7 +1161,7 @@ async function main() {
1142
1161
  pendingConfirmations.delete(senderId);
1143
1162
  console.log(`[timeout] \u5F85\u529E\u786E\u8BA4\u8D85\u65F6, userId=${senderId}, meetingId=${record.id}`);
1144
1163
  }
1145
- }, 6e4);
1164
+ }, 18e4);
1146
1165
  pendingConfirmations.set(senderId, {
1147
1166
  meetingId: record.id,
1148
1167
  todos: record.todos,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lih-x-x/kmr",
3
- "version": "1.0.15",
3
+ "version": "1.0.17",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {