chattercatcher 0.1.24 → 0.1.26

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
@@ -59,8 +59,11 @@ ChatterCatcher 是一个早期 MVP。它已经具备飞书长连接接入、本
59
59
 
60
60
  - **无 native 向量库依赖**:语义向量写入 SQLite,避免 LanceDB 平台包在不同 macOS/CPU 架构上安装失败。
61
61
  - **SQLite FTS + embedding 混合 RAG**:关键词和语义检索并行召回,回答前必须先找到本地证据。
62
+ - **相对时间归一化**:自动将”今天””明天””今晚”等相对时间表述转换为具体日期。所有 LLM 路径(会话摘要、RAG 问答、Agent 工具循环)统一注入当前时间并强调时间推导规则,让回答中的日期始终准确。
63
+ - **群内定时任务**:在群里用自然语言创建 cron 定时任务,支持”每天 9 点总结昨天群聊”等日常需求。创建、查看、删除都在群里一句话搞定,任务限定当前群聊不能跨群。
64
+ - **工具循环智能兜底**:当 Agent 多轮工具调用达到上限时,自动用对话历史生成最终答案,不再返回含混的”操作已提交”提示。
62
65
  - **自动识别飞书机器人身份**:可通过 App ID / App Secret 自动获取 `botOpenId`,减少手动配置错误。
63
- - **会话记忆块**:把 10 分钟窗口、静默 2 分钟后的碎片聊天整理成 episode summary,让“我要发一个 API key”与后续短消息保持上下文关联。
66
+ - **会话记忆块**:把 10 分钟窗口、静默 2 分钟后的碎片聊天整理成 episode summary,让”我要发一个 API key”与后续短消息保持上下文关联。
64
67
  - **敏感摘要保护**:会话摘要会脱敏疑似 token/API key;原始消息仍保留在本地,方便必要时追溯。
65
68
 
66
69
  当前核心方向是:
@@ -121,7 +124,9 @@ ChatterCatcher 是一个早期 MVP。它已经具备飞书长连接接入、本
121
124
  | 消息入库 | 普通文本消息写入 SQLite;`@` 提问直接回答并跳过入库 |
122
125
  | 会话记忆块 | 默认 10 分钟窗口 + 2 分钟静默期,把碎片聊天整理成可检索 episode summary,并关联原始消息 |
123
126
  | RAG 检索 | SQLite FTS 关键词检索、SQLite embedding 向量检索、episode summary 检索、混合重排、证据来源保留 |
124
- | 问答 | OpenAI-compatible chat completions、证据不足时说不知道、回答带引用 |
127
+ | 时间归一化 | 自动将相对时间(今天/明天/今晚)推导为具体日期,覆盖会话摘要、RAG 问答、Agent 工具循环全部 LLM 路径 |
128
+ | 问答 | OpenAI-compatible chat completions、Agent 多轮工具调用、证据不足时说不知道、回答带引用、工具循环耗尽智能兜底 |
129
+ | 定时任务 | 群内自然语言创建 cron 定时任务(如"每天 9 点总结昨天群聊"),限定当前群聊,支持创建/查看/删除 |
125
130
  | 引用格式 | 展示“谁在什么时候说了什么”,避免暴露 `ou_` / `oc_` 等 opaque id |
126
131
  | 文件知识源 | 支持 txt、md、json、csv、tsv、log、docx、pdf 导入和解析 |
127
132
  | CLI | setup、settings、doctor、gateway、process、index、files、export、restore |
@@ -250,12 +255,14 @@ http://127.0.0.1:3878
250
255
  期望回答类似:
251
256
 
252
257
  ```text
253
- 后天 13:40 [S1]。
258
+ 最近一次编程课是 2026-04-28 13:40 [S1]。
254
259
 
255
260
  引用:
256
- [S1] 群成员在 2026-04-26 13:36 说:“编程课的时间改成了后天13:40”
261
+ [S1] 群成员在 2026-04-26 13:36 说:”编程课的时间改成了后天13:40”
257
262
  ```
258
263
 
264
+ 注意:原文说的是”后天”,但 ChatterCatcher 已根据消息时间戳自动推导为具体日期。
265
+
259
266
  ---
260
267
 
261
268
  ## 常用命令
@@ -275,6 +282,8 @@ http://127.0.0.1:3878
275
282
  | `chattercatcher files jobs` | 查看文件解析任务 |
276
283
  | `chattercatcher export --out <file>` | 导出本地知识库数据,不含密钥 |
277
284
  | `chattercatcher restore <file>` | 从导出文件恢复 |
285
+ | `chattercatcher cron list` | 列出所有定时任务 |
286
+ | `chattercatcher cron run` | 手动触发到期定时任务 |
278
287
 
279
288
  ---
280
289
 
@@ -301,6 +310,28 @@ chattercatcher process episodes
301
310
 
302
311
  ---
303
312
 
313
+ ## 群内定时任务
314
+
315
+ 在群里用自然语言即可创建定时任务。ChatterCatcher 会把自然语言时间描述自动转换为 cron 表达式:
316
+
317
+ ```text
318
+ @小陈 每天 9 点总结昨天群聊
319
+ @小陈 每周一上午 10 点提醒大家报备周末计划
320
+ @小陈 查看定时任务
321
+ @小陈 删除 job-abc123
322
+ ```
323
+
324
+ 定时任务限定在当前群聊内,不能跨群查看或操作其他群的任务。
325
+
326
+ CLI 管理:
327
+
328
+ ```bash
329
+ chattercatcher cron list # 查看所有定时任务
330
+ chattercatcher cron run # 手动触发到期任务
331
+ ```
332
+
333
+ ---
334
+
304
335
  ## 本地数据目录
305
336
 
306
337
  默认数据目录:
@@ -391,6 +422,14 @@ npm install -g chattercatcher@latest
391
422
 
392
423
  摘要层会脱敏疑似 API key、token、cookie、私钥和 URL 凭据;原始消息仍然只保存在本地数据库,用于必要时追溯证据。
393
424
 
425
+ ### ChatterCatcher 能理解"明天""今晚"这种相对时间吗?
426
+
427
+ 可以。ChatterCatcher 会自动识别消息的时间戳,将"今天""明天""今晚""下周三"等相对表述推导为具体日期写入摘要和回答。你不需要在每次提问时手工推算日期。
428
+
429
+ ### 定时任务怎么用?会不会跨群操作?
430
+
431
+ 在群里 @ 机器人说"每天 9 点总结昨天群聊"即可创建定时任务。用"查看定时任务"列出当前群的任务,"删除 xxx"删掉指定任务。定时任务限定在当前群聊,不会跨群查看或操作。
432
+
394
433
  ### Web UI 可以暴露到公网吗?
395
434
 
396
435
  默认不建议。ChatterCatcher 面向家庭隐私数据,默认只监听 `127.0.0.1`。
package/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ import fs14 from "fs/promises";
8
8
  // package.json
9
9
  var package_default = {
10
10
  name: "chattercatcher",
11
- version: "0.1.24",
11
+ version: "0.1.26",
12
12
  description: "\u672C\u5730\u4F18\u5148\u7684\u98DE\u4E66/Lark \u5BB6\u5EAD\u7FA4\u77E5\u8BC6\u5E93\u673A\u5668\u4EBA",
13
13
  type: "module",
14
14
  main: "dist/index.js",
@@ -3773,19 +3773,36 @@ function stripMentions(text, mentions) {
3773
3773
  }
3774
3774
  return result.replace(/@/g, " ").replace(/\s+/g, " ").trim();
3775
3775
  }
3776
- var FEISHU_TOOL_SYSTEM_PROMPT = "\u4F60\u662F\u98DE\u4E66\u7FA4\u804A\u52A9\u624B\u3002\u4F60\u53EF\u4EE5\u5148\u641C\u7D22\u672C\u5730\u77E5\u8BC6\u6765\u56DE\u7B54\u95EE\u9898\uFF1B\u5F53\u7528\u6237\u660E\u786E\u8981\u6C42\u521B\u5EFA\u3001\u67E5\u770B\u6216\u5220\u9664\u7FA4\u6D88\u606F\u5B9A\u65F6\u4EFB\u52A1\u65F6\uFF0C\u4E5F\u53EF\u4EE5\u8C03\u7528\u5B9A\u65F6\u4EFB\u52A1\u5DE5\u5177\u3002\u5B9A\u65F6\u4EFB\u52A1\u5DE5\u5177\u53EA\u7BA1\u7406\u5F53\u524D\u7FA4\u804A\uFF0C\u4E0D\u80FD\u8DE8\u7FA4\u64CD\u4F5C\u3002\u82E5\u7528\u6237\u7528\u81EA\u7136\u8BED\u8A00\u63CF\u8FF0\u65F6\u95F4\uFF0C\u4F60\u9700\u8981\u5148\u5C06\u5176\u8F6C\u6362\u4E3A\u4E94\u5B57\u6BB5 cron \u8868\u8FBE\u5F0F\uFF08\u5206 \u65F6 \u65E5 \u6708 \u5468\uFF09\uFF0C\u518D\u8C03\u7528\u5DE5\u5177\u3002\u5F53\u524D\u65F6\u95F4\u4F1A\u63D0\u4F9B\u7ED9\u4F60\u3002\u68C0\u7D22\u8BC1\u636E\u4E2D\u7684\u65F6\u95F4\u6233\u662F\u6D88\u606F\u88AB\u53D1\u9001\u65F6\u7684\u771F\u5B9E\u65F6\u95F4\u3002\u56DE\u7B54\u65F6\u82E5\u6D89\u53CA\u76F8\u5BF9\u65F6\u95F4\u8868\u8FF0\uFF08\u5982\u6D88\u606F\u4E2D\u8BF4\u201C\u660E\u5929\u201D\u201C\u4ECA\u665A\u201D\uFF09\uFF0C\u5FC5\u987B\u57FA\u4E8E\u8BC1\u636E\u4E2D\u6BCF\u6761\u6D88\u606F\u7684\u65F6\u95F4\u6233\u63A8\u5BFC\u4E3A\u5177\u4F53\u65E5\u671F\uFF0C\u4E0D\u8981\u7167\u642C\u539F\u6587\u7684\u76F8\u5BF9\u8868\u8FF0\u3002\u5BF9\u4E8E\u4E00\u822C\u95EE\u7B54\uFF0C\u5148\u6309\u9700\u8C03\u7528\u641C\u7D22\u5DE5\u5177\uFF0C\u518D\u57FA\u4E8E\u5DE5\u5177\u8FD4\u56DE\u7684\u8BC1\u636E\u76F4\u63A5\u7ED9\u51FA\u6700\u7EC8\u7B54\u6848\uFF1B\u82E5\u5F15\u7528\u4E86\u68C0\u7D22\u7ED3\u679C\uFF0C\u8981\u5728\u7B54\u6848\u91CC\u76F4\u63A5\u5199\u51FA\u5F15\u7528\u5185\u5BB9\u3002\u4E0D\u8981\u58F0\u79F0\u5B8C\u6210\u4E86\u672A\u5B9E\u9645\u8C03\u7528\u7684\u64CD\u4F5C\u3002";
3776
+ var FEISHU_TOOL_SYSTEM_PROMPT = `\u4F60\u662F\u98DE\u4E66\u7FA4\u804A\u52A9\u624B\u3002\u4F60\u53EF\u4EE5\u5148\u641C\u7D22\u672C\u5730\u77E5\u8BC6\u6765\u56DE\u7B54\u95EE\u9898\uFF1B\u5F53\u7528\u6237\u660E\u786E\u8981\u6C42\u521B\u5EFA\u3001\u67E5\u770B\u6216\u5220\u9664\u7FA4\u6D88\u606F\u5B9A\u65F6\u4EFB\u52A1\u65F6\uFF0C\u4E5F\u53EF\u4EE5\u8C03\u7528\u5B9A\u65F6\u4EFB\u52A1\u5DE5\u5177\u3002\u5B9A\u65F6\u4EFB\u52A1\u5DE5\u5177\u53EA\u7BA1\u7406\u5F53\u524D\u7FA4\u804A\uFF0C\u4E0D\u80FD\u8DE8\u7FA4\u64CD\u4F5C\u3002\u82E5\u7528\u6237\u7528\u81EA\u7136\u8BED\u8A00\u63CF\u8FF0\u65F6\u95F4\uFF0C\u4F60\u9700\u8981\u5148\u5C06\u5176\u8F6C\u6362\u4E3A\u4E94\u5B57\u6BB5 cron \u8868\u8FBE\u5F0F\uFF08\u5206 \u65F6 \u65E5 \u6708 \u5468\uFF09\uFF0C\u518D\u8C03\u7528\u5DE5\u5177\u3002\u5F53\u524D\u65F6\u95F4\u4F1A\u63D0\u4F9B\u7ED9\u4F60\u3002\u68C0\u7D22\u8BC1\u636E\u4E2D\u7684\u65F6\u95F4\u6233\u662F\u6D88\u606F\u88AB\u53D1\u9001\u65F6\u7684\u771F\u5B9E\u65F6\u95F4\u3002\u56DE\u7B54\u65F6\u82E5\u6D89\u53CA\u76F8\u5BF9\u65F6\u95F4\u8868\u8FF0\uFF08\u5982\u6D88\u606F\u4E2D\u8BF4\u201D\u660E\u5929\u201D\u201D\u4ECA\u665A\u201D\uFF09\uFF0C\u5FC5\u987B\u57FA\u4E8E\u8BC1\u636E\u4E2D\u6BCF\u6761\u6D88\u606F\u7684\u65F6\u95F4\u6233\u63A8\u5BFC\u4E3A\u5177\u4F53\u65E5\u671F\uFF0C\u4E0D\u8981\u7167\u642C\u539F\u6587\u7684\u76F8\u5BF9\u8868\u8FF0\u3002\u5BF9\u4E8E\u4E00\u822C\u95EE\u7B54\uFF0C\u5148\u6309\u9700\u8C03\u7528\u641C\u7D22\u5DE5\u5177\uFF0C\u518D\u57FA\u4E8E\u5DE5\u5177\u8FD4\u56DE\u7684\u8BC1\u636E\u76F4\u63A5\u7ED9\u51FA\u6700\u7EC8\u7B54\u6848\uFF1B\u82E5\u5F15\u7528\u4E86\u68C0\u7D22\u7ED3\u679C\uFF0C\u8981\u5728\u7B54\u6848\u91CC\u76F4\u63A5\u5199\u51FA\u5F15\u7528\u5185\u5BB9\u3002\u4E0D\u8981\u58F0\u79F0\u5B8C\u6210\u4E86\u672A\u5B9E\u9645\u8C03\u7528\u7684\u64CD\u4F5C\u3002\u91CD\u8981\uFF1A\u4F60\u7684\u56DE\u7B54\u5FC5\u987B\u662F\u9762\u5411\u7FA4\u6210\u5458\u7684\u81EA\u7136\u8BED\u8A00\uFF0C\u7EDD\u5BF9\u4E0D\u80FD\u8F93\u51FA JSON\u3001\u5DE5\u5177\u8C03\u7528\u7EC6\u8282\u6216\u539F\u59CB\u7684\u641C\u7D22\u7ED3\u679C\u683C\u5F0F\u3002\u7528\u6237\u53EA\u5E94\u770B\u5230\u4F60\u6574\u5408\u540E\u7684\u6700\u7EC8\u7B54\u6848\u3002`;
3777
3777
  var DEFAULT_MAX_MODEL_TURNS = 4;
3778
3778
  var DEFAULT_MAX_TOOL_CALLS = 8;
3779
3779
  var FEISHU_TOOL_LOOP_FALLBACK = "\u5B9A\u65F6\u4EFB\u52A1\u64CD\u4F5C\u5DF2\u63D0\u4EA4\uFF0C\u4F46\u6A21\u578B\u6CA1\u6709\u751F\u6210\u6700\u7EC8\u56DE\u590D\u3002";
3780
3780
  var FEISHU_TOOL_LOOP_LIMIT_REACHED = "\u5DE5\u5177\u8C03\u7528\u6B21\u6570\u5DF2\u8FBE\u5230\u4E0A\u9650\uFF0C\u8BF7\u7F29\u5C0F\u8BF7\u6C42\u540E\u91CD\u8BD5\u3002";
3781
3781
  function toToolResultContent(value) {
3782
- return typeof value === "string" ? value : JSON.stringify(value);
3782
+ if (typeof value === "string") return value;
3783
+ return JSON.stringify(value);
3784
+ }
3785
+ function isEvidenceBlockArray(value) {
3786
+ return Array.isArray(value) && value.length > 0 && typeof value[0]?.text === "string";
3787
+ }
3788
+ function formatEvidenceBlocks(blocks) {
3789
+ return blocks.map((block, index2) => {
3790
+ const source = block.source;
3791
+ const sender = source.sender ? `${source.sender} ` : "";
3792
+ const timestamp = source.timestamp ? `(${source.timestamp.slice(0, 19).replace("T", " ")})` : "";
3793
+ const header = `[\u8BC1\u636E${index2 + 1}] ${sender}${timestamp}:`;
3794
+ return `${header}
3795
+ ${block.text}`;
3796
+ }).join("\n\n");
3783
3797
  }
3784
3798
  function toToolErrorContent(message) {
3785
3799
  return JSON.stringify({ ok: false, error: message });
3786
3800
  }
3787
3801
  async function executeFeishuTool(tool, input2) {
3788
3802
  const result = await tool.execute(input2);
3803
+ if (isEvidenceBlockArray(result)) {
3804
+ return formatEvidenceBlocks(result);
3805
+ }
3789
3806
  return toToolResultContent(result);
3790
3807
  }
3791
3808
  async function runFeishuToolLoop(input2) {
@@ -3843,7 +3860,15 @@ async function runFeishuToolLoop(input2) {
3843
3860
  }
3844
3861
  }
3845
3862
  }
3846
- return FEISHU_TOOL_LOOP_FALLBACK;
3863
+ try {
3864
+ const salvageAnswer = await input2.model.complete([
3865
+ ...messages,
3866
+ { role: "system", content: "\u8BF7\u57FA\u4E8E\u4EE5\u4E0A\u6240\u6709\u5DE5\u5177\u8FD4\u56DE\u7684\u4FE1\u606F\uFF0C\u76F4\u63A5\u7ED9\u51FA\u6700\u7EC8\u7B54\u6848\u3002\u4E0D\u8981\u518D\u8C03\u7528\u5DE5\u5177\u3002" }
3867
+ ]);
3868
+ return salvageAnswer || "\u62B1\u6B49\uFF0C\u56DE\u7B54\u751F\u6210\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002";
3869
+ } catch {
3870
+ return "\u62B1\u6B49\uFF0C\u56DE\u7B54\u751F\u6210\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002";
3871
+ }
3847
3872
  }
3848
3873
  function isMentionForBot(mention, config) {
3849
3874
  if (!config.feishu.botOpenId) {